@orangecheck/agent-anthropic 0.1.1 → 0.2.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 +1 -1
- package/dist/index.d.mts +14 -5
- package/dist/index.d.ts +14 -5
- package/dist/index.js +39 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +39 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -2
- package/src/index.test.ts +12 -12
- package/src/index.ts +80 -22
package/README.md
CHANGED
|
@@ -94,4 +94,4 @@ MIT.
|
|
|
94
94
|
- [@orangecheck/agent-core](https://www.npmjs.com/package/@orangecheck/agent-core) — verifier library.
|
|
95
95
|
- [@orangecheck/agent-mcp](https://www.npmjs.com/package/@orangecheck/agent-mcp) — same pattern, for MCP.
|
|
96
96
|
- [@orangecheck/agent-openai](https://www.npmjs.com/package/@orangecheck/agent-openai) — same pattern, for OpenAI Responses + Chat Completions.
|
|
97
|
-
- [
|
|
97
|
+
- [fleet.ochk.io/integrations/anthropic](https://fleet.ochk.io/integrations/anthropic) — the managed-tier story this slots into.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { DelegationEnvelope } from '@orangecheck/agent-core';
|
|
2
2
|
import { SignerRef, ActionEnvelope } from '@orangecheck/agent-signer';
|
|
3
|
-
import { ConsoleClient, PostActionResult } from '@orangecheck/agent-console-client';
|
|
4
|
-
export { ConsoleClient, PostActionResult, postActionToConsole } from '@orangecheck/agent-console-client';
|
|
5
3
|
|
|
6
4
|
interface AnthropicToolUse {
|
|
7
5
|
id: string;
|
|
@@ -27,13 +25,24 @@ interface InvokeWithStampResult<T> {
|
|
|
27
25
|
action: ActionEnvelope;
|
|
28
26
|
}
|
|
29
27
|
declare function invokeWithStamp<T>(input: InvokeWithStampInput<T>): Promise<InvokeWithStampResult<T>>;
|
|
30
|
-
|
|
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>;
|
|
31
40
|
interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {
|
|
32
|
-
|
|
41
|
+
fleet?: FleetClient;
|
|
33
42
|
}
|
|
34
43
|
interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {
|
|
35
44
|
posted: PostActionResult | null;
|
|
36
45
|
}
|
|
37
46
|
declare function invokeWithStampAndPost<T>(input: InvokeWithStampAndPostInput<T>): Promise<InvokeWithStampAndPostResult<T>>;
|
|
38
47
|
|
|
39
|
-
export { type AnthropicToolUse, type InvokeWithStampAndPostInput, type InvokeWithStampAndPostResult, type InvokeWithStampInput, type InvokeWithStampResult, type StampToolUseInput, canonicalizeToolUse, invokeWithStamp, invokeWithStampAndPost, stampToolUse, toolUseHash, toolUseId };
|
|
48
|
+
export { type AnthropicToolUse, type FleetClient, type InvokeWithStampAndPostInput, type InvokeWithStampAndPostResult, type InvokeWithStampInput, type InvokeWithStampResult, type PostActionResult, type StampToolUseInput, canonicalizeToolUse, invokeWithStamp, invokeWithStampAndPost, postActionToFleet, stampToolUse, toolUseHash, toolUseId };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { DelegationEnvelope } from '@orangecheck/agent-core';
|
|
2
2
|
import { SignerRef, ActionEnvelope } from '@orangecheck/agent-signer';
|
|
3
|
-
import { ConsoleClient, PostActionResult } from '@orangecheck/agent-console-client';
|
|
4
|
-
export { ConsoleClient, PostActionResult, postActionToConsole } from '@orangecheck/agent-console-client';
|
|
5
3
|
|
|
6
4
|
interface AnthropicToolUse {
|
|
7
5
|
id: string;
|
|
@@ -27,13 +25,24 @@ interface InvokeWithStampResult<T> {
|
|
|
27
25
|
action: ActionEnvelope;
|
|
28
26
|
}
|
|
29
27
|
declare function invokeWithStamp<T>(input: InvokeWithStampInput<T>): Promise<InvokeWithStampResult<T>>;
|
|
30
|
-
|
|
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>;
|
|
31
40
|
interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {
|
|
32
|
-
|
|
41
|
+
fleet?: FleetClient;
|
|
33
42
|
}
|
|
34
43
|
interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {
|
|
35
44
|
posted: PostActionResult | null;
|
|
36
45
|
}
|
|
37
46
|
declare function invokeWithStampAndPost<T>(input: InvokeWithStampAndPostInput<T>): Promise<InvokeWithStampAndPostResult<T>>;
|
|
38
47
|
|
|
39
|
-
export { type AnthropicToolUse, type InvokeWithStampAndPostInput, type InvokeWithStampAndPostResult, type InvokeWithStampInput, type InvokeWithStampResult, type StampToolUseInput, canonicalizeToolUse, invokeWithStamp, invokeWithStampAndPost, stampToolUse, toolUseHash, toolUseId };
|
|
48
|
+
export { type AnthropicToolUse, type FleetClient, type InvokeWithStampAndPostInput, type InvokeWithStampAndPostResult, type InvokeWithStampInput, type InvokeWithStampResult, type PostActionResult, type StampToolUseInput, canonicalizeToolUse, invokeWithStamp, invokeWithStampAndPost, postActionToFleet, stampToolUse, toolUseHash, toolUseId };
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
var sha256 = require('@noble/hashes/sha256');
|
|
4
4
|
var agentCore = require('@orangecheck/agent-core');
|
|
5
5
|
var agentSigner = require('@orangecheck/agent-signer');
|
|
6
|
-
var agentConsoleClient = require('@orangecheck/agent-console-client');
|
|
7
6
|
|
|
8
7
|
// src/index.ts
|
|
9
8
|
function canonicalizeToolUse(t) {
|
|
@@ -53,26 +52,58 @@ async function invokeWithStamp(input) {
|
|
|
53
52
|
const result = await input.call(input.toolUse);
|
|
54
53
|
return { result, action };
|
|
55
54
|
}
|
|
55
|
+
async function postActionToFleet(action, client) {
|
|
56
|
+
const baseUrl = client.baseUrl ?? "https://fleet.ochk.io";
|
|
57
|
+
const f = client.fetch ?? fetch;
|
|
58
|
+
const body = {
|
|
59
|
+
project_id: client.projectId,
|
|
60
|
+
delegation_id: action.delegation_id,
|
|
61
|
+
agent_address: action.signer.address,
|
|
62
|
+
scope_exercised: action.scope_exercised,
|
|
63
|
+
content_hash: action.content.hash,
|
|
64
|
+
content_length: action.content.length,
|
|
65
|
+
content_mime: action.content.mime,
|
|
66
|
+
signed_at: action.signed_at,
|
|
67
|
+
signature: action.sig.value,
|
|
68
|
+
id: action.id
|
|
69
|
+
};
|
|
70
|
+
const r = await f(`${baseUrl}/api/actions`, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: {
|
|
73
|
+
"Content-Type": "application/json",
|
|
74
|
+
Authorization: `Bearer ${client.apiToken}`
|
|
75
|
+
},
|
|
76
|
+
body: JSON.stringify(body)
|
|
77
|
+
});
|
|
78
|
+
if (!r.ok) {
|
|
79
|
+
let reason = `http_${r.status}`;
|
|
80
|
+
try {
|
|
81
|
+
const j2 = await r.json();
|
|
82
|
+
if (j2.reason) reason = j2.reason;
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
throw new Error(`postActionToFleet failed: ${reason}`);
|
|
86
|
+
}
|
|
87
|
+
const j = await r.json();
|
|
88
|
+
return j.action;
|
|
89
|
+
}
|
|
56
90
|
async function invokeWithStampAndPost(input) {
|
|
57
91
|
const { result, action } = await invokeWithStamp(input);
|
|
58
92
|
let posted = null;
|
|
59
|
-
if (input.
|
|
93
|
+
if (input.fleet) {
|
|
60
94
|
try {
|
|
61
|
-
posted = await
|
|
95
|
+
posted = await postActionToFleet(action, input.fleet);
|
|
62
96
|
} catch (err) {
|
|
63
|
-
console.error("[oc-agent-anthropic]
|
|
97
|
+
console.error("[oc-agent-anthropic] postActionToFleet failed:", err);
|
|
64
98
|
}
|
|
65
99
|
}
|
|
66
100
|
return { result, action, posted };
|
|
67
101
|
}
|
|
68
102
|
|
|
69
|
-
Object.defineProperty(exports, "postActionToConsole", {
|
|
70
|
-
enumerable: true,
|
|
71
|
-
get: function () { return agentConsoleClient.postActionToConsole; }
|
|
72
|
-
});
|
|
73
103
|
exports.canonicalizeToolUse = canonicalizeToolUse;
|
|
74
104
|
exports.invokeWithStamp = invokeWithStamp;
|
|
75
105
|
exports.invokeWithStampAndPost = invokeWithStampAndPost;
|
|
106
|
+
exports.postActionToFleet = postActionToFleet;
|
|
76
107
|
exports.stampToolUse = stampToolUse;
|
|
77
108
|
exports.toolUseHash = toolUseHash;
|
|
78
109
|
exports.toolUseId = toolUseId;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["canonicalize","hexEncode","sha256","computeActionId","parseScope","isSubScope","signAsAgent","_post"],"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 canonicalize,\n computeActionId,\n hexEncode,\n isSubScope,\n parseScope,\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 const granted = (input.delegation.scopes ?? []).map(parseScope);\n const exercisedParsed = parseScope(scopeExercised);\n if (!granted.some((g) => isSubScope(exercisedParsed, g))) {\n throw new Error(\n `stampToolUse: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`\n );\n }\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// Console integration — re-export from the shared client\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport {\n postActionToConsole,\n type ConsoleClient,\n type PostActionResult,\n} from '@orangecheck/agent-console-client';\n\nimport {\n postActionToConsole as _post,\n type ConsoleClient as _ConsoleClient,\n type PostActionResult as _PostActionResult,\n} from '@orangecheck/agent-console-client';\n\nexport interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {\n /** Console credentials. If absent, behaves like invokeWithStamp + posted: null. */\n console?: _ConsoleClient;\n}\n\nexport interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {\n /** Set when console 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 console. Three roundtrips → one call. If `console`\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 + postActionToConsole\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.console) {\n try {\n posted = await _post(action, input.console);\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] postActionToConsole failed:', err);\n }\n }\n return { result, action, posted };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["canonicalize","hexEncode","sha256","computeActionId","parseScope","isSubScope","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 canonicalize,\n computeActionId,\n hexEncode,\n isSubScope,\n parseScope,\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 const granted = (input.delegation.scopes ?? []).map(parseScope);\n const exercisedParsed = parseScope(scopeExercised);\n if (!granted.some((g) => isSubScope(exercisedParsed, g))) {\n throw new Error(\n `stampToolUse: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`\n );\n }\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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { sha256 } from '@noble/hashes/sha256';
|
|
2
2
|
import { canonicalize, hexEncode, computeActionId, parseScope, isSubScope } from '@orangecheck/agent-core';
|
|
3
3
|
import { signAsAgent } from '@orangecheck/agent-signer';
|
|
4
|
-
import { postActionToConsole } from '@orangecheck/agent-console-client';
|
|
5
|
-
export { postActionToConsole } from '@orangecheck/agent-console-client';
|
|
6
4
|
|
|
7
5
|
// src/index.ts
|
|
8
6
|
function canonicalizeToolUse(t) {
|
|
@@ -52,19 +50,54 @@ async function invokeWithStamp(input) {
|
|
|
52
50
|
const result = await input.call(input.toolUse);
|
|
53
51
|
return { result, action };
|
|
54
52
|
}
|
|
53
|
+
async function postActionToFleet(action, client) {
|
|
54
|
+
const baseUrl = client.baseUrl ?? "https://fleet.ochk.io";
|
|
55
|
+
const f = client.fetch ?? fetch;
|
|
56
|
+
const body = {
|
|
57
|
+
project_id: client.projectId,
|
|
58
|
+
delegation_id: action.delegation_id,
|
|
59
|
+
agent_address: action.signer.address,
|
|
60
|
+
scope_exercised: action.scope_exercised,
|
|
61
|
+
content_hash: action.content.hash,
|
|
62
|
+
content_length: action.content.length,
|
|
63
|
+
content_mime: action.content.mime,
|
|
64
|
+
signed_at: action.signed_at,
|
|
65
|
+
signature: action.sig.value,
|
|
66
|
+
id: action.id
|
|
67
|
+
};
|
|
68
|
+
const r = await f(`${baseUrl}/api/actions`, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: {
|
|
71
|
+
"Content-Type": "application/json",
|
|
72
|
+
Authorization: `Bearer ${client.apiToken}`
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify(body)
|
|
75
|
+
});
|
|
76
|
+
if (!r.ok) {
|
|
77
|
+
let reason = `http_${r.status}`;
|
|
78
|
+
try {
|
|
79
|
+
const j2 = await r.json();
|
|
80
|
+
if (j2.reason) reason = j2.reason;
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`postActionToFleet failed: ${reason}`);
|
|
84
|
+
}
|
|
85
|
+
const j = await r.json();
|
|
86
|
+
return j.action;
|
|
87
|
+
}
|
|
55
88
|
async function invokeWithStampAndPost(input) {
|
|
56
89
|
const { result, action } = await invokeWithStamp(input);
|
|
57
90
|
let posted = null;
|
|
58
|
-
if (input.
|
|
91
|
+
if (input.fleet) {
|
|
59
92
|
try {
|
|
60
|
-
posted = await
|
|
93
|
+
posted = await postActionToFleet(action, input.fleet);
|
|
61
94
|
} catch (err) {
|
|
62
|
-
console.error("[oc-agent-anthropic]
|
|
95
|
+
console.error("[oc-agent-anthropic] postActionToFleet failed:", err);
|
|
63
96
|
}
|
|
64
97
|
}
|
|
65
98
|
return { result, action, posted };
|
|
66
99
|
}
|
|
67
100
|
|
|
68
|
-
export { canonicalizeToolUse, invokeWithStamp, invokeWithStampAndPost, stampToolUse, toolUseHash, toolUseId };
|
|
101
|
+
export { canonicalizeToolUse, invokeWithStamp, invokeWithStampAndPost, postActionToFleet, stampToolUse, toolUseHash, toolUseId };
|
|
69
102
|
//# sourceMappingURL=index.mjs.map
|
|
70
103
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["_post"],"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 canonicalize,\n computeActionId,\n hexEncode,\n isSubScope,\n parseScope,\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 const granted = (input.delegation.scopes ?? []).map(parseScope);\n const exercisedParsed = parseScope(scopeExercised);\n if (!granted.some((g) => isSubScope(exercisedParsed, g))) {\n throw new Error(\n `stampToolUse: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`\n );\n }\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// Console integration — re-export from the shared client\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport {\n postActionToConsole,\n type ConsoleClient,\n type PostActionResult,\n} from '@orangecheck/agent-console-client';\n\nimport {\n postActionToConsole as _post,\n type ConsoleClient as _ConsoleClient,\n type PostActionResult as _PostActionResult,\n} from '@orangecheck/agent-console-client';\n\nexport interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {\n /** Console credentials. If absent, behaves like invokeWithStamp + posted: null. */\n console?: _ConsoleClient;\n}\n\nexport interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {\n /** Set when console 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 console. Three roundtrips → one call. If `console`\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 + postActionToConsole\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.console) {\n try {\n posted = await _post(action, input.console);\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] postActionToConsole failed:', err);\n }\n }\n return { result, action, posted };\n}\n"]}
|
|
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 canonicalize,\n computeActionId,\n hexEncode,\n isSubScope,\n parseScope,\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 const granted = (input.delegation.scopes ?? []).map(parseScope);\n const exercisedParsed = parseScope(scopeExercised);\n if (!granted.some((g) => isSubScope(exercisedParsed, g))) {\n throw new Error(\n `stampToolUse: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`\n );\n }\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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangecheck/agent-anthropic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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,7 +51,6 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@noble/hashes": "^1.5.0",
|
|
54
|
-
"@orangecheck/agent-console-client": "0.1.0",
|
|
55
54
|
"@orangecheck/agent-core": "^0.3.0",
|
|
56
55
|
"@orangecheck/agent-signer": "^0.1.0"
|
|
57
56
|
},
|
package/src/index.test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
canonicalizeToolUse,
|
|
5
|
-
|
|
5
|
+
postActionToFleet,
|
|
6
6
|
toolUseHash,
|
|
7
7
|
} from './index.js';
|
|
8
8
|
import type { ActionEnvelope } from '@orangecheck/agent-signer';
|
|
@@ -49,7 +49,7 @@ describe('canonicalizeToolUse', () => {
|
|
|
49
49
|
});
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
describe('
|
|
52
|
+
describe('postActionToFleet', () => {
|
|
53
53
|
const fakeAction: ActionEnvelope = {
|
|
54
54
|
v: 1,
|
|
55
55
|
kind: 'agent-action',
|
|
@@ -69,7 +69,7 @@ describe('postActionToConsole', () => {
|
|
|
69
69
|
|
|
70
70
|
it('POSTs to /api/actions with Bearer auth + the action body fields', async () => {
|
|
71
71
|
const fakeFetch = vi.fn(async (url: string | URL, init?: RequestInit) => {
|
|
72
|
-
expect(String(url)).toBe('https://
|
|
72
|
+
expect(String(url)).toBe('https://fleet.example/api/actions');
|
|
73
73
|
const headers = init!.headers as Record<string, string>;
|
|
74
74
|
expect(headers.Authorization).toBe('Bearer ock_xxxx');
|
|
75
75
|
expect(headers['Content-Type']).toBe('application/json');
|
|
@@ -93,10 +93,10 @@ describe('postActionToConsole', () => {
|
|
|
93
93
|
{ status: 201, headers: { 'Content-Type': 'application/json' } }
|
|
94
94
|
);
|
|
95
95
|
});
|
|
96
|
-
const result = await
|
|
96
|
+
const result = await postActionToFleet(fakeAction, {
|
|
97
97
|
apiToken: 'ock_xxxx',
|
|
98
98
|
projectId: 'proj_test',
|
|
99
|
-
baseUrl: 'https://
|
|
99
|
+
baseUrl: 'https://fleet.example',
|
|
100
100
|
fetch: fakeFetch as unknown as typeof fetch,
|
|
101
101
|
});
|
|
102
102
|
expect(result.id).toBe(fakeAction.id);
|
|
@@ -112,10 +112,10 @@ describe('postActionToConsole', () => {
|
|
|
112
112
|
})
|
|
113
113
|
);
|
|
114
114
|
await expect(
|
|
115
|
-
|
|
115
|
+
postActionToFleet(fakeAction, {
|
|
116
116
|
apiToken: 'ock_xxxx',
|
|
117
117
|
projectId: 'proj_test',
|
|
118
|
-
baseUrl: 'https://
|
|
118
|
+
baseUrl: 'https://fleet.example',
|
|
119
119
|
fetch: fakeFetch as unknown as typeof fetch,
|
|
120
120
|
})
|
|
121
121
|
).rejects.toThrow(/agent_must_match_delegation/);
|
|
@@ -126,18 +126,18 @@ describe('postActionToConsole', () => {
|
|
|
126
126
|
new Response('upstream error', { status: 502, headers: { 'Content-Type': 'text/plain' } })
|
|
127
127
|
);
|
|
128
128
|
await expect(
|
|
129
|
-
|
|
129
|
+
postActionToFleet(fakeAction, {
|
|
130
130
|
apiToken: 'ock_xxxx',
|
|
131
131
|
projectId: 'proj_test',
|
|
132
|
-
baseUrl: 'https://
|
|
132
|
+
baseUrl: 'https://fleet.example',
|
|
133
133
|
fetch: fakeFetch as unknown as typeof fetch,
|
|
134
134
|
})
|
|
135
135
|
).rejects.toThrow(/http_502/);
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
-
it('defaults to https://
|
|
138
|
+
it('defaults to https://fleet.ochk.io when baseUrl is omitted', async () => {
|
|
139
139
|
const fakeFetch = vi.fn(async (url: string | URL) => {
|
|
140
|
-
expect(String(url)).toBe('https://
|
|
140
|
+
expect(String(url)).toBe('https://fleet.ochk.io/api/actions');
|
|
141
141
|
return new Response(
|
|
142
142
|
JSON.stringify({
|
|
143
143
|
ok: true,
|
|
@@ -150,7 +150,7 @@ describe('postActionToConsole', () => {
|
|
|
150
150
|
{ status: 201, headers: { 'Content-Type': 'application/json' } }
|
|
151
151
|
);
|
|
152
152
|
});
|
|
153
|
-
await
|
|
153
|
+
await postActionToFleet(fakeAction, {
|
|
154
154
|
apiToken: 'ock_xxxx',
|
|
155
155
|
projectId: 'proj_test',
|
|
156
156
|
fetch: fakeFetch as unknown as typeof fetch,
|
package/src/index.ts
CHANGED
|
@@ -147,55 +147,113 @@ export async function invokeWithStamp<T>(
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
150
|
-
//
|
|
150
|
+
// Fleet integration: POST stamped actions to fleet.ochk.io/api/actions
|
|
151
151
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
152
152
|
|
|
153
|
-
export {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
export interface FleetClient {
|
|
154
|
+
/** Defaults to https://fleet.ochk.io. */
|
|
155
|
+
baseUrl?: string;
|
|
156
|
+
/** Bearer token from /settings § 03 (`ock_<hex>`). */
|
|
157
|
+
apiToken: string;
|
|
158
|
+
/** Project the action belongs to (proj_*). */
|
|
159
|
+
projectId: string;
|
|
160
|
+
/** Optional fetch override for runtimes that need it (Node 18 LTS w/o global fetch, etc). */
|
|
161
|
+
fetch?: typeof fetch;
|
|
162
|
+
}
|
|
158
163
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
+
export interface PostActionResult {
|
|
165
|
+
id: string;
|
|
166
|
+
project_id: string;
|
|
167
|
+
delegation_id: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* POST a stamped action envelope to fleet.ochk.io's /api/actions
|
|
172
|
+
* registry. The fleet:
|
|
173
|
+
* - re-derives the action id from canonical inputs and rejects on
|
|
174
|
+
* mismatch (tamper defense)
|
|
175
|
+
* - validates that the agent address matches the parent delegation's
|
|
176
|
+
* agent (forgery defense)
|
|
177
|
+
* - persists, fans out to Nostr (kind 30084), submits to OC Stamp,
|
|
178
|
+
* and triggers any subscribed webhooks (action.registered)
|
|
179
|
+
*
|
|
180
|
+
* Returns the registered action's id + project_id + delegation_id.
|
|
181
|
+
* Throws on non-2xx with the server's reason string.
|
|
182
|
+
*/
|
|
183
|
+
export async function postActionToFleet(
|
|
184
|
+
action: ActionEnvelope,
|
|
185
|
+
client: FleetClient
|
|
186
|
+
): Promise<PostActionResult> {
|
|
187
|
+
const baseUrl = client.baseUrl ?? 'https://fleet.ochk.io';
|
|
188
|
+
const f = client.fetch ?? fetch;
|
|
189
|
+
const body = {
|
|
190
|
+
project_id: client.projectId,
|
|
191
|
+
delegation_id: action.delegation_id,
|
|
192
|
+
agent_address: action.signer.address,
|
|
193
|
+
scope_exercised: action.scope_exercised,
|
|
194
|
+
content_hash: action.content.hash,
|
|
195
|
+
content_length: action.content.length,
|
|
196
|
+
content_mime: action.content.mime,
|
|
197
|
+
signed_at: action.signed_at,
|
|
198
|
+
signature: action.sig.value,
|
|
199
|
+
id: action.id,
|
|
200
|
+
};
|
|
201
|
+
const r = await f(`${baseUrl}/api/actions`, {
|
|
202
|
+
method: 'POST',
|
|
203
|
+
headers: {
|
|
204
|
+
'Content-Type': 'application/json',
|
|
205
|
+
Authorization: `Bearer ${client.apiToken}`,
|
|
206
|
+
},
|
|
207
|
+
body: JSON.stringify(body),
|
|
208
|
+
});
|
|
209
|
+
if (!r.ok) {
|
|
210
|
+
let reason = `http_${r.status}`;
|
|
211
|
+
try {
|
|
212
|
+
const j = (await r.json()) as { reason?: string };
|
|
213
|
+
if (j.reason) reason = j.reason;
|
|
214
|
+
} catch {
|
|
215
|
+
// body wasn't json; keep the http_<n> reason
|
|
216
|
+
}
|
|
217
|
+
throw new Error(`postActionToFleet failed: ${reason}`);
|
|
218
|
+
}
|
|
219
|
+
const j = (await r.json()) as { ok: true; action: PostActionResult };
|
|
220
|
+
return j.action;
|
|
221
|
+
}
|
|
164
222
|
|
|
165
223
|
export interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {
|
|
166
|
-
/**
|
|
167
|
-
|
|
224
|
+
/** Fleet credentials. If absent, behaves like invokeWithStamp (no posting). */
|
|
225
|
+
fleet?: FleetClient;
|
|
168
226
|
}
|
|
169
227
|
|
|
170
228
|
export interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {
|
|
171
|
-
/** Set when
|
|
172
|
-
posted:
|
|
229
|
+
/** Set when fleet is configured AND the POST succeeded. null otherwise. */
|
|
230
|
+
posted: PostActionResult | null;
|
|
173
231
|
}
|
|
174
232
|
|
|
175
233
|
/**
|
|
176
234
|
* Convenience: stamp the tool_use, execute the handler, AND post the
|
|
177
|
-
* stamped envelope to
|
|
178
|
-
* is omitted, this is identical to invokeWithStamp + posted
|
|
235
|
+
* stamped envelope to fleet. Three roundtrips → one call. If `fleet`
|
|
236
|
+
* is omitted, this is identical to invokeWithStamp + posted=null.
|
|
179
237
|
*
|
|
180
238
|
* The post happens AFTER the call so a failing post doesn't prevent the
|
|
181
239
|
* tool from running. If you need pre-authorization (the post must
|
|
182
|
-
* succeed before the call runs), use stampToolUse +
|
|
240
|
+
* succeed before the call runs), use stampToolUse + postActionToFleet
|
|
183
241
|
* + your call() in that order.
|
|
184
242
|
*/
|
|
185
243
|
export async function invokeWithStampAndPost<T>(
|
|
186
244
|
input: InvokeWithStampAndPostInput<T>
|
|
187
245
|
): Promise<InvokeWithStampAndPostResult<T>> {
|
|
188
246
|
const { result, action } = await invokeWithStamp(input);
|
|
189
|
-
let posted:
|
|
190
|
-
if (input.
|
|
247
|
+
let posted: PostActionResult | null = null;
|
|
248
|
+
if (input.fleet) {
|
|
191
249
|
try {
|
|
192
|
-
posted = await
|
|
250
|
+
posted = await postActionToFleet(action, input.fleet);
|
|
193
251
|
} catch (err) {
|
|
194
252
|
// Best-effort post — surfacing the error here would force
|
|
195
253
|
// every caller to handle "tool ran fine but post failed",
|
|
196
254
|
// which is rarely what they want. Log and continue.
|
|
197
255
|
// eslint-disable-next-line no-console
|
|
198
|
-
console.error('[oc-agent-anthropic]
|
|
256
|
+
console.error('[oc-agent-anthropic] postActionToFleet failed:', err);
|
|
199
257
|
}
|
|
200
258
|
}
|
|
201
259
|
return { result, action, posted };
|