@myagentroam/agent 0.11.20 → 0.11.22
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/agent-core/error.d.ts +1 -1
- package/dist/agent-core/error.js +20 -14
- package/dist/agent-core/extensions/compaction.d.ts +6 -0
- package/dist/agent-core/extensions/compaction.js +16 -2
- package/dist/agent-core/extensions/model-request.d.ts +5 -9
- package/dist/agent-core/extensions/model-request.js +14 -3
- package/dist/agent-core/extensions/prompt-input.js +3 -1
- package/dist/agent-core/host/contracts.d.ts +8 -0
- package/dist/agent-core/host/local-agent-host.d.ts +2 -0
- package/dist/agent-core/host/local-agent-host.js +3 -0
- package/dist/agent-core/model/anthropic-messages.js +15 -26
- package/dist/agent-core/model/catalog-file.d.ts +2 -0
- package/dist/agent-core/model/configuration.d.ts +3 -1
- package/dist/agent-core/model/configuration.js +1 -0
- package/dist/agent-core/model/contracts.d.ts +5 -8
- package/dist/agent-core/model/image.d.ts +17 -0
- package/dist/agent-core/model/image.js +53 -0
- package/dist/agent-core/model/openai-responses.d.ts +7 -2
- package/dist/agent-core/model/openai-responses.js +81 -27
- package/dist/agent-core/runtime/compaction/retained-context.js +2 -9
- package/dist/agent-core/runtime/context/gc-runtime.d.ts +3 -1
- package/dist/agent-core/runtime/context/gc-runtime.js +17 -6
- package/dist/agent-core/runtime/context/gc-tokens.js +13 -0
- package/dist/agent-core/runtime/context/gc.d.ts +2 -1
- package/dist/agent-core/runtime/context/gc.js +75 -12
- package/dist/agent-core/runtime/context/projection.js +4 -0
- package/dist/agent-core/runtime/context/request-budget.d.ts +2 -4
- package/dist/agent-core/runtime/context/restore.d.ts +2 -0
- package/dist/agent-core/runtime/context/restore.js +19 -3
- package/dist/agent-core/runtime/contracts.d.ts +2 -3
- package/dist/agent-core/runtime/execution.d.ts +1 -1
- package/dist/agent-core/runtime/execution.js +10 -4
- package/dist/agent-core/runtime/resource-publication.d.ts +8 -0
- package/dist/agent-core/runtime/resource-publication.js +60 -0
- package/dist/agent-core/sdk/agent.js +16 -0
- package/dist/agent-core/sdk/model-runtime.js +2 -0
- package/dist/agent-core/sdk/resource-input.d.ts +31 -0
- package/dist/agent-core/sdk/resource-input.js +342 -0
- package/dist/agent-core/sdk/session.js +144 -39
- package/dist/agent-core/session/catalog.d.ts +4 -0
- package/dist/agent-core/session/catalog.js +4 -0
- package/dist/agent-core/session/context-gc-checkpoint.d.ts +6 -4
- package/dist/agent-core/session/context-gc-checkpoint.js +41 -16
- package/dist/agent-core/session/inline-media.d.ts +45 -0
- package/dist/agent-core/session/inline-media.js +163 -0
- package/dist/agent-core/session/store/contracts.d.ts +11 -0
- package/dist/agent-core/session/store/history.d.ts +1 -1
- package/dist/agent-core/session/store/history.js +26 -2
- package/dist/agent-core/session/store/images.d.ts +4 -6
- package/dist/agent-core/session/store/images.js +18 -111
- package/dist/agent-core/session/store/jsonl-store.d.ts +9 -0
- package/dist/agent-core/session/store/jsonl-store.js +100 -28
- package/dist/agent-core/session/store/resources.d.ts +26 -0
- package/dist/agent-core/session/store/resources.js +196 -0
- package/dist/agent-core/session/store/types.d.ts +7 -6
- package/dist/agent-core/session/transaction-recovery.js +1 -1
- package/dist/agent-core/tools/code-mode/cells.js +3 -4
- package/dist/agent-core/tools/code-mode/index.js +32 -8
- package/dist/agent-core/tools/code-mode/observations.d.ts +5 -16
- package/dist/agent-core/tools/code-mode/quickjs-engine.js +23 -26
- package/dist/agent-core/tools/contracts.d.ts +15 -5
- package/dist/agent-core/tools/execution-limits.d.ts +3 -1
- package/dist/agent-core/tools/execution-limits.js +3 -1
- package/dist/agent-core/tools/execution.d.ts +2 -1
- package/dist/agent-core/tools/execution.js +31 -4
- package/dist/agent-core/tools/input-diagnostics/source-preview.d.ts +10 -0
- package/dist/agent-core/tools/input-diagnostics/source-preview.js +64 -0
- package/dist/agent-core/tools/local/apply-patch-parser.js +2 -53
- package/dist/agent-core/tools/local/apply-patch.js +1 -1
- package/dist/agent-core/tools/local/image-processing.d.ts +24 -0
- package/dist/agent-core/tools/local/image-processing.js +135 -0
- package/dist/agent-core/tools/local/read.js +1 -1
- package/dist/agent-core/tools/local/view-image.d.ts +4 -0
- package/dist/agent-core/tools/local/view-image.js +154 -25
- package/dist/agent-core/tools/output-projection.d.ts +2 -4
- package/dist/agent-core/tools/output-projection.js +81 -10
- package/dist/agent-core/tools/runtime-context.d.ts +1 -0
- package/dist/agent-core/tools/runtime.js +20 -1
- package/dist/cli/main.js +2 -1
- package/dist/index.d.ts +1 -1
- package/package.json +2 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { observeModelRequestUsage } from './usage-observer.js';
|
|
2
2
|
import { observationModelParts } from '../tools/code-mode/observations.js';
|
|
3
|
+
import { modelImageKey, modelImageUrl } from './image.js';
|
|
3
4
|
import { stableToolSchema } from '../tools/definition-projection.js';
|
|
4
5
|
import { isDeepStrictEqual } from 'node:util';
|
|
5
6
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
7
|
import { describeMarAgentError, MarAgentError } from '../error.js';
|
|
8
|
+
import { assertInlineMediaBudget } from '../session/inline-media.js';
|
|
7
9
|
import { marAgentReasoningEffortSchema, resolveModelDefaultReasoningEffort, usesResponsesConfigurationUpdates } from './configuration.js';
|
|
8
10
|
import WebSocket from 'ws';
|
|
9
11
|
import { isRetryableUpstreamOverloadCode, number, parseSse, record, text, observeModelStreamEvent, modelStreamEventObservedAt } from './sse.js';
|
|
@@ -95,7 +97,7 @@ export class OpenAiResponsesAdapter {
|
|
|
95
97
|
const attemptTransport = this.configuration.responsesTransport?.transport === 'WEBSOCKET' ? 'WEBSOCKET' : 'HTTP';
|
|
96
98
|
const requestPrefixDiagnostic = request.onAttemptDiagnostic
|
|
97
99
|
? {
|
|
98
|
-
requestInputItems: prepared.
|
|
100
|
+
requestInputItems: prepared.inputItemCount
|
|
99
101
|
}
|
|
100
102
|
: {};
|
|
101
103
|
const prelude = [];
|
|
@@ -667,7 +669,6 @@ class OpenAiResponsesTurnSession {
|
|
|
667
669
|
#active = false;
|
|
668
670
|
#closed = false;
|
|
669
671
|
#turnState;
|
|
670
|
-
#fullBody;
|
|
671
672
|
#completedResponseId;
|
|
672
673
|
#connectionDiagnostic;
|
|
673
674
|
#webSocketDiagnostic;
|
|
@@ -686,11 +687,15 @@ class OpenAiResponsesTurnSession {
|
|
|
686
687
|
const outputItems = [];
|
|
687
688
|
let completed = false;
|
|
688
689
|
let sawUnrepresentedOutput = false;
|
|
690
|
+
let outputMessageCount = 0;
|
|
689
691
|
try {
|
|
690
692
|
const prepared = this.prepareRequest(request);
|
|
691
|
-
this.#fullBody = prepared.fullBody;
|
|
692
693
|
this.#completedResponseId = undefined;
|
|
693
694
|
for await (const event of this.adapter.startTurn(request, signal, this, prepared)) {
|
|
695
|
+
if (event.type === 'response.item' ||
|
|
696
|
+
event.type === 'text.completed' ||
|
|
697
|
+
event.type === 'client_tool.call')
|
|
698
|
+
outputMessageCount++;
|
|
694
699
|
if (event.type === 'response.started' && event.responseId !== 'unknown')
|
|
695
700
|
responseId = event.responseId;
|
|
696
701
|
else if (event.type === 'response.item')
|
|
@@ -713,7 +718,13 @@ class OpenAiResponsesTurnSession {
|
|
|
713
718
|
completed &&
|
|
714
719
|
responseId !== undefined &&
|
|
715
720
|
(outputItems.length > 0 || !sawUnrepresentedOutput)) {
|
|
716
|
-
this.state.lastRequest =
|
|
721
|
+
this.state.lastRequest =
|
|
722
|
+
this.adapter.configuration.responsesTransport?.transport === 'WEBSOCKET'
|
|
723
|
+
? { ...prepared.body, input: [] }
|
|
724
|
+
: prepared.fullBody;
|
|
725
|
+
this.state.lastMessageCount = request.messages.length + outputMessageCount;
|
|
726
|
+
this.state.lastInputItemCount = prepared.inputItemCount;
|
|
727
|
+
this.state.lastSystem = JSON.stringify([request.system, request.systemBlocks]);
|
|
717
728
|
this.state.lastResponse = { responseId, outputItems };
|
|
718
729
|
}
|
|
719
730
|
else
|
|
@@ -725,7 +736,6 @@ class OpenAiResponsesTurnSession {
|
|
|
725
736
|
}
|
|
726
737
|
finally {
|
|
727
738
|
this.#completedResponseId = undefined;
|
|
728
|
-
this.#fullBody = undefined;
|
|
729
739
|
this.#active = false;
|
|
730
740
|
}
|
|
731
741
|
}
|
|
@@ -738,11 +748,19 @@ class OpenAiResponsesTurnSession {
|
|
|
738
748
|
resetContinuation() {
|
|
739
749
|
clearResponsesContinuation(this.state);
|
|
740
750
|
}
|
|
751
|
+
continuationInputCount() {
|
|
752
|
+
return this.state.lastMessageCount ?? 0;
|
|
753
|
+
}
|
|
754
|
+
commitContinuationInput(messageCount) {
|
|
755
|
+
if (this.state.lastResponse !== undefined)
|
|
756
|
+
this.state.lastMessageCount = messageCount;
|
|
757
|
+
}
|
|
741
758
|
captureCompletedResponseId(responseId) {
|
|
742
759
|
if (responseId !== undefined)
|
|
743
760
|
this.#completedResponseId = responseId;
|
|
744
761
|
}
|
|
745
762
|
prepareRequest(request) {
|
|
763
|
+
assertInlineMediaBudget(request);
|
|
746
764
|
if (this.adapter.configuration.responsesTransport?.transport === 'WEBSOCKET' &&
|
|
747
765
|
this.state.socket !== undefined &&
|
|
748
766
|
this.state.socket.readyState !== WebSocket.OPEN) {
|
|
@@ -750,16 +768,44 @@ class OpenAiResponsesTurnSession {
|
|
|
750
768
|
clearResponsesContinuation(this.state);
|
|
751
769
|
}
|
|
752
770
|
// The continuation baseline must not share mutable schemas or items with callers.
|
|
753
|
-
const
|
|
771
|
+
const snapshot = (body) => JSON.parse(JSON.stringify(body));
|
|
772
|
+
let full;
|
|
773
|
+
const fullBody = () => (full ??= snapshot(responsesRequestBody(this.adapter.configuration, request, request.messages)));
|
|
754
774
|
if (responsesContinuationEnabled(this.adapter.configuration)) {
|
|
755
|
-
const
|
|
775
|
+
const websocket = this.adapter.configuration.responsesTransport?.transport === 'WEBSOCKET';
|
|
776
|
+
const suffixMessages = websocket && this.state.lastMessageCount !== undefined
|
|
777
|
+
? request.messages.slice(this.state.lastMessageCount)
|
|
778
|
+
: undefined;
|
|
779
|
+
const suffixBody = websocket &&
|
|
780
|
+
this.state.lastResponse !== undefined &&
|
|
781
|
+
suffixMessages !== undefined &&
|
|
782
|
+
this.state.lastRequest !== undefined &&
|
|
783
|
+
this.state.lastSystem === JSON.stringify([request.system, request.systemBlocks]) &&
|
|
784
|
+
// Top-level images attach to the last real user, which must be in this suffix.
|
|
785
|
+
(!request.images?.length || suffixMessages.some(isRealUserMessage))
|
|
786
|
+
? snapshot(responsesRequestBody(this.adapter.configuration, { ...request, system: '', systemBlocks: [] }, suffixMessages, this.state.lastResponse.outputItems))
|
|
787
|
+
: undefined;
|
|
788
|
+
const incremental = websocket
|
|
789
|
+
? suffixBody !== undefined &&
|
|
790
|
+
responsesContinuationPropertiesMatch(this.state.lastRequest, suffixBody)
|
|
791
|
+
? suffixBody.input
|
|
792
|
+
: undefined
|
|
793
|
+
: incrementalResponsesInput(this.state, fullBody());
|
|
756
794
|
if (incremental !== undefined) {
|
|
757
795
|
const responseId = this.state.lastResponse.responseId;
|
|
796
|
+
const inputItemCount = websocket
|
|
797
|
+
? (this.state.lastInputItemCount ?? 0) +
|
|
798
|
+
this.state.lastResponse.outputItems.length +
|
|
799
|
+
incremental.length
|
|
800
|
+
: fullBody().input.length;
|
|
758
801
|
delete this.state.lastResponse;
|
|
759
802
|
return {
|
|
760
|
-
fullBody
|
|
803
|
+
get fullBody() {
|
|
804
|
+
return fullBody();
|
|
805
|
+
},
|
|
806
|
+
inputItemCount,
|
|
761
807
|
body: {
|
|
762
|
-
...fullBody,
|
|
808
|
+
...(suffixBody ?? fullBody()),
|
|
763
809
|
input: incremental,
|
|
764
810
|
previous_response_id: responseId
|
|
765
811
|
},
|
|
@@ -768,16 +814,12 @@ class OpenAiResponsesTurnSession {
|
|
|
768
814
|
}
|
|
769
815
|
}
|
|
770
816
|
return {
|
|
771
|
-
fullBody,
|
|
772
|
-
|
|
817
|
+
fullBody: fullBody(),
|
|
818
|
+
inputItemCount: fullBody().input.length,
|
|
819
|
+
body: fullBody(),
|
|
773
820
|
continuation: false
|
|
774
821
|
};
|
|
775
822
|
}
|
|
776
|
-
fullRequestBody() {
|
|
777
|
-
if (this.#fullBody === undefined)
|
|
778
|
-
throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Full Responses request is unavailable.');
|
|
779
|
-
return this.#fullBody;
|
|
780
|
-
}
|
|
781
823
|
currentTurnState() {
|
|
782
824
|
return this.#turnState;
|
|
783
825
|
}
|
|
@@ -906,7 +948,7 @@ function localContinuationReset() {
|
|
|
906
948
|
function isLocalContinuationReset(error) {
|
|
907
949
|
return error instanceof MarAgentError && error.code === 'MAR_AGENT_RESPONSES_CONTINUATION_RESET';
|
|
908
950
|
}
|
|
909
|
-
function responsesRequestBody(configuration, request, messages) {
|
|
951
|
+
function responsesRequestBody(configuration, request, messages, previousOutputItems = []) {
|
|
910
952
|
const systemBlocks = resolveModelTextBlocks(request.system, request.systemBlocks ?? []);
|
|
911
953
|
if ((request.systemBlocks !== undefined && systemBlocks.some((block) => block.cache)) ||
|
|
912
954
|
messages.some((message) => message.textBlocks !== undefined &&
|
|
@@ -940,7 +982,7 @@ function responsesRequestBody(configuration, request, messages) {
|
|
|
940
982
|
configuration.hostedWebSearch)
|
|
941
983
|
tools.push({ type: 'web_search' });
|
|
942
984
|
const toolChoice = request.toolChoice ?? 'auto';
|
|
943
|
-
const input = openAiInput(messages, request.images, configuration.id, true, usesResponsesConfigurationUpdates(configuration), configuration.responsesOptionalParameters?.nativeTools !== false, request.tools);
|
|
985
|
+
const input = openAiInput(messages, request.images, configuration.id, true, usesResponsesConfigurationUpdates(configuration), configuration.responsesOptionalParameters?.nativeTools !== false, request.tools, previousOutputItems);
|
|
944
986
|
const systemInput = responsesSystemInput(request, systemBlocks);
|
|
945
987
|
if (systemInput !== undefined)
|
|
946
988
|
input.unshift(systemInput);
|
|
@@ -1021,11 +1063,14 @@ function incrementalResponsesInput(state, current) {
|
|
|
1021
1063
|
return current.input.slice(baseline.length);
|
|
1022
1064
|
}
|
|
1023
1065
|
function responsesContinuationPropertiesMatch(previous, current) {
|
|
1024
|
-
return (JSON.stringify({ ...previous, input: undefined }) ===
|
|
1025
|
-
JSON.stringify({ ...current, input: undefined }));
|
|
1066
|
+
return (JSON.stringify({ ...previous, input: undefined, previous_response_id: undefined }) ===
|
|
1067
|
+
JSON.stringify({ ...current, input: undefined, previous_response_id: undefined }));
|
|
1026
1068
|
}
|
|
1027
1069
|
function clearResponsesContinuation(state) {
|
|
1028
1070
|
delete state.lastRequest;
|
|
1071
|
+
delete state.lastMessageCount;
|
|
1072
|
+
delete state.lastInputItemCount;
|
|
1073
|
+
delete state.lastSystem;
|
|
1029
1074
|
delete state.lastResponse;
|
|
1030
1075
|
}
|
|
1031
1076
|
function hashResponsesValue(value) {
|
|
@@ -1846,7 +1891,7 @@ function responseCitations(response) {
|
|
|
1846
1891
|
function responseEventId(event) {
|
|
1847
1892
|
return text(record(event.response).id);
|
|
1848
1893
|
}
|
|
1849
|
-
export function openAiInput(messages, images, modelId, requireEncryptedReasoning, configurationUpdates = true, nativeTools = true, tools = []) {
|
|
1894
|
+
export function openAiInput(messages, images, modelId, requireEncryptedReasoning, configurationUpdates = true, nativeTools = true, tools = [], previousOutputItems = []) {
|
|
1850
1895
|
const compatibleMessages = messages.filter((message) => message.role !== 'provider' ||
|
|
1851
1896
|
(message.provider === 'OPENAI_RESPONSES' &&
|
|
1852
1897
|
(message.providerModelId === undefined || message.providerModelId === modelId) &&
|
|
@@ -1861,6 +1906,15 @@ export function openAiInput(messages, images, modelId, requireEncryptedReasoning
|
|
|
1861
1906
|
const functionCallIds = new Set();
|
|
1862
1907
|
const customCallIds = new Set();
|
|
1863
1908
|
const providerMessageTexts = new Set();
|
|
1909
|
+
// Incremental input omits the calls already retained by the WebSocket response.
|
|
1910
|
+
for (const item of previousOutputItems) {
|
|
1911
|
+
if (typeof item.call_id !== 'string')
|
|
1912
|
+
continue;
|
|
1913
|
+
if (item.type === 'function_call')
|
|
1914
|
+
functionCallIds.add(item.call_id);
|
|
1915
|
+
if (nativeTools && item.type === 'custom_tool_call')
|
|
1916
|
+
customCallIds.add(item.call_id);
|
|
1917
|
+
}
|
|
1864
1918
|
for (const message of compatibleMessages) {
|
|
1865
1919
|
if (message.role !== 'provider' || !message.item)
|
|
1866
1920
|
continue;
|
|
@@ -1970,7 +2024,7 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
|
|
|
1970
2024
|
? { type: 'input_text', text: part.text }
|
|
1971
2025
|
: {
|
|
1972
2026
|
type: 'input_image',
|
|
1973
|
-
image_url:
|
|
2027
|
+
image_url: modelImageUrl(part.image)
|
|
1974
2028
|
}) ??
|
|
1975
2029
|
(message.textBlocks !== undefined
|
|
1976
2030
|
? [
|
|
@@ -1980,7 +2034,7 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
|
|
|
1980
2034
|
})),
|
|
1981
2035
|
...(message.images ?? []).map((image) => ({
|
|
1982
2036
|
type: 'input_image',
|
|
1983
|
-
image_url:
|
|
2037
|
+
image_url: modelImageUrl(image)
|
|
1984
2038
|
}))
|
|
1985
2039
|
]
|
|
1986
2040
|
: message.images?.length
|
|
@@ -1988,7 +2042,7 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
|
|
|
1988
2042
|
{ type: 'input_text', text: message.content },
|
|
1989
2043
|
...message.images.map((image) => ({
|
|
1990
2044
|
type: 'input_image',
|
|
1991
|
-
image_url:
|
|
2045
|
+
image_url: modelImageUrl(image)
|
|
1992
2046
|
}))
|
|
1993
2047
|
]
|
|
1994
2048
|
: message.content)
|
|
@@ -2005,7 +2059,7 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
|
|
|
2005
2059
|
})),
|
|
2006
2060
|
...userImages.map((image) => ({
|
|
2007
2061
|
type: 'input_image',
|
|
2008
|
-
image_url:
|
|
2062
|
+
image_url: modelImageUrl(image)
|
|
2009
2063
|
}))
|
|
2010
2064
|
]
|
|
2011
2065
|
: userImages.length > 0
|
|
@@ -2013,7 +2067,7 @@ function openAiInputItem(message, images, customCallIds = new Set(), functionCal
|
|
|
2013
2067
|
{ type: 'input_text', text: message.content },
|
|
2014
2068
|
...userImages.map((image) => ({
|
|
2015
2069
|
type: 'input_image',
|
|
2016
|
-
image_url:
|
|
2070
|
+
image_url: modelImageUrl(image)
|
|
2017
2071
|
}))
|
|
2018
2072
|
]
|
|
2019
2073
|
: message.content
|
|
@@ -2060,7 +2114,7 @@ function hasOpenFunctionObject(value) {
|
|
|
2060
2114
|
function uniqueModelImages(...groups) {
|
|
2061
2115
|
const seen = new Set();
|
|
2062
2116
|
return groups.flatMap((group) => (group ?? []).flatMap((image) => {
|
|
2063
|
-
const key =
|
|
2117
|
+
const key = modelImageKey(image);
|
|
2064
2118
|
if (seen.has(key))
|
|
2065
2119
|
return [];
|
|
2066
2120
|
seen.add(key);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseModelInputMetadata } from '../../session/store/execution-state.js';
|
|
2
2
|
import { restoreModelContext } from '../context/projection.js';
|
|
3
|
+
import { parseModelImages } from '../../model/image.js';
|
|
3
4
|
/** Use a public native identity, never private context text, as the visible boundary. */
|
|
4
5
|
export function compactSnapshotAnchor(messages, sources) {
|
|
5
6
|
for (let index = messages.length - 1; index >= 0; index--) {
|
|
@@ -39,15 +40,7 @@ export function restoreCompactTail(value) {
|
|
|
39
40
|
: {}),
|
|
40
41
|
...(metadata ? { metadata } : {}),
|
|
41
42
|
...(projected?.textBlocks ? { textBlocks: projected.textBlocks } : {}),
|
|
42
|
-
...(
|
|
43
|
-
input.images.every((image) => image && typeof image.mimeType === 'string' && typeof image.dataBase64 === 'string')
|
|
44
|
-
? {
|
|
45
|
-
images: input.images.map((image) => ({
|
|
46
|
-
mimeType: image.mimeType,
|
|
47
|
-
dataBase64: image.dataBase64
|
|
48
|
-
}))
|
|
49
|
-
}
|
|
50
|
-
: {})
|
|
43
|
+
...(parseModelImages(input.images)?.length ? { images: parseModelImages(input.images) } : {})
|
|
51
44
|
};
|
|
52
45
|
}
|
|
53
46
|
return restoreModelContext(input);
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import type { ClientToolDefinition, ModelUsage } from '../../model/contracts.js';
|
|
1
|
+
import type { ClientToolDefinition, ModelMessage, ModelUsage } from '../../model/contracts.js';
|
|
2
2
|
import { type ContextGcPlatform } from '../../session/context-gc-checkpoint.js';
|
|
3
3
|
import type { SessionStore, SessionRecord } from '../../session/store/contracts.js';
|
|
4
4
|
import { type ContextProjection } from './projection.js';
|
|
5
5
|
export interface ContextGcRuntimeInput {
|
|
6
|
+
readonly inlineMedia?: import('../../session/inline-media.js').InlineMediaGcCandidate | undefined;
|
|
6
7
|
readonly store: SessionStore;
|
|
7
8
|
readonly sessionId: string;
|
|
8
9
|
readonly records?: readonly SessionRecord[];
|
|
10
|
+
readonly mediaView?: (messages: readonly ModelMessage[]) => readonly ModelMessage[];
|
|
9
11
|
readonly system: string;
|
|
10
12
|
readonly tools: readonly ClientToolDefinition[];
|
|
11
13
|
readonly modelId: string;
|
|
@@ -29,7 +29,10 @@ export class ContextGcRuntime {
|
|
|
29
29
|
this.reset();
|
|
30
30
|
this.#scope = scope;
|
|
31
31
|
}
|
|
32
|
-
const
|
|
32
|
+
const mediaRequired = input.inlineMedia !== undefined && input.inlineMedia.reason !== 'TEXT_GC';
|
|
33
|
+
const usage = mediaRequired
|
|
34
|
+
? (input.usage.contextInputTokens ?? input.usage.inputTokens ?? 0)
|
|
35
|
+
: contextGcEligibleUsage(input.usage, input.contextWindowTokens);
|
|
33
36
|
if (usage === undefined) {
|
|
34
37
|
this.#lastUsage = undefined;
|
|
35
38
|
this.#failures = 0;
|
|
@@ -45,11 +48,12 @@ export class ContextGcRuntime {
|
|
|
45
48
|
this.#rolloutsSinceCheckpoint++;
|
|
46
49
|
const maximumInterval = usage / input.contextWindowTokens >= 0.8 ? 2 : 8;
|
|
47
50
|
this.#remaining = Math.min(this.#remaining, maximumInterval - 1);
|
|
48
|
-
if (this.#remaining > 0) {
|
|
51
|
+
if (this.#remaining > 0 && !mediaRequired) {
|
|
49
52
|
this.#remaining--;
|
|
50
53
|
return false;
|
|
51
54
|
}
|
|
52
|
-
if (
|
|
55
|
+
if (!mediaRequired &&
|
|
56
|
+
this.#checkpoint?.trigger.modelId === input.modelId &&
|
|
53
57
|
this.#checkpoint.trigger.contextWindowTokens === input.contextWindowTokens &&
|
|
54
58
|
!this.#checkpoint.protectedCandidatesPending &&
|
|
55
59
|
this.#rolloutsSinceCheckpoint < CONTEXT_GC_POLICY.largeExecProtectionRollouts &&
|
|
@@ -90,7 +94,10 @@ export async function tryCommitContextGc(input) {
|
|
|
90
94
|
return (await attemptContextGc(input)).result;
|
|
91
95
|
}
|
|
92
96
|
async function attemptContextGc(input) {
|
|
93
|
-
const
|
|
97
|
+
const mediaRequired = input.inlineMedia !== undefined && input.inlineMedia.reason !== 'TEXT_GC';
|
|
98
|
+
const contextUsage = mediaRequired
|
|
99
|
+
? (input.usage.contextInputTokens ?? input.usage.inputTokens ?? 0)
|
|
100
|
+
: contextGcEligibleUsage(input.usage, input.contextWindowTokens);
|
|
94
101
|
if (contextUsage === undefined)
|
|
95
102
|
return { result: false };
|
|
96
103
|
let deferred = { result: false };
|
|
@@ -108,7 +115,8 @@ async function attemptContextGc(input) {
|
|
|
108
115
|
protectedCandidatesPending: previous.payload.metrics.protectedCandidateTokens > 0
|
|
109
116
|
};
|
|
110
117
|
}
|
|
111
|
-
if (
|
|
118
|
+
if (!mediaRequired &&
|
|
119
|
+
previous?.payload.trigger.modelId === input.modelId &&
|
|
112
120
|
previous.payload.trigger.contextWindowTokens === input.contextWindowTokens) {
|
|
113
121
|
const rolloutGrowth = records.filter((record) => record.sequence > previous.record.sequence &&
|
|
114
122
|
record.type === 'model.attempt' &&
|
|
@@ -124,10 +132,12 @@ async function attemptContextGc(input) {
|
|
|
124
132
|
return deferred;
|
|
125
133
|
}
|
|
126
134
|
const projection = buildContextProjection(records, checkpointCache);
|
|
135
|
+
const messages = projection.entries.map((entry) => entry.message);
|
|
127
136
|
const inspection = inspectContextGc({
|
|
137
|
+
...(input.inlineMedia ? { inlineMedia: input.inlineMedia } : {}),
|
|
128
138
|
records,
|
|
129
139
|
checkpointCache,
|
|
130
|
-
messages:
|
|
140
|
+
messages: input.mediaView?.(messages) ?? messages,
|
|
131
141
|
messageSources: projection.entries.map((entry) => entry.record),
|
|
132
142
|
system: input.system,
|
|
133
143
|
tools: input.tools,
|
|
@@ -197,6 +207,7 @@ async function matchingCheckpointWasPersisted(store, sessionId, draft, records,
|
|
|
197
207
|
!isDeepStrictEqual(latest.payload.base, draft.base) ||
|
|
198
208
|
!isDeepStrictEqual(latest.payload.trigger, draft.trigger) ||
|
|
199
209
|
!isDeepStrictEqual(latest.payload.economics, draft.economics) ||
|
|
210
|
+
!isDeepStrictEqual(latest.payload.inlineMedia, draft.inlineMedia) ||
|
|
200
211
|
!isDeepStrictEqual(latest.payload.metrics, draft.metrics))
|
|
201
212
|
return false;
|
|
202
213
|
const persisted = new Map(latest.payload.replacements.map((replacement) => [
|
|
@@ -10,6 +10,19 @@ export function contextGcTokenEntries(input, messages) {
|
|
|
10
10
|
}));
|
|
11
11
|
}
|
|
12
12
|
function serializedEntries(input, messages) {
|
|
13
|
+
// Media bytes have a separate budget. Keep identity for cache boundaries without
|
|
14
|
+
// loading resources or counting base64 payloads as text tokens.
|
|
15
|
+
messages = messages.map((message) => message.images?.length
|
|
16
|
+
? {
|
|
17
|
+
...message,
|
|
18
|
+
images: message.images.map((image) => ({
|
|
19
|
+
mimeType: image.mimeType,
|
|
20
|
+
url: image.resourceId
|
|
21
|
+
? `https://mar.invalid/resource/${image.resourceId}`
|
|
22
|
+
: (image.url ?? 'https://mar.invalid/inline-image')
|
|
23
|
+
}))
|
|
24
|
+
}
|
|
25
|
+
: message);
|
|
13
26
|
const responses = input.modelProtocol === 'OPENAI_RESPONSES' ||
|
|
14
27
|
(input.modelProtocol === undefined &&
|
|
15
28
|
(input.messages ?? messages).some((message) => message.provider === 'OPENAI_RESPONSES'));
|
|
@@ -24,7 +24,7 @@ export interface ContextGcEconomicsResult {
|
|
|
24
24
|
readonly passes: boolean;
|
|
25
25
|
}
|
|
26
26
|
export interface ContextGcPlan {
|
|
27
|
-
readonly stage: '
|
|
27
|
+
readonly stage: ContextGcMetrics['stage'];
|
|
28
28
|
readonly draft: ContextGcCheckpointDraft;
|
|
29
29
|
readonly metrics: ContextGcMetrics;
|
|
30
30
|
}
|
|
@@ -38,6 +38,7 @@ export interface ContextGcInspection {
|
|
|
38
38
|
readonly retryAfterRollouts?: number;
|
|
39
39
|
}
|
|
40
40
|
export interface ContextGcPlannerInput {
|
|
41
|
+
readonly inlineMedia?: import('../../session/inline-media.js').InlineMediaGcCandidate;
|
|
41
42
|
readonly records: readonly SessionRecord[];
|
|
42
43
|
readonly messages: readonly ModelMessage[];
|
|
43
44
|
readonly messageSources: readonly SessionRecord[];
|
|
@@ -118,6 +118,55 @@ export function planContextGc(input) {
|
|
|
118
118
|
return inspectContextGc(input).plan;
|
|
119
119
|
}
|
|
120
120
|
export function inspectContextGc(input) {
|
|
121
|
+
const text = inspectTextGc(input);
|
|
122
|
+
const media = input.inlineMedia;
|
|
123
|
+
if (!media || (media.reason === 'TEXT_GC' && !text.plan))
|
|
124
|
+
return text;
|
|
125
|
+
const usageRecord = latestUsageRecord(input.records, input.modelId) ?? input.records.at(-1);
|
|
126
|
+
const contextUsageTokens = modelContextUsage(input.usage) ?? 0;
|
|
127
|
+
if (!usageRecord || contextUsageTokens === undefined)
|
|
128
|
+
return text;
|
|
129
|
+
input = {
|
|
130
|
+
...input,
|
|
131
|
+
tokenCache: new Map(),
|
|
132
|
+
checkpointCache: input.checkpointCache ?? new ContextGcCheckpointCache()
|
|
133
|
+
};
|
|
134
|
+
const state = {
|
|
135
|
+
stage: text.plan?.stage ?? 'INLINE_MEDIA',
|
|
136
|
+
contextUsageTokens,
|
|
137
|
+
usageRatio: contextUsageTokens / input.contextWindowTokens,
|
|
138
|
+
usageRecord,
|
|
139
|
+
normalRolloutOrdinal: countSuccessfulNormalRollouts(input.records, input.modelId, input.contextWindowTokens),
|
|
140
|
+
protectedCandidateTokens: text.plan?.metrics.protectedCandidateTokens ?? 0
|
|
141
|
+
};
|
|
142
|
+
if (text.plan) {
|
|
143
|
+
const combined = evaluateCandidateSet(input, [], [
|
|
144
|
+
{
|
|
145
|
+
replacements: text.plan.draft.replacements,
|
|
146
|
+
observedRollouts: text.plan.metrics.minimumObservedRollouts,
|
|
147
|
+
evidenceSequence: text.plan.draft.boundarySequence
|
|
148
|
+
}
|
|
149
|
+
], state, media.project);
|
|
150
|
+
if (combined?.economics.passes) {
|
|
151
|
+
const plan = finalizePlan(input, combined, true);
|
|
152
|
+
if (plan)
|
|
153
|
+
return { plan };
|
|
154
|
+
}
|
|
155
|
+
if (media.reason === 'TEXT_GC')
|
|
156
|
+
return text;
|
|
157
|
+
}
|
|
158
|
+
// Byte pressure has its own admission signal; it never relaxes text GC eligibility.
|
|
159
|
+
const onlyMedia = evaluateCandidateSet(input, input.checkpointCache.read(input.records)?.payload.replacements ?? [], [
|
|
160
|
+
{
|
|
161
|
+
replacements: [],
|
|
162
|
+
observedRollouts: media.reason === 'HARD_LIMIT' ? 0 : 2,
|
|
163
|
+
evidenceSequence: input.records.at(-1).sequence
|
|
164
|
+
}
|
|
165
|
+
], { ...state, stage: 'INLINE_MEDIA', mediaOnly: true }, media.project);
|
|
166
|
+
const plan = onlyMedia && finalizePlan(input, onlyMedia, true);
|
|
167
|
+
return plan ? { plan } : text;
|
|
168
|
+
}
|
|
169
|
+
function inspectTextGc(input) {
|
|
121
170
|
const contextUsageTokens = contextGcEligibleUsage(input.usage, input.contextWindowTokens);
|
|
122
171
|
if (contextUsageTokens === undefined)
|
|
123
172
|
return {};
|
|
@@ -396,14 +445,19 @@ export function contextGcEarliestReplacementSequence(replacements) {
|
|
|
396
445
|
: [])
|
|
397
446
|
]));
|
|
398
447
|
}
|
|
399
|
-
function finalizePlan(input, evaluated) {
|
|
448
|
+
function finalizePlan(input, evaluated, includeMedia = false) {
|
|
400
449
|
if (!evaluated)
|
|
401
450
|
return undefined;
|
|
451
|
+
let inlineMedia;
|
|
452
|
+
if (includeMedia && input.inlineMedia) {
|
|
453
|
+
const { project: _project, ...checkpoint } = input.inlineMedia;
|
|
454
|
+
inlineMedia = checkpoint;
|
|
455
|
+
}
|
|
402
456
|
const base = contextGcBaseFromRecords(input.records, evaluated.boundarySequence);
|
|
403
457
|
if (!base)
|
|
404
458
|
return undefined;
|
|
405
459
|
const replacements = normalizeContextGcReplacements(evaluated.replacements);
|
|
406
|
-
if (!replacements || replacements.length === 0)
|
|
460
|
+
if (!replacements || (replacements.length === 0 && !inlineMedia))
|
|
407
461
|
return undefined;
|
|
408
462
|
const metrics = {
|
|
409
463
|
stage: evaluated.stage,
|
|
@@ -418,8 +472,12 @@ function finalizePlan(input, evaluated) {
|
|
|
418
472
|
compactDeferralCreditCostUnits: evaluated.compactDeferralCreditCostUnits,
|
|
419
473
|
imminentCompactAvoidanceCreditCostUnits: evaluated.imminentCompactAvoidanceCreditCostUnits,
|
|
420
474
|
appliedCompactCreditCostUnits: evaluated.appliedCompactCreditCostUnits,
|
|
421
|
-
requiredPositiveNetRollouts: evaluated.economics.requiredPositiveNetRollouts
|
|
422
|
-
|
|
475
|
+
requiredPositiveNetRollouts: Number.isFinite(evaluated.economics.requiredPositiveNetRollouts)
|
|
476
|
+
? evaluated.economics.requiredPositiveNetRollouts
|
|
477
|
+
: null,
|
|
478
|
+
requiredTargetRoiRollouts: Number.isFinite(evaluated.economics.requiredTargetRoiRollouts)
|
|
479
|
+
? evaluated.economics.requiredTargetRoiRollouts
|
|
480
|
+
: null,
|
|
423
481
|
projectedRolloutsUntilCompactAfterGc: evaluated.projectedRolloutsUntilCompactAfterGc,
|
|
424
482
|
projectedTokensBeforeGc: evaluated.projectedTokensBeforeGc,
|
|
425
483
|
projectedTokensAfterGc: evaluated.projectedTokensAfterGc,
|
|
@@ -436,12 +494,16 @@ function finalizePlan(input, evaluated) {
|
|
|
436
494
|
cachePenaltyKnown: input.usage.cacheReadTokens !== undefined
|
|
437
495
|
};
|
|
438
496
|
const draft = {
|
|
497
|
+
...(inlineMedia ? { inlineMedia } : {}),
|
|
439
498
|
version: 1,
|
|
440
499
|
base,
|
|
441
500
|
boundarySequence: evaluated.boundarySequence,
|
|
442
501
|
replacements,
|
|
443
502
|
trigger: {
|
|
444
|
-
usageSequence: evaluated.usageRecord.
|
|
503
|
+
usageSequence: isRecord(evaluated.usageRecord.payload) &&
|
|
504
|
+
evaluated.usageRecord.payload.type === 'usage.updated'
|
|
505
|
+
? evaluated.usageRecord.sequence
|
|
506
|
+
: null,
|
|
445
507
|
contextInputTokens: evaluated.contextUsageTokens,
|
|
446
508
|
contextWindowTokens: input.contextWindowTokens,
|
|
447
509
|
normalRolloutOrdinal: evaluated.normalRolloutOrdinal,
|
|
@@ -453,7 +515,7 @@ function finalizePlan(input, evaluated) {
|
|
|
453
515
|
};
|
|
454
516
|
return { stage: evaluated.stage, draft, metrics };
|
|
455
517
|
}
|
|
456
|
-
function evaluateCandidateSet(input, existing, candidates, state) {
|
|
518
|
+
function evaluateCandidateSet(input, existing, candidates, state, mediaProjection) {
|
|
457
519
|
if (candidates.length === 0)
|
|
458
520
|
return undefined;
|
|
459
521
|
const replacements = normalizeContextGcReplacements([
|
|
@@ -463,16 +525,17 @@ function evaluateCandidateSet(input, existing, candidates, state) {
|
|
|
463
525
|
if (!replacements)
|
|
464
526
|
return undefined;
|
|
465
527
|
const projected = applyContextGcReplacementsToMessages(input.messages, input.messageSources, replacements);
|
|
466
|
-
const
|
|
528
|
+
const projectedMessages = mediaProjection?.(projected.messages) ?? projected.messages;
|
|
529
|
+
const reclaimedTokens = contextGcReclaimedTokens(input, input.messages, projectedMessages);
|
|
467
530
|
const minimumGrossReductionRate = contextGcMinimumGrossReductionRate(state.usageRatio);
|
|
468
531
|
const minimumReclaimedTokens = contextGcMinimumReclaimedTokens(state.contextUsageTokens, input.contextWindowTokens);
|
|
469
|
-
if (reclaimedTokens < minimumReclaimedTokens)
|
|
532
|
+
if (!state.mediaOnly && reclaimedTokens < minimumReclaimedTokens)
|
|
470
533
|
return undefined;
|
|
471
534
|
const beforeTokens = contextGcPrefixTokens(input) + contextGcMessageTokens(input, input.messages);
|
|
472
|
-
const afterTokens = contextGcPrefixTokens(input) + contextGcMessageTokens(input,
|
|
473
|
-
if (reclaimedTokens === 0)
|
|
535
|
+
const afterTokens = contextGcPrefixTokens(input) + contextGcMessageTokens(input, projectedMessages);
|
|
536
|
+
if (!state.mediaOnly && reclaimedTokens === 0)
|
|
474
537
|
return undefined;
|
|
475
|
-
const cache = contextGcCacheImpact(input, input.messages,
|
|
538
|
+
const cache = contextGcCacheImpact(input, input.messages, projectedMessages, input.usage.cacheReadTokens ?? 0, reclaimedTokens);
|
|
476
539
|
const growthSamples = collectContextGrowthSamples(input.records, input.modelId, input.contextWindowTokens, input.checkpointCache);
|
|
477
540
|
const contextGrowthP75Tokens = percentile75(growthSamples);
|
|
478
541
|
const compactThresholdTokens = Math.floor(input.contextWindowTokens * COMPACT_THRESHOLD_RATIO);
|
|
@@ -481,7 +544,7 @@ function evaluateCandidateSet(input, existing, candidates, state) {
|
|
|
481
544
|
const pendingTokens = Math.max(0, input.usage.outputTokens ?? 0) + contextGcMessageTokens(input, pendingMessages);
|
|
482
545
|
const projectedTokensBeforeGc = state.contextUsageTokens + pendingTokens;
|
|
483
546
|
const projectedTokensAfterGc = Math.max(0, projectedTokensBeforeGc - reclaimedTokens);
|
|
484
|
-
if (projectedTokensAfterGc >= compactThresholdTokens)
|
|
547
|
+
if (!state.mediaOnly && projectedTokensAfterGc >= compactThresholdTokens)
|
|
485
548
|
return undefined;
|
|
486
549
|
const projectedRolloutsUntilCompactAfterGc = contextGrowthP75Tokens <= 0
|
|
487
550
|
? 0
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { restoreNativeModelContext } from '../../model/native-context.js';
|
|
2
2
|
import { appliedInputRecord } from '../../session/deferred-input.js';
|
|
3
3
|
import { inputMetadataFromRecord } from '../../session/store/execution-state.js';
|
|
4
|
+
import { parseModelImages } from '../../model/image.js';
|
|
4
5
|
import { contextGcTargetKey, latestValidContextGcCheckpoint } from '../../session/context-gc-checkpoint.js';
|
|
5
6
|
import { TOOL_EXECUTION_LIMITS } from '../../tools/execution-limits.js';
|
|
6
7
|
import { replaceReadContextItems } from '../../tools/local/read-context.js';
|
|
@@ -187,6 +188,9 @@ export function restoreModelContext(payload, providerModelId) {
|
|
|
187
188
|
role: 'tool',
|
|
188
189
|
content: payload.content,
|
|
189
190
|
callId: payload.callId,
|
|
191
|
+
...(parseModelImages(payload.images)?.length
|
|
192
|
+
? { images: parseModelImages(payload.images) }
|
|
193
|
+
: {}),
|
|
190
194
|
...(typeof payload.name === 'string' ? { name: payload.name } : {}),
|
|
191
195
|
...(typeof payload.isError === 'boolean' ? { isError: payload.isError } : {}),
|
|
192
196
|
...(Array.isArray(payload.textBlocks) &&
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type { ClientToolDefinition, ModelMessage } from '../../model/contracts.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
dataBase64: string;
|
|
5
|
-
}[]): number;
|
|
2
|
+
import type { ModelImage } from '../../model/image.js';
|
|
3
|
+
export declare function estimatedRequestTokens(system: string, messages: readonly ModelMessage[], tools: readonly ClientToolDefinition[], images?: readonly ModelImage[]): number;
|
|
6
4
|
export declare function stableJson(value: unknown): string;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { SessionStore } from '../../session/store/contracts.js';
|
|
2
2
|
import type { ModelMessage } from '../../model/contracts.js';
|
|
3
|
+
import type { ModelImage } from '../../model/image.js';
|
|
3
4
|
import type { CompactUserInput } from '../../session/store/contracts.js';
|
|
4
5
|
import type { EnvironmentContextState } from './contracts.js';
|
|
5
6
|
import { type ContextProjection } from './projection.js';
|
|
7
|
+
export declare function readResourceImages(store: Pick<SessionStore, 'readResource'>, sessionId: string, images: readonly ModelImage[]): Promise<ModelImage[]>;
|
|
6
8
|
export declare function restoreMessages(store: SessionStore, sessionId: string, records: readonly import('../../session/store/index.js').SessionRecord[], options: {
|
|
7
9
|
includeImages: boolean;
|
|
8
10
|
strictImages?: boolean;
|
|
@@ -5,6 +5,16 @@ import { compactedHistoryMessages, continuationMessage } from '../compaction/his
|
|
|
5
5
|
import { restoreCompactTail } from '../compaction/retained-context.js';
|
|
6
6
|
import { environmentContextUpdate, restoreEnvironmentState } from './environment.js';
|
|
7
7
|
import { buildContextProjection } from './projection.js';
|
|
8
|
+
export async function readResourceImages(store, sessionId, images) {
|
|
9
|
+
return Promise.all(images.map(async (image) => {
|
|
10
|
+
const resource = image.resourceId === undefined
|
|
11
|
+
? undefined
|
|
12
|
+
: await store.readResource(sessionId, image.resourceId);
|
|
13
|
+
if (!resource)
|
|
14
|
+
throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Session input images cannot be fully restored.');
|
|
15
|
+
return { ...image, dataBase64: resource.dataBase64 };
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
8
18
|
export async function restoreMessages(store, sessionId, records, options) {
|
|
9
19
|
const messages = [];
|
|
10
20
|
const outcomes = new Map();
|
|
@@ -41,8 +51,9 @@ export async function restoreMessages(store, sessionId, records, options) {
|
|
|
41
51
|
try {
|
|
42
52
|
images = await store.readInputImages(sessionId, payload.images);
|
|
43
53
|
if (options.strictImages &&
|
|
44
|
-
payload.images
|
|
45
|
-
|
|
54
|
+
Array.isArray(payload.images) &&
|
|
55
|
+
images.length !==
|
|
56
|
+
payload.images.filter((image) => image?.resourceInputVersion === 1).length)
|
|
46
57
|
throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Session input images cannot be fully restored.');
|
|
47
58
|
}
|
|
48
59
|
catch (error) {
|
|
@@ -145,8 +156,13 @@ export async function restoreMessages(store, sessionId, records, options) {
|
|
|
145
156
|
}
|
|
146
157
|
}
|
|
147
158
|
}
|
|
148
|
-
for (const message of messages.slice(messageStart))
|
|
159
|
+
for (const message of messages.slice(messageStart)) {
|
|
160
|
+
if (!options.includeImages)
|
|
161
|
+
delete message.images;
|
|
162
|
+
else if (options.strictImages && message.images?.length)
|
|
163
|
+
message.images = await readResourceImages(store, sessionId, message.images);
|
|
149
164
|
options.messageSources?.set(message, record);
|
|
165
|
+
}
|
|
150
166
|
}
|
|
151
167
|
const completedToolCalls = new Set(messages.filter((message) => message.role === 'tool').map((message) => message.callId));
|
|
152
168
|
return {
|