fix(ai): prefix HTTP status codes onto Azure OpenAI and OpenAI Responses error messages so auto-retry fires on 5xx/429

closes #4232
This commit is contained in:
Mario Zechner
2026-05-18 01:10:54 +02:00
parent 21d80deda2
commit 52e13870a1
3 changed files with 35 additions and 2 deletions

View File

@@ -4,6 +4,7 @@
### Fixed
- Fixed Azure OpenAI Responses and OpenAI Responses error formatting to prefix HTTP status codes onto `errorMessage`, so transient 5xx and 429 errors are correctly matched by the agent-level auto-retry classifier ([#4232](https://github.com/earendil-works/pi/issues/4232)).
- Fixed Xiaomi MiMo model metadata to use the OpenAI-compatible endpoints and `openai-completions` API, restoring multi-turn thinking/tool-call sessions ([#4505](https://github.com/earendil-works/pi/issues/4505)).
- Fixed OpenCode Go Kimi reasoning replay by normalizing streamed `reasoning` fields back to `reasoning_content` for OpenCode Go only ([#4251](https://github.com/earendil-works/pi/issues/4251)).

View File

@@ -40,6 +40,22 @@ function resolveDeploymentName(model: Model<"azure-openai-responses">, options?:
return mappedDeployment || model.id;
}
function formatAzureOpenAIError(error: unknown): string {
if (error instanceof Error) {
const status = (error as Error & { status?: unknown }).status;
const statusCode = typeof status === "number" ? status : undefined;
if (statusCode !== undefined) {
return `Azure OpenAI API error (${statusCode}): ${error.message}`;
}
return error.message;
}
try {
return JSON.stringify(error);
} catch {
return String(error);
}
}
// Azure OpenAI Responses-specific options
export interface AzureOpenAIResponsesOptions extends StreamOptions {
reasoningEffort?: "minimal" | "low" | "medium" | "high" | "xhigh";
@@ -119,7 +135,7 @@ export const streamAzureOpenAIResponses: StreamFunction<"azure-openai-responses"
delete (block as { partialJson?: string }).partialJson;
}
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
output.errorMessage = formatAzureOpenAIError(error);
stream.push({ type: "error", reason: output.stopReason, error: output });
stream.end();
}

View File

@@ -51,6 +51,22 @@ function getPromptCacheRetention(
return cacheRetention === "long" && compat.supportsLongCacheRetention ? "24h" : undefined;
}
function formatOpenAIResponsesError(error: unknown): string {
if (error instanceof Error) {
const status = (error as Error & { status?: unknown }).status;
const statusCode = typeof status === "number" ? status : undefined;
if (statusCode !== undefined) {
return `OpenAI API error (${statusCode}): ${error.message}`;
}
return error.message;
}
try {
return JSON.stringify(error);
} catch {
return String(error);
}
}
// OpenAI Responses-specific options
export interface OpenAIResponsesOptions extends StreamOptions {
reasoningEffort?: "minimal" | "low" | "medium" | "high" | "xhigh";
@@ -130,7 +146,7 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses", OpenAIRes
delete (block as { partialJson?: string }).partialJson;
}
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
output.errorMessage = formatOpenAIResponsesError(error);
stream.push({ type: "error", reason: output.stopReason, error: output });
stream.end();
}