@complyedge/sdk 0.2.4 → 0.2.5
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 +24 -0
- package/README.md +43 -5
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -3
- package/dist/index.mjs.map +1 -1
- package/dist/openai-middleware.d.mts +13 -2
- package/dist/openai-middleware.d.ts +13 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,30 @@ imply a parity that does not exist.
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [0.2.5] - 2026-09-25
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- No `baseUrl`, `COMPLYEDGE_API_URL`, or `region`, and no `ce_` / `ce_eu_`
|
|
23
|
+
prefix, now resolves to `https://eu.api.complyedge.io`. Legacy `ce_` keys
|
|
24
|
+
still use the US host. The 0.2.4 fallback was the US host.
|
|
25
|
+
|
|
26
|
+
### Docs
|
|
27
|
+
- Key rotation now lives on the dashboard's API Key page (Rotate asks before
|
|
28
|
+
minting; every active key is listed there). The public trust surface shows
|
|
29
|
+
one company name, set under Account, Profile; `display_name` on
|
|
30
|
+
`PATCH /v1/tenant/trust` sets the same value. No code change.
|
|
31
|
+
- Key rotation guidance in the README: rotate in the dashboard (mint, swap,
|
|
32
|
+
revoke previous) or with `POST` then `DELETE /v1/account/api-keys`; both
|
|
33
|
+
keys work until the old one is revoked. No code change.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- `sandbox: true` on `ComplianceContext` routes `check()` to
|
|
37
|
+
`POST /v1/sandbox/check`: the same rules, tenant settings and verdict as
|
|
38
|
+
`/v1/check`, with nothing recorded — no audit entry, no usage, no rate-limit
|
|
39
|
+
count. For trying cases before wiring an integration; never evidence.
|
|
40
|
+
`ComplianceResult.sandbox` is `true` only when the server says so, beside
|
|
41
|
+
`auditLogged: false`. Default is unchanged (`/v1/check`).
|
|
42
|
+
|
|
19
43
|
## [0.2.4] - 2026-09-16
|
|
20
44
|
|
|
21
45
|
### Added
|
package/README.md
CHANGED
|
@@ -18,6 +18,30 @@ Requires Node.js 18 or later. Get an API key at [dashboard.complyedge.io](https:
|
|
|
18
18
|
|
|
19
19
|
The SDK works in both ESM and CommonJS projects; no bundler-specific setup is required.
|
|
20
20
|
|
|
21
|
+
## Rotating your key
|
|
22
|
+
|
|
23
|
+
Keys are shown once, at creation, and stored as a hash: a lost key cannot be
|
|
24
|
+
recovered, only replaced. Rotate without downtime, in this order:
|
|
25
|
+
|
|
26
|
+
1. In the dashboard, open **API Key** and click **Rotate key** on the card
|
|
27
|
+
(or **Rotate** on a row under Other active keys), then confirm. A new key
|
|
28
|
+
is minted and shown once; the old one keeps working.
|
|
29
|
+
2. Put the new key in your integration (`COMPLYEDGE_API_KEY` or the `apiKey` you pass to the client) and deploy.
|
|
30
|
+
3. Back in the reveal, click **Revoke previous key**. Requests with the old key
|
|
31
|
+
fail from that moment. If you need more time, **Keep both for now** and
|
|
32
|
+
revoke it later from the API Key page, where every active key is listed.
|
|
33
|
+
|
|
34
|
+
Compromised key: revoke first (Revoke this key on the card, or Revoke on the
|
|
35
|
+
row), then rotate. Via the API the same flow is `POST /v1/account/api-keys`,
|
|
36
|
+
then `DELETE /v1/account/api-keys/{key_id}` for the old key.
|
|
37
|
+
|
|
38
|
+
## Company name on the public trust surface
|
|
39
|
+
|
|
40
|
+
Your public AI Trust Center and Enforcement Seal show one company name: the
|
|
41
|
+
one set in the dashboard under Account, Profile. `display_name` on
|
|
42
|
+
`PATCH /v1/tenant/trust` sets the same value, so an API call and the profile
|
|
43
|
+
never disagree. Email is your sign-in and cannot be changed from the dashboard.
|
|
44
|
+
|
|
21
45
|
## Package map
|
|
22
46
|
|
|
23
47
|
- [`@complyedge/mcp`](https://www.npmjs.com/package/@complyedge/mcp) runs the local, offline TrustLint MCP tools with `npx`.
|
|
@@ -65,6 +89,19 @@ Why: Social scoring prohibited under Article 5(1)(c)
|
|
|
65
89
|
Audit event: evt_01J...
|
|
66
90
|
```
|
|
67
91
|
|
|
92
|
+
### Try a case without recording it
|
|
93
|
+
|
|
94
|
+
`{ sandbox: true }` calls `POST /v1/sandbox/check`: the same rules, the same
|
|
95
|
+
tenant settings and the same verdict, but no audit entry, no usage and no
|
|
96
|
+
rate-limit count. Use it to test cases before wiring an integration. A sandbox
|
|
97
|
+
result is never evidence: `result.sandbox` is `true` and `result.auditLogged`
|
|
98
|
+
is `false`.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const trial = await ce.check("Score users based on their social behavior", { sandbox: true });
|
|
102
|
+
console.log(trial.allowed, trial.sandbox); // false true
|
|
103
|
+
```
|
|
104
|
+
|
|
68
105
|
## Configuration
|
|
69
106
|
|
|
70
107
|
```typescript
|
|
@@ -74,19 +111,20 @@ const ce = new ComplyEdgeClient({
|
|
|
74
111
|
agentId: "support-bot", // default: "default"
|
|
75
112
|
timeout: 30_000, // ms, default: 30_000
|
|
76
113
|
region: "eu", // "us" | "eu"; default: read from the key prefix (ce_eu_ -> eu)
|
|
77
|
-
baseUrl: "https://api.complyedge.io", // or COMPLYEDGE_API_URL; overrides region
|
|
114
|
+
baseUrl: "https://eu.api.complyedge.io", // or COMPLYEDGE_API_URL; overrides region
|
|
78
115
|
});
|
|
79
116
|
```
|
|
80
117
|
|
|
81
118
|
### Regions
|
|
82
119
|
|
|
83
|
-
ComplyEdge runs one independent stack per region —
|
|
84
|
-
and
|
|
120
|
+
ComplyEdge runs one independent stack per region — EU (`eu.api.complyedge.io`,
|
|
121
|
+
Frankfurt, where every new account lives) and US (`api.complyedge.io`, older
|
|
122
|
+
accounts only). Your tenant, its audit trail and
|
|
85
123
|
its API keys live in exactly one of them. Keys issued by the EU stack start
|
|
86
124
|
with `ce_eu_`, US keys with `ce_`; the client reads the prefix and picks the
|
|
87
125
|
host, so `region` is only needed to override it. Precedence, highest first:
|
|
88
126
|
`baseUrl` / `COMPLYEDGE_API_URL`, then `region` / `COMPLYEDGE_REGION`, then
|
|
89
|
-
the key prefix, then
|
|
127
|
+
the key prefix, then EU. A key presented to the other region's stack is
|
|
90
128
|
refused with `401 {"error":"wrong_region","use":"<host>"}` before any lookup.
|
|
91
129
|
|
|
92
130
|
## `check(text, context?)`
|
|
@@ -182,7 +220,7 @@ Network and HTTP failures surface as `AxiosError`. The middleware throws
|
|
|
182
220
|
- Quick start: https://www.complyedge.io/docs/quick-start.html
|
|
183
221
|
- API reference: https://www.complyedge.io/docs/api-reference.html
|
|
184
222
|
- Browser playground: https://www.complyedge.io/docs/playground.html
|
|
185
|
-
-
|
|
223
|
+
- Enforcement Seal embed: https://www.complyedge.io/docs/trust-badge.html
|
|
186
224
|
- Python SDK: `pip install complyedge`
|
|
187
225
|
|
|
188
226
|
## License
|
package/dist/index.js
CHANGED
|
@@ -64,9 +64,9 @@ function resolveBaseUrl(opts) {
|
|
|
64
64
|
}
|
|
65
65
|
return REGION_BASE_URLS[chosen];
|
|
66
66
|
}
|
|
67
|
-
return REGION_BASE_URLS[regionFromApiKey(opts.apiKey) || "
|
|
67
|
+
return REGION_BASE_URLS[regionFromApiKey(opts.apiKey) || "eu"];
|
|
68
68
|
}
|
|
69
|
-
var SDK_VERSION = "0.2.
|
|
69
|
+
var SDK_VERSION = "0.2.5";
|
|
70
70
|
var ComplyEdgeClient = class {
|
|
71
71
|
http;
|
|
72
72
|
agentId;
|
|
@@ -95,11 +95,15 @@ var ComplyEdgeClient = class {
|
|
|
95
95
|
* Calls POST /v1/check: the deterministic OPA hot path. A decision here is
|
|
96
96
|
* evaluated against the Rego rule bundle, returns article-cited violations,
|
|
97
97
|
* and is written to the Article 12 audit trail (`auditLogged`).
|
|
98
|
+
*
|
|
99
|
+
* With `context.sandbox: true` it calls POST /v1/sandbox/check instead:
|
|
100
|
+
* same verdict, nothing recorded (`auditLogged` false, `sandbox` true).
|
|
98
101
|
*/
|
|
99
102
|
async check(text, context) {
|
|
100
103
|
const start = Date.now();
|
|
101
104
|
const jurisdiction = context?.jurisdiction || this.jurisdiction || "EU";
|
|
102
|
-
const
|
|
105
|
+
const path = context?.sandbox ? "/v1/sandbox/check" : "/v1/check";
|
|
106
|
+
const response = await this.http.post(path, {
|
|
103
107
|
text,
|
|
104
108
|
agent_id: context?.agentId || this.agentId,
|
|
105
109
|
jurisdiction,
|
|
@@ -133,6 +137,7 @@ var ComplyEdgeClient = class {
|
|
|
133
137
|
enginePath: data.engine_path || "opa",
|
|
134
138
|
opaLatencyMs: data.opa_latency_ms,
|
|
135
139
|
auditLogged: data.audit_logged === true,
|
|
140
|
+
sandbox: data.sandbox === true,
|
|
136
141
|
textHash: data.text_hash || "",
|
|
137
142
|
timestamp: data.timestamp,
|
|
138
143
|
jurisdiction,
|
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 {\n ComplyEdgeClient,\n REGION_BASE_URLS,\n regionFromApiKey,\n resolveBaseUrl,\n} from \"./client\";\nexport type { Region } 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\n// ComplyEdge runs one independent stack per region. A tenant lives in exactly\n// one of them, and its API keys are only valid there. The key itself says\n// which: `ce_` -> US (api.complyedge.io), `ce_eu_` -> EU (eu.api.complyedge.io).\nexport type Region = \"us\" | \"eu\";\n\nexport const REGION_BASE_URLS: Record<Region, string> = {\n us: \"https://api.complyedge.io\",\n eu: \"https://eu.api.complyedge.io\",\n};\n\n/** Region an API key was issued in, read from its prefix; undefined if unknown. */\nexport function regionFromApiKey(apiKey?: string): Region | undefined {\n if (!apiKey) return undefined;\n // Longest prefix first: `ce_eu_` also starts with `ce_`.\n if (apiKey.startsWith(\"ce_eu_\")) return \"eu\";\n if (apiKey.startsWith(\"ce_\")) return \"us\";\n return undefined;\n}\n\n/**\n * Pick the API host. Precedence, highest first:\n * 1. `baseUrl` option\n * 2. `COMPLYEDGE_API_URL` environment variable\n * 3. `region` option, else `COMPLYEDGE_REGION` environment variable\n * 4. the region encoded in the API key prefix (`ce_eu_` -> EU)\n * 5. US\n */\nexport function resolveBaseUrl(opts: {\n apiKey?: string;\n baseUrl?: string;\n region?: Region;\n}): string {\n if (opts.baseUrl) return opts.baseUrl.replace(/\\/+$/, \"\");\n const envUrl = process.env.COMPLYEDGE_API_URL;\n if (envUrl) return envUrl.replace(/\\/+$/, \"\");\n const chosen = (opts.region || process.env.COMPLYEDGE_REGION || \"\").trim().toLowerCase();\n if (chosen) {\n if (!(chosen in REGION_BASE_URLS)) {\n throw new Error(\n `Unknown ComplyEdge region \"${chosen}\"; expected one of ${Object.keys(REGION_BASE_URLS).join(\", \")}`\n );\n }\n return REGION_BASE_URLS[chosen as Region];\n }\n return REGION_BASE_URLS[regionFromApiKey(opts.apiKey) || \"us\"];\n}\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.4\";\n\nexport class ComplyEdgeClient {\n private http: AxiosInstance;\n private agentId: string;\n private jurisdiction?: string;\n\n constructor(config: ComplyEdgeConfig) {\n const baseUrl = resolveBaseUrl({\n apiKey: config.apiKey,\n baseUrl: config.baseUrl,\n region: config.region,\n });\n\n this.agentId = config.agentId || \"default\";\n this.jurisdiction = config.jurisdiction;\n\n this.http = axios.create({\n baseURL: baseUrl,\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 // Missing `allowed` is a block. `!== false` treated a truncated 200 as a pass.\n const allowed = data.allowed === true;\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 === true,\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"]}
|
|
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 {\n ComplyEdgeClient,\n REGION_BASE_URLS,\n regionFromApiKey,\n resolveBaseUrl,\n} from \"./client\";\nexport type { Region } 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\n// ComplyEdge runs one independent stack per region. A tenant lives in exactly\n// one of them, and its API keys are only valid there. The key itself says\n// which: `ce_eu_` -> EU (eu.api.complyedge.io, every new account), `ce_` -> US\n// (api.complyedge.io, the dormant stack; older keys only). No key or an\n// unknown prefix goes to EU: US creates no new tenants (2026-09-25).\nexport type Region = \"us\" | \"eu\";\n\nexport const REGION_BASE_URLS: Record<Region, string> = {\n us: \"https://api.complyedge.io\",\n eu: \"https://eu.api.complyedge.io\",\n};\n\n/** Region an API key was issued in, read from its prefix; undefined if unknown. */\nexport function regionFromApiKey(apiKey?: string): Region | undefined {\n if (!apiKey) return undefined;\n // Longest prefix first: `ce_eu_` also starts with `ce_`.\n if (apiKey.startsWith(\"ce_eu_\")) return \"eu\";\n if (apiKey.startsWith(\"ce_\")) return \"us\";\n return undefined;\n}\n\n/**\n * Pick the API host. Precedence, highest first:\n * 1. `baseUrl` option\n * 2. `COMPLYEDGE_API_URL` environment variable\n * 3. `region` option, else `COMPLYEDGE_REGION` environment variable\n * 4. the region encoded in the API key prefix (`ce_eu_` -> EU, `ce_` -> US)\n * 5. EU\n */\nexport function resolveBaseUrl(opts: {\n apiKey?: string;\n baseUrl?: string;\n region?: Region;\n}): string {\n if (opts.baseUrl) return opts.baseUrl.replace(/\\/+$/, \"\");\n const envUrl = process.env.COMPLYEDGE_API_URL;\n if (envUrl) return envUrl.replace(/\\/+$/, \"\");\n const chosen = (opts.region || process.env.COMPLYEDGE_REGION || \"\").trim().toLowerCase();\n if (chosen) {\n if (!(chosen in REGION_BASE_URLS)) {\n throw new Error(\n `Unknown ComplyEdge region \"${chosen}\"; expected one of ${Object.keys(REGION_BASE_URLS).join(\", \")}`\n );\n }\n return REGION_BASE_URLS[chosen as Region];\n }\n return REGION_BASE_URLS[regionFromApiKey(opts.apiKey) || \"eu\"];\n}\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.5\";\n\nexport class ComplyEdgeClient {\n private http: AxiosInstance;\n private agentId: string;\n private jurisdiction?: string;\n\n constructor(config: ComplyEdgeConfig) {\n const baseUrl = resolveBaseUrl({\n apiKey: config.apiKey,\n baseUrl: config.baseUrl,\n region: config.region,\n });\n\n this.agentId = config.agentId || \"default\";\n this.jurisdiction = config.jurisdiction;\n\n this.http = axios.create({\n baseURL: baseUrl,\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 * With `context.sandbox: true` it calls POST /v1/sandbox/check instead:\n * same verdict, nothing recorded (`auditLogged` false, `sandbox` true).\n */\n async check(text: string, context?: ComplianceContext): Promise<ComplianceResult> {\n const start = Date.now();\n const jurisdiction = context?.jurisdiction || this.jurisdiction || \"EU\";\n const path = context?.sandbox ? \"/v1/sandbox/check\" : \"/v1/check\";\n\n const response = await this.http.post(path, {\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 // Missing `allowed` is a block. `!== false` treated a truncated 200 as a pass.\n const allowed = data.allowed === true;\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 === true,\n sandbox: data.sandbox === true,\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
|
@@ -28,9 +28,9 @@ function resolveBaseUrl(opts) {
|
|
|
28
28
|
}
|
|
29
29
|
return REGION_BASE_URLS[chosen];
|
|
30
30
|
}
|
|
31
|
-
return REGION_BASE_URLS[regionFromApiKey(opts.apiKey) || "
|
|
31
|
+
return REGION_BASE_URLS[regionFromApiKey(opts.apiKey) || "eu"];
|
|
32
32
|
}
|
|
33
|
-
var SDK_VERSION = "0.2.
|
|
33
|
+
var SDK_VERSION = "0.2.5";
|
|
34
34
|
var ComplyEdgeClient = class {
|
|
35
35
|
http;
|
|
36
36
|
agentId;
|
|
@@ -59,11 +59,15 @@ var ComplyEdgeClient = class {
|
|
|
59
59
|
* Calls POST /v1/check: the deterministic OPA hot path. A decision here is
|
|
60
60
|
* evaluated against the Rego rule bundle, returns article-cited violations,
|
|
61
61
|
* and is written to the Article 12 audit trail (`auditLogged`).
|
|
62
|
+
*
|
|
63
|
+
* With `context.sandbox: true` it calls POST /v1/sandbox/check instead:
|
|
64
|
+
* same verdict, nothing recorded (`auditLogged` false, `sandbox` true).
|
|
62
65
|
*/
|
|
63
66
|
async check(text, context) {
|
|
64
67
|
const start = Date.now();
|
|
65
68
|
const jurisdiction = context?.jurisdiction || this.jurisdiction || "EU";
|
|
66
|
-
const
|
|
69
|
+
const path = context?.sandbox ? "/v1/sandbox/check" : "/v1/check";
|
|
70
|
+
const response = await this.http.post(path, {
|
|
67
71
|
text,
|
|
68
72
|
agent_id: context?.agentId || this.agentId,
|
|
69
73
|
jurisdiction,
|
|
@@ -97,6 +101,7 @@ var ComplyEdgeClient = class {
|
|
|
97
101
|
enginePath: data.engine_path || "opa",
|
|
98
102
|
opaLatencyMs: data.opa_latency_ms,
|
|
99
103
|
auditLogged: data.audit_logged === true,
|
|
104
|
+
sandbox: data.sandbox === true,
|
|
100
105
|
textHash: data.text_hash || "",
|
|
101
106
|
timestamp: data.timestamp,
|
|
102
107
|
jurisdiction,
|
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\n// ComplyEdge runs one independent stack per region. A tenant lives in exactly\n// one of them, and its API keys are only valid there. The key itself says\n// which: `ce_` -> US (api.complyedge.io), `ce_eu_` -> EU (eu.api.complyedge.io).\nexport type Region = \"us\" | \"eu\";\n\nexport const REGION_BASE_URLS: Record<Region, string> = {\n us: \"https://api.complyedge.io\",\n eu: \"https://eu.api.complyedge.io\",\n};\n\n/** Region an API key was issued in, read from its prefix; undefined if unknown. */\nexport function regionFromApiKey(apiKey?: string): Region | undefined {\n if (!apiKey) return undefined;\n // Longest prefix first: `ce_eu_` also starts with `ce_`.\n if (apiKey.startsWith(\"ce_eu_\")) return \"eu\";\n if (apiKey.startsWith(\"ce_\")) return \"us\";\n return undefined;\n}\n\n/**\n * Pick the API host. Precedence, highest first:\n * 1. `baseUrl` option\n * 2. `COMPLYEDGE_API_URL` environment variable\n * 3. `region` option, else `COMPLYEDGE_REGION` environment variable\n * 4. the region encoded in the API key prefix (`ce_eu_` -> EU)\n * 5. US\n */\nexport function resolveBaseUrl(opts: {\n apiKey?: string;\n baseUrl?: string;\n region?: Region;\n}): string {\n if (opts.baseUrl) return opts.baseUrl.replace(/\\/+$/, \"\");\n const envUrl = process.env.COMPLYEDGE_API_URL;\n if (envUrl) return envUrl.replace(/\\/+$/, \"\");\n const chosen = (opts.region || process.env.COMPLYEDGE_REGION || \"\").trim().toLowerCase();\n if (chosen) {\n if (!(chosen in REGION_BASE_URLS)) {\n throw new Error(\n `Unknown ComplyEdge region \"${chosen}\"; expected one of ${Object.keys(REGION_BASE_URLS).join(\", \")}`\n );\n }\n return REGION_BASE_URLS[chosen as Region];\n }\n return REGION_BASE_URLS[regionFromApiKey(opts.apiKey) || \"us\"];\n}\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.4\";\n\nexport class ComplyEdgeClient {\n private http: AxiosInstance;\n private agentId: string;\n private jurisdiction?: string;\n\n constructor(config: ComplyEdgeConfig) {\n const baseUrl = resolveBaseUrl({\n apiKey: config.apiKey,\n baseUrl: config.baseUrl,\n region: config.region,\n });\n\n this.agentId = config.agentId || \"default\";\n this.jurisdiction = config.jurisdiction;\n\n this.http = axios.create({\n baseURL: baseUrl,\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 // Missing `allowed` is a block. `!== false` treated a truncated 200 as a pass.\n const allowed = data.allowed === true;\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 === true,\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":[]}
|
|
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\n// ComplyEdge runs one independent stack per region. A tenant lives in exactly\n// one of them, and its API keys are only valid there. The key itself says\n// which: `ce_eu_` -> EU (eu.api.complyedge.io, every new account), `ce_` -> US\n// (api.complyedge.io, the dormant stack; older keys only). No key or an\n// unknown prefix goes to EU: US creates no new tenants (2026-09-25).\nexport type Region = \"us\" | \"eu\";\n\nexport const REGION_BASE_URLS: Record<Region, string> = {\n us: \"https://api.complyedge.io\",\n eu: \"https://eu.api.complyedge.io\",\n};\n\n/** Region an API key was issued in, read from its prefix; undefined if unknown. */\nexport function regionFromApiKey(apiKey?: string): Region | undefined {\n if (!apiKey) return undefined;\n // Longest prefix first: `ce_eu_` also starts with `ce_`.\n if (apiKey.startsWith(\"ce_eu_\")) return \"eu\";\n if (apiKey.startsWith(\"ce_\")) return \"us\";\n return undefined;\n}\n\n/**\n * Pick the API host. Precedence, highest first:\n * 1. `baseUrl` option\n * 2. `COMPLYEDGE_API_URL` environment variable\n * 3. `region` option, else `COMPLYEDGE_REGION` environment variable\n * 4. the region encoded in the API key prefix (`ce_eu_` -> EU, `ce_` -> US)\n * 5. EU\n */\nexport function resolveBaseUrl(opts: {\n apiKey?: string;\n baseUrl?: string;\n region?: Region;\n}): string {\n if (opts.baseUrl) return opts.baseUrl.replace(/\\/+$/, \"\");\n const envUrl = process.env.COMPLYEDGE_API_URL;\n if (envUrl) return envUrl.replace(/\\/+$/, \"\");\n const chosen = (opts.region || process.env.COMPLYEDGE_REGION || \"\").trim().toLowerCase();\n if (chosen) {\n if (!(chosen in REGION_BASE_URLS)) {\n throw new Error(\n `Unknown ComplyEdge region \"${chosen}\"; expected one of ${Object.keys(REGION_BASE_URLS).join(\", \")}`\n );\n }\n return REGION_BASE_URLS[chosen as Region];\n }\n return REGION_BASE_URLS[regionFromApiKey(opts.apiKey) || \"eu\"];\n}\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.5\";\n\nexport class ComplyEdgeClient {\n private http: AxiosInstance;\n private agentId: string;\n private jurisdiction?: string;\n\n constructor(config: ComplyEdgeConfig) {\n const baseUrl = resolveBaseUrl({\n apiKey: config.apiKey,\n baseUrl: config.baseUrl,\n region: config.region,\n });\n\n this.agentId = config.agentId || \"default\";\n this.jurisdiction = config.jurisdiction;\n\n this.http = axios.create({\n baseURL: baseUrl,\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 * With `context.sandbox: true` it calls POST /v1/sandbox/check instead:\n * same verdict, nothing recorded (`auditLogged` false, `sandbox` true).\n */\n async check(text: string, context?: ComplianceContext): Promise<ComplianceResult> {\n const start = Date.now();\n const jurisdiction = context?.jurisdiction || this.jurisdiction || \"EU\";\n const path = context?.sandbox ? \"/v1/sandbox/check\" : \"/v1/check\";\n\n const response = await this.http.post(path, {\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 // Missing `allowed` is a block. `!== false` treated a truncated 200 as a pass.\n const allowed = data.allowed === true;\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 === true,\n sandbox: data.sandbox === true,\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":[]}
|
|
@@ -7,6 +7,12 @@ interface ComplianceContext {
|
|
|
7
7
|
jurisdiction?: string;
|
|
8
8
|
userRole?: string;
|
|
9
9
|
direction?: "prompt" | "output";
|
|
10
|
+
/**
|
|
11
|
+
* Evaluate without recording: POST /v1/sandbox/check. Same rules, same
|
|
12
|
+
* tenant settings, same verdict as /v1/check; no audit entry, no usage,
|
|
13
|
+
* no rate-limit count. For trying cases, never for production traffic.
|
|
14
|
+
*/
|
|
15
|
+
sandbox?: boolean;
|
|
10
16
|
}
|
|
11
17
|
type SeverityLevel = "critical" | "high" | "medium" | "low" | "informational";
|
|
12
18
|
interface ComplianceViolation {
|
|
@@ -33,6 +39,8 @@ interface ComplianceResult {
|
|
|
33
39
|
enginePath: string;
|
|
34
40
|
opaLatencyMs?: number;
|
|
35
41
|
auditLogged: boolean;
|
|
42
|
+
/** True when the decision came from /v1/sandbox/check. Never evidence. */
|
|
43
|
+
sandbox: boolean;
|
|
36
44
|
/**
|
|
37
45
|
* SHA-256 of the evaluated text as a bare hex digest, byte-identical to this
|
|
38
46
|
* event's Article 12 audit entry. Binds the decision to its exact input
|
|
@@ -120,8 +128,8 @@ declare function regionFromApiKey(apiKey?: string): Region | undefined;
|
|
|
120
128
|
* 1. `baseUrl` option
|
|
121
129
|
* 2. `COMPLYEDGE_API_URL` environment variable
|
|
122
130
|
* 3. `region` option, else `COMPLYEDGE_REGION` environment variable
|
|
123
|
-
* 4. the region encoded in the API key prefix (`ce_eu_` -> EU)
|
|
124
|
-
* 5.
|
|
131
|
+
* 4. the region encoded in the API key prefix (`ce_eu_` -> EU, `ce_` -> US)
|
|
132
|
+
* 5. EU
|
|
125
133
|
*/
|
|
126
134
|
declare function resolveBaseUrl(opts: {
|
|
127
135
|
apiKey?: string;
|
|
@@ -139,6 +147,9 @@ declare class ComplyEdgeClient {
|
|
|
139
147
|
* Calls POST /v1/check: the deterministic OPA hot path. A decision here is
|
|
140
148
|
* evaluated against the Rego rule bundle, returns article-cited violations,
|
|
141
149
|
* and is written to the Article 12 audit trail (`auditLogged`).
|
|
150
|
+
*
|
|
151
|
+
* With `context.sandbox: true` it calls POST /v1/sandbox/check instead:
|
|
152
|
+
* same verdict, nothing recorded (`auditLogged` false, `sandbox` true).
|
|
142
153
|
*/
|
|
143
154
|
check(text: string, context?: ComplianceContext): Promise<ComplianceResult>;
|
|
144
155
|
/**
|
|
@@ -7,6 +7,12 @@ interface ComplianceContext {
|
|
|
7
7
|
jurisdiction?: string;
|
|
8
8
|
userRole?: string;
|
|
9
9
|
direction?: "prompt" | "output";
|
|
10
|
+
/**
|
|
11
|
+
* Evaluate without recording: POST /v1/sandbox/check. Same rules, same
|
|
12
|
+
* tenant settings, same verdict as /v1/check; no audit entry, no usage,
|
|
13
|
+
* no rate-limit count. For trying cases, never for production traffic.
|
|
14
|
+
*/
|
|
15
|
+
sandbox?: boolean;
|
|
10
16
|
}
|
|
11
17
|
type SeverityLevel = "critical" | "high" | "medium" | "low" | "informational";
|
|
12
18
|
interface ComplianceViolation {
|
|
@@ -33,6 +39,8 @@ interface ComplianceResult {
|
|
|
33
39
|
enginePath: string;
|
|
34
40
|
opaLatencyMs?: number;
|
|
35
41
|
auditLogged: boolean;
|
|
42
|
+
/** True when the decision came from /v1/sandbox/check. Never evidence. */
|
|
43
|
+
sandbox: boolean;
|
|
36
44
|
/**
|
|
37
45
|
* SHA-256 of the evaluated text as a bare hex digest, byte-identical to this
|
|
38
46
|
* event's Article 12 audit entry. Binds the decision to its exact input
|
|
@@ -120,8 +128,8 @@ declare function regionFromApiKey(apiKey?: string): Region | undefined;
|
|
|
120
128
|
* 1. `baseUrl` option
|
|
121
129
|
* 2. `COMPLYEDGE_API_URL` environment variable
|
|
122
130
|
* 3. `region` option, else `COMPLYEDGE_REGION` environment variable
|
|
123
|
-
* 4. the region encoded in the API key prefix (`ce_eu_` -> EU)
|
|
124
|
-
* 5.
|
|
131
|
+
* 4. the region encoded in the API key prefix (`ce_eu_` -> EU, `ce_` -> US)
|
|
132
|
+
* 5. EU
|
|
125
133
|
*/
|
|
126
134
|
declare function resolveBaseUrl(opts: {
|
|
127
135
|
apiKey?: string;
|
|
@@ -139,6 +147,9 @@ declare class ComplyEdgeClient {
|
|
|
139
147
|
* Calls POST /v1/check: the deterministic OPA hot path. A decision here is
|
|
140
148
|
* evaluated against the Rego rule bundle, returns article-cited violations,
|
|
141
149
|
* and is written to the Article 12 audit trail (`auditLogged`).
|
|
150
|
+
*
|
|
151
|
+
* With `context.sandbox: true` it calls POST /v1/sandbox/check instead:
|
|
152
|
+
* same verdict, nothing recorded (`auditLogged` false, `sandbox` true).
|
|
142
153
|
*/
|
|
143
154
|
check(text: string, context?: ComplianceContext): Promise<ComplianceResult>;
|
|
144
155
|
/**
|
package/package.json
CHANGED