120 lines
3.9 KiB
TypeScript
120 lines
3.9 KiB
TypeScript
export interface ProviderExecutionInput {
|
|
apiKey: string;
|
|
model: string;
|
|
prompt: string;
|
|
instructions: string | null;
|
|
}
|
|
|
|
export interface ProviderExecutionResult {
|
|
text: string;
|
|
rawResponseJson: string;
|
|
}
|
|
|
|
export interface ProviderAdapter {
|
|
createResponse(input: ProviderExecutionInput): Promise<ProviderExecutionResult>;
|
|
}
|
|
|
|
type JsonRecord = Record<string, unknown>;
|
|
|
|
async function postJson(endpoint: string, headers: Record<string, string>, body: JsonRecord): Promise<JsonRecord> {
|
|
const response = await fetch(endpoint, {
|
|
method: "POST",
|
|
headers: {
|
|
"content-type": "application/json",
|
|
...headers,
|
|
},
|
|
body: JSON.stringify(body),
|
|
});
|
|
const data = await response.json() as JsonRecord;
|
|
if (!response.ok) {
|
|
const message = readProviderError(data) ?? `Provider request failed with HTTP ${response.status}.`;
|
|
throw new Error(message);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function readProviderError(data: JsonRecord): string | null {
|
|
const error = data.error;
|
|
if (typeof error === "string") return error;
|
|
if (isRecord(error) && typeof error.message === "string") return error.message;
|
|
return null;
|
|
}
|
|
|
|
function isRecord(value: unknown): value is JsonRecord {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
function collectText(value: unknown): string[] {
|
|
if (typeof value === "string") return [value];
|
|
if (Array.isArray(value)) return value.flatMap((item) => collectText(item));
|
|
if (!isRecord(value)) return [];
|
|
const direct = ["text", "output_text", "content"].flatMap((key) => collectText(value[key]));
|
|
return direct.length > 0 ? direct : Object.values(value).flatMap((item) => collectText(item));
|
|
}
|
|
|
|
function requireText(parts: string[], providerKey: string): string {
|
|
const text = parts.map((part) => part.trim()).filter(Boolean).join("\n");
|
|
if (!text) throw new Error(`${providerKey} returned no text content.`);
|
|
return text;
|
|
}
|
|
|
|
export const providerAdapters: Record<string, ProviderAdapter> = {
|
|
openai: {
|
|
async createResponse(input) {
|
|
const data = await postJson("https://api.openai.com/v1/responses", {
|
|
authorization: `Bearer ${input.apiKey}`,
|
|
}, {
|
|
model: input.model,
|
|
input: input.prompt,
|
|
...(input.instructions ? { instructions: input.instructions } : {}),
|
|
});
|
|
const text = requireText(collectText(data.output_text ?? data.output), "OpenAI");
|
|
return { text, rawResponseJson: JSON.stringify(data) };
|
|
},
|
|
},
|
|
anthropic: {
|
|
async createResponse(input) {
|
|
const data = await postJson("https://api.anthropic.com/v1/messages", {
|
|
"x-api-key": input.apiKey,
|
|
"anthropic-version": "2023-06-01",
|
|
}, {
|
|
model: input.model,
|
|
max_tokens: 4096,
|
|
...(input.instructions ? { system: input.instructions } : {}),
|
|
messages: [{ role: "user", content: input.prompt }],
|
|
});
|
|
const text = requireText(collectText(data.content), "Anthropic");
|
|
return { text, rawResponseJson: JSON.stringify(data) };
|
|
},
|
|
},
|
|
xai: {
|
|
async createResponse(input) {
|
|
return createOpenAiCompatibleResponse("https://api.x.ai/v1/chat/completions", "xAI", input);
|
|
},
|
|
},
|
|
groq: {
|
|
async createResponse(input) {
|
|
return createOpenAiCompatibleResponse("https://api.groq.com/openai/v1/chat/completions", "Groq", input);
|
|
},
|
|
},
|
|
};
|
|
|
|
async function createOpenAiCompatibleResponse(
|
|
endpoint: string,
|
|
providerLabel: string,
|
|
input: ProviderExecutionInput,
|
|
): Promise<ProviderExecutionResult> {
|
|
const messages = [
|
|
...(input.instructions ? [{ role: "system", content: input.instructions }] : []),
|
|
{ role: "user", content: input.prompt },
|
|
];
|
|
const data = await postJson(endpoint, {
|
|
authorization: `Bearer ${input.apiKey}`,
|
|
}, {
|
|
model: input.model,
|
|
messages,
|
|
});
|
|
const text = requireText(collectText(data.choices), providerLabel);
|
|
return { text, rawResponseJson: JSON.stringify(data) };
|
|
}
|