refactor: share provider HTTP error parsing · openclaw/openclaw@2c516fe
steipete
·
2026-04-25
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | | -b758a1c5503c08325113e0d6c9f1ac2db5a5fd9992a3902706ebe0f0dbbc1213 plugin-sdk-api-baseline.json |
2 | | -2c9d0a00e526dcd47d131261b8ceddd8e59faa8530b129d108a3721a4cbcbea7 plugin-sdk-api-baseline.jsonl |
| 1 | +b86e6785acbd0f8f0f012691ce823c2e8d52bfd2507c2258408503162abb9adf plugin-sdk-api-baseline.json |
| 2 | +e10acbed6c7c1b21700e358411c73877bdc0cb59ce102bafe680e210a2ac741b plugin-sdk-api-baseline.jsonl |
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; |
| 2 | +import { assertOkOrThrowProviderError } from "openclaw/plugin-sdk/provider-http"; |
2 | 3 | import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input"; |
3 | 4 | import type { |
4 | 5 | SpeechDirectiveTokenParseContext, |
@@ -297,9 +298,7 @@ export async function listElevenLabsVoices(params: {
|
297 | 298 | "xi-api-key": params.apiKey, |
298 | 299 | }, |
299 | 300 | }); |
300 | | -if (!res.ok) { |
301 | | -throw new Error(`ElevenLabs voices API error (${res.status})`); |
302 | | -} |
| 301 | +await assertOkOrThrowProviderError(res, "ElevenLabs voices API error"); |
303 | 302 | const json = (await res.json()) as { |
304 | 303 | voices?: Array<{ |
305 | 304 | voice_id?: string; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -6,6 +6,7 @@ import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth";
|
6 | 6 | import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime"; |
7 | 7 | import { |
8 | 8 | assertOkOrThrowHttpError, |
| 9 | +assertOkOrThrowProviderError, |
9 | 10 | resolveProviderHttpRequestConfig, |
10 | 11 | } from "openclaw/plugin-sdk/provider-http"; |
11 | 12 | import { |
@@ -251,12 +252,7 @@ async function fetchImageBuffer(
|
251 | 252 | auditContext: "fal-image-download", |
252 | 253 | }); |
253 | 254 | try { |
254 | | -if (!response.ok) { |
255 | | -const text = await response.text().catch(() => ""); |
256 | | -throw new Error( |
257 | | -`fal image download failed (${response.status}): ${text || response.statusText}`, |
258 | | -); |
259 | | -} |
| 255 | +await assertOkOrThrowProviderError(response, "fal image download failed"); |
260 | 256 | const mimeType = response.headers.get("content-type")?.trim() || "image/png"; |
261 | 257 | const arrayBuffer = await response.arrayBuffer(); |
262 | 258 | return { buffer: Buffer.from(arrayBuffer), mimeType }; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -9,6 +9,7 @@ import {
|
9 | 9 | sanitizeAndNormalizeEmbedding, |
10 | 10 | withRemoteHttpResponse, |
11 | 11 | } from "openclaw/plugin-sdk/memory-core-host-engine-embeddings"; |
| 12 | +import { createProviderHttpError } from "openclaw/plugin-sdk/provider-http"; |
12 | 13 | import type { GeminiEmbeddingClient, GeminiTextEmbeddingRequest } from "./embedding-provider.js"; |
13 | 14 | |
14 | 15 | export type GeminiBatchRequest = { |
@@ -179,8 +180,7 @@ async function fetchGeminiBatchStatus(params: {
|
179 | 180 | }, |
180 | 181 | onResponse: async (res) => { |
181 | 182 | if (!res.ok) { |
182 | | -const text = await res.text(); |
183 | | -throw new Error(`gemini batch status failed: ${res.status} ${text}`); |
| 183 | +throw await createProviderHttpError(res, "gemini batch status failed"); |
184 | 184 | } |
185 | 185 | return (await res.json()) as GeminiBatchStatus; |
186 | 186 | }, |
@@ -203,8 +203,7 @@ async function fetchGeminiFileContent(params: {
|
203 | 203 | }, |
204 | 204 | onResponse: async (res) => { |
205 | 205 | if (!res.ok) { |
206 | | -const text = await res.text(); |
207 | | -throw new Error(`gemini batch file content failed: ${res.status} ${text}`); |
| 206 | +throw await createProviderHttpError(res, "gemini batch file content failed"); |
208 | 207 | } |
209 | 208 | return await res.text(); |
210 | 209 | }, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -15,6 +15,7 @@ import {
|
15 | 15 | requireApiKey, |
16 | 16 | resolveApiKeyForProvider, |
17 | 17 | } from "openclaw/plugin-sdk/provider-auth-runtime"; |
| 18 | +import { createProviderHttpError } from "openclaw/plugin-sdk/provider-http"; |
18 | 19 | import type { SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime"; |
19 | 20 | import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; |
20 | 21 | |
@@ -189,8 +190,7 @@ async function fetchGeminiEmbeddingPayload(params: {
|
189 | 190 | }, |
190 | 191 | onResponse: async (res) => { |
191 | 192 | if (!res.ok) { |
192 | | -const text = await res.text(); |
193 | | -throw new Error(`gemini embeddings failed: ${res.status} ${text}`); |
| 193 | +throw await createProviderHttpError(res, "gemini embeddings failed"); |
194 | 194 | } |
195 | 195 | return (await res.json()) as { |
196 | 196 | embedding?: { values?: number[] }; |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
| 1 | +import { |
| 2 | +createProviderHttpError, |
| 3 | +formatProviderHttpErrorMessage, |
| 4 | +} from "openclaw/plugin-sdk/provider-http"; |
1 | 5 | import { |
2 | 6 | buildSearchCacheKey, |
3 | 7 | buildUnsupportedSearchFilterResponse, |
@@ -81,11 +85,8 @@ async function runGeminiSearch(params: {
|
81 | 85 | }, |
82 | 86 | async (res) => { |
83 | 87 | if (!res.ok) { |
84 | | -const safeDetail = ((await res.text()) || res.statusText).replace( |
85 | | -/key=[^&\s]+/giu, |
86 | | -"key=***", |
87 | | -); |
88 | | -throw new Error(`Gemini API error (${res.status}): ${safeDetail}`); |
| 88 | +const error = await createProviderHttpError(res, "Gemini API error"); |
| 89 | +throw new Error(error.message.replace(/key=[^&\s]+/giu, "key=***")); |
89 | 90 | } |
90 | 91 | |
91 | 92 | let data: GeminiGroundingResponse; |
@@ -99,7 +100,11 @@ async function runGeminiSearch(params: {
|
99 | 100 | if (data.error) { |
100 | 101 | const rawMessage = data.error.message || data.error.status || "unknown"; |
101 | 102 | throw new Error( |
102 | | -`Gemini API error (${data.error.code}): ${rawMessage.replace(/key=[^&\s]+/giu, "key=***")}`, |
| 103 | +formatProviderHttpErrorMessage({ |
| 104 | +label: "Gemini API error", |
| 105 | +status: data.error.code ?? 0, |
| 106 | +detail: rawMessage.replace(/key=[^&\s]+/giu, "key=***"), |
| 107 | +}), |
103 | 108 | ); |
104 | 109 | } |
105 | 110 | |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -7,6 +7,7 @@ import {
|
7 | 7 | type SimpleStreamOptions, |
8 | 8 | type ThinkingLevel, |
9 | 9 | } from "@mariozechner/pi-ai"; |
| 10 | +import { createProviderHttpError } from "openclaw/plugin-sdk/provider-http"; |
10 | 11 | import { |
11 | 12 | buildGuardedModelFetch, |
12 | 13 | coerceTransportToolCallArguments, |
@@ -631,8 +632,7 @@ export function createGoogleGenerativeAiTransportStreamFn(): StreamFn {
|
631 | 632 | signal: options?.signal, |
632 | 633 | }); |
633 | 634 | if (!response.ok) { |
634 | | -const message = await response.text().catch(() => ""); |
635 | | -throw new Error(`Google Generative AI API error (${response.status}): ${message}`); |
| 635 | +throw await createProviderHttpError(response, "Google Generative AI API error"); |
636 | 636 | } |
637 | 637 | stream.push({ type: "start", partial: output as never }); |
638 | 638 | let currentBlockIndex = -1; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -6,6 +6,7 @@ import {
|
6 | 6 | generateSecMsGecToken, |
7 | 7 | } from "node-edge-tts/dist/drm.js"; |
8 | 8 | import { isVoiceCompatibleAudio } from "openclaw/plugin-sdk/media-runtime"; |
| 9 | +import { assertOkOrThrowProviderError } from "openclaw/plugin-sdk/provider-http"; |
9 | 10 | import { |
10 | 11 | captureHttpExchange, |
11 | 12 | isDebugProxyGlobalFetchPatchInstalled, |
@@ -153,9 +154,7 @@ export async function listMicrosoftVoices(): Promise<SpeechVoiceOption[]> {
|
153 | 154 | }, |
154 | 155 | }); |
155 | 156 | } |
156 | | -if (!response.ok) { |
157 | | -throw new Error(`Microsoft voices API error (${response.status})`); |
158 | | -} |
| 157 | +await assertOkOrThrowProviderError(response, "Microsoft voices API error"); |
159 | 158 | const voices = (await response.json()) as MicrosoftVoiceListEntry[]; |
160 | 159 | return Array.isArray(voices) |
161 | 160 | ? voices |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
| 1 | +import { |
| 2 | +createProviderHttpError, |
| 3 | +formatProviderHttpErrorMessage, |
| 4 | +} from "openclaw/plugin-sdk/provider-http"; |
1 | 5 | import { |
2 | 6 | DEFAULT_SEARCH_COUNT, |
3 | 7 | buildSearchCacheKey, |
@@ -134,15 +138,18 @@ async function runMiniMaxSearch(params: {
|
134 | 138 | }, |
135 | 139 | async (res) => { |
136 | 140 | if (!res.ok) { |
137 | | -const detail = await res.text(); |
138 | | -throw new Error(`MiniMax Search API error (${res.status}): ${detail || res.statusText}`); |
| 141 | +throw await createProviderHttpError(res, "MiniMax Search API error"); |
139 | 142 | } |
140 | 143 | |
141 | 144 | const data = (await res.json()) as MiniMaxSearchResponse; |
142 | 145 | |
143 | 146 | if (data.base_resp?.status_code && data.base_resp.status_code !== 0) { |
144 | 147 | throw new Error( |
145 | | -`MiniMax Search API error (${data.base_resp.status_code}): ${data.base_resp.status_msg || "unknown error"}`, |
| 148 | +formatProviderHttpErrorMessage({ |
| 149 | +label: "MiniMax Search API error", |
| 150 | +status: data.base_resp.status_code, |
| 151 | +detail: data.base_resp.status_msg || "unknown error", |
| 152 | +}), |
146 | 153 | ); |
147 | 154 | } |
148 | 155 | |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
| 1 | +import { assertOkOrThrowProviderError } from "openclaw/plugin-sdk/provider-http"; |
| 2 | + |
1 | 3 | export const DEFAULT_MINIMAX_TTS_BASE_URL = "https://api.minimax.io"; |
2 | 4 | |
3 | 5 | export const MINIMAX_TTS_MODELS = ["speech-2.8-hd", "speech-01-240228"] as const; |
@@ -72,10 +74,7 @@ export async function minimaxTTS(params: {
|
72 | 74 | signal: controller.signal, |
73 | 75 | }); |
74 | 76 | |
75 | | -if (!response.ok) { |
76 | | -const errBody = await response.text().catch(() => ""); |
77 | | -throw new Error(`MiniMax TTS API error (${response.status})${errBody ? `: ${errBody}` : ""}`); |
78 | | -} |
| 77 | +await assertOkOrThrowProviderError(response, "MiniMax TTS API error"); |
79 | 78 | |
80 | 79 | const body = (await response.json()) as { data?: { audio?: string } }; |
81 | 80 | const hexAudio = body?.data?.audio; |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。