feat(pi): fetch and refresh upstream model lists, searchable model picker
- POST /pi/upstreams/{id}/models/refresh calls the provider's model list
endpoint (OpenAI-shaped /models, Anthropic /v1/models, Google
/v1beta/models, OpenRouter /api/v1/models; Radius via pi-ai refresh),
normalizes {id, displayName, contextWindow, supportsVision,
supportsTools, pricing} and stores it in pi_upstream.models (0044)
- GET .../models returns {source, fetchedAt, models[]}: remote merged
with the static catalog (remote first); failures (401/network/
unsupported) fall back to static with a one-line error
- POST /pi/models/probe for trying credentials before creating
- wizard and edit drawer: fetch button, searchable dropdown with
vision/tool tags, manual id, source line; default upstream needs a model
- tests: mock /models, 401, network failure; smoke step; runbook
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
e5abfa6217
commit
0765264a47
16 files changed
+1178
-52
No files matched your search
@@ -5,6 +5,7 @@ import {
|
||||
Api,
|
||||
apiResponse,
|
||||
type EndpointDef,
|
||||
type PiModelsProbeRequest,
|
||||
type PiOAuthCompleteRequest,
|
||||
type PiOAuthStartRequest,
|
||||
type PiUpstreamCreate,
|
||||
@@ -13,8 +14,8 @@ import {
|
||||
} from "@umt/contracts";
|
||||
import type { z } from "zod";
|
||||
import { requireAdminSession, requireScope } from "../../http/auth.ts";
|
||||
import { ApiException } from "../../http/errors.ts";
|
||||
import type { ModuleApp, ServerContext } from "../types.ts";
|
||||
import { probeModels, readUpstreamModels, refreshUpstreamModels } from "./models.ts";
|
||||
import type { OAuthSessions } from "./oauth.ts";
|
||||
import { requireRow } from "./oauth.ts";
|
||||
import { refreshUpstream, resolveExpiredDecision } from "./refresh.ts";
|
||||
@@ -125,16 +126,21 @@ export function registerPiRoutes(
|
||||
return { ...out, mode, latencyMs: Date.now() - started, upstream: await toView(ctx, (await getRow(ctx, row.id))!) };
|
||||
});
|
||||
|
||||
route(app, Api.pi.models, async ({ params }) => {
|
||||
route(app, Api.pi.models, async ({ params }) => await readUpstreamModels(ctx, await requireRow(ctx, params.id)));
|
||||
|
||||
route(app, Api.pi.modelsRefresh, async ({ params, actor }) => {
|
||||
const row = await requireRow(ctx, params.id);
|
||||
try {
|
||||
return await clientFor(ctx, row, deps.client).listModels();
|
||||
} catch (err) {
|
||||
if (err instanceof UpstreamError) throw new ApiException("validation_failed", err.message);
|
||||
throw err;
|
||||
}
|
||||
const r = await refreshUpstreamModels(ctx, row);
|
||||
await auditPi(ctx, actor, "pi_upstream.models_refresh", row.id, {
|
||||
source: r.source,
|
||||
count: r.models.length,
|
||||
errorKind: r.errorKind,
|
||||
});
|
||||
return r;
|
||||
});
|
||||
|
||||
route(app, Api.pi.modelsProbe, async ({ body }) => await probeModels(body as PiModelsProbeRequest));
|
||||
|
||||
route(app, Api.pi.oauthStart, async ({ params, body, actor }) => {
|
||||
const row = await requireRow(ctx, params.id);
|
||||
const r = await deps.oauth().start(row, body as PiOAuthStartRequest);
|
||||
|
||||
Reference in new issue
Block a user