diff --git a/src/hooks/compaction-context-injector/session-prompt-config-resolver.ts b/src/hooks/compaction-context-injector/session-prompt-config-resolver.ts new file mode 100644 index 000000000..8d946ef7e --- /dev/null +++ b/src/hooks/compaction-context-injector/session-prompt-config-resolver.ts @@ -0,0 +1,131 @@ +import { getSessionAgent } from "../../features/claude-code-session-state" +import type { CompactionAgentConfigCheckpoint } from "../../shared/compaction-agent-config-checkpoint" +import { log } from "../../shared/logger" +import { normalizeSDKResponse } from "../../shared/normalize-sdk-response" +import { normalizePromptTools } from "../../shared/prompt-tools" +import { getSessionModel } from "../../shared/session-model-state" +import { getSessionTools } from "../../shared/session-tools-store" + +type SessionMessage = { + info?: { + agent?: string + model?: { + providerID?: string + modelID?: string + } + providerID?: string + modelID?: string + tools?: Record + } +} + +type ResolverContext = { + client: { + session: { + messages: (input: { path: { id: string } }) => Promise + } + } + directory: string +} + +function isCompactionAgent(agent: string | undefined): boolean { + return agent?.trim().toLowerCase() === "compaction" +} + +function resolveModel( + info: SessionMessage["info"], +): CompactionAgentConfigCheckpoint["model"] | undefined { + const providerID = info?.model?.providerID ?? info?.providerID + const modelID = info?.model?.modelID ?? info?.modelID + + if (!providerID || !modelID) { + return undefined + } + + return { providerID, modelID } +} + +export async function resolveSessionPromptConfig( + ctx: ResolverContext, + sessionID: string, +): Promise { + const promptConfig: CompactionAgentConfigCheckpoint = { + agent: getSessionAgent(sessionID), + model: getSessionModel(sessionID), + tools: getSessionTools(sessionID), + } + + try { + const response = await ctx.client.session.messages({ path: { id: sessionID } }) + const messages = normalizeSDKResponse(response, [] as SessionMessage[], { + preferResponseOnMissingData: true, + }) + + for (let index = messages.length - 1; index >= 0; index--) { + const info = messages[index].info + + if (!promptConfig.agent && info?.agent && !isCompactionAgent(info.agent)) { + promptConfig.agent = info.agent + } + + if (!promptConfig.model) { + const model = resolveModel(info) + if (model) { + promptConfig.model = model + } + } + + if (!promptConfig.tools) { + const tools = normalizePromptTools(info?.tools) + if (tools) { + promptConfig.tools = tools + } + } + + if (promptConfig.agent && promptConfig.model && promptConfig.tools) { + break + } + } + } catch (error) { + log("[compaction-context-injector] Failed to resolve prompt config from messages", { + sessionID, + directory: ctx.directory, + error: String(error), + }) + } + + return promptConfig +} + +export async function resolveLatestSessionPromptConfig( + ctx: ResolverContext, + sessionID: string, +): Promise { + try { + const response = await ctx.client.session.messages({ path: { id: sessionID } }) + const messages = normalizeSDKResponse(response, [] as SessionMessage[], { + preferResponseOnMissingData: true, + }) + const latestInfo = messages.at(-1)?.info + + if (!latestInfo) { + return {} + } + + const model = resolveModel(latestInfo) + const tools = normalizePromptTools(latestInfo.tools) + + return { + ...(latestInfo.agent ? { agent: latestInfo.agent } : {}), + ...(model ? { model } : {}), + ...(tools ? { tools } : {}), + } + } catch (error) { + log("[compaction-context-injector] Failed to resolve latest prompt config", { + sessionID, + directory: ctx.directory, + error: String(error), + }) + return {} + } +}