Merge remote-tracking branch 'origin/master' into xtr/react-loop-simplification

# Conflicts:
#	.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-06-sandbox.md
#	.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md
#	packages/host/apiproxy/README.i18n.yaml
This commit is contained in:
_Kerman
2026-08-05 15:34:19 +08:00
109 changed files with 2973 additions and 475 deletions
@@ -792,6 +792,26 @@ describe('plugin registration and config', () => {
expect(ctx.llm.listProviders()).toEqual([])
})
it.each([0, 1.5])('rejects a per-model output cap of %s', (maxTokens) => {
expect(() => resolveAdapterOptions({ models: [{ id: 'bad-cap', maxTokens }] }))
.toThrow(/maxTokens must be a positive integer/)
})
it('prefers a model\'s own output cap over the profile default', async () => {
// The profile default stays what an unlisted or uncapped model resolves
// to, so adding a per-model cap changes one model rather than the route.
const adapter = adapterOf({ maxTokens: 4096, models: [
{ id: 'capped', maxTokens: 512 },
{ id: 'uncapped' },
] })
await expect(adapter.resolveModel('deepseek-official', 'capped'))
.resolves.toMatchObject({ defaultMaxTokens: 512 })
await expect(adapter.resolveModel('deepseek-official', 'uncapped'))
.resolves.toMatchObject({ defaultMaxTokens: 4096 })
await expect(adapter.resolveModel('deepseek-official', 'not-in-catalog'))
.resolves.toMatchObject({ defaultMaxTokens: 4096 })
})
it('rejects invalid context capacity when apply is called directly', async () => {
const ctx = new Context()
await ctx.plugin(LlmService)