@voltro/ai 0.74.0 → 0.75.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 +319 -0
- package/dist/index.d.ts +238 -1
- package/dist/index.js +625 -537
- package/dist/{inferenceQueue-CXEVU6mI.js → inferenceQueue-CdWTzRp7.js} +310 -229
- package/dist/workflow.d.ts +26 -6
- package/dist/workflow.js +80 -79
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { LanguageModelV4 } from '@ai-sdk/provider';
|
|
|
16
16
|
import { LanguageModelV4Source } from '@ai-sdk/provider';
|
|
17
17
|
import { ModelMessage } from 'ai';
|
|
18
18
|
import { PluginMediaPersistCapability } from '@voltro/protocol';
|
|
19
|
+
import { PluginMediaPersistMetadata } from '@voltro/protocol';
|
|
19
20
|
import { QueryDescriptor } from '@voltro/database';
|
|
20
21
|
import { QueryProcedureDescriptor } from '@voltro/protocol';
|
|
21
22
|
import { Runtime } from 'effect';
|
|
@@ -28,6 +29,8 @@ import { Stream } from 'effect';
|
|
|
28
29
|
import { Table } from '@voltro/database';
|
|
29
30
|
import { ToolSet } from 'ai';
|
|
30
31
|
import { TranscriptionModelV4 } from '@ai-sdk/provider';
|
|
32
|
+
import { VoidIfEmpty } from 'effect/Types';
|
|
33
|
+
import { YieldableError } from 'effect/Cause';
|
|
31
34
|
|
|
32
35
|
/** Count of in-flight streams (diagnostics / leak detection). */
|
|
33
36
|
export declare const activeStreamCount: () => number;
|
|
@@ -405,6 +408,29 @@ declare const AiError_base: Schema.TaggedErrorClass<AiError, "AiError", {
|
|
|
405
408
|
}>>;
|
|
406
409
|
}>;
|
|
407
410
|
|
|
411
|
+
/**
|
|
412
|
+
* The one way a CAUGHT provider failure becomes an `AiError`.
|
|
413
|
+
*
|
|
414
|
+
* Every call site used to write `new AiError({ message: String(e.message) })`,
|
|
415
|
+
* which kept the top message and dropped `cause`. For a network failure that
|
|
416
|
+
* leaves `fetch failed` with nothing under it — no `ECONNREFUSED`, no TLS code —
|
|
417
|
+
* so the one fact that says what to fix was gone. Two sites were worse and
|
|
418
|
+
* dropped the provider's message too (`structured provider call failed`).
|
|
419
|
+
*
|
|
420
|
+
* Two things carry the cause now, deliberately split by audience:
|
|
421
|
+
* - the CODES of the cause chain (`ECONNREFUSED`, `CERT_HAS_EXPIRED`, …) join
|
|
422
|
+
* the message. Codes only: a nested message can carry an internal host or
|
|
423
|
+
* address, and `message` is a schema field — it is encoded, and it reaches a
|
|
424
|
+
* browser whenever a procedure declares `AiError` in its error union;
|
|
425
|
+
* - the raw failure stays on the instance as `cause`, NOT enumerable, so a
|
|
426
|
+
* server log or `error.cause` has the whole chain and the encoder never
|
|
427
|
+
* sees it.
|
|
428
|
+
*/
|
|
429
|
+
export declare const aiGenerationError: (cause: unknown, options?: {
|
|
430
|
+
readonly prefix?: string;
|
|
431
|
+
readonly evidence?: AiCallEvidence;
|
|
432
|
+
}) => AiError;
|
|
433
|
+
|
|
408
434
|
/**
|
|
409
435
|
* One offloaded model call.
|
|
410
436
|
*
|
|
@@ -469,6 +495,18 @@ export declare interface AiUsage {
|
|
|
469
495
|
readonly totalTokens?: number | undefined;
|
|
470
496
|
}
|
|
471
497
|
|
|
498
|
+
/** The usage ledger could not be written — the call it describes is not booked. */
|
|
499
|
+
export declare class AiUsageLedgerError extends AiUsageLedgerError_base<{
|
|
500
|
+
readonly receiptKey: string;
|
|
501
|
+
readonly cause: unknown;
|
|
502
|
+
}> {
|
|
503
|
+
get message(): string;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
declare const AiUsageLedgerError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
|
|
507
|
+
readonly _tag: "AiUsageLedgerError";
|
|
508
|
+
} & Readonly<A>;
|
|
509
|
+
|
|
472
510
|
/**
|
|
473
511
|
* Price a usage tally and write a ledger row. Returns the computed
|
|
474
512
|
* `CostBreakdown` so the caller can surface the cost without a re-query.
|
|
@@ -476,6 +514,40 @@ export declare interface AiUsage {
|
|
|
476
514
|
/** What `recordAiUsage` did with the receipt: wrote it, or found it already written. */
|
|
477
515
|
export declare type AiUsageReceipt = 'recorded' | 'replayed';
|
|
478
516
|
|
|
517
|
+
/**
|
|
518
|
+
* A receipt was recorded again with DIFFERENT values than the row already
|
|
519
|
+
* written under it.
|
|
520
|
+
*
|
|
521
|
+
* A replay is the same paid call recorded twice, so its values are the same by
|
|
522
|
+
* definition. Different values under one receipt mean one of them is wrong — a
|
|
523
|
+
* second call billed under the first one's identity, or a caller whose receipt
|
|
524
|
+
* key is not the identity it thinks it is. Before this existed the second
|
|
525
|
+
* recording answered `replayed` and handed back the values it was GIVEN, so the
|
|
526
|
+
* caller saw a charge that was never booked and no sign of the disagreement.
|
|
527
|
+
*/
|
|
528
|
+
export declare class AiUsageReceiptConflict extends AiUsageReceiptConflict_base<{
|
|
529
|
+
readonly receiptKey: string;
|
|
530
|
+
readonly recorded: AiUsageReceiptFacts;
|
|
531
|
+
readonly attempted: AiUsageReceiptFacts;
|
|
532
|
+
}> {
|
|
533
|
+
get message(): string;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
declare const AiUsageReceiptConflict_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
|
|
537
|
+
readonly _tag: "AiUsageReceiptConflict";
|
|
538
|
+
} & Readonly<A>;
|
|
539
|
+
|
|
540
|
+
/** The facts of one usage row that a replay must agree with. */
|
|
541
|
+
export declare interface AiUsageReceiptFacts {
|
|
542
|
+
readonly tenantId: string | null;
|
|
543
|
+
readonly provider: string;
|
|
544
|
+
readonly model: string;
|
|
545
|
+
readonly operation: string;
|
|
546
|
+
readonly inputTokens: number;
|
|
547
|
+
readonly outputTokens: number;
|
|
548
|
+
readonly costMicroUsd: number;
|
|
549
|
+
}
|
|
550
|
+
|
|
479
551
|
export declare interface AiUsageRecordInput {
|
|
480
552
|
readonly tenantId?: string | null;
|
|
481
553
|
readonly provider: ProviderName | string;
|
|
@@ -690,6 +762,9 @@ export declare const buildCopilotSystemPrompt: (schema: CopilotSchema) => string
|
|
|
690
762
|
* `execute` tools ignore it. */
|
|
691
763
|
export declare const buildSdkTools: (tools: Record<string, AnyTool>, ctx?: ToolContext) => ToolSet;
|
|
692
764
|
|
|
765
|
+
/** The names the built-in generators resolve without a registration. */
|
|
766
|
+
export declare const BUILTIN_MEDIA_PROVIDERS: ReadonlyArray<string>;
|
|
767
|
+
|
|
693
768
|
/** The names `generateSpeech` resolves without a registration. */
|
|
694
769
|
export declare const BUILTIN_SPEECH_PROVIDERS: ReadonlyArray<string>;
|
|
695
770
|
|
|
@@ -725,6 +800,9 @@ export declare const capabilitiesOf: (raw: RawGatewayModel) => ModelCapabilities
|
|
|
725
800
|
/** Clear the global middleware stack. */
|
|
726
801
|
export declare const clearAiMiddleware: () => void;
|
|
727
802
|
|
|
803
|
+
/** Test seam: forget every registration. */
|
|
804
|
+
export declare const clearMediaTaskProviders: () => void;
|
|
805
|
+
|
|
728
806
|
/** Test seam: forget every registration. */
|
|
729
807
|
export declare const clearSpeechProviders: () => void;
|
|
730
808
|
|
|
@@ -1865,6 +1943,10 @@ export declare interface InferenceDispatchOptions {
|
|
|
1865
1943
|
readonly maxAttempts?: number;
|
|
1866
1944
|
readonly baseDelayMs?: number;
|
|
1867
1945
|
readonly maxDelayMs?: number;
|
|
1946
|
+
/** Between polls of a task-provider media job. Default {@link DEFAULT_MEDIA_TASK_POLL_MS}. */
|
|
1947
|
+
readonly taskPollMs?: number;
|
|
1948
|
+
/** A task still pending this long after submission is failed. Default {@link DEFAULT_MEDIA_TASK_TIMEOUT_MS}. */
|
|
1949
|
+
readonly taskTimeoutMs?: number;
|
|
1868
1950
|
}
|
|
1869
1951
|
|
|
1870
1952
|
export declare interface InferenceDispatchResult {
|
|
@@ -1876,6 +1958,9 @@ export declare interface InferenceDispatchResult {
|
|
|
1876
1958
|
readonly failed: number;
|
|
1877
1959
|
readonly retried: number;
|
|
1878
1960
|
readonly reclaimed: number;
|
|
1961
|
+
/** Task-provider media jobs asked once and found still pending — released
|
|
1962
|
+
* back to the queue for a later claim, without a lease held across the job. */
|
|
1963
|
+
readonly polled: number;
|
|
1879
1964
|
readonly failures: ReadonlyArray<{
|
|
1880
1965
|
readonly id: string;
|
|
1881
1966
|
readonly detail: string;
|
|
@@ -2245,6 +2330,105 @@ export declare interface McpTransport {
|
|
|
2245
2330
|
readonly close: () => Effect.Effect<void>;
|
|
2246
2331
|
}
|
|
2247
2332
|
|
|
2333
|
+
/** One artifact a finished task produced — a URL the dispatcher re-hosts, or
|
|
2334
|
+
* bytes (raw or base64) it stores. Same shape the built-in generators hand
|
|
2335
|
+
* over, so both paths persist identically. */
|
|
2336
|
+
export declare interface MediaTaskArtifact {
|
|
2337
|
+
readonly url?: string;
|
|
2338
|
+
readonly data?: Uint8Array | string;
|
|
2339
|
+
readonly mediaType: string;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
/**
|
|
2343
|
+
* The provider's handle for one submitted task.
|
|
2344
|
+
*
|
|
2345
|
+
* Stored on the queue row VERBATIM as JSON and handed back to `poll`, so it
|
|
2346
|
+
* may carry whatever the provider needs to find the task again — a request
|
|
2347
|
+
* id, the endpoint it was submitted to, a region. It must survive
|
|
2348
|
+
* `JSON.stringify`: a handle that does not is refused before it is stored,
|
|
2349
|
+
* because a handle that came back different would poll a task that does not
|
|
2350
|
+
* exist.
|
|
2351
|
+
*/
|
|
2352
|
+
export declare type MediaTaskHandle = Readonly<Record<string, unknown>>;
|
|
2353
|
+
|
|
2354
|
+
export declare interface MediaTaskProvider {
|
|
2355
|
+
/** Submit the job; return the provider's handle as soon as it has one. */
|
|
2356
|
+
readonly submit: (request: MediaTaskSubmitRequest) => Effect.Effect<MediaTaskHandle, AiError>;
|
|
2357
|
+
/** Ask the provider about one handle. Called once per dispatcher claim. */
|
|
2358
|
+
readonly poll: (handle: MediaTaskHandle, request: MediaTaskSubmitRequest) => Effect.Effect<MediaTaskStatus, AiError>;
|
|
2359
|
+
/**
|
|
2360
|
+
* The provider de-duplicates on `idempotencyKey`: a second `submit` with the
|
|
2361
|
+
* same key returns the SAME task's handle rather than starting a second job.
|
|
2362
|
+
*
|
|
2363
|
+
* This closes the one window the handle cannot: a worker lost between the
|
|
2364
|
+
* provider accepting the job and the handle reaching the row. Without this
|
|
2365
|
+
* flag such a row is resumed with `AiOffloadError.outcome === 'unknown'` —
|
|
2366
|
+
* the job may have run and been charged for, and nothing can find it. With
|
|
2367
|
+
* it, the reclaiming worker submits again under the same key, receives the
|
|
2368
|
+
* same handle, and polls it.
|
|
2369
|
+
*
|
|
2370
|
+
* Say it only when the provider guarantees it. A `true` over a provider that
|
|
2371
|
+
* ignores the key turns the lost-worker case into a second paid job — the
|
|
2372
|
+
* exact outcome the `unknown` report exists to avoid.
|
|
2373
|
+
*/
|
|
2374
|
+
readonly idempotentSubmit?: boolean;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
/** The task provider registered under `name`, or `undefined`. */
|
|
2378
|
+
export declare const mediaTaskProvider: (name: string) => MediaTaskProvider | undefined;
|
|
2379
|
+
|
|
2380
|
+
export declare type MediaTaskStatus = {
|
|
2381
|
+
readonly status: 'pending';
|
|
2382
|
+
/** How long to wait before asking again, when the provider says. Bounded
|
|
2383
|
+
* below by the dispatcher's own poll interval. */
|
|
2384
|
+
readonly retryAfterMs?: number;
|
|
2385
|
+
} | {
|
|
2386
|
+
readonly status: 'succeeded';
|
|
2387
|
+
readonly artifacts: ReadonlyArray<MediaTaskArtifact>;
|
|
2388
|
+
/** What the task consumed, in the provider's unit — seconds of video,
|
|
2389
|
+
* characters of text, or tokens. Recorded in the usage ledger under
|
|
2390
|
+
* the job's row and attempt. */
|
|
2391
|
+
readonly usage?: MediaUsage | MeteredUsage;
|
|
2392
|
+
readonly providerMetadata?: unknown;
|
|
2393
|
+
} | {
|
|
2394
|
+
readonly status: 'failed';
|
|
2395
|
+
/** The provider's own message. It is what the parked run is told. */
|
|
2396
|
+
readonly message: string;
|
|
2397
|
+
/**
|
|
2398
|
+
* Whether a fresh submission could succeed. Default `false`: a task the
|
|
2399
|
+
* provider ran and failed is a paid job, and re-submitting it is a second
|
|
2400
|
+
* charge, not a retry. Say `true` for a transient failure the provider
|
|
2401
|
+
* itself names as such (capacity, an upstream outage) — then the job is
|
|
2402
|
+
* re-submitted under the next attempt, with the dispatcher's backoff.
|
|
2403
|
+
*/
|
|
2404
|
+
readonly retryable?: boolean;
|
|
2405
|
+
};
|
|
2406
|
+
|
|
2407
|
+
/**
|
|
2408
|
+
* What the dispatcher hands `submit`.
|
|
2409
|
+
*
|
|
2410
|
+
* `idempotencyKey` is derived from the queue row and the attempt — the same
|
|
2411
|
+
* two things the artifacts are stored under — so it is stable across a lost
|
|
2412
|
+
* worker and different for a deliberate re-submission. Forward it to the
|
|
2413
|
+
* provider when the provider takes one: see `MediaTaskProvider.idempotentSubmit`
|
|
2414
|
+
* for what that buys.
|
|
2415
|
+
*/
|
|
2416
|
+
export declare interface MediaTaskSubmitRequest {
|
|
2417
|
+
readonly modality: 'image' | 'video' | 'speech';
|
|
2418
|
+
readonly prompt: string;
|
|
2419
|
+
readonly model?: string;
|
|
2420
|
+
/** The job's `params`, as declared on `mediaStep`. */
|
|
2421
|
+
readonly params: Readonly<Record<string, unknown>>;
|
|
2422
|
+
/** URL inputs, as declared on `mediaStep`. Never bytes. */
|
|
2423
|
+
readonly inputs: Readonly<Record<string, unknown>>;
|
|
2424
|
+
readonly providerOptions?: AiProviderOptions;
|
|
2425
|
+
readonly tenantId: string | null;
|
|
2426
|
+
/** `<queue row id>#<attempt>`. */
|
|
2427
|
+
readonly idempotencyKey: string;
|
|
2428
|
+
/** Aborted on Effect interruption; pass to provider I/O. */
|
|
2429
|
+
readonly abortSignal?: AbortSignal;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2248
2432
|
/** Token tally, mirroring `GenerateTextResult['usage']`. Only `generateImage`
|
|
2249
2433
|
* reports one (the speech + video model specs carry no usage), which is why
|
|
2250
2434
|
* it's optional on every media result. */
|
|
@@ -2739,6 +2923,9 @@ export declare interface QueuedInference {
|
|
|
2739
2923
|
readonly transcription: Record<string, unknown> | null;
|
|
2740
2924
|
/** The media job for a `media` call. */
|
|
2741
2925
|
readonly media: QueuedMediaJob | null;
|
|
2926
|
+
/** The stored provider handle of a task-provider media job, or `null`. */
|
|
2927
|
+
readonly taskHandle: Record<string, unknown> | null;
|
|
2928
|
+
readonly taskSubmittedAt: Date | null;
|
|
2742
2929
|
readonly attempts: number;
|
|
2743
2930
|
readonly fence: number;
|
|
2744
2931
|
}
|
|
@@ -2748,6 +2935,8 @@ export declare interface QueuedMediaJob {
|
|
|
2748
2935
|
readonly modality: 'image' | 'video' | 'speech';
|
|
2749
2936
|
readonly params: Record<string, unknown>;
|
|
2750
2937
|
readonly inputs: Record<string, unknown>;
|
|
2938
|
+
/** Owner, visibility and tags for the stored artifacts — see `PluginMediaPersistMetadata`. */
|
|
2939
|
+
readonly persist?: PluginMediaPersistMetadata;
|
|
2751
2940
|
}
|
|
2752
2941
|
|
|
2753
2942
|
/** What a media job resumes its run with: hosted artifacts, never bytes. */
|
|
@@ -2818,7 +3007,20 @@ export declare interface ReadRecorder<S extends QueryableStore> {
|
|
|
2818
3007
|
|
|
2819
3008
|
export declare const recordAiUsage: (store: DataStore, record: AiUsageRecordInput) => Effect.Effect<CostBreakdown & {
|
|
2820
3009
|
readonly receipt: AiUsageReceipt;
|
|
2821
|
-
}>;
|
|
3010
|
+
}, AiUsageReceiptConflict | AiUsageLedgerError>;
|
|
3011
|
+
|
|
3012
|
+
/**
|
|
3013
|
+
* `recordAiUsage` for a call that has ALREADY been paid for, where failing the
|
|
3014
|
+
* caller would lose a finished answer over a ledger row. Never fails — and never
|
|
3015
|
+
* silent: a ledger failure or a receipt conflict is logged at error level with
|
|
3016
|
+
* everything needed to reconcile it by hand.
|
|
3017
|
+
*
|
|
3018
|
+
* The framework's three callers each swallowed the failure outright
|
|
3019
|
+
* (`.catch(() => {})`, `catchAllCause(() => Effect.void)` twice). "Do not fail
|
|
3020
|
+
* the call" was right; "leave no trace" was not — an unbooked charge that
|
|
3021
|
+
* nothing reports is found only when the invoice disagrees with the ledger.
|
|
3022
|
+
*/
|
|
3023
|
+
export declare const recordAiUsageBestEffort: (store: DataStore, record: AiUsageRecordInput) => Effect.Effect<void>;
|
|
2822
3024
|
|
|
2823
3025
|
/**
|
|
2824
3026
|
* Register a prompt version, idempotently, and return it.
|
|
@@ -2876,6 +3078,18 @@ export declare const redisResumableStreamStore: (redis: ResumableRedis, opts?: {
|
|
|
2876
3078
|
*/
|
|
2877
3079
|
export declare const refreshMcpToolset: (server: McpServer, policy: McpToolPolicy) => Effect.Effect<McpToolset, McpError>;
|
|
2878
3080
|
|
|
3081
|
+
/**
|
|
3082
|
+
* The provider config `mediaStep` accepts for a registered task provider —
|
|
3083
|
+
* named, not enumerated, exactly like `RegisteredSpeechConfig`.
|
|
3084
|
+
*/
|
|
3085
|
+
export declare interface RegisteredMediaTaskConfig {
|
|
3086
|
+
readonly name: string;
|
|
3087
|
+
readonly model?: string;
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
/** Every registered name. */
|
|
3091
|
+
export declare const registeredMediaTaskProviders: () => ReadonlyArray<string>;
|
|
3092
|
+
|
|
2879
3093
|
/**
|
|
2880
3094
|
* Synthesize speech audio from text. Returns the audio bytes + media type and
|
|
2881
3095
|
* provider metadata; provider failures surface as `AiError`.
|
|
@@ -2898,6 +3112,29 @@ export declare interface RegisteredSpeechConfig {
|
|
|
2898
3112
|
/** Every registered name — what a picker asks for. */
|
|
2899
3113
|
export declare const registeredSpeechProviders: () => ReadonlyArray<string>;
|
|
2900
3114
|
|
|
3115
|
+
/**
|
|
3116
|
+
* Register an external-task media provider under a name.
|
|
3117
|
+
*
|
|
3118
|
+
* The name then works in `mediaStep({ provider: { name, model } })`: the
|
|
3119
|
+
* dispatcher submits through the provider, keeps the handle on the queue row,
|
|
3120
|
+
* polls it once per claim, and resumes the run with the hosted artifacts.
|
|
3121
|
+
* Taking over a built-in name needs `{ replaceBuiltin: true }`.
|
|
3122
|
+
*/
|
|
3123
|
+
export declare const registerMediaTaskProvider: (name: string, provider: MediaTaskProvider, options?: RegisterMediaTaskProviderOptions) => void;
|
|
3124
|
+
|
|
3125
|
+
export declare interface RegisterMediaTaskProviderOptions {
|
|
3126
|
+
/**
|
|
3127
|
+
* Take over a BUILT-IN name (`openai`, `gateway`, `anthropic`, `mock`).
|
|
3128
|
+
*
|
|
3129
|
+
* A registration under a built-in name routes every `mediaStep` that names
|
|
3130
|
+
* it through the task contract instead of the generator. Refused unless the
|
|
3131
|
+
* intent is in the call, for the reason the speech registry gives: a silent
|
|
3132
|
+
* shadow makes the same config mean different things depending on which
|
|
3133
|
+
* module loaded first.
|
|
3134
|
+
*/
|
|
3135
|
+
readonly replaceBuiltin?: boolean;
|
|
3136
|
+
}
|
|
3137
|
+
|
|
2901
3138
|
/**
|
|
2902
3139
|
* Register a speech provider under a name.
|
|
2903
3140
|
*
|