@voltro/ai 0.58.0 → 0.60.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 +113 -0
- package/dist/index.d.ts +106 -9
- package/dist/index.js +451 -453
- package/dist/{inferenceQueue-EQJJepZ8.js → inferenceQueue-CS1VouiW.js} +213 -192
- package/dist/workflow.js +14 -14
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -39,6 +39,119 @@ _Changes staged for the next release accumulate here (rolled up from
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## [0.60.0] — 2026-08-31
|
|
43
|
+
|
|
44
|
+
### ⚠ BREAKING
|
|
45
|
+
|
|
46
|
+
- **@voltro/plugin-sentry** — **A declared failure no longer reaches Sentry by default, and the source-map upload now injects debug ids.** Two separate defects, both found by the same deployment on its first day of real server-side events.
|
|
47
|
+
|
|
48
|
+
**1. The contract was being reported as an incident.** The rpc interceptor skipped only clean interrupts; everything else went to `captureException`. So a failure declared in a procedure's `error:` union — the thing the client gets typed and branches on — arrived as `level: error`, `handled: yes`. The first server-side issue a deployment ever received was a person clicking a team they are not a member of.
|
|
49
|
+
|
|
50
|
+
Effect separates a failure from a defect, this framework leans on that split deliberately (a store refusal was made typed so an app could branch on it; an unlookupable conflict key was deliberately left a defect, because it is a broken call rather than a condition in the data), and a descriptor carries it in `error:`. Reporting both as an incident discarded that one layer up.
|
|
51
|
+
|
|
52
|
+
`shouldCapture` now skips a cause that is failures-ONLY. A defect is reported as before, including a defect that travelled beside a failure — the rule is failures-only rather than "any failure present" precisely so one cannot hide the other. `captureFailures: true` restores the old behaviour; a predicate keeps the ones that are signal.
|
|
53
|
+
|
|
54
|
+
**The browser half moved with it**, or the option would have been half-wired: a rejected call is an rpc error on the client too, published to the client error bus and captured by the browser bridge. `initSentryBrowser` takes the same option and applies it to `rpc.*` events carrying a `_tag`. Route render failures and `reportClientError` calls are never filtered — nobody declared those.
|
|
55
|
+
|
|
56
|
+
**2. `sentry-cli sourcemaps upload` does NOT write debug ids.** `inject` is a separate subcommand; `upload` only uses ids that are already present, and falls back to matching on the artifact NAME when they are not. That fallback cannot work for a server bundle: the artifact is named from `--url-prefix` (`~/chunk- ABC.js`) while the frame carries the absolute path the node process loaded, and nothing rewrites either side.
|
|
57
|
+
|
|
58
|
+
Measured downstream: 4300 artifacts uploaded, release finalised, every frame still minified. Nothing was red — the exact shape this code's own header warns about, an upload that matched nothing looking like one that worked. The comment above the uploader asserted the injection happened, which made it a description standing where a check belonged.
|
|
59
|
+
|
|
60
|
+
`inject` now runs first, over the same directories, and `sourcemapDebugIds.test.ts` drives the real binary to assert an id lands in both the JS and the map. `--url-prefix` stays as the fallback for the browser bundle, whose frames really are URLs.
|
|
61
|
+
|
|
62
|
+
The boot line names the new setting (`sentry active … captureFailures=false`), because a default the framework picks for you is one nobody finds again.
|
|
63
|
+
|
|
64
|
+
**`voltro update` carries you across this** — codemod `0.60.0/01_declared_failures_are_not_incidents`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.60.0).
|
|
65
|
+
|
|
66
|
+
### Added
|
|
67
|
+
|
|
68
|
+
- **@voltro/cli, @voltro/plugin-sentry** — **The web server process now initialises Sentry and reports its own errors.**
|
|
69
|
+
|
|
70
|
+
A framework app runs two server processes. `voltro serve` is the api, where `sentryPlugin()` initialises the SDK through the plugin lifecycle. `voltro start` is the web server — SSR, loaders, ISR, the revalidation legs — and it has no plugin lifecycle, so it had none.
|
|
71
|
+
|
|
72
|
+
That was not a cosmetic difference in boot output. A web pod legitimately logs less than an api pod, because it has no store, no rpc, no scheduler, no workflows and no plugins. What it also had was **no error reporting**: an SSR shell throw is caught, logged and answered with a 500, so no browser ever renders it and the client-side ErrorBoundary bridge cannot see it either. Both ends of the integration worked and the middle was dark — while the docs' "React render error ✅ auto" row, true of the client path, read as covering all of them.
|
|
73
|
+
|
|
74
|
+
Set `SENTRY_DSN` on the web deployment and the boot says `sentry active` with the same message and the same field names as the api half, from the same function — `initSentryServer` in `@voltro/plugin-sentry/server`, which the api plugin now calls too. One init, because a copy of `skipOpenTelemetrySetup`, the traces default, the overrides-first spread and the degrade-on-missing-SDK path would have drifted the moment either half gained a case.
|
|
75
|
+
|
|
76
|
+
Three deliberate asymmetries, each because the two processes are not the same thing:
|
|
77
|
+
|
|
78
|
+
- **`traces: false` on the web side.** The api contributes a span processor to the framework's tracer; the web server has no tracer at all, so tracing on would put `traces: true` in a boot line while nothing produces a span. - **No DSN is SILENT here.** On the api side `sentryPlugin()` is a declaration and an inert one contradicts it. There is no declaration here. - **`@voltro/plugin-sentry` must be installed in the WEB app.** With a DSN set and the package missing, the boot names the command rather than failing — monitoring must never be what stops a deploy.
|
|
79
|
+
|
|
80
|
+
Measured against a real production `voltro start`, both branches. Note that production `voltro start` loads the app's precompiled start bundle, so a web deployment picks this up when that bundle is rebuilt — the framework version alone is not enough.
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- **@voltro/workflow, @voltro/runtime, @voltro/database, @voltro/cli, @voltro/plugin-sentry** — **Six raw writes removed from production log streams — and the guard that was supposed to catch them rewritten, because it was green for two independent reasons.**
|
|
85
|
+
|
|
86
|
+
A pod tail showed `[voltro:workflow] shard-lock coordination: row-based (dialect=mariadb, mode=row)` sitting between JSON records. `@voltro/logger` is what makes a line JSON in a pod and pretty on a TTY; a `process.stderr.write` bypasses that decision at exactly the place nobody looks, because a dev terminal renders both the same.
|
|
87
|
+
|
|
88
|
+
Fixed at the source: the workflow cluster layer (2), `rpcServer`'s computed-cache warning, the subscription outbox's and the RYW store's `warn`/`onError` defaults — those two are not fallbacks, the callers pass nothing, so the default IS the production path — and the migration file discovery's skip notice, which lands in the migrate job's stream.
|
|
89
|
+
|
|
90
|
+
**The guard is the part worth reading.** `prodLogDiscipline.test.ts` existed for this exact class and reported clean, for two reasons that had to be fixed separately:
|
|
91
|
+
|
|
92
|
+
- Its file set was a hand-written list of five. A guard that opts files IN says nothing about any file added after it was written. - Its matcher was LINE-LOCAL, so `process.stderr.write(` on one line and the `` `[tag] `` on the next never matched — 4 of the 9 call sites in the repo are written that way, including one in a file that WAS on the list. The guard had been pointed straight at an offender and called it clean.
|
|
93
|
+
|
|
94
|
+
It is opt-OUT now: every server-side package's source is scanned, exceptions carry a reason, and a second test fails if an exception's call site disappears — an allowlist entry for code that is gone reads as a rule with a hole in it.
|
|
95
|
+
|
|
96
|
+
Verified against a real `voltro serve` under `NODE_ENV=production`: 35 records, zero framework lines that are not JSON.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## [0.59.0] — 2026-08-30
|
|
101
|
+
|
|
102
|
+
### ⚠ BREAKING
|
|
103
|
+
|
|
104
|
+
- **@voltro/cli** — **`web.sourcemaps` moved to the top level of `app.config.ts`, and now covers every bundle the build minifies.**
|
|
105
|
+
|
|
106
|
+
It reached exactly one of them before: the vite CLIENT build. `voltro build` minifies four — the browser bundle, the SSR bundle, the `voltro start` bundle, and for an api app the precompiled entry plus the serve bundle that `voltro serve` actually runs — and none of the other three had a `sourcemap` option or a config path that could ask for one. `keepNames` rescues the identifiers that are read as strings and nothing else, so a frame out of the artefact production runs read `at Qo (serveEntry.js:236:76405)`.
|
|
107
|
+
|
|
108
|
+
The docs closed that hole with a sentence instead of a field: *server stack traces are un-minified already*. The built file says otherwise, and the server half is where it costs most — a schedule, a workflow step, a subscriber or a REST route has no browser caller whose frame could stand in for it.
|
|
109
|
+
|
|
110
|
+
A second field (`api.sourcemaps`) would have made the same mistake twice. "Do I want resolvable frames from this app" has one answer; how many bundles the build happens to emit is not the caller's business.
|
|
111
|
+
|
|
112
|
+
Three details that come with it:
|
|
113
|
+
|
|
114
|
+
- **The old spelling is REFUSED, not ignored.** A moved option that is silently dropped goes green, stops emitting maps, and is found by someone reading an unresolvable frame months later. - **Each output directory uploads under the prefix that matches how its files are named** — `~/assets` for the browser bundle, `~/` for the bundles a node process loaded. One prefix across both is wrong for one of them. - **The upload and sweep run at every exit of the build**, not after the client bundle. Sweeping there would have deleted the client maps before the server ones existed, leaving those neither uploaded nor deleted.
|
|
115
|
+
|
|
116
|
+
`minifiedBundlesCarryMaps.test.ts` asserts the property over the SET: every bundler options object in the CLI that turns minification on must also decide `sourcemap`. That is what was missing — not one option, three, because nothing was watching the class.
|
|
117
|
+
|
|
118
|
+
**`voltro update` carries you across this** — codemod `0.59.0/01_sourcemaps_moved_to_the_top_level`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.59.0).
|
|
119
|
+
- **@voltro/ai** — **Speech has a cost path, and a built-in provider name can be taken over.**
|
|
120
|
+
|
|
121
|
+
Three changes, one thread: the speech seam existed and nothing could reach it.
|
|
122
|
+
|
|
123
|
+
**`registerSpeechProvider(name, provider, { replaceBuiltin: true })`.** Refusing `mock` / `openai` / `gateway` / `anthropic` outright was wrong, and the case that shows it is one the refusal could not see: a provider name is not always an alias the app chooses. `openai` is the segment the gateway puts in its model ids, so it is what a stored catalog's rows carry and what a picker submits — an app told to pick another name cannot, without rewriting persisted rows and live sessions. It keeps its own dispatch instead, for every provider rather than the colliding one. What the refusal protected is kept by putting the intent in the call.
|
|
124
|
+
|
|
125
|
+
**Registered providers resolve BEFORE built-ins.** The dispatch decided from a hard-coded list of built-in names, so without this the flag would let you register `openai` and never route to it — declared, documented, wired at zero call sites.
|
|
126
|
+
|
|
127
|
+
**`MeteredUsage` — a tally in characters or seconds.** Every speech vendor meters one of those; none meters tokens. A ledger that only knew tokens could not hold a speech call at all, so speech spend was not un-priced, it was *absent*. The token shape is untouched (it is what the AI SDK returns); the new shape is separate rather than a unit tacked onto `AiUsage`, because writing a character count into a field called `inputTokens` puts a lie in a row someone reads later.
|
|
128
|
+
|
|
129
|
+
`_voltro_ai_usage` gains a `unit` column, and `costSource` gains `'unpriced'`: a non-token unit with no supplied price is written with the real count and a zero cost rather than a fabricated one, since a per-1M-token rate applied to a character count produces a number indistinguishable from a real one. Sum the count columns grouped by `unit`, never across it.
|
|
130
|
+
|
|
131
|
+
BREAKING for a caller that had narrowed `CostBreakdown['costSource']` to two values or reads `estimateCostUsd`'s return positionally. No call site changes SHAPE — the token path takes the same arguments and returns the same numbers — but a narrowing is user-authored code, so this carries a `manual` codemod rather than `none`: nothing here can be transformed (whether the third member belongs in your union is your decision), and the reader who needs it is exactly the one a "touches no user-authored code" line would have sent away. It also names the query that does not fail but answers wrongly: summing `_voltro_ai_usage` counts ACROSS units now adds characters to tokens.
|
|
132
|
+
|
|
133
|
+
**`voltro update` carries you across this** — codemod `0.59.0/02_costsource_gained_unpriced`. If you pin versions by hand and never run it, print the notes without changing anything: `voltro update --codemods-only --from <your current version> --dry-run` (this one ships in 0.59.0).
|
|
134
|
+
|
|
135
|
+
### Added
|
|
136
|
+
|
|
137
|
+
- **@voltro/ai** — **`GatewayModelInfo.raw`** — the `/v1/models` entry exactly as it came over the wire, beside the normalised fields.
|
|
138
|
+
|
|
139
|
+
Normalising is the right default: the union of `tags`, `supported_parameters` and `modalities` is framework knowledge, and every app on the gateway was writing those forty lines differently. But normalising is lossy by construction, and the fields it drops are not decoration — a picker built on the vendor's own capability tags, a pricing display that shows a structure we have not normalised, `zdr` and `no_training` kept apart instead of folded into `dataPolicy`.
|
|
140
|
+
|
|
141
|
+
Without `raw`, such an app fetches the endpoint a second time and maintains a second parser of the same response. One field rather than three more normalised ones, because the alternative asks the same question again at every field the gateway adds.
|
|
142
|
+
|
|
143
|
+
Absent on the injected-provider path, for the same reason `capabilities` is: that seam never saw a response, and an empty object would turn "no wire data" into the claim that the wire carried nothing.
|
|
144
|
+
|
|
145
|
+
### Fixed
|
|
146
|
+
|
|
147
|
+
- **@voltro/cli** — **`@sentry/cli` 3.x loads.** The loader read one export spelling — `module.exports = SentryCli`, which is 2.x. 3.x ships `exports.SentryCli = SentryCli`, so `mod.default` is a namespace object rather than the class, and a build with a source-map upload configured stopped with *could not be loaded*.
|
|
148
|
+
|
|
149
|
+
The framework cannot pin its way out of this: `@sentry/cli` is FSL-1.1-MIT, so it is not ours to depend on and the app installs it — which means `pnpm add -D @sentry/cli`, the command we ourselves print, resolves the latest. Reading one spelling turned our own documented instruction into a failing build.
|
|
150
|
+
|
|
151
|
+
All four positions are accepted now (both spellings, each of which `import()` presents twice), and the two failures are told apart: a package that resolved but had no readable constructor no longer says *install it*. That reading is what cost a pipeline — locally `require()` resolves and the binary is there, so nothing about the install looks wrong.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
42
155
|
## [0.58.0] — 2026-08-30
|
|
43
156
|
|
|
44
157
|
### ⚠ BREAKING
|
package/dist/index.d.ts
CHANGED
|
@@ -373,7 +373,7 @@ export declare interface AiUsageRecordInput {
|
|
|
373
373
|
readonly model: string;
|
|
374
374
|
readonly operation: string;
|
|
375
375
|
readonly agent?: string | null;
|
|
376
|
-
readonly usage: AiUsage;
|
|
376
|
+
readonly usage: AiUsage | MeteredUsage;
|
|
377
377
|
/** Override the price table (negotiated/volume pricing or unknown model). */
|
|
378
378
|
readonly price?: ModelPrice;
|
|
379
379
|
/** The ACTUAL per-call cost reported by the provider/gateway (USD). When set,
|
|
@@ -573,6 +573,9 @@ export declare const buildCopilotSystemPrompt: (schema: CopilotSchema) => string
|
|
|
573
573
|
* `execute` tools ignore it. */
|
|
574
574
|
export declare const buildSdkTools: (tools: Record<string, AnyTool>, ctx?: ToolContext) => ToolSet;
|
|
575
575
|
|
|
576
|
+
/** The names `generateSpeech` resolves without a registration. */
|
|
577
|
+
export declare const BUILTIN_SPEECH_PROVIDERS: ReadonlyArray<string>;
|
|
578
|
+
|
|
576
579
|
/**
|
|
577
580
|
* Cancel an in-flight stream by id. Aborts its controller (the SDK call stops +
|
|
578
581
|
* the stream emits a terminal `cancelled` error). Returns `true` if a live
|
|
@@ -734,7 +737,9 @@ export declare interface CostBreakdown {
|
|
|
734
737
|
* the ACTUAL per-call cost (authoritative); `estimated` = derived from the
|
|
735
738
|
* static price table (or zero for an unknown model). Analytics can flag
|
|
736
739
|
* estimated rows + a model that priced at zero. */
|
|
737
|
-
readonly costSource: 'gateway' | 'estimated';
|
|
740
|
+
readonly costSource: 'gateway' | 'estimated' | 'unpriced';
|
|
741
|
+
/** What `inputTokens`/`outputTokens` are counts OF. */
|
|
742
|
+
readonly unit: UsageUnit;
|
|
738
743
|
}
|
|
739
744
|
|
|
740
745
|
/** Open a new thread (the store generates the id). */
|
|
@@ -1018,7 +1023,7 @@ export declare const embedMany: (texts: ReadonlyArray<string>, config?: Embeddin
|
|
|
1018
1023
|
* unknown model (or the `mock` provider) resolves to zero cost rather
|
|
1019
1024
|
* than throwing, so cost accounting never breaks a call.
|
|
1020
1025
|
*/
|
|
1021
|
-
export declare const estimateCostUsd: (usage: AiUsage, opts: {
|
|
1026
|
+
export declare const estimateCostUsd: (usage: AiUsage | MeteredUsage, opts: {
|
|
1022
1027
|
readonly model: string;
|
|
1023
1028
|
readonly price?: ModelPrice;
|
|
1024
1029
|
readonly actualCostUsd?: number;
|
|
@@ -1219,6 +1224,26 @@ export declare interface GatewayModelInfo {
|
|
|
1219
1224
|
* through is a decision nobody makes.
|
|
1220
1225
|
*/
|
|
1221
1226
|
readonly dataPolicy?: ModelDataPolicy;
|
|
1227
|
+
/**
|
|
1228
|
+
* The entry exactly as it came over the wire.
|
|
1229
|
+
*
|
|
1230
|
+
* The normalised fields above are the shared answer to the questions every
|
|
1231
|
+
* app on the gateway asks. `raw` is for the ones only one app asks — a chips
|
|
1232
|
+
* row built from `tags`, a pricing display that shows the vendor's own
|
|
1233
|
+
* structure, `zdr` and `no_training` kept apart rather than folded into
|
|
1234
|
+
* `dataPolicy`.
|
|
1235
|
+
*
|
|
1236
|
+
* Without it, an app with its own display fetches `/v1/models` a SECOND time
|
|
1237
|
+
* and maintains a SECOND parser of the same response — and then has to decide
|
|
1238
|
+
* again, at every new field the gateway adds, whether to wait for us. That is
|
|
1239
|
+
* the decision this field removes, which is why it is one field and not three
|
|
1240
|
+
* more normalised ones.
|
|
1241
|
+
*
|
|
1242
|
+
* Absent on the injected-provider path for the same reason `capabilities` is:
|
|
1243
|
+
* that seam never saw a response, and inventing an empty object would turn
|
|
1244
|
+
* "no wire data" into the claim "the wire carried nothing".
|
|
1245
|
+
*/
|
|
1246
|
+
readonly raw?: RawGatewayModel;
|
|
1222
1247
|
}
|
|
1223
1248
|
|
|
1224
1249
|
/**
|
|
@@ -1410,8 +1435,21 @@ export declare interface GenerateSpeechOptions {
|
|
|
1410
1435
|
|
|
1411
1436
|
export declare interface GenerateSpeechResult {
|
|
1412
1437
|
readonly audio: GeneratedAudio;
|
|
1413
|
-
/**
|
|
1414
|
-
|
|
1438
|
+
/**
|
|
1439
|
+
* What the call consumed.
|
|
1440
|
+
*
|
|
1441
|
+
* The speech model SPEC reports none, so a gateway-routed call still leaves
|
|
1442
|
+
* this undefined. A REGISTERED provider is the other case, and it is not a
|
|
1443
|
+
* property of the model any more: an app's own adapter knows the billed
|
|
1444
|
+
* quantity, because its vendor invoices on it — characters for ElevenLabs,
|
|
1445
|
+
* Cartesia and OpenAI, seconds for others.
|
|
1446
|
+
*
|
|
1447
|
+
* So it takes a `MeteredUsage` too, and that is what makes speech spend
|
|
1448
|
+
* reachable: without a shape that can say "4200 characters", the number the
|
|
1449
|
+
* adapter already has has nowhere to go, and `recordAiUsage` never sees the
|
|
1450
|
+
* call at all. Not un-priced — absent from the ledger.
|
|
1451
|
+
*/
|
|
1452
|
+
readonly usage?: MediaUsage | MeteredUsage;
|
|
1415
1453
|
readonly providerMetadata?: unknown;
|
|
1416
1454
|
}
|
|
1417
1455
|
|
|
@@ -1676,6 +1714,8 @@ export declare const isEvalDefinition: (value: unknown) => value is EvalDefiniti
|
|
|
1676
1714
|
*/
|
|
1677
1715
|
export declare const isExternalWrite: (tool: McpToolInfo, namespace: string, policy: McpToolPolicy) => boolean;
|
|
1678
1716
|
|
|
1717
|
+
export declare const isMeteredUsage: (u: AiUsage | MeteredUsage) => u is MeteredUsage;
|
|
1718
|
+
|
|
1679
1719
|
/** Is this a rendered prompt or a bare string? The discriminator `aiStep` uses
|
|
1680
1720
|
* so `prompt:` can accept either without a wrapper type at every call site. */
|
|
1681
1721
|
export declare const isRenderedPrompt: (value: unknown) => value is RenderedPrompt;
|
|
@@ -2047,6 +2087,23 @@ export declare type MessagePart = {
|
|
|
2047
2087
|
readonly data: string;
|
|
2048
2088
|
};
|
|
2049
2089
|
|
|
2090
|
+
/**
|
|
2091
|
+
* A tally in a unit that is not tokens.
|
|
2092
|
+
*
|
|
2093
|
+
* Deliberately NOT `AiUsage` with a unit tacked on. Writing a character count
|
|
2094
|
+
* into a field called `inputTokens` puts a lie in a ledger row that someone
|
|
2095
|
+
* reads later, and a dashboard summing that column across units is wrong in a
|
|
2096
|
+
* way nothing reports. The token shape is left exactly as it was — it is what
|
|
2097
|
+
* the AI SDK returns, and text callers change nothing.
|
|
2098
|
+
*/
|
|
2099
|
+
export declare interface MeteredUsage {
|
|
2100
|
+
readonly unit: Exclude<UsageUnit, 'tokens'>;
|
|
2101
|
+
/** What went in — characters of text, say. */
|
|
2102
|
+
readonly input?: number | undefined;
|
|
2103
|
+
/** What came out — seconds of audio, say. */
|
|
2104
|
+
readonly output?: number | undefined;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2050
2107
|
/** A scripted inline file the mock model emits on a turn (usually an image).
|
|
2051
2108
|
* `data` is base64 (a short ascii string works for assertions). */
|
|
2052
2109
|
export declare interface MockFile {
|
|
@@ -2176,6 +2233,13 @@ export declare interface ModelPrice {
|
|
|
2176
2233
|
/** The current merged price map (defaults + installed overrides). Overrides win. */
|
|
2177
2234
|
export declare const modelPricing: () => Record<string, ModelPrice>;
|
|
2178
2235
|
|
|
2236
|
+
/** Both shapes, flattened to the two counts + the unit they are counts of. */
|
|
2237
|
+
export declare const normaliseUsage: (usage: AiUsage | MeteredUsage) => {
|
|
2238
|
+
readonly unit: UsageUnit;
|
|
2239
|
+
readonly input: number;
|
|
2240
|
+
readonly output: number;
|
|
2241
|
+
};
|
|
2242
|
+
|
|
2179
2243
|
/**
|
|
2180
2244
|
* Wrap a `streamText` `Stream<AgentEvent>` with the same `voltro.ai.call` span +
|
|
2181
2245
|
* metrics. The stream never fails — usage arrives on the terminal `done` event,
|
|
@@ -2619,11 +2683,31 @@ export declare const registeredSpeechProviders: () => ReadonlyArray<string>;
|
|
|
2619
2683
|
* Register a speech provider under a name.
|
|
2620
2684
|
*
|
|
2621
2685
|
* The name then works in `generateSpeech({ provider: { name, model, voice } })`.
|
|
2622
|
-
*
|
|
2623
|
-
* `anthropic`): silently shadowing one would make a config mean something
|
|
2624
|
-
* different depending on import order.
|
|
2686
|
+
* Taking over a built-in name needs `{ replaceBuiltin: true }` — see the option.
|
|
2625
2687
|
*/
|
|
2626
|
-
export declare const registerSpeechProvider: (name: string, provider: SpeechProvider) => void;
|
|
2688
|
+
export declare const registerSpeechProvider: (name: string, provider: SpeechProvider, options?: RegisterSpeechProviderOptions) => void;
|
|
2689
|
+
|
|
2690
|
+
export declare interface RegisterSpeechProviderOptions {
|
|
2691
|
+
/**
|
|
2692
|
+
* Take over a BUILT-IN name (`openai`, `gateway`, `anthropic`, `mock`).
|
|
2693
|
+
*
|
|
2694
|
+
* Refusing these outright was wrong, and the case that showed it is one the
|
|
2695
|
+
* refusal could not see: a provider name is not always an alias the app
|
|
2696
|
+
* chooses. `openai` is the value the GATEWAY puts in its model ids, so it is
|
|
2697
|
+
* what an app's stored catalog rows carry and what its picker submits. Told
|
|
2698
|
+
* to pick another name, such an app cannot — it would have to rewrite
|
|
2699
|
+
* persisted rows and live sessions — so it keeps its own dispatch instead,
|
|
2700
|
+
* and the primitive stays unused for every provider, not just the colliding
|
|
2701
|
+
* one.
|
|
2702
|
+
*
|
|
2703
|
+
* What the refusal was actually protecting is worth keeping: a SILENT shadow,
|
|
2704
|
+
* where the same config means different things depending on which module
|
|
2705
|
+
* loaded first. This flag keeps that property by putting the intent in the
|
|
2706
|
+
* call. An override is then a decision someone wrote down, not an accident of
|
|
2707
|
+
* import order.
|
|
2708
|
+
*/
|
|
2709
|
+
readonly replaceBuiltin?: boolean;
|
|
2710
|
+
}
|
|
2627
2711
|
|
|
2628
2712
|
/**
|
|
2629
2713
|
* Register (or reuse) the `AbortController` for `streamId`. `streamText` calls
|
|
@@ -2648,6 +2732,9 @@ export declare interface RenderedPrompt extends PromptStamp {
|
|
|
2648
2732
|
readonly definition: PromptDefinition;
|
|
2649
2733
|
}
|
|
2650
2734
|
|
|
2735
|
+
/** Whether `name` is a registration standing in front of a built-in. */
|
|
2736
|
+
export declare const replacesBuiltinSpeechProvider: (name: string) => boolean;
|
|
2737
|
+
|
|
2651
2738
|
/**
|
|
2652
2739
|
* Gate an AI call against a per-tenant USD budget with an ATOMIC reservation —
|
|
2653
2740
|
* the cap precedent is billing's `requireEntitlement` / storage's
|
|
@@ -3525,6 +3612,16 @@ export declare type TranscriptionTimestamps = 'none' | 'segment' | 'word';
|
|
|
3525
3612
|
*/
|
|
3526
3613
|
export declare const unregisterStream: (streamId: string) => void;
|
|
3527
3614
|
|
|
3615
|
+
/**
|
|
3616
|
+
* What a call was billed in.
|
|
3617
|
+
*
|
|
3618
|
+
* Text is tokens. Speech is not: every speech vendor meters CHARACTERS of input
|
|
3619
|
+
* or SECONDS of audio, and none of them meters tokens. A ledger that only knows
|
|
3620
|
+
* tokens therefore cannot hold a speech call at all — which is why speech spend
|
|
3621
|
+
* was invisible rather than merely un-priced.
|
|
3622
|
+
*/
|
|
3623
|
+
export declare type UsageUnit = 'tokens' | 'characters' | 'seconds';
|
|
3624
|
+
|
|
3528
3625
|
/**
|
|
3529
3626
|
* Validate a proposal against the schema and (on success) build a read-only
|
|
3530
3627
|
* `QueryDescriptor`. EVERY table/column the proposal names must exist in the
|