Skip to content

Commit 9514d33

Browse files
fix: improve error handling (#28)
* fix(error-handling): improve error message extraction from runpod api responses - create centralized error handler following fal/replicate/elevenlabs patterns - extract actual error messages instead of generic fallbacks - parse nested json in image api errors for cleaner messages - prefer message field over error field when available - export runpodimageerrordata type for users * chore: add changeset for error handling improvements
1 parent 51dc85f commit 9514d33

File tree

4 files changed

+56
-20
lines changed

4 files changed

+56
-20
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@runpod/ai-sdk-provider": patch
3+
---
4+
5+
Improve error message extraction from Runpod API responses. Users now see actual error messages from the API instead of generic fallbacks like "Unknown error". The error handler extracts nested JSON messages from image API errors and properly surfaces all error details.
6+

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export type { RunpodChatModelId } from './runpod-chat-options';
44
export type { RunpodCompletionModelId } from './runpod-completion-options';
55
export type { RunpodImageModelId } from './runpod-image-options';
66
export type { OpenAICompatibleErrorData as RunpodErrorData } from '@ai-sdk/openai-compatible';
7+
export type { RunpodImageErrorData } from './runpod-error';

src/runpod-error.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { z } from 'zod';
2+
import { createJsonErrorResponseHandler } from '@ai-sdk/provider-utils';
3+
4+
// Runpod image API error schema (supports both error formats)
5+
export const runpodImageErrorSchema = z.object({
6+
error: z.string().optional(),
7+
message: z.string().optional(),
8+
});
9+
10+
export type RunpodImageErrorData = z.infer<typeof runpodImageErrorSchema>;
11+
12+
export const runpodImageFailedResponseHandler = createJsonErrorResponseHandler({
13+
errorSchema: runpodImageErrorSchema as any,
14+
errorToMessage: (data: RunpodImageErrorData) => {
15+
// Prefer message if available (more descriptive)
16+
if (data.message) {
17+
return data.message;
18+
}
19+
20+
// If error field exists, try to extract nested JSON message
21+
if (data.error) {
22+
// Runpod sometimes returns nested JSON in the error field like:
23+
// "Error submitting task: 400, {\"code\":400,\"message\":\"...\"}"
24+
// Try to extract the inner message for cleaner error messages
25+
// Find the last occurrence of { which likely starts the JSON object
26+
const lastBraceIndex = data.error.lastIndexOf('{');
27+
if (lastBraceIndex !== -1) {
28+
try {
29+
const jsonStr = data.error.substring(lastBraceIndex);
30+
const nestedError = JSON.parse(jsonStr);
31+
if (nestedError.message && typeof nestedError.message === 'string') {
32+
return nestedError.message;
33+
}
34+
} catch {
35+
// If parsing fails, fall back to the original error string
36+
}
37+
}
38+
return data.error;
39+
}
40+
41+
return 'Unknown Runpod error';
42+
},
43+
});
44+

src/runpod-image-model.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import { ImageModelV2, ImageModelV2CallWarning } from '@ai-sdk/provider';
22
import {
33
combineHeaders,
44
createJsonResponseHandler,
5-
createJsonErrorResponseHandler,
65
createBinaryResponseHandler,
76
FetchFunction,
87
postJsonToApi,
98
getFromApi,
109
} from '@ai-sdk/provider-utils';
1110
import { InvalidArgumentError } from '@ai-sdk/provider';
1211
import { z } from 'zod';
12+
import { runpodImageFailedResponseHandler } from './runpod-error';
1313

1414
/* eslint-disable @typescript-eslint/no-explicit-any */
1515
interface RunpodImageModelConfig {
@@ -136,10 +136,8 @@ export class RunpodImageModel implements ImageModelV2 {
136136
headers: combineHeaders(this.config.headers(), headers),
137137
body: {
138138
input: inputPayload,
139-
}, failedResponseHandler: createJsonErrorResponseHandler({
140-
errorSchema: runpodImageErrorSchema as any,
141-
errorToMessage: (data: any) => data.error ?? 'Unknown error',
142-
}),
139+
},
140+
failedResponseHandler: runpodImageFailedResponseHandler,
143141
successfulResponseHandler: createJsonResponseHandler(
144142
runpodImageResponseSchema as any
145143
),
@@ -226,10 +224,7 @@ export class RunpodImageModel implements ImageModelV2 {
226224
const { value: imageData } = await getFromApi({
227225
url: imageUrl,
228226
successfulResponseHandler: createBinaryResponseHandler(),
229-
failedResponseHandler: createJsonErrorResponseHandler({
230-
errorSchema: runpodImageErrorSchema as any,
231-
errorToMessage: (data: any) => data.error ?? 'Failed to download image',
232-
}),
227+
failedResponseHandler: runpodImageFailedResponseHandler,
233228
abortSignal,
234229
fetch: this.config.fetch,
235230
});
@@ -255,11 +250,7 @@ export class RunpodImageModel implements ImageModelV2 {
255250
successfulResponseHandler: createJsonResponseHandler(
256251
runpodImageStatusSchema as any
257252
),
258-
failedResponseHandler: createJsonErrorResponseHandler({
259-
errorSchema: runpodImageErrorSchema as any,
260-
errorToMessage: (data: any) =>
261-
data.error ?? 'Failed to check job status',
262-
}),
253+
failedResponseHandler: runpodImageFailedResponseHandler,
263254
abortSignal,
264255
fetch: this.config.fetch,
265256
});
@@ -377,9 +368,3 @@ const runpodImageStatusSchema = z.object({
377368
.optional(),
378369
error: z.string().optional(), // Error message if FAILED
379370
});
380-
381-
// Runpod image API error schema
382-
const runpodImageErrorSchema = z.object({
383-
error: z.string(),
384-
message: z.string().optional(),
385-
});

0 commit comments

Comments
 (0)