diff --git a/.changeset/empty-hoops-tie.md b/.changeset/empty-hoops-tie.md new file mode 100644 index 0000000..42f15c3 --- /dev/null +++ b/.changeset/empty-hoops-tie.md @@ -0,0 +1,5 @@ +--- +"opencode-adaptive-thinking": patch +--- + +Preserve the active model when synthetic prompts adjust reasoning effort and when temporary overrides reset after a session becomes idle. This prevents OpenCode from falling back to the agent/provider default model instead of the model that was selected when the reasoning change was initiated. diff --git a/src/index.test.ts b/src/index.test.ts index 7c13359..991f46a 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -117,8 +117,8 @@ describe("AdaptiveThinkingPlugin", () => { expect(system[0]).toContain("set_reasoning_effort"); }); - test("preserves the calling agent when setting reasoning effort", async () => { - const sessionID = "preserve-agent"; + test("preserves the calling agent and model when setting reasoning effort", async () => { + const sessionID = "preserve-agent-model"; const { client, toolContext } = createClient(sessionID, [createMessage("medium")]); toolContext.agent = "custom-worker"; const plugin = await AdaptiveThinkingPlugin({ client } as never); @@ -127,7 +127,11 @@ describe("AdaptiveThinkingPlugin", () => { expect(client.session.promptAsync).toHaveBeenCalledWith( expect.objectContaining({ - body: expect.objectContaining({ agent: "custom-worker", variant: "high" }), + body: expect.objectContaining({ + agent: "custom-worker", + model: { providerID: "provider", modelID: "model" }, + variant: "high", + }), }), ); }); @@ -314,6 +318,7 @@ describe("AdaptiveThinkingPlugin", () => { expect(secondPrompt).toMatchObject({ body: { agent: "agent", + model: { providerID: "provider", modelID: "model" }, variant: "medium", parts: [{ ignored: true, synthetic: true }], }, diff --git a/src/index.ts b/src/index.ts index 48ae69b..3b21c89 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ type SessionState = { persistedVariant?: string; temporaryResetVariant?: string; temporaryResetAgent?: string; + temporaryResetModel?: { providerID: string; modelID: string }; }; class SessionStateCache { @@ -45,6 +46,7 @@ const state = new SessionStateCache(maxSessionStateSize); type PromptAsyncOptions = Parameters[0]; type PromptAsyncBody = NonNullable & { variant: string }; +type ModelInfo = { providerID: string; modelID: string }; export const AdaptiveThinkingPlugin: Plugin = async ({ client }, options) => { const configParseResult = ConfigSchema.safeParse(options); @@ -85,6 +87,7 @@ export const AdaptiveThinkingPlugin: Plugin = async ({ client }, options) => { variant: string, text: string, agent: string, + model?: ModelInfo, ignored = true, ) => { const body: PromptAsyncBody = { @@ -101,6 +104,10 @@ export const AdaptiveThinkingPlugin: Plugin = async ({ client }, options) => { variant, }; + if (model) { + body.model = model; + } + return client.session.promptAsync({ path: { id: sessionID }, body, @@ -124,16 +131,7 @@ export const AdaptiveThinkingPlugin: Plugin = async ({ client }, options) => { }); return []; } - let modelInfo: { providerID: string; modelID: string } | undefined; - const messages = messagesResponse.data as Array<{ info: Message; parts: Array }>; - for (let i = messages.length - 1; i >= 0; i--) { - const message = messages[i]!; - if ("model" in message.info) { - modelInfo = message.info.model; - break; - } - } - + const modelInfo = resolveLatestModelInfo(messagesResponse.data); if (!modelInfo) return []; const providers = await client.provider.list(); @@ -155,6 +153,22 @@ export const AdaptiveThinkingPlugin: Plugin = async ({ client }, options) => { return getModelVariants(provider.models[modelInfo.modelID] as Model | undefined); }; + const resolveLatestModelInfo = ( + data: unknown, + ): { providerID: string; modelID: string } | undefined => { + const messages = data as Array<{ info: Message; parts: Array }>; + for (let i = messages.length - 1; i >= 0; i--) { + const message = messages[i]!; + if ("model" in message.info) { + const { providerID, modelID } = message.info.model; + if (providerID && modelID) { + return { providerID, modelID }; + } + } + } + return; + }; + const resolveCurrentVariant = async (sessionID: string): Promise => { const messagesResponse = await client.session.messages({ path: { id: sessionID }, @@ -228,12 +242,16 @@ export const AdaptiveThinkingPlugin: Plugin = async ({ client }, options) => { const resetVariant = persist ? undefined : (sessionState?.persistedVariant ?? (await resolveCurrentVariant(sessionID))); + const currentModel = resolveLatestModelInfo( + (await client.session.messages({ path: { id: sessionID } })).data, + ); const promptResponse = await sendVariantPrompt( sessionID, level, `Reasoning effort set to ${level}`, agent, + currentModel, ); if (promptResponse.error) { return `Failed to set reasoning effort: ${JSON.stringify(promptResponse.error.data)}`; @@ -245,12 +263,19 @@ export const AdaptiveThinkingPlugin: Plugin = async ({ client }, options) => { entry.persistedVariant = level; delete entry.temporaryResetVariant; delete entry.temporaryResetAgent; + delete entry.temporaryResetModel; } else if (resetVariant && resetVariant !== level) { entry.temporaryResetVariant = resetVariant; entry.temporaryResetAgent = agent; + if (currentModel) { + entry.temporaryResetModel = currentModel; + } else { + delete entry.temporaryResetModel; + } } else { delete entry.temporaryResetVariant; delete entry.temporaryResetAgent; + delete entry.temporaryResetModel; } }); @@ -264,6 +289,7 @@ export const AdaptiveThinkingPlugin: Plugin = async ({ client }, options) => { const sessionState = state.get(sessionID); const resetVariant = sessionState?.temporaryResetVariant; const resetAgent = sessionState?.temporaryResetAgent; + const resetModel = sessionState?.temporaryResetModel; if (!resetVariant || !resetAgent) return; const promptResponse = await sendVariantPrompt( @@ -271,6 +297,7 @@ export const AdaptiveThinkingPlugin: Plugin = async ({ client }, options) => { resetVariant, `Reasoning effort reset to ${resetVariant}.`, resetAgent, + resetModel, ); if (promptResponse.error) { client.app.log({ @@ -287,6 +314,7 @@ export const AdaptiveThinkingPlugin: Plugin = async ({ client }, options) => { entry.currentVariant = resetVariant; delete entry.temporaryResetVariant; delete entry.temporaryResetAgent; + delete entry.temporaryResetModel; }); return; }