@namzu/sdk 5.1.0 → 6.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 +465 -0
- package/dist/agents/__tests__/run-agent-forwarding.test.d.ts +2 -0
- package/dist/agents/__tests__/run-agent-forwarding.test.d.ts.map +1 -0
- package/dist/agents/__tests__/run-agent-forwarding.test.js +76 -0
- package/dist/agents/__tests__/run-agent-forwarding.test.js.map +1 -0
- package/dist/agents/__tests__/run-completed-stop-reason.test.d.ts +2 -0
- package/dist/agents/__tests__/run-completed-stop-reason.test.d.ts.map +1 -0
- package/dist/agents/__tests__/run-completed-stop-reason.test.js +50 -0
- package/dist/agents/__tests__/run-completed-stop-reason.test.js.map +1 -0
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +4 -0
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/runAgent.d.ts +34 -0
- package/dist/agents/runAgent.d.ts.map +1 -1
- package/dist/agents/runAgent.js +5 -3
- package/dist/agents/runAgent.js.map +1 -1
- package/dist/config/runtime.d.ts +30 -30
- package/dist/constants/tools/__tests__/dangerous-patterns.test.d.ts +2 -0
- package/dist/constants/tools/__tests__/dangerous-patterns.test.d.ts.map +1 -0
- package/dist/constants/tools/__tests__/dangerous-patterns.test.js +54 -0
- package/dist/constants/tools/__tests__/dangerous-patterns.test.js.map +1 -0
- package/dist/constants/tools/index.d.ts.map +1 -1
- package/dist/constants/tools/index.js +28 -1
- package/dist/constants/tools/index.js.map +1 -1
- package/dist/contracts/a2a.d.ts +44 -44
- package/dist/directory/__tests__/derive.test.d.ts +2 -0
- package/dist/directory/__tests__/derive.test.d.ts.map +1 -0
- package/dist/directory/__tests__/derive.test.js +111 -0
- package/dist/directory/__tests__/derive.test.js.map +1 -0
- package/dist/directory/__tests__/load-diagnostics.test.d.ts +2 -0
- package/dist/directory/__tests__/load-diagnostics.test.d.ts.map +1 -0
- package/dist/directory/__tests__/load-diagnostics.test.js +106 -0
- package/dist/directory/__tests__/load-diagnostics.test.js.map +1 -0
- package/dist/directory/__tests__/load-directory.test.d.ts +2 -0
- package/dist/directory/__tests__/load-directory.test.d.ts.map +1 -0
- package/dist/directory/__tests__/load-directory.test.js +196 -0
- package/dist/directory/__tests__/load-directory.test.js.map +1 -0
- package/dist/directory/__tests__/subagents.test.d.ts +2 -0
- package/dist/directory/__tests__/subagents.test.d.ts.map +1 -0
- package/dist/directory/__tests__/subagents.test.js +117 -0
- package/dist/directory/__tests__/subagents.test.js.map +1 -0
- package/dist/directory/__tests__/supervisor-budget.test.d.ts +2 -0
- package/dist/directory/__tests__/supervisor-budget.test.d.ts.map +1 -0
- package/dist/directory/__tests__/supervisor-budget.test.js +97 -0
- package/dist/directory/__tests__/supervisor-budget.test.js.map +1 -0
- package/dist/directory/derive-supervisor.d.ts +58 -0
- package/dist/directory/derive-supervisor.d.ts.map +1 -0
- package/dist/directory/derive-supervisor.js +88 -0
- package/dist/directory/derive-supervisor.js.map +1 -0
- package/dist/directory/derive.d.ts +12 -0
- package/dist/directory/derive.d.ts.map +1 -0
- package/dist/directory/derive.js +58 -0
- package/dist/directory/derive.js.map +1 -0
- package/dist/directory/index.d.ts +7 -0
- package/dist/directory/index.d.ts.map +1 -0
- package/dist/directory/index.js +5 -0
- package/dist/directory/index.js.map +1 -0
- package/dist/directory/load.d.ts +14 -0
- package/dist/directory/load.d.ts.map +1 -0
- package/dist/directory/load.js +488 -0
- package/dist/directory/load.js.map +1 -0
- package/dist/directory/scan.d.ts +36 -0
- package/dist/directory/scan.d.ts.map +1 -0
- package/dist/directory/scan.js +136 -0
- package/dist/directory/scan.js.map +1 -0
- package/dist/directory/types.d.ts +215 -0
- package/dist/directory/types.d.ts.map +1 -0
- package/dist/directory/types.js +8 -0
- package/dist/directory/types.js.map +1 -0
- package/dist/plugin/__tests__/path-containment.test.d.ts +2 -0
- package/dist/plugin/__tests__/path-containment.test.d.ts.map +1 -0
- package/dist/plugin/__tests__/path-containment.test.js +91 -0
- package/dist/plugin/__tests__/path-containment.test.js.map +1 -0
- package/dist/plugin/lifecycle.d.ts.map +1 -1
- package/dist/plugin/lifecycle.js +3 -3
- package/dist/plugin/lifecycle.js.map +1 -1
- package/dist/plugin/loader.d.ts.map +1 -1
- package/dist/plugin/loader.js +12 -2
- package/dist/plugin/loader.js.map +1 -1
- package/dist/provider/__tests__/model-version.test.d.ts +2 -0
- package/dist/provider/__tests__/model-version.test.d.ts.map +1 -0
- package/dist/provider/__tests__/model-version.test.js +80 -0
- package/dist/provider/__tests__/model-version.test.js.map +1 -0
- package/dist/provider/__tests__/strict-schema.test.d.ts +2 -0
- package/dist/provider/__tests__/strict-schema.test.d.ts.map +1 -0
- package/dist/provider/__tests__/strict-schema.test.js +157 -0
- package/dist/provider/__tests__/strict-schema.test.js.map +1 -0
- package/dist/provider/__tests__/vendor-detail.test.d.ts +2 -0
- package/dist/provider/__tests__/vendor-detail.test.d.ts.map +1 -0
- package/dist/provider/__tests__/vendor-detail.test.js +89 -0
- package/dist/provider/__tests__/vendor-detail.test.js.map +1 -0
- package/dist/provider/errors.d.ts +38 -5
- package/dist/provider/errors.d.ts.map +1 -1
- package/dist/provider/errors.js +107 -5
- package/dist/provider/errors.js.map +1 -1
- package/dist/provider/model-version.d.ts +60 -0
- package/dist/provider/model-version.d.ts.map +1 -0
- package/dist/provider/model-version.js +64 -0
- package/dist/provider/model-version.js.map +1 -0
- package/dist/provider/strict-schema.d.ts +49 -0
- package/dist/provider/strict-schema.d.ts.map +1 -0
- package/dist/provider/strict-schema.js +168 -0
- package/dist/provider/strict-schema.js.map +1 -0
- package/dist/public-runtime.d.ts +9 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +27 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +2 -0
- package/dist/public-types.d.ts.map +1 -1
- package/dist/registry/tool/__tests__/dialect.test.d.ts +2 -0
- package/dist/registry/tool/__tests__/dialect.test.d.ts.map +1 -0
- package/dist/registry/tool/__tests__/dialect.test.js +143 -0
- package/dist/registry/tool/__tests__/dialect.test.js.map +1 -0
- package/dist/registry/tool/__tests__/strict-registration.test.d.ts +2 -0
- package/dist/registry/tool/__tests__/strict-registration.test.d.ts.map +1 -0
- package/dist/registry/tool/__tests__/strict-registration.test.js +82 -0
- package/dist/registry/tool/__tests__/strict-registration.test.js.map +1 -0
- package/dist/registry/tool/dialect.d.ts +50 -0
- package/dist/registry/tool/dialect.d.ts.map +1 -0
- package/dist/registry/tool/dialect.js +131 -0
- package/dist/registry/tool/dialect.js.map +1 -0
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +16 -0
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/registry/toolset/catalog.d.ts.map +1 -1
- package/dist/registry/toolset/catalog.js +10 -5
- package/dist/registry/toolset/catalog.js.map +1 -1
- package/dist/runtime/query/__tests__/stream-recovery.test.js +6 -0
- package/dist/runtime/query/__tests__/stream-recovery.test.js.map +1 -1
- package/dist/runtime/query/result.d.ts.map +1 -1
- package/dist/runtime/query/result.js +11 -0
- package/dist/runtime/query/result.js.map +1 -1
- package/dist/tools/builtins/__tests__/edit-insert-reachable.test.js +7 -1
- package/dist/tools/builtins/__tests__/edit-insert-reachable.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/edit.test.js +1 -1
- package/dist/tools/builtins/__tests__/edit.test.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +20 -2
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/types/plugin/index.d.ts +4 -4
- package/dist/types/provider/error.d.ts +20 -4
- package/dist/types/provider/error.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +17 -2
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/verification/index.d.ts +6 -6
- package/package.json +1 -1
- package/src/agents/__tests__/run-agent-forwarding.test.ts +85 -0
- package/src/agents/__tests__/run-completed-stop-reason.test.ts +59 -0
- package/src/agents/index.ts +8 -0
- package/src/agents/runAgent.ts +30 -4
- package/src/constants/tools/__tests__/dangerous-patterns.test.ts +62 -0
- package/src/constants/tools/index.ts +28 -1
- package/src/directory/__tests__/derive.test.ts +135 -0
- package/src/directory/__tests__/load-diagnostics.test.ts +126 -0
- package/src/directory/__tests__/load-directory.test.ts +243 -0
- package/src/directory/__tests__/subagents.test.ts +155 -0
- package/src/directory/__tests__/supervisor-budget.test.ts +117 -0
- package/src/directory/derive-supervisor.ts +149 -0
- package/src/directory/derive.ts +70 -0
- package/src/directory/index.ts +27 -0
- package/src/directory/load.ts +556 -0
- package/src/directory/scan.ts +163 -0
- package/src/directory/types.ts +254 -0
- package/src/plugin/__tests__/path-containment.test.ts +107 -0
- package/src/plugin/lifecycle.ts +3 -3
- package/src/plugin/loader.ts +12 -2
- package/src/provider/__tests__/model-version.test.ts +93 -0
- package/src/provider/__tests__/strict-schema.test.ts +189 -0
- package/src/provider/__tests__/vendor-detail.test.ts +107 -0
- package/src/provider/errors.ts +106 -5
- package/src/provider/model-version.ts +98 -0
- package/src/provider/strict-schema.ts +182 -0
- package/src/public-runtime.ts +38 -0
- package/src/public-types.ts +8 -0
- package/src/registry/tool/__tests__/dialect.test.ts +197 -0
- package/src/registry/tool/__tests__/strict-registration.test.ts +115 -0
- package/src/registry/tool/dialect.ts +136 -0
- package/src/registry/tool/execute.ts +16 -0
- package/src/registry/toolset/catalog.ts +10 -5
- package/src/runtime/query/__tests__/stream-recovery.test.ts +6 -0
- package/src/runtime/query/result.ts +11 -0
- package/src/tools/builtins/__tests__/edit-insert-reachable.test.ts +8 -2
- package/src/tools/builtins/__tests__/edit.test.ts +1 -1
- package/src/tools/builtins/edit.ts +20 -2
- package/src/types/provider/error.ts +20 -4
- package/src/types/run/events.ts +15 -2
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dialect a wire speaks, and how to say the same schema in it.
|
|
3
|
+
*
|
|
4
|
+
* A tool has one Zod schema. What changes between providers is not the tool —
|
|
5
|
+
* it is the JSON Schema dialect the wire parses, which is a property of the
|
|
6
|
+
* wire. So the shape is rendered once, canonically, and each driver converts
|
|
7
|
+
* at the boundary where it knows which wire it is about to talk to.
|
|
8
|
+
*
|
|
9
|
+
* This exists because that layering was missing and it cost a production
|
|
10
|
+
* outage. `renderToolSchema` emits draft-07 (zod-to-json-schema's
|
|
11
|
+
* `jsonSchema7` target), every driver forwarded it verbatim, and one of the
|
|
12
|
+
* wires namzu speaks requires draft 2020-12. Measured against that live
|
|
13
|
+
* endpoint:
|
|
14
|
+
*
|
|
15
|
+
* | tool schema | result |
|
|
16
|
+
* |--------------------------------|-----------------------------------------|
|
|
17
|
+
* | `items: [a, b]` (draft-07) | 400 — "must match JSON Schema draft 2020-12" |
|
|
18
|
+
* | `prefixItems: [a, b]` (2020-12)| accepted |
|
|
19
|
+
* | `items: { a }` | accepted |
|
|
20
|
+
*
|
|
21
|
+
* The failure is NOT about strict tool use. It fires with strict validation
|
|
22
|
+
* unset, and with it on the dialect error arrives *before* the strict-subset
|
|
23
|
+
* error — so a guard scoped to strict misses it entirely. One was, and did.
|
|
24
|
+
*
|
|
25
|
+
* Which wires want which dialect is the drivers' knowledge, not this file's:
|
|
26
|
+
* the vocabulary lives beside the wire that speaks it, and only the mechanism
|
|
27
|
+
* lives here.
|
|
28
|
+
*
|
|
29
|
+
* Only the conversions namzu can actually emit are implemented. The renderer
|
|
30
|
+
* runs with `$refStrategy: 'none'`, so there are no `$ref`/`definitions` to
|
|
31
|
+
* rewrite; a construct that cannot appear is not worth code that cannot be
|
|
32
|
+
* tested.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** Which spelling of JSON Schema a wire accepts. */
|
|
36
|
+
export type JsonSchemaDialect = 'draft-07' | '2020-12'
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Rendered schemas are memoized and deeply frozen, so their identity is
|
|
40
|
+
* stable for the life of the tool — which makes them a sound `WeakMap` key.
|
|
41
|
+
*
|
|
42
|
+
* Converting per request would re-walk every tool's tree on every iteration,
|
|
43
|
+
* which is the waste `renderToolSchema`'s own cache exists to remove, and it
|
|
44
|
+
* would hand a fresh object to the wire each time. The tools block renders at
|
|
45
|
+
* position 0 of the prompt-cache prefix, so a differently-ordered but equal
|
|
46
|
+
* object still invalidates the cache for the whole run. Caching the conversion
|
|
47
|
+
* keeps the bytes identical across iterations.
|
|
48
|
+
*/
|
|
49
|
+
const CONVERTED = new Map<JsonSchemaDialect, WeakMap<object, Record<string, unknown>>>()
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Say this schema in the dialect the wire parses.
|
|
53
|
+
*
|
|
54
|
+
* Returns the input unchanged — same reference — when nothing needs saying
|
|
55
|
+
* differently, so the common case costs one map lookup and no allocation.
|
|
56
|
+
*/
|
|
57
|
+
export function toSchemaDialect(
|
|
58
|
+
schema: Record<string, unknown>,
|
|
59
|
+
dialect: JsonSchemaDialect,
|
|
60
|
+
): Record<string, unknown> {
|
|
61
|
+
if (dialect === 'draft-07') return schema
|
|
62
|
+
|
|
63
|
+
let cache = CONVERTED.get(dialect)
|
|
64
|
+
if (!cache) {
|
|
65
|
+
cache = new WeakMap()
|
|
66
|
+
CONVERTED.set(dialect, cache)
|
|
67
|
+
}
|
|
68
|
+
const hit = cache.get(schema)
|
|
69
|
+
if (hit) return hit
|
|
70
|
+
|
|
71
|
+
const converted = to2020(schema) as Record<string, unknown>
|
|
72
|
+
const frozen = deepFreeze(converted)
|
|
73
|
+
cache.set(schema, frozen)
|
|
74
|
+
return frozen
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whether a schema still carries a construct the 2020-12 wire refuses.
|
|
79
|
+
*
|
|
80
|
+
* Exported so a test can sweep every shipped tool without reaching into the
|
|
81
|
+
* conversion, and so a driver can assert rather than hope.
|
|
82
|
+
*/
|
|
83
|
+
export function findDraft07Only(schema: unknown, path = ''): string[] {
|
|
84
|
+
if (Array.isArray(schema)) {
|
|
85
|
+
return schema.flatMap((item, i) => findDraft07Only(item, `${path}[${i}]`))
|
|
86
|
+
}
|
|
87
|
+
if (typeof schema !== 'object' || schema === null) return []
|
|
88
|
+
|
|
89
|
+
const node = schema as Record<string, unknown>
|
|
90
|
+
const found: string[] = []
|
|
91
|
+
// A tuple. In draft-07 the positional schemas live in `items`; 2020-12
|
|
92
|
+
// moved them to `prefixItems` and kept `items` for the rest, so the array
|
|
93
|
+
// form is not merely old — it means something else now, and the wire
|
|
94
|
+
// rejects the whole request rather than one field.
|
|
95
|
+
if (Array.isArray(node.items)) found.push(`${path ? `${path}.` : ''}items`)
|
|
96
|
+
// `additionalItems` only ever qualified an array-form `items`; 2020-12
|
|
97
|
+
// spells that `items`.
|
|
98
|
+
if ('additionalItems' in node) found.push(`${path ? `${path}.` : ''}additionalItems`)
|
|
99
|
+
|
|
100
|
+
for (const [key, value] of Object.entries(node)) {
|
|
101
|
+
if (key === 'items' && Array.isArray(value)) continue
|
|
102
|
+
found.push(...findDraft07Only(value, path ? `${path}.${key}` : key))
|
|
103
|
+
}
|
|
104
|
+
return found
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function to2020(value: unknown): unknown {
|
|
108
|
+
if (Array.isArray(value)) return value.map(to2020)
|
|
109
|
+
if (typeof value !== 'object' || value === null) return value
|
|
110
|
+
|
|
111
|
+
const node = value as Record<string, unknown>
|
|
112
|
+
const out: Record<string, unknown> = {}
|
|
113
|
+
for (const [key, child] of Object.entries(node)) {
|
|
114
|
+
if (key === 'items' && Array.isArray(child)) {
|
|
115
|
+
out.prefixItems = child.map(to2020)
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
if (key === 'additionalItems') {
|
|
119
|
+
// Only meaningful alongside an array-form `items`, where 2020-12
|
|
120
|
+
// calls the same thing `items`. `false` is the default in both
|
|
121
|
+
// dialects once `prefixItems` is set, so it carries nothing.
|
|
122
|
+
if (Array.isArray(node.items) && child !== false) out.items = to2020(child)
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
out[key] = to2020(child)
|
|
126
|
+
}
|
|
127
|
+
return out
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function deepFreeze<T>(value: T): T {
|
|
131
|
+
if (typeof value !== 'object' || value === null || Object.isFrozen(value)) return value
|
|
132
|
+
for (const key of Object.keys(value as Record<string, unknown>)) {
|
|
133
|
+
deepFreeze((value as Record<string, unknown>)[key])
|
|
134
|
+
}
|
|
135
|
+
return Object.freeze(value)
|
|
136
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SpanStatusCode, context as otelContext, trace } from '@opentelemetry/api'
|
|
2
|
+
import { assertStrictSchema } from '../../provider/strict-schema.js'
|
|
2
3
|
import { GENAI, NAMZU, toolSpanName } from '../../telemetry/attributes.js'
|
|
3
4
|
import { recordToolCall } from '../../telemetry/metrics.js'
|
|
4
5
|
import { getTracer } from '../../telemetry/runtime-accessors.js'
|
|
@@ -177,6 +178,21 @@ export class ToolRegistry extends ManagedRegistry<ToolDefinition> {
|
|
|
177
178
|
`Tool "${id}" enables enforceModelInput but does not define modelInputSchema. Constrained input generation requires an explicit provider-safe model schema.`,
|
|
178
179
|
)
|
|
179
180
|
}
|
|
181
|
+
// …and the schema has to be one a constrained decoder can actually be
|
|
182
|
+
// given. The check above asks whether a model schema EXISTS; this asks
|
|
183
|
+
// whether it can carry the guarantee the tool just requested.
|
|
184
|
+
//
|
|
185
|
+
// Both belong here for the reason the comment above already states.
|
|
186
|
+
// Strict validation runs against a SUBSET of JSON Schema, and a keyword
|
|
187
|
+
// outside it is not degraded — the request is rejected whole, so one
|
|
188
|
+
// unexpressible field takes down every other tool in the call. The
|
|
189
|
+
// first version of this check lived in a provider driver, which meant
|
|
190
|
+
// it fired per request, in one driver, long after the author had moved
|
|
191
|
+
// on. A tool that asks for a guarantee its own schema cannot carry is
|
|
192
|
+
// wrong at the moment it is declared, whichever model it later meets.
|
|
193
|
+
if (tool.enforceModelInput) {
|
|
194
|
+
assertStrictSchema(id, tool.modelInputSchema)
|
|
195
|
+
}
|
|
180
196
|
if (tool.tier && this.tierConfig) {
|
|
181
197
|
const validIds = this.tierConfig.tiers.map((t) => t.id)
|
|
182
198
|
if (!validIds.includes(tool.tier)) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { zodToJsonSchema } from 'zod-to-json-schema'
|
|
2
1
|
import type { ToolRegistryContract } from '../../types/tool/index.js'
|
|
3
2
|
import type { LLMToolSchema, ToolAvailability, ToolDefinition } from '../../types/tool/index.js'
|
|
4
3
|
import type {
|
|
@@ -10,6 +9,7 @@ import type {
|
|
|
10
9
|
ToolsetDefinition,
|
|
11
10
|
ToolsetPolicy,
|
|
12
11
|
} from '../../types/toolset/index.js'
|
|
12
|
+
import { renderToolSchema } from '../tool/schema.js'
|
|
13
13
|
|
|
14
14
|
export interface ToolCatalogSearchOptions {
|
|
15
15
|
readonly loading?: readonly ToolLoadingMode[]
|
|
@@ -245,12 +245,17 @@ function toolDefinitionToLLMTool(definition: ToolDefinition | undefined): LLMToo
|
|
|
245
245
|
function: {
|
|
246
246
|
name: definition.name,
|
|
247
247
|
description: definition.description,
|
|
248
|
+
// Through `renderToolSchema`, not a second inline conversion. The
|
|
249
|
+
// options were already identical, so this was not a different
|
|
250
|
+
// rendering — it was the same rendering without the guarantees:
|
|
251
|
+
// no `$schema` stripping (that key rides in the tools block, which
|
|
252
|
+
// sits at position 0 inside the prompt-cache prefix), no memoization,
|
|
253
|
+
// and no freeze. Two paths that agree today are two paths that can
|
|
254
|
+
// disagree tomorrow, and a tool reaching the wire through the catalog
|
|
255
|
+
// rather than the registry is not a different tool.
|
|
248
256
|
parameters:
|
|
249
257
|
(definition.modelInputSchema ? structuredClone(definition.modelInputSchema) : undefined) ??
|
|
250
|
-
(
|
|
251
|
-
target: 'jsonSchema7',
|
|
252
|
-
$refStrategy: 'none',
|
|
253
|
-
}) as Record<string, unknown>),
|
|
258
|
+
renderToolSchema(definition.inputSchema),
|
|
254
259
|
},
|
|
255
260
|
}
|
|
256
261
|
}
|
|
@@ -84,6 +84,7 @@ class ClassifiedFailureProvider implements LLMProvider {
|
|
|
84
84
|
providerId: 'classified-failure',
|
|
85
85
|
status: 429,
|
|
86
86
|
retryAfterMs: 2000,
|
|
87
|
+
detail: 'rate limit reached for this organization',
|
|
87
88
|
}),
|
|
88
89
|
)
|
|
89
90
|
}
|
|
@@ -210,11 +211,16 @@ describe('query stream recovery', () => {
|
|
|
210
211
|
)
|
|
211
212
|
|
|
212
213
|
expect(run.status).toBe('failed')
|
|
214
|
+
// `detail` rides along with the classification. Without it a host
|
|
215
|
+
// rendering this metadata knows a request was rejected but not why, and
|
|
216
|
+
// has to go re-parse the message string — which is the re-parsing this
|
|
217
|
+
// structured field exists to avoid.
|
|
213
218
|
expect(run.lastProviderError).toEqual({
|
|
214
219
|
kind: 'throttle',
|
|
215
220
|
providerId: 'classified-failure',
|
|
216
221
|
status: 429,
|
|
217
222
|
retryAfterMs: 2000,
|
|
223
|
+
detail: 'rate limit reached for this organization',
|
|
218
224
|
})
|
|
219
225
|
expect(events.find((event) => event.type === 'run_failed')).toMatchObject({
|
|
220
226
|
type: 'run_failed',
|
|
@@ -47,6 +47,11 @@ export class ResultAssembler {
|
|
|
47
47
|
type: 'run_completed',
|
|
48
48
|
runId: runMgr.id,
|
|
49
49
|
result: runMgr.getRun().result ?? '',
|
|
50
|
+
// Read AFTER `markCompleted`, which is where a run that was stopped
|
|
51
|
+
// mid-flight has its reason settled. Carried on the event so a
|
|
52
|
+
// consumer can tell "answered" from "ran out of budget" without
|
|
53
|
+
// holding the `Run`.
|
|
54
|
+
...(runMgr.getRun().stopReason ? { stopReason: runMgr.getRun().stopReason } : {}),
|
|
50
55
|
})
|
|
51
56
|
yield* drainPending()
|
|
52
57
|
|
|
@@ -124,6 +129,12 @@ export class ResultAssembler {
|
|
|
124
129
|
providerId: err.providerId,
|
|
125
130
|
...(err.status !== undefined ? { status: err.status } : {}),
|
|
126
131
|
...(err.retryAfterMs !== undefined ? { retryAfterMs: err.retryAfterMs } : {}),
|
|
132
|
+
// The provider's own sentence, already truncated and scrubbed
|
|
133
|
+
// by the driver. Without it a host rendering this metadata
|
|
134
|
+
// knows a request was rejected but not which field, and has to
|
|
135
|
+
// go re-parse `error` to find out — which is exactly the
|
|
136
|
+
// re-parsing the line above says this exists to avoid.
|
|
137
|
+
...(err.detail !== undefined ? { detail: err.detail } : {}),
|
|
127
138
|
}
|
|
128
139
|
: undefined
|
|
129
140
|
runMgr.markFailed(errorMessage, providerError)
|
|
@@ -70,12 +70,18 @@ describe('the model can emit the idiom the description recommends', () => {
|
|
|
70
70
|
})
|
|
71
71
|
|
|
72
72
|
it('admits only "end" as a string, so a synonym cannot be generated', () => {
|
|
73
|
-
const insert = (schema().properties as Record<string, {
|
|
73
|
+
const insert = (schema().properties as Record<string, { anyOf?: unknown[] }>).insertLine
|
|
74
74
|
|
|
75
75
|
// The schema is where the synonym problem is solved for a provider that
|
|
76
76
|
// constrains: `"EOF"` is not emittable because `"end"` is the only
|
|
77
77
|
// string the union admits.
|
|
78
|
-
|
|
78
|
+
//
|
|
79
|
+
// `anyOf`, not `oneOf`: strict tool use validates against a subset of
|
|
80
|
+
// JSON Schema that excludes `oneOf`, and the vendor rejects the whole
|
|
81
|
+
// request rather than one field — so the spelling here is load-bearing,
|
|
82
|
+
// not stylistic. The two are equivalent for disjoint branches.
|
|
83
|
+
// `minimum` is gone for the same reason; the execution schema keeps it.
|
|
84
|
+
expect(insert?.anyOf).toEqual([{ type: 'integer' }, { const: 'end' }])
|
|
79
85
|
})
|
|
80
86
|
})
|
|
81
87
|
|
|
@@ -39,7 +39,7 @@ describe('EditTool', () => {
|
|
|
39
39
|
'Exact replacement text. May be empty to delete old_string. Keep under 12000 characters.',
|
|
40
40
|
},
|
|
41
41
|
insertLine: {
|
|
42
|
-
|
|
42
|
+
anyOf: [{ type: 'integer' }, { const: 'end' }],
|
|
43
43
|
description:
|
|
44
44
|
'Insert instead of replacing. The new_string goes after this 1-indexed line; 0 inserts before the first line; "end" appends. Omit for a find-and-replace.',
|
|
45
45
|
},
|
|
@@ -102,12 +102,30 @@ const modelInputSchema: Record<string, unknown> = {
|
|
|
102
102
|
},
|
|
103
103
|
insertLine: {
|
|
104
104
|
// The union the execution schema already accepts, stated so a
|
|
105
|
-
// constrained decoder can emit it.
|
|
105
|
+
// constrained decoder can emit it. Stating it as a union of an
|
|
106
106
|
// integer and the literal `"end"` also makes the synonym problem
|
|
107
107
|
// structurally impossible: `"EOF"`, `"append"` and `"last"` are
|
|
108
108
|
// not emittable, because `"end"` is the only string the schema
|
|
109
109
|
// admits.
|
|
110
|
-
|
|
110
|
+
//
|
|
111
|
+
// `anyOf`, NOT `oneOf`. Strict tool use validates against a SUBSET
|
|
112
|
+
// of JSON Schema, and `oneOf` is not in it — the vendor rejects the
|
|
113
|
+
// whole request with `tools.N.custom: Schema type 'oneOf' is not
|
|
114
|
+
// supported`, so the tool never mounts and the turn dies before a
|
|
115
|
+
// single token. Measured against the live API: strict + `oneOf` is
|
|
116
|
+
// a 400, strict + `anyOf` is accepted, and non-strict + `oneOf` is
|
|
117
|
+
// accepted — which is why nothing caught it. Both halves were
|
|
118
|
+
// individually fine; only their combination fails, and strict is on
|
|
119
|
+
// for every model at or above the gate.
|
|
120
|
+
//
|
|
121
|
+
// The two branches are disjoint, so `anyOf` and `oneOf` mean the
|
|
122
|
+
// same thing here — nothing is loosened.
|
|
123
|
+
//
|
|
124
|
+
// `minimum` is gone for the same reason: numeric constraints are
|
|
125
|
+
// outside the strict subset too. The bound is not lost — the
|
|
126
|
+
// execution schema still enforces it, which is where a value that
|
|
127
|
+
// crosses a boundary should be checked anyway.
|
|
128
|
+
anyOf: [{ type: 'integer' }, { const: 'end' }],
|
|
111
129
|
description:
|
|
112
130
|
'Insert instead of replacing. The new_string goes after this 1-indexed line; 0 inserts before the first line; "end" appends. Omit for a find-and-replace.',
|
|
113
131
|
},
|
|
@@ -10,20 +10,36 @@ export type ProviderErrorKind =
|
|
|
10
10
|
/**
|
|
11
11
|
* Serializable provider-failure metadata carried by failed runs and events.
|
|
12
12
|
*
|
|
13
|
-
* No response body,
|
|
13
|
+
* No response body, URL, or `cause` belongs here. `detail` is the one thing
|
|
14
|
+
* the provider itself said, and it arrives scrubbed — see below.
|
|
14
15
|
*/
|
|
15
16
|
export interface ProviderErrorInfo {
|
|
16
17
|
readonly kind: ProviderErrorKind
|
|
17
18
|
readonly providerId: string
|
|
18
19
|
readonly status?: number
|
|
19
20
|
readonly retryAfterMs?: number
|
|
21
|
+
/**
|
|
22
|
+
* What the provider said was wrong, truncated and scrubbed of anything
|
|
23
|
+
* credential-shaped.
|
|
24
|
+
*
|
|
25
|
+
* Carried here and not only on the error's `message` for the same reason
|
|
26
|
+
* `kind` is: a host rendering a failure should not have to parse a
|
|
27
|
+
* sentence to show one. It is the field that names the offending
|
|
28
|
+
* parameter, which is usually the whole diagnosis.
|
|
29
|
+
*/
|
|
30
|
+
readonly detail?: string
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
export interface ProviderRequestErrorInit extends ProviderErrorInfo {
|
|
23
34
|
/**
|
|
24
|
-
* Optional extra clause for the message.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
35
|
+
* Optional extra clause for the message.
|
|
36
|
+
*
|
|
37
|
+
* This used to be required to be text the codebase authored, never a
|
|
38
|
+
* fragment of a vendor error — and the constructor did not read the field
|
|
39
|
+
* at all, so nothing carried either kind of text. Providers may now pass
|
|
40
|
+
* their own complaint through `vendorDetail`, which truncates it and
|
|
41
|
+
* replaces anything credential-shaped. Text this codebase authored is
|
|
42
|
+
* still welcome; what is not welcome is a raw body passed straight in.
|
|
27
43
|
*/
|
|
28
44
|
readonly detail?: string
|
|
29
45
|
}
|
package/src/types/run/events.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type { PlanStep } from '../plan/index.js'
|
|
|
16
16
|
import type { PluginHookEvent, PluginHookResult } from '../plugin/index.js'
|
|
17
17
|
import type { TaskStatus } from '../task/index.js'
|
|
18
18
|
import type { Lineage } from './lineage.js'
|
|
19
|
-
import type { MessageStopReason } from './stop-reason.js'
|
|
19
|
+
import type { MessageStopReason, StopReason } from './stop-reason.js'
|
|
20
20
|
import type {
|
|
21
21
|
SubsessionIdledEvent,
|
|
22
22
|
SubsessionMessagedEvent,
|
|
@@ -252,7 +252,20 @@ type CoreRunEvent =
|
|
|
252
252
|
guardrail?: string
|
|
253
253
|
reason?: string
|
|
254
254
|
}
|
|
255
|
-
|
|
255
|
+
/**
|
|
256
|
+
* The run reached its end without throwing.
|
|
257
|
+
*
|
|
258
|
+
* `completed` is not `succeeded`. A run stopped by its token budget, its
|
|
259
|
+
* timeout, its iteration cap, a cancellation or a blocking output guardrail
|
|
260
|
+
* all arrive here — `run_failed` is emitted only from the throw path — so a
|
|
261
|
+
* consumer that treated this event as success reported one for a run whose
|
|
262
|
+
* answer was refused. Measured: `max_iterations` produces
|
|
263
|
+
* `status: 'completed'` with `result` holding whatever partial text existed.
|
|
264
|
+
*
|
|
265
|
+
* `stopReason` is what separates them, and it is on the event because the
|
|
266
|
+
* alternative is asking every consumer to hold the `Run` as well.
|
|
267
|
+
*/
|
|
268
|
+
| { type: 'run_completed'; runId: RunId; result: string; stopReason?: StopReason }
|
|
256
269
|
/**
|
|
257
270
|
* The run failed.
|
|
258
271
|
*
|