nanocodex 0.6.5 → 0.6.6
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 +15 -1
- package/browser/Voice.mjs +11 -4
- package/browser/VoiceSession.mjs +20 -6
- package/cloud/types.d.mts +1 -0
- package/cloudflare/Agent.d.mts +4 -0
- package/cloudflare/Agent.mjs +103 -9
- package/cloudflare/agent-settings.d.mts +1 -1
- package/cloudflare/agent-settings.mjs +3 -3
- package/cloudflare/gateway-binding-responses.mjs +3 -3
- package/cloudflare/gateway-responses.mjs +5 -5
- package/cloudflare/managed-auth.d.mts +1 -1
- package/cloudflare/managed-auth.mjs +6 -0
- package/cloudflare/tracing.d.mts +16 -0
- package/cloudflare/tracing.mjs +47 -0
- package/cloudflare/workers-ai-responses.d.mts +1 -1
- package/cloudflare/workers-ai-responses.mjs +3 -2
- package/internal.mjs +5 -0
- package/managed/Agent.d.mts +4 -1
- package/managed/Agent.mjs +22 -6
- package/managed/Voice.mjs +3 -1
- package/managed/internal.mjs +2 -1
- package/package.json +6 -2
- package/pkg-node/nanocodex.d.ts +14 -2
- package/pkg-node/nanocodex.js +73 -50
- package/pkg-node/nanocodex_bg.wasm.d.ts +5 -3
- package/pkg-web/.nanocodex-bindgen-stamp +2 -2
- package/pkg-web/nanocodex-build.json +1 -1
- package/pkg-web/nanocodex.d.ts +19 -5
- package/pkg-web/nanocodex.js +72 -50
- package/pkg-web/nanocodex_bg.js +72 -50
- package/pkg-web/nanocodex_bg.wasm +0 -0
- package/pkg-web/nanocodex_bg.wasm.d.ts +5 -3
- package/pkg-web/nanocodex_worker.js +1 -1
- package/runtime/mcp-runtime.mjs +5 -3
- package/runtime/response-controls.mjs +15 -4
- package/runtime/subagent-routing.mjs +2 -1
- package/tools/environment.d.mts +9 -0
- package/tools/environment.mjs +14 -1
- package/types.d.mts +3 -2
package/README.md
CHANGED
|
@@ -252,6 +252,20 @@ normal browser Agent, an account-owned managed Agent, or a grant-scoped
|
|
|
252
252
|
one-use ticket for that path. The browser binding never receives ChatGPT
|
|
253
253
|
credentials or places its reusable grant bearer in a WebSocket URL.
|
|
254
254
|
|
|
255
|
+
### Content-free Worker tracing
|
|
256
|
+
|
|
257
|
+
`nanocodex/cloudflare/tracing` wraps native `tracing.enterSpan(name, callback)`
|
|
258
|
+
with sanitized exception events. Use static operation names: they become
|
|
259
|
+
exception codes on failure. The original error is rethrown; its message and
|
|
260
|
+
stack are never passed to `recordException`.
|
|
261
|
+
|
|
262
|
+
`setSpanAttributes(span, attributes)` sets bulk metadata, while
|
|
263
|
+
`annotateActiveSpan(attributes)` annotates the current invocation or active
|
|
264
|
+
span without changing nesting. `recordSpanException(span, code)` handles
|
|
265
|
+
failures represented by results rather than thrown errors. Supply only static
|
|
266
|
+
codes and content-free attributes. On older workerd versions, these retain
|
|
267
|
+
individual attributes and `error.type` without requiring the September APIs.
|
|
268
|
+
|
|
255
269
|
### Durable Cloudflare Agent
|
|
256
270
|
|
|
257
271
|
`nanocodex/cloudflare` is the standard Durable Object consumer. It keeps the
|
|
@@ -1167,7 +1181,7 @@ package manager or build step:
|
|
|
1167
1181
|
|
|
1168
1182
|
```html
|
|
1169
1183
|
<script type="module">
|
|
1170
|
-
import { Agent, Transport } from "https://cdn.jsdelivr.net/npm/nanocodex@0.6.
|
|
1184
|
+
import { Agent, Transport } from "https://cdn.jsdelivr.net/npm/nanocodex@0.6.6/host/index.mjs";
|
|
1171
1185
|
const agent = await Agent.create({
|
|
1172
1186
|
transport: Transport.hostManaged({
|
|
1173
1187
|
websocketUrl: "/api/responses",
|
package/browser/Voice.mjs
CHANGED
|
@@ -198,10 +198,17 @@ export function create(agent, options = {}) {
|
|
|
198
198
|
// Media readiness updates the UI independently; start still waits for task
|
|
199
199
|
// admission, and a later rejection closes media through the same error path.
|
|
200
200
|
startPromise = next.start().then(publishReady).catch(async (cause) => {
|
|
201
|
-
if (session === next)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
if (session === next) {
|
|
202
|
+
session = undefined;
|
|
203
|
+
if (activeResources.get(agent) === resource) activeResources.delete(agent);
|
|
204
|
+
cleanupWatcher();
|
|
205
|
+
}
|
|
206
|
+
// Keep replacement starts behind failed startup cleanup, just like stop().
|
|
207
|
+
// A stale attempt must never detach a replacement session's event watcher.
|
|
208
|
+
const closing = next.close().catch(() => next.abort());
|
|
209
|
+
if (generation === current) stopPromise = closing;
|
|
210
|
+
await closing;
|
|
211
|
+
if (stopPromise === closing) stopPromise = undefined;
|
|
205
212
|
if (destroyed || generation !== current) return;
|
|
206
213
|
if (attempt === 0 && cause?.code === "peer_connection_timeout") return start(parameters, 1);
|
|
207
214
|
const error = cause instanceof Error ? cause : new Error(String(cause));
|
package/browser/VoiceSession.mjs
CHANGED
|
@@ -90,6 +90,7 @@ export class BrowserVoiceSession {
|
|
|
90
90
|
#elevenLabs;
|
|
91
91
|
#muted = false;
|
|
92
92
|
#inputGeneration = 0;
|
|
93
|
+
#playbackRevision = 0;
|
|
93
94
|
#meterTimer;
|
|
94
95
|
#backendReady;
|
|
95
96
|
#resolveBackendReady;
|
|
@@ -267,7 +268,7 @@ export class BrowserVoiceSession {
|
|
|
267
268
|
const channel = this.#channel;
|
|
268
269
|
channel.addEventListener("message", (event) => {
|
|
269
270
|
if (this.#directControl && !this.#closed && this.#channel === channel) {
|
|
270
|
-
this.#receiveControl(event.data, () => this.#channel === channel);
|
|
271
|
+
this.#receiveControl(event.data, () => this.#channel === channel && channel.readyState === "open");
|
|
271
272
|
}
|
|
272
273
|
});
|
|
273
274
|
channel.addEventListener("close", () => {
|
|
@@ -311,6 +312,7 @@ export class BrowserVoiceSession {
|
|
|
311
312
|
}
|
|
312
313
|
|
|
313
314
|
noteTypedInput() {
|
|
315
|
+
this.#playbackRevision++;
|
|
314
316
|
this.#playbackEnabled = false;
|
|
315
317
|
this.#elevenLabs?.interrupt();
|
|
316
318
|
this.#speaker?.setEnabled(false);
|
|
@@ -429,14 +431,19 @@ export class BrowserVoiceSession {
|
|
|
429
431
|
for (const text of effects.undelivered_answers ?? []) this.#options.onUndeliveredAnswer?.(text);
|
|
430
432
|
if (effects.input_generation !== undefined) {
|
|
431
433
|
if (effects.input_generation < this.#inputGeneration) return;
|
|
432
|
-
if (effects.input_generation > this.#inputGeneration)
|
|
434
|
+
if (effects.input_generation > this.#inputGeneration) {
|
|
435
|
+
this.#playbackRevision++;
|
|
436
|
+
this.#elevenLabs?.interrupt();
|
|
437
|
+
}
|
|
433
438
|
this.#inputGeneration = effects.input_generation;
|
|
434
439
|
}
|
|
435
440
|
if (effects.playback_enabled === false) {
|
|
441
|
+
this.#playbackRevision++;
|
|
436
442
|
this.#playbackEnabled = false;
|
|
437
443
|
this.#elevenLabs?.interrupt();
|
|
438
444
|
this.#speaker?.setEnabled(false);
|
|
439
445
|
}
|
|
446
|
+
const playbackRevision = this.#playbackRevision;
|
|
440
447
|
let sent = 0;
|
|
441
448
|
for (const frame of effects.frames ?? []) {
|
|
442
449
|
if (this.#directControl && this.#channel?.readyState === "open") {
|
|
@@ -448,12 +455,15 @@ export class BrowserVoiceSession {
|
|
|
448
455
|
}
|
|
449
456
|
}
|
|
450
457
|
if (effects.acknowledge_frames && sent > 0) await this.#core?.framesSent(sent);
|
|
451
|
-
|
|
458
|
+
// A live interruption can overtake asynchronous frame acknowledgement.
|
|
459
|
+
// Keep its speaker gate authoritative while retaining accepted captions.
|
|
460
|
+
const currentPlayback = playbackRevision === this.#playbackRevision;
|
|
461
|
+
if (currentPlayback && !this.#closed && effects.playback_enabled === true && sent === (effects.frames?.length ?? 0)) {
|
|
452
462
|
this.#playbackEnabled = true;
|
|
453
463
|
this.#speaker?.setEnabled(!this.#elevenLabs);
|
|
454
464
|
}
|
|
455
465
|
for (const entry of effects.transcripts ?? []) {
|
|
456
|
-
if (this.#playbackEnabled && !this.#closed) this.#elevenLabs?.transcript(entry);
|
|
466
|
+
if (currentPlayback && this.#playbackEnabled && !this.#closed) this.#elevenLabs?.transcript(entry);
|
|
457
467
|
this.#options.onTranscript(entry.speaker, entry.text, entry);
|
|
458
468
|
}
|
|
459
469
|
if (effects.status) this.#status(effects.status);
|
|
@@ -490,7 +500,7 @@ export class BrowserVoiceSession {
|
|
|
490
500
|
let opened = false;
|
|
491
501
|
sideband.addEventListener("message", (event) => {
|
|
492
502
|
if (!this.#closed && generation === this.#sidebandGeneration) {
|
|
493
|
-
this.#receiveControl(event.data, () => generation === this.#sidebandGeneration);
|
|
503
|
+
this.#receiveControl(event.data, () => generation === this.#sidebandGeneration && sideband.readyState === WebSocket.OPEN);
|
|
494
504
|
}
|
|
495
505
|
});
|
|
496
506
|
sideband.addEventListener("close", () => {
|
|
@@ -516,7 +526,11 @@ export class BrowserVoiceSession {
|
|
|
516
526
|
this.#applyLive(async () => {
|
|
517
527
|
if (!this.#directControl) await this.#admission;
|
|
518
528
|
if (this.#closed || !isCurrent()) return;
|
|
519
|
-
|
|
529
|
+
const requiresAdmission = await this.#core.requiresAgentAdmission(payload);
|
|
530
|
+
// Classification can yield while this transport closes or is replaced.
|
|
531
|
+
// Only requests accepted on the current connection may enter the queue.
|
|
532
|
+
if (this.#closed || !isCurrent()) return;
|
|
533
|
+
if (requiresAdmission) {
|
|
520
534
|
// Retain accepted requests on close, but keep captions and interruptions
|
|
521
535
|
// independent of the durable route and its ordered task queue.
|
|
522
536
|
void this.#enqueue(async () => {
|
package/cloud/types.d.mts
CHANGED
|
@@ -22,6 +22,7 @@ export type ConnectorConnection = Readonly<{
|
|
|
22
22
|
label: string;
|
|
23
23
|
accountId?: string | undefined;
|
|
24
24
|
capabilities?: readonly CloudAccount[] | undefined;
|
|
25
|
+
scopes?: readonly string[] | undefined;
|
|
25
26
|
}>;
|
|
26
27
|
|
|
27
28
|
/** Provider-neutral status for a connector service capability. */
|
package/cloudflare/Agent.d.mts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
AgentEvent,
|
|
6
6
|
DefaultAgent,
|
|
7
7
|
ToolConfiguration,
|
|
8
|
+
SessionSnapshot,
|
|
8
9
|
} from "../types.mjs";
|
|
9
10
|
import type { CloudflareDurableObjectStorage } from "../runtime/cloudflare-durability-store.mjs";
|
|
10
11
|
import type { Tool as SubagentTool } from "../runtime/subagents.mjs";
|
|
@@ -52,6 +53,9 @@ export type Agent<extended extends object = {}> =
|
|
|
52
53
|
): Agent<extended & extension>;
|
|
53
54
|
}>;
|
|
54
55
|
|
|
56
|
+
/** Copies the exact latest committed model boundary; rejects before the first safe boundary. */
|
|
57
|
+
export function checkpoint(agent: Agent): Promise<SessionSnapshot>;
|
|
58
|
+
|
|
55
59
|
/** Removes the package-owned durable history for one Cloudflare Agent. */
|
|
56
60
|
export function destroy(owner: DurableObjectOwner): void;
|
|
57
61
|
|
package/cloudflare/Agent.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { responseControlsBody, responseControlsSocket } from "../runtime/respons
|
|
|
6
6
|
import * as HostAgent from "../host/Agent.mjs";
|
|
7
7
|
import {
|
|
8
8
|
CLOUDFLARE_SESSION_RESERVATION,
|
|
9
|
+
checkpoint as checkpointAgent,
|
|
9
10
|
commitCloudflareAgentSession,
|
|
10
11
|
installHostBridge,
|
|
11
12
|
loadDurabilityRuntime,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
const STARTUP_TIMEOUT_MS = 10_000;
|
|
39
40
|
const INTERNAL_RUNTIME = Symbol.for("nanocodex.cloudflare.internalRuntime");
|
|
40
41
|
const INTERNAL_CONFIGURATION = Symbol.for("nanocodex.cloudflare.internalConfiguration");
|
|
42
|
+
const INTERNAL_FORK_RESUME = Symbol.for("nanocodex.cloudflare.internalForkResume");
|
|
41
43
|
const EPHEMERAL_APPLICATION_OPTIONS = new Set([
|
|
42
44
|
"beforeCompaction",
|
|
43
45
|
"additionalInstructions",
|
|
@@ -70,6 +72,7 @@ export function bindAgent(module, hostAgent = HostAgent) {
|
|
|
70
72
|
pruneDurableReceipts: (owner, options) => pruneDurableReceipts(module, owner, options),
|
|
71
73
|
create: (owner, options) => create(module, owner, options, hostAgent),
|
|
72
74
|
createEphemeral: (owner, options) => createEphemeral(module, owner, options),
|
|
75
|
+
checkpoint,
|
|
73
76
|
destroy,
|
|
74
77
|
exportDurabilityState,
|
|
75
78
|
exportDurabilityHead,
|
|
@@ -78,6 +81,11 @@ export function bindAgent(module, hostAgent = HostAgent) {
|
|
|
78
81
|
});
|
|
79
82
|
}
|
|
80
83
|
|
|
84
|
+
/** Copies the latest safe committed boundary as a resumable SessionSnapshot. */
|
|
85
|
+
export function checkpoint(agent) {
|
|
86
|
+
return checkpointAgent(agent);
|
|
87
|
+
}
|
|
88
|
+
|
|
81
89
|
/** Atomically steers an active Cloudflare Agent turn or starts a new turn. */
|
|
82
90
|
export function route(agent, options) {
|
|
83
91
|
return routePrompt(agent, options);
|
|
@@ -122,6 +130,7 @@ export function destroy(owner) {
|
|
|
122
130
|
stateId,
|
|
123
131
|
);
|
|
124
132
|
}
|
|
133
|
+
storage.sql.exec("DROP TABLE IF EXISTS nanocodex_cloudflare_fork_resume");
|
|
125
134
|
clearCloudflareEventSocket(context);
|
|
126
135
|
});
|
|
127
136
|
}
|
|
@@ -294,7 +303,7 @@ async function createPrepared(module, resolved, options, hostAgent, lifecycle, p
|
|
|
294
303
|
if (typeof prepare !== "function") throw new TypeError("Cloudflare Agent preparation must be a function");
|
|
295
304
|
const configuration = options[INTERNAL_CONFIGURATION];
|
|
296
305
|
validateInternalConfiguration(configuration);
|
|
297
|
-
if (!configuration || !["gpt-6-astra", "gpt-6-sol", "gpt-6-luna"].includes(configuration.model)) {
|
|
306
|
+
if (!configuration || !["gpt-6-astra", "gpt-6.1-sol", "gpt-6-luna"].includes(configuration.model)) {
|
|
298
307
|
throw new Error("Cloudflare Agent preparation requires a native root configuration");
|
|
299
308
|
}
|
|
300
309
|
const runtime = options[INTERNAL_RUNTIME];
|
|
@@ -304,7 +313,37 @@ async function createPrepared(module, resolved, options, hostAgent, lifecycle, p
|
|
|
304
313
|
const signal = runtime.preparationSignal;
|
|
305
314
|
signal?.throwIfAborted();
|
|
306
315
|
createCloudflareDurabilityStore(resolved.context.storage);
|
|
316
|
+
initializeAgentStorage(resolved.context.storage);
|
|
317
|
+
const pristineAtStart = storedSessionId(resolved.context.storage) === undefined
|
|
318
|
+
&& storedStateId(resolved.context.storage) === undefined;
|
|
319
|
+
const initialForkResume = options?.[INTERNAL_FORK_RESUME];
|
|
320
|
+
let initialForkDigest;
|
|
321
|
+
if (initialForkResume !== undefined) {
|
|
322
|
+
if (!initialForkResume || typeof initialForkResume !== "object" || Array.isArray(initialForkResume))
|
|
323
|
+
throw new TypeError("Cloudflare Agent fork resume must be a SessionSnapshot");
|
|
324
|
+
const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256",
|
|
325
|
+
new TextEncoder().encode(JSON.stringify(initialForkResume))));
|
|
326
|
+
initialForkDigest = [...bytes].map(byte => byte.toString(16).padStart(2, "0")).join("");
|
|
327
|
+
}
|
|
307
328
|
const { sessionId, stateId } = durableIdentity(resolved.context.storage, options.durabilityId);
|
|
329
|
+
if (initialForkDigest !== undefined) {
|
|
330
|
+
// Pin the seed before optional tool discovery/transport preparation. A
|
|
331
|
+
// failed preparation can reopen the exact fork with the same snapshot.
|
|
332
|
+
resolved.context.storage.sql.exec(`CREATE TABLE IF NOT EXISTS nanocodex_cloudflare_fork_resume (
|
|
333
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
334
|
+
state_id TEXT NOT NULL, digest TEXT NOT NULL
|
|
335
|
+
)`);
|
|
336
|
+
const previous = resolved.context.storage.sql.exec(
|
|
337
|
+
"SELECT state_id, digest FROM nanocodex_cloudflare_fork_resume WHERE singleton = 1",
|
|
338
|
+
).toArray()[0];
|
|
339
|
+
if ((!pristineAtStart && !previous) || (previous
|
|
340
|
+
&& (previous.state_id !== stateId || previous.digest !== initialForkDigest))) {
|
|
341
|
+
throw new Error("Cloudflare Agent fork resume conflicts with its retained seed");
|
|
342
|
+
}
|
|
343
|
+
resolved.context.storage.sql.exec(
|
|
344
|
+
"INSERT OR IGNORE INTO nanocodex_cloudflare_fork_resume(singleton,state_id,digest) VALUES (1,?,?)",
|
|
345
|
+
stateId, initialForkDigest);
|
|
346
|
+
}
|
|
308
347
|
const endpoint = cloudflareEgress({ binding: scopeCloudflareEgress(resolved.egress, resolved.subject) });
|
|
309
348
|
const connection = prepareConnection(endpoint, sessionId, signal);
|
|
310
349
|
let completing;
|
|
@@ -328,10 +367,12 @@ async function createPrepared(module, resolved, options, hostAgent, lifecycle, p
|
|
|
328
367
|
if (prepared?.durabilityId !== stateId
|
|
329
368
|
|| ["model", "reasoning_mode"].some(key => pinned?.[key] !== configuration[key])
|
|
330
369
|
|| runtime?.prepare !== undefined || runtime?.workersAi !== undefined || runtime?.gateway !== undefined
|
|
331
|
-
|| (runtime?.inferenceForSession !== undefined && runtime?.preserveRootTransport !== true)
|
|
370
|
+
|| (runtime?.inferenceForSession !== undefined && runtime?.preserveRootTransport !== true)
|
|
371
|
+
|| (initialForkDigest !== undefined && JSON.stringify(prepared?.[INTERNAL_FORK_RESUME]) !== JSON.stringify(initialForkResume))) {
|
|
332
372
|
throw new Error("Cloudflare Agent preparation changed its pinned transport or configuration");
|
|
333
373
|
}
|
|
334
|
-
return createOwned(module, resolved, prepared, hostAgent, lifecycle, connection
|
|
374
|
+
return createOwned(module, resolved, prepared, hostAgent, lifecycle, connection,
|
|
375
|
+
pristineAtStart ? { sessionId, stateId } : undefined);
|
|
335
376
|
});
|
|
336
377
|
void completing.catch(() => {});
|
|
337
378
|
return completing;
|
|
@@ -350,7 +391,7 @@ async function createPrepared(module, resolved, options, hostAgent, lifecycle, p
|
|
|
350
391
|
}
|
|
351
392
|
}
|
|
352
393
|
|
|
353
|
-
async function createOwned(module, resolved, options, hostAgent, lifecycle, preparedConnection) {
|
|
394
|
+
async function createOwned(module, resolved, options, hostAgent, lifecycle, preparedConnection, pristinePreparedIdentity) {
|
|
354
395
|
const { context, egress, subject } = resolved;
|
|
355
396
|
const configured = applicationOptions(options);
|
|
356
397
|
const {
|
|
@@ -358,6 +399,7 @@ async function createOwned(module, resolved, options, hostAgent, lifecycle, prep
|
|
|
358
399
|
eventPersistence = "durable",
|
|
359
400
|
[INTERNAL_RUNTIME]: internalRuntime,
|
|
360
401
|
[INTERNAL_CONFIGURATION]: internalConfiguration,
|
|
402
|
+
[INTERNAL_FORK_RESUME]: forkResume,
|
|
361
403
|
...agentOptions
|
|
362
404
|
} = configured;
|
|
363
405
|
if (internalRuntime !== undefined
|
|
@@ -368,6 +410,10 @@ async function createOwned(module, resolved, options, hostAgent, lifecycle, prep
|
|
|
368
410
|
&& typeof internalRuntime.onSocketTiming !== "function") {
|
|
369
411
|
throw new TypeError("Cloudflare Agent socket timing hook must be a function");
|
|
370
412
|
}
|
|
413
|
+
if (internalRuntime?.onResponseCreateSent !== undefined
|
|
414
|
+
&& typeof internalRuntime.onResponseCreateSent !== "function") {
|
|
415
|
+
throw new TypeError("Cloudflare Agent response.create sent hook must be a function");
|
|
416
|
+
}
|
|
371
417
|
if (internalRuntime?.onRequestShape !== undefined
|
|
372
418
|
&& typeof internalRuntime.onRequestShape !== "function") {
|
|
373
419
|
throw new TypeError("Cloudflare Agent request shape hook must be a function");
|
|
@@ -397,7 +443,54 @@ async function createOwned(module, resolved, options, hostAgent, lifecycle, prep
|
|
|
397
443
|
: undefined;
|
|
398
444
|
if (eventPersistence === "caller") clearCloudflareEventSocket(context);
|
|
399
445
|
const durability = createCloudflareDurabilityStore(context.storage);
|
|
446
|
+
let resumeDigest;
|
|
447
|
+
if (forkResume !== undefined) {
|
|
448
|
+
if (!forkResume || typeof forkResume !== "object" || Array.isArray(forkResume)) {
|
|
449
|
+
throw new TypeError("Cloudflare Agent fork resume must be a SessionSnapshot");
|
|
450
|
+
}
|
|
451
|
+
initializeAgentStorage(context.storage);
|
|
452
|
+
context.storage.sql.exec(`CREATE TABLE IF NOT EXISTS nanocodex_cloudflare_fork_resume (
|
|
453
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
454
|
+
state_id TEXT NOT NULL, digest TEXT NOT NULL
|
|
455
|
+
)`);
|
|
456
|
+
const digestBytes = new Uint8Array(await crypto.subtle.digest("SHA-256",
|
|
457
|
+
new TextEncoder().encode(JSON.stringify(forkResume))));
|
|
458
|
+
resumeDigest = [...digestBytes].map(byte => byte.toString(16).padStart(2, "0")).join("");
|
|
459
|
+
const recordedSeed = context.storage.sql.exec(
|
|
460
|
+
"SELECT state_id, digest FROM nanocodex_cloudflare_fork_resume WHERE singleton = 1",
|
|
461
|
+
).toArray()[0];
|
|
462
|
+
const existingSessionId = storedSessionId(context.storage);
|
|
463
|
+
const existingStateId = storedStateId(context.storage);
|
|
464
|
+
// The preparation barrier may have installed the new identity already. A
|
|
465
|
+
// failed/restarted child construction may retain that identity and a
|
|
466
|
+
// revision-zero owner fence, but still has no executed checkpoint. The
|
|
467
|
+
// durable seed is unchanged; allow that cold retry and reject any real head.
|
|
468
|
+
if ((existingSessionId !== undefined || existingStateId !== undefined)
|
|
469
|
+
&& !((pristinePreparedIdentity?.sessionId === existingSessionId
|
|
470
|
+
&& pristinePreparedIdentity?.stateId === existingStateId)
|
|
471
|
+
|| (existingSessionId !== undefined && existingStateId !== undefined
|
|
472
|
+
&& (durabilityId === undefined || existingStateId === durabilityId)
|
|
473
|
+
&& recordedSeed?.state_id === existingStateId
|
|
474
|
+
&& recordedSeed?.digest === resumeDigest))) {
|
|
475
|
+
throw new Error("Cloudflare Agent fork resume requires a pristine Durable Object");
|
|
476
|
+
}
|
|
477
|
+
if (recordedSeed && (recordedSeed.digest !== resumeDigest
|
|
478
|
+
|| (existingStateId !== undefined && recordedSeed.state_id !== existingStateId))) {
|
|
479
|
+
throw new Error("Cloudflare Agent fork resume conflicts with its retained seed");
|
|
480
|
+
}
|
|
481
|
+
const checkStateId = existingStateId ?? durabilityId;
|
|
482
|
+
if (checkStateId !== undefined && context.storage.sql.exec(
|
|
483
|
+
"SELECT revision, payload FROM nanocodex_durable_states WHERE state_id = ?", checkStateId,
|
|
484
|
+
).toArray().some(row => row.revision !== "0" || row.payload !== null)) {
|
|
485
|
+
throw new Error("Cloudflare Agent fork resume requires pristine durability state");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
400
488
|
const { sessionId, stateId } = durableIdentity(context.storage, durabilityId);
|
|
489
|
+
if (resumeDigest !== undefined) {
|
|
490
|
+
context.storage.sql.exec(
|
|
491
|
+
"INSERT OR IGNORE INTO nanocodex_cloudflare_fork_resume(singleton,state_id,digest) VALUES (1,?,?)",
|
|
492
|
+
stateId, resumeDigest);
|
|
493
|
+
}
|
|
401
494
|
const workersAi = internalRuntime?.workersAi;
|
|
402
495
|
const gateway = internalRuntime?.gateway;
|
|
403
496
|
if (gateway !== undefined && (workersAi !== undefined
|
|
@@ -439,7 +532,7 @@ async function createOwned(module, resolved, options, hostAgent, lifecycle, prep
|
|
|
439
532
|
throw new Error("Native inference cannot replace a routed transport");
|
|
440
533
|
}
|
|
441
534
|
const parsed = JSON.parse(body);
|
|
442
|
-
if (!["gpt-6-astra", "gpt-6-sol", "gpt-6-luna"].includes(parsed.model)
|
|
535
|
+
if (!["gpt-6-astra", "gpt-6.1-sol", "gpt-6-luna"].includes(parsed.model)
|
|
443
536
|
|| !["none", "low", "medium", "high", "xhigh", "max"].includes(parsed.reasoning?.effort)) {
|
|
444
537
|
throw new Error("Native inference requires a supported GPT model and thinking");
|
|
445
538
|
}
|
|
@@ -473,7 +566,7 @@ async function createOwned(module, resolved, options, hostAgent, lifecycle, prep
|
|
|
473
566
|
}
|
|
474
567
|
selected = createWorkersAiResponses(profile.workersAi.ai);
|
|
475
568
|
} else {
|
|
476
|
-
if (!["gpt-6-astra", "gpt-6-sol", "gpt-6-luna"].includes(profile.model)) {
|
|
569
|
+
if (!["gpt-6-astra", "gpt-6.1-sol", "gpt-6-luna"].includes(profile.model)) {
|
|
477
570
|
throw new Error("Session model requires an explicit inference binding");
|
|
478
571
|
}
|
|
479
572
|
selected = frontierEndpoint;
|
|
@@ -500,7 +593,7 @@ async function createOwned(module, resolved, options, hostAgent, lifecycle, prep
|
|
|
500
593
|
const opened = await (preparation === undefined
|
|
501
594
|
? endpoint.createWebSocket(url, id, request) : preparation.take(url, id, request));
|
|
502
595
|
if (request.authorization === "preconnect") startup.resolve();
|
|
503
|
-
return { ...opened, socket: responseControlsSocket(opened.socket, internalRuntime?.responseControls, internalRuntime?.onRequestShape) };
|
|
596
|
+
return { ...opened, socket: responseControlsSocket(opened.socket, internalRuntime?.responseControls, internalRuntime?.onRequestShape, internalRuntime?.onResponseCreateSent) };
|
|
504
597
|
} catch (error) {
|
|
505
598
|
if (request.authorization === "preconnect") startup.reject(error);
|
|
506
599
|
throw error;
|
|
@@ -542,6 +635,7 @@ async function createOwned(module, resolved, options, hostAgent, lifecycle, prep
|
|
|
542
635
|
sessionId,
|
|
543
636
|
durability,
|
|
544
637
|
durabilityId: stateId,
|
|
638
|
+
...(forkResume === undefined ? {} : { resume: forkResume }),
|
|
545
639
|
});
|
|
546
640
|
// Managed voice needs the durable session before the separate Responses
|
|
547
641
|
// relay is ready. Its preconnection remains owned by the host and a later
|
|
@@ -730,14 +824,14 @@ function validateInternalConfiguration(configuration) {
|
|
|
730
824
|
"reasoning_mode",
|
|
731
825
|
"fast_mode",
|
|
732
826
|
].includes(key))
|
|
733
|
-
|| !["gpt-6-sol", "gpt-6-luna", "gpt-6-astra", "@cf/zai-org/glm-5.3", "kimi-k3", "mimo-v2.6-pro"]
|
|
827
|
+
|| !["gpt-6.1-sol", "gpt-6-luna", "gpt-6-astra", "@cf/zai-org/glm-5.3", "kimi-k3", "mimo-v2.6-pro"]
|
|
734
828
|
.includes(configuration.model)
|
|
735
829
|
|| !["none", "low", "medium", "high", "xhigh", "max"].includes(configuration.thinking)
|
|
736
830
|
|| !["standard", "pro"].includes(configuration.reasoning_mode)
|
|
737
831
|
|| typeof configuration.fast_mode !== "boolean"
|
|
738
832
|
|| (["@cf/zai-org/glm-5.3", "kimi-k3", "mimo-v2.6-pro"].includes(configuration.model)
|
|
739
833
|
&& (!(configuration.model === "kimi-k3" ? ["low", "high"] : ["low", "medium", "high"]).includes(configuration.thinking) || configuration.reasoning_mode !== "standard"))
|
|
740
|
-
|| (
|
|
834
|
+
|| (["gpt-6-astra", "gpt-6.1-sol"].includes(configuration.model) && configuration.thinking === "none")) {
|
|
741
835
|
throw new TypeError("Cloudflare Agent internal configuration is invalid");
|
|
742
836
|
}
|
|
743
837
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const AGENT_MODELS: readonly ["gpt-6-sol", "gpt-6-luna", "gpt-6-astra", "@cf/zai-org/glm-5.3", "kimi-k3", "mimo-v2.6-pro"];
|
|
1
|
+
export const AGENT_MODELS: readonly ["gpt-6.1-sol", "gpt-6-luna", "gpt-6-astra", "@cf/zai-org/glm-5.3", "kimi-k3", "mimo-v2.6-pro"];
|
|
2
2
|
export const AGENT_THINKING: readonly ["none", "low", "medium", "high", "xhigh", "max"];
|
|
3
3
|
export const AGENT_REASONING_MODES: readonly ["standard", "pro"];
|
|
4
4
|
export type ManagedAgentSettings = Readonly<{ model: typeof AGENT_MODELS[number]; thinking: typeof AGENT_THINKING[number]; reasoning_mode: typeof AGENT_REASONING_MODES[number]; fast_mode: boolean }>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const AGENT_MODELS = [
|
|
2
|
-
"gpt-6-sol",
|
|
2
|
+
"gpt-6.1-sol",
|
|
3
3
|
"gpt-6-luna",
|
|
4
4
|
"gpt-6-astra",
|
|
5
5
|
"@cf/zai-org/glm-5.3",
|
|
@@ -108,8 +108,8 @@ export function validateAgentSettings(settings) {
|
|
|
108
108
|
&& (!(settings.model === "kimi-k3" ? ["low", "high"] : ["low", "medium", "high"]).includes(settings.thinking) || settings.reasoning_mode !== "standard" || settings.fast_mode)) {
|
|
109
109
|
throw new TypeError("Gateway model requires a supported effort, standard mode, and no fast mode");
|
|
110
110
|
}
|
|
111
|
-
if (
|
|
112
|
-
throw new TypeError("GPT-6 Astra
|
|
111
|
+
if (["gpt-6-astra", "gpt-6.1-sol"].includes(settings.model) && settings.thinking === "none") {
|
|
112
|
+
throw new TypeError("GPT-6 Astra and GPT-6.1 Sol require low, medium, high, xhigh, or max thinking");
|
|
113
113
|
}
|
|
114
114
|
if (settings.model === "gpt-6-astra" && settings.reasoning_mode === "pro") {
|
|
115
115
|
throw new TypeError("GPT-6 Astra does not support pro reasoning mode");
|
|
@@ -23,9 +23,9 @@ export function toBindingResponsesInput(chat, effort) {
|
|
|
23
23
|
}
|
|
24
24
|
const payload = { input, stream: chat.stream === true, store: false, reasoning: { effort } };
|
|
25
25
|
if (chat.tools) payload.tools = chat.tools.map(tool => ({ type: "function", ...tool.function,
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
strict:
|
|
26
|
+
// Responses normalizes omitted strict to true; preserve explicit strict
|
|
27
|
+
// functions while leaving legacy optional/loose schemas non-strict.
|
|
28
|
+
strict: tool.function.strict === true }));
|
|
29
29
|
if (chat.tool_choice !== undefined) payload.tool_choice = typeof chat.tool_choice === "string"
|
|
30
30
|
? chat.tool_choice : { type: "function", name: chat.tool_choice.function.name };
|
|
31
31
|
if (chat.max_completion_tokens !== undefined) payload.max_output_tokens = chat.max_completion_tokens;
|
|
@@ -6,7 +6,7 @@ const ENDPOINTS = Object.freeze({
|
|
|
6
6
|
openrouter: "https://openrouter.ai/api/v1/chat/completions",
|
|
7
7
|
vercel: "https://ai-gateway.vercel.sh/v1/chat/completions",
|
|
8
8
|
});
|
|
9
|
-
const MODELS = ["@cf/zai-org/glm-5.3", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "kimi-k3", "mimo-v2.6-pro"];
|
|
9
|
+
const MODELS = ["@cf/zai-org/glm-5.3", "gpt-6-astra", "gpt-6.1-sol", "gpt-6-luna", "kimi-k3", "mimo-v2.6-pro"];
|
|
10
10
|
const fail = message => { throw new Error(`Gateway Responses: ${message}`); };
|
|
11
11
|
|
|
12
12
|
/** Server-side, full-history transport; no WebSocket or opaque compaction. */
|
|
@@ -14,7 +14,7 @@ export function createGatewayResponses(options) {
|
|
|
14
14
|
const { provider, model, reasoningEffort, apiKey, fetch: fetchImpl = globalThis.fetch } = options;
|
|
15
15
|
if (provider !== "cloudflare" && !Object.hasOwn(ENDPOINTS, provider)) fail("unsupported provider");
|
|
16
16
|
if (!MODELS.includes(model)) fail("unsupported canonical model");
|
|
17
|
-
if (!(
|
|
17
|
+
if (!(model === "gpt-6-luna" ? ["none", "low", "medium", "high", "xhigh", "max"] : model === "gpt-6.1-sol" ? ["low", "medium", "high", "xhigh", "max"] : model === "kimi-k3" ? ["low", "high"] : ["low", "medium", "high"]).includes(reasoningEffort)) fail("unsupported reasoning effort");
|
|
18
18
|
const cloudflareHttp = provider === "cloudflare" && (options.accountId !== undefined || apiKey !== undefined);
|
|
19
19
|
if (provider === "cloudflare") {
|
|
20
20
|
if (!model.startsWith("gpt-")) fail("Cloudflare gateway requires an OpenAI canonical model");
|
|
@@ -36,8 +36,8 @@ export function createGatewayResponses(options) {
|
|
|
36
36
|
const adapter = (signal, attempt, reasoningMode) => createWorkersAiResponses({
|
|
37
37
|
async run(_model, input) {
|
|
38
38
|
signal?.throwIfAborted();
|
|
39
|
-
if (provider !== "cloudflare" &&
|
|
40
|
-
fail("
|
|
39
|
+
if (provider !== "cloudflare" && input.tools?.length && (model === "gpt-6.1-sol" || model === "gpt-6-luna" && reasoningEffort !== "none")) {
|
|
40
|
+
fail("pinned Chat model/effort cannot call tools; use Responses");
|
|
41
41
|
}
|
|
42
42
|
if (input.reasoning_effort !== undefined && input.reasoning_effort !== reasoningEffort) fail("reasoning override does not match pinned effort");
|
|
43
43
|
if (provider === "cloudflare" && !cloudflareHttp) {
|
|
@@ -160,7 +160,7 @@ export function createGatewayResponses(options) {
|
|
|
160
160
|
// The portable Chat translator has no reasoning.mode field. Preserve it
|
|
161
161
|
// explicitly on Responses transports and reject unsupported pro requests.
|
|
162
162
|
const body = JSON.parse(request.body);
|
|
163
|
-
const supportsMode = ["gpt-6-sol", "gpt-6-luna"].includes(model);
|
|
163
|
+
const supportsMode = ["gpt-6.1-sol", "gpt-6-luna"].includes(model);
|
|
164
164
|
const validateMode = value => {
|
|
165
165
|
if (value !== undefined && !["standard", "pro"].includes(value)) fail("unsupported reasoning mode");
|
|
166
166
|
if (value === "pro" && (provider !== "cloudflare" || !supportsMode)) fail("pro reasoning requires a supported Responses model");
|
|
@@ -2,7 +2,7 @@ export type OrganizationCapability = "agents:read" | "agents:portability" | "age
|
|
|
2
2
|
export type ApiKeyBase = Readonly<{ id: string; label: string; prefix: string; createdAt: number; digest: string; userId: string }>;
|
|
3
3
|
export type StoredApiKey = ApiKeyBase & Readonly<{ organizationId: string; teamId: string; role: "owner" | "writer" | "reader"; capabilities: readonly OrganizationCapability[]; authorizationEpoch: number }>;
|
|
4
4
|
export type ApiKeyPrincipal = Readonly<{ kind: "api_key"; userId: string; organizationId: string; teamId: string; role: StoredApiKey["role"]; subjectId: `api_key:${string}`; credentialId: string; authorizationEpoch: number; capabilities: readonly OrganizationCapability[] }>;
|
|
5
|
-
export type AdmissionPrincipal = Readonly<{ kind: "api_key" | "account_session" | "connect_grant" | "service"; userId: string; organizationId: string; teamId: string; authorizationEpoch: number; capabilities: readonly string[]; connectGrant?: Readonly<{ grantId: string; connectors: readonly string[]; connectorConnections?: unknown; mcpIds: readonly string[]; appToolCatalogDigest?: string }> }>;
|
|
5
|
+
export type AdmissionPrincipal = Readonly<{ kind: "api_key" | "account_session" | "connect_grant" | "service"; userId: string; organizationId: string; teamId: string; authorizationEpoch: number; capabilities: readonly string[]; connectGrant?: Readonly<{ grantId: string; connectors: readonly string[]; connectorConnections?: unknown; mcpIds: readonly string[]; appToolCatalogDigest?: string; sandboxExecution?: true; outputCheckpoints?: true }> }>;
|
|
6
6
|
export const API_KEY: RegExp;
|
|
7
7
|
export function isUserId(value: unknown): value is string;
|
|
8
8
|
export function isOrganizationCapabilities(value: unknown): value is readonly OrganizationCapability[];
|
|
@@ -77,10 +77,16 @@ export function forwardPrincipalAssertions(headers, principal) {
|
|
|
77
77
|
CONNECT_CONNECTOR_CONNECTIONS_HEADER,
|
|
78
78
|
CONNECT_MCP_IDS_HEADER,
|
|
79
79
|
CONNECT_APP_TOOL_CATALOG_DIGEST_HEADER,
|
|
80
|
+
"x-nanocodex-connect-sandbox-execution",
|
|
81
|
+
"x-nanocodex-connect-output-checkpoints",
|
|
80
82
|
]) {
|
|
81
83
|
headers.delete(name);
|
|
82
84
|
}
|
|
83
85
|
if (principal.connectGrant) {
|
|
86
|
+
if (principal.connectGrant.outputCheckpoints === true) headers.set("x-nanocodex-connect-output-checkpoints", "true");
|
|
87
|
+
if (principal.connectGrant.sandboxExecution === true) {
|
|
88
|
+
headers.set("x-nanocodex-connect-sandbox-execution", "true");
|
|
89
|
+
}
|
|
84
90
|
headers.set(CONNECT_GRANT_ID_HEADER, principal.connectGrant.grantId);
|
|
85
91
|
headers.set(CONNECT_CONNECTORS_HEADER, JSON.stringify(principal.connectGrant.connectors));
|
|
86
92
|
if (principal.connectGrant.connectorConnections !== undefined) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type SpanAttributes = Record<string, string | number | boolean | undefined>;
|
|
2
|
+
export interface TraceSpan {
|
|
3
|
+
setAttribute(key: string, value: string | number | boolean): unknown;
|
|
4
|
+
setAttributes?(attributes: SpanAttributes): unknown;
|
|
5
|
+
recordException?(exception: { code: string }): void;
|
|
6
|
+
}
|
|
7
|
+
/** Only content-free metadata may be supplied. Uses individual attributes on older workerd. */
|
|
8
|
+
export declare function setSpanAttributes(span: TraceSpan, attributes: SpanAttributes): void;
|
|
9
|
+
/** Annotates the active native span when supported; does not create a span. */
|
|
10
|
+
export declare function annotateActiveSpan(attributes: SpanAttributes): void;
|
|
11
|
+
/** code must be a static operation label, never derived from a caught error. */
|
|
12
|
+
export declare function recordSpanException(span: TraceSpan, code: string): void;
|
|
13
|
+
/** Native nested spans that record sanitized failures and rethrow the original error. */
|
|
14
|
+
export declare const tracing: {
|
|
15
|
+
enterSpan<T, A extends unknown[]>(name: string, callback: (span: TraceSpan, ...args: A) => T, ...args: A): T;
|
|
16
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { tracing as runtimeTracing } from "cloudflare:workers";
|
|
2
|
+
|
|
3
|
+
/** Attributes must be content-free metadata, never prompts, credentials or bodies. */
|
|
4
|
+
export function setSpanAttributes(span, attributes) {
|
|
5
|
+
if (typeof span.setAttributes === "function") {
|
|
6
|
+
span.setAttributes(attributes);
|
|
7
|
+
} else {
|
|
8
|
+
// The pinned local workerd predates the September 2026 span additions.
|
|
9
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
10
|
+
if (value !== undefined) span.setAttribute(key, value);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Annotate the current invocation/context without creating another child span. */
|
|
16
|
+
export function annotateActiveSpan(attributes) {
|
|
17
|
+
const span = runtimeTracing.getActiveSpan?.();
|
|
18
|
+
if (span) setSpanAttributes(span, attributes);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** code must be a static operation label, never derived from a caught error. */
|
|
22
|
+
export function recordSpanException(span, code) {
|
|
23
|
+
span.setAttribute("error.type", code);
|
|
24
|
+
// Error objects include their raw message and stack. These can contain secrets.
|
|
25
|
+
span.recordException?.({ code });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Native nesting and lifetime, with content-free exception events. */
|
|
29
|
+
export const tracing = {
|
|
30
|
+
enterSpan(name, callback, ...args) {
|
|
31
|
+
return runtimeTracing.enterSpan(name, span => {
|
|
32
|
+
try {
|
|
33
|
+
const result = callback(span, ...args);
|
|
34
|
+
if (result != null && typeof result.then === "function") {
|
|
35
|
+
return Promise.resolve(result).catch(error => {
|
|
36
|
+
recordSpanException(span, name);
|
|
37
|
+
throw error;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
recordSpanException(span, name);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BrowserHttpRequest } from "../browser/host.mjs";
|
|
2
2
|
|
|
3
|
-
export type ResponsesCanonicalModel = "@cf/zai-org/glm-5.3" | "gpt-6-astra" | "gpt-6-sol" | "gpt-6-luna" | "kimi-k3" | "mimo-v2.6-pro";
|
|
3
|
+
export type ResponsesCanonicalModel = "@cf/zai-org/glm-5.3" | "gpt-6-astra" | "gpt-6.1-sol" | "gpt-6-luna" | "kimi-k3" | "mimo-v2.6-pro";
|
|
4
4
|
|
|
5
5
|
/** Structural Workers AI binding; provider credentials stay with Cloudflare. */
|
|
6
6
|
export type WorkersAiBinding<Model extends ResponsesCanonicalModel = "@cf/zai-org/glm-5.3"> = {
|
|
@@ -2,7 +2,7 @@ import { toolAliasFailure } from "./tool-alias-diagnostic.mjs";
|
|
|
2
2
|
import { chatReasoningText } from "./chat-reasoning.mjs";
|
|
3
3
|
import { providerStream, streamResponse } from "./provider-stream.mjs";
|
|
4
4
|
const MODEL = "@cf/zai-org/glm-5.3";
|
|
5
|
-
const MODELS = [MODEL, "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "kimi-k3", "mimo-v2.6-pro"];
|
|
5
|
+
const MODELS = [MODEL, "gpt-6-astra", "gpt-6.1-sol", "gpt-6-luna", "kimi-k3", "mimo-v2.6-pro"];
|
|
6
6
|
const BASE = "https://workers-ai.invalid/v1";
|
|
7
7
|
const fail = (message) => { throw new Error(`Workers AI Responses: ${message}`); };
|
|
8
8
|
const json = (value) => typeof value === "string" ? value : JSON.stringify(value);
|
|
@@ -68,7 +68,7 @@ function translate(body, model) {
|
|
|
68
68
|
if (!body || typeof body !== "object" || Array.isArray(body)) fail("expected a Responses request object");
|
|
69
69
|
if (body.model !== undefined && body.model !== model) fail("unsupported model override; expected pinned model");
|
|
70
70
|
const effort = (value) => {
|
|
71
|
-
if (value !== undefined && !(
|
|
71
|
+
if (value !== undefined && !(model === "gpt-6-luna" ? ["none", "low", "medium", "high", "xhigh", "max"] : model === "gpt-6.1-sol" ? ["low", "medium", "high", "xhigh", "max"] : model === "kimi-k3" ? ["low", "high"] : ["low", "medium", "high"]).includes(value)) fail("unsupported reasoning effort for pinned model");
|
|
72
72
|
return value;
|
|
73
73
|
};
|
|
74
74
|
if (body.previous_response_id) fail("previous_response_id is unsupported; send the complete Responses history");
|
|
@@ -100,6 +100,7 @@ function translate(body, model) {
|
|
|
100
100
|
description: [namespace ? `${namespace}.${name}` : name, description, tool.description,
|
|
101
101
|
tool.type === "custom" ? "Pass the exact free-form tool input as the JSON string field input. Preserve all code and newlines." : "",
|
|
102
102
|
tool.format?.definition ? `Input grammar (${tool.format.syntax}): ${tool.format.definition}` : ""].filter(Boolean).join("\n"),
|
|
103
|
+
...(tool.type === "function" && tool.strict === true ? { strict: true } : {}),
|
|
103
104
|
parameters: tool.type === "custom" ? {
|
|
104
105
|
type: "object", properties: { input: { type: "string" } }, required: ["input"], additionalProperties: false,
|
|
105
106
|
} : tool.parameters ?? { type: "object", properties: {} },
|
package/internal.mjs
CHANGED
|
@@ -229,6 +229,11 @@ export function compact(agent) {
|
|
|
229
229
|
return agentState(agent).raw.compact();
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
/** Copies the live agent's latest committed, resumable model boundary. */
|
|
233
|
+
export async function checkpoint(agent) {
|
|
234
|
+
return JSON.parse(await agentState(agent).raw.checkpoint());
|
|
235
|
+
}
|
|
236
|
+
|
|
232
237
|
export async function context(agent) {
|
|
233
238
|
return parseSessionContext(await agentState(agent).raw.context());
|
|
234
239
|
}
|