@genkit-ai/core
Version:
Genkit AI framework core libraries.
446 lines • 16.4 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var reflection_exports = {};
__export(reflection_exports, {
ReflectionServer: () => ReflectionServer,
RunActionResponseSchema: () => RunActionResponseSchema
});
module.exports = __toCommonJS(reflection_exports);
var import_express = __toESM(require("express"));
var import_promises = __toESM(require("fs/promises"));
var import_get_port = __toESM(require("get-port"));
var import_path = __toESM(require("path"));
var z = __toESM(require("zod"));
var import_action = require("./action.js");
var import_index = require("./index.js");
var import_logging = require("./logging.js");
var import_schema = require("./schema.js");
var import_tracing = require("./tracing.js");
const RunActionResponseSchema = z.object({
result: z.unknown().optional(),
error: z.unknown().optional(),
telemetry: z.object({
traceId: z.string().optional()
}).optional()
});
function isAbortError(err) {
return err?.name === "AbortError" || typeof DOMException !== "undefined" && err instanceof DOMException && err.name === "AbortError";
}
class ReflectionServer {
/** List of all running servers needed to be cleaned up on process exit. */
static RUNNING_SERVERS = [];
/** Registry instance to be used for API calls. */
registry;
/** Options for the reflection server. */
options;
/** Port the server is actually running on. This may differ from `options.port` if the original was occupied. Null if server is not running. */
port = null;
/** Express server instance. Null if server is not running. */
server = null;
/** Path to the runtime file. Null if server is not running. */
runtimeFilePath = null;
/** Map of active actions indexed by trace ID for cancellation support. */
activeActions = /* @__PURE__ */ new Map();
constructor(registry, options) {
this.registry = registry;
this.options = {
port: 3100,
bodyLimit: "30mb",
configuredEnvs: ["dev"],
...options
};
}
get runtimeId() {
return `${process.pid}${this.port !== null ? `-${this.port}` : ""}`;
}
/**
* Finds a free port to run the server on based on the original chosen port and environment.
*/
async findPort() {
const chosenPort = this.options.port;
const freePort = await (0, import_get_port.default)({
port: (0, import_get_port.makeRange)(chosenPort, chosenPort + 100)
});
if (freePort !== chosenPort) {
import_logging.logger.warn(
`Port ${chosenPort} is already in use, using next available port ${freePort} instead.`
);
}
return freePort;
}
/**
* Starts the server.
*
* The server will be registered to be shut down on process exit.
*/
async start() {
const server = (0, import_express.default)();
server.use(import_express.default.json({ limit: this.options.bodyLimit }));
server.use((req, res, next) => {
res.header("x-genkit-version", import_index.GENKIT_VERSION);
next();
});
server.get("/api/__health", async (req, response) => {
if (req.query["id"] && req.query["id"] !== this.runtimeId) {
response.status(503).send("Invalid runtime ID");
return;
}
await this.registry.listActions();
response.status(200).send("OK");
});
server.get("/api/__quitquitquit", async (_, response) => {
import_logging.logger.debug("Received quitquitquit");
response.status(200).send("OK");
await this.stop();
});
server.get("/api/actions", async (_, response, next) => {
import_logging.logger.debug("Fetching actions.");
try {
const actions = await this.registry.listResolvableActions();
const convertedActions = {};
Object.keys(actions).forEach((key) => {
const action = actions[key];
convertedActions[key] = {
key,
name: action.name,
description: action.description,
metadata: action.metadata
};
if (action.inputSchema || action.inputJsonSchema) {
convertedActions[key].inputSchema = (0, import_schema.toJsonSchema)({
schema: action.inputSchema,
jsonSchema: action.inputJsonSchema
});
}
if (action.outputSchema || action.outputJsonSchema) {
convertedActions[key].outputSchema = (0, import_schema.toJsonSchema)({
schema: action.outputSchema,
jsonSchema: action.outputJsonSchema
});
}
});
response.send(convertedActions);
} catch (err) {
const { message, stack } = err;
next({ message, stack });
}
});
server.post("/api/runAction", async (request, response, next) => {
const { key, input, context, telemetryLabels } = request.body;
const { stream } = request.query;
import_logging.logger.debug(`Running action \`${key}\` with stream=${stream}...`);
const abortController = new AbortController();
let traceId;
try {
const action = await this.registry.lookupAction(key);
if (!action) {
response.status(404).send(`action ${key} not found`);
return;
}
const onTraceStartCallback = ({
traceId: tid,
spanId
}) => {
traceId = tid;
this.activeActions.set(tid, {
abortController,
startTime: /* @__PURE__ */ new Date()
});
response.setHeader("X-Genkit-Trace-Id", tid);
response.setHeader("X-Genkit-Span-Id", spanId);
response.setHeader("X-Genkit-Version", import_index.GENKIT_VERSION);
if (stream === "true") {
response.setHeader("Content-Type", "text/plain");
response.setHeader("Transfer-Encoding", "chunked");
} else {
response.setHeader("Content-Type", "application/json");
response.setHeader("Transfer-Encoding", "chunked");
}
response.statusCode = 200;
response.flushHeaders();
};
if (stream === "true") {
try {
const callback = (chunk) => {
response.write(JSON.stringify(chunk) + "\n");
};
const result = await action.run(input, {
context,
onChunk: callback,
telemetryLabels,
onTraceStart: onTraceStartCallback,
abortSignal: abortController.signal
});
await (0, import_tracing.flushTracing)();
response.write(
JSON.stringify({
result: result.result,
telemetry: {
traceId: result.telemetry.traceId
}
})
);
response.end();
} catch (err) {
const { message, stack } = err;
const errorResponse = {
code: isAbortError(err) ? import_action.StatusCodes.CANCELLED : import_action.StatusCodes.INTERNAL,
message: isAbortError(err) ? "Action was cancelled" : message,
details: {
stack
}
};
if (err.traceId) {
errorResponse.details.traceId = err.traceId;
}
response.write(
JSON.stringify({
error: errorResponse
})
);
response.end();
}
} else {
const result = await action.run(input, {
context,
telemetryLabels,
onTraceStart: onTraceStartCallback,
abortSignal: abortController.signal
});
await (0, import_tracing.flushTracing)();
response.end(
JSON.stringify({
result: result.result,
telemetry: {
traceId: result.telemetry.traceId
}
})
);
}
} catch (err) {
const { message, stack } = err;
const errorResponse = {
code: isAbortError(err) ? import_action.StatusCodes.CANCELLED : import_action.StatusCodes.INTERNAL,
message: isAbortError(err) ? "Action was cancelled" : message,
details: { stack, traceId: err.traceId || traceId }
};
if (response.headersSent) {
response.end(
JSON.stringify({ error: errorResponse })
);
} else {
next({ message, stack });
}
} finally {
if (traceId) {
this.activeActions.delete(traceId);
}
}
});
server.post("/api/cancelAction", async (request, response) => {
const { traceId } = request.body;
if (!traceId || typeof traceId !== "string") {
response.status(400).json({ error: "traceId is required" });
return;
}
const activeAction = this.activeActions.get(traceId);
if (activeAction) {
activeAction.abortController.abort();
this.activeActions.delete(traceId);
response.status(200).json({ message: "Action cancelled" });
} else {
response.status(404).json({
message: "Action not found or already completed"
});
}
});
server.get("/api/envs", async (_, response) => {
response.json(this.options.configuredEnvs);
});
server.post("/api/notify", async (request, response) => {
const { telemetryServerUrl, reflectionApiSpecVersion } = request.body;
if (!process.env.GENKIT_TELEMETRY_SERVER) {
if (typeof telemetryServerUrl === "string") {
(0, import_tracing.setTelemetryServerUrl)(telemetryServerUrl);
import_logging.logger.debug(
`Connected to telemetry server on ${telemetryServerUrl}`
);
}
}
if (reflectionApiSpecVersion !== import_index.GENKIT_REFLECTION_API_SPEC_VERSION) {
if (!reflectionApiSpecVersion || reflectionApiSpecVersion < import_index.GENKIT_REFLECTION_API_SPEC_VERSION) {
import_logging.logger.warn(
"WARNING: Genkit CLI version may be outdated. Please update `genkit-cli` to the latest version."
);
} else {
import_logging.logger.warn(
`Genkit CLI is newer than runtime library. Some feature may not be supported. Consider upgrading your runtime library version (debug info: expected ${import_index.GENKIT_REFLECTION_API_SPEC_VERSION}, got ${reflectionApiSpecVersion}).`
);
}
}
response.status(200).send("OK");
});
server.use((err, req, res, next) => {
import_logging.logger.error(err.stack);
const error = err;
const { message, stack } = error;
const errorResponse = {
code: import_action.StatusCodes.INTERNAL,
message,
details: {
stack
}
};
res.status(200).end(JSON.stringify({ error: errorResponse }));
});
this.port = await this.findPort();
this.server = server.listen(this.port, async () => {
import_logging.logger.debug(
`Reflection server (${process.pid}) running on http://localhost:${this.port}`
);
ReflectionServer.RUNNING_SERVERS.push(this);
await this.writeRuntimeFile();
});
}
/**
* Stops the server and removes it from the list of running servers to clean up on exit.
*/
async stop() {
if (!this.server) {
return;
}
return new Promise(async (resolve, reject) => {
await this.cleanupRuntimeFile();
this.server.close(async (err) => {
if (err) {
import_logging.logger.error(
`Error shutting down reflection server on port ${this.port}: ${err}`
);
reject(err);
}
const index = ReflectionServer.RUNNING_SERVERS.indexOf(this);
if (index > -1) {
ReflectionServer.RUNNING_SERVERS.splice(index, 1);
}
import_logging.logger.debug(
`Reflection server on port ${this.port} has successfully shut down.`
);
this.port = null;
this.server = null;
resolve();
});
});
}
/**
* Writes the runtime file to the project root.
*/
async writeRuntimeFile() {
try {
const rootDir = await findProjectRoot();
const runtimesDir = import_path.default.join(rootDir, ".genkit", "runtimes");
const date = /* @__PURE__ */ new Date();
const time = date.getTime();
const timestamp = date.toISOString();
this.runtimeFilePath = import_path.default.join(
runtimesDir,
`${this.runtimeId}-${time}.json`
);
const fileContent = JSON.stringify(
{
id: process.env.GENKIT_RUNTIME_ID || this.runtimeId,
pid: process.pid,
name: this.options.name,
reflectionServerUrl: `http://localhost:${this.port}`,
timestamp,
genkitVersion: `nodejs/${import_index.GENKIT_VERSION}`,
reflectionApiSpecVersion: import_index.GENKIT_REFLECTION_API_SPEC_VERSION
},
null,
2
);
await import_promises.default.mkdir(runtimesDir, { recursive: true });
await import_promises.default.writeFile(this.runtimeFilePath, fileContent, "utf8");
import_logging.logger.debug(`Runtime file written: ${this.runtimeFilePath}`);
} catch (error) {
import_logging.logger.error(`Error writing runtime file: ${error}`);
}
}
/**
* Cleans up the port file.
*/
async cleanupRuntimeFile() {
if (!this.runtimeFilePath) {
return;
}
try {
const fileContent = await import_promises.default.readFile(this.runtimeFilePath, "utf8");
const data = JSON.parse(fileContent);
if (data.pid === process.pid) {
await import_promises.default.unlink(this.runtimeFilePath);
import_logging.logger.debug(`Runtime file cleaned up: ${this.runtimeFilePath}`);
}
} catch (error) {
import_logging.logger.error(`Error cleaning up runtime file: ${error}`);
}
}
/**
* Stops all running reflection servers.
*/
static async stopAll() {
return Promise.all(
ReflectionServer.RUNNING_SERVERS.map((server) => server.stop())
);
}
}
async function findProjectRoot() {
let currentDir = process.cwd();
while (currentDir !== import_path.default.parse(currentDir).root) {
const packageJsonPath = import_path.default.join(currentDir, "package.json");
try {
await import_promises.default.access(packageJsonPath);
return currentDir;
} catch {
currentDir = import_path.default.dirname(currentDir);
}
}
throw new Error("Could not find project root (package.json not found)");
}
if (typeof module !== "undefined" && "hot" in module) {
module.hot.accept();
module.hot.dispose(async () => {
import_logging.logger.debug("Cleaning up reflection server(s) before module reload...");
await ReflectionServer.stopAll();
});
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ReflectionServer,
RunActionResponseSchema
});
//# sourceMappingURL=reflection.js.map