narraeon 0.7.10 → 0.8.0
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/dist/node/protocol/modelConnections.js +15 -0
- package/dist/node/protocol/modelUsage.js +4 -0
- package/dist/node/protocol/sillyTavernContent.js +1 -0
- package/dist/node/protocol/v1.js +75 -0
- package/dist/node/runtime/V1Runtime.js +71 -1
- package/dist/node/runtime/authoring/AuthoringRequestSnapshot.js +15 -3
- package/dist/node/runtime/authoring/DamagedGoogleContinuation.js +68 -0
- package/dist/node/runtime/authoring/ModelToolConversation.js +47 -22
- package/dist/node/runtime/content/ContentWorkspace.js +3 -0
- package/dist/node/runtime/content/SillyTavernContent.js +796 -0
- package/dist/node/runtime/model/AiFailureLog.js +8 -1
- package/dist/node/runtime/model/FileNativeModelAdapters.js +610 -18
- package/dist/node/runtime/model/GoogleHopJournal.js +90 -0
- package/dist/node/runtime/model/GoogleInteractionsStream.js +242 -0
- package/dist/node/runtime/model/GoogleRequestPreview.js +184 -0
- package/dist/node/runtime/model/ModelConnectionStore.js +4 -1
- package/dist/node/runtime/model/ProviderExchangeState.js +44 -1
- package/dist/node/runtime/model/ProviderStream.js +4 -2
- package/dist/node/runtime/play/FileNativePlayAdvanceStore.js +4 -4
- package/dist/node/runtime/play/FileNativePlayPresetStore.js +150 -78
- package/dist/node/runtime/play/FileNativePlayTimelineStore.js +150 -10
- package/dist/node/runtime/play/OrderedPlayPrompts.js +139 -178
- package/dist/node/runtime/play/PlayAdvanceLifecycle.js +33 -9
- package/dist/node/runtime/play/PlayCallChain.js +76 -4
- package/dist/node/runtime/play/PlayFollowupRequests.js +23 -3
- package/dist/node/runtime/prompt/PromptCompilationCodec.js +2 -1
- package/dist/node/runtime/setting/FileNativeSettingImprovementStore.js +112 -8
- package/dist/node/runtime/setting/SettingImprovementSession.js +48 -7
- package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +84 -8
- package/dist/node/runtime/world-revision/WorldRevisionSession.js +125 -17
- package/dist/node/shared/application-preset-files.js +29 -0
- package/dist/node/shared/default-preset-host.js +50 -38
- package/dist/node/shared/first-party-action-choices.js +7 -11
- package/dist/node/shared/first-party-generic-panels.js +7 -11
- package/dist/node/shared/first-party-player-view.js +7 -11
- package/dist/node/shared/ordered-author-prompts.js +6 -4
- package/dist/node/shared/ordered-play-prompts.js +4 -2
- package/dist/node/shared/prompt-arrangement.js +205 -0
- package/dist/node/shared/sillytavern-preset.js +454 -0
- package/dist/web/assets/{index-DH2BVsYd.css → index-BKA1D0Rz.css} +1 -1
- package/dist/web/assets/index-C_Qta8UL.js +1432 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-B2kbaOGe.js +0 -1408
|
@@ -49,6 +49,8 @@ export async function runPlayFollowupRequests(input) {
|
|
|
49
49
|
}
|
|
50
50
|
if (signalWasAborted(input.signal)) {
|
|
51
51
|
failure ??= "The follow-up request was cancelled.";
|
|
52
|
+
outcomes.push(outcome);
|
|
53
|
+
input.observer?.onOutcome?.(outcome);
|
|
52
54
|
break;
|
|
53
55
|
}
|
|
54
56
|
outcomes.push(outcome);
|
|
@@ -91,10 +93,9 @@ async function runOne(input, followup, prefix, requestSignal) {
|
|
|
91
93
|
try {
|
|
92
94
|
await input.artifacts.beginRequestAttempt(requestContext);
|
|
93
95
|
requestSignal?.throwIfAborted();
|
|
94
|
-
input.observer?.onProviderDispatch?.();
|
|
95
96
|
let response;
|
|
96
97
|
try {
|
|
97
|
-
|
|
98
|
+
const request = {
|
|
98
99
|
bootstrap: structuredClone(input.bootstrap),
|
|
99
100
|
tools: structuredClone(followup.tools),
|
|
100
101
|
toolUniverse: structuredClone(followup.tools),
|
|
@@ -114,8 +115,23 @@ async function runOne(input, followup, prefix, requestSignal) {
|
|
|
114
115
|
requestAttempt: 1,
|
|
115
116
|
exchange: 1,
|
|
116
117
|
maxOutputTokens: input.maxOutputTokens,
|
|
117
|
-
}
|
|
118
|
+
};
|
|
119
|
+
try {
|
|
120
|
+
outcome.requestPreviewRecorded =
|
|
121
|
+
(await input.observer?.onRequest?.(followup.id, request)) ?? false;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Diagnostic recording must not change the provider request.
|
|
125
|
+
}
|
|
126
|
+
requestSignal?.throwIfAborted();
|
|
127
|
+
outcome.requestPreviewStatus = "attempted";
|
|
128
|
+
input.observer?.onProviderDispatch?.();
|
|
129
|
+
response = await input.modelHost.exchange(request, {
|
|
118
130
|
...(requestSignal === undefined ? {} : { signal: requestSignal }),
|
|
131
|
+
onGoogleHop: (evidence) => input.observer?.onGoogleHop?.(followup.id, evidence),
|
|
132
|
+
...(input.controls !== undefined
|
|
133
|
+
? { retainLateExtensionResponse: true }
|
|
134
|
+
: {}),
|
|
119
135
|
onDelta: (delta) => input.observer?.onProviderDelta?.(delta),
|
|
120
136
|
});
|
|
121
137
|
if (input.signal?.aborted === true)
|
|
@@ -124,6 +140,7 @@ async function runOne(input, followup, prefix, requestSignal) {
|
|
|
124
140
|
finally {
|
|
125
141
|
input.observer?.onProviderSettled?.();
|
|
126
142
|
}
|
|
143
|
+
outcome.requestPreviewStatus = "completed";
|
|
127
144
|
responseDiagnostics = response.diagnostics;
|
|
128
145
|
if (response.diagnostics !== undefined)
|
|
129
146
|
await input.failureLog?.recordExchangeIfActive(response.diagnostics);
|
|
@@ -182,6 +199,9 @@ async function runOne(input, followup, prefix, requestSignal) {
|
|
|
182
199
|
});
|
|
183
200
|
}
|
|
184
201
|
catch (error) {
|
|
202
|
+
if (outcome.requestPreviewStatus !== undefined &&
|
|
203
|
+
error instanceof ModelHostCancelledError)
|
|
204
|
+
outcome.requestPreviewStatus = "cancelled";
|
|
185
205
|
outcome.failure =
|
|
186
206
|
error instanceof Error
|
|
187
207
|
? error.message
|
|
@@ -315,7 +315,8 @@ function validPromptCache(value) {
|
|
|
315
315
|
export function validProviderKind(value) {
|
|
316
316
|
return (value === "chat_completions" ||
|
|
317
317
|
value === "openai_responses" ||
|
|
318
|
-
value === "anthropic_messages"
|
|
318
|
+
value === "anthropic_messages" ||
|
|
319
|
+
value === "google_interactions");
|
|
319
320
|
}
|
|
320
321
|
export function validCacheStrategy(value) {
|
|
321
322
|
return (value === "explicit_anthropic_blocks" ||
|
|
@@ -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,35 @@ export class FileNativeSettingImprovementStore {
|
|
|
28
31
|
createId() {
|
|
29
32
|
return `setting-${randomUUID()}`;
|
|
30
33
|
}
|
|
34
|
+
recordGoogleHop(sessionId, requestId, hop) {
|
|
35
|
+
assertSessionId(sessionId);
|
|
36
|
+
return writeGoogleHop(join(this.#sessionsRoot, ".google-hops", sessionId), requestId, hop, () => stat(this.#path(sessionId)).then(() => true, () => false));
|
|
37
|
+
}
|
|
31
38
|
async read(sessionId) {
|
|
32
39
|
await this.#mutationTail;
|
|
33
|
-
|
|
40
|
+
try {
|
|
41
|
+
const session = await this.#read(sessionId);
|
|
42
|
+
if (hasDamagedGoogleSteps(session) &&
|
|
43
|
+
(await this.#damagedSummary(sessionId)))
|
|
44
|
+
throw new Error("The historical conversation has damaged Google continuation; start a fresh model context");
|
|
45
|
+
return session;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (await this.#damagedSummary(sessionId))
|
|
49
|
+
throw new Error("The historical conversation has damaged Google continuation; start a fresh model context", { cause: error });
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
34
52
|
}
|
|
35
53
|
async findLatestByPackage(packageId) {
|
|
36
54
|
const summaries = await this.listSummariesByPackage(packageId);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
: this.read(summaries[0].sessionId);
|
|
55
|
+
const latest = summaries.find((summary) => !summary.damagedContinuation);
|
|
56
|
+
return latest === undefined ? null : this.read(latest.sessionId);
|
|
40
57
|
}
|
|
41
58
|
async findByCreationRequest(packageId, requestId) {
|
|
42
59
|
const summaries = await this.listSummariesByPackage(packageId);
|
|
43
60
|
const match = summaries.find(({ creationRequestId }) => creationRequestId === requestId);
|
|
61
|
+
if (match?.damagedContinuation)
|
|
62
|
+
throw new Error("The historical conversation has damaged Google continuation; start a fresh model context with a new request ID");
|
|
44
63
|
return match === undefined ? null : this.read(match.sessionId);
|
|
45
64
|
}
|
|
46
65
|
async listSummariesByPackage(packageId) {
|
|
@@ -88,6 +107,10 @@ export class FileNativeSettingImprovementStore {
|
|
|
88
107
|
if (session.packageId !== packageId)
|
|
89
108
|
throw new Error("The setting-improvement conversation does not belong to this content package");
|
|
90
109
|
await rm(this.#path(sessionId));
|
|
110
|
+
await rm(join(this.#sessionsRoot, ".google-hops", sessionId), {
|
|
111
|
+
recursive: true,
|
|
112
|
+
force: true,
|
|
113
|
+
});
|
|
91
114
|
await rm(join(this.#sessionsRoot, `${sessionId}.schema-v1.json`), {
|
|
92
115
|
force: true,
|
|
93
116
|
});
|
|
@@ -117,8 +140,20 @@ export class FileNativeSettingImprovementStore {
|
|
|
117
140
|
for (const entry of entries) {
|
|
118
141
|
if (!entry.isFile() || !sessionFilePattern.test(entry.name))
|
|
119
142
|
continue;
|
|
120
|
-
const
|
|
121
|
-
|
|
143
|
+
const sessionId = entry.name.slice(0, -5);
|
|
144
|
+
try {
|
|
145
|
+
const session = await this.#read(sessionId);
|
|
146
|
+
summaries.set(session.sessionId, (!hasDamagedGoogleSteps(session)
|
|
147
|
+
? null
|
|
148
|
+
: await this.#damagedSummary(sessionId)) ??
|
|
149
|
+
summarizeStoredSettingImprovementSession(session));
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
const summary = await this.#damagedSummary(sessionId);
|
|
153
|
+
if (summary === null)
|
|
154
|
+
throw error;
|
|
155
|
+
summaries.set(sessionId, summary);
|
|
156
|
+
}
|
|
122
157
|
}
|
|
123
158
|
const completedRevision = await directoryRevision(this.#sessionsRoot);
|
|
124
159
|
if (completedRevision !== revision)
|
|
@@ -127,6 +162,39 @@ export class FileNativeSettingImprovementStore {
|
|
|
127
162
|
return;
|
|
128
163
|
}
|
|
129
164
|
}
|
|
165
|
+
async #damagedSummary(sessionId) {
|
|
166
|
+
let session;
|
|
167
|
+
try {
|
|
168
|
+
const raw = JSON.parse(await readFile(this.#path(sessionId), "utf8"));
|
|
169
|
+
const copy = withoutDamagedGoogleSteps(raw);
|
|
170
|
+
if (copy === null) {
|
|
171
|
+
if (hasDamagedGoogleSteps(raw))
|
|
172
|
+
throw new Error("Cannot safely identify the damaged historical conversation; settlement or identity is unverified");
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
session = validateStoredSession(copy);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error instanceof Error &&
|
|
179
|
+
error.message.startsWith("Cannot safely identify"))
|
|
180
|
+
throw error;
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
if (session.sessionId !== sessionId ||
|
|
184
|
+
session.googleIdentity?.sessionId !== sessionId ||
|
|
185
|
+
session.googleIdentity.packageId !== session.packageId ||
|
|
186
|
+
session.googleIdentity.creationRequestId !== session.creationRequestId ||
|
|
187
|
+
session.requests?.[0]?.requestId !== session.creationRequestId ||
|
|
188
|
+
!/^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) ||
|
|
189
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u.test(session.creationRequestId) ||
|
|
190
|
+
hasPendingToolCalls(session))
|
|
191
|
+
throw new Error("Cannot safely identify the damaged historical conversation; its owner or creation identity is unverified");
|
|
192
|
+
return {
|
|
193
|
+
...summarizeStoredSettingImprovementSession(session),
|
|
194
|
+
runStatus: "interrupted",
|
|
195
|
+
damagedContinuation: true,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
130
198
|
async #recordSavedSession(session) {
|
|
131
199
|
if (this.#summaryIndex !== null)
|
|
132
200
|
this.#cacheSummary(summarizeStoredSettingImprovementSession(session));
|
|
@@ -219,6 +287,9 @@ export class FileNativeSettingImprovementStore {
|
|
|
219
287
|
const session = validateStoredSession(value);
|
|
220
288
|
if (session.sessionId !== sessionId)
|
|
221
289
|
throw new Error("Setting-improvement conversation identity does not match its file");
|
|
290
|
+
for (const request of session.requests ?? [])
|
|
291
|
+
if (request.wireRequest !== undefined)
|
|
292
|
+
request.wireRequest = await readGoogleHops(join(this.#sessionsRoot, ".google-hops", sessionId), request.requestId, request.wireRequest);
|
|
222
293
|
return session;
|
|
223
294
|
}
|
|
224
295
|
async #migrateLegacy(sessionId, source, value) {
|
|
@@ -362,7 +433,13 @@ function validateStoredSession(value) {
|
|
|
362
433
|
"authorization",
|
|
363
434
|
"pendingSettlement",
|
|
364
435
|
"legacyDraft",
|
|
365
|
-
], [
|
|
436
|
+
], [
|
|
437
|
+
"contentPackageTitle",
|
|
438
|
+
"playPreset",
|
|
439
|
+
"requests",
|
|
440
|
+
"googleOutcomeUnknown",
|
|
441
|
+
"googleIdentity",
|
|
442
|
+
]) ||
|
|
366
443
|
(value.schemaVersion !== 2 && value.schemaVersion !== 3) ||
|
|
367
444
|
(value.schemaVersion === 2
|
|
368
445
|
? value.requests !== undefined
|
|
@@ -392,12 +469,33 @@ function validateStoredSession(value) {
|
|
|
392
469
|
typeof value.activeRequestId !== "string") ||
|
|
393
470
|
!Array.isArray(value.completedRequestIds) ||
|
|
394
471
|
(value.lastFailure !== null && typeof value.lastFailure !== "string") ||
|
|
472
|
+
(value.googleOutcomeUnknown !== undefined &&
|
|
473
|
+
(typeof value.googleOutcomeUnknown !== "boolean" ||
|
|
474
|
+
!isRecord(value.modelBinding) ||
|
|
475
|
+
value.modelBinding.provider !== "google_interactions")) ||
|
|
476
|
+
(value.googleIdentity !== undefined &&
|
|
477
|
+
(!isRecord(value.googleIdentity) ||
|
|
478
|
+
!hasExactKeys(value.googleIdentity, [
|
|
479
|
+
"sessionId",
|
|
480
|
+
"packageId",
|
|
481
|
+
"creationRequestId",
|
|
482
|
+
]) ||
|
|
483
|
+
typeof value.googleIdentity.sessionId !== "string" ||
|
|
484
|
+
typeof value.googleIdentity.packageId !== "string" ||
|
|
485
|
+
typeof value.googleIdentity.creationRequestId !== "string" ||
|
|
486
|
+
!isRecord(value.modelBinding) ||
|
|
487
|
+
value.modelBinding.provider !== "google_interactions")) ||
|
|
395
488
|
!validAuthorization(value.authorization) ||
|
|
396
489
|
!validPendingSettlement(value.pendingSettlement) ||
|
|
397
490
|
!validPendingSettlementForTranscript(value.pendingSettlement, value.modelItems, value.runStatus) ||
|
|
398
491
|
!validLegacyDraft(value.legacyDraft))
|
|
399
492
|
throw new Error("Setting-improvement conversation does not match its durable schema");
|
|
400
493
|
assertSessionId(value.sessionId);
|
|
494
|
+
if (isRecord(value.googleIdentity) &&
|
|
495
|
+
(value.googleIdentity.sessionId !== value.sessionId ||
|
|
496
|
+
value.googleIdentity.packageId !== value.packageId ||
|
|
497
|
+
value.googleIdentity.creationRequestId !== value.creationRequestId))
|
|
498
|
+
throw new Error("Cannot safely identify the historical conversation; its owner or creation identity is inconsistent");
|
|
401
499
|
if (!value.completedRequestIds.every((id) => typeof id === "string") ||
|
|
402
500
|
!value.messages.every(validMessage) ||
|
|
403
501
|
!value.modelItems.every(validModelItem))
|
|
@@ -823,6 +921,9 @@ function validModelToolCall(value) {
|
|
|
823
921
|
function validProviderState(value) {
|
|
824
922
|
if (!isRecord(value))
|
|
825
923
|
return false;
|
|
924
|
+
if (value.protocol === "google_interactions")
|
|
925
|
+
return (hasExactKeys(value, ["protocol", "steps"]) &&
|
|
926
|
+
validGoogleInteractionsSteps(value.steps));
|
|
826
927
|
if (value.protocol === "chat_completions")
|
|
827
928
|
return (hasExactKeys(value, ["protocol", "assistantMessage"]) &&
|
|
828
929
|
Object.hasOwn(value, "assistantMessage"));
|
|
@@ -850,7 +951,10 @@ function validModelUsage(value) {
|
|
|
850
951
|
"totalTokens",
|
|
851
952
|
];
|
|
852
953
|
if (!isRecord(value) ||
|
|
853
|
-
!hasExactKeys(value, [...fields, "provenance"]) ||
|
|
954
|
+
!hasExactKeys(value, [...fields, "provenance"], ["providerRaw", "accounting"]) ||
|
|
955
|
+
(value.providerRaw !== undefined && !isRecord(value.providerRaw)) ||
|
|
956
|
+
(value.accounting !== undefined &&
|
|
957
|
+
value.accounting !== "google_interactions") ||
|
|
854
958
|
!fields.every((field) => validNullableCount(value[field])) ||
|
|
855
959
|
!isRecord(value.provenance) ||
|
|
856
960
|
!hasExactKeys(value.provenance, fields))
|
|
@@ -5,7 +5,7 @@ import { contentTreeFingerprint } from "../content/ContentTreeFingerprint.js";
|
|
|
5
5
|
import { equalModelHostBinding } from "../model/ModelHost.js";
|
|
6
6
|
import { settingImprovementPromptForBinding, } from "../play/FileNativePlayPresetStore.js";
|
|
7
7
|
import { WorldDocumentStore } from "../world/WorldDocumentStore.js";
|
|
8
|
-
import { appendAuthoringUserMessage, ModelToolConversation, rememberAuthoringRequest, resumePausedAuthoringConversation, } from "../authoring/ModelToolConversation.js";
|
|
8
|
+
import { appendAuthoringUserMessage, authoringExchange, ModelToolConversation, rememberAuthoringRequest, resumePausedAuthoringConversation, } from "../authoring/ModelToolConversation.js";
|
|
9
9
|
import { summarizeStoredSettingImprovementSession, } from "./FileNativeSettingImprovementStore.js";
|
|
10
10
|
import { SettingAuthoringTransaction, settingImprovementRuntimeContract, settingImprovementToolDefinitions, authoringPreloadAuthorization, buildAuthoringPreload, contentPackageAuthoringSurface, } from "./SettingAuthoringTransaction.js";
|
|
11
11
|
class RecoveryWriteConflictError extends Error {
|
|
@@ -62,7 +62,7 @@ export class SettingImprovementSession {
|
|
|
62
62
|
async overview(packageId) {
|
|
63
63
|
return this.#mutate(async () => {
|
|
64
64
|
let summaries = await this.#store.listSummariesByPackage(packageId);
|
|
65
|
-
const latestSummary = summaries
|
|
65
|
+
const latestSummary = summaries.find((summary) => !summary.damagedContinuation);
|
|
66
66
|
const latest = latestSummary === undefined
|
|
67
67
|
? null
|
|
68
68
|
: await this.#recoverIfNeeded(await this.#store.read(latestSummary.sessionId));
|
|
@@ -187,6 +187,8 @@ export class SettingImprovementSession {
|
|
|
187
187
|
assertPackage(session, input.packageId);
|
|
188
188
|
}
|
|
189
189
|
return this.#startRequest(session, requestId, async (next) => {
|
|
190
|
+
if (next.runStatus === "paused")
|
|
191
|
+
throw new Error("Resume the paused authoring request before sending another message");
|
|
190
192
|
const { playPreset, bootstrap, preload } = await this.#compileCurrent(input.packageId, next.modelBinding);
|
|
191
193
|
next.schemaVersion = 3;
|
|
192
194
|
next.requests ??= [];
|
|
@@ -199,6 +201,8 @@ export class SettingImprovementSession {
|
|
|
199
201
|
});
|
|
200
202
|
next.authorization = authoringPreloadAuthorization(next.authorization, preload.record);
|
|
201
203
|
appendAuthoringUserMessage(next, message, requestId);
|
|
204
|
+
if (next.modelBinding.provider === "google_interactions")
|
|
205
|
+
next.googleOutcomeUnknown = false;
|
|
202
206
|
});
|
|
203
207
|
});
|
|
204
208
|
return prepared.run ?? this.#view(prepared.session);
|
|
@@ -223,10 +227,16 @@ export class SettingImprovementSession {
|
|
|
223
227
|
throw new Error("Another message in this setting-improvement conversation is still running");
|
|
224
228
|
return { session, run: running.promise };
|
|
225
229
|
}
|
|
230
|
+
if (session.googleOutcomeUnknown)
|
|
231
|
+
throw new Error("The Google request outcome is unknown; start a fresh model context.");
|
|
226
232
|
const host = await this.#bindExistingModelHost(session.modelBinding);
|
|
227
233
|
if (!equalModelHostBinding(session.modelBinding, host.binding()))
|
|
228
234
|
throw new Error("No saved model connection matches this setting-improvement conversation");
|
|
229
235
|
await prepare(session);
|
|
236
|
+
if (session.modelBinding.provider === "google_interactions" &&
|
|
237
|
+
host.previewAuthoringRequest !== undefined &&
|
|
238
|
+
session.requests?.at(-1)?.requestId === requestId)
|
|
239
|
+
session.requests.at(-1).wireRequest = host.previewAuthoringRequest(authoringExchange(session, requestId));
|
|
230
240
|
await this.#store.save(session);
|
|
231
241
|
return { session, run: this.#startRun(session, host, requestId) };
|
|
232
242
|
}
|
|
@@ -317,9 +327,10 @@ export class SettingImprovementSession {
|
|
|
317
327
|
tools,
|
|
318
328
|
});
|
|
319
329
|
const now = Date.now();
|
|
330
|
+
const sessionId = this.#store.createId();
|
|
320
331
|
const session = {
|
|
321
332
|
schemaVersion: 2,
|
|
322
|
-
sessionId
|
|
333
|
+
sessionId,
|
|
323
334
|
packageId,
|
|
324
335
|
contentPackageTitle: package_.title,
|
|
325
336
|
locale,
|
|
@@ -327,6 +338,9 @@ export class SettingImprovementSession {
|
|
|
327
338
|
createdAt: now,
|
|
328
339
|
updatedAt: now,
|
|
329
340
|
creationRequestId,
|
|
341
|
+
...(modelBinding.provider === "google_interactions"
|
|
342
|
+
? { googleIdentity: { sessionId, packageId, creationRequestId } }
|
|
343
|
+
: {}),
|
|
330
344
|
bootstrap,
|
|
331
345
|
modelBinding,
|
|
332
346
|
playPreset: structuredClone(playPreset),
|
|
@@ -353,6 +367,7 @@ export class SettingImprovementSession {
|
|
|
353
367
|
host,
|
|
354
368
|
requestId,
|
|
355
369
|
save: (next) => this.#store.save(next),
|
|
370
|
+
recordGoogleHop: (id, hop) => this.#store.recordGoogleHop(session.sessionId, id, hop),
|
|
356
371
|
settleToolResponse: async (next, assistantItemIndex, toolCalls) => {
|
|
357
372
|
activeTransaction = await this.#settleToolResponse(next, assistantItemIndex, toolCalls, activeTransaction);
|
|
358
373
|
},
|
|
@@ -451,6 +466,8 @@ export class SettingImprovementSession {
|
|
|
451
466
|
return this.#recoverIfNeeded(await this.#store.read(session.sessionId));
|
|
452
467
|
}
|
|
453
468
|
if (session.pendingSettlement !== null) {
|
|
469
|
+
if (session.modelBinding.provider === "google_interactions")
|
|
470
|
+
session.googleOutcomeUnknown = false;
|
|
454
471
|
const lease = await this.#content.beginCurrentTreeContentPackageOperation(session.packageId);
|
|
455
472
|
let retry = false;
|
|
456
473
|
try {
|
|
@@ -510,7 +527,21 @@ export class SettingImprovementSession {
|
|
|
510
527
|
return this.#recoverIfNeeded(await this.#store.read(session.sessionId));
|
|
511
528
|
return session;
|
|
512
529
|
}
|
|
513
|
-
|
|
530
|
+
const lastItem = session.modelItems.at(-1);
|
|
531
|
+
const legacyUnknown = session.modelBinding.provider === "google_interactions" &&
|
|
532
|
+
session.runStatus === "interrupted" &&
|
|
533
|
+
session.googleOutcomeUnknown === undefined &&
|
|
534
|
+
(lastItem?.kind === "user" || lastItem?.kind === "tool");
|
|
535
|
+
if (legacyUnknown) {
|
|
536
|
+
session.googleOutcomeUnknown = true;
|
|
537
|
+
session.lastFailure =
|
|
538
|
+
"The Google request outcome is unknown; start a fresh model context.";
|
|
539
|
+
session.updatedAt = Date.now();
|
|
540
|
+
}
|
|
541
|
+
else if (session.runStatus === "running") {
|
|
542
|
+
if (session.modelBinding.provider === "google_interactions" &&
|
|
543
|
+
session.googleOutcomeUnknown !== false)
|
|
544
|
+
session.googleOutcomeUnknown = true;
|
|
514
545
|
markInterruptedAfterRestart(session);
|
|
515
546
|
session.updatedAt = Date.now();
|
|
516
547
|
}
|
|
@@ -606,12 +637,17 @@ function lastPendingAssistant(session) {
|
|
|
606
637
|
}
|
|
607
638
|
function markInterruptedAfterRestart(session) {
|
|
608
639
|
const requestId = session.activeRequestId;
|
|
609
|
-
session.runStatus =
|
|
640
|
+
session.runStatus =
|
|
641
|
+
session.modelBinding.provider === "google_interactions" &&
|
|
642
|
+
session.googleOutcomeUnknown === false
|
|
643
|
+
? "paused"
|
|
644
|
+
: "interrupted";
|
|
610
645
|
session.activeRequestId = null;
|
|
611
646
|
if (requestId !== null)
|
|
612
647
|
session.completedRequestIds = rememberAuthoringRequest(session.completedRequestIds, requestId);
|
|
613
|
-
session.lastFailure =
|
|
614
|
-
"The
|
|
648
|
+
session.lastFailure = session.googleOutcomeUnknown
|
|
649
|
+
? "The Google request outcome is unknown; start a fresh model context."
|
|
650
|
+
: "The previous model request stopped before its next complete response. Every settled current-tree change and complete tool result was retained; continue this conversation when ready.";
|
|
615
651
|
}
|
|
616
652
|
function requiredMessage(message) {
|
|
617
653
|
if (message.trim().length === 0)
|
|
@@ -750,6 +786,11 @@ function diffForRollback(files, change) {
|
|
|
750
786
|
function settingImprovementHistoryItem(summary) {
|
|
751
787
|
return {
|
|
752
788
|
sessionId: summary.sessionId,
|
|
789
|
+
...(summary.damagedContinuation
|
|
790
|
+
? {
|
|
791
|
+
unavailableReason: "Google continuation is damaged; start a fresh model context.",
|
|
792
|
+
}
|
|
793
|
+
: {}),
|
|
753
794
|
runStatus: summary.runStatus,
|
|
754
795
|
createdAt: summary.createdAt,
|
|
755
796
|
updatedAt: summary.updatedAt,
|
|
@@ -3,9 +3,12 @@ import { upgradeLegacyEpochs } from "./LegacyWorldRevisionEpochUpgrade.js";
|
|
|
3
3
|
import { isPackageFollowupControlPath } from "../content/PackageFollowups.js";
|
|
4
4
|
import { validAuthoringRequests, } from "../authoring/AuthoringRequestSnapshot.js";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
|
-
import {
|
|
6
|
+
import { readGoogleHops, writeGoogleHop } from "../model/GoogleHopJournal.js";
|
|
7
|
+
import { mkdir, open, readFile, readdir, rename, rm, stat, } from "node:fs/promises";
|
|
7
8
|
import { dirname, join, resolve } from "node:path";
|
|
8
9
|
import { gameplayPath } from "../../protocol/gameplay.js";
|
|
10
|
+
import { validGoogleInteractionsSteps } from "../model/ProviderExchangeState.js";
|
|
11
|
+
import { hasDamagedGoogleSteps, withoutDamagedGoogleSteps, } from "../authoring/DamagedGoogleContinuation.js";
|
|
9
12
|
import { contentTreeFingerprint } from "../content/ContentTreeFingerprint.js";
|
|
10
13
|
import { isPlayPresetBinding, } from "../play/FileNativePlayPresetStore.js";
|
|
11
14
|
const uuidV4Pattern = "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
|
|
@@ -46,6 +49,10 @@ export class FileNativeWorldRevisionStore {
|
|
|
46
49
|
createSessionId() {
|
|
47
50
|
return `world-session-${randomUUID()}`;
|
|
48
51
|
}
|
|
52
|
+
recordGoogleHop(sessionId, requestId, hop) {
|
|
53
|
+
assertSessionId(sessionId);
|
|
54
|
+
return writeGoogleHop(join(this.#sessionsRoot, ".google-hops", sessionId), requestId, hop, () => stat(this.#sessionPath(sessionId)).then(() => true, () => false));
|
|
55
|
+
}
|
|
49
56
|
createChangeSetId() {
|
|
50
57
|
return `change-set:${randomUUID()}`;
|
|
51
58
|
}
|
|
@@ -89,7 +96,20 @@ export class FileNativeWorldRevisionStore {
|
|
|
89
96
|
});
|
|
90
97
|
}
|
|
91
98
|
readSession(sessionId) {
|
|
92
|
-
return this.#afterMutations(() =>
|
|
99
|
+
return this.#afterMutations(async () => {
|
|
100
|
+
try {
|
|
101
|
+
const session = await this.#readSession(sessionId);
|
|
102
|
+
if (hasDamagedGoogleSteps(session) &&
|
|
103
|
+
(await this.#damagedSession(sessionId)))
|
|
104
|
+
throw new Error("The historical conversation has damaged Google continuation; start a fresh model context");
|
|
105
|
+
return session;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (await this.#damagedSession(sessionId))
|
|
109
|
+
throw new Error("The historical conversation has damaged Google continuation; start a fresh model context", { cause: error });
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
93
113
|
}
|
|
94
114
|
saveSession(session) {
|
|
95
115
|
return this.#mutate(async () => {
|
|
@@ -110,7 +130,19 @@ export class FileNativeWorldRevisionStore {
|
|
|
110
130
|
for (const entry of entries) {
|
|
111
131
|
if (!entry.isFile() || !sessionName.test(entry.name))
|
|
112
132
|
continue;
|
|
113
|
-
const
|
|
133
|
+
const sessionId = entry.name.slice(0, -5);
|
|
134
|
+
let session;
|
|
135
|
+
try {
|
|
136
|
+
session = await this.#readSession(sessionId);
|
|
137
|
+
if (hasDamagedGoogleSteps(session))
|
|
138
|
+
session = (await this.#damagedSession(sessionId)) ?? session;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
const damaged = await this.#damagedSession(sessionId);
|
|
142
|
+
if (damaged === null)
|
|
143
|
+
throw error;
|
|
144
|
+
session = damaged;
|
|
145
|
+
}
|
|
114
146
|
if (session.worldId === worldId)
|
|
115
147
|
sessions.push(session);
|
|
116
148
|
}
|
|
@@ -118,7 +150,10 @@ export class FileNativeWorldRevisionStore {
|
|
|
118
150
|
});
|
|
119
151
|
}
|
|
120
152
|
async findByCreationRequest(worldId, requestId) {
|
|
121
|
-
|
|
153
|
+
const match = (await this.listSessions(worldId)).find(({ creationRequestId }) => creationRequestId === requestId);
|
|
154
|
+
if (match?.damagedContinuation)
|
|
155
|
+
throw new Error("The historical conversation has damaged Google continuation; start a fresh model context with a new request ID");
|
|
156
|
+
return match ?? null;
|
|
122
157
|
}
|
|
123
158
|
deleteSession(worldId, sessionId) {
|
|
124
159
|
return this.#mutate(async () => {
|
|
@@ -126,6 +161,10 @@ export class FileNativeWorldRevisionStore {
|
|
|
126
161
|
if (session.worldId !== worldId)
|
|
127
162
|
throw new Error("World-revision conversation belongs to another world");
|
|
128
163
|
await rm(this.#sessionPath(sessionId));
|
|
164
|
+
await rm(join(this.#sessionsRoot, ".google-hops", sessionId), {
|
|
165
|
+
recursive: true,
|
|
166
|
+
force: true,
|
|
167
|
+
});
|
|
129
168
|
});
|
|
130
169
|
}
|
|
131
170
|
async #readEpoch(epochId) {
|
|
@@ -155,8 +194,34 @@ export class FileNativeWorldRevisionStore {
|
|
|
155
194
|
const session = validateSession(value);
|
|
156
195
|
if (session.sessionId !== sessionId)
|
|
157
196
|
throw new Error("World-revision conversation identity does not match its file");
|
|
197
|
+
for (const request of session.requests ?? [])
|
|
198
|
+
if (request.wireRequest !== undefined)
|
|
199
|
+
request.wireRequest = await readGoogleHops(join(this.#sessionsRoot, ".google-hops", sessionId), request.requestId, request.wireRequest);
|
|
158
200
|
return session;
|
|
159
201
|
}
|
|
202
|
+
async #damagedSession(sessionId) {
|
|
203
|
+
const raw = JSON.parse(await readFile(this.#sessionPath(sessionId), "utf8"));
|
|
204
|
+
const copy = withoutDamagedGoogleSteps(raw);
|
|
205
|
+
if (copy === null) {
|
|
206
|
+
if (hasDamagedGoogleSteps(raw))
|
|
207
|
+
throw new Error("Cannot safely identify the damaged historical conversation; settlement or identity is unverified");
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
const session = validateSession(copy);
|
|
211
|
+
if (session.sessionId !== sessionId ||
|
|
212
|
+
session.requests?.[0]?.requestId !== session.creationRequestId ||
|
|
213
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u.test(session.creationRequestId))
|
|
214
|
+
throw new Error("Cannot safely identify the damaged historical conversation; its creation identity is unverified");
|
|
215
|
+
const epoch = await this.#readEpoch(session.epochId);
|
|
216
|
+
if (epoch.worldId !== session.worldId)
|
|
217
|
+
throw new Error("Cannot safely identify the damaged historical conversation; its world is inconsistent");
|
|
218
|
+
return {
|
|
219
|
+
...session,
|
|
220
|
+
damagedContinuation: true,
|
|
221
|
+
runStatus: "interrupted",
|
|
222
|
+
lastFailure: "Google continuation is damaged; start a fresh model context.",
|
|
223
|
+
};
|
|
224
|
+
}
|
|
160
225
|
async #afterMutations(read) {
|
|
161
226
|
await this.#mutationTail;
|
|
162
227
|
return read();
|
|
@@ -274,7 +339,7 @@ function validateSession(value) {
|
|
|
274
339
|
"lastFailure",
|
|
275
340
|
"authorization",
|
|
276
341
|
"pendingSettlement",
|
|
277
|
-
], ["requests"]) ||
|
|
342
|
+
], ["requests", "googleOutcomeUnknown"]) ||
|
|
278
343
|
(value.schemaVersion !== 1 && value.schemaVersion !== 2) ||
|
|
279
344
|
(value.schemaVersion === 1
|
|
280
345
|
? value.requests !== undefined
|
|
@@ -311,6 +376,10 @@ function validateSession(value) {
|
|
|
311
376
|
!Array.isArray(value.completedRequestIds) ||
|
|
312
377
|
!value.completedRequestIds.every((item) => typeof item === "string") ||
|
|
313
378
|
(value.lastFailure !== null && typeof value.lastFailure !== "string") ||
|
|
379
|
+
(value.googleOutcomeUnknown !== undefined &&
|
|
380
|
+
(typeof value.googleOutcomeUnknown !== "boolean" ||
|
|
381
|
+
!isRecord(value.modelBinding) ||
|
|
382
|
+
value.modelBinding.provider !== "google_interactions")) ||
|
|
314
383
|
!validAuthorization(value.authorization) ||
|
|
315
384
|
!validPendingSettlement(value.pendingSettlement) ||
|
|
316
385
|
!validPendingForSession(value.pendingSettlement, value.epochId, value.runStatus) ||
|
|
@@ -452,6 +521,9 @@ function validModelToolCall(value) {
|
|
|
452
521
|
function validProviderState(value) {
|
|
453
522
|
if (!isRecord(value))
|
|
454
523
|
return false;
|
|
524
|
+
if (value.protocol === "google_interactions")
|
|
525
|
+
return (hasExactKeys(value, ["protocol", "steps"]) &&
|
|
526
|
+
validGoogleInteractionsSteps(value.steps));
|
|
455
527
|
if (value.protocol === "chat_completions")
|
|
456
528
|
return hasExactKeys(value, ["protocol", "assistantMessage"]);
|
|
457
529
|
if (value.protocol === "openai_responses")
|
|
@@ -509,7 +581,8 @@ function validModelBinding(value) {
|
|
|
509
581
|
], ["cacheStrategy"]) &&
|
|
510
582
|
(value.provider === "chat_completions" ||
|
|
511
583
|
value.provider === "openai_responses" ||
|
|
512
|
-
value.provider === "anthropic_messages"
|
|
584
|
+
value.provider === "anthropic_messages" ||
|
|
585
|
+
value.provider === "google_interactions") &&
|
|
513
586
|
typeof value.endpointFingerprint === "string" &&
|
|
514
587
|
typeof value.modelId === "string" &&
|
|
515
588
|
count(value.contextWindowTokens) &&
|
|
@@ -531,7 +604,10 @@ function validModelUsage(value) {
|
|
|
531
604
|
"totalTokens",
|
|
532
605
|
];
|
|
533
606
|
if (!isRecord(value) ||
|
|
534
|
-
!hasExactKeys(value, [...fields, "provenance"]) ||
|
|
607
|
+
!hasExactKeys(value, [...fields, "provenance"], ["providerRaw", "accounting"]) ||
|
|
608
|
+
(value.providerRaw !== undefined && !isRecord(value.providerRaw)) ||
|
|
609
|
+
(value.accounting !== undefined &&
|
|
610
|
+
value.accounting !== "google_interactions") ||
|
|
535
611
|
!fields.every((field) => value[field] === null || count(value[field])) ||
|
|
536
612
|
!isRecord(value.provenance) ||
|
|
537
613
|
!hasExactKeys(value.provenance, fields))
|
|
@@ -594,7 +670,7 @@ function validPendingSettlement(value) {
|
|
|
594
670
|
function validPendingForSession(pending, epochId, runStatus) {
|
|
595
671
|
return (pending === null ||
|
|
596
672
|
(isRecord(pending) &&
|
|
597
|
-
runStatus === "running" &&
|
|
673
|
+
(runStatus === "running" || runStatus === "interrupted") &&
|
|
598
674
|
pending.epochId === epochId));
|
|
599
675
|
}
|
|
600
676
|
function validToolResult(value) {
|