Skip to content
Merged
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
14 changes: 14 additions & 0 deletions packages/ai/phala/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@ describe('Phala OpenAI-compatible generation', () => {
expect(url).toBe('https://proxy.example.com/v1/chat/completions');
});

it.each([
['not-a-url', 'valid URL'],
['ftp://proxy.example.com', 'http or https'],
['https://user:pass@proxy.example.com', 'must not include credentials'],
['https://proxy.example.com?region=us', 'must not include query strings or fragments'],
['https://proxy.example.com#v1', 'must not include query strings or fragments'],
])('rejects unsafe configured base URL %s', async (baseUrl, message) => {
const fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);

await expect(adapter.generate(ctx(), 'hello', {}, { baseUrl })).rejects.toThrow(message);
expect(fetchMock).not.toHaveBeenCalled();
});

it('includes status and redacted response body excerpt on errors', async () => {
const apiKey = 'test-key-crossing-truncation-boundary';
const prefix = 'x'.repeat(190);
Expand Down
25 changes: 24 additions & 1 deletion packages/ai/phala/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,31 @@ interface Config {

const DEFAULT_BASE = 'https://inference.phala.com';

function cleanBaseUrl(baseUrl?: string): string {
if (!baseUrl) return DEFAULT_BASE;

let parsed: URL;
try {
parsed = new URL(baseUrl);
} catch {
throw new Error('Phala baseUrl must be a valid URL');
}

if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
throw new Error('Phala baseUrl must use http or https');
}
if (parsed.username || parsed.password) {
throw new Error('Phala baseUrl must not include credentials');
}
if (parsed.search || parsed.hash) {
throw new Error('Phala baseUrl must not include query strings or fragments');
}

return parsed.toString().replace(/\/+$/, '');
}

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

function redact(value: string, apiKey: string): string {
Expand Down
Loading