@namzu/sdk 16.0.0 → 17.0.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 +24 -0
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +13 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +16 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/contracts/api.d.ts +8 -1
- package/dist/contracts/api.d.ts.map +1 -1
- package/dist/provider/fallback.d.ts +93 -0
- package/dist/provider/fallback.d.ts.map +1 -0
- package/dist/provider/fallback.js +265 -0
- package/dist/provider/fallback.js.map +1 -0
- package/dist/provider/index.d.ts +2 -0
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +1 -0
- package/dist/provider/index.js.map +1 -1
- package/dist/public-runtime.d.ts +2 -2
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +1 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +16 -0
- package/dist/run/reporter.js.map +1 -1
- package/dist/runtime/query/index.d.ts +17 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +56 -3
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +25 -0
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/types/provider/stream.d.ts +35 -0
- package/dist/types/provider/stream.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +29 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/package.json +1 -1
- package/src/bridge/a2a/mapper.ts +16 -0
- package/src/bridge/sse/mapper.ts +17 -0
- package/src/contracts/api.ts +7 -0
- package/src/provider/fallback.ts +300 -0
- package/src/provider/index.ts +2 -0
- package/src/public-runtime.ts +7 -1
- package/src/run/reporter.ts +20 -0
- package/src/runtime/query/index.ts +80 -3
- package/src/runtime/query/iteration/stream-turn.ts +26 -0
- package/src/types/provider/stream.ts +37 -0
- package/src/types/run/events.ts +29 -0
|
@@ -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 { resolveProviderCapabilities } from '../../provider/capabilities.js'
|
|
19
|
+
import { type ProviderChainMember, withProviderFallback } from '../../provider/fallback.js'
|
|
19
20
|
import { type ProviderRetryConfig, withProviderRetry } from '../../provider/retry.js'
|
|
20
21
|
import type { PathBuilder } from '../../session/workspace/path-builder.js'
|
|
21
22
|
import {
|
|
@@ -120,6 +121,23 @@ export interface QueryParams {
|
|
|
120
121
|
*/
|
|
121
122
|
retry?: Partial<ProviderRetryConfig> | false
|
|
122
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Members to fall over to, in order, when {@link provider} cannot serve.
|
|
126
|
+
*
|
|
127
|
+
* Absent means what it always meant: one provider, no failover. Each member
|
|
128
|
+
* is tried at most once per call and the chain never rewinds, so the scope
|
|
129
|
+
* of a swap is this `query()` — for a host whose call is one user turn, that
|
|
130
|
+
* is turn scope with no reset to forget. See `withProviderFallback`.
|
|
131
|
+
*
|
|
132
|
+
* Two things this does NOT do, both deliberate. Capabilities are negotiated
|
|
133
|
+
* once against {@link provider}, so a member that declares less will be sent
|
|
134
|
+
* a request shaped for the head — refuse a disagreeing chain before you
|
|
135
|
+
* build one. And a fallback loses the prompt cache: the replacement provider
|
|
136
|
+
* has never seen this conversation, so the turn re-reads its whole context
|
|
137
|
+
* at full price.
|
|
138
|
+
*/
|
|
139
|
+
fallbackProviders?: readonly ProviderChainMember[]
|
|
140
|
+
|
|
123
141
|
/**
|
|
124
142
|
* Install process-level crash handlers that dump this run's state to
|
|
125
143
|
* `<runDir>/../emergency/<runId>.json` on SIGINT, SIGTERM or an
|
|
@@ -472,6 +490,46 @@ export interface QueryParams {
|
|
|
472
490
|
strictCapabilities?: boolean
|
|
473
491
|
}
|
|
474
492
|
|
|
493
|
+
/**
|
|
494
|
+
* Refuse to price a run whose tokens two differently-priced members may produce.
|
|
495
|
+
*
|
|
496
|
+
* `RunPersistence` holds ONE {@link ModelPricing} table and applies it to every
|
|
497
|
+
* accumulation regardless of which model produced the tokens. Across a swap that
|
|
498
|
+
* makes `costInfo.totalCost` wrong by an unbounded margin, and silently — the
|
|
499
|
+
* number keeps the shape of an answer. `CostInfo` cannot express the truth
|
|
500
|
+
* either: it carries `inputCostPer1M` / `outputCostPer1M`, and there is no
|
|
501
|
+
* honest value for those once a total spans two rate cards.
|
|
502
|
+
*
|
|
503
|
+
* So the total is refused rather than blended. Naming what that costs is part
|
|
504
|
+
* of the refusal, because the caller loses `costLimitUsd` with it: the guard
|
|
505
|
+
* enforces that limit from this same accumulated total, and a limit enforced
|
|
506
|
+
* with the wrong rate card stops a run early or late by the same unbounded
|
|
507
|
+
* margin. A budget that is quietly wrong is worse than a budget that is
|
|
508
|
+
* declined.
|
|
509
|
+
*
|
|
510
|
+
* Reachable, not decorative: a host that passes `pricing` and declares a chain
|
|
511
|
+
* hits it on the first call. It costs `@namzu/cli` nothing, which passes no
|
|
512
|
+
* pricing at all — its `/cost` already reports that the provider gave no price.
|
|
513
|
+
*
|
|
514
|
+
* The way out is per-member pricing, which needs a `CostInfo` that can sum over
|
|
515
|
+
* heterogeneous rates. That is a public-type change and it is not this one.
|
|
516
|
+
*/
|
|
517
|
+
function assertCostIsAttributable(
|
|
518
|
+
chain: readonly ProviderChainMember[],
|
|
519
|
+
pricing: ModelPricing | undefined,
|
|
520
|
+
): void {
|
|
521
|
+
if (pricing === undefined || chain.length < 2) return
|
|
522
|
+
throw new NamzuError({
|
|
523
|
+
code: 'invalid_config',
|
|
524
|
+
message:
|
|
525
|
+
`A provider chain of ${chain.length} members was declared together with a single pricing table. ` +
|
|
526
|
+
'One table cannot price two members, so the run would report a total that is wrong by an unbounded ' +
|
|
527
|
+
'margin — and `runConfig.costLimitUsd` would be enforced against that same wrong total. ' +
|
|
528
|
+
'Either drop `pricing` (usage is still reported per model in the run) or declare one member.',
|
|
529
|
+
details: { chainLength: chain.length },
|
|
530
|
+
})
|
|
531
|
+
}
|
|
532
|
+
|
|
475
533
|
export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run> {
|
|
476
534
|
// Boot-time filesystem migration (session-hierarchy.md §13.4.1). First
|
|
477
535
|
// call per process per root actually runs; subsequent calls short-circuit
|
|
@@ -489,10 +547,29 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
|
|
|
489
547
|
// of its warns behind `options.log`, and this is its only production
|
|
490
548
|
// call site — so without it the "failed, retrying" and "failed, giving
|
|
491
549
|
// up" lines were dead code and a backoff left no trace anywhere.
|
|
492
|
-
|
|
550
|
+
//
|
|
551
|
+
// With a chain declared, the same sentence holds one level out: retry is
|
|
552
|
+
// applied per MEMBER and the fallback decorator wraps the result, so the
|
|
553
|
+
// composition is `fallback(retry(m0), retry(m1), …)`. That order is not a
|
|
554
|
+
// preference. Assembled the other way round — which is what a host gets if
|
|
555
|
+
// it wraps its own chain and hands the result in, because this function
|
|
556
|
+
// would then wrap THAT in retry — an exhausted chain gets restarted from
|
|
557
|
+
// the head by the outer loop and a throttle on the last member is counted
|
|
558
|
+
// by two budgets. Building it here is what makes the order unspellable
|
|
559
|
+
// wrong.
|
|
560
|
+
const chain: readonly ProviderChainMember[] = [
|
|
561
|
+
{ provider: params.provider },
|
|
562
|
+
...(params.fallbackProviders ?? []),
|
|
563
|
+
]
|
|
564
|
+
assertCostIsAttributable(chain, params.pricing)
|
|
565
|
+
const withRetry = (provider: LLMProvider): LLMProvider =>
|
|
493
566
|
params.retry === false
|
|
494
|
-
?
|
|
495
|
-
: withProviderRetry(
|
|
567
|
+
? provider
|
|
568
|
+
: withProviderRetry(provider, { config: params.retry, log: getRootLogger() })
|
|
569
|
+
const resilientProvider = withProviderFallback(
|
|
570
|
+
chain.map((member) => ({ ...member, provider: withRetry(member.provider) })),
|
|
571
|
+
{ log: getRootLogger() },
|
|
572
|
+
)
|
|
496
573
|
|
|
497
574
|
const ctx = RunContextFactory.build({
|
|
498
575
|
agentId: params.agentId,
|
|
@@ -258,6 +258,32 @@ export async function* streamProviderTurn(
|
|
|
258
258
|
continue
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
// A chain swap, not output, and handled beside the retry notice
|
|
262
|
+
// because it is the same kind of thing: a fact about HOW the answer
|
|
263
|
+
// is being produced, arriving on the only channel open while the
|
|
264
|
+
// consumer is blocked inside the provider's iterator. It carries no
|
|
265
|
+
// delta, so nothing below applies to it either.
|
|
266
|
+
if (chunk.fallback) {
|
|
267
|
+
await emitEvent({
|
|
268
|
+
type: 'provider_fallback',
|
|
269
|
+
runId,
|
|
270
|
+
iteration,
|
|
271
|
+
fromIndex: chunk.fallback.fromIndex,
|
|
272
|
+
fromProviderId: chunk.fallback.fromProviderId,
|
|
273
|
+
...(chunk.fallback.fromModel !== undefined
|
|
274
|
+
? { fromModel: chunk.fallback.fromModel }
|
|
275
|
+
: {}),
|
|
276
|
+
toIndex: chunk.fallback.toIndex,
|
|
277
|
+
toProviderId: chunk.fallback.toProviderId,
|
|
278
|
+
...(chunk.fallback.toModel !== undefined ? { toModel: chunk.fallback.toModel } : {}),
|
|
279
|
+
code: chunk.fallback.code,
|
|
280
|
+
...(chunk.fallback.status !== undefined ? { status: chunk.fallback.status } : {}),
|
|
281
|
+
reason: chunk.fallback.reason,
|
|
282
|
+
})
|
|
283
|
+
yield* drainPending()
|
|
284
|
+
continue
|
|
285
|
+
}
|
|
286
|
+
|
|
261
287
|
if (chunk.error) {
|
|
262
288
|
streamError = chunk.error
|
|
263
289
|
break
|
|
@@ -81,6 +81,18 @@ export interface StreamChunk {
|
|
|
81
81
|
* output.
|
|
82
82
|
*/
|
|
83
83
|
retry?: ProviderRetryNotice
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The call failed and a different member of the provider chain is taking
|
|
87
|
+
* over from here.
|
|
88
|
+
*
|
|
89
|
+
* Emitted by the fallback decorator, never by a driver, and it rides the
|
|
90
|
+
* stream for the reason {@link retry} does. Like a retry notice it carries
|
|
91
|
+
* no delta and must not be treated as output — and that distinction is
|
|
92
|
+
* load-bearing twice over, because the fallback decorator reads these
|
|
93
|
+
* chunks too when deciding whether output has already gone out.
|
|
94
|
+
*/
|
|
95
|
+
fallback?: ProviderFallbackNotice
|
|
84
96
|
}
|
|
85
97
|
|
|
86
98
|
/** See {@link StreamChunk.retry}. */
|
|
@@ -96,3 +108,28 @@ export interface ProviderRetryNotice {
|
|
|
96
108
|
/** The delay came from the server's own `Retry-After`, not backoff. */
|
|
97
109
|
readonly serverDirected: boolean
|
|
98
110
|
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* See {@link StreamChunk.fallback}.
|
|
114
|
+
*
|
|
115
|
+
* Both members are named, not just the new one. Naming only the replacement
|
|
116
|
+
* does not tell an operator which of their declared members went down, and on a
|
|
117
|
+
* chain of four that is the only fact they can act on.
|
|
118
|
+
*
|
|
119
|
+
* The positions are 0-based indices into the chain as the host declared it, so
|
|
120
|
+
* a surface can name a member the way its own configuration does rather than
|
|
121
|
+
* inventing a second numbering.
|
|
122
|
+
*/
|
|
123
|
+
export interface ProviderFallbackNotice {
|
|
124
|
+
readonly fromIndex: number
|
|
125
|
+
readonly fromProviderId: string
|
|
126
|
+
readonly fromModel?: string
|
|
127
|
+
readonly toIndex: number
|
|
128
|
+
readonly toProviderId: string
|
|
129
|
+
readonly toModel?: string
|
|
130
|
+
/** Classified failure code, as `classifyProviderError` reports it. */
|
|
131
|
+
readonly code: string
|
|
132
|
+
readonly status?: number
|
|
133
|
+
/** The classified failure's own sentence, already redacted at its source. */
|
|
134
|
+
readonly reason: string
|
|
135
|
+
}
|
package/src/types/run/events.ts
CHANGED
|
@@ -186,6 +186,35 @@ type CoreRunEvent =
|
|
|
186
186
|
/** The delay came from the server's own `Retry-After`. */
|
|
187
187
|
serverDirected: boolean
|
|
188
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* A member of the provider chain could not serve, and a later member has
|
|
191
|
+
* taken over. The run continues from where it stopped.
|
|
192
|
+
*
|
|
193
|
+
* This event is the feature's honesty. A chain that swapped silently would
|
|
194
|
+
* produce a run that succeeded while quietly not doing what the operator
|
|
195
|
+
* asked — served by a provider they did not choose, at a price and a
|
|
196
|
+
* quality they did not agree to, with nothing in the transcript saying so.
|
|
197
|
+
* A host is expected to SHOW this, not log it.
|
|
198
|
+
*
|
|
199
|
+
* Emitted at the moment of the swap, before the replacement request runs.
|
|
200
|
+
*/
|
|
201
|
+
| {
|
|
202
|
+
type: 'provider_fallback'
|
|
203
|
+
runId: RunId
|
|
204
|
+
iteration: number
|
|
205
|
+
/** 0-based position in the chain, as the host declared it. */
|
|
206
|
+
fromIndex: number
|
|
207
|
+
fromProviderId: string
|
|
208
|
+
fromModel?: string
|
|
209
|
+
toIndex: number
|
|
210
|
+
toProviderId: string
|
|
211
|
+
toModel?: string
|
|
212
|
+
/** Classified failure code, as the boundary classifier reports it. */
|
|
213
|
+
code: string
|
|
214
|
+
status?: number
|
|
215
|
+
/** The classified failure's own sentence. */
|
|
216
|
+
reason: string
|
|
217
|
+
}
|
|
189
218
|
| {
|
|
190
219
|
type: 'tool_completed'
|
|
191
220
|
runId: RunId
|