@genkit-ai/flow
Version:
Genkit AI framework workflow APIs.
670 lines • 23.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 __knownSymbol = (name, symbol) => {
return (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
};
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 __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
var __await = function(promise, isYieldStar) {
this[0] = promise;
this[1] = isYieldStar;
};
var __asyncGenerator = (__this, __arguments, generator) => {
var resume = (k, v, yes, no) => {
try {
var x = generator[k](v), isAwait = (v = x.value) instanceof __await, done = x.done;
Promise.resolve(isAwait ? v[0] : v).then((y) => isAwait ? resume(k === "return" ? k : "next", v[1] ? { done: y.done, value: y.value } : y, yes, no) : yes({ value: y, done })).catch((e) => resume("throw", e, yes, no));
} catch (e) {
no(e);
}
};
var method = (k) => it[k] = (x) => new Promise((yes, no) => resume(k, x, yes, no));
var it = {};
return generator = generator.apply(__this, __arguments), it[__knownSymbol("asyncIterator")] = () => it, method("next"), method("throw"), method("return"), it;
};
var flow_exports = {};
__export(flow_exports, {
Flow: () => Flow,
defineFlow: () => defineFlow,
runFlow: () => runFlow,
startFlowsServer: () => startFlowsServer,
streamFlow: () => streamFlow
});
module.exports = __toCommonJS(flow_exports);
var import_core = require("@genkit-ai/core");
var import_logging = require("@genkit-ai/core/logging");
var import_schema = require("@genkit-ai/core/schema");
var import_tracing = require("@genkit-ai/core/tracing");
var import_api = require("@opentelemetry/api");
var bodyParser = __toESM(require("body-parser"));
var import_cors = __toESM(require("cors"));
var import_express = __toESM(require("express"));
var import_node_perf_hooks = require("node:perf_hooks");
var import_context = require("./context.js");
var import_errors = require("./errors.js");
var import_types = require("./types.js");
var import_utils = require("./utils.js");
const streamDelimiter = "\n";
const CREATED_FLOWS = "genkit__CREATED_FLOWS";
function createdFlows() {
if (global[CREATED_FLOWS] === void 0) {
global[CREATED_FLOWS] = [];
}
return global[CREATED_FLOWS];
}
function defineFlow(config, steps) {
const f = new Flow(
{
name: config.name,
inputSchema: config.inputSchema,
outputSchema: config.outputSchema,
streamSchema: config.streamSchema,
experimentalDurable: !!config.experimentalDurable,
stateStore: import_core.config ? () => import_core.config.getFlowStateStore() : void 0,
authPolicy: config.authPolicy,
middleware: config.middleware,
// We always use local dispatcher in dev mode or when one is not provided.
invoker: (flow, msg, streamingCallback) => __async(this, null, function* () {
if (!(0, import_core.isDevEnv)() && config.invoker) {
return config.invoker(flow, msg, streamingCallback);
}
const state = yield flow.runEnvelope(msg, streamingCallback);
return state.operation;
}),
scheduler: (flow, msg, delay = 0) => __async(this, null, function* () {
if (!config.experimentalDurable) {
throw new Error(
"This flow is not durable, cannot use scheduling features."
);
}
if (!(0, import_core.isDevEnv)() && config.experimentalScheduler) {
return config.experimentalScheduler(flow, msg, delay);
}
setTimeout(() => flow.runEnvelope(msg), delay * 1e3);
})
},
steps
);
createdFlows().push(f);
wrapAsAction(f);
return f;
}
class Flow {
constructor(config, steps) {
this.steps = steps;
this.name = config.name;
this.inputSchema = config.inputSchema;
this.outputSchema = config.outputSchema;
this.streamSchema = config.streamSchema;
this.stateStore = config.stateStore;
this.invoker = config.invoker;
this.scheduler = config.scheduler;
this.experimentalDurable = config.experimentalDurable;
this.authPolicy = config.authPolicy;
this.middleware = config.middleware;
if (this.authPolicy && this.experimentalDurable) {
throw new Error("Durable flows can not define auth policies.");
}
}
/**
* Executes the flow with the input directly.
*
* This will either be called by runEnvelope when starting durable flows,
* or it will be called directly when starting non-durable flows.
*/
runDirectly(input, opts) {
return __async(this, null, function* () {
const flowId = (0, import_utils.generateFlowId)();
const state = createNewState(flowId, this.name, input);
const ctx = new import_context.Context(this, flowId, state, opts.auth);
try {
yield this.executeSteps(
ctx,
this.steps,
"start",
opts.streamingCallback,
opts.labels
);
} finally {
if ((0, import_core.isDevEnv)() || this.experimentalDurable) {
yield ctx.saveState();
}
}
return state;
});
}
/**
* Executes the flow with the input in the envelope format.
*/
runEnvelope(req, streamingCallback, auth) {
return __async(this, null, function* () {
import_logging.logger.debug(req, "runEnvelope");
if (req.start) {
return this.runDirectly(req.start.input, {
streamingCallback,
auth,
labels: req.start.labels
});
}
if (req.schedule) {
if (!this.experimentalDurable) {
throw new Error("Cannot schedule a non-durable flow");
}
if (!this.stateStore) {
throw new Error(
"Flow state store for durable flows must be configured"
);
}
const flowId = (0, import_utils.generateFlowId)();
const state = createNewState(flowId, this.name, req.schedule.input);
try {
yield (yield this.stateStore()).save(flowId, state);
yield this.scheduler(
this,
{ runScheduled: { flowId } },
req.schedule.delay
);
} catch (e) {
state.operation.done = true;
state.operation.result = {
error: (0, import_errors.getErrorMessage)(e),
stacktrace: (0, import_errors.getErrorStack)(e)
};
yield (yield this.stateStore()).save(flowId, state);
}
return state;
}
if (req.state) {
if (!this.experimentalDurable) {
throw new Error("Cannot state check a non-durable flow");
}
if (!this.stateStore) {
throw new Error(
"Flow state store for durable flows must be configured"
);
}
const flowId = req.state.flowId;
const state = yield (yield this.stateStore()).load(flowId);
if (state === void 0) {
throw new Error(`Unable to find flow state for ${flowId}`);
}
return state;
}
if (req.runScheduled) {
if (!this.experimentalDurable) {
throw new Error("Cannot run scheduled non-durable flow");
}
if (!this.stateStore) {
throw new Error(
"Flow state store for durable flows must be configured"
);
}
const flowId = req.runScheduled.flowId;
const state = yield (yield this.stateStore()).load(flowId);
if (state === void 0) {
throw new Error(`Unable to find flow state for ${flowId}`);
}
const ctx = new import_context.Context(this, flowId, state);
try {
yield this.executeSteps(
ctx,
this.steps,
"runScheduled",
void 0,
void 0
);
} finally {
yield ctx.saveState();
}
return state;
}
if (req.resume) {
if (!this.experimentalDurable) {
throw new Error("Cannot resume a non-durable flow");
}
if (!this.stateStore) {
throw new Error(
"Flow state store for durable flows must be configured"
);
}
const flowId = req.resume.flowId;
const state = yield (yield this.stateStore()).load(flowId);
if (state === void 0) {
throw new Error(`Unable to find flow state for ${flowId}`);
}
if (!state.blockedOnStep) {
throw new Error(
"Unable to resume flow that's currently not interrupted"
);
}
state.eventsTriggered[state.blockedOnStep.name] = req.resume.payload;
const ctx = new import_context.Context(this, flowId, state);
try {
yield this.executeSteps(
ctx,
this.steps,
"resume",
void 0,
void 0
);
} finally {
yield ctx.saveState();
}
return state;
}
throw new Error(
"Unexpected envelope message case, must set one of: start, schedule, runScheduled, resume, retry, state"
);
});
}
// TODO: refactor me... this is a mess!
executeSteps(ctx, handler, dispatchType, streamingCallback, labels) {
return __async(this, null, function* () {
const startTimeMs = import_node_perf_hooks.performance.now();
yield (0, import_utils.runWithActiveContext)(ctx, () => __async(this, null, function* () {
let traceContext;
if (ctx.state.traceContext) {
traceContext = JSON.parse(ctx.state.traceContext);
}
let ctxLinks = traceContext ? [{ context: traceContext }] : [];
let errored = false;
const output = yield (0, import_tracing.newTrace)(
{
name: ctx.flow.name,
labels: {
[import_tracing.SPAN_TYPE_ATTR]: "flow"
},
links: ctxLinks
},
(metadata, rootSpan) => __async(this, null, function* () {
ctx.state.executions.push({
startTime: Date.now(),
traceIds: []
});
(0, import_tracing.setCustomMetadataAttribute)(
(0, import_utils.metadataPrefix)(`execution`),
(ctx.state.executions.length - 1).toString()
);
if (labels) {
Object.keys(labels).forEach((label) => {
(0, import_tracing.setCustomMetadataAttribute)(
(0, import_utils.metadataPrefix)(`label:${label}`),
labels[label]
);
});
}
(0, import_tracing.setCustomMetadataAttributes)({
[(0, import_utils.metadataPrefix)("name")]: this.name,
[(0, import_utils.metadataPrefix)("id")]: ctx.flowId
});
ctx.getCurrentExecution().traceIds.push(rootSpan.spanContext().traceId);
if (!traceContext) {
ctx.state.traceContext = JSON.stringify(rootSpan.spanContext());
}
(0, import_tracing.setCustomMetadataAttribute)(
(0, import_utils.metadataPrefix)("dispatchType"),
dispatchType
);
try {
const input = this.inputSchema ? this.inputSchema.parse(ctx.state.input) : ctx.state.input;
metadata.input = input;
const output2 = yield handler(input, streamingCallback);
metadata.output = JSON.stringify(output2);
(0, import_tracing.setCustomMetadataAttribute)((0, import_utils.metadataPrefix)("state"), "done");
return output2;
} catch (e) {
if (e instanceof import_errors.InterruptError) {
(0, import_tracing.setCustomMetadataAttribute)(
(0, import_utils.metadataPrefix)("state"),
"interrupted"
);
} else {
metadata.state = "error";
rootSpan.setStatus({
code: import_api.SpanStatusCode.ERROR,
message: (0, import_errors.getErrorMessage)(e)
});
if (e instanceof Error) {
rootSpan.recordException(e);
}
(0, import_tracing.setCustomMetadataAttribute)((0, import_utils.metadataPrefix)("state"), "error");
ctx.state.operation.done = true;
ctx.state.operation.result = {
error: (0, import_errors.getErrorMessage)(e),
stacktrace: (0, import_errors.getErrorStack)(e)
};
}
errored = true;
}
})
);
if (!errored) {
ctx.state.operation.done = true;
ctx.state.operation.result = { response: output };
}
}));
});
}
durableExpressHandler(req, res) {
return __async(this, null, function* () {
if (req.query.stream === "true") {
const respBody = {
error: {
status: "INVALID_ARGUMENT",
message: "Output from durable flows cannot be streamed"
}
};
res.status(400).send(respBody).end();
return;
}
let data = req.body;
if (req.body.data) {
data = req.body.data;
}
const envMsg = import_types.FlowInvokeEnvelopeMessageSchema.parse(data);
try {
const state = yield this.runEnvelope(envMsg);
res.status(200).send(state.operation).end();
} catch (e) {
const respBody = {
done: true,
result: {
error: (0, import_errors.getErrorMessage)(e),
stacktrace: (0, import_errors.getErrorStack)(e)
}
};
res.status(500).send(respBody).end();
}
});
}
nonDurableExpressHandler(req, res) {
return __async(this, null, function* () {
var _a, _b, _c, _d;
const { stream } = req.query;
const auth = req.auth;
let input = req.body.data;
try {
yield (_a = this.authPolicy) == null ? void 0 : _a.call(this, auth, input);
} catch (e) {
const respBody = {
error: {
status: "PERMISSION_DENIED",
message: e.message || "Permission denied to resource"
}
};
res.status(403).send(respBody).end();
return;
}
if (stream === "true") {
res.writeHead(200, {
"Content-Type": "text/plain",
"Transfer-Encoding": "chunked"
});
try {
const state = yield this.runDirectly(input, {
streamingCallback: (chunk) => {
res.write(JSON.stringify(chunk) + streamDelimiter);
},
auth
});
res.write(JSON.stringify(state.operation));
res.end();
} catch (e) {
const respBody = {
done: true,
result: {
error: (0, import_errors.getErrorMessage)(e),
stacktrace: (0, import_errors.getErrorStack)(e)
}
};
res.write(JSON.stringify(respBody));
res.end();
}
} else {
try {
const state = yield this.runDirectly(input, { auth });
if ((_b = state.operation.result) == null ? void 0 : _b.error) {
throw new Error((_c = state.operation.result) == null ? void 0 : _c.error);
}
res.status(200).send({
result: (_d = state.operation.result) == null ? void 0 : _d.response
}).end();
} catch (e) {
res.status(500).send({
error: {
status: "INTERNAL",
message: (0, import_errors.getErrorMessage)(e),
details: (0, import_errors.getErrorStack)(e)
}
}).end();
}
}
});
}
get expressHandler() {
return this.experimentalDurable ? this.durableExpressHandler.bind(this) : this.nonDurableExpressHandler.bind(this);
}
}
function runFlow(flow, payload, opts) {
return __async(this, null, function* () {
var _a, _b, _c, _d, _e;
if (!(flow instanceof Flow)) {
flow = flow.flow;
}
const input = flow.inputSchema ? flow.inputSchema.parse(payload) : payload;
yield (_a = flow.authPolicy) == null ? void 0 : _a.call(flow, opts == null ? void 0 : opts.withLocalAuthContext, payload);
if (flow.middleware) {
import_logging.logger.warn(
`Flow (${flow.name}) middleware won't run when invoked with runFlow.`
);
}
const state = yield flow.runEnvelope(
{
start: {
input
}
},
void 0,
opts == null ? void 0 : opts.withLocalAuthContext
);
if (!state.operation.done) {
throw new import_errors.FlowStillRunningError(
`flow ${state.name} did not finish execution`
);
}
if ((_b = state.operation.result) == null ? void 0 : _b.error) {
throw new import_errors.FlowExecutionError(
state.operation.name,
(_c = state.operation.result) == null ? void 0 : _c.error,
(_d = state.operation.result) == null ? void 0 : _d.stacktrace
);
}
return (_e = state.operation.result) == null ? void 0 : _e.response;
});
}
function streamFlow(flowOrFlowWrapper, payload, opts) {
var _a, _b;
const flow = !(flowOrFlowWrapper instanceof Flow) ? flowOrFlowWrapper.flow : flowOrFlowWrapper;
let chunkStreamController;
const chunkStream = new ReadableStream({
start(controller) {
chunkStreamController = controller;
},
pull() {
},
cancel() {
}
});
const authPromise = (_b = (_a = flow.authPolicy) == null ? void 0 : _a.call(flow, opts == null ? void 0 : opts.withLocalAuthContext, payload)) != null ? _b : Promise.resolve();
const operationPromise = authPromise.then(
() => flow.runEnvelope(
{
start: {
input: flow.inputSchema ? flow.inputSchema.parse(payload) : payload
}
},
(c) => {
chunkStreamController.enqueue(c);
},
opts == null ? void 0 : opts.withLocalAuthContext
)
).then((s) => s.operation).finally(() => {
chunkStreamController.close();
});
return {
output() {
return operationPromise.then((op) => {
var _a2, _b2, _c2, _d;
if (!op.done) {
throw new import_errors.FlowStillRunningError(
`flow ${op.name} did not finish execution`
);
}
if ((_a2 = op.result) == null ? void 0 : _a2.error) {
throw new import_errors.FlowExecutionError(
op.name,
(_b2 = op.result) == null ? void 0 : _b2.error,
(_c2 = op.result) == null ? void 0 : _c2.stacktrace
);
}
return (_d = op.result) == null ? void 0 : _d.response;
});
},
stream() {
return __asyncGenerator(this, null, function* () {
const reader = chunkStream.getReader();
while (true) {
const chunk = yield new __await(reader.read());
if (chunk.value) {
yield chunk.value;
}
if (chunk.done) {
break;
}
}
return yield new __await(operationPromise);
});
}
};
}
function createNewState(flowId, name, input) {
return {
flowId,
name,
startTime: Date.now(),
input,
cache: {},
eventsTriggered: {},
blockedOnStep: null,
executions: [],
operation: {
name: flowId,
done: false
}
};
}
function wrapAsAction(flow) {
return (0, import_core.defineAction)(
{
actionType: "flow",
name: flow.name,
inputSchema: import_types.FlowActionInputSchema,
outputSchema: import_core.FlowStateSchema,
metadata: {
inputSchema: (0, import_schema.toJsonSchema)({ schema: flow.inputSchema }),
outputSchema: (0, import_schema.toJsonSchema)({ schema: flow.outputSchema }),
experimentalDurable: !!flow.experimentalDurable,
requiresAuth: !!flow.authPolicy
}
},
(envelope) => __async(this, null, function* () {
var _a, _b;
yield (_b = flow.authPolicy) == null ? void 0 : _b.call(
flow,
envelope.auth,
(_a = envelope.start) == null ? void 0 : _a.input
);
(0, import_tracing.setCustomMetadataAttribute)((0, import_utils.metadataPrefix)("wrapperAction"), "true");
return yield flow.runEnvelope(
envelope,
(0, import_core.getStreamingCallback)(),
envelope.auth
);
})
);
}
function startFlowsServer(params) {
var _a;
const port = (params == null ? void 0 : params.port) || (process.env.PORT ? parseInt(process.env.PORT) : 0) || 3400;
const pathPrefix = (_a = params == null ? void 0 : params.pathPrefix) != null ? _a : "";
const app = (0, import_express.default)();
app.use(bodyParser.json(params == null ? void 0 : params.jsonParserOptions));
app.use((0, import_cors.default)(params == null ? void 0 : params.cors));
const flows = (params == null ? void 0 : params.flows) || createdFlows();
import_logging.logger.info(`Starting flows server on port ${port}`);
flows.forEach((f) => {
var _a2;
const flowPath = `/${pathPrefix}${f.name}`;
import_logging.logger.info(` - ${flowPath}`);
(_a2 = f.middleware) == null ? void 0 : _a2.forEach((m) => {
app.post(flowPath, m);
});
app.post(flowPath, f.expressHandler);
});
app.listen(port, () => {
console.log(`Flows server listening on port ${port}`);
});
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Flow,
defineFlow,
runFlow,
startFlowsServer,
streamFlow
});
//# sourceMappingURL=flow.js.map