modelrig 0.9.0 → 0.11.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.
Files changed (231) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/README.md +51 -27
  3. package/data/PROVENANCE.md +14 -0
  4. package/data/pricing-map.json +26 -0
  5. package/dist/adapters/attempt-meta-map.d.ts +8 -1
  6. package/dist/adapters/attempt-meta-map.d.ts.map +1 -1
  7. package/dist/adapters/attempt-meta-map.js +4 -0
  8. package/dist/adapters/build-adapters.js +2 -2
  9. package/dist/adapters/gemini.d.ts +28 -1
  10. package/dist/adapters/gemini.d.ts.map +1 -1
  11. package/dist/adapters/gemini.js +38 -9
  12. package/dist/adapters/grok.d.ts.map +1 -1
  13. package/dist/adapters/grok.js +53 -4
  14. package/dist/adapters/openai-compat.d.ts +8 -2
  15. package/dist/adapters/openai-compat.d.ts.map +1 -1
  16. package/dist/adapters/openai-compat.js +8 -1
  17. package/dist/adapters/openai-stream.d.ts +10 -2
  18. package/dist/adapters/openai-stream.d.ts.map +1 -1
  19. package/dist/adapters/openai-stream.js +5 -1
  20. package/dist/adapters/openai.d.ts +10 -1
  21. package/dist/adapters/openai.d.ts.map +1 -1
  22. package/dist/adapters/openai.js +57 -7
  23. package/dist/adapters/responses-api.d.ts +61 -0
  24. package/dist/adapters/responses-api.d.ts.map +1 -0
  25. package/dist/adapters/responses-api.js +124 -0
  26. package/dist/artifacts/artifact.d.ts +20 -0
  27. package/dist/artifacts/artifact.d.ts.map +1 -1
  28. package/dist/artifacts/artifact.js +207 -171
  29. package/dist/artifacts/index.d.ts +3 -1
  30. package/dist/artifacts/index.d.ts.map +1 -1
  31. package/dist/artifacts/index.js +8 -1
  32. package/dist/bakeoff/groundedness.d.ts +58 -0
  33. package/dist/bakeoff/groundedness.d.ts.map +1 -0
  34. package/dist/bakeoff/groundedness.js +165 -0
  35. package/dist/bakeoff/quality.d.ts +14 -1
  36. package/dist/bakeoff/quality.d.ts.map +1 -1
  37. package/dist/bakeoff/quality.js +10 -0
  38. package/dist/bakeoff/replay.d.ts.map +1 -1
  39. package/dist/bakeoff/replay.js +4 -0
  40. package/dist/cli.d.ts.map +1 -1
  41. package/dist/cli.js +8 -0
  42. package/dist/config.d.ts +31 -0
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/config.js +23 -0
  45. package/dist/doctor-cli.d.ts +7 -0
  46. package/dist/doctor-cli.d.ts.map +1 -0
  47. package/dist/doctor-cli.js +68 -0
  48. package/dist/doctor-preflight.d.ts +64 -0
  49. package/dist/doctor-preflight.d.ts.map +1 -0
  50. package/dist/doctor-preflight.js +167 -0
  51. package/dist/feedback/coverage.d.ts.map +1 -1
  52. package/dist/feedback/coverage.js +9 -2
  53. package/dist/feedback/grade.d.ts +8 -1
  54. package/dist/feedback/grade.d.ts.map +1 -1
  55. package/dist/feedback/grade.js +4 -0
  56. package/dist/gateway/http-protocol.d.ts +19 -0
  57. package/dist/gateway/http-protocol.d.ts.map +1 -1
  58. package/dist/gateway/serve-client.d.ts +99 -0
  59. package/dist/gateway/serve-client.d.ts.map +1 -0
  60. package/dist/gateway/serve-client.js +360 -0
  61. package/dist/index.d.ts +31 -3
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +71 -8
  64. package/dist/prompts/manifest.d.ts +128 -0
  65. package/dist/prompts/manifest.d.ts.map +1 -0
  66. package/dist/prompts/manifest.js +214 -0
  67. package/dist/prompts/prompt-fetch.d.ts +84 -0
  68. package/dist/prompts/prompt-fetch.d.ts.map +1 -0
  69. package/dist/prompts/prompt-fetch.js +120 -0
  70. package/dist/prompts/prompt-resolve.d.ts +76 -0
  71. package/dist/prompts/prompt-resolve.d.ts.map +1 -0
  72. package/dist/prompts/prompt-resolve.js +93 -0
  73. package/dist/prompts/slot-resolver.d.ts +91 -0
  74. package/dist/prompts/slot-resolver.d.ts.map +1 -0
  75. package/dist/prompts/slot-resolver.js +135 -0
  76. package/dist/prompts/tools-register.d.ts +60 -0
  77. package/dist/prompts/tools-register.d.ts.map +1 -0
  78. package/dist/prompts/tools-register.js +48 -0
  79. package/dist/prompts/version-hash.d.ts +29 -0
  80. package/dist/prompts/version-hash.d.ts.map +1 -0
  81. package/dist/prompts/version-hash.js +54 -0
  82. package/dist/prompts-export-cli.d.ts +21 -0
  83. package/dist/prompts-export-cli.d.ts.map +1 -0
  84. package/dist/prompts-export-cli.js +130 -0
  85. package/dist/registry/cache-policy.d.ts.map +1 -1
  86. package/dist/registry/cache-policy.js +22 -0
  87. package/dist/registry/pricing-shape.d.ts +38 -8
  88. package/dist/registry/pricing-shape.d.ts.map +1 -1
  89. package/dist/registry/pricing-shape.js +40 -12
  90. package/dist/registry/pricing.d.ts +6 -5
  91. package/dist/registry/pricing.d.ts.map +1 -1
  92. package/dist/registry/pricing.js +9 -5
  93. package/dist/routes/destination-loader.d.ts +16 -0
  94. package/dist/routes/destination-loader.d.ts.map +1 -0
  95. package/dist/routes/destination-loader.js +29 -0
  96. package/dist/routes/draft.d.ts +7 -0
  97. package/dist/routes/draft.d.ts.map +1 -1
  98. package/dist/routes/draft.js +12 -0
  99. package/dist/routes/loader.d.ts.map +1 -1
  100. package/dist/routes/loader.js +27 -54
  101. package/dist/routes/output-loader.d.ts +42 -0
  102. package/dist/routes/output-loader.d.ts.map +1 -0
  103. package/dist/routes/output-loader.js +112 -0
  104. package/dist/routes/prompt-loader.d.ts +49 -0
  105. package/dist/routes/prompt-loader.d.ts.map +1 -0
  106. package/dist/routes/prompt-loader.js +113 -0
  107. package/dist/routes/resolved-bundle.d.ts +19 -0
  108. package/dist/routes/resolved-bundle.d.ts.map +1 -1
  109. package/dist/routes/resolved-bundle.js +24 -0
  110. package/dist/routes/screen-loader.d.ts +1 -1
  111. package/dist/routes/screen-loader.d.ts.map +1 -1
  112. package/dist/routes/screen-loader.js +8 -1
  113. package/dist/run/accept.d.ts +3 -0
  114. package/dist/run/accept.d.ts.map +1 -1
  115. package/dist/run/accept.js +1 -0
  116. package/dist/run/cost.d.ts +31 -2
  117. package/dist/run/cost.d.ts.map +1 -1
  118. package/dist/run/cost.js +57 -2
  119. package/dist/run/destination.d.ts +39 -0
  120. package/dist/run/destination.d.ts.map +1 -0
  121. package/dist/run/destination.js +45 -0
  122. package/dist/run/dispatch.d.ts +25 -3
  123. package/dist/run/dispatch.d.ts.map +1 -1
  124. package/dist/run/dispatch.js +26 -4
  125. package/dist/run/raw-defaults.d.ts.map +1 -1
  126. package/dist/run/raw-defaults.js +6 -1
  127. package/dist/run/raw-finalize.d.ts +96 -0
  128. package/dist/run/raw-finalize.d.ts.map +1 -0
  129. package/dist/run/raw-finalize.js +283 -0
  130. package/dist/run/raw-knobs.d.ts +36 -0
  131. package/dist/run/raw-knobs.d.ts.map +1 -0
  132. package/dist/run/raw-knobs.js +99 -0
  133. package/dist/run/raw-request.d.ts +24 -0
  134. package/dist/run/raw-request.d.ts.map +1 -0
  135. package/dist/run/raw-request.js +69 -0
  136. package/dist/run/raw-schema.d.ts +76 -0
  137. package/dist/run/raw-schema.d.ts.map +1 -0
  138. package/dist/run/raw-schema.js +166 -0
  139. package/dist/run/raw-search-cost.d.ts +49 -0
  140. package/dist/run/raw-search-cost.d.ts.map +1 -0
  141. package/dist/run/raw-search-cost.js +79 -0
  142. package/dist/run/raw-support.d.ts +64 -6
  143. package/dist/run/raw-support.d.ts.map +1 -1
  144. package/dist/run/raw-support.js +84 -11
  145. package/dist/run/raw.d.ts +25 -26
  146. package/dist/run/raw.d.ts.map +1 -1
  147. package/dist/run/raw.js +30 -351
  148. package/dist/run/record-attempt.d.ts +8 -0
  149. package/dist/run/record-attempt.d.ts.map +1 -1
  150. package/dist/run/record-attempt.js +26 -12
  151. package/dist/run/record-call.d.ts +104 -0
  152. package/dist/run/record-call.d.ts.map +1 -0
  153. package/dist/run/record-call.js +277 -0
  154. package/dist/run/repair-attempt.d.ts +68 -0
  155. package/dist/run/repair-attempt.d.ts.map +1 -0
  156. package/dist/run/repair-attempt.js +180 -0
  157. package/dist/run/resolve-destination.d.ts +49 -0
  158. package/dist/run/resolve-destination.d.ts.map +1 -0
  159. package/dist/run/resolve-destination.js +76 -0
  160. package/dist/run/routing-docs.d.ts.map +1 -1
  161. package/dist/run/routing-docs.js +9 -4
  162. package/dist/run/run.d.ts +30 -1
  163. package/dist/run/run.d.ts.map +1 -1
  164. package/dist/run/run.js +75 -148
  165. package/dist/run/telemetry-row.d.ts +92 -0
  166. package/dist/run/telemetry-row.d.ts.map +1 -0
  167. package/dist/run/telemetry-row.js +81 -0
  168. package/dist/run/validate.d.ts +12 -1
  169. package/dist/run/validate.d.ts.map +1 -1
  170. package/dist/run/validate.js +31 -0
  171. package/dist/run/web-search-pricing.d.ts +43 -0
  172. package/dist/run/web-search-pricing.d.ts.map +1 -0
  173. package/dist/run/web-search-pricing.js +56 -0
  174. package/dist/settings/grading-guide.d.ts +27 -1
  175. package/dist/settings/grading-guide.d.ts.map +1 -1
  176. package/dist/status.d.ts +6 -0
  177. package/dist/status.d.ts.map +1 -1
  178. package/dist/status.js +4 -1
  179. package/dist/telemetry/artifact-store.d.ts.map +1 -1
  180. package/dist/telemetry/artifact-store.js +11 -0
  181. package/dist/telemetry/attempt-meta.d.ts +66 -1
  182. package/dist/telemetry/attempt-meta.d.ts.map +1 -1
  183. package/dist/telemetry/attempt-meta.js +27 -1
  184. package/dist/telemetry/exporter-setup.d.ts +2 -0
  185. package/dist/telemetry/exporter-setup.d.ts.map +1 -1
  186. package/dist/telemetry/exporter-setup.js +6 -1
  187. package/dist/telemetry/grade-mappers.d.ts +5 -2
  188. package/dist/telemetry/grade-mappers.d.ts.map +1 -1
  189. package/dist/telemetry/grade-mappers.js +8 -2
  190. package/dist/telemetry/grades-sql.d.ts +4 -1
  191. package/dist/telemetry/grades-sql.d.ts.map +1 -1
  192. package/dist/telemetry/grades-sql.js +26 -3
  193. package/dist/telemetry/ingest-protocol.d.ts +26 -2
  194. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  195. package/dist/telemetry/ingest-protocol.js +32 -29
  196. package/dist/telemetry/lanes.d.ts +31 -0
  197. package/dist/telemetry/lanes.d.ts.map +1 -0
  198. package/dist/telemetry/lanes.js +41 -0
  199. package/dist/telemetry/org-identity.d.ts +47 -0
  200. package/dist/telemetry/org-identity.d.ts.map +1 -0
  201. package/dist/telemetry/org-identity.js +100 -0
  202. package/dist/telemetry/route-registration.d.ts +48 -0
  203. package/dist/telemetry/route-registration.d.ts.map +1 -0
  204. package/dist/telemetry/route-registration.js +77 -0
  205. package/dist/telemetry/sinks.d.ts +10 -1
  206. package/dist/telemetry/sinks.d.ts.map +1 -1
  207. package/dist/telemetry/sinks.js +52 -6
  208. package/dist/telemetry/store-migrations.d.ts.map +1 -1
  209. package/dist/telemetry/store-migrations.js +13 -0
  210. package/dist/tenancy/api-key-format.d.ts +16 -1
  211. package/dist/tenancy/api-key-format.d.ts.map +1 -1
  212. package/dist/tenancy/api-key-format.js +17 -0
  213. package/dist/types/provider.d.ts +20 -3
  214. package/dist/types/provider.d.ts.map +1 -1
  215. package/dist/types/provider.js +1 -0
  216. package/dist/types/route.d.ts +76 -0
  217. package/dist/types/route.d.ts.map +1 -1
  218. package/dist/types/telemetry.d.ts +30 -2
  219. package/dist/types/telemetry.d.ts.map +1 -1
  220. package/docs/agent-skill/SKILL.md +75 -5
  221. package/docs/instrumentation.md +98 -36
  222. package/docs/llms.txt +96 -16
  223. package/docs/managed-serving.md +150 -0
  224. package/docs/mcp-oracle.md +75 -2
  225. package/docs/migration-playbook.md +5 -4
  226. package/docs/quickstart.md +169 -83
  227. package/package.json +3 -2
  228. package/registry/registry.json +82 -0
  229. package/dist/feedback/feedback.d.ts +0 -35
  230. package/dist/feedback/feedback.d.ts.map +0 -1
  231. package/dist/feedback/feedback.js +0 -39
@@ -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
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * PSR-2b — the client-fill engine (spec §4 "two-layer resolution", ADJ-2;
3
+ * authoring-guide §5–§6). Resolves a served {@link Manifest} into an assembled
4
+ * prompt string, ENTIRELY in the customer's process.
5
+ *
6
+ * Per call the SDK walks the manifest's slots IN ORDER (order = send order, D6)
7
+ * and fills each one:
8
+ * • `static` → authored text (inline `text`, or `from: system|template`)
9
+ * • `dynamic·tool` → the registered {@link ToolFn}, run in-process with the
10
+ * customer's keys (spec §4)
11
+ * • `dynamic·cache` → the declared cache handle, read by the caller's reader
12
+ * • `dynamic·upstream` → the prior step's APPROVED output (from the caller)
13
+ * • `dynamic·variable` → the call's variable whitelist
14
+ * then assembles the resolved parts in order. The assembled text is a plain
15
+ * string the caller dispatches through the existing `rig.run`/`runRaw` seam.
16
+ *
17
+ * FAIL CLOSED, NEVER SILENT (spec §4, HARD requirement). A `dynamic·tool` slot
18
+ * whose tool is not registered throws a teaching {@link SlotResolutionError} that
19
+ * NAMES the missing tool and what IS bound — never an empty slot, never a silent
20
+ * skip. The same discipline covers every dynamic kind: a slot that cannot be
21
+ * filled from what the caller provided fails closed, so a half-assembled prompt
22
+ * is never dispatched.
23
+ *
24
+ * PRIVACY: this module has ZERO network/telemetry imports. Its inputs (tool fns,
25
+ * cache reader, upstream outputs, variables) and its output (the assembled text)
26
+ * are all in-process values. Nothing tool-derived is sent anywhere — assembly is
27
+ * a local computation. That is the "tool keys + dynamic data never leave the
28
+ * process" guarantee, structural rather than promised.
29
+ */
30
+ import type { Manifest } from "./manifest";
31
+ import type { ToolRegistry } from "./tools-register";
32
+ /** Reads a `dynamic·cache` slot's declared handle into text, in-process. Async
33
+ * (a cache is usually an out-of-band read). The handle is the slot's
34
+ * `source.ref`; the reader is the customer's — ModelRig never reads it. */
35
+ export type CacheReader = (handle: string) => string | Promise<string>;
36
+ /**
37
+ * Everything the resolver needs to fill slots — supplied by the caller, all
38
+ * in-process. Only the kinds a given manifest uses need be present; a slot of a
39
+ * kind the caller did not provide for FAILS CLOSED (never a silent empty slot).
40
+ */
41
+ export interface SlotResolveContext {
42
+ /** The authored template text a `from: template` static slot pulls (the served
43
+ * `ServedPrompt.template`). */
44
+ readonly template: string;
45
+ /** The authored system text a `from: system` static slot pulls, when the
46
+ * manifest declares one. */
47
+ readonly system?: string;
48
+ /** The tool bindings (`rig.tools`) a `dynamic·tool` slot resolves against. */
49
+ readonly tools: ToolRegistry;
50
+ /** The variable whitelist — the customer's own call inputs. A
51
+ * `dynamic·variable` slot reads `variables[ref]`; also passed to each tool as
52
+ * its {@link ToolResolveContext.input}. */
53
+ readonly variables?: Readonly<Record<string, unknown>>;
54
+ /** Reader for `dynamic·cache` slots. Absent ⇒ a cache slot fails closed. */
55
+ readonly cache?: CacheReader;
56
+ /** Prior steps' APPROVED outputs, keyed by step key — a `dynamic·upstream`
57
+ * slot reads `upstream[ref]`. Absent/missing ref ⇒ fails closed. */
58
+ readonly upstream?: Readonly<Record<string, string>>;
59
+ }
60
+ /** One resolved slot: its id and the text it contributed, in send order. */
61
+ export interface ResolvedSlot {
62
+ readonly id: string;
63
+ readonly text: string;
64
+ }
65
+ /** The assembled prompt plus the per-slot parts (send order preserved). `text`
66
+ * is `parts` joined with a blank line — the string the caller dispatches. */
67
+ export interface AssembledPrompt {
68
+ readonly text: string;
69
+ readonly parts: readonly ResolvedSlot[];
70
+ }
71
+ /**
72
+ * A slot that could not be filled — the fail-closed signal (spec §4). Carries
73
+ * the slot id and a teaching message; the tool-unbound case names the missing
74
+ * tool and the bound names, so the fix is obvious at the call site.
75
+ */
76
+ export declare class SlotResolutionError extends Error {
77
+ readonly slotId: string;
78
+ constructor(slotId: string, message: string);
79
+ }
80
+ /**
81
+ * Resolve every slot of a served manifest IN ORDER and assemble the prompt
82
+ * (spec §4). Slots resolve sequentially so `upstream` ordering and any
83
+ * side-effecting tool run in declared send order. Throws
84
+ * {@link SlotResolutionError} on the FIRST slot that cannot be filled — the
85
+ * prompt is never dispatched half-assembled (fail closed).
86
+ *
87
+ * Pure over its inputs apart from the tool/cache functions the caller supplies,
88
+ * which run in-process. The returned {@link AssembledPrompt} is a local value.
89
+ */
90
+ export declare function assemblePrompt(manifest: Manifest, ctx: SlotResolveContext): Promise<AssembledPrompt>;
91
+ //# sourceMappingURL=slot-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot-resolver.d.ts","sourceRoot":"","sources":["../../src/prompts/slot-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAQ,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD;;2EAE2E;AAC3E,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAEvE;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC;mCAC+B;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;gCAC4B;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B;;+CAE2C;IAC3C,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;IAC7B;wEACoE;IACpE,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACtD;AAED,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;6EAC6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;CACzC;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK5C;AAiGD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,kBAAkB,GACtB,OAAO,CAAC,eAAe,CAAC,CAO1B"}
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ /**
3
+ * PSR-2b — the client-fill engine (spec §4 "two-layer resolution", ADJ-2;
4
+ * authoring-guide §5–§6). Resolves a served {@link Manifest} into an assembled
5
+ * prompt string, ENTIRELY in the customer's process.
6
+ *
7
+ * Per call the SDK walks the manifest's slots IN ORDER (order = send order, D6)
8
+ * and fills each one:
9
+ * • `static` → authored text (inline `text`, or `from: system|template`)
10
+ * • `dynamic·tool` → the registered {@link ToolFn}, run in-process with the
11
+ * customer's keys (spec §4)
12
+ * • `dynamic·cache` → the declared cache handle, read by the caller's reader
13
+ * • `dynamic·upstream` → the prior step's APPROVED output (from the caller)
14
+ * • `dynamic·variable` → the call's variable whitelist
15
+ * then assembles the resolved parts in order. The assembled text is a plain
16
+ * string the caller dispatches through the existing `rig.run`/`runRaw` seam.
17
+ *
18
+ * FAIL CLOSED, NEVER SILENT (spec §4, HARD requirement). A `dynamic·tool` slot
19
+ * whose tool is not registered throws a teaching {@link SlotResolutionError} that
20
+ * NAMES the missing tool and what IS bound — never an empty slot, never a silent
21
+ * skip. The same discipline covers every dynamic kind: a slot that cannot be
22
+ * filled from what the caller provided fails closed, so a half-assembled prompt
23
+ * is never dispatched.
24
+ *
25
+ * PRIVACY: this module has ZERO network/telemetry imports. Its inputs (tool fns,
26
+ * cache reader, upstream outputs, variables) and its output (the assembled text)
27
+ * are all in-process values. Nothing tool-derived is sent anywhere — assembly is
28
+ * a local computation. That is the "tool keys + dynamic data never leave the
29
+ * process" guarantee, structural rather than promised.
30
+ */
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.SlotResolutionError = void 0;
33
+ exports.assemblePrompt = assemblePrompt;
34
+ /**
35
+ * A slot that could not be filled — the fail-closed signal (spec §4). Carries
36
+ * the slot id and a teaching message; the tool-unbound case names the missing
37
+ * tool and the bound names, so the fix is obvious at the call site.
38
+ */
39
+ class SlotResolutionError extends Error {
40
+ constructor(slotId, message) {
41
+ super(message);
42
+ this.name = "SlotResolutionError";
43
+ this.slotId = slotId;
44
+ }
45
+ }
46
+ exports.SlotResolutionError = SlotResolutionError;
47
+ /** The separator between assembled slot parts — a blank line, so adjacent blocks
48
+ * stay legible and a cache `prefix` slot keeps a stable boundary. */
49
+ const PART_SEPARATOR = "\n\n";
50
+ /** Resolve ONE slot to its text, or throw {@link SlotResolutionError} (fail
51
+ * closed). Every branch either returns a concrete string or throws — a slot
52
+ * never resolves to `undefined`/empty by omission. */
53
+ async function resolveSlot(slot, ctx) {
54
+ if (slot.kind === "static") {
55
+ if (slot.text !== undefined)
56
+ return slot.text;
57
+ if (slot.from === "template")
58
+ return ctx.template;
59
+ if (slot.from === "system") {
60
+ if (ctx.system === undefined) {
61
+ throw new SlotResolutionError(slot.id, `static slot "${slot.id}" references \`from: system\`, but no authored ` +
62
+ "`system` text was provided to the resolver");
63
+ }
64
+ return ctx.system;
65
+ }
66
+ // A validated manifest guarantees text XOR from; this is unreachable for a
67
+ // validated slot, but fail closed rather than emit an empty slot.
68
+ throw new SlotResolutionError(slot.id, `static slot "${slot.id}" has neither inline \`text\` nor a \`from\` reference`);
69
+ }
70
+ // dynamic — a validated manifest guarantees a well-formed `source`.
71
+ const source = slot.source;
72
+ if (source === undefined) {
73
+ throw new SlotResolutionError(slot.id, `dynamic slot "${slot.id}" is missing its \`source\``);
74
+ }
75
+ const ref = source.ref;
76
+ switch (source.kind) {
77
+ case "tool": {
78
+ const fn = ctx.tools.get(ref);
79
+ if (fn === undefined) {
80
+ const bound = ctx.tools.names();
81
+ const boundList = bound.length > 0 ? `bound tools: ${bound.join(", ")}` : "no tools are bound";
82
+ throw new SlotResolutionError(slot.id, `slot "${slot.id}" needs tool "${ref}", but it is not bound — call ` +
83
+ `rig.tools.register("${ref}", fn) before this route runs (${boundList}). ` +
84
+ "The prompt was NOT sent with an empty slot (spec §4, fail closed).");
85
+ }
86
+ return fn({ ref, input: ctx.variables ?? {} });
87
+ }
88
+ case "cache": {
89
+ if (ctx.cache === undefined) {
90
+ throw new SlotResolutionError(slot.id, `slot "${slot.id}" needs cache handle "${ref}", but no cache reader was ` +
91
+ "provided to the resolver");
92
+ }
93
+ return ctx.cache(ref);
94
+ }
95
+ case "upstream": {
96
+ const value = ctx.upstream?.[ref];
97
+ if (value === undefined) {
98
+ throw new SlotResolutionError(slot.id, `slot "${slot.id}" needs the approved output of upstream step "${ref}", ` +
99
+ "but it was not provided to the resolver");
100
+ }
101
+ return value;
102
+ }
103
+ case "variable": {
104
+ const value = ctx.variables?.[ref];
105
+ if (value === undefined) {
106
+ throw new SlotResolutionError(slot.id, `slot "${slot.id}" needs variable "${ref}", but it is not in the call's ` +
107
+ "variable whitelist");
108
+ }
109
+ return typeof value === "string" ? value : JSON.stringify(value);
110
+ }
111
+ default: {
112
+ // Exhaustive over DynamicSourceKind; a future kind fails closed here.
113
+ const exhaustive = source.kind;
114
+ throw new SlotResolutionError(slot.id, `slot "${slot.id}" has an unknown dynamic source kind ${JSON.stringify(exhaustive)}`);
115
+ }
116
+ }
117
+ }
118
+ /**
119
+ * Resolve every slot of a served manifest IN ORDER and assemble the prompt
120
+ * (spec §4). Slots resolve sequentially so `upstream` ordering and any
121
+ * side-effecting tool run in declared send order. Throws
122
+ * {@link SlotResolutionError} on the FIRST slot that cannot be filled — the
123
+ * prompt is never dispatched half-assembled (fail closed).
124
+ *
125
+ * Pure over its inputs apart from the tool/cache functions the caller supplies,
126
+ * which run in-process. The returned {@link AssembledPrompt} is a local value.
127
+ */
128
+ async function assemblePrompt(manifest, ctx) {
129
+ const parts = [];
130
+ for (const slot of manifest.slots) {
131
+ const text = await resolveSlot(slot, ctx);
132
+ parts.push({ id: slot.id, text });
133
+ }
134
+ return { text: parts.map((p) => p.text).join(PART_SEPARATOR), parts };
135
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * PSR-2b — the client-side tool-binding registry (spec §4 ADJ-2, authoring-guide
3
+ * §5). The client-fill half of the prompt system-of-record.
4
+ *
5
+ * A `dynamic·tool` manifest slot NAMES a tool (`source.ref`, e.g.
6
+ * `"search_tickets"`); it carries no code and no data (structure-only, D6). To
7
+ * fill that slot at call time the SDK needs a **client-side binding** from that
8
+ * name to a function that runs IN THE CUSTOMER'S PROCESS, with the customer's
9
+ * keys. `rig.tools.register(name, fn)` is that binding.
10
+ *
11
+ * The privacy guarantee lives here: a bound tool `fn` closes over the customer's
12
+ * credentials and does its work locally; ModelRig only ever knows the tool's
13
+ * NAME (already in the manifest). Nothing this registry holds — not a fn, not a
14
+ * key it closes over, not a value it returns — is ever serialized or sent
15
+ * anywhere. This module has ZERO network/telemetry imports, by design, so
16
+ * "tool keys never leave the process" is a structural property, not a promise.
17
+ *
18
+ * Pure + dependency-free.
19
+ */
20
+ /**
21
+ * The context a bound tool receives when the SDK resolves its slot. Carries the
22
+ * tool NAME being filled and the call's variable whitelist (the same values a
23
+ * `dynamic·variable` slot draws from) so a tool can shape its work from the
24
+ * call's inputs — never anything ModelRig-side. Read-only: a tool observes, it
25
+ * does not mutate the call.
26
+ */
27
+ export interface ToolResolveContext {
28
+ /** The tool name being resolved (the slot's `source.ref`). */
29
+ readonly ref: string;
30
+ /** The call's variable whitelist — the customer's own inputs, in-process. */
31
+ readonly input: Readonly<Record<string, unknown>>;
32
+ }
33
+ /**
34
+ * A bound tool: runs in-process with the customer's keys and returns the text
35
+ * to place in its slot. May be async (a tool usually calls out to the customer's
36
+ * own upstream). Whatever it returns stays local — it becomes the slot's filled
37
+ * text and is assembled into the prompt on the customer's machine.
38
+ */
39
+ export type ToolFn = (ctx: ToolResolveContext) => string | Promise<string>;
40
+ /**
41
+ * The registry backing `rig.tools`. `register` binds a name → fn (last write
42
+ * wins, so a re-register is a rebind, never a silent duplicate); `get`/`has`
43
+ * look a binding up by name; `names` lists the bound names (for a teaching
44
+ * error). Never throws on read — the caller decides what a missing binding
45
+ * means (the slot resolver FAILS CLOSED with a teaching error, spec §4).
46
+ */
47
+ export interface ToolRegistry {
48
+ /** Bind `name` to `fn`. Rejects an empty name or a non-function with a
49
+ * teaching error — a misbinding is a bug at the developer's machine, surfaced
50
+ * at register time, never a mystery at call time. */
51
+ register(name: string, fn: ToolFn): void;
52
+ get(name: string): ToolFn | undefined;
53
+ has(name: string): boolean;
54
+ /** The bound names, sorted — used to name what IS available when a slot
55
+ * references a tool that is not. */
56
+ names(): readonly string[];
57
+ }
58
+ /** Build an empty {@link ToolRegistry}. One per rig; `rig.tools` exposes it. */
59
+ export declare function createToolRegistry(): ToolRegistry;
60
+ //# sourceMappingURL=tools-register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools-register.d.ts","sourceRoot":"","sources":["../../src/prompts/tools-register.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACnD;AAED;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,kBAAkB,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAE3E;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B;;yDAEqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACtC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B;wCACoC;IACpC,KAAK,IAAI,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,IAAI,YAAY,CA4BjD"}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ /**
3
+ * PSR-2b — the client-side tool-binding registry (spec §4 ADJ-2, authoring-guide
4
+ * §5). The client-fill half of the prompt system-of-record.
5
+ *
6
+ * A `dynamic·tool` manifest slot NAMES a tool (`source.ref`, e.g.
7
+ * `"search_tickets"`); it carries no code and no data (structure-only, D6). To
8
+ * fill that slot at call time the SDK needs a **client-side binding** from that
9
+ * name to a function that runs IN THE CUSTOMER'S PROCESS, with the customer's
10
+ * keys. `rig.tools.register(name, fn)` is that binding.
11
+ *
12
+ * The privacy guarantee lives here: a bound tool `fn` closes over the customer's
13
+ * credentials and does its work locally; ModelRig only ever knows the tool's
14
+ * NAME (already in the manifest). Nothing this registry holds — not a fn, not a
15
+ * key it closes over, not a value it returns — is ever serialized or sent
16
+ * anywhere. This module has ZERO network/telemetry imports, by design, so
17
+ * "tool keys never leave the process" is a structural property, not a promise.
18
+ *
19
+ * Pure + dependency-free.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.createToolRegistry = createToolRegistry;
23
+ /** Build an empty {@link ToolRegistry}. One per rig; `rig.tools` exposes it. */
24
+ function createToolRegistry() {
25
+ const bindings = new Map();
26
+ return {
27
+ register(name, fn) {
28
+ if (typeof name !== "string" || name.length === 0) {
29
+ throw new Error("rig.tools.register: the tool name must be a non-empty string (it is the " +
30
+ "name a `dynamic·tool` manifest slot references)");
31
+ }
32
+ if (typeof fn !== "function") {
33
+ throw new Error(`rig.tools.register: the binding for tool "${name}" must be a function ` +
34
+ "(it runs in-process with your keys to fill the slot)");
35
+ }
36
+ bindings.set(name, fn);
37
+ },
38
+ get(name) {
39
+ return bindings.get(name);
40
+ },
41
+ has(name) {
42
+ return bindings.has(name);
43
+ },
44
+ names() {
45
+ return [...bindings.keys()].sort();
46
+ },
47
+ };
48
+ }