mirror of
https://github.com/hanzoai/extension.git
synced 2026-10-31 21:37:51 +00:00
28 apps told the user to paste an `hk-` key — input placeholders, settings help text, READMEs, the error thrown when the field is empty — and five of them validated that paste against /^hk-/ before sending it as a bearer. Nothing mints that prefix any more: cloud accepts pk- and sk-, IAM mints sk-. So the guard admitted a credential that authenticates nothing, and the panel failed later at the gateway with nothing on screen to explain it. The three leak detectors keep hk- and gain sk-. Salesforce's source scan and the two Zendesk tests exist to catch a key committed to source or echoed back through a request; retired keys are still in circulation and leak exactly as badly, while a detector that only knew hk- would have gone blind to every key minted from here on. `sk-` used to be the example of the foreign key the guard screened out — an OpenAI key. It cannot mean that now, and shape alone cannot tell one vendor's sk- from another's, so those comments say what the check really proves and point at the /v1/models call that proves the rest. The three tests that pinned "sk- is rejected" now pin "hk- is rejected", which is the invariant that survived. Verified: 2029 tests pass across the 28 packages; typecheck clean on 25 of them (hubspot, jupyter and teams fail identically at HEAD on JSX and ESM type conflicts in files this does not touch); salesforce validate-source green. Co-authored-by: Hanzo Dev <[email protected]>
82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { buildMessages, extractContent, complete } from '../src/hanzo.js';
|
|
import type { AiClient, ChatCompletion } from '@hanzo/ai';
|
|
|
|
describe('buildMessages', () => {
|
|
it('produces a system+user pair in order', () => {
|
|
const msgs = buildMessages('SYS', 'USER');
|
|
expect(msgs).toEqual([
|
|
{ role: 'system', content: 'SYS' },
|
|
{ role: 'user', content: 'USER' },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('extractContent', () => {
|
|
it('reads a string content body', () => {
|
|
expect(extractContent({ choices: [{ message: { content: 'hi' } }] })).toBe('hi');
|
|
});
|
|
|
|
it('joins text content parts', () => {
|
|
const res = {
|
|
choices: [{ message: { content: [{ type: 'text', text: 'a' }, { type: 'text', text: 'b' }] } }],
|
|
};
|
|
expect(extractContent(res)).toBe('ab');
|
|
});
|
|
|
|
it('throws on empty content', () => {
|
|
expect(() => extractContent({ choices: [{ message: { content: '' } }] })).toThrow(/no content/);
|
|
expect(() => extractContent({ choices: [] })).toThrow(/no content/);
|
|
});
|
|
});
|
|
|
|
describe('complete (request shaping via mock client)', () => {
|
|
it('shapes model + messages, non-stream, and returns assistant text', async () => {
|
|
const seen: unknown[] = [];
|
|
const fake: AiClient = {
|
|
chat: {
|
|
completions: {
|
|
async create(params: unknown) {
|
|
seen.push(params);
|
|
return {
|
|
id: 'x',
|
|
object: 'chat.completion',
|
|
created: 0,
|
|
model: 'zen5',
|
|
choices: [{ index: 0, message: { role: 'assistant', content: 'REVIEW' }, finish_reason: 'stop' }],
|
|
} as ChatCompletion;
|
|
},
|
|
},
|
|
},
|
|
} as unknown as AiClient;
|
|
|
|
const out = await complete({
|
|
system: 'SYS',
|
|
user: 'USER',
|
|
model: 'zen5',
|
|
token: 'sk-abc',
|
|
baseURL: 'https://api.hanzo.ai',
|
|
client: fake,
|
|
});
|
|
|
|
expect(out).toBe('REVIEW');
|
|
expect(seen).toHaveLength(1);
|
|
const params = seen[0] as { model: string; messages: unknown[]; stream: boolean };
|
|
expect(params.model).toBe('zen5');
|
|
expect(params.stream).toBe(false);
|
|
expect(params.messages).toEqual([
|
|
{ role: 'system', content: 'SYS' },
|
|
{ role: 'user', content: 'USER' },
|
|
]);
|
|
});
|
|
|
|
it('propagates a gateway error', async () => {
|
|
const fake = {
|
|
chat: { completions: { async create() { throw new Error('HTTP 500'); } } },
|
|
} as unknown as AiClient;
|
|
await expect(
|
|
complete({ system: 's', user: 'u', model: 'm', token: '', baseURL: 'x', client: fake }),
|
|
).rejects.toThrow(/HTTP 500/);
|
|
});
|
|
});
|