|
@@ -6,6 +6,7 @@ import {
|
|
|
Model,
|
|
Model,
|
|
|
ToolFailure,
|
|
ToolFailure,
|
|
|
TransportReason,
|
|
TransportReason,
|
|
|
|
|
+ InvalidProviderOutputReason,
|
|
|
InvalidRequestReason,
|
|
InvalidRequestReason,
|
|
|
RateLimitReason,
|
|
RateLimitReason,
|
|
|
type LLMClientShape,
|
|
type LLMClientShape,
|
|
@@ -716,7 +717,18 @@ const verifyPartialFlushOnFailure = (kind: FragmentKind) =>
|
|
|
type: "assistant",
|
|
type: "assistant",
|
|
|
finish: "error",
|
|
finish: "error",
|
|
|
error: { type: "provider.transport", message: "Provider unavailable" },
|
|
error: { type: "provider.transport", message: "Provider unavailable" },
|
|
|
- content: [fixture.expectedContent],
|
|
|
|
|
|
|
+ content: [
|
|
|
|
|
+ kind === "tool input"
|
|
|
|
|
+ ? {
|
|
|
|
|
+ type: "tool",
|
|
|
|
|
+ id: fragmentID(kind, "partial"),
|
|
|
|
|
+ state: {
|
|
|
|
|
+ status: "error",
|
|
|
|
|
+ error: { type: "provider.transport", message: "Provider unavailable" },
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+ : fixture.expectedContent,
|
|
|
|
|
+ ],
|
|
|
},
|
|
},
|
|
|
])
|
|
])
|
|
|
expect(requests).toHaveLength(1)
|
|
expect(requests).toHaveLength(1)
|
|
@@ -3876,6 +3888,45 @@ describe("SessionRunnerLLM", () => {
|
|
|
}),
|
|
}),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+ it.effect("settles malformed streamed tool input before the provider failure", () =>
|
|
|
|
|
+ Effect.gen(function* () {
|
|
|
|
|
+ const session = yield* setup
|
|
|
|
|
+ yield* admit(session, "Call a malformed tool")
|
|
|
|
|
+ const failure = new LLMError({
|
|
|
|
|
+ module: "test",
|
|
|
|
|
+ method: "stream",
|
|
|
|
|
+ reason: new InvalidProviderOutputReason({ message: "Invalid JSON input for tool call echo" }),
|
|
|
|
|
+ })
|
|
|
|
|
+ responseStream = Stream.fromIterable([
|
|
|
|
|
+ LLMEvent.stepStart({ index: 0 }),
|
|
|
|
|
+ LLMEvent.toolInputStart({ id: "call-malformed", name: "echo" }),
|
|
|
|
|
+ LLMEvent.toolInputDelta({ id: "call-malformed", name: "echo", text: '{"text":"partial' }),
|
|
|
|
|
+ ]).pipe(Stream.concat(Stream.fail(failure)))
|
|
|
|
|
+
|
|
|
|
|
+ expect(yield* session.resume(sessionID).pipe(Effect.flip)).toBe(failure)
|
|
|
|
|
+ const assistant = requireAssistant(yield* session.context(sessionID))
|
|
|
|
|
+
|
|
|
|
|
+ response = reply.stop()
|
|
|
|
|
+ yield* admit(session, "Continue")
|
|
|
|
|
+ yield* session.resume(sessionID)
|
|
|
|
|
+
|
|
|
|
|
+ expect(yield* recordedStepSettlementEvents(sessionID, assistant.id)).toMatchObject([
|
|
|
|
|
+ { type: "session.step.started.1" },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "session.tool.failed.1",
|
|
|
|
|
+ data: {
|
|
|
|
|
+ callID: "call-malformed",
|
|
|
|
|
+ error: { type: "provider.invalid-output", message: "Invalid JSON input for tool call echo" },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "session.step.failed.1",
|
|
|
|
|
+ data: { error: { type: "provider.invalid-output", message: "Invalid JSON input for tool call echo" } },
|
|
|
|
|
+ },
|
|
|
|
|
+ ])
|
|
|
|
|
+ }),
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
it.effect("does not continue automatically after a provider error follows a local tool call", () =>
|
|
it.effect("does not continue automatically after a provider error follows a local tool call", () =>
|
|
|
Effect.gen(function* () {
|
|
Effect.gen(function* () {
|
|
|
const session = yield* setup
|
|
const session = yield* setup
|