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
@@ -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;
}