@namzu/sdk 1.3.0 → 2.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 +106 -0
- package/dist/compaction/__tests__/verifier-empty-reply.test.d.ts +12 -0
- package/dist/compaction/__tests__/verifier-empty-reply.test.d.ts.map +1 -0
- package/dist/compaction/__tests__/verifier-empty-reply.test.js +73 -0
- package/dist/compaction/__tests__/verifier-empty-reply.test.js.map +1 -0
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +10 -1
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/config/__tests__/compaction-budget-schema.test.d.ts +18 -0
- package/dist/config/__tests__/compaction-budget-schema.test.d.ts.map +1 -0
- package/dist/config/__tests__/compaction-budget-schema.test.js +96 -0
- package/dist/config/__tests__/compaction-budget-schema.test.js.map +1 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +20 -11
- package/dist/config/runtime.js.map +1 -1
- package/dist/manager/run/persistence.d.ts +2 -1
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +3 -1
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/provider/__tests__/errors.test.d.ts +2 -0
- package/dist/provider/__tests__/errors.test.d.ts.map +1 -0
- package/dist/provider/__tests__/errors.test.js +61 -0
- package/dist/provider/__tests__/errors.test.js.map +1 -0
- package/dist/provider/errors.d.ts +108 -0
- package/dist/provider/errors.d.ts.map +1 -0
- package/dist/provider/errors.js +311 -0
- package/dist/provider/errors.js.map +1 -0
- package/dist/provider/index.d.ts +2 -0
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +1 -0
- package/dist/provider/index.js.map +1 -1
- package/dist/public-runtime.d.ts +11 -11
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +11 -11
- package/dist/public-runtime.js.map +1 -1
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +45 -12
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/registry/tool/execute.test.js +104 -7
- package/dist/registry/tool/execute.test.js.map +1 -1
- package/dist/registry/toolset/catalog.d.ts.map +1 -1
- package/dist/registry/toolset/catalog.js +17 -4
- package/dist/registry/toolset/catalog.js.map +1 -1
- package/dist/registry/toolset/catalog.test.js +101 -0
- package/dist/registry/toolset/catalog.test.js.map +1 -1
- package/dist/runtime/query/__tests__/capability-negotiation.test.js +79 -0
- package/dist/runtime/query/__tests__/capability-negotiation.test.js.map +1 -1
- package/dist/runtime/query/__tests__/deferred-tools.test.js +100 -0
- package/dist/runtime/query/__tests__/deferred-tools.test.js.map +1 -1
- package/dist/runtime/query/__tests__/long-document-flow.test.js +12 -12
- package/dist/runtime/query/__tests__/long-document-flow.test.js.map +1 -1
- package/dist/runtime/query/__tests__/stream-recovery.test.js +55 -2
- package/dist/runtime/query/__tests__/stream-recovery.test.js.map +1 -1
- package/dist/runtime/query/executor.js +2 -2
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +10 -0
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.d.ts +32 -0
- package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.js +152 -0
- package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +52 -12
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +11 -5
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/result.d.ts.map +1 -1
- package/dist/runtime/query/result.js +11 -1
- package/dist/runtime/query/result.js.map +1 -1
- package/dist/tools/builtins/__tests__/edit.test.js +216 -10
- package/dist/tools/builtins/__tests__/edit.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/payload-budget.test.js +30 -16
- package/dist/tools/builtins/__tests__/payload-budget.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/write-file.test.js +37 -13
- package/dist/tools/builtins/__tests__/write-file.test.js.map +1 -1
- package/dist/tools/builtins/atomic-write-file.d.ts +13 -0
- package/dist/tools/builtins/atomic-write-file.d.ts.map +1 -0
- package/dist/tools/builtins/atomic-write-file.js +46 -0
- package/dist/tools/builtins/atomic-write-file.js.map +1 -0
- package/dist/tools/builtins/bash.js +2 -2
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts +2 -5
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +105 -125
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/file-mutation-lock.d.ts +7 -0
- package/dist/tools/builtins/file-mutation-lock.d.ts.map +1 -0
- package/dist/tools/builtins/file-mutation-lock.js +25 -0
- package/dist/tools/builtins/file-mutation-lock.js.map +1 -0
- package/dist/tools/builtins/index.js +2 -2
- package/dist/tools/builtins/index.js.map +1 -1
- package/dist/tools/builtins/write-file.d.ts +1 -2
- package/dist/tools/builtins/write-file.d.ts.map +1 -1
- package/dist/tools/builtins/write-file.js +66 -40
- package/dist/tools/builtins/write-file.js.map +1 -1
- package/dist/tools/coordinator/__tests__/task-list.test.js +14 -0
- package/dist/tools/coordinator/__tests__/task-list.test.js.map +1 -1
- package/dist/tools/coordinator/agent.d.ts +6 -8
- package/dist/tools/coordinator/agent.d.ts.map +1 -1
- package/dist/tools/coordinator/agent.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +11 -32
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/defineTool.d.ts +3 -0
- package/dist/tools/defineTool.d.ts.map +1 -1
- package/dist/tools/defineTool.js +3 -0
- package/dist/tools/defineTool.js.map +1 -1
- package/dist/types/provider/chat.d.ts +9 -0
- package/dist/types/provider/chat.d.ts.map +1 -1
- package/dist/types/provider/error.d.ts +22 -0
- package/dist/types/provider/error.d.ts.map +1 -0
- package/dist/types/provider/error.js +2 -0
- package/dist/types/provider/error.js.map +1 -0
- package/dist/types/provider/index.d.ts +1 -0
- package/dist/types/provider/index.d.ts.map +1 -1
- package/dist/types/run/entity.d.ts +2 -0
- package/dist/types/run/entity.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +2 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/sandbox/index.d.ts +4 -0
- package/dist/types/sandbox/index.d.ts.map +1 -1
- package/dist/types/sandbox/index.js.map +1 -1
- package/dist/types/tool/index.d.ts +20 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/compaction/__tests__/verifier-empty-reply.test.ts +96 -0
- package/src/compaction/verifier.ts +10 -1
- package/src/config/__tests__/compaction-budget-schema.test.ts +114 -0
- package/src/config/runtime.ts +20 -11
- package/src/manager/run/persistence.ts +3 -1
- package/src/provider/__tests__/errors.test.ts +85 -0
- package/src/provider/errors.ts +344 -0
- package/src/provider/index.ts +15 -0
- package/src/public-runtime.ts +53 -10
- package/src/registry/tool/execute.test.ts +121 -7
- package/src/registry/tool/execute.ts +50 -12
- package/src/registry/toolset/catalog.test.ts +118 -0
- package/src/registry/toolset/catalog.ts +26 -4
- package/src/runtime/query/__tests__/capability-negotiation.test.ts +88 -0
- package/src/runtime/query/__tests__/deferred-tools.test.ts +111 -0
- package/src/runtime/query/__tests__/long-document-flow.test.ts +12 -12
- package/src/runtime/query/__tests__/stream-recovery.test.ts +65 -2
- package/src/runtime/query/executor.ts +2 -2
- package/src/runtime/query/iteration/index.ts +15 -0
- package/src/runtime/query/iteration/phases/compaction-safe-cut.test.ts +186 -0
- package/src/runtime/query/iteration/phases/compaction.ts +54 -12
- package/src/runtime/query/iteration/stream-turn.ts +11 -6
- package/src/runtime/query/result.ts +11 -1
- package/src/tools/builtins/__tests__/edit.test.ts +270 -10
- package/src/tools/builtins/__tests__/payload-budget.test.ts +40 -20
- package/src/tools/builtins/__tests__/write-file.test.ts +52 -13
- package/src/tools/builtins/atomic-write-file.ts +58 -0
- package/src/tools/builtins/bash.ts +2 -2
- package/src/tools/builtins/edit.ts +116 -162
- package/src/tools/builtins/file-mutation-lock.ts +27 -0
- package/src/tools/builtins/index.ts +2 -2
- package/src/tools/builtins/write-file.ts +68 -44
- package/src/tools/coordinator/__tests__/task-list.test.ts +16 -0
- package/src/tools/coordinator/agent.ts +6 -8
- package/src/tools/coordinator/index.ts +11 -34
- package/src/tools/defineTool.ts +6 -0
- package/src/types/provider/chat.ts +14 -1
- package/src/types/provider/error.ts +29 -0
- package/src/types/provider/index.ts +5 -0
- package/src/types/run/entity.ts +2 -0
- package/src/types/run/events.ts +7 -1
- package/src/types/sandbox/index.ts +4 -0
- package/src/types/tool/index.ts +20 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One classified error for every provider failure.
|
|
3
|
+
*
|
|
4
|
+
* Before this, a failure from any of the seven drivers reached the caller as an
|
|
5
|
+
* opaque string, so nothing downstream could decide whether it was worth
|
|
6
|
+
* retrying, whether it was the caller's fault, or whether the context window had
|
|
7
|
+
* simply run out. Two drivers built that string by interpolating the response
|
|
8
|
+
* body; three more inherited a vendor SDK error whose message IS the response
|
|
9
|
+
* body. Either way, a credential the upstream echoed back landed in a message
|
|
10
|
+
* that gets logged.
|
|
11
|
+
*
|
|
12
|
+
* So the contract here is deliberately narrow:
|
|
13
|
+
*
|
|
14
|
+
* - the message is built from the STATUS LINE and the classified `kind`. The
|
|
15
|
+
* response body is read to classify and then dropped. It is never
|
|
16
|
+
* interpolated, never re-thrown, and never attached as `cause` — a `cause`
|
|
17
|
+
* survives every logger that serializes an error chain, which defeats the
|
|
18
|
+
* point.
|
|
19
|
+
* - `retryAfterMs` is DATA. Nothing in this module sleeps, backs off or
|
|
20
|
+
* retries. A retry loop inside a driver burns the run's wall clock and hides
|
|
21
|
+
* the failure from the layer that should decide.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { ProviderErrorKind, ProviderRequestErrorInit } from '../types/provider/error.js'
|
|
25
|
+
export type {
|
|
26
|
+
ProviderErrorInfo,
|
|
27
|
+
ProviderErrorKind,
|
|
28
|
+
ProviderRequestErrorInit,
|
|
29
|
+
} from '../types/provider/error.js'
|
|
30
|
+
|
|
31
|
+
const PROVIDER_ERROR_KINDS: readonly ProviderErrorKind[] = [
|
|
32
|
+
'throttle',
|
|
33
|
+
'network',
|
|
34
|
+
'auth',
|
|
35
|
+
'context_overflow',
|
|
36
|
+
'bad_request',
|
|
37
|
+
'server',
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A provider request that failed, classified.
|
|
42
|
+
*
|
|
43
|
+
* `name` is set explicitly rather than inherited, because the classifier is
|
|
44
|
+
* matched structurally across a package boundary (a driver in one package throws
|
|
45
|
+
* it; the runtime in another reads it) and `instanceof` is unreliable when two
|
|
46
|
+
* copies of the SDK end up in one process.
|
|
47
|
+
*/
|
|
48
|
+
export class ProviderRequestError extends Error {
|
|
49
|
+
public readonly kind: ProviderErrorKind
|
|
50
|
+
public readonly providerId: string
|
|
51
|
+
public readonly status?: number
|
|
52
|
+
public readonly retryAfterMs?: number
|
|
53
|
+
|
|
54
|
+
constructor(init: ProviderRequestErrorInit) {
|
|
55
|
+
super(buildProviderErrorMessage(init))
|
|
56
|
+
this.name = 'ProviderRequestError'
|
|
57
|
+
this.kind = init.kind
|
|
58
|
+
this.providerId = init.providerId
|
|
59
|
+
if (init.status !== undefined) this.status = init.status
|
|
60
|
+
if (init.retryAfterMs !== undefined) this.retryAfterMs = init.retryAfterMs
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Is this a classified provider failure, whichever SDK copy threw it? */
|
|
65
|
+
export function isProviderRequestError(err: unknown): err is ProviderRequestError {
|
|
66
|
+
return (
|
|
67
|
+
err instanceof Error &&
|
|
68
|
+
err.name === 'ProviderRequestError' &&
|
|
69
|
+
typeof (err as { providerId?: unknown }).providerId === 'string' &&
|
|
70
|
+
PROVIDER_ERROR_KINDS.includes((err as { kind?: ProviderErrorKind }).kind as ProviderErrorKind)
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Did the caller's own AbortSignal terminate this request?
|
|
76
|
+
*
|
|
77
|
+
* Provider SDKs do not agree on the object they reject with: some preserve
|
|
78
|
+
* `signal.reason`, while others replace it with `AbortError` or
|
|
79
|
+
* `APIUserAbortError`. Reclassifying any of those as a network failure breaks
|
|
80
|
+
* the runtime's Stop/cancel path, which depends on the abort escaping the
|
|
81
|
+
* provider boundary.
|
|
82
|
+
*
|
|
83
|
+
* The signal must itself be aborted. That condition distinguishes a caller Stop
|
|
84
|
+
* from an SDK timeout which may also use an `AbortError`-shaped rejection.
|
|
85
|
+
*/
|
|
86
|
+
export function isCallerAbortError(error: unknown, signal?: AbortSignal): boolean {
|
|
87
|
+
if (!signal?.aborted) return false
|
|
88
|
+
if (error === signal.reason) return true
|
|
89
|
+
if (!(error instanceof Error)) return false
|
|
90
|
+
return error.name === 'AbortError' || error.name === 'APIUserAbortError'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const KIND_SENTENCE: Record<ProviderErrorKind, string> = {
|
|
94
|
+
throttle: 'rate limited by the provider',
|
|
95
|
+
network: 'could not reach the provider',
|
|
96
|
+
auth: 'the provider rejected the request credentials',
|
|
97
|
+
context_overflow: 'the request exceeded the model context window',
|
|
98
|
+
bad_request: 'the provider rejected the request as invalid',
|
|
99
|
+
server: 'the provider failed to complete the request',
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function buildProviderErrorMessage(init: ProviderRequestErrorInit): string {
|
|
103
|
+
const status = init.status !== undefined ? ` (HTTP ${init.status})` : ''
|
|
104
|
+
const detail = init.detail ? `: ${init.detail}` : ''
|
|
105
|
+
return `${init.providerId}${status} — ${KIND_SENTENCE[init.kind]}${detail}`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Body fragments that mean "the request did not fit", across the vendors we
|
|
110
|
+
* drive. Matched case-insensitively against the raw body, which is then
|
|
111
|
+
* discarded.
|
|
112
|
+
*
|
|
113
|
+
* A 400 is otherwise `bad_request`: an overflow is the one 400 a caller can act
|
|
114
|
+
* on automatically (compact and retry), and mistaking a genuine schema error for
|
|
115
|
+
* an overflow would send the run into a pointless compaction loop.
|
|
116
|
+
*/
|
|
117
|
+
const OVERFLOW_BODY_PATTERNS: readonly RegExp[] = [
|
|
118
|
+
/prompt is too long/i,
|
|
119
|
+
/context[_ ]length[_ ]exceeded/i,
|
|
120
|
+
/maximum context length/i,
|
|
121
|
+
/too long for requested model/i,
|
|
122
|
+
/input is too long/i,
|
|
123
|
+
/(?:input|prompt|context).{0,80}exceeds the maximum/i,
|
|
124
|
+
/reduce the (?:input|prompt|context) length/i,
|
|
125
|
+
// Ollama phrases it the other way round — "input length exceeds context
|
|
126
|
+
// length" — so a pattern anchored on "exceeded" misses it entirely.
|
|
127
|
+
/exceeds?\s+(?:the\s+)?(?:model'?s?\s+)?context\s+(?:length|window|size)/i,
|
|
128
|
+
/context\s+(?:length|window|size)\s+exceed/i,
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
/** Does this response body say the request did not fit the window? */
|
|
132
|
+
export function bodySaysContextOverflow(body: string | undefined | null): boolean {
|
|
133
|
+
if (!body) return false
|
|
134
|
+
return OVERFLOW_BODY_PATTERNS.some((re) => re.test(body))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* `Retry-After` in milliseconds. The header is either delta-seconds or an
|
|
139
|
+
* HTTP-date; both are specified, and vendors use both.
|
|
140
|
+
*
|
|
141
|
+
* Returns undefined for anything unparseable or for a date already in the past —
|
|
142
|
+
* a negative delay is worse than none, because a caller would treat it as
|
|
143
|
+
* "retry immediately" against a provider that just asked it to wait.
|
|
144
|
+
*/
|
|
145
|
+
export function parseRetryAfterMs(
|
|
146
|
+
headerValue: string | null | undefined,
|
|
147
|
+
now: number = Date.now(),
|
|
148
|
+
): number | undefined {
|
|
149
|
+
if (!headerValue) return undefined
|
|
150
|
+
const trimmed = headerValue.trim()
|
|
151
|
+
if (trimmed === '') return undefined
|
|
152
|
+
|
|
153
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
154
|
+
const seconds = Number(trimmed)
|
|
155
|
+
if (!Number.isFinite(seconds) || seconds < 0) return undefined
|
|
156
|
+
return Math.round(seconds * 1000)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const at = Date.parse(trimmed)
|
|
160
|
+
if (Number.isNaN(at)) return undefined
|
|
161
|
+
const delta = at - now
|
|
162
|
+
return delta > 0 ? delta : undefined
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Classify an HTTP failure. `body` is used ONLY to separate a context overflow
|
|
167
|
+
* from an ordinary bad request, and is not retained.
|
|
168
|
+
*/
|
|
169
|
+
export function classifyProviderHttpStatus(
|
|
170
|
+
status: number,
|
|
171
|
+
body?: string | null,
|
|
172
|
+
): ProviderErrorKind {
|
|
173
|
+
if (status === 401 || status === 403) return 'auth'
|
|
174
|
+
if (status === 429) return 'throttle'
|
|
175
|
+
if (status === 408 || status === 425) return 'network'
|
|
176
|
+
if (status >= 500) return 'server'
|
|
177
|
+
if (status === 413) {
|
|
178
|
+
return bodySaysContextOverflow(body) ? 'context_overflow' : 'bad_request'
|
|
179
|
+
}
|
|
180
|
+
if (status >= 400) {
|
|
181
|
+
return bodySaysContextOverflow(body) ? 'context_overflow' : 'bad_request'
|
|
182
|
+
}
|
|
183
|
+
// A non-failure status reaching here is a caller bug, not a provider one.
|
|
184
|
+
return 'server'
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Vendor error TYPES, as the vendors name them in their own payloads. This is a
|
|
189
|
+
* defined vocabulary, not prose, which is what makes it safe to classify on:
|
|
190
|
+
* Anthropic sends `{"error":{"type":"overloaded_error"}}` and OpenAI sends
|
|
191
|
+
* `{"error":{"code":"context_length_exceeded"}}`.
|
|
192
|
+
*
|
|
193
|
+
* It matters for MID-STREAM failures above all. Those arrive after a 200, so
|
|
194
|
+
* there is no status to classify from, and without this an upstream overload
|
|
195
|
+
* would be filed as `network` — "we could not reach the provider" — when the
|
|
196
|
+
* provider answered and then gave up. The distinction is the whole point of the
|
|
197
|
+
* taxonomy: one is worth retrying elsewhere, the other is worth retrying here.
|
|
198
|
+
*/
|
|
199
|
+
const VENDOR_TYPE_KINDS: ReadonlyArray<readonly [RegExp, ProviderErrorKind]> = [
|
|
200
|
+
[/overloaded_error|api_error|service_unavailable/i, 'server'],
|
|
201
|
+
[/rate_limit_error|rate_limit_exceeded/i, 'throttle'],
|
|
202
|
+
[/authentication_error|permission_error|invalid_api_key|unauthorized/i, 'auth'],
|
|
203
|
+
[/timeout_error|connection_error/i, 'network'],
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
/** The kind a vendor's own error-type vocabulary implies, if it says one. */
|
|
207
|
+
function vendorTypeKind(message: string): ProviderErrorKind | undefined {
|
|
208
|
+
for (const [re, kind] of VENDOR_TYPE_KINDS) {
|
|
209
|
+
if (re.test(message)) return kind
|
|
210
|
+
}
|
|
211
|
+
return undefined
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Status code off a vendor SDK's own error object, whatever it calls the field.
|
|
216
|
+
* Anthropic and OpenAI use `status`; the ollama client uses `status_code`; AWS
|
|
217
|
+
* puts it under `$metadata.httpStatusCode`.
|
|
218
|
+
*/
|
|
219
|
+
function vendorErrorStatus(err: unknown): number | undefined {
|
|
220
|
+
if (typeof err !== 'object' || err === null) return undefined
|
|
221
|
+
const e = err as {
|
|
222
|
+
status?: unknown
|
|
223
|
+
statusCode?: unknown
|
|
224
|
+
status_code?: unknown
|
|
225
|
+
$metadata?: { httpStatusCode?: unknown }
|
|
226
|
+
}
|
|
227
|
+
for (const candidate of [e.status, e.statusCode, e.status_code, e.$metadata?.httpStatusCode]) {
|
|
228
|
+
if (typeof candidate === 'number' && Number.isFinite(candidate)) return candidate
|
|
229
|
+
}
|
|
230
|
+
return undefined
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* `retry-after` off a vendor SDK error's captured response headers. The
|
|
235
|
+
* Anthropic and OpenAI clients both attach them (as a `Headers` instance or a
|
|
236
|
+
* plain object depending on version); the ollama client throws them away, which
|
|
237
|
+
* is why `retryAfterMs` is genuinely unavailable on that driver.
|
|
238
|
+
*/
|
|
239
|
+
function vendorRetryAfter(err: unknown): string | undefined {
|
|
240
|
+
if (typeof err !== 'object' || err === null) return undefined
|
|
241
|
+
// AWS keeps the response one level down, on `$response`; the Anthropic and
|
|
242
|
+
// OpenAI clients attach `headers` directly.
|
|
243
|
+
const headers =
|
|
244
|
+
(err as { headers?: unknown }).headers ??
|
|
245
|
+
(err as { $response?: { headers?: unknown } }).$response?.headers
|
|
246
|
+
if (!headers) return undefined
|
|
247
|
+
if (typeof (headers as Headers).get === 'function') {
|
|
248
|
+
return (headers as Headers).get('retry-after') ?? undefined
|
|
249
|
+
}
|
|
250
|
+
const record = headers as Record<string, unknown>
|
|
251
|
+
for (const key of ['retry-after', 'Retry-After', 'retryAfter']) {
|
|
252
|
+
const value = record[key]
|
|
253
|
+
if (typeof value === 'string') return value
|
|
254
|
+
}
|
|
255
|
+
return undefined
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Classify an error thrown by a vendor SDK and replace it.
|
|
260
|
+
*
|
|
261
|
+
* This exists because wrapping our OWN `!response.ok` throws is not enough. The
|
|
262
|
+
* Anthropic, OpenAI and ollama clients each build their error message FROM the
|
|
263
|
+
* response body, so a credential the upstream echoed back is already inside
|
|
264
|
+
* `err.message` before our code sees it. Proven with a planted fake token on all
|
|
265
|
+
* three.
|
|
266
|
+
*
|
|
267
|
+
* So the vendor error is read for its status and scanned for an overflow
|
|
268
|
+
* signature, and then **dropped entirely** — not re-thrown, not wrapped, and not
|
|
269
|
+
* attached as `cause`. A `cause` is exactly what a structured logger walks, so
|
|
270
|
+
* keeping one for debuggability would reintroduce the leak it is meant to close.
|
|
271
|
+
*
|
|
272
|
+
* `name` is checked too, because AWS models its failures as distinct classes
|
|
273
|
+
* (`ThrottlingException`, `ValidationException`, `AccessDeniedException`) rather
|
|
274
|
+
* than as status codes.
|
|
275
|
+
*/
|
|
276
|
+
export function providerVendorError(input: {
|
|
277
|
+
readonly providerId: string
|
|
278
|
+
readonly error: unknown
|
|
279
|
+
readonly retryAfter?: string | null
|
|
280
|
+
readonly now?: number
|
|
281
|
+
}): ProviderRequestError {
|
|
282
|
+
const { error } = input
|
|
283
|
+
const status = vendorErrorStatus(error)
|
|
284
|
+
const name = error instanceof Error ? error.name : ''
|
|
285
|
+
const message = error instanceof Error ? error.message : ''
|
|
286
|
+
|
|
287
|
+
let kind: ProviderErrorKind
|
|
288
|
+
if (/ThrottlingException|TooManyRequests|ServiceQuotaExceeded/i.test(name)) {
|
|
289
|
+
kind = 'throttle'
|
|
290
|
+
} else if (/AccessDenied|Unauthorized|Forbidden|Authentication/i.test(name)) {
|
|
291
|
+
kind = 'auth'
|
|
292
|
+
} else if (/ValidationException/i.test(name)) {
|
|
293
|
+
kind = bodySaysContextOverflow(message) ? 'context_overflow' : 'bad_request'
|
|
294
|
+
} else if (
|
|
295
|
+
/ServiceUnavailable|InternalServer|ModelTimeout|ModelStreamError|ModelError|ModelNotReady/i.test(
|
|
296
|
+
name,
|
|
297
|
+
)
|
|
298
|
+
) {
|
|
299
|
+
kind = 'server'
|
|
300
|
+
} else if (status !== undefined) {
|
|
301
|
+
kind = classifyProviderHttpStatus(status, message)
|
|
302
|
+
} else if (bodySaysContextOverflow(message)) {
|
|
303
|
+
kind = 'context_overflow'
|
|
304
|
+
} else {
|
|
305
|
+
// No status: a mid-stream failure, or a transport error. The vendor's own
|
|
306
|
+
// error-type vocabulary is the only signal left; absent that, the request
|
|
307
|
+
// genuinely did not get an answer.
|
|
308
|
+
kind = vendorTypeKind(message) ?? 'network'
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const retryAfterMs = parseRetryAfterMs(
|
|
312
|
+
input.retryAfter ?? vendorRetryAfter(error),
|
|
313
|
+
input.now ?? Date.now(),
|
|
314
|
+
)
|
|
315
|
+
return new ProviderRequestError({
|
|
316
|
+
kind,
|
|
317
|
+
providerId: input.providerId,
|
|
318
|
+
...(status !== undefined ? { status } : {}),
|
|
319
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Build a classified error from a failed HTTP response.
|
|
325
|
+
*
|
|
326
|
+
* Callers pass the body they already read for classification; this function
|
|
327
|
+
* does not return it, store it, or put it in the message.
|
|
328
|
+
*/
|
|
329
|
+
export function providerHttpError(input: {
|
|
330
|
+
readonly providerId: string
|
|
331
|
+
readonly status: number
|
|
332
|
+
readonly body?: string | null
|
|
333
|
+
readonly retryAfter?: string | null
|
|
334
|
+
readonly now?: number
|
|
335
|
+
}): ProviderRequestError {
|
|
336
|
+
const kind = classifyProviderHttpStatus(input.status, input.body)
|
|
337
|
+
const retryAfterMs = parseRetryAfterMs(input.retryAfter, input.now ?? Date.now())
|
|
338
|
+
return new ProviderRequestError({
|
|
339
|
+
kind,
|
|
340
|
+
providerId: input.providerId,
|
|
341
|
+
status: input.status,
|
|
342
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
343
|
+
})
|
|
344
|
+
}
|
package/src/provider/index.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ProviderRequestError,
|
|
3
|
+
isCallerAbortError,
|
|
4
|
+
isProviderRequestError,
|
|
5
|
+
classifyProviderHttpStatus,
|
|
6
|
+
bodySaysContextOverflow,
|
|
7
|
+
parseRetryAfterMs,
|
|
8
|
+
providerHttpError,
|
|
9
|
+
providerVendorError,
|
|
10
|
+
} from './errors.js'
|
|
11
|
+
export type {
|
|
12
|
+
ProviderErrorInfo,
|
|
13
|
+
ProviderErrorKind,
|
|
14
|
+
ProviderRequestErrorInit,
|
|
15
|
+
} from './errors.js'
|
|
1
16
|
export {
|
|
2
17
|
ProviderRegistry,
|
|
3
18
|
UnknownProviderError,
|
package/src/public-runtime.ts
CHANGED
|
@@ -47,11 +47,19 @@ export * from './utils/id.js'
|
|
|
47
47
|
|
|
48
48
|
// ─── utility helpers ─────────────────────────────────────────────────────
|
|
49
49
|
|
|
50
|
-
export {
|
|
50
|
+
export {
|
|
51
|
+
accumulateCost,
|
|
52
|
+
calculateCost,
|
|
53
|
+
formatCost,
|
|
54
|
+
ZERO_COST,
|
|
55
|
+
} from './utils/cost.js'
|
|
51
56
|
export { toErrorMessage } from './utils/error.js'
|
|
52
57
|
export { configureLogger, getRootLogger, Logger } from './utils/logger.js'
|
|
53
58
|
export { buildToolResultHashes, hashToolResult } from './utils/hash.js'
|
|
54
|
-
export {
|
|
59
|
+
export {
|
|
60
|
+
compressShellOutput,
|
|
61
|
+
compressShellOutputFull,
|
|
62
|
+
} from './utils/shell-compress.js'
|
|
55
63
|
export { createChildAbortController } from './utils/abort.js'
|
|
56
64
|
export { memoizeAsync } from './utils/memoize.js'
|
|
57
65
|
export { extractFinalResponse } from './utils/conversation.js'
|
|
@@ -61,7 +69,10 @@ export { extractFinalResponse } from './utils/conversation.js'
|
|
|
61
69
|
export { resolveTaskModel } from './router/task-router.js'
|
|
62
70
|
export { drainQuery, query } from './runtime/query/index.js'
|
|
63
71
|
export { ContextCache } from './runtime/query/context-cache.js'
|
|
64
|
-
export {
|
|
72
|
+
export {
|
|
73
|
+
CheckpointManager,
|
|
74
|
+
projectEmergencyToCheckpoint,
|
|
75
|
+
} from './runtime/query/checkpoint.js'
|
|
65
76
|
export { prepareReplayState } from './runtime/query/replay/prepare.js'
|
|
66
77
|
export { listCheckpoints } from './runtime/query/replay/list.js'
|
|
67
78
|
export { DecisionParser, FallbackResolver } from './runtime/decision/index.js'
|
|
@@ -73,8 +84,17 @@ export {
|
|
|
73
84
|
|
|
74
85
|
// ─── personas, skills, advisory ──────────────────────────────────────────
|
|
75
86
|
|
|
76
|
-
export {
|
|
77
|
-
|
|
87
|
+
export {
|
|
88
|
+
assembleSystemPrompt,
|
|
89
|
+
mergePersonas,
|
|
90
|
+
withSessionContext,
|
|
91
|
+
} from './persona/index.js'
|
|
92
|
+
export {
|
|
93
|
+
discoverSkills,
|
|
94
|
+
loadSkill,
|
|
95
|
+
resolveSkillChain,
|
|
96
|
+
SkillRegistry,
|
|
97
|
+
} from './skills/index.js'
|
|
78
98
|
export {
|
|
79
99
|
AdvisorRegistry,
|
|
80
100
|
AdvisoryContext,
|
|
@@ -144,19 +164,30 @@ export { LocalTaskGateway } from './gateway/local.js'
|
|
|
144
164
|
// ─── providers, sandbox, vault ───────────────────────────────────────────
|
|
145
165
|
|
|
146
166
|
export {
|
|
167
|
+
bodySaysContextOverflow,
|
|
168
|
+
classifyProviderHttpStatus,
|
|
147
169
|
DuplicateProviderError,
|
|
170
|
+
isCallerAbortError,
|
|
171
|
+
isProviderRequestError,
|
|
148
172
|
LazyProviderLoadError,
|
|
149
173
|
LazyProviderSyncCreateError,
|
|
150
174
|
MOCK_CAPABILITIES,
|
|
151
175
|
MockLLMProvider,
|
|
176
|
+
parseRetryAfterMs,
|
|
152
177
|
PERMISSIVE_PROVIDER_CAPABILITIES,
|
|
178
|
+
providerHttpError,
|
|
179
|
+
providerVendorError,
|
|
153
180
|
ProviderRegistry,
|
|
181
|
+
ProviderRequestError,
|
|
154
182
|
registerMock,
|
|
155
183
|
resolveProviderCapabilities,
|
|
156
184
|
UnknownProviderError,
|
|
157
185
|
} from './provider/index.js'
|
|
158
186
|
|
|
159
|
-
export {
|
|
187
|
+
export {
|
|
188
|
+
LocalSandboxProvider,
|
|
189
|
+
SandboxProviderFactory,
|
|
190
|
+
} from './sandbox/index.js'
|
|
160
191
|
|
|
161
192
|
export { InMemoryCredentialVault } from './vault/index.js'
|
|
162
193
|
|
|
@@ -222,7 +253,10 @@ export {
|
|
|
222
253
|
runToA2ATask,
|
|
223
254
|
} from './bridge/a2a/index.js'
|
|
224
255
|
|
|
225
|
-
export {
|
|
256
|
+
export {
|
|
257
|
+
mapRunToStreamEvent,
|
|
258
|
+
mapSessionToStreamEvent,
|
|
259
|
+
} from './bridge/sse/index.js'
|
|
226
260
|
|
|
227
261
|
// ─── bus, verification ───────────────────────────────────────────────────
|
|
228
262
|
|
|
@@ -355,10 +389,19 @@ export {
|
|
|
355
389
|
// ─── runtime helpers colocated with shapes under `types/` (§1.5) ─────────
|
|
356
390
|
|
|
357
391
|
export { A2AProtocolError } from './types/a2a/index.js'
|
|
358
|
-
export {
|
|
392
|
+
export {
|
|
393
|
+
isTerminalActivityStatus,
|
|
394
|
+
resolveActivityTracking,
|
|
395
|
+
} from './types/activity/index.js'
|
|
359
396
|
export { isTerminalAgentTaskState } from './types/agent/task.js'
|
|
360
|
-
export {
|
|
361
|
-
|
|
397
|
+
export {
|
|
398
|
+
accumulateTokenUsage,
|
|
399
|
+
isTerminalStatus,
|
|
400
|
+
} from './types/common/index.js'
|
|
401
|
+
export {
|
|
402
|
+
assertComputerUseActionType,
|
|
403
|
+
assertDisplayServer,
|
|
404
|
+
} from './types/computer-use/index.js'
|
|
362
405
|
export { isConnectorActive } from './types/connector/core.js'
|
|
363
406
|
export { CONNECTOR_SCOPE_ORDER } from './types/connector/scope.js'
|
|
364
407
|
export { RoutingResponseSchema } from './types/decision/index.js'
|
|
@@ -98,6 +98,17 @@ describe('ToolRegistry — register + availability', () => {
|
|
|
98
98
|
expect(() => r.register('oops', 'not-a-tool' as never)).toThrow(/requires a ToolDefinition/)
|
|
99
99
|
})
|
|
100
100
|
|
|
101
|
+
it('rejects enforced model input without an explicit model schema', () => {
|
|
102
|
+
const r = new ToolRegistry()
|
|
103
|
+
expect(() =>
|
|
104
|
+
r.register(
|
|
105
|
+
makeTool('unsafe_strict', {
|
|
106
|
+
enforceModelInput: true,
|
|
107
|
+
}),
|
|
108
|
+
),
|
|
109
|
+
).toThrow(/enforceModelInput.*modelInputSchema/)
|
|
110
|
+
})
|
|
111
|
+
|
|
101
112
|
it('getAvailability returns active for unknown names (current default)', () => {
|
|
102
113
|
const r = new ToolRegistry()
|
|
103
114
|
expect(r.getAvailability('never-registered')).toBe('active')
|
|
@@ -228,7 +239,10 @@ describe('ToolRegistry — searchDeferred', () => {
|
|
|
228
239
|
r.register(
|
|
229
240
|
[
|
|
230
241
|
makeTool('send_invoice', {
|
|
231
|
-
inputSchema: z.object({
|
|
242
|
+
inputSchema: z.object({
|
|
243
|
+
customerEmail: z.string(),
|
|
244
|
+
amount: z.number(),
|
|
245
|
+
}),
|
|
232
246
|
}),
|
|
233
247
|
],
|
|
234
248
|
'deferred',
|
|
@@ -237,10 +251,50 @@ describe('ToolRegistry — searchDeferred', () => {
|
|
|
237
251
|
expect(r.searchDeferred('customerEmail').map((t) => t.name)).toEqual(['send_invoice'])
|
|
238
252
|
})
|
|
239
253
|
|
|
254
|
+
it('indexes canonical model-schema branches without re-advertising runtime aliases', () => {
|
|
255
|
+
const r = new ToolRegistry()
|
|
256
|
+
r.register(
|
|
257
|
+
[
|
|
258
|
+
makeTool('edit_document', {
|
|
259
|
+
inputSchema: z.object({ oldStr: z.string(), newStr: z.string() }),
|
|
260
|
+
modelInputSchema: {
|
|
261
|
+
type: 'object',
|
|
262
|
+
properties: {
|
|
263
|
+
path: { type: 'string' },
|
|
264
|
+
old_string: { type: 'string' },
|
|
265
|
+
new_string: { type: 'string' },
|
|
266
|
+
},
|
|
267
|
+
required: ['path', 'new_string'],
|
|
268
|
+
additionalProperties: false,
|
|
269
|
+
anyOf: [
|
|
270
|
+
{
|
|
271
|
+
type: 'object',
|
|
272
|
+
properties: {
|
|
273
|
+
path: { type: 'string' },
|
|
274
|
+
old_string: { type: 'string' },
|
|
275
|
+
new_string: { type: 'string' },
|
|
276
|
+
},
|
|
277
|
+
required: ['path', 'old_string', 'new_string'],
|
|
278
|
+
additionalProperties: false,
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
}),
|
|
283
|
+
],
|
|
284
|
+
'deferred',
|
|
285
|
+
)
|
|
286
|
+
expect(r.searchDeferred('old_string').map((t) => t.name)).toEqual(['edit_document'])
|
|
287
|
+
expect(r.searchDeferred('oldStr')).toEqual([])
|
|
288
|
+
})
|
|
289
|
+
|
|
240
290
|
it('stops generic CRUD verbs so they cannot activate catalog slices', () => {
|
|
241
291
|
const r = new ToolRegistry()
|
|
242
292
|
r.register(
|
|
243
|
-
[
|
|
293
|
+
[
|
|
294
|
+
makeTool('list_deals', {
|
|
295
|
+
description: 'List the deals in an account.',
|
|
296
|
+
}),
|
|
297
|
+
],
|
|
244
298
|
'deferred',
|
|
245
299
|
)
|
|
246
300
|
r.register([makeTool('list_workflows', { description: 'List workflows.' })], 'deferred')
|
|
@@ -370,6 +424,34 @@ describe('ToolRegistry — toPromptSection + toLLMTools', () => {
|
|
|
370
424
|
expect(names).toEqual(['a', 'c'])
|
|
371
425
|
})
|
|
372
426
|
|
|
427
|
+
it('toLLMTools: prefers a canonical model schema over runtime compatibility aliases', () => {
|
|
428
|
+
const r = new ToolRegistry()
|
|
429
|
+
const modelInputSchema = {
|
|
430
|
+
type: 'object',
|
|
431
|
+
properties: { new_string: { type: 'string' } },
|
|
432
|
+
required: ['new_string'],
|
|
433
|
+
additionalProperties: false,
|
|
434
|
+
}
|
|
435
|
+
r.register(
|
|
436
|
+
makeTool('edit', {
|
|
437
|
+
inputSchema: z.object({
|
|
438
|
+
new_string: z.string().optional(),
|
|
439
|
+
newStr: z.string().optional(),
|
|
440
|
+
}),
|
|
441
|
+
modelInputSchema,
|
|
442
|
+
}),
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
const firstSchema = r.toLLMTools()[0]?.function.parameters
|
|
446
|
+
expect(firstSchema).toEqual(modelInputSchema)
|
|
447
|
+
expect(firstSchema).not.toBe(modelInputSchema)
|
|
448
|
+
;(firstSchema?.properties as Record<string, unknown>).newStr = { type: 'string' }
|
|
449
|
+
|
|
450
|
+
const nextSchema = r.toLLMTools()[0]?.function.parameters
|
|
451
|
+
expect(nextSchema).toEqual(modelInputSchema)
|
|
452
|
+
expect(JSON.stringify(nextSchema)).not.toContain('newStr')
|
|
453
|
+
})
|
|
454
|
+
|
|
373
455
|
it('toLLMTools: prefixes description with tier label when labelInDescription is true', () => {
|
|
374
456
|
const tierConfig: ToolTierConfig = {
|
|
375
457
|
tiers: [{ id: 'safe', label: 'Safe', priority: 1 }],
|
|
@@ -398,7 +480,11 @@ describe('ToolRegistry — execute', () => {
|
|
|
398
480
|
'write',
|
|
399
481
|
{},
|
|
400
482
|
makeContext({
|
|
401
|
-
permissionContext: {
|
|
483
|
+
permissionContext: {
|
|
484
|
+
mode: 'plan',
|
|
485
|
+
runId: 'run_1',
|
|
486
|
+
workingDirectory: '/tmp',
|
|
487
|
+
},
|
|
402
488
|
}),
|
|
403
489
|
)
|
|
404
490
|
expect(result.success).toBe(false)
|
|
@@ -413,7 +499,11 @@ describe('ToolRegistry — execute', () => {
|
|
|
413
499
|
'read',
|
|
414
500
|
{},
|
|
415
501
|
makeContext({
|
|
416
|
-
permissionContext: {
|
|
502
|
+
permissionContext: {
|
|
503
|
+
mode: 'plan',
|
|
504
|
+
runId: 'run_1',
|
|
505
|
+
workingDirectory: '/tmp',
|
|
506
|
+
},
|
|
417
507
|
}),
|
|
418
508
|
)
|
|
419
509
|
expect(result.success).toBe(true)
|
|
@@ -433,11 +523,28 @@ describe('ToolRegistry — execute', () => {
|
|
|
433
523
|
expect(result.error).toContain('Expected string, received number')
|
|
434
524
|
})
|
|
435
525
|
|
|
526
|
+
it('appends a tool-specific recovery hint to validation failures', async () => {
|
|
527
|
+
const r = new ToolRegistry()
|
|
528
|
+
r.register(
|
|
529
|
+
makeTool('strict', {
|
|
530
|
+
inputSchema: z.object({ required: z.string() }),
|
|
531
|
+
validationErrorHint: 'Retry with {"required":"value"}.',
|
|
532
|
+
}),
|
|
533
|
+
)
|
|
534
|
+
const result = await r.execute('strict', { required: 123 }, makeContext())
|
|
535
|
+
expect(result.success).toBe(false)
|
|
536
|
+
expect(result.error).toContain('Required: required: string.')
|
|
537
|
+
expect(result.error).toContain('Retry with {"required":"value"}.')
|
|
538
|
+
})
|
|
539
|
+
|
|
436
540
|
it('empty-args validation lists required params with descriptions', async () => {
|
|
437
541
|
const r = new ToolRegistry()
|
|
438
542
|
r.register(
|
|
439
543
|
makeTool('needs', {
|
|
440
|
-
inputSchema: z.object({
|
|
544
|
+
inputSchema: z.object({
|
|
545
|
+
q: z.string().describe('the query'),
|
|
546
|
+
n: z.number(),
|
|
547
|
+
}),
|
|
441
548
|
}),
|
|
442
549
|
)
|
|
443
550
|
const result = await r.execute('needs', {}, makeContext())
|
|
@@ -458,7 +565,10 @@ describe('ToolRegistry — execute', () => {
|
|
|
458
565
|
it('validation hint tolerates a schema it cannot introspect', async () => {
|
|
459
566
|
const r = new ToolRegistry()
|
|
460
567
|
const bogusSchema = {
|
|
461
|
-
safeParse: () => ({
|
|
568
|
+
safeParse: () => ({
|
|
569
|
+
success: false,
|
|
570
|
+
error: { issues: [{ path: [], message: 'nope' }] },
|
|
571
|
+
}),
|
|
462
572
|
}
|
|
463
573
|
r.register(makeTool('weird', { inputSchema: bogusSchema as never }))
|
|
464
574
|
const result = await r.execute('weird', { a: 1 }, makeContext())
|
|
@@ -516,7 +626,11 @@ describe('ToolRegistry — execute', () => {
|
|
|
516
626
|
'mutate',
|
|
517
627
|
{},
|
|
518
628
|
makeContext({
|
|
519
|
-
permissionContext: {
|
|
629
|
+
permissionContext: {
|
|
630
|
+
mode: 'plan',
|
|
631
|
+
runId: 'run_1',
|
|
632
|
+
workingDirectory: '/tmp',
|
|
633
|
+
},
|
|
520
634
|
}),
|
|
521
635
|
)
|
|
522
636
|
expect(result.success).toBe(false)
|