promptguard-sdk 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +63 -13
  2. package/dist/auto.d.ts +1 -0
  3. package/dist/auto.d.ts.map +1 -1
  4. package/dist/auto.js +5 -0
  5. package/dist/auto.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/integrations/langchain.d.ts +37 -1
  11. package/dist/integrations/langchain.d.ts.map +1 -1
  12. package/dist/integrations/langchain.js +64 -5
  13. package/dist/integrations/langchain.js.map +1 -1
  14. package/dist/integrations/session.d.ts +33 -0
  15. package/dist/integrations/session.d.ts.map +1 -0
  16. package/dist/integrations/session.js +71 -0
  17. package/dist/integrations/session.js.map +1 -0
  18. package/dist/integrations/vercel-ai-output.d.ts +109 -0
  19. package/dist/integrations/vercel-ai-output.d.ts.map +1 -0
  20. package/dist/integrations/vercel-ai-output.js +330 -0
  21. package/dist/integrations/vercel-ai-output.js.map +1 -0
  22. package/dist/integrations/vercel-ai.d.ts +114 -24
  23. package/dist/integrations/vercel-ai.d.ts.map +1 -1
  24. package/dist/integrations/vercel-ai.js +200 -82
  25. package/dist/integrations/vercel-ai.js.map +1 -1
  26. package/dist/patches/base.d.ts +5 -0
  27. package/dist/patches/base.d.ts.map +1 -1
  28. package/dist/patches/base.js +5 -2
  29. package/dist/patches/base.js.map +1 -1
  30. package/dist/patches/openai.d.ts +8 -0
  31. package/dist/patches/openai.d.ts.map +1 -1
  32. package/dist/patches/openai.js +16 -0
  33. package/dist/patches/openai.js.map +1 -1
  34. package/dist/version.d.ts +1 -1
  35. package/dist/version.js +1 -1
  36. package/package.json +5 -2
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Output scanning for the Vercel AI SDK integration — generated and streamed.
3
+ *
4
+ * This is the one place that decides what a model's output is scanned as and
5
+ * what a decision about it does. The middleware (`vercel-ai.ts`) calls it from
6
+ * both `wrapGenerate` and `wrapStream`, and anything else that wraps an AI SDK
7
+ * model should call it too rather than restating the policy.
8
+ *
9
+ * Streams are scanned in windows, then once more in full:
10
+ *
11
+ * - **Enforce mode** holds back the stream parts that carry scanned text
12
+ * (text deltas, tool-call input) until that text has been scanned. Each
13
+ * time `windowChars` new characters have arrived, everything held is
14
+ * scanned and, if clean, released — except the last `WINDOW_OVERLAP_CHARS`
15
+ * characters, which stay held and are scanned again at the head of the next
16
+ * window. So every released character has been scanned together with at
17
+ * least the overlap that follows it, and a secret up to the overlap long is
18
+ * always seen whole before any of it is released. When the model finishes,
19
+ * the complete text is scanned — the same text, built the same way, as a
20
+ * generated result — before the held tail and the `finish` part are
21
+ * released. Parts that carry no scanned text (stream start, reasoning,
22
+ * sources, files, metadata) are delivered at once when nothing is held
23
+ * ahead of them, and queue behind held text otherwise, so order is kept.
24
+ * - **Monitor mode** never stops a call, so it never holds one: parts pass
25
+ * through as they arrive and only the complete text is scanned, at the end.
26
+ *
27
+ * Reasoning is not scanned, here or on the generated path.
28
+ *
29
+ * A block (or an output redact, which cannot be honoured on text already
30
+ * generated) stops the stream with `PromptGuardBlockedError`. A Guard API
31
+ * outage fails open by default (ADR-0002): the held parts are released and the
32
+ * rest of that stream passes unscanned, so an outage costs one failed scan per
33
+ * stream rather than one per window. With `failOpen: false` it stops the
34
+ * stream with the `GuardApiError` instead.
35
+ *
36
+ * How a stopped stream ends is the caller's choice (`StreamStop`): by
37
+ * erroring it, so whatever reads it throws, or by delivering one AI SDK
38
+ * `error` part and closing it. Either way nothing held is released and the
39
+ * `finish` part never arrives.
40
+ */
41
+ import { type GuardClient } from "../guard";
42
+ /**
43
+ * Default new characters of streamed output per scanned window.
44
+ *
45
+ * Not taken from any external standard: 1000 was chosen as a balance between
46
+ * how long output is held and how many Guard API round trips a stream costs.
47
+ * At roughly four characters a token it is about 250 tokens, which takes a
48
+ * model a few seconds to generate — about as long as one Guard round trip, so
49
+ * scanning a window roughly keeps pace with generating the next. A smaller
50
+ * window delivers the first text sooner and costs more scans; a larger one
51
+ * costs fewer scans and holds output longer.
52
+ */
53
+ export declare const DEFAULT_STREAM_WINDOW_CHARS = 1000;
54
+ /**
55
+ * Characters of scanned text always kept held and rescanned at the head of
56
+ * the next window — the longest secret guaranteed to be seen whole across a
57
+ * window boundary.
58
+ */
59
+ export declare const WINDOW_OVERLAP_CHARS = 200;
60
+ export interface OutputScanPolicy {
61
+ guard: GuardClient;
62
+ mode: "enforce" | "monitor";
63
+ failOpen: boolean;
64
+ /** Characters per streamed window; `Infinity` holds the whole response. */
65
+ windowChars: number;
66
+ }
67
+ /** Throws `RangeError` unless `value` is a usable `streamWindowChars`. */
68
+ export declare function validateWindowChars(value: number): number;
69
+ /**
70
+ * What happened to a piece of output that was allowed through: `"scanned"`
71
+ * when the Guard API saw it, `"failed-open"` when it was unreachable and
72
+ * fail-open let the output through unscanned.
73
+ */
74
+ export type OutputScanOutcome = "scanned" | "failed-open";
75
+ /**
76
+ * Scan one piece of model output. Resolves when the output may be delivered;
77
+ * throws `PromptGuardBlockedError` when enforce mode stops it, and rethrows
78
+ * the `GuardApiError` when failing closed. `sessionId` names the
79
+ * Conversation the call belongs to, when it names one.
80
+ */
81
+ export declare function scanOutputText(text: string, policy: OutputScanPolicy, modelId: string | undefined, what?: string, sessionId?: string): Promise<OutputScanOutcome>;
82
+ /**
83
+ * Extract all scannable text from a `doGenerate` result.
84
+ *
85
+ * Handles both AI SDK result shapes:
86
+ * - **v4 (LanguageModelV1):** top-level `text` string plus `toolCalls`
87
+ * (every tool call's `args`/`input` is scanned — not just the first).
88
+ * - **v5+ (LanguageModelV2/V3):** `content` is an array of parts
89
+ * (`{ type: "text", text }` / `{ type: "tool-call", input }`) with no
90
+ * top-level `text`.
91
+ *
92
+ * Returns the concatenated text, or `null` when nothing scannable exists.
93
+ */
94
+ export declare function extractGeneratedText(result: Record<string, unknown> | null | undefined): string | null;
95
+ /**
96
+ * How a stream the policy stops ends: `"error"` errors the stream, so the
97
+ * reader's next read throws the error; `"error-part"` delivers one
98
+ * `{ type: "error", error }` stream part — the AI SDK's own way for a model to
99
+ * report a failure mid-stream — and then closes the stream.
100
+ */
101
+ export type StreamStop = "error" | "error-part";
102
+ /**
103
+ * Wrap a model stream so its output is scanned as described in this module's
104
+ * header. Parts are passed through unchanged and in order; where the policy
105
+ * stops the output, the stream ends with `PromptGuardBlockedError` (or the
106
+ * `GuardApiError` when failing closed), as `stopAs` says.
107
+ */
108
+ export declare function scanOutputStream<T>(stream: ReadableStream<T>, policy: OutputScanPolicy, modelId: string | undefined, stopAs?: StreamStop, sessionId?: string): ReadableStream<T>;
109
+ //# sourceMappingURL=vercel-ai-output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel-ai-output.d.ts","sourceRoot":"","sources":["../../src/integrations/vercel-ai-output.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAiB,KAAK,WAAW,EAA2C,MAAM,UAAU,CAAA;AAGnG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,OAAO,CAAA;AAE/C;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,MAAM,CAAA;AAEvC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,WAAW,CAAA;IAClB,IAAI,EAAE,SAAS,GAAG,SAAS,CAAA;IAC3B,QAAQ,EAAE,OAAO,CAAA;IACjB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,0EAA0E;AAC1E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,aAAa,CAAA;AAEzD;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,IAAI,SAAa,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC,CA4B5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GACjD,MAAM,GAAG,IAAI,CAgCf;AAuED;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,YAAY,CAAA;AAE/C;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,MAAM,GAAE,UAAoB,EAC5B,SAAS,CAAC,EAAE,MAAM,GACjB,cAAc,CAAC,CAAC,CAAC,CAyGnB"}
@@ -0,0 +1,330 @@
1
+ "use strict";
2
+ /**
3
+ * Output scanning for the Vercel AI SDK integration — generated and streamed.
4
+ *
5
+ * This is the one place that decides what a model's output is scanned as and
6
+ * what a decision about it does. The middleware (`vercel-ai.ts`) calls it from
7
+ * both `wrapGenerate` and `wrapStream`, and anything else that wraps an AI SDK
8
+ * model should call it too rather than restating the policy.
9
+ *
10
+ * Streams are scanned in windows, then once more in full:
11
+ *
12
+ * - **Enforce mode** holds back the stream parts that carry scanned text
13
+ * (text deltas, tool-call input) until that text has been scanned. Each
14
+ * time `windowChars` new characters have arrived, everything held is
15
+ * scanned and, if clean, released — except the last `WINDOW_OVERLAP_CHARS`
16
+ * characters, which stay held and are scanned again at the head of the next
17
+ * window. So every released character has been scanned together with at
18
+ * least the overlap that follows it, and a secret up to the overlap long is
19
+ * always seen whole before any of it is released. When the model finishes,
20
+ * the complete text is scanned — the same text, built the same way, as a
21
+ * generated result — before the held tail and the `finish` part are
22
+ * released. Parts that carry no scanned text (stream start, reasoning,
23
+ * sources, files, metadata) are delivered at once when nothing is held
24
+ * ahead of them, and queue behind held text otherwise, so order is kept.
25
+ * - **Monitor mode** never stops a call, so it never holds one: parts pass
26
+ * through as they arrive and only the complete text is scanned, at the end.
27
+ *
28
+ * Reasoning is not scanned, here or on the generated path.
29
+ *
30
+ * A block (or an output redact, which cannot be honoured on text already
31
+ * generated) stops the stream with `PromptGuardBlockedError`. A Guard API
32
+ * outage fails open by default (ADR-0002): the held parts are released and the
33
+ * rest of that stream passes unscanned, so an outage costs one failed scan per
34
+ * stream rather than one per window. With `failOpen: false` it stops the
35
+ * stream with the `GuardApiError` instead.
36
+ *
37
+ * How a stopped stream ends is the caller's choice (`StreamStop`): by
38
+ * erroring it, so whatever reads it throws, or by delivering one AI SDK
39
+ * `error` part and closing it. Either way nothing held is released and the
40
+ * `finish` part never arrives.
41
+ */
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.WINDOW_OVERLAP_CHARS = exports.DEFAULT_STREAM_WINDOW_CHARS = void 0;
44
+ exports.validateWindowChars = validateWindowChars;
45
+ exports.scanOutputText = scanOutputText;
46
+ exports.extractGeneratedText = extractGeneratedText;
47
+ exports.scanOutputStream = scanOutputStream;
48
+ const guard_1 = require("../guard");
49
+ const logger_1 = require("../logger");
50
+ /**
51
+ * Default new characters of streamed output per scanned window.
52
+ *
53
+ * Not taken from any external standard: 1000 was chosen as a balance between
54
+ * how long output is held and how many Guard API round trips a stream costs.
55
+ * At roughly four characters a token it is about 250 tokens, which takes a
56
+ * model a few seconds to generate — about as long as one Guard round trip, so
57
+ * scanning a window roughly keeps pace with generating the next. A smaller
58
+ * window delivers the first text sooner and costs more scans; a larger one
59
+ * costs fewer scans and holds output longer.
60
+ */
61
+ exports.DEFAULT_STREAM_WINDOW_CHARS = 1000;
62
+ /**
63
+ * Characters of scanned text always kept held and rescanned at the head of
64
+ * the next window — the longest secret guaranteed to be seen whole across a
65
+ * window boundary.
66
+ */
67
+ exports.WINDOW_OVERLAP_CHARS = 200;
68
+ /** Throws `RangeError` unless `value` is a usable `streamWindowChars`. */
69
+ function validateWindowChars(value) {
70
+ if (!(value > 0)) {
71
+ throw new RangeError(`streamWindowChars must be a positive number, got ${value}`);
72
+ }
73
+ return value;
74
+ }
75
+ /**
76
+ * Scan one piece of model output. Resolves when the output may be delivered;
77
+ * throws `PromptGuardBlockedError` when enforce mode stops it, and rethrows
78
+ * the `GuardApiError` when failing closed. `sessionId` names the
79
+ * Conversation the call belongs to, when it names one.
80
+ */
81
+ async function scanOutputText(text, policy, modelId, what = "response", sessionId) {
82
+ try {
83
+ const decision = await policy.guard.scan([{ role: "assistant", content: text }], "output", modelId, { framework: "vercel-ai-sdk", ...(sessionId !== undefined ? { sessionId } : {}) });
84
+ if (decision.blocked || decision.redacted) {
85
+ // Generated output cannot be rewritten after the fact, so an
86
+ // output-direction redact decision is enforced as a block.
87
+ if (policy.mode === "enforce")
88
+ throw new guard_1.PromptGuardBlockedError(decision);
89
+ logger_1.logger.warn(`[monitor] would ${decision.redacted ? "redact" : "block"} ${what}: ${decision.threatType}`);
90
+ }
91
+ return "scanned";
92
+ }
93
+ catch (err) {
94
+ if (err instanceof guard_1.PromptGuardBlockedError)
95
+ throw err;
96
+ // Only a Guard API outage is eligible for fail-open; rethrow the
97
+ // original error when failing closed.
98
+ if (!(err instanceof guard_1.GuardApiError))
99
+ throw err;
100
+ if (!policy.failOpen)
101
+ throw err;
102
+ logger_1.logger.warn(`Guard API unavailable, ${what} left unscanned (failOpen=true): ${(0, guard_1.safeErrorLabel)(err)}`);
103
+ return "failed-open";
104
+ }
105
+ }
106
+ /**
107
+ * Extract all scannable text from a `doGenerate` result.
108
+ *
109
+ * Handles both AI SDK result shapes:
110
+ * - **v4 (LanguageModelV1):** top-level `text` string plus `toolCalls`
111
+ * (every tool call's `args`/`input` is scanned — not just the first).
112
+ * - **v5+ (LanguageModelV2/V3):** `content` is an array of parts
113
+ * (`{ type: "text", text }` / `{ type: "tool-call", input }`) with no
114
+ * top-level `text`.
115
+ *
116
+ * Returns the concatenated text, or `null` when nothing scannable exists.
117
+ */
118
+ function extractGeneratedText(result) {
119
+ const texts = [];
120
+ const stringify = (value) => typeof value === "string" ? value : JSON.stringify(value);
121
+ if (typeof result?.text === "string" && result.text)
122
+ texts.push(result.text);
123
+ if (Array.isArray(result?.content)) {
124
+ for (const raw of result.content) {
125
+ if (!raw || typeof raw !== "object")
126
+ continue;
127
+ const part = raw;
128
+ if (part.type === "text" && typeof part.text === "string" && part.text) {
129
+ texts.push(part.text);
130
+ }
131
+ else if (part.type === "tool-call") {
132
+ const args = part.input ?? part.args;
133
+ if (args != null)
134
+ texts.push(stringify(args));
135
+ }
136
+ }
137
+ }
138
+ if (Array.isArray(result?.toolCalls)) {
139
+ for (const raw of result.toolCalls) {
140
+ if (!raw || typeof raw !== "object")
141
+ continue;
142
+ const call = raw;
143
+ const args = call.args ?? call.input;
144
+ if (args != null)
145
+ texts.push(stringify(args));
146
+ }
147
+ }
148
+ const combined = texts.join("\n");
149
+ return combined || null;
150
+ }
151
+ /**
152
+ * Accumulates a model stream's parts into the result `doGenerate` would have
153
+ * returned, so the final scan sees exactly what a generated response would.
154
+ *
155
+ * Recognises both stream-part vocabularies:
156
+ * - **LanguageModelV1:** `text-delta { textDelta }`,
157
+ * `tool-call-delta { toolCallId, argsTextDelta }`, `tool-call { args }`.
158
+ * - **LanguageModelV2/V3:** `text-delta { id, delta }`,
159
+ * `tool-input-delta { id, delta }`, `tool-call { toolCallId, input }`.
160
+ */
161
+ class StreamedOutput {
162
+ constructor() {
163
+ /** Text blocks by id, in first-seen order (V1 parts have no id). */
164
+ this.texts = new Map();
165
+ /** Tool input assembled from deltas, by call id. */
166
+ this.toolDeltas = new Map();
167
+ /** Complete tool calls, by call id. */
168
+ this.toolCalls = new Map();
169
+ }
170
+ /**
171
+ * Record one part. Returns the newly visible text it carries (for the
172
+ * window), or `""` when it carries none.
173
+ */
174
+ add(part) {
175
+ switch (part.type) {
176
+ case "text-delta": {
177
+ const delta = str(part.delta) ?? str(part.textDelta) ?? "";
178
+ const id = str(part.id) ?? "";
179
+ this.texts.set(id, (this.texts.get(id) ?? "") + delta);
180
+ return delta;
181
+ }
182
+ case "tool-input-delta":
183
+ case "tool-call-delta": {
184
+ const delta = str(part.delta) ?? str(part.argsTextDelta) ?? "";
185
+ const id = str(part.id) ?? str(part.toolCallId) ?? "";
186
+ this.toolDeltas.set(id, (this.toolDeltas.get(id) ?? "") + delta);
187
+ return delta;
188
+ }
189
+ case "tool-call": {
190
+ const id = str(part.toolCallId) ?? "";
191
+ const input = part.input ?? part.args;
192
+ if (input == null)
193
+ return "";
194
+ this.toolCalls.set(id, input);
195
+ // Input already streamed as deltas was counted when it arrived.
196
+ if (this.toolDeltas.has(id))
197
+ return "";
198
+ return typeof input === "string" ? input : JSON.stringify(input);
199
+ }
200
+ default:
201
+ return "";
202
+ }
203
+ }
204
+ /** The complete output, as `extractGeneratedText` renders a generated result. */
205
+ text() {
206
+ const content = [];
207
+ for (const text of this.texts.values())
208
+ content.push({ type: "text", text });
209
+ for (const [id, input] of this.toolCalls)
210
+ content.push({ type: "tool-call", toolCallId: id, input });
211
+ // A call streamed as deltas whose closing `tool-call` part never came.
212
+ for (const [id, input] of this.toolDeltas) {
213
+ if (!this.toolCalls.has(id))
214
+ content.push({ type: "tool-call", toolCallId: id, input });
215
+ }
216
+ return extractGeneratedText({ content });
217
+ }
218
+ }
219
+ function str(value) {
220
+ return typeof value === "string" ? value : undefined;
221
+ }
222
+ /**
223
+ * Wrap a model stream so its output is scanned as described in this module's
224
+ * header. Parts are passed through unchanged and in order; where the policy
225
+ * stops the output, the stream ends with `PromptGuardBlockedError` (or the
226
+ * `GuardApiError` when failing closed), as `stopAs` says.
227
+ */
228
+ function scanOutputStream(stream, policy, modelId, stopAs = "error", sessionId) {
229
+ const output = new StreamedOutput();
230
+ const enforce = policy.mode === "enforce";
231
+ const what = "streamed response";
232
+ /** Held parts, in order, with the scanned text each carries. */
233
+ let held = [];
234
+ /** Total length of the text carried by `held`. */
235
+ let heldChars = 0;
236
+ /** Characters that have arrived since the last window scan. */
237
+ let unscannedChars = 0;
238
+ /** Set once fail-open has given up on this stream. */
239
+ let unscanned = false;
240
+ /** Set once the stream has ended with an error part. */
241
+ let stopped = false;
242
+ const releaseAll = (controller) => {
243
+ for (const { part } of held)
244
+ controller.enqueue(part);
245
+ held = [];
246
+ heldChars = 0;
247
+ };
248
+ /** Release from the front while the overlap stays held behind it. */
249
+ const releaseScanned = (controller) => {
250
+ while (held.length && heldChars - held[0].text.length >= exports.WINDOW_OVERLAP_CHARS) {
251
+ const front = held.shift();
252
+ heldChars -= front.text.length;
253
+ controller.enqueue(front.part);
254
+ }
255
+ };
256
+ return stream.pipeThrough(new TransformStream({
257
+ async transform(part, controller) {
258
+ if (stopped)
259
+ return;
260
+ try {
261
+ await step(part, controller);
262
+ }
263
+ catch (err) {
264
+ stop(err, controller);
265
+ // Ends the stream here, and cancels the model's stream behind it.
266
+ controller.terminate();
267
+ }
268
+ },
269
+ async flush(controller) {
270
+ if (stopped)
271
+ return;
272
+ try {
273
+ await finish(controller);
274
+ }
275
+ catch (err) {
276
+ stop(err, controller);
277
+ }
278
+ },
279
+ }));
280
+ /** End a stopped stream as `stopAs` says. */
281
+ function stop(err, controller) {
282
+ if (stopAs === "error")
283
+ throw err;
284
+ stopped = true;
285
+ controller.enqueue({ type: "error", error: err });
286
+ }
287
+ async function step(part, controller) {
288
+ if (unscanned) {
289
+ controller.enqueue(part);
290
+ return;
291
+ }
292
+ const record = part && typeof part === "object" ? part : null;
293
+ const text = record ? output.add(record) : "";
294
+ if (!enforce) {
295
+ controller.enqueue(part);
296
+ return;
297
+ }
298
+ // Nothing to scan and nothing ahead of it: no reason to hold it. The
299
+ // finish part always waits for the final scan.
300
+ if (!text && !held.length && record?.type !== "finish") {
301
+ controller.enqueue(part);
302
+ return;
303
+ }
304
+ held.push({ part, text });
305
+ heldChars += text.length;
306
+ unscannedChars += text.length;
307
+ if (unscannedChars < policy.windowChars)
308
+ return;
309
+ unscannedChars = 0;
310
+ const window = held.map((h) => h.text).join("");
311
+ // Throws on block or fail-closed, which stops the stream (see `stop`).
312
+ const outcome = await scanOutputText(window, policy, modelId, `rest of ${what}`, sessionId);
313
+ if (outcome === "scanned") {
314
+ releaseScanned(controller);
315
+ }
316
+ else {
317
+ unscanned = true;
318
+ releaseAll(controller);
319
+ }
320
+ }
321
+ async function finish(controller) {
322
+ if (unscanned)
323
+ return;
324
+ const text = output.text();
325
+ if (text)
326
+ await scanOutputText(text, policy, modelId, what, sessionId);
327
+ releaseAll(controller);
328
+ }
329
+ }
330
+ //# sourceMappingURL=vercel-ai-output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel-ai-output.js","sourceRoot":"","sources":["../../src/integrations/vercel-ai-output.ts"],"names":[],"mappings":"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"}
@@ -1,29 +1,65 @@
1
1
  /**
2
- * Vercel AI SDK integration - middleware for `ai` package.
2
+ * Vercel AI SDK integration for the `ai` package.
3
3
  *
4
- * Works with the Vercel AI SDK's `wrapLanguageModel` API to intercept
5
- * all model calls with PromptGuard security scanning.
4
+ * Two ways in, with the same scanning:
5
+ *
6
+ * - `promptguard(model)` wraps a language model of ai 5, 6 or 7
7
+ * (specification v2, v3 or v4), and the wrapped model is used wherever the
8
+ * original was:
6
9
  *
7
- * @example
8
10
  * ```ts
9
11
  * import { openai } from '@ai-sdk/openai';
10
- * import { wrapLanguageModel, generateText } from 'ai';
12
+ * import { generateText } from 'ai';
13
+ * import { promptguard } from 'promptguard-sdk/integrations/vercel-ai';
14
+ *
15
+ * const model = promptguard(openai('gpt-5-nano'), { scanResponses: true });
16
+ * const { text } = await generateText({ model, prompt: 'Hello!' });
17
+ * ```
18
+ *
19
+ * - `promptGuardMiddleware(options)` is the same scanning as AI SDK
20
+ * middleware, for `wrapLanguageModel` — useful alongside other middleware,
21
+ * and the way in on ai 4:
22
+ *
23
+ * ```ts
24
+ * import { wrapLanguageModel } from 'ai';
11
25
  * import { promptGuardMiddleware } from 'promptguard-sdk/integrations/vercel-ai';
12
26
  *
13
27
  * const model = wrapLanguageModel({
14
28
  * model: openai('gpt-5-nano'),
15
29
  * middleware: promptGuardMiddleware({ apiKey: 'pg_live_xxx' }),
16
30
  * });
17
- *
18
- * const { text } = await generateText({ model, prompt: 'Hello!' });
19
31
  * ```
32
+ *
33
+ * With `scanResponses: true`, `streamText()` output is scanned too: in
34
+ * enforce mode text is held until it has been scanned, a window
35
+ * (`streamWindowChars`, default 1000 characters) at a time, and the complete
36
+ * text is scanned before the stream finishes. See `vercel-ai-output.ts` for
37
+ * the full behaviour and its guarantee.
38
+ *
39
+ * Neither imports `ai`: the wrapper calls the middleware's hooks the way
40
+ * `wrapLanguageModel` does, so it works with whichever `ai` the application
41
+ * has (ai@7 is ESM-only; this package is CommonJS) and loads without one.
20
42
  */
21
43
  import { type GuardClientConfig } from "../guard";
22
44
  import { type LogLevel } from "../logger";
23
- export interface PromptGuardMiddlewareOptions extends GuardClientConfig {
45
+ export interface PromptGuardMiddlewareOptions extends Omit<GuardClientConfig, "apiKey"> {
46
+ /** PromptGuard API key (default: the `PROMPTGUARD_API_KEY` environment variable). */
47
+ apiKey?: string;
24
48
  mode?: "enforce" | "monitor";
49
+ /**
50
+ * Scan the model's output as well as its input (default: `false`). Applies
51
+ * to `generateText()` and to `streamText()` alike.
52
+ */
25
53
  scanResponses?: boolean;
26
54
  failOpen?: boolean;
55
+ /**
56
+ * With `scanResponses` in enforce mode, how many new characters of
57
+ * streamed output are collected before the held text is scanned (default:
58
+ * `1000`; see `DEFAULT_STREAM_WINDOW_CHARS` for why). A smaller window
59
+ * delivers the first text sooner at the cost of more scans; `Infinity`
60
+ * holds the whole response for a single scan at the end.
61
+ */
62
+ streamWindowChars?: number;
27
63
  /**
28
64
  * SDK log verbosity (default: `"warn"`). Set to `"silent"` to suppress logs.
29
65
  * NOTE: this sets the **process-global** SDK log level (shared with `init()`
@@ -32,36 +68,90 @@ export interface PromptGuardMiddlewareOptions extends GuardClientConfig {
32
68
  logLevel?: LogLevel;
33
69
  /** Convenience shorthand for `logLevel: "silent"`. */
34
70
  silent?: boolean;
71
+ /**
72
+ * The Conversation to name when a call names none of its own. The AI SDK
73
+ * has no conversation id, so a call names one with
74
+ * `providerOptions: { promptguard: { sessionId } }` (`providerMetadata` on
75
+ * `ai@4`), which wins over this. Without either, no session id is sent.
76
+ */
77
+ sessionId?: string;
35
78
  }
36
79
  /**
37
80
  * Create a Vercel AI SDK middleware that scans inputs/outputs via
38
81
  * the PromptGuard Guard API.
39
82
  *
40
83
  * Returns an object conforming to the Vercel AI SDK `LanguageModelMiddleware`
41
- * interface (`transformParams` + `wrapGenerate`).
84
+ * interface (`transformParams`, plus `wrapGenerate` and `wrapStream` when
85
+ * `scanResponses` is set). The hooks are generic over the call options and
86
+ * results they pass through, so the object is assignable to the middleware
87
+ * type of each supported `ai` major (checked by `tests/vercel-ai-types.test.ts`).
42
88
  */
43
- export declare function promptGuardMiddleware(options: PromptGuardMiddlewareOptions): {
89
+ export declare function promptGuardMiddleware(options?: PromptGuardMiddlewareOptions): {
90
+ /**
91
+ * The middleware specification version. ai@6 requires `"v3"` here;
92
+ * ai@7 accepts any version string, and ai@5 does not read it. The hooks
93
+ * themselves read every call shape from V2 to V4.
94
+ */
95
+ specificationVersion: "v3";
44
96
  /**
45
97
  * transformParams - scans the input messages before they reach the model.
46
98
  */
47
- transformParams: ({ params }: {
48
- params: Record<string, unknown>;
49
- }) => Promise<Record<string, unknown>>;
99
+ transformParams: <P extends object>({ params, model, }: {
100
+ params: P;
101
+ model?: ModelIdentity;
102
+ }) => Promise<P>;
50
103
  /**
51
104
  * wrapGenerate - optionally scans the model's response.
52
105
  */
53
- wrapGenerate: (({ doGenerate, params, }: {
54
- doGenerate: () => Promise<unknown>;
55
- params: Record<string, unknown>;
56
- }) => Promise<unknown>) | undefined;
106
+ wrapGenerate: (<R>({ doGenerate, params, model, }: {
107
+ doGenerate: () => PromiseLike<R>;
108
+ params?: object;
109
+ model?: ModelIdentity;
110
+ }) => Promise<R>) | undefined;
57
111
  /**
58
- * wrapStream - streamed outputs are NOT scanned (chunks are consumed
59
- * incrementally by the caller and cannot be buffered without breaking
60
- * stream semantics). Present only to log the skip so the gap is never
61
- * silent; input scanning via transformParams still applies.
112
+ * wrapStream - optionally scans the model's streamed response, in
113
+ * windows and then in full, with the same decisions and the same
114
+ * fail-open policy as wrapGenerate (see `vercel-ai-output.ts`).
62
115
  */
63
- wrapStream: (({ doStream }: {
64
- doStream: () => Promise<unknown>;
65
- }) => Promise<unknown>) | undefined;
116
+ wrapStream: (<R>({ doStream, params, model, }: {
117
+ doStream: () => PromiseLike<R>;
118
+ params?: object;
119
+ model?: ModelIdentity;
120
+ }) => Promise<R>) | undefined;
66
121
  };
122
+ /**
123
+ * What every AI SDK language model specification (V1 to V4) has in common,
124
+ * which is all `promptguard()` relies on.
125
+ */
126
+ export interface LanguageModelLike {
127
+ readonly specificationVersion: string;
128
+ readonly modelId: string;
129
+ doGenerate(options: never): PromiseLike<unknown>;
130
+ doStream(options: never): PromiseLike<unknown>;
131
+ }
132
+ /**
133
+ * Wrap an AI SDK language model so every call it makes is scanned by
134
+ * PromptGuard, exactly as `promptGuardMiddleware(options)` would scan it
135
+ * under `wrapLanguageModel` — generated and streamed alike.
136
+ *
137
+ * The wrapped model is the model it wraps in every other respect: its own
138
+ * specification version, provider, model id, supported URLs and any other
139
+ * property, so it is accepted wherever the original was and has the same
140
+ * type. Options are the middleware's; the API key defaults to
141
+ * `PROMPTGUARD_API_KEY`, and a missing one throws here rather than on the
142
+ * first call.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * const model = promptguard(openai('gpt-5-nano'), { scanResponses: true });
147
+ * const result = streamText({ model, prompt: 'Hello!' });
148
+ * ```
149
+ */
150
+ export declare function promptguard<M extends LanguageModelLike>(model: M, options?: PromptGuardMiddlewareOptions): M;
151
+ /** What the hooks read from the model `wrapLanguageModel` hands them. */
152
+ interface ModelIdentity {
153
+ readonly specificationVersion?: unknown;
154
+ readonly modelId?: unknown;
155
+ }
156
+ export {};
67
157
  //# sourceMappingURL=vercel-ai.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vercel-ai.d.ts","sourceRoot":"","sources":["../../src/integrations/vercel-ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAGL,KAAK,iBAAiB,EAIvB,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,QAAQ,EAAuB,MAAM,WAAW,CAAA;AAO9D,MAAM,WAAW,4BAA6B,SAAQ,iBAAiB;IACrE,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,sDAAsD;IACtD,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAMD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B;IAkBvE;;OAEG;kCACiC;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE;IAuEvE;;OAEG;6CAKI;QACD,UAAU,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;QAClC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAChC;IAyCL;;;;;OAKG;gCAEsB;QAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE;EAMhE"}
1
+ {"version":3,"file":"vercel-ai.d.ts","sourceRoot":"","sources":["../../src/integrations/vercel-ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAGL,KAAK,iBAAiB,EAIvB,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,QAAQ,EAAuB,MAAM,WAAW,CAAA;AAiB9D,MAAM,WAAW,4BAA6B,SAAQ,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC;IACrF,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IAC5B;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,sDAAsD;IACtD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAMD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,4BAAiC;IA2B5E;;;;OAIG;;IAGH;;OAEG;sBACqB,CAAC,SAAS,MAAM,sBAGrC;QACD,MAAM,EAAE,CAAC,CAAA;QACT,KAAK,CAAC,EAAE,aAAa,CAAA;KACtB,KAAG,OAAO,CAAC,CAAC,CAAC;IAoEd;;OAEG;oBAEQ,CAAC,kCAIL;QACD,UAAU,EAAE,MAAM,WAAW,CAAC,CAAC,CAAC,CAAA;QAChC,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,KAAK,CAAC,EAAE,aAAa,CAAA;KACtB,KAAG,OAAO,CAAC,CAAC,CAAC;IAiBlB;;;;OAIG;kBAEQ,CAAC,gCAIL;QACD,QAAQ,EAAE,MAAM,WAAW,CAAC,CAAC,CAAC,CAAA;QAC9B,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,KAAK,CAAC,EAAE,aAAa,CAAA;KACtB,KAAG,OAAO,CAAC,CAAC,CAAC;EAuBrB;AAyBD;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,UAAU,CAAC,OAAO,EAAE,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;IAChD,QAAQ,CAAC,OAAO,EAAE,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;CAC/C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,iBAAiB,EACrD,KAAK,EAAE,CAAC,EACR,OAAO,GAAE,4BAAiC,GACzC,CAAC,CA+DH;AAgBD,yEAAyE;AACzE,UAAU,aAAa;IACrB,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAC3B"}