Add DeepSeek API provider login
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
});
|
||||
@@ -55,6 +55,11 @@ const builtInOAuthProviders: OAuthProviderInfo[] = [
|
||||
name: "Cerebras",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: "deepseek",
|
||||
name: "DeepSeek",
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: "fireworks",
|
||||
name: "Fireworks",
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user