narraeon 0.7.10 → 0.8.1

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.
Files changed (49) hide show
  1. package/dist/node/protocol/modelConnections.js +15 -0
  2. package/dist/node/protocol/modelUsage.js +4 -0
  3. package/dist/node/protocol/sillyTavernContent.js +1 -0
  4. package/dist/node/protocol/v1.js +97 -0
  5. package/dist/node/runtime/V1Runtime.js +105 -3
  6. package/dist/node/runtime/authoring/AuthoringEditPlan.js +79 -0
  7. package/dist/node/runtime/authoring/AuthoringRequestSnapshot.js +15 -3
  8. package/dist/node/runtime/authoring/AuthoringRetry.js +59 -0
  9. package/dist/node/runtime/authoring/DamagedGoogleContinuation.js +68 -0
  10. package/dist/node/runtime/authoring/ModelToolConversation.js +66 -25
  11. package/dist/node/runtime/content/ContentWorkspace.js +10 -2
  12. package/dist/node/runtime/content/CurrentTreeContentLibrary.js +14 -8
  13. package/dist/node/runtime/content/SillyTavernContent.js +796 -0
  14. package/dist/node/runtime/model/AiFailureLog.js +8 -1
  15. package/dist/node/runtime/model/DispatchFailure.js +8 -0
  16. package/dist/node/runtime/model/FileNativeModelAdapters.js +627 -18
  17. package/dist/node/runtime/model/GoogleHopJournal.js +90 -0
  18. package/dist/node/runtime/model/GoogleInteractionsStream.js +242 -0
  19. package/dist/node/runtime/model/GoogleRequestPreview.js +184 -0
  20. package/dist/node/runtime/model/ModelConnectionStore.js +4 -1
  21. package/dist/node/runtime/model/ProviderExchangeState.js +44 -1
  22. package/dist/node/runtime/model/ProviderStream.js +4 -2
  23. package/dist/node/runtime/play/FileNativePlayAdvanceStore.js +4 -4
  24. package/dist/node/runtime/play/FileNativePlayPresetStore.js +150 -78
  25. package/dist/node/runtime/play/FileNativePlayTimelineStore.js +199 -10
  26. package/dist/node/runtime/play/OrderedPlayPrompts.js +139 -178
  27. package/dist/node/runtime/play/PlayAdvanceLifecycle.js +51 -23
  28. package/dist/node/runtime/play/PlayCallChain.js +92 -8
  29. package/dist/node/runtime/play/PlayFollowupRequests.js +23 -3
  30. package/dist/node/runtime/prompt/PromptCompilationCodec.js +2 -1
  31. package/dist/node/runtime/setting/FileNativeSettingImprovementStore.js +321 -15
  32. package/dist/node/runtime/setting/SettingImprovementSession.js +285 -21
  33. package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +236 -15
  34. package/dist/node/runtime/world-revision/WorldRevisionSession.js +324 -50
  35. package/dist/node/runtime/world-revision/WorldRevisionWorkspace.js +47 -1
  36. package/dist/node/shared/application-preset-files.js +29 -0
  37. package/dist/node/shared/default-preset-host.js +50 -38
  38. package/dist/node/shared/first-party-action-choices.js +7 -11
  39. package/dist/node/shared/first-party-generic-panels.js +7 -11
  40. package/dist/node/shared/first-party-player-view.js +7 -11
  41. package/dist/node/shared/ordered-author-prompts.js +6 -4
  42. package/dist/node/shared/ordered-play-prompts.js +4 -2
  43. package/dist/node/shared/prompt-arrangement.js +205 -0
  44. package/dist/node/shared/sillytavern-preset.js +454 -0
  45. package/dist/web/assets/index-BthiDIQu.js +1432 -0
  46. package/dist/web/assets/{index-DH2BVsYd.css → index-wO9Y4HT1.css} +1 -1
  47. package/dist/web/index.html +2 -2
  48. package/package.json +1 -1
  49. package/dist/web/assets/index-B2kbaOGe.js +0 -1408
@@ -1,11 +1,14 @@
1
1
  import { validPromptCompilation, validLogicalMessages, validPromptTools, validPlayFollowup, validProviderKind, validCacheStrategy, validToolStrategy, } from "../prompt/PromptCompilationCodec.js";
2
2
  import { validAuthoringRequests, } from "../authoring/AuthoringRequestSnapshot.js";
3
3
  import { randomUUID } from "node:crypto";
4
+ import { readGoogleHops, writeGoogleHop } from "../model/GoogleHopJournal.js";
4
5
  import { mkdir, open, readFile, readdir, rename, rm, stat, } from "node:fs/promises";
5
6
  import { linkExclusive } from "../FileNativeExclusiveLink.js";
6
7
  import { dirname, join } from "node:path";
7
8
  import { isDeepStrictEqual } from "node:util";
8
9
  import { fictionGameplay } from "../../protocol/gameplay.js";
10
+ import { validGoogleInteractionsSteps } from "../model/ProviderExchangeState.js";
11
+ import { hasDamagedGoogleSteps, withoutDamagedGoogleSteps, } from "../authoring/DamagedGoogleContinuation.js";
9
12
  import { ContentWorkspace } from "../content/ContentWorkspace.js";
10
13
  import { contentTreeFingerprint } from "../content/ContentTreeFingerprint.js";
11
14
  import { isPlayPresetBinding, } from "../play/FileNativePlayPresetStore.js";
@@ -28,19 +31,38 @@ export class FileNativeSettingImprovementStore {
28
31
  createId() {
29
32
  return `setting-${randomUUID()}`;
30
33
  }
34
+ get sessionScope() {
35
+ return this.#sessionsRoot;
36
+ }
37
+ recordGoogleHop(sessionId, requestId, hop) {
38
+ assertSessionId(sessionId);
39
+ return writeGoogleHop(join(this.#sessionsRoot, ".google-hops", sessionId), requestId, hop, () => stat(this.#path(sessionId)).then(() => true, () => false));
40
+ }
31
41
  async read(sessionId) {
32
42
  await this.#mutationTail;
33
- return this.#read(sessionId);
43
+ try {
44
+ const session = await this.#read(sessionId);
45
+ if (hasDamagedGoogleSteps(session) &&
46
+ (await this.#damagedSummary(sessionId)))
47
+ throw new Error("The historical conversation has damaged Google continuation; start a fresh model context");
48
+ return session;
49
+ }
50
+ catch (error) {
51
+ if (await this.#damagedSummary(sessionId))
52
+ throw new Error("The historical conversation has damaged Google continuation; start a fresh model context", { cause: error });
53
+ throw error;
54
+ }
34
55
  }
35
56
  async findLatestByPackage(packageId) {
36
57
  const summaries = await this.listSummariesByPackage(packageId);
37
- return summaries[0] === undefined
38
- ? null
39
- : this.read(summaries[0].sessionId);
58
+ const latest = summaries.find((summary) => !summary.damagedContinuation);
59
+ return latest === undefined ? null : this.read(latest.sessionId);
40
60
  }
41
61
  async findByCreationRequest(packageId, requestId) {
42
62
  const summaries = await this.listSummariesByPackage(packageId);
43
63
  const match = summaries.find(({ creationRequestId }) => creationRequestId === requestId);
64
+ if (match?.damagedContinuation)
65
+ throw new Error("The historical conversation has damaged Google continuation; start a fresh model context with a new request ID");
44
66
  return match === undefined ? null : this.read(match.sessionId);
45
67
  }
46
68
  async listSummariesByPackage(packageId) {
@@ -65,10 +87,65 @@ export class FileNativeSettingImprovementStore {
65
87
  save(session) {
66
88
  return this.#mutate(async () => {
67
89
  validateStoredSession(session);
90
+ await this.#preservePreviousSchema(session);
68
91
  await publishJson(this.#path(session.sessionId), session);
69
92
  await this.#recordSavedSession(session);
70
93
  });
71
94
  }
95
+ async preparePendingEdit(session) {
96
+ if (session.pendingEdit === undefined)
97
+ throw new Error("No pending setting conversation edit");
98
+ await publishJson(this.#pendingEditPath(session.packageId), {
99
+ schemaVersion: 1,
100
+ packageId: session.packageId,
101
+ sessionId: session.sessionId,
102
+ });
103
+ await this.save(session);
104
+ }
105
+ async recoverPendingEdits(packageId, lease) {
106
+ const path = this.#pendingEditPath(packageId);
107
+ let marker;
108
+ try {
109
+ marker = JSON.parse(await readFile(path, "utf8"));
110
+ }
111
+ catch (error) {
112
+ if (isNodeError(error) && error.code === "ENOENT")
113
+ return;
114
+ throw error;
115
+ }
116
+ if (!isRecord(marker) ||
117
+ !hasExactKeys(marker, ["schemaVersion", "packageId", "sessionId"]) ||
118
+ marker.schemaVersion !== 1 ||
119
+ marker.packageId !== packageId ||
120
+ typeof marker.sessionId !== "string")
121
+ throw new Error("Pending authoring edit marker is damaged");
122
+ assertSessionId(marker.sessionId);
123
+ const raw = JSON.parse(await readFile(this.#path(marker.sessionId), "utf8"));
124
+ const session = validateStoredSession(raw);
125
+ if (session.sessionId !== marker.sessionId ||
126
+ session.packageId !== packageId)
127
+ throw new Error("Pending edit identity does not match its package");
128
+ if (session.pendingEdit === undefined) {
129
+ await rm(path, { force: true });
130
+ return;
131
+ }
132
+ await this.finishPendingEdit(session, lease);
133
+ }
134
+ async finishPendingEdit(session, lease) {
135
+ const pending = session.pendingEdit;
136
+ if (pending === undefined)
137
+ throw new Error("No pending setting conversation edit");
138
+ const fingerprint = contentTreeFingerprint(lease.package.files);
139
+ if (fingerprint === pending.beforeFingerprint &&
140
+ fingerprint !== pending.afterFingerprint)
141
+ await lease.replace(pending.afterFiles);
142
+ else if (fingerprint !== pending.afterFingerprint)
143
+ throw new Error("Cannot recover the conversation edit because the content package changed; no further changes were made");
144
+ await publishJson(this.#path(session.sessionId), pending.next);
145
+ await rm(this.#pendingEditPath(session.packageId), { force: true });
146
+ await this.#recordSavedSession(pending.next);
147
+ return structuredClone(pending.next);
148
+ }
72
149
  saveIfUnchanged(expected, replacement) {
73
150
  return this.#mutate(async () => {
74
151
  if (expected.sessionId !== replacement.sessionId)
@@ -77,6 +154,7 @@ export class FileNativeSettingImprovementStore {
77
154
  const current = await this.#read(expected.sessionId);
78
155
  if (!isDeepStrictEqual(current, expected))
79
156
  return false;
157
+ await this.#preservePreviousSchema(replacement);
80
158
  await publishJson(this.#path(replacement.sessionId), replacement);
81
159
  await this.#recordSavedSession(replacement);
82
160
  return true;
@@ -88,9 +166,15 @@ export class FileNativeSettingImprovementStore {
88
166
  if (session.packageId !== packageId)
89
167
  throw new Error("The setting-improvement conversation does not belong to this content package");
90
168
  await rm(this.#path(sessionId));
169
+ await rm(join(this.#sessionsRoot, ".google-hops", sessionId), {
170
+ recursive: true,
171
+ force: true,
172
+ });
91
173
  await rm(join(this.#sessionsRoot, `${sessionId}.schema-v1.json`), {
92
174
  force: true,
93
175
  });
176
+ for (const version of [2, 3, 4])
177
+ await rm(join(this.#sessionsRoot, `${sessionId}.schema-v${version}.json`), { force: true });
94
178
  await this.#recordDeletedSession(session);
95
179
  });
96
180
  }
@@ -117,8 +201,20 @@ export class FileNativeSettingImprovementStore {
117
201
  for (const entry of entries) {
118
202
  if (!entry.isFile() || !sessionFilePattern.test(entry.name))
119
203
  continue;
120
- const session = await this.#read(entry.name.slice(0, -5));
121
- summaries.set(session.sessionId, summarizeStoredSettingImprovementSession(session));
204
+ const sessionId = entry.name.slice(0, -5);
205
+ try {
206
+ const session = await this.#read(sessionId);
207
+ summaries.set(session.sessionId, (!hasDamagedGoogleSteps(session)
208
+ ? null
209
+ : await this.#damagedSummary(sessionId)) ??
210
+ summarizeStoredSettingImprovementSession(session));
211
+ }
212
+ catch (error) {
213
+ const summary = await this.#damagedSummary(sessionId);
214
+ if (summary === null)
215
+ throw error;
216
+ summaries.set(sessionId, summary);
217
+ }
122
218
  }
123
219
  const completedRevision = await directoryRevision(this.#sessionsRoot);
124
220
  if (completedRevision !== revision)
@@ -127,6 +223,41 @@ export class FileNativeSettingImprovementStore {
127
223
  return;
128
224
  }
129
225
  }
226
+ async #damagedSummary(sessionId) {
227
+ let session;
228
+ try {
229
+ const raw = JSON.parse(await readFile(this.#path(sessionId), "utf8"));
230
+ const copy = withoutDamagedGoogleSteps(raw);
231
+ if (copy === null) {
232
+ if (hasDamagedGoogleSteps(raw))
233
+ throw new Error("Cannot safely identify the damaged historical conversation; settlement or identity is unverified");
234
+ return null;
235
+ }
236
+ session = validateStoredSession(copy);
237
+ }
238
+ catch (error) {
239
+ if (error instanceof Error &&
240
+ error.message.startsWith("Cannot safely identify"))
241
+ throw error;
242
+ return null;
243
+ }
244
+ if (session.sessionId !== sessionId ||
245
+ session.googleIdentity?.sessionId !== sessionId ||
246
+ session.googleIdentity.packageId !== session.packageId ||
247
+ session.googleIdentity.creationRequestId !== session.creationRequestId ||
248
+ (session.requests?.[0]?.requestId !== session.creationRequestId &&
249
+ !(session.schemaVersion === 5 &&
250
+ session.editReceipts?.some((receipt) => receipt.requestId === session.requests?.[0]?.requestId))) ||
251
+ !/^package-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(session.packageId) ||
252
+ !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u.test(session.creationRequestId) ||
253
+ hasPendingToolCalls(session))
254
+ throw new Error("Cannot safely identify the damaged historical conversation; its owner or creation identity is unverified");
255
+ return {
256
+ ...summarizeStoredSettingImprovementSession(session),
257
+ runStatus: "interrupted",
258
+ damagedContinuation: true,
259
+ };
260
+ }
130
261
  async #recordSavedSession(session) {
131
262
  if (this.#summaryIndex !== null)
132
263
  this.#cacheSummary(summarizeStoredSettingImprovementSession(session));
@@ -219,8 +350,32 @@ export class FileNativeSettingImprovementStore {
219
350
  const session = validateStoredSession(value);
220
351
  if (session.sessionId !== sessionId)
221
352
  throw new Error("Setting-improvement conversation identity does not match its file");
353
+ for (const request of session.requests ?? [])
354
+ if (request.wireRequest !== undefined)
355
+ request.wireRequest = await readGoogleHops(join(this.#sessionsRoot, ".google-hops", sessionId), request.requestId, request.wireRequest);
222
356
  return session;
223
357
  }
358
+ async #preservePreviousSchema(next) {
359
+ if (next.schemaVersion < 4)
360
+ return;
361
+ let source;
362
+ try {
363
+ source = await readFile(this.#path(next.sessionId), "utf8");
364
+ }
365
+ catch (error) {
366
+ if (isNodeError(error) && error.code === "ENOENT")
367
+ return;
368
+ throw error;
369
+ }
370
+ const previous = validateStoredSession(JSON.parse(source));
371
+ if (previous.sessionId !== next.sessionId)
372
+ throw new Error("Setting-improvement conversation identity does not match its file");
373
+ if (previous.schemaVersion === next.schemaVersion)
374
+ return;
375
+ if (previous.schemaVersion > next.schemaVersion)
376
+ throw new Error("Cannot downgrade a setting-improvement conversation");
377
+ await publishImmutableSource(join(this.#sessionsRoot, `${next.sessionId}.schema-v${previous.schemaVersion}.json`), source);
378
+ }
224
379
  async #migrateLegacy(sessionId, source, value) {
225
380
  if (!validLegacySession(value))
226
381
  throw new Error("Legacy setting-improvement conversation is damaged");
@@ -267,6 +422,11 @@ export class FileNativeSettingImprovementStore {
267
422
  assertSessionId(sessionId);
268
423
  return join(this.#sessionsRoot, `${sessionId}.json`);
269
424
  }
425
+ #pendingEditPath(packageId) {
426
+ if (!/^package-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(packageId))
427
+ throw new Error("Invalid content package ID");
428
+ return join(this.#sessionsRoot, ".pending-edits", `${packageId}.json`);
429
+ }
270
430
  }
271
431
  function compareSummaries(left, right) {
272
432
  return (right.updatedAt - left.updatedAt ||
@@ -309,7 +469,8 @@ export function summarizeStoredSettingImprovementSession(session) {
309
469
  changedFileCount: changedPaths.size,
310
470
  exchange: session.exchange,
311
471
  toolCalls: session.toolCalls,
312
- needsRecovery: session.pendingSettlement !== null ||
472
+ needsRecovery: session.pendingEdit !== undefined ||
473
+ session.pendingSettlement !== null ||
313
474
  session.runStatus === "running" ||
314
475
  hasPendingToolCalls(session),
315
476
  };
@@ -362,15 +523,49 @@ function validateStoredSession(value) {
362
523
  "authorization",
363
524
  "pendingSettlement",
364
525
  "legacyDraft",
365
- ], ["contentPackageTitle", "playPreset", "requests"]) ||
366
- (value.schemaVersion !== 2 && value.schemaVersion !== 3) ||
526
+ ], [
527
+ "contentPackageTitle",
528
+ "playPreset",
529
+ "requests",
530
+ "googleOutcomeUnknown",
531
+ "googleIdentity",
532
+ "retryDispatch",
533
+ "pendingEdit",
534
+ "editReceipts",
535
+ ]) ||
536
+ (value.schemaVersion !== 2 &&
537
+ value.schemaVersion !== 3 &&
538
+ value.schemaVersion !== 4 &&
539
+ value.schemaVersion !== 5) ||
540
+ (value.schemaVersion !== 4 &&
541
+ value.schemaVersion !== 5 &&
542
+ value.retryDispatch !== undefined) ||
543
+ (value.schemaVersion !== 5 && value.pendingEdit !== undefined) ||
544
+ (value.schemaVersion !== 5 && value.editReceipts !== undefined) ||
545
+ (value.editReceipts !== undefined &&
546
+ !validEditReceipts(value.editReceipts)) ||
547
+ (value.pendingEdit !== undefined &&
548
+ !validPendingEdit(value.pendingEdit, value)) ||
367
549
  (value.schemaVersion === 2
368
550
  ? value.requests !== undefined
369
- : !validAuthoringRequests(value.requests, validPromptCompilation, {
370
- modelItems: value.modelItems,
371
- activeRequestId: value.activeRequestId,
372
- completedRequestIds: value.completedRequestIds,
373
- })) ||
551
+ : (value.schemaVersion === 4 || value.schemaVersion === 5) &&
552
+ Array.isArray(value.requests) &&
553
+ value.requests.length === 0 &&
554
+ ((value.schemaVersion === 5 && value.pendingEdit !== undefined) ||
555
+ (value.runStatus === "ready" &&
556
+ Array.isArray(value.modelItems) &&
557
+ value.modelItems.length === 0 &&
558
+ Array.isArray(value.messages) &&
559
+ value.messages.length === 0 &&
560
+ value.activeRequestId === null &&
561
+ Array.isArray(value.completedRequestIds) &&
562
+ value.completedRequestIds.length === 0))
563
+ ? false
564
+ : !validAuthoringRequests(value.requests, validPromptCompilation, {
565
+ modelItems: value.modelItems,
566
+ activeRequestId: value.activeRequestId,
567
+ completedRequestIds: value.completedRequestIds,
568
+ })) ||
374
569
  typeof value.sessionId !== "string" ||
375
570
  typeof value.packageId !== "string" ||
376
571
  !validOptionalTitle(value.contentPackageTitle) ||
@@ -381,6 +576,27 @@ function validateStoredSession(value) {
381
576
  typeof value.creationRequestId !== "string" ||
382
577
  !validPromptCompilation(value.bootstrap) ||
383
578
  !validModelBinding(value.modelBinding) ||
579
+ (value.retryDispatch !== undefined &&
580
+ (!isRecord(value.retryDispatch) ||
581
+ !hasExactKeys(value.retryDispatch, [
582
+ "requestId",
583
+ "exchange",
584
+ "modelItemCount",
585
+ "state",
586
+ ]) ||
587
+ typeof value.retryDispatch.requestId !== "string" ||
588
+ !validCount(value.retryDispatch.exchange) ||
589
+ !validCount(value.retryDispatch.modelItemCount) ||
590
+ !["dispatching", "retryable", "blocked"].includes(String(value.retryDispatch.state)) ||
591
+ (value.schemaVersion !== 4 && value.schemaVersion !== 5) ||
592
+ lastRequestId(value.requests) !== value.retryDispatch.requestId ||
593
+ (value.runStatus !== "running" && value.runStatus !== "interrupted") ||
594
+ value.pendingSettlement !== null ||
595
+ !validCount(value.exchange) ||
596
+ (value.retryDispatch.exchange !== value.exchange &&
597
+ value.retryDispatch.exchange !== Number(value.exchange) + 1) ||
598
+ !Array.isArray(value.modelItems) ||
599
+ value.retryDispatch.modelItemCount !== value.modelItems.length)) ||
384
600
  (value.playPreset !== undefined &&
385
601
  !isPlayPresetBinding(value.playPreset)) ||
386
602
  !Array.isArray(value.modelItems) ||
@@ -392,18 +608,102 @@ function validateStoredSession(value) {
392
608
  typeof value.activeRequestId !== "string") ||
393
609
  !Array.isArray(value.completedRequestIds) ||
394
610
  (value.lastFailure !== null && typeof value.lastFailure !== "string") ||
611
+ (value.googleOutcomeUnknown !== undefined &&
612
+ (typeof value.googleOutcomeUnknown !== "boolean" ||
613
+ !isRecord(value.modelBinding) ||
614
+ value.modelBinding.provider !== "google_interactions")) ||
615
+ (value.googleIdentity !== undefined &&
616
+ (!isRecord(value.googleIdentity) ||
617
+ !hasExactKeys(value.googleIdentity, [
618
+ "sessionId",
619
+ "packageId",
620
+ "creationRequestId",
621
+ ]) ||
622
+ typeof value.googleIdentity.sessionId !== "string" ||
623
+ typeof value.googleIdentity.packageId !== "string" ||
624
+ typeof value.googleIdentity.creationRequestId !== "string" ||
625
+ !isRecord(value.modelBinding) ||
626
+ value.modelBinding.provider !== "google_interactions")) ||
395
627
  !validAuthorization(value.authorization) ||
396
628
  !validPendingSettlement(value.pendingSettlement) ||
397
629
  !validPendingSettlementForTranscript(value.pendingSettlement, value.modelItems, value.runStatus) ||
398
630
  !validLegacyDraft(value.legacyDraft))
399
631
  throw new Error("Setting-improvement conversation does not match its durable schema");
400
632
  assertSessionId(value.sessionId);
633
+ if (isRecord(value.googleIdentity) &&
634
+ (value.googleIdentity.sessionId !== value.sessionId ||
635
+ value.googleIdentity.packageId !== value.packageId ||
636
+ value.googleIdentity.creationRequestId !== value.creationRequestId))
637
+ throw new Error("Cannot safely identify the historical conversation; its owner or creation identity is inconsistent");
401
638
  if (!value.completedRequestIds.every((id) => typeof id === "string") ||
402
639
  !value.messages.every(validMessage) ||
403
640
  !value.modelItems.every(validModelItem))
404
641
  throw new Error("Setting-improvement transcript is damaged");
405
642
  return structuredClone(value);
406
643
  }
644
+ function lastRequestId(requests) {
645
+ if (!Array.isArray(requests))
646
+ return undefined;
647
+ const last = requests.at(-1);
648
+ return isRecord(last) ? last.requestId : undefined;
649
+ }
650
+ function validEditReceipts(value) {
651
+ if (!Array.isArray(value))
652
+ return false;
653
+ const ids = new Set();
654
+ return value.every((entry) => {
655
+ if (!isRecord(entry) ||
656
+ !hasExactKeys(entry, [
657
+ "requestId",
658
+ "messageId",
659
+ "message",
660
+ "filePolicy",
661
+ ]) ||
662
+ typeof entry.requestId !== "string" ||
663
+ !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u.test(entry.requestId) ||
664
+ ids.has(entry.requestId) ||
665
+ typeof entry.messageId !== "string" ||
666
+ typeof entry.message !== "string" ||
667
+ entry.message.trim().length === 0 ||
668
+ entry.message.length > 64 * 1024 ||
669
+ (entry.filePolicy !== "keep" && entry.filePolicy !== "revert"))
670
+ return false;
671
+ ids.add(entry.requestId);
672
+ return true;
673
+ });
674
+ }
675
+ function validPendingEdit(value, original) {
676
+ if (!isRecord(value) ||
677
+ !hasExactKeys(value, [
678
+ "beforeFingerprint",
679
+ "afterFingerprint",
680
+ "afterFiles",
681
+ "next",
682
+ ]) ||
683
+ typeof value.beforeFingerprint !== "string" ||
684
+ !/^[0-9a-f]{64}$/u.test(value.beforeFingerprint) ||
685
+ typeof value.afterFingerprint !== "string" ||
686
+ !/^[0-9a-f]{64}$/u.test(value.afterFingerprint) ||
687
+ !validContentTreeFiles(value.afterFiles) ||
688
+ contentTreeFingerprint(value.afterFiles) !== value.afterFingerprint ||
689
+ !isRecord(value.next) ||
690
+ value.next.pendingEdit !== undefined ||
691
+ value.next.schemaVersion !== 5 ||
692
+ value.next.sessionId !== original.sessionId ||
693
+ value.next.packageId !== original.packageId ||
694
+ value.next.creationRequestId !== original.creationRequestId ||
695
+ value.next.runStatus !== "running" ||
696
+ value.next.activeRequestId === null ||
697
+ original.pendingSettlement !== null)
698
+ return false;
699
+ try {
700
+ validateStoredSession(value.next);
701
+ return true;
702
+ }
703
+ catch {
704
+ return false;
705
+ }
706
+ }
407
707
  function migrateLegacySession(value, applyOutcome) {
408
708
  if (!validLegacySession(value))
409
709
  throw new Error("Legacy setting-improvement conversation is damaged");
@@ -823,6 +1123,9 @@ function validModelToolCall(value) {
823
1123
  function validProviderState(value) {
824
1124
  if (!isRecord(value))
825
1125
  return false;
1126
+ if (value.protocol === "google_interactions")
1127
+ return (hasExactKeys(value, ["protocol", "steps"]) &&
1128
+ validGoogleInteractionsSteps(value.steps));
826
1129
  if (value.protocol === "chat_completions")
827
1130
  return (hasExactKeys(value, ["protocol", "assistantMessage"]) &&
828
1131
  Object.hasOwn(value, "assistantMessage"));
@@ -850,7 +1153,10 @@ function validModelUsage(value) {
850
1153
  "totalTokens",
851
1154
  ];
852
1155
  if (!isRecord(value) ||
853
- !hasExactKeys(value, [...fields, "provenance"]) ||
1156
+ !hasExactKeys(value, [...fields, "provenance"], ["providerRaw", "accounting"]) ||
1157
+ (value.providerRaw !== undefined && !isRecord(value.providerRaw)) ||
1158
+ (value.accounting !== undefined &&
1159
+ value.accounting !== "google_interactions") ||
854
1160
  !fields.every((field) => validNullableCount(value[field])) ||
855
1161
  !isRecord(value.provenance) ||
856
1162
  !hasExactKeys(value.provenance, fields))