@namzu/sdk 3.0.0 → 3.2.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 +223 -0
- package/dist/advisory/__tests__/consultation-context.test.d.ts +2 -0
- package/dist/advisory/__tests__/consultation-context.test.d.ts.map +1 -0
- package/dist/advisory/__tests__/consultation-context.test.js +124 -0
- package/dist/advisory/__tests__/consultation-context.test.js.map +1 -0
- package/dist/advisory/context.d.ts +25 -0
- package/dist/advisory/context.d.ts.map +1 -1
- package/dist/advisory/context.js +18 -0
- package/dist/advisory/context.js.map +1 -1
- package/dist/advisory/executor.d.ts.map +1 -1
- package/dist/advisory/executor.js +25 -3
- package/dist/advisory/executor.js.map +1 -1
- package/dist/compaction/__tests__/context-reducer.test.d.ts +2 -0
- package/dist/compaction/__tests__/context-reducer.test.d.ts.map +1 -0
- package/dist/compaction/__tests__/context-reducer.test.js +197 -0
- package/dist/compaction/__tests__/context-reducer.test.js.map +1 -0
- package/dist/compaction/factory.d.ts +7 -0
- package/dist/compaction/factory.d.ts.map +1 -1
- package/dist/compaction/factory.js +7 -0
- package/dist/compaction/factory.js.map +1 -1
- package/dist/compaction/index.d.ts +2 -0
- package/dist/compaction/index.d.ts.map +1 -1
- package/dist/compaction/index.js +1 -0
- package/dist/compaction/index.js.map +1 -1
- package/dist/compaction/interface.d.ts +13 -0
- package/dist/compaction/interface.d.ts.map +1 -1
- package/dist/compaction/managers/null.d.ts +3 -0
- package/dist/compaction/managers/null.d.ts.map +1 -1
- package/dist/compaction/managers/null.js +3 -0
- package/dist/compaction/managers/null.js.map +1 -1
- package/dist/compaction/managers/slidingWindow.d.ts +6 -0
- package/dist/compaction/managers/slidingWindow.d.ts.map +1 -1
- package/dist/compaction/managers/slidingWindow.js +6 -0
- package/dist/compaction/managers/slidingWindow.js.map +1 -1
- package/dist/compaction/managers/structured.d.ts +10 -0
- package/dist/compaction/managers/structured.d.ts.map +1 -1
- package/dist/compaction/managers/structured.js +10 -0
- package/dist/compaction/managers/structured.js.map +1 -1
- package/dist/compaction/reducer.d.ts +86 -0
- package/dist/compaction/reducer.d.ts.map +1 -0
- package/dist/compaction/reducer.js +77 -0
- package/dist/compaction/reducer.js.map +1 -0
- package/dist/connector/builtins/__tests__/oauth2-auth.test.d.ts +2 -0
- package/dist/connector/builtins/__tests__/oauth2-auth.test.d.ts.map +1 -0
- package/dist/connector/builtins/__tests__/oauth2-auth.test.js +54 -0
- package/dist/connector/builtins/__tests__/oauth2-auth.test.js.map +1 -0
- package/dist/connector/builtins/http.d.ts.map +1 -1
- package/dist/connector/builtins/http.js +24 -2
- package/dist/connector/builtins/http.js.map +1 -1
- package/dist/connector/builtins/http.test.js +18 -2
- package/dist/connector/builtins/http.test.js.map +1 -1
- package/dist/connector/index.d.ts +2 -2
- package/dist/connector/index.d.ts.map +1 -1
- package/dist/connector/index.js +1 -1
- package/dist/connector/index.js.map +1 -1
- package/dist/connector/mcp/__tests__/prompts-and-lifecycle.test.d.ts +2 -0
- package/dist/connector/mcp/__tests__/prompts-and-lifecycle.test.d.ts.map +1 -0
- package/dist/connector/mcp/__tests__/prompts-and-lifecycle.test.js +214 -0
- package/dist/connector/mcp/__tests__/prompts-and-lifecycle.test.js.map +1 -0
- package/dist/connector/mcp/client.d.ts +52 -1
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +86 -0
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/connector/mcp/discovery.d.ts +12 -1
- package/dist/connector/mcp/discovery.d.ts.map +1 -1
- package/dist/connector/mcp/discovery.js +19 -4
- package/dist/connector/mcp/discovery.js.map +1 -1
- package/dist/connector/mcp/index.d.ts +2 -2
- package/dist/connector/mcp/index.d.ts.map +1 -1
- package/dist/connector/mcp/index.js +1 -1
- package/dist/connector/mcp/index.js.map +1 -1
- package/dist/connector/mcp/server.d.ts +42 -1
- package/dist/connector/mcp/server.d.ts.map +1 -1
- package/dist/connector/mcp/server.js +77 -4
- package/dist/connector/mcp/server.js.map +1 -1
- package/dist/manager/agent/__tests__/depth-limit-authority.test.d.ts +2 -0
- package/dist/manager/agent/__tests__/depth-limit-authority.test.d.ts.map +1 -0
- package/dist/manager/agent/__tests__/depth-limit-authority.test.js +58 -0
- package/dist/manager/agent/__tests__/depth-limit-authority.test.js.map +1 -0
- package/dist/plugin/__tests__/discovery-scopes.test.d.ts +2 -0
- package/dist/plugin/__tests__/discovery-scopes.test.d.ts.map +1 -0
- package/dist/plugin/__tests__/discovery-scopes.test.js +97 -0
- package/dist/plugin/__tests__/discovery-scopes.test.js.map +1 -0
- package/dist/plugin/__tests__/enable-contributions.test.js +5 -1
- package/dist/plugin/__tests__/enable-contributions.test.js.map +1 -1
- package/dist/plugin/__tests__/mcp-admission.test.d.ts +2 -0
- package/dist/plugin/__tests__/mcp-admission.test.d.ts.map +1 -0
- package/dist/plugin/__tests__/mcp-admission.test.js +192 -0
- package/dist/plugin/__tests__/mcp-admission.test.js.map +1 -0
- package/dist/plugin/lifecycle.d.ts +41 -0
- package/dist/plugin/lifecycle.d.ts.map +1 -1
- package/dist/plugin/lifecycle.js +29 -1
- package/dist/plugin/lifecycle.js.map +1 -1
- package/dist/plugin/loader.d.ts +39 -3
- package/dist/plugin/loader.d.ts.map +1 -1
- package/dist/plugin/loader.js +37 -4
- package/dist/plugin/loader.js.map +1 -1
- package/dist/public-runtime.d.ts +5 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +11 -3
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +2 -2
- package/dist/public-types.d.ts.map +1 -1
- package/dist/rag/__tests__/namespace-isolation.test.d.ts +2 -0
- package/dist/rag/__tests__/namespace-isolation.test.d.ts.map +1 -0
- package/dist/rag/__tests__/namespace-isolation.test.js +80 -0
- package/dist/rag/__tests__/namespace-isolation.test.js.map +1 -0
- package/dist/rag/ingestion.d.ts.map +1 -1
- package/dist/rag/ingestion.js +1 -0
- package/dist/rag/ingestion.js.map +1 -1
- package/dist/rag/retriever.d.ts.map +1 -1
- package/dist/rag/retriever.js +2 -0
- package/dist/rag/retriever.js.map +1 -1
- package/dist/rag/vector-store.d.ts.map +1 -1
- package/dist/rag/vector-store.js +6 -0
- package/dist/rag/vector-store.js.map +1 -1
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +113 -109
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/runtime/query/__tests__/capability-negotiation.test.js +59 -0
- package/dist/runtime/query/__tests__/capability-negotiation.test.js.map +1 -1
- package/dist/runtime/query/__tests__/per-step-skills.test.d.ts +10 -0
- package/dist/runtime/query/__tests__/per-step-skills.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/per-step-skills.test.js +122 -0
- package/dist/runtime/query/__tests__/per-step-skills.test.js.map +1 -0
- package/dist/runtime/query/__tests__/per-step-tool-choice.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/per-step-tool-choice.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/per-step-tool-choice.test.js +153 -0
- package/dist/runtime/query/__tests__/per-step-tool-choice.test.js.map +1 -0
- package/dist/runtime/query/__tests__/resume-run.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/resume-run.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/resume-run.test.js +211 -0
- package/dist/runtime/query/__tests__/resume-run.test.js.map +1 -0
- package/dist/runtime/query/executor.d.ts +1 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +10 -1
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +10 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +26 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +118 -34
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-model-routing.test.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/compaction-model-routing.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/compaction-model-routing.test.js +96 -0
- package/dist/runtime/query/iteration/phases/compaction-model-routing.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 +95 -5
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context-reducer-dispatch.test.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/context-reducer-dispatch.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/context-reducer-dispatch.test.js +180 -0
- package/dist/runtime/query/iteration/phases/context-reducer-dispatch.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/context.d.ts +9 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/resume-run.d.ts +70 -0
- package/dist/runtime/query/resume-run.d.ts.map +1 -0
- package/dist/runtime/query/resume-run.js +46 -0
- package/dist/runtime/query/resume-run.js.map +1 -0
- package/dist/telemetry/__tests__/model-call-span.test.d.ts +2 -0
- package/dist/telemetry/__tests__/model-call-span.test.d.ts.map +1 -0
- package/dist/telemetry/__tests__/model-call-span.test.js +147 -0
- package/dist/telemetry/__tests__/model-call-span.test.js.map +1 -0
- package/dist/telemetry/__tests__/span-closure.test.d.ts +2 -0
- package/dist/telemetry/__tests__/span-closure.test.d.ts.map +1 -0
- package/dist/telemetry/__tests__/span-closure.test.js +124 -0
- package/dist/telemetry/__tests__/span-closure.test.js.map +1 -0
- package/dist/tools/advisory/index.js +1 -1
- package/dist/tools/advisory/index.js.map +1 -1
- package/dist/tools/builtins/__tests__/edit-commits-atomically.test.d.ts +2 -0
- package/dist/tools/builtins/__tests__/edit-commits-atomically.test.d.ts.map +1 -0
- package/dist/tools/builtins/__tests__/edit-commits-atomically.test.js +78 -0
- package/dist/tools/builtins/__tests__/edit-commits-atomically.test.js.map +1 -0
- package/dist/tools/builtins/__tests__/edit.test.js +242 -1
- package/dist/tools/builtins/__tests__/edit.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/stale-file-refusal.test.d.ts +2 -0
- package/dist/tools/builtins/__tests__/stale-file-refusal.test.d.ts.map +1 -0
- package/dist/tools/builtins/__tests__/stale-file-refusal.test.js +187 -0
- package/dist/tools/builtins/__tests__/stale-file-refusal.test.js.map +1 -0
- package/dist/tools/builtins/atomic-write-file.d.ts +1 -1
- package/dist/tools/builtins/atomic-write-file.d.ts.map +1 -1
- package/dist/tools/builtins/atomic-write-file.js +31 -2
- package/dist/tools/builtins/atomic-write-file.js.map +1 -1
- package/dist/tools/builtins/content-fingerprint.d.ts +23 -0
- package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -0
- package/dist/tools/builtins/content-fingerprint.js +30 -0
- package/dist/tools/builtins/content-fingerprint.js.map +1 -0
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +172 -31
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +5 -2
- 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 +75 -27
- package/dist/tools/builtins/write-file.js.map +1 -1
- package/dist/tools/coordinator/__tests__/ask-user-question.test.js +55 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.js.map +1 -1
- package/dist/tools/coordinator/__tests__/plan-dependencies.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/plan-dependencies.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/plan-dependencies.test.js +126 -0
- package/dist/tools/coordinator/__tests__/plan-dependencies.test.js.map +1 -0
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +75 -5
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/coordinator/plan-dependencies.d.ts +43 -0
- package/dist/tools/coordinator/plan-dependencies.d.ts.map +1 -0
- package/dist/tools/coordinator/plan-dependencies.js +148 -0
- package/dist/tools/coordinator/plan-dependencies.js.map +1 -0
- package/dist/types/agent/supervisor.d.ts +15 -0
- package/dist/types/agent/supervisor.d.ts.map +1 -1
- package/dist/types/connector/core.d.ts +34 -0
- package/dist/types/connector/core.d.ts.map +1 -1
- package/dist/types/connector/definition.d.ts +10 -0
- package/dist/types/connector/definition.d.ts.map +1 -1
- package/dist/types/connector/mcp.d.ts +13 -0
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/rag/retrieval.d.ts +16 -0
- package/dist/types/rag/retrieval.d.ts.map +1 -1
- package/dist/types/rag/storage.d.ts +9 -0
- package/dist/types/rag/storage.d.ts.map +1 -1
- package/dist/types/rag/vector.d.ts +11 -0
- package/dist/types/rag/vector.d.ts.map +1 -1
- package/dist/types/router/task-router.d.ts +19 -0
- package/dist/types/router/task-router.d.ts.map +1 -1
- package/dist/types/run/prepare-step.d.ts +56 -3
- package/dist/types/run/prepare-step.d.ts.map +1 -1
- package/dist/types/tool/index.d.ts +17 -1
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/types/toolset/index.d.ts +22 -0
- package/dist/types/toolset/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/advisory/__tests__/consultation-context.test.ts +191 -0
- package/src/advisory/context.ts +32 -0
- package/src/advisory/executor.ts +30 -3
- package/src/compaction/__tests__/context-reducer.test.ts +239 -0
- package/src/compaction/factory.ts +7 -0
- package/src/compaction/index.ts +8 -0
- package/src/compaction/interface.ts +13 -0
- package/src/compaction/managers/null.ts +3 -0
- package/src/compaction/managers/slidingWindow.ts +6 -0
- package/src/compaction/managers/structured.ts +10 -0
- package/src/compaction/reducer.ts +154 -0
- package/src/connector/builtins/__tests__/oauth2-auth.test.ts +73 -0
- package/src/connector/builtins/http.test.ts +28 -2
- package/src/connector/builtins/http.ts +26 -2
- package/src/connector/index.ts +6 -2
- package/src/connector/mcp/__tests__/prompts-and-lifecycle.test.ts +286 -0
- package/src/connector/mcp/client.ts +95 -0
- package/src/connector/mcp/discovery.ts +19 -4
- package/src/connector/mcp/index.ts +6 -2
- package/src/connector/mcp/server.ts +101 -3
- package/src/manager/agent/__tests__/depth-limit-authority.test.ts +74 -0
- package/src/plugin/__tests__/discovery-scopes.test.ts +133 -0
- package/src/plugin/__tests__/enable-contributions.test.ts +5 -1
- package/src/plugin/__tests__/mcp-admission.test.ts +242 -0
- package/src/plugin/lifecycle.ts +57 -1
- package/src/plugin/loader.ts +57 -3
- package/src/public-runtime.ts +16 -1
- package/src/public-types.ts +5 -0
- package/src/rag/__tests__/namespace-isolation.test.ts +109 -0
- package/src/rag/ingestion.ts +1 -0
- package/src/rag/retriever.ts +2 -0
- package/src/rag/vector-store.ts +5 -0
- package/src/registry/tool/execute.ts +123 -119
- package/src/runtime/query/__tests__/capability-negotiation.test.ts +69 -0
- package/src/runtime/query/__tests__/per-step-skills.test.ts +154 -0
- package/src/runtime/query/__tests__/per-step-tool-choice.test.ts +180 -0
- package/src/runtime/query/__tests__/resume-run.test.ts +262 -0
- package/src/runtime/query/executor.ts +9 -1
- package/src/runtime/query/index.ts +39 -0
- package/src/runtime/query/iteration/index.ts +130 -34
- package/src/runtime/query/iteration/phases/compaction-model-routing.test.ts +125 -0
- package/src/runtime/query/iteration/phases/compaction.ts +106 -5
- package/src/runtime/query/iteration/phases/context-reducer-dispatch.test.ts +238 -0
- package/src/runtime/query/iteration/phases/context.ts +11 -0
- package/src/runtime/query/resume-run.ts +93 -0
- package/src/telemetry/__tests__/model-call-span.test.ts +189 -0
- package/src/telemetry/__tests__/span-closure.test.ts +153 -0
- package/src/tools/advisory/index.ts +1 -1
- package/src/tools/builtins/__tests__/edit-commits-atomically.test.ts +103 -0
- package/src/tools/builtins/__tests__/edit.test.ts +324 -1
- package/src/tools/builtins/__tests__/stale-file-refusal.test.ts +258 -0
- package/src/tools/builtins/atomic-write-file.ts +30 -2
- package/src/tools/builtins/content-fingerprint.ts +33 -0
- package/src/tools/builtins/edit.ts +188 -36
- package/src/tools/builtins/read-file.ts +5 -2
- package/src/tools/builtins/write-file.ts +79 -28
- package/src/tools/coordinator/__tests__/ask-user-question.test.ts +68 -0
- package/src/tools/coordinator/__tests__/plan-dependencies.test.ts +186 -0
- package/src/tools/coordinator/index.ts +116 -42
- package/src/tools/coordinator/plan-dependencies.ts +175 -0
- package/src/types/agent/supervisor.ts +15 -0
- package/src/types/connector/core.ts +34 -0
- package/src/types/connector/definition.ts +10 -0
- package/src/types/connector/mcp.ts +14 -0
- package/src/types/rag/retrieval.ts +16 -0
- package/src/types/rag/storage.ts +9 -0
- package/src/types/rag/vector.ts +11 -0
- package/src/types/router/task-router.ts +19 -0
- package/src/types/run/prepare-step.ts +58 -3
- package/src/types/tool/index.ts +17 -1
- package/src/types/toolset/index.ts +22 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { mkdtemp, rm } from 'node:fs/promises'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
|
|
7
|
+
import { MockLLMProvider } from '../../../provider/mock.js'
|
|
8
|
+
import { ToolRegistry } from '../../../registry/tool/execute.js'
|
|
9
|
+
import type { SessionId, TenantId } from '../../../types/ids/index.js'
|
|
10
|
+
import { createUserMessage } from '../../../types/message/index.js'
|
|
11
|
+
import type { ProjectId, ThreadId } from '../../../types/session/ids.js'
|
|
12
|
+
import { drainQuery } from '../index.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Forcing a tool is the one step-shaping knob that can hang an agent.
|
|
16
|
+
*
|
|
17
|
+
* A forced choice that PERSISTS makes the model call a tool, read the
|
|
18
|
+
* result, and be forced again — forever. The peer SDK that puts
|
|
19
|
+
* `tool_choice` on persistent model settings carries three moving parts to
|
|
20
|
+
* undo that: a tool-use tracker, an opt-out flag, and a reset applied at
|
|
21
|
+
* two separate call sites. Its flag defaults to on precisely because
|
|
22
|
+
* turning it off hangs the agent.
|
|
23
|
+
*
|
|
24
|
+
* Putting the knob on `prepareStep` removes the failure instead of
|
|
25
|
+
* managing it: the next step is prepared from scratch, so a force cannot
|
|
26
|
+
* outlive the step that asked for it. There is no flag to get wrong.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
let workdirs: string[] = []
|
|
30
|
+
|
|
31
|
+
afterEach(async () => {
|
|
32
|
+
await Promise.all(workdirs.map((d) => rm(d, { recursive: true, force: true })))
|
|
33
|
+
workdirs = []
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
async function mkWorkdir(): Promise<string> {
|
|
37
|
+
const dir = await mkdtemp(join(tmpdir(), 'namzu-tool-choice-'))
|
|
38
|
+
workdirs.push(dir)
|
|
39
|
+
return dir
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function registerEcho(tools: ToolRegistry): void {
|
|
43
|
+
tools.register({
|
|
44
|
+
name: 'echo',
|
|
45
|
+
description: 'Echo the text back.',
|
|
46
|
+
inputSchema: z.object({ text: z.string().optional() }),
|
|
47
|
+
execute: async () => ({ success: true, output: 'ok' }),
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function baseParams(provider: MockLLMProvider, tools: ToolRegistry) {
|
|
52
|
+
return {
|
|
53
|
+
provider,
|
|
54
|
+
tools,
|
|
55
|
+
runConfig: {
|
|
56
|
+
model: 'mock-model',
|
|
57
|
+
timeoutMs: 30_000,
|
|
58
|
+
tokenBudget: 100_000,
|
|
59
|
+
maxIterations: 4,
|
|
60
|
+
maxResponseTokens: 256,
|
|
61
|
+
},
|
|
62
|
+
agentId: 'agent_tc',
|
|
63
|
+
agentName: 'Tool Choice Agent',
|
|
64
|
+
workingDirectory: await mkWorkdir(),
|
|
65
|
+
sessionId: 'ses_tc' as SessionId,
|
|
66
|
+
threadId: 'thd_tc' as ThreadId,
|
|
67
|
+
projectId: 'prj_tc' as ProjectId,
|
|
68
|
+
tenantId: 'tnt_tc' as TenantId,
|
|
69
|
+
messages: [createUserMessage('go')],
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
describe('a step can force the model to call a tool', () => {
|
|
74
|
+
it('puts the caller choice on that step request', async () => {
|
|
75
|
+
const provider = new MockLLMProvider({ turns: [{ text: 'done' }] })
|
|
76
|
+
const tools = new ToolRegistry()
|
|
77
|
+
registerEcho(tools)
|
|
78
|
+
|
|
79
|
+
await drainQuery({
|
|
80
|
+
...(await baseParams(provider, tools)),
|
|
81
|
+
prepareStep: () => ({ toolChoice: 'required' as const }),
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
expect(provider.requests.at(0)?.toolChoice).toBe('required')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('carries a named function through unchanged', async () => {
|
|
88
|
+
const provider = new MockLLMProvider({ turns: [{ text: 'done' }] })
|
|
89
|
+
const tools = new ToolRegistry()
|
|
90
|
+
registerEcho(tools)
|
|
91
|
+
|
|
92
|
+
await drainQuery({
|
|
93
|
+
...(await baseParams(provider, tools)),
|
|
94
|
+
prepareStep: () => ({
|
|
95
|
+
toolChoice: { type: 'function' as const, function: { name: 'echo' } },
|
|
96
|
+
}),
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
expect(provider.requests.at(0)?.toolChoice).toEqual({
|
|
100
|
+
type: 'function',
|
|
101
|
+
function: { name: 'echo' },
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('leaves the request alone when no step asks', async () => {
|
|
106
|
+
const provider = new MockLLMProvider({ turns: [{ text: 'done' }] })
|
|
107
|
+
const tools = new ToolRegistry()
|
|
108
|
+
registerEcho(tools)
|
|
109
|
+
|
|
110
|
+
await drainQuery(await baseParams(provider, tools))
|
|
111
|
+
|
|
112
|
+
expect(provider.requests.at(0)?.toolChoice).toBeUndefined()
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('says nothing when there are no tools to choose between', async () => {
|
|
116
|
+
const provider = new MockLLMProvider({ turns: [{ text: 'done' }] })
|
|
117
|
+
|
|
118
|
+
await drainQuery({
|
|
119
|
+
...(await baseParams(provider, new ToolRegistry())),
|
|
120
|
+
prepareStep: () => ({ toolChoice: 'required' as const }),
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
// `tool_choice` alongside an absent tool list is rejected by the
|
|
124
|
+
// providers, so a forced choice with nothing registered has to drop.
|
|
125
|
+
expect(provider.requests.at(0)?.toolChoice).toBeUndefined()
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* NOT covered here, and deliberately not faked: the forced-final turn takes
|
|
131
|
+
* precedence over a step's choice (`forceFinalize` wins in
|
|
132
|
+
* `iteration/index.ts`), so a budget-exhausted run can still stop asking
|
|
133
|
+
* for tools and answer. Reaching that branch needs the guard to raise a
|
|
134
|
+
* budget warning, which needs real usage the mock provider does not report.
|
|
135
|
+
* The precedence is implemented and read in review; it is not pinned.
|
|
136
|
+
*/
|
|
137
|
+
describe('a forced choice cannot outlive the step that asked for it', () => {
|
|
138
|
+
it('applies to the named step only, leaving later steps free', async () => {
|
|
139
|
+
const provider = new MockLLMProvider({
|
|
140
|
+
turns: [
|
|
141
|
+
{ toolCalls: [{ id: 'c1', name: 'echo', rawArguments: '{}' }] },
|
|
142
|
+
{ text: 'finished' },
|
|
143
|
+
],
|
|
144
|
+
})
|
|
145
|
+
const tools = new ToolRegistry()
|
|
146
|
+
registerEcho(tools)
|
|
147
|
+
|
|
148
|
+
await drainQuery({
|
|
149
|
+
...(await baseParams(provider, tools)),
|
|
150
|
+
// Only the first step is forced. Without per-step scoping this
|
|
151
|
+
// would keep forcing and the run would never reach a text answer.
|
|
152
|
+
prepareStep: ({ stepNumber }) =>
|
|
153
|
+
stepNumber === 1 ? { toolChoice: 'required' as const } : {},
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
expect(provider.requests.at(0)?.toolChoice).toBe('required')
|
|
157
|
+
expect(provider.requests.at(1)?.toolChoice).toBeUndefined()
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('a run whose every step forces a tool still ends, bounded by the loop', async () => {
|
|
161
|
+
const provider = new MockLLMProvider({
|
|
162
|
+
turns: [
|
|
163
|
+
{ toolCalls: [{ id: 'c1', name: 'echo', rawArguments: '{}' }] },
|
|
164
|
+
{ toolCalls: [{ id: 'c2', name: 'echo', rawArguments: '{}' }] },
|
|
165
|
+
{ text: 'finished' },
|
|
166
|
+
],
|
|
167
|
+
})
|
|
168
|
+
const tools = new ToolRegistry()
|
|
169
|
+
registerEcho(tools)
|
|
170
|
+
|
|
171
|
+
const run = await drainQuery({
|
|
172
|
+
...(await baseParams(provider, tools)),
|
|
173
|
+
prepareStep: () => ({ toolChoice: 'required' as const }),
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// The knob cannot hang a run on its own: the iteration cap is still
|
|
177
|
+
// the backstop, and it settles rather than spinning.
|
|
178
|
+
expect(['completed', 'failed']).toContain(run.status)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { mkdtemp, rm } from 'node:fs/promises'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { MockLLMProvider } from '../../../provider/mock.js'
|
|
7
|
+
import { ToolRegistry } from '../../../registry/tool/execute.js'
|
|
8
|
+
import type { CheckpointId, IterationCheckpoint } from '../../../types/hitl/index.js'
|
|
9
|
+
import type { RunId, SessionId, TenantId } from '../../../types/ids/index.js'
|
|
10
|
+
import { createUserMessage } from '../../../types/message/index.js'
|
|
11
|
+
import type { CheckpointRunScope, CheckpointStore } from '../../../types/run/checkpoint-store.js'
|
|
12
|
+
import type { ProjectId, ThreadId } from '../../../types/session/ids.js'
|
|
13
|
+
import { resumeRun } from '../resume-run.js'
|
|
14
|
+
import type { RunStateScope } from '../run-state.js'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The pieces of a cross-process resume all existed and nothing joined them.
|
|
18
|
+
* `CheckpointManager` wrote the history, budgets, working state and any
|
|
19
|
+
* park; `loadRunState` read them back; `query` accepted `runId` +
|
|
20
|
+
* `resumeFromCheckpoint` and restored all of it. But `resumeFromCheckpoint`
|
|
21
|
+
* had no caller anywhere outside `packages/sdk/src`, so the whole path
|
|
22
|
+
* shipped untravelled — every host was expected to write the same wiring
|
|
23
|
+
* and none did.
|
|
24
|
+
*
|
|
25
|
+
* These cover the join, and especially its two refusals: a resume must not
|
|
26
|
+
* quietly become a fresh run under a recycled id, and it must not step past
|
|
27
|
+
* a park without the answer that park is waiting for.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const SCOPE: RunStateScope = {
|
|
31
|
+
tenantId: 'tnt_resume' as TenantId,
|
|
32
|
+
projectId: 'prj_resume' as ProjectId,
|
|
33
|
+
sessionId: 'ses_resume' as SessionId,
|
|
34
|
+
runId: 'run_resume' as RunId,
|
|
35
|
+
threadId: 'thd_resume' as ThreadId,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ZERO_USAGE = {
|
|
39
|
+
promptTokens: 0,
|
|
40
|
+
completionTokens: 0,
|
|
41
|
+
totalTokens: 0,
|
|
42
|
+
cachedTokens: 0,
|
|
43
|
+
cacheWriteTokens: 0,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const ZERO_COST = {
|
|
47
|
+
inputCostPer1M: 0,
|
|
48
|
+
outputCostPer1M: 0,
|
|
49
|
+
totalCost: 0,
|
|
50
|
+
cacheDiscount: 0,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class InMemoryCheckpointStore implements CheckpointStore {
|
|
54
|
+
readonly rows = new Map<string, IterationCheckpoint>()
|
|
55
|
+
|
|
56
|
+
private key(scope: CheckpointRunScope, id: CheckpointId): string {
|
|
57
|
+
return [scope.tenantId, scope.projectId, scope.sessionId, scope.runId, id].join('/')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async writeCheckpoint(scope: CheckpointRunScope, checkpoint: IterationCheckpoint): Promise<void> {
|
|
61
|
+
this.rows.set(this.key(scope, checkpoint.id), checkpoint)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async readCheckpoint(
|
|
65
|
+
scope: CheckpointRunScope,
|
|
66
|
+
id: CheckpointId,
|
|
67
|
+
): Promise<IterationCheckpoint | null> {
|
|
68
|
+
return this.rows.get(this.key(scope, id)) ?? null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async listCheckpoints(scope: CheckpointRunScope): Promise<IterationCheckpoint[]> {
|
|
72
|
+
const prefix = `${[scope.tenantId, scope.projectId, scope.sessionId, scope.runId].join('/')}/`
|
|
73
|
+
return [...this.rows.entries()]
|
|
74
|
+
.filter(([key]) => key.startsWith(prefix))
|
|
75
|
+
.map(([, cp]) => cp)
|
|
76
|
+
.sort((a, b) => a.createdAt - b.createdAt)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async deleteCheckpoint(scope: CheckpointRunScope, id: CheckpointId): Promise<void> {
|
|
80
|
+
this.rows.delete(this.key(scope, id))
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function checkpoint(overrides: Partial<IterationCheckpoint> = {}): IterationCheckpoint {
|
|
85
|
+
return {
|
|
86
|
+
id: 'ckpt_1' as CheckpointId,
|
|
87
|
+
runId: SCOPE.runId,
|
|
88
|
+
iteration: 2,
|
|
89
|
+
messages: [createUserMessage('the work so far')],
|
|
90
|
+
tokenUsage: { ...ZERO_USAGE, promptTokens: 120, totalTokens: 120 },
|
|
91
|
+
costInfo: { ...ZERO_COST, totalCost: 0.4 },
|
|
92
|
+
guardState: { iterationCount: 2, elapsedMs: 9_000 },
|
|
93
|
+
createdAt: Date.now(),
|
|
94
|
+
...overrides,
|
|
95
|
+
} as IterationCheckpoint
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let workdirs: string[] = []
|
|
99
|
+
|
|
100
|
+
afterEach(async () => {
|
|
101
|
+
await Promise.all(workdirs.map((d) => rm(d, { recursive: true, force: true })))
|
|
102
|
+
workdirs = []
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
async function mkWorkdir(): Promise<string> {
|
|
106
|
+
const dir = await mkdtemp(join(tmpdir(), 'namzu-resume-run-'))
|
|
107
|
+
workdirs.push(dir)
|
|
108
|
+
return dir
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function baseParams(store: CheckpointStore) {
|
|
112
|
+
return {
|
|
113
|
+
scope: SCOPE,
|
|
114
|
+
checkpointStore: store,
|
|
115
|
+
provider: new MockLLMProvider({ turns: [{ text: 'continued' }] }),
|
|
116
|
+
tools: new ToolRegistry(),
|
|
117
|
+
runConfig: {
|
|
118
|
+
model: 'mock-model',
|
|
119
|
+
timeoutMs: 30_000,
|
|
120
|
+
tokenBudget: 100_000,
|
|
121
|
+
maxIterations: 2,
|
|
122
|
+
maxResponseTokens: 256,
|
|
123
|
+
},
|
|
124
|
+
agentId: 'agent_resume',
|
|
125
|
+
agentName: 'Resume Agent',
|
|
126
|
+
workingDirectory: await mkWorkdir(),
|
|
127
|
+
sessionId: SCOPE.sessionId,
|
|
128
|
+
threadId: SCOPE.threadId,
|
|
129
|
+
projectId: SCOPE.projectId,
|
|
130
|
+
tenantId: SCOPE.tenantId,
|
|
131
|
+
// Required by the contract, and rightly so: a resume that lands on a
|
|
132
|
+
// park has to have somewhere to ask.
|
|
133
|
+
resumeHandler: async () => ({ action: 'continue' as const }),
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
describe('a run is picked back up from its store', () => {
|
|
138
|
+
it('continues the same run id rather than starting a new one', async () => {
|
|
139
|
+
const store = new InMemoryCheckpointStore()
|
|
140
|
+
await store.writeCheckpoint(SCOPE, checkpoint())
|
|
141
|
+
|
|
142
|
+
const outcome = await resumeRun(await baseParams(store))
|
|
143
|
+
|
|
144
|
+
expect(outcome.resumed).toBe(true)
|
|
145
|
+
if (!outcome.resumed) return
|
|
146
|
+
// The whole point: a resume is the same run in a different process,
|
|
147
|
+
// so its id, budgets and trace all have to carry across.
|
|
148
|
+
expect(outcome.run.id).toBe(SCOPE.runId)
|
|
149
|
+
expect(outcome.state.checkpointId).toBe('ckpt_1')
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('carries the spent budget forward instead of granting a fresh one', async () => {
|
|
153
|
+
const store = new InMemoryCheckpointStore()
|
|
154
|
+
await store.writeCheckpoint(SCOPE, checkpoint())
|
|
155
|
+
|
|
156
|
+
const outcome = await resumeRun(await baseParams(store))
|
|
157
|
+
|
|
158
|
+
expect(outcome.resumed).toBe(true)
|
|
159
|
+
if (!outcome.resumed) return
|
|
160
|
+
// A run recalled at 120 tokens must not come back at zero — the
|
|
161
|
+
// budget belongs to the run, not to the process hosting it.
|
|
162
|
+
expect(outcome.run.tokenUsage.totalTokens).toBeGreaterThanOrEqual(120)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('picks the newest checkpoint when the caller names none', async () => {
|
|
166
|
+
const store = new InMemoryCheckpointStore()
|
|
167
|
+
await store.writeCheckpoint(SCOPE, checkpoint({ id: 'ckpt_old' as CheckpointId, createdAt: 1 }))
|
|
168
|
+
await store.writeCheckpoint(
|
|
169
|
+
SCOPE,
|
|
170
|
+
checkpoint({ id: 'ckpt_new' as CheckpointId, createdAt: 2_000 }),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
const outcome = await resumeRun(await baseParams(store))
|
|
174
|
+
|
|
175
|
+
expect(outcome.resumed).toBe(true)
|
|
176
|
+
if (!outcome.resumed) return
|
|
177
|
+
expect(outcome.state.checkpointId).toBe('ckpt_new')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('honours an explicitly named checkpoint', async () => {
|
|
181
|
+
const store = new InMemoryCheckpointStore()
|
|
182
|
+
await store.writeCheckpoint(SCOPE, checkpoint({ id: 'ckpt_old' as CheckpointId, createdAt: 1 }))
|
|
183
|
+
await store.writeCheckpoint(
|
|
184
|
+
SCOPE,
|
|
185
|
+
checkpoint({ id: 'ckpt_new' as CheckpointId, createdAt: 2_000 }),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
const outcome = await resumeRun({
|
|
189
|
+
...(await baseParams(store)),
|
|
190
|
+
checkpointId: 'ckpt_old' as CheckpointId,
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
expect(outcome.resumed).toBe(true)
|
|
194
|
+
if (!outcome.resumed) return
|
|
195
|
+
expect(outcome.state.checkpointId).toBe('ckpt_old')
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
describe('it refuses rather than guessing', () => {
|
|
200
|
+
it('reports no checkpoint instead of silently starting fresh', async () => {
|
|
201
|
+
const outcome = await resumeRun(await baseParams(new InMemoryCheckpointStore()))
|
|
202
|
+
|
|
203
|
+
// Starting a new run here would be the worst outcome: a different
|
|
204
|
+
// run wearing a recycled id, with the original's budget reset.
|
|
205
|
+
expect(outcome).toEqual({ resumed: false, reason: 'no-checkpoint' })
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('hands back the outstanding question instead of resuming past it', async () => {
|
|
209
|
+
const store = new InMemoryCheckpointStore()
|
|
210
|
+
await store.writeCheckpoint(
|
|
211
|
+
SCOPE,
|
|
212
|
+
checkpoint({
|
|
213
|
+
pending: {
|
|
214
|
+
request: {
|
|
215
|
+
type: 'tool_review',
|
|
216
|
+
runId: SCOPE.runId,
|
|
217
|
+
checkpointId: 'ckpt_1' as CheckpointId,
|
|
218
|
+
toolCalls: [{ id: 'call_1', name: 'write', input: {} }],
|
|
219
|
+
},
|
|
220
|
+
parkedAt: Date.now(),
|
|
221
|
+
deadlineAt: Date.now() + 60_000,
|
|
222
|
+
},
|
|
223
|
+
} as unknown as Partial<IterationCheckpoint>),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
const outcome = await resumeRun(await baseParams(store))
|
|
227
|
+
|
|
228
|
+
expect(outcome.resumed).toBe(false)
|
|
229
|
+
if (outcome.resumed || outcome.reason !== 'awaiting-decision') {
|
|
230
|
+
throw new Error(`expected awaiting-decision, got ${JSON.stringify(outcome)}`)
|
|
231
|
+
}
|
|
232
|
+
// The host needs the request itself to put in front of a person.
|
|
233
|
+
expect(outcome.pending.request.type).toBe('tool_review')
|
|
234
|
+
expect(outcome.state.runId).toBe(SCOPE.runId)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('treats an already-answered park as an ordinary resume', async () => {
|
|
238
|
+
const store = new InMemoryCheckpointStore()
|
|
239
|
+
await store.writeCheckpoint(
|
|
240
|
+
SCOPE,
|
|
241
|
+
checkpoint({
|
|
242
|
+
pending: {
|
|
243
|
+
request: {
|
|
244
|
+
type: 'tool_review',
|
|
245
|
+
runId: SCOPE.runId,
|
|
246
|
+
checkpointId: 'ckpt_1' as CheckpointId,
|
|
247
|
+
toolCalls: [{ id: 'call_1', name: 'write', input: {} }],
|
|
248
|
+
},
|
|
249
|
+
parkedAt: Date.now(),
|
|
250
|
+
deadlineAt: Date.now() + 60_000,
|
|
251
|
+
resolvedAt: Date.now(),
|
|
252
|
+
},
|
|
253
|
+
} as unknown as Partial<IterationCheckpoint>),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
const outcome = await resumeRun(await baseParams(store))
|
|
257
|
+
|
|
258
|
+
// `resolvedAt` is what makes a park answered. Blocking on one that
|
|
259
|
+
// already has its answer would strand the run permanently.
|
|
260
|
+
expect(outcome.resumed).toBe(true)
|
|
261
|
+
})
|
|
262
|
+
})
|
|
@@ -7,6 +7,7 @@ import { ProbeVetoError } from '../../probe/errors.js'
|
|
|
7
7
|
import { type ProbeRegistry, probe as defaultProbeRegistry } from '../../probe/registry.js'
|
|
8
8
|
import { renderToolSchema } from '../../registry/tool/schema.js'
|
|
9
9
|
import type { ActivityStore } from '../../store/activity/memory.js'
|
|
10
|
+
import { fingerprintContent } from '../../tools/builtins/content-fingerprint.js'
|
|
10
11
|
import type { RunId, ToolUseId } from '../../types/ids/index.js'
|
|
11
12
|
import type { InvocationState } from '../../types/invocation/index.js'
|
|
12
13
|
import {
|
|
@@ -205,11 +206,18 @@ export class ToolExecutor {
|
|
|
205
206
|
private probes: ProbeRegistry
|
|
206
207
|
private parentSpan?: Span
|
|
207
208
|
private readonly readPaths: Set<string> = new Set()
|
|
209
|
+
private readonly readFingerprints: Map<string, string> = new Map()
|
|
208
210
|
private readonly fileReadTracker: FileReadTracker = {
|
|
209
|
-
recordRead: (key: string) => {
|
|
211
|
+
recordRead: (key: string, content?: string) => {
|
|
210
212
|
this.readPaths.add(key)
|
|
213
|
+
// Only when the reader had the body. A tool that records a read
|
|
214
|
+
// without one leaves the previous fingerprint alone rather than
|
|
215
|
+
// clearing it, so a later write is still checked against the last
|
|
216
|
+
// body anyone actually saw.
|
|
217
|
+
if (content !== undefined) this.readFingerprints.set(key, fingerprintContent(content))
|
|
211
218
|
},
|
|
212
219
|
hasRead: (key: string) => this.readPaths.has(key),
|
|
220
|
+
fingerprint: (key: string) => this.readFingerprints.get(key),
|
|
213
221
|
}
|
|
214
222
|
|
|
215
223
|
constructor(
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
import { findDanglingMessages, removeDanglingMessages } from '../../compaction/dangling.js'
|
|
10
10
|
import { extractFromUserMessage } from '../../compaction/extractor.js'
|
|
11
11
|
import { WorkingStateManager } from '../../compaction/manager.js'
|
|
12
|
+
import type { ContextReducer } from '../../compaction/reducer.js'
|
|
13
|
+
import { serializeState as serializeWorkingState } from '../../compaction/serializer.js'
|
|
12
14
|
import { restoreWorkingState, snapshotWorkingState } from '../../compaction/wire.js'
|
|
13
15
|
import type { CompactionConfig } from '../../config/runtime.js'
|
|
14
16
|
import { TOOL_OUTPUT_DIR_NAME } from '../../constants/tools/index.js'
|
|
@@ -421,6 +423,16 @@ export interface QueryParams {
|
|
|
421
423
|
*/
|
|
422
424
|
workingMemoryProvider?: WorkingMemoryProvider
|
|
423
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Replace context reduction for this run.
|
|
428
|
+
*
|
|
429
|
+
* Outranks `compactionConfig.strategy`, and the built-in structured pass
|
|
430
|
+
* does not also run: two mechanisms editing one history in the same pass
|
|
431
|
+
* cannot both be reasoned about. See `ContextReducer` for the invariants a
|
|
432
|
+
* reducer is expected to keep.
|
|
433
|
+
*/
|
|
434
|
+
contextReducer?: ContextReducer
|
|
435
|
+
|
|
424
436
|
agentBus?: import('../../bus/index.js').AgentBus
|
|
425
437
|
|
|
426
438
|
verificationGate?: VerificationGateConfig
|
|
@@ -752,6 +764,31 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
|
|
|
752
764
|
params.advisory.budget,
|
|
753
765
|
)
|
|
754
766
|
|
|
767
|
+
// What the run looks like when the MODEL consults an advisor, as
|
|
768
|
+
// opposed to when a trigger does. The trigger path has always passed
|
|
769
|
+
// this; the tool path passed an empty context, so an advisor the model
|
|
770
|
+
// asked for help saw the question and nothing else.
|
|
771
|
+
//
|
|
772
|
+
// `includeToolCatalog` and `useCompactedContext` are read here and
|
|
773
|
+
// nowhere else. Both were declared on `AdvisoryConfig` /
|
|
774
|
+
// `AdvisorDefinition` and consulted by nothing, so a host who turned
|
|
775
|
+
// the catalogue off still paid for it in every advisory prompt.
|
|
776
|
+
const advisoryConfig = params.advisory
|
|
777
|
+
advisoryCtx.setCallContextProvider(() => {
|
|
778
|
+
const summary =
|
|
779
|
+
workingStateManager && advisoryConfig.advisors.some((a) => a.useCompactedContext)
|
|
780
|
+
? serializeWorkingState(workingStateManager.getState())
|
|
781
|
+
: undefined
|
|
782
|
+
return {
|
|
783
|
+
messages: ctx.runMgr.messages,
|
|
784
|
+
...(summary !== undefined ? { workingStateSummary: summary } : {}),
|
|
785
|
+
...(advisoryConfig.includeToolCatalog
|
|
786
|
+
? { toolCatalog: params.tools.toLLMTools(effectiveAllowedTools) }
|
|
787
|
+
: {}),
|
|
788
|
+
iteration: ctx.runMgr.currentIteration,
|
|
789
|
+
}
|
|
790
|
+
})
|
|
791
|
+
|
|
755
792
|
if (params.advisory.enableAgentTool) {
|
|
756
793
|
const advisoryTools = buildAdvisoryTools({ advisoryCtx })
|
|
757
794
|
const overrides = params.runtimeToolOverrides
|
|
@@ -800,6 +837,8 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
|
|
|
800
837
|
toolGrants: new ToolGrantSet(),
|
|
801
838
|
compactionConfig: params.compactionConfig,
|
|
802
839
|
workingStateManager,
|
|
840
|
+
taskRouter: params.taskRouter,
|
|
841
|
+
contextReducer: params.contextReducer,
|
|
803
842
|
workingMemoryProvider: params.workingMemoryProvider,
|
|
804
843
|
advisoryCtx,
|
|
805
844
|
agentBus: params.agentBus,
|