fix(agent): recover completed tools after stream read errors
This commit is contained in:
@@ -141,6 +141,66 @@ describe("AgentSession retry delay cap", () => {
|
||||
expect(session.isRetrying).toBe(false);
|
||||
});
|
||||
|
||||
it("auto-retries OpenAI Responses stream_read_error instead of stopping the conversation", async () => {
|
||||
const model = getBundledModel("openai", "gpt-5");
|
||||
if (!model) {
|
||||
throw new Error("Expected bundled OpenAI test model to exist");
|
||||
}
|
||||
authStorage.setRuntimeApiKey("openai", "openai-test-key");
|
||||
|
||||
const mock = createMockModel({
|
||||
responses: [
|
||||
{ throw: "Error Code stream_read_error: stream_read_error" },
|
||||
{ content: ["recovered after stream read retry"], stopReason: "stop" },
|
||||
],
|
||||
});
|
||||
const agent = new Agent({
|
||||
getApiKey: requestedModel => `${requestedModel.provider}-test-key`,
|
||||
initialState: {
|
||||
model,
|
||||
systemPrompt: ["Test"],
|
||||
tools: [],
|
||||
messages: [],
|
||||
},
|
||||
streamFn: (requestedModel, context, options) => mock.stream(requestedModel, context, options),
|
||||
});
|
||||
|
||||
const settings = Settings.isolated({
|
||||
"compaction.enabled": false,
|
||||
"retry.baseDelayMs": 5,
|
||||
"retry.maxDelayMs": 5_000,
|
||||
"retry.maxRetries": 1,
|
||||
"retry.modelFallback": false,
|
||||
});
|
||||
settings.setModelRole("default", `${model.provider}/${model.id}`);
|
||||
|
||||
session = new AgentSession({
|
||||
agent,
|
||||
sessionManager: SessionManager.inMemory(),
|
||||
settings,
|
||||
modelRegistry,
|
||||
});
|
||||
|
||||
vi.spyOn(scheduler, "wait").mockResolvedValue(undefined);
|
||||
const retryStartEvents: AutoRetryStartEvent[] = [];
|
||||
const retryEndEvents: AutoRetryEndEvent[] = [];
|
||||
session.subscribe(event => {
|
||||
if (event.type === "auto_retry_start") retryStartEvents.push(event);
|
||||
if (event.type === "auto_retry_end") retryEndEvents.push(event);
|
||||
});
|
||||
|
||||
await session.prompt("Trigger stream read retry");
|
||||
await session.waitForIdle();
|
||||
|
||||
expect(mock.calls).toHaveLength(2);
|
||||
expect(retryStartEvents).toHaveLength(1);
|
||||
expect(retryEndEvents).toHaveLength(1);
|
||||
expect(retryEndEvents[0]).toMatchObject({ success: true });
|
||||
const last = lastAssistant(session);
|
||||
expect(last.stopReason).toBe("stop");
|
||||
expect(last.content).toContainEqual({ type: "text", text: "recovered after stream read retry" });
|
||||
});
|
||||
|
||||
it("switches credentials instead of failing the delay cap for account rate limits", async () => {
|
||||
const model = getBundledModel("anthropic", "claude-sonnet-4-5");
|
||||
if (!model) {
|
||||
|
||||
Reference in New Issue
Block a user