initial
Release / release (push) Failing after 9s

This commit is contained in:
2026-09-09 09:56:21 +02:00
commit 04e80929f0
37 changed files with 3463 additions and 0 deletions
+200
View File
@@ -0,0 +1,200 @@
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { homedir } from "node:os";
import { isAbsolute, join } from "node:path";
import type { Config } from "@opencode-ai/sdk/v2";
import { z } from "zod";
const endpoint = z.object({ npm: z.string().optional(), api: z.string().optional() });
const catalogProvider = endpoint.extend({
id: z.string(),
env: z.array(z.string()),
models: z.record(
z.string(),
z.object({
id: z.string(),
status: z.enum(["active", "alpha", "beta", "deprecated"]).optional(),
provider: endpoint.optional(),
}),
),
});
const record = z.record(z.string(), z.unknown());
const auth = z.discriminatedUnion("type", [
z.object({
type: z.literal("api"),
key: z.string(),
metadata: z.record(z.string(), z.string()).optional(),
}),
z.object({
type: z.literal("oauth"),
refresh: z.string(),
access: z.string(),
expires: z.number().int().nonnegative(),
accountId: z.string().optional(),
enterpriseUrl: z.string().optional(),
}),
z.object({ type: z.literal("wellknown"), key: z.string(), token: z.string() }),
]);
const priorityOptions = z.object({ serviceTier: z.enum(["priority", "fast"]) });
const missingFile = z.object({ code: z.literal("ENOENT") });
type Model = {
id: string;
npm: string;
baseURL: string | undefined;
serviceTier: z.infer<typeof priorityOptions>["serviceTier"] | undefined;
status: string | undefined;
};
async function local(path: string) {
try {
return await readFile(path, "utf8");
} catch (error) {
if (missingFile.safeParse(error).success) return undefined;
throw error;
}
}
async function catalog() {
const source = process.env.OPENCODE_MODELS_URL || "https://models.opencode.ai";
const filename =
source === "https://models.opencode.ai"
? "models.json"
: `models-${createHash("sha1").update(source).digest("hex")}.json`;
const cache = process.env.XDG_CACHE_HOME;
const path =
process.env.OPENCODE_MODELS_PATH ??
join(cache && isAbsolute(cache) ? cache : join(homedir(), ".cache"), "opencode", filename);
const text = await local(path);
if (text !== undefined) return record.parse(JSON.parse(text));
const disabled = process.env.OPENCODE_DISABLE_MODELS_FETCH?.toLowerCase();
if (disabled === "true" || disabled === "1") return {};
const response = await fetch(`${source}/api.json`, { signal: AbortSignal.timeout(10_000) });
if (!response.ok) throw new Error(`Model catalog request failed (${response.status})`);
return record.parse(await response.json());
}
async function authenticated(providerID: string) {
const data = process.env.XDG_DATA_HOME;
const text =
process.env.OPENCODE_AUTH_CONTENT ||
(await local(
join(
data && isAbsolute(data) ? data : join(homedir(), ".local/share"),
"opencode",
"auth.json",
),
));
if (text === undefined) return false;
// Neither JSON nor schema diagnostics may include credential contents.
try {
const entries = record.parse(JSON.parse(text));
if (!Object.hasOwn(entries, providerID)) return false;
const credential = auth.parse(entries[providerID]);
return credential.type === "api" || credential.type === "oauth";
} catch {
throw new Error("Invalid OpenCode authentication data");
}
}
// OpenAI lists Luna Fast/Priority support; account limits can still downgrade requests.
// https://developers.openai.com/api/docs/guides/fast-mode
function official(npm: string | undefined, baseURL: string | undefined) {
if (npm !== "@ai-sdk/openai") return false;
if (!baseURL) return true;
if (!URL.canParse(baseURL)) return false;
const url = new URL(baseURL);
return url.origin === "https://api.openai.com" && /^\/v1\/?$/.test(url.pathname);
}
/**
* Bootstrap snapshot only: provider.list would wait for this config hook itself.
* ponytail: catalog/config/env/stored auth only; bundled snapshots, experimental
* catalog modes, runtime discovery, and later plugin/auth-loader rewrites are not
* observable here. A post-bootstrap provider hook is needed to include those.
* Credentials establish connectivity, not model entitlement or OAuth validity.
*/
export async function discover(config: Config, providerID: string) {
if (config.disabled_providers?.includes(providerID)) return undefined;
if (config.enabled_providers && !config.enabled_providers.includes(providerID))
return undefined;
const configured =
config.provider && Object.hasOwn(config.provider, providerID)
? config.provider[providerID]
: undefined;
const entries = await catalog();
const provider = Object.hasOwn(entries, providerID)
? catalogProvider.parse(entries[providerID])
: undefined;
if (!provider && !configured) return undefined;
if (
!configured &&
!provider?.env.some((key) => Boolean(process.env[key])) &&
!(await authenticated(providerID))
) {
return undefined;
}
const effective = new Map<string, Model>();
for (const [key, model] of Object.entries(provider?.models ?? {})) {
const npm = model.provider?.npm ?? provider?.npm ?? "@ai-sdk/openai-compatible";
const baseURL = configured?.options?.baseURL || (model.provider?.api ?? provider?.api);
effective.set(key, {
id: model.id,
npm,
baseURL,
status: model.status,
serviceTier: priorityOptions.safeParse(configured?.options).data?.serviceTier,
});
}
for (const [key, model] of Object.entries(configured?.models ?? {})) {
const existing = effective.get(model.id ?? key);
const npm =
model.provider?.npm ??
configured?.npm ??
existing?.npm ??
provider?.npm ??
"@ai-sdk/openai-compatible";
const baseURL =
configured?.options?.baseURL ||
(model.provider?.api ?? configured?.api ?? existing?.baseURL ?? provider?.api);
let serviceTier = priorityOptions.safeParse({
serviceTier: model.options?.serviceTier ?? configured?.options?.serviceTier,
}).data?.serviceTier;
if (!serviceTier) {
for (const variant of Object.values(model.variants ?? {})) {
if (variant.disabled) continue;
serviceTier = priorityOptions.safeParse(variant).data?.serviceTier;
if (serviceTier) break;
}
}
effective.set(key, {
id: model.id ?? existing?.id ?? key,
npm,
baseURL,
status: model.status ?? existing?.status,
serviceTier,
});
}
const experimental = process.env.OPENCODE_ENABLE_EXPERIMENTAL_MODELS?.toLowerCase();
for (const [key, model] of effective) {
if (
model.status === "deprecated" ||
(model.status === "alpha" && experimental !== "true" && experimental !== "1") ||
configured?.blacklist?.includes(key) ||
(configured?.whitelist && !configured.whitelist.includes(key)) ||
(key === "gpt-5-chat-latest" &&
["openai", "github-copilot", "openrouter"].includes(providerID)) ||
(providerID === "openrouter" && key === "openai/gpt-5-chat")
) {
effective.delete(key);
continue;
}
model.serviceTier =
model.id === "gpt-5.6-luna"
? (model.serviceTier ??
(official(model.npm, model.baseURL) ? "priority" : undefined))
: undefined;
}
return { models: effective };
}