Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input
# Conflicts: # .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml # docs/core-data-structures/core.i18n.yaml # docs/module-graph.md # packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx # packages/client/ui-conversation/src/client/index.ts # packages/compact/compact-basic/README.i18n.yaml
This commit is contained in:
@@ -481,6 +481,185 @@ describe('provider profile lifecycle', () => {
|
||||
.resolves.toMatchObject({ reasoning: { defaultEffort: ReasoningEffortId('off') } })
|
||||
})
|
||||
|
||||
it('serves declared reasoning efforts to selectors and honours the profile default', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmService)
|
||||
await ctx.plugin(LlmPiAi, {
|
||||
providers: {
|
||||
'acme-gateway': {
|
||||
apiKeyEnv: 'PI_TEST_KEY',
|
||||
api: 'openai-completions',
|
||||
baseURL: 'https://acme.test/v1',
|
||||
reasoning: 'high',
|
||||
models: [{
|
||||
id: 'acme-think',
|
||||
contextWindow: 65_536,
|
||||
maxTokens: 4096,
|
||||
reasoningEfforts: { off: null, low: 'low', high: 'high' },
|
||||
}],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Declared levels reach the same seam catalog metadata does, so the
|
||||
// effort picker works for a model pi-ai has never heard of.
|
||||
await expect(ctx.llm.resolveModelInfo('acme-gateway', 'acme-think')).resolves.toMatchObject({
|
||||
reasoning: {
|
||||
efforts: [
|
||||
{ id: ReasoningEffortId('off'), name: 'Off' },
|
||||
{ id: ReasoningEffortId('low'), name: 'Low' },
|
||||
{ id: ReasoningEffortId('high'), name: 'High' },
|
||||
],
|
||||
defaultEffort: ReasoningEffortId('high'),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
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': {
|
||||
apiKeyEnv: 'PI_TEST_KEY',
|
||||
api: 'openai-completions',
|
||||
baseURL: `${server.url}/v1`,
|
||||
models: [{
|
||||
id: 'acme-think',
|
||||
contextWindow: 65_536,
|
||||
maxTokens: 4096,
|
||||
reasoningEfforts: { off: null, high: 'ultra' },
|
||||
}],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await assemble(ctx, {
|
||||
provider: 'acme-gateway',
|
||||
model: 'acme-think',
|
||||
reasoningEffort: ReasoningEffortId('high'),
|
||||
messages: [],
|
||||
})
|
||||
// The declared value, not the canonical level name, goes on the wire.
|
||||
expect(server.requests[0]).toMatchObject({ reasoning_effort: 'ultra' })
|
||||
|
||||
const undeclared = await assemble(ctx, {
|
||||
provider: 'acme-gateway',
|
||||
model: 'acme-think',
|
||||
reasoningEffort: ReasoningEffortId('max'),
|
||||
messages: [],
|
||||
})
|
||||
expect(undeclared.finish).toMatchObject({
|
||||
kind: 'error',
|
||||
failure: { code: 'UNSUPPORTED_REASONING_EFFORT' },
|
||||
})
|
||||
expect(server.requests).toHaveLength(1)
|
||||
})
|
||||
|
||||
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': {
|
||||
apiKeyEnv: 'PI_TEST_KEY',
|
||||
api: 'openai-completions',
|
||||
baseURL: `${server.url}/v1`,
|
||||
// Without the switch pi-ai guesses the dialect from the endpoint
|
||||
// URL, and a private gateway's URL says nothing.
|
||||
compat: { thinkingFormat: 'deepseek' },
|
||||
models: [{
|
||||
id: 'acme-think',
|
||||
contextWindow: 65_536,
|
||||
maxTokens: 4096,
|
||||
reasoningEfforts: { off: null, high: 'high' },
|
||||
}],
|
||||
},
|
||||
},
|
||||
})
|
||||
const prompt = (effort: string): Promise<unknown> => assemble(ctx, {
|
||||
provider: 'acme-gateway',
|
||||
model: 'acme-think',
|
||||
reasoningEffort: ReasoningEffortId(effort),
|
||||
messages: [],
|
||||
})
|
||||
|
||||
await prompt('high')
|
||||
expect(server.requests[0]).toMatchObject({ thinking: { type: 'enabled' }, reasoning_effort: 'high' })
|
||||
|
||||
await prompt('off')
|
||||
expect(server.requests[1]).toMatchObject({ thinking: { type: 'disabled' } })
|
||||
expect(server.requests[1]).not.toHaveProperty('reasoning_effort')
|
||||
})
|
||||
|
||||
it('sends a declared off value as the effort parameter instead of omitting 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': {
|
||||
apiKeyEnv: 'PI_TEST_KEY',
|
||||
api: 'openai-completions',
|
||||
baseURL: `${server.url}/v1`,
|
||||
models: [{
|
||||
id: 'acme-think',
|
||||
contextWindow: 65_536,
|
||||
maxTokens: 4096,
|
||||
reasoningEfforts: { off: 'none', high: 'high' },
|
||||
}],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// The adapter strips a selected Off to "no reasoning option", and pi-ai's
|
||||
// dispatch reads thinkingLevelMap.off exactly then — so the declared value
|
||||
// still reaches the wire, which is the README's promise for `off: none`.
|
||||
await assemble(ctx, {
|
||||
provider: 'acme-gateway',
|
||||
model: 'acme-think',
|
||||
reasoningEffort: ReasoningEffortId('off'),
|
||||
messages: [],
|
||||
})
|
||||
expect(server.requests[0]).toMatchObject({ reasoning_effort: 'none' })
|
||||
})
|
||||
|
||||
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': {
|
||||
apiKeyEnv: 'PI_TEST_KEY',
|
||||
api: 'openai-completions',
|
||||
baseURL: `${server.url}/v1`,
|
||||
compat: { supportsReasoningEffort: false },
|
||||
models: [{
|
||||
id: 'acme-think',
|
||||
contextWindow: 65_536,
|
||||
maxTokens: 4096,
|
||||
reasoningEfforts: { off: null, high: 'high' },
|
||||
}],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await assemble(ctx, {
|
||||
provider: 'acme-gateway',
|
||||
model: 'acme-think',
|
||||
reasoningEffort: ReasoningEffortId('high'),
|
||||
messages: [],
|
||||
})
|
||||
expect(server.requests[0]).not.toHaveProperty('reasoning_effort')
|
||||
})
|
||||
|
||||
it('accepts absent credentials for pi-ai ambient authentication', async () => {
|
||||
vi.stubEnv('DEEPSEEK_API_KEY', 'ambient-key')
|
||||
const server = await mockServer([{ events: textEvents }])
|
||||
|
||||
Reference in New Issue
Block a user