@orangecheck/agent-anthropic 0.3.0 → 1.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/README.md +0 -1
- package/dist/index.d.mts +1 -20
- package/dist/index.d.ts +1 -20
- package/dist/index.js +0 -49
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -48
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/index.test.ts +0 -109
- package/src/index.ts +0 -113
package/README.md
CHANGED
|
@@ -98,4 +98,3 @@ MIT.
|
|
|
98
98
|
- [@orangecheck/agent-core](https://www.npmjs.com/package/@orangecheck/agent-core) — verifier library.
|
|
99
99
|
- [@orangecheck/agent-mcp](https://www.npmjs.com/package/@orangecheck/agent-mcp) — same pattern, for MCP.
|
|
100
100
|
- [@orangecheck/agent-openai](https://www.npmjs.com/package/@orangecheck/agent-openai) — same pattern, for OpenAI Responses + Chat Completions.
|
|
101
|
-
- [fleet.ochk.io/integrations/anthropic](https://fleet.ochk.io/integrations/anthropic) — the managed-tier story this slots into.
|
package/dist/index.d.mts
CHANGED
|
@@ -25,24 +25,5 @@ interface InvokeWithStampResult<T> {
|
|
|
25
25
|
action: ActionEnvelope;
|
|
26
26
|
}
|
|
27
27
|
declare function invokeWithStamp<T>(input: InvokeWithStampInput<T>): Promise<InvokeWithStampResult<T>>;
|
|
28
|
-
interface FleetClient {
|
|
29
|
-
baseUrl?: string;
|
|
30
|
-
apiToken: string;
|
|
31
|
-
projectId: string;
|
|
32
|
-
fetch?: typeof fetch;
|
|
33
|
-
}
|
|
34
|
-
interface PostActionResult {
|
|
35
|
-
id: string;
|
|
36
|
-
project_id: string;
|
|
37
|
-
delegation_id: string;
|
|
38
|
-
}
|
|
39
|
-
declare function postActionToFleet(action: ActionEnvelope, client: FleetClient): Promise<PostActionResult>;
|
|
40
|
-
interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {
|
|
41
|
-
fleet?: FleetClient;
|
|
42
|
-
}
|
|
43
|
-
interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {
|
|
44
|
-
posted: PostActionResult | null;
|
|
45
|
-
}
|
|
46
|
-
declare function invokeWithStampAndPost<T>(input: InvokeWithStampAndPostInput<T>): Promise<InvokeWithStampAndPostResult<T>>;
|
|
47
28
|
|
|
48
|
-
export { type AnthropicToolUse, type
|
|
29
|
+
export { type AnthropicToolUse, type InvokeWithStampInput, type InvokeWithStampResult, type StampToolUseInput, canonicalizeToolUse, invokeWithStamp, stampToolUse, toolUseHash, toolUseId };
|
package/dist/index.d.ts
CHANGED
|
@@ -25,24 +25,5 @@ interface InvokeWithStampResult<T> {
|
|
|
25
25
|
action: ActionEnvelope;
|
|
26
26
|
}
|
|
27
27
|
declare function invokeWithStamp<T>(input: InvokeWithStampInput<T>): Promise<InvokeWithStampResult<T>>;
|
|
28
|
-
interface FleetClient {
|
|
29
|
-
baseUrl?: string;
|
|
30
|
-
apiToken: string;
|
|
31
|
-
projectId: string;
|
|
32
|
-
fetch?: typeof fetch;
|
|
33
|
-
}
|
|
34
|
-
interface PostActionResult {
|
|
35
|
-
id: string;
|
|
36
|
-
project_id: string;
|
|
37
|
-
delegation_id: string;
|
|
38
|
-
}
|
|
39
|
-
declare function postActionToFleet(action: ActionEnvelope, client: FleetClient): Promise<PostActionResult>;
|
|
40
|
-
interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {
|
|
41
|
-
fleet?: FleetClient;
|
|
42
|
-
}
|
|
43
|
-
interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {
|
|
44
|
-
posted: PostActionResult | null;
|
|
45
|
-
}
|
|
46
|
-
declare function invokeWithStampAndPost<T>(input: InvokeWithStampAndPostInput<T>): Promise<InvokeWithStampAndPostResult<T>>;
|
|
47
28
|
|
|
48
|
-
export { type AnthropicToolUse, type
|
|
29
|
+
export { type AnthropicToolUse, type InvokeWithStampInput, type InvokeWithStampResult, type StampToolUseInput, canonicalizeToolUse, invokeWithStamp, stampToolUse, toolUseHash, toolUseId };
|
package/dist/index.js
CHANGED
|
@@ -46,58 +46,9 @@ async function invokeWithStamp(input) {
|
|
|
46
46
|
const result = await input.call(input.toolUse);
|
|
47
47
|
return { result, action };
|
|
48
48
|
}
|
|
49
|
-
async function postActionToFleet(action, client) {
|
|
50
|
-
const baseUrl = client.baseUrl ?? "https://fleet.ochk.io";
|
|
51
|
-
const f = client.fetch ?? fetch;
|
|
52
|
-
const body = {
|
|
53
|
-
project_id: client.projectId,
|
|
54
|
-
delegation_id: action.delegation_id,
|
|
55
|
-
agent_address: action.signer.address,
|
|
56
|
-
scope_exercised: action.scope_exercised,
|
|
57
|
-
content_hash: action.content.hash,
|
|
58
|
-
content_length: action.content.length,
|
|
59
|
-
content_mime: action.content.mime,
|
|
60
|
-
signed_at: action.signed_at,
|
|
61
|
-
signature: action.sig.value,
|
|
62
|
-
id: action.id
|
|
63
|
-
};
|
|
64
|
-
const r = await f(`${baseUrl}/api/actions`, {
|
|
65
|
-
method: "POST",
|
|
66
|
-
headers: {
|
|
67
|
-
"Content-Type": "application/json",
|
|
68
|
-
Authorization: `Bearer ${client.apiToken}`
|
|
69
|
-
},
|
|
70
|
-
body: JSON.stringify(body)
|
|
71
|
-
});
|
|
72
|
-
if (!r.ok) {
|
|
73
|
-
let reason = `http_${r.status}`;
|
|
74
|
-
try {
|
|
75
|
-
const j2 = await r.json();
|
|
76
|
-
if (j2.reason) reason = j2.reason;
|
|
77
|
-
} catch {
|
|
78
|
-
}
|
|
79
|
-
throw new Error(`postActionToFleet failed: ${reason}`);
|
|
80
|
-
}
|
|
81
|
-
const j = await r.json();
|
|
82
|
-
return j.action;
|
|
83
|
-
}
|
|
84
|
-
async function invokeWithStampAndPost(input) {
|
|
85
|
-
const { result, action } = await invokeWithStamp(input);
|
|
86
|
-
let posted = null;
|
|
87
|
-
if (input.fleet) {
|
|
88
|
-
try {
|
|
89
|
-
posted = await postActionToFleet(action, input.fleet);
|
|
90
|
-
} catch (err) {
|
|
91
|
-
console.error("[oc-agent-anthropic] postActionToFleet failed:", err);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return { result, action, posted };
|
|
95
|
-
}
|
|
96
49
|
|
|
97
50
|
exports.canonicalizeToolUse = canonicalizeToolUse;
|
|
98
51
|
exports.invokeWithStamp = invokeWithStamp;
|
|
99
|
-
exports.invokeWithStampAndPost = invokeWithStampAndPost;
|
|
100
|
-
exports.postActionToFleet = postActionToFleet;
|
|
101
52
|
exports.stampToolUse = stampToolUse;
|
|
102
53
|
exports.toolUseHash = toolUseHash;
|
|
103
54
|
exports.toolUseId = toolUseId;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["canonicalize","hexEncode","sha256","computeActionId","assertScopeGranted","signAsAgent","j"],"mappings":"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","file":"index.js","sourcesContent":["// @orangecheck/agent-anthropic — wrap Anthropic Tool Use invocations in\n// OC Agent action envelopes.\n//\n// Anthropic's Messages API surfaces tool calls as `tool_use` content\n// blocks: { type: 'tool_use', id, name, input }. This module canonicalizes\n// each tool_use, hashes it, and produces an agent-action envelope binding\n// the BIP-322-authenticated agent to the (tool_name, input) tuple — at a\n// scope of `anthropic:tool(name=<tool>)`.\n\nimport { sha256 } from '@noble/hashes/sha256';\nimport {\n assertScopeGranted,\n canonicalize,\n computeActionId,\n hexEncode,\n type DelegationEnvelope,\n} from '@orangecheck/agent-core';\nimport {\n signAsAgent,\n type ActionEnvelope,\n type SignerRef,\n} from '@orangecheck/agent-signer';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalization of an Anthropic tool_use\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AnthropicToolUse {\n /** The `id` Anthropic assigns to this tool_use block (passed back as tool_use_id on result). */\n id: string;\n /** The tool name registered in your `tools` array on messages.create. */\n name: string;\n /** The model's structured input for this tool — JSON-serializable. */\n input: Record<string, unknown>;\n}\n\n/**\n * Produce the canonical bytes for an Anthropic tool_use. The bytes are what\n * the agent-action envelope's `content.hash` is a SHA-256 of.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"id\": <tool_use_id>,\n * \"input\": <canonical JSON of input>,\n * \"name\": <tool name>\n * }\n */\nexport function canonicalizeToolUse(t: AnthropicToolUse): Uint8Array {\n const canon = {\n id: t.id,\n input: t.input,\n name: t.name,\n };\n const str = canonicalize(canon as unknown as Parameters<typeof canonicalize>[0]);\n return new TextEncoder().encode(str + '\\n');\n}\n\nexport function toolUseHash(t: AnthropicToolUse): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeToolUse(t)));\n}\n\nexport function toolUseId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n t: AnthropicToolUse\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: toolUseHash(t),\n content_length: canonicalizeToolUse(t).byteLength,\n content_mime: 'application/vnd.oc-agent.anthropic-tool-use+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamped tool_use\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface StampToolUseInput {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n toolUse: AnthropicToolUse;\n /** Defaults to `anthropic:tool(name=<tool>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\n/**\n * Stamp an Anthropic tool_use without executing it. Useful for pre-\n * authorization flows or batch stamping.\n */\nexport async function stampToolUse(input: StampToolUseInput): Promise<ActionEnvelope> {\n const scopeExercised =\n input.scopeExercised ?? `anthropic:tool(name=${input.toolUse.name})`;\n\n assertScopeGranted(input.delegation, scopeExercised, 'stampToolUse');\n\n const bytes = canonicalizeToolUse(input.toolUse);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: toolUseHash(input.toolUse), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.anthropic-tool-use+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// invokeWithStamp — call the tool AND produce the stamp around it\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface InvokeWithStampInput<T> extends StampToolUseInput {\n /** Your handler that actually runs the tool. Returns whatever the tool returns. */\n call: (toolUse: AnthropicToolUse) => Promise<T>;\n}\n\nexport interface InvokeWithStampResult<T> {\n result: T;\n action: ActionEnvelope;\n}\n\n/**\n * Stamp the tool_use first, then execute the handler. The stamped envelope\n * is returned alongside the result so the caller can ship them together —\n * e.g. as a side-channel to an audit log, or as the `_oc_agent` metadata on\n * the tool_result content block back to Claude.\n */\nexport async function invokeWithStamp<T>(\n input: InvokeWithStampInput<T>\n): Promise<InvokeWithStampResult<T>> {\n const action = await stampToolUse(input);\n const result = await input.call(input.toolUse);\n return { result, action };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Fleet integration: POST stamped actions to fleet.ochk.io/api/actions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface FleetClient {\n /** Defaults to https://fleet.ochk.io. */\n baseUrl?: string;\n /** Bearer token from /settings § 03 (`ock_<hex>`). */\n apiToken: string;\n /** Project the action belongs to (proj_*). */\n projectId: string;\n /** Optional fetch override for runtimes that need it (Node 18 LTS w/o global fetch, etc). */\n fetch?: typeof fetch;\n}\n\nexport interface PostActionResult {\n id: string;\n project_id: string;\n delegation_id: string;\n}\n\n/**\n * POST a stamped action envelope to fleet.ochk.io's /api/actions\n * registry. The fleet:\n * - re-derives the action id from canonical inputs and rejects on\n * mismatch (tamper defense)\n * - validates that the agent address matches the parent delegation's\n * agent (forgery defense)\n * - persists, fans out to Nostr (kind 30084), submits to OC Stamp,\n * and triggers any subscribed webhooks (action.registered)\n *\n * Returns the registered action's id + project_id + delegation_id.\n * Throws on non-2xx with the server's reason string.\n */\nexport async function postActionToFleet(\n action: ActionEnvelope,\n client: FleetClient\n): Promise<PostActionResult> {\n const baseUrl = client.baseUrl ?? 'https://fleet.ochk.io';\n const f = client.fetch ?? fetch;\n const body = {\n project_id: client.projectId,\n delegation_id: action.delegation_id,\n agent_address: action.signer.address,\n scope_exercised: action.scope_exercised,\n content_hash: action.content.hash,\n content_length: action.content.length,\n content_mime: action.content.mime,\n signed_at: action.signed_at,\n signature: action.sig.value,\n id: action.id,\n };\n const r = await f(`${baseUrl}/api/actions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${client.apiToken}`,\n },\n body: JSON.stringify(body),\n });\n if (!r.ok) {\n let reason = `http_${r.status}`;\n try {\n const j = (await r.json()) as { reason?: string };\n if (j.reason) reason = j.reason;\n } catch {\n // body wasn't json; keep the http_<n> reason\n }\n throw new Error(`postActionToFleet failed: ${reason}`);\n }\n const j = (await r.json()) as { ok: true; action: PostActionResult };\n return j.action;\n}\n\nexport interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {\n /** Fleet credentials. If absent, behaves like invokeWithStamp (no posting). */\n fleet?: FleetClient;\n}\n\nexport interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {\n /** Set when fleet is configured AND the POST succeeded. null otherwise. */\n posted: PostActionResult | null;\n}\n\n/**\n * Convenience: stamp the tool_use, execute the handler, AND post the\n * stamped envelope to fleet. Three roundtrips → one call. If `fleet`\n * is omitted, this is identical to invokeWithStamp + posted=null.\n *\n * The post happens AFTER the call so a failing post doesn't prevent the\n * tool from running. If you need pre-authorization (the post must\n * succeed before the call runs), use stampToolUse + postActionToFleet\n * + your call() in that order.\n */\nexport async function invokeWithStampAndPost<T>(\n input: InvokeWithStampAndPostInput<T>\n): Promise<InvokeWithStampAndPostResult<T>> {\n const { result, action } = await invokeWithStamp(input);\n let posted: PostActionResult | null = null;\n if (input.fleet) {\n try {\n posted = await postActionToFleet(action, input.fleet);\n } catch (err) {\n // Best-effort post — surfacing the error here would force\n // every caller to handle \"tool ran fine but post failed\",\n // which is rarely what they want. Log and continue.\n // eslint-disable-next-line no-console\n console.error('[oc-agent-anthropic] postActionToFleet failed:', err);\n }\n }\n return { result, action, posted };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["canonicalize","hexEncode","sha256","computeActionId","assertScopeGranted","signAsAgent"],"mappings":";;;;;;;AA+CO,SAAS,oBAAoB,CAAA,EAAiC;AACjE,EAAA,MAAM,KAAA,GAAQ;AAAA,IACV,IAAI,CAAA,CAAE,EAAA;AAAA,IACN,OAAO,CAAA,CAAE,KAAA;AAAA,IACT,MAAM,CAAA,CAAE;AAAA,GACZ;AACA,EAAA,MAAM,GAAA,GAAMA,uBAAa,KAAsD,CAAA;AAC/E,EAAA,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,MAAM,IAAI,CAAA;AAC9C;AAEO,SAAS,YAAY,CAAA,EAA6B;AACrD,EAAA,OAAO,YAAYC,mBAAA,CAAUC,aAAA,CAAO,mBAAA,CAAoB,CAAC,CAAC,CAAC,CAAA;AAC/D;AAEO,SAAS,SAAA,CACZ,YAAA,EACA,QAAA,EACA,YAAA,EACA,gBACA,CAAA,EACM;AACN,EAAA,OAAOC,yBAAA,CAAgB;AAAA,IACnB,OAAA,EAAS,YAAA;AAAA,IACT,YAAA,EAAc,YAAY,CAAC,CAAA;AAAA,IAC3B,cAAA,EAAgB,mBAAA,CAAoB,CAAC,CAAA,CAAE,UAAA;AAAA,IACvC,YAAA,EAAc,kDAAA;AAAA,IACd,SAAA,EAAW,QAAA;AAAA,IACX,aAAA,EAAe,YAAA;AAAA,IACf,eAAA,EAAiB;AAAA,GACpB,CAAA;AACL;AAmBA,eAAsB,aAAa,KAAA,EAAmD;AAClF,EAAA,MAAM,iBACF,KAAA,CAAM,cAAA,IAAkB,CAAA,oBAAA,EAAuB,KAAA,CAAM,QAAQ,IAAI,CAAA,CAAA,CAAA;AAErE,EAAAC,4BAAA,CAAmB,KAAA,CAAM,UAAA,EAAY,cAAA,EAAgB,cAAc,CAAA;AAEnE,EAAA,MAAM,KAAA,GAAQ,mBAAA,CAAoB,KAAA,CAAM,OAAO,CAAA;AAC/C,EAAA,OAAOC,uBAAA,CAAY;AAAA,IACf,OAAO,KAAA,CAAM,KAAA;AAAA,IACb,YAAY,KAAA,CAAM,UAAA;AAAA,IAClB,OAAA,EAAS,EAAE,IAAA,EAAM,WAAA,CAAY,MAAM,OAAO,CAAA,EAAG,MAAA,EAAQ,KAAA,CAAM,UAAA,EAAW;AAAA,IACtE,IAAA,EAAM,kDAAA;AAAA,IACN,cAAA;AAAA,IACA,UAAU,KAAA,CAAM;AAAA,GACnB,CAAA;AACL;AAsBA,eAAsB,gBAClB,KAAA,EACiC;AACjC,EAAA,MAAM,MAAA,GAAS,MAAM,YAAA,CAAa,KAAK,CAAA;AACvC,EAAA,MAAM,MAAA,GAAS,MAAM,KAAA,CAAM,IAAA,CAAK,MAAM,OAAO,CAAA;AAC7C,EAAA,OAAO,EAAE,QAAQ,MAAA,EAAO;AAC5B","file":"index.js","sourcesContent":["// @orangecheck/agent-anthropic — wrap Anthropic Tool Use invocations in\n// OC Agent action envelopes.\n//\n// Anthropic's Messages API surfaces tool calls as `tool_use` content\n// blocks: { type: 'tool_use', id, name, input }. This module canonicalizes\n// each tool_use, hashes it, and produces an agent-action envelope binding\n// the BIP-322-authenticated agent to the (tool_name, input) tuple — at a\n// scope of `anthropic:tool(name=<tool>)`.\n\nimport { sha256 } from '@noble/hashes/sha256';\nimport {\n assertScopeGranted,\n canonicalize,\n computeActionId,\n hexEncode,\n type DelegationEnvelope,\n} from '@orangecheck/agent-core';\nimport {\n signAsAgent,\n type ActionEnvelope,\n type SignerRef,\n} from '@orangecheck/agent-signer';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalization of an Anthropic tool_use\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AnthropicToolUse {\n /** The `id` Anthropic assigns to this tool_use block (passed back as tool_use_id on result). */\n id: string;\n /** The tool name registered in your `tools` array on messages.create. */\n name: string;\n /** The model's structured input for this tool — JSON-serializable. */\n input: Record<string, unknown>;\n}\n\n/**\n * Produce the canonical bytes for an Anthropic tool_use. The bytes are what\n * the agent-action envelope's `content.hash` is a SHA-256 of.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"id\": <tool_use_id>,\n * \"input\": <canonical JSON of input>,\n * \"name\": <tool name>\n * }\n */\nexport function canonicalizeToolUse(t: AnthropicToolUse): Uint8Array {\n const canon = {\n id: t.id,\n input: t.input,\n name: t.name,\n };\n const str = canonicalize(canon as unknown as Parameters<typeof canonicalize>[0]);\n return new TextEncoder().encode(str + '\\n');\n}\n\nexport function toolUseHash(t: AnthropicToolUse): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeToolUse(t)));\n}\n\nexport function toolUseId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n t: AnthropicToolUse\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: toolUseHash(t),\n content_length: canonicalizeToolUse(t).byteLength,\n content_mime: 'application/vnd.oc-agent.anthropic-tool-use+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamped tool_use\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface StampToolUseInput {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n toolUse: AnthropicToolUse;\n /** Defaults to `anthropic:tool(name=<tool>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\n/**\n * Stamp an Anthropic tool_use without executing it. Useful for pre-\n * authorization flows or batch stamping.\n */\nexport async function stampToolUse(input: StampToolUseInput): Promise<ActionEnvelope> {\n const scopeExercised =\n input.scopeExercised ?? `anthropic:tool(name=${input.toolUse.name})`;\n\n assertScopeGranted(input.delegation, scopeExercised, 'stampToolUse');\n\n const bytes = canonicalizeToolUse(input.toolUse);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: toolUseHash(input.toolUse), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.anthropic-tool-use+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// invokeWithStamp — call the tool AND produce the stamp around it\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface InvokeWithStampInput<T> extends StampToolUseInput {\n /** Your handler that actually runs the tool. Returns whatever the tool returns. */\n call: (toolUse: AnthropicToolUse) => Promise<T>;\n}\n\nexport interface InvokeWithStampResult<T> {\n result: T;\n action: ActionEnvelope;\n}\n\n/**\n * Stamp the tool_use first, then execute the handler. The stamped envelope\n * is returned alongside the result so the caller can ship them together —\n * e.g. as a side-channel to an audit log, or as the `_oc_agent` metadata on\n * the tool_result content block back to Claude.\n */\nexport async function invokeWithStamp<T>(\n input: InvokeWithStampInput<T>\n): Promise<InvokeWithStampResult<T>> {\n const action = await stampToolUse(input);\n const result = await input.call(input.toolUse);\n return { result, action };\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -44,54 +44,7 @@ async function invokeWithStamp(input) {
|
|
|
44
44
|
const result = await input.call(input.toolUse);
|
|
45
45
|
return { result, action };
|
|
46
46
|
}
|
|
47
|
-
async function postActionToFleet(action, client) {
|
|
48
|
-
const baseUrl = client.baseUrl ?? "https://fleet.ochk.io";
|
|
49
|
-
const f = client.fetch ?? fetch;
|
|
50
|
-
const body = {
|
|
51
|
-
project_id: client.projectId,
|
|
52
|
-
delegation_id: action.delegation_id,
|
|
53
|
-
agent_address: action.signer.address,
|
|
54
|
-
scope_exercised: action.scope_exercised,
|
|
55
|
-
content_hash: action.content.hash,
|
|
56
|
-
content_length: action.content.length,
|
|
57
|
-
content_mime: action.content.mime,
|
|
58
|
-
signed_at: action.signed_at,
|
|
59
|
-
signature: action.sig.value,
|
|
60
|
-
id: action.id
|
|
61
|
-
};
|
|
62
|
-
const r = await f(`${baseUrl}/api/actions`, {
|
|
63
|
-
method: "POST",
|
|
64
|
-
headers: {
|
|
65
|
-
"Content-Type": "application/json",
|
|
66
|
-
Authorization: `Bearer ${client.apiToken}`
|
|
67
|
-
},
|
|
68
|
-
body: JSON.stringify(body)
|
|
69
|
-
});
|
|
70
|
-
if (!r.ok) {
|
|
71
|
-
let reason = `http_${r.status}`;
|
|
72
|
-
try {
|
|
73
|
-
const j2 = await r.json();
|
|
74
|
-
if (j2.reason) reason = j2.reason;
|
|
75
|
-
} catch {
|
|
76
|
-
}
|
|
77
|
-
throw new Error(`postActionToFleet failed: ${reason}`);
|
|
78
|
-
}
|
|
79
|
-
const j = await r.json();
|
|
80
|
-
return j.action;
|
|
81
|
-
}
|
|
82
|
-
async function invokeWithStampAndPost(input) {
|
|
83
|
-
const { result, action } = await invokeWithStamp(input);
|
|
84
|
-
let posted = null;
|
|
85
|
-
if (input.fleet) {
|
|
86
|
-
try {
|
|
87
|
-
posted = await postActionToFleet(action, input.fleet);
|
|
88
|
-
} catch (err) {
|
|
89
|
-
console.error("[oc-agent-anthropic] postActionToFleet failed:", err);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return { result, action, posted };
|
|
93
|
-
}
|
|
94
47
|
|
|
95
|
-
export { canonicalizeToolUse, invokeWithStamp,
|
|
48
|
+
export { canonicalizeToolUse, invokeWithStamp, stampToolUse, toolUseHash, toolUseId };
|
|
96
49
|
//# sourceMappingURL=index.mjs.map
|
|
97
50
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["j"],"mappings":"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","file":"index.mjs","sourcesContent":["// @orangecheck/agent-anthropic — wrap Anthropic Tool Use invocations in\n// OC Agent action envelopes.\n//\n// Anthropic's Messages API surfaces tool calls as `tool_use` content\n// blocks: { type: 'tool_use', id, name, input }. This module canonicalizes\n// each tool_use, hashes it, and produces an agent-action envelope binding\n// the BIP-322-authenticated agent to the (tool_name, input) tuple — at a\n// scope of `anthropic:tool(name=<tool>)`.\n\nimport { sha256 } from '@noble/hashes/sha256';\nimport {\n assertScopeGranted,\n canonicalize,\n computeActionId,\n hexEncode,\n type DelegationEnvelope,\n} from '@orangecheck/agent-core';\nimport {\n signAsAgent,\n type ActionEnvelope,\n type SignerRef,\n} from '@orangecheck/agent-signer';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalization of an Anthropic tool_use\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AnthropicToolUse {\n /** The `id` Anthropic assigns to this tool_use block (passed back as tool_use_id on result). */\n id: string;\n /** The tool name registered in your `tools` array on messages.create. */\n name: string;\n /** The model's structured input for this tool — JSON-serializable. */\n input: Record<string, unknown>;\n}\n\n/**\n * Produce the canonical bytes for an Anthropic tool_use. The bytes are what\n * the agent-action envelope's `content.hash` is a SHA-256 of.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"id\": <tool_use_id>,\n * \"input\": <canonical JSON of input>,\n * \"name\": <tool name>\n * }\n */\nexport function canonicalizeToolUse(t: AnthropicToolUse): Uint8Array {\n const canon = {\n id: t.id,\n input: t.input,\n name: t.name,\n };\n const str = canonicalize(canon as unknown as Parameters<typeof canonicalize>[0]);\n return new TextEncoder().encode(str + '\\n');\n}\n\nexport function toolUseHash(t: AnthropicToolUse): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeToolUse(t)));\n}\n\nexport function toolUseId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n t: AnthropicToolUse\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: toolUseHash(t),\n content_length: canonicalizeToolUse(t).byteLength,\n content_mime: 'application/vnd.oc-agent.anthropic-tool-use+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamped tool_use\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface StampToolUseInput {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n toolUse: AnthropicToolUse;\n /** Defaults to `anthropic:tool(name=<tool>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\n/**\n * Stamp an Anthropic tool_use without executing it. Useful for pre-\n * authorization flows or batch stamping.\n */\nexport async function stampToolUse(input: StampToolUseInput): Promise<ActionEnvelope> {\n const scopeExercised =\n input.scopeExercised ?? `anthropic:tool(name=${input.toolUse.name})`;\n\n assertScopeGranted(input.delegation, scopeExercised, 'stampToolUse');\n\n const bytes = canonicalizeToolUse(input.toolUse);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: toolUseHash(input.toolUse), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.anthropic-tool-use+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// invokeWithStamp — call the tool AND produce the stamp around it\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface InvokeWithStampInput<T> extends StampToolUseInput {\n /** Your handler that actually runs the tool. Returns whatever the tool returns. */\n call: (toolUse: AnthropicToolUse) => Promise<T>;\n}\n\nexport interface InvokeWithStampResult<T> {\n result: T;\n action: ActionEnvelope;\n}\n\n/**\n * Stamp the tool_use first, then execute the handler. The stamped envelope\n * is returned alongside the result so the caller can ship them together —\n * e.g. as a side-channel to an audit log, or as the `_oc_agent` metadata on\n * the tool_result content block back to Claude.\n */\nexport async function invokeWithStamp<T>(\n input: InvokeWithStampInput<T>\n): Promise<InvokeWithStampResult<T>> {\n const action = await stampToolUse(input);\n const result = await input.call(input.toolUse);\n return { result, action };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Fleet integration: POST stamped actions to fleet.ochk.io/api/actions\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface FleetClient {\n /** Defaults to https://fleet.ochk.io. */\n baseUrl?: string;\n /** Bearer token from /settings § 03 (`ock_<hex>`). */\n apiToken: string;\n /** Project the action belongs to (proj_*). */\n projectId: string;\n /** Optional fetch override for runtimes that need it (Node 18 LTS w/o global fetch, etc). */\n fetch?: typeof fetch;\n}\n\nexport interface PostActionResult {\n id: string;\n project_id: string;\n delegation_id: string;\n}\n\n/**\n * POST a stamped action envelope to fleet.ochk.io's /api/actions\n * registry. The fleet:\n * - re-derives the action id from canonical inputs and rejects on\n * mismatch (tamper defense)\n * - validates that the agent address matches the parent delegation's\n * agent (forgery defense)\n * - persists, fans out to Nostr (kind 30084), submits to OC Stamp,\n * and triggers any subscribed webhooks (action.registered)\n *\n * Returns the registered action's id + project_id + delegation_id.\n * Throws on non-2xx with the server's reason string.\n */\nexport async function postActionToFleet(\n action: ActionEnvelope,\n client: FleetClient\n): Promise<PostActionResult> {\n const baseUrl = client.baseUrl ?? 'https://fleet.ochk.io';\n const f = client.fetch ?? fetch;\n const body = {\n project_id: client.projectId,\n delegation_id: action.delegation_id,\n agent_address: action.signer.address,\n scope_exercised: action.scope_exercised,\n content_hash: action.content.hash,\n content_length: action.content.length,\n content_mime: action.content.mime,\n signed_at: action.signed_at,\n signature: action.sig.value,\n id: action.id,\n };\n const r = await f(`${baseUrl}/api/actions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${client.apiToken}`,\n },\n body: JSON.stringify(body),\n });\n if (!r.ok) {\n let reason = `http_${r.status}`;\n try {\n const j = (await r.json()) as { reason?: string };\n if (j.reason) reason = j.reason;\n } catch {\n // body wasn't json; keep the http_<n> reason\n }\n throw new Error(`postActionToFleet failed: ${reason}`);\n }\n const j = (await r.json()) as { ok: true; action: PostActionResult };\n return j.action;\n}\n\nexport interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {\n /** Fleet credentials. If absent, behaves like invokeWithStamp (no posting). */\n fleet?: FleetClient;\n}\n\nexport interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {\n /** Set when fleet is configured AND the POST succeeded. null otherwise. */\n posted: PostActionResult | null;\n}\n\n/**\n * Convenience: stamp the tool_use, execute the handler, AND post the\n * stamped envelope to fleet. Three roundtrips → one call. If `fleet`\n * is omitted, this is identical to invokeWithStamp + posted=null.\n *\n * The post happens AFTER the call so a failing post doesn't prevent the\n * tool from running. If you need pre-authorization (the post must\n * succeed before the call runs), use stampToolUse + postActionToFleet\n * + your call() in that order.\n */\nexport async function invokeWithStampAndPost<T>(\n input: InvokeWithStampAndPostInput<T>\n): Promise<InvokeWithStampAndPostResult<T>> {\n const { result, action } = await invokeWithStamp(input);\n let posted: PostActionResult | null = null;\n if (input.fleet) {\n try {\n posted = await postActionToFleet(action, input.fleet);\n } catch (err) {\n // Best-effort post — surfacing the error here would force\n // every caller to handle \"tool ran fine but post failed\",\n // which is rarely what they want. Log and continue.\n // eslint-disable-next-line no-console\n console.error('[oc-agent-anthropic] postActionToFleet failed:', err);\n }\n }\n return { result, action, posted };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;;;AA+CO,SAAS,oBAAoB,CAAA,EAAiC;AACjE,EAAA,MAAM,KAAA,GAAQ;AAAA,IACV,IAAI,CAAA,CAAE,EAAA;AAAA,IACN,OAAO,CAAA,CAAE,KAAA;AAAA,IACT,MAAM,CAAA,CAAE;AAAA,GACZ;AACA,EAAA,MAAM,GAAA,GAAM,aAAa,KAAsD,CAAA;AAC/E,EAAA,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,MAAM,IAAI,CAAA;AAC9C;AAEO,SAAS,YAAY,CAAA,EAA6B;AACrD,EAAA,OAAO,YAAY,SAAA,CAAU,MAAA,CAAO,mBAAA,CAAoB,CAAC,CAAC,CAAC,CAAA;AAC/D;AAEO,SAAS,SAAA,CACZ,YAAA,EACA,QAAA,EACA,YAAA,EACA,gBACA,CAAA,EACM;AACN,EAAA,OAAO,eAAA,CAAgB;AAAA,IACnB,OAAA,EAAS,YAAA;AAAA,IACT,YAAA,EAAc,YAAY,CAAC,CAAA;AAAA,IAC3B,cAAA,EAAgB,mBAAA,CAAoB,CAAC,CAAA,CAAE,UAAA;AAAA,IACvC,YAAA,EAAc,kDAAA;AAAA,IACd,SAAA,EAAW,QAAA;AAAA,IACX,aAAA,EAAe,YAAA;AAAA,IACf,eAAA,EAAiB;AAAA,GACpB,CAAA;AACL;AAmBA,eAAsB,aAAa,KAAA,EAAmD;AAClF,EAAA,MAAM,iBACF,KAAA,CAAM,cAAA,IAAkB,CAAA,oBAAA,EAAuB,KAAA,CAAM,QAAQ,IAAI,CAAA,CAAA,CAAA;AAErE,EAAA,kBAAA,CAAmB,KAAA,CAAM,UAAA,EAAY,cAAA,EAAgB,cAAc,CAAA;AAEnE,EAAA,MAAM,KAAA,GAAQ,mBAAA,CAAoB,KAAA,CAAM,OAAO,CAAA;AAC/C,EAAA,OAAO,WAAA,CAAY;AAAA,IACf,OAAO,KAAA,CAAM,KAAA;AAAA,IACb,YAAY,KAAA,CAAM,UAAA;AAAA,IAClB,OAAA,EAAS,EAAE,IAAA,EAAM,WAAA,CAAY,MAAM,OAAO,CAAA,EAAG,MAAA,EAAQ,KAAA,CAAM,UAAA,EAAW;AAAA,IACtE,IAAA,EAAM,kDAAA;AAAA,IACN,cAAA;AAAA,IACA,UAAU,KAAA,CAAM;AAAA,GACnB,CAAA;AACL;AAsBA,eAAsB,gBAClB,KAAA,EACiC;AACjC,EAAA,MAAM,MAAA,GAAS,MAAM,YAAA,CAAa,KAAK,CAAA;AACvC,EAAA,MAAM,MAAA,GAAS,MAAM,KAAA,CAAM,IAAA,CAAK,MAAM,OAAO,CAAA;AAC7C,EAAA,OAAO,EAAE,QAAQ,MAAA,EAAO;AAC5B","file":"index.mjs","sourcesContent":["// @orangecheck/agent-anthropic — wrap Anthropic Tool Use invocations in\n// OC Agent action envelopes.\n//\n// Anthropic's Messages API surfaces tool calls as `tool_use` content\n// blocks: { type: 'tool_use', id, name, input }. This module canonicalizes\n// each tool_use, hashes it, and produces an agent-action envelope binding\n// the BIP-322-authenticated agent to the (tool_name, input) tuple — at a\n// scope of `anthropic:tool(name=<tool>)`.\n\nimport { sha256 } from '@noble/hashes/sha256';\nimport {\n assertScopeGranted,\n canonicalize,\n computeActionId,\n hexEncode,\n type DelegationEnvelope,\n} from '@orangecheck/agent-core';\nimport {\n signAsAgent,\n type ActionEnvelope,\n type SignerRef,\n} from '@orangecheck/agent-signer';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalization of an Anthropic tool_use\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AnthropicToolUse {\n /** The `id` Anthropic assigns to this tool_use block (passed back as tool_use_id on result). */\n id: string;\n /** The tool name registered in your `tools` array on messages.create. */\n name: string;\n /** The model's structured input for this tool — JSON-serializable. */\n input: Record<string, unknown>;\n}\n\n/**\n * Produce the canonical bytes for an Anthropic tool_use. The bytes are what\n * the agent-action envelope's `content.hash` is a SHA-256 of.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"id\": <tool_use_id>,\n * \"input\": <canonical JSON of input>,\n * \"name\": <tool name>\n * }\n */\nexport function canonicalizeToolUse(t: AnthropicToolUse): Uint8Array {\n const canon = {\n id: t.id,\n input: t.input,\n name: t.name,\n };\n const str = canonicalize(canon as unknown as Parameters<typeof canonicalize>[0]);\n return new TextEncoder().encode(str + '\\n');\n}\n\nexport function toolUseHash(t: AnthropicToolUse): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeToolUse(t)));\n}\n\nexport function toolUseId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n t: AnthropicToolUse\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: toolUseHash(t),\n content_length: canonicalizeToolUse(t).byteLength,\n content_mime: 'application/vnd.oc-agent.anthropic-tool-use+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamped tool_use\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface StampToolUseInput {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n toolUse: AnthropicToolUse;\n /** Defaults to `anthropic:tool(name=<tool>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\n/**\n * Stamp an Anthropic tool_use without executing it. Useful for pre-\n * authorization flows or batch stamping.\n */\nexport async function stampToolUse(input: StampToolUseInput): Promise<ActionEnvelope> {\n const scopeExercised =\n input.scopeExercised ?? `anthropic:tool(name=${input.toolUse.name})`;\n\n assertScopeGranted(input.delegation, scopeExercised, 'stampToolUse');\n\n const bytes = canonicalizeToolUse(input.toolUse);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: toolUseHash(input.toolUse), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.anthropic-tool-use+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// invokeWithStamp — call the tool AND produce the stamp around it\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface InvokeWithStampInput<T> extends StampToolUseInput {\n /** Your handler that actually runs the tool. Returns whatever the tool returns. */\n call: (toolUse: AnthropicToolUse) => Promise<T>;\n}\n\nexport interface InvokeWithStampResult<T> {\n result: T;\n action: ActionEnvelope;\n}\n\n/**\n * Stamp the tool_use first, then execute the handler. The stamped envelope\n * is returned alongside the result so the caller can ship them together —\n * e.g. as a side-channel to an audit log, or as the `_oc_agent` metadata on\n * the tool_result content block back to Claude.\n */\nexport async function invokeWithStamp<T>(\n input: InvokeWithStampInput<T>\n): Promise<InvokeWithStampResult<T>> {\n const action = await stampToolUse(input);\n const result = await input.call(input.toolUse);\n return { result, action };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangecheck/agent-anthropic",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Wrap Anthropic Tool Use (Claude tool_use blocks) with OC Agent scope enforcement and emit a signed agent-action envelope per tool execution.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin",
|
|
@@ -51,14 +51,14 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@noble/hashes": "^1.5.0",
|
|
54
|
-
"@orangecheck/agent-core": "^1.
|
|
54
|
+
"@orangecheck/agent-core": "^2.1.0",
|
|
55
55
|
"@orangecheck/agent-signer": "^0.3.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/node": "^22.10.2",
|
|
59
59
|
"tsup": "^8.3.5",
|
|
60
60
|
"typescript": "^5.7.2",
|
|
61
|
-
"vitest": "^
|
|
61
|
+
"vitest": "^4.1.11"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|
package/src/index.test.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
canonicalizeToolUse,
|
|
5
|
-
postActionToFleet,
|
|
6
5
|
toolUseHash,
|
|
7
6
|
} from './index.js';
|
|
8
7
|
import type { ActionEnvelope } from '@orangecheck/agent-signer';
|
|
@@ -49,111 +48,3 @@ describe('canonicalizeToolUse', () => {
|
|
|
49
48
|
});
|
|
50
49
|
});
|
|
51
50
|
|
|
52
|
-
describe('postActionToFleet', () => {
|
|
53
|
-
const fakeAction: ActionEnvelope = {
|
|
54
|
-
v: 1,
|
|
55
|
-
kind: 'agent-action',
|
|
56
|
-
id: 'a'.repeat(64),
|
|
57
|
-
signer: { address: 'bc1qbot00000000000000000000000000000000000a', alg: 'bip322' },
|
|
58
|
-
delegation_id: 'd'.repeat(64),
|
|
59
|
-
scope_exercised: 'anthropic:tool(name=invoice.create)',
|
|
60
|
-
content: {
|
|
61
|
-
hash: 'sha256:' + 'b'.repeat(64),
|
|
62
|
-
length: 1024,
|
|
63
|
-
mime: 'application/vnd.oc-agent.anthropic-tool-use+json',
|
|
64
|
-
},
|
|
65
|
-
signed_at: '2026-04-30T12:00:00Z',
|
|
66
|
-
ots: null,
|
|
67
|
-
sig: { alg: 'bip322', pubkey: 'bc1qbot00000000000000000000000000000000000a', value: 'CCCC' },
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
it('POSTs to /api/actions with Bearer auth + the action body fields', async () => {
|
|
71
|
-
const fakeFetch = vi.fn(async (url: string | URL, init?: RequestInit) => {
|
|
72
|
-
expect(String(url)).toBe('https://fleet.example/api/actions');
|
|
73
|
-
const headers = init!.headers as Record<string, string>;
|
|
74
|
-
expect(headers.Authorization).toBe('Bearer ock_xxxx');
|
|
75
|
-
expect(headers['Content-Type']).toBe('application/json');
|
|
76
|
-
const body = JSON.parse(init!.body as string);
|
|
77
|
-
expect(body.id).toBe(fakeAction.id);
|
|
78
|
-
expect(body.delegation_id).toBe(fakeAction.delegation_id);
|
|
79
|
-
expect(body.agent_address).toBe(fakeAction.signer.address);
|
|
80
|
-
expect(body.scope_exercised).toBe(fakeAction.scope_exercised);
|
|
81
|
-
expect(body.content_hash).toBe(fakeAction.content.hash);
|
|
82
|
-
expect(body.signature).toBe(fakeAction.sig.value);
|
|
83
|
-
expect(body.project_id).toBe('proj_test');
|
|
84
|
-
return new Response(
|
|
85
|
-
JSON.stringify({
|
|
86
|
-
ok: true,
|
|
87
|
-
action: {
|
|
88
|
-
id: fakeAction.id,
|
|
89
|
-
project_id: 'proj_test',
|
|
90
|
-
delegation_id: fakeAction.delegation_id,
|
|
91
|
-
},
|
|
92
|
-
}),
|
|
93
|
-
{ status: 201, headers: { 'Content-Type': 'application/json' } }
|
|
94
|
-
);
|
|
95
|
-
});
|
|
96
|
-
const result = await postActionToFleet(fakeAction, {
|
|
97
|
-
apiToken: 'ock_xxxx',
|
|
98
|
-
projectId: 'proj_test',
|
|
99
|
-
baseUrl: 'https://fleet.example',
|
|
100
|
-
fetch: fakeFetch as unknown as typeof fetch,
|
|
101
|
-
});
|
|
102
|
-
expect(result.id).toBe(fakeAction.id);
|
|
103
|
-
expect(result.project_id).toBe('proj_test');
|
|
104
|
-
expect(fakeFetch).toHaveBeenCalledOnce();
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('throws with the server reason on non-2xx', async () => {
|
|
108
|
-
const fakeFetch = vi.fn(async () =>
|
|
109
|
-
new Response(JSON.stringify({ ok: false, reason: 'agent_must_match_delegation' }), {
|
|
110
|
-
status: 403,
|
|
111
|
-
headers: { 'Content-Type': 'application/json' },
|
|
112
|
-
})
|
|
113
|
-
);
|
|
114
|
-
await expect(
|
|
115
|
-
postActionToFleet(fakeAction, {
|
|
116
|
-
apiToken: 'ock_xxxx',
|
|
117
|
-
projectId: 'proj_test',
|
|
118
|
-
baseUrl: 'https://fleet.example',
|
|
119
|
-
fetch: fakeFetch as unknown as typeof fetch,
|
|
120
|
-
})
|
|
121
|
-
).rejects.toThrow(/agent_must_match_delegation/);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('falls back to http_<status> when the body is not JSON', async () => {
|
|
125
|
-
const fakeFetch = vi.fn(async () =>
|
|
126
|
-
new Response('upstream error', { status: 502, headers: { 'Content-Type': 'text/plain' } })
|
|
127
|
-
);
|
|
128
|
-
await expect(
|
|
129
|
-
postActionToFleet(fakeAction, {
|
|
130
|
-
apiToken: 'ock_xxxx',
|
|
131
|
-
projectId: 'proj_test',
|
|
132
|
-
baseUrl: 'https://fleet.example',
|
|
133
|
-
fetch: fakeFetch as unknown as typeof fetch,
|
|
134
|
-
})
|
|
135
|
-
).rejects.toThrow(/http_502/);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('defaults to https://fleet.ochk.io when baseUrl is omitted', async () => {
|
|
139
|
-
const fakeFetch = vi.fn(async (url: string | URL) => {
|
|
140
|
-
expect(String(url)).toBe('https://fleet.ochk.io/api/actions');
|
|
141
|
-
return new Response(
|
|
142
|
-
JSON.stringify({
|
|
143
|
-
ok: true,
|
|
144
|
-
action: {
|
|
145
|
-
id: fakeAction.id,
|
|
146
|
-
project_id: 'proj_test',
|
|
147
|
-
delegation_id: fakeAction.delegation_id,
|
|
148
|
-
},
|
|
149
|
-
}),
|
|
150
|
-
{ status: 201, headers: { 'Content-Type': 'application/json' } }
|
|
151
|
-
);
|
|
152
|
-
});
|
|
153
|
-
await postActionToFleet(fakeAction, {
|
|
154
|
-
apiToken: 'ock_xxxx',
|
|
155
|
-
projectId: 'proj_test',
|
|
156
|
-
fetch: fakeFetch as unknown as typeof fetch,
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
});
|
package/src/index.ts
CHANGED
|
@@ -138,116 +138,3 @@ export async function invokeWithStamp<T>(
|
|
|
138
138
|
const result = await input.call(input.toolUse);
|
|
139
139
|
return { result, action };
|
|
140
140
|
}
|
|
141
|
-
|
|
142
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
143
|
-
// Fleet integration: POST stamped actions to fleet.ochk.io/api/actions
|
|
144
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
145
|
-
|
|
146
|
-
export interface FleetClient {
|
|
147
|
-
/** Defaults to https://fleet.ochk.io. */
|
|
148
|
-
baseUrl?: string;
|
|
149
|
-
/** Bearer token from /settings § 03 (`ock_<hex>`). */
|
|
150
|
-
apiToken: string;
|
|
151
|
-
/** Project the action belongs to (proj_*). */
|
|
152
|
-
projectId: string;
|
|
153
|
-
/** Optional fetch override for runtimes that need it (Node 18 LTS w/o global fetch, etc). */
|
|
154
|
-
fetch?: typeof fetch;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export interface PostActionResult {
|
|
158
|
-
id: string;
|
|
159
|
-
project_id: string;
|
|
160
|
-
delegation_id: string;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* POST a stamped action envelope to fleet.ochk.io's /api/actions
|
|
165
|
-
* registry. The fleet:
|
|
166
|
-
* - re-derives the action id from canonical inputs and rejects on
|
|
167
|
-
* mismatch (tamper defense)
|
|
168
|
-
* - validates that the agent address matches the parent delegation's
|
|
169
|
-
* agent (forgery defense)
|
|
170
|
-
* - persists, fans out to Nostr (kind 30084), submits to OC Stamp,
|
|
171
|
-
* and triggers any subscribed webhooks (action.registered)
|
|
172
|
-
*
|
|
173
|
-
* Returns the registered action's id + project_id + delegation_id.
|
|
174
|
-
* Throws on non-2xx with the server's reason string.
|
|
175
|
-
*/
|
|
176
|
-
export async function postActionToFleet(
|
|
177
|
-
action: ActionEnvelope,
|
|
178
|
-
client: FleetClient
|
|
179
|
-
): Promise<PostActionResult> {
|
|
180
|
-
const baseUrl = client.baseUrl ?? 'https://fleet.ochk.io';
|
|
181
|
-
const f = client.fetch ?? fetch;
|
|
182
|
-
const body = {
|
|
183
|
-
project_id: client.projectId,
|
|
184
|
-
delegation_id: action.delegation_id,
|
|
185
|
-
agent_address: action.signer.address,
|
|
186
|
-
scope_exercised: action.scope_exercised,
|
|
187
|
-
content_hash: action.content.hash,
|
|
188
|
-
content_length: action.content.length,
|
|
189
|
-
content_mime: action.content.mime,
|
|
190
|
-
signed_at: action.signed_at,
|
|
191
|
-
signature: action.sig.value,
|
|
192
|
-
id: action.id,
|
|
193
|
-
};
|
|
194
|
-
const r = await f(`${baseUrl}/api/actions`, {
|
|
195
|
-
method: 'POST',
|
|
196
|
-
headers: {
|
|
197
|
-
'Content-Type': 'application/json',
|
|
198
|
-
Authorization: `Bearer ${client.apiToken}`,
|
|
199
|
-
},
|
|
200
|
-
body: JSON.stringify(body),
|
|
201
|
-
});
|
|
202
|
-
if (!r.ok) {
|
|
203
|
-
let reason = `http_${r.status}`;
|
|
204
|
-
try {
|
|
205
|
-
const j = (await r.json()) as { reason?: string };
|
|
206
|
-
if (j.reason) reason = j.reason;
|
|
207
|
-
} catch {
|
|
208
|
-
// body wasn't json; keep the http_<n> reason
|
|
209
|
-
}
|
|
210
|
-
throw new Error(`postActionToFleet failed: ${reason}`);
|
|
211
|
-
}
|
|
212
|
-
const j = (await r.json()) as { ok: true; action: PostActionResult };
|
|
213
|
-
return j.action;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {
|
|
217
|
-
/** Fleet credentials. If absent, behaves like invokeWithStamp (no posting). */
|
|
218
|
-
fleet?: FleetClient;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {
|
|
222
|
-
/** Set when fleet is configured AND the POST succeeded. null otherwise. */
|
|
223
|
-
posted: PostActionResult | null;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Convenience: stamp the tool_use, execute the handler, AND post the
|
|
228
|
-
* stamped envelope to fleet. Three roundtrips → one call. If `fleet`
|
|
229
|
-
* is omitted, this is identical to invokeWithStamp + posted=null.
|
|
230
|
-
*
|
|
231
|
-
* The post happens AFTER the call so a failing post doesn't prevent the
|
|
232
|
-
* tool from running. If you need pre-authorization (the post must
|
|
233
|
-
* succeed before the call runs), use stampToolUse + postActionToFleet
|
|
234
|
-
* + your call() in that order.
|
|
235
|
-
*/
|
|
236
|
-
export async function invokeWithStampAndPost<T>(
|
|
237
|
-
input: InvokeWithStampAndPostInput<T>
|
|
238
|
-
): Promise<InvokeWithStampAndPostResult<T>> {
|
|
239
|
-
const { result, action } = await invokeWithStamp(input);
|
|
240
|
-
let posted: PostActionResult | null = null;
|
|
241
|
-
if (input.fleet) {
|
|
242
|
-
try {
|
|
243
|
-
posted = await postActionToFleet(action, input.fleet);
|
|
244
|
-
} catch (err) {
|
|
245
|
-
// Best-effort post — surfacing the error here would force
|
|
246
|
-
// every caller to handle "tool ran fine but post failed",
|
|
247
|
-
// which is rarely what they want. Log and continue.
|
|
248
|
-
// eslint-disable-next-line no-console
|
|
249
|
-
console.error('[oc-agent-anthropic] postActionToFleet failed:', err);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return { result, action, posted };
|
|
253
|
-
}
|