@librechat/agents 3.9.3 → 3.9.5
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/README.md +32 -0
- package/dist/cjs/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/events.cjs +11 -0
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +41 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/acceptedModelResponse.cjs +168 -0
- package/dist/cjs/graphs/acceptedModelResponse.cjs.map +1 -0
- package/dist/cjs/llm/invoke.cjs +10 -5
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/streamLimits.cjs +1 -1
- package/dist/cjs/llm/streamLimits.cjs.map +1 -1
- package/dist/cjs/main.cjs +2 -0
- package/dist/cjs/messages/fading.cjs +14 -6
- package/dist/cjs/messages/fading.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +95 -36
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/openai/index.cjs +2 -0
- package/dist/cjs/openai/index.cjs.map +1 -1
- package/dist/cjs/openai/toolProjection.cjs +196 -0
- package/dist/cjs/openai/toolProjection.cjs.map +1 -0
- package/dist/cjs/run.cjs +8 -1
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +1 -1
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/stream.cjs +9 -4
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/ArtifactTruncation.cjs +45 -0
- package/dist/cjs/tools/ArtifactTruncation.cjs.map +1 -0
- package/dist/cjs/tools/BashExecutor.cjs +6 -1
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +6 -1
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +5 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +2 -1
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs +4 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
- package/dist/cjs/utils/acceptedToolArguments.cjs +143 -0
- package/dist/cjs/utils/acceptedToolArguments.cjs.map +1 -0
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/events.mjs +11 -0
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +41 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/acceptedModelResponse.mjs +165 -0
- package/dist/esm/graphs/acceptedModelResponse.mjs.map +1 -0
- package/dist/esm/llm/invoke.mjs +10 -5
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/streamLimits.mjs +1 -1
- package/dist/esm/llm/streamLimits.mjs.map +1 -1
- package/dist/esm/main.mjs +3 -3
- package/dist/esm/messages/fading.mjs +14 -7
- package/dist/esm/messages/fading.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +95 -37
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/openai/index.mjs +2 -1
- package/dist/esm/openai/index.mjs.map +1 -1
- package/dist/esm/openai/toolProjection.mjs +196 -0
- package/dist/esm/openai/toolProjection.mjs.map +1 -0
- package/dist/esm/run.mjs +8 -1
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +1 -1
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/stream.mjs +9 -4
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/ArtifactTruncation.mjs +44 -0
- package/dist/esm/tools/ArtifactTruncation.mjs.map +1 -0
- package/dist/esm/tools/BashExecutor.mjs +6 -1
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +6 -1
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +5 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +2 -1
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs +5 -2
- package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
- package/dist/esm/utils/acceptedToolArguments.mjs +142 -0
- package/dist/esm/utils/acceptedToolArguments.mjs.map +1 -0
- package/dist/types/common/enum.d.ts +4 -0
- package/dist/types/graphs/Graph.d.ts +3 -1
- package/dist/types/graphs/acceptedModelResponse.d.ts +15 -0
- package/dist/types/messages/fading.d.ts +14 -2
- package/dist/types/messages/prune.d.ts +7 -0
- package/dist/types/openai/arguments.d.ts +2 -0
- package/dist/types/openai/index.d.ts +2 -0
- package/dist/types/openai/toolProjection.d.ts +30 -0
- package/dist/types/run.d.ts +1 -0
- package/dist/types/tools/ArtifactTruncation.d.ts +3 -0
- package/dist/types/tools/ToolNode.d.ts +1 -1
- package/dist/types/types/graph.d.ts +5 -3
- package/dist/types/types/run.d.ts +5 -0
- package/dist/types/types/stream.d.ts +22 -1
- package/dist/types/types/tools.d.ts +14 -0
- package/dist/types/utils/acceptedToolArguments.d.ts +10 -0
- package/package.json +1 -1
- package/src/common/enum.ts +5 -0
- package/src/events.ts +24 -1
- package/src/graphs/Graph.ts +95 -0
- package/src/graphs/acceptedModelResponse.ts +307 -0
- package/src/llm/invoke.ts +39 -14
- package/src/llm/streamLimits.ts +1 -1
- package/src/messages/fading.ts +30 -5
- package/src/messages/prune.ts +168 -55
- package/src/openai/arguments.ts +2 -0
- package/src/openai/index.ts +6 -0
- package/src/openai/toolProjection.ts +318 -0
- package/src/run.ts +21 -0
- package/src/session/AgentSession.ts +2 -2
- package/src/stream.ts +21 -1
- package/src/tools/ArtifactTruncation.ts +85 -0
- package/src/tools/BashExecutor.ts +22 -5
- package/src/tools/CodeExecutor.ts +18 -1
- package/src/tools/ProgrammaticToolCalling.ts +17 -3
- package/src/tools/ToolNode.ts +5 -0
- package/src/tools/subagent/SubagentReplay.ts +10 -9
- package/src/types/graph.ts +15 -9
- package/src/types/run.ts +5 -0
- package/src/types/stream.ts +26 -0
- package/src/types/tools.ts +21 -0
- package/src/utils/acceptedToolArguments.ts +204 -0
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
resolveCodeApiAuthHeaders,
|
|
23
23
|
selectRuntimeSessionHint,
|
|
24
24
|
} from './CodeExecutor';
|
|
25
|
-
import { appendExecutionArtifactFileSummary } from './CodeSessionFileSummary';
|
|
26
25
|
import {
|
|
27
26
|
assertUnambiguousIdentifiers,
|
|
28
27
|
projectProgrammaticToolMap,
|
|
@@ -39,10 +38,15 @@ import {
|
|
|
39
38
|
appendArtifactDeliveryWarning,
|
|
40
39
|
normalizeArtifactDeliveryFailure,
|
|
41
40
|
} from '@/tools/ArtifactDelivery';
|
|
41
|
+
import {
|
|
42
|
+
appendArtifactTruncationWarning,
|
|
43
|
+
normalizeArtifactTruncation,
|
|
44
|
+
} from '@/tools/ArtifactTruncation';
|
|
42
45
|
import {
|
|
43
46
|
describeCodeApiError,
|
|
44
47
|
logCodeApiDiagnostic,
|
|
45
48
|
} from '@/tools/diagnostics';
|
|
49
|
+
import { appendExecutionArtifactFileSummary } from './CodeSessionFileSummary';
|
|
46
50
|
import { resolveFetchProxyAgent } from '@/utils/proxy';
|
|
47
51
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
48
52
|
import { Constants } from '@/common';
|
|
@@ -946,11 +950,18 @@ export function formatCompletedResponse(
|
|
|
946
950
|
outputWithReminder,
|
|
947
951
|
artifactDelivery
|
|
948
952
|
);
|
|
953
|
+
const artifactTruncation = normalizeArtifactTruncation(
|
|
954
|
+
response.artifact_truncation
|
|
955
|
+
);
|
|
956
|
+
const outputWithWarnings = appendArtifactTruncationWarning(
|
|
957
|
+
outputWithDeliveryWarning,
|
|
958
|
+
artifactTruncation
|
|
959
|
+
);
|
|
949
960
|
|
|
950
961
|
return [
|
|
951
962
|
filePersistence === 'execution'
|
|
952
|
-
? appendExecutionArtifactFileSummary(
|
|
953
|
-
: appendCodeSessionFileSummary(
|
|
963
|
+
? appendExecutionArtifactFileSummary(outputWithWarnings, response.files)
|
|
964
|
+
: appendCodeSessionFileSummary(outputWithWarnings, response.files),
|
|
954
965
|
{
|
|
955
966
|
session_id: response.session_id,
|
|
956
967
|
files: response.files,
|
|
@@ -960,6 +971,9 @@ export function formatCompletedResponse(
|
|
|
960
971
|
...(artifactDelivery != null
|
|
961
972
|
? { artifact_delivery: artifactDelivery }
|
|
962
973
|
: {}),
|
|
974
|
+
...(artifactTruncation != null
|
|
975
|
+
? { artifact_truncation: artifactTruncation }
|
|
976
|
+
: {}),
|
|
963
977
|
...(response.runtime_session_id != null
|
|
964
978
|
? {
|
|
965
979
|
runtime_session_id: response.runtime_session_id,
|
package/src/tools/ToolNode.ts
CHANGED
|
@@ -1021,6 +1021,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1021
1021
|
preparedSubagents,
|
|
1022
1022
|
restoreRunStepResumeState,
|
|
1023
1023
|
createRunStepResumeState,
|
|
1024
|
+
onToolCallsClaimed,
|
|
1024
1025
|
}: t.ToolNodeConstructorParams) {
|
|
1025
1026
|
super({
|
|
1026
1027
|
name: name ?? TOOL_NODE_RUN_NAME,
|
|
@@ -1155,6 +1156,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1155
1156
|
}
|
|
1156
1157
|
const state = input as T & Pick<t.BaseGraphState, 'runStepState'>;
|
|
1157
1158
|
restoreRunStepResumeState?.(state.runStepState, config);
|
|
1159
|
+
// Freeze replay authority before observers can yield to sibling tasks.
|
|
1160
|
+
if (onToolCallsClaimed != null && assistantBatch?.message.id != null) {
|
|
1161
|
+
await onToolCallsClaimed(assistantBatch.message.id, config);
|
|
1162
|
+
}
|
|
1158
1163
|
let result: T;
|
|
1159
1164
|
try {
|
|
1160
1165
|
result = await this.run(input, config, referenceReplay);
|
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
RunStepResumeState,
|
|
9
9
|
ToolSessionContext,
|
|
10
10
|
} from '@/types';
|
|
11
|
-
import { isFadingTier } from '@/messages/fading';
|
|
11
|
+
import { isFadingTier, isLegacyFadingTier } from '@/messages/fading';
|
|
12
12
|
import {
|
|
13
13
|
attachRunStepResumeState,
|
|
14
14
|
getRunStepResumeState,
|
|
@@ -290,6 +290,11 @@ export function isToolOutputReferenceState(
|
|
|
290
290
|
);
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
/** A current tier, or a legacy one the run then drops so it re-derives. */
|
|
294
|
+
function isStoredFadingTier(value: unknown): boolean {
|
|
295
|
+
return isFadingTier(value) || isLegacyFadingTier(value);
|
|
296
|
+
}
|
|
297
|
+
|
|
293
298
|
function isGraphResumeState(value: unknown): value is SubagentGraphResumeState {
|
|
294
299
|
if (value == null || typeof value !== 'object') {
|
|
295
300
|
return false;
|
|
@@ -306,15 +311,14 @@ function isGraphResumeState(value: unknown): value is SubagentGraphResumeState {
|
|
|
306
311
|
!state.eagerToolUsage.every(isEagerToolUsageState) ||
|
|
307
312
|
!Array.isArray(state.eagerToolSuppressions) ||
|
|
308
313
|
!state.eagerToolSuppressions.every(isString) ||
|
|
309
|
-
(state.runStepState != null &&
|
|
310
|
-
!isRunStepResumeState(state.runStepState)) ||
|
|
314
|
+
(state.runStepState != null && !isRunStepResumeState(state.runStepState)) ||
|
|
311
315
|
(state.toolOutputReferences != null &&
|
|
312
316
|
!isToolOutputReferenceState(state.toolOutputReferences)) ||
|
|
313
|
-
(state.fadingTier != null && !
|
|
317
|
+
(state.fadingTier != null && !isStoredFadingTier(state.fadingTier)) ||
|
|
314
318
|
(state.fadingTiers != null &&
|
|
315
319
|
(typeof state.fadingTiers !== 'object' ||
|
|
316
320
|
Array.isArray(state.fadingTiers) ||
|
|
317
|
-
!Object.values(state.fadingTiers).every(
|
|
321
|
+
!Object.values(state.fadingTiers).every(isStoredFadingTier)))
|
|
318
322
|
) {
|
|
319
323
|
return false;
|
|
320
324
|
}
|
|
@@ -515,10 +519,7 @@ export function attachSubagentResumeManifest(
|
|
|
515
519
|
const runStepState = getRunStepResumeState(payload);
|
|
516
520
|
if (runStepState != null) {
|
|
517
521
|
return attachRunStepResumeState(
|
|
518
|
-
attachSubagentResumeManifest(
|
|
519
|
-
stripRunStepResumeState(payload),
|
|
520
|
-
manifest
|
|
521
|
-
),
|
|
522
|
+
attachSubagentResumeManifest(stripRunStepResumeState(payload), manifest),
|
|
522
523
|
runStepState
|
|
523
524
|
);
|
|
524
525
|
}
|
package/src/types/graph.ts
CHANGED
|
@@ -10,22 +10,24 @@ import type { START, StateGraph, StateGraphArgs } from '@langchain/langgraph';
|
|
|
10
10
|
import type { RunnableConfig, Runnable } from '@langchain/core/runnables';
|
|
11
11
|
import type { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
12
12
|
import type { GoogleAIToolType } from '@langchain/google-common';
|
|
13
|
-
import type {
|
|
14
|
-
SummarizationNodeInput,
|
|
15
|
-
SummarizeCompleteEvent,
|
|
16
|
-
CompactionSemanticIndex,
|
|
17
|
-
SummarizationConfig,
|
|
18
|
-
SummarizeStartEvent,
|
|
19
|
-
SummarizeDeltaEvent,
|
|
20
|
-
} from '@/types/summarize';
|
|
21
13
|
import type {
|
|
22
14
|
RunStep,
|
|
15
|
+
ModelResponseEvent,
|
|
16
|
+
ModelToolsClaimedEvent,
|
|
23
17
|
RunStepDeltaEvent,
|
|
24
18
|
RunStepResumeState,
|
|
25
19
|
RunStepClosedEvent,
|
|
26
20
|
MessageDeltaEvent,
|
|
27
21
|
ReasoningDeltaEvent,
|
|
28
22
|
} from '@/types/stream';
|
|
23
|
+
import type {
|
|
24
|
+
SummarizationNodeInput,
|
|
25
|
+
SummarizeCompleteEvent,
|
|
26
|
+
CompactionSemanticIndex,
|
|
27
|
+
SummarizationConfig,
|
|
28
|
+
SummarizeStartEvent,
|
|
29
|
+
SummarizeDeltaEvent,
|
|
30
|
+
} from '@/types/summarize';
|
|
29
31
|
import type {
|
|
30
32
|
ToolMap,
|
|
31
33
|
ToolSessionMap,
|
|
@@ -183,7 +185,7 @@ export interface ContextUsageEvent {
|
|
|
183
185
|
* `RunConfig.fadingTiers[agentId]`.
|
|
184
186
|
*/
|
|
185
187
|
export interface FadingTier {
|
|
186
|
-
v:
|
|
188
|
+
v: 2;
|
|
187
189
|
/** Token budget the caps derive from, in raw token space. Never grows;
|
|
188
190
|
* clamped to the current context window when seeded. */
|
|
189
191
|
budgetTokens: number;
|
|
@@ -202,6 +204,8 @@ export interface EventHandler {
|
|
|
202
204
|
data:
|
|
203
205
|
| StreamEventData
|
|
204
206
|
| ModelEndData
|
|
207
|
+
| ModelResponseEvent
|
|
208
|
+
| ModelToolsClaimedEvent
|
|
205
209
|
| RunStep
|
|
206
210
|
| RunStepDeltaEvent
|
|
207
211
|
| RunStepClosedEvent
|
|
@@ -406,6 +410,8 @@ export type StandardGraphInput = {
|
|
|
406
410
|
agents: AgentInputs[];
|
|
407
411
|
/** Execution backend used to resolve the effective tool registry. */
|
|
408
412
|
toolExecution?: ToolExecutionConfig;
|
|
413
|
+
/** Trusted single-agent client delegation policy; mixed batches fail closed. */
|
|
414
|
+
clientDelegatedToolNames?: readonly string[];
|
|
409
415
|
langfuse?: LangfuseConfig;
|
|
410
416
|
tokenCounter?: TokenCounter;
|
|
411
417
|
indexTokenCountMap?: Record<string, number>;
|
package/src/types/run.ts
CHANGED
|
@@ -279,6 +279,11 @@ export type RunConfig = {
|
|
|
279
279
|
*/
|
|
280
280
|
langfuse?: g.LangfuseConfig;
|
|
281
281
|
customHandlers?: Record<string, g.EventHandler>;
|
|
282
|
+
/** Explicit client-owned tools for a single-agent graph. A batch mixing
|
|
283
|
+
* client and SDK/provider calls fails closed; omitted means SDK ownership.
|
|
284
|
+
* Hosts must register the corresponding model-facing tool schemas.
|
|
285
|
+
*/
|
|
286
|
+
clientDelegatedToolNames?: readonly string[];
|
|
282
287
|
/**
|
|
283
288
|
* Receives token usage for every model call made inside subagent child
|
|
284
289
|
* runs (including nested subagents). Child graphs execute via `invoke()`
|
package/src/types/stream.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/types/stream.ts
|
|
2
2
|
import type {
|
|
3
|
+
AIMessageChunk,
|
|
3
4
|
MessageContentImageUrl,
|
|
4
5
|
MessageContentText,
|
|
5
6
|
ToolMessage,
|
|
@@ -15,6 +16,24 @@ import type { SummarizeCompleteEvent } from '@/types/summarize';
|
|
|
15
16
|
import type { ToolEndEvent } from '@/types/tools';
|
|
16
17
|
import { StepTypes, ContentTypes, GraphEvents } from '@/common/enum';
|
|
17
18
|
|
|
19
|
+
/** One accepted model result, detached from execution state before host dispatch.
|
|
20
|
+
* Provider chunks, failed attempts and UI run-step events are not this contract. */
|
|
21
|
+
export interface ModelResponseEvent {
|
|
22
|
+
type: 'model_response';
|
|
23
|
+
/** Graph-generated acceptance ID, not a provider ID or run-step index. */
|
|
24
|
+
id: string;
|
|
25
|
+
agentId: string;
|
|
26
|
+
/** Graph-state message identity used to correlate ToolNode ownership. */
|
|
27
|
+
messageId?: string;
|
|
28
|
+
toolCalls: ReadonlyArray<ToolCall>;
|
|
29
|
+
/** Same index as toolCalls. Only a trusted graph decision of 'client'
|
|
30
|
+
* permits this call on the OpenAI client wire; absence fails closed. */
|
|
31
|
+
toolCallDispositions: ReadonlyArray<'sdk' | 'provider' | 'client'>;
|
|
32
|
+
invalidToolCalls: ReadonlyArray<
|
|
33
|
+
NonNullable<AIMessageChunk['invalid_tool_calls']>[number]
|
|
34
|
+
>;
|
|
35
|
+
}
|
|
36
|
+
|
|
18
37
|
export type HandleLLMEnd = (
|
|
19
38
|
output: LLMResult,
|
|
20
39
|
runId: string,
|
|
@@ -542,3 +561,10 @@ export type ContentAggregatorResult = {
|
|
|
542
561
|
contentParts: Array<MessageContentComplex | undefined>;
|
|
543
562
|
aggregateContent: ContentAggregator;
|
|
544
563
|
};
|
|
564
|
+
|
|
565
|
+
/** Ownership, not successful completion. Interrupted/failed batches remain graph-owned. */
|
|
566
|
+
export interface ModelToolsClaimedEvent {
|
|
567
|
+
type: 'model_tools_claimed';
|
|
568
|
+
agentId: string;
|
|
569
|
+
messageId: string;
|
|
570
|
+
}
|
package/src/types/tools.ts
CHANGED
|
@@ -313,6 +313,8 @@ export type ToolNodeOptions = {
|
|
|
313
313
|
) => void;
|
|
314
314
|
/** SDK-owned checkpoint snapshot for open run-step lifecycle state. */
|
|
315
315
|
createRunStepResumeState?: () => RunStepResumeState;
|
|
316
|
+
/** Internal ownership bridge, awaited before ToolNode can execute a batch. */
|
|
317
|
+
onToolCallsClaimed?: (messageId: string, config: RunnableConfig) => Promise<void>;
|
|
316
318
|
};
|
|
317
319
|
|
|
318
320
|
export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
|
|
@@ -495,6 +497,21 @@ export type ArtifactDeliveryFailure = {
|
|
|
495
497
|
failed: number;
|
|
496
498
|
};
|
|
497
499
|
|
|
500
|
+
export type ArtifactTruncationReason =
|
|
501
|
+
| 'max_files'
|
|
502
|
+
| 'depth'
|
|
503
|
+
| 'size'
|
|
504
|
+
| 'path'
|
|
505
|
+
| 'unreadable';
|
|
506
|
+
|
|
507
|
+
export type ArtifactTruncation = {
|
|
508
|
+
code: 'artifact_truncated';
|
|
509
|
+
reasons: Partial<Record<ArtifactTruncationReason, number>>;
|
|
510
|
+
/** Up to 20 relative paths omitted from the response. */
|
|
511
|
+
skipped: string[];
|
|
512
|
+
skipped_count: number;
|
|
513
|
+
};
|
|
514
|
+
|
|
498
515
|
export type ExecuteResult = {
|
|
499
516
|
/**
|
|
500
517
|
* Execution session id — the (transient) sandbox run that produced
|
|
@@ -508,6 +525,7 @@ export type ExecuteResult = {
|
|
|
508
525
|
/** Persisted input paths explicitly removed during this execution. */
|
|
509
526
|
deleted_files?: string[];
|
|
510
527
|
artifact_delivery?: ArtifactDeliveryFailure;
|
|
528
|
+
artifact_truncation?: ArtifactTruncation;
|
|
511
529
|
/**
|
|
512
530
|
* Durable runtime session id echoed by a stateful Code API backend
|
|
513
531
|
* (hash of tenant+user+hint). Additive; absent on stateless servers.
|
|
@@ -1354,6 +1372,7 @@ export type ProgrammaticExecutionResponse = {
|
|
|
1354
1372
|
/** Persisted input paths explicitly removed during this execution. */
|
|
1355
1373
|
deleted_files?: string[];
|
|
1356
1374
|
artifact_delivery?: ArtifactDeliveryFailure;
|
|
1375
|
+
artifact_truncation?: ArtifactTruncation;
|
|
1357
1376
|
|
|
1358
1377
|
/** Durable runtime session echo from a stateful backend (additive). */
|
|
1359
1378
|
runtime_session_id?: string;
|
|
@@ -1373,6 +1392,7 @@ export type ProgrammaticExecutionArtifact = {
|
|
|
1373
1392
|
/** Persisted input paths explicitly removed during this execution. */
|
|
1374
1393
|
deleted_files?: string[];
|
|
1375
1394
|
artifact_delivery?: ArtifactDeliveryFailure;
|
|
1395
|
+
artifact_truncation?: ArtifactTruncation;
|
|
1376
1396
|
/** Durable runtime session echo from a stateful backend (additive). */
|
|
1377
1397
|
runtime_session_id?: string;
|
|
1378
1398
|
runtime_status?: 'new' | 'reused';
|
|
@@ -1455,6 +1475,7 @@ export type CodeExecutionArtifact = {
|
|
|
1455
1475
|
/** Persisted input paths explicitly removed during this execution. */
|
|
1456
1476
|
deleted_files?: string[];
|
|
1457
1477
|
artifact_delivery?: ArtifactDeliveryFailure;
|
|
1478
|
+
artifact_truncation?: ArtifactTruncation;
|
|
1458
1479
|
/** Durable runtime session echo from a stateful backend (additive). */
|
|
1459
1480
|
runtime_session_id?: string;
|
|
1460
1481
|
runtime_status?: 'new' | 'reused';
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { types } from 'node:util';
|
|
2
|
+
|
|
3
|
+
const MAX_ARGUMENT_DEPTH = 64;
|
|
4
|
+
const INVALID = 'Accepted tool call arguments are not JSON serializable';
|
|
5
|
+
const LIMIT = 'Tool projection buffer limit exceeded';
|
|
6
|
+
|
|
7
|
+
function invalid(): never {
|
|
8
|
+
throw new Error(INVALID);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** structuredClone and VM providers may return objects from another realm. */
|
|
12
|
+
function hasJSONPrototype(value: object, array: boolean): boolean {
|
|
13
|
+
const proto: unknown = Object.getPrototypeOf(value);
|
|
14
|
+
if (proto === null) return true;
|
|
15
|
+
if (typeof proto !== 'object' || types.isProxy(proto)) return false;
|
|
16
|
+
const ctor: unknown = Object.getOwnPropertyDescriptor(
|
|
17
|
+
proto,
|
|
18
|
+
'constructor'
|
|
19
|
+
)?.value;
|
|
20
|
+
if (typeof ctor !== 'function' || types.isProxy(ctor)) return false;
|
|
21
|
+
return (
|
|
22
|
+
Object.getOwnPropertyDescriptor(ctor, 'prototype')?.value === proto &&
|
|
23
|
+
Function.prototype.toString.call(ctor) ===
|
|
24
|
+
Function.prototype.toString.call(array ? Array : Object)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Encode JSON data, not arbitrary JS values. No getters, proxies or toJSON hooks run.
|
|
29
|
+
* Repeated references expand like JSON (and count against the budget); cycles fail.
|
|
30
|
+
* Limits bound output and traversal, including deeply nested/alias-heavy input.
|
|
31
|
+
*/
|
|
32
|
+
export function serializeToolArguments(
|
|
33
|
+
value: unknown,
|
|
34
|
+
maxBytes: number
|
|
35
|
+
): string {
|
|
36
|
+
if (maxBytes < 2) throw new Error(LIMIT);
|
|
37
|
+
const parts: string[] = [];
|
|
38
|
+
const ancestors = new Set<object>();
|
|
39
|
+
let remaining = maxBytes;
|
|
40
|
+
const append = (text: string): void => {
|
|
41
|
+
remaining -= Buffer.byteLength(text, 'utf8');
|
|
42
|
+
if (remaining < 0) throw new Error(LIMIT);
|
|
43
|
+
parts.push(text);
|
|
44
|
+
};
|
|
45
|
+
const string = (text: string): void => {
|
|
46
|
+
// Every UTF-16 unit needs at least one encoded byte. Check before allocating escapes.
|
|
47
|
+
if (text.length + 2 > remaining) throw new Error(LIMIT);
|
|
48
|
+
append(JSON.stringify(text));
|
|
49
|
+
};
|
|
50
|
+
const encode = (input: unknown, depth: number): void => {
|
|
51
|
+
if (depth > MAX_ARGUMENT_DEPTH) invalid();
|
|
52
|
+
if (input === null) {
|
|
53
|
+
append('null');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (typeof input === 'string') {
|
|
57
|
+
string(input);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (typeof input === 'boolean') {
|
|
61
|
+
append(input ? 'true' : 'false');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (typeof input === 'number') {
|
|
65
|
+
if (!Number.isFinite(input)) invalid();
|
|
66
|
+
append(Object.is(input, -0) ? '-0' : String(input));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (typeof input !== 'object') invalid();
|
|
70
|
+
if (types.isProxy(input) || ancestors.has(input)) invalid();
|
|
71
|
+
const array = Array.isArray(input);
|
|
72
|
+
if (!hasJSONPrototype(input, array)) invalid();
|
|
73
|
+
const keys = Reflect.ownKeys(input);
|
|
74
|
+
// At least one byte per entry, plus punctuation. Bound descriptor work up front.
|
|
75
|
+
if (keys.length > remaining + (array ? 1 : 0)) throw new Error(LIMIT);
|
|
76
|
+
ancestors.add(input);
|
|
77
|
+
if (array) {
|
|
78
|
+
// Dense arrays only, with no symbols, hidden entries or extra named properties.
|
|
79
|
+
if (keys.length !== input.length + 1) invalid();
|
|
80
|
+
append('[');
|
|
81
|
+
for (let i = 0; i < input.length; i++) {
|
|
82
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, String(i));
|
|
83
|
+
if (
|
|
84
|
+
descriptor == null ||
|
|
85
|
+
descriptor.enumerable !== true ||
|
|
86
|
+
!('value' in descriptor)
|
|
87
|
+
)
|
|
88
|
+
invalid();
|
|
89
|
+
if (i !== 0) append(',');
|
|
90
|
+
encode(descriptor.value, depth + 1);
|
|
91
|
+
}
|
|
92
|
+
append(']');
|
|
93
|
+
} else {
|
|
94
|
+
append('{');
|
|
95
|
+
for (let i = 0; i < keys.length; i++) {
|
|
96
|
+
const key = keys[i];
|
|
97
|
+
if (typeof key !== 'string') invalid();
|
|
98
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
99
|
+
if (
|
|
100
|
+
descriptor == null ||
|
|
101
|
+
descriptor.enumerable !== true ||
|
|
102
|
+
!('value' in descriptor)
|
|
103
|
+
)
|
|
104
|
+
invalid();
|
|
105
|
+
if (i !== 0) append(',');
|
|
106
|
+
string(key);
|
|
107
|
+
append(':');
|
|
108
|
+
encode(descriptor.value, depth + 1);
|
|
109
|
+
}
|
|
110
|
+
append('}');
|
|
111
|
+
}
|
|
112
|
+
ancestors.delete(input);
|
|
113
|
+
};
|
|
114
|
+
if (value != null && typeof value === 'object' && types.isProxy(value))
|
|
115
|
+
invalid();
|
|
116
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
117
|
+
throw new Error('Accepted tool call arguments must be an object');
|
|
118
|
+
}
|
|
119
|
+
encode(value, 0);
|
|
120
|
+
return parts.join('');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Detach JSON data without encoding it. Unlike projection, invocation has no
|
|
124
|
+
* formatting budget: preserve shared references and traverse iteratively so a
|
|
125
|
+
* deep or alias-heavy object cannot expand exponentially during validation.
|
|
126
|
+
*/
|
|
127
|
+
export function cloneToolArguments(value: unknown): Record<string, unknown> {
|
|
128
|
+
const copies = new Map<object, object>();
|
|
129
|
+
const ancestors = new Set<object>();
|
|
130
|
+
const stack: Array<{
|
|
131
|
+
input: object;
|
|
132
|
+
output: object;
|
|
133
|
+
keys: (string | symbol)[];
|
|
134
|
+
index: number;
|
|
135
|
+
}> = [];
|
|
136
|
+
const copy = (input: unknown): unknown => {
|
|
137
|
+
if (
|
|
138
|
+
input === null ||
|
|
139
|
+
typeof input === 'string' ||
|
|
140
|
+
typeof input === 'boolean'
|
|
141
|
+
)
|
|
142
|
+
return input;
|
|
143
|
+
if (typeof input === 'number') {
|
|
144
|
+
if (!Number.isFinite(input)) invalid();
|
|
145
|
+
return input;
|
|
146
|
+
}
|
|
147
|
+
if (
|
|
148
|
+
typeof input !== 'object' ||
|
|
149
|
+
types.isProxy(input) ||
|
|
150
|
+
ancestors.has(input)
|
|
151
|
+
)
|
|
152
|
+
invalid();
|
|
153
|
+
const existing = copies.get(input);
|
|
154
|
+
if (existing != null) return existing;
|
|
155
|
+
const array = Array.isArray(input);
|
|
156
|
+
if (!hasJSONPrototype(input, array)) invalid();
|
|
157
|
+
const keys = Reflect.ownKeys(input);
|
|
158
|
+
if (array && keys.length !== input.length + 1) invalid();
|
|
159
|
+
const output: object = array ? [] : {};
|
|
160
|
+
copies.set(input, output);
|
|
161
|
+
ancestors.add(input);
|
|
162
|
+
stack.push({
|
|
163
|
+
input,
|
|
164
|
+
output,
|
|
165
|
+
keys: array ? keys.filter((key) => key !== 'length') : keys,
|
|
166
|
+
index: 0,
|
|
167
|
+
});
|
|
168
|
+
return output;
|
|
169
|
+
};
|
|
170
|
+
if (
|
|
171
|
+
value == null ||
|
|
172
|
+
typeof value !== 'object' ||
|
|
173
|
+
types.isProxy(value) ||
|
|
174
|
+
Array.isArray(value)
|
|
175
|
+
)
|
|
176
|
+
invalid();
|
|
177
|
+
const result = copy(value) as Record<string, unknown>;
|
|
178
|
+
while (stack.length > 0) {
|
|
179
|
+
const frame = stack[stack.length - 1];
|
|
180
|
+
if (frame.index === frame.keys.length) {
|
|
181
|
+
ancestors.delete(frame.input);
|
|
182
|
+
stack.pop();
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const key = frame.keys[frame.index++];
|
|
186
|
+
if (typeof key !== 'string') invalid();
|
|
187
|
+
if (Array.isArray(frame.input) && key !== String(frame.index - 1))
|
|
188
|
+
invalid();
|
|
189
|
+
const descriptor = Object.getOwnPropertyDescriptor(frame.input, key);
|
|
190
|
+
if (
|
|
191
|
+
descriptor == null ||
|
|
192
|
+
!('value' in descriptor) ||
|
|
193
|
+
descriptor.enumerable !== true
|
|
194
|
+
)
|
|
195
|
+
invalid();
|
|
196
|
+
Object.defineProperty(frame.output, key, {
|
|
197
|
+
value: copy(descriptor.value),
|
|
198
|
+
enumerable: true,
|
|
199
|
+
writable: true,
|
|
200
|
+
configurable: true,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
}
|