occ-openai 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +200 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
- package/src/index.ts +282 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# occ-openai
|
|
2
|
+
|
|
3
|
+
Cryptographic proof signing for OpenAI Node SDK tool calls. Every tool call gets an Ed25519-signed proof written to `proof.jsonl`.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install occ-openai openai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Wrap the client
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import OpenAI from "openai";
|
|
17
|
+
import { wrapOpenAI } from "occ-openai";
|
|
18
|
+
|
|
19
|
+
const client = wrapOpenAI(new OpenAI(), {
|
|
20
|
+
proofFile: "proof.jsonl", // default
|
|
21
|
+
agentId: "my-agent",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Use as normal — tool calls are automatically signed
|
|
25
|
+
const response = await client.chat.completions.create({
|
|
26
|
+
model: "gpt-4o",
|
|
27
|
+
messages: [{ role: "user", content: "What's the weather?" }],
|
|
28
|
+
tools: [{
|
|
29
|
+
type: "function",
|
|
30
|
+
function: {
|
|
31
|
+
name: "get_weather",
|
|
32
|
+
parameters: { type: "object", properties: { city: { type: "string" } } },
|
|
33
|
+
},
|
|
34
|
+
}],
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Sign tool execution results
|
|
39
|
+
|
|
40
|
+
When you execute tool calls in your own loop, sign the results too:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { signToolResult } from "occ-openai";
|
|
44
|
+
|
|
45
|
+
// After executing the tool
|
|
46
|
+
const result = await getWeather({ city: "Buffalo" });
|
|
47
|
+
|
|
48
|
+
// Sign the result
|
|
49
|
+
await signToolResult("get_weather", { city: "Buffalo" }, result);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
interface WrapOpenAIOptions {
|
|
56
|
+
proofFile?: string; // Default: "proof.jsonl"
|
|
57
|
+
statePath?: string; // Default: ".occ/signer-state.json"
|
|
58
|
+
measurement?: string; // Default: "occ-openai:stub"
|
|
59
|
+
agentId?: string; // Default: "openai-agent"
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## How it works
|
|
64
|
+
|
|
65
|
+
1. `wrapOpenAI()` returns a Proxy around your OpenAI client
|
|
66
|
+
2. When `chat.completions.create()` returns tool calls, each one is signed
|
|
67
|
+
3. Pre-execution proof: Ed25519 signature over SHA-256 of tool name + arguments
|
|
68
|
+
4. Post-execution proof (via `signToolResult`): signature over tool name + args + result
|
|
69
|
+
5. Both proofs are chained via `prevB64` for tamper-evident ordering
|
|
70
|
+
6. All proofs written to `proof.jsonl` as append-only log
|
|
71
|
+
|
|
72
|
+
## Verify
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx occ-mcp-proxy verify proof.jsonl
|
|
76
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* occ-openai — Cryptographic proof signing for OpenAI Node SDK tool calls.
|
|
3
|
+
*
|
|
4
|
+
* Wraps an OpenAI client so that every tool call in a chat completion response
|
|
5
|
+
* gets an Ed25519-signed pre/post proof pair written to a local proof.jsonl.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import OpenAI from "openai";
|
|
9
|
+
* import { wrapOpenAI } from "occ-openai";
|
|
10
|
+
*
|
|
11
|
+
* const client = wrapOpenAI(new OpenAI());
|
|
12
|
+
* const response = await client.chat.completions.create({ ... });
|
|
13
|
+
*/
|
|
14
|
+
import { type OCCProof } from "occproof";
|
|
15
|
+
export interface WrapOpenAIOptions {
|
|
16
|
+
/** Path to proof log file. Default: "proof.jsonl" */
|
|
17
|
+
proofFile?: string;
|
|
18
|
+
/** Path to signer state file. Default: ".occ/signer-state.json" */
|
|
19
|
+
statePath?: string;
|
|
20
|
+
/** Measurement string for proof metadata. Default: "occ-openai:stub" */
|
|
21
|
+
measurement?: string;
|
|
22
|
+
/** Agent identifier for metadata. Default: "openai-agent" */
|
|
23
|
+
agentId?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Wrap an OpenAI client instance to produce Ed25519-signed proofs for
|
|
27
|
+
* every tool call in chat completion responses.
|
|
28
|
+
*
|
|
29
|
+
* The returned client is a Proxy — all non-completion methods pass through
|
|
30
|
+
* unchanged. Only `client.chat.completions.create()` is intercepted.
|
|
31
|
+
*
|
|
32
|
+
* @param client - An OpenAI client instance
|
|
33
|
+
* @param options - Configuration options
|
|
34
|
+
* @returns A wrapped OpenAI client (same type)
|
|
35
|
+
*/
|
|
36
|
+
export declare function wrapOpenAI<T extends {
|
|
37
|
+
chat: {
|
|
38
|
+
completions: {
|
|
39
|
+
create: (...args: any[]) => any;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
}>(client: T, options?: WrapOpenAIOptions): T;
|
|
43
|
+
/**
|
|
44
|
+
* Sign a tool execution result after the tool function has been called.
|
|
45
|
+
*
|
|
46
|
+
* Use this to create post-execution proofs when you handle tool calls
|
|
47
|
+
* in your own execution loop.
|
|
48
|
+
*
|
|
49
|
+
* @param toolName - Name of the tool that was executed
|
|
50
|
+
* @param toolArgs - Arguments passed to the tool
|
|
51
|
+
* @param result - The tool execution result
|
|
52
|
+
* @param options - Configuration options
|
|
53
|
+
*/
|
|
54
|
+
export declare function signToolResult(toolName: string, toolArgs: Record<string, unknown>, result: unknown, options?: WrapOpenAIOptions): Promise<OCCProof>;
|
|
55
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAA6B,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAUpE,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAoGD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE;QAAE,WAAW,EAAE;YAAE,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,EACjG,MAAM,EAAE,CAAC,EACT,OAAO,CAAC,EAAE,iBAAiB,GAC1B,CAAC,CAiFH;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,MAAM,EAAE,OAAO,EACf,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,QAAQ,CAAC,CAgCnB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2024-2026 Mike Argento
|
|
3
|
+
/**
|
|
4
|
+
* occ-openai — Cryptographic proof signing for OpenAI Node SDK tool calls.
|
|
5
|
+
*
|
|
6
|
+
* Wraps an OpenAI client so that every tool call in a chat completion response
|
|
7
|
+
* gets an Ed25519-signed pre/post proof pair written to a local proof.jsonl.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* import OpenAI from "openai";
|
|
11
|
+
* import { wrapOpenAI } from "occ-openai";
|
|
12
|
+
*
|
|
13
|
+
* const client = wrapOpenAI(new OpenAI());
|
|
14
|
+
* const response = await client.chat.completions.create({ ... });
|
|
15
|
+
*/
|
|
16
|
+
import { Constructor, canonicalize } from "occproof";
|
|
17
|
+
import { StubHost } from "occ-stub";
|
|
18
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
19
|
+
import { appendFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
20
|
+
import { resolve, dirname } from "node:path";
|
|
21
|
+
const signerCache = new Map();
|
|
22
|
+
async function getSigner(statePath, measurement) {
|
|
23
|
+
const key = statePath;
|
|
24
|
+
let cached = signerCache.get(key);
|
|
25
|
+
if (cached)
|
|
26
|
+
return cached;
|
|
27
|
+
cached = (async () => {
|
|
28
|
+
const stub = await StubHost.createPersistent({
|
|
29
|
+
statePath,
|
|
30
|
+
measurement,
|
|
31
|
+
enableTime: true,
|
|
32
|
+
enableCounter: true,
|
|
33
|
+
});
|
|
34
|
+
const constructor = await Constructor.initialize({
|
|
35
|
+
host: stub.host,
|
|
36
|
+
policy: { requireCounter: true, requireTime: true },
|
|
37
|
+
});
|
|
38
|
+
const publicKeyB64 = Buffer.from(stub.publicKeyBytes).toString("base64");
|
|
39
|
+
return { constructor, stub, publicKeyB64 };
|
|
40
|
+
})();
|
|
41
|
+
signerCache.set(key, cached);
|
|
42
|
+
return cached;
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Proof writer
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
function appendProof(entry, proofFile) {
|
|
48
|
+
if (!existsSync(proofFile)) {
|
|
49
|
+
mkdirSync(dirname(resolve(proofFile)), { recursive: true });
|
|
50
|
+
writeFileSync(proofFile, "");
|
|
51
|
+
}
|
|
52
|
+
appendFileSync(proofFile, JSON.stringify(entry) + "\n");
|
|
53
|
+
}
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Core signing
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
async function signDigest(digestB64, metadata, signer) {
|
|
58
|
+
const prevHash = signer.stub.getLastProofHash();
|
|
59
|
+
const commitInput = { digestB64, metadata };
|
|
60
|
+
if (prevHash)
|
|
61
|
+
commitInput.prevProofHashB64 = prevHash;
|
|
62
|
+
const proof = await signer.constructor.commitDigest(commitInput);
|
|
63
|
+
// Chain: store this proof's hash for the next commit
|
|
64
|
+
const proofHash = Buffer.from(sha256(canonicalize(proof))).toString("base64");
|
|
65
|
+
signer.stub.setLastProofHash(proofHash);
|
|
66
|
+
return proof;
|
|
67
|
+
}
|
|
68
|
+
function hashPayload(data) {
|
|
69
|
+
const bytes = new TextEncoder().encode(JSON.stringify(data));
|
|
70
|
+
return Buffer.from(sha256(bytes)).toString("base64");
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// OpenAI wrapper
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
/**
|
|
76
|
+
* Wrap an OpenAI client instance to produce Ed25519-signed proofs for
|
|
77
|
+
* every tool call in chat completion responses.
|
|
78
|
+
*
|
|
79
|
+
* The returned client is a Proxy — all non-completion methods pass through
|
|
80
|
+
* unchanged. Only `client.chat.completions.create()` is intercepted.
|
|
81
|
+
*
|
|
82
|
+
* @param client - An OpenAI client instance
|
|
83
|
+
* @param options - Configuration options
|
|
84
|
+
* @returns A wrapped OpenAI client (same type)
|
|
85
|
+
*/
|
|
86
|
+
export function wrapOpenAI(client, options) {
|
|
87
|
+
const proofFile = resolve(options?.proofFile ?? "proof.jsonl");
|
|
88
|
+
const statePath = resolve(options?.statePath ?? ".occ/signer-state.json");
|
|
89
|
+
const measurement = options?.measurement ?? "occ-openai:stub";
|
|
90
|
+
const agentId = options?.agentId ?? "openai-agent";
|
|
91
|
+
// Deep proxy: client.chat.completions.create
|
|
92
|
+
const originalCreate = client.chat.completions.create.bind(client.chat.completions);
|
|
93
|
+
const wrappedCreate = async (...args) => {
|
|
94
|
+
const response = await originalCreate(...args);
|
|
95
|
+
// Only sign if the response has tool calls
|
|
96
|
+
const choices = response?.choices;
|
|
97
|
+
if (!choices || !Array.isArray(choices))
|
|
98
|
+
return response;
|
|
99
|
+
const signer = await getSigner(statePath, measurement);
|
|
100
|
+
for (const choice of choices) {
|
|
101
|
+
const toolCalls = choice?.message?.tool_calls;
|
|
102
|
+
if (!toolCalls || !Array.isArray(toolCalls))
|
|
103
|
+
continue;
|
|
104
|
+
for (const toolCall of toolCalls) {
|
|
105
|
+
const toolName = toolCall.function?.name ?? "unknown";
|
|
106
|
+
let toolArgs = {};
|
|
107
|
+
try {
|
|
108
|
+
toolArgs = JSON.parse(toolCall.function?.arguments ?? "{}");
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
toolArgs = { raw: toolCall.function?.arguments };
|
|
112
|
+
}
|
|
113
|
+
// Pre-execution proof: sign the tool call input
|
|
114
|
+
const preDigest = hashPayload({
|
|
115
|
+
tool: toolName,
|
|
116
|
+
args: toolArgs,
|
|
117
|
+
callId: toolCall.id,
|
|
118
|
+
});
|
|
119
|
+
const preProof = await signDigest(preDigest, {
|
|
120
|
+
phase: "pre-execution",
|
|
121
|
+
tool: toolName,
|
|
122
|
+
callId: toolCall.id,
|
|
123
|
+
agentId,
|
|
124
|
+
}, signer);
|
|
125
|
+
appendProof({
|
|
126
|
+
timestamp: new Date().toISOString(),
|
|
127
|
+
phase: "pre-execution",
|
|
128
|
+
tool: toolName,
|
|
129
|
+
agentId,
|
|
130
|
+
args: toolArgs,
|
|
131
|
+
proofDigestB64: preDigest,
|
|
132
|
+
receipt: preProof,
|
|
133
|
+
}, proofFile);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return response;
|
|
137
|
+
};
|
|
138
|
+
// Build the proxy chain
|
|
139
|
+
const wrappedCompletions = new Proxy(client.chat.completions, {
|
|
140
|
+
get(target, prop) {
|
|
141
|
+
if (prop === "create")
|
|
142
|
+
return wrappedCreate;
|
|
143
|
+
return target[prop];
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
const wrappedChat = new Proxy(client.chat, {
|
|
147
|
+
get(target, prop) {
|
|
148
|
+
if (prop === "completions")
|
|
149
|
+
return wrappedCompletions;
|
|
150
|
+
return target[prop];
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
return new Proxy(client, {
|
|
154
|
+
get(target, prop) {
|
|
155
|
+
if (prop === "chat")
|
|
156
|
+
return wrappedChat;
|
|
157
|
+
return target[prop];
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Sign a tool execution result after the tool function has been called.
|
|
163
|
+
*
|
|
164
|
+
* Use this to create post-execution proofs when you handle tool calls
|
|
165
|
+
* in your own execution loop.
|
|
166
|
+
*
|
|
167
|
+
* @param toolName - Name of the tool that was executed
|
|
168
|
+
* @param toolArgs - Arguments passed to the tool
|
|
169
|
+
* @param result - The tool execution result
|
|
170
|
+
* @param options - Configuration options
|
|
171
|
+
*/
|
|
172
|
+
export async function signToolResult(toolName, toolArgs, result, options) {
|
|
173
|
+
const proofFile = resolve(options?.proofFile ?? "proof.jsonl");
|
|
174
|
+
const statePath = resolve(options?.statePath ?? ".occ/signer-state.json");
|
|
175
|
+
const measurement = options?.measurement ?? "occ-openai:stub";
|
|
176
|
+
const agentId = options?.agentId ?? "openai-agent";
|
|
177
|
+
const signer = await getSigner(statePath, measurement);
|
|
178
|
+
const postDigest = hashPayload({
|
|
179
|
+
tool: toolName,
|
|
180
|
+
args: toolArgs,
|
|
181
|
+
result,
|
|
182
|
+
});
|
|
183
|
+
const postProof = await signDigest(postDigest, {
|
|
184
|
+
phase: "post-execution",
|
|
185
|
+
tool: toolName,
|
|
186
|
+
agentId,
|
|
187
|
+
}, signer);
|
|
188
|
+
appendProof({
|
|
189
|
+
timestamp: new Date().toISOString(),
|
|
190
|
+
phase: "post-execution",
|
|
191
|
+
tool: toolName,
|
|
192
|
+
agentId,
|
|
193
|
+
args: toolArgs,
|
|
194
|
+
output: typeof result === "string" ? result.slice(0, 1000) : result,
|
|
195
|
+
proofDigestB64: postDigest,
|
|
196
|
+
receipt: postProof,
|
|
197
|
+
}, proofFile);
|
|
198
|
+
return postProof;
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "occ-openai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OCC cryptographic proof signing for OpenAI Node SDK tool calls",
|
|
5
|
+
"author": "Mike Argento",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"typecheck": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"occproof": "^1.0.2",
|
|
27
|
+
"occ-stub": "^1.0.0",
|
|
28
|
+
"@noble/ed25519": "^2.1.0",
|
|
29
|
+
"@noble/hashes": "^1.4.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"openai": ">=4.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.0.0",
|
|
36
|
+
"openai": ">=4.0.0",
|
|
37
|
+
"typescript": "^5.4.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2024-2026 Mike Argento
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* occ-openai — Cryptographic proof signing for OpenAI Node SDK tool calls.
|
|
6
|
+
*
|
|
7
|
+
* Wraps an OpenAI client so that every tool call in a chat completion response
|
|
8
|
+
* gets an Ed25519-signed pre/post proof pair written to a local proof.jsonl.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import OpenAI from "openai";
|
|
12
|
+
* import { wrapOpenAI } from "occ-openai";
|
|
13
|
+
*
|
|
14
|
+
* const client = wrapOpenAI(new OpenAI());
|
|
15
|
+
* const response = await client.chat.completions.create({ ... });
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Constructor, canonicalize, type OCCProof } from "occproof";
|
|
19
|
+
import { StubHost } from "occ-stub";
|
|
20
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
21
|
+
import { appendFileSync, writeFileSync, existsSync, readFileSync, mkdirSync } from "node:fs";
|
|
22
|
+
import { resolve, dirname } from "node:path";
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Types
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
export interface WrapOpenAIOptions {
|
|
29
|
+
/** Path to proof log file. Default: "proof.jsonl" */
|
|
30
|
+
proofFile?: string;
|
|
31
|
+
/** Path to signer state file. Default: ".occ/signer-state.json" */
|
|
32
|
+
statePath?: string;
|
|
33
|
+
/** Measurement string for proof metadata. Default: "occ-openai:stub" */
|
|
34
|
+
measurement?: string;
|
|
35
|
+
/** Agent identifier for metadata. Default: "openai-agent" */
|
|
36
|
+
agentId?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ProofLogEntry {
|
|
40
|
+
timestamp: string;
|
|
41
|
+
phase: "pre-execution" | "post-execution";
|
|
42
|
+
tool: string;
|
|
43
|
+
agentId: string;
|
|
44
|
+
args?: Record<string, unknown>;
|
|
45
|
+
output?: unknown;
|
|
46
|
+
proofDigestB64: string;
|
|
47
|
+
receipt: OCCProof;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Signer singleton
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
interface SignerState {
|
|
55
|
+
constructor: Constructor;
|
|
56
|
+
stub: StubHost;
|
|
57
|
+
publicKeyB64: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const signerCache = new Map<string, Promise<SignerState>>();
|
|
61
|
+
|
|
62
|
+
async function getSigner(statePath: string, measurement: string): Promise<SignerState> {
|
|
63
|
+
const key = statePath;
|
|
64
|
+
let cached = signerCache.get(key);
|
|
65
|
+
if (cached) return cached;
|
|
66
|
+
|
|
67
|
+
cached = (async () => {
|
|
68
|
+
const stub = await StubHost.createPersistent({
|
|
69
|
+
statePath,
|
|
70
|
+
measurement,
|
|
71
|
+
enableTime: true,
|
|
72
|
+
enableCounter: true,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const constructor = await Constructor.initialize({
|
|
76
|
+
host: stub.host,
|
|
77
|
+
policy: { requireCounter: true, requireTime: true },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const publicKeyB64 = Buffer.from(stub.publicKeyBytes).toString("base64");
|
|
81
|
+
return { constructor, stub, publicKeyB64 };
|
|
82
|
+
})();
|
|
83
|
+
|
|
84
|
+
signerCache.set(key, cached);
|
|
85
|
+
return cached;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Proof writer
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
function appendProof(entry: ProofLogEntry, proofFile: string): void {
|
|
93
|
+
if (!existsSync(proofFile)) {
|
|
94
|
+
mkdirSync(dirname(resolve(proofFile)), { recursive: true });
|
|
95
|
+
writeFileSync(proofFile, "");
|
|
96
|
+
}
|
|
97
|
+
appendFileSync(proofFile, JSON.stringify(entry) + "\n");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Core signing
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
async function signDigest(
|
|
105
|
+
digestB64: string,
|
|
106
|
+
metadata: Record<string, unknown>,
|
|
107
|
+
signer: SignerState,
|
|
108
|
+
): Promise<OCCProof> {
|
|
109
|
+
const prevHash = signer.stub.getLastProofHash();
|
|
110
|
+
|
|
111
|
+
const commitInput: {
|
|
112
|
+
digestB64: string;
|
|
113
|
+
metadata?: Record<string, unknown>;
|
|
114
|
+
prevProofHashB64?: string;
|
|
115
|
+
} = { digestB64, metadata };
|
|
116
|
+
|
|
117
|
+
if (prevHash) commitInput.prevProofHashB64 = prevHash;
|
|
118
|
+
|
|
119
|
+
const proof = await signer.constructor.commitDigest(commitInput);
|
|
120
|
+
|
|
121
|
+
// Chain: store this proof's hash for the next commit
|
|
122
|
+
const proofHash = Buffer.from(sha256(canonicalize(proof))).toString("base64");
|
|
123
|
+
signer.stub.setLastProofHash(proofHash);
|
|
124
|
+
|
|
125
|
+
return proof;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function hashPayload(data: unknown): string {
|
|
129
|
+
const bytes = new TextEncoder().encode(JSON.stringify(data));
|
|
130
|
+
return Buffer.from(sha256(bytes)).toString("base64");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// OpenAI wrapper
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Wrap an OpenAI client instance to produce Ed25519-signed proofs for
|
|
139
|
+
* every tool call in chat completion responses.
|
|
140
|
+
*
|
|
141
|
+
* The returned client is a Proxy — all non-completion methods pass through
|
|
142
|
+
* unchanged. Only `client.chat.completions.create()` is intercepted.
|
|
143
|
+
*
|
|
144
|
+
* @param client - An OpenAI client instance
|
|
145
|
+
* @param options - Configuration options
|
|
146
|
+
* @returns A wrapped OpenAI client (same type)
|
|
147
|
+
*/
|
|
148
|
+
export function wrapOpenAI<T extends { chat: { completions: { create: (...args: any[]) => any } } }>(
|
|
149
|
+
client: T,
|
|
150
|
+
options?: WrapOpenAIOptions,
|
|
151
|
+
): T {
|
|
152
|
+
const proofFile = resolve(options?.proofFile ?? "proof.jsonl");
|
|
153
|
+
const statePath = resolve(options?.statePath ?? ".occ/signer-state.json");
|
|
154
|
+
const measurement = options?.measurement ?? "occ-openai:stub";
|
|
155
|
+
const agentId = options?.agentId ?? "openai-agent";
|
|
156
|
+
|
|
157
|
+
// Deep proxy: client.chat.completions.create
|
|
158
|
+
const originalCreate = client.chat.completions.create.bind(client.chat.completions);
|
|
159
|
+
|
|
160
|
+
const wrappedCreate = async (...args: any[]): Promise<any> => {
|
|
161
|
+
const response = await originalCreate(...args);
|
|
162
|
+
|
|
163
|
+
// Only sign if the response has tool calls
|
|
164
|
+
const choices = response?.choices;
|
|
165
|
+
if (!choices || !Array.isArray(choices)) return response;
|
|
166
|
+
|
|
167
|
+
const signer = await getSigner(statePath, measurement);
|
|
168
|
+
|
|
169
|
+
for (const choice of choices) {
|
|
170
|
+
const toolCalls = choice?.message?.tool_calls;
|
|
171
|
+
if (!toolCalls || !Array.isArray(toolCalls)) continue;
|
|
172
|
+
|
|
173
|
+
for (const toolCall of toolCalls) {
|
|
174
|
+
const toolName = toolCall.function?.name ?? "unknown";
|
|
175
|
+
let toolArgs: Record<string, unknown> = {};
|
|
176
|
+
try {
|
|
177
|
+
toolArgs = JSON.parse(toolCall.function?.arguments ?? "{}");
|
|
178
|
+
} catch {
|
|
179
|
+
toolArgs = { raw: toolCall.function?.arguments };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Pre-execution proof: sign the tool call input
|
|
183
|
+
const preDigest = hashPayload({
|
|
184
|
+
tool: toolName,
|
|
185
|
+
args: toolArgs,
|
|
186
|
+
callId: toolCall.id,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const preProof = await signDigest(preDigest, {
|
|
190
|
+
phase: "pre-execution",
|
|
191
|
+
tool: toolName,
|
|
192
|
+
callId: toolCall.id,
|
|
193
|
+
agentId,
|
|
194
|
+
}, signer);
|
|
195
|
+
|
|
196
|
+
appendProof({
|
|
197
|
+
timestamp: new Date().toISOString(),
|
|
198
|
+
phase: "pre-execution",
|
|
199
|
+
tool: toolName,
|
|
200
|
+
agentId,
|
|
201
|
+
args: toolArgs,
|
|
202
|
+
proofDigestB64: preDigest,
|
|
203
|
+
receipt: preProof,
|
|
204
|
+
}, proofFile);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return response;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Build the proxy chain
|
|
212
|
+
const wrappedCompletions = new Proxy(client.chat.completions, {
|
|
213
|
+
get(target: any, prop: string | symbol) {
|
|
214
|
+
if (prop === "create") return wrappedCreate;
|
|
215
|
+
return target[prop];
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const wrappedChat = new Proxy(client.chat, {
|
|
220
|
+
get(target: any, prop: string | symbol) {
|
|
221
|
+
if (prop === "completions") return wrappedCompletions;
|
|
222
|
+
return target[prop];
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return new Proxy(client, {
|
|
227
|
+
get(target: any, prop: string | symbol) {
|
|
228
|
+
if (prop === "chat") return wrappedChat;
|
|
229
|
+
return target[prop];
|
|
230
|
+
},
|
|
231
|
+
}) as T;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Sign a tool execution result after the tool function has been called.
|
|
236
|
+
*
|
|
237
|
+
* Use this to create post-execution proofs when you handle tool calls
|
|
238
|
+
* in your own execution loop.
|
|
239
|
+
*
|
|
240
|
+
* @param toolName - Name of the tool that was executed
|
|
241
|
+
* @param toolArgs - Arguments passed to the tool
|
|
242
|
+
* @param result - The tool execution result
|
|
243
|
+
* @param options - Configuration options
|
|
244
|
+
*/
|
|
245
|
+
export async function signToolResult(
|
|
246
|
+
toolName: string,
|
|
247
|
+
toolArgs: Record<string, unknown>,
|
|
248
|
+
result: unknown,
|
|
249
|
+
options?: WrapOpenAIOptions,
|
|
250
|
+
): Promise<OCCProof> {
|
|
251
|
+
const proofFile = resolve(options?.proofFile ?? "proof.jsonl");
|
|
252
|
+
const statePath = resolve(options?.statePath ?? ".occ/signer-state.json");
|
|
253
|
+
const measurement = options?.measurement ?? "occ-openai:stub";
|
|
254
|
+
const agentId = options?.agentId ?? "openai-agent";
|
|
255
|
+
|
|
256
|
+
const signer = await getSigner(statePath, measurement);
|
|
257
|
+
|
|
258
|
+
const postDigest = hashPayload({
|
|
259
|
+
tool: toolName,
|
|
260
|
+
args: toolArgs,
|
|
261
|
+
result,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const postProof = await signDigest(postDigest, {
|
|
265
|
+
phase: "post-execution",
|
|
266
|
+
tool: toolName,
|
|
267
|
+
agentId,
|
|
268
|
+
}, signer);
|
|
269
|
+
|
|
270
|
+
appendProof({
|
|
271
|
+
timestamp: new Date().toISOString(),
|
|
272
|
+
phase: "post-execution",
|
|
273
|
+
tool: toolName,
|
|
274
|
+
agentId,
|
|
275
|
+
args: toolArgs,
|
|
276
|
+
output: typeof result === "string" ? result.slice(0, 1000) : result,
|
|
277
|
+
proofDigestB64: postDigest,
|
|
278
|
+
receipt: postProof,
|
|
279
|
+
}, proofFile);
|
|
280
|
+
|
|
281
|
+
return postProof;
|
|
282
|
+
}
|