@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 +5 -1
- package/dist/index.d.mts +14 -5
- package/dist/index.d.ts +14 -5
- 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 +73 -28
- package/src/scope-preflight.test.ts +55 -0
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
|
-
- [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
122
|
+
if (input.fleet) {
|
|
95
123
|
try {
|
|
96
|
-
posted = await
|
|
124
|
+
posted = await postActionToFleet(action, input.fleet);
|
|
97
125
|
} catch (err) {
|
|
98
|
-
console.error("[oc-agent-openai]
|
|
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,
|
|
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
|
-
|
|
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.
|
|
120
|
+
if (input.fleet) {
|
|
94
121
|
try {
|
|
95
|
-
posted = await
|
|
122
|
+
posted = await postActionToFleet(action, input.fleet);
|
|
96
123
|
} catch (err) {
|
|
97
|
-
console.error("[oc-agent-openai]
|
|
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
|
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-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.
|
|
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-
|
|
56
|
-
"@orangecheck/agent-
|
|
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
|
-
|
|
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
|
-
//
|
|
201
|
+
// Fleet integration: POST stamped actions to fleet.ochk.io/api/actions
|
|
209
202
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
210
203
|
|
|
211
|
-
export {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
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
|
-
/**
|
|
225
|
-
|
|
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
|
|
230
|
-
posted:
|
|
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:
|
|
238
|
-
if (input.
|
|
282
|
+
let posted: PostActionResult | null = null;
|
|
283
|
+
if (input.fleet) {
|
|
239
284
|
try {
|
|
240
|
-
posted = await
|
|
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]
|
|
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
|
+
});
|