agents
Version:
A home for your AI agents
259 lines (258 loc) • 9.09 kB
JavaScript
//#region src/models/core/settings.ts
/** The gateway id Cloudflare auto-creates on first use. */
const DEFAULT_GATEWAY_ID = "default";
/** Key that per-call options live under in a framework's per-call options bag. */
const PROVIDER_OPTIONS_KEY = "cloudflare";
/** The id legs of a fallback list; model objects are their module's business. */
function idLegs(fallback) {
return (fallback ?? []).filter((leg) => typeof leg === "string");
}
/** Strips `undefined` values so a later layer never blanks an earlier one. */
function defined(value) {
const result = {};
for (const [key, entry] of Object.entries(value)) if (entry !== void 0) result[key] = entry;
return result;
}
function normalizeGateway(gateway) {
if (gateway === void 0) return {};
return typeof gateway === "string" ? { id: gateway } : defined(gateway);
}
/**
* Folds one layer's flat gateway sugar into its `gateway` object. The flat key
* wins over the nested one, and an explicit `undefined` on either never blanks
* a lower layer.
*/
function layerGateway(options) {
if (options === void 0) return {};
return {
...normalizeGateway(options.gateway),
...defined({
byokAlias: options.byokAlias,
cacheKey: options.cacheKey,
cacheTtl: options.cacheTtl,
collectLog: options.collectLog,
eventId: options.eventId,
id: options.id,
metadata: options.metadata,
requestTimeoutMs: options.requestTimeoutMs,
retries: options.retries,
skipCache: options.skipCache,
zdr: options.zdr
})
};
}
/** The flat gateway sugar {@link layerGateway} folds into `gateway`. */
const FLAT_GATEWAY_KEYS = [
"byokAlias",
"cacheKey",
"cacheTtl",
"collectLog",
"eventId",
"gateway",
"id",
"metadata",
"requestTimeoutMs",
"retries",
"skipCache",
"zdr"
];
/**
* One layer's gateway options as a single object, both spellings folded in.
* Exported so a module that collapses two option layers into one bag (a
* fallback leg inheriting a chain's gateway) can merge them field by field
* rather than replacing one object with the other.
*
* @experimental This surface is experimental and may change.
*/
function gatewayLayerOf(options) {
return layerGateway(options);
}
/** An option bag without the gateway keys, whichever spelling they used. */
function withoutGatewayKeys(options) {
const rest = { ...options };
for (const key of FLAT_GATEWAY_KEYS) delete rest[key];
return defined(rest);
}
/**
* Collapses two option layers into one bag, `override` winning field by field.
*
* The gateway keys of both are folded into a single `gateway` object, so
* neither layer's flat sugar can out-rank the other's nested spelling when the
* bag is resolved later, and `metadata` merges rather than replaces — the same
* rules {@link resolveOptions} applies across its three layers.
*
* @experimental This surface is experimental and may change.
*/
function mergeModelOptions(base, override) {
if (base === void 0) return override;
if (override === void 0) return base;
const layers = [layerGateway(base), layerGateway(override)];
const gateway = Object.assign({}, ...layers);
const metadata = Object.assign({}, ...layers.map((layer) => layer.metadata));
const retries = mergeRetries(layers);
const headers = {
...base.headers,
...override.headers
};
return {
...withoutGatewayKeys(base),
...withoutGatewayKeys(override),
...Object.keys(headers).length > 0 ? { headers } : {},
gateway: {
...gateway,
...Object.keys(metadata).length > 0 ? { metadata } : {},
...retries === void 0 ? {} : { retries }
}
};
}
/**
* Merges the retry policies field by field, so a provider-wide `backoff` still
* applies when a call only sets `maxAttempts`. `undefined` on any layer means
* "not set" and never blanks a lower one.
*/
function mergeRetries(layers) {
const merged = layers.reduce((result, layer) => ({
...result,
...layer.retries === void 0 ? {} : defined(layer.retries)
}), {});
return Object.keys(merged).length > 0 ? merged : void 0;
}
/**
* Merges the option layers. Precedence is per-call
* (`providerOptions.cloudflare`) > per-model (`ai(id, options)`) >
* provider (`createAI({ ... })`).
*
* The provider layer takes the whole settings object as well as a bare
* `gateway` value, so `createAI({ binding, id: "prod", cacheTtl: 60 })` reads
* the same as `createAI({ binding, gateway: { id: "prod", cacheTtl: 60 } })`.
*
* @experimental This surface is experimental and may change.
*/
function resolveOptions(provider, model, call) {
const layers = typeof provider === "string" ? [
normalizeGateway(provider),
layerGateway(model),
layerGateway(call)
] : [
layerGateway(provider),
layerGateway(model),
layerGateway(call)
];
const gateway = Object.assign({}, ...layers);
const metadata = Object.assign({}, ...layers.map((layer) => layer.metadata));
const retries = mergeRetries(layers);
return {
chatTemplateKwargs: call?.chatTemplateKwargs ?? model?.chatTemplateKwargs,
fallback: idLegs(call?.fallback ?? model?.fallback),
gateway: {
...gateway,
id: gateway.id ?? "default",
...Object.keys(metadata).length > 0 ? { metadata } : {},
...retries === void 0 ? {} : { retries }
},
headers: {
...model?.headers,
...call?.headers
},
reasoningEffort: call?.reasoningEffort !== void 0 ? call.reasoningEffort : model?.reasoningEffort,
sessionAffinity: call?.sessionAffinity ?? model?.sessionAffinity
};
}
function asRecord(value) {
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
}
function asStringRecord(value) {
const record = asRecord(value);
if (record === void 0) return void 0;
const result = {};
for (const [key, entry] of Object.entries(record)) if (typeof entry === "string") result[key] = entry;
return result;
}
function asString(value) {
return typeof value === "string" ? value : void 0;
}
function asNumber(value) {
return typeof value === "number" ? value : void 0;
}
function asBoolean(value) {
return typeof value === "boolean" ? value : void 0;
}
function asReasoningEffort(value) {
if (value === null) return null;
if (value === "low" || value === "medium" || value === "high") return value;
}
/**
* Metadata values the gateway accepts are scalars only; a nested object or
* array would be rejected by the binding, so drop those rather than forward
* them.
*/
function asMetadata(value) {
const record = asRecord(value);
if (record === void 0) return void 0;
const result = {};
for (const [key, entry] of Object.entries(record)) if (entry === null || typeof entry === "string" || typeof entry === "number" || typeof entry === "boolean") result[key] = entry;
return result;
}
const BACKOFFS = [
"constant",
"linear",
"exponential"
];
/** Reads the retry policy field by field; the binding validates it strictly. */
function asRetries(value) {
const record = asRecord(value);
if (record === void 0) return void 0;
const maxAttempts = asNumber(record.maxAttempts);
const backoff = asString(record.backoff);
const retries = defined({
backoff: BACKOFFS.find((entry) => entry === backoff),
maxAttempts: maxAttempts !== void 0 && Number.isInteger(maxAttempts) && maxAttempts >= 1 && maxAttempts <= 5 ? maxAttempts : void 0,
retryDelayMs: asNumber(record.retryDelayMs)
});
return Object.keys(retries).length > 0 ? retries : void 0;
}
/** Reads the shared gateway keys out of a loosely-typed record. */
function parseGatewayFields(raw) {
return defined({
byokAlias: asString(raw.byokAlias),
cacheKey: asString(raw.cacheKey),
cacheTtl: asNumber(raw.cacheTtl),
collectLog: asBoolean(raw.collectLog),
eventId: asString(raw.eventId),
id: asString(raw.id),
metadata: asMetadata(raw.metadata),
requestTimeoutMs: asNumber(raw.requestTimeoutMs),
retries: asRetries(raw.retries),
skipCache: asBoolean(raw.skipCache),
zdr: asBoolean(raw.zdr)
});
}
/**
* Reads per-call options out of a loosely-typed options bag (the value a
* framework keeps under {@link PROVIDER_OPTIONS_KEY}).
*
* The bag is plain JSON, so every field is read defensively: anything of an
* unexpected type is dropped rather than thrown, because a malformed
* observability hint should never fail a generation.
*
* @experimental This surface is experimental and may change.
*/
function parseModelOptions(value) {
const raw = asRecord(value);
if (raw === void 0) return void 0;
const nested = asRecord(raw.gateway);
const gateway = typeof raw.gateway === "string" ? raw.gateway : nested !== void 0 ? parseGatewayFields(nested) : void 0;
const fallback = Array.isArray(raw.fallback) ? raw.fallback.filter((entry) => typeof entry === "string") : void 0;
return defined({
...parseGatewayFields(raw),
chatTemplateKwargs: asRecord(raw.chatTemplateKwargs),
fallback,
gateway,
headers: asStringRecord(raw.headers),
reasoningEffort: asReasoningEffort(raw.reasoningEffort),
sessionAffinity: asString(raw.sessionAffinity)
});
}
//#endregion
export { parseModelOptions as a, mergeModelOptions as i, PROVIDER_OPTIONS_KEY as n, resolveOptions as o, gatewayLayerOf as r, DEFAULT_GATEWAY_ID as t };
//# sourceMappingURL=settings-Dk2il2Hf.js.map