nanocodex 0.4.0 → 0.6.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 +944 -78
- package/actions/events.mjs +14 -25
- package/actions/index.d.mts +1 -0
- package/actions/index.mjs +11 -1
- package/actions/session.d.mts +29 -1
- package/actions/session.mjs +22 -0
- package/actions/turn.d.mts +15 -6
- package/actions/turn.mjs +10 -0
- package/actions/voice.d.mts +26 -0
- package/actions/voice.mjs +54 -0
- package/browser/Agent.d.mts +41 -24
- package/browser/Agent.mjs +7 -84
- package/browser/ChatGptSubscription.d.mts +7 -0
- package/browser/ChatGptSubscription.mjs +19 -0
- package/browser/InlineAgent.mjs +267 -0
- package/browser/Transport.d.mts +80 -0
- package/browser/Transport.mjs +63 -0
- package/browser/Voice.d.mts +86 -0
- package/browser/Voice.mjs +316 -0
- package/browser/VoiceSession.mjs +763 -0
- package/browser/WorkerAgent.d.mts +65 -0
- package/browser/WorkerAgent.mjs +1467 -0
- package/browser/agent.worker.mjs +3 -0
- package/browser/config.d.mts +36 -0
- package/browser/config.mjs +439 -0
- package/browser/engine.mjs +13 -0
- package/browser/harness.mjs +55 -0
- package/browser/host.d.mts +33 -5
- package/browser/host.mjs +320 -26
- package/browser/hostManagedWebSocket.d.mts +14 -0
- package/browser/hostManagedWebSocket.mjs +110 -0
- package/browser/index.d.mts +64 -1
- package/browser/index.mjs +22 -1
- package/browser/indexeddb-durability-store.mjs +210 -0
- package/browser/workspace.d.mts +16 -0
- package/browser/workspace.mjs +107 -0
- package/cloud/Client.d.mts +83 -0
- package/cloud/Client.mjs +231 -0
- package/cloud/Decorator.d.mts +31 -0
- package/cloud/Decorator.mjs +37 -0
- package/cloud/Dialog.d.mts +113 -0
- package/cloud/Dialog.mjs +366 -0
- package/cloud/Errors.d.mts +22 -0
- package/cloud/Errors.mjs +39 -0
- package/cloud/Principal.d.mts +26 -0
- package/cloud/Principal.mjs +88 -0
- package/cloud/RemoteProvider.mjs +88 -0
- package/cloud/Transport.d.mts +47 -0
- package/cloud/Transport.mjs +454 -0
- package/cloud/actions/account.d.mts +9 -0
- package/cloud/actions/account.mjs +19 -0
- package/cloud/actions/agent.d.mts +18 -0
- package/cloud/actions/agent.mjs +429 -0
- package/cloud/actions/connection.d.mts +133 -0
- package/cloud/actions/connection.mjs +580 -0
- package/cloud/actions/grant.d.mts +10 -0
- package/cloud/actions/grant.mjs +12 -0
- package/cloud/actions/index.d.mts +7 -0
- package/cloud/actions/index.mjs +7 -0
- package/cloud/actions/machineUsd.d.mts +23 -0
- package/cloud/actions/machineUsd.mjs +37 -0
- package/cloud/actions/model.d.mts +12 -0
- package/cloud/actions/model.mjs +50 -0
- package/cloud/actions/mpp.d.mts +28 -0
- package/cloud/actions/mpp.mjs +39 -0
- package/cloud/index.d.mts +30 -0
- package/cloud/index.mjs +9 -0
- package/cloud/internal.mjs +533 -0
- package/cloud/mercator.mjs +108 -0
- package/cloud/server/HostPrincipal.d.mts +34 -0
- package/cloud/server/HostPrincipal.mjs +202 -0
- package/cloud/server/index.d.mts +1 -0
- package/cloud/server/index.mjs +1 -0
- package/cloud/types.d.mts +212 -0
- package/cloudflare/Agent.d.mts +136 -0
- package/cloudflare/Agent.mjs +784 -0
- package/cloudflare/egress-subject.mjs +19 -0
- package/cloudflare/egress.d.mts +41 -0
- package/cloudflare/egress.mjs +119 -0
- package/cloudflare/event-socket.mjs +316 -0
- package/cloudflare/index.d.mts +7 -0
- package/cloudflare/index.mjs +6 -0
- package/host/Agent.d.mts +55 -0
- package/host/Agent.mjs +1 -0
- package/host/index.d.mts +71 -0
- package/host/index.mjs +10 -0
- package/index.d.mts +46 -0
- package/index.mjs +11 -0
- package/internal.mjs +673 -61
- package/managed/Agent.d.mts +459 -0
- package/managed/Agent.mjs +1824 -0
- package/managed/ManagedError.d.mts +9 -0
- package/managed/ManagedError.mjs +8 -0
- package/managed/README.md +244 -0
- package/managed/Voice.mjs +209 -0
- package/managed/index.d.mts +22 -0
- package/managed/index.mjs +2 -0
- package/managed/internal.mjs +119 -0
- package/node/Agent.d.mts +35 -8
- package/node/Agent.mjs +116 -25
- package/node/ChatGptSubscription.d.mts +9 -0
- package/node/ChatGptSubscription.mjs +17 -0
- package/node/Transport.d.mts +41 -0
- package/node/Transport.mjs +46 -0
- package/node/host.mjs +99 -17
- package/node/index.d.mts +45 -1
- package/node/index.mjs +16 -1
- package/node/workspace.d.mts +10 -0
- package/node/workspace.mjs +198 -0
- package/package.json +145 -11
- package/pkg-node/nanocodex.d.ts +523 -11
- package/pkg-node/nanocodex.js +2038 -179
- package/pkg-node/nanocodex_bg.wasm.d.ts +100 -29
- package/pkg-web/.nanocodex-bindgen-stamp +6 -0
- package/pkg-web/nanocodex-build.json +1 -0
- package/pkg-web/nanocodex.d.ts +623 -40
- package/pkg-web/nanocodex.js +2031 -178
- package/pkg-web/nanocodex_bg.js +2667 -0
- package/pkg-web/nanocodex_bg.wasm +0 -0
- package/pkg-web/nanocodex_bg.wasm.d.ts +100 -29
- package/pkg-web/nanocodex_worker.js +9 -0
- package/runtime/chatgpt-subscription.mjs +212 -0
- package/runtime/cloudflare-durability-store.d.mts +26 -0
- package/runtime/cloudflare-durability-store.mjs +14 -0
- package/runtime/code-evaluator.worker.mjs +137 -0
- package/runtime/code-runtime.mjs +1 -243
- package/runtime/durability-store.d.mts +85 -0
- package/runtime/durability-store.mjs +752 -0
- package/runtime/durability.mjs +174 -0
- package/runtime/managed-transport.mjs +461 -0
- package/runtime/mcp-runtime.mjs +534 -0
- package/runtime/postgres-durability-store.d.mts +64 -0
- package/runtime/postgres-durability-store.mjs +581 -0
- package/runtime/quickjs-evaluator.d.mts +17 -0
- package/runtime/quickjs-evaluator.mjs +236 -0
- package/runtime/response-controls.mjs +42 -0
- package/runtime/response-lanes.mjs +72 -0
- package/runtime/responses-transport.mjs +22 -0
- package/runtime/subagents.d.mts +107 -0
- package/runtime/subagents.mjs +54 -0
- package/runtime/subscription-store.d.mts +8 -0
- package/runtime/subscription-store.mjs +42 -0
- package/runtime/tempo-provider.d.mts +125 -0
- package/runtime/tempo-provider.mjs +264 -0
- package/runtime/tool-configuration.mjs +1 -0
- package/runtime/tool-router.mjs +1 -0
- package/runtime/utf8.mjs +1 -0
- package/runtime/worker-evaluator.mjs +162 -0
- package/runtime/workspace.d.mts +1 -0
- package/runtime/workspace.mjs +1 -0
- package/tools/Tools.d.mts +56 -0
- package/tools/Tools.mjs +136 -0
- package/tools/artifact.d.mts +1 -0
- package/tools/artifact.mjs +1 -0
- package/tools/attachment.mjs +1 -0
- package/tools/bash.d.mts +1 -0
- package/tools/bash.mjs +1 -0
- package/tools/browser/accountInfo.mjs +795 -0
- package/tools/browser/browserBuffer.mjs +18 -0
- package/tools/browser/browserCompiler.mjs +155 -0
- package/tools/browser/browserEgress.mjs +161 -0
- package/tools/browser/browserPython.mjs +133 -0
- package/tools/browser/browserShell.mjs +1075 -0
- package/tools/browser/browserSprintf.mjs +4 -0
- package/tools/browser/browserSsh.mjs +201 -0
- package/tools/browser/browserZlib.mjs +136 -0
- package/tools/browser/compiler.worker.mjs +160 -0
- package/tools/browser/devTunnelsSshBrowser.mjs +13 -0
- package/tools/browser/index.d.mts +209 -0
- package/tools/browser/index.mjs +174 -0
- package/tools/browser/opfsGit.mjs +248 -0
- package/tools/browser/python.worker.mjs +114 -0
- package/tools/browser/threadGit.mjs +197 -0
- package/tools/browser/unsupportedNodeRsa.mjs +5 -0
- package/tools/browser/workspace.mjs +83 -0
- package/tools/dataset.d.mts +1 -0
- package/tools/dataset.mjs +1 -0
- package/tools/datasetContract.mjs +1 -0
- package/tools/datasetEngine.mjs +1 -0
- package/tools/hostedCatalog.d.mts +1 -0
- package/tools/hostedCatalog.mjs +1 -0
- package/tools/index.d.mts +49 -0
- package/tools/index.mjs +19 -0
- package/tools/namedTool.mjs +1 -0
- package/tools/repository-workspace.d.mts +1 -0
- package/tools/repository-workspace.mjs +1 -0
- package/tools/ssh.d.mts +1 -0
- package/tools/ssh.mjs +1 -0
- package/tools/standard.mjs +1 -0
- package/tools/standardDescriptions.mjs +1 -0
- package/types.d.mts +431 -16
- package/worker/ChatGptSubscription.d.mts +9 -0
- package/worker/ChatGptSubscription.mjs +29 -0
- package/worker/index.d.mts +18 -0
- package/worker/index.mjs +5 -0
- package/pkg-node/nanocodex_bg.wasm +0 -0
package/internal.mjs
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
const agentStates = new WeakMap();
|
|
2
2
|
const turnStates = new WeakMap();
|
|
3
3
|
const resultStates = new WeakMap();
|
|
4
|
+
const resultFinalizer = typeof FinalizationRegistry === "function"
|
|
5
|
+
? new FinalizationRegistry((raw) => {
|
|
6
|
+
try { raw.free(); } catch (error) { reportError(error); }
|
|
7
|
+
})
|
|
8
|
+
: undefined;
|
|
4
9
|
const hostSessions = new Map();
|
|
10
|
+
const cloudflareHostReservations = new WeakMap();
|
|
11
|
+
const activeAgentSessions = new Map();
|
|
12
|
+
const pendingCloudflareAgentSessions = new Map();
|
|
13
|
+
const cloudflareAgentSessions = new WeakSet();
|
|
5
14
|
const hostConnections = new Map();
|
|
6
|
-
|
|
15
|
+
const definitionHosts = new Map();
|
|
7
16
|
let nextHostConnection = 1;
|
|
17
|
+
let nextDefinitionHost = 1;
|
|
8
18
|
let nextAgentUid = 1;
|
|
9
19
|
|
|
20
|
+
export const CLOUDFLARE_SESSION_RESERVATION = Symbol("nanocodex.cloudflare.sessionReservation");
|
|
21
|
+
|
|
10
22
|
export function defineRuntime(definition) {
|
|
11
23
|
if (!definition || typeof definition.create !== "function") {
|
|
12
24
|
throw new TypeError("a Nanocodex runtime must define create(options)");
|
|
@@ -21,33 +33,107 @@ export function defineRuntime(definition) {
|
|
|
21
33
|
adopt: definition.adopt,
|
|
22
34
|
release: definition.release,
|
|
23
35
|
decorate: definition.decorate,
|
|
36
|
+
reserveSessions: definition.reserveSessions !== false,
|
|
24
37
|
});
|
|
25
38
|
}
|
|
26
39
|
|
|
27
|
-
export async function createAgentClient(runtime, options = {}) {
|
|
40
|
+
export async function createAgentClient(runtime, options = {}, requestedReservation) {
|
|
28
41
|
if (!runtime || typeof runtime.create !== "function") {
|
|
29
42
|
throw new TypeError("createAgent requires a Nanocodex runtime");
|
|
30
43
|
}
|
|
31
|
-
|
|
44
|
+
const reserveSession = runtime.reserveSessions !== false;
|
|
45
|
+
const reservation = requestedReservation ?? (
|
|
46
|
+
!reserveSession || options.sessionId === undefined
|
|
47
|
+
? undefined
|
|
48
|
+
: reserveAgentSession(options.sessionId)
|
|
49
|
+
);
|
|
50
|
+
let raw;
|
|
51
|
+
try {
|
|
52
|
+
raw = await runtime.create(options);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
releaseAgentSession(reservation);
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
return createAgent(raw, runtime, reservation, reserveSession);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Internal adapter seam: observes completion of the owned Agent release path. */
|
|
61
|
+
export function observeAgentRelease(agent, listener) {
|
|
62
|
+
if (typeof listener !== "function") throw new TypeError("Agent release observer must be a function");
|
|
63
|
+
const state = knownAgentState(agent);
|
|
64
|
+
if (state.released) {
|
|
65
|
+
listener({ graceful: state.shutdownPromise !== undefined });
|
|
66
|
+
return () => {};
|
|
67
|
+
}
|
|
68
|
+
state.releaseObservers.add(listener);
|
|
69
|
+
return () => state.releaseObservers.delete(listener);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Creates the stable UUIDv7 identity reserved before a WASM Agent is constructed. */
|
|
73
|
+
export function createSessionId() {
|
|
74
|
+
if (typeof globalThis.crypto?.getRandomValues !== "function") {
|
|
75
|
+
throw new Error("Nanocodex Agent creation requires crypto.getRandomValues()");
|
|
76
|
+
}
|
|
77
|
+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
78
|
+
let timestamp = Date.now();
|
|
79
|
+
for (let index = 5; index >= 0; index -= 1) {
|
|
80
|
+
bytes[index] = timestamp % 256;
|
|
81
|
+
timestamp = Math.floor(timestamp / 256);
|
|
82
|
+
}
|
|
83
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x70;
|
|
84
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
85
|
+
const encoded = [...bytes].map((byte) => byte.toString(16).padStart(2, "0"));
|
|
86
|
+
return `${encoded.slice(0, 4).join("")}-${encoded.slice(4, 6).join("")}-${encoded.slice(6, 8).join("")}-${encoded.slice(8, 10).join("")}-${encoded.slice(10).join("")}`;
|
|
32
87
|
}
|
|
33
88
|
|
|
34
89
|
export function prompt(agent, options) {
|
|
35
90
|
const state = agentState(agent);
|
|
36
91
|
const input = actionInput(options);
|
|
92
|
+
const operationId = options?.id;
|
|
93
|
+
const cancelOnAdmission = options?.cancelOnAdmission === true;
|
|
37
94
|
const raw = typeof input === "string"
|
|
38
|
-
? state.raw.prompt(input)
|
|
39
|
-
: state.raw.promptContent(JSON.stringify(input));
|
|
95
|
+
? state.raw.prompt(input, operationId, cancelOnAdmission)
|
|
96
|
+
: state.raw.promptContent(JSON.stringify(input), operationId, cancelOnAdmission);
|
|
40
97
|
return createTurn(raw, agent);
|
|
41
98
|
}
|
|
42
99
|
|
|
100
|
+
/** Internal live-input seam: atomically steers the active turn or starts one. */
|
|
101
|
+
export async function routePrompt(agent, options) {
|
|
102
|
+
const state = agentState(agent);
|
|
103
|
+
const input = actionInput(options);
|
|
104
|
+
if (typeof input !== "string") {
|
|
105
|
+
throw new TypeError("live routed input must be text");
|
|
106
|
+
}
|
|
107
|
+
const raw = await state.raw.routePrompt(input);
|
|
108
|
+
return raw === undefined ? undefined : createTurn(raw, agent);
|
|
109
|
+
}
|
|
110
|
+
|
|
43
111
|
export function getTurnResult(turn) {
|
|
44
112
|
const state = turnState(turn);
|
|
45
|
-
state.result
|
|
46
|
-
|
|
47
|
-
|
|
113
|
+
if (!state.result) {
|
|
114
|
+
try {
|
|
115
|
+
state.result = Promise.resolve(state.raw.result()).then(createTurnResult);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
state.result = Promise.reject(error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
48
120
|
return state.result;
|
|
49
121
|
}
|
|
50
122
|
|
|
123
|
+
export function awaitTurnAcceptance(turn) {
|
|
124
|
+
const state = turnState(turn);
|
|
125
|
+
if (!state.acceptance) {
|
|
126
|
+
try {
|
|
127
|
+
state.acceptance = typeof state.raw.accepted === "function"
|
|
128
|
+
? Promise.resolve(state.raw.accepted())
|
|
129
|
+
: Promise.resolve(undefined);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
state.acceptance = Promise.reject(error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return state.acceptance;
|
|
135
|
+
}
|
|
136
|
+
|
|
51
137
|
export function getTurnSnapshot(result) {
|
|
52
138
|
return resultState(result).snapshot();
|
|
53
139
|
}
|
|
@@ -56,12 +142,27 @@ export function getTurnUsage(result) {
|
|
|
56
142
|
return resultState(result).usage();
|
|
57
143
|
}
|
|
58
144
|
|
|
145
|
+
/** Internal Worker seam: preserves the Rust-owned encoding until it reaches its consumer. */
|
|
146
|
+
export function getEncodedTurnSnapshot(result) {
|
|
147
|
+
return encodedTurnResultValue(resultState(result), "snapshot");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Internal Worker seam: preserves the Rust-owned encoding until it reaches its consumer. */
|
|
151
|
+
export function getEncodedTurnUsage(result) {
|
|
152
|
+
return encodedTurnResultValue(resultState(result), "usage");
|
|
153
|
+
}
|
|
154
|
+
|
|
59
155
|
export function steer(turn, options) {
|
|
60
156
|
const state = turnState(turn);
|
|
61
157
|
const input = actionInput(options);
|
|
62
158
|
return typeof input === "string"
|
|
63
|
-
? state.raw.steer(input)
|
|
64
|
-
: state.raw.steerContent(JSON.stringify(input));
|
|
159
|
+
? state.raw.steer(input, options.messageId)
|
|
160
|
+
: state.raw.steerContent(JSON.stringify(input), options.messageId);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function withdrawSteer(turn, { messageId }) {
|
|
164
|
+
if (typeof messageId !== "string" || !messageId) throw new TypeError("messageId must be a non-empty string");
|
|
165
|
+
return turnState(turn).raw.withdrawSteer(messageId);
|
|
65
166
|
}
|
|
66
167
|
|
|
67
168
|
export function cancel(turn) {
|
|
@@ -82,10 +183,42 @@ export async function spawn(agent) {
|
|
|
82
183
|
return createAgent(await state.raw.spawn(), state.runtime);
|
|
83
184
|
}
|
|
84
185
|
|
|
186
|
+
export async function spawnSubagent(agent, options) {
|
|
187
|
+
return JSON.parse(await agentState(agent).raw.spawnSubagent(JSON.stringify(options)));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function spawnSubagents(agent, options) {
|
|
191
|
+
return JSON.parse(await agentState(agent).raw.spawnSubagents(JSON.stringify(options)));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function waitSubagents(agent, options) {
|
|
195
|
+
return JSON.parse(await agentState(agent).raw.waitSubagents(JSON.stringify(options)));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export async function listSubagents(agent, options) {
|
|
199
|
+
return JSON.parse(await agentState(agent).raw.listSubagents(JSON.stringify(options ?? {})));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function sendSubagentMessage(agent, options) {
|
|
203
|
+
return JSON.parse(await agentState(agent).raw.sendSubagentMessage(JSON.stringify(options)));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function interruptSubagent(agent, agentId) {
|
|
207
|
+
return JSON.parse(await agentState(agent).raw.interruptSubagent(JSON.stringify({ agentId })));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export async function closeSubagent(agent, agentId) {
|
|
211
|
+
return JSON.parse(await agentState(agent).raw.closeSubagent(JSON.stringify({ agentId })));
|
|
212
|
+
}
|
|
213
|
+
|
|
85
214
|
export function setThinking(agent, thinking) {
|
|
86
215
|
return agentState(agent).raw.setThinking(thinking);
|
|
87
216
|
}
|
|
88
217
|
|
|
218
|
+
export function setModel(agent, model) {
|
|
219
|
+
return agentState(agent).raw.setModel(model);
|
|
220
|
+
}
|
|
221
|
+
|
|
89
222
|
export function setFastMode(agent, enabled) {
|
|
90
223
|
return agentState(agent).raw.setFastMode(enabled);
|
|
91
224
|
}
|
|
@@ -94,6 +227,38 @@ export function compact(agent) {
|
|
|
94
227
|
return agentState(agent).raw.compact();
|
|
95
228
|
}
|
|
96
229
|
|
|
230
|
+
export async function context(agent) {
|
|
231
|
+
return parseSessionContext(await agentState(agent).raw.context());
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function appendDeveloperMessage(agent, text) {
|
|
235
|
+
if (typeof text !== "string" || !text.trim()) {
|
|
236
|
+
throw new TypeError("non-empty string");
|
|
237
|
+
}
|
|
238
|
+
return parseSessionContext(await agentState(agent).raw.appendDeveloperMessage(text));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export async function startRealtimeConversation(agent) {
|
|
242
|
+
return parseSessionContext(await agentState(agent).raw.startRealtimeConversation());
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function endRealtimeConversation(agent) {
|
|
246
|
+
return parseSessionContext(await agentState(agent).raw.endRealtimeConversation());
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export async function realtimeDelegation(agent, input, transcript = []) {
|
|
250
|
+
return agentState(agent).raw.realtimeDelegation(input, JSON.stringify(transcript));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function realtimeTailDelegation(agent, transcript) {
|
|
254
|
+
return agentState(agent).raw.realtimeTailDelegation(JSON.stringify(transcript));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Internal browser seam over the Rust-owned voice controller. */
|
|
258
|
+
export function createBrowserVoice(agent, voice) {
|
|
259
|
+
return agentState(agent).raw.browserVoice(voice);
|
|
260
|
+
}
|
|
261
|
+
|
|
97
262
|
export async function shutdown(agent) {
|
|
98
263
|
const state = knownAgentState(agent);
|
|
99
264
|
if (state.shutdownPromise) return state.shutdownPromise;
|
|
@@ -106,6 +271,10 @@ export async function shutdown(agent) {
|
|
|
106
271
|
return state.shutdownPromise;
|
|
107
272
|
}
|
|
108
273
|
|
|
274
|
+
function parseSessionContext(context) {
|
|
275
|
+
return JSON.parse(context);
|
|
276
|
+
}
|
|
277
|
+
|
|
109
278
|
export function subscribeAgentEvents(agent, listener, options = {}, onRelease) {
|
|
110
279
|
const state = agentState(agent);
|
|
111
280
|
if (typeof state.runtime.subscribe !== "function") {
|
|
@@ -114,9 +283,9 @@ export function subscribeAgentEvents(agent, listener, options = {}, onRelease) {
|
|
|
114
283
|
if (typeof listener !== "function") {
|
|
115
284
|
throw new TypeError("watchAgentEvents requires a listener");
|
|
116
285
|
}
|
|
117
|
-
const unsubscribe = state.runtime.subscribe((event, encodedLength) => {
|
|
286
|
+
const unsubscribe = state.runtime.subscribe((event, encodedLength, encodedEvent, agentId) => {
|
|
118
287
|
if (options.includeAllSessions || !event?.request_id || event.request_id === agent.sessionId) {
|
|
119
|
-
listener(event, encodedLength);
|
|
288
|
+
listener(event, encodedLength, encodedEvent, agentId);
|
|
120
289
|
}
|
|
121
290
|
});
|
|
122
291
|
let active = true;
|
|
@@ -145,27 +314,72 @@ export function toWasmConfig(options = {}) {
|
|
|
145
314
|
copy(config, "thinking", options.thinking);
|
|
146
315
|
copy(config, "reasoning_mode", options.reasoningMode);
|
|
147
316
|
copy(config, "fast_mode", options.fastMode);
|
|
317
|
+
copy(config, "websocket_warmup", options.websocketWarmup);
|
|
148
318
|
copy(config, "websocket_url", options.websocketUrl);
|
|
149
319
|
copy(config, "api_base_url", options.apiBaseUrl);
|
|
150
320
|
copy(config, "instructions", options.instructions);
|
|
321
|
+
copy(config, "additional_instructions", options.additionalInstructions);
|
|
151
322
|
copy(config, "session_id", options.sessionId);
|
|
152
323
|
copy(config, "workspace", options.workspace);
|
|
324
|
+
if (options.executionEnvironment !== undefined) {
|
|
325
|
+
const environment = options.executionEnvironment;
|
|
326
|
+
if (!environment || typeof environment !== "object" || Array.isArray(environment)) {
|
|
327
|
+
throw new TypeError("executionEnvironment must be an object");
|
|
328
|
+
}
|
|
329
|
+
config.execution_environment = {
|
|
330
|
+
current_date: environment.currentDate,
|
|
331
|
+
timezone: environment.timezone,
|
|
332
|
+
};
|
|
333
|
+
copy(
|
|
334
|
+
config.execution_environment,
|
|
335
|
+
"project_instructions",
|
|
336
|
+
environment.projectInstructions,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
153
339
|
copy(config, "resume", options.resume);
|
|
340
|
+
copy(config, "durability_id", options.durabilityId);
|
|
341
|
+
copy(config, "durability_host_id", options.durabilityHostId);
|
|
342
|
+
copy(config, "terminal_receipt_retention", options.terminalReceiptRetention);
|
|
343
|
+
copy(config, "subagents", options.subagents);
|
|
344
|
+
copy(config, "host_definition_id", options.hostDefinitionId);
|
|
154
345
|
return config;
|
|
155
346
|
}
|
|
156
347
|
|
|
157
348
|
export function createEventChannel() {
|
|
158
349
|
const listeners = new Set();
|
|
350
|
+
const sources = new Set();
|
|
159
351
|
return Object.freeze({
|
|
160
|
-
emit(eventJson) {
|
|
352
|
+
emit(eventJson, encodedBytes, agentId) {
|
|
161
353
|
if (!listeners.size) return;
|
|
162
|
-
const event = typeof eventJson === "string" ? JSON.parse(eventJson) : eventJson;
|
|
163
|
-
const encodedLength =
|
|
164
|
-
|
|
354
|
+
const event = freezeJson(typeof eventJson === "string" ? JSON.parse(eventJson) : eventJson);
|
|
355
|
+
const encodedLength = Number.isSafeInteger(encodedBytes) && encodedBytes >= 0
|
|
356
|
+
? encodedBytes
|
|
357
|
+
: undefined;
|
|
358
|
+
for (const listener of listeners) listener(event, encodedLength, eventJson, agentId);
|
|
165
359
|
},
|
|
166
360
|
subscribe(listener) {
|
|
361
|
+
const activate = listeners.size === 0;
|
|
167
362
|
listeners.add(listener);
|
|
168
|
-
|
|
363
|
+
if (activate) {
|
|
364
|
+
for (const source of sources) source.setEventForwarding?.(true);
|
|
365
|
+
}
|
|
366
|
+
let active = true;
|
|
367
|
+
return () => {
|
|
368
|
+
if (!active) return;
|
|
369
|
+
active = false;
|
|
370
|
+
listeners.delete(listener);
|
|
371
|
+
if (listeners.size === 0) {
|
|
372
|
+
for (const source of sources) source.setEventForwarding?.(false);
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
},
|
|
376
|
+
addSource(source) {
|
|
377
|
+
sources.add(source);
|
|
378
|
+
if (listeners.size) source.setEventForwarding?.(true);
|
|
379
|
+
},
|
|
380
|
+
removeSource(source) {
|
|
381
|
+
if (!sources.delete(source)) return;
|
|
382
|
+
source.setEventForwarding?.(false);
|
|
169
383
|
},
|
|
170
384
|
});
|
|
171
385
|
}
|
|
@@ -174,30 +388,71 @@ export function activateHost(host) {
|
|
|
174
388
|
if (!host || typeof host.connect !== "function") {
|
|
175
389
|
throw new TypeError("a Nanocodex host must define connect()");
|
|
176
390
|
}
|
|
177
|
-
|
|
391
|
+
installHostBridge();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function installHostBridge() {
|
|
178
395
|
globalThis.nanocodexHost = hostBridge;
|
|
179
396
|
}
|
|
180
397
|
|
|
181
|
-
export function
|
|
398
|
+
export function loadDurabilityRuntime() {
|
|
399
|
+
return import("./runtime/durability.mjs");
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function reportError(error) {
|
|
403
|
+
try {
|
|
404
|
+
if (typeof globalThis.reportError === "function") globalThis.reportError(error);
|
|
405
|
+
else globalThis.console?.error?.(error);
|
|
406
|
+
} catch {}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function bindHostSession(host, sessionId, reservation) {
|
|
182
410
|
const existing = hostSessions.get(sessionId);
|
|
411
|
+
const replacementOwner = cloudflareSessionOwner(reservation, sessionId);
|
|
183
412
|
if (existing && existing !== host) {
|
|
184
|
-
|
|
413
|
+
if (replacementOwner === undefined
|
|
414
|
+
|| cloudflareHostReservations.get(existing)?.ownerId !== replacementOwner) {
|
|
415
|
+
throw new Error(`Nanocodex session ID is already active: ${sessionId}`);
|
|
416
|
+
}
|
|
185
417
|
}
|
|
186
418
|
hostSessions.set(sessionId, host);
|
|
419
|
+
if (replacementOwner !== undefined) {
|
|
420
|
+
reservation.host = host;
|
|
421
|
+
}
|
|
187
422
|
}
|
|
188
423
|
|
|
189
424
|
export function releaseHostSession(host, sessionId) {
|
|
190
|
-
if (hostSessions.get(sessionId)
|
|
425
|
+
if (hostSessions.get(sessionId) !== host) return;
|
|
426
|
+
hostSessions.delete(sessionId);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function registerDefinitionHost(host, cloudflareReservation) {
|
|
430
|
+
const id = nextDefinitionHost++;
|
|
431
|
+
definitionHosts.set(id, host);
|
|
432
|
+
if (cloudflareReservation !== undefined) {
|
|
433
|
+
if (!cloudflareAgentSessions.has(cloudflareReservation)
|
|
434
|
+
|| cloudflareReservation.released) {
|
|
435
|
+
definitionHosts.delete(id);
|
|
436
|
+
throw new Error("Cloudflare Agent session reservation is no longer active");
|
|
437
|
+
}
|
|
438
|
+
cloudflareHostReservations.set(host, cloudflareReservation);
|
|
439
|
+
}
|
|
440
|
+
return id;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export function releaseDefinitionHost(id) {
|
|
444
|
+
definitionHosts.delete(id);
|
|
191
445
|
}
|
|
192
446
|
|
|
193
447
|
const hostBridge = Object.freeze({
|
|
194
|
-
async connect(endpoint, apiKey, accountId, fedramp, sessionId, turnState) {
|
|
195
|
-
const host = requiredSessionHost(
|
|
448
|
+
async connect(endpoint, apiKey, accountId, fedramp, sessionId, threadId, turnState) {
|
|
449
|
+
const host = requiredSessionHost(threadId);
|
|
196
450
|
let result;
|
|
197
451
|
try {
|
|
198
452
|
result = JSON.parse(await host.connect(endpoint, apiKey, sessionId, {
|
|
199
453
|
accountId: accountId ?? undefined,
|
|
200
454
|
fedramp,
|
|
455
|
+
threadId,
|
|
201
456
|
turnState: turnState ?? undefined,
|
|
202
457
|
}));
|
|
203
458
|
} catch (error) {
|
|
@@ -205,7 +460,7 @@ const hostBridge = Object.freeze({
|
|
|
205
460
|
}
|
|
206
461
|
const handle = nextHostConnection++;
|
|
207
462
|
hostConnections.set(handle, { host, handle: result.handle });
|
|
208
|
-
hostSessions.set(
|
|
463
|
+
hostSessions.set(threadId, host);
|
|
209
464
|
return JSON.stringify({ ...result, handle });
|
|
210
465
|
},
|
|
211
466
|
send(handle, message) {
|
|
@@ -214,10 +469,10 @@ const hostBridge = Object.freeze({
|
|
|
214
469
|
? connection.host.send(connection.handle, message)
|
|
215
470
|
: Promise.resolve(JSON.stringify({ ok: false, reconnectable: true, error: "unknown WebSocket handle" }));
|
|
216
471
|
},
|
|
217
|
-
next(handle
|
|
472
|
+
next(handle) {
|
|
218
473
|
const connection = hostConnections.get(handle);
|
|
219
474
|
return connection
|
|
220
|
-
? connection.host.next(connection.handle
|
|
475
|
+
? connection.host.next(connection.handle)
|
|
221
476
|
: Promise.resolve(JSON.stringify({ kind: "closed", detail: "for an unknown WebSocket handle" }));
|
|
222
477
|
},
|
|
223
478
|
close(handle) {
|
|
@@ -233,59 +488,220 @@ const hostBridge = Object.freeze({
|
|
|
233
488
|
}
|
|
234
489
|
return host.sleep(milliseconds);
|
|
235
490
|
},
|
|
236
|
-
|
|
237
|
-
|
|
491
|
+
bindSubagentSession(
|
|
492
|
+
hostDefinitionId,
|
|
493
|
+
rootSessionId,
|
|
494
|
+
sessionId,
|
|
495
|
+
contextJson,
|
|
496
|
+
hostContextRef,
|
|
497
|
+
) {
|
|
498
|
+
let host;
|
|
499
|
+
if (contextJson === undefined) {
|
|
500
|
+
contextJson = sessionId;
|
|
501
|
+
sessionId = rootSessionId;
|
|
502
|
+
rootSessionId = hostDefinitionId;
|
|
503
|
+
host = requiredSessionHost(rootSessionId);
|
|
504
|
+
} else {
|
|
505
|
+
host = requiredDefinitionHost(hostDefinitionId);
|
|
506
|
+
}
|
|
507
|
+
if (!cloudflareHostMayBindSubagent(host)) return;
|
|
508
|
+
const existing = hostSessions.get(sessionId);
|
|
509
|
+
if (existing && existing !== host) {
|
|
510
|
+
const ownerId = cloudflareHostReservations.get(host)?.ownerId;
|
|
511
|
+
if (ownerId === undefined
|
|
512
|
+
|| cloudflareHostReservations.get(existing)?.ownerId !== ownerId) {
|
|
513
|
+
throw new Error(`Nanocodex subagent session ID is already active: ${sessionId}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
host.bindSubagentSession(sessionId, JSON.parse(contextJson), hostContextRef);
|
|
517
|
+
hostSessions.set(sessionId, host);
|
|
518
|
+
},
|
|
519
|
+
releaseSubagentSession(hostDefinitionId, rootSessionId, sessionId) {
|
|
520
|
+
let host;
|
|
521
|
+
if (sessionId !== undefined) {
|
|
522
|
+
host = definitionHosts.get(hostDefinitionId);
|
|
523
|
+
} else if (rootSessionId !== undefined) {
|
|
524
|
+
sessionId = rootSessionId;
|
|
525
|
+
host = hostSessions.get(hostDefinitionId);
|
|
526
|
+
} else {
|
|
527
|
+
sessionId = hostDefinitionId;
|
|
528
|
+
host = hostSessions.get(sessionId);
|
|
529
|
+
}
|
|
530
|
+
if (!host || hostSessions.get(sessionId) !== host) return;
|
|
531
|
+
host.releaseSession(sessionId);
|
|
532
|
+
if (hostSessions.get(sessionId) === host) hostSessions.delete(sessionId);
|
|
533
|
+
},
|
|
534
|
+
executeCode(source, sessionId, callId, model) {
|
|
535
|
+
return requiredSessionHost(sessionId).executeCode(source, sessionId, callId, model);
|
|
238
536
|
},
|
|
239
|
-
|
|
240
|
-
return requiredSessionHost(sessionId).
|
|
537
|
+
waitCode(input, sessionId, callId) {
|
|
538
|
+
return requiredSessionHost(sessionId).waitCode(input, sessionId, callId);
|
|
241
539
|
},
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return (hostSessions.get(sessionId) ?? requiredActiveHost()).toolMode();
|
|
540
|
+
nextCodeUpdate(sessionId, callId) {
|
|
541
|
+
return requiredSessionHost(sessionId).nextCodeUpdate(sessionId, callId);
|
|
245
542
|
},
|
|
246
|
-
|
|
543
|
+
executeTool(name, input, sessionId, callId, model) {
|
|
544
|
+
return requiredSessionHost(sessionId).executeTool(name, input, sessionId, callId, model);
|
|
545
|
+
},
|
|
546
|
+
beginCodeTurn(sessionId) {
|
|
547
|
+
hostSessions.get(sessionId)?.beginCodeTurn?.(sessionId);
|
|
548
|
+
},
|
|
549
|
+
cancelCodeTurn(sessionId) {
|
|
550
|
+
hostSessions.get(sessionId)?.cancelCodeTurn?.(sessionId);
|
|
551
|
+
},
|
|
552
|
+
cancelCode(sessionId) {
|
|
553
|
+
hostSessions.get(sessionId)?.cancelCode?.(sessionId);
|
|
554
|
+
},
|
|
555
|
+
readWorkspaceFile(path, sessionId) {
|
|
556
|
+
return requiredSessionHost(sessionId).readWorkspaceFile(path);
|
|
557
|
+
},
|
|
558
|
+
async listWorkspace(path, sessionId) {
|
|
559
|
+
return JSON.stringify(await requiredSessionHost(sessionId).listWorkspace(path));
|
|
560
|
+
},
|
|
561
|
+
writeWorkspaceFile(path, contents, sessionId) {
|
|
562
|
+
return requiredSessionHost(sessionId).writeWorkspaceFile(path, contents);
|
|
563
|
+
},
|
|
564
|
+
removeWorkspaceFile(path, sessionId) {
|
|
565
|
+
return requiredSessionHost(sessionId).removeWorkspaceFile(path);
|
|
566
|
+
},
|
|
567
|
+
async subscriptionLoad(subscriptionId) {
|
|
568
|
+
return (await loadSubscriptionRuntime()).load(subscriptionId);
|
|
569
|
+
},
|
|
570
|
+
async subscriptionCompareAndSwap(subscriptionId, expectedRevision, payload) {
|
|
571
|
+
return (await loadSubscriptionRuntime()).compareAndSwap(
|
|
572
|
+
subscriptionId,
|
|
573
|
+
expectedRevision,
|
|
574
|
+
payload,
|
|
575
|
+
);
|
|
576
|
+
},
|
|
577
|
+
async subscriptionRequest(subscriptionId, request) {
|
|
578
|
+
return (await loadSubscriptionRuntime()).request(subscriptionId, request);
|
|
579
|
+
},
|
|
580
|
+
toolMode(definitionHostId, sessionId) {
|
|
581
|
+
return requiredDefinitionHost(definitionHostId).toolMode(sessionId);
|
|
582
|
+
},
|
|
583
|
+
toolDefinitions(definitionHostId, sessionId) {
|
|
247
584
|
// ModelRun builds its stable tool prefix inside the WASM constructor,
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
return (
|
|
585
|
+
// before the returned session can be adopted. The private definition host
|
|
586
|
+
// keeps that lookup instance-scoped for roots and Rust-spawned children.
|
|
587
|
+
return requiredDefinitionHost(definitionHostId).toolDefinitions(sessionId);
|
|
251
588
|
},
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
589
|
+
async durabilityReadRecords(routeId, stateId, keys) {
|
|
590
|
+
return (await loadDurabilityRuntime()).readRecords(routeId, stateId, keys);
|
|
591
|
+
},
|
|
592
|
+
async durabilityReadRecord(routeId, stateId, key) {
|
|
593
|
+
return (await loadDurabilityRuntime()).readRecord(routeId, stateId, key);
|
|
594
|
+
},
|
|
595
|
+
async durabilityAcquire(routeId, stateId, ownerId) {
|
|
596
|
+
return (await loadDurabilityRuntime()).acquire(routeId, stateId, ownerId);
|
|
597
|
+
},
|
|
598
|
+
async durabilityReplace(
|
|
599
|
+
routeId,
|
|
600
|
+
stateId,
|
|
601
|
+
ownerId,
|
|
602
|
+
fence,
|
|
603
|
+
expectedRevision,
|
|
604
|
+
payload,
|
|
605
|
+
records,
|
|
606
|
+
) {
|
|
607
|
+
return (await loadDurabilityRuntime()).replace(
|
|
608
|
+
routeId,
|
|
609
|
+
stateId,
|
|
610
|
+
ownerId,
|
|
611
|
+
fence,
|
|
612
|
+
expectedRevision,
|
|
613
|
+
payload,
|
|
614
|
+
records,
|
|
615
|
+
);
|
|
616
|
+
},
|
|
617
|
+
emitEvent(sessionId, eventJson, encodedBytes, encodedAgentId) {
|
|
618
|
+
requiredSessionHost(sessionId).emitEvent(
|
|
619
|
+
eventJson,
|
|
620
|
+
encodedBytes,
|
|
621
|
+
parseSubagentAgentId(encodedAgentId),
|
|
622
|
+
);
|
|
255
623
|
},
|
|
256
624
|
});
|
|
257
625
|
|
|
258
|
-
|
|
626
|
+
/** Decodes the string ABI without changing JavaScript's numeric AgentId contract. */
|
|
627
|
+
export function parseSubagentAgentId(encoded) {
|
|
628
|
+
if (encoded === undefined) return undefined;
|
|
629
|
+
if (typeof encoded !== "string" || !/^[1-9][0-9]*$/.test(encoded)) {
|
|
630
|
+
throw new TypeError("subagent agent ID must be a canonical positive decimal string");
|
|
631
|
+
}
|
|
632
|
+
const agentId = Number(encoded);
|
|
633
|
+
if (!Number.isSafeInteger(agentId)) {
|
|
634
|
+
throw new RangeError("subagent agent ID exceeds JavaScript's safe integer range");
|
|
635
|
+
}
|
|
636
|
+
return agentId;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export function loadSubscriptionRuntime() {
|
|
640
|
+
return import("./runtime/chatgpt-subscription.mjs");
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function createAgent(
|
|
644
|
+
raw,
|
|
645
|
+
runtime,
|
|
646
|
+
requestedReservation,
|
|
647
|
+
reserveSession = runtime.reserveSessions !== false,
|
|
648
|
+
) {
|
|
649
|
+
let reservation = requestedReservation;
|
|
259
650
|
if (!raw || typeof raw.prompt !== "function") {
|
|
651
|
+
releaseAgentSession(reservation);
|
|
260
652
|
throw new TypeError("the runtime returned an invalid Nanocodex agent handle");
|
|
261
653
|
}
|
|
654
|
+
try {
|
|
655
|
+
if (reserveSession) {
|
|
656
|
+
reservation ??= reserveAgentSession(raw.sessionId);
|
|
657
|
+
adoptAgentSession(reservation, raw.sessionId);
|
|
658
|
+
}
|
|
659
|
+
} catch (error) {
|
|
660
|
+
const errors = [error];
|
|
661
|
+
runCleanup(errors, () => runtime.dispose(raw));
|
|
662
|
+
runCleanup(errors, () => releaseAgentSession(reservation));
|
|
663
|
+
throwCleanupErrors(errors);
|
|
664
|
+
}
|
|
262
665
|
const state = {
|
|
263
666
|
raw,
|
|
264
667
|
runtime,
|
|
668
|
+
reservation,
|
|
669
|
+
releaseObservers: new Set(),
|
|
265
670
|
disposed: false,
|
|
266
671
|
released: false,
|
|
267
672
|
shutdownPromise: undefined,
|
|
268
673
|
subscriptions: new Set(),
|
|
674
|
+
agentId: typeof raw.agentId === "string" ? raw.agentId : raw.sessionId,
|
|
269
675
|
sessionId: raw.sessionId,
|
|
270
676
|
uid: `agent-${nextAgentUid++}`,
|
|
271
677
|
};
|
|
272
678
|
try {
|
|
273
679
|
runtime.adopt?.(raw);
|
|
274
680
|
} catch (error) {
|
|
275
|
-
|
|
276
|
-
|
|
681
|
+
const errors = [error];
|
|
682
|
+
runCleanup(errors, () => runtime.dispose(raw));
|
|
683
|
+
runCleanup(errors, () => releaseAgentSession(reservation));
|
|
684
|
+
throwCleanupErrors(errors);
|
|
277
685
|
}
|
|
278
686
|
const agent = agentView(state, {});
|
|
279
|
-
|
|
687
|
+
try {
|
|
688
|
+
return runtime.decorate ? runtime.decorate(agent, raw) : agent;
|
|
689
|
+
} catch (error) {
|
|
690
|
+
const errors = [error];
|
|
691
|
+
runCleanup(errors, () => releaseAgentState(state));
|
|
692
|
+
throwCleanupErrors(errors);
|
|
693
|
+
}
|
|
280
694
|
}
|
|
281
695
|
|
|
282
696
|
function agentView(state, extensions) {
|
|
283
697
|
let agent;
|
|
698
|
+
let reservedKeys;
|
|
284
699
|
const base = {
|
|
285
700
|
uid: state.uid,
|
|
286
701
|
key: state.runtime.key,
|
|
287
702
|
name: state.runtime.name,
|
|
288
703
|
type: state.runtime.type,
|
|
704
|
+
get agentId() { return state.agentId; },
|
|
289
705
|
get sessionId() { return state.sessionId; },
|
|
290
706
|
extend(fn) {
|
|
291
707
|
if (typeof fn !== "function") throw new TypeError("agent.extend requires a decorator function");
|
|
@@ -294,7 +710,7 @@ function agentView(state, extensions) {
|
|
|
294
710
|
throw new TypeError("an agent decorator must return an object");
|
|
295
711
|
}
|
|
296
712
|
const extension = { ...value };
|
|
297
|
-
for (const key of
|
|
713
|
+
for (const key of reservedKeys) delete extension[key];
|
|
298
714
|
return agentView(state, deepMerge(extensions, extension));
|
|
299
715
|
},
|
|
300
716
|
dispose() {
|
|
@@ -302,6 +718,7 @@ function agentView(state, extensions) {
|
|
|
302
718
|
releaseAgentState(state);
|
|
303
719
|
},
|
|
304
720
|
};
|
|
721
|
+
reservedKeys = Object.keys(base);
|
|
305
722
|
agent = Object.assign(base, extensions);
|
|
306
723
|
agentStates.set(agent, state);
|
|
307
724
|
return agent;
|
|
@@ -309,7 +726,15 @@ function agentView(state, extensions) {
|
|
|
309
726
|
|
|
310
727
|
function requiredSessionHost(sessionId) {
|
|
311
728
|
const host = hostSessions.get(sessionId);
|
|
312
|
-
if (!host
|
|
729
|
+
if (!host || !cloudflareHostMayBindSubagent(host)) {
|
|
730
|
+
throw new Error(`no Nanocodex host is active for session: ${sessionId}`);
|
|
731
|
+
}
|
|
732
|
+
return host;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function requiredDefinitionHost(id) {
|
|
736
|
+
const host = definitionHosts.get(id);
|
|
737
|
+
if (!host) throw new Error(`no Nanocodex definition host is active: ${id}`);
|
|
313
738
|
return host;
|
|
314
739
|
}
|
|
315
740
|
|
|
@@ -323,9 +748,164 @@ function releaseAgentState(state) {
|
|
|
323
748
|
}
|
|
324
749
|
runCleanup(errors, () => state.runtime.release?.(state.raw));
|
|
325
750
|
runCleanup(errors, () => state.runtime.dispose(state.raw));
|
|
751
|
+
runCleanup(errors, () => releaseAgentSession(state.reservation));
|
|
752
|
+
for (const observer of [...state.releaseObservers]) {
|
|
753
|
+
runCleanup(errors, () => observer({ graceful: state.shutdownPromise !== undefined }));
|
|
754
|
+
}
|
|
755
|
+
state.releaseObservers.clear();
|
|
326
756
|
throwCleanupErrors(errors);
|
|
327
757
|
}
|
|
328
758
|
|
|
759
|
+
/** Internal adapter seam: reserves a stable identity before starting its runtime. */
|
|
760
|
+
export function reserveAgentSession(sessionId) {
|
|
761
|
+
if (typeof sessionId !== "string" || !sessionId) {
|
|
762
|
+
throw new TypeError("sessionId must be a non-empty string");
|
|
763
|
+
}
|
|
764
|
+
if (activeAgentSessions.has(sessionId) || pendingCloudflareAgentSessions.has(sessionId)) {
|
|
765
|
+
throw new Error(`Nanocodex session ID is already active: ${sessionId}`);
|
|
766
|
+
}
|
|
767
|
+
const reservation = { sessionId, adopted: false, released: false };
|
|
768
|
+
activeAgentSessions.set(sessionId, reservation);
|
|
769
|
+
return reservation;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/** Internal Cloudflare seam: prepares, but does not activate, a reconstructed DO owner. */
|
|
773
|
+
export function prepareCloudflareAgentSession(sessionId, ownerId) {
|
|
774
|
+
if (typeof sessionId !== "string" || !sessionId) {
|
|
775
|
+
throw new TypeError("sessionId must be a non-empty string");
|
|
776
|
+
}
|
|
777
|
+
if (typeof ownerId !== "string" || !ownerId) {
|
|
778
|
+
throw new TypeError("Cloudflare Durable Object owner ID must be a non-empty string");
|
|
779
|
+
}
|
|
780
|
+
const existing = activeAgentSessions.get(sessionId);
|
|
781
|
+
if (pendingCloudflareAgentSessions.has(sessionId)
|
|
782
|
+
|| (existing !== undefined
|
|
783
|
+
&& (!cloudflareAgentSessions.has(existing)
|
|
784
|
+
|| existing.ownerId !== ownerId
|
|
785
|
+
|| !existing.committed))) {
|
|
786
|
+
throw new Error(`Nanocodex session ID is already active: ${sessionId}`);
|
|
787
|
+
}
|
|
788
|
+
const reservation = {
|
|
789
|
+
sessionId,
|
|
790
|
+
ownerId,
|
|
791
|
+
adopted: false,
|
|
792
|
+
committed: false,
|
|
793
|
+
predecessor: undefined,
|
|
794
|
+
predecessorHost: undefined,
|
|
795
|
+
host: undefined,
|
|
796
|
+
released: false,
|
|
797
|
+
};
|
|
798
|
+
cloudflareAgentSessions.add(reservation);
|
|
799
|
+
pendingCloudflareAgentSessions.set(sessionId, reservation);
|
|
800
|
+
return reservation;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/** @internal Whether this exact Cloudflare generation may publish child descriptors. */
|
|
804
|
+
export function mayBindCloudflareSubagentSession(reservation) {
|
|
805
|
+
return cloudflareAgentSessions.has(reservation)
|
|
806
|
+
&& !reservation.released
|
|
807
|
+
&& (pendingCloudflareAgentSessions.get(reservation.sessionId) === reservation
|
|
808
|
+
|| activeAgentSessions.get(reservation.sessionId) === reservation);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/** @internal Whether this committed current generation may delete child descriptors. */
|
|
812
|
+
export function mayReleaseCloudflareSubagentSession(reservation) {
|
|
813
|
+
return cloudflareAgentSessions.has(reservation)
|
|
814
|
+
&& !reservation.released
|
|
815
|
+
&& reservation.committed
|
|
816
|
+
&& activeAgentSessions.get(reservation.sessionId) === reservation;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function cloudflareHostMayBindSubagent(host) {
|
|
820
|
+
const reservation = cloudflareHostReservations.get(host);
|
|
821
|
+
return reservation === undefined || mayBindCloudflareSubagentSession(reservation);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/** Internal Cloudflare seam: activates a prepared owner after raw construction acquires its durable fence. */
|
|
825
|
+
export function activateCloudflareAgentSession(reservation) {
|
|
826
|
+
if (!cloudflareAgentSessions.has(reservation) || reservation.released) {
|
|
827
|
+
throw new Error("Cloudflare Agent session reservation is no longer active");
|
|
828
|
+
}
|
|
829
|
+
if (activeAgentSessions.get(reservation.sessionId) === reservation) return;
|
|
830
|
+
if (pendingCloudflareAgentSessions.get(reservation.sessionId) !== reservation) {
|
|
831
|
+
throw new Error("Cloudflare Agent session reservation is no longer pending");
|
|
832
|
+
}
|
|
833
|
+
const existing = activeAgentSessions.get(reservation.sessionId);
|
|
834
|
+
if (existing !== undefined
|
|
835
|
+
&& (!cloudflareAgentSessions.has(existing)
|
|
836
|
+
|| existing.ownerId !== reservation.ownerId
|
|
837
|
+
|| !existing.committed)) {
|
|
838
|
+
throw new Error(`Nanocodex session ID is already active: ${reservation.sessionId}`);
|
|
839
|
+
}
|
|
840
|
+
reservation.predecessor = existing;
|
|
841
|
+
reservation.predecessorHost = hostSessions.get(reservation.sessionId);
|
|
842
|
+
activeAgentSessions.set(reservation.sessionId, reservation);
|
|
843
|
+
pendingCloudflareAgentSessions.delete(reservation.sessionId);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/** Internal Cloudflare seam: publishes a reconstructed owner after adapter setup succeeds. */
|
|
847
|
+
export function commitCloudflareAgentSession(reservation) {
|
|
848
|
+
if (!cloudflareAgentSessions.has(reservation)
|
|
849
|
+
|| reservation.released
|
|
850
|
+
|| !reservation.adopted
|
|
851
|
+
|| activeAgentSessions.get(reservation.sessionId) !== reservation) {
|
|
852
|
+
throw new Error("Cloudflare Agent session reservation is not ready to commit");
|
|
853
|
+
}
|
|
854
|
+
reservation.committed = true;
|
|
855
|
+
reservation.predecessor = undefined;
|
|
856
|
+
reservation.predecessorHost = undefined;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function adoptAgentSession(reservation, sessionId) {
|
|
860
|
+
if (reservation.released || activeAgentSessions.get(reservation.sessionId) !== reservation) {
|
|
861
|
+
throw new Error(`Nanocodex session reservation is no longer active: ${sessionId}`);
|
|
862
|
+
}
|
|
863
|
+
if (reservation.sessionId !== sessionId) {
|
|
864
|
+
throw new Error(
|
|
865
|
+
`Nanocodex runtime changed reserved session ID ${reservation.sessionId} to ${sessionId}`,
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
if (reservation.adopted) {
|
|
869
|
+
throw new Error(`Nanocodex session reservation was already adopted: ${sessionId}`);
|
|
870
|
+
}
|
|
871
|
+
reservation.adopted = true;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/** Internal adapter seam: rolls back a runtime reservation that was not adopted. */
|
|
875
|
+
export function releaseAgentSession(reservation) {
|
|
876
|
+
if (!reservation || reservation.released) return;
|
|
877
|
+
reservation.released = true;
|
|
878
|
+
if (pendingCloudflareAgentSessions.get(reservation.sessionId) === reservation) {
|
|
879
|
+
pendingCloudflareAgentSessions.delete(reservation.sessionId);
|
|
880
|
+
}
|
|
881
|
+
if (activeAgentSessions.get(reservation.sessionId) === reservation) {
|
|
882
|
+
if (cloudflareAgentSessions.has(reservation)
|
|
883
|
+
&& !reservation.committed
|
|
884
|
+
&& reservation.predecessor !== undefined
|
|
885
|
+
&& !reservation.predecessor.released) {
|
|
886
|
+
activeAgentSessions.set(reservation.sessionId, reservation.predecessor);
|
|
887
|
+
if (reservation.predecessorHost !== undefined) {
|
|
888
|
+
hostSessions.set(reservation.sessionId, reservation.predecessorHost);
|
|
889
|
+
}
|
|
890
|
+
} else {
|
|
891
|
+
activeAgentSessions.delete(reservation.sessionId);
|
|
892
|
+
if (hostSessions.get(reservation.sessionId) === reservation.host) {
|
|
893
|
+
hostSessions.delete(reservation.sessionId);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function cloudflareSessionOwner(reservation, sessionId) {
|
|
900
|
+
if (!cloudflareAgentSessions.has(reservation)
|
|
901
|
+
|| reservation.released
|
|
902
|
+
|| reservation.sessionId !== sessionId
|
|
903
|
+
|| activeAgentSessions.get(sessionId) !== reservation) {
|
|
904
|
+
return undefined;
|
|
905
|
+
}
|
|
906
|
+
return reservation.ownerId;
|
|
907
|
+
}
|
|
908
|
+
|
|
329
909
|
async function joinAgentShutdown(state) {
|
|
330
910
|
await Promise.resolve();
|
|
331
911
|
let shutdownFailed = false;
|
|
@@ -359,11 +939,6 @@ async function joinAgentShutdown(state) {
|
|
|
359
939
|
if (cleanupFailed) throw cleanupError;
|
|
360
940
|
}
|
|
361
941
|
|
|
362
|
-
function requiredActiveHost() {
|
|
363
|
-
if (!activeHost) throw new Error("no Nanocodex host is active");
|
|
364
|
-
return activeHost;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
942
|
function connectFailure(error) {
|
|
368
943
|
if (typeof error === "string") {
|
|
369
944
|
try {
|
|
@@ -398,11 +973,13 @@ function createTurn(raw, agent) {
|
|
|
398
973
|
if (!raw || typeof raw.result !== "function") {
|
|
399
974
|
throw new TypeError("the runtime returned an invalid Nanocodex turn handle");
|
|
400
975
|
}
|
|
401
|
-
const state = { raw, agent, result: undefined, disposed: false };
|
|
976
|
+
const state = { raw, agent, acceptance: undefined, result: undefined, disposed: false };
|
|
402
977
|
const turn = {
|
|
403
978
|
get agent() { return state.agent; },
|
|
979
|
+
accepted: () => awaitTurnAcceptance(turn),
|
|
404
980
|
result: () => getTurnResult(turn),
|
|
405
981
|
steer: (input) => steer(turn, input),
|
|
982
|
+
withdrawSteer: (options) => withdrawSteer(turn, options),
|
|
406
983
|
cancel: () => cancel(turn),
|
|
407
984
|
dispose() {
|
|
408
985
|
if (state.disposed) return;
|
|
@@ -420,32 +997,66 @@ function createTurnResult(raw) {
|
|
|
420
997
|
|| typeof raw.finalMessage !== "string"
|
|
421
998
|
|| typeof raw.snapshot !== "function"
|
|
422
999
|
|| typeof raw.usage !== "function"
|
|
1000
|
+
|| typeof raw.free !== "function"
|
|
423
1001
|
) {
|
|
424
1002
|
raw?.free?.();
|
|
425
1003
|
throw new TypeError("the runtime returned an invalid Nanocodex turn result");
|
|
426
1004
|
}
|
|
427
1005
|
const state = {
|
|
1006
|
+
disposed: false,
|
|
428
1007
|
raw,
|
|
429
|
-
|
|
430
|
-
|
|
1008
|
+
snapshotPromise: undefined,
|
|
1009
|
+
usagePromise: undefined,
|
|
431
1010
|
snapshot() {
|
|
432
|
-
state.
|
|
433
|
-
|
|
1011
|
+
if (state.disposed) return Promise.reject(new Error("the Nanocodex turn result has been disposed"));
|
|
1012
|
+
state.snapshotPromise ||= materializeTurnResultValue(state, "snapshot");
|
|
1013
|
+
return state.snapshotPromise;
|
|
434
1014
|
},
|
|
435
1015
|
usage() {
|
|
436
|
-
state.
|
|
437
|
-
|
|
1016
|
+
if (state.disposed) return Promise.reject(new Error("the Nanocodex turn result has been disposed"));
|
|
1017
|
+
state.usagePromise ||= materializeTurnResultValue(state, "usage");
|
|
1018
|
+
return state.usagePromise;
|
|
438
1019
|
},
|
|
439
1020
|
};
|
|
440
1021
|
const result = {
|
|
441
1022
|
finalMessage: raw.finalMessage,
|
|
442
|
-
|
|
443
|
-
|
|
1023
|
+
snapshot: () => state.snapshot(),
|
|
1024
|
+
usage: () => state.usage(),
|
|
1025
|
+
dispose() {
|
|
1026
|
+
if (state.disposed) return;
|
|
1027
|
+
state.disposed = true;
|
|
1028
|
+
resultFinalizer?.unregister(result);
|
|
1029
|
+
state.raw.free();
|
|
1030
|
+
},
|
|
444
1031
|
};
|
|
445
1032
|
resultStates.set(result, state);
|
|
1033
|
+
resultFinalizer?.register(result, raw, result);
|
|
446
1034
|
return Object.freeze(result);
|
|
447
1035
|
}
|
|
448
1036
|
|
|
1037
|
+
function materializeTurnResultValue(state, method) {
|
|
1038
|
+
let encoded;
|
|
1039
|
+
try {
|
|
1040
|
+
encoded = state.raw[method]();
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
return Promise.reject(error);
|
|
1043
|
+
}
|
|
1044
|
+
return Promise.resolve(encoded).then((value) => {
|
|
1045
|
+
if (typeof value !== "string") {
|
|
1046
|
+
throw new TypeError(`the runtime returned an invalid encoded turn ${method}`);
|
|
1047
|
+
}
|
|
1048
|
+
return freezeJson(JSON.parse(value));
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
async function encodedTurnResultValue(state, method) {
|
|
1053
|
+
const encoded = await state.raw[method]();
|
|
1054
|
+
if (typeof encoded !== "string") {
|
|
1055
|
+
throw new TypeError(`the runtime returned an invalid encoded turn ${method}`);
|
|
1056
|
+
}
|
|
1057
|
+
return encoded;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
449
1060
|
function agentState(agent) {
|
|
450
1061
|
const state = knownAgentState(agent);
|
|
451
1062
|
if (state.disposed) throw new Error("the Nanocodex agent has been disposed");
|
|
@@ -468,6 +1079,7 @@ function turnState(turn) {
|
|
|
468
1079
|
function resultState(result) {
|
|
469
1080
|
const state = resultStates.get(result);
|
|
470
1081
|
if (!state) throw new TypeError("expected a completed Nanocodex turn result");
|
|
1082
|
+
if (state.disposed) throw new Error("the Nanocodex turn result has been disposed");
|
|
471
1083
|
return state;
|
|
472
1084
|
}
|
|
473
1085
|
|
|
@@ -512,7 +1124,7 @@ function throwCleanupErrors(errors) {
|
|
|
512
1124
|
}
|
|
513
1125
|
}
|
|
514
1126
|
|
|
515
|
-
function freezeJson(value) {
|
|
1127
|
+
export function freezeJson(value) {
|
|
516
1128
|
if (!value || typeof value !== "object") return value;
|
|
517
1129
|
const pending = [value];
|
|
518
1130
|
while (pending.length) {
|