@evalguard/vercel-ai 1.0.1 → 1.0.2

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.
@@ -0,0 +1,126 @@
1
+ import { type SurfacePolicy } from "@evalguard/wrapper-core";
2
+ /**
3
+ * Real API RESOURCES exempted by decision.
4
+ *
5
+ * Empty, deliberately. A model has no management endpoints — every method on
6
+ * every `@ai-sdk/provider` specification issues a provider request carrying
7
+ * caller-supplied content. Nothing has earned an exemption. An entry added here
8
+ * must carry its reason inline.
9
+ */
10
+ export declare const EXEMPT_RESOURCES: ReadonlySet<string>;
11
+ /**
12
+ * Capability / identity fields on a model instance that must be handed back
13
+ * unproxied.
14
+ *
15
+ * ⚠ THIS LIST IS A DENYLIST WEARING AN ALLOWLIST'S CLOTHES. Anything named here
16
+ * bypasses the guard entirely, so an entry that DOES reach the model is a silent
17
+ * leak. Every name below was read out of the installed `@ai-sdk/provider@3.0.10`
18
+ * specifications and each was driven through the real `ai` entry point with the
19
+ * firewall blocking: the firewall-check count stays 0 because NO REQUEST IS
20
+ * ISSUED, not because the guard was skipped.
21
+ *
22
+ * Why these specifically cannot be left to the generic gate:
23
+ *
24
+ * `supportedUrls` is `Record<string, RegExp[]> | PromiseLike<…>`. Default-deny
25
+ * proxies a non-function object, so `Object.entries(await model.supportedUrls)`
26
+ * would yield proxied `RegExp[]` arrays whose `.test` is a FUNCTION — gated,
27
+ * returning a Promise where the SDK expects a boolean. The capability map
28
+ * would break, and `supportedUrls` being silently dropped is already a
29
+ * MEDIUM this package fixed once (audit 2026-07-14).
30
+ *
31
+ * `maxImagesPerCall` / `maxVideosPerCall` are
32
+ * `number | undefined | GetMaxXPerCallFunction`. The FUNCTION form, gated,
33
+ * returns a Promise where `generateImage` reads a number.
34
+ *
35
+ * `maxEmbeddingsPerCall` / `supportsParallelCalls` are
36
+ * `PromiseLike<…> | value`. A proxied thenable is a different object identity
37
+ * than the SDK's own and buys nothing — neither carries caller text.
38
+ *
39
+ * `specificationVersion` / `provider` / `modelId` are strings and are passed
40
+ * through by the primitive rule without needing a name here; they are listed
41
+ * anyway so this file is a complete statement of the model spec's non-method
42
+ * surface rather than a partial one a reader has to cross-check.
43
+ */
44
+ export declare const TRANSPORT_PASSTHROUGH: ReadonlySet<string>;
45
+ /** Union, for callers/tests asking "is this name exempt at all". */
46
+ export declare const KNOWN_SAFE_PASSTHROUGH: ReadonlySet<string>;
47
+ /**
48
+ * Methods with a DEDICATED proxy in index.ts — the rich paths that also do
49
+ * stream teeing, token accounting, cost estimation and trace logging.
50
+ *
51
+ * These are NOT passthrough: `assertSurfacePolicy` fails at wrap time if the
52
+ * wrapper does not supply a handler for every name here, and it fails if a name
53
+ * appears here AND on a passthrough set.
54
+ *
55
+ * `doGenerate` is deliberately dedicated for EVERY model kind, not just language
56
+ * models. Image, speech, transcription and video specifications all spell their
57
+ * call method `doGenerate`, so routing it anywhere else would mean sniffing the
58
+ * model's shape to decide how hard to guard it — which is a denylist with extra
59
+ * steps. The rich override is shape-tolerant (`readTokens` and
60
+ * `extractOutputText` both degrade to 0 / "" on a non-language result), and its
61
+ * input rail now walks the whole call-options body, so a speech call is gated on
62
+ * `options.text` exactly like a language call is gated on `options.prompt`.
63
+ */
64
+ export declare const DEDICATED_RESOURCES: ReadonlySet<string>;
65
+ /**
66
+ * True for an `@ai-sdk/provider` model specification object.
67
+ *
68
+ * Verified against `@ai-sdk/provider@3.0.10`: every one of the seven
69
+ * specifications declares `specificationVersion` AND `modelId` (language v2:52,
70
+ * v3:12/22; embedding v2:24/34, v3:18/28; image v2:27/37, v3:28/38; speech
71
+ * v2:17/27, v3:17/27; transcription v2:17/27, v3:17/27; reranking v3:15/25;
72
+ * video v3:49/59).
73
+ *
74
+ * This is what stops a passthrough NAME from un-guarding a MODEL: a wrapper or
75
+ * middleware that exposes an inner model under a capability name (e.g. a
76
+ * `provider` field holding a delegate rather than a string) still gets guarded,
77
+ * because the passthrough allowlist only wins when the VALUE is not a model.
78
+ */
79
+ export declare function isAiSdkModel(value: unknown): boolean;
80
+ /**
81
+ * Call-option fields KNOWN to carry model-visible caller text on
82
+ * `@ai-sdk/provider@3.0.10`.
83
+ *
84
+ * The FLOOR, not the gate — the shared extractor unions a whole-body walk on
85
+ * top, so a field nobody enumerated is scanned anyway. REMOVING a name here
86
+ * narrows detection; ADDING one is redundant. Every entry is a real field on a
87
+ * real specification's call-options type:
88
+ *
89
+ * prompt LanguageModelV{2,3}CallOptions, ImageModelV3CallOptions:3,
90
+ * VideoModelV3CallOptions:3
91
+ * values EmbeddingModelV3CallOptions:3
92
+ * text SpeechModelV3CallOptions:4
93
+ * instructions SpeechModelV3CallOptions:7
94
+ * audio TranscriptionModelV3CallOptions:4
95
+ * documents / query RerankingModelV3CallOptions:4/8
96
+ * messages / content / input the shapes `collapsePromptText` renders
97
+ *
98
+ * ⚠ MEASURED LIMIT — TRANSCRIPTION AUDIO IS NOT SCANNED. Listing `audio` above
99
+ * does not make it readable. `ai@6.0.208`'s `transcribe` normalises whatever the
100
+ * caller passed (base64 string, data URL, ArrayBuffer) into a `Uint8Array`
101
+ * BEFORE the model boundary, and wrapper-core's walker excludes typed arrays by
102
+ * SHAPE — decoding an upload as UTF-8 produces megabytes of mojibake that would
103
+ * trip the size bound and refuse every legitimate audio call. Measured, with a
104
+ * canary in the audio and the firewall recording what it was shown:
105
+ *
106
+ * transcribe(audio: Uint8Array) firewall saw ["audio/wav"]
107
+ * transcribe(audio: base64 string) firewall saw ["audio/wav"]
108
+ * generateSpeech(text) firewall saw ["<canary>"]
109
+ * embed(value) firewall saw ["<canary>"]
110
+ * rerank(documents, query) firewall saw ["<canary>", "<canary>"]
111
+ *
112
+ * So a transcription call IS gated — `mediaType` is model-visible text, the rail
113
+ * runs, and the firewall can refuse the call — but the gate is not looking at the
114
+ * audio. That is the same documented unscannable limit as an uploaded file body,
115
+ * stated here rather than left to be inferred from a green matrix.
116
+ */
117
+ export declare const MODEL_INPUT_FIELDS: readonly string[];
118
+ export declare const VERCEL_AI_SURFACE_POLICY: SurfacePolicy;
119
+ /**
120
+ * The policy bound to one model instance, so traces carry the real model id.
121
+ *
122
+ * A function rather than a mutation: two wrapped models in the same process must
123
+ * not be able to overwrite each other's trace metadata.
124
+ */
125
+ export declare function vercelAiSurfacePolicy(modelId: unknown): SurfacePolicy;
126
+ //# sourceMappingURL=surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../src/surface.ts"],"names":[],"mappings":"AAqFA,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAuB,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,qBAAqB,EAAE,WAAW,CAAC,MAAM,CAapD,CAAC;AAEH,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAGrD,CAAC;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAGlD,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIpD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAW/C,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,aAatC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAOrE"}
@@ -0,0 +1,264 @@
1
+ // ── Default-deny classification of the AI SDK model surface ─────────────────
2
+ //
3
+ // AUDIT 2026-08-08 (vercel-ai residual). `withEvalguard` returned a Proxy whose
4
+ // `get` trap branched on `doGenerate` / `doStream` and sent everything else to
5
+ // `Reflect.get`. That is the same DENYLIST shape that made `@evalguard/anthropic`
6
+ // and `@evalguard/gemini` bypassable, and it was left in place on the strength of
7
+ // an argument that was never executed:
8
+ //
9
+ // "`ai@6.0.208`'s type surface contains zero `doX` names and imports the model
10
+ // spec from `@ai-sdk/provider`, so it is a FIXED interface, not an open
11
+ // resource tree a provider can extend."
12
+ //
13
+ // Both halves are false. `@ai-sdk/provider@3.0.10` (the exact version `ai@6.0.208`
14
+ // depends on) declares FOUR distinct `doX` method names across SEVEN model
15
+ // specifications, and `withEvalguard` gated two of them:
16
+ //
17
+ // spec method file:line
18
+ // LanguageModelV2 doGenerate language-model/v2/language-model-v2.ts:52 GATED
19
+ // LanguageModelV2 doStream language-model/v2/language-model-v2.ts:114 GATED
20
+ // LanguageModelV3 doGenerate language-model/v3/language-model-v3.ts:46 GATED
21
+ // LanguageModelV3 doStream language-model/v3/language-model-v3.ts:58 GATED
22
+ // EmbeddingModelV2 doEmbed embedding-model/v2/embedding-model-v2.ts:57 ungated
23
+ // EmbeddingModelV3 doEmbed embedding-model/v3/embedding-model-v3.ts:51 ungated
24
+ // ImageModelV2 doGenerate image-model/v2/image-model-v2.ts:50 gated by NAME COLLISION
25
+ // ImageModelV3 doGenerate image-model/v3/image-model-v3.ts:51 gated by NAME COLLISION
26
+ // SpeechModelV2 doGenerate speech-model/v2/speech-model-v2.ts:32 ungated IN EFFECT
27
+ // SpeechModelV3 doGenerate speech-model/v3/speech-model-v3.ts:32 ungated IN EFFECT
28
+ // TranscriptionModelV2 doGenerate transcription-model/v2/transcription-model-v2.ts:32 ungated IN EFFECT
29
+ // TranscriptionModelV3 doGenerate transcription-model/v3/transcription-model-v3.ts:32 ungated IN EFFECT
30
+ // RerankingModelV3 doRerank reranking-model/v3/reranking-model-v3.ts:31 ungated
31
+ // VideoModelV3 doGenerate video-model/v3/video-model-v3.ts:75 gated by NAME COLLISION
32
+ //
33
+ // Measured by driving each model kind through the REAL `ai@6.0.208` entry point
34
+ // (`embed`, `embedMany`, `generateImage`, `experimental_generateSpeech`,
35
+ // `experimental_transcribe`, `rerank`, `experimental_generateVideo`) against the
36
+ // pre-conversion `src/index.ts`, firewall configured to BLOCK ALL input:
37
+ //
38
+ // model / entry point checks leaked outcome
39
+ // LanguageModelV3.doGenerate (generateText) 1 0 BLOCKED
40
+ // LanguageModelV3.doStream (streamText) 1 0 BLOCKED
41
+ // EmbeddingModelV3.doEmbed (embed) 0 1 RETURNED
42
+ // EmbeddingModelV3.doEmbed (embedMany) 0 1 RETURNED
43
+ // ImageModelV3.doGenerate (generateImage) 1 0 BLOCKED
44
+ // SpeechModelV3.doGenerate (generateSpeech) 0 1 RETURNED
45
+ // TranscriptionModelV3.doGenerate (transcribe) 0 1 RETURNED
46
+ // RerankingModelV3.doRerank (rerank) 0 1 RETURNED
47
+ // VideoModelV3.doGenerate (generateVideo) 1 0 BLOCKED
48
+ //
49
+ // 4 of 9 delivered the attack text; all 4 on ZERO firewall calls.
50
+ //
51
+ // TWO DISTINCT FAILURE MODES, and only one of them is the classic denylist:
52
+ //
53
+ // 1. UNGATED METHOD NAME — `doEmbed`, `doRerank`. `Reflect.get` handed the real
54
+ // method straight back. This is the anthropic/gemini bug verbatim, and it is
55
+ // what `createDefaultDenySurface` closes.
56
+ //
57
+ // 2. GATED METHOD, BLIND EXTRACTOR — speech and transcription. Their payload is
58
+ // `options.text` / `options.audio`; `collapsePrompt` read only
59
+ // `params.prompt`, produced "", and the caller's rule was
60
+ // `if (promptText) { …check… }` — so "the collapser surfaced nothing" meant
61
+ // SKIP THE RAIL and call the model anyway. checks=0 with the interception
62
+ // working perfectly. Default-deny alone does NOT close this; the extractor
63
+ // has to walk the WHOLE call-options body, which is what
64
+ // `makeBodyTextExtractor` does and why `collapsePrompt` now unions
65
+ // `collectRequestStrings` on top of `collapsePromptText`.
66
+ //
67
+ // Reachability, stated honestly: `tsc` REJECTS all five non-language model kinds
68
+ // at the `withEvalguard` boundary (measured — TS2345 on MockEmbeddingModelV3,
69
+ // MockImageModelV3, MockSpeechModelV3, MockTranscriptionModelV3,
70
+ // MockRerankingModelV3), because the `LanguageModelV2` constraint requires
71
+ // `doStream`. So a type-checked TypeScript consumer cannot reach the leaks. Plain
72
+ // JavaScript, `as any`, and any config/registry-driven factory typed `unknown`
73
+ // can, and always could. The type system is not the gate — that is the entire
74
+ // lesson of 2026-08-08 — and a denylist that is correct only for one of seven
75
+ // specifications becomes a bypass the day upstream adds the eighth.
76
+ // FOLLOW-UP OWNED BY wrapper-core, not by this file:
77
+ // `wrapper-core/src/__tests__/surface-conformance-registry.test.ts` still lists
78
+ // `vercel-ai-wrapper` in `NON_PROXY_WRAPPERS`, with the reason quoted above. That
79
+ // list only records "no conformance test is REQUIRED here", so the ratchet is
80
+ // green either way and this package now carries one regardless
81
+ // (`src/__tests__/surface-conformance.test.ts`). The entry should move into
82
+ // `PROXY_WRAPPERS` with `conformanceTests: ["surface-conformance.test.ts"]` so
83
+ // the registry can never let this wrapper silently lose it again.
84
+ import { makeBodyTextExtractor, } from "@evalguard/wrapper-core";
85
+ /**
86
+ * Real API RESOURCES exempted by decision.
87
+ *
88
+ * Empty, deliberately. A model has no management endpoints — every method on
89
+ * every `@ai-sdk/provider` specification issues a provider request carrying
90
+ * caller-supplied content. Nothing has earned an exemption. An entry added here
91
+ * must carry its reason inline.
92
+ */
93
+ export const EXEMPT_RESOURCES = new Set([]);
94
+ /**
95
+ * Capability / identity fields on a model instance that must be handed back
96
+ * unproxied.
97
+ *
98
+ * ⚠ THIS LIST IS A DENYLIST WEARING AN ALLOWLIST'S CLOTHES. Anything named here
99
+ * bypasses the guard entirely, so an entry that DOES reach the model is a silent
100
+ * leak. Every name below was read out of the installed `@ai-sdk/provider@3.0.10`
101
+ * specifications and each was driven through the real `ai` entry point with the
102
+ * firewall blocking: the firewall-check count stays 0 because NO REQUEST IS
103
+ * ISSUED, not because the guard was skipped.
104
+ *
105
+ * Why these specifically cannot be left to the generic gate:
106
+ *
107
+ * `supportedUrls` is `Record<string, RegExp[]> | PromiseLike<…>`. Default-deny
108
+ * proxies a non-function object, so `Object.entries(await model.supportedUrls)`
109
+ * would yield proxied `RegExp[]` arrays whose `.test` is a FUNCTION — gated,
110
+ * returning a Promise where the SDK expects a boolean. The capability map
111
+ * would break, and `supportedUrls` being silently dropped is already a
112
+ * MEDIUM this package fixed once (audit 2026-07-14).
113
+ *
114
+ * `maxImagesPerCall` / `maxVideosPerCall` are
115
+ * `number | undefined | GetMaxXPerCallFunction`. The FUNCTION form, gated,
116
+ * returns a Promise where `generateImage` reads a number.
117
+ *
118
+ * `maxEmbeddingsPerCall` / `supportsParallelCalls` are
119
+ * `PromiseLike<…> | value`. A proxied thenable is a different object identity
120
+ * than the SDK's own and buys nothing — neither carries caller text.
121
+ *
122
+ * `specificationVersion` / `provider` / `modelId` are strings and are passed
123
+ * through by the primitive rule without needing a name here; they are listed
124
+ * anyway so this file is a complete statement of the model spec's non-method
125
+ * surface rather than a partial one a reader has to cross-check.
126
+ */
127
+ export const TRANSPORT_PASSTHROUGH = new Set([
128
+ // Identity — every specification, all `string`.
129
+ "specificationVersion",
130
+ "provider",
131
+ "modelId",
132
+ // LanguageModelV2 / V3 capability map.
133
+ "supportedUrls",
134
+ // EmbeddingModelV2 / V3 batching capability.
135
+ "maxEmbeddingsPerCall",
136
+ "supportsParallelCalls",
137
+ // ImageModelV2 / V3 and VideoModelV3 batching capability (may be a function).
138
+ "maxImagesPerCall",
139
+ "maxVideosPerCall",
140
+ ]);
141
+ /** Union, for callers/tests asking "is this name exempt at all". */
142
+ export const KNOWN_SAFE_PASSTHROUGH = new Set([
143
+ ...TRANSPORT_PASSTHROUGH,
144
+ ...EXEMPT_RESOURCES,
145
+ ]);
146
+ /**
147
+ * Methods with a DEDICATED proxy in index.ts — the rich paths that also do
148
+ * stream teeing, token accounting, cost estimation and trace logging.
149
+ *
150
+ * These are NOT passthrough: `assertSurfacePolicy` fails at wrap time if the
151
+ * wrapper does not supply a handler for every name here, and it fails if a name
152
+ * appears here AND on a passthrough set.
153
+ *
154
+ * `doGenerate` is deliberately dedicated for EVERY model kind, not just language
155
+ * models. Image, speech, transcription and video specifications all spell their
156
+ * call method `doGenerate`, so routing it anywhere else would mean sniffing the
157
+ * model's shape to decide how hard to guard it — which is a denylist with extra
158
+ * steps. The rich override is shape-tolerant (`readTokens` and
159
+ * `extractOutputText` both degrade to 0 / "" on a non-language result), and its
160
+ * input rail now walks the whole call-options body, so a speech call is gated on
161
+ * `options.text` exactly like a language call is gated on `options.prompt`.
162
+ */
163
+ export const DEDICATED_RESOURCES = new Set([
164
+ "doGenerate",
165
+ "doStream",
166
+ ]);
167
+ /**
168
+ * True for an `@ai-sdk/provider` model specification object.
169
+ *
170
+ * Verified against `@ai-sdk/provider@3.0.10`: every one of the seven
171
+ * specifications declares `specificationVersion` AND `modelId` (language v2:52,
172
+ * v3:12/22; embedding v2:24/34, v3:18/28; image v2:27/37, v3:28/38; speech
173
+ * v2:17/27, v3:17/27; transcription v2:17/27, v3:17/27; reranking v3:15/25;
174
+ * video v3:49/59).
175
+ *
176
+ * This is what stops a passthrough NAME from un-guarding a MODEL: a wrapper or
177
+ * middleware that exposes an inner model under a capability name (e.g. a
178
+ * `provider` field holding a delegate rather than a string) still gets guarded,
179
+ * because the passthrough allowlist only wins when the VALUE is not a model.
180
+ */
181
+ export function isAiSdkModel(value) {
182
+ if (!value || typeof value !== "object")
183
+ return false;
184
+ const v = value;
185
+ return "specificationVersion" in v && "modelId" in v;
186
+ }
187
+ /**
188
+ * Call-option fields KNOWN to carry model-visible caller text on
189
+ * `@ai-sdk/provider@3.0.10`.
190
+ *
191
+ * The FLOOR, not the gate — the shared extractor unions a whole-body walk on
192
+ * top, so a field nobody enumerated is scanned anyway. REMOVING a name here
193
+ * narrows detection; ADDING one is redundant. Every entry is a real field on a
194
+ * real specification's call-options type:
195
+ *
196
+ * prompt LanguageModelV{2,3}CallOptions, ImageModelV3CallOptions:3,
197
+ * VideoModelV3CallOptions:3
198
+ * values EmbeddingModelV3CallOptions:3
199
+ * text SpeechModelV3CallOptions:4
200
+ * instructions SpeechModelV3CallOptions:7
201
+ * audio TranscriptionModelV3CallOptions:4
202
+ * documents / query RerankingModelV3CallOptions:4/8
203
+ * messages / content / input the shapes `collapsePromptText` renders
204
+ *
205
+ * ⚠ MEASURED LIMIT — TRANSCRIPTION AUDIO IS NOT SCANNED. Listing `audio` above
206
+ * does not make it readable. `ai@6.0.208`'s `transcribe` normalises whatever the
207
+ * caller passed (base64 string, data URL, ArrayBuffer) into a `Uint8Array`
208
+ * BEFORE the model boundary, and wrapper-core's walker excludes typed arrays by
209
+ * SHAPE — decoding an upload as UTF-8 produces megabytes of mojibake that would
210
+ * trip the size bound and refuse every legitimate audio call. Measured, with a
211
+ * canary in the audio and the firewall recording what it was shown:
212
+ *
213
+ * transcribe(audio: Uint8Array) firewall saw ["audio/wav"]
214
+ * transcribe(audio: base64 string) firewall saw ["audio/wav"]
215
+ * generateSpeech(text) firewall saw ["<canary>"]
216
+ * embed(value) firewall saw ["<canary>"]
217
+ * rerank(documents, query) firewall saw ["<canary>", "<canary>"]
218
+ *
219
+ * So a transcription call IS gated — `mediaType` is model-visible text, the rail
220
+ * runs, and the firewall can refuse the call — but the gate is not looking at the
221
+ * audio. That is the same documented unscannable limit as an uploaded file body,
222
+ * stated here rather than left to be inferred from a green matrix.
223
+ */
224
+ export const MODEL_INPUT_FIELDS = [
225
+ "prompt",
226
+ "messages",
227
+ "content",
228
+ "input",
229
+ "values",
230
+ "text",
231
+ "instructions",
232
+ "audio",
233
+ "documents",
234
+ "query",
235
+ ];
236
+ export const VERCEL_AI_SURFACE_POLICY = {
237
+ provider: "vercel-ai",
238
+ exemptResources: EXEMPT_RESOURCES,
239
+ transportPassthrough: TRANSPORT_PASSTHROUGH,
240
+ dedicatedResources: DEDICATED_RESOURCES,
241
+ isProviderResource: isAiSdkModel,
242
+ extractText: makeBodyTextExtractor(MODEL_INPUT_FIELDS),
243
+ // The model id lives on the MODEL, not in the call options — no
244
+ // `@ai-sdk/provider` call-options type carries a `model` field, so
245
+ // `extractModelIdFromArgs` would report "unknown" for every call. The wrapper
246
+ // binds the real `model.modelId` via {@link vercelAiSurfacePolicy}; this
247
+ // default is only what a caller gets who asks for the policy without a model.
248
+ extractModelId: () => "unknown",
249
+ };
250
+ /**
251
+ * The policy bound to one model instance, so traces carry the real model id.
252
+ *
253
+ * A function rather than a mutation: two wrapped models in the same process must
254
+ * not be able to overwrite each other's trace metadata.
255
+ */
256
+ export function vercelAiSurfacePolicy(modelId) {
257
+ // `modelId` is `string` on every specification, but this runs on whatever the
258
+ // caller handed `withEvalguard` — including, in the conformance suite, an
259
+ // object that is not a model at all. A non-string here must degrade to the
260
+ // trace-metadata default, never propagate `undefined` into a trace field.
261
+ const id = typeof modelId === "string" && modelId ? modelId : "unknown";
262
+ return { ...VERCEL_AI_SURFACE_POLICY, extractModelId: () => id };
263
+ }
264
+ //# sourceMappingURL=surface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surface.js","sourceRoot":"","sources":["../src/surface.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,kFAAkF;AAClF,kFAAkF;AAClF,uCAAuC;AACvC,EAAE;AACF,iFAAiF;AACjF,2EAA2E;AAC3E,2CAA2C;AAC3C,EAAE;AACF,mFAAmF;AACnF,2EAA2E;AAC3E,yDAAyD;AACzD,EAAE;AACF,oDAAoD;AACpD,8FAA8F;AAC9F,8FAA8F;AAC9F,8FAA8F;AAC9F,8FAA8F;AAC9F,gGAAgG;AAChG,gGAAgG;AAChG,gHAAgH;AAChH,gHAAgH;AAChH,0GAA0G;AAC1G,0GAA0G;AAC1G,iHAAiH;AACjH,iHAAiH;AACjH,gGAAgG;AAChG,gHAAgH;AAChH,EAAE;AACF,gFAAgF;AAChF,yEAAyE;AACzE,iFAAiF;AACjF,yEAAyE;AACzE,EAAE;AACF,yEAAyE;AACzE,0EAA0E;AAC1E,0EAA0E;AAC1E,2EAA2E;AAC3E,2EAA2E;AAC3E,0EAA0E;AAC1E,2EAA2E;AAC3E,2EAA2E;AAC3E,2EAA2E;AAC3E,0EAA0E;AAC1E,EAAE;AACF,oEAAoE;AACpE,EAAE;AACF,4EAA4E;AAC5E,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AACjF,8CAA8C;AAC9C,EAAE;AACF,iFAAiF;AACjF,mEAAmE;AACnE,8DAA8D;AAC9D,gFAAgF;AAChF,8EAA8E;AAC9E,+EAA+E;AAC/E,6DAA6D;AAC7D,uEAAuE;AACvE,8DAA8D;AAC9D,EAAE;AACF,iFAAiF;AACjF,8EAA8E;AAC9E,iEAAiE;AACjE,2EAA2E;AAC3E,kFAAkF;AAClF,+EAA+E;AAC/E,8EAA8E;AAC9E,8EAA8E;AAC9E,oEAAoE;AAEpE,qDAAqD;AACrD,gFAAgF;AAChF,kFAAkF;AAClF,8EAA8E;AAC9E,+DAA+D;AAC/D,4EAA4E;AAC5E,+EAA+E;AAC/E,kEAAkE;AAElE,OAAO,EACL,qBAAqB,GAEtB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAwB,IAAI,GAAG,CAAS,EAAE,CAAC,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAwB,IAAI,GAAG,CAAC;IAChE,gDAAgD;IAChD,sBAAsB;IACtB,UAAU;IACV,SAAS;IACT,uCAAuC;IACvC,eAAe;IACf,6CAA6C;IAC7C,sBAAsB;IACtB,uBAAuB;IACvB,8EAA8E;IAC9E,kBAAkB;IAClB,kBAAkB;CACnB,CAAC,CAAC;AAEH,oEAAoE;AACpE,MAAM,CAAC,MAAM,sBAAsB,GAAwB,IAAI,GAAG,CAAC;IACjE,GAAG,qBAAqB;IACxB,GAAG,gBAAgB;CACpB,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAwB,IAAI,GAAG,CAAC;IAC9D,YAAY;IACZ,UAAU;CACX,CAAC,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,OAAO,sBAAsB,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,QAAQ;IACR,UAAU;IACV,SAAS;IACT,OAAO;IACP,QAAQ;IACR,MAAM;IACN,cAAc;IACd,OAAO;IACP,WAAW;IACX,OAAO;CACR,CAAC;AAEF,MAAM,CAAC,MAAM,wBAAwB,GAAkB;IACrD,QAAQ,EAAE,WAAW;IACrB,eAAe,EAAE,gBAAgB;IACjC,oBAAoB,EAAE,qBAAqB;IAC3C,kBAAkB,EAAE,mBAAmB;IACvC,kBAAkB,EAAE,YAAY;IAChC,WAAW,EAAE,qBAAqB,CAAC,kBAAkB,CAAC;IACtD,gEAAgE;IAChE,mEAAmE;IACnE,8EAA8E;IAC9E,yEAAyE;IACzE,8EAA8E;IAC9E,cAAc,EAAE,GAAG,EAAE,CAAC,SAAS;CAChC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,8EAA8E;IAC9E,0EAA0E;IAC1E,2EAA2E;IAC3E,0EAA0E;IAC1E,MAAM,EAAE,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACxE,OAAO,EAAE,GAAG,wBAAwB,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AACnE,CAAC"}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "1.0.0";
1
+ export declare const VERSION = "1.0.2";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Single source of the wrapper version WITHIN src (so the User-Agent header
2
2
  // can't drift between call sites). Kept in lockstep with package.json#version;
3
3
  // a unit test (version.test.ts) fails CI if the two ever diverge. (#120)
4
- export const VERSION = "1.0.0";
4
+ export const VERSION = "1.0.2";
5
5
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evalguard/vercel-ai",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Drop-in Vercel AI SDK middleware with EvalGuard guardrails, trace logging & cost tracking",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -13,6 +13,7 @@
13
13
  "files": [
14
14
  "dist",
15
15
  "README.md",
16
+ "CHANGELOG.md",
16
17
  "LICENSE",
17
18
  "peer-matrix.json"
18
19
  ],
@@ -40,7 +41,7 @@
40
41
  "ai": ">=5.0.0"
41
42
  },
42
43
  "dependencies": {
43
- "@evalguard/wrapper-core": "^1.2.0"
44
+ "@evalguard/wrapper-core": "^1.3.0"
44
45
  },
45
46
  "devDependencies": {
46
47
  "@types/node": "^26.0.0",