nanocodex 0.2.0 → 0.4.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 +111 -13
- package/actions/events.mjs +105 -22
- package/actions/index.mjs +2 -0
- package/actions/session.d.mts +7 -1
- package/actions/session.mjs +10 -0
- package/actions/turn.d.mts +18 -5
- package/actions/turn.mjs +7 -2
- package/browser/Agent.d.mts +14 -6
- package/browser/Agent.mjs +21 -2
- package/browser/host.d.mts +33 -1
- package/browser/host.mjs +53 -11
- package/browser/index.d.mts +4 -0
- package/index.d.mts +4 -0
- package/internal.mjs +233 -24
- package/node/Agent.d.mts +1 -5
- package/node/Agent.mjs +13 -2
- package/node/host.mjs +39 -12
- package/node/index.d.mts +4 -0
- package/package.json +8 -3
- package/pkg-node/nanocodex.d.ts +69 -38
- package/pkg-node/nanocodex.js +203 -88
- package/pkg-node/nanocodex_bg.wasm +0 -0
- package/pkg-node/nanocodex_bg.wasm.d.ts +7 -2
- package/pkg-web/nanocodex.d.ts +76 -40
- package/pkg-web/nanocodex.js +201 -87
- package/pkg-web/nanocodex_bg.wasm +0 -0
- package/pkg-web/nanocodex_bg.wasm.d.ts +7 -2
- package/runtime/code-runtime.mjs +58 -12
- package/types.d.mts +40 -3
- package/wasm.d.mts +4 -0
- package/scripts/check-package.mjs +0 -38
package/runtime/code-runtime.mjs
CHANGED
|
@@ -2,23 +2,45 @@ export function createCodeRuntime(toolConfiguration = {}, extras = {}) {
|
|
|
2
2
|
const stores = new Map();
|
|
3
3
|
let nextCallId = 1;
|
|
4
4
|
const definitions = [];
|
|
5
|
+
const configuredTools = [];
|
|
5
6
|
|
|
6
7
|
for (const [name, tool] of Object.entries(toolConfiguration)) {
|
|
7
8
|
if (!tool || typeof tool.handler !== "function") {
|
|
8
9
|
throw new TypeError(`tool ${name} requires a handler function`);
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
+
configuredTools.push(Object.freeze({ handler: tool.handler, name }));
|
|
12
|
+
definitions.push(deepFreeze({
|
|
11
13
|
type: "function",
|
|
12
14
|
name,
|
|
13
15
|
description: tool.description || "Application-defined tool.",
|
|
14
16
|
strict: false,
|
|
15
|
-
parameters: tool.parameters || {
|
|
17
|
+
parameters: jsonSnapshot(tool.parameters || {
|
|
16
18
|
type: "object",
|
|
17
19
|
additionalProperties: true,
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
+
}, `tool ${name} parameters`),
|
|
21
|
+
}));
|
|
20
22
|
}
|
|
23
|
+
Object.freeze(configuredTools);
|
|
21
24
|
Object.freeze(definitions);
|
|
25
|
+
const encodedDefinitions = JSON.stringify(definitions);
|
|
26
|
+
const toolByName = new Map(configuredTools.map((tool) => [tool.name, tool]));
|
|
27
|
+
|
|
28
|
+
async function executeTool(name, encodedInput, sessionId = "default", callId = "tool") {
|
|
29
|
+
const tool = toolByName.get(name);
|
|
30
|
+
if (!tool) return encodeToolOutput(`unknown application tool: ${name}`, false, null);
|
|
31
|
+
let input;
|
|
32
|
+
try {
|
|
33
|
+
input = JSON.parse(encodedInput);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return encodeToolOutput(`invalid tool input: ${errorMessage(error)}`, false, null);
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const result = await tool.handler(input, { sessionId, parentCallId: "", callId });
|
|
39
|
+
return encodeToolOutput(outputBody(result), true, clone(result) ?? null);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return encodeToolOutput(errorMessage(error), false, null);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
22
44
|
|
|
23
45
|
async function executeCode(source, sessionId = "default", parentCallId = "exec") {
|
|
24
46
|
const startedAt = performance.now();
|
|
@@ -27,7 +49,7 @@ export function createCodeRuntime(toolConfiguration = {}, extras = {}) {
|
|
|
27
49
|
stores.set(sessionId, stored);
|
|
28
50
|
const nestedCalls = [];
|
|
29
51
|
const tools = Object.create(null);
|
|
30
|
-
for (const
|
|
52
|
+
for (const { handler, name } of configuredTools) {
|
|
31
53
|
tools[name] = async (input) => {
|
|
32
54
|
const callId = `${parentCallId}/code-${nextCallId++}`;
|
|
33
55
|
const toolStartedAt = performance.now();
|
|
@@ -35,14 +57,13 @@ export function createCodeRuntime(toolConfiguration = {}, extras = {}) {
|
|
|
35
57
|
0,
|
|
36
58
|
Math.round((toolStartedAt - startedAt) * 1_000_000),
|
|
37
59
|
);
|
|
60
|
+
const recordedInput = clone(input) ?? null;
|
|
38
61
|
try {
|
|
39
|
-
const result = await
|
|
62
|
+
const result = await handler(input, { sessionId, parentCallId, callId });
|
|
40
63
|
nestedCalls.push({
|
|
41
64
|
call_id: callId,
|
|
42
65
|
name,
|
|
43
|
-
|
|
44
|
-
// Keep zero-argument calls wire-complete for Rust's typed event.
|
|
45
|
-
input: clone(input) ?? null,
|
|
66
|
+
input: recordedInput,
|
|
46
67
|
output: outputBody(result),
|
|
47
68
|
success: true,
|
|
48
69
|
started_after_ns: startedAfterNs,
|
|
@@ -53,7 +74,7 @@ export function createCodeRuntime(toolConfiguration = {}, extras = {}) {
|
|
|
53
74
|
nestedCalls.push({
|
|
54
75
|
call_id: callId,
|
|
55
76
|
name,
|
|
56
|
-
input:
|
|
77
|
+
input: recordedInput,
|
|
57
78
|
output: errorMessage(error),
|
|
58
79
|
success: false,
|
|
59
80
|
started_after_ns: startedAfterNs,
|
|
@@ -148,16 +169,27 @@ export function createCodeRuntime(toolConfiguration = {}, extras = {}) {
|
|
|
148
169
|
|
|
149
170
|
return Object.freeze({
|
|
150
171
|
executeCode,
|
|
151
|
-
|
|
172
|
+
executeTool,
|
|
173
|
+
toolDefinitions: () => encodedDefinitions,
|
|
152
174
|
reset() {
|
|
153
175
|
stores.clear();
|
|
154
176
|
},
|
|
155
177
|
});
|
|
156
178
|
}
|
|
157
179
|
|
|
180
|
+
function encodeToolOutput(output, success, codeModeValue) {
|
|
181
|
+
return JSON.stringify({
|
|
182
|
+
output,
|
|
183
|
+
success,
|
|
184
|
+
code_mode_value: codeModeValue,
|
|
185
|
+
metadata: null,
|
|
186
|
+
process_trace: null,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
158
190
|
function outputBody(value) {
|
|
159
191
|
if (Array.isArray(value) && value.every((item) => item?.type === "input_text" || item?.type === "input_image")) {
|
|
160
|
-
return value;
|
|
192
|
+
return clone(value);
|
|
161
193
|
}
|
|
162
194
|
return stringify(value);
|
|
163
195
|
}
|
|
@@ -177,6 +209,20 @@ function clone(value) {
|
|
|
177
209
|
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
178
210
|
}
|
|
179
211
|
|
|
212
|
+
function jsonSnapshot(value, label) {
|
|
213
|
+
try {
|
|
214
|
+
return JSON.parse(JSON.stringify(value));
|
|
215
|
+
} catch (error) {
|
|
216
|
+
throw new TypeError(`${label} must be JSON-serializable`, { cause: error });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function deepFreeze(value) {
|
|
221
|
+
if (!value || typeof value !== "object" || Object.isFrozen(value)) return value;
|
|
222
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
223
|
+
return Object.freeze(value);
|
|
224
|
+
}
|
|
225
|
+
|
|
180
226
|
function errorMessage(error) {
|
|
181
227
|
if (error && (error.stack || error.message)) return error.stack || error.message;
|
|
182
228
|
return String(error);
|
package/types.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type Thinking = "none" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
2
2
|
export type ReasoningMode = "standard" | "pro";
|
|
3
|
+
export type Model = "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-5.6-luna";
|
|
3
4
|
|
|
4
5
|
export type PromptItem =
|
|
5
6
|
| { type: "text"; text: string }
|
|
@@ -18,13 +19,29 @@ export type AgentEvent = {
|
|
|
18
19
|
|
|
19
20
|
export type AgentOptions = {
|
|
20
21
|
instructions?: string | undefined;
|
|
22
|
+
model?: Model | undefined;
|
|
21
23
|
reasoningMode?: ReasoningMode | undefined;
|
|
22
24
|
fastMode?: boolean | undefined;
|
|
23
25
|
sessionId?: string | undefined;
|
|
24
26
|
thinking?: Thinking | undefined;
|
|
27
|
+
workspace?: string | undefined;
|
|
25
28
|
resume?: SessionSnapshot | undefined;
|
|
26
29
|
};
|
|
27
30
|
|
|
31
|
+
export type EstimatedUsdCost = Readonly<{
|
|
32
|
+
usd: string;
|
|
33
|
+
input_usd: string;
|
|
34
|
+
cached_input_usd: string;
|
|
35
|
+
cache_write_input_usd: string;
|
|
36
|
+
output_usd: string;
|
|
37
|
+
service_tier: "standard" | "priority";
|
|
38
|
+
}>;
|
|
39
|
+
|
|
40
|
+
export type CostStatus =
|
|
41
|
+
| "estimated_from_usage"
|
|
42
|
+
| "usage_not_reported"
|
|
43
|
+
| "other";
|
|
44
|
+
|
|
28
45
|
export type SessionSnapshot = Readonly<{
|
|
29
46
|
version: number;
|
|
30
47
|
model: string;
|
|
@@ -36,7 +53,18 @@ export type SessionSnapshot = Readonly<{
|
|
|
36
53
|
history: readonly Record<string, unknown>[];
|
|
37
54
|
}>;
|
|
38
55
|
|
|
39
|
-
export type
|
|
56
|
+
export type TurnUsage = Readonly<{
|
|
57
|
+
input_tokens: number;
|
|
58
|
+
cached_input_tokens: number;
|
|
59
|
+
cache_write_input_tokens: number;
|
|
60
|
+
output_tokens: number;
|
|
61
|
+
reasoning_output_tokens: number;
|
|
62
|
+
total_tokens: number;
|
|
63
|
+
estimated_cost: EstimatedUsdCost | null;
|
|
64
|
+
cost_status: CostStatus;
|
|
65
|
+
}>;
|
|
66
|
+
|
|
67
|
+
export type ForkOptions = { at?: TurnResult | undefined };
|
|
40
68
|
export type WatchEventsOptions = { includeAllSessions?: boolean | undefined };
|
|
41
69
|
|
|
42
70
|
export type EventWatcher = Readonly<{
|
|
@@ -50,9 +78,11 @@ export type AgentActions = {
|
|
|
50
78
|
watch(options?: WatchEventsOptions): EventWatcher;
|
|
51
79
|
};
|
|
52
80
|
session: {
|
|
81
|
+
compact(): Promise<void>;
|
|
53
82
|
fork(options?: ForkOptions): Promise<DefaultAgent>;
|
|
54
83
|
setFastMode(enabled: boolean): Promise<void>;
|
|
55
84
|
setThinking(thinking: Thinking): Promise<void>;
|
|
85
|
+
shutdown(): Promise<void>;
|
|
56
86
|
spawn(): Promise<DefaultAgent>;
|
|
57
87
|
};
|
|
58
88
|
turn: {
|
|
@@ -69,6 +99,7 @@ export type Agent<extended extends object = {}> = {
|
|
|
69
99
|
extend<const extension extends object>(
|
|
70
100
|
decorator: (agent: Agent<extended>) => extension,
|
|
71
101
|
): Agent<extended & extension>;
|
|
102
|
+
/** Releases this JavaScript/WASM handle without joining unfinished turns. */
|
|
72
103
|
dispose(): void;
|
|
73
104
|
} & extended;
|
|
74
105
|
|
|
@@ -76,13 +107,19 @@ export type DefaultAgent = Agent<AgentActions>;
|
|
|
76
107
|
|
|
77
108
|
export type Turn<agent extends Agent<object> = Agent<object>> = Readonly<{
|
|
78
109
|
readonly agent: agent;
|
|
79
|
-
result(): Promise<
|
|
80
|
-
snapshot(): SessionSnapshot;
|
|
110
|
+
result(): Promise<TurnResult>;
|
|
81
111
|
steer(options: { input: PromptInput }): Promise<void>;
|
|
82
112
|
cancel(): Promise<void>;
|
|
113
|
+
/** Releases this handle without cancelling its accepted turn. */
|
|
83
114
|
dispose(): void;
|
|
84
115
|
}>;
|
|
85
116
|
|
|
117
|
+
export type TurnResult = Readonly<{
|
|
118
|
+
finalMessage: string;
|
|
119
|
+
snapshot: SessionSnapshot;
|
|
120
|
+
usage: TurnUsage;
|
|
121
|
+
}>;
|
|
122
|
+
|
|
86
123
|
export type ToolContext = {
|
|
87
124
|
callId: string;
|
|
88
125
|
parentCallId: string;
|
package/wasm.d.mts
ADDED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { readFile, stat } from "node:fs/promises";
|
|
3
|
-
|
|
4
|
-
const root = new URL("../", import.meta.url);
|
|
5
|
-
const packageJson = JSON.parse(await readFile(new URL("package.json", root), "utf8"));
|
|
6
|
-
|
|
7
|
-
assert.equal(packageJson.name, "nanocodex");
|
|
8
|
-
assert.equal(packageJson.type, "module");
|
|
9
|
-
assert.equal(packageJson.publishConfig?.access, "public");
|
|
10
|
-
assert.equal(packageJson.exports?.["./browser"]?.import, "./browser/index.mjs");
|
|
11
|
-
assert.equal(packageJson.exports?.["./node"]?.import, "./node/index.mjs");
|
|
12
|
-
|
|
13
|
-
const requiredFiles = [
|
|
14
|
-
"browser/index.mjs",
|
|
15
|
-
"browser/index.d.mts",
|
|
16
|
-
"node/index.mjs",
|
|
17
|
-
"node/index.d.mts",
|
|
18
|
-
"pkg-web/nanocodex.js",
|
|
19
|
-
"pkg-web/nanocodex.d.ts",
|
|
20
|
-
"pkg-web/nanocodex_bg.wasm",
|
|
21
|
-
"pkg-node/nanocodex.js",
|
|
22
|
-
"pkg-node/nanocodex.d.ts",
|
|
23
|
-
"pkg-node/nanocodex_bg.wasm",
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
for (const file of requiredFiles) {
|
|
27
|
-
const metadata = await stat(new URL(file, root));
|
|
28
|
-
assert(metadata.isFile(), `${file} must be a file`);
|
|
29
|
-
assert(metadata.size > 0, `${file} must not be empty`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
for (const target of ["web", "node"]) {
|
|
33
|
-
const wasm = await readFile(new URL(`pkg-${target}/nanocodex_bg.wasm`, root));
|
|
34
|
-
assert(wasm.byteLength > 100_000, `pkg-${target} WASM is unexpectedly small`);
|
|
35
|
-
assert.deepEqual([...wasm.subarray(0, 4)], [0x00, 0x61, 0x73, 0x6d]);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
console.log(`nanocodex@${packageJson.version} package artifacts are complete`);
|