@librechat/agents 4.0.1 → 4.0.3
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/cjs/agents/AgentContext.cjs +3 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/decisions/deadline.cjs +52 -0
- package/dist/cjs/decisions/deadline.cjs.map +1 -0
- package/dist/cjs/decisions/dialect.cjs +88 -0
- package/dist/cjs/decisions/dialect.cjs.map +1 -0
- package/dist/cjs/decisions/http.cjs +77 -0
- package/dist/cjs/decisions/http.cjs.map +1 -0
- package/dist/cjs/decisions/index.cjs +7 -0
- package/dist/cjs/decisions/presets.cjs +82 -0
- package/dist/cjs/decisions/presets.cjs.map +1 -0
- package/dist/cjs/decisions/questions.cjs +70 -0
- package/dist/cjs/decisions/questions.cjs.map +1 -0
- package/dist/cjs/decisions/structuredChat.cjs +172 -0
- package/dist/cjs/decisions/structuredChat.cjs.map +1 -0
- package/dist/cjs/decisions/structuredOutput.cjs +18 -0
- package/dist/cjs/decisions/structuredOutput.cjs.map +1 -0
- package/dist/cjs/decisions/traceMarker.cjs +6 -0
- package/dist/cjs/decisions/traceMarker.cjs.map +1 -0
- package/dist/cjs/decisions/transport.cjs +166 -0
- package/dist/cjs/decisions/transport.cjs.map +1 -0
- package/dist/cjs/decisions/types.cjs +44 -0
- package/dist/cjs/decisions/types.cjs.map +1 -0
- package/dist/cjs/graphs/Graph.cjs +28 -9
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +6 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs +1 -1
- package/dist/cjs/llm/invoke.cjs +34 -12
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +7 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/providerTextBoundary.cjs +355 -0
- package/dist/cjs/llm/providerTextBoundary.cjs.map +1 -0
- package/dist/cjs/llm/providerTextChunk.cjs +14 -0
- package/dist/cjs/llm/providerTextChunk.cjs.map +1 -0
- package/dist/cjs/llm/providerTextControls.cjs +236 -0
- package/dist/cjs/llm/providerTextControls.cjs.map +1 -0
- package/dist/cjs/llm/stream/smoother.cjs +2 -1
- package/dist/cjs/llm/stream/smoother.cjs.map +1 -1
- package/dist/cjs/main.cjs +49 -1
- package/dist/cjs/protection/providerText.cjs +224 -0
- package/dist/cjs/protection/providerText.cjs.map +1 -0
- package/dist/cjs/protection/providerTextInput.cjs +25 -0
- package/dist/cjs/protection/providerTextInput.cjs.map +1 -0
- package/dist/cjs/rerank/index.cjs +2 -0
- package/dist/cjs/rerank/search.cjs +45 -0
- package/dist/cjs/rerank/search.cjs.map +1 -0
- package/dist/cjs/rerank/systemone.cjs +70 -0
- package/dist/cjs/rerank/systemone.cjs.map +1 -0
- package/dist/cjs/run.cjs +11 -1
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +42 -14
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +26 -8
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/SubagentTool.cjs +11 -3
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +6 -5
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs +1 -0
- package/dist/cjs/tools/search/search.cjs +3 -3
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +4 -3
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +13 -2
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +149 -26
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs +4 -2
- package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs +3 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
- package/dist/cjs/tools/subagent/diagnostics.cjs +88 -2
- package/dist/cjs/tools/subagent/diagnostics.cjs.map +1 -1
- package/dist/cjs/tools/subagent/hostArgs.cjs +214 -0
- package/dist/cjs/tools/subagent/hostArgs.cjs.map +1 -0
- package/dist/cjs/tools/subagent/index.cjs +2 -0
- package/dist/cjs/tools/toolErrorContent.cjs +1 -1
- package/dist/esm/agents/AgentContext.mjs +3 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/decisions/deadline.mjs +52 -0
- package/dist/esm/decisions/deadline.mjs.map +1 -0
- package/dist/esm/decisions/dialect.mjs +87 -0
- package/dist/esm/decisions/dialect.mjs.map +1 -0
- package/dist/esm/decisions/http.mjs +75 -0
- package/dist/esm/decisions/http.mjs.map +1 -0
- package/dist/esm/decisions/index.mjs +8 -0
- package/dist/esm/decisions/presets.mjs +77 -0
- package/dist/esm/decisions/presets.mjs.map +1 -0
- package/dist/esm/decisions/questions.mjs +67 -0
- package/dist/esm/decisions/questions.mjs.map +1 -0
- package/dist/esm/decisions/structuredChat.mjs +172 -0
- package/dist/esm/decisions/structuredChat.mjs.map +1 -0
- package/dist/esm/decisions/structuredOutput.mjs +18 -0
- package/dist/esm/decisions/structuredOutput.mjs.map +1 -0
- package/dist/esm/decisions/traceMarker.mjs +6 -0
- package/dist/esm/decisions/traceMarker.mjs.map +1 -0
- package/dist/esm/decisions/transport.mjs +164 -0
- package/dist/esm/decisions/transport.mjs.map +1 -0
- package/dist/esm/decisions/types.mjs +39 -0
- package/dist/esm/decisions/types.mjs.map +1 -0
- package/dist/esm/graphs/Graph.mjs +28 -9
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +6 -1
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +34 -12
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +7 -0
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/providerTextBoundary.mjs +355 -0
- package/dist/esm/llm/providerTextBoundary.mjs.map +1 -0
- package/dist/esm/llm/providerTextChunk.mjs +13 -0
- package/dist/esm/llm/providerTextChunk.mjs.map +1 -0
- package/dist/esm/llm/providerTextControls.mjs +230 -0
- package/dist/esm/llm/providerTextControls.mjs.map +1 -0
- package/dist/esm/llm/stream/smoother.mjs +2 -1
- package/dist/esm/llm/stream/smoother.mjs.map +1 -1
- package/dist/esm/main.mjs +16 -2
- package/dist/esm/protection/providerText.mjs +218 -0
- package/dist/esm/protection/providerText.mjs.map +1 -0
- package/dist/esm/protection/providerTextInput.mjs +24 -0
- package/dist/esm/protection/providerTextInput.mjs.map +1 -0
- package/dist/esm/rerank/index.mjs +3 -0
- package/dist/esm/rerank/search.mjs +45 -0
- package/dist/esm/rerank/search.mjs.map +1 -0
- package/dist/esm/rerank/systemone.mjs +70 -0
- package/dist/esm/rerank/systemone.mjs.map +1 -0
- package/dist/esm/run.mjs +11 -1
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +42 -14
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +26 -8
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/SubagentTool.mjs +11 -3
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +6 -5
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs +1 -1
- package/dist/esm/tools/search/search.mjs +3 -3
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +4 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +13 -2
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +149 -26
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs +4 -2
- package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs +3 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
- package/dist/esm/tools/subagent/diagnostics.mjs +84 -3
- package/dist/esm/tools/subagent/diagnostics.mjs.map +1 -1
- package/dist/esm/tools/subagent/hostArgs.mjs +207 -0
- package/dist/esm/tools/subagent/hostArgs.mjs.map +1 -0
- package/dist/esm/tools/subagent/index.mjs +2 -0
- package/dist/esm/tools/toolErrorContent.mjs +1 -1
- package/dist/types/decisions/deadline.d.ts +3 -0
- package/dist/types/decisions/dialect.d.ts +11 -0
- package/dist/types/decisions/http.d.ts +22 -0
- package/dist/types/decisions/index.d.ts +10 -0
- package/dist/types/decisions/presets.d.ts +17 -0
- package/dist/types/decisions/questions.d.ts +8 -0
- package/dist/types/decisions/structuredChat.d.ts +17 -0
- package/dist/types/decisions/structuredOutput.d.ts +4 -0
- package/dist/types/decisions/traceMarker.d.ts +2 -0
- package/dist/types/decisions/transport.d.ts +29 -0
- package/dist/types/decisions/types.d.ts +119 -0
- package/dist/types/graphs/Graph.d.ts +7 -3
- package/dist/types/index.d.ts +4 -0
- package/dist/types/llm/invoke.d.ts +4 -1
- package/dist/types/llm/providerTextBoundary.d.ts +4 -0
- package/dist/types/llm/providerTextChunk.d.ts +7 -0
- package/dist/types/llm/providerTextControls.d.ts +18 -0
- package/dist/types/protection/providerText.d.ts +77 -0
- package/dist/types/protection/providerTextInput.d.ts +3 -0
- package/dist/types/rerank/index.d.ts +4 -0
- package/dist/types/rerank/search.d.ts +7 -0
- package/dist/types/rerank/systemone.d.ts +13 -0
- package/dist/types/rerank/types.d.ts +24 -0
- package/dist/types/run.d.ts +2 -0
- package/dist/types/tools/BashExecutor.d.ts +4 -0
- package/dist/types/tools/CodeExecutor.d.ts +4 -0
- package/dist/types/tools/ReadFile.d.ts +4 -0
- package/dist/types/tools/SkillTool.d.ts +4 -0
- package/dist/types/tools/SubagentTool.d.ts +2 -0
- package/dist/types/tools/ToolSearch.d.ts +4 -0
- package/dist/types/tools/search/schema.d.ts +4 -0
- package/dist/types/tools/search/types.d.ts +3 -1
- package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +6 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +22 -1
- package/dist/types/tools/subagent/SubagentReplay.d.ts +3 -1
- package/dist/types/tools/subagent/diagnostics.d.ts +56 -8
- package/dist/types/tools/subagent/hostArgs.d.ts +57 -0
- package/dist/types/tools/subagent/index.d.ts +3 -0
- package/dist/types/types/graph.d.ts +39 -0
- package/dist/types/types/run.d.ts +6 -2
- package/dist/types/types/tools.d.ts +2 -0
- package/package.json +4 -4
- package/src/agents/AgentContext.ts +3 -2
- package/src/decisions/deadline.ts +92 -0
- package/src/decisions/dialect.ts +200 -0
- package/src/decisions/http.ts +175 -0
- package/src/decisions/index.ts +14 -0
- package/src/decisions/presets.ts +111 -0
- package/src/decisions/questions.ts +128 -0
- package/src/decisions/structuredChat.ts +358 -0
- package/src/decisions/structuredOutput.ts +25 -0
- package/src/decisions/traceMarker.ts +2 -0
- package/src/decisions/transport.ts +335 -0
- package/src/decisions/types.ts +193 -0
- package/src/graphs/Graph.ts +57 -9
- package/src/index.ts +9 -0
- package/src/langfuseToolOutputTracing.ts +8 -1
- package/src/llm/invoke.ts +48 -7
- package/src/llm/openai/index.ts +8 -0
- package/src/llm/providerTextBoundary.ts +446 -0
- package/src/llm/providerTextChunk.ts +15 -0
- package/src/llm/providerTextControls.ts +173 -0
- package/src/llm/stream/smoother.ts +4 -2
- package/src/protection/providerText.ts +325 -0
- package/src/protection/providerTextInput.ts +21 -0
- package/src/rerank/index.ts +4 -0
- package/src/rerank/search.ts +85 -0
- package/src/rerank/systemone.ts +140 -0
- package/src/rerank/types.ts +28 -0
- package/src/run.ts +13 -0
- package/src/stream.ts +33 -7
- package/src/summarization/node.ts +32 -14
- package/src/tools/SubagentTool.ts +14 -3
- package/src/tools/ToolNode.ts +7 -3
- package/src/tools/search/search.ts +4 -4
- package/src/tools/search/tool.ts +27 -25
- package/src/tools/search/types.ts +4 -1
- package/src/tools/subagent/SubagentExecutionRegistry.ts +27 -2
- package/src/tools/subagent/SubagentExecutor.ts +323 -28
- package/src/tools/subagent/SubagentReplay.ts +10 -3
- package/src/tools/subagent/childGraphConfig.ts +12 -1
- package/src/tools/subagent/diagnostics.ts +180 -9
- package/src/tools/subagent/hostArgs.ts +413 -0
- package/src/tools/subagent/index.ts +20 -0
- package/src/types/graph.ts +44 -0
- package/src/types/run.ts +6 -2
- package/src/types/tools.ts +6 -1
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import type { AwaitWithinDeadline } from './deadline';
|
|
2
|
+
import type { DecisionCredential } from './types';
|
|
3
|
+
import { withDecisionDeadline } from './deadline';
|
|
4
|
+
import { DecisionError } from './types';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 4_000;
|
|
7
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
8
|
+
const MAX_RESPONSE_BYTES = 256 * 1024;
|
|
9
|
+
const BACKOFF_MS = [250, 750, 1_500, 3_000, 6_000];
|
|
10
|
+
const MAX_RETRY_AFTER_MS = 10_000;
|
|
11
|
+
|
|
12
|
+
export type DecisionFetch = (
|
|
13
|
+
input: string,
|
|
14
|
+
init: {
|
|
15
|
+
method: string;
|
|
16
|
+
headers: Record<string, string>;
|
|
17
|
+
body: string;
|
|
18
|
+
signal: AbortSignal;
|
|
19
|
+
redirect: 'error';
|
|
20
|
+
}
|
|
21
|
+
) => Promise<Pick<Response, 'ok' | 'status' | 'headers' | 'body'>>;
|
|
22
|
+
|
|
23
|
+
export interface TransportOptions {
|
|
24
|
+
providerId: string;
|
|
25
|
+
apiKey?: DecisionCredential;
|
|
26
|
+
requiresAuth?: boolean;
|
|
27
|
+
/** Full URL, not a base path. */
|
|
28
|
+
endpoint: string;
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
maxRetries?: number;
|
|
31
|
+
fetch?: DecisionFetch;
|
|
32
|
+
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
33
|
+
/** Called only after the response parses, with no state, answers, or credentials. */
|
|
34
|
+
onAnswered?: (label: string, ms: number) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type Transport = <T>(
|
|
38
|
+
prepare: () => { payload: string; parse: (body: string) => T },
|
|
39
|
+
signal: AbortSignal | undefined,
|
|
40
|
+
label: string,
|
|
41
|
+
timeoutOverrideMs?: number
|
|
42
|
+
) => Promise<T>;
|
|
43
|
+
|
|
44
|
+
export function failureForStatus(
|
|
45
|
+
status: number
|
|
46
|
+
): 'unauthorized' | 'rate_limited' | 'server_error' | 'bad_request' {
|
|
47
|
+
if (status === 401 || status === 403) {
|
|
48
|
+
return 'unauthorized';
|
|
49
|
+
}
|
|
50
|
+
if (status === 429) {
|
|
51
|
+
return 'rate_limited';
|
|
52
|
+
}
|
|
53
|
+
if (status >= 500) {
|
|
54
|
+
return 'server_error';
|
|
55
|
+
}
|
|
56
|
+
return 'bad_request';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function retryAfterMs(header: string | null): number | undefined {
|
|
60
|
+
if (header == null || header === '') {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const seconds = Number(header);
|
|
64
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
65
|
+
return Math.min(seconds * 1_000, MAX_RETRY_AFTER_MS);
|
|
66
|
+
}
|
|
67
|
+
const at = Date.parse(header);
|
|
68
|
+
return Number.isFinite(at)
|
|
69
|
+
? Math.min(Math.max(at - Date.now(), 0), MAX_RETRY_AFTER_MS)
|
|
70
|
+
: undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function defaultSleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
74
|
+
if (signal?.aborted === true) {
|
|
75
|
+
return Promise.resolve();
|
|
76
|
+
}
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
const done = (): void => {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
signal?.removeEventListener('abort', done);
|
|
81
|
+
resolve();
|
|
82
|
+
};
|
|
83
|
+
const timer = setTimeout(done, ms);
|
|
84
|
+
signal?.addEventListener('abort', done, { once: true });
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Stream successful bodies only, so neither errors nor oversized replies are ever buffered. */
|
|
89
|
+
async function readResponse(
|
|
90
|
+
body: ReadableStream<Uint8Array> | null,
|
|
91
|
+
waitFor: AwaitWithinDeadline,
|
|
92
|
+
provider: string
|
|
93
|
+
): Promise<string> {
|
|
94
|
+
if (!body) {
|
|
95
|
+
return '';
|
|
96
|
+
}
|
|
97
|
+
const reader = body.getReader();
|
|
98
|
+
const decoder = new TextDecoder();
|
|
99
|
+
let bytes = 0;
|
|
100
|
+
let text = '';
|
|
101
|
+
try {
|
|
102
|
+
for (;;) {
|
|
103
|
+
const chunk = await waitFor(reader.read());
|
|
104
|
+
if (chunk.done) {
|
|
105
|
+
return text + decoder.decode();
|
|
106
|
+
}
|
|
107
|
+
bytes += chunk.value.byteLength;
|
|
108
|
+
if (bytes > MAX_RESPONSE_BYTES) {
|
|
109
|
+
throw new DecisionError(
|
|
110
|
+
'malformed_response',
|
|
111
|
+
'decision model response too large',
|
|
112
|
+
{
|
|
113
|
+
provider,
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
void reader.cancel().catch(() => {});
|
|
121
|
+
throw error;
|
|
122
|
+
} finally {
|
|
123
|
+
try {
|
|
124
|
+
reader.releaseLock();
|
|
125
|
+
} catch {
|
|
126
|
+
// A timed-out read still holds the lock until cancellation settles.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** One immutable transport; every call owns its signal, deadline, and credentials. */
|
|
132
|
+
export function createTransport(options: TransportOptions): Transport {
|
|
133
|
+
const { providerId } = options;
|
|
134
|
+
const credential = options.apiKey;
|
|
135
|
+
if (
|
|
136
|
+
options.requiresAuth !== false &&
|
|
137
|
+
(credential == null ||
|
|
138
|
+
(typeof credential === 'string' && credential.trim() === ''))
|
|
139
|
+
) {
|
|
140
|
+
throw new DecisionError(
|
|
141
|
+
'unauthorized',
|
|
142
|
+
'decision model requires an API key',
|
|
143
|
+
{
|
|
144
|
+
provider: providerId,
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const endpoint = options.endpoint.trim();
|
|
149
|
+
if (!endpoint) {
|
|
150
|
+
throw new DecisionError(
|
|
151
|
+
'bad_request',
|
|
152
|
+
'decision model requires an endpoint',
|
|
153
|
+
{
|
|
154
|
+
provider: providerId,
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
159
|
+
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
160
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
161
|
+
const onAnswered = options.onAnswered;
|
|
162
|
+
if (!Number.isSafeInteger(maxRetries) || maxRetries < 0 || maxRetries > 5) {
|
|
163
|
+
throw new DecisionError(
|
|
164
|
+
'bad_request',
|
|
165
|
+
'invalid decision model retry limit',
|
|
166
|
+
{
|
|
167
|
+
provider: providerId,
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
const fetchImpl: DecisionFetch = options.fetch ?? globalThis.fetch;
|
|
172
|
+
if (typeof fetchImpl !== 'function') {
|
|
173
|
+
throw new DecisionError('network', 'no fetch implementation available', {
|
|
174
|
+
provider: providerId,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function resolveKey(
|
|
179
|
+
refresh: boolean,
|
|
180
|
+
signal: AbortSignal
|
|
181
|
+
): Promise<string | null> {
|
|
182
|
+
if (typeof credential === 'string') {
|
|
183
|
+
return credential.trim() || null;
|
|
184
|
+
}
|
|
185
|
+
if (!credential) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const minted: string = await credential({ refresh, signal });
|
|
189
|
+
const token = typeof minted === 'string' ? minted.trim() : '';
|
|
190
|
+
if (!token) {
|
|
191
|
+
throw new DecisionError(
|
|
192
|
+
'unauthorized',
|
|
193
|
+
'credential function returned no token',
|
|
194
|
+
{
|
|
195
|
+
provider: providerId,
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return token;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return async function send(prepare, callerSignal, label, timeoutOverrideMs) {
|
|
203
|
+
const started = performance.now();
|
|
204
|
+
return withDecisionDeadline(
|
|
205
|
+
providerId,
|
|
206
|
+
timeoutOverrideMs ?? timeoutMs,
|
|
207
|
+
callerSignal,
|
|
208
|
+
async (signal, waitFor) => {
|
|
209
|
+
let prepared: ReturnType<typeof prepare>;
|
|
210
|
+
try {
|
|
211
|
+
prepared = prepare();
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (error instanceof DecisionError) {
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
throw new DecisionError(
|
|
217
|
+
'bad_request',
|
|
218
|
+
'invalid decision model request',
|
|
219
|
+
{
|
|
220
|
+
provider: providerId,
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
const { payload, parse } = prepared;
|
|
225
|
+
await waitFor(Promise.resolve());
|
|
226
|
+
const wasAborted = (): boolean => signal.aborted;
|
|
227
|
+
const interrupted = (): DecisionError =>
|
|
228
|
+
new DecisionError(
|
|
229
|
+
callerSignal?.aborted === true ? 'aborted' : 'timeout',
|
|
230
|
+
'decision model request interrupted',
|
|
231
|
+
{ provider: providerId }
|
|
232
|
+
);
|
|
233
|
+
let refreshed = false;
|
|
234
|
+
let key: string | null | undefined;
|
|
235
|
+
let lastError: DecisionError | undefined;
|
|
236
|
+
for (let attemptNo = 0; attemptNo <= maxRetries; attemptNo++) {
|
|
237
|
+
if (wasAborted()) {
|
|
238
|
+
throw interrupted();
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
if (key === undefined) {
|
|
242
|
+
key = await waitFor(resolveKey(refreshed, signal));
|
|
243
|
+
}
|
|
244
|
+
const response = await waitFor(
|
|
245
|
+
fetchImpl(endpoint, {
|
|
246
|
+
method: 'POST',
|
|
247
|
+
headers: {
|
|
248
|
+
...(key !== null ? { Authorization: `Bearer ${key}` } : {}),
|
|
249
|
+
'Content-Type': 'application/json',
|
|
250
|
+
},
|
|
251
|
+
body: payload,
|
|
252
|
+
signal,
|
|
253
|
+
redirect: 'error',
|
|
254
|
+
})
|
|
255
|
+
);
|
|
256
|
+
if (!response.ok) {
|
|
257
|
+
void response.body?.cancel().catch(() => {});
|
|
258
|
+
const error = new DecisionError(
|
|
259
|
+
failureForStatus(response.status),
|
|
260
|
+
`decision model returned HTTP ${response.status}`,
|
|
261
|
+
{ provider: providerId, status: response.status }
|
|
262
|
+
);
|
|
263
|
+
error.retryAfterMs = retryAfterMs(
|
|
264
|
+
response.headers.get('retry-after')
|
|
265
|
+
);
|
|
266
|
+
throw error;
|
|
267
|
+
}
|
|
268
|
+
const body = await readResponse(response.body, waitFor, providerId);
|
|
269
|
+
const result = await waitFor(Promise.resolve(parse(body)));
|
|
270
|
+
try {
|
|
271
|
+
onAnswered?.(label, performance.now() - started);
|
|
272
|
+
} catch {
|
|
273
|
+
// Observability must not turn a valid answer into a retried request.
|
|
274
|
+
}
|
|
275
|
+
return result;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
if (wasAborted()) {
|
|
278
|
+
throw interrupted();
|
|
279
|
+
}
|
|
280
|
+
if (error instanceof DecisionError) {
|
|
281
|
+
lastError = error;
|
|
282
|
+
} else {
|
|
283
|
+
lastError = new DecisionError(
|
|
284
|
+
'network',
|
|
285
|
+
'decision model request failed',
|
|
286
|
+
{ provider: providerId }
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
if (
|
|
290
|
+
lastError.status === 401 &&
|
|
291
|
+
typeof credential === 'function' &&
|
|
292
|
+
!refreshed
|
|
293
|
+
) {
|
|
294
|
+
refreshed = true;
|
|
295
|
+
key = undefined;
|
|
296
|
+
attemptNo -= 1;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (
|
|
300
|
+
attemptNo === maxRetries ||
|
|
301
|
+
!['rate_limited', 'server_error', 'network'].includes(
|
|
302
|
+
lastError.failure
|
|
303
|
+
)
|
|
304
|
+
) {
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
const wait =
|
|
308
|
+
lastError.retryAfterMs ??
|
|
309
|
+
BACKOFF_MS[Math.min(attemptNo, BACKOFF_MS.length - 1)];
|
|
310
|
+
try {
|
|
311
|
+
await waitFor(sleep(wait, signal));
|
|
312
|
+
} catch (error) {
|
|
313
|
+
if (error instanceof DecisionError) {
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
throw new DecisionError(
|
|
317
|
+
'network',
|
|
318
|
+
'decision model backoff failed',
|
|
319
|
+
{
|
|
320
|
+
provider: providerId,
|
|
321
|
+
}
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
throw (
|
|
327
|
+
lastError ??
|
|
328
|
+
new DecisionError('network', 'decision model request failed', {
|
|
329
|
+
provider: providerId,
|
|
330
|
+
})
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
);
|
|
334
|
+
};
|
|
335
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/** Typed decisions over content, independent of the model that makes them. */
|
|
2
|
+
|
|
3
|
+
export type DecisionJson =
|
|
4
|
+
| string
|
|
5
|
+
| number
|
|
6
|
+
| boolean
|
|
7
|
+
| null
|
|
8
|
+
| DecisionJson[]
|
|
9
|
+
| { [key: string]: DecisionJson };
|
|
10
|
+
|
|
11
|
+
export type DecisionText =
|
|
12
|
+
| string
|
|
13
|
+
| DecisionJson[]
|
|
14
|
+
| { [key: string]: DecisionJson };
|
|
15
|
+
|
|
16
|
+
export type DecisionState = DecisionText;
|
|
17
|
+
|
|
18
|
+
export interface BooleanCriteria {
|
|
19
|
+
true?: DecisionText;
|
|
20
|
+
false?: DecisionText;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A string criterion describes the yes side of a yes/no question. */
|
|
24
|
+
export interface BooleanQuestion {
|
|
25
|
+
type: 'boolean';
|
|
26
|
+
instructions: DecisionText;
|
|
27
|
+
criteria?: BooleanCriteria | string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ChoiceQuestion {
|
|
31
|
+
type: 'choice';
|
|
32
|
+
instructions: DecisionText;
|
|
33
|
+
criteria: Record<string, DecisionText | null>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The measured answer is an expected level, which may fall between two rubric levels. */
|
|
37
|
+
export interface ScoreQuestion {
|
|
38
|
+
type: 'score';
|
|
39
|
+
instructions: DecisionText;
|
|
40
|
+
criteria: DecisionText[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type DecisionQuestion = BooleanQuestion | ChoiceQuestion | ScoreQuestion;
|
|
44
|
+
|
|
45
|
+
/** `null` is an unmeasured decision, not a probability of zero. */
|
|
46
|
+
export type BooleanAnswer =
|
|
47
|
+
| { type: 'boolean'; probability: number; decision?: never }
|
|
48
|
+
| { type: 'boolean'; probability: null; decision: boolean };
|
|
49
|
+
|
|
50
|
+
export interface ChoiceAnswer {
|
|
51
|
+
type: 'choice';
|
|
52
|
+
choice: string;
|
|
53
|
+
/** Backend-specific concentration measure; Jev and Laya use different definitions. */
|
|
54
|
+
confidence: number | null;
|
|
55
|
+
/** `null` when no distribution was measured; never substitute an empty distribution. */
|
|
56
|
+
probabilities: Record<string, number> | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ScoreAnswer {
|
|
60
|
+
type: 'score';
|
|
61
|
+
/** Expected value over rubric levels, not an LLM's selection of one level. */
|
|
62
|
+
score: number;
|
|
63
|
+
/** Backend-specific concentration measure; do not transfer thresholds between checkpoints. */
|
|
64
|
+
confidence: number | null;
|
|
65
|
+
probabilities: Record<string, number> | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type DecisionAnswer = BooleanAnswer | ChoiceAnswer | ScoreAnswer;
|
|
69
|
+
|
|
70
|
+
/** Unknown counts are omitted, never inferred from an answer or replaced by zero. */
|
|
71
|
+
export interface DecisionUsage {
|
|
72
|
+
inputTokens?: number;
|
|
73
|
+
outputTokens?: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface DecisionRequest {
|
|
77
|
+
state: DecisionState;
|
|
78
|
+
questions: Record<string, DecisionQuestion>;
|
|
79
|
+
signal?: AbortSignal;
|
|
80
|
+
label?: string;
|
|
81
|
+
/** Overrides the provider's timeout for this request alone. */
|
|
82
|
+
timeoutMs?: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface DecisionResult {
|
|
86
|
+
model: string;
|
|
87
|
+
/** A requested answer can be omitted; callers must handle `undefined` explicitly. */
|
|
88
|
+
answers: Record<string, DecisionAnswer | undefined>;
|
|
89
|
+
usage: DecisionUsage | null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface DecisionModel {
|
|
93
|
+
readonly id: string;
|
|
94
|
+
readonly model: string;
|
|
95
|
+
decide(request: DecisionRequest): Promise<DecisionResult>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type DecisionDialect = 'port' | 'systemone';
|
|
99
|
+
|
|
100
|
+
/** A bearer key, or a per-request token minter; a 401 requests one fresh token for that call. */
|
|
101
|
+
export type DecisionCredential =
|
|
102
|
+
| string
|
|
103
|
+
| ((options: { refresh: boolean; signal?: AbortSignal }) => Promise<string>);
|
|
104
|
+
|
|
105
|
+
export interface DecisionProviderSettings {
|
|
106
|
+
/** Full URL of the decision endpoint, not a base path. */
|
|
107
|
+
baseURL?: string;
|
|
108
|
+
model?: string;
|
|
109
|
+
dialect?: DecisionDialect;
|
|
110
|
+
requestKey?: string;
|
|
111
|
+
responseKey?: string;
|
|
112
|
+
timeoutMs?: number;
|
|
113
|
+
maxRetries?: number;
|
|
114
|
+
/** Auth is required by default; self-hosted Laya can be unauthenticated. */
|
|
115
|
+
requiresAuth?: boolean;
|
|
116
|
+
apiKeyEnv?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type DecisionFailure =
|
|
120
|
+
| 'timeout'
|
|
121
|
+
| 'aborted'
|
|
122
|
+
| 'rate_limited'
|
|
123
|
+
| 'unauthorized'
|
|
124
|
+
| 'bad_request'
|
|
125
|
+
| 'server_error'
|
|
126
|
+
| 'network'
|
|
127
|
+
| 'unsupported_question'
|
|
128
|
+
| 'unsupported_mode'
|
|
129
|
+
| 'malformed_response';
|
|
130
|
+
|
|
131
|
+
export class DecisionError extends Error {
|
|
132
|
+
readonly failure: DecisionFailure;
|
|
133
|
+
readonly provider: string;
|
|
134
|
+
readonly status?: number;
|
|
135
|
+
retryAfterMs?: number;
|
|
136
|
+
|
|
137
|
+
constructor(
|
|
138
|
+
failure: DecisionFailure,
|
|
139
|
+
message: string,
|
|
140
|
+
options?: { provider?: string; status?: number }
|
|
141
|
+
) {
|
|
142
|
+
super(message);
|
|
143
|
+
this.name = 'DecisionError';
|
|
144
|
+
this.failure = failure;
|
|
145
|
+
this.provider = options?.provider ?? 'unknown';
|
|
146
|
+
this.status = options?.status;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** A JSON boundary guard; never trust a parsed response's declared TypeScript type. */
|
|
151
|
+
export function isDecisionObject(
|
|
152
|
+
value: unknown
|
|
153
|
+
): value is { [key: string]: unknown } {
|
|
154
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function readDecisionUsage(raw: unknown): DecisionUsage | null {
|
|
158
|
+
if (!isDecisionObject(raw)) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const input = raw.input_tokens;
|
|
162
|
+
const output = raw.output_tokens;
|
|
163
|
+
const usage: DecisionUsage = {};
|
|
164
|
+
if (typeof input === 'number' && Number.isSafeInteger(input) && input >= 0) {
|
|
165
|
+
usage.inputTokens = input;
|
|
166
|
+
}
|
|
167
|
+
if (
|
|
168
|
+
typeof output === 'number' &&
|
|
169
|
+
Number.isSafeInteger(output) &&
|
|
170
|
+
output >= 0
|
|
171
|
+
) {
|
|
172
|
+
usage.outputTokens = output;
|
|
173
|
+
}
|
|
174
|
+
return Object.keys(usage).length > 0 ? usage : null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function isBooleanAnswer(
|
|
178
|
+
answer: DecisionAnswer | undefined
|
|
179
|
+
): answer is BooleanAnswer {
|
|
180
|
+
return answer?.type === 'boolean';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function isChoiceAnswer(
|
|
184
|
+
answer: DecisionAnswer | undefined
|
|
185
|
+
): answer is ChoiceAnswer {
|
|
186
|
+
return answer?.type === 'choice';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function isScoreAnswer(
|
|
190
|
+
answer: DecisionAnswer | undefined
|
|
191
|
+
): answer is ScoreAnswer {
|
|
192
|
+
return answer?.type === 'score';
|
|
193
|
+
}
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -131,6 +131,10 @@ import {
|
|
|
131
131
|
resolveLangfuseConfig,
|
|
132
132
|
resolveToolOutputTracingConfig,
|
|
133
133
|
} from '@/langfuseConfig';
|
|
134
|
+
import {
|
|
135
|
+
ProviderTextProtectionError,
|
|
136
|
+
validateProviderTextProtection,
|
|
137
|
+
} from '@/protection/providerText';
|
|
134
138
|
import {
|
|
135
139
|
annotateMessagesForLLM,
|
|
136
140
|
ToolOutputReferenceRegistry,
|
|
@@ -139,6 +143,10 @@ import {
|
|
|
139
143
|
InvalidModelToolCallError,
|
|
140
144
|
snapshotAcceptedModelResponse,
|
|
141
145
|
} from './acceptedModelResponse';
|
|
146
|
+
import {
|
|
147
|
+
collectSubagentHostArgNames,
|
|
148
|
+
pickSubagentHostArgInput,
|
|
149
|
+
} from '@/tools/subagent/hostArgs';
|
|
142
150
|
import {
|
|
143
151
|
prepareProviderRequest,
|
|
144
152
|
usesNativeOpenAIResponses,
|
|
@@ -1369,6 +1377,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1369
1377
|
/** See {@link t.StandardGraphInput.subagentExecutionContext}. */
|
|
1370
1378
|
readonly subagentExecutionContext?: t.SubagentExecutionContext;
|
|
1371
1379
|
private readonly subagentContext?: t.SubagentContextAdapter;
|
|
1380
|
+
private readonly onSubagentResolutionFailure?: t.StandardGraphInput['onSubagentResolutionFailure'];
|
|
1372
1381
|
/** See {@link t.StandardGraphInput.preemption}. */
|
|
1373
1382
|
preemption?: t.StreamPreemption;
|
|
1374
1383
|
/**
|
|
@@ -1377,6 +1386,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1377
1386
|
* these on every streamed chunk event.
|
|
1378
1387
|
*/
|
|
1379
1388
|
streamLimits: ResolvedStreamLimits;
|
|
1389
|
+
/** Default-off mandatory provider prose release. */
|
|
1390
|
+
providerTextProtection?: t.StandardGraphInput['providerTextProtection'];
|
|
1380
1391
|
/**
|
|
1381
1392
|
* Cumulative streamed argument bytes per in-flight tool call, keyed by
|
|
1382
1393
|
* generation key + chunk index (see `resolveGenerationKey`). Per-run
|
|
@@ -1422,25 +1433,31 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1422
1433
|
* attempt lease; spans resets on purpose. */
|
|
1423
1434
|
activeStreamLimitGenerations?: Set<string>;
|
|
1424
1435
|
|
|
1425
|
-
/** The
|
|
1436
|
+
/** The safety error behind an already-fired breaker, whether this
|
|
1426
1437
|
* graph's own controller tripped or a parent run's breaker arrived through
|
|
1427
1438
|
* the composed constructor signal (child graphs own separate controllers).
|
|
1428
1439
|
* Providers can translate either abort into a generic error, and recovery
|
|
1429
1440
|
* paths must not run in that state. */
|
|
1430
1441
|
protected resolveTrippedBreakerReason(
|
|
1431
1442
|
breakerSignal: AbortSignal = this.breakerAbort.signal
|
|
1432
|
-
):
|
|
1443
|
+
):
|
|
1444
|
+
| StreamLimitExceededError
|
|
1445
|
+
| PreparedSubagentError
|
|
1446
|
+
| ProviderTextProtectionError
|
|
1447
|
+
| undefined {
|
|
1433
1448
|
if (
|
|
1434
1449
|
breakerSignal.aborted &&
|
|
1435
1450
|
(breakerSignal.reason instanceof StreamLimitExceededError ||
|
|
1436
|
-
breakerSignal.reason instanceof PreparedSubagentError
|
|
1451
|
+
breakerSignal.reason instanceof PreparedSubagentError ||
|
|
1452
|
+
breakerSignal.reason instanceof ProviderTextProtectionError)
|
|
1437
1453
|
) {
|
|
1438
1454
|
return breakerSignal.reason;
|
|
1439
1455
|
}
|
|
1440
1456
|
if (
|
|
1441
1457
|
this.signal?.aborted === true &&
|
|
1442
1458
|
(this.signal.reason instanceof StreamLimitExceededError ||
|
|
1443
|
-
this.signal.reason instanceof PreparedSubagentError
|
|
1459
|
+
this.signal.reason instanceof PreparedSubagentError ||
|
|
1460
|
+
this.signal.reason instanceof ProviderTextProtectionError)
|
|
1444
1461
|
) {
|
|
1445
1462
|
return this.signal.reason;
|
|
1446
1463
|
}
|
|
@@ -1547,8 +1564,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1547
1564
|
subagentScope,
|
|
1548
1565
|
subagentExecutionContext,
|
|
1549
1566
|
subagentContext,
|
|
1567
|
+
onSubagentResolutionFailure,
|
|
1550
1568
|
preemption,
|
|
1551
1569
|
streamLimits,
|
|
1570
|
+
providerTextProtection,
|
|
1552
1571
|
toolExecution,
|
|
1553
1572
|
clientDelegatedToolNames,
|
|
1554
1573
|
}: t.StandardGraphInput,
|
|
@@ -1575,10 +1594,17 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1575
1594
|
this.subagentScope = subagentScope === true;
|
|
1576
1595
|
this.subagentExecutionContext = subagentExecutionContext;
|
|
1577
1596
|
this.subagentContext = subagentContext;
|
|
1597
|
+
this.onSubagentResolutionFailure = onSubagentResolutionFailure;
|
|
1578
1598
|
this.preemption = preemption;
|
|
1579
1599
|
this.streamLimits = resolveStreamLimits(streamLimits);
|
|
1600
|
+
if (providerTextProtection != null)
|
|
1601
|
+
validateProviderTextProtection(providerTextProtection);
|
|
1602
|
+
this.providerTextProtection = providerTextProtection;
|
|
1580
1603
|
this.toolExecution = toolExecution;
|
|
1581
|
-
if (
|
|
1604
|
+
if (
|
|
1605
|
+
clientDelegatedToolNames != null &&
|
|
1606
|
+
clientDelegatedToolNames.length > 0
|
|
1607
|
+
) {
|
|
1582
1608
|
if (agents.length !== 1) {
|
|
1583
1609
|
throw new Error('Client tool delegation requires a single-agent graph');
|
|
1584
1610
|
}
|
|
@@ -4401,7 +4427,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4401
4427
|
* succeeding fallback would resolve a run the public contract says
|
|
4402
4428
|
* must reject. Rethrow before any recovery path.
|
|
4403
4429
|
*/
|
|
4404
|
-
if (
|
|
4430
|
+
if (
|
|
4431
|
+
primaryError instanceof InvalidModelToolCallError ||
|
|
4432
|
+
primaryError instanceof ProviderTextProtectionError
|
|
4433
|
+
) {
|
|
4434
|
+
if (primaryError instanceof ProviderTextProtectionError)
|
|
4435
|
+
attemptBreaker.abort(primaryError);
|
|
4405
4436
|
throw primaryError;
|
|
4406
4437
|
}
|
|
4407
4438
|
if (
|
|
@@ -4744,7 +4775,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4744
4775
|
})
|
|
4745
4776
|
);
|
|
4746
4777
|
} catch (fallbackError) {
|
|
4747
|
-
if (
|
|
4778
|
+
if (
|
|
4779
|
+
fallbackError instanceof InvalidModelToolCallError ||
|
|
4780
|
+
fallbackError instanceof ProviderTextProtectionError
|
|
4781
|
+
) {
|
|
4782
|
+
if (fallbackError instanceof ProviderTextProtectionError)
|
|
4783
|
+
attemptBreaker.abort(fallbackError);
|
|
4748
4784
|
throw fallbackError;
|
|
4749
4785
|
}
|
|
4750
4786
|
if (
|
|
@@ -5342,7 +5378,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
5342
5378
|
usageSink: this.subagentUsageSink,
|
|
5343
5379
|
taskConfig: this.subagentTasks,
|
|
5344
5380
|
subagentContext: this.subagentContext,
|
|
5381
|
+
onResolutionFailure: this.onSubagentResolutionFailure,
|
|
5345
5382
|
streamLimits: this.streamLimits,
|
|
5383
|
+
providerTextProtection: this.providerTextProtection,
|
|
5346
5384
|
humanInTheLoop: this.humanInTheLoop,
|
|
5347
5385
|
checkpointer: this.compileOptions?.checkpointer,
|
|
5348
5386
|
maxDepth: effectiveSubagentDepth,
|
|
@@ -5357,6 +5395,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
5357
5395
|
): GraphFactory => snapshotChildGraphFactory(parentHandlerRegistry),
|
|
5358
5396
|
});
|
|
5359
5397
|
this.registerSubagentExecutor(executor);
|
|
5398
|
+
const hostArgNames = collectSubagentHostArgNames(executableConfigs);
|
|
5360
5399
|
|
|
5361
5400
|
const subagentTool = tool(
|
|
5362
5401
|
async (rawInput, config) => {
|
|
@@ -5366,6 +5405,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
5366
5405
|
subagent_thread_id?: string;
|
|
5367
5406
|
run_in_background?: boolean;
|
|
5368
5407
|
};
|
|
5408
|
+
const hostArgInput = pickSubagentHostArgInput(input, hostArgNames);
|
|
5409
|
+
if (!hostArgInput.ok) {
|
|
5410
|
+
return hostArgInput.message;
|
|
5411
|
+
}
|
|
5369
5412
|
const description =
|
|
5370
5413
|
typeof input.description === 'string' &&
|
|
5371
5414
|
input.description.trim().length > 0
|
|
@@ -5422,6 +5465,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
5422
5465
|
parentConfigurable: config.configurable as
|
|
5423
5466
|
| Record<string, unknown>
|
|
5424
5467
|
| undefined,
|
|
5468
|
+
...(hostArgInput.hostArgs == null
|
|
5469
|
+
? {}
|
|
5470
|
+
: { hostArgs: hostArgInput.hostArgs }),
|
|
5425
5471
|
};
|
|
5426
5472
|
if (input.run_in_background === true) {
|
|
5427
5473
|
return executor.executeInBackground({
|
|
@@ -5552,8 +5598,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
5552
5598
|
const delegatedNames = this.clientDelegatedToolNames;
|
|
5553
5599
|
if (delegatedNames != null && delegatedNames.size > 0) {
|
|
5554
5600
|
const { messages } = state as t.BaseGraphState;
|
|
5555
|
-
const last = messages[messages.length - 1] as
|
|
5556
|
-
|
|
5601
|
+
const last = messages[messages.length - 1] as
|
|
5602
|
+
| AIMessageChunk
|
|
5603
|
+
| undefined;
|
|
5604
|
+
const calls = last?.getType() === 'ai' ? (last.tool_calls ?? []) : [];
|
|
5557
5605
|
if (calls.some((call) => delegatedNames.has(call.name))) {
|
|
5558
5606
|
if (
|
|
5559
5607
|
calls.some(
|