@orangecheck/agent-openai 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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # @orangecheck/agent-openai
2
2
 
3
+ > **Full reference:** [docs.ochk.io/sdk/agent-openai](https://docs.ochk.io/sdk/agent-openai) — 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 [OpenAI function calls](https://platform.openai.com/docs/guides/function-calling) with [OC Agent](https://github.com/orangecheck/oc-agent-protocol) scope enforcement and emit a signed `agent-action` envelope per invocation. Covers both the new **Responses API** (`function_call` items) and the legacy **Chat Completions** tool path (`tool_calls`).
4
8
 
5
9
  ## Why
@@ -72,4 +76,4 @@ MIT.
72
76
  - [@orangecheck/agent-core](https://www.npmjs.com/package/@orangecheck/agent-core) — verifier library.
73
77
  - [@orangecheck/agent-mcp](https://www.npmjs.com/package/@orangecheck/agent-mcp) — same pattern, for MCP.
74
78
  - [@orangecheck/agent-anthropic](https://www.npmjs.com/package/@orangecheck/agent-anthropic) — same pattern, for Claude Tool Use.
75
- - [console.ochk.io/integrations/openai](https://console.ochk.io/integrations/openai) — managed-tier story.
79
+ - [fleet.ochk.io/integrations/openai](https://fleet.ochk.io/integrations/openai) — 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 OpenAiFunctionCall {
7
5
  id: string;
@@ -29,13 +27,24 @@ interface InvokeWithStampResult<T> {
29
27
  call: OpenAiFunctionCall;
30
28
  }
31
29
  declare function invokeWithStamp<T>(input: InvokeWithStampInput<T>): Promise<InvokeWithStampResult<T>>;
32
-
30
+ interface FleetClient {
31
+ baseUrl?: string;
32
+ apiToken: string;
33
+ projectId: string;
34
+ fetch?: typeof fetch;
35
+ }
36
+ interface PostActionResult {
37
+ id: string;
38
+ project_id: string;
39
+ delegation_id: string;
40
+ }
41
+ declare function postActionToFleet(action: ActionEnvelope, client: FleetClient): Promise<PostActionResult>;
33
42
  interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {
34
- console?: ConsoleClient;
43
+ fleet?: FleetClient;
35
44
  }
36
45
  interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {
37
46
  posted: PostActionResult | null;
38
47
  }
39
48
  declare function invokeWithStampAndPost<T>(input: InvokeWithStampAndPostInput<T>): Promise<InvokeWithStampAndPostResult<T>>;
40
49
 
41
- export { type InvokeWithStampAndPostInput, type InvokeWithStampAndPostResult, type InvokeWithStampInput, type InvokeWithStampResult, type OpenAiFunctionCall, type StampFunctionCallInput, canonicalizeFunctionCall, functionCallActionId, functionCallHash, invokeWithStamp, invokeWithStampAndPost, parseFunctionCall, stampFunctionCall };
50
+ export { type FleetClient, type InvokeWithStampAndPostInput, type InvokeWithStampAndPostResult, type InvokeWithStampInput, type InvokeWithStampResult, type OpenAiFunctionCall, type PostActionResult, type StampFunctionCallInput, canonicalizeFunctionCall, functionCallActionId, functionCallHash, invokeWithStamp, invokeWithStampAndPost, parseFunctionCall, postActionToFleet, stampFunctionCall };
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 OpenAiFunctionCall {
7
5
  id: string;
@@ -29,13 +27,24 @@ interface InvokeWithStampResult<T> {
29
27
  call: OpenAiFunctionCall;
30
28
  }
31
29
  declare function invokeWithStamp<T>(input: InvokeWithStampInput<T>): Promise<InvokeWithStampResult<T>>;
32
-
30
+ interface FleetClient {
31
+ baseUrl?: string;
32
+ apiToken: string;
33
+ projectId: string;
34
+ fetch?: typeof fetch;
35
+ }
36
+ interface PostActionResult {
37
+ id: string;
38
+ project_id: string;
39
+ delegation_id: string;
40
+ }
41
+ declare function postActionToFleet(action: ActionEnvelope, client: FleetClient): Promise<PostActionResult>;
33
42
  interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {
34
- console?: ConsoleClient;
43
+ fleet?: FleetClient;
35
44
  }
36
45
  interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {
37
46
  posted: PostActionResult | null;
38
47
  }
39
48
  declare function invokeWithStampAndPost<T>(input: InvokeWithStampAndPostInput<T>): Promise<InvokeWithStampAndPostResult<T>>;
40
49
 
41
- export { type InvokeWithStampAndPostInput, type InvokeWithStampAndPostResult, type InvokeWithStampInput, type InvokeWithStampResult, type OpenAiFunctionCall, type StampFunctionCallInput, canonicalizeFunctionCall, functionCallActionId, functionCallHash, invokeWithStamp, invokeWithStampAndPost, parseFunctionCall, stampFunctionCall };
50
+ export { type FleetClient, type InvokeWithStampAndPostInput, type InvokeWithStampAndPostResult, type InvokeWithStampInput, type InvokeWithStampResult, type OpenAiFunctionCall, type PostActionResult, type StampFunctionCallInput, canonicalizeFunctionCall, functionCallActionId, functionCallHash, invokeWithStamp, invokeWithStampAndPost, parseFunctionCall, postActionToFleet, stampFunctionCall };
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 parseFunctionCall(raw) {
@@ -65,13 +64,7 @@ function functionCallActionId(agentAddress, signedAt, delegationId, scopeExercis
65
64
  async function stampFunctionCall(input) {
66
65
  const fc = typeof input.call === "object" && input.call !== null && "arguments" in input.call && typeof input.call.arguments !== "string" ? input.call : parseFunctionCall(input.call);
67
66
  const scopeExercised = input.scopeExercised ?? `openai:function(name=${fc.name})`;
68
- const granted = (input.delegation.scopes ?? []).map(agentCore.parseScope);
69
- const exercisedParsed = agentCore.parseScope(scopeExercised);
70
- if (!granted.some((g) => agentCore.isSubScope(exercisedParsed, g))) {
71
- throw new Error(
72
- `stampFunctionCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`
73
- );
74
- }
67
+ agentCore.assertScopeGranted(input.delegation, scopeExercised, "stampFunctionCall");
75
68
  const bytes = canonicalizeFunctionCall(fc);
76
69
  return agentSigner.signAsAgent({
77
70
  agent: input.agent,
@@ -88,29 +81,61 @@ async function invokeWithStamp(input) {
88
81
  const result = await input.execute(fc);
89
82
  return { result, action, call: fc };
90
83
  }
84
+ async function postActionToFleet(action, client) {
85
+ const baseUrl = client.baseUrl ?? "https://fleet.ochk.io";
86
+ const f = client.fetch ?? fetch;
87
+ const body = {
88
+ project_id: client.projectId,
89
+ delegation_id: action.delegation_id,
90
+ agent_address: action.signer.address,
91
+ scope_exercised: action.scope_exercised,
92
+ content_hash: action.content.hash,
93
+ content_length: action.content.length,
94
+ content_mime: action.content.mime,
95
+ signed_at: action.signed_at,
96
+ signature: action.sig.value,
97
+ id: action.id
98
+ };
99
+ const r = await f(`${baseUrl}/api/actions`, {
100
+ method: "POST",
101
+ headers: {
102
+ "Content-Type": "application/json",
103
+ Authorization: `Bearer ${client.apiToken}`
104
+ },
105
+ body: JSON.stringify(body)
106
+ });
107
+ if (!r.ok) {
108
+ let reason = `http_${r.status}`;
109
+ try {
110
+ const j2 = await r.json();
111
+ if (j2.reason) reason = j2.reason;
112
+ } catch {
113
+ }
114
+ throw new Error(`postActionToFleet failed: ${reason}`);
115
+ }
116
+ const j = await r.json();
117
+ return j.action;
118
+ }
91
119
  async function invokeWithStampAndPost(input) {
92
120
  const { result, action, call } = await invokeWithStamp(input);
93
121
  let posted = null;
94
- if (input.console) {
122
+ if (input.fleet) {
95
123
  try {
96
- posted = await agentConsoleClient.postActionToConsole(action, input.console);
124
+ posted = await postActionToFleet(action, input.fleet);
97
125
  } catch (err) {
98
- console.error("[oc-agent-openai] postActionToConsole failed:", err);
126
+ console.error("[oc-agent-openai] postActionToFleet failed:", err);
99
127
  }
100
128
  }
101
129
  return { result, action, call, posted };
102
130
  }
103
131
 
104
- Object.defineProperty(exports, "postActionToConsole", {
105
- enumerable: true,
106
- get: function () { return agentConsoleClient.postActionToConsole; }
107
- });
108
132
  exports.canonicalizeFunctionCall = canonicalizeFunctionCall;
109
133
  exports.functionCallActionId = functionCallActionId;
110
134
  exports.functionCallHash = functionCallHash;
111
135
  exports.invokeWithStamp = invokeWithStamp;
112
136
  exports.invokeWithStampAndPost = invokeWithStampAndPost;
113
137
  exports.parseFunctionCall = parseFunctionCall;
138
+ exports.postActionToFleet = postActionToFleet;
114
139
  exports.stampFunctionCall = stampFunctionCall;
115
140
  //# sourceMappingURL=index.js.map
116
141
  //# sourceMappingURL=index.js.map
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-openai — wrap OpenAI function calls in OC Agent\n// action envelopes.\n//\n// Covers both surfaces:\n// - Responses API: response.output → tool_call items with\n// { type: 'function_call', name, arguments, call_id }\n// (`arguments` is a JSON string)\n// - Chat Completions: message.tool_calls[i] = { id, function: { name, arguments } }\n// (`arguments` is also a JSON string)\n//\n// Both shapes normalize through `parseFunctionCall()` to the canonical\n// {id, name, arguments} form before hashing.\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// Shape normalization\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface OpenAiFunctionCall {\n /** OpenAI's `id` for this call (Chat Completions: `tool_call.id`; Responses: `call_id`). */\n id: string;\n /** The function name. */\n name: string;\n /** The (already-parsed) function arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Normalize either a Chat Completions `tool_call` or a Responses\n * `function_call` item into our canonical {id, name, arguments} shape.\n *\n * Accepts:\n * - { id, function: { name, arguments } } (Chat Completions)\n * - { call_id, name, arguments } (Responses, function_call)\n * - { id, name, arguments } (already normalized)\n *\n * `arguments` may be either a JSON string (OpenAI's wire shape) or an\n * already-parsed object (your post-processing). Both work.\n */\nexport function parseFunctionCall(raw: unknown): OpenAiFunctionCall {\n if (raw == null || typeof raw !== 'object') {\n throw new TypeError('parseFunctionCall: input is not an object');\n }\n const r = raw as Record<string, unknown>;\n\n // Chat Completions shape\n if (r.function && typeof r.function === 'object') {\n const f = r.function as Record<string, unknown>;\n return {\n id: String(r.id ?? ''),\n name: String(f.name ?? ''),\n arguments: parseArgs(f.arguments),\n };\n }\n\n // Responses API or already-normalized\n return {\n id: String(r.id ?? r.call_id ?? ''),\n name: String(r.name ?? ''),\n arguments: parseArgs(r.arguments),\n };\n}\n\nfunction parseArgs(args: unknown): Record<string, unknown> {\n if (args == null) return {};\n if (typeof args === 'object') return args as Record<string, unknown>;\n if (typeof args === 'string') {\n try {\n const v = JSON.parse(args);\n if (v && typeof v === 'object') return v as Record<string, unknown>;\n return { _raw: args };\n } catch {\n return { _raw: args };\n }\n }\n return { _raw: String(args) };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalization\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Produce the canonical bytes for an OpenAI function call. The bytes are\n * what the agent-action envelope's `content.hash` is a SHA-256 of.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"arguments\": <canonical JSON of arguments>,\n * \"id\": <call id>,\n * \"name\": <function name>\n * }\n */\nexport function canonicalizeFunctionCall(c: OpenAiFunctionCall): Uint8Array {\n const canon = {\n arguments: c.arguments,\n id: c.id,\n name: c.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 functionCallHash(c: OpenAiFunctionCall): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeFunctionCall(c)));\n}\n\nexport function functionCallActionId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n c: OpenAiFunctionCall\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: functionCallHash(c),\n content_length: canonicalizeFunctionCall(c).byteLength,\n content_mime: 'application/vnd.oc-agent.openai-function-call+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamping + invocation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface StampFunctionCallInput {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n /** Either a normalized `OpenAiFunctionCall` or a raw OpenAI tool_call / function_call object. */\n call: OpenAiFunctionCall | unknown;\n /** Defaults to `openai:function(name=<name>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\nexport async function stampFunctionCall(\n input: StampFunctionCallInput\n): Promise<ActionEnvelope> {\n const fc =\n typeof input.call === 'object' &&\n input.call !== null &&\n 'arguments' in (input.call as Record<string, unknown>) &&\n typeof (input.call as Record<string, unknown>).arguments !== 'string'\n ? (input.call as OpenAiFunctionCall)\n : parseFunctionCall(input.call);\n\n const scopeExercised =\n input.scopeExercised ?? `openai:function(name=${fc.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 `stampFunctionCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`\n );\n }\n\n const bytes = canonicalizeFunctionCall(fc);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: functionCallHash(fc), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.openai-function-call+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\nexport interface InvokeWithStampInput<T> extends StampFunctionCallInput {\n /** Your handler. Receives the normalized OpenAiFunctionCall. */\n execute: (call: OpenAiFunctionCall) => Promise<T>;\n}\n\nexport interface InvokeWithStampResult<T> {\n result: T;\n action: ActionEnvelope;\n call: OpenAiFunctionCall;\n}\n\nexport async function invokeWithStamp<T>(\n input: InvokeWithStampInput<T>\n): Promise<InvokeWithStampResult<T>> {\n const action = await stampFunctionCall(input);\n const fc = parseFunctionCall(input.call);\n const result = await input.execute(fc);\n return { result, action, call: fc };\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. */\n posted: _PostActionResult | null;\n}\n\nexport async function invokeWithStampAndPost<T>(\n input: InvokeWithStampAndPostInput<T>\n): Promise<InvokeWithStampAndPostResult<T>> {\n const { result, action, call } = 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 // eslint-disable-next-line no-console\n console.error('[oc-agent-openai] postActionToConsole failed:', err);\n }\n }\n return { result, action, call, posted };\n}\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-openai — wrap OpenAI function calls in OC Agent\n// action envelopes.\n//\n// Covers both surfaces:\n// - Responses API: response.output → tool_call items with\n// { type: 'function_call', name, arguments, call_id }\n// (`arguments` is a JSON string)\n// - Chat Completions: message.tool_calls[i] = { id, function: { name, arguments } }\n// (`arguments` is also a JSON string)\n//\n// Both shapes normalize through `parseFunctionCall()` to the canonical\n// {id, name, arguments} form before hashing.\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// Shape normalization\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface OpenAiFunctionCall {\n /** OpenAI's `id` for this call (Chat Completions: `tool_call.id`; Responses: `call_id`). */\n id: string;\n /** The function name. */\n name: string;\n /** The (already-parsed) function arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Normalize either a Chat Completions `tool_call` or a Responses\n * `function_call` item into our canonical {id, name, arguments} shape.\n *\n * Accepts:\n * - { id, function: { name, arguments } } (Chat Completions)\n * - { call_id, name, arguments } (Responses, function_call)\n * - { id, name, arguments } (already normalized)\n *\n * `arguments` may be either a JSON string (OpenAI's wire shape) or an\n * already-parsed object (your post-processing). Both work.\n */\nexport function parseFunctionCall(raw: unknown): OpenAiFunctionCall {\n if (raw == null || typeof raw !== 'object') {\n throw new TypeError('parseFunctionCall: input is not an object');\n }\n const r = raw as Record<string, unknown>;\n\n // Chat Completions shape\n if (r.function && typeof r.function === 'object') {\n const f = r.function as Record<string, unknown>;\n return {\n id: String(r.id ?? ''),\n name: String(f.name ?? ''),\n arguments: parseArgs(f.arguments),\n };\n }\n\n // Responses API or already-normalized\n return {\n id: String(r.id ?? r.call_id ?? ''),\n name: String(r.name ?? ''),\n arguments: parseArgs(r.arguments),\n };\n}\n\nfunction parseArgs(args: unknown): Record<string, unknown> {\n if (args == null) return {};\n if (typeof args === 'object') return args as Record<string, unknown>;\n if (typeof args === 'string') {\n try {\n const v = JSON.parse(args);\n if (v && typeof v === 'object') return v as Record<string, unknown>;\n return { _raw: args };\n } catch {\n return { _raw: args };\n }\n }\n return { _raw: String(args) };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalization\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Produce the canonical bytes for an OpenAI function call. The bytes are\n * what the agent-action envelope's `content.hash` is a SHA-256 of.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"arguments\": <canonical JSON of arguments>,\n * \"id\": <call id>,\n * \"name\": <function name>\n * }\n */\nexport function canonicalizeFunctionCall(c: OpenAiFunctionCall): Uint8Array {\n const canon = {\n arguments: c.arguments,\n id: c.id,\n name: c.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 functionCallHash(c: OpenAiFunctionCall): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeFunctionCall(c)));\n}\n\nexport function functionCallActionId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n c: OpenAiFunctionCall\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: functionCallHash(c),\n content_length: canonicalizeFunctionCall(c).byteLength,\n content_mime: 'application/vnd.oc-agent.openai-function-call+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamping + invocation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface StampFunctionCallInput {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n /** Either a normalized `OpenAiFunctionCall` or a raw OpenAI tool_call / function_call object. */\n call: OpenAiFunctionCall | unknown;\n /** Defaults to `openai:function(name=<name>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\nexport async function stampFunctionCall(\n input: StampFunctionCallInput\n): Promise<ActionEnvelope> {\n const fc =\n typeof input.call === 'object' &&\n input.call !== null &&\n 'arguments' in (input.call as Record<string, unknown>) &&\n typeof (input.call as Record<string, unknown>).arguments !== 'string'\n ? (input.call as OpenAiFunctionCall)\n : parseFunctionCall(input.call);\n\n const scopeExercised =\n input.scopeExercised ?? `openai:function(name=${fc.name})`;\n\n assertScopeGranted(input.delegation, scopeExercised, 'stampFunctionCall');\n\n const bytes = canonicalizeFunctionCall(fc);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: functionCallHash(fc), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.openai-function-call+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\nexport interface InvokeWithStampInput<T> extends StampFunctionCallInput {\n /** Your handler. Receives the normalized OpenAiFunctionCall. */\n execute: (call: OpenAiFunctionCall) => Promise<T>;\n}\n\nexport interface InvokeWithStampResult<T> {\n result: T;\n action: ActionEnvelope;\n call: OpenAiFunctionCall;\n}\n\nexport async function invokeWithStamp<T>(\n input: InvokeWithStampInput<T>\n): Promise<InvokeWithStampResult<T>> {\n const action = await stampFunctionCall(input);\n const fc = parseFunctionCall(input.call);\n const result = await input.execute(fc);\n return { result, action, call: fc };\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\nexport interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {\n /** Fleet credentials. If absent, behaves like invokeWithStamp. */\n fleet?: FleetClient;\n}\n\nexport interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {\n /** Set when fleet is configured AND the POST succeeded. */\n posted: PostActionResult | null;\n}\n\nexport async function invokeWithStampAndPost<T>(\n input: InvokeWithStampAndPostInput<T>\n): Promise<InvokeWithStampAndPostResult<T>> {\n const { result, action, call } = 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 // eslint-disable-next-line no-console\n console.error('[oc-agent-openai] postActionToFleet failed:', err);\n }\n }\n return { result, action, call, posted };\n}\n"]}
package/dist/index.mjs CHANGED
@@ -1,8 +1,6 @@
1
1
  import { sha256 } from '@noble/hashes/sha256';
2
- import { canonicalize, hexEncode, computeActionId, parseScope, isSubScope } from '@orangecheck/agent-core';
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 parseFunctionCall(raw) {
@@ -64,13 +62,7 @@ function functionCallActionId(agentAddress, signedAt, delegationId, scopeExercis
64
62
  async function stampFunctionCall(input) {
65
63
  const fc = typeof input.call === "object" && input.call !== null && "arguments" in input.call && typeof input.call.arguments !== "string" ? input.call : parseFunctionCall(input.call);
66
64
  const scopeExercised = input.scopeExercised ?? `openai:function(name=${fc.name})`;
67
- const granted = (input.delegation.scopes ?? []).map(parseScope);
68
- const exercisedParsed = parseScope(scopeExercised);
69
- if (!granted.some((g) => isSubScope(exercisedParsed, g))) {
70
- throw new Error(
71
- `stampFunctionCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`
72
- );
73
- }
65
+ assertScopeGranted(input.delegation, scopeExercised, "stampFunctionCall");
74
66
  const bytes = canonicalizeFunctionCall(fc);
75
67
  return signAsAgent({
76
68
  agent: input.agent,
@@ -87,19 +79,54 @@ async function invokeWithStamp(input) {
87
79
  const result = await input.execute(fc);
88
80
  return { result, action, call: fc };
89
81
  }
82
+ async function postActionToFleet(action, client) {
83
+ const baseUrl = client.baseUrl ?? "https://fleet.ochk.io";
84
+ const f = client.fetch ?? fetch;
85
+ const body = {
86
+ project_id: client.projectId,
87
+ delegation_id: action.delegation_id,
88
+ agent_address: action.signer.address,
89
+ scope_exercised: action.scope_exercised,
90
+ content_hash: action.content.hash,
91
+ content_length: action.content.length,
92
+ content_mime: action.content.mime,
93
+ signed_at: action.signed_at,
94
+ signature: action.sig.value,
95
+ id: action.id
96
+ };
97
+ const r = await f(`${baseUrl}/api/actions`, {
98
+ method: "POST",
99
+ headers: {
100
+ "Content-Type": "application/json",
101
+ Authorization: `Bearer ${client.apiToken}`
102
+ },
103
+ body: JSON.stringify(body)
104
+ });
105
+ if (!r.ok) {
106
+ let reason = `http_${r.status}`;
107
+ try {
108
+ const j2 = await r.json();
109
+ if (j2.reason) reason = j2.reason;
110
+ } catch {
111
+ }
112
+ throw new Error(`postActionToFleet failed: ${reason}`);
113
+ }
114
+ const j = await r.json();
115
+ return j.action;
116
+ }
90
117
  async function invokeWithStampAndPost(input) {
91
118
  const { result, action, call } = await invokeWithStamp(input);
92
119
  let posted = null;
93
- if (input.console) {
120
+ if (input.fleet) {
94
121
  try {
95
- posted = await postActionToConsole(action, input.console);
122
+ posted = await postActionToFleet(action, input.fleet);
96
123
  } catch (err) {
97
- console.error("[oc-agent-openai] postActionToConsole failed:", err);
124
+ console.error("[oc-agent-openai] postActionToFleet failed:", err);
98
125
  }
99
126
  }
100
127
  return { result, action, call, posted };
101
128
  }
102
129
 
103
- export { canonicalizeFunctionCall, functionCallActionId, functionCallHash, invokeWithStamp, invokeWithStampAndPost, parseFunctionCall, stampFunctionCall };
130
+ export { canonicalizeFunctionCall, functionCallActionId, functionCallHash, invokeWithStamp, invokeWithStampAndPost, parseFunctionCall, postActionToFleet, stampFunctionCall };
104
131
  //# sourceMappingURL=index.mjs.map
105
132
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["_post"],"mappings":"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","file":"index.mjs","sourcesContent":["// @orangecheck/agent-openai — wrap OpenAI function calls in OC Agent\n// action envelopes.\n//\n// Covers both surfaces:\n// - Responses API: response.output → tool_call items with\n// { type: 'function_call', name, arguments, call_id }\n// (`arguments` is a JSON string)\n// - Chat Completions: message.tool_calls[i] = { id, function: { name, arguments } }\n// (`arguments` is also a JSON string)\n//\n// Both shapes normalize through `parseFunctionCall()` to the canonical\n// {id, name, arguments} form before hashing.\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// Shape normalization\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface OpenAiFunctionCall {\n /** OpenAI's `id` for this call (Chat Completions: `tool_call.id`; Responses: `call_id`). */\n id: string;\n /** The function name. */\n name: string;\n /** The (already-parsed) function arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Normalize either a Chat Completions `tool_call` or a Responses\n * `function_call` item into our canonical {id, name, arguments} shape.\n *\n * Accepts:\n * - { id, function: { name, arguments } } (Chat Completions)\n * - { call_id, name, arguments } (Responses, function_call)\n * - { id, name, arguments } (already normalized)\n *\n * `arguments` may be either a JSON string (OpenAI's wire shape) or an\n * already-parsed object (your post-processing). Both work.\n */\nexport function parseFunctionCall(raw: unknown): OpenAiFunctionCall {\n if (raw == null || typeof raw !== 'object') {\n throw new TypeError('parseFunctionCall: input is not an object');\n }\n const r = raw as Record<string, unknown>;\n\n // Chat Completions shape\n if (r.function && typeof r.function === 'object') {\n const f = r.function as Record<string, unknown>;\n return {\n id: String(r.id ?? ''),\n name: String(f.name ?? ''),\n arguments: parseArgs(f.arguments),\n };\n }\n\n // Responses API or already-normalized\n return {\n id: String(r.id ?? r.call_id ?? ''),\n name: String(r.name ?? ''),\n arguments: parseArgs(r.arguments),\n };\n}\n\nfunction parseArgs(args: unknown): Record<string, unknown> {\n if (args == null) return {};\n if (typeof args === 'object') return args as Record<string, unknown>;\n if (typeof args === 'string') {\n try {\n const v = JSON.parse(args);\n if (v && typeof v === 'object') return v as Record<string, unknown>;\n return { _raw: args };\n } catch {\n return { _raw: args };\n }\n }\n return { _raw: String(args) };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalization\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Produce the canonical bytes for an OpenAI function call. The bytes are\n * what the agent-action envelope's `content.hash` is a SHA-256 of.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"arguments\": <canonical JSON of arguments>,\n * \"id\": <call id>,\n * \"name\": <function name>\n * }\n */\nexport function canonicalizeFunctionCall(c: OpenAiFunctionCall): Uint8Array {\n const canon = {\n arguments: c.arguments,\n id: c.id,\n name: c.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 functionCallHash(c: OpenAiFunctionCall): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeFunctionCall(c)));\n}\n\nexport function functionCallActionId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n c: OpenAiFunctionCall\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: functionCallHash(c),\n content_length: canonicalizeFunctionCall(c).byteLength,\n content_mime: 'application/vnd.oc-agent.openai-function-call+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamping + invocation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface StampFunctionCallInput {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n /** Either a normalized `OpenAiFunctionCall` or a raw OpenAI tool_call / function_call object. */\n call: OpenAiFunctionCall | unknown;\n /** Defaults to `openai:function(name=<name>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\nexport async function stampFunctionCall(\n input: StampFunctionCallInput\n): Promise<ActionEnvelope> {\n const fc =\n typeof input.call === 'object' &&\n input.call !== null &&\n 'arguments' in (input.call as Record<string, unknown>) &&\n typeof (input.call as Record<string, unknown>).arguments !== 'string'\n ? (input.call as OpenAiFunctionCall)\n : parseFunctionCall(input.call);\n\n const scopeExercised =\n input.scopeExercised ?? `openai:function(name=${fc.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 `stampFunctionCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`\n );\n }\n\n const bytes = canonicalizeFunctionCall(fc);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: functionCallHash(fc), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.openai-function-call+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\nexport interface InvokeWithStampInput<T> extends StampFunctionCallInput {\n /** Your handler. Receives the normalized OpenAiFunctionCall. */\n execute: (call: OpenAiFunctionCall) => Promise<T>;\n}\n\nexport interface InvokeWithStampResult<T> {\n result: T;\n action: ActionEnvelope;\n call: OpenAiFunctionCall;\n}\n\nexport async function invokeWithStamp<T>(\n input: InvokeWithStampInput<T>\n): Promise<InvokeWithStampResult<T>> {\n const action = await stampFunctionCall(input);\n const fc = parseFunctionCall(input.call);\n const result = await input.execute(fc);\n return { result, action, call: fc };\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. */\n posted: _PostActionResult | null;\n}\n\nexport async function invokeWithStampAndPost<T>(\n input: InvokeWithStampAndPostInput<T>\n): Promise<InvokeWithStampAndPostResult<T>> {\n const { result, action, call } = 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 // eslint-disable-next-line no-console\n console.error('[oc-agent-openai] postActionToConsole failed:', err);\n }\n }\n return { result, action, call, posted };\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["j"],"mappings":";;;;;AAoDO,SAAS,kBAAkB,GAAA,EAAkC;AAChE,EAAA,IAAI,GAAA,IAAO,IAAA,IAAQ,OAAO,GAAA,KAAQ,QAAA,EAAU;AACxC,IAAA,MAAM,IAAI,UAAU,2CAA2C,CAAA;AAAA,EACnE;AACA,EAAA,MAAM,CAAA,GAAI,GAAA;AAGV,EAAA,IAAI,CAAA,CAAE,QAAA,IAAY,OAAO,CAAA,CAAE,aAAa,QAAA,EAAU;AAC9C,IAAA,MAAM,IAAI,CAAA,CAAE,QAAA;AACZ,IAAA,OAAO;AAAA,MACH,EAAA,EAAI,MAAA,CAAO,CAAA,CAAE,EAAA,IAAM,EAAE,CAAA;AAAA,MACrB,IAAA,EAAM,MAAA,CAAO,CAAA,CAAE,IAAA,IAAQ,EAAE,CAAA;AAAA,MACzB,SAAA,EAAW,SAAA,CAAU,CAAA,CAAE,SAAS;AAAA,KACpC;AAAA,EACJ;AAGA,EAAA,OAAO;AAAA,IACH,IAAI,MAAA,CAAO,CAAA,CAAE,EAAA,IAAM,CAAA,CAAE,WAAW,EAAE,CAAA;AAAA,IAClC,IAAA,EAAM,MAAA,CAAO,CAAA,CAAE,IAAA,IAAQ,EAAE,CAAA;AAAA,IACzB,SAAA,EAAW,SAAA,CAAU,CAAA,CAAE,SAAS;AAAA,GACpC;AACJ;AAEA,SAAS,UAAU,IAAA,EAAwC;AACvD,EAAA,IAAI,IAAA,IAAQ,IAAA,EAAM,OAAO,EAAC;AAC1B,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,EAAU,OAAO,IAAA;AACrC,EAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC1B,IAAA,IAAI;AACA,MAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AACzB,MAAA,IAAI,CAAA,IAAK,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,CAAA;AACvC,MAAA,OAAO,EAAE,MAAM,IAAA,EAAK;AAAA,IACxB,CAAA,CAAA,MAAQ;AACJ,MAAA,OAAO,EAAE,MAAM,IAAA,EAAK;AAAA,IACxB;AAAA,EACJ;AACA,EAAA,OAAO,EAAE,IAAA,EAAM,MAAA,CAAO,IAAI,CAAA,EAAE;AAChC;AAiBO,SAAS,yBAAyB,CAAA,EAAmC;AACxE,EAAA,MAAM,KAAA,GAAQ;AAAA,IACV,WAAW,CAAA,CAAE,SAAA;AAAA,IACb,IAAI,CAAA,CAAE,EAAA;AAAA,IACN,MAAM,CAAA,CAAE;AAAA,GACZ;AACA,EAAA,MAAM,GAAA,GAAM,aAAa,KAAsD,CAAA;AAC/E,EAAA,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,MAAM,IAAI,CAAA;AAC9C;AAEO,SAAS,iBAAiB,CAAA,EAA+B;AAC5D,EAAA,OAAO,YAAY,SAAA,CAAU,MAAA,CAAO,wBAAA,CAAyB,CAAC,CAAC,CAAC,CAAA;AACpE;AAEO,SAAS,oBAAA,CACZ,YAAA,EACA,QAAA,EACA,YAAA,EACA,gBACA,CAAA,EACM;AACN,EAAA,OAAO,eAAA,CAAgB;AAAA,IACnB,OAAA,EAAS,YAAA;AAAA,IACT,YAAA,EAAc,iBAAiB,CAAC,CAAA;AAAA,IAChC,cAAA,EAAgB,wBAAA,CAAyB,CAAC,CAAA,CAAE,UAAA;AAAA,IAC5C,YAAA,EAAc,oDAAA;AAAA,IACd,SAAA,EAAW,QAAA;AAAA,IACX,aAAA,EAAe,YAAA;AAAA,IACf,eAAA,EAAiB;AAAA,GACpB,CAAA;AACL;AAgBA,eAAsB,kBAClB,KAAA,EACuB;AACvB,EAAA,MAAM,EAAA,GACF,OAAO,KAAA,CAAM,IAAA,KAAS,YACtB,KAAA,CAAM,IAAA,KAAS,QACf,WAAA,IAAgB,KAAA,CAAM,QACtB,OAAQ,KAAA,CAAM,KAAiC,SAAA,KAAc,QAAA,GACtD,MAAM,IAAA,GACP,iBAAA,CAAkB,MAAM,IAAI,CAAA;AAEtC,EAAA,MAAM,cAAA,GACF,KAAA,CAAM,cAAA,IAAkB,CAAA,qBAAA,EAAwB,GAAG,IAAI,CAAA,CAAA,CAAA;AAE3D,EAAA,kBAAA,CAAmB,KAAA,CAAM,UAAA,EAAY,cAAA,EAAgB,mBAAmB,CAAA;AAExE,EAAA,MAAM,KAAA,GAAQ,yBAAyB,EAAE,CAAA;AACzC,EAAA,OAAO,WAAA,CAAY;AAAA,IACf,OAAO,KAAA,CAAM,KAAA;AAAA,IACb,YAAY,KAAA,CAAM,UAAA;AAAA,IAClB,OAAA,EAAS,EAAE,IAAA,EAAM,gBAAA,CAAiB,EAAE,CAAA,EAAG,MAAA,EAAQ,MAAM,UAAA,EAAW;AAAA,IAChE,IAAA,EAAM,oDAAA;AAAA,IACN,cAAA;AAAA,IACA,UAAU,KAAA,CAAM;AAAA,GACnB,CAAA;AACL;AAaA,eAAsB,gBAClB,KAAA,EACiC;AACjC,EAAA,MAAM,MAAA,GAAS,MAAM,iBAAA,CAAkB,KAAK,CAAA;AAC5C,EAAA,MAAM,EAAA,GAAK,iBAAA,CAAkB,KAAA,CAAM,IAAI,CAAA;AACvC,EAAA,MAAM,MAAA,GAAS,MAAM,KAAA,CAAM,OAAA,CAAQ,EAAE,CAAA;AACrC,EAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,EAAQ,IAAA,EAAM,EAAA,EAAG;AACtC;AA8BA,eAAsB,iBAAA,CAClB,QACA,MAAA,EACyB;AACzB,EAAA,MAAM,OAAA,GAAU,OAAO,OAAA,IAAW,uBAAA;AAClC,EAAA,MAAM,CAAA,GAAI,OAAO,KAAA,IAAS,KAAA;AAC1B,EAAA,MAAM,IAAA,GAAO;AAAA,IACT,YAAY,MAAA,CAAO,SAAA;AAAA,IACnB,eAAe,MAAA,CAAO,aAAA;AAAA,IACtB,aAAA,EAAe,OAAO,MAAA,CAAO,OAAA;AAAA,IAC7B,iBAAiB,MAAA,CAAO,eAAA;AAAA,IACxB,YAAA,EAAc,OAAO,OAAA,CAAQ,IAAA;AAAA,IAC7B,cAAA,EAAgB,OAAO,OAAA,CAAQ,MAAA;AAAA,IAC/B,YAAA,EAAc,OAAO,OAAA,CAAQ,IAAA;AAAA,IAC7B,WAAW,MAAA,CAAO,SAAA;AAAA,IAClB,SAAA,EAAW,OAAO,GAAA,CAAI,KAAA;AAAA,IACtB,IAAI,MAAA,CAAO;AAAA,GACf;AACA,EAAA,MAAM,CAAA,GAAI,MAAM,CAAA,CAAE,CAAA,EAAG,OAAO,CAAA,YAAA,CAAA,EAAgB;AAAA,IACxC,MAAA,EAAQ,MAAA;AAAA,IACR,OAAA,EAAS;AAAA,MACL,cAAA,EAAgB,kBAAA;AAAA,MAChB,aAAA,EAAe,CAAA,OAAA,EAAU,MAAA,CAAO,QAAQ,CAAA;AAAA,KAC5C;AAAA,IACA,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,IAAI;AAAA,GAC5B,CAAA;AACD,EAAA,IAAI,CAAC,EAAE,EAAA,EAAI;AACP,IAAA,IAAI,MAAA,GAAS,CAAA,KAAA,EAAQ,CAAA,CAAE,MAAM,CAAA,CAAA;AAC7B,IAAA,IAAI;AACA,MAAA,MAAMA,EAAAA,GAAK,MAAM,CAAA,CAAE,IAAA,EAAK;AACxB,MAAA,IAAIA,EAAAA,CAAE,MAAA,EAAQ,MAAA,GAASA,EAAAA,CAAE,MAAA;AAAA,IAC7B,CAAA,CAAA,MAAQ;AAAA,IAER;AACA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,MAAM,CAAA,CAAE,CAAA;AAAA,EACzD;AACA,EAAA,MAAM,CAAA,GAAK,MAAM,CAAA,CAAE,IAAA,EAAK;AACxB,EAAA,OAAO,CAAA,CAAE,MAAA;AACb;AAYA,eAAsB,uBAClB,KAAA,EACwC;AACxC,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAQ,MAAK,GAAI,MAAM,gBAAgB,KAAK,CAAA;AAC5D,EAAA,IAAI,MAAA,GAAkC,IAAA;AACtC,EAAA,IAAI,MAAM,KAAA,EAAO;AACb,IAAA,IAAI;AACA,MAAA,MAAA,GAAS,MAAM,iBAAA,CAAkB,MAAA,EAAQ,KAAA,CAAM,KAAK,CAAA;AAAA,IACxD,SAAS,GAAA,EAAK;AAEV,MAAA,OAAA,CAAQ,KAAA,CAAM,+CAA+C,GAAG,CAAA;AAAA,IACpE;AAAA,EACJ;AACA,EAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,EAAQ,IAAA,EAAM,MAAA,EAAO;AAC1C","file":"index.mjs","sourcesContent":["// @orangecheck/agent-openai — wrap OpenAI function calls in OC Agent\n// action envelopes.\n//\n// Covers both surfaces:\n// - Responses API: response.output → tool_call items with\n// { type: 'function_call', name, arguments, call_id }\n// (`arguments` is a JSON string)\n// - Chat Completions: message.tool_calls[i] = { id, function: { name, arguments } }\n// (`arguments` is also a JSON string)\n//\n// Both shapes normalize through `parseFunctionCall()` to the canonical\n// {id, name, arguments} form before hashing.\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// Shape normalization\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface OpenAiFunctionCall {\n /** OpenAI's `id` for this call (Chat Completions: `tool_call.id`; Responses: `call_id`). */\n id: string;\n /** The function name. */\n name: string;\n /** The (already-parsed) function arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Normalize either a Chat Completions `tool_call` or a Responses\n * `function_call` item into our canonical {id, name, arguments} shape.\n *\n * Accepts:\n * - { id, function: { name, arguments } } (Chat Completions)\n * - { call_id, name, arguments } (Responses, function_call)\n * - { id, name, arguments } (already normalized)\n *\n * `arguments` may be either a JSON string (OpenAI's wire shape) or an\n * already-parsed object (your post-processing). Both work.\n */\nexport function parseFunctionCall(raw: unknown): OpenAiFunctionCall {\n if (raw == null || typeof raw !== 'object') {\n throw new TypeError('parseFunctionCall: input is not an object');\n }\n const r = raw as Record<string, unknown>;\n\n // Chat Completions shape\n if (r.function && typeof r.function === 'object') {\n const f = r.function as Record<string, unknown>;\n return {\n id: String(r.id ?? ''),\n name: String(f.name ?? ''),\n arguments: parseArgs(f.arguments),\n };\n }\n\n // Responses API or already-normalized\n return {\n id: String(r.id ?? r.call_id ?? ''),\n name: String(r.name ?? ''),\n arguments: parseArgs(r.arguments),\n };\n}\n\nfunction parseArgs(args: unknown): Record<string, unknown> {\n if (args == null) return {};\n if (typeof args === 'object') return args as Record<string, unknown>;\n if (typeof args === 'string') {\n try {\n const v = JSON.parse(args);\n if (v && typeof v === 'object') return v as Record<string, unknown>;\n return { _raw: args };\n } catch {\n return { _raw: args };\n }\n }\n return { _raw: String(args) };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalization\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Produce the canonical bytes for an OpenAI function call. The bytes are\n * what the agent-action envelope's `content.hash` is a SHA-256 of.\n *\n * Shape (RFC 8785 canonical JSON, then trailing LF):\n * {\n * \"arguments\": <canonical JSON of arguments>,\n * \"id\": <call id>,\n * \"name\": <function name>\n * }\n */\nexport function canonicalizeFunctionCall(c: OpenAiFunctionCall): Uint8Array {\n const canon = {\n arguments: c.arguments,\n id: c.id,\n name: c.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 functionCallHash(c: OpenAiFunctionCall): string {\n return 'sha256:' + hexEncode(sha256(canonicalizeFunctionCall(c)));\n}\n\nexport function functionCallActionId(\n agentAddress: string,\n signedAt: string,\n delegationId: string,\n scopeExercised: string,\n c: OpenAiFunctionCall\n): string {\n return computeActionId({\n address: agentAddress,\n content_hash: functionCallHash(c),\n content_length: canonicalizeFunctionCall(c).byteLength,\n content_mime: 'application/vnd.oc-agent.openai-function-call+json',\n signed_at: signedAt,\n delegation_id: delegationId,\n scope_exercised: scopeExercised,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Stamping + invocation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface StampFunctionCallInput {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n /** Either a normalized `OpenAiFunctionCall` or a raw OpenAI tool_call / function_call object. */\n call: OpenAiFunctionCall | unknown;\n /** Defaults to `openai:function(name=<name>)` — the tightest admissible sub-scope. */\n scopeExercised?: string;\n signedAt?: Date;\n}\n\nexport async function stampFunctionCall(\n input: StampFunctionCallInput\n): Promise<ActionEnvelope> {\n const fc =\n typeof input.call === 'object' &&\n input.call !== null &&\n 'arguments' in (input.call as Record<string, unknown>) &&\n typeof (input.call as Record<string, unknown>).arguments !== 'string'\n ? (input.call as OpenAiFunctionCall)\n : parseFunctionCall(input.call);\n\n const scopeExercised =\n input.scopeExercised ?? `openai:function(name=${fc.name})`;\n\n assertScopeGranted(input.delegation, scopeExercised, 'stampFunctionCall');\n\n const bytes = canonicalizeFunctionCall(fc);\n return signAsAgent({\n agent: input.agent,\n delegation: input.delegation as never,\n content: { hash: functionCallHash(fc), length: bytes.byteLength },\n mime: 'application/vnd.oc-agent.openai-function-call+json',\n scopeExercised,\n signedAt: input.signedAt,\n });\n}\n\nexport interface InvokeWithStampInput<T> extends StampFunctionCallInput {\n /** Your handler. Receives the normalized OpenAiFunctionCall. */\n execute: (call: OpenAiFunctionCall) => Promise<T>;\n}\n\nexport interface InvokeWithStampResult<T> {\n result: T;\n action: ActionEnvelope;\n call: OpenAiFunctionCall;\n}\n\nexport async function invokeWithStamp<T>(\n input: InvokeWithStampInput<T>\n): Promise<InvokeWithStampResult<T>> {\n const action = await stampFunctionCall(input);\n const fc = parseFunctionCall(input.call);\n const result = await input.execute(fc);\n return { result, action, call: fc };\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\nexport interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {\n /** Fleet credentials. If absent, behaves like invokeWithStamp. */\n fleet?: FleetClient;\n}\n\nexport interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {\n /** Set when fleet is configured AND the POST succeeded. */\n posted: PostActionResult | null;\n}\n\nexport async function invokeWithStampAndPost<T>(\n input: InvokeWithStampAndPostInput<T>\n): Promise<InvokeWithStampAndPostResult<T>> {\n const { result, action, call } = 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 // eslint-disable-next-line no-console\n console.error('[oc-agent-openai] postActionToFleet failed:', err);\n }\n }\n return { result, action, call, posted };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangecheck/agent-openai",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Wrap OpenAI function-calling (Responses API + Chat Completions tool path) with OC Agent scope enforcement and emit a signed agent-action envelope per function invocation.",
5
5
  "keywords": [
6
6
  "bitcoin",
@@ -52,9 +52,8 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@noble/hashes": "^1.5.0",
55
- "@orangecheck/agent-console-client": "0.1.0",
56
- "@orangecheck/agent-core": "^0.3.0",
57
- "@orangecheck/agent-signer": "^0.1.0"
55
+ "@orangecheck/agent-core": "^1.2.0",
56
+ "@orangecheck/agent-signer": "^0.3.0"
58
57
  },
59
58
  "devDependencies": {
60
59
  "@types/node": "^22.10.2",
package/src/index.ts CHANGED
@@ -13,11 +13,10 @@
13
13
 
14
14
  import { sha256 } from '@noble/hashes/sha256';
15
15
  import {
16
+ assertScopeGranted,
16
17
  canonicalize,
17
18
  computeActionId,
18
19
  hexEncode,
19
- isSubScope,
20
- parseScope,
21
20
  type DelegationEnvelope,
22
21
  } from '@orangecheck/agent-core';
23
22
  import {
@@ -165,13 +164,7 @@ export async function stampFunctionCall(
165
164
  const scopeExercised =
166
165
  input.scopeExercised ?? `openai:function(name=${fc.name})`;
167
166
 
168
- const granted = (input.delegation.scopes ?? []).map(parseScope);
169
- const exercisedParsed = parseScope(scopeExercised);
170
- if (!granted.some((g) => isSubScope(exercisedParsed, g))) {
171
- throw new Error(
172
- `stampFunctionCall: scope_exercised (${scopeExercised}) is not a sub-scope of any granted scope`
173
- );
174
- }
167
+ assertScopeGranted(input.delegation, scopeExercised, 'stampFunctionCall');
175
168
 
176
169
  const bytes = canonicalizeFunctionCall(fc);
177
170
  return signAsAgent({
@@ -205,42 +198,94 @@ export async function invokeWithStamp<T>(
205
198
  }
206
199
 
207
200
  // ─────────────────────────────────────────────────────────────────────────────
208
- // Console integration — re-export from the shared client
201
+ // Fleet integration: POST stamped actions to fleet.ochk.io/api/actions
209
202
  // ─────────────────────────────────────────────────────────────────────────────
210
203
 
211
- export {
212
- postActionToConsole,
213
- type ConsoleClient,
214
- type PostActionResult,
215
- } from '@orangecheck/agent-console-client';
204
+ export interface FleetClient {
205
+ /** Defaults to https://fleet.ochk.io. */
206
+ baseUrl?: string;
207
+ /** Bearer token from /settings § 03 (`ock_<hex>`). */
208
+ apiToken: string;
209
+ /** Project the action belongs to (proj_*). */
210
+ projectId: string;
211
+ /** Optional fetch override for runtimes that need it. */
212
+ fetch?: typeof fetch;
213
+ }
216
214
 
217
- import {
218
- postActionToConsole as _post,
219
- type ConsoleClient as _ConsoleClient,
220
- type PostActionResult as _PostActionResult,
221
- } from '@orangecheck/agent-console-client';
215
+ export interface PostActionResult {
216
+ id: string;
217
+ project_id: string;
218
+ delegation_id: string;
219
+ }
220
+
221
+ /**
222
+ * POST a stamped action envelope to fleet.ochk.io/api/actions. The
223
+ * fleet re-derives the action id, validates agent-must-match-
224
+ * delegation, persists, fans out to Nostr (kind 30084), submits to
225
+ * OC Stamp, and triggers any subscribed webhooks. Throws on non-2xx
226
+ * with the server's reason string.
227
+ */
228
+ export async function postActionToFleet(
229
+ action: ActionEnvelope,
230
+ client: FleetClient
231
+ ): Promise<PostActionResult> {
232
+ const baseUrl = client.baseUrl ?? 'https://fleet.ochk.io';
233
+ const f = client.fetch ?? fetch;
234
+ const body = {
235
+ project_id: client.projectId,
236
+ delegation_id: action.delegation_id,
237
+ agent_address: action.signer.address,
238
+ scope_exercised: action.scope_exercised,
239
+ content_hash: action.content.hash,
240
+ content_length: action.content.length,
241
+ content_mime: action.content.mime,
242
+ signed_at: action.signed_at,
243
+ signature: action.sig.value,
244
+ id: action.id,
245
+ };
246
+ const r = await f(`${baseUrl}/api/actions`, {
247
+ method: 'POST',
248
+ headers: {
249
+ 'Content-Type': 'application/json',
250
+ Authorization: `Bearer ${client.apiToken}`,
251
+ },
252
+ body: JSON.stringify(body),
253
+ });
254
+ if (!r.ok) {
255
+ let reason = `http_${r.status}`;
256
+ try {
257
+ const j = (await r.json()) as { reason?: string };
258
+ if (j.reason) reason = j.reason;
259
+ } catch {
260
+ // body wasn't json
261
+ }
262
+ throw new Error(`postActionToFleet failed: ${reason}`);
263
+ }
264
+ const j = (await r.json()) as { ok: true; action: PostActionResult };
265
+ return j.action;
266
+ }
222
267
 
223
268
  export interface InvokeWithStampAndPostInput<T> extends InvokeWithStampInput<T> {
224
- /** Console credentials. If absent, behaves like invokeWithStamp + posted: null. */
225
- console?: _ConsoleClient;
269
+ /** Fleet credentials. If absent, behaves like invokeWithStamp. */
270
+ fleet?: FleetClient;
226
271
  }
227
272
 
228
273
  export interface InvokeWithStampAndPostResult<T> extends InvokeWithStampResult<T> {
229
- /** Set when console is configured AND the POST succeeded. */
230
- posted: _PostActionResult | null;
274
+ /** Set when fleet is configured AND the POST succeeded. */
275
+ posted: PostActionResult | null;
231
276
  }
232
277
 
233
278
  export async function invokeWithStampAndPost<T>(
234
279
  input: InvokeWithStampAndPostInput<T>
235
280
  ): Promise<InvokeWithStampAndPostResult<T>> {
236
281
  const { result, action, call } = await invokeWithStamp(input);
237
- let posted: _PostActionResult | null = null;
238
- if (input.console) {
282
+ let posted: PostActionResult | null = null;
283
+ if (input.fleet) {
239
284
  try {
240
- posted = await _post(action, input.console);
285
+ posted = await postActionToFleet(action, input.fleet);
241
286
  } catch (err) {
242
287
  // eslint-disable-next-line no-console
243
- console.error('[oc-agent-openai] postActionToConsole failed:', err);
288
+ console.error('[oc-agent-openai] postActionToFleet failed:', err);
244
289
  }
245
290
  }
246
291
  return { result, action, call, posted };
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { ScopeNotGrantedError } from '@orangecheck/agent-core';
4
+
5
+ import { stampFunctionCall } 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('stampFunctionCall scope pre-flight', () => {
29
+ const base = {
30
+ agent: { address: AGENT, sign: sign() },
31
+ call: { name: 'search', arguments: { q: 'x' } },
32
+ } as never;
33
+
34
+ it('names encrypted scopes rather than blaming the scope string', async () => {
35
+ const err = await stampFunctionCall({
36
+ ...(base as object),
37
+ delegation: { scopes_encrypted: SEALED },
38
+ scopeExercised: 'openai: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 stampFunctionCall({
48
+ ...(base as object),
49
+ delegation: {},
50
+ scopeExercised: 'openai:tool(name=search)',
51
+ } as never).catch((e: unknown) => e);
52
+
53
+ expect((err as ScopeNotGrantedError).reason).toBe('no_scopes');
54
+ });
55
+ });