@namzu/sdk 1.2.0 → 1.4.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 +99 -0
- package/dist/compaction/__tests__/verifier-empty-reply.test.d.ts +12 -0
- package/dist/compaction/__tests__/verifier-empty-reply.test.d.ts.map +1 -0
- package/dist/compaction/__tests__/verifier-empty-reply.test.js +73 -0
- package/dist/compaction/__tests__/verifier-empty-reply.test.js.map +1 -0
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +10 -1
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/config/__tests__/compaction-budget-schema.test.d.ts +18 -0
- package/dist/config/__tests__/compaction-budget-schema.test.d.ts.map +1 -0
- package/dist/config/__tests__/compaction-budget-schema.test.js +96 -0
- package/dist/config/__tests__/compaction-budget-schema.test.js.map +1 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +20 -11
- package/dist/config/runtime.js.map +1 -1
- package/dist/manager/run/persistence.d.ts +2 -1
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +3 -1
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/provider/__tests__/errors.test.d.ts +2 -0
- package/dist/provider/__tests__/errors.test.d.ts.map +1 -0
- package/dist/provider/__tests__/errors.test.js +61 -0
- package/dist/provider/__tests__/errors.test.js.map +1 -0
- package/dist/provider/__tests__/registry.test.d.ts +5 -0
- package/dist/provider/__tests__/registry.test.d.ts.map +1 -1
- package/dist/provider/__tests__/registry.test.js +186 -1
- package/dist/provider/__tests__/registry.test.js.map +1 -1
- package/dist/provider/errors.d.ts +108 -0
- package/dist/provider/errors.d.ts.map +1 -0
- package/dist/provider/errors.js +311 -0
- package/dist/provider/errors.js.map +1 -0
- package/dist/provider/index.d.ts +3 -1
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +2 -1
- package/dist/provider/index.js.map +1 -1
- package/dist/provider/registry.d.ts +81 -1
- package/dist/provider/registry.d.ts.map +1 -1
- package/dist/provider/registry.js +173 -7
- package/dist/provider/registry.js.map +1 -1
- package/dist/public-runtime.d.ts +11 -11
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +11 -11
- package/dist/public-runtime.js.map +1 -1
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +5 -2
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/registry/tool/execute.test.js +11 -0
- package/dist/registry/tool/execute.test.js.map +1 -1
- package/dist/runtime/query/__tests__/stream-recovery.test.js +54 -1
- package/dist/runtime/query/__tests__/stream-recovery.test.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.d.ts +32 -0
- package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.js +152 -0
- package/dist/runtime/query/iteration/phases/compaction-safe-cut.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +52 -12
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +11 -5
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/result.d.ts.map +1 -1
- package/dist/runtime/query/result.js +11 -1
- package/dist/runtime/query/result.js.map +1 -1
- package/dist/tools/builtins/__tests__/edit.test.js +54 -0
- package/dist/tools/builtins/__tests__/edit.test.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts +1 -1
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +11 -13
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/coordinator/__tests__/task-list.test.js +14 -0
- package/dist/tools/coordinator/__tests__/task-list.test.js.map +1 -1
- package/dist/tools/coordinator/agent.d.ts +6 -8
- package/dist/tools/coordinator/agent.d.ts.map +1 -1
- package/dist/tools/coordinator/agent.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +11 -32
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/defineTool.d.ts +1 -0
- package/dist/tools/defineTool.d.ts.map +1 -1
- package/dist/tools/defineTool.js +1 -0
- package/dist/tools/defineTool.js.map +1 -1
- package/dist/types/provider/config.d.ts +33 -0
- package/dist/types/provider/config.d.ts.map +1 -1
- package/dist/types/provider/error.d.ts +22 -0
- package/dist/types/provider/error.d.ts.map +1 -0
- package/dist/types/provider/error.js +2 -0
- package/dist/types/provider/error.js.map +1 -0
- package/dist/types/provider/index.d.ts +2 -1
- package/dist/types/provider/index.d.ts.map +1 -1
- package/dist/types/run/entity.d.ts +2 -0
- package/dist/types/run/entity.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +2 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/tool/index.d.ts +6 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/compaction/__tests__/verifier-empty-reply.test.ts +96 -0
- package/src/compaction/verifier.ts +10 -1
- package/src/config/__tests__/compaction-budget-schema.test.ts +114 -0
- package/src/config/runtime.ts +20 -11
- package/src/manager/run/persistence.ts +3 -1
- package/src/provider/__tests__/errors.test.ts +85 -0
- package/src/provider/__tests__/registry.test.ts +266 -1
- package/src/provider/errors.ts +344 -0
- package/src/provider/index.ts +22 -1
- package/src/provider/registry.ts +202 -7
- package/src/public-runtime.ts +55 -10
- package/src/registry/tool/execute.test.ts +14 -0
- package/src/registry/tool/execute.ts +5 -2
- package/src/runtime/query/__tests__/stream-recovery.test.ts +64 -1
- package/src/runtime/query/iteration/phases/compaction-safe-cut.test.ts +186 -0
- package/src/runtime/query/iteration/phases/compaction.ts +54 -12
- package/src/runtime/query/iteration/stream-turn.ts +11 -6
- package/src/runtime/query/result.ts +11 -1
- package/src/tools/builtins/__tests__/edit.test.ts +76 -0
- package/src/tools/builtins/edit.ts +12 -12
- package/src/tools/coordinator/__tests__/task-list.test.ts +16 -0
- package/src/tools/coordinator/agent.ts +6 -8
- package/src/tools/coordinator/index.ts +11 -34
- package/src/tools/defineTool.ts +2 -0
- package/src/types/provider/config.ts +36 -0
- package/src/types/provider/error.ts +29 -0
- package/src/types/provider/index.ts +8 -0
- package/src/types/run/entity.ts +2 -0
- package/src/types/run/events.ts +7 -1
- package/src/types/tool/index.ts +6 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ProviderRequestError,
|
|
5
|
+
bodySaysContextOverflow,
|
|
6
|
+
classifyProviderHttpStatus,
|
|
7
|
+
isCallerAbortError,
|
|
8
|
+
isProviderRequestError,
|
|
9
|
+
parseRetryAfterMs,
|
|
10
|
+
providerVendorError,
|
|
11
|
+
} from '../errors.js'
|
|
12
|
+
|
|
13
|
+
describe('provider error taxonomy', () => {
|
|
14
|
+
it('classifies only context-specific limit messages as context overflow', () => {
|
|
15
|
+
expect(bodySaysContextOverflow('input token count exceeds the maximum for this model')).toBe(
|
|
16
|
+
true,
|
|
17
|
+
)
|
|
18
|
+
expect(bodySaysContextOverflow('tool count exceeds the maximum allowed')).toBe(false)
|
|
19
|
+
expect(bodySaysContextOverflow('reduce the tool list length')).toBe(false)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('does not treat a generic HTTP 413 as model context overflow', () => {
|
|
23
|
+
expect(classifyProviderHttpStatus(413, 'request body too large at reverse proxy')).toBe(
|
|
24
|
+
'bad_request',
|
|
25
|
+
)
|
|
26
|
+
expect(classifyProviderHttpStatus(413, 'prompt is too long for requested model')).toBe(
|
|
27
|
+
'context_overflow',
|
|
28
|
+
)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('parses both Retry-After forms and rejects stale or invalid values', () => {
|
|
32
|
+
expect(parseRetryAfterMs('2', 0)).toBe(2000)
|
|
33
|
+
expect(parseRetryAfterMs('Thu, 01 Jan 1970 00:00:05 GMT', 1000)).toBe(4000)
|
|
34
|
+
expect(parseRetryAfterMs('Thu, 01 Jan 1970 00:00:01 GMT', 1000)).toBeUndefined()
|
|
35
|
+
expect(parseRetryAfterMs('later', 0)).toBeUndefined()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('drops the vendor object and its non-enumerable cause entirely', () => {
|
|
39
|
+
const secret = 'sk-FAKE-DO-NOT-LOG'
|
|
40
|
+
const vendor = new Error(`invalid api key ${secret}`, {
|
|
41
|
+
cause: new Error(`response body ${secret}`),
|
|
42
|
+
})
|
|
43
|
+
Object.assign(vendor, { status: 401 })
|
|
44
|
+
|
|
45
|
+
const classified = providerVendorError({
|
|
46
|
+
providerId: 'test-provider',
|
|
47
|
+
error: vendor,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
expect(classified).toMatchObject({
|
|
51
|
+
name: 'ProviderRequestError',
|
|
52
|
+
kind: 'auth',
|
|
53
|
+
status: 401,
|
|
54
|
+
providerId: 'test-provider',
|
|
55
|
+
})
|
|
56
|
+
expect(classified.message).not.toContain(secret)
|
|
57
|
+
expect('cause' in classified).toBe(false)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('recognizes a caller abort only when that signal actually aborted', () => {
|
|
61
|
+
const controller = new AbortController()
|
|
62
|
+
const sdkAbort = Object.assign(new Error('request aborted'), {
|
|
63
|
+
name: 'APIUserAbortError',
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
expect(isCallerAbortError(sdkAbort, controller.signal)).toBe(false)
|
|
67
|
+
controller.abort(new Error('user stopped'))
|
|
68
|
+
expect(isCallerAbortError(sdkAbort, controller.signal)).toBe(true)
|
|
69
|
+
expect(isCallerAbortError(controller.signal.reason, controller.signal)).toBe(true)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('requires the complete structural contract when SDK copies differ', () => {
|
|
73
|
+
const classified = new ProviderRequestError({
|
|
74
|
+
kind: 'server',
|
|
75
|
+
providerId: 'test-provider',
|
|
76
|
+
})
|
|
77
|
+
expect(isProviderRequestError(classified)).toBe(true)
|
|
78
|
+
|
|
79
|
+
const impostor = Object.assign(new Error('not classified'), {
|
|
80
|
+
name: 'ProviderRequestError',
|
|
81
|
+
kind: 'anything',
|
|
82
|
+
})
|
|
83
|
+
expect(isProviderRequestError(impostor)).toBe(false)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
LLMProvider,
|
|
4
|
+
LazyProviderModule,
|
|
5
|
+
ProviderCapabilities,
|
|
6
|
+
} from '../../types/provider/index.js'
|
|
7
|
+
import { PERMISSIVE_PROVIDER_CAPABILITIES, resolveProviderCapabilities } from '../capabilities.js'
|
|
3
8
|
import { registerMock } from '../mock-register.js'
|
|
4
9
|
import { MockLLMProvider } from '../mock.js'
|
|
5
10
|
import {
|
|
6
11
|
DuplicateProviderError,
|
|
12
|
+
LazyProviderLoadError,
|
|
13
|
+
LazyProviderSyncCreateError,
|
|
7
14
|
ProviderRegistry,
|
|
8
15
|
UnknownProviderError,
|
|
9
16
|
__resetProviderRegistryInternal,
|
|
@@ -16,9 +23,15 @@ interface TestProviderConfig {
|
|
|
16
23
|
value?: string
|
|
17
24
|
}
|
|
18
25
|
|
|
26
|
+
interface LazyTestProviderConfig {
|
|
27
|
+
type: 'lazytest'
|
|
28
|
+
value?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
declare module '../../types/provider/config.js' {
|
|
20
32
|
interface ProviderConfigRegistry {
|
|
21
33
|
test: TestProviderConfig
|
|
34
|
+
lazytest: LazyTestProviderConfig
|
|
22
35
|
}
|
|
23
36
|
}
|
|
24
37
|
|
|
@@ -52,6 +65,41 @@ const TEST_CAPS: ProviderCapabilities = {
|
|
|
52
65
|
supportsFunctionCalling: false,
|
|
53
66
|
}
|
|
54
67
|
|
|
68
|
+
class LazyTestProvider implements LLMProvider {
|
|
69
|
+
readonly id = 'lazytest'
|
|
70
|
+
readonly name = 'Lazy Test'
|
|
71
|
+
constructor(
|
|
72
|
+
public readonly config: LazyTestProviderConfig,
|
|
73
|
+
readonly capabilities?: ProviderCapabilities,
|
|
74
|
+
) {}
|
|
75
|
+
async *chatStream() {
|
|
76
|
+
yield { id: 'x', delta: { content: 'ok' } }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const HINT_CAPS: ProviderCapabilities = {
|
|
81
|
+
supportsTools: false,
|
|
82
|
+
supportsStreaming: false,
|
|
83
|
+
supportsFunctionCalling: false,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const MODULE_CAPS: ProviderCapabilities = {
|
|
87
|
+
supportsTools: true,
|
|
88
|
+
supportsStreaming: true,
|
|
89
|
+
supportsFunctionCalling: true,
|
|
90
|
+
supportsVision: false,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function deferred<T>() {
|
|
94
|
+
let resolve!: (value: T) => void
|
|
95
|
+
let reject!: (reason?: unknown) => void
|
|
96
|
+
const promise = new Promise<T>((res, rej) => {
|
|
97
|
+
resolve = res
|
|
98
|
+
reject = rej
|
|
99
|
+
})
|
|
100
|
+
return { promise, resolve, reject }
|
|
101
|
+
}
|
|
102
|
+
|
|
55
103
|
describe('ProviderRegistry', () => {
|
|
56
104
|
beforeEach(() => {
|
|
57
105
|
__resetProviderRegistryInternal()
|
|
@@ -152,4 +200,221 @@ describe('ProviderRegistry', () => {
|
|
|
152
200
|
expect(err.name).toBe('DuplicateProviderError')
|
|
153
201
|
})
|
|
154
202
|
})
|
|
203
|
+
|
|
204
|
+
describe('registerLazy', () => {
|
|
205
|
+
it('does not invoke the loader at registration time', () => {
|
|
206
|
+
let calls = 0
|
|
207
|
+
ProviderRegistry.registerLazy('lazytest', async () => {
|
|
208
|
+
calls++
|
|
209
|
+
return { create: (config) => new LazyTestProvider(config) }
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
expect(calls).toBe(0)
|
|
213
|
+
expect(ProviderRegistry.isSupported('lazytest')).toBe(true)
|
|
214
|
+
expect(ProviderRegistry.listTypes()).toContain('lazytest')
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('throws DuplicateProviderError over an existing eager registration', () => {
|
|
218
|
+
ProviderRegistry.register('test', TestProvider, TEST_CAPS)
|
|
219
|
+
expect(() =>
|
|
220
|
+
ProviderRegistry.registerLazy('test', async () => ({
|
|
221
|
+
create: (config) => new TestProvider(config),
|
|
222
|
+
})),
|
|
223
|
+
).toThrowError(DuplicateProviderError)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('throws DuplicateProviderError over an existing lazy registration', () => {
|
|
227
|
+
const loader = async (): Promise<LazyProviderModule<LazyTestProviderConfig>> => ({
|
|
228
|
+
create: (config) => new LazyTestProvider(config),
|
|
229
|
+
})
|
|
230
|
+
ProviderRegistry.registerLazy('lazytest', loader)
|
|
231
|
+
expect(() => ProviderRegistry.registerLazy('lazytest', loader)).toThrowError(
|
|
232
|
+
DuplicateProviderError,
|
|
233
|
+
)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('replace: true swaps between eager and lazy registrations', async () => {
|
|
237
|
+
ProviderRegistry.register('lazytest', LazyTestProvider, TEST_CAPS)
|
|
238
|
+
ProviderRegistry.registerLazy(
|
|
239
|
+
'lazytest',
|
|
240
|
+
async () => ({ create: (config) => new LazyTestProvider(config) }),
|
|
241
|
+
{ replace: true },
|
|
242
|
+
)
|
|
243
|
+
expect(() => ProviderRegistry.createProvider({ type: 'lazytest' })).toThrowError(
|
|
244
|
+
LazyProviderSyncCreateError,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
ProviderRegistry.register('lazytest', LazyTestProvider, TEST_CAPS, { replace: true })
|
|
248
|
+
expect(ProviderRegistry.createProvider({ type: 'lazytest' })).toBeInstanceOf(LazyTestProvider)
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('sync create()/createProvider() always throws for lazy types, even after load', async () => {
|
|
252
|
+
ProviderRegistry.registerLazy('lazytest', async () => ({
|
|
253
|
+
create: (config) => new LazyTestProvider(config),
|
|
254
|
+
}))
|
|
255
|
+
|
|
256
|
+
expect(() => ProviderRegistry.create({ type: 'lazytest' })).toThrowError(
|
|
257
|
+
LazyProviderSyncCreateError,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
await ProviderRegistry.createAsync({ type: 'lazytest' })
|
|
261
|
+
expect(() => ProviderRegistry.createProvider({ type: 'lazytest' })).toThrowError(
|
|
262
|
+
LazyProviderSyncCreateError,
|
|
263
|
+
)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('unregister removes a lazy registration', () => {
|
|
267
|
+
ProviderRegistry.registerLazy('lazytest', async () => ({
|
|
268
|
+
create: (config) => new LazyTestProvider(config),
|
|
269
|
+
}))
|
|
270
|
+
expect(ProviderRegistry.unregister('lazytest')).toBe(true)
|
|
271
|
+
expect(ProviderRegistry.isSupported('lazytest')).toBe(false)
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
describe('createAsync', () => {
|
|
276
|
+
it('first create invokes the loader once; subsequent creates reuse the cached factory', async () => {
|
|
277
|
+
let calls = 0
|
|
278
|
+
ProviderRegistry.registerLazy('lazytest', async () => {
|
|
279
|
+
calls++
|
|
280
|
+
return { create: (config) => new LazyTestProvider(config) }
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
const first = await ProviderRegistry.createAsync({ type: 'lazytest', value: 'a' })
|
|
284
|
+
const second = await ProviderRegistry.createAsync({ type: 'lazytest', value: 'b' })
|
|
285
|
+
|
|
286
|
+
expect(calls).toBe(1)
|
|
287
|
+
expect(first.provider).toBeInstanceOf(LazyTestProvider)
|
|
288
|
+
expect(second.provider).toBeInstanceOf(LazyTestProvider)
|
|
289
|
+
expect((second.provider as LazyTestProvider).config.value).toBe('b')
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it('concurrent first-creates share a single loader invocation', async () => {
|
|
293
|
+
let calls = 0
|
|
294
|
+
const gate = deferred<void>()
|
|
295
|
+
ProviderRegistry.registerLazy('lazytest', async () => {
|
|
296
|
+
calls++
|
|
297
|
+
await gate.promise
|
|
298
|
+
return { create: (config) => new LazyTestProvider(config) }
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
const a = ProviderRegistry.createAsync({ type: 'lazytest' })
|
|
302
|
+
const b = ProviderRegistry.createAsync({ type: 'lazytest' })
|
|
303
|
+
gate.resolve()
|
|
304
|
+
|
|
305
|
+
const [ra, rb] = await Promise.all([a, b])
|
|
306
|
+
expect(calls).toBe(1)
|
|
307
|
+
expect(ra.provider).toBeInstanceOf(LazyTestProvider)
|
|
308
|
+
expect(rb.provider).toBeInstanceOf(LazyTestProvider)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it('loader rejection surfaces as LazyProviderLoadError and the next create retries', async () => {
|
|
312
|
+
let calls = 0
|
|
313
|
+
ProviderRegistry.registerLazy('lazytest', async () => {
|
|
314
|
+
calls++
|
|
315
|
+
if (calls === 1) {
|
|
316
|
+
throw new Error('transient network failure')
|
|
317
|
+
}
|
|
318
|
+
return { create: (config) => new LazyTestProvider(config) }
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
const failure = await ProviderRegistry.createAsync({ type: 'lazytest' }).catch(
|
|
322
|
+
(err: unknown) => err,
|
|
323
|
+
)
|
|
324
|
+
expect(failure).toBeInstanceOf(LazyProviderLoadError)
|
|
325
|
+
expect((failure as LazyProviderLoadError).providerType).toBe('lazytest')
|
|
326
|
+
expect(((failure as LazyProviderLoadError).cause as Error).message).toBe(
|
|
327
|
+
'transient network failure',
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
const { provider } = await ProviderRegistry.createAsync({ type: 'lazytest' })
|
|
331
|
+
expect(calls).toBe(2)
|
|
332
|
+
expect(provider).toBeInstanceOf(LazyTestProvider)
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('rejects with LazyProviderLoadError when the loader resolves an invalid module shape', async () => {
|
|
336
|
+
ProviderRegistry.registerLazy(
|
|
337
|
+
'lazytest',
|
|
338
|
+
async () => ({}) as unknown as LazyProviderModule<LazyTestProviderConfig>,
|
|
339
|
+
)
|
|
340
|
+
await expect(ProviderRegistry.createAsync({ type: 'lazytest' })).rejects.toThrowError(
|
|
341
|
+
LazyProviderLoadError,
|
|
342
|
+
)
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('works for eagerly registered types too', async () => {
|
|
346
|
+
const { provider, capabilities } = await ProviderRegistry.createAsync({
|
|
347
|
+
type: 'mock',
|
|
348
|
+
model: 'test-model',
|
|
349
|
+
})
|
|
350
|
+
expect(provider).toBeInstanceOf(MockLLMProvider)
|
|
351
|
+
expect(capabilities.supportsStreaming).toBe(true)
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
it('rejects with UnknownProviderError for unregistered types', async () => {
|
|
355
|
+
await expect(
|
|
356
|
+
ProviderRegistry.createAsync({ type: 'nonexistent' } as unknown as { type: 'mock' }),
|
|
357
|
+
).rejects.toThrowError(UnknownProviderError)
|
|
358
|
+
})
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
describe('lazy capabilities', () => {
|
|
362
|
+
it('answers with the registration hint before load, without invoking the loader', () => {
|
|
363
|
+
let calls = 0
|
|
364
|
+
ProviderRegistry.registerLazy(
|
|
365
|
+
'lazytest',
|
|
366
|
+
async () => {
|
|
367
|
+
calls++
|
|
368
|
+
return { create: (config) => new LazyTestProvider(config) }
|
|
369
|
+
},
|
|
370
|
+
{ capabilities: HINT_CAPS },
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
expect(ProviderRegistry.getCapabilities('lazytest')).toEqual(HINT_CAPS)
|
|
374
|
+
expect(calls).toBe(0)
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('answers permissively before load when no hint was given', () => {
|
|
378
|
+
ProviderRegistry.registerLazy('lazytest', async () => ({
|
|
379
|
+
create: (config) => new LazyTestProvider(config),
|
|
380
|
+
}))
|
|
381
|
+
expect(ProviderRegistry.getCapabilities('lazytest')).toEqual(PERMISSIVE_PROVIDER_CAPABILITIES)
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it('module capabilities replace the hint after load', async () => {
|
|
385
|
+
ProviderRegistry.registerLazy(
|
|
386
|
+
'lazytest',
|
|
387
|
+
async () => ({
|
|
388
|
+
create: (config) => new LazyTestProvider(config),
|
|
389
|
+
capabilities: MODULE_CAPS,
|
|
390
|
+
}),
|
|
391
|
+
{ capabilities: HINT_CAPS },
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
const { capabilities } = await ProviderRegistry.createAsync({ type: 'lazytest' })
|
|
395
|
+
expect(capabilities).toEqual(MODULE_CAPS)
|
|
396
|
+
expect(ProviderRegistry.getCapabilities('lazytest')).toEqual(MODULE_CAPS)
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
it('the constructed instance own capabilities win at run time over any hint', async () => {
|
|
400
|
+
const instanceCaps: ProviderCapabilities = {
|
|
401
|
+
supportsTools: true,
|
|
402
|
+
supportsStreaming: true,
|
|
403
|
+
supportsFunctionCalling: false,
|
|
404
|
+
supportsVision: false,
|
|
405
|
+
}
|
|
406
|
+
ProviderRegistry.registerLazy(
|
|
407
|
+
'lazytest',
|
|
408
|
+
async () => ({ create: (config) => new LazyTestProvider(config, instanceCaps) }),
|
|
409
|
+
{ capabilities: HINT_CAPS },
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
const { provider } = await ProviderRegistry.createAsync({ type: 'lazytest' })
|
|
413
|
+
// The query runtime negotiates against the instance, not the registry:
|
|
414
|
+
const resolved = resolveProviderCapabilities(provider)
|
|
415
|
+
expect(resolved.supportsTools).toBe(true)
|
|
416
|
+
expect(resolved.supportsFunctionCalling).toBe(false)
|
|
417
|
+
expect(resolved.supportsVision).toBe(false)
|
|
418
|
+
})
|
|
419
|
+
})
|
|
155
420
|
})
|