Skip to content

Commit 759454b

Browse files
authored
fix: handle ByteString conversion errors in OpenAI embedders (#8008)
1 parent beb0a59 commit 759454b

File tree

3 files changed

+54
-5
lines changed

3 files changed

+54
-5
lines changed

src/services/code-index/embedders/__tests__/openai-compatible.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,26 @@ vitest.mock("../../../../i18n", () => ({
3030
"embeddings:textExceedsTokenLimit": `Text at index ${params?.index} exceeds maximum token limit (${params?.itemTokens} > ${params?.maxTokens}). Skipping.`,
3131
"embeddings:rateLimitRetry": `Rate limit hit, retrying in ${params?.delayMs}ms (attempt ${params?.attempt}/${params?.maxRetries})`,
3232
"embeddings:unknownError": "Unknown error",
33+
"common:errors.api.invalidKeyInvalidChars":
34+
"API key contains invalid characters. Please check your API key for special characters.",
3335
}
3436
return translations[key] || key
3537
},
3638
}))
3739

40+
// Mock i18n/setup module used by the error handler
41+
vitest.mock("../../../../i18n/setup", () => ({
42+
default: {
43+
t: (key: string) => {
44+
const translations: Record<string, string> = {
45+
"common:errors.api.invalidKeyInvalidChars":
46+
"API key contains invalid characters. Please check your API key for special characters.",
47+
}
48+
return translations[key] || key
49+
},
50+
},
51+
}))
52+
3853
const MockedOpenAI = OpenAI as MockedClass<typeof OpenAI>
3954

4055
describe("OpenAICompatibleEmbedder", () => {
@@ -114,6 +129,22 @@ describe("OpenAICompatibleEmbedder", () => {
114129
"embeddings:validation.baseUrlRequired",
115130
)
116131
})
132+
133+
it("should handle API key with invalid characters (ByteString conversion error)", () => {
134+
// API key with special characters that cause ByteString conversion error
135+
const invalidApiKey = "sk-test•invalid" // Contains bullet character (U+2022)
136+
137+
// Mock the OpenAI constructor to throw ByteString error
138+
MockedOpenAI.mockImplementationOnce(() => {
139+
throw new Error(
140+
"Cannot convert argument to a ByteString because the character at index 7 has a value of 8226 which is greater than 255.",
141+
)
142+
})
143+
144+
expect(() => new OpenAICompatibleEmbedder(testBaseUrl, invalidApiKey, testModelId)).toThrow(
145+
"API key contains invalid characters",
146+
)
147+
})
117148
})
118149

119150
describe("embedderInfo", () => {

src/services/code-index/embedders/openai-compatible.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { withValidationErrorHandling, HttpError, formatEmbeddingError } from "..
1212
import { TelemetryEventName } from "@roo-code/types"
1313
import { TelemetryService } from "@roo-code/telemetry"
1414
import { Mutex } from "async-mutex"
15+
import { handleOpenAIError } from "../../../api/providers/utils/openai-error-handler"
1516

1617
interface EmbeddingItem {
1718
embedding: string | number[]
@@ -66,10 +67,18 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
6667

6768
this.baseUrl = baseUrl
6869
this.apiKey = apiKey
69-
this.embeddingsClient = new OpenAI({
70-
baseURL: baseUrl,
71-
apiKey: apiKey,
72-
})
70+
71+
// Wrap OpenAI client creation to handle invalid API key characters
72+
try {
73+
this.embeddingsClient = new OpenAI({
74+
baseURL: baseUrl,
75+
apiKey: apiKey,
76+
})
77+
} catch (error) {
78+
// Use the error handler to transform ByteString conversion errors
79+
throw handleOpenAIError(error, "OpenAI Compatible")
80+
}
81+
7382
this.defaultModelId = modelId || getDefaultModelId("openai-compatible")
7483
// Cache the URL type check for performance
7584
this.isFullUrl = this.isFullEndpointUrl(baseUrl)

src/services/code-index/embedders/openai.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { t } from "../../../i18n"
1313
import { withValidationErrorHandling, formatEmbeddingError, HttpError } from "../shared/validation-helpers"
1414
import { TelemetryEventName } from "@roo-code/types"
1515
import { TelemetryService } from "@roo-code/telemetry"
16+
import { handleOpenAIError } from "../../../api/providers/utils/openai-error-handler"
1617

1718
/**
1819
* OpenAI implementation of the embedder interface with batching and rate limiting
@@ -28,7 +29,15 @@ export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder {
2829
constructor(options: ApiHandlerOptions & { openAiEmbeddingModelId?: string }) {
2930
super(options)
3031
const apiKey = this.options.openAiNativeApiKey ?? "not-provided"
31-
this.embeddingsClient = new OpenAI({ apiKey })
32+
33+
// Wrap OpenAI client creation to handle invalid API key characters
34+
try {
35+
this.embeddingsClient = new OpenAI({ apiKey })
36+
} catch (error) {
37+
// Use the error handler to transform ByteString conversion errors
38+
throw handleOpenAIError(error, "OpenAI")
39+
}
40+
3241
this.defaultModelId = options.openAiEmbeddingModelId || "text-embedding-3-small"
3342
}
3443

0 commit comments

Comments
 (0)