@namzu/sdk 1.0.0 → 1.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 +253 -0
- package/dist/agents/ReactiveAgent.d.ts.map +1 -1
- package/dist/agents/ReactiveAgent.js +5 -0
- package/dist/agents/ReactiveAgent.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts +22 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +47 -14
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/agents/__tests__/supervisor-ledger.test.d.ts +2 -0
- package/dist/agents/__tests__/supervisor-ledger.test.d.ts.map +1 -0
- package/dist/agents/__tests__/supervisor-ledger.test.js +131 -0
- package/dist/agents/__tests__/supervisor-ledger.test.js.map +1 -0
- 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 +4 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/config/runtime.d.ts +24 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +9 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/connector/builtins/webhook.test.js +17 -0
- package/dist/connector/builtins/webhook.test.js.map +1 -1
- package/dist/connector/index.d.ts +1 -0
- package/dist/connector/index.d.ts.map +1 -1
- package/dist/connector/index.js +1 -0
- package/dist/connector/index.js.map +1 -1
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +4 -0
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/connector/mcp/index.d.ts +1 -0
- package/dist/connector/mcp/index.d.ts.map +1 -1
- package/dist/connector/mcp/index.js +1 -0
- package/dist/connector/mcp/index.js.map +1 -1
- package/dist/connector/mcp/streamable-http.d.ts +22 -0
- package/dist/connector/mcp/streamable-http.d.ts.map +1 -0
- package/dist/connector/mcp/streamable-http.js +128 -0
- package/dist/connector/mcp/streamable-http.js.map +1 -0
- package/dist/connector/mcp/streamable-http.test.d.ts +2 -0
- package/dist/connector/mcp/streamable-http.test.d.ts.map +1 -0
- package/dist/connector/mcp/streamable-http.test.js +132 -0
- package/dist/connector/mcp/streamable-http.test.js.map +1 -0
- package/dist/{runtime/query → constants}/continuation.d.ts +5 -0
- package/dist/constants/continuation.d.ts.map +1 -0
- package/dist/{runtime/query → constants}/continuation.js +5 -0
- package/dist/constants/continuation.js.map +1 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +1 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/manager/run/persistence.d.ts +11 -0
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +30 -1
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/persona/assembler.d.ts.map +1 -1
- package/dist/persona/assembler.js +18 -9
- package/dist/persona/assembler.js.map +1 -1
- package/dist/provider/capabilities.d.ts +27 -0
- package/dist/provider/capabilities.d.ts.map +1 -0
- package/dist/provider/capabilities.js +29 -0
- package/dist/provider/capabilities.js.map +1 -0
- package/dist/provider/collect.d.ts.map +1 -1
- package/dist/provider/collect.js +4 -1
- package/dist/provider/collect.js.map +1 -1
- 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 +4 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +3 -3
- package/dist/public-runtime.js.map +1 -1
- package/dist/registry/tool/execute.d.ts +27 -0
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +124 -21
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/registry/tool/execute.test.d.ts +9 -6
- package/dist/registry/tool/execute.test.d.ts.map +1 -1
- package/dist/registry/tool/execute.test.js +72 -10
- package/dist/registry/tool/execute.test.js.map +1 -1
- package/dist/registry/toolset/catalog.test.js +62 -0
- package/dist/registry/toolset/catalog.test.js.map +1 -1
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +8 -0
- package/dist/run/reporter.js.map +1 -1
- package/dist/runtime/query/__tests__/capability-negotiation.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/capability-negotiation.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/capability-negotiation.test.js +184 -0
- package/dist/runtime/query/__tests__/capability-negotiation.test.js.map +1 -0
- package/dist/runtime/query/__tests__/checkpoint-store.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/checkpoint-store.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/checkpoint-store.test.js +259 -0
- package/dist/runtime/query/__tests__/checkpoint-store.test.js.map +1 -0
- package/dist/runtime/query/__tests__/checkpoint.test.js +9 -3
- package/dist/runtime/query/__tests__/checkpoint.test.js.map +1 -1
- package/dist/runtime/query/__tests__/deferred-tools.test.js +43 -5
- package/dist/runtime/query/__tests__/deferred-tools.test.js.map +1 -1
- package/dist/runtime/query/__tests__/long-document-flow.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/long-document-flow.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/long-document-flow.test.js +154 -0
- package/dist/runtime/query/__tests__/long-document-flow.test.js.map +1 -0
- package/dist/runtime/query/__tests__/orphan-task-turn-end.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/orphan-task-turn-end.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/orphan-task-turn-end.test.js +110 -0
- package/dist/runtime/query/__tests__/orphan-task-turn-end.test.js.map +1 -0
- package/dist/runtime/query/__tests__/prompt.test.js +15 -11
- package/dist/runtime/query/__tests__/prompt.test.js.map +1 -1
- package/dist/runtime/query/checkpoint.d.ts +16 -2
- package/dist/runtime/query/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/checkpoint.js +21 -7
- package/dist/runtime/query/checkpoint.js.map +1 -1
- package/dist/runtime/query/context.d.ts +7 -0
- package/dist/runtime/query/context.d.ts.map +1 -1
- package/dist/runtime/query/context.js +1 -0
- package/dist/runtime/query/context.js.map +1 -1
- package/dist/runtime/query/index.d.ts +29 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +98 -8
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +3 -55
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +260 -656
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/checkpoint.js +24 -2
- package/dist/runtime/query/iteration/phases/checkpoint.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts +8 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +84 -3
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +13 -2
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js +53 -0
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.js +73 -0
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.js +120 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/tool-review.d.ts +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +7 -2
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.d.ts +19 -0
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.js +177 -0
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/working-memory.d.ts +31 -0
- package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/working-memory.js +81 -0
- package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -0
- package/dist/runtime/query/iteration/stream-turn.d.ts +35 -0
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -0
- package/dist/runtime/query/iteration/stream-turn.js +341 -0
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -0
- package/dist/runtime/query/replay/list.d.ts +17 -1
- package/dist/runtime/query/replay/list.d.ts.map +1 -1
- package/dist/runtime/query/replay/list.js +21 -4
- package/dist/runtime/query/replay/list.js.map +1 -1
- package/dist/runtime/query/replay/prepare.d.ts +10 -0
- package/dist/runtime/query/replay/prepare.d.ts.map +1 -1
- package/dist/runtime/query/replay/prepare.js +20 -4
- package/dist/runtime/query/replay/prepare.js.map +1 -1
- package/dist/session/workspace/__tests__/shared-run.test.js +1 -1
- package/dist/session/workspace/__tests__/shared-run.test.js.map +1 -1
- package/dist/session/workspace/shared-run.js +1 -0
- package/dist/session/workspace/shared-run.js.map +1 -1
- package/dist/skills/loader.test.js +8 -8
- package/dist/skills/loader.test.js.map +1 -1
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +1 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/run/checkpoint-disk.d.ts +28 -0
- package/dist/store/run/checkpoint-disk.d.ts.map +1 -0
- package/dist/store/run/checkpoint-disk.js +55 -0
- package/dist/store/run/checkpoint-disk.js.map +1 -0
- package/dist/streaming/coalesce.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/payload-budget.test.js +48 -0
- package/dist/tools/builtins/__tests__/payload-budget.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/read-file.test.js +9 -0
- package/dist/tools/builtins/__tests__/read-file.test.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +50 -2
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/builtins/search-tools.d.ts.map +1 -1
- package/dist/tools/builtins/search-tools.js +32 -7
- package/dist/tools/builtins/search-tools.js.map +1 -1
- package/dist/tools/coordinator/__tests__/approve-plan.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/approve-plan.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/approve-plan.test.js +114 -0
- package/dist/tools/coordinator/__tests__/approve-plan.test.js.map +1 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.js +309 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.js.map +1 -0
- package/dist/tools/coordinator/index.d.ts +8 -0
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +195 -15
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/types/agent/index.d.ts +1 -0
- package/dist/types/agent/index.d.ts.map +1 -1
- package/dist/types/agent/index.js +1 -0
- package/dist/types/agent/index.js.map +1 -1
- package/dist/types/agent/reactive.d.ts +15 -0
- package/dist/types/agent/reactive.d.ts.map +1 -1
- package/dist/types/agent/supervisor.d.ts +18 -0
- package/dist/types/agent/supervisor.d.ts.map +1 -1
- package/dist/types/agent/working-memory.d.ts +29 -0
- package/dist/types/agent/working-memory.d.ts.map +1 -0
- package/dist/types/agent/working-memory.js +2 -0
- package/dist/types/agent/working-memory.js.map +1 -0
- package/dist/types/common/index.d.ts +10 -0
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/common/index.js +18 -0
- package/dist/types/common/index.js.map +1 -1
- package/dist/types/common/usage-merge.test.d.ts +2 -0
- package/dist/types/common/usage-merge.test.d.ts.map +1 -0
- package/dist/types/common/usage-merge.test.js +48 -0
- package/dist/types/common/usage-merge.test.js.map +1 -0
- package/dist/types/connector/mcp.d.ts +9 -2
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +42 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js +11 -0
- package/dist/types/hitl/index.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/config.d.ts +18 -0
- package/dist/types/provider/config.d.ts.map +1 -1
- package/dist/types/provider/interface.d.ts +10 -0
- package/dist/types/provider/interface.d.ts.map +1 -1
- package/dist/types/run/checkpoint-store.d.ts +64 -0
- package/dist/types/run/checkpoint-store.d.ts.map +1 -0
- package/dist/types/run/checkpoint-store.js +12 -0
- package/dist/types/run/checkpoint-store.js.map +1 -0
- package/dist/types/run/config.d.ts +27 -0
- package/dist/types/run/config.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +6 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/run/index.d.ts +1 -0
- package/dist/types/run/index.d.ts.map +1 -1
- package/dist/types/run/index.js +1 -0
- package/dist/types/run/index.js.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +25 -4
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/ReactiveAgent.ts +5 -0
- package/src/agents/SupervisorAgent.ts +56 -16
- package/src/agents/__tests__/supervisor-ledger.test.ts +185 -0
- package/src/bridge/a2a/mapper.ts +3 -0
- package/src/bridge/sse/mapper.ts +5 -0
- package/src/config/runtime.ts +9 -0
- package/src/connector/builtins/webhook.test.ts +21 -0
- package/src/connector/index.ts +1 -0
- package/src/connector/mcp/client.ts +4 -0
- package/src/connector/mcp/index.ts +1 -0
- package/src/connector/mcp/streamable-http.test.ts +169 -0
- package/src/connector/mcp/streamable-http.ts +152 -0
- package/src/{runtime/query → constants}/continuation.ts +5 -0
- package/src/constants/index.ts +1 -0
- package/src/manager/run/persistence.ts +34 -1
- package/src/persona/assembler.ts +18 -6
- package/src/provider/capabilities.ts +47 -0
- package/src/provider/collect.ts +4 -1
- package/src/provider/index.ts +5 -0
- package/src/public-runtime.ts +5 -0
- package/src/registry/tool/execute.test.ts +85 -10
- package/src/registry/tool/execute.ts +124 -21
- package/src/registry/toolset/catalog.test.ts +68 -0
- package/src/run/reporter.ts +9 -0
- package/src/runtime/query/__tests__/capability-negotiation.test.ts +248 -0
- package/src/runtime/query/__tests__/checkpoint-store.test.ts +322 -0
- package/src/runtime/query/__tests__/checkpoint.test.ts +15 -6
- package/src/runtime/query/__tests__/deferred-tools.test.ts +52 -5
- package/src/runtime/query/__tests__/long-document-flow.test.ts +180 -0
- package/src/runtime/query/__tests__/orphan-task-turn-end.test.ts +127 -0
- package/src/runtime/query/__tests__/prompt.test.ts +15 -11
- package/src/runtime/query/checkpoint.ts +23 -9
- package/src/runtime/query/context.ts +9 -0
- package/src/runtime/query/index.ts +148 -29
- package/src/runtime/query/iteration/index.ts +316 -831
- package/src/runtime/query/iteration/phases/checkpoint.ts +32 -2
- package/src/runtime/query/iteration/phases/compaction.ts +89 -3
- package/src/runtime/query/iteration/phases/context.ts +59 -3
- package/src/runtime/query/iteration/phases/hitl-abort-race.test.ts +86 -0
- package/src/runtime/query/iteration/phases/hitl-answer-question.test.ts +139 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +7 -3
- package/src/runtime/query/iteration/phases/working-memory-compaction.test.ts +215 -0
- package/src/runtime/query/iteration/phases/working-memory.ts +86 -0
- package/src/runtime/query/iteration/stream-turn.ts +396 -0
- package/src/runtime/query/replay/list.ts +38 -4
- package/src/runtime/query/replay/prepare.ts +37 -4
- package/src/session/workspace/__tests__/shared-run.test.ts +1 -1
- package/src/session/workspace/shared-run.ts +1 -0
- package/src/skills/loader.test.ts +8 -8
- package/src/store/index.ts +1 -0
- package/src/store/run/checkpoint-disk.ts +67 -0
- package/src/streaming/coalesce.test.ts +3 -3
- package/src/tools/builtins/__tests__/payload-budget.test.ts +63 -0
- package/src/tools/builtins/__tests__/read-file.test.ts +12 -0
- package/src/tools/builtins/read-file.ts +50 -2
- package/src/tools/builtins/search-tools.ts +38 -7
- package/src/tools/coordinator/__tests__/approve-plan.test.ts +139 -0
- package/src/tools/coordinator/__tests__/ask-user-question.test.ts +370 -0
- package/src/tools/coordinator/index.ts +227 -17
- package/src/types/agent/index.ts +1 -0
- package/src/types/agent/reactive.ts +17 -0
- package/src/types/agent/supervisor.ts +20 -0
- package/src/types/agent/working-memory.ts +29 -0
- package/src/types/common/index.ts +19 -0
- package/src/types/common/usage-merge.test.ts +53 -0
- package/src/types/connector/mcp.ts +14 -2
- package/src/types/hitl/index.ts +52 -1
- package/src/types/provider/chat.ts +10 -0
- package/src/types/provider/config.ts +18 -0
- package/src/types/provider/interface.ts +11 -0
- package/src/types/run/checkpoint-store.ts +72 -0
- package/src/types/run/config.ts +30 -0
- package/src/types/run/events.ts +13 -0
- package/src/types/run/index.ts +1 -0
- package/src/version.ts +26 -4
- package/dist/runtime/query/continuation.d.ts.map +0 -1
- package/dist/runtime/query/continuation.js.map +0 -1
|
@@ -0,0 +1,322 @@
|
|
|
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 type { RunPersistence } from '../../../manager/run/persistence.js'
|
|
8
|
+
import { ToolRegistry } from '../../../registry/tool/execute.js'
|
|
9
|
+
import type { CheckpointId, IterationCheckpoint } from '../../../types/hitl/index.js'
|
|
10
|
+
import type { RunId, SessionId, TenantId } from '../../../types/ids/index.js'
|
|
11
|
+
import { createUserMessage } from '../../../types/message/index.js'
|
|
12
|
+
import type { LLMProvider, StreamChunk } from '../../../types/provider/index.js'
|
|
13
|
+
import type { CheckpointRunScope, CheckpointStore } from '../../../types/run/checkpoint-store.js'
|
|
14
|
+
import type { RunEvent } from '../../../types/run/index.js'
|
|
15
|
+
import type { ProjectId, ThreadId } from '../../../types/session/ids.js'
|
|
16
|
+
import { CheckpointManager } from '../checkpoint.js'
|
|
17
|
+
import { drainQuery } from '../index.js'
|
|
18
|
+
import { runIterationCheckpoint } from '../iteration/phases/checkpoint.js'
|
|
19
|
+
import type { IterationContext } from '../iteration/phases/context.js'
|
|
20
|
+
|
|
21
|
+
const ZERO_USAGE = {
|
|
22
|
+
promptTokens: 0,
|
|
23
|
+
completionTokens: 0,
|
|
24
|
+
totalTokens: 0,
|
|
25
|
+
cachedTokens: 0,
|
|
26
|
+
cacheWriteTokens: 0,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const ZERO_COST = {
|
|
30
|
+
inputCostPer1M: 0,
|
|
31
|
+
outputCostPer1M: 0,
|
|
32
|
+
totalCost: 0,
|
|
33
|
+
cacheDiscount: 0,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SCOPE: CheckpointRunScope = {
|
|
37
|
+
tenantId: 'tnt_cp_store' as TenantId,
|
|
38
|
+
projectId: 'prj_cp_store' as ProjectId,
|
|
39
|
+
sessionId: 'ses_cp_store' as SessionId,
|
|
40
|
+
runId: 'run_cp_store' as RunId,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* In-memory {@link CheckpointStore} used both as the conformance fixture for
|
|
45
|
+
* `CheckpointManager` and as the injected store in the query()-level test.
|
|
46
|
+
* Keys rows by the full scope so the tests can assert the kernel passes the
|
|
47
|
+
* five-layer attribution through, not just the checkpoint payload.
|
|
48
|
+
*/
|
|
49
|
+
class InMemoryCheckpointStore implements CheckpointStore {
|
|
50
|
+
readonly rows = new Map<string, IterationCheckpoint>()
|
|
51
|
+
readonly seenScopes: CheckpointRunScope[] = []
|
|
52
|
+
|
|
53
|
+
private key(scope: CheckpointRunScope, checkpointId: CheckpointId): string {
|
|
54
|
+
return [scope.tenantId, scope.projectId, scope.sessionId, scope.runId, checkpointId].join('/')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private runPrefix(scope: CheckpointRunScope): string {
|
|
58
|
+
return `${[scope.tenantId, scope.projectId, scope.sessionId, scope.runId].join('/')}/`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async writeCheckpoint(scope: CheckpointRunScope, checkpoint: IterationCheckpoint): Promise<void> {
|
|
62
|
+
this.seenScopes.push(scope)
|
|
63
|
+
this.rows.set(this.key(scope, checkpoint.id), checkpoint)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async readCheckpoint(
|
|
67
|
+
scope: CheckpointRunScope,
|
|
68
|
+
checkpointId: CheckpointId,
|
|
69
|
+
): Promise<IterationCheckpoint | null> {
|
|
70
|
+
return this.rows.get(this.key(scope, checkpointId)) ?? null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async listCheckpoints(scope: CheckpointRunScope): Promise<IterationCheckpoint[]> {
|
|
74
|
+
const prefix = this.runPrefix(scope)
|
|
75
|
+
return [...this.rows.entries()]
|
|
76
|
+
.filter(([key]) => key.startsWith(prefix))
|
|
77
|
+
.map(([, cp]) => cp)
|
|
78
|
+
.sort((a, b) => a.createdAt - b.createdAt)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async deleteCheckpoint(scope: CheckpointRunScope, checkpointId: CheckpointId): Promise<void> {
|
|
82
|
+
this.rows.delete(this.key(scope, checkpointId))
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function makeRunMgrStub(): RunPersistence {
|
|
87
|
+
return {
|
|
88
|
+
id: SCOPE.runId,
|
|
89
|
+
messages: [{ role: 'user', content: 'hello' }],
|
|
90
|
+
tokenUsage: { ...ZERO_USAGE },
|
|
91
|
+
costInfo: { ...ZERO_COST },
|
|
92
|
+
currentIteration: 1,
|
|
93
|
+
getSession: () => ({ startedAt: Date.now() }),
|
|
94
|
+
} as unknown as RunPersistence
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
describe('CheckpointManager against an injected CheckpointStore', () => {
|
|
98
|
+
it('round-trips create → restore → list → prune through the interface', async () => {
|
|
99
|
+
const store = new InMemoryCheckpointStore()
|
|
100
|
+
const mgr = new CheckpointManager(store, SCOPE)
|
|
101
|
+
const runMgr = makeRunMgrStub()
|
|
102
|
+
|
|
103
|
+
const first = await mgr.create(runMgr, 1)
|
|
104
|
+
const second = await mgr.create(runMgr, 2)
|
|
105
|
+
// Deterministic ordering for prune (createdAt can tie at ms resolution).
|
|
106
|
+
const storedFirst = store.rows.get(
|
|
107
|
+
[SCOPE.tenantId, SCOPE.projectId, SCOPE.sessionId, SCOPE.runId, first.id].join('/'),
|
|
108
|
+
)
|
|
109
|
+
const storedSecond = store.rows.get(
|
|
110
|
+
[SCOPE.tenantId, SCOPE.projectId, SCOPE.sessionId, SCOPE.runId, second.id].join('/'),
|
|
111
|
+
)
|
|
112
|
+
if (storedFirst) storedFirst.createdAt = 1_000
|
|
113
|
+
if (storedSecond) storedSecond.createdAt = 2_000
|
|
114
|
+
|
|
115
|
+
// restore reads back through the interface
|
|
116
|
+
const restored = await mgr.restore(first.id)
|
|
117
|
+
expect(restored.id).toBe(first.id)
|
|
118
|
+
expect(restored.iteration).toBe(1)
|
|
119
|
+
|
|
120
|
+
// list sees both, oldest first
|
|
121
|
+
const listed = await mgr.list()
|
|
122
|
+
expect(listed.map((cp) => cp.id)).toEqual([first.id, second.id])
|
|
123
|
+
|
|
124
|
+
// prune deletes oldest-first through deleteCheckpoint
|
|
125
|
+
await mgr.prune(1)
|
|
126
|
+
const remaining = await mgr.list()
|
|
127
|
+
expect(remaining.map((cp) => cp.id)).toEqual([second.id])
|
|
128
|
+
|
|
129
|
+
// every write carried the full five-layer scope
|
|
130
|
+
expect(store.seenScopes.every((s) => s.tenantId === SCOPE.tenantId)).toBe(true)
|
|
131
|
+
expect(store.seenScopes.every((s) => s.sessionId === SCOPE.sessionId)).toBe(true)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('restore throws a descriptive error for a missing checkpoint', async () => {
|
|
135
|
+
const mgr = new CheckpointManager(new InMemoryCheckpointStore(), SCOPE)
|
|
136
|
+
await expect(mgr.restore('cp_missing' as CheckpointId)).rejects.toThrow(
|
|
137
|
+
'Checkpoint not found: cp_missing',
|
|
138
|
+
)
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// ─── cadence + prune via the iteration-checkpoint phase ──────────────────────
|
|
143
|
+
|
|
144
|
+
function makePhaseContext(
|
|
145
|
+
store: InMemoryCheckpointStore,
|
|
146
|
+
runConfig: { checkpointEvery?: number; pruneKeepLast?: number },
|
|
147
|
+
): { ctx: IterationContext; events: RunEvent[] } {
|
|
148
|
+
const events: RunEvent[] = []
|
|
149
|
+
const ctx = {
|
|
150
|
+
runConfig: {
|
|
151
|
+
model: 'mock-model',
|
|
152
|
+
timeoutMs: 5_000,
|
|
153
|
+
tokenBudget: 100_000,
|
|
154
|
+
...runConfig,
|
|
155
|
+
},
|
|
156
|
+
runMgr: makeRunMgrStub(),
|
|
157
|
+
checkpointMgr: new CheckpointManager(store, SCOPE),
|
|
158
|
+
emitEvent: async (event: RunEvent) => {
|
|
159
|
+
events.push(event)
|
|
160
|
+
},
|
|
161
|
+
drainPending: function* (): Generator<RunEvent> {},
|
|
162
|
+
resumeHandler: async () => ({ action: 'continue' as const }),
|
|
163
|
+
} as unknown as IterationContext
|
|
164
|
+
return { ctx, events }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function drivePhase(ctx: IterationContext, iterationNum: number): Promise<void> {
|
|
168
|
+
const gen = runIterationCheckpoint(ctx, iterationNum)
|
|
169
|
+
let result = await gen.next()
|
|
170
|
+
while (!result.done) {
|
|
171
|
+
result = await gen.next()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
describe('iteration checkpoint cadence (checkpointEvery)', () => {
|
|
176
|
+
it('defaults to a checkpoint on every iteration', async () => {
|
|
177
|
+
const store = new InMemoryCheckpointStore()
|
|
178
|
+
const { ctx, events } = makePhaseContext(store, {})
|
|
179
|
+
|
|
180
|
+
await drivePhase(ctx, 1)
|
|
181
|
+
await drivePhase(ctx, 2)
|
|
182
|
+
await drivePhase(ctx, 3)
|
|
183
|
+
|
|
184
|
+
expect(store.rows.size).toBe(3)
|
|
185
|
+
expect(events.filter((e) => e.type === 'checkpoint_created')).toHaveLength(3)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('checkpointEvery: 2 checkpoints iterations 1, 3, 5 and skips the rest', async () => {
|
|
189
|
+
const store = new InMemoryCheckpointStore()
|
|
190
|
+
const { ctx, events } = makePhaseContext(store, { checkpointEvery: 2 })
|
|
191
|
+
|
|
192
|
+
for (const iteration of [1, 2, 3, 4, 5]) {
|
|
193
|
+
await drivePhase(ctx, iteration)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const created = events.filter(
|
|
197
|
+
(e): e is Extract<RunEvent, { type: 'checkpoint_created' }> =>
|
|
198
|
+
e.type === 'checkpoint_created',
|
|
199
|
+
)
|
|
200
|
+
expect(created.map((e) => e.iteration)).toEqual([1, 3, 5])
|
|
201
|
+
expect(store.rows.size).toBe(3)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('pruneKeepLast keeps only the newest N checkpoints after each create', async () => {
|
|
205
|
+
const store = new InMemoryCheckpointStore()
|
|
206
|
+
const { ctx } = makePhaseContext(store, { pruneKeepLast: 2 })
|
|
207
|
+
|
|
208
|
+
// Make createdAt strictly increasing so prune order is deterministic.
|
|
209
|
+
let tick = 0
|
|
210
|
+
const originalWrite = store.writeCheckpoint.bind(store)
|
|
211
|
+
store.writeCheckpoint = async (scope, checkpoint) => {
|
|
212
|
+
tick += 1
|
|
213
|
+
await originalWrite(scope, { ...checkpoint, createdAt: tick })
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await drivePhase(ctx, 1)
|
|
217
|
+
await drivePhase(ctx, 2)
|
|
218
|
+
await drivePhase(ctx, 3)
|
|
219
|
+
await drivePhase(ctx, 4)
|
|
220
|
+
|
|
221
|
+
const remaining = await store.listCheckpoints(SCOPE)
|
|
222
|
+
expect(remaining).toHaveLength(2)
|
|
223
|
+
expect(remaining.map((cp) => cp.iteration)).toEqual([3, 4])
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
// ─── query()-level injection ─────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
class OneToolCallProvider implements LLMProvider {
|
|
230
|
+
readonly id = 'one-tool-call'
|
|
231
|
+
readonly name = 'One Tool Call Provider'
|
|
232
|
+
calls = 0
|
|
233
|
+
|
|
234
|
+
async *chatStream(): AsyncIterable<StreamChunk> {
|
|
235
|
+
this.calls += 1
|
|
236
|
+
|
|
237
|
+
if (this.calls === 1) {
|
|
238
|
+
yield {
|
|
239
|
+
id: 'msg_1',
|
|
240
|
+
delta: {
|
|
241
|
+
toolCalls: [
|
|
242
|
+
{
|
|
243
|
+
index: 0,
|
|
244
|
+
id: 'toolu_echo_1',
|
|
245
|
+
type: 'function',
|
|
246
|
+
function: { name: 'echo', arguments: '{"text":"hi"}' },
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
yield {
|
|
252
|
+
id: 'msg_1',
|
|
253
|
+
delta: { toolCallEnd: { index: 0, id: 'toolu_echo_1' } },
|
|
254
|
+
}
|
|
255
|
+
yield {
|
|
256
|
+
id: 'msg_1',
|
|
257
|
+
delta: {},
|
|
258
|
+
finishReason: 'tool_calls',
|
|
259
|
+
usage: ZERO_USAGE,
|
|
260
|
+
}
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
yield { id: 'msg_2', delta: { content: 'done' } }
|
|
265
|
+
yield { id: 'msg_2', delta: {}, finishReason: 'stop', usage: ZERO_USAGE }
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
describe('query() with an injected checkpointStore', () => {
|
|
270
|
+
let workdirs: string[] = []
|
|
271
|
+
|
|
272
|
+
afterEach(async () => {
|
|
273
|
+
await Promise.all(workdirs.map((dir) => rm(dir, { recursive: true, force: true })))
|
|
274
|
+
workdirs = []
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('persists iteration checkpoints into the injected store, keyed by run scope', async () => {
|
|
278
|
+
const provider = new OneToolCallProvider()
|
|
279
|
+
const store = new InMemoryCheckpointStore()
|
|
280
|
+
const tools = new ToolRegistry()
|
|
281
|
+
tools.register({
|
|
282
|
+
name: 'echo',
|
|
283
|
+
description: 'echo the text back',
|
|
284
|
+
inputSchema: z.object({ text: z.string() }),
|
|
285
|
+
execute: async () => ({ success: true, output: 'hi' }),
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
const workingDirectory = await mkdtemp(join(tmpdir(), 'namzu-checkpoint-store-'))
|
|
289
|
+
workdirs.push(workingDirectory)
|
|
290
|
+
|
|
291
|
+
const run = await drainQuery({
|
|
292
|
+
provider,
|
|
293
|
+
tools,
|
|
294
|
+
checkpointStore: store,
|
|
295
|
+
runConfig: {
|
|
296
|
+
model: 'mock-model',
|
|
297
|
+
timeoutMs: 5_000,
|
|
298
|
+
tokenBudget: 100_000,
|
|
299
|
+
maxIterations: 3,
|
|
300
|
+
maxResponseTokens: 256,
|
|
301
|
+
},
|
|
302
|
+
agentId: 'agent_test',
|
|
303
|
+
agentName: 'Test Agent',
|
|
304
|
+
messages: [createUserMessage('use the echo tool')],
|
|
305
|
+
workingDirectory,
|
|
306
|
+
sessionId: 'ses_cp_inject' as SessionId,
|
|
307
|
+
threadId: 'thd_cp_inject' as ThreadId,
|
|
308
|
+
projectId: 'prj_cp_inject' as ProjectId,
|
|
309
|
+
tenantId: 'tnt_cp_inject' as TenantId,
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
expect(run.status).toBe('completed')
|
|
313
|
+
// The tool-call iteration produced at least one checkpoint, and it
|
|
314
|
+
// landed in the injected store — not on disk.
|
|
315
|
+
expect(store.rows.size).toBeGreaterThan(0)
|
|
316
|
+
const scope = store.seenScopes[0]
|
|
317
|
+
expect(scope?.tenantId).toBe('tnt_cp_inject')
|
|
318
|
+
expect(scope?.projectId).toBe('prj_cp_inject')
|
|
319
|
+
expect(scope?.sessionId).toBe('ses_cp_inject')
|
|
320
|
+
expect(scope?.runId).toBe(run.id)
|
|
321
|
+
})
|
|
322
|
+
})
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import type { RunDiskStore } from '../../../store/run/disk.js'
|
|
3
2
|
import type { CheckpointId, IterationCheckpoint } from '../../../types/hitl/index.js'
|
|
4
|
-
import type { EmergencySaveId, RunId } from '../../../types/ids/index.js'
|
|
3
|
+
import type { EmergencySaveId, RunId, SessionId, TenantId } from '../../../types/ids/index.js'
|
|
4
|
+
import type { CheckpointRunScope, CheckpointStore } from '../../../types/run/checkpoint-store.js'
|
|
5
5
|
import type { EmergencySaveData } from '../../../types/run/emergency.js'
|
|
6
|
+
import type { ProjectId } from '../../../types/session/ids.js'
|
|
6
7
|
import { CheckpointManager, projectEmergencyToCheckpoint } from '../checkpoint.js'
|
|
7
8
|
|
|
9
|
+
const TEST_SCOPE: CheckpointRunScope = {
|
|
10
|
+
tenantId: 'tnt_test' as TenantId,
|
|
11
|
+
projectId: 'prj_test' as ProjectId,
|
|
12
|
+
sessionId: 'ses_test' as SessionId,
|
|
13
|
+
runId: 'run_test' as RunId,
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
function makeCheckpoint(overrides: Partial<IterationCheckpoint> = {}): IterationCheckpoint {
|
|
9
17
|
return {
|
|
10
18
|
id: 'cp_test_a' as CheckpointId,
|
|
@@ -30,10 +38,10 @@ function makeCheckpoint(overrides: Partial<IterationCheckpoint> = {}): Iteration
|
|
|
30
38
|
}
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
function makeStoreStub(checkpoints: IterationCheckpoint[]):
|
|
41
|
+
function makeStoreStub(checkpoints: IterationCheckpoint[]): CheckpointStore {
|
|
34
42
|
return {
|
|
35
43
|
listCheckpoints: async () => checkpoints,
|
|
36
|
-
} as unknown as
|
|
44
|
+
} as unknown as CheckpointStore
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
describe('CheckpointManager.listEntries', () => {
|
|
@@ -60,7 +68,7 @@ describe('CheckpointManager.listEntries', () => {
|
|
|
60
68
|
}),
|
|
61
69
|
])
|
|
62
70
|
|
|
63
|
-
const mgr = new CheckpointManager(store)
|
|
71
|
+
const mgr = new CheckpointManager(store, TEST_SCOPE)
|
|
64
72
|
const entries = await mgr.listEntries()
|
|
65
73
|
|
|
66
74
|
expect(entries).toHaveLength(2)
|
|
@@ -81,7 +89,7 @@ describe('CheckpointManager.listEntries', () => {
|
|
|
81
89
|
})
|
|
82
90
|
|
|
83
91
|
it('returns empty array when no checkpoints exist', async () => {
|
|
84
|
-
const mgr = new CheckpointManager(makeStoreStub([]))
|
|
92
|
+
const mgr = new CheckpointManager(makeStoreStub([]), TEST_SCOPE)
|
|
85
93
|
expect(await mgr.listEntries()).toEqual([])
|
|
86
94
|
})
|
|
87
95
|
|
|
@@ -93,6 +101,7 @@ describe('CheckpointManager.listEntries', () => {
|
|
|
93
101
|
branchStack: [{ agentId: 'a', decision: 'd', confidence: 1, timestamp: 0 }],
|
|
94
102
|
}),
|
|
95
103
|
]),
|
|
104
|
+
TEST_SCOPE,
|
|
96
105
|
)
|
|
97
106
|
const [entry] = await mgr.listEntries()
|
|
98
107
|
expect(entry).not.toHaveProperty('tokenUsage')
|
|
@@ -159,10 +159,12 @@ describe('query deferred tool discovery', () => {
|
|
|
159
159
|
it('does not let search_tools reveal or activate deferred tools outside allowedTools', async () => {
|
|
160
160
|
const tools = new ToolRegistry()
|
|
161
161
|
registerDeferredDocumentTool(tools)
|
|
162
|
-
registerDeferredDocumentTool(tools, '
|
|
162
|
+
registerDeferredDocumentTool(tools, 'dangerous_purge_document')
|
|
163
163
|
|
|
164
|
+
// 'dangerous' matches only the out-of-allowlist tool ('delete'-style
|
|
165
|
+
// CRUD verbs are stop tokens and never match anything by themselves).
|
|
164
166
|
const result = await SearchToolsTool.execute(
|
|
165
|
-
{ query: '
|
|
167
|
+
{ query: 'dangerous' },
|
|
166
168
|
{
|
|
167
169
|
runId: 'run_deferred_allowed_tools' as RunId,
|
|
168
170
|
workingDirectory: '/tmp',
|
|
@@ -175,9 +177,54 @@ describe('query deferred tool discovery', () => {
|
|
|
175
177
|
)
|
|
176
178
|
|
|
177
179
|
expect(result.success).toBe(true)
|
|
178
|
-
expect(result.output).toContain('No deferred tools matching "
|
|
179
|
-
expect(result.output).not.toContain('
|
|
180
|
+
expect(result.output).toContain('No deferred tools matching "dangerous"')
|
|
181
|
+
expect(result.output).not.toContain('dangerous_purge_document')
|
|
180
182
|
expect(tools.getAvailability('generate_document')).toBe('deferred')
|
|
181
|
-
expect(tools.getAvailability('
|
|
183
|
+
expect(tools.getAvailability('dangerous_purge_document')).toBe('deferred')
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('activates only the top-5 ranked matches and reports near-misses without activating', async () => {
|
|
187
|
+
const tools = new ToolRegistry()
|
|
188
|
+
// Eight deferred tools that all match "invoice" equally by name; the
|
|
189
|
+
// alphabetical tie-break makes the top-5 cut deterministic.
|
|
190
|
+
const names = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'].map((s) => `invoice_${s}`)
|
|
191
|
+
for (const name of names) {
|
|
192
|
+
tools.register(
|
|
193
|
+
{
|
|
194
|
+
name,
|
|
195
|
+
description: `Billing helper ${name.slice(-1)}.`,
|
|
196
|
+
inputSchema: z.object({ id: z.string() }),
|
|
197
|
+
execute: async () => ({ success: true, output: 'ok' }),
|
|
198
|
+
},
|
|
199
|
+
'deferred',
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const result = await SearchToolsTool.execute(
|
|
204
|
+
{ query: 'invoice' },
|
|
205
|
+
{
|
|
206
|
+
runId: 'run_deferred_top_k' as RunId,
|
|
207
|
+
workingDirectory: '/tmp',
|
|
208
|
+
abortSignal: new AbortController().signal,
|
|
209
|
+
env: {},
|
|
210
|
+
log: () => undefined,
|
|
211
|
+
toolRegistry: tools,
|
|
212
|
+
},
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
expect(result.success).toBe(true)
|
|
216
|
+
expect(result.output).toContain('Activated 5 tool(s)')
|
|
217
|
+
expect(result.output).toContain('NOT loaded')
|
|
218
|
+
expect(result.data).toMatchObject({
|
|
219
|
+
activated: ['invoice_a', 'invoice_b', 'invoice_c', 'invoice_d', 'invoice_e'],
|
|
220
|
+
count: 5,
|
|
221
|
+
nearMisses: ['invoice_f', 'invoice_g', 'invoice_h'],
|
|
222
|
+
})
|
|
223
|
+
for (const name of ['invoice_a', 'invoice_b', 'invoice_c', 'invoice_d', 'invoice_e']) {
|
|
224
|
+
expect(tools.getAvailability(name)).toBe('active')
|
|
225
|
+
}
|
|
226
|
+
for (const name of ['invoice_f', 'invoice_g', 'invoice_h']) {
|
|
227
|
+
expect(tools.getAvailability(name)).toBe('deferred')
|
|
228
|
+
}
|
|
182
229
|
})
|
|
183
230
|
})
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { readFile, rm } from 'node:fs/promises'
|
|
2
|
+
import { mkdtemp } from 'node:fs/promises'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { ToolRegistry } from '../../../registry/tool/execute.js'
|
|
8
|
+
import { EditTool, WriteFileTool } from '../../../tools/builtins/index.js'
|
|
9
|
+
import type { SessionId, TenantId } from '../../../types/ids/index.js'
|
|
10
|
+
import { createUserMessage } from '../../../types/message/index.js'
|
|
11
|
+
import type { LLMProvider, StreamChunk } from '../../../types/provider/index.js'
|
|
12
|
+
import type { RunEvent } from '../../../types/run/index.js'
|
|
13
|
+
import type { ProjectId, ThreadId } from '../../../types/session/ids.js'
|
|
14
|
+
import { drainQuery } from '../index.js'
|
|
15
|
+
|
|
16
|
+
const ZERO_USAGE = {
|
|
17
|
+
promptTokens: 0,
|
|
18
|
+
completionTokens: 0,
|
|
19
|
+
totalTokens: 0,
|
|
20
|
+
cachedTokens: 0,
|
|
21
|
+
cacheWriteTokens: 0,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ToolStep {
|
|
25
|
+
readonly name: string
|
|
26
|
+
readonly input: Record<string, unknown>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class ScriptedLongDocumentProvider implements LLMProvider {
|
|
30
|
+
readonly id = 'scripted-long-document'
|
|
31
|
+
readonly name = 'Scripted Long Document Provider'
|
|
32
|
+
calls = 0
|
|
33
|
+
|
|
34
|
+
constructor(private readonly steps: readonly ToolStep[]) {}
|
|
35
|
+
|
|
36
|
+
async *chatStream(): AsyncIterable<StreamChunk> {
|
|
37
|
+
const step = this.steps[this.calls]
|
|
38
|
+
this.calls += 1
|
|
39
|
+
|
|
40
|
+
if (!step) {
|
|
41
|
+
yield {
|
|
42
|
+
id: 'msg_done',
|
|
43
|
+
delta: { content: 'Long document created and verified.' },
|
|
44
|
+
finishReason: 'stop',
|
|
45
|
+
usage: ZERO_USAGE,
|
|
46
|
+
}
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
yield {
|
|
51
|
+
id: `msg_${this.calls}`,
|
|
52
|
+
delta: {
|
|
53
|
+
toolCalls: [
|
|
54
|
+
{
|
|
55
|
+
index: 0,
|
|
56
|
+
id: `toolu_${this.calls}`,
|
|
57
|
+
type: 'function',
|
|
58
|
+
function: { name: step.name },
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
yield {
|
|
64
|
+
id: `msg_${this.calls}`,
|
|
65
|
+
delta: {
|
|
66
|
+
toolCalls: [
|
|
67
|
+
{
|
|
68
|
+
index: 0,
|
|
69
|
+
id: `toolu_${this.calls}`,
|
|
70
|
+
function: { arguments: JSON.stringify(step.input) },
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
yield {
|
|
76
|
+
id: `msg_${this.calls}`,
|
|
77
|
+
delta: {},
|
|
78
|
+
finishReason: 'tool_calls',
|
|
79
|
+
usage: ZERO_USAGE,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe('query long-document tool flow', () => {
|
|
85
|
+
const workdirs: string[] = []
|
|
86
|
+
|
|
87
|
+
afterEach(async () => {
|
|
88
|
+
await Promise.all(workdirs.map((dir) => rm(dir, { recursive: true, force: true })))
|
|
89
|
+
workdirs.length = 0
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('executes the canonical write skeleton plus bounded edit chunks flow end to end', async () => {
|
|
93
|
+
const workingDirectory = await mkdtemp(join(tmpdir(), 'namzu-query-long-doc-'))
|
|
94
|
+
workdirs.push(workingDirectory)
|
|
95
|
+
|
|
96
|
+
const chunks = Array.from({ length: 4 }, (_, sectionIndex) => {
|
|
97
|
+
const lines = Array.from({ length: 40 }, (_, lineIndex) => {
|
|
98
|
+
return `Section ${sectionIndex + 1}.${lineIndex + 1}: this content is intentionally long enough to require repeated bounded edit calls.`
|
|
99
|
+
})
|
|
100
|
+
const chunk = [`## Section ${sectionIndex + 1}`, ...lines, ''].join('\n')
|
|
101
|
+
expect(chunk.length).toBeLessThan(12_000)
|
|
102
|
+
return chunk
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const provider = new ScriptedLongDocumentProvider([
|
|
106
|
+
{
|
|
107
|
+
name: 'write',
|
|
108
|
+
input: {
|
|
109
|
+
path: 'outputs/long-document-flow.md',
|
|
110
|
+
content: '# Long document flow\n\n{{BODY}}\n',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'edit',
|
|
115
|
+
input: {
|
|
116
|
+
path: 'outputs/long-document-flow.md',
|
|
117
|
+
oldStr: '{{BODY}}',
|
|
118
|
+
newStr: chunks[0],
|
|
119
|
+
replace_all: false,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
...chunks.slice(1).map((chunk) => ({
|
|
123
|
+
name: 'edit',
|
|
124
|
+
input: {
|
|
125
|
+
path: 'outputs/long-document-flow.md',
|
|
126
|
+
insertLine: 'end',
|
|
127
|
+
newStr: chunk,
|
|
128
|
+
replace_all: false,
|
|
129
|
+
},
|
|
130
|
+
})),
|
|
131
|
+
])
|
|
132
|
+
const tools = new ToolRegistry()
|
|
133
|
+
tools.register(WriteFileTool)
|
|
134
|
+
tools.register(EditTool)
|
|
135
|
+
const events: RunEvent[] = []
|
|
136
|
+
|
|
137
|
+
const run = await drainQuery(
|
|
138
|
+
{
|
|
139
|
+
provider,
|
|
140
|
+
tools,
|
|
141
|
+
runConfig: {
|
|
142
|
+
model: 'mock-model',
|
|
143
|
+
timeoutMs: 5_000,
|
|
144
|
+
tokenBudget: 100_000,
|
|
145
|
+
maxIterations: 8,
|
|
146
|
+
maxResponseTokens: 256,
|
|
147
|
+
},
|
|
148
|
+
agentId: 'agent_long_doc',
|
|
149
|
+
agentName: 'Long Document Agent',
|
|
150
|
+
messages: [createUserMessage('create a long document')],
|
|
151
|
+
workingDirectory,
|
|
152
|
+
sessionId: 'ses_long_document_flow' as SessionId,
|
|
153
|
+
threadId: 'thd_long_document_flow' as ThreadId,
|
|
154
|
+
projectId: 'prj_long_document_flow' as ProjectId,
|
|
155
|
+
tenantId: 'tnt_long_document_flow' as TenantId,
|
|
156
|
+
},
|
|
157
|
+
(event) => {
|
|
158
|
+
events.push(event)
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const final = await readFile(join(workingDirectory, 'outputs/long-document-flow.md'), 'utf-8')
|
|
163
|
+
const executingTools = events
|
|
164
|
+
.filter(
|
|
165
|
+
(event): event is Extract<RunEvent, { type: 'tool_executing' }> =>
|
|
166
|
+
event.type === 'tool_executing',
|
|
167
|
+
)
|
|
168
|
+
.map((event) => event.toolName)
|
|
169
|
+
|
|
170
|
+
expect(run.status).toBe('completed')
|
|
171
|
+
expect(run.result).toBe('Long document created and verified.')
|
|
172
|
+
expect(provider.calls).toBe(6)
|
|
173
|
+
expect(executingTools).toEqual(['write', 'edit', 'edit', 'edit', 'edit'])
|
|
174
|
+
expect(final).not.toContain('{{BODY}}')
|
|
175
|
+
expect(final.split('\n').length).toBeGreaterThan(160)
|
|
176
|
+
expect(final).toContain('## Section 1')
|
|
177
|
+
expect(final).toContain('## Section 4')
|
|
178
|
+
expect(final).toContain('Section 4.40')
|
|
179
|
+
})
|
|
180
|
+
})
|