@sema-agent/core 5.7.0 → 5.8.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 +37 -0
- package/dist/agents/cascade.d.ts +1 -1
- package/dist/agents/subagent.d.ts +4 -7
- package/dist/agents/subagent.js +10 -19
- package/dist/agents/teacher.js +3 -3
- package/dist/agents/team.d.ts +1 -1
- package/dist/agents/team.js +1 -1
- package/dist/agents/verify.js +1 -1
- package/dist/brain/anthropic.js +24 -35
- package/dist/brain/openai.js +22 -33
- package/dist/brain/stream-engine.d.ts +0 -2
- package/dist/brain/stream-engine.js +5 -47
- package/dist/brain/stream-shared.d.ts +0 -10
- package/dist/brain/stream-shared.js +0 -23
- package/dist/brain/terminal-cause.d.ts +0 -1
- package/dist/brain/terminal-cause.js +0 -3
- package/dist/brain/timeout.d.ts +4 -2
- package/dist/brain/timeout.js +4 -14
- package/dist/config/catalog.d.ts +3 -11
- package/dist/config/catalog.js +30 -69
- package/dist/config/defaults.d.ts +0 -3
- package/dist/config/defaults.js +0 -3
- package/dist/core/auto-compaction.d.ts +2 -11
- package/dist/core/auto-compaction.js +3 -67
- package/dist/core/checkpoint-store.d.ts +10 -8
- package/dist/core/checkpoint-store.js +7 -7
- package/dist/core/memory-engine/file-backend.js +20 -17
- package/dist/core/memory-engine/layout.d.ts +5 -0
- package/dist/core/memory-engine/layout.js +72 -47
- package/dist/core/runner/assemble-result.d.ts +2 -8
- package/dist/core/runner/assemble-result.js +15 -16
- package/dist/core/runner/prepare-task.d.ts +26 -6
- package/dist/core/runner/prepare-task.js +203 -141
- package/dist/core/runner/runtask.d.ts +0 -2
- package/dist/core/runner/runtask.js +200 -244
- package/dist/core/session-policy-store.d.ts +5 -0
- package/dist/core/session-policy-store.js +3 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
- package/dist/core/task-registry-agent.d.ts +2 -0
- package/dist/core/task-registry-agent.js +33 -1
- package/dist/core/task-registry.d.ts +2 -0
- package/dist/core/task-registry.js +9 -1
- package/dist/core/tool-errors.js +10 -2
- package/dist/core/trace.d.ts +1 -8
- package/dist/core/types.d.ts +24 -30
- package/dist/core/usage-window-store.d.ts +39 -0
- package/dist/core/usage-window-store.js +115 -0
- package/dist/engine/execution-env/node-execution-env.js +0 -22
- package/dist/engine/harness/agent-harness.d.ts +0 -7
- package/dist/engine/harness/agent-harness.js +13 -53
- package/dist/engine/harness/types.d.ts +2 -15
- package/dist/engine/llm/types.d.ts +1 -2
- package/dist/engine/loop/agent-loop.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +5 -171
- package/dist/engine/loop/types.d.ts +0 -18
- package/dist/index.d.ts +10 -5
- package/dist/index.js +8 -4
- package/dist/orchestration/goal.js +1 -5
- package/dist/orchestration/run-workflow-tool.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.js +1 -2
- package/dist/orchestration/workflow-governance.d.ts +6 -6
- package/dist/orchestration/workflow-governance.js +33 -47
- package/dist/prompt-assembly/event-registry.js +2 -3
- package/dist/stores/cc/lockfile.js +9 -11
- package/dist/stores/cc/mailbox-store.js +49 -38
- package/dist/stores/file/index.d.ts +3 -0
- package/dist/stores/file/index.js +4 -0
- package/dist/stores/file/usage-window-store.d.ts +9 -0
- package/dist/stores/file/usage-window-store.js +86 -0
- package/dist/tools/fs/fs-bash.d.ts +1 -10
- package/dist/tools/fs/fs-bash.js +20 -60
- package/dist/tools/fs/index.d.ts +0 -2
- package/dist/tools/fs/index.js +1 -2
- package/package.json +1 -4
- package/dist/bin/sema-tb.d.ts +0 -31
- package/dist/bin/sema-tb.js +0 -448
- package/dist/bin/tb-env.d.ts +0 -2
- package/dist/bin/tb-env.js +0 -17
- package/dist/brain/walltime.d.ts +0 -1
- package/dist/brain/walltime.js +0 -1
- package/dist/core/runner/call-cap.d.ts +0 -67
- package/dist/core/runner/call-cap.js +0 -145
- package/dist/core/runner/cut-kill.d.ts +0 -10
- package/dist/core/runner/cut-kill.js +0 -37
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.8.0 (2026-08-04)
|
|
4
|
+
|
|
5
|
+
_The limits restructure: time leaves the task-limit axis, tokens become the primary budget, and every default is empty. One release, five construction stages — there is no intermediate version. Old usage fails loudly (compile errors for removed fields; a typed refusal for unknown limit keys at runtime) — nothing is silently ignored._
|
|
6
|
+
|
|
7
|
+
### BREAKING — task limits (`TaskSpec.limits` is the one budget surface)
|
|
8
|
+
|
|
9
|
+
- `limits` now holds every budget knob: `maxTokens` (primary, cumulative per-slice window — promoted from report-only to a GATE) / `maxCostUsd` / `maxTurns` / `maxWalltimeMs` (opt-in, per-slice active clock, **milliseconds**) / `maxOutputTokens` (unchanged, per-request shape) / `approachNotice` / `budgetStreamCancel` / `degrade` / `brainCallGuardrailMs`. The former top-level `maxTokens` / `maxCostUsd` / `budgetStreamCancel` / `degrade` fields are gone.
|
|
10
|
+
- `limits.timeoutSec` is REMOVED (unit and semantics changed → `maxWalltimeMs`). An unknown key on `limits` is refused at prepare with `config.limit_unknown_key` naming the replacement and the unit; a non-finite or negative value refuses with `config.limit_invalid`. Never silently ignored.
|
|
11
|
+
- EVERY default is empty: `DEFAULT_MAX_TURNS=1000`, `FORK_DEFAULT_MAX_TURNS=200`, the session-scoped background child's injected 30-minute wall clock, the resource-suspend `maxSlices=100` default, and the workflow per-agent 300s cap are all retired. No limit is armed unless configured. Anti-zombie coverage for session-scoped background children moved to the stale-running reaper (`TaskRegistry.reapStaleSessionBackgroundAgents` — collects by STALL, not by wall age). Honest consequence, stated plainly: a zero-config task with a non-terminating brain loop now has no task-level backstop — the engineering guardrails (brain-call guardrail, stall detection) keep it from wedging silently, and the deployment governance windows are the burn ceiling.
|
|
12
|
+
- To-limit behavior is two-tier: WITHOUT `resourceSuspend`, hitting any limit is a loud terminal failure — `TaskResult.status` loses `"timeout"`; all four axes end as `failed` with `limits.max_tokens_exceeded` / `limits.max_cost_exceeded` / `limits.max_turns_exceeded` / `limits.max_walltime_exceeded` (the code names the AXIS, replacing `limit.timeout` / `limit.max_turns` / `budget.exceeded` / `budget.precall`), salvage attached. WITH `resourceSuspend` (+ checkpoint infrastructure), a per-slice window exhaustion suspends with a checkpoint and resume grants the next window; only the optional frozen totals (`resourceSuspend.totalTokens` / `totalBudgetUsd`) are hard stops. `ResourceLimitReason` splits `"budget"` into `"budget_tokens"` / `"budget_cost"` — consumers pinning either closed set are affected and named here.
|
|
13
|
+
- Priority when several axes trip at one boundary: tokens > cost > turns > walltime (attribution only — every configured axis stays armed).
|
|
14
|
+
- `limits.approachNotice` replaces `deadlineNudge`: one "start converging" frame at ~80% and one "deliver now" at ~95% of the tightest configured budget, measured in BUDGET (tokens/cost/turns), never in clock; one-shot; `stats.mechanisms.approachNoticesSent`; a continuation gate keeps the notice from extending a run the model had already ended.
|
|
15
|
+
|
|
16
|
+
### BREAKING — retired mechanisms
|
|
17
|
+
|
|
18
|
+
- The design/128/130 deadline family is gone: `deadlineNudge` / `callCapByDeadline` / `gracefulFinalize`, the write-out cushion mathematics, and the per-call deadline shrink.
|
|
19
|
+
- tool-cut is gone end to end: `stats.mechanisms.{toolCuts,toolCutKills,pendingCutTools,finalizeInjected,nudgesSent,capShrinks,callCutoffs,toolClamps}`, `engineCut:"walltime_writeout"`, the bash exec deadline clamp, and `cut-kill.ts`.
|
|
20
|
+
- The brain soft-cutoff is gone: `errorKind:"walltime_cutoff"`, `WALLTIME_CUTOFF_MESSAGE`, `StreamOptions.callDeadlineMs`, the loop's `walltime_cutoff_recovery`.
|
|
21
|
+
- auto-compaction's walltime gate / abort race / throughput EWMA are gone (`clampReason` loses `"walltime"`).
|
|
22
|
+
- Stall detection (connect / first-token / idle) is DECOUPLED from the retired walltime lane and now arms unconditionally (guardrail class; constants renamed `STALL_*`; knob semantics unchanged).
|
|
23
|
+
- The `ExecutionEnv.exec` `onSpawn` / `onDetachAdopted` seam is retired (its only consumer left with tool-cut; zero producers remained).
|
|
24
|
+
- The Terminal-Bench surface is retired: the `sema-tb` bin, `tb-env`, the `tb/` adapter tree, and all thirteen `TB_*` env knobs. No switch survives.
|
|
25
|
+
|
|
26
|
+
### BREAKING — checkpoint / ledger / config
|
|
27
|
+
|
|
28
|
+
- `ResourceLedger.totalWalltimeSec` and `remainingWalltimeMs()` retired; `totalTokens` + `remainingTokens()` added; `spentWalltimeMs` REMAINS (accounting only). Checkpoint version 5 (`TOKEN_CHECKPOINT_VERSION`); a v4 checkpoint carrying the retired wall-clock allocation is refused pre-CAS with `checkpoint.walltime_axis_retired` (the checkpoint stays pending — finish it on the previous release, or start fresh; no silent migration). `resourceSuspend.totalWalltimeSec` → `resourceSuspend.totalTokens`.
|
|
29
|
+
- Config catalog: `limits.{timeoutSec,deadlineNudge,callCapByDeadline,gracefulFinalize}`, `budget.{maxCostUsd,maxTokens,totalWalltimeSec}`, `delegation.{forkMaxTurns,sessionBgTimeoutSec}` retired/moved; `limits.{maxWalltimeMs,approachNotice,maxTokens,maxCostUsd}`, `budget.totalTokens` added. Workflow script `limits` whitelist: `{maxTurns,timeoutSec}` → `{maxTurns,maxWalltimeMs,maxTokens,maxCostUsd}`; `WorkflowChildCaps.perAgentTimeoutSec` → `perAgentMaxWalltimeMs` (no default).
|
|
30
|
+
- Workflow resume journals from 5.7 cache-miss entirely on this version (the spec shape participates in the call identity) — reruns, not errors.
|
|
31
|
+
- Trace: the eight mechanisms fields above are gone; `timerLatenessMs` / `walltimeSyncBackstopFired` REMAIN (the per-slice wall timer survives as `maxWalltimeMs`, so its starvation evidence keeps a producer).
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- memory-engine: partition adoption is a single atomic rename (the delete-then-rename window could destroy a concurrently adopted partition and then disclose the opposite of what happened); fileless-scaffolding cleanup uses `rmdir` only, so a peer's files are unremovable by construction. Both control-plane lock waits are bounded and yield every round — a permanent errno can no longer starve the event loop in a silent spin (the fail-open sidecar lock now honors its documented "proceed anyway" by going lockless after three post-deadline steal rounds).
|
|
36
|
+
- cc stores: the mailbox sweep AND `drop` now judge and delete inside the box's cross-process lock (a compliant writer's freshly committed message can no longer be deleted unseen, and another holder's lock directory is never torn down); the cc lockfile's retry bound is read before every round (three `continue` shortcuts skipped it — a permanently failing reclaim spun forever); the lease table's entry keys fold the same way the table itself does (two store instances over one directory via a symlink could each grant the same lease).
|
|
37
|
+
- session policy: the tighten gate's path-containment fold is win-family-conditional, matching the runtime's — a POSIX sibling directory whose literal name embeds backslashes no longer reads as "within" the confined root (a loosening the gate exists to refuse). The transcript-dir gate's write-tool literal gains a mechanical sync pin against `PATH_WRITE_TOOLS`.
|
|
38
|
+
- `tool-name-aliases` docstring named a compile-time enforcement symbol that never existed; it now states the real division of labor (the prepare-time audit escalates, the compile pass deliberately does not).
|
|
39
|
+
|
|
3
40
|
## 5.7.0 (2026-08-04)
|
|
4
41
|
|
|
5
42
|
_Three collected cars: a parked question is answered by the decision itself (behavior face — see the consumer note), a descendant's terminal notification anchors on the delegation tree, and the env scrub stops deleting infrastructure key names (behavior face)._
|
package/dist/agents/cascade.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type GateVerdict = boolean | {
|
|
|
6
6
|
};
|
|
7
7
|
export interface CascadeRung {
|
|
8
8
|
model: ModelRef;
|
|
9
|
-
overrides?: Partial<Pick<TaskSpec, "limits" | "
|
|
9
|
+
overrides?: Partial<Pick<TaskSpec, "limits" | "systemPrompt">>;
|
|
10
10
|
}
|
|
11
11
|
export interface CascadeConfig {
|
|
12
12
|
onNotifyError?: (failure: import("../core/safe-notify.js").SafeNotifyFailure) => void;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Runner } from "../core/runner/runtask.js";
|
|
2
|
-
import type { AgentDefinition, Model, ModelRef, ToolSpec } from "../core/types.js";
|
|
2
|
+
import type { AgentDefinition, Model, ModelRef, TaskLimits, ToolSpec } from "../core/types.js";
|
|
3
3
|
import { type ExecutionEnv } from "../internal/harness.js";
|
|
4
4
|
import type { RunInternals } from "../core/runner/prepare-task.js";
|
|
5
5
|
import type { TaskNotificationPayload } from "../core/task-notification.js";
|
|
6
|
-
import {
|
|
7
|
-
export {
|
|
6
|
+
import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX } from "../config/defaults.js";
|
|
7
|
+
export { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX };
|
|
8
8
|
import { SubagentRetainLedger } from "./retain-ledger.js";
|
|
9
9
|
export type { SubagentStep, SubagentEditedFile } from "./subagent-steps.js";
|
|
10
10
|
export declare function notifyResultField(result: string | undefined): string | undefined;
|
|
@@ -101,10 +101,7 @@ export interface SubagentToolOptions {
|
|
|
101
101
|
systemPrompt?: string;
|
|
102
102
|
name?: string;
|
|
103
103
|
purpose?: string;
|
|
104
|
-
limits?:
|
|
105
|
-
maxTurns?: number;
|
|
106
|
-
timeoutSec?: number;
|
|
107
|
-
};
|
|
104
|
+
limits?: TaskLimits;
|
|
108
105
|
maxDepth?: number;
|
|
109
106
|
retainFailedSessions?: boolean;
|
|
110
107
|
onObserverError?: (err: unknown, info: {
|
package/dist/agents/subagent.js
CHANGED
|
@@ -17,7 +17,7 @@ import { addWorktree } from "../core/git-worktree-env.js";
|
|
|
17
17
|
import { shellQuote } from "../tools/fs/search.js";
|
|
18
18
|
import { BG_AGENT_REAP_STOP_ERROR } from "../core/task-registry.js";
|
|
19
19
|
import { extractErrorCode } from "../brain/errors.js";
|
|
20
|
-
import {
|
|
20
|
+
import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX, RUNNING_AGENT_OBSERVE_EVERY_BEATS } from "../config/defaults.js";
|
|
21
21
|
function rollupDelegatedCost(stats, nested) {
|
|
22
22
|
if (stats.costMicroUsd === undefined)
|
|
23
23
|
return undefined;
|
|
@@ -29,7 +29,7 @@ function delegatedCostField(stats) {
|
|
|
29
29
|
const total = rollupDelegatedCost(stats, stats.nested);
|
|
30
30
|
return total !== undefined ? { costMicroUsd: total } : {};
|
|
31
31
|
}
|
|
32
|
-
export {
|
|
32
|
+
export { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX };
|
|
33
33
|
import { SUBAGENT_RESUME_CAP, SubagentRetainLedger, getOrCreateSessionRetainLedger, ensureSessionReapHook, createResumePrompt, } from "./retain-ledger.js";
|
|
34
34
|
import { recordRosterSpawn } from "./roster-store.js";
|
|
35
35
|
import { ObserverDigestTap, ObserverPairing, createObserverReportToolSpec, markObserverTaskId, unmarkObserverTaskId, isObserverTaskId, ObserverResumeStateError, ObserverStoppedByUserError, observerFramingPrompt, observerSlug, resolveObserverDeclaration, } from "./observer.js";
|
|
@@ -224,7 +224,7 @@ export function classifySubagentError(child) {
|
|
|
224
224
|
? "rate_limit"
|
|
225
225
|
: code === "server" && (msg.includes("overloaded") || msg.includes("529"))
|
|
226
226
|
? "overloaded"
|
|
227
|
-
: code === "
|
|
227
|
+
: code === "limits.max_walltime_exceeded" || ((code === "network" || code === "server") && msg.includes("timeout"))
|
|
228
228
|
? "timeout"
|
|
229
229
|
: code === "network" || code === "server"
|
|
230
230
|
? "network"
|
|
@@ -237,7 +237,7 @@ function errorKindClause(c) {
|
|
|
237
237
|
const FAILED_SESSION_RETAIN_TTL_MS = 15 * 60 * 1000;
|
|
238
238
|
const PARTIAL_FINDINGS_MAX_CHARS = 1200;
|
|
239
239
|
const BG_NOTIFY_DRAIN_WINDOW_MS = 2_000;
|
|
240
|
-
function createBgActivityBeat(parentToolCallId, emitTick) {
|
|
240
|
+
function createBgActivityBeat(parentToolCallId, emitTick, noteActivity) {
|
|
241
241
|
let beats = 0;
|
|
242
242
|
let starts = 0;
|
|
243
243
|
return (e) => {
|
|
@@ -248,6 +248,7 @@ function createBgActivityBeat(parentToolCallId, emitTick) {
|
|
|
248
248
|
if (e.type === "tool_start")
|
|
249
249
|
starts += 1;
|
|
250
250
|
beats += 1;
|
|
251
|
+
noteActivity?.();
|
|
251
252
|
if (beats === 1 || beats % RUNNING_AGENT_OBSERVE_EVERY_BEATS === 0)
|
|
252
253
|
emitTick(starts);
|
|
253
254
|
};
|
|
@@ -395,7 +396,7 @@ export function createSubagentResume(deps) {
|
|
|
395
396
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
396
397
|
usage: { toolUses: toolStarts },
|
|
397
398
|
});
|
|
398
|
-
});
|
|
399
|
+
}, deps.registry !== undefined && deps.taskId !== undefined ? () => deps.registry.noteBackgroundAgentActivity(deps.taskId) : undefined);
|
|
399
400
|
stream = childRunner.runTaskStream(resumeSpec, undefined, {
|
|
400
401
|
...entry.internalsSnapshot,
|
|
401
402
|
...(true
|
|
@@ -1149,11 +1150,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1149
1150
|
const childSystemPrompt = def?.systemPrompt ?? opts.systemPrompt;
|
|
1150
1151
|
const childDefaultPersona = childSystemPrompt === undefined && !wantsFork ? SUBAGENT_PROMPT : undefined;
|
|
1151
1152
|
const defMaxTurns = typeof def?.maxTurns === "number" && Number.isFinite(def.maxTurns) && def.maxTurns > 0 ? def.maxTurns : undefined;
|
|
1152
|
-
const childLimits = defMaxTurns !== undefined
|
|
1153
|
-
? { ...opts.limits, maxTurns: defMaxTurns }
|
|
1154
|
-
: wantsFork && opts.limits?.maxTurns === undefined
|
|
1155
|
-
? { ...opts.limits, maxTurns: FORK_DEFAULT_MAX_TURNS }
|
|
1156
|
-
: opts.limits;
|
|
1153
|
+
const childLimits = defMaxTurns !== undefined ? { ...opts.limits, maxTurns: defMaxTurns } : opts.limits;
|
|
1157
1154
|
const nextExcluded = def ? new Set([...excluded, def.name]) : excluded;
|
|
1158
1155
|
const defAllowsDelegation = def === undefined || toolNameAllowed(opts.name ?? DEFAULT_SUBAGENT_TOOL_NAME, def.allowTools, def.denyTools);
|
|
1159
1156
|
if (depth + 1 < maxDepth && defAllowsDelegation) {
|
|
@@ -1233,7 +1230,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1233
1230
|
enableBlockedReport: false,
|
|
1234
1231
|
limits: {
|
|
1235
1232
|
maxTurns: typeof observerDef.maxTurns === "number" && Number.isFinite(observerDef.maxTurns) && observerDef.maxTurns > 0 ? observerDef.maxTurns : 8,
|
|
1236
|
-
|
|
1233
|
+
maxWalltimeMs: 120_000,
|
|
1237
1234
|
},
|
|
1238
1235
|
});
|
|
1239
1236
|
const runObserverLeg = async (spec) => {
|
|
@@ -1722,9 +1719,6 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1722
1719
|
});
|
|
1723
1720
|
const forkBgStartedAt = Date.now();
|
|
1724
1721
|
const bgForkSpec = { ...buildChildSpec(abort.signal), sessionId: forkedId, requireExistingSession: true, objective: forkObjective };
|
|
1725
|
-
if (sessionScopedBg && bgForkSpec.limits?.timeoutSec === undefined) {
|
|
1726
|
-
bgForkSpec.limits = { ...(bgForkSpec.limits ?? {}), timeoutSec: SESSION_BG_DEFAULT_TIMEOUT_SEC };
|
|
1727
|
-
}
|
|
1728
1722
|
const s2ForkNotifyReady = (inject) => {
|
|
1729
1723
|
bg.registry.attachAgentNotify(taskId, inject);
|
|
1730
1724
|
};
|
|
@@ -1744,7 +1738,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1744
1738
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
1745
1739
|
usage: { toolUses: toolStarts },
|
|
1746
1740
|
});
|
|
1747
|
-
});
|
|
1741
|
+
}, () => bg.registry.noteBackgroundAgentActivity(taskId));
|
|
1748
1742
|
const bgForkInternals = bgSink
|
|
1749
1743
|
? {
|
|
1750
1744
|
...forkInternals,
|
|
@@ -2195,9 +2189,6 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2195
2189
|
...(forwardDurableApproval ? { durableApproval: { ...ctx.durableApprovalForChildren } } : {}),
|
|
2196
2190
|
...(reviveRow !== undefined ? { requireExistingSession: true } : {}),
|
|
2197
2191
|
};
|
|
2198
|
-
if ((sessionScopedBg || reviveRow !== undefined) && bgSpec.limits?.timeoutSec === undefined) {
|
|
2199
|
-
bgSpec.limits = { ...(bgSpec.limits ?? {}), timeoutSec: SESSION_BG_DEFAULT_TIMEOUT_SEC };
|
|
2200
|
-
}
|
|
2201
2192
|
let reviveAttachedResolve;
|
|
2202
2193
|
const reviveAttached = reviveRow !== undefined ? new Promise((r) => (reviveAttachedResolve = r)) : undefined;
|
|
2203
2194
|
const s2NotifyReady = (inject) => {
|
|
@@ -2222,7 +2213,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2222
2213
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
2223
2214
|
usage: { toolUses: toolStarts },
|
|
2224
2215
|
});
|
|
2225
|
-
});
|
|
2216
|
+
}, () => bg.registry.noteBackgroundAgentActivity(taskId));
|
|
2226
2217
|
const bgInternals = bgSink
|
|
2227
2218
|
? {
|
|
2228
2219
|
...childInternals,
|
package/dist/agents/teacher.js
CHANGED
|
@@ -192,7 +192,7 @@ async function runTeacherCore(runner, studentSpec, teacher) {
|
|
|
192
192
|
...helperBase(),
|
|
193
193
|
objective: helperObjective,
|
|
194
194
|
enableBlockedReport: false,
|
|
195
|
-
limits: { maxTurns: 1,
|
|
195
|
+
limits: { maxTurns: 1, ...(studentSpec.limits?.maxWalltimeMs !== undefined ? { maxWalltimeMs: studentSpec.limits.maxWalltimeMs } : {}) },
|
|
196
196
|
signal: extSignal,
|
|
197
197
|
});
|
|
198
198
|
await runner.sessions.release(r.sessionId).catch(() => undefined);
|
|
@@ -341,7 +341,7 @@ async function runTeacherCore(runner, studentSpec, teacher) {
|
|
|
341
341
|
...teacherModelFields(),
|
|
342
342
|
systemPrompt: teacher.prompts?.teacher ?? TEACHER_PROMPT,
|
|
343
343
|
enableBlockedReport: false,
|
|
344
|
-
limits: { maxTurns: 4,
|
|
344
|
+
limits: { maxTurns: 4, ...(studentSpec.limits?.maxWalltimeMs !== undefined ? { maxWalltimeMs: studentSpec.limits.maxWalltimeMs } : {}) },
|
|
345
345
|
signal: extSignal,
|
|
346
346
|
});
|
|
347
347
|
await runner.sessions.release(r.sessionId).catch(() => undefined);
|
|
@@ -363,7 +363,7 @@ async function runTeacherCore(runner, studentSpec, teacher) {
|
|
|
363
363
|
else if (result.status === "blocked") {
|
|
364
364
|
trigger = "blocked";
|
|
365
365
|
}
|
|
366
|
-
else if (result.status === "failed"
|
|
366
|
+
else if (result.status === "failed") {
|
|
367
367
|
trigger = "failed";
|
|
368
368
|
}
|
|
369
369
|
else if (!verifyOutput) {
|
package/dist/agents/team.d.ts
CHANGED
package/dist/agents/team.js
CHANGED
|
@@ -63,7 +63,7 @@ export async function runTeamDiscussion(opts) {
|
|
|
63
63
|
: undefined;
|
|
64
64
|
const memberLimits = opts.limits
|
|
65
65
|
? {
|
|
66
|
-
...(opts.limits.
|
|
66
|
+
...(opts.limits.maxWalltimeMs !== undefined ? { maxWalltimeMs: opts.limits.maxWalltimeMs } : {}),
|
|
67
67
|
...(opts.limits.maxTurns !== undefined ? { maxTurns: opts.limits.maxTurns } : {}),
|
|
68
68
|
}
|
|
69
69
|
: undefined;
|
package/dist/agents/verify.js
CHANGED
|
@@ -101,7 +101,7 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
|
|
|
101
101
|
handsReadOnly: config.verifierHandsReadOnly ?? true,
|
|
102
102
|
outputSchema: VerdictSchema,
|
|
103
103
|
enableBlockedReport: false,
|
|
104
|
-
limits: {
|
|
104
|
+
limits: { ...(specBase.limits?.maxWalltimeMs !== undefined ? { maxWalltimeMs: specBase.limits.maxWalltimeMs } : {}) },
|
|
105
105
|
getApiKeyAndHeaders: specBase.getApiKeyAndHeaders,
|
|
106
106
|
signal: specBase.signal,
|
|
107
107
|
});
|
package/dist/brain/anthropic.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import {} from "../internal/llm.js";
|
|
2
2
|
import { BrainError } from "./errors.js";
|
|
3
3
|
import { DEGENERATE_MESSAGE, trimDegenerateTail } from "./repetition.js";
|
|
4
|
-
import { createRepetitionPoll,
|
|
4
|
+
import { createRepetitionPoll, parseStreamedToolArgs } from "./stream-shared.js";
|
|
5
5
|
import { mintFallbackToolCallId } from "./tool-call-id.js";
|
|
6
|
-
import { WALLTIME_CUTOFF_MESSAGE } from "./walltime.js";
|
|
7
6
|
import { emitBrainTelemetry } from "./status-sink.js";
|
|
8
7
|
import { errorResultMediaNote, IMAGE_OMITTED_NO_VISION, modelSupportsVision, sendableImages } from "./media-degrade.js";
|
|
9
8
|
import { ANTHROPIC_RESERVED, applyExtraBody, stripAuthHeaders } from "./request-params.js";
|
|
@@ -233,7 +232,6 @@ export function createAnthropicBrain(config = {}) {
|
|
|
233
232
|
signal: options?.signal,
|
|
234
233
|
config,
|
|
235
234
|
httpLabel: "anthropic",
|
|
236
|
-
callDeadlineMs: options?.callDeadlineMs,
|
|
237
235
|
stallTimeouts: options?.stallTimeouts,
|
|
238
236
|
buildRequest: () => {
|
|
239
237
|
const apiKey = options?.apiKey ?? config.apiKey;
|
|
@@ -342,7 +340,6 @@ export function createAnthropicBrain(config = {}) {
|
|
|
342
340
|
let degenerate = false;
|
|
343
341
|
let degenTextBlock;
|
|
344
342
|
const rep = createRepetitionPoll();
|
|
345
|
-
const walltime = createWalltimeGate({ callDeadlineMs: options?.callDeadlineMs, isDegenerate: () => degenerate, cancel: () => ctrl.cancel() });
|
|
346
343
|
let lastDegenCheck = 0;
|
|
347
344
|
let lastDegenCheckThinking = 0;
|
|
348
345
|
const detectRep = config.detectRepetition !== false;
|
|
@@ -497,10 +494,7 @@ export function createAnthropicBrain(config = {}) {
|
|
|
497
494
|
}
|
|
498
495
|
}
|
|
499
496
|
},
|
|
500
|
-
onDeadline: () => walltime.onDeadline(),
|
|
501
497
|
onLine(rawLine) {
|
|
502
|
-
if (walltime.shouldDropLine())
|
|
503
|
-
return;
|
|
504
498
|
const line = rawLine.trim();
|
|
505
499
|
if (!line.startsWith("data:"))
|
|
506
500
|
return;
|
|
@@ -576,40 +570,35 @@ export function createAnthropicBrain(config = {}) {
|
|
|
576
570
|
const staticReasoningCut = truncatedEmpty && !dynamicCut && reasoningSeen && options?.staticReasoningCutDowngrade === true;
|
|
577
571
|
const safetyCut = stopReason === "refusal";
|
|
578
572
|
const contextWindowExceeded = stopReason === "model_context_window_exceeded";
|
|
579
|
-
const emptyNoFinish = noUsableContent && stopReason == null && !degenerate
|
|
573
|
+
const emptyNoFinish = noUsableContent && stopReason == null && !degenerate;
|
|
580
574
|
const maxTokensNote = `effective max_tokens=${sentMaxTokens ?? "?"} (from ${sentMaxTokensLane === "options" ? "options.maxTokens" : sentMaxTokensLane === "model" ? "model.maxTokens" : "config default"})`;
|
|
581
|
-
const errorMessage =
|
|
582
|
-
?
|
|
583
|
-
:
|
|
584
|
-
?
|
|
585
|
-
:
|
|
586
|
-
? `[
|
|
587
|
-
:
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
? `[length_empty] response truncated at max_tokens with no answer text (${maxTokensNote}) —
|
|
593
|
-
:
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
:
|
|
597
|
-
|
|
598
|
-
: undefined));
|
|
599
|
-
const errored = walltime.cut ||
|
|
600
|
-
degenerate ||
|
|
575
|
+
const errorMessage = degenerate
|
|
576
|
+
? DEGENERATE_MESSAGE
|
|
577
|
+
: safetyCut
|
|
578
|
+
? `[refusal] response refused by the model (stop_reason="refusal"${stopDetails?.category ? `, category="${stopDetails.category}"` : ""}) — ${stopDetails?.explanation ?? "the output was withheld for policy and is unreliable"}`
|
|
579
|
+
: contextWindowExceeded
|
|
580
|
+
? `[invalid_request] model context window exceeded (stop_reason="model_context_window_exceeded") — the conversation no longer fits the model's context window`
|
|
581
|
+
: (toolError ??
|
|
582
|
+
(truncatedEmpty && !dynamicCut && !staticReasoningCut
|
|
583
|
+
? thinkingRequested
|
|
584
|
+
? `[length_empty] response truncated at max_tokens with no answer text (${maxTokensNote}) — extended thinking likely consumed the entire budget; raise max_tokens or lower thinkingBudgetShare`
|
|
585
|
+
: reasoningSeen
|
|
586
|
+
? `[length_empty] response truncated at max_tokens with no answer text (${maxTokensNote}) — the model emitted reasoning although no thinking was requested (a budget-ignoring gateway?) and it consumed the entire output budget; raise max_tokens`
|
|
587
|
+
: `[length_empty] response truncated at max_tokens with no answer text (${maxTokensNote}); raise max_tokens`
|
|
588
|
+
: emptyNoFinish
|
|
589
|
+
? `[stream_torn] model stream ended with no content and no stop_reason — the response was lost (a torn stream or an in-band provider error frame)${malformedFrames > 0 ? ` — ${malformedFrames} unparseable data frame(s) were dropped on this stream (first sample: ${JSON.stringify(malformedSample)})` : ""}`
|
|
590
|
+
: undefined));
|
|
591
|
+
const errored = degenerate ||
|
|
601
592
|
safetyCut ||
|
|
602
593
|
contextWindowExceeded ||
|
|
603
594
|
emptyNoFinish ||
|
|
604
595
|
(toolError !== undefined && noUsableContent) ||
|
|
605
596
|
(truncatedEmpty && !dynamicCut && !staticReasoningCut);
|
|
606
|
-
const errorKind =
|
|
607
|
-
? "
|
|
608
|
-
:
|
|
609
|
-
? "
|
|
610
|
-
:
|
|
611
|
-
? "length_empty"
|
|
612
|
-
: undefined;
|
|
597
|
+
const errorKind = degenerate
|
|
598
|
+
? "degenerate"
|
|
599
|
+
: !safetyCut && toolError === undefined && truncatedEmpty && !dynamicCut && !staticReasoningCut
|
|
600
|
+
? "length_empty"
|
|
601
|
+
: undefined;
|
|
613
602
|
const finalMessage = {
|
|
614
603
|
...partial,
|
|
615
604
|
content: finalContent.length > 0 ? finalContent : [{ type: "text", text: "" }],
|
package/dist/brain/openai.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import {} from "../internal/llm.js";
|
|
2
2
|
import { repairTextToolCalls } from "./tool-call-repair.js";
|
|
3
3
|
import { DEGENERATE_MESSAGE, trimDegenerateTail } from "./repetition.js";
|
|
4
|
-
import { createRepetitionPoll,
|
|
4
|
+
import { createRepetitionPoll, parseStreamedToolArgs } from "./stream-shared.js";
|
|
5
5
|
import { mintFallbackToolCallId } from "./tool-call-id.js";
|
|
6
|
-
import { WALLTIME_CUTOFF_MESSAGE } from "./walltime.js";
|
|
7
6
|
import { emitBrainTelemetry } from "./status-sink.js";
|
|
8
7
|
import { errorResultMediaNote, IMAGE_OMITTED_NO_VISION, imagesOmittedNoVisionNote, modelSupportsVision, sendableImages } from "./media-degrade.js";
|
|
9
8
|
import { OPENAI_RESERVED, applyExtraBody, stripAuthHeaders } from "./request-params.js";
|
|
@@ -275,7 +274,6 @@ export function createOpenAIBrain(config = {}) {
|
|
|
275
274
|
signal: options?.signal,
|
|
276
275
|
config,
|
|
277
276
|
httpLabel: "gateway",
|
|
278
|
-
callDeadlineMs: options?.callDeadlineMs,
|
|
279
277
|
stallTimeouts: options?.stallTimeouts,
|
|
280
278
|
buildRequest: () => {
|
|
281
279
|
const apiKey = options?.apiKey ?? config.apiKey;
|
|
@@ -330,7 +328,6 @@ export function createOpenAIBrain(config = {}) {
|
|
|
330
328
|
let degenerate = false;
|
|
331
329
|
let degenFace;
|
|
332
330
|
const rep = createRepetitionPoll();
|
|
333
|
-
const walltime = createWalltimeGate({ callDeadlineMs: options?.callDeadlineMs, isDegenerate: () => degenerate, cancel: () => ctrl.cancel() });
|
|
334
331
|
let lastDegenCheck = 0;
|
|
335
332
|
let lastDegenCheckReasoning = 0;
|
|
336
333
|
const detectRep = config.detectRepetition !== false;
|
|
@@ -410,10 +407,7 @@ export function createOpenAIBrain(config = {}) {
|
|
|
410
407
|
sealForRetry() {
|
|
411
408
|
closeThinking();
|
|
412
409
|
},
|
|
413
|
-
onDeadline: () => walltime.onDeadline(),
|
|
414
410
|
onLine(rawLine) {
|
|
415
|
-
if (walltime.shouldDropLine())
|
|
416
|
-
return;
|
|
417
411
|
const line = rawLine.trim();
|
|
418
412
|
if (!line.startsWith("data:"))
|
|
419
413
|
return;
|
|
@@ -534,40 +528,35 @@ export function createOpenAIBrain(config = {}) {
|
|
|
534
528
|
const dynamicCut = truncatedEmpty && options?.maxTokensDynamic === true;
|
|
535
529
|
const safetyCut = finishReason === "content_filter";
|
|
536
530
|
const refused = accumRefusal.trim().length > 0;
|
|
537
|
-
const emptyNoFinish = noUsableContent && finishReason == null && !degenerate
|
|
531
|
+
const emptyNoFinish = noUsableContent && finishReason == null && !degenerate;
|
|
538
532
|
const maxTokensNote = sentMaxTokens !== undefined
|
|
539
533
|
? `effective max_tokens=${sentMaxTokens} (from ${sentMaxTokensLane === "options" ? "options.maxTokens" : "model.maxTokens"})`
|
|
540
534
|
: "max_tokens not set (neither options.maxTokens nor model.maxTokens — the provider's default cap applied)";
|
|
541
|
-
const errorMessage =
|
|
542
|
-
?
|
|
543
|
-
:
|
|
544
|
-
?
|
|
545
|
-
:
|
|
546
|
-
? `[refusal] response
|
|
547
|
-
:
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
:
|
|
555
|
-
|
|
556
|
-
: undefined));
|
|
557
|
-
const errored = walltime.cut ||
|
|
558
|
-
degenerate ||
|
|
535
|
+
const errorMessage = degenerate
|
|
536
|
+
? DEGENERATE_MESSAGE
|
|
537
|
+
: safetyCut
|
|
538
|
+
? `[refusal] response cut by the provider content filter (finish_reason="content_filter") — the output was censored/truncated for policy and is unreliable`
|
|
539
|
+
: refused
|
|
540
|
+
? `[refusal] response refused by the model for policy reasons: ${accumRefusal.trim()}`
|
|
541
|
+
: (toolError ??
|
|
542
|
+
(truncatedEmpty && !dynamicCut
|
|
543
|
+
? accumReasoning.trim()
|
|
544
|
+
? `[length_empty] response truncated at max_tokens (finish_reason="length", ${maxTokensNote}) with no answer text — the model spent the whole budget on reasoning (${accumReasoning.length} reasoning chars). Raise max_tokens, or disable thinking for this task.`
|
|
545
|
+
: `[length_empty] response truncated at max_tokens (finish_reason="length", ${maxTokensNote}) with no answer text — raise max_tokens`
|
|
546
|
+
: emptyNoFinish
|
|
547
|
+
? `[stream_torn] model stream ended with no content and no finish_reason — the response was lost (a torn stream or an in-band provider error frame)${malformedFrames > 0 ? ` — ${malformedFrames} unparseable data frame(s) were dropped on this stream (first sample: ${JSON.stringify(malformedSample)})` : ""}`
|
|
548
|
+
: undefined));
|
|
549
|
+
const errored = degenerate ||
|
|
559
550
|
safetyCut ||
|
|
560
551
|
refused ||
|
|
561
552
|
emptyNoFinish ||
|
|
562
553
|
(toolError !== undefined && noUsableContent) ||
|
|
563
554
|
(truncatedEmpty && !dynamicCut);
|
|
564
|
-
const errorKind =
|
|
565
|
-
? "
|
|
566
|
-
:
|
|
567
|
-
? "
|
|
568
|
-
:
|
|
569
|
-
? "length_empty"
|
|
570
|
-
: undefined;
|
|
555
|
+
const errorKind = degenerate
|
|
556
|
+
? "degenerate"
|
|
557
|
+
: !safetyCut && !refused && toolError === undefined && truncatedEmpty && !dynamicCut
|
|
558
|
+
? "length_empty"
|
|
559
|
+
: undefined;
|
|
571
560
|
const finalMessage = {
|
|
572
561
|
...partial,
|
|
573
562
|
content: finalContent.length > 0 ? finalContent : [{ type: "text", text: "" }],
|
|
@@ -18,7 +18,6 @@ export interface StreamControls {
|
|
|
18
18
|
}
|
|
19
19
|
export interface StreamParser {
|
|
20
20
|
onLine(line: string): void;
|
|
21
|
-
onDeadline?(): boolean;
|
|
22
21
|
finalize(): void;
|
|
23
22
|
snapshot(): {
|
|
24
23
|
hasSubstantiveText: boolean;
|
|
@@ -35,7 +34,6 @@ export declare function runStreamingBrain(args: {
|
|
|
35
34
|
httpLabel: string;
|
|
36
35
|
buildRequest: () => SSERequest;
|
|
37
36
|
makeParser: (ctrl: StreamControls) => StreamParser;
|
|
38
|
-
callDeadlineMs?: number;
|
|
39
37
|
stallTimeouts?: {
|
|
40
38
|
connectMs?: number;
|
|
41
39
|
firstTokenMs?: number;
|
|
@@ -3,7 +3,6 @@ import { BrainError, classifyHttp, describeNetworkError } from "./errors.js";
|
|
|
3
3
|
import { retryBackoffMs } from "./retry.js";
|
|
4
4
|
import { emitBrainStatus, emitBrainTelemetry } from "./status-sink.js";
|
|
5
5
|
import { createConnectController, resolveStallTimeoutMs } from "./timeout.js";
|
|
6
|
-
import { WALLTIME_CUTOFF_MESSAGE } from "./walltime.js";
|
|
7
6
|
const DEFAULT_MAX_RETRIES = 10;
|
|
8
7
|
const MAX_RETRIES_ENV_CEILING = 15;
|
|
9
8
|
const MAX_RETRIES_ENV = "SEMA_MAX_RETRIES";
|
|
@@ -43,9 +42,6 @@ function emptyAssistant(model) {
|
|
|
43
42
|
function isAbortError(err) {
|
|
44
43
|
return err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
|
|
45
44
|
}
|
|
46
|
-
function isWalltimeCutoff(err) {
|
|
47
|
-
return err instanceof Error && err.message === WALLTIME_CUTOFF_MESSAGE;
|
|
48
|
-
}
|
|
49
45
|
function sleep(ms, signal) {
|
|
50
46
|
return new Promise((resolve) => {
|
|
51
47
|
if (signal?.aborted)
|
|
@@ -78,7 +74,7 @@ async function sleepAnnouncingRetry(totalMs, signal, frame) {
|
|
|
78
74
|
}
|
|
79
75
|
}
|
|
80
76
|
export function runStreamingBrain(args) {
|
|
81
|
-
const { model, doFetch, signal, config, httpLabel, buildRequest, makeParser,
|
|
77
|
+
const { model, doFetch, signal, config, httpLabel, buildRequest, makeParser, stallTimeouts } = args;
|
|
82
78
|
const out = createAssistantMessageEventStream();
|
|
83
79
|
let cleanup;
|
|
84
80
|
let announcedRetry = false;
|
|
@@ -88,13 +84,10 @@ export function runStreamingBrain(args) {
|
|
|
88
84
|
.catch((err) => {
|
|
89
85
|
const aborted = signal?.aborted === true || isAbortError(err);
|
|
90
86
|
terminalRetryPhase = "gave_up";
|
|
91
|
-
|
|
92
|
-
terminalRetryDetail = aborted ? "cancelled while retrying" : walltimeCut ? "wall-clock deadline reached while retrying" : "retries exhausted";
|
|
87
|
+
terminalRetryDetail = aborted ? "cancelled while retrying" : "retries exhausted";
|
|
93
88
|
const errorMsg = emptyAssistant(model);
|
|
94
89
|
errorMsg.stopReason = aborted ? "aborted" : "error";
|
|
95
90
|
errorMsg.errorMessage = err instanceof Error ? err.message : String(err);
|
|
96
|
-
if (walltimeCut)
|
|
97
|
-
errorMsg.errorKind = "walltime_cutoff";
|
|
98
91
|
errorMsg.usageMissing = true;
|
|
99
92
|
out.push({ type: "error", reason: aborted ? "aborted" : "error", error: errorMsg });
|
|
100
93
|
})
|
|
@@ -128,20 +121,13 @@ export function runStreamingBrain(args) {
|
|
|
128
121
|
e.name = "AbortError";
|
|
129
122
|
throw e;
|
|
130
123
|
}
|
|
131
|
-
|
|
132
|
-
throw new Error(WALLTIME_CUTOFF_MESSAGE);
|
|
133
|
-
}
|
|
134
|
-
const cc = createConnectController(connectTimeoutMs, signal, callDeadlineMs);
|
|
124
|
+
const cc = createConnectController(connectTimeoutMs, signal);
|
|
135
125
|
let r;
|
|
136
126
|
let netErr;
|
|
137
127
|
try {
|
|
138
128
|
r = await doFetch(req.url, { method: "POST", headers: req.headers, body: req.body, signal: cc.signal });
|
|
139
129
|
}
|
|
140
130
|
catch (e) {
|
|
141
|
-
if (cc.deadlineHit()) {
|
|
142
|
-
cc.dispose();
|
|
143
|
-
throw new Error(WALLTIME_CUTOFF_MESSAGE);
|
|
144
|
-
}
|
|
145
131
|
if (cc.timedOut()) {
|
|
146
132
|
netErr = new Error(`connect timeout after ${connectTimeoutMs}ms`);
|
|
147
133
|
}
|
|
@@ -153,10 +139,6 @@ export function runStreamingBrain(args) {
|
|
|
153
139
|
netErr = e;
|
|
154
140
|
}
|
|
155
141
|
}
|
|
156
|
-
if (r && cc.deadlineHit()) {
|
|
157
|
-
cc.dispose();
|
|
158
|
-
throw new Error(WALLTIME_CUTOFF_MESSAGE);
|
|
159
|
-
}
|
|
160
142
|
if (r && r.ok && r.body) {
|
|
161
143
|
cc.clearTimer();
|
|
162
144
|
connect = cc;
|
|
@@ -168,13 +150,7 @@ export function runStreamingBrain(args) {
|
|
|
168
150
|
const retryable = netErr !== undefined ||
|
|
169
151
|
(r !== undefined && (r.status >= 500 || r.status === 429 || r.status === 408 || r.status === 409));
|
|
170
152
|
if (retryable && attempt < maxRetries) {
|
|
171
|
-
if (callDeadlineMs !== undefined && Date.now() >= callDeadlineMs) {
|
|
172
|
-
throw new Error(WALLTIME_CUTOFF_MESSAGE);
|
|
173
|
-
}
|
|
174
153
|
const delayMs = retryBackoffMs(baseDelay, attempt, r);
|
|
175
|
-
if (callDeadlineMs !== undefined && Date.now() + delayMs >= callDeadlineMs) {
|
|
176
|
-
throw new Error(WALLTIME_CUTOFF_MESSAGE);
|
|
177
|
-
}
|
|
178
154
|
const statusPhase = r?.status === 429 ? "rate_limited" : netErr !== undefined ? "reconnecting" : "retrying";
|
|
179
155
|
emitBrainTelemetry({ kind: "retry", attempt: attempt + 1, phase: "connect" });
|
|
180
156
|
announcedRetry = true;
|
|
@@ -228,8 +204,6 @@ export function runStreamingBrain(args) {
|
|
|
228
204
|
void reader.cancel().catch(() => undefined);
|
|
229
205
|
}, idleTimeoutMs);
|
|
230
206
|
};
|
|
231
|
-
let dlTimer;
|
|
232
|
-
let deadlineCut = false;
|
|
233
207
|
const sawContentToken = () => {
|
|
234
208
|
bumpIdle();
|
|
235
209
|
if (firstTokenSeen)
|
|
@@ -250,10 +224,6 @@ export function runStreamingBrain(args) {
|
|
|
250
224
|
clearTimeout(idleTimer);
|
|
251
225
|
idleTimer = undefined;
|
|
252
226
|
}
|
|
253
|
-
if (dlTimer) {
|
|
254
|
-
clearTimeout(dlTimer);
|
|
255
|
-
dlTimer = undefined;
|
|
256
|
-
}
|
|
257
227
|
connect?.dispose();
|
|
258
228
|
void reader.cancel().catch(() => undefined);
|
|
259
229
|
};
|
|
@@ -273,14 +243,6 @@ export function runStreamingBrain(args) {
|
|
|
273
243
|
sawContentToken,
|
|
274
244
|
cancel: () => void reader.cancel().catch(() => undefined),
|
|
275
245
|
});
|
|
276
|
-
if (callDeadlineMs !== undefined && parser.onDeadline) {
|
|
277
|
-
dlTimer = setTimeout(() => {
|
|
278
|
-
dlTimer = undefined;
|
|
279
|
-
deadlineCut = parser.onDeadline();
|
|
280
|
-
void reader.cancel().catch(() => undefined);
|
|
281
|
-
}, Math.max(0, callDeadlineMs - Date.now()));
|
|
282
|
-
dlTimer.unref?.();
|
|
283
|
-
}
|
|
284
246
|
const INBAND_OVERLOAD_WARNING = "API overloaded mid-response. The response above may be incomplete.";
|
|
285
247
|
const onLineOverloadTierA = (line) => {
|
|
286
248
|
try {
|
|
@@ -334,14 +296,10 @@ export function runStreamingBrain(args) {
|
|
|
334
296
|
clearTimeout(idleTimer);
|
|
335
297
|
idleTimer = undefined;
|
|
336
298
|
}
|
|
337
|
-
if (
|
|
338
|
-
clearTimeout(dlTimer);
|
|
339
|
-
dlTimer = undefined;
|
|
340
|
-
}
|
|
341
|
-
if (!failure && firstTokenTimedOut && !deadlineCut) {
|
|
299
|
+
if (!failure && firstTokenTimedOut) {
|
|
342
300
|
failure = { kind: "stalled", err: new BrainError("network", `first-token timeout after ${firstTokenTimeoutMs}ms`) };
|
|
343
301
|
}
|
|
344
|
-
if (!failure && idleTimedOut
|
|
302
|
+
if (!failure && idleTimedOut) {
|
|
345
303
|
failure = { kind: "stalled", err: new BrainError("network", `stream idle timeout after ${idleTimeoutMs}ms`) };
|
|
346
304
|
}
|
|
347
305
|
if (!failure) {
|