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
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dispatchRequiresFreshContext } from "../model/DispatchFailure.js";
|
|
2
|
+
import { ModelHostCancelledError, ModelHostFailureError, ModelHostOutcomeUnknownError, } from "../model/ModelHost.js";
|
|
2
3
|
import { classifyPlayFailure } from "./PlayFailure.js";
|
|
3
4
|
const messages = {
|
|
4
5
|
playerCommitted: "The player message is committed; the frozen model request is ready to continue.",
|
|
@@ -10,6 +11,7 @@ const messages = {
|
|
|
10
11
|
serviceStoppedAfterHead: "The service stopped after the world was updated; use a fresh context.",
|
|
11
12
|
serviceStoppedSavingPlayer: "The service stopped while saving the player message; use a fresh context.",
|
|
12
13
|
serviceStoppedRetryable: "The service stopped while the model request was in progress; retry to call the model again.",
|
|
14
|
+
serviceStoppedUnknown: "The service stopped after the model request may have been dispatched; its outcome is unknown. Use a fresh context.",
|
|
13
15
|
serviceStoppedNoRequest: "The service stopped before preparing a model request; use a fresh context.",
|
|
14
16
|
dispatchInterrupted: "The model request was interrupted.",
|
|
15
17
|
};
|
|
@@ -43,29 +45,52 @@ export class PlayAdvanceLifecycle {
|
|
|
43
45
|
notify?.();
|
|
44
46
|
}
|
|
45
47
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
48
|
+
* Recovers an advance cut off by a process exit. A persisted ending wins;
|
|
49
|
+
* otherwise Google requests that may have been dispatched cannot be replayed
|
|
50
|
+
* (ADR-0035), while other recovery follows ADR-0044.
|
|
49
51
|
*/
|
|
50
52
|
async recoverAbandoned(session, advance, headMoved) {
|
|
53
|
+
// Endings are persisted before their marker is cleared. If the process
|
|
54
|
+
// stopped in that gap, keep the durable ending rather than inventing a
|
|
55
|
+
// new retry decision for an advance that already settled.
|
|
56
|
+
if (advance !== null &&
|
|
57
|
+
session.status !== "running" &&
|
|
58
|
+
!(advance.advanceKind === "player" && session.status === "ready") &&
|
|
59
|
+
!headMoved) {
|
|
60
|
+
await this.#ports.clearMarker(advance);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
51
63
|
const playerPhase = advance?.advanceKind === "player";
|
|
52
64
|
const streaming = playerPhase
|
|
53
65
|
? undefined
|
|
54
66
|
: session.events.findLast((event) => event.kind === "assistant" &&
|
|
55
67
|
event.status === "streaming" &&
|
|
56
68
|
(advance === null || event.id === advance.eventId));
|
|
57
|
-
const
|
|
69
|
+
const unknownGoogleDispatch = advance?.advanceKind === "response" &&
|
|
70
|
+
session.status === "running" &&
|
|
71
|
+
(session.lastRequest?.modelConfig?.provider ??
|
|
72
|
+
session.modelBinding?.provider) === "google_interactions";
|
|
73
|
+
const canRetry = !headMoved &&
|
|
74
|
+
!playerPhase &&
|
|
75
|
+
!unknownGoogleDispatch &&
|
|
76
|
+
session.lastRequest !== null;
|
|
58
77
|
const message = headMoved
|
|
59
78
|
? messages.serviceStoppedAfterHead
|
|
60
79
|
: playerPhase
|
|
61
80
|
? messages.serviceStoppedSavingPlayer
|
|
62
|
-
:
|
|
63
|
-
? messages.
|
|
64
|
-
:
|
|
81
|
+
: unknownGoogleDispatch
|
|
82
|
+
? messages.serviceStoppedUnknown
|
|
83
|
+
: canRetry
|
|
84
|
+
? messages.serviceStoppedRetryable
|
|
85
|
+
: messages.serviceStoppedNoRequest;
|
|
65
86
|
await this.end(session, {
|
|
66
87
|
kind: "failed",
|
|
67
88
|
message,
|
|
68
|
-
failureKind:
|
|
89
|
+
failureKind: unknownGoogleDispatch
|
|
90
|
+
? "outcome_unknown"
|
|
91
|
+
: canRetry
|
|
92
|
+
? "retryable"
|
|
93
|
+
: "other",
|
|
69
94
|
canRetry,
|
|
70
95
|
streamingEventId: streaming?.id,
|
|
71
96
|
}, advance);
|
|
@@ -73,19 +98,21 @@ export class PlayAdvanceLifecycle {
|
|
|
73
98
|
}
|
|
74
99
|
/**
|
|
75
100
|
* The ending of an exchange that raised an error before its response was
|
|
76
|
-
* settled.
|
|
77
|
-
* a retry, even though `ModelHostCancelledError` is also an outcome-unknown
|
|
78
|
-
* error (ADR-0030).
|
|
101
|
+
* settled. Google dispatched outcomes remain unknown and cannot be replayed.
|
|
79
102
|
*/
|
|
80
|
-
export function dispatchFailureEnding(error, streamingEventId) {
|
|
103
|
+
export function dispatchFailureEnding(error, streamingEventId, googleDispatched = false) {
|
|
81
104
|
const message = errorMessage(error, messages.dispatchInterrupted);
|
|
82
105
|
if (error instanceof ModelHostCancelledError)
|
|
83
|
-
return {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
106
|
+
return {
|
|
107
|
+
kind: "cancelled_after_dispatch",
|
|
108
|
+
message,
|
|
109
|
+
canRetry: !googleDispatched,
|
|
110
|
+
streamingEventId,
|
|
111
|
+
};
|
|
112
|
+
const requiresFreshContext = dispatchRequiresFreshContext(error, googleDispatched);
|
|
113
|
+
const canRetry = !requiresFreshContext &&
|
|
114
|
+
(error instanceof ModelHostFailureError ||
|
|
115
|
+
error instanceof ModelHostOutcomeUnknownError);
|
|
89
116
|
return {
|
|
90
117
|
kind: "failed",
|
|
91
118
|
message,
|
|
@@ -117,12 +144,13 @@ export function noContinuationEnding() {
|
|
|
117
144
|
};
|
|
118
145
|
}
|
|
119
146
|
/** A settled response with neither text nor a complete tool call. */
|
|
120
|
-
export function emptyResponseEnding() {
|
|
147
|
+
export function emptyResponseEnding(canRetry = false, streamingEventId) {
|
|
121
148
|
return {
|
|
122
149
|
kind: "failed",
|
|
123
150
|
message: messages.noOutput,
|
|
124
151
|
failureKind: "other",
|
|
125
|
-
canRetry
|
|
152
|
+
canRetry,
|
|
153
|
+
streamingEventId,
|
|
126
154
|
};
|
|
127
155
|
}
|
|
128
156
|
/**
|
|
@@ -175,7 +203,7 @@ function applyEnding(session, ending) {
|
|
|
175
203
|
case "cancelled_after_dispatch":
|
|
176
204
|
interruptStreamingEvent(session, ending.streamingEventId);
|
|
177
205
|
session.status = "interrupted";
|
|
178
|
-
session.canRetry =
|
|
206
|
+
session.canRetry = ending.canRetry;
|
|
179
207
|
session.lastFailure = ending.message;
|
|
180
208
|
delete session.lastFailureKind;
|
|
181
209
|
session.events.push({
|
|
@@ -203,7 +231,7 @@ function interruptStreamingEvent(session, eventId) {
|
|
|
203
231
|
if (eventId === undefined)
|
|
204
232
|
return;
|
|
205
233
|
const event = session.events.find(({ id }) => id === eventId);
|
|
206
|
-
if (event?.kind === "assistant")
|
|
234
|
+
if (event?.kind === "assistant" && event.status === "streaming")
|
|
207
235
|
event.status = "interrupted";
|
|
208
236
|
}
|
|
209
237
|
function errorMessage(error, fallback) {
|
|
@@ -16,7 +16,8 @@ import { turnGuideWithTurnNote } from "../../shared/turn-guide.js";
|
|
|
16
16
|
import { completedPlayerRounds, continuationNotice, playerInputAppend, toolStepNotice, turnGuideNotice, } from "./PlayContinuity.js";
|
|
17
17
|
import { renderWriteSizeAdvice, readDeclaredWorldClock, } from "../prompt/WorldMaintenanceReport.js";
|
|
18
18
|
import { WorldDocumentStore } from "../world/WorldDocumentStore.js";
|
|
19
|
-
import { equalModelHostBinding, ModelHostCancelledError, } from "../model/ModelHost.js";
|
|
19
|
+
import { equalModelHostBinding, ModelHostCancelledError, ModelHostOutcomeUnknownError, } from "../model/ModelHost.js";
|
|
20
|
+
import { validGoogleInteractionsContinuation } from "../model/ProviderExchangeState.js";
|
|
20
21
|
import { FileNativePromptCompiler, } from "../prompt/FileNativePromptCompiler.js";
|
|
21
22
|
import { parsePlayPresetFiles, } from "./FileNativePlayPresetStore.js";
|
|
22
23
|
import { runPlayFollowupRequests, } from "./PlayFollowupRequests.js";
|
|
@@ -234,6 +235,12 @@ export class PlayCallChain {
|
|
|
234
235
|
const session = await this.#requireChain(input.worldId, input.chainId);
|
|
235
236
|
await this.#assertCurrentHead(session);
|
|
236
237
|
assertSessionModelHostBinding(session, input.modelHost);
|
|
238
|
+
if (session.status === "ready" &&
|
|
239
|
+
session.modelBinding?.provider === "google_interactions" &&
|
|
240
|
+
session.transcript.some((item) => item.kind === "assistant" &&
|
|
241
|
+
(item.providerState?.protocol !== "google_interactions" ||
|
|
242
|
+
!validGoogleInteractionsContinuation(item.providerState.steps))))
|
|
243
|
+
throw new PlayCallChainError("The Google Interactions continuation is missing or corrupted; start a fresh context.");
|
|
237
244
|
if (input.playerText.trim().length === 0) {
|
|
238
245
|
if (session.status === "running")
|
|
239
246
|
throw new PlayCallChainError("A model request is still running.");
|
|
@@ -869,6 +876,10 @@ export class PlayCallChain {
|
|
|
869
876
|
updatedAt: Date.now(),
|
|
870
877
|
};
|
|
871
878
|
}
|
|
879
|
+
if (intent.continuation === "continue_context")
|
|
880
|
+
await this.#worlds.playTimeline
|
|
881
|
+
.copyRetainedEncodings(worldId, sourceContext.chainId, revised)
|
|
882
|
+
.catch(() => forgetFollowupPreviews(revised));
|
|
872
883
|
await this.#worlds.playTimeline.persist(revised);
|
|
873
884
|
if (intent.continuation === "fresh_context")
|
|
874
885
|
await this.#worlds.completeArchiveFreshContext(worldId);
|
|
@@ -965,6 +976,9 @@ export class PlayCallChain {
|
|
|
965
976
|
parentHead: intent.restoresHead,
|
|
966
977
|
updatedAt: Date.now(),
|
|
967
978
|
};
|
|
979
|
+
await this.#worlds.playTimeline
|
|
980
|
+
.copyRetainedEncodings(intent.worldId, sourceContext.chainId, prefix)
|
|
981
|
+
.catch(() => forgetFollowupPreviews(prefix));
|
|
968
982
|
await this.#worlds.playTimeline.persistDetached(structuredClone(prefix));
|
|
969
983
|
previousChainId = prefix.chainId;
|
|
970
984
|
}
|
|
@@ -1254,14 +1268,27 @@ export class PlayCallChain {
|
|
|
1254
1268
|
if (existing?.kind !== "assistant")
|
|
1255
1269
|
throw new PlayCallChainError("The durable Provider result no longer matches its assistant event.");
|
|
1256
1270
|
const calls = durable.toolCalls ?? [];
|
|
1257
|
-
|
|
1271
|
+
if ((session.lastRequest?.modelConfig?.provider ??
|
|
1272
|
+
session.modelBinding?.provider) === "google_interactions" &&
|
|
1273
|
+
calls.length > 0) {
|
|
1274
|
+
const boundary = session.events.findLastIndex((event) => event.kind === "player" ||
|
|
1275
|
+
(event.kind === "assistant" &&
|
|
1276
|
+
event.status === "completed" &&
|
|
1277
|
+
event.responseKind === "narrative"));
|
|
1278
|
+
if (session.events
|
|
1279
|
+
.slice(boundary + 1)
|
|
1280
|
+
.some((event) => event.kind === "tool_call" &&
|
|
1281
|
+
calls.some((call) => call.id === event.callId)))
|
|
1282
|
+
throw new ModelHostOutcomeUnknownError("Google Interactions repeated a tool-call ID during the same send; its outcome cannot be safely settled. Use a fresh context.");
|
|
1283
|
+
}
|
|
1284
|
+
const text = durable.text ?? "";
|
|
1258
1285
|
const hasText = text.trim().length > 0;
|
|
1259
1286
|
const responseKind = calls.length > 0 ? "tool_step" : hasText ? "narrative" : "empty";
|
|
1260
1287
|
const narrative = responseKind === "narrative";
|
|
1261
1288
|
const assistantEvent = {
|
|
1262
1289
|
id: advance.eventId,
|
|
1263
1290
|
kind: "assistant",
|
|
1264
|
-
text,
|
|
1291
|
+
text: responseKind === "empty" ? existing.text : text,
|
|
1265
1292
|
status: "completed",
|
|
1266
1293
|
responseKind,
|
|
1267
1294
|
exchange: advance.exchange,
|
|
@@ -1279,6 +1306,15 @@ export class PlayCallChain {
|
|
|
1279
1306
|
: { stopReason: durable.stopReason }),
|
|
1280
1307
|
continuation: durable.providerState === undefined ? "unavailable" : "available",
|
|
1281
1308
|
};
|
|
1309
|
+
// No response has been settled or added to the transcript. Non-Google
|
|
1310
|
+
// providers may explicitly replay the frozen request without duplicating work.
|
|
1311
|
+
if (responseKind === "empty" &&
|
|
1312
|
+
(session.lastRequest?.modelConfig?.provider ??
|
|
1313
|
+
session.modelBinding?.provider) !== "google_interactions") {
|
|
1314
|
+
session.events = session.events.map((event) => event.id === advance.eventId ? assistantEvent : event);
|
|
1315
|
+
await this.#advances.end(session, emptyResponseEnding(true, advance.eventId), advance);
|
|
1316
|
+
return { kind: "terminal", status: "interrupted", narrative: false };
|
|
1317
|
+
}
|
|
1282
1318
|
const workingTools = new Map(session.completedToolMap);
|
|
1283
1319
|
const prepared = [];
|
|
1284
1320
|
const trailingEvents = [];
|
|
@@ -1561,6 +1597,8 @@ export class PlayCallChain {
|
|
|
1561
1597
|
}
|
|
1562
1598
|
response = await modelHost.exchange(request, {
|
|
1563
1599
|
signal,
|
|
1600
|
+
diagnosticAttempt: attempt,
|
|
1601
|
+
onGoogleHop: (hop) => this.#worlds.playTimeline.recordGoogleHop(session.worldId, session.chainId, session.promptRuns?.at(-1)?.exchange ?? 1, hop),
|
|
1564
1602
|
onDelta: (delta) => {
|
|
1565
1603
|
this.#observeInvocation(session.chainId, delta.kind, {
|
|
1566
1604
|
...(delta.kind === "reasoning"
|
|
@@ -1586,7 +1624,7 @@ export class PlayCallChain {
|
|
|
1586
1624
|
},
|
|
1587
1625
|
});
|
|
1588
1626
|
if (signal.aborted)
|
|
1589
|
-
throw new ModelHostCancelledError("The player cancelled the dispatched model request; its response was not committed
|
|
1627
|
+
throw new ModelHostCancelledError("The player cancelled the dispatched model request; its response was not committed.");
|
|
1590
1628
|
this.#setInvocationAbortable(session.chainId, false);
|
|
1591
1629
|
if ((response.toolCalls?.length ?? 0) > 0)
|
|
1592
1630
|
this.#observeInvocation(session.chainId, "tool", {
|
|
@@ -1596,7 +1634,10 @@ export class PlayCallChain {
|
|
|
1596
1634
|
await this.#failureLog?.recordExchangeIfActive(response.diagnostics);
|
|
1597
1635
|
}
|
|
1598
1636
|
catch (error) {
|
|
1599
|
-
await this.#advances.end(session, dispatchFailureEnding(
|
|
1637
|
+
await this.#advances.end(session, dispatchFailureEnding(signal.aborted && !(error instanceof ModelHostCancelledError)
|
|
1638
|
+
? new ModelHostCancelledError("The player cancelled the dispatched model request; its response was not committed.")
|
|
1639
|
+
: error, event.id, (request.modelConfig?.provider ??
|
|
1640
|
+
session.modelBinding?.provider) === "google_interactions"), advance, () => this.#notifySnapshot(observer, session));
|
|
1600
1641
|
return projectView(session);
|
|
1601
1642
|
}
|
|
1602
1643
|
try {
|
|
@@ -1661,7 +1702,7 @@ export class PlayCallChain {
|
|
|
1661
1702
|
},
|
|
1662
1703
|
],
|
|
1663
1704
|
});
|
|
1664
|
-
await this.#advances.end(session, ending, advance, () => this.#notifySnapshot(observer, session));
|
|
1705
|
+
await this.#advances.end(session, { ...ending, streamingEventId: event.id }, advance, () => this.#notifySnapshot(observer, session));
|
|
1665
1706
|
return projectView(session);
|
|
1666
1707
|
}
|
|
1667
1708
|
}
|
|
@@ -1691,6 +1732,13 @@ export class PlayCallChain {
|
|
|
1691
1732
|
event.committedHead === session.parentHead);
|
|
1692
1733
|
if (reply === undefined)
|
|
1693
1734
|
throw new PlayCallChainError("Follow-up reply is not committed");
|
|
1735
|
+
const run = session.promptRuns?.at(-1);
|
|
1736
|
+
const runReplies = session.events.filter((event) => event.kind === "assistant" &&
|
|
1737
|
+
event.responseKind === "narrative" &&
|
|
1738
|
+
event.committedHead !== undefined &&
|
|
1739
|
+
event.id >= (run?.firstEventId ?? 1));
|
|
1740
|
+
// Without a new prompt run there is no distinct bounded encoding slot.
|
|
1741
|
+
const previewExchange = runReplies.length === 1 ? (run?.exchange ?? 1) : undefined;
|
|
1694
1742
|
const observeFollowupDelta = (delta) => {
|
|
1695
1743
|
this.#observeInvocation(session.chainId, "followup", {
|
|
1696
1744
|
...(delta.kind === "reasoning"
|
|
@@ -1715,6 +1763,14 @@ export class PlayCallChain {
|
|
|
1715
1763
|
? {}
|
|
1716
1764
|
: { reasoning: outcome.reasoning }),
|
|
1717
1765
|
...(outcome.usage === undefined ? {} : { usage: outcome.usage }),
|
|
1766
|
+
...(outcome.requestPreviewRecorded !== true ||
|
|
1767
|
+
outcome.requestPreviewStatus === undefined ||
|
|
1768
|
+
previewExchange === undefined
|
|
1769
|
+
? {}
|
|
1770
|
+
: {
|
|
1771
|
+
requestPreviewExchange: previewExchange,
|
|
1772
|
+
requestPreviewStatus: outcome.requestPreviewStatus,
|
|
1773
|
+
}),
|
|
1718
1774
|
toolCalls: structuredClone(outcome.toolCalls),
|
|
1719
1775
|
...(outcome.failure === undefined ? {} : { failure: outcome.failure }),
|
|
1720
1776
|
});
|
|
@@ -1757,6 +1813,21 @@ export class PlayCallChain {
|
|
|
1757
1813
|
? {}
|
|
1758
1814
|
: { failureLog: this.#failureLog }),
|
|
1759
1815
|
observer: {
|
|
1816
|
+
onGoogleHop: async (id, hop) => {
|
|
1817
|
+
if (previewExchange !== undefined)
|
|
1818
|
+
await this.#worlds.playTimeline.recordGoogleHop(session.worldId, session.chainId, previewExchange, hop, id);
|
|
1819
|
+
},
|
|
1820
|
+
onRequest: async (id, request) => {
|
|
1821
|
+
if (previewExchange === undefined)
|
|
1822
|
+
return false;
|
|
1823
|
+
// Only Google currently has a continuation-redacting encoder.
|
|
1824
|
+
const preview = modelHost.binding().provider === "google_interactions"
|
|
1825
|
+
? modelHost.previewAuthoringRequest?.(request)
|
|
1826
|
+
: undefined;
|
|
1827
|
+
if (preview === undefined)
|
|
1828
|
+
return false;
|
|
1829
|
+
return this.#worlds.playTimeline.recordFollowupEncoding(session.worldId, session.chainId, previewExchange, id, preview);
|
|
1830
|
+
},
|
|
1760
1831
|
onProviderDispatch: () => {
|
|
1761
1832
|
this.#setInvocationAbortable(session.chainId, true);
|
|
1762
1833
|
this.#observeInvocation(session.chainId, "followup", {
|
|
@@ -1837,8 +1908,9 @@ export class PlayCallChain {
|
|
|
1837
1908
|
}
|
|
1838
1909
|
/**
|
|
1839
1910
|
* An in-progress advance, or a session still marked running, means a model
|
|
1840
|
-
* dispatch or settlement was cut off by a process exit.
|
|
1841
|
-
*
|
|
1911
|
+
* dispatch or settlement was cut off by a process exit. Recovery preserves
|
|
1912
|
+
* persisted endings and applies the protocol's retry boundary (ADR-0035,
|
|
1913
|
+
* ADR-0044).
|
|
1842
1914
|
*/
|
|
1843
1915
|
async #reconcileSessionAdvance(session, loaded) {
|
|
1844
1916
|
const advance = loaded === undefined
|
|
@@ -2364,6 +2436,9 @@ function assertSessionModelHostBinding(session, modelHost) {
|
|
|
2364
2436
|
throw new PlayCallChainError("This model context predates durable provider continuation bindings; start a fresh context.");
|
|
2365
2437
|
if (session.modelBinding.provider !== modelHost.binding().provider)
|
|
2366
2438
|
throw new PlayCallChainError("The selected model protocol differs from this context. Switch back to its original protocol to continue, or start a fresh context.", "model_protocol_mismatch");
|
|
2439
|
+
if (session.modelBinding.provider === "google_interactions" &&
|
|
2440
|
+
!equalModelHostBinding(session.modelBinding, modelHost.binding()))
|
|
2441
|
+
throw new PlayCallChainError("The selected model connection does not match the frozen Google binding; start a fresh context.");
|
|
2367
2442
|
}
|
|
2368
2443
|
function validateIdentity(value, label) {
|
|
2369
2444
|
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/u.test(value))
|
|
@@ -2398,6 +2473,15 @@ function turnNoteField(turnNote) {
|
|
|
2398
2473
|
function touch(session) {
|
|
2399
2474
|
session.updatedAt = Date.now();
|
|
2400
2475
|
}
|
|
2476
|
+
/** Failed diagnostic copies cannot attach unrelated encodings to retained events. */
|
|
2477
|
+
function forgetFollowupPreviews(session) {
|
|
2478
|
+
for (const event of session.events) {
|
|
2479
|
+
if (event.kind !== "followup")
|
|
2480
|
+
continue;
|
|
2481
|
+
delete event.requestPreviewExchange;
|
|
2482
|
+
delete event.requestPreviewStatus;
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2401
2485
|
function notifyAssistantDelta(observer, delta) {
|
|
2402
2486
|
try {
|
|
2403
2487
|
observer?.onAssistantDelta?.(structuredClone(delta));
|
|
@@ -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" ||
|