Files
hapi/shared/src/rpcMethods.ts
T
Junmo KimandGitHub cd0b8e3589 fix(claude): stop locking Claude sessions to three hard-coded model aliases (#1948)
* feat(shared): derive Claude model choices from the CLI catalog

The choices are the rows of the claude CLI's own /model picker, in its
order and with its display names. The stored value is what
claude --model receives: an alias is resolved again each time the
session's claude process starts, a full id stays on its model, so
following and staying need no new field.

Effort support is read per model so a model switch can drop an effort
level the next model does not offer.

* feat(cli): discover Claude models from the CLI's initialize response

A machine RPC starts claude in stream-json mode, sends only an
initialize control request and reads the models it reports, so no turn
runs and no transcript is written.

claude --print runs a directory's project hooks and .mcp.json servers
without a trust prompt, so the probe starts from the home directory
with user settings only, no MCP servers and hooks disabled.

Successful catalogs are cached per claude executable for five minutes
and concurrent requests share one probe; failures are not cached.

* feat(hub): expose the Claude model catalog per machine

GET /machines/:id/claude-models forwards to the runner's
listClaudeModels RPC. A runner that predates the handler answers with
the stable rpc_target_missing code so clients can keep their built-in
list.

* feat(web): offer the machine's Claude models in the model pickers

The composer and the create-session form list the rows the machine's
claude CLI shows in its own /model picker, as it shows them. Built-in
presets the catalog does not list remain selectable after it.

Until the catalog arrives, or on a runner without discovery, the
built-in presets are shown as before. A saved Claude model is no longer
reset to Default when the built-in list lacks it; the create-session
form keeps it and marks it as no longer listed when the catalog
dropped it.

* feat(hub,web): keep Claude effort within what the selected model offers

For Claude sessions the model endpoint accepts an optional effort, so a
model switch and the effort it keeps reach the session as one config
change. Other flavors are refused: Pi, for one, commits the model
before its thinking level. When the machine's catalog says the next
Claude model does not offer the current effort, the composer clears it
in that same request. The create-session form drops such an effort
however the model was set, including a restored preference or draft.

Effort choices in both places narrow to the selected model's levels;
without a catalog every level stays available as before.

* fix(web): retry Claude model discovery after a failed probe

The machine answers a failed probe with success:false over HTTP 200.
The query cached that as an answer for its stale time, so a user who
signed in and reopened a picker within a minute still saw only the
built-in models. A failed probe is now a query error, which is not
kept, and the next picker to mount asks the machine again.

* fix(web): list only the Claude CLI's models once its catalog is known

The built-in presets were kept after the catalog so that a partial list
could not hide Opus. The catalog is the CLI's own /model list, though,
not a partial one, and the presets added entries the CLI does not show
(Sonnet 1M, Opus 1M, Fable 1M). With a catalog the list is now exactly
its rows; a saved model it does not list stays as the current model,
and New Session marks it as no longer listed. Without a catalog the
built-in presets are shown as before.

* fix(shared,web): list earlier Claude versions under Other models

Claude Code's model sheet shows each family's newest model first and
every earlier version under an "Other models" heading. The CLI's
catalog lists models in that order but does not say where the split
is, so the first row of each family is taken as its newest and later
rows of the same family as earlier versions.

The composer's model list and New Session's model select now show the
same two groups, keeping the CLI order within each. Nothing is folded:
every row stays one click away, as it does in Claude Code.

* test(web): pin that Claude 1M variants come from the CLI catalog

Claude Code 2.1.105, 2.1.200, 2.1.240 and 2.1.274 list a 1M choice as
its own catalog row (opus[1m], claude-fable-5[1m]); 2.1.303 and 2.1.310
list none, and on 2.1.310 opus and sonnet run with a 1M context window.
The catalog-only list therefore offers a 1M variant exactly when the CLI
does. Pin that and record why the built-in [1m] presets are not appended.
2026-10-28 12:10:36 +08:00

81 lines
3.3 KiB
TypeScript

export const RPC_METHODS = {
Permission: 'permission',
Abort: 'abort',
Switch: 'switch',
SetSessionConfig: 'set-session-config',
KillSession: 'killSession',
HandoffLocal: 'handoff-local',
SpawnHappySession: 'spawn-happy-session',
StopSession: 'stop-session',
StopRunner: 'stop-runner',
ListMachineDirectory: 'list-directory',
PathExists: 'path-exists',
AgentAvailability: 'agent-availability',
CursorChatStoreStatus: 'cursor-chat-store-status',
GitStatus: 'git-status',
GitDiffNumstat: 'git-diff-numstat',
GitDiffFile: 'git-diff-file',
ReadFile: 'readFile',
ReadGeneratedImage: 'readGeneratedImage',
WriteFile: 'writeFile',
ListDirectory: 'listDirectory',
StatFiles: 'statFiles',
GetDirectoryTree: 'getDirectoryTree',
UploadFile: 'uploadFile',
DeleteUpload: 'deleteUpload',
WorkspaceReadFile: 'workspace-read-file',
WorkspaceListDirectory: 'workspace-list-directory',
WorkspaceStatFiles: 'workspace-stat-files',
WorkspaceGitStatus: 'workspace-git-status',
WorkspaceGitDiffNumstat: 'workspace-git-diff-numstat',
WorkspaceGitDiffFile: 'workspace-git-diff-file',
WorkspaceRipgrep: 'workspace-ripgrep',
Ripgrep: 'ripgrep',
Difftastic: 'difftastic',
Bash: 'bash',
ListSlashCommands: 'listSlashCommands',
ListSkills: 'listSkills',
ListCodexModels: 'listCodexModels',
ListPiModelsForMachine: 'listPiModelsForMachine',
ListCodexSessions: 'listCodexSessions',
ArchiveCodexSession: 'archiveCodexSession',
ListCursorModels: 'listCursorModels',
ListPiModels: 'listPiModels',
ListPiSessions: 'listPiSessions',
ListOpencodeModels: 'listOpencodeModels',
ListOpencodeModelVariants: 'listOpencodeModelVariants',
ListOpencodeModelsForCwd: 'listOpencodeModelsForCwd',
ListGrokModelsForCwd: 'listGrokModelsForCwd',
ListGrokModels: 'listGrokModels',
ListGrokReasoningEffortOptions: 'listGrokReasoningEffortOptions',
ListCopilotModelsForCwd: 'listCopilotModelsForCwd',
ListCopilotModels: 'listCopilotModels',
ListKimiModelsForCwd: 'listKimiModelsForCwd',
ListKimiModels: 'listKimiModels',
ListDshModels: 'listDshModels',
ListDshModelsForMachine: 'listDshModelsForMachine',
ListOpencodeReasoningEffortOptions: 'listOpencodeReasoningEffortOptions',
ListAgyModels: 'listAgyModels',
ListClaudeModels: 'listClaudeModels',
/** Deliver one queued message into the active Pi turn (native steer). */
SteerQueuedMessage: 'steer-queued-message',
ForkConversation: 'fork-conversation',
RewindConversation: 'rewind-conversation',
ClearConversation: 'clear-conversation',
ImplementCodexPlan: 'implement-codex-plan',
} as const
export const RPC_TARGET_MISSING_ERROR_CODE = 'rpc_target_missing' as const
/**
* Thrown by a permission handler's handleMissingPendingResponse when a
* Permission RPC response arrives for a request the CLI no longer has
* pending (already answered, or canceled on the agent side). Shared so the
* hub can match on the specific message rather than treating any error on
* this RPC method as "not found" — see RpcGateway.approvePermission/
* denyPermission.
*/
export const PERMISSION_REQUEST_NOT_FOUND_MESSAGE = 'Permission request not found or already resolved' as const
export type RpcMethod = typeof RPC_METHODS[keyof typeof RPC_METHODS]