@loomwork/llm 0.0.0-stage → 0.1.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 +202 -0
- package/NOTICE +8 -0
- package/README.md +80 -2
- package/dist/assembleStream.d.ts +95 -0
- package/dist/assembleStream.d.ts.map +1 -0
- package/dist/assembleStream.js +180 -0
- package/dist/assembleStream.js.map +1 -0
- package/dist/capabilities.d.ts +43 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +77 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/createLlmEffect.d.ts +110 -0
- package/dist/createLlmEffect.d.ts.map +1 -0
- package/dist/createLlmEffect.js +177 -0
- package/dist/createLlmEffect.js.map +1 -0
- package/dist/errors.d.ts +96 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +280 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/parts.d.ts +220 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +231 -0
- package/dist/parts.js.map +1 -0
- package/dist/providers/AnthropicProvider.d.ts +53 -0
- package/dist/providers/AnthropicProvider.d.ts.map +1 -0
- package/dist/providers/AnthropicProvider.js +443 -0
- package/dist/providers/AnthropicProvider.js.map +1 -0
- package/dist/providers/GeminiProvider.d.ts +66 -0
- package/dist/providers/GeminiProvider.d.ts.map +1 -0
- package/dist/providers/GeminiProvider.js +281 -0
- package/dist/providers/GeminiProvider.js.map +1 -0
- package/dist/providers/OpenAiProvider.d.ts +56 -0
- package/dist/providers/OpenAiProvider.d.ts.map +1 -0
- package/dist/providers/OpenAiProvider.js +402 -0
- package/dist/providers/OpenAiProvider.js.map +1 -0
- package/dist/providers/extraHeaders.d.ts +7 -0
- package/dist/providers/extraHeaders.d.ts.map +1 -0
- package/dist/providers/extraHeaders.js +40 -0
- package/dist/providers/extraHeaders.js.map +1 -0
- package/dist/providers/systemPrompt.d.ts +10 -0
- package/dist/providers/systemPrompt.d.ts.map +1 -0
- package/dist/providers/systemPrompt.js +22 -0
- package/dist/providers/systemPrompt.js.map +1 -0
- package/dist/reasoning.d.ts +154 -0
- package/dist/reasoning.d.ts.map +1 -0
- package/dist/reasoning.js +256 -0
- package/dist/reasoning.js.map +1 -0
- package/dist/resolveContent.d.ts +74 -0
- package/dist/resolveContent.d.ts.map +1 -0
- package/dist/resolveContent.js +335 -0
- package/dist/resolveContent.js.map +1 -0
- package/dist/types.d.ts +152 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/untrusted.d.ts +91 -0
- package/dist/untrusted.d.ts.map +1 -0
- package/dist/untrusted.js +385 -0
- package/dist/untrusted.js.map +1 -0
- package/package.json +54 -3
- package/src/assembleStream.ts +219 -0
- package/src/capabilities.ts +96 -0
- package/src/createLlmEffect.ts +287 -0
- package/src/errors.ts +269 -0
- package/src/index.ts +137 -0
- package/src/parts.ts +409 -0
- package/src/providers/AnthropicProvider.ts +550 -0
- package/src/providers/GeminiProvider.ts +380 -0
- package/src/providers/OpenAiProvider.ts +511 -0
- package/src/providers/extraHeaders.ts +41 -0
- package/src/providers/systemPrompt.ts +19 -0
- package/src/reasoning.ts +386 -0
- package/src/resolveContent.ts +425 -0
- package/src/types.ts +175 -0
- package/src/untrusted.ts +440 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UnsupportedModalityError = void 0;
|
|
4
|
+
exports.mediaTypeAccepted = mediaTypeAccepted;
|
|
5
|
+
exports.requestMediaParts = requestMediaParts;
|
|
6
|
+
exports.assertRequestSupported = assertRequestSupported;
|
|
7
|
+
exports.capabilitiesForModel = capabilitiesForModel;
|
|
8
|
+
/** Thrown, before any network call, when a request carries a modality the provider did not declare. */
|
|
9
|
+
class UnsupportedModalityError extends Error {
|
|
10
|
+
modality;
|
|
11
|
+
mediaType;
|
|
12
|
+
provider;
|
|
13
|
+
constructor(modality, mediaType, provider) {
|
|
14
|
+
super(`${provider ?? "This LLM provider"} does not accept ${modality} content of type "${mediaType}" (not in its declared capabilities). The request was refused before any network call.`);
|
|
15
|
+
this.name = "UnsupportedModalityError";
|
|
16
|
+
this.modality = modality;
|
|
17
|
+
this.mediaType = mediaType;
|
|
18
|
+
this.provider = provider;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.UnsupportedModalityError = UnsupportedModalityError;
|
|
22
|
+
function mediaTypeAccepted(set, mediaType) {
|
|
23
|
+
if (set === undefined)
|
|
24
|
+
return false;
|
|
25
|
+
const mt = mediaType.toLowerCase();
|
|
26
|
+
for (const allowed of set) {
|
|
27
|
+
if (allowed === "*" || allowed.toLowerCase() === mt)
|
|
28
|
+
return true;
|
|
29
|
+
if (allowed.endsWith("/*") && mt.startsWith(allowed.slice(0, -1).toLowerCase()))
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
function* mediaParts(parts) {
|
|
35
|
+
for (const p of parts) {
|
|
36
|
+
if (p.type === "image" || p.type === "audio" || p.type === "file")
|
|
37
|
+
yield p;
|
|
38
|
+
else if (p.type === "tool_result" && typeof p.content !== "string") {
|
|
39
|
+
for (const r of p.content)
|
|
40
|
+
if (r.type !== "text")
|
|
41
|
+
yield r;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Every media part in a request, in order (including those inside `tool_result`s). */
|
|
46
|
+
function* requestMediaParts(request) {
|
|
47
|
+
for (const m of request.messages) {
|
|
48
|
+
if (typeof m.content !== "string")
|
|
49
|
+
yield* mediaParts(m.content);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Refuse a request that uses a modality / media type the provider has not declared.
|
|
54
|
+
* Pure and cheap (one walk); providers call it at the top of `complete` / `stream` and
|
|
55
|
+
* `createLlmEffect` calls it before resolving any content, so the refusal precedes
|
|
56
|
+
* every byte of I/O.
|
|
57
|
+
*/
|
|
58
|
+
function assertRequestSupported(request, capabilities, provider) {
|
|
59
|
+
for (const p of requestMediaParts(request)) {
|
|
60
|
+
if (!mediaTypeAccepted(capabilities?.[p.type], p.mediaType)) {
|
|
61
|
+
throw new UnsupportedModalityError(p.type, p.mediaType, provider);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The modalities a shipped provider declares for `model`: an ALLOWLIST. `base` only when the id
|
|
67
|
+
* matches a family known to take media (`allow`); every other id — unknown, older, a local alias —
|
|
68
|
+
* declares nothing, so a model that cannot take an image is refused locally instead of 400'd by the
|
|
69
|
+
* API, and a new family needs one line here before it is trusted with media. `config.capabilities`
|
|
70
|
+
* always wins (`capabilities: {}` = text-only). A provider on a non-default `baseUrl` (an
|
|
71
|
+
* OpenAI-compatible server) declares nothing either, since the model id says nothing about what is
|
|
72
|
+
* behind it; pass `capabilities` to turn media on.
|
|
73
|
+
*/
|
|
74
|
+
function capabilitiesForModel(model, base, allow) {
|
|
75
|
+
return allow.some((re) => re.test(model)) ? base : {};
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=capabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { LlmEffect } from "@loomwork/effect-runtime";
|
|
2
|
+
import type { EffectContext } from "@loomwork/effect-runtime";
|
|
3
|
+
import type { StreamProgress } from "./assembleStream";
|
|
4
|
+
import { type LlmReasoningOptions } from "./reasoning";
|
|
5
|
+
import { type ContentResolver } from "./resolveContent";
|
|
6
|
+
import type { LlmCapturedResponse, LlmProvider, LlmRequestInput } from "./types";
|
|
7
|
+
export interface CreateLlmEffectOptions<TIn, TOut> {
|
|
8
|
+
provider: LlmProvider;
|
|
9
|
+
buildRequest: (input: TIn, ctx: EffectContext) => LlmRequestInput;
|
|
10
|
+
parseResponse?: (response: LlmCapturedResponse, input: TIn) => TOut;
|
|
11
|
+
/**
|
|
12
|
+
* ADR 0133 - model reasoning as audit evidence. Readable text goes to `store` (a sidecar, never
|
|
13
|
+
* the step value), `parseResponse` sees only recorded `reasoning` parts (kind, provider,
|
|
14
|
+
* signature, reference, digest), and one `LLM_REASONING_CAPTURED` event names references and
|
|
15
|
+
* digests. `capture` defaults to on iff a store is configured; `live` streams readable reasoning
|
|
16
|
+
* deltas through the governed per-call sink as `{ reasoning }` chunks (off by default).
|
|
17
|
+
* Unset, an app sees no change except that `response.reasoning` parts appear (signatures only)
|
|
18
|
+
* when a provider returns reasoning.
|
|
19
|
+
*/
|
|
20
|
+
reasoning?: LlmReasoningOptions;
|
|
21
|
+
/**
|
|
22
|
+
* Consume the provider's `stream` instead of `complete` (R9). OPT-IN: the two
|
|
23
|
+
* paths return the same outcome by the R8 parity contract, but they are
|
|
24
|
+
* different wire calls, and an effect that silently changed which one it makes
|
|
25
|
+
* would be a surprise for anyone already running this.
|
|
26
|
+
*
|
|
27
|
+
* Falls back to `complete` when the provider implements no `stream`, so a
|
|
28
|
+
* provider swap cannot turn this into a runtime failure.
|
|
29
|
+
*
|
|
30
|
+
* What streaming buys, given the outcome is identical: deltas reach
|
|
31
|
+
* {@link onDelta} as they arrive, and an interrupted generation keeps its
|
|
32
|
+
* partial text ({@link LLM_INTERRUPTED}).
|
|
33
|
+
*/
|
|
34
|
+
stream?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* R5 — fetch the bytes behind a `{ kind: "ref" }` media source, at call time. The
|
|
37
|
+
* journaled request carries only the address; this is the one place bytes appear, and
|
|
38
|
+
* only on a LIVE call (a replay is served from the log and never reaches it). Typically
|
|
39
|
+
* `(ref) => contentStore.get({ __loomContent: ref.address })`. Without it, a request
|
|
40
|
+
* with a ref is refused (`UnresolvedContentError`) before any network call.
|
|
41
|
+
*/
|
|
42
|
+
resolveContent?: ContentResolver;
|
|
43
|
+
/** R5 — cap on one inline media part, decoded. Default 64 KiB (`MAX_INLINE_BYTES`). */
|
|
44
|
+
maxInlineBytes?: number;
|
|
45
|
+
/** R5 — cap on ALL inline media in one request. Default 256 KiB (`MAX_INLINE_TOTAL_BYTES`). */
|
|
46
|
+
maxInlineTotalBytes?: number;
|
|
47
|
+
/** R5 — cap on the total bytes `resolveContent` may return for one request. Default 32 MiB. */
|
|
48
|
+
maxResolvedBytes?: number;
|
|
49
|
+
/**
|
|
50
|
+
* R5 — a sink for streamed tool-call INPUT fragments (`id`, `name` on the first, then
|
|
51
|
+
* partial JSON). Transient and bare: it is NOT routed through the governed `ctx.onDelta`
|
|
52
|
+
* channel (ADR 0119), because middleware delta guards inspect text. Streaming only.
|
|
53
|
+
*/
|
|
54
|
+
onToolDelta?: (delta: {
|
|
55
|
+
id: string;
|
|
56
|
+
name?: string;
|
|
57
|
+
inputDelta: string;
|
|
58
|
+
}, ctx: EffectContext) => void;
|
|
59
|
+
/**
|
|
60
|
+
* R5 — a retried attempt `attempt` (> 1) is about to stream: discard the fragments
|
|
61
|
+
* {@link onToolDelta} delivered so far (the same contract as {@link onReset} for text). Called
|
|
62
|
+
* once per retry, via the same runtime reset chunk as `onReset`. Like `onToolDelta`, it sits OUTSIDE the governed
|
|
63
|
+
* channel: no middleware delta guard or output validator sees tool-input fragments, and
|
|
64
|
+
* `streamTokenBudget` meters the text only.
|
|
65
|
+
*/
|
|
66
|
+
onToolReset?: (attempt: number, ctx: EffectContext) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Where deltas go. TRANSIENT by construction — this effect never journals them,
|
|
69
|
+
* and must not: chunks-as-events is the fold-economics problem ST-R rejected,
|
|
70
|
+
* and `ctx.stream` is durable PER DELTA (one permanent event each), so routing
|
|
71
|
+
* a token stream through it is a choice an author makes deliberately, at a
|
|
72
|
+
* granularity they pick.
|
|
73
|
+
*
|
|
74
|
+
* The natural sink is `progressHub` (or the app's own `onDelta` handler). `@loomwork/live-query`
|
|
75
|
+
* is NOT such a sink: it has no delta-push API — `subscribe` re-folds DURABLE events
|
|
76
|
+
* (`snapshot` + `EventStore.subscribe`), so there is nothing there to push a per-token
|
|
77
|
+
* delta into (2026-09-27 audit).
|
|
78
|
+
*
|
|
79
|
+
* Not called on replay: a completed effect is served from the log and never
|
|
80
|
+
* re-invoked, so a tail attached to a replay sees silence.
|
|
81
|
+
*
|
|
82
|
+
* ADR 0119 — under an `EffectRuntime` this sink is GOVERNED: it receives a delta only
|
|
83
|
+
* after every middleware delta guard accepted it (a PII/output validator severs the
|
|
84
|
+
* stream before the breaching delta), on durable and transient calls alike, and it
|
|
85
|
+
* receives NOTHING when a middleware `inspectsOutput` without a delta form (the call
|
|
86
|
+
* completes non-streamed). Used bare (no runtime) it is called directly.
|
|
87
|
+
*
|
|
88
|
+
* A per-call sink (`EffectContext.onDelta` — what `DecideScope.invokeTransient`
|
|
89
|
+
* `{ onDelta }` sets) is called IN ADDITION to this one, with `{ delta }` per delta and a
|
|
90
|
+
* final `{ delta: "", done: true, usage }`.
|
|
91
|
+
*/
|
|
92
|
+
onDelta?: (delta: string, ctx: EffectContext) => void;
|
|
93
|
+
/**
|
|
94
|
+
* ADR 0119 — called when a retried attempt `attempt` (> 1) begins: discard what
|
|
95
|
+
* {@link onDelta} delivered so far. Same governed delivery as `onDelta`.
|
|
96
|
+
*/
|
|
97
|
+
onReset?: (attempt: number, ctx: EffectContext) => void;
|
|
98
|
+
/**
|
|
99
|
+
* K8.4 — a mid-flight guard, called after every delta; THROW to sever the
|
|
100
|
+
* generation. Curried over the effect context so it can read `ctx.resources`
|
|
101
|
+
* and emit a durable fact before throwing.
|
|
102
|
+
*
|
|
103
|
+
* `@loomwork/middleware`'s `streamTokenBudget(budget)` is the budget-shaped one.
|
|
104
|
+
* Streaming-only by nature: `complete` produces nothing to observe until it is
|
|
105
|
+
* already finished, which is the whole reason K8.4 waited on streaming effects.
|
|
106
|
+
*/
|
|
107
|
+
guard?: (ctx: EffectContext) => (progress: StreamProgress) => void;
|
|
108
|
+
}
|
|
109
|
+
export declare function createLlmEffect<TIn = string, TOut = string>(options: CreateLlmEffectOptions<TIn, TOut>): LlmEffect<TIn, TOut>;
|
|
110
|
+
//# sourceMappingURL=createLlmEffect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createLlmEffect.d.ts","sourceRoot":"","sources":["../src/createLlmEffect.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,KAAK,EACV,aAAa,EAId,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,KAAK,mBAAmB,EAA4C,MAAM,aAAa,CAAC;AACjG,OAAO,EAAE,KAAK,eAAe,EAAyB,MAAM,kBAAkB,CAAC;AAC/E,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAc,eAAe,EAAE,MAAM,SAAS,CAAC;AAG7F,MAAM,WAAW,sBAAsB,CAAC,GAAG,EAAE,IAAI;IAC/C,QAAQ,EAAE,WAAW,CAAC;IACtB,YAAY,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,aAAa,KAAK,eAAe,CAAC;IAClE,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,mBAAmB,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IACpE;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,eAAe,CAAC;IACjC,uFAAuF;IACvF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+FAA+F;IAC/F,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,+FAA+F;IAC/F,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,CACZ,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EACxD,GAAG,EAAE,aAAa,KACf,IAAI,CAAC;IACV;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5D;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;IACtD;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;IACxD;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;CACpE;AAMD,wBAAgB,eAAe,CAAC,GAAG,GAAG,MAAM,EAAE,IAAI,GAAG,MAAM,EACzD,OAAO,EAAE,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,GACzC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAkKtB"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLlmEffect = createLlmEffect;
|
|
4
|
+
const classify_1 = require("@loomwork/classify");
|
|
5
|
+
const effect_runtime_1 = require("@loomwork/effect-runtime");
|
|
6
|
+
const assembleStream_1 = require("./assembleStream");
|
|
7
|
+
const capabilities_1 = require("./capabilities");
|
|
8
|
+
const reasoning_1 = require("./reasoning");
|
|
9
|
+
const resolveContent_1 = require("./resolveContent");
|
|
10
|
+
const untrusted_1 = require("./untrusted");
|
|
11
|
+
function reasoningResolverOf(r) {
|
|
12
|
+
return r?.resolve ?? (r?.store !== undefined ? (0, reasoning_1.reasoningStoreResolver)(r.store) : undefined);
|
|
13
|
+
}
|
|
14
|
+
function createLlmEffect(options) {
|
|
15
|
+
const parse = options.parseResponse ?? ((r) => r.content);
|
|
16
|
+
// ADR 0133 D7 - the app's defaults (`observability.reasoning`) are adopted at boot; the effect's own
|
|
17
|
+
// options win field by field. Resolved per call, so an adoption after construction takes effect.
|
|
18
|
+
let adopted;
|
|
19
|
+
const reasoningOf = () => adopted === undefined && options.reasoning === undefined
|
|
20
|
+
? undefined
|
|
21
|
+
: adopted === false
|
|
22
|
+
? // The app-wide `false` is a default for effects with NO policy of their own: an effect that
|
|
23
|
+
// configured its own store or capture choice keeps it entirely.
|
|
24
|
+
options.reasoning?.store !== undefined || options.reasoning?.capture !== undefined
|
|
25
|
+
? options.reasoning
|
|
26
|
+
: { capture: false, ...options.reasoning }
|
|
27
|
+
: { ...adopted, ...options.reasoning };
|
|
28
|
+
const reasoningHook = {
|
|
29
|
+
capable: options.provider.capabilities?.reasoning === true,
|
|
30
|
+
posture() {
|
|
31
|
+
const r = reasoningOf();
|
|
32
|
+
if (r?.capture === false)
|
|
33
|
+
return "off";
|
|
34
|
+
return r?.store !== undefined ? "keeps" : "unconfigured";
|
|
35
|
+
},
|
|
36
|
+
adopt(defaults) {
|
|
37
|
+
adopted = defaults;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
return new effect_runtime_1.LlmEffect(async (input, ctx) => {
|
|
41
|
+
// The integrity check at the prompt boundary: tainted content is refused
|
|
42
|
+
// outside a `user` message and fenced inside one, and what comes back is
|
|
43
|
+
// plain strings — so no provider has to know taint exists.
|
|
44
|
+
const guarded = (0, untrusted_1.guardRequest)(options.buildRequest(input, ctx), {
|
|
45
|
+
...(options.maxInlineBytes !== undefined && { maxInlineBytes: options.maxInlineBytes }),
|
|
46
|
+
...(options.maxInlineTotalBytes !== undefined && {
|
|
47
|
+
maxInlineTotalBytes: options.maxInlineTotalBytes,
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
// R5 — fail closed on an undeclared modality BEFORE fetching any content or touching the
|
|
51
|
+
// network, then turn claim-check refs into bytes (live calls only).
|
|
52
|
+
(0, capabilities_1.assertRequestSupported)(guarded, options.provider.capabilities);
|
|
53
|
+
const reasoningResolver = reasoningResolverOf(reasoningOf());
|
|
54
|
+
const request = await (0, resolveContent_1.resolveRequestContent)(guarded, options.resolveContent, {
|
|
55
|
+
...(options.maxResolvedBytes !== undefined && { maxBytes: options.maxResolvedBytes }),
|
|
56
|
+
...(ctx.signal !== undefined && { signal: ctx.signal }),
|
|
57
|
+
context: {
|
|
58
|
+
executionId: ctx.executionId,
|
|
59
|
+
workflowId: ctx.workflowId,
|
|
60
|
+
stepId: ctx.stepId,
|
|
61
|
+
...(ctx.principal !== undefined && { principal: ctx.principal }),
|
|
62
|
+
},
|
|
63
|
+
// ADR 0133 D5 - reasoning text lives where capture put it, and is read back through an
|
|
64
|
+
// AUTHORIZING resolver (the ADR 0120 seam, origin "reasoning"): the configured one, else the
|
|
65
|
+
// sidecar read that serves a blob only to the execution it is sealed to.
|
|
66
|
+
...(reasoningResolver !== undefined && { reasoningResolver }),
|
|
67
|
+
});
|
|
68
|
+
// K8.1 — forward the effect's cooperative-cancellation signal to the
|
|
69
|
+
// provider (unless buildRequest already set one), so a cancelled/timed-out
|
|
70
|
+
// execution severs the in-flight HTTP call instead of orphaning it.
|
|
71
|
+
if (request.signal === undefined && ctx.signal !== undefined) {
|
|
72
|
+
request.signal = ctx.signal;
|
|
73
|
+
}
|
|
74
|
+
// ONE durable outcome either way. The streamed path returns the assembled
|
|
75
|
+
// response, which the R8 conformance contract pins equal to what `complete`
|
|
76
|
+
// would have returned — so the journal shape does not depend on which path
|
|
77
|
+
// ran, and switching `stream` on cannot change what replay serves.
|
|
78
|
+
const reasoningSink = reasoningOf()?.onDelta;
|
|
79
|
+
const live = reasoningOf()?.live === true && (ctx.onDelta !== undefined || reasoningSink !== undefined);
|
|
80
|
+
const raw = await (options.stream === true && options.provider.stream !== undefined
|
|
81
|
+
? (0, assembleStream_1.collectStream)(options.provider.stream(request), {
|
|
82
|
+
...(ctx.signal !== undefined && { signal: ctx.signal }),
|
|
83
|
+
// ADR 0119 — the per-call sink (ctx.onDelta, set by a decider's scope) AND the
|
|
84
|
+
// configured one both fire; neither replaces the other. The per-call sink goes
|
|
85
|
+
// FIRST: it is the governed one, and a middleware delta guard severs the stream
|
|
86
|
+
// by THROWING from it — which must happen before any other tail sees the delta.
|
|
87
|
+
// Do not catch around it. The text is wrapped as untrusted content (ADR 0085).
|
|
88
|
+
// The configured sink is NOT called here under a runtime: ctx.onDelta is the
|
|
89
|
+
// guarded channel and the runtime forwards to `deltaSink` after the guards.
|
|
90
|
+
...((options.onDelta !== undefined || ctx.onDelta !== undefined) && {
|
|
91
|
+
onDelta: (delta) => {
|
|
92
|
+
if (ctx.onDelta !== undefined) {
|
|
93
|
+
ctx.onDelta({ delta: (0, classify_1.tainted)(delta, `llm:${ctx.stepId}`) });
|
|
94
|
+
}
|
|
95
|
+
else
|
|
96
|
+
options.onDelta?.(delta, ctx);
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
// ADR 0133 D6 - readable reasoning rides its own field on the governed per-call sink
|
|
100
|
+
// (so the runtime's delta guards see it, and a guard that throws severs the stream).
|
|
101
|
+
// Off unless `live`; never the configured `onDelta`, which is text-only.
|
|
102
|
+
...(live && {
|
|
103
|
+
onReasoningDelta: (delta) => {
|
|
104
|
+
const reasoning = (0, classify_1.tainted)(delta, `llm-reasoning:${ctx.stepId}`);
|
|
105
|
+
if (ctx.onDelta !== undefined) {
|
|
106
|
+
ctx.onDelta({ delta: (0, classify_1.tainted)("", `llm:${ctx.stepId}`), reasoning });
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
try {
|
|
110
|
+
reasoningSink?.(reasoning, ctx);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// A live tail is a convenience: it cannot fail the model call (the durable record is D3).
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
...(options.onToolDelta !== undefined && {
|
|
119
|
+
onToolDelta: (d) => options.onToolDelta?.(d, ctx),
|
|
120
|
+
}),
|
|
121
|
+
...(options.guard !== undefined && { guard: options.guard(ctx) }),
|
|
122
|
+
})
|
|
123
|
+
: options.provider.complete(request));
|
|
124
|
+
// ADR 0119 — hand the runtime the stream's end + usage. The runtime HOLDS this and
|
|
125
|
+
// sends the caller's terminal chunk only once the whole call has succeeded
|
|
126
|
+
// (middleware output phase and parse included), so `done` never precedes a failure.
|
|
127
|
+
// Streamed path only: a `complete` fallback delivered no deltas to terminate.
|
|
128
|
+
if (options.stream === true && options.provider.stream !== undefined) {
|
|
129
|
+
ctx.onDelta?.({
|
|
130
|
+
delta: (0, classify_1.tainted)("", `llm:${ctx.stepId}`),
|
|
131
|
+
done: true,
|
|
132
|
+
...(raw.usage !== undefined && { usage: raw.usage }),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// ADR 0133 D3 - readable reasoning is reduced to its recorded form BEFORE parse, so text
|
|
136
|
+
// never reaches a step value. A response with no reasoning is passed through untouched (G2).
|
|
137
|
+
// The same object goes through when there is none (callers compare responses by identity).
|
|
138
|
+
let response = raw;
|
|
139
|
+
if (raw.reasoning !== undefined) {
|
|
140
|
+
const { reasoning: returned, ...rest } = raw;
|
|
141
|
+
response =
|
|
142
|
+
returned.length > 0
|
|
143
|
+
? { ...rest, reasoning: await (0, reasoning_1.captureReasoning)(returned, reasoningOf(), ctx) }
|
|
144
|
+
: rest;
|
|
145
|
+
}
|
|
146
|
+
return parse(response, input);
|
|
147
|
+
},
|
|
148
|
+
// Wire pre-warm to the provider's own warm hook (connection/model cache),
|
|
149
|
+
// only when the provider implements one.
|
|
150
|
+
{
|
|
151
|
+
...(options.provider.warm !== undefined && { warm: () => options.provider.warm?.() }),
|
|
152
|
+
// ADR 0133 D6 - the configured reasoning sink is reached through the runtime's governed pipe
|
|
153
|
+
// (delta guards first), only when the app turned live reasoning on.
|
|
154
|
+
reasoning: reasoningHook,
|
|
155
|
+
...(options.reasoning?.onDelta !== undefined && {
|
|
156
|
+
reasoningSink: (delta, ctx) => {
|
|
157
|
+
if (reasoningOf()?.live === true)
|
|
158
|
+
options.reasoning?.onDelta?.(delta, ctx);
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
...((options.onDelta !== undefined ||
|
|
162
|
+
options.onReset !== undefined ||
|
|
163
|
+
options.onToolReset !== undefined) && {
|
|
164
|
+
deltaSink: (chunk, ctx) => {
|
|
165
|
+
if (chunk.reset === true) {
|
|
166
|
+
options.onReset?.(chunk.attempt ?? 2, ctx);
|
|
167
|
+
options.onToolReset?.(chunk.attempt ?? 2, ctx);
|
|
168
|
+
}
|
|
169
|
+
else if (chunk.done !== true && chunk.reasoning === undefined) {
|
|
170
|
+
// A reasoning chunk (ADR 0133) is not text: the configured sink is text-only.
|
|
171
|
+
options.onDelta?.((0, classify_1.unwrapTainted)(chunk.delta), ctx);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
}),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=createLlmEffect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createLlmEffect.js","sourceRoot":"","sources":["../src/createLlmEffect.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export type LlmHttpProvider = "anthropic" | "openai" | "gemini";
|
|
2
|
+
/**
|
|
3
|
+
* Release the body of a non-2xx response without reading it: an unconsumed body keeps the
|
|
4
|
+
* connection (undici) busy. Never reads the text, and a failing cancel must not mask the status.
|
|
5
|
+
*/
|
|
6
|
+
export declare function drainBody(resp: Response): Promise<void>;
|
|
7
|
+
/** Why a request failed when the status alone cannot say. Closed; `"auth"` is a refused credential. */
|
|
8
|
+
export type LlmHttpErrorKind = "auth";
|
|
9
|
+
/**
|
|
10
|
+
* A provider's HTTP API answered with a non-2xx status. `status` is what a caller should branch
|
|
11
|
+
* on. The response body is deliberately NOT kept: it can echo the prompt, so it is never put in
|
|
12
|
+
* the message or on the error.
|
|
13
|
+
*/
|
|
14
|
+
export declare class LlmHttpError extends Error {
|
|
15
|
+
readonly status: number;
|
|
16
|
+
readonly provider: LlmHttpProvider;
|
|
17
|
+
/** Set to `false` for a 3xx (a redirect, never followed): retrying cannot help. */
|
|
18
|
+
readonly retryable?: false;
|
|
19
|
+
/**
|
|
20
|
+
* Why the request failed, when the status alone cannot say. Closed: `"auth"` means the
|
|
21
|
+
* credential was refused (401/403, or a 400 the provider's own error code names as a bad key), a
|
|
22
|
+
* configuration fault rather than a malformed request. Absent otherwise.
|
|
23
|
+
*/
|
|
24
|
+
readonly kind?: LlmHttpErrorKind;
|
|
25
|
+
constructor(provider: LlmHttpProvider, status: number, kind?: LlmHttpErrorKind);
|
|
26
|
+
}
|
|
27
|
+
/** The most of a non-2xx body that is ever read to look for a provider error code. */
|
|
28
|
+
export declare const MAX_ERROR_BODY_BYTES: number;
|
|
29
|
+
/** The longest the error-body read may take; a server that stalls after the headers is given up on. */
|
|
30
|
+
export declare const ERROR_BODY_READ_TIMEOUT_MS = 2000;
|
|
31
|
+
/**
|
|
32
|
+
* Read at most `maxBytes` of a non-2xx body, for at most `timeoutMs` (and no longer than `signal`
|
|
33
|
+
* allows), and parse it as JSON. The body is always consumed or cancelled and its reader released
|
|
34
|
+
* before this returns, so the caller need not drain it. Returns `undefined` for anything else (over
|
|
35
|
+
* the bound, too slow, aborted, unreadable, not JSON): the caller only ever looks for a fixed error
|
|
36
|
+
* code in the result and keeps nothing of it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function readBoundedErrorJson(resp: Response, signal?: AbortSignal, maxBytes?: number, timeoutMs?: number): Promise<unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* A provider reported an error INSIDE a 200 stream (Anthropic `event: error`, an OpenAI-compatible
|
|
41
|
+
* `{"error": ...}` chunk). Carries only the provider and the error type or code, and only when it is
|
|
42
|
+
* a plain identifier: the message text can echo the prompt, so it is never kept.
|
|
43
|
+
*/
|
|
44
|
+
export declare class LlmStreamError extends Error {
|
|
45
|
+
readonly provider: LlmHttpProvider;
|
|
46
|
+
readonly code: string;
|
|
47
|
+
constructor(provider: LlmHttpProvider, code: unknown);
|
|
48
|
+
}
|
|
49
|
+
/** Default cap on a complete response body (bytes), and on the total bytes of a stream. */
|
|
50
|
+
export declare const DEFAULT_MAX_RESPONSE_BYTES: number;
|
|
51
|
+
/**
|
|
52
|
+
* Default cap on the TOTAL bytes of a stream. Much larger than the complete-body cap: an SSE
|
|
53
|
+
* chunk is ~250-300 B per token on the wire, so a long answer legitimately needs tens of MiB, and
|
|
54
|
+
* memory is bounded by the per-line cap, not by this.
|
|
55
|
+
*/
|
|
56
|
+
export declare const DEFAULT_MAX_STREAM_BYTES: number;
|
|
57
|
+
/** Default cap on one line of a stream, in UTF-8 bytes. */
|
|
58
|
+
export declare const DEFAULT_MAX_LINE_BYTES: number;
|
|
59
|
+
export type LlmResponseLimitReason = "body" | "stream" | "line";
|
|
60
|
+
/**
|
|
61
|
+
* A provider's response exceeded the configured size cap (`maxResponseBytes` / `maxStreamBytes` / `maxLineBytes`).
|
|
62
|
+
* Carries only the provider and which limit was hit: never any of the body.
|
|
63
|
+
*/
|
|
64
|
+
export declare class LlmResponseLimitError extends Error {
|
|
65
|
+
readonly provider: LlmHttpProvider;
|
|
66
|
+
readonly reason: LlmResponseLimitReason;
|
|
67
|
+
constructor(provider: LlmHttpProvider, reason: LlmResponseLimitReason);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A 2xx response whose body is not valid JSON. Fixed message, no `cause`: the parser's own error
|
|
71
|
+
* quotes the start of the body, which can hold anything the endpoint sent.
|
|
72
|
+
*/
|
|
73
|
+
export declare class LlmResponseInvalidError extends Error {
|
|
74
|
+
readonly provider: LlmHttpProvider;
|
|
75
|
+
constructor(provider: LlmHttpProvider);
|
|
76
|
+
}
|
|
77
|
+
/** Reject a non-positive or non-integer cap at construction. */
|
|
78
|
+
export declare function resolveLimit(value: number | undefined, fallback: number, name: string): number;
|
|
79
|
+
/**
|
|
80
|
+
* Read a complete JSON body with a byte cap. Over the cap the body is cancelled and an
|
|
81
|
+
* {@link LlmResponseLimitError} is thrown (no body text). Streams the bytes, so a hostile endpoint
|
|
82
|
+
* cannot make the process buffer more than the cap (plus one chunk).
|
|
83
|
+
*/
|
|
84
|
+
export declare function readBoundedJson(resp: Response, provider: LlmHttpProvider, maxBytes: number): Promise<unknown>;
|
|
85
|
+
/** Byte and line accounting for an SSE stream; each check throws {@link LlmResponseLimitError}. */
|
|
86
|
+
export declare class StreamLimiter {
|
|
87
|
+
private readonly provider;
|
|
88
|
+
private readonly maxBytes;
|
|
89
|
+
private readonly maxLine;
|
|
90
|
+
private total;
|
|
91
|
+
constructor(provider: LlmHttpProvider, maxBytes: number, maxLine: number);
|
|
92
|
+
chunk(value: Uint8Array): void;
|
|
93
|
+
/** `lines` are the complete lines of this chunk, `rest` the unfinished tail. */
|
|
94
|
+
lines(lines: readonly string[], rest: string): void;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAQhE;;;GAGG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7D;AAED,uGAAuG;AACvG,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEtC;;;;GAIG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,mFAAmF;IACnF,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC;gBACrB,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB;CAU/E;AAED,sFAAsF;AACtF,eAAO,MAAM,oBAAoB,QAAY,CAAC;AAE9C,uGAAuG;AACvG,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,QAAQ,EACd,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,GAAE,MAA6B,EACvC,SAAS,GAAE,MAAmC,GAC7C,OAAO,CAAC,OAAO,CAAC,CAgDlB;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,OAAO;CAOrD;AAED,2FAA2F;AAC3F,eAAO,MAAM,0BAA0B,QAAkB,CAAC;AAC1D;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,QAAoB,CAAC;AAC1D,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,QAAc,CAAC;AAiBlD,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEhE;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;gBAC5B,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,sBAAsB;CAMtE;AAED;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;gBACvB,QAAQ,EAAE,eAAe;CAKtC;AAED,gEAAgE;AAChE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAK9F;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CA0ClB;AAED,mGAAmG;AACnG,qBAAa,aAAa;IAGtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJ1B,OAAO,CAAC,KAAK,CAAK;gBAEC,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM;IAElC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAI9B,gFAAgF;IAChF,KAAK,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;CAMpD"}
|