modelrig 0.8.1 → 0.10.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 +324 -0
- package/README.md +55 -29
- package/data/PROVENANCE.md +14 -0
- package/data/pricing-map.json +26 -0
- package/dist/adapters/attempt-meta-map.d.ts +8 -1
- package/dist/adapters/attempt-meta-map.d.ts.map +1 -1
- package/dist/adapters/attempt-meta-map.js +4 -0
- package/dist/adapters/build-adapters.js +2 -2
- package/dist/adapters/gemini.d.ts +28 -1
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +38 -9
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +53 -4
- package/dist/adapters/openai-compat.d.ts +8 -2
- package/dist/adapters/openai-compat.d.ts.map +1 -1
- package/dist/adapters/openai-compat.js +8 -1
- package/dist/adapters/openai-stream.d.ts +10 -2
- package/dist/adapters/openai-stream.d.ts.map +1 -1
- package/dist/adapters/openai-stream.js +5 -1
- package/dist/adapters/openai.d.ts +10 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +57 -7
- package/dist/adapters/responses-api.d.ts +61 -0
- package/dist/adapters/responses-api.d.ts.map +1 -0
- package/dist/adapters/responses-api.js +124 -0
- package/dist/artifacts/artifact.d.ts +31 -12
- package/dist/artifacts/artifact.d.ts.map +1 -1
- package/dist/artifacts/artifact.js +218 -188
- package/dist/artifacts/index.d.ts +15 -5
- package/dist/artifacts/index.d.ts.map +1 -1
- package/dist/artifacts/index.js +19 -5
- package/dist/artifacts/run-context.d.ts +3 -24
- package/dist/artifacts/run-context.d.ts.map +1 -1
- package/dist/artifacts/run-context.js +3 -39
- package/dist/bakeoff/engine.d.ts +29 -0
- package/dist/bakeoff/engine.d.ts.map +1 -1
- package/dist/bakeoff/engine.js +47 -0
- package/dist/bakeoff/eval-cases-rig.d.ts +36 -0
- package/dist/bakeoff/eval-cases-rig.d.ts.map +1 -0
- package/dist/bakeoff/eval-cases-rig.js +27 -0
- package/dist/bakeoff/eval-cases.d.ts +19 -0
- package/dist/bakeoff/eval-cases.d.ts.map +1 -1
- package/dist/bakeoff/eval-cases.js +18 -1
- package/dist/bakeoff/golden-judge.d.ts +47 -0
- package/dist/bakeoff/golden-judge.d.ts.map +1 -0
- package/dist/bakeoff/golden-judge.js +49 -0
- package/dist/bakeoff/groundedness.d.ts +58 -0
- package/dist/bakeoff/groundedness.d.ts.map +1 -0
- package/dist/bakeoff/groundedness.js +165 -0
- package/dist/bakeoff/quality.d.ts +43 -0
- package/dist/bakeoff/quality.d.ts.map +1 -1
- package/dist/bakeoff/quality.js +31 -0
- package/dist/bakeoff/replay.d.ts +11 -0
- package/dist/bakeoff/replay.d.ts.map +1 -1
- package/dist/bakeoff/replay.js +12 -0
- package/dist/bakeoff/report.d.ts +7 -0
- package/dist/bakeoff/report.d.ts.map +1 -1
- package/dist/bakeoff/report.js +26 -0
- package/dist/cli-judge.d.ts +12 -0
- package/dist/cli-judge.d.ts.map +1 -0
- package/dist/cli-judge.js +48 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +22 -42
- package/dist/config.d.ts +85 -29
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +109 -47
- package/dist/detect/detector.d.ts +4 -2
- package/dist/detect/detector.d.ts.map +1 -1
- package/dist/detect/safe-harbor.d.ts +57 -0
- package/dist/detect/safe-harbor.d.ts.map +1 -0
- package/dist/detect/safe-harbor.js +278 -0
- package/dist/doctor-cli.d.ts +7 -0
- package/dist/doctor-cli.d.ts.map +1 -0
- package/dist/doctor-cli.js +67 -0
- package/dist/doctor-preflight.d.ts +52 -0
- package/dist/doctor-preflight.d.ts.map +1 -0
- package/dist/doctor-preflight.js +112 -0
- package/dist/feedback/coverage.d.ts +11 -8
- package/dist/feedback/coverage.d.ts.map +1 -1
- package/dist/feedback/coverage.js +29 -16
- package/dist/feedback/grade.d.ts +58 -0
- package/dist/feedback/grade.d.ts.map +1 -0
- package/dist/feedback/grade.js +67 -0
- package/dist/gateway/http-protocol.d.ts +20 -1
- package/dist/gateway/http-protocol.d.ts.map +1 -1
- package/dist/gateway/http-protocol.js +1 -1
- package/dist/gateway/serve-client.d.ts +99 -0
- package/dist/gateway/serve-client.d.ts.map +1 -0
- package/dist/gateway/serve-client.js +360 -0
- package/dist/index.d.ts +68 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +167 -46
- package/dist/init/observe-cli.js +1 -1
- package/dist/init/observe.d.ts +2 -2
- package/dist/init/observe.d.ts.map +1 -1
- package/dist/init/observe.js +11 -7
- package/dist/mask/arm.d.ts +112 -0
- package/dist/mask/arm.d.ts.map +1 -0
- package/dist/mask/arm.js +179 -0
- package/dist/mask/index.d.ts +2 -0
- package/dist/mask/index.d.ts.map +1 -1
- package/dist/mask/index.js +7 -1
- package/dist/mask/mask-policy-cli.d.ts +12 -4
- package/dist/mask/mask-policy-cli.d.ts.map +1 -1
- package/dist/mask/mask-policy-cli.js +34 -9
- package/dist/prompts/manifest.d.ts +128 -0
- package/dist/prompts/manifest.d.ts.map +1 -0
- package/dist/prompts/manifest.js +214 -0
- package/dist/prompts/prompt-fetch.d.ts +84 -0
- package/dist/prompts/prompt-fetch.d.ts.map +1 -0
- package/dist/prompts/prompt-fetch.js +120 -0
- package/dist/prompts/prompt-resolve.d.ts +76 -0
- package/dist/prompts/prompt-resolve.d.ts.map +1 -0
- package/dist/prompts/prompt-resolve.js +93 -0
- package/dist/prompts/slot-resolver.d.ts +91 -0
- package/dist/prompts/slot-resolver.d.ts.map +1 -0
- package/dist/prompts/slot-resolver.js +135 -0
- package/dist/prompts/tools-register.d.ts +60 -0
- package/dist/prompts/tools-register.d.ts.map +1 -0
- package/dist/prompts/tools-register.js +48 -0
- package/dist/prompts/version-hash.d.ts +29 -0
- package/dist/prompts/version-hash.d.ts.map +1 -0
- package/dist/prompts/version-hash.js +54 -0
- package/dist/prompts-export-cli.d.ts +21 -0
- package/dist/prompts-export-cli.d.ts.map +1 -0
- package/dist/prompts-export-cli.js +130 -0
- package/dist/query/grammar.d.ts +2 -2
- package/dist/query/grammar.d.ts.map +1 -1
- package/dist/query/grammar.js +3 -1
- package/dist/query/saved.d.ts +13 -0
- package/dist/query/saved.d.ts.map +1 -1
- package/dist/query/saved.js +67 -1
- package/dist/receipts/types.d.ts +1 -1
- package/dist/receipts/types.d.ts.map +1 -1
- package/dist/receipts/types.js +1 -0
- package/dist/registry/build.d.ts +43 -1
- package/dist/registry/build.d.ts.map +1 -1
- package/dist/registry/build.js +52 -0
- package/dist/registry/cache-policy.d.ts.map +1 -1
- package/dist/registry/cache-policy.js +22 -0
- package/dist/registry/pricing-shape.d.ts +67 -8
- package/dist/registry/pricing-shape.d.ts.map +1 -1
- package/dist/registry/pricing-shape.js +50 -13
- package/dist/registry/pricing.d.ts +6 -5
- package/dist/registry/pricing.d.ts.map +1 -1
- package/dist/registry/pricing.js +9 -5
- package/dist/registry/rows.d.ts.map +1 -1
- package/dist/registry/rows.js +1 -0
- package/dist/registry/select.d.ts +6 -1
- package/dist/registry/select.d.ts.map +1 -1
- package/dist/registry/select.js +11 -0
- package/dist/routes/call-config.d.ts +199 -0
- package/dist/routes/call-config.d.ts.map +1 -0
- package/dist/routes/call-config.js +343 -0
- package/dist/routes/destination-loader.d.ts +16 -0
- package/dist/routes/destination-loader.d.ts.map +1 -0
- package/dist/routes/destination-loader.js +29 -0
- package/dist/routes/draft.d.ts +7 -0
- package/dist/routes/draft.d.ts.map +1 -1
- package/dist/routes/draft.js +12 -0
- package/dist/routes/loader.d.ts.map +1 -1
- package/dist/routes/loader.js +31 -56
- package/dist/routes/objective-loader.d.ts +12 -0
- package/dist/routes/objective-loader.d.ts.map +1 -1
- package/dist/routes/objective-loader.js +82 -0
- package/dist/routes/output-loader.d.ts +42 -0
- package/dist/routes/output-loader.d.ts.map +1 -0
- package/dist/routes/output-loader.js +112 -0
- package/dist/routes/prompt-loader.d.ts +49 -0
- package/dist/routes/prompt-loader.d.ts.map +1 -0
- package/dist/routes/prompt-loader.js +113 -0
- package/dist/routes/resolved-bundle.d.ts +69 -0
- package/dist/routes/resolved-bundle.d.ts.map +1 -1
- package/dist/routes/resolved-bundle.js +76 -0
- package/dist/routes/screen-loader.d.ts +29 -1
- package/dist/routes/screen-loader.d.ts.map +1 -1
- package/dist/routes/screen-loader.js +59 -1
- package/dist/run/accept.d.ts +5 -0
- package/dist/run/accept.d.ts.map +1 -1
- package/dist/run/accept.js +2 -0
- package/dist/run/cost.d.ts +31 -2
- package/dist/run/cost.d.ts.map +1 -1
- package/dist/run/cost.js +57 -2
- package/dist/run/destination.d.ts +39 -0
- package/dist/run/destination.d.ts.map +1 -0
- package/dist/run/destination.js +45 -0
- package/dist/run/dispatch.d.ts +67 -16
- package/dist/run/dispatch.d.ts.map +1 -1
- package/dist/run/dispatch.js +133 -29
- package/dist/run/grounding.d.ts +28 -2
- package/dist/run/grounding.d.ts.map +1 -1
- package/dist/run/grounding.js +54 -10
- package/dist/run/raw-defaults.d.ts.map +1 -1
- package/dist/run/raw-defaults.js +6 -1
- package/dist/run/raw-finalize.d.ts +50 -0
- package/dist/run/raw-finalize.d.ts.map +1 -0
- package/dist/run/raw-finalize.js +266 -0
- package/dist/run/raw-knobs.d.ts +36 -0
- package/dist/run/raw-knobs.d.ts.map +1 -0
- package/dist/run/raw-knobs.js +99 -0
- package/dist/run/raw-request.d.ts +24 -0
- package/dist/run/raw-request.d.ts.map +1 -0
- package/dist/run/raw-request.js +69 -0
- package/dist/run/raw-schema.d.ts +76 -0
- package/dist/run/raw-schema.d.ts.map +1 -0
- package/dist/run/raw-schema.js +166 -0
- package/dist/run/raw-search-cost.d.ts +48 -0
- package/dist/run/raw-search-cost.d.ts.map +1 -0
- package/dist/run/raw-search-cost.js +78 -0
- package/dist/run/raw-support.d.ts +64 -6
- package/dist/run/raw-support.d.ts.map +1 -1
- package/dist/run/raw-support.js +84 -11
- package/dist/run/raw.d.ts +25 -26
- package/dist/run/raw.d.ts.map +1 -1
- package/dist/run/raw.js +30 -351
- package/dist/run/record-attempt.d.ts +12 -0
- package/dist/run/record-attempt.d.ts.map +1 -1
- package/dist/run/record-attempt.js +30 -12
- package/dist/run/repair-attempt.d.ts +68 -0
- package/dist/run/repair-attempt.d.ts.map +1 -0
- package/dist/run/repair-attempt.js +180 -0
- package/dist/run/resolve-destination.d.ts +49 -0
- package/dist/run/resolve-destination.d.ts.map +1 -0
- package/dist/run/resolve-destination.js +76 -0
- package/dist/run/routing-docs.d.ts.map +1 -1
- package/dist/run/routing-docs.js +10 -5
- package/dist/run/run.d.ts +30 -1
- package/dist/run/run.d.ts.map +1 -1
- package/dist/run/run.js +94 -172
- package/dist/run/telemetry-row.d.ts +92 -0
- package/dist/run/telemetry-row.d.ts.map +1 -0
- package/dist/run/telemetry-row.js +81 -0
- package/dist/run/validate.d.ts +12 -1
- package/dist/run/validate.d.ts.map +1 -1
- package/dist/run/validate.js +31 -0
- package/dist/run/web-search-pricing.d.ts +43 -0
- package/dist/run/web-search-pricing.d.ts.map +1 -0
- package/dist/run/web-search-pricing.js +56 -0
- package/dist/savings/odometer.d.ts +4 -0
- package/dist/savings/odometer.d.ts.map +1 -1
- package/dist/savings/odometer.js +20 -1
- package/dist/savings/swap-execute.d.ts +7 -0
- package/dist/savings/swap-execute.d.ts.map +1 -1
- package/dist/savings/swap-execute.js +30 -0
- package/dist/savings/swaps-client.d.ts +10 -0
- package/dist/savings/swaps-client.d.ts.map +1 -1
- package/dist/screen/fixtures/probe-corpora.d.ts +23 -0
- package/dist/screen/fixtures/probe-corpora.d.ts.map +1 -0
- package/dist/screen/fixtures/probe-corpora.js +69 -0
- package/dist/screen/index.d.ts +21 -0
- package/dist/screen/index.d.ts.map +1 -0
- package/dist/screen/index.js +64 -0
- package/dist/screen/model-screen.d.ts +111 -0
- package/dist/screen/model-screen.d.ts.map +1 -0
- package/dist/screen/model-screen.js +178 -0
- package/dist/screen/patterns.d.ts +51 -0
- package/dist/screen/patterns.d.ts.map +1 -0
- package/dist/screen/patterns.js +151 -0
- package/dist/screen/phi-gate.d.ts +52 -0
- package/dist/screen/phi-gate.d.ts.map +1 -0
- package/dist/screen/phi-gate.js +86 -0
- package/dist/screen/policy.d.ts +71 -0
- package/dist/screen/policy.d.ts.map +1 -0
- package/dist/screen/policy.js +334 -0
- package/dist/screen/probe.d.ts +118 -0
- package/dist/screen/probe.d.ts.map +1 -0
- package/dist/screen/probe.js +132 -0
- package/dist/screen/screen-cli.d.ts +16 -0
- package/dist/screen/screen-cli.d.ts.map +1 -0
- package/dist/screen/screen-cli.js +178 -0
- package/dist/screen/screen.d.ts +99 -0
- package/dist/screen/screen.d.ts.map +1 -0
- package/dist/screen/screen.js +0 -0
- package/dist/screen/sidecar.d.ts +69 -0
- package/dist/screen/sidecar.d.ts.map +1 -0
- package/dist/screen/sidecar.js +145 -0
- package/dist/screen/types.d.ts +43 -2
- package/dist/screen/types.d.ts.map +1 -1
- package/dist/settings/api-fetcher.d.ts +14 -3
- package/dist/settings/api-fetcher.d.ts.map +1 -1
- package/dist/settings/api-fetcher.js +61 -10
- package/dist/settings/grading-guide.d.ts +102 -0
- package/dist/settings/grading-guide.d.ts.map +1 -0
- package/dist/settings/grading-guide.js +23 -0
- package/dist/settings/index.d.ts +3 -2
- package/dist/settings/index.d.ts.map +1 -1
- package/dist/settings/index.js +2 -1
- package/dist/settings/task-settings.d.ts +44 -7
- package/dist/settings/task-settings.d.ts.map +1 -1
- package/dist/settings/task-settings.js +53 -12
- package/dist/status.d.ts +32 -20
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +51 -30
- package/dist/telemetry/artifact-store.d.ts +5 -6
- package/dist/telemetry/artifact-store.d.ts.map +1 -1
- package/dist/telemetry/artifact-store.js +21 -24
- package/dist/telemetry/attempt-meta.d.ts +76 -1
- package/dist/telemetry/attempt-meta.d.ts.map +1 -1
- package/dist/telemetry/attempt-meta.js +33 -1
- package/dist/telemetry/exporter-setup.d.ts +2 -0
- package/dist/telemetry/exporter-setup.d.ts.map +1 -1
- package/dist/telemetry/exporter-setup.js +6 -1
- package/dist/telemetry/exporter.d.ts +2 -2
- package/dist/telemetry/exporter.d.ts.map +1 -1
- package/dist/telemetry/exporter.js +22 -41
- package/dist/telemetry/grade-mappers.d.ts +25 -0
- package/dist/telemetry/grade-mappers.d.ts.map +1 -0
- package/dist/telemetry/grade-mappers.js +64 -0
- package/dist/telemetry/grades-sql.d.ts +56 -0
- package/dist/telemetry/grades-sql.d.ts.map +1 -0
- package/dist/telemetry/grades-sql.js +156 -0
- package/dist/telemetry/ingest-protocol.d.ts +55 -48
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
- package/dist/telemetry/ingest-protocol.js +48 -83
- package/dist/telemetry/org-identity.d.ts +44 -0
- package/dist/telemetry/org-identity.d.ts.map +1 -0
- package/dist/telemetry/org-identity.js +98 -0
- package/dist/telemetry/route-registration.d.ts +48 -0
- package/dist/telemetry/route-registration.d.ts.map +1 -0
- package/dist/telemetry/route-registration.js +77 -0
- package/dist/telemetry/sinks.d.ts +10 -1
- package/dist/telemetry/sinks.d.ts.map +1 -1
- package/dist/telemetry/sinks.js +52 -6
- package/dist/telemetry/store-migrations.d.ts.map +1 -1
- package/dist/telemetry/store-migrations.js +64 -0
- package/dist/telemetry/store.d.ts +9 -6
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/store.js +8 -50
- package/dist/tenancy/api-key-format.d.ts +45 -1
- package/dist/tenancy/api-key-format.d.ts.map +1 -1
- package/dist/tenancy/api-key-format.js +50 -1
- package/dist/types/provider.d.ts +23 -3
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/provider.js +26 -0
- package/dist/types/route.d.ts +154 -2
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +73 -48
- package/dist/types/telemetry.d.ts.map +1 -1
- package/dist/validate-cli.d.ts.map +1 -1
- package/dist/validate-cli.js +32 -0
- package/dist/verify/verify.d.ts +5 -0
- package/dist/verify/verify.d.ts.map +1 -1
- package/dist/verify/verify.js +2 -0
- package/dist/watch/evaluate.d.ts +84 -1
- package/dist/watch/evaluate.d.ts.map +1 -1
- package/dist/watch/evaluate.js +28 -0
- package/dist/watch/monitor.d.ts +8 -2
- package/dist/watch/monitor.d.ts.map +1 -1
- package/dist/watch/monitor.js +13 -5
- package/dist/watch/regret-monitor-cli.d.ts +16 -0
- package/dist/watch/regret-monitor-cli.d.ts.map +1 -0
- package/dist/watch/regret-monitor-cli.js +137 -0
- package/dist/watch/regret-monitor.d.ts +168 -0
- package/dist/watch/regret-monitor.d.ts.map +1 -0
- package/dist/watch/regret-monitor.js +304 -0
- package/dist/watch/upgrade.d.ts +62 -0
- package/dist/watch/upgrade.d.ts.map +1 -0
- package/dist/watch/upgrade.js +57 -0
- package/docs/agent-skill/SKILL.md +181 -9
- package/docs/how-it-fits.md +3 -2
- package/docs/instrumentation.md +219 -46
- package/docs/llms.txt +158 -35
- package/docs/managed-serving.md +150 -0
- package/docs/mcp-oracle.md +322 -0
- package/docs/migration-playbook.md +5 -4
- package/docs/quickstart.md +263 -89
- package/package.json +18 -4
- package/registry/leaderboard.json +74 -0
- package/registry/registry.json +232 -112
- package/screen.d.ts +16 -0
- package/templates/call-disposition-qa.json +77 -0
- package/templates/catalog-cleansing.json +77 -0
- package/templates/compliance-review.json +76 -0
- package/templates/cs-next-action.json +76 -0
- package/templates/document-extraction.json +78 -0
- package/templates/financial-classification.json +75 -0
- package/templates/lead-qualification.json +76 -0
- package/templates/medical-classification.json +71 -0
- package/templates/ticket-triage.json +76 -0
- package/dist/feedback/feedback.d.ts +0 -35
- package/dist/feedback/feedback.d.ts.map +0 -1
- package/dist/feedback/feedback.js +0 -39
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PSR-1 — the assembly-manifest model (spec §2.1, authoring-guide §2–§3).
|
|
4
|
+
*
|
|
5
|
+
* A prompt in ModelRig is TWO things: the authored `template` text and the
|
|
6
|
+
* `manifest` — the ordered recipe for assembling the final prompt at call time.
|
|
7
|
+
* This module is the **structure-only** manifest: its `Slot[]` records HOW each
|
|
8
|
+
* part of the prompt is filled, never the filled value (the privacy guarantee —
|
|
9
|
+
* a `dynamic·tool` slot names `search_tickets`, never what it returned).
|
|
10
|
+
*
|
|
11
|
+
* PSR-1 is ZERO-DDL and validate-only: nothing here serves or stores a version.
|
|
12
|
+
* It exists to prove the model against real prompts (the InferWealth inference)
|
|
13
|
+
* and a hand-authored doc (ADJ-1) before PSR-2 builds the `prompt_versions`
|
|
14
|
+
* store. Pure + dependency-free (no YAML dep — the authoring doc is accepted as
|
|
15
|
+
* JSON per §7; YAML is an optional future add).
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.validateManifest = validateManifest;
|
|
19
|
+
exports.assertValidManifest = assertValidManifest;
|
|
20
|
+
exports.normalizeManifest = normalizeManifest;
|
|
21
|
+
exports.parseAuthoringDoc = parseAuthoringDoc;
|
|
22
|
+
const DYNAMIC_KINDS = new Set([
|
|
23
|
+
"tool",
|
|
24
|
+
"cache",
|
|
25
|
+
"upstream",
|
|
26
|
+
"variable",
|
|
27
|
+
]);
|
|
28
|
+
const CACHE_POLICIES = new Set(["prefix", "none"]);
|
|
29
|
+
const STATIC_FROMS = new Set(["system", "template"]);
|
|
30
|
+
function isRecord(v) {
|
|
31
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
32
|
+
}
|
|
33
|
+
function isNonEmptyString(v) {
|
|
34
|
+
return typeof v === "string" && v.length > 0;
|
|
35
|
+
}
|
|
36
|
+
/** Validate an untrusted value as a {@link Manifest} (spec §4). Rejects: an
|
|
37
|
+
* unknown slot kind, a `static` slot with BOTH or NEITHER `text`/`from`, a
|
|
38
|
+
* `dynamic` slot missing its `source` (or with a bad source kind / empty ref /
|
|
39
|
+
* bad cache_policy), a slot carrying fields of the wrong kind, and duplicate
|
|
40
|
+
* slot ids. Never throws — returns a result. */
|
|
41
|
+
function validateManifest(input) {
|
|
42
|
+
const errors = [];
|
|
43
|
+
if (!isRecord(input)) {
|
|
44
|
+
return { ok: false, errors: ["manifest must be an object with a `slots` array"] };
|
|
45
|
+
}
|
|
46
|
+
const slots = input["slots"];
|
|
47
|
+
if (!Array.isArray(slots)) {
|
|
48
|
+
return { ok: false, errors: ["manifest.slots must be an array"] };
|
|
49
|
+
}
|
|
50
|
+
const seenIds = new Set();
|
|
51
|
+
slots.forEach((raw, i) => {
|
|
52
|
+
const at = `slots[${i}]`;
|
|
53
|
+
if (!isRecord(raw)) {
|
|
54
|
+
errors.push(`${at} must be an object`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const id = raw["id"];
|
|
58
|
+
if (!isNonEmptyString(id)) {
|
|
59
|
+
errors.push(`${at}.id must be a non-empty string`);
|
|
60
|
+
}
|
|
61
|
+
else if (seenIds.has(id)) {
|
|
62
|
+
errors.push(`${at}.id "${id}" is a duplicate — slot ids must be unique`);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
seenIds.add(id);
|
|
66
|
+
}
|
|
67
|
+
const kind = raw["kind"];
|
|
68
|
+
const hasText = raw["text"] !== undefined;
|
|
69
|
+
const hasFrom = raw["from"] !== undefined;
|
|
70
|
+
const hasSource = raw["source"] !== undefined;
|
|
71
|
+
if (kind === "static") {
|
|
72
|
+
if (hasSource)
|
|
73
|
+
errors.push(`${at} is static but carries a dynamic \`source\``);
|
|
74
|
+
if (hasText && hasFrom) {
|
|
75
|
+
errors.push(`${at} static slot has BOTH \`text\` and \`from\` — exactly one is allowed`);
|
|
76
|
+
}
|
|
77
|
+
else if (!hasText && !hasFrom) {
|
|
78
|
+
errors.push(`${at} static slot has NEITHER \`text\` nor \`from\` — exactly one is required`);
|
|
79
|
+
}
|
|
80
|
+
else if (hasText && typeof raw["text"] !== "string") {
|
|
81
|
+
errors.push(`${at}.text must be a string`);
|
|
82
|
+
}
|
|
83
|
+
else if (hasFrom && !STATIC_FROMS.has(raw["from"])) {
|
|
84
|
+
errors.push(`${at}.from must be one of system, template`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (kind === "dynamic") {
|
|
88
|
+
if (hasText || hasFrom) {
|
|
89
|
+
errors.push(`${at} is dynamic but carries static \`text\`/\`from\``);
|
|
90
|
+
}
|
|
91
|
+
if (!hasSource) {
|
|
92
|
+
errors.push(`${at} dynamic slot is missing its \`source\``);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
validateSource(raw["source"], at, errors);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
errors.push(`${at}.kind must be one of static, dynamic (got ${JSON.stringify(kind)})`);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
if (errors.length > 0)
|
|
103
|
+
return { ok: false, errors };
|
|
104
|
+
return { ok: true, manifest: { slots: slots } };
|
|
105
|
+
}
|
|
106
|
+
function validateSource(source, at, errors) {
|
|
107
|
+
if (!isRecord(source)) {
|
|
108
|
+
errors.push(`${at}.source must be an object`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!DYNAMIC_KINDS.has(source["kind"])) {
|
|
112
|
+
errors.push(`${at}.source.kind must be one of tool, cache, upstream, variable`);
|
|
113
|
+
}
|
|
114
|
+
if (!isNonEmptyString(source["ref"])) {
|
|
115
|
+
errors.push(`${at}.source.ref must be a non-empty string`);
|
|
116
|
+
}
|
|
117
|
+
if (source["cache_policy"] !== undefined && !CACHE_POLICIES.has(source["cache_policy"])) {
|
|
118
|
+
errors.push(`${at}.source.cache_policy must be one of prefix, none`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Throwing form of {@link validateManifest} — returns the narrowed manifest or
|
|
122
|
+
* throws with the joined error list. Use where an invalid manifest is a bug the
|
|
123
|
+
* caller wants surfaced (tests, the emit path's guard). */
|
|
124
|
+
function assertValidManifest(input) {
|
|
125
|
+
const result = validateManifest(input);
|
|
126
|
+
if (!result.ok) {
|
|
127
|
+
throw new Error(`invalid manifest: ${result.errors.join("; ")}`);
|
|
128
|
+
}
|
|
129
|
+
return result.manifest;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Reduce a validated {@link Manifest} to ITS CANONICAL SHAPE — the ONE clean
|
|
133
|
+
* projection PSR-2 persists (spec §4 "re-validate + normalize on ingest").
|
|
134
|
+
*
|
|
135
|
+
* WHY (a PSR-1 QA finding): {@link validateManifest} NARROWS an untrusted value
|
|
136
|
+
* to `Manifest` but casts `slots as Slot[]` — it does NOT strip unknown keys, so
|
|
137
|
+
* a raw artifact blob can carry extra fields (`text` alongside a `from`, a stray
|
|
138
|
+
* `note`, a `source` on a static slot) that pass validation and would be stored
|
|
139
|
+
* verbatim. The ingest path (prompt-ingest.ts) must NEVER trust the artifact blob
|
|
140
|
+
* as-is: it re-validates AND normalizes here so `template_sha` (computed over the
|
|
141
|
+
* normalized manifest) is stable — identical content always dedupes to one row,
|
|
142
|
+
* regardless of incidental key noise in the source artifact.
|
|
143
|
+
*
|
|
144
|
+
* Each slot is rebuilt from scratch keeping ONLY the canonical keys:
|
|
145
|
+
* static → { id, kind, text } XOR { id, kind, from }
|
|
146
|
+
* dynamic → { id, kind, source: { kind, ref, cache_policy? } }
|
|
147
|
+
* Slot order is preserved (order = send order, D6). The rebuilt manifest is
|
|
148
|
+
* re-validated ({@link assertValidManifest}) so a caller that skipped validation
|
|
149
|
+
* still cannot persist a malformed shape.
|
|
150
|
+
*/
|
|
151
|
+
function normalizeManifest(input) {
|
|
152
|
+
const slots = input.slots.map((slot) => {
|
|
153
|
+
if (slot.kind === "static") {
|
|
154
|
+
// text XOR from — validation guarantees exactly one is present.
|
|
155
|
+
if (slot.text !== undefined) {
|
|
156
|
+
return { id: slot.id, kind: "static", text: slot.text };
|
|
157
|
+
}
|
|
158
|
+
return { id: slot.id, kind: "static", from: slot.from };
|
|
159
|
+
}
|
|
160
|
+
const src = slot.source;
|
|
161
|
+
const source = src.cache_policy !== undefined
|
|
162
|
+
? { kind: src.kind, ref: src.ref, cache_policy: src.cache_policy }
|
|
163
|
+
: { kind: src.kind, ref: src.ref };
|
|
164
|
+
return { id: slot.id, kind: "dynamic", source };
|
|
165
|
+
});
|
|
166
|
+
return assertValidManifest({ slots });
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* ADJ-1 — the "author from scratch" on-ramp. Parse a declared authoring doc
|
|
170
|
+
* (authoring-guide §3) into a validated {@link Manifest}. Accepts the doc as a
|
|
171
|
+
* JSON string OR an already-parsed object (§7: the MCP tool takes JSON; YAML is
|
|
172
|
+
* an optional future add and is NOT parsed here — a YAML string throws a
|
|
173
|
+
* teaching error). Beyond {@link validateManifest} it cross-checks that every
|
|
174
|
+
* `static` slot's `from: system|template` resolves to a present top-level field,
|
|
175
|
+
* so an author cannot reference authored text the doc does not carry.
|
|
176
|
+
*
|
|
177
|
+
* Structure-only: the returned manifest's slots are the authored slots verbatim
|
|
178
|
+
* (a `from`-slot keeps its `from`; the authored text is NOT inlined), so
|
|
179
|
+
* `parseAuthoringDoc` round-trips a hand-authored doc.
|
|
180
|
+
*/
|
|
181
|
+
function parseAuthoringDoc(input) {
|
|
182
|
+
let doc = input;
|
|
183
|
+
if (typeof input === "string") {
|
|
184
|
+
try {
|
|
185
|
+
doc = JSON.parse(input);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
throw new Error("authoring doc must be JSON (YAML is not parsed here — pass JSON per authoring-guide §7): " +
|
|
189
|
+
(err instanceof Error ? err.message : String(err)));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!isRecord(doc)) {
|
|
193
|
+
throw new Error("authoring doc must be an object");
|
|
194
|
+
}
|
|
195
|
+
const manifestBlock = doc["manifest"];
|
|
196
|
+
if (!isRecord(manifestBlock)) {
|
|
197
|
+
throw new Error("authoring doc is missing a `manifest` block");
|
|
198
|
+
}
|
|
199
|
+
const manifest = assertValidManifest(manifestBlock);
|
|
200
|
+
// ADJ-1 cross-check: a `from` slot must reference a present authored field.
|
|
201
|
+
const hasSystem = typeof doc["system"] === "string";
|
|
202
|
+
const hasTemplate = typeof doc["template"] === "string";
|
|
203
|
+
const missing = [];
|
|
204
|
+
for (const slot of manifest.slots) {
|
|
205
|
+
if (slot.from === "system" && !hasSystem)
|
|
206
|
+
missing.push(`slot "${slot.id}" references \`system\``);
|
|
207
|
+
if (slot.from === "template" && !hasTemplate)
|
|
208
|
+
missing.push(`slot "${slot.id}" references \`template\``);
|
|
209
|
+
}
|
|
210
|
+
if (missing.length > 0) {
|
|
211
|
+
throw new Error(`authoring doc has \`from\` slots with no matching authored field: ${missing.join("; ")}`);
|
|
212
|
+
}
|
|
213
|
+
return manifest;
|
|
214
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PSR-2b — SDK fetch-by-label + the content-addressed prompt cache (spec §3
|
|
3
|
+
* "SDK read", §4). The remote half of serving a prompt by its movable `prod`
|
|
4
|
+
* label.
|
|
5
|
+
*
|
|
6
|
+
* The gateway already serves the promoted version's template+manifest through
|
|
7
|
+
* `routes.bundle` (PSR-2a's write-through). This module adds the OPTIONAL
|
|
8
|
+
* explicit read the SDK uses when it wants a prompt by label directly:
|
|
9
|
+
* GET /v1/prompts/:route?label=prod (key-auth, org-scoped)
|
|
10
|
+
* → { version, template, manifest, template_sha }
|
|
11
|
+
* ETag: <template_sha> (304 Not Modified when it matches)
|
|
12
|
+
*
|
|
13
|
+
* The fetcher mirrors `call-config.ts`'s leak discipline: header-only auth, no
|
|
14
|
+
* body/URL in an error (C2), and it NEVER throws — a network/HTTP failure
|
|
15
|
+
* resolves `null`, a 304 resolves `"not-modified"`, a malformed 200 resolves
|
|
16
|
+
* `null`. So the fail-open resolver ({@link ./prompt-resolve}) always stays
|
|
17
|
+
* fail-open around it.
|
|
18
|
+
*
|
|
19
|
+
* The cache is CONTENT-ADDRESSED, keyed `label+template_sha` (spec §3): the
|
|
20
|
+
* same served content is one entry regardless of how many times it is fetched,
|
|
21
|
+
* and a per-(route,label) pointer records the last-known-good sha — the tier the
|
|
22
|
+
* resolver falls back to when the control plane is unreachable.
|
|
23
|
+
*/
|
|
24
|
+
import { type Manifest } from "./manifest";
|
|
25
|
+
/** A prompt served by label — the explicit endpoint's body and the unit the
|
|
26
|
+
* client cache holds. `template_sha` is both the content id and the ETag. */
|
|
27
|
+
export interface ServedPrompt {
|
|
28
|
+
readonly version: number;
|
|
29
|
+
readonly template: string;
|
|
30
|
+
readonly manifest: Manifest;
|
|
31
|
+
readonly template_sha: string;
|
|
32
|
+
}
|
|
33
|
+
/** Pulls one route's prompt for a label, ETag-aware. Returns the prompt, `"not-
|
|
34
|
+
* modified"` (304 — keep the cache), or `null` (miss/error — the resolver keeps
|
|
35
|
+
* last-known-good). NEVER throws. */
|
|
36
|
+
export interface PromptFetcher {
|
|
37
|
+
fetch(route: string, label: string, etag: string | undefined): Promise<ServedPrompt | "not-modified" | null>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Narrow an untrusted endpoint body into a {@link ServedPrompt}, or null when the
|
|
41
|
+
* shape is not what the endpoint promises (a malformed 200 is a miss, never
|
|
42
|
+
* trusted — we never fabricate a served prompt from junk). The manifest is
|
|
43
|
+
* validated with the shared {@link validateManifest} so a structurally-broken
|
|
44
|
+
* manifest can never reach the client-fill engine.
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseServedPrompt(body: unknown): ServedPrompt | null;
|
|
47
|
+
/**
|
|
48
|
+
* The content-addressed prompt cache (spec §3). Holds served content keyed
|
|
49
|
+
* `label+template_sha`, plus a per-(route,label) pointer to the last-known-good
|
|
50
|
+
* sha. `put` stores the content AND advances the pointer (a fetched version is
|
|
51
|
+
* now the last-known-good); `lastKnown` reads the current content for a label —
|
|
52
|
+
* the fail-open tier the resolver serves when a fetch cannot complete.
|
|
53
|
+
*/
|
|
54
|
+
export interface PromptCache {
|
|
55
|
+
/** The served content for an exact (route,label,sha), if held. */
|
|
56
|
+
get(route: string, label: string, sha: string): ServedPrompt | undefined;
|
|
57
|
+
/** The last-known-good served content for (route,label), if any has landed. */
|
|
58
|
+
lastKnown(route: string, label: string): ServedPrompt | undefined;
|
|
59
|
+
/** The last-known-good sha for (route,label) — the ETag to revalidate with. */
|
|
60
|
+
lastKnownSha(route: string, label: string): string | undefined;
|
|
61
|
+
/** Store content and advance the last-known-good pointer to its sha. */
|
|
62
|
+
put(route: string, label: string, prompt: ServedPrompt): void;
|
|
63
|
+
}
|
|
64
|
+
/** Build an empty {@link PromptCache}. One per rig. In-memory only — a prompt
|
|
65
|
+
* cache is a per-process convenience, never durable state. */
|
|
66
|
+
export declare function createPromptCache(): PromptCache;
|
|
67
|
+
export interface ApiPromptFetcherConfig {
|
|
68
|
+
/** Base URL of the hosted control plane — the SAME `ingestUrl` the api sink
|
|
69
|
+
* and call-config fetcher use (the prompt path hangs off the host). */
|
|
70
|
+
readonly ingestUrl: string;
|
|
71
|
+
/** `rig_sk_…` key. A read rides the readable route + prompt rows; header only,
|
|
72
|
+
* never a URL (C2). */
|
|
73
|
+
readonly apiKey: string;
|
|
74
|
+
readonly fetchFn?: typeof fetch;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The api-mode prompt fetcher: one authenticated, ETag-aware GET per (route,
|
|
78
|
+
* label). Mirrors the api sink's leak discipline (status only, never the
|
|
79
|
+
* body/URL in an error — C2). NEVER throws: a network/HTTP failure resolves
|
|
80
|
+
* null, a 304 resolves "not-modified", a malformed 200 resolves null — so the
|
|
81
|
+
* resolver stays fail-open.
|
|
82
|
+
*/
|
|
83
|
+
export declare function createApiPromptFetcher(config: ApiPromptFetcherConfig): PromptFetcher;
|
|
84
|
+
//# sourceMappingURL=prompt-fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-fetch.d.ts","sourceRoot":"","sources":["../../src/prompts/prompt-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE7D;6EAC6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;qCAEqC;AACrC,MAAM,WAAW,aAAa;IAC5B,KAAK,CACH,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,OAAO,CAAC,YAAY,GAAG,cAAc,GAAG,IAAI,CAAC,CAAC;CAClD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAcpE;AAYD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IACzE,+EAA+E;IAC/E,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAClE,+EAA+E;IAC/E,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC/D,wEAAwE;IACxE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;CAC/D;AAED;8DAC8D;AAC9D,wBAAgB,iBAAiB,IAAI,WAAW,CAmB/C;AAED,MAAM,WAAW,sBAAsB;IACrC;2EACuE;IACvE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;2BACuB;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,aAAa,CA4BpF"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PSR-2b — SDK fetch-by-label + the content-addressed prompt cache (spec §3
|
|
4
|
+
* "SDK read", §4). The remote half of serving a prompt by its movable `prod`
|
|
5
|
+
* label.
|
|
6
|
+
*
|
|
7
|
+
* The gateway already serves the promoted version's template+manifest through
|
|
8
|
+
* `routes.bundle` (PSR-2a's write-through). This module adds the OPTIONAL
|
|
9
|
+
* explicit read the SDK uses when it wants a prompt by label directly:
|
|
10
|
+
* GET /v1/prompts/:route?label=prod (key-auth, org-scoped)
|
|
11
|
+
* → { version, template, manifest, template_sha }
|
|
12
|
+
* ETag: <template_sha> (304 Not Modified when it matches)
|
|
13
|
+
*
|
|
14
|
+
* The fetcher mirrors `call-config.ts`'s leak discipline: header-only auth, no
|
|
15
|
+
* body/URL in an error (C2), and it NEVER throws — a network/HTTP failure
|
|
16
|
+
* resolves `null`, a 304 resolves `"not-modified"`, a malformed 200 resolves
|
|
17
|
+
* `null`. So the fail-open resolver ({@link ./prompt-resolve}) always stays
|
|
18
|
+
* fail-open around it.
|
|
19
|
+
*
|
|
20
|
+
* The cache is CONTENT-ADDRESSED, keyed `label+template_sha` (spec §3): the
|
|
21
|
+
* same served content is one entry regardless of how many times it is fetched,
|
|
22
|
+
* and a per-(route,label) pointer records the last-known-good sha — the tier the
|
|
23
|
+
* resolver falls back to when the control plane is unreachable.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.parseServedPrompt = parseServedPrompt;
|
|
27
|
+
exports.createPromptCache = createPromptCache;
|
|
28
|
+
exports.createApiPromptFetcher = createApiPromptFetcher;
|
|
29
|
+
const manifest_1 = require("./manifest");
|
|
30
|
+
/**
|
|
31
|
+
* Narrow an untrusted endpoint body into a {@link ServedPrompt}, or null when the
|
|
32
|
+
* shape is not what the endpoint promises (a malformed 200 is a miss, never
|
|
33
|
+
* trusted — we never fabricate a served prompt from junk). The manifest is
|
|
34
|
+
* validated with the shared {@link validateManifest} so a structurally-broken
|
|
35
|
+
* manifest can never reach the client-fill engine.
|
|
36
|
+
*/
|
|
37
|
+
function parseServedPrompt(body) {
|
|
38
|
+
if (typeof body !== "object" || body === null || Array.isArray(body))
|
|
39
|
+
return null;
|
|
40
|
+
const b = body;
|
|
41
|
+
if (typeof b.version !== "number" || !Number.isInteger(b.version))
|
|
42
|
+
return null;
|
|
43
|
+
if (typeof b.template !== "string")
|
|
44
|
+
return null;
|
|
45
|
+
if (typeof b.template_sha !== "string" || b.template_sha === "")
|
|
46
|
+
return null;
|
|
47
|
+
const validation = (0, manifest_1.validateManifest)(b.manifest);
|
|
48
|
+
if (!validation.ok)
|
|
49
|
+
return null;
|
|
50
|
+
return {
|
|
51
|
+
version: b.version,
|
|
52
|
+
template: b.template,
|
|
53
|
+
manifest: validation.manifest,
|
|
54
|
+
template_sha: b.template_sha,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const KEY_SEP = "\0"; // NUL — cannot appear in a route/label/sha identifier.
|
|
58
|
+
function contentKey(route, label, sha) {
|
|
59
|
+
return `${route}${KEY_SEP}${label}${KEY_SEP}${sha}`;
|
|
60
|
+
}
|
|
61
|
+
function pointerKey(route, label) {
|
|
62
|
+
return `${route}${KEY_SEP}${label}`;
|
|
63
|
+
}
|
|
64
|
+
/** Build an empty {@link PromptCache}. One per rig. In-memory only — a prompt
|
|
65
|
+
* cache is a per-process convenience, never durable state. */
|
|
66
|
+
function createPromptCache() {
|
|
67
|
+
const content = new Map();
|
|
68
|
+
const pointer = new Map(); // (route,label) → last-known-good sha
|
|
69
|
+
return {
|
|
70
|
+
get(route, label, sha) {
|
|
71
|
+
return content.get(contentKey(route, label, sha));
|
|
72
|
+
},
|
|
73
|
+
lastKnown(route, label) {
|
|
74
|
+
const sha = pointer.get(pointerKey(route, label));
|
|
75
|
+
return sha === undefined ? undefined : content.get(contentKey(route, label, sha));
|
|
76
|
+
},
|
|
77
|
+
lastKnownSha(route, label) {
|
|
78
|
+
return pointer.get(pointerKey(route, label));
|
|
79
|
+
},
|
|
80
|
+
put(route, label, prompt) {
|
|
81
|
+
content.set(contentKey(route, label, prompt.template_sha), prompt);
|
|
82
|
+
pointer.set(pointerKey(route, label), prompt.template_sha);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The api-mode prompt fetcher: one authenticated, ETag-aware GET per (route,
|
|
88
|
+
* label). Mirrors the api sink's leak discipline (status only, never the
|
|
89
|
+
* body/URL in an error — C2). NEVER throws: a network/HTTP failure resolves
|
|
90
|
+
* null, a 304 resolves "not-modified", a malformed 200 resolves null — so the
|
|
91
|
+
* resolver stays fail-open.
|
|
92
|
+
*/
|
|
93
|
+
function createApiPromptFetcher(config) {
|
|
94
|
+
const base = config.ingestUrl.replace(/\/$/, "");
|
|
95
|
+
const fetchFn = config.fetchFn ?? fetch;
|
|
96
|
+
return {
|
|
97
|
+
async fetch(route, label, etag) {
|
|
98
|
+
const url = `${base}/v1/prompts/${encodeURIComponent(route)}?label=${encodeURIComponent(label)}`;
|
|
99
|
+
let response;
|
|
100
|
+
try {
|
|
101
|
+
response = await fetchFn(url, {
|
|
102
|
+
method: "GET",
|
|
103
|
+
headers: {
|
|
104
|
+
authorization: `Bearer ${config.apiKey}`,
|
|
105
|
+
...(etag !== undefined ? { "if-none-match": etag } : {}),
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null; // network throw — fail-open, keep last-known.
|
|
111
|
+
}
|
|
112
|
+
if (response.status === 304)
|
|
113
|
+
return "not-modified";
|
|
114
|
+
if (!response.ok)
|
|
115
|
+
return null;
|
|
116
|
+
const body = (await response.json().catch(() => null));
|
|
117
|
+
return parseServedPrompt(body);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PSR-2b — the fail-open prompt resolver (spec §4 "Fail-open", HARD requirement).
|
|
3
|
+
*
|
|
4
|
+
* The template fetch is the ONLY remote part of serving a prompt; the SDK stays
|
|
5
|
+
* in-process. A control-plane blip must NEVER block a call, so this resolver
|
|
6
|
+
* degrades exactly like the artifact namespace already does — through named
|
|
7
|
+
* tiers, in order:
|
|
8
|
+
*
|
|
9
|
+
* 1. cache-fresh — within `ttlMs` of the last fetch, serve the cached
|
|
10
|
+
* last-known-good with no network at all.
|
|
11
|
+
* 2. fetch — revalidate by ETag (`template_sha`); a new version is
|
|
12
|
+
* cached and served, a 304 keeps the cache.
|
|
13
|
+
* 3. last-known-good — the fetch failed/miss, but a version was fetched before:
|
|
14
|
+
* serve the cached last-known-good.
|
|
15
|
+
* 4. embedded — never fetched successfully, and the fetch is failing:
|
|
16
|
+
* serve the embedded/pinned template (the last export or a
|
|
17
|
+
* bundled default) supplied by the app.
|
|
18
|
+
*
|
|
19
|
+
* If every tier is empty (never fetched, no embedded) the resolver returns
|
|
20
|
+
* `null` — "no PSR prompt; use your existing bundle serve path" (which itself
|
|
21
|
+
* fail-opens). So a call is never BLOCKED by the control plane: it is served the
|
|
22
|
+
* best template available, or falls through to the pre-PSR path.
|
|
23
|
+
*
|
|
24
|
+
* `resolve` NEVER throws. It is the fail-open envelope around the fetcher (which
|
|
25
|
+
* already never throws) and the cache (in-memory).
|
|
26
|
+
*/
|
|
27
|
+
import type { PromptCache, PromptFetcher, ServedPrompt } from "./prompt-fetch";
|
|
28
|
+
/** Where a resolution came from — the served tier (spec §4). Surfaced so status
|
|
29
|
+
* / a test can prove which fail-open tier answered a call. */
|
|
30
|
+
export type PromptServeSource = "cache" | "fetch" | "last-known-good" | "embedded";
|
|
31
|
+
/** A resolved served prompt plus the tier that answered. */
|
|
32
|
+
export interface ServedPromptResolution {
|
|
33
|
+
readonly prompt: ServedPrompt;
|
|
34
|
+
readonly source: PromptServeSource;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The embedded/pinned templates the app ships as the last-resort floor (spec
|
|
38
|
+
* §4: "the last export or a bundled default"). Keyed by (route,label); a `get`
|
|
39
|
+
* miss just means no floor for that label — the resolver returns null and the
|
|
40
|
+
* caller uses its existing bundle path.
|
|
41
|
+
*/
|
|
42
|
+
export interface EmbeddedPrompts {
|
|
43
|
+
get(route: string, label: string): ServedPrompt | undefined;
|
|
44
|
+
}
|
|
45
|
+
/** Build an {@link EmbeddedPrompts} from a plain in-memory table (the common
|
|
46
|
+
* case: a small set of bundled defaults or the last export the app pins). */
|
|
47
|
+
export declare function createEmbeddedPrompts(table?: Readonly<Record<string, Readonly<Record<string, ServedPrompt>>>>): EmbeddedPrompts;
|
|
48
|
+
export interface PromptResolverConfig {
|
|
49
|
+
readonly fetcher: PromptFetcher;
|
|
50
|
+
readonly cache: PromptCache;
|
|
51
|
+
/** The last-resort floor. Absent ⇒ tier 4 is empty (a cold fetch failure
|
|
52
|
+
* returns null ⇒ the caller's existing bundle path). */
|
|
53
|
+
readonly embedded?: EmbeddedPrompts;
|
|
54
|
+
/** Cache freshness window (spec §4, parity with call-config ~30–60s). Within
|
|
55
|
+
* it, no fetch — serve the cached last-known-good. */
|
|
56
|
+
readonly ttlMs: number;
|
|
57
|
+
/** Injectable clock for tests; defaults to Date.now. */
|
|
58
|
+
readonly now?: () => number;
|
|
59
|
+
}
|
|
60
|
+
export interface PromptResolver {
|
|
61
|
+
/**
|
|
62
|
+
* Resolve `route`'s prompt for `label`, fail-open through the tiers above.
|
|
63
|
+
* NEVER throws. Returns null only when every tier is empty (⇒ the caller
|
|
64
|
+
* serves its existing bundle, which itself fail-opens).
|
|
65
|
+
*/
|
|
66
|
+
resolve(route: string, label: string): Promise<ServedPromptResolution | null>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build the fail-open prompt resolver (spec §4). Holds a per-(route,label) fetch
|
|
70
|
+
* timestamp so a read within `ttlMs` skips the network; a stale read revalidates
|
|
71
|
+
* by ETag and folds the result into the cache. Every failure path falls to the
|
|
72
|
+
* next tier — last-known-good, then embedded, then null — so a control-plane
|
|
73
|
+
* outage never blocks a call.
|
|
74
|
+
*/
|
|
75
|
+
export declare function createPromptResolver(config: PromptResolverConfig): PromptResolver;
|
|
76
|
+
//# sourceMappingURL=prompt-resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-resolve.d.ts","sourceRoot":"","sources":["../../src/prompts/prompt-resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE/E;8DAC8D;AAC9D,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,OAAO,GAAG,iBAAiB,GAAG,UAAU,CAAC;AAEnF,4DAA4D;AAC5D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;CACpC;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;CAC7D;AAED;6EAC6E;AAC7E,wBAAgB,qBAAqB,CACnC,KAAK,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,CAAM,GAC3E,eAAe,CAMjB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B;4DACwD;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC;IACpC;0DACsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAC;CAC/E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,cAAc,CA+CjF"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PSR-2b — the fail-open prompt resolver (spec §4 "Fail-open", HARD requirement).
|
|
4
|
+
*
|
|
5
|
+
* The template fetch is the ONLY remote part of serving a prompt; the SDK stays
|
|
6
|
+
* in-process. A control-plane blip must NEVER block a call, so this resolver
|
|
7
|
+
* degrades exactly like the artifact namespace already does — through named
|
|
8
|
+
* tiers, in order:
|
|
9
|
+
*
|
|
10
|
+
* 1. cache-fresh — within `ttlMs` of the last fetch, serve the cached
|
|
11
|
+
* last-known-good with no network at all.
|
|
12
|
+
* 2. fetch — revalidate by ETag (`template_sha`); a new version is
|
|
13
|
+
* cached and served, a 304 keeps the cache.
|
|
14
|
+
* 3. last-known-good — the fetch failed/miss, but a version was fetched before:
|
|
15
|
+
* serve the cached last-known-good.
|
|
16
|
+
* 4. embedded — never fetched successfully, and the fetch is failing:
|
|
17
|
+
* serve the embedded/pinned template (the last export or a
|
|
18
|
+
* bundled default) supplied by the app.
|
|
19
|
+
*
|
|
20
|
+
* If every tier is empty (never fetched, no embedded) the resolver returns
|
|
21
|
+
* `null` — "no PSR prompt; use your existing bundle serve path" (which itself
|
|
22
|
+
* fail-opens). So a call is never BLOCKED by the control plane: it is served the
|
|
23
|
+
* best template available, or falls through to the pre-PSR path.
|
|
24
|
+
*
|
|
25
|
+
* `resolve` NEVER throws. It is the fail-open envelope around the fetcher (which
|
|
26
|
+
* already never throws) and the cache (in-memory).
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.createEmbeddedPrompts = createEmbeddedPrompts;
|
|
30
|
+
exports.createPromptResolver = createPromptResolver;
|
|
31
|
+
/** Build an {@link EmbeddedPrompts} from a plain in-memory table (the common
|
|
32
|
+
* case: a small set of bundled defaults or the last export the app pins). */
|
|
33
|
+
function createEmbeddedPrompts(table = {}) {
|
|
34
|
+
return {
|
|
35
|
+
get(route, label) {
|
|
36
|
+
return table[route]?.[label];
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build the fail-open prompt resolver (spec §4). Holds a per-(route,label) fetch
|
|
42
|
+
* timestamp so a read within `ttlMs` skips the network; a stale read revalidates
|
|
43
|
+
* by ETag and folds the result into the cache. Every failure path falls to the
|
|
44
|
+
* next tier — last-known-good, then embedded, then null — so a control-plane
|
|
45
|
+
* outage never blocks a call.
|
|
46
|
+
*/
|
|
47
|
+
function createPromptResolver(config) {
|
|
48
|
+
const now = config.now ?? (() => Date.now());
|
|
49
|
+
const { fetcher, cache, embedded } = config;
|
|
50
|
+
const fetchedAt = new Map(); // (route,label) → last fetch ts
|
|
51
|
+
const stamp = (route, label) => `${route}\0${label}`;
|
|
52
|
+
/** The last-known-good or embedded floor, in that order, or null. */
|
|
53
|
+
function floor(route, label) {
|
|
54
|
+
const known = cache.lastKnown(route, label);
|
|
55
|
+
if (known !== undefined)
|
|
56
|
+
return { prompt: known, source: "last-known-good" };
|
|
57
|
+
const pinned = embedded?.get(route, label);
|
|
58
|
+
if (pinned !== undefined)
|
|
59
|
+
return { prompt: pinned, source: "embedded" };
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
async resolve(route, label) {
|
|
64
|
+
const key = stamp(route, label);
|
|
65
|
+
const last = fetchedAt.get(key);
|
|
66
|
+
const cached = cache.lastKnown(route, label);
|
|
67
|
+
// Tier 1 — cache-fresh: within the TTL of a prior fetch, no network.
|
|
68
|
+
if (cached !== undefined && last !== undefined && now() - last < config.ttlMs) {
|
|
69
|
+
return { prompt: cached, source: "cache" };
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
// Tier 2 — fetch, ETag-revalidated against the last-known-good sha.
|
|
73
|
+
const result = await fetcher.fetch(route, label, cache.lastKnownSha(route, label));
|
|
74
|
+
if (result === "not-modified") {
|
|
75
|
+
fetchedAt.set(key, now());
|
|
76
|
+
// 304 ⇒ the cached last-known-good is current.
|
|
77
|
+
return floor(route, label);
|
|
78
|
+
}
|
|
79
|
+
if (result === null) {
|
|
80
|
+
// Miss/error ⇒ tiers 3–4 (last-known-good, then embedded).
|
|
81
|
+
return floor(route, label);
|
|
82
|
+
}
|
|
83
|
+
cache.put(route, label, result);
|
|
84
|
+
fetchedAt.set(key, now());
|
|
85
|
+
return { prompt: result, source: "fetch" };
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// The hot path never throws — fall to the fail-open floor.
|
|
89
|
+
return floor(route, label);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|