@lamplitisles/dsh-nanocodex 0.1.0-beta.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/LICENSE +65 -0
- package/NOTICE +2 -0
- package/README.md +182 -0
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/cordis.patch.yml +52 -0
- package/dist/index.d.ts +435 -0
- package/dist/index.js +3405 -0
- package/dist/standard-preset.d.ts +7 -0
- package/dist/standard-preset.js +25 -0
- package/engine-release.json +14 -0
- package/package.json +119 -0
- package/presets/standard/agent.cordis.yml +6 -0
- package/presets/standard/preset.yml +2 -0
- package/vendor/nanocodex/README.md +1344 -0
- package/vendor/nanocodex/actions/events.d.mts +10 -0
- package/vendor/nanocodex/actions/events.mjs +163 -0
- package/vendor/nanocodex/actions/index.d.mts +13 -0
- package/vendor/nanocodex/actions/index.mjs +35 -0
- package/vendor/nanocodex/actions/session.d.mts +66 -0
- package/vendor/nanocodex/actions/session.mjs +59 -0
- package/vendor/nanocodex/actions/turn.d.mts +59 -0
- package/vendor/nanocodex/actions/turn.mjs +37 -0
- package/vendor/nanocodex/actions/voice.d.mts +22 -0
- package/vendor/nanocodex/actions/voice.mjs +50 -0
- package/vendor/nanocodex/browser/Agent.d.mts +55 -0
- package/vendor/nanocodex/browser/Agent.mjs +11 -0
- package/vendor/nanocodex/browser/ChatGptSubscription.d.mts +7 -0
- package/vendor/nanocodex/browser/ChatGptSubscription.mjs +19 -0
- package/vendor/nanocodex/browser/InlineAgent.mjs +266 -0
- package/vendor/nanocodex/browser/Transport.d.mts +74 -0
- package/vendor/nanocodex/browser/Transport.mjs +61 -0
- package/vendor/nanocodex/browser/Voice.d.mts +71 -0
- package/vendor/nanocodex/browser/Voice.mjs +291 -0
- package/vendor/nanocodex/browser/VoiceSession.mjs +688 -0
- package/vendor/nanocodex/browser/WorkerAgent.d.mts +65 -0
- package/vendor/nanocodex/browser/WorkerAgent.mjs +1472 -0
- package/vendor/nanocodex/browser/agent.worker.mjs +3 -0
- package/vendor/nanocodex/browser/config.d.mts +36 -0
- package/vendor/nanocodex/browser/config.mjs +439 -0
- package/vendor/nanocodex/browser/engine.mjs +13 -0
- package/vendor/nanocodex/browser/harness.mjs +55 -0
- package/vendor/nanocodex/browser/host.d.mts +83 -0
- package/vendor/nanocodex/browser/host.mjs +555 -0
- package/vendor/nanocodex/browser/hostManagedWebSocket.d.mts +14 -0
- package/vendor/nanocodex/browser/hostManagedWebSocket.mjs +110 -0
- package/vendor/nanocodex/browser/index.d.mts +92 -0
- package/vendor/nanocodex/browser/index.mjs +23 -0
- package/vendor/nanocodex/browser/indexeddb-durability-store.mjs +197 -0
- package/vendor/nanocodex/browser/workspace.d.mts +16 -0
- package/vendor/nanocodex/browser/workspace.mjs +107 -0
- package/vendor/nanocodex/cloud/Client.d.mts +83 -0
- package/vendor/nanocodex/cloud/Client.mjs +231 -0
- package/vendor/nanocodex/cloud/Decorator.d.mts +31 -0
- package/vendor/nanocodex/cloud/Decorator.mjs +37 -0
- package/vendor/nanocodex/cloud/Dialog.d.mts +113 -0
- package/vendor/nanocodex/cloud/Dialog.mjs +366 -0
- package/vendor/nanocodex/cloud/Errors.d.mts +22 -0
- package/vendor/nanocodex/cloud/Errors.mjs +39 -0
- package/vendor/nanocodex/cloud/Principal.d.mts +26 -0
- package/vendor/nanocodex/cloud/Principal.mjs +88 -0
- package/vendor/nanocodex/cloud/RemoteProvider.mjs +88 -0
- package/vendor/nanocodex/cloud/Transport.d.mts +47 -0
- package/vendor/nanocodex/cloud/Transport.mjs +454 -0
- package/vendor/nanocodex/cloud/actions/account.d.mts +9 -0
- package/vendor/nanocodex/cloud/actions/account.mjs +19 -0
- package/vendor/nanocodex/cloud/actions/agent.d.mts +18 -0
- package/vendor/nanocodex/cloud/actions/agent.mjs +428 -0
- package/vendor/nanocodex/cloud/actions/connection.d.mts +133 -0
- package/vendor/nanocodex/cloud/actions/connection.mjs +580 -0
- package/vendor/nanocodex/cloud/actions/grant.d.mts +10 -0
- package/vendor/nanocodex/cloud/actions/grant.mjs +12 -0
- package/vendor/nanocodex/cloud/actions/index.d.mts +7 -0
- package/vendor/nanocodex/cloud/actions/index.mjs +7 -0
- package/vendor/nanocodex/cloud/actions/machineUsd.d.mts +23 -0
- package/vendor/nanocodex/cloud/actions/machineUsd.mjs +37 -0
- package/vendor/nanocodex/cloud/actions/model.d.mts +12 -0
- package/vendor/nanocodex/cloud/actions/model.mjs +50 -0
- package/vendor/nanocodex/cloud/actions/mpp.d.mts +28 -0
- package/vendor/nanocodex/cloud/actions/mpp.mjs +39 -0
- package/vendor/nanocodex/cloud/index.d.mts +30 -0
- package/vendor/nanocodex/cloud/index.mjs +9 -0
- package/vendor/nanocodex/cloud/internal.mjs +533 -0
- package/vendor/nanocodex/cloud/mercator.mjs +108 -0
- package/vendor/nanocodex/cloud/server/HostPrincipal.d.mts +34 -0
- package/vendor/nanocodex/cloud/server/HostPrincipal.mjs +202 -0
- package/vendor/nanocodex/cloud/server/index.d.mts +1 -0
- package/vendor/nanocodex/cloud/server/index.mjs +1 -0
- package/vendor/nanocodex/cloud/types.d.mts +211 -0
- package/vendor/nanocodex/cloudflare/Agent.d.mts +133 -0
- package/vendor/nanocodex/cloudflare/Agent.mjs +811 -0
- package/vendor/nanocodex/cloudflare/egress-subject.mjs +19 -0
- package/vendor/nanocodex/cloudflare/egress.d.mts +41 -0
- package/vendor/nanocodex/cloudflare/egress.mjs +119 -0
- package/vendor/nanocodex/cloudflare/event-socket.mjs +316 -0
- package/vendor/nanocodex/cloudflare/index.d.mts +7 -0
- package/vendor/nanocodex/cloudflare/index.mjs +6 -0
- package/vendor/nanocodex/host/Agent.d.mts +55 -0
- package/vendor/nanocodex/host/Agent.mjs +1 -0
- package/vendor/nanocodex/host/index.d.mts +82 -0
- package/vendor/nanocodex/host/index.mjs +10 -0
- package/vendor/nanocodex/index.d.mts +81 -0
- package/vendor/nanocodex/index.mjs +12 -0
- package/vendor/nanocodex/internal.mjs +1275 -0
- package/vendor/nanocodex/managed/Agent.d.mts +380 -0
- package/vendor/nanocodex/managed/Agent.mjs +1694 -0
- package/vendor/nanocodex/managed/ManagedError.d.mts +9 -0
- package/vendor/nanocodex/managed/ManagedError.mjs +8 -0
- package/vendor/nanocodex/managed/README.md +241 -0
- package/vendor/nanocodex/managed/Voice.mjs +202 -0
- package/vendor/nanocodex/managed/index.d.mts +20 -0
- package/vendor/nanocodex/managed/index.mjs +2 -0
- package/vendor/nanocodex/managed/internal.mjs +119 -0
- package/vendor/nanocodex/node/Agent.d.mts +50 -0
- package/vendor/nanocodex/node/Agent.mjs +203 -0
- package/vendor/nanocodex/node/ChatGptSubscription.d.mts +9 -0
- package/vendor/nanocodex/node/ChatGptSubscription.mjs +17 -0
- package/vendor/nanocodex/node/Transport.d.mts +44 -0
- package/vendor/nanocodex/node/Transport.mjs +46 -0
- package/vendor/nanocodex/node/host.mjs +731 -0
- package/vendor/nanocodex/node/index.d.mts +73 -0
- package/vendor/nanocodex/node/index.mjs +17 -0
- package/vendor/nanocodex/node/workspace.d.mts +10 -0
- package/vendor/nanocodex/node/workspace.mjs +198 -0
- package/vendor/nanocodex/package.json +200 -0
- package/vendor/nanocodex/pkg-node/nanocodex.d.ts +675 -0
- package/vendor/nanocodex/pkg-node/nanocodex.js +2628 -0
- package/vendor/nanocodex/pkg-node/nanocodex_bg.wasm.d.ts +105 -0
- package/vendor/nanocodex/pkg-node/package.json +1 -0
- package/vendor/nanocodex/pkg-web/.nanocodex-bindgen-stamp +6 -0
- package/vendor/nanocodex/pkg-web/nanocodex-build.json +1 -0
- package/vendor/nanocodex/pkg-web/nanocodex.d.ts +805 -0
- package/vendor/nanocodex/pkg-web/nanocodex.js +2714 -0
- package/vendor/nanocodex/pkg-web/nanocodex_bg.js +2615 -0
- package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm +0 -0
- package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm.d.ts +105 -0
- package/vendor/nanocodex/pkg-web/nanocodex_worker.js +9 -0
- package/vendor/nanocodex/pkg-web/package.json +1 -0
- package/vendor/nanocodex/runtime/chatgpt-subscription.mjs +212 -0
- package/vendor/nanocodex/runtime/cloudflare-durability-store.d.mts +26 -0
- package/vendor/nanocodex/runtime/cloudflare-durability-store.mjs +168 -0
- package/vendor/nanocodex/runtime/code-evaluator.worker.mjs +137 -0
- package/vendor/nanocodex/runtime/code-runtime.mjs +1 -0
- package/vendor/nanocodex/runtime/durability-store.d.mts +85 -0
- package/vendor/nanocodex/runtime/durability-store.mjs +631 -0
- package/vendor/nanocodex/runtime/durability.mjs +153 -0
- package/vendor/nanocodex/runtime/managed-transport.mjs +460 -0
- package/vendor/nanocodex/runtime/mcp-runtime.mjs +534 -0
- package/vendor/nanocodex/runtime/postgres-durability-store.d.mts +64 -0
- package/vendor/nanocodex/runtime/postgres-durability-store.mjs +532 -0
- package/vendor/nanocodex/runtime/quickjs-evaluator.d.mts +17 -0
- package/vendor/nanocodex/runtime/quickjs-evaluator.mjs +236 -0
- package/vendor/nanocodex/runtime/responses-transport.mjs +22 -0
- package/vendor/nanocodex/runtime/subagents.d.mts +107 -0
- package/vendor/nanocodex/runtime/subagents.mjs +54 -0
- package/vendor/nanocodex/runtime/subscription-store.d.mts +8 -0
- package/vendor/nanocodex/runtime/subscription-store.mjs +42 -0
- package/vendor/nanocodex/runtime/tempo-provider.d.mts +125 -0
- package/vendor/nanocodex/runtime/tempo-provider.mjs +264 -0
- package/vendor/nanocodex/runtime/tool-configuration.mjs +1 -0
- package/vendor/nanocodex/runtime/tool-router.mjs +1 -0
- package/vendor/nanocodex/runtime/utf8.mjs +1 -0
- package/vendor/nanocodex/runtime/worker-evaluator.mjs +162 -0
- package/vendor/nanocodex/runtime/workspace.d.mts +1 -0
- package/vendor/nanocodex/runtime/workspace.mjs +1 -0
- package/vendor/nanocodex/tools/Tools.d.mts +56 -0
- package/vendor/nanocodex/tools/Tools.mjs +136 -0
- package/vendor/nanocodex/tools/artifact.d.mts +1 -0
- package/vendor/nanocodex/tools/artifact.mjs +1 -0
- package/vendor/nanocodex/tools/attachment.mjs +1 -0
- package/vendor/nanocodex/tools/bash.d.mts +1 -0
- package/vendor/nanocodex/tools/bash.mjs +1 -0
- package/vendor/nanocodex/tools/browser/accountInfo.mjs +795 -0
- package/vendor/nanocodex/tools/browser/browserBuffer.mjs +18 -0
- package/vendor/nanocodex/tools/browser/browserCompiler.mjs +155 -0
- package/vendor/nanocodex/tools/browser/browserEgress.mjs +161 -0
- package/vendor/nanocodex/tools/browser/browserPython.mjs +133 -0
- package/vendor/nanocodex/tools/browser/browserShell.mjs +1075 -0
- package/vendor/nanocodex/tools/browser/browserSprintf.mjs +4 -0
- package/vendor/nanocodex/tools/browser/browserSsh.mjs +201 -0
- package/vendor/nanocodex/tools/browser/browserZlib.mjs +136 -0
- package/vendor/nanocodex/tools/browser/compiler.worker.mjs +160 -0
- package/vendor/nanocodex/tools/browser/devTunnelsSshBrowser.mjs +13 -0
- package/vendor/nanocodex/tools/browser/index.d.mts +209 -0
- package/vendor/nanocodex/tools/browser/index.mjs +174 -0
- package/vendor/nanocodex/tools/browser/opfsGit.mjs +248 -0
- package/vendor/nanocodex/tools/browser/python.worker.mjs +114 -0
- package/vendor/nanocodex/tools/browser/threadGit.mjs +197 -0
- package/vendor/nanocodex/tools/browser/unsupportedNodeRsa.mjs +5 -0
- package/vendor/nanocodex/tools/browser/workspace.mjs +83 -0
- package/vendor/nanocodex/tools/dataset.d.mts +1 -0
- package/vendor/nanocodex/tools/dataset.mjs +1 -0
- package/vendor/nanocodex/tools/datasetContract.mjs +1 -0
- package/vendor/nanocodex/tools/datasetEngine.mjs +1 -0
- package/vendor/nanocodex/tools/hostedCatalog.d.mts +1 -0
- package/vendor/nanocodex/tools/hostedCatalog.mjs +1 -0
- package/vendor/nanocodex/tools/index.d.mts +49 -0
- package/vendor/nanocodex/tools/index.mjs +19 -0
- package/vendor/nanocodex/tools/namedTool.mjs +1 -0
- package/vendor/nanocodex/tools/repository-workspace.d.mts +1 -0
- package/vendor/nanocodex/tools/repository-workspace.mjs +1 -0
- package/vendor/nanocodex/tools/ssh.d.mts +1 -0
- package/vendor/nanocodex/tools/ssh.mjs +1 -0
- package/vendor/nanocodex/tools/standard.mjs +1 -0
- package/vendor/nanocodex/tools/standardDescriptions.mjs +1 -0
- package/vendor/nanocodex/types.d.mts +663 -0
- package/vendor/nanocodex/wasm.d.mts +4 -0
- package/vendor/nanocodex/worker/ChatGptSubscription.d.mts +9 -0
- package/vendor/nanocodex/worker/ChatGptSubscription.mjs +29 -0
- package/vendor/nanocodex/worker/index.d.mts +18 -0
- package/vendor/nanocodex/worker/index.mjs +5 -0
- package/vendor/nanocodex-tools/README.md +65 -0
- package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.d.ts +7 -0
- package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.js +14 -0
- package/vendor/nanocodex-tools/dist/hosted/broker-core.d.ts +172 -0
- package/vendor/nanocodex-tools/dist/hosted/broker-core.js +1278 -0
- package/vendor/nanocodex-tools/dist/hosted/index.d.ts +3 -0
- package/vendor/nanocodex-tools/dist/hosted/index.js +3 -0
- package/vendor/nanocodex-tools/dist/hosted/protocol.d.ts +120 -0
- package/vendor/nanocodex-tools/dist/hosted/protocol.js +415 -0
- package/vendor/nanocodex-tools/dist/index.d.ts +19 -0
- package/vendor/nanocodex-tools/dist/index.js +14 -0
- package/vendor/nanocodex-tools/dist/memory.d.ts +204 -0
- package/vendor/nanocodex-tools/dist/memory.js +411 -0
- package/vendor/nanocodex-tools/dist/namespace.d.ts +87 -0
- package/vendor/nanocodex-tools/dist/namespace.js +338 -0
- package/vendor/nanocodex-tools/dist/repository-archive.d.ts +4 -0
- package/vendor/nanocodex-tools/dist/repository-archive.js +104 -0
- package/vendor/nanocodex-tools/dist/runtime.d.ts +30 -0
- package/vendor/nanocodex-tools/dist/runtime.js +37 -0
- package/vendor/nanocodex-tools/dist/session.d.ts +167 -0
- package/vendor/nanocodex-tools/dist/session.js +286 -0
- package/vendor/nanocodex-tools/dist/shell.d.ts +51 -0
- package/vendor/nanocodex-tools/dist/shell.js +553 -0
- package/vendor/nanocodex-tools/dist/ssh.d.ts +29 -0
- package/vendor/nanocodex-tools/dist/ssh.js +66 -0
- package/vendor/nanocodex-tools/dist/workspace.d.ts +45 -0
- package/vendor/nanocodex-tools/dist/workspace.js +237 -0
- package/vendor/nanocodex-tools/package.json +122 -0
- package/vendor/nanocodex-tools/runtime/code-runtime.mjs +817 -0
- package/vendor/nanocodex-tools/runtime/tool-configuration.mjs +50 -0
- package/vendor/nanocodex-tools/runtime/tool-router.mjs +730 -0
- package/vendor/nanocodex-tools/runtime/utf8.d.mts +2 -0
- package/vendor/nanocodex-tools/runtime/utf8.mjs +25 -0
- package/vendor/nanocodex-tools/tools/artifact.d.mts +69 -0
- package/vendor/nanocodex-tools/tools/artifact.mjs +270 -0
- package/vendor/nanocodex-tools/tools/attachment.mjs +603 -0
- package/vendor/nanocodex-tools/tools/bash.d.mts +90 -0
- package/vendor/nanocodex-tools/tools/bash.mjs +742 -0
- package/vendor/nanocodex-tools/tools/dataset.d.mts +9 -0
- package/vendor/nanocodex-tools/tools/dataset.mjs +47 -0
- package/vendor/nanocodex-tools/tools/datasetContract.mjs +54 -0
- package/vendor/nanocodex-tools/tools/datasetEngine.mjs +1167 -0
- package/vendor/nanocodex-tools/tools/execution-contract.d.mts +5 -0
- package/vendor/nanocodex-tools/tools/execution-contract.mjs +63 -0
- package/vendor/nanocodex-tools/tools/hostedCatalog.d.mts +20 -0
- package/vendor/nanocodex-tools/tools/hostedCatalog.mjs +67 -0
- package/vendor/nanocodex-tools/tools/hostedMachine.d.mts +10 -0
- package/vendor/nanocodex-tools/tools/hostedMachine.mjs +56 -0
- package/vendor/nanocodex-tools/tools/just-bash-browser.d.mts +1 -0
- package/vendor/nanocodex-tools/tools/just-bash-browser.mjs +1 -0
- package/vendor/nanocodex-tools/tools/namedTool.d.mts +3 -0
- package/vendor/nanocodex-tools/tools/namedTool.mjs +3 -0
- package/vendor/nanocodex-tools/tools/nodeProcess.d.mts +10 -0
- package/vendor/nanocodex-tools/tools/nodeProcess.mjs +314 -0
- package/vendor/nanocodex-tools/tools/processOutput.mjs +56 -0
- package/vendor/nanocodex-tools/tools/repository-workspace.d.mts +55 -0
- package/vendor/nanocodex-tools/tools/repository-workspace.mjs +397 -0
- package/vendor/nanocodex-tools/tools/ssh.d.mts +56 -0
- package/vendor/nanocodex-tools/tools/ssh.mjs +503 -0
- package/vendor/nanocodex-tools/tools/standard.mjs +458 -0
- package/vendor/nanocodex-tools/tools/standardDescriptions.mjs +3 -0
- package/vendor/nanocodex-tools/tools/types.d.mts +83 -0
- package/vendor/nanocodex-tools/tools/workspace.d.mts +25 -0
- package/vendor/nanocodex-tools/tools/workspace.mjs +282 -0
- package/vendor/nanocodex-tools/tools/x.d.mts +30 -0
- package/vendor/nanocodex-tools/tools/x.mjs +91 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { createCodeRuntime, toolResult } from "../runtime/code-runtime.mjs";
|
|
2
|
+
import {
|
|
3
|
+
toolRouterBrand,
|
|
4
|
+
toolRouterRuntime,
|
|
5
|
+
toolRuntimeLifecycle,
|
|
6
|
+
} from "../runtime/tool-router.mjs";
|
|
7
|
+
import { utf8ByteLength } from "../runtime/utf8.mjs";
|
|
8
|
+
import { createWorkerEvaluator } from "../runtime/worker-evaluator.mjs";
|
|
9
|
+
import { openHostManagedWebSocket } from "./hostManagedWebSocket.mjs";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_MAX_QUEUED_MESSAGES = 4_096;
|
|
12
|
+
const DEFAULT_MAX_QUEUED_BYTES = 32 * 1024 * 1024;
|
|
13
|
+
const DEFAULT_MAX_BUFFERED_SEND_BYTES = 16 * 1024 * 1024;
|
|
14
|
+
const MPP_CLIENT_PROTOCOL_ERROR_CLOSE_CODE = 3008;
|
|
15
|
+
const WEBSOCKET_OPEN = 1;
|
|
16
|
+
|
|
17
|
+
export function createBrowserHost(options = {}) {
|
|
18
|
+
if (options.resolveCompactionInstruction !== undefined
|
|
19
|
+
&& typeof options.resolveCompactionInstruction !== "function") {
|
|
20
|
+
throw new TypeError("resolveCompactionInstruction must be a function");
|
|
21
|
+
}
|
|
22
|
+
const toolMode = options.toolMode ?? "code";
|
|
23
|
+
if (toolMode !== "code" && toolMode !== "direct") {
|
|
24
|
+
throw new TypeError("toolMode must be code or direct");
|
|
25
|
+
}
|
|
26
|
+
const toolsRouter = options.tools?.[toolRouterBrand]
|
|
27
|
+
? options.tools[toolRouterRuntime]
|
|
28
|
+
: undefined;
|
|
29
|
+
const toolsMcp = toolsRouter?.hasSourceKind("mcp") === true;
|
|
30
|
+
if (toolsRouter?.hasSource("workspace") && options.filesystem) {
|
|
31
|
+
throw new TypeError("workspace is already configured in Tools");
|
|
32
|
+
}
|
|
33
|
+
if (toolsMcp && options.mcp) {
|
|
34
|
+
throw new TypeError("MCP is already configured in Tools");
|
|
35
|
+
}
|
|
36
|
+
if ((toolsMcp || options.mcp) && toolMode !== "code") {
|
|
37
|
+
throw new TypeError("remote MCP requires Code Mode");
|
|
38
|
+
}
|
|
39
|
+
const toolsLifecycle = options.tools?.[toolRuntimeLifecycle];
|
|
40
|
+
toolsLifecycle?.available();
|
|
41
|
+
const WebSocketImpl = options.WebSocketImpl ?? globalThis.WebSocket;
|
|
42
|
+
const createWebSocket = options.createWebSocket
|
|
43
|
+
?? (options.hostManagedProtocol && ((endpoint, sessionId, request) =>
|
|
44
|
+
openHostManagedWebSocket(endpoint, sessionId, {
|
|
45
|
+
WebSocketImpl,
|
|
46
|
+
threadId: request.threadId,
|
|
47
|
+
})))
|
|
48
|
+
?? (WebSocketImpl && ((endpoint) => new WebSocketImpl(endpoint)));
|
|
49
|
+
if (!options.mpp && !createWebSocket) {
|
|
50
|
+
throw new Error("WebSocket is unavailable in this runtime");
|
|
51
|
+
}
|
|
52
|
+
const connections = new Map();
|
|
53
|
+
const openingAttempts = new Set();
|
|
54
|
+
const connectingConnections = new Set();
|
|
55
|
+
const codeEvaluator = options.codeEvaluator
|
|
56
|
+
?? (typeof globalThis.Worker === "function"
|
|
57
|
+
? createWorkerEvaluator()
|
|
58
|
+
: () => Promise.reject(new Error(
|
|
59
|
+
"browser Code Mode requires a child Worker or an explicit codeEvaluator",
|
|
60
|
+
)));
|
|
61
|
+
const code = createCodeRuntime(options.tools, {
|
|
62
|
+
evaluate: codeEvaluator,
|
|
63
|
+
subagentSessions: options.subagentSessions,
|
|
64
|
+
});
|
|
65
|
+
const toolProviders = options.toolProviders ?? [];
|
|
66
|
+
if (!Array.isArray(toolProviders)) {
|
|
67
|
+
throw new TypeError("toolProviders must be an array");
|
|
68
|
+
}
|
|
69
|
+
for (const [index, provider] of toolProviders.entries()) {
|
|
70
|
+
const sourceOptions = {
|
|
71
|
+
id: provider.sourceId ?? `attached:${String(index).padStart(8, "0")}`,
|
|
72
|
+
kind: "attached",
|
|
73
|
+
mode: "attached-over-cloud",
|
|
74
|
+
deferred: true,
|
|
75
|
+
};
|
|
76
|
+
const sourceId = code.addProvider(provider, sourceOptions);
|
|
77
|
+
provider.setCatalogValidator?.((definitions) =>
|
|
78
|
+
code.validateProviderDefinitions(sourceId, definitions, sourceOptions));
|
|
79
|
+
}
|
|
80
|
+
const toolProvidersReady = Promise.all(
|
|
81
|
+
toolProviders.map((provider) => provider.settled?.()),
|
|
82
|
+
);
|
|
83
|
+
if (options.filesystem && options.filesystemTools === false) {
|
|
84
|
+
code.addTools({
|
|
85
|
+
apply_patch: {
|
|
86
|
+
description: "Apply a Rust-verified patch to the browser workspace.",
|
|
87
|
+
parameters: { type: "object", additionalProperties: false },
|
|
88
|
+
async handler(input, context) {
|
|
89
|
+
if (typeof options.applyPatch !== "function") {
|
|
90
|
+
throw new Error("the Rust browser apply_patch planner is unavailable");
|
|
91
|
+
}
|
|
92
|
+
if (typeof input !== "string") {
|
|
93
|
+
throw new TypeError("browser apply_patch requires a raw string input");
|
|
94
|
+
}
|
|
95
|
+
const summary = await options.applyPatch(input, context.sessionId);
|
|
96
|
+
return toolResult(summary, {});
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const filesystemReady = options.filesystem && options.filesystemTools !== false
|
|
102
|
+
? import("../runtime/workspace.mjs")
|
|
103
|
+
.then(({ tools }) => code.addTools(tools(options.filesystem)))
|
|
104
|
+
: undefined;
|
|
105
|
+
const mcp = options.mcp
|
|
106
|
+
? import("../runtime/mcp-runtime.mjs").then(({ createMcpRuntime }) =>
|
|
107
|
+
createMcpRuntime(options.mcp, { clientName: "nanocodex-browser" }))
|
|
108
|
+
: undefined;
|
|
109
|
+
const mcpInstalled = mcp?.then(async (provider) => {
|
|
110
|
+
if (disposal) return provider.close();
|
|
111
|
+
try { code.addProvider(provider, { id: "mcp", kind: "mcp" }); }
|
|
112
|
+
catch (error) {
|
|
113
|
+
try { await provider.close(); }
|
|
114
|
+
catch (cleanupError) {
|
|
115
|
+
throw new AggregateError([error, cleanupError], "MCP installation and cleanup failed");
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
return provider;
|
|
120
|
+
});
|
|
121
|
+
const onEvent = options.onEvent || (() => {});
|
|
122
|
+
const maxQueuedMessages = options.maxQueuedMessages ?? DEFAULT_MAX_QUEUED_MESSAGES;
|
|
123
|
+
const maxQueuedBytes = options.maxQueuedBytes ?? DEFAULT_MAX_QUEUED_BYTES;
|
|
124
|
+
const maxBufferedSendBytes = options.maxBufferedSendBytes ?? DEFAULT_MAX_BUFFERED_SEND_BYTES;
|
|
125
|
+
let nextHandle = 1;
|
|
126
|
+
let references = 0;
|
|
127
|
+
let disposal;
|
|
128
|
+
let disposalError;
|
|
129
|
+
let preconnected;
|
|
130
|
+
|
|
131
|
+
function preconnect(endpoint, sessionId) {
|
|
132
|
+
if (disposal) return Promise.reject(new Error("Nanocodex host is already disposed"));
|
|
133
|
+
if (preconnected?.endpoint === endpoint && preconnected.sessionId === sessionId) {
|
|
134
|
+
return preconnected.ownership.promise.then(() => undefined);
|
|
135
|
+
}
|
|
136
|
+
void closePreconnected().catch(() => {});
|
|
137
|
+
const ownership = openOwned(() => createWebSocket(endpoint, sessionId, {
|
|
138
|
+
authorization: "preconnect",
|
|
139
|
+
}));
|
|
140
|
+
const entry = { endpoint, sessionId, ownership };
|
|
141
|
+
preconnected = entry;
|
|
142
|
+
void ownership.promise.catch(() => {
|
|
143
|
+
if (preconnected === entry) preconnected = undefined;
|
|
144
|
+
});
|
|
145
|
+
return ownership.promise.then(() => undefined);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function takePreconnected(endpoint, sessionId) {
|
|
149
|
+
if (preconnected?.endpoint !== endpoint || preconnected.sessionId !== sessionId) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const ownership = preconnected.ownership;
|
|
153
|
+
preconnected = undefined;
|
|
154
|
+
return ownership;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function closePreconnected(error = new Error("WebSocket preconnection was closed")) {
|
|
158
|
+
const entry = preconnected;
|
|
159
|
+
preconnected = undefined;
|
|
160
|
+
if (!entry) return Promise.resolve();
|
|
161
|
+
return entry.ownership.dispose(error);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function connect(endpoint, apiKey, sessionId, metadata = {}) {
|
|
165
|
+
if (disposal) throw new Error("Nanocodex host is already disposed");
|
|
166
|
+
if (options.mpp) return connectMpp(endpoint);
|
|
167
|
+
const authorization = options.hostAuth
|
|
168
|
+
? { authorization: "host_managed" }
|
|
169
|
+
: { authorization: "bearer", bearerToken: apiKey };
|
|
170
|
+
const request = { ...metadata };
|
|
171
|
+
delete request.authorization;
|
|
172
|
+
delete request.bearerToken;
|
|
173
|
+
Object.assign(request, authorization);
|
|
174
|
+
const threadId = metadata.threadId ?? sessionId;
|
|
175
|
+
let ownership = threadId === sessionId ? takePreconnected(endpoint, sessionId) : undefined;
|
|
176
|
+
if (ownership === undefined) {
|
|
177
|
+
if (preconnected !== undefined) await closePreconnected();
|
|
178
|
+
ownership = openOwned(() => createWebSocket(endpoint, sessionId, request));
|
|
179
|
+
}
|
|
180
|
+
const opened = await ownership.promise;
|
|
181
|
+
const { socket, ...handshake } = normalizeWebSocketConnection(opened);
|
|
182
|
+
return new Promise((resolve, reject) => {
|
|
183
|
+
let settled = false;
|
|
184
|
+
const connection = {
|
|
185
|
+
socket,
|
|
186
|
+
queue: [],
|
|
187
|
+
queuedBytes: 0,
|
|
188
|
+
waiter: undefined,
|
|
189
|
+
intentionallyClosed: false,
|
|
190
|
+
overflowed: false,
|
|
191
|
+
};
|
|
192
|
+
const rejectConnection = (error) => {
|
|
193
|
+
if (settled) return;
|
|
194
|
+
settled = true;
|
|
195
|
+
connectingConnections.delete(connection);
|
|
196
|
+
reject(error);
|
|
197
|
+
};
|
|
198
|
+
connection.reject = rejectConnection;
|
|
199
|
+
ownership.transfer();
|
|
200
|
+
connectingConnections.add(connection);
|
|
201
|
+
const resolveOpen = () => {
|
|
202
|
+
if (settled) return;
|
|
203
|
+
if (disposal) {
|
|
204
|
+
connection.intentionallyClosed = true;
|
|
205
|
+
settled = true;
|
|
206
|
+
reject(disposalError);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
settled = true;
|
|
210
|
+
connectingConnections.delete(connection);
|
|
211
|
+
delete connection.reject;
|
|
212
|
+
const handle = nextHandle++;
|
|
213
|
+
connections.set(handle, connection);
|
|
214
|
+
resolve(JSON.stringify({
|
|
215
|
+
handle,
|
|
216
|
+
status: handshake.status ?? 101,
|
|
217
|
+
request_id: handshake.requestId,
|
|
218
|
+
server_model: handshake.serverModel,
|
|
219
|
+
reasoning_included: handshake.reasoningIncluded ?? false,
|
|
220
|
+
turn_state: handshake.turnState,
|
|
221
|
+
}));
|
|
222
|
+
};
|
|
223
|
+
socket.addEventListener("open", resolveOpen, { once: true });
|
|
224
|
+
socket.addEventListener("message", (event) => {
|
|
225
|
+
enqueue(connection, typeof event.data === "string"
|
|
226
|
+
? { kind: "text", text: event.data }
|
|
227
|
+
: { kind: "binary" });
|
|
228
|
+
});
|
|
229
|
+
socket.addEventListener("close", (event) => {
|
|
230
|
+
if (!settled) {
|
|
231
|
+
rejectConnection(new Error(`WebSocket closed during connection with code ${event.code}`));
|
|
232
|
+
} else if (!connection.intentionallyClosed && !connection.overflowed) {
|
|
233
|
+
enqueue(connection, { kind: "closed", detail: `with code ${event.code}` });
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
socket.addEventListener("error", () => {
|
|
237
|
+
if (!settled) {
|
|
238
|
+
rejectConnection(new Error("WebSocket connection failed"));
|
|
239
|
+
} else {
|
|
240
|
+
enqueue(connection, { kind: "error", detail: "WebSocket connection failed" });
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
if (socket.readyState === WEBSOCKET_OPEN) resolveOpen();
|
|
244
|
+
else if (socket.readyState > WEBSOCKET_OPEN) {
|
|
245
|
+
rejectConnection(new Error("WebSocket closed during connection"));
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function connectMpp(endpoint) {
|
|
251
|
+
if (disposal) throw new Error("Nanocodex host is already disposed");
|
|
252
|
+
if (typeof options.mpp.ws !== "function") {
|
|
253
|
+
throw new TypeError("mpp must provide ws(endpoint)");
|
|
254
|
+
}
|
|
255
|
+
const ownership = openOwned(() => options.mpp.ws(endpoint));
|
|
256
|
+
const socket = await ownership.promise;
|
|
257
|
+
if (!socket || typeof socket.addEventListener !== "function") {
|
|
258
|
+
throw new TypeError("mpp.ws(endpoint) must return a WebSocket");
|
|
259
|
+
}
|
|
260
|
+
const handle = nextHandle++;
|
|
261
|
+
const connection = {
|
|
262
|
+
socket,
|
|
263
|
+
queue: [],
|
|
264
|
+
queuedBytes: 0,
|
|
265
|
+
waiter: undefined,
|
|
266
|
+
intentionallyClosed: false,
|
|
267
|
+
overflowed: false,
|
|
268
|
+
managed: true,
|
|
269
|
+
};
|
|
270
|
+
ownership.transfer();
|
|
271
|
+
connections.set(handle, connection);
|
|
272
|
+
socket.addEventListener("message", (event) => {
|
|
273
|
+
enqueue(connection, typeof event.data === "string"
|
|
274
|
+
? { kind: "text", text: event.data }
|
|
275
|
+
: { kind: "binary" });
|
|
276
|
+
});
|
|
277
|
+
socket.addEventListener("close", (event) => {
|
|
278
|
+
if (!connection.intentionallyClosed && !connection.overflowed) {
|
|
279
|
+
const code = event.code ?? 1000;
|
|
280
|
+
const suffix = event.reason ? `: ${event.reason}` : "";
|
|
281
|
+
enqueue(connection, code === MPP_CLIENT_PROTOCOL_ERROR_CLOSE_CODE
|
|
282
|
+
? {
|
|
283
|
+
kind: "error",
|
|
284
|
+
detail: `MPP WebSocket payment flow failed with code ${code}${suffix}`,
|
|
285
|
+
reconnectable: false,
|
|
286
|
+
}
|
|
287
|
+
: { kind: "closed", detail: `with code ${code}${suffix}` });
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
socket.addEventListener("error", () => {
|
|
291
|
+
enqueue(connection, { kind: "error", detail: "MPP WebSocket connection failed" });
|
|
292
|
+
});
|
|
293
|
+
return JSON.stringify({ handle, status: 101, reasoning_included: false });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function send(handle, message) {
|
|
297
|
+
const connection = connections.get(handle);
|
|
298
|
+
if (!connection || connection.socket.readyState !== WEBSOCKET_OPEN) {
|
|
299
|
+
return Promise.resolve(JSON.stringify({
|
|
300
|
+
ok: false,
|
|
301
|
+
reconnectable: true,
|
|
302
|
+
error: "WebSocket is no longer open",
|
|
303
|
+
}));
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
if (connection.managed) {
|
|
307
|
+
connection.socket.send(JSON.stringify({ mpp: "message", data: message }));
|
|
308
|
+
return Promise.resolve(JSON.stringify({ ok: true }));
|
|
309
|
+
}
|
|
310
|
+
const frameBytes = utf8ByteLength(message);
|
|
311
|
+
if (frameBytes > maxBufferedSendBytes
|
|
312
|
+
|| connection.socket.bufferedAmount + frameBytes > maxBufferedSendBytes) {
|
|
313
|
+
return Promise.resolve(JSON.stringify({
|
|
314
|
+
ok: false,
|
|
315
|
+
reconnectable: false,
|
|
316
|
+
error: `buffered WebSocket sends exceeded ${maxBufferedSendBytes} bytes`,
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
connection.socket.send(message);
|
|
320
|
+
return Promise.resolve(JSON.stringify({ ok: true }));
|
|
321
|
+
} catch (error) {
|
|
322
|
+
return Promise.resolve(JSON.stringify({
|
|
323
|
+
ok: false,
|
|
324
|
+
reconnectable: connection.socket.readyState !== WEBSOCKET_OPEN,
|
|
325
|
+
error: error instanceof Error ? error.message : String(error),
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function next(handle, timeoutMs) {
|
|
331
|
+
const connection = connections.get(handle);
|
|
332
|
+
if (!connection) {
|
|
333
|
+
return Promise.resolve(JSON.stringify({ kind: "closed", detail: "before the next frame" }));
|
|
334
|
+
}
|
|
335
|
+
if (connection.queue.length) {
|
|
336
|
+
const entry = connection.queue.shift();
|
|
337
|
+
connection.queuedBytes -= entry.bytes;
|
|
338
|
+
return Promise.resolve(JSON.stringify(entry.message));
|
|
339
|
+
}
|
|
340
|
+
if (connection.waiter) return Promise.reject(new Error("concurrent reads are unsupported"));
|
|
341
|
+
return new Promise((resolve) => {
|
|
342
|
+
const timer = setTimeout(() => {
|
|
343
|
+
connection.waiter = undefined;
|
|
344
|
+
resolve(JSON.stringify({ kind: "timeout" }));
|
|
345
|
+
}, timeoutMs);
|
|
346
|
+
connection.waiter = (message) => {
|
|
347
|
+
clearTimeout(timer);
|
|
348
|
+
connection.waiter = undefined;
|
|
349
|
+
resolve(JSON.stringify(message));
|
|
350
|
+
};
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function close(handle) {
|
|
355
|
+
const connection = connections.get(handle);
|
|
356
|
+
if (!connection) return;
|
|
357
|
+
connections.delete(handle);
|
|
358
|
+
connection.intentionallyClosed = true;
|
|
359
|
+
connection.waiter?.({ kind: "closed", detail: "by the WASM runtime" });
|
|
360
|
+
return connection.socket.close();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function enqueue(connection, message) {
|
|
364
|
+
if (connection.overflowed) return;
|
|
365
|
+
if (connection.waiter) {
|
|
366
|
+
connection.waiter(message);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const bytes = utf8ByteLength(message.kind === "text" ? message.text : JSON.stringify(message));
|
|
370
|
+
if (connection.queue.length >= maxQueuedMessages || connection.queuedBytes + bytes > maxQueuedBytes) {
|
|
371
|
+
connection.queue.length = 0;
|
|
372
|
+
connection.queuedBytes = 0;
|
|
373
|
+
connection.overflowed = true;
|
|
374
|
+
const error = {
|
|
375
|
+
kind: "error",
|
|
376
|
+
detail: `receive queue exceeded ${maxQueuedMessages} messages or ${maxQueuedBytes} bytes`,
|
|
377
|
+
};
|
|
378
|
+
const errorBytes = utf8ByteLength(JSON.stringify(error));
|
|
379
|
+
connection.queue.push({ message: error, bytes: errorBytes });
|
|
380
|
+
connection.queuedBytes = errorBytes;
|
|
381
|
+
connection.socket.close(1009, "receive queue exceeded configured bounds");
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
connection.queue.push({ message, bytes });
|
|
385
|
+
connection.queuedBytes += bytes;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function dispose() {
|
|
389
|
+
if (disposal) return disposal;
|
|
390
|
+
disposalError = new Error("Nanocodex host was disposed during WebSocket connection");
|
|
391
|
+
disposal = Promise.resolve().then(async () => {
|
|
392
|
+
const cleanups = [];
|
|
393
|
+
const cleanup = (action) => {
|
|
394
|
+
try { cleanups.push(Promise.resolve(action())); }
|
|
395
|
+
catch (failure) { cleanups.push(Promise.reject(failure)); }
|
|
396
|
+
};
|
|
397
|
+
const preconnectedOwnership = preconnected?.ownership;
|
|
398
|
+
for (const attempt of [...openingAttempts]) {
|
|
399
|
+
if (attempt !== preconnectedOwnership) cleanup(() => attempt.dispose(disposalError));
|
|
400
|
+
}
|
|
401
|
+
for (const connection of [...connectingConnections]) {
|
|
402
|
+
connection.intentionallyClosed = true;
|
|
403
|
+
cleanup(() => connection.reject(disposalError));
|
|
404
|
+
cleanup(() => connection.socket.close());
|
|
405
|
+
}
|
|
406
|
+
for (const handle of [...connections.keys()]) cleanup(() => close(handle));
|
|
407
|
+
cleanup(() => closePreconnected(disposalError));
|
|
408
|
+
cleanup(() => code.reset());
|
|
409
|
+
cleanup(() => mcpInstalled?.then(() => {}));
|
|
410
|
+
cleanup(() => toolsLifecycle?.close());
|
|
411
|
+
cleanup(() => options.onDispose?.());
|
|
412
|
+
const settled = await Promise.allSettled(cleanups);
|
|
413
|
+
const errors = settled
|
|
414
|
+
.filter((result) => result.status === "rejected")
|
|
415
|
+
.map((result) => result.reason);
|
|
416
|
+
if (errors.length === 1) throw errors[0];
|
|
417
|
+
if (errors.length > 1) {
|
|
418
|
+
throw new AggregateError(errors, "Nanocodex host disposal failed");
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
return disposal;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function openOwned(factory) {
|
|
425
|
+
if (disposal) throw new Error("Nanocodex host is already disposed");
|
|
426
|
+
let rejectDisposed;
|
|
427
|
+
const disposed = new Promise((_, reject) => { rejectDisposed = reject; });
|
|
428
|
+
const ownership = {
|
|
429
|
+
disposed: false,
|
|
430
|
+
opened: undefined,
|
|
431
|
+
closePromise: undefined,
|
|
432
|
+
error: undefined,
|
|
433
|
+
dispose(error) {
|
|
434
|
+
if (ownership.disposed) return ownership.closePromise ?? Promise.resolve();
|
|
435
|
+
ownership.disposed = true;
|
|
436
|
+
ownership.error = error;
|
|
437
|
+
openingAttempts.delete(ownership);
|
|
438
|
+
rejectDisposed(error);
|
|
439
|
+
return closeOpened();
|
|
440
|
+
},
|
|
441
|
+
transfer() {
|
|
442
|
+
if (disposal || ownership.disposed) {
|
|
443
|
+
throw disposalError ?? ownership.error
|
|
444
|
+
?? new Error("Nanocodex host was disposed during WebSocket connection");
|
|
445
|
+
}
|
|
446
|
+
openingAttempts.delete(ownership);
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
openingAttempts.add(ownership);
|
|
450
|
+
let opening;
|
|
451
|
+
try {
|
|
452
|
+
opening = Promise.resolve(factory());
|
|
453
|
+
} catch (error) {
|
|
454
|
+
opening = Promise.reject(error);
|
|
455
|
+
}
|
|
456
|
+
opening = opening.then((opened) => {
|
|
457
|
+
ownership.opened = opened;
|
|
458
|
+
if (!ownership.disposed) return opened;
|
|
459
|
+
void closeOpened().catch(() => {});
|
|
460
|
+
throw ownership.error;
|
|
461
|
+
});
|
|
462
|
+
ownership.promise = Promise.race([opening, disposed]);
|
|
463
|
+
void ownership.promise.catch(() => {
|
|
464
|
+
openingAttempts.delete(ownership);
|
|
465
|
+
});
|
|
466
|
+
return ownership;
|
|
467
|
+
|
|
468
|
+
function closeOpened() {
|
|
469
|
+
if (ownership.closePromise) return ownership.closePromise;
|
|
470
|
+
if (ownership.opened === undefined) return Promise.resolve();
|
|
471
|
+
try {
|
|
472
|
+
ownership.closePromise = Promise.resolve(
|
|
473
|
+
normalizeWebSocketConnection(ownership.opened).socket.close(),
|
|
474
|
+
);
|
|
475
|
+
} catch (error) {
|
|
476
|
+
ownership.closePromise = Promise.reject(error);
|
|
477
|
+
}
|
|
478
|
+
return ownership.closePromise;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
toolsLifecycle?.claim();
|
|
483
|
+
return Object.freeze({
|
|
484
|
+
ready: async () => {
|
|
485
|
+
await Promise.all([filesystemReady, mcpInstalled, toolProvidersReady]);
|
|
486
|
+
},
|
|
487
|
+
retain() {
|
|
488
|
+
if (disposal) throw new Error("Nanocodex host is already disposed");
|
|
489
|
+
references += 1;
|
|
490
|
+
},
|
|
491
|
+
release() {
|
|
492
|
+
if (references > 0) references -= 1;
|
|
493
|
+
return references === 0 ? dispose() : Promise.resolve();
|
|
494
|
+
},
|
|
495
|
+
connect,
|
|
496
|
+
preconnect,
|
|
497
|
+
send,
|
|
498
|
+
next,
|
|
499
|
+
close,
|
|
500
|
+
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
501
|
+
executeCode: code.executeCodeObserved,
|
|
502
|
+
waitCode: code.waitCodeObserved,
|
|
503
|
+
beginCodeTurn: code.beginTurn,
|
|
504
|
+
cancelCodeTurn: code.cancelTurn,
|
|
505
|
+
nextCodeUpdate: code.nextCodeUpdate,
|
|
506
|
+
executeTool: code.executeTool,
|
|
507
|
+
bindSubagentSession: code.bindSubagentSession,
|
|
508
|
+
cancelCode: code.cancel,
|
|
509
|
+
readWorkspaceFile: async (path) => {
|
|
510
|
+
if (!options.filesystem) throw new Error("browser workspace is unavailable");
|
|
511
|
+
const contents = await options.filesystem.readFile(path);
|
|
512
|
+
if (!(contents instanceof Uint8Array)) {
|
|
513
|
+
throw new TypeError("browser workspace readFile() must return Uint8Array");
|
|
514
|
+
}
|
|
515
|
+
return contents;
|
|
516
|
+
},
|
|
517
|
+
listWorkspace: async (path) => {
|
|
518
|
+
if (!options.filesystem) throw new Error("browser workspace is unavailable");
|
|
519
|
+
if (typeof options.filesystem.list !== "function") {
|
|
520
|
+
throw new Error("browser workspace does not expose list()");
|
|
521
|
+
}
|
|
522
|
+
return options.filesystem.list(path, { maxEntries: 2_000 });
|
|
523
|
+
},
|
|
524
|
+
writeWorkspaceFile: async (path, contents) => {
|
|
525
|
+
if (!options.filesystem) throw new Error("browser workspace is unavailable");
|
|
526
|
+
await options.filesystem.writeFile(path, contents);
|
|
527
|
+
},
|
|
528
|
+
removeWorkspaceFile: async (path) => {
|
|
529
|
+
if (!options.filesystem) throw new Error("browser workspace is unavailable");
|
|
530
|
+
await options.filesystem.remove(path);
|
|
531
|
+
},
|
|
532
|
+
toolMode: () => toolMode,
|
|
533
|
+
toolDefinitions: code.toolDefinitions,
|
|
534
|
+
resolveCompactionInstruction: (context, signal) => {
|
|
535
|
+
if (typeof options.resolveCompactionInstruction !== "function") {
|
|
536
|
+
throw new Error("resolveCompactionInstruction is not configured");
|
|
537
|
+
}
|
|
538
|
+
return options.resolveCompactionInstruction(context, signal);
|
|
539
|
+
},
|
|
540
|
+
releaseSession: code.releaseSession,
|
|
541
|
+
emitEvent: onEvent,
|
|
542
|
+
reset: code.reset,
|
|
543
|
+
dispose,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function normalizeWebSocketConnection(opened) {
|
|
548
|
+
if (opened?.socket && typeof opened.socket.addEventListener === "function") {
|
|
549
|
+
return opened;
|
|
550
|
+
}
|
|
551
|
+
if (!opened || typeof opened.addEventListener !== "function") {
|
|
552
|
+
throw new TypeError("createWebSocket must return a WebSocket or a connection descriptor");
|
|
553
|
+
}
|
|
554
|
+
return { socket: opened };
|
|
555
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type HostManagedWebSocketOptions = Readonly<{
|
|
2
|
+
WebSocketImpl?: typeof WebSocket | undefined;
|
|
3
|
+
timeoutMs?: number | undefined;
|
|
4
|
+
}>;
|
|
5
|
+
|
|
6
|
+
export function openHostManagedWebSocket(
|
|
7
|
+
endpoint: string | URL,
|
|
8
|
+
sessionId: string,
|
|
9
|
+
options?: HostManagedWebSocketOptions,
|
|
10
|
+
): Promise<WebSocket>;
|
|
11
|
+
|
|
12
|
+
export function defaultHostManagedWebSocketUrl(
|
|
13
|
+
location?: Pick<Location, "href">,
|
|
14
|
+
): string;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
2
|
+
|
|
3
|
+
/** Open Nanocodex's same-origin Responses proxy and consume its setup frame. */
|
|
4
|
+
export function openHostManagedWebSocket(endpoint, sessionId, options = {}) {
|
|
5
|
+
if (typeof sessionId !== "string" || !sessionId) {
|
|
6
|
+
throw new TypeError("host-managed WebSocket requires a session ID");
|
|
7
|
+
}
|
|
8
|
+
const WebSocketImpl = options.WebSocketImpl ?? globalThis.WebSocket;
|
|
9
|
+
if (typeof WebSocketImpl !== "function") {
|
|
10
|
+
throw new Error("WebSocket is unavailable in this runtime");
|
|
11
|
+
}
|
|
12
|
+
const socketUrl = resolveWebSocketUrl(endpoint);
|
|
13
|
+
socketUrl.searchParams.set("session_id", sessionId);
|
|
14
|
+
socketUrl.searchParams.set("thread_id", options.threadId ?? sessionId);
|
|
15
|
+
return waitForProxyHandshake(
|
|
16
|
+
new WebSocketImpl(socketUrl),
|
|
17
|
+
options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function defaultHostManagedWebSocketUrl(location = globalThis.location) {
|
|
22
|
+
if (!location?.href) {
|
|
23
|
+
throw new Error("host-managed transport requires websocketUrl outside a browser location");
|
|
24
|
+
}
|
|
25
|
+
const url = new URL("/api/responses", location.href);
|
|
26
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
27
|
+
return url.href;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveWebSocketUrl(endpoint) {
|
|
31
|
+
const base = globalThis.location?.href;
|
|
32
|
+
const url = base === undefined ? new URL(endpoint) : new URL(endpoint, base);
|
|
33
|
+
if (url.protocol !== "ws:" && url.protocol !== "wss:") {
|
|
34
|
+
throw new TypeError("host-managed websocketUrl must use ws: or wss:");
|
|
35
|
+
}
|
|
36
|
+
return url;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function waitForProxyHandshake(socket, timeoutMs) {
|
|
40
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
|
|
41
|
+
socket.close();
|
|
42
|
+
throw new TypeError("host-managed WebSocket timeout must be a positive integer");
|
|
43
|
+
}
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
let settled = false;
|
|
46
|
+
const timer = setTimeout(
|
|
47
|
+
() => fail(new Error("Agent connection timed out")),
|
|
48
|
+
timeoutMs,
|
|
49
|
+
);
|
|
50
|
+
const cleanup = () => {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
socket.removeEventListener("message", onMessage);
|
|
53
|
+
socket.removeEventListener("error", onError);
|
|
54
|
+
socket.removeEventListener("close", onClose);
|
|
55
|
+
};
|
|
56
|
+
const fail = (error) => {
|
|
57
|
+
if (settled) return;
|
|
58
|
+
settled = true;
|
|
59
|
+
cleanup();
|
|
60
|
+
socket.close();
|
|
61
|
+
reject(error);
|
|
62
|
+
};
|
|
63
|
+
const onMessage = (event) => {
|
|
64
|
+
const message = parseHandshake(event.data);
|
|
65
|
+
if (message?.type === "nanocodex.proxy.ready") {
|
|
66
|
+
settled = true;
|
|
67
|
+
cleanup();
|
|
68
|
+
resolve(socket);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (message?.type === "nanocodex.proxy.rejected"
|
|
72
|
+
&& Number.isInteger(message.status)
|
|
73
|
+
&& message.status >= 100
|
|
74
|
+
&& message.status <= 599) {
|
|
75
|
+
const status = message.status;
|
|
76
|
+
const body = typeof message.error === "string" ? message.error : `HTTP ${status}`;
|
|
77
|
+
const retryAfter = Number(message.retryAfter);
|
|
78
|
+
fail(Object.assign(
|
|
79
|
+
new Error(`Agent connection rejected with HTTP ${status}: ${body}`),
|
|
80
|
+
{
|
|
81
|
+
status,
|
|
82
|
+
body,
|
|
83
|
+
...(Number.isFinite(retryAfter) && retryAfter >= 0 ? { retryAfter } : {}),
|
|
84
|
+
},
|
|
85
|
+
));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
fail(new Error("Agent connection returned an invalid handshake"));
|
|
89
|
+
};
|
|
90
|
+
const onError = () => fail(new Error("WebSocket connection failed"));
|
|
91
|
+
const onClose = (event) => fail(
|
|
92
|
+
new Error(`WebSocket closed during connection with code ${event.code}`),
|
|
93
|
+
);
|
|
94
|
+
socket.addEventListener("message", onMessage);
|
|
95
|
+
socket.addEventListener("error", onError);
|
|
96
|
+
socket.addEventListener("close", onClose);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseHandshake(data) {
|
|
101
|
+
if (typeof data !== "string") return undefined;
|
|
102
|
+
try {
|
|
103
|
+
const value = JSON.parse(data);
|
|
104
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
105
|
+
? value
|
|
106
|
+
: undefined;
|
|
107
|
+
} catch {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|