@agentionai/agents 1.11.0 → 1.13.0
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/agents/Agent.d.ts +1 -1
- package/dist/agents/AgentConfig.d.ts +23 -0
- package/dist/agents/AgentEvent.d.ts +5 -0
- package/dist/agents/AgentEvent.js +5 -0
- package/dist/agents/BaseAgent.d.ts +107 -1
- package/dist/agents/BaseAgent.js +56 -0
- package/dist/agents/anthropic/ClaudeAgent.js +157 -117
- package/dist/agents/errors/AgentError.d.ts +12 -0
- package/dist/agents/openai/CodexAgent.d.ts +135 -0
- package/dist/agents/openai/CodexAgent.js +179 -0
- package/dist/agents/openai/OpenAiAgent.d.ts +89 -6
- package/dist/agents/openai/OpenAiAgent.js +305 -75
- package/dist/agents/openai/codex-auth.d.ts +166 -0
- package/dist/agents/openai/codex-auth.js +264 -0
- package/dist/agents/openai-compatible/OpenAICompatibleAgent.js +140 -107
- package/dist/agents/openrouter/OpenRouterAgent.js +128 -95
- package/dist/agents/reasoning-text.d.ts +44 -0
- package/dist/agents/reasoning-text.js +43 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +13 -1
- package/dist/openai.d.ts +4 -1
- package/dist/openai.js +16 -1
- package/package.json +3 -3
|
@@ -89,6 +89,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
89
89
|
async execute(input, options) {
|
|
90
90
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
91
91
|
this.resetTokenUsage();
|
|
92
|
+
this.resetPartialTurn();
|
|
92
93
|
this.currentToolCallCount = 0;
|
|
93
94
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
94
95
|
if (VizConfig_1.vizConfig.isEnabled()) {
|
|
@@ -312,6 +313,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
312
313
|
async *executeStream(input, options) {
|
|
313
314
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
314
315
|
this.resetTokenUsage();
|
|
316
|
+
this.resetPartialTurn();
|
|
315
317
|
this.currentToolCallCount = 0;
|
|
316
318
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
317
319
|
if (VizConfig_1.vizConfig.isEnabled()) {
|
|
@@ -339,7 +341,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
339
341
|
VizReporter_1.vizReporter.agentError(this.vizEventId, "AbortError", abortError.message, false);
|
|
340
342
|
this.vizEventId = undefined;
|
|
341
343
|
}
|
|
342
|
-
throw abortError;
|
|
344
|
+
throw this.withPartialTurn(abortError);
|
|
343
345
|
}
|
|
344
346
|
if (error instanceof openai_1.default.APIError) {
|
|
345
347
|
const apiError = new AgentError_1.ApiError(`${this.getVendorName()} API error: ${error.message}`, error.status, error);
|
|
@@ -348,7 +350,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
348
350
|
VizReporter_1.vizReporter.agentError(this.vizEventId, "ApiError", apiError.message, error.status === 429);
|
|
349
351
|
this.vizEventId = undefined;
|
|
350
352
|
}
|
|
351
|
-
throw apiError;
|
|
353
|
+
throw this.withPartialTurn(apiError);
|
|
352
354
|
}
|
|
353
355
|
if (error instanceof AgentError_1.AgentError) {
|
|
354
356
|
this.emit(AgentEvent_1.AgentEvent.ERROR, error);
|
|
@@ -356,7 +358,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
356
358
|
VizReporter_1.vizReporter.agentError(this.vizEventId, error.constructor.name, error.message, false);
|
|
357
359
|
this.vizEventId = undefined;
|
|
358
360
|
}
|
|
359
|
-
throw error;
|
|
361
|
+
throw this.withPartialTurn(error);
|
|
360
362
|
}
|
|
361
363
|
const executionError = new AgentError_1.ExecutionError(`${this.getVendorName()} error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
362
364
|
this.emit(AgentEvent_1.AgentEvent.ERROR, executionError);
|
|
@@ -364,7 +366,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
364
366
|
VizReporter_1.vizReporter.agentError(this.vizEventId, "ExecutionError", executionError.message, false);
|
|
365
367
|
this.vizEventId = undefined;
|
|
366
368
|
}
|
|
367
|
-
throw executionError;
|
|
369
|
+
throw this.withPartialTurn(executionError);
|
|
368
370
|
}
|
|
369
371
|
finally {
|
|
370
372
|
this.history.endExecution();
|
|
@@ -393,116 +395,147 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
393
395
|
const toolCallAcc = new Map();
|
|
394
396
|
let finishReason = null;
|
|
395
397
|
let streamUsage;
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
398
|
+
// Set once this frame's assistant message reaches history. Until then the
|
|
399
|
+
// turn exists only in the accumulators above, and the `finally` salvages
|
|
400
|
+
// them — a reasoning trail can be twenty minutes of local compute, and the
|
|
401
|
+
// stream throwing (or the consumer walking away) would otherwise drop it.
|
|
402
|
+
let committed = false;
|
|
403
|
+
let failure;
|
|
404
|
+
try {
|
|
405
|
+
for await (const chunk of stream) {
|
|
406
|
+
// Usage can ride on any chunk: OpenAI sends it on a final choice-less
|
|
407
|
+
// chunk, OpenRouter attaches it to the last content chunk (the one
|
|
408
|
+
// carrying finish_reason). Keep the most recent and fold it in once the
|
|
409
|
+
// stream ends — it is a running total for the turn, not a delta, so
|
|
410
|
+
// taking the last one covers both layouts without double-counting.
|
|
411
|
+
if (chunk.usage)
|
|
412
|
+
streamUsage = chunk.usage;
|
|
413
|
+
if (chunk.id)
|
|
414
|
+
this.lastChunkId = chunk.id;
|
|
415
|
+
if (chunk.choices.length === 0)
|
|
416
|
+
continue;
|
|
417
|
+
const choice = chunk.choices[0];
|
|
418
|
+
finishReason = choice.finish_reason ?? finishReason;
|
|
419
|
+
const delta = choice.delta;
|
|
420
|
+
if (delta.content) {
|
|
421
|
+
this.markFirstToken();
|
|
422
|
+
textContent += delta.content;
|
|
423
|
+
this.emit(AgentEvent_1.AgentEvent.CHUNK, delta.content);
|
|
424
|
+
yield { type: "text", content: delta.content };
|
|
425
|
+
}
|
|
426
|
+
// Reasoning tokens (not in OpenAI SDK types — cast required). Servers
|
|
427
|
+
// disagree on the field name: OpenRouter sends `delta.reasoning`, while
|
|
428
|
+
// DeepSeek/llama.cpp send `delta.reasoning_content`. Prefer `reasoning`;
|
|
429
|
+
// never concatenate — that would duplicate the text if both were sent.
|
|
430
|
+
const deltaExtras = delta;
|
|
431
|
+
const reasoningDelta = (deltaExtras.reasoning ?? deltaExtras.reasoning_content);
|
|
432
|
+
if (reasoningDelta) {
|
|
433
|
+
this.markFirstToken();
|
|
434
|
+
// Accumulated as well as yielded: DeepSeek's thinking mode requires the
|
|
435
|
+
// assistant turn's reasoning to be replayed on the next request, so it
|
|
436
|
+
// has to reach history rather than only the caller.
|
|
437
|
+
reasoningContent += reasoningDelta;
|
|
438
|
+
this.emit(AgentEvent_1.AgentEvent.REASONING_CHUNK, reasoningDelta);
|
|
439
|
+
yield { type: "reasoning", content: reasoningDelta };
|
|
440
|
+
}
|
|
441
|
+
if (delta.tool_calls) {
|
|
442
|
+
for (const tc of delta.tool_calls) {
|
|
443
|
+
if (!toolCallAcc.has(tc.index)) {
|
|
444
|
+
toolCallAcc.set(tc.index, { id: "", name: "", arguments: "" });
|
|
445
|
+
}
|
|
446
|
+
const acc = toolCallAcc.get(tc.index);
|
|
447
|
+
if (tc.id)
|
|
448
|
+
acc.id = tc.id;
|
|
449
|
+
if (tc.function?.name)
|
|
450
|
+
acc.name += tc.function.name;
|
|
451
|
+
if (tc.function?.arguments)
|
|
452
|
+
acc.arguments += tc.function.arguments;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
416
455
|
}
|
|
417
|
-
//
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
this.
|
|
430
|
-
|
|
456
|
+
// Before any early return below, so a turn that hits the token limit or
|
|
457
|
+
// continues into a tool call still reports what it spent.
|
|
458
|
+
if (streamUsage)
|
|
459
|
+
this.accumulateStreamUsage(streamUsage);
|
|
460
|
+
// The SDK's stream iterator swallows the abort and simply stops yielding,
|
|
461
|
+
// so without this an interrupted stream would look like a short but
|
|
462
|
+
// complete turn — writing partial text to history and emitting DONE.
|
|
463
|
+
(0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
|
|
464
|
+
if (finishReason === "length") {
|
|
465
|
+
const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens);
|
|
466
|
+
this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
|
|
467
|
+
this.emit(AgentEvent_1.AgentEvent.ERROR, error);
|
|
468
|
+
if (this.vizEventId) {
|
|
469
|
+
VizReporter_1.vizReporter.agentError(this.vizEventId, "MaxTokensExceededError", error.message, false);
|
|
470
|
+
this.vizEventId = undefined;
|
|
471
|
+
}
|
|
472
|
+
throw error;
|
|
431
473
|
}
|
|
432
|
-
if (
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
474
|
+
if (finishReason === "tool_calls" && toolCallAcc.size > 0) {
|
|
475
|
+
// As in handleResponse(): bail out before the assistant turn is written,
|
|
476
|
+
// so a cancelled run leaves no unanswered tool call in history.
|
|
477
|
+
(0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
|
|
478
|
+
const toolCalls = Array.from(toolCallAcc.entries())
|
|
479
|
+
.sort(([a], [b]) => a - b)
|
|
480
|
+
.map(([, tc]) => ({
|
|
481
|
+
id: tc.id,
|
|
482
|
+
type: "function",
|
|
483
|
+
function: { name: tc.name, arguments: tc.arguments },
|
|
484
|
+
}));
|
|
485
|
+
this.emit(AgentEvent_1.AgentEvent.TOOL_USE, toolCalls);
|
|
486
|
+
this.currentToolCallCount += toolCalls.length;
|
|
487
|
+
const assistantEntry = transformers_1.chatCompletionsTransformer.fromProviderMessage({
|
|
488
|
+
role: "assistant",
|
|
489
|
+
content: textContent || null,
|
|
490
|
+
tool_calls: toolCalls,
|
|
491
|
+
reasoning_content: reasoningContent || null,
|
|
492
|
+
});
|
|
493
|
+
this.addToHistory(assistantEntry);
|
|
494
|
+
committed = true;
|
|
495
|
+
const toolResults = await this.handleToolCalls(toolCalls, options);
|
|
496
|
+
for (const result of toolResults) {
|
|
497
|
+
this.addToHistory(transformers_1.chatCompletionsTransformer.toolResultEntry(result.toolCallId, result.content));
|
|
444
498
|
}
|
|
499
|
+
yield* this.streamTurn(options);
|
|
445
500
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
501
|
+
else {
|
|
502
|
+
const assistantEntry = transformers_1.chatCompletionsTransformer.fromProviderMessage({
|
|
503
|
+
role: "assistant",
|
|
504
|
+
content: textContent || null,
|
|
505
|
+
reasoning_content: reasoningContent || null,
|
|
506
|
+
});
|
|
507
|
+
this.addToHistory(assistantEntry);
|
|
508
|
+
committed = true;
|
|
509
|
+
this.emit(AgentEvent_1.AgentEvent.DONE, { content: textContent }, this.lastTokenUsage);
|
|
510
|
+
if (this.vizEventId) {
|
|
511
|
+
VizReporter_1.vizReporter.agentComplete(this.vizEventId, {
|
|
512
|
+
input: this.lastTokenUsage?.input_tokens || 0,
|
|
513
|
+
output: this.lastTokenUsage?.output_tokens || 0,
|
|
514
|
+
total: this.lastTokenUsage?.total_tokens || 0,
|
|
515
|
+
}, "end_turn", this.currentToolCallCount > 0, this.currentToolCallCount, textContent);
|
|
516
|
+
this.vizEventId = undefined;
|
|
517
|
+
}
|
|
462
518
|
}
|
|
463
|
-
throw error;
|
|
464
519
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
(0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
|
|
469
|
-
const toolCalls = Array.from(toolCallAcc.entries())
|
|
470
|
-
.sort(([a], [b]) => a - b)
|
|
471
|
-
.map(([, tc]) => ({
|
|
472
|
-
id: tc.id,
|
|
473
|
-
type: "function",
|
|
474
|
-
function: { name: tc.name, arguments: tc.arguments },
|
|
475
|
-
}));
|
|
476
|
-
this.emit(AgentEvent_1.AgentEvent.TOOL_USE, toolCalls);
|
|
477
|
-
this.currentToolCallCount += toolCalls.length;
|
|
478
|
-
const assistantEntry = transformers_1.chatCompletionsTransformer.fromProviderMessage({
|
|
479
|
-
role: "assistant",
|
|
480
|
-
content: textContent || null,
|
|
481
|
-
tool_calls: toolCalls,
|
|
482
|
-
reasoning_content: reasoningContent || null,
|
|
483
|
-
});
|
|
484
|
-
this.addToHistory(assistantEntry);
|
|
485
|
-
const toolResults = await this.handleToolCalls(toolCalls, options);
|
|
486
|
-
for (const result of toolResults) {
|
|
487
|
-
this.addToHistory(transformers_1.chatCompletionsTransformer.toolResultEntry(result.toolCallId, result.content));
|
|
488
|
-
}
|
|
489
|
-
yield* this.streamTurn(options);
|
|
520
|
+
catch (error) {
|
|
521
|
+
failure = error;
|
|
522
|
+
throw error;
|
|
490
523
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
524
|
+
finally {
|
|
525
|
+
if (!committed) {
|
|
526
|
+
this.capturePartialTurn({
|
|
527
|
+
text: textContent,
|
|
528
|
+
reasoning: reasoningContent,
|
|
529
|
+
toolCalls: Array.from(toolCallAcc.entries())
|
|
530
|
+
.sort(([a], [b]) => a - b)
|
|
531
|
+
.map(([, tc]) => ({
|
|
532
|
+
id: tc.id,
|
|
533
|
+
name: tc.name,
|
|
534
|
+
arguments: tc.arguments,
|
|
535
|
+
})),
|
|
536
|
+
reason: this.partialTurnReason(failure, options?.signal),
|
|
537
|
+
error: failure,
|
|
538
|
+
});
|
|
506
539
|
}
|
|
507
540
|
}
|
|
508
541
|
}
|
|
@@ -294,6 +294,7 @@ class OpenRouterAgent extends BaseAgent_1.BaseAgent {
|
|
|
294
294
|
beginRun(input) {
|
|
295
295
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
296
296
|
this.resetTokenUsage();
|
|
297
|
+
this.resetPartialTurn();
|
|
297
298
|
this.lastGeneration = undefined;
|
|
298
299
|
this.currentToolCallCount = 0;
|
|
299
300
|
if (VizConfig_1.vizConfig.isEnabled()) {
|
|
@@ -323,12 +324,12 @@ class OpenRouterAgent extends BaseAgent_1.BaseAgent {
|
|
|
323
324
|
if ((0, cancellation_1.isAbortError)(error, options?.signal)) {
|
|
324
325
|
const abortError = this.abortError(error, options?.signal);
|
|
325
326
|
this.closeViz("AbortError", abortError.message, false);
|
|
326
|
-
return abortError;
|
|
327
|
+
return this.withPartialTurn(abortError);
|
|
327
328
|
}
|
|
328
329
|
const mapped = this.mapProviderError(error);
|
|
329
330
|
this.emit(AgentEvent_1.AgentEvent.ERROR, mapped);
|
|
330
331
|
this.closeViz(mapped.name, mapped.message, mapped instanceof AgentError_1.ApiError && mapped.statusCode === 429);
|
|
331
|
-
return mapped;
|
|
332
|
+
return this.withPartialTurn(mapped);
|
|
332
333
|
}
|
|
333
334
|
/**
|
|
334
335
|
* Turn an `@openrouter/sdk` error into an {@link AgentError}.
|
|
@@ -515,108 +516,140 @@ class OpenRouterAgent extends BaseAgent_1.BaseAgent {
|
|
|
515
516
|
let finishReason = null;
|
|
516
517
|
let streamUsage;
|
|
517
518
|
let streamError;
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
519
|
+
// Set once this frame's assistant message reaches history. Until then the
|
|
520
|
+
// turn exists only in the accumulators above, and the `finally` salvages
|
|
521
|
+
// them — a reasoning trail can be minutes of generation, and the stream
|
|
522
|
+
// throwing (or the consumer walking away) would otherwise drop it.
|
|
523
|
+
let committed = false;
|
|
524
|
+
let failure;
|
|
525
|
+
try {
|
|
526
|
+
for await (const chunk of stream) {
|
|
527
|
+
// Once the first token is out the 200 and its headers are committed, so a
|
|
528
|
+
// provider failure after that point arrives as an SSE payload instead of
|
|
529
|
+
// an HTTP status. Recorded and thrown after the loop, so the tokens
|
|
530
|
+
// already spent still get reported.
|
|
531
|
+
if (chunk?.error)
|
|
532
|
+
streamError = chunk.error;
|
|
533
|
+
// Usage rides on whichever chunk OpenRouter chooses — often the last
|
|
534
|
+
// content chunk rather than a trailing choice-less one. It is a running
|
|
535
|
+
// total for the turn, not a delta, so keeping the most recent covers both
|
|
536
|
+
// layouts without double-counting.
|
|
537
|
+
if (chunk?.usage)
|
|
538
|
+
streamUsage = chunk.usage;
|
|
539
|
+
if (chunk?.id || chunk?.model)
|
|
540
|
+
this.recordGeneration(chunk);
|
|
541
|
+
const choice = chunk?.choices?.[0];
|
|
542
|
+
if (!choice)
|
|
543
|
+
continue;
|
|
544
|
+
finishReason = choice.finishReason ?? finishReason;
|
|
545
|
+
const delta = choice.delta ?? {};
|
|
546
|
+
if (delta.content) {
|
|
547
|
+
this.markFirstToken();
|
|
548
|
+
textContent += delta.content;
|
|
549
|
+
this.emit(AgentEvent_1.AgentEvent.CHUNK, delta.content);
|
|
550
|
+
yield { type: "text", content: delta.content };
|
|
551
|
+
}
|
|
552
|
+
if (delta.reasoning) {
|
|
553
|
+
this.markFirstToken();
|
|
554
|
+
// Accumulated as well as yielded: the assistant turn has to carry its
|
|
555
|
+
// reasoning back on the next request.
|
|
556
|
+
reasoningContent += delta.reasoning;
|
|
557
|
+
this.emit(AgentEvent_1.AgentEvent.REASONING_CHUNK, delta.reasoning);
|
|
558
|
+
yield { type: "reasoning", content: delta.reasoning };
|
|
559
|
+
}
|
|
560
|
+
if (delta.reasoningDetails?.length) {
|
|
561
|
+
reasoningDetails = reasoningDetails.concat(delta.reasoningDetails);
|
|
562
|
+
}
|
|
563
|
+
if (delta.toolCalls) {
|
|
564
|
+
for (const tc of delta.toolCalls) {
|
|
565
|
+
const index = tc.index ?? 0;
|
|
566
|
+
if (!toolCallAcc.has(index)) {
|
|
567
|
+
toolCallAcc.set(index, { id: "", name: "", arguments: "" });
|
|
568
|
+
}
|
|
569
|
+
const acc = toolCallAcc.get(index);
|
|
570
|
+
if (tc.id)
|
|
571
|
+
acc.id = tc.id;
|
|
572
|
+
if (tc.function?.name)
|
|
573
|
+
acc.name += tc.function.name;
|
|
574
|
+
if (tc.function?.arguments)
|
|
575
|
+
acc.arguments += tc.function.arguments;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
543
578
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
579
|
+
// Before any throw below, so a turn that failed part way still reports what
|
|
580
|
+
// it spent.
|
|
581
|
+
if (streamUsage)
|
|
582
|
+
this.accumulateUsage(this.parseUsageObject(streamUsage));
|
|
583
|
+
// The SDK's stream iterator stops yielding on abort rather than throwing, so
|
|
584
|
+
// without this an interrupted stream would look like a short but complete
|
|
585
|
+
// turn — writing partial text to history and emitting DONE.
|
|
586
|
+
(0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
|
|
587
|
+
if (streamError) {
|
|
588
|
+
throw new AgentError_1.ApiError(`OpenRouter stream error: ${unwrapOpenRouterMessage(streamError, streamError.message ?? "no message")}`, streamError.code, streamError);
|
|
551
589
|
}
|
|
552
|
-
if (
|
|
553
|
-
|
|
590
|
+
if (finishReason === "length") {
|
|
591
|
+
const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens);
|
|
592
|
+
this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
|
|
593
|
+
throw error;
|
|
554
594
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
595
|
+
const assistantMessage = {
|
|
596
|
+
role: "assistant",
|
|
597
|
+
content: textContent || null,
|
|
598
|
+
reasoning: reasoningContent || null,
|
|
599
|
+
reasoningDetails,
|
|
600
|
+
};
|
|
601
|
+
if (finishReason === "tool_calls" && toolCallAcc.size > 0) {
|
|
602
|
+
// As in handleResponse(): bail out before the assistant turn is written,
|
|
603
|
+
// so a cancelled run leaves no unanswered tool call in history.
|
|
604
|
+
(0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
|
|
605
|
+
const toolCalls = Array.from(toolCallAcc.entries())
|
|
606
|
+
.sort(([a], [b]) => a - b)
|
|
607
|
+
.map(([, tc]) => ({
|
|
608
|
+
id: tc.id,
|
|
609
|
+
type: "function",
|
|
610
|
+
function: { name: tc.name, arguments: tc.arguments },
|
|
611
|
+
}));
|
|
612
|
+
this.emit(AgentEvent_1.AgentEvent.TOOL_USE, toolCalls);
|
|
613
|
+
this.currentToolCallCount += toolCalls.length;
|
|
614
|
+
this.addToHistory(transformers_1.openRouterTransformer.fromProviderMessage({
|
|
615
|
+
...assistantMessage,
|
|
616
|
+
toolCalls,
|
|
617
|
+
}));
|
|
618
|
+
committed = true;
|
|
619
|
+
const toolResults = await this.handleToolCalls(toolCalls, options);
|
|
620
|
+
for (const result of toolResults) {
|
|
621
|
+
this.addToHistory(transformers_1.openRouterTransformer.toolResultEntry(result.toolCallId, result.content));
|
|
568
622
|
}
|
|
623
|
+
yield* this.streamTurn(options);
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
this.addToHistory(transformers_1.openRouterTransformer.fromProviderMessage(assistantMessage));
|
|
627
|
+
committed = true;
|
|
628
|
+
this.emit(AgentEvent_1.AgentEvent.DONE, { content: textContent }, this.lastTokenUsage);
|
|
629
|
+
this.completeViz(textContent);
|
|
569
630
|
}
|
|
570
631
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
if (streamUsage)
|
|
574
|
-
this.accumulateUsage(this.parseUsageObject(streamUsage));
|
|
575
|
-
// The SDK's stream iterator stops yielding on abort rather than throwing, so
|
|
576
|
-
// without this an interrupted stream would look like a short but complete
|
|
577
|
-
// turn — writing partial text to history and emitting DONE.
|
|
578
|
-
(0, cancellation_1.throwIfAborted)(options?.signal, `Execution of agent ${this.getName()}`);
|
|
579
|
-
if (streamError) {
|
|
580
|
-
throw new AgentError_1.ApiError(`OpenRouter stream error: ${unwrapOpenRouterMessage(streamError, streamError.message ?? "no message")}`, streamError.code, streamError);
|
|
581
|
-
}
|
|
582
|
-
if (finishReason === "length") {
|
|
583
|
-
const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens);
|
|
584
|
-
this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
|
|
632
|
+
catch (error) {
|
|
633
|
+
failure = error;
|
|
585
634
|
throw error;
|
|
586
635
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
}));
|
|
604
|
-
this.emit(AgentEvent_1.AgentEvent.TOOL_USE, toolCalls);
|
|
605
|
-
this.currentToolCallCount += toolCalls.length;
|
|
606
|
-
this.addToHistory(transformers_1.openRouterTransformer.fromProviderMessage({
|
|
607
|
-
...assistantMessage,
|
|
608
|
-
toolCalls,
|
|
609
|
-
}));
|
|
610
|
-
const toolResults = await this.handleToolCalls(toolCalls, options);
|
|
611
|
-
for (const result of toolResults) {
|
|
612
|
-
this.addToHistory(transformers_1.openRouterTransformer.toolResultEntry(result.toolCallId, result.content));
|
|
636
|
+
finally {
|
|
637
|
+
if (!committed) {
|
|
638
|
+
this.capturePartialTurn({
|
|
639
|
+
text: textContent,
|
|
640
|
+
reasoning: reasoningContent,
|
|
641
|
+
toolCalls: Array.from(toolCallAcc.entries())
|
|
642
|
+
.sort(([a], [b]) => a - b)
|
|
643
|
+
.map(([, tc]) => ({
|
|
644
|
+
id: tc.id,
|
|
645
|
+
name: tc.name,
|
|
646
|
+
arguments: tc.arguments,
|
|
647
|
+
})),
|
|
648
|
+
reason: this.partialTurnReason(failure, options?.signal),
|
|
649
|
+
error: failure,
|
|
650
|
+
meta: reasoningDetails.length ? { reasoningDetails } : undefined,
|
|
651
|
+
});
|
|
613
652
|
}
|
|
614
|
-
yield* this.streamTurn(options);
|
|
615
|
-
}
|
|
616
|
-
else {
|
|
617
|
-
this.addToHistory(transformers_1.openRouterTransformer.fromProviderMessage(assistantMessage));
|
|
618
|
-
this.emit(AgentEvent_1.AgentEvent.DONE, { content: textContent }, this.lastTokenUsage);
|
|
619
|
-
this.completeViz(textContent);
|
|
620
653
|
}
|
|
621
654
|
}
|
|
622
655
|
async handleToolCalls(toolCalls, options) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display-only normalization for streamed reasoning text.
|
|
3
|
+
*
|
|
4
|
+
* Reasoning models routed through OpenRouter (and other OpenAI-compatible
|
|
5
|
+
* backends) sometimes stream `reasoning`/`reasoning_content` whose formatting
|
|
6
|
+
* is far noisier than the model's final answer — GLM-series models in
|
|
7
|
+
* particular emit heavily bulleted chain-of-thought with a blank line between
|
|
8
|
+
* almost every point, and some provider routes break tokens one phrase per
|
|
9
|
+
* line instead of wrapping normally. That formatting comes from the model
|
|
10
|
+
* itself (verified against live OpenRouter streams — the SDK and this
|
|
11
|
+
* library's accumulation just concatenate deltas verbatim), so it can't be
|
|
12
|
+
* fixed at the source.
|
|
13
|
+
*
|
|
14
|
+
* This is display-only: never apply it to the string that gets stored in
|
|
15
|
+
* history or replayed to the provider on the next turn (DeepSeek/GLM require
|
|
16
|
+
* that text back byte-for-byte, see {@link OpenAICompatibleAgent.streamTurn}).
|
|
17
|
+
* Apply it only where you render or log a `reasoning` chunk for a human.
|
|
18
|
+
*/
|
|
19
|
+
export interface CollapseReasoningWhitespaceOptions {
|
|
20
|
+
/** Collapse runs of 3+ newlines down to a single blank line. Default `true`. */
|
|
21
|
+
collapseBlankLines?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Merge consecutive non-blank lines into one, joined by a space — for
|
|
24
|
+
* providers that stream reasoning broken one word or phrase per line. A
|
|
25
|
+
* line starting a markdown block (list item, heading, blockquote) is never
|
|
26
|
+
* merged into the line before it, so intentional structure survives.
|
|
27
|
+
* Off by default since it can also merge genuinely short paragraphs;
|
|
28
|
+
* enable it for the specific model/provider you've seen this on.
|
|
29
|
+
*/
|
|
30
|
+
collapseLineWraps?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Collapses excess linebreaks in reasoning text for display, leaving the
|
|
34
|
+
* original string untouched for anything that needs it verbatim.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* agent.on(AgentEvent.REASONING_CHUNK, (delta) => {
|
|
39
|
+
* process.stdout.write(collapseReasoningWhitespace(delta));
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function collapseReasoningWhitespace(text: string, options?: CollapseReasoningWhitespaceOptions): string;
|
|
44
|
+
//# sourceMappingURL=reasoning-text.d.ts.map
|