feature: First push to git

This commit is contained in:
Keith Solomon
2026-05-16 14:02:49 -05:00
commit 265f69d95a
46 changed files with 11551 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
import { ExtractedLink } from '../parsing/types.js';
export interface LlmProvider {
categorize(link: ExtractedLink, categories: string[]): Promise<string | undefined>;
}
interface ProviderOptions {
apiKey?: string;
baseUrl?: string | null;
model: string;
}
async function postJson(url: string, apiKey: string | undefined, body: unknown): Promise<any> {
const response = await fetch(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
...(apiKey ? { authorization: `Bearer ${apiKey}` } : {})
},
body: JSON.stringify(body)
});
if (!response.ok) {
throw new Error(`LLM request failed: ${response.status}`);
}
return response.json();
}
function prompt(link: ExtractedLink, categories: string[]): string {
return `Choose the best newsletter category from ${categories.join(', ')} for: ${link.title} ${link.url}. Return only the category.`;
}
export class OpenAiCompatibleProvider implements LlmProvider {
public constructor(private readonly options: ProviderOptions) {}
public async categorize(link: ExtractedLink, categories: string[]): Promise<string | undefined> {
const data = await postJson(
`${this.options.baseUrl ?? 'https://api.openai.com/v1'}/chat/completions`,
this.options.apiKey,
{
model: this.options.model,
messages: [{ role: 'user', content: prompt(link, categories) }],
temperature: 0
}
);
return data.choices?.[0]?.message?.content?.trim();
}
}
export class OpenAiProvider extends OpenAiCompatibleProvider {}
export class LocalProvider extends OpenAiCompatibleProvider {}
export class AnthropicProvider implements LlmProvider {
public constructor(private readonly options: ProviderOptions) {}
public async categorize(link: ExtractedLink, categories: string[]): Promise<string | undefined> {
const response = await fetch(
`${this.options.baseUrl ?? 'https://api.anthropic.com'}/v1/messages`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
'x-api-key': this.options.apiKey ?? '',
'anthropic-version': '2023-06-01'
},
body: JSON.stringify({
model: this.options.model,
max_tokens: 64,
messages: [{ role: 'user', content: prompt(link, categories) }]
})
}
);
if (!response.ok) {
throw new Error(`Anthropic request failed: ${response.status}`);
}
const data = await response.json();
return data.content?.[0]?.text?.trim();
}
}