fix(hooks): extract model from assistant messages with flat modelID/providerID
OpenCode API returns different structures for user vs assistant messages:
- User: info.model = { providerID, modelID } (nested)
- Assistant: info.modelID, info.providerID (flat top-level)
Previous code only checked nested format, causing model info loss when
continuation hooks fired after assistant messages.
Files modified:
- todo-continuation-enforcer.ts
- ralph-loop/index.ts
- sisyphus-task/tools.ts
- background-agent/manager.ts
Added test for assistant message model extraction.
This commit is contained in:
parent
8d545723dc
commit
d00c2e7439
@ -826,13 +826,13 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
|||||||
try {
|
try {
|
||||||
const messagesResp = await this.client.session.messages({ path: { id: task.parentSessionID } })
|
const messagesResp = await this.client.session.messages({ path: { id: task.parentSessionID } })
|
||||||
const messages = (messagesResp.data ?? []) as Array<{
|
const messages = (messagesResp.data ?? []) as Array<{
|
||||||
info?: { agent?: string; model?: { providerID: string; modelID: string } }
|
info?: { agent?: string; model?: { providerID: string; modelID: string }; modelID?: string; providerID?: string }
|
||||||
}>
|
}>
|
||||||
for (let i = messages.length - 1; i >= 0; i--) {
|
for (let i = messages.length - 1; i >= 0; i--) {
|
||||||
const info = messages[i].info
|
const info = messages[i].info
|
||||||
if (info?.agent || info?.model) {
|
if (info?.agent || info?.model || (info?.modelID && info?.providerID)) {
|
||||||
agent = info.agent ?? task.parentAgent
|
agent = info.agent ?? task.parentAgent
|
||||||
model = info.model
|
model = info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -322,13 +322,13 @@ export function createRalphLoopHook(
|
|||||||
try {
|
try {
|
||||||
const messagesResp = await ctx.client.session.messages({ path: { id: sessionID } })
|
const messagesResp = await ctx.client.session.messages({ path: { id: sessionID } })
|
||||||
const messages = (messagesResp.data ?? []) as Array<{
|
const messages = (messagesResp.data ?? []) as Array<{
|
||||||
info?: { agent?: string; model?: { providerID: string; modelID: string } }
|
info?: { agent?: string; model?: { providerID: string; modelID: string }; modelID?: string; providerID?: string }
|
||||||
}>
|
}>
|
||||||
for (let i = messages.length - 1; i >= 0; i--) {
|
for (let i = messages.length - 1; i >= 0; i--) {
|
||||||
const info = messages[i].info
|
const info = messages[i].info
|
||||||
if (info?.agent || info?.model) {
|
if (info?.agent || info?.model || (info?.modelID && info?.providerID)) {
|
||||||
agent = info.agent
|
agent = info.agent
|
||||||
model = info.model
|
model = info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -829,4 +829,50 @@ describe("todo-continuation-enforcer", () => {
|
|||||||
expect(promptCalls[0].text).toContain("TODO CONTINUATION")
|
expect(promptCalls[0].text).toContain("TODO CONTINUATION")
|
||||||
expect("model" in promptCalls[0]).toBe(true)
|
expect("model" in promptCalls[0]).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("should extract model from assistant message with flat modelID/providerID", async () => {
|
||||||
|
// #given - session with assistant message that has flat modelID/providerID (OpenCode API format)
|
||||||
|
const sessionID = "main-assistant-model"
|
||||||
|
setMainSession(sessionID)
|
||||||
|
|
||||||
|
// OpenCode returns assistant messages with flat modelID/providerID, not nested model object
|
||||||
|
const mockMessagesWithAssistant = [
|
||||||
|
{ info: { id: "msg-1", role: "user", agent: "Sisyphus", model: { providerID: "openai", modelID: "gpt-5.2" } } },
|
||||||
|
{ info: { id: "msg-2", role: "assistant", agent: "Sisyphus", modelID: "gpt-5.2", providerID: "openai" } },
|
||||||
|
]
|
||||||
|
|
||||||
|
const mockInput = {
|
||||||
|
client: {
|
||||||
|
session: {
|
||||||
|
todo: async () => ({
|
||||||
|
data: [{ id: "1", content: "Task 1", status: "pending", priority: "high" }],
|
||||||
|
}),
|
||||||
|
messages: async () => ({ data: mockMessagesWithAssistant }),
|
||||||
|
prompt: async (opts: any) => {
|
||||||
|
promptCalls.push({
|
||||||
|
sessionID: opts.path.id,
|
||||||
|
agent: opts.body.agent,
|
||||||
|
model: opts.body.model,
|
||||||
|
text: opts.body.parts[0].text,
|
||||||
|
})
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tui: { showToast: async () => ({}) },
|
||||||
|
},
|
||||||
|
directory: "/tmp/test",
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const hook = createTodoContinuationEnforcer(mockInput, {
|
||||||
|
backgroundManager: createMockBackgroundManager(false),
|
||||||
|
})
|
||||||
|
|
||||||
|
// #when - session goes idle
|
||||||
|
await hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
|
||||||
|
await new Promise(r => setTimeout(r, 2500))
|
||||||
|
|
||||||
|
// #then - model should be extracted from assistant message's flat modelID/providerID
|
||||||
|
expect(promptCalls.length).toBe(1)
|
||||||
|
expect(promptCalls[0].model).toEqual({ providerID: "openai", modelID: "gpt-5.2" })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -381,15 +381,17 @@ export function createTodoContinuationEnforcer(
|
|||||||
info?: {
|
info?: {
|
||||||
agent?: string
|
agent?: string
|
||||||
model?: { providerID: string; modelID: string }
|
model?: { providerID: string; modelID: string }
|
||||||
|
modelID?: string
|
||||||
|
providerID?: string
|
||||||
tools?: Record<string, ToolPermission>
|
tools?: Record<string, ToolPermission>
|
||||||
}
|
}
|
||||||
}>
|
}>
|
||||||
for (let i = messages.length - 1; i >= 0; i--) {
|
for (let i = messages.length - 1; i >= 0; i--) {
|
||||||
const info = messages[i].info
|
const info = messages[i].info
|
||||||
if (info?.agent || info?.model) {
|
if (info?.agent || info?.model || (info?.modelID && info?.providerID)) {
|
||||||
resolvedInfo = {
|
resolvedInfo = {
|
||||||
agent: info.agent,
|
agent: info.agent,
|
||||||
model: info.model,
|
model: info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined),
|
||||||
tools: info.tools,
|
tools: info.tools,
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|||||||
@ -297,13 +297,13 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
|
|||||||
try {
|
try {
|
||||||
const messagesResp = await client.session.messages({ path: { id: args.resume } })
|
const messagesResp = await client.session.messages({ path: { id: args.resume } })
|
||||||
const messages = (messagesResp.data ?? []) as Array<{
|
const messages = (messagesResp.data ?? []) as Array<{
|
||||||
info?: { agent?: string; model?: { providerID: string; modelID: string } }
|
info?: { agent?: string; model?: { providerID: string; modelID: string }; modelID?: string; providerID?: string }
|
||||||
}>
|
}>
|
||||||
for (let i = messages.length - 1; i >= 0; i--) {
|
for (let i = messages.length - 1; i >= 0; i--) {
|
||||||
const info = messages[i].info
|
const info = messages[i].info
|
||||||
if (info?.agent || info?.model) {
|
if (info?.agent || info?.model || (info?.modelID && info?.providerID)) {
|
||||||
resumeAgent = info.agent
|
resumeAgent = info.agent
|
||||||
resumeModel = info.model
|
resumeModel = info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user