diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index 4a40281567..8d404114b7 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -76,6 +76,27 @@ export async function sendFollowupDraft(input: FollowupSendInput) { return true } + const select = async () => { + const session = input.session() + if (session?.agent !== input.draft.agent) { + await input.api.switchAgent({ sessionID: input.draft.sessionID, agent: input.draft.agent }) + } + if ( + session?.model?.providerID === input.draft.model.providerID && + session.model.id === input.draft.model.modelID && + (session.model.variant ?? "default") === (input.draft.variant ?? "default") + ) + return + await input.api.switchModel({ + sessionID: input.draft.sessionID, + model: { + id: input.draft.model.modelID, + providerID: input.draft.model.providerID, + variant: input.draft.variant, + }, + }) + } + const [head, ...tail] = text.split(" ") const cmd = head?.startsWith("/") ? head.slice(1) : undefined if (cmd && input.sync.data.command.find((item) => item.name === cmd)) { @@ -86,18 +107,13 @@ export async function sendFollowupDraft(input: FollowupSendInput) { return false } + await select() const messageID = Identifier.ascending("message") await input.api.command({ sessionID: input.draft.sessionID, id: messageID, command: cmd, arguments: tail.join(" "), - agent: input.draft.agent, - model: { - id: input.draft.model.modelID, - providerID: input.draft.model.providerID, - variant: input.draft.variant, - }, files: await Promise.all( images.map(async (attachment) => ({ uri: await blobDataUrl(attachment.blob, attachment.mime), @@ -167,24 +183,7 @@ export async function sendFollowupDraft(input: FollowupSendInput) { return false } - const session = input.session() - if (session?.agent !== input.draft.agent) { - await input.api.switchAgent({ sessionID: input.draft.sessionID, agent: input.draft.agent }) - } - if ( - session?.model?.providerID !== input.draft.model.providerID || - session.model.id !== input.draft.model.modelID || - (session.model.variant ?? "default") !== (input.draft.variant ?? "default") - ) { - await input.api.switchModel({ - sessionID: input.draft.sessionID, - model: { - id: input.draft.model.modelID, - providerID: input.draft.model.providerID, - variant: input.draft.variant, - }, - }) - } + await select() await input.api.prompt({ sessionID: input.draft.sessionID, @@ -524,14 +523,22 @@ export function createPromptSubmit(input: PromptSubmitInput) { clearInput() const messageID = Identifier.ascending("message") serverSync().session.set("session_status", session.id, { type: "busy" }) - sdk() - .api.session.command({ + void (async () => { + if (session.agent !== agent) await sdk().api.session.switchAgent({ sessionID: session.id, agent }) + if ( + session.model?.providerID !== model.providerID || + session.model.id !== model.modelID || + (session.model.variant ?? "default") !== (variant ?? "default") + ) + await sdk().api.session.switchModel({ + sessionID: session.id, + model: { id: model.modelID, providerID: model.providerID, variant }, + }) + await sdk().api.session.command({ sessionID: session.id, id: messageID, command: commandName, arguments: args.join(" "), - agent, - model: { id: model.modelID, providerID: model.providerID, variant }, files: await Promise.all( images.map(async (attachment) => ({ uri: await blobDataUrl(attachment.blob, attachment.mime), @@ -539,14 +546,14 @@ export function createPromptSubmit(input: PromptSubmitInput) { })), ), }) - .catch((err) => { - serverSync().session.set("session_status", session.id, { type: "idle" }) - showToast({ - title: language.t("prompt.toast.commandSendFailed.title"), - description: formatServerError(err, language.t, language.t("common.requestFailed")), - }) - restoreInput() + })().catch((err) => { + serverSync().session.set("session_status", session.id, { type: "idle" }) + showToast({ + title: language.t("prompt.toast.commandSendFailed.title"), + description: formatServerError(err, language.t, language.t("common.requestFailed")), }) + restoreInput() + }) return } } diff --git a/packages/client/src/effect/api/api.ts b/packages/client/src/effect/api/api.ts index 52b848b2ea..733e516b00 100644 --- a/packages/client/src/effect/api/api.ts +++ b/packages/client/src/effect/api/api.ts @@ -193,8 +193,6 @@ export type Endpoint5_13Input = { readonly id?: SessionMessage.ID | undefined readonly command: string readonly arguments?: string | undefined - readonly agent?: Agent.ID | undefined - readonly model?: Model.Ref | undefined readonly files?: ReadonlyArray | undefined readonly agents?: ReadonlyArray | undefined readonly skills?: ReadonlyArray | undefined diff --git a/packages/client/src/effect/generated/client.ts b/packages/client/src/effect/generated/client.ts index cc334c9709..cad50d36b0 100644 --- a/packages/client/src/effect/generated/client.ts +++ b/packages/client/src/effect/generated/client.ts @@ -431,8 +431,6 @@ const Endpoint5_13 = (raw: RawClient["server.session"]) => (input: Endpoint5_13I id: input["id"], command: input["command"], arguments: input["arguments"], - agent: input["agent"], - model: input["model"], files: input["files"], agents: input["agents"], skills: input["skills"], diff --git a/packages/client/src/promise/generated/client.ts b/packages/client/src/promise/generated/client.ts index 8dbcd8c966..52b2749ceb 100644 --- a/packages/client/src/promise/generated/client.ts +++ b/packages/client/src/promise/generated/client.ts @@ -635,8 +635,6 @@ export function make(options: ClientOptions) { id: input["id"], command: input["command"], arguments: input["arguments"], - agent: input["agent"], - model: input["model"], files: input["files"], agents: input["agents"], skills: input["skills"], diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index 747f0492e0..186a80eb2b 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -3485,8 +3485,6 @@ export type SessionCommandInput = { readonly id?: string | null readonly command: string readonly arguments?: string | null - readonly agent?: string | null - readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null readonly files?: ReadonlyArray<{ readonly uri: string readonly name?: string @@ -3508,8 +3506,6 @@ export type SessionCommandInput = { readonly id?: string | null readonly command: string readonly arguments?: string | null - readonly agent?: string | null - readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null readonly files?: ReadonlyArray<{ readonly uri: string readonly name?: string @@ -3531,8 +3527,6 @@ export type SessionCommandInput = { readonly id?: string | null readonly command: string readonly arguments?: string | null - readonly agent?: string | null - readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null readonly files?: ReadonlyArray<{ readonly uri: string readonly name?: string @@ -3550,58 +3544,10 @@ export type SessionCommandInput = { readonly delivery?: "steer" | "queue" | null readonly resume?: boolean | null }["arguments"] - readonly agent?: { - readonly id?: string | null - readonly command: string - readonly arguments?: string | null - readonly agent?: string | null - readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null - readonly files?: ReadonlyArray<{ - readonly uri: string - readonly name?: string - readonly description?: string - readonly mention?: { readonly start: number; readonly end: number; readonly text: string } - }> - readonly agents?: ReadonlyArray<{ - readonly name: string - readonly mention?: { readonly start: number; readonly end: number; readonly text: string } - }> - readonly skills?: ReadonlyArray<{ - readonly id: string - readonly mention?: { readonly start: number; readonly end: number; readonly text: string } - }> - readonly delivery?: "steer" | "queue" | null - readonly resume?: boolean | null - }["agent"] - readonly model?: { - readonly id?: string | null - readonly command: string - readonly arguments?: string | null - readonly agent?: string | null - readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null - readonly files?: ReadonlyArray<{ - readonly uri: string - readonly name?: string - readonly description?: string - readonly mention?: { readonly start: number; readonly end: number; readonly text: string } - }> - readonly agents?: ReadonlyArray<{ - readonly name: string - readonly mention?: { readonly start: number; readonly end: number; readonly text: string } - }> - readonly skills?: ReadonlyArray<{ - readonly id: string - readonly mention?: { readonly start: number; readonly end: number; readonly text: string } - }> - readonly delivery?: "steer" | "queue" | null - readonly resume?: boolean | null - }["model"] readonly files?: { readonly id?: string | null readonly command: string readonly arguments?: string | null - readonly agent?: string | null - readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null readonly files?: ReadonlyArray<{ readonly uri: string readonly name?: string @@ -3623,8 +3569,6 @@ export type SessionCommandInput = { readonly id?: string | null readonly command: string readonly arguments?: string | null - readonly agent?: string | null - readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null readonly files?: ReadonlyArray<{ readonly uri: string readonly name?: string @@ -3646,8 +3590,6 @@ export type SessionCommandInput = { readonly id?: string | null readonly command: string readonly arguments?: string | null - readonly agent?: string | null - readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null readonly files?: ReadonlyArray<{ readonly uri: string readonly name?: string @@ -3669,8 +3611,6 @@ export type SessionCommandInput = { readonly id?: string | null readonly command: string readonly arguments?: string | null - readonly agent?: string | null - readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null readonly files?: ReadonlyArray<{ readonly uri: string readonly name?: string @@ -3692,8 +3632,6 @@ export type SessionCommandInput = { readonly id?: string | null readonly command: string readonly arguments?: string | null - readonly agent?: string | null - readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null readonly files?: ReadonlyArray<{ readonly uri: string readonly name?: string diff --git a/packages/core/src/plugin/promise.ts b/packages/core/src/plugin/promise.ts index d256fb8964..beedb00a12 100644 --- a/packages/core/src/plugin/promise.ts +++ b/packages/core/src/plugin/promise.ts @@ -310,8 +310,6 @@ export function fromPromise(plugin: Plugin) { ...input, sessionID: Session.ID.make(input.sessionID), id: input.id == null ? undefined : SessionMessage.ID.make(input.id), - agent: input.agent == null ? undefined : Agent.ID.make(input.agent), - model: input.model == null ? undefined : model(input.model), skills: input.skills?.map((skill) => ({ ...skill, id: Skill.ID.make(skill.id) })), arguments: input.arguments ?? undefined, delivery: input.delivery ?? undefined, diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index fb49b19cb8..3d7ab84111 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -233,8 +233,6 @@ export interface Interface { sessionID: SessionSchema.ID command: string arguments?: string - agent?: Agent.ID - model?: Model.Ref files?: PromptInput.Prompt["files"] agents?: PromptInput.Prompt["agents"] skills?: PromptInput.Prompt["skills"] @@ -625,13 +623,13 @@ const layer = Layer.effect( const evaluated = yield* commands.evaluate({ name: input.command, arguments: input.arguments }) // TODO(v2 commands): decide whether command-level subtask/background execution belongs in v2 commands. - const agent = command.agent ?? input.agent + const agent = command.agent const commandAgent = yield* Effect.gen(function* () { if (!command.agent) return undefined const agents = yield* Agent.Service.pipe(Effect.provide(locations.get(session.location))) return yield* agents.get(Agent.ID.make(command.agent)) }) - const model = command.model ?? commandAgent?.model ?? input.model + const model = command.model ?? commandAgent?.model if (agent !== undefined && session.agent !== Agent.ID.make(agent)) yield* result.switchAgent({ sessionID: input.sessionID, agent: Agent.ID.make(agent) }) if (model !== undefined) yield* result.switchModel({ sessionID: input.sessionID, model }) diff --git a/packages/protocol/src/groups/session.ts b/packages/protocol/src/groups/session.ts index 543442039f..8799e06fb4 100644 --- a/packages/protocol/src/groups/session.ts +++ b/packages/protocol/src/groups/session.ts @@ -341,8 +341,6 @@ export const makeSessionGroup = (sessionLo id: SessionMessage.ID.pipe(Schema.optional), command: Schema.String, arguments: Schema.String.pipe(Schema.optional), - agent: Agent.ID.pipe(Schema.optional), - model: Model.Ref.pipe(Schema.optional), files: PromptInput.Prompt.fields.files, agents: PromptInput.Prompt.fields.agents, skills: PromptInput.Prompt.fields.skills, diff --git a/packages/server/src/handlers/session.ts b/packages/server/src/handlers/session.ts index 8691e480c1..bc7f654f02 100644 --- a/packages/server/src/handlers/session.ts +++ b/packages/server/src/handlers/session.ts @@ -355,8 +355,6 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl id: ctx.payload.id, command: ctx.payload.command, arguments: ctx.payload.arguments, - agent: ctx.payload.agent, - model: ctx.payload.model, files: ctx.payload.files, agents: ctx.payload.agents, skills: ctx.payload.skills, diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index 4303432db1..b58c25d250 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -1149,15 +1149,23 @@ export function Prompt(props: PromptProps) { } else if (slashHead && isCommand) { move.startSubmit() const model = { providerID: selection.providerID, id: selection.modelID, variant } + if (session?.agent !== agent.id) await client.api.session.switchAgent({ sessionID, agent: agent.id }) const cancelCommit = local.model.trackSessionCommit(sessionID, model) + if ( + session?.model?.providerID !== model.providerID || + session.model.id !== model.id || + (session.model.variant ?? "default") !== (model.variant ?? "default") + ) + await client.api.session.switchModel({ sessionID, model }).catch((error) => { + cancelCommit() + throw error + }) void client.api.session .command({ sessionID, command: slashHead.name, arguments: slashHead.arguments, - agent: agent.id, - model, files: store.prompt.files, agents: store.prompt.agents, skills: store.prompt.skills?.length ? store.prompt.skills : undefined, diff --git a/packages/tui/src/mini/stream-v2.transport.ts b/packages/tui/src/mini/stream-v2.transport.ts index 438993cf54..b8d4f2602e 100644 --- a/packages/tui/src/mini/stream-v2.transport.ts +++ b/packages/tui/src/mini/stream-v2.transport.ts @@ -1653,8 +1653,6 @@ export async function createSessionTransport(input: StreamInput): Promise { id: "msg_cmd", command: "deploy", arguments: "prod", - agent: "build", - model: { providerID: "test", id: "model" }, files: [ { uri: "file:///tmp/context.txt", name: "context.txt" }, { @@ -2868,9 +2866,11 @@ describe("V2 mini transport", () => { skills: [{ id: "api-design", mention: { start: 13, end: 24, text: "/api-design" } }], delivery: "steer", }) - // Selection rides the command payload; no separate client-side switch. - expect(client.session.switchAgent).not.toHaveBeenCalled() - expect(client.session.switchModel).not.toHaveBeenCalled() + expect(client.session.switchAgent).toHaveBeenCalledWith({ sessionID: "ses_1", agent: "build" }, expect.anything()) + expect(client.session.switchModel).toHaveBeenCalledWith( + { sessionID: "ses_1", model: { providerID: "test", id: "model" } }, + expect.anything(), + ) await transport.close() })