feat(server): add proactive channel probes and load-aware stable routing / 服务端新增主动探活与负载感知稳定路由 (#330)

* wip: preserve local changes before upstream merge

* Improve proxy routing stability and add stable-first strategy

* Sync explicit-group routing strategy to eligible source routes

* Fix proxy retry and runtime health review issues

* fix: make proxy channel retry attempts configurable

* fix: harden proxy retry recovery and bookkeeping

* fix: address pr 257 review comments

* feat(proxy): add sticky session channel coordination

* feat(server): add proactive channel probes and load-aware stable routing

* test(server): stabilize session load routing selection assertion

* fix(proxy): harden codex forwarding stability

* fix(router): rotate stable-first channels by healthy order

* fix(server): address probe review feedback

* fix(server): address probe review follow-ups

* fix(ci): address CodeQL and core test failures

* feat(server): refine stable-first routing recovery and observation

* Finish PR 330 settings and review fixes

* Fix chat site endpoint CI mock

* Address latest PR 330 review comments

---------

Co-authored-by: Cita <juricek.chen@gmail.com>
This commit is contained in:
Bainily
2026-04-02 09:49:04 +08:00
committed by GitHub
parent 1133846998
commit 596d1b93cb
75 changed files with 6365 additions and 507 deletions
@@ -1,5 +1,57 @@
import { type DownstreamFormat } from '../../shared/normalized.js';
function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object' && !Array.isArray(value);
}
function asTrimmedString(value: unknown): string {
return typeof value === 'string' ? value.trim() : '';
}
function hasAnthropicContinuationHint(body: Record<string, unknown>): boolean {
if (asTrimmedString(body.previous_response_id)) return true;
if (asTrimmedString(body.prompt_cache_key)) return true;
if (isRecord(body.metadata) && asTrimmedString(body.metadata.user_id)) return true;
return false;
}
function hasOrphanAnthropicToolResult(body: Record<string, unknown>): boolean {
const messages = Array.isArray(body.messages) ? body.messages : [];
const seenToolUseIds = new Set<string>();
for (const message of messages) {
if (!isRecord(message) || !Array.isArray(message.content)) continue;
const role = asTrimmedString(message.role).toLowerCase();
for (const block of message.content) {
if (!isRecord(block)) continue;
const type = asTrimmedString(block.type).toLowerCase();
if (!type) continue;
if (role === 'assistant' && type === 'tool_use') {
const toolUseId = asTrimmedString(block.id);
if (toolUseId) seenToolUseIds.add(toolUseId);
continue;
}
if (type !== 'tool_result') continue;
const toolUseId = asTrimmedString(block.tool_use_id ?? block.toolUseId);
if (toolUseId && !seenToolUseIds.has(toolUseId)) {
return true;
}
}
}
return false;
}
export function shouldPreferResponsesForAnthropicContinuation(
body: Record<string, unknown> | null | undefined,
): boolean {
if (!body) return false;
return hasAnthropicContinuationHint(body) && hasOrphanAnthropicToolResult(body);
}
export function shouldRetryNormalizedMessagesBody(input: {
downstreamFormat: DownstreamFormat;
endpointPath: string;
@@ -62,6 +62,30 @@ describe('sanitizeAnthropicMessagesBody', () => {
]);
});
it('normalizes primitive content arrays into text blocks and drops unsupported entries', () => {
const result = sanitizeAnthropicMessagesBody({
model: 'claude-opus-4-6',
messages: [
{
role: 'user',
content: ['hello', true, 42, null, { type: 'text', text: 'done' }],
},
],
});
expect(result.messages).toEqual([
{
role: 'user',
content: [
{ type: 'text', text: 'hello' },
{ type: 'text', text: 'true' },
{ type: 'text', text: '42' },
{ type: 'text', text: 'done', cache_control: { type: 'ephemeral' } },
],
},
]);
});
it('drops output_config.effort unless thinking stays adaptive', () => {
const enabled = sanitizeAnthropicMessagesBody({
model: 'claude-opus-4-6',
@@ -425,6 +425,43 @@ function sanitizeAnthropicContentBlock(item: Record<string, unknown>): Record<st
return { ...item };
}
function sanitizeAnthropicMessage(message: Record<string, unknown>): Record<string, unknown> | null {
const next: Record<string, unknown> = { ...message };
const rawContent = next.content;
if (Array.isArray(rawContent)) {
const content = rawContent
.map((item) => {
if (isRecord(item)) return sanitizeAnthropicContentBlock(item);
if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') {
return sanitizeAnthropicContentBlock(toAnthropicTextBlock(String(item)) ?? {});
}
return null;
})
.filter((item): item is Record<string, unknown> => item !== null);
if (content.length <= 0) return null;
next.content = content;
return next;
}
if (rawContent === null || rawContent === undefined) {
return null;
}
if (isRecord(rawContent)) {
const block = sanitizeAnthropicContentBlock(rawContent);
if (!block) return null;
next.content = [block];
return next;
}
if (typeof rawContent === 'number' || typeof rawContent === 'boolean') {
next.content = String(rawContent);
}
return typeof next.content === 'string' ? next : null;
}
function isCacheableAnthropicMessageBlock(item: Record<string, unknown>): boolean {
const type = asTrimmedString(item.type).toLowerCase();
if (type === 'thinking' || type === 'redacted_thinking') return false;
@@ -710,15 +747,9 @@ export function sanitizeAnthropicMessagesBody(
const rawMessages = Array.isArray(sanitized.messages) ? sanitized.messages : [];
if (rawMessages.length > 0) {
sanitized.messages = rawMessages.map((message) => {
if (!isRecord(message)) return message;
const next = { ...message };
if (Array.isArray(next.content)) {
next.content = next.content
.map((item) => (isRecord(item) ? sanitizeAnthropicContentBlock(item) : item))
.filter((item) => item !== null);
}
return next;
});
if (!isRecord(message)) return null;
return sanitizeAnthropicMessage(message);
}).filter((message): message is Record<string, unknown> => message !== null);
}
if (autoOptimizeCacheControls) {
@@ -43,6 +43,66 @@ describe('anthropicMessagesInbound', () => {
});
});
it('drops null-content turns instead of rejecting the whole Claude request', () => {
const result = anthropicMessagesInbound.parse({
model: 'claude-opus-4-6',
max_tokens: 256,
messages: [
{ role: 'user', content: 'hello' },
{ role: 'assistant', content: null },
],
});
expect(result.error).toBeUndefined();
expect(result.value?.parsed.upstreamBody.messages).toEqual([
{ role: 'user', content: 'hello' },
]);
expect(result.value?.parsed.claudeOriginalBody).toMatchObject({
messages: [
{ role: 'user', content: 'hello' },
],
});
});
it('preserves continuation hints for tool_result-only follow-up turns', () => {
const result = anthropicMessagesInbound.parse({
model: 'claude-opus-4-6',
max_tokens: 256,
previous_response_id: 'resp_prev_1',
prompt_cache_key: 'cache-key-1',
messages: [
{
role: 'user',
content: [
{
type: 'tool_result',
tool_use_id: 'toolu_missing',
content: [{ type: 'text', text: '{"matches":1}' }],
},
{ type: 'text', text: 'continue' },
],
},
],
});
expect(result.error).toBeUndefined();
expect(result.value?.parsed.upstreamBody).toMatchObject({
previous_response_id: 'resp_prev_1',
prompt_cache_key: 'cache-key-1',
messages: [
{
role: 'tool',
tool_call_id: 'toolu_missing',
content: '{"matches":1}',
},
{
role: 'user',
content: 'continue',
},
],
});
});
it('normalizes adaptive effort and tool choice at the inbound boundary', () => {
const result = anthropicMessagesInbound.parse({
model: 'claude-opus-4-6',
@@ -0,0 +1,125 @@
import { describe, expect, it } from 'vitest';
import {
extractResponsesTerminalResponseId,
isResponsesPreviousResponseNotFoundError,
shouldInferResponsesPreviousResponseId,
stripResponsesPreviousResponseId,
withResponsesPreviousResponseId,
} from './continuation.js';
describe('responses continuation helpers', () => {
it('infers previous_response_id only for tool-output follow-up turns that omit it', () => {
expect(shouldInferResponsesPreviousResponseId({
input: [{
type: 'function_call_output',
call_id: 'call_1',
output: '{"ok":true}',
}],
}, 'resp_prev_1')).toBe(true);
expect(shouldInferResponsesPreviousResponseId({
previous_response_id: 'resp_existing',
input: [{
type: 'function_call_output',
call_id: 'call_1',
output: '{"ok":true}',
}],
}, 'resp_prev_1')).toBe(false);
expect(shouldInferResponsesPreviousResponseId({
input: [{
type: 'message',
role: 'user',
content: [{ type: 'input_text', text: 'hello' }],
}],
}, 'resp_prev_1')).toBe(false);
});
it('adds and removes previous_response_id without mutating other fields', () => {
const body = {
model: 'gpt-5.4',
input: [{
type: 'function_call_output',
call_id: 'call_1',
output: '{"ok":true}',
}],
};
const withPrevious = withResponsesPreviousResponseId(body, 'resp_prev_1');
expect(withPrevious).toMatchObject({
model: 'gpt-5.4',
previous_response_id: 'resp_prev_1',
});
const stripped = stripResponsesPreviousResponseId(withPrevious);
expect(stripped.removed).toBe(true);
expect(stripped.body.previous_response_id).toBeUndefined();
expect(stripped.body.input).toEqual(body.input);
});
it('detects previous_response_not_found from either raw text or structured payloads', () => {
expect(isResponsesPreviousResponseNotFoundError({
rawErrText: JSON.stringify({
error: {
code: 'previous_response_not_found',
message: 'previous_response_not_found',
},
}),
})).toBe(true);
expect(isResponsesPreviousResponseNotFoundError({
payload: {
type: 'response.failed',
response: {
error: {
message: 'Previous response id not found',
type: 'invalid_request_error',
},
},
},
})).toBe(true);
expect(isResponsesPreviousResponseNotFoundError({
rawErrText: JSON.stringify({
error: {
code: 'rate_limit_exceeded',
message: 'too many requests',
},
}),
})).toBe(false);
});
it('extracts terminal response ids from responses payloads and websocket terminal events', () => {
expect(extractResponsesTerminalResponseId({
id: 'resp_1',
object: 'response',
status: 'completed',
})).toBe('resp_1');
expect(extractResponsesTerminalResponseId({
type: 'response.failed',
response: {
id: 'resp_2',
status: 'failed',
error: {
message: 'tool failed',
},
},
})).toBe('resp_2');
expect(extractResponsesTerminalResponseId({
type: 'response.created',
response: {
id: 'resp_3',
status: 'in_progress',
},
})).toBeNull();
expect(extractResponsesTerminalResponseId({
id: 'resp_in_progress',
object: 'response',
status: 'in_progress',
})).toBeNull();
});
});
@@ -0,0 +1,143 @@
function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object' && !Array.isArray(value);
}
function asTrimmedString(value: unknown): string {
return typeof value === 'string' ? value.trim() : '';
}
const RESPONSES_TOOL_OUTPUT_TYPES = new Set([
'function_call_output',
'custom_tool_call_output',
]);
const RESPONSES_TERMINAL_STATUSES = new Set([
'completed',
'failed',
'incomplete',
]);
function collectResponsesErrorFragments(value: unknown): string[] {
if (!value) return [];
if (typeof value === 'string') return [value];
if (!isRecord(value)) return [];
const fragments = [
value.type,
value.code,
value.message,
value.reason,
]
.map((entry) => asTrimmedString(entry))
.filter(Boolean);
if (isRecord(value.error)) {
fragments.push(...collectResponsesErrorFragments(value.error));
}
if (isRecord(value.response)) {
fragments.push(...collectResponsesErrorFragments(value.response));
}
if (isRecord(value.incomplete_details)) {
fragments.push(...collectResponsesErrorFragments(value.incomplete_details));
}
return fragments;
}
function hasResponsesToolOutput(input: unknown): boolean {
if (!Array.isArray(input)) return false;
return input.some((item) => {
if (!isRecord(item)) return false;
const type = asTrimmedString(item.type).toLowerCase();
if (!RESPONSES_TOOL_OUTPUT_TYPES.has(type)) return false;
return asTrimmedString(item.call_id ?? item.id).length > 0;
});
}
export function shouldInferResponsesPreviousResponseId(
body: Record<string, unknown> | null | undefined,
candidatePreviousResponseId: unknown,
): candidatePreviousResponseId is string {
if (!body) return false;
if (asTrimmedString(body.previous_response_id)) return false;
const candidate = asTrimmedString(candidatePreviousResponseId);
if (!candidate) return false;
return hasResponsesToolOutput(body.input);
}
export function withResponsesPreviousResponseId(
body: Record<string, unknown>,
previousResponseId: string,
): Record<string, unknown> {
return {
...body,
previous_response_id: previousResponseId.trim(),
};
}
export function stripResponsesPreviousResponseId(
body: Record<string, unknown>,
): { body: Record<string, unknown>; removed: boolean } {
if (!Object.prototype.hasOwnProperty.call(body, 'previous_response_id')) {
return { body, removed: false };
}
const next = { ...body };
delete next.previous_response_id;
return { body: next, removed: true };
}
export function isResponsesPreviousResponseNotFoundError(input: {
rawErrText?: string | null;
payload?: unknown;
}): boolean {
const fragments = [
...collectResponsesErrorFragments(input.payload),
];
const rawErrText = asTrimmedString(input.rawErrText);
if (rawErrText) fragments.push(rawErrText);
const combined = fragments.join(' ').toLowerCase();
if (!combined) return false;
return (
combined.includes('previous_response_not_found')
|| /previous[\s_-]*response(?:[\s_-]*(?:id|identifier))?[\s_-]*not[\s_-]*found/i.test(combined)
);
}
export function extractResponsesTerminalResponseId(payload: unknown): string | null {
if (!isRecord(payload)) return null;
const type = asTrimmedString(payload.type).toLowerCase();
if (
type === 'response.completed'
|| type === 'response.failed'
|| type === 'response.incomplete'
) {
if (isRecord(payload.response)) {
const responseId = asTrimmedString(payload.response.id);
return responseId || null;
}
return null;
}
const object = asTrimmedString(payload.object).toLowerCase();
const status = asTrimmedString(payload.status).toLowerCase();
if (
(object === 'response' && RESPONSES_TERMINAL_STATUSES.has(status))
|| RESPONSES_TERMINAL_STATUSES.has(status)
) {
const responseId = asTrimmedString(payload.id);
return responseId || null;
}
if (isRecord(payload.response)) {
const nestedStatus = asTrimmedString(payload.response.status).toLowerCase();
if (RESPONSES_TERMINAL_STATUSES.has(nestedStatus)) {
const responseId = asTrimmedString(payload.response.id);
return responseId || null;
}
}
return null;
}
@@ -1234,6 +1234,12 @@ export function convertClaudeRequestToOpenAiBody(body: Record<string, unknown>):
if (body.tools !== undefined) payload.tools = convertClaudeToolsToOpenAiChat(body.tools);
if (body.tool_choice !== undefined) payload.tool_choice = convertClaudeToolChoiceToOpenAiChat(body.tool_choice);
const promptCacheKey = asTrimmedString(body.prompt_cache_key);
if (promptCacheKey) payload.prompt_cache_key = promptCacheKey;
const previousResponseId = asTrimmedString(body.previous_response_id);
if (previousResponseId) payload.previous_response_id = previousResponseId;
return { model, stream, messages, payload };
}