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.
@@ -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
- definitions.push({
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 [name, tool] of Object.entries(toolConfiguration)) {
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 tool.handler(input, { sessionId, parentCallId, callId });
62
+ const result = await handler(input, { sessionId, parentCallId, callId });
40
63
  nestedCalls.push({
41
64
  call_id: callId,
42
65
  name,
43
- // JSON.stringify omits object fields whose value is undefined.
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: clone(input) ?? null,
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
- toolDefinitions: () => JSON.stringify(definitions),
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 ForkOptions = { at?: Turn | undefined };
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<string>;
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
@@ -0,0 +1,4 @@
1
+ /** Precompiled web WASM module for runtimes that import binary modules. */
2
+ declare const module: WebAssembly.Module;
3
+
4
+ export default module;
@@ -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`);