feat(ai): exposed catalog metadata in auth gateway model list

- Added context length, max tokens, input modalities, and tool support flags to GET /v1/models response in auth gateway server.
- Updated auth gateway model list tests to verify catalog metadata fields in output rows.
This commit is contained in:
can1357
2026-08-20 04:38:56 +02:00
parent 042f27f5e4
commit d7fbb503d8
3 changed files with 50 additions and 5 deletions
+1
View File
@@ -4,6 +4,7 @@
### Added
- Added model metadata fields (`context_length`, `max_output_tokens`, `input_modalities`, etc.) to auth gateway model listing responses
- Added `Tool.coerceArguments` (default `true`): setting it `false` opts a tool out of every LLM-quirk argument repair pass (JSON-string parsing, object→string stringification, unrecognized-key dropping, singleton array wrapping) so validation runs verbatim. Tools whose arguments are the deliverable payload — like the subagent `yield` tool — use it to keep lossy repairs from silently corrupting data their own validate-and-retry loop is designed to correct.
### Fixed
+29 -3
View File
@@ -730,19 +730,45 @@ async function handleCredentialsCheck(storage: AuthStorage, signal: AbortSignal)
return json(200, { generatedAt: Date.now(), credentials });
}
/**
* Row shape for `GET /v1/models`. Beyond the OpenAI-standard `id`/`object`/
* `owned_by`, rows advertise the catalog metadata OpenAI-compatible clients
* (omp's own proxy discovery, Zed's openai_compatible provider, ...) read to
* size and capability-gate discovered models: `context_length`,
* `max_output_tokens`, `input_modalities`, and `supports_tools` (only emitted
* when the catalog explicitly reports `false`; absent means usable).
*/
interface ModelListRow {
id: string;
object: "model";
owned_by: string;
api: Api;
display_name: string;
context_length?: number;
max_output_tokens?: number;
input_modalities: ("text" | "image")[];
supports_tools?: boolean;
}
function handleModelsList(opts: AuthGatewayBootOptions): Response {
const seen = new Set<string>();
const data: Array<{ id: string; object: "model"; owned_by: string; api: Api }> = [];
const data: ModelListRow[] = [];
for (const model of opts.listModels?.() ?? []) {
const id = `${model.provider}/${model.id}`;
if (seen.has(id)) continue;
seen.add(id);
data.push({
const row: ModelListRow = {
id,
object: "model",
owned_by: model.provider,
api: model.api,
});
display_name: model.name,
input_modalities: model.input,
};
if (model.contextWindow != null) row.context_length = model.contextWindow;
if (model.maxTokens != null) row.max_output_tokens = model.maxTokens;
if (model.supportsTools === false) row.supports_tools = false;
data.push(row);
}
return json(200, { object: "list", data });
}
@@ -26,8 +26,26 @@ test("model listing exposes one provider-qualified route per upstream model", as
expect(await response.json()).toEqual({
object: "list",
data: [
{ id: "anthropic/shared-model", object: "model", owned_by: "anthropic", api: "mock" },
{ id: "devin/shared-model", object: "model", owned_by: "devin", api: "mock" },
{
id: "anthropic/shared-model",
object: "model",
owned_by: "anthropic",
api: "mock",
display_name: "shared-model",
context_length: 200_000,
max_output_tokens: 32_768,
input_modalities: ["text"],
},
{
id: "devin/shared-model",
object: "model",
owned_by: "devin",
api: "mock",
display_name: "shared-model",
context_length: 200_000,
max_output_tokens: 32_768,
input_modalities: ["text"],
},
],
});
} finally {