diff --git a/packages/ai/cohere/src/index.ts b/packages/ai/cohere/src/index.ts index 6d8a13b0..94129382 100644 --- a/packages/ai/cohere/src/index.ts +++ b/packages/ai/cohere/src/index.ts @@ -4,17 +4,51 @@ interface Config { baseUrl?: string; } +const DEFAULT_BASE = 'https://api.cohere.ai'; + export default defineAi({ id: 'ai-cohere', label: 'Cohere', defaultModel: 'command-r-plus', models: ['command-r-plus'], - async generate(ctx, prompt, _opts, _config) { + async generate(ctx, prompt, opts, config) { const apiKey = ctx.secret('COHERE_API_KEY'); if (!apiKey) throw new Error('COHERE_API_KEY not in vault — run `sh1pt promote ai setup`'); - ctx.log(`[stub] ai-cohere · ${prompt.length} chars in — integration pending`); - return { text: '[stub — ai-cohere integration not yet implemented]', model: 'command-r-plus' }; + const model = opts.model ?? 'command-r-plus'; + ctx.log(`cohere · model=${model} · ${prompt.length} chars in`); + if (ctx.dryRun) return { text: '[dry-run]', model }; + + const messages: Array<{ role: string; content: string }> = []; + if (opts.system) messages.push({ role: 'system', content: opts.system }); + messages.push({ role: 'user', content: prompt }); + + const res = await fetch(`${config.baseUrl ?? DEFAULT_BASE}/v1/chat/completions`, { + method: 'POST', + headers: { + authorization: `Bearer ${apiKey}`, + 'content-type': 'application/json', + }, + body: JSON.stringify({ + model, + messages, + ...(opts.maxTokens !== undefined ? { max_tokens: opts.maxTokens } : {}), + ...(opts.temperature !== undefined ? { temperature: opts.temperature } : {}), + ...opts.extra, + }), + }); + if (!res.ok) throw new Error(`Cohere ${res.status}: ${(await res.text()).slice(0, 200)}`); + const data = (await res.json()) as { + choices: Array<{ message?: { content?: string } }>; + model: string; + usage?: { prompt_tokens?: number; completion_tokens?: number }; + }; + return { + text: data.choices[0]?.message?.content ?? '', + model: data.model, + inputTokens: data.usage?.prompt_tokens, + outputTokens: data.usage?.completion_tokens, + }; }, setup: tokenSetup({ diff --git a/packages/ai/kimi/src/index.ts b/packages/ai/kimi/src/index.ts index 6bbf7fd3..fb6d8fae 100644 --- a/packages/ai/kimi/src/index.ts +++ b/packages/ai/kimi/src/index.ts @@ -4,17 +4,51 @@ interface Config { baseUrl?: string; } +const DEFAULT_BASE = 'https://api.moonshot.cn'; + export default defineAi({ id: 'ai-kimi', label: 'Kimi (Moonshot)', defaultModel: 'kimi-k2-0905-preview', models: ['kimi-k2-0905-preview'], - async generate(ctx, prompt, _opts, _config) { + async generate(ctx, prompt, opts, config) { const apiKey = ctx.secret('MOONSHOT_API_KEY'); if (!apiKey) throw new Error('MOONSHOT_API_KEY not in vault — run `sh1pt promote ai setup`'); - ctx.log(`[stub] ai-kimi · ${prompt.length} chars in — integration pending`); - return { text: '[stub — ai-kimi integration not yet implemented]', model: 'kimi-k2-0905-preview' }; + const model = opts.model ?? 'kimi-k2-0905-preview'; + ctx.log(`kimi · model=${model} · ${prompt.length} chars in`); + if (ctx.dryRun) return { text: '[dry-run]', model }; + + const messages: Array<{ role: string; content: string }> = []; + if (opts.system) messages.push({ role: 'system', content: opts.system }); + messages.push({ role: 'user', content: prompt }); + + const res = await fetch(`${config.baseUrl ?? DEFAULT_BASE}/v1/chat/completions`, { + method: 'POST', + headers: { + authorization: `Bearer ${apiKey}`, + 'content-type': 'application/json', + }, + body: JSON.stringify({ + model, + messages, + ...(opts.maxTokens !== undefined ? { max_tokens: opts.maxTokens } : {}), + ...(opts.temperature !== undefined ? { temperature: opts.temperature } : {}), + ...opts.extra, + }), + }); + if (!res.ok) throw new Error(`Moonshot ${res.status}: ${(await res.text()).slice(0, 200)}`); + const data = (await res.json()) as { + choices: Array<{ message?: { content?: string } }>; + model: string; + usage?: { prompt_tokens?: number; completion_tokens?: number }; + }; + return { + text: data.choices[0]?.message?.content ?? '', + model: data.model, + inputTokens: data.usage?.prompt_tokens, + outputTokens: data.usage?.completion_tokens, + }; }, setup: tokenSetup({ diff --git a/packages/ai/novita/src/index.ts b/packages/ai/novita/src/index.ts index 70f3b05a..182b59e4 100644 --- a/packages/ai/novita/src/index.ts +++ b/packages/ai/novita/src/index.ts @@ -4,17 +4,51 @@ interface Config { baseUrl?: string; } +const DEFAULT_BASE = 'https://api.novita.ai'; + export default defineAi({ id: 'ai-novita', label: 'NovitaAI', defaultModel: 'meta-llama/llama-3.3-70b-instruct', models: ['meta-llama/llama-3.3-70b-instruct'], - async generate(ctx, prompt, _opts, _config) { + async generate(ctx, prompt, opts, config) { const apiKey = ctx.secret('NOVITA_API_KEY'); if (!apiKey) throw new Error('NOVITA_API_KEY not in vault — run `sh1pt promote ai setup`'); - ctx.log(`[stub] ai-novita · ${prompt.length} chars in — integration pending`); - return { text: '[stub — ai-novita integration not yet implemented]', model: 'meta-llama/llama-3.3-70b-instruct' }; + const model = opts.model ?? 'meta-llama/llama-3.3-70b-instruct'; + ctx.log(`novita · model=${model} · ${prompt.length} chars in`); + if (ctx.dryRun) return { text: '[dry-run]', model }; + + const messages: Array<{ role: string; content: string }> = []; + if (opts.system) messages.push({ role: 'system', content: opts.system }); + messages.push({ role: 'user', content: prompt }); + + const res = await fetch(`${config.baseUrl ?? DEFAULT_BASE}/v1/chat/completions`, { + method: 'POST', + headers: { + authorization: `Bearer ${apiKey}`, + 'content-type': 'application/json', + }, + body: JSON.stringify({ + model, + messages, + ...(opts.maxTokens !== undefined ? { max_tokens: opts.maxTokens } : {}), + ...(opts.temperature !== undefined ? { temperature: opts.temperature } : {}), + ...opts.extra, + }), + }); + if (!res.ok) throw new Error(`NovitaAI ${res.status}: ${(await res.text()).slice(0, 200)}`); + const data = (await res.json()) as { + choices: Array<{ message?: { content?: string } }>; + model: string; + usage?: { prompt_tokens?: number; completion_tokens?: number }; + }; + return { + text: data.choices[0]?.message?.content ?? '', + model: data.model, + inputTokens: data.usage?.prompt_tokens, + outputTokens: data.usage?.completion_tokens, + }; }, setup: tokenSetup({ diff --git a/packages/ai/parasail/src/index.ts b/packages/ai/parasail/src/index.ts index 88d4be22..0a502fb4 100644 --- a/packages/ai/parasail/src/index.ts +++ b/packages/ai/parasail/src/index.ts @@ -4,25 +4,59 @@ interface Config { baseUrl?: string; } +const DEFAULT_BASE = 'https://api.parasail.io'; + export default defineAi({ id: 'ai-parasail', label: 'Parasail', - defaultModel: 'PARASAIL_API_KEY', - models: ['PARASAIL_API_KEY'], + defaultModel: 'gpt-4o', + models: ['gpt-4o', 'gpt-4o-mini'], + + async generate(ctx, prompt, opts, config) { + const apiKey = ctx.secret('PARASAIL_API_KEY'); + if (!apiKey) throw new Error('PARASAIL_API_KEY not in vault — run `sh1pt promote ai setup`'); + const model = opts.model ?? 'gpt-4o'; + ctx.log(`parasail · model=${model} · ${prompt.length} chars in`); + if (ctx.dryRun) return { text: '[dry-run]', model }; + + const messages: Array<{ role: string; content: string }> = []; + if (opts.system) messages.push({ role: 'system', content: opts.system }); + messages.push({ role: 'user', content: prompt }); - async generate(ctx, prompt, _opts, _config) { - const apiKey = ctx.secret('https://parasail.io'); - if (!apiKey) throw new Error('https://parasail.io not in vault — run `sh1pt promote ai setup`'); - ctx.log(`[stub] ai-parasail · ${prompt.length} chars in — integration pending`); - return { text: '[stub — ai-parasail integration not yet implemented]', model: 'PARASAIL_API_KEY' }; + const res = await fetch(`${config.baseUrl ?? DEFAULT_BASE}/v1/chat/completions`, { + method: 'POST', + headers: { + authorization: `Bearer ${apiKey}`, + 'content-type': 'application/json', + }, + body: JSON.stringify({ + model, + messages, + ...(opts.maxTokens !== undefined ? { max_tokens: opts.maxTokens } : {}), + ...(opts.temperature !== undefined ? { temperature: opts.temperature } : {}), + ...opts.extra, + }), + }); + if (!res.ok) throw new Error(`Parasail ${res.status}: ${(await res.text()).slice(0, 200)}`); + const data = (await res.json()) as { + choices: Array<{ message?: { content?: string } }>; + model: string; + usage?: { prompt_tokens?: number; completion_tokens?: number }; + }; + return { + text: data.choices[0]?.message?.content ?? '', + model: data.model, + inputTokens: data.usage?.prompt_tokens, + outputTokens: data.usage?.completion_tokens, + }; }, setup: tokenSetup({ - secretKey: 'https://parasail.io', + secretKey: 'PARASAIL_API_KEY', label: 'Parasail', - vendorDocUrl: '', + vendorDocUrl: 'https://parasail.io', steps: [ - 'Sign in at and create an API key', + 'Sign in at https://parasail.io and create an API key', 'Copy the key — usually shown once', 'Paste below; sh1pt encrypts it in the vault', ], diff --git a/packages/ai/venice/src/index.ts b/packages/ai/venice/src/index.ts index a0aa321f..532ff37d 100644 --- a/packages/ai/venice/src/index.ts +++ b/packages/ai/venice/src/index.ts @@ -4,17 +4,51 @@ interface Config { baseUrl?: string; } +const DEFAULT_BASE = 'https://api.venice.ai'; + export default defineAi({ id: 'ai-venice', label: 'Venice AI', defaultModel: 'llama-3.3-70b', models: ['llama-3.3-70b'], - async generate(ctx, prompt, _opts, _config) { + async generate(ctx, prompt, opts, config) { const apiKey = ctx.secret('VENICE_API_KEY'); if (!apiKey) throw new Error('VENICE_API_KEY not in vault — run `sh1pt promote ai setup`'); - ctx.log(`[stub] ai-venice · ${prompt.length} chars in — integration pending`); - return { text: '[stub — ai-venice integration not yet implemented]', model: 'llama-3.3-70b' }; + const model = opts.model ?? 'llama-3.3-70b'; + ctx.log(`venice · model=${model} · ${prompt.length} chars in`); + if (ctx.dryRun) return { text: '[dry-run]', model }; + + const messages: Array<{ role: string; content: string }> = []; + if (opts.system) messages.push({ role: 'system', content: opts.system }); + messages.push({ role: 'user', content: prompt }); + + const res = await fetch(`${config.baseUrl ?? DEFAULT_BASE}/v1/chat/completions`, { + method: 'POST', + headers: { + authorization: `Bearer ${apiKey}`, + 'content-type': 'application/json', + }, + body: JSON.stringify({ + model, + messages, + ...(opts.maxTokens !== undefined ? { max_tokens: opts.maxTokens } : {}), + ...(opts.temperature !== undefined ? { temperature: opts.temperature } : {}), + ...opts.extra, + }), + }); + if (!res.ok) throw new Error(`Venice ${res.status}: ${(await res.text()).slice(0, 200)}`); + const data = (await res.json()) as { + choices: Array<{ message?: { content?: string } }>; + model: string; + usage?: { prompt_tokens?: number; completion_tokens?: number }; + }; + return { + text: data.choices[0]?.message?.content ?? '', + model: data.model, + inputTokens: data.usage?.prompt_tokens, + outputTokens: data.usage?.completion_tokens, + }; }, setup: tokenSetup({