@iinm/plain-agent 1.4.0 → 1.5.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/.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 +36 -8
- package/src/costTracker.mjs +171 -0
- package/src/main.mjs +1 -0
- package/src/mcp.mjs +1 -1
- package/src/modelDefinition.d.ts +7 -0
- package/src/providers/anthropic.mjs +5 -4
- package/src/providers/bedrock.mjs +5 -4
- package/src/providers/gemini.mjs +9 -7
- package/src/providers/openaiCompatible.mjs +5 -4
|
@@ -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
|
/**
|
|
@@ -214,11 +216,12 @@ export function startInteractiveSession({
|
|
|
214
216
|
onStop,
|
|
215
217
|
claudeCodePlugins,
|
|
216
218
|
}) {
|
|
217
|
-
/** @type {{ turn: boolean, multiLineBuffer: string[] | null, subagentName: string }} */
|
|
219
|
+
/** @type {{ turn: boolean, multiLineBuffer: string[] | null, subagentName: string, skipNextUserMessage: boolean }} */
|
|
218
220
|
const state = {
|
|
219
221
|
turn: true,
|
|
220
222
|
multiLineBuffer: null,
|
|
221
223
|
subagentName: "",
|
|
224
|
+
skipNextUserMessage: false,
|
|
222
225
|
};
|
|
223
226
|
|
|
224
227
|
/**
|
|
@@ -236,8 +239,8 @@ export function startInteractiveSession({
|
|
|
236
239
|
console.log(message);
|
|
237
240
|
console.log(styleText("gray", "</agent>"));
|
|
238
241
|
|
|
242
|
+
state.skipNextUserMessage = true;
|
|
239
243
|
userEventEmitter.emit("userInput", [{ type: "text", text: message }]);
|
|
240
|
-
state.turn = false;
|
|
241
244
|
}
|
|
242
245
|
|
|
243
246
|
/**
|
|
@@ -252,6 +255,7 @@ export function startInteractiveSession({
|
|
|
252
255
|
|
|
253
256
|
if (!prompt) {
|
|
254
257
|
console.log(styleText("red", `\nPrompt not found: ${id}`));
|
|
258
|
+
state.turn = true;
|
|
255
259
|
cli.prompt();
|
|
256
260
|
return;
|
|
257
261
|
}
|
|
@@ -265,8 +269,8 @@ export function startInteractiveSession({
|
|
|
265
269
|
console.log(message);
|
|
266
270
|
console.log(styleText("gray", "</prompt>"));
|
|
267
271
|
|
|
272
|
+
state.skipNextUserMessage = true;
|
|
268
273
|
userEventEmitter.emit("userInput", [{ type: "text", text: message }]);
|
|
269
|
-
state.turn = false;
|
|
270
274
|
}
|
|
271
275
|
|
|
272
276
|
const getCliPrompt = (subagentName = "") =>
|
|
@@ -388,9 +392,13 @@ export function startInteractiveSession({
|
|
|
388
392
|
* @returns {Promise<void>}
|
|
389
393
|
*/
|
|
390
394
|
async function processInput(input) {
|
|
395
|
+
// Prevent concurrent input processing from multi-line paste
|
|
396
|
+
state.turn = false;
|
|
397
|
+
|
|
391
398
|
const inputTrimmed = input.trim();
|
|
392
399
|
|
|
393
400
|
if (inputTrimmed.length === 0) {
|
|
401
|
+
state.turn = true;
|
|
394
402
|
cli.prompt();
|
|
395
403
|
return;
|
|
396
404
|
}
|
|
@@ -400,6 +408,7 @@ export function startInteractiveSession({
|
|
|
400
408
|
|
|
401
409
|
if (["/help", "help"].includes(inputTrimmed.toLowerCase())) {
|
|
402
410
|
console.log(`\n${HELP_MESSAGE}`);
|
|
411
|
+
state.turn = true;
|
|
403
412
|
cli.prompt();
|
|
404
413
|
return;
|
|
405
414
|
}
|
|
@@ -408,6 +417,7 @@ export function startInteractiveSession({
|
|
|
408
417
|
const fileRange = parseFileRange(inputTrimmed.slice(1));
|
|
409
418
|
if (fileRange instanceof Error) {
|
|
410
419
|
console.log(styleText("red", `\n${fileRange.message}`));
|
|
420
|
+
state.turn = true;
|
|
411
421
|
cli.prompt();
|
|
412
422
|
return;
|
|
413
423
|
}
|
|
@@ -415,6 +425,7 @@ export function startInteractiveSession({
|
|
|
415
425
|
const fileContent = await readFileRange(fileRange);
|
|
416
426
|
if (fileContent instanceof Error) {
|
|
417
427
|
console.log(styleText("red", `\n${fileContent.message}`));
|
|
428
|
+
state.turn = true;
|
|
418
429
|
cli.prompt();
|
|
419
430
|
return;
|
|
420
431
|
}
|
|
@@ -425,19 +436,29 @@ export function startInteractiveSession({
|
|
|
425
436
|
|
|
426
437
|
const messageWithContext = await loadUserMessageContext(fileContent);
|
|
427
438
|
|
|
439
|
+
state.skipNextUserMessage = true;
|
|
428
440
|
userEventEmitter.emit("userInput", messageWithContext);
|
|
429
|
-
state.turn = false;
|
|
430
441
|
return;
|
|
431
442
|
}
|
|
432
443
|
|
|
433
444
|
if (inputTrimmed.toLowerCase() === "/dump") {
|
|
434
445
|
await agentCommands.dumpMessages();
|
|
446
|
+
state.turn = true;
|
|
435
447
|
cli.prompt();
|
|
436
448
|
return;
|
|
437
449
|
}
|
|
438
450
|
|
|
439
451
|
if (inputTrimmed.toLowerCase() === "/load") {
|
|
440
452
|
await agentCommands.loadMessages();
|
|
453
|
+
state.turn = true;
|
|
454
|
+
cli.prompt();
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (inputTrimmed.toLowerCase() === "/cost") {
|
|
459
|
+
const summary = agentCommands.getCostSummary();
|
|
460
|
+
console.log(formatCostSummary(summary));
|
|
461
|
+
state.turn = true;
|
|
441
462
|
cli.prompt();
|
|
442
463
|
return;
|
|
443
464
|
}
|
|
@@ -457,6 +478,7 @@ export function startInteractiveSession({
|
|
|
457
478
|
);
|
|
458
479
|
}
|
|
459
480
|
}
|
|
481
|
+
state.turn = true;
|
|
460
482
|
cli.prompt();
|
|
461
483
|
return;
|
|
462
484
|
}
|
|
@@ -477,6 +499,7 @@ export function startInteractiveSession({
|
|
|
477
499
|
);
|
|
478
500
|
}
|
|
479
501
|
}
|
|
502
|
+
state.turn = true;
|
|
480
503
|
cli.prompt();
|
|
481
504
|
return;
|
|
482
505
|
}
|
|
@@ -485,6 +508,7 @@ export function startInteractiveSession({
|
|
|
485
508
|
const match = inputTrimmed.match(/^\/prompts:([^ ]+)(?:\s+(.*))?$/);
|
|
486
509
|
if (!match) {
|
|
487
510
|
console.log(styleText("red", "\nInvalid prompt invocation format."));
|
|
511
|
+
state.turn = true;
|
|
488
512
|
cli.prompt();
|
|
489
513
|
return;
|
|
490
514
|
}
|
|
@@ -497,6 +521,7 @@ export function startInteractiveSession({
|
|
|
497
521
|
const match = inputTrimmed.match(/^\/agents:([^ ]+)(?:\s+(.*))?$/);
|
|
498
522
|
if (!match) {
|
|
499
523
|
console.log(styleText("red", "\nInvalid agent invocation format."));
|
|
524
|
+
state.turn = true;
|
|
500
525
|
cli.prompt();
|
|
501
526
|
return;
|
|
502
527
|
}
|
|
@@ -521,6 +546,7 @@ export function startInteractiveSession({
|
|
|
521
546
|
`\nUnsupported platform for /paste: ${process.platform}`,
|
|
522
547
|
),
|
|
523
548
|
);
|
|
549
|
+
state.turn = true;
|
|
524
550
|
cli.prompt();
|
|
525
551
|
return;
|
|
526
552
|
}
|
|
@@ -532,6 +558,7 @@ export function startInteractiveSession({
|
|
|
532
558
|
`\nFailed to get clipboard content: ${errorMessage}`,
|
|
533
559
|
),
|
|
534
560
|
);
|
|
561
|
+
state.turn = true;
|
|
535
562
|
cli.prompt();
|
|
536
563
|
return;
|
|
537
564
|
}
|
|
@@ -543,8 +570,8 @@ export function startInteractiveSession({
|
|
|
543
570
|
console.log(styleText("gray", "</paste>"));
|
|
544
571
|
|
|
545
572
|
const messageWithContext = await loadUserMessageContext(combinedInput);
|
|
573
|
+
state.skipNextUserMessage = true;
|
|
546
574
|
userEventEmitter.emit("userInput", messageWithContext);
|
|
547
|
-
state.turn = false;
|
|
548
575
|
return;
|
|
549
576
|
}
|
|
550
577
|
|
|
@@ -564,8 +591,8 @@ export function startInteractiveSession({
|
|
|
564
591
|
}
|
|
565
592
|
|
|
566
593
|
const messageWithContext = await loadUserMessageContext(inputTrimmed);
|
|
594
|
+
state.skipNextUserMessage = true;
|
|
567
595
|
userEventEmitter.emit("userInput", messageWithContext);
|
|
568
|
-
state.turn = false;
|
|
569
596
|
}
|
|
570
597
|
|
|
571
598
|
cli.on("line", async (lineInput) => {
|
|
@@ -627,8 +654,9 @@ export function startInteractiveSession({
|
|
|
627
654
|
});
|
|
628
655
|
|
|
629
656
|
agentEventEmitter.on("message", (message) => {
|
|
630
|
-
// Skip user message
|
|
631
|
-
if (state.
|
|
657
|
+
// Skip user input message (echoing back what the user just sent)
|
|
658
|
+
if (state.skipNextUserMessage) {
|
|
659
|
+
state.skipNextUserMessage = false;
|
|
632
660
|
return;
|
|
633
661
|
}
|
|
634
662
|
printMessage(message);
|
|
@@ -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/mcp.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @import { Client } from "@modelcontextprotocol/client";
|
|
2
3
|
* @import { StructuredToolResultContent, Tool, ToolImplementation } from "./tool";
|
|
3
4
|
* @import { MCPServerConfig } from "./config";
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { mkdir, open } from "node:fs/promises";
|
|
7
8
|
import path from "node:path";
|
|
8
|
-
import { Client } from "@modelcontextprotocol/client";
|
|
9
9
|
import { AGENT_PROJECT_METADATA_DIR } from "./env.mjs";
|
|
10
10
|
import { writeTmpFile } from "./tmpfile.mjs";
|
|
11
11
|
import { noThrow } from "./utils/noThrow.mjs";
|
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
|
+
};
|
|
@@ -5,10 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { styleText } from "node:util";
|
|
8
|
-
import { Sha256 } from "@aws-crypto/sha256-js";
|
|
9
|
-
import { fromIni } from "@aws-sdk/credential-providers";
|
|
10
|
-
import { HttpRequest } from "@smithy/protocol-http";
|
|
11
|
-
import { SignatureV4 } from "@smithy/signature-v4";
|
|
12
8
|
import { noThrow } from "../utils/noThrow.mjs";
|
|
13
9
|
import { readBedrockStreamEvents } from "./platform/bedrock.mjs";
|
|
14
10
|
import { getGoogleCloudAccessToken } from "./platform/googleCloud.mjs";
|
|
@@ -112,6 +108,11 @@ export async function callAnthropicModel(
|
|
|
112
108
|
|
|
113
109
|
// bedrock + sso profile
|
|
114
110
|
const runFetchForBedrock = async () => {
|
|
111
|
+
const { Sha256 } = await import("@aws-crypto/sha256-js");
|
|
112
|
+
const { fromIni } = await import("@aws-sdk/credential-providers");
|
|
113
|
+
const { HttpRequest } = await import("@smithy/protocol-http");
|
|
114
|
+
const { SignatureV4 } = await import("@smithy/signature-v4");
|
|
115
|
+
|
|
115
116
|
const region =
|
|
116
117
|
url.match(/bedrock-runtime\.([\w-]+)\.amazonaws\.com/)?.[1] ?? "";
|
|
117
118
|
const urlParsed = new URL(url);
|
|
@@ -5,10 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { styleText } from "node:util";
|
|
8
|
-
import { Sha256 } from "@aws-crypto/sha256-js";
|
|
9
|
-
import { fromIni } from "@aws-sdk/credential-providers";
|
|
10
|
-
import { HttpRequest } from "@smithy/protocol-http";
|
|
11
|
-
import { SignatureV4 } from "@smithy/signature-v4";
|
|
12
8
|
import { noThrow } from "../utils/noThrow.mjs";
|
|
13
9
|
import { readBedrockStreamEvents } from "./platform/bedrock.mjs";
|
|
14
10
|
|
|
@@ -25,6 +21,11 @@ export async function callBedrockConverseModel(
|
|
|
25
21
|
input,
|
|
26
22
|
retryCount = 0,
|
|
27
23
|
) {
|
|
24
|
+
const { Sha256 } = await import("@aws-crypto/sha256-js");
|
|
25
|
+
const { fromIni } = await import("@aws-sdk/credential-providers");
|
|
26
|
+
const { HttpRequest } = await import("@smithy/protocol-http");
|
|
27
|
+
const { SignatureV4 } = await import("@smithy/signature-v4");
|
|
28
|
+
|
|
28
29
|
return await noThrow(async () => {
|
|
29
30
|
const messages = convertGenericMessageToBedrockFormat(input.messages);
|
|
30
31
|
const cachedMessages = modelConfig.enablePromptCaching
|
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);
|
|
@@ -5,10 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { styleText } from "node:util";
|
|
8
|
-
import { Sha256 } from "@aws-crypto/sha256-js";
|
|
9
|
-
import { fromIni } from "@aws-sdk/credential-providers";
|
|
10
|
-
import { HttpRequest } from "@smithy/protocol-http";
|
|
11
|
-
import { SignatureV4 } from "@smithy/signature-v4";
|
|
12
8
|
import { noThrow } from "../utils/noThrow.mjs";
|
|
13
9
|
import { retryOnError } from "../utils/retryOnError.mjs";
|
|
14
10
|
import { readBedrockStreamEvents } from "./platform/bedrock.mjs";
|
|
@@ -112,6 +108,11 @@ export async function callOpenAICompatibleModel(
|
|
|
112
108
|
|
|
113
109
|
// bedrock + sso profile
|
|
114
110
|
const runFetchForBedrock = async () => {
|
|
111
|
+
const { Sha256 } = await import("@aws-crypto/sha256-js");
|
|
112
|
+
const { fromIni } = await import("@aws-sdk/credential-providers");
|
|
113
|
+
const { HttpRequest } = await import("@smithy/protocol-http");
|
|
114
|
+
const { SignatureV4 } = await import("@smithy/signature-v4");
|
|
115
|
+
|
|
115
116
|
const region =
|
|
116
117
|
url.match(/bedrock-runtime\.([\w-]+)\.amazonaws\.com/)?.[1] ?? "";
|
|
117
118
|
const urlParsed = new URL(url);
|