@namzu/sdk 1.2.0 → 1.4.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.
Files changed (127) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/dist/compaction/__tests__/verifier-empty-reply.test.d.ts +12 -0
  3. package/dist/compaction/__tests__/verifier-empty-reply.test.d.ts.map +1 -0
  4. package/dist/compaction/__tests__/verifier-empty-reply.test.js +73 -0
  5. package/dist/compaction/__tests__/verifier-empty-reply.test.js.map +1 -0
  6. package/dist/compaction/verifier.d.ts.map +1 -1
  7. package/dist/compaction/verifier.js +10 -1
  8. package/dist/compaction/verifier.js.map +1 -1
  9. package/dist/config/__tests__/compaction-budget-schema.test.d.ts +18 -0
  10. package/dist/config/__tests__/compaction-budget-schema.test.d.ts.map +1 -0
  11. package/dist/config/__tests__/compaction-budget-schema.test.js +96 -0
  12. package/dist/config/__tests__/compaction-budget-schema.test.js.map +1 -0
  13. package/dist/config/runtime.d.ts.map +1 -1
  14. package/dist/config/runtime.js +20 -11
  15. package/dist/config/runtime.js.map +1 -1
  16. package/dist/manager/run/persistence.d.ts +2 -1
  17. package/dist/manager/run/persistence.d.ts.map +1 -1
  18. package/dist/manager/run/persistence.js +3 -1
  19. package/dist/manager/run/persistence.js.map +1 -1
  20. package/dist/provider/__tests__/errors.test.d.ts +2 -0
  21. package/dist/provider/__tests__/errors.test.d.ts.map +1 -0
  22. package/dist/provider/__tests__/errors.test.js +61 -0
  23. package/dist/provider/__tests__/errors.test.js.map +1 -0
  24. package/dist/provider/__tests__/registry.test.d.ts +5 -0
  25. package/dist/provider/__tests__/registry.test.d.ts.map +1 -1
  26. package/dist/provider/__tests__/registry.test.js +186 -1
  27. package/dist/provider/__tests__/registry.test.js.map +1 -1
  28. package/dist/provider/errors.d.ts +108 -0
  29. package/dist/provider/errors.d.ts.map +1 -0
  30. package/dist/provider/errors.js +311 -0
  31. package/dist/provider/errors.js.map +1 -0
  32. package/dist/provider/index.d.ts +3 -1
  33. package/dist/provider/index.d.ts.map +1 -1
  34. package/dist/provider/index.js +2 -1
  35. package/dist/provider/index.js.map +1 -1
  36. package/dist/provider/registry.d.ts +81 -1
  37. package/dist/provider/registry.d.ts.map +1 -1
  38. package/dist/provider/registry.js +173 -7
  39. package/dist/provider/registry.js.map +1 -1
  40. package/dist/public-runtime.d.ts +11 -11
  41. package/dist/public-runtime.d.ts.map +1 -1
  42. package/dist/public-runtime.js +11 -11
  43. package/dist/public-runtime.js.map +1 -1
  44. package/dist/registry/tool/execute.d.ts.map +1 -1
  45. package/dist/registry/tool/execute.js +5 -2
  46. package/dist/registry/tool/execute.js.map +1 -1
  47. package/dist/registry/tool/execute.test.js +11 -0
  48. package/dist/registry/tool/execute.test.js.map +1 -1
  49. package/dist/runtime/query/__tests__/stream-recovery.test.js +54 -1
  50. package/dist/runtime/query/__tests__/stream-recovery.test.js.map +1 -1
  51. package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.d.ts +32 -0
  52. package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.d.ts.map +1 -0
  53. package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.js +152 -0
  54. package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.js.map +1 -0
  55. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  56. package/dist/runtime/query/iteration/phases/compaction.js +52 -12
  57. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  58. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  59. package/dist/runtime/query/iteration/stream-turn.js +11 -5
  60. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  61. package/dist/runtime/query/result.d.ts.map +1 -1
  62. package/dist/runtime/query/result.js +11 -1
  63. package/dist/runtime/query/result.js.map +1 -1
  64. package/dist/tools/builtins/__tests__/edit.test.js +54 -0
  65. package/dist/tools/builtins/__tests__/edit.test.js.map +1 -1
  66. package/dist/tools/builtins/edit.d.ts +1 -1
  67. package/dist/tools/builtins/edit.d.ts.map +1 -1
  68. package/dist/tools/builtins/edit.js +11 -13
  69. package/dist/tools/builtins/edit.js.map +1 -1
  70. package/dist/tools/coordinator/__tests__/task-list.test.js +14 -0
  71. package/dist/tools/coordinator/__tests__/task-list.test.js.map +1 -1
  72. package/dist/tools/coordinator/agent.d.ts +6 -8
  73. package/dist/tools/coordinator/agent.d.ts.map +1 -1
  74. package/dist/tools/coordinator/agent.js.map +1 -1
  75. package/dist/tools/coordinator/index.d.ts.map +1 -1
  76. package/dist/tools/coordinator/index.js +11 -32
  77. package/dist/tools/coordinator/index.js.map +1 -1
  78. package/dist/tools/defineTool.d.ts +1 -0
  79. package/dist/tools/defineTool.d.ts.map +1 -1
  80. package/dist/tools/defineTool.js +1 -0
  81. package/dist/tools/defineTool.js.map +1 -1
  82. package/dist/types/provider/config.d.ts +33 -0
  83. package/dist/types/provider/config.d.ts.map +1 -1
  84. package/dist/types/provider/error.d.ts +22 -0
  85. package/dist/types/provider/error.d.ts.map +1 -0
  86. package/dist/types/provider/error.js +2 -0
  87. package/dist/types/provider/error.js.map +1 -0
  88. package/dist/types/provider/index.d.ts +2 -1
  89. package/dist/types/provider/index.d.ts.map +1 -1
  90. package/dist/types/run/entity.d.ts +2 -0
  91. package/dist/types/run/entity.d.ts.map +1 -1
  92. package/dist/types/run/events.d.ts +2 -0
  93. package/dist/types/run/events.d.ts.map +1 -1
  94. package/dist/types/run/events.js.map +1 -1
  95. package/dist/types/tool/index.d.ts +6 -0
  96. package/dist/types/tool/index.d.ts.map +1 -1
  97. package/package.json +1 -1
  98. package/src/compaction/__tests__/verifier-empty-reply.test.ts +96 -0
  99. package/src/compaction/verifier.ts +10 -1
  100. package/src/config/__tests__/compaction-budget-schema.test.ts +114 -0
  101. package/src/config/runtime.ts +20 -11
  102. package/src/manager/run/persistence.ts +3 -1
  103. package/src/provider/__tests__/errors.test.ts +85 -0
  104. package/src/provider/__tests__/registry.test.ts +266 -1
  105. package/src/provider/errors.ts +344 -0
  106. package/src/provider/index.ts +22 -1
  107. package/src/provider/registry.ts +202 -7
  108. package/src/public-runtime.ts +55 -10
  109. package/src/registry/tool/execute.test.ts +14 -0
  110. package/src/registry/tool/execute.ts +5 -2
  111. package/src/runtime/query/__tests__/stream-recovery.test.ts +64 -1
  112. package/src/runtime/query/iteration/phases/compaction-safe-cut.test.ts +186 -0
  113. package/src/runtime/query/iteration/phases/compaction.ts +54 -12
  114. package/src/runtime/query/iteration/stream-turn.ts +11 -6
  115. package/src/runtime/query/result.ts +11 -1
  116. package/src/tools/builtins/__tests__/edit.test.ts +76 -0
  117. package/src/tools/builtins/edit.ts +12 -12
  118. package/src/tools/coordinator/__tests__/task-list.test.ts +16 -0
  119. package/src/tools/coordinator/agent.ts +6 -8
  120. package/src/tools/coordinator/index.ts +11 -34
  121. package/src/tools/defineTool.ts +2 -0
  122. package/src/types/provider/config.ts +36 -0
  123. package/src/types/provider/error.ts +29 -0
  124. package/src/types/provider/index.ts +8 -0
  125. package/src/types/run/entity.ts +2 -0
  126. package/src/types/run/events.ts +7 -1
  127. package/src/types/tool/index.ts +6 -0
@@ -2,6 +2,8 @@ import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs'
2
2
  import { tmpdir } from 'node:os'
3
3
  import { join } from 'node:path'
4
4
  import { describe, expect, it } from 'vitest'
5
+ import { zodToJsonSchema } from 'zod-to-json-schema'
6
+ import { ToolRegistry } from '../../../registry/tool/execute.js'
5
7
  import type { ToolContext } from '../../../types/tool/index.js'
6
8
  import { EditTool } from '../edit.js'
7
9
 
@@ -54,4 +56,78 @@ describe('EditTool', () => {
54
56
  expect(result.success).toBe(true)
55
57
  expect(readFileSync(join(dir, 'doc.md'), 'utf-8')).toBe('alpha\nomega\n')
56
58
  })
59
+
60
+ it('publishes only the insertLine values the executor can apply', () => {
61
+ for (const insertLine of ['## Progress', null, '', '1', -1]) {
62
+ const parsed = EditTool.inputSchema.safeParse({
63
+ path: 'doc.md',
64
+ insertLine,
65
+ newStr: 'inserted',
66
+ })
67
+ expect(parsed.success, JSON.stringify(insertLine)).toBe(false)
68
+ }
69
+
70
+ for (const insertLine of [0, 1, 'end']) {
71
+ const parsed = EditTool.inputSchema.safeParse({
72
+ path: 'doc.md',
73
+ insertLine,
74
+ newStr: 'inserted',
75
+ })
76
+ expect(parsed.success, JSON.stringify(insertLine)).toBe(true)
77
+ }
78
+
79
+ const json = zodToJsonSchema(EditTool.inputSchema, {
80
+ target: 'jsonSchema7',
81
+ $refStrategy: 'none',
82
+ }) as {
83
+ properties?: {
84
+ insertLine?: {
85
+ anyOf?: Record<string, unknown>[]
86
+ }
87
+ }
88
+ }
89
+ expect(json.properties?.insertLine?.anyOf).toEqual([
90
+ { type: 'integer', minimum: 0 },
91
+ { type: 'string', const: 'end' },
92
+ ])
93
+ })
94
+
95
+ it('refuses invalid insertLine values even when a caller bypasses the schema', async () => {
96
+ for (const insertLine of ['## Progress', null, '', '1']) {
97
+ const dir = mkdtempSync(join(tmpdir(), 'namzu-edit-'))
98
+ writeFileSync(join(dir, 'doc.md'), 'alpha\nbeta\n')
99
+
100
+ const result = await EditTool.execute(
101
+ {
102
+ path: 'doc.md',
103
+ insertLine,
104
+ newStr: 'inserted',
105
+ replace_all: false,
106
+ } as never,
107
+ makeContext(dir),
108
+ )
109
+
110
+ expect(result.success, JSON.stringify(insertLine)).toBe(false)
111
+ expect(result.error).toBe('insertLine must be a non-negative line number or "end".')
112
+ expect(readFileSync(join(dir, 'doc.md'), 'utf-8')).toBe('alpha\nbeta\n')
113
+ }
114
+ })
115
+
116
+ it('returns complete recovery shapes when path is missing and insertLine is invalid', async () => {
117
+ const registry = new ToolRegistry()
118
+ registry.register(EditTool)
119
+
120
+ const result = await registry.execute(
121
+ 'edit',
122
+ { insertLine: '## Progress' },
123
+ makeContext('/tmp'),
124
+ )
125
+
126
+ expect(result.success).toBe(false)
127
+ expect(result.error).toContain('Validation failed for "edit":')
128
+ expect(result.error).toContain('insertLine: Invalid input')
129
+ expect(result.error).toContain('Required: path: string — Path to the file to edit.')
130
+ expect(result.error).toContain('{"path":"file.md","insertLine":"end","new_string":"text"}')
131
+ expect(result.error).toContain('{"path":"file.md","old_string":"old","new_string":"new"}')
132
+ })
57
133
  })
@@ -29,10 +29,10 @@ const inputSchema = z
29
29
  'Alias for new_string. Also used as inserted content when insertLine is provided. Self-budget this payload under 12000 characters before calling.',
30
30
  ),
31
31
  insertLine: z
32
- .union([z.coerce.number().int().min(0), z.string().min(1)])
32
+ .union([z.number().int().min(0), z.literal('end')])
33
33
  .optional()
34
34
  .describe(
35
- 'Optional line insertion target. Inserts the replacement after this 1-indexed line; 0 inserts before the first line; "end" appends to the file.',
35
+ 'Optional line insertion target. Pass a JSON integer to insert after that 1-indexed line, 0 to insert before the first line, or the exact string "end" to append. Headings, anchors, numeric strings, null, and empty strings are invalid.',
36
36
  ),
37
37
  replace_all: z
38
38
  .boolean()
@@ -71,6 +71,8 @@ export const EditTool = defineTool({
71
71
  description:
72
72
  'Makes targeted edits to a file using exact string find-and-replace or line insertion. THIS IS THE PREFERRED WAY TO MODIFY AN EXISTING FILE — never reach for `write` to change a file that already exists, because `write` overwrites the whole body and discards earlier work on partial failure. `edit` keeps the rest of the file byte-for-byte intact and is recoverable: if a single edit fails (old_string/oldStr ambiguous, broader restructuring needed), follow up with another `edit` instead of re-emitting the entire file via `write`. The old_string/oldStr must be unique in the file unless replace_all is true. For insertions, pass insertLine plus new_string/newStr; use insertLine: "end" to extend a file at the end. Self-budget new_string/newStr under 12000 characters before emitting the tool call; use repeated bounded edits for long sections. Preserves file formatting and indentation.',
73
73
  inputSchema,
74
+ validationErrorHint:
75
+ 'Accepted shapes: {"path":"file.md","insertLine":"end","new_string":"text"} or {"path":"file.md","old_string":"old","new_string":"new"}. Always include path; insertLine accepts only a non-negative JSON integer or the exact string "end".',
74
76
  category: 'filesystem',
75
77
  permissions: ['file_write'],
76
78
  readOnly: false,
@@ -166,18 +168,16 @@ function normalizeEditInput(
166
168
  }
167
169
 
168
170
  function normalizeInsertLine(
169
- value: string | number,
171
+ value: unknown,
170
172
  ): { success: true; value: number | 'end' } | { success: false; error: string } {
171
- if (typeof value === 'string') {
172
- if (value.trim().toLowerCase() === 'end') return { success: true, value: 'end' }
173
- const parsed = Number(value)
174
- if (Number.isInteger(parsed) && parsed >= 0) return { success: true, value: parsed }
175
- return {
176
- success: false,
177
- error: 'insertLine must be a non-negative line number or "end".',
178
- }
173
+ if (value === 'end') return { success: true, value: 'end' }
174
+ if (typeof value === 'number' && Number.isInteger(value) && value >= 0) {
175
+ return { success: true, value }
176
+ }
177
+ return {
178
+ success: false,
179
+ error: 'insertLine must be a non-negative line number or "end".',
179
180
  }
180
- return { success: true, value }
181
181
  }
182
182
 
183
183
  function applyEdit(
@@ -179,4 +179,20 @@ describe('coordinator agent_task_list tool', () => {
179
179
  expect(names).toContain('agent_task_list')
180
180
  expect(names).not.toContain('task_list')
181
181
  })
182
+
183
+ it('does not advertise per-task cancellation on the blocking coordinator surface', () => {
184
+ const coordinatorTools = buildCoordinatorTools({
185
+ gateway: gatewayWith([]),
186
+ workingDirectory: '/tmp/test',
187
+ allowedAgentIds: ['solution-architecture'],
188
+ })
189
+ const names = coordinatorTools.map((tool) => tool.name)
190
+
191
+ // create_task returns only after the worker is terminal, so the
192
+ // supervisor cannot know a live task id in a later model turn.
193
+ // Keeping cancel_task here only manufactured success for missing
194
+ // or terminal ids because every gateway cancellation is a void
195
+ // no-op in those states.
196
+ expect(names).not.toContain('cancel_task')
197
+ })
182
198
  })
@@ -19,14 +19,12 @@ import type { TaskLaunchedCallback } from './index.js'
19
19
  * subagent tool calls are isolated — only the summary surfaces to
20
20
  * the parent.
21
21
  *
22
- * This is **NOT** the same shape as the legacy `create_task` /
23
- * `continue_task` / `cancel_task` trio that this package ships
24
- * alongside it: those are non-blocking and use a `<task-notification>`
25
- * callback model. The async pattern is useful for hosts that want a
26
- * work-queue surface, but it is not what Claude Code trained against.
27
- * For free agentic alignment, prefer the canonical `Agent` tool; keep
28
- * the legacy coordinator tools only when you genuinely need
29
- * fire-and-forget multi-task fan-out.
22
+ * This is **NOT** the same shape as the coordinator `create_task`
23
+ * tool that this package ships alongside it. That tool exposes
24
+ * Namzu's task IDs and optional planning integration, while still
25
+ * returning results synchronously. For free agentic alignment,
26
+ * prefer the canonical `Agent` tool; use the coordinator tool when
27
+ * the host needs task tracking.
30
28
  */
31
29
  export interface AgentToolOptions {
32
30
  gateway: TaskGateway
@@ -227,28 +227,6 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
227
227
  },
228
228
  })
229
229
 
230
- const cancelTask = defineTool({
231
- name: 'cancel_task',
232
- description:
233
- 'Cancel a running agent task. Only use this with a task_id from a previous create_task.',
234
- inputSchema: z.object({
235
- task_id: z.string().describe('Agent task ID from a previous create_task'),
236
- }),
237
- category: 'custom',
238
- permissions: [],
239
- readOnly: false,
240
- destructive: false,
241
- concurrencySafe: true,
242
- async execute({ task_id }) {
243
- gateway.cancelTask(task_id as TaskId)
244
- return {
245
- success: true,
246
- output: `Task ${task_id} cancelled`,
247
- data: { task_id },
248
- }
249
- },
250
- })
251
-
252
230
  const agentTaskList = defineTool({
253
231
  name: 'agent_task_list',
254
232
  description:
@@ -305,19 +283,18 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
305
283
  },
306
284
  })
307
285
 
308
- // `continue_task` was a follow-up channel for a still-alive worker
309
- // task. With `create_task` now blocking + tool_result returning
310
- // the worker's final output, every worker reaches a terminal
311
- // state by the time the supervisor wants to follow up and the
312
- // agent manager rejects `continue` on terminal tasks. The
313
- // industrial pattern is to issue a fresh `create_task` that
314
- // references the prior worker's output path, so we drop
315
- // `continue_task` from the registered surface entirely. The
316
- // definition stays in this file for now in case a future
317
- // non-default gateway (one that keeps the worker process alive
318
- // for follow-ups) wants to re-register it.
286
+ // `continue_task` and model-facing per-task cancellation both
287
+ // belonged to the old non-blocking worker protocol. With
288
+ // `create_task` now blocking + tool_result returning the worker's
289
+ // final output, every worker is terminal by the time a later model
290
+ // turn learns its task id. The old cancel tool additionally called
291
+ // a void/no-op gateway method and manufactured "cancelled" even for
292
+ // a missing id. Host-owned run interruption still uses the gateway
293
+ // cancellation contract directly; it does not need a model tool.
294
+ // Keep only the follow-up definition for a future non-default
295
+ // gateway that deliberately preserves live workers between turns.
319
296
  void continueTask
320
- const tools: ToolDefinition[] = [createTask, cancelTask, agentTaskList]
297
+ const tools: ToolDefinition[] = [createTask, agentTaskList]
321
298
 
322
299
  if (getPlanManager) {
323
300
  const approvePlan = defineTool({
@@ -11,6 +11,7 @@ export interface DefineToolOptions<S extends z.ZodType> {
11
11
  name: string
12
12
  description: string
13
13
  inputSchema: S
14
+ validationErrorHint?: string
14
15
  category: ToolDefinition['category']
15
16
  permissions: ToolPermission[]
16
17
  readOnly: boolean
@@ -29,6 +30,7 @@ export function defineTool<S extends z.ZodType>(
29
30
  name: options.name,
30
31
  description: options.description,
31
32
  inputSchema: options.inputSchema,
33
+ validationErrorHint: options.validationErrorHint,
32
34
  tier: options.tier,
33
35
  category: options.category,
34
36
  permissions: options.permissions,
@@ -70,4 +70,40 @@ export interface RegisterOptions {
70
70
  replace?: boolean
71
71
  }
72
72
 
73
+ /**
74
+ * What a lazy loader must resolve to: a factory that constructs the
75
+ * provider, plus (optionally) the provider package's authoritative
76
+ * capability declaration resolved at load time.
77
+ *
78
+ * Designed so a host can map a dynamic import in one line:
79
+ *
80
+ * ```ts
81
+ * ProviderRegistry.registerLazy('anthropic', async () => {
82
+ * const m = await import('@namzu/anthropic')
83
+ * return { create: (c) => new m.AnthropicProvider(c), capabilities: m.ANTHROPIC_CAPABILITIES }
84
+ * })
85
+ * ```
86
+ */
87
+ export interface LazyProviderModule<C = unknown> {
88
+ create: (config: C) => LLMProvider
89
+ /**
90
+ * Authoritative type-level capabilities shipped by the loaded module.
91
+ * When present, replaces any registration-time hint in the registry.
92
+ */
93
+ capabilities?: ProviderCapabilities
94
+ }
95
+
96
+ export type LazyProviderLoader<C = unknown> = () => Promise<LazyProviderModule<C>>
97
+
98
+ export interface RegisterLazyOptions extends RegisterOptions {
99
+ /**
100
+ * Pre-load capability HINT so `ProviderRegistry.getCapabilities(type)`
101
+ * can answer without triggering the loader. Precedence (weakest first):
102
+ * this hint → the loaded module's `capabilities` → the constructed
103
+ * instance's own `LLMProvider.capabilities` (what the query runtime
104
+ * resolves via `resolveProviderCapabilities` and actually respects).
105
+ */
106
+ capabilities?: ProviderCapabilities
107
+ }
108
+
73
109
  export type LLMProviderConstructor<C = unknown> = new (config: C) => LLMProvider
@@ -0,0 +1,29 @@
1
+ /** What went wrong, at the coarsest granularity a caller can act on. */
2
+ export type ProviderErrorKind =
3
+ | 'throttle'
4
+ | 'network'
5
+ | 'auth'
6
+ | 'context_overflow'
7
+ | 'bad_request'
8
+ | 'server'
9
+
10
+ /**
11
+ * Serializable provider-failure metadata carried by failed runs and events.
12
+ *
13
+ * No response body, vendor message, URL, or `cause` belongs here.
14
+ */
15
+ export interface ProviderErrorInfo {
16
+ readonly kind: ProviderErrorKind
17
+ readonly providerId: string
18
+ readonly status?: number
19
+ readonly retryAfterMs?: number
20
+ }
21
+
22
+ export interface ProviderRequestErrorInit extends ProviderErrorInfo {
23
+ /**
24
+ * Optional extra clause for the message. MUST be text this codebase
25
+ * authored — never a fragment of a response body, a header, or a vendor
26
+ * error.
27
+ */
28
+ readonly detail?: string
29
+ }
@@ -8,6 +8,11 @@ export type {
8
8
  export type { StreamChunk } from './stream.js'
9
9
  export type { ModelInfo } from './model.js'
10
10
  export type { LLMProvider } from './interface.js'
11
+ export type {
12
+ ProviderErrorInfo,
13
+ ProviderErrorKind,
14
+ ProviderRequestErrorInit,
15
+ } from './error.js'
11
16
  export type {
12
17
  ProviderConfigRegistry,
13
18
  ProviderType,
@@ -16,5 +21,8 @@ export type {
16
21
  ProviderCapabilities,
17
22
  ProviderFactoryResult,
18
23
  RegisterOptions,
24
+ LazyProviderModule,
25
+ LazyProviderLoader,
26
+ RegisterLazyOptions,
19
27
  LLMProviderConstructor,
20
28
  } from './config.js'
@@ -1,6 +1,7 @@
1
1
  import type { AgentStatus, CostInfo, TokenUsage } from '../common/index.js'
2
2
  import type { RunId } from '../ids/index.js'
3
3
  import type { Message } from '../message/index.js'
4
+ import type { ProviderErrorInfo } from '../provider/index.js'
4
5
  import type { AgentRunConfig } from './config.js'
5
6
  import type { ReplayAttribution } from './replay.js'
6
7
  import type { StopReason } from './stop-reason.js'
@@ -37,6 +38,7 @@ export interface Run {
37
38
  endedAt?: number
38
39
  stopReason?: StopReason
39
40
  lastError?: string
41
+ lastProviderError?: ProviderErrorInfo
40
42
  result?: string
41
43
 
42
44
  parentRunId?: RunId
@@ -14,6 +14,7 @@ import type {
14
14
  } from '../ids/index.js'
15
15
  import type { PlanStep } from '../plan/index.js'
16
16
  import type { PluginHookEvent, PluginHookResult } from '../plugin/index.js'
17
+ import type { ProviderErrorInfo } from '../provider/index.js'
17
18
  import type { TaskStatus } from '../task/index.js'
18
19
  import type { Lineage } from './lineage.js'
19
20
  import type { MessageStopReason } from './stop-reason.js'
@@ -98,7 +99,12 @@ type CoreRunEvent =
98
99
  fromCheckpointId: CheckpointId
99
100
  }
100
101
  | { type: 'run_completed'; runId: RunId; result: string }
101
- | { type: 'run_failed'; runId: RunId; error: string }
102
+ | {
103
+ type: 'run_failed'
104
+ runId: RunId
105
+ error: string
106
+ providerError?: ProviderErrorInfo
107
+ }
102
108
  // Additive 2026-07 (provider capability negotiation): emitted once per
103
109
  // run when the request asks for something the provider DRIVER declared
104
110
  // it cannot do — tools registered against a no-tools driver (tool
@@ -64,6 +64,12 @@ export interface ToolDefinition<TInput = unknown> {
64
64
  name: string
65
65
  description: string
66
66
  inputSchema: z.ZodType<TInput, z.ZodTypeDef, unknown>
67
+ /**
68
+ * Concise, model-readable recovery guidance appended when inputSchema
69
+ * rejects a call. Use for conditional schemas whose required shapes
70
+ * cannot be reconstructed from JSON Schema's top-level `required` list.
71
+ */
72
+ validationErrorHint?: string
67
73
  execute(input: TInput, context: ToolContext): Promise<ToolResult>
68
74
  tier?: string
69
75
  permissions?: ToolPermission[]