@namzu/sdk 1.1.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 +86 -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/{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/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/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 +3 -2
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +2 -2
- package/dist/public-runtime.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__/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/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 +20 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +75 -6
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +3 -57
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +246 -735
- 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 +22 -0
- package/dist/runtime/query/iteration/phases/checkpoint.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +1 -2
- 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/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/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/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/package.json +1 -1
- package/src/bridge/a2a/mapper.ts +3 -0
- package/src/bridge/sse/mapper.ts +5 -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/provider/capabilities.ts +47 -0
- package/src/provider/index.ts +5 -0
- package/src/public-runtime.ts +4 -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__/orphan-task-turn-end.test.ts +127 -0
- package/src/runtime/query/checkpoint.ts +23 -9
- package/src/runtime/query/context.ts +9 -0
- package/src/runtime/query/index.ts +115 -28
- package/src/runtime/query/iteration/index.ts +298 -905
- package/src/runtime/query/iteration/phases/checkpoint.ts +25 -0
- package/src/runtime/query/iteration/phases/context.ts +1 -3
- 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/store/index.ts +1 -0
- package/src/store/run/checkpoint-disk.ts +67 -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/dist/runtime/query/continuation.d.ts.map +0 -1
- package/dist/runtime/query/continuation.js.map +0 -1
package/src/constants/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './rag/index.js'
|
|
|
7
7
|
export * from './tools/index.js'
|
|
8
8
|
export * from './telemetry/index.js'
|
|
9
9
|
export * from './agent/index.js'
|
|
10
|
+
export * from './continuation.js'
|
|
10
11
|
export * from './limits.js'
|
|
11
12
|
export * from './bus/index.js'
|
|
12
13
|
export * from './verification/index.js'
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { AUTO_CONTINUATION_USER_MESSAGE } from '../../constants/continuation.js'
|
|
1
2
|
import { EMPTY_TOKEN_USAGE } from '../../constants/limits.js'
|
|
2
|
-
import {
|
|
3
|
+
import { DiskCheckpointStore } from '../../store/run/checkpoint-disk.js'
|
|
3
4
|
import { RunDiskStore } from '../../store/run/disk.js'
|
|
4
5
|
import { type CostInfo, type TokenUsage, accumulateTokenUsage } from '../../types/common/index.js'
|
|
5
6
|
import type { RunId, SessionId, TenantId } from '../../types/ids/index.js'
|
|
6
7
|
import type { Message } from '../../types/message/index.js'
|
|
8
|
+
import type { CheckpointRunScope, CheckpointStore } from '../../types/run/checkpoint-store.js'
|
|
7
9
|
import type { EmergencySaveData } from '../../types/run/emergency.js'
|
|
8
10
|
import type { Run, RunPersistenceConfig, StopReason } from '../../types/run/index.js'
|
|
9
11
|
import type { ProjectId, ThreadId } from '../../types/session/ids.js'
|
|
@@ -14,6 +16,7 @@ import type { Logger } from '../../utils/logger.js'
|
|
|
14
16
|
export class RunPersistence {
|
|
15
17
|
private run: Run
|
|
16
18
|
private runStore: RunDiskStore
|
|
19
|
+
private checkpointStore: CheckpointStore
|
|
17
20
|
private pricing?: ModelPricing
|
|
18
21
|
private log: Logger
|
|
19
22
|
private readonly _sessionId: SessionId
|
|
@@ -34,6 +37,15 @@ export class RunPersistence {
|
|
|
34
37
|
logger: config.log,
|
|
35
38
|
})
|
|
36
39
|
|
|
40
|
+
// Checkpoints go through the injectable seam; the disk layout under
|
|
41
|
+
// `outputDir` (same tree the runStore writes to) stays the default.
|
|
42
|
+
this.checkpointStore =
|
|
43
|
+
config.checkpointStore ??
|
|
44
|
+
new DiskCheckpointStore({
|
|
45
|
+
baseDir: config.outputDir,
|
|
46
|
+
logger: config.log,
|
|
47
|
+
})
|
|
48
|
+
|
|
37
49
|
this.run = {
|
|
38
50
|
id: config.runId,
|
|
39
51
|
status: 'idle',
|
|
@@ -109,6 +121,27 @@ export class RunPersistence {
|
|
|
109
121
|
return this.runStore
|
|
110
122
|
}
|
|
111
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Checkpoint persistence for this run — the injected
|
|
126
|
+
* {@link CheckpointStore} when the host provided one, otherwise the
|
|
127
|
+
* disk default. Pair with {@link getRunScope} when constructing a
|
|
128
|
+
* `CheckpointManager`.
|
|
129
|
+
*/
|
|
130
|
+
getCheckpointStore(): CheckpointStore {
|
|
131
|
+
return this.checkpointStore
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Full five-layer scope key for this run's checkpoint operations. */
|
|
135
|
+
getRunScope(): CheckpointRunScope {
|
|
136
|
+
return {
|
|
137
|
+
tenantId: this._tenantId,
|
|
138
|
+
projectId: this._projectId,
|
|
139
|
+
sessionId: this._sessionId,
|
|
140
|
+
runId: this.run.id,
|
|
141
|
+
parentRunId: this.run.parentRunId,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
112
145
|
getRunDir(): string | null {
|
|
113
146
|
return this.runStore.getRunDir()
|
|
114
147
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { LLMProvider, ProviderCapabilities } from '../types/provider/index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fully-resolved capability set: every flag present, no optionals left.
|
|
5
|
+
* What the query runtime branches on after applying the permissive default.
|
|
6
|
+
*/
|
|
7
|
+
export interface ResolvedProviderCapabilities {
|
|
8
|
+
supportsTools: boolean
|
|
9
|
+
supportsStreaming: boolean
|
|
10
|
+
supportsFunctionCalling: boolean
|
|
11
|
+
supportsVision: boolean
|
|
12
|
+
maxOutputTokens?: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* THE permissive default: a provider that declares nothing is assumed to
|
|
17
|
+
* handle everything — exactly the pre-negotiation behavior, so third-party
|
|
18
|
+
* providers written before `LLMProvider.capabilities` existed keep working
|
|
19
|
+
* without a warning storm. Shipped drivers always declare explicitly.
|
|
20
|
+
*/
|
|
21
|
+
export const PERMISSIVE_PROVIDER_CAPABILITIES: ResolvedProviderCapabilities = {
|
|
22
|
+
supportsTools: true,
|
|
23
|
+
supportsStreaming: true,
|
|
24
|
+
supportsFunctionCalling: true,
|
|
25
|
+
supportsVision: true,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a provider's declared capabilities against the permissive
|
|
30
|
+
* default. Absent declaration ⇒ everything `true`; a partial declaration
|
|
31
|
+
* (e.g. one missing `supportsVision`, added later) fills the gap
|
|
32
|
+
* permissively per field.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveProviderCapabilities(
|
|
35
|
+
provider: Pick<LLMProvider, 'capabilities'>,
|
|
36
|
+
): ResolvedProviderCapabilities {
|
|
37
|
+
const declared: ProviderCapabilities | undefined = provider.capabilities
|
|
38
|
+
return {
|
|
39
|
+
supportsTools: declared?.supportsTools ?? PERMISSIVE_PROVIDER_CAPABILITIES.supportsTools,
|
|
40
|
+
supportsStreaming:
|
|
41
|
+
declared?.supportsStreaming ?? PERMISSIVE_PROVIDER_CAPABILITIES.supportsStreaming,
|
|
42
|
+
supportsFunctionCalling:
|
|
43
|
+
declared?.supportsFunctionCalling ?? PERMISSIVE_PROVIDER_CAPABILITIES.supportsFunctionCalling,
|
|
44
|
+
supportsVision: declared?.supportsVision ?? PERMISSIVE_PROVIDER_CAPABILITIES.supportsVision,
|
|
45
|
+
maxOutputTokens: declared?.maxOutputTokens,
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/provider/index.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export { ProviderRegistry, UnknownProviderError, DuplicateProviderError } from './registry.js'
|
|
2
2
|
export { MockLLMProvider } from './mock.js'
|
|
3
3
|
export { registerMock, MOCK_CAPABILITIES } from './mock-register.js'
|
|
4
|
+
export {
|
|
5
|
+
PERMISSIVE_PROVIDER_CAPABILITIES,
|
|
6
|
+
resolveProviderCapabilities,
|
|
7
|
+
} from './capabilities.js'
|
|
8
|
+
export type { ResolvedProviderCapabilities } from './capabilities.js'
|
package/src/public-runtime.ts
CHANGED
|
@@ -99,6 +99,7 @@ export {
|
|
|
99
99
|
|
|
100
100
|
export {
|
|
101
101
|
ActivityStore,
|
|
102
|
+
DiskCheckpointStore,
|
|
102
103
|
DiskMemoryStore,
|
|
103
104
|
DiskTaskStore,
|
|
104
105
|
InMemoryMemoryIndex,
|
|
@@ -146,8 +147,10 @@ export {
|
|
|
146
147
|
DuplicateProviderError,
|
|
147
148
|
MOCK_CAPABILITIES,
|
|
148
149
|
MockLLMProvider,
|
|
150
|
+
PERMISSIVE_PROVIDER_CAPABILITIES,
|
|
149
151
|
ProviderRegistry,
|
|
150
152
|
registerMock,
|
|
153
|
+
resolveProviderCapabilities,
|
|
151
154
|
UnknownProviderError,
|
|
152
155
|
} from './provider/index.js'
|
|
153
156
|
|
|
@@ -248,6 +251,7 @@ export {
|
|
|
248
251
|
|
|
249
252
|
export { wrapProviderWithProbes } from './provider/instrumentation.js'
|
|
250
253
|
export type { ProviderInstrumentationOptions } from './provider/instrumentation.js'
|
|
254
|
+
export type { ResolvedProviderCapabilities } from './provider/capabilities.js'
|
|
251
255
|
export { collect } from './provider/collect.js'
|
|
252
256
|
|
|
253
257
|
export { wrapVaultWithProbes } from './vault/instrumentation.js'
|
package/src/run/reporter.ts
CHANGED
|
@@ -225,6 +225,15 @@ export function createRunReporter(parentLogger?: Logger): RunReporter {
|
|
|
225
225
|
})
|
|
226
226
|
break
|
|
227
227
|
|
|
228
|
+
case 'capability_warning':
|
|
229
|
+
log.warn('Provider capability mismatch', {
|
|
230
|
+
runId: event.runId,
|
|
231
|
+
capability: event.capability,
|
|
232
|
+
providerId: event.providerId,
|
|
233
|
+
message: event.message,
|
|
234
|
+
})
|
|
235
|
+
break
|
|
236
|
+
|
|
228
237
|
default: {
|
|
229
238
|
const _exhaustive: never = event
|
|
230
239
|
throw new Error(`Unhandled run event type: ${(_exhaustive as RunEvent).type}`)
|
|
@@ -0,0 +1,248 @@
|
|
|
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 {
|
|
8
|
+
PERMISSIVE_PROVIDER_CAPABILITIES,
|
|
9
|
+
resolveProviderCapabilities,
|
|
10
|
+
} from '../../../provider/capabilities.js'
|
|
11
|
+
import { ToolRegistry } from '../../../registry/tool/execute.js'
|
|
12
|
+
import type { SessionId, TenantId } from '../../../types/ids/index.js'
|
|
13
|
+
import { createUserMessage } from '../../../types/message/index.js'
|
|
14
|
+
import type {
|
|
15
|
+
ChatCompletionParams,
|
|
16
|
+
LLMProvider,
|
|
17
|
+
ProviderCapabilities,
|
|
18
|
+
StreamChunk,
|
|
19
|
+
} from '../../../types/provider/index.js'
|
|
20
|
+
import type { RunEvent } from '../../../types/run/index.js'
|
|
21
|
+
import type { ProjectId, ThreadId } from '../../../types/session/ids.js'
|
|
22
|
+
import { drainQuery } from '../index.js'
|
|
23
|
+
|
|
24
|
+
const ZERO_USAGE = {
|
|
25
|
+
promptTokens: 0,
|
|
26
|
+
completionTokens: 0,
|
|
27
|
+
totalTokens: 0,
|
|
28
|
+
cachedTokens: 0,
|
|
29
|
+
cacheWriteTokens: 0,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class CapturingProvider implements LLMProvider {
|
|
33
|
+
readonly id = 'capturing'
|
|
34
|
+
readonly name = 'Capturing Provider'
|
|
35
|
+
readonly capabilities?: ProviderCapabilities
|
|
36
|
+
lastParams?: ChatCompletionParams
|
|
37
|
+
|
|
38
|
+
constructor(capabilities?: ProviderCapabilities) {
|
|
39
|
+
this.capabilities = capabilities
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async *chatStream(params: ChatCompletionParams): AsyncIterable<StreamChunk> {
|
|
43
|
+
this.lastParams = params
|
|
44
|
+
yield { id: 'msg_1', delta: { content: 'done' } }
|
|
45
|
+
yield { id: 'msg_1', delta: {}, finishReason: 'stop', usage: ZERO_USAGE }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const NO_TOOLS_CAPABILITIES: ProviderCapabilities = {
|
|
50
|
+
supportsTools: false,
|
|
51
|
+
supportsStreaming: true,
|
|
52
|
+
supportsFunctionCalling: false,
|
|
53
|
+
supportsVision: true,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const NO_VISION_CAPABILITIES: ProviderCapabilities = {
|
|
57
|
+
supportsTools: true,
|
|
58
|
+
supportsStreaming: true,
|
|
59
|
+
supportsFunctionCalling: true,
|
|
60
|
+
supportsVision: false,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function registerEchoTool(tools: ToolRegistry): void {
|
|
64
|
+
tools.register({
|
|
65
|
+
name: 'echo',
|
|
66
|
+
description: 'Echo the text back.',
|
|
67
|
+
inputSchema: z.object({ text: z.string() }),
|
|
68
|
+
execute: async () => ({ success: true, output: 'ok' }),
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function baseParams(provider: LLMProvider, tools: ToolRegistry, workingDirectory: string) {
|
|
73
|
+
return {
|
|
74
|
+
provider,
|
|
75
|
+
tools,
|
|
76
|
+
runConfig: {
|
|
77
|
+
model: 'mock-model',
|
|
78
|
+
timeoutMs: 5_000,
|
|
79
|
+
tokenBudget: 100_000,
|
|
80
|
+
maxIterations: 1,
|
|
81
|
+
maxResponseTokens: 256,
|
|
82
|
+
},
|
|
83
|
+
agentId: 'agent_test',
|
|
84
|
+
agentName: 'Test Agent',
|
|
85
|
+
workingDirectory,
|
|
86
|
+
sessionId: 'ses_capability' as SessionId,
|
|
87
|
+
threadId: 'thd_capability' as ThreadId,
|
|
88
|
+
projectId: 'prj_capability' as ProjectId,
|
|
89
|
+
tenantId: 'tnt_capability' as TenantId,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
describe('resolveProviderCapabilities', () => {
|
|
94
|
+
it('resolves an undeclared provider to the permissive default', () => {
|
|
95
|
+
expect(resolveProviderCapabilities(new CapturingProvider())).toEqual(
|
|
96
|
+
PERMISSIVE_PROVIDER_CAPABILITIES,
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('fills a partial declaration (missing supportsVision) permissively per field', () => {
|
|
101
|
+
const resolved = resolveProviderCapabilities({
|
|
102
|
+
capabilities: {
|
|
103
|
+
supportsTools: false,
|
|
104
|
+
supportsStreaming: true,
|
|
105
|
+
supportsFunctionCalling: false,
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
expect(resolved.supportsTools).toBe(false)
|
|
109
|
+
expect(resolved.supportsVision).toBe(true)
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe('query() capability negotiation', () => {
|
|
114
|
+
let workdirs: string[] = []
|
|
115
|
+
|
|
116
|
+
afterEach(async () => {
|
|
117
|
+
await Promise.all(workdirs.map((dir) => rm(dir, { recursive: true, force: true })))
|
|
118
|
+
workdirs = []
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
async function mkWorkdir(): Promise<string> {
|
|
122
|
+
const dir = await mkdtemp(join(tmpdir(), 'namzu-capability-'))
|
|
123
|
+
workdirs.push(dir)
|
|
124
|
+
return dir
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
it('strips tool surfaces and emits a capability_warning for a no-tools provider', async () => {
|
|
128
|
+
const provider = new CapturingProvider(NO_TOOLS_CAPABILITIES)
|
|
129
|
+
const tools = new ToolRegistry()
|
|
130
|
+
registerEchoTool(tools)
|
|
131
|
+
const events: RunEvent[] = []
|
|
132
|
+
|
|
133
|
+
const run = await drainQuery(
|
|
134
|
+
{
|
|
135
|
+
...baseParams(provider, tools, await mkWorkdir()),
|
|
136
|
+
messages: [createUserMessage('hello')],
|
|
137
|
+
},
|
|
138
|
+
(event) => {
|
|
139
|
+
events.push(event)
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
expect(run.status).toBe('completed')
|
|
144
|
+
|
|
145
|
+
// The model was never told about tools: no tools param on the wire…
|
|
146
|
+
expect(provider.lastParams?.tools).toBeUndefined()
|
|
147
|
+
// …and no tool section in the system prompt.
|
|
148
|
+
const systemPrompt = (provider.lastParams?.messages ?? [])
|
|
149
|
+
.filter((m) => m.role === 'system')
|
|
150
|
+
.map((m) => m.content)
|
|
151
|
+
.join('\n')
|
|
152
|
+
expect(systemPrompt).not.toContain('<available_tools>')
|
|
153
|
+
expect(systemPrompt).not.toContain('echo')
|
|
154
|
+
|
|
155
|
+
// The host got the machine-readable warning.
|
|
156
|
+
const warning = events.find(
|
|
157
|
+
(e): e is Extract<RunEvent, { type: 'capability_warning' }> =>
|
|
158
|
+
e.type === 'capability_warning',
|
|
159
|
+
)
|
|
160
|
+
expect(warning?.capability).toBe('tools')
|
|
161
|
+
expect(warning?.providerId).toBe('capturing')
|
|
162
|
+
expect(warning?.message).toContain('stripped')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('keeps tool surfaces intact for an undeclared (permissive-default) provider', async () => {
|
|
166
|
+
const provider = new CapturingProvider()
|
|
167
|
+
const tools = new ToolRegistry()
|
|
168
|
+
registerEchoTool(tools)
|
|
169
|
+
|
|
170
|
+
const run = await drainQuery({
|
|
171
|
+
...baseParams(provider, tools, await mkWorkdir()),
|
|
172
|
+
messages: [createUserMessage('hello')],
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
expect(run.status).toBe('completed')
|
|
176
|
+
expect(provider.lastParams?.tools?.map((t) => t.function.name)).toContain('echo')
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('emits a vision capability_warning when attachments hit a no-vision provider', async () => {
|
|
180
|
+
const provider = new CapturingProvider(NO_VISION_CAPABILITIES)
|
|
181
|
+
const events: RunEvent[] = []
|
|
182
|
+
|
|
183
|
+
const run = await drainQuery(
|
|
184
|
+
{
|
|
185
|
+
...baseParams(provider, new ToolRegistry(), await mkWorkdir()),
|
|
186
|
+
messages: [
|
|
187
|
+
createUserMessage('what is in this image?', [
|
|
188
|
+
{ data: 'aGVsbG8=', mediaType: 'image/png' },
|
|
189
|
+
]),
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
(event) => {
|
|
193
|
+
events.push(event)
|
|
194
|
+
},
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
expect(run.status).toBe('completed')
|
|
198
|
+
const warning = events.find(
|
|
199
|
+
(e): e is Extract<RunEvent, { type: 'capability_warning' }> =>
|
|
200
|
+
e.type === 'capability_warning',
|
|
201
|
+
)
|
|
202
|
+
expect(warning?.capability).toBe('vision')
|
|
203
|
+
expect(warning?.providerId).toBe('capturing')
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('does not warn when no attachments are present on a no-vision provider', async () => {
|
|
207
|
+
const provider = new CapturingProvider(NO_VISION_CAPABILITIES)
|
|
208
|
+
const events: RunEvent[] = []
|
|
209
|
+
|
|
210
|
+
await drainQuery(
|
|
211
|
+
{
|
|
212
|
+
...baseParams(provider, new ToolRegistry(), await mkWorkdir()),
|
|
213
|
+
messages: [createUserMessage('plain text')],
|
|
214
|
+
},
|
|
215
|
+
(event) => {
|
|
216
|
+
events.push(event)
|
|
217
|
+
},
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
expect(events.some((e) => e.type === 'capability_warning')).toBe(false)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('strictCapabilities: true throws on a tools mismatch instead of degrading', async () => {
|
|
224
|
+
const provider = new CapturingProvider(NO_TOOLS_CAPABILITIES)
|
|
225
|
+
const tools = new ToolRegistry()
|
|
226
|
+
registerEchoTool(tools)
|
|
227
|
+
|
|
228
|
+
await expect(
|
|
229
|
+
drainQuery({
|
|
230
|
+
...baseParams(provider, tools, await mkWorkdir()),
|
|
231
|
+
messages: [createUserMessage('hello')],
|
|
232
|
+
strictCapabilities: true,
|
|
233
|
+
}),
|
|
234
|
+
).rejects.toThrow(/supportsTools: false/)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('strictCapabilities: true throws on a vision mismatch instead of degrading', async () => {
|
|
238
|
+
const provider = new CapturingProvider(NO_VISION_CAPABILITIES)
|
|
239
|
+
|
|
240
|
+
await expect(
|
|
241
|
+
drainQuery({
|
|
242
|
+
...baseParams(provider, new ToolRegistry(), await mkWorkdir()),
|
|
243
|
+
messages: [createUserMessage('describe', [{ data: 'aGVsbG8=', mediaType: 'image/png' }])],
|
|
244
|
+
strictCapabilities: true,
|
|
245
|
+
}),
|
|
246
|
+
).rejects.toThrow(/supportsVision: false/)
|
|
247
|
+
})
|
|
248
|
+
})
|