feat(llm): route adapters by provider
This commit is contained in:
@@ -167,7 +167,7 @@ describe('dsh-jsonrpc plugin apply', () => {
|
||||
vi.stubEnv('DEEPSEEK_API_KEY', 'test-key')
|
||||
const harness = await mountPlugin(storageDir)
|
||||
try {
|
||||
harness.send({ jsonrpc: '2.0', id: 'init-1', method: 'initialize', params: { cwd: storageDir, model: 'apply-model' } })
|
||||
harness.send({ jsonrpc: '2.0', id: 'init-1', method: 'initialize', params: { cwd: storageDir, provider: 'deepseek', model: 'apply-model' } })
|
||||
|
||||
const response = await harness.waitForFrame(frame => frame.id === 'init-1', 'initialize response')
|
||||
expect(response).toEqual({
|
||||
@@ -189,7 +189,7 @@ describe('dsh-jsonrpc plugin apply', () => {
|
||||
vi.stubEnv('DEEPSEEK_BASE_URL', llmServer.url)
|
||||
const harness = await mountPlugin(storageDir)
|
||||
try {
|
||||
harness.send({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { cwd: storageDir, model: 'dsagent-model' } })
|
||||
harness.send({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { cwd: storageDir, provider: 'deepseek', model: 'dsagent-model' } })
|
||||
await harness.waitForFrame(frame => frame.id === 1, 'initialize response')
|
||||
|
||||
harness.send({
|
||||
@@ -256,7 +256,7 @@ describe('dsh-jsonrpc plugin apply', () => {
|
||||
|
||||
// The plugin fiber is disposed: the transport reads no further frames.
|
||||
const before = harness.frames().length
|
||||
harness.send({ jsonrpc: '2.0', id: 'after-exit', method: 'initialize', params: { cwd: storageDir, model: 'x' } })
|
||||
harness.send({ jsonrpc: '2.0', id: 'after-exit', method: 'initialize', params: { cwd: storageDir, provider: 'deepseek', model: 'x' } })
|
||||
await settle()
|
||||
expect(harness.frames().length).toBe(before)
|
||||
} finally {
|
||||
@@ -277,7 +277,7 @@ describe('dsh-jsonrpc plugin apply', () => {
|
||||
expect(harness.outputErrors.map(error => error.message)).toEqual(['flush callback failed'])
|
||||
|
||||
const before = harness.frames().length
|
||||
harness.send({ jsonrpc: '2.0', id: 'after-flush-failure', method: 'initialize', params: { cwd: storageDir, model: 'x' } })
|
||||
harness.send({ jsonrpc: '2.0', id: 'after-flush-failure', method: 'initialize', params: { cwd: storageDir, provider: 'deepseek', model: 'x' } })
|
||||
await settle()
|
||||
expect(harness.frames().length).toBe(before)
|
||||
} finally {
|
||||
@@ -304,7 +304,7 @@ describe('dsh-jsonrpc plugin apply', () => {
|
||||
// The effect disposer shut the server and closed the transport — later
|
||||
// frames are never read — and the exit seam was never touched.
|
||||
const before = harness.frames().length
|
||||
harness.send({ jsonrpc: '2.0', id: 'probe-2', method: 'initialize', params: { cwd: storageDir, model: 'x' } })
|
||||
harness.send({ jsonrpc: '2.0', id: 'probe-2', method: 'initialize', params: { cwd: storageDir, provider: 'deepseek', model: 'x' } })
|
||||
await settle()
|
||||
expect(harness.frames().length).toBe(before)
|
||||
expect(harness.exits()).toEqual([])
|
||||
|
||||
@@ -107,6 +107,7 @@ describe('HarnessSdkServer', () => {
|
||||
|
||||
const init = await server.handleRequest('initialize', {
|
||||
cwd: storageDir,
|
||||
provider: 'deepseek',
|
||||
model: 'dsagent-model',
|
||||
}) as { serverInfo: { name: string } }
|
||||
expect(init.serverInfo.name).toBe('deepseek-harness-sdk-runtime')
|
||||
@@ -138,7 +139,7 @@ describe('HarnessSdkServer', () => {
|
||||
agentId: AgentId('orphan-agent'),
|
||||
sessionId: SessionId('orphan-session'),
|
||||
meta: { cwd: storageDir },
|
||||
agentOptions: { model: 'dsagent-model' },
|
||||
agentOptions: { provider: 'deepseek', model: 'dsagent-model' },
|
||||
})
|
||||
orphanHandle.agent.send([{ type: 'text', text: 'outside the sdk session map' }])
|
||||
await orphanHandle.agent.whenIdle()
|
||||
@@ -241,7 +242,7 @@ describe('HarnessSdkServer', () => {
|
||||
try {
|
||||
const server = new HarnessSdkServer(ctx, new FakeTransport())
|
||||
|
||||
await server.initialize({ cwd: storageDir, model: 'plain-model' })
|
||||
await server.initialize({ cwd: storageDir, provider: 'deepseek', model: 'plain-model' })
|
||||
await server.prompt({
|
||||
sessionId: 'plain',
|
||||
contentBlocks: [{ type: 'text', text: 'hello' }],
|
||||
@@ -266,13 +267,13 @@ describe('HarnessSdkServer', () => {
|
||||
agentId: AgentId('parent-agent'),
|
||||
sessionId: SessionId('main'),
|
||||
meta: { cwd: storageDir },
|
||||
agentOptions: { model: 'deepseek' },
|
||||
agentOptions: { provider: 'deepseek', model: 'deepseek' },
|
||||
})
|
||||
const handle = await ctx.agents.create({
|
||||
agentId: AgentId('child-agent'),
|
||||
sessionId: SessionId('child-session'),
|
||||
meta: { cwd: storageDir, parentSession: SessionId('main') },
|
||||
agentOptions: { model: 'deepseek' },
|
||||
agentOptions: { provider: 'deepseek', model: 'deepseek' },
|
||||
})
|
||||
await settleSubagent(ctx, parentHandle.agent, {
|
||||
provider: 'spawn',
|
||||
@@ -314,19 +315,19 @@ describe('HarnessSdkServer', () => {
|
||||
agentId: AgentId('fallback-parent-agent'),
|
||||
sessionId: SessionId('fallback-parent'),
|
||||
meta: { cwd: storageDir },
|
||||
agentOptions: { model: 'deepseek' },
|
||||
agentOptions: { provider: 'deepseek', model: 'deepseek' },
|
||||
})
|
||||
handle = await ctx.agents.create({
|
||||
agentId: AgentId('fallback-child-agent'),
|
||||
sessionId: SessionId('fallback-child-session'),
|
||||
meta: { cwd: storageDir, parentSession: SessionId('fallback-parent') },
|
||||
agentOptions: { model: 'deepseek' },
|
||||
agentOptions: { provider: 'deepseek', model: 'deepseek' },
|
||||
})
|
||||
failedHandle = await ctx.agents.create({
|
||||
agentId: AgentId('failed-child-agent'),
|
||||
sessionId: SessionId('failed-child-session'),
|
||||
meta: { cwd: storageDir },
|
||||
agentOptions: { model: 'deepseek' },
|
||||
agentOptions: { provider: 'deepseek', model: 'deepseek' },
|
||||
})
|
||||
const transport = new FakeTransport()
|
||||
const server = new HarnessSdkServer(ctx, transport)
|
||||
@@ -385,20 +386,20 @@ describe('HarnessSdkServer', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('does not re-register an LLM adapter that already exists', async () => {
|
||||
it('does not re-register an LLM adapter whose provider already has an owner', async () => {
|
||||
const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-existing-llm-'))
|
||||
const ctx = await makeHarness(storageDir)
|
||||
vi.stubEnv('DEEPSEEK_API_KEY', 'test-key')
|
||||
await ctx.plugin(LlmDeepSeek, { models: ['preinstalled-model'] })
|
||||
await ctx.plugin(LlmDeepSeek)
|
||||
try {
|
||||
const server = new HarnessSdkServer(ctx, new FakeTransport())
|
||||
const inspect = server as unknown as { hasAdapterFor(model: string): boolean }
|
||||
const inspect = server as unknown as { hasAdapterFor(provider: string): boolean }
|
||||
|
||||
expect(inspect.hasAdapterFor('preinstalled-model')).toBe(true)
|
||||
expect(inspect.hasAdapterFor('missing-model')).toBe(false)
|
||||
await server.initialize({ cwd: storageDir, model: 'preinstalled-model' })
|
||||
expect(inspect.hasAdapterFor('deepseek')).toBe(true)
|
||||
expect(inspect.hasAdapterFor('missing-provider')).toBe(false)
|
||||
await server.initialize({ cwd: storageDir, provider: 'deepseek', model: 'preinstalled-model' })
|
||||
|
||||
expect(ctx.get('llm')?.models().filter(model => model === 'preinstalled-model')).toEqual(['preinstalled-model'])
|
||||
expect(ctx.get('llm')?.providers().filter(provider => provider === 'deepseek')).toEqual(['deepseek'])
|
||||
await server.shutdown()
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
@@ -406,17 +407,18 @@ describe('HarnessSdkServer', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('registers a missing model when an LLM service already exists', async () => {
|
||||
it('rejects a missing non-DeepSeek provider when an LLM service already exists', async () => {
|
||||
const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-new-llm-'))
|
||||
const ctx = await makeHarness(storageDir)
|
||||
vi.stubEnv('DEEPSEEK_API_KEY', 'test-key')
|
||||
await ctx.plugin(LlmDeepSeek, { models: ['other-model'] })
|
||||
await ctx.plugin(LlmDeepSeek)
|
||||
try {
|
||||
const server = new HarnessSdkServer(ctx, new FakeTransport())
|
||||
|
||||
await server.initialize({ cwd: storageDir, model: 'new-model' })
|
||||
await expect(server.initialize({ cwd: storageDir, provider: 'private', model: 'new-model' }))
|
||||
.rejects.toThrow('no adapter registered for provider "private"')
|
||||
|
||||
expect(ctx.get('llm')?.models()).toEqual(expect.arrayContaining(['other-model', 'new-model']))
|
||||
expect(ctx.get('llm')?.providers()).toEqual(['deepseek'])
|
||||
await server.shutdown()
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
@@ -517,15 +519,15 @@ describe('HarnessSdkServer', () => {
|
||||
const ctx = {
|
||||
on: vi.fn(() => () => undefined),
|
||||
agents: { create, get: () => undefined },
|
||||
get: () => ({ models: () => ['model'] }),
|
||||
get: () => ({ providers: () => ['mock'] }),
|
||||
} as unknown as Context
|
||||
const server = new HarnessSdkServer(ctx, new FakeTransport()) as unknown as {
|
||||
initialize(params: { cwd: string; model: string }): Promise<unknown>
|
||||
initialize(params: { cwd: string; provider: string; model: string }): Promise<unknown>
|
||||
getOrCreateSession(sessionId: string): Promise<unknown>
|
||||
shutdown(): Promise<Record<string, never>>
|
||||
}
|
||||
|
||||
await server.initialize({ cwd: '.', model: 'model' })
|
||||
await server.initialize({ cwd: '.', provider: 'mock', model: 'model' })
|
||||
await server.getOrCreateSession('relative')
|
||||
|
||||
expect(create).toHaveBeenCalledWith(expect.objectContaining({ meta: { cwd: process.cwd() } }))
|
||||
|
||||
Reference in New Issue
Block a user