@clampd/sdk 0.4.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/README.md +139 -0
- package/dist/auth.d.ts +22 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +54 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +118 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +191 -0
- package/dist/client.js.map +1 -0
- package/dist/delegation.d.ts +31 -0
- package/dist/delegation.d.ts.map +1 -0
- package/dist/delegation.js +80 -0
- package/dist/delegation.js.map +1 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +472 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptor.d.ts +174 -0
- package/dist/interceptor.d.ts.map +1 -0
- package/dist/interceptor.js +173 -0
- package/dist/interceptor.js.map +1 -0
- package/dist/tool-verify.d.ts +99 -0
- package/dist/tool-verify.d.ts.map +1 -0
- package/dist/tool-verify.js +205 -0
- package/dist/tool-verify.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Clampd TypeScript SDK
|
|
2
|
+
|
|
3
|
+
Runtime security for AI agents. Guard every tool call — OpenAI, Anthropic, LangChain.js — in 1 line.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install clampd
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import clampd from "clampd";
|
|
15
|
+
import OpenAI from "openai";
|
|
16
|
+
|
|
17
|
+
// Configure once at startup
|
|
18
|
+
clampd.init({
|
|
19
|
+
agentId: "my-agent",
|
|
20
|
+
secret: "ags_...", // from dashboard → Agent → Secret
|
|
21
|
+
gatewayUrl: "http://localhost:8080",
|
|
22
|
+
apiKey: "ag_live_...",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Wrap your OpenAI client — done
|
|
26
|
+
const client = clampd.openai(new OpenAI());
|
|
27
|
+
|
|
28
|
+
// Use it exactly like before. Clampd intercepts every tool call.
|
|
29
|
+
const response = await client.chat.completions.create({
|
|
30
|
+
model: "gpt-4o",
|
|
31
|
+
messages: [{ role: "user", content: "Look up active users" }],
|
|
32
|
+
tools: [...],
|
|
33
|
+
});
|
|
34
|
+
// Dangerous tool calls → blocked before execution
|
|
35
|
+
// Safe tool calls → proceed normally
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
Three ways to configure (pick one):
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// Option 1: Environment variables
|
|
44
|
+
// CLAMPD_GATEWAY_URL=http://localhost:8080
|
|
45
|
+
// CLAMPD_API_KEY=ag_live_...
|
|
46
|
+
// CLAMPD_AGENT_ID=my-agent
|
|
47
|
+
// CLAMPD_AGENT_SECRET=ags_...
|
|
48
|
+
|
|
49
|
+
// Option 2: Global init (recommended)
|
|
50
|
+
clampd.init({ agentId: "my-agent", secret: "ags_...", gatewayUrl: "...", apiKey: "..." });
|
|
51
|
+
|
|
52
|
+
// Option 3: Inline per-call
|
|
53
|
+
const safeFn = clampd.guard(myFn, {
|
|
54
|
+
toolName: "db.query",
|
|
55
|
+
agentId: "my-agent",
|
|
56
|
+
secret: "ags_...",
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Anthropic / Claude
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import clampd from "clampd";
|
|
64
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
65
|
+
|
|
66
|
+
clampd.init({ agentId: "my-agent", secret: "ags_..." });
|
|
67
|
+
const client = clampd.anthropic(new Anthropic());
|
|
68
|
+
|
|
69
|
+
const response = await client.messages.create({
|
|
70
|
+
model: "claude-sonnet-4-20250514",
|
|
71
|
+
max_tokens: 1024,
|
|
72
|
+
messages: [{ role: "user", content: "..." }],
|
|
73
|
+
tools: [...],
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Direct Guard (any function)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import clampd from "clampd";
|
|
81
|
+
|
|
82
|
+
clampd.init({ agentId: "my-agent", secret: "ags_..." });
|
|
83
|
+
|
|
84
|
+
const safeQuery = clampd.guard(runQuery, {
|
|
85
|
+
toolName: "database.query",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// With response checking (opt-in)
|
|
89
|
+
const safeRead = clampd.guard(readFile, {
|
|
90
|
+
toolName: "file_read",
|
|
91
|
+
checkResponse: true,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await safeQuery("SELECT * FROM users"); // allowed
|
|
95
|
+
await safeQuery("DROP TABLE users"); // throws ClampdBlockedError
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Tool Definitions Wrapper
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import clampd from "clampd";
|
|
102
|
+
|
|
103
|
+
// Wrap OpenAI-style tool definitions
|
|
104
|
+
const safeTools = clampd.tools(myToolDefs, { agentId: "my-agent", secret: "ags_..." });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Error Handling
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { ClampdBlockedError } from "clampd";
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
await safeQuery("DROP TABLE users");
|
|
114
|
+
} catch (e) {
|
|
115
|
+
if (e instanceof ClampdBlockedError) {
|
|
116
|
+
console.log(`Blocked: ${e.message}`);
|
|
117
|
+
// e.response.risk_score, e.response.denial_reason
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## API Reference
|
|
123
|
+
|
|
124
|
+
| Function | Description |
|
|
125
|
+
|----------|-------------|
|
|
126
|
+
| `clampd.init(opts)` | Configure global client (once at startup) |
|
|
127
|
+
| `clampd.openai(client)` | Wrap OpenAI client |
|
|
128
|
+
| `clampd.anthropic(client)` | Wrap Anthropic client |
|
|
129
|
+
| `clampd.guard(fn, opts)` | Wrap any async function |
|
|
130
|
+
| `clampd.tools(defs, opts)` | Wrap OpenAI tool definitions |
|
|
131
|
+
|
|
132
|
+
## Requirements
|
|
133
|
+
|
|
134
|
+
- Node.js 18+
|
|
135
|
+
- A running [Clampd](https://clampd.dev) gateway
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
BUSL-1.1
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT helper for Clampd agent authentication.
|
|
3
|
+
*
|
|
4
|
+
* Uses HMAC-SHA256 when `secret` is provided or `CLAMPD_JWT_SECRET` env var
|
|
5
|
+
* is set. Throws an error if no signing secret is available — unsigned JWTs
|
|
6
|
+
* (alg: none) are NOT supported.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Create a JWT with `sub` = agentId.
|
|
10
|
+
*
|
|
11
|
+
* @param agentId Agent identifier (becomes the `sub` claim).
|
|
12
|
+
* @param opts.secret HMAC-SHA256 signing key. Falls back to
|
|
13
|
+
* `CLAMPD_JWT_SECRET` env var. If unset, creates an unsigned JWT.
|
|
14
|
+
* @param opts.scopes Optional scopes to include in the token.
|
|
15
|
+
* @param opts.ttlSeconds Token TTL in seconds (default: 3600).
|
|
16
|
+
*/
|
|
17
|
+
export declare function makeAgentJwt(agentId: string, opts?: {
|
|
18
|
+
secret?: string;
|
|
19
|
+
scopes?: string[];
|
|
20
|
+
ttlSeconds?: number;
|
|
21
|
+
}): string;
|
|
22
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACjE,MAAM,CAqCR"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT helper for Clampd agent authentication.
|
|
3
|
+
*
|
|
4
|
+
* Uses HMAC-SHA256 when `secret` is provided or `CLAMPD_JWT_SECRET` env var
|
|
5
|
+
* is set. Throws an error if no signing secret is available — unsigned JWTs
|
|
6
|
+
* (alg: none) are NOT supported.
|
|
7
|
+
*/
|
|
8
|
+
import { createHmac, createHash } from "node:crypto";
|
|
9
|
+
function b64url(data) {
|
|
10
|
+
const bytes = typeof data === "string" ? Buffer.from(data) : Buffer.from(data);
|
|
11
|
+
return bytes.toString("base64url");
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a JWT with `sub` = agentId.
|
|
15
|
+
*
|
|
16
|
+
* @param agentId Agent identifier (becomes the `sub` claim).
|
|
17
|
+
* @param opts.secret HMAC-SHA256 signing key. Falls back to
|
|
18
|
+
* `CLAMPD_JWT_SECRET` env var. If unset, creates an unsigned JWT.
|
|
19
|
+
* @param opts.scopes Optional scopes to include in the token.
|
|
20
|
+
* @param opts.ttlSeconds Token TTL in seconds (default: 3600).
|
|
21
|
+
*/
|
|
22
|
+
export function makeAgentJwt(agentId, opts) {
|
|
23
|
+
const rawSecret = opts?.secret || process.env.CLAMPD_AGENT_SECRET || process.env.CLAMPD_JWT_SECRET || "";
|
|
24
|
+
const ttl = opts?.ttlSeconds ?? 3600;
|
|
25
|
+
const now = Math.floor(Date.now() / 1000);
|
|
26
|
+
const payloadObj = {
|
|
27
|
+
sub: agentId,
|
|
28
|
+
iss: "clampd-sdk",
|
|
29
|
+
iat: now,
|
|
30
|
+
exp: now + ttl,
|
|
31
|
+
};
|
|
32
|
+
if (opts?.scopes?.length) {
|
|
33
|
+
payloadObj.scopes = opts.scopes;
|
|
34
|
+
}
|
|
35
|
+
if (rawSecret) {
|
|
36
|
+
// Derive signing key: if agent secret (ags_ prefix), hash with SHA-256
|
|
37
|
+
// so the HMAC key matches the credential_hash stored server-side.
|
|
38
|
+
// Otherwise use as-is (legacy global JWT_SECRET).
|
|
39
|
+
const signingKey = rawSecret.startsWith("ags_")
|
|
40
|
+
? createHash("sha256").update(rawSecret).digest("hex")
|
|
41
|
+
: rawSecret;
|
|
42
|
+
const header = b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
43
|
+
const payload = b64url(JSON.stringify(payloadObj));
|
|
44
|
+
const signingInput = `${header}.${payload}`;
|
|
45
|
+
const sig = createHmac("sha256", signingKey).update(signingInput).digest();
|
|
46
|
+
const signature = Buffer.from(sig).toString("base64url");
|
|
47
|
+
return `${header}.${payload}.${signature}`;
|
|
48
|
+
}
|
|
49
|
+
// Fail-closed: unsigned JWTs are not supported
|
|
50
|
+
throw new Error("[clampd] No signing secret available. " +
|
|
51
|
+
"Set CLAMPD_JWT_SECRET env var or pass { secret } to makeAgentJwt(). " +
|
|
52
|
+
"Unsigned JWTs (alg: none) are not supported — gateways require JWT_SECRET.");
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAErD,SAAS,MAAM,CAAC,IAAyB;IACvC,MAAM,KAAK,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/E,OAAO,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAe,EACf,IAAkE;IAElE,MAAM,SAAS,GAAG,IAAI,EAAE,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC;IACzG,MAAM,GAAG,GAAG,IAAI,EAAE,UAAU,IAAI,IAAI,CAAC;IACrC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAE1C,MAAM,UAAU,GAA4B;QAC1C,GAAG,EAAE,OAAO;QACZ,GAAG,EAAE,YAAY;QACjB,GAAG,EAAE,GAAG;QACR,GAAG,EAAE,GAAG,GAAG,GAAG;KACf,CAAC;IACF,IAAI,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QACzB,UAAU,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAClC,CAAC;IAED,IAAI,SAAS,EAAE,CAAC;QACd,uEAAuE;QACvE,kEAAkE;QAClE,kDAAkD;QAClD,MAAM,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC;YAC7C,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YACtD,CAAC,CAAC,SAAS,CAAC;QAEd,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QACpE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;QACnD,MAAM,YAAY,GAAG,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC;QAC5C,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,EAAE,CAAC;QAC3E,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QACzD,OAAO,GAAG,MAAM,IAAI,OAAO,IAAI,SAAS,EAAE,CAAC;IAC7C,CAAC;IAED,+CAA+C;IAC/C,MAAM,IAAI,KAAK,CACb,wCAAwC;QACxC,sEAAsE;QACtE,4EAA4E,CAC7E,CAAC;AACJ,CAAC"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClampdClient — thin wrapper around the ag-gateway HTTP API.
|
|
3
|
+
*/
|
|
4
|
+
export interface ProxyResponse {
|
|
5
|
+
request_id: string;
|
|
6
|
+
allowed: boolean;
|
|
7
|
+
/** Intent action: "pass", "flag", or "block". */
|
|
8
|
+
action?: string;
|
|
9
|
+
risk_score: number;
|
|
10
|
+
scope_granted?: string | null;
|
|
11
|
+
tool_response?: unknown | null;
|
|
12
|
+
denial_reason?: string | null;
|
|
13
|
+
/** Human-readable explanation of the risk assessment. */
|
|
14
|
+
reasoning?: string | null;
|
|
15
|
+
/** Rule IDs that matched this request (e.g. ["R001", "R005"]). */
|
|
16
|
+
matched_rules?: string[];
|
|
17
|
+
latency_ms: number;
|
|
18
|
+
degraded_stages: string[];
|
|
19
|
+
session_flags: string[];
|
|
20
|
+
/** HMAC scope token binding this approval to response scanning. */
|
|
21
|
+
scope_token?: string | null;
|
|
22
|
+
}
|
|
23
|
+
export interface ScanResponse {
|
|
24
|
+
allowed: boolean;
|
|
25
|
+
risk_score: number;
|
|
26
|
+
denial_reason?: string;
|
|
27
|
+
matched_rules: string[];
|
|
28
|
+
latency_ms: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ScanOutputResponse extends ScanResponse {
|
|
31
|
+
pii_found: Array<{
|
|
32
|
+
pii_type: string;
|
|
33
|
+
count: number;
|
|
34
|
+
}>;
|
|
35
|
+
secrets_found: Array<{
|
|
36
|
+
secret_type: string;
|
|
37
|
+
count: number;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
export interface ProxyRequest {
|
|
41
|
+
tool: string;
|
|
42
|
+
params: Record<string, unknown>;
|
|
43
|
+
target_url: string;
|
|
44
|
+
prompt_context?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface VerifyRequest {
|
|
47
|
+
tool: string;
|
|
48
|
+
params: Record<string, unknown>;
|
|
49
|
+
target_url?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface ClampdClientOptions {
|
|
52
|
+
gatewayUrl?: string;
|
|
53
|
+
agentId: string;
|
|
54
|
+
apiKey?: string;
|
|
55
|
+
secret?: string;
|
|
56
|
+
timeoutMs?: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Synchronous-style (async/await) client for the Clampd gateway proxy API.
|
|
60
|
+
*
|
|
61
|
+
* Uses the built-in `fetch` available in Node 18+.
|
|
62
|
+
*/
|
|
63
|
+
export declare class ClampdClient {
|
|
64
|
+
private readonly gatewayUrl;
|
|
65
|
+
readonly agentId: string;
|
|
66
|
+
private readonly apiKey;
|
|
67
|
+
private readonly jwt;
|
|
68
|
+
private readonly timeoutMs;
|
|
69
|
+
constructor(opts: ClampdClientOptions);
|
|
70
|
+
private headers;
|
|
71
|
+
/**
|
|
72
|
+
* Send a tool call through the Clampd gateway for evaluation.
|
|
73
|
+
*
|
|
74
|
+
* When targetUrl is empty (default), the gateway runs evaluate-only mode:
|
|
75
|
+
* classify + policy check, no token exchange or forwarding. The tool
|
|
76
|
+
* executes locally in the agent's runtime.
|
|
77
|
+
*
|
|
78
|
+
* When targetUrl is set, the gateway also exchanges a micro-token and
|
|
79
|
+
* forwards the request to the target, inspecting the response.
|
|
80
|
+
*/
|
|
81
|
+
proxy(tool: string, params: Record<string, unknown>, targetUrl?: string, promptContext?: string, toolDescriptorHash?: string): Promise<ProxyResponse>;
|
|
82
|
+
/**
|
|
83
|
+
* Get delegation headers for cross-service HTTP propagation.
|
|
84
|
+
* Static convenience method.
|
|
85
|
+
*/
|
|
86
|
+
static delegationHeaders(): Record<string, string>;
|
|
87
|
+
/**
|
|
88
|
+
* Inspect a tool response for PII, anomalies, or policy violations.
|
|
89
|
+
*
|
|
90
|
+
* When scopeToken is provided (from a prior proxy() call),
|
|
91
|
+
* the gateway can verify this response came from a Clampd-approved call.
|
|
92
|
+
*/
|
|
93
|
+
inspect(tool: string, responseData: unknown, requestId?: string, scopeToken?: string): Promise<ProxyResponse>;
|
|
94
|
+
/**
|
|
95
|
+
* Dry-run: stages 1-6 only — no token exchange or forwarding.
|
|
96
|
+
*/
|
|
97
|
+
verify(tool: string, params: Record<string, unknown>, targetUrl?: string): Promise<ProxyResponse>;
|
|
98
|
+
/**
|
|
99
|
+
* Scan input text (prompt) for injection attacks, jailbreaks, etc.
|
|
100
|
+
* Unlike proxy/verify, this throws on network errors so callers can
|
|
101
|
+
* implement fail-open vs fail-closed logic.
|
|
102
|
+
*/
|
|
103
|
+
scanInput(text: string, messageCount?: number): Promise<ScanResponse>;
|
|
104
|
+
/**
|
|
105
|
+
* Scan output text (LLM response) for PII, secrets, policy violations.
|
|
106
|
+
* Unlike proxy/verify, this throws on network errors so callers can
|
|
107
|
+
* implement fail-open vs fail-closed logic.
|
|
108
|
+
*/
|
|
109
|
+
scanOutput(text: string, requestId?: string): Promise<ScanOutputResponse>;
|
|
110
|
+
private post;
|
|
111
|
+
/**
|
|
112
|
+
* Like `post`, but throws on network errors and non-OK responses
|
|
113
|
+
* instead of synthesizing a blocked response. Used by scan methods
|
|
114
|
+
* so callers can distinguish gateway errors from policy decisions.
|
|
115
|
+
*/
|
|
116
|
+
private postOrThrow;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClampdClient — thin wrapper around the ag-gateway HTTP API.
|
|
3
|
+
*/
|
|
4
|
+
import { makeAgentJwt } from "./auth.js";
|
|
5
|
+
import { getDelegation, delegationHeaders } from "./delegation.js";
|
|
6
|
+
// ── Synthesized error response ─────────────────────────────────────
|
|
7
|
+
function blockedResponse(reason) {
|
|
8
|
+
return {
|
|
9
|
+
request_id: "error",
|
|
10
|
+
allowed: false,
|
|
11
|
+
risk_score: 1.0,
|
|
12
|
+
denial_reason: reason,
|
|
13
|
+
matched_rules: [],
|
|
14
|
+
latency_ms: 0,
|
|
15
|
+
degraded_stages: [],
|
|
16
|
+
session_flags: [],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// ── Client ─────────────────────────────────────────────────────────
|
|
20
|
+
/**
|
|
21
|
+
* Synchronous-style (async/await) client for the Clampd gateway proxy API.
|
|
22
|
+
*
|
|
23
|
+
* Uses the built-in `fetch` available in Node 18+.
|
|
24
|
+
*/
|
|
25
|
+
export class ClampdClient {
|
|
26
|
+
gatewayUrl;
|
|
27
|
+
agentId;
|
|
28
|
+
apiKey;
|
|
29
|
+
jwt;
|
|
30
|
+
timeoutMs;
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
this.gatewayUrl = (opts.gatewayUrl ?? "http://localhost:8080").replace(/\/$/, "");
|
|
33
|
+
this.agentId = opts.agentId;
|
|
34
|
+
this.apiKey = opts.apiKey ?? process.env.CLAMPD_API_KEY ?? "";
|
|
35
|
+
this.jwt = makeAgentJwt(this.agentId, { secret: opts.secret });
|
|
36
|
+
this.timeoutMs = opts.timeoutMs ?? 30_000;
|
|
37
|
+
}
|
|
38
|
+
headers() {
|
|
39
|
+
return {
|
|
40
|
+
Authorization: `Bearer ${this.jwt}`,
|
|
41
|
+
"X-AG-Key": this.apiKey,
|
|
42
|
+
"Content-Type": "application/json",
|
|
43
|
+
...delegationHeaders(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Send a tool call through the Clampd gateway for evaluation.
|
|
48
|
+
*
|
|
49
|
+
* When targetUrl is empty (default), the gateway runs evaluate-only mode:
|
|
50
|
+
* classify + policy check, no token exchange or forwarding. The tool
|
|
51
|
+
* executes locally in the agent's runtime.
|
|
52
|
+
*
|
|
53
|
+
* When targetUrl is set, the gateway also exchanges a micro-token and
|
|
54
|
+
* forwards the request to the target, inspecting the response.
|
|
55
|
+
*/
|
|
56
|
+
async proxy(tool, params, targetUrl = "", promptContext, toolDescriptorHash) {
|
|
57
|
+
const body = {
|
|
58
|
+
tool,
|
|
59
|
+
params,
|
|
60
|
+
target_url: targetUrl,
|
|
61
|
+
};
|
|
62
|
+
if (promptContext) {
|
|
63
|
+
body.prompt_context = promptContext;
|
|
64
|
+
}
|
|
65
|
+
if (toolDescriptorHash) {
|
|
66
|
+
body.tool_descriptor_hash = toolDescriptorHash;
|
|
67
|
+
}
|
|
68
|
+
// Auto-include delegation context if present
|
|
69
|
+
const delegation = getDelegation();
|
|
70
|
+
if (delegation && delegation.chain.length > 1) {
|
|
71
|
+
body.caller_agent_id = delegation.chain[delegation.chain.length - 2];
|
|
72
|
+
body.delegation_chain = delegation.chain;
|
|
73
|
+
body.delegation_trace_id = delegation.traceId;
|
|
74
|
+
}
|
|
75
|
+
return this.post("/v1/proxy", body);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get delegation headers for cross-service HTTP propagation.
|
|
79
|
+
* Static convenience method.
|
|
80
|
+
*/
|
|
81
|
+
static delegationHeaders() {
|
|
82
|
+
return delegationHeaders();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Inspect a tool response for PII, anomalies, or policy violations.
|
|
86
|
+
*
|
|
87
|
+
* When scopeToken is provided (from a prior proxy() call),
|
|
88
|
+
* the gateway can verify this response came from a Clampd-approved call.
|
|
89
|
+
*/
|
|
90
|
+
async inspect(tool, responseData, requestId, scopeToken) {
|
|
91
|
+
const body = { tool, response_data: responseData };
|
|
92
|
+
if (requestId)
|
|
93
|
+
body.request_id = requestId;
|
|
94
|
+
if (scopeToken)
|
|
95
|
+
body.scope_token = scopeToken;
|
|
96
|
+
return this.post("/v1/inspect", body);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Dry-run: stages 1-6 only — no token exchange or forwarding.
|
|
100
|
+
*/
|
|
101
|
+
async verify(tool, params, targetUrl = "") {
|
|
102
|
+
const body = {
|
|
103
|
+
tool,
|
|
104
|
+
params,
|
|
105
|
+
target_url: targetUrl,
|
|
106
|
+
};
|
|
107
|
+
return this.post("/v1/verify", body);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Scan input text (prompt) for injection attacks, jailbreaks, etc.
|
|
111
|
+
* Unlike proxy/verify, this throws on network errors so callers can
|
|
112
|
+
* implement fail-open vs fail-closed logic.
|
|
113
|
+
*/
|
|
114
|
+
async scanInput(text, messageCount) {
|
|
115
|
+
const body = { text };
|
|
116
|
+
if (messageCount)
|
|
117
|
+
body.message_count = messageCount;
|
|
118
|
+
return this.postOrThrow("/v1/scan-input", body);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Scan output text (LLM response) for PII, secrets, policy violations.
|
|
122
|
+
* Unlike proxy/verify, this throws on network errors so callers can
|
|
123
|
+
* implement fail-open vs fail-closed logic.
|
|
124
|
+
*/
|
|
125
|
+
async scanOutput(text, requestId) {
|
|
126
|
+
const body = { text };
|
|
127
|
+
if (requestId)
|
|
128
|
+
body.request_id = requestId;
|
|
129
|
+
return this.postOrThrow("/v1/scan-output", body);
|
|
130
|
+
}
|
|
131
|
+
// ── Internal ────────────────────────────────────────────────────
|
|
132
|
+
async post(path, body) {
|
|
133
|
+
const url = `${this.gatewayUrl}${path}`;
|
|
134
|
+
let resp;
|
|
135
|
+
try {
|
|
136
|
+
resp = await fetch(url, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: this.headers(),
|
|
139
|
+
body: JSON.stringify(body),
|
|
140
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
const message = err instanceof Error ? err.message : "Unknown fetch error";
|
|
145
|
+
return blockedResponse(`Fetch error: ${message}`);
|
|
146
|
+
}
|
|
147
|
+
if (resp.ok) {
|
|
148
|
+
const json = (await resp.json());
|
|
149
|
+
// Ensure array fields are always present for ProxyResponse shape
|
|
150
|
+
if (typeof json === "object" && json !== null && "degraded_stages" in json) {
|
|
151
|
+
const pr = json;
|
|
152
|
+
return {
|
|
153
|
+
...json,
|
|
154
|
+
degraded_stages: pr.degraded_stages ?? [],
|
|
155
|
+
session_flags: pr.session_flags ?? [],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return json;
|
|
159
|
+
}
|
|
160
|
+
// Gateway returned an error — synthesize a blocked response
|
|
161
|
+
const text = await resp.text().catch(() => `HTTP ${resp.status}`);
|
|
162
|
+
return blockedResponse(text);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Like `post`, but throws on network errors and non-OK responses
|
|
166
|
+
* instead of synthesizing a blocked response. Used by scan methods
|
|
167
|
+
* so callers can distinguish gateway errors from policy decisions.
|
|
168
|
+
*/
|
|
169
|
+
async postOrThrow(path, body) {
|
|
170
|
+
const url = `${this.gatewayUrl}${path}`;
|
|
171
|
+
let resp;
|
|
172
|
+
try {
|
|
173
|
+
resp = await fetch(url, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: this.headers(),
|
|
176
|
+
body: JSON.stringify(body),
|
|
177
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
182
|
+
throw new Error(`Scan fetch error: ${message}`);
|
|
183
|
+
}
|
|
184
|
+
if (!resp.ok) {
|
|
185
|
+
const text = await resp.text().catch(() => `HTTP ${resp.status}`);
|
|
186
|
+
throw new Error(`Scan request failed: ${text}`);
|
|
187
|
+
}
|
|
188
|
+
return (await resp.json());
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface DelegationContext {
|
|
2
|
+
traceId: string;
|
|
3
|
+
chain: string[];
|
|
4
|
+
confidence: "verified" | "inferred" | "declared";
|
|
5
|
+
}
|
|
6
|
+
export declare const MAX_DELEGATION_DEPTH = 5;
|
|
7
|
+
/**
|
|
8
|
+
* Get the current delegation context, if any.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getDelegation(): DelegationContext | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Get the caller (parent) agent ID from the delegation chain.
|
|
13
|
+
* Returns undefined if there is no parent (single agent or no context).
|
|
14
|
+
*/
|
|
15
|
+
export declare function getCallerAgentId(): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Check whether a chain contains a cycle (duplicate agent ID).
|
|
18
|
+
*/
|
|
19
|
+
export declare function hasCycle(chain: string[]): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Run a function within a delegation context.
|
|
22
|
+
* If already inside a delegation, extends the chain.
|
|
23
|
+
* If not, starts a new chain.
|
|
24
|
+
*/
|
|
25
|
+
export declare function withDelegation<T>(agentId: string, fn: () => T): T;
|
|
26
|
+
/**
|
|
27
|
+
* Get delegation headers for cross-service HTTP propagation.
|
|
28
|
+
* Returns an empty object when called outside a delegation context.
|
|
29
|
+
*/
|
|
30
|
+
export declare function delegationHeaders(): Record<string, string>;
|
|
31
|
+
//# sourceMappingURL=delegation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegation.d.ts","sourceRoot":"","sources":["../src/delegation.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;CAClD;AAID,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;GAEG;AACH,wBAAgB,aAAa,IAAI,iBAAiB,GAAG,SAAS,CAE7D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,SAAS,CAIrD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAgCjE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQ1D"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic delegation chain tracking via AsyncLocalStorage.
|
|
3
|
+
* Propagates through async/await, Promises, and callbacks automatically.
|
|
4
|
+
*
|
|
5
|
+
* When Agent A's guarded tool calls Agent B's guarded tool in the same
|
|
6
|
+
* process, the SDK auto-detects the cross-agent hop and includes
|
|
7
|
+
* delegation metadata in proxy requests.
|
|
8
|
+
*/
|
|
9
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
const delegationStore = new AsyncLocalStorage();
|
|
12
|
+
export const MAX_DELEGATION_DEPTH = 5;
|
|
13
|
+
/**
|
|
14
|
+
* Get the current delegation context, if any.
|
|
15
|
+
*/
|
|
16
|
+
export function getDelegation() {
|
|
17
|
+
return delegationStore.getStore();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get the caller (parent) agent ID from the delegation chain.
|
|
21
|
+
* Returns undefined if there is no parent (single agent or no context).
|
|
22
|
+
*/
|
|
23
|
+
export function getCallerAgentId() {
|
|
24
|
+
const ctx = delegationStore.getStore();
|
|
25
|
+
if (!ctx || ctx.chain.length < 2)
|
|
26
|
+
return undefined;
|
|
27
|
+
return ctx.chain[ctx.chain.length - 2];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check whether a chain contains a cycle (duplicate agent ID).
|
|
31
|
+
*/
|
|
32
|
+
export function hasCycle(chain) {
|
|
33
|
+
return new Set(chain).size !== chain.length;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Run a function within a delegation context.
|
|
37
|
+
* If already inside a delegation, extends the chain.
|
|
38
|
+
* If not, starts a new chain.
|
|
39
|
+
*/
|
|
40
|
+
export function withDelegation(agentId, fn) {
|
|
41
|
+
const parent = delegationStore.getStore();
|
|
42
|
+
let ctx;
|
|
43
|
+
if (parent) {
|
|
44
|
+
ctx = {
|
|
45
|
+
traceId: parent.traceId,
|
|
46
|
+
chain: [...parent.chain, agentId],
|
|
47
|
+
confidence: parent.confidence,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
ctx = {
|
|
52
|
+
traceId: randomUUID().replace(/-/g, "").slice(0, 16),
|
|
53
|
+
chain: [agentId],
|
|
54
|
+
confidence: "verified",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (ctx.chain.length > MAX_DELEGATION_DEPTH) {
|
|
58
|
+
throw new Error(`Delegation depth ${ctx.chain.length} exceeds maximum ${MAX_DELEGATION_DEPTH}. ` +
|
|
59
|
+
`Chain: ${ctx.chain.join(" \u2192 ")}`);
|
|
60
|
+
}
|
|
61
|
+
if (hasCycle(ctx.chain)) {
|
|
62
|
+
throw new Error(`Circular delegation detected: ${ctx.chain.join(" \u2192 ")}`);
|
|
63
|
+
}
|
|
64
|
+
return delegationStore.run(ctx, fn);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get delegation headers for cross-service HTTP propagation.
|
|
68
|
+
* Returns an empty object when called outside a delegation context.
|
|
69
|
+
*/
|
|
70
|
+
export function delegationHeaders() {
|
|
71
|
+
const ctx = delegationStore.getStore();
|
|
72
|
+
if (!ctx)
|
|
73
|
+
return {};
|
|
74
|
+
return {
|
|
75
|
+
"X-Clampd-Delegation-Trace": ctx.traceId,
|
|
76
|
+
"X-Clampd-Delegation-Chain": ctx.chain.join(","),
|
|
77
|
+
"X-Clampd-Delegation-Confidence": ctx.confidence,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=delegation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegation.js","sourceRoot":"","sources":["../src/delegation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAQzC,MAAM,eAAe,GAAG,IAAI,iBAAiB,EAAqB,CAAC;AAEnE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAEtC;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,eAAe,CAAC,QAAQ,EAAE,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,GAAG,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC;IACvC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IACnD,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAe;IACtC,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAI,OAAe,EAAE,EAAW;IAC5D,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC;IAE1C,IAAI,GAAsB,CAAC;IAC3B,IAAI,MAAM,EAAE,CAAC;QACX,GAAG,GAAG;YACJ,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC;YACjC,UAAU,EAAE,MAAM,CAAC,UAAU;SAC9B,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,GAAG,GAAG;YACJ,OAAO,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;YACpD,KAAK,EAAE,CAAC,OAAO,CAAC;YAChB,UAAU,EAAE,UAAU;SACvB,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,oBAAoB,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CACb,oBAAoB,GAAG,CAAC,KAAK,CAAC,MAAM,oBAAoB,oBAAoB,IAAI;YAC9E,UAAU,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CACzC,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,iCAAiC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAC9D,CAAC;IACJ,CAAC;IAED,OAAO,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,GAAG,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC;IACvC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,OAAO;QACL,2BAA2B,EAAE,GAAG,CAAC,OAAO;QACxC,2BAA2B,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;QAChD,gCAAgC,EAAE,GAAG,CAAC,UAAU;KACjD,CAAC;AACJ,CAAC"}
|