Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196import * as Effect from "effect/Effect";import { OperationFailure } from "./operation-result.ts";import { Secret } from "../configuration/secrets.ts";import { MODEL_CATALOG, MODEL_PROVIDERS, isModelProvider, isCredentialProvider, modelEfforts, type CredentialProvider, type ModelEffort, type ModelConfiguration, type ModelProvider,} from "../shared/model-providers.ts";export { MODEL_CATALOG, type ModelConfiguration,} from "../shared/model-providers.ts";
export interface ModelSettings { configuration: ModelConfiguration; credentials: Record<CredentialProvider, "configured" | "missing">; providers: Record<ModelProvider, boolean>; providerSetupUrl: string | null;}
export interface ConversationModelSettings extends ModelSettings { override: ModelConfiguration | null;}
export const DEFAULT_MODEL: ModelConfiguration = { provider: "workers-ai", model: MODEL_CATALOG["workers-ai"][0],};
export function parseModelConfiguration(value: unknown): ModelConfiguration { if (typeof value !== "object" || value === null || Array.isArray(value)) throw new OperationFailure("invalid_model"); const record = value as Record<string, unknown>; if ( Object.keys(record).some( (key) => !["provider", "model", "effort"].includes(key), ) || !Object.hasOwn(record, "provider") || !Object.hasOwn(record, "model") || !isModelProvider(record.provider) || typeof record.model !== "string" || !(MODEL_CATALOG[record.provider] as readonly string[]).includes( record.model, ) ) throw new OperationFailure("invalid_model"); if ( Object.hasOwn(record, "effort") && !modelEfforts(record as ModelConfiguration).includes( record.effort as ModelEffort, ) ) throw new OperationFailure("invalid_effort"); return { provider: record.provider, model: record.model, ...(record.effort === undefined ? {} : { effort: record.effort }), } as ModelConfiguration;}
export function parseProviderKey( provider: unknown, value: unknown,): Secret | null { if (!isCredentialProvider(provider)) throw new Error("Unsupported credential provider"); if (value === null) return null; if ( typeof value !== "string" || value.length < 20 || value.length > 512 || !/^[\x21-\x7e]+$/.test(value) ) throw new Error( `${MODEL_PROVIDERS[provider].name} API key must be 20–512 printable characters without spaces`, ); return new Secret(value);}
// HKDF separates stored credentials from cookie signing. Keep this byte format// stable so existing customer storage remains readable after an upgrade.export class CredentialFailure extends OperationFailure {}
function credentialCall<A>( read: () => PromiseLike<A>, code: "credential_unreadable" | "credential_save_failed",) { return Effect.tryPromise({ try: read, catch: () => new CredentialFailure(code), });}const unreadable = "credential_unreadable";
function encryptionKey(secret: Secret, installationId: string) { return Effect.gen(function* () { const material = yield* credentialCall( () => crypto.subtle.importKey( "raw", new TextEncoder().encode(secret.reveal()), "HKDF", false, ["deriveKey"], ), unreadable, ); return yield* credentialCall( () => crypto.subtle.deriveKey( { name: "HKDF", hash: "SHA-256", salt: new TextEncoder().encode(installationId), info: new TextEncoder().encode("flarebot/provider-credentials/v1"), }, material, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"], ), unreadable, ); });}
export function encryptProviderKey( value: Secret, secret: Secret, installationId: string,) { return Effect.gen(function* () { const iv = crypto.getRandomValues(new Uint8Array(12)); const key = yield* encryptionKey(secret, installationId); const ciphertext = yield* credentialCall( () => crypto.subtle.encrypt( { name: "AES-GCM", iv, additionalData: new TextEncoder().encode(installationId), }, key, new TextEncoder().encode(value.reveal()), ), "credential_save_failed", ); return JSON.stringify({ version: 1, iv: Array.from(iv), ciphertext: Array.from(new Uint8Array(ciphertext)), }); });}
export function decryptProviderKey( value: string, secret: Secret, installationId: string,) { return Effect.gen(function* () { const envelope = yield* Effect.try({ try: () => { const parsed = JSON.parse(value); if (parsed?.version !== 1) throw new CredentialFailure(unreadable); return { iv: new Uint8Array(parsed.iv), ciphertext: new Uint8Array(parsed.ciphertext), }; }, catch: () => new CredentialFailure(unreadable), }); const key = yield* encryptionKey(secret, installationId); const plaintext = yield* credentialCall( () => crypto.subtle.decrypt( { name: "AES-GCM", iv: envelope.iv, additionalData: new TextEncoder().encode(installationId), }, key, envelope.ciphertext, ), unreadable, ); return new Secret(new TextDecoder().decode(plaintext)); });}