@sweny-ai/core 0.1.37 → 0.1.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/claude.test.js +99 -0
- package/dist/claude.js +13 -0
- package/package.json +1 -1
|
@@ -250,6 +250,105 @@ describe("ClaudeClient", () => {
|
|
|
250
250
|
expect(result.status).toBe("failed");
|
|
251
251
|
expect(result.data.error).toContain("Too many turns");
|
|
252
252
|
});
|
|
253
|
+
// terminal_reason was added in @anthropic-ai/claude-agent-sdk v0.2.91.
|
|
254
|
+
// When the turn budget is exhausted the SDK now emits subtype='success'
|
|
255
|
+
// alongside terminal_reason='max_turns' instead of the old error subtype,
|
|
256
|
+
// so the claude.ts handler has to look at both fields or silently accept
|
|
257
|
+
// truncated output as a success.
|
|
258
|
+
describe("terminal_reason handling (claude-agent-sdk v0.2.91+)", () => {
|
|
259
|
+
it("returns failed when terminal_reason is max_turns", async () => {
|
|
260
|
+
mockQuery.mockReturnValueOnce(makeStream([
|
|
261
|
+
{
|
|
262
|
+
type: "result",
|
|
263
|
+
subtype: "success",
|
|
264
|
+
result: "partial output",
|
|
265
|
+
terminal_reason: "max_turns",
|
|
266
|
+
},
|
|
267
|
+
]));
|
|
268
|
+
const client = new ClaudeClient();
|
|
269
|
+
const result = await client.run({ instruction: "x", context: {}, tools: [] });
|
|
270
|
+
expect(result.status).toBe("failed");
|
|
271
|
+
expect(result.data.error).toContain("max_turns");
|
|
272
|
+
expect(result.data.error).toContain("terminated early");
|
|
273
|
+
});
|
|
274
|
+
it("returns failed for any non-completed terminal_reason", async () => {
|
|
275
|
+
// Future SDK versions may add new terminal reasons (aborted_tools,
|
|
276
|
+
// blocking_limit, etc.) — treat anything that isn't 'completed' as a
|
|
277
|
+
// failure rather than assume it's recoverable.
|
|
278
|
+
mockQuery.mockReturnValueOnce(makeStream([
|
|
279
|
+
{
|
|
280
|
+
type: "result",
|
|
281
|
+
subtype: "success",
|
|
282
|
+
result: "partial",
|
|
283
|
+
terminal_reason: "aborted_tools",
|
|
284
|
+
},
|
|
285
|
+
]));
|
|
286
|
+
const client = new ClaudeClient();
|
|
287
|
+
const result = await client.run({ instruction: "x", context: {}, tools: [] });
|
|
288
|
+
expect(result.status).toBe("failed");
|
|
289
|
+
expect(result.data.error).toContain("aborted_tools");
|
|
290
|
+
});
|
|
291
|
+
it("returns success when terminal_reason is 'completed'", async () => {
|
|
292
|
+
mockQuery.mockReturnValueOnce(makeStream([
|
|
293
|
+
{
|
|
294
|
+
type: "result",
|
|
295
|
+
subtype: "success",
|
|
296
|
+
result: '{"ok": true}',
|
|
297
|
+
terminal_reason: "completed",
|
|
298
|
+
},
|
|
299
|
+
]));
|
|
300
|
+
const client = new ClaudeClient();
|
|
301
|
+
const result = await client.run({ instruction: "x", context: {}, tools: [] });
|
|
302
|
+
expect(result.status).toBe("success");
|
|
303
|
+
expect(result.data.ok).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
it("returns success when terminal_reason is absent (pre-v0.2.91 SDK)", async () => {
|
|
306
|
+
// Old SDKs never set terminal_reason. The guard must not treat an
|
|
307
|
+
// absent field as a failure or every caller would break on downgrade.
|
|
308
|
+
mockQuery.mockReturnValueOnce(makeStream([
|
|
309
|
+
{
|
|
310
|
+
type: "result",
|
|
311
|
+
subtype: "success",
|
|
312
|
+
result: '{"done": true}',
|
|
313
|
+
// terminal_reason deliberately omitted
|
|
314
|
+
},
|
|
315
|
+
]));
|
|
316
|
+
const client = new ClaudeClient();
|
|
317
|
+
const result = await client.run({ instruction: "x", context: {}, tools: [] });
|
|
318
|
+
expect(result.status).toBe("success");
|
|
319
|
+
expect(result.data.done).toBe(true);
|
|
320
|
+
});
|
|
321
|
+
it("preserves toolCalls when failing on terminal_reason", async () => {
|
|
322
|
+
// A max_turns failure should still surface any tool calls that
|
|
323
|
+
// happened before the budget ran out, so debuggers can see what
|
|
324
|
+
// Claude was trying to do.
|
|
325
|
+
mockQuery.mockReturnValueOnce(makeStream([
|
|
326
|
+
{
|
|
327
|
+
type: "assistant",
|
|
328
|
+
message: {
|
|
329
|
+
content: [
|
|
330
|
+
{
|
|
331
|
+
type: "tool_use",
|
|
332
|
+
name: "search_code",
|
|
333
|
+
input: { query: "foo" },
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
type: "result",
|
|
340
|
+
subtype: "success",
|
|
341
|
+
result: "partial",
|
|
342
|
+
terminal_reason: "max_turns",
|
|
343
|
+
},
|
|
344
|
+
]));
|
|
345
|
+
const client = new ClaudeClient();
|
|
346
|
+
const result = await client.run({ instruction: "x", context: {}, tools: [] });
|
|
347
|
+
expect(result.status).toBe("failed");
|
|
348
|
+
expect(result.toolCalls).toHaveLength(1);
|
|
349
|
+
expect(result.toolCalls[0].tool).toBe("search_code");
|
|
350
|
+
});
|
|
351
|
+
});
|
|
253
352
|
it("passes model option to query", async () => {
|
|
254
353
|
mockQuery.mockReturnValueOnce(makeStream([{ type: "result", subtype: "success", result: "ok" }]));
|
|
255
354
|
const client = new ClaudeClient({ model: "claude-sonnet-4-20250514" });
|
package/dist/claude.js
CHANGED
|
@@ -112,6 +112,19 @@ export class ClaudeClient {
|
|
|
112
112
|
else if (message.type === "result") {
|
|
113
113
|
const resultMsg = message;
|
|
114
114
|
if (resultMsg.subtype === "success" && "result" in resultMsg) {
|
|
115
|
+
// terminal_reason was added in @anthropic-ai/claude-agent-sdk v0.2.91.
|
|
116
|
+
// When the turn budget is exhausted the SDK still emits subtype='success'
|
|
117
|
+
// but sets terminal_reason='max_turns'. Treat anything other than
|
|
118
|
+
// 'completed' (or absent) as a failure so callers are not silently fed
|
|
119
|
+
// incomplete output.
|
|
120
|
+
const terminalReason = resultMsg.terminal_reason;
|
|
121
|
+
if (terminalReason && terminalReason !== "completed") {
|
|
122
|
+
return {
|
|
123
|
+
status: "failed",
|
|
124
|
+
data: { error: `Claude query terminated early: ${terminalReason}` },
|
|
125
|
+
toolCalls,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
115
128
|
response = resultMsg.result;
|
|
116
129
|
}
|
|
117
130
|
else if ("errors" in resultMsg) {
|