/**
* Alibaba Model Studio "token plan" provider for Prime Agent.
*
* `/login alibaba` stores a pasted API key in ~/.prime/agent/auth.json.
* Fallbacks, in order: stored key, ALIBABA_API_KEY, ~/.alibaba.key.
* `/login alibaba` stores a pasted API key in ~/.prime/agent/auth.json, and that is the
* only place the key lives. ALIBABA_API_KEY is the sole fallback; when neither is present
* the provider reports an unresolved key instead of sending a placeholder, so a missing
* credential cannot be mistaken for an expired one.
* ALIBABA_BASE_URL overrides the endpoint (e.g. another region).
*
* The model list is baked into models.json and regenerated by regen.ts, which
* probes the live endpoint. Nothing here is fetched at startup except the
export const PROVIDER = "alibaba";
export const DEFAULT_BASE_URL = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1";
export const CATALOG = join(fileURLToPath(new URL(".", import.meta.url)), "models.json");
const FETCH_TIMEOUT_MS = 10000;
const KEY_FILE = join(homedir(), ".alibaba.key");
const AUTH_FILE = join(homedir(), ".prime", "agent", "auth.json");
/**
* What Prime Agent resolves when no credential is stored. `!`-prefixed values run through
* the shell (bash -c), and an empty result is an unresolved key rather than an empty
* string, which is what turns "no key" into a readable error instead of a 401.
*/
const API_KEY_FALLBACK = '!printf %s "$ALIBABA_API_KEY"';
const ENTITLEMENT_CACHE = join(homedir(), ".prime", "agent", "alibaba-entitlement.json");
export interface BakedModel {
id: string;
return undefined;
}
};
export const readKeyFile = (): string | undefined => readFile(KEY_FILE)?.trim() || undefined;
/** Key saved by `/login alibaba`, used only to list the account's entitled models. */
/** Key saved by `/login alibaba`; the only place this provider reads a key from. */
const readStoredKey = (): string | undefined => {
const raw = readFile(AUTH_FILE);
if (!raw) return undefined;
try {
return undefined;
}
};
export const resolveKey = (): string | undefined =>
readStoredKey() ?? process.env.ALIBABA_API_KEY ?? readKeyFile();
export const resolveKey = (): string | undefined => readStoredKey() ?? process.env.ALIBABA_API_KEY;
export const loadCatalog = (): BakedCatalog => JSON.parse(readFileSync(CATALOG, "utf-8")) as BakedCatalog;
/** Model ids the account can actually call, straight from the endpoint. */
}
export default async function (pi: ExtensionAPI) {
const baseUrl = baseUrlFromEnv();
const keyFileValue = readKeyFile();
const catalog = loadCatalog();
const entitled = await entitledIds(baseUrl, resolveKey());
const models = catalog.models
pi.registerProvider(PROVIDER, {
name: "Alibaba Model Studio (token plan)",
baseUrl,
apiKey: process.env.ALIBABA_API_KEY ? "ALIBABA_API_KEY" : (keyFileValue ?? "ALIBABA_API_KEY"),
apiKey: API_KEY_FALLBACK,
api: "openai-completions" as Api,
models,
});
}