@agentionai/agents 1.0.2 → 1.2.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 +17 -1
- package/dist/agents/BaseAgent.d.ts +82 -0
- package/dist/agents/BaseAgent.js +107 -0
- package/dist/agents/anthropic/ClaudeAgent.d.ts +0 -2
- package/dist/agents/anthropic/ClaudeAgent.js +11 -21
- package/dist/agents/google/GeminiAgent.d.ts +1 -2
- package/dist/agents/google/GeminiAgent.js +12 -11
- package/dist/agents/mistral/MistralAgent.d.ts +0 -2
- package/dist/agents/mistral/MistralAgent.js +4 -10
- package/dist/agents/model-types.d.ts +77 -1
- package/dist/agents/model-types.js +33 -0
- package/dist/agents/ollama/OllamaAgent.d.ts +0 -2
- package/dist/agents/ollama/OllamaAgent.js +12 -10
- package/dist/agents/openai/OpenAiAgent.d.ts +55 -9
- package/dist/agents/openai/OpenAiAgent.js +70 -35
- package/dist/agents/openai-compatible/OpenAICompatibleAgent.d.ts +0 -1
- package/dist/agents/openai-compatible/OpenAICompatibleAgent.js +47 -21
- package/dist/history/transformers.d.ts +10 -0
- package/dist/history/transformers.js +24 -0
- package/dist/mcp/MCPClient.d.ts +185 -8
- package/dist/mcp/MCPClient.js +459 -70
- package/dist/mcp/content.d.ts +28 -0
- package/dist/mcp/content.js +90 -0
- package/dist/mcp/errors.d.ts +62 -0
- package/dist/mcp/errors.js +74 -0
- package/dist/mcp/index.d.ts +20 -2
- package/dist/mcp/index.js +26 -1
- package/dist/mcp/types.d.ts +278 -11
- package/package.json +2 -1
|
@@ -135,7 +135,7 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
|
|
|
135
135
|
}
|
|
136
136
|
async execute(input) {
|
|
137
137
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
138
|
-
this.
|
|
138
|
+
this.resetTokenUsage();
|
|
139
139
|
this.currentToolCallCount = 0;
|
|
140
140
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
141
141
|
if (VizConfig_1.vizConfig.isEnabled()) {
|
|
@@ -203,6 +203,7 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
|
|
|
203
203
|
const messages = transformers_1.ollamaTransformer.toProvider(this.history.getEntries());
|
|
204
204
|
const tools = this.tools.size > 0 ? this.getToolDefinitions() : undefined;
|
|
205
205
|
const options = this.buildOptions();
|
|
206
|
+
this.startTurnTimer();
|
|
206
207
|
return client.chat({
|
|
207
208
|
model: this.config.model,
|
|
208
209
|
messages,
|
|
@@ -214,15 +215,7 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
|
|
|
214
215
|
}
|
|
215
216
|
async handleResponse(response) {
|
|
216
217
|
const ollamaResponse = response;
|
|
217
|
-
const usage = this.parseUsage(ollamaResponse);
|
|
218
|
-
if (this.lastTokenUsage) {
|
|
219
|
-
this.lastTokenUsage.input_tokens += usage.input_tokens;
|
|
220
|
-
this.lastTokenUsage.output_tokens += usage.output_tokens;
|
|
221
|
-
this.lastTokenUsage.total_tokens += usage.total_tokens;
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
this.lastTokenUsage = { ...usage };
|
|
225
|
-
}
|
|
218
|
+
const usage = this.accumulateUsage(this.parseUsage(ollamaResponse));
|
|
226
219
|
if (ollamaResponse.done_reason === "length") {
|
|
227
220
|
const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 2048);
|
|
228
221
|
this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
|
|
@@ -306,10 +299,19 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
|
|
|
306
299
|
}
|
|
307
300
|
parseUsage(input) {
|
|
308
301
|
const response = input;
|
|
302
|
+
// Ollama reports its own timings in nanoseconds. Time to first token is
|
|
303
|
+
// model load plus prompt evaluation — everything before generation starts.
|
|
304
|
+
const toMs = (ns) => ns === undefined ? undefined : ns / 1000000;
|
|
305
|
+
const timeToFirstTokenMs = response.prompt_eval_duration === undefined
|
|
306
|
+
? undefined
|
|
307
|
+
: toMs((response.load_duration ?? 0) + response.prompt_eval_duration);
|
|
309
308
|
return {
|
|
310
309
|
input_tokens: response.prompt_eval_count ?? 0,
|
|
311
310
|
output_tokens: response.eval_count ?? 0,
|
|
312
311
|
total_tokens: (response.prompt_eval_count ?? 0) + (response.eval_count ?? 0),
|
|
312
|
+
timeToFirstTokenMs,
|
|
313
|
+
generationMs: toMs(response.eval_duration),
|
|
314
|
+
totalMs: toMs(response.total_duration),
|
|
313
315
|
};
|
|
314
316
|
}
|
|
315
317
|
}
|
|
@@ -1,18 +1,43 @@
|
|
|
1
1
|
import { BaseAgent, BaseAgentConfig, TokenUsage } from "../BaseAgent";
|
|
2
2
|
import { History, MessageContent } from "../../history/History";
|
|
3
3
|
import { Tool, Response, ResponseUsage } from "openai/resources/responses/responses";
|
|
4
|
-
import { OpenAIModel } from "../model-types";
|
|
4
|
+
import { OpenAIModel, ReasoningEffort, ReasoningEffortFor } from "../model-types";
|
|
5
5
|
import { StreamChunk } from "../openai-compatible/OpenAICompatibleAgent";
|
|
6
|
-
type AgentConfig = BaseAgentConfig & {
|
|
6
|
+
type AgentConfig<M extends OpenAIModel = OpenAIModel> = BaseAgentConfig & {
|
|
7
7
|
apiKey: string;
|
|
8
|
-
model?:
|
|
8
|
+
model?: M;
|
|
9
9
|
maxTokens?: number;
|
|
10
10
|
disableParallelToolUse?: boolean;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Ask for the least reasoning the configured model supports (e.g. `minimal` on
|
|
13
|
+
* `gpt-5-nano`, `none` on `gpt-5.6`). Takes precedence over `reasoningEffort`.
|
|
14
|
+
* No effect on models without reasoning support.
|
|
15
|
+
*/
|
|
12
16
|
disableReasoning?: boolean;
|
|
13
|
-
|
|
17
|
+
/**
|
|
18
|
+
* How hard the model should think. Narrowed to the values the configured
|
|
19
|
+
* `model` actually accepts — `reasoningEffort: "none"` is a type error on
|
|
20
|
+
* `gpt-5-nano`, which takes `minimal` instead.
|
|
21
|
+
*/
|
|
22
|
+
reasoningEffort?: ReasoningEffortFor<M>;
|
|
14
23
|
user?: string;
|
|
15
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Lowest `reasoning.effort` the given model accepts, used to resolve
|
|
27
|
+
* `disableReasoning`. Returns `undefined` when the model has no reasoning to turn
|
|
28
|
+
* off, in which case the caller omits `reasoning` entirely rather than risk a 400
|
|
29
|
+
* — non-reasoning models such as `gpt-4.1-mini` reject the parameter outright.
|
|
30
|
+
*
|
|
31
|
+
* There is no single "off" value, and `effort: null` is not one either: it means
|
|
32
|
+
* *unset*, so the model falls back to its own default (`medium` on every family
|
|
33
|
+
* released before `gpt-5.1`).
|
|
34
|
+
*
|
|
35
|
+
* Reads {@link OPENAI_REASONING_SUPPORT}, the same table {@link ReasoningEffortFor}
|
|
36
|
+
* is derived from, so the compile-time and runtime views cannot disagree. Models
|
|
37
|
+
* missing from it — including newer families — return `undefined`; set
|
|
38
|
+
* `reasoningEffort` explicitly to override.
|
|
39
|
+
*/
|
|
40
|
+
export declare function lowestReasoningEffort(model: string | undefined): ReasoningEffort | undefined;
|
|
16
41
|
/**
|
|
17
42
|
* Agent for OpenAI models using the Responses API.
|
|
18
43
|
*
|
|
@@ -28,17 +53,38 @@ type AgentConfig = BaseAgentConfig & {
|
|
|
28
53
|
* const response = await agent.execute("Hello!");
|
|
29
54
|
* ```
|
|
30
55
|
*/
|
|
31
|
-
export declare class OpenAiAgent extends BaseAgent {
|
|
56
|
+
export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel> extends BaseAgent {
|
|
32
57
|
private client;
|
|
58
|
+
/**
|
|
59
|
+
* Resolved runtime config. Deliberately not narrowed by `M` — the constructor
|
|
60
|
+
* fills in defaults and merges `vendorConfig`, whose values are not
|
|
61
|
+
* model-scoped. Narrowing happens on the constructor's parameter, where the
|
|
62
|
+
* caller's model is known.
|
|
63
|
+
*/
|
|
33
64
|
protected config: Partial<AgentConfig>;
|
|
34
|
-
/** Token usage from the last execution (for metrics tracking) */
|
|
35
|
-
lastTokenUsage?: TokenUsage;
|
|
36
65
|
/** Current visualization event ID for tracking */
|
|
37
66
|
private vizEventId?;
|
|
38
67
|
/** Count of tool calls in current execution */
|
|
39
68
|
private currentToolCallCount;
|
|
40
|
-
constructor(config: Omit<AgentConfig
|
|
69
|
+
constructor(config: Omit<AgentConfig<M>, "vendor">, history?: History);
|
|
41
70
|
protected getToolDefinitions(): Tool[];
|
|
71
|
+
/**
|
|
72
|
+
* Build the `reasoning` field for a Responses API request, as an object to
|
|
73
|
+
* spread into the request params.
|
|
74
|
+
*
|
|
75
|
+
* `disableReasoning` takes precedence over `reasoningEffort` and resolves to the
|
|
76
|
+
* lowest effort the configured model accepts (see {@link lowestReasoningEffort}).
|
|
77
|
+
* The field is omitted entirely when neither option applies — `reasoning: {}` is
|
|
78
|
+
* not the same as omitting it, and non-reasoning models reject the parameter.
|
|
79
|
+
*
|
|
80
|
+
* All three request sites go through here: they were copies of the same
|
|
81
|
+
* expression, and one drifted into overwriting the disable case with an
|
|
82
|
+
* unconditional `reasoning` key.
|
|
83
|
+
*
|
|
84
|
+
* @param summary Pass `"auto"` for streaming requests — the Responses API only
|
|
85
|
+
* emits `response.reasoning_summary_text.delta` events when it is set.
|
|
86
|
+
*/
|
|
87
|
+
private buildReasoningParams;
|
|
42
88
|
protected process(_input: string): Promise<string>;
|
|
43
89
|
execute(input: string | MessageContent[]): Promise<string>;
|
|
44
90
|
protected handleResponse(response: Response): Promise<string>;
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.OpenAiAgent = void 0;
|
|
7
|
+
exports.lowestReasoningEffort = lowestReasoningEffort;
|
|
7
8
|
const openai_1 = __importDefault(require("openai"));
|
|
8
9
|
const BaseAgent_1 = require("../BaseAgent");
|
|
9
10
|
const AgentEvent_1 = require("../AgentEvent");
|
|
@@ -11,6 +12,30 @@ const AgentError_1 = require("../errors/AgentError");
|
|
|
11
12
|
const transformers_1 = require("../../history/transformers");
|
|
12
13
|
const VizReporter_1 = require("../../viz/VizReporter");
|
|
13
14
|
const VizConfig_1 = require("../../viz/VizConfig");
|
|
15
|
+
const model_types_1 = require("../model-types");
|
|
16
|
+
/**
|
|
17
|
+
* Lowest `reasoning.effort` the given model accepts, used to resolve
|
|
18
|
+
* `disableReasoning`. Returns `undefined` when the model has no reasoning to turn
|
|
19
|
+
* off, in which case the caller omits `reasoning` entirely rather than risk a 400
|
|
20
|
+
* — non-reasoning models such as `gpt-4.1-mini` reject the parameter outright.
|
|
21
|
+
*
|
|
22
|
+
* There is no single "off" value, and `effort: null` is not one either: it means
|
|
23
|
+
* *unset*, so the model falls back to its own default (`medium` on every family
|
|
24
|
+
* released before `gpt-5.1`).
|
|
25
|
+
*
|
|
26
|
+
* Reads {@link OPENAI_REASONING_SUPPORT}, the same table {@link ReasoningEffortFor}
|
|
27
|
+
* is derived from, so the compile-time and runtime views cannot disagree. Models
|
|
28
|
+
* missing from it — including newer families — return `undefined`; set
|
|
29
|
+
* `reasoningEffort` explicitly to override.
|
|
30
|
+
*/
|
|
31
|
+
function lowestReasoningEffort(model) {
|
|
32
|
+
if (!model)
|
|
33
|
+
return undefined;
|
|
34
|
+
// Snapshot ids (`gpt-5-nano-2025-08-07`) share their alias's support set.
|
|
35
|
+
const base = model.replace(/-20\d{2}-\d{2}-\d{2}$/, "");
|
|
36
|
+
const group = model_types_1.OPENAI_REASONING_SUPPORT.find((entry) => entry.models.includes(base));
|
|
37
|
+
return group?.efforts[0];
|
|
38
|
+
}
|
|
14
39
|
/**
|
|
15
40
|
* Agent for OpenAI models using the Responses API.
|
|
16
41
|
*
|
|
@@ -78,13 +103,44 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
78
103
|
};
|
|
79
104
|
});
|
|
80
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Build the `reasoning` field for a Responses API request, as an object to
|
|
108
|
+
* spread into the request params.
|
|
109
|
+
*
|
|
110
|
+
* `disableReasoning` takes precedence over `reasoningEffort` and resolves to the
|
|
111
|
+
* lowest effort the configured model accepts (see {@link lowestReasoningEffort}).
|
|
112
|
+
* The field is omitted entirely when neither option applies — `reasoning: {}` is
|
|
113
|
+
* not the same as omitting it, and non-reasoning models reject the parameter.
|
|
114
|
+
*
|
|
115
|
+
* All three request sites go through here: they were copies of the same
|
|
116
|
+
* expression, and one drifted into overwriting the disable case with an
|
|
117
|
+
* unconditional `reasoning` key.
|
|
118
|
+
*
|
|
119
|
+
* @param summary Pass `"auto"` for streaming requests — the Responses API only
|
|
120
|
+
* emits `response.reasoning_summary_text.delta` events when it is set.
|
|
121
|
+
*/
|
|
122
|
+
buildReasoningParams(summary) {
|
|
123
|
+
const effort = this.config.disableReasoning
|
|
124
|
+
? lowestReasoningEffort(this.config.model)
|
|
125
|
+
: this.config.reasoningEffort;
|
|
126
|
+
if (!effort)
|
|
127
|
+
return {};
|
|
128
|
+
return {
|
|
129
|
+
reasoning: {
|
|
130
|
+
// The Responses API accepts "max" (verified on gpt-5.6), but the installed
|
|
131
|
+
// SDK's ReasoningEffort union predates it — cast at this one boundary.
|
|
132
|
+
effort: effort,
|
|
133
|
+
...(summary ? { summary } : {}),
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
81
137
|
async process(_input) {
|
|
82
138
|
return "";
|
|
83
139
|
}
|
|
84
140
|
async execute(input) {
|
|
85
141
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
86
142
|
// Reset token usage for this execution
|
|
87
|
-
this.
|
|
143
|
+
this.resetTokenUsage();
|
|
88
144
|
this.currentToolCallCount = 0;
|
|
89
145
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
90
146
|
// Start visualization reporting
|
|
@@ -110,6 +166,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
110
166
|
this.history.beginExecution();
|
|
111
167
|
try {
|
|
112
168
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
169
|
+
this.startTurnTimer();
|
|
113
170
|
const response = await this.client.responses.create({
|
|
114
171
|
model: this.config.model,
|
|
115
172
|
max_output_tokens: this.config.maxTokens,
|
|
@@ -120,8 +177,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
120
177
|
top_p: this.config.topP,
|
|
121
178
|
// Note: Responses API doesn't support seed, presence_penalty, frequency_penalty, stop
|
|
122
179
|
user: this.config.user,
|
|
123
|
-
...
|
|
124
|
-
reasoning: { effort: this.config.reasoningEffort },
|
|
180
|
+
...this.buildReasoningParams(),
|
|
125
181
|
});
|
|
126
182
|
this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, response);
|
|
127
183
|
return await this.handleResponse(response);
|
|
@@ -165,15 +221,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
165
221
|
}
|
|
166
222
|
// Track token usage if available
|
|
167
223
|
if (response.usage) {
|
|
168
|
-
|
|
169
|
-
if (this.lastTokenUsage) {
|
|
170
|
-
this.lastTokenUsage.input_tokens += usage.input_tokens;
|
|
171
|
-
this.lastTokenUsage.output_tokens += usage.output_tokens;
|
|
172
|
-
this.lastTokenUsage.total_tokens += usage.total_tokens;
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
this.lastTokenUsage = { ...usage };
|
|
176
|
-
}
|
|
224
|
+
this.accumulateUsage(this.parseUsage(response.usage));
|
|
177
225
|
}
|
|
178
226
|
const toolCalls = response.output.filter((output) => output.type === "function_call");
|
|
179
227
|
// Find the message output (skip reasoning outputs)
|
|
@@ -232,6 +280,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
232
280
|
// Continue conversation
|
|
233
281
|
try {
|
|
234
282
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
283
|
+
this.startTurnTimer();
|
|
235
284
|
const newResponse = await this.client.responses.create({
|
|
236
285
|
model: this.config.model,
|
|
237
286
|
max_output_tokens: this.config.maxTokens,
|
|
@@ -242,13 +291,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
242
291
|
top_p: this.config.topP,
|
|
243
292
|
// Note: Responses API doesn't support seed, presence_penalty, frequency_penalty, stop
|
|
244
293
|
user: this.config.user,
|
|
245
|
-
...
|
|
246
|
-
reasoning: { effort: null },
|
|
247
|
-
}),
|
|
248
|
-
...(this.config.reasoningEffort &&
|
|
249
|
-
!this.config.disableReasoning && {
|
|
250
|
-
reasoning: { effort: this.config.reasoningEffort },
|
|
251
|
-
}),
|
|
294
|
+
...this.buildReasoningParams(),
|
|
252
295
|
});
|
|
253
296
|
this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, newResponse);
|
|
254
297
|
return this.handleResponse(newResponse);
|
|
@@ -355,7 +398,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
355
398
|
*/
|
|
356
399
|
async *executeStream(input) {
|
|
357
400
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
358
|
-
this.
|
|
401
|
+
this.resetTokenUsage();
|
|
359
402
|
this.currentToolCallCount = 0;
|
|
360
403
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
361
404
|
if (VizConfig_1.vizConfig.isEnabled()) {
|
|
@@ -409,6 +452,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
409
452
|
}
|
|
410
453
|
async *streamTurn() {
|
|
411
454
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
455
|
+
this.startTurnTimer();
|
|
412
456
|
const stream = await this.client.responses.create({
|
|
413
457
|
model: this.config.model,
|
|
414
458
|
max_output_tokens: this.config.maxTokens,
|
|
@@ -419,35 +463,24 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
419
463
|
temperature: this.config.temperature,
|
|
420
464
|
top_p: this.config.topP,
|
|
421
465
|
user: this.config.user,
|
|
422
|
-
...
|
|
423
|
-
...(this.config.reasoningEffort && !this.config.disableReasoning && {
|
|
424
|
-
// `summary: "auto"` is required for the Responses API to stream
|
|
425
|
-
// `response.reasoning_summary_text.delta` events.
|
|
426
|
-
reasoning: { effort: this.config.reasoningEffort, summary: "auto" },
|
|
427
|
-
}),
|
|
466
|
+
...this.buildReasoningParams("auto"),
|
|
428
467
|
});
|
|
429
468
|
let completedEvent = null;
|
|
430
469
|
for await (const event of stream) {
|
|
431
470
|
if (event.type === "response.output_text.delta") {
|
|
471
|
+
this.markFirstToken();
|
|
432
472
|
this.emit(AgentEvent_1.AgentEvent.CHUNK, event.delta);
|
|
433
473
|
yield { type: "text", content: event.delta };
|
|
434
474
|
}
|
|
435
475
|
if (event.type === "response.reasoning_summary_text.delta") {
|
|
476
|
+
this.markFirstToken();
|
|
436
477
|
this.emit(AgentEvent_1.AgentEvent.REASONING_CHUNK, event.delta);
|
|
437
478
|
yield { type: "reasoning", content: event.delta };
|
|
438
479
|
}
|
|
439
480
|
if (event.type === "response.completed") {
|
|
440
481
|
completedEvent = event;
|
|
441
482
|
if (event.response.usage) {
|
|
442
|
-
|
|
443
|
-
if (this.lastTokenUsage) {
|
|
444
|
-
this.lastTokenUsage.input_tokens += usage.input_tokens;
|
|
445
|
-
this.lastTokenUsage.output_tokens += usage.output_tokens;
|
|
446
|
-
this.lastTokenUsage.total_tokens += usage.total_tokens;
|
|
447
|
-
}
|
|
448
|
-
else {
|
|
449
|
-
this.lastTokenUsage = { ...usage };
|
|
450
|
-
}
|
|
483
|
+
this.accumulateUsage(this.parseUsage(event.response.usage));
|
|
451
484
|
}
|
|
452
485
|
}
|
|
453
486
|
if (event.type === "response.incomplete") {
|
|
@@ -496,6 +529,8 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
496
529
|
input_tokens: input.input_tokens,
|
|
497
530
|
output_tokens: input.output_tokens,
|
|
498
531
|
total_tokens: input.total_tokens,
|
|
532
|
+
// Reasoning tokens are already counted inside `output_tokens`.
|
|
533
|
+
reasoning_tokens: input.output_tokens_details?.reasoning_tokens,
|
|
499
534
|
};
|
|
500
535
|
}
|
|
501
536
|
}
|
|
@@ -32,7 +32,6 @@ export type OpenAICompatibleConfig = BaseAgentConfig & {
|
|
|
32
32
|
export declare abstract class OpenAICompatibleAgent extends BaseAgent {
|
|
33
33
|
protected client: OpenAI;
|
|
34
34
|
protected config: Partial<OpenAICompatibleConfig>;
|
|
35
|
-
lastTokenUsage?: TokenUsage;
|
|
36
35
|
private vizEventId?;
|
|
37
36
|
private currentToolCallCount;
|
|
38
37
|
constructor(config: OpenAICompatibleConfig & {
|
|
@@ -77,7 +77,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
77
77
|
}
|
|
78
78
|
async execute(input) {
|
|
79
79
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
80
|
-
this.
|
|
80
|
+
this.resetTokenUsage();
|
|
81
81
|
this.currentToolCallCount = 0;
|
|
82
82
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
83
83
|
if (VizConfig_1.vizConfig.isEnabled()) {
|
|
@@ -133,6 +133,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
133
133
|
async callProvider() {
|
|
134
134
|
const messages = transformers_1.chatCompletionsTransformer.toProvider(this.history.getEntries());
|
|
135
135
|
const tools = this.tools.size > 0 ? this.getToolDefinitions() : undefined;
|
|
136
|
+
this.startTurnTimer();
|
|
136
137
|
return this.client.chat.completions.create({
|
|
137
138
|
model: this.config.model,
|
|
138
139
|
messages,
|
|
@@ -149,15 +150,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
149
150
|
});
|
|
150
151
|
}
|
|
151
152
|
async handleResponse(response) {
|
|
152
|
-
const usage = this.parseUsage(response);
|
|
153
|
-
if (this.lastTokenUsage) {
|
|
154
|
-
this.lastTokenUsage.input_tokens += usage.input_tokens;
|
|
155
|
-
this.lastTokenUsage.output_tokens += usage.output_tokens;
|
|
156
|
-
this.lastTokenUsage.total_tokens += usage.total_tokens;
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
this.lastTokenUsage = { ...usage };
|
|
160
|
-
}
|
|
153
|
+
const usage = this.accumulateUsage(this.parseUsage(response));
|
|
161
154
|
const choice = response.choices[0];
|
|
162
155
|
const message = choice.message;
|
|
163
156
|
if (choice.finish_reason === "length") {
|
|
@@ -253,7 +246,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
253
246
|
*/
|
|
254
247
|
async *executeStream(input) {
|
|
255
248
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
256
|
-
this.
|
|
249
|
+
this.resetTokenUsage();
|
|
257
250
|
this.currentToolCallCount = 0;
|
|
258
251
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
259
252
|
if (VizConfig_1.vizConfig.isEnabled()) {
|
|
@@ -307,6 +300,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
307
300
|
async *streamTurn() {
|
|
308
301
|
const messages = transformers_1.chatCompletionsTransformer.toProvider(this.history.getEntries());
|
|
309
302
|
const tools = this.tools.size > 0 ? this.getToolDefinitions() : undefined;
|
|
303
|
+
this.startTurnTimer();
|
|
310
304
|
const stream = await this.client.chat.completions.create({
|
|
311
305
|
model: this.config.model,
|
|
312
306
|
messages,
|
|
@@ -323,6 +317,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
323
317
|
...this.buildExtraRequestParams(),
|
|
324
318
|
});
|
|
325
319
|
let textContent = "";
|
|
320
|
+
let reasoningContent = "";
|
|
326
321
|
const toolCallAcc = new Map();
|
|
327
322
|
let finishReason = null;
|
|
328
323
|
for await (const chunk of stream) {
|
|
@@ -336,6 +331,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
336
331
|
finishReason = choice.finish_reason ?? finishReason;
|
|
337
332
|
const delta = choice.delta;
|
|
338
333
|
if (delta.content) {
|
|
334
|
+
this.markFirstToken();
|
|
339
335
|
textContent += delta.content;
|
|
340
336
|
this.emit(AgentEvent_1.AgentEvent.CHUNK, delta.content);
|
|
341
337
|
yield { type: "text", content: delta.content };
|
|
@@ -347,6 +343,11 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
347
343
|
const deltaExtras = delta;
|
|
348
344
|
const reasoningDelta = (deltaExtras.reasoning ?? deltaExtras.reasoning_content);
|
|
349
345
|
if (reasoningDelta) {
|
|
346
|
+
this.markFirstToken();
|
|
347
|
+
// Accumulated as well as yielded: DeepSeek's thinking mode requires the
|
|
348
|
+
// assistant turn's reasoning to be replayed on the next request, so it
|
|
349
|
+
// has to reach history rather than only the caller.
|
|
350
|
+
reasoningContent += reasoningDelta;
|
|
350
351
|
this.emit(AgentEvent_1.AgentEvent.REASONING_CHUNK, reasoningDelta);
|
|
351
352
|
yield { type: "reasoning", content: reasoningDelta };
|
|
352
353
|
}
|
|
@@ -389,6 +390,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
389
390
|
role: "assistant",
|
|
390
391
|
content: textContent || null,
|
|
391
392
|
tool_calls: toolCalls,
|
|
393
|
+
reasoning_content: reasoningContent || null,
|
|
392
394
|
});
|
|
393
395
|
this.addToHistory(assistantEntry);
|
|
394
396
|
const toolResults = await this.handleToolCalls(toolCalls);
|
|
@@ -401,6 +403,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
401
403
|
const assistantEntry = transformers_1.chatCompletionsTransformer.fromProviderMessage({
|
|
402
404
|
role: "assistant",
|
|
403
405
|
content: textContent || null,
|
|
406
|
+
reasoning_content: reasoningContent || null,
|
|
404
407
|
});
|
|
405
408
|
this.addToHistory(assistantEntry);
|
|
406
409
|
this.emit(AgentEvent_1.AgentEvent.DONE, { content: textContent }, this.lastTokenUsage);
|
|
@@ -417,19 +420,12 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
417
420
|
accumulateStreamUsage(usage) {
|
|
418
421
|
if (!usage)
|
|
419
422
|
return;
|
|
420
|
-
|
|
423
|
+
this.accumulateUsage({
|
|
421
424
|
input_tokens: usage.prompt_tokens ?? 0,
|
|
422
425
|
output_tokens: usage.completion_tokens ?? 0,
|
|
423
426
|
total_tokens: usage.total_tokens ?? 0,
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
this.lastTokenUsage.input_tokens += u.input_tokens;
|
|
427
|
-
this.lastTokenUsage.output_tokens += u.output_tokens;
|
|
428
|
-
this.lastTokenUsage.total_tokens += u.total_tokens;
|
|
429
|
-
}
|
|
430
|
-
else {
|
|
431
|
-
this.lastTokenUsage = u;
|
|
432
|
-
}
|
|
427
|
+
reasoning_tokens: extractReasoningTokens(usage),
|
|
428
|
+
});
|
|
433
429
|
}
|
|
434
430
|
parseUsage(response) {
|
|
435
431
|
const usage = response.usage;
|
|
@@ -437,8 +433,38 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
437
433
|
input_tokens: usage?.prompt_tokens ?? 0,
|
|
438
434
|
output_tokens: usage?.completion_tokens ?? 0,
|
|
439
435
|
total_tokens: usage?.total_tokens ?? 0,
|
|
436
|
+
reasoning_tokens: extractReasoningTokens(usage),
|
|
437
|
+
...extractServerTimings(response),
|
|
440
438
|
};
|
|
441
439
|
}
|
|
442
440
|
}
|
|
443
441
|
exports.OpenAICompatibleAgent = OpenAICompatibleAgent;
|
|
442
|
+
/**
|
|
443
|
+
* Reasoning token count from `completion_tokens_details`, which reasoning
|
|
444
|
+
* models (and OpenAI-compatible proxies fronting them) fill in.
|
|
445
|
+
*/
|
|
446
|
+
function extractReasoningTokens(usage) {
|
|
447
|
+
const details = usage
|
|
448
|
+
?.completion_tokens_details;
|
|
449
|
+
return details?.reasoning_tokens;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Timings reported by llama.cpp's `llama-server`, which adds a non-standard
|
|
453
|
+
* `timings` object to its chat completion responses. Absent on every other
|
|
454
|
+
* OpenAI-compatible server, in which case the durations are measured locally.
|
|
455
|
+
*/
|
|
456
|
+
function extractServerTimings(response) {
|
|
457
|
+
const timings = response?.timings;
|
|
458
|
+
if (!timings)
|
|
459
|
+
return {};
|
|
460
|
+
const { prompt_ms, predicted_ms } = timings;
|
|
461
|
+
const totalMs = prompt_ms === undefined && predicted_ms === undefined
|
|
462
|
+
? undefined
|
|
463
|
+
: (prompt_ms ?? 0) + (predicted_ms ?? 0);
|
|
464
|
+
return {
|
|
465
|
+
timeToFirstTokenMs: prompt_ms,
|
|
466
|
+
generationMs: predicted_ms,
|
|
467
|
+
totalMs,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
444
470
|
//# sourceMappingURL=OpenAICompatibleAgent.js.map
|
|
@@ -182,6 +182,12 @@ type ChatCompletionMessage = {
|
|
|
182
182
|
role: "assistant";
|
|
183
183
|
content: string | null;
|
|
184
184
|
tool_calls?: ChatCompletionToolCallParam[];
|
|
185
|
+
/**
|
|
186
|
+
* Reasoning replayed from a previous turn. Required by DeepSeek's thinking
|
|
187
|
+
* mode; accepted by OpenRouter as an alias for `reasoning`. Omitted
|
|
188
|
+
* entirely when the turn carried no reasoning.
|
|
189
|
+
*/
|
|
190
|
+
reasoning_content?: string;
|
|
185
191
|
} | {
|
|
186
192
|
role: "tool";
|
|
187
193
|
tool_call_id: string;
|
|
@@ -197,6 +203,10 @@ type ChatCompletionResponseMessage = {
|
|
|
197
203
|
arguments: string;
|
|
198
204
|
};
|
|
199
205
|
}>;
|
|
206
|
+
/** Reasoning tokens as sent by OpenRouter. Not part of the OpenAI schema. */
|
|
207
|
+
reasoning?: string | null;
|
|
208
|
+
/** Reasoning tokens as sent by DeepSeek and llama.cpp. */
|
|
209
|
+
reasoning_content?: string | null;
|
|
200
210
|
};
|
|
201
211
|
export {};
|
|
202
212
|
//# sourceMappingURL=transformers.d.ts.map
|
|
@@ -614,6 +614,7 @@ exports.chatCompletionsTransformer = {
|
|
|
614
614
|
const textBlocks = entry.content.filter(types_1.isTextContent);
|
|
615
615
|
const toolUseBlocks = entry.content.filter(types_1.isToolUseContent);
|
|
616
616
|
const toolResultBlocks = entry.content.filter(types_1.isToolResultContent);
|
|
617
|
+
const thinkingBlocks = entry.content.filter(types_1.isThinkingContent);
|
|
617
618
|
const imageUrlBlocks = entry.content.filter(types_1.isImageUrlContent);
|
|
618
619
|
const imageBase64Blocks = entry.content.filter(types_1.isImageBase64Content);
|
|
619
620
|
const hasImages = imageUrlBlocks.length > 0 || imageBase64Blocks.length > 0;
|
|
@@ -626,6 +627,20 @@ exports.chatCompletionsTransformer = {
|
|
|
626
627
|
role: "assistant",
|
|
627
628
|
content: textBlocks.map((c) => c.text).join("\n") || null,
|
|
628
629
|
};
|
|
630
|
+
// DeepSeek's thinking mode rejects a conversation whose assistant turns
|
|
631
|
+
// dropped their reasoning ("The reasoning_content in the thinking mode
|
|
632
|
+
// must be passed back to the API"), so it has to survive the round trip.
|
|
633
|
+
// `reasoning_content` is DeepSeek's field name and an accepted alias for
|
|
634
|
+
// `reasoning` on OpenRouter. Only set it when there is something to send:
|
|
635
|
+
// servers that reject unknown fields must not start seeing it, and
|
|
636
|
+
// redacted-only blocks (Anthropic) carry no text to replay.
|
|
637
|
+
const reasoning = thinkingBlocks
|
|
638
|
+
.map((block) => block.thinking)
|
|
639
|
+
.filter((thought) => thought.length > 0)
|
|
640
|
+
.join("\n");
|
|
641
|
+
if (reasoning) {
|
|
642
|
+
msg.reasoning_content = reasoning;
|
|
643
|
+
}
|
|
629
644
|
if (toolUseBlocks.length > 0) {
|
|
630
645
|
msg.tool_calls = toolUseBlocks.map((block) => ({
|
|
631
646
|
id: block.id,
|
|
@@ -684,6 +699,15 @@ exports.chatCompletionsTransformer = {
|
|
|
684
699
|
*/
|
|
685
700
|
fromProviderMessage(message) {
|
|
686
701
|
const content = [];
|
|
702
|
+
// Reasoning first, matching the order the model produced it in. Servers
|
|
703
|
+
// disagree on the field name — OpenRouter sends `reasoning`, DeepSeek and
|
|
704
|
+
// llama.cpp send `reasoning_content` — so accept either, preferring
|
|
705
|
+
// `reasoning` as the streaming path does. Stored as the neutral thinking
|
|
706
|
+
// block the history layer already round-trips for Anthropic.
|
|
707
|
+
const reasoning = message.reasoning ?? message.reasoning_content;
|
|
708
|
+
if (typeof reasoning === "string" && reasoning) {
|
|
709
|
+
content.push((0, types_1.thinking)(reasoning));
|
|
710
|
+
}
|
|
687
711
|
if (typeof message.content === "string" && message.content) {
|
|
688
712
|
content.push((0, types_1.text)(message.content));
|
|
689
713
|
}
|