Merge remote-tracking branch 'origin/master' into worktree/pi-ai-per-model-reasoning

# Conflicts:
#	docs/config-catalog.md
#	docs/user/guide/providers.i18n.yaml
#	packages/llm/llm-pi-ai/README.i18n.yaml
#	packages/llm/llm-pi-ai/README.md
#	packages/llm/llm-pi-ai/README.zh.md
#	packages/llm/llm-pi-ai/tests/config.spec.ts
This commit is contained in:
Yichen Jiang
2026-08-08 10:32:22 +08:00
172 changed files with 2499 additions and 986 deletions
+32 -20
View File
@@ -1,4 +1,4 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { Context } from 'cordis'
import LlmService, { createUserMessage, CONTEXT_WINDOW_EXCEEDED_CODE, LlmError, ReasoningEffortId, userAgent } from '@deepseek-ai/dsh-llm'
import * as LlmPiAi from '@deepseek-ai/dsh-llm-pi-ai'
@@ -15,22 +15,32 @@ afterEach(async () => {
})
async function harness(baseURL: string, overrides: Record<string, unknown> = {}): Promise<Context> {
vi.stubEnv('PI_TEST_KEY', 'test-key')
const ctx = new Context()
await ctx.plugin(LlmService)
await ctx.plugin(LlmPiAi, {
providers: { deepseek: { apiKey: 'test-key', baseURL, ...overrides } },
providers: { deepseek: { apiKeyEnv: 'PI_TEST_KEY', baseURL, ...overrides } },
})
return ctx
}
/** Direct adapter over the real profile resolver, with literal-key resolution. */
function adapterOf(providers: Record<string, LlmPiAi.PiAiProviderProfile>): PiAiAdapter {
/** Direct adapter over the real profile resolver, with a fixed key per call. */
function adapterOf(
providers: Record<string, LlmPiAi.PiAiProviderProfile>,
apiKey: string | undefined = 'test-key',
): PiAiAdapter {
return new PiAiAdapter({
profiles: () => resolveProfiles(providers),
resolveApiKey: (_provider, profile) => Promise.resolve(profile.apiKey),
resolveApiKey: () => Promise.resolve(apiKey),
})
}
beforeEach(() => {
// Configuration carries only the reference; these mounts resolve it from
// the environment, which is the whole credential plane without a seam.
vi.stubEnv('PI_TEST_KEY', 'test-key')
})
describe('PiAiAdapter provider routing', () => {
it('resolves a catalog model dynamically and uses a private endpoint', async () => {
const server = await mockServer([{ events: textEvents }])
@@ -121,7 +131,7 @@ describe('PiAiAdapter provider routing', () => {
const ctx = new Context()
await ctx.plugin(LlmService)
ctx.llm.registerAdapter(['deepseek'], adapterOf({
deepseek: { apiKey: 'test-key', baseURL: server.url },
deepseek: { apiKeyEnv: 'PI_TEST_KEY', baseURL: server.url },
}))
const result = await assemble(ctx, { model: 'deepseek-v4-flash', messages: [] })
@@ -131,7 +141,6 @@ describe('PiAiAdapter provider routing', () => {
it('names a route by its displayName, and by its own key once the profiles drop it', () => {
const adapter = adapterOf({ 'acme-gateway': {
apiKey: 'k',
displayName: 'Acme Gateway',
api: 'openai-completions',
baseURL: 'https://acme.test/v1',
@@ -167,7 +176,7 @@ describe('PiAiAdapter provider routing', () => {
const ctx = new Context()
await ctx.plugin(LlmService)
await ctx.plugin(LlmPiAi, {
providers: { openai: { apiKey: 'test-key', baseURL: `${server.url}/v1` } },
providers: { openai: { apiKeyEnv: 'PI_TEST_KEY', baseURL: `${server.url}/v1` } },
})
const result = await assemble(ctx, { provider: 'openai', model: 'gpt-4.1', messages: [] })
expect(result.finish.kind).toBe('error')
@@ -187,7 +196,7 @@ describe('PiAiAdapter provider routing', () => {
const ctx = new Context()
await ctx.plugin(LlmService)
await ctx.plugin(LlmPiAi, {
providers: { openai: { apiKey: 'test-key', baseURL: `${server.url}/v1` } },
providers: { openai: { apiKeyEnv: 'PI_TEST_KEY', baseURL: `${server.url}/v1` } },
})
const result = await assemble(ctx, { provider: 'openai', model: 'gpt-4.1', messages: [] })
@@ -203,7 +212,7 @@ describe('PiAiAdapter provider routing', () => {
await ctx.plugin(LlmPiAi, {
providers: {
openai: {
apiKey: 'test-key',
apiKeyEnv: 'PI_TEST_KEY',
baseURL: `${server.url}/api/projects/openai/openai/v1`,
headers: { 'api-key': 'test-key', Authorization: '' },
},
@@ -406,7 +415,7 @@ describe('provider profile lifecycle', () => {
await ctx.plugin(LlmPiAi, {
providers: {
'acme-gateway': {
apiKey: 'gw-key',
apiKeyEnv: 'PI_TEST_KEY',
api: 'openai-completions',
baseURL: 'https://acme.test/v1',
reasoning: 'high',
@@ -435,13 +444,14 @@ describe('provider profile lifecycle', () => {
})
it('sends the declared wire spelling and refuses undeclared levels before network I/O', async () => {
vi.stubEnv('PI_TEST_KEY', 'test-key')
const server = await mockServer([{ events: textEvents }])
const ctx = new Context()
await ctx.plugin(LlmService)
await ctx.plugin(LlmPiAi, {
providers: {
'acme-gateway': {
apiKey: 'gw-key',
apiKeyEnv: 'PI_TEST_KEY',
api: 'openai-completions',
baseURL: `${server.url}/v1`,
models: [{
@@ -477,13 +487,14 @@ describe('provider profile lifecycle', () => {
})
it('dispatches the compat-switched dialect on a declared route', async () => {
vi.stubEnv('PI_TEST_KEY', 'test-key')
const server = await mockServer([{ events: textEvents }, { events: textEvents }])
const ctx = new Context()
await ctx.plugin(LlmService)
await ctx.plugin(LlmPiAi, {
providers: {
'acme-gateway': {
apiKey: 'gw-key',
apiKeyEnv: 'PI_TEST_KEY',
api: 'openai-completions',
baseURL: `${server.url}/v1`,
// Without the switch pi-ai guesses the dialect from the endpoint
@@ -514,13 +525,14 @@ describe('provider profile lifecycle', () => {
})
it('holds back reasoning_effort when the endpoint cannot take it', async () => {
vi.stubEnv('PI_TEST_KEY', 'test-key')
const server = await mockServer([{ events: textEvents }])
const ctx = new Context()
await ctx.plugin(LlmService)
await ctx.plugin(LlmPiAi, {
providers: {
'acme-gateway': {
apiKey: 'gw-key',
apiKeyEnv: 'PI_TEST_KEY',
api: 'openai-completions',
baseURL: `${server.url}/v1`,
compat: { supportsReasoningEffort: false },
@@ -546,7 +558,9 @@ describe('provider profile lifecycle', () => {
it('accepts absent credentials for pi-ai ambient authentication', async () => {
vi.stubEnv('DEEPSEEK_API_KEY', 'ambient-key')
const server = await mockServer([{ events: textEvents }])
const ctx = await harness(server.url, { apiKey: undefined })
// A profile that names no reference at all is the one case that defers to
// pi-ai's own provider-native discovery.
const ctx = await harness(server.url, { apiKeyEnv: undefined })
await assemble(ctx, { model: 'deepseek-v4-flash', messages: [] })
expect(server.headers[0]?.authorization).toBe('Bearer ambient-key')
})
@@ -588,8 +602,6 @@ describe('provider profile lifecycle', () => {
// loud with migration directions instead of half-working.
expect(() => resolveProfiles([{ provider: 'openai' }] as never)).toThrow(/dict keyed by provider/)
expect(() => resolveProfiles({ openai: { provider: 'openai' } as never })).toThrow(/moved to the providers dict key/)
expect(() => resolveProfiles({ openai: { apiKey: '' } })).toThrow(/empty apiKey/)
expect(() => resolveProfiles({ openai: { apiKey: ' ' } })).toThrow(/empty apiKey/)
expect(() => resolveProfiles({ openai: { baseURL: '' } })).toThrow(/empty baseURL/)
expect(() => resolveProfiles({ openai: { apiKeyEnv: 'not-a-var!' } })).toThrow(/must match/)
})
@@ -664,7 +676,7 @@ describe('abort wiring', () => {
const message = Object.defineProperty({}, 'role', {
get() { throw original },
})
const adapter = adapterOf({ deepseek: { apiKey: 'test-key' } })
const adapter = adapterOf({ deepseek: {} })
const drain = async (): Promise<void> => {
for await (const _chunk of adapter.stream({
provider: 'deepseek',
@@ -685,7 +697,7 @@ describe('abort wiring', () => {
throw original
},
})
const adapter = adapterOf({ deepseek: { apiKey: 'test-key' } })
const adapter = adapterOf({ deepseek: {} })
const drain = async (): Promise<void> => {
for await (const _chunk of adapter.stream({
provider: 'deepseek',
@@ -699,7 +711,7 @@ describe('abort wiring', () => {
})
it('resolves catalog endpoints without an override before honoring pre-abort', async () => {
const adapter = adapterOf({ deepseek: { apiKey: 'test-key' } })
const adapter = adapterOf({ deepseek: {} })
const controller = new AbortController()
controller.abort('already stopped')
const chunks = []