@namzu/sdk 21.1.0 → 22.1.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/CHANGELOG.md +173 -0
- package/dist/advisory/executor.d.ts +9 -4
- package/dist/advisory/executor.d.ts.map +1 -1
- package/dist/advisory/executor.js +16 -9
- package/dist/advisory/executor.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +12 -0
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/constants/limits.d.ts +13 -0
- package/dist/constants/limits.d.ts.map +1 -1
- package/dist/constants/limits.js +14 -2
- package/dist/constants/limits.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts +2 -2
- package/dist/manager/run/persistence.d.ts +37 -2
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +48 -7
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/pricing/catalogue.generated.d.ts +15 -0
- package/dist/pricing/catalogue.generated.d.ts.map +1 -0
- package/dist/pricing/catalogue.generated.js +293 -0
- package/dist/pricing/catalogue.generated.js.map +1 -0
- package/dist/pricing/index.d.ts +46 -0
- package/dist/pricing/index.d.ts.map +1 -0
- package/dist/pricing/index.js +70 -0
- package/dist/pricing/index.js.map +1 -0
- package/dist/public-runtime.d.ts +2 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +6 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +3 -1
- package/dist/public-types.d.ts.map +1 -1
- package/dist/run/LimitChecker.d.ts +10 -0
- package/dist/run/LimitChecker.d.ts.map +1 -1
- package/dist/run/LimitChecker.js +14 -2
- package/dist/run/LimitChecker.js.map +1 -1
- package/dist/runtime/query/checkpoint.d.ts +23 -2
- package/dist/runtime/query/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/checkpoint.js +24 -3
- package/dist/runtime/query/checkpoint.js.map +1 -1
- package/dist/runtime/query/guard.d.ts.map +1 -1
- package/dist/runtime/query/guard.js +1 -0
- package/dist/runtime/query/guard.js.map +1 -1
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +42 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +44 -8
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +71 -13
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/advisory.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/advisory.js +9 -1
- package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +11 -2
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/store/run/conformance.d.ts.map +1 -1
- package/dist/store/run/conformance.js +8 -2
- package/dist/store/run/conformance.js.map +1 -1
- package/dist/types/agent/supervisor.d.ts +29 -0
- package/dist/types/agent/supervisor.d.ts.map +1 -1
- package/dist/types/common/index.d.ts +38 -2
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/run/stop-reason.d.ts +31 -1
- package/dist/types/run/stop-reason.d.ts.map +1 -1
- package/dist/utils/cost.d.ts +60 -2
- package/dist/utils/cost.d.ts.map +1 -1
- package/dist/utils/cost.js +126 -11
- package/dist/utils/cost.js.map +1 -1
- package/package.json +1 -1
- package/src/advisory/executor.ts +16 -9
- package/src/agents/SupervisorAgent.ts +12 -0
- package/src/constants/limits.ts +14 -2
- package/src/manager/agent/lifecycle.ts +2 -2
- package/src/manager/run/persistence.ts +67 -7
- package/src/pricing/catalogue.generated.ts +308 -0
- package/src/pricing/index.ts +78 -0
- package/src/pricing/rates.source.json +206 -0
- package/src/public-runtime.ts +8 -0
- package/src/public-types.ts +3 -1
- package/src/run/LimitChecker.ts +24 -2
- package/src/runtime/query/checkpoint.ts +24 -3
- package/src/runtime/query/guard.ts +1 -0
- package/src/runtime/query/index.ts +42 -0
- package/src/runtime/query/iteration/index.ts +76 -13
- package/src/runtime/query/iteration/phases/advisory.ts +9 -1
- package/src/runtime/query/iteration/phases/compaction.ts +12 -2
- package/src/store/run/conformance.ts +8 -2
- package/src/types/agent/supervisor.ts +30 -0
- package/src/types/common/index.ts +38 -2
- package/src/types/run/stop-reason.ts +30 -0
- package/src/utils/cost.ts +186 -16
|
@@ -43,11 +43,32 @@ export function toCheckpointListEntry(cp: IterationCheckpoint): CheckpointListEn
|
|
|
43
43
|
* the same restore path as any other checkpoint.
|
|
44
44
|
*
|
|
45
45
|
* The projection is lossy: `costInfo`, `guardState.elapsedMs` and
|
|
46
|
-
* `toolResultHashes` are not captured at emergency-save time
|
|
47
|
-
* to zero/empty values. The synthetic
|
|
46
|
+
* `toolResultHashes` are not captured at emergency-save time. The synthetic
|
|
48
47
|
* checkpoint id is derived deterministically from the emergency save id so
|
|
49
48
|
* re-projecting the same dump yields the same {@link CheckpointId}.
|
|
50
49
|
*
|
|
50
|
+
* ## The cost projection says "unknown", not "zero"
|
|
51
|
+
*
|
|
52
|
+
* `costInfo` used to default to `{ ...ZERO_COST }` beside a `tokenUsage` the
|
|
53
|
+
* dump preserves faithfully — a run that had spent real money coming back as
|
|
54
|
+
* one that had spent nothing. That is the same lie the price catalogue was
|
|
55
|
+
* added to end, arriving through the restore path instead of the accumulation
|
|
56
|
+
* one, and it took `runConfig.costLimitUsd` back to being enforced against a
|
|
57
|
+
* zero with it.
|
|
58
|
+
*
|
|
59
|
+
* It also broke the accumulator downstream. `accumulateCost` decides whether
|
|
60
|
+
* one rate card describes the whole total by asking whether the total is
|
|
61
|
+
* FRESH — zero cost, zero unpriced tokens, no rates — and `ZERO_COST` is
|
|
62
|
+
* exactly that shape. So the first turn after such a resume adopted its own
|
|
63
|
+
* rate card as covering the pre-crash spend too, and reported a number that
|
|
64
|
+
* was confidently wrong while looking like a measured one.
|
|
65
|
+
*
|
|
66
|
+
* Saying instead that the pre-crash tokens are UNPRICED is the true statement,
|
|
67
|
+
* in the vocabulary {@link CostInfo} already has: nobody knows what they cost,
|
|
68
|
+
* because nothing recorded it. It needs no marker of its own to stay correct,
|
|
69
|
+
* and it is not fresh-shaped, so the accumulator does the right thing without
|
|
70
|
+
* being told about this path at all.
|
|
71
|
+
*
|
|
51
72
|
* See ses_005-deterministic-replay design §2 + §5.2.
|
|
52
73
|
*/
|
|
53
74
|
export function projectEmergencyToCheckpoint(dump: EmergencySaveData): IterationCheckpoint {
|
|
@@ -64,7 +85,7 @@ export function projectEmergencyToCheckpoint(dump: EmergencySaveData): Iteration
|
|
|
64
85
|
iteration: dump.currentIteration,
|
|
65
86
|
messages: dump.messages,
|
|
66
87
|
tokenUsage: dump.tokenUsage,
|
|
67
|
-
costInfo: { ...ZERO_COST },
|
|
88
|
+
costInfo: { ...ZERO_COST, unpricedTokens: dump.tokenUsage.totalTokens },
|
|
68
89
|
guardState: {
|
|
69
90
|
iterationCount: dump.currentIteration,
|
|
70
91
|
elapsedMs: Math.max(0, dump.savedAt - dump.startedAt),
|
|
@@ -92,6 +92,7 @@ export class GuardCoordinator {
|
|
|
92
92
|
aborted: abortSignal.aborted,
|
|
93
93
|
totalTokens: runMgr.tokenUsage.totalTokens,
|
|
94
94
|
totalCost: runMgr.costInfo.totalCost,
|
|
95
|
+
unpricedTokens: runMgr.costInfo.unpricedTokens,
|
|
95
96
|
currentIteration: runMgr.currentIteration,
|
|
96
97
|
startTime: this.startTime,
|
|
97
98
|
}
|
|
@@ -16,6 +16,7 @@ import type { CompactionConfig } from '../../config/runtime.js'
|
|
|
16
16
|
import { TOOL_OUTPUT_DIR_NAME } from '../../constants/tools/index.js'
|
|
17
17
|
import { EmergencySaveManager } from '../../manager/run/emergency.js'
|
|
18
18
|
import type { RunPersistence } from '../../manager/run/persistence.js'
|
|
19
|
+
import { resolveModelPricing } from '../../pricing/index.js'
|
|
19
20
|
import { resolveProviderCapabilities } from '../../provider/capabilities.js'
|
|
20
21
|
import {
|
|
21
22
|
type ProviderChainMember,
|
|
@@ -607,6 +608,46 @@ function assertCostIsAttributable(
|
|
|
607
608
|
})
|
|
608
609
|
}
|
|
609
610
|
|
|
611
|
+
/**
|
|
612
|
+
* Refuse a budget that cannot be measured.
|
|
613
|
+
*
|
|
614
|
+
* `runConfig.costLimitUsd` is enforced against `costInfo.totalCost`, and that
|
|
615
|
+
* total only moves for tokens something has a rate for. A model no rate card
|
|
616
|
+
* covers therefore produced a limit that could never trip — a host that set a
|
|
617
|
+
* cost cap had no cost cap, and nothing said so. That was every run before the
|
|
618
|
+
* price catalogue existed, which is how it went unnoticed.
|
|
619
|
+
*
|
|
620
|
+
* Refusing at the front is the cheap half of the answer: it costs the caller
|
|
621
|
+
* nothing, fires before any spend, and names both ways out. The other half is
|
|
622
|
+
* the `cost_unmeasurable` stop, for the models this cannot see — a step naming
|
|
623
|
+
* its own, or a chain member declaring one.
|
|
624
|
+
*
|
|
625
|
+
* This is the same shape `advisory/budget.ts` already applies to
|
|
626
|
+
* `AdvisoryBudget.maxCostPerRun`, one layer down, and for the same reason. The
|
|
627
|
+
* run path simply never had it.
|
|
628
|
+
*/
|
|
629
|
+
function assertBudgetIsMeasurable(params: QueryParams): void {
|
|
630
|
+
const limit = params.runConfig.costLimitUsd
|
|
631
|
+
if (limit === undefined || limit <= 0) return
|
|
632
|
+
// A host-supplied table prices whatever it is pointed at, so a caller who
|
|
633
|
+
// brought one has answered the question themselves.
|
|
634
|
+
if (params.pricing !== undefined) return
|
|
635
|
+
const model = params.runConfig.model
|
|
636
|
+
if (resolveModelPricing(params.provider.id, model) !== undefined) return
|
|
637
|
+
|
|
638
|
+
throw new NamzuError({
|
|
639
|
+
code: 'invalid_config',
|
|
640
|
+
message:
|
|
641
|
+
`runConfig.costLimitUsd is set to ${limit}, but no rate is known for model "${model}" on ` +
|
|
642
|
+
`provider "${params.provider.id}". The limit is enforced against the run's accumulated ` +
|
|
643
|
+
'cost, and tokens with no rate never reach that total — so the budget would read as ' +
|
|
644
|
+
'satisfied for the whole run and stop nothing. Either pass `pricing` to declare the rate ' +
|
|
645
|
+
'yourself, add the model to packages/sdk/src/pricing/rates.source.json, or drop ' +
|
|
646
|
+
'`costLimitUsd` and bound the run with `tokenBudget`, which is measurable here.',
|
|
647
|
+
details: { model, providerId: params.provider.id, costLimitUsd: limit },
|
|
648
|
+
})
|
|
649
|
+
}
|
|
650
|
+
|
|
610
651
|
export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run> {
|
|
611
652
|
// Boot-time filesystem migration (session-hierarchy.md §13.4.1). First
|
|
612
653
|
// call per process per root actually runs; subsequent calls short-circuit
|
|
@@ -639,6 +680,7 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
|
|
|
639
680
|
...(params.fallbackProviders ?? []),
|
|
640
681
|
]
|
|
641
682
|
assertCostIsAttributable(chain, params.pricing)
|
|
683
|
+
assertBudgetIsMeasurable(params)
|
|
642
684
|
const withRetry = (provider: LLMProvider): LLMProvider =>
|
|
643
685
|
params.retry === false
|
|
644
686
|
? provider
|
|
@@ -461,7 +461,17 @@ export class IterationOrchestrator {
|
|
|
461
461
|
})()
|
|
462
462
|
|
|
463
463
|
// Main-loop turn: also records the prompt size compaction reads.
|
|
464
|
-
|
|
464
|
+
//
|
|
465
|
+
// `servedBy` is what prices it, and it is the exact pair —
|
|
466
|
+
// the member at the cursor when the stream ended, and the
|
|
467
|
+
// model it was asked for. This is the seam that ended the
|
|
468
|
+
// always-zero cost: the rate lookup happens per turn,
|
|
469
|
+
// against who actually answered, rather than against one
|
|
470
|
+
// table the run was constructed with.
|
|
471
|
+
runMgr.recordTurnUsage(response.usage, {
|
|
472
|
+
providerId: servedBy.providerId,
|
|
473
|
+
model: servedBy.model,
|
|
474
|
+
})
|
|
465
475
|
|
|
466
476
|
// The turn went through, so the run is not sitting on an
|
|
467
477
|
// irreducible prompt any more. Re-arm relief for the next one.
|
|
@@ -862,8 +872,9 @@ export class IterationOrchestrator {
|
|
|
862
872
|
|
|
863
873
|
// A successful `structured_output` call IS the answer, so the
|
|
864
874
|
// run ends here rather than paying for another turn whose only
|
|
865
|
-
// job would be to restate it
|
|
866
|
-
|
|
875
|
+
// job would be to restate it — unless it shared its turn with
|
|
876
|
+
// other calls, which relays instead. See the method.
|
|
877
|
+
if (this.captureStructuredOutput(reviewOutcome.results, response)) {
|
|
867
878
|
this.ctx.log.info('Structured output produced — ending run', {
|
|
868
879
|
runId: runMgr.id,
|
|
869
880
|
iteration: iterationNum,
|
|
@@ -1357,14 +1368,6 @@ export class IterationOrchestrator {
|
|
|
1357
1368
|
return this.ctx.structuredOutput !== undefined && !this.structuredOutputDone
|
|
1358
1369
|
}
|
|
1359
1370
|
|
|
1360
|
-
/**
|
|
1361
|
-
* Record the structured output if this batch produced one.
|
|
1362
|
-
*
|
|
1363
|
-
* The tool validates against the Zod schema before its `execute` runs,
|
|
1364
|
-
* so reaching here successfully means the value is already valid — a
|
|
1365
|
-
* failed parse comes back as an error result and simply does not
|
|
1366
|
-
* satisfy the demand, which sends the loop round again.
|
|
1367
|
-
*/
|
|
1368
1371
|
/**
|
|
1369
1372
|
* The answer a terminal tool produced, or `undefined` to keep looping.
|
|
1370
1373
|
*
|
|
@@ -1405,10 +1408,67 @@ export class IterationOrchestrator {
|
|
|
1405
1408
|
return hit
|
|
1406
1409
|
}
|
|
1407
1410
|
|
|
1408
|
-
|
|
1411
|
+
/**
|
|
1412
|
+
* Record the structured output if this batch produced one.
|
|
1413
|
+
*
|
|
1414
|
+
* The tool validates against the Zod schema before its `execute` runs,
|
|
1415
|
+
* so reaching here successfully means the value is already valid — a
|
|
1416
|
+
* failed parse comes back as an error result and simply does not
|
|
1417
|
+
* satisfy the demand, which sends the loop round again.
|
|
1418
|
+
*
|
|
1419
|
+
* Narrow in the same way {@link terminalToolOutput} is, for its stated
|
|
1420
|
+
* reason and one that is sharper here. The neighbour refuses a shared
|
|
1421
|
+
* turn because "a model that asked for other work meant to see those
|
|
1422
|
+
* results". That applies unchanged. But the batch has ALREADY executed
|
|
1423
|
+
* by the time this runs — `runToolReview` settles it, side effects
|
|
1424
|
+
* included, before either of these is consulted — so settling here is
|
|
1425
|
+
* worse than discarding an answer the model wanted: the work happened,
|
|
1426
|
+
* its results went into the transcript, and the run ended before any
|
|
1427
|
+
* model turn could read them. Nothing consumed what was spent, and
|
|
1428
|
+
* nothing said so.
|
|
1429
|
+
*
|
|
1430
|
+
* Sharper, too, because of WHEN this value was produced. The model
|
|
1431
|
+
* emitted its final answer in the same turn as a request for
|
|
1432
|
+
* information it had not yet received — it would not have asked
|
|
1433
|
+
* otherwise — so the answer is under-informed on the model's own
|
|
1434
|
+
* account, and settling ships it as final.
|
|
1435
|
+
*
|
|
1436
|
+
* So: relay, do not settle. The results are already in the transcript,
|
|
1437
|
+
* the demand is still unsatisfied, and the next turn produces the
|
|
1438
|
+
* answer with them in hand. Refusing to EXECUTE the batch was the other
|
|
1439
|
+
* candidate and is wrong — the defect is not that the tools ran, it is
|
|
1440
|
+
* that nobody read them, and denying a model work it asked for to
|
|
1441
|
+
* protect an answer it has not finished forming gives up a real
|
|
1442
|
+
* capability for nothing. The price is one extra turn when the paired
|
|
1443
|
+
* call was a pure side effect whose result the model did not need;
|
|
1444
|
+
* that is the price `terminalToolOutput` already pays, and a model
|
|
1445
|
+
* avoids it by not pairing.
|
|
1446
|
+
*
|
|
1447
|
+
* NOT charged to `maxRetries`. That budget bounds a model that cannot
|
|
1448
|
+
* satisfy the SCHEMA, and this one did. A run reading two files a turn
|
|
1449
|
+
* while optimistically attaching its answer is making progress, and it
|
|
1450
|
+
* must not die reported as `structured_output_failed` — a failure that
|
|
1451
|
+
* did not happen. `maxIterations` is the bound for a model that keeps
|
|
1452
|
+
* doing work, and it is the bound the neighbour relies on for the
|
|
1453
|
+
* identical pathology.
|
|
1454
|
+
*/
|
|
1455
|
+
private captureStructuredOutput(
|
|
1456
|
+
results: readonly ToolCallOutcome[],
|
|
1457
|
+
response: ChatCompletionResponse,
|
|
1458
|
+
): boolean {
|
|
1409
1459
|
if (!this.needsStructuredOutput()) return false
|
|
1410
1460
|
const hit = results.find((r) => r.toolName === STRUCTURED_OUTPUT_TOOL_NAME && !r.isError)
|
|
1411
1461
|
if (!hit) return false
|
|
1462
|
+
|
|
1463
|
+
const callCount = response.message.toolCalls?.length ?? 0
|
|
1464
|
+
if (callCount > 1) {
|
|
1465
|
+
this.ctx.log.info('Structured output shared its turn — relaying instead of settling', {
|
|
1466
|
+
runId: this.ctx.runMgr.id,
|
|
1467
|
+
callsInTurn: callCount,
|
|
1468
|
+
})
|
|
1469
|
+
return false
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1412
1472
|
try {
|
|
1413
1473
|
this.ctx.runMgr.setStructuredOutput(JSON.parse(hit.output))
|
|
1414
1474
|
} catch {
|
|
@@ -1529,7 +1589,10 @@ export class IterationOrchestrator {
|
|
|
1529
1589
|
}),
|
|
1530
1590
|
)
|
|
1531
1591
|
|
|
1532
|
-
this.ctx.runMgr.accumulateUsage(response.usage
|
|
1592
|
+
this.ctx.runMgr.accumulateUsage(response.usage, {
|
|
1593
|
+
providerId: this.ctx.runMgr.servingProviderId,
|
|
1594
|
+
model,
|
|
1595
|
+
})
|
|
1533
1596
|
|
|
1534
1597
|
const assistantMsg = createAssistantMessage(response.message.content)
|
|
1535
1598
|
this.ctx.runMgr.pushMessage(assistantMsg)
|
|
@@ -117,7 +117,15 @@ export async function runAdvisoryPhase(
|
|
|
117
117
|
// `tokenBudget: 200_000` and an `on_error` trigger could send well
|
|
118
118
|
// past 200k and never trip `token_budget`. The usage is already in
|
|
119
119
|
// hand — this just tells the accountant about it.
|
|
120
|
-
|
|
120
|
+
// Priced against the ADVISOR's own driver and model, not the run's. An
|
|
121
|
+
// advisor carries its own `provider`, so attributing its tokens to
|
|
122
|
+
// whoever is serving the main loop would price one vendor's work at
|
|
123
|
+
// another's card — which is the class of quiet wrongness the whole
|
|
124
|
+
// catalogue exists to remove, and it would be invisible here.
|
|
125
|
+
ctx.runMgr.accumulateUsage(executionResult.usage, {
|
|
126
|
+
providerId: advisor.provider.id,
|
|
127
|
+
model: advisor.model,
|
|
128
|
+
})
|
|
121
129
|
|
|
122
130
|
advisoryCtx.recordCall({
|
|
123
131
|
advisorId: advisor.id,
|
|
@@ -542,19 +542,29 @@ export async function runCompactionCheck(
|
|
|
542
542
|
let compactedContent: string
|
|
543
543
|
|
|
544
544
|
if (config.llmVerification && manager.slotCount() < config.richStateThreshold) {
|
|
545
|
+
// Named once and used twice on purpose. The model the summariser is
|
|
546
|
+
// asked for is also the model its tokens are priced at, and computing
|
|
547
|
+
// the two separately is how they drift — a router that sends compaction
|
|
548
|
+
// to a cheap model while the bill is written against the expensive one
|
|
549
|
+
// is a mistake with no symptom.
|
|
550
|
+
const compactionModel = resolveTaskModel('compaction', ctx.taskRouter, ctx.runConfig.model)
|
|
545
551
|
compactedContent = await buildVerifiedSummary(
|
|
546
552
|
manager,
|
|
547
553
|
olderMessages,
|
|
548
554
|
ctx.provider,
|
|
549
555
|
config,
|
|
550
|
-
(usage) =>
|
|
556
|
+
(usage) =>
|
|
557
|
+
ctx.runMgr.accumulateUsage(usage, {
|
|
558
|
+
providerId: ctx.runMgr.servingProviderId,
|
|
559
|
+
model: compactionModel,
|
|
560
|
+
}),
|
|
551
561
|
// The one model call a run makes that the user never asked for. It
|
|
552
562
|
// reads a transcript and writes a summary, which is the cheapest
|
|
553
563
|
// thing a small model does well, and it fires on exactly the long
|
|
554
564
|
// runs where the primary model is most expensive. `taskRouter` had
|
|
555
565
|
// been accepted, validated and threaded through four types since it
|
|
556
566
|
// was added, and nothing ever consulted it.
|
|
557
|
-
|
|
567
|
+
compactionModel,
|
|
558
568
|
)
|
|
559
569
|
} else {
|
|
560
570
|
compactedContent = serializeState(manager.getState())
|
|
@@ -177,11 +177,17 @@ const FOREIGN_TENANT = 'tnt_conformance_other' as TenantId
|
|
|
177
177
|
/** Fixed instant, so every expiry in a case is judged against one clock. */
|
|
178
178
|
const NOW = 5_000_000
|
|
179
179
|
|
|
180
|
+
/**
|
|
181
|
+
* A run that cost nothing and has nothing unaccounted for — the shape a store
|
|
182
|
+
* has to round-trip, not a claim about any real run. Carries no rate fields,
|
|
183
|
+
* matching `ZERO_COST`: a store fixture that invents `inputCostPer1M: 0` would
|
|
184
|
+
* be asserting a rate card of zero, which now means "this model is free"
|
|
185
|
+
* rather than "no rate was applied".
|
|
186
|
+
*/
|
|
180
187
|
const NO_COST: CostInfo = {
|
|
181
|
-
inputCostPer1M: 0,
|
|
182
|
-
outputCostPer1M: 0,
|
|
183
188
|
totalCost: 0,
|
|
184
189
|
cacheDiscount: 0,
|
|
190
|
+
unpricedTokens: 0,
|
|
185
191
|
}
|
|
186
192
|
|
|
187
193
|
let checkpointSeq = 0
|
|
@@ -6,6 +6,7 @@ import type { LLMProvider } from '../provider/index.js'
|
|
|
6
6
|
import type { TaskRouterConfig } from '../router/index.js'
|
|
7
7
|
import type { SandboxProvider } from '../sandbox/index.js'
|
|
8
8
|
import type { Skill } from '../skills/index.js'
|
|
9
|
+
import type { StructuredOutputConfig } from '../structured-output/index.js'
|
|
9
10
|
import type { ToolRegistryContract } from '../tool/index.js'
|
|
10
11
|
import type { VerificationGateConfig } from '../verification/index.js'
|
|
11
12
|
import type { BaseAgentConfig, BaseAgentResult } from './base.js'
|
|
@@ -172,6 +173,35 @@ export interface SupervisorAgentConfig extends BaseAgentConfig {
|
|
|
172
173
|
*/
|
|
173
174
|
compactionConfig?: CompactionConfig
|
|
174
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Demand that the supervisor's own final answer match a schema.
|
|
178
|
+
*
|
|
179
|
+
* `ReactiveAgent` has forwarded this since the field existed and the
|
|
180
|
+
* supervisor never took it, with nothing in this file saying why —
|
|
181
|
+
* which in a file where `maxDepth`, `allowDelegation`,
|
|
182
|
+
* `maxToolConcurrency` and `siblingFailurePolicy` each carry a
|
|
183
|
+
* paragraph of argument is the signature of an oversight, not of a
|
|
184
|
+
* decision. The kernel path is archetype-blind: `drainQuery` registers
|
|
185
|
+
* `structured_output` from this config and the loop captures it, so the
|
|
186
|
+
* capability was always there and only the hop was missing.
|
|
187
|
+
*
|
|
188
|
+
* **What it buys, exactly.** Structured output is terminal and
|
|
189
|
+
* exclusive by policy: `setStructuredOutput` overwrites `Run.result`
|
|
190
|
+
* behind a sticky flag and the run ends on the turn that produces it.
|
|
191
|
+
* So this gives a supervisor a schema-constrained FINAL ANSWER and
|
|
192
|
+
* nothing more. It does not shape a delegated child's answer — a child
|
|
193
|
+
* carries its own config — it does not run alongside prose, and it is
|
|
194
|
+
* not a return type for the fan-out. A host wanting typed results from
|
|
195
|
+
* the workers sets the schema on the workers.
|
|
196
|
+
*
|
|
197
|
+
* One consequence a supervisor host in particular should know: the
|
|
198
|
+
* answer decides the run, so delegated work still running when it lands
|
|
199
|
+
* is walked away from rather than waited for. It is recorded — the run
|
|
200
|
+
* names it on `abandonedTaskIds` — but it is not delivered. That is the
|
|
201
|
+
* same precedence a terminal tool has, stated in the iteration loop.
|
|
202
|
+
*/
|
|
203
|
+
structuredOutput?: StructuredOutputConfig
|
|
204
|
+
|
|
175
205
|
/**
|
|
176
206
|
* Optional neutral working-memory seam. When set, the SDK re-renders the
|
|
177
207
|
* provider's string into a single pinned leading system message every
|
|
@@ -74,10 +74,46 @@ export function mergeTokenUsage(current: TokenUsage, next: TokenUsage): TokenUsa
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
export interface CostInfo {
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The input rate this total was accumulated at — present only when ONE rate
|
|
79
|
+
* card describes the whole total.
|
|
80
|
+
*
|
|
81
|
+
* Absent means "no single rate describes this", not "zero". Three ways to
|
|
82
|
+
* get there: nothing has been accumulated yet, the run spanned two
|
|
83
|
+
* differently-priced models, or part of it was accumulated at no known rate
|
|
84
|
+
* at all. This is the same contract {@link TokenUsage.reasoningTokens} uses
|
|
85
|
+
* one field up, for the same reason — a number invented to fill the slot
|
|
86
|
+
* would be indistinguishable from a measured one.
|
|
87
|
+
*
|
|
88
|
+
* The field used to be required, and a run that swapped models reported
|
|
89
|
+
* whichever card was applied last, which is a claim about the whole total
|
|
90
|
+
* that was true of only part of it.
|
|
91
|
+
*/
|
|
92
|
+
inputCostPer1M?: number
|
|
93
|
+
/** As {@link CostInfo.inputCostPer1M}, for output tokens. */
|
|
94
|
+
outputCostPer1M?: number
|
|
79
95
|
totalCost: number
|
|
80
96
|
cacheDiscount: number
|
|
97
|
+
/**
|
|
98
|
+
* Tokens accumulated at no known rate, so `totalCost` does not include what
|
|
99
|
+
* they cost.
|
|
100
|
+
*
|
|
101
|
+
* This exists so a consumer can tell "this run cost nothing" from "nobody
|
|
102
|
+
* knows what this run cost". Reporting the second as zero is the defect the
|
|
103
|
+
* price catalogue was added to fix, one level down: a total that is always
|
|
104
|
+
* zero and a total that is zero because it is unknown look identical, and
|
|
105
|
+
* `runConfig.costLimitUsd` is enforced against both.
|
|
106
|
+
*
|
|
107
|
+
* - `totalCost: 0, unpricedTokens: 0` — the run genuinely cost nothing
|
|
108
|
+
* (local inference bills per token exactly never).
|
|
109
|
+
* - `totalCost: 0, unpricedTokens: 4210` — nobody knows.
|
|
110
|
+
* - `totalCost: 0.12, unpricedTokens: 900` — partly known; the total is a
|
|
111
|
+
* floor, not the answer.
|
|
112
|
+
*
|
|
113
|
+
* A count rather than a flag because a run mixes turns: a step can name its
|
|
114
|
+
* own model and a provider chain can swap members mid-run.
|
|
115
|
+
*/
|
|
116
|
+
unpricedTokens: number
|
|
81
117
|
}
|
|
82
118
|
|
|
83
119
|
export interface PlatformError {
|
|
@@ -2,6 +2,36 @@ export type StopReason =
|
|
|
2
2
|
| 'end_turn'
|
|
3
3
|
| 'token_budget'
|
|
4
4
|
| 'cost_limit'
|
|
5
|
+
/**
|
|
6
|
+
* A `costLimitUsd` was set and part of the run ran at a rate nobody has, so
|
|
7
|
+
* the limit could not be measured.
|
|
8
|
+
*
|
|
9
|
+
* Distinct from `cost_limit`, and the distinction is the reason this value
|
|
10
|
+
* exists: nothing was overspent. Reporting `cost_limit` would send the
|
|
11
|
+
* reader to look at spend that was never computed, and would hide the one
|
|
12
|
+
* fact they need — that the budget they configured was unenforceable for
|
|
13
|
+
* part of this run.
|
|
14
|
+
*
|
|
15
|
+
* `query()` refuses the same combination up front, so this is the case
|
|
16
|
+
* preflight cannot see: a step naming its own model, or a chain member
|
|
17
|
+
* declaring one, arriving at a model the price catalogue has no row for.
|
|
18
|
+
* `costInfo.unpricedTokens` says how much of the run it covers.
|
|
19
|
+
*
|
|
20
|
+
* ## The closing call is outside the budget, deliberately
|
|
21
|
+
*
|
|
22
|
+
* Every hard stop is followed by one more model call — `requestFinalResponse`
|
|
23
|
+
* asks for a closing summary, and the guard does not run again before or
|
|
24
|
+
* after it. That is pre-existing and true of `cost_limit` and
|
|
25
|
+
* `token_budget` alike, and it is stated here rather than left to be
|
|
26
|
+
* discovered because for THIS reason it cannot be otherwise: the model that
|
|
27
|
+
* triggered the stop is by definition one with no rate, so the closing
|
|
28
|
+
* call's cost is unmeasurable by construction. Bounding it would mean
|
|
29
|
+
* refusing to close the run at all, which loses the work.
|
|
30
|
+
*
|
|
31
|
+
* Its tokens are counted, so `costInfo.unpricedTokens` includes them and
|
|
32
|
+
* the run reports honestly what it could not price.
|
|
33
|
+
*/
|
|
34
|
+
| 'cost_unmeasurable'
|
|
5
35
|
| 'timeout'
|
|
6
36
|
| 'max_iterations'
|
|
7
37
|
| 'cancelled'
|