Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/supercode-cli/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "supercode-cli",
"version": "0.1.74",
"version": "0.1.75",
"description": "AI-powered coding agent CLI",
"main": "dist/main.js",
"bin": {
Expand Down
17 changes: 12 additions & 5 deletions apps/supercode-cli/server/src/cli/ai/concentrate-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ const HIGH_VALUE_MODELS = ["anthropic/claude-fable-5", "anthropic/claude-opus-4-
const OPUS_MODELS = ["anthropic/claude-opus-4-8", "anthropic/claude-opus-4-7"]
const OPUS_MODEL = "anthropic/claude-opus-4-8"

const CONCENTRATE_API_KEY = process.env.CONCENTRATEAI_API_KEY || ""
function getConcentrateApiKey(): string {
return process.env.CONCENTRATE_BYOK_PROD_KEY
|| process.env.CONCENTRATE_BYOK_DEV_KEY
|| process.env.CONCENTRATEAI_API_KEY
|| ""
}

const BASE_URL = "https://api.concentrate.ai/v1"

const MAX_RETRIES = 3
Expand Down Expand Up @@ -45,7 +51,7 @@ async function nonStreamingRequest(modelName: string, system: string, messages:
}
const res = await fetchWithRetry(`${BASE_URL}/chat/completions`, {
method: "POST",
headers: { Authorization: `Bearer ${CONCENTRATE_API_KEY}`, "Content-Type": "application/json" },
headers: { Authorization: `Bearer ${getConcentrateApiKey()}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
signal: AbortSignal.timeout(60_000),
})
Expand All @@ -68,8 +74,9 @@ export class ConcentrateService {
readonly modelName: string

constructor(modelName?: string) {
if (!CONCENTRATE_API_KEY) {
throw new Error("ConcentrateAI is not configured.\n\n Set CONCENTRATEAI_API_KEY in your environment:\n export CONCENTRATEAI_API_KEY=<your-key>\n\n Get a key at: https://concentrate.ai")
const apiKey = getConcentrateApiKey()
if (!apiKey) {
throw new Error("ConcentrateAI is not configured.\n\n Set CONCENTRATE_BYOK_PROD_KEY or CONCENTRATE_BYOK_DEV_KEY in your\n environment, or run /connect to provide your API key.\n\n Get a key at: https://concentrate.ai")
}

this.modelName = modelName || "deepseek-v4-flash"
Expand All @@ -78,7 +85,7 @@ export class ConcentrateService {
name: "concentrate",
baseURL: BASE_URL,
headers: {
Authorization: `Bearer ${CONCENTRATE_API_KEY}`,
Authorization: `Bearer ${apiKey}`,
},
fetch: fetchWithRetry as typeof fetch,
})
Expand Down
4 changes: 2 additions & 2 deletions apps/supercode-cli/server/src/cli/ai/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const providerMeta: Record<ModelProvider, { env: string; label: string; d
minimax: { env: "MINIMAX_API_KEY", label: "MiniMax", defaultModel: "MiniMax-M2" },
openrouter: { env: "OPENROUTER_API_KEY", label: "OpenRouter", defaultModel: "openai/gpt-oss-120b:free", link: "https://openrouter.ai/keys" },
nvidia: { env: "NVIDIA_API_KEY", label: "NVIDIA NIM", defaultModel: "minimaxai/minimax-m3" },
concentrateai: { env: "CONCENTRATEAI_API_KEY", label: "ConcentrateAI", defaultModel: "deepseek-v4-flash", link: "https://concentrate.ai" },
concentrateai: { env: "CONCENTRATE_BYOK_PROD_KEY / CONCENTRATE_BYOK_DEV_KEY", label: "ConcentrateAI", defaultModel: "deepseek-v4-flash", link: "https://concentrate.ai" },
mergedev: { env: "MERGE_DEV_API_KEY", label: "Merge Dev Gateway", defaultModel: "anthropic/claude-opus-4-8", link: "https://app.merge.dev" },
}

Expand All @@ -55,7 +55,7 @@ const providerConfigs: Record<ModelProvider, () => string> = {
minimax: () => minimaxConfig.apiKey,
openrouter: () => openRouterConfig.apiKey,
nvidia: () => nvidiaConfig.apiKey,
concentrateai: () => process.env.CONCENTRATEAI_API_KEY || "",
concentrateai: () => process.env.CONCENTRATE_BYOK_PROD_KEY || process.env.CONCENTRATE_BYOK_DEV_KEY || process.env.CONCENTRATEAI_API_KEY || "",
mergedev: () => mergedevConfig.apiKey,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ export async function connectProvider(): Promise<{ type: "connect"; provider?: M

const provider = PROVIDERS.find((p) => p.value === providerChoice)!

// Check if user already has a key saved for this provider
// Check if user already has a BYOK key for this session (for concentrateai, don't count the proxy key)
const config = await getCliConfig()
const envKeys = getProviderApiKeys()
const existingKey = config?.apiKeys?.[provider.value] || envKeys[provider.value]
const byokSessionKey = process.env.CONCENTRATE_BYOK_PROD_KEY || process.env.CONCENTRATE_BYOK_DEV_KEY
const existingKey = config?.apiKeys?.[provider.value]
|| (provider.value === "concentrateai" ? byokSessionKey : envKeys[provider.value])

if (existingKey) {
console.log(` ${chalk.hex(theme.green)("✓")} ${chalk.hex(theme.green)(`Already have a key for ${provider.label} — switching to direct mode (🔑)`)}`)
Expand Down
19 changes: 19 additions & 0 deletions apps/supercode-cli/server/src/lib/cli-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,31 @@ export async function applyStoredApiKeys(): Promise<void> {
}
}
}
// Clean up old persisted concentrateai key — now session-only via BYOK vars
if (config.apiKeys?.["concentrateai"]) {
const cleaned = { ...config.apiKeys }
delete cleaned.concentrateai
await saveCliConfig({ apiKeys: cleaned } as any)
}
} catch {
// no stored keys — fine
}
}

function getConcentrateByokVar(): string {
const serverUrl = process.env.SUPERCODE_SERVER_URL
if (serverUrl && (serverUrl.includes("localhost") || serverUrl.includes("127.0.0.1"))) {
return "CONCENTRATE_BYOK_DEV_KEY"
}
return "CONCENTRATE_BYOK_PROD_KEY"
}

export async function saveProviderApiKey(provider: ModelProvider, apiKey: string): Promise<void> {
if (provider === "concentrateai") {
const byokVar = getConcentrateByokVar()
process.env[byokVar] = apiKey
return
}
const config = await getCliConfig()
const existingConfig = config || {}
const currentKeys = (existingConfig as any)?.apiKeys || {}
Expand Down
Loading