@blekline/client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -0
- package/README.md +35 -0
- package/dist/BleklineClient.d.ts +39 -0
- package/dist/BleklineClient.d.ts.map +1 -0
- package/dist/BleklineClient.js +95 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +26 -0
- package/dist/headers.d.ts +15 -0
- package/dist/headers.d.ts.map +1 -0
- package/dist/headers.js +34 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/package.json +34 -0
- package/src/BleklineClient.ts +128 -0
- package/src/errors.ts +29 -0
- package/src/headers.ts +47 -0
- package/src/index.ts +3 -0
- package/tsconfig.json +14 -0
package/LICENSE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @blekline/client
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for the Blekline ingress control plane.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @blekline/client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Monorepo: `"@blekline/client": "workspace:*"`
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { BleklineClient } from "@blekline/client";
|
|
17
|
+
|
|
18
|
+
const client = new BleklineClient({
|
|
19
|
+
workspaceToken: process.env.BLEKLINE_WORKSPACE_TOKEN!,
|
|
20
|
+
metadata: { clientSurface: "sdk" },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const mask = await client.mask({ text: "Contact alice@corp.com", platform: "SDK" });
|
|
24
|
+
const tool = await client.enforceToolCall({
|
|
25
|
+
toolName: "run_terminal_cmd",
|
|
26
|
+
arguments: { command: "echo hello" },
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Methods
|
|
31
|
+
|
|
32
|
+
- `mask()` → `POST /api/mask`
|
|
33
|
+
- `emitEvent()` → `POST /api/events`
|
|
34
|
+
- `simulatePolicy()` → `POST /api/policy/simulate`
|
|
35
|
+
- `enforceToolCall()` → `POST /api/mcp/enforce-tool-call`
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { EnforceToolCallRequest, EnforceToolCallResult, EventIngest, MaskRequest, MaskResponse, PolicySimulateRequest } from "@blekline/contracts";
|
|
2
|
+
import { type ClientMetadata } from "./headers.js";
|
|
3
|
+
export type BleklineClientOptions = {
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
baseUrls?: string[];
|
|
6
|
+
workspaceToken: string;
|
|
7
|
+
workspaceId?: string;
|
|
8
|
+
bearer?: string;
|
|
9
|
+
metadata?: ClientMetadata;
|
|
10
|
+
fetchImpl?: typeof fetch;
|
|
11
|
+
};
|
|
12
|
+
export declare class BleklineClient {
|
|
13
|
+
private readonly baseUrls;
|
|
14
|
+
private readonly workspaceToken;
|
|
15
|
+
private readonly workspaceId?;
|
|
16
|
+
private readonly bearer?;
|
|
17
|
+
private readonly metadata?;
|
|
18
|
+
private readonly fetchImpl;
|
|
19
|
+
constructor(opts: BleklineClientOptions);
|
|
20
|
+
private request;
|
|
21
|
+
mask(input: MaskRequest & {
|
|
22
|
+
platform?: string;
|
|
23
|
+
}): Promise<MaskResponse>;
|
|
24
|
+
emitEvent(event: EventIngest): Promise<{
|
|
25
|
+
ok: true;
|
|
26
|
+
}>;
|
|
27
|
+
simulatePolicy(input: PolicySimulateRequest): Promise<{
|
|
28
|
+
ok: true;
|
|
29
|
+
simulation: {
|
|
30
|
+
platform: string;
|
|
31
|
+
risk: "low" | "medium" | "high";
|
|
32
|
+
action: string;
|
|
33
|
+
matchedKeywords: string[];
|
|
34
|
+
shieldEnabled: boolean;
|
|
35
|
+
};
|
|
36
|
+
}>;
|
|
37
|
+
enforceToolCall(input: EnforceToolCallRequest): Promise<EnforceToolCallResult>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=BleklineClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BleklineClient.d.ts","sourceRoot":"","sources":["../src/BleklineClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,WAAW,EACX,WAAW,EACX,YAAY,EACZ,qBAAqB,EACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAmC,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAGpF,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,CAAC;AAEF,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAiB;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;gBAE7B,IAAI,EAAE,qBAAqB;YASzB,OAAO;IAsBf,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAiBvE,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC;IAcpD,cAAc,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC;QAC1D,EAAE,EAAE,IAAI,CAAC;QACT,UAAU,EAAE;YACV,QAAQ,EAAE,MAAM,CAAC;YACjB,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;YAChC,MAAM,EAAE,MAAM,CAAC;YACf,eAAe,EAAE,MAAM,EAAE,CAAC;YAC1B,aAAa,EAAE,OAAO,CAAC;SACxB,CAAC;KACH,CAAC;IAcI,eAAe,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAarF"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { buildHeaders, DEFAULT_BASE_URLS } from "./headers.js";
|
|
2
|
+
import { BleklineApiError, parseJsonError } from "./errors.js";
|
|
3
|
+
export class BleklineClient {
|
|
4
|
+
baseUrls;
|
|
5
|
+
workspaceToken;
|
|
6
|
+
workspaceId;
|
|
7
|
+
bearer;
|
|
8
|
+
metadata;
|
|
9
|
+
fetchImpl;
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
this.baseUrls = opts.baseUrls ?? (opts.baseUrl ? [opts.baseUrl] : DEFAULT_BASE_URLS);
|
|
12
|
+
this.workspaceToken = opts.workspaceToken;
|
|
13
|
+
this.workspaceId = opts.workspaceId;
|
|
14
|
+
this.bearer = opts.bearer;
|
|
15
|
+
this.metadata = opts.metadata;
|
|
16
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
17
|
+
}
|
|
18
|
+
async request(path, init) {
|
|
19
|
+
const terminal = new Set([400, 401, 403, 404, 422, 429, 500, 502, 503, 504]);
|
|
20
|
+
let lastError;
|
|
21
|
+
for (const base of this.baseUrls) {
|
|
22
|
+
const url = `${base.replace(/\/$/, "")}${path}`;
|
|
23
|
+
try {
|
|
24
|
+
const res = await this.fetchImpl(url, init);
|
|
25
|
+
if (res.ok)
|
|
26
|
+
return (await res.json());
|
|
27
|
+
if (terminal.has(res.status)) {
|
|
28
|
+
await parseJsonError(res);
|
|
29
|
+
}
|
|
30
|
+
lastError = new BleklineApiError(`HTTP ${res.status}`, { status: res.status });
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
lastError = err;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (lastError instanceof Error)
|
|
37
|
+
throw lastError;
|
|
38
|
+
throw new BleklineApiError("All base URLs failed", { status: 503 });
|
|
39
|
+
}
|
|
40
|
+
async mask(input) {
|
|
41
|
+
const headers = buildHeaders({
|
|
42
|
+
workspaceToken: this.workspaceToken,
|
|
43
|
+
workspaceId: this.workspaceId,
|
|
44
|
+
bearer: this.bearer,
|
|
45
|
+
metadata: this.metadata,
|
|
46
|
+
});
|
|
47
|
+
return this.request("/api/mask", {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers,
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
text: input.text,
|
|
52
|
+
platform: input.platform ?? "SDK",
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async emitEvent(event) {
|
|
57
|
+
const headers = buildHeaders({
|
|
58
|
+
workspaceToken: this.workspaceToken,
|
|
59
|
+
workspaceId: this.workspaceId,
|
|
60
|
+
bearer: this.bearer,
|
|
61
|
+
metadata: this.metadata,
|
|
62
|
+
});
|
|
63
|
+
return this.request("/api/events", {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers,
|
|
66
|
+
body: JSON.stringify(event),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async simulatePolicy(input) {
|
|
70
|
+
const headers = buildHeaders({
|
|
71
|
+
workspaceToken: this.workspaceToken,
|
|
72
|
+
workspaceId: this.workspaceId,
|
|
73
|
+
bearer: this.bearer,
|
|
74
|
+
metadata: this.metadata,
|
|
75
|
+
});
|
|
76
|
+
return this.request("/api/policy/simulate", {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers,
|
|
79
|
+
body: JSON.stringify(input),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async enforceToolCall(input) {
|
|
83
|
+
const headers = buildHeaders({
|
|
84
|
+
workspaceToken: this.workspaceToken,
|
|
85
|
+
workspaceId: this.workspaceId,
|
|
86
|
+
bearer: this.bearer,
|
|
87
|
+
metadata: this.metadata,
|
|
88
|
+
});
|
|
89
|
+
return this.request("/api/mcp/enforce-tool-call", {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers,
|
|
92
|
+
body: JSON.stringify(input),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MaskErrorCode } from "@blekline/contracts";
|
|
2
|
+
export declare class BleklineApiError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly code?: MaskErrorCode | string;
|
|
5
|
+
readonly requestId?: string;
|
|
6
|
+
constructor(message: string, opts: {
|
|
7
|
+
status: number;
|
|
8
|
+
code?: string;
|
|
9
|
+
requestId?: string;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export declare function parseJsonError(res: Response, requestId?: string): Promise<never>;
|
|
13
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;CAOzF;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAYtF"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export class BleklineApiError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
code;
|
|
4
|
+
requestId;
|
|
5
|
+
constructor(message, opts) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "BleklineApiError";
|
|
8
|
+
this.status = opts.status;
|
|
9
|
+
this.code = opts.code;
|
|
10
|
+
this.requestId = opts.requestId;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function parseJsonError(res, requestId) {
|
|
14
|
+
let payload = {};
|
|
15
|
+
try {
|
|
16
|
+
payload = (await res.json());
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
/* ignore */
|
|
20
|
+
}
|
|
21
|
+
throw new BleklineApiError(payload.error ?? `HTTP ${res.status}`, {
|
|
22
|
+
status: res.status,
|
|
23
|
+
code: payload.code,
|
|
24
|
+
requestId: payload.requestId ?? requestId ?? res.headers.get("x-request-id") ?? undefined,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ClientSurface, type ModelProvider } from "@blekline/contracts";
|
|
2
|
+
export type ClientMetadata = {
|
|
3
|
+
clientSurface?: ClientSurface;
|
|
4
|
+
modelProvider?: ModelProvider;
|
|
5
|
+
modelId?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function buildHeaders(input: {
|
|
8
|
+
workspaceToken: string;
|
|
9
|
+
workspaceId?: string;
|
|
10
|
+
bearer?: string;
|
|
11
|
+
requestId?: string;
|
|
12
|
+
metadata?: ClientMetadata;
|
|
13
|
+
}): Record<string, string>;
|
|
14
|
+
export declare const DEFAULT_BASE_URLS: string[];
|
|
15
|
+
//# sourceMappingURL=headers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAS/F,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkBzB;AAED,eAAO,MAAM,iBAAiB,UAI7B,CAAC"}
|
package/dist/headers.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { BLEKLINE_HEADERS } from "@blekline/contracts";
|
|
3
|
+
function newRequestId() {
|
|
4
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
5
|
+
return crypto.randomUUID();
|
|
6
|
+
}
|
|
7
|
+
return randomUUID();
|
|
8
|
+
}
|
|
9
|
+
export function buildHeaders(input) {
|
|
10
|
+
const headers = {
|
|
11
|
+
"Content-Type": "application/json",
|
|
12
|
+
[BLEKLINE_HEADERS.requestId]: input.requestId ?? newRequestId(),
|
|
13
|
+
[BLEKLINE_HEADERS.workspaceToken]: input.workspaceToken,
|
|
14
|
+
};
|
|
15
|
+
if (input.bearer)
|
|
16
|
+
headers.Authorization = `Bearer ${input.bearer}`;
|
|
17
|
+
if (input.workspaceId)
|
|
18
|
+
headers[BLEKLINE_HEADERS.workspaceId] = input.workspaceId;
|
|
19
|
+
if (input.metadata?.clientSurface) {
|
|
20
|
+
headers[BLEKLINE_HEADERS.clientSurface] = input.metadata.clientSurface;
|
|
21
|
+
}
|
|
22
|
+
if (input.metadata?.modelProvider) {
|
|
23
|
+
headers[BLEKLINE_HEADERS.modelProvider] = input.metadata.modelProvider;
|
|
24
|
+
}
|
|
25
|
+
if (input.metadata?.modelId) {
|
|
26
|
+
headers[BLEKLINE_HEADERS.modelId] = input.metadata.modelId;
|
|
27
|
+
}
|
|
28
|
+
return headers;
|
|
29
|
+
}
|
|
30
|
+
export const DEFAULT_BASE_URLS = [
|
|
31
|
+
"https://app.blekline.com",
|
|
32
|
+
"http://localhost:3000",
|
|
33
|
+
"http://127.0.0.1:3000",
|
|
34
|
+
];
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blekline/client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"private": false,
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/Blekline/blekline-oss.git",
|
|
9
|
+
"directory": "packages/client"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@blekline/contracts": "0.1.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20",
|
|
28
|
+
"typescript": "^5.7.3"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc",
|
|
32
|
+
"typecheck": "tsc --noEmit"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EnforceToolCallRequest,
|
|
3
|
+
EnforceToolCallResult,
|
|
4
|
+
EventIngest,
|
|
5
|
+
MaskRequest,
|
|
6
|
+
MaskResponse,
|
|
7
|
+
PolicySimulateRequest,
|
|
8
|
+
} from "@blekline/contracts";
|
|
9
|
+
import { buildHeaders, DEFAULT_BASE_URLS, type ClientMetadata } from "./headers.js";
|
|
10
|
+
import { BleklineApiError, parseJsonError } from "./errors.js";
|
|
11
|
+
|
|
12
|
+
export type BleklineClientOptions = {
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
baseUrls?: string[];
|
|
15
|
+
workspaceToken: string;
|
|
16
|
+
workspaceId?: string;
|
|
17
|
+
bearer?: string;
|
|
18
|
+
metadata?: ClientMetadata;
|
|
19
|
+
fetchImpl?: typeof fetch;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class BleklineClient {
|
|
23
|
+
private readonly baseUrls: string[];
|
|
24
|
+
private readonly workspaceToken: string;
|
|
25
|
+
private readonly workspaceId?: string;
|
|
26
|
+
private readonly bearer?: string;
|
|
27
|
+
private readonly metadata?: ClientMetadata;
|
|
28
|
+
private readonly fetchImpl: typeof fetch;
|
|
29
|
+
|
|
30
|
+
constructor(opts: BleklineClientOptions) {
|
|
31
|
+
this.baseUrls = opts.baseUrls ?? (opts.baseUrl ? [opts.baseUrl] : DEFAULT_BASE_URLS);
|
|
32
|
+
this.workspaceToken = opts.workspaceToken;
|
|
33
|
+
this.workspaceId = opts.workspaceId;
|
|
34
|
+
this.bearer = opts.bearer;
|
|
35
|
+
this.metadata = opts.metadata;
|
|
36
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private async request<T>(path: string, init: RequestInit): Promise<T> {
|
|
40
|
+
const terminal = new Set([400, 401, 403, 404, 422, 429, 500, 502, 503, 504]);
|
|
41
|
+
let lastError: unknown;
|
|
42
|
+
|
|
43
|
+
for (const base of this.baseUrls) {
|
|
44
|
+
const url = `${base.replace(/\/$/, "")}${path}`;
|
|
45
|
+
try {
|
|
46
|
+
const res = await this.fetchImpl(url, init);
|
|
47
|
+
if (res.ok) return (await res.json()) as T;
|
|
48
|
+
if (terminal.has(res.status)) {
|
|
49
|
+
await parseJsonError(res);
|
|
50
|
+
}
|
|
51
|
+
lastError = new BleklineApiError(`HTTP ${res.status}`, { status: res.status });
|
|
52
|
+
} catch (err) {
|
|
53
|
+
lastError = err;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (lastError instanceof Error) throw lastError;
|
|
58
|
+
throw new BleklineApiError("All base URLs failed", { status: 503 });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async mask(input: MaskRequest & { platform?: string }): Promise<MaskResponse> {
|
|
62
|
+
const headers = buildHeaders({
|
|
63
|
+
workspaceToken: this.workspaceToken,
|
|
64
|
+
workspaceId: this.workspaceId,
|
|
65
|
+
bearer: this.bearer,
|
|
66
|
+
metadata: this.metadata,
|
|
67
|
+
});
|
|
68
|
+
return this.request<MaskResponse>("/api/mask", {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers,
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
text: input.text,
|
|
73
|
+
platform: input.platform ?? "SDK",
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async emitEvent(event: EventIngest): Promise<{ ok: true }> {
|
|
79
|
+
const headers = buildHeaders({
|
|
80
|
+
workspaceToken: this.workspaceToken,
|
|
81
|
+
workspaceId: this.workspaceId,
|
|
82
|
+
bearer: this.bearer,
|
|
83
|
+
metadata: this.metadata,
|
|
84
|
+
});
|
|
85
|
+
return this.request<{ ok: true }>("/api/events", {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers,
|
|
88
|
+
body: JSON.stringify(event),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async simulatePolicy(input: PolicySimulateRequest): Promise<{
|
|
93
|
+
ok: true;
|
|
94
|
+
simulation: {
|
|
95
|
+
platform: string;
|
|
96
|
+
risk: "low" | "medium" | "high";
|
|
97
|
+
action: string;
|
|
98
|
+
matchedKeywords: string[];
|
|
99
|
+
shieldEnabled: boolean;
|
|
100
|
+
};
|
|
101
|
+
}> {
|
|
102
|
+
const headers = buildHeaders({
|
|
103
|
+
workspaceToken: this.workspaceToken,
|
|
104
|
+
workspaceId: this.workspaceId,
|
|
105
|
+
bearer: this.bearer,
|
|
106
|
+
metadata: this.metadata,
|
|
107
|
+
});
|
|
108
|
+
return this.request("/api/policy/simulate", {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers,
|
|
111
|
+
body: JSON.stringify(input),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async enforceToolCall(input: EnforceToolCallRequest): Promise<EnforceToolCallResult> {
|
|
116
|
+
const headers = buildHeaders({
|
|
117
|
+
workspaceToken: this.workspaceToken,
|
|
118
|
+
workspaceId: this.workspaceId,
|
|
119
|
+
bearer: this.bearer,
|
|
120
|
+
metadata: this.metadata,
|
|
121
|
+
});
|
|
122
|
+
return this.request<EnforceToolCallResult>("/api/mcp/enforce-tool-call", {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers,
|
|
125
|
+
body: JSON.stringify(input),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { MaskErrorCode } from "@blekline/contracts";
|
|
2
|
+
|
|
3
|
+
export class BleklineApiError extends Error {
|
|
4
|
+
readonly status: number;
|
|
5
|
+
readonly code?: MaskErrorCode | string;
|
|
6
|
+
readonly requestId?: string;
|
|
7
|
+
|
|
8
|
+
constructor(message: string, opts: { status: number; code?: string; requestId?: string }) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "BleklineApiError";
|
|
11
|
+
this.status = opts.status;
|
|
12
|
+
this.code = opts.code;
|
|
13
|
+
this.requestId = opts.requestId;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function parseJsonError(res: Response, requestId?: string): Promise<never> {
|
|
18
|
+
let payload: { error?: string; code?: string; requestId?: string } = {};
|
|
19
|
+
try {
|
|
20
|
+
payload = (await res.json()) as typeof payload;
|
|
21
|
+
} catch {
|
|
22
|
+
/* ignore */
|
|
23
|
+
}
|
|
24
|
+
throw new BleklineApiError(payload.error ?? `HTTP ${res.status}`, {
|
|
25
|
+
status: res.status,
|
|
26
|
+
code: payload.code,
|
|
27
|
+
requestId: payload.requestId ?? requestId ?? res.headers.get("x-request-id") ?? undefined,
|
|
28
|
+
});
|
|
29
|
+
}
|
package/src/headers.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { BLEKLINE_HEADERS, type ClientSurface, type ModelProvider } from "@blekline/contracts";
|
|
3
|
+
|
|
4
|
+
function newRequestId(): string {
|
|
5
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
6
|
+
return crypto.randomUUID();
|
|
7
|
+
}
|
|
8
|
+
return randomUUID();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type ClientMetadata = {
|
|
12
|
+
clientSurface?: ClientSurface;
|
|
13
|
+
modelProvider?: ModelProvider;
|
|
14
|
+
modelId?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function buildHeaders(input: {
|
|
18
|
+
workspaceToken: string;
|
|
19
|
+
workspaceId?: string;
|
|
20
|
+
bearer?: string;
|
|
21
|
+
requestId?: string;
|
|
22
|
+
metadata?: ClientMetadata;
|
|
23
|
+
}): Record<string, string> {
|
|
24
|
+
const headers: Record<string, string> = {
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
[BLEKLINE_HEADERS.requestId]: input.requestId ?? newRequestId(),
|
|
27
|
+
[BLEKLINE_HEADERS.workspaceToken]: input.workspaceToken,
|
|
28
|
+
};
|
|
29
|
+
if (input.bearer) headers.Authorization = `Bearer ${input.bearer}`;
|
|
30
|
+
if (input.workspaceId) headers[BLEKLINE_HEADERS.workspaceId] = input.workspaceId;
|
|
31
|
+
if (input.metadata?.clientSurface) {
|
|
32
|
+
headers[BLEKLINE_HEADERS.clientSurface] = input.metadata.clientSurface;
|
|
33
|
+
}
|
|
34
|
+
if (input.metadata?.modelProvider) {
|
|
35
|
+
headers[BLEKLINE_HEADERS.modelProvider] = input.metadata.modelProvider;
|
|
36
|
+
}
|
|
37
|
+
if (input.metadata?.modelId) {
|
|
38
|
+
headers[BLEKLINE_HEADERS.modelId] = input.metadata.modelId;
|
|
39
|
+
}
|
|
40
|
+
return headers;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const DEFAULT_BASE_URLS = [
|
|
44
|
+
"https://app.blekline.com",
|
|
45
|
+
"http://localhost:3000",
|
|
46
|
+
"http://127.0.0.1:3000",
|
|
47
|
+
];
|
package/src/index.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"outDir": "dist",
|
|
9
|
+
"rootDir": "src",
|
|
10
|
+
"strict": true,
|
|
11
|
+
"skipLibCheck": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|