nanocodex 0.1.1
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 +116 -0
- package/actions/events.d.mts +10 -0
- package/actions/events.mjs +91 -0
- package/actions/index.d.mts +12 -0
- package/actions/index.mjs +20 -0
- package/actions/session.d.mts +14 -0
- package/actions/session.mjs +9 -0
- package/actions/turn.d.mts +30 -0
- package/actions/turn.mjs +22 -0
- package/browser/Agent.d.mts +25 -0
- package/browser/Agent.mjs +54 -0
- package/browser/host.d.mts +20 -0
- package/browser/host.mjs +161 -0
- package/browser/index.d.mts +13 -0
- package/browser/index.mjs +2 -0
- package/index.d.mts +19 -0
- package/index.mjs +1 -0
- package/internal.mjs +303 -0
- package/node/Agent.d.mts +24 -0
- package/node/Agent.mjs +51 -0
- package/node/host.mjs +204 -0
- package/node/index.d.mts +13 -0
- package/node/index.mjs +2 -0
- package/package.json +61 -0
- package/pkg-node/nanocodex.d.ts +109 -0
- package/pkg-node/nanocodex.js +652 -0
- package/pkg-node/nanocodex_bg.wasm +0 -0
- package/pkg-node/nanocodex_bg.wasm.d.ts +27 -0
- package/pkg-node/package.json +1 -0
- package/pkg-web/nanocodex.d.ts +161 -0
- package/pkg-web/nanocodex.js +745 -0
- package/pkg-web/nanocodex_bg.wasm +0 -0
- package/pkg-web/nanocodex_bg.wasm.d.ts +27 -0
- package/pkg-web/package.json +1 -0
- package/runtime/code-runtime.mjs +191 -0
- package/scripts/check-package.mjs +38 -0
- package/types.d.mts +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Nanocodex for JavaScript
|
|
2
|
+
|
|
3
|
+
The Node and browser entrypoints expose the same viem-v3-style API over the
|
|
4
|
+
same Rust/WASM agent. Runtime-specific host options are flattened into
|
|
5
|
+
`Agent.create(...)`; generated WASM handles and host routing remain private.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { Actions, Agent } from "nanocodex/node";
|
|
9
|
+
|
|
10
|
+
const agent = await Agent.create({
|
|
11
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
12
|
+
reasoningMode: "pro",
|
|
13
|
+
thinking: "high",
|
|
14
|
+
tools,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const turn = agent.turn.prompt({ input: "Build the thing." });
|
|
18
|
+
console.log(await turn.result());
|
|
19
|
+
|
|
20
|
+
const branch = await agent.session.fork({ at: turn });
|
|
21
|
+
console.log(await branch.turn.prompt({ input: "Try another approach." }).result());
|
|
22
|
+
|
|
23
|
+
const followOn = Actions.turn.prompt(agent, { input: "Now explain it." });
|
|
24
|
+
console.log(await Actions.turn.getResult(followOn));
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`Agent` and `Actions` are module namespaces, not classes. `Agent.create` returns
|
|
28
|
+
an owned client decorated with matching domain actions:
|
|
29
|
+
|
|
30
|
+
- `agent.turn.prompt(...)` / `Actions.turn.prompt(agent, ...)`
|
|
31
|
+
- `agent.session.fork(...)` / `Actions.session.fork(agent, ...)`
|
|
32
|
+
- `agent.session.spawn()` / `Actions.session.spawn(agent)`
|
|
33
|
+
- `agent.events.watch(...)` / `Actions.events.watch(agent, ...)`
|
|
34
|
+
|
|
35
|
+
Every action owns its types, for example `Actions.turn.prompt.Options`,
|
|
36
|
+
`Actions.turn.prompt.ReturnType`, and `Actions.events.watch.Watcher`.
|
|
37
|
+
|
|
38
|
+
Event watches are lazy, terminal handles:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const watch = agent.events.watch();
|
|
42
|
+
const unlisten = watch.onEvent(console.log);
|
|
43
|
+
|
|
44
|
+
unlisten();
|
|
45
|
+
watch.off();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The same watcher can instead be consumed as an ordered async iterable; breaking
|
|
49
|
+
the loop releases that iterator, while `watch.off()` terminates the whole watch.
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
const watch = agent.events.watch();
|
|
53
|
+
for await (const event of watch) {
|
|
54
|
+
console.log(event);
|
|
55
|
+
if (done) break;
|
|
56
|
+
}
|
|
57
|
+
watch.off();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Applications add typed action domains with decorators:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
const extended = agent.extend((client) => ({
|
|
64
|
+
inspect: {
|
|
65
|
+
session: () => client.sessionId,
|
|
66
|
+
},
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
extended.inspect.session();
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Browser Workers use the identical shape:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
import { Agent } from "nanocodex/browser";
|
|
76
|
+
|
|
77
|
+
const agent = await Agent.create({
|
|
78
|
+
websocketUrl: signedOrCookieAuthorizedEndpoint,
|
|
79
|
+
createWebSocket(endpoint, sessionId) {
|
|
80
|
+
const url = new URL(endpoint);
|
|
81
|
+
url.searchParams.set("session_id", sessionId);
|
|
82
|
+
return new WebSocket(url);
|
|
83
|
+
},
|
|
84
|
+
tools,
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
After publication, a browser can load the same entrypoint without a package
|
|
89
|
+
manager or build step:
|
|
90
|
+
|
|
91
|
+
```html
|
|
92
|
+
<script type="module">
|
|
93
|
+
import { Agent } from "https://cdn.jsdelivr.net/npm/nanocodex@0.1.0/browser/index.mjs";
|
|
94
|
+
const agent = await Agent.create({ websocketUrl: "/api/responses" });
|
|
95
|
+
console.log(await agent.turn.prompt({ input: "Hello." }).result());
|
|
96
|
+
</script>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Pin the package version in production. The adjacent WASM file is part of the
|
|
100
|
+
npm package and is resolved relative to the browser module. The endpoint must
|
|
101
|
+
be authorized by the embedding application because browser WebSockets cannot
|
|
102
|
+
attach OpenAI's upgrade authorization header.
|
|
103
|
+
|
|
104
|
+
The owned Rust session retains follow-on history, response state, tool output,
|
|
105
|
+
its WebSocket, and stable prompt-cache identity. Typed browser content accepts
|
|
106
|
+
ordered text, remote/data-URL image, and audio items. JavaScript tools are
|
|
107
|
+
ordinary async handlers described by JSON Schema and appear in the same ordered
|
|
108
|
+
agent event stream as built-in code mode.
|
|
109
|
+
|
|
110
|
+
Run the standalone Node proof with:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
cd examples/node
|
|
114
|
+
npm install
|
|
115
|
+
OPENAI_API_KEY=... npm start
|
|
116
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Agent, AgentEvent, EventWatcher, WatchEventsOptions } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
/** Creates a lazy, terminal watcher for an Agent's ordered event stream. */
|
|
4
|
+
export function watch(agent: Agent<object>, options?: watch.Options): watch.ReturnType;
|
|
5
|
+
export declare namespace watch {
|
|
6
|
+
type OnEventFn = (event: AgentEvent) => void;
|
|
7
|
+
type Options = WatchEventsOptions;
|
|
8
|
+
type ReturnType = EventWatcher;
|
|
9
|
+
type Watcher = EventWatcher;
|
|
10
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { subscribeAgentEvents } from "../internal.mjs";
|
|
2
|
+
|
|
3
|
+
export function watch(agent, options = {}) {
|
|
4
|
+
const listeners = new Set();
|
|
5
|
+
const iterators = new Set();
|
|
6
|
+
let unsubscribe;
|
|
7
|
+
let closed = false;
|
|
8
|
+
|
|
9
|
+
const emit = (event) => {
|
|
10
|
+
for (const listener of listeners) listener(event);
|
|
11
|
+
for (const iterator of iterators) iterator.push(event);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const start = () => {
|
|
15
|
+
if (closed || unsubscribe) return;
|
|
16
|
+
unsubscribe = subscribeAgentEvents(agent, emit, options);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const watcher = {
|
|
20
|
+
onEvent(listener) {
|
|
21
|
+
if (typeof listener !== "function") throw new TypeError("events.watch.onEvent requires a listener");
|
|
22
|
+
if (closed) return () => {};
|
|
23
|
+
listeners.add(listener);
|
|
24
|
+
start();
|
|
25
|
+
return () => listeners.delete(listener);
|
|
26
|
+
},
|
|
27
|
+
off() {
|
|
28
|
+
if (closed) return;
|
|
29
|
+
closed = true;
|
|
30
|
+
unsubscribe?.();
|
|
31
|
+
unsubscribe = undefined;
|
|
32
|
+
listeners.clear();
|
|
33
|
+
for (const iterator of [...iterators]) iterator.end();
|
|
34
|
+
iterators.clear();
|
|
35
|
+
},
|
|
36
|
+
[Symbol.asyncIterator]() {
|
|
37
|
+
if (closed) return emptyIterator();
|
|
38
|
+
const iterator = eventIterator(() => iterators.delete(iterator));
|
|
39
|
+
iterators.add(iterator);
|
|
40
|
+
start();
|
|
41
|
+
return iterator;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
return Object.freeze(watcher);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function eventIterator(onEnd) {
|
|
48
|
+
const queue = [];
|
|
49
|
+
let pending;
|
|
50
|
+
let done = false;
|
|
51
|
+
|
|
52
|
+
const iterator = {
|
|
53
|
+
push(event) {
|
|
54
|
+
if (done) return;
|
|
55
|
+
if (pending) {
|
|
56
|
+
const resolve = pending;
|
|
57
|
+
pending = undefined;
|
|
58
|
+
resolve({ done: false, value: event });
|
|
59
|
+
} else {
|
|
60
|
+
queue.push(event);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
end() {
|
|
64
|
+
if (done) return;
|
|
65
|
+
done = true;
|
|
66
|
+
onEnd();
|
|
67
|
+
pending?.({ done: true, value: undefined });
|
|
68
|
+
pending = undefined;
|
|
69
|
+
queue.length = 0;
|
|
70
|
+
},
|
|
71
|
+
next() {
|
|
72
|
+
if (queue.length) return Promise.resolve({ done: false, value: queue.shift() });
|
|
73
|
+
if (done) return Promise.resolve({ done: true, value: undefined });
|
|
74
|
+
return new Promise((resolve) => { pending = resolve; });
|
|
75
|
+
},
|
|
76
|
+
return() {
|
|
77
|
+
iterator.end();
|
|
78
|
+
return Promise.resolve({ done: true, value: undefined });
|
|
79
|
+
},
|
|
80
|
+
[Symbol.asyncIterator]() { return this; },
|
|
81
|
+
};
|
|
82
|
+
return iterator;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function emptyIterator() {
|
|
86
|
+
return {
|
|
87
|
+
next: () => Promise.resolve({ done: true, value: undefined }),
|
|
88
|
+
return: () => Promise.resolve({ done: true, value: undefined }),
|
|
89
|
+
[Symbol.asyncIterator]() { return this; },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Agent, AgentActions } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
export * as events from "./events.mjs";
|
|
4
|
+
export * as session from "./session.mjs";
|
|
5
|
+
export * as turn from "./turn.mjs";
|
|
6
|
+
|
|
7
|
+
/** Decorates a base Agent with the standard `turn`, `session`, and `events` domains. */
|
|
8
|
+
export function agentActions(): agentActions.DecoratorFn;
|
|
9
|
+
export declare namespace agentActions {
|
|
10
|
+
type Decorator = AgentActions;
|
|
11
|
+
type DecoratorFn = (agent: Agent<object>) => Decorator;
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as events from "./events.mjs";
|
|
2
|
+
import * as session from "./session.mjs";
|
|
3
|
+
import * as turn from "./turn.mjs";
|
|
4
|
+
|
|
5
|
+
export { events, session, turn };
|
|
6
|
+
|
|
7
|
+
export function agentActions() {
|
|
8
|
+
return (agent) => ({
|
|
9
|
+
events: {
|
|
10
|
+
watch: (options) => events.watch(agent, options),
|
|
11
|
+
},
|
|
12
|
+
session: {
|
|
13
|
+
fork: (options) => session.fork(agent, options),
|
|
14
|
+
spawn: () => session.spawn(agent),
|
|
15
|
+
},
|
|
16
|
+
turn: {
|
|
17
|
+
prompt: (options) => turn.prompt(agent, options),
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Agent, DefaultAgent, ForkOptions } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
/** Forks the latest checkpoint, or the exact completed Turn supplied in `options.at`. */
|
|
4
|
+
export function fork(agent: Agent<object>, options?: fork.Options): Promise<fork.ReturnType>;
|
|
5
|
+
export declare namespace fork {
|
|
6
|
+
type Options = ForkOptions;
|
|
7
|
+
type ReturnType = DefaultAgent;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Creates a clean sibling with the Agent's configuration and tools. */
|
|
11
|
+
export function spawn(agent: Agent<object>): Promise<spawn.ReturnType>;
|
|
12
|
+
export declare namespace spawn {
|
|
13
|
+
type ReturnType = DefaultAgent;
|
|
14
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Agent, PromptInput, Turn } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
/** Accepts a prompt on an owned Agent and returns its independently awaitable Turn. */
|
|
4
|
+
export function prompt<const agent extends Agent<object>>(
|
|
5
|
+
agent: agent,
|
|
6
|
+
options: prompt.Options,
|
|
7
|
+
): prompt.ReturnType<agent>;
|
|
8
|
+
export declare namespace prompt {
|
|
9
|
+
type Options = { input: PromptInput };
|
|
10
|
+
type ReturnType<agent extends Agent<object> = Agent<object>> = Turn<agent>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Waits for a Turn's final assistant message. */
|
|
14
|
+
export function getResult(turn: Turn): Promise<getResult.ReturnType>;
|
|
15
|
+
export declare namespace getResult {
|
|
16
|
+
type ReturnType = string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Adds input to an active Turn. */
|
|
20
|
+
export function steer(turn: Turn, options: steer.Options): Promise<void>;
|
|
21
|
+
export declare namespace steer {
|
|
22
|
+
type Options = { input: PromptInput };
|
|
23
|
+
type ReturnType = void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Cancels an active or queued Turn. */
|
|
27
|
+
export function cancel(turn: Turn): Promise<void>;
|
|
28
|
+
export declare namespace cancel {
|
|
29
|
+
type ReturnType = void;
|
|
30
|
+
}
|
package/actions/turn.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cancel as cancelTurn,
|
|
3
|
+
getTurnResult,
|
|
4
|
+
prompt as promptTurn,
|
|
5
|
+
steer as steerTurn,
|
|
6
|
+
} from "../internal.mjs";
|
|
7
|
+
|
|
8
|
+
export function prompt(agent, options) {
|
|
9
|
+
return promptTurn(agent, options);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getResult(turn) {
|
|
13
|
+
return getTurnResult(turn);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function steer(turn, options) {
|
|
17
|
+
return steerTurn(turn, options);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function cancel(turn) {
|
|
21
|
+
return cancelTurn(turn);
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentOptions,
|
|
3
|
+
DefaultAgent,
|
|
4
|
+
ToolMap,
|
|
5
|
+
} from "../types.mjs";
|
|
6
|
+
|
|
7
|
+
export type Agent = DefaultAgent;
|
|
8
|
+
|
|
9
|
+
/** Creates a browser- or Worker-hosted Rust/WASM Agent. */
|
|
10
|
+
export function create(options?: create.Options): Promise<create.ReturnType>;
|
|
11
|
+
export declare namespace create {
|
|
12
|
+
type Options = AgentOptions & {
|
|
13
|
+
WebSocketImpl?: typeof WebSocket | undefined;
|
|
14
|
+
apiBaseUrl?: string | undefined;
|
|
15
|
+
apiKey?: string | undefined;
|
|
16
|
+
createWebSocket?(endpoint: string, sessionId: string): WebSocket;
|
|
17
|
+
maxBufferedSendBytes?: number | undefined;
|
|
18
|
+
maxQueuedBytes?: number | undefined;
|
|
19
|
+
maxQueuedMessages?: number | undefined;
|
|
20
|
+
module?: unknown;
|
|
21
|
+
tools?: ToolMap | undefined;
|
|
22
|
+
websocketUrl?: string | undefined;
|
|
23
|
+
};
|
|
24
|
+
type ReturnType = Agent;
|
|
25
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import init, { Nanocodex } from "../pkg-web/nanocodex.js";
|
|
2
|
+
|
|
3
|
+
import { agentActions } from "../actions/index.mjs";
|
|
4
|
+
import {
|
|
5
|
+
activateHost,
|
|
6
|
+
bindHostSession,
|
|
7
|
+
createAgentClient,
|
|
8
|
+
createEventChannel,
|
|
9
|
+
defineRuntime,
|
|
10
|
+
releaseHostSession,
|
|
11
|
+
toWasmConfig,
|
|
12
|
+
} from "../internal.mjs";
|
|
13
|
+
import { createBrowserHost } from "./host.mjs";
|
|
14
|
+
|
|
15
|
+
let initialized;
|
|
16
|
+
|
|
17
|
+
export function create(options = {}) {
|
|
18
|
+
const {
|
|
19
|
+
apiKey = "host-managed",
|
|
20
|
+
websocketUrl,
|
|
21
|
+
apiBaseUrl,
|
|
22
|
+
module,
|
|
23
|
+
thinking,
|
|
24
|
+
reasoningMode,
|
|
25
|
+
instructions,
|
|
26
|
+
sessionId,
|
|
27
|
+
...hostOptions
|
|
28
|
+
} = options;
|
|
29
|
+
const events = createEventChannel();
|
|
30
|
+
const host = createBrowserHost({ ...hostOptions, onEvent: events.emit });
|
|
31
|
+
activateHost(host);
|
|
32
|
+
const runtime = defineRuntime({
|
|
33
|
+
key: "browser-wasm",
|
|
34
|
+
name: "Nanocodex Browser WASM",
|
|
35
|
+
type: "browser",
|
|
36
|
+
async create(config) {
|
|
37
|
+
activateHost(host);
|
|
38
|
+
initialized ||= module === undefined ? init() : init({ module_or_path: module });
|
|
39
|
+
await initialized;
|
|
40
|
+
activateHost(host);
|
|
41
|
+
return new Nanocodex(JSON.stringify(toWasmConfig({
|
|
42
|
+
apiKey,
|
|
43
|
+
websocketUrl,
|
|
44
|
+
apiBaseUrl,
|
|
45
|
+
...config,
|
|
46
|
+
})));
|
|
47
|
+
},
|
|
48
|
+
subscribe: events.subscribe,
|
|
49
|
+
adopt: (raw) => bindHostSession(host, raw.sessionId),
|
|
50
|
+
release: (raw) => releaseHostSession(host, raw.sessionId),
|
|
51
|
+
decorate: (agent) => agent.extend(agentActions()),
|
|
52
|
+
});
|
|
53
|
+
return createAgentClient(runtime, { thinking, reasoningMode, instructions, sessionId });
|
|
54
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type BrowserTool = {
|
|
2
|
+
description: string;
|
|
3
|
+
parameters: Record<string, unknown>;
|
|
4
|
+
handler: (
|
|
5
|
+
input: unknown,
|
|
6
|
+
context: { sessionId: string },
|
|
7
|
+
) => unknown | Promise<unknown>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type BrowserToolMap = Record<string, BrowserTool>;
|
|
11
|
+
|
|
12
|
+
export function createBrowserHost(options?: {
|
|
13
|
+
WebSocketImpl?: typeof WebSocket;
|
|
14
|
+
createWebSocket?: (endpoint: string, sessionId: string) => WebSocket;
|
|
15
|
+
onEvent?: (eventJson: string) => void;
|
|
16
|
+
tools?: BrowserToolMap;
|
|
17
|
+
maxQueuedMessages?: number;
|
|
18
|
+
maxQueuedBytes?: number;
|
|
19
|
+
maxBufferedSendBytes?: number;
|
|
20
|
+
}): unknown;
|
package/browser/host.mjs
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { createCodeRuntime } from "../runtime/code-runtime.mjs";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_MAX_QUEUED_MESSAGES = 4_096;
|
|
4
|
+
const DEFAULT_MAX_QUEUED_BYTES = 32 * 1024 * 1024;
|
|
5
|
+
const DEFAULT_MAX_BUFFERED_SEND_BYTES = 16 * 1024 * 1024;
|
|
6
|
+
|
|
7
|
+
export function createBrowserHost(options = {}) {
|
|
8
|
+
const WebSocketImpl = options.WebSocketImpl || globalThis.WebSocket;
|
|
9
|
+
if (!WebSocketImpl) throw new Error("WebSocket is unavailable in this runtime");
|
|
10
|
+
const connections = new Map();
|
|
11
|
+
const code = createCodeRuntime(options.tools);
|
|
12
|
+
const onEvent = options.onEvent || (() => {});
|
|
13
|
+
const createWebSocket = options.createWebSocket || ((endpoint) => new WebSocketImpl(endpoint));
|
|
14
|
+
const maxQueuedMessages = options.maxQueuedMessages ?? DEFAULT_MAX_QUEUED_MESSAGES;
|
|
15
|
+
const maxQueuedBytes = options.maxQueuedBytes ?? DEFAULT_MAX_QUEUED_BYTES;
|
|
16
|
+
const maxBufferedSendBytes = options.maxBufferedSendBytes ?? DEFAULT_MAX_BUFFERED_SEND_BYTES;
|
|
17
|
+
const encoder = new TextEncoder();
|
|
18
|
+
let nextHandle = 1;
|
|
19
|
+
|
|
20
|
+
function connect(endpoint, _apiKey, sessionId) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
let settled = false;
|
|
23
|
+
const handle = nextHandle++;
|
|
24
|
+
const socket = createWebSocket(endpoint, sessionId);
|
|
25
|
+
const connection = {
|
|
26
|
+
socket,
|
|
27
|
+
queue: [],
|
|
28
|
+
queuedBytes: 0,
|
|
29
|
+
waiter: undefined,
|
|
30
|
+
intentionallyClosed: false,
|
|
31
|
+
overflowed: false,
|
|
32
|
+
};
|
|
33
|
+
socket.addEventListener("open", () => {
|
|
34
|
+
settled = true;
|
|
35
|
+
connections.set(handle, connection);
|
|
36
|
+
resolve(JSON.stringify({ handle, status: 101, reasoning_included: false }));
|
|
37
|
+
}, { once: true });
|
|
38
|
+
socket.addEventListener("message", (event) => {
|
|
39
|
+
enqueue(connection, typeof event.data === "string"
|
|
40
|
+
? { kind: "text", text: event.data }
|
|
41
|
+
: { kind: "binary" });
|
|
42
|
+
});
|
|
43
|
+
socket.addEventListener("close", (event) => {
|
|
44
|
+
if (!settled) {
|
|
45
|
+
settled = true;
|
|
46
|
+
reject(new Error(`WebSocket closed during connection with code ${event.code}`));
|
|
47
|
+
} else if (!connection.intentionallyClosed && !connection.overflowed) {
|
|
48
|
+
enqueue(connection, { kind: "closed", detail: `with code ${event.code}` });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
socket.addEventListener("error", () => {
|
|
52
|
+
if (!settled) {
|
|
53
|
+
settled = true;
|
|
54
|
+
reject(new Error("WebSocket connection failed"));
|
|
55
|
+
} else {
|
|
56
|
+
enqueue(connection, { kind: "error", detail: "WebSocket connection failed" });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function send(handle, message) {
|
|
63
|
+
const connection = connections.get(handle);
|
|
64
|
+
if (!connection || connection.socket.readyState !== WebSocketImpl.OPEN) {
|
|
65
|
+
return Promise.resolve(JSON.stringify({
|
|
66
|
+
ok: false,
|
|
67
|
+
reconnectable: true,
|
|
68
|
+
error: "WebSocket is no longer open",
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const frameBytes = encoder.encode(message).byteLength;
|
|
73
|
+
if (frameBytes > maxBufferedSendBytes
|
|
74
|
+
|| connection.socket.bufferedAmount + frameBytes > maxBufferedSendBytes) {
|
|
75
|
+
return Promise.resolve(JSON.stringify({
|
|
76
|
+
ok: false,
|
|
77
|
+
reconnectable: false,
|
|
78
|
+
error: `buffered WebSocket sends exceeded ${maxBufferedSendBytes} bytes`,
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
connection.socket.send(message);
|
|
82
|
+
return Promise.resolve(JSON.stringify({ ok: true }));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return Promise.resolve(JSON.stringify({
|
|
85
|
+
ok: false,
|
|
86
|
+
reconnectable: connection.socket.readyState !== WebSocketImpl.OPEN,
|
|
87
|
+
error: error instanceof Error ? error.message : String(error),
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function next(handle, timeoutMs) {
|
|
93
|
+
const connection = connections.get(handle);
|
|
94
|
+
if (!connection) {
|
|
95
|
+
return Promise.resolve(JSON.stringify({ kind: "closed", detail: "before the next frame" }));
|
|
96
|
+
}
|
|
97
|
+
if (connection.queue.length) {
|
|
98
|
+
const entry = connection.queue.shift();
|
|
99
|
+
connection.queuedBytes -= entry.bytes;
|
|
100
|
+
return Promise.resolve(JSON.stringify(entry.message));
|
|
101
|
+
}
|
|
102
|
+
if (connection.waiter) return Promise.reject(new Error("concurrent reads are unsupported"));
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
const timer = setTimeout(() => {
|
|
105
|
+
connection.waiter = undefined;
|
|
106
|
+
resolve(JSON.stringify({ kind: "timeout" }));
|
|
107
|
+
}, timeoutMs);
|
|
108
|
+
connection.waiter = (message) => {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
connection.waiter = undefined;
|
|
111
|
+
resolve(JSON.stringify(message));
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function close(handle) {
|
|
117
|
+
const connection = connections.get(handle);
|
|
118
|
+
if (!connection) return;
|
|
119
|
+
connections.delete(handle);
|
|
120
|
+
connection.intentionallyClosed = true;
|
|
121
|
+
connection.waiter?.({ kind: "closed", detail: "by the WASM runtime" });
|
|
122
|
+
connection.socket.close();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function enqueue(connection, message) {
|
|
126
|
+
if (connection.overflowed) return;
|
|
127
|
+
if (connection.waiter) {
|
|
128
|
+
connection.waiter(message);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const bytes = encoder.encode(message.kind === "text" ? message.text : JSON.stringify(message)).byteLength;
|
|
132
|
+
if (connection.queue.length >= maxQueuedMessages || connection.queuedBytes + bytes > maxQueuedBytes) {
|
|
133
|
+
connection.queue.length = 0;
|
|
134
|
+
connection.queuedBytes = 0;
|
|
135
|
+
connection.overflowed = true;
|
|
136
|
+
const error = {
|
|
137
|
+
kind: "error",
|
|
138
|
+
detail: `receive queue exceeded ${maxQueuedMessages} messages or ${maxQueuedBytes} bytes`,
|
|
139
|
+
};
|
|
140
|
+
const errorBytes = encoder.encode(JSON.stringify(error)).byteLength;
|
|
141
|
+
connection.queue.push({ message: error, bytes: errorBytes });
|
|
142
|
+
connection.queuedBytes = errorBytes;
|
|
143
|
+
connection.socket.close(1009, "receive queue exceeded configured bounds");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
connection.queue.push({ message, bytes });
|
|
147
|
+
connection.queuedBytes += bytes;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return Object.freeze({
|
|
151
|
+
connect,
|
|
152
|
+
send,
|
|
153
|
+
next,
|
|
154
|
+
close,
|
|
155
|
+
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
156
|
+
executeCode: code.executeCode,
|
|
157
|
+
toolDefinitions: code.toolDefinitions,
|
|
158
|
+
emitEvent: onEvent,
|
|
159
|
+
reset: code.reset,
|
|
160
|
+
});
|
|
161
|
+
}
|
package/index.d.mts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * as Actions from "./actions/index.mjs";
|
|
2
|
+
export type {
|
|
3
|
+
Agent,
|
|
4
|
+
AgentActions,
|
|
5
|
+
AgentEvent,
|
|
6
|
+
AgentOptions,
|
|
7
|
+
DefaultAgent,
|
|
8
|
+
EventWatcher,
|
|
9
|
+
ForkOptions,
|
|
10
|
+
PromptInput,
|
|
11
|
+
PromptItem,
|
|
12
|
+
ReasoningMode,
|
|
13
|
+
Thinking,
|
|
14
|
+
Tool,
|
|
15
|
+
ToolContext,
|
|
16
|
+
ToolMap,
|
|
17
|
+
Turn,
|
|
18
|
+
WatchEventsOptions,
|
|
19
|
+
} from "./types.mjs";
|
package/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as Actions from "./actions/index.mjs";
|