fix(types): normalize executor result contracts (#10256)

This commit is contained in:
backryun
2026-08-14 12:57:53 +09:00
committed by GitHub
parent 2eec31b84a
commit 9da4e24013
4 changed files with 59 additions and 34 deletions

View File

@@ -387,6 +387,12 @@ import {
isRpmExhausted,
} from "../services/geminiRateLimitTracker.ts";
import { isSmallEnoughForSemanticCache } from "../utils/estimateSize.ts";
type ChatCoreExecutorResult = ReturnType<typeof normalizeExecutorResult> & {
_executionCredentials?: Record<string, unknown>;
_accountSemaphoreRelease?: () => void;
};
/**
* Core chat handler - shared between SSE and Worker
* Returns { success, response, status, error } for caller to handle fallback
@@ -2731,7 +2737,7 @@ export async function handleChatCore({
let releaseRawResultAccountSemaphore = () => {};
try {
const rawResult = await (async () => {
const rawResult: ChatCoreExecutorResult = await (async () => {
let attempts = 0;
const isModelScopeForRequest = isModelScope();
const maxAttempts = isModelScopeForRequest ? 3 : provider === "codex" ? 3 : 1;
@@ -3550,22 +3556,24 @@ export async function handleChatCore({
// stay aligned if this block ever runs after a path that mutates body.model (e.g. fallback).
try {
const retryModelId = String(translatedBody.model || effectiveModel);
const retryResult = await runWithCapture(providerRequestCapture, () =>
executor.execute({
model: retryModelId,
body: translatedBody,
stream: upstreamStream,
credentials: getExecutionCredentials(),
signal: streamController.signal,
log,
extendedContext,
upstreamExtraHeaders: buildUpstreamHeadersForExecute(retryModelId),
clientHeaders: buildExecutorClientHeaders(clientRawRequest?.headers, userAgent),
clientResponseFormat,
onCredentialsRefreshed,
skipUpstreamRetry: isCombo,
contextEditing: { enabled: contextEditingEnabled },
})
const retryResult = normalizeExecutorResult(
await runWithCapture(providerRequestCapture, () =>
executor.execute({
model: retryModelId,
body: translatedBody,
stream: upstreamStream,
credentials: getExecutionCredentials(),
signal: streamController.signal,
log,
extendedContext,
upstreamExtraHeaders: buildUpstreamHeadersForExecute(retryModelId),
clientHeaders: buildExecutorClientHeaders(clientRawRequest?.headers, userAgent),
clientResponseFormat,
onCredentialsRefreshed,
skipUpstreamRetry: isCombo,
contextEditing: { enabled: contextEditingEnabled },
})
)
);
if (retryResult.response.ok) {

View File

@@ -20,6 +20,10 @@ type CredentialsLike =
| null
| undefined;
type ResolvedExecutionCredentials = Record<string, unknown> & {
providerSpecificData: Record<string, unknown>;
};
function buildKimiThinkingMetadata(
modelInfo: Record<string, unknown> | null | undefined,
staticThinkingPolicy: ReturnType<typeof getKimiCodeStaticThinkingPolicy>
@@ -79,7 +83,7 @@ export function resolveExecutionCredentials(opts: {
provider: string | null | undefined;
ccSessionId: string | null;
modelInfo?: Record<string, unknown> | null;
}) {
}): ResolvedExecutionCredentials {
const {
credentials,
nativeCodexPassthrough,

View File

@@ -98,17 +98,7 @@ export function getExecutorTimeoutMs(executor: unknown, provider?: string, model
return resolveProviderTimeoutMs(executor);
}
export function normalizeExecutorResult(
result:
| Response
| {
response: Response;
url?: string;
headers?: Record<string, string>;
transformedBody?: unknown;
transport?: string;
}
): {
export function normalizeExecutorResult(result: unknown): {
response: Response;
url: string;
headers: Record<string, string>;
@@ -118,12 +108,27 @@ export function normalizeExecutorResult(
if (result instanceof Response) {
return { response: result, url: "", headers: {}, transformedBody: null };
}
if (
!result ||
typeof result !== "object" ||
!("response" in result) ||
!(result.response instanceof Response)
) {
throw new TypeError("Executor result must contain a Response");
}
const normalized = result as {
response: Response;
url?: string;
headers?: Record<string, string>;
transformedBody?: unknown;
transport?: string;
};
return {
response: result.response,
url: result.url || "",
headers: result.headers || {},
transformedBody: result.transformedBody ?? null,
transport: result.transport,
response: normalized.response,
url: normalized.url || "",
headers: normalized.headers || {},
transformedBody: normalized.transformedBody ?? null,
transport: normalized.transport,
};
}

View File

@@ -49,3 +49,11 @@ test("normalizeExecutorResult wraps bare Response and passes through rich result
assert.equal(rich.url, "u");
assert.equal(rich.headers.a, "b");
});
test("normalizeExecutorResult rejects malformed executor output", () => {
assert.throws(() => normalizeExecutorResult({}), /must contain a Response/);
assert.throws(
() => normalizeExecutorResult({ response: "not-a-response" }),
/must contain a Response/
);
});