libfx 0.0.13-dev.1472.g2c7a4f83f06f → 0.0.13-dev.1510.ga878d4900a6a
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 +60 -23
- package/browser.js +1 -1
- package/durable/local.mjs +55 -55
- package/durable/vercel.mjs +64 -64
- package/durable/world.mjs +1 -1
- package/durable.js +504 -104
- package/fx-core.wasm +0 -0
- package/fx-harness.js +20 -4
- package/fx-sdk.js +92 -36
- package/fx-term.wasm +0 -0
- package/libfx.darwin-arm64.node +0 -0
- package/libfx.darwin-x64.node +0 -0
- package/libfx.linux-arm64.node +0 -0
- package/libfx.linux-x64.node +0 -0
- package/mcp.js +3 -0
- package/node.cjs +446 -125
- package/node.js +22 -6
- package/package.json +1 -1
- package/types/browser.d.ts +5 -1
- package/types/durable-vercel.d.ts +8 -2
- package/types/durable-world.d.ts +16 -0
- package/types/libfx.d.cts +62 -8
- package/types/mcp.d.ts +3 -2
- package/types/node.d.cts +6 -1
- package/types/skills-node.d.ts +3 -3
- package/types/skills.d.ts +4 -3
package/node.cjs
CHANGED
|
@@ -53,6 +53,7 @@ __export(exports_node, {
|
|
|
53
53
|
listModels: () => listModels,
|
|
54
54
|
memory: () => memory,
|
|
55
55
|
supportsJspi: () => supportsJspi,
|
|
56
|
+
wakeHandler: () => wakeHandler,
|
|
56
57
|
xtermAdapter: () => xtermAdapter
|
|
57
58
|
});
|
|
58
59
|
module.exports = __toCommonJS(exports_node);
|
|
@@ -214,8 +215,10 @@ var idRule = "1 to 128 letters, digits, '.', '_' or '-'";
|
|
|
214
215
|
var newId = (prefix) => `${prefix}_${crypto.randomUUID().replaceAll("-", "")}`;
|
|
215
216
|
var defaultReserveMs = 30000;
|
|
216
217
|
var defaultLeaseMs = 15000;
|
|
218
|
+
var queueHeld = (backend, deadline) => !backend.livenessKnown && backend.queueLeases === true && deadline !== null;
|
|
219
|
+
var heldToDeadline = (backend, deadline) => backend.livenessKnown || queueHeld(backend, deadline);
|
|
217
220
|
function leaseEnd(backend, deadline) {
|
|
218
|
-
if (backend
|
|
221
|
+
if (heldToDeadline(backend, deadline))
|
|
219
222
|
return deadline;
|
|
220
223
|
const renewed = Date.now() + backend.leaseMs;
|
|
221
224
|
return deadline === null ? renewed : Math.min(renewed, deadline);
|
|
@@ -285,6 +288,7 @@ function foldSessionLog(entries, { now = 0, alive = () => null } = {}) {
|
|
|
285
288
|
const records = [];
|
|
286
289
|
let openTurn = typeof base?.openTurn?.id === "string" ? { id: base.openTurn.id, at: Number(base.openTurn.at) || 0, context: base.openTurn.context ?? null, settings: base.openTurn.settings ?? null } : null;
|
|
287
290
|
let lastTurnId = base?.lastTurnId ?? null;
|
|
291
|
+
let agent = typeof base?.agent === "string" ? base.agent : base && base.through != null ? defaultAgentName : null;
|
|
288
292
|
let yielded = base?.yielded === true;
|
|
289
293
|
const contexts = new Map;
|
|
290
294
|
const settingsOf = new Map;
|
|
@@ -316,6 +320,7 @@ function foldSessionLog(entries, { now = 0, alive = () => null } = {}) {
|
|
|
316
320
|
maxCursor = position;
|
|
317
321
|
switch (entry?.k) {
|
|
318
322
|
case "input":
|
|
323
|
+
agent ??= typeof entry.agent === "string" ? entry.agent : defaultAgentName;
|
|
319
324
|
if (seenInputs.has(entry.key))
|
|
320
325
|
break;
|
|
321
326
|
seenInputs.add(entry.key);
|
|
@@ -332,7 +337,7 @@ function foldSessionLog(entries, { now = 0, alive = () => null } = {}) {
|
|
|
332
337
|
break;
|
|
333
338
|
head = cursor;
|
|
334
339
|
if (entry.k === "lease") {
|
|
335
|
-
lease = { holder: entry.holder, epoch: entry.epoch, expiresAt: entry.expiresAt ?? null, pid: entry.pid, host: entry.host, cursor };
|
|
340
|
+
lease = { holder: entry.holder, epoch: entry.epoch, expiresAt: entry.expiresAt ?? null, pid: entry.pid, host: entry.host, delivery: entry.delivery, cursor };
|
|
336
341
|
if (Number.isSafeInteger(entry.epoch) && entry.epoch > epoch)
|
|
337
342
|
epoch = entry.epoch;
|
|
338
343
|
} else if (entry.k === "release") {
|
|
@@ -427,6 +432,7 @@ function foldSessionLog(entries, { now = 0, alive = () => null } = {}) {
|
|
|
427
432
|
cutoffs: openTurn === null ? 0 : cut.cutoffs,
|
|
428
433
|
openFailed,
|
|
429
434
|
lastTurnId,
|
|
435
|
+
agent,
|
|
430
436
|
yielded,
|
|
431
437
|
pending,
|
|
432
438
|
steers,
|
|
@@ -452,11 +458,19 @@ function leaseStands(lease, now, alive) {
|
|
|
452
458
|
return false;
|
|
453
459
|
return true;
|
|
454
460
|
}
|
|
461
|
+
function deliveryOf(meta) {
|
|
462
|
+
const messageId = meta?.messageId;
|
|
463
|
+
const attempt = meta?.attempt;
|
|
464
|
+
return typeof messageId === "string" && messageId.length > 0 && Number.isSafeInteger(attempt) && attempt > 0 ? { messageId, attempt } : null;
|
|
465
|
+
}
|
|
466
|
+
function redelivers(delivery, lease) {
|
|
467
|
+
const held = lease?.delivery;
|
|
468
|
+
return Boolean(delivery && held && held.messageId === delivery.messageId && Number.isSafeInteger(held.attempt) && delivery.attempt >= held.attempt);
|
|
469
|
+
}
|
|
455
470
|
function createMemoryBackend({ maxDurationMs, reserveMs } = {}) {
|
|
456
471
|
const sessions = new Map;
|
|
457
472
|
const holders = liveHolders;
|
|
458
|
-
const handlers =
|
|
459
|
-
let turnOf = 0;
|
|
473
|
+
const handlers = new Map;
|
|
460
474
|
const timers = new Set;
|
|
461
475
|
const sessionFor = (id) => {
|
|
462
476
|
let session = sessions.get(id);
|
|
@@ -473,21 +487,28 @@ function createMemoryBackend({ maxDurationMs, reserveMs } = {}) {
|
|
|
473
487
|
}, ms);
|
|
474
488
|
timers.add(timer);
|
|
475
489
|
};
|
|
476
|
-
const deliver = (message, attempt) => {
|
|
477
|
-
if (handlers.
|
|
478
|
-
return later(() => deliver(message, attempt), 10);
|
|
479
|
-
const
|
|
480
|
-
|
|
490
|
+
const deliver = (message, attempt, messageId = newId("qmsg")) => {
|
|
491
|
+
if (handlers.size === 0)
|
|
492
|
+
return later(() => deliver(message, attempt, messageId), 10);
|
|
493
|
+
const group = handlers.get(agentOf(message));
|
|
494
|
+
if (!group) {
|
|
495
|
+
if (attempt < 48)
|
|
496
|
+
later(() => deliver(message, attempt + 1, messageId), 1000);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const handler = group.members[group.turn++ % group.members.length];
|
|
500
|
+
Promise.resolve().then(() => handler(message, { messageId, attempt })).then((result) => {
|
|
481
501
|
if (Number.isFinite(result?.timeoutSeconds))
|
|
482
|
-
later(() => deliver(message, attempt + 1), result.timeoutSeconds * 1000);
|
|
502
|
+
later(() => deliver(message, attempt + 1, messageId), result.timeoutSeconds * 1000);
|
|
483
503
|
}, () => {
|
|
484
504
|
if (attempt < 48)
|
|
485
|
-
later(() => deliver(message, attempt + 1), 1000);
|
|
505
|
+
later(() => deliver(message, attempt + 1, messageId), 1000);
|
|
486
506
|
});
|
|
487
507
|
};
|
|
488
508
|
return {
|
|
489
509
|
name: "memory",
|
|
490
510
|
livenessKnown: true,
|
|
511
|
+
runInRequest: false,
|
|
491
512
|
queueDurable: false,
|
|
492
513
|
pollMs: null,
|
|
493
514
|
reserveMs: reserveMs ?? defaultReserveMs,
|
|
@@ -580,13 +601,11 @@ function createMemoryBackend({ maxDurationMs, reserveMs } = {}) {
|
|
|
580
601
|
later(() => deliver(message, 1), delaySeconds * 1000);
|
|
581
602
|
}
|
|
582
603
|
},
|
|
583
|
-
listen(fn) {
|
|
584
|
-
handlers
|
|
604
|
+
listen(fn, name = defaultAgentName) {
|
|
605
|
+
const group = joinGroup(handlers, name, fn);
|
|
585
606
|
return () => {
|
|
586
|
-
|
|
587
|
-
if (
|
|
588
|
-
handlers.splice(index, 1);
|
|
589
|
-
if (handlers.length > 0)
|
|
607
|
+
leaveGroup(handlers, name, group, fn);
|
|
608
|
+
if (handlers.size > 0)
|
|
590
609
|
return;
|
|
591
610
|
for (const timer of timers)
|
|
592
611
|
clearTimeout(timer);
|
|
@@ -598,6 +617,9 @@ function createMemoryBackend({ maxDurationMs, reserveMs } = {}) {
|
|
|
598
617
|
var logStep = "fx.log";
|
|
599
618
|
var uiStreamOf = (runId) => `${runId.replace(/^wrun_/, "strm_")}_user_bGliZngtdWk`;
|
|
600
619
|
var queuePrefix = "__libfx_wkf_workflow_";
|
|
620
|
+
var defaultAgentName = "default";
|
|
621
|
+
var agentNameRule = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
622
|
+
var agentOf = (message) => typeof message?.agent === "string" ? message.agent : defaultAgentName;
|
|
601
623
|
var queueName = `${queuePrefix}session`;
|
|
602
624
|
var eventIdPattern = /^[a-z]+_(\d{26})$/;
|
|
603
625
|
function ulid(now = Date.now()) {
|
|
@@ -610,6 +632,13 @@ function ulid(now = Date.now()) {
|
|
|
610
632
|
random += alphabet[byte % 32];
|
|
611
633
|
return time + random;
|
|
612
634
|
}
|
|
635
|
+
function chainedKind(k) {
|
|
636
|
+
return k === "lease" || k === "release" || k === "record";
|
|
637
|
+
}
|
|
638
|
+
var inBandAtRunCreation = 1;
|
|
639
|
+
function inBandSuperseded(error) {
|
|
640
|
+
return error?.name === "InBandSupersededError" || error?.code === "in-band-superseded" || error?.status === 412;
|
|
641
|
+
}
|
|
613
642
|
function slotOf(event) {
|
|
614
643
|
const match = eventIdPattern.exec(String(event?.eventId ?? ""));
|
|
615
644
|
if (!match)
|
|
@@ -635,7 +664,56 @@ var missingStreamFastMs = 2000;
|
|
|
635
664
|
var firstPageEvents = 10;
|
|
636
665
|
var laterPageEvents = 100;
|
|
637
666
|
var listenersOf = new WeakMap;
|
|
638
|
-
function
|
|
667
|
+
function joinGroup(groups, name, member) {
|
|
668
|
+
let group = groups.get(name);
|
|
669
|
+
if (!group)
|
|
670
|
+
groups.set(name, group = { members: [], turn: 0 });
|
|
671
|
+
group.members.push(member);
|
|
672
|
+
return group;
|
|
673
|
+
}
|
|
674
|
+
function leaveGroup(groups, name, group, member) {
|
|
675
|
+
const index = group.members.indexOf(member);
|
|
676
|
+
if (index >= 0)
|
|
677
|
+
group.members.splice(index, 1);
|
|
678
|
+
if (group.members.length === 0 && groups.get(name) === group)
|
|
679
|
+
groups.delete(name);
|
|
680
|
+
}
|
|
681
|
+
function noAgentNamed(name) {
|
|
682
|
+
return Object.assign(new Error(`no libfx agent named ${JSON.stringify(name)} runs in this process; the queue delivers this again`), { code: "FX_NO_AGENT", agent: name });
|
|
683
|
+
}
|
|
684
|
+
var agentsByName = globalThis[Symbol.for("libfx.durableAgents")] ??= new Map;
|
|
685
|
+
async function agentNamed(name, matches) {
|
|
686
|
+
const group = agentsByName.get(name);
|
|
687
|
+
if (!group)
|
|
688
|
+
return null;
|
|
689
|
+
const { members } = group;
|
|
690
|
+
const first = group.turn++;
|
|
691
|
+
for (let index = 0;index < members.length; index += 1) {
|
|
692
|
+
const member = members[(first + index) % members.length];
|
|
693
|
+
if (!matches(await member.durability()))
|
|
694
|
+
continue;
|
|
695
|
+
const created = await member.open();
|
|
696
|
+
if (typeof created.deliverHere === "function")
|
|
697
|
+
return created;
|
|
698
|
+
}
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
var agentSharing = (name, durability) => agentNamed(name, (other) => other === durability);
|
|
702
|
+
var agentOnRoute = async (name, durability) => await agentSharing(name, durability) ?? agentNamed(name, (other) => other !== durability && other.name === durability.name && typeof other.world === "function" && other.create === undefined);
|
|
703
|
+
function wakeHandler() {
|
|
704
|
+
return async (request) => {
|
|
705
|
+
for (const group of agentsByName.values()) {
|
|
706
|
+
for (const member of group.members) {
|
|
707
|
+
const created = await member.open();
|
|
708
|
+
if (created.viaRoute === true)
|
|
709
|
+
return created.queueHandler(request);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return new Response("no libfx agent in this process takes queue deliveries from a route", { status: 503 });
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function createWorldBackend(world, durability) {
|
|
716
|
+
const { name, reserveMs, pollMs, leaseMs, alive, holderInfo, livenessKnown = false, queueLeases = false, runInRequest = queueLeases, waitUntil, queueDurable = false, maxDurationMs, owned = true } = durability;
|
|
639
717
|
const spec = world.specVersion === undefined ? {} : { specVersion: world.specVersion };
|
|
640
718
|
const created = new Set;
|
|
641
719
|
const creating = new Map;
|
|
@@ -651,7 +729,7 @@ function createWorldBackend(world, { name, reserveMs, pollMs, leaseMs, alive, ho
|
|
|
651
729
|
pagination: { sortOrder: "desc", limit, ...cursor ? { cursor } : {} },
|
|
652
730
|
resolveData: "all"
|
|
653
731
|
});
|
|
654
|
-
const deliverTo = async (handle, message) => {
|
|
732
|
+
const deliverTo = async (handle, message, meta) => {
|
|
655
733
|
const input = message?.input;
|
|
656
734
|
if (!input || typeof input !== "object")
|
|
657
735
|
return;
|
|
@@ -660,13 +738,24 @@ function createWorldBackend(world, { name, reserveMs, pollMs, leaseMs, alive, ho
|
|
|
660
738
|
return;
|
|
661
739
|
throw new Error("no libfx agent is listening; the queue delivers this again");
|
|
662
740
|
}
|
|
663
|
-
const result = await handle(input);
|
|
741
|
+
const result = await handle(input, meta);
|
|
742
|
+
if (result?.redeliver === true)
|
|
743
|
+
throw new Error("libfx asks the queue to deliver this message again");
|
|
664
744
|
return Number.isFinite(result?.timeoutSeconds) ? { timeoutSeconds: result.timeoutSeconds } : undefined;
|
|
665
745
|
};
|
|
666
|
-
const queueHandler = world.createQueueHandler(queuePrefix, (message) =>
|
|
746
|
+
const queueHandler = world.createQueueHandler(queuePrefix, async (message, meta) => {
|
|
747
|
+
const named = agentOf(message?.input);
|
|
748
|
+
const agent = await agentOnRoute(named, durability);
|
|
749
|
+
if (!agent)
|
|
750
|
+
throw noAgentNamed(named);
|
|
751
|
+
return agent.deliverHere(message, meta);
|
|
752
|
+
});
|
|
667
753
|
const backend = {
|
|
668
754
|
name,
|
|
669
755
|
livenessKnown,
|
|
756
|
+
queueLeases,
|
|
757
|
+
runInRequest: queueLeases && runInRequest,
|
|
758
|
+
waitUntil: waitUntil ?? null,
|
|
670
759
|
queueDurable,
|
|
671
760
|
pollMs: pollMs ?? 1000,
|
|
672
761
|
reserveMs: reserveMs ?? defaultReserveMs,
|
|
@@ -677,10 +766,14 @@ function createWorldBackend(world, { name, reserveMs, pollMs, leaseMs, alive, ho
|
|
|
677
766
|
validSessionId: (id) => typeof id === "string" && /^wrun_[0-9A-Za-z]{10,64}$/.test(id),
|
|
678
767
|
deadline: () => null,
|
|
679
768
|
holderInfo: holderInfo ?? (() => ({})),
|
|
680
|
-
alive(lease) {
|
|
769
|
+
alive(lease, delivery = null) {
|
|
770
|
+
if (liveHolders.has(lease.holder))
|
|
771
|
+
return true;
|
|
681
772
|
const mine = holderInfo?.() ?? {};
|
|
682
773
|
if (mine.pid !== undefined && lease.pid === mine.pid && lease.host === mine.host)
|
|
683
|
-
return
|
|
774
|
+
return false;
|
|
775
|
+
if (redelivers(delivery, lease))
|
|
776
|
+
return false;
|
|
684
777
|
return alive ? alive(lease) : null;
|
|
685
778
|
},
|
|
686
779
|
holding(holder, held) {
|
|
@@ -708,6 +801,21 @@ function createWorldBackend(world, { name, reserveMs, pollMs, leaseMs, alive, ho
|
|
|
708
801
|
},
|
|
709
802
|
async session(runId) {
|
|
710
803
|
let known = 0;
|
|
804
|
+
const inBandAt = new Map;
|
|
805
|
+
let inBand = world.capabilities?.inBandFence === true;
|
|
806
|
+
const noteInBand = (cursor, count) => {
|
|
807
|
+
inBandAt.delete(cursor);
|
|
808
|
+
inBandAt.set(cursor, count);
|
|
809
|
+
if (inBandAt.size > 16)
|
|
810
|
+
inBandAt.delete(inBandAt.keys().next().value);
|
|
811
|
+
};
|
|
812
|
+
const noteSnapshot = (snapshot, cursor) => {
|
|
813
|
+
if (!inBand || cursor === undefined || inBandAt.has(cursor) || !Number.isSafeInteger(snapshot?.seqInBand))
|
|
814
|
+
return;
|
|
815
|
+
if (cursor !== "null" && !(Number(cursor) <= snapshot.seq))
|
|
816
|
+
return;
|
|
817
|
+
noteInBand(cursor, snapshot.seqInBand);
|
|
818
|
+
};
|
|
711
819
|
const ensureRun = () => {
|
|
712
820
|
if (created.has(runId))
|
|
713
821
|
return Promise.resolve();
|
|
@@ -741,34 +849,45 @@ function createWorldBackend(world, { name, reserveMs, pollMs, leaseMs, alive, ho
|
|
|
741
849
|
const newestFirst = async (stopBelow, stopAtCheckpoint) => {
|
|
742
850
|
const events = [];
|
|
743
851
|
let through = null;
|
|
852
|
+
let snapshot;
|
|
853
|
+
let newestChained;
|
|
854
|
+
const done = (head) => {
|
|
855
|
+
noteSnapshot(snapshot, newestChained ?? head);
|
|
856
|
+
return { events, through };
|
|
857
|
+
};
|
|
744
858
|
for (let cursor, limit = firstPageEvents;; limit = laterPageEvents) {
|
|
745
859
|
let page;
|
|
746
860
|
try {
|
|
747
861
|
page = await listEvents(runId, cursor, limit);
|
|
748
862
|
} catch (error) {
|
|
749
|
-
if (missingRun(error))
|
|
750
|
-
|
|
751
|
-
|
|
863
|
+
if (!missingRun(error))
|
|
864
|
+
throw error;
|
|
865
|
+
if (stopBelow === 0)
|
|
866
|
+
noteSnapshot({ seq: 0, seqInBand: inBandAtRunCreation }, "null");
|
|
867
|
+
return { events, through };
|
|
752
868
|
}
|
|
869
|
+
snapshot ??= page.snapshot;
|
|
753
870
|
for (const event of page.data) {
|
|
754
871
|
const slot = slotOf(event);
|
|
755
872
|
if (slot > known)
|
|
756
873
|
known = slot;
|
|
757
874
|
if (slot <= stopBelow)
|
|
758
|
-
return
|
|
875
|
+
return done(undefined);
|
|
759
876
|
if (through !== null && slot <= through)
|
|
760
|
-
return
|
|
877
|
+
return done(through === 0 ? "null" : String(through));
|
|
761
878
|
const entry = logEntryOf(event);
|
|
762
879
|
if (!entry)
|
|
763
880
|
continue;
|
|
764
881
|
created.add(runId);
|
|
882
|
+
if (newestChained === undefined && chainedKind(entry.k))
|
|
883
|
+
newestChained = String(slot);
|
|
765
884
|
events.push({ cursor: String(slot), entry });
|
|
766
885
|
if (stopAtCheckpoint && through === null && entry.k === "checkpoint") {
|
|
767
886
|
through = entry.through === null || entry.through === undefined ? 0 : Number(entry.through);
|
|
768
887
|
}
|
|
769
888
|
}
|
|
770
889
|
if (!page.hasMore)
|
|
771
|
-
return
|
|
890
|
+
return done(stopBelow > 0 ? undefined : through ? String(through) : "null");
|
|
772
891
|
cursor = page.cursor;
|
|
773
892
|
}
|
|
774
893
|
};
|
|
@@ -783,16 +902,50 @@ function createWorldBackend(world, { name, reserveMs, pollMs, leaseMs, alive, ho
|
|
|
783
902
|
},
|
|
784
903
|
async append(entry) {
|
|
785
904
|
await ensureRun();
|
|
786
|
-
const chained = entry.k
|
|
905
|
+
const chained = chainedKind(entry.k);
|
|
787
906
|
const after = chained ? entry.a === null || entry.a === undefined ? 0 : Number(entry.a) : known;
|
|
788
|
-
const
|
|
907
|
+
const continues = entry.a === null || entry.a === undefined ? "null" : String(entry.a);
|
|
908
|
+
let expected = chained && inBand ? inBandAt.get(continues) : undefined;
|
|
909
|
+
const event = {
|
|
789
910
|
eventType: "step_created",
|
|
790
911
|
correlationId: `step_${ulid()}`,
|
|
791
912
|
eventData: { stepName: logStep, input: encoder.encode(JSON.stringify(entry)) }
|
|
792
|
-
}
|
|
913
|
+
};
|
|
914
|
+
let result;
|
|
915
|
+
for (let recounted = false;; ) {
|
|
916
|
+
try {
|
|
917
|
+
result = await world.events.create(runId, event, expected === undefined ? { eventCount: after } : { eventCount: after, inBand: true, expectedSeqInBand: expected });
|
|
918
|
+
break;
|
|
919
|
+
} catch (error) {
|
|
920
|
+
if (expected === undefined)
|
|
921
|
+
throw error;
|
|
922
|
+
if (inBandSuperseded(error)) {
|
|
923
|
+
inBandAt.delete(continues);
|
|
924
|
+
if (!recounted && Number.isSafeInteger(error?.seqInBand)) {
|
|
925
|
+
const { events: since } = await newestFirst(after, false);
|
|
926
|
+
const rivals = since.filter(({ entry: other }) => chainedKind(other.k) && (other.a ?? null) === (entry.a ?? null));
|
|
927
|
+
const ours = rivals.find(({ entry: other }) => other.key !== undefined && other.key === entry.key && other.k === entry.k);
|
|
928
|
+
if (ours)
|
|
929
|
+
return ours.cursor;
|
|
930
|
+
if (rivals.length === 0) {
|
|
931
|
+
recounted = true;
|
|
932
|
+
expected = error.seqInBand;
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
throw new FencedError(`another worker continued session ${runId}`);
|
|
937
|
+
}
|
|
938
|
+
if (!(error?.status >= 400 && error?.status < 500) || error?.status === 409)
|
|
939
|
+
throw error;
|
|
940
|
+
inBand = false;
|
|
941
|
+
expected = undefined;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
793
944
|
const slot = slotOf(result.event);
|
|
794
945
|
if (slot > known)
|
|
795
946
|
known = slot;
|
|
947
|
+
if (expected !== undefined && result.allocated !== undefined)
|
|
948
|
+
noteInBand(String(slot), expected + result.allocated);
|
|
796
949
|
changed(runId);
|
|
797
950
|
if (!chained || slot === after + 1)
|
|
798
951
|
return String(slot);
|
|
@@ -802,7 +955,7 @@ function createWorldBackend(world, { name, reserveMs, pollMs, leaseMs, alive, ho
|
|
|
802
955
|
const otherSlot = event.entry ? Number(event.cursor) : slotOf(event);
|
|
803
956
|
if (!other || otherSlot <= after || otherSlot >= slot)
|
|
804
957
|
continue;
|
|
805
|
-
const otherChained = other.k
|
|
958
|
+
const otherChained = chainedKind(other.k);
|
|
806
959
|
if (!otherChained || (other.a ?? null) !== (entry.a ?? null))
|
|
807
960
|
continue;
|
|
808
961
|
if (other.key !== undefined && other.key === entry.key && other.k === entry.k)
|
|
@@ -875,14 +1028,18 @@ function createWorldBackend(world, { name, reserveMs, pollMs, leaseMs, alive, ho
|
|
|
875
1028
|
},
|
|
876
1029
|
queue: {
|
|
877
1030
|
async send(message, { delaySeconds = 0, idempotencyKey } = {}) {
|
|
878
|
-
await world.queue(queueName, { runId: message.sessionId, requestId: message.messageId, input: message }, {
|
|
1031
|
+
const sent = await world.queue(queueName, { runId: message.sessionId, requestId: message.messageId, input: message }, {
|
|
879
1032
|
...idempotencyKey ? { idempotencyKey } : {},
|
|
880
1033
|
...delaySeconds > 0 ? { delaySeconds } : {}
|
|
881
1034
|
});
|
|
1035
|
+
return typeof sent?.messageId === "string" && sent.messageId.length > 0 ? { messageId: sent.messageId } : {};
|
|
882
1036
|
}
|
|
883
1037
|
},
|
|
884
1038
|
queueHandler,
|
|
885
|
-
|
|
1039
|
+
durability,
|
|
1040
|
+
viaRoute: route,
|
|
1041
|
+
deliverHere: (message, meta) => deliverTo(handler, message, meta),
|
|
1042
|
+
listen(fn, name = defaultAgentName) {
|
|
886
1043
|
handler = fn;
|
|
887
1044
|
if (route)
|
|
888
1045
|
return () => {
|
|
@@ -891,20 +1048,26 @@ function createWorldBackend(world, { name, reserveMs, pollMs, leaseMs, alive, ho
|
|
|
891
1048
|
};
|
|
892
1049
|
let listeners = listenersOf.get(world);
|
|
893
1050
|
if (!listeners) {
|
|
894
|
-
listeners =
|
|
1051
|
+
listeners = new Map;
|
|
895
1052
|
listenersOf.set(world, listeners);
|
|
896
|
-
world.registerHandler(queuePrefix, world.createQueueHandler(queuePrefix, (message) => {
|
|
897
|
-
const
|
|
898
|
-
|
|
1053
|
+
world.registerHandler(queuePrefix, world.createQueueHandler(queuePrefix, async (message, meta) => {
|
|
1054
|
+
const named = agentOf(message?.input);
|
|
1055
|
+
const group = listeners.get(named);
|
|
1056
|
+
if (group)
|
|
1057
|
+
return deliverTo(group.members[group.turn++ % group.members.length], message, meta);
|
|
1058
|
+
const agent = await agentSharing(named, durability);
|
|
1059
|
+
if (agent)
|
|
1060
|
+
return agent.deliverHere(message, meta);
|
|
1061
|
+
if (listeners.size === 0)
|
|
1062
|
+
return deliverTo(null, message, meta);
|
|
1063
|
+
throw noAgentNamed(named);
|
|
899
1064
|
}));
|
|
900
1065
|
}
|
|
901
|
-
listeners
|
|
1066
|
+
const group = joinGroup(listeners, name, fn);
|
|
902
1067
|
return () => {
|
|
903
1068
|
if (handler === fn)
|
|
904
1069
|
handler = null;
|
|
905
|
-
|
|
906
|
-
if (index >= 0)
|
|
907
|
-
listeners.handlers.splice(index, 1);
|
|
1070
|
+
leaveGroup(listeners, name, group, fn);
|
|
908
1071
|
};
|
|
909
1072
|
},
|
|
910
1073
|
async close() {
|
|
@@ -946,8 +1109,9 @@ var errorSummary = (error) => ({ name: error?.name ?? "Error", message: String(e
|
|
|
946
1109
|
var isFenced = (error) => error?.code === "FX_FENCED";
|
|
947
1110
|
function messageEntries(state, message) {
|
|
948
1111
|
const entries = [];
|
|
1112
|
+
const named = typeof message.agent === "string" ? { agent: message.agent } : {};
|
|
949
1113
|
if (message.seed && state.checkpoint === null && state.records.length === 0) {
|
|
950
|
-
entries.push({ k: "checkpoint", through: null, data: message.seed });
|
|
1114
|
+
entries.push({ k: "checkpoint", through: null, data: message.seed, ...named });
|
|
951
1115
|
}
|
|
952
1116
|
const key = `${message.type}:${message.messageId}`;
|
|
953
1117
|
const known = state.inputKeys.has(key) || state.consumed.has(message.messageId);
|
|
@@ -957,6 +1121,7 @@ function messageEntries(state, message) {
|
|
|
957
1121
|
key,
|
|
958
1122
|
type: message.type,
|
|
959
1123
|
messageId: message.messageId,
|
|
1124
|
+
...named,
|
|
960
1125
|
...message.input === undefined ? {} : { input: message.input },
|
|
961
1126
|
...typeof message.target === "string" ? { target: message.target } : {},
|
|
962
1127
|
...(message.type === "prompt" || message.type === "steer") && message.context !== undefined ? { context: message.context } : {},
|
|
@@ -1000,6 +1165,8 @@ function gatedLog(log, ready, claimError) {
|
|
|
1000
1165
|
var hardStopMs = 2000;
|
|
1001
1166
|
var durableInternals = Symbol.for("libfx.durableInternals");
|
|
1002
1167
|
var runningBackstopSeconds = 2;
|
|
1168
|
+
var deliveryDeadline = (backend) => backend.deadline() ?? (backend.maxDurationMs === null ? null : Date.now() + backend.maxDurationMs);
|
|
1169
|
+
var inlineBackstopMarginSeconds = 5;
|
|
1003
1170
|
|
|
1004
1171
|
class SessionWorker {
|
|
1005
1172
|
constructor(agent, backend, sessionId) {
|
|
@@ -1010,24 +1177,32 @@ class SessionWorker {
|
|
|
1010
1177
|
this.deliveries = 0;
|
|
1011
1178
|
this.holder = null;
|
|
1012
1179
|
}
|
|
1013
|
-
fold(entries) {
|
|
1014
|
-
return foldSessionLog(entries, { now: Date.now(), alive: (lease) => this.backend.alive(lease) });
|
|
1180
|
+
fold(entries, delivery = null) {
|
|
1181
|
+
return foldSessionLog(entries, { now: Date.now(), alive: (lease) => this.backend.alive(lease, delivery) });
|
|
1015
1182
|
}
|
|
1016
|
-
async consume(message) {
|
|
1183
|
+
async consume(message, delivery = null) {
|
|
1017
1184
|
const log = await this.backend.session(this.sessionId);
|
|
1018
1185
|
await this.backend.refreshDeadline?.();
|
|
1019
1186
|
const deadline = this.backend.deadline() ?? (this.backend.maxDurationMs === null ? null : Date.now() + this.backend.maxDurationMs);
|
|
1020
1187
|
let entries = await log.read();
|
|
1021
|
-
let state = this.fold(entries);
|
|
1188
|
+
let state = this.fold(entries, delivery);
|
|
1189
|
+
const owner = state.agent ?? agentOf(message);
|
|
1190
|
+
if (owner !== this.agent.name) {
|
|
1191
|
+
await this.backend.queue.send({ ...message, agent: owner }, {
|
|
1192
|
+
idempotencyKey: `${message.sessionId}:${message.type}:${message.messageId}:to:${owner}`
|
|
1193
|
+
});
|
|
1194
|
+
this.agent.emit("session.forwarded", { sessionId: this.sessionId, agent: owner });
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1022
1197
|
for (let claims = 0;claims < 8; claims += 1) {
|
|
1023
|
-
const early = this.running || state.lease ? null : earlyStart(entries, state, message, (items) => this.fold(items));
|
|
1198
|
+
const early = this.running || state.lease ? null : earlyStart(entries, state, message, (items) => this.fold(items, delivery));
|
|
1024
1199
|
let outcome;
|
|
1025
1200
|
if (early) {
|
|
1026
|
-
outcome = await this.claim(log, state, deadline, early);
|
|
1201
|
+
outcome = await this.claim(log, state, deadline, early, delivery);
|
|
1027
1202
|
} else {
|
|
1028
1203
|
if (await recordMessage(log, state, message) > 0) {
|
|
1029
1204
|
entries = await log.read();
|
|
1030
|
-
state = this.fold(entries);
|
|
1205
|
+
state = this.fold(entries, delivery);
|
|
1031
1206
|
}
|
|
1032
1207
|
if (this.running)
|
|
1033
1208
|
return { timeoutSeconds: runningBackstopSeconds };
|
|
@@ -1040,22 +1215,22 @@ class SessionWorker {
|
|
|
1040
1215
|
const left = state.lease.expiresAt ? Math.ceil((state.lease.expiresAt - Date.now()) / 1000) + 1 : 5;
|
|
1041
1216
|
return { timeoutSeconds: Math.max(1, Math.min(maxBackstopSeconds, left)) };
|
|
1042
1217
|
}
|
|
1043
|
-
outcome = await this.claim(log, state, deadline, null);
|
|
1218
|
+
outcome = await this.claim(log, state, deadline, null, delivery);
|
|
1044
1219
|
}
|
|
1045
1220
|
if (outcome === "yielded")
|
|
1046
1221
|
return { timeoutSeconds: 0 };
|
|
1047
1222
|
if (outcome === "failed")
|
|
1048
|
-
return { timeoutSeconds: 1 };
|
|
1223
|
+
return queueHeld(this.backend, deadline) ? { redeliver: true } : { timeoutSeconds: 1 };
|
|
1049
1224
|
if (outcome === "fenced") {
|
|
1050
1225
|
this.agent.emit("session.fenced", { sessionId: this.sessionId, epoch: state.lastEpoch + 1 });
|
|
1051
1226
|
return;
|
|
1052
1227
|
}
|
|
1053
1228
|
entries = await log.read();
|
|
1054
|
-
state = this.fold(entries);
|
|
1229
|
+
state = this.fold(entries, delivery);
|
|
1055
1230
|
}
|
|
1056
1231
|
return { timeoutSeconds: 1 };
|
|
1057
1232
|
}
|
|
1058
|
-
async claim(log, state, deadline, early) {
|
|
1233
|
+
async claim(log, state, deadline, early, delivery = null) {
|
|
1059
1234
|
this.running = true;
|
|
1060
1235
|
this.holder = newId("wkr");
|
|
1061
1236
|
const lease = {
|
|
@@ -1064,7 +1239,8 @@ class SessionWorker {
|
|
|
1064
1239
|
holder: this.holder,
|
|
1065
1240
|
epoch: state.lastEpoch + 1,
|
|
1066
1241
|
expiresAt: leaseEnd(this.backend, deadline),
|
|
1067
|
-
...this.backend.holderInfo()
|
|
1242
|
+
...this.backend.holderInfo(),
|
|
1243
|
+
...delivery ? { delivery } : {}
|
|
1068
1244
|
};
|
|
1069
1245
|
this.backend.holding(this.holder, true);
|
|
1070
1246
|
try {
|
|
@@ -1186,19 +1362,20 @@ class SessionWorker {
|
|
|
1186
1362
|
pending: now.unconsumed.filter((input) => input.cursor <= Number(through)),
|
|
1187
1363
|
recent: now.recent,
|
|
1188
1364
|
lastTurnId: now.lastTurnId,
|
|
1365
|
+
...now.agent === null ? {} : { agent: now.agent },
|
|
1189
1366
|
...now.openTurn ? { openTurn: now.openTurn, yielded: now.yielded } : {},
|
|
1190
|
-
lease: { holder: this.holder, epoch: lease.epoch, expiresAt: leaseExpiresAt, pid: lease.pid, host: lease.host }
|
|
1367
|
+
lease: { holder: this.holder, epoch: lease.epoch, expiresAt: leaseExpiresAt, pid: lease.pid, host: lease.host, ...lease.delivery ? { delivery: lease.delivery } : {} }
|
|
1191
1368
|
});
|
|
1192
1369
|
}
|
|
1193
1370
|
};
|
|
1194
1371
|
let turnNow = null;
|
|
1195
1372
|
let renew = null;
|
|
1196
|
-
if (!this.backend
|
|
1373
|
+
if (!heldToDeadline(this.backend, deadline)) {
|
|
1197
1374
|
renew = setInterval(() => {
|
|
1198
1375
|
if (released || failed || this.stopping)
|
|
1199
1376
|
return;
|
|
1200
1377
|
leaseExpiresAt = leaseEnd(this.backend, deadline);
|
|
1201
|
-
chained({ k: "lease", holder: this.holder, epoch: lease.epoch, expiresAt: leaseExpiresAt, ...this.backend.holderInfo() }).catch((error) => {
|
|
1378
|
+
chained({ k: "lease", holder: this.holder, epoch: lease.epoch, expiresAt: leaseExpiresAt, ...this.backend.holderInfo(), ...lease.delivery ? { delivery: lease.delivery } : {} }).catch((error) => {
|
|
1202
1379
|
if (isFenced(error))
|
|
1203
1380
|
turnNow?.cancel({ reason: "handoff" });
|
|
1204
1381
|
});
|
|
@@ -1362,11 +1539,14 @@ class SessionWorker {
|
|
|
1362
1539
|
if (state.lastLease?.holder !== this.holder && !failed)
|
|
1363
1540
|
return "fenced";
|
|
1364
1541
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1542
|
+
if (failed) {
|
|
1543
|
+
await harness.close();
|
|
1544
|
+
harness = null;
|
|
1368
1545
|
return broken();
|
|
1546
|
+
}
|
|
1369
1547
|
await release();
|
|
1548
|
+
agent.track(harness.close().catch(() => {}));
|
|
1549
|
+
harness = null;
|
|
1370
1550
|
return failed ? broken() : "done";
|
|
1371
1551
|
} finally {
|
|
1372
1552
|
if (hardStop)
|
|
@@ -1467,8 +1647,7 @@ class SessionWorker {
|
|
|
1467
1647
|
}
|
|
1468
1648
|
}
|
|
1469
1649
|
var internalEvent = new Set(["turn_start", "turn_resume", "turn_yield", "turn_end", "idle"]);
|
|
1470
|
-
function uiRanker() {
|
|
1471
|
-
let high = 0;
|
|
1650
|
+
function uiRanker(high = 0) {
|
|
1472
1651
|
return (event) => {
|
|
1473
1652
|
if (!Number.isSafeInteger(event?.epoch))
|
|
1474
1653
|
return true;
|
|
@@ -1587,9 +1766,18 @@ function turnView({ messageId, resumeRequest = null, start }) {
|
|
|
1587
1766
|
settle.resolve(repeated ? { ...value, repeated: true } : value);
|
|
1588
1767
|
wake();
|
|
1589
1768
|
};
|
|
1590
|
-
|
|
1769
|
+
const started = start();
|
|
1770
|
+
started.catch(() => {});
|
|
1771
|
+
let following = false;
|
|
1772
|
+
const follow = () => {
|
|
1773
|
+
if (following)
|
|
1774
|
+
return;
|
|
1775
|
+
following = true;
|
|
1776
|
+
read();
|
|
1777
|
+
};
|
|
1778
|
+
const read = async () => {
|
|
1591
1779
|
try {
|
|
1592
|
-
const { log, from, settled, attaching, endWithinMs, unsure } = await
|
|
1780
|
+
const { log, from, settled, attaching, endWithinMs, unsure } = await started;
|
|
1593
1781
|
repeated = settled?.repeated === true || endWithinMs !== undefined && !unsure;
|
|
1594
1782
|
if (settled) {
|
|
1595
1783
|
const { messageId: _id, ...rest } = settled;
|
|
@@ -1654,7 +1842,7 @@ function turnView({ messageId, resumeRequest = null, start }) {
|
|
|
1654
1842
|
} catch (error) {
|
|
1655
1843
|
finish(null, error);
|
|
1656
1844
|
}
|
|
1657
|
-
}
|
|
1845
|
+
};
|
|
1658
1846
|
async function* iterate(raw) {
|
|
1659
1847
|
for (let index = 0;; index += 1) {
|
|
1660
1848
|
while (index >= events.length) {
|
|
@@ -1678,11 +1866,16 @@ function turnView({ messageId, resumeRequest = null, start }) {
|
|
|
1678
1866
|
get messageId() {
|
|
1679
1867
|
return id;
|
|
1680
1868
|
},
|
|
1681
|
-
result
|
|
1869
|
+
get result() {
|
|
1870
|
+
follow();
|
|
1871
|
+
return result;
|
|
1872
|
+
},
|
|
1682
1873
|
[Symbol.asyncIterator]() {
|
|
1874
|
+
follow();
|
|
1683
1875
|
return iterate(false);
|
|
1684
1876
|
},
|
|
1685
1877
|
get readable() {
|
|
1878
|
+
follow();
|
|
1686
1879
|
const iterator = iterate(true);
|
|
1687
1880
|
return new ReadableStream({
|
|
1688
1881
|
async pull(controller) {
|
|
@@ -1709,10 +1902,13 @@ function createDurableAgentFactory({ harness, defaultDurability, name = "createA
|
|
|
1709
1902
|
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
1710
1903
|
throw new TypeError(`${name}() options must be an object`);
|
|
1711
1904
|
}
|
|
1712
|
-
const { durability, ...harnessOptions } = options;
|
|
1905
|
+
const { durability, name: agentName = defaultAgentName, ...harnessOptions } = options;
|
|
1713
1906
|
if (durability !== undefined && !isDurability(durability)) {
|
|
1714
1907
|
throw new TypeError("durability must come from memory(), local(), vercel() or world()");
|
|
1715
1908
|
}
|
|
1909
|
+
if (typeof agentName !== "string" || !agentNameRule.test(agentName)) {
|
|
1910
|
+
throw new TypeError(`${name}() name must be 1 to 64 letters, digits, ".", "_" or "-", starting with a letter or digit`);
|
|
1911
|
+
}
|
|
1716
1912
|
const plugged = harness(harnessOptions);
|
|
1717
1913
|
const seed = plugged.seed === undefined ? undefined : toBase64(plugged.seed);
|
|
1718
1914
|
const emit = (type, detail = {}) => {
|
|
@@ -1721,7 +1917,12 @@ function createDurableAgentFactory({ harness, defaultDurability, name = "createA
|
|
|
1721
1917
|
} catch {}
|
|
1722
1918
|
};
|
|
1723
1919
|
const agent = {
|
|
1920
|
+
name: agentName,
|
|
1724
1921
|
emit,
|
|
1922
|
+
track(work) {
|
|
1923
|
+
active.add(work);
|
|
1924
|
+
work.finally(() => active.delete(work));
|
|
1925
|
+
},
|
|
1725
1926
|
openHarness: (sessionId, store, run, ready) => plugged.open({
|
|
1726
1927
|
sessionId,
|
|
1727
1928
|
store,
|
|
@@ -1732,6 +1933,7 @@ function createDurableAgentFactory({ harness, defaultDurability, name = "createA
|
|
|
1732
1933
|
})
|
|
1733
1934
|
};
|
|
1734
1935
|
let backendPromise = null;
|
|
1936
|
+
let durabilityPromise = null;
|
|
1735
1937
|
let chosenDurability = null;
|
|
1736
1938
|
let unlisten = null;
|
|
1737
1939
|
let closed = false;
|
|
@@ -1741,33 +1943,48 @@ function createDurableAgentFactory({ harness, defaultDurability, name = "createA
|
|
|
1741
1943
|
if (closed)
|
|
1742
1944
|
return Promise.reject(new Error(`${label} is closed`));
|
|
1743
1945
|
backendPromise ??= (async () => {
|
|
1744
|
-
const chosen =
|
|
1946
|
+
const chosen = await durabilityOf();
|
|
1745
1947
|
chosenDurability = chosen;
|
|
1746
1948
|
const created = await createBackend(chosen);
|
|
1747
1949
|
await created.start?.();
|
|
1748
|
-
unlisten = created.listen((message) =>
|
|
1749
|
-
let worker = workers.get(message.sessionId);
|
|
1750
|
-
if (!worker) {
|
|
1751
|
-
worker = new SessionWorker(agent, created, message.sessionId);
|
|
1752
|
-
workers.set(message.sessionId, worker);
|
|
1753
|
-
}
|
|
1754
|
-
worker.deliveries += 1;
|
|
1755
|
-
const work = worker.consume(message);
|
|
1756
|
-
active.add(work);
|
|
1757
|
-
work.catch((error) => {
|
|
1758
|
-
emit("session.error", { sessionId: message.sessionId, error: errorSummary(error) });
|
|
1759
|
-
}).finally(() => {
|
|
1760
|
-
active.delete(work);
|
|
1761
|
-
worker.deliveries -= 1;
|
|
1762
|
-
if (worker.deliveries === 0 && !worker.running && workers.get(message.sessionId) === worker)
|
|
1763
|
-
workers.delete(message.sessionId);
|
|
1764
|
-
});
|
|
1765
|
-
return work;
|
|
1766
|
-
});
|
|
1950
|
+
unlisten = created.listen((message, meta) => receive(created, message, deliveryOf(meta)), agentName);
|
|
1767
1951
|
return created;
|
|
1768
1952
|
})();
|
|
1769
1953
|
return backendPromise;
|
|
1770
1954
|
};
|
|
1955
|
+
const durabilityOf = () => durabilityPromise ??= durability ? Promise.resolve(durability) : defaultDurability();
|
|
1956
|
+
const member = { open: backend, durability: durabilityOf };
|
|
1957
|
+
const registered = joinGroup(agentsByName, agentName, member);
|
|
1958
|
+
const receive = (created, message, delivery, { inRequest = false } = {}) => {
|
|
1959
|
+
let worker = workers.get(message.sessionId);
|
|
1960
|
+
if (!worker) {
|
|
1961
|
+
worker = new SessionWorker(agent, created, message.sessionId);
|
|
1962
|
+
workers.set(message.sessionId, worker);
|
|
1963
|
+
}
|
|
1964
|
+
worker.deliveries += 1;
|
|
1965
|
+
let work = worker.consume(message, delivery);
|
|
1966
|
+
if (inRequest) {
|
|
1967
|
+
work = work.then(async (result) => {
|
|
1968
|
+
if (Number.isFinite(result?.timeoutSeconds)) {
|
|
1969
|
+
await created.queue.send(message, {
|
|
1970
|
+
idempotencyKey: `${message.sessionId}:${message.type}:${message.messageId}:${newId("again")}`,
|
|
1971
|
+
...result.timeoutSeconds > 0 ? { delaySeconds: result.timeoutSeconds } : {}
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
return result;
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
active.add(work);
|
|
1978
|
+
work.catch((error) => {
|
|
1979
|
+
emit("session.error", { sessionId: message.sessionId, error: errorSummary(error) });
|
|
1980
|
+
}).finally(() => {
|
|
1981
|
+
active.delete(work);
|
|
1982
|
+
worker.deliveries -= 1;
|
|
1983
|
+
if (worker.deliveries === 0 && !worker.running && workers.get(message.sessionId) === worker)
|
|
1984
|
+
workers.delete(message.sessionId);
|
|
1985
|
+
});
|
|
1986
|
+
return work;
|
|
1987
|
+
};
|
|
1771
1988
|
const openSession = (requestedId, sessionOptions = {}) => {
|
|
1772
1989
|
if (requestedId !== undefined && !validId(requestedId))
|
|
1773
1990
|
throw new TypeError(`session id must be ${idRule}`);
|
|
@@ -1789,19 +2006,33 @@ function createDurableAgentFactory({ harness, defaultDurability, name = "createA
|
|
|
1789
2006
|
return { created, id };
|
|
1790
2007
|
});
|
|
1791
2008
|
resolved.catch(() => {});
|
|
1792
|
-
const send = async (message) => {
|
|
2009
|
+
const send = async (message, { inRequest = false } = {}) => {
|
|
1793
2010
|
const { created, id: sessionId } = await resolved;
|
|
1794
2011
|
const extra = {
|
|
1795
2012
|
...seeded ? {} : { seed },
|
|
1796
2013
|
...context === undefined ? {} : { context },
|
|
1797
2014
|
...settings === undefined ? {} : { settings }
|
|
1798
2015
|
};
|
|
1799
|
-
const full = { ...message, ...extra, sessionId };
|
|
2016
|
+
const full = { ...message, ...extra, agent: agentName, sessionId };
|
|
1800
2017
|
if (!created.queueDurable) {
|
|
1801
2018
|
const log = await created.session(sessionId);
|
|
1802
2019
|
await recordMessage(log, foldSessionLog(await log.read()), full);
|
|
1803
2020
|
}
|
|
1804
|
-
|
|
2021
|
+
const idempotencyKey = `${sessionId}:${message.type}:${message.messageId}`;
|
|
2022
|
+
if (inRequest && created.runInRequest) {
|
|
2023
|
+
await created.refreshDeadline?.();
|
|
2024
|
+
const deadline = deliveryDeadline(created);
|
|
2025
|
+
if (deadline !== null) {
|
|
2026
|
+
const delaySeconds = Math.ceil(Math.max(0, deadline - Date.now()) / 1000) + inlineBackstopMarginSeconds;
|
|
2027
|
+
const { messageId: backstop } = await created.queue.send(full, { idempotencyKey, delaySeconds }) ?? {};
|
|
2028
|
+
seeded = true;
|
|
2029
|
+
const delivery = backstop ? { messageId: backstop, attempt: 0 } : null;
|
|
2030
|
+
const work = receive(created, full, delivery, { inRequest: true }).then(() => {}, () => {});
|
|
2031
|
+
created.waitUntil?.(work);
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
await created.queue.send(full, { idempotencyKey });
|
|
1805
2036
|
seeded = true;
|
|
1806
2037
|
};
|
|
1807
2038
|
const startTurn = (type, messageId, input, retried = false, aborted = false) => {
|
|
@@ -1823,7 +2054,7 @@ function createDurableAgentFactory({ harness, defaultDurability, name = "createA
|
|
|
1823
2054
|
from = await log.ui.length();
|
|
1824
2055
|
const [state] = await Promise.all([
|
|
1825
2056
|
log.read().then(foldSessionLog),
|
|
1826
|
-
send({ type, messageId, ...input === undefined ? {} : { input } }).then(() => accept.resolve({ messageId, sessionId }))
|
|
2057
|
+
send({ type, messageId, ...input === undefined ? {} : { input } }, { inRequest: type === "prompt" }).then(() => accept.resolve({ messageId, sessionId }))
|
|
1827
2058
|
]);
|
|
1828
2059
|
const outcome = await retryOutcome(log, state, messageId, from, { sent: true });
|
|
1829
2060
|
if (outcome.settled)
|
|
@@ -1849,7 +2080,7 @@ function createDurableAgentFactory({ harness, defaultDurability, name = "createA
|
|
|
1849
2080
|
return { settled: { messageId, stopReason: "cancelled", usage: {} } };
|
|
1850
2081
|
}
|
|
1851
2082
|
from ??= await log.ui.length();
|
|
1852
|
-
await send({ type, messageId, ...input === undefined ? {} : { input } });
|
|
2083
|
+
await send({ type, messageId, ...input === undefined ? {} : { input } }, { inRequest: type === "prompt" });
|
|
1853
2084
|
accept.resolve({ messageId, sessionId });
|
|
1854
2085
|
return { log, from, attaching };
|
|
1855
2086
|
} catch (error) {
|
|
@@ -1899,12 +2130,40 @@ function createDurableAgentFactory({ harness, defaultDurability, name = "createA
|
|
|
1899
2130
|
let reader = null;
|
|
1900
2131
|
let buffered = "";
|
|
1901
2132
|
let next = 0;
|
|
1902
|
-
|
|
2133
|
+
let shows = uiRanker();
|
|
2134
|
+
const open = async (ui) => {
|
|
2135
|
+
if (cursor === 0)
|
|
2136
|
+
return ui.read(0).getReader();
|
|
2137
|
+
const at = ui.read(cursor - 1).getReader();
|
|
2138
|
+
let first = "";
|
|
2139
|
+
for (;; ) {
|
|
2140
|
+
const index = first.indexOf(`
|
|
2141
|
+
`);
|
|
2142
|
+
if (index >= 0) {
|
|
2143
|
+
let epoch = null;
|
|
2144
|
+
try {
|
|
2145
|
+
epoch = JSON.parse(first.slice(0, index))?.epoch;
|
|
2146
|
+
} catch {}
|
|
2147
|
+
if (!Number.isSafeInteger(epoch))
|
|
2148
|
+
break;
|
|
2149
|
+
shows = uiRanker(epoch);
|
|
2150
|
+
buffered = first.slice(index + 1);
|
|
2151
|
+
next = cursor;
|
|
2152
|
+
return at;
|
|
2153
|
+
}
|
|
2154
|
+
const { value, done } = await at.read();
|
|
2155
|
+
if (done)
|
|
2156
|
+
return at;
|
|
2157
|
+
first += decoder.decode(value, { stream: true });
|
|
2158
|
+
}
|
|
2159
|
+
at.cancel().catch(() => {});
|
|
2160
|
+
return ui.read(0).getReader();
|
|
2161
|
+
};
|
|
1903
2162
|
return new ReadableStream({
|
|
1904
2163
|
async pull(controller) {
|
|
1905
2164
|
if (!reader) {
|
|
1906
2165
|
const { created, id: sessionId } = await resolved;
|
|
1907
|
-
reader = (await created.session(sessionId)).ui
|
|
2166
|
+
reader = await open((await created.session(sessionId)).ui);
|
|
1908
2167
|
}
|
|
1909
2168
|
for (;; ) {
|
|
1910
2169
|
const index = buffered.indexOf(`
|
|
@@ -1974,7 +2233,12 @@ function createDurableAgentFactory({ harness, defaultDurability, name = "createA
|
|
|
1974
2233
|
const durableAgent = {
|
|
1975
2234
|
[durableInternals]: {
|
|
1976
2235
|
stopAtDeadline: (sessionId) => workers.get(sessionId)?.stopNow?.(),
|
|
2236
|
+
settled: async (sessionId) => {
|
|
2237
|
+
await workers.get(sessionId)?.settled?.();
|
|
2238
|
+
},
|
|
2239
|
+
deliver: async (message, delivery) => receive(await backend(), message, delivery),
|
|
1977
2240
|
liveWorkers: () => workers.size,
|
|
2241
|
+
openLog: async (sessionId) => (await backend()).session(sessionId),
|
|
1978
2242
|
lastLease: async (sessionId) => {
|
|
1979
2243
|
const log = await (await backend()).session(sessionId);
|
|
1980
2244
|
return foldSessionLog(await log.read()).lastLease;
|
|
@@ -1993,6 +2257,9 @@ function createDurableAgentFactory({ harness, defaultDurability, name = "createA
|
|
|
1993
2257
|
async newSessionId() {
|
|
1994
2258
|
return (await backend()).newSessionId();
|
|
1995
2259
|
},
|
|
2260
|
+
get name() {
|
|
2261
|
+
return agentName;
|
|
2262
|
+
},
|
|
1996
2263
|
get sessionId() {
|
|
1997
2264
|
return defaultSession?.id ?? null;
|
|
1998
2265
|
},
|
|
@@ -2016,6 +2283,7 @@ function createDurableAgentFactory({ harness, defaultDurability, name = "createA
|
|
|
2016
2283
|
if (closed)
|
|
2017
2284
|
return;
|
|
2018
2285
|
closed = true;
|
|
2286
|
+
leaveGroup(agentsByName, agentName, registered, member);
|
|
2019
2287
|
while (active.size > 0)
|
|
2020
2288
|
await Promise.allSettled([...active]);
|
|
2021
2289
|
if (backendPromise) {
|
|
@@ -2484,6 +2752,7 @@ var maxApiKeyBytes = 64 * 1024;
|
|
|
2484
2752
|
var maxModelBytes = 1024;
|
|
2485
2753
|
var maxEffortBytes = 64;
|
|
2486
2754
|
var maxUrlBytes = 16 * 1024;
|
|
2755
|
+
var unfinishedToolOutput = "fx stopped while this tool was running, so it may have partly run. Check its effects before running it again.";
|
|
2487
2756
|
var maxModelCatalogBytes = 4 * 1024 * 1024;
|
|
2488
2757
|
var maxModelCatalogEntries = 1e4;
|
|
2489
2758
|
var streamReadsPerTaskYield = 32;
|
|
@@ -2721,12 +2990,20 @@ function journalAppendError(cause) {
|
|
|
2721
2990
|
function withoutSupersededProgress(events) {
|
|
2722
2991
|
const kept = new Array(events.length);
|
|
2723
2992
|
let superseded = false;
|
|
2993
|
+
let answered = false;
|
|
2724
2994
|
for (let index = events.length - 1;index >= 0; index -= 1) {
|
|
2725
2995
|
const event = events[index];
|
|
2726
2996
|
const type = event?.type;
|
|
2727
|
-
|
|
2728
|
-
|
|
2997
|
+
const dropped = event?.data != null && (type === "turn_progress" && superseded || type === "tool_result" && answered);
|
|
2998
|
+
kept[index] = dropped ? { ...event, data: null } : event;
|
|
2999
|
+
if (type === "turn_committed" || type === "turn_progress_cleared") {
|
|
2729
3000
|
superseded = true;
|
|
3001
|
+
answered = true;
|
|
3002
|
+
} else if (type === "turn_progress") {
|
|
3003
|
+
answered = true;
|
|
3004
|
+
if (event.base === undefined)
|
|
3005
|
+
superseded = true;
|
|
3006
|
+
}
|
|
2730
3007
|
}
|
|
2731
3008
|
return kept;
|
|
2732
3009
|
}
|
|
@@ -2836,9 +3113,9 @@ function journalMarks(events) {
|
|
|
2836
3113
|
if (event?.type === "turn_progress") {
|
|
2837
3114
|
if (typeof event.turnId === "string")
|
|
2838
3115
|
marks.push({ start: event.turnId });
|
|
2839
|
-
const steps = event.data?.execution?.tool_steps;
|
|
2840
|
-
if (
|
|
2841
|
-
marks.push({ step: `${steps
|
|
3116
|
+
const steps = Number.isSafeInteger(event.steps) ? event.steps : event.data?.execution?.tool_steps?.length;
|
|
3117
|
+
if (Number.isSafeInteger(steps))
|
|
3118
|
+
marks.push({ step: `${steps}:${String(event.data?.tool_state ?? "none")}` });
|
|
2842
3119
|
} else if (event?.type === "turn_committed" || event?.type === "turn_progress_cleared")
|
|
2843
3120
|
marks.push({ end: true });
|
|
2844
3121
|
else if (event?.type === "turn_yielded")
|
|
@@ -4779,6 +5056,7 @@ async function createFxEngine(options = {}) {
|
|
|
4779
5056
|
let coreExitError = null;
|
|
4780
5057
|
const isCurrentTurn = (turn) => turn && activeTurn === turn && !turn.cancelled && !closing;
|
|
4781
5058
|
const persistence = options.persistence ?? null;
|
|
5059
|
+
const wantsMarks = persistence?.marks === true || persistence?.[journalMarksWanted] === true;
|
|
4782
5060
|
const checkpointAfterBytes = options.checkpointAfterBytes ?? defaultCheckpointAfterBytes;
|
|
4783
5061
|
let journalHead = null;
|
|
4784
5062
|
let appendedSeq = 0;
|
|
@@ -4786,12 +5064,12 @@ async function createFxEngine(options = {}) {
|
|
|
4786
5064
|
let appendTail = Promise.resolve();
|
|
4787
5065
|
const writerId = crypto.randomUUID();
|
|
4788
5066
|
const journal = persistence && {
|
|
4789
|
-
append(events) {
|
|
5067
|
+
append(events, barrier) {
|
|
4790
5068
|
const data = encodeJournalRecord(events);
|
|
4791
5069
|
const lastSeq = events.at(-1)?.seq;
|
|
4792
|
-
const marks =
|
|
5070
|
+
const marks = wantsMarks ? { marks: journalMarks(events) } : {};
|
|
4793
5071
|
const appended = appendTail.then(async () => {
|
|
4794
|
-
const result = await persistence.append({ expected: journalHead, idempotencyKey: `${writerId}:${events[0]?.seq}`, data, ...marks });
|
|
5072
|
+
const result = await persistence.append({ expected: journalHead, idempotencyKey: `${writerId}:${events[0]?.seq}`, data, barrier, ...marks });
|
|
4795
5073
|
if (!validCursor(result?.cursor))
|
|
4796
5074
|
throw new TypeError("persistence.append() must resolve to { cursor }");
|
|
4797
5075
|
journalHead = result.cursor;
|
|
@@ -4807,12 +5085,15 @@ async function createFxEngine(options = {}) {
|
|
|
4807
5085
|
let openTurnId = null;
|
|
4808
5086
|
let lastTurnId = null;
|
|
4809
5087
|
let ambiguousCalls = [];
|
|
4810
|
-
const
|
|
5088
|
+
const settleAmbiguous = (onAmbiguous) => {
|
|
4811
5089
|
const calls = ambiguousCalls;
|
|
4812
5090
|
ambiguousCalls = [];
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
5091
|
+
const rerun = onAmbiguous === undefined ? [] : calls.filter((call) => onAmbiguous({ executionId: call.id, name: call.name, input: parsedToolInput(call.arguments) }) === "rerun").map((call) => call.id);
|
|
5092
|
+
return { rerun, answered: calls.filter((call) => !rerun.includes(call.id)) };
|
|
5093
|
+
};
|
|
5094
|
+
const resumeOpenTurn = (promptOptions) => {
|
|
5095
|
+
const { rerun, answered } = settleAmbiguous(promptOptions.onAmbiguous);
|
|
5096
|
+
return normalizeTurn(startTurn(null, { ...promptOptions, [resumeTurn]: true, [rerunCallIds]: rerun }), answered);
|
|
4816
5097
|
};
|
|
4817
5098
|
const followUps = [];
|
|
4818
5099
|
const runNextFollowUp = (resuming = false) => {
|
|
@@ -4834,6 +5115,8 @@ async function createFxEngine(options = {}) {
|
|
|
4834
5115
|
let journalQueue = [];
|
|
4835
5116
|
let journalFlushScheduled = false;
|
|
4836
5117
|
const journalPending = new Set;
|
|
5118
|
+
let journalInFlight = null;
|
|
5119
|
+
let journalBarrier = false;
|
|
4837
5120
|
let journalFailure = null;
|
|
4838
5121
|
let handedOff = false;
|
|
4839
5122
|
const handedOffError = () => new Error("this agent handed its session off; open it again to continue");
|
|
@@ -4873,7 +5156,7 @@ async function createFxEngine(options = {}) {
|
|
|
4873
5156
|
journalFailureReported = true;
|
|
4874
5157
|
return journalFailure;
|
|
4875
5158
|
};
|
|
4876
|
-
const queueJournalEvents = (events) => {
|
|
5159
|
+
const queueJournalEvents = (events, barrier = false) => {
|
|
4877
5160
|
if (journalFailure || !Array.isArray(events) || events.length === 0)
|
|
4878
5161
|
return;
|
|
4879
5162
|
if (handedOff) {
|
|
@@ -4882,6 +5165,8 @@ async function createFxEngine(options = {}) {
|
|
|
4882
5165
|
return;
|
|
4883
5166
|
}
|
|
4884
5167
|
journalQueue.push(...events);
|
|
5168
|
+
if (barrier || events.some((event) => event?.type === "input_accepted" || event?.type === "input_withdrawn"))
|
|
5169
|
+
journalBarrier = true;
|
|
4885
5170
|
if (events.every((event) => event?.type === "session_config"))
|
|
4886
5171
|
return;
|
|
4887
5172
|
if (journalFlushScheduled)
|
|
@@ -4891,14 +5176,16 @@ async function createFxEngine(options = {}) {
|
|
|
4891
5176
|
};
|
|
4892
5177
|
function flushJournal() {
|
|
4893
5178
|
journalFlushScheduled = false;
|
|
4894
|
-
if (journalFailure || journalQueue.length === 0)
|
|
5179
|
+
if (journalFailure || journalInFlight || journalQueue.length === 0)
|
|
4895
5180
|
return;
|
|
4896
5181
|
const batch = journalQueue;
|
|
4897
5182
|
journalQueue = [];
|
|
4898
|
-
|
|
5183
|
+
const barrier = journalBarrier;
|
|
5184
|
+
journalBarrier = false;
|
|
5185
|
+
emit("journal.append", { events: batch.length, barrier });
|
|
4899
5186
|
let appended;
|
|
4900
5187
|
try {
|
|
4901
|
-
appended = Promise.resolve(journal.append(batch));
|
|
5188
|
+
appended = Promise.resolve(journal.append(batch, barrier));
|
|
4902
5189
|
} catch (error) {
|
|
4903
5190
|
appended = Promise.reject(error);
|
|
4904
5191
|
}
|
|
@@ -4912,7 +5199,12 @@ async function createFxEngine(options = {}) {
|
|
|
4912
5199
|
emit("journal.error", { error: error instanceof Error ? error.name : "Error" });
|
|
4913
5200
|
rejectInputWaiters([...inputWaiters.keys()], journalFailure);
|
|
4914
5201
|
activeTurn?.cancel();
|
|
4915
|
-
}).finally(() =>
|
|
5202
|
+
}).finally(() => {
|
|
5203
|
+
journalPending.delete(settled);
|
|
5204
|
+
journalInFlight = null;
|
|
5205
|
+
flushJournal();
|
|
5206
|
+
});
|
|
5207
|
+
journalInFlight = settled;
|
|
4916
5208
|
journalPending.add(settled);
|
|
4917
5209
|
}
|
|
4918
5210
|
let journalClosed = null;
|
|
@@ -4922,6 +5214,8 @@ async function createFxEngine(options = {}) {
|
|
|
4922
5214
|
}
|
|
4923
5215
|
async function journalSettled() {
|
|
4924
5216
|
for (;; ) {
|
|
5217
|
+
if (journalQueue.length > 0)
|
|
5218
|
+
journalBarrier = true;
|
|
4925
5219
|
if (journalFlushScheduled || journalQueue.length > 0)
|
|
4926
5220
|
flushJournal();
|
|
4927
5221
|
if (journalPending.size === 0)
|
|
@@ -5202,7 +5496,7 @@ async function createFxEngine(options = {}) {
|
|
|
5202
5496
|
emit("acp.receive", { message });
|
|
5203
5497
|
if (message.method === "libfx/journal_append") {
|
|
5204
5498
|
if (journal && message.params?.sessionId === sessionId) {
|
|
5205
|
-
queueJournalEvents(message.params.events);
|
|
5499
|
+
queueJournalEvents(message.params.events, message.params.barrier === true);
|
|
5206
5500
|
}
|
|
5207
5501
|
return;
|
|
5208
5502
|
}
|
|
@@ -5282,7 +5576,11 @@ async function createFxEngine(options = {}) {
|
|
|
5282
5576
|
sessionId = sessionResult.sessionId;
|
|
5283
5577
|
if (initialCheckpoint) {
|
|
5284
5578
|
const [checkpointAttachment] = attachBytes([initialCheckpoint]);
|
|
5285
|
-
const restored = await request("libfx/restore", { sessionId, checkpointAttachment })
|
|
5579
|
+
const restored = await request("libfx/restore", { sessionId, checkpointAttachment }).catch((error) => {
|
|
5580
|
+
if (/newer fx/.test(error?.message ?? ""))
|
|
5581
|
+
throw new FxJournalVersionError(error.message);
|
|
5582
|
+
throw error;
|
|
5583
|
+
});
|
|
5286
5584
|
compareCheckpointMeta(restored?.checkpointMeta);
|
|
5287
5585
|
}
|
|
5288
5586
|
if (journal) {
|
|
@@ -5373,7 +5671,7 @@ async function createFxEngine(options = {}) {
|
|
|
5373
5671
|
if (turnId !== undefined && journal) {
|
|
5374
5672
|
if (resumable && turnId === openTurnId) {
|
|
5375
5673
|
resumable = false;
|
|
5376
|
-
return
|
|
5674
|
+
return resumeOpenTurn(promptOptions);
|
|
5377
5675
|
}
|
|
5378
5676
|
if (turnId === lastTurnId)
|
|
5379
5677
|
return endedTurn(turnId);
|
|
@@ -5394,7 +5692,7 @@ async function createFxEngine(options = {}) {
|
|
|
5394
5692
|
checkOnAmbiguous(promptOptions.onAmbiguous);
|
|
5395
5693
|
if (resumable) {
|
|
5396
5694
|
resumable = false;
|
|
5397
|
-
return
|
|
5695
|
+
return resumeOpenTurn(promptOptions);
|
|
5398
5696
|
}
|
|
5399
5697
|
const next = followUps.find((entry) => entry.held);
|
|
5400
5698
|
if (!next)
|
|
@@ -5494,7 +5792,7 @@ async function createFxEngine(options = {}) {
|
|
|
5494
5792
|
}
|
|
5495
5793
|
};
|
|
5496
5794
|
return agent;
|
|
5497
|
-
function normalizeTurn(rawTurn) {
|
|
5795
|
+
function normalizeTurn(rawTurn, answered = []) {
|
|
5498
5796
|
const toolNames = new Map;
|
|
5499
5797
|
const started = new Set;
|
|
5500
5798
|
const eventFor = (update) => {
|
|
@@ -5555,6 +5853,9 @@ async function createFxEngine(options = {}) {
|
|
|
5555
5853
|
},
|
|
5556
5854
|
[Symbol.asyncIterator]() {
|
|
5557
5855
|
const iterator = async function* () {
|
|
5856
|
+
for (const call of answered) {
|
|
5857
|
+
yield { type: "tool_end", id: call.id, name: call.name, content: unfinishedToolOutput, isError: true };
|
|
5858
|
+
}
|
|
5558
5859
|
for await (const update of rawTurn) {
|
|
5559
5860
|
const event = eventFor(update);
|
|
5560
5861
|
if (event)
|
|
@@ -5934,6 +6235,7 @@ async function createFxEngine(options = {}) {
|
|
|
5934
6235
|
|
|
5935
6236
|
// sdk/fx-harness.js
|
|
5936
6237
|
var encoder5 = new TextEncoder;
|
|
6238
|
+
var durableCheckpointAfterBytes = 64 * 1024;
|
|
5937
6239
|
function fxHarness({ createEngine, defaultApiKey = async () => {
|
|
5938
6240
|
return;
|
|
5939
6241
|
} }) {
|
|
@@ -5958,7 +6260,7 @@ function fxHarness({ createEngine, defaultApiKey = async () => {
|
|
|
5958
6260
|
[credentialMemory]: credentials,
|
|
5959
6261
|
persistence: { ...store, [journalMarksWanted]: true },
|
|
5960
6262
|
inputsDurable: true,
|
|
5961
|
-
checkpointAfterBytes:
|
|
6263
|
+
checkpointAfterBytes: durableCheckpointAfterBytes,
|
|
5962
6264
|
...context === undefined || context === null ? {} : { toolContext: context }
|
|
5963
6265
|
});
|
|
5964
6266
|
return fxSession(engine, idempotent);
|
|
@@ -6080,9 +6382,16 @@ function claimedTools(tools, ready) {
|
|
|
6080
6382
|
}
|
|
6081
6383
|
function idempotentTools(tools) {
|
|
6082
6384
|
const names = new Set;
|
|
6083
|
-
for (const tool of toolEntries(tools))
|
|
6084
|
-
if (tool
|
|
6385
|
+
for (const [index, tool] of toolEntries(tools).entries()) {
|
|
6386
|
+
if (!tool || typeof tool !== "object" || tool.providerExecuted === true)
|
|
6387
|
+
continue;
|
|
6388
|
+
if (typeof tool.idempotent !== "boolean") {
|
|
6389
|
+
const name = typeof tool.name === "string" ? tool.name : index;
|
|
6390
|
+
throw new TypeError(`tool ${name} must set idempotent: true if running a call twice is safe, or idempotent: false if it is not`);
|
|
6391
|
+
}
|
|
6392
|
+
if (tool.idempotent && typeof tool.name === "string")
|
|
6085
6393
|
names.add(tool.name);
|
|
6394
|
+
}
|
|
6086
6395
|
return names;
|
|
6087
6396
|
}
|
|
6088
6397
|
function checkpointBytesOf(value) {
|
|
@@ -6107,7 +6416,7 @@ function snapshotOf(checkpoint) {
|
|
|
6107
6416
|
}
|
|
6108
6417
|
|
|
6109
6418
|
// sdk/node.js
|
|
6110
|
-
var libfxApiVersion =
|
|
6419
|
+
var libfxApiVersion = 3;
|
|
6111
6420
|
var nativeCoreApiVersion = 4;
|
|
6112
6421
|
var fetchOperationStale = 0;
|
|
6113
6422
|
var fetchOperationApplied = 1;
|
|
@@ -6792,14 +7101,26 @@ async function createWithFallback(surface, nativeMethod, wasmFactory, defaultWas
|
|
|
6792
7101
|
async function createFxEngine2(options = {}) {
|
|
6793
7102
|
return createWithFallback("agent", "createCore", createFxEngine, defaultCoreWasm, options);
|
|
6794
7103
|
}
|
|
6795
|
-
|
|
7104
|
+
var environments = new Map;
|
|
7105
|
+
function environmentDurability() {
|
|
7106
|
+
const dir = process.env.VERCEL ? null : process.env.FX_SESSIONS_DIR || import_node_path.resolve(import_node_os.tmpdir(), "libfx", "sessions");
|
|
7107
|
+
const key = dir ?? "vercel";
|
|
7108
|
+
if (!environments.has(key)) {
|
|
7109
|
+
environments.set(key, chooseEnvironmentDurability(dir).catch((error) => {
|
|
7110
|
+
environments.delete(key);
|
|
7111
|
+
throw error;
|
|
7112
|
+
}));
|
|
7113
|
+
}
|
|
7114
|
+
return environments.get(key);
|
|
7115
|
+
}
|
|
7116
|
+
async function chooseEnvironmentDurability(dir) {
|
|
6796
7117
|
const load = (asset) => import((asset.protocol === "" ? bundledAssetUrl(asset) : asset).href);
|
|
6797
|
-
if (
|
|
7118
|
+
if (dir === null) {
|
|
6798
7119
|
const { vercel } = await load(new URL("./durable/vercel.mjs", __libfxModuleUrl));
|
|
6799
7120
|
return vercel();
|
|
6800
7121
|
}
|
|
6801
7122
|
const { local } = await load(new URL("./durable/local.mjs", __libfxModuleUrl));
|
|
6802
|
-
return local({ dir
|
|
7123
|
+
return local({ dir });
|
|
6803
7124
|
}
|
|
6804
7125
|
var createFxAgent = createDurableAgentFactory({
|
|
6805
7126
|
name: "createFxAgent",
|