@iinm/plain-agent 1.4.1 → 1.5.1
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/.config/config.predefined.json +165 -0
- package/package.json +1 -1
- package/src/agent.d.ts +3 -0
- package/src/agent.mjs +9 -0
- package/src/cliBatch.mjs +8 -1
- package/src/cliFormatter.mjs +67 -0
- package/src/cliInteractive.mjs +26 -9
- package/src/costTracker.mjs +171 -0
- package/src/main.mjs +1 -0
- package/src/modelDefinition.d.ts +7 -0
- package/src/providers/gemini.mjs +9 -7
|
@@ -140,6 +140,16 @@
|
|
|
140
140
|
"max_tokens": 32768,
|
|
141
141
|
"thinking": { "type": "enabled", "budget_tokens": 16384 }
|
|
142
142
|
}
|
|
143
|
+
},
|
|
144
|
+
"cost": {
|
|
145
|
+
"currency": "USD",
|
|
146
|
+
"unit": "1M",
|
|
147
|
+
"costs": {
|
|
148
|
+
"input_tokens": 1.0,
|
|
149
|
+
"output_tokens": 5.0,
|
|
150
|
+
"cache_read_input_tokens": 0.1,
|
|
151
|
+
"cache_creation_input_tokens": 1.25
|
|
152
|
+
}
|
|
143
153
|
}
|
|
144
154
|
},
|
|
145
155
|
{
|
|
@@ -157,6 +167,16 @@
|
|
|
157
167
|
"max_tokens": 64000,
|
|
158
168
|
"thinking": { "type": "enabled", "budget_tokens": 32768 }
|
|
159
169
|
}
|
|
170
|
+
},
|
|
171
|
+
"cost": {
|
|
172
|
+
"currency": "USD",
|
|
173
|
+
"unit": "1M",
|
|
174
|
+
"costs": {
|
|
175
|
+
"input_tokens": 1.0,
|
|
176
|
+
"output_tokens": 5.0,
|
|
177
|
+
"cache_read_input_tokens": 0.1,
|
|
178
|
+
"cache_creation_input_tokens": 1.25
|
|
179
|
+
}
|
|
160
180
|
}
|
|
161
181
|
},
|
|
162
182
|
{
|
|
@@ -174,6 +194,16 @@
|
|
|
174
194
|
"max_tokens": 32768,
|
|
175
195
|
"thinking": { "type": "enabled", "budget_tokens": 16384 }
|
|
176
196
|
}
|
|
197
|
+
},
|
|
198
|
+
"cost": {
|
|
199
|
+
"currency": "USD",
|
|
200
|
+
"unit": "1M",
|
|
201
|
+
"costs": {
|
|
202
|
+
"input_tokens": 3,
|
|
203
|
+
"output_tokens": 15,
|
|
204
|
+
"cache_read_input_tokens": 0.3,
|
|
205
|
+
"cache_creation_input_tokens": 3.75
|
|
206
|
+
}
|
|
177
207
|
}
|
|
178
208
|
},
|
|
179
209
|
{
|
|
@@ -191,6 +221,16 @@
|
|
|
191
221
|
"max_tokens": 64000,
|
|
192
222
|
"thinking": { "type": "enabled", "budget_tokens": 32768 }
|
|
193
223
|
}
|
|
224
|
+
},
|
|
225
|
+
"cost": {
|
|
226
|
+
"currency": "USD",
|
|
227
|
+
"unit": "1M",
|
|
228
|
+
"costs": {
|
|
229
|
+
"input_tokens": 3,
|
|
230
|
+
"output_tokens": 15,
|
|
231
|
+
"cache_read_input_tokens": 0.3,
|
|
232
|
+
"cache_creation_input_tokens": 3.75
|
|
233
|
+
}
|
|
194
234
|
}
|
|
195
235
|
},
|
|
196
236
|
{
|
|
@@ -208,6 +248,16 @@
|
|
|
208
248
|
"max_tokens": 32768,
|
|
209
249
|
"thinking": { "type": "enabled", "budget_tokens": 16384 }
|
|
210
250
|
}
|
|
251
|
+
},
|
|
252
|
+
"cost": {
|
|
253
|
+
"currency": "USD",
|
|
254
|
+
"unit": "1M",
|
|
255
|
+
"costs": {
|
|
256
|
+
"input_tokens": 5,
|
|
257
|
+
"output_tokens": 25,
|
|
258
|
+
"cache_read_input_tokens": 0.5,
|
|
259
|
+
"cache_creation_input_tokens": 6.25
|
|
260
|
+
}
|
|
211
261
|
}
|
|
212
262
|
},
|
|
213
263
|
{
|
|
@@ -225,6 +275,16 @@
|
|
|
225
275
|
"max_tokens": 64000,
|
|
226
276
|
"thinking": { "type": "enabled", "budget_tokens": 32768 }
|
|
227
277
|
}
|
|
278
|
+
},
|
|
279
|
+
"cost": {
|
|
280
|
+
"currency": "USD",
|
|
281
|
+
"unit": "1M",
|
|
282
|
+
"costs": {
|
|
283
|
+
"input_tokens": 5,
|
|
284
|
+
"output_tokens": 25,
|
|
285
|
+
"cache_read_input_tokens": 0.5,
|
|
286
|
+
"cache_creation_input_tokens": 6.25
|
|
287
|
+
}
|
|
228
288
|
}
|
|
229
289
|
},
|
|
230
290
|
|
|
@@ -242,6 +302,16 @@
|
|
|
242
302
|
"max_tokens": 32768,
|
|
243
303
|
"thinking": { "type": "enabled", "budget_tokens": 16384 }
|
|
244
304
|
}
|
|
305
|
+
},
|
|
306
|
+
"cost": {
|
|
307
|
+
"currency": "USD",
|
|
308
|
+
"unit": "1M",
|
|
309
|
+
"costs": {
|
|
310
|
+
"input_tokens": 1.0,
|
|
311
|
+
"output_tokens": 5.0,
|
|
312
|
+
"cache_read_input_tokens": 0.1,
|
|
313
|
+
"cache_creation_input_tokens": 1.25
|
|
314
|
+
}
|
|
245
315
|
}
|
|
246
316
|
},
|
|
247
317
|
{
|
|
@@ -258,6 +328,16 @@
|
|
|
258
328
|
"max_tokens": 64000,
|
|
259
329
|
"thinking": { "type": "enabled", "budget_tokens": 32768 }
|
|
260
330
|
}
|
|
331
|
+
},
|
|
332
|
+
"cost": {
|
|
333
|
+
"currency": "USD",
|
|
334
|
+
"unit": "1M",
|
|
335
|
+
"costs": {
|
|
336
|
+
"input_tokens": 1.0,
|
|
337
|
+
"output_tokens": 5.0,
|
|
338
|
+
"cache_read_input_tokens": 0.1,
|
|
339
|
+
"cache_creation_input_tokens": 1.25
|
|
340
|
+
}
|
|
261
341
|
}
|
|
262
342
|
},
|
|
263
343
|
{
|
|
@@ -274,6 +354,16 @@
|
|
|
274
354
|
"max_tokens": 32768,
|
|
275
355
|
"thinking": { "type": "enabled", "budget_tokens": 16384 }
|
|
276
356
|
}
|
|
357
|
+
},
|
|
358
|
+
"cost": {
|
|
359
|
+
"currency": "USD",
|
|
360
|
+
"unit": "1M",
|
|
361
|
+
"costs": {
|
|
362
|
+
"input_tokens": 3,
|
|
363
|
+
"output_tokens": 15,
|
|
364
|
+
"cache_read_input_tokens": 0.3,
|
|
365
|
+
"cache_creation_input_tokens": 3.75
|
|
366
|
+
}
|
|
277
367
|
}
|
|
278
368
|
},
|
|
279
369
|
{
|
|
@@ -290,6 +380,16 @@
|
|
|
290
380
|
"max_tokens": 64000,
|
|
291
381
|
"thinking": { "type": "enabled", "budget_tokens": 32768 }
|
|
292
382
|
}
|
|
383
|
+
},
|
|
384
|
+
"cost": {
|
|
385
|
+
"currency": "USD",
|
|
386
|
+
"unit": "1M",
|
|
387
|
+
"costs": {
|
|
388
|
+
"input_tokens": 3,
|
|
389
|
+
"output_tokens": 15,
|
|
390
|
+
"cache_read_input_tokens": 0.3,
|
|
391
|
+
"cache_creation_input_tokens": 3.75
|
|
392
|
+
}
|
|
293
393
|
}
|
|
294
394
|
},
|
|
295
395
|
{
|
|
@@ -306,6 +406,16 @@
|
|
|
306
406
|
"max_tokens": 32768,
|
|
307
407
|
"thinking": { "type": "enabled", "budget_tokens": 16384 }
|
|
308
408
|
}
|
|
409
|
+
},
|
|
410
|
+
"cost": {
|
|
411
|
+
"currency": "USD",
|
|
412
|
+
"unit": "1M",
|
|
413
|
+
"costs": {
|
|
414
|
+
"input_tokens": 5,
|
|
415
|
+
"output_tokens": 25,
|
|
416
|
+
"cache_read_input_tokens": 0.5,
|
|
417
|
+
"cache_creation_input_tokens": 6.25
|
|
418
|
+
}
|
|
309
419
|
}
|
|
310
420
|
},
|
|
311
421
|
{
|
|
@@ -322,6 +432,16 @@
|
|
|
322
432
|
"max_tokens": 64000,
|
|
323
433
|
"thinking": { "type": "enabled", "budget_tokens": 32768 }
|
|
324
434
|
}
|
|
435
|
+
},
|
|
436
|
+
"cost": {
|
|
437
|
+
"currency": "USD",
|
|
438
|
+
"unit": "1M",
|
|
439
|
+
"costs": {
|
|
440
|
+
"input_tokens": 5,
|
|
441
|
+
"output_tokens": 25,
|
|
442
|
+
"cache_read_input_tokens": 0.5,
|
|
443
|
+
"cache_creation_input_tokens": 6.25
|
|
444
|
+
}
|
|
325
445
|
}
|
|
326
446
|
},
|
|
327
447
|
|
|
@@ -349,6 +469,15 @@
|
|
|
349
469
|
}
|
|
350
470
|
}
|
|
351
471
|
}
|
|
472
|
+
},
|
|
473
|
+
"cost": {
|
|
474
|
+
"currency": "USD",
|
|
475
|
+
"unit": "1M",
|
|
476
|
+
"costs": {
|
|
477
|
+
"promptTokenCount": 0.5,
|
|
478
|
+
"cachedContentTokenCount": -0.45,
|
|
479
|
+
"candidatesTokenCount": 3
|
|
480
|
+
}
|
|
352
481
|
}
|
|
353
482
|
},
|
|
354
483
|
{
|
|
@@ -375,6 +504,15 @@
|
|
|
375
504
|
}
|
|
376
505
|
}
|
|
377
506
|
}
|
|
507
|
+
},
|
|
508
|
+
"cost": {
|
|
509
|
+
"currency": "USD",
|
|
510
|
+
"unit": "1M",
|
|
511
|
+
"costs": {
|
|
512
|
+
"promptTokenCount": 0.5,
|
|
513
|
+
"cachedContentTokenCount": -0.45,
|
|
514
|
+
"candidatesTokenCount": 3
|
|
515
|
+
}
|
|
378
516
|
}
|
|
379
517
|
},
|
|
380
518
|
{
|
|
@@ -453,6 +591,15 @@
|
|
|
453
591
|
}
|
|
454
592
|
}
|
|
455
593
|
}
|
|
594
|
+
},
|
|
595
|
+
"cost": {
|
|
596
|
+
"currency": "USD",
|
|
597
|
+
"unit": "1M",
|
|
598
|
+
"costs": {
|
|
599
|
+
"promptTokenCount": 0.5,
|
|
600
|
+
"cachedContentTokenCount": -0.45,
|
|
601
|
+
"candidatesTokenCount": 3
|
|
602
|
+
}
|
|
456
603
|
}
|
|
457
604
|
},
|
|
458
605
|
{
|
|
@@ -478,6 +625,15 @@
|
|
|
478
625
|
}
|
|
479
626
|
}
|
|
480
627
|
}
|
|
628
|
+
},
|
|
629
|
+
"cost": {
|
|
630
|
+
"currency": "USD",
|
|
631
|
+
"unit": "1M",
|
|
632
|
+
"costs": {
|
|
633
|
+
"promptTokenCount": 0.5,
|
|
634
|
+
"cachedContentTokenCount": -0.45,
|
|
635
|
+
"candidatesTokenCount": 3
|
|
636
|
+
}
|
|
481
637
|
}
|
|
482
638
|
},
|
|
483
639
|
{
|
|
@@ -699,6 +855,15 @@
|
|
|
699
855
|
"config": {
|
|
700
856
|
"model": "zai-org/glm-5-maas"
|
|
701
857
|
}
|
|
858
|
+
},
|
|
859
|
+
"cost": {
|
|
860
|
+
"currency": "USD",
|
|
861
|
+
"unit": "1M",
|
|
862
|
+
"costs": {
|
|
863
|
+
"prompt_tokens": 1,
|
|
864
|
+
"completion_tokens": 3.2,
|
|
865
|
+
"prompt_tokens_details.cached_tokens": -0.9
|
|
866
|
+
}
|
|
702
867
|
}
|
|
703
868
|
},
|
|
704
869
|
{
|
package/package.json
CHANGED
package/src/agent.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
} from "./model";
|
|
10
10
|
import type { Tool, ToolUseApprover } from "./tool";
|
|
11
11
|
import type { AgentRole } from "./context/loadAgentRoles.mjs";
|
|
12
|
+
import type { CostSummary, CostConfig } from "./costTracker.mjs";
|
|
12
13
|
|
|
13
14
|
export type Agent = {
|
|
14
15
|
userEventEmitter: UserEventEmitter;
|
|
@@ -19,6 +20,7 @@ export type Agent = {
|
|
|
19
20
|
export type AgentCommands = {
|
|
20
21
|
dumpMessages: () => Promise<void>;
|
|
21
22
|
loadMessages: () => Promise<void>;
|
|
23
|
+
getCostSummary: () => CostSummary;
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
type UserEventMap = {
|
|
@@ -45,4 +47,5 @@ export type AgentConfig = {
|
|
|
45
47
|
tools: Tool[];
|
|
46
48
|
toolUseApprover: ToolUseApprover;
|
|
47
49
|
agentRoles: Map<string, AgentRole>;
|
|
50
|
+
modelCostConfig?: CostConfig;
|
|
48
51
|
};
|
package/src/agent.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { EventEmitter } from "node:events";
|
|
|
9
9
|
import fs from "node:fs/promises";
|
|
10
10
|
import { createAgentLoop } from "./agentLoop.mjs";
|
|
11
11
|
import { createStateManager } from "./agentState.mjs";
|
|
12
|
+
import { createCostTracker } from "./costTracker.mjs";
|
|
12
13
|
import { MESSAGES_DUMP_FILE_PATH } from "./env.mjs";
|
|
13
14
|
import { createSubagentManager } from "./subagent.mjs";
|
|
14
15
|
import { createToolExecutor } from "./toolExecutor.mjs";
|
|
@@ -25,12 +26,19 @@ export function createAgent({
|
|
|
25
26
|
tools,
|
|
26
27
|
toolUseApprover,
|
|
27
28
|
agentRoles,
|
|
29
|
+
modelCostConfig,
|
|
28
30
|
}) {
|
|
29
31
|
/** @type {UserEventEmitter} */
|
|
30
32
|
const userEventEmitter = new EventEmitter();
|
|
31
33
|
/** @type {AgentEventEmitter} */
|
|
32
34
|
const agentEventEmitter = new EventEmitter();
|
|
33
35
|
|
|
36
|
+
const costTracker = createCostTracker(modelCostConfig);
|
|
37
|
+
|
|
38
|
+
agentEventEmitter.on("providerTokenUsage", (usage) => {
|
|
39
|
+
costTracker.recordUsage(usage);
|
|
40
|
+
});
|
|
41
|
+
|
|
34
42
|
const stateManager = createStateManager(
|
|
35
43
|
[
|
|
36
44
|
{
|
|
@@ -154,6 +162,7 @@ export function createAgent({
|
|
|
154
162
|
agentCommands: {
|
|
155
163
|
dumpMessages,
|
|
156
164
|
loadMessages,
|
|
165
|
+
getCostSummary: () => costTracker.calculateCost(),
|
|
157
166
|
},
|
|
158
167
|
};
|
|
159
168
|
}
|
package/src/cliBatch.mjs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import { UserEventEmitter, AgentEventEmitter } from "./agent"
|
|
2
|
+
* @import { UserEventEmitter, AgentEventEmitter, AgentCommands } from "./agent"
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { formatCostForBatch } from "./cliFormatter.mjs";
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* @typedef {object} BatchSessionOptions
|
|
7
9
|
* @property {UserEventEmitter} userEventEmitter
|
|
8
10
|
* @property {AgentEventEmitter} agentEventEmitter
|
|
11
|
+
* @property {AgentCommands} agentCommands
|
|
9
12
|
* @property {string} task - Task instruction to execute
|
|
10
13
|
* @property {string} sessionId
|
|
11
14
|
* @property {string} modelName
|
|
@@ -23,6 +26,7 @@
|
|
|
23
26
|
export async function startBatchSession({
|
|
24
27
|
userEventEmitter,
|
|
25
28
|
agentEventEmitter,
|
|
29
|
+
agentCommands,
|
|
26
30
|
task,
|
|
27
31
|
sessionId,
|
|
28
32
|
modelName,
|
|
@@ -35,9 +39,12 @@ export async function startBatchSession({
|
|
|
35
39
|
|
|
36
40
|
await new Promise((/** @type {(value?: void) => void} */ resolve) => {
|
|
37
41
|
agentEventEmitter.on("turnEnd", async () => {
|
|
42
|
+
const costSummary = agentCommands.getCostSummary();
|
|
43
|
+
|
|
38
44
|
outputEvent({
|
|
39
45
|
type: "session_end",
|
|
40
46
|
timestamp: new Date().toISOString(),
|
|
47
|
+
cost: formatCostForBatch(costSummary),
|
|
41
48
|
});
|
|
42
49
|
await onStop();
|
|
43
50
|
resolve();
|
package/src/cliFormatter.mjs
CHANGED
|
@@ -206,3 +206,70 @@ export function formatProviderTokenUsage(usage) {
|
|
|
206
206
|
|
|
207
207
|
return styleText("gray", outputLines.join("\n"));
|
|
208
208
|
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Format cost summary for interactive display
|
|
212
|
+
* @param {import("./costTracker.mjs").CostSummary} summary
|
|
213
|
+
* @returns {string}
|
|
214
|
+
*/
|
|
215
|
+
export function formatCostSummary(summary) {
|
|
216
|
+
if (!summary || Object.keys(summary.breakdown).length === 0) {
|
|
217
|
+
return styleText("gray", "No token usage recorded yet.");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const lines = [];
|
|
221
|
+
|
|
222
|
+
// Header
|
|
223
|
+
lines.push(styleText("bold", "\nSession Cost Summary\n"));
|
|
224
|
+
|
|
225
|
+
// Tokens
|
|
226
|
+
lines.push(styleText("bold", "Tokens:"));
|
|
227
|
+
|
|
228
|
+
for (const [key, { tokens, cost }] of Object.entries(summary.breakdown)) {
|
|
229
|
+
const tokenStr = `${key}: ${tokens.toLocaleString()}`;
|
|
230
|
+
|
|
231
|
+
if (cost !== undefined) {
|
|
232
|
+
const costStr = `${cost.toFixed(4)} ${summary.currency}`;
|
|
233
|
+
lines.push(` ${tokenStr.padEnd(30)} ${styleText("cyan", costStr)}`);
|
|
234
|
+
} else {
|
|
235
|
+
lines.push(` ${tokenStr.padEnd(30)} ${styleText("gray", "N/A")}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Total
|
|
240
|
+
lines.push("");
|
|
241
|
+
if (summary.totalCost !== undefined) {
|
|
242
|
+
lines.push(
|
|
243
|
+
styleText(
|
|
244
|
+
"bold",
|
|
245
|
+
`Total: ${summary.totalCost.toFixed(4)} ${summary.currency}`,
|
|
246
|
+
),
|
|
247
|
+
);
|
|
248
|
+
} else {
|
|
249
|
+
lines.push(styleText("yellow", "Total: N/A (no cost configuration)"));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return lines.join("\n");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Format cost for batch mode JSON output
|
|
257
|
+
* @param {import("./costTracker.mjs").CostSummary} summary
|
|
258
|
+
*/
|
|
259
|
+
export function formatCostForBatch(summary) {
|
|
260
|
+
if (!summary || Object.keys(summary.breakdown).length === 0) {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
total: summary.totalCost,
|
|
266
|
+
currency: summary.currency,
|
|
267
|
+
unit: summary.unit,
|
|
268
|
+
breakdown: Object.fromEntries(
|
|
269
|
+
Object.entries(summary.breakdown).map(([key, { tokens, cost }]) => [
|
|
270
|
+
key,
|
|
271
|
+
{ tokens, cost },
|
|
272
|
+
]),
|
|
273
|
+
),
|
|
274
|
+
};
|
|
275
|
+
}
|
package/src/cliInteractive.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { execFileSync } from "node:child_process";
|
|
|
8
8
|
import readline from "node:readline";
|
|
9
9
|
import { styleText } from "node:util";
|
|
10
10
|
import {
|
|
11
|
+
formatCostSummary,
|
|
11
12
|
formatProviderTokenUsage,
|
|
12
13
|
formatToolResult,
|
|
13
14
|
formatToolUse,
|
|
@@ -47,6 +48,7 @@ const SLASH_COMMANDS = [
|
|
|
47
48
|
},
|
|
48
49
|
{ name: "/dump", description: "Save current messages to a JSON file" },
|
|
49
50
|
{ name: "/load", description: "Load messages from a JSON file" },
|
|
51
|
+
{ name: "/cost", description: "Display session cost and token usage" },
|
|
50
52
|
];
|
|
51
53
|
|
|
52
54
|
/**
|
|
@@ -237,7 +239,6 @@ export function startInteractiveSession({
|
|
|
237
239
|
console.log(styleText("gray", "</agent>"));
|
|
238
240
|
|
|
239
241
|
userEventEmitter.emit("userInput", [{ type: "text", text: message }]);
|
|
240
|
-
state.turn = false;
|
|
241
242
|
}
|
|
242
243
|
|
|
243
244
|
/**
|
|
@@ -252,6 +253,7 @@ export function startInteractiveSession({
|
|
|
252
253
|
|
|
253
254
|
if (!prompt) {
|
|
254
255
|
console.log(styleText("red", `\nPrompt not found: ${id}`));
|
|
256
|
+
state.turn = true;
|
|
255
257
|
cli.prompt();
|
|
256
258
|
return;
|
|
257
259
|
}
|
|
@@ -266,7 +268,6 @@ export function startInteractiveSession({
|
|
|
266
268
|
console.log(styleText("gray", "</prompt>"));
|
|
267
269
|
|
|
268
270
|
userEventEmitter.emit("userInput", [{ type: "text", text: message }]);
|
|
269
|
-
state.turn = false;
|
|
270
271
|
}
|
|
271
272
|
|
|
272
273
|
const getCliPrompt = (subagentName = "") =>
|
|
@@ -388,9 +389,13 @@ export function startInteractiveSession({
|
|
|
388
389
|
* @returns {Promise<void>}
|
|
389
390
|
*/
|
|
390
391
|
async function processInput(input) {
|
|
392
|
+
// Prevent concurrent input processing from multi-line paste
|
|
393
|
+
state.turn = false;
|
|
394
|
+
|
|
391
395
|
const inputTrimmed = input.trim();
|
|
392
396
|
|
|
393
397
|
if (inputTrimmed.length === 0) {
|
|
398
|
+
state.turn = true;
|
|
394
399
|
cli.prompt();
|
|
395
400
|
return;
|
|
396
401
|
}
|
|
@@ -400,6 +405,7 @@ export function startInteractiveSession({
|
|
|
400
405
|
|
|
401
406
|
if (["/help", "help"].includes(inputTrimmed.toLowerCase())) {
|
|
402
407
|
console.log(`\n${HELP_MESSAGE}`);
|
|
408
|
+
state.turn = true;
|
|
403
409
|
cli.prompt();
|
|
404
410
|
return;
|
|
405
411
|
}
|
|
@@ -408,6 +414,7 @@ export function startInteractiveSession({
|
|
|
408
414
|
const fileRange = parseFileRange(inputTrimmed.slice(1));
|
|
409
415
|
if (fileRange instanceof Error) {
|
|
410
416
|
console.log(styleText("red", `\n${fileRange.message}`));
|
|
417
|
+
state.turn = true;
|
|
411
418
|
cli.prompt();
|
|
412
419
|
return;
|
|
413
420
|
}
|
|
@@ -415,6 +422,7 @@ export function startInteractiveSession({
|
|
|
415
422
|
const fileContent = await readFileRange(fileRange);
|
|
416
423
|
if (fileContent instanceof Error) {
|
|
417
424
|
console.log(styleText("red", `\n${fileContent.message}`));
|
|
425
|
+
state.turn = true;
|
|
418
426
|
cli.prompt();
|
|
419
427
|
return;
|
|
420
428
|
}
|
|
@@ -426,18 +434,27 @@ export function startInteractiveSession({
|
|
|
426
434
|
const messageWithContext = await loadUserMessageContext(fileContent);
|
|
427
435
|
|
|
428
436
|
userEventEmitter.emit("userInput", messageWithContext);
|
|
429
|
-
state.turn = false;
|
|
430
437
|
return;
|
|
431
438
|
}
|
|
432
439
|
|
|
433
440
|
if (inputTrimmed.toLowerCase() === "/dump") {
|
|
434
441
|
await agentCommands.dumpMessages();
|
|
442
|
+
state.turn = true;
|
|
435
443
|
cli.prompt();
|
|
436
444
|
return;
|
|
437
445
|
}
|
|
438
446
|
|
|
439
447
|
if (inputTrimmed.toLowerCase() === "/load") {
|
|
440
448
|
await agentCommands.loadMessages();
|
|
449
|
+
state.turn = true;
|
|
450
|
+
cli.prompt();
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (inputTrimmed.toLowerCase() === "/cost") {
|
|
455
|
+
const summary = agentCommands.getCostSummary();
|
|
456
|
+
console.log(formatCostSummary(summary));
|
|
457
|
+
state.turn = true;
|
|
441
458
|
cli.prompt();
|
|
442
459
|
return;
|
|
443
460
|
}
|
|
@@ -457,6 +474,7 @@ export function startInteractiveSession({
|
|
|
457
474
|
);
|
|
458
475
|
}
|
|
459
476
|
}
|
|
477
|
+
state.turn = true;
|
|
460
478
|
cli.prompt();
|
|
461
479
|
return;
|
|
462
480
|
}
|
|
@@ -477,6 +495,7 @@ export function startInteractiveSession({
|
|
|
477
495
|
);
|
|
478
496
|
}
|
|
479
497
|
}
|
|
498
|
+
state.turn = true;
|
|
480
499
|
cli.prompt();
|
|
481
500
|
return;
|
|
482
501
|
}
|
|
@@ -485,6 +504,7 @@ export function startInteractiveSession({
|
|
|
485
504
|
const match = inputTrimmed.match(/^\/prompts:([^ ]+)(?:\s+(.*))?$/);
|
|
486
505
|
if (!match) {
|
|
487
506
|
console.log(styleText("red", "\nInvalid prompt invocation format."));
|
|
507
|
+
state.turn = true;
|
|
488
508
|
cli.prompt();
|
|
489
509
|
return;
|
|
490
510
|
}
|
|
@@ -497,6 +517,7 @@ export function startInteractiveSession({
|
|
|
497
517
|
const match = inputTrimmed.match(/^\/agents:([^ ]+)(?:\s+(.*))?$/);
|
|
498
518
|
if (!match) {
|
|
499
519
|
console.log(styleText("red", "\nInvalid agent invocation format."));
|
|
520
|
+
state.turn = true;
|
|
500
521
|
cli.prompt();
|
|
501
522
|
return;
|
|
502
523
|
}
|
|
@@ -521,6 +542,7 @@ export function startInteractiveSession({
|
|
|
521
542
|
`\nUnsupported platform for /paste: ${process.platform}`,
|
|
522
543
|
),
|
|
523
544
|
);
|
|
545
|
+
state.turn = true;
|
|
524
546
|
cli.prompt();
|
|
525
547
|
return;
|
|
526
548
|
}
|
|
@@ -532,6 +554,7 @@ export function startInteractiveSession({
|
|
|
532
554
|
`\nFailed to get clipboard content: ${errorMessage}`,
|
|
533
555
|
),
|
|
534
556
|
);
|
|
557
|
+
state.turn = true;
|
|
535
558
|
cli.prompt();
|
|
536
559
|
return;
|
|
537
560
|
}
|
|
@@ -544,7 +567,6 @@ export function startInteractiveSession({
|
|
|
544
567
|
|
|
545
568
|
const messageWithContext = await loadUserMessageContext(combinedInput);
|
|
546
569
|
userEventEmitter.emit("userInput", messageWithContext);
|
|
547
|
-
state.turn = false;
|
|
548
570
|
return;
|
|
549
571
|
}
|
|
550
572
|
|
|
@@ -565,7 +587,6 @@ export function startInteractiveSession({
|
|
|
565
587
|
|
|
566
588
|
const messageWithContext = await loadUserMessageContext(inputTrimmed);
|
|
567
589
|
userEventEmitter.emit("userInput", messageWithContext);
|
|
568
|
-
state.turn = false;
|
|
569
590
|
}
|
|
570
591
|
|
|
571
592
|
cli.on("line", async (lineInput) => {
|
|
@@ -627,10 +648,6 @@ export function startInteractiveSession({
|
|
|
627
648
|
});
|
|
628
649
|
|
|
629
650
|
agentEventEmitter.on("message", (message) => {
|
|
630
|
-
// Skip user message
|
|
631
|
-
if (state.turn) {
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
651
|
printMessage(message);
|
|
635
652
|
});
|
|
636
653
|
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { ProviderTokenUsage } from "./model"
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} TokenBreakdown
|
|
7
|
+
* @property {number} tokens - Token count
|
|
8
|
+
* @property {number | undefined} cost - Cost (undefined if no pricing)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} CostSummary
|
|
13
|
+
* @property {string} currency - Currency code (e.g., "USD")
|
|
14
|
+
* @property {string} unit - Unit size (e.g., "1M")
|
|
15
|
+
* @property {Record<string, TokenBreakdown>} breakdown - Token breakdown
|
|
16
|
+
* @property {number | undefined} totalCost - Total cost (undefined if no pricing)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} CostConfig
|
|
21
|
+
* @property {string} currency
|
|
22
|
+
* @property {string} unit
|
|
23
|
+
* @property {Record<string, number>} costs
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Object} CostTracker
|
|
28
|
+
* @property {(usage: ProviderTokenUsage) => void} recordUsage - Record token usage
|
|
29
|
+
* @property {() => Record<string, number>} getAggregatedUsage - Get aggregated usage
|
|
30
|
+
* @property {() => CostSummary} calculateCost - Calculate cost summary
|
|
31
|
+
* @property {() => boolean} hasUsage - Check if any usage recorded
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a cost tracker for session token usage
|
|
36
|
+
* @param {CostConfig} [costConfig] - Optional cost configuration
|
|
37
|
+
* @returns {CostTracker}
|
|
38
|
+
*/
|
|
39
|
+
export function createCostTracker(costConfig) {
|
|
40
|
+
/** @type {ProviderTokenUsage[]} */
|
|
41
|
+
const usageHistory = [];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Record token usage from a provider
|
|
45
|
+
* @param {ProviderTokenUsage} usage
|
|
46
|
+
*/
|
|
47
|
+
function recordUsage(usage) {
|
|
48
|
+
if (typeof usage === "object" && usage !== null) {
|
|
49
|
+
usageHistory.push(usage);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get aggregated token usage
|
|
55
|
+
* @returns {Record<string, number>}
|
|
56
|
+
*/
|
|
57
|
+
function getAggregatedUsage() {
|
|
58
|
+
return aggregateTokens(usageHistory);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Calculate cost summary
|
|
63
|
+
* @returns {CostSummary}
|
|
64
|
+
*/
|
|
65
|
+
function calculateCost() {
|
|
66
|
+
const aggregated = aggregateTokens(usageHistory);
|
|
67
|
+
return calculateCostFromConfig(aggregated, costConfig);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if any usage recorded
|
|
72
|
+
* @returns {boolean}
|
|
73
|
+
*/
|
|
74
|
+
function hasUsage() {
|
|
75
|
+
return usageHistory.length > 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
recordUsage,
|
|
80
|
+
getAggregatedUsage,
|
|
81
|
+
calculateCost,
|
|
82
|
+
hasUsage,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Aggregate token usage history by key
|
|
88
|
+
* @param {ProviderTokenUsage[]} usageHistory
|
|
89
|
+
* @returns {Record<string, number>}
|
|
90
|
+
*/
|
|
91
|
+
function aggregateTokens(usageHistory) {
|
|
92
|
+
/** @type {Record<string, number>} */
|
|
93
|
+
const aggregated = {};
|
|
94
|
+
|
|
95
|
+
for (const usage of usageHistory) {
|
|
96
|
+
recursivelySumValues(usage, [], aggregated);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return aggregated;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Recursively sum numeric values in token usage
|
|
104
|
+
* @param {ProviderTokenUsage} obj
|
|
105
|
+
* @param {string[]} path
|
|
106
|
+
* @param {Record<string, number>} result
|
|
107
|
+
*/
|
|
108
|
+
function recursivelySumValues(obj, path, result) {
|
|
109
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
110
|
+
const currentPath = [...path, key];
|
|
111
|
+
const pathStr = currentPath.join(".");
|
|
112
|
+
|
|
113
|
+
if (typeof value === "number") {
|
|
114
|
+
result[pathStr] = (result[pathStr] || 0) + value;
|
|
115
|
+
} else if (
|
|
116
|
+
typeof value === "object" &&
|
|
117
|
+
value !== null &&
|
|
118
|
+
!Array.isArray(value)
|
|
119
|
+
) {
|
|
120
|
+
recursivelySumValues(value, currentPath, result);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Calculate cost from aggregated tokens and config
|
|
127
|
+
* @param {Record<string, number>} aggregated
|
|
128
|
+
* @param {CostConfig | undefined} config
|
|
129
|
+
* @returns {CostSummary}
|
|
130
|
+
*/
|
|
131
|
+
function calculateCostFromConfig(aggregated, config) {
|
|
132
|
+
/** @type {Record<string, TokenBreakdown>} */
|
|
133
|
+
const breakdown = {};
|
|
134
|
+
let totalCost = 0;
|
|
135
|
+
const hasPricing = config?.costs;
|
|
136
|
+
|
|
137
|
+
for (const [key, tokens] of Object.entries(aggregated)) {
|
|
138
|
+
breakdown[key] = { tokens, cost: undefined };
|
|
139
|
+
|
|
140
|
+
if (!hasPricing || !config.costs[key]) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const costValue = config.costs[key];
|
|
145
|
+
const unitSize = parseUnit(config.unit);
|
|
146
|
+
|
|
147
|
+
if (typeof costValue === "number") {
|
|
148
|
+
const cost = (tokens * costValue) / unitSize;
|
|
149
|
+
breakdown[key].cost = cost;
|
|
150
|
+
totalCost += cost;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
currency: config?.currency || "USD",
|
|
156
|
+
unit: config?.unit || "1M",
|
|
157
|
+
breakdown,
|
|
158
|
+
totalCost: hasPricing ? totalCost : undefined,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Parse unit string to number
|
|
164
|
+
* @param {string} unit
|
|
165
|
+
* @returns {number}
|
|
166
|
+
*/
|
|
167
|
+
function parseUnit(unit) {
|
|
168
|
+
if (unit === "1M") return 1_000_000;
|
|
169
|
+
if (unit === "1K") return 1_000;
|
|
170
|
+
return 1;
|
|
171
|
+
}
|
package/src/main.mjs
CHANGED
package/src/modelDefinition.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type ModelDefinition = {
|
|
|
9
9
|
variant: string;
|
|
10
10
|
platform: PlatformConfig;
|
|
11
11
|
model: ModelConfig;
|
|
12
|
+
cost?: CostConfig;
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
export type PlatformConfig =
|
|
@@ -76,3 +77,9 @@ export type ModelConfig =
|
|
|
76
77
|
format: "bedrock-converse";
|
|
77
78
|
config: BedrockConverseModelConfig;
|
|
78
79
|
};
|
|
80
|
+
|
|
81
|
+
export type CostConfig = {
|
|
82
|
+
currency: string;
|
|
83
|
+
unit: string;
|
|
84
|
+
costs: Record<string, number>;
|
|
85
|
+
};
|
package/src/providers/gemini.mjs
CHANGED
|
@@ -204,13 +204,15 @@ export function createCacheEnabledGeminiModelCaller(
|
|
|
204
204
|
|
|
205
205
|
/** @type {ProviderTokenUsage} */
|
|
206
206
|
const tokenUsage = {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
207
|
+
promptTokenCount: content.usageMetadata.promptTokenCount,
|
|
208
|
+
// nonCachedPromptTokenCount:
|
|
209
|
+
// content.usageMetadata.promptTokenCount -
|
|
210
|
+
// (content.usageMetadata.cachedContentTokenCount ?? 0),
|
|
211
|
+
cachedContentTokenCount:
|
|
212
|
+
content.usageMetadata.cachedContentTokenCount ?? 0,
|
|
213
|
+
candidatesTokenCount: content.usageMetadata.candidatesTokenCount ?? 0,
|
|
214
|
+
thoughtsTokenCount: content.usageMetadata.thoughtsTokenCount ?? 0,
|
|
215
|
+
totalTokenCount: content.usageMetadata.totalTokenCount,
|
|
214
216
|
};
|
|
215
217
|
|
|
216
218
|
const message = convertGeminiAssistantMessageToGenericFormat(content);
|