Browse Source
Replace 6 duplicated header-building if/else chains (across executeWorkflow and regenerateNode) with buildGenerateHeaders() and buildLlmHeaders() utility functions. Add 13 new tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>handoff-20260429-1057
3 changed files with 174 additions and 150 deletions
@ -0,0 +1,96 @@ |
|||||
|
import { describe, it, expect } from "vitest"; |
||||
|
import { buildGenerateHeaders, buildLlmHeaders } from "../buildApiHeaders"; |
||||
|
import type { ProviderSettings } from "@/types"; |
||||
|
|
||||
|
function makeSettings(overrides: Partial<Record<string, { apiKey: string | null }>> = {}): ProviderSettings { |
||||
|
const defaults: Record<string, { id: string; name: string; enabled: boolean; apiKey: string | null }> = { |
||||
|
gemini: { id: "gemini", name: "Gemini", enabled: true, apiKey: null }, |
||||
|
replicate: { id: "replicate", name: "Replicate", enabled: true, apiKey: null }, |
||||
|
fal: { id: "fal", name: "fal.ai", enabled: true, apiKey: null }, |
||||
|
kie: { id: "kie", name: "Kie.ai", enabled: true, apiKey: null }, |
||||
|
wavespeed: { id: "wavespeed", name: "WaveSpeed", enabled: true, apiKey: null }, |
||||
|
openai: { id: "openai", name: "OpenAI", enabled: true, apiKey: null }, |
||||
|
}; |
||||
|
for (const [key, val] of Object.entries(overrides)) { |
||||
|
if (defaults[key]) { |
||||
|
defaults[key].apiKey = val.apiKey; |
||||
|
} |
||||
|
} |
||||
|
return { providers: defaults } as unknown as ProviderSettings; |
||||
|
} |
||||
|
|
||||
|
describe("buildGenerateHeaders", () => { |
||||
|
it("should always include Content-Type", () => { |
||||
|
const headers = buildGenerateHeaders("gemini", makeSettings()); |
||||
|
expect(headers["Content-Type"]).toBe("application/json"); |
||||
|
}); |
||||
|
|
||||
|
it("should add Gemini API key header", () => { |
||||
|
const settings = makeSettings({ gemini: { apiKey: "gem-key-123" } }); |
||||
|
const headers = buildGenerateHeaders("gemini", settings); |
||||
|
expect(headers["X-Gemini-API-Key"]).toBe("gem-key-123"); |
||||
|
}); |
||||
|
|
||||
|
it("should add Replicate API key header", () => { |
||||
|
const settings = makeSettings({ replicate: { apiKey: "rep-key" } }); |
||||
|
const headers = buildGenerateHeaders("replicate", settings); |
||||
|
expect(headers["X-Replicate-API-Key"]).toBe("rep-key"); |
||||
|
}); |
||||
|
|
||||
|
it("should add fal.ai API key header", () => { |
||||
|
const settings = makeSettings({ fal: { apiKey: "fal-key" } }); |
||||
|
const headers = buildGenerateHeaders("fal", settings); |
||||
|
expect(headers["X-Fal-API-Key"]).toBe("fal-key"); |
||||
|
}); |
||||
|
|
||||
|
it("should add Kie API key header", () => { |
||||
|
const settings = makeSettings({ kie: { apiKey: "kie-key" } }); |
||||
|
const headers = buildGenerateHeaders("kie", settings); |
||||
|
expect(headers["X-Kie-Key"]).toBe("kie-key"); |
||||
|
}); |
||||
|
|
||||
|
it("should add WaveSpeed API key header", () => { |
||||
|
const settings = makeSettings({ wavespeed: { apiKey: "ws-key" } }); |
||||
|
const headers = buildGenerateHeaders("wavespeed", settings); |
||||
|
expect(headers["X-WaveSpeed-Key"]).toBe("ws-key"); |
||||
|
}); |
||||
|
|
||||
|
it("should not add header when API key is null", () => { |
||||
|
const headers = buildGenerateHeaders("gemini", makeSettings()); |
||||
|
expect(headers["X-Gemini-API-Key"]).toBeUndefined(); |
||||
|
}); |
||||
|
|
||||
|
it("should handle unknown provider gracefully", () => { |
||||
|
const headers = buildGenerateHeaders("unknown", makeSettings()); |
||||
|
expect(headers).toEqual({ "Content-Type": "application/json" }); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe("buildLlmHeaders", () => { |
||||
|
it("should always include Content-Type", () => { |
||||
|
const headers = buildLlmHeaders("google", makeSettings()); |
||||
|
expect(headers["Content-Type"]).toBe("application/json"); |
||||
|
}); |
||||
|
|
||||
|
it("should add Gemini API key for google provider", () => { |
||||
|
const settings = makeSettings({ gemini: { apiKey: "gem-key" } }); |
||||
|
const headers = buildLlmHeaders("google", settings); |
||||
|
expect(headers["X-Gemini-API-Key"]).toBe("gem-key"); |
||||
|
}); |
||||
|
|
||||
|
it("should add OpenAI API key for openai provider", () => { |
||||
|
const settings = makeSettings({ openai: { apiKey: "oai-key" } }); |
||||
|
const headers = buildLlmHeaders("openai", settings); |
||||
|
expect(headers["X-OpenAI-API-Key"]).toBe("oai-key"); |
||||
|
}); |
||||
|
|
||||
|
it("should not add header when API key is null", () => { |
||||
|
const headers = buildLlmHeaders("google", makeSettings()); |
||||
|
expect(headers["X-Gemini-API-Key"]).toBeUndefined(); |
||||
|
}); |
||||
|
|
||||
|
it("should handle unknown LLM provider gracefully", () => { |
||||
|
const headers = buildLlmHeaders("unknown", makeSettings()); |
||||
|
expect(headers).toEqual({ "Content-Type": "application/json" }); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,71 @@ |
|||||
|
/** |
||||
|
* API Header Builder Utilities |
||||
|
* |
||||
|
* Centralizes the duplicated header-building logic for API calls |
||||
|
* across executeWorkflow and regenerateNode. |
||||
|
*/ |
||||
|
|
||||
|
import { ProviderType, ProviderSettings, LLMProvider } from "@/types"; |
||||
|
|
||||
|
/** |
||||
|
* Header name mapping for each provider |
||||
|
*/ |
||||
|
const PROVIDER_HEADER_MAP: Record<ProviderType, string> = { |
||||
|
gemini: "X-Gemini-API-Key", |
||||
|
replicate: "X-Replicate-API-Key", |
||||
|
fal: "X-Fal-API-Key", |
||||
|
kie: "X-Kie-Key", |
||||
|
wavespeed: "X-WaveSpeed-Key", |
||||
|
openai: "X-OpenAI-API-Key", |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Build headers for image/video generation API calls. |
||||
|
* Adds the appropriate API key header based on the provider. |
||||
|
*/ |
||||
|
export function buildGenerateHeaders( |
||||
|
provider: ProviderType | string, |
||||
|
providerSettings: ProviderSettings |
||||
|
): Record<string, string> { |
||||
|
const headers: Record<string, string> = { |
||||
|
"Content-Type": "application/json", |
||||
|
}; |
||||
|
|
||||
|
const providerKey = provider as ProviderType; |
||||
|
const headerName = PROVIDER_HEADER_MAP[providerKey]; |
||||
|
if (headerName) { |
||||
|
const config = providerSettings.providers[providerKey]; |
||||
|
if (config?.apiKey) { |
||||
|
headers[headerName] = config.apiKey; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return headers; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Build headers for LLM API calls. |
||||
|
* Maps LLM provider names ("google", "openai") to their API key headers. |
||||
|
*/ |
||||
|
export function buildLlmHeaders( |
||||
|
llmProvider: LLMProvider | string, |
||||
|
providerSettings: ProviderSettings |
||||
|
): Record<string, string> { |
||||
|
const headers: Record<string, string> = { |
||||
|
"Content-Type": "application/json", |
||||
|
}; |
||||
|
|
||||
|
if (llmProvider === "google") { |
||||
|
const geminiConfig = providerSettings.providers.gemini; |
||||
|
if (geminiConfig?.apiKey) { |
||||
|
headers["X-Gemini-API-Key"] = geminiConfig.apiKey; |
||||
|
} |
||||
|
} else if (llmProvider === "openai") { |
||||
|
const openaiConfig = providerSettings.providers.openai; |
||||
|
if (openaiConfig?.apiKey) { |
||||
|
headers["X-OpenAI-API-Key"] = openaiConfig.apiKey; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return headers; |
||||
|
} |
||||
Loading…
Reference in new issue