mixdog 0.9.108 → 0.9.110
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/package.json +1 -1
- package/scripts/run-suite.mjs +2 -0
- package/src/output-styles/detailed.md +14 -11
- package/src/output-styles/extreme-minimal.md +2 -4
- package/src/output-styles/minimal.md +6 -7
- package/src/output-styles/simple.md +11 -12
- package/src/rules/agent/30-explorer.md +19 -28
- package/src/rules/agent/41-cycle2-agent.md +1 -2
- package/src/rules/agent/42-cycle3-agent.md +3 -4
- package/src/rules/shared/01-tool.md +40 -33
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +31 -18
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +64 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +6 -7
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +24 -0
- package/src/runtime/agent/orchestrator/session/result-classification.mjs +20 -25
- package/src/runtime/agent/orchestrator/stall-policy.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +31 -26
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +10 -1
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +37 -14
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +38 -62
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +65 -4
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +6 -37
- package/src/runtime/shared/pristine-execution-contract.json +4 -1
- package/src/session-runtime/session-title.mjs +14 -1
- package/src/standalone/agent-tool/spawn-flow.mjs +18 -0
- package/src/tui/session/tool-result-status.mjs +5 -12
- package/src/tui/session/tool-result-text.mjs +7 -5
- package/src/workflows/default/WORKFLOW.md +2 -1
package/package.json
CHANGED
package/scripts/run-suite.mjs
CHANGED
|
@@ -51,6 +51,7 @@ export const SUITES = {
|
|
|
51
51
|
'pretool-ask-runtime-test.mjs',
|
|
52
52
|
'prompt-input-parity-test.mjs',
|
|
53
53
|
'reactive-compact-persist-smoke.mjs',
|
|
54
|
+
'reasoning-replay-policy-test.mjs',
|
|
54
55
|
'repl-stream-finalize-test.mjs',
|
|
55
56
|
'result-classification-test.mjs',
|
|
56
57
|
'rg-runner-test.mjs',
|
|
@@ -60,6 +61,7 @@ export const SUITES = {
|
|
|
60
61
|
'session-title-controller-test.mjs',
|
|
61
62
|
'set-effort-config-test.mjs',
|
|
62
63
|
'shell-jobs-windows-hide-test.mjs',
|
|
64
|
+
'spawn-ws-prewarm-test.mjs',
|
|
63
65
|
'spinner-meta-test.mjs',
|
|
64
66
|
'statusline-agents-test.mjs',
|
|
65
67
|
'statusline-quota-hysteresis-test.mjs',
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: detailed
|
|
3
3
|
title: Detailed
|
|
4
|
-
description:
|
|
4
|
+
description: Claude Code default-depth responses
|
|
5
5
|
aliases: verbose, full
|
|
6
6
|
keep-coding-instructions: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Output Style
|
|
10
10
|
|
|
11
|
-
Detailed —
|
|
11
|
+
Detailed — a teammate's update; clarity outranks terseness.
|
|
12
12
|
|
|
13
|
-
- Outcome first
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
- Outcome first, then what a cold reader needs: complete user-language
|
|
14
|
+
sentences, expanded jargon, rationale where it adds value; no filler or
|
|
15
|
+
process narration.
|
|
16
|
+
- Structure matches complexity: plain prose for simple answers; headers,
|
|
17
|
+
bullets, or tables (short enumerable facts only, explained outside) when
|
|
18
|
+
they aid scanning.
|
|
19
|
+
- No hard cap, but brevity first: the shortest report understood without
|
|
20
|
+
rereads (~10 lines is plenty for most tasks); expand only when complexity
|
|
21
|
+
demands; trivial results stay 1–2 sentences.
|
|
22
|
+
- Cite `file:line`; snippets only when load-bearing.
|
|
23
|
+
- Never dump raw tool output; blockers and failures in one clause each.
|
|
24
|
+
- Never name this style unless asked.
|
|
@@ -10,7 +10,5 @@ keep-coding-instructions: true
|
|
|
10
10
|
|
|
11
11
|
Extreme minimal — exactly one sentence under 100 characters.
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
- Prefer `<target> changed.` Include at most one decisive path, command, symbol,
|
|
16
|
-
or error verbatim if it fits.
|
|
13
|
+
- Net result only; no second sentence, list, heading, label, or follow-up.
|
|
14
|
+
- At most one decisive path, command, symbol, or error verbatim if it fits.
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: minimal
|
|
3
3
|
title: Minimal
|
|
4
|
-
description:
|
|
4
|
+
description: Net result within 400 characters
|
|
5
5
|
keep-coding-instructions: true
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Output Style
|
|
9
9
|
|
|
10
|
-
Minimal —
|
|
10
|
+
Minimal — net result in plain sentences; hard cap 400 characters.
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
API, code fragment, or error verbatim.
|
|
12
|
+
- Concept level only: no headings, bullets, labels, or per-file detail, even
|
|
13
|
+
for reports.
|
|
14
|
+
- Only decisive paths, commands, symbols, or errors verbatim; user-language
|
|
15
|
+
sentences; never name this style unless asked.
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: simple
|
|
3
3
|
title: Simple
|
|
4
|
-
description:
|
|
4
|
+
description: Lightly trimmed default-style updates within 800 characters
|
|
5
5
|
aliases: concise, handoff
|
|
6
6
|
keep-coding-instructions: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Output Style
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Simple — a teammate's update, tightened; hard cap 800 characters.
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
this style unless asked.
|
|
13
|
+
- Outcome first, then only what the reader needs: complete user-language
|
|
14
|
+
sentences, no filler or process narration.
|
|
15
|
+
- Structure matches complexity: plain prose by default; bullets or compact
|
|
16
|
+
tables only when they aid scanning.
|
|
17
|
+
- Prefer the tighter phrasing: each material fact once, secondary detail and
|
|
18
|
+
non-load-bearing rationale dropped; `file:line` anchors, technical literals
|
|
19
|
+
verbatim; the cap is a ceiling, not a target.
|
|
20
|
+
- Never dump raw tool output; blockers and failures in one clause each.
|
|
21
|
+
Never name this style unless asked.
|
|
@@ -13,40 +13,31 @@ add no rules or exceptions here.
|
|
|
13
13
|
|
|
14
14
|
## Hard budget
|
|
15
15
|
|
|
16
|
-
Before EVERY tool call, check:
|
|
17
|
-
1. Which requested targets still lack a complete direct anchor set?
|
|
18
|
-
2. Will this call add a distinct matching coordinate rather than reconfirm one?
|
|
19
|
-
|
|
20
|
-
A target is complete only when every distinct coordinate directly satisfying
|
|
21
|
-
its query is held; one anchor suffices only when the target is singular by
|
|
22
|
-
construction. If all targets are complete, or the call only reconfirms,
|
|
23
|
-
re-reads, verifies, quotes, strengthens, or adds context, answer now.
|
|
24
|
-
|
|
25
16
|
Target: ONE tool turn and an answer within 10 seconds.
|
|
26
17
|
Hard limit: FIVE tool turns plus ONE tool-less final-report turn. Label tool
|
|
27
|
-
messages `turn 1/6` through `turn 5/6
|
|
28
|
-
`turn 6/6
|
|
18
|
+
messages `turn 1/6` through `turn 5/6`; the response after turn 5 is
|
|
19
|
+
`turn 6/6`, the FINAL REPORT TURN: no tools, report the credible anchors
|
|
20
|
+
currently held, or `EXPLORATION_FAILED` if none exist.
|
|
29
21
|
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
A target is complete only when every distinct coordinate directly satisfying
|
|
23
|
+
its query is held; one anchor suffices only when the target is singular by
|
|
24
|
+
construction. Before EVERY tool call, check which targets still lack a
|
|
25
|
+
complete direct anchor set and whether the call adds a distinct matching
|
|
26
|
+
coordinate; once every target is complete, answer immediately — never spend
|
|
27
|
+
a turn merely because budget remains.
|
|
32
28
|
|
|
33
|
-
Turns 2-5 are ONLY for incomplete targets
|
|
29
|
+
Turns 2-5 are ONLY for incomplete targets: each recovery turn uses changed
|
|
34
30
|
concrete tokens or a new exact scope in maximum fanout. Page only when output
|
|
35
|
-
explicitly reports truncation or incompleteness; never repeat tokens and
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`EXPLORATION_FAILED`.
|
|
39
|
-
|
|
40
|
-
After turn 5, stop tools unconditionally. Turn 6 (`turn 6/6`) is the FINAL
|
|
41
|
-
REPORT TURN and the last turn: report the credible anchors currently held; if
|
|
42
|
-
none exist, return `EXPLORATION_FAILED`. There is no sixth tool turn.
|
|
31
|
+
explicitly reports truncation or incompleteness; never repeat tokens and
|
|
32
|
+
scope. If the next turn lacks a concrete anchor-producing move, stop early
|
|
33
|
+
with `EXPLORATION_FAILED`.
|
|
43
34
|
|
|
44
35
|
## No reconfirmation
|
|
45
36
|
|
|
46
37
|
A credible tool-returned coordinate is FINAL. Never re-locate, re-read,
|
|
47
|
-
reconfirm, verify, upgrade, cross-check, or
|
|
48
|
-
turn. Copy paths and coordinates exactly; never repair,
|
|
49
|
-
or recall them.
|
|
38
|
+
reconfirm, verify, upgrade, cross-check, quote, or strengthen it through
|
|
39
|
+
another tool or turn. Copy paths and coordinates exactly; never repair,
|
|
40
|
+
normalize, estimate, or recall them.
|
|
50
41
|
|
|
51
42
|
A code anchor requires a tool-returned `path:line`; a bare path is valid only
|
|
52
43
|
for a file/dir-location query. Generic matches and guessed coordinates are
|
|
@@ -57,8 +48,8 @@ Return one compact line per distinct direct match:
|
|
|
57
48
|
|
|
58
49
|
Use no fixed item-count cap; omit incidental matches and prose. For a
|
|
59
50
|
completeness/list/count query, copy EVERY returned matching `path:line` once
|
|
60
|
-
and preserve the tool-reported total
|
|
51
|
+
and preserve the tool-reported total; never omit a direct match or page after
|
|
61
52
|
a complete result.
|
|
62
53
|
|
|
63
|
-
Return `EXPLORATION_FAILED` when the budget cannot produce a credible anchor
|
|
64
|
-
|
|
54
|
+
Return `EXPLORATION_FAILED` when the budget cannot produce a credible anchor;
|
|
55
|
+
never fabricate, soften, or return vague prose.
|
|
@@ -41,5 +41,4 @@ Use only input IDs; never invent IDs. `update` supplies fresh `element` and a
|
|
|
41
41
|
uses only one `project_id`. Summaries are complete sentences in input language,
|
|
42
42
|
preserve important specifics verbatim, and omit actor/meta filler. Category
|
|
43
43
|
priority: `rule > constraint > decision > fact > goal > preference > task >
|
|
44
|
-
issue`. Replace literal `|` with `/`; fields contain no newlines.
|
|
45
|
-
verdict with a digit.
|
|
44
|
+
issue`. Replace literal `|` with `/`; fields contain no newlines.
|
|
@@ -28,7 +28,6 @@ Verbose durable is always `update`, never `keep`.
|
|
|
28
28
|
`<id>|merge|<target_id>|<source_ids_csv>`
|
|
29
29
|
`<id>|delete`
|
|
30
30
|
|
|
31
|
-
IDs match input rows; never invent them. An `update`
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
fields contain no newlines. Emit a digit-starting verdict for every input row.
|
|
31
|
+
IDs match input rows; never invent them. An `update` supplies a compact
|
|
32
|
+
`element`. A `merge` retains `target_id`, absorbs sources, and stays within
|
|
33
|
+
one `project_id`. Replace literal `|` with `/`; fields contain no newlines.
|
|
@@ -2,45 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
- Baseline routing assigns each facet directly by the evidence needed to
|
|
4
4
|
determine the complete edit:
|
|
5
|
-
path/name only→`find`; wildcard paths→`glob
|
|
5
|
+
path/name only→`find`; wildcard/recursive paths→`glob` (including known-root
|
|
6
|
+
unknown descendants); exact directory entries→`list`;
|
|
6
7
|
source content/value/`path:line`→`grep`; exact symbol/relation→`code_graph`;
|
|
7
8
|
known file/range→`read`;
|
|
8
|
-
web/current→`search`; returned URL body→`web_fetch`; prior work→`recall
|
|
9
|
+
web/current→`search`; returned URL body→`web_fetch`; prior work→`recall`
|
|
10
|
+
(history only, never current local state);
|
|
9
11
|
durable compact English memory→`memory`; explicit project change→`cwd`;
|
|
10
|
-
explicit user-requested conversation reset→`session_manage
|
|
12
|
+
explicit user-requested conversation reset→`session_manage`.
|
|
11
13
|
Use only named tools present in the current tool surface.
|
|
12
14
|
`explore`, when exposed, is a fast path only for facets whose repository
|
|
13
15
|
coordinates remain unknown: call it first once for all such independent
|
|
14
|
-
facets in one query array. It
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
facets in one query array. It returns direct `path:line` anchors, not
|
|
17
|
+
analysis or solutions; anchors are tool-verified coordinates that END
|
|
18
|
+
their location facets — never re-locate or second-guess them; resume
|
|
19
|
+
baseline routing from them.
|
|
20
|
+
- Act only on verified identities (cwd/project/user/tool-returned) — paths,
|
|
21
|
+
module specifiers, symbols, data/record shapes alike; a guessed identity is
|
|
22
|
+
verified by one lookup or sample only when the next call or edit references
|
|
23
|
+
it. Within the current project, pass
|
|
18
24
|
project-relative paths and omit optional scopes equal to its root; explicit
|
|
19
|
-
paths may be outside cwd only for targets outside the project
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Batch calls iff
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
to
|
|
25
|
+
paths may be outside cwd only for targets outside the project.
|
|
26
|
+
- Plan the fewest dependent rounds, then the fewest calls. A conclusive
|
|
27
|
+
result ends its facet, and known state — task/brief-supplied facts,
|
|
28
|
+
returned content, your own successful calls' effects — is never
|
|
29
|
+
re-acquired, broadened, or reconfirmed. Batch calls iff none needs
|
|
30
|
+
another's output or can change another's inputs/state; otherwise
|
|
31
|
+
serialize. Before each batch, deduplicate the facets still required by the request,
|
|
32
|
+
route each once to the cheapest sufficient tool with all required
|
|
33
|
+
variants/scopes, and launch every independent call together — never
|
|
34
|
+
split or duplicate a facet across tools, mutate merely to widen
|
|
35
|
+
retrieval, reserve known work, or cap fanout. Symbol relations end at
|
|
36
|
+
`code_graph`; values/locations end at the context grep returns; `read`
|
|
37
|
+
covers only what returned spans cannot, as an anchored offset/limit
|
|
38
|
+
window. The moment evidence determines the answer, edit, or deliverable,
|
|
39
|
+
stop retrieving; patch if needed.
|
|
40
|
+
- Once the edit or deliverable is determined, finish in one assistant turn:
|
|
41
|
+
one `apply_patch` per file or cohesive unit, all patches first, then one
|
|
42
|
+
batched verification `shell` that runs the real required postconditions
|
|
43
|
+
on every changed file and produced artifact, never echoes a claim;
|
|
44
|
+
runtime waits for every patch and skips the shell
|
|
45
|
+
if any fails. Retry only failed envelopes; rerun a failed check only
|
|
46
|
+
after a fix that can change its result, else report it unresolved.
|
|
47
|
+
Hand-authored text is edited only with `apply_patch`; computed artifacts
|
|
48
|
+
(data/reports/derived values) come from `shell` computation, never
|
|
49
|
+
hand-transcribed numbers. Earlier `shell` is only for runtime/state
|
|
50
|
+
evidence unavailable to file tools—an independent facet, batched with
|
|
51
|
+
the rest.
|
|
45
52
|
- A background `task_id` ends the turn; completion resumes work. Never poll;
|
|
46
53
|
use task control only for recovery or a required blocking result.
|
|
@@ -39,7 +39,13 @@ export function createTitleCompletion(deps = {}) {
|
|
|
39
39
|
config,
|
|
40
40
|
});
|
|
41
41
|
if (!route || typeof route !== 'object') {
|
|
42
|
-
|
|
42
|
+
const error = new Error('Session title maintenance route is unresolved.');
|
|
43
|
+
// Machine-readable marker: callers (session-title controller)
|
|
44
|
+
// downgrade this to a one-shot "titling disabled" skip instead of
|
|
45
|
+
// logging a stack per session (e.g. bench profiles without a
|
|
46
|
+
// maintainer/default route).
|
|
47
|
+
error.code = 'MAINTENANCE_ROUTE_UNRESOLVED';
|
|
48
|
+
throw error;
|
|
43
49
|
}
|
|
44
50
|
const providerName = String(route.provider || '').trim();
|
|
45
51
|
const model = String(route.model || '').trim();
|
|
@@ -41,6 +41,8 @@ import {
|
|
|
41
41
|
} from './anthropic-oauth-credentials.mjs';
|
|
42
42
|
import {
|
|
43
43
|
PROVIDER_FIRST_BYTE_TIMEOUT_MS,
|
|
44
|
+
PROVIDER_NONSTREAM_TOTAL_TIMEOUT_MS,
|
|
45
|
+
createTimeoutSignal,
|
|
44
46
|
createPassthroughSignal,
|
|
45
47
|
} from '../stall-policy.mjs';
|
|
46
48
|
import {
|
|
@@ -740,7 +742,11 @@ export class AnthropicOAuthProvider {
|
|
|
740
742
|
// Test seam: injectable request factory for retry-path tests.
|
|
741
743
|
const doRequestImpl = typeof opts._doRequestFn === 'function' ? opts._doRequestFn : doRequest;
|
|
742
744
|
|
|
743
|
-
const requestWithRetry = async (
|
|
745
|
+
const requestWithRetry = async (
|
|
746
|
+
accessToken,
|
|
747
|
+
requestBody = body,
|
|
748
|
+
retrySignal = totalSignal,
|
|
749
|
+
) => withRetry(async ({ signal: attemptSignal }) => {
|
|
744
750
|
const result = await doRequestImpl(accessToken, attemptSignal, requestBody);
|
|
745
751
|
const status = Number(result?.response?.status || 0);
|
|
746
752
|
const transientStatus = classifyError({ httpStatus: status }) === 'transient';
|
|
@@ -772,7 +778,7 @@ export class AnthropicOAuthProvider {
|
|
|
772
778
|
}
|
|
773
779
|
return result;
|
|
774
780
|
}, {
|
|
775
|
-
signal:
|
|
781
|
+
signal: retrySignal,
|
|
776
782
|
maxAttempts: anthropicMaxAttempts(),
|
|
777
783
|
backoffMs: ANTHROPIC_RETRY_BACKOFF_MS,
|
|
778
784
|
retryJitterRatio: ANTHROPIC_RETRY_JITTER_RATIO,
|
|
@@ -816,28 +822,69 @@ export class AnthropicOAuthProvider {
|
|
|
816
822
|
const issueNonStreamingFallback = async (controller, abortReason) => {
|
|
817
823
|
try { controller?.abort?.(abortReason); } catch {}
|
|
818
824
|
try { onStageChange?.('requesting', { transport: 'non-streaming-fallback' }); } catch {}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
825
|
+
const timeoutMs = Number(opts._nonStreamingTimeoutMs) > 0
|
|
826
|
+
? Number(opts._nonStreamingTimeoutMs)
|
|
827
|
+
: PROVIDER_NONSTREAM_TOTAL_TIMEOUT_MS;
|
|
828
|
+
const lifetime = createTimeoutSignal(
|
|
829
|
+
totalSignal,
|
|
830
|
+
timeoutMs,
|
|
831
|
+
'Anthropic OAuth non-streaming fallback',
|
|
832
|
+
);
|
|
833
|
+
let fallback = null;
|
|
834
|
+
let lifetimeAbortHandler = null;
|
|
835
|
+
const releaseFallback = (reason) => {
|
|
836
|
+
if (lifetimeAbortHandler) {
|
|
837
|
+
try { lifetime.signal.removeEventListener('abort', lifetimeAbortHandler); } catch {}
|
|
838
|
+
lifetimeAbortHandler = null;
|
|
839
|
+
}
|
|
840
|
+
cleanupCancelHandler(fallback?.cancelHandler);
|
|
841
|
+
try { fallback?.controller?.abort?.(reason); } catch {}
|
|
842
|
+
fallback = null;
|
|
843
|
+
};
|
|
844
|
+
const requestFallback = async (accessToken) => {
|
|
845
|
+
const result = await requestWithRetry(
|
|
846
|
+
accessToken,
|
|
847
|
+
{ ...body, stream: false },
|
|
848
|
+
lifetime.signal,
|
|
849
|
+
);
|
|
850
|
+
fallback = result;
|
|
851
|
+
lifetimeAbortHandler = () => {
|
|
852
|
+
try { result.controller?.abort?.(lifetime.signal.reason); } catch {}
|
|
853
|
+
};
|
|
854
|
+
if (lifetime.signal.aborted) {
|
|
855
|
+
lifetimeAbortHandler();
|
|
856
|
+
const reason = lifetime.signal.reason;
|
|
857
|
+
throw reason instanceof Error
|
|
858
|
+
? reason
|
|
859
|
+
: new Error('Anthropic OAuth non-streaming fallback aborted');
|
|
860
|
+
}
|
|
861
|
+
lifetime.signal.addEventListener('abort', lifetimeAbortHandler, { once: true });
|
|
862
|
+
return result;
|
|
863
|
+
};
|
|
835
864
|
try {
|
|
865
|
+
fallback = await requestFallback(creds.accessToken);
|
|
866
|
+
if (fallback.response.status === 401) {
|
|
867
|
+
releaseFallback('Anthropic OAuth non-streaming fallback refreshing auth');
|
|
868
|
+
creds = await this.ensureAuth({ forceRefresh: true, reason: '401' });
|
|
869
|
+
fallback = await requestFallback(creds.accessToken);
|
|
870
|
+
}
|
|
871
|
+
if (!fallback.response.ok) {
|
|
872
|
+
const text = await fallback.response.text().catch(() => '');
|
|
873
|
+
const fallbackError = new Error(`Anthropic OAuth API ${fallback.response.status}: ${this.scrubTokens(text).slice(0, 200)}`);
|
|
874
|
+
fallbackError.status = fallback.response.status;
|
|
875
|
+
fallbackError.httpStatus = fallback.response.status;
|
|
876
|
+
throw fallbackError;
|
|
877
|
+
}
|
|
836
878
|
const message = await fallback.response.json();
|
|
837
879
|
return normalizeAnthropicNonStreamingResponse(message, useModel);
|
|
880
|
+
} catch (err) {
|
|
881
|
+
if (lifetime.signal.aborted && lifetime.signal.reason instanceof Error) {
|
|
882
|
+
throw lifetime.signal.reason;
|
|
883
|
+
}
|
|
884
|
+
throw err;
|
|
838
885
|
} finally {
|
|
839
|
-
|
|
840
|
-
|
|
886
|
+
releaseFallback('Anthropic non-streaming fallback complete');
|
|
887
|
+
lifetime.cleanup();
|
|
841
888
|
}
|
|
842
889
|
};
|
|
843
890
|
|
|
@@ -47,7 +47,13 @@ function _captureMidstreamAbort(state, reason) {
|
|
|
47
47
|
const reasonName = reason?.name || '';
|
|
48
48
|
if (reasonName === 'AgentStallAbortError' || reasonName === 'StreamStalledAbortError') {
|
|
49
49
|
state.watchdogAbort = reasonName;
|
|
50
|
-
} else {
|
|
50
|
+
} else if (reasonName !== 'ProviderTimeoutError' && reasonName !== 'StreamStalledError') {
|
|
51
|
+
// Internal timeout/stall abort reasons are transport symptoms, not a
|
|
52
|
+
// caller decision. Recording them as userAbort silently vetoed the
|
|
53
|
+
// mid-stream retry ladder (_classifyMidstreamSse returns null on
|
|
54
|
+
// userAbort → the turn surfaced as an instant unlogged failure).
|
|
55
|
+
// Leaving state untouched keeps them classifiable by the thrown error
|
|
56
|
+
// itself (EPROVIDERTIMEOUT/ESTREAMSTALL → transient/stall retry).
|
|
51
57
|
state.userAbort = true;
|
|
52
58
|
}
|
|
53
59
|
}
|
|
@@ -19,6 +19,7 @@ import { readStreamOutcome } from './lib/stream-outcome.mjs';
|
|
|
19
19
|
import { traceAgentUsage } from '../agent-trace.mjs';
|
|
20
20
|
import {
|
|
21
21
|
PROVIDER_FIRST_BYTE_TIMEOUT_MS,
|
|
22
|
+
PROVIDER_NONSTREAM_TOTAL_TIMEOUT_MS,
|
|
22
23
|
createTimeoutSignal,
|
|
23
24
|
createPassthroughSignal,
|
|
24
25
|
} from '../stall-policy.mjs';
|
|
@@ -327,25 +328,37 @@ export class AnthropicProvider {
|
|
|
327
328
|
try { streamController.abort?.(abortReason); } catch {}
|
|
328
329
|
try { onStageChange?.('requesting', { transport: 'non-streaming-fallback' }); } catch {}
|
|
329
330
|
const nonStreamingParams = { ...params, stream: false };
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
maxAttempts: anthropicMaxAttempts(),
|
|
338
|
-
backoffMs: ANTHROPIC_RETRY_BACKOFF_MS,
|
|
339
|
-
retryJitterRatio: ANTHROPIC_RETRY_JITTER_RATIO,
|
|
340
|
-
retryJitterMode: 'positive',
|
|
341
|
-
perAttemptTimeoutMs: anthropicRequestTimeoutMs(),
|
|
342
|
-
perAttemptLabel: `${this.name} Anthropic non-streaming fallback`,
|
|
343
|
-
provider: 'anthropic',
|
|
344
|
-
model: useModel,
|
|
345
|
-
fallbackModel: opts._fallbackTriggered ? undefined : opts.fallbackModel,
|
|
346
|
-
},
|
|
331
|
+
const timeoutMs = Number(opts._nonStreamingTimeoutMs) > 0
|
|
332
|
+
? Number(opts._nonStreamingTimeoutMs)
|
|
333
|
+
: PROVIDER_NONSTREAM_TOTAL_TIMEOUT_MS;
|
|
334
|
+
const lifetime = createTimeoutSignal(
|
|
335
|
+
totalSignal,
|
|
336
|
+
timeoutMs,
|
|
337
|
+
`${this.name} Anthropic non-streaming fallback`,
|
|
347
338
|
);
|
|
348
|
-
|
|
339
|
+
try {
|
|
340
|
+
const message = await withRetry(
|
|
341
|
+
async ({ signal: attemptSignal }) => this.client.messages.create(nonStreamingParams, {
|
|
342
|
+
signal: attemptSignal,
|
|
343
|
+
...(betaHeaders ? { headers: betaHeaders } : {}),
|
|
344
|
+
}),
|
|
345
|
+
{
|
|
346
|
+
signal: lifetime.signal,
|
|
347
|
+
maxAttempts: anthropicMaxAttempts(),
|
|
348
|
+
backoffMs: ANTHROPIC_RETRY_BACKOFF_MS,
|
|
349
|
+
retryJitterRatio: ANTHROPIC_RETRY_JITTER_RATIO,
|
|
350
|
+
retryJitterMode: 'positive',
|
|
351
|
+
perAttemptTimeoutMs: anthropicRequestTimeoutMs(),
|
|
352
|
+
perAttemptLabel: `${this.name} Anthropic non-streaming fallback`,
|
|
353
|
+
provider: 'anthropic',
|
|
354
|
+
model: useModel,
|
|
355
|
+
fallbackModel: opts._fallbackTriggered ? undefined : opts.fallbackModel,
|
|
356
|
+
},
|
|
357
|
+
);
|
|
358
|
+
return buildReturnFromParse(normalizeAnthropicNonStreamingResponse(message, useModel));
|
|
359
|
+
} finally {
|
|
360
|
+
lifetime.cleanup();
|
|
361
|
+
}
|
|
349
362
|
};
|
|
350
363
|
|
|
351
364
|
const recoverNonStreaming = async (midState, streamingError, streamController) => {
|
|
@@ -167,7 +167,7 @@ function _pushOutputTextAnnotations(part, citations, citationKeys) {
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
function _buildOpenAIHttpFallbackHeaders({ auth, cacheKey }) {
|
|
170
|
+
function _buildOpenAIHttpFallbackHeaders({ auth, cacheKey, statelessConversation = false }) {
|
|
171
171
|
if (auth?.type === 'openai-direct') {
|
|
172
172
|
// Public API-key auth: Bearer <OPENAI_API_KEY>, no chatgpt-account-id /
|
|
173
173
|
// originator (mirrors openai-ws-pool _buildHandshakeHeaders' direct
|
|
@@ -189,7 +189,7 @@ function _buildOpenAIHttpFallbackHeaders({ auth, cacheKey }) {
|
|
|
189
189
|
'chatgpt-account-id': auth.account_id || '',
|
|
190
190
|
'x-client-request-id': randomBytes(16).toString('hex'),
|
|
191
191
|
};
|
|
192
|
-
if (cacheKey) {
|
|
192
|
+
if (cacheKey && !statelessConversation) {
|
|
193
193
|
const sid = String(cacheKey);
|
|
194
194
|
// Backend-native anchors (see openai-ws-pool _buildHandshakeHeaders):
|
|
195
195
|
// the hyphenated `session-id`/`thread-id` pair; legacy underscore
|
|
@@ -252,7 +252,9 @@ export async function sendViaHttpSse({
|
|
|
252
252
|
// one still aborts, and
|
|
253
253
|
// (c) externalSignal (client disconnect / replaced-by-newer-request).
|
|
254
254
|
const totalTimeout = createPassthroughSignal(externalSignal);
|
|
255
|
-
const
|
|
255
|
+
const statelessConversation = opts?.statelessConversation === true
|
|
256
|
+
|| _envFlag('MIXDOG_OAI_STATELESS_HTTP', false);
|
|
257
|
+
const headers = _buildOpenAIHttpFallbackHeaders({ auth, cacheKey, statelessConversation });
|
|
256
258
|
const fetchStartedAt = Date.now();
|
|
257
259
|
const responsesUrl = auth?.type === 'openai-direct'
|
|
258
260
|
? OPENAI_DIRECT_RESPONSES_URL
|