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
|
@@ -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
|
@@ -89,6 +89,28 @@ const requiredFields = {
|
|
|
89
89
|
"play.select": { presetId: "string" },
|
|
90
90
|
"play.export": { presetId: "string" },
|
|
91
91
|
"play.import": { name: "string", files: "array" },
|
|
92
|
+
"content.sillytavern.inspect": { source: "string" },
|
|
93
|
+
"content.sillytavern.preview": {
|
|
94
|
+
source: "string",
|
|
95
|
+
name: "string",
|
|
96
|
+
selection: "object",
|
|
97
|
+
},
|
|
98
|
+
"content.sillytavern.import": {
|
|
99
|
+
source: "string",
|
|
100
|
+
name: "string",
|
|
101
|
+
selection: "object",
|
|
102
|
+
},
|
|
103
|
+
"play.sillytavern.inspect": { source: "string" },
|
|
104
|
+
"play.sillytavern.preview": {
|
|
105
|
+
source: "string",
|
|
106
|
+
selected: "array",
|
|
107
|
+
name: "string",
|
|
108
|
+
},
|
|
109
|
+
"play.sillytavern.import": {
|
|
110
|
+
source: "string",
|
|
111
|
+
selected: "array",
|
|
112
|
+
name: "string",
|
|
113
|
+
},
|
|
92
114
|
"play.workbench.read": {},
|
|
93
115
|
"prompt.preview": {
|
|
94
116
|
packageId: "string",
|
|
@@ -326,6 +348,53 @@ function validateRequestFields(request) {
|
|
|
326
348
|
if (request.revision !== undefined && request.presetId === undefined)
|
|
327
349
|
throw new V1ProtocolError("invalid_request", "play.workbench.read.revision must be provided with presetId");
|
|
328
350
|
}
|
|
351
|
+
if (typeof request.type === "string" &&
|
|
352
|
+
request.type.startsWith("content.sillytavern.")) {
|
|
353
|
+
const inspect = request.type === "content.sillytavern.inspect";
|
|
354
|
+
const allowed = inspect
|
|
355
|
+
? ["type", "source"]
|
|
356
|
+
: ["type", "source", "name", "selection"];
|
|
357
|
+
if (Object.keys(request).some((key) => !allowed.includes(key)))
|
|
358
|
+
throw new V1ProtocolError("invalid_request", "Invalid SillyTavern content fields");
|
|
359
|
+
if (!inspect) {
|
|
360
|
+
const selection = request.selection;
|
|
361
|
+
if (!isRecord(selection) ||
|
|
362
|
+
Object.keys(selection).some((key) => ![
|
|
363
|
+
"character",
|
|
364
|
+
"scenario",
|
|
365
|
+
"opening",
|
|
366
|
+
"example",
|
|
367
|
+
"systemPrompt",
|
|
368
|
+
"attachments",
|
|
369
|
+
"entries",
|
|
370
|
+
].includes(key)) ||
|
|
371
|
+
(!Array.isArray(selection.attachments) &&
|
|
372
|
+
selection.attachments !== undefined) ||
|
|
373
|
+
(Array.isArray(selection.attachments) &&
|
|
374
|
+
!selection.attachments.every((value) => typeof value === "string")) ||
|
|
375
|
+
!isRecord(selection.entries) ||
|
|
376
|
+
Object.values(selection.entries).some((value) => value !== "world" && value !== "attachment") ||
|
|
377
|
+
["character", "scenario", "example", "systemPrompt"].some((key) => selection[key] !== undefined && typeof selection[key] !== "boolean") ||
|
|
378
|
+
(selection.opening !== undefined &&
|
|
379
|
+
typeof selection.opening !== "string"))
|
|
380
|
+
throw new V1ProtocolError("invalid_request", "Invalid SillyTavern content selection");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (typeof request.type === "string" &&
|
|
384
|
+
request.type.startsWith("play.sillytavern.")) {
|
|
385
|
+
const inspect = request.type === "play.sillytavern.inspect";
|
|
386
|
+
const allowed = inspect
|
|
387
|
+
? ["type", "source", "groupId"]
|
|
388
|
+
: ["type", "source", "groupId", "selected", "name"];
|
|
389
|
+
if (Object.keys(request).some((key) => !allowed.includes(key)) ||
|
|
390
|
+
(request.groupId !== undefined &&
|
|
391
|
+
(typeof request.groupId !== "string" ||
|
|
392
|
+
request.groupId.length === 0)) ||
|
|
393
|
+
(!inspect &&
|
|
394
|
+
(!Array.isArray(request.selected) ||
|
|
395
|
+
!request.selected.every((value) => typeof value === "string" && value.length > 0))))
|
|
396
|
+
throw new V1ProtocolError("invalid_request", `${request.type} has invalid fields`);
|
|
397
|
+
}
|
|
329
398
|
if (request.type === "play.import") {
|
|
330
399
|
if (!request.files.every((file) => isRecord(file) &&
|
|
331
400
|
typeof file.path === "string" &&
|
|
@@ -454,6 +523,12 @@ const requestTypes = new Set([
|
|
|
454
523
|
"play.select",
|
|
455
524
|
"play.export",
|
|
456
525
|
"play.import",
|
|
526
|
+
"content.sillytavern.inspect",
|
|
527
|
+
"content.sillytavern.preview",
|
|
528
|
+
"content.sillytavern.import",
|
|
529
|
+
"play.sillytavern.inspect",
|
|
530
|
+
"play.sillytavern.preview",
|
|
531
|
+
"play.sillytavern.import",
|
|
457
532
|
"play.workbench.read",
|
|
458
533
|
"prompt.preview",
|
|
459
534
|
"world.create",
|
|
@@ -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,7 +18,7 @@ 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";
|
|
@@ -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 {
|
|
@@ -423,6 +452,47 @@ export class V1Runtime {
|
|
|
423
452
|
};
|
|
424
453
|
case "play.import":
|
|
425
454
|
return this.#playPresets.importPortable(request);
|
|
455
|
+
case "play.sillytavern.inspect":
|
|
456
|
+
case "play.sillytavern.preview":
|
|
457
|
+
case "play.sillytavern.import": {
|
|
458
|
+
// Do not trust the browser to enforce the import limit or supply
|
|
459
|
+
// already-converted files. Preview and import both parse from source.
|
|
460
|
+
if (Buffer.byteLength(request.source, "utf8") > 2 * 1024 * 1024)
|
|
461
|
+
throw new V1ProtocolError("invalid_request", "SillyTavern source exceeds the size limit");
|
|
462
|
+
if (request.type !== "play.sillytavern.inspect" &&
|
|
463
|
+
(request.selected.length > 4096 ||
|
|
464
|
+
request.selected.reduce((bytes, id) => bytes + Buffer.byteLength(id, "utf8"), 0) >
|
|
465
|
+
2 * 1024 * 1024))
|
|
466
|
+
throw new V1ProtocolError("invalid_request", "SillyTavern selection exceeds the size limit");
|
|
467
|
+
try {
|
|
468
|
+
if (request.type === "play.sillytavern.inspect")
|
|
469
|
+
return inspectSillyTavernPreset(request.source, request.groupId);
|
|
470
|
+
const converted = convertSillyTavernPreset(request.source, {
|
|
471
|
+
groupId: request.groupId,
|
|
472
|
+
selected: request.selected,
|
|
473
|
+
name: request.name,
|
|
474
|
+
locale: this.#locale,
|
|
475
|
+
});
|
|
476
|
+
if (request.type === "play.sillytavern.preview") {
|
|
477
|
+
const parsed = parsePlayPresetFiles(converted.files);
|
|
478
|
+
if (parsed.kind === "invalid")
|
|
479
|
+
throw parsed.error;
|
|
480
|
+
toPlayPresetStructuredEditor(parsed.definition);
|
|
481
|
+
return converted;
|
|
482
|
+
}
|
|
483
|
+
return await this.#playPresets.importPortable({
|
|
484
|
+
name: request.name,
|
|
485
|
+
files: converted.files,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
if (error instanceof V1ProtocolError)
|
|
490
|
+
throw error;
|
|
491
|
+
throw new V1ProtocolError("invalid_request", error instanceof Error
|
|
492
|
+
? error.message
|
|
493
|
+
: "Invalid SillyTavern preset");
|
|
494
|
+
}
|
|
495
|
+
}
|
|
426
496
|
case "play.workbench.read": {
|
|
427
497
|
const binding = request.presetId === undefined
|
|
428
498
|
? await this.#playPresets.bindCurrent()
|
|
@@ -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,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
|
+
}
|
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { aggregateModelUsage, } from "../../protocol/modelUsage.js";
|
|
3
3
|
import { ModelHostCancelledError } from "../model/ModelHost.js";
|
|
4
|
+
import { withGoogleHop } from "../model/GoogleRequestPreview.js";
|
|
4
5
|
const maximumExchangesPerMessage = 64;
|
|
5
6
|
const maximumStreamTailCharacters = 240;
|
|
7
|
+
export function authoringExchange(session, requestId, boundaries) {
|
|
8
|
+
const bootstrap = session.requests?.at(-1)?.bootstrap ?? session.bootstrap;
|
|
9
|
+
return {
|
|
10
|
+
bootstrap,
|
|
11
|
+
toolUniverse: bootstrap.toolUniverse,
|
|
12
|
+
allowedTools: bootstrap.toolUniverse.map(({ name }) => name),
|
|
13
|
+
toolStrategy: bootstrap.toolStrategy,
|
|
14
|
+
tools: bootstrap.tools,
|
|
15
|
+
appended: session.modelItems,
|
|
16
|
+
...(boundaries === undefined
|
|
17
|
+
? {}
|
|
18
|
+
: { worldRevisionBoundaries: boundaries }),
|
|
19
|
+
requestId,
|
|
20
|
+
operationId: session.sessionId,
|
|
21
|
+
requestAttempt: 1,
|
|
22
|
+
exchange: session.exchange + 1,
|
|
23
|
+
maxOutputTokens: session.modelBinding.maxOutputTokens,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
6
26
|
/**
|
|
7
27
|
* Deep Module for the shared authoring-conversation loop. Its Interface leaves
|
|
8
28
|
* content-package and world-revision publication behind the settlement seam.
|
|
@@ -56,28 +76,25 @@ export class ModelToolConversation {
|
|
|
56
76
|
}
|
|
57
77
|
async #execute(input, live) {
|
|
58
78
|
const { session } = input;
|
|
59
|
-
const bootstrap = session.requests?.at(-1)?.bootstrap ?? session.bootstrap;
|
|
60
79
|
try {
|
|
61
80
|
for (let round = 0; round < maximumExchangesPerMessage; round += 1) {
|
|
81
|
+
const request = authoringExchange(session, live.requestId, input.worldRevisionBoundaries);
|
|
62
82
|
session.exchange += 1;
|
|
83
|
+
if (session.modelBinding.provider === "google_interactions")
|
|
84
|
+
session.googleOutcomeUnknown = true;
|
|
63
85
|
live.streaming = emptyStreaming();
|
|
64
86
|
session.updatedAt = Date.now();
|
|
65
87
|
await input.save(session);
|
|
66
88
|
input.changed?.(true);
|
|
67
|
-
const response = await input.host.exchange({
|
|
68
|
-
bootstrap,
|
|
69
|
-
toolUniverse: bootstrap.toolUniverse,
|
|
70
|
-
allowedTools: bootstrap.toolUniverse.map(({ name }) => name),
|
|
71
|
-
toolStrategy: bootstrap.toolStrategy,
|
|
72
|
-
tools: bootstrap.tools,
|
|
73
|
-
appended: session.modelItems,
|
|
74
|
-
requestId: live.requestId,
|
|
75
|
-
operationId: session.sessionId,
|
|
76
|
-
requestAttempt: 1,
|
|
77
|
-
exchange: session.exchange,
|
|
78
|
-
maxOutputTokens: session.modelBinding.maxOutputTokens,
|
|
79
|
-
}, {
|
|
89
|
+
const response = await input.host.exchange(request, {
|
|
80
90
|
signal: live.controller.signal,
|
|
91
|
+
onGoogleHop: (hop) => {
|
|
92
|
+
const snapshot = session.requests?.findLast((entry) => entry.requestId === live.requestId);
|
|
93
|
+
if (snapshot?.wireRequest === undefined)
|
|
94
|
+
return;
|
|
95
|
+
snapshot.wireRequest = withGoogleHop(snapshot.wireRequest, hop);
|
|
96
|
+
return input.recordGoogleHop?.(live.requestId, hop);
|
|
97
|
+
},
|
|
81
98
|
onDelta: (delta) => {
|
|
82
99
|
recordDelta(live, delta);
|
|
83
100
|
input.changed?.(false);
|
|
@@ -100,6 +117,8 @@ export class ModelToolConversation {
|
|
|
100
117
|
providerState: response.providerState,
|
|
101
118
|
toolCalls: structuredClone(toolCalls),
|
|
102
119
|
});
|
|
120
|
+
if (session.modelBinding.provider === "google_interactions")
|
|
121
|
+
session.googleOutcomeUnknown = false;
|
|
103
122
|
session.updatedAt = Date.now();
|
|
104
123
|
if (toolCalls.length === 0) {
|
|
105
124
|
session.messages.push({
|
|
@@ -134,13 +153,15 @@ export class ModelToolConversation {
|
|
|
134
153
|
session.activeRequestId = null;
|
|
135
154
|
session.completedRequestIds = rememberRequest(session.completedRequestIds, live.requestId);
|
|
136
155
|
session.lastFailure =
|
|
137
|
-
|
|
138
|
-
?
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
:
|
|
143
|
-
|
|
156
|
+
session.googleOutcomeUnknown === true
|
|
157
|
+
? "The Google request outcome is unknown; start a fresh model context before sending again."
|
|
158
|
+
: error instanceof ModelHostCancelledError
|
|
159
|
+
? (input.failure?.cancelled ??
|
|
160
|
+
"The authoring response was cancelled. Complete tool responses already settled were retained.")
|
|
161
|
+
: error instanceof Error
|
|
162
|
+
? error.message
|
|
163
|
+
: (input.failure?.interrupted ??
|
|
164
|
+
"The authoring response was interrupted");
|
|
144
165
|
session.updatedAt = Date.now();
|
|
145
166
|
await input.save(session);
|
|
146
167
|
return input.view(session);
|
|
@@ -170,7 +191,11 @@ export function appendAuthoringUserMessage(session, message, requestId) {
|
|
|
170
191
|
export function resumePausedAuthoringConversation(session) {
|
|
171
192
|
if (session.runStatus !== "paused")
|
|
172
193
|
throw new Error("Only a paused authoring conversation can be continued");
|
|
173
|
-
|
|
194
|
+
const last = session.modelItems.at(-1);
|
|
195
|
+
if (last?.kind !== "tool" &&
|
|
196
|
+
!(session.modelBinding.provider === "google_interactions" &&
|
|
197
|
+
session.googleOutcomeUnknown === false &&
|
|
198
|
+
last?.kind === "user"))
|
|
174
199
|
throw new Error("The paused authoring conversation does not end with settled tool results");
|
|
175
200
|
session.runStatus = "running";
|
|
176
201
|
session.activeRequestId = session.requests?.at(-1)?.requestId ?? null;
|
|
@@ -96,6 +96,9 @@ export class ContentWorkspace {
|
|
|
96
96
|
}, options.signal === undefined ? {} : { signal: options.signal });
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
|
+
importSillyTavernContent(title, files) {
|
|
100
|
+
return this.#mutate(() => this.#currentTreeLibrary.importPackage({ title, files }));
|
|
101
|
+
}
|
|
99
102
|
copyCurrentTreeContentPackage(localId, options = {}) {
|
|
100
103
|
return this.#mutate(() => this.#currentTreeLibrary.copyPackage(localId, options.signal === undefined ? {} : { signal: options.signal }));
|
|
101
104
|
}
|