Skip to content

Commit ff101bf

Browse files
authored
Harden DeepInfra AI adapter (#367)
1 parent ab9a8ed commit ff101bf

2 files changed

Lines changed: 19 additions & 10 deletions

File tree

packages/ai/deepinfra/src/index.test.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import adapter from './index.js';
44

55
smokeTest(adapter, { idPrefix: 'ai' });
66

7-
const ctx = (secrets: Record<string, string> = { DEEPINFRA_API_KEY: 'test-key' }, dryRun = false) => ({
7+
const ctx = (secrets: Record<string, string> = { DEEPINFRA_API_KEY: 'test-deepinfra-key' }, dryRun = false) => ({
88
secret: (key: string) => secrets[key],
99
log: () => {},
1010
dryRun,
@@ -19,7 +19,7 @@ describe('DeepInfra OpenAI-compatible generation', () => {
1919
const fetchMock = vi.fn();
2020
vi.stubGlobal('fetch', fetchMock);
2121

22-
const result = await adapter.generate(ctx({ DEEPINFRA_API_KEY: 'test-key' }, true), 'hello', {}, {});
22+
const result = await adapter.generate(ctx({ DEEPINFRA_API_KEY: 'test-deepinfra-key' }, true), 'hello', {}, {});
2323

2424
expect(result).toEqual({ text: '[dry-run]', model: 'deepseek-ai/DeepSeek-V3' });
2525
expect(fetchMock).not.toHaveBeenCalled();
@@ -42,14 +42,15 @@ describe('DeepInfra OpenAI-compatible generation', () => {
4242
maxTokens: 20,
4343
temperature: 0.2,
4444
extra: { reasoning_effort: 'low' },
45-
}, {});
45+
}, { baseUrl: 'https://deepinfra.test/v1/openai/' });
4646

4747
expect(fetchMock).toHaveBeenCalledOnce();
4848
const call = fetchMock.mock.calls[0];
4949
expect(call).toBeDefined();
5050
const [url, request] = call!;
51-
expect(url).toBe('https://api.deepinfra.com/v1/openai/chat/completions');
52-
expect(request.headers.authorization).toBe('Bearer test-key');
51+
expect(url).toBe('https://deepinfra.test/v1/openai/chat/completions');
52+
expect(request.headers.authorization).toBe(['Bearer', 'test-deepinfra-key'].join(' '));
53+
expect(request.headers['content-type']).toBe('application/json');
5354
expect(JSON.parse(request.body)).toEqual({
5455
model: 'deepseek-ai/DeepSeek-R1',
5556
messages: [
@@ -68,13 +69,15 @@ describe('DeepInfra OpenAI-compatible generation', () => {
6869
});
6970
});
7071

71-
it('includes status and response body excerpt on errors', async () => {
72+
it('redacts DeepInfra keys from provider error excerpts', async () => {
7273
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
7374
ok: false,
7475
status: 429,
75-
text: async () => 'rate limited'.repeat(30),
76+
text: async () => 'rate limited for test-deepinfra-key',
7677
}));
7778

78-
await expect(adapter.generate(ctx(), 'hello', {}, {})).rejects.toThrow(/DeepInfra 429: rate limited/);
79+
await expect(adapter.generate(ctx(), 'hello', {}, {})).rejects.toThrow(
80+
'DeepInfra 429: rate limited for [redacted]',
81+
);
7982
});
8083
});

packages/ai/deepinfra/src/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ interface Config {
55
}
66

77
const DEFAULT_BASE = 'https://api.deepinfra.com/v1/openai';
8+
const trimTrailingSlash = (value: string) => value.replace(/\/+$/, '');
89

910
export default defineAi<Config>({
1011
id: 'ai-deepinfra',
@@ -28,7 +29,8 @@ export default defineAi<Config>({
2829
if (opts.system) messages.push({ role: 'system', content: opts.system });
2930
messages.push({ role: 'user', content: prompt });
3031

31-
const res = await fetch(`${config.baseUrl ?? DEFAULT_BASE}/chat/completions`, {
32+
const baseUrl = trimTrailingSlash(config.baseUrl ?? DEFAULT_BASE);
33+
const res = await fetch(`${baseUrl}/chat/completions`, {
3234
method: 'POST',
3335
headers: {
3436
authorization: `Bearer ${apiKey}`,
@@ -42,7 +44,7 @@ export default defineAi<Config>({
4244
...opts.extra,
4345
}),
4446
});
45-
if (!res.ok) throw new Error(`DeepInfra ${res.status}: ${(await res.text()).slice(0, 200)}`);
47+
if (!res.ok) throw new Error(`DeepInfra ${res.status}: ${redact((await res.text()).slice(0, 200), apiKey)}`);
4648
const data = (await res.json()) as {
4749
choices: Array<{ message?: { content?: string } }>;
4850
model: string;
@@ -67,3 +69,7 @@ export default defineAi<Config>({
6769
],
6870
}),
6971
});
72+
73+
function redact(value: string, apiKey: string): string {
74+
return value.replaceAll(apiKey, '[redacted]');
75+
}

0 commit comments

Comments
 (0)