praxis-agent 0.55.0 → 0.55.2

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.
@@ -0,0 +1,74 @@
1
+ import type { ModelUsage } from '../core/runtime.js';
2
+ import type { ModelPricingRegistry } from '../core/usage.js';
3
+ import { ClaudeSessionCostTracker } from './session-cost-tracker.js';
4
+ export interface TurnToolMetric {
5
+ readonly isError: boolean;
6
+ readonly usage?: ModelUsage;
7
+ readonly modelUsage?: Readonly<Record<string, ModelUsage>>;
8
+ readonly durationApiMs?: number;
9
+ readonly durationApiWithoutRetriesMs?: number;
10
+ readonly durationToolMs?: number;
11
+ readonly linesAdded?: number;
12
+ readonly linesRemoved?: number;
13
+ }
14
+ export interface TurnRuntimeMetric {
15
+ readonly usage: ModelUsage;
16
+ readonly modelUsage?: Readonly<Record<string, ModelUsage>>;
17
+ readonly durationApiMs?: number;
18
+ readonly durationApiWithoutRetriesMs?: number;
19
+ readonly durationToolMs?: number;
20
+ readonly linesAdded?: number;
21
+ readonly linesRemoved?: number;
22
+ readonly unrecordedModelUsage?: Readonly<Record<string, ModelUsage>>;
23
+ readonly unrecordedDurationApiMs?: number;
24
+ readonly unrecordedDurationApiWithoutRetriesMs?: number;
25
+ }
26
+ export interface TurnCompactionMetric {
27
+ readonly usage: ModelUsage;
28
+ readonly model?: string;
29
+ readonly durationApiMs: number;
30
+ readonly durationApiWithoutRetriesMs: number;
31
+ }
32
+ export interface TurnRuntimeCompletion {
33
+ readonly kind: 'runtime';
34
+ readonly recovery: readonly TurnToolMetric[];
35
+ readonly result: TurnRuntimeMetric;
36
+ }
37
+ export interface TurnShellCompletion {
38
+ readonly kind: 'shell';
39
+ readonly recovery: readonly TurnToolMetric[];
40
+ readonly result: TurnToolMetric;
41
+ }
42
+ export type TurnAccountingCompletion = TurnRuntimeCompletion | TurnShellCompletion;
43
+ export interface TurnAccountingOutcome {
44
+ readonly usage: ModelUsage;
45
+ readonly modelUsage?: Readonly<Record<string, ModelUsage>>;
46
+ readonly costUsd?: number;
47
+ readonly durationApiMs?: number;
48
+ }
49
+ export interface PreparedCompactionAccounting {
50
+ readonly commit: () => void;
51
+ }
52
+ export declare class TurnAccounting {
53
+ private readonly tracker;
54
+ private readonly pricing;
55
+ private compactionUsage;
56
+ private compactionModelUsage;
57
+ private compactionDurationMs;
58
+ private compactionDurationWithoutRetriesMs;
59
+ private hasCompaction;
60
+ private outstandingCompaction;
61
+ private completed;
62
+ constructor(options: {
63
+ readonly tracker: ClaudeSessionCostTracker;
64
+ readonly pricing?: ModelPricingRegistry;
65
+ });
66
+ prepareCompaction(input: TurnCompactionMetric): PreparedCompactionAccounting;
67
+ complete(input: TurnAccountingCompletion): TurnAccountingOutcome;
68
+ private compactionStateAfter;
69
+ private compactionPlan;
70
+ private aggregate;
71
+ private trackerPlan;
72
+ private appendModelInputs;
73
+ }
74
+ //# sourceMappingURL=turn-accounting.d.ts.map
@@ -0,0 +1,444 @@
1
+ import { usageCostUsd } from '../core/usage.js';
2
+ import { ClaudeSessionCostTracker, } from './session-cost-tracker.js';
3
+ const counterFields = [
4
+ 'inputTokens',
5
+ 'outputTokens',
6
+ 'cacheReadInputTokens',
7
+ 'cacheCreationInputTokens',
8
+ 'webSearchRequests',
9
+ ];
10
+ const metadataFields = ['contextWindow', 'maxOutputTokens'];
11
+ function clone(value) {
12
+ return structuredClone(value);
13
+ }
14
+ function mergeUsage(left, right) {
15
+ for (const [usage, prefix] of [
16
+ [left, 'usage.left'],
17
+ [right, 'usage.right'],
18
+ ]) {
19
+ for (const field of counterFields) {
20
+ const value = usage[field];
21
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
22
+ throw new TypeError(`${prefix}.${field} must be a nonnegative safe integer`);
23
+ }
24
+ }
25
+ }
26
+ const cacheReadInputTokens = (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0);
27
+ const cacheCreationInputTokens = (left.cacheCreationInputTokens ?? 0) + (right.cacheCreationInputTokens ?? 0);
28
+ const webSearchRequests = (left.webSearchRequests ?? 0) + (right.webSearchRequests ?? 0);
29
+ if (!Number.isSafeInteger(left.inputTokens + right.inputTokens) ||
30
+ !Number.isSafeInteger(left.outputTokens + right.outputTokens) ||
31
+ !Number.isSafeInteger(cacheReadInputTokens) ||
32
+ !Number.isSafeInteger(cacheCreationInputTokens) ||
33
+ !Number.isSafeInteger(webSearchRequests)) {
34
+ throw new Error('Model usage total overflow');
35
+ }
36
+ return {
37
+ inputTokens: left.inputTokens + right.inputTokens,
38
+ outputTokens: left.outputTokens + right.outputTokens,
39
+ ...(cacheReadInputTokens === 0 ? {} : { cacheReadInputTokens }),
40
+ ...(cacheCreationInputTokens === 0 ? {} : { cacheCreationInputTokens }),
41
+ ...(webSearchRequests === 0 ? {} : { webSearchRequests }),
42
+ };
43
+ }
44
+ function assertValidSessionUsageEntry(model, usage) {
45
+ if (model.trim() === '') {
46
+ throw new Error('Model usage breakdown contains a blank model name');
47
+ }
48
+ for (const field of counterFields) {
49
+ const value = usage[field];
50
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
51
+ throw new Error(`Model usage for "${model}" has an invalid ${field} counter`);
52
+ }
53
+ }
54
+ for (const field of metadataFields) {
55
+ const value = usage[field];
56
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 1)) {
57
+ throw new Error(`Model usage for "${model}" has an invalid ${field} metadata value`);
58
+ }
59
+ }
60
+ }
61
+ function mergeSessionUsageMetadataField(model, field, left, right) {
62
+ if (left === undefined)
63
+ return right;
64
+ if (right === undefined)
65
+ return left;
66
+ if (left !== right) {
67
+ throw new Error(`Model usage for "${model}" has conflicting ${field} values: ${left} vs ${right}`);
68
+ }
69
+ return left;
70
+ }
71
+ function mergeSessionUsageMetadata(model, left, right) {
72
+ const contextWindow = mergeSessionUsageMetadataField(model, 'contextWindow', left.contextWindow, right.contextWindow);
73
+ const maxOutputTokens = mergeSessionUsageMetadataField(model, 'maxOutputTokens', left.maxOutputTokens, right.maxOutputTokens);
74
+ return {
75
+ ...(contextWindow === undefined ? {} : { contextWindow }),
76
+ ...(maxOutputTokens === undefined ? {} : { maxOutputTokens }),
77
+ };
78
+ }
79
+ function addSessionUsageChecked(model, left, right) {
80
+ const inputTokens = left.inputTokens + right.inputTokens;
81
+ const outputTokens = left.outputTokens + right.outputTokens;
82
+ const cacheReadInputTokens = (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0);
83
+ const cacheCreationInputTokens = (left.cacheCreationInputTokens ?? 0) + (right.cacheCreationInputTokens ?? 0);
84
+ const webSearchRequests = (left.webSearchRequests ?? 0) + (right.webSearchRequests ?? 0);
85
+ if (!Number.isSafeInteger(inputTokens) ||
86
+ !Number.isSafeInteger(outputTokens) ||
87
+ !Number.isSafeInteger(cacheReadInputTokens) ||
88
+ !Number.isSafeInteger(cacheCreationInputTokens) ||
89
+ !Number.isSafeInteger(webSearchRequests)) {
90
+ throw new Error('Model usage total overflow');
91
+ }
92
+ const metadata = mergeSessionUsageMetadata(model, left, right);
93
+ return {
94
+ inputTokens,
95
+ outputTokens,
96
+ ...(cacheReadInputTokens === 0 ? {} : { cacheReadInputTokens }),
97
+ ...(cacheCreationInputTokens === 0 ? {} : { cacheCreationInputTokens }),
98
+ ...(webSearchRequests === 0 ? {} : { webSearchRequests }),
99
+ ...metadata,
100
+ };
101
+ }
102
+ function mergeModelUsage(...parts) {
103
+ const merged = new Map();
104
+ for (const part of parts) {
105
+ if (part === undefined)
106
+ continue;
107
+ for (const [model, usage] of Object.entries(part)) {
108
+ assertValidSessionUsageEntry(model, usage);
109
+ const existing = merged.get(model);
110
+ merged.set(model, existing === undefined
111
+ ? { ...usage }
112
+ : addSessionUsageChecked(model, existing, usage));
113
+ }
114
+ }
115
+ return merged.size === 0 ? undefined : Object.fromEntries(merged);
116
+ }
117
+ function hasNonZeroUsage(usage) {
118
+ return (usage.inputTokens > 0 ||
119
+ usage.outputTokens > 0 ||
120
+ (usage.cacheReadInputTokens ?? 0) > 0 ||
121
+ (usage.cacheCreationInputTokens ?? 0) > 0 ||
122
+ (usage.webSearchRequests ?? 0) > 0);
123
+ }
124
+ function validateMetricUsage(usage) {
125
+ for (const field of ['inputTokens', 'outputTokens']) {
126
+ const value = usage[field];
127
+ if (!Number.isSafeInteger(value) || value < 0) {
128
+ throw new TypeError(`usage.${field} must be a nonnegative safe integer`);
129
+ }
130
+ }
131
+ for (const field of counterFields.slice(2)) {
132
+ const value = usage[field];
133
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
134
+ throw new TypeError(`usage.${field} must be a nonnegative safe integer`);
135
+ }
136
+ }
137
+ }
138
+ function requireMetric(value, field) {
139
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
140
+ throw new TypeError(`${field} must be a finite nonnegative number`);
141
+ }
142
+ return value;
143
+ }
144
+ function addMetric(value, total, field) {
145
+ if (value === undefined)
146
+ return total;
147
+ const next = total + requireMetric(value, field);
148
+ if (!Number.isFinite(next) || next < 0) {
149
+ throw new TypeError(`${field} total overflow`);
150
+ }
151
+ return next;
152
+ }
153
+ function addLines(value, total, field) {
154
+ if (value === undefined)
155
+ return total;
156
+ if (!Number.isSafeInteger(value) || value < 0) {
157
+ throw new TypeError(`${field} must be a nonnegative safe integer`);
158
+ }
159
+ const next = total + value;
160
+ if (!Number.isSafeInteger(next) || next < 0) {
161
+ throw new TypeError(`${field} total must be a nonnegative safe integer`);
162
+ }
163
+ return next;
164
+ }
165
+ function costForModelUsage(modelUsage, pricing) {
166
+ if (modelUsage === undefined)
167
+ return undefined;
168
+ let costUsd;
169
+ for (const [model, usage] of Object.entries(modelUsage)) {
170
+ const resolved = pricing?.resolve(model);
171
+ if (resolved !== undefined) {
172
+ costUsd = (costUsd ?? 0) + usageCostUsd(usage, resolved);
173
+ }
174
+ }
175
+ return costUsd;
176
+ }
177
+ function applyPlan(tracker, plan) {
178
+ for (const input of plan.turnInputs)
179
+ tracker.recordTurn(input);
180
+ if (plan.durations !== undefined)
181
+ tracker.recordDurations(plan.durations);
182
+ if (plan.linesAdded !== 0 || plan.linesRemoved !== 0) {
183
+ tracker.recordLineChanges({
184
+ ...(plan.linesAdded === 0 ? {} : { linesAdded: plan.linesAdded }),
185
+ ...(plan.linesRemoved === 0 ? {} : { linesRemoved: plan.linesRemoved }),
186
+ });
187
+ }
188
+ }
189
+ function cloneTracker(tracker) {
190
+ const snapshot = tracker.snapshot();
191
+ return new ClaudeSessionCostTracker({
192
+ sessionId: snapshot.sessionId,
193
+ restored: snapshot,
194
+ });
195
+ }
196
+ export class TurnAccounting {
197
+ tracker;
198
+ pricing;
199
+ compactionUsage = {
200
+ inputTokens: 0,
201
+ outputTokens: 0,
202
+ };
203
+ compactionModelUsage;
204
+ compactionDurationMs = 0;
205
+ compactionDurationWithoutRetriesMs = 0;
206
+ hasCompaction = false;
207
+ outstandingCompaction = false;
208
+ completed = false;
209
+ constructor(options) {
210
+ this.tracker = options.tracker;
211
+ this.pricing = options.pricing;
212
+ }
213
+ prepareCompaction(input) {
214
+ if (this.completed) {
215
+ throw new Error('Cannot prepare compaction after turn completion');
216
+ }
217
+ if (this.outstandingCompaction) {
218
+ throw new Error('A compaction accounting plan is already outstanding');
219
+ }
220
+ const snapshot = clone(input);
221
+ this.compactionStateAfter(snapshot);
222
+ const plan = this.compactionPlan(snapshot);
223
+ const preflight = cloneTracker(this.tracker);
224
+ applyPlan(preflight, plan);
225
+ this.outstandingCompaction = true;
226
+ let consumed = false;
227
+ return {
228
+ commit: () => {
229
+ if (consumed)
230
+ throw new Error('Prepared compaction accounting was already committed');
231
+ consumed = true;
232
+ try {
233
+ const latestState = this.compactionStateAfter(snapshot);
234
+ const latestPlan = this.compactionPlan(snapshot);
235
+ const latestPreflight = cloneTracker(this.tracker);
236
+ applyPlan(latestPreflight, latestPlan);
237
+ applyPlan(this.tracker, latestPlan);
238
+ this.compactionUsage = clone(latestState.usage);
239
+ this.compactionModelUsage = clone(latestState.modelUsage);
240
+ this.compactionDurationMs = latestState.durationApiMs;
241
+ this.compactionDurationWithoutRetriesMs =
242
+ latestState.durationApiWithoutRetriesMs;
243
+ this.hasCompaction = true;
244
+ }
245
+ finally {
246
+ this.outstandingCompaction = false;
247
+ }
248
+ },
249
+ };
250
+ }
251
+ complete(input) {
252
+ if (this.completed) {
253
+ throw new Error('Turn accounting is already complete');
254
+ }
255
+ if (this.outstandingCompaction) {
256
+ throw new Error('Cannot complete a turn while compaction accounting is outstanding');
257
+ }
258
+ const snapshot = clone(input);
259
+ const aggregate = this.aggregate(snapshot);
260
+ const plan = this.trackerPlan(snapshot);
261
+ const preflight = cloneTracker(this.tracker);
262
+ applyPlan(preflight, plan);
263
+ applyPlan(this.tracker, plan);
264
+ this.completed = true;
265
+ return clone({
266
+ usage: aggregate.usage,
267
+ ...(aggregate.modelUsage === undefined
268
+ ? {}
269
+ : { modelUsage: aggregate.modelUsage }),
270
+ ...(aggregate.costUsd === undefined
271
+ ? {}
272
+ : { costUsd: aggregate.costUsd }),
273
+ ...(aggregate.durationApiMs === undefined
274
+ ? {}
275
+ : { durationApiMs: aggregate.durationApiMs }),
276
+ });
277
+ }
278
+ compactionStateAfter(input) {
279
+ validateMetricUsage(input.usage);
280
+ requireMetric(input.durationApiMs, 'compaction durationMs');
281
+ requireMetric(input.durationApiWithoutRetriesMs, 'compaction durationWithoutRetriesMs');
282
+ const meaningfulUsage = hasNonZeroUsage(input.usage);
283
+ if (meaningfulUsage &&
284
+ (input.model === undefined || input.model.trim() === '')) {
285
+ throw new Error('Auto compact usage requires a nonblank model identity');
286
+ }
287
+ const modelUsage = input.model !== undefined && input.model.trim() !== ''
288
+ ? { [input.model]: input.usage }
289
+ : undefined;
290
+ return {
291
+ usage: mergeUsage(this.compactionUsage, input.usage),
292
+ modelUsage: mergeModelUsage(this.compactionModelUsage, modelUsage),
293
+ durationApiMs: addMetric(input.durationApiMs, this.compactionDurationMs, 'compaction durationMs'),
294
+ durationApiWithoutRetriesMs: addMetric(input.durationApiWithoutRetriesMs, this.compactionDurationWithoutRetriesMs, 'compaction durationWithoutRetriesMs'),
295
+ };
296
+ }
297
+ compactionPlan(input) {
298
+ const turnInputs = [];
299
+ if (hasNonZeroUsage(input.usage)) {
300
+ const model = input.model;
301
+ if (model === undefined || model.trim() === '') {
302
+ throw new Error('Auto compact usage requires a nonblank model identity');
303
+ }
304
+ const costUsd = this.pricing
305
+ ? costForModelUsage({ [model]: input.usage }, this.pricing)
306
+ : undefined;
307
+ turnInputs.push({
308
+ model,
309
+ usage: input.usage,
310
+ ...(costUsd === undefined ? {} : { costUsd }),
311
+ ...(input.usage.webSearchRequests === undefined
312
+ ? {}
313
+ : { webSearchRequests: input.usage.webSearchRequests }),
314
+ });
315
+ }
316
+ const durations = {
317
+ ...(input.durationApiMs === 0
318
+ ? {}
319
+ : { apiDurationMs: input.durationApiMs }),
320
+ apiDurationWithoutRetriesMs: input.durationApiWithoutRetriesMs,
321
+ };
322
+ return {
323
+ turnInputs,
324
+ durations,
325
+ linesAdded: 0,
326
+ linesRemoved: 0,
327
+ };
328
+ }
329
+ aggregate(input) {
330
+ if (input.kind === 'runtime') {
331
+ requireMetric(input.result.durationApiMs ?? 0, 'durationApiMs');
332
+ requireMetric(input.result.durationApiWithoutRetriesMs ?? 0, 'durationApiWithoutRetriesMs');
333
+ }
334
+ if (input.kind === 'shell') {
335
+ if (input.result.durationApiMs !== undefined)
336
+ requireMetric(input.result.durationApiMs, 'durationApiMs');
337
+ if (input.result.durationApiWithoutRetriesMs !== undefined)
338
+ requireMetric(input.result.durationApiWithoutRetriesMs, 'durationApiWithoutRetriesMs');
339
+ }
340
+ const recoveryUsage = input.recovery.reduce((total, result) => result.usage ? mergeUsage(total, result.usage) : total, { inputTokens: 0, outputTokens: 0 });
341
+ const recoveryModelUsage = mergeModelUsage(...input.recovery.map((result) => result.isError ? undefined : result.modelUsage));
342
+ const resultUsage = input.result.usage ?? {
343
+ inputTokens: 0,
344
+ outputTokens: 0,
345
+ };
346
+ const usage = input.kind === 'runtime'
347
+ ? mergeUsage(mergeUsage(recoveryUsage, this.compactionUsage), resultUsage)
348
+ : mergeUsage(recoveryUsage, resultUsage);
349
+ const modelUsage = mergeModelUsage(recoveryModelUsage, ...(input.kind === 'runtime' ? [this.compactionModelUsage] : []), input.kind === 'runtime'
350
+ ? input.result.modelUsage
351
+ : input.result.isError
352
+ ? undefined
353
+ : input.result.modelUsage);
354
+ const hasDuration = input.kind === 'runtime' &&
355
+ (this.hasCompaction || input.result.durationApiMs !== undefined);
356
+ const durationApiMs = input.kind === 'runtime' && hasDuration
357
+ ? addMetric(input.result.durationApiMs, this.compactionDurationMs, 'durationApiMs')
358
+ : input.result.durationApiMs;
359
+ const costUsd = costForModelUsage(modelUsage, this.pricing);
360
+ return {
361
+ usage,
362
+ ...(modelUsage === undefined ? {} : { modelUsage }),
363
+ ...(costUsd === undefined ? {} : { costUsd }),
364
+ ...(durationApiMs === undefined ? {} : { durationApiMs }),
365
+ };
366
+ }
367
+ trackerPlan(input) {
368
+ const turnInputs = [];
369
+ const lines = { linesAdded: 0, linesRemoved: 0 };
370
+ let toolDurationMs = 0;
371
+ for (const result of input.recovery) {
372
+ toolDurationMs = addMetric(result.durationToolMs, toolDurationMs, 'durationToolMs');
373
+ if (!result.isError) {
374
+ lines.linesAdded = addLines(result.linesAdded, lines.linesAdded, 'linesAdded');
375
+ lines.linesRemoved = addLines(result.linesRemoved, lines.linesRemoved, 'linesRemoved');
376
+ }
377
+ }
378
+ const trackedModelUsage = input.kind === 'shell'
379
+ ? mergeModelUsage(...input.recovery.map((result) => result.isError ? undefined : result.modelUsage), input.result.isError ? undefined : input.result.modelUsage)
380
+ : mergeModelUsage(...input.recovery.map((result) => result.isError ? undefined : result.modelUsage), input.result.unrecordedModelUsage ?? input.result.modelUsage);
381
+ if (trackedModelUsage !== undefined) {
382
+ this.appendModelInputs(turnInputs, trackedModelUsage);
383
+ }
384
+ if (input.kind === 'shell') {
385
+ toolDurationMs = addMetric(input.result.durationToolMs, toolDurationMs, 'durationToolMs');
386
+ if (!input.result.isError) {
387
+ lines.linesAdded = addLines(input.result.linesAdded, lines.linesAdded, 'linesAdded');
388
+ lines.linesRemoved = addLines(input.result.linesRemoved, lines.linesRemoved, 'linesRemoved');
389
+ }
390
+ return {
391
+ turnInputs,
392
+ durations: {
393
+ ...(input.result.durationApiMs === undefined
394
+ ? {}
395
+ : { apiDurationMs: input.result.durationApiMs }),
396
+ ...(input.result.durationApiWithoutRetriesMs === undefined
397
+ ? {}
398
+ : {
399
+ apiDurationWithoutRetriesMs: input.result.durationApiWithoutRetriesMs,
400
+ }),
401
+ ...(toolDurationMs === 0 ? {} : { toolDurationMs }),
402
+ },
403
+ ...lines,
404
+ };
405
+ }
406
+ toolDurationMs = addMetric(input.result.durationToolMs, toolDurationMs, 'durationToolMs');
407
+ lines.linesAdded = addLines(input.result.linesAdded, lines.linesAdded, 'linesAdded');
408
+ lines.linesRemoved = addLines(input.result.linesRemoved, lines.linesRemoved, 'linesRemoved');
409
+ const trackedDurationApiMs = input.result.unrecordedDurationApiMs ?? input.result.durationApiMs;
410
+ const trackedDurationApiWithoutRetriesMs = input.result.unrecordedDurationApiWithoutRetriesMs ??
411
+ input.result.durationApiWithoutRetriesMs;
412
+ return {
413
+ turnInputs,
414
+ durations: {
415
+ ...(trackedDurationApiMs === undefined
416
+ ? {}
417
+ : { apiDurationMs: trackedDurationApiMs }),
418
+ ...(trackedDurationApiWithoutRetriesMs === undefined
419
+ ? {}
420
+ : {
421
+ apiDurationWithoutRetriesMs: trackedDurationApiWithoutRetriesMs,
422
+ }),
423
+ ...(toolDurationMs === 0 ? {} : { toolDurationMs }),
424
+ },
425
+ ...lines,
426
+ };
427
+ }
428
+ appendModelInputs(target, modelUsage) {
429
+ for (const [model, usage] of Object.entries(modelUsage)) {
430
+ const costUsd = this.pricing
431
+ ? costForModelUsage({ [model]: usage }, this.pricing)
432
+ : undefined;
433
+ target.push({
434
+ model,
435
+ usage,
436
+ ...(costUsd === undefined ? {} : { costUsd }),
437
+ ...(usage.webSearchRequests === undefined
438
+ ? {}
439
+ : { webSearchRequests: usage.webSearchRequests }),
440
+ });
441
+ }
442
+ }
443
+ }
444
+ //# sourceMappingURL=turn-accounting.js.map
@@ -1,6 +1,5 @@
1
1
  import { type ModelDocument, type ModelImage, type RuntimeEventSink } from '../core/runtime.js';
2
- import type { LifecycleState } from '../core/agent-orchestration.js';
3
- export type TurnTerminalState = Extract<LifecycleState, 'completed' | 'failed' | 'cancelled'>;
2
+ import { type ActiveTurnInputCommandResult, type ActiveTurnInputPort } from '../core/active-turn-input.js';
4
3
  export type TurnActivation = {
5
4
  kind: 'start';
6
5
  sessionId: string;
@@ -28,13 +27,26 @@ export interface TurnRequest {
28
27
  submission: TurnSubmission;
29
28
  signal?: AbortSignal;
30
29
  }
31
- export declare class TurnTerminalController {
32
- private readonly sink;
33
- private terminal;
34
- constructor(sink: RuntimeEventSink);
35
- emit: RuntimeEventSink;
36
- complete(): void;
37
- fail(error: unknown, signal?: AbortSignal): void;
30
+ export interface TurnScope {
31
+ readonly emit: RuntimeEventSink;
32
+ readonly steering?: ActiveTurnInputPort;
33
+ }
34
+ export interface TurnCoordinatorOptions {
35
+ readonly eventSink: RuntimeEventSink;
36
+ readonly createSteeringId: () => string;
37
+ }
38
+ /** Owns the lifecycle and active-turn coordination for one session service. */
39
+ export declare class TurnCoordinator {
40
+ private readonly options;
41
+ private readonly activeTurns;
42
+ constructor(options: TurnCoordinatorOptions);
43
+ run<T>(request: TurnRequest, work: (scope: TurnScope) => Promise<T>): Promise<T>;
44
+ steer(sessionId: string, content: string): ActiveTurnInputCommandResult;
45
+ withdrawSteering(sessionId: string, id: string): ActiveTurnInputCommandResult;
46
+ close(): void;
47
+ private validateRequest;
48
+ private terminalState;
38
49
  private transition;
50
+ private rejectPending;
39
51
  }
40
52
  //# sourceMappingURL=turn-lifecycle.d.ts.map