Reject model IDs unsafe for headers and clarify supported capabilities
All checks were successful
CI / test (22) (push) Successful in 22s
CI / test (24) (push) Successful in 21s

This commit is contained in:
Benjamin Hackl 2026-11-03 21:46:37 +01:00
commit 7754377b3e
3 changed files with 22 additions and 5 deletions

View file

@ -3,9 +3,9 @@
Use [uniGPT](https://unigpt.uni-graz.at), the University of Graz's AI Use [uniGPT](https://unigpt.uni-graz.at), the University of Graz's AI
assistant, inside the [pi](https://pi.dev) coding agent. assistant, inside the [pi](https://pi.dev) coding agent.
Once set up, every model your uniGPT account offers appears in pi's model Models exposed by your uniGPT API key appear in pi's model picker.
picker, including GPT-4o, GPT-4.1, and the GPT-5 and GPT-6 families. Tools, Streaming and tool calls are supported; recognized vision models also
streaming responses, and image input all work. support image input.
## What you need ## What you need

View file

@ -53,8 +53,11 @@ function isRecord(value: unknown): value is Record<string, unknown> {
} }
function isValidId(id: unknown): id is string { function isValidId(id: unknown): id is string {
// IDs become an HTTP header value, so reject padding and control characters. // Fetch header values must fit Latin-1; also reject padding and control characters.
return typeof id === "string" && id.length > 0 && id.trim() === id && !/[\u0000-\u001f\u007f]/.test(id); return typeof id === "string"
&& id.length > 0
&& id.trim() === id
&& !/[\u0000-\u001f\u007f\u0100-\uffff]/.test(id);
} }
export interface ParsedCatalog { export interface ParsedCatalog {

View file

@ -58,6 +58,20 @@ test("skips unusable entries but rejects malformed or entirely unusable catalogs
} }
}); });
test("catalog IDs must fit Fetch's Latin-1 header values", () => {
const valid = ["gpt-4o", "model-\u00ff"];
const invalid = ["model-\u0100", "model-\u{1f600}", "model-\ud800"];
const parsed = parseCatalog({ data: [...valid, ...invalid].map((id) => ({ id })) });
assert.deepEqual(parsed, { ids: valid, skipped: invalid.length });
for (const id of parsed.ids) {
const headers = new Headers(makeModel(id, BASE_URL).headers);
assert.equal(headers.get("deployment-id"), id);
}
for (const id of invalid) {
assert.throws(() => parseCatalog({ data: [{ id }] }), /without any valid model IDs/);
}
});
const known: Record<string, ModelMetadata> = { const known: Record<string, ModelMetadata> = {
"model-a": { name: "Model A", input: ["text", "image"], contextWindow: 272_000, maxTokens: 128_000 }, "model-a": { name: "Model A", input: ["text", "image"], contextWindow: 272_000, maxTokens: 128_000 },
}; };