@voltro/ai 0.33.0 → 0.35.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 +1968 -0
- package/dist/agent.js +1 -1
- package/dist/{agentDescriptor-CCeWeadZ.js → agentDescriptor-Bv-ikWax.js} +4 -2
- package/dist/index.d.ts +711 -5
- package/dist/index.js +696 -243
- package/dist/inferenceQueue-uj2kKyqp.js +568 -0
- package/dist/workflow.d.ts +155 -4
- package/dist/workflow.js +171 -96
- package/package.json +5 -5
- package/dist/inferenceQueue-B25RTJZ4.js +0 -447
package/dist/index.d.ts
CHANGED
|
@@ -353,6 +353,10 @@ export declare interface AiServiceImpl {
|
|
|
353
353
|
export declare const aiSpendUsd: (store: DataStore, opts?: {
|
|
354
354
|
readonly tenantId?: string | null;
|
|
355
355
|
readonly since?: Date;
|
|
356
|
+
/** Scope the sum to ONE prompt version. This is the "did revision 4 cost
|
|
357
|
+
* more than revision 3" query, and it is the reason the digest is on the
|
|
358
|
+
* ledger row rather than only on the step row. */
|
|
359
|
+
readonly promptDigest?: string;
|
|
356
360
|
}) => Effect.Effect<number>;
|
|
357
361
|
|
|
358
362
|
/** Token tally as returned by `generateText` / `generateObject` / `streamText`. */
|
|
@@ -376,6 +380,10 @@ export declare interface AiUsageRecordInput {
|
|
|
376
380
|
* models the static table prices at zero. Extract it from the AI-SDK result's
|
|
377
381
|
* `providerMetadata.gateway.cost` (see `gatewayCostUsd`). */
|
|
378
382
|
readonly actualCostUsd?: number;
|
|
383
|
+
/** Which prompt VERSION produced this call — `definePrompt(...).render(...)`
|
|
384
|
+
* returns one, and `aiStep` passes it through automatically. Stamped onto the
|
|
385
|
+
* ledger row so spend can be attributed to a prompt revision. */
|
|
386
|
+
readonly prompt?: PromptStamp;
|
|
379
387
|
/** Override the timestamp (tests; default `new Date()`). */
|
|
380
388
|
readonly calledAt?: Date;
|
|
381
389
|
}
|
|
@@ -406,6 +414,9 @@ export declare interface AnyTool {
|
|
|
406
414
|
readonly name: string;
|
|
407
415
|
readonly description: string;
|
|
408
416
|
readonly input: Schema.Schema.Any;
|
|
417
|
+
/** See `ToolDef.inputJsonSchema` — a foreign parameter schema shown to the
|
|
418
|
+
* model in place of a rendered `input`. */
|
|
419
|
+
readonly inputJsonSchema?: Record<string, unknown>;
|
|
409
420
|
readonly output?: Schema.Schema.Any;
|
|
410
421
|
readonly execute?: (input: never) => Effect.Effect<unknown, never, unknown>;
|
|
411
422
|
readonly handler?: ToolHandler<never, unknown>;
|
|
@@ -442,6 +453,57 @@ export declare const appendStreamingMessage: (store: DataStore, input: {
|
|
|
442
453
|
*/
|
|
443
454
|
export declare const applyMiddleware: (model: LanguageModelV4, middleware?: AiMiddleware | ReadonlyArray<AiMiddleware>) => LanguageModelV4;
|
|
444
455
|
|
|
456
|
+
/**
|
|
457
|
+
* The EXECUTION admission for one descriptor, as data.
|
|
458
|
+
*
|
|
459
|
+
* Same shape and same ORDER as `mcpToolDecision` in `mcpTools.ts`, on purpose:
|
|
460
|
+
* an app tool and an external MCP tool are admitted by two functions that agree
|
|
461
|
+
* about what a decision is, so one confirm-UI, one inventory type and one
|
|
462
|
+
* refusal message cover both. The asymmetry between them is only in step 1 —
|
|
463
|
+
* an app descriptor carries an `exposeAsTool` act, so an absent allowlist is
|
|
464
|
+
* permissive here and refused there.
|
|
465
|
+
*
|
|
466
|
+
* It exists because a SECOND caller appeared. `appTools` used to inline the
|
|
467
|
+
* three filters, which was fine while it was the only executor; the moment a
|
|
468
|
+
* transport (the inspect agent-tool surface, reached over MCP) needed the same
|
|
469
|
+
* verdict, an inlined chain would have become a second policy path — and a
|
|
470
|
+
* second policy path is how a ceiling gets bypassed. So the verdict is a
|
|
471
|
+
* function, `appTools` calls it, and the transport calls the same one.
|
|
472
|
+
*
|
|
473
|
+
* Order, asserted in the tests:
|
|
474
|
+
* 1. `passesPolicy` (deny beats allow) on the descriptor NAME,
|
|
475
|
+
* 2. exposability — an `exposeAsTool` object WITH a description,
|
|
476
|
+
* 3. the writes gate — a mutation/action needs `includeWrites`,
|
|
477
|
+
* 4. the CONFIRM gate — `confirm` must be backed by `requiresApproval:`.
|
|
478
|
+
*
|
|
479
|
+
* ── Why step 4 exists (`confirm` was reported, not enforced) ─────────────────
|
|
480
|
+
*
|
|
481
|
+
* `confirm` shipped as metadata: `synthesizeToolSpecs` reported it, this
|
|
482
|
+
* function admitted the tool anyway, and the file said out loud that enforcement
|
|
483
|
+
* was "the agent loop / UI"'s job. That is the shape this repo keeps paying for
|
|
484
|
+
* — a control that reads as enforcement in every inventory and enforces nothing
|
|
485
|
+
* in any caller that forgets to look.
|
|
486
|
+
*
|
|
487
|
+
* There is now somewhere for it to MEAN something. A descriptor that declares
|
|
488
|
+
* `requiresApproval:` parks the call as a durable approval in the shared serve
|
|
489
|
+
* pipeline — the same gate a human's call goes through — so the second human is
|
|
490
|
+
* the app's own approval queue rather than a prompt in a harness we do not
|
|
491
|
+
* control. A confirm tool WITH that policy is therefore admitted: executing it
|
|
492
|
+
* cannot skip the human, because the handler behind it will not run without one.
|
|
493
|
+
*
|
|
494
|
+
* A confirm tool WITHOUT it is refused. The alternative is to mount it and hope
|
|
495
|
+
* the caller asks, and the caller is a model.
|
|
496
|
+
*/
|
|
497
|
+
export declare type AppToolDecision = {
|
|
498
|
+
readonly admitted: true;
|
|
499
|
+
readonly spec: SynthesizedTool;
|
|
500
|
+
} | {
|
|
501
|
+
readonly admitted: false;
|
|
502
|
+
readonly reason: string;
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
export declare const appToolDecision: (descriptor: AppToolDescriptor, policy: AppToolPolicy) => AppToolDecision;
|
|
506
|
+
|
|
445
507
|
/** The descriptor fields tool synthesis reads. */
|
|
446
508
|
export declare interface AppToolDescriptor {
|
|
447
509
|
readonly kind: 'query' | 'mutation' | 'action' | string;
|
|
@@ -449,6 +511,15 @@ export declare interface AppToolDescriptor {
|
|
|
449
511
|
readonly input: Schema.Schema.Any;
|
|
450
512
|
readonly output?: Schema.Schema.Any;
|
|
451
513
|
readonly exposeAsTool?: ExposeAsToolLike;
|
|
514
|
+
/**
|
|
515
|
+
* Presence of the descriptor's `requiresApproval:` policy — read as a BOOLEAN
|
|
516
|
+
* here and nothing more, so `@voltro/ai` needs no `@voltro/protocol` shape for
|
|
517
|
+
* it (structural, like `exposeAsTool` above).
|
|
518
|
+
*
|
|
519
|
+
* It is what turns `confirm` from a report into an enforcement: see
|
|
520
|
+
* `appToolDecision`.
|
|
521
|
+
*/
|
|
522
|
+
readonly requiresApproval?: unknown;
|
|
452
523
|
}
|
|
453
524
|
|
|
454
525
|
/** A descriptor paired with its ctx-bound executor (the real handler). */
|
|
@@ -475,9 +546,14 @@ export declare interface AppToolPolicy {
|
|
|
475
546
|
* The EXECUTABLE toolset for the agent loop. Read tools are always included;
|
|
476
547
|
* WRITE tools only when `policy.includeWrites` is true (writes are opt-in —
|
|
477
548
|
* the safety default). Each tool's handler runs the descriptor's bound
|
|
478
|
-
* `invoke` (the real handler under the caller's subject).
|
|
479
|
-
*
|
|
480
|
-
* tool
|
|
549
|
+
* `invoke` (the real handler under the caller's subject).
|
|
550
|
+
*
|
|
551
|
+
* A `confirm` tool is admitted only when the descriptor is APPROVAL-BACKED
|
|
552
|
+
* (`requiresApproval:`), and then the human step is enforced by the real handler
|
|
553
|
+
* — the tool call returns the typed `ApprovalRequired` refusal, a human decides
|
|
554
|
+
* in the app, and the identical call succeeds once. This module still gates no
|
|
555
|
+
* human prompt of its own, and no longer needs to: it decides which tools can
|
|
556
|
+
* exist, and the pipeline decides whether the call runs.
|
|
481
557
|
*/
|
|
482
558
|
export declare const appTools: (entries: ReadonlyArray<AppToolEntry>, policy?: AppToolPolicy) => ReadonlyArray<AnyTool>;
|
|
483
559
|
|
|
@@ -660,8 +736,9 @@ export declare interface DataCopilotDeps {
|
|
|
660
736
|
*
|
|
661
737
|
* The two tables (`streamEventsTable`, `streamStateTable`) register into the
|
|
662
738
|
* app's migration set like the agent/usage tables — add them to the database
|
|
663
|
-
* barrel (or they ride along with a `*.agent.tsx` app).
|
|
664
|
-
*
|
|
739
|
+
* barrel (or they ride along with a `*.agent.tsx` app). Building this store
|
|
740
|
+
* BOUNDS them (see `registerStreamLogRetention`); `gcResumableStreams` remains
|
|
741
|
+
* for a tighter, done-aware collection an app can run on its own schedule.
|
|
665
742
|
*/
|
|
666
743
|
export declare const dataStoreResumableStreamStore: (store: DataStore) => ResumableStreamStore;
|
|
667
744
|
|
|
@@ -697,6 +774,14 @@ export declare const DEFAULT_INFERENCE_LEASE_MS: number;
|
|
|
697
774
|
* top of it. */
|
|
698
775
|
export declare const DEFAULT_INFERENCE_POLL_MS = 250;
|
|
699
776
|
|
|
777
|
+
/** Default window on `_voltro_ai_inferences`: 30 days since COMPLETION.
|
|
778
|
+
*
|
|
779
|
+
* Env `VOLTRO_AI_INFERENCES_TTL_HOURS`. Shorter than the usage ledger's year
|
|
780
|
+
* because this table stores the PROMPT verbatim (the dispatcher has to, to
|
|
781
|
+
* perform the call) — so it is the one AI table where "keep it forever by
|
|
782
|
+
* default" is a data-protection decision as well as a size one. */
|
|
783
|
+
export declare const DEFAULT_INFERENCE_RETENTION_HOURS: number;
|
|
784
|
+
|
|
700
785
|
declare const DEFAULT_INPUT: Schema.Struct<{
|
|
701
786
|
prompt: typeof Schema.String;
|
|
702
787
|
}>;
|
|
@@ -704,6 +789,21 @@ declare const DEFAULT_INPUT: Schema.Struct<{
|
|
|
704
789
|
/** The default judge threshold when a judge is configured without one. */
|
|
705
790
|
export declare const DEFAULT_JUDGE_THRESHOLD = 0.7;
|
|
706
791
|
|
|
792
|
+
/** Default window on `_voltro_prompts`: a year since last use. Long, because a
|
|
793
|
+
* registry row is tiny and its value is answering a question about a run you
|
|
794
|
+
* are only now investigating. Env `VOLTRO_AI_PROMPTS_TTL_HOURS`. */
|
|
795
|
+
export declare const DEFAULT_PROMPT_RETENTION_HOURS: number;
|
|
796
|
+
|
|
797
|
+
/** Default window on the resumable-stream log: 7 days since the row was
|
|
798
|
+
* written. Env `VOLTRO_STREAM_LOG_TTL_HOURS`.
|
|
799
|
+
*
|
|
800
|
+
* It is measured in DAYS against a thing whose useful life is MINUTES on
|
|
801
|
+
* purpose. A resumable stream exists so a browser that lost its socket can
|
|
802
|
+
* reconnect and replay; nothing reads a week-old log. The width is there so the
|
|
803
|
+
* bound can never be the reason a reconnect came back short, which makes it a
|
|
804
|
+
* bound on the DISK rather than a decision about the feature. */
|
|
805
|
+
export declare const DEFAULT_STREAM_LOG_RETENTION_HOURS: number;
|
|
806
|
+
|
|
707
807
|
/** Default input schema type when `input` is omitted (`{ prompt: string }`). */
|
|
708
808
|
export declare type DefaultAgentInputSchema = typeof DEFAULT_INPUT;
|
|
709
809
|
|
|
@@ -754,6 +854,17 @@ export declare const defineAgent: <S extends Schema.Schema.Any = DefaultAgentInp
|
|
|
754
854
|
*/
|
|
755
855
|
export declare const defineAgentExecutor: <S extends Schema.Schema.Any = DefaultAgentInputSchema>(descriptor: AgentDescriptor<S>, config: AgentExecutorConfig<AgentInput<S>>) => AgentExecutor<S>;
|
|
756
856
|
|
|
857
|
+
export declare interface DefinedPrompt extends PromptDefinition {
|
|
858
|
+
/** Content digest of `template` + `system`. The version. */
|
|
859
|
+
readonly digest: string;
|
|
860
|
+
/** Placeholder names found in the template, in first-appearance order. */
|
|
861
|
+
readonly variables: ReadonlyArray<string>;
|
|
862
|
+
/** Substitute the variables. Fails when one is missing — a `{{ticket}}` left
|
|
863
|
+
* unsubstituted is a prompt that quietly asks the model about the literal
|
|
864
|
+
* string `{{ticket}}`, which is worse than an error. */
|
|
865
|
+
readonly render: (vars?: Record<string, string | number | boolean>) => Effect.Effect<RenderedPrompt, PromptRenderError>;
|
|
866
|
+
}
|
|
867
|
+
|
|
757
868
|
/**
|
|
758
869
|
* Declare an eval suite. Validated at module load — an empty case list, a
|
|
759
870
|
* duplicate case id, or a nonsensical threshold throws loudly so a broken eval
|
|
@@ -773,16 +884,46 @@ export declare interface DefineEvalInput {
|
|
|
773
884
|
readonly tools?: ReadonlyArray<string>;
|
|
774
885
|
}
|
|
775
886
|
|
|
887
|
+
/**
|
|
888
|
+
* Declare a versioned prompt.
|
|
889
|
+
*
|
|
890
|
+
* ```ts
|
|
891
|
+
* export const triage = definePrompt({
|
|
892
|
+
* id: 'support.triage',
|
|
893
|
+
* system: 'You triage support tickets. Answer only with the category.',
|
|
894
|
+
* template: 'Ticket:\n{{body}}\n\nCategories: {{categories}}',
|
|
895
|
+
* label: 'v3-shorter-system',
|
|
896
|
+
* })
|
|
897
|
+
* ```
|
|
898
|
+
*
|
|
899
|
+
* The digest is computed here, from the file — so it exists before any database
|
|
900
|
+
* does, and a test, a CLI, or an eval can identify a prompt version without a
|
|
901
|
+
* connection. Registration (`recordPromptVersion`) only adds the human-facing
|
|
902
|
+
* revision number and the retrievable copy.
|
|
903
|
+
*/
|
|
904
|
+
export declare const definePrompt: (definition: PromptDefinition) => DefinedPrompt;
|
|
905
|
+
|
|
776
906
|
export declare const defineTool: <A, I, O>(def: {
|
|
777
907
|
readonly name: string;
|
|
778
908
|
readonly description: string;
|
|
779
909
|
readonly input: Schema.Schema<A, I>;
|
|
910
|
+
readonly inputJsonSchema?: Record<string, unknown>;
|
|
780
911
|
readonly output?: Schema.Schema.Any;
|
|
781
912
|
readonly execute?: (input: A) => Effect.Effect<O, never, unknown>;
|
|
782
913
|
readonly handler?: ToolHandler<A, O>;
|
|
783
914
|
readonly timeoutMs?: number;
|
|
784
915
|
}) => AnyTool;
|
|
785
916
|
|
|
917
|
+
/**
|
|
918
|
+
* Describe an external tool to the model.
|
|
919
|
+
*
|
|
920
|
+
* The provenance prefix is short on purpose — it is paid per tool, per turn —
|
|
921
|
+
* but it is not decoration. A model that has been told which text is
|
|
922
|
+
* third-party has something to weigh an instruction in that text against; a
|
|
923
|
+
* model handed the same sentence with no attribution has nothing.
|
|
924
|
+
*/
|
|
925
|
+
export declare const describeExternalTool: (namespace: string, tool: McpToolInfo) => string;
|
|
926
|
+
|
|
786
927
|
/**
|
|
787
928
|
* One dispatch tick. Never throws.
|
|
788
929
|
*
|
|
@@ -961,6 +1102,15 @@ export declare type ExposeAsToolLike = boolean | {
|
|
|
961
1102
|
|
|
962
1103
|
export { extractReasoningMiddleware }
|
|
963
1104
|
|
|
1105
|
+
/**
|
|
1106
|
+
* Flatten an MCP `content` array into the string the model sees, bounded.
|
|
1107
|
+
*
|
|
1108
|
+
* Non-text content is DESCRIBED, not inlined: a server returning a 6 MB base64
|
|
1109
|
+
* image would otherwise spend a context window (and a lot of money) on
|
|
1110
|
+
* something the text channel cannot use anyway.
|
|
1111
|
+
*/
|
|
1112
|
+
export declare const flattenMcpContent: (result: Record<string, unknown>, maxBytes: number) => string;
|
|
1113
|
+
|
|
964
1114
|
/**
|
|
965
1115
|
* Pull the ACTUAL per-call cost (USD) out of an AI-SDK result's provider
|
|
966
1116
|
* metadata, when the Vercel AI Gateway reported it. The gateway attaches
|
|
@@ -1322,6 +1472,36 @@ export declare const hashToVector: (text: string, dim: number) => number[];
|
|
|
1322
1472
|
*/
|
|
1323
1473
|
export declare const historyToModelMessages: (history: ReadonlyArray<AgentMessage>) => ModelMessage[];
|
|
1324
1474
|
|
|
1475
|
+
/**
|
|
1476
|
+
* MCP over Streamable HTTP.
|
|
1477
|
+
*
|
|
1478
|
+
* SSRF note, stated rather than silently assumed: `url` is app configuration.
|
|
1479
|
+
* Nothing here validates that it points outside your own network, because a
|
|
1480
|
+
* legitimate deployment mounts an MCP server on a private address. If the URL
|
|
1481
|
+
* can ever come from user or model input in your app, gate it there — this
|
|
1482
|
+
* layer treats it as trusted and everything the URL RETURNS as untrusted.
|
|
1483
|
+
*/
|
|
1484
|
+
export declare const httpMcpTransport: (options: HttpMcpTransportOptions) => McpTransport;
|
|
1485
|
+
|
|
1486
|
+
export declare interface HttpMcpTransportOptions {
|
|
1487
|
+
/** The server's Streamable-HTTP endpoint. App configuration — NEVER derived
|
|
1488
|
+
* from anything a model produced. */
|
|
1489
|
+
readonly url: string;
|
|
1490
|
+
/** Namespace, used only for error attribution. */
|
|
1491
|
+
readonly server: string;
|
|
1492
|
+
/**
|
|
1493
|
+
* Extra request headers — this is where an `Authorization` goes.
|
|
1494
|
+
*
|
|
1495
|
+
* Read it from the environment at the call site (`process.env.GITHUB_MCP_TOKEN`).
|
|
1496
|
+
* The framework ships no credential value for any server, and there is
|
|
1497
|
+
* deliberately no "default token" field here to make that accidental.
|
|
1498
|
+
*/
|
|
1499
|
+
readonly headers?: Record<string, string>;
|
|
1500
|
+
readonly bounds?: Partial<McpBounds>;
|
|
1501
|
+
/** Injectable for tests. Defaults to the global `fetch`. */
|
|
1502
|
+
readonly fetchImpl?: typeof fetch;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1325
1505
|
export declare interface InferenceDispatchDeps {
|
|
1326
1506
|
readonly store: DataStore;
|
|
1327
1507
|
/**
|
|
@@ -1398,12 +1578,31 @@ export declare const isAgentExecutor: (x: unknown) => x is AnyAgentExecutor;
|
|
|
1398
1578
|
|
|
1399
1579
|
export declare const isEvalDefinition: (value: unknown) => value is EvalDefinition;
|
|
1400
1580
|
|
|
1581
|
+
/**
|
|
1582
|
+
* Is this external tool a WRITE, for the purpose of the `includeWrites` gate
|
|
1583
|
+
* and the confirm default?
|
|
1584
|
+
*
|
|
1585
|
+
* The default answer is YES for everything. That is not pessimism for its own
|
|
1586
|
+
* sake: the classification exists to decide whether an agent may take an action
|
|
1587
|
+
* with side effects, and the only party who can answer that for an external
|
|
1588
|
+
* server is the app that chose to mount it. So the app answers, with `readOnly`.
|
|
1589
|
+
* `trustToolHints` delegates the answer back to the server, explicitly, in one
|
|
1590
|
+
* place a reviewer can find.
|
|
1591
|
+
*/
|
|
1592
|
+
export declare const isExternalWrite: (tool: McpToolInfo, namespace: string, policy: McpToolPolicy) => boolean;
|
|
1593
|
+
|
|
1594
|
+
/** Is this a rendered prompt or a bare string? The discriminator `aiStep` uses
|
|
1595
|
+
* so `prompt:` can accept either without a wrapper type at every call site. */
|
|
1596
|
+
export declare const isRenderedPrompt: (value: unknown) => value is RenderedPrompt;
|
|
1597
|
+
|
|
1401
1598
|
/** Whether a stream with this id is currently registered (in flight). */
|
|
1402
1599
|
export declare const isStreamActive: (streamId: string) => boolean;
|
|
1403
1600
|
|
|
1404
1601
|
/** Is `x` a `defineTool(...)` result? */
|
|
1405
1602
|
export declare const isTool: (x: unknown) => x is AnyTool;
|
|
1406
1603
|
|
|
1604
|
+
export declare const isUsableMcpToolName: (name: unknown, maxChars: number) => name is string;
|
|
1605
|
+
|
|
1407
1606
|
export declare interface JudgeVerdict {
|
|
1408
1607
|
readonly pass: boolean;
|
|
1409
1608
|
/** 0..1. Below `judge.threshold` fails the case even when `pass` is true. */
|
|
@@ -1437,6 +1636,267 @@ export declare const loggingMiddleware: (opts?: {
|
|
|
1437
1636
|
readonly log?: (event: AiCallLog) => void;
|
|
1438
1637
|
}) => AiMiddleware;
|
|
1439
1638
|
|
|
1639
|
+
/**
|
|
1640
|
+
* Build ONE external tool. Exported so the gate can be tested at the sharpest
|
|
1641
|
+
* point: hand this a tool the policy denies and it still refuses at call time,
|
|
1642
|
+
* without touching the transport.
|
|
1643
|
+
*/
|
|
1644
|
+
export declare const makeMcpTool: (server: McpServer, tool: McpToolInfo, policy: McpToolPolicy, bounds: McpBounds) => AnyTool;
|
|
1645
|
+
|
|
1646
|
+
export declare const MCP_BOUNDS_DEFAULTS: McpBounds;
|
|
1647
|
+
|
|
1648
|
+
/** Identity this client reports in `initialize`. No version literal here — the
|
|
1649
|
+
* caller passes its own, so a published build reports the lockstep release. */
|
|
1650
|
+
export declare const MCP_CLIENT_INFO: {
|
|
1651
|
+
readonly name: string;
|
|
1652
|
+
readonly version: string;
|
|
1653
|
+
};
|
|
1654
|
+
|
|
1655
|
+
/** The MCP revision this client requests. Mirror of `@voltro/mcp`'s. */
|
|
1656
|
+
export declare const MCP_CLIENT_PROTOCOL_VERSION = "2025-06-18";
|
|
1657
|
+
|
|
1658
|
+
/**
|
|
1659
|
+
* The ceilings applied to everything an external server sends.
|
|
1660
|
+
*
|
|
1661
|
+
* Resolution order is explicit option → environment variable → default, so an
|
|
1662
|
+
* operator can tighten a deployment without a code change and an app can pin a
|
|
1663
|
+
* value the operator cannot loosen.
|
|
1664
|
+
*/
|
|
1665
|
+
export declare interface McpBounds {
|
|
1666
|
+
/** How many of a server's tools may be mounted, AFTER the policy gate.
|
|
1667
|
+
* A server advertising 4 000 tools would otherwise buy the whole context
|
|
1668
|
+
* window. Env `VOLTRO_MCP_MAX_TOOLS`. */
|
|
1669
|
+
readonly maxTools: number;
|
|
1670
|
+
/** Ceiling on one tool's description — the prompt-injection surface.
|
|
1671
|
+
* Env `VOLTRO_MCP_MAX_DESCRIPTION_CHARS`. */
|
|
1672
|
+
readonly maxDescriptionChars: number;
|
|
1673
|
+
/** Ceiling on a tool's advertised input JSON Schema, serialized. A schema
|
|
1674
|
+
* over this is DROPPED rather than truncated — a half-schema would make the
|
|
1675
|
+
* model call the tool wrongly. Env `VOLTRO_MCP_MAX_SCHEMA_BYTES`. */
|
|
1676
|
+
readonly maxSchemaBytes: number;
|
|
1677
|
+
/** How deep + how wide the input schema may be before it is pruned. Guards
|
|
1678
|
+
* the recursive sanitizer itself against a schema built to blow the stack. */
|
|
1679
|
+
readonly maxSchemaDepth: number;
|
|
1680
|
+
readonly maxSchemaNodes: number;
|
|
1681
|
+
/** Ceiling on one tool-call RESULT as handed back to the model. Truncated
|
|
1682
|
+
* with a visible marker. Env `VOLTRO_MCP_MAX_RESULT_BYTES`. */
|
|
1683
|
+
readonly maxResultBytes: number;
|
|
1684
|
+
/** Hard ceiling on any single HTTP/stdio response body, applied while
|
|
1685
|
+
* reading — an unbounded stream is cancelled, not buffered.
|
|
1686
|
+
* Env `VOLTRO_MCP_MAX_RESPONSE_BYTES`. */
|
|
1687
|
+
readonly maxResponseBytes: number;
|
|
1688
|
+
/** Per-JSON-RPC-request deadline. Env `VOLTRO_MCP_TIMEOUT_MS`. */
|
|
1689
|
+
readonly requestTimeoutMs: number;
|
|
1690
|
+
/** Ceiling on a tool NAME. Longer names are dropped, not truncated —
|
|
1691
|
+
* a truncated name would not be the name we allowed. */
|
|
1692
|
+
readonly maxToolNameChars: number;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
/**
|
|
1696
|
+
* `tools/call`. Returns the flattened, bounded text the model receives.
|
|
1697
|
+
*
|
|
1698
|
+
* `isError: true` is surfaced as text prefixed with `Error:` rather than as a
|
|
1699
|
+
* failed Effect: a tool that failed IS information the model should get and act
|
|
1700
|
+
* on, and that is exactly how `buildSdkTools` treats an app tool's rejection.
|
|
1701
|
+
*/
|
|
1702
|
+
export declare const mcpCallTool: (transport: McpTransport, server: string, toolName: string, args: Record<string, unknown>, bounds: McpBounds) => Effect.Effect<string, McpError>;
|
|
1703
|
+
|
|
1704
|
+
/**
|
|
1705
|
+
* Typed MCP client failure.
|
|
1706
|
+
*
|
|
1707
|
+
* `transport` — the socket/process/HTTP layer failed or timed out.
|
|
1708
|
+
* `protocol` — we reached the server and it answered with something that is
|
|
1709
|
+
* not a usable MCP response (a JSON-RPC error, a malformed body).
|
|
1710
|
+
* `bounds` — the server answered, and the answer exceeded a stated bound.
|
|
1711
|
+
* Kept SEPARATE from `protocol` on purpose: "the server is broken"
|
|
1712
|
+
* and "the server sent 40 MB" are different operational events and
|
|
1713
|
+
* the second one is the one you want to alert on.
|
|
1714
|
+
* `policy` — the app's own allow/deny gate refused. Never the server's fault.
|
|
1715
|
+
*/
|
|
1716
|
+
export declare class McpError extends McpError_base {
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
declare const McpError_base: Schema.TaggedErrorClass<McpError, "McpError", {
|
|
1720
|
+
readonly _tag: Schema.tag<"McpError">;
|
|
1721
|
+
} & {
|
|
1722
|
+
/** The app-chosen namespace of the server, never a server-supplied string. */
|
|
1723
|
+
server: typeof Schema.String;
|
|
1724
|
+
reason: Schema.Literal<["transport", "protocol", "bounds", "policy"]>;
|
|
1725
|
+
message: typeof Schema.String;
|
|
1726
|
+
}>;
|
|
1727
|
+
|
|
1728
|
+
/** The MCP handshake: `initialize`, then the `notifications/initialized` the
|
|
1729
|
+
* spec requires before any other request. */
|
|
1730
|
+
export declare const mcpInitialize: (transport: McpTransport, server: string, clientInfo?: {
|
|
1731
|
+
readonly name: string;
|
|
1732
|
+
readonly version: string;
|
|
1733
|
+
}) => Effect.Effect<{
|
|
1734
|
+
readonly protocolVersion: string;
|
|
1735
|
+
readonly serverName: string;
|
|
1736
|
+
}, McpError>;
|
|
1737
|
+
|
|
1738
|
+
export declare interface McpJsonRpcRequest {
|
|
1739
|
+
readonly jsonrpc: '2.0';
|
|
1740
|
+
readonly id?: string | number | null;
|
|
1741
|
+
readonly method: string;
|
|
1742
|
+
readonly params?: Record<string, unknown>;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
export declare interface McpJsonRpcResponse {
|
|
1746
|
+
readonly jsonrpc: '2.0';
|
|
1747
|
+
readonly id: string | number | null;
|
|
1748
|
+
readonly result?: unknown;
|
|
1749
|
+
readonly error?: {
|
|
1750
|
+
readonly code: number;
|
|
1751
|
+
readonly message: string;
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
/**
|
|
1756
|
+
* `tools/list`, bounded.
|
|
1757
|
+
*
|
|
1758
|
+
* Everything a server can inflate is capped HERE rather than at the call site:
|
|
1759
|
+
* a tool whose name is unusable or whose schema is oversized is DROPPED (with
|
|
1760
|
+
* its name reported), a description is sanitized + truncated, and the list is
|
|
1761
|
+
* cut to `maxTools` after sorting so the cut is deterministic across calls
|
|
1762
|
+
* rather than "whichever ones the server put first this time".
|
|
1763
|
+
*/
|
|
1764
|
+
export declare const mcpListTools: (transport: McpTransport, server: string, bounds: McpBounds) => Effect.Effect<{
|
|
1765
|
+
readonly tools: ReadonlyArray<McpToolInfo>;
|
|
1766
|
+
readonly dropped: ReadonlyArray<{
|
|
1767
|
+
readonly name: string;
|
|
1768
|
+
readonly reason: string;
|
|
1769
|
+
}>;
|
|
1770
|
+
}, McpError>;
|
|
1771
|
+
|
|
1772
|
+
/** An external server, as the app names and reaches it. */
|
|
1773
|
+
export declare interface McpServer {
|
|
1774
|
+
/**
|
|
1775
|
+
* The app's own name for this server. Prefixes every tool tag
|
|
1776
|
+
* (`github.create_issue`), so it is what an allow/deny rule matches on.
|
|
1777
|
+
* App-chosen — never the server's self-reported name, which is untrusted.
|
|
1778
|
+
*/
|
|
1779
|
+
readonly namespace: string;
|
|
1780
|
+
readonly transport: McpTransport;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
export declare type McpToolDecision = {
|
|
1784
|
+
readonly admitted: true;
|
|
1785
|
+
readonly spec: SynthesizedTool;
|
|
1786
|
+
} | {
|
|
1787
|
+
readonly admitted: false;
|
|
1788
|
+
readonly reason: string;
|
|
1789
|
+
};
|
|
1790
|
+
|
|
1791
|
+
/**
|
|
1792
|
+
* THE gate. One pure function, called at mount AND re-called inside every tool
|
|
1793
|
+
* body before a request leaves the process, so a toolset that was tampered with
|
|
1794
|
+
* after construction still cannot reach the server.
|
|
1795
|
+
*
|
|
1796
|
+
* Order matters and is asserted in the tests: default-deny, then the shared
|
|
1797
|
+
* `passesPolicy` (deny beats allow), then the writes gate.
|
|
1798
|
+
*/
|
|
1799
|
+
export declare const mcpToolDecision: (tool: McpToolInfo, namespace: string, policy: McpToolPolicy) => McpToolDecision;
|
|
1800
|
+
|
|
1801
|
+
/** One tool as an external server advertises it, already bounded + sanitized. */
|
|
1802
|
+
export declare interface McpToolInfo {
|
|
1803
|
+
readonly name: string;
|
|
1804
|
+
readonly description: string;
|
|
1805
|
+
readonly inputSchema: Record<string, unknown>;
|
|
1806
|
+
/**
|
|
1807
|
+
* The server's own hints about the tool. ADVISORY ONLY — see
|
|
1808
|
+
* `mcpTools.ts`; the server is the untrusted party, so its claim that a tool
|
|
1809
|
+
* is read-only cannot be what decides whether the tool is allowed to run.
|
|
1810
|
+
*/
|
|
1811
|
+
readonly annotations?: {
|
|
1812
|
+
readonly readOnlyHint?: boolean;
|
|
1813
|
+
readonly destructiveHint?: boolean;
|
|
1814
|
+
readonly idempotentHint?: boolean;
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
/**
|
|
1819
|
+
* The mount policy. Extends `AppToolPolicy` rather than paralleling it: an app
|
|
1820
|
+
* that already expresses its agent policy in one shape expresses this one in
|
|
1821
|
+
* the same shape, and the two lists can literally be the same object.
|
|
1822
|
+
*/
|
|
1823
|
+
export declare interface McpToolPolicy extends AppToolPolicy {
|
|
1824
|
+
/**
|
|
1825
|
+
* Tools the APP declares to be reads (tags, `*` globs, or a RegExp), matched
|
|
1826
|
+
* against the namespaced tag. This is the app's judgement, and it is the only
|
|
1827
|
+
* one that counts by default — see {@link isExternalWrite}.
|
|
1828
|
+
*/
|
|
1829
|
+
readonly readOnly?: ReadonlyArray<string> | RegExp;
|
|
1830
|
+
/**
|
|
1831
|
+
* Accept the server's `annotations.readOnlyHint` as evidence a tool is a read.
|
|
1832
|
+
*
|
|
1833
|
+
* Default FALSE, and the default is the security decision. For an app tool the
|
|
1834
|
+
* read/write split comes from the descriptor KIND, which somebody in this
|
|
1835
|
+
* repository wrote. For an external tool it would come from the untrusted
|
|
1836
|
+
* party, who can flip it between two `tools/list` calls — so trusting it would
|
|
1837
|
+
* let a server talk its way past `includeWrites: false`.
|
|
1838
|
+
*/
|
|
1839
|
+
readonly trustToolHints?: boolean;
|
|
1840
|
+
/** Ceilings on everything the server sends. See {@link McpBounds}. */
|
|
1841
|
+
readonly bounds?: Partial<McpBounds>;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
/** The `Record<string, AnyTool>` an agent loop wants, without the inventory. */
|
|
1845
|
+
export declare const mcpTools: (server: McpServer, policy: McpToolPolicy) => Effect.Effect<Record<string, AnyTool>, McpError>;
|
|
1846
|
+
|
|
1847
|
+
/** A mounted server's toolset. */
|
|
1848
|
+
export declare interface McpToolset {
|
|
1849
|
+
readonly namespace: string;
|
|
1850
|
+
/** Keyed by the namespaced tag — the shape the agent loop consumes. */
|
|
1851
|
+
readonly tools: Record<string, AnyTool>;
|
|
1852
|
+
/** The same inventory type `synthesizeToolSpecs` returns, for a confirm-UI. */
|
|
1853
|
+
readonly specs: ReadonlyArray<SynthesizedTool>;
|
|
1854
|
+
/** Everything the server advertised that did NOT mount, and why. Surfaced
|
|
1855
|
+
* rather than logged: a tool silently missing from an agent's set is a
|
|
1856
|
+
* support ticket that starts "the agent just says it can't do that". */
|
|
1857
|
+
readonly dropped: ReadonlyArray<{
|
|
1858
|
+
readonly name: string;
|
|
1859
|
+
readonly reason: string;
|
|
1860
|
+
}>;
|
|
1861
|
+
/** The bounds actually in force (after env + defaults resolved). */
|
|
1862
|
+
readonly bounds: McpBounds;
|
|
1863
|
+
readonly close: () => Effect.Effect<void>;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
/**
|
|
1867
|
+
* Connect to an external MCP server, list its tools, and mount the ones this
|
|
1868
|
+
* app's policy admits.
|
|
1869
|
+
*
|
|
1870
|
+
* ```ts
|
|
1871
|
+
* const github = yield* mcpToolset(
|
|
1872
|
+
* { namespace: 'github', transport: httpMcpTransport({
|
|
1873
|
+
* server: 'github', url: process.env.GITHUB_MCP_URL!,
|
|
1874
|
+
* headers: { authorization: `Bearer ${process.env.GITHUB_MCP_TOKEN!}` },
|
|
1875
|
+
* }) },
|
|
1876
|
+
* { allow: ['github.get_*', 'github.search_*'], readOnly: ['github.get_*', 'github.search_*'] },
|
|
1877
|
+
* )
|
|
1878
|
+
* yield* generateObjectWithTools({ prompt, tools: github.tools, schema: Result })
|
|
1879
|
+
* ```
|
|
1880
|
+
*/
|
|
1881
|
+
export declare const mcpToolset: (server: McpServer, policy: McpToolPolicy) => Effect.Effect<McpToolset, McpError>;
|
|
1882
|
+
|
|
1883
|
+
/** `<namespace>.<toolName>` — the tag an allow/deny rule matches. */
|
|
1884
|
+
export declare const mcpToolTag: (namespace: string, toolName: string) => string;
|
|
1885
|
+
|
|
1886
|
+
/**
|
|
1887
|
+
* One JSON-RPC round trip against an external server. The seam every test uses
|
|
1888
|
+
* — a test supplies an in-memory transport and never opens a socket, which is
|
|
1889
|
+
* what makes the untrusted-input bounds assertable on their own.
|
|
1890
|
+
*/
|
|
1891
|
+
export declare interface McpTransport {
|
|
1892
|
+
/** Send a request; resolve with its response. */
|
|
1893
|
+
readonly send: (request: McpJsonRpcRequest) => Effect.Effect<McpJsonRpcResponse, McpError>;
|
|
1894
|
+
/** Send a notification (no id, no response expected). */
|
|
1895
|
+
readonly notify: (request: McpJsonRpcRequest) => Effect.Effect<void, McpError>;
|
|
1896
|
+
/** Release the socket / child process. Idempotent. */
|
|
1897
|
+
readonly close: () => Effect.Effect<void>;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1440
1900
|
/** Token tally, mirroring `GenerateTextResult['usage']`. Only `generateImage`
|
|
1441
1901
|
* reports one (the speech + video model specs carry no usage), which is why
|
|
1442
1902
|
* it's optional on every media result. */
|
|
@@ -1619,6 +2079,16 @@ export declare type OpenAIModel = 'gpt-5.5' | 'gpt-4o' | 'gpt-4o-mini' | (string
|
|
|
1619
2079
|
* cannot be journaled, and its JSON rendering can. */
|
|
1620
2080
|
export declare const outputJsonSchema: (schema: Schema.Schema.Any, fn: string) => Effect.Effect<Record<string, unknown>, AiError>;
|
|
1621
2081
|
|
|
2082
|
+
/**
|
|
2083
|
+
* Pull the first JSON-RPC message out of a Streamable-HTTP response body.
|
|
2084
|
+
* A server may answer a POST with `application/json` (one object) or with
|
|
2085
|
+
* `text/event-stream` (SSE frames, the response interleaved with whatever else
|
|
2086
|
+
* the server wants to push). Both are spec-legal; we take the first frame whose
|
|
2087
|
+
* `id` matches and ignore the rest, because a client that keeps reading after
|
|
2088
|
+
* its answer is a client an unbounded server can hold open forever.
|
|
2089
|
+
*/
|
|
2090
|
+
export declare const parseHttpMcpBody: (body: string, contentType: string, id: string | number | null) => McpJsonRpcResponse | null;
|
|
2091
|
+
|
|
1622
2092
|
/** Does this tag pass the allow/deny policy? deny wins; allow (when set) gates. */
|
|
1623
2093
|
export declare const passesPolicy: (tag: string, policy: AppToolPolicy) => boolean;
|
|
1624
2094
|
|
|
@@ -1638,6 +2108,119 @@ export declare const patchStreamingMessage: (store: DataStore, id: string, parts
|
|
|
1638
2108
|
*/
|
|
1639
2109
|
export declare const priceForModel: (model: string) => ModelPrice | undefined;
|
|
1640
2110
|
|
|
2111
|
+
export declare interface PromptDefinition {
|
|
2112
|
+
/**
|
|
2113
|
+
* Stable identity across revisions. Dotted, like a descriptor tag
|
|
2114
|
+
* (`support.triage`) — the same vocabulary the rest of the framework uses for
|
|
2115
|
+
* "the name of a thing an app owns".
|
|
2116
|
+
*/
|
|
2117
|
+
readonly id: string;
|
|
2118
|
+
/**
|
|
2119
|
+
* The template, with `{{name}}` placeholders. A STRING, not a function, and
|
|
2120
|
+
* that is the design: a function has no stable content to hash, so a
|
|
2121
|
+
* function-built prompt could be versioned only by hashing its output — which
|
|
2122
|
+
* would make every distinct customer message a new "version". Compose in the
|
|
2123
|
+
* variables instead.
|
|
2124
|
+
*/
|
|
2125
|
+
readonly template: string;
|
|
2126
|
+
/** Optional system message. Part of the digest — changing it IS a new
|
|
2127
|
+
* version, because it changes the model's behaviour as surely as the
|
|
2128
|
+
* template does. */
|
|
2129
|
+
readonly system?: string;
|
|
2130
|
+
/** Optional human label for this revision (`'shorter-system'`). Metadata:
|
|
2131
|
+
* deliberately NOT part of the digest, so relabelling does not fork a
|
|
2132
|
+
* version. */
|
|
2133
|
+
readonly label?: string;
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
/** The digest form: a prefix, not the whole hash. This is a correlation handle,
|
|
2137
|
+
* and a full sha256 in a dashboard cell is a wall of hex nobody reads.
|
|
2138
|
+
*
|
|
2139
|
+
* Lives here rather than in `inferStep.ts` (where it started) because it is now
|
|
2140
|
+
* the identity of a versioned artefact, and `inferStep` imports
|
|
2141
|
+
* `@voltro/workflow` — a prompt registry has no business needing the workflow
|
|
2142
|
+
* engine to compute a hash. `inferStep` re-exports it. */
|
|
2143
|
+
export declare const promptDigest: (prompt: string) => string;
|
|
2144
|
+
|
|
2145
|
+
export declare class PromptRenderError extends PromptRenderError_base {
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
declare const PromptRenderError_base: Schema.TaggedErrorClass<PromptRenderError, "PromptRenderError", {
|
|
2149
|
+
readonly _tag: Schema.tag<"PromptRenderError">;
|
|
2150
|
+
} & {
|
|
2151
|
+
promptId: typeof Schema.String;
|
|
2152
|
+
missing: Schema.Array$<typeof Schema.String>;
|
|
2153
|
+
message: typeof Schema.String;
|
|
2154
|
+
}>;
|
|
2155
|
+
|
|
2156
|
+
export declare const PROMPTS_TABLE = "_voltro_prompts";
|
|
2157
|
+
|
|
2158
|
+
/**
|
|
2159
|
+
* One row per (prompt id · content digest) — i.e. one row per VERSION.
|
|
2160
|
+
*
|
|
2161
|
+
* `promptId` + `digest` are `.maxLength(191)` because they carry a composite
|
|
2162
|
+
* UNIQUE: an unbounded text column under a UNIQUE is backed on MariaDB by a
|
|
2163
|
+
* hash long-unique index whose hidden column wedges binlog CDC (see
|
|
2164
|
+
* `@voltro/database`'s maintainer note). 191 is the utf8mb4 index-prefix limit.
|
|
2165
|
+
*
|
|
2166
|
+
* `lastUsedAt` is what the retention sweep prunes on, and the table is
|
|
2167
|
+
* self-healing under it: `recordPromptVersion` re-inserts on the next use, so a
|
|
2168
|
+
* version that ages out is a version nothing has run in a year.
|
|
2169
|
+
*/
|
|
2170
|
+
export declare const promptsTable: Table<typeof PROMPTS_TABLE, Record<string, ColumnDefinition<unknown>>, true>;
|
|
2171
|
+
|
|
2172
|
+
/** A prompt's identity, as it is stamped onto a step row and a usage row. */
|
|
2173
|
+
export declare interface PromptStamp {
|
|
2174
|
+
/** The author-chosen stable id, e.g. `support.triage`. */
|
|
2175
|
+
readonly promptId: string;
|
|
2176
|
+
/** Content digest of the template (+ system). CHANGES when the template does
|
|
2177
|
+
* — that is the version. */
|
|
2178
|
+
readonly digest: string;
|
|
2179
|
+
/** The monotonic revision `recordPromptVersion` assigned, once it has been
|
|
2180
|
+
* registered. Absent before first registration; the digest is the identity
|
|
2181
|
+
* either way. */
|
|
2182
|
+
readonly revision?: number;
|
|
2183
|
+
/** Optional human label the author gave this revision. */
|
|
2184
|
+
readonly label?: string;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
/** Look one version up by its digest — the reverse of the stamp on a step row
|
|
2188
|
+
* or a usage row, and therefore the answer to "which prompt produced this
|
|
2189
|
+
* run". */
|
|
2190
|
+
export declare const promptVersionByDigest: (store: DataStore, digest: string) => Effect.Effect<PromptVersionRow | null>;
|
|
2191
|
+
|
|
2192
|
+
/**
|
|
2193
|
+
* The version digest of a (template, system) pair.
|
|
2194
|
+
*
|
|
2195
|
+
* LENGTH-PREFIXED, not separator-joined. A separator is only injective when it
|
|
2196
|
+
* cannot occur in the parts, and every printable candidate can occur in prose:
|
|
2197
|
+
* join on a space and template `'a b'` + no system collides with template `'a'`
|
|
2198
|
+
* + system `'b'`, so two genuinely different prompts read as ONE version and the
|
|
2199
|
+
* second one's provenance silently points at the first. A length prefix has no
|
|
2200
|
+
* such case and needs no exotic character.
|
|
2201
|
+
*
|
|
2202
|
+
* (The first version of this used a raw NUL as the separator, which is the
|
|
2203
|
+
* repo-wide rule this file now also demonstrates the reason for: the file
|
|
2204
|
+
* became BINARY to grep, and every text search over it silently returned
|
|
2205
|
+
* nothing — indistinguishable from a clean file.)
|
|
2206
|
+
*/
|
|
2207
|
+
export declare const promptVersionDigest: (template: string, system: string | undefined) => string;
|
|
2208
|
+
|
|
2209
|
+
/** A registered version, as read back. */
|
|
2210
|
+
export declare interface PromptVersionRow {
|
|
2211
|
+
readonly promptId: string;
|
|
2212
|
+
readonly digest: string;
|
|
2213
|
+
readonly revision: number;
|
|
2214
|
+
readonly label: string | null;
|
|
2215
|
+
readonly template: string | null;
|
|
2216
|
+
readonly system: string | null;
|
|
2217
|
+
readonly firstSeenAt: Date | null;
|
|
2218
|
+
readonly lastUsedAt: Date | null;
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
/** Every registered version of one prompt, newest revision first. */
|
|
2222
|
+
export declare const promptVersionsFor: (store: DataStore, promptId: string) => Effect.Effect<ReadonlyArray<PromptVersionRow>>;
|
|
2223
|
+
|
|
1641
2224
|
/**
|
|
1642
2225
|
* A resolved provider choice — a DISCRIMINATED UNION on `name`. Each provider
|
|
1643
2226
|
* only carries the fields that apply to it: the mock-only `mockText` / `script`
|
|
@@ -1749,6 +2332,10 @@ export declare interface QueuedInference {
|
|
|
1749
2332
|
readonly model: string;
|
|
1750
2333
|
readonly prompt: string;
|
|
1751
2334
|
readonly system: string | null;
|
|
2335
|
+
/** Prompt provenance carried from the parked run — see the table. */
|
|
2336
|
+
readonly promptId: string | null;
|
|
2337
|
+
readonly promptDigest: string | null;
|
|
2338
|
+
readonly promptRevision: number | null;
|
|
1752
2339
|
readonly maxTokens: number | null;
|
|
1753
2340
|
readonly providerOptions: unknown;
|
|
1754
2341
|
readonly jsonSchema: Record<string, unknown> | null;
|
|
@@ -1775,6 +2362,25 @@ export declare interface ReadRecorder<S extends QueryableStore> {
|
|
|
1775
2362
|
*/
|
|
1776
2363
|
export declare const recordAiUsage: (store: DataStore, record: AiUsageRecordInput) => Effect.Effect<CostBreakdown>;
|
|
1777
2364
|
|
|
2365
|
+
/**
|
|
2366
|
+
* Register a prompt version, idempotently, and return it.
|
|
2367
|
+
*
|
|
2368
|
+
* First sight of a `(promptId, digest)` pair inserts it with the next revision
|
|
2369
|
+
* number for that `promptId`; every later sight only touches `lastUsedAt`. The
|
|
2370
|
+
* insert is an `insertIgnore` over the composite UNIQUE, so two replicas
|
|
2371
|
+
* racing the same first sight produce one row.
|
|
2372
|
+
*
|
|
2373
|
+
* The revision NUMBER is read-then-write and therefore not strictly serialised:
|
|
2374
|
+
* two different versions of one prompt first seen in the same millisecond on
|
|
2375
|
+
* two replicas can both land on revision 4. That is accepted deliberately — the
|
|
2376
|
+
* digest is the identity and it is exact; the revision is a label for humans,
|
|
2377
|
+
* and paying for a serialisable counter to make a label prettier is the wrong
|
|
2378
|
+
* trade. Order by `firstSeenAt` when you need the true sequence.
|
|
2379
|
+
*/
|
|
2380
|
+
export declare const recordPromptVersion: (store: DataStore, prompt: PromptDefinition & {
|
|
2381
|
+
readonly digest?: string;
|
|
2382
|
+
}) => Effect.Effect<PromptVersionRow>;
|
|
2383
|
+
|
|
1778
2384
|
/**
|
|
1779
2385
|
* Wrap a store so the rows it serves become a dependency set — automatic
|
|
1780
2386
|
* provenance capture with no runtime edit. Every `query()` records
|
|
@@ -1802,6 +2408,16 @@ export declare const redisResumableStreamStore: (redis: ResumableRedis, opts?: {
|
|
|
1802
2408
|
readonly ttlSeconds?: number;
|
|
1803
2409
|
}) => ResumableStreamStore;
|
|
1804
2410
|
|
|
2411
|
+
/**
|
|
2412
|
+
* Re-list and re-gate a mounted server.
|
|
2413
|
+
*
|
|
2414
|
+
* This is the ONLY way the tool set changes. Nothing reacts to a
|
|
2415
|
+
* `notifications/tools/list_changed`, and that is deliberate: a set that can be
|
|
2416
|
+
* rewritten by the party it protects against is not a set, and the cost of the
|
|
2417
|
+
* alternative is a stale tool until the app decides otherwise.
|
|
2418
|
+
*/
|
|
2419
|
+
export declare const refreshMcpToolset: (server: McpServer, policy: McpToolPolicy) => Effect.Effect<McpToolset, McpError>;
|
|
2420
|
+
|
|
1805
2421
|
/**
|
|
1806
2422
|
* Register (or reuse) the `AbortController` for `streamId`. `streamText` calls
|
|
1807
2423
|
* this when given a `streamId`; it threads the returned controller's `signal`
|
|
@@ -1811,6 +2427,18 @@ export declare const redisResumableStreamStore: (redis: ResumableRedis, opts?: {
|
|
|
1811
2427
|
*/
|
|
1812
2428
|
export declare const registerStream: (streamId: string) => AbortController;
|
|
1813
2429
|
|
|
2430
|
+
/** What `.render(vars)` produces — a stamp plus the text to send. */
|
|
2431
|
+
export declare interface RenderedPrompt extends PromptStamp {
|
|
2432
|
+
readonly prompt: string;
|
|
2433
|
+
readonly system?: string;
|
|
2434
|
+
/** The artefact this was rendered from. Carried along so a consumer that
|
|
2435
|
+
* receives only the rendered value (`aiStep`) can REGISTER the version
|
|
2436
|
+
* without the app having to remember a separate boot-time call. It is the
|
|
2437
|
+
* template — code, not data — so carrying it costs nothing a repository
|
|
2438
|
+
* checkout does not already cost. */
|
|
2439
|
+
readonly definition: PromptDefinition;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
1814
2442
|
/**
|
|
1815
2443
|
* Gate an AI call against a per-tenant USD budget with an ATOMIC reservation —
|
|
1816
2444
|
* the cap precedent is billing's `requireEntitlement` / storage's
|
|
@@ -1888,6 +2516,9 @@ export declare const resolveEmbeddingModel: (config: EmbeddingProviderConfig) =>
|
|
|
1888
2516
|
|
|
1889
2517
|
export declare const resolveImageModel: (config: ProviderConfig) => ImageModelV4;
|
|
1890
2518
|
|
|
2519
|
+
/** Explicit option → env → default, per field. */
|
|
2520
|
+
export declare const resolveMcpBounds: (overrides: Partial<McpBounds> | undefined, env?: Record<string, string | undefined>) => McpBounds;
|
|
2521
|
+
|
|
1891
2522
|
export declare const resolveModel: (config: ProviderConfig, middleware?: AiMiddleware | ReadonlyArray<AiMiddleware>) => LanguageModelV4;
|
|
1892
2523
|
|
|
1893
2524
|
export declare const resolveSpeechModel: (config: ProviderConfig) => SpeechModelV4;
|
|
@@ -2076,6 +2707,30 @@ export declare interface RunEvalDeps {
|
|
|
2076
2707
|
readonly branchId?: string;
|
|
2077
2708
|
}
|
|
2078
2709
|
|
|
2710
|
+
/**
|
|
2711
|
+
* Make one server-supplied string safe to place in a model's context: strip
|
|
2712
|
+
* invisibles, normalise runaway whitespace, and cap the length with a marker
|
|
2713
|
+
* the model can see.
|
|
2714
|
+
*
|
|
2715
|
+
* The cap is the honest part. It does NOT stop a description from CONTAINING
|
|
2716
|
+
* an instruction — nothing does, short of not showing the model the description
|
|
2717
|
+
* at all, which would make the tool unusable. It stops the description from
|
|
2718
|
+
* being a whole second system prompt, and it stops the invisible encodings that
|
|
2719
|
+
* make an injection unreviewable by a human reading the same string.
|
|
2720
|
+
*/
|
|
2721
|
+
export declare const sanitizeExternalText: (raw: unknown, maxChars: number) => string;
|
|
2722
|
+
|
|
2723
|
+
/**
|
|
2724
|
+
* Recursively bound + clean a server's input JSON Schema.
|
|
2725
|
+
*
|
|
2726
|
+
* Depth and node budget are enforced as we walk (a subtree past either is
|
|
2727
|
+
* dropped, not truncated mid-object), and every `description` / `title` string
|
|
2728
|
+
* anywhere in the tree is sanitized — those are model-visible too, and a
|
|
2729
|
+
* per-property description is the least-inspected place to hide an instruction.
|
|
2730
|
+
* Returns `null` when the schema is unusable, in which case the tool is dropped.
|
|
2731
|
+
*/
|
|
2732
|
+
export declare const sanitizeInputSchema: (schema: unknown, bounds: McpBounds) => Record<string, unknown> | null;
|
|
2733
|
+
|
|
2079
2734
|
/**
|
|
2080
2735
|
* Combine assertion failures + the judge verdict into one pass/fail. Pure. A case
|
|
2081
2736
|
* passes iff no assertion failed AND (no judge, or the judge's score clears the
|
|
@@ -2252,6 +2907,31 @@ export { simulateStreamingMiddleware }
|
|
|
2252
2907
|
*/
|
|
2253
2908
|
export declare const startResumableProducer: (streamId: string, store: ResumableStreamStore, source: () => Stream.Stream<AgentEvent>) => Effect.Effect<void>;
|
|
2254
2909
|
|
|
2910
|
+
/**
|
|
2911
|
+
* MCP over stdio — the transport most published servers ship (`npx
|
|
2912
|
+
* some-mcp-server`).
|
|
2913
|
+
*
|
|
2914
|
+
* The child does NOT inherit `process.env`. That is a deliberate difference
|
|
2915
|
+
* from `child_process.spawn`'s default: an MCP server is a program you are
|
|
2916
|
+
* handing your model's tool surface to, and the default should not also hand
|
|
2917
|
+
* it every credential in the process. Spread what it needs explicitly.
|
|
2918
|
+
*/
|
|
2919
|
+
export declare const stdioMcpTransport: (options: StdioMcpTransportOptions) => McpTransport;
|
|
2920
|
+
|
|
2921
|
+
export declare interface StdioMcpTransportOptions {
|
|
2922
|
+
/** Executable to spawn. App configuration, never model output. */
|
|
2923
|
+
readonly command: string;
|
|
2924
|
+
readonly args?: ReadonlyArray<string>;
|
|
2925
|
+
/** Namespace, for error attribution. */
|
|
2926
|
+
readonly server: string;
|
|
2927
|
+
/** Environment for the child. Pass credentials here, from YOUR env — the
|
|
2928
|
+
* child does NOT inherit the parent environment unless you spread it in,
|
|
2929
|
+
* so a server cannot read a secret you did not hand it. */
|
|
2930
|
+
readonly env?: Record<string, string>;
|
|
2931
|
+
readonly cwd?: string;
|
|
2932
|
+
readonly bounds?: Partial<McpBounds>;
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2255
2935
|
export declare const STREAM_EVENTS_TABLE = "_voltro_stream_events";
|
|
2256
2936
|
|
|
2257
2937
|
export declare const STREAM_STATE_TABLE = "_voltro_stream_state";
|
|
@@ -2368,6 +3048,16 @@ export declare interface SynthesizedTool {
|
|
|
2368
3048
|
readonly write: boolean;
|
|
2369
3049
|
/** Require human confirmation before this call executes. */
|
|
2370
3050
|
readonly confirm: boolean;
|
|
3051
|
+
/**
|
|
3052
|
+
* Whether that confirmation is ENFORCED by the app — i.e. the descriptor also
|
|
3053
|
+
* declares `requiresApproval:`, so the real handler parks the call as a
|
|
3054
|
+
* durable approval instead of running it.
|
|
3055
|
+
*
|
|
3056
|
+
* The inventory reports both flags because they answer different questions: a
|
|
3057
|
+
* confirm-UI asks `confirm`, and "why is this tool not executable" is answered
|
|
3058
|
+
* only by the pair.
|
|
3059
|
+
*/
|
|
3060
|
+
readonly approvalBacked: boolean;
|
|
2371
3061
|
readonly maxPerRun?: number;
|
|
2372
3062
|
}
|
|
2373
3063
|
|
|
@@ -2375,6 +3065,10 @@ export declare interface SynthesizedTool {
|
|
|
2375
3065
|
* The discovery/inventory view: every descriptor that is exposable AND passes
|
|
2376
3066
|
* the policy, as `SynthesizedTool` metadata (no executable body). Use for the
|
|
2377
3067
|
* capability surface / a confirm-UI. Sorted by name.
|
|
3068
|
+
*
|
|
3069
|
+
* Deliberately does NOT apply the `includeWrites` gate — that is an EXECUTION
|
|
3070
|
+
* decision (`appToolDecision`), and an inventory that hid write tools would
|
|
3071
|
+
* make a confirm-UI unable to explain why one is missing.
|
|
2378
3072
|
*/
|
|
2379
3073
|
export declare const synthesizeToolSpecs: (entries: ReadonlyArray<AppToolEntry>, policy?: AppToolPolicy) => ReadonlyArray<SynthesizedTool>;
|
|
2380
3074
|
|
|
@@ -2409,6 +3103,18 @@ export declare interface ToolDef<A, I, O> {
|
|
|
2409
3103
|
readonly name: string;
|
|
2410
3104
|
readonly description: string;
|
|
2411
3105
|
readonly input: Schema.Schema<A, I>;
|
|
3106
|
+
/**
|
|
3107
|
+
* A ready-made JSON Schema for the tool's parameters, used INSTEAD of
|
|
3108
|
+
* rendering `input`.
|
|
3109
|
+
*
|
|
3110
|
+
* For a tool whose shape is defined elsewhere — an external MCP server's
|
|
3111
|
+
* `inputSchema` — there is no faithful Effect Schema to render from, and a
|
|
3112
|
+
* lossy re-expression would reject arguments the far side would have
|
|
3113
|
+
* accepted. So the far side's schema is what the model sees, while `input`
|
|
3114
|
+
* stays the decode gate on the way in. Leave unset for a normal tool; a
|
|
3115
|
+
* rendered `input` is strictly better when you own the shape.
|
|
3116
|
+
*/
|
|
3117
|
+
readonly inputJsonSchema?: Record<string, unknown>;
|
|
2412
3118
|
/** Optional result schema. When set, `buildSdkTools` decodes the body's
|
|
2413
3119
|
* output through it before handing the value back to the model. */
|
|
2414
3120
|
readonly output?: Schema.Schema.Any;
|