Skip to content
Open
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
40 changes: 37 additions & 3 deletions packages/ai/cohere/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,51 @@ interface Config {
baseUrl?: string;
}

const DEFAULT_BASE = 'https://api.cohere.ai';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Wrong base URL path for Cohere's OpenAI-compatible endpoint

Cohere's OpenAI-compatible chat completions endpoint is at https://api.cohere.ai/compatibility/v1/chat/completions, not https://api.cohere.ai/v1/chat/completions. The current DEFAULT_BASE will produce requests to the wrong path, which will fail at runtime. The official Cohere compatibility docs confirm this path.


export default defineAi<Config>({
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<Config>({
Expand Down
40 changes: 37 additions & 3 deletions packages/ai/kimi/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,51 @@ interface Config {
baseUrl?: string;
}

const DEFAULT_BASE = 'https://api.moonshot.cn';

export default defineAi<Config>({
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<Config>({
Expand Down
40 changes: 37 additions & 3 deletions packages/ai/novita/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,51 @@ interface Config {
baseUrl?: string;
}

const DEFAULT_BASE = 'https://api.novita.ai';

export default defineAi<Config>({
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<Config>({
Expand Down
54 changes: 44 additions & 10 deletions packages/ai/parasail/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,59 @@ interface Config {
baseUrl?: string;
}

const DEFAULT_BASE = 'https://api.parasail.io';

export default defineAi<Config>({
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<Config>({
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',
],
Expand Down
40 changes: 37 additions & 3 deletions packages/ai/venice/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,51 @@ interface Config {
baseUrl?: string;
}

const DEFAULT_BASE = 'https://api.venice.ai';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Wrong base URL path for Venice API

The Venice API base URL is https://api.venice.ai/api/v1, not https://api.venice.ai/v1. With the current DEFAULT_BASE, every request will be sent to https://api.venice.ai/v1/chat/completions, which does not exist and will return a 404. The official Venice docs confirm the base URL as https://api.venice.ai/api/v1.


export default defineAi<Config>({
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<Config>({
Expand Down