@orangecheck/agent-vercel 0.1.1 → 0.3.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 +5 -1
- package/dist/index.d.mts +14 -4
- package/dist/index.d.ts +14 -4
- package/dist/index.js +40 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +41 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -4
- package/src/index.ts +72 -28
- package/src/scope-preflight.test.ts +55 -0
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @orangecheck/agent-vercel
|
|
2
2
|
|
|
3
|
+
> **Full reference:** [docs.ochk.io/sdk/agent-vercel](https://docs.ochk.io/sdk/agent-vercel) — auto-generated from the TypeScript source on every release.
|
|
4
|
+
> Hand-written prose below is the high-level overview; the docs site is the source of truth for every export, type, and signature.
|
|
5
|
+
|
|
6
|
+
|
|
3
7
|
Wrap [Vercel AI SDK](https://sdk.vercel.ai/) `tool()` invocations with [OC Agent](https://github.com/orangecheck/oc-agent-protocol) scope enforcement and emit a signed `agent-action` envelope per tool execution. Provider-agnostic — works under Anthropic, OpenAI, Cohere, or any other model the AI SDK speaks.
|
|
4
8
|
|
|
5
9
|
## Why
|
|
@@ -91,4 +95,4 @@ MIT.
|
|
|
91
95
|
- [oc-agent-protocol](https://github.com/orangecheck/oc-agent-protocol) — normative wire format.
|
|
92
96
|
- [@orangecheck/agent-core](https://www.npmjs.com/package/@orangecheck/agent-core) — verifier library.
|
|
93
97
|
- [@orangecheck/agent-mcp](https://www.npmjs.com/package/@orangecheck/agent-mcp) / [-anthropic](https://www.npmjs.com/package/@orangecheck/agent-anthropic) / [-openai](https://www.npmjs.com/package/@orangecheck/agent-openai) — sibling adapters.
|
|
94
|
-
- [
|
|
98
|
+
- [fleet.ochk.io/integrations/vercel-ai](https://fleet.ochk.io/integrations/vercel-ai) — managed-tier story.
|
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 VercelToolCall {
|
|
7
5
|
callId: string;
|
|
@@ -33,7 +31,7 @@ interface OcToolWrapped<TArgs extends Record<string, unknown>, TResult> {
|
|
|
33
31
|
description?: string;
|
|
34
32
|
execute: (args: TArgs, ctx: AgentContext & {
|
|
35
33
|
callId: string;
|
|
36
|
-
|
|
34
|
+
fleet?: FleetClient;
|
|
37
35
|
}) => Promise<{
|
|
38
36
|
result: TResult;
|
|
39
37
|
action: ActionEnvelope;
|
|
@@ -41,5 +39,17 @@ interface OcToolWrapped<TArgs extends Record<string, unknown>, TResult> {
|
|
|
41
39
|
}>;
|
|
42
40
|
}
|
|
43
41
|
declare function ocTool<TArgs extends Record<string, unknown>, TResult>(input: OcToolInput<TArgs, TResult>): OcToolWrapped<TArgs, TResult>;
|
|
42
|
+
interface FleetClient {
|
|
43
|
+
baseUrl?: string;
|
|
44
|
+
apiToken: string;
|
|
45
|
+
projectId: string;
|
|
46
|
+
fetch?: typeof fetch;
|
|
47
|
+
}
|
|
48
|
+
interface PostActionResult {
|
|
49
|
+
id: string;
|
|
50
|
+
project_id: string;
|
|
51
|
+
delegation_id: string;
|
|
52
|
+
}
|
|
53
|
+
declare function postActionToFleet(action: ActionEnvelope, client: FleetClient): Promise<PostActionResult>;
|
|
44
54
|
|
|
45
|
-
export { type AgentContext, type OcToolInput, type OcToolWrapped, type StampToolCallInput, type VercelToolCall, canonicalizeToolCall, ocTool, stampToolCall, toolCallActionId, toolCallHash };
|
|
55
|
+
export { type AgentContext, type FleetClient, type OcToolInput, type OcToolWrapped, type PostActionResult, type StampToolCallInput, type VercelToolCall, canonicalizeToolCall, ocTool, postActionToFleet, stampToolCall, toolCallActionId, toolCallHash };
|
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 VercelToolCall {
|
|
7
5
|
callId: string;
|
|
@@ -33,7 +31,7 @@ interface OcToolWrapped<TArgs extends Record<string, unknown>, TResult> {
|
|
|
33
31
|
description?: string;
|
|
34
32
|
execute: (args: TArgs, ctx: AgentContext & {
|
|
35
33
|
callId: string;
|
|
36
|
-
|
|
34
|
+
fleet?: FleetClient;
|
|
37
35
|
}) => Promise<{
|
|
38
36
|
result: TResult;
|
|
39
37
|
action: ActionEnvelope;
|
|
@@ -41,5 +39,17 @@ interface OcToolWrapped<TArgs extends Record<string, unknown>, TResult> {
|
|
|
41
39
|
}>;
|
|
42
40
|
}
|
|
43
41
|
declare function ocTool<TArgs extends Record<string, unknown>, TResult>(input: OcToolInput<TArgs, TResult>): OcToolWrapped<TArgs, TResult>;
|
|
42
|
+
interface FleetClient {
|
|
43
|
+
baseUrl?: string;
|
|
44
|
+
apiToken: string;
|
|
45
|
+
projectId: string;
|
|
46
|
+
fetch?: typeof fetch;
|
|
47
|
+
}
|
|
48
|
+
interface PostActionResult {
|
|
49
|
+
id: string;
|
|
50
|
+
project_id: string;
|
|
51
|
+
delegation_id: string;
|
|
52
|
+
}
|
|
53
|
+
declare function postActionToFleet(action: ActionEnvelope, client: FleetClient): Promise<PostActionResult>;
|
|
44
54
|
|
|
45
|
-
export { type AgentContext, type OcToolInput, type OcToolWrapped, type StampToolCallInput, type VercelToolCall, canonicalizeToolCall, ocTool, stampToolCall, toolCallActionId, toolCallHash };
|
|
55
|
+
export { type AgentContext, type FleetClient, type OcToolInput, type OcToolWrapped, type PostActionResult, type StampToolCallInput, type VercelToolCall, canonicalizeToolCall, ocTool, postActionToFleet, stampToolCall, toolCallActionId, toolCallHash };
|
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 canonicalizeToolCall(c) {
|
|
@@ -31,13 +30,7 @@ function toolCallActionId(agentAddress, signedAt, delegationId, scopeExercised,
|
|
|
31
30
|
}
|
|
32
31
|
async function stampToolCall(input) {
|
|
33
32
|
const scopeExercised = input.scopeExercised ?? `vercel:tool(verb=${input.call.verb})`;
|
|
34
|
-
|
|
35
|
-
const exercisedParsed = agentCore.parseScope(scopeExercised);
|
|
36
|
-
if (!granted.some((g) => agentCore.isSubScope(exercisedParsed, g))) {
|
|
37
|
-
throw new Error(
|
|
38
|
-
`stampToolCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`
|
|
39
|
-
);
|
|
40
|
-
}
|
|
33
|
+
agentCore.assertScopeGranted(input.delegation, scopeExercised, "stampToolCall");
|
|
41
34
|
const bytes = canonicalizeToolCall(input.call);
|
|
42
35
|
return agentSigner.signAsAgent({
|
|
43
36
|
agent: input.agent,
|
|
@@ -66,24 +59,56 @@ function ocTool(input) {
|
|
|
66
59
|
});
|
|
67
60
|
const result = await input.execute(args);
|
|
68
61
|
let posted = null;
|
|
69
|
-
if (ctx.
|
|
62
|
+
if (ctx.fleet) {
|
|
70
63
|
try {
|
|
71
|
-
posted = await
|
|
64
|
+
posted = await postActionToFleet(action, ctx.fleet);
|
|
72
65
|
} catch (err) {
|
|
73
|
-
console.error("[oc-agent-vercel]
|
|
66
|
+
console.error("[oc-agent-vercel] postActionToFleet failed:", err);
|
|
74
67
|
}
|
|
75
68
|
}
|
|
76
69
|
return { result, action, posted };
|
|
77
70
|
}
|
|
78
71
|
};
|
|
79
72
|
}
|
|
73
|
+
async function postActionToFleet(action, client) {
|
|
74
|
+
const baseUrl = client.baseUrl ?? "https://fleet.ochk.io";
|
|
75
|
+
const f = client.fetch ?? fetch;
|
|
76
|
+
const body = {
|
|
77
|
+
project_id: client.projectId,
|
|
78
|
+
delegation_id: action.delegation_id,
|
|
79
|
+
agent_address: action.signer.address,
|
|
80
|
+
scope_exercised: action.scope_exercised,
|
|
81
|
+
content_hash: action.content.hash,
|
|
82
|
+
content_length: action.content.length,
|
|
83
|
+
content_mime: action.content.mime,
|
|
84
|
+
signed_at: action.signed_at,
|
|
85
|
+
signature: action.sig.value,
|
|
86
|
+
id: action.id
|
|
87
|
+
};
|
|
88
|
+
const r = await f(`${baseUrl}/api/actions`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: {
|
|
91
|
+
"Content-Type": "application/json",
|
|
92
|
+
Authorization: `Bearer ${client.apiToken}`
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify(body)
|
|
95
|
+
});
|
|
96
|
+
if (!r.ok) {
|
|
97
|
+
let reason = `http_${r.status}`;
|
|
98
|
+
try {
|
|
99
|
+
const j2 = await r.json();
|
|
100
|
+
if (j2.reason) reason = j2.reason;
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`postActionToFleet failed: ${reason}`);
|
|
104
|
+
}
|
|
105
|
+
const j = await r.json();
|
|
106
|
+
return j.action;
|
|
107
|
+
}
|
|
80
108
|
|
|
81
|
-
Object.defineProperty(exports, "postActionToConsole", {
|
|
82
|
-
enumerable: true,
|
|
83
|
-
get: function () { return agentConsoleClient.postActionToConsole; }
|
|
84
|
-
});
|
|
85
109
|
exports.canonicalizeToolCall = canonicalizeToolCall;
|
|
86
110
|
exports.ocTool = ocTool;
|
|
111
|
+
exports.postActionToFleet = postActionToFleet;
|
|
87
112
|
exports.stampToolCall = stampToolCall;
|
|
88
113
|
exports.toolCallActionId = toolCallActionId;
|
|
89
114
|
exports.toolCallHash = toolCallHash;
|
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-vercel — wrap Vercel AI SDK `tool()` invocations in\n// OC Agent action envelopes.\n//\n// Provider-agnostic: the AI SDK is the abstraction layer. Whatever model\n// you use underneath (Anthropic, OpenAI, Cohere, …) the tool primitive is\n// the same {description, parameters, execute} shape. This adapter wraps\n// `execute` so every successful call produces a signed agent-action\n// envelope citing the active delegation.\n//\n// Two integration shapes:\n//\n// 1. ocTool({verb, parameters, execute})\n// A drop-in replacement for `tool()`. The wrapped execute runs the\n// scope check, signs the canonical (verb, args, callId) tuple,\n// then runs your real handler.\n//\n// 2. stampToolCall(input)\n// Lower-level stamping helper for cases where you have your own\n// tool-execution loop and want to emit envelopes manually.\n//\n// The agent + delegation are passed via a per-request context object\n// (or threaded through closures) — we don't depend on AsyncLocalStorage\n// to keep edge-runtime compatibility maximal.\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';\nimport {\n postActionToConsole as _post,\n type ConsoleClient,\n type PostActionResult,\n} from '@orangecheck/agent-console-client';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalization of a tool call\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface VercelToolCall {\n /** Unique id assigned by the AI SDK for this tool invocation. */\n callId: string;\n /** Tool verb / name — passed in via ocTool() or set per-call by your wrapper. */\n verb: string;\n /** JSON-serializable arguments produced by the model. */\n args: Record<string, unknown>;\n}\n\n/**\n * Produce the canonical bytes for a Vercel AI SDK tool call.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"args\": <canonical JSON of args>,\n * \"call_id\": <callId>,\n * \"verb\": <verb>\n * }\n */\nexport function canonicalizeToolCall(c: VercelToolCall): Uint8Array {\n const canon = {\n args: c.args,\n call_id: c.callId,\n verb: c.verb,\n };\n const str = canonicalize(canon as unknown as Parameters<typeof canonicalize>[0]);\n return new TextEncoder().encode(str + '\\n');\n}\n\nexport function toolCallHash(c: VercelToolCall): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeToolCall(c)));\n}\n\nexport function toolCallActionId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n c: VercelToolCall\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: toolCallHash(c),\n content_length: canonicalizeToolCall(c).byteLength,\n content_mime: 'application/vnd.oc-agent.vercel-tool-call+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamping primitive\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AgentContext {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n}\n\nexport interface StampToolCallInput extends AgentContext {\n call: VercelToolCall;\n /** Defaults to `vercel:tool(verb=<verb>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\nexport async function stampToolCall(input: StampToolCallInput): Promise<ActionEnvelope> {\n const scopeExercised =\n input.scopeExercised ?? `vercel:tool(verb=${input.call.verb})`;\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 `stampToolCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`\n );\n }\n\n const bytes = canonicalizeToolCall(input.call);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: toolCallHash(input.call), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.vercel-tool-call+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ocTool — drop-in tool() replacement\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface OcToolInput<TArgs extends Record<string, unknown>, TResult> {\n /** OC Agent verb under which this tool is exercised. */\n verb: string;\n /** Pass-through to AI SDK's `tool()` — the JSON Schema for inputs. */\n parameters?: unknown;\n /** Pass-through description shown to the model. */\n description?: string;\n /** Your real handler. Returns whatever the tool returns. */\n execute: (args: TArgs) => Promise<TResult>;\n}\n\nexport interface OcToolWrapped<TArgs extends Record<string, unknown>, TResult> {\n verb: string;\n parameters?: unknown;\n description?: string;\n /**\n * The execute fn the AI SDK calls. Receives args + a callId (the AI\n * SDK passes its own callId through — adapter glue passes it via\n * context). Optional `console` field on the context tells the\n * wrapper to fire-and-forget POST the stamped action to console.\n * ochk.io/api/actions after the underlying execute() returns.\n */\n execute: (\n args: TArgs,\n ctx: AgentContext & { callId: string; console?: ConsoleClient }\n ) => Promise<{\n result: TResult;\n action: ActionEnvelope;\n posted: PostActionResult | null;\n }>;\n}\n\n/**\n * Wrap a tool() definition so its execute path is scope-checked and\n * envelope-emitting. The exact glue depends on which AI-SDK release you're\n * on — the typical pattern is:\n *\n * const tools = {\n * 'invoice.create': tool({\n * description: '…',\n * parameters: schema,\n * execute: async (args, { toolCallId }) => {\n * const { result, action } = await invoiceCreate.execute(args, {\n * agent, delegation, callId: toolCallId,\n * });\n * await yourAuditPipeline.append(action);\n * return result;\n * },\n * }),\n * };\n *\n * const invoiceCreate = ocTool({\n * verb: 'invoice.create',\n * parameters: schema,\n * execute: async (args) => myInvoiceCreateImpl(args),\n * });\n *\n * The `ocTool` value is provider/SDK-agnostic — bring your own glue around\n * it. v1 of the adapter intentionally does not import `ai` so it works on\n * any release.\n */\nexport function ocTool<TArgs extends Record<string, unknown>, TResult>(\n input: OcToolInput<TArgs, TResult>\n): OcToolWrapped<TArgs, TResult> {\n return {\n verb: input.verb,\n parameters: input.parameters,\n description: input.description,\n execute: async (args, ctx) => {\n const call: VercelToolCall = {\n callId: ctx.callId,\n verb: input.verb,\n args,\n };\n const action = await stampToolCall({\n agent: ctx.agent,\n delegation: ctx.delegation,\n call,\n });\n const result = await input.execute(args);\n let posted: PostActionResult | null = null;\n if (ctx.console) {\n try {\n posted = await _post(action, ctx.console);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[oc-agent-vercel] postActionToConsole failed:', err);\n }\n }\n return { result, action, posted };\n },\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Console integration — re-export from the shared client. The local OcTool\n// surface above references ConsoleClient + PostActionResult, so we also\n// pull them into module scope (re-exporting alone doesn't expose the names).\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport {\n postActionToConsole,\n type ConsoleClient,\n type PostActionResult,\n} from '@orangecheck/agent-console-client';\n"]}
|
|
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-vercel — wrap Vercel AI SDK `tool()` invocations in\n// OC Agent action envelopes.\n//\n// Provider-agnostic: the AI SDK is the abstraction layer. Whatever model\n// you use underneath (Anthropic, OpenAI, Cohere, …) the tool primitive is\n// the same {description, parameters, execute} shape. This adapter wraps\n// `execute` so every successful call produces a signed agent-action\n// envelope citing the active delegation.\n//\n// Two integration shapes:\n//\n// 1. ocTool({verb, parameters, execute})\n// A drop-in replacement for `tool()`. The wrapped execute runs the\n// scope check, signs the canonical (verb, args, callId) tuple,\n// then runs your real handler.\n//\n// 2. stampToolCall(input)\n// Lower-level stamping helper for cases where you have your own\n// tool-execution loop and want to emit envelopes manually.\n//\n// The agent + delegation are passed via a per-request context object\n// (or threaded through closures) — we don't depend on AsyncLocalStorage\n// to keep edge-runtime compatibility maximal.\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 a tool call\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface VercelToolCall {\n /** Unique id assigned by the AI SDK for this tool invocation. */\n callId: string;\n /** Tool verb / name — passed in via ocTool() or set per-call by your wrapper. */\n verb: string;\n /** JSON-serializable arguments produced by the model. */\n args: Record<string, unknown>;\n}\n\n/**\n * Produce the canonical bytes for a Vercel AI SDK tool call.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"args\": <canonical JSON of args>,\n * \"call_id\": <callId>,\n * \"verb\": <verb>\n * }\n */\nexport function canonicalizeToolCall(c: VercelToolCall): Uint8Array {\n const canon = {\n args: c.args,\n call_id: c.callId,\n verb: c.verb,\n };\n const str = canonicalize(canon as unknown as Parameters<typeof canonicalize>[0]);\n return new TextEncoder().encode(str + '\\n');\n}\n\nexport function toolCallHash(c: VercelToolCall): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeToolCall(c)));\n}\n\nexport function toolCallActionId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n c: VercelToolCall\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: toolCallHash(c),\n content_length: canonicalizeToolCall(c).byteLength,\n content_mime: 'application/vnd.oc-agent.vercel-tool-call+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamping primitive\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AgentContext {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n}\n\nexport interface StampToolCallInput extends AgentContext {\n call: VercelToolCall;\n /** Defaults to `vercel:tool(verb=<verb>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\nexport async function stampToolCall(input: StampToolCallInput): Promise<ActionEnvelope> {\n const scopeExercised =\n input.scopeExercised ?? `vercel:tool(verb=${input.call.verb})`;\n\n assertScopeGranted(input.delegation, scopeExercised, 'stampToolCall');\n\n const bytes = canonicalizeToolCall(input.call);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: toolCallHash(input.call), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.vercel-tool-call+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ocTool — drop-in tool() replacement\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface OcToolInput<TArgs extends Record<string, unknown>, TResult> {\n /** OC Agent verb under which this tool is exercised. */\n verb: string;\n /** Pass-through to AI SDK's `tool()` — the JSON Schema for inputs. */\n parameters?: unknown;\n /** Pass-through description shown to the model. */\n description?: string;\n /** Your real handler. Returns whatever the tool returns. */\n execute: (args: TArgs) => Promise<TResult>;\n}\n\nexport interface OcToolWrapped<TArgs extends Record<string, unknown>, TResult> {\n verb: string;\n parameters?: unknown;\n description?: string;\n /**\n * The execute fn the AI SDK calls. Receives args + a callId (the AI\n * SDK passes its own callId through — adapter glue passes it via\n * context). Optional `fleet` field on the context tells the\n * wrapper to fire-and-forget POST the stamped action to fleet.\n * ochk.io/api/actions after the underlying execute() returns.\n */\n execute: (\n args: TArgs,\n ctx: AgentContext & { callId: string; fleet?: FleetClient }\n ) => Promise<{\n result: TResult;\n action: ActionEnvelope;\n posted: PostActionResult | null;\n }>;\n}\n\n/**\n * Wrap a tool() definition so its execute path is scope-checked and\n * envelope-emitting. The exact glue depends on which AI-SDK release you're\n * on — the typical pattern is:\n *\n * const tools = {\n * 'invoice.create': tool({\n * description: '…',\n * parameters: schema,\n * execute: async (args, { toolCallId }) => {\n * const { result, action } = await invoiceCreate.execute(args, {\n * agent, delegation, callId: toolCallId,\n * });\n * await yourAuditPipeline.append(action);\n * return result;\n * },\n * }),\n * };\n *\n * const invoiceCreate = ocTool({\n * verb: 'invoice.create',\n * parameters: schema,\n * execute: async (args) => myInvoiceCreateImpl(args),\n * });\n *\n * The `ocTool` value is provider/SDK-agnostic — bring your own glue around\n * it. v1 of the adapter intentionally does not import `ai` so it works on\n * any release.\n */\nexport function ocTool<TArgs extends Record<string, unknown>, TResult>(\n input: OcToolInput<TArgs, TResult>\n): OcToolWrapped<TArgs, TResult> {\n return {\n verb: input.verb,\n parameters: input.parameters,\n description: input.description,\n execute: async (args, ctx) => {\n const call: VercelToolCall = {\n callId: ctx.callId,\n verb: input.verb,\n args,\n };\n const action = await stampToolCall({\n agent: ctx.agent,\n delegation: ctx.delegation,\n call,\n });\n const result = await input.execute(args);\n let posted: PostActionResult | null = null;\n if (ctx.fleet) {\n try {\n posted = await postActionToFleet(action, ctx.fleet);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[oc-agent-vercel] postActionToFleet failed:', err);\n }\n }\n return { result, action, posted };\n },\n };\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. */\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/api/actions. The\n * fleet re-derives the action id, validates agent-must-match-\n * delegation, persists, fans out to Nostr (kind 30084), submits to\n * OC Stamp, and triggers any subscribed webhooks. Throws on non-2xx\n * 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\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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { sha256 } from '@noble/hashes/sha256';
|
|
2
|
-
import { canonicalize, hexEncode, computeActionId,
|
|
2
|
+
import { canonicalize, hexEncode, computeActionId, assertScopeGranted } 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 canonicalizeToolCall(c) {
|
|
@@ -30,13 +28,7 @@ function toolCallActionId(agentAddress, signedAt, delegationId, scopeExercised,
|
|
|
30
28
|
}
|
|
31
29
|
async function stampToolCall(input) {
|
|
32
30
|
const scopeExercised = input.scopeExercised ?? `vercel:tool(verb=${input.call.verb})`;
|
|
33
|
-
|
|
34
|
-
const exercisedParsed = parseScope(scopeExercised);
|
|
35
|
-
if (!granted.some((g) => isSubScope(exercisedParsed, g))) {
|
|
36
|
-
throw new Error(
|
|
37
|
-
`stampToolCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`
|
|
38
|
-
);
|
|
39
|
-
}
|
|
31
|
+
assertScopeGranted(input.delegation, scopeExercised, "stampToolCall");
|
|
40
32
|
const bytes = canonicalizeToolCall(input.call);
|
|
41
33
|
return signAsAgent({
|
|
42
34
|
agent: input.agent,
|
|
@@ -65,18 +57,53 @@ function ocTool(input) {
|
|
|
65
57
|
});
|
|
66
58
|
const result = await input.execute(args);
|
|
67
59
|
let posted = null;
|
|
68
|
-
if (ctx.
|
|
60
|
+
if (ctx.fleet) {
|
|
69
61
|
try {
|
|
70
|
-
posted = await
|
|
62
|
+
posted = await postActionToFleet(action, ctx.fleet);
|
|
71
63
|
} catch (err) {
|
|
72
|
-
console.error("[oc-agent-vercel]
|
|
64
|
+
console.error("[oc-agent-vercel] postActionToFleet failed:", err);
|
|
73
65
|
}
|
|
74
66
|
}
|
|
75
67
|
return { result, action, posted };
|
|
76
68
|
}
|
|
77
69
|
};
|
|
78
70
|
}
|
|
71
|
+
async function postActionToFleet(action, client) {
|
|
72
|
+
const baseUrl = client.baseUrl ?? "https://fleet.ochk.io";
|
|
73
|
+
const f = client.fetch ?? fetch;
|
|
74
|
+
const body = {
|
|
75
|
+
project_id: client.projectId,
|
|
76
|
+
delegation_id: action.delegation_id,
|
|
77
|
+
agent_address: action.signer.address,
|
|
78
|
+
scope_exercised: action.scope_exercised,
|
|
79
|
+
content_hash: action.content.hash,
|
|
80
|
+
content_length: action.content.length,
|
|
81
|
+
content_mime: action.content.mime,
|
|
82
|
+
signed_at: action.signed_at,
|
|
83
|
+
signature: action.sig.value,
|
|
84
|
+
id: action.id
|
|
85
|
+
};
|
|
86
|
+
const r = await f(`${baseUrl}/api/actions`, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: {
|
|
89
|
+
"Content-Type": "application/json",
|
|
90
|
+
Authorization: `Bearer ${client.apiToken}`
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify(body)
|
|
93
|
+
});
|
|
94
|
+
if (!r.ok) {
|
|
95
|
+
let reason = `http_${r.status}`;
|
|
96
|
+
try {
|
|
97
|
+
const j2 = await r.json();
|
|
98
|
+
if (j2.reason) reason = j2.reason;
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`postActionToFleet failed: ${reason}`);
|
|
102
|
+
}
|
|
103
|
+
const j = await r.json();
|
|
104
|
+
return j.action;
|
|
105
|
+
}
|
|
79
106
|
|
|
80
|
-
export { canonicalizeToolCall, ocTool, stampToolCall, toolCallActionId, toolCallHash };
|
|
107
|
+
export { canonicalizeToolCall, ocTool, postActionToFleet, stampToolCall, toolCallActionId, toolCallHash };
|
|
81
108
|
//# sourceMappingURL=index.mjs.map
|
|
82
109
|
//# 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-vercel — wrap Vercel AI SDK `tool()` invocations in\n// OC Agent action envelopes.\n//\n// Provider-agnostic: the AI SDK is the abstraction layer. Whatever model\n// you use underneath (Anthropic, OpenAI, Cohere, …) the tool primitive is\n// the same {description, parameters, execute} shape. This adapter wraps\n// `execute` so every successful call produces a signed agent-action\n// envelope citing the active delegation.\n//\n// Two integration shapes:\n//\n// 1. ocTool({verb, parameters, execute})\n// A drop-in replacement for `tool()`. The wrapped execute runs the\n// scope check, signs the canonical (verb, args, callId) tuple,\n// then runs your real handler.\n//\n// 2. stampToolCall(input)\n// Lower-level stamping helper for cases where you have your own\n// tool-execution loop and want to emit envelopes manually.\n//\n// The agent + delegation are passed via a per-request context object\n// (or threaded through closures) — we don't depend on AsyncLocalStorage\n// to keep edge-runtime compatibility maximal.\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';\nimport {\n postActionToConsole as _post,\n type ConsoleClient,\n type PostActionResult,\n} from '@orangecheck/agent-console-client';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalization of a tool call\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface VercelToolCall {\n /** Unique id assigned by the AI SDK for this tool invocation. */\n callId: string;\n /** Tool verb / name — passed in via ocTool() or set per-call by your wrapper. */\n verb: string;\n /** JSON-serializable arguments produced by the model. */\n args: Record<string, unknown>;\n}\n\n/**\n * Produce the canonical bytes for a Vercel AI SDK tool call.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"args\": <canonical JSON of args>,\n * \"call_id\": <callId>,\n * \"verb\": <verb>\n * }\n */\nexport function canonicalizeToolCall(c: VercelToolCall): Uint8Array {\n const canon = {\n args: c.args,\n call_id: c.callId,\n verb: c.verb,\n };\n const str = canonicalize(canon as unknown as Parameters<typeof canonicalize>[0]);\n return new TextEncoder().encode(str + '\\n');\n}\n\nexport function toolCallHash(c: VercelToolCall): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeToolCall(c)));\n}\n\nexport function toolCallActionId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n c: VercelToolCall\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: toolCallHash(c),\n content_length: canonicalizeToolCall(c).byteLength,\n content_mime: 'application/vnd.oc-agent.vercel-tool-call+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamping primitive\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AgentContext {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n}\n\nexport interface StampToolCallInput extends AgentContext {\n call: VercelToolCall;\n /** Defaults to `vercel:tool(verb=<verb>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\nexport async function stampToolCall(input: StampToolCallInput): Promise<ActionEnvelope> {\n const scopeExercised =\n input.scopeExercised ?? `vercel:tool(verb=${input.call.verb})`;\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 `stampToolCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`\n );\n }\n\n const bytes = canonicalizeToolCall(input.call);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: toolCallHash(input.call), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.vercel-tool-call+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ocTool — drop-in tool() replacement\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface OcToolInput<TArgs extends Record<string, unknown>, TResult> {\n /** OC Agent verb under which this tool is exercised. */\n verb: string;\n /** Pass-through to AI SDK's `tool()` — the JSON Schema for inputs. */\n parameters?: unknown;\n /** Pass-through description shown to the model. */\n description?: string;\n /** Your real handler. Returns whatever the tool returns. */\n execute: (args: TArgs) => Promise<TResult>;\n}\n\nexport interface OcToolWrapped<TArgs extends Record<string, unknown>, TResult> {\n verb: string;\n parameters?: unknown;\n description?: string;\n /**\n * The execute fn the AI SDK calls. Receives args + a callId (the AI\n * SDK passes its own callId through — adapter glue passes it via\n * context). Optional `console` field on the context tells the\n * wrapper to fire-and-forget POST the stamped action to console.\n * ochk.io/api/actions after the underlying execute() returns.\n */\n execute: (\n args: TArgs,\n ctx: AgentContext & { callId: string; console?: ConsoleClient }\n ) => Promise<{\n result: TResult;\n action: ActionEnvelope;\n posted: PostActionResult | null;\n }>;\n}\n\n/**\n * Wrap a tool() definition so its execute path is scope-checked and\n * envelope-emitting. The exact glue depends on which AI-SDK release you're\n * on — the typical pattern is:\n *\n * const tools = {\n * 'invoice.create': tool({\n * description: '…',\n * parameters: schema,\n * execute: async (args, { toolCallId }) => {\n * const { result, action } = await invoiceCreate.execute(args, {\n * agent, delegation, callId: toolCallId,\n * });\n * await yourAuditPipeline.append(action);\n * return result;\n * },\n * }),\n * };\n *\n * const invoiceCreate = ocTool({\n * verb: 'invoice.create',\n * parameters: schema,\n * execute: async (args) => myInvoiceCreateImpl(args),\n * });\n *\n * The `ocTool` value is provider/SDK-agnostic — bring your own glue around\n * it. v1 of the adapter intentionally does not import `ai` so it works on\n * any release.\n */\nexport function ocTool<TArgs extends Record<string, unknown>, TResult>(\n input: OcToolInput<TArgs, TResult>\n): OcToolWrapped<TArgs, TResult> {\n return {\n verb: input.verb,\n parameters: input.parameters,\n description: input.description,\n execute: async (args, ctx) => {\n const call: VercelToolCall = {\n callId: ctx.callId,\n verb: input.verb,\n args,\n };\n const action = await stampToolCall({\n agent: ctx.agent,\n delegation: ctx.delegation,\n call,\n });\n const result = await input.execute(args);\n let posted: PostActionResult | null = null;\n if (ctx.console) {\n try {\n posted = await _post(action, ctx.console);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[oc-agent-vercel] postActionToConsole failed:', err);\n }\n }\n return { result, action, posted };\n },\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Console integration — re-export from the shared client. The local OcTool\n// surface above references ConsoleClient + PostActionResult, so we also\n// pull them into module scope (re-exporting alone doesn't expose the names).\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport {\n postActionToConsole,\n type ConsoleClient,\n type PostActionResult,\n} from '@orangecheck/agent-console-client';\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["j"],"mappings":"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","file":"index.mjs","sourcesContent":["// @orangecheck/agent-vercel — wrap Vercel AI SDK `tool()` invocations in\n// OC Agent action envelopes.\n//\n// Provider-agnostic: the AI SDK is the abstraction layer. Whatever model\n// you use underneath (Anthropic, OpenAI, Cohere, …) the tool primitive is\n// the same {description, parameters, execute} shape. This adapter wraps\n// `execute` so every successful call produces a signed agent-action\n// envelope citing the active delegation.\n//\n// Two integration shapes:\n//\n// 1. ocTool({verb, parameters, execute})\n// A drop-in replacement for `tool()`. The wrapped execute runs the\n// scope check, signs the canonical (verb, args, callId) tuple,\n// then runs your real handler.\n//\n// 2. stampToolCall(input)\n// Lower-level stamping helper for cases where you have your own\n// tool-execution loop and want to emit envelopes manually.\n//\n// The agent + delegation are passed via a per-request context object\n// (or threaded through closures) — we don't depend on AsyncLocalStorage\n// to keep edge-runtime compatibility maximal.\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 a tool call\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface VercelToolCall {\n /** Unique id assigned by the AI SDK for this tool invocation. */\n callId: string;\n /** Tool verb / name — passed in via ocTool() or set per-call by your wrapper. */\n verb: string;\n /** JSON-serializable arguments produced by the model. */\n args: Record<string, unknown>;\n}\n\n/**\n * Produce the canonical bytes for a Vercel AI SDK tool call.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"args\": <canonical JSON of args>,\n * \"call_id\": <callId>,\n * \"verb\": <verb>\n * }\n */\nexport function canonicalizeToolCall(c: VercelToolCall): Uint8Array {\n const canon = {\n args: c.args,\n call_id: c.callId,\n verb: c.verb,\n };\n const str = canonicalize(canon as unknown as Parameters<typeof canonicalize>[0]);\n return new TextEncoder().encode(str + '\\n');\n}\n\nexport function toolCallHash(c: VercelToolCall): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeToolCall(c)));\n}\n\nexport function toolCallActionId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n c: VercelToolCall\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: toolCallHash(c),\n content_length: canonicalizeToolCall(c).byteLength,\n content_mime: 'application/vnd.oc-agent.vercel-tool-call+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamping primitive\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AgentContext {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n}\n\nexport interface StampToolCallInput extends AgentContext {\n call: VercelToolCall;\n /** Defaults to `vercel:tool(verb=<verb>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\nexport async function stampToolCall(input: StampToolCallInput): Promise<ActionEnvelope> {\n const scopeExercised =\n input.scopeExercised ?? `vercel:tool(verb=${input.call.verb})`;\n\n assertScopeGranted(input.delegation, scopeExercised, 'stampToolCall');\n\n const bytes = canonicalizeToolCall(input.call);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: toolCallHash(input.call), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.vercel-tool-call+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ocTool — drop-in tool() replacement\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface OcToolInput<TArgs extends Record<string, unknown>, TResult> {\n /** OC Agent verb under which this tool is exercised. */\n verb: string;\n /** Pass-through to AI SDK's `tool()` — the JSON Schema for inputs. */\n parameters?: unknown;\n /** Pass-through description shown to the model. */\n description?: string;\n /** Your real handler. Returns whatever the tool returns. */\n execute: (args: TArgs) => Promise<TResult>;\n}\n\nexport interface OcToolWrapped<TArgs extends Record<string, unknown>, TResult> {\n verb: string;\n parameters?: unknown;\n description?: string;\n /**\n * The execute fn the AI SDK calls. Receives args + a callId (the AI\n * SDK passes its own callId through — adapter glue passes it via\n * context). Optional `fleet` field on the context tells the\n * wrapper to fire-and-forget POST the stamped action to fleet.\n * ochk.io/api/actions after the underlying execute() returns.\n */\n execute: (\n args: TArgs,\n ctx: AgentContext & { callId: string; fleet?: FleetClient }\n ) => Promise<{\n result: TResult;\n action: ActionEnvelope;\n posted: PostActionResult | null;\n }>;\n}\n\n/**\n * Wrap a tool() definition so its execute path is scope-checked and\n * envelope-emitting. The exact glue depends on which AI-SDK release you're\n * on — the typical pattern is:\n *\n * const tools = {\n * 'invoice.create': tool({\n * description: '…',\n * parameters: schema,\n * execute: async (args, { toolCallId }) => {\n * const { result, action } = await invoiceCreate.execute(args, {\n * agent, delegation, callId: toolCallId,\n * });\n * await yourAuditPipeline.append(action);\n * return result;\n * },\n * }),\n * };\n *\n * const invoiceCreate = ocTool({\n * verb: 'invoice.create',\n * parameters: schema,\n * execute: async (args) => myInvoiceCreateImpl(args),\n * });\n *\n * The `ocTool` value is provider/SDK-agnostic — bring your own glue around\n * it. v1 of the adapter intentionally does not import `ai` so it works on\n * any release.\n */\nexport function ocTool<TArgs extends Record<string, unknown>, TResult>(\n input: OcToolInput<TArgs, TResult>\n): OcToolWrapped<TArgs, TResult> {\n return {\n verb: input.verb,\n parameters: input.parameters,\n description: input.description,\n execute: async (args, ctx) => {\n const call: VercelToolCall = {\n callId: ctx.callId,\n verb: input.verb,\n args,\n };\n const action = await stampToolCall({\n agent: ctx.agent,\n delegation: ctx.delegation,\n call,\n });\n const result = await input.execute(args);\n let posted: PostActionResult | null = null;\n if (ctx.fleet) {\n try {\n posted = await postActionToFleet(action, ctx.fleet);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[oc-agent-vercel] postActionToFleet failed:', err);\n }\n }\n return { result, action, posted };\n },\n };\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. */\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/api/actions. The\n * fleet re-derives the action id, validates agent-must-match-\n * delegation, persists, fans out to Nostr (kind 30084), submits to\n * OC Stamp, and triggers any subscribed webhooks. Throws on non-2xx\n * 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\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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangecheck/agent-vercel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Wrap Vercel AI SDK tool() invocations with OC Agent scope enforcement and emit a signed agent-action envelope per tool execution. Provider-agnostic — works under Anthropic, OpenAI, Cohere, etc.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin",
|
|
@@ -51,9 +51,8 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@noble/hashes": "^1.5.0",
|
|
54
|
-
"@orangecheck/agent-
|
|
55
|
-
"@orangecheck/agent-
|
|
56
|
-
"@orangecheck/agent-signer": "^0.1.0"
|
|
54
|
+
"@orangecheck/agent-core": "^1.2.0",
|
|
55
|
+
"@orangecheck/agent-signer": "^0.3.0"
|
|
57
56
|
},
|
|
58
57
|
"devDependencies": {
|
|
59
58
|
"@types/node": "^22.10.2",
|
package/src/index.ts
CHANGED
|
@@ -24,11 +24,10 @@
|
|
|
24
24
|
|
|
25
25
|
import { sha256 } from '@noble/hashes/sha256';
|
|
26
26
|
import {
|
|
27
|
+
assertScopeGranted,
|
|
27
28
|
canonicalize,
|
|
28
29
|
computeActionId,
|
|
29
30
|
hexEncode,
|
|
30
|
-
isSubScope,
|
|
31
|
-
parseScope,
|
|
32
31
|
type DelegationEnvelope,
|
|
33
32
|
} from '@orangecheck/agent-core';
|
|
34
33
|
import {
|
|
@@ -36,11 +35,6 @@ import {
|
|
|
36
35
|
type ActionEnvelope,
|
|
37
36
|
type SignerRef,
|
|
38
37
|
} from '@orangecheck/agent-signer';
|
|
39
|
-
import {
|
|
40
|
-
postActionToConsole as _post,
|
|
41
|
-
type ConsoleClient,
|
|
42
|
-
type PostActionResult,
|
|
43
|
-
} from '@orangecheck/agent-console-client';
|
|
44
38
|
|
|
45
39
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
40
|
// Canonicalization of a tool call
|
|
@@ -117,13 +111,7 @@ export async function stampToolCall(input: StampToolCallInput): Promise<ActionEn
|
|
|
117
111
|
const scopeExercised =
|
|
118
112
|
input.scopeExercised ?? `vercel:tool(verb=${input.call.verb})`;
|
|
119
113
|
|
|
120
|
-
|
|
121
|
-
const exercisedParsed = parseScope(scopeExercised);
|
|
122
|
-
if (!granted.some((g) => isSubScope(exercisedParsed, g))) {
|
|
123
|
-
throw new Error(
|
|
124
|
-
`stampToolCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`
|
|
125
|
-
);
|
|
126
|
-
}
|
|
114
|
+
assertScopeGranted(input.delegation, scopeExercised, 'stampToolCall');
|
|
127
115
|
|
|
128
116
|
const bytes = canonicalizeToolCall(input.call);
|
|
129
117
|
return signAsAgent({
|
|
@@ -158,13 +146,13 @@ export interface OcToolWrapped<TArgs extends Record<string, unknown>, TResult> {
|
|
|
158
146
|
/**
|
|
159
147
|
* The execute fn the AI SDK calls. Receives args + a callId (the AI
|
|
160
148
|
* SDK passes its own callId through — adapter glue passes it via
|
|
161
|
-
* context). Optional `
|
|
162
|
-
* wrapper to fire-and-forget POST the stamped action to
|
|
149
|
+
* context). Optional `fleet` field on the context tells the
|
|
150
|
+
* wrapper to fire-and-forget POST the stamped action to fleet.
|
|
163
151
|
* ochk.io/api/actions after the underlying execute() returns.
|
|
164
152
|
*/
|
|
165
153
|
execute: (
|
|
166
154
|
args: TArgs,
|
|
167
|
-
ctx: AgentContext & { callId: string;
|
|
155
|
+
ctx: AgentContext & { callId: string; fleet?: FleetClient }
|
|
168
156
|
) => Promise<{
|
|
169
157
|
result: TResult;
|
|
170
158
|
action: ActionEnvelope;
|
|
@@ -221,12 +209,12 @@ export function ocTool<TArgs extends Record<string, unknown>, TResult>(
|
|
|
221
209
|
});
|
|
222
210
|
const result = await input.execute(args);
|
|
223
211
|
let posted: PostActionResult | null = null;
|
|
224
|
-
if (ctx.
|
|
212
|
+
if (ctx.fleet) {
|
|
225
213
|
try {
|
|
226
|
-
posted = await
|
|
214
|
+
posted = await postActionToFleet(action, ctx.fleet);
|
|
227
215
|
} catch (err) {
|
|
228
216
|
// eslint-disable-next-line no-console
|
|
229
|
-
console.error('[oc-agent-vercel]
|
|
217
|
+
console.error('[oc-agent-vercel] postActionToFleet failed:', err);
|
|
230
218
|
}
|
|
231
219
|
}
|
|
232
220
|
return { result, action, posted };
|
|
@@ -235,13 +223,69 @@ export function ocTool<TArgs extends Record<string, unknown>, TResult>(
|
|
|
235
223
|
}
|
|
236
224
|
|
|
237
225
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
238
|
-
//
|
|
239
|
-
// surface above references ConsoleClient + PostActionResult, so we also
|
|
240
|
-
// pull them into module scope (re-exporting alone doesn't expose the names).
|
|
226
|
+
// Fleet integration: POST stamped actions to fleet.ochk.io/api/actions
|
|
241
227
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
242
228
|
|
|
243
|
-
export {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
229
|
+
export interface FleetClient {
|
|
230
|
+
/** Defaults to https://fleet.ochk.io. */
|
|
231
|
+
baseUrl?: string;
|
|
232
|
+
/** Bearer token from /settings § 03 (`ock_<hex>`). */
|
|
233
|
+
apiToken: string;
|
|
234
|
+
/** Project the action belongs to (proj_*). */
|
|
235
|
+
projectId: string;
|
|
236
|
+
/** Optional fetch override for runtimes that need it. */
|
|
237
|
+
fetch?: typeof fetch;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface PostActionResult {
|
|
241
|
+
id: string;
|
|
242
|
+
project_id: string;
|
|
243
|
+
delegation_id: string;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* POST a stamped action envelope to fleet.ochk.io/api/actions. The
|
|
248
|
+
* fleet re-derives the action id, validates agent-must-match-
|
|
249
|
+
* delegation, persists, fans out to Nostr (kind 30084), submits to
|
|
250
|
+
* OC Stamp, and triggers any subscribed webhooks. Throws on non-2xx
|
|
251
|
+
* with the server's reason string.
|
|
252
|
+
*/
|
|
253
|
+
export async function postActionToFleet(
|
|
254
|
+
action: ActionEnvelope,
|
|
255
|
+
client: FleetClient
|
|
256
|
+
): Promise<PostActionResult> {
|
|
257
|
+
const baseUrl = client.baseUrl ?? 'https://fleet.ochk.io';
|
|
258
|
+
const f = client.fetch ?? fetch;
|
|
259
|
+
const body = {
|
|
260
|
+
project_id: client.projectId,
|
|
261
|
+
delegation_id: action.delegation_id,
|
|
262
|
+
agent_address: action.signer.address,
|
|
263
|
+
scope_exercised: action.scope_exercised,
|
|
264
|
+
content_hash: action.content.hash,
|
|
265
|
+
content_length: action.content.length,
|
|
266
|
+
content_mime: action.content.mime,
|
|
267
|
+
signed_at: action.signed_at,
|
|
268
|
+
signature: action.sig.value,
|
|
269
|
+
id: action.id,
|
|
270
|
+
};
|
|
271
|
+
const r = await f(`${baseUrl}/api/actions`, {
|
|
272
|
+
method: 'POST',
|
|
273
|
+
headers: {
|
|
274
|
+
'Content-Type': 'application/json',
|
|
275
|
+
Authorization: `Bearer ${client.apiToken}`,
|
|
276
|
+
},
|
|
277
|
+
body: JSON.stringify(body),
|
|
278
|
+
});
|
|
279
|
+
if (!r.ok) {
|
|
280
|
+
let reason = `http_${r.status}`;
|
|
281
|
+
try {
|
|
282
|
+
const j = (await r.json()) as { reason?: string };
|
|
283
|
+
if (j.reason) reason = j.reason;
|
|
284
|
+
} catch {
|
|
285
|
+
// body wasn't json
|
|
286
|
+
}
|
|
287
|
+
throw new Error(`postActionToFleet failed: ${reason}`);
|
|
288
|
+
}
|
|
289
|
+
const j = (await r.json()) as { ok: true; action: PostActionResult };
|
|
290
|
+
return j.action;
|
|
291
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { ScopeNotGrantedError } from '@orangecheck/agent-core';
|
|
4
|
+
|
|
5
|
+
import { stampToolCall } from './index.js';
|
|
6
|
+
|
|
7
|
+
const AGENT = 'bc1qagent0000000000000000000000000000000000';
|
|
8
|
+
const sign = () => async (msg: string) => Buffer.from(msg, 'utf8').toString('base64');
|
|
9
|
+
|
|
10
|
+
const SEALED = {
|
|
11
|
+
v: 1,
|
|
12
|
+
alg: 'x25519-xchacha20poly1305',
|
|
13
|
+
recipients: [{ device_id: 'dev1', ct: 'AA' }],
|
|
14
|
+
ct: 'BB',
|
|
15
|
+
nonce: 'CC',
|
|
16
|
+
} as never;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* This adapter used to open-code the pre-flight scope check. It now routes to
|
|
20
|
+
* agent-core's `assertScopeGranted`, and these assert the routing rather than
|
|
21
|
+
* the check — agent-core owns testing the check itself.
|
|
22
|
+
*
|
|
23
|
+
* The case that matters is the encrypted one: a v1.2 private delegation has no
|
|
24
|
+
* readable `scopes`, and the old `?? []` reported that as "not a sub-scope of
|
|
25
|
+
* any granted scope" — pointing the integrator at a scope string that was
|
|
26
|
+
* probably fine, when the actual need was to decrypt the grant.
|
|
27
|
+
*/
|
|
28
|
+
describe('stampToolCall scope pre-flight', () => {
|
|
29
|
+
const base = {
|
|
30
|
+
agent: { address: AGENT, sign: sign() },
|
|
31
|
+
toolCall: { toolCallId: 't1', toolName: 'search', args: { q: 'x' } },
|
|
32
|
+
} as never;
|
|
33
|
+
|
|
34
|
+
it('names encrypted scopes rather than blaming the scope string', async () => {
|
|
35
|
+
const err = await stampToolCall({
|
|
36
|
+
...(base as object),
|
|
37
|
+
delegation: { scopes_encrypted: SEALED },
|
|
38
|
+
scopeExercised: 'vercel:tool(name=search)',
|
|
39
|
+
} as never).catch((e: unknown) => e);
|
|
40
|
+
|
|
41
|
+
expect(err).toBeInstanceOf(ScopeNotGrantedError);
|
|
42
|
+
expect((err as ScopeNotGrantedError).reason).toBe('scopes_encrypted');
|
|
43
|
+
expect((err as Error).message).toMatch(/decryptPrivateScopes/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('treats an absent grant as granting nothing', async () => {
|
|
47
|
+
const err = await stampToolCall({
|
|
48
|
+
...(base as object),
|
|
49
|
+
delegation: {},
|
|
50
|
+
scopeExercised: 'vercel:tool(name=search)',
|
|
51
|
+
} as never).catch((e: unknown) => e);
|
|
52
|
+
|
|
53
|
+
expect((err as ScopeNotGrantedError).reason).toBe('no_scopes');
|
|
54
|
+
});
|
|
55
|
+
});
|