@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
124 lines (123 loc) • 5.28 kB
JavaScript
import { logger } from "../utils/logger.js";
import { redactUrlForError } from "../utils/logSanitize.js";
import { getProviderModel } from "../utils/providerConfig.js";
import { catalogCredentialsKey, catalogEnvVar } from "./catalog/loader.js";
import { baseURLShapeProblem, normalizeBaseURL } from "./decide/baseUrl.js";
import { getDecisionDialect } from "./decide/dialects/index.js";
import { classifyDecisionError } from "./decide/errors.js";
import { isRecord } from "./decide/guards.js";
import { readPath } from "./decide/paths.js";
import { fillEndpointTemplate } from "./decide/template.js";
import { SystemOneDecisionProvider } from "./systemOneDecision.js";
const readText = (value) => typeof value === "string" ? value.trim() : "";
/** A `decide` provider described entirely by its catalog entry. */
export class ConfiguredDecisionProvider extends SystemOneDecisionProvider {
entry;
apiKey;
baseURL;
endpoint;
constructor(entry, modelName, sdk, credentials) {
// BaseProvider's constructor calls getDefaultModel() for a falsy name,
// before this.entry is assigned, so the name is settled here.
super(modelName ||
getProviderModel(catalogEnvVar(entry, "model"), entry.models.default), entry.id, sdk);
this.entry = entry;
this.apiKey =
readText(credentials?.apiKey) ||
readText(process.env[catalogEnvVar(entry, "apiKey")]);
// `||`, not `??`, so a blank value counts as unset.
this.baseURL = normalizeBaseURL(readText(credentials?.baseURL) ||
readText(process.env[catalogEnvVar(entry, "baseURL")]) ||
readText(entry.wire.baseURL), entry.decide.baseURLNormalize ?? []);
this.endpoint = fillEndpointTemplate(entry.decide.endpoint, {
baseURL: this.baseURL,
});
// A self-hosted base URL can carry `user:pass@` or a `?token=`.
logger.debug(`${entry.displayName} Provider initialized (decide only)`, {
modelName: this.modelName,
baseURL: this.baseURL ? redactUrlForError(this.baseURL) : "(not set)",
});
}
getDefaultModel() {
return getProviderModel(catalogEnvVar(this.entry, "model"), this.entry.models.default);
}
vendorLabel() {
return this.entry.displayName;
}
vendorDisplayName() {
return this.entry.displayName;
}
decisionApiKey() {
return this.apiKey;
}
missingKeyMessage() {
return `${this.entry.displayName} requires an API key. Set ${catalogEnvVar(this.entry, "apiKey")} or pass credentials.${catalogCredentialsKey(this.entry)}.apiKey.`;
}
missingConfigMessage() {
const { baseURLRequired, baseURLShape } = this.entry.decide;
if (!this.baseURL) {
return baseURLRequired
? `${this.entry.displayName} requires a base URL. Set ${catalogEnvVar(this.entry, "baseURL")} or pass credentials.${catalogCredentialsKey(this.entry)}.baseURL.`
: undefined;
}
return baseURLShape
? baseURLShapeProblem(baseURLShape, this.baseURL, this.entry.displayName, `Set ${catalogEnvVar(this.entry, "baseURL")} or pass credentials.${catalogCredentialsKey(this.entry)}.baseURL to the origin, and give the key through ${catalogEnvVar(this.entry, "apiKey")} or credentials.${catalogCredentialsKey(this.entry)}.apiKey.`)
: undefined;
}
decisionEndpoint() {
return this.endpoint;
}
decisionHeaders() {
return {
Authorization: `Bearer ${this.apiKey}`,
"Content-Type": "application/json",
};
}
buildDecisionBody(state, questions, model, media) {
const { placement, alias } = this.entry.decide.model;
return this.dialect().buildBody({
state,
questions,
model: placement === "body-unless-alias" && model === alias
? undefined
: model,
media,
mediaKeys: this.entry.decide.media,
});
}
parseDecisionError(status, payload, requestId) {
return classifyDecisionError(this.entry.decide.errors, {
status,
payload,
requestId,
label: this.entry.displayName,
apiKey: this.apiKey,
});
}
readRequestId(headers) {
for (const name of this.entry.decide.requestIdHeaders ?? []) {
const value = headers.get(name);
if (value !== null) {
return value;
}
}
return undefined;
}
resolveResponseModel(payload, requestedModel) {
// Defensive: the base class passes a record today. A direct call with
// null or undefined must not throw here, where the base lookup would.
if (!isRecord(payload)) {
return requestedModel;
}
for (const path of this.entry.decide.response?.modelPaths ?? []) {
const value = readPath(payload, path);
if (typeof value === "string") {
return value;
}
}
return super.resolveResponseModel(payload, requestedModel);
}
dialect() {
return getDecisionDialect(this.entry.decide.dialect);
}
}