feat: manual model IDs when /models is unavailable
Hosted OpenAI-compatible APIs often serve chat but not GET /models (Volcengine Ark Agent Plan returns 404), so connect failed. Accept a typed list of model IDs: merged into discovered models when /models works, used alone when it does not. The typed list is stored as customProvider.manualModels so reconnects and pin saves keep it.
This commit is contained in:
@@ -99,7 +99,7 @@ export interface EffectiveSettings {
|
||||
compaction: Required<CompactionSettings>;
|
||||
/** Custom endpoint, resolved concrete: baseUrl defaults "", models and
|
||||
* user pins default to empty. */
|
||||
customProvider: { baseUrl: string; models: CustomModel[]; overrides: Record<string, CustomModelOverride> };
|
||||
customProvider: { baseUrl: string; models: CustomModel[]; overrides: Record<string, CustomModelOverride>; manualModels: string[] };
|
||||
}
|
||||
|
||||
/** The conversation pair's chain (BYOK, owner 2026-09-22): the chat's own
|
||||
@@ -230,6 +230,7 @@ export function resolveSettings(record: UserSettings | null): EffectiveSettings
|
||||
baseUrl: record?.customProvider?.baseUrl ?? "",
|
||||
models: record?.customProvider?.models ?? [],
|
||||
overrides: record?.customProvider?.overrides ?? {},
|
||||
manualModels: record?.customProvider?.manualModels ?? [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -259,6 +259,13 @@ export interface CustomProviderSettings {
|
||||
/** User pins keyed by model id — pure user intent, never rewritten by
|
||||
* discovery, pruned to the endpoint's live ids on connect. */
|
||||
overrides?: Record<string, CustomModelOverride>;
|
||||
manualModels?: string[];
|
||||
}
|
||||
|
||||
export function parseModelIds(raw: unknown): string[] {
|
||||
const items = typeof raw === "string" ? raw.split(/[\s,]+/) : Array.isArray(raw) ? raw : [];
|
||||
const ids = items.filter((v): v is string => typeof v === "string").map((v) => v.trim()).filter(Boolean);
|
||||
return [...new Set(ids)];
|
||||
}
|
||||
|
||||
/** Canonical base-URL form (single source): trims, requires absolute http(s),
|
||||
@@ -644,6 +651,8 @@ function sanitizeCustomProvider(raw: Record<string, unknown>): CustomProviderSet
|
||||
}
|
||||
const overrides = sanitizeCustomModelOverrides(raw.overrides);
|
||||
if (Object.keys(overrides).length) out.overrides = overrides;
|
||||
const manualModels = parseModelIds(raw.manualModels);
|
||||
if (manualModels.length) out.manualModels = manualModels;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,12 @@ export async function discoverModels(baseUrl: string, apiKey?: string): Promise<
|
||||
}
|
||||
}
|
||||
|
||||
export function mergeManualModels(discovered: readonly CustomModel[], manualIds: readonly string[]): CustomModel[] {
|
||||
const byId = new Map(discovered.map((m) => [m.id, m]));
|
||||
for (const id of manualIds) if (!byId.has(id)) byId.set(id, { id });
|
||||
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
||||
}
|
||||
|
||||
/** Model rows from a list response ({data:[…]} | {models:[…]} | bare array),
|
||||
* or null if unrecognized. */
|
||||
function modelRows(body: unknown): unknown[] | null {
|
||||
|
||||
Reference in New Issue
Block a user