fix(hooks): switch session.prompt to promptAsync in all hooks
This commit is contained in:
parent
5f21ddf473
commit
46e02b9457
@ -484,7 +484,7 @@ export function createAtlasHook(
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.client.session.prompt({
|
await ctx.client.session.promptAsync({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
body: {
|
body: {
|
||||||
agent: agent ?? "atlas",
|
agent: agent ?? "atlas",
|
||||||
|
|||||||
@ -364,7 +364,7 @@ export function createRalphLoopHook(
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.client.session.prompt({
|
await ctx.client.session.promptAsync({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
body: {
|
body: {
|
||||||
...(agent !== undefined ? { agent } : {}),
|
...(agent !== undefined ? { agent } : {}),
|
||||||
|
|||||||
@ -75,7 +75,7 @@ function extractResumeConfig(userMessage: MessageData | undefined, sessionID: st
|
|||||||
|
|
||||||
async function resumeSession(client: Client, config: ResumeConfig): Promise<boolean> {
|
async function resumeSession(client: Client, config: ResumeConfig): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await client.session.prompt({
|
await client.session.promptAsync({
|
||||||
path: { id: config.sessionID },
|
path: { id: config.sessionID },
|
||||||
body: {
|
body: {
|
||||||
parts: [{ type: "text", text: RECOVERY_RESUME_TEXT }],
|
parts: [{ type: "text", text: RECOVERY_RESUME_TEXT }],
|
||||||
@ -185,7 +185,7 @@ async function recoverToolResultMissing(
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.session.prompt({
|
await client.session.promptAsync({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
// @ts-expect-error - SDK types may not include tool_result parts
|
// @ts-expect-error - SDK types may not include tool_result parts
|
||||||
body: { parts: toolResultParts },
|
body: { parts: toolResultParts },
|
||||||
|
|||||||
@ -245,7 +245,7 @@ ${todoList}`
|
|||||||
try {
|
try {
|
||||||
log(`[${HOOK_NAME}] Injecting continuation`, { sessionID, agent: agentName, model, incompleteCount: freshIncompleteCount })
|
log(`[${HOOK_NAME}] Injecting continuation`, { sessionID, agent: agentName, model, incompleteCount: freshIncompleteCount })
|
||||||
|
|
||||||
await ctx.client.session.prompt({
|
await ctx.client.session.promptAsync({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
body: {
|
body: {
|
||||||
agent: agentName,
|
agent: agentName,
|
||||||
|
|||||||
@ -25,6 +25,15 @@ type BabysitterContext = {
|
|||||||
}
|
}
|
||||||
query?: { directory?: string }
|
query?: { directory?: string }
|
||||||
}) => Promise<unknown>
|
}) => Promise<unknown>
|
||||||
|
promptAsync: (args: {
|
||||||
|
path: { id: string }
|
||||||
|
body: {
|
||||||
|
parts: Array<{ type: "text"; text: string }>
|
||||||
|
agent?: string
|
||||||
|
model?: { providerID: string; modelID: string }
|
||||||
|
}
|
||||||
|
query?: { directory?: string }
|
||||||
|
}) => Promise<unknown>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,7 +227,7 @@ export function createUnstableAgentBabysitterHook(ctx: BabysitterContext, option
|
|||||||
const { agent, model } = await resolveMainSessionTarget(ctx, mainSessionID)
|
const { agent, model } = await resolveMainSessionTarget(ctx, mainSessionID)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ctx.client.session.prompt({
|
await ctx.client.session.promptAsync({
|
||||||
path: { id: mainSessionID },
|
path: { id: mainSessionID },
|
||||||
body: {
|
body: {
|
||||||
...(agent ? { agent } : {}),
|
...(agent ? { agent } : {}),
|
||||||
|
|||||||
@ -212,9 +212,9 @@ describe("parseModelSuggestion", () => {
|
|||||||
|
|
||||||
describe("promptWithModelSuggestionRetry", () => {
|
describe("promptWithModelSuggestionRetry", () => {
|
||||||
it("should succeed on first try without retry", async () => {
|
it("should succeed on first try without retry", async () => {
|
||||||
// given a client where prompt succeeds
|
// given a client where promptAsync succeeds
|
||||||
const promptMock = mock(() => Promise.resolve())
|
const promptMock = mock(() => Promise.resolve())
|
||||||
const client = { session: { prompt: promptMock } }
|
const client = { session: { promptAsync: promptMock } }
|
||||||
|
|
||||||
// when calling promptWithModelSuggestionRetry
|
// when calling promptWithModelSuggestionRetry
|
||||||
await promptWithModelSuggestionRetry(client as any, {
|
await promptWithModelSuggestionRetry(client as any, {
|
||||||
@ -225,21 +225,158 @@ describe("promptWithModelSuggestionRetry", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// then should call prompt exactly once
|
// then should call promptAsync exactly once
|
||||||
expect(promptMock).toHaveBeenCalledTimes(1)
|
expect(promptMock).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should retry with suggestion on model-not-found error", async () => {
|
it("should throw error from promptAsync directly on model-not-found error", async () => {
|
||||||
// given a client that fails first with model-not-found, then succeeds
|
// given a client that fails with model-not-found error
|
||||||
const promptMock = mock()
|
const promptMock = mock().mockRejectedValueOnce({
|
||||||
.mockRejectedValueOnce({
|
name: "ProviderModelNotFoundError",
|
||||||
name: "ProviderModelNotFoundError",
|
data: {
|
||||||
data: {
|
providerID: "anthropic",
|
||||||
providerID: "anthropic",
|
modelID: "claude-sonet-4",
|
||||||
modelID: "claude-sonet-4",
|
suggestions: ["claude-sonnet-4"],
|
||||||
suggestions: ["claude-sonnet-4"],
|
},
|
||||||
|
})
|
||||||
|
const client = { session: { promptAsync: promptMock } }
|
||||||
|
|
||||||
|
// when calling promptWithModelSuggestionRetry
|
||||||
|
// then should throw the error without retrying
|
||||||
|
await expect(
|
||||||
|
promptWithModelSuggestionRetry(client as any, {
|
||||||
|
path: { id: "session-1" },
|
||||||
|
body: {
|
||||||
|
agent: "explore",
|
||||||
|
parts: [{ type: "text", text: "hello" }],
|
||||||
|
model: { providerID: "anthropic", modelID: "claude-sonet-4" },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
).rejects.toThrow()
|
||||||
|
|
||||||
|
// and should call promptAsync only once
|
||||||
|
expect(promptMock).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw original error when no suggestion available", async () => {
|
||||||
|
// given a client that fails with a non-model-not-found error
|
||||||
|
const originalError = new Error("Connection refused")
|
||||||
|
const promptMock = mock().mockRejectedValueOnce(originalError)
|
||||||
|
const client = { session: { promptAsync: promptMock } }
|
||||||
|
|
||||||
|
// when calling promptWithModelSuggestionRetry
|
||||||
|
// then should throw the original error
|
||||||
|
await expect(
|
||||||
|
promptWithModelSuggestionRetry(client as any, {
|
||||||
|
path: { id: "session-1" },
|
||||||
|
body: {
|
||||||
|
parts: [{ type: "text", text: "hello" }],
|
||||||
|
model: { providerID: "anthropic", modelID: "claude-sonnet-4" },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).rejects.toThrow("Connection refused")
|
||||||
|
|
||||||
|
expect(promptMock).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error from promptAsync directly", async () => {
|
||||||
|
// given a client that fails with an error
|
||||||
|
const error = new Error("Still not found")
|
||||||
|
const promptMock = mock().mockRejectedValueOnce(error)
|
||||||
|
const client = { session: { promptAsync: promptMock } }
|
||||||
|
|
||||||
|
// when calling promptWithModelSuggestionRetry
|
||||||
|
// then should throw the error
|
||||||
|
await expect(
|
||||||
|
promptWithModelSuggestionRetry(client as any, {
|
||||||
|
path: { id: "session-1" },
|
||||||
|
body: {
|
||||||
|
parts: [{ type: "text", text: "hello" }],
|
||||||
|
model: { providerID: "anthropic", modelID: "claude-sonnet-4" },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).rejects.toThrow("Still not found")
|
||||||
|
|
||||||
|
// and should call promptAsync only once
|
||||||
|
expect(promptMock).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should pass all body fields through to promptAsync", async () => {
|
||||||
|
// given a client where promptAsync succeeds
|
||||||
|
const promptMock = mock().mockResolvedValueOnce(undefined)
|
||||||
|
const client = { session: { promptAsync: promptMock } }
|
||||||
|
|
||||||
|
// when calling with additional body fields
|
||||||
|
await promptWithModelSuggestionRetry(client as any, {
|
||||||
|
path: { id: "session-1" },
|
||||||
|
body: {
|
||||||
|
agent: "explore",
|
||||||
|
system: "You are a helpful agent",
|
||||||
|
tools: { task: false },
|
||||||
|
parts: [{ type: "text", text: "hello" }],
|
||||||
|
model: { providerID: "anthropic", modelID: "claude-sonnet-4" },
|
||||||
|
variant: "max",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// then call should pass all fields through unchanged
|
||||||
|
const call = promptMock.mock.calls[0][0]
|
||||||
|
expect(call.body.agent).toBe("explore")
|
||||||
|
expect(call.body.system).toBe("You are a helpful agent")
|
||||||
|
expect(call.body.tools).toEqual({ task: false })
|
||||||
|
expect(call.body.variant).toBe("max")
|
||||||
|
expect(call.body.model).toEqual({
|
||||||
|
providerID: "anthropic",
|
||||||
|
modelID: "claude-sonnet-4",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw string error message from promptAsync", async () => {
|
||||||
|
// given a client that fails with a string error
|
||||||
|
const promptMock = mock().mockRejectedValueOnce(
|
||||||
|
new Error("Model not found: anthropic/claude-sonet-4. Did you mean: claude-sonnet-4?")
|
||||||
|
)
|
||||||
|
const client = { session: { promptAsync: promptMock } }
|
||||||
|
|
||||||
|
// when calling promptWithModelSuggestionRetry
|
||||||
|
// then should throw the error
|
||||||
|
await expect(
|
||||||
|
promptWithModelSuggestionRetry(client as any, {
|
||||||
|
path: { id: "session-1" },
|
||||||
|
body: {
|
||||||
|
parts: [{ type: "text", text: "hello" }],
|
||||||
|
model: { providerID: "anthropic", modelID: "claude-sonnet-4" },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).rejects.toThrow()
|
||||||
|
|
||||||
|
// and should call promptAsync only once
|
||||||
|
expect(promptMock).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error when no model in original request", async () => {
|
||||||
|
// given a client that fails with an error
|
||||||
|
const modelNotFoundError = new Error(
|
||||||
|
"Model not found: anthropic/claude-sonet-4. Did you mean: claude-sonnet-4?"
|
||||||
|
)
|
||||||
|
const promptMock = mock().mockRejectedValueOnce(modelNotFoundError)
|
||||||
|
const client = { session: { promptAsync: promptMock } }
|
||||||
|
|
||||||
|
// when calling without model in body
|
||||||
|
// then should throw the error
|
||||||
|
await expect(
|
||||||
|
promptWithModelSuggestionRetry(client as any, {
|
||||||
|
path: { id: "session-1" },
|
||||||
|
body: {
|
||||||
|
parts: [{ type: "text", text: "hello" }],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).rejects.toThrow()
|
||||||
|
|
||||||
|
// and should call promptAsync only once
|
||||||
|
expect(promptMock).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
.mockResolvedValueOnce(undefined)
|
.mockResolvedValueOnce(undefined)
|
||||||
const client = { session: { prompt: promptMock } }
|
const client = { session: { prompt: promptMock } }
|
||||||
|
|
||||||
|
|||||||
@ -219,18 +219,18 @@ Original error: ${createResult.error}`
|
|||||||
log(`[call_omo_agent] Sending prompt to session ${sessionID}`)
|
log(`[call_omo_agent] Sending prompt to session ${sessionID}`)
|
||||||
log(`[call_omo_agent] Prompt text:`, args.prompt.substring(0, 100))
|
log(`[call_omo_agent] Prompt text:`, args.prompt.substring(0, 100))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ctx.client.session.prompt({
|
await ctx.client.session.promptAsync({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
body: {
|
body: {
|
||||||
agent: args.subagent_type,
|
agent: args.subagent_type,
|
||||||
tools: {
|
tools: {
|
||||||
...getAgentToolRestrictions(args.subagent_type),
|
...getAgentToolRestrictions(args.subagent_type),
|
||||||
task: false,
|
task: false,
|
||||||
},
|
},
|
||||||
parts: [{ type: "text", text: args.prompt }],
|
parts: [{ type: "text", text: args.prompt }],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||||
log(`[call_omo_agent] Prompt error:`, errorMessage)
|
log(`[call_omo_agent] Prompt error:`, errorMessage)
|
||||||
|
|||||||
@ -211,20 +211,20 @@ export async function executeSyncContinuation(
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.session.prompt({
|
await client.session.promptAsync({
|
||||||
path: { id: args.session_id! },
|
path: { id: args.session_id! },
|
||||||
body: {
|
body: {
|
||||||
...(resumeAgent !== undefined ? { agent: resumeAgent } : {}),
|
...(resumeAgent !== undefined ? { agent: resumeAgent } : {}),
|
||||||
...(resumeModel !== undefined ? { model: resumeModel } : {}),
|
...(resumeModel !== undefined ? { model: resumeModel } : {}),
|
||||||
tools: {
|
tools: {
|
||||||
...(resumeAgent ? getAgentToolRestrictions(resumeAgent) : {}),
|
...(resumeAgent ? getAgentToolRestrictions(resumeAgent) : {}),
|
||||||
task: false,
|
task: false,
|
||||||
call_omo_agent: true,
|
call_omo_agent: true,
|
||||||
question: false,
|
question: false,
|
||||||
},
|
},
|
||||||
parts: [{ type: "text", text: args.prompt }],
|
parts: [{ type: "text", text: args.prompt }],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (promptError) {
|
} catch (promptError) {
|
||||||
if (toastManager) {
|
if (toastManager) {
|
||||||
toastManager.removeTask(taskId)
|
toastManager.removeTask(taskId)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user