weifuwu 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -134
- package/dist/agent/run.d.ts +1 -1
- package/dist/agent/types.d.ts +2 -2
- package/dist/ai/workflow.d.ts +14 -0
- package/dist/ai.d.ts +5 -17
- package/dist/deploy/types.d.ts +1 -1
- package/dist/graphql.d.ts +3 -1
- package/dist/health.d.ts +6 -0
- package/dist/i18n.d.ts +6 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +947 -840
- package/dist/mailer.d.ts +20 -0
- package/dist/opencode/rest.d.ts +1 -1
- package/dist/opencode/run.d.ts +1 -1
- package/dist/opencode/tools/index.d.ts +1 -1
- package/dist/opencode/ws.d.ts +1 -1
- package/dist/postgres/schema/columns.d.ts +1 -0
- package/dist/postgres/schema/index.d.ts +2 -2
- package/dist/postgres/schema/table.d.ts +27 -0
- package/dist/postgres/types.d.ts +4 -0
- package/dist/serve.d.ts +4 -0
- package/dist/types.d.ts +2 -0
- package/dist/user/oauth2.d.ts +2 -1
- package/dist/vendor.d.ts +0 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -63,6 +63,11 @@ async function sendResponse(res, response) {
|
|
|
63
63
|
}
|
|
64
64
|
res.end();
|
|
65
65
|
}
|
|
66
|
+
async function createTestServer(handler) {
|
|
67
|
+
const server = serve(handler, { port: 0 });
|
|
68
|
+
await server.ready;
|
|
69
|
+
return { server, url: `http://localhost:${server.port}` };
|
|
70
|
+
}
|
|
66
71
|
function serve(handler, options) {
|
|
67
72
|
const port = options?.port ?? 0;
|
|
68
73
|
const hostname = options?.hostname ?? "0.0.0.0";
|
|
@@ -329,13 +334,13 @@ var Router = class _Router {
|
|
|
329
334
|
const mw = match.middlewares[index++];
|
|
330
335
|
return mw(innerReq, ctx2, dispatch);
|
|
331
336
|
}
|
|
332
|
-
return await new Promise((
|
|
337
|
+
return await new Promise((resolve10) => {
|
|
333
338
|
try {
|
|
334
339
|
upgradeSocket(router.wss, req, socket, head, match.handler, ctx2);
|
|
335
|
-
|
|
340
|
+
resolve10(new Response(null, { status: 101 }));
|
|
336
341
|
} catch {
|
|
337
342
|
socket.destroy();
|
|
338
|
-
|
|
343
|
+
resolve10(new Response("WebSocket upgrade failed", { status: 500 }));
|
|
339
344
|
}
|
|
340
345
|
});
|
|
341
346
|
};
|
|
@@ -1946,37 +1951,39 @@ function graphql(handler) {
|
|
|
1946
1951
|
}
|
|
1947
1952
|
return executeQuery(schema, params, options, req, ctx);
|
|
1948
1953
|
});
|
|
1949
|
-
return r;
|
|
1954
|
+
return { router: () => r };
|
|
1950
1955
|
}
|
|
1951
1956
|
|
|
1952
1957
|
// ai.ts
|
|
1953
|
-
var _ai = {
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1958
|
+
var _ai = {};
|
|
1959
|
+
async function getStreamText() {
|
|
1960
|
+
if (!_ai.streamText) _ai.streamText = (await import("ai")).streamText;
|
|
1961
|
+
return _ai.streamText;
|
|
1962
|
+
}
|
|
1963
|
+
async function getStreamObject() {
|
|
1964
|
+
if (!_ai.streamObject) _ai.streamObject = (await import("ai")).streamObject;
|
|
1965
|
+
return _ai.streamObject;
|
|
1966
|
+
}
|
|
1967
|
+
async function aiStream(handler) {
|
|
1960
1968
|
const r = new Router();
|
|
1961
1969
|
r.post("/", async (req, ctx) => {
|
|
1962
1970
|
const options = await handler(req, ctx);
|
|
1963
|
-
|
|
1971
|
+
if (options.schema) {
|
|
1972
|
+
const streamObject = await getStreamObject();
|
|
1973
|
+
const { schema, ...params } = options;
|
|
1974
|
+
const result2 = streamObject({ ...params, schema, output: "object" });
|
|
1975
|
+
return result2.toTextStreamResponse();
|
|
1976
|
+
}
|
|
1977
|
+
const streamText3 = await getStreamText();
|
|
1978
|
+
const result = streamText3(options);
|
|
1964
1979
|
return result.toTextStreamResponse();
|
|
1965
1980
|
});
|
|
1966
|
-
return r;
|
|
1981
|
+
return { router: () => r };
|
|
1967
1982
|
}
|
|
1968
1983
|
|
|
1969
|
-
// workflow
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
name: def.name ?? "",
|
|
1973
|
-
description: def.description,
|
|
1974
|
-
inputSchema: def.inputSchema,
|
|
1975
|
-
execute: def.execute
|
|
1976
|
-
};
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
// workflow/reference.ts
|
|
1984
|
+
// ai/workflow.ts
|
|
1985
|
+
import { tool, generateText } from "ai";
|
|
1986
|
+
import { z } from "zod";
|
|
1980
1987
|
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1981
1988
|
function getByPath(obj, path2) {
|
|
1982
1989
|
let current = obj;
|
|
@@ -1993,33 +2000,21 @@ function getByPath(obj, path2) {
|
|
|
1993
2000
|
}
|
|
1994
2001
|
function resolveRef(path2, ctx) {
|
|
1995
2002
|
if (path2.startsWith("$nodes.")) {
|
|
1996
|
-
const
|
|
1997
|
-
const
|
|
1998
|
-
if (
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
const id2 = afterNodes.slice(0, dotIdx);
|
|
2002
|
-
const propPath = afterNodes.slice(dotIdx + 1);
|
|
2003
|
+
const after = path2.slice(7);
|
|
2004
|
+
const dot = after.indexOf(".");
|
|
2005
|
+
if (dot === -1) return ctx.nodeOutputs.get(after);
|
|
2006
|
+
const id2 = after.slice(0, dot);
|
|
2007
|
+
const propPath = after.slice(dot + 1);
|
|
2003
2008
|
const output = ctx.nodeOutputs.get(id2);
|
|
2004
|
-
if (output === void 0) {
|
|
2005
|
-
|
|
2006
|
-
}
|
|
2007
|
-
if (propPath.startsWith("output")) {
|
|
2008
|
-
return getByPath(output, propPath.slice(7).split(".").filter(Boolean));
|
|
2009
|
-
}
|
|
2010
|
-
return getByPath(output, propPath.split("."));
|
|
2009
|
+
if (output === void 0) throw new Error(`Node "${id2}" has no output yet`);
|
|
2010
|
+
return getByPath(output, propPath.startsWith("output") ? propPath.slice(7).split(".").filter(Boolean) : propPath.split("."));
|
|
2011
2011
|
}
|
|
2012
2012
|
if (path2.startsWith("$var.")) {
|
|
2013
2013
|
const name = path2.slice(5);
|
|
2014
|
-
if (!ctx.variables.has(name)) {
|
|
2015
|
-
throw new Error(`Variable "${name}" is not defined`);
|
|
2016
|
-
}
|
|
2014
|
+
if (!ctx.variables.has(name)) throw new Error(`Variable "${name}" is not defined`);
|
|
2017
2015
|
return ctx.variables.get(name);
|
|
2018
2016
|
}
|
|
2019
|
-
if (path2.startsWith("$input."))
|
|
2020
|
-
const key = path2.slice(7);
|
|
2021
|
-
return ctx.input[key];
|
|
2022
|
-
}
|
|
2017
|
+
if (path2.startsWith("$input.")) return ctx.input[path2.slice(7)];
|
|
2023
2018
|
if (path2 === "true") return true;
|
|
2024
2019
|
if (path2 === "false") return false;
|
|
2025
2020
|
if (path2 === "null") return null;
|
|
@@ -2028,23 +2023,15 @@ function resolveRef(path2, ctx) {
|
|
|
2028
2023
|
return path2;
|
|
2029
2024
|
}
|
|
2030
2025
|
function resolveValue(v, ctx) {
|
|
2031
|
-
if (typeof v === "string" && v.startsWith("$"))
|
|
2032
|
-
|
|
2033
|
-
}
|
|
2034
|
-
if (Array.isArray(v)) {
|
|
2035
|
-
return v.map((item) => resolveValue(item, ctx));
|
|
2036
|
-
}
|
|
2026
|
+
if (typeof v === "string" && v.startsWith("$")) return resolveRef(v, ctx);
|
|
2027
|
+
if (Array.isArray(v)) return v.map((item) => resolveValue(item, ctx));
|
|
2037
2028
|
if (typeof v === "object" && v !== null) {
|
|
2038
2029
|
const result = {};
|
|
2039
|
-
for (const [k, val] of Object.entries(v))
|
|
2040
|
-
result[k] = resolveValue(val, ctx);
|
|
2041
|
-
}
|
|
2030
|
+
for (const [k, val] of Object.entries(v)) result[k] = resolveValue(val, ctx);
|
|
2042
2031
|
return result;
|
|
2043
2032
|
}
|
|
2044
2033
|
return v;
|
|
2045
2034
|
}
|
|
2046
|
-
|
|
2047
|
-
// workflow/nodes.ts
|
|
2048
2035
|
function evaluateExpression(expr, ctx) {
|
|
2049
2036
|
const operators = [
|
|
2050
2037
|
{ op: "===", fn: (a, b) => a === b },
|
|
@@ -2074,19 +2061,13 @@ function evaluateExpression(expr, ctx) {
|
|
|
2074
2061
|
bestFn = fn;
|
|
2075
2062
|
}
|
|
2076
2063
|
}
|
|
2077
|
-
function resolveOperand(raw) {
|
|
2078
|
-
const trimmed2 = raw.trim();
|
|
2079
|
-
if (/^[\d.]+$/.test(trimmed2)) return Number(trimmed2);
|
|
2080
|
-
const subIdx = operators.findIndex(({ op }) => trimmed2.includes(op));
|
|
2081
|
-
if (subIdx !== -1) return evaluateExpression(trimmed2, ctx);
|
|
2082
|
-
return resolveValue(trimmed2, ctx);
|
|
2083
|
-
}
|
|
2084
2064
|
if (bestIdx > 0 && bestOp && bestFn) {
|
|
2085
|
-
const
|
|
2086
|
-
const
|
|
2087
|
-
const
|
|
2088
|
-
const
|
|
2089
|
-
|
|
2065
|
+
const left = expr.slice(0, bestIdx).trim();
|
|
2066
|
+
const right = expr.slice(bestIdx + bestOp.length).trim();
|
|
2067
|
+
const isExpr = (s) => /[+\-*/%&|><=!]/.test(s) && !/^[\d.]+$/.test(s);
|
|
2068
|
+
const lv = isExpr(left) ? evaluateExpression(left, ctx) : /^[\d.]+$/.test(left) ? Number(left) : resolveValue(left, ctx);
|
|
2069
|
+
const rv = isExpr(right) ? evaluateExpression(right, ctx) : /^[\d.]+$/.test(right) ? Number(right) : resolveValue(right, ctx);
|
|
2070
|
+
return bestFn(lv, rv);
|
|
2090
2071
|
}
|
|
2091
2072
|
const trimmed = expr.trim();
|
|
2092
2073
|
if (trimmed === "true") return true;
|
|
@@ -2096,466 +2077,448 @@ function evaluateExpression(expr, ctx) {
|
|
|
2096
2077
|
if (!isNaN(num) && trimmed !== "") return num;
|
|
2097
2078
|
return resolveValue(expr, ctx);
|
|
2098
2079
|
}
|
|
2099
|
-
async function
|
|
2100
|
-
const
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
return lastOutput;
|
|
2080
|
+
async function executeNode(node, ctx) {
|
|
2081
|
+
const { tool: nodeType, input, conditions, body } = node;
|
|
2082
|
+
ctx.stepCount++;
|
|
2083
|
+
if (ctx.stepCount > ctx.maxSteps) throw new Error(`Step limit exceeded (${ctx.maxSteps})`);
|
|
2084
|
+
switch (nodeType) {
|
|
2085
|
+
case "eval": {
|
|
2086
|
+
const expression = input.expression;
|
|
2087
|
+
if (!expression) throw new Error('eval node requires "expression"');
|
|
2088
|
+
return { result: evaluateExpression(expression, ctx) };
|
|
2089
|
+
}
|
|
2090
|
+
case "set": {
|
|
2091
|
+
const name = input.name;
|
|
2092
|
+
if (!name) throw new Error('set node requires "name"');
|
|
2093
|
+
const value = typeof input.value === "string" ? evaluateExpression(input.value, ctx) : resolveValue(input.value, ctx);
|
|
2094
|
+
ctx.variables.set(name, value);
|
|
2095
|
+
return value;
|
|
2096
|
+
}
|
|
2097
|
+
case "get": {
|
|
2098
|
+
const name = input.name;
|
|
2099
|
+
if (!name) throw new Error('get node requires "name"');
|
|
2100
|
+
if (!ctx.variables.has(name)) throw new Error(`Variable "${name}" is not defined`);
|
|
2101
|
+
return ctx.variables.get(name);
|
|
2102
|
+
}
|
|
2103
|
+
case "if": {
|
|
2104
|
+
for (const c of conditions ?? []) {
|
|
2105
|
+
const test = typeof c.test === "string" ? Boolean(resolveValue(c.test, ctx)) : c.test;
|
|
2106
|
+
if (test && c.body) {
|
|
2107
|
+
let last;
|
|
2108
|
+
for (const n of c.body) {
|
|
2109
|
+
last = await executeNode(n, ctx);
|
|
2110
|
+
ctx.nodeOutputs.set(n.id, last);
|
|
2111
|
+
}
|
|
2112
|
+
return last;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
return void 0;
|
|
2136
2116
|
}
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
throw new Error(`Step limit exceeded (${ctx.maxSteps})`);
|
|
2151
|
-
}
|
|
2152
|
-
const condition = Boolean(evaluateExpression(conditionExpr, ctx));
|
|
2153
|
-
if (!condition) break;
|
|
2154
|
-
for (const bodyNode of node.body ?? []) {
|
|
2155
|
-
lastOutput = await executeNode(bodyNode, ctx);
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
return lastOutput;
|
|
2159
|
-
}
|
|
2160
|
-
async function executeCall(node, ctx) {
|
|
2161
|
-
const toolName = node.input.tool;
|
|
2162
|
-
const args = node.input.args ?? {};
|
|
2163
|
-
if (toolName && ctx.toolRegistry.has(toolName)) {
|
|
2164
|
-
const tool12 = ctx.toolRegistry.get(toolName);
|
|
2165
|
-
const resolvedInput = resolveValue(args, ctx);
|
|
2166
|
-
const parsed = tool12.inputSchema.parse(resolvedInput);
|
|
2167
|
-
return tool12.execute(parsed, {
|
|
2168
|
-
nodeId: node.id,
|
|
2169
|
-
workflowId: ctx.workflowId,
|
|
2170
|
-
onStream: async (event) => {
|
|
2171
|
-
if (ctx.sseManager && ctx.workflowId) {
|
|
2172
|
-
ctx.sseManager.send(ctx.workflowId, { event: "llm-stream", data: { nodeId: node.id, ...event } });
|
|
2117
|
+
case "while": {
|
|
2118
|
+
const conditionExpr = input.condition;
|
|
2119
|
+
if (!conditionExpr) throw new Error('while node requires "condition"');
|
|
2120
|
+
let last;
|
|
2121
|
+
let iters = 0;
|
|
2122
|
+
while (iters < 1e3) {
|
|
2123
|
+
iters++;
|
|
2124
|
+
ctx.stepCount++;
|
|
2125
|
+
if (ctx.stepCount > ctx.maxSteps) throw new Error(`Step limit exceeded`);
|
|
2126
|
+
if (!Boolean(evaluateExpression(conditionExpr, ctx))) break;
|
|
2127
|
+
for (const n of body ?? []) {
|
|
2128
|
+
last = await executeNode(n, ctx);
|
|
2129
|
+
ctx.nodeOutputs.set(n.id, last);
|
|
2173
2130
|
}
|
|
2174
2131
|
}
|
|
2175
|
-
|
|
2176
|
-
}
|
|
2177
|
-
const functionName = node.input.function;
|
|
2178
|
-
if (functionName && ctx.functions[functionName]) {
|
|
2179
|
-
const fn = ctx.functions[functionName];
|
|
2180
|
-
const prevFunctions = ctx.functions;
|
|
2181
|
-
const prevInput = ctx.input;
|
|
2182
|
-
ctx.input = resolveValue(args, ctx);
|
|
2183
|
-
let lastOutput = void 0;
|
|
2184
|
-
for (const bodyNode of fn.workflow.nodes) {
|
|
2185
|
-
lastOutput = await executeNode(bodyNode, ctx);
|
|
2186
|
-
}
|
|
2187
|
-
ctx.input = prevInput;
|
|
2188
|
-
return lastOutput;
|
|
2189
|
-
}
|
|
2190
|
-
throw new Error(`call node: tool "${toolName ?? functionName}" not found`);
|
|
2191
|
-
}
|
|
2192
|
-
async function executeHttp(node, ctx) {
|
|
2193
|
-
const input = resolveValue(node.input, ctx);
|
|
2194
|
-
const url = input.url;
|
|
2195
|
-
if (!url) throw new Error('http node requires "url" field');
|
|
2196
|
-
const controller = new AbortController();
|
|
2197
|
-
const timeout = input.timeout ?? 3e4;
|
|
2198
|
-
const timer = setTimeout(() => controller.abort(), timeout);
|
|
2199
|
-
try {
|
|
2200
|
-
const fetchInit = {
|
|
2201
|
-
method: input.method ?? "GET",
|
|
2202
|
-
headers: input.headers ?? {},
|
|
2203
|
-
signal: controller.signal
|
|
2204
|
-
};
|
|
2205
|
-
if (input.body && fetchInit.method !== "GET") {
|
|
2206
|
-
fetchInit.body = JSON.stringify(input.body);
|
|
2132
|
+
return last;
|
|
2207
2133
|
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2134
|
+
case "call": {
|
|
2135
|
+
const toolName = input.tool;
|
|
2136
|
+
const args = resolveValue(input.args ?? {}, ctx);
|
|
2137
|
+
if (ctx.toolRegistry.has(toolName)) {
|
|
2138
|
+
const t = ctx.toolRegistry.get(toolName);
|
|
2139
|
+
const parsed = t.parameters?.parse ? t.parameters.parse(args) : args;
|
|
2140
|
+
return await t.execute(parsed, { toolCallId: node.id });
|
|
2141
|
+
}
|
|
2142
|
+
throw new Error(`call node: tool "${toolName}" not found`);
|
|
2143
|
+
}
|
|
2144
|
+
case "http": {
|
|
2145
|
+
const opts = resolveValue(input, ctx);
|
|
2146
|
+
const url = opts.url;
|
|
2147
|
+
if (!url) throw new Error('http node requires "url"');
|
|
2148
|
+
const controller = new AbortController();
|
|
2149
|
+
const timer = setTimeout(() => controller.abort(), opts.timeout ?? 3e4);
|
|
2150
|
+
try {
|
|
2151
|
+
const res = await fetch(url, {
|
|
2152
|
+
method: opts.method ?? "GET",
|
|
2153
|
+
headers: opts.headers ?? {},
|
|
2154
|
+
body: opts.method !== "GET" ? JSON.stringify(opts.body) : void 0,
|
|
2155
|
+
signal: controller.signal
|
|
2156
|
+
});
|
|
2157
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
2158
|
+
return { status: res.status, body: ct.includes("json") ? await res.json() : await res.text() };
|
|
2159
|
+
} finally {
|
|
2160
|
+
clearTimeout(timer);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
default:
|
|
2164
|
+
throw new Error(`Unknown node type: "${nodeType}"`);
|
|
2234
2165
|
}
|
|
2235
|
-
return executor(node, ctx);
|
|
2236
2166
|
}
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
{
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2167
|
+
function runWorkflow(opts = {}) {
|
|
2168
|
+
const toolRegistry = /* @__PURE__ */ new Map();
|
|
2169
|
+
if (opts.tools) {
|
|
2170
|
+
for (const [key, t] of Object.entries(opts.tools)) {
|
|
2171
|
+
toolRegistry.set(key, t);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
return tool({
|
|
2175
|
+
description: "Execute a multi-step workflow. Supports eval, set, get, if, while, call, http nodes. Use $var.x for variables, $nodes.id.output for previous node results, $input.x for input parameters. Call nodes invoke registered tools.",
|
|
2176
|
+
inputSchema: z.object({
|
|
2177
|
+
goal: z.string().describe("What the workflow should accomplish"),
|
|
2178
|
+
nodes: z.array(z.object({
|
|
2179
|
+
id: z.string(),
|
|
2180
|
+
tool: z.string(),
|
|
2181
|
+
input: z.record(z.string(), z.unknown()).optional(),
|
|
2182
|
+
conditions: z.array(z.object({ test: z.any(), body: z.any() })).optional(),
|
|
2183
|
+
body: z.array(z.any()).optional()
|
|
2184
|
+
})).optional().describe("Workflow nodes. Skip this and provide model for LLM to generate from goal.")
|
|
2185
|
+
}),
|
|
2186
|
+
execute: async (input) => {
|
|
2187
|
+
let nodes;
|
|
2188
|
+
if (input.nodes && input.nodes.length > 0) {
|
|
2189
|
+
nodes = input.nodes;
|
|
2190
|
+
} else if (opts.model) {
|
|
2191
|
+
const toolsDesc = Object.entries(opts.tools ?? {}).map(([k, t]) => `- ${k}: ${t.description}`).join("\n");
|
|
2192
|
+
const result = await generateText({
|
|
2193
|
+
model: opts.model,
|
|
2194
|
+
system: [
|
|
2195
|
+
"You are a workflow generator. Given a user goal and available tools, output a workflow JSON.",
|
|
2196
|
+
"",
|
|
2197
|
+
"Available tools:",
|
|
2198
|
+
toolsDesc,
|
|
2199
|
+
"",
|
|
2200
|
+
"Node types: eval (expression), set (variable), get (variable), if (condition), while (loop), call (tool), http (request).",
|
|
2201
|
+
"Reference syntax: $var.name, $nodes.id.output, $nodes.id.output.field, $input.field",
|
|
2202
|
+
"Output ONLY valid JSON. No explanation, no markdown."
|
|
2203
|
+
].filter(Boolean).join("\n"),
|
|
2204
|
+
messages: [{ role: "user", content: input.goal }]
|
|
2205
|
+
});
|
|
2206
|
+
const text2 = result.text.trim();
|
|
2207
|
+
const jsonStart = text2.indexOf("{");
|
|
2208
|
+
const jsonEnd = text2.lastIndexOf("}");
|
|
2209
|
+
if (jsonStart === -1 || jsonEnd === -1) throw new Error("LLM did not return valid JSON");
|
|
2210
|
+
const parsed = JSON.parse(text2.slice(jsonStart, jsonEnd + 1));
|
|
2211
|
+
nodes = parsed.nodes ?? parsed.workflow?.nodes ?? [];
|
|
2212
|
+
if (!Array.isArray(nodes)) throw new Error("Generated workflow has no nodes array");
|
|
2213
|
+
} else {
|
|
2214
|
+
throw new Error('Provide either "nodes" or a "model" to generate the workflow from "goal"');
|
|
2215
|
+
}
|
|
2216
|
+
const ctx = {
|
|
2217
|
+
variables: /* @__PURE__ */ new Map(),
|
|
2218
|
+
nodeOutputs: /* @__PURE__ */ new Map(),
|
|
2219
|
+
stepCount: 0,
|
|
2220
|
+
maxSteps: opts.maxSteps ?? 200,
|
|
2221
|
+
toolRegistry
|
|
2222
|
+
};
|
|
2223
|
+
let lastOutput;
|
|
2224
|
+
for (const n of nodes) {
|
|
2225
|
+
const output = await executeNode(n, ctx);
|
|
2226
|
+
ctx.nodeOutputs.set(n.id, output);
|
|
2227
|
+
lastOutput = output;
|
|
2228
|
+
}
|
|
2229
|
+
return { result: lastOutput, nodeOutputs: Object.fromEntries(ctx.nodeOutputs) };
|
|
2275
2230
|
}
|
|
2276
|
-
|
|
2231
|
+
});
|
|
2277
2232
|
}
|
|
2278
2233
|
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
- set: assign a variable. input: { name, value }
|
|
2282
|
-
- get: read a variable. input: { name }
|
|
2283
|
-
- if: conditional branch. input: {}, conditions: [{ test, body }]
|
|
2284
|
-
- while: loop. input: { condition }, body: [nodes]
|
|
2285
|
-
- call: call a registered tool. input: { tool, args }
|
|
2286
|
-
- http: HTTP request. input: { url, method?, headers?, body? }
|
|
2287
|
-
|
|
2288
|
-
Reference syntax:
|
|
2289
|
-
- $var.name - read a variable
|
|
2290
|
-
- $nodes.id.output - output of a previous node
|
|
2291
|
-
- $nodes.id.output.field - specific field of a node's output
|
|
2292
|
-
- $input.field - workflow input parameter
|
|
2234
|
+
// postgres/client.ts
|
|
2235
|
+
import postgresFactory from "postgres";
|
|
2293
2236
|
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
}
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
try {
|
|
2310
|
-
const workflow2 = JSON.parse(jsonStr);
|
|
2311
|
-
if (!workflow2.nodes || !Array.isArray(workflow2.nodes)) {
|
|
2312
|
-
throw new Error("Generated workflow has no nodes array");
|
|
2313
|
-
}
|
|
2314
|
-
return workflow2;
|
|
2315
|
-
} catch (err) {
|
|
2316
|
-
if (err instanceof SyntaxError) {
|
|
2317
|
-
throw new Error(`Failed to parse LLM output as JSON: ${err.message}`);
|
|
2237
|
+
// postgres/schema/sql.ts
|
|
2238
|
+
var SQL = class {
|
|
2239
|
+
strings;
|
|
2240
|
+
values;
|
|
2241
|
+
constructor(strings, values) {
|
|
2242
|
+
this.strings = strings;
|
|
2243
|
+
this.values = values;
|
|
2244
|
+
}
|
|
2245
|
+
toSQL() {
|
|
2246
|
+
let result = "";
|
|
2247
|
+
for (let i = 0; i < this.strings.length; i++) {
|
|
2248
|
+
result += this.strings[i];
|
|
2249
|
+
if (i < this.values.length) {
|
|
2250
|
+
result += String(this.values[i]);
|
|
2251
|
+
}
|
|
2318
2252
|
}
|
|
2319
|
-
|
|
2253
|
+
return result;
|
|
2320
2254
|
}
|
|
2255
|
+
};
|
|
2256
|
+
function sql(strings, ...values) {
|
|
2257
|
+
return new SQL(strings, values);
|
|
2321
2258
|
}
|
|
2322
2259
|
|
|
2323
|
-
//
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
sseManager: options.sseManager,
|
|
2342
|
-
workflowId: opts?.workflowId
|
|
2343
|
-
};
|
|
2344
|
-
let lastOutput = void 0;
|
|
2345
|
-
for (const node of workflow2.nodes) {
|
|
2346
|
-
ctx.stepCount++;
|
|
2347
|
-
if (ctx.stepCount > ctx.maxSteps) {
|
|
2348
|
-
throw new Error(`Step limit exceeded (${ctx.maxSteps})`);
|
|
2349
|
-
}
|
|
2350
|
-
options.sseManager?.send(ctx.workflowId ?? "", { event: "node-start", data: { nodeId: node.id, tool: node.tool, input: node.input } });
|
|
2351
|
-
const output = await executeNode(node, ctx);
|
|
2352
|
-
ctx.nodeOutputs.set(node.id, output);
|
|
2353
|
-
lastOutput = output;
|
|
2354
|
-
options.sseManager?.send(ctx.workflowId ?? "", { event: "node-end", data: { nodeId: node.id, output } });
|
|
2355
|
-
}
|
|
2356
|
-
return lastOutput;
|
|
2357
|
-
}
|
|
2358
|
-
async function runAsync(workflowId, workflow2, opts) {
|
|
2359
|
-
const state = {
|
|
2360
|
-
workflowId,
|
|
2361
|
-
status: "running",
|
|
2362
|
-
goal: workflow2.name ?? "",
|
|
2363
|
-
startTime: Date.now()
|
|
2364
|
-
};
|
|
2365
|
-
states.set(workflowId, state);
|
|
2366
|
-
const sse = options.sseManager;
|
|
2367
|
-
sse?.send(workflowId, { event: "workflow-start", data: { workflowId, goal: state.goal } });
|
|
2368
|
-
try {
|
|
2369
|
-
const result = await execute(workflow2, { ...opts, workflowId });
|
|
2370
|
-
state.status = "completed";
|
|
2371
|
-
state.result = result;
|
|
2372
|
-
state.endTime = Date.now();
|
|
2373
|
-
sse?.send(workflowId, { event: "complete", data: { result, duration: state.endTime - state.startTime } });
|
|
2374
|
-
} catch (err) {
|
|
2375
|
-
state.status = "error";
|
|
2376
|
-
state.error = err instanceof Error ? err.message : String(err);
|
|
2377
|
-
state.endTime = Date.now();
|
|
2378
|
-
sse?.send(workflowId, { event: "error", data: { error: state.error } });
|
|
2379
|
-
} finally {
|
|
2380
|
-
sse?.close(workflowId);
|
|
2381
|
-
}
|
|
2260
|
+
// postgres/schema/columns.ts
|
|
2261
|
+
var ColumnBuilder = class {
|
|
2262
|
+
name;
|
|
2263
|
+
sqlType;
|
|
2264
|
+
isPrimaryKey = false;
|
|
2265
|
+
isNullable = true;
|
|
2266
|
+
isUnique = false;
|
|
2267
|
+
isAutoGenerate = false;
|
|
2268
|
+
defaultExpr = null;
|
|
2269
|
+
ref = null;
|
|
2270
|
+
constructor(name, sqlType) {
|
|
2271
|
+
this.name = name;
|
|
2272
|
+
this.sqlType = sqlType;
|
|
2273
|
+
}
|
|
2274
|
+
primaryKey() {
|
|
2275
|
+
this.isPrimaryKey = true;
|
|
2276
|
+
this.isNullable = false;
|
|
2277
|
+
return this;
|
|
2382
2278
|
}
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
}
|
|
2387
|
-
return generateWorkflow(goal, options.tools, async (prompt) => {
|
|
2388
|
-
const result = await generateText({
|
|
2389
|
-
model: options.model,
|
|
2390
|
-
system: prompt.system,
|
|
2391
|
-
messages: prompt.messages
|
|
2392
|
-
});
|
|
2393
|
-
return { text: result.text };
|
|
2394
|
-
});
|
|
2279
|
+
notNull() {
|
|
2280
|
+
this.isNullable = false;
|
|
2281
|
+
return this;
|
|
2395
2282
|
}
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2283
|
+
nullable() {
|
|
2284
|
+
this.isNullable = true;
|
|
2285
|
+
return this;
|
|
2286
|
+
}
|
|
2287
|
+
default(expr) {
|
|
2288
|
+
if (expr instanceof SQL) {
|
|
2289
|
+
this.defaultExpr = expr.toSQL();
|
|
2290
|
+
} else if (typeof expr === "string") {
|
|
2291
|
+
this.defaultExpr = `'${expr.replace(/'/g, "''")}'`;
|
|
2292
|
+
} else {
|
|
2293
|
+
this.defaultExpr = String(expr);
|
|
2402
2294
|
}
|
|
2403
|
-
|
|
2295
|
+
return this;
|
|
2296
|
+
}
|
|
2297
|
+
unique() {
|
|
2298
|
+
this.isUnique = true;
|
|
2299
|
+
return this;
|
|
2300
|
+
}
|
|
2301
|
+
references(table, column = "id", onDelete) {
|
|
2302
|
+
this.ref = { table, column, onDelete };
|
|
2303
|
+
return this;
|
|
2304
|
+
}
|
|
2305
|
+
};
|
|
2306
|
+
function col(name, sqlType) {
|
|
2307
|
+
return new ColumnBuilder(name, sqlType);
|
|
2404
2308
|
}
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
return `event: ${event}
|
|
2410
|
-
data: ${JSON.stringify(data)}
|
|
2411
|
-
|
|
2412
|
-
`;
|
|
2309
|
+
function serial(name) {
|
|
2310
|
+
const c = col(name, "SERIAL");
|
|
2311
|
+
c.isAutoGenerate = true;
|
|
2312
|
+
return c;
|
|
2413
2313
|
}
|
|
2414
|
-
function
|
|
2415
|
-
return
|
|
2416
|
-
|
|
2417
|
-
`;
|
|
2314
|
+
function uuid(name) {
|
|
2315
|
+
return col(name, "UUID");
|
|
2418
2316
|
}
|
|
2419
|
-
function
|
|
2420
|
-
return
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
}
|
|
2448
|
-
|
|
2317
|
+
function text(name) {
|
|
2318
|
+
return col(name, "TEXT");
|
|
2319
|
+
}
|
|
2320
|
+
function integer(name) {
|
|
2321
|
+
return col(name, "INTEGER");
|
|
2322
|
+
}
|
|
2323
|
+
function boolean_(name) {
|
|
2324
|
+
return col(name, "BOOLEAN");
|
|
2325
|
+
}
|
|
2326
|
+
function timestamptz(name) {
|
|
2327
|
+
return col(name, "TIMESTAMPTZ");
|
|
2328
|
+
}
|
|
2329
|
+
function jsonb(name) {
|
|
2330
|
+
return col(name, "JSONB");
|
|
2331
|
+
}
|
|
2332
|
+
function textArray(name) {
|
|
2333
|
+
return col(name, "TEXT[]");
|
|
2334
|
+
}
|
|
2335
|
+
function vector(name, dims) {
|
|
2336
|
+
return col(name, `vector(${dims})`);
|
|
2337
|
+
}
|
|
2338
|
+
function toDDL(col2) {
|
|
2339
|
+
const parts = [`"${col2.name}"`, col2.sqlType];
|
|
2340
|
+
if (col2.isPrimaryKey) parts.push("PRIMARY KEY");
|
|
2341
|
+
if (!col2.isPrimaryKey && !col2.isNullable) parts.push("NOT NULL");
|
|
2342
|
+
if (col2.isUnique) parts.push("UNIQUE");
|
|
2343
|
+
if (col2.defaultExpr) parts.push(`DEFAULT ${col2.defaultExpr}`);
|
|
2344
|
+
if (col2.ref) {
|
|
2345
|
+
parts.push(`REFERENCES "${col2.ref.table}"("${col2.ref.column}")`);
|
|
2346
|
+
if (col2.ref.onDelete) parts.push(`ON DELETE ${col2.ref.onDelete.toUpperCase()}`);
|
|
2347
|
+
}
|
|
2348
|
+
return parts.join(" ");
|
|
2449
2349
|
}
|
|
2450
2350
|
|
|
2451
|
-
//
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
state.controller = controller;
|
|
2465
|
-
streams.set(workflowId, state);
|
|
2466
|
-
for (const chunk of state.buffer) {
|
|
2467
|
-
try {
|
|
2468
|
-
controller.enqueue(chunk);
|
|
2469
|
-
} catch {
|
|
2470
|
-
break;
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
state.buffer = [];
|
|
2474
|
-
},
|
|
2475
|
-
cancel() {
|
|
2476
|
-
state.closed = true;
|
|
2477
|
-
streams.delete(workflowId);
|
|
2478
|
-
}
|
|
2479
|
-
});
|
|
2480
|
-
return stream;
|
|
2351
|
+
// postgres/schema/table.ts
|
|
2352
|
+
var Table = class {
|
|
2353
|
+
tableName;
|
|
2354
|
+
columns;
|
|
2355
|
+
colEntries;
|
|
2356
|
+
constructor(tableName, builders) {
|
|
2357
|
+
this.tableName = tableName;
|
|
2358
|
+
this.columns = Object.values(builders);
|
|
2359
|
+
this.colEntries = Object.entries(builders).map(([prop, col2]) => ({
|
|
2360
|
+
prop,
|
|
2361
|
+
db: col2.name,
|
|
2362
|
+
auto: col2.isAutoGenerate
|
|
2363
|
+
}));
|
|
2481
2364
|
}
|
|
2482
|
-
|
|
2483
|
-
const
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2365
|
+
async create(sql2) {
|
|
2366
|
+
const colDDL = this.columns.map(toDDL);
|
|
2367
|
+
const ddl = `CREATE TABLE IF NOT EXISTS "${this.tableName}" (
|
|
2368
|
+
${colDDL.join(",\n ")}
|
|
2369
|
+
)`;
|
|
2370
|
+
await sql2.unsafe(ddl);
|
|
2371
|
+
}
|
|
2372
|
+
async drop(sql2, opts) {
|
|
2373
|
+
const cascade = opts?.cascade ? " CASCADE" : "";
|
|
2374
|
+
await sql2.unsafe(`DROP TABLE IF EXISTS "${this.tableName}"${cascade}`);
|
|
2375
|
+
}
|
|
2376
|
+
async createIndex(sql2, columns, opts) {
|
|
2377
|
+
const cols = Array.isArray(columns) ? columns : [columns];
|
|
2378
|
+
const name = `"${this.tableName}_${cols.join("_")}${opts?.unique ? "_uidx" : "_idx"}"`;
|
|
2379
|
+
const unique = opts?.unique ? "UNIQUE" : "";
|
|
2380
|
+
const using = opts?.type ? `USING ${opts.type.toUpperCase()}` : "";
|
|
2381
|
+
const colList = cols.map((c) => opts?.desc ? `"${c}" DESC` : `"${c}"`).join(", ");
|
|
2382
|
+
const operator = opts?.operator ? ` ${opts.operator}` : "";
|
|
2383
|
+
const ddl = `CREATE ${unique} INDEX IF NOT EXISTS ${name} ON "${this.tableName}" ${using} (${colList}${operator})`.replace(/\s+/g, " ");
|
|
2384
|
+
await sql2.unsafe(ddl);
|
|
2385
|
+
}
|
|
2386
|
+
async createUniqueIndex(sql2, columns) {
|
|
2387
|
+
await this.createIndex(sql2, columns, { unique: true });
|
|
2388
|
+
}
|
|
2389
|
+
// --- CRUD ---
|
|
2390
|
+
async insert(sql2, data) {
|
|
2391
|
+
const filtered = {};
|
|
2392
|
+
for (const { prop, db, auto } of this.colEntries) {
|
|
2393
|
+
if (auto) continue;
|
|
2394
|
+
if (prop in data) {
|
|
2395
|
+
filtered[db] = data[prop];
|
|
2492
2396
|
}
|
|
2493
|
-
} else {
|
|
2494
|
-
state.buffer.push(chunk);
|
|
2495
2397
|
}
|
|
2398
|
+
const [row] = await sql2`
|
|
2399
|
+
INSERT INTO ${sql2(this.tableName)} ${sql2(filtered)} RETURNING *
|
|
2400
|
+
`;
|
|
2401
|
+
return row;
|
|
2496
2402
|
}
|
|
2497
|
-
|
|
2498
|
-
const
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
state.controller?.close();
|
|
2504
|
-
} catch {
|
|
2505
|
-
}
|
|
2403
|
+
async findById(sql2, id2) {
|
|
2404
|
+
const [row] = await sql2`
|
|
2405
|
+
SELECT * FROM ${sql2(this.tableName)}
|
|
2406
|
+
WHERE ${sql2("id")} = ${id2} LIMIT 1
|
|
2407
|
+
`;
|
|
2408
|
+
return row ?? void 0;
|
|
2506
2409
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2410
|
+
async find(sql2, where, opts) {
|
|
2411
|
+
const conditions = [];
|
|
2412
|
+
const values = [];
|
|
2413
|
+
for (const [prop, value] of Object.entries(where || {})) {
|
|
2414
|
+
if (value === void 0) continue;
|
|
2415
|
+
const entry = this.colEntries.find((e) => e.prop === prop);
|
|
2416
|
+
const db = entry ? entry.db : prop;
|
|
2417
|
+
conditions.push(`"${db}" = $${conditions.length + 1}`);
|
|
2418
|
+
values.push(value);
|
|
2419
|
+
}
|
|
2420
|
+
let query = `SELECT * FROM "${this.tableName}"`;
|
|
2421
|
+
if (conditions.length > 0) query += ` WHERE ${conditions.join(" AND ")}`;
|
|
2422
|
+
if (opts?.orderBy) {
|
|
2423
|
+
const orders = Object.entries(opts.orderBy).map(([prop, dir]) => {
|
|
2424
|
+
const entry = this.colEntries.find((e) => e.prop === prop);
|
|
2425
|
+
return `"${entry?.db || prop}" ${dir.toUpperCase()}`;
|
|
2426
|
+
}).join(", ");
|
|
2427
|
+
query += ` ORDER BY ${orders}`;
|
|
2428
|
+
}
|
|
2429
|
+
if (opts?.limit) query += ` LIMIT ${opts.limit}`;
|
|
2430
|
+
if (opts?.offset) query += ` OFFSET ${opts.offset}`;
|
|
2431
|
+
if (conditions.length > 0 || opts?.orderBy || opts?.limit !== void 0 || opts?.offset !== void 0) {
|
|
2432
|
+
const rows2 = await sql2.unsafe(query, values);
|
|
2433
|
+
return rows2;
|
|
2434
|
+
}
|
|
2435
|
+
const rows = await sql2`SELECT * FROM ${sql2(this.tableName)}`;
|
|
2436
|
+
return rows;
|
|
2437
|
+
}
|
|
2438
|
+
async update(sql2, where, data) {
|
|
2439
|
+
const sets = [];
|
|
2440
|
+
const setValues = [];
|
|
2441
|
+
for (const { prop, db } of this.colEntries) {
|
|
2442
|
+
if (prop in data && data[prop] !== void 0) {
|
|
2443
|
+
const val = data[prop];
|
|
2444
|
+
if (val instanceof SQL) {
|
|
2445
|
+
sets.push(`"${db}" = ${val.toSQL()}`);
|
|
2446
|
+
} else {
|
|
2447
|
+
sets.push(`"${db}" = $${sets.length + 1}`);
|
|
2448
|
+
setValues.push(val);
|
|
2449
|
+
}
|
|
2522
2450
|
}
|
|
2523
|
-
});
|
|
2524
|
-
});
|
|
2525
|
-
r.post("/", async (req, ctx) => {
|
|
2526
|
-
const options = await handler(req, ctx);
|
|
2527
|
-
const engine = createWorkflowEngine({
|
|
2528
|
-
tools: options.tools,
|
|
2529
|
-
model: options.model,
|
|
2530
|
-
sseManager: options.stream ? sseManager : void 0
|
|
2531
|
-
});
|
|
2532
|
-
const body = await req.json();
|
|
2533
|
-
let wf;
|
|
2534
|
-
if (body.goal && options.model) {
|
|
2535
|
-
wf = await engine.generateWorkflow(body.goal);
|
|
2536
|
-
} else if (body.workflow) {
|
|
2537
|
-
wf = body.workflow;
|
|
2538
|
-
} else if (body.nodes) {
|
|
2539
|
-
wf = { nodes: body.nodes };
|
|
2540
|
-
} else {
|
|
2541
|
-
return Response.json(
|
|
2542
|
-
{ error: 'Provide "goal" (with model) or "workflow"/"nodes"' },
|
|
2543
|
-
{ status: 400 }
|
|
2544
|
-
);
|
|
2545
2451
|
}
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2452
|
+
const values = [...setValues];
|
|
2453
|
+
const wConditions = [];
|
|
2454
|
+
for (const [prop, value] of Object.entries(where)) {
|
|
2455
|
+
if (value === void 0) continue;
|
|
2456
|
+
const entry = this.colEntries.find((e) => e.prop === prop);
|
|
2457
|
+
const db = entry ? entry.db : prop;
|
|
2458
|
+
wConditions.push(`"${db}" = $${values.length + 1}`);
|
|
2459
|
+
values.push(value);
|
|
2550
2460
|
}
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2461
|
+
if (sets.length === 0 || wConditions.length === 0) return void 0;
|
|
2462
|
+
const query = `UPDATE "${this.tableName}" SET ${sets.join(", ")} WHERE ${wConditions.join(" AND ")} RETURNING *`;
|
|
2463
|
+
const rows = await sql2.unsafe(query, values);
|
|
2464
|
+
return rows[0] ?? void 0;
|
|
2465
|
+
}
|
|
2466
|
+
async delete(sql2, where) {
|
|
2467
|
+
const conditions = [];
|
|
2468
|
+
const values = [];
|
|
2469
|
+
for (const [prop, value] of Object.entries(where)) {
|
|
2470
|
+
if (value === void 0) continue;
|
|
2471
|
+
const entry = this.colEntries.find((e) => e.prop === prop);
|
|
2472
|
+
const db = entry ? entry.db : prop;
|
|
2473
|
+
conditions.push(`"${db}" = $${conditions.length + 1}`);
|
|
2474
|
+
values.push(value);
|
|
2475
|
+
}
|
|
2476
|
+
if (conditions.length === 0) return false;
|
|
2477
|
+
const query = `DELETE FROM "${this.tableName}" WHERE ${conditions.join(" AND ")} RETURNING 1`;
|
|
2478
|
+
const rows = await sql2.unsafe(query, values);
|
|
2479
|
+
return rows.length > 0;
|
|
2480
|
+
}
|
|
2481
|
+
};
|
|
2482
|
+
var BoundTable = class {
|
|
2483
|
+
inner;
|
|
2484
|
+
sql;
|
|
2485
|
+
constructor(sql2, tableName, builders) {
|
|
2486
|
+
this.inner = new Table(tableName, builders);
|
|
2487
|
+
this.sql = sql2;
|
|
2488
|
+
}
|
|
2489
|
+
async create() {
|
|
2490
|
+
await this.inner.create(this.sql);
|
|
2491
|
+
}
|
|
2492
|
+
async drop(opts) {
|
|
2493
|
+
await this.inner.drop(this.sql, opts);
|
|
2494
|
+
}
|
|
2495
|
+
async createIndex(columns, opts) {
|
|
2496
|
+
await this.inner.createIndex(this.sql, columns, opts);
|
|
2497
|
+
}
|
|
2498
|
+
async createUniqueIndex(columns) {
|
|
2499
|
+
await this.inner.createUniqueIndex(this.sql, columns);
|
|
2500
|
+
}
|
|
2501
|
+
async insert(data) {
|
|
2502
|
+
return await this.inner.insert(this.sql, data);
|
|
2503
|
+
}
|
|
2504
|
+
async findById(id2) {
|
|
2505
|
+
return await this.inner.findById(this.sql, id2);
|
|
2506
|
+
}
|
|
2507
|
+
async find(where, opts) {
|
|
2508
|
+
return await this.inner.find(this.sql, where, opts);
|
|
2509
|
+
}
|
|
2510
|
+
async update(where, data) {
|
|
2511
|
+
return await this.inner.update(this.sql, where, data);
|
|
2512
|
+
}
|
|
2513
|
+
async delete(where) {
|
|
2514
|
+
return await this.inner.delete(this.sql, where);
|
|
2515
|
+
}
|
|
2516
|
+
};
|
|
2517
|
+
function pgTable(tableName, builders) {
|
|
2518
|
+
return new Table(tableName, builders);
|
|
2555
2519
|
}
|
|
2556
2520
|
|
|
2557
2521
|
// postgres/client.ts
|
|
2558
|
-
import postgresFactory from "postgres";
|
|
2559
2522
|
function postgres(opts) {
|
|
2560
2523
|
const options = typeof opts === "string" ? { connection: opts } : opts ?? {};
|
|
2561
2524
|
const connection = options.connection ?? process.env.DATABASE_URL;
|
|
@@ -2581,6 +2544,9 @@ function postgres(opts) {
|
|
|
2581
2544
|
return next(req, ctx);
|
|
2582
2545
|
});
|
|
2583
2546
|
mw.sql = sql2;
|
|
2547
|
+
mw.table = ((tableName, builders) => {
|
|
2548
|
+
return new BoundTable(sql2, tableName, builders);
|
|
2549
|
+
});
|
|
2584
2550
|
mw.migrate = async () => {
|
|
2585
2551
|
};
|
|
2586
2552
|
mw.transaction = (async (fn) => {
|
|
@@ -2608,66 +2574,62 @@ var PgModule = class {
|
|
|
2608
2574
|
// user/client.ts
|
|
2609
2575
|
import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
|
|
2610
2576
|
import jwt2 from "jsonwebtoken";
|
|
2611
|
-
import { z } from "zod";
|
|
2577
|
+
import { z as z2 } from "zod";
|
|
2612
2578
|
|
|
2613
2579
|
// user/migrate.ts
|
|
2614
2580
|
async function migrate(opts) {
|
|
2615
|
-
const { pg, usersTable, oauth2 } = opts;
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
`);
|
|
2581
|
+
const { pg, usersTable: name, oauth2 } = opts;
|
|
2582
|
+
const users = pgTable(name, {
|
|
2583
|
+
id: serial("id").primaryKey(),
|
|
2584
|
+
email: text("email").unique().notNull(),
|
|
2585
|
+
password: text("password").notNull(),
|
|
2586
|
+
name: text("name").notNull(),
|
|
2587
|
+
role: text("role").default("user"),
|
|
2588
|
+
created_at: timestamptz("created_at").default(sql`NOW()`),
|
|
2589
|
+
updated_at: timestamptz("updated_at").default(sql`NOW()`)
|
|
2590
|
+
});
|
|
2591
|
+
await users.create(pg.sql);
|
|
2627
2592
|
if (!oauth2) return;
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
"revoked" BOOLEAN NOT NULL DEFAULT FALSE
|
|
2662
|
-
)
|
|
2663
|
-
`);
|
|
2593
|
+
const clients2 = pgTable("_oauth2_clients", {
|
|
2594
|
+
id: serial("id").primaryKey(),
|
|
2595
|
+
name: text("name").notNull(),
|
|
2596
|
+
client_id: text("client_id").unique().notNull(),
|
|
2597
|
+
client_secret: text("client_secret").notNull(),
|
|
2598
|
+
redirect_uris: textArray("redirect_uris").notNull(),
|
|
2599
|
+
scopes: text("scopes").default(""),
|
|
2600
|
+
created_at: timestamptz("created_at").default(sql`NOW()`)
|
|
2601
|
+
});
|
|
2602
|
+
await clients2.create(pg.sql);
|
|
2603
|
+
const codes = pgTable("_oauth2_codes", {
|
|
2604
|
+
id: serial("id").primaryKey(),
|
|
2605
|
+
code: text("code").unique().notNull(),
|
|
2606
|
+
client_id: text("client_id").notNull(),
|
|
2607
|
+
user_id: integer("user_id").notNull().references(name, "id"),
|
|
2608
|
+
redirect_uri: text("redirect_uri").notNull(),
|
|
2609
|
+
code_challenge: text("code_challenge"),
|
|
2610
|
+
code_challenge_method: text("code_challenge_method"),
|
|
2611
|
+
scope: text("scope"),
|
|
2612
|
+
expires_at: timestamptz("expires_at").notNull(),
|
|
2613
|
+
used: boolean_("used").default(false)
|
|
2614
|
+
});
|
|
2615
|
+
await codes.create(pg.sql);
|
|
2616
|
+
const tokens = pgTable("_oauth2_tokens", {
|
|
2617
|
+
id: serial("id").primaryKey(),
|
|
2618
|
+
token: text("token").unique().notNull(),
|
|
2619
|
+
client_id: text("client_id").notNull(),
|
|
2620
|
+
user_id: integer("user_id").references(name, "id"),
|
|
2621
|
+
scope: text("scope"),
|
|
2622
|
+
expires_at: timestamptz("expires_at").notNull(),
|
|
2623
|
+
revoked: boolean_("revoked").default(false)
|
|
2624
|
+
});
|
|
2625
|
+
await tokens.create(pg.sql);
|
|
2664
2626
|
}
|
|
2665
2627
|
|
|
2666
2628
|
// user/oauth2.ts
|
|
2667
|
-
import
|
|
2629
|
+
import crypto from "node:crypto";
|
|
2668
2630
|
import jwt from "jsonwebtoken";
|
|
2669
2631
|
function createOAuth2Server(deps) {
|
|
2670
|
-
const { pg,
|
|
2632
|
+
const { pg, users, jwtSecret, expiresIn } = deps;
|
|
2671
2633
|
async function getClient(clientId) {
|
|
2672
2634
|
const [row] = await pg.sql`
|
|
2673
2635
|
SELECT * FROM "_oauth2_clients" WHERE "client_id" = ${clientId} LIMIT 1
|
|
@@ -2683,8 +2645,8 @@ function createOAuth2Server(deps) {
|
|
|
2683
2645
|
};
|
|
2684
2646
|
}
|
|
2685
2647
|
async function registerClient(data) {
|
|
2686
|
-
const clientId =
|
|
2687
|
-
const clientSecret =
|
|
2648
|
+
const clientId = crypto.randomUUID();
|
|
2649
|
+
const clientSecret = crypto.randomBytes(32).toString("hex");
|
|
2688
2650
|
const [row] = await pg.sql`
|
|
2689
2651
|
INSERT INTO "_oauth2_clients" ("name", "client_id", "client_secret", "redirect_uris")
|
|
2690
2652
|
VALUES (${data.name}, ${clientId}, ${clientSecret}, ${pg.sql.array(data.redirectUris)})
|
|
@@ -2832,7 +2794,7 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
2832
2794
|
const loc2 = `${redirectUri}?error=access_denied${state ? `&state=${state}` : ""}`;
|
|
2833
2795
|
return Response.redirect(loc2, 302);
|
|
2834
2796
|
}
|
|
2835
|
-
const code =
|
|
2797
|
+
const code = crypto.randomUUID();
|
|
2836
2798
|
const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
|
|
2837
2799
|
await pg.sql`
|
|
2838
2800
|
INSERT INTO "_oauth2_codes" ("code", "client_id", "user_id", "redirect_uri", "code_challenge", "code_challenge_method", "scope", "expires_at")
|
|
@@ -2897,16 +2859,14 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
2897
2859
|
if (stored.code_challenge_method === "plain") {
|
|
2898
2860
|
expected = codeVerifier;
|
|
2899
2861
|
} else {
|
|
2900
|
-
expected =
|
|
2862
|
+
expected = crypto.createHash("sha256").update(codeVerifier).digest().toString("base64url");
|
|
2901
2863
|
}
|
|
2902
2864
|
if (expected !== stored.code_challenge) {
|
|
2903
2865
|
return Response.json({ error: "invalid_grant", error_description: "code_verifier mismatch" }, { status: 400 });
|
|
2904
2866
|
}
|
|
2905
2867
|
}
|
|
2906
2868
|
await pg.sql`UPDATE "_oauth2_codes" SET "used" = TRUE WHERE "id" = ${stored.id}`;
|
|
2907
|
-
const
|
|
2908
|
-
SELECT * FROM ${pg.sql(usersTable)} WHERE "id" = ${stored.user_id} LIMIT 1
|
|
2909
|
-
`;
|
|
2869
|
+
const user2 = await users.findById(stored.user_id);
|
|
2910
2870
|
if (!user2) {
|
|
2911
2871
|
return Response.json({ error: "invalid_grant" }, { status: 400 });
|
|
2912
2872
|
}
|
|
@@ -2916,7 +2876,7 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
2916
2876
|
jwtSecret,
|
|
2917
2877
|
{ expiresIn }
|
|
2918
2878
|
);
|
|
2919
|
-
const refreshToken =
|
|
2879
|
+
const refreshToken = crypto.randomUUID();
|
|
2920
2880
|
const refreshExpires = new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3);
|
|
2921
2881
|
await pg.sql`
|
|
2922
2882
|
INSERT INTO "_oauth2_tokens" ("token", "client_id", "user_id", "scope", "expires_at")
|
|
@@ -2954,14 +2914,14 @@ h2{color:#dc2626}.desc{color:#555}</style>
|
|
|
2954
2914
|
}
|
|
2955
2915
|
|
|
2956
2916
|
// user/client.ts
|
|
2957
|
-
var RegisterSchema =
|
|
2958
|
-
email:
|
|
2959
|
-
password:
|
|
2960
|
-
name:
|
|
2917
|
+
var RegisterSchema = z2.object({
|
|
2918
|
+
email: z2.string().email(),
|
|
2919
|
+
password: z2.string().min(6),
|
|
2920
|
+
name: z2.string().min(1)
|
|
2961
2921
|
});
|
|
2962
|
-
var LoginSchema =
|
|
2963
|
-
email:
|
|
2964
|
-
password:
|
|
2922
|
+
var LoginSchema = z2.object({
|
|
2923
|
+
email: z2.string().email(),
|
|
2924
|
+
password: z2.string().min(1)
|
|
2965
2925
|
});
|
|
2966
2926
|
function hashPassword(password) {
|
|
2967
2927
|
const salt = randomBytes(16).toString("hex");
|
|
@@ -2981,9 +2941,18 @@ function user(options) {
|
|
|
2981
2941
|
const expiresIn = options.expiresIn ?? "24h";
|
|
2982
2942
|
const oauth2Enabled = options.oauth2?.server ?? false;
|
|
2983
2943
|
const base = new PgModule(pg);
|
|
2944
|
+
const users = pg.table(table, {
|
|
2945
|
+
id: serial("id").primaryKey(),
|
|
2946
|
+
email: text("email").unique().notNull(),
|
|
2947
|
+
password: text("password").notNull(),
|
|
2948
|
+
name: text("name").notNull(),
|
|
2949
|
+
role: text("role").default("user"),
|
|
2950
|
+
created_at: timestamptz("created_at").default(sql`NOW()`),
|
|
2951
|
+
updated_at: timestamptz("updated_at").default(sql`NOW()`)
|
|
2952
|
+
});
|
|
2984
2953
|
let oauth2 = null;
|
|
2985
2954
|
if (oauth2Enabled) {
|
|
2986
|
-
oauth2 = createOAuth2Server({ pg,
|
|
2955
|
+
oauth2 = createOAuth2Server({ pg, users, jwtSecret: secret, expiresIn });
|
|
2987
2956
|
}
|
|
2988
2957
|
async function migrate6() {
|
|
2989
2958
|
await migrate({ pg, usersTable: table, oauth2: oauth2Enabled });
|
|
@@ -3000,12 +2969,11 @@ function user(options) {
|
|
|
3000
2969
|
return user2;
|
|
3001
2970
|
}
|
|
3002
2971
|
async function findByEmail(email) {
|
|
3003
|
-
const
|
|
3004
|
-
return
|
|
2972
|
+
const rows = await users.find({ email });
|
|
2973
|
+
return rows[0];
|
|
3005
2974
|
}
|
|
3006
2975
|
async function findById(id2) {
|
|
3007
|
-
|
|
3008
|
-
return row;
|
|
2976
|
+
return await users.findById(id2);
|
|
3009
2977
|
}
|
|
3010
2978
|
async function register(data) {
|
|
3011
2979
|
const { email, password, name } = RegisterSchema.parse(data);
|
|
@@ -3016,16 +2984,15 @@ function user(options) {
|
|
|
3016
2984
|
throw err;
|
|
3017
2985
|
}
|
|
3018
2986
|
const hashed = hashPassword(password);
|
|
3019
|
-
const
|
|
3020
|
-
INSERT INTO ${pg.sql(table)} ("email", "password", "name") VALUES (${email}, ${hashed}, ${name}) RETURNING *
|
|
3021
|
-
`;
|
|
2987
|
+
const row = await users.insert({ email, password: hashed, name });
|
|
3022
2988
|
const userData = row;
|
|
3023
2989
|
const token = signToken(userData);
|
|
3024
2990
|
return { user: stripPassword(userData), token };
|
|
3025
2991
|
}
|
|
3026
2992
|
async function login(data) {
|
|
3027
2993
|
const { email, password } = LoginSchema.parse(data);
|
|
3028
|
-
const
|
|
2994
|
+
const rows = await users.find({ email });
|
|
2995
|
+
const row = rows[0];
|
|
3029
2996
|
if (!row) {
|
|
3030
2997
|
const err = new Error("Invalid email or password");
|
|
3031
2998
|
err.status = 401;
|
|
@@ -3074,7 +3041,7 @@ function user(options) {
|
|
|
3074
3041
|
const result = await register(body);
|
|
3075
3042
|
return Response.json(result, { status: 201 });
|
|
3076
3043
|
} catch (err) {
|
|
3077
|
-
if (err instanceof
|
|
3044
|
+
if (err instanceof z2.ZodError) {
|
|
3078
3045
|
return Response.json({ error: "Validation failed", issues: err.issues }, { status: 400 });
|
|
3079
3046
|
}
|
|
3080
3047
|
const status = err.status ?? 500;
|
|
@@ -3089,7 +3056,7 @@ function user(options) {
|
|
|
3089
3056
|
res.headers.set("Set-Cookie", `session=${result.token}; HttpOnly; SameSite=Lax; Path=/`);
|
|
3090
3057
|
return res;
|
|
3091
3058
|
} catch (err) {
|
|
3092
|
-
if (err instanceof
|
|
3059
|
+
if (err instanceof z2.ZodError) {
|
|
3093
3060
|
return Response.json({ error: "Validation failed", issues: err.issues }, { status: 400 });
|
|
3094
3061
|
}
|
|
3095
3062
|
const status = err.status ?? 500;
|
|
@@ -3143,7 +3110,7 @@ function redis(opts) {
|
|
|
3143
3110
|
|
|
3144
3111
|
// queue/index.ts
|
|
3145
3112
|
import { Redis as IORedis2 } from "ioredis";
|
|
3146
|
-
import
|
|
3113
|
+
import crypto2 from "node:crypto";
|
|
3147
3114
|
function cronNext(expr, from = /* @__PURE__ */ new Date()) {
|
|
3148
3115
|
const parts = expr.trim().split(/\s+/);
|
|
3149
3116
|
if (parts.length !== 5) throw new Error(`Invalid cron expression "${expr}": expected 5 fields`);
|
|
@@ -3234,7 +3201,7 @@ function queue(opts) {
|
|
|
3234
3201
|
if (job.schedule) {
|
|
3235
3202
|
try {
|
|
3236
3203
|
const nextRun = cronNext(job.schedule);
|
|
3237
|
-
const nextJob = { ...job, id:
|
|
3204
|
+
const nextJob = { ...job, id: crypto2.randomUUID(), runAt: nextRun, createdAt: Date.now() };
|
|
3238
3205
|
redis2.zadd(jobKey, nextRun, JSON.stringify(nextJob)).catch(() => {
|
|
3239
3206
|
});
|
|
3240
3207
|
} catch {
|
|
@@ -3252,7 +3219,7 @@ function queue(opts) {
|
|
|
3252
3219
|
}
|
|
3253
3220
|
}
|
|
3254
3221
|
mw.add = function add(type, payload, opts2) {
|
|
3255
|
-
const id2 =
|
|
3222
|
+
const id2 = crypto2.randomUUID();
|
|
3256
3223
|
let runAt;
|
|
3257
3224
|
if (opts2?.schedule) {
|
|
3258
3225
|
runAt = cronNext(opts2.schedule);
|
|
@@ -3289,46 +3256,39 @@ function queue(opts) {
|
|
|
3289
3256
|
|
|
3290
3257
|
// tenant/migrate.ts
|
|
3291
3258
|
async function migrate2(opts) {
|
|
3292
|
-
const { sql: sql2
|
|
3259
|
+
const { sql: sql2 } = opts;
|
|
3293
3260
|
await sql2.unsafe(`CREATE EXTENSION IF NOT EXISTS "vector"`);
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
3322
|
-
UNIQUE("tenant_id", "slug")
|
|
3323
|
-
)
|
|
3324
|
-
`);
|
|
3325
|
-
await sql2.unsafe(`
|
|
3326
|
-
CREATE INDEX IF NOT EXISTS "_user_tables_tenant_id_idx" ON "_user_tables" ("tenant_id")
|
|
3327
|
-
`);
|
|
3261
|
+
const tenants = pgTable("_tenants", {
|
|
3262
|
+
id: text("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
3263
|
+
name: text("name").notNull(),
|
|
3264
|
+
created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
|
|
3265
|
+
});
|
|
3266
|
+
await tenants.create(sql2);
|
|
3267
|
+
const members = pgTable("_tenant_members", {
|
|
3268
|
+
id: serial("id").primaryKey(),
|
|
3269
|
+
tenant_id: text("tenant_id").notNull().references("_tenants", "id", "cascade"),
|
|
3270
|
+
user_id: integer("user_id").notNull(),
|
|
3271
|
+
role: text("role").notNull().default("member"),
|
|
3272
|
+
created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
|
|
3273
|
+
});
|
|
3274
|
+
await members.create(sql2);
|
|
3275
|
+
await members.createIndex(sql2, "user_id");
|
|
3276
|
+
await sql2.unsafe(`CREATE UNIQUE INDEX IF NOT EXISTS "_tenant_members_unique_idx" ON "_tenant_members" ("tenant_id", "user_id")`);
|
|
3277
|
+
const tables = pgTable("_user_tables", {
|
|
3278
|
+
id: serial("id").primaryKey(),
|
|
3279
|
+
tenant_id: text("tenant_id").notNull().references("_tenants", "id", "cascade"),
|
|
3280
|
+
slug: text("slug").notNull(),
|
|
3281
|
+
label: text("label").notNull().default(""),
|
|
3282
|
+
fields: jsonb("fields").notNull().default(sql`'[]'::jsonb`),
|
|
3283
|
+
created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
|
|
3284
|
+
});
|
|
3285
|
+
await tables.create(sql2);
|
|
3286
|
+
await tables.createIndex(sql2, "tenant_id");
|
|
3287
|
+
await sql2.unsafe(`CREATE UNIQUE INDEX IF NOT EXISTS "_user_tables_unique_idx" ON "_user_tables" ("tenant_id", "slug")`);
|
|
3328
3288
|
}
|
|
3329
3289
|
|
|
3330
3290
|
// tenant/rest.ts
|
|
3331
|
-
import { z as
|
|
3291
|
+
import { z as z3 } from "zod";
|
|
3332
3292
|
|
|
3333
3293
|
// tenant/utils.ts
|
|
3334
3294
|
function internalTableName(tenantId, slug) {
|
|
@@ -3489,25 +3449,25 @@ function zodType(field) {
|
|
|
3489
3449
|
let t;
|
|
3490
3450
|
switch (field.type) {
|
|
3491
3451
|
case "integer":
|
|
3492
|
-
t =
|
|
3452
|
+
t = z3.number().int();
|
|
3493
3453
|
break;
|
|
3494
3454
|
case "float":
|
|
3495
|
-
t =
|
|
3455
|
+
t = z3.number();
|
|
3496
3456
|
break;
|
|
3497
3457
|
case "boolean":
|
|
3498
|
-
t =
|
|
3458
|
+
t = z3.boolean();
|
|
3499
3459
|
break;
|
|
3500
3460
|
case "enum":
|
|
3501
|
-
t = field.options && field.options.length > 0 ?
|
|
3461
|
+
t = field.options && field.options.length > 0 ? z3.enum(field.options) : z3.string();
|
|
3502
3462
|
break;
|
|
3503
3463
|
case "json":
|
|
3504
|
-
t =
|
|
3464
|
+
t = z3.record(z3.string(), z3.unknown());
|
|
3505
3465
|
break;
|
|
3506
3466
|
case "vector":
|
|
3507
|
-
t =
|
|
3467
|
+
t = z3.array(z3.number());
|
|
3508
3468
|
break;
|
|
3509
3469
|
default:
|
|
3510
|
-
t =
|
|
3470
|
+
t = z3.string();
|
|
3511
3471
|
}
|
|
3512
3472
|
if (!field.required) {
|
|
3513
3473
|
if (field.default !== void 0) {
|
|
@@ -3714,7 +3674,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
3714
3674
|
for (const f of table.fields) {
|
|
3715
3675
|
shape[f.name] = zodType(f);
|
|
3716
3676
|
}
|
|
3717
|
-
const zodSchema =
|
|
3677
|
+
const zodSchema = z3.object(shape);
|
|
3718
3678
|
const parsed = zodSchema.parse(data);
|
|
3719
3679
|
parsed.tenant_id = ctx.tenant.id;
|
|
3720
3680
|
delete parsed.id;
|
|
@@ -3742,7 +3702,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
3742
3702
|
for (const f of table.fields) {
|
|
3743
3703
|
shape[f.name] = zodType(f);
|
|
3744
3704
|
}
|
|
3745
|
-
const zodSchema =
|
|
3705
|
+
const zodSchema = z3.object(shape).partial();
|
|
3746
3706
|
const parsed = zodSchema.parse(data);
|
|
3747
3707
|
delete parsed.id;
|
|
3748
3708
|
delete parsed.tenant_id;
|
|
@@ -3832,7 +3792,7 @@ function buildRouter(sql2, usersTable) {
|
|
|
3832
3792
|
for (const f of childTable.fields) {
|
|
3833
3793
|
shape[f.name] = zodType(f);
|
|
3834
3794
|
}
|
|
3835
|
-
const zodSchema =
|
|
3795
|
+
const zodSchema = z3.object(shape);
|
|
3836
3796
|
const parsed = zodSchema.parse(body);
|
|
3837
3797
|
parsed.tenant_id = ctx.tenant.id;
|
|
3838
3798
|
parsed[relField.name] = parentId;
|
|
@@ -4209,38 +4169,32 @@ import { createOpenAI } from "@ai-sdk/openai";
|
|
|
4209
4169
|
// agent/migrate.ts
|
|
4210
4170
|
async function migrate3(opts) {
|
|
4211
4171
|
const { sql: sql2, embeddingDimension } = opts;
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
4239
|
-
)
|
|
4240
|
-
`);
|
|
4241
|
-
await sql2.unsafe(`
|
|
4242
|
-
CREATE INDEX IF NOT EXISTS "_knowledge_documents_agent_id_idx" ON "_knowledge_documents" ("agent_id")
|
|
4243
|
-
`);
|
|
4172
|
+
const agents = pgTable("_agents", {
|
|
4173
|
+
id: serial("id").primaryKey(),
|
|
4174
|
+
tenant_id: text("tenant_id"),
|
|
4175
|
+
name: text("name").notNull(),
|
|
4176
|
+
description: text("description").notNull().default(""),
|
|
4177
|
+
type: text("type").notNull().default("chat"),
|
|
4178
|
+
model: text("model").notNull().default(""),
|
|
4179
|
+
system_prompt: text("system_prompt").notNull().default(""),
|
|
4180
|
+
owner_id: integer("owner_id").notNull(),
|
|
4181
|
+
active: boolean_("active").notNull().default(true),
|
|
4182
|
+
created_at: timestamptz("created_at").notNull().default(sql`NOW()`),
|
|
4183
|
+
updated_at: timestamptz("updated_at").notNull().default(sql`NOW()`)
|
|
4184
|
+
});
|
|
4185
|
+
await agents.create(sql2);
|
|
4186
|
+
await agents.createIndex(sql2, "tenant_id");
|
|
4187
|
+
const docs = pgTable("_knowledge_documents", {
|
|
4188
|
+
id: serial("id").primaryKey(),
|
|
4189
|
+
agent_id: integer("agent_id").notNull().references("_agents", "id", "cascade"),
|
|
4190
|
+
title: text("title").notNull().default(""),
|
|
4191
|
+
content: text("content").notNull(),
|
|
4192
|
+
embedding: vector("embedding", embeddingDimension),
|
|
4193
|
+
metadata: jsonb("metadata").notNull().default(sql`'{}'::jsonb`),
|
|
4194
|
+
created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
|
|
4195
|
+
});
|
|
4196
|
+
await docs.create(sql2);
|
|
4197
|
+
await docs.createIndex(sql2, "agent_id");
|
|
4244
4198
|
}
|
|
4245
4199
|
|
|
4246
4200
|
// agent/rest.ts
|
|
@@ -4350,7 +4304,54 @@ function buildRouter2(deps) {
|
|
|
4350
4304
|
|
|
4351
4305
|
// agent/run.ts
|
|
4352
4306
|
import { streamText, generateText as generateText2, embed } from "ai";
|
|
4353
|
-
import { z as
|
|
4307
|
+
import { z as z4 } from "zod";
|
|
4308
|
+
|
|
4309
|
+
// sse.ts
|
|
4310
|
+
var encoder = new TextEncoder();
|
|
4311
|
+
function formatSSE(event, data) {
|
|
4312
|
+
return `event: ${event}
|
|
4313
|
+
data: ${JSON.stringify(data)}
|
|
4314
|
+
|
|
4315
|
+
`;
|
|
4316
|
+
}
|
|
4317
|
+
function formatSSEData(data) {
|
|
4318
|
+
return `data: ${JSON.stringify(data)}
|
|
4319
|
+
|
|
4320
|
+
`;
|
|
4321
|
+
}
|
|
4322
|
+
function createSSEStream(iterable, opts) {
|
|
4323
|
+
return new Response(
|
|
4324
|
+
new ReadableStream({
|
|
4325
|
+
async start(controller) {
|
|
4326
|
+
try {
|
|
4327
|
+
for await (const event of iterable) {
|
|
4328
|
+
const text2 = event.type ? formatSSE(event.type, event) : formatSSEData(event);
|
|
4329
|
+
controller.enqueue(encoder.encode(text2));
|
|
4330
|
+
}
|
|
4331
|
+
} catch (e) {
|
|
4332
|
+
if (e.name !== "AbortError") {
|
|
4333
|
+
controller.enqueue(
|
|
4334
|
+
encoder.encode(formatSSE("error", { error: e.message }))
|
|
4335
|
+
);
|
|
4336
|
+
}
|
|
4337
|
+
} finally {
|
|
4338
|
+
controller.close();
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
}),
|
|
4342
|
+
{
|
|
4343
|
+
status: opts?.status ?? 200,
|
|
4344
|
+
headers: {
|
|
4345
|
+
"Content-Type": "text/event-stream",
|
|
4346
|
+
"Cache-Control": "no-cache",
|
|
4347
|
+
Connection: "keep-alive",
|
|
4348
|
+
...opts?.headers
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
);
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4354
|
+
// agent/run.ts
|
|
4354
4355
|
function chunkContent(content, chunkSize = 512, overlap = 64) {
|
|
4355
4356
|
const paragraphs = content.split(/\n\n+/);
|
|
4356
4357
|
const chunks = [];
|
|
@@ -4390,26 +4391,26 @@ function createRunner(deps) {
|
|
|
4390
4391
|
const embedModel = getEmbeddingModel();
|
|
4391
4392
|
const start = Date.now();
|
|
4392
4393
|
const hasKB = await hasKnowledgeDocs(sql2, agentId);
|
|
4393
|
-
const
|
|
4394
|
-
if (
|
|
4395
|
-
|
|
4394
|
+
const messages2 = params.messages ?? [];
|
|
4395
|
+
if (messages2.length === 0) {
|
|
4396
|
+
messages2.push({ role: "user", content: params.input });
|
|
4396
4397
|
}
|
|
4397
4398
|
const tools = {};
|
|
4398
4399
|
if (hasKB) {
|
|
4399
4400
|
tools.searchKnowledge = {
|
|
4400
4401
|
description: "\u641C\u7D22\u77E5\u8BC6\u5E93\u6587\u6863\u83B7\u53D6\u4E0E\u67E5\u8BE2\u76F8\u5173\u7684\u4FE1\u606F\u3002\u56DE\u7B54\u7528\u6237\u95EE\u9898\u524D\u5FC5\u987B\u5148\u641C\u7D22\u77E5\u8BC6\u5E93\u3002\u5982\u679C\u641C\u7D22\u7ED3\u679C\u4E0D\u8DB3\u4EE5\u56DE\u7B54\uFF0C\u544A\u8BC9\u7528\u6237\u4F60\u4E0D\u77E5\u9053\u3002",
|
|
4401
|
-
parameters:
|
|
4402
|
-
query:
|
|
4403
|
-
limit:
|
|
4402
|
+
parameters: z4.object({
|
|
4403
|
+
query: z4.string().describe("\u641C\u7D22\u5173\u952E\u8BCD\uFF0C\u5E94\u8BE5\u7528\u4E2D\u6587\u63D0\u95EE"),
|
|
4404
|
+
limit: z4.number().default(5).describe("\u8FD4\u56DE\u7ED3\u679C\u6570\u91CF")
|
|
4404
4405
|
}),
|
|
4405
4406
|
execute: async ({ query, limit }) => {
|
|
4406
4407
|
return searchKnowledge(sql2, embedModel, agentId, query, limit);
|
|
4407
4408
|
}
|
|
4408
4409
|
};
|
|
4409
4410
|
}
|
|
4410
|
-
if (agent2.type === "
|
|
4411
|
-
for (const [key,
|
|
4412
|
-
tools[key] =
|
|
4411
|
+
if (agent2.type === "tool-use" && userTools) {
|
|
4412
|
+
for (const [key, tool11] of Object.entries(userTools)) {
|
|
4413
|
+
tools[key] = tool11;
|
|
4413
4414
|
}
|
|
4414
4415
|
}
|
|
4415
4416
|
const system = agent2.system_prompt || void 0;
|
|
@@ -4417,7 +4418,7 @@ function createRunner(deps) {
|
|
|
4417
4418
|
const result = streamText({
|
|
4418
4419
|
model,
|
|
4419
4420
|
system,
|
|
4420
|
-
messages,
|
|
4421
|
+
messages: messages2,
|
|
4421
4422
|
tools: Object.keys(tools).length > 0 ? tools : void 0
|
|
4422
4423
|
});
|
|
4423
4424
|
const fullStream = result.fullStream;
|
|
@@ -4440,7 +4441,7 @@ function createRunner(deps) {
|
|
|
4440
4441
|
const result = await generateText2({
|
|
4441
4442
|
model,
|
|
4442
4443
|
system,
|
|
4443
|
-
messages,
|
|
4444
|
+
messages: messages2,
|
|
4444
4445
|
tools: Object.keys(tools).length > 0 ? tools : void 0
|
|
4445
4446
|
});
|
|
4446
4447
|
return { output: result.text, elapsed: Date.now() - start };
|
|
@@ -4511,52 +4512,43 @@ function agent(options) {
|
|
|
4511
4512
|
|
|
4512
4513
|
// messager/migrate.ts
|
|
4513
4514
|
async function migrate4(sql2) {
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
)
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
"file_name" TEXT DEFAULT NULL,
|
|
4552
|
-
"file_size" INTEGER DEFAULT NULL,
|
|
4553
|
-
"mime_type" TEXT DEFAULT NULL,
|
|
4554
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
4555
|
-
)
|
|
4556
|
-
`);
|
|
4557
|
-
await sql2.unsafe(`
|
|
4558
|
-
CREATE INDEX IF NOT EXISTS "_messages_channel_created_idx" ON "_messages" ("channel_id", "created_at" DESC)
|
|
4559
|
-
`);
|
|
4515
|
+
const channels2 = pgTable("_channels", {
|
|
4516
|
+
id: serial("id").primaryKey(),
|
|
4517
|
+
tenant_id: text("tenant_id"),
|
|
4518
|
+
name: text("name").notNull().default(""),
|
|
4519
|
+
type: text("type").notNull().default("channel"),
|
|
4520
|
+
created_by: integer("created_by").notNull(),
|
|
4521
|
+
created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
|
|
4522
|
+
});
|
|
4523
|
+
await channels2.create(sql2);
|
|
4524
|
+
await channels2.createIndex(sql2, "tenant_id");
|
|
4525
|
+
const members = pgTable("_channel_members", {
|
|
4526
|
+
id: serial("id").primaryKey(),
|
|
4527
|
+
channel_id: integer("channel_id").notNull().references("_channels", "id", "cascade"),
|
|
4528
|
+
member_id: integer("member_id").notNull(),
|
|
4529
|
+
member_type: text("member_type").notNull().default("user"),
|
|
4530
|
+
role: text("role").notNull().default("member"),
|
|
4531
|
+
last_read_id: integer("last_read_id"),
|
|
4532
|
+
last_read_at: timestamptz("last_read_at")
|
|
4533
|
+
});
|
|
4534
|
+
await members.create(sql2);
|
|
4535
|
+
await members.createIndex(sql2, "member_id");
|
|
4536
|
+
await sql2.unsafe(`CREATE UNIQUE INDEX IF NOT EXISTS "_channel_members_unique_idx" ON "_channel_members" ("channel_id", "member_id", "member_type")`);
|
|
4537
|
+
const messages2 = pgTable("_messages", {
|
|
4538
|
+
id: serial("id").primaryKey(),
|
|
4539
|
+
channel_id: integer("channel_id").notNull().references("_channels", "id", "cascade"),
|
|
4540
|
+
sender_id: integer("sender_id").notNull(),
|
|
4541
|
+
sender_type: text("sender_type").notNull().default("user"),
|
|
4542
|
+
type: text("type").notNull().default("text"),
|
|
4543
|
+
content: text("content").notNull().default(""),
|
|
4544
|
+
file_url: text("file_url"),
|
|
4545
|
+
file_name: text("file_name"),
|
|
4546
|
+
file_size: integer("file_size"),
|
|
4547
|
+
mime_type: text("mime_type"),
|
|
4548
|
+
created_at: timestamptz("created_at").notNull().default(sql`NOW()`)
|
|
4549
|
+
});
|
|
4550
|
+
await messages2.create(sql2);
|
|
4551
|
+
await messages2.createIndex(sql2, ["channel_id", "created_at"], { desc: true });
|
|
4560
4552
|
}
|
|
4561
4553
|
|
|
4562
4554
|
// messager/ws.ts
|
|
@@ -4950,7 +4942,7 @@ function createGateway(config, getPort) {
|
|
|
4950
4942
|
}
|
|
4951
4943
|
|
|
4952
4944
|
// deploy/manager.ts
|
|
4953
|
-
import
|
|
4945
|
+
import crypto3 from "node:crypto";
|
|
4954
4946
|
function createManager(config, apps, manager) {
|
|
4955
4947
|
const router = new Router();
|
|
4956
4948
|
const auth2 = (req, ctx, next) => {
|
|
@@ -4959,7 +4951,7 @@ function createManager(config, apps, manager) {
|
|
|
4959
4951
|
const token = header.replace("Bearer ", "");
|
|
4960
4952
|
const tokenBuf = Buffer.from(token);
|
|
4961
4953
|
const secretBuf = Buffer.from(config.deployToken);
|
|
4962
|
-
if (tokenBuf.length !== secretBuf.length || !
|
|
4954
|
+
if (tokenBuf.length !== secretBuf.length || !crypto3.timingSafeEqual(tokenBuf, secretBuf)) {
|
|
4963
4955
|
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
4964
4956
|
}
|
|
4965
4957
|
return next(req, ctx);
|
|
@@ -5058,10 +5050,10 @@ function createManager(config, apps, manager) {
|
|
|
5058
5050
|
const rawBody = await req.text();
|
|
5059
5051
|
if (config.webhookSecret) {
|
|
5060
5052
|
const sig = req.headers.get("x-hub-signature-256") ?? "";
|
|
5061
|
-
const expected = `sha256=${
|
|
5053
|
+
const expected = `sha256=${crypto3.createHmac("sha256", config.webhookSecret).update(rawBody).digest("hex")}`;
|
|
5062
5054
|
const sigBuf = Buffer.from(sig);
|
|
5063
5055
|
const expectedBuf = Buffer.from(expected);
|
|
5064
|
-
if (sigBuf.length !== expectedBuf.length || !
|
|
5056
|
+
if (sigBuf.length !== expectedBuf.length || !crypto3.timingSafeEqual(sigBuf, expectedBuf)) {
|
|
5065
5057
|
return Response.json({ error: "invalid signature" }, { status: 401 });
|
|
5066
5058
|
}
|
|
5067
5059
|
}
|
|
@@ -5114,14 +5106,14 @@ function forkApp(opts) {
|
|
|
5114
5106
|
return { child, port: opts.port };
|
|
5115
5107
|
}
|
|
5116
5108
|
function stopProcess(mp, timeout = 1e4) {
|
|
5117
|
-
return new Promise((
|
|
5109
|
+
return new Promise((resolve10) => {
|
|
5118
5110
|
const timer = setTimeout(() => {
|
|
5119
5111
|
mp.child.kill("SIGKILL");
|
|
5120
|
-
|
|
5112
|
+
resolve10();
|
|
5121
5113
|
}, timeout);
|
|
5122
5114
|
mp.child.on("exit", () => {
|
|
5123
5115
|
clearTimeout(timer);
|
|
5124
|
-
|
|
5116
|
+
resolve10();
|
|
5125
5117
|
});
|
|
5126
5118
|
mp.child.kill("SIGTERM");
|
|
5127
5119
|
});
|
|
@@ -5340,7 +5332,7 @@ async function deploy(config) {
|
|
|
5340
5332
|
});
|
|
5341
5333
|
const portSuffix = config.port !== 80 ? `:${config.port}` : "";
|
|
5342
5334
|
return {
|
|
5343
|
-
|
|
5335
|
+
close: async () => {
|
|
5344
5336
|
for (const [, app] of apps) {
|
|
5345
5337
|
if (app.restartTimer) clearTimeout(app.restartTimer);
|
|
5346
5338
|
if (app.process) {
|
|
@@ -5413,47 +5405,63 @@ import { createOpenAI as createOpenAI2 } from "@ai-sdk/openai";
|
|
|
5413
5405
|
|
|
5414
5406
|
// opencode/migrate.ts
|
|
5415
5407
|
async function migrate5(sql2) {
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
5446
|
-
)
|
|
5447
|
-
`);
|
|
5448
|
-
await sql2.unsafe(`
|
|
5449
|
-
CREATE INDEX IF NOT EXISTS "_opencode_messages_session_idx" ON "_opencode_messages" ("session_id", "created_at")
|
|
5450
|
-
`);
|
|
5408
|
+
const sessions2 = pgTable("_opencode_sessions", {
|
|
5409
|
+
id: uuid("id").default(sql`gen_random_uuid()`).primaryKey(),
|
|
5410
|
+
tenant_id: text("tenant_id"),
|
|
5411
|
+
user_id: integer("user_id").default(0),
|
|
5412
|
+
title: text("title"),
|
|
5413
|
+
agent_type: text("agent_type").default("build"),
|
|
5414
|
+
model: text("model").default("deepseek-v4-flash"),
|
|
5415
|
+
system_prompt: text("system_prompt"),
|
|
5416
|
+
workspace: text("workspace"),
|
|
5417
|
+
metadata: jsonb("metadata").default(sql`'{}'::jsonb`),
|
|
5418
|
+
active: boolean_("active").default(true),
|
|
5419
|
+
created_at: timestamptz("created_at").default(sql`NOW()`),
|
|
5420
|
+
updated_at: timestamptz("updated_at").default(sql`NOW()`)
|
|
5421
|
+
});
|
|
5422
|
+
await sessions2.create(sql2);
|
|
5423
|
+
await sessions2.createIndex(sql2, "user_id");
|
|
5424
|
+
const messages2 = pgTable("_opencode_messages", {
|
|
5425
|
+
id: serial("id").primaryKey(),
|
|
5426
|
+
session_id: uuid("session_id").notNull().references("_opencode_sessions", "id", "cascade"),
|
|
5427
|
+
role: text("role").notNull(),
|
|
5428
|
+
content: text("content"),
|
|
5429
|
+
tool_calls: jsonb("tool_calls"),
|
|
5430
|
+
tool_results: jsonb("tool_results"),
|
|
5431
|
+
tokens_in: integer("tokens_in").default(0),
|
|
5432
|
+
tokens_out: integer("tokens_out").default(0),
|
|
5433
|
+
created_at: timestamptz("created_at").default(sql`NOW()`)
|
|
5434
|
+
});
|
|
5435
|
+
await messages2.create(sql2);
|
|
5436
|
+
await messages2.createIndex(sql2, ["session_id", "created_at"]);
|
|
5451
5437
|
}
|
|
5452
5438
|
|
|
5453
5439
|
// opencode/session.ts
|
|
5454
5440
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
5455
5441
|
import { join as join3 } from "node:path";
|
|
5456
5442
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
5443
|
+
var sessions = pgTable("_opencode_sessions", {
|
|
5444
|
+
id: uuid("id"),
|
|
5445
|
+
user_id: integer("user_id"),
|
|
5446
|
+
title: text("title"),
|
|
5447
|
+
model: text("model"),
|
|
5448
|
+
workspace: text("workspace"),
|
|
5449
|
+
system_prompt: text("system_prompt"),
|
|
5450
|
+
active: boolean_("active"),
|
|
5451
|
+
updated_at: timestamptz("updated_at"),
|
|
5452
|
+
created_at: timestamptz("created_at")
|
|
5453
|
+
});
|
|
5454
|
+
var messages = pgTable("_opencode_messages", {
|
|
5455
|
+
id: serial("id"),
|
|
5456
|
+
session_id: uuid("session_id"),
|
|
5457
|
+
role: text("role"),
|
|
5458
|
+
content: text("content"),
|
|
5459
|
+
tool_calls: jsonb("tool_calls"),
|
|
5460
|
+
tool_results: jsonb("tool_results"),
|
|
5461
|
+
tokens_in: integer("tokens_in"),
|
|
5462
|
+
tokens_out: integer("tokens_out"),
|
|
5463
|
+
created_at: timestamptz("created_at")
|
|
5464
|
+
});
|
|
5457
5465
|
async function createSession(sql2, opts, cwd, mountPath) {
|
|
5458
5466
|
const id2 = randomUUID2();
|
|
5459
5467
|
const ws = computeSessionWorkspace(cwd, mountPath, id2);
|
|
@@ -5470,27 +5478,26 @@ function computeSessionWorkspace(cwd, mountPath, sessionId) {
|
|
|
5470
5478
|
return join3(cwd, ".sessions", name, sessionId);
|
|
5471
5479
|
}
|
|
5472
5480
|
async function getSession(sql2, id2) {
|
|
5473
|
-
const
|
|
5474
|
-
return
|
|
5481
|
+
const rows = await sessions.find(sql2, { id: id2, active: true });
|
|
5482
|
+
return rows[0] ?? null;
|
|
5475
5483
|
}
|
|
5476
5484
|
async function listSessions(sql2, userId) {
|
|
5485
|
+
const opts = { orderBy: { updated_at: "desc" } };
|
|
5477
5486
|
if (userId !== void 0) {
|
|
5478
|
-
const rows2 = await sql2
|
|
5487
|
+
const rows2 = await sessions.find(sql2, { user_id: userId, active: true }, opts);
|
|
5479
5488
|
return rows2;
|
|
5480
5489
|
}
|
|
5481
|
-
const rows = await sql2
|
|
5490
|
+
const rows = await sessions.find(sql2, { active: true }, opts);
|
|
5482
5491
|
return rows;
|
|
5483
5492
|
}
|
|
5484
5493
|
async function deleteSession(sql2, id2) {
|
|
5485
|
-
await sql2
|
|
5494
|
+
await sessions.update(sql2, { id: id2 }, { active: false, updated_at: sql`NOW()` });
|
|
5486
5495
|
}
|
|
5487
5496
|
async function getHistory(sql2, sessionId, limit = 50) {
|
|
5488
|
-
const rows = await sql2
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
LIMIT ${limit}
|
|
5493
|
-
`;
|
|
5497
|
+
const rows = await messages.find(sql2, { session_id: sessionId }, {
|
|
5498
|
+
orderBy: { created_at: "asc" },
|
|
5499
|
+
limit
|
|
5500
|
+
});
|
|
5494
5501
|
return rows;
|
|
5495
5502
|
}
|
|
5496
5503
|
async function addTextMessage(sql2, sessionId, role, content, tokensIn = 0, tokensOut = 0) {
|
|
@@ -5513,7 +5520,7 @@ async function addToolMessages(sql2, sessionId, toolCalls, toolResults) {
|
|
|
5513
5520
|
// opencode/run.ts
|
|
5514
5521
|
import { streamText as streamText2, stepCountIs } from "ai";
|
|
5515
5522
|
async function* executeGenerator(opts) {
|
|
5516
|
-
const { sessionId, input, model, tools, systemPrompt, messages, sql: sql2, abortSignal } = opts;
|
|
5523
|
+
const { sessionId, input, model, tools, systemPrompt, messages: messages2, sql: sql2, abortSignal } = opts;
|
|
5517
5524
|
const lastStepToolCalls = [];
|
|
5518
5525
|
let currentAssistantText = "";
|
|
5519
5526
|
let currentUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
@@ -5521,7 +5528,7 @@ async function* executeGenerator(opts) {
|
|
|
5521
5528
|
model,
|
|
5522
5529
|
system: systemPrompt,
|
|
5523
5530
|
messages: [
|
|
5524
|
-
...
|
|
5531
|
+
...messages2.map((m) => {
|
|
5525
5532
|
if (m.role === "user") return { role: "user", content: m.content ?? "" };
|
|
5526
5533
|
if (m.role === "assistant") return { role: "assistant", content: m.content ?? "" };
|
|
5527
5534
|
return { role: "user", content: "" };
|
|
@@ -5649,26 +5656,26 @@ function isSkillAllowed(name, perms) {
|
|
|
5649
5656
|
}
|
|
5650
5657
|
|
|
5651
5658
|
// opencode/tools/bash.ts
|
|
5652
|
-
import { tool as
|
|
5653
|
-
import { z as
|
|
5659
|
+
import { tool as tool2 } from "ai";
|
|
5660
|
+
import { z as z5 } from "zod";
|
|
5654
5661
|
import { exec } from "node:child_process";
|
|
5655
5662
|
function createBashTool(ctx) {
|
|
5656
|
-
return
|
|
5663
|
+
return tool2({
|
|
5657
5664
|
description: "Execute a shell command in the workspace directory. Returns stdout, stderr, and exit code.",
|
|
5658
|
-
inputSchema:
|
|
5659
|
-
command:
|
|
5660
|
-
timeout:
|
|
5661
|
-
workdir:
|
|
5665
|
+
inputSchema: z5.object({
|
|
5666
|
+
command: z5.string().describe("The shell command to execute"),
|
|
5667
|
+
timeout: z5.number().default(30).describe("Timeout in seconds"),
|
|
5668
|
+
workdir: z5.string().optional().describe("Subdirectory relative to workspace")
|
|
5662
5669
|
}),
|
|
5663
5670
|
execute: async ({ command, timeout, workdir }) => {
|
|
5664
5671
|
if (!isCommandAllowed(command)) {
|
|
5665
5672
|
return { stdout: "", stderr: "Command denied: potentially dangerous command", exitCode: 1 };
|
|
5666
5673
|
}
|
|
5667
5674
|
const cwd = workdir ? `${ctx.workspace}/${workdir}` : ctx.workspace;
|
|
5668
|
-
return new Promise((
|
|
5675
|
+
return new Promise((resolve10) => {
|
|
5669
5676
|
const child = exec(command, { cwd, timeout: timeout * 1e3, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
|
|
5670
5677
|
const truncated = stdout.length > 1e6 || stderr.length > 1e6;
|
|
5671
|
-
|
|
5678
|
+
resolve10({
|
|
5672
5679
|
stdout: stdout.slice(0, 1e6),
|
|
5673
5680
|
stderr: stderr.slice(0, 1e6),
|
|
5674
5681
|
exitCode: error?.code ?? 0,
|
|
@@ -5682,17 +5689,17 @@ function createBashTool(ctx) {
|
|
|
5682
5689
|
}
|
|
5683
5690
|
|
|
5684
5691
|
// opencode/tools/read.ts
|
|
5685
|
-
import { tool as
|
|
5686
|
-
import { z as
|
|
5692
|
+
import { tool as tool3 } from "ai";
|
|
5693
|
+
import { z as z6 } from "zod";
|
|
5687
5694
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
5688
5695
|
import { resolve as resolve3 } from "node:path";
|
|
5689
5696
|
function createReadTool(ctx) {
|
|
5690
|
-
return
|
|
5697
|
+
return tool3({
|
|
5691
5698
|
description: "Read file contents. Supports offset and limit for reading specific line ranges.",
|
|
5692
|
-
inputSchema:
|
|
5693
|
-
path:
|
|
5694
|
-
offset:
|
|
5695
|
-
limit:
|
|
5699
|
+
inputSchema: z6.object({
|
|
5700
|
+
path: z6.string().describe("File path relative to workspace"),
|
|
5701
|
+
offset: z6.number().optional().describe("Starting line number (1-indexed)"),
|
|
5702
|
+
limit: z6.number().optional().describe("Number of lines to read")
|
|
5696
5703
|
}),
|
|
5697
5704
|
execute: async ({ path: path2, offset, limit }) => {
|
|
5698
5705
|
const resolved = resolve3(ctx.workspace, path2);
|
|
@@ -5724,16 +5731,16 @@ function createReadTool(ctx) {
|
|
|
5724
5731
|
}
|
|
5725
5732
|
|
|
5726
5733
|
// opencode/tools/write.ts
|
|
5727
|
-
import { tool as
|
|
5728
|
-
import { z as
|
|
5734
|
+
import { tool as tool4 } from "ai";
|
|
5735
|
+
import { z as z7 } from "zod";
|
|
5729
5736
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
5730
5737
|
import { resolve as resolve4, dirname as dirname2 } from "node:path";
|
|
5731
5738
|
function createWriteTool(ctx) {
|
|
5732
|
-
return
|
|
5739
|
+
return tool4({
|
|
5733
5740
|
description: "Create or overwrite a file. Parent directories are created automatically.",
|
|
5734
|
-
inputSchema:
|
|
5735
|
-
path:
|
|
5736
|
-
content:
|
|
5741
|
+
inputSchema: z7.object({
|
|
5742
|
+
path: z7.string().describe("File path relative to workspace"),
|
|
5743
|
+
content: z7.string().describe("File content")
|
|
5737
5744
|
}),
|
|
5738
5745
|
execute: async ({ path: path2, content }) => {
|
|
5739
5746
|
const resolved = resolve4(ctx.workspace, path2);
|
|
@@ -5748,18 +5755,18 @@ function createWriteTool(ctx) {
|
|
|
5748
5755
|
}
|
|
5749
5756
|
|
|
5750
5757
|
// opencode/tools/edit.ts
|
|
5751
|
-
import { tool as
|
|
5752
|
-
import { z as
|
|
5758
|
+
import { tool as tool5 } from "ai";
|
|
5759
|
+
import { z as z8 } from "zod";
|
|
5753
5760
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5754
5761
|
import { resolve as resolve5 } from "node:path";
|
|
5755
5762
|
function createEditTool(ctx) {
|
|
5756
|
-
return
|
|
5763
|
+
return tool5({
|
|
5757
5764
|
description: "Perform exact string replacements in a file. If oldString appears multiple times, provide more surrounding context.",
|
|
5758
|
-
inputSchema:
|
|
5759
|
-
path:
|
|
5760
|
-
oldString:
|
|
5761
|
-
newString:
|
|
5762
|
-
replaceAll:
|
|
5765
|
+
inputSchema: z8.object({
|
|
5766
|
+
path: z8.string().describe("File path relative to workspace"),
|
|
5767
|
+
oldString: z8.string().describe("The exact text to replace"),
|
|
5768
|
+
newString: z8.string().describe("The replacement text"),
|
|
5769
|
+
replaceAll: z8.boolean().default(false).describe("Replace all occurrences")
|
|
5763
5770
|
}),
|
|
5764
5771
|
execute: async ({ path: path2, oldString, newString, replaceAll }) => {
|
|
5765
5772
|
const resolved = resolve5(ctx.workspace, path2);
|
|
@@ -5792,19 +5799,19 @@ function createEditTool(ctx) {
|
|
|
5792
5799
|
}
|
|
5793
5800
|
|
|
5794
5801
|
// opencode/tools/grep.ts
|
|
5795
|
-
import { tool as
|
|
5796
|
-
import { z as
|
|
5802
|
+
import { tool as tool6 } from "ai";
|
|
5803
|
+
import { z as z9 } from "zod";
|
|
5797
5804
|
import { execSync as execSync2 } from "node:child_process";
|
|
5798
5805
|
import { resolve as resolve6 } from "node:path";
|
|
5799
5806
|
import { existsSync as existsSync2 } from "node:fs";
|
|
5800
5807
|
function createGrepTool(ctx) {
|
|
5801
|
-
return
|
|
5808
|
+
return tool6({
|
|
5802
5809
|
description: "Search file contents using regex. Supports file type filtering and context lines.",
|
|
5803
|
-
inputSchema:
|
|
5804
|
-
pattern:
|
|
5805
|
-
include:
|
|
5806
|
-
path:
|
|
5807
|
-
context:
|
|
5810
|
+
inputSchema: z9.object({
|
|
5811
|
+
pattern: z9.string().describe("The regex pattern to search for"),
|
|
5812
|
+
include: z9.string().optional().describe('File glob filter (e.g. "*.ts")'),
|
|
5813
|
+
path: z9.string().optional().describe("Subdirectory relative to workspace"),
|
|
5814
|
+
context: z9.number().default(0).describe("Number of context lines before and after each match")
|
|
5808
5815
|
}),
|
|
5809
5816
|
execute: async ({ pattern, include, path: path2, context }) => {
|
|
5810
5817
|
const searchDir = path2 ? resolve6(ctx.workspace, path2) : ctx.workspace;
|
|
@@ -5830,16 +5837,16 @@ function createGrepTool(ctx) {
|
|
|
5830
5837
|
}
|
|
5831
5838
|
|
|
5832
5839
|
// opencode/tools/glob.ts
|
|
5833
|
-
import { tool as
|
|
5834
|
-
import { z as
|
|
5840
|
+
import { tool as tool7 } from "ai";
|
|
5841
|
+
import { z as z10 } from "zod";
|
|
5835
5842
|
import { execSync as execSync3 } from "node:child_process";
|
|
5836
5843
|
import { resolve as resolve7 } from "node:path";
|
|
5837
5844
|
function createGlobTool(ctx) {
|
|
5838
|
-
return
|
|
5845
|
+
return tool7({
|
|
5839
5846
|
description: "Find files matching a glob pattern.",
|
|
5840
|
-
inputSchema:
|
|
5841
|
-
pattern:
|
|
5842
|
-
path:
|
|
5847
|
+
inputSchema: z10.object({
|
|
5848
|
+
pattern: z10.string().describe('Glob pattern (e.g. "src/**/*.ts")'),
|
|
5849
|
+
path: z10.string().optional().describe("Subdirectory relative to workspace")
|
|
5843
5850
|
}),
|
|
5844
5851
|
execute: async ({ pattern, path: path2 }) => {
|
|
5845
5852
|
const searchDir = path2 ? resolve7(ctx.workspace, path2) : ctx.workspace;
|
|
@@ -5858,13 +5865,13 @@ function createGlobTool(ctx) {
|
|
|
5858
5865
|
}
|
|
5859
5866
|
|
|
5860
5867
|
// opencode/tools/web.ts
|
|
5861
|
-
import { tool as
|
|
5862
|
-
import { z as
|
|
5868
|
+
import { tool as tool8 } from "ai";
|
|
5869
|
+
import { z as z11 } from "zod";
|
|
5863
5870
|
function createWebTool(ctx) {
|
|
5864
|
-
return
|
|
5871
|
+
return tool8({
|
|
5865
5872
|
description: "Fetch a URL and return the content as text.",
|
|
5866
|
-
inputSchema:
|
|
5867
|
-
url:
|
|
5873
|
+
inputSchema: z11.object({
|
|
5874
|
+
url: z11.string().describe("The URL to fetch")
|
|
5868
5875
|
}),
|
|
5869
5876
|
execute: async ({ url }) => {
|
|
5870
5877
|
try {
|
|
@@ -5886,17 +5893,17 @@ function createWebTool(ctx) {
|
|
|
5886
5893
|
}
|
|
5887
5894
|
|
|
5888
5895
|
// opencode/tools/question.ts
|
|
5889
|
-
import { tool as
|
|
5890
|
-
import { z as
|
|
5896
|
+
import { tool as tool9 } from "ai";
|
|
5897
|
+
import { z as z12 } from "zod";
|
|
5891
5898
|
function createQuestionTool(ctx) {
|
|
5892
|
-
return
|
|
5899
|
+
return tool9({
|
|
5893
5900
|
description: "Ask the user a question when you need more information to proceed.",
|
|
5894
|
-
inputSchema:
|
|
5895
|
-
question:
|
|
5896
|
-
options:
|
|
5901
|
+
inputSchema: z12.object({
|
|
5902
|
+
question: z12.string().describe("The question to ask the user"),
|
|
5903
|
+
options: z12.array(z12.string()).optional().describe("Optional multiple choice options")
|
|
5897
5904
|
}),
|
|
5898
5905
|
execute: async ({ question, options }, { toolCallId }) => {
|
|
5899
|
-
return new Promise((
|
|
5906
|
+
return new Promise((resolve10, reject) => {
|
|
5900
5907
|
const timeout = setTimeout(() => {
|
|
5901
5908
|
ctx.pendingQuestions.delete(toolCallId);
|
|
5902
5909
|
reject(new Error("Question timed out"));
|
|
@@ -5904,7 +5911,7 @@ function createQuestionTool(ctx) {
|
|
|
5904
5911
|
ctx.pendingQuestions.set(toolCallId, {
|
|
5905
5912
|
resolve: (answer) => {
|
|
5906
5913
|
clearTimeout(timeout);
|
|
5907
|
-
|
|
5914
|
+
resolve10(answer);
|
|
5908
5915
|
},
|
|
5909
5916
|
reject: (err) => {
|
|
5910
5917
|
clearTimeout(timeout);
|
|
@@ -5917,8 +5924,8 @@ function createQuestionTool(ctx) {
|
|
|
5917
5924
|
}
|
|
5918
5925
|
|
|
5919
5926
|
// opencode/tools/skill.ts
|
|
5920
|
-
import { tool as
|
|
5921
|
-
import { z as
|
|
5927
|
+
import { tool as tool10 } from "ai";
|
|
5928
|
+
import { z as z13 } from "zod";
|
|
5922
5929
|
function createSkillTool(ctx) {
|
|
5923
5930
|
const skills = ctx.skillsRegistry.list();
|
|
5924
5931
|
const availableList = skills.map(
|
|
@@ -5932,10 +5939,10 @@ function createSkillTool(ctx) {
|
|
|
5932
5939
|
<available_skills>
|
|
5933
5940
|
${availableList}
|
|
5934
5941
|
</available_skills>` : "No skills available.";
|
|
5935
|
-
return
|
|
5942
|
+
return tool10({
|
|
5936
5943
|
description,
|
|
5937
|
-
inputSchema:
|
|
5938
|
-
name:
|
|
5944
|
+
inputSchema: z13.object({
|
|
5945
|
+
name: z13.string().describe("The name of the skill to load")
|
|
5939
5946
|
}),
|
|
5940
5947
|
execute: async ({ name }) => {
|
|
5941
5948
|
if (!isSkillAllowed(name, ctx.permissions)) {
|
|
@@ -6001,15 +6008,15 @@ async function buildRouter4(deps) {
|
|
|
6001
6008
|
return Response.json(session, { status: 201 });
|
|
6002
6009
|
});
|
|
6003
6010
|
router.get("/sessions", async () => {
|
|
6004
|
-
const
|
|
6005
|
-
return Response.json(
|
|
6011
|
+
const sessions2 = await listSessions(sql2);
|
|
6012
|
+
return Response.json(sessions2);
|
|
6006
6013
|
});
|
|
6007
6014
|
router.get("/sessions/:id", async (_req, ctx) => {
|
|
6008
6015
|
const id2 = ctx.params.id;
|
|
6009
6016
|
const session = await getSession(sql2, id2);
|
|
6010
6017
|
if (!session) return new Response("Not Found", { status: 404 });
|
|
6011
|
-
const
|
|
6012
|
-
return Response.json({ session, messages });
|
|
6018
|
+
const messages2 = await getHistory(sql2, id2);
|
|
6019
|
+
return Response.json({ session, messages: messages2 });
|
|
6013
6020
|
});
|
|
6014
6021
|
router.delete("/sessions/:id", async (_req, ctx) => {
|
|
6015
6022
|
const id2 = ctx.params.id;
|
|
@@ -6289,38 +6296,138 @@ async function opencode(options) {
|
|
|
6289
6296
|
close: () => base.close()
|
|
6290
6297
|
};
|
|
6291
6298
|
}
|
|
6299
|
+
|
|
6300
|
+
// health.ts
|
|
6301
|
+
function health(options) {
|
|
6302
|
+
const path2 = options?.path ?? "/health";
|
|
6303
|
+
const r = new Router();
|
|
6304
|
+
const handler = async () => {
|
|
6305
|
+
try {
|
|
6306
|
+
await options?.check?.();
|
|
6307
|
+
return new Response("OK", { status: 200 });
|
|
6308
|
+
} catch {
|
|
6309
|
+
return new Response("Service Unavailable", { status: 503 });
|
|
6310
|
+
}
|
|
6311
|
+
};
|
|
6312
|
+
r.get(path2, handler);
|
|
6313
|
+
r.head(path2, handler);
|
|
6314
|
+
return r;
|
|
6315
|
+
}
|
|
6316
|
+
|
|
6317
|
+
// i18n.ts
|
|
6318
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
6319
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
6320
|
+
import { join as join4, resolve as resolve9 } from "node:path";
|
|
6321
|
+
function i18n(options) {
|
|
6322
|
+
const dir = resolve9(options.dir);
|
|
6323
|
+
const defaultLocale = options.defaultLocale ?? "en";
|
|
6324
|
+
const cache = /* @__PURE__ */ new Map();
|
|
6325
|
+
async function load(locale) {
|
|
6326
|
+
const cached = cache.get(locale);
|
|
6327
|
+
if (cached) return cached;
|
|
6328
|
+
const filePath = join4(dir, `${locale}.json`);
|
|
6329
|
+
if (!existsSync3(filePath)) return {};
|
|
6330
|
+
try {
|
|
6331
|
+
const content = await readFile2(filePath, "utf-8");
|
|
6332
|
+
const data = JSON.parse(content);
|
|
6333
|
+
cache.set(locale, data);
|
|
6334
|
+
return data;
|
|
6335
|
+
} catch {
|
|
6336
|
+
return {};
|
|
6337
|
+
}
|
|
6338
|
+
}
|
|
6339
|
+
function detect(req) {
|
|
6340
|
+
const url = new URL(req.url);
|
|
6341
|
+
const fromCookie = extractCookie(req, "locale");
|
|
6342
|
+
if (fromCookie) return fromCookie;
|
|
6343
|
+
const fromHeader = req.headers.get("Accept-Language")?.split(",")[0]?.split("-")[0];
|
|
6344
|
+
if (fromHeader) return fromHeader;
|
|
6345
|
+
return defaultLocale;
|
|
6346
|
+
}
|
|
6347
|
+
function t(localeMsgs, key, params) {
|
|
6348
|
+
let msg = localeMsgs[key];
|
|
6349
|
+
if (msg === void 0) msg = key;
|
|
6350
|
+
if (params) {
|
|
6351
|
+
for (const [k, v] of Object.entries(params)) {
|
|
6352
|
+
msg = msg.replace(`{${k}}`, v);
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
6355
|
+
return msg;
|
|
6356
|
+
}
|
|
6357
|
+
return async (req, ctx, next) => {
|
|
6358
|
+
const locale = detect(req);
|
|
6359
|
+
const msgs = await load(locale);
|
|
6360
|
+
ctx.locale = locale;
|
|
6361
|
+
ctx.t = (key, params) => t(msgs, key, params);
|
|
6362
|
+
return next(req, ctx);
|
|
6363
|
+
};
|
|
6364
|
+
}
|
|
6365
|
+
function extractCookie(req, name) {
|
|
6366
|
+
const cookie = req.headers.get("cookie");
|
|
6367
|
+
if (!cookie) return null;
|
|
6368
|
+
for (const part of cookie.split(";")) {
|
|
6369
|
+
const [k, v] = part.trim().split("=");
|
|
6370
|
+
if (k === name && v) return decodeURIComponent(v);
|
|
6371
|
+
}
|
|
6372
|
+
return null;
|
|
6373
|
+
}
|
|
6374
|
+
|
|
6375
|
+
// mailer.ts
|
|
6376
|
+
import { createTransport } from "nodemailer";
|
|
6377
|
+
function mailer(options) {
|
|
6378
|
+
const sender = options.send;
|
|
6379
|
+
const from = options.from;
|
|
6380
|
+
let transporter = null;
|
|
6381
|
+
if (!sender && options.transport) {
|
|
6382
|
+
transporter = typeof options.transport === "string" ? createTransport(options.transport) : options.transport;
|
|
6383
|
+
}
|
|
6384
|
+
async function send(opts) {
|
|
6385
|
+
if (sender) {
|
|
6386
|
+
await sender(opts);
|
|
6387
|
+
return;
|
|
6388
|
+
}
|
|
6389
|
+
if (!transporter) {
|
|
6390
|
+
throw new Error("mailer: no transport configured \u2014 provide `transport` or `send` option");
|
|
6391
|
+
}
|
|
6392
|
+
await transporter.sendMail({ ...opts, from: opts.from ?? from });
|
|
6393
|
+
}
|
|
6394
|
+
async function close() {
|
|
6395
|
+
if (transporter) transporter.close();
|
|
6396
|
+
}
|
|
6397
|
+
return { send, close };
|
|
6398
|
+
}
|
|
6292
6399
|
export {
|
|
6293
6400
|
Router,
|
|
6294
6401
|
TsxContext,
|
|
6295
6402
|
agent,
|
|
6296
|
-
|
|
6403
|
+
aiStream,
|
|
6297
6404
|
auth,
|
|
6298
6405
|
compress,
|
|
6299
6406
|
cors,
|
|
6300
|
-
|
|
6301
|
-
createWorkflowEngine,
|
|
6407
|
+
createTestServer,
|
|
6302
6408
|
defineConfig,
|
|
6303
6409
|
deleteCookie,
|
|
6304
6410
|
deploy,
|
|
6305
|
-
generateWorkflow,
|
|
6306
6411
|
getCookies,
|
|
6307
6412
|
graphql,
|
|
6413
|
+
health,
|
|
6414
|
+
i18n,
|
|
6308
6415
|
logger,
|
|
6416
|
+
mailer,
|
|
6309
6417
|
messager,
|
|
6310
6418
|
opencode,
|
|
6311
6419
|
postgres,
|
|
6312
6420
|
queue,
|
|
6313
6421
|
rateLimit,
|
|
6314
6422
|
redis,
|
|
6423
|
+
runWorkflow,
|
|
6315
6424
|
serve,
|
|
6316
6425
|
serveStatic,
|
|
6317
6426
|
setCookie,
|
|
6318
6427
|
tenant,
|
|
6319
|
-
tool,
|
|
6320
6428
|
tsx,
|
|
6321
6429
|
upload,
|
|
6322
6430
|
useTsx,
|
|
6323
6431
|
user,
|
|
6324
|
-
validate
|
|
6325
|
-
workflow
|
|
6432
|
+
validate
|
|
6326
6433
|
};
|