@namzu/sdk 37.0.0 → 38.1.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 +86 -0
- package/dist/agents/ReactiveAgent.d.ts.map +1 -1
- package/dist/agents/ReactiveAgent.js +1 -0
- package/dist/agents/ReactiveAgent.js.map +1 -1
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +2 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +16 -1
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/config/runtime.d.ts +16 -6
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +2 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/contracts/api.d.ts +1 -1
- package/dist/contracts/api.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +14 -9
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/plan/lifecycle.d.ts +1 -1
- package/dist/manager/plan/lifecycle.d.ts.map +1 -1
- package/dist/manager/plan/lifecycle.js +14 -12
- package/dist/manager/plan/lifecycle.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +2 -2
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +11 -5
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/provider/capabilities.d.ts +2 -0
- package/dist/provider/capabilities.d.ts.map +1 -1
- package/dist/provider/capabilities.js +14 -0
- package/dist/provider/capabilities.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +7 -3
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/idle-timeout.d.ts.map +1 -1
- package/dist/provider/idle-timeout.js +5 -0
- package/dist/provider/idle-timeout.js.map +1 -1
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +5 -0
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +1 -0
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +1 -0
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +1 -0
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +1 -0
- package/dist/public-tools.js.map +1 -1
- package/dist/public-types.d.ts +1 -0
- package/dist/public-types.d.ts.map +1 -1
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +1 -0
- package/dist/run/reporter.js.map +1 -1
- package/dist/run/token-budget.d.ts +10 -4
- package/dist/run/token-budget.d.ts.map +1 -1
- package/dist/run/token-budget.js +45 -14
- package/dist/run/token-budget.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +3 -3
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +24 -16
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +2 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +1 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +2 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +23 -2
- package/dist/runtime/query/iteration/index.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 -1
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/observation-context.d.ts +10 -0
- package/dist/runtime/query/observation-context.d.ts.map +1 -0
- package/dist/runtime/query/observation-context.js +84 -0
- package/dist/runtime/query/observation-context.js.map +1 -0
- package/dist/runtime/query/request-context.d.ts +30 -0
- package/dist/runtime/query/request-context.d.ts.map +1 -0
- package/dist/runtime/query/request-context.js +76 -0
- package/dist/runtime/query/request-context.js.map +1 -0
- package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
- package/dist/runtime/query/request-rich-content.js +6 -3
- package/dist/runtime/query/request-rich-content.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts +3 -0
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +4 -1
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +1 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +1 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/sandbox/file-walk-program.d.ts.map +1 -1
- package/dist/sandbox/file-walk-program.js +8 -0
- package/dist/sandbox/file-walk-program.js.map +1 -1
- package/dist/sandbox/file-walk.d.ts.map +1 -1
- package/dist/sandbox/file-walk.js +9 -0
- package/dist/sandbox/file-walk.js.map +1 -1
- package/dist/scheduler/completion-inbox.d.ts.map +1 -1
- package/dist/scheduler/completion-inbox.js +3 -0
- package/dist/scheduler/completion-inbox.js.map +1 -1
- package/dist/scheduler/local.d.ts.map +1 -1
- package/dist/scheduler/local.js +2 -0
- package/dist/scheduler/local.js.map +1 -1
- package/dist/store/run/token-budget-disk.js +3 -0
- package/dist/store/run/token-budget-disk.js.map +1 -1
- package/dist/testing.d.ts +1 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +1 -0
- package/dist/testing.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +12 -26
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/file-write-result.d.ts +4 -0
- package/dist/tools/builtins/file-write-result.d.ts.map +1 -0
- package/dist/tools/builtins/file-write-result.js +72 -0
- package/dist/tools/builtins/file-write-result.js.map +1 -0
- package/dist/tools/builtins/glob.d.ts.map +1 -1
- package/dist/tools/builtins/glob.js +8 -1
- package/dist/tools/builtins/glob.js.map +1 -1
- package/dist/tools/builtins/grep.d.ts.map +1 -1
- package/dist/tools/builtins/grep.js +10 -0
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/job.d.ts.map +1 -1
- package/dist/tools/builtins/job.js +6 -0
- package/dist/tools/builtins/job.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +10 -0
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/builtins/write-file.d.ts.map +1 -1
- package/dist/tools/builtins/write-file.js +30 -44
- package/dist/tools/builtins/write-file.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +3 -0
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/file-read-tracker.d.ts +4 -0
- package/dist/tools/file-read-tracker.d.ts.map +1 -0
- package/dist/tools/file-read-tracker.js +16 -0
- package/dist/tools/file-read-tracker.js.map +1 -0
- package/dist/tools/task/list.js +2 -2
- package/dist/tools/task/list.js.map +1 -1
- package/dist/types/agent/reactive.d.ts +4 -0
- package/dist/types/agent/reactive.d.ts.map +1 -1
- package/dist/types/agent/scheduler.d.ts +3 -0
- package/dist/types/agent/scheduler.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +3 -0
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/plugin/index.d.ts +7 -0
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js.map +1 -1
- package/dist/types/provider/chat.d.ts +4 -0
- package/dist/types/provider/chat.d.ts.map +1 -1
- package/dist/types/provider/config.d.ts +2 -0
- package/dist/types/provider/config.d.ts.map +1 -1
- package/dist/types/provider/interface.d.ts +2 -0
- package/dist/types/provider/interface.d.ts.map +1 -1
- package/dist/types/provider/stream.d.ts +6 -0
- package/dist/types/provider/stream.d.ts.map +1 -1
- package/dist/types/run/config.d.ts +2 -0
- package/dist/types/run/config.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +13 -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 +37 -3
- package/dist/types/sandbox/index.d.ts.map +1 -1
- package/dist/types/sandbox/index.js.map +1 -1
- package/dist/types/tool/presentation.d.ts +4 -0
- package/dist/types/tool/presentation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agents/ReactiveAgent.ts +1 -0
- package/src/bridge/a2a/mapper.ts +2 -0
- package/src/bridge/sse/mapper.ts +16 -1
- package/src/config/runtime.ts +2 -0
- package/src/contracts/api.ts +1 -0
- package/src/manager/agent/lifecycle.ts +16 -9
- package/src/manager/plan/lifecycle.ts +15 -13
- package/src/prompt/coding-agent-doctrine.ts +11 -5
- package/src/provider/capabilities.ts +21 -0
- package/src/provider/fallback.ts +14 -3
- package/src/provider/idle-timeout.ts +6 -0
- package/src/provider/retry.ts +6 -0
- package/src/public-runtime.ts +2 -0
- package/src/public-tools.ts +2 -0
- package/src/public-types.ts +6 -0
- package/src/run/reporter.ts +1 -0
- package/src/run/token-budget.ts +51 -14
- package/src/runtime/query/executor.ts +23 -15
- package/src/runtime/query/index.ts +3 -0
- package/src/runtime/query/iteration/index.ts +28 -2
- package/src/runtime/query/iteration/stream-turn.ts +14 -1
- package/src/runtime/query/observation-context.ts +86 -0
- package/src/runtime/query/request-context.ts +103 -0
- package/src/runtime/query/request-rich-content.ts +8 -3
- package/src/runtime/query/review-policy.ts +7 -1
- package/src/runtime/query/tooling.ts +2 -0
- package/src/sandbox/file-walk-program.ts +8 -0
- package/src/sandbox/file-walk.ts +9 -0
- package/src/scheduler/completion-inbox.ts +3 -0
- package/src/scheduler/local.ts +2 -0
- package/src/store/run/token-budget-disk.ts +3 -0
- package/src/testing.ts +2 -0
- package/src/tools/builtins/edit.ts +12 -27
- package/src/tools/builtins/file-write-result.ts +84 -0
- package/src/tools/builtins/glob.ts +7 -1
- package/src/tools/builtins/grep.ts +10 -0
- package/src/tools/builtins/job.ts +8 -0
- package/src/tools/builtins/read-file.ts +10 -0
- package/src/tools/builtins/write-file.ts +39 -44
- package/src/tools/coordinator/index.ts +3 -0
- package/src/tools/file-read-tracker.ts +16 -0
- package/src/tools/task/list.ts +2 -2
- package/src/types/agent/reactive.ts +2 -0
- package/src/types/agent/scheduler.ts +4 -0
- package/src/types/agent/task.ts +4 -0
- package/src/types/plugin/index.ts +10 -0
- package/src/types/provider/chat.ts +2 -0
- package/src/types/provider/config.ts +2 -0
- package/src/types/provider/interface.ts +3 -0
- package/src/types/provider/stream.ts +7 -0
- package/src/types/run/config.ts +2 -0
- package/src/types/run/events.ts +13 -0
- package/src/types/sandbox/index.ts +43 -4
- package/src/types/tool/presentation.ts +4 -0
package/src/provider/fallback.ts
CHANGED
|
@@ -31,7 +31,10 @@ import type {
|
|
|
31
31
|
ThinkingConfig,
|
|
32
32
|
} from '../types/provider/index.js'
|
|
33
33
|
import type { Logger } from '../utils/logger.js'
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
assertHostedWebSearchSupported,
|
|
36
|
+
assertNativeStructuredOutputSupported,
|
|
37
|
+
} from './capabilities.js'
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
40
|
* One member of the chain: a constructed provider, and the model to ask it for.
|
|
@@ -210,13 +213,14 @@ function shouldFallOver(err: unknown, providerId: string): boolean {
|
|
|
210
213
|
*/
|
|
211
214
|
function isOutputChunk(chunk: StreamChunk): boolean {
|
|
212
215
|
if (chunk.retry !== undefined || chunk.fallback !== undefined) return false
|
|
213
|
-
const { content, toolCalls, toolCallEnd, reasoning, citation } = chunk.delta
|
|
216
|
+
const { content, toolCalls, toolCallEnd, reasoning, citation, hostedTool } = chunk.delta
|
|
214
217
|
return Boolean(
|
|
215
218
|
content ||
|
|
216
219
|
toolCalls?.length ||
|
|
217
220
|
toolCallEnd ||
|
|
218
221
|
reasoning !== undefined ||
|
|
219
|
-
citation !== undefined
|
|
222
|
+
citation !== undefined ||
|
|
223
|
+
hostedTool !== undefined,
|
|
220
224
|
)
|
|
221
225
|
}
|
|
222
226
|
|
|
@@ -398,6 +402,7 @@ export function withProviderFallback(
|
|
|
398
402
|
},
|
|
399
403
|
}
|
|
400
404
|
assertNativeStructuredOutputSupported(member.provider, request)
|
|
405
|
+
assertHostedWebSearchSupported(member.provider, request)
|
|
401
406
|
let produced = false
|
|
402
407
|
try {
|
|
403
408
|
for await (const chunk of member.provider.chatStream(request)) {
|
|
@@ -500,6 +505,12 @@ export function withProviderFallback(
|
|
|
500
505
|
// the request that may actually traverse the whole chain. The method is
|
|
501
506
|
// always present on a multi-member wrapper: `undefined` is its honest
|
|
502
507
|
// answer when any member cannot enumerate its selected model.
|
|
508
|
+
supportsHostedWebSearchFor: (model: string, mode: 'live' | 'cached') =>
|
|
509
|
+
members.every(
|
|
510
|
+
(member) =>
|
|
511
|
+
member.provider.capabilities?.supportsHostedWebSearch === true &&
|
|
512
|
+
(member.provider.supportsHostedWebSearchFor?.(member.model ?? model, mode) ?? true),
|
|
513
|
+
),
|
|
503
514
|
reasoningEffortLevelsFor: (model: string, thinking?: ThinkingConfig) =>
|
|
504
515
|
commonReasoningEffortLevels(members, model, thinking),
|
|
505
516
|
// Present on the chain wrapper for the same reason the menu method is:
|
|
@@ -212,6 +212,12 @@ export function withStreamIdleTimeout(
|
|
|
212
212
|
...(provider.doctorCheck
|
|
213
213
|
? { doctorCheck: (model?: string) => provider.doctorCheck?.(model) }
|
|
214
214
|
: {}),
|
|
215
|
+
...(provider.supportsHostedWebSearchFor
|
|
216
|
+
? {
|
|
217
|
+
supportsHostedWebSearchFor: (model: string, mode: 'live' | 'cached') =>
|
|
218
|
+
provider.supportsHostedWebSearchFor?.(model, mode) ?? false,
|
|
219
|
+
}
|
|
220
|
+
: {}),
|
|
215
221
|
...(provider.reasoningEffortLevelsFor
|
|
216
222
|
? {
|
|
217
223
|
reasoningEffortLevelsFor: (
|
package/src/provider/retry.ts
CHANGED
|
@@ -270,6 +270,12 @@ export function withProviderRetry(
|
|
|
270
270
|
...(provider.doctorCheck
|
|
271
271
|
? { doctorCheck: (model?: string) => provider.doctorCheck?.(model) }
|
|
272
272
|
: {}),
|
|
273
|
+
...(provider.supportsHostedWebSearchFor
|
|
274
|
+
? {
|
|
275
|
+
supportsHostedWebSearchFor: (model: string, mode: 'live' | 'cached') =>
|
|
276
|
+
provider.supportsHostedWebSearchFor?.(model, mode) ?? false,
|
|
277
|
+
}
|
|
278
|
+
: {}),
|
|
273
279
|
...(provider.reasoningEffortLevelsFor
|
|
274
280
|
? {
|
|
275
281
|
reasoningEffortLevelsFor: (
|
package/src/public-runtime.ts
CHANGED
|
@@ -1339,3 +1339,5 @@ export { WORKING_STATE_MIME } from './connector/mcp/adapter.js'
|
|
|
1339
1339
|
export { TokenBudget, validateTokenBudgetSnapshot } from './run/token-budget.js'
|
|
1340
1340
|
export { DiskTokenBudgetStore, openTokenBudget } from './store/run/token-budget-disk.js'
|
|
1341
1341
|
export { validateTokenBudgetBinding } from './types/run/token-budget-store.js'
|
|
1342
|
+
|
|
1343
|
+
export { snapshotRequestContext, diffRequestContext } from './runtime/query/request-context.js'
|
package/src/public-tools.ts
CHANGED
package/src/public-types.ts
CHANGED
|
@@ -439,3 +439,9 @@ export type {
|
|
|
439
439
|
DiskTokenBudgetStoreConfig,
|
|
440
440
|
OpenTokenBudgetOptions,
|
|
441
441
|
} from './store/run/token-budget-disk.js'
|
|
442
|
+
|
|
443
|
+
export type {
|
|
444
|
+
RequestContextPart,
|
|
445
|
+
RequestContextSnapshot,
|
|
446
|
+
RequestContextChange,
|
|
447
|
+
} from './runtime/query/request-context.js'
|
package/src/run/reporter.ts
CHANGED
package/src/run/token-budget.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface TokenBudgetRequestSnapshot {
|
|
|
21
21
|
runId: RunId
|
|
22
22
|
/** Cumulative request usage already charged to the owning run. */
|
|
23
23
|
usage?: TokenUsage
|
|
24
|
+
/** No reliable final receipt; only explicit reconciliation clears this marker. */
|
|
25
|
+
unresolved?: boolean
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
/** Durable authority independent of a message checkpoint's cadence. */
|
|
@@ -49,8 +51,10 @@ export interface TokenBudgetSummary {
|
|
|
49
51
|
remainingTokens: number | null
|
|
50
52
|
inFlightRequests: number
|
|
51
53
|
unsettledChildren: number
|
|
52
|
-
/**
|
|
54
|
+
/** This account is blocked by accounting failure or uncertain finite allowance. */
|
|
53
55
|
poisoned: boolean
|
|
56
|
+
/** Unresolved receipts in this subtree, including retained completion records. */
|
|
57
|
+
unresolvedRequests?: number
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
interface SharedLedger {
|
|
@@ -150,9 +154,6 @@ export function validateTokenBudgetSnapshot(value: unknown): TokenBudgetSnapshot
|
|
|
150
154
|
const accountId = id(account.id, 'account id')
|
|
151
155
|
if (typeof account.settled !== 'boolean') throw new Error('Invalid token budget settled flag')
|
|
152
156
|
const limit = count(account.limit, 'limit')
|
|
153
|
-
if (accountId !== rootAccountId && limit === 0) {
|
|
154
|
-
throw new Error('A child token budget must have a positive limit')
|
|
155
|
-
}
|
|
156
157
|
const usage: Record<string, TokenUsage> = {}
|
|
157
158
|
for (const [runId, rawUsage] of Object.entries(object(account.usage, 'account usage'))) {
|
|
158
159
|
id(runId, 'usage run id')
|
|
@@ -198,6 +199,8 @@ export function validateTokenBudgetSnapshot(value: unknown): TokenBudgetSnapshot
|
|
|
198
199
|
const request = object(raw, 'request')
|
|
199
200
|
const requestId = id(request.id, 'request id')
|
|
200
201
|
const accountId = id(request.accountId, 'request account id')
|
|
202
|
+
if (request.unresolved !== undefined && typeof request.unresolved !== 'boolean')
|
|
203
|
+
throw new Error('Invalid unresolved request marker')
|
|
201
204
|
if (!isEntityId(request.runId, 'run')) throw new Error('Invalid token budget request run id')
|
|
202
205
|
if (requestIds.has(requestId)) throw new Error('Duplicate token budget request')
|
|
203
206
|
requestIds.add(requestId)
|
|
@@ -209,6 +212,7 @@ export function validateTokenBudgetSnapshot(value: unknown): TokenBudgetSnapshot
|
|
|
209
212
|
accountId,
|
|
210
213
|
runId: request.runId,
|
|
211
214
|
...(request.usage === undefined ? {} : { usage: usageValue(request.usage) }),
|
|
215
|
+
...(request.unresolved === true ? { unresolved: true } : {}),
|
|
212
216
|
}
|
|
213
217
|
}
|
|
214
218
|
const requests = source.requests.map(parseRequest)
|
|
@@ -346,9 +350,11 @@ export class TokenBudget {
|
|
|
346
350
|
rootAccountId: state.rootAccountId,
|
|
347
351
|
rootRunId: state.rootRunId,
|
|
348
352
|
accounts: new Map(state.accounts.map((account) => [account.id, account])),
|
|
349
|
-
requests: new Map(
|
|
353
|
+
requests: new Map(
|
|
354
|
+
state.requests.map((request) => [request.id, { ...request, unresolved: true }]),
|
|
355
|
+
),
|
|
350
356
|
finishedRequests: new Map(state.completedRequests.map((request) => [request.id, request])),
|
|
351
|
-
poisoned: state.poisoned === true
|
|
357
|
+
poisoned: state.poisoned === true,
|
|
352
358
|
persistence,
|
|
353
359
|
writes: Promise.resolve(),
|
|
354
360
|
},
|
|
@@ -384,7 +390,7 @@ export class TokenBudget {
|
|
|
384
390
|
}
|
|
385
391
|
|
|
386
392
|
get remaining(): number {
|
|
387
|
-
if (this.
|
|
393
|
+
if (this.admissionBlocked() || this.node.settled) return 0
|
|
388
394
|
let cursor = this.node
|
|
389
395
|
let available = this.free(cursor)
|
|
390
396
|
while (cursor.parentId !== undefined) {
|
|
@@ -430,7 +436,7 @@ export class TokenBudget {
|
|
|
430
436
|
reserve(tokens: number): TokenBudget {
|
|
431
437
|
count(tokens, 'reservation')
|
|
432
438
|
if (this.hasRequest(this.node)) throw new Error('Token budget has an in-flight request')
|
|
433
|
-
if (tokens === 0 || tokens > this.remaining)
|
|
439
|
+
if ((tokens === 0 && this.remaining !== Number.POSITIVE_INFINITY) || tokens > this.remaining)
|
|
434
440
|
throw new Error('Token budget cannot reserve the requested tokens')
|
|
435
441
|
count(this.totals(this.node).reserved + tokens, 'reserved allowance')
|
|
436
442
|
const accountId = randomUUID()
|
|
@@ -468,7 +474,8 @@ export class TokenBudget {
|
|
|
468
474
|
}
|
|
469
475
|
|
|
470
476
|
async beginRequest(): Promise<string> {
|
|
471
|
-
if (this.
|
|
477
|
+
if (this.admissionBlocked())
|
|
478
|
+
throw new Error('Token budget has unresolved spend or an accounting failure')
|
|
472
479
|
if (this.node.settled) throw new Error('Token budget is settled')
|
|
473
480
|
if (this.remaining <= 0) throw new Error('Token budget is exhausted')
|
|
474
481
|
const runId = this.runId
|
|
@@ -504,10 +511,11 @@ export class TokenBudget {
|
|
|
504
511
|
async failRequest(requestId: string, usage?: TokenUsage): Promise<void> {
|
|
505
512
|
if (this.hasReceipt(requestId)) return this.flush()
|
|
506
513
|
const request = this.requireRequest(requestId)
|
|
507
|
-
|
|
514
|
+
request.unresolved = true
|
|
508
515
|
try {
|
|
509
516
|
if (usage !== undefined) this.recordRequestUsage(request, usage)
|
|
510
517
|
} catch (error) {
|
|
518
|
+
this.ledger.poisoned = true
|
|
511
519
|
this.persist()
|
|
512
520
|
await this.flush()
|
|
513
521
|
throw error
|
|
@@ -518,9 +526,9 @@ export class TokenBudget {
|
|
|
518
526
|
|
|
519
527
|
/**
|
|
520
528
|
* Explicit host recovery after obtaining the provider's final usage receipt.
|
|
521
|
-
* Ordinary completion never
|
|
522
|
-
*
|
|
523
|
-
*
|
|
529
|
+
* Ordinary completion never clears uncertainty. Reconciliation resolves this
|
|
530
|
+
* request after its receipt is persisted; other uncertain requests continue
|
|
531
|
+
* to constrain their shared finite allowances. Usage and settlement are retained.
|
|
524
532
|
*/
|
|
525
533
|
async reconcileRequest(requestId: string, usage: TokenUsage): Promise<void> {
|
|
526
534
|
const measured = usageValue(usage)
|
|
@@ -536,6 +544,10 @@ export class TokenBudget {
|
|
|
536
544
|
} else {
|
|
537
545
|
await this.finishRequest(requestId, measured)
|
|
538
546
|
}
|
|
547
|
+
const receipt = this.ledger.finishedRequests.get(requestId)
|
|
548
|
+
if (receipt) receipt.unresolved = false
|
|
549
|
+
this.persist()
|
|
550
|
+
await this.flush()
|
|
539
551
|
if (this.ledger.requests.size === 0 && this.ledger.persistenceError === undefined) {
|
|
540
552
|
this.ledger.poisoned = false
|
|
541
553
|
this.persist()
|
|
@@ -574,7 +586,10 @@ export class TokenBudget {
|
|
|
574
586
|
).length,
|
|
575
587
|
unsettledChildren: this.children(this.node).filter((child) => !this.totals(child).closed)
|
|
576
588
|
.length,
|
|
577
|
-
poisoned: this.
|
|
589
|
+
poisoned: this.admissionBlocked(),
|
|
590
|
+
unresolvedRequests: this.uncertainRequests().filter((request) =>
|
|
591
|
+
descendants.has(request.accountId),
|
|
592
|
+
).length,
|
|
578
593
|
}
|
|
579
594
|
}
|
|
580
595
|
|
|
@@ -595,6 +610,28 @@ export class TokenBudget {
|
|
|
595
610
|
if (this.ledger.persistenceError !== undefined) throw this.ledger.persistenceError
|
|
596
611
|
}
|
|
597
612
|
|
|
613
|
+
private uncertainRequests(): TokenBudgetRequestSnapshot[] {
|
|
614
|
+
return [...this.ledger.requests.values(), ...this.ledger.finishedRequests.values()].filter(
|
|
615
|
+
(request) => request.unresolved === true,
|
|
616
|
+
)
|
|
617
|
+
}
|
|
618
|
+
private admissionBlocked(): boolean {
|
|
619
|
+
if (this.ledger.poisoned || this.ledger.persistenceError !== undefined) return true
|
|
620
|
+
const uncertain = this.uncertainRequests()
|
|
621
|
+
if (uncertain.length === 0) return false
|
|
622
|
+
// An account cannot resume its own uncertain request, even without a cap.
|
|
623
|
+
if (uncertain.some((request) => request.accountId === this.accountId)) return true
|
|
624
|
+
let cursor = this.node
|
|
625
|
+
for (;;) {
|
|
626
|
+
if (cursor.limit > 0) {
|
|
627
|
+
const descendants = this.descendants(cursor)
|
|
628
|
+
if (uncertain.some((request) => descendants.has(request.accountId))) return true
|
|
629
|
+
}
|
|
630
|
+
if (cursor.parentId === undefined) return false
|
|
631
|
+
cursor = this.requireAccount(cursor.parentId)
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
598
635
|
private get node(): TokenBudgetAccountSnapshot {
|
|
599
636
|
return this.requireAccount(this.accountId)
|
|
600
637
|
}
|
|
@@ -11,8 +11,8 @@ import { probe as defaultProbeRegistry } from '../../probe/registry.js'
|
|
|
11
11
|
import type { ProbeEnforcement } from '../../probe/registry.js'
|
|
12
12
|
import { renderToolSchema } from '../../registry/tool/schema.js'
|
|
13
13
|
import type { ActivityStore } from '../../store/activity/memory.js'
|
|
14
|
-
import { fingerprintContent } from '../../tools/builtins/content-fingerprint.js'
|
|
15
14
|
import { SKILL_TOOL_NAME } from '../../tools/builtins/skill.js'
|
|
15
|
+
import { createFileReadTracker } from '../../tools/file-read-tracker.js'
|
|
16
16
|
import type { RunId, ToolUseId } from '../../types/ids/index.js'
|
|
17
17
|
import type { InvocationState } from '../../types/invocation/index.js'
|
|
18
18
|
import {
|
|
@@ -290,6 +290,7 @@ function parseArguments(raw: string): unknown {
|
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
export interface ToolExecutorConfig {
|
|
293
|
+
fileReadTracker?: FileReadTracker
|
|
293
294
|
tools: ToolRegistryContract
|
|
294
295
|
runId: RunId
|
|
295
296
|
workingDirectory: string
|
|
@@ -501,20 +502,7 @@ export class ToolExecutor {
|
|
|
501
502
|
private readonly preparedBatches = new WeakSet<PreparedToolBatch>()
|
|
502
503
|
/** Set per turn by the orchestrator; see {@link setStepAllowedTools}. */
|
|
503
504
|
private stepAllowedTools?: readonly string[]
|
|
504
|
-
private readonly
|
|
505
|
-
private readonly readFingerprints: Map<string, string> = new Map()
|
|
506
|
-
private readonly fileReadTracker: FileReadTracker = {
|
|
507
|
-
recordRead: (key: string, content?: string) => {
|
|
508
|
-
this.readPaths.add(key)
|
|
509
|
-
// Only when the reader had the body. A tool that records a read
|
|
510
|
-
// without one leaves the previous fingerprint alone rather than
|
|
511
|
-
// clearing it, so a later write is still checked against the last
|
|
512
|
-
// body anyone actually saw.
|
|
513
|
-
if (content !== undefined) this.readFingerprints.set(key, fingerprintContent(content))
|
|
514
|
-
},
|
|
515
|
-
hasRead: (key: string) => this.readPaths.has(key),
|
|
516
|
-
fingerprint: (key: string) => this.readFingerprints.get(key),
|
|
517
|
-
}
|
|
505
|
+
private readonly fileReadTracker: FileReadTracker
|
|
518
506
|
|
|
519
507
|
constructor(
|
|
520
508
|
config: ToolExecutorConfig,
|
|
@@ -532,6 +520,7 @@ export class ToolExecutor {
|
|
|
532
520
|
config.readToolCallBudgetEvents,
|
|
533
521
|
)
|
|
534
522
|
}
|
|
523
|
+
this.fileReadTracker = config.fileReadTracker ?? createFileReadTracker()
|
|
535
524
|
this.config = config
|
|
536
525
|
this.activityStore = activityStore
|
|
537
526
|
this.emitEvent = emitEvent
|
|
@@ -1211,6 +1200,7 @@ export class ToolExecutor {
|
|
|
1211
1200
|
toolUseId: nestedId,
|
|
1212
1201
|
toolName: name,
|
|
1213
1202
|
result: budgeted.output,
|
|
1203
|
+
...(!budgeted.truncated ? this.resultPresentation(name, preparedInput, result) : {}),
|
|
1214
1204
|
isError: !result.success,
|
|
1215
1205
|
durationMs: Date.now() - startedAt,
|
|
1216
1206
|
outputLength: budgeted.originalLength,
|
|
@@ -1230,6 +1220,21 @@ export class ToolExecutor {
|
|
|
1230
1220
|
return visibleResult
|
|
1231
1221
|
}
|
|
1232
1222
|
|
|
1223
|
+
private resultPresentation(name: string, input: unknown, result: ToolResult) {
|
|
1224
|
+
if (!result.success) return {}
|
|
1225
|
+
try {
|
|
1226
|
+
const view = this.config.tools.get(name)?.presentResult?.(input, result)
|
|
1227
|
+
if (view?.kind !== 'diff') return {}
|
|
1228
|
+
const serialized = JSON.stringify(view)
|
|
1229
|
+
if (serialized.length > (this.config.maxToolOutputChars ?? DEFAULT_MAX_TOOL_OUTPUT_CHARS))
|
|
1230
|
+
return {}
|
|
1231
|
+
return { presentation: view }
|
|
1232
|
+
} catch {
|
|
1233
|
+
// A presentation hook must not fail an already completed mutation.
|
|
1234
|
+
return {}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1233
1238
|
private buildToolContext(
|
|
1234
1239
|
recordObservation: (observation: ToolResultObservation) => void = () => {},
|
|
1235
1240
|
): ToolContext {
|
|
@@ -1721,6 +1726,9 @@ export class ToolExecutor {
|
|
|
1721
1726
|
toolUseId: toolCall.id,
|
|
1722
1727
|
toolName,
|
|
1723
1728
|
result: output,
|
|
1729
|
+
...(!postOverride && !budgeted.truncated && rawOutput === output
|
|
1730
|
+
? this.resultPresentation(toolName, input, result)
|
|
1731
|
+
: {}),
|
|
1724
1732
|
isError: effectiveIsError,
|
|
1725
1733
|
durationMs,
|
|
1726
1734
|
// Pre-truncation size, so a host can show "returned 2.1 MB" even
|
|
@@ -157,6 +157,8 @@ import { createToolPause } from './tool-pause.js'
|
|
|
157
157
|
import { ToolingBootstrap } from './tooling.js'
|
|
158
158
|
|
|
159
159
|
export interface QueryParams {
|
|
160
|
+
/** Share observations across turns of one conversation and filesystem; otherwise run-local. */
|
|
161
|
+
fileReadTracker?: import('../../types/tool/index.js').FileReadTracker
|
|
160
162
|
/** One account shared with the task scheduler and descendant runs. */
|
|
161
163
|
budget?: TokenBudget
|
|
162
164
|
/** Canonical tree ledger; defaults to disk beside the root run. */
|
|
@@ -1731,6 +1733,7 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
|
|
|
1731
1733
|
// registry would answer about whatever the last run configured.
|
|
1732
1734
|
...(params.skillRegistry ? { skills: params.skillRegistry } : {}),
|
|
1733
1735
|
...(params.web ? { web: params.web } : {}),
|
|
1736
|
+
...(params.fileReadTracker ? { fileReadTracker: params.fileReadTracker } : {}),
|
|
1734
1737
|
...(params.toolTimeoutMs !== undefined ? { toolTimeoutMs: params.toolTimeoutMs } : {}),
|
|
1735
1738
|
...(params.toolRetryBackoff !== undefined
|
|
1736
1739
|
? { toolRetryBackoff: params.toolRetryBackoff }
|
|
@@ -54,7 +54,13 @@ import { toErrorMessage } from '../../../utils/error.js'
|
|
|
54
54
|
import { stableDigest } from '../../../utils/hash.js'
|
|
55
55
|
import { generateMessageId } from '../../../utils/id.js'
|
|
56
56
|
import type { ToolCallOutcome } from '../executor.js'
|
|
57
|
+
import { projectObservationContext } from '../observation-context.js'
|
|
57
58
|
import { applyLifecycleHookResults } from '../plugin-hooks.js'
|
|
59
|
+
import {
|
|
60
|
+
type RequestContextSnapshot,
|
|
61
|
+
diffRequestContext,
|
|
62
|
+
snapshotRequestContext,
|
|
63
|
+
} from '../request-context.js'
|
|
58
64
|
import {
|
|
59
65
|
DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
|
|
60
66
|
type RequestImageIdentity,
|
|
@@ -164,6 +170,14 @@ export class IterationOrchestrator {
|
|
|
164
170
|
* one process must not suppress each other's first envelope.
|
|
165
171
|
*/
|
|
166
172
|
private lastEnvelopeKey: string | undefined
|
|
173
|
+
private previousRequestContext: RequestContextSnapshot | undefined
|
|
174
|
+
|
|
175
|
+
private projectObservations(messages: Message[]): Message[] {
|
|
176
|
+
const config = this.ctx.compactionConfig
|
|
177
|
+
return config && config.strategy !== 'disabled' && config.deduplicateObservations !== false
|
|
178
|
+
? projectObservationContext(messages, this.ctx.tools, config.preserveToolResultsFrom)
|
|
179
|
+
: messages
|
|
180
|
+
}
|
|
167
181
|
/** Rich tool blocks already reported; durable history is scanned every turn. */
|
|
168
182
|
private readonly warnedRichToolResults = new Set<string>()
|
|
169
183
|
/**
|
|
@@ -614,7 +628,7 @@ export class IterationOrchestrator {
|
|
|
614
628
|
? [...baseMessages, createSystemMessage(stepPreamble)]
|
|
615
629
|
: [...baseMessages]
|
|
616
630
|
const messages = projectRequestRichContent(
|
|
617
|
-
requestHistory,
|
|
631
|
+
this.projectObservations(requestHistory),
|
|
618
632
|
this.ctx.runConfig.maxRequestRichContentBytes ?? DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
|
|
619
633
|
)
|
|
620
634
|
await this.reportUnsupportedToolResults(messages)
|
|
@@ -664,6 +678,16 @@ export class IterationOrchestrator {
|
|
|
664
678
|
}
|
|
665
679
|
|
|
666
680
|
if (this.ctx.pluginManager) {
|
|
681
|
+
const snapshot = snapshotRequestContext(messages)
|
|
682
|
+
const context = Object.freeze({
|
|
683
|
+
snapshot,
|
|
684
|
+
...(this.previousRequestContext
|
|
685
|
+
? {
|
|
686
|
+
change: diffRequestContext(this.previousRequestContext, snapshot),
|
|
687
|
+
}
|
|
688
|
+
: {}),
|
|
689
|
+
})
|
|
690
|
+
this.previousRequestContext = snapshot
|
|
667
691
|
const hookResults = await this.ctx.pluginManager.executeHooks(
|
|
668
692
|
'pre_llm_call',
|
|
669
693
|
{
|
|
@@ -673,6 +697,7 @@ export class IterationOrchestrator {
|
|
|
673
697
|
// Built inside the guard: a run with no plugins installed
|
|
674
698
|
// pays nothing for a projection nobody reads.
|
|
675
699
|
request: Object.freeze({
|
|
700
|
+
context,
|
|
676
701
|
model: stepModel,
|
|
677
702
|
// Copied per turn, not handed over live: these are the
|
|
678
703
|
// run's own message objects, and a hook writing into
|
|
@@ -752,6 +777,7 @@ export class IterationOrchestrator {
|
|
|
752
777
|
cacheControl: { type: 'auto' },
|
|
753
778
|
...(runConfig.thinking ? { thinking: runConfig.thinking } : {}),
|
|
754
779
|
...(runConfig.effort ? { effort: runConfig.effort } : {}),
|
|
780
|
+
...(!forceFinalize && runConfig.webSearch ? { webSearch: runConfig.webSearch } : {}),
|
|
755
781
|
// Thread the run abort into the model call so a Stop tears the
|
|
756
782
|
// in-flight turn down (provider passes it to fetch; the consumer
|
|
757
783
|
// also races it). Inert when never aborted.
|
|
@@ -2424,7 +2450,7 @@ export class IterationOrchestrator {
|
|
|
2424
2450
|
),
|
|
2425
2451
|
]
|
|
2426
2452
|
const finalMessages = projectRequestRichContent(
|
|
2427
|
-
finalHistory,
|
|
2453
|
+
this.projectObservations(finalHistory),
|
|
2428
2454
|
this.ctx.runConfig.maxRequestRichContentBytes ?? DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
|
|
2429
2455
|
)
|
|
2430
2456
|
await this.reportUnsupportedToolResults(finalMessages)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type Span, SpanStatusCode } from '@opentelemetry/api'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
assertHostedWebSearchSupported,
|
|
4
|
+
assertNativeStructuredOutputSupported,
|
|
5
|
+
} from '../../../provider/capabilities.js'
|
|
3
6
|
import { isProviderRequestError } from '../../../provider/errors.js'
|
|
4
7
|
import { GENAI, NAMZU, chatSpanName, parentContext } from '../../../telemetry/attributes.js'
|
|
5
8
|
import {
|
|
@@ -155,6 +158,7 @@ export async function* streamProviderTurn(
|
|
|
155
158
|
},
|
|
156
159
|
): AsyncGenerator<RunEvent, StreamingTurnResult> {
|
|
157
160
|
assertNativeStructuredOutputSupported(provider, params)
|
|
161
|
+
assertHostedWebSearchSupported(provider, params)
|
|
158
162
|
// The `chat {model}` span the GenAI conventions require. There was none:
|
|
159
163
|
// `chatSpanName` existed with zero call sites, so a trace carried no LLM
|
|
160
164
|
// latency at all and the token counts landed on the iteration span
|
|
@@ -340,6 +344,15 @@ export async function* streamProviderTurn(
|
|
|
340
344
|
recordTimeToFirstToken(params.model, Date.now() - callStartedAt)
|
|
341
345
|
}
|
|
342
346
|
|
|
347
|
+
if (chunk.delta.hostedTool) {
|
|
348
|
+
await emitEvent({
|
|
349
|
+
type: 'hosted_tool',
|
|
350
|
+
runId,
|
|
351
|
+
iteration,
|
|
352
|
+
tool: chunk.delta.hostedTool,
|
|
353
|
+
})
|
|
354
|
+
yield* drainPending()
|
|
355
|
+
}
|
|
343
356
|
if (chunk.delta.citation) citations.push(chunk.delta.citation)
|
|
344
357
|
|
|
345
358
|
const reasoning = chunk.delta.reasoning
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { findRetainedIndices } from '../../compaction/retention.js'
|
|
3
|
+
import { estimateMessageTokens } from '../../compaction/token-estimate.js'
|
|
4
|
+
import { isClearedToolResult } from '../../compaction/tool-result-editing.js'
|
|
5
|
+
import type { Message, ToolMessage } from '../../types/message/index.js'
|
|
6
|
+
import type { ToolRegistryContract } from '../../types/tool/index.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Request-only exact observation masking. Every reference targets a full result
|
|
10
|
+
* in THIS projection. Recompute from canonical history, never from yesterday's
|
|
11
|
+
* mask, so removing a former representative cannot leave a dangling reference.
|
|
12
|
+
* No tool executions are skipped, and equal output is not a freshness claim.
|
|
13
|
+
*/
|
|
14
|
+
export function projectObservationContext(
|
|
15
|
+
messages: Message[],
|
|
16
|
+
tools: Pick<ToolRegistryContract, 'get'>,
|
|
17
|
+
preserveToolResultsFrom: readonly string[] = [],
|
|
18
|
+
): Message[] {
|
|
19
|
+
const calls = new Map<string, { name: string; arguments: string } | null>()
|
|
20
|
+
for (const message of messages) {
|
|
21
|
+
if (message.role !== 'assistant') continue
|
|
22
|
+
for (const call of message.toolCalls ?? []) {
|
|
23
|
+
// Ambiguous IDs cannot prove which observation belongs to which call.
|
|
24
|
+
if (calls.has(call.id)) calls.set(call.id, null)
|
|
25
|
+
else calls.set(call.id, call.metadata?.inputTruncated ? null : call.function)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const resultCounts = new Map<string, number>()
|
|
29
|
+
for (const message of messages) {
|
|
30
|
+
if (message.role === 'tool') {
|
|
31
|
+
resultCounts.set(message.toolCallId, (resultCounts.get(message.toolCallId) ?? 0) + 1)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const protectedIndices = findRetainedIndices(messages)
|
|
35
|
+
const representatives = new Map<string, ToolMessage>()
|
|
36
|
+
let projected: Message[] | undefined
|
|
37
|
+
for (let index = 0; index < messages.length; index++) {
|
|
38
|
+
const message = messages[index]
|
|
39
|
+
if (
|
|
40
|
+
!message ||
|
|
41
|
+
message.role !== 'tool' ||
|
|
42
|
+
message.isError ||
|
|
43
|
+
typeof message.content !== 'string' ||
|
|
44
|
+
message.content.length < 1024 ||
|
|
45
|
+
isClearedToolResult(message.content) ||
|
|
46
|
+
resultCounts.get(message.toolCallId) !== 1
|
|
47
|
+
)
|
|
48
|
+
continue
|
|
49
|
+
const call = calls.get(message.toolCallId)
|
|
50
|
+
if (!call || preserveToolResultsFrom.includes(call.name)) continue
|
|
51
|
+
const definition = tools.get(call.name)
|
|
52
|
+
if (!definition) continue
|
|
53
|
+
try {
|
|
54
|
+
const input = definition.inputSchema.safeParse(JSON.parse(call.arguments))
|
|
55
|
+
if (
|
|
56
|
+
!input.success ||
|
|
57
|
+
definition.isReadOnly?.(input.data) !== true ||
|
|
58
|
+
definition.isDestructive?.(input.data) === true
|
|
59
|
+
)
|
|
60
|
+
continue
|
|
61
|
+
} catch {
|
|
62
|
+
// Classification is advisory; invalid input or a broken custom predicate
|
|
63
|
+
// must leave evidence intact, not abort the request.
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
// Exact arguments intentionally: equivalent JSON with different formatting
|
|
67
|
+
// is a missed optimization, not permission to merge different file ranges.
|
|
68
|
+
const key = createHash('sha256')
|
|
69
|
+
.update(JSON.stringify([call.name, call.arguments, message.content]))
|
|
70
|
+
.digest('hex')
|
|
71
|
+
const representative = representatives.get(key)
|
|
72
|
+
if (!representative) {
|
|
73
|
+
representatives.set(key, message)
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
if (protectedIndices.has(index)) continue
|
|
77
|
+
const replacement: ToolMessage = {
|
|
78
|
+
...message,
|
|
79
|
+
content: `[Duplicate observation: this call returned exactly the same text as tool result ${JSON.stringify(representative.toolCallId)}, whose full content remains in this request. Both calls occurred. This is historical evidence, not a claim that external state is still unchanged.]`,
|
|
80
|
+
}
|
|
81
|
+
if (estimateMessageTokens(replacement) >= estimateMessageTokens(message)) continue
|
|
82
|
+
projected ??= [...messages]
|
|
83
|
+
projected[index] = replacement
|
|
84
|
+
}
|
|
85
|
+
return projected ?? messages
|
|
86
|
+
}
|