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 failToolCall = Effect.fn("SessionProcessor.failToolCall")(function* (toolCallID: string, error: unknown) {
|
||||||
const match = yield* readToolCall(toolCallID)
|
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({
|
yield* session.updatePart({
|
||||||
...match.part,
|
...match.part,
|
||||||
state: {
|
state: {
|
||||||
|
|
@ -193,8 +194,8 @@ const layer = Layer.effect(
|
||||||
input: match.part.state.input,
|
input: match.part.state.input,
|
||||||
error: errorMessage(error),
|
error: errorMessage(error),
|
||||||
// Keep metadata streamed while running so failures retain progress detail (e.g. execute's child calls).
|
// Keep metadata streamed while running so failures retain progress detail (e.g. execute's child calls).
|
||||||
metadata: match.part.state.metadata,
|
metadata: match.part.state.status === "running" ? match.part.state.metadata : undefined,
|
||||||
time: { start: match.part.state.time.start, end: Date.now() },
|
time: { start, end: Date.now() },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (error instanceof PermissionV1.RejectedError || error instanceof Question.RejectedError) {
|
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 fragmentFailureEnv = LayerNode.compile(root, [...replacements, [LLM.node, fragmentFailureLLM]])
|
||||||
const itFragmentFailure = testEffect(fragmentFailureEnv)
|
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 boot = Effect.fn("test.boot")(function* () {
|
||||||
const processors = yield* SessionProcessor.Service
|
const processors = yield* SessionProcessor.Service
|
||||||
const session = yield* Session.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", () =>
|
it.live("session.processor effect tests record aborted errors and idle state", () =>
|
||||||
provideTmpdirServer(
|
provideTmpdirServer(
|
||||||
({ dir, llm }) =>
|
({ dir, llm }) =>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue