@complyedge/sdk 0.2.3 → 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 CHANGED
@@ -14,6 +14,43 @@ numbers are deliberately not kept level between the two — they are separate
14
14
  implementations with different capability surfaces, so matching numbers would
15
15
  imply a parity that does not exist.
16
16
 
17
+ ## [Unreleased]
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
+
43
+ ## [0.2.4] - 2026-09-16
44
+
45
+ ### Added
46
+ - `region: "us" | "eu"` config option and `COMPLYEDGE_REGION` environment
47
+ variable. The client now picks the API host from the key prefix
48
+ (`ce_eu_` -> `https://eu.api.complyedge.io`, `ce_` -> `https://api.complyedge.io`)
49
+ when neither `baseUrl` nor `COMPLYEDGE_API_URL` is set. Exported helpers:
50
+ `resolveBaseUrl`, `regionFromApiKey`, `REGION_BASE_URLS`, type `Region`.
51
+ Precedence: `baseUrl` > `COMPLYEDGE_API_URL` > `region` / `COMPLYEDGE_REGION`
52
+ > key prefix > US. Existing configurations resolve exactly as before.
53
+
17
54
  ## [0.2.3] - 2026-08-20
18
55
 
19
56
  ### Added
@@ -23,11 +60,12 @@ imply a parity that does not exist.
23
60
  doctrine.
24
61
 
25
62
  ### Fixed
26
- - Release-path fact: publishes still use `NPM_TOKEN`. The 0.2.2 changelog
27
- entry that claimed OIDC Trusted Publishing described an attempt that was
28
- reverted (`repository.url` points at the public repo while workflows run in
29
- the private platform). Provenance remains unavailable until that layout
30
- changes (G1a).
63
+ - Release-path fact: publishes still authenticate with a long-lived npm
64
+ credential. The 0.2.2 changelog entry that claimed OIDC Trusted Publishing
65
+ described an attempt that was reverted — `repository.url` names the public
66
+ repository while the release workflow runs in the private platform one, and
67
+ npm matches those two when authenticating. Provenance stays unavailable
68
+ until that layout changes.
31
69
 
32
70
  ## [0.2.2] - 2026-08-20
33
71
 
@@ -66,6 +104,7 @@ imply a parity that does not exist.
66
104
 
67
105
  Released before this changelog existed. See the repository history.
68
106
 
107
+ [0.2.4]: https://www.npmjs.com/package/@complyedge/sdk/v/0.2.4
69
108
  [0.2.3]: https://www.npmjs.com/package/@complyedge/sdk/v/0.2.3
70
109
  [0.2.2]: https://www.npmjs.com/package/@complyedge/sdk/v/0.2.2
71
110
  [0.2.1]: https://www.npmjs.com/package/@complyedge/sdk/v/0.2.1
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
@@ -73,10 +110,23 @@ const ce = new ComplyEdgeClient({
73
110
  jurisdiction: "EU", // default: "EU"
74
111
  agentId: "support-bot", // default: "default"
75
112
  timeout: 30_000, // ms, default: 30_000
76
- baseUrl: "https://api.complyedge.io", // or COMPLYEDGE_API_URL
113
+ region: "eu", // "us" | "eu"; default: read from the key prefix (ce_eu_ -> eu)
114
+ baseUrl: "https://eu.api.complyedge.io", // or COMPLYEDGE_API_URL; overrides region
77
115
  });
78
116
  ```
79
117
 
118
+ ### Regions
119
+
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
123
+ its API keys live in exactly one of them. Keys issued by the EU stack start
124
+ with `ce_eu_`, US keys with `ce_`; the client reads the prefix and picks the
125
+ host, so `region` is only needed to override it. Precedence, highest first:
126
+ `baseUrl` / `COMPLYEDGE_API_URL`, then `region` / `COMPLYEDGE_REGION`, then
127
+ the key prefix, then EU. A key presented to the other region's stack is
128
+ refused with `401 {"error":"wrong_region","use":"<host>"}` before any lookup.
129
+
80
130
  ## `check(text, context?)`
81
131
 
82
132
  Calls `POST /v1/check`, the deterministic OPA hot path. Returns:
@@ -170,7 +220,7 @@ Network and HTTP failures surface as `AxiosError`. The middleware throws
170
220
  - Quick start: https://www.complyedge.io/docs/quick-start.html
171
221
  - API reference: https://www.complyedge.io/docs/api-reference.html
172
222
  - Browser playground: https://www.complyedge.io/docs/playground.html
173
- - Trust badge setup: https://www.complyedge.io/docs/trust-badge.html
223
+ - Enforcement Seal embed: https://www.complyedge.io/docs/trust-badge.html
174
224
  - Python SDK: `pip install complyedge`
175
225
 
176
226
  ## License
package/dist/index.d.mts CHANGED
@@ -1 +1 @@
1
- export { b as ComplianceContext, ComplianceError, c as ComplianceResult, d as ComplianceViolation, C as ComplyEdgeClient, a as ComplyEdgeConfig, P as PreDeploymentInput, g as PreDeploymentResult, f as SensitivityDetection, e as SensitivityResult, S as SeverityLevel, withCompliance } from './openai-middleware.mjs';
1
+ export { d as ComplianceContext, ComplianceError, e as ComplianceResult, f as ComplianceViolation, C as ComplyEdgeClient, c as ComplyEdgeConfig, P as PreDeploymentInput, i as PreDeploymentResult, R as REGION_BASE_URLS, b as Region, h as SensitivityDetection, g as SensitivityResult, S as SeverityLevel, r as regionFromApiKey, a as resolveBaseUrl, withCompliance } from './openai-middleware.mjs';
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { b as ComplianceContext, ComplianceError, c as ComplianceResult, d as ComplianceViolation, C as ComplyEdgeClient, a as ComplyEdgeConfig, P as PreDeploymentInput, g as PreDeploymentResult, f as SensitivityDetection, e as SensitivityResult, S as SeverityLevel, withCompliance } from './openai-middleware.js';
1
+ export { d as ComplianceContext, ComplianceError, e as ComplianceResult, f as ComplianceViolation, C as ComplyEdgeClient, c as ComplyEdgeConfig, P as PreDeploymentInput, i as PreDeploymentResult, R as REGION_BASE_URLS, b as Region, h as SensitivityDetection, g as SensitivityResult, S as SeverityLevel, r as regionFromApiKey, a as resolveBaseUrl, withCompliance } from './openai-middleware.js';
package/dist/index.js CHANGED
@@ -32,24 +32,55 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  ComplianceError: () => ComplianceError,
34
34
  ComplyEdgeClient: () => ComplyEdgeClient,
35
+ REGION_BASE_URLS: () => REGION_BASE_URLS,
36
+ regionFromApiKey: () => regionFromApiKey,
37
+ resolveBaseUrl: () => resolveBaseUrl,
35
38
  withCompliance: () => withCompliance
36
39
  });
37
40
  module.exports = __toCommonJS(src_exports);
38
41
 
39
42
  // src/client.ts
40
43
  var import_axios = __toESM(require("axios"));
41
- var DEFAULT_BASE_URL = "https://api.complyedge.io";
42
- var SDK_VERSION = "0.2.0";
44
+ var REGION_BASE_URLS = {
45
+ us: "https://api.complyedge.io",
46
+ eu: "https://eu.api.complyedge.io"
47
+ };
48
+ function regionFromApiKey(apiKey) {
49
+ if (!apiKey) return void 0;
50
+ if (apiKey.startsWith("ce_eu_")) return "eu";
51
+ if (apiKey.startsWith("ce_")) return "us";
52
+ return void 0;
53
+ }
54
+ function resolveBaseUrl(opts) {
55
+ if (opts.baseUrl) return opts.baseUrl.replace(/\/+$/, "");
56
+ const envUrl = process.env.COMPLYEDGE_API_URL;
57
+ if (envUrl) return envUrl.replace(/\/+$/, "");
58
+ const chosen = (opts.region || process.env.COMPLYEDGE_REGION || "").trim().toLowerCase();
59
+ if (chosen) {
60
+ if (!(chosen in REGION_BASE_URLS)) {
61
+ throw new Error(
62
+ `Unknown ComplyEdge region "${chosen}"; expected one of ${Object.keys(REGION_BASE_URLS).join(", ")}`
63
+ );
64
+ }
65
+ return REGION_BASE_URLS[chosen];
66
+ }
67
+ return REGION_BASE_URLS[regionFromApiKey(opts.apiKey) || "eu"];
68
+ }
69
+ var SDK_VERSION = "0.2.5";
43
70
  var ComplyEdgeClient = class {
44
71
  http;
45
72
  agentId;
46
73
  jurisdiction;
47
74
  constructor(config) {
48
- const baseUrl = config.baseUrl || process.env.COMPLYEDGE_API_URL || DEFAULT_BASE_URL;
75
+ const baseUrl = resolveBaseUrl({
76
+ apiKey: config.apiKey,
77
+ baseUrl: config.baseUrl,
78
+ region: config.region
79
+ });
49
80
  this.agentId = config.agentId || "default";
50
81
  this.jurisdiction = config.jurisdiction;
51
82
  this.http = import_axios.default.create({
52
- baseURL: baseUrl.replace(/\/+$/, ""),
83
+ baseURL: baseUrl,
53
84
  timeout: config.timeout || 3e4,
54
85
  headers: {
55
86
  Authorization: `Bearer ${config.apiKey}`,
@@ -64,11 +95,15 @@ var ComplyEdgeClient = class {
64
95
  * Calls POST /v1/check: the deterministic OPA hot path. A decision here is
65
96
  * evaluated against the Rego rule bundle, returns article-cited violations,
66
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).
67
101
  */
68
102
  async check(text, context) {
69
103
  const start = Date.now();
70
104
  const jurisdiction = context?.jurisdiction || this.jurisdiction || "EU";
71
- const response = await this.http.post("/v1/check", {
105
+ const path = context?.sandbox ? "/v1/sandbox/check" : "/v1/check";
106
+ const response = await this.http.post(path, {
72
107
  text,
73
108
  agent_id: context?.agentId || this.agentId,
74
109
  jurisdiction,
@@ -83,7 +118,7 @@ var ComplyEdgeClient = class {
83
118
  });
84
119
  const data = response.data;
85
120
  const processingTimeMs = Date.now() - start;
86
- const allowed = data.allowed !== false;
121
+ const allowed = data.allowed === true;
87
122
  return {
88
123
  eventId: data.event_id || "",
89
124
  allowed,
@@ -101,7 +136,8 @@ var ComplyEdgeClient = class {
101
136
  evaluatedRules: data.evaluated_rules || [],
102
137
  enginePath: data.engine_path || "opa",
103
138
  opaLatencyMs: data.opa_latency_ms,
104
- auditLogged: data.audit_logged !== false,
139
+ auditLogged: data.audit_logged === true,
140
+ sandbox: data.sandbox === true,
105
141
  textHash: data.text_hash || "",
106
142
  timestamp: data.timestamp,
107
143
  jurisdiction,
@@ -264,6 +300,9 @@ function extractCompletionText(response) {
264
300
  0 && (module.exports = {
265
301
  ComplianceError,
266
302
  ComplyEdgeClient,
303
+ REGION_BASE_URLS,
304
+ regionFromApiKey,
305
+ resolveBaseUrl,
267
306
  withCompliance
268
307
  });
269
308
  //# sourceMappingURL=index.js.map
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\";\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"]}
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
@@ -5,18 +5,46 @@ import {
5
5
 
6
6
  // src/client.ts
7
7
  import axios from "axios";
8
- var DEFAULT_BASE_URL = "https://api.complyedge.io";
9
- var SDK_VERSION = "0.2.0";
8
+ var REGION_BASE_URLS = {
9
+ us: "https://api.complyedge.io",
10
+ eu: "https://eu.api.complyedge.io"
11
+ };
12
+ function regionFromApiKey(apiKey) {
13
+ if (!apiKey) return void 0;
14
+ if (apiKey.startsWith("ce_eu_")) return "eu";
15
+ if (apiKey.startsWith("ce_")) return "us";
16
+ return void 0;
17
+ }
18
+ function resolveBaseUrl(opts) {
19
+ if (opts.baseUrl) return opts.baseUrl.replace(/\/+$/, "");
20
+ const envUrl = process.env.COMPLYEDGE_API_URL;
21
+ if (envUrl) return envUrl.replace(/\/+$/, "");
22
+ const chosen = (opts.region || process.env.COMPLYEDGE_REGION || "").trim().toLowerCase();
23
+ if (chosen) {
24
+ if (!(chosen in REGION_BASE_URLS)) {
25
+ throw new Error(
26
+ `Unknown ComplyEdge region "${chosen}"; expected one of ${Object.keys(REGION_BASE_URLS).join(", ")}`
27
+ );
28
+ }
29
+ return REGION_BASE_URLS[chosen];
30
+ }
31
+ return REGION_BASE_URLS[regionFromApiKey(opts.apiKey) || "eu"];
32
+ }
33
+ var SDK_VERSION = "0.2.5";
10
34
  var ComplyEdgeClient = class {
11
35
  http;
12
36
  agentId;
13
37
  jurisdiction;
14
38
  constructor(config) {
15
- const baseUrl = config.baseUrl || process.env.COMPLYEDGE_API_URL || DEFAULT_BASE_URL;
39
+ const baseUrl = resolveBaseUrl({
40
+ apiKey: config.apiKey,
41
+ baseUrl: config.baseUrl,
42
+ region: config.region
43
+ });
16
44
  this.agentId = config.agentId || "default";
17
45
  this.jurisdiction = config.jurisdiction;
18
46
  this.http = axios.create({
19
- baseURL: baseUrl.replace(/\/+$/, ""),
47
+ baseURL: baseUrl,
20
48
  timeout: config.timeout || 3e4,
21
49
  headers: {
22
50
  Authorization: `Bearer ${config.apiKey}`,
@@ -31,11 +59,15 @@ var ComplyEdgeClient = class {
31
59
  * Calls POST /v1/check: the deterministic OPA hot path. A decision here is
32
60
  * evaluated against the Rego rule bundle, returns article-cited violations,
33
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).
34
65
  */
35
66
  async check(text, context) {
36
67
  const start = Date.now();
37
68
  const jurisdiction = context?.jurisdiction || this.jurisdiction || "EU";
38
- const response = await this.http.post("/v1/check", {
69
+ const path = context?.sandbox ? "/v1/sandbox/check" : "/v1/check";
70
+ const response = await this.http.post(path, {
39
71
  text,
40
72
  agent_id: context?.agentId || this.agentId,
41
73
  jurisdiction,
@@ -50,7 +82,7 @@ var ComplyEdgeClient = class {
50
82
  });
51
83
  const data = response.data;
52
84
  const processingTimeMs = Date.now() - start;
53
- const allowed = data.allowed !== false;
85
+ const allowed = data.allowed === true;
54
86
  return {
55
87
  eventId: data.event_id || "",
56
88
  allowed,
@@ -68,7 +100,8 @@ var ComplyEdgeClient = class {
68
100
  evaluatedRules: data.evaluated_rules || [],
69
101
  enginePath: data.engine_path || "opa",
70
102
  opaLatencyMs: data.opa_latency_ms,
71
- auditLogged: data.audit_logged !== false,
103
+ auditLogged: data.audit_logged === true,
104
+ sandbox: data.sandbox === true,
72
105
  textHash: data.text_hash || "",
73
106
  timestamp: data.timestamp,
74
107
  jurisdiction,
@@ -147,6 +180,9 @@ var ComplyEdgeClient = class {
147
180
  export {
148
181
  ComplianceError,
149
182
  ComplyEdgeClient,
183
+ REGION_BASE_URLS,
184
+ regionFromApiKey,
185
+ resolveBaseUrl,
150
186
  withCompliance
151
187
  };
152
188
  //# sourceMappingURL=index.mjs.map
@@ -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\";\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":[]}
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
@@ -95,7 +103,13 @@ interface PreDeploymentResult {
95
103
  }
96
104
  interface ComplyEdgeConfig {
97
105
  apiKey: string;
106
+ /** Overrides region and key inference. Also settable via COMPLYEDGE_API_URL. */
98
107
  baseUrl?: string;
108
+ /**
109
+ * "us" | "eu". Defaults to the region encoded in the API key prefix
110
+ * (`ce_eu_` -> EU), else US. Also settable via COMPLYEDGE_REGION.
111
+ */
112
+ region?: "us" | "eu";
99
113
  agentId?: string;
100
114
  jurisdiction?: string;
101
115
  timeout?: number;
@@ -105,6 +119,23 @@ interface ComplyEdgeConfig {
105
119
  * ComplyEdge TypeScript SDK — Client
106
120
  */
107
121
 
122
+ type Region = "us" | "eu";
123
+ declare const REGION_BASE_URLS: Record<Region, string>;
124
+ /** Region an API key was issued in, read from its prefix; undefined if unknown. */
125
+ declare function regionFromApiKey(apiKey?: string): Region | undefined;
126
+ /**
127
+ * Pick the API host. Precedence, highest first:
128
+ * 1. `baseUrl` option
129
+ * 2. `COMPLYEDGE_API_URL` environment variable
130
+ * 3. `region` option, else `COMPLYEDGE_REGION` environment variable
131
+ * 4. the region encoded in the API key prefix (`ce_eu_` -> EU, `ce_` -> US)
132
+ * 5. EU
133
+ */
134
+ declare function resolveBaseUrl(opts: {
135
+ apiKey?: string;
136
+ baseUrl?: string;
137
+ region?: Region;
138
+ }): string;
108
139
  declare class ComplyEdgeClient {
109
140
  private http;
110
141
  private agentId;
@@ -116,6 +147,9 @@ declare class ComplyEdgeClient {
116
147
  * Calls POST /v1/check: the deterministic OPA hot path. A decision here is
117
148
  * evaluated against the Rego rule bundle, returns article-cited violations,
118
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).
119
153
  */
120
154
  check(text: string, context?: ComplianceContext): Promise<ComplianceResult>;
121
155
  /**
@@ -164,4 +198,4 @@ declare function withCompliance<T extends Record<string, unknown>>(openaiClient:
164
198
  blockOnViolation?: boolean;
165
199
  }): T;
166
200
 
167
- export { ComplyEdgeClient as C, ComplianceError, type PreDeploymentInput as P, type SeverityLevel as S, type ComplyEdgeConfig as a, type ComplianceContext as b, type ComplianceResult as c, type ComplianceViolation as d, type SensitivityResult as e, type SensitivityDetection as f, type PreDeploymentResult as g, withCompliance };
201
+ export { ComplyEdgeClient as C, ComplianceError, type PreDeploymentInput as P, REGION_BASE_URLS as R, type SeverityLevel as S, resolveBaseUrl as a, type Region as b, type ComplyEdgeConfig as c, type ComplianceContext as d, type ComplianceResult as e, type ComplianceViolation as f, type SensitivityResult as g, type SensitivityDetection as h, type PreDeploymentResult as i, regionFromApiKey as r, withCompliance };
@@ -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
@@ -95,7 +103,13 @@ interface PreDeploymentResult {
95
103
  }
96
104
  interface ComplyEdgeConfig {
97
105
  apiKey: string;
106
+ /** Overrides region and key inference. Also settable via COMPLYEDGE_API_URL. */
98
107
  baseUrl?: string;
108
+ /**
109
+ * "us" | "eu". Defaults to the region encoded in the API key prefix
110
+ * (`ce_eu_` -> EU), else US. Also settable via COMPLYEDGE_REGION.
111
+ */
112
+ region?: "us" | "eu";
99
113
  agentId?: string;
100
114
  jurisdiction?: string;
101
115
  timeout?: number;
@@ -105,6 +119,23 @@ interface ComplyEdgeConfig {
105
119
  * ComplyEdge TypeScript SDK — Client
106
120
  */
107
121
 
122
+ type Region = "us" | "eu";
123
+ declare const REGION_BASE_URLS: Record<Region, string>;
124
+ /** Region an API key was issued in, read from its prefix; undefined if unknown. */
125
+ declare function regionFromApiKey(apiKey?: string): Region | undefined;
126
+ /**
127
+ * Pick the API host. Precedence, highest first:
128
+ * 1. `baseUrl` option
129
+ * 2. `COMPLYEDGE_API_URL` environment variable
130
+ * 3. `region` option, else `COMPLYEDGE_REGION` environment variable
131
+ * 4. the region encoded in the API key prefix (`ce_eu_` -> EU, `ce_` -> US)
132
+ * 5. EU
133
+ */
134
+ declare function resolveBaseUrl(opts: {
135
+ apiKey?: string;
136
+ baseUrl?: string;
137
+ region?: Region;
138
+ }): string;
108
139
  declare class ComplyEdgeClient {
109
140
  private http;
110
141
  private agentId;
@@ -116,6 +147,9 @@ declare class ComplyEdgeClient {
116
147
  * Calls POST /v1/check: the deterministic OPA hot path. A decision here is
117
148
  * evaluated against the Rego rule bundle, returns article-cited violations,
118
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).
119
153
  */
120
154
  check(text: string, context?: ComplianceContext): Promise<ComplianceResult>;
121
155
  /**
@@ -164,4 +198,4 @@ declare function withCompliance<T extends Record<string, unknown>>(openaiClient:
164
198
  blockOnViolation?: boolean;
165
199
  }): T;
166
200
 
167
- export { ComplyEdgeClient as C, ComplianceError, type PreDeploymentInput as P, type SeverityLevel as S, type ComplyEdgeConfig as a, type ComplianceContext as b, type ComplianceResult as c, type ComplianceViolation as d, type SensitivityResult as e, type SensitivityDetection as f, type PreDeploymentResult as g, withCompliance };
201
+ export { ComplyEdgeClient as C, ComplianceError, type PreDeploymentInput as P, REGION_BASE_URLS as R, type SeverityLevel as S, resolveBaseUrl as a, type Region as b, type ComplyEdgeConfig as c, type ComplianceContext as d, type ComplianceResult as e, type ComplianceViolation as f, type SensitivityResult as g, type SensitivityDetection as h, type PreDeploymentResult as i, regionFromApiKey as r, withCompliance };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@complyedge/sdk",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
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"