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.
- 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 +97 -0
- package/dist/node/runtime/V1Runtime.js +105 -3
- package/dist/node/runtime/authoring/AuthoringEditPlan.js +79 -0
- package/dist/node/runtime/authoring/AuthoringRequestSnapshot.js +15 -3
- package/dist/node/runtime/authoring/AuthoringRetry.js +59 -0
- package/dist/node/runtime/authoring/DamagedGoogleContinuation.js +68 -0
- package/dist/node/runtime/authoring/ModelToolConversation.js +66 -25
- package/dist/node/runtime/content/ContentWorkspace.js +10 -2
- package/dist/node/runtime/content/CurrentTreeContentLibrary.js +14 -8
- package/dist/node/runtime/content/SillyTavernContent.js +796 -0
- package/dist/node/runtime/model/AiFailureLog.js +8 -1
- package/dist/node/runtime/model/DispatchFailure.js +8 -0
- package/dist/node/runtime/model/FileNativeModelAdapters.js +627 -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 +199 -10
- package/dist/node/runtime/play/OrderedPlayPrompts.js +139 -178
- package/dist/node/runtime/play/PlayAdvanceLifecycle.js +51 -23
- package/dist/node/runtime/play/PlayCallChain.js +92 -8
- 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 +321 -15
- package/dist/node/runtime/setting/SettingImprovementSession.js +285 -21
- package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +236 -15
- package/dist/node/runtime/world-revision/WorldRevisionSession.js +324 -50
- package/dist/node/runtime/world-revision/WorldRevisionWorkspace.js +47 -1
- 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-BthiDIQu.js +1432 -0
- package/dist/web/assets/{index-DH2BVsYd.css → index-wO9Y4HT1.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-B2kbaOGe.js +0 -1408
|
@@ -6,6 +6,15 @@ export function hasCLIProxyThinkingSuffix(modelId) {
|
|
|
6
6
|
export function modelReasoningPolicyIssue(input) {
|
|
7
7
|
const thinking = input.thinking ?? "provider_default";
|
|
8
8
|
const thinkingBudgetTokens = input.thinkingBudgetTokens ?? null;
|
|
9
|
+
if (input.provider === "google_interactions") {
|
|
10
|
+
if (input.dialect !== "standard")
|
|
11
|
+
return "Google Interactions requires the standard dialect";
|
|
12
|
+
if (input.effort !== "provider_default" ||
|
|
13
|
+
input.summary !== "provider_default" ||
|
|
14
|
+
thinking !== "provider_default" ||
|
|
15
|
+
thinkingBudgetTokens !== null)
|
|
16
|
+
return "Google Interactions model capabilities are unverified; use provider defaults";
|
|
17
|
+
}
|
|
9
18
|
const thinkingControlledByCLIProxySuffix = input.provider === "anthropic_messages" &&
|
|
10
19
|
input.dialect === "cliproxyapi" &&
|
|
11
20
|
hasCLIProxyThinkingSuffix(input.modelId);
|
|
@@ -54,6 +63,12 @@ export const modelProviderPresets = [
|
|
|
54
63
|
provider: "chat_completions",
|
|
55
64
|
baseUrl: "",
|
|
56
65
|
},
|
|
66
|
+
{
|
|
67
|
+
id: "google-interactions",
|
|
68
|
+
name: "Google Gemini · Interactions",
|
|
69
|
+
provider: "google_interactions",
|
|
70
|
+
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
|
71
|
+
},
|
|
57
72
|
{
|
|
58
73
|
id: "openai",
|
|
59
74
|
name: "OpenAI · Responses API",
|
|
@@ -24,6 +24,10 @@ export function aggregateModelUsage(current, next) {
|
|
|
24
24
|
}
|
|
25
25
|
return {
|
|
26
26
|
...result,
|
|
27
|
+
...(next?.accounting === "google_interactions" ||
|
|
28
|
+
current.accounting === "google_interactions"
|
|
29
|
+
? { accounting: "google_interactions" }
|
|
30
|
+
: {}),
|
|
27
31
|
provenance: Object.fromEntries(modelUsageKeys.map((key) => [
|
|
28
32
|
key,
|
|
29
33
|
result[key] === null ? "unavailable" : "derived_provider_fields",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/node/protocol/v1.js
CHANGED
|
@@ -73,6 +73,14 @@ const requiredFields = {
|
|
|
73
73
|
message: "string",
|
|
74
74
|
continuation: "object",
|
|
75
75
|
},
|
|
76
|
+
"setting-improvement.message.edit": {
|
|
77
|
+
packageId: "string",
|
|
78
|
+
sessionId: "string",
|
|
79
|
+
requestId: "string",
|
|
80
|
+
messageId: "string",
|
|
81
|
+
message: "string",
|
|
82
|
+
filePolicy: "string",
|
|
83
|
+
},
|
|
76
84
|
"setting-improvement.cancel": { sessionId: "string" },
|
|
77
85
|
"setting-improvement.resume": {
|
|
78
86
|
packageId: "string",
|
|
@@ -89,6 +97,28 @@ const requiredFields = {
|
|
|
89
97
|
"play.select": { presetId: "string" },
|
|
90
98
|
"play.export": { presetId: "string" },
|
|
91
99
|
"play.import": { name: "string", files: "array" },
|
|
100
|
+
"content.sillytavern.inspect": { source: "string" },
|
|
101
|
+
"content.sillytavern.preview": {
|
|
102
|
+
source: "string",
|
|
103
|
+
name: "string",
|
|
104
|
+
selection: "object",
|
|
105
|
+
},
|
|
106
|
+
"content.sillytavern.import": {
|
|
107
|
+
source: "string",
|
|
108
|
+
name: "string",
|
|
109
|
+
selection: "object",
|
|
110
|
+
},
|
|
111
|
+
"play.sillytavern.inspect": { source: "string" },
|
|
112
|
+
"play.sillytavern.preview": {
|
|
113
|
+
source: "string",
|
|
114
|
+
selected: "array",
|
|
115
|
+
name: "string",
|
|
116
|
+
},
|
|
117
|
+
"play.sillytavern.import": {
|
|
118
|
+
source: "string",
|
|
119
|
+
selected: "array",
|
|
120
|
+
name: "string",
|
|
121
|
+
},
|
|
92
122
|
"play.workbench.read": {},
|
|
93
123
|
"prompt.preview": {
|
|
94
124
|
packageId: "string",
|
|
@@ -130,6 +160,14 @@ const requiredFields = {
|
|
|
130
160
|
message: "string",
|
|
131
161
|
continuation: "object",
|
|
132
162
|
},
|
|
163
|
+
"world.revision.message.edit": {
|
|
164
|
+
worldId: "string",
|
|
165
|
+
sessionId: "string",
|
|
166
|
+
requestId: "string",
|
|
167
|
+
messageId: "string",
|
|
168
|
+
message: "string",
|
|
169
|
+
filePolicy: "string",
|
|
170
|
+
},
|
|
133
171
|
"world.revision.cancel": { sessionId: "string" },
|
|
134
172
|
"world.revision.resume": {
|
|
135
173
|
worldId: "string",
|
|
@@ -229,6 +267,10 @@ function validateRequestFields(request) {
|
|
|
229
267
|
if (!valid || (kind === "string" && value === "" && !allowsEmptyString))
|
|
230
268
|
throw new V1ProtocolError("invalid_request", `${String(request.type)}.${field} is invalid`);
|
|
231
269
|
}
|
|
270
|
+
if (request.type === "setting-improvement.message.edit" &&
|
|
271
|
+
request.filePolicy !== "keep" &&
|
|
272
|
+
request.filePolicy !== "revert")
|
|
273
|
+
throw new V1ProtocolError("invalid_request", "setting-improvement.message.edit.filePolicy is invalid");
|
|
232
274
|
if (request.type === "world.story.export" &&
|
|
233
275
|
request.format !== "markdown" &&
|
|
234
276
|
request.format !== "text")
|
|
@@ -326,6 +368,53 @@ function validateRequestFields(request) {
|
|
|
326
368
|
if (request.revision !== undefined && request.presetId === undefined)
|
|
327
369
|
throw new V1ProtocolError("invalid_request", "play.workbench.read.revision must be provided with presetId");
|
|
328
370
|
}
|
|
371
|
+
if (typeof request.type === "string" &&
|
|
372
|
+
request.type.startsWith("content.sillytavern.")) {
|
|
373
|
+
const inspect = request.type === "content.sillytavern.inspect";
|
|
374
|
+
const allowed = inspect
|
|
375
|
+
? ["type", "source"]
|
|
376
|
+
: ["type", "source", "name", "selection"];
|
|
377
|
+
if (Object.keys(request).some((key) => !allowed.includes(key)))
|
|
378
|
+
throw new V1ProtocolError("invalid_request", "Invalid SillyTavern content fields");
|
|
379
|
+
if (!inspect) {
|
|
380
|
+
const selection = request.selection;
|
|
381
|
+
if (!isRecord(selection) ||
|
|
382
|
+
Object.keys(selection).some((key) => ![
|
|
383
|
+
"character",
|
|
384
|
+
"scenario",
|
|
385
|
+
"opening",
|
|
386
|
+
"example",
|
|
387
|
+
"systemPrompt",
|
|
388
|
+
"attachments",
|
|
389
|
+
"entries",
|
|
390
|
+
].includes(key)) ||
|
|
391
|
+
(!Array.isArray(selection.attachments) &&
|
|
392
|
+
selection.attachments !== undefined) ||
|
|
393
|
+
(Array.isArray(selection.attachments) &&
|
|
394
|
+
!selection.attachments.every((value) => typeof value === "string")) ||
|
|
395
|
+
!isRecord(selection.entries) ||
|
|
396
|
+
Object.values(selection.entries).some((value) => value !== "world" && value !== "attachment") ||
|
|
397
|
+
["character", "scenario", "example", "systemPrompt"].some((key) => selection[key] !== undefined && typeof selection[key] !== "boolean") ||
|
|
398
|
+
(selection.opening !== undefined &&
|
|
399
|
+
typeof selection.opening !== "string"))
|
|
400
|
+
throw new V1ProtocolError("invalid_request", "Invalid SillyTavern content selection");
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (typeof request.type === "string" &&
|
|
404
|
+
request.type.startsWith("play.sillytavern.")) {
|
|
405
|
+
const inspect = request.type === "play.sillytavern.inspect";
|
|
406
|
+
const allowed = inspect
|
|
407
|
+
? ["type", "source", "groupId"]
|
|
408
|
+
: ["type", "source", "groupId", "selected", "name"];
|
|
409
|
+
if (Object.keys(request).some((key) => !allowed.includes(key)) ||
|
|
410
|
+
(request.groupId !== undefined &&
|
|
411
|
+
(typeof request.groupId !== "string" ||
|
|
412
|
+
request.groupId.length === 0)) ||
|
|
413
|
+
(!inspect &&
|
|
414
|
+
(!Array.isArray(request.selected) ||
|
|
415
|
+
!request.selected.every((value) => typeof value === "string" && value.length > 0))))
|
|
416
|
+
throw new V1ProtocolError("invalid_request", `${request.type} has invalid fields`);
|
|
417
|
+
}
|
|
329
418
|
if (request.type === "play.import") {
|
|
330
419
|
if (!request.files.every((file) => isRecord(file) &&
|
|
331
420
|
typeof file.path === "string" &&
|
|
@@ -441,6 +530,7 @@ const requestTypes = new Set([
|
|
|
441
530
|
"setting-improvement.session.delete",
|
|
442
531
|
"setting-improvement.rollback",
|
|
443
532
|
"setting-improvement.message",
|
|
533
|
+
"setting-improvement.message.edit",
|
|
444
534
|
"setting-improvement.cancel",
|
|
445
535
|
"setting-improvement.resume",
|
|
446
536
|
"play.read",
|
|
@@ -454,6 +544,12 @@ const requestTypes = new Set([
|
|
|
454
544
|
"play.select",
|
|
455
545
|
"play.export",
|
|
456
546
|
"play.import",
|
|
547
|
+
"content.sillytavern.inspect",
|
|
548
|
+
"content.sillytavern.preview",
|
|
549
|
+
"content.sillytavern.import",
|
|
550
|
+
"play.sillytavern.inspect",
|
|
551
|
+
"play.sillytavern.preview",
|
|
552
|
+
"play.sillytavern.import",
|
|
457
553
|
"play.workbench.read",
|
|
458
554
|
"prompt.preview",
|
|
459
555
|
"world.create",
|
|
@@ -476,6 +572,7 @@ const requestTypes = new Set([
|
|
|
476
572
|
"world.revision.session.read",
|
|
477
573
|
"world.revision.session.delete",
|
|
478
574
|
"world.revision.message",
|
|
575
|
+
"world.revision.message.edit",
|
|
479
576
|
"world.revision.cancel",
|
|
480
577
|
"world.revision.resume",
|
|
481
578
|
"world.revision.files.replace",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { inspectSillyTavernContent, convertSillyTavernContent, } from "./content/SillyTavernContent.js";
|
|
2
|
+
import { inspectSillyTavernPreset, convertSillyTavernPreset, } from "../shared/sillytavern-preset.js";
|
|
1
3
|
import { WorldArchives } from "./archive/WorldArchives.js";
|
|
2
4
|
import { WorldArchiveError } from "./archive/WorldArchiveZip.js";
|
|
3
5
|
import { FileNativeWorldCreationError } from "./world/FileNativeWorldStore.js";
|
|
@@ -16,14 +18,14 @@ import { ModelConnectionStore } from "./model/ModelConnectionStore.js";
|
|
|
16
18
|
import { FileNativeContinuityCorrection } from "./play/FileNativeContinuityCorrection.js";
|
|
17
19
|
import { fingerprintControl } from "./play/PlayDocumentTools.js";
|
|
18
20
|
import { PlayCallChain, PlayCallChainError, } from "./play/PlayCallChain.js";
|
|
19
|
-
import { FileNativePlayPresetError, FileNativePlayPresetStore, presetHostBinding, } from "./play/FileNativePlayPresetStore.js";
|
|
21
|
+
import { FileNativePlayPresetError, FileNativePlayPresetStore, presetHostBinding, parsePlayPresetFiles, toPlayPresetStructuredEditor, } from "./play/FileNativePlayPresetStore.js";
|
|
20
22
|
import { buildPlayPresetWorkbenchSnapshot } from "./play/PlayPresetWorkbench.js";
|
|
21
23
|
import { FileNativeArtifactStore } from "./artifact/FileNativeArtifactStore.js";
|
|
22
24
|
import { projectArtifactForFrontend, projectDebugArtifactForFrontend, } from "./extension/FrontendExtensionBundle.js";
|
|
23
25
|
import { projectPlayerViewPanels, } from "./extension/PlayerViewPanelProjector.js";
|
|
24
26
|
import { FileNativePromptCompiler } from "./prompt/FileNativePromptCompiler.js";
|
|
25
27
|
import { FileNativeSettingImprovementStore } from "./setting/FileNativeSettingImprovementStore.js";
|
|
26
|
-
import { SettingImprovementRollbackError, SettingImprovementSession, } from "./setting/SettingImprovementSession.js";
|
|
28
|
+
import { SettingImprovementRollbackError, SettingImprovementSession, SettingMessageEditError, } from "./setting/SettingImprovementSession.js";
|
|
27
29
|
import { FileNativeWorldStore } from "./world/FileNativeWorldStore.js";
|
|
28
30
|
import { WorldDocumentStore } from "./world/WorldDocumentStore.js";
|
|
29
31
|
import { FileNativeAiFailureLog } from "./model/AiFailureLog.js";
|
|
@@ -31,7 +33,7 @@ import { FileNativeRuntimeErrorLog } from "./RuntimeErrorLog.js";
|
|
|
31
33
|
import { ProtagonistTemplateStore } from "./config/ProtagonistTemplateStore.js";
|
|
32
34
|
import { AppPreferencesStore } from "./config/AppPreferencesStore.js";
|
|
33
35
|
import { FileNativeWorldRevisionStore } from "./world-revision/FileNativeWorldRevisionStore.js";
|
|
34
|
-
import { WorldRevisionSession } from "./world-revision/WorldRevisionSession.js";
|
|
36
|
+
import { WorldRevisionSession, WorldRevisionMessageEditError, } from "./world-revision/WorldRevisionSession.js";
|
|
35
37
|
import { WorldRevisionWorkspace, WorldRevisionWorkspaceError, } from "./world-revision/WorldRevisionWorkspace.js";
|
|
36
38
|
import { ConversationChanges } from "./ConversationChanges.js";
|
|
37
39
|
// 先取得当前端点再按它读取的请求,在端点被回退丢弃时最多重做这么多次(#94)。
|
|
@@ -363,6 +365,33 @@ export class V1Runtime {
|
|
|
363
365
|
throw error;
|
|
364
366
|
}
|
|
365
367
|
}
|
|
368
|
+
case "content.sillytavern.inspect":
|
|
369
|
+
case "content.sillytavern.preview":
|
|
370
|
+
case "content.sillytavern.import": {
|
|
371
|
+
try {
|
|
372
|
+
if (request.source.length > 12 * 1024 * 1024 ||
|
|
373
|
+
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(request.source))
|
|
374
|
+
throw new Error("Invalid source encoding");
|
|
375
|
+
const source = Buffer.from(request.source, "base64");
|
|
376
|
+
if (source.toString("base64") !== request.source)
|
|
377
|
+
throw new Error("Invalid source encoding");
|
|
378
|
+
if (request.type === "content.sillytavern.inspect")
|
|
379
|
+
return inspectSillyTavernContent(source);
|
|
380
|
+
const converted = convertSillyTavernContent(source, request.selection, this.#locale);
|
|
381
|
+
this.#content.validateCurrentTreeContentPackage(converted.files);
|
|
382
|
+
if (request.type === "content.sillytavern.preview")
|
|
383
|
+
return {
|
|
384
|
+
...converted,
|
|
385
|
+
inspection: this.#content.inspectCurrentTreeContentPackage(converted.files),
|
|
386
|
+
};
|
|
387
|
+
return await this.#content.importSillyTavernContent(request.name, converted.files);
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
throw new V1ProtocolError("invalid_request", error instanceof Error
|
|
391
|
+
? error.message
|
|
392
|
+
: "Invalid SillyTavern content");
|
|
393
|
+
}
|
|
394
|
+
}
|
|
366
395
|
case "content.export": {
|
|
367
396
|
const exported = await this.#content.exportCurrentTreeContentPackage(request.packageId);
|
|
368
397
|
return {
|
|
@@ -395,6 +424,22 @@ export class V1Runtime {
|
|
|
395
424
|
}
|
|
396
425
|
case "setting-improvement.message":
|
|
397
426
|
return this.#settingImprovements.send(request);
|
|
427
|
+
case "setting-improvement.message.edit":
|
|
428
|
+
try {
|
|
429
|
+
return await this.#settingImprovements.editMessage(request);
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
if (error instanceof SettingMessageEditError)
|
|
433
|
+
throw new V1ProtocolError("invalid_request", error.message, {
|
|
434
|
+
cause: error,
|
|
435
|
+
details: {
|
|
436
|
+
kind: "setting_message_edit",
|
|
437
|
+
reason: error.reason,
|
|
438
|
+
paths: error.paths,
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
398
443
|
case "setting-improvement.cancel":
|
|
399
444
|
return this.#settingImprovements.cancel(request.sessionId);
|
|
400
445
|
case "setting-improvement.resume":
|
|
@@ -423,6 +468,47 @@ export class V1Runtime {
|
|
|
423
468
|
};
|
|
424
469
|
case "play.import":
|
|
425
470
|
return this.#playPresets.importPortable(request);
|
|
471
|
+
case "play.sillytavern.inspect":
|
|
472
|
+
case "play.sillytavern.preview":
|
|
473
|
+
case "play.sillytavern.import": {
|
|
474
|
+
// Do not trust the browser to enforce the import limit or supply
|
|
475
|
+
// already-converted files. Preview and import both parse from source.
|
|
476
|
+
if (Buffer.byteLength(request.source, "utf8") > 2 * 1024 * 1024)
|
|
477
|
+
throw new V1ProtocolError("invalid_request", "SillyTavern source exceeds the size limit");
|
|
478
|
+
if (request.type !== "play.sillytavern.inspect" &&
|
|
479
|
+
(request.selected.length > 4096 ||
|
|
480
|
+
request.selected.reduce((bytes, id) => bytes + Buffer.byteLength(id, "utf8"), 0) >
|
|
481
|
+
2 * 1024 * 1024))
|
|
482
|
+
throw new V1ProtocolError("invalid_request", "SillyTavern selection exceeds the size limit");
|
|
483
|
+
try {
|
|
484
|
+
if (request.type === "play.sillytavern.inspect")
|
|
485
|
+
return inspectSillyTavernPreset(request.source, request.groupId);
|
|
486
|
+
const converted = convertSillyTavernPreset(request.source, {
|
|
487
|
+
groupId: request.groupId,
|
|
488
|
+
selected: request.selected,
|
|
489
|
+
name: request.name,
|
|
490
|
+
locale: this.#locale,
|
|
491
|
+
});
|
|
492
|
+
if (request.type === "play.sillytavern.preview") {
|
|
493
|
+
const parsed = parsePlayPresetFiles(converted.files);
|
|
494
|
+
if (parsed.kind === "invalid")
|
|
495
|
+
throw parsed.error;
|
|
496
|
+
toPlayPresetStructuredEditor(parsed.definition);
|
|
497
|
+
return converted;
|
|
498
|
+
}
|
|
499
|
+
return await this.#playPresets.importPortable({
|
|
500
|
+
name: request.name,
|
|
501
|
+
files: converted.files,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
if (error instanceof V1ProtocolError)
|
|
506
|
+
throw error;
|
|
507
|
+
throw new V1ProtocolError("invalid_request", error instanceof Error
|
|
508
|
+
? error.message
|
|
509
|
+
: "Invalid SillyTavern preset");
|
|
510
|
+
}
|
|
511
|
+
}
|
|
426
512
|
case "play.workbench.read": {
|
|
427
513
|
const binding = request.presetId === undefined
|
|
428
514
|
? await this.#playPresets.bindCurrent()
|
|
@@ -638,6 +724,22 @@ export class V1Runtime {
|
|
|
638
724
|
return this.#worldRevisionCall(() => this.#worldRevisions.deleteSession(request.worldId, request.sessionId));
|
|
639
725
|
case "world.revision.message":
|
|
640
726
|
return this.#worldRevisionCall(() => this.#worldRevisions.send(request));
|
|
727
|
+
case "world.revision.message.edit":
|
|
728
|
+
try {
|
|
729
|
+
return await this.#worldRevisionCall(() => this.#worldRevisions.editMessage(request));
|
|
730
|
+
}
|
|
731
|
+
catch (error) {
|
|
732
|
+
if (error instanceof WorldRevisionMessageEditError)
|
|
733
|
+
throw new V1ProtocolError("invalid_request", error.message, {
|
|
734
|
+
cause: error,
|
|
735
|
+
details: {
|
|
736
|
+
kind: "world_revision_message_edit",
|
|
737
|
+
reason: error.reason,
|
|
738
|
+
paths: error.paths,
|
|
739
|
+
},
|
|
740
|
+
});
|
|
741
|
+
throw error;
|
|
742
|
+
}
|
|
641
743
|
case "world.revision.cancel":
|
|
642
744
|
return this.#worldRevisionCall(() => this.#worldRevisions.cancel(request.sessionId));
|
|
643
745
|
case "world.revision.resume":
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { validAuthoringNativeContinuation } from "../model/FileNativeModelAdapters.js";
|
|
2
|
+
/** Checks idempotency before a target claims its own publication lease. */
|
|
3
|
+
export function completedAuthoringEdit(session, edit) {
|
|
4
|
+
const prior = session.editReceipts?.find(({ requestId }) => requestId === edit.requestId);
|
|
5
|
+
if (prior !== undefined &&
|
|
6
|
+
(prior.messageId !== edit.messageId ||
|
|
7
|
+
prior.message !== edit.message ||
|
|
8
|
+
prior.filePolicy !== edit.filePolicy))
|
|
9
|
+
throw new Error("This edit request ID was already used for different content");
|
|
10
|
+
if (!session.completedRequestIds.includes(edit.requestId))
|
|
11
|
+
return false;
|
|
12
|
+
if (prior === undefined)
|
|
13
|
+
throw new Error("This request ID was already used for another authoring operation");
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
/** Locates and validates the native prefix and prepares the replacement transcript. */
|
|
17
|
+
export function planAuthoringEdit(session, edit) {
|
|
18
|
+
const messageIndex = session.messages.findIndex((entry) => entry.role === "user" && entry.id === edit.messageId);
|
|
19
|
+
if (messageIndex < 0)
|
|
20
|
+
throw new Error("The selected authoring message no longer exists");
|
|
21
|
+
const userIndex = session.messages
|
|
22
|
+
.slice(0, messageIndex)
|
|
23
|
+
.filter((entry) => entry.role === "user").length;
|
|
24
|
+
const itemIndex = session.modelItems
|
|
25
|
+
.map((item, index) => (item.kind === "user" ? index : -1))
|
|
26
|
+
.filter((index) => index >= 0)[userIndex];
|
|
27
|
+
if (itemIndex === undefined ||
|
|
28
|
+
session.modelItems[itemIndex]?.kind !== "user" ||
|
|
29
|
+
session.modelItems[itemIndex].text !== session.messages[messageIndex].text)
|
|
30
|
+
throw new Error("The selected authoring message cannot be matched to the native transcript");
|
|
31
|
+
const prefix = session.modelItems.slice(0, itemIndex);
|
|
32
|
+
if (!validAuthoringNativeContinuation(prefix, session.modelBinding.provider))
|
|
33
|
+
throw new Error("The native continuation before this message is damaged; start a fresh context");
|
|
34
|
+
const next = structuredClone(session);
|
|
35
|
+
next.editReceipts = [...(next.editReceipts ?? []), edit];
|
|
36
|
+
delete next.retryDispatch;
|
|
37
|
+
next.modelItems = prefix;
|
|
38
|
+
next.messages = next.messages.slice(0, messageIndex);
|
|
39
|
+
next.requests = (next.requests ?? []).filter((entry) => entry.modelItemStart < itemIndex);
|
|
40
|
+
return {
|
|
41
|
+
itemIndex,
|
|
42
|
+
tail: session.modelItems.slice(itemIndex),
|
|
43
|
+
next: { ...next, requests: next.requests },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Returns a candidate tree and conflicts; the target validates and publishes it. */
|
|
47
|
+
export function revertAuthoringChanges(files, items) {
|
|
48
|
+
const result = new Map(files.map((file) => [file.path, structuredClone(file)]));
|
|
49
|
+
const conflicts = new Set();
|
|
50
|
+
const changes = items.flatMap((item) => item.kind === "tool" && !item.isError ? item.changes : []);
|
|
51
|
+
for (const change of changes.reverse()) {
|
|
52
|
+
if (conflicts.has(change.path))
|
|
53
|
+
continue;
|
|
54
|
+
const malformed = (change.kind === "create" &&
|
|
55
|
+
(change.before !== null || change.after === null)) ||
|
|
56
|
+
(change.kind === "delete" &&
|
|
57
|
+
(change.before === null || change.after !== null)) ||
|
|
58
|
+
(change.kind === "modify" &&
|
|
59
|
+
(change.before === null ||
|
|
60
|
+
change.after === null ||
|
|
61
|
+
change.before === change.after));
|
|
62
|
+
const current = result.get(change.path);
|
|
63
|
+
if (malformed ||
|
|
64
|
+
(change.after === null
|
|
65
|
+
? current !== undefined
|
|
66
|
+
: current?.contents !== change.after || current.encoding !== undefined)) {
|
|
67
|
+
conflicts.add(change.path);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (change.before === null)
|
|
71
|
+
result.delete(change.path);
|
|
72
|
+
else
|
|
73
|
+
result.set(change.path, { path: change.path, contents: change.before });
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
files: [...result.values()].sort((a, b) => a.path.localeCompare(b.path)),
|
|
77
|
+
conflicts: [...conflicts].sort(),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isPlayPresetBinding, } from "../play/FileNativePlayPresetStore.js";
|
|
2
|
+
import { isSafeGoogleWireRequest } from "../model/GoogleRequestPreview.js";
|
|
2
3
|
export function isAuthoringPreloadRecord(value) {
|
|
3
4
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
4
5
|
return false;
|
|
@@ -25,9 +26,17 @@ export function validAuthoringRequests(value, validPrompt, session) {
|
|
|
25
26
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
26
27
|
return false;
|
|
27
28
|
const item = raw;
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
if (!Object.keys(item).every((key) => [
|
|
30
|
+
"requestId",
|
|
31
|
+
"modelItemStart",
|
|
32
|
+
"bootstrap",
|
|
33
|
+
"playPreset",
|
|
34
|
+
"preload",
|
|
35
|
+
"wireRequest",
|
|
36
|
+
].includes(key)) ||
|
|
30
37
|
(item.preload !== undefined && !isAuthoringPreloadRecord(item.preload)) ||
|
|
38
|
+
(item.wireRequest !== undefined &&
|
|
39
|
+
!isSafeGoogleWireRequest(item.wireRequest)) ||
|
|
31
40
|
typeof item.requestId !== "string" ||
|
|
32
41
|
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u.test(item.requestId) ||
|
|
33
42
|
ids.has(item.requestId) ||
|
|
@@ -67,9 +76,12 @@ export function authoringRequestPreviews(session) {
|
|
|
67
76
|
},
|
|
68
77
|
]
|
|
69
78
|
: []),
|
|
70
|
-
...requests.map(({ requestId, bootstrap }) => ({
|
|
79
|
+
...requests.map(({ requestId, bootstrap, wireRequest }) => ({
|
|
71
80
|
requestId,
|
|
72
81
|
compilation: structuredClone(bootstrap),
|
|
82
|
+
...(wireRequest === undefined
|
|
83
|
+
? {}
|
|
84
|
+
: { wireRequest: structuredClone(wireRequest) }),
|
|
73
85
|
legacyBootstrap: false,
|
|
74
86
|
})),
|
|
75
87
|
];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { validAuthoringNativeContinuation } from "../model/FileNativeModelAdapters.js";
|
|
2
|
+
import { dispatchRequiresFreshContext } from "../model/DispatchFailure.js";
|
|
3
|
+
export function prepareAuthoringDispatch(session, request) {
|
|
4
|
+
const retry = session.retryDispatch;
|
|
5
|
+
if (retry?.state === "retryable") {
|
|
6
|
+
session.retryDispatch = { ...retry, state: "dispatching" };
|
|
7
|
+
return { ...request, requestId: retry.requestId, exchange: retry.exchange };
|
|
8
|
+
}
|
|
9
|
+
session.retryDispatch = {
|
|
10
|
+
requestId: request.requestId,
|
|
11
|
+
exchange: request.exchange,
|
|
12
|
+
modelItemCount: session.modelItems.length,
|
|
13
|
+
state: "dispatching",
|
|
14
|
+
};
|
|
15
|
+
return request;
|
|
16
|
+
}
|
|
17
|
+
export function checkpointNextAuthoringDispatch(session, state) {
|
|
18
|
+
const request = session.requests?.at(-1);
|
|
19
|
+
if (session.modelBinding.provider !== "google_interactions" &&
|
|
20
|
+
request !== undefined)
|
|
21
|
+
session.retryDispatch = {
|
|
22
|
+
requestId: request.requestId,
|
|
23
|
+
exchange: session.exchange + 1,
|
|
24
|
+
modelItemCount: session.modelItems.length,
|
|
25
|
+
state,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function failAuthoringDispatch(session, error, phase) {
|
|
29
|
+
const checkpoint = session.retryDispatch;
|
|
30
|
+
if (checkpoint === undefined)
|
|
31
|
+
return;
|
|
32
|
+
// Acceptance clears the previous checkpoint. During settlement only a
|
|
33
|
+
// completed tool receipt can create the next one, before its final save.
|
|
34
|
+
const settledNextRequest = phase === "settling" &&
|
|
35
|
+
session.pendingSettlement === null &&
|
|
36
|
+
checkpoint.state === "dispatching" &&
|
|
37
|
+
checkpoint.modelItemCount === session.modelItems.length;
|
|
38
|
+
checkpoint.state =
|
|
39
|
+
session.modelBinding.provider !== "google_interactions" &&
|
|
40
|
+
(phase === "preparing" || phase === "dispatched" || settledNextRequest) &&
|
|
41
|
+
!dispatchRequiresFreshContext(error, false)
|
|
42
|
+
? "retryable"
|
|
43
|
+
: "blocked";
|
|
44
|
+
}
|
|
45
|
+
export function recoverAuthoringDispatch(session) {
|
|
46
|
+
if (session.retryDispatch?.state === "dispatching")
|
|
47
|
+
session.retryDispatch.state =
|
|
48
|
+
session.modelBinding.provider === "google_interactions"
|
|
49
|
+
? "blocked"
|
|
50
|
+
: "retryable";
|
|
51
|
+
}
|
|
52
|
+
export function canRetryAuthoringDispatch(session) {
|
|
53
|
+
return (session.runStatus === "interrupted" &&
|
|
54
|
+
session.modelBinding.provider !== "google_interactions" &&
|
|
55
|
+
session.googleOutcomeUnknown !== true &&
|
|
56
|
+
session.pendingSettlement === null &&
|
|
57
|
+
session.retryDispatch?.state === "retryable" &&
|
|
58
|
+
validAuthoringNativeContinuation(session.modelItems, session.modelBinding.provider));
|
|
59
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { validGoogleInteractionsContinuation } from "../model/ProviderExchangeState.js";
|
|
2
|
+
export function hasDamagedGoogleSteps(value) {
|
|
3
|
+
return (isRecord(value) &&
|
|
4
|
+
isRecord(value.modelBinding) &&
|
|
5
|
+
value.modelBinding.provider === "google_interactions" &&
|
|
6
|
+
Array.isArray(value.modelItems) &&
|
|
7
|
+
value.modelItems.some((item) => isRecord(item) &&
|
|
8
|
+
item.kind === "assistant" &&
|
|
9
|
+
damagedGoogleState(item.providerState)));
|
|
10
|
+
}
|
|
11
|
+
/** A disposable copy for discovery only. Callers must still validate the complete schema. */
|
|
12
|
+
export function withoutDamagedGoogleSteps(value) {
|
|
13
|
+
if (!isRecord(value) ||
|
|
14
|
+
!isRecord(value.modelBinding) ||
|
|
15
|
+
value.modelBinding.provider !== "google_interactions" ||
|
|
16
|
+
value.pendingSettlement !== null ||
|
|
17
|
+
value.googleOutcomeUnknown === true ||
|
|
18
|
+
value.activeRequestId !== null ||
|
|
19
|
+
(value.runStatus !== "ready" && value.runStatus !== "interrupted") ||
|
|
20
|
+
!Array.isArray(value.modelItems))
|
|
21
|
+
return null;
|
|
22
|
+
const copy = structuredClone(value);
|
|
23
|
+
if (!Array.isArray(copy.modelItems))
|
|
24
|
+
return null;
|
|
25
|
+
const items = copy.modelItems;
|
|
26
|
+
for (const [index, item] of items.entries()) {
|
|
27
|
+
if (isRecord(item) &&
|
|
28
|
+
item.kind === "assistant" &&
|
|
29
|
+
isRecord(item.providerState) &&
|
|
30
|
+
item.providerState.protocol !== "google_interactions")
|
|
31
|
+
return null;
|
|
32
|
+
if (!isRecord(item) ||
|
|
33
|
+
item.kind !== "assistant" ||
|
|
34
|
+
!Array.isArray(item.toolCalls))
|
|
35
|
+
continue;
|
|
36
|
+
for (const [offset, call] of item.toolCalls.entries()) {
|
|
37
|
+
const result = items[index + offset + 1];
|
|
38
|
+
if (!isRecord(call) ||
|
|
39
|
+
typeof call.id !== "string" ||
|
|
40
|
+
!isRecord(result) ||
|
|
41
|
+
result.kind !== "tool" ||
|
|
42
|
+
result.toolCallId !== call.id)
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
let damaged = false;
|
|
47
|
+
for (const item of items) {
|
|
48
|
+
if (!isRecord(item) ||
|
|
49
|
+
item.kind !== "assistant" ||
|
|
50
|
+
!damagedGoogleState(item.providerState))
|
|
51
|
+
continue;
|
|
52
|
+
if (isRecord(item.providerState))
|
|
53
|
+
item.providerState.steps = [];
|
|
54
|
+
else
|
|
55
|
+
item.providerState = { protocol: "google_interactions", steps: [] };
|
|
56
|
+
damaged = true;
|
|
57
|
+
}
|
|
58
|
+
return damaged ? copy : null;
|
|
59
|
+
}
|
|
60
|
+
function damagedGoogleState(value) {
|
|
61
|
+
return (value == null ||
|
|
62
|
+
(isRecord(value) &&
|
|
63
|
+
value.protocol === "google_interactions" &&
|
|
64
|
+
!validGoogleInteractionsContinuation(value.steps)));
|
|
65
|
+
}
|
|
66
|
+
function isRecord(value) {
|
|
67
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
68
|
+
}
|