@namzu/sdk 1.1.0 → 1.3.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 +98 -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/__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/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 +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 +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 +51 -0
- package/dist/types/provider/config.d.ts.map +1 -1
- package/dist/types/provider/index.d.ts +1 -1
- package/dist/types/provider/index.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/__tests__/registry.test.ts +266 -1
- package/src/provider/capabilities.ts +47 -0
- package/src/provider/index.ts +12 -1
- package/src/provider/registry.ts +202 -7
- package/src/public-runtime.ts +6 -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 +54 -0
- package/src/types/provider/index.ts +3 -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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/run/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,wEAAwE;AACxE,4EAA4E;AAC5E,0EAA0E;AAC1E,sBAAsB;AACtB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAQ9B,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/run/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,wEAAwE;AACxE,4EAA4E;AAC5E,0EAA0E;AAC1E,sBAAsB;AACtB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAQ9B,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA"}
|
package/package.json
CHANGED
package/src/bridge/a2a/mapper.ts
CHANGED
|
@@ -60,6 +60,9 @@ const MAPPING: {
|
|
|
60
60
|
parts: [{ kind: 'text', text: e.error }],
|
|
61
61
|
}),
|
|
62
62
|
|
|
63
|
+
// Capability degradation is host-facing diagnostics, not A2A task state.
|
|
64
|
+
capability_warning: null,
|
|
65
|
+
|
|
63
66
|
iteration_started: (e, ctx) =>
|
|
64
67
|
statusEvent(e.runId, 'running', false, ctx, {
|
|
65
68
|
role: 'agent',
|
package/src/bridge/sse/mapper.ts
CHANGED
|
@@ -165,6 +165,11 @@ const MAPPING: {
|
|
|
165
165
|
run_completed: null,
|
|
166
166
|
run_failed: null,
|
|
167
167
|
|
|
168
|
+
// Not mapped to a wire event yet — hosts consume `capability_warning`
|
|
169
|
+
// from the RunEvent stream directly; promoting it to the SSE contract
|
|
170
|
+
// needs a StreamEventType addition first.
|
|
171
|
+
capability_warning: null,
|
|
172
|
+
|
|
168
173
|
agent_pending: {
|
|
169
174
|
wire: 'agent.pending',
|
|
170
175
|
transform: (e, runId) => ({
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* that match this constant verbatim, so the run's `result` field
|
|
12
12
|
* concatenates the full multi-turn assistant output instead of only
|
|
13
13
|
* surfacing the trailing continuation chunk.
|
|
14
|
+
*
|
|
15
|
+
* Lives in `constants/` (not `runtime/query/`) because both the
|
|
16
|
+
* runtime iteration loop and `manager/run/persistence.ts` consume
|
|
17
|
+
* it — a manager→runtime import for a string constant would create
|
|
18
|
+
* a manager↔runtime directory cycle.
|
|
14
19
|
*/
|
|
15
20
|
export const AUTO_CONTINUATION_USER_MESSAGE =
|
|
16
21
|
'Continue exactly where you left off. Do not repeat content you already wrote — pick up at the next token.'
|
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
|
}
|
|
@@ -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
|
})
|
|
@@ -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,14 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
ProviderRegistry,
|
|
3
|
+
UnknownProviderError,
|
|
4
|
+
DuplicateProviderError,
|
|
5
|
+
LazyProviderLoadError,
|
|
6
|
+
LazyProviderSyncCreateError,
|
|
7
|
+
} from './registry.js'
|
|
2
8
|
export { MockLLMProvider } from './mock.js'
|
|
3
9
|
export { registerMock, MOCK_CAPABILITIES } from './mock-register.js'
|
|
10
|
+
export {
|
|
11
|
+
PERMISSIVE_PROVIDER_CAPABILITIES,
|
|
12
|
+
resolveProviderCapabilities,
|
|
13
|
+
} from './capabilities.js'
|
|
14
|
+
export type { ResolvedProviderCapabilities } from './capabilities.js'
|