@namzu/sdk 56.2.0 → 57.0.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/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +1 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +1 -0
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/plan.d.ts +20 -0
- package/dist/agents/plan.d.ts.map +1 -0
- package/dist/agents/plan.js +27 -0
- package/dist/agents/plan.js.map +1 -0
- package/dist/compaction/dangling.d.ts +12 -1
- package/dist/compaction/dangling.d.ts.map +1 -1
- package/dist/compaction/dangling.js +17 -3
- package/dist/compaction/dangling.js.map +1 -1
- package/dist/compaction/index.d.ts +2 -2
- package/dist/compaction/index.d.ts.map +1 -1
- package/dist/compaction/index.js.map +1 -1
- package/dist/compaction/manager.d.ts +16 -0
- package/dist/compaction/manager.d.ts.map +1 -1
- package/dist/compaction/manager.js +55 -0
- package/dist/compaction/manager.js.map +1 -1
- package/dist/compaction/manual.d.ts +2 -0
- package/dist/compaction/manual.d.ts.map +1 -1
- package/dist/compaction/manual.js +33 -4
- package/dist/compaction/manual.js.map +1 -1
- package/dist/compaction/restore.d.ts +51 -0
- package/dist/compaction/restore.d.ts.map +1 -0
- package/dist/compaction/restore.js +123 -0
- package/dist/compaction/restore.js.map +1 -0
- package/dist/compaction/types.d.ts +22 -0
- package/dist/compaction/types.d.ts.map +1 -1
- package/dist/compaction/wire.d.ts +3 -1
- package/dist/compaction/wire.d.ts.map +1 -1
- package/dist/compaction/wire.js +16 -0
- package/dist/compaction/wire.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/plugin/shell-hook.d.ts +19 -1
- package/dist/plugin/shell-hook.d.ts.map +1 -1
- package/dist/plugin/shell-hook.js +21 -5
- package/dist/plugin/shell-hook.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +2 -2
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +3 -2
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/provider/errors.d.ts +3 -0
- package/dist/provider/errors.d.ts.map +1 -1
- package/dist/provider/errors.js +23 -3
- package/dist/provider/errors.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +34 -3
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/retry.d.ts +12 -5
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +22 -8
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +4 -2
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +4 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +1 -1
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.d.ts +42 -0
- package/dist/runtime/jobs/registry.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.js +77 -0
- package/dist/runtime/jobs/registry.js.map +1 -1
- package/dist/runtime/query/calls-that-never-started.d.ts +12 -0
- package/dist/runtime/query/calls-that-never-started.d.ts.map +1 -0
- package/dist/runtime/query/calls-that-never-started.js +41 -0
- package/dist/runtime/query/calls-that-never-started.js.map +1 -0
- package/dist/runtime/query/executor/tool-call-admission.d.ts +2 -2
- package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
- package/dist/runtime/query/executor/tool-call-admission.js +13 -9
- package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +8 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +35 -0
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/file-evidence-context.js +1 -1
- package/dist/runtime/query/file-evidence-context.js.map +1 -1
- package/dist/runtime/query/index.d.ts +6 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +5 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +20 -0
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +45 -25
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
- package/dist/runtime/query/prepare-turn.js +10 -1
- package/dist/runtime/query/prepare-turn.js.map +1 -1
- package/dist/scheduler/completion-inbox.js +3 -3
- package/dist/scheduler/completion-inbox.js.map +1 -1
- package/dist/tools/admit-facts.d.ts.map +1 -1
- package/dist/tools/admit-facts.js +1 -0
- package/dist/tools/admit-facts.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts +42 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +115 -48
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +55 -5
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/file-read-tracker.d.ts.map +1 -1
- package/dist/tools/file-read-tracker.js +28 -0
- package/dist/tools/file-read-tracker.js.map +1 -1
- package/dist/types/agent/base.d.ts +6 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/errors/catalog.d.ts +3 -0
- package/dist/types/errors/catalog.d.ts.map +1 -1
- package/dist/types/errors/catalog.js +24 -2
- package/dist/types/errors/catalog.js.map +1 -1
- package/dist/types/provider/error.d.ts +7 -0
- package/dist/types/provider/error.d.ts.map +1 -1
- package/dist/types/provider/errors.d.ts +24 -0
- package/dist/types/provider/errors.d.ts.map +1 -1
- package/dist/types/provider/errors.js +101 -1
- package/dist/types/provider/errors.js.map +1 -1
- package/dist/types/provider/stream.d.ts +6 -0
- package/dist/types/provider/stream.d.ts.map +1 -1
- package/dist/types/session/events.d.ts +8 -0
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/events.js.map +1 -1
- package/dist/types/tool/index.d.ts +39 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/types/tool/presentation.d.ts +4 -0
- package/dist/types/tool/presentation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +1 -0
- package/src/agents/index.ts +1 -0
- package/src/agents/plan.ts +30 -0
- package/src/compaction/dangling.ts +35 -3
- package/src/compaction/index.ts +4 -0
- package/src/compaction/manager.ts +53 -0
- package/src/compaction/manual.ts +42 -10
- package/src/compaction/restore.ts +159 -0
- package/src/compaction/types.ts +25 -0
- package/src/compaction/wire.ts +26 -1
- package/src/index.ts +2 -0
- package/src/plugin/shell-hook.ts +33 -5
- package/src/prompt/coding-agent-doctrine.ts +3 -2
- package/src/provider/errors.ts +27 -3
- package/src/provider/fallback.ts +34 -2
- package/src/provider/retry.ts +30 -10
- package/src/public-runtime.ts +8 -0
- package/src/public-types.ts +3 -0
- package/src/runtime/jobs/registry.ts +105 -0
- package/src/runtime/query/calls-that-never-started.ts +37 -0
- package/src/runtime/query/executor/tool-call-admission.ts +12 -9
- package/src/runtime/query/executor.ts +37 -0
- package/src/runtime/query/file-evidence-context.ts +1 -1
- package/src/runtime/query/index.ts +12 -0
- package/src/runtime/query/iteration/phases/compaction.ts +19 -0
- package/src/runtime/query/iteration/phases/context.ts +3 -0
- package/src/runtime/query/iteration/stream-turn.ts +47 -25
- package/src/runtime/query/prepare-turn.ts +10 -1
- package/src/scheduler/completion-inbox.ts +3 -3
- package/src/tools/admit-facts.ts +1 -0
- package/src/tools/builtins/bash.ts +149 -54
- package/src/tools/builtins/read-file.ts +66 -4
- package/src/tools/file-read-tracker.ts +25 -1
- package/src/types/agent/base.ts +7 -0
- package/src/types/errors/catalog.ts +31 -2
- package/src/types/provider/error.ts +7 -0
- package/src/types/provider/errors.ts +120 -1
- package/src/types/provider/stream.ts +6 -0
- package/src/types/session/events.ts +8 -0
- package/src/types/tool/index.ts +40 -0
- package/src/types/tool/presentation.ts +4 -0
package/src/compaction/wire.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { CompactionConfig } from '../config/runtime.js'
|
|
2
2
|
import { WorkingStateManager } from './manager.js'
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
FileSlot,
|
|
5
|
+
PinSlot,
|
|
6
|
+
PlanSlot,
|
|
7
|
+
RestorableState,
|
|
8
|
+
ToolResultSlot,
|
|
9
|
+
WorkingState,
|
|
10
|
+
} from './types.js'
|
|
4
11
|
|
|
5
12
|
/**
|
|
6
13
|
* The working state in a form that survives a process boundary.
|
|
@@ -42,6 +49,8 @@ export interface WorkingStateSnapshot {
|
|
|
42
49
|
* completeness it does not have.
|
|
43
50
|
*/
|
|
44
51
|
readonly evicted: Readonly<Record<string, number>>
|
|
52
|
+
/** Absent in snapshots written before bodies were restored; restored as none. */
|
|
53
|
+
readonly restorable?: RestorableState
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
export function snapshotWorkingState(manager: WorkingStateManager): WorkingStateSnapshot {
|
|
@@ -59,6 +68,14 @@ export function snapshotWorkingState(manager: WorkingStateManager): WorkingState
|
|
|
59
68
|
userRequirements: [...state.userRequirements],
|
|
60
69
|
assistantNotes: [...state.assistantNotes],
|
|
61
70
|
evicted: { ...state.evicted },
|
|
71
|
+
...(state.restorable
|
|
72
|
+
? {
|
|
73
|
+
restorable: {
|
|
74
|
+
skills: state.restorable.skills.map((skill) => ({ ...skill })),
|
|
75
|
+
files: state.restorable.files.map((file) => ({ ...file })),
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
: {}),
|
|
62
79
|
}
|
|
63
80
|
}
|
|
64
81
|
|
|
@@ -89,6 +106,14 @@ export function restoreWorkingState(
|
|
|
89
106
|
userRequirements: [...snapshot.userRequirements],
|
|
90
107
|
assistantNotes: [...snapshot.assistantNotes],
|
|
91
108
|
evicted: { ...snapshot.evicted },
|
|
109
|
+
...(snapshot.restorable
|
|
110
|
+
? {
|
|
111
|
+
restorable: {
|
|
112
|
+
skills: snapshot.restorable.skills.map((skill) => ({ ...skill })),
|
|
113
|
+
files: snapshot.restorable.files.map((file) => ({ ...file })),
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
: {}),
|
|
92
117
|
}
|
|
93
118
|
manager.replaceState(state)
|
|
94
119
|
return manager
|
package/src/index.ts
CHANGED
package/src/plugin/shell-hook.ts
CHANGED
|
@@ -14,7 +14,13 @@
|
|
|
14
14
|
* The command runs with `sh -c` in `cwd`. It receives one JSON object on
|
|
15
15
|
* stdin — `event`, `cwd`, `session_id`, `turn_id` inside a turn, and for
|
|
16
16
|
* tool events `tool_name`, `tool_input` and (after the call) `tool_result`;
|
|
17
|
-
* `subagent_stop` adds `parent_session_id` and `parent_turn_id
|
|
17
|
+
* `subagent_stop` adds `parent_session_id` and `parent_turn_id`. Each fact a
|
|
18
|
+
* script written for another host would look for under a different name is
|
|
19
|
+
* also sent under that name: `hook_event_name` (the PascalCase spelling,
|
|
20
|
+
* `PreToolUse`, `Stop`, ...), `tool_response` (the same object as
|
|
21
|
+
* `tool_result`), and, when the host knows them, `transcript_path` and
|
|
22
|
+
* `permission_mode`. The Namzu names stay, so no existing script changes.
|
|
23
|
+
* The same facts go out as
|
|
18
24
|
* same facts as `NAMZU_HOOK_EVENT`, `NAMZU_SESSION_ID`, `NAMZU_TURN_ID`,
|
|
19
25
|
* `NAMZU_TOOL_NAME` and, when the input names a file, `NAMZU_TOOL_PATH`.
|
|
20
26
|
*
|
|
@@ -305,6 +311,10 @@ export interface ShellHookInput {
|
|
|
305
311
|
readonly notification?: PluginNotificationInfo
|
|
306
312
|
/** On `turn_end` and `subagent_stop`: an earlier answer already kept this turn going. */
|
|
307
313
|
readonly stopHookActive?: boolean
|
|
314
|
+
/** The session's transcript file, when the host has one. Sent as `transcript_path`. */
|
|
315
|
+
readonly transcriptPath?: string
|
|
316
|
+
/** How tool calls are being decided right now. Sent as `permission_mode`. */
|
|
317
|
+
readonly permissionMode?: string
|
|
308
318
|
}
|
|
309
319
|
|
|
310
320
|
/** Run one hook command to completion, bounded by its deadline and capture. */
|
|
@@ -319,8 +329,11 @@ export function runShellHook(
|
|
|
319
329
|
)
|
|
320
330
|
const payload = JSON.stringify({
|
|
321
331
|
event: input.event,
|
|
332
|
+
hook_event_name: PASCAL_EVENT_NAMES[input.event] ?? input.event,
|
|
322
333
|
cwd: input.cwd,
|
|
323
334
|
session_id: input.sessionId,
|
|
335
|
+
...(input.transcriptPath !== undefined ? { transcript_path: input.transcriptPath } : {}),
|
|
336
|
+
...(input.permissionMode !== undefined ? { permission_mode: input.permissionMode } : {}),
|
|
324
337
|
...(input.turnId !== undefined ? { turn_id: input.turnId } : {}),
|
|
325
338
|
...(input.parentSessionId !== undefined ? { parent_session_id: input.parentSessionId } : {}),
|
|
326
339
|
...(input.parentTurnId !== undefined ? { parent_turn_id: input.parentTurnId } : {}),
|
|
@@ -328,7 +341,9 @@ export function runShellHook(
|
|
|
328
341
|
...(input.compaction !== undefined ? { compaction: input.compaction } : {}),
|
|
329
342
|
...(input.toolName !== undefined ? { tool_name: input.toolName } : {}),
|
|
330
343
|
...(input.toolInput !== undefined ? { tool_input: input.toolInput } : {}),
|
|
331
|
-
...(input.toolResult !== undefined
|
|
344
|
+
...(input.toolResult !== undefined
|
|
345
|
+
? { tool_result: input.toolResult, tool_response: input.toolResult }
|
|
346
|
+
: {}),
|
|
332
347
|
...(input.event === 'post_tool_use_failure' && input.toolResult?.error !== undefined
|
|
333
348
|
? { error: input.toolResult.error }
|
|
334
349
|
: {}),
|
|
@@ -589,11 +604,12 @@ function readShellHookJson(event: ShellHookEvent, stdout: string): ShellHookRead
|
|
|
589
604
|
reading.permission = { decision, ...(why ? { reason: why } : {}) }
|
|
590
605
|
} else if (decision !== 'defer') {
|
|
591
606
|
return {
|
|
592
|
-
invalid: '`permissionDecision` must be "allow", "deny" or "
|
|
607
|
+
invalid: '`permissionDecision` must be "allow", "deny", "ask" or "defer"',
|
|
593
608
|
}
|
|
594
609
|
}
|
|
595
|
-
// "defer"
|
|
596
|
-
//
|
|
610
|
+
// "defer" is accepted and decides nothing: a script that hands a call
|
|
611
|
+
// back to a separate permission service gets the ordinary rules, the
|
|
612
|
+
// same as a hook that said nothing.
|
|
597
613
|
}
|
|
598
614
|
if (specific.updatedInput !== undefined) {
|
|
599
615
|
if (!isRecord(specific.updatedInput)) return { invalid: '`updatedInput` must be an object' }
|
|
@@ -802,6 +818,14 @@ export interface ShellHookOptions {
|
|
|
802
818
|
/** Where the command runs and what `cwd` says on stdin. */
|
|
803
819
|
readonly cwd: string
|
|
804
820
|
readonly log?: Logger
|
|
821
|
+
/**
|
|
822
|
+
* The transcript file of a session, sent as `transcript_path`. Called at
|
|
823
|
+
* each run, because a host's session can change under one manager. Absent
|
|
824
|
+
* or returning `undefined`, the field is left out.
|
|
825
|
+
*/
|
|
826
|
+
readonly transcriptPath?: (sessionId: string) => string | undefined
|
|
827
|
+
/** The permission mode in force now, sent as `permission_mode`. Called at each run. */
|
|
828
|
+
readonly permissionMode?: () => string | undefined
|
|
805
829
|
}
|
|
806
830
|
|
|
807
831
|
/** One entry as a hook definition the lifecycle manager accepts. */
|
|
@@ -828,6 +852,8 @@ export function createShellHook(
|
|
|
828
852
|
return { action: 'continue' }
|
|
829
853
|
}
|
|
830
854
|
if (entry.once === true) spent = true
|
|
855
|
+
const transcriptPath = options.transcriptPath?.(String(context.sessionId))
|
|
856
|
+
const permissionMode = options.permissionMode?.()
|
|
831
857
|
const outcome = await runShellHook(
|
|
832
858
|
entry,
|
|
833
859
|
{
|
|
@@ -858,6 +884,8 @@ export function createShellHook(
|
|
|
858
884
|
: {}),
|
|
859
885
|
...(context.notification !== undefined ? { notification: context.notification } : {}),
|
|
860
886
|
...(context.stopHookActive === true ? { stopHookActive: true } : {}),
|
|
887
|
+
...(transcriptPath !== undefined ? { transcriptPath } : {}),
|
|
888
|
+
...(permissionMode !== undefined ? { permissionMode } : {}),
|
|
861
889
|
},
|
|
862
890
|
context.signal,
|
|
863
891
|
)
|
|
@@ -70,7 +70,7 @@ export const CODING_AGENT_WORKING_DOCTRINE = `## How you work
|
|
|
70
70
|
- Never change git configuration or other persistent settings without asking: not \`user.name\` or \`user.email\`, hooks, remotes, credential helpers, or any \`git config\` (local or global), nor a shell profile or a tool's own config file. If a commit fails because no identity is set, stop and tell the user the command to set one; do not invent one.
|
|
71
71
|
|
|
72
72
|
### Keeping the user informed
|
|
73
|
-
- Before
|
|
73
|
+
- Before your first tool call, say in one short line what you are about to do and why. While you work, add a one-line update when you find something, change direction or hit a blocker, and after a long stretch without one, say in a few words where you are, then continue. Brief is good and silence is not; the tool rows on screen already show the details.
|
|
74
74
|
- Keep the final reply short and specific: what changed, what was verified, what is left.`
|
|
75
75
|
|
|
76
76
|
/**
|
|
@@ -78,8 +78,9 @@ export const CODING_AGENT_WORKING_DOCTRINE = `## How you work
|
|
|
78
78
|
* delegated sub-agent does not have. See the module comment for the split.
|
|
79
79
|
*/
|
|
80
80
|
export const CODING_AGENT_DELEGATION_DOCTRINE = `### Planning and delegating
|
|
81
|
-
- For work
|
|
81
|
+
- For work of three or more steps — several files, several distinct stages, anything you would write a checklist for — open a task list with \`task_create\` and keep it current with \`task_update\`, marking each step done when it is done rather than at the end. Skip it for a single inspect-edit-test cycle or a two-step job: the list is for the user to follow, and a list of one or two items tells them nothing.
|
|
82
82
|
- When the \`Agent\` tool is available, delegate independent work through it and give each delegation a short, specific description; the user watches those descriptions, not the prompts. Brief each sub-agent with four things: the objective; what is out of bounds; which tools and sources to use; and the shape of the reply you want, such as a short summary with a file path for anything long. It starts with none of your context and returns what you ask for.
|
|
83
|
+
- When delegated agents finish, tell the user what each one found in your next message. Their results reach you and not the user, so a result you do not pass on stays unseen.
|
|
83
84
|
- Delegate lookups — where something is defined, which files reference it, how a module works — with \`subagent_type: "explore"\`: it has reading and searching tools only and never interrupts the user for permission. Reserve the default sub-agent for work that changes files or runs commands.`
|
|
84
85
|
|
|
85
86
|
/**
|
package/src/provider/errors.ts
CHANGED
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
|
|
32
32
|
import { LOG_SECRET_PATTERNS } from '../constants/secret-patterns.js'
|
|
33
33
|
import type { ProviderErrorKind, ProviderRequestErrorInit } from '../types/provider/error.js'
|
|
34
|
+
import {
|
|
35
|
+
type TransportCause,
|
|
36
|
+
readCertificateFailure,
|
|
37
|
+
readTransportCause,
|
|
38
|
+
} from '../types/provider/errors.js'
|
|
34
39
|
export type {
|
|
35
40
|
ProviderErrorInfo,
|
|
36
41
|
ProviderErrorKind,
|
|
@@ -78,6 +83,8 @@ export class ProviderRequestError extends Error {
|
|
|
78
83
|
public readonly detail?: string
|
|
79
84
|
/** The provider's `x-should-retry` verdict, when it sent one; see {@link ProviderRequestErrorInit.shouldRetry}. */
|
|
80
85
|
public readonly shouldRetry?: boolean
|
|
86
|
+
/** The part of the network path the request tripped on, when known; see {@link ProviderRequestErrorInit.transport}. */
|
|
87
|
+
public readonly transport?: TransportCause
|
|
81
88
|
|
|
82
89
|
constructor(init: ProviderRequestErrorInit) {
|
|
83
90
|
super(buildProviderErrorMessage(init))
|
|
@@ -90,6 +97,7 @@ export class ProviderRequestError extends Error {
|
|
|
90
97
|
if (init.retryAfterMs !== undefined) this.retryAfterMs = init.retryAfterMs
|
|
91
98
|
if (init.detail !== undefined) this.detail = init.detail
|
|
92
99
|
if (init.shouldRetry !== undefined) this.shouldRetry = init.shouldRetry
|
|
100
|
+
if (init.transport !== undefined) this.transport = init.transport
|
|
93
101
|
}
|
|
94
102
|
}
|
|
95
103
|
|
|
@@ -520,8 +528,14 @@ export function providerVendorError(input: {
|
|
|
520
528
|
const name = error instanceof Error ? error.name : ''
|
|
521
529
|
const message = error instanceof Error ? error.message : ''
|
|
522
530
|
|
|
531
|
+
// A proxy that refuses the tunnel or a certificate nobody trusts has no status
|
|
532
|
+
// to read, and the same request meets the same obstacle again.
|
|
533
|
+
const transport = readTransportCause(error)
|
|
534
|
+
|
|
523
535
|
let kind: ProviderErrorKind
|
|
524
|
-
if (
|
|
536
|
+
if (transport !== undefined) {
|
|
537
|
+
kind = 'network'
|
|
538
|
+
} else if (/ThrottlingException|TooManyRequests|ServiceQuotaExceeded/i.test(name)) {
|
|
525
539
|
kind = 'throttle'
|
|
526
540
|
} else if (/AccessDenied|Unauthorized|Forbidden|Authentication/i.test(name)) {
|
|
527
541
|
kind = 'auth'
|
|
@@ -550,7 +564,12 @@ export function providerVendorError(input: {
|
|
|
550
564
|
input.retryAfter ?? vendorRetryAfter(error),
|
|
551
565
|
input.now ?? Date.now(),
|
|
552
566
|
)
|
|
553
|
-
|
|
567
|
+
// A certificate that fails verification is refused the same way every time.
|
|
568
|
+
const certificate = status === undefined ? readCertificateFailure(error) : undefined
|
|
569
|
+
const shouldRetry =
|
|
570
|
+
transport !== undefined || certificate !== undefined
|
|
571
|
+
? false
|
|
572
|
+
: parseShouldRetry(vendorHeader(error, ['x-should-retry']))
|
|
554
573
|
const fields = error as { type?: unknown; param?: unknown; requestID?: unknown } | null
|
|
555
574
|
// A message that already carries its body (Anthropic's) has the fields; one that is only the
|
|
556
575
|
// parsed sentence (OpenAI's) gets them from the SDK error's own properties.
|
|
@@ -566,11 +585,16 @@ export function providerVendorError(input: {
|
|
|
566
585
|
return new ProviderRequestError({
|
|
567
586
|
kind,
|
|
568
587
|
providerId: input.providerId,
|
|
569
|
-
...(
|
|
588
|
+
...(certificate !== undefined
|
|
589
|
+
? { providerCode: certificate }
|
|
590
|
+
: exactProviderCode !== undefined
|
|
591
|
+
? { providerCode: exactProviderCode }
|
|
592
|
+
: {}),
|
|
570
593
|
...(status !== undefined ? { status } : {}),
|
|
571
594
|
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
572
595
|
...(detail !== undefined ? { detail } : {}),
|
|
573
596
|
...(shouldRetry !== undefined ? { shouldRetry } : {}),
|
|
597
|
+
...(transport !== undefined ? { transport } : {}),
|
|
574
598
|
})
|
|
575
599
|
}
|
|
576
600
|
|
package/src/provider/fallback.ts
CHANGED
|
@@ -225,6 +225,24 @@ function isOutputChunk(chunk: StreamChunk): boolean {
|
|
|
225
225
|
)
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Output the consumer can take back: reasoning, or the pieces of a tool call
|
|
230
|
+
* that is still being written (nothing runs before the response settles).
|
|
231
|
+
* A finished call, text, a citation, a hosted tool or a finish reason is real.
|
|
232
|
+
*/
|
|
233
|
+
function isVoidable(chunk: StreamChunk): boolean {
|
|
234
|
+
const { content, toolCallEnd, citation, hostedTool, textPart } = chunk.delta
|
|
235
|
+
return !(
|
|
236
|
+
content ||
|
|
237
|
+
toolCallEnd ||
|
|
238
|
+
citation !== undefined ||
|
|
239
|
+
hostedTool !== undefined ||
|
|
240
|
+
textPart !== undefined ||
|
|
241
|
+
chunk.textParts !== undefined ||
|
|
242
|
+
chunk.finishReason !== undefined
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
228
246
|
/** Read one member's canonical menu, adapting the one-release legacy member. */
|
|
229
247
|
function memberReasoningEffortLevels(
|
|
230
248
|
member: ProviderChainMember,
|
|
@@ -405,9 +423,20 @@ export function withProviderFallback(
|
|
|
405
423
|
assertNativeStructuredOutputSupported(member.provider, request)
|
|
406
424
|
assertHostedWebSearchSupported(member.provider, request)
|
|
407
425
|
let produced = false
|
|
426
|
+
let fragments = false
|
|
408
427
|
try {
|
|
409
428
|
for await (const chunk of member.provider.chatStream(request)) {
|
|
410
|
-
|
|
429
|
+
// The retry notice that restarts a request voids what the failed
|
|
430
|
+
// attempt wrote, and the consumer drops it: nothing real was produced.
|
|
431
|
+
if (chunk.retry?.restart) {
|
|
432
|
+
produced = false
|
|
433
|
+
fragments = false
|
|
434
|
+
} else if (isOutputChunk(chunk)) {
|
|
435
|
+
// Pieces the consumer can drop on a notice (a call still being
|
|
436
|
+
// written, reasoning) are not output a swap would duplicate.
|
|
437
|
+
if (isVoidable(chunk)) fragments = true
|
|
438
|
+
else produced = true
|
|
439
|
+
}
|
|
411
440
|
yield chunk
|
|
412
441
|
}
|
|
413
442
|
return
|
|
@@ -457,13 +486,16 @@ export function withProviderFallback(
|
|
|
457
486
|
fallback: {
|
|
458
487
|
fromIndex: cursor - 1,
|
|
459
488
|
fromProviderId: member.provider.id,
|
|
460
|
-
|
|
489
|
+
// The model this member was asked for, which for the head of the chain
|
|
490
|
+
// (declared without one) is the request's own.
|
|
491
|
+
...(targetModel ? { fromModel: targetModel } : {}),
|
|
461
492
|
toIndex: cursor,
|
|
462
493
|
toProviderId: to.provider.id,
|
|
463
494
|
...(to.model !== undefined ? { toModel: to.model } : {}),
|
|
464
495
|
code: classified.code,
|
|
465
496
|
...(classified.status !== undefined ? { status: classified.status } : {}),
|
|
466
497
|
reason: classified.message,
|
|
498
|
+
...(fragments ? { restart: true } : {}),
|
|
467
499
|
},
|
|
468
500
|
}
|
|
469
501
|
}
|
package/src/provider/retry.ts
CHANGED
|
@@ -60,8 +60,13 @@ export interface ProviderRetryConfig extends BackoffPolicy {
|
|
|
60
60
|
* Off by default, because the decorator cannot un-emit: the consumer has
|
|
61
61
|
* already seen those deltas. When on, the retry notice carries
|
|
62
62
|
* `restart: true` and the consumer must discard what the failed attempt
|
|
63
|
-
* produced; the runtime does this itself.
|
|
64
|
-
*
|
|
63
|
+
* produced; the runtime does this itself.
|
|
64
|
+
*
|
|
65
|
+
* A stream that has so far produced only tool-call fragments (no text) is
|
|
66
|
+
* restarted whatever this says: nothing runs before the response settles,
|
|
67
|
+
* and the half-written call is discarded on the same notice. A stream in
|
|
68
|
+
* which a call finished, or the provider ran a hosted tool, is never
|
|
69
|
+
* restarted, because that work is real.
|
|
65
70
|
*/
|
|
66
71
|
readonly retryMidStream?: boolean
|
|
67
72
|
}
|
|
@@ -113,9 +118,11 @@ function isReasoningOnly(chunk: StreamChunk): boolean {
|
|
|
113
118
|
* blocks by index, and clears them on a restart). Once a text or tool-call delta has been
|
|
114
119
|
* yielded the consumer has already emitted `text_delta` events and
|
|
115
120
|
* appended to its buffer, so restarting the request would duplicate
|
|
116
|
-
* output. A mid-stream failure is therefore surfaced, not retried
|
|
117
|
-
* existing stream-error path in
|
|
118
|
-
* still salvage a
|
|
121
|
+
* output. A mid-stream failure after text is therefore surfaced, not retried,
|
|
122
|
+
* unless `retryMidStream` — the existing stream-error path in
|
|
123
|
+
* `streamProviderTurn` handles it (and can still salvage a finished call).
|
|
124
|
+
* A stream that has only begun tool calls has shown no text, so it is retried
|
|
125
|
+
* too, the half-written call dropped. This is the whole reason the
|
|
119
126
|
* retry lives in a decorator rather than inside the loop: the loop cannot
|
|
120
127
|
* un-emit.
|
|
121
128
|
*
|
|
@@ -158,8 +165,13 @@ export function withProviderRetry(
|
|
|
158
165
|
// this is retryable without `retryMidStream`; the notice says
|
|
159
166
|
// `restart` so a host that showed the thinking drops it.
|
|
160
167
|
let reasoned = false
|
|
161
|
-
//
|
|
162
|
-
//
|
|
168
|
+
// Text, a citation or a part reached the consumer: a pipe cannot take
|
|
169
|
+
// that back, so a restart needs `retryMidStream`.
|
|
170
|
+
let printed = false
|
|
171
|
+
// A call finished (its arguments are whole) or the provider ran a hosted
|
|
172
|
+
// tool: that work is real, so a restart is never offered. A call still
|
|
173
|
+
// being written is not work: nothing runs before the response settles,
|
|
174
|
+
// and the consumer drops the half-written call on `restart`.
|
|
163
175
|
let acted = false
|
|
164
176
|
try {
|
|
165
177
|
for await (const chunk of provider.chatStream(params)) {
|
|
@@ -173,9 +185,16 @@ export function withProviderRetry(
|
|
|
173
185
|
if (isReasoningOnly(chunk)) reasoned = true
|
|
174
186
|
else produced = true
|
|
175
187
|
}
|
|
176
|
-
if (
|
|
177
|
-
|
|
188
|
+
if (
|
|
189
|
+
chunk.delta.content ||
|
|
190
|
+
chunk.delta.textPart !== undefined ||
|
|
191
|
+
chunk.textParts !== undefined ||
|
|
192
|
+
chunk.delta.citation !== undefined
|
|
193
|
+
) {
|
|
194
|
+
printed = true
|
|
178
195
|
}
|
|
196
|
+
// A reported finish means the response was whole.
|
|
197
|
+
if (chunk.delta.toolCallEnd || chunk.delta.hostedTool || chunk.finishReason) acted = true
|
|
179
198
|
|
|
180
199
|
if (isErrorOnly && !produced && attempt < config.maxRetries) {
|
|
181
200
|
throw new Error(chunk.error)
|
|
@@ -203,7 +222,8 @@ export function withProviderRetry(
|
|
|
203
222
|
const exhausted = attempt >= config.maxRetries
|
|
204
223
|
|
|
205
224
|
const restartable =
|
|
206
|
-
(produced && config.retryMidStream === true
|
|
225
|
+
(produced && !acted && (config.retryMidStream === true || !printed)) ||
|
|
226
|
+
(!produced && reasoned)
|
|
207
227
|
if ((produced && !restartable) || !classified.retryable || exhausted) {
|
|
208
228
|
log?.warn('Provider call failed', {
|
|
209
229
|
[GENAI.SYSTEM]: provider.id,
|
package/src/public-runtime.ts
CHANGED
|
@@ -543,6 +543,9 @@ export {
|
|
|
543
543
|
EXPLORE_AGENT_ID,
|
|
544
544
|
EXPLORE_AGENT_PROMPT,
|
|
545
545
|
MAX_AGENT_FILE_CHARS,
|
|
546
|
+
PLAN_AGENT_DESCRIPTION,
|
|
547
|
+
PLAN_AGENT_ID,
|
|
548
|
+
PLAN_AGENT_PROMPT,
|
|
546
549
|
parseAgentFile,
|
|
547
550
|
parseAgentMarkdown,
|
|
548
551
|
} from './agents/index.js'
|
|
@@ -782,6 +785,10 @@ export type {
|
|
|
782
785
|
WithProviderFallbackOptions,
|
|
783
786
|
WithProviderRetryOptions,
|
|
784
787
|
} from './provider/index.js'
|
|
788
|
+
// Which part of the network path a failed request tripped on (a proxy that
|
|
789
|
+
// wants a sign-in, an untrusted certificate), read off the failure's causes.
|
|
790
|
+
export { readTransportCause } from './types/provider/errors.js'
|
|
791
|
+
export type { TransportCause } from './types/provider/errors.js'
|
|
785
792
|
// The curve `ProviderRetryConfig` extends and `query({ toolRetryBackoff })`
|
|
786
793
|
// takes a partial of. Both public surfaces name it, so a consumer has to be
|
|
787
794
|
// able to name it too — a type reachable only through an inline `import(...)`
|
|
@@ -1340,6 +1347,7 @@ export {
|
|
|
1340
1347
|
WorkingStateManager,
|
|
1341
1348
|
} from './compaction/index.js'
|
|
1342
1349
|
export type {
|
|
1350
|
+
ToolHistoryRepairOptions,
|
|
1343
1351
|
ToolHistoryRepairReport,
|
|
1344
1352
|
ToolHistoryRepairResult,
|
|
1345
1353
|
} from './compaction/index.js'
|
package/src/public-types.ts
CHANGED
|
@@ -358,12 +358,111 @@ function groupAlive(pid: number | undefined): boolean {
|
|
|
358
358
|
|
|
359
359
|
const GROUP_POLL_MS = 250
|
|
360
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Why a command that was running in the foreground moved to the background:
|
|
363
|
+
* the person asked (`user`), or the person sent a message that has to reach
|
|
364
|
+
* the model while the command is still running (`message`).
|
|
365
|
+
*/
|
|
366
|
+
export type ForegroundHandoffReason = 'user' | 'message'
|
|
367
|
+
|
|
368
|
+
/** A foreground command that can be moved to the background right now. */
|
|
369
|
+
export interface ForegroundCommand {
|
|
370
|
+
readonly id: string
|
|
371
|
+
readonly owner: string
|
|
372
|
+
readonly command: string
|
|
373
|
+
readonly startedAt: number
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
interface ForegroundEntry extends ForegroundCommand {
|
|
377
|
+
readonly background: (reason: ForegroundHandoffReason) => boolean
|
|
378
|
+
}
|
|
379
|
+
|
|
361
380
|
export class BackgroundJobRegistry {
|
|
362
381
|
private readonly jobs = new Map<string, JobEntry>()
|
|
363
382
|
private readonly exitListeners = new Set<(job: BackgroundJob) => void>()
|
|
364
383
|
private readonly monitorListeners = new Set<(event: MonitorEvent) => void>()
|
|
365
384
|
private outputWaiters = 0
|
|
366
385
|
private readonly outputWaitersByOwner = new Map<string, number>()
|
|
386
|
+
private readonly foreground = new Map<string, ForegroundEntry>()
|
|
387
|
+
private readonly foregroundListeners = new Set<(owner: string) => void>()
|
|
388
|
+
private foregroundSeq = 0
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* A foreground command a tool is waiting on, offered for hand-over: while it
|
|
392
|
+
* is registered, {@link backgroundForeground} can move it to a job. The tool
|
|
393
|
+
* registers once the command has run long enough that moving it is worth
|
|
394
|
+
* offering, and unregisters (the returned function) when the call settles.
|
|
395
|
+
* `background` returns whether the command was taken over.
|
|
396
|
+
*/
|
|
397
|
+
registerForeground(
|
|
398
|
+
owner: string,
|
|
399
|
+
entry: {
|
|
400
|
+
readonly command: string
|
|
401
|
+
readonly background: (reason: ForegroundHandoffReason) => boolean
|
|
402
|
+
},
|
|
403
|
+
): () => void {
|
|
404
|
+
const id = `fg-${++this.foregroundSeq}`
|
|
405
|
+
this.foreground.set(id, {
|
|
406
|
+
id,
|
|
407
|
+
owner,
|
|
408
|
+
command: entry.command,
|
|
409
|
+
startedAt: Date.now(),
|
|
410
|
+
background: entry.background,
|
|
411
|
+
})
|
|
412
|
+
this.announceForeground(owner)
|
|
413
|
+
return () => {
|
|
414
|
+
if (this.foreground.delete(id)) this.announceForeground(owner)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Foreground commands of one owner that can be moved to the background, oldest first. */
|
|
419
|
+
foregroundCommands(owner: string): readonly ForegroundCommand[] {
|
|
420
|
+
return [...this.foreground.values()]
|
|
421
|
+
.filter((entry) => entry.owner === owner)
|
|
422
|
+
.map(({ id, command, startedAt }) => ({ id, owner, command, startedAt }))
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Move every foreground command of `owner` to the background. Returns how
|
|
427
|
+
* many were taken over; a command that ended or could not be taken over
|
|
428
|
+
* keeps its own outcome and is not counted.
|
|
429
|
+
*/
|
|
430
|
+
backgroundForeground(owner: string, reason: ForegroundHandoffReason): number {
|
|
431
|
+
let moved = 0
|
|
432
|
+
for (const entry of [...this.foreground.values()]) {
|
|
433
|
+
if (entry.owner !== owner) continue
|
|
434
|
+
let taken = false
|
|
435
|
+
try {
|
|
436
|
+
taken = entry.background(reason)
|
|
437
|
+
} catch {
|
|
438
|
+
// A command that cannot be handed over keeps running where it is.
|
|
439
|
+
}
|
|
440
|
+
if (taken) {
|
|
441
|
+
moved += 1
|
|
442
|
+
this.foreground.delete(entry.id)
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (moved > 0) this.announceForeground(owner)
|
|
446
|
+
return moved
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** Be told when the set of hand-over-able foreground commands of any owner changes. */
|
|
450
|
+
onForegroundChange(listener: (owner: string) => void): () => void {
|
|
451
|
+
this.foregroundListeners.add(listener)
|
|
452
|
+
return () => {
|
|
453
|
+
this.foregroundListeners.delete(listener)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private announceForeground(owner: string): void {
|
|
458
|
+
for (const listener of this.foregroundListeners) {
|
|
459
|
+
try {
|
|
460
|
+
listener(owner)
|
|
461
|
+
} catch {
|
|
462
|
+
// A listener that throws is its owner's problem.
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
367
466
|
|
|
368
467
|
/**
|
|
369
468
|
* Be told when a job ends, whoever owns it. A job outlives the call that
|
|
@@ -1257,6 +1356,12 @@ export function bindOwner(
|
|
|
1257
1356
|
hostOutput: true,
|
|
1258
1357
|
...(params.initialOutput ? { initialOutput: params.initialOutput } : {}),
|
|
1259
1358
|
}),
|
|
1359
|
+
// The owner is bound here, so a tool can offer its own waiting command and
|
|
1360
|
+
// no other turn's.
|
|
1361
|
+
registerForeground: (entry: {
|
|
1362
|
+
command: string
|
|
1363
|
+
background: (reason: ForegroundHandoffReason) => boolean
|
|
1364
|
+
}) => registry.registerForeground(owner, entry),
|
|
1260
1365
|
get: (id: string) => mine(id),
|
|
1261
1366
|
awaitStarted: (id: string) => {
|
|
1262
1367
|
mine(id)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SessionLog } from '../../store/session-log/index.js'
|
|
2
|
+
import type { SessionRecord } from '../../types/session/records.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Ids of the tool calls the log proves never started.
|
|
6
|
+
*
|
|
7
|
+
* A call that started has a `tool_executing` record, so one with no result is
|
|
8
|
+
* of unknown outcome. A call of a turn that journals its calls (it has a
|
|
9
|
+
* `tool_executing` or a `tool_review_requested` record) and has none of its
|
|
10
|
+
* own ran nothing: it was queued or waiting for approval when the process
|
|
11
|
+
* ended. A turn with no such record proves nothing, so its calls are left out.
|
|
12
|
+
*/
|
|
13
|
+
export async function readCallsThatNeverStarted(log: SessionLog): Promise<ReadonlySet<string>> {
|
|
14
|
+
const started = new Set<string>()
|
|
15
|
+
const journaled = new Set<string>()
|
|
16
|
+
const callsOfTurn = new Map<string, string[]>()
|
|
17
|
+
for await (const entry of log.read()) {
|
|
18
|
+
const record = entry.record as SessionRecord
|
|
19
|
+
if (record.type === 'tool_review_requested') {
|
|
20
|
+
journaled.add(record.turnId)
|
|
21
|
+
} else if (record.type === 'tool_executing') {
|
|
22
|
+
journaled.add(record.turnId)
|
|
23
|
+
started.add(record.toolUseId)
|
|
24
|
+
} else if (record.type === 'message') {
|
|
25
|
+
const content = record.content as { role?: string; toolCalls?: readonly { id: string }[] }
|
|
26
|
+
if (content?.role !== 'assistant' || !content.toolCalls) continue
|
|
27
|
+
const ids = callsOfTurn.get(record.turnId) ?? []
|
|
28
|
+
for (const call of content.toolCalls) ids.push(call.id)
|
|
29
|
+
callsOfTurn.set(record.turnId, ids)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const never = new Set<string>()
|
|
33
|
+
for (const turnId of journaled) {
|
|
34
|
+
for (const id of callsOfTurn.get(turnId) ?? []) if (!started.has(id)) never.add(id)
|
|
35
|
+
}
|
|
36
|
+
return never
|
|
37
|
+
}
|
|
@@ -717,8 +717,8 @@ function ceilingBelow(length: number): number | undefined {
|
|
|
717
717
|
|
|
718
718
|
/**
|
|
719
719
|
* How much one call should carry, in the model's words, or `undefined` when
|
|
720
|
-
* there is no number to give:
|
|
721
|
-
* large arguments.
|
|
720
|
+
* there is no number to give: after an output limit that left no figure worth
|
|
721
|
+
* stating, to a tool that declares no large arguments.
|
|
722
722
|
*
|
|
723
723
|
* A tool that declares large arguments is given a budget for each of them.
|
|
724
724
|
* After an output limit, any tool is told to keep its arguments under half of
|
|
@@ -741,9 +741,7 @@ function sizeAdvice(
|
|
|
741
741
|
// would tell a call cut after three characters to keep `content` under
|
|
742
742
|
// 12000 of them: more than a budget, an invitation back into the same
|
|
743
743
|
// cutoff, and a broken invariant (a call is always told less than what
|
|
744
|
-
// arrived).
|
|
745
|
-
// limit — where no ceiling applies at all — still gets the plain
|
|
746
|
-
// declared budgets below.
|
|
744
|
+
// arrived).
|
|
747
745
|
if (lengthCutoff && ceiling === undefined) return undefined
|
|
748
746
|
const declared = Object.entries(largeStringArguments ?? {}).filter(
|
|
749
747
|
([, budget]) => Number.isFinite(budget) && budget > 0,
|
|
@@ -831,8 +829,8 @@ function unseenOutput(error: ToolInputError): string | undefined {
|
|
|
831
829
|
* call is to carry less, {@link sizeAdvice} and the tool's
|
|
832
830
|
* `truncatedInputHint`, with nothing about reasoning or what came before
|
|
833
831
|
* it: the window is the conversation's length, not this response's.
|
|
834
|
-
* - Cut off by the
|
|
835
|
-
*
|
|
832
|
+
* - Cut off by the connection dropping: no size and no hint. The call did not
|
|
833
|
+
* come near any limit, so it is to be sent again unchanged.
|
|
836
834
|
* - Stopped by a content filter: no advice. Sending less does not get past a
|
|
837
835
|
* filter.
|
|
838
836
|
*
|
|
@@ -874,7 +872,7 @@ export function unreadableToolInputMessage(
|
|
|
874
872
|
? 'the response reached its output token limit'
|
|
875
873
|
: error.finishReason === 'content_filter'
|
|
876
874
|
? "the provider's content filter stopped the response"
|
|
877
|
-
: 'the
|
|
875
|
+
: 'the connection dropped'
|
|
878
876
|
parts.push(
|
|
879
877
|
`Error: The call to "${toolName}" was cut off: ${cause} after ${error.length} characters of its arguments, before they were complete. The tool was NOT executed.`,
|
|
880
878
|
)
|
|
@@ -897,9 +895,14 @@ export function unreadableToolInputMessage(
|
|
|
897
895
|
parts.push(
|
|
898
896
|
`${error.precedingLength} of the ${error.precedingLength + error.length} characters the response streamed came before this call, so send the call again with less before it in the same response.`,
|
|
899
897
|
)
|
|
900
|
-
} else if (error.finishReason
|
|
898
|
+
} else if (error.finishReason === 'length') {
|
|
901
899
|
parts.push(sizeAdvice(tool?.largeStringArguments, error) ?? 'Send the call again.')
|
|
902
900
|
hint(tool?.truncatedInputHint)
|
|
901
|
+
} else if (error.finishReason !== 'content_filter') {
|
|
902
|
+
// The transport ended the response, not a limit: the call may have been
|
|
903
|
+
// well inside any budget, so no size is named and the call is not to
|
|
904
|
+
// change. Sending the same call again is the whole remedy.
|
|
905
|
+
parts.push('Nothing was wrong with the call. Send the same call again, unchanged.')
|
|
903
906
|
}
|
|
904
907
|
}
|
|
905
908
|
return parts.join(' ')
|