@agentionai/agents 1.1.0 → 1.3.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/BaseAgent.d.ts +90 -0
- package/dist/agents/BaseAgent.js +107 -0
- package/dist/agents/anthropic/ClaudeAgent.d.ts +0 -2
- package/dist/agents/anthropic/ClaudeAgent.js +12 -22
- package/dist/agents/google/GeminiAgent.d.ts +1 -2
- package/dist/agents/google/GeminiAgent.js +13 -12
- package/dist/agents/mistral/MistralAgent.d.ts +0 -2
- package/dist/agents/mistral/MistralAgent.js +4 -10
- package/dist/agents/model-types.d.ts +3 -3
- package/dist/agents/ollama/OllamaAgent.d.ts +0 -2
- package/dist/agents/ollama/OllamaAgent.js +12 -10
- package/dist/agents/openai/OpenAiAgent.d.ts +0 -2
- package/dist/agents/openai/OpenAiAgent.js +11 -20
- package/dist/agents/openai-compatible/OpenAICompatibleAgent.d.ts +0 -1
- package/dist/agents/openai-compatible/OpenAICompatibleAgent.js +53 -26
- package/package.json +1 -1
|
@@ -12,10 +12,60 @@ export interface BaseAgentConfig extends CommonAgentConfig {
|
|
|
12
12
|
vendor: AgentVendor;
|
|
13
13
|
vendorConfig?: VendorSpecificConfig;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Token counts and timing for one or more provider API calls.
|
|
17
|
+
*
|
|
18
|
+
* Counts are always present. Timing fields are optional because their
|
|
19
|
+
* availability depends on the provider and on whether the call was streamed:
|
|
20
|
+
*
|
|
21
|
+
* - `totalMs` is always measured (wall clock around the API call).
|
|
22
|
+
* - `timeToFirstTokenMs` / `generationMs` require either a streamed response
|
|
23
|
+
* (measured locally) or a provider that reports its own timings
|
|
24
|
+
* (Ollama, llama.cpp).
|
|
25
|
+
*
|
|
26
|
+
* When usage from several calls is folded together (a tool-use loop, for
|
|
27
|
+
* example) the counts and durations are summed and the rates recomputed from
|
|
28
|
+
* those totals.
|
|
29
|
+
*/
|
|
15
30
|
export type TokenUsage = {
|
|
16
31
|
input_tokens: number;
|
|
17
32
|
output_tokens: number;
|
|
18
33
|
total_tokens: number;
|
|
34
|
+
/**
|
|
35
|
+
* Reasoning/thinking tokens, where the provider reports them separately
|
|
36
|
+
* (OpenAI, Gemini). These are a subset of `output_tokens`, not an addition
|
|
37
|
+
* to them. Undefined when the provider does not break them out — Anthropic,
|
|
38
|
+
* for instance, folds thinking tokens into `output_tokens`.
|
|
39
|
+
*/
|
|
40
|
+
reasoning_tokens?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Milliseconds from sending the request to the first token of the response —
|
|
43
|
+
* prompt upload plus prompt processing.
|
|
44
|
+
*/
|
|
45
|
+
timeToFirstTokenMs?: number;
|
|
46
|
+
/** Milliseconds spent generating the response after the first token. */
|
|
47
|
+
generationMs?: number;
|
|
48
|
+
/** Total wall-clock milliseconds spent in provider API calls. */
|
|
49
|
+
totalMs?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Prompt processing throughput: `input_tokens` over `timeToFirstTokenMs`.
|
|
52
|
+
* Undefined when the time to first token is unknown.
|
|
53
|
+
*/
|
|
54
|
+
inputTokensPerSecond?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Generation throughput: `output_tokens` over `generationMs`. Falls back to
|
|
57
|
+
* `totalMs` when the first-token time is unknown (an unstreamed call), in
|
|
58
|
+
* which case it is an end-to-end rate rather than a pure generation rate.
|
|
59
|
+
*
|
|
60
|
+
* Accurate wherever thinking is streamed, because the first thinking chunk
|
|
61
|
+
* starts the generation window — verified on Anthropic extended thinking and
|
|
62
|
+
* on DeepSeek via OpenRouter. It over-reports on OpenAI's Responses API,
|
|
63
|
+
* which does not stream raw reasoning: there the thinking finishes before the
|
|
64
|
+
* first visible token, so it lands inside `timeToFirstTokenMs` while its
|
|
65
|
+
* tokens still count toward `output_tokens`. For the visible-output rate on
|
|
66
|
+
* that path, divide `output_tokens - reasoning_tokens` by `generationMs`.
|
|
67
|
+
*/
|
|
68
|
+
outputTokensPerSecond?: number;
|
|
19
69
|
};
|
|
20
70
|
/**
|
|
21
71
|
* The base agent is what the other agents are inheriting from
|
|
@@ -33,6 +83,16 @@ export declare abstract class BaseAgent<TInput = unknown, TOutput = unknown> ext
|
|
|
33
83
|
protected vendor: AgentVendor;
|
|
34
84
|
/** The model identifier for this agent */
|
|
35
85
|
protected model: string;
|
|
86
|
+
/**
|
|
87
|
+
* Token counts and timings for the most recent `execute()` call, summed
|
|
88
|
+
* across every provider API call it made (including tool-use follow-ups).
|
|
89
|
+
* Reset at the start of each execution.
|
|
90
|
+
*/
|
|
91
|
+
lastTokenUsage?: TokenUsage;
|
|
92
|
+
/** Start of the API call currently in flight, set by `startTurnTimer()`. */
|
|
93
|
+
private turnStartedAt?;
|
|
94
|
+
/** First-token timestamp of the call in flight, set by `markFirstToken()`. */
|
|
95
|
+
private turnFirstTokenAt?;
|
|
36
96
|
/**
|
|
37
97
|
* An Agent is the primary LLM entity.
|
|
38
98
|
*
|
|
@@ -78,5 +138,35 @@ export declare abstract class BaseAgent<TInput = unknown, TOutput = unknown> ext
|
|
|
78
138
|
getTools(): Tool<unknown>[];
|
|
79
139
|
clearHistory(): void;
|
|
80
140
|
protected abstract parseUsage(input: unknown): TokenUsage;
|
|
141
|
+
/**
|
|
142
|
+
* Clear accumulated usage. Called at the start of every `execute()` /
|
|
143
|
+
* `executeStream()` so `lastTokenUsage` describes a single execution.
|
|
144
|
+
*/
|
|
145
|
+
protected resetTokenUsage(): void;
|
|
146
|
+
/**
|
|
147
|
+
* Mark the moment a provider API call is sent. Call this immediately before
|
|
148
|
+
* every request so the usage folded in afterwards can be timed.
|
|
149
|
+
*/
|
|
150
|
+
protected startTurnTimer(): void;
|
|
151
|
+
/**
|
|
152
|
+
* Mark the arrival of the first token of a streamed response. Subsequent
|
|
153
|
+
* calls within the same turn are ignored, so it is safe to call on every
|
|
154
|
+
* chunk.
|
|
155
|
+
*/
|
|
156
|
+
protected markFirstToken(): void;
|
|
157
|
+
/**
|
|
158
|
+
* Fold one API call's usage into `lastTokenUsage`, filling in any timings
|
|
159
|
+
* the provider did not report from the local turn timer.
|
|
160
|
+
*
|
|
161
|
+
* @returns the usage for this single call, with timings and rates filled in —
|
|
162
|
+
* `lastTokenUsage` holds the running total across calls.
|
|
163
|
+
*/
|
|
164
|
+
protected accumulateUsage(usage: TokenUsage): TokenUsage;
|
|
165
|
+
/**
|
|
166
|
+
* Add locally measured timings to a provider-parsed usage object. Timings the
|
|
167
|
+
* provider reported itself (Ollama, llama.cpp) are kept as-is — they exclude
|
|
168
|
+
* network overhead and are more accurate than anything measured here.
|
|
169
|
+
*/
|
|
170
|
+
private applyTurnTiming;
|
|
81
171
|
}
|
|
82
172
|
//# sourceMappingURL=BaseAgent.d.ts.map
|
package/dist/agents/BaseAgent.js
CHANGED
|
@@ -120,6 +120,113 @@ class BaseAgent extends events_1.default {
|
|
|
120
120
|
clearHistory() {
|
|
121
121
|
this.history.clear();
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Clear accumulated usage. Called at the start of every `execute()` /
|
|
125
|
+
* `executeStream()` so `lastTokenUsage` describes a single execution.
|
|
126
|
+
*/
|
|
127
|
+
resetTokenUsage() {
|
|
128
|
+
this.lastTokenUsage = undefined;
|
|
129
|
+
this.turnStartedAt = undefined;
|
|
130
|
+
this.turnFirstTokenAt = undefined;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Mark the moment a provider API call is sent. Call this immediately before
|
|
134
|
+
* every request so the usage folded in afterwards can be timed.
|
|
135
|
+
*/
|
|
136
|
+
startTurnTimer() {
|
|
137
|
+
this.turnStartedAt = Date.now();
|
|
138
|
+
this.turnFirstTokenAt = undefined;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Mark the arrival of the first token of a streamed response. Subsequent
|
|
142
|
+
* calls within the same turn are ignored, so it is safe to call on every
|
|
143
|
+
* chunk.
|
|
144
|
+
*/
|
|
145
|
+
markFirstToken() {
|
|
146
|
+
if (this.turnStartedAt !== undefined &&
|
|
147
|
+
this.turnFirstTokenAt === undefined) {
|
|
148
|
+
this.turnFirstTokenAt = Date.now();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Fold one API call's usage into `lastTokenUsage`, filling in any timings
|
|
153
|
+
* the provider did not report from the local turn timer.
|
|
154
|
+
*
|
|
155
|
+
* @returns the usage for this single call, with timings and rates filled in —
|
|
156
|
+
* `lastTokenUsage` holds the running total across calls.
|
|
157
|
+
*/
|
|
158
|
+
accumulateUsage(usage) {
|
|
159
|
+
const timed = this.applyTurnTiming(usage);
|
|
160
|
+
const previous = this.lastTokenUsage;
|
|
161
|
+
const merged = previous
|
|
162
|
+
? {
|
|
163
|
+
input_tokens: previous.input_tokens + timed.input_tokens,
|
|
164
|
+
output_tokens: previous.output_tokens + timed.output_tokens,
|
|
165
|
+
total_tokens: previous.total_tokens + timed.total_tokens,
|
|
166
|
+
reasoning_tokens: sumOptional(previous.reasoning_tokens, timed.reasoning_tokens),
|
|
167
|
+
timeToFirstTokenMs: sumOptional(previous.timeToFirstTokenMs, timed.timeToFirstTokenMs),
|
|
168
|
+
generationMs: sumOptional(previous.generationMs, timed.generationMs),
|
|
169
|
+
totalMs: sumOptional(previous.totalMs, timed.totalMs),
|
|
170
|
+
}
|
|
171
|
+
: { ...timed };
|
|
172
|
+
this.lastTokenUsage = withThroughput(merged);
|
|
173
|
+
this.turnStartedAt = undefined;
|
|
174
|
+
this.turnFirstTokenAt = undefined;
|
|
175
|
+
return withThroughput(timed);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Add locally measured timings to a provider-parsed usage object. Timings the
|
|
179
|
+
* provider reported itself (Ollama, llama.cpp) are kept as-is — they exclude
|
|
180
|
+
* network overhead and are more accurate than anything measured here.
|
|
181
|
+
*/
|
|
182
|
+
applyTurnTiming(usage) {
|
|
183
|
+
if (this.turnStartedAt === undefined)
|
|
184
|
+
return usage;
|
|
185
|
+
const now = Date.now();
|
|
186
|
+
const timed = { ...usage };
|
|
187
|
+
timed.totalMs ?? (timed.totalMs = now - this.turnStartedAt);
|
|
188
|
+
if (this.turnFirstTokenAt !== undefined) {
|
|
189
|
+
timed.timeToFirstTokenMs ?? (timed.timeToFirstTokenMs = this.turnFirstTokenAt - this.turnStartedAt);
|
|
190
|
+
timed.generationMs ?? (timed.generationMs = now - this.turnFirstTokenAt);
|
|
191
|
+
}
|
|
192
|
+
return timed;
|
|
193
|
+
}
|
|
123
194
|
}
|
|
124
195
|
exports.BaseAgent = BaseAgent;
|
|
196
|
+
/**
|
|
197
|
+
* Add two values that may each be undefined, returning undefined only when
|
|
198
|
+
* neither side has a value (so "not reported" never reads as zero).
|
|
199
|
+
*/
|
|
200
|
+
function sumOptional(a, b) {
|
|
201
|
+
if (a === undefined)
|
|
202
|
+
return b;
|
|
203
|
+
if (b === undefined)
|
|
204
|
+
return a;
|
|
205
|
+
return a + b;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Derive tokens-per-second rates from the counts and durations on a usage
|
|
209
|
+
* object. Recomputed from totals rather than averaged, so folding several calls
|
|
210
|
+
* together stays correct.
|
|
211
|
+
*
|
|
212
|
+
* Fields that stayed unknown are dropped rather than left as explicit
|
|
213
|
+
* `undefined`, keeping serialized usage free of empty keys.
|
|
214
|
+
*/
|
|
215
|
+
function withThroughput(usage) {
|
|
216
|
+
const result = { ...usage };
|
|
217
|
+
if (result.timeToFirstTokenMs) {
|
|
218
|
+
result.inputTokensPerSecond =
|
|
219
|
+
result.input_tokens / (result.timeToFirstTokenMs / 1000);
|
|
220
|
+
}
|
|
221
|
+
const outputWindowMs = result.generationMs || result.totalMs;
|
|
222
|
+
if (outputWindowMs) {
|
|
223
|
+
result.outputTokensPerSecond =
|
|
224
|
+
result.output_tokens / (outputWindowMs / 1000);
|
|
225
|
+
}
|
|
226
|
+
for (const key of Object.keys(result)) {
|
|
227
|
+
if (result[key] === undefined)
|
|
228
|
+
delete result[key];
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
125
232
|
//# sourceMappingURL=BaseAgent.js.map
|
|
@@ -49,8 +49,6 @@ type AgentConfig = BaseAgentConfig & {
|
|
|
49
49
|
export declare class ClaudeAgent extends BaseAgent {
|
|
50
50
|
private client;
|
|
51
51
|
protected config: Partial<AgentConfig>;
|
|
52
|
-
/** Token usage from the last execution (for metrics tracking) */
|
|
53
|
-
lastTokenUsage?: TokenUsage;
|
|
54
52
|
/** Current visualization event ID for tracking */
|
|
55
53
|
private vizEventId?;
|
|
56
54
|
/** Count of tool calls in current execution */
|
|
@@ -43,7 +43,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
43
43
|
? { authToken: config.apiKey }
|
|
44
44
|
: { apiKey: config.apiKey });
|
|
45
45
|
this.config = {
|
|
46
|
-
model: config.model || "claude-
|
|
46
|
+
model: config.model || "claude-haiku-4-5",
|
|
47
47
|
maxTokens: config.maxTokens || 1024,
|
|
48
48
|
disableParallelToolUse,
|
|
49
49
|
metadata,
|
|
@@ -109,7 +109,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
109
109
|
async execute(input) {
|
|
110
110
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
111
111
|
// Reset token usage for this execution
|
|
112
|
-
this.
|
|
112
|
+
this.resetTokenUsage();
|
|
113
113
|
this.currentToolCallCount = 0;
|
|
114
114
|
// Normalise input to a display string for viz reporting
|
|
115
115
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
@@ -135,6 +135,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
135
135
|
// mid-loop. endExecution() in the finally block enforces limits once.
|
|
136
136
|
this.history.beginExecution();
|
|
137
137
|
try {
|
|
138
|
+
this.startTurnTimer();
|
|
138
139
|
const response = (await this.client.messages.create(this.buildMessageParams()));
|
|
139
140
|
this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, response);
|
|
140
141
|
return await this.handleResponse(response);
|
|
@@ -167,16 +168,8 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
170
|
async handleResponse(response) {
|
|
170
|
-
const usage = this.parseUsage(response.usage);
|
|
171
171
|
// Store token usage for metrics tracking
|
|
172
|
-
|
|
173
|
-
this.lastTokenUsage.input_tokens += usage.input_tokens;
|
|
174
|
-
this.lastTokenUsage.output_tokens += usage.output_tokens;
|
|
175
|
-
this.lastTokenUsage.total_tokens += usage.total_tokens;
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
this.lastTokenUsage = { ...usage };
|
|
179
|
-
}
|
|
172
|
+
const usage = this.accumulateUsage(this.parseUsage(response.usage));
|
|
180
173
|
if (response.stop_reason === "max_tokens") {
|
|
181
174
|
const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
|
|
182
175
|
this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
|
|
@@ -232,6 +225,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
232
225
|
this.addMessageToHistory("user", toolResults);
|
|
233
226
|
// Continue conversation with tool results
|
|
234
227
|
try {
|
|
228
|
+
this.startTurnTimer();
|
|
235
229
|
const newResponse = (await this.client.messages.create(this.buildMessageParams()));
|
|
236
230
|
this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, newResponse);
|
|
237
231
|
return this.handleResponse(newResponse);
|
|
@@ -329,7 +323,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
329
323
|
*/
|
|
330
324
|
async *executeStream(input) {
|
|
331
325
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
332
|
-
this.
|
|
326
|
+
this.resetTokenUsage();
|
|
333
327
|
this.currentToolCallCount = 0;
|
|
334
328
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
335
329
|
if (VizConfig_1.vizConfig.isEnabled()) {
|
|
@@ -379,6 +373,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
379
373
|
}
|
|
380
374
|
}
|
|
381
375
|
async *streamTurn() {
|
|
376
|
+
this.startTurnTimer();
|
|
382
377
|
const stream = await this.client.messages.create({
|
|
383
378
|
...this.buildMessageParams(),
|
|
384
379
|
stream: true,
|
|
@@ -416,6 +411,9 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
416
411
|
}
|
|
417
412
|
}
|
|
418
413
|
if (event.type === "content_block_delta") {
|
|
414
|
+
// First generated content of the turn — thinking counts, since it is
|
|
415
|
+
// generation time either way.
|
|
416
|
+
this.markFirstToken();
|
|
419
417
|
const e = event;
|
|
420
418
|
const delta = e.delta;
|
|
421
419
|
const acc = blocks.get(e.index);
|
|
@@ -442,19 +440,11 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
442
440
|
}
|
|
443
441
|
}
|
|
444
442
|
}
|
|
445
|
-
|
|
443
|
+
this.accumulateUsage({
|
|
446
444
|
input_tokens: inputTokens,
|
|
447
445
|
output_tokens: outputTokens,
|
|
448
446
|
total_tokens: inputTokens + outputTokens,
|
|
449
|
-
};
|
|
450
|
-
if (this.lastTokenUsage) {
|
|
451
|
-
this.lastTokenUsage.input_tokens += usage.input_tokens;
|
|
452
|
-
this.lastTokenUsage.output_tokens += usage.output_tokens;
|
|
453
|
-
this.lastTokenUsage.total_tokens += usage.total_tokens;
|
|
454
|
-
}
|
|
455
|
-
else {
|
|
456
|
-
this.lastTokenUsage = { ...usage };
|
|
457
|
-
}
|
|
447
|
+
});
|
|
458
448
|
if (stopReason === "max_tokens") {
|
|
459
449
|
const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
|
|
460
450
|
this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
|
|
@@ -29,8 +29,6 @@ export declare class GeminiAgent extends BaseAgent {
|
|
|
29
29
|
private client;
|
|
30
30
|
private generativeModel;
|
|
31
31
|
protected config: Partial<AgentConfig>;
|
|
32
|
-
/** Token usage from the last execution (for metrics tracking) */
|
|
33
|
-
lastTokenUsage?: TokenUsage;
|
|
34
32
|
/** Current visualization event ID for tracking */
|
|
35
33
|
private vizEventId?;
|
|
36
34
|
/** Count of tool calls in current execution */
|
|
@@ -57,6 +55,7 @@ export declare class GeminiAgent extends BaseAgent {
|
|
|
57
55
|
promptTokenCount?: number;
|
|
58
56
|
candidatesTokenCount?: number;
|
|
59
57
|
totalTokenCount?: number;
|
|
58
|
+
thoughtsTokenCount?: number;
|
|
60
59
|
}): TokenUsage;
|
|
61
60
|
}
|
|
62
61
|
export {};
|
|
@@ -35,7 +35,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
|
|
|
35
35
|
const responseMimeType = config.responseMimeType ?? vendorConfig.responseMimeType;
|
|
36
36
|
const responseSchema = config.responseSchema ?? vendorConfig.responseSchema;
|
|
37
37
|
this.config = {
|
|
38
|
-
model: config.model || "gemini-
|
|
38
|
+
model: config.model || "gemini-flash-latest",
|
|
39
39
|
maxTokens: config.maxTokens || 1024,
|
|
40
40
|
apiKey: config.apiKey,
|
|
41
41
|
temperature: config.temperature,
|
|
@@ -164,7 +164,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
|
|
|
164
164
|
async execute(input) {
|
|
165
165
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
166
166
|
// Reset token usage for this execution
|
|
167
|
-
this.
|
|
167
|
+
this.resetTokenUsage();
|
|
168
168
|
this.currentToolCallCount = 0;
|
|
169
169
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
170
170
|
// Start visualization reporting
|
|
@@ -192,6 +192,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
|
|
|
192
192
|
const contents = transformers_1.geminiTransformer.toProvider(this.history.getEntries());
|
|
193
193
|
const systemMessage = this.history.getSystemMessage();
|
|
194
194
|
const tools = this.getToolDefinitionsForGemini();
|
|
195
|
+
this.startTurnTimer();
|
|
195
196
|
const response = await this.generativeModel.generateContent({
|
|
196
197
|
contents,
|
|
197
198
|
systemInstruction: systemMessage,
|
|
@@ -241,15 +242,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
|
|
|
241
242
|
const result = response.response;
|
|
242
243
|
// Parse and track usage
|
|
243
244
|
if (result.usageMetadata) {
|
|
244
|
-
|
|
245
|
-
if (this.lastTokenUsage) {
|
|
246
|
-
this.lastTokenUsage.input_tokens += usage.input_tokens;
|
|
247
|
-
this.lastTokenUsage.output_tokens += usage.output_tokens;
|
|
248
|
-
this.lastTokenUsage.total_tokens += usage.total_tokens;
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
this.lastTokenUsage = { ...usage };
|
|
252
|
-
}
|
|
245
|
+
this.accumulateUsage(this.parseUsage(result.usageMetadata));
|
|
253
246
|
}
|
|
254
247
|
// Check for finish reason
|
|
255
248
|
const candidate = result.candidates?.[0];
|
|
@@ -309,6 +302,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
|
|
|
309
302
|
const newContents = transformers_1.geminiTransformer.toProvider(this.history.getEntries());
|
|
310
303
|
const systemMessage = this.history.getSystemMessage();
|
|
311
304
|
const tools = this.getToolDefinitionsForGemini();
|
|
305
|
+
this.startTurnTimer();
|
|
312
306
|
const newResponse = await this.generativeModel.generateContent({
|
|
313
307
|
contents: newContents,
|
|
314
308
|
systemInstruction: systemMessage,
|
|
@@ -400,10 +394,17 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
|
|
|
400
394
|
return results;
|
|
401
395
|
}
|
|
402
396
|
parseUsage(input) {
|
|
397
|
+
// Gemini excludes thought tokens from `candidatesTokenCount` but includes
|
|
398
|
+
// them in `totalTokenCount`. Fold them into `output_tokens` so
|
|
399
|
+
// input + output === total holds, matching how other providers report.
|
|
400
|
+
// `thoughtsTokenCount` is sent by thinking models but is not declared on
|
|
401
|
+
// the legacy SDK's `UsageMetadata`, hence the widened parameter type.
|
|
402
|
+
const thoughts = input.thoughtsTokenCount;
|
|
403
403
|
return {
|
|
404
404
|
input_tokens: input.promptTokenCount || 0,
|
|
405
|
-
output_tokens: input.candidatesTokenCount || 0,
|
|
405
|
+
output_tokens: (input.candidatesTokenCount || 0) + (thoughts || 0),
|
|
406
406
|
total_tokens: input.totalTokenCount || 0,
|
|
407
|
+
reasoning_tokens: thoughts,
|
|
407
408
|
};
|
|
408
409
|
}
|
|
409
410
|
}
|
|
@@ -29,8 +29,6 @@ type AgentConfig = BaseAgentConfig & {
|
|
|
29
29
|
export declare class MistralAgent extends BaseAgent {
|
|
30
30
|
private client;
|
|
31
31
|
protected config: Partial<AgentConfig>;
|
|
32
|
-
/** Token usage from the last execution (for metrics tracking) */
|
|
33
|
-
lastTokenUsage?: TokenUsage;
|
|
34
32
|
/** Current visualization event ID for tracking */
|
|
35
33
|
private vizEventId?;
|
|
36
34
|
/** Count of tool calls in current execution */
|
|
@@ -73,7 +73,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
|
|
|
73
73
|
async execute(input) {
|
|
74
74
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
75
75
|
// Reset token usage for this execution
|
|
76
|
-
this.
|
|
76
|
+
this.resetTokenUsage();
|
|
77
77
|
this.currentToolCallCount = 0;
|
|
78
78
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
79
79
|
// Start visualization reporting
|
|
@@ -99,6 +99,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
|
|
|
99
99
|
this.history.beginExecution();
|
|
100
100
|
try {
|
|
101
101
|
const messages = transformers_1.mistralTransformer.toProvider(this.history.getEntries());
|
|
102
|
+
this.startTurnTimer();
|
|
102
103
|
const response = await this.client.chat.complete({
|
|
103
104
|
model: this.config.model,
|
|
104
105
|
messages: messages,
|
|
@@ -147,16 +148,8 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
|
|
|
147
148
|
throw error;
|
|
148
149
|
}
|
|
149
150
|
const choice = response.choices[0];
|
|
150
|
-
const usage = this.parseUsage(response.usage);
|
|
151
151
|
// Track token usage
|
|
152
|
-
|
|
153
|
-
this.lastTokenUsage.input_tokens += usage.input_tokens;
|
|
154
|
-
this.lastTokenUsage.output_tokens += usage.output_tokens;
|
|
155
|
-
this.lastTokenUsage.total_tokens += usage.total_tokens;
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
this.lastTokenUsage = { ...usage };
|
|
159
|
-
}
|
|
152
|
+
const usage = this.accumulateUsage(this.parseUsage(response.usage));
|
|
160
153
|
if (choice.finishReason === "length") {
|
|
161
154
|
const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
|
|
162
155
|
this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
|
|
@@ -218,6 +211,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
|
|
|
218
211
|
// Continue conversation
|
|
219
212
|
try {
|
|
220
213
|
const messages = transformers_1.mistralTransformer.toProvider(this.history.getEntries());
|
|
214
|
+
this.startTurnTimer();
|
|
221
215
|
const newResponse = await this.client.chat.complete({
|
|
222
216
|
model: this.config.model,
|
|
223
217
|
messages: messages,
|
|
@@ -8,19 +8,19 @@
|
|
|
8
8
|
* You can also provide any custom string for newer models not yet listed.
|
|
9
9
|
* @see https://docs.anthropic.com/en/docs/about-claude/models
|
|
10
10
|
*/
|
|
11
|
-
export type ClaudeModel = "claude-opus-4-7" | "claude-opus-4-6" | "claude-sonnet-4-6" | "claude-opus-4-5" | "claude-sonnet-4-5" | "claude-haiku-4-5" | "claude-haiku-4-5-20251001" | (string & Record<never, never>);
|
|
11
|
+
export type ClaudeModel = "claude-opus-5" | "claude-sonnet-5" | "claude-fable-5" | "claude-opus-4-8" | "claude-opus-4-7" | "claude-opus-4-6" | "claude-sonnet-4-6" | "claude-opus-4-5" | "claude-opus-4-5-20251101" | "claude-sonnet-4-5" | "claude-sonnet-4-5-20250929" | "claude-haiku-4-5" | "claude-haiku-4-5-20251001" | (string & Record<never, never>);
|
|
12
12
|
/**
|
|
13
13
|
* Supported Google Gemini models.
|
|
14
14
|
* You can also provide any custom string for newer models not yet listed.
|
|
15
15
|
* @see https://ai.google.dev/gemini-api/docs/models/gemini
|
|
16
16
|
*/
|
|
17
|
-
export type GeminiModel = "gemini-flash-latest" | "gemini-flash-lite-latest" | "gemini-3.
|
|
17
|
+
export type GeminiModel = "gemini-pro-latest" | "gemini-flash-latest" | "gemini-flash-lite-latest" | "gemini-3.6-flash" | "gemini-3.5-flash" | "gemini-3.5-flash-lite" | "gemini-3.1-pro-preview" | "gemini-3.1-flash-lite" | "gemini-3.1-flash-lite-preview" | "gemini-3-pro-preview" | "gemini-3-flash-preview" | "gemini-2.5-pro" | "gemini-2.5-flash" | "gemini-2.5-flash-lite" | "gemini-2.0-flash" | "gemini-2.0-flash-001" | "gemini-2.0-flash-lite" | "gemini-2.0-flash-lite-001" | (string & {});
|
|
18
18
|
/**
|
|
19
19
|
* Supported Mistral models.
|
|
20
20
|
* You can also provide any custom string for newer models not yet listed.
|
|
21
21
|
* @see https://docs.mistral.ai/getting-started/models/
|
|
22
22
|
*/
|
|
23
|
-
export type MistralModel = "mistral-large-latest" | "mistral-small-latest" | "ministral-8b-latest" | "ministral-8b-
|
|
23
|
+
export type MistralModel = "mistral-large-latest" | "mistral-large-2512" | "mistral-medium-latest" | "mistral-medium-3.5" | "mistral-small-latest" | "mistral-small-2603" | "magistral-small-latest" | "ministral-14b-latest" | "ministral-8b-latest" | "ministral-8b-2512" | "ministral-3b-latest" | "ministral-3b-2512" | "codestral-latest" | "codestral-2508" | "devstral-latest" | "devstral-medium-latest" | "mistral-embed" | "codestral-embed" | "mistral-moderation-latest" | "mistral-moderation-2603" | "mistral-ocr-latest" | (string & {});
|
|
24
24
|
/**
|
|
25
25
|
* Popular Ollama models (locally hosted).
|
|
26
26
|
* You can also provide any custom string for models you have pulled.
|
|
@@ -46,8 +46,6 @@ type OllamaToolDefinition = {
|
|
|
46
46
|
*/
|
|
47
47
|
export declare class OllamaAgent extends BaseAgent {
|
|
48
48
|
protected config: Partial<AgentConfig>;
|
|
49
|
-
/** Token usage from the last execution (for metrics tracking) */
|
|
50
|
-
lastTokenUsage?: TokenUsage;
|
|
51
49
|
/** Current visualization event ID */
|
|
52
50
|
private vizEventId?;
|
|
53
51
|
/** Count of tool calls in current execution */
|
|
@@ -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
|
}
|
|
@@ -62,8 +62,6 @@ export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel> extends Ba
|
|
|
62
62
|
* caller's model is known.
|
|
63
63
|
*/
|
|
64
64
|
protected config: Partial<AgentConfig>;
|
|
65
|
-
/** Token usage from the last execution (for metrics tracking) */
|
|
66
|
-
lastTokenUsage?: TokenUsage;
|
|
67
65
|
/** Current visualization event ID for tracking */
|
|
68
66
|
private vizEventId?;
|
|
69
67
|
/** Count of tool calls in current execution */
|
|
@@ -140,7 +140,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
140
140
|
async execute(input) {
|
|
141
141
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
142
142
|
// Reset token usage for this execution
|
|
143
|
-
this.
|
|
143
|
+
this.resetTokenUsage();
|
|
144
144
|
this.currentToolCallCount = 0;
|
|
145
145
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
146
146
|
// Start visualization reporting
|
|
@@ -166,6 +166,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
166
166
|
this.history.beginExecution();
|
|
167
167
|
try {
|
|
168
168
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
169
|
+
this.startTurnTimer();
|
|
169
170
|
const response = await this.client.responses.create({
|
|
170
171
|
model: this.config.model,
|
|
171
172
|
max_output_tokens: this.config.maxTokens,
|
|
@@ -220,15 +221,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
220
221
|
}
|
|
221
222
|
// Track token usage if available
|
|
222
223
|
if (response.usage) {
|
|
223
|
-
|
|
224
|
-
if (this.lastTokenUsage) {
|
|
225
|
-
this.lastTokenUsage.input_tokens += usage.input_tokens;
|
|
226
|
-
this.lastTokenUsage.output_tokens += usage.output_tokens;
|
|
227
|
-
this.lastTokenUsage.total_tokens += usage.total_tokens;
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
this.lastTokenUsage = { ...usage };
|
|
231
|
-
}
|
|
224
|
+
this.accumulateUsage(this.parseUsage(response.usage));
|
|
232
225
|
}
|
|
233
226
|
const toolCalls = response.output.filter((output) => output.type === "function_call");
|
|
234
227
|
// Find the message output (skip reasoning outputs)
|
|
@@ -287,6 +280,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
287
280
|
// Continue conversation
|
|
288
281
|
try {
|
|
289
282
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
283
|
+
this.startTurnTimer();
|
|
290
284
|
const newResponse = await this.client.responses.create({
|
|
291
285
|
model: this.config.model,
|
|
292
286
|
max_output_tokens: this.config.maxTokens,
|
|
@@ -404,7 +398,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
404
398
|
*/
|
|
405
399
|
async *executeStream(input) {
|
|
406
400
|
this.emit(AgentEvent_1.AgentEvent.BEFORE_EXECUTE, input);
|
|
407
|
-
this.
|
|
401
|
+
this.resetTokenUsage();
|
|
408
402
|
this.currentToolCallCount = 0;
|
|
409
403
|
const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
|
|
410
404
|
if (VizConfig_1.vizConfig.isEnabled()) {
|
|
@@ -458,6 +452,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
458
452
|
}
|
|
459
453
|
async *streamTurn() {
|
|
460
454
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
455
|
+
this.startTurnTimer();
|
|
461
456
|
const stream = await this.client.responses.create({
|
|
462
457
|
model: this.config.model,
|
|
463
458
|
max_output_tokens: this.config.maxTokens,
|
|
@@ -473,25 +468,19 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
473
468
|
let completedEvent = null;
|
|
474
469
|
for await (const event of stream) {
|
|
475
470
|
if (event.type === "response.output_text.delta") {
|
|
471
|
+
this.markFirstToken();
|
|
476
472
|
this.emit(AgentEvent_1.AgentEvent.CHUNK, event.delta);
|
|
477
473
|
yield { type: "text", content: event.delta };
|
|
478
474
|
}
|
|
479
475
|
if (event.type === "response.reasoning_summary_text.delta") {
|
|
476
|
+
this.markFirstToken();
|
|
480
477
|
this.emit(AgentEvent_1.AgentEvent.REASONING_CHUNK, event.delta);
|
|
481
478
|
yield { type: "reasoning", content: event.delta };
|
|
482
479
|
}
|
|
483
480
|
if (event.type === "response.completed") {
|
|
484
481
|
completedEvent = event;
|
|
485
482
|
if (event.response.usage) {
|
|
486
|
-
|
|
487
|
-
if (this.lastTokenUsage) {
|
|
488
|
-
this.lastTokenUsage.input_tokens += usage.input_tokens;
|
|
489
|
-
this.lastTokenUsage.output_tokens += usage.output_tokens;
|
|
490
|
-
this.lastTokenUsage.total_tokens += usage.total_tokens;
|
|
491
|
-
}
|
|
492
|
-
else {
|
|
493
|
-
this.lastTokenUsage = { ...usage };
|
|
494
|
-
}
|
|
483
|
+
this.accumulateUsage(this.parseUsage(event.response.usage));
|
|
495
484
|
}
|
|
496
485
|
}
|
|
497
486
|
if (event.type === "response.incomplete") {
|
|
@@ -540,6 +529,8 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
540
529
|
input_tokens: input.input_tokens,
|
|
541
530
|
output_tokens: input.output_tokens,
|
|
542
531
|
total_tokens: input.total_tokens,
|
|
532
|
+
// Reasoning tokens are already counted inside `output_tokens`.
|
|
533
|
+
reasoning_tokens: input.output_tokens_details?.reasoning_tokens,
|
|
543
534
|
};
|
|
544
535
|
}
|
|
545
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,
|
|
@@ -326,17 +320,22 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
326
320
|
let reasoningContent = "";
|
|
327
321
|
const toolCallAcc = new Map();
|
|
328
322
|
let finishReason = null;
|
|
323
|
+
let streamUsage;
|
|
329
324
|
for await (const chunk of stream) {
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
325
|
+
// Usage can ride on any chunk: OpenAI sends it on a final choice-less
|
|
326
|
+
// chunk, OpenRouter attaches it to the last content chunk (the one
|
|
327
|
+
// carrying finish_reason). Keep the most recent and fold it in once the
|
|
328
|
+
// stream ends — it is a running total for the turn, not a delta, so
|
|
329
|
+
// taking the last one covers both layouts without double-counting.
|
|
330
|
+
if (chunk.usage)
|
|
331
|
+
streamUsage = chunk.usage;
|
|
332
|
+
if (chunk.choices.length === 0)
|
|
334
333
|
continue;
|
|
335
|
-
}
|
|
336
334
|
const choice = chunk.choices[0];
|
|
337
335
|
finishReason = choice.finish_reason ?? finishReason;
|
|
338
336
|
const delta = choice.delta;
|
|
339
337
|
if (delta.content) {
|
|
338
|
+
this.markFirstToken();
|
|
340
339
|
textContent += delta.content;
|
|
341
340
|
this.emit(AgentEvent_1.AgentEvent.CHUNK, delta.content);
|
|
342
341
|
yield { type: "text", content: delta.content };
|
|
@@ -348,6 +347,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
348
347
|
const deltaExtras = delta;
|
|
349
348
|
const reasoningDelta = (deltaExtras.reasoning ?? deltaExtras.reasoning_content);
|
|
350
349
|
if (reasoningDelta) {
|
|
350
|
+
this.markFirstToken();
|
|
351
351
|
// Accumulated as well as yielded: DeepSeek's thinking mode requires the
|
|
352
352
|
// assistant turn's reasoning to be replayed on the next request, so it
|
|
353
353
|
// has to reach history rather than only the caller.
|
|
@@ -370,6 +370,10 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
}
|
|
373
|
+
// Before any early return below, so a turn that hits the token limit or
|
|
374
|
+
// continues into a tool call still reports what it spent.
|
|
375
|
+
if (streamUsage)
|
|
376
|
+
this.accumulateStreamUsage(streamUsage);
|
|
373
377
|
if (finishReason === "length") {
|
|
374
378
|
const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
|
|
375
379
|
this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
|
|
@@ -424,19 +428,12 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
424
428
|
accumulateStreamUsage(usage) {
|
|
425
429
|
if (!usage)
|
|
426
430
|
return;
|
|
427
|
-
|
|
431
|
+
this.accumulateUsage({
|
|
428
432
|
input_tokens: usage.prompt_tokens ?? 0,
|
|
429
433
|
output_tokens: usage.completion_tokens ?? 0,
|
|
430
434
|
total_tokens: usage.total_tokens ?? 0,
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
this.lastTokenUsage.input_tokens += u.input_tokens;
|
|
434
|
-
this.lastTokenUsage.output_tokens += u.output_tokens;
|
|
435
|
-
this.lastTokenUsage.total_tokens += u.total_tokens;
|
|
436
|
-
}
|
|
437
|
-
else {
|
|
438
|
-
this.lastTokenUsage = u;
|
|
439
|
-
}
|
|
435
|
+
reasoning_tokens: extractReasoningTokens(usage),
|
|
436
|
+
});
|
|
440
437
|
}
|
|
441
438
|
parseUsage(response) {
|
|
442
439
|
const usage = response.usage;
|
|
@@ -444,8 +441,38 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
|
|
|
444
441
|
input_tokens: usage?.prompt_tokens ?? 0,
|
|
445
442
|
output_tokens: usage?.completion_tokens ?? 0,
|
|
446
443
|
total_tokens: usage?.total_tokens ?? 0,
|
|
444
|
+
reasoning_tokens: extractReasoningTokens(usage),
|
|
445
|
+
...extractServerTimings(response),
|
|
447
446
|
};
|
|
448
447
|
}
|
|
449
448
|
}
|
|
450
449
|
exports.OpenAICompatibleAgent = OpenAICompatibleAgent;
|
|
450
|
+
/**
|
|
451
|
+
* Reasoning token count from `completion_tokens_details`, which reasoning
|
|
452
|
+
* models (and OpenAI-compatible proxies fronting them) fill in.
|
|
453
|
+
*/
|
|
454
|
+
function extractReasoningTokens(usage) {
|
|
455
|
+
const details = usage
|
|
456
|
+
?.completion_tokens_details;
|
|
457
|
+
return details?.reasoning_tokens;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Timings reported by llama.cpp's `llama-server`, which adds a non-standard
|
|
461
|
+
* `timings` object to its chat completion responses. Absent on every other
|
|
462
|
+
* OpenAI-compatible server, in which case the durations are measured locally.
|
|
463
|
+
*/
|
|
464
|
+
function extractServerTimings(response) {
|
|
465
|
+
const timings = response?.timings;
|
|
466
|
+
if (!timings)
|
|
467
|
+
return {};
|
|
468
|
+
const { prompt_ms, predicted_ms } = timings;
|
|
469
|
+
const totalMs = prompt_ms === undefined && predicted_ms === undefined
|
|
470
|
+
? undefined
|
|
471
|
+
: (prompt_ms ?? 0) + (predicted_ms ?? 0);
|
|
472
|
+
return {
|
|
473
|
+
timeToFirstTokenMs: prompt_ms,
|
|
474
|
+
generationMs: predicted_ms,
|
|
475
|
+
totalMs,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
451
478
|
//# sourceMappingURL=OpenAICompatibleAgent.js.map
|