@complyedge/sdk 0.2.0 → 0.2.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.
- package/CHANGELOG.md +55 -0
- package/README.md +27 -0
- package/dist/chunk-X7L22AQP.mjs +88 -0
- package/dist/chunk-X7L22AQP.mjs.map +1 -0
- package/dist/index.js +50 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10 -3
- package/dist/index.mjs.map +1 -1
- package/dist/openai-middleware.d.mts +8 -0
- package/dist/openai-middleware.d.ts +8 -0
- package/dist/openai-middleware.js +41 -7
- package/dist/openai-middleware.js.map +1 -1
- package/dist/openai-middleware.mjs +1 -1
- package/package.json +20 -4
- package/dist/chunk-KZYIMMVM.mjs +0 -54
- package/dist/chunk-KZYIMMVM.mjs.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@complyedge/sdk` are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
6
|
+
this package follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
**This record begins at 0.2.2.** Earlier releases shipped before the changelog
|
|
9
|
+
existed; 0.2.1 is reconstructed from its commits, and anything not evidenced
|
|
10
|
+
there is left out rather than guessed at.
|
|
11
|
+
|
|
12
|
+
The Python SDK (`complyedge` on PyPI) is versioned independently. Version
|
|
13
|
+
numbers are deliberately not kept level between the two — they are separate
|
|
14
|
+
implementations with different capability surfaces, so matching numbers would
|
|
15
|
+
imply a parity that does not exist.
|
|
16
|
+
|
|
17
|
+
## [0.2.2] - 2026-08-20
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Releases now publish through npm Trusted Publishing (OIDC). Each publish uses
|
|
21
|
+
a short-lived, workflow-scoped credential instead of a long-lived token, and
|
|
22
|
+
the token has been removed from the release workflow rather than left in place
|
|
23
|
+
as a fallback.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- This changelog.
|
|
27
|
+
|
|
28
|
+
### Note
|
|
29
|
+
- Provenance attestations are still not generated. npm produces them only when
|
|
30
|
+
publishing from a public repository, and the release workflow lives in a
|
|
31
|
+
private one. No package page claims provenance.
|
|
32
|
+
|
|
33
|
+
## [0.2.1] - 2026-08-16
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- `repository` field, which the package had never carried.
|
|
37
|
+
- `Limitations and honest scope` section in the README — the SDK calls the
|
|
38
|
+
hosted API and is not an offline engine, a rule finding is technical evidence
|
|
39
|
+
rather than legal advice, and the OpenAI middleware is not a security sandbox.
|
|
40
|
+
- Explicit note that the package works in both ESM and CommonJS projects.
|
|
41
|
+
- Cross-links to `@complyedge/mcp`, `trustlint`, and the PyPI packages.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
- Description rewritten to name what the SDK actually does: OPA/Rego policy
|
|
45
|
+
checks, Article 12 audit trails, risk assessment, OpenAI middleware.
|
|
46
|
+
- Keywords: 5 → 16, replacing generic terms with the ones a compliance buyer
|
|
47
|
+
searches. `opa` and `rego` are kept here because this package does call the
|
|
48
|
+
OPA hot path — they were removed from `trustlint`, which does not.
|
|
49
|
+
|
|
50
|
+
## 0.2.0 and earlier
|
|
51
|
+
|
|
52
|
+
Released before this changelog existed. See the repository history.
|
|
53
|
+
|
|
54
|
+
[0.2.2]: https://www.npmjs.com/package/@complyedge/sdk/v/0.2.2
|
|
55
|
+
[0.2.1]: https://www.npmjs.com/package/@complyedge/sdk/v/0.2.1
|
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ TypeScript/JavaScript SDK for [ComplyEdge](https://www.complyedge.io): runtime E
|
|
|
4
4
|
|
|
5
5
|
Every `check()` call is evaluated against a deterministic Rego rule bundle, returns article-cited violations, and is written to an Article 12 audit trail.
|
|
6
6
|
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
7
11
|
## Install
|
|
8
12
|
|
|
9
13
|
```bash
|
|
@@ -12,6 +16,19 @@ npm install @complyedge/sdk
|
|
|
12
16
|
|
|
13
17
|
Requires Node.js 18 or later. Get an API key at [dashboard.complyedge.io](https://dashboard.complyedge.io/?intent=get-started).
|
|
14
18
|
|
|
19
|
+
The SDK works in both ESM and CommonJS projects; no bundler-specific setup is required.
|
|
20
|
+
|
|
21
|
+
## Package map
|
|
22
|
+
|
|
23
|
+
- [`@complyedge/mcp`](https://www.npmjs.com/package/@complyedge/mcp) runs the local, offline TrustLint MCP tools with `npx`.
|
|
24
|
+
- [`trustlint`](https://www.npmjs.com/package/trustlint) is the local Node.js CLI for offline rule checks.
|
|
25
|
+
- [`complyedge` on PyPI](https://pypi.org/project/complyedge/) is the Python SDK with the same enforcement API.
|
|
26
|
+
- [Documentation](https://www.complyedge.io/docs) covers the hosted API; the [trust portal](https://trust.complyedge.io) covers security and reliability information.
|
|
27
|
+
|
|
28
|
+
## Version policy
|
|
29
|
+
|
|
30
|
+
The TypeScript SDK and Python `complyedge` package have independent semantic versions. A version number does not imply feature parity across languages; each release documents and tests its own supported API surface.
|
|
31
|
+
|
|
15
32
|
## Quick start
|
|
16
33
|
|
|
17
34
|
```typescript
|
|
@@ -126,6 +143,16 @@ the Article 12 audit trail. Use `check()` for runtime enforcement.
|
|
|
126
143
|
Network and HTTP failures surface as `AxiosError`. The middleware throws
|
|
127
144
|
`ComplianceError` when a check blocks a request.
|
|
128
145
|
|
|
146
|
+
## Limitations and honest scope
|
|
147
|
+
|
|
148
|
+
- This SDK calls the hosted ComplyEdge API; it is not an offline rules engine.
|
|
149
|
+
Use `trustlint` or `@complyedge/mcp` when a local-only check is required.
|
|
150
|
+
- A rule finding is technical compliance evidence, not legal advice or a legal
|
|
151
|
+
determination of an AI system's full regulatory classification.
|
|
152
|
+
- The OpenAI middleware checks the request path shown above. It is not a
|
|
153
|
+
general-purpose security sandbox and does not automatically govern other
|
|
154
|
+
model providers or application code.
|
|
155
|
+
|
|
129
156
|
## Links
|
|
130
157
|
|
|
131
158
|
- Quick start: https://www.complyedge.io/docs/quick-start.html
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// src/openai-middleware.ts
|
|
2
|
+
var ComplianceError = class extends Error {
|
|
3
|
+
violations;
|
|
4
|
+
constructor(message, violations) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "ComplianceError";
|
|
7
|
+
this.violations = violations;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
function withCompliance(openaiClient, ceClient, options) {
|
|
11
|
+
const opts = {
|
|
12
|
+
checkInput: true,
|
|
13
|
+
checkOutput: false,
|
|
14
|
+
blockOnViolation: true,
|
|
15
|
+
...options
|
|
16
|
+
};
|
|
17
|
+
const chat = openaiClient.chat;
|
|
18
|
+
if (!chat || !chat.completions) {
|
|
19
|
+
return openaiClient;
|
|
20
|
+
}
|
|
21
|
+
const completions = chat.completions;
|
|
22
|
+
const originalCreate = completions.create;
|
|
23
|
+
if (typeof originalCreate !== "function") {
|
|
24
|
+
return openaiClient;
|
|
25
|
+
}
|
|
26
|
+
completions.create = async function(...args) {
|
|
27
|
+
const params = args[0];
|
|
28
|
+
if (opts.checkInput && params?.messages) {
|
|
29
|
+
const messages = params.messages;
|
|
30
|
+
for (const message of messages) {
|
|
31
|
+
if (message.role !== "user") continue;
|
|
32
|
+
for (const text of extractText(message.content)) {
|
|
33
|
+
const result = await ceClient.check(text, {
|
|
34
|
+
direction: "prompt",
|
|
35
|
+
jurisdiction: opts.jurisdiction
|
|
36
|
+
});
|
|
37
|
+
if (!result.allowed && opts.blockOnViolation) {
|
|
38
|
+
throw new ComplianceError(
|
|
39
|
+
`Compliance violation detected: ${result.violations.map((v) => v.ruleId).join(", ")}`,
|
|
40
|
+
result.violations
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const response = await originalCreate.apply(this, args);
|
|
47
|
+
if (opts.checkOutput) {
|
|
48
|
+
for (const text of extractCompletionText(response)) {
|
|
49
|
+
const result = await ceClient.check(text, {
|
|
50
|
+
direction: "output",
|
|
51
|
+
jurisdiction: opts.jurisdiction
|
|
52
|
+
});
|
|
53
|
+
if (!result.allowed && opts.blockOnViolation) {
|
|
54
|
+
throw new ComplianceError(
|
|
55
|
+
`Compliance violation in model output: ${result.violations.map((v) => v.ruleId).join(", ")}`,
|
|
56
|
+
result.violations
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return response;
|
|
62
|
+
};
|
|
63
|
+
return openaiClient;
|
|
64
|
+
}
|
|
65
|
+
function extractText(content) {
|
|
66
|
+
if (typeof content === "string") {
|
|
67
|
+
return content.trim() ? [content] : [];
|
|
68
|
+
}
|
|
69
|
+
if (Array.isArray(content)) {
|
|
70
|
+
return content.map((part) => {
|
|
71
|
+
if (typeof part === "string") return part;
|
|
72
|
+
const p = part;
|
|
73
|
+
return p && typeof p.text === "string" ? p.text : "";
|
|
74
|
+
}).filter((t) => t.trim().length > 0);
|
|
75
|
+
}
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
function extractCompletionText(response) {
|
|
79
|
+
const r = response;
|
|
80
|
+
if (!r?.choices) return [];
|
|
81
|
+
return r.choices.flatMap((choice) => extractText(choice?.message?.content));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
ComplianceError,
|
|
86
|
+
withCompliance
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=chunk-X7L22AQP.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/openai-middleware.ts"],"sourcesContent":["/**\n * ComplyEdge OpenAI Middleware\n *\n * Wraps an OpenAI client to run compliance checks on messages\n * before they are sent to the model.\n *\n * Usage:\n * import OpenAI from \"openai\";\n * import { ComplyEdgeClient } from \"@complyedge/sdk\";\n * import { withCompliance } from \"@complyedge/sdk/openai-middleware\";\n *\n * const ce = new ComplyEdgeClient({ apiKey: \"...\" });\n * const openai = withCompliance(new OpenAI(), ce);\n * // Now openai.chat.completions.create() runs compliance checks automatically\n */\n\nimport type { ComplyEdgeClient } from \"./client\";\nimport type { ComplianceViolation } from \"./types\";\n\nexport class ComplianceError extends Error {\n public violations: ComplianceViolation[];\n\n constructor(message: string, violations: ComplianceViolation[]) {\n super(message);\n this.name = \"ComplianceError\";\n this.violations = violations;\n }\n}\n\n/**\n * Wrap an OpenAI client with ComplyEdge compliance checks.\n * The returned object proxies chat.completions.create() to check\n * user messages before sending to OpenAI.\n */\nexport function withCompliance<T extends Record<string, unknown>>(\n openaiClient: T,\n ceClient: ComplyEdgeClient,\n options?: {\n checkInput?: boolean;\n checkOutput?: boolean;\n jurisdiction?: string;\n blockOnViolation?: boolean;\n }\n): T {\n const opts = {\n checkInput: true,\n checkOutput: false,\n blockOnViolation: true,\n ...options,\n };\n\n const chat = openaiClient.chat as Record<string, unknown> | undefined;\n if (!chat || !chat.completions) {\n return openaiClient; // Not an OpenAI-shaped client, return as-is\n }\n\n const completions = chat.completions as Record<string, unknown>;\n const originalCreate = completions.create as (...args: unknown[]) => Promise<unknown>;\n\n if (typeof originalCreate !== \"function\") {\n return openaiClient;\n }\n\n completions.create = async function (...args: unknown[]) {\n const params = args[0] as Record<string, unknown> | undefined;\n\n if (opts.checkInput && params?.messages) {\n const messages = params.messages as Array<{ role: string; content: unknown }>;\n // Check EVERY user turn, not just the last one. A violation in an earlier\n // message is still sent to the model verbatim, so checking only the tail\n // left the majority of a multi-turn conversation unenforced.\n for (const message of messages) {\n if (message.role !== \"user\") continue;\n for (const text of extractText(message.content)) {\n const result = await ceClient.check(text, {\n direction: \"prompt\",\n jurisdiction: opts.jurisdiction,\n });\n if (!result.allowed && opts.blockOnViolation) {\n throw new ComplianceError(\n `Compliance violation detected: ${result.violations.map((v) => v.ruleId).join(\", \")}`,\n result.violations\n );\n }\n }\n }\n }\n\n const response = await originalCreate.apply(this, args);\n\n if (opts.checkOutput) {\n // Previously declared in the options type and never read, so the README's\n // \"runs compliance checks automatically\" was half true: inputs were\n // checked, model output never was, silently.\n for (const text of extractCompletionText(response)) {\n const result = await ceClient.check(text, {\n direction: \"output\",\n jurisdiction: opts.jurisdiction,\n });\n if (!result.allowed && opts.blockOnViolation) {\n throw new ComplianceError(\n `Compliance violation in model output: ${result.violations.map((v) => v.ruleId).join(\", \")}`,\n result.violations\n );\n }\n }\n }\n\n return response;\n };\n\n return openaiClient;\n}\n\n/**\n * Pull checkable strings out of a message `content` field.\n *\n * OpenAI accepts either a plain string or an array of typed content parts.\n * The array form was previously passed to the API as a non-string and 422'd,\n * so multimodal callers could not use the middleware at all.\n */\nfunction extractText(content: unknown): string[] {\n if (typeof content === \"string\") {\n return content.trim() ? [content] : [];\n }\n if (Array.isArray(content)) {\n return content\n .map((part) => {\n if (typeof part === \"string\") return part;\n const p = part as Record<string, unknown> | null;\n return p && typeof p.text === \"string\" ? p.text : \"\";\n })\n .filter((t) => t.trim().length > 0);\n }\n return [];\n}\n\n/** Pull assistant message text out of a chat completion response. */\nfunction extractCompletionText(response: unknown): string[] {\n const r = response as { choices?: Array<{ message?: { content?: unknown } }> } | null;\n if (!r?.choices) return [];\n return r.choices.flatMap((choice) => extractText(choice?.message?.content));\n}\n"],"mappings":";AAmBO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAClC;AAAA,EAEP,YAAY,SAAiB,YAAmC;AAC9D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAOO,SAAS,eACd,cACA,UACA,SAMG;AACH,QAAM,OAAO;AAAA,IACX,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,GAAG;AAAA,EACL;AAEA,QAAM,OAAO,aAAa;AAC1B,MAAI,CAAC,QAAQ,CAAC,KAAK,aAAa;AAC9B,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,KAAK;AACzB,QAAM,iBAAiB,YAAY;AAEnC,MAAI,OAAO,mBAAmB,YAAY;AACxC,WAAO;AAAA,EACT;AAEA,cAAY,SAAS,kBAAmB,MAAiB;AACvD,UAAM,SAAS,KAAK,CAAC;AAErB,QAAI,KAAK,cAAc,QAAQ,UAAU;AACvC,YAAM,WAAW,OAAO;AAIxB,iBAAW,WAAW,UAAU;AAC9B,YAAI,QAAQ,SAAS,OAAQ;AAC7B,mBAAW,QAAQ,YAAY,QAAQ,OAAO,GAAG;AAC/C,gBAAM,SAAS,MAAM,SAAS,MAAM,MAAM;AAAA,YACxC,WAAW;AAAA,YACX,cAAc,KAAK;AAAA,UACrB,CAAC;AACD,cAAI,CAAC,OAAO,WAAW,KAAK,kBAAkB;AAC5C,kBAAM,IAAI;AAAA,cACR,kCAAkC,OAAO,WAAW,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAAA,cACnF,OAAO;AAAA,YACT;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,eAAe,MAAM,MAAM,IAAI;AAEtD,QAAI,KAAK,aAAa;AAIpB,iBAAW,QAAQ,sBAAsB,QAAQ,GAAG;AAClD,cAAM,SAAS,MAAM,SAAS,MAAM,MAAM;AAAA,UACxC,WAAW;AAAA,UACX,cAAc,KAAK;AAAA,QACrB,CAAC;AACD,YAAI,CAAC,OAAO,WAAW,KAAK,kBAAkB;AAC5C,gBAAM,IAAI;AAAA,YACR,yCAAyC,OAAO,WAAW,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAAA,YAC1F,OAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AASA,SAAS,YAAY,SAA4B;AAC/C,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,QAAQ,KAAK,IAAI,CAAC,OAAO,IAAI,CAAC;AAAA,EACvC;AACA,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,WAAO,QACJ,IAAI,CAAC,SAAS;AACb,UAAI,OAAO,SAAS,SAAU,QAAO;AACrC,YAAM,IAAI;AACV,aAAO,KAAK,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AAAA,IACpD,CAAC,EACA,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC;AAAA,EACtC;AACA,SAAO,CAAC;AACV;AAGA,SAAS,sBAAsB,UAA6B;AAC1D,QAAM,IAAI;AACV,MAAI,CAAC,GAAG,QAAS,QAAO,CAAC;AACzB,SAAO,EAAE,QAAQ,QAAQ,CAAC,WAAW,YAAY,QAAQ,SAAS,OAAO,CAAC;AAC5E;","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -72,7 +72,12 @@ var ComplyEdgeClient = class {
|
|
|
72
72
|
text,
|
|
73
73
|
agent_id: context?.agentId || this.agentId,
|
|
74
74
|
jurisdiction,
|
|
75
|
-
|
|
75
|
+
// Pass the caller's direction through untouched. This previously mapped
|
|
76
|
+
// "prompt" to "input", a value the API's DirectionType enum does not
|
|
77
|
+
// accept ("prompt" | "output"), so every input check 422'd and the
|
|
78
|
+
// wrapped call threw. The SDK's own ComplianceContext type has always
|
|
79
|
+
// declared the correct union; only this line disagreed with it.
|
|
80
|
+
direction: context?.direction ?? "output",
|
|
76
81
|
use_semantic_fallback: false,
|
|
77
82
|
context: context?.userRole ? { user_role: context.userRole } : void 0
|
|
78
83
|
});
|
|
@@ -97,6 +102,8 @@ var ComplyEdgeClient = class {
|
|
|
97
102
|
enginePath: data.engine_path || "opa",
|
|
98
103
|
opaLatencyMs: data.opa_latency_ms,
|
|
99
104
|
auditLogged: data.audit_logged !== false,
|
|
105
|
+
textHash: data.text_hash || "",
|
|
106
|
+
timestamp: data.timestamp,
|
|
100
107
|
jurisdiction,
|
|
101
108
|
processingTimeMs
|
|
102
109
|
};
|
|
@@ -110,7 +117,7 @@ var ComplyEdgeClient = class {
|
|
|
110
117
|
*/
|
|
111
118
|
async detectSensitivity(text, context) {
|
|
112
119
|
const start = Date.now();
|
|
113
|
-
const jurisdiction = context?.jurisdiction || this.jurisdiction || "
|
|
120
|
+
const jurisdiction = context?.jurisdiction || this.jurisdiction || "EU";
|
|
114
121
|
const response = await this.http.post("/v1/sensitivity/detect", {
|
|
115
122
|
input_text: text,
|
|
116
123
|
agent_id: context?.agentId || this.agentId,
|
|
@@ -200,25 +207,59 @@ function withCompliance(openaiClient, ceClient, options) {
|
|
|
200
207
|
const params = args[0];
|
|
201
208
|
if (opts.checkInput && params?.messages) {
|
|
202
209
|
const messages = params.messages;
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
210
|
+
for (const message of messages) {
|
|
211
|
+
if (message.role !== "user") continue;
|
|
212
|
+
for (const text of extractText(message.content)) {
|
|
213
|
+
const result = await ceClient.check(text, {
|
|
214
|
+
direction: "prompt",
|
|
215
|
+
jurisdiction: opts.jurisdiction
|
|
216
|
+
});
|
|
217
|
+
if (!result.allowed && opts.blockOnViolation) {
|
|
218
|
+
throw new ComplianceError(
|
|
219
|
+
`Compliance violation detected: ${result.violations.map((v) => v.ruleId).join(", ")}`,
|
|
220
|
+
result.violations
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const response = await originalCreate.apply(this, args);
|
|
227
|
+
if (opts.checkOutput) {
|
|
228
|
+
for (const text of extractCompletionText(response)) {
|
|
229
|
+
const result = await ceClient.check(text, {
|
|
230
|
+
direction: "output",
|
|
208
231
|
jurisdiction: opts.jurisdiction
|
|
209
232
|
});
|
|
210
233
|
if (!result.allowed && opts.blockOnViolation) {
|
|
211
234
|
throw new ComplianceError(
|
|
212
|
-
`Compliance violation
|
|
235
|
+
`Compliance violation in model output: ${result.violations.map((v) => v.ruleId).join(", ")}`,
|
|
213
236
|
result.violations
|
|
214
237
|
);
|
|
215
238
|
}
|
|
216
239
|
}
|
|
217
240
|
}
|
|
218
|
-
return
|
|
241
|
+
return response;
|
|
219
242
|
};
|
|
220
243
|
return openaiClient;
|
|
221
244
|
}
|
|
245
|
+
function extractText(content) {
|
|
246
|
+
if (typeof content === "string") {
|
|
247
|
+
return content.trim() ? [content] : [];
|
|
248
|
+
}
|
|
249
|
+
if (Array.isArray(content)) {
|
|
250
|
+
return content.map((part) => {
|
|
251
|
+
if (typeof part === "string") return part;
|
|
252
|
+
const p = part;
|
|
253
|
+
return p && typeof p.text === "string" ? p.text : "";
|
|
254
|
+
}).filter((t) => t.trim().length > 0);
|
|
255
|
+
}
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
function extractCompletionText(response) {
|
|
259
|
+
const r = response;
|
|
260
|
+
if (!r?.choices) return [];
|
|
261
|
+
return r.choices.flatMap((choice) => extractText(choice?.message?.content));
|
|
262
|
+
}
|
|
222
263
|
// Annotate the CommonJS export names for ESM import in node:
|
|
223
264
|
0 && (module.exports = {
|
|
224
265
|
ComplianceError,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/openai-middleware.ts"],"sourcesContent":["/**\n * ComplyEdge TypeScript SDK\n *\n * Runtime EU AI Act enforcement for AI agents.\n *\n * @example\n * ```typescript\n * import { ComplyEdgeClient } from \"@complyedge/sdk\";\n *\n * const ce = new ComplyEdgeClient({ apiKey: process.env.COMPLYEDGE_API_KEY! });\n * const result = await ce.check(\"Your AI prompt here\");\n *\n * if (result.status === \"violation\") {\n * console.log(\"Blocked:\", result.violations);\n * }\n * ```\n */\n\nexport { ComplyEdgeClient } from \"./client\";\nexport { withCompliance, ComplianceError } from \"./openai-middleware\";\nexport type {\n ComplyEdgeConfig,\n ComplianceContext,\n ComplianceResult,\n ComplianceViolation,\n SeverityLevel,\n SensitivityResult,\n SensitivityDetection,\n PreDeploymentInput,\n PreDeploymentResult,\n} from \"./types\";\n","/**\n * ComplyEdge TypeScript SDK — Client\n */\n\nimport axios, { AxiosInstance } from \"axios\";\nimport type {\n ComplyEdgeConfig,\n ComplianceContext,\n ComplianceResult,\n ComplianceViolation,\n SensitivityDetection,\n SensitivityResult,\n PreDeploymentInput,\n PreDeploymentResult,\n} from \"./types\";\n\nconst DEFAULT_BASE_URL = \"https://api.complyedge.io\";\nconst SDK_VERSION = \"0.2.0\";\n\nexport class ComplyEdgeClient {\n private http: AxiosInstance;\n private agentId: string;\n private jurisdiction?: string;\n\n constructor(config: ComplyEdgeConfig) {\n const baseUrl = config.baseUrl || process.env.COMPLYEDGE_API_URL || DEFAULT_BASE_URL;\n\n this.agentId = config.agentId || \"default\";\n this.jurisdiction = config.jurisdiction;\n\n this.http = axios.create({\n baseURL: baseUrl.replace(/\\/+$/, \"\"),\n timeout: config.timeout || 30_000,\n headers: {\n Authorization: `Bearer ${config.apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": `complyedge-typescript-sdk/${SDK_VERSION}`,\n },\n });\n }\n\n /**\n * Run a compliance check on text input.\n *\n * Calls POST /v1/check: the deterministic OPA hot path. A decision here is\n * evaluated against the Rego rule bundle, returns article-cited violations,\n * and is written to the Article 12 audit trail (`auditLogged`).\n */\n async check(text: string, context?: ComplianceContext): Promise<ComplianceResult> {\n const start = Date.now();\n const jurisdiction = context?.jurisdiction || this.jurisdiction || \"EU\";\n\n const response = await this.http.post(\"/v1/check\", {\n text,\n agent_id: context?.agentId || this.agentId,\n jurisdiction,\n direction: context?.direction === \"prompt\" ? \"input\" : \"output\",\n use_semantic_fallback: false,\n context: context?.userRole ? { user_role: context.userRole } : undefined,\n });\n\n const data = response.data;\n const processingTimeMs = Date.now() - start;\n const allowed = data.allowed !== false;\n\n return {\n eventId: data.event_id || \"\",\n allowed,\n status: allowed ? \"safe\" : \"violation\",\n violations: (data.violations || []).map((v: Record<string, unknown>) => ({\n ruleId: (v.rule_id as string) || \"\",\n ruleDescription: (v.rule_description as string) || \"\",\n severity: (v.severity as ComplianceViolation[\"severity\"]) || \"medium\",\n reason: (v.reason as string) || \"\",\n confidence: (v.confidence as number) ?? 1.0,\n textExcerpt: v.text_excerpt as string | undefined,\n })),\n latencyMs: data.latency_ms || 0,\n bundleVersion: data.bundle_version || \"\",\n evaluatedRules: data.evaluated_rules || [],\n enginePath: data.engine_path || \"opa\",\n opaLatencyMs: data.opa_latency_ms,\n auditLogged: data.audit_logged !== false,\n jurisdiction,\n processingTimeMs,\n };\n }\n\n /**\n * Run proactive sensitivity detection on user input.\n *\n * Calls POST /v1/sensitivity/detect, which deliberately stays on the legacy\n * TrustLint + LLM pipeline. It does NOT run OPA and does NOT write the\n * Article 12 audit trail. Use `check()` for runtime EU AI Act enforcement.\n */\n async detectSensitivity(\n text: string,\n context?: ComplianceContext\n ): Promise<SensitivityResult> {\n const start = Date.now();\n const jurisdiction = context?.jurisdiction || this.jurisdiction || \"US\";\n\n const response = await this.http.post(\"/v1/sensitivity/detect\", {\n input_text: text,\n agent_id: context?.agentId || this.agentId,\n jurisdiction,\n direction: context?.direction || \"prompt\",\n user_role: context?.userRole,\n });\n\n const data = response.data;\n\n return {\n eventId: data.event_id || \"\",\n status: data.overall_risk_assessment === \"safe\" ? \"safe\" : \"violation\",\n detections: (data.detections || []).map((d: Record<string, unknown>) => ({\n ruleId: (d.rule_id as string) || \"\",\n severity: (d.severity as SensitivityDetection[\"severity\"]) || \"medium\",\n regulation: (d.regulation as string) || \"\",\n description: (d.description as string) || \"\",\n article: d.article as string | undefined,\n remediation: d.remediation as string | undefined,\n })),\n riskScore: data.overall_risk_score || 0,\n jurisdiction,\n processingTimeMs: Date.now() - start,\n };\n }\n\n /**\n * Run a pre-deployment assessment on an AI system configuration.\n */\n async assessPreDeployment(input: PreDeploymentInput): Promise<PreDeploymentResult> {\n const response = await this.http.post(\"/v1/assessment/pre-deployment\", {\n system_prompt: input.systemPrompt,\n model_config: input.modelConfig\n ? {\n provider: input.modelConfig.provider,\n model_id: input.modelConfig.modelId,\n temperature: input.modelConfig.temperature,\n }\n : undefined,\n agent_pipeline: input.agentPipeline\n ? {\n tools: input.agentPipeline.tools,\n memory: input.agentPipeline.memory,\n autonomy_level: input.agentPipeline.autonomyLevel,\n human_oversight: input.agentPipeline.humanOversight,\n }\n : undefined,\n jurisdiction: input.jurisdiction || \"EU\",\n });\n\n const data = response.data;\n return {\n complianceScore: data.compliance_score,\n riskTier: data.risk_tier,\n violations: (data.violations || []).map((v: Record<string, unknown>) => ({\n ruleId: v.rule_id as string,\n article: v.article as string,\n description: v.description as string,\n requiredAction: v.required_action as string,\n })),\n requiredDisclosures: data.required_disclosures || [],\n euAiActCategory: data.eu_ai_act_category || \"\",\n estimatedDeadline: data.estimated_deadline || \"\",\n };\n }\n}\n","/**\n * ComplyEdge OpenAI Middleware\n *\n * Wraps an OpenAI client to run compliance checks on messages\n * before they are sent to the model.\n *\n * Usage:\n * import OpenAI from \"openai\";\n * import { ComplyEdgeClient } from \"@complyedge/sdk\";\n * import { withCompliance } from \"@complyedge/sdk/openai-middleware\";\n *\n * const ce = new ComplyEdgeClient({ apiKey: \"...\" });\n * const openai = withCompliance(new OpenAI(), ce);\n * // Now openai.chat.completions.create() runs compliance checks automatically\n */\n\nimport type { ComplyEdgeClient } from \"./client\";\nimport type { ComplianceViolation } from \"./types\";\n\nexport class ComplianceError extends Error {\n public violations: ComplianceViolation[];\n\n constructor(message: string, violations: ComplianceViolation[]) {\n super(message);\n this.name = \"ComplianceError\";\n this.violations = violations;\n }\n}\n\n/**\n * Wrap an OpenAI client with ComplyEdge compliance checks.\n * The returned object proxies chat.completions.create() to check\n * user messages before sending to OpenAI.\n */\nexport function withCompliance<T extends Record<string, unknown>>(\n openaiClient: T,\n ceClient: ComplyEdgeClient,\n options?: {\n checkInput?: boolean;\n checkOutput?: boolean;\n jurisdiction?: string;\n blockOnViolation?: boolean;\n }\n): T {\n const opts = {\n checkInput: true,\n checkOutput: false,\n blockOnViolation: true,\n ...options,\n };\n\n const chat = openaiClient.chat as Record<string, unknown> | undefined;\n if (!chat || !chat.completions) {\n return openaiClient; // Not an OpenAI-shaped client, return as-is\n }\n\n const completions = chat.completions as Record<string, unknown>;\n const originalCreate = completions.create as (...args: unknown[]) => Promise<unknown>;\n\n if (typeof originalCreate !== \"function\") {\n return openaiClient;\n }\n\n completions.create = async function (...args: unknown[]) {\n const params = args[0] as Record<string, unknown> | undefined;\n\n if (opts.checkInput && params?.messages) {\n const messages = params.messages as Array<{ role: string; content: string }>;\n const userMessages = messages.filter((m) => m.role === \"user\");\n const lastUserMessage = userMessages[userMessages.length - 1];\n\n if (lastUserMessage?.content) {\n const result = await ceClient.check(lastUserMessage.content, {\n direction: \"prompt\",\n jurisdiction: opts.jurisdiction,\n });\n\n if (!result.allowed && opts.blockOnViolation) {\n throw new ComplianceError(\n `Compliance violation detected: ${result.violations.map((v) => v.ruleId).join(\", \")}`,\n result.violations\n );\n }\n }\n }\n\n return originalCreate.apply(this, args);\n };\n\n return openaiClient;\n}\n"],"mappings":"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","names":["axios"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/openai-middleware.ts"],"sourcesContent":["/**\n * ComplyEdge TypeScript SDK\n *\n * Runtime EU AI Act enforcement for AI agents.\n *\n * @example\n * ```typescript\n * import { ComplyEdgeClient } from \"@complyedge/sdk\";\n *\n * const ce = new ComplyEdgeClient({ apiKey: process.env.COMPLYEDGE_API_KEY! });\n * const result = await ce.check(\"Your AI prompt here\");\n *\n * if (result.status === \"violation\") {\n * console.log(\"Blocked:\", result.violations);\n * }\n * ```\n */\n\nexport { ComplyEdgeClient } from \"./client\";\nexport { withCompliance, ComplianceError } from \"./openai-middleware\";\nexport type {\n ComplyEdgeConfig,\n ComplianceContext,\n ComplianceResult,\n ComplianceViolation,\n SeverityLevel,\n SensitivityResult,\n SensitivityDetection,\n PreDeploymentInput,\n PreDeploymentResult,\n} from \"./types\";\n","/**\n * ComplyEdge TypeScript SDK — Client\n */\n\nimport axios, { AxiosInstance } from \"axios\";\nimport type {\n ComplyEdgeConfig,\n ComplianceContext,\n ComplianceResult,\n ComplianceViolation,\n SensitivityDetection,\n SensitivityResult,\n PreDeploymentInput,\n PreDeploymentResult,\n} from \"./types\";\n\nconst DEFAULT_BASE_URL = \"https://api.complyedge.io\";\n// Keep in sync with package.json. Hardcoding it here meant the User-Agent\n// silently reported a stale version after every release bump, which is the\n// one field support uses to tell which client a customer is actually on.\nconst SDK_VERSION = \"0.2.0\";\n\nexport class ComplyEdgeClient {\n private http: AxiosInstance;\n private agentId: string;\n private jurisdiction?: string;\n\n constructor(config: ComplyEdgeConfig) {\n const baseUrl = config.baseUrl || process.env.COMPLYEDGE_API_URL || DEFAULT_BASE_URL;\n\n this.agentId = config.agentId || \"default\";\n this.jurisdiction = config.jurisdiction;\n\n this.http = axios.create({\n baseURL: baseUrl.replace(/\\/+$/, \"\"),\n timeout: config.timeout || 30_000,\n headers: {\n Authorization: `Bearer ${config.apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": `complyedge-typescript-sdk/${SDK_VERSION}`,\n },\n });\n }\n\n /**\n * Run a compliance check on text input.\n *\n * Calls POST /v1/check: the deterministic OPA hot path. A decision here is\n * evaluated against the Rego rule bundle, returns article-cited violations,\n * and is written to the Article 12 audit trail (`auditLogged`).\n */\n async check(text: string, context?: ComplianceContext): Promise<ComplianceResult> {\n const start = Date.now();\n const jurisdiction = context?.jurisdiction || this.jurisdiction || \"EU\";\n\n const response = await this.http.post(\"/v1/check\", {\n text,\n agent_id: context?.agentId || this.agentId,\n jurisdiction,\n // Pass the caller's direction through untouched. This previously mapped\n // \"prompt\" to \"input\", a value the API's DirectionType enum does not\n // accept (\"prompt\" | \"output\"), so every input check 422'd and the\n // wrapped call threw. The SDK's own ComplianceContext type has always\n // declared the correct union; only this line disagreed with it.\n direction: context?.direction ?? \"output\",\n use_semantic_fallback: false,\n context: context?.userRole ? { user_role: context.userRole } : undefined,\n });\n\n const data = response.data;\n const processingTimeMs = Date.now() - start;\n const allowed = data.allowed !== false;\n\n return {\n eventId: data.event_id || \"\",\n allowed,\n status: allowed ? \"safe\" : \"violation\",\n violations: (data.violations || []).map((v: Record<string, unknown>) => ({\n ruleId: (v.rule_id as string) || \"\",\n ruleDescription: (v.rule_description as string) || \"\",\n severity: (v.severity as ComplianceViolation[\"severity\"]) || \"medium\",\n reason: (v.reason as string) || \"\",\n confidence: (v.confidence as number) ?? 1.0,\n textExcerpt: v.text_excerpt as string | undefined,\n })),\n latencyMs: data.latency_ms || 0,\n bundleVersion: data.bundle_version || \"\",\n evaluatedRules: data.evaluated_rules || [],\n enginePath: data.engine_path || \"opa\",\n opaLatencyMs: data.opa_latency_ms,\n auditLogged: data.audit_logged !== false,\n textHash: data.text_hash || \"\",\n timestamp: data.timestamp,\n jurisdiction,\n processingTimeMs,\n };\n }\n\n /**\n * Run proactive sensitivity detection on user input.\n *\n * Calls POST /v1/sensitivity/detect, which deliberately stays on the legacy\n * TrustLint + LLM pipeline. It does NOT run OPA and does NOT write the\n * Article 12 audit trail. Use `check()` for runtime EU AI Act enforcement.\n */\n async detectSensitivity(\n text: string,\n context?: ComplianceContext\n ): Promise<SensitivityResult> {\n const start = Date.now();\n // \"EU\" matches the Python SDK and the server default. This read \"US\",\n // so the two SDKs enforced different rule sets for identical code.\n const jurisdiction = context?.jurisdiction || this.jurisdiction || \"EU\";\n\n const response = await this.http.post(\"/v1/sensitivity/detect\", {\n input_text: text,\n agent_id: context?.agentId || this.agentId,\n jurisdiction,\n direction: context?.direction || \"prompt\",\n user_role: context?.userRole,\n });\n\n const data = response.data;\n\n return {\n eventId: data.event_id || \"\",\n status: data.overall_risk_assessment === \"safe\" ? \"safe\" : \"violation\",\n detections: (data.detections || []).map((d: Record<string, unknown>) => ({\n ruleId: (d.rule_id as string) || \"\",\n severity: (d.severity as SensitivityDetection[\"severity\"]) || \"medium\",\n regulation: (d.regulation as string) || \"\",\n description: (d.description as string) || \"\",\n article: d.article as string | undefined,\n remediation: d.remediation as string | undefined,\n })),\n riskScore: data.overall_risk_score || 0,\n jurisdiction,\n processingTimeMs: Date.now() - start,\n };\n }\n\n /**\n * Run a pre-deployment assessment on an AI system configuration.\n */\n async assessPreDeployment(input: PreDeploymentInput): Promise<PreDeploymentResult> {\n const response = await this.http.post(\"/v1/assessment/pre-deployment\", {\n system_prompt: input.systemPrompt,\n model_config: input.modelConfig\n ? {\n provider: input.modelConfig.provider,\n model_id: input.modelConfig.modelId,\n temperature: input.modelConfig.temperature,\n }\n : undefined,\n agent_pipeline: input.agentPipeline\n ? {\n tools: input.agentPipeline.tools,\n memory: input.agentPipeline.memory,\n autonomy_level: input.agentPipeline.autonomyLevel,\n human_oversight: input.agentPipeline.humanOversight,\n }\n : undefined,\n jurisdiction: input.jurisdiction || \"EU\",\n });\n\n const data = response.data;\n return {\n complianceScore: data.compliance_score,\n riskTier: data.risk_tier,\n violations: (data.violations || []).map((v: Record<string, unknown>) => ({\n ruleId: v.rule_id as string,\n article: v.article as string,\n description: v.description as string,\n requiredAction: v.required_action as string,\n })),\n requiredDisclosures: data.required_disclosures || [],\n euAiActCategory: data.eu_ai_act_category || \"\",\n estimatedDeadline: data.estimated_deadline || \"\",\n };\n }\n}\n","/**\n * ComplyEdge OpenAI Middleware\n *\n * Wraps an OpenAI client to run compliance checks on messages\n * before they are sent to the model.\n *\n * Usage:\n * import OpenAI from \"openai\";\n * import { ComplyEdgeClient } from \"@complyedge/sdk\";\n * import { withCompliance } from \"@complyedge/sdk/openai-middleware\";\n *\n * const ce = new ComplyEdgeClient({ apiKey: \"...\" });\n * const openai = withCompliance(new OpenAI(), ce);\n * // Now openai.chat.completions.create() runs compliance checks automatically\n */\n\nimport type { ComplyEdgeClient } from \"./client\";\nimport type { ComplianceViolation } from \"./types\";\n\nexport class ComplianceError extends Error {\n public violations: ComplianceViolation[];\n\n constructor(message: string, violations: ComplianceViolation[]) {\n super(message);\n this.name = \"ComplianceError\";\n this.violations = violations;\n }\n}\n\n/**\n * Wrap an OpenAI client with ComplyEdge compliance checks.\n * The returned object proxies chat.completions.create() to check\n * user messages before sending to OpenAI.\n */\nexport function withCompliance<T extends Record<string, unknown>>(\n openaiClient: T,\n ceClient: ComplyEdgeClient,\n options?: {\n checkInput?: boolean;\n checkOutput?: boolean;\n jurisdiction?: string;\n blockOnViolation?: boolean;\n }\n): T {\n const opts = {\n checkInput: true,\n checkOutput: false,\n blockOnViolation: true,\n ...options,\n };\n\n const chat = openaiClient.chat as Record<string, unknown> | undefined;\n if (!chat || !chat.completions) {\n return openaiClient; // Not an OpenAI-shaped client, return as-is\n }\n\n const completions = chat.completions as Record<string, unknown>;\n const originalCreate = completions.create as (...args: unknown[]) => Promise<unknown>;\n\n if (typeof originalCreate !== \"function\") {\n return openaiClient;\n }\n\n completions.create = async function (...args: unknown[]) {\n const params = args[0] as Record<string, unknown> | undefined;\n\n if (opts.checkInput && params?.messages) {\n const messages = params.messages as Array<{ role: string; content: unknown }>;\n // Check EVERY user turn, not just the last one. A violation in an earlier\n // message is still sent to the model verbatim, so checking only the tail\n // left the majority of a multi-turn conversation unenforced.\n for (const message of messages) {\n if (message.role !== \"user\") continue;\n for (const text of extractText(message.content)) {\n const result = await ceClient.check(text, {\n direction: \"prompt\",\n jurisdiction: opts.jurisdiction,\n });\n if (!result.allowed && opts.blockOnViolation) {\n throw new ComplianceError(\n `Compliance violation detected: ${result.violations.map((v) => v.ruleId).join(\", \")}`,\n result.violations\n );\n }\n }\n }\n }\n\n const response = await originalCreate.apply(this, args);\n\n if (opts.checkOutput) {\n // Previously declared in the options type and never read, so the README's\n // \"runs compliance checks automatically\" was half true: inputs were\n // checked, model output never was, silently.\n for (const text of extractCompletionText(response)) {\n const result = await ceClient.check(text, {\n direction: \"output\",\n jurisdiction: opts.jurisdiction,\n });\n if (!result.allowed && opts.blockOnViolation) {\n throw new ComplianceError(\n `Compliance violation in model output: ${result.violations.map((v) => v.ruleId).join(\", \")}`,\n result.violations\n );\n }\n }\n }\n\n return response;\n };\n\n return openaiClient;\n}\n\n/**\n * Pull checkable strings out of a message `content` field.\n *\n * OpenAI accepts either a plain string or an array of typed content parts.\n * The array form was previously passed to the API as a non-string and 422'd,\n * so multimodal callers could not use the middleware at all.\n */\nfunction extractText(content: unknown): string[] {\n if (typeof content === \"string\") {\n return content.trim() ? [content] : [];\n }\n if (Array.isArray(content)) {\n return content\n .map((part) => {\n if (typeof part === \"string\") return part;\n const p = part as Record<string, unknown> | null;\n return p && typeof p.text === \"string\" ? p.text : \"\";\n })\n .filter((t) => t.trim().length > 0);\n }\n return [];\n}\n\n/** Pull assistant message text out of a chat completion response. */\nfunction extractCompletionText(response: unknown): string[] {\n const r = response as { choices?: Array<{ message?: { content?: unknown } }> } | null;\n if (!r?.choices) return [];\n return r.choices.flatMap((choice) => extractText(choice?.message?.content));\n}\n"],"mappings":"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","names":["axios"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ComplianceError,
|
|
3
3
|
withCompliance
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-X7L22AQP.mjs";
|
|
5
5
|
|
|
6
6
|
// src/client.ts
|
|
7
7
|
import axios from "axios";
|
|
@@ -39,7 +39,12 @@ var ComplyEdgeClient = class {
|
|
|
39
39
|
text,
|
|
40
40
|
agent_id: context?.agentId || this.agentId,
|
|
41
41
|
jurisdiction,
|
|
42
|
-
|
|
42
|
+
// Pass the caller's direction through untouched. This previously mapped
|
|
43
|
+
// "prompt" to "input", a value the API's DirectionType enum does not
|
|
44
|
+
// accept ("prompt" | "output"), so every input check 422'd and the
|
|
45
|
+
// wrapped call threw. The SDK's own ComplianceContext type has always
|
|
46
|
+
// declared the correct union; only this line disagreed with it.
|
|
47
|
+
direction: context?.direction ?? "output",
|
|
43
48
|
use_semantic_fallback: false,
|
|
44
49
|
context: context?.userRole ? { user_role: context.userRole } : void 0
|
|
45
50
|
});
|
|
@@ -64,6 +69,8 @@ var ComplyEdgeClient = class {
|
|
|
64
69
|
enginePath: data.engine_path || "opa",
|
|
65
70
|
opaLatencyMs: data.opa_latency_ms,
|
|
66
71
|
auditLogged: data.audit_logged !== false,
|
|
72
|
+
textHash: data.text_hash || "",
|
|
73
|
+
timestamp: data.timestamp,
|
|
67
74
|
jurisdiction,
|
|
68
75
|
processingTimeMs
|
|
69
76
|
};
|
|
@@ -77,7 +84,7 @@ var ComplyEdgeClient = class {
|
|
|
77
84
|
*/
|
|
78
85
|
async detectSensitivity(text, context) {
|
|
79
86
|
const start = Date.now();
|
|
80
|
-
const jurisdiction = context?.jurisdiction || this.jurisdiction || "
|
|
87
|
+
const jurisdiction = context?.jurisdiction || this.jurisdiction || "EU";
|
|
81
88
|
const response = await this.http.post("/v1/sensitivity/detect", {
|
|
82
89
|
input_text: text,
|
|
83
90
|
agent_id: context?.agentId || this.agentId,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * ComplyEdge TypeScript SDK — Client\n */\n\nimport axios, { AxiosInstance } from \"axios\";\nimport type {\n ComplyEdgeConfig,\n ComplianceContext,\n ComplianceResult,\n ComplianceViolation,\n SensitivityDetection,\n SensitivityResult,\n PreDeploymentInput,\n PreDeploymentResult,\n} from \"./types\";\n\nconst DEFAULT_BASE_URL = \"https://api.complyedge.io\";\nconst SDK_VERSION = \"0.2.0\";\n\nexport class ComplyEdgeClient {\n private http: AxiosInstance;\n private agentId: string;\n private jurisdiction?: string;\n\n constructor(config: ComplyEdgeConfig) {\n const baseUrl = config.baseUrl || process.env.COMPLYEDGE_API_URL || DEFAULT_BASE_URL;\n\n this.agentId = config.agentId || \"default\";\n this.jurisdiction = config.jurisdiction;\n\n this.http = axios.create({\n baseURL: baseUrl.replace(/\\/+$/, \"\"),\n timeout: config.timeout || 30_000,\n headers: {\n Authorization: `Bearer ${config.apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": `complyedge-typescript-sdk/${SDK_VERSION}`,\n },\n });\n }\n\n /**\n * Run a compliance check on text input.\n *\n * Calls POST /v1/check: the deterministic OPA hot path. A decision here is\n * evaluated against the Rego rule bundle, returns article-cited violations,\n * and is written to the Article 12 audit trail (`auditLogged`).\n */\n async check(text: string, context?: ComplianceContext): Promise<ComplianceResult> {\n const start = Date.now();\n const jurisdiction = context?.jurisdiction || this.jurisdiction || \"EU\";\n\n const response = await this.http.post(\"/v1/check\", {\n text,\n agent_id: context?.agentId || this.agentId,\n jurisdiction,\n
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * ComplyEdge TypeScript SDK — Client\n */\n\nimport axios, { AxiosInstance } from \"axios\";\nimport type {\n ComplyEdgeConfig,\n ComplianceContext,\n ComplianceResult,\n ComplianceViolation,\n SensitivityDetection,\n SensitivityResult,\n PreDeploymentInput,\n PreDeploymentResult,\n} from \"./types\";\n\nconst DEFAULT_BASE_URL = \"https://api.complyedge.io\";\n// Keep in sync with package.json. Hardcoding it here meant the User-Agent\n// silently reported a stale version after every release bump, which is the\n// one field support uses to tell which client a customer is actually on.\nconst SDK_VERSION = \"0.2.0\";\n\nexport class ComplyEdgeClient {\n private http: AxiosInstance;\n private agentId: string;\n private jurisdiction?: string;\n\n constructor(config: ComplyEdgeConfig) {\n const baseUrl = config.baseUrl || process.env.COMPLYEDGE_API_URL || DEFAULT_BASE_URL;\n\n this.agentId = config.agentId || \"default\";\n this.jurisdiction = config.jurisdiction;\n\n this.http = axios.create({\n baseURL: baseUrl.replace(/\\/+$/, \"\"),\n timeout: config.timeout || 30_000,\n headers: {\n Authorization: `Bearer ${config.apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": `complyedge-typescript-sdk/${SDK_VERSION}`,\n },\n });\n }\n\n /**\n * Run a compliance check on text input.\n *\n * Calls POST /v1/check: the deterministic OPA hot path. A decision here is\n * evaluated against the Rego rule bundle, returns article-cited violations,\n * and is written to the Article 12 audit trail (`auditLogged`).\n */\n async check(text: string, context?: ComplianceContext): Promise<ComplianceResult> {\n const start = Date.now();\n const jurisdiction = context?.jurisdiction || this.jurisdiction || \"EU\";\n\n const response = await this.http.post(\"/v1/check\", {\n text,\n agent_id: context?.agentId || this.agentId,\n jurisdiction,\n // Pass the caller's direction through untouched. This previously mapped\n // \"prompt\" to \"input\", a value the API's DirectionType enum does not\n // accept (\"prompt\" | \"output\"), so every input check 422'd and the\n // wrapped call threw. The SDK's own ComplianceContext type has always\n // declared the correct union; only this line disagreed with it.\n direction: context?.direction ?? \"output\",\n use_semantic_fallback: false,\n context: context?.userRole ? { user_role: context.userRole } : undefined,\n });\n\n const data = response.data;\n const processingTimeMs = Date.now() - start;\n const allowed = data.allowed !== false;\n\n return {\n eventId: data.event_id || \"\",\n allowed,\n status: allowed ? \"safe\" : \"violation\",\n violations: (data.violations || []).map((v: Record<string, unknown>) => ({\n ruleId: (v.rule_id as string) || \"\",\n ruleDescription: (v.rule_description as string) || \"\",\n severity: (v.severity as ComplianceViolation[\"severity\"]) || \"medium\",\n reason: (v.reason as string) || \"\",\n confidence: (v.confidence as number) ?? 1.0,\n textExcerpt: v.text_excerpt as string | undefined,\n })),\n latencyMs: data.latency_ms || 0,\n bundleVersion: data.bundle_version || \"\",\n evaluatedRules: data.evaluated_rules || [],\n enginePath: data.engine_path || \"opa\",\n opaLatencyMs: data.opa_latency_ms,\n auditLogged: data.audit_logged !== false,\n textHash: data.text_hash || \"\",\n timestamp: data.timestamp,\n jurisdiction,\n processingTimeMs,\n };\n }\n\n /**\n * Run proactive sensitivity detection on user input.\n *\n * Calls POST /v1/sensitivity/detect, which deliberately stays on the legacy\n * TrustLint + LLM pipeline. It does NOT run OPA and does NOT write the\n * Article 12 audit trail. Use `check()` for runtime EU AI Act enforcement.\n */\n async detectSensitivity(\n text: string,\n context?: ComplianceContext\n ): Promise<SensitivityResult> {\n const start = Date.now();\n // \"EU\" matches the Python SDK and the server default. This read \"US\",\n // so the two SDKs enforced different rule sets for identical code.\n const jurisdiction = context?.jurisdiction || this.jurisdiction || \"EU\";\n\n const response = await this.http.post(\"/v1/sensitivity/detect\", {\n input_text: text,\n agent_id: context?.agentId || this.agentId,\n jurisdiction,\n direction: context?.direction || \"prompt\",\n user_role: context?.userRole,\n });\n\n const data = response.data;\n\n return {\n eventId: data.event_id || \"\",\n status: data.overall_risk_assessment === \"safe\" ? \"safe\" : \"violation\",\n detections: (data.detections || []).map((d: Record<string, unknown>) => ({\n ruleId: (d.rule_id as string) || \"\",\n severity: (d.severity as SensitivityDetection[\"severity\"]) || \"medium\",\n regulation: (d.regulation as string) || \"\",\n description: (d.description as string) || \"\",\n article: d.article as string | undefined,\n remediation: d.remediation as string | undefined,\n })),\n riskScore: data.overall_risk_score || 0,\n jurisdiction,\n processingTimeMs: Date.now() - start,\n };\n }\n\n /**\n * Run a pre-deployment assessment on an AI system configuration.\n */\n async assessPreDeployment(input: PreDeploymentInput): Promise<PreDeploymentResult> {\n const response = await this.http.post(\"/v1/assessment/pre-deployment\", {\n system_prompt: input.systemPrompt,\n model_config: input.modelConfig\n ? {\n provider: input.modelConfig.provider,\n model_id: input.modelConfig.modelId,\n temperature: input.modelConfig.temperature,\n }\n : undefined,\n agent_pipeline: input.agentPipeline\n ? {\n tools: input.agentPipeline.tools,\n memory: input.agentPipeline.memory,\n autonomy_level: input.agentPipeline.autonomyLevel,\n human_oversight: input.agentPipeline.humanOversight,\n }\n : undefined,\n jurisdiction: input.jurisdiction || \"EU\",\n });\n\n const data = response.data;\n return {\n complianceScore: data.compliance_score,\n riskTier: data.risk_tier,\n violations: (data.violations || []).map((v: Record<string, unknown>) => ({\n ruleId: v.rule_id as string,\n article: v.article as string,\n description: v.description as string,\n requiredAction: v.required_action as string,\n })),\n requiredDisclosures: data.required_disclosures || [],\n euAiActCategory: data.eu_ai_act_category || \"\",\n estimatedDeadline: data.estimated_deadline || \"\",\n };\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -33,6 +33,14 @@ interface ComplianceResult {
|
|
|
33
33
|
enginePath: string;
|
|
34
34
|
opaLatencyMs?: number;
|
|
35
35
|
auditLogged: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* SHA-256 of the evaluated text as a bare hex digest, byte-identical to this
|
|
38
|
+
* event's Article 12 audit entry. Binds the decision to its exact input
|
|
39
|
+
* without a second call to the audit endpoint. The text is never stored.
|
|
40
|
+
*/
|
|
41
|
+
textHash: string;
|
|
42
|
+
/** UTC instant the evaluation started, matching the audit entry. */
|
|
43
|
+
timestamp?: string;
|
|
36
44
|
jurisdiction: string;
|
|
37
45
|
/** Client-measured round trip, including network. */
|
|
38
46
|
processingTimeMs: number;
|
|
@@ -33,6 +33,14 @@ interface ComplianceResult {
|
|
|
33
33
|
enginePath: string;
|
|
34
34
|
opaLatencyMs?: number;
|
|
35
35
|
auditLogged: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* SHA-256 of the evaluated text as a bare hex digest, byte-identical to this
|
|
38
|
+
* event's Article 12 audit entry. Binds the decision to its exact input
|
|
39
|
+
* without a second call to the audit endpoint. The text is never stored.
|
|
40
|
+
*/
|
|
41
|
+
textHash: string;
|
|
42
|
+
/** UTC instant the evaluation started, matching the audit entry. */
|
|
43
|
+
timestamp?: string;
|
|
36
44
|
jurisdiction: string;
|
|
37
45
|
/** Client-measured round trip, including network. */
|
|
38
46
|
processingTimeMs: number;
|
|
@@ -52,25 +52,59 @@ function withCompliance(openaiClient, ceClient, options) {
|
|
|
52
52
|
const params = args[0];
|
|
53
53
|
if (opts.checkInput && params?.messages) {
|
|
54
54
|
const messages = params.messages;
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
for (const message of messages) {
|
|
56
|
+
if (message.role !== "user") continue;
|
|
57
|
+
for (const text of extractText(message.content)) {
|
|
58
|
+
const result = await ceClient.check(text, {
|
|
59
|
+
direction: "prompt",
|
|
60
|
+
jurisdiction: opts.jurisdiction
|
|
61
|
+
});
|
|
62
|
+
if (!result.allowed && opts.blockOnViolation) {
|
|
63
|
+
throw new ComplianceError(
|
|
64
|
+
`Compliance violation detected: ${result.violations.map((v) => v.ruleId).join(", ")}`,
|
|
65
|
+
result.violations
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const response = await originalCreate.apply(this, args);
|
|
72
|
+
if (opts.checkOutput) {
|
|
73
|
+
for (const text of extractCompletionText(response)) {
|
|
74
|
+
const result = await ceClient.check(text, {
|
|
75
|
+
direction: "output",
|
|
60
76
|
jurisdiction: opts.jurisdiction
|
|
61
77
|
});
|
|
62
78
|
if (!result.allowed && opts.blockOnViolation) {
|
|
63
79
|
throw new ComplianceError(
|
|
64
|
-
`Compliance violation
|
|
80
|
+
`Compliance violation in model output: ${result.violations.map((v) => v.ruleId).join(", ")}`,
|
|
65
81
|
result.violations
|
|
66
82
|
);
|
|
67
83
|
}
|
|
68
84
|
}
|
|
69
85
|
}
|
|
70
|
-
return
|
|
86
|
+
return response;
|
|
71
87
|
};
|
|
72
88
|
return openaiClient;
|
|
73
89
|
}
|
|
90
|
+
function extractText(content) {
|
|
91
|
+
if (typeof content === "string") {
|
|
92
|
+
return content.trim() ? [content] : [];
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(content)) {
|
|
95
|
+
return content.map((part) => {
|
|
96
|
+
if (typeof part === "string") return part;
|
|
97
|
+
const p = part;
|
|
98
|
+
return p && typeof p.text === "string" ? p.text : "";
|
|
99
|
+
}).filter((t) => t.trim().length > 0);
|
|
100
|
+
}
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
function extractCompletionText(response) {
|
|
104
|
+
const r = response;
|
|
105
|
+
if (!r?.choices) return [];
|
|
106
|
+
return r.choices.flatMap((choice) => extractText(choice?.message?.content));
|
|
107
|
+
}
|
|
74
108
|
// Annotate the CommonJS export names for ESM import in node:
|
|
75
109
|
0 && (module.exports = {
|
|
76
110
|
ComplianceError,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/openai-middleware.ts"],"sourcesContent":["/**\n * ComplyEdge OpenAI Middleware\n *\n * Wraps an OpenAI client to run compliance checks on messages\n * before they are sent to the model.\n *\n * Usage:\n * import OpenAI from \"openai\";\n * import { ComplyEdgeClient } from \"@complyedge/sdk\";\n * import { withCompliance } from \"@complyedge/sdk/openai-middleware\";\n *\n * const ce = new ComplyEdgeClient({ apiKey: \"...\" });\n * const openai = withCompliance(new OpenAI(), ce);\n * // Now openai.chat.completions.create() runs compliance checks automatically\n */\n\nimport type { ComplyEdgeClient } from \"./client\";\nimport type { ComplianceViolation } from \"./types\";\n\nexport class ComplianceError extends Error {\n public violations: ComplianceViolation[];\n\n constructor(message: string, violations: ComplianceViolation[]) {\n super(message);\n this.name = \"ComplianceError\";\n this.violations = violations;\n }\n}\n\n/**\n * Wrap an OpenAI client with ComplyEdge compliance checks.\n * The returned object proxies chat.completions.create() to check\n * user messages before sending to OpenAI.\n */\nexport function withCompliance<T extends Record<string, unknown>>(\n openaiClient: T,\n ceClient: ComplyEdgeClient,\n options?: {\n checkInput?: boolean;\n checkOutput?: boolean;\n jurisdiction?: string;\n blockOnViolation?: boolean;\n }\n): T {\n const opts = {\n checkInput: true,\n checkOutput: false,\n blockOnViolation: true,\n ...options,\n };\n\n const chat = openaiClient.chat as Record<string, unknown> | undefined;\n if (!chat || !chat.completions) {\n return openaiClient; // Not an OpenAI-shaped client, return as-is\n }\n\n const completions = chat.completions as Record<string, unknown>;\n const originalCreate = completions.create as (...args: unknown[]) => Promise<unknown>;\n\n if (typeof originalCreate !== \"function\") {\n return openaiClient;\n }\n\n completions.create = async function (...args: unknown[]) {\n const params = args[0] as Record<string, unknown> | undefined;\n\n if (opts.checkInput && params?.messages) {\n const messages = params.messages as Array<{ role: string; content:
|
|
1
|
+
{"version":3,"sources":["../src/openai-middleware.ts"],"sourcesContent":["/**\n * ComplyEdge OpenAI Middleware\n *\n * Wraps an OpenAI client to run compliance checks on messages\n * before they are sent to the model.\n *\n * Usage:\n * import OpenAI from \"openai\";\n * import { ComplyEdgeClient } from \"@complyedge/sdk\";\n * import { withCompliance } from \"@complyedge/sdk/openai-middleware\";\n *\n * const ce = new ComplyEdgeClient({ apiKey: \"...\" });\n * const openai = withCompliance(new OpenAI(), ce);\n * // Now openai.chat.completions.create() runs compliance checks automatically\n */\n\nimport type { ComplyEdgeClient } from \"./client\";\nimport type { ComplianceViolation } from \"./types\";\n\nexport class ComplianceError extends Error {\n public violations: ComplianceViolation[];\n\n constructor(message: string, violations: ComplianceViolation[]) {\n super(message);\n this.name = \"ComplianceError\";\n this.violations = violations;\n }\n}\n\n/**\n * Wrap an OpenAI client with ComplyEdge compliance checks.\n * The returned object proxies chat.completions.create() to check\n * user messages before sending to OpenAI.\n */\nexport function withCompliance<T extends Record<string, unknown>>(\n openaiClient: T,\n ceClient: ComplyEdgeClient,\n options?: {\n checkInput?: boolean;\n checkOutput?: boolean;\n jurisdiction?: string;\n blockOnViolation?: boolean;\n }\n): T {\n const opts = {\n checkInput: true,\n checkOutput: false,\n blockOnViolation: true,\n ...options,\n };\n\n const chat = openaiClient.chat as Record<string, unknown> | undefined;\n if (!chat || !chat.completions) {\n return openaiClient; // Not an OpenAI-shaped client, return as-is\n }\n\n const completions = chat.completions as Record<string, unknown>;\n const originalCreate = completions.create as (...args: unknown[]) => Promise<unknown>;\n\n if (typeof originalCreate !== \"function\") {\n return openaiClient;\n }\n\n completions.create = async function (...args: unknown[]) {\n const params = args[0] as Record<string, unknown> | undefined;\n\n if (opts.checkInput && params?.messages) {\n const messages = params.messages as Array<{ role: string; content: unknown }>;\n // Check EVERY user turn, not just the last one. A violation in an earlier\n // message is still sent to the model verbatim, so checking only the tail\n // left the majority of a multi-turn conversation unenforced.\n for (const message of messages) {\n if (message.role !== \"user\") continue;\n for (const text of extractText(message.content)) {\n const result = await ceClient.check(text, {\n direction: \"prompt\",\n jurisdiction: opts.jurisdiction,\n });\n if (!result.allowed && opts.blockOnViolation) {\n throw new ComplianceError(\n `Compliance violation detected: ${result.violations.map((v) => v.ruleId).join(\", \")}`,\n result.violations\n );\n }\n }\n }\n }\n\n const response = await originalCreate.apply(this, args);\n\n if (opts.checkOutput) {\n // Previously declared in the options type and never read, so the README's\n // \"runs compliance checks automatically\" was half true: inputs were\n // checked, model output never was, silently.\n for (const text of extractCompletionText(response)) {\n const result = await ceClient.check(text, {\n direction: \"output\",\n jurisdiction: opts.jurisdiction,\n });\n if (!result.allowed && opts.blockOnViolation) {\n throw new ComplianceError(\n `Compliance violation in model output: ${result.violations.map((v) => v.ruleId).join(\", \")}`,\n result.violations\n );\n }\n }\n }\n\n return response;\n };\n\n return openaiClient;\n}\n\n/**\n * Pull checkable strings out of a message `content` field.\n *\n * OpenAI accepts either a plain string or an array of typed content parts.\n * The array form was previously passed to the API as a non-string and 422'd,\n * so multimodal callers could not use the middleware at all.\n */\nfunction extractText(content: unknown): string[] {\n if (typeof content === \"string\") {\n return content.trim() ? [content] : [];\n }\n if (Array.isArray(content)) {\n return content\n .map((part) => {\n if (typeof part === \"string\") return part;\n const p = part as Record<string, unknown> | null;\n return p && typeof p.text === \"string\" ? p.text : \"\";\n })\n .filter((t) => t.trim().length > 0);\n }\n return [];\n}\n\n/** Pull assistant message text out of a chat completion response. */\nfunction extractCompletionText(response: unknown): string[] {\n const r = response as { choices?: Array<{ message?: { content?: unknown } }> } | null;\n if (!r?.choices) return [];\n return r.choices.flatMap((choice) => extractText(choice?.message?.content));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAClC;AAAA,EAEP,YAAY,SAAiB,YAAmC;AAC9D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAOO,SAAS,eACd,cACA,UACA,SAMG;AACH,QAAM,OAAO;AAAA,IACX,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,GAAG;AAAA,EACL;AAEA,QAAM,OAAO,aAAa;AAC1B,MAAI,CAAC,QAAQ,CAAC,KAAK,aAAa;AAC9B,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,KAAK;AACzB,QAAM,iBAAiB,YAAY;AAEnC,MAAI,OAAO,mBAAmB,YAAY;AACxC,WAAO;AAAA,EACT;AAEA,cAAY,SAAS,kBAAmB,MAAiB;AACvD,UAAM,SAAS,KAAK,CAAC;AAErB,QAAI,KAAK,cAAc,QAAQ,UAAU;AACvC,YAAM,WAAW,OAAO;AAIxB,iBAAW,WAAW,UAAU;AAC9B,YAAI,QAAQ,SAAS,OAAQ;AAC7B,mBAAW,QAAQ,YAAY,QAAQ,OAAO,GAAG;AAC/C,gBAAM,SAAS,MAAM,SAAS,MAAM,MAAM;AAAA,YACxC,WAAW;AAAA,YACX,cAAc,KAAK;AAAA,UACrB,CAAC;AACD,cAAI,CAAC,OAAO,WAAW,KAAK,kBAAkB;AAC5C,kBAAM,IAAI;AAAA,cACR,kCAAkC,OAAO,WAAW,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAAA,cACnF,OAAO;AAAA,YACT;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,eAAe,MAAM,MAAM,IAAI;AAEtD,QAAI,KAAK,aAAa;AAIpB,iBAAW,QAAQ,sBAAsB,QAAQ,GAAG;AAClD,cAAM,SAAS,MAAM,SAAS,MAAM,MAAM;AAAA,UACxC,WAAW;AAAA,UACX,cAAc,KAAK;AAAA,QACrB,CAAC;AACD,YAAI,CAAC,OAAO,WAAW,KAAK,kBAAkB;AAC5C,gBAAM,IAAI;AAAA,YACR,yCAAyC,OAAO,WAAW,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAAA,YAC1F,OAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AASA,SAAS,YAAY,SAA4B;AAC/C,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,QAAQ,KAAK,IAAI,CAAC,OAAO,IAAI,CAAC;AAAA,EACvC;AACA,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,WAAO,QACJ,IAAI,CAAC,SAAS;AACb,UAAI,OAAO,SAAS,SAAU,QAAO;AACrC,YAAM,IAAI;AACV,aAAO,KAAK,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AAAA,IACpD,CAAC,EACA,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC;AAAA,EACtC;AACA,SAAO,CAAC;AACV;AAGA,SAAS,sBAAsB,UAA6B;AAC1D,QAAM,IAAI;AACV,MAAI,CAAC,GAAG,QAAS,QAAO,CAAC;AACzB,SAAO,EAAE,QAAQ,QAAQ,CAAC,WAAW,YAAY,QAAQ,SAAS,OAAO,CAAC;AAC5E;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@complyedge/sdk",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "EU AI Act runtime enforcement SDK: OPA/Rego policy checks, Article 12 audit trails, risk assessment, and OpenAI middleware.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"keywords": [
|
|
9
|
+
"eu-ai-act",
|
|
10
|
+
"ai-act",
|
|
11
|
+
"ai-governance",
|
|
12
|
+
"ai-compliance",
|
|
9
13
|
"compliance",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
14
|
+
"runtime-enforcement",
|
|
15
|
+
"guardrails",
|
|
16
|
+
"llm-security",
|
|
17
|
+
"opa",
|
|
18
|
+
"rego",
|
|
19
|
+
"audit-trail",
|
|
20
|
+
"article-12",
|
|
21
|
+
"risk-assessment",
|
|
22
|
+
"openai",
|
|
12
23
|
"sdk",
|
|
13
24
|
"typescript"
|
|
14
25
|
],
|
|
@@ -32,6 +43,7 @@
|
|
|
32
43
|
"files": [
|
|
33
44
|
"dist/**/*",
|
|
34
45
|
"README.md",
|
|
46
|
+
"CHANGELOG.md",
|
|
35
47
|
"LICENSE"
|
|
36
48
|
],
|
|
37
49
|
"scripts": {
|
|
@@ -69,6 +81,10 @@
|
|
|
69
81
|
"optional": true
|
|
70
82
|
}
|
|
71
83
|
},
|
|
84
|
+
"repository": {
|
|
85
|
+
"type": "git",
|
|
86
|
+
"url": "git+https://github.com/ComplyEdge/complyedge.git"
|
|
87
|
+
},
|
|
72
88
|
"bugs": {
|
|
73
89
|
"url": "https://github.com/ComplyEdge/complyedge/issues"
|
|
74
90
|
},
|
package/dist/chunk-KZYIMMVM.mjs
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// src/openai-middleware.ts
|
|
2
|
-
var ComplianceError = class extends Error {
|
|
3
|
-
violations;
|
|
4
|
-
constructor(message, violations) {
|
|
5
|
-
super(message);
|
|
6
|
-
this.name = "ComplianceError";
|
|
7
|
-
this.violations = violations;
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
function withCompliance(openaiClient, ceClient, options) {
|
|
11
|
-
const opts = {
|
|
12
|
-
checkInput: true,
|
|
13
|
-
checkOutput: false,
|
|
14
|
-
blockOnViolation: true,
|
|
15
|
-
...options
|
|
16
|
-
};
|
|
17
|
-
const chat = openaiClient.chat;
|
|
18
|
-
if (!chat || !chat.completions) {
|
|
19
|
-
return openaiClient;
|
|
20
|
-
}
|
|
21
|
-
const completions = chat.completions;
|
|
22
|
-
const originalCreate = completions.create;
|
|
23
|
-
if (typeof originalCreate !== "function") {
|
|
24
|
-
return openaiClient;
|
|
25
|
-
}
|
|
26
|
-
completions.create = async function(...args) {
|
|
27
|
-
const params = args[0];
|
|
28
|
-
if (opts.checkInput && params?.messages) {
|
|
29
|
-
const messages = params.messages;
|
|
30
|
-
const userMessages = messages.filter((m) => m.role === "user");
|
|
31
|
-
const lastUserMessage = userMessages[userMessages.length - 1];
|
|
32
|
-
if (lastUserMessage?.content) {
|
|
33
|
-
const result = await ceClient.check(lastUserMessage.content, {
|
|
34
|
-
direction: "prompt",
|
|
35
|
-
jurisdiction: opts.jurisdiction
|
|
36
|
-
});
|
|
37
|
-
if (!result.allowed && opts.blockOnViolation) {
|
|
38
|
-
throw new ComplianceError(
|
|
39
|
-
`Compliance violation detected: ${result.violations.map((v) => v.ruleId).join(", ")}`,
|
|
40
|
-
result.violations
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return originalCreate.apply(this, args);
|
|
46
|
-
};
|
|
47
|
-
return openaiClient;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export {
|
|
51
|
-
ComplianceError,
|
|
52
|
-
withCompliance
|
|
53
|
-
};
|
|
54
|
-
//# sourceMappingURL=chunk-KZYIMMVM.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/openai-middleware.ts"],"sourcesContent":["/**\n * ComplyEdge OpenAI Middleware\n *\n * Wraps an OpenAI client to run compliance checks on messages\n * before they are sent to the model.\n *\n * Usage:\n * import OpenAI from \"openai\";\n * import { ComplyEdgeClient } from \"@complyedge/sdk\";\n * import { withCompliance } from \"@complyedge/sdk/openai-middleware\";\n *\n * const ce = new ComplyEdgeClient({ apiKey: \"...\" });\n * const openai = withCompliance(new OpenAI(), ce);\n * // Now openai.chat.completions.create() runs compliance checks automatically\n */\n\nimport type { ComplyEdgeClient } from \"./client\";\nimport type { ComplianceViolation } from \"./types\";\n\nexport class ComplianceError extends Error {\n public violations: ComplianceViolation[];\n\n constructor(message: string, violations: ComplianceViolation[]) {\n super(message);\n this.name = \"ComplianceError\";\n this.violations = violations;\n }\n}\n\n/**\n * Wrap an OpenAI client with ComplyEdge compliance checks.\n * The returned object proxies chat.completions.create() to check\n * user messages before sending to OpenAI.\n */\nexport function withCompliance<T extends Record<string, unknown>>(\n openaiClient: T,\n ceClient: ComplyEdgeClient,\n options?: {\n checkInput?: boolean;\n checkOutput?: boolean;\n jurisdiction?: string;\n blockOnViolation?: boolean;\n }\n): T {\n const opts = {\n checkInput: true,\n checkOutput: false,\n blockOnViolation: true,\n ...options,\n };\n\n const chat = openaiClient.chat as Record<string, unknown> | undefined;\n if (!chat || !chat.completions) {\n return openaiClient; // Not an OpenAI-shaped client, return as-is\n }\n\n const completions = chat.completions as Record<string, unknown>;\n const originalCreate = completions.create as (...args: unknown[]) => Promise<unknown>;\n\n if (typeof originalCreate !== \"function\") {\n return openaiClient;\n }\n\n completions.create = async function (...args: unknown[]) {\n const params = args[0] as Record<string, unknown> | undefined;\n\n if (opts.checkInput && params?.messages) {\n const messages = params.messages as Array<{ role: string; content: string }>;\n const userMessages = messages.filter((m) => m.role === \"user\");\n const lastUserMessage = userMessages[userMessages.length - 1];\n\n if (lastUserMessage?.content) {\n const result = await ceClient.check(lastUserMessage.content, {\n direction: \"prompt\",\n jurisdiction: opts.jurisdiction,\n });\n\n if (!result.allowed && opts.blockOnViolation) {\n throw new ComplianceError(\n `Compliance violation detected: ${result.violations.map((v) => v.ruleId).join(\", \")}`,\n result.violations\n );\n }\n }\n }\n\n return originalCreate.apply(this, args);\n };\n\n return openaiClient;\n}\n"],"mappings":";AAmBO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAClC;AAAA,EAEP,YAAY,SAAiB,YAAmC;AAC9D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAOO,SAAS,eACd,cACA,UACA,SAMG;AACH,QAAM,OAAO;AAAA,IACX,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,GAAG;AAAA,EACL;AAEA,QAAM,OAAO,aAAa;AAC1B,MAAI,CAAC,QAAQ,CAAC,KAAK,aAAa;AAC9B,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,KAAK;AACzB,QAAM,iBAAiB,YAAY;AAEnC,MAAI,OAAO,mBAAmB,YAAY;AACxC,WAAO;AAAA,EACT;AAEA,cAAY,SAAS,kBAAmB,MAAiB;AACvD,UAAM,SAAS,KAAK,CAAC;AAErB,QAAI,KAAK,cAAc,QAAQ,UAAU;AACvC,YAAM,WAAW,OAAO;AACxB,YAAM,eAAe,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM;AAC7D,YAAM,kBAAkB,aAAa,aAAa,SAAS,CAAC;AAE5D,UAAI,iBAAiB,SAAS;AAC5B,cAAM,SAAS,MAAM,SAAS,MAAM,gBAAgB,SAAS;AAAA,UAC3D,WAAW;AAAA,UACX,cAAc,KAAK;AAAA,QACrB,CAAC;AAED,YAAI,CAAC,OAAO,WAAW,KAAK,kBAAkB;AAC5C,gBAAM,IAAI;AAAA,YACR,kCAAkC,OAAO,WAAW,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAAA,YACnF,OAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO,eAAe,MAAM,MAAM,IAAI;AAAA,EACxC;AAEA,SAAO;AACT;","names":[]}
|