narraeon 0.6.0 → 0.6.2
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 +11 -0
- package/README.zh-CN.md +8 -0
- package/dist/node/protocol/v1.js +12 -2
- package/dist/node/runtime/FileNativeExclusiveLink.js +39 -0
- package/dist/node/runtime/V1Runtime.js +5 -5
- package/dist/node/runtime/archive/WorldArchives.js +14 -46
- package/dist/node/runtime/artifact/ArtifactStoreFiles.js +330 -0
- package/dist/node/runtime/artifact/FileNativeArtifactStore.js +495 -1535
- package/dist/node/runtime/artifact/LegacyArtifactStoreMigration.js +260 -0
- package/dist/node/runtime/authoring/ModelToolConversation.js +25 -2
- package/dist/node/runtime/config/AppPreferencesStore.js +0 -1
- package/dist/node/runtime/config/ProtagonistTemplateStore.js +0 -1
- package/dist/node/runtime/config/dataRootLock.js +108 -0
- package/dist/node/runtime/content/ContentWorkspace.js +0 -10
- package/dist/node/runtime/content/CurrentTreeContentLibrary.js +7 -44
- package/dist/node/runtime/extension/PackageScriptPermissions.js +0 -8
- package/dist/node/runtime/extension/WorldExtensionControls.js +0 -8
- package/dist/node/runtime/model/ModelConnectionStore.js +0 -1
- package/dist/node/runtime/play/FileNativeContinuityCorrection.js +3 -14
- package/dist/node/runtime/play/FileNativePlayAdvanceStore.js +32 -207
- package/dist/node/runtime/play/FileNativePlayTimelineStore.js +29 -239
- package/dist/node/runtime/play/PlayCallChain.js +338 -366
- package/dist/node/runtime/play/PlayDocumentTools.js +8 -125
- package/dist/node/runtime/play/PlayFollowupRequests.js +2 -2
- package/dist/node/runtime/play/PlayTraceRetention.js +8 -36
- package/dist/node/runtime/prompt/WorldMaintenanceReport.js +39 -7
- package/dist/node/runtime/setting/FileNativeSettingImprovementStore.js +7 -16
- package/dist/node/runtime/setting/SettingAuthoringTransaction.js +14 -6
- package/dist/node/runtime/setting/SettingImprovementSession.js +39 -25
- package/dist/node/runtime/world/FileNativeWorldStorageMigrator.js +213 -1033
- package/dist/node/runtime/world/FileNativeWorldStore.js +230 -781
- package/dist/node/runtime/world/{FileNativeAuthorityV3.js → LegacyAuthorityV3Reader.js} +13 -404
- package/dist/node/runtime/world/WorldActivity.js +14 -22
- package/dist/node/runtime/world/WorldDocumentMaintenance.js +2 -2
- package/dist/node/runtime/world/WorldLedgerAuthority.js +340 -0
- package/dist/node/runtime/world/WorldOperationCoordinator.js +59 -401
- package/dist/node/runtime/world/WorldQueue.js +46 -0
- package/dist/node/runtime/world/WorldTurnLedger.js +353 -0
- package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +1 -13
- package/dist/node/runtime/world-revision/WorldRevisionSession.js +29 -2
- package/dist/node/server/mobile.js +52 -0
- package/dist/node/server/startWebServer.js +15 -8
- package/dist/web/assets/index-CXnFaYK9.js +1323 -0
- package/dist/web/assets/{index-BJ-1Mb0e.css → index-LPMV0pl3.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +2 -1
- package/dist/node/runtime/FileNativePhysicalClone.js +0 -86
- package/dist/web/assets/index-BgBL8G-A.js +0 -1323
|
@@ -4,60 +4,30 @@ import { isPlayFailureKind, } from "../../protocol/playFailure.js";
|
|
|
4
4
|
import { isNarrativeOrigin, } from "../history/HistoryBackground.js";
|
|
5
5
|
import { encodePlayPromptRun, decodePlayPromptRun, } from "./PlayPromptRun.js";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
-
import {
|
|
7
|
+
import { constants } from "node:fs";
|
|
8
|
+
import { chmod, copyFile, mkdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
|
|
9
|
+
import { linkExclusive } from "../FileNativeExclusiveLink.js";
|
|
8
10
|
import { dirname, join, resolve } from "node:path";
|
|
9
11
|
import { isDeepStrictEqual } from "node:util";
|
|
10
|
-
import { cloneFilePhysically } from "../FileNativePhysicalClone.js";
|
|
11
12
|
export class FileNativePlayTimelineStore {
|
|
12
13
|
#worldsRoot;
|
|
13
14
|
constructor(dataRoot) {
|
|
14
15
|
this.#worldsRoot = join(resolve(dataRoot), "worlds-file-native");
|
|
15
16
|
}
|
|
16
|
-
/**
|
|
17
|
-
* Convert the cumulative call-chain record written by every released
|
|
18
|
-
* Narraeon version through v0.1.1 into immutable context segments. The
|
|
19
|
-
* source digest makes interrupted retries reproduce the same generation and
|
|
20
|
-
* identities; callers publish the world-wide storage marker only after this
|
|
21
|
-
* method has completed.
|
|
22
|
-
*/
|
|
23
|
-
async migrateReleasedRecord(worldId, value, sourceDigest) {
|
|
24
|
-
assertIdentity(worldId, "World ID");
|
|
25
|
-
if (!/^[a-f0-9]{64}$/u.test(sourceDigest))
|
|
26
|
-
throw new TypeError("Released call-chain source digest is invalid");
|
|
27
|
-
assertReleasedPersistedPlayCallChain(value, worldId);
|
|
28
|
-
const contexts = [
|
|
29
|
-
...(value.previousContexts ?? []).map((context) => structuredClone(context)),
|
|
30
|
-
releasedCurrentContext(value),
|
|
31
|
-
];
|
|
32
|
-
const seen = new Set();
|
|
33
|
-
const generation = `released:${sourceDigest}`;
|
|
34
|
-
let previousChainId = null;
|
|
35
|
-
for (const context of contexts) {
|
|
36
|
-
assertIdentity(context.chainId, "Call-chain ID");
|
|
37
|
-
if (seen.has(context.chainId))
|
|
38
|
-
throw new Error("Released play-call-chain contexts contain a duplicate identity");
|
|
39
|
-
seen.add(context.chainId);
|
|
40
|
-
await this.persist({
|
|
41
|
-
...structuredClone(context),
|
|
42
|
-
events: context.events.map(normalizePlayEvent),
|
|
43
|
-
schemaVersion: 3,
|
|
44
|
-
kind: "play_call_chain",
|
|
45
|
-
worldId,
|
|
46
|
-
previousChainId,
|
|
47
|
-
timelineGeneration: generation,
|
|
48
|
-
documentAuthorizationCheckpoints: structuredClone(context.documentAuthorizationCheckpoints ?? []),
|
|
49
|
-
});
|
|
50
|
-
previousChainId = context.chainId;
|
|
51
|
-
}
|
|
52
|
-
const migrated = await this.readAllContexts(worldId);
|
|
53
|
-
if (migrated.length !== contexts.length ||
|
|
54
|
-
migrated.some((context, index) => context.chainId !== contexts[index]?.chainId))
|
|
55
|
-
throw new Error("Released play-call-chain migration did not reproduce its context chain");
|
|
56
|
-
}
|
|
57
17
|
async persist(value, cursor) {
|
|
58
18
|
return this.#persistAtRuntimeRoot(this.#worldRuntimeRoot(value.worldId), value, cursor);
|
|
59
19
|
}
|
|
60
20
|
/** Write a sealed context segment without making it the active timeline. */
|
|
21
|
+
/** Physically remove the segments of contexts a rewind discarded. */
|
|
22
|
+
async discardContexts(worldId, chainIds) {
|
|
23
|
+
for (const chainId of chainIds) {
|
|
24
|
+
assertIdentity(chainId, "Call-chain ID");
|
|
25
|
+
await rm(this.#contextRoot(worldId, chainId), {
|
|
26
|
+
recursive: true,
|
|
27
|
+
force: true,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
61
31
|
async persistDetached(value, cursor) {
|
|
62
32
|
return this.#persistAtRuntimeRoot(this.#worldRuntimeRoot(value.worldId), value, cursor, false);
|
|
63
33
|
}
|
|
@@ -73,7 +43,6 @@ export class FileNativePlayTimelineStore {
|
|
|
73
43
|
const transcriptCount = prefixLength(input.source.transcript, input.target.transcript);
|
|
74
44
|
const eventCount = prefixLength(input.source.events, input.target.events);
|
|
75
45
|
const completedToolCount = prefixLength(input.source.completedTools, input.target.completedTools);
|
|
76
|
-
const authorizationCheckpointCount = prefixLength(input.source.documentAuthorizationCheckpoints ?? [], input.target.documentAuthorizationCheckpoints ?? []);
|
|
77
46
|
if (input.retainCompleteContext ||
|
|
78
47
|
input.target.events.some((event) => event.kind === "assistant" && event.status === "completed")) {
|
|
79
48
|
const encoding = await this.readInitialEncoding(input.sourceWorldId, input.source.chainId).catch(() => null);
|
|
@@ -114,19 +83,11 @@ export class FileNativePlayTimelineStore {
|
|
|
114
83
|
count: completedToolCount,
|
|
115
84
|
immutable: true,
|
|
116
85
|
}),
|
|
117
|
-
cloneNumberedPrefix({
|
|
118
|
-
sourceRoot,
|
|
119
|
-
targetRoot,
|
|
120
|
-
directory: "authorization",
|
|
121
|
-
count: authorizationCheckpointCount,
|
|
122
|
-
immutable: true,
|
|
123
|
-
}),
|
|
124
86
|
]);
|
|
125
87
|
return this.persistStaged(input.targetWorldRoot, input.target, {
|
|
126
88
|
transcriptCount,
|
|
127
89
|
eventCount,
|
|
128
90
|
completedToolCount,
|
|
129
|
-
authorizationCheckpointCount,
|
|
130
91
|
});
|
|
131
92
|
}
|
|
132
93
|
async #persistAtRuntimeRoot(runtimeRoot, value, cursor, publishHead = true) {
|
|
@@ -156,7 +117,6 @@ export class FileNativePlayTimelineStore {
|
|
|
156
117
|
eventCount: Math.min(supplied.eventCount, value.events.length),
|
|
157
118
|
transcriptCount: Math.min(supplied.transcriptCount, value.transcript.length),
|
|
158
119
|
completedToolCount: Math.min(supplied.completedToolCount, value.completedTools.length),
|
|
159
|
-
authorizationCheckpointCount: Math.min(supplied.authorizationCheckpointCount, value.documentAuthorizationCheckpoints?.length ?? 0),
|
|
160
120
|
};
|
|
161
121
|
const lastRequestDigest = value.lastRequest === null
|
|
162
122
|
? null
|
|
@@ -187,9 +147,6 @@ export class FileNativePlayTimelineStore {
|
|
|
187
147
|
}
|
|
188
148
|
for (let index = previous.completedToolCount; index < value.completedTools.length; index += 1)
|
|
189
149
|
await publishImmutableJson(join(root, "completed-tools", numbered(index + 1)), value.completedTools[index]);
|
|
190
|
-
const checkpoints = value.documentAuthorizationCheckpoints ?? [];
|
|
191
|
-
for (let index = previous.authorizationCheckpointCount; index < checkpoints.length; index += 1)
|
|
192
|
-
await publishImmutableJson(join(root, "authorization", numbered(index + 1)), checkpoints[index]);
|
|
193
150
|
const promptRuns = value.promptRuns ?? [];
|
|
194
151
|
for (let index = supplied.promptRunCount ?? 0; index < promptRuns.length; index += 1) {
|
|
195
152
|
await publishImmutableJson(join(root, "prompt-runs", numbered(index + 1)), encodePlayPromptRun(promptRuns[index]));
|
|
@@ -203,7 +160,6 @@ export class FileNativePlayTimelineStore {
|
|
|
203
160
|
eventCount: value.events.length,
|
|
204
161
|
transcriptCount: value.transcript.length,
|
|
205
162
|
completedToolCount: value.completedTools.length,
|
|
206
|
-
authorizationCheckpointCount: checkpoints.length,
|
|
207
163
|
promptRunCount: promptRuns.length,
|
|
208
164
|
changedDocuments: structuredClone(value.changedDocuments),
|
|
209
165
|
nextEventId: value.nextEventId,
|
|
@@ -228,7 +184,6 @@ export class FileNativePlayTimelineStore {
|
|
|
228
184
|
eventCount: value.events.length,
|
|
229
185
|
transcriptCount: value.transcript.length,
|
|
230
186
|
completedToolCount: value.completedTools.length,
|
|
231
|
-
authorizationCheckpointCount: checkpoints.length,
|
|
232
187
|
promptRunCount: promptRuns.length,
|
|
233
188
|
};
|
|
234
189
|
}
|
|
@@ -263,16 +218,14 @@ export class FileNativePlayTimelineStore {
|
|
|
263
218
|
if (metadata === null)
|
|
264
219
|
return null;
|
|
265
220
|
const { index: indexValue, state: stateValue, root } = metadata;
|
|
266
|
-
const [baseValue, continuationValue, transcript,
|
|
221
|
+
const [baseValue, continuationValue, transcript, events, completedTools, promptRuns,] = await Promise.all([
|
|
267
222
|
readJson(join(root, "base.json")),
|
|
268
223
|
readJson(join(root, "continuations", `${stateValue.continuationDigest}.json`)),
|
|
269
224
|
readNumbered(join(root, "transcript"), stateValue.transcriptCount),
|
|
270
225
|
readNumberedValidated(join(root, "events"), stateValue.eventCount, assertPlayEvent),
|
|
271
226
|
readNumbered(join(root, "completed-tools"), stateValue.completedToolCount),
|
|
272
|
-
readNumbered(join(root, "authorization"), stateValue.authorizationCheckpointCount),
|
|
273
227
|
readNumbered(join(root, "prompt-runs"), stateValue.promptRunCount ?? 0).then((records) => records.map(decodePlayPromptRun)),
|
|
274
228
|
]);
|
|
275
|
-
const events = rawEvents.map(normalizePlayEvent);
|
|
276
229
|
assertContextBase(baseValue);
|
|
277
230
|
assertContextContinuation(continuationValue);
|
|
278
231
|
if (baseValue.worldId !== worldId ||
|
|
@@ -335,7 +288,6 @@ export class FileNativePlayTimelineStore {
|
|
|
335
288
|
transcript,
|
|
336
289
|
events,
|
|
337
290
|
completedTools,
|
|
338
|
-
documentAuthorizationCheckpoints: checkpoints,
|
|
339
291
|
promptRuns,
|
|
340
292
|
changedDocuments: structuredClone(stateValue.changedDocuments),
|
|
341
293
|
nextMaterials: structuredClone(continuationValue.nextMaterials),
|
|
@@ -360,7 +312,6 @@ export class FileNativePlayTimelineStore {
|
|
|
360
312
|
eventCount: stateValue.eventCount,
|
|
361
313
|
transcriptCount: stateValue.transcriptCount,
|
|
362
314
|
completedToolCount: stateValue.completedToolCount,
|
|
363
|
-
authorizationCheckpointCount: stateValue.authorizationCheckpointCount,
|
|
364
315
|
promptRunCount: stateValue.promptRunCount ?? 0,
|
|
365
316
|
},
|
|
366
317
|
};
|
|
@@ -547,7 +498,7 @@ export class FileNativePlayTimelineStore {
|
|
|
547
498
|
assertPlayEvent(event);
|
|
548
499
|
if (event.id !== eventId)
|
|
549
500
|
throw new Error("Play timeline event identity is inconsistent");
|
|
550
|
-
return projectLegacyModelBindingFailure(
|
|
501
|
+
return projectLegacyModelBindingFailure(event);
|
|
551
502
|
}
|
|
552
503
|
newGeneration() {
|
|
553
504
|
return `timeline:${randomUUID()}`;
|
|
@@ -761,7 +712,6 @@ function emptyPersistenceCursor() {
|
|
|
761
712
|
eventCount: 0,
|
|
762
713
|
transcriptCount: 0,
|
|
763
714
|
completedToolCount: 0,
|
|
764
|
-
authorizationCheckpointCount: 0,
|
|
765
715
|
};
|
|
766
716
|
}
|
|
767
717
|
function numbered(index) {
|
|
@@ -830,7 +780,6 @@ function assertContextState(value) {
|
|
|
830
780
|
!validCount(value.eventCount) ||
|
|
831
781
|
!validCount(value.transcriptCount) ||
|
|
832
782
|
!validCount(value.completedToolCount) ||
|
|
833
|
-
!validCount(value.authorizationCheckpointCount) ||
|
|
834
783
|
(value.promptRunCount !== undefined && !validCount(value.promptRunCount)) ||
|
|
835
784
|
!Array.isArray(value.changedDocuments) ||
|
|
836
785
|
!validCount(value.nextEventId) ||
|
|
@@ -865,65 +814,6 @@ function assertTimelineHead(value) {
|
|
|
865
814
|
typeof value.generation !== "string")
|
|
866
815
|
throw new Error("Play timeline head has an invalid shape");
|
|
867
816
|
}
|
|
868
|
-
function assertReleasedPersistedPlayCallChain(value, worldId) {
|
|
869
|
-
if (!isRecord(value) ||
|
|
870
|
-
(value.schemaVersion !== 1 && value.schemaVersion !== 2) ||
|
|
871
|
-
value.kind !== "play_call_chain" ||
|
|
872
|
-
value.worldId !== worldId ||
|
|
873
|
-
!isReleasedPlayContext(value) ||
|
|
874
|
-
(value.previousContexts !== undefined &&
|
|
875
|
-
(!Array.isArray(value.previousContexts) ||
|
|
876
|
-
value.previousContexts.some((context) => !isReleasedPlayContext(context)))))
|
|
877
|
-
throw new Error("Released play-call-chain durable data has an invalid shape");
|
|
878
|
-
}
|
|
879
|
-
function isReleasedPlayContext(value) {
|
|
880
|
-
if (!isRecord(value))
|
|
881
|
-
return false;
|
|
882
|
-
if (typeof value.chainId !== "string" ||
|
|
883
|
-
typeof value.baselineHead !== "string" ||
|
|
884
|
-
(value.continuityContextId !== undefined &&
|
|
885
|
-
(typeof value.continuityContextId !== "string" ||
|
|
886
|
-
value.continuityContextId.length === 0)) ||
|
|
887
|
-
(value.baselineHistoryLength !== undefined &&
|
|
888
|
-
!validCount(value.baselineHistoryLength)) ||
|
|
889
|
-
typeof value.parentHead !== "string" ||
|
|
890
|
-
!validReleasedDerivedFrom(value.derivedFrom) ||
|
|
891
|
-
!validReleasedBranchedBeforePlayer(value.branchedBeforePlayer) ||
|
|
892
|
-
!isRecord(value.playPreset) ||
|
|
893
|
-
(value.followups !== undefined && !Array.isArray(value.followups)) ||
|
|
894
|
-
(value.playPresetScriptsEnabled !== undefined &&
|
|
895
|
-
typeof value.playPresetScriptsEnabled !== "boolean") ||
|
|
896
|
-
(value.modelBinding !== undefined &&
|
|
897
|
-
!validModelHostBinding(value.modelBinding)) ||
|
|
898
|
-
(value.status !== "ready" &&
|
|
899
|
-
value.status !== "running" &&
|
|
900
|
-
value.status !== "interrupted") ||
|
|
901
|
-
typeof value.canRetry !== "boolean" ||
|
|
902
|
-
!isRecord(value.bootstrap) ||
|
|
903
|
-
!Array.isArray(value.tools) ||
|
|
904
|
-
!Array.isArray(value.transcript) ||
|
|
905
|
-
!Array.isArray(value.events) ||
|
|
906
|
-
!Array.isArray(value.completedTools) ||
|
|
907
|
-
!Array.isArray(value.changedDocuments) ||
|
|
908
|
-
!Array.isArray(value.nextMaterials) ||
|
|
909
|
-
!validCount(value.nextEventId) ||
|
|
910
|
-
!validCount(value.exchange) ||
|
|
911
|
-
(value.lastRequest !== null && !isRecord(value.lastRequest)) ||
|
|
912
|
-
!validCount(value.lastRequestAttempt) ||
|
|
913
|
-
(value.lastFailure !== null && typeof value.lastFailure !== "string") ||
|
|
914
|
-
(value.lastFailureKind !== undefined &&
|
|
915
|
-
!isPlayFailureKind(value.lastFailureKind)) ||
|
|
916
|
-
typeof value.updatedAt !== "number")
|
|
917
|
-
return false;
|
|
918
|
-
try {
|
|
919
|
-
for (const event of value.events)
|
|
920
|
-
assertPlayEvent(event);
|
|
921
|
-
}
|
|
922
|
-
catch {
|
|
923
|
-
return false;
|
|
924
|
-
}
|
|
925
|
-
return validReleasedAuthorizationCheckpoints(value.documentAuthorizationCheckpoints, value.events);
|
|
926
|
-
}
|
|
927
817
|
function validModelHostBinding(value) {
|
|
928
818
|
return (isRecord(value) &&
|
|
929
819
|
(value.provider === "chat_completions" ||
|
|
@@ -946,54 +836,6 @@ function validModelHostBinding(value) {
|
|
|
946
836
|
value.cacheStrategy === "explicit_cliproxyapi_message" ||
|
|
947
837
|
value.cacheStrategy === "provider_managed"));
|
|
948
838
|
}
|
|
949
|
-
function validReleasedDerivedFrom(value) {
|
|
950
|
-
return (value === undefined ||
|
|
951
|
-
(isRecord(value) &&
|
|
952
|
-
typeof value.worldId === "string" &&
|
|
953
|
-
typeof value.chainId === "string" &&
|
|
954
|
-
typeof value.head === "string"));
|
|
955
|
-
}
|
|
956
|
-
function validReleasedBranchedBeforePlayer(value) {
|
|
957
|
-
return (value === undefined ||
|
|
958
|
-
(isRecord(value) &&
|
|
959
|
-
typeof value.worldId === "string" &&
|
|
960
|
-
typeof value.chainId === "string" &&
|
|
961
|
-
Number.isSafeInteger(value.eventId) &&
|
|
962
|
-
Number(value.eventId) > 0));
|
|
963
|
-
}
|
|
964
|
-
function validReleasedAuthorizationCheckpoints(value, events) {
|
|
965
|
-
if (value === undefined)
|
|
966
|
-
return true;
|
|
967
|
-
if (!Array.isArray(value) || value.length > 100_000)
|
|
968
|
-
return false;
|
|
969
|
-
const eventIds = new Set(events.flatMap((event) => isRecord(event) && Number.isSafeInteger(event.id) && Number(event.id) > 0
|
|
970
|
-
? [Number(event.id)]
|
|
971
|
-
: []));
|
|
972
|
-
let previousEventId = -1;
|
|
973
|
-
for (const checkpoint of value) {
|
|
974
|
-
if (!isRecord(checkpoint) ||
|
|
975
|
-
Object.keys(checkpoint).length !== 2 ||
|
|
976
|
-
!Object.hasOwn(checkpoint, "afterEventId") ||
|
|
977
|
-
!Object.hasOwn(checkpoint, "authorization") ||
|
|
978
|
-
!Number.isSafeInteger(checkpoint.afterEventId) ||
|
|
979
|
-
Number(checkpoint.afterEventId) < 0 ||
|
|
980
|
-
Number(checkpoint.afterEventId) <= previousEventId ||
|
|
981
|
-
(Number(checkpoint.afterEventId) !== 0 &&
|
|
982
|
-
!eventIds.has(Number(checkpoint.afterEventId))) ||
|
|
983
|
-
!isRecord(checkpoint.authorization))
|
|
984
|
-
return false;
|
|
985
|
-
previousEventId = Number(checkpoint.afterEventId);
|
|
986
|
-
}
|
|
987
|
-
return true;
|
|
988
|
-
}
|
|
989
|
-
function releasedCurrentContext(value) {
|
|
990
|
-
const { schemaVersion: _schemaVersion, kind: _kind, worldId: _worldId, previousContexts: _previousContexts, ...context } = value;
|
|
991
|
-
void _schemaVersion;
|
|
992
|
-
void _kind;
|
|
993
|
-
void _worldId;
|
|
994
|
-
void _previousContexts;
|
|
995
|
-
return structuredClone(context);
|
|
996
|
-
}
|
|
997
839
|
function assertPlayEvent(value) {
|
|
998
840
|
if (!isRecord(value) ||
|
|
999
841
|
!Number.isSafeInteger(value.id) ||
|
|
@@ -1140,43 +982,7 @@ function validOptionalString(value) {
|
|
|
1140
982
|
return value === undefined || typeof value === "string";
|
|
1141
983
|
}
|
|
1142
984
|
function validOptionalUsage(value) {
|
|
1143
|
-
return
|
|
1144
|
-
(isRecord(value) &&
|
|
1145
|
-
validUsageToken(value.inputTokens) &&
|
|
1146
|
-
validUsageToken(value.outputTokens) &&
|
|
1147
|
-
(value.provenance === undefined || validFullUsage(value))));
|
|
1148
|
-
}
|
|
1149
|
-
/**
|
|
1150
|
-
* v0.1.1 stored only input/output usage. Keep that released shape readable,
|
|
1151
|
-
* but expose one current protocol projection with honest unavailable fields.
|
|
1152
|
-
*/
|
|
1153
|
-
function normalizePlayEvent(event) {
|
|
1154
|
-
if ((event.kind !== "assistant" && event.kind !== "followup") ||
|
|
1155
|
-
event.usage === undefined ||
|
|
1156
|
-
validFullUsage(event.usage))
|
|
1157
|
-
return structuredClone(event);
|
|
1158
|
-
const legacy = event.usage;
|
|
1159
|
-
const unavailable = "unavailable";
|
|
1160
|
-
const provider = (value) => value === null ? unavailable : "provider";
|
|
1161
|
-
const usage = {
|
|
1162
|
-
inputTokens: legacy.inputTokens,
|
|
1163
|
-
uncachedInputTokens: null,
|
|
1164
|
-
cacheReadTokens: null,
|
|
1165
|
-
cacheWriteTokens: null,
|
|
1166
|
-
reasoningTokens: null,
|
|
1167
|
-
outputTokens: legacy.outputTokens,
|
|
1168
|
-
totalTokens: null,
|
|
1169
|
-
provenance: {
|
|
1170
|
-
inputTokens: provider(legacy.inputTokens),
|
|
1171
|
-
uncachedInputTokens: unavailable,
|
|
1172
|
-
cacheReadTokens: unavailable,
|
|
1173
|
-
cacheWriteTokens: unavailable,
|
|
1174
|
-
reasoningTokens: unavailable,
|
|
1175
|
-
outputTokens: provider(legacy.outputTokens),
|
|
1176
|
-
totalTokens: unavailable,
|
|
1177
|
-
},
|
|
1178
|
-
};
|
|
1179
|
-
return { ...structuredClone(event), usage };
|
|
985
|
+
return value === undefined || validFullUsage(value);
|
|
1180
986
|
}
|
|
1181
987
|
function validFullUsage(value) {
|
|
1182
988
|
if (!isRecord(value) || !isRecord(value.provenance))
|
|
@@ -1232,12 +1038,18 @@ async function cloneNumberedPrefix(input) {
|
|
|
1232
1038
|
await cloneTimelineFile(join(input.sourceRoot, input.directory, numbered(index)), join(input.targetRoot, input.directory, numbered(index)), input.immutable);
|
|
1233
1039
|
}
|
|
1234
1040
|
async function cloneTimelineFile(source, target, immutable) {
|
|
1235
|
-
await
|
|
1236
|
-
|
|
1237
|
-
target,
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1041
|
+
await mkdir(dirname(target), { recursive: true });
|
|
1042
|
+
try {
|
|
1043
|
+
await copyFile(source, target, constants.COPYFILE_EXCL);
|
|
1044
|
+
}
|
|
1045
|
+
catch (error) {
|
|
1046
|
+
if (!isNodeError(error) || error.code !== "EEXIST")
|
|
1047
|
+
throw error;
|
|
1048
|
+
await assertSameFileBytes(source, target);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
if (immutable)
|
|
1052
|
+
await chmod(target, 0o444);
|
|
1241
1053
|
}
|
|
1242
1054
|
async function assertSameFileBytes(source, target) {
|
|
1243
1055
|
const [sourceBytes, targetBytes] = await Promise.all([
|
|
@@ -1279,9 +1091,7 @@ async function publishJson(path, value) {
|
|
|
1279
1091
|
encoding: "utf8",
|
|
1280
1092
|
mode: 0o600,
|
|
1281
1093
|
});
|
|
1282
|
-
await syncFile(temporary);
|
|
1283
1094
|
await rename(temporary, path);
|
|
1284
|
-
await syncDirectory(dirname(path));
|
|
1285
1095
|
}
|
|
1286
1096
|
async function publishImmutableJson(path, value) {
|
|
1287
1097
|
const contents = `${JSON.stringify(value, null, 2)}\n`;
|
|
@@ -1293,10 +1103,8 @@ async function publishImmutableJson(path, value) {
|
|
|
1293
1103
|
flag: "wx",
|
|
1294
1104
|
mode: 0o600,
|
|
1295
1105
|
});
|
|
1296
|
-
await syncFile(temporary);
|
|
1297
1106
|
try {
|
|
1298
|
-
await
|
|
1299
|
-
await syncDirectory(dirname(path));
|
|
1107
|
+
await linkExclusive(temporary, path);
|
|
1300
1108
|
}
|
|
1301
1109
|
catch (error) {
|
|
1302
1110
|
if (!isNodeError(error) || error.code !== "EEXIST")
|
|
@@ -1311,21 +1119,3 @@ async function publishImmutableJson(path, value) {
|
|
|
1311
1119
|
await rm(temporary, { force: true });
|
|
1312
1120
|
}
|
|
1313
1121
|
}
|
|
1314
|
-
async function syncFile(path) {
|
|
1315
|
-
const handle = await open(path, "r");
|
|
1316
|
-
try {
|
|
1317
|
-
await handle.sync();
|
|
1318
|
-
}
|
|
1319
|
-
finally {
|
|
1320
|
-
await handle.close();
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
async function syncDirectory(path) {
|
|
1324
|
-
const handle = await open(path, "r");
|
|
1325
|
-
try {
|
|
1326
|
-
await handle.sync();
|
|
1327
|
-
}
|
|
1328
|
-
finally {
|
|
1329
|
-
await handle.close();
|
|
1330
|
-
}
|
|
1331
|
-
}
|