Skip to content
Closed
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
45 changes: 18 additions & 27 deletions packages/ai/xai/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,40 @@

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

function chatCompletionsUrl(baseUrl?: string): string {
return `${(baseUrl ?? DEFAULT_BASE).replace(/\/+$/, '')}/v1/chat/completions`;
}

function redact(value: string, apiKey: string): string {
return apiKey ? value.split(apiKey).join('[redacted]') : value;
}

export default defineAi<Config>({
id: 'ai-xai',
label: 'xAI',
defaultModel: 'grok-3',
models: ['grok-3', 'grok-3-mini', 'grok-2-latest'],
defaultModel: 'grok-beta',
models: ['grok-beta'],

async generate(ctx, prompt, opts, config) {
const apiKey = ctx.secret('XAI_API_KEY');
if (!apiKey) throw new Error('XAI_API_KEY not in vault');
const model = opts.model ?? 'grok-3';
ctx.log(`xai · model=${model} · ${prompt.length} chars in`);
const model = opts.model ?? 'grok-beta';
ctx.log(`${xAI_LOWER} · model=${model} · ${prompt.length} chars in`);

Check failure on line 19 in packages/ai/xai/src/index.ts

View workflow job for this annotation

GitHub Actions / test

packages/ai/xai/src/index.test.ts > xAI OpenAI-compatible generation > normalizes configured base URLs with trailing slashes

ReferenceError: xAI_LOWER is not defined ❯ Object.generate packages/ai/xai/src/index.ts:19:16 ❯ packages/ai/xai/src/index.test.ts:78:19

Check failure on line 19 in packages/ai/xai/src/index.ts

View workflow job for this annotation

GitHub Actions / test

packages/ai/xai/src/index.test.ts > xAI OpenAI-compatible generation > posts chat completions requests and maps usage tokens

ReferenceError: xAI_LOWER is not defined ❯ Object.generate packages/ai/xai/src/index.ts:19:16 ❯ packages/ai/xai/src/index.test.ts:39:34

Check failure on line 19 in packages/ai/xai/src/index.ts

View workflow job for this annotation

GitHub Actions / test

packages/ai/xai/src/index.test.ts > xAI OpenAI-compatible generation > short-circuits dry-run before network calls

ReferenceError: xAI_LOWER is not defined ❯ Object.generate packages/ai/xai/src/index.ts:19:16 ❯ packages/ai/xai/src/index.test.ts:22:34

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.

P0 Undefined variable xAI_LOWER causes ReferenceError

xAI_LOWER is not defined, imported, or declared anywhere in this file or the repository. Every call to generate() will throw a ReferenceError: xAI_LOWER is not defined at runtime (or fail TypeScript compilation). The same applies to xAI on line 42. The original code used string literals 'xai' and 'xAI' directly.

if (ctx.dryRun) return { text: '[dry-run]', model };
Comment on lines +18 to 20

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 Default model changed to grok-beta breaks the dry-run test

The dry-run test in index.test.ts (line 24) asserts { text: '[dry-run]', model: 'grok-3' }. With the default now changed to 'grok-beta', that assertion will fail. Additionally, grok-beta is the older xAI model name; the xAI API currently supports grok-3, grok-3-mini, and grok-2-latest — the model list was fully narrowed down to a legacy alias.


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(chatCompletionsUrl(config.baseUrl), {
const headers: Record<string, string> = {
authorization: `Bearer ${apiKey}`,
'content-type': 'application/json',
};

const res = await fetch(`${config.baseUrl ?? DEFAULT_BASE}/v1/chat/completions`, {

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 Trailing slash in baseUrl produces a double-slash URL

The removed chatCompletionsUrl helper called .replace(/\/+$/, '') to strip trailing slashes before appending /v1/chat/completions. The bare template literal ${config.baseUrl ?? DEFAULT_BASE}/v1/chat/completions no longer does that normalization. When a caller passes { baseUrl: 'https://proxy.example.com/' }, the resulting URL is https://proxy.example.com//v1/chat/completions. The test at index.test.ts line 81 explicitly asserts the single-slash form and will fail with this change.

method: 'POST',
headers: {
authorization: `Bearer ${apiKey}`,
'content-type': 'application/json',
},
headers,
body: JSON.stringify({
model,
messages,
...(opts.maxTokens !== undefined ? { max_tokens: opts.maxTokens } : {}),
...(opts.temperature !== undefined ? { temperature: opts.temperature } : {}),
... (opts.maxTokens !== undefined ? { max_tokens: opts.maxTokens } : {}),
... (opts.temperature !== undefined ? { temperature: opts.temperature } : {}),
...opts.extra,
}),
});
if (!res.ok) throw new Error(`xAI ${res.status}: ${redact(await res.text(), apiKey).slice(0, 200)}`);
if (!res.ok) throw new Error(`${xAI} ${res.status}: ${(await res.text()).slice(0, 200)}`);

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 security Removal of redact exposes API key in error messages

The redact helper was deleted, so API error responses are now sliced and thrown verbatim. The existing test (index.test.ts lines 84–103) explicitly verifies that the error message contains [redacted] and does not contain the API key or its first 10 characters — that test will now fail. An API key that spans the 200-character truncation boundary can leak partially in the thrown Error message, which may surface in logs, Sentry, or other observability tools.

const data = (await res.json()) as {
choices: Array<{ message?: { content?: string } }>;
model: string;
Expand All @@ -59,14 +53,11 @@
};
},

setup: tokenSetup<Config>({
setup: tokenSetup({
secretKey: 'XAI_API_KEY',
label: 'xAI',
vendorDocUrl: 'https://console.x.ai',
steps: [
'Sign in at https://console.x.ai and create an API key',
'Copy the key — usually shown once',
'Paste below; sh1pt encrypts it in the vault',
],
vendorDocUrl: 'https://console.x.ai/',
steps: ['Go to xAI Console', 'Create API Key', 'Paste below'],
fields: [],
}),
});
Loading