Add DeepSeek API provider login

This commit is contained in:
Chris Watson
2026-05-18 22:12:57 -06:00
parent 3b072a10b9
commit 21c3999e9d
8 changed files with 69 additions and 0 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Added
- Added DeepSeek to the built-in API-key login provider catalog so `omp login deepseek` stores a reusable `DEEPSEEK_API_KEY` credential for the bundled DeepSeek models.
## [15.2.4] - 2026-05-22
### Fixed
+6
View File
@@ -29,6 +29,7 @@ import { kimiUsageProvider } from "./usage/kimi";
import { codexRankingStrategy, openaiCodexUsageProvider } from "./usage/openai-codex";
import { zaiUsageProvider } from "./usage/zai";
import { getOAuthApiKey, getOAuthProvider, refreshOAuthToken } from "./utils/oauth";
import { loginDeepSeek } from "./utils/oauth/deepseek";
import { loginOpenAICodexDevice } from "./utils/oauth/openai-codex";
import type { OAuthController, OAuthCredentials, OAuthProvider, OAuthProviderId } from "./utils/oauth/types";
@@ -1378,6 +1379,11 @@ export class AuthStorage {
await saveApiKeyCredential(apiKey);
return;
}
case "deepseek": {
const apiKey = await loginDeepSeek(ctrl);
await saveApiKeyCredential(apiKey);
return;
}
case "fireworks": {
const { loginFireworks } = await import("./utils/oauth/fireworks");
const apiKey = await loginFireworks(ctrl);
+1
View File
@@ -109,6 +109,7 @@ Providers:
kagi Kagi
tavily Tavily
zai Z.AI (GLM Coding Plan)
deepseek DeepSeek
nanogpt NanoGPT
minimax-code MiniMax Coding Plan (International)
minimax-code-cn MiniMax Coding Plan (China)
+16
View File
@@ -0,0 +1,16 @@
/** DeepSeek login flow (API key paste against https://api.deepseek.com). */
import { createApiKeyLogin } from "./api-key-login";
export const loginDeepSeek = createApiKeyLogin({
providerLabel: "DeepSeek",
authUrl: "https://platform.deepseek.com/api_keys",
instructions: "Create or copy your API key from the DeepSeek dashboard",
promptMessage: "Paste your DeepSeek API key",
placeholder: "sk-...",
validation: {
kind: "chat-completions",
provider: "deepseek",
baseUrl: "https://api.deepseek.com/v1",
model: "deepseek-v4-pro",
},
});
+5
View File
@@ -55,6 +55,11 @@ const builtInOAuthProviders: OAuthProviderInfo[] = [
name: "Cerebras",
available: true,
},
{
id: "deepseek",
name: "DeepSeek",
available: true,
},
{
id: "fireworks",
name: "Fireworks",
+1
View File
@@ -14,6 +14,7 @@ export type OAuthProvider =
| "cerebras"
| "cloudflare-ai-gateway"
| "cursor"
| "deepseek"
| "fireworks"
| "firepass"
| "github-copilot"
@@ -5,6 +5,7 @@ import * as os from "node:os";
import * as path from "node:path";
import { AuthStorage, SqliteAuthCredentialStore } from "../src/auth-storage";
import * as deepseekModule from "../src/utils/oauth/deepseek";
import * as kagiModule from "../src/utils/oauth/kagi";
import * as ollamaCloudModule from "../src/utils/oauth/ollama-cloud";
@@ -25,6 +26,7 @@ describe("AuthStorage api-key login replacement", () => {
let dbPath = "";
let store: SqliteAuthCredentialStore | null = null;
let authStorage: AuthStorage | null = null;
let loginDeepSeekSpy: Mock<typeof deepseekModule.loginDeepSeek>;
let loginKagiSpy: Mock<typeof kagiModule.loginKagi>;
let loginOllamaCloudSpy: Mock<typeof ollamaCloudModule.loginOllamaCloud>;
@@ -33,6 +35,7 @@ describe("AuthStorage api-key login replacement", () => {
dbPath = path.join(tempDir, "agent.db");
store = await SqliteAuthCredentialStore.open(dbPath);
authStorage = new AuthStorage(store);
loginDeepSeekSpy = vi.spyOn(deepseekModule, "loginDeepSeek");
loginKagiSpy = vi.spyOn(kagiModule, "loginKagi");
loginOllamaCloudSpy = vi.spyOn(ollamaCloudModule, "loginOllamaCloud");
});
@@ -100,4 +103,30 @@ describe("AuthStorage api-key login replacement", () => {
expect(store.getApiKey("ollama-cloud")).toBe("same-ollama-cloud-key");
expect(await authStorage.getApiKey("ollama-cloud", "session-ollama-cloud-relogin")).toBe("same-ollama-cloud-key");
});
it("stores DeepSeek login credentials as a reusable api-key credential", async () => {
if (!store || !authStorage || !dbPath) throw new Error("test setup failed");
loginDeepSeekSpy.mockResolvedValueOnce("same-deepseek-key").mockResolvedValueOnce("same-deepseek-key");
const controller = {
onAuth: () => {},
onPrompt: async () => "",
};
await authStorage.login("deepseek", controller);
await authStorage.login("deepseek", controller);
expect(countCredentialRows(dbPath, "deepseek")).toBe(1);
const credentials = store.listAuthCredentials("deepseek");
expect(credentials).toHaveLength(1);
const [stored] = credentials;
expect(stored?.credential.type).toBe("api_key");
if (!stored || stored.credential.type !== "api_key") {
throw new Error("expected stored api-key credential");
}
expect(stored.credential.key).toBe("same-deepseek-key");
expect(store.getApiKey("deepseek")).toBe("same-deepseek-key");
expect(await authStorage.getApiKey("deepseek", "session-deepseek-relogin")).toBe("same-deepseek-key");
});
});
+7
View File
@@ -3,6 +3,7 @@ import { DEFAULT_MODEL_PER_PROVIDER, PROVIDER_DESCRIPTORS } from "../src/provide
import { MODELS_DEV_PROVIDER_DESCRIPTORS } from "../src/provider-models/openai-compat";
import { getEnvApiKey } from "../src/stream";
import type { OpenAICompat } from "../src/types";
import { getOAuthProviders } from "../src/utils/oauth";
describe("deepseek built-in provider (issue #830)", () => {
test("registers built-in runtime descriptor with DEEPSEEK_API_KEY env discovery", () => {
@@ -13,6 +14,12 @@ describe("deepseek built-in provider (issue #830)", () => {
expect(DEFAULT_MODEL_PER_PROVIDER.deepseek).toBe("deepseek-v4-pro");
});
test("registers DeepSeek as an API-key login provider", () => {
const provider = getOAuthProviders().find(item => item.id === "deepseek");
expect(provider?.name).toBe("DeepSeek");
expect(provider?.available).toBe(true);
});
test("resolves DEEPSEEK_API_KEY via env", () => {
const previous = Bun.env.DEEPSEEK_API_KEY;
Bun.env.DEEPSEEK_API_KEY = "deepseek-test-key";