@mugwork/mug 0.1.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/LICENSE +21 -0
- package/README.md +251 -0
- package/dist/explorer.js +3 -0
- package/dist/packages/email-template/src/email-template.d.ts +18 -0
- package/dist/packages/email-template/src/email-template.js +74 -0
- package/dist/packages/email-template/src/index.d.ts +1 -0
- package/dist/packages/email-template/src/index.js +1 -0
- package/dist/packages/surface-renderer/src/form-renderer.d.ts +117 -0
- package/dist/packages/surface-renderer/src/form-renderer.js +719 -0
- package/dist/packages/surface-renderer/src/index.d.ts +4 -0
- package/dist/packages/surface-renderer/src/index.js +2 -0
- package/dist/packages/surface-renderer/src/portal-renderer.d.ts +177 -0
- package/dist/packages/surface-renderer/src/portal-renderer.js +1089 -0
- package/dist/packages/surface-renderer/src/workspace-home.d.ts +46 -0
- package/dist/packages/surface-renderer/src/workspace-home.js +345 -0
- package/dist/runtime/agent-types.d.ts +48 -0
- package/dist/runtime/agent-types.js +3 -0
- package/dist/runtime/ai-router.d.ts +32 -0
- package/dist/runtime/ai-router.js +112 -0
- package/dist/runtime/app.d.ts +6 -0
- package/dist/runtime/app.js +399 -0
- package/dist/runtime/chunker.d.ts +6 -0
- package/dist/runtime/chunker.js +30 -0
- package/dist/runtime/context.d.ts +115 -0
- package/dist/runtime/context.js +440 -0
- package/dist/runtime/do/workspace-database.d.ts +10 -0
- package/dist/runtime/do/workspace-database.js +199 -0
- package/dist/runtime/form-types.d.ts +143 -0
- package/dist/runtime/form-types.js +1 -0
- package/dist/runtime/runtime.d.ts +9 -0
- package/dist/runtime/runtime.js +7 -0
- package/dist/runtime/source-types.d.ts +15 -0
- package/dist/runtime/source-types.js +1 -0
- package/dist/runtime/source.d.ts +70 -0
- package/dist/runtime/source.js +21 -0
- package/dist/runtime/sync-runtime.d.ts +10 -0
- package/dist/runtime/sync-runtime.js +185 -0
- package/dist/runtime/types.d.ts +21 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/workflow-entrypoint.d.ts +31 -0
- package/dist/runtime/workflow-entrypoint.js +1297 -0
- package/dist/runtime/workflow.d.ts +285 -0
- package/dist/runtime/workflow.js +1008 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +44116 -0
- package/dist/src/commands/ai-gateway-route.d.ts +24 -0
- package/dist/src/commands/ai-gateway-route.js +192 -0
- package/dist/src/commands/auth.d.ts +1 -0
- package/dist/src/commands/auth.js +42 -0
- package/dist/src/commands/billing.d.ts +6 -0
- package/dist/src/commands/billing.js +76 -0
- package/dist/src/commands/brain.d.ts +1 -0
- package/dist/src/commands/brain.js +194 -0
- package/dist/src/commands/demo.d.ts +12 -0
- package/dist/src/commands/demo.js +147 -0
- package/dist/src/commands/deploy.d.ts +1 -0
- package/dist/src/commands/deploy.js +1052 -0
- package/dist/src/commands/dev.d.ts +14 -0
- package/dist/src/commands/dev.js +2818 -0
- package/dist/src/commands/form.d.ts +8 -0
- package/dist/src/commands/form.js +396 -0
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.js +139 -0
- package/dist/src/commands/issue.d.ts +7 -0
- package/dist/src/commands/issue.js +191 -0
- package/dist/src/commands/login.d.ts +9 -0
- package/dist/src/commands/login.js +163 -0
- package/dist/src/commands/logs.d.ts +8 -0
- package/dist/src/commands/logs.js +113 -0
- package/dist/src/commands/portal.d.ts +2 -0
- package/dist/src/commands/portal.js +111 -0
- package/dist/src/commands/pull.d.ts +3 -0
- package/dist/src/commands/pull.js +184 -0
- package/dist/src/commands/push.d.ts +4 -0
- package/dist/src/commands/push.js +183 -0
- package/dist/src/commands/run.d.ts +6 -0
- package/dist/src/commands/run.js +91 -0
- package/dist/src/commands/secret.d.ts +7 -0
- package/dist/src/commands/secret.js +105 -0
- package/dist/src/commands/shutdown.d.ts +1 -0
- package/dist/src/commands/shutdown.js +46 -0
- package/dist/src/commands/sql.d.ts +8 -0
- package/dist/src/commands/sql.js +142 -0
- package/dist/src/commands/status.d.ts +5 -0
- package/dist/src/commands/status.js +39 -0
- package/dist/src/commands/sync.d.ts +7 -0
- package/dist/src/commands/sync.js +991 -0
- package/dist/src/commands/usage.d.ts +6 -0
- package/dist/src/commands/usage.js +78 -0
- package/dist/src/commands/webhooks.d.ts +1 -0
- package/dist/src/commands/webhooks.js +102 -0
- package/dist/src/commands/workspace.d.ts +23 -0
- package/dist/src/commands/workspace.js +590 -0
- package/dist/src/connector-migration.d.ts +20 -0
- package/dist/src/connector-migration.js +43 -0
- package/dist/src/connector-parser.d.ts +14 -0
- package/dist/src/connector-parser.js +94 -0
- package/dist/src/connector-service/discover.d.ts +37 -0
- package/dist/src/connector-service/discover.js +79 -0
- package/dist/src/connector-service/gather.d.ts +22 -0
- package/dist/src/connector-service/gather.js +89 -0
- package/dist/src/connector-service/init.d.ts +14 -0
- package/dist/src/connector-service/init.js +109 -0
- package/dist/src/connector-service/scaffold.d.ts +17 -0
- package/dist/src/connector-service/scaffold.js +194 -0
- package/dist/src/connector-service/spec-storage.d.ts +8 -0
- package/dist/src/connector-service/spec-storage.js +48 -0
- package/dist/src/connector-service/types.d.ts +57 -0
- package/dist/src/connector-service/types.js +2 -0
- package/dist/src/connector-service/verify.d.ts +24 -0
- package/dist/src/connector-service/verify.js +575 -0
- package/dist/src/email-template.d.ts +2 -0
- package/dist/src/email-template.js +1 -0
- package/dist/src/manifest.d.ts +31 -0
- package/dist/src/manifest.js +25 -0
- package/dist/src/mug-icon.d.ts +1 -0
- package/dist/src/mug-icon.js +12 -0
- package/dist/src/slack-manifest.d.ts +119 -0
- package/dist/src/slack-manifest.js +163 -0
- package/dist/src/source-migration.d.ts +20 -0
- package/dist/src/source-migration.js +43 -0
- package/dist/src/surface-renderer.d.ts +5 -0
- package/dist/src/surface-renderer.js +3 -0
- package/dist/src/templates.d.ts +3 -0
- package/dist/src/templates.js +48 -0
- package/dist/src/version-check.d.ts +1 -0
- package/dist/src/version-check.js +28 -0
- package/dist/src/workflow-parser.d.ts +95 -0
- package/dist/src/workflow-parser.js +526 -0
- package/dist/worker/src/agent-types.d.ts +27 -0
- package/dist/worker/src/agent-types.js +3 -0
- package/dist/worker/src/source-types.d.ts +14 -0
- package/dist/worker/src/source-types.js +1 -0
- package/package.json +90 -0
- package/src/data/model-capabilities.json +171 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { WorkspaceContext } from "./context.js";
|
|
3
|
+
import { getSource, allSources } from "./source.js";
|
|
4
|
+
import { getWorkflow, allWorkflows, runWorkflow, queryLogs, queryActions, findTriggeredWorkflows } from "./workflow.js";
|
|
5
|
+
import { chunkText } from "./chunker.js";
|
|
6
|
+
const app = new Hono();
|
|
7
|
+
app.get("/health", (c) => {
|
|
8
|
+
return c.json({
|
|
9
|
+
status: "ok",
|
|
10
|
+
workspace: c.env.WORKSPACE_ID ?? "local",
|
|
11
|
+
ts: new Date().toISOString(),
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
app.post("/sync/:source", async (c) => {
|
|
15
|
+
const sourceName = c.req.param("source");
|
|
16
|
+
const def = getSource(sourceName);
|
|
17
|
+
if (!def) {
|
|
18
|
+
return c.json({ error: `Source "${sourceName}" not found` }, 404);
|
|
19
|
+
}
|
|
20
|
+
return runSync(c.env, def.name);
|
|
21
|
+
});
|
|
22
|
+
app.post("/sync", async (c) => {
|
|
23
|
+
const sources = allSources();
|
|
24
|
+
const results = {};
|
|
25
|
+
for (const def of sources) {
|
|
26
|
+
const res = await runSync(c.env, def.name);
|
|
27
|
+
results[def.name] = await res.json();
|
|
28
|
+
}
|
|
29
|
+
return c.json(results);
|
|
30
|
+
});
|
|
31
|
+
app.post("/api/query", async (c) => {
|
|
32
|
+
const { database, sql, params } = (await c.req.json());
|
|
33
|
+
const ctx = new WorkspaceContext(c.env);
|
|
34
|
+
const rows = await ctx.query(database, sql, params);
|
|
35
|
+
return c.json({ rows });
|
|
36
|
+
});
|
|
37
|
+
app.post("/api/seed", async (c) => {
|
|
38
|
+
const { database, tables } = (await c.req.json());
|
|
39
|
+
const ctx = new WorkspaceContext(c.env);
|
|
40
|
+
const stub = ctx.getDatabaseStub(database);
|
|
41
|
+
const res = await stub.fetch(new Request("https://do/seed", {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "Content-Type": "application/json" },
|
|
44
|
+
body: JSON.stringify({ tables }),
|
|
45
|
+
}));
|
|
46
|
+
return c.json(await res.json());
|
|
47
|
+
});
|
|
48
|
+
app.post("/api/introspect", async (c) => {
|
|
49
|
+
const { database } = (await c.req.json());
|
|
50
|
+
const ctx = new WorkspaceContext(c.env);
|
|
51
|
+
const stub = ctx.getDatabaseStub(database);
|
|
52
|
+
const res = await stub.fetch(new Request("https://do/introspect", {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "Content-Type": "application/json" },
|
|
55
|
+
body: JSON.stringify({}),
|
|
56
|
+
}));
|
|
57
|
+
return c.json(await res.json());
|
|
58
|
+
});
|
|
59
|
+
app.post("/api/export", async (c) => {
|
|
60
|
+
const { database } = (await c.req.json());
|
|
61
|
+
const ctx = new WorkspaceContext(c.env);
|
|
62
|
+
const stub = ctx.getDatabaseStub(database);
|
|
63
|
+
const res = await stub.fetch(new Request("https://do/export", {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: { "Content-Type": "application/json" },
|
|
66
|
+
body: JSON.stringify({}),
|
|
67
|
+
}));
|
|
68
|
+
return c.json(await res.json());
|
|
69
|
+
});
|
|
70
|
+
app.post("/api/ai", async (c) => {
|
|
71
|
+
const body = (await c.req.json());
|
|
72
|
+
const ctx = new WorkspaceContext(c.env);
|
|
73
|
+
const result = await ctx.ai(body.model, body);
|
|
74
|
+
return c.json(result);
|
|
75
|
+
});
|
|
76
|
+
app.get("/workflows", (c) => {
|
|
77
|
+
return c.json({ workflows: allWorkflows().map((w) => w.name) });
|
|
78
|
+
});
|
|
79
|
+
app.post("/run/:workflow", async (c) => {
|
|
80
|
+
const name = c.req.param("workflow");
|
|
81
|
+
const def = getWorkflow(name);
|
|
82
|
+
if (!def) {
|
|
83
|
+
return c.json({ error: `Workflow "${name}" not found` }, 404);
|
|
84
|
+
}
|
|
85
|
+
let params;
|
|
86
|
+
try {
|
|
87
|
+
const body = await c.req.json();
|
|
88
|
+
if (body && typeof body === "object")
|
|
89
|
+
params = body;
|
|
90
|
+
}
|
|
91
|
+
catch { /* no body or not JSON — params stays undefined */ }
|
|
92
|
+
const result = await runWorkflow(name, c.env, params);
|
|
93
|
+
return c.json(result);
|
|
94
|
+
});
|
|
95
|
+
app.post("/agent/:name", async (c) => {
|
|
96
|
+
const name = c.req.param("name");
|
|
97
|
+
let body = {};
|
|
98
|
+
try {
|
|
99
|
+
body = (await c.req.json());
|
|
100
|
+
}
|
|
101
|
+
catch { /* no body */ }
|
|
102
|
+
if (!body.goal) {
|
|
103
|
+
return c.json({ error: "goal is required — POST { \"goal\": \"...\" }" }, 400);
|
|
104
|
+
}
|
|
105
|
+
const workspace = c.env.WORKSPACE_ID ?? "local";
|
|
106
|
+
const res = await c.env.MUG_DISPATCH.fetch(`https://mug-dispatch/agent/${workspace}/invoke`, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
body: JSON.stringify({ agent: name, goal: body.goal, context: body.context }),
|
|
109
|
+
headers: {
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
"X-Mug-Internal": c.env.MUG_INTERNAL_SECRET ?? "",
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
const errText = await res.text();
|
|
116
|
+
return c.json({ error: errText }, 502);
|
|
117
|
+
}
|
|
118
|
+
return c.json(await res.json());
|
|
119
|
+
});
|
|
120
|
+
app.get("/logs", async (c) => {
|
|
121
|
+
const limit = parseInt(c.req.query("limit") ?? "10");
|
|
122
|
+
const logs = await queryLogs(c.env, undefined, limit);
|
|
123
|
+
return c.json(logs);
|
|
124
|
+
});
|
|
125
|
+
app.get("/logs/:workflow", async (c) => {
|
|
126
|
+
const name = c.req.param("workflow");
|
|
127
|
+
const limit = parseInt(c.req.query("limit") ?? "10");
|
|
128
|
+
const logs = await queryLogs(c.env, name, limit);
|
|
129
|
+
return c.json(logs);
|
|
130
|
+
});
|
|
131
|
+
app.get("/actions", async (c) => {
|
|
132
|
+
const limit = parseInt(c.req.query("limit") ?? "50");
|
|
133
|
+
const connector = c.req.query("connector") ?? undefined;
|
|
134
|
+
const operation = c.req.query("operation") ?? undefined;
|
|
135
|
+
const runId = c.req.query("run") ?? undefined;
|
|
136
|
+
const actions = await queryActions(c.env, { workflowRunId: runId, connector, operation }, limit);
|
|
137
|
+
return c.json(actions);
|
|
138
|
+
});
|
|
139
|
+
app.post("/api/notify", async (c) => {
|
|
140
|
+
const body = (await c.req.json());
|
|
141
|
+
const ctx = new WorkspaceContext(c.env);
|
|
142
|
+
await ctx.notify[body.channel]({
|
|
143
|
+
to: body.to,
|
|
144
|
+
message: body.message,
|
|
145
|
+
blocks: body.blocks,
|
|
146
|
+
thread_ts: body.thread_ts,
|
|
147
|
+
unfurl_links: body.unfurl_links,
|
|
148
|
+
unfurl_media: body.unfurl_media,
|
|
149
|
+
});
|
|
150
|
+
return c.json({ status: "sent", channel: body.channel, to: body.to });
|
|
151
|
+
});
|
|
152
|
+
app.post("/changesets/:database", async (c) => {
|
|
153
|
+
const database = c.req.param("database");
|
|
154
|
+
const ctx = new WorkspaceContext(c.env);
|
|
155
|
+
const body = await c.req.json().catch(() => ({}));
|
|
156
|
+
try {
|
|
157
|
+
const stub = ctx.getDatabaseStub(database);
|
|
158
|
+
const res = await stub.fetch(new URL("/changesets", "http://do").toString(), {
|
|
159
|
+
method: "POST",
|
|
160
|
+
body: JSON.stringify(body),
|
|
161
|
+
headers: { "Content-Type": "application/json" },
|
|
162
|
+
});
|
|
163
|
+
const data = await res.json();
|
|
164
|
+
return c.json(data);
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
return c.json({ error: e.message }, 500);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
app.post("/rollback/:database", async (c) => {
|
|
171
|
+
const database = c.req.param("database");
|
|
172
|
+
const ctx = new WorkspaceContext(c.env);
|
|
173
|
+
const body = await c.req.json();
|
|
174
|
+
try {
|
|
175
|
+
const stub = ctx.getDatabaseStub(database);
|
|
176
|
+
const res = await stub.fetch(new URL("/rollback", "http://do").toString(), {
|
|
177
|
+
method: "POST",
|
|
178
|
+
body: JSON.stringify(body),
|
|
179
|
+
headers: { "Content-Type": "application/json" },
|
|
180
|
+
});
|
|
181
|
+
const data = await res.json();
|
|
182
|
+
return c.json(data);
|
|
183
|
+
}
|
|
184
|
+
catch (e) {
|
|
185
|
+
return c.json({ error: e.message }, 500);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
async function runSync(env, sourceName) {
|
|
189
|
+
const def = getSource(sourceName);
|
|
190
|
+
if (!def) {
|
|
191
|
+
return Response.json({ error: `Source "${sourceName}" not found` }, { status: 404 });
|
|
192
|
+
}
|
|
193
|
+
const sources = JSON.parse(env.MUG_SOURCES || "{}");
|
|
194
|
+
const src = sources[sourceName];
|
|
195
|
+
const sourceCtx = {
|
|
196
|
+
credential: async (name) => {
|
|
197
|
+
if (name) {
|
|
198
|
+
const direct = env[name];
|
|
199
|
+
if (typeof direct === "string")
|
|
200
|
+
return direct;
|
|
201
|
+
}
|
|
202
|
+
if (src?.auth?.value) {
|
|
203
|
+
const resolved = env[src.auth.value];
|
|
204
|
+
if (typeof resolved === "string")
|
|
205
|
+
return resolved;
|
|
206
|
+
return src.auth.value;
|
|
207
|
+
}
|
|
208
|
+
if (env.MUG_DISPATCH && env.MUG_INTERNAL_SECRET) {
|
|
209
|
+
const provider = name ?? sourceName;
|
|
210
|
+
const res = await env.MUG_DISPATCH.fetch(`https://mug-dispatch/credential/${env.WORKSPACE_ID}/${provider}`, { headers: { "X-Mug-Internal": env.MUG_INTERNAL_SECRET } });
|
|
211
|
+
if (res.ok) {
|
|
212
|
+
const data = (await res.json());
|
|
213
|
+
return data.access_token;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
throw new Error(`No credentials for "${sourceName}"`);
|
|
217
|
+
},
|
|
218
|
+
fetch: (url, init) => {
|
|
219
|
+
if (url.includes("api.mug.work") && env.MUG_DISPATCH && env.MUG_INTERNAL_SECRET) {
|
|
220
|
+
const proxyUrl = url.replace(/https?:\/\/api\.mug\.work/, "https://mug-dispatch");
|
|
221
|
+
const headers = new Headers(init?.headers);
|
|
222
|
+
if (!headers.has("X-Mug-Internal"))
|
|
223
|
+
headers.set("X-Mug-Internal", env.MUG_INTERNAL_SECRET);
|
|
224
|
+
return env.MUG_DISPATCH.fetch(proxyUrl, { ...init, headers });
|
|
225
|
+
}
|
|
226
|
+
return fetch(url, init);
|
|
227
|
+
},
|
|
228
|
+
lastSync: null,
|
|
229
|
+
};
|
|
230
|
+
const results = {};
|
|
231
|
+
const isLocal = !!env.WORKSPACE_DB;
|
|
232
|
+
for (const table of def.tables) {
|
|
233
|
+
const rows = await table.fetch(sourceCtx);
|
|
234
|
+
let syncRes;
|
|
235
|
+
if (isLocal) {
|
|
236
|
+
const stub = env.WORKSPACE_DB.get(env.WORKSPACE_DB.idFromName(def.database));
|
|
237
|
+
syncRes = await stub.fetch(new URL("/sync", "http://do").toString(), {
|
|
238
|
+
method: "POST",
|
|
239
|
+
body: JSON.stringify({ table: table.name, primaryKey: table.primaryKey, rows }),
|
|
240
|
+
headers: { "Content-Type": "application/json" },
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
syncRes = await env.MUG_DATA.fetch(`https://mug-data/workspace/${env.WORKSPACE_ID}/db/${def.database}/sync`, {
|
|
245
|
+
method: "POST",
|
|
246
|
+
body: JSON.stringify({ table: table.name, primaryKey: table.primaryKey, rows }),
|
|
247
|
+
headers: {
|
|
248
|
+
"Content-Type": "application/json",
|
|
249
|
+
"X-Mug-Internal": env.MUG_INTERNAL_SECRET,
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const syncData = await syncRes.json();
|
|
254
|
+
results[table.name] = syncData;
|
|
255
|
+
if (!isLocal && env.MUG_DISPATCH && sourceCtx.lastSync) {
|
|
256
|
+
const opsCount = (syncData.upserted ?? 0) + (syncData.deleted ?? 0);
|
|
257
|
+
if (opsCount > 0) {
|
|
258
|
+
await env.MUG_DISPATCH.fetch(`https://mug-dispatch/meter/${env.WORKSPACE_ID}/increment`, {
|
|
259
|
+
method: "POST",
|
|
260
|
+
body: JSON.stringify({ dimension: "operations", delta: opsCount, source: `sync:${sourceName}-${table.name}` }),
|
|
261
|
+
headers: { "Content-Type": "application/json", "X-Mug-Internal": env.MUG_INTERNAL_SECRET },
|
|
262
|
+
}).catch(() => { });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
const embedResult = await embedSyncBatch(env, def.database, table.name, table.primaryKey, rows, syncData.deletedPks);
|
|
267
|
+
if (embedResult.vectors > 0 || embedResult.deletedVectors > 0) {
|
|
268
|
+
results[table.name].vectors = embedResult;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
console.error(`[${env.WORKSPACE_ID ?? "local"}] Embedding failed for ${table.name}:`, err);
|
|
273
|
+
}
|
|
274
|
+
const isInitialSync = !sourceCtx.lastSync;
|
|
275
|
+
const hasInserts = (syncData.upserted ?? 0) > 0;
|
|
276
|
+
const hasDeletes = (syncData.deleted ?? 0) > 0;
|
|
277
|
+
if (hasInserts || hasDeletes) {
|
|
278
|
+
const events = [];
|
|
279
|
+
if (hasInserts)
|
|
280
|
+
events.push("insert");
|
|
281
|
+
if (hasDeletes)
|
|
282
|
+
events.push("delete");
|
|
283
|
+
for (const event of events) {
|
|
284
|
+
const triggered = findTriggeredWorkflows(sourceName, table.name, event, isInitialSync);
|
|
285
|
+
for (const wf of triggered) {
|
|
286
|
+
const triggerParams = {
|
|
287
|
+
_trigger: { source: sourceName, table: table.name, event, count: event === "insert" ? syncData.upserted : syncData.deleted },
|
|
288
|
+
rows: event === "insert" ? rows : undefined,
|
|
289
|
+
deletedPks: event === "delete" ? syncData.deletedPks : undefined,
|
|
290
|
+
};
|
|
291
|
+
if (isLocal) {
|
|
292
|
+
runWorkflow(wf.name, env, triggerParams).catch((err) => {
|
|
293
|
+
console.error(`[trigger] ${wf.name} failed: ${err.message}`);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
else if (env.MUG_DISPATCH) {
|
|
297
|
+
env.MUG_DISPATCH.fetch(`https://mug-dispatch/trigger/${env.WORKSPACE_ID}/create-workflow`, {
|
|
298
|
+
method: "POST",
|
|
299
|
+
headers: { "Content-Type": "application/json", "X-Mug-Internal": env.MUG_INTERNAL_SECRET },
|
|
300
|
+
body: JSON.stringify({ workflow: wf.name, ...triggerParams }),
|
|
301
|
+
}).catch(() => { });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (!isLocal && env.MUG_DISPATCH && env.MUG_DATA) {
|
|
308
|
+
try {
|
|
309
|
+
const countRes = await env.MUG_DATA.fetch(`https://mug-data/workspace/${env.WORKSPACE_ID}/db/${def.database}/row-count`, { headers: { "X-Mug-Internal": env.MUG_INTERNAL_SECRET } });
|
|
310
|
+
if (countRes.ok) {
|
|
311
|
+
const { totalRows } = await countRes.json();
|
|
312
|
+
await env.MUG_DISPATCH.fetch(`https://mug-dispatch/meter/${env.WORKSPACE_ID}/set`, {
|
|
313
|
+
method: "POST",
|
|
314
|
+
body: JSON.stringify({ dimension: "records", value: totalRows, source: `sync:${sourceName}` }),
|
|
315
|
+
headers: { "Content-Type": "application/json", "X-Mug-Internal": env.MUG_INTERNAL_SECRET },
|
|
316
|
+
}).catch(() => { });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch { }
|
|
320
|
+
}
|
|
321
|
+
return Response.json({ source: sourceName, database: def.database, tables: results });
|
|
322
|
+
}
|
|
323
|
+
async function embedSyncBatch(env, database, tableName, primaryKey, rows, deletedPks) {
|
|
324
|
+
if (env.WORKSPACE_DB) {
|
|
325
|
+
return { embedded: 0, vectors: 0, deletedVectors: 0 };
|
|
326
|
+
}
|
|
327
|
+
const ctx = new WorkspaceContext(env);
|
|
328
|
+
const skipCols = new Set([primaryKey, "_mug_synced_at", "_mug_deleted_at"]);
|
|
329
|
+
const rowTexts = [];
|
|
330
|
+
for (const row of rows) {
|
|
331
|
+
const pk = String(row[primaryKey]);
|
|
332
|
+
const parts = [];
|
|
333
|
+
for (const [key, value] of Object.entries(row)) {
|
|
334
|
+
if (skipCols.has(key) || typeof value !== "string" || !value.trim())
|
|
335
|
+
continue;
|
|
336
|
+
parts.push(value);
|
|
337
|
+
}
|
|
338
|
+
if (parts.length > 0) {
|
|
339
|
+
rowTexts.push({ pk, text: parts.join(" ") });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
let vectorCount = 0;
|
|
343
|
+
if (rowTexts.length > 0) {
|
|
344
|
+
const allChunks = [];
|
|
345
|
+
for (const { pk, text } of rowTexts) {
|
|
346
|
+
for (const chunk of chunkText(text)) {
|
|
347
|
+
allChunks.push({ pk, chunkIndex: chunk.index, totalChunks: chunk.total, text: chunk.text });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const vectors = await ctx.embed(allChunks.map((c) => c.text));
|
|
351
|
+
const records = allChunks.map((c, i) => ({
|
|
352
|
+
id: c.totalChunks === 1 ? `${tableName}:${c.pk}` : `${tableName}:${c.pk}:chunk_${c.chunkIndex}`,
|
|
353
|
+
values: vectors[i],
|
|
354
|
+
metadata: {
|
|
355
|
+
database,
|
|
356
|
+
table: tableName,
|
|
357
|
+
pk_column: primaryKey,
|
|
358
|
+
primary_key: c.pk,
|
|
359
|
+
chunk_index: c.chunkIndex,
|
|
360
|
+
total_chunks: c.totalChunks,
|
|
361
|
+
synced_at: new Date().toISOString(),
|
|
362
|
+
},
|
|
363
|
+
}));
|
|
364
|
+
for (let i = 0; i < records.length; i += 1000) {
|
|
365
|
+
await env.MUG_DISPATCH.fetch(`https://mug-dispatch/vector/${env.WORKSPACE_ID}/upsert`, {
|
|
366
|
+
method: "POST",
|
|
367
|
+
headers: { "Content-Type": "application/json", "X-Mug-Internal": env.MUG_INTERNAL_SECRET },
|
|
368
|
+
body: JSON.stringify({ vectors: records.slice(i, i + 1000).map(r => ({ id: r.id, values: r.values, metadata: r.metadata })) }),
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
vectorCount = records.length;
|
|
372
|
+
}
|
|
373
|
+
let deletedVectors = 0;
|
|
374
|
+
if (deletedPks?.length) {
|
|
375
|
+
const idsToDelete = deletedPks.map((pk) => `${tableName}:${pk}`);
|
|
376
|
+
await env.MUG_DISPATCH.fetch(`https://mug-dispatch/vector/${env.WORKSPACE_ID}/delete`, {
|
|
377
|
+
method: "POST",
|
|
378
|
+
headers: { "Content-Type": "application/json", "X-Mug-Internal": env.MUG_INTERNAL_SECRET },
|
|
379
|
+
body: JSON.stringify({ ids: idsToDelete }),
|
|
380
|
+
});
|
|
381
|
+
deletedVectors = idsToDelete.length;
|
|
382
|
+
}
|
|
383
|
+
return { embedded: rowTexts.length, vectors: vectorCount, deletedVectors };
|
|
384
|
+
}
|
|
385
|
+
export default {
|
|
386
|
+
fetch: app.fetch,
|
|
387
|
+
async scheduled(_event, env) {
|
|
388
|
+
console.log(`[${env.WORKSPACE_ID ?? "local"}] Cron triggered at ${new Date().toISOString()}`);
|
|
389
|
+
for (const def of allSources()) {
|
|
390
|
+
try {
|
|
391
|
+
await runSync(env, def.name);
|
|
392
|
+
console.log(`[${env.WORKSPACE_ID ?? "local"}] Synced ${def.name}`);
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
console.error(`[${env.WORKSPACE_ID ?? "local"}] Sync failed for ${def.name}:`, err);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const SINGLE_CHUNK_LIMIT = 512;
|
|
2
|
+
const WINDOW_SIZE = 400;
|
|
3
|
+
const OVERLAP = 100;
|
|
4
|
+
function estimateTokens(text) {
|
|
5
|
+
return Math.ceil(text.split(/\s+/).length * 1.3);
|
|
6
|
+
}
|
|
7
|
+
export function chunkText(text) {
|
|
8
|
+
const trimmed = text.trim();
|
|
9
|
+
if (!trimmed)
|
|
10
|
+
return [];
|
|
11
|
+
if (estimateTokens(trimmed) <= SINGLE_CHUNK_LIMIT) {
|
|
12
|
+
return [{ text: trimmed, index: 0, total: 1 }];
|
|
13
|
+
}
|
|
14
|
+
const words = trimmed.split(/\s+/);
|
|
15
|
+
const wordsPerChunk = Math.floor(WINDOW_SIZE / 1.3);
|
|
16
|
+
const overlapWords = Math.floor(OVERLAP / 1.3);
|
|
17
|
+
const step = wordsPerChunk - overlapWords;
|
|
18
|
+
const chunks = [];
|
|
19
|
+
for (let i = 0; i < words.length; i += step) {
|
|
20
|
+
const slice = words.slice(i, i + wordsPerChunk);
|
|
21
|
+
if (slice.length === 0)
|
|
22
|
+
break;
|
|
23
|
+
chunks.push({ text: slice.join(" "), index: chunks.length, total: 0 });
|
|
24
|
+
if (i + wordsPerChunk >= words.length)
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
for (const c of chunks)
|
|
28
|
+
c.total = chunks.length;
|
|
29
|
+
return chunks;
|
|
30
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { Env } from "./types.js";
|
|
2
|
+
import type { CollectOptions } from "./form-types.js";
|
|
3
|
+
import type { Tier, RoutingConfig } from "./ai-router.js";
|
|
4
|
+
export interface SearchOptions {
|
|
5
|
+
source?: string;
|
|
6
|
+
limit?: number;
|
|
7
|
+
filter?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
export interface SearchResult {
|
|
10
|
+
score: number;
|
|
11
|
+
table: string;
|
|
12
|
+
primaryKey: string;
|
|
13
|
+
row: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export interface AskOptions {
|
|
16
|
+
source?: string;
|
|
17
|
+
limit?: number;
|
|
18
|
+
model?: string;
|
|
19
|
+
system?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface AskResult {
|
|
22
|
+
answer: string;
|
|
23
|
+
sources: SearchResult[];
|
|
24
|
+
usage: {
|
|
25
|
+
input_tokens: number;
|
|
26
|
+
output_tokens: number;
|
|
27
|
+
search_results: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
interface AiOptions {
|
|
31
|
+
prompt: string;
|
|
32
|
+
system?: string;
|
|
33
|
+
maxTokens?: number;
|
|
34
|
+
routing?: RoutingConfig;
|
|
35
|
+
billing?: string;
|
|
36
|
+
}
|
|
37
|
+
interface AiResponse {
|
|
38
|
+
text: string;
|
|
39
|
+
model: string;
|
|
40
|
+
usage: {
|
|
41
|
+
input_tokens: number;
|
|
42
|
+
output_tokens: number;
|
|
43
|
+
};
|
|
44
|
+
routing?: {
|
|
45
|
+
tier: Tier;
|
|
46
|
+
model: string;
|
|
47
|
+
provider: string;
|
|
48
|
+
reason: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
interface NotifyOptions {
|
|
52
|
+
to: string;
|
|
53
|
+
message: string;
|
|
54
|
+
subject?: string;
|
|
55
|
+
fromName?: string;
|
|
56
|
+
cta?: {
|
|
57
|
+
label: string;
|
|
58
|
+
url: string;
|
|
59
|
+
};
|
|
60
|
+
blocks?: unknown[];
|
|
61
|
+
thread_ts?: string;
|
|
62
|
+
unfurl_links?: boolean;
|
|
63
|
+
unfurl_media?: boolean;
|
|
64
|
+
}
|
|
65
|
+
export declare class WorkspaceContext {
|
|
66
|
+
private env;
|
|
67
|
+
private isLocal;
|
|
68
|
+
constructor(env: Env);
|
|
69
|
+
getDatabaseStub(database: string): DurableObjectStub;
|
|
70
|
+
query(database: string, sql: string, params?: (string | number | null)[]): Promise<Record<string, unknown>[]>;
|
|
71
|
+
exec(database: string, sql: string, params?: (string | number | null)[], changeset?: {
|
|
72
|
+
id: string;
|
|
73
|
+
source: string;
|
|
74
|
+
}): Promise<number>;
|
|
75
|
+
private getWorkspaceRouting;
|
|
76
|
+
private getWorkspaceBilling;
|
|
77
|
+
ai(model: string, options: AiOptions): Promise<AiResponse>;
|
|
78
|
+
embed(texts: string[]): Promise<number[][]>;
|
|
79
|
+
search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
|
|
80
|
+
ask(question: string, options?: AskOptions): Promise<AskResult>;
|
|
81
|
+
surfaceUrl(surfaceId: string, path?: string): string;
|
|
82
|
+
get notify(): {
|
|
83
|
+
email: (options: NotifyOptions) => Promise<string>;
|
|
84
|
+
sms: (options: NotifyOptions) => Promise<string>;
|
|
85
|
+
slack: (options: NotifyOptions) => Promise<string>;
|
|
86
|
+
channel: (name: string, options: NotifyOptions) => Promise<string>;
|
|
87
|
+
};
|
|
88
|
+
private getBranding;
|
|
89
|
+
private sendNotification;
|
|
90
|
+
slackApiCall(method: string, body: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
91
|
+
file(path: string): Promise<ArrayBuffer>;
|
|
92
|
+
fileText(path: string): Promise<string>;
|
|
93
|
+
invokeAgent(name: string, options: {
|
|
94
|
+
goal: string;
|
|
95
|
+
context?: Record<string, unknown>;
|
|
96
|
+
sessionKey?: string;
|
|
97
|
+
caps?: Record<string, number>;
|
|
98
|
+
}): Promise<{
|
|
99
|
+
response: string;
|
|
100
|
+
output?: Record<string, unknown>;
|
|
101
|
+
usage: {
|
|
102
|
+
turns: number;
|
|
103
|
+
credits: number;
|
|
104
|
+
};
|
|
105
|
+
capped?: boolean;
|
|
106
|
+
cappedReason?: string;
|
|
107
|
+
status?: string;
|
|
108
|
+
pendingApproval?: {
|
|
109
|
+
tool: string;
|
|
110
|
+
args: Record<string, unknown>;
|
|
111
|
+
};
|
|
112
|
+
}>;
|
|
113
|
+
collect(options: CollectOptions): Promise<string>;
|
|
114
|
+
}
|
|
115
|
+
export {};
|