nanocodex 0.6.6 → 0.6.7
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 +297 -6
- package/THIRD-PARTY-LICENSES +23 -0
- package/browser/Agent.d.mts +5 -4
- package/browser/Agent.mjs +4 -0
- package/browser/Claude.d.mts +4 -0
- package/browser/Claude.mjs +11 -0
- package/browser/InlineAgent.mjs +19 -1
- package/browser/host.d.mts +54 -1
- package/browser/host.mjs +65 -16
- package/browser/hostManagedWebSocket.mjs +11 -2
- package/browser/index.d.mts +3 -10
- package/browser/index.mjs +2 -5
- package/browser/socket-observations.mjs +203 -0
- package/cloud/Decorator.d.mts +2 -2
- package/cloud/Decorator.mjs +1 -0
- package/cloud/Dialog.d.mts +13 -0
- package/cloud/Dialog.mjs +52 -6
- package/cloud/Transport.d.mts +0 -11
- package/cloud/Transport.mjs +1 -386
- package/cloud/actions/account.d.mts +12 -0
- package/cloud/actions/account.mjs +11 -0
- package/cloud/actions/agent.mjs +3 -0
- package/cloud/actions/connection.d.mts +3 -1
- package/cloud/actions/connection.mjs +19 -4
- package/cloud/index.d.mts +1 -1
- package/cloud/index.mjs +1 -1
- package/cloud/internal.mjs +19 -4
- package/cloud/mercator.mjs +1 -1
- package/cloud/mock.d.mts +17 -0
- package/cloud/mock.mjs +392 -0
- package/cloud/types.d.mts +3 -1
- package/cloudflare/Agent.d.mts +2 -0
- package/cloudflare/Agent.mjs +88 -2
- package/cloudflare/agent-settings.d.mts +1 -1
- package/cloudflare/agent-settings.mjs +9 -0
- package/cloudflare/egress.mjs +9 -6
- package/cloudflare/gateway-responses.d.mts +1 -0
- package/cloudflare/gateway-responses.mjs +2 -2
- package/cloudflare/managed-auth.d.mts +4 -4
- package/cloudflare/managed-auth.mjs +14 -2
- package/cloudflare/managed-live.d.mts +6 -0
- package/cloudflare/managed-live.mjs +56 -1
- package/cloudflare/workers-ai-responses.d.mts +2 -1
- package/cloudflare/workers-ai-responses.mjs +80 -6
- package/host/Agent.d.mts +6 -2
- package/host/Claude.d.mts +3 -0
- package/host/Claude.mjs +2 -0
- package/host/index.d.mts +6 -10
- package/host/index.mjs +2 -5
- package/index.d.mts +11 -10
- package/index.mjs +0 -5
- package/internal.mjs +28 -3
- package/managed/Agent.d.mts +6 -2
- package/managed/Agent.mjs +39 -5
- package/managed/ManagedError.d.mts +3 -0
- package/node/Agent.d.mts +6 -2
- package/node/Agent.mjs +14 -1
- package/node/Claude.d.mts +4 -0
- package/node/Claude.mjs +24 -0
- package/node/host.mjs +18 -9
- package/node/index.d.mts +6 -10
- package/node/index.mjs +2 -5
- package/package.json +41 -9
- package/pkg-node/nanocodex.d.ts +109 -0
- package/pkg-node/nanocodex.js +818 -51
- package/pkg-node/nanocodex_bg.wasm.d.ts +47 -3
- package/pkg-web/.nanocodex-bindgen-stamp +3 -3
- package/pkg-web/nanocodex-build.json +1 -1
- package/pkg-web/nanocodex.d.ts +156 -3
- package/pkg-web/nanocodex.js +813 -51
- package/pkg-web/nanocodex_bg.js +813 -51
- package/pkg-web/nanocodex_bg.wasm +0 -0
- package/pkg-web/nanocodex_bg.wasm.d.ts +47 -3
- package/pkg-web/nanocodex_worker.js +1 -1
- package/runtime/claude-host.mjs +308 -0
- package/runtime/claude-subscription.mjs +79 -0
- package/runtime/claude.d.mts +112 -0
- package/runtime/claude.mjs +221 -0
- package/runtime/code-effect-identity.mjs +123 -0
- package/runtime/harnesses.mjs +51 -0
- package/runtime/mcp-payment.mjs +8 -0
- package/runtime/mcp-runtime.d.mts +10 -0
- package/runtime/mcp-runtime.mjs +242 -57
- package/runtime/mercator.mjs +2 -0
- package/runtime/quickjs-evaluator.mjs +23 -11
- package/runtime/response-controls.mjs +1 -6
- package/runtime/responses-http.mjs +4 -2
- package/runtime/retry-after.d.mts +10 -0
- package/runtime/retry-after.mjs +86 -0
- package/runtime/steer-receipt.mjs +8 -1
- package/runtime/subagent-routing.d.mts +4 -2
- package/runtime/subagent-routing.mjs +7 -2
- package/runtime/subagents.d.mts +8 -3
- package/services/account.d.mts +76 -0
- package/services/account.mjs +151 -0
- package/services/hosted.mjs +72 -0
- package/services/index.d.mts +95 -0
- package/services/index.mjs +192 -0
- package/services/scope.mjs +36 -0
- package/{runtime/tempo-provider.d.mts → tempo/index.d.mts} +20 -1
- package/{runtime/tempo-provider.mjs → tempo/index.mjs} +39 -3
- package/tools/browser/browserShell.mjs +18 -8
- package/tools/browser/python.worker.mjs +17 -1
- package/tools/browser/threadGit.mjs +12 -2
- package/tools/environment.d.mts +8 -0
- package/tools/environment.mjs +10 -0
- package/tools/index.d.mts +6 -3
- package/types.d.mts +104 -4
- package/worker/Claude.d.mts +4 -0
- package/worker/Claude.mjs +11 -0
- package/worker/ClaudeSubscription.d.mts +24 -0
- package/worker/ClaudeSubscription.mjs +9 -0
- package/worker/index.d.mts +9 -0
- package/worker/index.mjs +4 -0
package/browser/host.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { createCodeEffectIdentity } from "../runtime/code-effect-identity.mjs";
|
|
1
2
|
import { createBeforeCompaction } from "../runtime/before-compaction.mjs";
|
|
2
3
|
import { createResponsesHttp, responsesHttpHeaders } from "../runtime/responses-http.mjs";
|
|
3
|
-
import { createCodeRuntime, toolResult } from "../runtime/code-runtime.mjs";
|
|
4
|
+
import { createCodeRuntime, toolResult, traceToolInvocation } from "../runtime/code-runtime.mjs";
|
|
4
5
|
import {
|
|
5
6
|
toolRouterBrand,
|
|
6
7
|
toolRouterRuntime,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
import { utf8ByteLength } from "../runtime/utf8.mjs";
|
|
10
11
|
import { createWorkerEvaluator } from "../runtime/worker-evaluator.mjs";
|
|
11
12
|
import { openHostManagedWebSocket } from "./hostManagedWebSocket.mjs";
|
|
13
|
+
import { createSocketObservations } from "./socket-observations.mjs";
|
|
12
14
|
|
|
13
15
|
const DEFAULT_MAX_QUEUED_MESSAGES = 4_096;
|
|
14
16
|
const DEFAULT_MAX_QUEUED_BYTES = 32 * 1024 * 1024;
|
|
@@ -19,14 +21,24 @@ const MPP_CLIENT_PROTOCOL_ERROR_CLOSE_CODE = 3008;
|
|
|
19
21
|
const WEBSOCKET_OPEN = 1;
|
|
20
22
|
|
|
21
23
|
export function createBrowserHost(options = {}) {
|
|
24
|
+
const internalRuntime = options[Symbol.for("nanocodex.browser.internalRuntime")];
|
|
25
|
+
if (internalRuntime !== undefined
|
|
26
|
+
&& (!internalRuntime || typeof internalRuntime !== "object" || Array.isArray(internalRuntime))) {
|
|
27
|
+
throw new TypeError("browser host internal runtime options must be an object");
|
|
28
|
+
}
|
|
29
|
+
const traceTool = internalRuntime?.traceTool;
|
|
30
|
+
if (traceTool !== undefined && typeof traceTool !== "function") {
|
|
31
|
+
throw new TypeError("browser host tool tracing hook must be a function");
|
|
32
|
+
}
|
|
22
33
|
const onSocketTiming = options.onSocketTiming;
|
|
23
34
|
if (onSocketTiming !== undefined && typeof onSocketTiming !== "function") {
|
|
24
35
|
throw new TypeError("host socket timing hook must be a function");
|
|
25
36
|
}
|
|
37
|
+
const socketObservations = createSocketObservations(options.onSocketEvent);
|
|
26
38
|
const preservation = createBeforeCompaction(options.beforeCompaction);
|
|
27
39
|
const toolMode = options.toolMode ?? "code";
|
|
28
|
-
if (toolMode !== "code" && toolMode !== "direct") {
|
|
29
|
-
throw new TypeError("toolMode must be code or direct");
|
|
40
|
+
if (toolMode !== "code" && toolMode !== "code-only" && toolMode !== "direct") {
|
|
41
|
+
throw new TypeError("toolMode must be code, code-only or direct");
|
|
30
42
|
}
|
|
31
43
|
const toolsRouter = options.tools?.[toolRouterBrand]
|
|
32
44
|
? options.tools[toolRouterRuntime]
|
|
@@ -38,7 +50,7 @@ export function createBrowserHost(options = {}) {
|
|
|
38
50
|
if (toolsMcp && options.mcp) {
|
|
39
51
|
throw new TypeError("MCP is already configured in Tools");
|
|
40
52
|
}
|
|
41
|
-
if ((toolsMcp || options.mcp) && toolMode
|
|
53
|
+
if ((toolsMcp || options.mcp) && toolMode === "direct") {
|
|
42
54
|
throw new TypeError("remote MCP requires Code Mode");
|
|
43
55
|
}
|
|
44
56
|
const toolsLifecycle = options.tools?.[toolRuntimeLifecycle];
|
|
@@ -76,8 +88,12 @@ export function createBrowserHost(options = {}) {
|
|
|
76
88
|
: () => Promise.reject(new Error(
|
|
77
89
|
"browser Code Mode requires a child Worker or an explicit codeEvaluator",
|
|
78
90
|
)));
|
|
91
|
+
const effectIdentity = createCodeEffectIdentity(options.codeEffectJournal);
|
|
79
92
|
const code = createCodeRuntime(options.tools, {
|
|
93
|
+
traceTool,
|
|
80
94
|
evaluate: codeEvaluator,
|
|
95
|
+
effectJournal: options.codeEffectJournal,
|
|
96
|
+
effectIdentity: options.codeEffectJournal ? effectIdentity.resolve : undefined,
|
|
81
97
|
subagentSessions: options.subagentSessions,
|
|
82
98
|
});
|
|
83
99
|
const toolProviders = options.toolProviders ?? [];
|
|
@@ -192,12 +208,22 @@ export function createBrowserHost(options = {}) {
|
|
|
192
208
|
if (preconnected !== undefined) await closePreconnected();
|
|
193
209
|
ownership = openOwned(() => createWebSocket(endpoint, sessionId, request));
|
|
194
210
|
}
|
|
195
|
-
|
|
196
|
-
const
|
|
211
|
+
let connection;
|
|
212
|
+
const observation = socketObservations?.connect(sessionId, () => ({
|
|
213
|
+
queued_message_count: connection?.queue.length ?? 0,
|
|
214
|
+
socket_delivered_message_count: connection?.timing?.metrics.delivered_message_count ?? 0,
|
|
215
|
+
socket_queue_residence_max_ms: connection?.timing?.metrics.queue_residence_max_ms ?? 0,
|
|
216
|
+
buffered_send_bytes: connection?.socket.bufferedAmount ?? 0,
|
|
217
|
+
}));
|
|
218
|
+
let normalized;
|
|
219
|
+
try { normalized = normalizeWebSocketConnection(await ownership.promise); }
|
|
220
|
+
catch (error) { observation?.error(); observation?.close(undefined, false); throw error; }
|
|
221
|
+
const { socket, ...handshake } = normalized;
|
|
197
222
|
return new Promise((resolve, reject) => {
|
|
198
223
|
let settled = false;
|
|
199
|
-
|
|
224
|
+
connection = {
|
|
200
225
|
socket,
|
|
226
|
+
observation,
|
|
201
227
|
timing: socketTiming(),
|
|
202
228
|
queue: [],
|
|
203
229
|
queuedBytes: 0,
|
|
@@ -209,6 +235,7 @@ export function createBrowserHost(options = {}) {
|
|
|
209
235
|
if (settled) return;
|
|
210
236
|
settled = true;
|
|
211
237
|
connectingConnections.delete(connection);
|
|
238
|
+
observation?.close(undefined, false);
|
|
212
239
|
finishSocketTiming(connection);
|
|
213
240
|
reject(error);
|
|
214
241
|
};
|
|
@@ -228,6 +255,7 @@ export function createBrowserHost(options = {}) {
|
|
|
228
255
|
delete connection.reject;
|
|
229
256
|
const handle = nextHandle++;
|
|
230
257
|
connections.set(handle, connection);
|
|
258
|
+
observation?.connected(handshake);
|
|
231
259
|
resolve(JSON.stringify({
|
|
232
260
|
handle,
|
|
233
261
|
status: handshake.status ?? 101,
|
|
@@ -244,6 +272,7 @@ export function createBrowserHost(options = {}) {
|
|
|
244
272
|
: { kind: "binary" });
|
|
245
273
|
});
|
|
246
274
|
socket.addEventListener("close", (event) => {
|
|
275
|
+
observation?.close(event, connection.intentionallyClosed);
|
|
247
276
|
connection.wakeSend?.();
|
|
248
277
|
if (!settled) {
|
|
249
278
|
rejectConnection(new Error(`WebSocket closed during connection with code ${event.code}`));
|
|
@@ -252,6 +281,7 @@ export function createBrowserHost(options = {}) {
|
|
|
252
281
|
}
|
|
253
282
|
});
|
|
254
283
|
socket.addEventListener("error", () => {
|
|
284
|
+
observation?.error();
|
|
255
285
|
if (!settled) {
|
|
256
286
|
rejectConnection(new Error("WebSocket connection failed"));
|
|
257
287
|
} else {
|
|
@@ -329,6 +359,8 @@ export function createBrowserHost(options = {}) {
|
|
|
329
359
|
error: "concurrent WebSocket sends are unsupported" });
|
|
330
360
|
}
|
|
331
361
|
connection.sending = true;
|
|
362
|
+
let sent = false;
|
|
363
|
+
connection.observation?.sendStarted();
|
|
332
364
|
try {
|
|
333
365
|
if (connection.managed) {
|
|
334
366
|
connection.socket.send(JSON.stringify({ mpp: "message", data: message }));
|
|
@@ -363,6 +395,8 @@ export function createBrowserHost(options = {}) {
|
|
|
363
395
|
}
|
|
364
396
|
if (closed()) return closedResult();
|
|
365
397
|
connection.socket.send(message);
|
|
398
|
+
sent = true;
|
|
399
|
+
connection.observation?.sent();
|
|
366
400
|
return JSON.stringify({ ok: true });
|
|
367
401
|
} catch (error) {
|
|
368
402
|
return JSON.stringify({
|
|
@@ -371,6 +405,8 @@ export function createBrowserHost(options = {}) {
|
|
|
371
405
|
error: error instanceof Error ? error.message : String(error),
|
|
372
406
|
});
|
|
373
407
|
} finally {
|
|
408
|
+
// A send that did not reach socket.send must not leave a waiting timer.
|
|
409
|
+
if (!sent) connection.observation?.sendFailed();
|
|
374
410
|
connection.sending = false;
|
|
375
411
|
}
|
|
376
412
|
}
|
|
@@ -406,6 +442,7 @@ export function createBrowserHost(options = {}) {
|
|
|
406
442
|
if (!connection) return;
|
|
407
443
|
connections.delete(handle);
|
|
408
444
|
connection.intentionallyClosed = true;
|
|
445
|
+
connection.observation?.close(undefined, true);
|
|
409
446
|
connection.wakeSend?.();
|
|
410
447
|
connection.waiter?.({ kind: "closed", detail: "by the WASM runtime" });
|
|
411
448
|
finishSocketTiming(connection);
|
|
@@ -422,11 +459,13 @@ export function createBrowserHost(options = {}) {
|
|
|
422
459
|
if (timing && dataMessage) {
|
|
423
460
|
timing.metrics.message_count += 1;
|
|
424
461
|
// Only the exact metadata event can produce diagnostics; deltas stay opaque.
|
|
425
|
-
|
|
462
|
+
connection.observation?.message(message.kind === "text" ? message.text : undefined);
|
|
463
|
+
if (message.kind === "text" && (timing.provider.size < 32 || connection.observation)) {
|
|
426
464
|
const metadata = providerSocketTiming(message.text);
|
|
427
465
|
if (metadata) {
|
|
466
|
+
connection.observation?.provider(metadata);
|
|
428
467
|
const key = metadata.response_id ?? timing.provider.size;
|
|
429
|
-
if (!timing.provider.has(key)) timing.provider.set(key, metadata);
|
|
468
|
+
if (timing.provider.size < 32 && !timing.provider.has(key)) timing.provider.set(key, metadata);
|
|
430
469
|
}
|
|
431
470
|
}
|
|
432
471
|
}
|
|
@@ -461,7 +500,7 @@ export function createBrowserHost(options = {}) {
|
|
|
461
500
|
|
|
462
501
|
// No counters, timestamps, metadata parsing or observations without the internal hook.
|
|
463
502
|
function socketTiming() {
|
|
464
|
-
return onSocketTiming === undefined ? undefined : {
|
|
503
|
+
return onSocketTiming === undefined && socketObservations === undefined ? undefined : {
|
|
465
504
|
metrics: {
|
|
466
505
|
message_count: 0,
|
|
467
506
|
delivered_message_count: 0,
|
|
@@ -479,6 +518,7 @@ export function createBrowserHost(options = {}) {
|
|
|
479
518
|
// Finalize owned consumption, not the remote close event: buffered frames may
|
|
480
519
|
// still be drained after that event. Clear first for reentrant close/dispose.
|
|
481
520
|
connection.timing = undefined;
|
|
521
|
+
if (onSocketTiming === undefined) return;
|
|
482
522
|
const metrics = timing.metrics;
|
|
483
523
|
try {
|
|
484
524
|
const result = onSocketTiming({
|
|
@@ -513,7 +553,7 @@ export function createBrowserHost(options = {}) {
|
|
|
513
553
|
}
|
|
514
554
|
for (const handle of [...connections.keys()]) cleanup(() => close(handle));
|
|
515
555
|
cleanup(() => closePreconnected(disposalError));
|
|
516
|
-
cleanup(() => code.reset());
|
|
556
|
+
cleanup(() => { effectIdentity.reset(); return code.reset(); });
|
|
517
557
|
cleanup(() => mcpInstalled?.then(() => {}));
|
|
518
558
|
cleanup(() => toolsLifecycle?.close());
|
|
519
559
|
cleanup(() => options.onDispose?.());
|
|
@@ -612,10 +652,19 @@ export function createBrowserHost(options = {}) {
|
|
|
612
652
|
next,
|
|
613
653
|
close,
|
|
614
654
|
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
615
|
-
executeCode: code.executeCodeObserved
|
|
616
|
-
|
|
655
|
+
executeCode: traceTool === undefined ? code.executeCodeObserved
|
|
656
|
+
: (source, sessionId = "default", callId = "exec", model = "unknown", turnId, localDefinitions, executeLocalTool) =>
|
|
657
|
+
traceToolInvocation(traceTool, "exec", {
|
|
658
|
+
sessionId, callId, ...(turnId == null ? {} : { turnId }),
|
|
659
|
+
}, () => code.executeCodeObserved(source, sessionId, callId, model, turnId, localDefinitions, executeLocalTool)),
|
|
660
|
+
waitCode: traceTool === undefined ? code.waitCodeObserved
|
|
661
|
+
: (input, sessionId = "default", callId = "wait") =>
|
|
662
|
+
traceToolInvocation(traceTool, "wait", { sessionId, callId },
|
|
663
|
+
() => code.waitCodeObserved(input, sessionId, callId)),
|
|
617
664
|
beginCodeTurn: code.beginTurn,
|
|
618
665
|
cancelCodeTurn: code.cancelTurn,
|
|
666
|
+
preemptCode: code.preempt,
|
|
667
|
+
preemptCodeTurn: code.preemptTurn,
|
|
619
668
|
nextCodeUpdate: code.nextCodeUpdate,
|
|
620
669
|
executeTool: code.executeTool,
|
|
621
670
|
routeSubagent: (request) => {
|
|
@@ -653,9 +702,9 @@ export function createBrowserHost(options = {}) {
|
|
|
653
702
|
},
|
|
654
703
|
toolMode: () => toolMode,
|
|
655
704
|
toolDefinitions: code.toolDefinitions,
|
|
656
|
-
releaseSession: code.releaseSession,
|
|
657
|
-
emitEvent: onEvent,
|
|
658
|
-
reset: code.reset,
|
|
705
|
+
releaseSession: (sessionId) => { effectIdentity.release(sessionId); socketObservations?.release(sessionId); return code.releaseSession(sessionId); },
|
|
706
|
+
emitEvent: (event, ...args) => { code.observeEvent(event); effectIdentity.observe(event); socketObservations?.runtime(event); return onEvent(event, ...args); },
|
|
707
|
+
reset: () => { effectIdentity.reset(); return code.reset(); },
|
|
659
708
|
dispose,
|
|
660
709
|
});
|
|
661
710
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { retryAfterAdvice } from "../runtime/retry-after.mjs";
|
|
2
|
+
|
|
1
3
|
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
2
4
|
|
|
3
5
|
/** Open Nanocodex's same-origin Responses proxy and consume its setup frame. */
|
|
@@ -74,13 +76,20 @@ function waitForProxyHandshake(socket, timeoutMs) {
|
|
|
74
76
|
&& message.status <= 599) {
|
|
75
77
|
const status = message.status;
|
|
76
78
|
const body = typeof message.error === "string" ? message.error : `HTTP ${status}`;
|
|
77
|
-
const
|
|
79
|
+
const advice = retryAfterAdvice(
|
|
80
|
+
typeof message.retryAfter === "number" ? String(message.retryAfter) : message.retryAfter,
|
|
81
|
+
);
|
|
82
|
+
// The proxy may already have captured advice before draining its rejection body.
|
|
83
|
+
if (Number.isSafeInteger(message.retry_after_deadline_ms) && message.retry_after_deadline_ms >= 0) {
|
|
84
|
+
advice.retry_after_deadline_ms = message.retry_after_deadline_ms;
|
|
85
|
+
}
|
|
78
86
|
fail(Object.assign(
|
|
79
87
|
new Error(`Agent connection rejected with HTTP ${status}: ${body}`),
|
|
80
88
|
{
|
|
81
89
|
status,
|
|
82
90
|
body,
|
|
83
|
-
...
|
|
91
|
+
...advice,
|
|
92
|
+
...(advice.retry_after !== undefined ? { retryAfter: advice.retry_after } : {}),
|
|
84
93
|
},
|
|
85
94
|
));
|
|
86
95
|
return;
|
package/browser/index.d.mts
CHANGED
|
@@ -4,16 +4,6 @@ export {
|
|
|
4
4
|
subscriptionRevision,
|
|
5
5
|
} from "../runtime/subscription-store.mjs";
|
|
6
6
|
export { createQuickJsEvaluator } from "../runtime/quickjs-evaluator.mjs";
|
|
7
|
-
export {
|
|
8
|
-
createTempoProvider,
|
|
9
|
-
createTempoProviderFromAccounts,
|
|
10
|
-
DEFAULT_MERCATOR_MCP_URL,
|
|
11
|
-
} from "../runtime/tempo-provider.mjs";
|
|
12
|
-
export type {
|
|
13
|
-
AccountsTempoProviderOptions,
|
|
14
|
-
AccountsWallet,
|
|
15
|
-
TempoProvider,
|
|
16
|
-
} from "../runtime/tempo-provider.mjs";
|
|
17
7
|
export type {
|
|
18
8
|
AgentEvent,
|
|
19
9
|
AgentLifecycle,
|
|
@@ -46,6 +36,7 @@ export type {
|
|
|
46
36
|
TurnResult,
|
|
47
37
|
TurnUsage,
|
|
48
38
|
McpPayment,
|
|
39
|
+
PaidMcpPayment,
|
|
49
40
|
McpServer,
|
|
50
41
|
McpServers,
|
|
51
42
|
MemoryChatGptSubscriptionStore,
|
|
@@ -79,3 +70,5 @@ export type {
|
|
|
79
70
|
BrowserWebSocketConnection,
|
|
80
71
|
BrowserWebSocketRequest,
|
|
81
72
|
} from "./host.mjs";
|
|
73
|
+
|
|
74
|
+
export * as Claude from "./Claude.mjs";
|
package/browser/index.mjs
CHANGED
|
@@ -4,11 +4,6 @@ export {
|
|
|
4
4
|
subscriptionRevision,
|
|
5
5
|
} from "../runtime/subscription-store.mjs";
|
|
6
6
|
export { createQuickJsEvaluator } from "../runtime/quickjs-evaluator.mjs";
|
|
7
|
-
export {
|
|
8
|
-
createTempoProvider,
|
|
9
|
-
createTempoProviderFromAccounts,
|
|
10
|
-
DEFAULT_MERCATOR_MCP_URL,
|
|
11
|
-
} from "../runtime/tempo-provider.mjs";
|
|
12
7
|
export * as Agent from "./Agent.mjs";
|
|
13
8
|
export * as ChatGptSubscription from "./ChatGptSubscription.mjs";
|
|
14
9
|
export * as Subagents from "../runtime/subagents.mjs";
|
|
@@ -21,3 +16,5 @@ export {
|
|
|
21
16
|
defaultHostManagedWebSocketUrl,
|
|
22
17
|
openHostManagedWebSocket,
|
|
23
18
|
} from "./hostManagedWebSocket.mjs";
|
|
19
|
+
|
|
20
|
+
export * as Claude from "./Claude.mjs";
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// Passive, content-free observations. Timers report silence; they never cancel
|
|
2
|
+
// inference. Each socket retains only one active request and 32 response links.
|
|
3
|
+
export function createSocketObservations(observe) {
|
|
4
|
+
if (observe === undefined) return;
|
|
5
|
+
if (typeof observe !== "function") throw new TypeError("host socket event hook must be a function");
|
|
6
|
+
const turns = new Map();
|
|
7
|
+
const calls = new Map();
|
|
8
|
+
const sockets = new Set();
|
|
9
|
+
const emit = (value) => {
|
|
10
|
+
try {
|
|
11
|
+
const result = observe(value);
|
|
12
|
+
if (result?.then) void Promise.resolve(result).catch(() => {});
|
|
13
|
+
} catch { /* Observation cannot change transport behavior. */ }
|
|
14
|
+
};
|
|
15
|
+
const retain = (map, key, value, limit = 128) => {
|
|
16
|
+
if (!map.has(key) && map.size >= limit) map.delete(map.keys().next().value);
|
|
17
|
+
map.set(key, value);
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
runtime(encoded) {
|
|
21
|
+
if (typeof encoded === "string" && !/"(?:input\.accepted|model\.(?:call|compaction|warmup)\.|run\.)/.test(encoded.slice(0, 512))) return;
|
|
22
|
+
let event;
|
|
23
|
+
try { event = typeof encoded === "string" ? JSON.parse(encoded) : encoded; } catch { return; }
|
|
24
|
+
const p = event?.payload;
|
|
25
|
+
if (!p) return;
|
|
26
|
+
if (event.type === "input.accepted" && typeof p.turn_id === "string" && typeof p.session_id === "string") {
|
|
27
|
+
retain(turns, p.turn_id, p.session_id);
|
|
28
|
+
}
|
|
29
|
+
const sessionId = turns.get(p.turn_id) ?? event.request_id;
|
|
30
|
+
if (event.type === "model.call.started" || event.type === "model.compaction.started" || event.type === "model.warmup.started") {
|
|
31
|
+
retain(calls, sessionId, { ...(Number.isSafeInteger(p.call_index ?? p.after_model_call_index)
|
|
32
|
+
? { model_call_index: p.call_index ?? p.after_model_call_index } : {}),
|
|
33
|
+
phase: event.type.startsWith("model.compaction.") ? "compaction" : event.type.startsWith("model.warmup.") ? "warmup" : "generation" });
|
|
34
|
+
} else if (/^model\.(call|compaction|warmup)\.(completed|failed)$/.test(event.type)) {
|
|
35
|
+
for (const socket of sockets) {
|
|
36
|
+
if (socket.sessionId === sessionId) socket.finished(p.call_index ?? p.after_model_call_index, p.response_id, event.type.endsWith(".failed") ? "failed" : "completed");
|
|
37
|
+
}
|
|
38
|
+
calls.delete(sessionId);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
release(sessionId) {
|
|
42
|
+
calls.delete(sessionId);
|
|
43
|
+
for (const [turn, session] of turns) if (session === sessionId) turns.delete(turn);
|
|
44
|
+
},
|
|
45
|
+
connect(sessionId, snapshot) {
|
|
46
|
+
const measure = () => { try { return snapshot(); } catch { return {}; } };
|
|
47
|
+
const socketId = crypto.randomUUID();
|
|
48
|
+
const began = performance.now();
|
|
49
|
+
const responses = new Map();
|
|
50
|
+
let handshake = {};
|
|
51
|
+
let active, last, timer, ordinal = 0, closed = false;
|
|
52
|
+
const record = (event, fields = {}, request = active ?? last) => emit({
|
|
53
|
+
event, socket_id: socketId, request_id: sessionId,
|
|
54
|
+
...(uuid(handshake.egressRequestId) ? { egress_request_id: handshake.egressRequestId } : {}),
|
|
55
|
+
...(identifier(handshake.requestId, "req_") || uuid(handshake.requestId) ? { provider_request_id: handshake.requestId } : {}),
|
|
56
|
+
...(request ? { socket_request_index: request.index, ...request.context,
|
|
57
|
+
...(request.responseId ? { response_id: request.responseId } : {}) } : {}),
|
|
58
|
+
...fields,
|
|
59
|
+
});
|
|
60
|
+
const watch = (event, started) => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
let after = 1_000;
|
|
63
|
+
const waiting = () => {
|
|
64
|
+
if (closed) return;
|
|
65
|
+
const now = performance.now();
|
|
66
|
+
record(event, { elapsed_ms: now - started,
|
|
67
|
+
...(active ? { received_message_count: active.messages,
|
|
68
|
+
...(active.firstMessage === undefined ? {} : { first_message_ms: active.firstMessage - active.started }),
|
|
69
|
+
...milestones(active),
|
|
70
|
+
last_message_age_ms: now - (active.lastMessage ?? active.started), ...measure() } : {}) });
|
|
71
|
+
after = Math.min(after * 2, 5_000);
|
|
72
|
+
timer = setTimeout(waiting, after);
|
|
73
|
+
};
|
|
74
|
+
timer = setTimeout(waiting, after);
|
|
75
|
+
};
|
|
76
|
+
const responseLink = (request, id) => {
|
|
77
|
+
if (!request || !identifier(id, "resp_")) return;
|
|
78
|
+
request.responseId = id;
|
|
79
|
+
retain(responses, id, request, 32);
|
|
80
|
+
};
|
|
81
|
+
const finish = (outcome) => {
|
|
82
|
+
if (!active) return;
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
record("request.finished", { outcome, elapsed_ms: performance.now() - active.started,
|
|
85
|
+
received_message_count: active.messages, ...milestones(active), ...measure() });
|
|
86
|
+
last = active;
|
|
87
|
+
active = undefined;
|
|
88
|
+
};
|
|
89
|
+
const socket = {
|
|
90
|
+
sessionId,
|
|
91
|
+
connected(value) {
|
|
92
|
+
handshake = value;
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
record("socket.opened", { elapsed_ms: performance.now() - began });
|
|
95
|
+
},
|
|
96
|
+
sendStarted() {
|
|
97
|
+
finish("superseded");
|
|
98
|
+
active = { index: ++ordinal, context: calls.get(sessionId) ?? {}, started: performance.now(), messages: 0 };
|
|
99
|
+
record("request.send_started", measure());
|
|
100
|
+
watch("request.send_waiting", active.started);
|
|
101
|
+
},
|
|
102
|
+
sent() {
|
|
103
|
+
if (!active) return;
|
|
104
|
+
const now = performance.now();
|
|
105
|
+
record("request.sent", { send_wait_ms: now - active.started, ...measure() });
|
|
106
|
+
if (active.firstMessage === undefined) active.started = now;
|
|
107
|
+
active.sentAt = now;
|
|
108
|
+
watch("request.waiting", active.started);
|
|
109
|
+
},
|
|
110
|
+
sendFailed() { finish("send_failed"); },
|
|
111
|
+
message(text) {
|
|
112
|
+
const now = performance.now();
|
|
113
|
+
if (!active) return;
|
|
114
|
+
active.messages++;
|
|
115
|
+
active.lastMessage = now;
|
|
116
|
+
// Only parse bounded envelopes while a relevant milestone is missing.
|
|
117
|
+
// A large/unrecognized envelope remains unclassified, never copied to logs.
|
|
118
|
+
const candidates = typeof text === "string" && text.length <= 16_384
|
|
119
|
+
&& (active.firstMessage === undefined || active.firstOutput === undefined
|
|
120
|
+
|| DELTA_STAGES.some(([field, , types]) => active[field] === undefined
|
|
121
|
+
&& types.some(type => text.includes(`"${type}"`))));
|
|
122
|
+
let frame;
|
|
123
|
+
if (candidates) {
|
|
124
|
+
try { frame = JSON.parse(text); } catch { /* Invalid frames remain the runtime's responsibility. */ }
|
|
125
|
+
}
|
|
126
|
+
const type = EVENT_TYPES.has(frame?.type) ? frame.type : "unclassified";
|
|
127
|
+
const classification = { provider_event_type: type };
|
|
128
|
+
if (type !== "unclassified") responseLink(active, frame?.response?.id);
|
|
129
|
+
// A new frame ends the current silence interval. Streaming calls
|
|
130
|
+
// produce no waiting records while frames keep arriving.
|
|
131
|
+
if (active.sentAt !== undefined) watch("request.waiting", active.started);
|
|
132
|
+
if (active.firstMessage === undefined) {
|
|
133
|
+
active.firstMessage = now;
|
|
134
|
+
record("request.first_message", { elapsed_ms: now - active.started, ...classification, ...measure() });
|
|
135
|
+
}
|
|
136
|
+
// Keep the historical output-item milestone, but identify what caused
|
|
137
|
+
// it. Empty item announcements are not evidence of generated text.
|
|
138
|
+
if (active.firstOutput === undefined && OUTPUT_EVENTS.has(type)) {
|
|
139
|
+
active.firstOutput = now;
|
|
140
|
+
record("request.first_output", { elapsed_ms: now - active.started, ...classification,
|
|
141
|
+
output_kind: OUTPUT_KINDS.get(type) ?? "item", ...measure() });
|
|
142
|
+
}
|
|
143
|
+
for (const [field, stage, types] of DELTA_STAGES) {
|
|
144
|
+
if (active[field] === undefined && types.includes(type) && typeof frame.delta === "string" && frame.delta.length > 0) {
|
|
145
|
+
active[field] = now;
|
|
146
|
+
record(stage, { elapsed_ms: now - active.started, ...classification, ...measure() });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
provider(timing) {
|
|
151
|
+
const request = timing.response_id ? responses.get(timing.response_id) : active ?? last;
|
|
152
|
+
if (request?.timingReported) return;
|
|
153
|
+
if (request) request.timingReported = true;
|
|
154
|
+
record("provider.timing", timing, request ?? null);
|
|
155
|
+
},
|
|
156
|
+
finished(index, responseId, outcome) {
|
|
157
|
+
if (active?.context.model_call_index !== index) return;
|
|
158
|
+
responseLink(active, responseId);
|
|
159
|
+
finish(outcome);
|
|
160
|
+
},
|
|
161
|
+
close(event, intentional = false) {
|
|
162
|
+
if (closed) return;
|
|
163
|
+
closed = true;
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
record("socket.closed", { elapsed_ms: performance.now() - began, intentional,
|
|
166
|
+
...(Number.isInteger(event?.code) ? { close_code: event.code } : {}),
|
|
167
|
+
...(typeof event?.wasClean === "boolean" ? { close_clean: event.wasClean } : {}),
|
|
168
|
+
...(active ? { received_message_count: active.messages,
|
|
169
|
+
last_message_age_ms: performance.now() - (active.lastMessage ?? active.started) } : {}), ...measure() });
|
|
170
|
+
active = undefined;
|
|
171
|
+
sockets.delete(socket);
|
|
172
|
+
},
|
|
173
|
+
error() { record("socket.error", { elapsed_ms: performance.now() - began, ...measure() }); },
|
|
174
|
+
};
|
|
175
|
+
sockets.add(socket);
|
|
176
|
+
record("socket.connecting", {}, undefined);
|
|
177
|
+
watch("socket.connect_waiting", began);
|
|
178
|
+
return socket;
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function uuid(value) { return typeof value === "string" && /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(value); }
|
|
184
|
+
function identifier(value, prefix) { return typeof value === "string" && value.startsWith(prefix) && /^[A-Za-z0-9_-]{1,160}$/.test(value); }
|
|
185
|
+
const OUTPUT_EVENTS = new Set(["response.output_text.delta", "response.reasoning_summary_text.delta",
|
|
186
|
+
"response.reasoning_summary.delta", "response.reasoning_content.delta", "response.output_item.added", "response.output_item.done"]);
|
|
187
|
+
|
|
188
|
+
// These names are a fixed protocol allowlist, never provider-supplied log text.
|
|
189
|
+
const DELTA_STAGES = [
|
|
190
|
+
["firstReasoning", "request.first_reasoning_delta", ["response.reasoning_text.delta", "response.reasoning_summary_text.delta",
|
|
191
|
+
"response.reasoning_summary.delta", "response.reasoning_content.delta"]],
|
|
192
|
+
["firstAnswer", "request.first_answer_delta", ["response.output_text.delta"]],
|
|
193
|
+
["firstTool", "request.first_tool_delta", ["response.function_call_arguments.delta", "response.custom_tool_call_input.delta"]],
|
|
194
|
+
];
|
|
195
|
+
const OUTPUT_KINDS = new Map(DELTA_STAGES.flatMap(([, stage, types]) => types.map(type => [type,
|
|
196
|
+
stage === "request.first_reasoning_delta" ? "reasoning" : stage === "request.first_answer_delta" ? "answer" : "tool"])));
|
|
197
|
+
const EVENT_TYPES = new Set([...OUTPUT_EVENTS, ...OUTPUT_KINDS.keys(), "response.created", "response.in_progress", "response.queued",
|
|
198
|
+
"response.completed", "response.failed", "response.incomplete", "error", "responsesapi.websocket_timing"]);
|
|
199
|
+
function milestones(request) {
|
|
200
|
+
return Object.fromEntries([["firstOutput", "first_output_ms"], ["firstReasoning", "first_reasoning_delta_ms"],
|
|
201
|
+
["firstAnswer", "first_answer_delta_ms"], ["firstTool", "first_tool_delta_ms"]]
|
|
202
|
+
.filter(([field]) => request[field] !== undefined).map(([field, key]) => [key, request[field] - request.started]));
|
|
203
|
+
}
|
package/cloud/Decorator.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { logout } from "./actions/account.mjs";
|
|
1
|
+
import type { links, logout } from "./actions/account.mjs";
|
|
2
2
|
import type { create as createAgent } from "./actions/agent.mjs";
|
|
3
3
|
import type { request as connectorRequest } from "./actions/connector.mjs";
|
|
4
4
|
import type { connect, disconnect, reconnect } from "./actions/connection.mjs";
|
|
@@ -10,7 +10,7 @@ import type { Client } from "./Client.mjs";
|
|
|
10
10
|
import type { Connection, HostConnection } from "./types.mjs";
|
|
11
11
|
|
|
12
12
|
export type ConnectActions<connection extends Connection | HostConnection = Connection> = {
|
|
13
|
-
account: { logout(): logout.ReturnType };
|
|
13
|
+
account: { links(options?: links.Options): links.ReturnType; logout(): logout.ReturnType };
|
|
14
14
|
agent: { create(options: createAgent.Options): Promise<createAgent.ReturnType> };
|
|
15
15
|
connector: { request(options: connectorRequest.Options): connectorRequest.ReturnType };
|
|
16
16
|
connectors: {
|
package/cloud/Decorator.mjs
CHANGED
package/cloud/Dialog.d.mts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
export const DEFAULT_HOST: "https://nanocodex.gakonst.workers.dev/connect-dialog/";
|
|
2
2
|
|
|
3
|
+
/** Visual tokens for the hosted dialog. Arbitrary CSS is not supported. */
|
|
4
|
+
export type Appearance = Readonly<{
|
|
5
|
+
theme?: "light" | "dark" | "system" | undefined;
|
|
6
|
+
/** Six-digit hex color, for example #635bff. */
|
|
7
|
+
accentColor?: `#${string}` | undefined;
|
|
8
|
+
/** Installed or self-hosted font list, at most 160 characters. No CSS functions. */
|
|
9
|
+
fontFamily?: string | undefined;
|
|
10
|
+
/** Control corner radius in pixels, from 0 to 24. */
|
|
11
|
+
borderRadius?: number | undefined;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
3
14
|
export type Request = ConnectionRequest | FundingRequest;
|
|
4
15
|
|
|
5
16
|
export type ConnectionRequest = Readonly<{
|
|
@@ -93,12 +104,14 @@ export function from<const type extends string>(parameters: Readonly<{
|
|
|
93
104
|
}>): Dialog<type>;
|
|
94
105
|
|
|
95
106
|
export function iframe(options?: Readonly<{
|
|
107
|
+
appearance?: Appearance | undefined;
|
|
96
108
|
host?: string | undefined;
|
|
97
109
|
key?: string | undefined;
|
|
98
110
|
name?: string | undefined;
|
|
99
111
|
}>): Dialog<"iframe">;
|
|
100
112
|
|
|
101
113
|
export function popup(options?: Readonly<{
|
|
114
|
+
appearance?: Appearance | undefined;
|
|
102
115
|
host?: string | undefined;
|
|
103
116
|
key?: string | undefined;
|
|
104
117
|
name?: string | undefined;
|