@nola-lang/providers 0.1.0-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Evgen Mykhailenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # @nola-lang/providers
2
+
3
+ Everything provider-shaped in [Nola](https://github.com/nola-lang/nola):
4
+ provider factories, resilience combinators, and record/replay.
5
+
6
+ ```ts
7
+ import { defineConfig } from "@nola-lang/runtime";
8
+ import { openai, mockProvider, withRetry, exponential, fallback, record, replay } from "@nola-lang/providers";
9
+
10
+ export default defineConfig({
11
+ providers: {
12
+ default: withRetry(openai({ model: "gpt-5" }), exponential({ maxRetries: 3 })),
13
+ fast: fallback([openai({ model: "gpt-5-mini" }), openai({ model: "gpt-5-nano" })]),
14
+ test: replay("./nola.replay.jsonl"), // record(...) once, replay offline forever
15
+ },
16
+ });
17
+ ```
18
+
19
+ `openai()` reads `OPENAI_API_KEY` lazily and takes an injectable `fetch`.
20
+ `mockProvider([...])` gives deterministic, network-free answers for tests.
@@ -0,0 +1,32 @@
1
+ import type { NolaProvider } from "@nola-lang/core";
2
+ export interface RetryPolicy {
3
+ maxRetries: number;
4
+ delayMs: number;
5
+ multiplier: number;
6
+ maxDelayMs: number;
7
+ }
8
+ export declare function constant(opts: {
9
+ maxRetries: number;
10
+ delayMs?: number;
11
+ }): RetryPolicy;
12
+ export declare function exponential(opts: {
13
+ maxRetries: number;
14
+ delayMs?: number;
15
+ multiplier?: number;
16
+ maxDelayMs?: number;
17
+ }): RetryPolicy;
18
+ /** Definitive errors must not be retried: explicit flag, or HTTP 4xx except 408/429. */
19
+ export declare function isDefinitiveProviderError(error: unknown): boolean;
20
+ /**
21
+ * Wire-level retry: re-attempts the single `provider.complete` call with
22
+ * backoff, fail-fasting on definitive errors. Honors the provider's
23
+ * `retryAfterMs` (Retry-After) when it exceeds the scheduled delay, capped at
24
+ * `policy.maxDelayMs` — so a policy whose maxDelayMs is 0 (e.g. `constant()`
25
+ * with no delay) ignores the header entirely. Distinct from the intent method
26
+ * `.withRetry(n)`, which flat-retries the entire ask (composition, provider
27
+ * call, parse, validation) with no backoff and no definitive-error check.
28
+ */
29
+ export declare function withRetry(provider: NolaProvider, policy: RetryPolicy): NolaProvider;
30
+ export declare function fallback(providers: NolaProvider[]): NolaProvider;
31
+ export declare function roundRobin(providers: NolaProvider[]): NolaProvider;
32
+ //# sourceMappingURL=combinators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combinators.d.ts","sourceRoot":"","sources":["../src/combinators.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,CAGpF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,WAAW,CAOd;AAED,wFAAwF;AACxF,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKjE;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,GAAG,YAAY,CAoBnF;AAED,wBAAgB,QAAQ,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,YAAY,CAiBhE;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,YAAY,CAoBlE"}
@@ -0,0 +1,108 @@
1
+ import { Codes } from "@nola-lang/ast";
2
+ import { NolaConfigError, NolaProviderError } from "@nola-lang/core";
3
+ export function constant(opts) {
4
+ const delayMs = opts.delayMs ?? 0;
5
+ return { maxRetries: opts.maxRetries, delayMs, multiplier: 1, maxDelayMs: delayMs };
6
+ }
7
+ export function exponential(opts) {
8
+ return {
9
+ maxRetries: opts.maxRetries,
10
+ delayMs: opts.delayMs ?? 200,
11
+ multiplier: opts.multiplier ?? 2,
12
+ maxDelayMs: opts.maxDelayMs ?? 10_000,
13
+ };
14
+ }
15
+ /** Definitive errors must not be retried: explicit flag, or HTTP 4xx except 408/429. */
16
+ export function isDefinitiveProviderError(error) {
17
+ if (!(error instanceof NolaProviderError))
18
+ return false;
19
+ if (error.definitive)
20
+ return true;
21
+ const s = error.status;
22
+ return s !== undefined && s >= 400 && s < 500 && s !== 408 && s !== 429;
23
+ }
24
+ function sleep(ms) {
25
+ return ms <= 0 ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, ms));
26
+ }
27
+ function requireProviders(providers, combinator) {
28
+ if (providers.length === 0) {
29
+ throw new NolaConfigError(`${combinator}([]) needs at least one provider.`, Codes.ConfigInvalid);
30
+ }
31
+ }
32
+ function describeError(error) {
33
+ return error instanceof Error ? error.message : String(error);
34
+ }
35
+ /**
36
+ * Wire-level retry: re-attempts the single `provider.complete` call with
37
+ * backoff, fail-fasting on definitive errors. Honors the provider's
38
+ * `retryAfterMs` (Retry-After) when it exceeds the scheduled delay, capped at
39
+ * `policy.maxDelayMs` — so a policy whose maxDelayMs is 0 (e.g. `constant()`
40
+ * with no delay) ignores the header entirely. Distinct from the intent method
41
+ * `.withRetry(n)`, which flat-retries the entire ask (composition, provider
42
+ * call, parse, validation) with no backoff and no definitive-error check.
43
+ */
44
+ export function withRetry(provider, policy) {
45
+ return {
46
+ name: `retry(${provider.name})`,
47
+ async complete(req) {
48
+ let delay = policy.delayMs;
49
+ let lastError;
50
+ for (let attempt = 0; attempt <= policy.maxRetries; attempt++) {
51
+ try {
52
+ return await provider.complete(req);
53
+ }
54
+ catch (error) {
55
+ lastError = error;
56
+ if (isDefinitiveProviderError(error) || attempt === policy.maxRetries)
57
+ throw error;
58
+ const retryAfterMs = error instanceof NolaProviderError ? (error.retryAfterMs ?? 0) : 0;
59
+ await sleep(Math.min(Math.max(delay, retryAfterMs), policy.maxDelayMs));
60
+ delay = Math.min(delay * policy.multiplier, policy.maxDelayMs);
61
+ }
62
+ }
63
+ throw lastError; // unreachable; satisfies control-flow analysis
64
+ },
65
+ };
66
+ }
67
+ export function fallback(providers) {
68
+ requireProviders(providers, "fallback");
69
+ const name = `fallback(${providers.map((p) => p.name).join(", ")})`;
70
+ return {
71
+ name,
72
+ async complete(req) {
73
+ const failures = [];
74
+ for (const p of providers) {
75
+ try {
76
+ return await p.complete(req);
77
+ }
78
+ catch (error) {
79
+ failures.push(`${p.name}: ${describeError(error)}`);
80
+ }
81
+ }
82
+ throw new NolaProviderError(`${name}: all providers failed —\n ${failures.join("\n ")}`);
83
+ },
84
+ };
85
+ }
86
+ export function roundRobin(providers) {
87
+ requireProviders(providers, "roundRobin");
88
+ const name = `roundRobin(${providers.map((p) => p.name).join(", ")})`;
89
+ let nextStart = 0;
90
+ return {
91
+ name,
92
+ async complete(req) {
93
+ const start = nextStart++ % providers.length;
94
+ const failures = [];
95
+ for (let i = 0; i < providers.length; i++) {
96
+ const p = providers[(start + i) % providers.length];
97
+ try {
98
+ return await p.complete(req);
99
+ }
100
+ catch (error) {
101
+ failures.push(`${p.name}: ${describeError(error)}`);
102
+ }
103
+ }
104
+ throw new NolaProviderError(`${name}: all providers failed —\n ${failures.join("\n ")}`);
105
+ },
106
+ };
107
+ }
108
+ //# sourceMappingURL=combinators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combinators.js","sourceRoot":"","sources":["../src/combinators.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { mockProvider } from "./mock.js";
2
+ import { openai } from "./openai.js";
3
+ export { constant, exponential, fallback, isDefinitiveProviderError, type RetryPolicy, roundRobin, withRetry, } from "./combinators.js";
4
+ export { mockProvider } from "./mock.js";
5
+ export { type OpenAiOptions, openai } from "./openai.js";
6
+ export { record, replay } from "./record-replay.js";
7
+ /** Every built-in provider factory, keyed by the name its provider reports. */
8
+ export declare const providers: {
9
+ readonly openai: typeof openai;
10
+ readonly mock: typeof mockProvider;
11
+ };
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EACL,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,yBAAyB,EACzB,KAAK,WAAW,EAChB,UAAU,EACV,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEpD,+EAA+E;AAC/E,eAAO,MAAM,SAAS;;;CAGZ,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { mockProvider } from "./mock.js";
2
+ import { openai } from "./openai.js";
3
+ export { constant, exponential, fallback, isDefinitiveProviderError, roundRobin, withRetry, } from "./combinators.js";
4
+ export { mockProvider } from "./mock.js";
5
+ export { openai } from "./openai.js";
6
+ export { record, replay } from "./record-replay.js";
7
+ /** Every built-in provider factory, keyed by the name its provider reports. */
8
+ export const providers = {
9
+ openai,
10
+ mock: mockProvider,
11
+ };
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EACL,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,yBAAyB,EAEzB,UAAU,EACV,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAsB,MAAM,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEpD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,MAAM;IACN,IAAI,EAAE,YAAY;CACV,CAAC"}
package/dist/mock.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { NolaProvider, ProviderRequest } from "@nola-lang/core";
2
+ export declare function mockProvider(source: unknown[] | ((req: ProviderRequest) => unknown)): NolaProvider;
3
+ //# sourceMappingURL=mock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../src/mock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAErE,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,GAAG,YAAY,CAelG"}
package/dist/mock.js ADDED
@@ -0,0 +1,19 @@
1
+ export function mockProvider(source) {
2
+ const queue = Array.isArray(source) ? [...source] : null;
3
+ return {
4
+ name: "mock",
5
+ async complete(req) {
6
+ let value;
7
+ if (queue) {
8
+ if (queue.length === 0)
9
+ throw new Error("mockProvider queue exhausted");
10
+ value = queue.shift();
11
+ }
12
+ else {
13
+ value = source(req);
14
+ }
15
+ return { text: JSON.stringify(value) };
16
+ },
17
+ };
18
+ }
19
+ //# sourceMappingURL=mock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock.js","sourceRoot":"","sources":["../src/mock.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY,CAAC,MAAuD;IAClF,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACzD,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,KAAK,CAAC,QAAQ,CAAC,GAAG;YAChB,IAAI,KAAc,CAAC;YACnB,IAAI,KAAK,EAAE,CAAC;gBACV,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;oBAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;gBACxE,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,KAAK,GAAI,MAA4C,CAAC,GAAG,CAAC,CAAC;YAC7D,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;QACzC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { NolaProvider } from "@nola-lang/core";
2
+ export interface OpenAiOptions {
3
+ apiKey?: string;
4
+ /** Env var name holding the key. Default: "OPENAI_API_KEY". Value is read lazily at the first request. */
5
+ apiKeyEnv?: string;
6
+ /** Required — there is no default model; every config names its own. */
7
+ model: string;
8
+ baseUrl?: string;
9
+ fetch?: typeof globalThis.fetch;
10
+ }
11
+ export declare function openai(options: OpenAiOptions): NolaProvider;
12
+ //# sourceMappingURL=openai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../src/openai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGhE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0GAA0G;IAC1G,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AA8HD,wBAAgB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CAmE3D"}
package/dist/openai.js ADDED
@@ -0,0 +1,189 @@
1
+ import { NolaProviderError } from "@nola-lang/core";
2
+ /** Strict mode requires every property required; optionals become anyOf [T, null]. */
3
+ function toStrict(schema) {
4
+ const out = toStrictNode(schema);
5
+ // $defs only ever appears at the root of our emissions; transform each def
6
+ // through the same strict rewrite so refs resolve to strict shapes.
7
+ if (schema.$defs) {
8
+ const defs = {};
9
+ for (const [key, def] of Object.entries(schema.$defs))
10
+ defs[key] = toStrictNode(def);
11
+ out.$defs = defs;
12
+ }
13
+ return out;
14
+ }
15
+ function toStrictNode(schema) {
16
+ if ("$ref" in schema)
17
+ return { $ref: schema.$ref };
18
+ switch (schema.type) {
19
+ case "object": {
20
+ const properties = {};
21
+ for (const [key, prop] of Object.entries(schema.properties)) {
22
+ const strict = toStrictNode(prop);
23
+ properties[key] = schema.required.includes(key) ? strict : { anyOf: [strict, { type: "null" }] };
24
+ }
25
+ return { type: "object", properties, required: Object.keys(schema.properties), additionalProperties: false };
26
+ }
27
+ case "array":
28
+ return { type: "array", items: toStrictNode(schema.items) };
29
+ default:
30
+ return schema.type === "string" && schema.enum
31
+ ? { type: "string", enum: [...schema.enum] }
32
+ : { type: schema.type };
33
+ }
34
+ }
35
+ // When a scalar/array schema is wrapped in the {value} envelope, the response_format
36
+ // alone only constrains providers that do constrained decoding. Generate-then-validate
37
+ // backends (e.g. Groq) follow the prompt, so the prompt must ask for the envelope too.
38
+ const ENVELOPE_NOTE = ' Because the response schema is wrapped, reply with a JSON object of the form {"value": X} where X is the value that conforms to responseSchema.';
39
+ /**
40
+ * Generate-then-validate backends return HTTP 400 `json_validate_failed` with the
41
+ * model's raw output in `error.failed_generation`. When that output is actually the
42
+ * answer (just unwrapped, or wrapped when we expected bare), recover it instead of
43
+ * hard-failing; the context layer re-validates it against the real schema.
44
+ */
45
+ function recoverFailedGeneration(errorBody, enveloped, schema) {
46
+ let failed;
47
+ try {
48
+ failed = JSON.parse(errorBody).error?.failed_generation;
49
+ }
50
+ catch {
51
+ return undefined;
52
+ }
53
+ if (typeof failed !== "string")
54
+ return undefined;
55
+ let gen;
56
+ try {
57
+ gen = JSON.parse(failed);
58
+ }
59
+ catch {
60
+ return undefined;
61
+ }
62
+ const wrapped = enveloped && gen !== null && typeof gen === "object" && !Array.isArray(gen) && "value" in gen;
63
+ const value = wrapped ? gen.value : gen;
64
+ return JSON.stringify(fromStrict(value, schema));
65
+ }
66
+ /** Remove null-valued optionals the strict transport introduced. */
67
+ function fromStrict(value, schema, defs) {
68
+ const activeDefs = schema.$defs ? { ...defs, ...schema.$defs } : defs;
69
+ if ("$ref" in schema) {
70
+ const name = /^#\/\$defs\/(.+)$/.exec(schema.$ref)?.[1];
71
+ const target = name ? activeDefs?.[name] : undefined;
72
+ // Data-driven recursion: each step consumes value structure, so it terminates.
73
+ return target ? fromStrict(value, target, activeDefs) : value;
74
+ }
75
+ if (schema.type === "object" && typeof value === "object" && value !== null && !Array.isArray(value)) {
76
+ const out = {};
77
+ for (const [key, v] of Object.entries(value)) {
78
+ const propSchema = schema.properties[key];
79
+ if (v === null && propSchema && !schema.required.includes(key))
80
+ continue;
81
+ out[key] = propSchema ? fromStrict(v, propSchema, activeDefs) : v;
82
+ }
83
+ return out;
84
+ }
85
+ if (schema.type === "array" && Array.isArray(value)) {
86
+ return value.map((item) => fromStrict(item, schema.items, activeDefs));
87
+ }
88
+ return value;
89
+ }
90
+ /** Follow root-level $ref chains so the envelope decision sees the real shape. */
91
+ function resolveRootRef(schema) {
92
+ let current = schema;
93
+ const defs = schema.$defs;
94
+ for (let i = 0; i < 32 && "$ref" in current; i++) {
95
+ const name = /^#\/\$defs\/(.+)$/.exec(current.$ref)?.[1];
96
+ const next = name ? defs?.[name] : undefined;
97
+ if (!next)
98
+ break;
99
+ current = next;
100
+ }
101
+ return current;
102
+ }
103
+ /** Wrap a non-object schema in the {value} envelope, hoisting $defs to the new root. */
104
+ function envelope(schema) {
105
+ const { $defs, ...rest } = schema;
106
+ return {
107
+ type: "object",
108
+ properties: { value: rest },
109
+ required: ["value"],
110
+ additionalProperties: false,
111
+ ...($defs ? { $defs } : {}),
112
+ };
113
+ }
114
+ /** Retry-After is either delta-seconds or an HTTP-date; both become a ms delta. */
115
+ function parseRetryAfter(header) {
116
+ if (header === null || header.trim() === "")
117
+ return undefined;
118
+ const seconds = Number(header);
119
+ if (Number.isFinite(seconds))
120
+ return Math.max(0, seconds * 1000);
121
+ const date = Date.parse(header);
122
+ return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());
123
+ }
124
+ export function openai(options) {
125
+ const doFetch = options.fetch ?? globalThis.fetch;
126
+ const baseUrl = (options.baseUrl ?? "https://api.openai.com/v1").replace(/\/$/, "");
127
+ const model = options.model;
128
+ return {
129
+ name: "openai",
130
+ async complete(req) {
131
+ const requestedAt = Date.now();
132
+ const envName = options.apiKeyEnv ?? "OPENAI_API_KEY";
133
+ const apiKey = options.apiKey ?? process.env[envName];
134
+ if (!apiKey) {
135
+ throw new NolaProviderError(`OpenAI API key not found: environment variable ${envName} is not set (checked process.env, including the project .env applied by the Nola loader) and no \`apiKey\` was passed to openai(). Fix: set ${envName}, or pass openai({ apiKeyEnv: "MY_VAR" }) or openai({ apiKey }) in nola.config.ts.`, { definitive: true });
136
+ }
137
+ const reqSchema = req.output.syntax === "json" ? req.output.schema : undefined;
138
+ const rootShape = reqSchema === undefined ? undefined : resolveRootRef(reqSchema);
139
+ const enveloped = rootShape !== undefined && !("$ref" in rootShape) && rootShape.type !== "object";
140
+ const transport = reqSchema === undefined ? undefined : enveloped ? envelope(reqSchema) : reqSchema;
141
+ const system = enveloped ? req.system + ENVELOPE_NOTE : req.system;
142
+ const body = {
143
+ model,
144
+ messages: [{ role: "system", content: system }, ...req.messages],
145
+ };
146
+ if (transport) {
147
+ body.response_format = {
148
+ type: "json_schema",
149
+ json_schema: { name: "nola_extraction", strict: true, schema: toStrict(transport) },
150
+ };
151
+ }
152
+ const res = await doFetch(`${baseUrl}/chat/completions`, {
153
+ method: "POST",
154
+ headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
155
+ body: JSON.stringify(body),
156
+ signal: req.signal ?? null,
157
+ });
158
+ if (!res.ok) {
159
+ const errorBody = await res.text();
160
+ if (reqSchema !== undefined) {
161
+ const recovered = recoverFailedGeneration(errorBody, enveloped, reqSchema);
162
+ if (recovered !== undefined)
163
+ return { text: recovered };
164
+ }
165
+ throw new NolaProviderError(`OpenAI request failed: ${res.status} ${res.statusText} — ${errorBody.slice(0, 500)}`, {
166
+ status: res.status,
167
+ retryAfterMs: parseRetryAfter(res.headers.get("retry-after")),
168
+ });
169
+ }
170
+ const data = (await res.json());
171
+ const content = data.choices?.[0]?.message?.content;
172
+ if (typeof content !== "string")
173
+ throw new NolaProviderError("OpenAI response had no message content.");
174
+ if (!reqSchema)
175
+ return { text: content };
176
+ let parsed;
177
+ try {
178
+ parsed = JSON.parse(content);
179
+ }
180
+ catch (e) {
181
+ throw new NolaProviderError("OpenAI returned non-JSON despite structured outputs.", { cause: e });
182
+ }
183
+ const value = enveloped ? parsed.value : parsed;
184
+ const durationMs = Date.now() - requestedAt;
185
+ return { text: JSON.stringify(fromStrict(value, reqSchema)), durationMs };
186
+ },
187
+ };
188
+ }
189
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../src/openai.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAcpD,sFAAsF;AACtF,SAAS,QAAQ,CAAC,MAAkB;IAClC,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACjC,2EAA2E;IAC3E,oEAAoE;IACpE,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,IAAI,GAAiC,EAAE,CAAC;QAC9C,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QACrF,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,YAAY,CAAC,MAAkB;IACtC,IAAI,MAAM,IAAI,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IACnD,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,UAAU,GAAiC,EAAE,CAAC;YACpD,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC5D,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;gBAClC,UAAU,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;YACnG,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAAC;QAC/G,CAAC;QACD,KAAK,OAAO;YACV,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9D;YACE,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI;gBAC5C,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE;gBAC5C,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IAC9B,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,uFAAuF;AACvF,uFAAuF;AACvF,MAAM,aAAa,GACjB,kJAAkJ,CAAC;AAErJ;;;;;GAKG;AACH,SAAS,uBAAuB,CAAC,SAAiB,EAAE,SAAkB,EAAE,MAAkB;IACxF,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAiD,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAC3G,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACjD,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,OAAO,GAAG,SAAS,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,OAAO,IAAI,GAAG,CAAC;IAC9G,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAE,GAA0B,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;IAChE,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,oEAAoE;AACpE,SAAS,UAAU,CAAC,KAAc,EAAE,MAAkB,EAAE,IAAiC;IACvF,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACtE,IAAI,MAAM,IAAI,MAAM,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACrD,+EAA+E;QAC/E,OAAO,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAChE,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACrG,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;YACxE,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YAC1C,IAAI,CAAC,KAAK,IAAI,IAAI,UAAU,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAAE,SAAS;YACzE,GAAG,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,kFAAkF;AAClF,SAAS,cAAc,CAAC,MAAkB;IACxC,IAAI,OAAO,GAAG,MAAM,CAAC;IACrB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC;IAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,MAAM,IAAI,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;QACjD,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7C,IAAI,CAAC,IAAI;YAAE,MAAM;QACjB,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,wFAAwF;AACxF,SAAS,QAAQ,CAAC,MAAkB;IAClC,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,MAA6D,CAAC;IACzF,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,EAAE,KAAK,EAAE,IAAkB,EAAE;QACzC,QAAQ,EAAE,CAAC,OAAO,CAAC;QACnB,oBAAoB,EAAE,KAAK;QAC3B,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5B,CAAC;AACJ,CAAC;AAED,mFAAmF;AACnF,SAAS,eAAe,CAAC,MAAqB;IAC5C,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAC9D,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/B,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,OAAsB;IAC3C,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;IAClD,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,2BAA2B,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACpF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5B,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,KAAK,CAAC,QAAQ,CAAC,GAAG;YAChB,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,IAAI,gBAAgB,CAAC;YACtD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACtD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,iBAAiB,CACzB,kDAAkD,OAAO,+IAA+I,OAAO,oFAAoF,EACnS,EAAE,UAAU,EAAE,IAAI,EAAE,CACrB,CAAC;YACJ,CAAC;YACD,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;YAC/E,MAAM,SAAS,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;YAClF,MAAM,SAAS,GAAG,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,IAAI,SAAS,CAAC,IAAI,SAAS,CAAC,IAAI,KAAK,QAAQ,CAAC;YACnG,MAAM,SAAS,GACb,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YACpF,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;YACnE,MAAM,IAAI,GAA4B;gBACpC,KAAK;gBACL,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC;aACjE,CAAC;YACF,IAAI,SAAS,EAAE,CAAC;gBACd,IAAI,CAAC,eAAe,GAAG;oBACrB,IAAI,EAAE,aAAa;oBACnB,WAAW,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE;iBACpF,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,OAAO,mBAAmB,EAAE;gBACvD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE;gBAClF,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;gBAC1B,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,IAAI;aAC3B,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;gBACnC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;oBAC5B,MAAM,SAAS,GAAG,uBAAuB,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;oBAC3E,IAAI,SAAS,KAAK,SAAS;wBAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;gBAC1D,CAAC;gBACD,MAAM,IAAI,iBAAiB,CACzB,0BAA0B,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EACrF;oBACE,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,YAAY,EAAE,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;iBAC9D,CACF,CAAC;YACJ,CAAC;YACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA6D,CAAC;YAC5F,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC;YACpD,IAAI,OAAO,OAAO,KAAK,QAAQ;gBAAE,MAAM,IAAI,iBAAiB,CAAC,yCAAyC,CAAC,CAAC;YACxG,IAAI,CAAC,SAAS;gBAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;YACzC,IAAI,MAAe,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC/B,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,IAAI,iBAAiB,CAAC,sDAAsD,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YACpG,CAAC;YACD,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAE,MAA8B,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;YACzE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC;YAC5C,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC;QAC5E,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { NolaProvider } from "@nola-lang/core";
2
+ /**
3
+ * Pass-through provider that appends `{ fingerprint, request, response }` JSONL
4
+ * entries. The fingerprint is computed from the RAW request (so replay-time
5
+ * lookups match) and is never redacted; persisted content strings are.
6
+ */
7
+ export declare function record(inner: NolaProvider, ledgerPath: string): NolaProvider;
8
+ /**
9
+ * Offline provider serving recorded responses by request fingerprint. Strict:
10
+ * an unrecorded request is a definitive error, never a silent live call.
11
+ */
12
+ export declare function replay(ledgerPath: string): NolaProvider;
13
+ //# sourceMappingURL=record-replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-replay.d.ts","sourceRoot":"","sources":["../src/record-replay.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,YAAY,CAkB5E;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CA0CvD"}
@@ -0,0 +1,69 @@
1
+ import { appendFileSync, readFileSync } from "node:fs";
2
+ import { Codes } from "@nola-lang/ast";
3
+ import { fingerprintRequest, NolaConfigError, NolaProviderError, redactSecrets } from "@nola-lang/core";
4
+ /**
5
+ * Pass-through provider that appends `{ fingerprint, request, response }` JSONL
6
+ * entries. The fingerprint is computed from the RAW request (so replay-time
7
+ * lookups match) and is never redacted; persisted content strings are.
8
+ */
9
+ export function record(inner, ledgerPath) {
10
+ return {
11
+ name: `record(${inner.name})`,
12
+ async complete(req) {
13
+ const res = await inner.complete(req);
14
+ const entry = {
15
+ fingerprint: fingerprintRequest(req),
16
+ request: {
17
+ system: redactSecrets(req.system),
18
+ messages: req.messages.map((m) => ({ role: m.role, content: redactSecrets(m.content) })),
19
+ output: req.output,
20
+ },
21
+ response: { text: redactSecrets(res.text) },
22
+ };
23
+ appendFileSync(ledgerPath, `${JSON.stringify(entry)}\n`, "utf8");
24
+ return res;
25
+ },
26
+ };
27
+ }
28
+ /**
29
+ * Offline provider serving recorded responses by request fingerprint. Strict:
30
+ * an unrecorded request is a definitive error, never a silent live call.
31
+ */
32
+ export function replay(ledgerPath) {
33
+ let raw;
34
+ try {
35
+ raw = readFileSync(ledgerPath, "utf8");
36
+ }
37
+ catch (error) {
38
+ throw new NolaConfigError(`replay ledger ${ledgerPath} cannot be read: ${error instanceof Error ? error.message : String(error)}`, Codes.ReplayLedgerInvalid);
39
+ }
40
+ const entries = new Map();
41
+ raw.split("\n").forEach((line, i) => {
42
+ if (line.trim() === "")
43
+ return;
44
+ let parsed;
45
+ try {
46
+ parsed = JSON.parse(line);
47
+ }
48
+ catch {
49
+ throw new NolaConfigError(`replay ledger ${ledgerPath}:${i + 1} is not valid JSON.`, Codes.ReplayLedgerInvalid);
50
+ }
51
+ const e = parsed;
52
+ if (typeof e.fingerprint !== "string" || typeof e.response?.text !== "string") {
53
+ throw new NolaConfigError(`replay ledger ${ledgerPath}:${i + 1} is missing fingerprint or response.text.`, Codes.ReplayLedgerInvalid);
54
+ }
55
+ entries.set(e.fingerprint, { text: e.response.text });
56
+ });
57
+ return {
58
+ name: "replay",
59
+ async complete(req) {
60
+ const fingerprint = fingerprintRequest(req);
61
+ const hit = entries.get(fingerprint);
62
+ if (!hit) {
63
+ throw new NolaProviderError(`[${Codes.ReplayFingerprintMismatch}] replay ledger ${ledgerPath} has no entry for fingerprint ${fingerprint} — the prompt, schema, or context changed since the ledger was recorded. Re-record it.`, { definitive: true });
64
+ }
65
+ return { text: hit.text };
66
+ },
67
+ };
68
+ }
69
+ //# sourceMappingURL=record-replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-replay.js","sourceRoot":"","sources":["../src/record-replay.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@nola-lang/providers",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "Nola LLM providers: openai, mock, resilience combinators, record/replay",
5
+ "keywords": [
6
+ "nola",
7
+ "llm",
8
+ "ai",
9
+ "openai",
10
+ "providers"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Evgen Mykhailenko",
14
+ "homepage": "https://github.com/nola-lang/nola#readme",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/nola-lang/nola.git",
18
+ "directory": "packages/providers"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/nola-lang/nola/issues"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "type": "module",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "dependencies": {
37
+ "@nola-lang/ast": "0.1.0-alpha.0",
38
+ "@nola-lang/core": "0.1.0-alpha.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=22"
42
+ }
43
+ }