diff --git a/src/api/providers/__tests__/deepseek.spec.ts b/src/api/providers/__tests__/deepseek.spec.ts index 50cabfa922..6353af5efd 100644 --- a/src/api/providers/__tests__/deepseek.spec.ts +++ b/src/api/providers/__tests__/deepseek.spec.ts @@ -96,6 +96,60 @@ describe("DeepSeekHandler", () => { expect(handler.getModel().id).toBe(mockOptions.apiModelId) }) + it("should throw error if API key contains non-ASCII characters", () => { + expect(() => { + new DeepSeekHandler({ + ...mockOptions, + deepSeekApiKey: "sk-test中文characters", + }) + }).toThrow("Invalid DeepSeek API key: contains non-ASCII character at position 8") + }) + + it("should throw error with helpful message for non-ASCII characters", () => { + expect(() => { + new DeepSeekHandler({ + ...mockOptions, + deepSeekApiKey: "sk-test-😀-key", + }) + }).toThrow(/API keys must contain only ASCII characters/) + }) + + it("should accept valid ASCII-only API keys", () => { + expect(() => { + new DeepSeekHandler({ + ...mockOptions, + deepSeekApiKey: "sk-test-1234567890-abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ", + }) + }).not.toThrow() + }) + + it("should accept API keys with standard symbols", () => { + expect(() => { + new DeepSeekHandler({ + ...mockOptions, + deepSeekApiKey: "sk-test_key-123!@#$%^&*()_+-=[]{}|;:,.<>?/", + }) + }).not.toThrow() + }) + + it("should not validate when API key is not provided", () => { + expect(() => { + new DeepSeekHandler({ + ...mockOptions, + deepSeekApiKey: undefined, + }) + }).not.toThrow() + }) + + it("should not validate when API key is 'not-provided'", () => { + expect(() => { + new DeepSeekHandler({ + ...mockOptions, + deepSeekApiKey: "not-provided", + }) + }).not.toThrow() + }) + it.skip("should throw error if API key is missing", () => { expect(() => { new DeepSeekHandler({ diff --git a/src/api/providers/deepseek.ts b/src/api/providers/deepseek.ts index de119de6db..4fb24abc43 100644 --- a/src/api/providers/deepseek.ts +++ b/src/api/providers/deepseek.ts @@ -9,9 +9,13 @@ import { OpenAiHandler } from "./openai" export class DeepSeekHandler extends OpenAiHandler { constructor(options: ApiHandlerOptions) { + // Validate API key before passing to parent constructor + const apiKey = options.deepSeekApiKey ?? "not-provided" + DeepSeekHandler.validateApiKey(apiKey) + super({ ...options, - openAiApiKey: options.deepSeekApiKey ?? "not-provided", + openAiApiKey: apiKey, openAiModelId: options.apiModelId ?? deepSeekDefaultModelId, openAiBaseUrl: options.deepSeekBaseUrl ?? "https://api.deepseek.com", openAiStreamingEnabled: true, @@ -19,6 +23,26 @@ export class DeepSeekHandler extends OpenAiHandler { }) } + /** + * Validates that the API key contains only ASCII characters. + * Non-ASCII characters in API keys cause ByteString conversion errors. + */ + private static validateApiKey(apiKey: string): void { + if (apiKey && apiKey !== "not-provided") { + // Check for non-ASCII characters + for (let i = 0; i < apiKey.length; i++) { + const charCode = apiKey.charCodeAt(i) + if (charCode > 255) { + throw new Error( + `Invalid DeepSeek API key: contains non-ASCII character at position ${i + 1}. ` + + `API keys must contain only ASCII characters (letters, numbers, and standard symbols). ` + + `Please check your API key for any accidental non-ASCII characters or spaces.`, + ) + } + } + } + } + override getModel() { const id = this.options.apiModelId ?? deepSeekDefaultModelId const info = deepSeekModels[id as keyof typeof deepSeekModels] || deepSeekModels[deepSeekDefaultModelId]