fix: separate Anthropic output and context limits

Direct Anthropic connections reused the configured context window as max_tokens, which could impose an unintended output value or send a provider-invalid request. Anthropic connections now expose an independent per-response maximum output setting, defaulting to 32K for the protocol-required field, while OpenAI direct requests remain without an added output token cap and direct tool rounds remain unmetered.

Release note: Anthropic 直连模型现在可单独配置单次最大输出,不再复用上下文窗口;OpenAI 直连请求和工具调用轮次不增加额外配额。
This commit is contained in:
mesalogo
2026-08-26 22:53:49 +08:00
parent 49700bc7cf
commit b20c10f820
13 changed files with 96 additions and 12 deletions
+4 -1
View File
@@ -96,7 +96,8 @@ function resolveContextCompression(
'openai-images-generations'
>,
authentication: summaryProfile.authentication,
contextWindowTokens: summaryProfile.contextWindowTokens
contextWindowTokens: summaryProfile.contextWindowTokens,
maximumOutputTokens: summaryProfile.maximumOutputTokens
}
}
: {})
@@ -120,6 +121,7 @@ export function createDefaultModelRuntime(
protocol: settings.modelProtocol,
authentication: settings.modelAuthentication,
supportsImageInput: settings.supportsImageInput,
maximumOutputTokens: currentProfile?.maximumOutputTokens,
defaultWorkspace: settings.workspacePath || defaultWorkspace,
contextCompression: resolveContextCompression(
settings,
@@ -141,6 +143,7 @@ export function createModelProfileRuntime(
protocol: profile.protocol,
authentication: profile.authentication,
supportsImageInput: profile.supportsImageInput,
maximumOutputTokens: profile.maximumOutputTokens,
imageGenerationQuality:
profile.imageGenerationQuality ??
defaultRuntimeSettings.imageGenerationQuality,
+3
View File
@@ -476,6 +476,7 @@ describe('ModelAgentRuntime', () => {
model: 'sonnet-5',
protocol: 'anthropic-messages',
authentication: 'api-key',
maximumOutputTokens: 48_000,
skillInstructions: '# 文档写作',
fetcher
})
@@ -500,11 +501,13 @@ describe('ModelAgentRuntime', () => {
const body = JSON.parse(init?.body as string) as {
model: string
max_tokens: number
stream: boolean
system: string
}
expect(body).toMatchObject({
model: 'sonnet-5',
max_tokens: 48_000,
stream: true
})
expect(body.system).toMatch(
+8 -5
View File
@@ -8,6 +8,7 @@ import type {
ModelProtocol,
RuntimeConversationCompactInput
} from '../../shared/contracts'
import { defaultAnthropicMaximumOutputTokens } from '../../shared/contracts'
import type { ResolvedMcpServer } from '../capabilities/capability-service'
import type { BrowserToolService } from '../browser/browser-model-tools'
import type { WorkspaceAccess } from '../workspace'
@@ -233,8 +234,10 @@ export type ModelRuntimeOptions = {
protocol: Exclude<ModelProtocol, 'openai-images-generations'>
authentication: ModelAuthentication
contextWindowTokens?: number
maximumOutputTokens?: number
}
}
maximumOutputTokens?: number
}
function getErrorMessage(value: unknown): string | undefined {
@@ -1794,10 +1797,9 @@ export class ModelAgentRuntime implements AgentRuntime {
}
private get anthropicProtocolMaximumTokens(): number {
return Math.max(
this.options.contextCompression?.contextWindowTokens ??
minimumModelContextWindowTokens,
minimumModelContextWindowTokens
return (
this.options.maximumOutputTokens ??
defaultAnthropicMaximumOutputTokens
)
}
@@ -1877,7 +1879,8 @@ export class ModelAgentRuntime implements AgentRuntime {
ModelProtocol,
'openai-images-generations'
>,
authentication: this.options.authentication
authentication: this.options.authentication,
maximumOutputTokens: this.options.maximumOutputTokens
}
const summaryRuntime = new ModelAgentRuntime({
...summaryModel,
+1
View File
@@ -386,6 +386,7 @@ export function createResolvedModelProfileDigest(
authentication: profile.authentication,
supportsImageInput: profile.supportsImageInput === true,
contextWindowTokens: profile.contextWindowTokens ?? null,
maximumOutputTokens: profile.maximumOutputTokens ?? null,
imageGenerationQuality: profile.imageGenerationQuality ?? null
})
)
+5 -2
View File
@@ -283,6 +283,7 @@ describe('RuntimeSettingsStore', () => {
authentication: 'none',
supportsImageInput: false,
contextWindowTokens: 256_000,
maximumOutputTokens: 48_000,
imageGenerationQuality: 'auto',
apiKey: { action: 'keep' }
}
@@ -302,7 +303,8 @@ describe('RuntimeSettingsStore', () => {
modelProfiles: [
{
id: profileId,
contextWindowTokens: 256_000
contextWindowTokens: 256_000,
maximumOutputTokens: 48_000
}
],
contextCompression: {
@@ -316,7 +318,8 @@ describe('RuntimeSettingsStore', () => {
modelProfiles: [
{
id: profileId,
contextWindowTokens: 256_000
contextWindowTokens: 256_000,
maximumOutputTokens: 48_000
}
],
contextCompression: {
+16
View File
@@ -170,6 +170,12 @@ const currentStoredModelProfileSchema = storedModelProfileSchema.extend({
.int()
.min(8_000)
.max(10_000_000)
.optional(),
maximumOutputTokens: z
.number()
.int()
.min(1)
.max(10_000_000)
.optional()
})
@@ -434,6 +440,7 @@ export type ResolvedModelProfile = {
authentication: RuntimeSettings['modelAuthentication']
supportsImageInput?: boolean
contextWindowTokens?: number
maximumOutputTokens?: number
imageGenerationQuality?: RuntimeSettings['imageGenerationQuality']
apiKey?: string
}
@@ -1468,6 +1475,7 @@ export class RuntimeSettingsStore {
authentication: RuntimeSettings['modelAuthentication']
supportsImageInput: boolean
contextWindowTokens?: number
maximumOutputTokens?: number
imageGenerationQuality: RuntimeSettings['imageGenerationQuality']
credentialSource: RuntimeSettings['credentialSource']
} {
@@ -1529,6 +1537,7 @@ export class RuntimeSettingsStore {
: profile.authentication,
supportsImageInput: profile.supportsImageInput,
contextWindowTokens: profile.contextWindowTokens,
maximumOutputTokens: profile.maximumOutputTokens,
imageGenerationQuality: profile.imageGenerationQuality,
credentialSource: credential.source
}
@@ -1552,6 +1561,7 @@ export class RuntimeSettingsStore {
authentication: effective.authentication,
supportsImageInput: effective.supportsImageInput,
contextWindowTokens: effective.contextWindowTokens,
maximumOutputTokens: effective.maximumOutputTokens,
imageGenerationQuality: effective.imageGenerationQuality,
apiKey: effective.apiKey
}
@@ -1569,6 +1579,7 @@ export class RuntimeSettingsStore {
authentication: profile.authentication,
supportsImageInput: profile.supportsImageInput,
contextWindowTokens: profile.contextWindowTokens,
maximumOutputTokens: profile.maximumOutputTokens,
imageGenerationQuality: profile.imageGenerationQuality,
apiKey: credential.activeApiKey
}
@@ -1651,6 +1662,7 @@ export class RuntimeSettingsStore {
authentication: resolved.authentication,
supportsImageInput: resolved.supportsImageInput,
contextWindowTokens: resolved.contextWindowTokens,
maximumOutputTokens: resolved.maximumOutputTokens,
imageGenerationQuality:
resolved.imageGenerationQuality ??
defaultRuntimeSettings.imageGenerationQuality,
@@ -1672,6 +1684,7 @@ export class RuntimeSettingsStore {
authentication: profile.authentication,
supportsImageInput: profile.supportsImageInput,
contextWindowTokens: profile.contextWindowTokens,
maximumOutputTokens: profile.maximumOutputTokens,
imageGenerationQuality:
profile.imageGenerationQuality ??
defaultRuntimeSettings.imageGenerationQuality,
@@ -1966,6 +1979,7 @@ export class RuntimeSettingsStore {
authentication: input.modelAuthentication,
supportsImageInput: profile.supportsImageInput,
contextWindowTokens: profile.contextWindowTokens,
maximumOutputTokens: profile.maximumOutputTokens,
imageGenerationQuality: input.imageGenerationQuality,
apiKey: input.apiKey
}
@@ -1978,6 +1992,7 @@ export class RuntimeSettingsStore {
authentication: profile.authentication,
supportsImageInput: profile.supportsImageInput,
contextWindowTokens: profile.contextWindowTokens,
maximumOutputTokens: profile.maximumOutputTokens,
imageGenerationQuality: profile.imageGenerationQuality,
apiKey: { action: 'keep' as const }
}
@@ -2029,6 +2044,7 @@ export class RuntimeSettingsStore {
authentication: profile.authentication,
supportsImageInput: profile.supportsImageInput ?? false,
contextWindowTokens: profile.contextWindowTokens,
maximumOutputTokens: profile.maximumOutputTokens,
imageGenerationQuality: profile.imageGenerationQuality
}
if (