promptguard-sdk 1.2.0 → 1.5.1
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 +21 -0
- package/README.md +226 -78
- package/dist/auto.d.ts +40 -0
- package/dist/auto.d.ts.map +1 -0
- package/dist/auto.js +118 -0
- package/dist/auto.js.map +1 -0
- package/dist/guard.d.ts +80 -0
- package/dist/guard.d.ts.map +1 -0
- package/dist/guard.js +104 -0
- package/dist/guard.js.map +1 -0
- package/dist/index.d.ts +105 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +111 -29
- package/dist/index.js.map +1 -1
- package/dist/integrations/langchain.d.ts +55 -0
- package/dist/integrations/langchain.d.ts.map +1 -0
- package/dist/integrations/langchain.js +212 -0
- package/dist/integrations/langchain.js.map +1 -0
- package/dist/integrations/vercel-ai.d.ts +49 -0
- package/dist/integrations/vercel-ai.d.ts.map +1 -0
- package/dist/integrations/vercel-ai.js +170 -0
- package/dist/integrations/vercel-ai.js.map +1 -0
- package/dist/patches/anthropic.d.ts +13 -0
- package/dist/patches/anthropic.d.ts.map +1 -0
- package/dist/patches/anthropic.js +188 -0
- package/dist/patches/anthropic.js.map +1 -0
- package/dist/patches/bedrock.d.ts +14 -0
- package/dist/patches/bedrock.d.ts.map +1 -0
- package/dist/patches/bedrock.js +246 -0
- package/dist/patches/bedrock.js.map +1 -0
- package/dist/patches/cohere.d.ts +14 -0
- package/dist/patches/cohere.d.ts.map +1 -0
- package/dist/patches/cohere.js +183 -0
- package/dist/patches/cohere.js.map +1 -0
- package/dist/patches/google.d.ts +13 -0
- package/dist/patches/google.d.ts.map +1 -0
- package/dist/patches/google.js +163 -0
- package/dist/patches/google.js.map +1 -0
- package/dist/patches/openai.d.ts +14 -0
- package/dist/patches/openai.d.ts.map +1 -0
- package/dist/patches/openai.js +165 -0
- package/dist/patches/openai.js.map +1 -0
- package/package.json +83 -11
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LangChain.js integration — PromptGuardCallbackHandler.
|
|
4
|
+
*
|
|
5
|
+
* Implements the LangChain BaseCallbackHandler interface to scan prompts
|
|
6
|
+
* before LLM calls and responses after, with rich context about chains,
|
|
7
|
+
* tools, and agent steps.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { PromptGuardCallbackHandler } from 'promptguard-sdk/integrations/langchain';
|
|
12
|
+
*
|
|
13
|
+
* const handler = new PromptGuardCallbackHandler({ apiKey: 'pg_xxx' });
|
|
14
|
+
*
|
|
15
|
+
* const llm = new ChatOpenAI({ callbacks: [handler] });
|
|
16
|
+
* // or
|
|
17
|
+
* await chain.invoke({ input: '...' }, { callbacks: [handler] });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.PromptGuardCallbackHandler = void 0;
|
|
22
|
+
const guard_1 = require("../guard");
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Handler
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
class PromptGuardCallbackHandler {
|
|
27
|
+
constructor(options) {
|
|
28
|
+
this.name = "promptguard";
|
|
29
|
+
/** Chain context per run ID for richer threat detection. */
|
|
30
|
+
this.chainContext = new Map();
|
|
31
|
+
const apiKey = options.apiKey ?? process.env.PROMPTGUARD_API_KEY ?? "";
|
|
32
|
+
if (!apiKey) {
|
|
33
|
+
throw new Error("PromptGuard API key required. Pass apiKey or set PROMPTGUARD_API_KEY.");
|
|
34
|
+
}
|
|
35
|
+
this.guard = new guard_1.GuardClient({
|
|
36
|
+
apiKey,
|
|
37
|
+
baseUrl: options.baseUrl,
|
|
38
|
+
timeout: options.timeout,
|
|
39
|
+
});
|
|
40
|
+
this.mode = options.mode ?? "enforce";
|
|
41
|
+
this.scanResponses = options.scanResponses ?? true;
|
|
42
|
+
this.failOpen = options.failOpen ?? true;
|
|
43
|
+
}
|
|
44
|
+
// -- LLM callbacks -------------------------------------------------------
|
|
45
|
+
async handleLLMStart(serialized, prompts, runId, parentRunId, _extraParams, tags, metadata) {
|
|
46
|
+
const messages = prompts.map((p) => ({
|
|
47
|
+
role: "user",
|
|
48
|
+
content: p,
|
|
49
|
+
}));
|
|
50
|
+
const model = this.extractModel(serialized);
|
|
51
|
+
const context = this.buildContext(runId, parentRunId, "llm", tags, metadata);
|
|
52
|
+
const decision = await this.safeScan(messages, "input", model, context);
|
|
53
|
+
this.handleDecision(decision, runId);
|
|
54
|
+
}
|
|
55
|
+
async handleChatModelStart(serialized, messages, runId, parentRunId, _extraParams, tags, metadata) {
|
|
56
|
+
const guardMessages = [];
|
|
57
|
+
for (const messageList of messages) {
|
|
58
|
+
for (const raw of messageList) {
|
|
59
|
+
const msg = raw;
|
|
60
|
+
const role = this.mapRole(String(msg?.type ?? msg?.role ?? "user"));
|
|
61
|
+
const content = String(msg?.content ?? msg?.text ?? "");
|
|
62
|
+
guardMessages.push({ role, content });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const model = this.extractModel(serialized);
|
|
66
|
+
const context = this.buildContext(runId, parentRunId, "chat_model", tags, metadata);
|
|
67
|
+
const decision = await this.safeScan(guardMessages, "input", model, context);
|
|
68
|
+
this.handleDecision(decision, runId);
|
|
69
|
+
}
|
|
70
|
+
async handleLLMEnd(output, runId) {
|
|
71
|
+
if (!this.scanResponses)
|
|
72
|
+
return;
|
|
73
|
+
const text = this.extractLLMResponse(output);
|
|
74
|
+
if (!text)
|
|
75
|
+
return;
|
|
76
|
+
const context = this.buildContext(runId, undefined, "llm_response");
|
|
77
|
+
const decision = await this.safeScan([{ role: "assistant", content: text }], "output", undefined, context);
|
|
78
|
+
this.handleDecision(decision, runId);
|
|
79
|
+
}
|
|
80
|
+
handleLLMError(_error, runId) {
|
|
81
|
+
this.chainContext.delete(runId);
|
|
82
|
+
}
|
|
83
|
+
// -- Chain callbacks -----------------------------------------------------
|
|
84
|
+
async handleChainStart(serialized, _inputs, runId, parentRunId, tags) {
|
|
85
|
+
const chainName = serialized?.id?.slice(-1)[0] ?? "unknown";
|
|
86
|
+
this.chainContext.set(runId, {
|
|
87
|
+
chain_name: chainName,
|
|
88
|
+
parent_run_id: parentRunId,
|
|
89
|
+
tags,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
handleChainEnd(_outputs, runId) {
|
|
93
|
+
this.chainContext.delete(runId);
|
|
94
|
+
}
|
|
95
|
+
handleChainError(_error, runId) {
|
|
96
|
+
this.chainContext.delete(runId);
|
|
97
|
+
}
|
|
98
|
+
// -- Tool callbacks ------------------------------------------------------
|
|
99
|
+
async handleToolStart(serialized, input, runId, parentRunId, tags, metadata) {
|
|
100
|
+
const toolName = serialized?.name ??
|
|
101
|
+
serialized?.id?.slice(-1)[0] ??
|
|
102
|
+
"unknown";
|
|
103
|
+
const context = this.buildContext(runId, parentRunId, "tool", tags, {
|
|
104
|
+
...(metadata ?? {}),
|
|
105
|
+
tool_name: toolName,
|
|
106
|
+
});
|
|
107
|
+
const decision = await this.safeScan([{ role: "user", content: input }], "input", "tool", context);
|
|
108
|
+
this.handleDecision(decision, runId);
|
|
109
|
+
}
|
|
110
|
+
async handleToolEnd(output, runId) {
|
|
111
|
+
if (!this.scanResponses)
|
|
112
|
+
return;
|
|
113
|
+
const text = output ? String(output) : "";
|
|
114
|
+
if (!text)
|
|
115
|
+
return;
|
|
116
|
+
const context = this.buildContext(runId, undefined, "tool_response");
|
|
117
|
+
const decision = await this.safeScan([{ role: "assistant", content: text }], "output", undefined, context);
|
|
118
|
+
this.handleDecision(decision, runId);
|
|
119
|
+
}
|
|
120
|
+
handleToolError(_error, runId) {
|
|
121
|
+
this.chainContext.delete(runId);
|
|
122
|
+
}
|
|
123
|
+
// -- Retriever (no-op) ---------------------------------------------------
|
|
124
|
+
handleRetrieverStart(..._args) {
|
|
125
|
+
/* no-op */
|
|
126
|
+
}
|
|
127
|
+
handleRetrieverEnd(..._args) {
|
|
128
|
+
/* no-op */
|
|
129
|
+
}
|
|
130
|
+
handleRetrieverError(..._args) {
|
|
131
|
+
/* no-op */
|
|
132
|
+
}
|
|
133
|
+
handleText(..._args) {
|
|
134
|
+
/* no-op */
|
|
135
|
+
}
|
|
136
|
+
// -- Internal helpers ----------------------------------------------------
|
|
137
|
+
async safeScan(messages, direction, model, context) {
|
|
138
|
+
try {
|
|
139
|
+
return await this.guard.scan(messages, direction, model, context);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
if (!this.failOpen)
|
|
143
|
+
throw new Error("Guard API unavailable");
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
handleDecision(decision, runId) {
|
|
148
|
+
if (!decision)
|
|
149
|
+
return;
|
|
150
|
+
if (decision.blocked) {
|
|
151
|
+
if (this.mode === "enforce")
|
|
152
|
+
throw new guard_1.PromptGuardBlockedError(decision);
|
|
153
|
+
console.warn(`[promptguard][monitor] would block: ${decision.threatType} (event=${decision.eventId}, run=${runId})`);
|
|
154
|
+
}
|
|
155
|
+
if (decision.redacted) {
|
|
156
|
+
console.info(`[promptguard] redacted content (event=${decision.eventId}, run=${runId})`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
buildContext(runId, parentRunId, component = "unknown", tags, metadata) {
|
|
160
|
+
const chainInfo = this.chainContext.get(runId) ??
|
|
161
|
+
(parentRunId ? this.chainContext.get(parentRunId) : undefined) ??
|
|
162
|
+
{};
|
|
163
|
+
return {
|
|
164
|
+
framework: "langchain",
|
|
165
|
+
chain_name: chainInfo.chain_name,
|
|
166
|
+
session_id: runId,
|
|
167
|
+
metadata: {
|
|
168
|
+
component,
|
|
169
|
+
tags: tags ?? chainInfo.tags,
|
|
170
|
+
...(metadata ?? {}),
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
extractModel(serialized) {
|
|
175
|
+
const kwargs = serialized?.kwargs;
|
|
176
|
+
return (kwargs?.model_name ??
|
|
177
|
+
kwargs?.model ??
|
|
178
|
+
(serialized?.id ?? []).slice(-1)[0] ??
|
|
179
|
+
"unknown");
|
|
180
|
+
}
|
|
181
|
+
mapRole(type) {
|
|
182
|
+
const map = {
|
|
183
|
+
human: "user",
|
|
184
|
+
ai: "assistant",
|
|
185
|
+
system: "system",
|
|
186
|
+
};
|
|
187
|
+
return map[type] ?? type;
|
|
188
|
+
}
|
|
189
|
+
extractLLMResponse(output) {
|
|
190
|
+
try {
|
|
191
|
+
const typed = output;
|
|
192
|
+
if (typed?.generations) {
|
|
193
|
+
const texts = [];
|
|
194
|
+
for (const genList of typed.generations) {
|
|
195
|
+
for (const gen of genList) {
|
|
196
|
+
if (gen?.text)
|
|
197
|
+
texts.push(gen.text);
|
|
198
|
+
else if (gen?.message?.content)
|
|
199
|
+
texts.push(String(gen.message.content));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return texts.length ? texts.join("\n") : null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// ignore
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
exports.PromptGuardCallbackHandler = PromptGuardCallbackHandler;
|
|
212
|
+
//# sourceMappingURL=langchain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"langchain.js","sourceRoot":"","sources":["../../src/integrations/langchain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel AI SDK integration — middleware for `ai` package.
|
|
3
|
+
*
|
|
4
|
+
* Works with the Vercel AI SDK's `wrapLanguageModel` API to intercept
|
|
5
|
+
* all model calls with PromptGuard security scanning.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { openai } from '@ai-sdk/openai';
|
|
10
|
+
* import { wrapLanguageModel, generateText } from 'ai';
|
|
11
|
+
* import { promptGuardMiddleware } from 'promptguard-sdk/integrations/vercel-ai';
|
|
12
|
+
*
|
|
13
|
+
* const model = wrapLanguageModel({
|
|
14
|
+
* model: openai('gpt-4o'),
|
|
15
|
+
* middleware: promptGuardMiddleware({ apiKey: 'pg_xxx' }),
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* const { text } = await generateText({ model, prompt: 'Hello!' });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import { type GuardClientConfig } from "../guard";
|
|
22
|
+
export interface PromptGuardMiddlewareOptions extends GuardClientConfig {
|
|
23
|
+
mode?: "enforce" | "monitor";
|
|
24
|
+
scanResponses?: boolean;
|
|
25
|
+
failOpen?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a Vercel AI SDK middleware that scans inputs/outputs via
|
|
29
|
+
* the PromptGuard Guard API.
|
|
30
|
+
*
|
|
31
|
+
* Returns an object conforming to the Vercel AI SDK `LanguageModelMiddleware`
|
|
32
|
+
* interface (`transformParams` + `wrapGenerate`).
|
|
33
|
+
*/
|
|
34
|
+
export declare function promptGuardMiddleware(options: PromptGuardMiddlewareOptions): {
|
|
35
|
+
/**
|
|
36
|
+
* transformParams — scans the input messages before they reach the model.
|
|
37
|
+
*/
|
|
38
|
+
transformParams: ({ params }: {
|
|
39
|
+
params: Record<string, unknown>;
|
|
40
|
+
}) => Promise<Record<string, unknown>>;
|
|
41
|
+
/**
|
|
42
|
+
* wrapGenerate — optionally scans the model's response.
|
|
43
|
+
*/
|
|
44
|
+
wrapGenerate: (({ doGenerate, params, }: {
|
|
45
|
+
doGenerate: () => Promise<unknown>;
|
|
46
|
+
params: Record<string, unknown>;
|
|
47
|
+
}) => Promise<unknown>) | undefined;
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=vercel-ai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vercel-ai.d.ts","sourceRoot":"","sources":["../../src/integrations/vercel-ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAEL,KAAK,iBAAiB,EAGvB,MAAM,UAAU,CAAA;AAMjB,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;CACnB;AAMD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B;IAiBvE;;OAEG;kCACiC;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE;IAuCvE;;OAEG;6CAKI;QACD,UAAU,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;QAClC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAChC;EAiCR"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vercel AI SDK integration — middleware for `ai` package.
|
|
4
|
+
*
|
|
5
|
+
* Works with the Vercel AI SDK's `wrapLanguageModel` API to intercept
|
|
6
|
+
* all model calls with PromptGuard security scanning.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { openai } from '@ai-sdk/openai';
|
|
11
|
+
* import { wrapLanguageModel, generateText } from 'ai';
|
|
12
|
+
* import { promptGuardMiddleware } from 'promptguard-sdk/integrations/vercel-ai';
|
|
13
|
+
*
|
|
14
|
+
* const model = wrapLanguageModel({
|
|
15
|
+
* model: openai('gpt-4o'),
|
|
16
|
+
* middleware: promptGuardMiddleware({ apiKey: 'pg_xxx' }),
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* const { text } = await generateText({ model, prompt: 'Hello!' });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.promptGuardMiddleware = promptGuardMiddleware;
|
|
24
|
+
const guard_1 = require("../guard");
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Middleware factory
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
/**
|
|
29
|
+
* Create a Vercel AI SDK middleware that scans inputs/outputs via
|
|
30
|
+
* the PromptGuard Guard API.
|
|
31
|
+
*
|
|
32
|
+
* Returns an object conforming to the Vercel AI SDK `LanguageModelMiddleware`
|
|
33
|
+
* interface (`transformParams` + `wrapGenerate`).
|
|
34
|
+
*/
|
|
35
|
+
function promptGuardMiddleware(options) {
|
|
36
|
+
const apiKey = options.apiKey ?? process.env.PROMPTGUARD_API_KEY ?? "";
|
|
37
|
+
if (!apiKey) {
|
|
38
|
+
throw new Error("PromptGuard API key required. Pass apiKey or set PROMPTGUARD_API_KEY.");
|
|
39
|
+
}
|
|
40
|
+
const guard = new guard_1.GuardClient({
|
|
41
|
+
apiKey,
|
|
42
|
+
baseUrl: options.baseUrl,
|
|
43
|
+
timeout: options.timeout,
|
|
44
|
+
});
|
|
45
|
+
const mode = options.mode ?? "enforce";
|
|
46
|
+
const scanResponses = options.scanResponses ?? false;
|
|
47
|
+
const failOpen = options.failOpen ?? true;
|
|
48
|
+
return {
|
|
49
|
+
/**
|
|
50
|
+
* transformParams — scans the input messages before they reach the model.
|
|
51
|
+
*/
|
|
52
|
+
transformParams: async ({ params }) => {
|
|
53
|
+
const prompt = params?.prompt;
|
|
54
|
+
if (!prompt)
|
|
55
|
+
return params;
|
|
56
|
+
const guardMessages = vercelPromptToGuardMessages(prompt);
|
|
57
|
+
if (!guardMessages.length)
|
|
58
|
+
return params;
|
|
59
|
+
try {
|
|
60
|
+
const decision = await guard.scan(guardMessages, "input", params?.modelId ?? undefined, {
|
|
61
|
+
framework: "vercel-ai-sdk",
|
|
62
|
+
});
|
|
63
|
+
if (decision.blocked) {
|
|
64
|
+
if (mode === "enforce")
|
|
65
|
+
throw new guard_1.PromptGuardBlockedError(decision);
|
|
66
|
+
console.warn(`[promptguard][monitor] would block: ${decision.threatType} (event=${decision.eventId})`);
|
|
67
|
+
}
|
|
68
|
+
if (decision.redacted && decision.redactedMessages && mode === "enforce") {
|
|
69
|
+
return {
|
|
70
|
+
...params,
|
|
71
|
+
prompt: applyRedactionToPrompt(prompt, decision.redactedMessages),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
if (err instanceof guard_1.PromptGuardBlockedError)
|
|
77
|
+
throw err;
|
|
78
|
+
if (!failOpen)
|
|
79
|
+
throw err;
|
|
80
|
+
// fail-open: continue with original params
|
|
81
|
+
}
|
|
82
|
+
return params;
|
|
83
|
+
},
|
|
84
|
+
/**
|
|
85
|
+
* wrapGenerate — optionally scans the model's response.
|
|
86
|
+
*/
|
|
87
|
+
wrapGenerate: scanResponses
|
|
88
|
+
? async ({ doGenerate, params, }) => {
|
|
89
|
+
const result = await doGenerate();
|
|
90
|
+
const typed = result;
|
|
91
|
+
const toolCalls = typed?.toolCalls;
|
|
92
|
+
const text = typed?.text ?? toolCalls?.[0]?.args ?? null;
|
|
93
|
+
if (typeof text !== "string" || !text)
|
|
94
|
+
return result;
|
|
95
|
+
try {
|
|
96
|
+
const respDecision = await guard.scan([{ role: "assistant", content: text }], "output", params?.modelId ?? undefined, { framework: "vercel-ai-sdk" });
|
|
97
|
+
if (respDecision.blocked) {
|
|
98
|
+
if (mode === "enforce") {
|
|
99
|
+
throw new guard_1.PromptGuardBlockedError(respDecision);
|
|
100
|
+
}
|
|
101
|
+
console.warn(`[promptguard][monitor] would block response: ${respDecision.threatType}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
if (err instanceof guard_1.PromptGuardBlockedError)
|
|
106
|
+
throw err;
|
|
107
|
+
if (!failOpen)
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
: undefined,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Helpers
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
/**
|
|
119
|
+
* Convert the Vercel AI SDK prompt structure to GuardMessages.
|
|
120
|
+
*
|
|
121
|
+
* The Vercel AI SDK prompt can be:
|
|
122
|
+
* - A string (simple prompt)
|
|
123
|
+
* - An array of message objects with `role` and `content`
|
|
124
|
+
*/
|
|
125
|
+
function vercelPromptToGuardMessages(prompt) {
|
|
126
|
+
if (typeof prompt === "string") {
|
|
127
|
+
return [{ role: "user", content: prompt }];
|
|
128
|
+
}
|
|
129
|
+
if (!Array.isArray(prompt))
|
|
130
|
+
return [];
|
|
131
|
+
const result = [];
|
|
132
|
+
for (const msg of prompt) {
|
|
133
|
+
if (!msg)
|
|
134
|
+
continue;
|
|
135
|
+
const role = String(msg.role ?? "user");
|
|
136
|
+
if (typeof msg.content === "string") {
|
|
137
|
+
result.push({ role, content: msg.content });
|
|
138
|
+
}
|
|
139
|
+
else if (Array.isArray(msg.content)) {
|
|
140
|
+
const textParts = [];
|
|
141
|
+
for (const part of msg.content) {
|
|
142
|
+
if (typeof part === "string")
|
|
143
|
+
textParts.push(part);
|
|
144
|
+
else if (part?.type === "text")
|
|
145
|
+
textParts.push(part.text ?? "");
|
|
146
|
+
}
|
|
147
|
+
if (textParts.length) {
|
|
148
|
+
result.push({ role, content: textParts.join("\n") });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Apply redacted content back into the Vercel AI SDK prompt structure.
|
|
156
|
+
*/
|
|
157
|
+
function applyRedactionToPrompt(prompt, redacted) {
|
|
158
|
+
if (typeof prompt === "string" && redacted[0]) {
|
|
159
|
+
return redacted[0].content;
|
|
160
|
+
}
|
|
161
|
+
if (!Array.isArray(prompt))
|
|
162
|
+
return prompt;
|
|
163
|
+
return prompt.map((msg, i) => {
|
|
164
|
+
if (i < redacted.length && msg && typeof msg === "object") {
|
|
165
|
+
return { ...msg, content: redacted[i].content };
|
|
166
|
+
}
|
|
167
|
+
return msg;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=vercel-ai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vercel-ai.js","sourceRoot":"","sources":["../../src/integrations/vercel-ai.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic SDK patch — wraps `Messages.prototype.create` from
|
|
3
|
+
* `@anthropic-ai/sdk`.
|
|
4
|
+
*
|
|
5
|
+
* Covers direct Anthropic usage and frameworks that use it (LangChain.js
|
|
6
|
+
* ChatAnthropic, etc.).
|
|
7
|
+
*/
|
|
8
|
+
import { type GuardMessage } from "../guard";
|
|
9
|
+
export declare function messagesToGuardFormat(messages: unknown[], system?: unknown): GuardMessage[];
|
|
10
|
+
export declare function extractResponseContent(response: unknown): string | null;
|
|
11
|
+
export declare function apply(): boolean;
|
|
12
|
+
export declare function revert(): void;
|
|
13
|
+
//# sourceMappingURL=anthropic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/patches/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAiB,KAAK,YAAY,EAA2B,MAAM,UAAU,CAAA;AASpF,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,YAAY,EAAE,CA2C3F;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAcvE;AAMD,wBAAgB,KAAK,IAAI,OAAO,CAiG/B;AAED,wBAAgB,MAAM,IAAI,IAAI,CAa7B"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Anthropic SDK patch — wraps `Messages.prototype.create` from
|
|
4
|
+
* `@anthropic-ai/sdk`.
|
|
5
|
+
*
|
|
6
|
+
* Covers direct Anthropic usage and frameworks that use it (LangChain.js
|
|
7
|
+
* ChatAnthropic, etc.).
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.messagesToGuardFormat = messagesToGuardFormat;
|
|
11
|
+
exports.extractResponseContent = extractResponseContent;
|
|
12
|
+
exports.apply = apply;
|
|
13
|
+
exports.revert = revert;
|
|
14
|
+
const guard_1 = require("../guard");
|
|
15
|
+
let originalCreate = null;
|
|
16
|
+
let patched = false;
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Message conversion
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
function messagesToGuardFormat(messages, system) {
|
|
21
|
+
const result = [];
|
|
22
|
+
// Anthropic separates the system prompt from the messages array.
|
|
23
|
+
if (system) {
|
|
24
|
+
if (typeof system === "string") {
|
|
25
|
+
result.push({ role: "system", content: system });
|
|
26
|
+
}
|
|
27
|
+
else if (Array.isArray(system)) {
|
|
28
|
+
const parts = [];
|
|
29
|
+
for (const block of system) {
|
|
30
|
+
if (typeof block === "object" && block?.type === "text") {
|
|
31
|
+
parts.push(block.text ?? "");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (parts.length)
|
|
35
|
+
result.push({ role: "system", content: parts.join("\n") });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!messages)
|
|
39
|
+
return result;
|
|
40
|
+
for (const raw of messages) {
|
|
41
|
+
if (!raw)
|
|
42
|
+
continue;
|
|
43
|
+
const msg = raw;
|
|
44
|
+
const role = String(msg.role ?? "user");
|
|
45
|
+
let content;
|
|
46
|
+
if (typeof msg.content === "string") {
|
|
47
|
+
content = msg.content;
|
|
48
|
+
}
|
|
49
|
+
else if (Array.isArray(msg.content)) {
|
|
50
|
+
const textParts = [];
|
|
51
|
+
for (const block of msg.content) {
|
|
52
|
+
if (typeof block === "string")
|
|
53
|
+
textParts.push(block);
|
|
54
|
+
else if (block?.type === "text")
|
|
55
|
+
textParts.push(block.text ?? "");
|
|
56
|
+
}
|
|
57
|
+
content = textParts.join("\n");
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
content = String(msg.content ?? "");
|
|
61
|
+
}
|
|
62
|
+
result.push({ role, content });
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
function extractResponseContent(response) {
|
|
67
|
+
try {
|
|
68
|
+
const r = response;
|
|
69
|
+
if (r?.content && Array.isArray(r.content)) {
|
|
70
|
+
const parts = [];
|
|
71
|
+
for (const block of r.content) {
|
|
72
|
+
if (block?.type === "text")
|
|
73
|
+
parts.push(block.text ?? "");
|
|
74
|
+
}
|
|
75
|
+
return parts.length ? parts.join("\n") : null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// ignore
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Apply / revert
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
function apply() {
|
|
87
|
+
if (patched)
|
|
88
|
+
return true;
|
|
89
|
+
let Messages;
|
|
90
|
+
try {
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
92
|
+
const mod = require("@anthropic-ai/sdk/resources/messages");
|
|
93
|
+
Messages = mod.Messages;
|
|
94
|
+
if (!Messages?.prototype?.create)
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
originalCreate = Messages.prototype.create;
|
|
101
|
+
Messages.prototype.create = async function patchedCreate(...args) {
|
|
102
|
+
const { getGuardClient, getMode, isFailOpen, shouldScanResponses } = require("../auto");
|
|
103
|
+
const guard = getGuardClient();
|
|
104
|
+
if (!guard)
|
|
105
|
+
return originalCreate?.apply(this, args);
|
|
106
|
+
const params = (args[0] ?? {});
|
|
107
|
+
const messages = params.messages;
|
|
108
|
+
const system = params.system;
|
|
109
|
+
const model = params.model;
|
|
110
|
+
// -- Pre-call scan ---------------------------------------------------
|
|
111
|
+
if (messages) {
|
|
112
|
+
const guardMessages = messagesToGuardFormat(messages, system);
|
|
113
|
+
let decision = null;
|
|
114
|
+
try {
|
|
115
|
+
decision = await guard.scan(guardMessages, "input", model ? String(model) : undefined, {
|
|
116
|
+
framework: "anthropic",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
if (err instanceof guard_1.GuardApiError && !isFailOpen())
|
|
121
|
+
throw err;
|
|
122
|
+
}
|
|
123
|
+
if (decision?.blocked) {
|
|
124
|
+
if (getMode() === "enforce")
|
|
125
|
+
throw new guard_1.PromptGuardBlockedError(decision);
|
|
126
|
+
console.warn(`[promptguard][monitor] would block: ${decision.threatType} (event=${decision.eventId})`);
|
|
127
|
+
}
|
|
128
|
+
if (decision?.redacted && decision.redactedMessages) {
|
|
129
|
+
if (getMode() === "enforce") {
|
|
130
|
+
const redacted = decision.redactedMessages;
|
|
131
|
+
const hasSystem = system != null;
|
|
132
|
+
const offset = hasSystem ? 1 : 0;
|
|
133
|
+
const newParams = { ...params };
|
|
134
|
+
if (hasSystem && redacted[0]) {
|
|
135
|
+
newParams.system = redacted[0].content;
|
|
136
|
+
}
|
|
137
|
+
newParams.messages = messages.map((msg, i) => {
|
|
138
|
+
const idx = i + offset;
|
|
139
|
+
if (idx < redacted.length) {
|
|
140
|
+
const typedMsg = msg;
|
|
141
|
+
return { ...typedMsg, content: redacted[idx].content };
|
|
142
|
+
}
|
|
143
|
+
return msg;
|
|
144
|
+
});
|
|
145
|
+
args[0] = newParams;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// -- Original call ---------------------------------------------------
|
|
150
|
+
const response = await originalCreate?.apply(this, args);
|
|
151
|
+
// -- Post-call scan --------------------------------------------------
|
|
152
|
+
if (shouldScanResponses() && response && guard) {
|
|
153
|
+
try {
|
|
154
|
+
const text = extractResponseContent(response);
|
|
155
|
+
if (text) {
|
|
156
|
+
const respDecision = await guard.scan([{ role: "assistant", content: text }], "output", model ? String(model) : undefined);
|
|
157
|
+
if (respDecision.blocked && getMode() === "enforce") {
|
|
158
|
+
throw new guard_1.PromptGuardBlockedError(respDecision);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
if (err instanceof guard_1.PromptGuardBlockedError)
|
|
164
|
+
throw err;
|
|
165
|
+
if (err instanceof guard_1.GuardApiError && !isFailOpen())
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return response;
|
|
170
|
+
};
|
|
171
|
+
patched = true;
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
function revert() {
|
|
175
|
+
if (!patched || !originalCreate)
|
|
176
|
+
return;
|
|
177
|
+
try {
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
179
|
+
const mod = require("@anthropic-ai/sdk/resources/messages");
|
|
180
|
+
mod.Messages.prototype.create = originalCreate;
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// ignore
|
|
184
|
+
}
|
|
185
|
+
originalCreate = null;
|
|
186
|
+
patched = false;
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=anthropic.js.map
|