@sema-agent/core 5.12.0 → 5.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +320 -0
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +4 -0
- package/dist/agents/subagent.js +137 -43
- package/dist/brain/anthropic.js +33 -10
- package/dist/brain/context-overflow.d.ts +20 -0
- package/dist/brain/context-overflow.js +58 -0
- package/dist/brain/open-responses.js +24 -10
- package/dist/brain/openai.js +29 -11
- package/dist/brain/request-params.d.ts +2 -0
- package/dist/brain/request-params.js +16 -0
- package/dist/brain/stream-engine.d.ts +9 -1
- package/dist/brain/stream-engine.js +256 -27
- package/dist/brain/timeout.d.ts +1 -0
- package/dist/brain/timeout.js +1 -0
- package/dist/core/a2a.d.ts +2 -2
- package/dist/core/a2a.js +3 -3
- package/dist/core/ask-question.d.ts +47 -2
- package/dist/core/ask-question.js +209 -28
- package/dist/core/background-agent-store.d.ts +2 -0
- package/dist/core/checkpoint-store.d.ts +49 -18
- package/dist/core/checkpoint-store.js +117 -4
- package/dist/core/compliance.d.ts +11 -0
- package/dist/core/compliance.js +34 -0
- package/dist/core/governance-codes.d.ts +12 -0
- package/dist/core/governance-codes.js +24 -0
- package/dist/core/hooks.d.ts +24 -2
- package/dist/core/hooks.js +97 -10
- package/dist/core/human-input-projection.d.ts +12 -0
- package/dist/core/human-input-projection.js +27 -0
- package/dist/core/locked-config.d.ts +27 -0
- package/dist/core/locked-config.js +42 -0
- package/dist/core/mcp.d.ts +7 -2
- package/dist/core/mcp.js +7 -7
- package/dist/core/memory-admission.d.ts +51 -0
- package/dist/core/memory-admission.js +159 -0
- package/dist/core/memory.d.ts +2 -0
- package/dist/core/memory.js +3 -2
- package/dist/core/retention.d.ts +36 -0
- package/dist/core/retention.js +31 -0
- package/dist/core/runner/assemble-result.d.ts +1 -0
- package/dist/core/runner/assemble-result.js +2 -2
- package/dist/core/runner/prepare-memory.d.ts +9 -0
- package/dist/core/runner/prepare-memory.js +28 -2
- package/dist/core/runner/prepare-task.d.ts +18 -6
- package/dist/core/runner/prepare-task.js +428 -38
- package/dist/core/runner/runtask.d.ts +3 -6
- package/dist/core/runner/runtask.js +258 -77
- package/dist/core/runner/tool-output-projection.js +1 -0
- package/dist/core/session-store.d.ts +4 -0
- package/dist/core/session-store.js +5 -0
- package/dist/core/session.d.ts +1 -0
- package/dist/core/store-contracts/background-agent-store-contract.js +19 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +62 -3
- package/dist/core/task-notification.d.ts +2 -0
- package/dist/core/task-notification.js +5 -3
- package/dist/core/task-registry-agent.d.ts +1 -0
- package/dist/core/task-registry-agent.js +6 -0
- package/dist/core/task-registry.d.ts +1 -0
- package/dist/core/task-registry.js +4 -1
- package/dist/core/tool-policy.d.ts +5 -0
- package/dist/core/tool-policy.js +2 -1
- package/dist/core/tool-result-store.d.ts +2 -0
- package/dist/core/tool-result-store.js +1 -0
- package/dist/core/types.d.ts +38 -2
- package/dist/core/wiring-manifest.d.ts +97 -0
- package/dist/core/wiring-manifest.js +186 -0
- package/dist/engine/compaction/compaction.js +2 -2
- package/dist/engine/harness/agent-harness.d.ts +2 -1
- package/dist/engine/harness/agent-harness.js +19 -2
- package/dist/engine/harness/types.d.ts +3 -1
- package/dist/engine/llm/types.d.ts +7 -0
- package/dist/engine/llm/types.js +8 -1
- package/dist/engine/llm/validation.js +11 -1
- package/dist/engine/session/import-validate.d.ts +6 -1
- package/dist/engine/session/import-validate.js +29 -6
- package/dist/engine/session/memory-repo.d.ts +3 -1
- package/dist/engine/session/memory-repo.js +2 -2
- package/dist/index.d.ts +12 -4
- package/dist/index.js +12 -4
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/internal/llm.d.ts +2 -2
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +4 -0
- package/dist/orchestration/run-workflow-tool.js +3 -0
- package/dist/orchestration/workflow-types.d.ts +8 -0
- package/dist/orchestration/workflow-types.js +14 -0
- package/dist/orchestration/workflow.d.ts +4 -0
- package/dist/orchestration/workflow.js +134 -5
- package/dist/prompts/default.js +1 -1
- package/dist/stores/file/checkpoint-store.d.ts +3 -5
- package/dist/stores/file/checkpoint-store.js +31 -2
- package/dist/stores/file/index.js +1 -1
- package/dist/stores/file/session-store.d.ts +3 -1
- package/dist/stores/file/session-store.js +2 -2
- package/dist/stores/file/shared-ledger.js +8 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +3 -0
- package/dist/tools/fs/bash-readonly-classifier.js +94 -0
- package/dist/tools/fs/fs-bash.js +31 -12
- package/dist/tools/fs/safety.js +34 -10
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { createRepetitionPoll, parseStreamedToolArgs } from "./stream-shared.js"
|
|
|
5
5
|
import { mintFallbackToolCallId } from "./tool-call-id.js";
|
|
6
6
|
import { emitBrainTelemetry } from "./status-sink.js";
|
|
7
7
|
import { errorResultMediaNote, IMAGE_OMITTED_NO_VISION, imagesOmittedNoVisionNote, modelSupportsVision, sendableImages } from "./media-degrade.js";
|
|
8
|
-
import { RESPONSES_RESERVED, applyExtraBody, stripAuthHeaders } from "./request-params.js";
|
|
8
|
+
import { OUTPUT_CAP_KEYS, RESPONSES_RESERVED, applyExtraBody, effectiveOutputCap, stripAuthHeaders } from "./request-params.js";
|
|
9
9
|
import { DEFAULT_EFFORT_LEVELS, isThinkingLevel, resolveEffort } from "./reasoning.js";
|
|
10
10
|
import { runStreamingBrain } from "./stream-engine.js";
|
|
11
11
|
const DEGENERATE_POLL_CHARS = 64;
|
|
@@ -222,6 +222,8 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
222
222
|
let sentMaxTokens;
|
|
223
223
|
let sentMaxTokensLane = "unset";
|
|
224
224
|
let sentEffort;
|
|
225
|
+
let builtMaxTokens;
|
|
226
|
+
let builtMaxTokensLane = "unset";
|
|
225
227
|
return runStreamingBrain({
|
|
226
228
|
model,
|
|
227
229
|
doFetch,
|
|
@@ -229,7 +231,7 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
229
231
|
config,
|
|
230
232
|
httpLabel: "responses",
|
|
231
233
|
stallTimeouts: options?.stallTimeouts,
|
|
232
|
-
buildRequest: () => {
|
|
234
|
+
buildRequest: (overrides) => {
|
|
233
235
|
const apiKey = options?.apiKey ?? config.apiKey;
|
|
234
236
|
const root = (model.baseUrl || config.baseUrl || "").replace(/\/+$/, "");
|
|
235
237
|
if (!root) {
|
|
@@ -239,9 +241,8 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
239
241
|
throw new Error("createOpenResponsesBrain: options.stop is not expressible in the Open Responses request (the wire form has no stop-sequence field); " +
|
|
240
242
|
"remove it, or serve this model through the chat-completions lane, which does support it");
|
|
241
243
|
}
|
|
242
|
-
const requestedMaxTokens = options?.maxTokens ?? model.maxTokens;
|
|
244
|
+
const requestedMaxTokens = overrides?.maxOutputTokens ?? options?.maxTokens ?? model.maxTokens;
|
|
243
245
|
const effort = resolveWireEffort(model, options?.reasoning);
|
|
244
|
-
sentEffort = effort;
|
|
245
246
|
const body = {
|
|
246
247
|
model: model.id,
|
|
247
248
|
input: toResponsesInput(context, model, config.replayReasoning !== false),
|
|
@@ -257,12 +258,13 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
257
258
|
if (requestedMaxTokens !== undefined) {
|
|
258
259
|
const capped = Math.max(1, requestedMaxTokens);
|
|
259
260
|
body.max_output_tokens = capped;
|
|
260
|
-
|
|
261
|
-
|
|
261
|
+
builtMaxTokens = capped;
|
|
262
|
+
builtMaxTokensLane =
|
|
263
|
+
overrides?.maxOutputTokens !== undefined ? "engine" : options?.maxTokens !== undefined ? "options" : "model";
|
|
262
264
|
}
|
|
263
265
|
else {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
+
builtMaxTokens = undefined;
|
|
267
|
+
builtMaxTokensLane = "unset";
|
|
266
268
|
}
|
|
267
269
|
if (effort !== undefined)
|
|
268
270
|
body.reasoning = { effort };
|
|
@@ -276,10 +278,18 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
276
278
|
headers["content-type"] = "application/json";
|
|
277
279
|
if (apiKey)
|
|
278
280
|
headers["authorization"] = `Bearer ${apiKey}`;
|
|
281
|
+
const wire = applyExtraBody(body, model.extraBody, RESPONSES_RESERVED);
|
|
279
282
|
return {
|
|
280
283
|
url: `${root}/responses`,
|
|
281
284
|
headers,
|
|
282
|
-
body: JSON.stringify(
|
|
285
|
+
body: JSON.stringify(wire),
|
|
286
|
+
outputCapTokens: effectiveOutputCap(wire, OUTPUT_CAP_KEYS.responses),
|
|
287
|
+
capDerivedPaths: OUTPUT_CAP_KEYS.responses,
|
|
288
|
+
onCommitted: () => {
|
|
289
|
+
sentMaxTokens = builtMaxTokens;
|
|
290
|
+
sentMaxTokensLane = builtMaxTokensLane;
|
|
291
|
+
sentEffort = effort;
|
|
292
|
+
},
|
|
283
293
|
};
|
|
284
294
|
},
|
|
285
295
|
makeParser: (ctrl) => {
|
|
@@ -671,7 +681,11 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
671
681
|
const refused = accumRefusal.trim().length > 0;
|
|
672
682
|
const emptyNoFinish = noUsableContent && terminalStatus === undefined && !degenerate;
|
|
673
683
|
const maxTokensNote = sentMaxTokens !== undefined
|
|
674
|
-
? `effective max_output_tokens=${sentMaxTokens} (from ${sentMaxTokensLane === "options"
|
|
684
|
+
? `effective max_output_tokens=${sentMaxTokens} (from ${sentMaxTokensLane === "options"
|
|
685
|
+
? "options.maxTokens"
|
|
686
|
+
: sentMaxTokensLane === "engine"
|
|
687
|
+
? "the engine's context-overflow recovery"
|
|
688
|
+
: "model.maxTokens"})`
|
|
675
689
|
: "max_output_tokens not set (neither options.maxTokens nor model.maxTokens — the endpoint's default cap applied)";
|
|
676
690
|
const errorMessage = degenerate
|
|
677
691
|
? DEGENERATE_MESSAGE
|
package/dist/brain/openai.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createRepetitionPoll, parseStreamedToolArgs } from "./stream-shared.js"
|
|
|
5
5
|
import { mintFallbackToolCallId } from "./tool-call-id.js";
|
|
6
6
|
import { emitBrainTelemetry } from "./status-sink.js";
|
|
7
7
|
import { errorResultMediaNote, IMAGE_OMITTED_NO_VISION, imagesOmittedNoVisionNote, modelSupportsVision, sendableImages } from "./media-degrade.js";
|
|
8
|
-
import { OPENAI_RESERVED, applyExtraBody, stripAuthHeaders } from "./request-params.js";
|
|
8
|
+
import { OPENAI_RESERVED, OUTPUT_CAP_KEYS, applyExtraBody, effectiveOutputCap, stripAuthHeaders } from "./request-params.js";
|
|
9
9
|
import { DEFAULT_EFFORT_LEVELS, isThinkingLevel, resolveEffort } from "./reasoning.js";
|
|
10
10
|
import { runStreamingBrain } from "./stream-engine.js";
|
|
11
11
|
function closeToolCallAccum(acc) {
|
|
@@ -268,6 +268,8 @@ export function createOpenAIBrain(config = {}) {
|
|
|
268
268
|
const stream = (model, context, options) => {
|
|
269
269
|
let sentMaxTokens;
|
|
270
270
|
let sentMaxTokensLane = "unset";
|
|
271
|
+
let builtMaxTokens;
|
|
272
|
+
let builtMaxTokensLane = "unset";
|
|
271
273
|
return runStreamingBrain({
|
|
272
274
|
model,
|
|
273
275
|
doFetch,
|
|
@@ -275,7 +277,7 @@ export function createOpenAIBrain(config = {}) {
|
|
|
275
277
|
config,
|
|
276
278
|
httpLabel: "gateway",
|
|
277
279
|
stallTimeouts: options?.stallTimeouts,
|
|
278
|
-
buildRequest: () => {
|
|
280
|
+
buildRequest: (overrides) => {
|
|
279
281
|
const apiKey = options?.apiKey ?? config.apiKey;
|
|
280
282
|
const root = (model.baseUrl || config.baseUrl || "").replace(/\/+$/, "");
|
|
281
283
|
if (!root) {
|
|
@@ -292,18 +294,19 @@ export function createOpenAIBrain(config = {}) {
|
|
|
292
294
|
body.tools = tools;
|
|
293
295
|
if (options?.temperature !== undefined)
|
|
294
296
|
body.temperature = options.temperature;
|
|
295
|
-
const requestedMaxTokens = options?.maxTokens ?? model.maxTokens;
|
|
297
|
+
const requestedMaxTokens = overrides?.maxOutputTokens ?? options?.maxTokens ?? model.maxTokens;
|
|
298
|
+
const declared = (model.compat ?? {}).maxTokensField;
|
|
299
|
+
const mtField = declared ?? inferMaxTokensField(model.id);
|
|
296
300
|
if (requestedMaxTokens !== undefined) {
|
|
297
|
-
const declared = (model.compat ?? {}).maxTokensField;
|
|
298
|
-
const mtField = declared ?? inferMaxTokensField(model.id);
|
|
299
301
|
const capped = Math.max(1, requestedMaxTokens);
|
|
300
302
|
body[mtField] = capped;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
+
builtMaxTokens = capped;
|
|
304
|
+
builtMaxTokensLane =
|
|
305
|
+
overrides?.maxOutputTokens !== undefined ? "engine" : options?.maxTokens !== undefined ? "options" : "model";
|
|
303
306
|
}
|
|
304
307
|
else {
|
|
305
|
-
|
|
306
|
-
|
|
308
|
+
builtMaxTokens = undefined;
|
|
309
|
+
builtMaxTokensLane = "unset";
|
|
307
310
|
}
|
|
308
311
|
if (options?.stop)
|
|
309
312
|
body.stop = options.stop;
|
|
@@ -318,7 +321,18 @@ export function createOpenAIBrain(config = {}) {
|
|
|
318
321
|
headers["content-type"] = "application/json";
|
|
319
322
|
if (apiKey)
|
|
320
323
|
headers["authorization"] = `Bearer ${apiKey}`;
|
|
321
|
-
|
|
324
|
+
const wire = applyExtraBody(body, model.extraBody, OPENAI_RESERVED);
|
|
325
|
+
return {
|
|
326
|
+
url: `${root}/chat/completions`,
|
|
327
|
+
headers,
|
|
328
|
+
body: JSON.stringify(wire),
|
|
329
|
+
outputCapTokens: effectiveOutputCap(wire, OUTPUT_CAP_KEYS.openai),
|
|
330
|
+
capDerivedPaths: [mtField],
|
|
331
|
+
onCommitted: () => {
|
|
332
|
+
sentMaxTokens = builtMaxTokens;
|
|
333
|
+
sentMaxTokensLane = builtMaxTokensLane;
|
|
334
|
+
},
|
|
335
|
+
};
|
|
322
336
|
},
|
|
323
337
|
makeParser: (ctrl) => {
|
|
324
338
|
const { out, partial } = ctrl;
|
|
@@ -557,7 +571,11 @@ export function createOpenAIBrain(config = {}) {
|
|
|
557
571
|
const refused = accumRefusal.trim().length > 0;
|
|
558
572
|
const emptyNoFinish = noUsableContent && finishReason == null && !degenerate;
|
|
559
573
|
const maxTokensNote = sentMaxTokens !== undefined
|
|
560
|
-
? `effective max_tokens=${sentMaxTokens} (from ${sentMaxTokensLane === "options"
|
|
574
|
+
? `effective max_tokens=${sentMaxTokens} (from ${sentMaxTokensLane === "options"
|
|
575
|
+
? "options.maxTokens"
|
|
576
|
+
: sentMaxTokensLane === "engine"
|
|
577
|
+
? "the engine's context-overflow recovery"
|
|
578
|
+
: "model.maxTokens"})`
|
|
561
579
|
: "max_tokens not set (neither options.maxTokens nor model.maxTokens — the provider's default cap applied)";
|
|
562
580
|
const errorMessage = degenerate
|
|
563
581
|
? DEGENERATE_MESSAGE
|
|
@@ -4,4 +4,6 @@ export declare const RESPONSES_RESERVED: ReadonlySet<string>;
|
|
|
4
4
|
export declare function reservedFor(api: string): ReadonlySet<string>;
|
|
5
5
|
export declare function applyExtraBody(body: Record<string, unknown>, extraBody: Record<string, unknown> | undefined, reserved: ReadonlySet<string>): Record<string, unknown>;
|
|
6
6
|
export declare function stripAuthHeaders(headers: Record<string, string>): void;
|
|
7
|
+
export declare const OUTPUT_CAP_KEYS: Readonly<Record<"openai" | "anthropic" | "responses", readonly string[]>>;
|
|
8
|
+
export declare function effectiveOutputCap(body: Record<string, unknown>, keys: readonly string[]): number | undefined;
|
|
7
9
|
export declare function reservedCollisions(extraBody: Record<string, unknown> | undefined, reserved: ReadonlySet<string>): string[];
|
|
@@ -61,6 +61,22 @@ export function stripAuthHeaders(headers) {
|
|
|
61
61
|
delete headers[k];
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
export const OUTPUT_CAP_KEYS = {
|
|
65
|
+
openai: ["max_tokens", "max_completion_tokens"],
|
|
66
|
+
anthropic: ["max_tokens"],
|
|
67
|
+
responses: ["max_output_tokens"],
|
|
68
|
+
};
|
|
69
|
+
export function effectiveOutputCap(body, keys) {
|
|
70
|
+
let smallest;
|
|
71
|
+
for (const key of keys) {
|
|
72
|
+
const raw = body[key];
|
|
73
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0)
|
|
74
|
+
continue;
|
|
75
|
+
if (smallest === undefined || raw < smallest)
|
|
76
|
+
smallest = raw;
|
|
77
|
+
}
|
|
78
|
+
return smallest;
|
|
79
|
+
}
|
|
64
80
|
export function reservedCollisions(extraBody, reserved) {
|
|
65
81
|
return extraBody ? Object.keys(extraBody).filter((k) => reserved.has(k)) : [];
|
|
66
82
|
}
|
|
@@ -5,10 +5,18 @@ export interface StreamEngineConfig extends BrainTimeoutConfig {
|
|
|
5
5
|
retryDelayMs?: number;
|
|
6
6
|
}
|
|
7
7
|
export declare function resolveMaxRetries(configured: number | undefined): number;
|
|
8
|
+
export declare function sameRequestModuloCap(original: SSERequest, rebuilt: SSERequest, imposedCap: number): boolean;
|
|
9
|
+
export declare function shouldRetryHeaderVerdict(res: Response | undefined): boolean | undefined;
|
|
8
10
|
export interface SSERequest {
|
|
9
11
|
url: string;
|
|
10
12
|
headers: Record<string, string>;
|
|
11
13
|
body: string;
|
|
14
|
+
outputCapTokens?: number;
|
|
15
|
+
capDerivedPaths?: readonly string[];
|
|
16
|
+
onCommitted?: () => void;
|
|
17
|
+
}
|
|
18
|
+
export interface BuildRequestOverrides {
|
|
19
|
+
maxOutputTokens?: number;
|
|
12
20
|
}
|
|
13
21
|
export interface StreamControls {
|
|
14
22
|
out: ReturnType<typeof createAssistantMessageEventStream>;
|
|
@@ -32,7 +40,7 @@ export declare function runStreamingBrain(args: {
|
|
|
32
40
|
signal?: AbortSignal;
|
|
33
41
|
config: StreamEngineConfig;
|
|
34
42
|
httpLabel: string;
|
|
35
|
-
buildRequest: () => SSERequest;
|
|
43
|
+
buildRequest: (overrides?: BuildRequestOverrides) => SSERequest;
|
|
36
44
|
makeParser: (ctrl: StreamControls) => StreamParser;
|
|
37
45
|
stallTimeouts?: {
|
|
38
46
|
connectMs?: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createAssistantMessageEventStream, } from "../internal/llm.js";
|
|
2
|
+
import { FLOOR_OUTPUT_TOKENS, parseContextOverflow, planOutputCapAdjustment } from "./context-overflow.js";
|
|
2
3
|
import { BrainError, classifyHttp, describeNetworkError } from "./errors.js";
|
|
3
4
|
import { retryBackoffMs } from "./retry.js";
|
|
4
5
|
import { emitBrainStatus, emitBrainTelemetry } from "./status-sink.js";
|
|
@@ -17,6 +18,169 @@ export function resolveMaxRetries(configured) {
|
|
|
17
18
|
}
|
|
18
19
|
return DEFAULT_MAX_RETRIES;
|
|
19
20
|
}
|
|
21
|
+
const SHOULD_RETRY_HEADER = "x-should-retry";
|
|
22
|
+
function sameHeaders(a, b) {
|
|
23
|
+
const aKeys = Object.keys(a);
|
|
24
|
+
if (aKeys.length !== Object.keys(b).length)
|
|
25
|
+
return false;
|
|
26
|
+
return aKeys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && a[k] === b[k]);
|
|
27
|
+
}
|
|
28
|
+
function canonicalJson(value) {
|
|
29
|
+
if (Array.isArray(value))
|
|
30
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
31
|
+
if (typeof value === "object" && value !== null) {
|
|
32
|
+
const rec = value;
|
|
33
|
+
return `{${Object.keys(rec)
|
|
34
|
+
.sort()
|
|
35
|
+
.map((k) => `${JSON.stringify(k)}:${canonicalJson(rec[k])}`)
|
|
36
|
+
.join(",")}}`;
|
|
37
|
+
}
|
|
38
|
+
return JSON.stringify(value) ?? "null";
|
|
39
|
+
}
|
|
40
|
+
function leafHolder(root, path) {
|
|
41
|
+
const parts = path.split(".");
|
|
42
|
+
const leaf = parts.pop();
|
|
43
|
+
if (leaf === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
let cur = root;
|
|
46
|
+
for (const part of parts) {
|
|
47
|
+
if (typeof cur !== "object" || cur === null || Array.isArray(cur))
|
|
48
|
+
return undefined;
|
|
49
|
+
cur = cur[part];
|
|
50
|
+
}
|
|
51
|
+
if (typeof cur !== "object" || cur === null || Array.isArray(cur))
|
|
52
|
+
return undefined;
|
|
53
|
+
return { holder: cur, leaf };
|
|
54
|
+
}
|
|
55
|
+
export function sameRequestModuloCap(original, rebuilt, imposedCap) {
|
|
56
|
+
if (rebuilt.url !== original.url || !sameHeaders(rebuilt.headers, original.headers))
|
|
57
|
+
return false;
|
|
58
|
+
if (rebuilt.outputCapTokens !== imposedCap)
|
|
59
|
+
return false;
|
|
60
|
+
if (rebuilt.body === original.body)
|
|
61
|
+
return true;
|
|
62
|
+
const parse = (raw) => {
|
|
63
|
+
let parsed;
|
|
64
|
+
try {
|
|
65
|
+
parsed = JSON.parse(raw);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
71
|
+
return undefined;
|
|
72
|
+
return parsed;
|
|
73
|
+
};
|
|
74
|
+
const a = parse(original.body);
|
|
75
|
+
const b = parse(rebuilt.body);
|
|
76
|
+
if (a === undefined || b === undefined)
|
|
77
|
+
return false;
|
|
78
|
+
for (const path of new Set([...(original.capDerivedPaths ?? []), ...(rebuilt.capDerivedPaths ?? [])])) {
|
|
79
|
+
const from = leafHolder(a, path);
|
|
80
|
+
const to = leafHolder(b, path);
|
|
81
|
+
const before = from === undefined ? undefined : from.holder[from.leaf];
|
|
82
|
+
const after = to === undefined ? undefined : to.holder[to.leaf];
|
|
83
|
+
if (before !== undefined) {
|
|
84
|
+
if (typeof before !== "number" || typeof after !== "number")
|
|
85
|
+
return false;
|
|
86
|
+
if (after > before)
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if (from !== undefined)
|
|
90
|
+
delete from.holder[from.leaf];
|
|
91
|
+
if (to !== undefined)
|
|
92
|
+
delete to.holder[to.leaf];
|
|
93
|
+
}
|
|
94
|
+
return canonicalJson(a) === canonicalJson(b);
|
|
95
|
+
}
|
|
96
|
+
const ERROR_BODY_BYTE_CAP = 64 * 1024;
|
|
97
|
+
const ERROR_BODY_READ_TIMEOUT_MS = 5_000;
|
|
98
|
+
async function readErrorBody(res, signal) {
|
|
99
|
+
const body = res.body;
|
|
100
|
+
if (body === null)
|
|
101
|
+
return { text: "", endedNormally: true };
|
|
102
|
+
let reader;
|
|
103
|
+
try {
|
|
104
|
+
reader = body.getReader();
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return { text: "", endedNormally: false };
|
|
108
|
+
}
|
|
109
|
+
const decoder = new TextDecoder();
|
|
110
|
+
let stopped = false;
|
|
111
|
+
const stop = () => {
|
|
112
|
+
stopped = true;
|
|
113
|
+
void reader.cancel().catch(() => undefined);
|
|
114
|
+
};
|
|
115
|
+
let timer;
|
|
116
|
+
let out = "";
|
|
117
|
+
let endedNormally = false;
|
|
118
|
+
try {
|
|
119
|
+
timer = setTimeout(stop, ERROR_BODY_READ_TIMEOUT_MS);
|
|
120
|
+
signal?.addEventListener("abort", stop, { once: true });
|
|
121
|
+
if (signal?.aborted === true)
|
|
122
|
+
stop();
|
|
123
|
+
let bytes = 0;
|
|
124
|
+
for (;;) {
|
|
125
|
+
const chunk = await reader.read();
|
|
126
|
+
if (chunk.done) {
|
|
127
|
+
endedNormally = !stopped;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
const value = chunk.value;
|
|
131
|
+
if (value === undefined)
|
|
132
|
+
continue;
|
|
133
|
+
const room = ERROR_BODY_BYTE_CAP - bytes;
|
|
134
|
+
const usable = value.byteLength <= room ? value : value.subarray(0, room);
|
|
135
|
+
bytes += usable.byteLength;
|
|
136
|
+
out += decoder.decode(usable, { stream: true });
|
|
137
|
+
if (bytes >= ERROR_BODY_BYTE_CAP) {
|
|
138
|
+
stop();
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
out += decoder.decode();
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
endedNormally = false;
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
if (timer !== undefined)
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
signal?.removeEventListener("abort", stop);
|
|
151
|
+
stop();
|
|
152
|
+
}
|
|
153
|
+
return { text: out, endedNormally };
|
|
154
|
+
}
|
|
155
|
+
function discardResponseBody(res) {
|
|
156
|
+
const body = res?.body;
|
|
157
|
+
if (body === undefined || body === null || body.locked)
|
|
158
|
+
return;
|
|
159
|
+
try {
|
|
160
|
+
void body.cancel().catch(() => undefined);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
export function shouldRetryHeaderVerdict(res) {
|
|
166
|
+
if (res === undefined)
|
|
167
|
+
return undefined;
|
|
168
|
+
let raw = null;
|
|
169
|
+
try {
|
|
170
|
+
const headers = res.headers;
|
|
171
|
+
if (headers === undefined || headers === null || typeof headers.get !== "function")
|
|
172
|
+
return undefined;
|
|
173
|
+
raw = res.headers.get(SHOULD_RETRY_HEADER);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
if (raw === "true")
|
|
179
|
+
return true;
|
|
180
|
+
if (raw === "false")
|
|
181
|
+
return false;
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
20
184
|
const RETRY_STATUS_SLICE_MS = 30_000;
|
|
21
185
|
const THINKING_RETRY_BUDGET = 2;
|
|
22
186
|
const EMPTY_USAGE = {
|
|
@@ -103,7 +267,9 @@ export function runStreamingBrain(args) {
|
|
|
103
267
|
});
|
|
104
268
|
return out;
|
|
105
269
|
async function run() {
|
|
106
|
-
|
|
270
|
+
let req = buildRequest();
|
|
271
|
+
req.onCommitted?.();
|
|
272
|
+
let maxOutputTokensOverride;
|
|
107
273
|
const maxRetries = resolveMaxRetries(config.maxRetries);
|
|
108
274
|
const baseDelay = config.retryDelayMs ?? 500;
|
|
109
275
|
const firstTokenTimeoutMs = resolveStallTimeoutMs(config.firstTokenTimeoutMs ?? stallTimeouts?.firstTokenMs, "firstTokenTimeoutMs");
|
|
@@ -146,33 +312,96 @@ export function runStreamingBrain(args) {
|
|
|
146
312
|
res = r;
|
|
147
313
|
break;
|
|
148
314
|
}
|
|
149
|
-
cc.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
315
|
+
cc.clearTimer();
|
|
316
|
+
try {
|
|
317
|
+
let overflowBody;
|
|
318
|
+
let overflowDeclineNote = "";
|
|
319
|
+
if (r !== undefined && r.status === 400) {
|
|
320
|
+
const read = await readErrorBody(r, signal);
|
|
321
|
+
overflowBody = read.text;
|
|
322
|
+
const report = read.endedNormally ? parseContextOverflow(r.status, read.text) : undefined;
|
|
323
|
+
if (report !== undefined) {
|
|
324
|
+
const plan = planOutputCapAdjustment(report, [maxOutputTokensOverride, req.outputCapTokens]);
|
|
325
|
+
if (plan.outcome === "adjust" && attempt < maxRetries) {
|
|
326
|
+
let rebuilt;
|
|
327
|
+
try {
|
|
328
|
+
rebuilt = buildRequest({ maxOutputTokens: plan.maxOutputTokens });
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
rebuilt = undefined;
|
|
332
|
+
}
|
|
333
|
+
let acceptable = false;
|
|
334
|
+
try {
|
|
335
|
+
acceptable = rebuilt !== undefined && sameRequestModuloCap(req, rebuilt, plan.maxOutputTokens);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
acceptable = false;
|
|
339
|
+
}
|
|
340
|
+
if (acceptable && rebuilt !== undefined) {
|
|
341
|
+
const previousCap = plan.current;
|
|
342
|
+
maxOutputTokensOverride = plan.maxOutputTokens;
|
|
343
|
+
req = rebuilt;
|
|
344
|
+
req.onCommitted?.();
|
|
345
|
+
emitBrainTelemetry({ kind: "retry", attempt: attempt + 1, phase: "connect" });
|
|
346
|
+
announcedRetry = true;
|
|
347
|
+
emitBrainStatus({
|
|
348
|
+
phase: "retrying",
|
|
349
|
+
detail: `output limit lowered from ${previousCap} to ${plan.maxOutputTokens} tokens, retrying`,
|
|
350
|
+
attempt: attempt + 1,
|
|
351
|
+
maxRetries,
|
|
352
|
+
});
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
overflowDeclineNote =
|
|
356
|
+
rebuilt === undefined
|
|
357
|
+
? ` — output-cap recovery declined: the request could not be rebuilt with a smaller cap`
|
|
358
|
+
: ` — output-cap recovery declined: rebuilding the request changed more than the output cap (its destination, headers, or some other body field moved), so this would no longer be the same request with a smaller cap`;
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
overflowDeclineNote =
|
|
362
|
+
plan.outcome === "below_floor"
|
|
363
|
+
? ` — output-cap recovery declined: only ${plan.available} tokens are available under the context limit, below the ${FLOOR_OUTPUT_TOKENS}-token floor; the input itself must shrink`
|
|
364
|
+
: plan.outcome === "no_progress"
|
|
365
|
+
? ` — output-cap recovery declined: the recomputed cap (${plan.available}) is not below the ${plan.current} already in effect, so re-sending cannot help`
|
|
366
|
+
: ` — output-cap recovery declined: the retry budget (${maxRetries}) is exhausted`;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const retryableByStatus = netErr !== undefined ||
|
|
371
|
+
(r !== undefined && (r.status >= 500 || r.status === 429 || r.status === 408 || r.status === 409));
|
|
372
|
+
const retryable = shouldRetryHeaderVerdict(r) ?? retryableByStatus;
|
|
373
|
+
if (retryable && attempt < maxRetries) {
|
|
374
|
+
const delayMs = retryBackoffMs(baseDelay, attempt, r);
|
|
375
|
+
const statusPhase = r?.status === 429 ? "rate_limited" : netErr !== undefined ? "reconnecting" : "retrying";
|
|
376
|
+
emitBrainTelemetry({ kind: "retry", attempt: attempt + 1, phase: "connect" });
|
|
377
|
+
announcedRetry = true;
|
|
378
|
+
discardResponseBody(r);
|
|
379
|
+
cc.abort();
|
|
380
|
+
cc.dispose();
|
|
381
|
+
await sleepAnnouncingRetry(delayMs, signal, (remainingMs) => ({
|
|
382
|
+
phase: statusPhase,
|
|
383
|
+
detail: statusPhase === "rate_limited"
|
|
384
|
+
? "rate limited, backing off"
|
|
385
|
+
: statusPhase === "reconnecting"
|
|
386
|
+
? "connection lost, reconnecting"
|
|
387
|
+
: "transient error, retrying",
|
|
388
|
+
retryInSec: Math.ceil(remainingMs / 1000),
|
|
389
|
+
retryInMs: remainingMs,
|
|
390
|
+
attempt: attempt + 1,
|
|
391
|
+
maxRetries,
|
|
392
|
+
}));
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (netErr)
|
|
396
|
+
throw new BrainError("network", describeNetworkError(netErr));
|
|
397
|
+
const detail = overflowBody ?? (r ? (await readErrorBody(r, signal)).text : "");
|
|
398
|
+
const status = r?.status ?? 0;
|
|
399
|
+
throw new BrainError(classifyHttp(status), `${httpLabel} HTTP ${status || "ERR"}: ${detail.slice(0, 500)}${overflowDeclineNote}`, status);
|
|
400
|
+
}
|
|
401
|
+
finally {
|
|
402
|
+
cc.abort();
|
|
403
|
+
cc.dispose();
|
|
170
404
|
}
|
|
171
|
-
if (netErr)
|
|
172
|
-
throw new BrainError("network", describeNetworkError(netErr));
|
|
173
|
-
const detail = r ? await r.text().catch(() => "") : "";
|
|
174
|
-
const status = r?.status ?? 0;
|
|
175
|
-
throw new BrainError(classifyHttp(status), `${httpLabel} HTTP ${status || "ERR"}: ${detail.slice(0, 500)}`, status);
|
|
176
405
|
}
|
|
177
406
|
const partial = emptyAssistant(model);
|
|
178
407
|
if (!startEmitted) {
|
package/dist/brain/timeout.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare function createConnectController(connectTimeoutMs: number | undef
|
|
|
12
12
|
signal: AbortSignal;
|
|
13
13
|
timedOut: () => boolean;
|
|
14
14
|
clearTimer: () => void;
|
|
15
|
+
abort: () => void;
|
|
15
16
|
dispose: () => void;
|
|
16
17
|
};
|
|
17
18
|
export declare const BRAIN_CALL_GUARDRAIL_DEFAULT_MS = 3600000;
|
package/dist/brain/timeout.js
CHANGED
package/dist/core/a2a.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import type { A2aServerSpec, ToolEffect } from "./types.js";
|
|
|
3
3
|
export declare const A2A_SKILL_DESCRIPTION_MAX_CHARS = 2048;
|
|
4
4
|
export interface A2aToolAxis {
|
|
5
5
|
name: string;
|
|
6
|
-
irreversibility?: "always";
|
|
7
|
-
egress?:
|
|
6
|
+
irreversibility?: "always" | "never";
|
|
7
|
+
egress?: boolean;
|
|
8
8
|
effect?: ToolEffect;
|
|
9
9
|
}
|
|
10
10
|
export interface A2aPeerStatus {
|
package/dist/core/a2a.js
CHANGED
|
@@ -325,9 +325,9 @@ function a2aAxisFor(name, override) {
|
|
|
325
325
|
if (override.effect !== undefined)
|
|
326
326
|
axis.effect = override.effect;
|
|
327
327
|
if (override.egress === false)
|
|
328
|
-
|
|
329
|
-
if (override.irreversibility
|
|
330
|
-
axis.irreversibility =
|
|
328
|
+
axis.egress = false;
|
|
329
|
+
if (override.irreversibility !== undefined)
|
|
330
|
+
axis.irreversibility = override.irreversibility;
|
|
331
331
|
return axis;
|
|
332
332
|
}
|
|
333
333
|
function renderParts(parts) {
|
|
@@ -17,6 +17,8 @@ export interface AskQuestionRequest {
|
|
|
17
17
|
questions: AskQuestion[];
|
|
18
18
|
readonly principal?: string;
|
|
19
19
|
readonly sourceTaskId?: string;
|
|
20
|
+
readonly boundInputHash?: string;
|
|
21
|
+
readonly deliveryId?: string;
|
|
20
22
|
}
|
|
21
23
|
export interface QuestionAnswerItem {
|
|
22
24
|
header: string;
|
|
@@ -26,10 +28,53 @@ export interface QuestionAnswerItem {
|
|
|
26
28
|
export interface QuestionAnswer {
|
|
27
29
|
answers: QuestionAnswerItem[];
|
|
28
30
|
}
|
|
29
|
-
export
|
|
31
|
+
export interface QuestionUnavailable {
|
|
32
|
+
readonly kind: "unavailable";
|
|
33
|
+
}
|
|
34
|
+
export type OnQuestionOutcome = QuestionAnswer | QuestionUnavailable;
|
|
35
|
+
export type QuestionOutcomeReading = {
|
|
36
|
+
readonly shape: "answered";
|
|
37
|
+
readonly answer: QuestionAnswer;
|
|
38
|
+
} | {
|
|
39
|
+
readonly shape: "unavailable";
|
|
40
|
+
} | {
|
|
41
|
+
readonly shape: "contradictory";
|
|
42
|
+
};
|
|
43
|
+
export declare function classifyQuestionOutcome(outcome: OnQuestionOutcome): QuestionOutcomeReading;
|
|
44
|
+
export declare function canonicalizeCapturedPlainData(node: unknown, seen?: WeakSet<object>): boolean;
|
|
45
|
+
export declare function isQuestionUnavailable(outcome: OnQuestionOutcome): outcome is QuestionUnavailable;
|
|
46
|
+
export type OnQuestion = (req: AskQuestionRequest, signal?: AbortSignal) => Promise<OnQuestionOutcome>;
|
|
47
|
+
export type SyntheticContinuationReason = "seam_absent" | "callback_failed" | "declined_unavailable";
|
|
48
|
+
export type AskAnswerContinuationSource = "human_response" | "synthetic_self_answer_instruction";
|
|
49
|
+
export type AskQuestionCardDetails = {
|
|
50
|
+
type: "ask-question";
|
|
51
|
+
questionId: string;
|
|
52
|
+
continuationSource: AskAnswerContinuationSource;
|
|
53
|
+
runContinues: true;
|
|
54
|
+
reason?: SyntheticContinuationReason;
|
|
55
|
+
} | {
|
|
56
|
+
type: "ask-question";
|
|
57
|
+
questionId: string;
|
|
58
|
+
code: "question.human_channel_failed" | "question.human_unavailable";
|
|
59
|
+
reason: "callback_failed" | "declined_unavailable";
|
|
60
|
+
};
|
|
61
|
+
export declare function askQuestionContinuationCard(questionId: string, continuationSource: AskAnswerContinuationSource, reason?: SyntheticContinuationReason): AskQuestionCardDetails;
|
|
62
|
+
export declare function validateAskQuestions(questions: unknown): string | undefined;
|
|
63
|
+
export interface AskUserQuestionToolOptions {
|
|
64
|
+
posture?: "interactive" | "headless";
|
|
65
|
+
interactiveFallback?: boolean;
|
|
66
|
+
onSyntheticContinuation?: (info: {
|
|
67
|
+
questionId: string;
|
|
68
|
+
reason: SyntheticContinuationReason;
|
|
69
|
+
}) => void;
|
|
70
|
+
redeemedApprovalCallId?: string;
|
|
71
|
+
redeemedApprovalQuestionsHash?: string;
|
|
72
|
+
}
|
|
30
73
|
export declare function createDurableQuestionPolicy(): ToolPolicy;
|
|
31
74
|
export declare const QUESTION_AWAITS_RESUME: OnQuestion;
|
|
75
|
+
export declare function markBoundOnlyQuestionFace<F extends OnQuestion>(face: F): F;
|
|
76
|
+
export declare function isLiveQuestionFace(face: unknown): face is OnQuestion;
|
|
32
77
|
export declare function createAskUserQuestionTool(onQuestion?: OnQuestion, source?: {
|
|
33
78
|
principal?: string;
|
|
34
79
|
sourceTaskId?: string;
|
|
35
|
-
}): AgentTool;
|
|
80
|
+
}, opts?: AskUserQuestionToolOptions): AgentTool;
|