praxis-agent 0.16.0 → 0.17.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/README.md +11 -5
- package/dist/application/background-agent-manager.d.ts +7 -1
- package/dist/application/background-agent-manager.js +148 -5
- package/dist/application/metered-model-completion.d.ts +15 -0
- package/dist/application/metered-model-completion.js +76 -0
- package/dist/application/model-compactor.js +25 -1
- package/dist/application/session-cost-tracker.d.ts +50 -0
- package/dist/application/session-cost-tracker.js +254 -0
- package/dist/application/session-service.d.ts +13 -0
- package/dist/application/session-service.js +594 -69
- package/dist/application/subagent-service.d.ts +11 -1
- package/dist/application/subagent-service.js +41 -1
- package/dist/application/tool-use-summary.d.ts +7 -2
- package/dist/application/tool-use-summary.js +42 -10
- package/dist/application/workflow-manager.d.ts +9 -4
- package/dist/application/workflow-manager.js +151 -7
- package/dist/cli/interactive.d.ts +5 -1
- package/dist/cli/interactive.js +227 -15
- package/dist/cli/protocol.d.ts +11 -0
- package/dist/cli/protocol.js +16 -1
- package/dist/cli/tui/claude-command-inventory.d.ts +27 -27
- package/dist/cli/tui/claude-command-inventory.js +26 -26
- package/dist/cli/tui/config-dashboard.d.ts +2 -9
- package/dist/cli/tui/config-dashboard.js +6 -6
- package/dist/cli/tui/cost-summary.d.ts +34 -0
- package/dist/cli/tui/cost-summary.js +192 -0
- package/dist/cli/tui/doctor-dashboard.d.ts +15 -0
- package/dist/cli/tui/doctor-dashboard.js +81 -0
- package/dist/cli/tui/slash-commands.js +10 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +192 -15
- package/dist/core/compaction.d.ts +2 -0
- package/dist/core/runtime.d.ts +38 -1
- package/dist/core/runtime.js +265 -11
- package/dist/persistence/claude-cost-state-store.d.ts +48 -0
- package/dist/persistence/claude-cost-state-store.js +335 -0
- package/dist/providers/anthropic-compatible.js +34 -9
- package/dist/providers/fallback-provider.d.ts +2 -1
- package/dist/providers/fallback-provider.js +28 -3
- package/dist/tools/line-changes.d.ts +8 -0
- package/dist/tools/line-changes.js +35 -0
- package/dist/tools/local-tools.js +30 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -7,15 +7,17 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
9
|
import { ClaudeSessionService, } from './application/session-service.js';
|
|
10
|
+
import { ClaudeCostStateStore } from './persistence/claude-cost-state-store.js';
|
|
10
11
|
import { agentColorMessage, parseAgentColorInput, } from './compatibility/claude/agent-color.js';
|
|
11
12
|
import { ClaudeConditionalRuleResolver, ClaudeContextAssembler, } from './compatibility/claude/context.js';
|
|
12
13
|
import { loadClaudeDynamicContext } from './compatibility/claude/dynamic-context.js';
|
|
13
14
|
import { createClaudePrSessionFilter, filterClaudePrLinkedSessions, } from './compatibility/claude/pr-links.js';
|
|
14
15
|
import { isClaudeSessionId, resolveClaudePaths, } from './compatibility/claude/paths.js';
|
|
15
|
-
import { loadClaudeContextResources, loadClaudeSettings, loadClaudeSharedResources, resolveClaudeProjectMemoryDirectory, } from './compatibility/claude/shared-resources.js';
|
|
16
|
+
import { loadClaudeContextResources, loadClaudeSettings, loadClaudeSharedResources, resolveClaudeProjectIdentity, resolveClaudeProjectMemoryDirectory, } from './compatibility/claude/shared-resources.js';
|
|
16
17
|
import { AgentRunCancelledError, } from './core/runtime.js';
|
|
17
18
|
import { persistTuiPermissionUpdates } from './cli/tui/permission-settings.js';
|
|
18
19
|
import { loadClaudeReleaseNotes } from './cli/tui/release-notes.js';
|
|
20
|
+
import { canonicalClaudeCostModelName, formatCostSummary, } from './cli/tui/cost-summary.js';
|
|
19
21
|
import { projectTuiHooks, } from './cli/tui/hook-settings.js';
|
|
20
22
|
import { DEFAULT_CLI_CONTROLS, resolveCliControls } from './cli/controls.js';
|
|
21
23
|
import { applyRuntimeSettingDefaults, loadRuntimeSettings, runtimeSettingsSystemPrompt, } from './cli/tui/runtime-settings.js';
|
|
@@ -47,7 +49,7 @@ import { WorkspaceContext } from './application/session-worktree.js';
|
|
|
47
49
|
import { launchTmuxWorktree } from './platform/tmux-worktree.js';
|
|
48
50
|
import { claudeSandboxRuntime } from './sandbox/claude-sandbox-runtime.js';
|
|
49
51
|
import { claudeSandboxTempDirectory, loadClaudeSandboxSettings, } from './sandbox/claude-sandbox-settings.js';
|
|
50
|
-
import { createErrorResult, createSuccessResult, matchHeadlessColorCommand, parseCliInvocation, readStreamJsonMessages, StreamJsonOutput, } from './cli/protocol.js';
|
|
52
|
+
import { createErrorResult, createSuccessResult, isHeadlessCostCommand, matchHeadlessColorCommand, parseCliInvocation, readStreamJsonMessages, StreamJsonOutput, } from './cli/protocol.js';
|
|
51
53
|
import { describeClaudePlugin, initClaudePlugin, installClaudePlugin, loadClaudePlugins, readPluginRegistry, setClaudePluginEnabled, uninstallClaudePlugin, updateClaudePlugin, validateClaudePlugin, } from './plugins/claude-plugin-runtime.js';
|
|
52
54
|
import { addClaudeMarketplace, disableAllNativePlugins, installClaudeMarketplacePlugin, listClaudeMarketplaceAvailablePlugins, listNativePluginRecords, readClaudeKnownMarketplaces, removeClaudeMarketplace, setNativePluginEnabled, saveClaudePluginConfig, uninstallNativePlugin, updateClaudeMarketplace, updateNativePlugin, validateClaudeMarketplace, } from './plugins/claude-plugin-marketplace.js';
|
|
53
55
|
import { executeClaudePluginEvalCommand, PLUGIN_EVAL_HELP, } from './plugins/claude-plugin-eval.js';
|
|
@@ -770,12 +772,17 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
770
772
|
};
|
|
771
773
|
provider = providerForMainModel(model);
|
|
772
774
|
}
|
|
775
|
+
const costStateStore = new ClaudeCostStateStore({
|
|
776
|
+
statePath: claudeStatePath,
|
|
777
|
+
projectIdentity: await resolveClaudeProjectIdentity({ cwd }),
|
|
778
|
+
});
|
|
773
779
|
const options = {
|
|
774
780
|
configRoot,
|
|
775
781
|
cwd,
|
|
776
782
|
claudeVersion,
|
|
777
783
|
eventSink: runtimeEventSink,
|
|
778
784
|
sessionPersistence: cli.sessionPersistence,
|
|
785
|
+
costStateStore,
|
|
779
786
|
explicitModel: interactiveModel !== undefined || controls.model !== undefined,
|
|
780
787
|
explicitSystemPrompt: cli.systemPrompt !== undefined,
|
|
781
788
|
agentInitialPromptHandledExternally: interactive,
|
|
@@ -1293,6 +1300,9 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1293
1300
|
...(provider?.capabilities.contextWindowTokens === undefined
|
|
1294
1301
|
? {}
|
|
1295
1302
|
: { contextWindowTokens: provider.capabilities.contextWindowTokens }),
|
|
1303
|
+
...(provider?.capabilities.maxOutputTokens === undefined
|
|
1304
|
+
? {}
|
|
1305
|
+
: { maxOutputTokens: provider.capabilities.maxOutputTokens }),
|
|
1296
1306
|
tools: toolNames,
|
|
1297
1307
|
mcpServers: mcpTools.serverStatuses(),
|
|
1298
1308
|
permissionMode: cli.dangerouslySkipPermissions
|
|
@@ -1382,6 +1392,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1382
1392
|
}
|
|
1383
1393
|
},
|
|
1384
1394
|
sessions: () => service.sessions(),
|
|
1395
|
+
costSnapshot: (sessionId) => service.costSnapshot(sessionId),
|
|
1385
1396
|
slashCommands: () => extensions.slashCommandDefinitions().map((definition) => ({
|
|
1386
1397
|
name: definition.name,
|
|
1387
1398
|
description: definition.description,
|
|
@@ -1433,14 +1444,32 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1433
1444
|
if (failure !== undefined)
|
|
1434
1445
|
throw failure;
|
|
1435
1446
|
},
|
|
1436
|
-
runtimeInfo: () =>
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1447
|
+
runtimeInfo: () => {
|
|
1448
|
+
// Drop the static capability snapshot so a fallback-routed provider
|
|
1449
|
+
// never reports stale context/output limits, then re-derive them from
|
|
1450
|
+
// the currently active provider.
|
|
1451
|
+
const staticInfo = {
|
|
1452
|
+
...runtimeInfo,
|
|
1453
|
+
cwd: workspace.cwd(),
|
|
1454
|
+
model: service.model() ??
|
|
1455
|
+
provider?.model ??
|
|
1456
|
+
runtimeEnvironment.PRAXIS_MODEL ??
|
|
1457
|
+
'unknown',
|
|
1458
|
+
};
|
|
1459
|
+
delete staticInfo.contextWindowTokens;
|
|
1460
|
+
delete staticInfo.maxOutputTokens;
|
|
1461
|
+
return {
|
|
1462
|
+
...staticInfo,
|
|
1463
|
+
...(provider?.capabilities.contextWindowTokens === undefined
|
|
1464
|
+
? {}
|
|
1465
|
+
: {
|
|
1466
|
+
contextWindowTokens: provider.capabilities.contextWindowTokens,
|
|
1467
|
+
}),
|
|
1468
|
+
...(provider?.capabilities.maxOutputTokens === undefined
|
|
1469
|
+
? {}
|
|
1470
|
+
: { maxOutputTokens: provider.capabilities.maxOutputTokens }),
|
|
1471
|
+
};
|
|
1472
|
+
},
|
|
1444
1473
|
promptSuggestion: (sessionId, suggestionSignal) => service.promptSuggestion(sessionId, suggestionSignal),
|
|
1445
1474
|
};
|
|
1446
1475
|
}
|
|
@@ -3266,6 +3295,104 @@ function isCancellation(error, signal) {
|
|
|
3266
3295
|
signal?.aborted === true ||
|
|
3267
3296
|
(error instanceof DOMException && error.name === 'AbortError'));
|
|
3268
3297
|
}
|
|
3298
|
+
function emptyCostSnapshot(sessionId) {
|
|
3299
|
+
return {
|
|
3300
|
+
sessionId,
|
|
3301
|
+
totalCostUsd: 0,
|
|
3302
|
+
apiDurationMs: 0,
|
|
3303
|
+
apiDurationWithoutRetriesMs: 0,
|
|
3304
|
+
toolDurationMs: 0,
|
|
3305
|
+
wallDurationMs: 0,
|
|
3306
|
+
linesAdded: 0,
|
|
3307
|
+
linesRemoved: 0,
|
|
3308
|
+
modelUsage: {},
|
|
3309
|
+
hasUnknownModelCost: false,
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
/**
|
|
3313
|
+
* Computes the validated nonnegative delta between a current cost snapshot and
|
|
3314
|
+
* a process-local baseline. The baseline represents restored historic state or
|
|
3315
|
+
* the empty state before this process touched the session, so the delta shows
|
|
3316
|
+
* only same-process accumulation. Counter regression and incompatible model
|
|
3317
|
+
* state are rejected explicitly rather than clamped.
|
|
3318
|
+
*/
|
|
3319
|
+
function costSummaryDelta(current, baseline) {
|
|
3320
|
+
const base = baseline ?? emptyCostSnapshot(current.sessionId);
|
|
3321
|
+
const assertAtLeast = (label, value, floor) => {
|
|
3322
|
+
if (value < floor) {
|
|
3323
|
+
throw new Error(`Cost state regression: ${label} fell below the process baseline`);
|
|
3324
|
+
}
|
|
3325
|
+
return value - floor;
|
|
3326
|
+
};
|
|
3327
|
+
const totalCostUsd = assertAtLeast('total cost', current.totalCostUsd, base.totalCostUsd);
|
|
3328
|
+
const apiDurationMs = assertAtLeast('API duration', current.apiDurationMs, base.apiDurationMs);
|
|
3329
|
+
const wallDurationMs = assertAtLeast('wall duration', current.wallDurationMs, base.wallDurationMs);
|
|
3330
|
+
const linesAdded = assertAtLeast('lines added', current.linesAdded, base.linesAdded);
|
|
3331
|
+
const linesRemoved = assertAtLeast('lines removed', current.linesRemoved, base.linesRemoved);
|
|
3332
|
+
const modelUsage = [];
|
|
3333
|
+
for (const [model, usage] of Object.entries(current.modelUsage)) {
|
|
3334
|
+
const baseUsage = base.modelUsage[model];
|
|
3335
|
+
if (baseUsage === undefined) {
|
|
3336
|
+
modelUsage.push({
|
|
3337
|
+
model,
|
|
3338
|
+
canonicalName: canonicalClaudeCostModelName(model),
|
|
3339
|
+
inputTokens: usage.inputTokens,
|
|
3340
|
+
outputTokens: usage.outputTokens,
|
|
3341
|
+
cacheReadInputTokens: usage.cacheReadInputTokens,
|
|
3342
|
+
cacheCreationInputTokens: usage.cacheCreationInputTokens,
|
|
3343
|
+
webSearchRequests: usage.webSearchRequests,
|
|
3344
|
+
costUsd: usage.costUsd,
|
|
3345
|
+
});
|
|
3346
|
+
continue;
|
|
3347
|
+
}
|
|
3348
|
+
const inputTokens = usage.inputTokens - baseUsage.inputTokens;
|
|
3349
|
+
const outputTokens = usage.outputTokens - baseUsage.outputTokens;
|
|
3350
|
+
const cacheReadInputTokens = usage.cacheReadInputTokens - baseUsage.cacheReadInputTokens;
|
|
3351
|
+
const cacheCreationInputTokens = usage.cacheCreationInputTokens - baseUsage.cacheCreationInputTokens;
|
|
3352
|
+
const webSearchRequests = usage.webSearchRequests - baseUsage.webSearchRequests;
|
|
3353
|
+
const costUsd = usage.costUsd - baseUsage.costUsd;
|
|
3354
|
+
if (inputTokens < 0 ||
|
|
3355
|
+
outputTokens < 0 ||
|
|
3356
|
+
cacheReadInputTokens < 0 ||
|
|
3357
|
+
cacheCreationInputTokens < 0 ||
|
|
3358
|
+
webSearchRequests < 0 ||
|
|
3359
|
+
costUsd < 0) {
|
|
3360
|
+
throw new Error(`Cost state regression for model ${model}: counters fell below the process baseline`);
|
|
3361
|
+
}
|
|
3362
|
+
if (inputTokens === 0 &&
|
|
3363
|
+
outputTokens === 0 &&
|
|
3364
|
+
cacheReadInputTokens === 0 &&
|
|
3365
|
+
cacheCreationInputTokens === 0 &&
|
|
3366
|
+
webSearchRequests === 0 &&
|
|
3367
|
+
costUsd === 0) {
|
|
3368
|
+
continue;
|
|
3369
|
+
}
|
|
3370
|
+
modelUsage.push({
|
|
3371
|
+
model,
|
|
3372
|
+
canonicalName: canonicalClaudeCostModelName(model),
|
|
3373
|
+
inputTokens,
|
|
3374
|
+
outputTokens,
|
|
3375
|
+
cacheReadInputTokens,
|
|
3376
|
+
cacheCreationInputTokens,
|
|
3377
|
+
webSearchRequests,
|
|
3378
|
+
costUsd,
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
for (const model of Object.keys(base.modelUsage)) {
|
|
3382
|
+
if (current.modelUsage[model] === undefined) {
|
|
3383
|
+
throw new Error(`Cost state regression for model ${model}: model disappeared from the session tracker`);
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
return {
|
|
3387
|
+
totalCostUsd,
|
|
3388
|
+
apiDurationMs,
|
|
3389
|
+
wallDurationMs,
|
|
3390
|
+
linesAdded,
|
|
3391
|
+
linesRemoved,
|
|
3392
|
+
hasUnknownModelCost: current.hasUnknownModelCost && !base.hasUnknownModelCost,
|
|
3393
|
+
modelUsage,
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3269
3396
|
function helpActionAt(argv, commandIndex, actions) {
|
|
3270
3397
|
for (let index = commandIndex + 1; index < argv.length; index += 1) {
|
|
3271
3398
|
const value = argv[index];
|
|
@@ -3951,6 +4078,9 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
3951
4078
|
const firstTurnIsLocalColor = !invocation.disableSlashCommands &&
|
|
3952
4079
|
firstHeadlessPrompt !== undefined &&
|
|
3953
4080
|
matchHeadlessColorCommand(firstHeadlessPrompt) !== undefined;
|
|
4081
|
+
const firstTurnIsLocalCost = !invocation.disableSlashCommands &&
|
|
4082
|
+
firstHeadlessPrompt !== undefined &&
|
|
4083
|
+
isHeadlessCostCommand(firstHeadlessPrompt);
|
|
3954
4084
|
const service = await dependencies.createService({
|
|
3955
4085
|
eventSink: outputFormat === 'stream-json' && !invocation.legacyJson
|
|
3956
4086
|
? (event) => {
|
|
@@ -3975,7 +4105,10 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
3975
4105
|
}
|
|
3976
4106
|
}
|
|
3977
4107
|
: eventSink(io, outputFormat, invocation.legacyJson),
|
|
3978
|
-
requireProvider: !streamInputExhausted &&
|
|
4108
|
+
requireProvider: !streamInputExhausted &&
|
|
4109
|
+
!firstTurnIsLocalColor &&
|
|
4110
|
+
!firstTurnIsLocalCost &&
|
|
4111
|
+
headlessTurnReached,
|
|
3979
4112
|
...(retryInterruptedTools ? { approveRecovery: () => true } : {}),
|
|
3980
4113
|
...(streamIterator ? { approveTool: approveStreamTool } : {}),
|
|
3981
4114
|
...(streamIterator ? { onElicitation: respondStreamElicitation } : {}),
|
|
@@ -3984,6 +4117,12 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
3984
4117
|
...(agent ? { agent } : {}),
|
|
3985
4118
|
controls: invocation,
|
|
3986
4119
|
});
|
|
4120
|
+
const costBaselines = new Map();
|
|
4121
|
+
const ensureCostBaseline = async (sessionId) => {
|
|
4122
|
+
if (!service.costSnapshot || costBaselines.has(sessionId))
|
|
4123
|
+
return;
|
|
4124
|
+
costBaselines.set(sessionId, await service.costSnapshot(sessionId));
|
|
4125
|
+
};
|
|
3987
4126
|
try {
|
|
3988
4127
|
const lifecycleTrigger = invocation.init
|
|
3989
4128
|
? 'init'
|
|
@@ -4101,6 +4240,7 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
4101
4240
|
signal?.addEventListener('abort', forwardAbort, { once: true });
|
|
4102
4241
|
currentTurnAbort = streamIterator ? turnAbort : undefined;
|
|
4103
4242
|
const runSignal = streamIterator ? turnAbort.signal : signal;
|
|
4243
|
+
await ensureCostBaseline(activeSessionId);
|
|
4104
4244
|
if (streamOutput) {
|
|
4105
4245
|
streamOutput.init();
|
|
4106
4246
|
if (isFirstTurn) {
|
|
@@ -4112,11 +4252,46 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
4112
4252
|
}
|
|
4113
4253
|
let result;
|
|
4114
4254
|
let colorArgs;
|
|
4255
|
+
let costTurn = false;
|
|
4115
4256
|
try {
|
|
4257
|
+
costTurn =
|
|
4258
|
+
!invocation.disableSlashCommands && isHeadlessCostCommand(prompt);
|
|
4116
4259
|
colorArgs = invocation.disableSlashCommands
|
|
4117
4260
|
? undefined
|
|
4118
4261
|
: matchHeadlessColorCommand(prompt);
|
|
4119
|
-
if (
|
|
4262
|
+
if (costTurn) {
|
|
4263
|
+
if (!service.costSnapshot) {
|
|
4264
|
+
throw new Error('Session cost is unavailable.');
|
|
4265
|
+
}
|
|
4266
|
+
const current = await service.costSnapshot(activeSessionId);
|
|
4267
|
+
const delta = costSummaryDelta(current, costBaselines.get(activeSessionId));
|
|
4268
|
+
const modelUsageEntries = delta.modelUsage.map((entry) => [
|
|
4269
|
+
entry.model,
|
|
4270
|
+
{
|
|
4271
|
+
inputTokens: entry.inputTokens,
|
|
4272
|
+
outputTokens: entry.outputTokens,
|
|
4273
|
+
...(entry.cacheReadInputTokens === undefined
|
|
4274
|
+
? {}
|
|
4275
|
+
: { cacheReadInputTokens: entry.cacheReadInputTokens }),
|
|
4276
|
+
...(entry.cacheCreationInputTokens === undefined
|
|
4277
|
+
? {}
|
|
4278
|
+
: { cacheCreationInputTokens: entry.cacheCreationInputTokens }),
|
|
4279
|
+
...(entry.webSearchRequests === undefined
|
|
4280
|
+
? {}
|
|
4281
|
+
: { webSearchRequests: entry.webSearchRequests }),
|
|
4282
|
+
},
|
|
4283
|
+
]);
|
|
4284
|
+
result = {
|
|
4285
|
+
sessionId: activeSessionId,
|
|
4286
|
+
text: formatCostSummary(delta),
|
|
4287
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
4288
|
+
durationApiMs: 0,
|
|
4289
|
+
costUsd: delta.totalCostUsd,
|
|
4290
|
+
modelUsage: Object.fromEntries(modelUsageEntries),
|
|
4291
|
+
modelCostUsd: Object.fromEntries(delta.modelUsage.map((entry) => [entry.model, entry.costUsd])),
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
4294
|
+
else if (colorArgs !== undefined) {
|
|
4120
4295
|
if (!service.recordColorUsage) {
|
|
4121
4296
|
throw new Error('Session color is unavailable.');
|
|
4122
4297
|
}
|
|
@@ -4169,20 +4344,22 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
4169
4344
|
return false;
|
|
4170
4345
|
}
|
|
4171
4346
|
activeSessionId = result.sessionId;
|
|
4347
|
+
await ensureCostBaseline(activeSessionId);
|
|
4348
|
+
const localCommand = colorArgs !== undefined || costTurn;
|
|
4172
4349
|
if (streamOutput) {
|
|
4173
|
-
if (
|
|
4350
|
+
if (localCommand) {
|
|
4174
4351
|
streamOutput.syntheticAssistant(result.text);
|
|
4175
4352
|
}
|
|
4176
4353
|
streamOutput.result(result, startedAt);
|
|
4177
4354
|
}
|
|
4178
4355
|
else if (outputFormat === 'json') {
|
|
4179
4356
|
const resultRuntimeInfo = service.runtimeInfo?.() ?? runtimeInfo;
|
|
4180
|
-
writeJson(io, createSuccessResult(result, resultRuntimeInfo, startedAt,
|
|
4357
|
+
writeJson(io, createSuccessResult(result, resultRuntimeInfo, startedAt, localCommand ? 0 : Math.max(1, jsonModelTurns)));
|
|
4181
4358
|
}
|
|
4182
4359
|
else if (outputFormat !== 'text')
|
|
4183
4360
|
writeJson(io, { type: 'result', ...result });
|
|
4184
4361
|
else
|
|
4185
|
-
io.stdout(
|
|
4362
|
+
io.stdout(localCommand ? `${result.text}\n` : '\n');
|
|
4186
4363
|
if (streamOutput && invocation.promptSuggestions) {
|
|
4187
4364
|
try {
|
|
4188
4365
|
const suggestion = await service.promptSuggestion?.(activeSessionId, runSignal);
|
package/dist/core/runtime.d.ts
CHANGED
|
@@ -66,7 +66,13 @@ export interface ModelUsage {
|
|
|
66
66
|
outputTokens: number;
|
|
67
67
|
cacheReadInputTokens?: number;
|
|
68
68
|
cacheCreationInputTokens?: number;
|
|
69
|
+
webSearchRequests?: number;
|
|
70
|
+
/** Positive safe integer when the completing model's context window is known. */
|
|
71
|
+
contextWindow?: number;
|
|
72
|
+
/** Positive safe integer when the completing model's max output tokens are known. */
|
|
73
|
+
maxOutputTokens?: number;
|
|
69
74
|
}
|
|
75
|
+
export type ModelUsageByModel = Readonly<Record<string, ModelUsage>>;
|
|
70
76
|
export interface ModelWebSearch {
|
|
71
77
|
allowedDomains?: readonly string[];
|
|
72
78
|
blockedDomains?: readonly string[];
|
|
@@ -103,6 +109,9 @@ export type ModelStreamEvent = {
|
|
|
103
109
|
retryDelayMs: number;
|
|
104
110
|
errorStatus: number | null;
|
|
105
111
|
error: ProviderErrorKind;
|
|
112
|
+
} | {
|
|
113
|
+
type: 'api-attempt-duration';
|
|
114
|
+
durationMs: number;
|
|
106
115
|
};
|
|
107
116
|
export interface ModelRequest {
|
|
108
117
|
messages: readonly ModelMessage[];
|
|
@@ -125,6 +134,7 @@ export interface ModelProviderCapabilities {
|
|
|
125
134
|
maxTokens: boolean;
|
|
126
135
|
};
|
|
127
136
|
contextWindowTokens?: number;
|
|
137
|
+
maxOutputTokens?: number;
|
|
128
138
|
}
|
|
129
139
|
export interface ModelProvider {
|
|
130
140
|
readonly capabilities: ModelProviderCapabilities;
|
|
@@ -276,12 +286,17 @@ export interface ToolExecutionResult {
|
|
|
276
286
|
images?: readonly ModelImage[];
|
|
277
287
|
documents?: readonly ModelDocument[];
|
|
278
288
|
isError: boolean;
|
|
289
|
+
linesAdded?: number;
|
|
290
|
+
linesRemoved?: number;
|
|
279
291
|
usage?: ModelUsage;
|
|
292
|
+
modelUsage?: ModelUsageByModel;
|
|
280
293
|
accessedPaths?: readonly string[];
|
|
281
294
|
followUpUserMessages?: readonly string[];
|
|
282
295
|
nativeToolUseResult?: Record<string, unknown>;
|
|
283
296
|
nativeMcpMeta?: Record<string, unknown>;
|
|
284
297
|
durationApiMs?: number;
|
|
298
|
+
durationApiWithoutRetriesMs?: number;
|
|
299
|
+
durationToolMs?: number;
|
|
285
300
|
processOutput?: {
|
|
286
301
|
stdout: string;
|
|
287
302
|
stderr: string;
|
|
@@ -389,6 +404,14 @@ export interface AgentRunObserver {
|
|
|
389
404
|
toolCompleted(call: ModelToolCall, result: ToolExecutionResult): Promise<void>;
|
|
390
405
|
followUpUserMessagesCompleted?(messages: readonly string[]): Promise<void>;
|
|
391
406
|
}
|
|
407
|
+
export interface ToolUseSummaryOutcome {
|
|
408
|
+
summary: string | null;
|
|
409
|
+
usage: ModelUsage;
|
|
410
|
+
modelUsage?: ModelUsageByModel;
|
|
411
|
+
durationApiMs: number;
|
|
412
|
+
durationApiWithoutRetriesMs: number;
|
|
413
|
+
meteredExternally: boolean;
|
|
414
|
+
}
|
|
392
415
|
export interface AgentRuntimeOptions {
|
|
393
416
|
tools?: ToolRegistry;
|
|
394
417
|
permissions?: PermissionResolver;
|
|
@@ -407,7 +430,7 @@ export interface AgentRuntimeOptions {
|
|
|
407
430
|
}[];
|
|
408
431
|
lastAssistantText?: string;
|
|
409
432
|
signal: AbortSignal;
|
|
410
|
-
}) => Promise<
|
|
433
|
+
}) => Promise<ToolUseSummaryOutcome | null>;
|
|
411
434
|
}
|
|
412
435
|
export interface AgentRunRequest {
|
|
413
436
|
messages: readonly ModelMessage[];
|
|
@@ -421,6 +444,9 @@ export interface AgentRunRequest {
|
|
|
421
444
|
onStop?: (text: string) => Promise<readonly string[] | {
|
|
422
445
|
messages: readonly string[];
|
|
423
446
|
usage?: ModelUsage;
|
|
447
|
+
modelUsage?: ModelUsageByModel;
|
|
448
|
+
durationApiMs?: number;
|
|
449
|
+
durationApiWithoutRetriesMs?: number;
|
|
424
450
|
}>;
|
|
425
451
|
signal?: AbortSignal;
|
|
426
452
|
effort?: string;
|
|
@@ -436,7 +462,18 @@ export interface AgentToolRecoveryRequest extends Pick<AgentRunRequest, 'approve
|
|
|
436
462
|
export interface AgentRunResult {
|
|
437
463
|
text: string;
|
|
438
464
|
usage: ModelUsage;
|
|
465
|
+
modelUsage?: ModelUsageByModel;
|
|
439
466
|
durationApiMs?: number;
|
|
467
|
+
durationApiWithoutRetriesMs?: number;
|
|
468
|
+
durationToolMs?: number;
|
|
469
|
+
linesAdded?: number;
|
|
470
|
+
linesRemoved?: number;
|
|
471
|
+
/** Model rows the session tracker still must record after at least one
|
|
472
|
+
* externally metered summary outcome; undefined when every summary outcome
|
|
473
|
+
* is still unrecorded (so inclusive fields stay authoritative). */
|
|
474
|
+
unrecordedModelUsage?: ModelUsageByModel;
|
|
475
|
+
unrecordedDurationApiMs?: number;
|
|
476
|
+
unrecordedDurationApiWithoutRetriesMs?: number;
|
|
440
477
|
}
|
|
441
478
|
export declare class ModelProviderError extends Error {
|
|
442
479
|
readonly name = "ModelProviderError";
|