mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-10 03:18:31 +00:00
fix(opencode): settle pending tool errors
This commit is contained in:
parent
b44bc0ad07
commit
42d4995cee
2 changed files with 61 additions and 3 deletions
|
|
@ -185,7 +185,8 @@ const layer = Layer.effect(
|
|||
|
||||
const failToolCall = Effect.fn("SessionProcessor.failToolCall")(function* (toolCallID: string, error: unknown) {
|
||||
const match = yield* readToolCall(toolCallID)
|
||||
if (!match || match.part.state.status !== "running") return false
|
||||
if (!match || (match.part.state.status !== "pending" && match.part.state.status !== "running")) return false
|
||||
const start = match.part.state.status === "running" ? match.part.state.time.start : Date.now()
|
||||
yield* session.updatePart({
|
||||
...match.part,
|
||||
state: {
|
||||
|
|
@ -193,8 +194,8 @@ const layer = Layer.effect(
|
|||
input: match.part.state.input,
|
||||
error: errorMessage(error),
|
||||
// Keep metadata streamed while running so failures retain progress detail (e.g. execute's child calls).
|
||||
metadata: match.part.state.metadata,
|
||||
time: { start: match.part.state.time.start, end: Date.now() },
|
||||
metadata: match.part.state.status === "running" ? match.part.state.metadata : undefined,
|
||||
time: { start, end: Date.now() },
|
||||
},
|
||||
})
|
||||
if (error instanceof PermissionV1.RejectedError || error instanceof Question.RejectedError) {
|
||||
|
|
|
|||
|
|
@ -226,6 +226,22 @@ const fragmentFailureLLM = Layer.succeed(
|
|||
const fragmentFailureEnv = LayerNode.compile(root, [...replacements, [LLM.node, fragmentFailureLLM]])
|
||||
const itFragmentFailure = testEffect(fragmentFailureEnv)
|
||||
|
||||
const pendingToolErrorLLM = Layer.succeed(
|
||||
LLM.Service,
|
||||
LLM.Service.of({
|
||||
stream: () =>
|
||||
Stream.make(
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.toolInputStart({ id: "call-pending-error", name: "lookup" }),
|
||||
LLMEvent.toolError({ id: "call-pending-error", name: "lookup", message: "lookup failed" }),
|
||||
LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }),
|
||||
LLMEvent.finish({ reason: "tool-calls" }),
|
||||
),
|
||||
}),
|
||||
)
|
||||
const pendingToolErrorEnv = LayerNode.compile(root, [...replacements, [LLM.node, pendingToolErrorLLM]])
|
||||
const itPendingToolError = testEffect(pendingToolErrorEnv)
|
||||
|
||||
const boot = Effect.fn("test.boot")(function* () {
|
||||
const processors = yield* SessionProcessor.Service
|
||||
const session = yield* Session.Service
|
||||
|
|
@ -834,6 +850,47 @@ it.live("session.processor effect tests mark pending tools as aborted on cleanup
|
|||
),
|
||||
)
|
||||
|
||||
itPendingToolError.live("session.processor settles tool errors received while input is pending", () =>
|
||||
provideTmpdirInstance(
|
||||
(dir) =>
|
||||
Effect.gen(function* () {
|
||||
const { processors, session, provider } = yield* boot()
|
||||
const chat = yield* session.create({})
|
||||
const parent = yield* user(chat.id, "pending tool error")
|
||||
const msg = yield* assistant(chat.id, parent.id, path.resolve(dir))
|
||||
const mdl = yield* provider.getModel(ref.providerID, ref.modelID)
|
||||
const handle = yield* processors.create({ assistantMessage: msg, sessionID: chat.id, model: mdl })
|
||||
|
||||
expect(
|
||||
yield* handle.process({
|
||||
user: {
|
||||
id: parent.id,
|
||||
sessionID: chat.id,
|
||||
role: "user",
|
||||
time: parent.time,
|
||||
agent: parent.agent,
|
||||
model: { providerID: ref.providerID, modelID: ref.modelID },
|
||||
} satisfies SessionV1.User,
|
||||
sessionID: chat.id,
|
||||
model: mdl,
|
||||
agent: agent(),
|
||||
system: [],
|
||||
messages: [{ role: "user", content: "pending tool error" }],
|
||||
tools: {},
|
||||
}),
|
||||
).toBe("continue")
|
||||
|
||||
const parts = yield* MessageV2.parts(msg.id)
|
||||
const call = parts.find((part): part is SessionV1.ToolPart => part.type === "tool")
|
||||
expect(call?.state.status).toBe("error")
|
||||
if (call?.state.status !== "error") return
|
||||
expect(call.state.error).toBe("lookup failed")
|
||||
expect(call.state.metadata?.interrupted).toBeUndefined()
|
||||
}),
|
||||
{ config: cfg },
|
||||
),
|
||||
)
|
||||
|
||||
it.live("session.processor effect tests record aborted errors and idle state", () =>
|
||||
provideTmpdirServer(
|
||||
({ dir, llm }) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue