@nola-lang/providers 0.1.13 → 0.1.14
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/dist/anthropic.d.ts +6 -0
- package/dist/anthropic.js +12 -8
- package/dist/combinators.d.ts +1 -1
- package/dist/combinators.js +11 -15
- package/dist/decision-request.d.ts +4 -0
- package/dist/decision-request.js +7 -0
- package/dist/decisions.js +3 -3
- package/dist/google.d.ts +6 -0
- package/dist/google.js +12 -8
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/mock.d.ts +5 -3
- package/dist/mock.js +11 -3
- package/dist/openai.d.ts +7 -0
- package/dist/openai.js +53 -16
- package/dist/record-replay.d.ts +14 -15
- package/dist/record-replay.js +18 -38
- package/dist/require-infer.d.ts +10 -0
- package/dist/require-infer.js +23 -0
- package/dist/typesafe.js +2 -2
- package/dist/wire.d.ts +3 -1
- package/dist/wire.js +6 -2
- package/package.json +3 -3
- package/dist/dialect.d.ts +0 -12
- package/dist/dialect.js +0 -25
package/dist/anthropic.d.ts
CHANGED
|
@@ -9,6 +9,12 @@ export interface AnthropicOptions {
|
|
|
9
9
|
fetch?: typeof globalThis.fetch;
|
|
10
10
|
/** The Messages API requires max_tokens on every request. Default: 4096. */
|
|
11
11
|
maxOutputTokens?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Default true: the schema rides the API's structured-output field. false:
|
|
14
|
+
* nothing is sent on the wire and the schema is rendered into the system
|
|
15
|
+
* turn as a <schema> block — for a proxy or server that cannot enforce it.
|
|
16
|
+
*/
|
|
17
|
+
structuredOutputs?: boolean;
|
|
12
18
|
}
|
|
13
19
|
/** A bare model string is shorthand for `{ model }` — every other option defaulted. */
|
|
14
20
|
export declare function anthropic(optionsOrModel: AnthropicOptions | string): LanguageModel;
|
package/dist/anthropic.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { NolaProviderError, parseRetryAfter } from "@nola-lang/core";
|
|
2
|
-
import { ENVELOPE_NOTE, envelope, resolveRootRef } from "./wire.js";
|
|
1
|
+
import { NolaProviderError, parseRetryAfter, renderPrompt } from "@nola-lang/core";
|
|
2
|
+
import { ENVELOPE_NOTE, envelope, resolveRootRef, schemaNote } from "./wire.js";
|
|
3
3
|
const DEFAULT_MAX_OUTPUT_TOKENS = 4096;
|
|
4
4
|
/** A bare model string is shorthand for `{ model }` — every other option defaulted. */
|
|
5
5
|
export function anthropic(optionsOrModel) {
|
|
@@ -10,26 +10,30 @@ export function anthropic(optionsOrModel) {
|
|
|
10
10
|
const maxTokens = options.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
|
|
11
11
|
return {
|
|
12
12
|
name: "anthropic",
|
|
13
|
-
async
|
|
13
|
+
async infer(req) {
|
|
14
14
|
const requestedAt = Date.now();
|
|
15
15
|
const envName = options.apiKeyEnv ?? "ANTHROPIC_API_KEY";
|
|
16
16
|
const apiKey = options.apiKey ?? process.env[envName];
|
|
17
17
|
if (!apiKey) {
|
|
18
18
|
throw new NolaProviderError(`Anthropic 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 anthropic(). Fix: set ${envName}, or pass anthropic({ apiKeyEnv: "MY_VAR" }) or anthropic({ apiKey }) in nola.config.ts.`, { definitive: true });
|
|
19
19
|
}
|
|
20
|
-
const { system: baseSystem, messages
|
|
20
|
+
const { system: baseSystem, messages } = renderPrompt(req.intent);
|
|
21
|
+
const output = req.intent.output;
|
|
21
22
|
const reqSchema = output.syntax === "json" ? output.schema : undefined;
|
|
22
23
|
const rootShape = reqSchema === undefined ? undefined : resolveRootRef(reqSchema);
|
|
23
24
|
const enveloped = rootShape !== undefined && !("$ref" in rootShape) && !("type" in rootShape && rootShape.type === "object");
|
|
24
25
|
const transport = reqSchema === undefined ? undefined : enveloped ? envelope(reqSchema) : reqSchema;
|
|
25
|
-
|
|
26
|
+
// structuredOutputs: false — the schema goes into the system turn instead of the wire field
|
|
27
|
+
const enforce = options.structuredOutputs !== false;
|
|
28
|
+
const system = (enveloped ? baseSystem + ENVELOPE_NOTE : baseSystem) + (!enforce && transport ? schemaNote(transport) : "");
|
|
29
|
+
const sent = { system, messages };
|
|
26
30
|
const body = {
|
|
27
31
|
model,
|
|
28
32
|
max_tokens: maxTokens,
|
|
29
33
|
system,
|
|
30
34
|
messages,
|
|
31
35
|
};
|
|
32
|
-
if (transport) {
|
|
36
|
+
if (transport && enforce) {
|
|
33
37
|
body.output_config = { format: { type: "json_schema", schema: transport } };
|
|
34
38
|
}
|
|
35
39
|
const res = await doFetch(`${baseUrl}/v1/messages`, {
|
|
@@ -52,7 +56,7 @@ export function anthropic(optionsOrModel) {
|
|
|
52
56
|
const content = textBlock.text;
|
|
53
57
|
const durationMs = Date.now() - requestedAt;
|
|
54
58
|
if (!reqSchema)
|
|
55
|
-
return { text: content, durationMs };
|
|
59
|
+
return { text: content, durationMs, sent };
|
|
56
60
|
let parsed;
|
|
57
61
|
try {
|
|
58
62
|
parsed = JSON.parse(content);
|
|
@@ -61,7 +65,7 @@ export function anthropic(optionsOrModel) {
|
|
|
61
65
|
throw new NolaProviderError("Anthropic returned non-JSON despite structured outputs.", { cause: e });
|
|
62
66
|
}
|
|
63
67
|
const value = enveloped ? parsed.value : parsed;
|
|
64
|
-
return { text: JSON.stringify(value), durationMs };
|
|
68
|
+
return { text: JSON.stringify(value), durationMs, sent };
|
|
65
69
|
},
|
|
66
70
|
};
|
|
67
71
|
}
|
package/dist/combinators.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export declare function isDefinitiveProviderError(error: unknown): boolean;
|
|
|
25
25
|
* with no delay) ignores the header entirely. Distinct from the intent method
|
|
26
26
|
* `.withRetry(n)`, which flat-retries the entire ask (composition, provider
|
|
27
27
|
* call, parse, validation) with no backoff and no definitive-error check.
|
|
28
|
-
*
|
|
28
|
+
* Forwards `infer` and carries the inner's decision brand.
|
|
29
29
|
*/
|
|
30
30
|
export declare function withRetry(provider: LanguageModel, policy: RetryPolicy): LanguageModel;
|
|
31
31
|
export declare function fallback(providers: LanguageModel[]): LanguageModel;
|
package/dist/combinators.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Codes } from "@nola-lang/ast";
|
|
2
|
-
import { DECISION_MODEL, isDecisionModel,
|
|
3
|
-
import {
|
|
2
|
+
import { DECISION_MODEL, isDecisionModel, isPlatformModel, NolaConfigError, NolaProviderError } from "@nola-lang/core";
|
|
3
|
+
import { isDecisionRequest } from "./decision-request.js";
|
|
4
|
+
import { requireInfer } from "./require-infer.js";
|
|
4
5
|
export function constant(opts) {
|
|
5
6
|
const delayMs = opts.delayMs ?? 0;
|
|
6
7
|
return { maxRetries: opts.maxRetries, delayMs, multiplier: 1, maxDelayMs: delayMs };
|
|
@@ -45,18 +46,10 @@ function rejectPlatformModel(models, combinator) {
|
|
|
45
46
|
function describeError(error) {
|
|
46
47
|
return error instanceof Error ? error.message : String(error);
|
|
47
48
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Build the outer model of a combinator (decision types spec 2026-09-18
|
|
50
|
-
* §6.1–6.2): infer-dialect iff any inner is (chat inners get the rendering
|
|
51
|
-
* through callModel), branded a decision model iff any inner is. `run`
|
|
52
|
-
* receives the request in the outer dialect.
|
|
53
|
-
*/
|
|
49
|
+
/** Build the outer model of a combinator: branded a decision model iff any inner is; `run` receives the request as is. */
|
|
54
50
|
function outer(name, inners, run) {
|
|
55
51
|
const brand = inners.some((m) => isDecisionModel(m)) ? { [DECISION_MODEL]: true } : {};
|
|
56
|
-
|
|
57
|
-
? { name, ...brand, infer: (req) => run(req) }
|
|
58
|
-
: { name, ...brand, complete: (req) => run(req) };
|
|
59
|
-
return model;
|
|
52
|
+
return { name, ...brand, infer: (req) => run(req) };
|
|
60
53
|
}
|
|
61
54
|
/**
|
|
62
55
|
* Wire-level retry: re-attempts the single provider call with backoff,
|
|
@@ -66,9 +59,10 @@ function outer(name, inners, run) {
|
|
|
66
59
|
* with no delay) ignores the header entirely. Distinct from the intent method
|
|
67
60
|
* `.withRetry(n)`, which flat-retries the entire ask (composition, provider
|
|
68
61
|
* call, parse, validation) with no backoff and no definitive-error check.
|
|
69
|
-
*
|
|
62
|
+
* Forwards `infer` and carries the inner's decision brand.
|
|
70
63
|
*/
|
|
71
64
|
export function withRetry(provider, policy) {
|
|
65
|
+
requireInfer([provider], "withRetry");
|
|
72
66
|
rejectPlatformModel([provider], "withRetry");
|
|
73
67
|
const inner = provider;
|
|
74
68
|
return outer(`retry(${provider.name})`, [inner], async (req) => {
|
|
@@ -76,7 +70,7 @@ export function withRetry(provider, policy) {
|
|
|
76
70
|
let lastError;
|
|
77
71
|
for (let attempt = 0; attempt <= policy.maxRetries; attempt++) {
|
|
78
72
|
try {
|
|
79
|
-
return await
|
|
73
|
+
return await inner.infer(req);
|
|
80
74
|
}
|
|
81
75
|
catch (error) {
|
|
82
76
|
lastError = error;
|
|
@@ -100,7 +94,7 @@ async function tryInOrder(name, ordered, req) {
|
|
|
100
94
|
continue;
|
|
101
95
|
}
|
|
102
96
|
try {
|
|
103
|
-
return await
|
|
97
|
+
return await p.infer(req);
|
|
104
98
|
}
|
|
105
99
|
catch (error) {
|
|
106
100
|
failures.push(`${p.name}: ${describeError(error)}`);
|
|
@@ -110,6 +104,7 @@ async function tryInOrder(name, ordered, req) {
|
|
|
110
104
|
}
|
|
111
105
|
export function fallback(providers) {
|
|
112
106
|
requireModels(providers, "fallback");
|
|
107
|
+
requireInfer(providers, "fallback");
|
|
113
108
|
rejectPlatformModel(providers, "fallback");
|
|
114
109
|
const inners = providers;
|
|
115
110
|
const name = `fallback(${providers.map((p) => p.name).join(", ")})`;
|
|
@@ -117,6 +112,7 @@ export function fallback(providers) {
|
|
|
117
112
|
}
|
|
118
113
|
export function roundRobin(providers) {
|
|
119
114
|
requireModels(providers, "roundRobin");
|
|
115
|
+
requireInfer(providers, "roundRobin");
|
|
120
116
|
rejectPlatformModel(providers, "roundRobin");
|
|
121
117
|
const inners = providers;
|
|
122
118
|
const name = `roundRobin(${providers.map((p) => p.name).join(", ")})`;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { InferRequest } from "@nola-lang/core";
|
|
2
|
+
/** True when the request's output schema carries a decision question (Choice / Scale / Prob). */
|
|
3
|
+
export declare function isDecisionRequest(req: InferRequest): boolean;
|
|
4
|
+
//# sourceMappingURL=decision-request.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { findDecisionQuestions } from "@nola-lang/core";
|
|
2
|
+
/** True when the request's output schema carries a decision question (Choice / Scale / Prob). */
|
|
3
|
+
export function isDecisionRequest(req) {
|
|
4
|
+
const output = req.intent.output;
|
|
5
|
+
return output.syntax === "json" && findDecisionQuestions(output.schema).length > 0;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=decision-request.js.map
|
package/dist/decisions.js
CHANGED
|
@@ -26,12 +26,12 @@ function contextualState(model) {
|
|
|
26
26
|
}
|
|
27
27
|
return any ? state : undefined;
|
|
28
28
|
}
|
|
29
|
-
const askText = (model) => model.input.
|
|
30
|
-
/** What the model says about the ask, outer→inner: system, each scope's
|
|
29
|
+
const askText = (model) => model.input.instruction;
|
|
30
|
+
/** What the model says about the ask, outer→inner: system, each scope's instruction, then (optionally) the ask text. */
|
|
31
31
|
function contextText(model, includeAskText) {
|
|
32
32
|
const parts = [
|
|
33
33
|
model.system ?? "",
|
|
34
|
-
...scopesOuterFirst(model.scope).map((s) => s.
|
|
34
|
+
...scopesOuterFirst(model.scope).map((s) => s.instruction),
|
|
35
35
|
includeAskText ? askText(model) : "",
|
|
36
36
|
];
|
|
37
37
|
return parts.filter((p) => p.trim() !== "").reduce((acc, p) => joinBlocks(acc, p), "");
|
package/dist/google.d.ts
CHANGED
|
@@ -7,6 +7,12 @@ export interface GoogleOptions {
|
|
|
7
7
|
model: string;
|
|
8
8
|
baseUrl?: string;
|
|
9
9
|
fetch?: typeof globalThis.fetch;
|
|
10
|
+
/**
|
|
11
|
+
* Default true: the schema rides the API's structured-output field. false:
|
|
12
|
+
* nothing is sent on the wire and the schema is rendered into the system
|
|
13
|
+
* turn as a <schema> block — for a proxy or server that cannot enforce it.
|
|
14
|
+
*/
|
|
15
|
+
structuredOutputs?: boolean;
|
|
10
16
|
}
|
|
11
17
|
/** A bare model string is shorthand for `{ model }` — every other option defaulted. */
|
|
12
18
|
export declare function google(optionsOrModel: GoogleOptions | string): LanguageModel;
|
package/dist/google.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { NolaProviderError, parseRetryAfter } from "@nola-lang/core";
|
|
2
|
-
import { ENVELOPE_NOTE, envelope, resolveRootRef } from "./wire.js";
|
|
1
|
+
import { NolaProviderError, parseRetryAfter, renderPrompt } from "@nola-lang/core";
|
|
2
|
+
import { ENVELOPE_NOTE, envelope, resolveRootRef, schemaNote } from "./wire.js";
|
|
3
3
|
/** A bare model string is shorthand for `{ model }` — every other option defaulted. */
|
|
4
4
|
export function google(optionsOrModel) {
|
|
5
5
|
const options = typeof optionsOrModel === "string" ? { model: optionsOrModel } : optionsOrModel;
|
|
@@ -8,19 +8,23 @@ export function google(optionsOrModel) {
|
|
|
8
8
|
const model = options.model;
|
|
9
9
|
return {
|
|
10
10
|
name: "google",
|
|
11
|
-
async
|
|
11
|
+
async infer(req) {
|
|
12
12
|
const requestedAt = Date.now();
|
|
13
13
|
const envName = options.apiKeyEnv ?? "GEMINI_API_KEY";
|
|
14
14
|
const apiKey = options.apiKey ?? process.env[envName];
|
|
15
15
|
if (!apiKey) {
|
|
16
16
|
throw new NolaProviderError(`Google 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 google(). Fix: set ${envName}, or pass google({ apiKeyEnv: "MY_VAR" }) or google({ apiKey }) in nola.config.ts.`, { definitive: true });
|
|
17
17
|
}
|
|
18
|
-
const { system: baseSystem, messages
|
|
18
|
+
const { system: baseSystem, messages } = renderPrompt(req.intent);
|
|
19
|
+
const output = req.intent.output;
|
|
19
20
|
const reqSchema = output.syntax === "json" ? output.schema : undefined;
|
|
20
21
|
const rootShape = reqSchema === undefined ? undefined : resolveRootRef(reqSchema);
|
|
21
22
|
const enveloped = rootShape !== undefined && !("$ref" in rootShape) && !("type" in rootShape && rootShape.type === "object");
|
|
22
23
|
const transport = reqSchema === undefined ? undefined : enveloped ? envelope(reqSchema) : reqSchema;
|
|
23
|
-
|
|
24
|
+
// structuredOutputs: false — the schema goes into the system turn instead of the wire field
|
|
25
|
+
const enforce = options.structuredOutputs !== false;
|
|
26
|
+
const system = (enveloped ? baseSystem + ENVELOPE_NOTE : baseSystem) + (!enforce && transport ? schemaNote(transport) : "");
|
|
27
|
+
const sent = { system, messages };
|
|
24
28
|
const body = {
|
|
25
29
|
system_instruction: { parts: [{ text: system }] },
|
|
26
30
|
contents: messages.map((m) => ({
|
|
@@ -28,7 +32,7 @@ export function google(optionsOrModel) {
|
|
|
28
32
|
parts: [{ text: m.content }],
|
|
29
33
|
})),
|
|
30
34
|
};
|
|
31
|
-
if (transport) {
|
|
35
|
+
if (transport && enforce) {
|
|
32
36
|
body.generationConfig = { responseMimeType: "application/json", responseJsonSchema: transport };
|
|
33
37
|
}
|
|
34
38
|
const res = await doFetch(`${baseUrl}/v1beta/models/${model}:generateContent`, {
|
|
@@ -51,7 +55,7 @@ export function google(optionsOrModel) {
|
|
|
51
55
|
const content = part.text;
|
|
52
56
|
const durationMs = Date.now() - requestedAt;
|
|
53
57
|
if (!reqSchema)
|
|
54
|
-
return { text: content, durationMs };
|
|
58
|
+
return { text: content, durationMs, sent };
|
|
55
59
|
let parsed;
|
|
56
60
|
try {
|
|
57
61
|
parsed = JSON.parse(content);
|
|
@@ -60,7 +64,7 @@ export function google(optionsOrModel) {
|
|
|
60
64
|
throw new NolaProviderError("Google returned non-JSON despite structured output.", { cause: e });
|
|
61
65
|
}
|
|
62
66
|
const value = enveloped ? parsed.value : parsed;
|
|
63
|
-
return { text: JSON.stringify(value), durationMs };
|
|
67
|
+
return { text: JSON.stringify(value), durationMs, sent };
|
|
64
68
|
},
|
|
65
69
|
};
|
|
66
70
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,9 +3,10 @@ import { google } from "./google.js";
|
|
|
3
3
|
import { mockProvider } from "./mock.js";
|
|
4
4
|
import { openai } from "./openai.js";
|
|
5
5
|
import { typesafe } from "./typesafe.js";
|
|
6
|
+
export { DEFAULT_SYSTEM, type InferRequest, type InferResult, type RenderedPrompt, renderPrompt } from "@nola-lang/core";
|
|
6
7
|
export { type AnthropicOptions, anthropic } from "./anthropic.js";
|
|
7
8
|
export { constant, exponential, fallback, isDefinitiveProviderError, type RetryPolicy, roundRobin, withRetry, } from "./combinators.js";
|
|
8
|
-
export {
|
|
9
|
+
export { isDecisionRequest } from "./decision-request.js";
|
|
9
10
|
export { type GoogleOptions, google } from "./google.js";
|
|
10
11
|
export { type MockOptions, type MockRequest, mockProvider } from "./mock.js";
|
|
11
12
|
export { type OpenAiOptions, openai } from "./openai.js";
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,10 @@ import { google } from "./google.js";
|
|
|
3
3
|
import { mockProvider } from "./mock.js";
|
|
4
4
|
import { openai } from "./openai.js";
|
|
5
5
|
import { typesafe } from "./typesafe.js";
|
|
6
|
+
export { DEFAULT_SYSTEM, renderPrompt } from "@nola-lang/core";
|
|
6
7
|
export { anthropic } from "./anthropic.js";
|
|
7
8
|
export { constant, exponential, fallback, isDefinitiveProviderError, roundRobin, withRetry, } from "./combinators.js";
|
|
8
|
-
export {
|
|
9
|
+
export { isDecisionRequest } from "./decision-request.js";
|
|
9
10
|
export { google } from "./google.js";
|
|
10
11
|
export { mockProvider } from "./mock.js";
|
|
11
12
|
export { openai } from "./openai.js";
|
package/dist/mock.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { LanguageModel,
|
|
2
|
-
/** What a mock callback sees: the
|
|
3
|
-
export type MockRequest =
|
|
1
|
+
import type { InferRequest, LanguageModel, RenderedPrompt } from "@nola-lang/core";
|
|
2
|
+
/** What a mock callback sees: the intent, plus its default rendering on demand. */
|
|
3
|
+
export type MockRequest = InferRequest & {
|
|
4
|
+
readonly prompt: RenderedPrompt;
|
|
5
|
+
};
|
|
4
6
|
export interface MockOptions {
|
|
5
7
|
/**
|
|
6
8
|
* Brand the mock a decision model (decision types spec 2026-09-18 §6.1): a
|
package/dist/mock.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import { DECISION_MODEL } from "@nola-lang/core";
|
|
1
|
+
import { DECISION_MODEL, renderPrompt } from "@nola-lang/core";
|
|
2
|
+
/** The request with a memoized `prompt` getter — rendered only when a callback reads it. */
|
|
3
|
+
function withPrompt(req) {
|
|
4
|
+
let rendered;
|
|
5
|
+
return Object.defineProperty({ ...req }, "prompt", {
|
|
6
|
+
enumerable: false,
|
|
7
|
+
get: () => (rendered ??= renderPrompt(req.intent)),
|
|
8
|
+
});
|
|
9
|
+
}
|
|
2
10
|
export function mockProvider(source, options = {}) {
|
|
3
11
|
const queue = Array.isArray(source) ? [...source] : null;
|
|
4
12
|
return {
|
|
5
13
|
...(options.decisions ? { [DECISION_MODEL]: true } : {}),
|
|
6
14
|
name: "mock",
|
|
7
|
-
async
|
|
15
|
+
async infer(req) {
|
|
8
16
|
let value;
|
|
9
17
|
if (queue) {
|
|
10
18
|
if (queue.length === 0)
|
|
@@ -12,7 +20,7 @@ export function mockProvider(source, options = {}) {
|
|
|
12
20
|
value = queue.shift();
|
|
13
21
|
}
|
|
14
22
|
else {
|
|
15
|
-
value = source(req);
|
|
23
|
+
value = source(withPrompt(req));
|
|
16
24
|
}
|
|
17
25
|
return { text: JSON.stringify(value) };
|
|
18
26
|
},
|
package/dist/openai.d.ts
CHANGED
|
@@ -7,6 +7,13 @@ export interface OpenAiOptions {
|
|
|
7
7
|
model: string;
|
|
8
8
|
baseUrl?: string;
|
|
9
9
|
fetch?: typeof globalThis.fetch;
|
|
10
|
+
/**
|
|
11
|
+
* Default true: the schema rides the API's structured-output field. false:
|
|
12
|
+
* nothing is sent on the wire and the schema is rendered into the system
|
|
13
|
+
* turn as a <schema> block — for OpenAI-compatible servers that ignore or
|
|
14
|
+
* reject json_schema.
|
|
15
|
+
*/
|
|
16
|
+
structuredOutputs?: boolean;
|
|
10
17
|
}
|
|
11
18
|
/** A bare model string is shorthand for `{ model }` — every other option defaulted. */
|
|
12
19
|
export declare function openai(optionsOrModel: OpenAiOptions | string): LanguageModel;
|
package/dist/openai.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
import { NolaProviderError, parseRetryAfter } from "@nola-lang/core";
|
|
2
|
-
import { ENVELOPE_NOTE, envelope, resolveRootRef } from "./wire.js";
|
|
1
|
+
import { NolaProviderError, parseRetryAfter, renderPrompt } from "@nola-lang/core";
|
|
2
|
+
import { ENVELOPE_NOTE, envelope, resolveRootRef, schemaNote } from "./wire.js";
|
|
3
|
+
/**
|
|
4
|
+
* Keywords OpenAI's strict mode accepts beside type/enum/structure (its
|
|
5
|
+
* "supported properties" list); anything else is dropped. `format` is
|
|
6
|
+
* forwarded only for the values that list names — an unknown format value is
|
|
7
|
+
* a 400 from the API. minLength/maxLength are NOT forwarded: unverified.
|
|
8
|
+
*/
|
|
9
|
+
const STRING_KEYWORDS = ["pattern"];
|
|
10
|
+
const OPENAI_FORMATS = new Set(["date-time", "time", "date", "duration", "email", "hostname", "ipv4", "ipv6", "uuid"]);
|
|
11
|
+
const NUMBER_KEYWORDS = ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
|
|
12
|
+
const ARRAY_KEYWORDS = ["minItems", "maxItems"];
|
|
13
|
+
function pick(schema, keys) {
|
|
14
|
+
const out = {};
|
|
15
|
+
const s = schema;
|
|
16
|
+
for (const k of keys)
|
|
17
|
+
if (s[k] !== undefined)
|
|
18
|
+
out[k] = s[k];
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
3
21
|
/** Strict mode requires every property required; optionals become anyOf [T, null]. */
|
|
4
22
|
function toStrict(schema) {
|
|
5
23
|
const out = toStrictNode(schema);
|
|
@@ -17,18 +35,22 @@ function toStrict(schema) {
|
|
|
17
35
|
function toStrictNode(schema) {
|
|
18
36
|
if ("$ref" in schema)
|
|
19
37
|
return { $ref: schema.$ref };
|
|
38
|
+
// JSDoc descriptions ride every node: since the prompt no longer carries the
|
|
39
|
+
// schema text (prompt-rendering spec 2026-09-28), the wire schema is the only
|
|
40
|
+
// channel a property's description has to the model.
|
|
41
|
+
const described = "description" in schema && schema.description !== undefined ? { description: schema.description } : {};
|
|
20
42
|
// emit 15 shapes: choice passes through; strict mode accepts anyOf.
|
|
21
43
|
// A literal keeps its `const` but gains the `type` it implies: OpenAI-compatible
|
|
22
44
|
// backends (Cerebras) reject a bare `{ const }` node as an unsupported field,
|
|
23
45
|
// and every backend accepts the typed form (it is a strict subset).
|
|
24
46
|
if ("anyOf" in schema)
|
|
25
|
-
return { anyOf: schema.anyOf.map(toStrictNode) };
|
|
47
|
+
return { ...described, anyOf: schema.anyOf.map(toStrictNode) };
|
|
26
48
|
if ("const" in schema)
|
|
27
|
-
return { type: typeof schema.const, const: schema.const };
|
|
49
|
+
return { ...described, type: typeof schema.const, const: schema.const };
|
|
28
50
|
switch (schema.type) {
|
|
29
51
|
case "object": {
|
|
30
52
|
if (!("properties" in schema)) {
|
|
31
|
-
return { type: "object", additionalProperties: toStrictNode(schema.additionalProperties) };
|
|
53
|
+
return { ...described, type: "object", additionalProperties: toStrictNode(schema.additionalProperties) };
|
|
32
54
|
}
|
|
33
55
|
const properties = {};
|
|
34
56
|
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
@@ -36,6 +58,7 @@ function toStrictNode(schema) {
|
|
|
36
58
|
properties[key] = schema.required.includes(key) ? strict : { anyOf: [strict, { type: "null" }] };
|
|
37
59
|
}
|
|
38
60
|
return {
|
|
61
|
+
...described,
|
|
39
62
|
type: "object",
|
|
40
63
|
properties,
|
|
41
64
|
required: Object.keys(schema.properties),
|
|
@@ -45,6 +68,7 @@ function toStrictNode(schema) {
|
|
|
45
68
|
case "array":
|
|
46
69
|
if ("prefixItems" in schema) {
|
|
47
70
|
return {
|
|
71
|
+
...described,
|
|
48
72
|
type: "array",
|
|
49
73
|
prefixItems: schema.prefixItems.map(toStrictNode),
|
|
50
74
|
items: false,
|
|
@@ -52,13 +76,22 @@ function toStrictNode(schema) {
|
|
|
52
76
|
maxItems: schema.maxItems,
|
|
53
77
|
};
|
|
54
78
|
}
|
|
55
|
-
return { type: "array", items: toStrictNode(schema.items) };
|
|
79
|
+
return { ...described, type: "array", items: toStrictNode(schema.items), ...pick(schema, ARRAY_KEYWORDS) };
|
|
80
|
+
case "string":
|
|
81
|
+
return {
|
|
82
|
+
...described,
|
|
83
|
+
type: "string",
|
|
84
|
+
...(schema.enum ? { enum: [...schema.enum] } : {}),
|
|
85
|
+
...pick(schema, STRING_KEYWORDS),
|
|
86
|
+
...(schema.format !== undefined && OPENAI_FORMATS.has(schema.format) ? { format: schema.format } : {}),
|
|
87
|
+
};
|
|
88
|
+
case "number":
|
|
89
|
+
case "integer":
|
|
90
|
+
return { ...described, type: schema.type, ...pick(schema, NUMBER_KEYWORDS) };
|
|
56
91
|
case "null":
|
|
57
92
|
return { type: "null" };
|
|
58
93
|
default:
|
|
59
|
-
return
|
|
60
|
-
? { type: "string", enum: [...schema.enum] }
|
|
61
|
-
: { type: schema.type };
|
|
94
|
+
return { ...described, type: schema.type };
|
|
62
95
|
}
|
|
63
96
|
}
|
|
64
97
|
/**
|
|
@@ -128,24 +161,28 @@ export function openai(optionsOrModel) {
|
|
|
128
161
|
const model = options.model;
|
|
129
162
|
return {
|
|
130
163
|
name: "openai",
|
|
131
|
-
async
|
|
164
|
+
async infer(req) {
|
|
132
165
|
const requestedAt = Date.now();
|
|
133
166
|
const envName = options.apiKeyEnv ?? "OPENAI_API_KEY";
|
|
134
167
|
const apiKey = options.apiKey ?? process.env[envName];
|
|
135
168
|
if (!apiKey) {
|
|
136
169
|
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 });
|
|
137
170
|
}
|
|
138
|
-
const { system: baseSystem, messages
|
|
171
|
+
const { system: baseSystem, messages } = renderPrompt(req.intent);
|
|
172
|
+
const output = req.intent.output;
|
|
139
173
|
const reqSchema = output.syntax === "json" ? output.schema : undefined;
|
|
140
174
|
const rootShape = reqSchema === undefined ? undefined : resolveRootRef(reqSchema);
|
|
141
175
|
const enveloped = rootShape !== undefined && !("$ref" in rootShape) && !("type" in rootShape && rootShape.type === "object");
|
|
142
176
|
const transport = reqSchema === undefined ? undefined : enveloped ? envelope(reqSchema) : reqSchema;
|
|
143
|
-
|
|
177
|
+
// structuredOutputs: false — the schema goes into the system turn instead of the wire field
|
|
178
|
+
const enforce = options.structuredOutputs !== false;
|
|
179
|
+
const system = (enveloped ? baseSystem + ENVELOPE_NOTE : baseSystem) + (!enforce && transport ? schemaNote(transport) : "");
|
|
180
|
+
const sent = { system, messages };
|
|
144
181
|
const body = {
|
|
145
182
|
model,
|
|
146
183
|
messages: [{ role: "system", content: system }, ...messages],
|
|
147
184
|
};
|
|
148
|
-
if (transport) {
|
|
185
|
+
if (transport && enforce) {
|
|
149
186
|
body.response_format = {
|
|
150
187
|
type: "json_schema",
|
|
151
188
|
json_schema: { name: "nola_extraction", strict: true, schema: toStrict(transport) },
|
|
@@ -162,7 +199,7 @@ export function openai(optionsOrModel) {
|
|
|
162
199
|
if (reqSchema !== undefined) {
|
|
163
200
|
const recovered = recoverFailedGeneration(errorBody, enveloped, reqSchema);
|
|
164
201
|
if (recovered !== undefined)
|
|
165
|
-
return { text: recovered };
|
|
202
|
+
return { text: recovered, sent };
|
|
166
203
|
}
|
|
167
204
|
throw new NolaProviderError(`OpenAI request failed: ${res.status} ${res.statusText} — ${errorBody.slice(0, 500)}`, {
|
|
168
205
|
status: res.status,
|
|
@@ -174,7 +211,7 @@ export function openai(optionsOrModel) {
|
|
|
174
211
|
if (typeof content !== "string")
|
|
175
212
|
throw new NolaProviderError("OpenAI response had no message content.");
|
|
176
213
|
if (!reqSchema)
|
|
177
|
-
return { text: content };
|
|
214
|
+
return { text: content, sent };
|
|
178
215
|
let parsed;
|
|
179
216
|
try {
|
|
180
217
|
parsed = JSON.parse(content);
|
|
@@ -184,7 +221,7 @@ export function openai(optionsOrModel) {
|
|
|
184
221
|
}
|
|
185
222
|
const value = enveloped ? parsed.value : parsed;
|
|
186
223
|
const durationMs = Date.now() - requestedAt;
|
|
187
|
-
return { text: JSON.stringify(fromStrict(value, reqSchema)), durationMs };
|
|
224
|
+
return { text: JSON.stringify(fromStrict(value, reqSchema)), durationMs, sent };
|
|
188
225
|
},
|
|
189
226
|
};
|
|
190
227
|
}
|
package/dist/record-replay.d.ts
CHANGED
|
@@ -3,10 +3,9 @@ import type { LanguageModel, PlatformModel } from "@nola-lang/core";
|
|
|
3
3
|
* Pass-through provider that appends `{ fingerprint, request, response }` JSONL
|
|
4
4
|
* entries. The fingerprint is computed from the RAW request (so replay-time
|
|
5
5
|
* lookups match) and is never redacted; persisted content strings are. The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* platform model exposing infer; record(openai()) exposes complete.
|
|
6
|
+
* ledger stores the INTENT (the ask as data, never a rendering), and `record`
|
|
7
|
+
* carries the inner's brands: a platform inner yields a branded platform
|
|
8
|
+
* model, a decision inner a decision model.
|
|
10
9
|
*/
|
|
11
10
|
export declare function record(inner: PlatformModel, ledgerPath: string): PlatformModel;
|
|
12
11
|
export declare function record(inner: LanguageModel, ledgerPath: string): LanguageModel;
|
|
@@ -14,17 +13,17 @@ export declare function record(inner: LanguageModel, ledgerPath: string): Langua
|
|
|
14
13
|
* Offline provider serving recorded responses by request fingerprint. Strict:
|
|
15
14
|
* an unrecorded request is a definitive error, never a silent live call.
|
|
16
15
|
*
|
|
17
|
-
* A fingerprint is computed over the
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
16
|
+
* A fingerprint is computed over the intent as first composed (see
|
|
17
|
+
* `fingerprintRequest`), so a ledger recorded through any provider replays
|
|
18
|
+
* for any other, and one ask's first attempt and its correction retry hash
|
|
19
|
+
* identically — a recorded correction pair appends TWO ledger lines under
|
|
20
|
+
* the SAME key. Entries are served FIFO per fingerprint: each `infer()`
|
|
21
|
+
* shifts the next recorded response off that key's queue, so a replayed
|
|
22
|
+
* session reproduces the correction turn instead of jumping straight to the
|
|
23
|
+
* corrected answer. Once only one entry remains for a key it keeps serving
|
|
24
|
+
* that last one — a single-entry key (the common case) behaves exactly as a
|
|
25
|
+
* plain map lookup, and repeat asks beyond what was recorded still get an
|
|
26
|
+
* answer instead of failing.
|
|
28
27
|
*/
|
|
29
28
|
export declare function replay(ledgerPath: string): LanguageModel;
|
|
30
29
|
//# sourceMappingURL=record-replay.d.ts.map
|
package/dist/record-replay.js
CHANGED
|
@@ -1,41 +1,21 @@
|
|
|
1
1
|
import { appendFileSync, readFileSync } from "node:fs";
|
|
2
2
|
import { Codes } from "@nola-lang/ast";
|
|
3
|
-
import { DECISION_MODEL, fingerprintRequest, isDecisionModel,
|
|
3
|
+
import { DECISION_MODEL, fingerprintRequest, isDecisionModel, isPlatformModel, NolaConfigError, NolaProviderError, PLATFORM_MODEL, redactDeep, redactSecrets, } from "@nola-lang/core";
|
|
4
|
+
import { requireInfer } from "./require-infer.js";
|
|
4
5
|
export function record(inner, ledgerPath) {
|
|
5
|
-
|
|
6
|
+
requireInfer([inner], "record");
|
|
6
7
|
const brands = {
|
|
7
8
|
...(isPlatformModel(inner) ? { [PLATFORM_MODEL]: true } : {}),
|
|
8
9
|
...(isDecisionModel(inner) ? { [DECISION_MODEL]: true } : {}),
|
|
9
10
|
};
|
|
10
|
-
if (isInferModel(inner)) {
|
|
11
|
-
return {
|
|
12
|
-
...brands,
|
|
13
|
-
name: `record(${inner.name})`,
|
|
14
|
-
async infer(req) {
|
|
15
|
-
const res = await inner.infer(req);
|
|
16
|
-
const entry = {
|
|
17
|
-
fingerprint: fingerprintRequest({
|
|
18
|
-
payload: req.model,
|
|
19
|
-
...(req.params ? { params: req.params } : {}),
|
|
20
|
-
...(req.profile !== undefined ? { profile: req.profile } : {}),
|
|
21
|
-
}),
|
|
22
|
-
request: { payload: redactDeep(req.model), ...(req.params ? { params: req.params } : {}) },
|
|
23
|
-
response: { text: redactSecrets(res.text) },
|
|
24
|
-
};
|
|
25
|
-
appendFileSync(ledgerPath, `${JSON.stringify(entry)}\n`, "utf8");
|
|
26
|
-
return res;
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
const chat = inner;
|
|
31
11
|
return {
|
|
32
12
|
...brands,
|
|
33
13
|
name: `record(${inner.name})`,
|
|
34
|
-
async
|
|
35
|
-
const res = await
|
|
14
|
+
async infer(req) {
|
|
15
|
+
const res = await inner.infer(req);
|
|
36
16
|
const entry = {
|
|
37
17
|
fingerprint: fingerprintRequest(req),
|
|
38
|
-
request: {
|
|
18
|
+
request: { intent: redactDeep(req.intent), ...(req.params ? { params: req.params } : {}) },
|
|
39
19
|
response: { text: redactSecrets(res.text) },
|
|
40
20
|
};
|
|
41
21
|
appendFileSync(ledgerPath, `${JSON.stringify(entry)}\n`, "utf8");
|
|
@@ -47,17 +27,17 @@ export function record(inner, ledgerPath) {
|
|
|
47
27
|
* Offline provider serving recorded responses by request fingerprint. Strict:
|
|
48
28
|
* an unrecorded request is a definitive error, never a silent live call.
|
|
49
29
|
*
|
|
50
|
-
* A fingerprint is computed over the
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
30
|
+
* A fingerprint is computed over the intent as first composed (see
|
|
31
|
+
* `fingerprintRequest`), so a ledger recorded through any provider replays
|
|
32
|
+
* for any other, and one ask's first attempt and its correction retry hash
|
|
33
|
+
* identically — a recorded correction pair appends TWO ledger lines under
|
|
34
|
+
* the SAME key. Entries are served FIFO per fingerprint: each `infer()`
|
|
35
|
+
* shifts the next recorded response off that key's queue, so a replayed
|
|
36
|
+
* session reproduces the correction turn instead of jumping straight to the
|
|
37
|
+
* corrected answer. Once only one entry remains for a key it keeps serving
|
|
38
|
+
* that last one — a single-entry key (the common case) behaves exactly as a
|
|
39
|
+
* plain map lookup, and repeat asks beyond what was recorded still get an
|
|
40
|
+
* answer instead of failing.
|
|
61
41
|
*/
|
|
62
42
|
export function replay(ledgerPath) {
|
|
63
43
|
let raw;
|
|
@@ -93,7 +73,7 @@ export function replay(ledgerPath) {
|
|
|
93
73
|
// a ledger serves whatever it holds — a replayed decision ask needs no live capability
|
|
94
74
|
...{ [DECISION_MODEL]: true },
|
|
95
75
|
name: "replay",
|
|
96
|
-
async
|
|
76
|
+
async infer(req) {
|
|
97
77
|
const fingerprint = fingerprintRequest(req);
|
|
98
78
|
const queue = entries.get(fingerprint);
|
|
99
79
|
if (!queue || queue.length === 0) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every model a combinator (or `record`) wraps must speak the one dialect —
|
|
3
|
+
* `infer(req)`. A `complete`-only inner is the retired chat form and gets the
|
|
4
|
+
* same NOLA3003 migration message the config resolver gives a root model;
|
|
5
|
+
* anything else is not a model. Checked at construction so the failure is
|
|
6
|
+
* a config error, never a TypeError at the first ask (prompt-rendering spec
|
|
7
|
+
* 2026-09-28 §3.10).
|
|
8
|
+
*/
|
|
9
|
+
export declare function requireInfer(models: readonly unknown[], where: string): void;
|
|
10
|
+
//# sourceMappingURL=require-infer.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Codes } from "@nola-lang/ast";
|
|
2
|
+
import { NolaConfigError } from "@nola-lang/core";
|
|
3
|
+
/**
|
|
4
|
+
* Every model a combinator (or `record`) wraps must speak the one dialect —
|
|
5
|
+
* `infer(req)`. A `complete`-only inner is the retired chat form and gets the
|
|
6
|
+
* same NOLA3003 migration message the config resolver gives a root model;
|
|
7
|
+
* anything else is not a model. Checked at construction so the failure is
|
|
8
|
+
* a config error, never a TypeError at the first ask (prompt-rendering spec
|
|
9
|
+
* 2026-09-28 §3.10).
|
|
10
|
+
*/
|
|
11
|
+
export function requireInfer(models, where) {
|
|
12
|
+
for (const m of models) {
|
|
13
|
+
const o = m;
|
|
14
|
+
if (o && typeof o === "object" && typeof o.infer === "function")
|
|
15
|
+
continue;
|
|
16
|
+
const name = o && typeof o === "object" && typeof o.name === "string" ? o.name : String(m);
|
|
17
|
+
if (o && typeof o === "object" && typeof o.complete === "function") {
|
|
18
|
+
throw new NolaConfigError(`${where}(${name}) implements complete(req) — a model implements infer(req) and receives the InferenceModel; render it with renderPrompt() from @nola-lang/providers and return { text, sent }.`, Codes.ConfigInvalid);
|
|
19
|
+
}
|
|
20
|
+
throw new NolaConfigError(`${where}(${name}) is not a model (need { name: string, infer(req) }).`, Codes.ConfigInvalid);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=require-infer.js.map
|
package/dist/typesafe.js
CHANGED
|
@@ -36,9 +36,9 @@ export function typesafe(optionsOrModel) {
|
|
|
36
36
|
}
|
|
37
37
|
const askThreshold = req.params?.providerOptions?.threshold;
|
|
38
38
|
const threshold = typeof askThreshold === "number" ? askThreshold : (options.threshold ?? DEFAULT_THRESHOLD);
|
|
39
|
-
// Jev has no conversation: a correction turn (req.
|
|
39
|
+
// Jev has no conversation: a correction turn (req.intent.correction) has
|
|
40
40
|
// nothing to say to it, so the ask is re-answered from the same state.
|
|
41
|
-
const mapped = planFor(req.
|
|
41
|
+
const mapped = planFor(req.intent, { threshold });
|
|
42
42
|
if (!mapped.ok) {
|
|
43
43
|
throw new NolaProviderError(`TypeSafe cannot serve this ask: ${mapped.reason}`, { definitive: true });
|
|
44
44
|
}
|
package/dist/wire.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { JsonSchema } from "@nola-lang/core";
|
|
2
|
-
export declare const ENVELOPE_NOTE = "
|
|
2
|
+
export declare const ENVELOPE_NOTE = " Reply with a JSON object of the form {\"value\": X}, where X is the requested value.";
|
|
3
|
+
/** The schema rendered into the system turn for a backend that cannot enforce it (`structuredOutputs: false`). */
|
|
4
|
+
export declare function schemaNote(schema: JsonSchema): string;
|
|
3
5
|
/** Follow root-level $ref chains so the envelope decision sees the real shape. */
|
|
4
6
|
export declare function resolveRootRef(schema: JsonSchema): JsonSchema;
|
|
5
7
|
/** Wrap a non-object schema in the {value} envelope, hoisting $defs to the new root. */
|
package/dist/wire.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
// When a scalar/array schema is wrapped in the {value} envelope, the schema
|
|
2
2
|
// constraint alone only binds providers that do constrained decoding.
|
|
3
|
-
// Generate-then-validate backends follow the prompt, so the
|
|
3
|
+
// Generate-then-validate backends follow the prompt, so the system turn asks
|
|
4
4
|
// for the envelope too.
|
|
5
|
-
export const ENVELOPE_NOTE = '
|
|
5
|
+
export const ENVELOPE_NOTE = ' Reply with a JSON object of the form {"value": X}, where X is the requested value.';
|
|
6
|
+
/** The schema rendered into the system turn for a backend that cannot enforce it (`structuredOutputs: false`). */
|
|
7
|
+
export function schemaNote(schema) {
|
|
8
|
+
return `\n\n<schema>\n${JSON.stringify(schema)}\n</schema>\nReply with a single JSON value conforming to the schema.`;
|
|
9
|
+
}
|
|
6
10
|
/** Follow root-level $ref chains so the envelope decision sees the real shape. */
|
|
7
11
|
export function resolveRootRef(schema) {
|
|
8
12
|
let current = schema;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nola-lang/providers",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Nola bring-your-own LLM providers: openai, anthropic, google, typesafe, mock, resilience combinators, record/replay",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nola",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"!dist/**/*.map"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@nola-lang/ast": "0.1.
|
|
41
|
-
"@nola-lang/core": "0.1.
|
|
40
|
+
"@nola-lang/ast": "0.1.14",
|
|
41
|
+
"@nola-lang/core": "0.1.14"
|
|
42
42
|
},
|
|
43
43
|
"engines": {
|
|
44
44
|
"node": ">=22"
|
package/dist/dialect.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { ChatModel, InferModel, InferRequest, ProviderRequest, ProviderResponse } from "@nola-lang/core";
|
|
2
|
-
/**
|
|
3
|
-
* The dialect bridge (decision types spec 2026-09-18 §6.2): a combinator is
|
|
4
|
-
* infer-dialect iff any inner is, and renders for its chat inners. `callModel`
|
|
5
|
-
* is the one place that decides which method an inner gets.
|
|
6
|
-
*/
|
|
7
|
-
export declare function callModel(model: ChatModel | InferModel, req: InferRequest | ProviderRequest): Promise<ProviderResponse>;
|
|
8
|
-
/** An infer request rendered for a chat inner: the rendering replaces the model; everything else rides along. */
|
|
9
|
-
export declare function toClassic(req: InferRequest): ProviderRequest;
|
|
10
|
-
/** True when the request's output schema carries a decision question (either request shape). */
|
|
11
|
-
export declare function isDecisionRequest(req: InferRequest | ProviderRequest): boolean;
|
|
12
|
-
//# sourceMappingURL=dialect.d.ts.map
|
package/dist/dialect.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { findDecisionQuestions, isInferModel, NolaProviderError, renderClassic } from "@nola-lang/core";
|
|
2
|
-
/**
|
|
3
|
-
* The dialect bridge (decision types spec 2026-09-18 §6.2): a combinator is
|
|
4
|
-
* infer-dialect iff any inner is, and renders for its chat inners. `callModel`
|
|
5
|
-
* is the one place that decides which method an inner gets.
|
|
6
|
-
*/
|
|
7
|
-
export async function callModel(model, req) {
|
|
8
|
-
if (isInferModel(model)) {
|
|
9
|
-
if ("model" in req)
|
|
10
|
-
return model.infer(req);
|
|
11
|
-
throw new NolaProviderError(`infer-dialect model "${model.name}" received a classic request — a combinator over it must expose infer()`, { definitive: true });
|
|
12
|
-
}
|
|
13
|
-
return model.complete("model" in req ? toClassic(req) : req);
|
|
14
|
-
}
|
|
15
|
-
/** An infer request rendered for a chat inner: the rendering replaces the model; everything else rides along. */
|
|
16
|
-
export function toClassic(req) {
|
|
17
|
-
const { model, project: _project, ...rest } = req;
|
|
18
|
-
return { payload: renderClassic(model), ...rest };
|
|
19
|
-
}
|
|
20
|
-
/** True when the request's output schema carries a decision question (either request shape). */
|
|
21
|
-
export function isDecisionRequest(req) {
|
|
22
|
-
const output = "model" in req ? req.model.output : req.payload.output;
|
|
23
|
-
return output.syntax === "json" && findDecisionQuestions(output.schema).length > 0;
|
|
24
|
-
}
|
|
25
|
-
//# sourceMappingURL=dialect.js.map
|