@teamkeel/functions-runtime 0.458.0 → 0.460.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/dist/index.cjs +1321 -302
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +316 -9
- package/dist/index.d.ts +316 -9
- package/dist/index.js +1334 -305
- package/dist/index.js.map +1 -1
- package/package.json +11 -2
package/dist/index.js
CHANGED
|
@@ -1,10 +1,336 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
3
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
3
7
|
var __export = (target, all) => {
|
|
4
8
|
for (var name in all)
|
|
5
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
10
|
};
|
|
7
11
|
|
|
12
|
+
// src/File.ts
|
|
13
|
+
var File_exports = {};
|
|
14
|
+
__export(File_exports, {
|
|
15
|
+
File: () => File,
|
|
16
|
+
InlineFile: () => InlineFile,
|
|
17
|
+
buildContentDisposition: () => buildContentDisposition,
|
|
18
|
+
deleteStoredFile: () => deleteStoredFile,
|
|
19
|
+
rewriteFilesDomain: () => rewriteFilesDomain
|
|
20
|
+
});
|
|
21
|
+
import {
|
|
22
|
+
S3Client,
|
|
23
|
+
PutObjectCommand,
|
|
24
|
+
GetObjectCommand,
|
|
25
|
+
DeleteObjectCommand
|
|
26
|
+
} from "@aws-sdk/client-s3";
|
|
27
|
+
import { fromEnv } from "@aws-sdk/credential-providers";
|
|
28
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
29
|
+
import KSUID from "ksuid";
|
|
30
|
+
function rewriteFilesDomain(url) {
|
|
31
|
+
const domain = process.env.KEEL_FILES_DOMAIN;
|
|
32
|
+
if (domain) {
|
|
33
|
+
const override = new URL(domain);
|
|
34
|
+
url.protocol = override.protocol;
|
|
35
|
+
url.hostname = override.hostname;
|
|
36
|
+
url.port = override.port;
|
|
37
|
+
const overridePath = override.pathname.replace(/\/+$/, "");
|
|
38
|
+
if (overridePath && overridePath !== "/") {
|
|
39
|
+
url.pathname = overridePath + url.pathname;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return url;
|
|
43
|
+
}
|
|
44
|
+
function encodeRFC5987(value) {
|
|
45
|
+
return encodeURIComponent(value).replace(
|
|
46
|
+
/['()*]/g,
|
|
47
|
+
(c) => "%" + c.charCodeAt(0).toString(16).toUpperCase()
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
function buildContentDisposition(disposition, filename) {
|
|
51
|
+
const type = disposition === "attachment" ? "attachment" : "inline";
|
|
52
|
+
if (!filename || filename.trim() === "") {
|
|
53
|
+
return type;
|
|
54
|
+
}
|
|
55
|
+
const asciiFallback = filename.replace(/[^\x20-\x7e]|["\\]/g, "_");
|
|
56
|
+
return `${type}; filename="${asciiFallback}"; filename*=UTF-8''${encodeRFC5987(
|
|
57
|
+
filename
|
|
58
|
+
)}`;
|
|
59
|
+
}
|
|
60
|
+
async function storeFile(contents, key, filename, contentType, size, expires) {
|
|
61
|
+
if (!s3Client) {
|
|
62
|
+
throw new Error("S3 client is required");
|
|
63
|
+
}
|
|
64
|
+
const params = {
|
|
65
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
66
|
+
Key: "files/" + key,
|
|
67
|
+
Body: contents,
|
|
68
|
+
ContentType: contentType,
|
|
69
|
+
ContentDisposition: `attachment; filename="${encodeURIComponent(
|
|
70
|
+
filename
|
|
71
|
+
)}"`,
|
|
72
|
+
Metadata: {
|
|
73
|
+
filename
|
|
74
|
+
},
|
|
75
|
+
ACL: "private"
|
|
76
|
+
};
|
|
77
|
+
if (expires) {
|
|
78
|
+
if (expires instanceof Date) {
|
|
79
|
+
params.Expires = expires;
|
|
80
|
+
} else {
|
|
81
|
+
console.warn("Invalid expires value. Skipping Expires parameter.");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const command = new PutObjectCommand(params);
|
|
85
|
+
try {
|
|
86
|
+
await s3Client.send(command);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("Error uploading file:", error);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function deleteStoredFile(key) {
|
|
93
|
+
if (!s3Client) {
|
|
94
|
+
throw new Error("S3 client is required");
|
|
95
|
+
}
|
|
96
|
+
await s3Client.send(
|
|
97
|
+
new DeleteObjectCommand({
|
|
98
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
99
|
+
Key: "files/" + key
|
|
100
|
+
})
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
var s3Client, InlineFile, File;
|
|
104
|
+
var init_File = __esm({
|
|
105
|
+
"src/File.ts"() {
|
|
106
|
+
"use strict";
|
|
107
|
+
s3Client = (() => {
|
|
108
|
+
if (!process.env.KEEL_FILES_BUCKET_NAME) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const endpoint = process.env.KEEL_S3_ENDPOINT;
|
|
112
|
+
if (endpoint) {
|
|
113
|
+
return new S3Client({
|
|
114
|
+
region: process.env.KEEL_REGION,
|
|
115
|
+
credentials: {
|
|
116
|
+
accessKeyId: "keelstorage",
|
|
117
|
+
secretAccessKey: "keelstorage"
|
|
118
|
+
},
|
|
119
|
+
endpoint
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const testEndpoint = process.env.TEST_AWS_ENDPOINT;
|
|
123
|
+
if (testEndpoint) {
|
|
124
|
+
return new S3Client({
|
|
125
|
+
region: process.env.KEEL_REGION,
|
|
126
|
+
credentials: {
|
|
127
|
+
accessKeyId: "test",
|
|
128
|
+
secretAccessKey: "test"
|
|
129
|
+
},
|
|
130
|
+
endpointProvider: /* @__PURE__ */ __name(() => {
|
|
131
|
+
return {
|
|
132
|
+
url: new URL(testEndpoint)
|
|
133
|
+
};
|
|
134
|
+
}, "endpointProvider")
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return new S3Client({
|
|
138
|
+
region: process.env.KEEL_REGION,
|
|
139
|
+
credentials: fromEnv()
|
|
140
|
+
});
|
|
141
|
+
})();
|
|
142
|
+
__name(rewriteFilesDomain, "rewriteFilesDomain");
|
|
143
|
+
__name(encodeRFC5987, "encodeRFC5987");
|
|
144
|
+
__name(buildContentDisposition, "buildContentDisposition");
|
|
145
|
+
InlineFile = class _InlineFile {
|
|
146
|
+
static {
|
|
147
|
+
__name(this, "InlineFile");
|
|
148
|
+
}
|
|
149
|
+
constructor(input) {
|
|
150
|
+
this._filename = input.filename;
|
|
151
|
+
this._contentType = input.contentType;
|
|
152
|
+
this._contents = null;
|
|
153
|
+
}
|
|
154
|
+
static fromDataURL(dataURL) {
|
|
155
|
+
const info = dataURL.split(",")[0].split(":")[1];
|
|
156
|
+
const data = dataURL.split(",")[1];
|
|
157
|
+
const mimeType = info.split(";")[0];
|
|
158
|
+
const name = info.split(";")[1].split("=")[1] || "file";
|
|
159
|
+
const buffer = Buffer.from(data, "base64");
|
|
160
|
+
const file2 = new _InlineFile({ filename: name, contentType: mimeType });
|
|
161
|
+
file2.write(buffer);
|
|
162
|
+
return file2;
|
|
163
|
+
}
|
|
164
|
+
// Gets size of the file's contents in bytes
|
|
165
|
+
get size() {
|
|
166
|
+
if (this._contents) {
|
|
167
|
+
return this._contents.size;
|
|
168
|
+
}
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
// Gets the media type of the file contents
|
|
172
|
+
get contentType() {
|
|
173
|
+
return this._contentType;
|
|
174
|
+
}
|
|
175
|
+
// Gets the name of the file
|
|
176
|
+
get filename() {
|
|
177
|
+
return this._filename;
|
|
178
|
+
}
|
|
179
|
+
// Write the files contents from a buffer
|
|
180
|
+
write(buffer) {
|
|
181
|
+
this._contents = new Blob([
|
|
182
|
+
new Uint8Array(
|
|
183
|
+
buffer.buffer,
|
|
184
|
+
buffer.byteOffset,
|
|
185
|
+
buffer.byteLength
|
|
186
|
+
)
|
|
187
|
+
]);
|
|
188
|
+
}
|
|
189
|
+
// Reads the contents of the file as a buffer
|
|
190
|
+
async read() {
|
|
191
|
+
if (!this._contents) {
|
|
192
|
+
throw new Error("No contents to read");
|
|
193
|
+
}
|
|
194
|
+
const arrayBuffer = await this._contents.arrayBuffer();
|
|
195
|
+
return Buffer.from(arrayBuffer);
|
|
196
|
+
}
|
|
197
|
+
// Persists the file
|
|
198
|
+
async store(expires = null) {
|
|
199
|
+
const content = await this.read();
|
|
200
|
+
const key = KSUID.randomSync().string;
|
|
201
|
+
await storeFile(
|
|
202
|
+
content,
|
|
203
|
+
key,
|
|
204
|
+
this._filename,
|
|
205
|
+
this._contentType,
|
|
206
|
+
this.size,
|
|
207
|
+
expires
|
|
208
|
+
);
|
|
209
|
+
return new File({
|
|
210
|
+
key,
|
|
211
|
+
size: this.size,
|
|
212
|
+
filename: this.filename,
|
|
213
|
+
contentType: this.contentType
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
File = class _File extends InlineFile {
|
|
218
|
+
static {
|
|
219
|
+
__name(this, "File");
|
|
220
|
+
}
|
|
221
|
+
constructor(input) {
|
|
222
|
+
super({
|
|
223
|
+
filename: input.filename || "",
|
|
224
|
+
contentType: input.contentType || ""
|
|
225
|
+
});
|
|
226
|
+
this._key = input.key || "";
|
|
227
|
+
this._size = input.size || 0;
|
|
228
|
+
}
|
|
229
|
+
// Creates a new instance from the database record
|
|
230
|
+
static fromDbRecord(input) {
|
|
231
|
+
return new _File({
|
|
232
|
+
key: input.key,
|
|
233
|
+
filename: input.filename,
|
|
234
|
+
size: input.size,
|
|
235
|
+
contentType: input.contentType
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
get size() {
|
|
239
|
+
return this._size;
|
|
240
|
+
}
|
|
241
|
+
// Gets the stored key
|
|
242
|
+
get key() {
|
|
243
|
+
return this._key;
|
|
244
|
+
}
|
|
245
|
+
get isPublic() {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
async read() {
|
|
249
|
+
if (this._contents) {
|
|
250
|
+
const arrayBuffer = await this._contents.arrayBuffer();
|
|
251
|
+
return Buffer.from(arrayBuffer);
|
|
252
|
+
}
|
|
253
|
+
if (!s3Client) {
|
|
254
|
+
throw new Error("S3 client is required");
|
|
255
|
+
}
|
|
256
|
+
const params = {
|
|
257
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
258
|
+
Key: "files/" + this.key
|
|
259
|
+
};
|
|
260
|
+
const command = new GetObjectCommand(params);
|
|
261
|
+
const response = await s3Client.send(command);
|
|
262
|
+
const blob = await response.Body.transformToByteArray();
|
|
263
|
+
return Buffer.from(blob);
|
|
264
|
+
}
|
|
265
|
+
async store(expires = null) {
|
|
266
|
+
if (this._contents) {
|
|
267
|
+
const contents = await this.read();
|
|
268
|
+
await storeFile(
|
|
269
|
+
contents,
|
|
270
|
+
this.key,
|
|
271
|
+
this.filename,
|
|
272
|
+
this.contentType,
|
|
273
|
+
this.size,
|
|
274
|
+
expires
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
// Generates a presigned download URL.
|
|
280
|
+
//
|
|
281
|
+
// By default the browser previews the file inline and, when saved, uses the
|
|
282
|
+
// file's own filename. Pass `contentDisposition: "attachment"` to force a
|
|
283
|
+
// download, or `filename` to override the suggested name.
|
|
284
|
+
async getPresignedUrl(options) {
|
|
285
|
+
if (!s3Client) {
|
|
286
|
+
throw new Error("S3 client is required");
|
|
287
|
+
}
|
|
288
|
+
const disposition = options?.contentDisposition ?? "inline";
|
|
289
|
+
const filename = options?.filename ?? this.filename;
|
|
290
|
+
const command = new GetObjectCommand({
|
|
291
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
292
|
+
Key: "files/" + this.key,
|
|
293
|
+
ResponseContentDisposition: buildContentDisposition(
|
|
294
|
+
disposition,
|
|
295
|
+
filename
|
|
296
|
+
)
|
|
297
|
+
});
|
|
298
|
+
const url = await getSignedUrl(s3Client, command, { expiresIn: 60 * 60 });
|
|
299
|
+
return rewriteFilesDomain(new URL(url));
|
|
300
|
+
}
|
|
301
|
+
// Generates a presigned upload URL. If the file doesn't have a key, a new one will be generated
|
|
302
|
+
async getPresignedUploadUrl() {
|
|
303
|
+
if (!s3Client) {
|
|
304
|
+
throw new Error("S3 client is required");
|
|
305
|
+
}
|
|
306
|
+
if (!this.key) {
|
|
307
|
+
this._key = KSUID.randomSync().string;
|
|
308
|
+
}
|
|
309
|
+
const command = new PutObjectCommand({
|
|
310
|
+
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
311
|
+
Key: "files/" + this.key
|
|
312
|
+
});
|
|
313
|
+
const url = await getSignedUrl(s3Client, command, { expiresIn: 60 * 60 });
|
|
314
|
+
return rewriteFilesDomain(new URL(url));
|
|
315
|
+
}
|
|
316
|
+
// Persists the file
|
|
317
|
+
toDbRecord() {
|
|
318
|
+
return {
|
|
319
|
+
key: this.key,
|
|
320
|
+
filename: this.filename,
|
|
321
|
+
contentType: this.contentType,
|
|
322
|
+
size: this.size
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
toJSON() {
|
|
326
|
+
return this.toDbRecord();
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
__name(storeFile, "storeFile");
|
|
330
|
+
__name(deleteStoredFile, "deleteStoredFile");
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
8
334
|
// src/ModelAPI.js
|
|
9
335
|
import { sql as sql3 } from "kysely";
|
|
10
336
|
|
|
@@ -72,6 +398,9 @@ var AuditContextPlugin = class {
|
|
|
72
398
|
const rawNode = sql`set_trace_id(${audit.traceId})`.as(this.traceIdAlias).toOperationNode();
|
|
73
399
|
returning.selections.push(SelectionNode.create(rawNode));
|
|
74
400
|
}
|
|
401
|
+
if (returning.selections.length === 0) {
|
|
402
|
+
return { ...args.node };
|
|
403
|
+
}
|
|
75
404
|
return {
|
|
76
405
|
...args.node,
|
|
77
406
|
returning
|
|
@@ -441,20 +770,44 @@ var KEEL_INTERNAL_CHILDREN = "includeChildrenSpans";
|
|
|
441
770
|
import WebSocket from "ws";
|
|
442
771
|
import { readFileSync } from "fs";
|
|
443
772
|
var dbInstance = new AsyncLocalStorage2();
|
|
773
|
+
var fileCleanupStore = new AsyncLocalStorage2();
|
|
774
|
+
function deferFileDeletion(key) {
|
|
775
|
+
fileCleanupStore.getStore()?.add(key);
|
|
776
|
+
}
|
|
777
|
+
__name(deferFileDeletion, "deferFileDeletion");
|
|
778
|
+
async function flushFileDeletions(keys) {
|
|
779
|
+
if (keys.size === 0) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const { deleteStoredFile: deleteStoredFile2 } = await Promise.resolve().then(() => (init_File(), File_exports));
|
|
783
|
+
for (const key of keys) {
|
|
784
|
+
try {
|
|
785
|
+
await deleteStoredFile2(key);
|
|
786
|
+
} catch (e) {
|
|
787
|
+
console.error("failed to delete orphaned file from storage", e);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
__name(flushFileDeletions, "flushFileDeletions");
|
|
444
792
|
var vitestDb = null;
|
|
445
793
|
async function withDatabase(db, requiresTransaction, cb) {
|
|
794
|
+
const pending = /* @__PURE__ */ new Set();
|
|
446
795
|
if (requiresTransaction) {
|
|
447
|
-
|
|
448
|
-
return dbInstance.run(transaction,
|
|
449
|
-
return cb({ transaction });
|
|
796
|
+
const result2 = await db.transaction().execute(async (transaction) => {
|
|
797
|
+
return dbInstance.run(transaction, () => {
|
|
798
|
+
return fileCleanupStore.run(pending, () => cb({ transaction }));
|
|
450
799
|
});
|
|
451
800
|
});
|
|
801
|
+
await flushFileDeletions(pending);
|
|
802
|
+
return result2;
|
|
452
803
|
}
|
|
453
|
-
|
|
454
|
-
return dbInstance.run(sDb,
|
|
455
|
-
return cb({ sDb });
|
|
804
|
+
const result = await db.connection().execute(async (sDb) => {
|
|
805
|
+
return dbInstance.run(sDb, () => {
|
|
806
|
+
return fileCleanupStore.run(pending, () => cb({ sDb }));
|
|
456
807
|
});
|
|
457
808
|
});
|
|
809
|
+
await flushFileDeletions(pending);
|
|
810
|
+
return result;
|
|
458
811
|
}
|
|
459
812
|
__name(withDatabase, "withDatabase");
|
|
460
813
|
function useDatabase() {
|
|
@@ -533,9 +886,9 @@ var InstrumentedClient = class extends Client {
|
|
|
533
886
|
}
|
|
534
887
|
async query(...args) {
|
|
535
888
|
const _super = super.query.bind(this);
|
|
536
|
-
const
|
|
889
|
+
const sql5 = args[0];
|
|
537
890
|
let sqlAttribute = false;
|
|
538
|
-
let spanName = txStatements[
|
|
891
|
+
let spanName = txStatements[sql5.toLowerCase()];
|
|
539
892
|
if (!spanName) {
|
|
540
893
|
spanName = "Database Query";
|
|
541
894
|
sqlAttribute = true;
|
|
@@ -603,9 +956,9 @@ function getDialect(connString) {
|
|
|
603
956
|
pool.on("connect", (client) => {
|
|
604
957
|
const originalQuery = client.query;
|
|
605
958
|
client.query = function(...args) {
|
|
606
|
-
const
|
|
959
|
+
const sql5 = args[0];
|
|
607
960
|
let sqlAttribute = false;
|
|
608
|
-
let spanName = txStatements[
|
|
961
|
+
let spanName = txStatements[sql5.toLowerCase()];
|
|
609
962
|
if (!spanName) {
|
|
610
963
|
spanName = "Database Query";
|
|
611
964
|
sqlAttribute = true;
|
|
@@ -629,275 +982,8 @@ function getDialect(connString) {
|
|
|
629
982
|
}
|
|
630
983
|
__name(getDialect, "getDialect");
|
|
631
984
|
|
|
632
|
-
// src/File.ts
|
|
633
|
-
import {
|
|
634
|
-
S3Client,
|
|
635
|
-
PutObjectCommand,
|
|
636
|
-
GetObjectCommand
|
|
637
|
-
} from "@aws-sdk/client-s3";
|
|
638
|
-
import { fromEnv } from "@aws-sdk/credential-providers";
|
|
639
|
-
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
640
|
-
import KSUID from "ksuid";
|
|
641
|
-
var s3Client = (() => {
|
|
642
|
-
if (!process.env.KEEL_FILES_BUCKET_NAME) {
|
|
643
|
-
return null;
|
|
644
|
-
}
|
|
645
|
-
const endpoint = process.env.KEEL_S3_ENDPOINT;
|
|
646
|
-
if (endpoint) {
|
|
647
|
-
return new S3Client({
|
|
648
|
-
region: process.env.KEEL_REGION,
|
|
649
|
-
credentials: {
|
|
650
|
-
accessKeyId: "keelstorage",
|
|
651
|
-
secretAccessKey: "keelstorage"
|
|
652
|
-
},
|
|
653
|
-
endpoint
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
const testEndpoint = process.env.TEST_AWS_ENDPOINT;
|
|
657
|
-
if (testEndpoint) {
|
|
658
|
-
return new S3Client({
|
|
659
|
-
region: process.env.KEEL_REGION,
|
|
660
|
-
credentials: {
|
|
661
|
-
accessKeyId: "test",
|
|
662
|
-
secretAccessKey: "test"
|
|
663
|
-
},
|
|
664
|
-
endpointProvider: /* @__PURE__ */ __name(() => {
|
|
665
|
-
return {
|
|
666
|
-
url: new URL(testEndpoint)
|
|
667
|
-
};
|
|
668
|
-
}, "endpointProvider")
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
return new S3Client({
|
|
672
|
-
region: process.env.KEEL_REGION,
|
|
673
|
-
credentials: fromEnv()
|
|
674
|
-
});
|
|
675
|
-
})();
|
|
676
|
-
function rewriteFilesDomain(url) {
|
|
677
|
-
const domain = process.env.KEEL_FILES_DOMAIN;
|
|
678
|
-
if (domain) {
|
|
679
|
-
const override = new URL(domain);
|
|
680
|
-
url.protocol = override.protocol;
|
|
681
|
-
url.hostname = override.hostname;
|
|
682
|
-
url.port = override.port;
|
|
683
|
-
const overridePath = override.pathname.replace(/\/+$/, "");
|
|
684
|
-
if (overridePath && overridePath !== "/") {
|
|
685
|
-
url.pathname = overridePath + url.pathname;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
return url;
|
|
689
|
-
}
|
|
690
|
-
__name(rewriteFilesDomain, "rewriteFilesDomain");
|
|
691
|
-
var InlineFile = class _InlineFile {
|
|
692
|
-
static {
|
|
693
|
-
__name(this, "InlineFile");
|
|
694
|
-
}
|
|
695
|
-
constructor(input) {
|
|
696
|
-
this._filename = input.filename;
|
|
697
|
-
this._contentType = input.contentType;
|
|
698
|
-
this._contents = null;
|
|
699
|
-
}
|
|
700
|
-
static fromDataURL(dataURL) {
|
|
701
|
-
const info = dataURL.split(",")[0].split(":")[1];
|
|
702
|
-
const data = dataURL.split(",")[1];
|
|
703
|
-
const mimeType = info.split(";")[0];
|
|
704
|
-
const name = info.split(";")[1].split("=")[1] || "file";
|
|
705
|
-
const buffer = Buffer.from(data, "base64");
|
|
706
|
-
const file2 = new _InlineFile({ filename: name, contentType: mimeType });
|
|
707
|
-
file2.write(buffer);
|
|
708
|
-
return file2;
|
|
709
|
-
}
|
|
710
|
-
// Gets size of the file's contents in bytes
|
|
711
|
-
get size() {
|
|
712
|
-
if (this._contents) {
|
|
713
|
-
return this._contents.size;
|
|
714
|
-
}
|
|
715
|
-
return 0;
|
|
716
|
-
}
|
|
717
|
-
// Gets the media type of the file contents
|
|
718
|
-
get contentType() {
|
|
719
|
-
return this._contentType;
|
|
720
|
-
}
|
|
721
|
-
// Gets the name of the file
|
|
722
|
-
get filename() {
|
|
723
|
-
return this._filename;
|
|
724
|
-
}
|
|
725
|
-
// Write the files contents from a buffer
|
|
726
|
-
write(buffer) {
|
|
727
|
-
this._contents = new Blob([
|
|
728
|
-
new Uint8Array(
|
|
729
|
-
buffer.buffer,
|
|
730
|
-
buffer.byteOffset,
|
|
731
|
-
buffer.byteLength
|
|
732
|
-
)
|
|
733
|
-
]);
|
|
734
|
-
}
|
|
735
|
-
// Reads the contents of the file as a buffer
|
|
736
|
-
async read() {
|
|
737
|
-
if (!this._contents) {
|
|
738
|
-
throw new Error("No contents to read");
|
|
739
|
-
}
|
|
740
|
-
const arrayBuffer = await this._contents.arrayBuffer();
|
|
741
|
-
return Buffer.from(arrayBuffer);
|
|
742
|
-
}
|
|
743
|
-
// Persists the file
|
|
744
|
-
async store(expires = null) {
|
|
745
|
-
const content = await this.read();
|
|
746
|
-
const key = KSUID.randomSync().string;
|
|
747
|
-
await storeFile(
|
|
748
|
-
content,
|
|
749
|
-
key,
|
|
750
|
-
this._filename,
|
|
751
|
-
this._contentType,
|
|
752
|
-
this.size,
|
|
753
|
-
expires
|
|
754
|
-
);
|
|
755
|
-
return new File({
|
|
756
|
-
key,
|
|
757
|
-
size: this.size,
|
|
758
|
-
filename: this.filename,
|
|
759
|
-
contentType: this.contentType
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
};
|
|
763
|
-
var File = class _File extends InlineFile {
|
|
764
|
-
static {
|
|
765
|
-
__name(this, "File");
|
|
766
|
-
}
|
|
767
|
-
constructor(input) {
|
|
768
|
-
super({
|
|
769
|
-
filename: input.filename || "",
|
|
770
|
-
contentType: input.contentType || ""
|
|
771
|
-
});
|
|
772
|
-
this._key = input.key || "";
|
|
773
|
-
this._size = input.size || 0;
|
|
774
|
-
}
|
|
775
|
-
// Creates a new instance from the database record
|
|
776
|
-
static fromDbRecord(input) {
|
|
777
|
-
return new _File({
|
|
778
|
-
key: input.key,
|
|
779
|
-
filename: input.filename,
|
|
780
|
-
size: input.size,
|
|
781
|
-
contentType: input.contentType
|
|
782
|
-
});
|
|
783
|
-
}
|
|
784
|
-
get size() {
|
|
785
|
-
return this._size;
|
|
786
|
-
}
|
|
787
|
-
// Gets the stored key
|
|
788
|
-
get key() {
|
|
789
|
-
return this._key;
|
|
790
|
-
}
|
|
791
|
-
get isPublic() {
|
|
792
|
-
return false;
|
|
793
|
-
}
|
|
794
|
-
async read() {
|
|
795
|
-
if (this._contents) {
|
|
796
|
-
const arrayBuffer = await this._contents.arrayBuffer();
|
|
797
|
-
return Buffer.from(arrayBuffer);
|
|
798
|
-
}
|
|
799
|
-
if (!s3Client) {
|
|
800
|
-
throw new Error("S3 client is required");
|
|
801
|
-
}
|
|
802
|
-
const params = {
|
|
803
|
-
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
804
|
-
Key: "files/" + this.key
|
|
805
|
-
};
|
|
806
|
-
const command = new GetObjectCommand(params);
|
|
807
|
-
const response = await s3Client.send(command);
|
|
808
|
-
const blob = await response.Body.transformToByteArray();
|
|
809
|
-
return Buffer.from(blob);
|
|
810
|
-
}
|
|
811
|
-
async store(expires = null) {
|
|
812
|
-
if (this._contents) {
|
|
813
|
-
const contents = await this.read();
|
|
814
|
-
await storeFile(
|
|
815
|
-
contents,
|
|
816
|
-
this.key,
|
|
817
|
-
this.filename,
|
|
818
|
-
this.contentType,
|
|
819
|
-
this.size,
|
|
820
|
-
expires
|
|
821
|
-
);
|
|
822
|
-
}
|
|
823
|
-
return this;
|
|
824
|
-
}
|
|
825
|
-
// Generates a presigned download URL
|
|
826
|
-
async getPresignedUrl() {
|
|
827
|
-
if (!s3Client) {
|
|
828
|
-
throw new Error("S3 client is required");
|
|
829
|
-
}
|
|
830
|
-
const command = new GetObjectCommand({
|
|
831
|
-
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
832
|
-
Key: "files/" + this.key,
|
|
833
|
-
ResponseContentDisposition: "inline"
|
|
834
|
-
});
|
|
835
|
-
const url = await getSignedUrl(s3Client, command, { expiresIn: 60 * 60 });
|
|
836
|
-
return rewriteFilesDomain(new URL(url));
|
|
837
|
-
}
|
|
838
|
-
// Generates a presigned upload URL. If the file doesn't have a key, a new one will be generated
|
|
839
|
-
async getPresignedUploadUrl() {
|
|
840
|
-
if (!s3Client) {
|
|
841
|
-
throw new Error("S3 client is required");
|
|
842
|
-
}
|
|
843
|
-
if (!this.key) {
|
|
844
|
-
this._key = KSUID.randomSync().string;
|
|
845
|
-
}
|
|
846
|
-
const command = new PutObjectCommand({
|
|
847
|
-
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
848
|
-
Key: "files/" + this.key
|
|
849
|
-
});
|
|
850
|
-
const url = await getSignedUrl(s3Client, command, { expiresIn: 60 * 60 });
|
|
851
|
-
return rewriteFilesDomain(new URL(url));
|
|
852
|
-
}
|
|
853
|
-
// Persists the file
|
|
854
|
-
toDbRecord() {
|
|
855
|
-
return {
|
|
856
|
-
key: this.key,
|
|
857
|
-
filename: this.filename,
|
|
858
|
-
contentType: this.contentType,
|
|
859
|
-
size: this.size
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
toJSON() {
|
|
863
|
-
return this.toDbRecord();
|
|
864
|
-
}
|
|
865
|
-
};
|
|
866
|
-
async function storeFile(contents, key, filename, contentType, size, expires) {
|
|
867
|
-
if (!s3Client) {
|
|
868
|
-
throw new Error("S3 client is required");
|
|
869
|
-
}
|
|
870
|
-
const params = {
|
|
871
|
-
Bucket: process.env.KEEL_FILES_BUCKET_NAME,
|
|
872
|
-
Key: "files/" + key,
|
|
873
|
-
Body: contents,
|
|
874
|
-
ContentType: contentType,
|
|
875
|
-
ContentDisposition: `attachment; filename="${encodeURIComponent(
|
|
876
|
-
filename
|
|
877
|
-
)}"`,
|
|
878
|
-
Metadata: {
|
|
879
|
-
filename
|
|
880
|
-
},
|
|
881
|
-
ACL: "private"
|
|
882
|
-
};
|
|
883
|
-
if (expires) {
|
|
884
|
-
if (expires instanceof Date) {
|
|
885
|
-
params.Expires = expires;
|
|
886
|
-
} else {
|
|
887
|
-
console.warn("Invalid expires value. Skipping Expires parameter.");
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
const command = new PutObjectCommand(params);
|
|
891
|
-
try {
|
|
892
|
-
await s3Client.send(command);
|
|
893
|
-
} catch (error) {
|
|
894
|
-
console.error("Error uploading file:", error);
|
|
895
|
-
throw error;
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
__name(storeFile, "storeFile");
|
|
899
|
-
|
|
900
985
|
// src/parsing.js
|
|
986
|
+
init_File();
|
|
901
987
|
var dateFormat = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
902
988
|
function parseInputs(inputs) {
|
|
903
989
|
if (inputs != null && typeof inputs === "object") {
|
|
@@ -1103,23 +1189,23 @@ var TimePeriod = class _TimePeriod {
|
|
|
1103
1189
|
return new _TimePeriod(period, value, offset, complete2);
|
|
1104
1190
|
}
|
|
1105
1191
|
periodStartSQL() {
|
|
1106
|
-
let
|
|
1192
|
+
let sql5 = "NOW()";
|
|
1107
1193
|
if (this.offset !== 0) {
|
|
1108
|
-
|
|
1194
|
+
sql5 = `${sql5} + INTERVAL '${this.offset} ${this.period}'`;
|
|
1109
1195
|
}
|
|
1110
1196
|
if (this.complete) {
|
|
1111
|
-
|
|
1197
|
+
sql5 = `DATE_TRUNC('${this.period}', ${sql5})`;
|
|
1112
1198
|
} else {
|
|
1113
|
-
|
|
1199
|
+
sql5 = `(${sql5})`;
|
|
1114
1200
|
}
|
|
1115
|
-
return
|
|
1201
|
+
return sql5;
|
|
1116
1202
|
}
|
|
1117
1203
|
periodEndSQL() {
|
|
1118
|
-
let
|
|
1204
|
+
let sql5 = this.periodStartSQL();
|
|
1119
1205
|
if (this.value != 0) {
|
|
1120
|
-
|
|
1206
|
+
sql5 = `(${sql5} + INTERVAL '${this.value} ${this.period}')`;
|
|
1121
1207
|
}
|
|
1122
|
-
return
|
|
1208
|
+
return sql5;
|
|
1123
1209
|
}
|
|
1124
1210
|
};
|
|
1125
1211
|
|
|
@@ -1833,6 +1919,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
1833
1919
|
};
|
|
1834
1920
|
|
|
1835
1921
|
// src/ModelAPI.js
|
|
1922
|
+
init_File();
|
|
1836
1923
|
var ModelAPI = class {
|
|
1837
1924
|
static {
|
|
1838
1925
|
__name(this, "ModelAPI");
|
|
@@ -1842,9 +1929,10 @@ var ModelAPI = class {
|
|
|
1842
1929
|
* @param {Function} _ Used to be a function that returns the default values for a row in this table. No longer used.
|
|
1843
1930
|
* @param {TableConfigMap} tableConfigMap
|
|
1844
1931
|
*/
|
|
1845
|
-
constructor(tableName, _, tableConfigMap = {}) {
|
|
1932
|
+
constructor(tableName, _, tableConfigMap = {}, fileFieldsMap = {}) {
|
|
1846
1933
|
this._tableName = tableName;
|
|
1847
1934
|
this._tableConfigMap = tableConfigMap;
|
|
1935
|
+
this._fileFields = fileFieldsMap[tableName] || {};
|
|
1848
1936
|
this._modelName = upperCamelCase(this._tableName);
|
|
1849
1937
|
}
|
|
1850
1938
|
async create(values) {
|
|
@@ -1914,6 +2002,10 @@ var ModelAPI = class {
|
|
|
1914
2002
|
const name = spanNameForModelAPI(this._modelName, "update");
|
|
1915
2003
|
const db = useDatabase();
|
|
1916
2004
|
return withSpan(name, async (span) => {
|
|
2005
|
+
const fileColumns = Object.keys(values || {}).filter(
|
|
2006
|
+
(k) => k in this._fileFields
|
|
2007
|
+
);
|
|
2008
|
+
const existingFileRows = fileColumns.length > 0 ? await this._selectExistingFileValues(where, fileColumns) : [];
|
|
1917
2009
|
let builder = db.updateTable(this._tableName).returningAll();
|
|
1918
2010
|
const keys = values ? Object.keys(values) : [];
|
|
1919
2011
|
const row = {};
|
|
@@ -1952,7 +2044,9 @@ var ModelAPI = class {
|
|
|
1952
2044
|
span.setAttribute("sql", builder.compile().sql);
|
|
1953
2045
|
try {
|
|
1954
2046
|
const row2 = await builder.executeTakeFirstOrThrow();
|
|
1955
|
-
|
|
2047
|
+
const result = transformRichDataTypes(camelCaseObject(row2));
|
|
2048
|
+
this._deferReplacedFiles(existingFileRows, fileColumns, result);
|
|
2049
|
+
return result;
|
|
1956
2050
|
} catch (e) {
|
|
1957
2051
|
throw new DatabaseError(e);
|
|
1958
2052
|
}
|
|
@@ -1962,12 +2056,15 @@ var ModelAPI = class {
|
|
|
1962
2056
|
const name = spanNameForModelAPI(this._modelName, "delete");
|
|
1963
2057
|
const db = useDatabase();
|
|
1964
2058
|
return withSpan(name, async (span) => {
|
|
2059
|
+
const fileColumns = Object.keys(this._fileFields);
|
|
2060
|
+
const existingFileRows = fileColumns.length > 0 ? await this._selectExistingFileValues(where, fileColumns) : [];
|
|
1965
2061
|
let builder = db.deleteFrom(this._tableName).returning(["id"]);
|
|
1966
2062
|
const context7 = new QueryContext([this._tableName], this._tableConfigMap);
|
|
1967
2063
|
builder = applyWhereConditions(context7, builder, where);
|
|
1968
2064
|
span.setAttribute("sql", builder.compile().sql);
|
|
1969
2065
|
try {
|
|
1970
2066
|
const row = await builder.executeTakeFirstOrThrow();
|
|
2067
|
+
this._deferReplacedFiles(existingFileRows, fileColumns, null);
|
|
1971
2068
|
return row.id;
|
|
1972
2069
|
} catch (e) {
|
|
1973
2070
|
throw new DatabaseError(e);
|
|
@@ -1982,6 +2079,30 @@ var ModelAPI = class {
|
|
|
1982
2079
|
builder = applyWhereConditions(context7, builder, where);
|
|
1983
2080
|
return new QueryBuilder(this._tableName, context7, builder);
|
|
1984
2081
|
}
|
|
2082
|
+
// Reads the current file-column values for rows matched by `where`.
|
|
2083
|
+
async _selectExistingFileValues(where, fileColumns) {
|
|
2084
|
+
if (fileColumns.length === 0) {
|
|
2085
|
+
return [];
|
|
2086
|
+
}
|
|
2087
|
+
const db = useDatabase();
|
|
2088
|
+
let builder = db.selectFrom(this._tableName).selectAll(this._tableName);
|
|
2089
|
+
const context7 = new QueryContext([this._tableName], this._tableConfigMap);
|
|
2090
|
+
builder = applyWhereConditions(context7, builder, where);
|
|
2091
|
+
const rows = await builder.execute();
|
|
2092
|
+
return rows.map((x) => transformRichDataTypes(camelCaseObject(x)));
|
|
2093
|
+
}
|
|
2094
|
+
// Defers deletion of every old file key in existingRows that is not still
|
|
2095
|
+
// referenced by newRow.
|
|
2096
|
+
_deferReplacedFiles(existingRows, fileColumns, newRow) {
|
|
2097
|
+
const retained = new Set(
|
|
2098
|
+
collectFileKeys(newRow ? [newRow] : [], fileColumns)
|
|
2099
|
+
);
|
|
2100
|
+
for (const key of collectFileKeys(existingRows, fileColumns)) {
|
|
2101
|
+
if (!retained.has(key)) {
|
|
2102
|
+
deferFileDeletion(key);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
1985
2106
|
};
|
|
1986
2107
|
async function create(conn, tableName, tableConfigs, values) {
|
|
1987
2108
|
try {
|
|
@@ -2091,6 +2212,23 @@ async function create(conn, tableName, tableConfigs, values) {
|
|
|
2091
2212
|
}
|
|
2092
2213
|
}
|
|
2093
2214
|
__name(create, "create");
|
|
2215
|
+
function collectFileKeys(rows, fileColumns) {
|
|
2216
|
+
const keys = [];
|
|
2217
|
+
for (const row of rows || []) {
|
|
2218
|
+
if (!row) continue;
|
|
2219
|
+
for (const col of fileColumns) {
|
|
2220
|
+
const v = row[col];
|
|
2221
|
+
if (!v) continue;
|
|
2222
|
+
if (Array.isArray(v)) {
|
|
2223
|
+
for (const f of v) if (f?.key) keys.push(f.key);
|
|
2224
|
+
} else if (v.key) {
|
|
2225
|
+
keys.push(v.key);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
return keys;
|
|
2230
|
+
}
|
|
2231
|
+
__name(collectFileKeys, "collectFileKeys");
|
|
2094
2232
|
|
|
2095
2233
|
// src/TaskAPI.js
|
|
2096
2234
|
import jwt from "jsonwebtoken";
|
|
@@ -2636,6 +2774,51 @@ var FlowsAPI = class _FlowsAPI {
|
|
|
2636
2774
|
);
|
|
2637
2775
|
});
|
|
2638
2776
|
}
|
|
2777
|
+
/**
|
|
2778
|
+
* Mints a signed link that lets an external, unauthenticated actor execute this flow. The flow
|
|
2779
|
+
* must be declared with @externalAccess. Each time the link is opened a fresh flow run is created;
|
|
2780
|
+
* a reusable link can be opened many times, otherwise it permits exactly one run.
|
|
2781
|
+
* @param {Object} [options] Signed link options
|
|
2782
|
+
* @param {Object} [options.inputs] Default inputs applied to every run created from the link
|
|
2783
|
+
* @param {Date} [options.expiresAt] When the link stops being valid
|
|
2784
|
+
* @param {boolean} [options.reusable] Whether the link can be opened more than once
|
|
2785
|
+
* @returns {Promise<{url: string, expiresAt: Date|null, flow: {name: string, runId: string|null}}>} The signed link
|
|
2786
|
+
*/
|
|
2787
|
+
async signedLink(options = {}) {
|
|
2788
|
+
const name = spanNameForModelAPI(this._flowName, "signedLink");
|
|
2789
|
+
return withSpan(name, async () => {
|
|
2790
|
+
const apiUrl = getApiUrl2();
|
|
2791
|
+
const url = `${apiUrl}/flows/json/${encodeURIComponent(
|
|
2792
|
+
this._flowName
|
|
2793
|
+
)}/share`;
|
|
2794
|
+
const body = {};
|
|
2795
|
+
if (options.inputs !== void 0) body.inputs = options.inputs;
|
|
2796
|
+
if (options.reusable !== void 0) body.reusable = options.reusable;
|
|
2797
|
+
if (options.expiresAt !== void 0 && options.expiresAt !== null) {
|
|
2798
|
+
body.expiresAt = options.expiresAt instanceof Date ? options.expiresAt.toISOString() : options.expiresAt;
|
|
2799
|
+
}
|
|
2800
|
+
const response = await fetch(url, {
|
|
2801
|
+
method: "POST",
|
|
2802
|
+
headers: buildHeaders2(this._identity, this._authToken),
|
|
2803
|
+
body: JSON.stringify(body)
|
|
2804
|
+
});
|
|
2805
|
+
if (!response.ok) {
|
|
2806
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
2807
|
+
throw new Error(
|
|
2808
|
+
`Failed to create signed link: ${response.status} ${response.statusText} - ${errorBody.message || JSON.stringify(errorBody)}`
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
const result = await response.json();
|
|
2812
|
+
return {
|
|
2813
|
+
url: result.url,
|
|
2814
|
+
expiresAt: result.expiresAt ? new Date(result.expiresAt) : null,
|
|
2815
|
+
flow: {
|
|
2816
|
+
name: result.flow?.name ?? this._flowName,
|
|
2817
|
+
runId: result.flow?.runId ?? null
|
|
2818
|
+
}
|
|
2819
|
+
};
|
|
2820
|
+
});
|
|
2821
|
+
}
|
|
2639
2822
|
/**
|
|
2640
2823
|
* Gets a flow run by ID.
|
|
2641
2824
|
* @param {string} runId The flow run ID
|
|
@@ -2717,6 +2900,61 @@ var FlowsAPI = class _FlowsAPI {
|
|
|
2717
2900
|
}
|
|
2718
2901
|
};
|
|
2719
2902
|
|
|
2903
|
+
// src/integrationServer.js
|
|
2904
|
+
import jwt3 from "jsonwebtoken";
|
|
2905
|
+
function buildHeaders3(identity) {
|
|
2906
|
+
const headers = { "Content-Type": "application/json" };
|
|
2907
|
+
const base64pk = process.env.KEEL_PRIVATE_KEY;
|
|
2908
|
+
if (!base64pk) {
|
|
2909
|
+
throw new Error(
|
|
2910
|
+
"KEEL_PRIVATE_KEY is not set; cannot sign the integration proxy token"
|
|
2911
|
+
);
|
|
2912
|
+
}
|
|
2913
|
+
const privateKey = Buffer.from(base64pk, "base64").toString("utf8");
|
|
2914
|
+
const subject = identity && identity.id ? identity.id : "integration-proxy";
|
|
2915
|
+
headers["Authorization"] = "Bearer " + jwt3.sign({}, privateKey, {
|
|
2916
|
+
algorithm: "RS256",
|
|
2917
|
+
expiresIn: 60 * 60 * 24,
|
|
2918
|
+
subject,
|
|
2919
|
+
// Scope the token to the integration proxy so it can't be mistaken for an ordinary user
|
|
2920
|
+
// access token; the runtime proxy verifies this audience before injecting credentials.
|
|
2921
|
+
audience: "integration-proxy",
|
|
2922
|
+
issuer: "https://keel.so"
|
|
2923
|
+
});
|
|
2924
|
+
return headers;
|
|
2925
|
+
}
|
|
2926
|
+
__name(buildHeaders3, "buildHeaders");
|
|
2927
|
+
function getApiUrl3() {
|
|
2928
|
+
const apiUrl = process.env.KEEL_API_URL;
|
|
2929
|
+
if (!apiUrl) {
|
|
2930
|
+
throw new Error("KEEL_API_URL environment variable is not set");
|
|
2931
|
+
}
|
|
2932
|
+
return apiUrl;
|
|
2933
|
+
}
|
|
2934
|
+
__name(getApiUrl3, "getApiUrl");
|
|
2935
|
+
function createIntegrationServer(name, identity) {
|
|
2936
|
+
return {
|
|
2937
|
+
do: /* @__PURE__ */ __name(async (request) => {
|
|
2938
|
+
const url = `${getApiUrl3()}/integrations/${encodeURIComponent(
|
|
2939
|
+
name
|
|
2940
|
+
)}/proxy`;
|
|
2941
|
+
const response = await fetch(url, {
|
|
2942
|
+
method: "POST",
|
|
2943
|
+
headers: buildHeaders3(identity ?? null),
|
|
2944
|
+
body: JSON.stringify(request ?? {})
|
|
2945
|
+
});
|
|
2946
|
+
if (!response.ok) {
|
|
2947
|
+
const text = await response.text();
|
|
2948
|
+
throw new Error(
|
|
2949
|
+
`integration "${name}" proxy request failed (${response.status}): ${text}`
|
|
2950
|
+
);
|
|
2951
|
+
}
|
|
2952
|
+
return await response.json();
|
|
2953
|
+
}, "do")
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
__name(createIntegrationServer, "createIntegrationServer");
|
|
2957
|
+
|
|
2720
2958
|
// src/RequestHeaders.ts
|
|
2721
2959
|
var RequestHeaders = class {
|
|
2722
2960
|
/**
|
|
@@ -3917,6 +4155,7 @@ var datePickerInput = /* @__PURE__ */ __name((name, options) => {
|
|
|
3917
4155
|
}, "datePickerInput");
|
|
3918
4156
|
|
|
3919
4157
|
// src/flows/ui/elements/input/file.ts
|
|
4158
|
+
init_File();
|
|
3920
4159
|
var fileInput = /* @__PURE__ */ __name((name, options) => {
|
|
3921
4160
|
return {
|
|
3922
4161
|
__type: "input",
|
|
@@ -3942,6 +4181,7 @@ var fileInput = /* @__PURE__ */ __name((name, options) => {
|
|
|
3942
4181
|
}, "fileInput");
|
|
3943
4182
|
|
|
3944
4183
|
// src/flows/ui/elements/input/imageCapture.ts
|
|
4184
|
+
init_File();
|
|
3945
4185
|
var isMultiOptions = /* @__PURE__ */ __name((opts) => opts && opts.mode === "multi", "isMultiOptions");
|
|
3946
4186
|
function validateEntry(entry, requireCaption, context7) {
|
|
3947
4187
|
if (!entry?.file?.key) {
|
|
@@ -4174,6 +4414,233 @@ var file = /* @__PURE__ */ __name(async (options) => {
|
|
|
4174
4414
|
}, "file");
|
|
4175
4415
|
|
|
4176
4416
|
// src/flows/index.ts
|
|
4417
|
+
import { sql as sql4 } from "kysely";
|
|
4418
|
+
|
|
4419
|
+
// src/agents/engine.ts
|
|
4420
|
+
var ENGINE = Symbol.for("keel.agents.engine");
|
|
4421
|
+
var ScriptedEngine = class {
|
|
4422
|
+
constructor(script) {
|
|
4423
|
+
this.script = script;
|
|
4424
|
+
this._requests = [];
|
|
4425
|
+
this.index = 0;
|
|
4426
|
+
}
|
|
4427
|
+
static {
|
|
4428
|
+
__name(this, "ScriptedEngine");
|
|
4429
|
+
}
|
|
4430
|
+
get requests() {
|
|
4431
|
+
return this._requests;
|
|
4432
|
+
}
|
|
4433
|
+
async turn(req) {
|
|
4434
|
+
this._requests.push(req);
|
|
4435
|
+
if (this.index >= this.script.length) {
|
|
4436
|
+
throw new Error(
|
|
4437
|
+
`ScriptedEngine: no scripted result for turn ${this.index + 1}`
|
|
4438
|
+
);
|
|
4439
|
+
}
|
|
4440
|
+
const entry = this.script[this.index++];
|
|
4441
|
+
return typeof entry === "function" ? entry(req) : entry;
|
|
4442
|
+
}
|
|
4443
|
+
};
|
|
4444
|
+
var callCounter = 0;
|
|
4445
|
+
var scriptedTurn = {
|
|
4446
|
+
text(text) {
|
|
4447
|
+
return {
|
|
4448
|
+
text,
|
|
4449
|
+
toolCalls: [],
|
|
4450
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
4451
|
+
stopReason: "end_turn"
|
|
4452
|
+
};
|
|
4453
|
+
},
|
|
4454
|
+
/** Auto-generated ids increment per process — pass an explicit `id` when asserting on ids in tests. */
|
|
4455
|
+
toolCall(name, args, id) {
|
|
4456
|
+
return {
|
|
4457
|
+
text: "",
|
|
4458
|
+
toolCalls: [
|
|
4459
|
+
{ id: id ?? `call_${name}_${++callCounter}`, name, arguments: args }
|
|
4460
|
+
],
|
|
4461
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
4462
|
+
stopReason: "tool_calls"
|
|
4463
|
+
};
|
|
4464
|
+
}
|
|
4465
|
+
};
|
|
4466
|
+
|
|
4467
|
+
// src/agents/engineResolver.ts
|
|
4468
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
4469
|
+
|
|
4470
|
+
// src/agents/aiSdkEngine.ts
|
|
4471
|
+
import {
|
|
4472
|
+
generateText,
|
|
4473
|
+
jsonSchema,
|
|
4474
|
+
tool,
|
|
4475
|
+
stepCountIs
|
|
4476
|
+
} from "ai";
|
|
4477
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
4478
|
+
var AiSdkEngine = class {
|
|
4479
|
+
constructor(opts) {
|
|
4480
|
+
this.opts = opts;
|
|
4481
|
+
}
|
|
4482
|
+
static {
|
|
4483
|
+
__name(this, "AiSdkEngine");
|
|
4484
|
+
}
|
|
4485
|
+
// Exposed as a method (not arrow function) so tests can monkey-patch at the
|
|
4486
|
+
// instance level: (engine as any).resolveModel = () => mockModel(...)
|
|
4487
|
+
resolveModel(model) {
|
|
4488
|
+
const slash = model.indexOf("/");
|
|
4489
|
+
const provider = slash === -1 ? "anthropic" : model.slice(0, slash);
|
|
4490
|
+
const id = slash === -1 ? model : model.slice(slash + 1);
|
|
4491
|
+
if (provider !== "anthropic") {
|
|
4492
|
+
throw new Error(
|
|
4493
|
+
`Unsupported provider "${provider}" in model "${model}" \u2014 only anthropic/* is supported in this milestone`
|
|
4494
|
+
);
|
|
4495
|
+
}
|
|
4496
|
+
const baseURL = this.opts.baseURL ?? "https://api.anthropic.com/v1";
|
|
4497
|
+
if (this.opts.authToken) {
|
|
4498
|
+
return createAnthropic({
|
|
4499
|
+
apiKey: "unused",
|
|
4500
|
+
baseURL,
|
|
4501
|
+
headers: {
|
|
4502
|
+
"x-api-key": "",
|
|
4503
|
+
authorization: `Bearer ${this.opts.authToken}`,
|
|
4504
|
+
"anthropic-beta": "oauth-2025-04-20"
|
|
4505
|
+
}
|
|
4506
|
+
})(id);
|
|
4507
|
+
}
|
|
4508
|
+
if (!this.opts.apiKey) {
|
|
4509
|
+
throw new Error("AiSdkEngine requires an apiKey or authToken");
|
|
4510
|
+
}
|
|
4511
|
+
return createAnthropic({
|
|
4512
|
+
apiKey: this.opts.apiKey,
|
|
4513
|
+
baseURL
|
|
4514
|
+
})(id);
|
|
4515
|
+
}
|
|
4516
|
+
async turn(req) {
|
|
4517
|
+
const model = this.resolveModel(req.model);
|
|
4518
|
+
const tools = {};
|
|
4519
|
+
for (const spec of req.tools ?? []) {
|
|
4520
|
+
tools[spec.name] = tool({
|
|
4521
|
+
description: spec.description,
|
|
4522
|
+
inputSchema: jsonSchema(
|
|
4523
|
+
spec.parameters
|
|
4524
|
+
)
|
|
4525
|
+
});
|
|
4526
|
+
}
|
|
4527
|
+
let toolChoice;
|
|
4528
|
+
if (req.toolChoice === "auto") {
|
|
4529
|
+
toolChoice = "auto";
|
|
4530
|
+
} else if (req.toolChoice && typeof req.toolChoice === "object") {
|
|
4531
|
+
toolChoice = { type: "tool", toolName: req.toolChoice.name };
|
|
4532
|
+
}
|
|
4533
|
+
const messages = req.messages.map((msg) => {
|
|
4534
|
+
if (msg.role === "user") {
|
|
4535
|
+
return { role: "user", content: msg.content };
|
|
4536
|
+
}
|
|
4537
|
+
if (msg.role === "assistant") {
|
|
4538
|
+
const content = [];
|
|
4539
|
+
if (msg.content) {
|
|
4540
|
+
content.push({ type: "text", text: msg.content });
|
|
4541
|
+
}
|
|
4542
|
+
for (const tc of msg.toolCalls ?? []) {
|
|
4543
|
+
content.push({
|
|
4544
|
+
type: "tool-call",
|
|
4545
|
+
toolCallId: tc.id,
|
|
4546
|
+
toolName: tc.name,
|
|
4547
|
+
input: tc.arguments
|
|
4548
|
+
});
|
|
4549
|
+
}
|
|
4550
|
+
return { role: "assistant", content };
|
|
4551
|
+
}
|
|
4552
|
+
return {
|
|
4553
|
+
role: "tool",
|
|
4554
|
+
content: [
|
|
4555
|
+
{
|
|
4556
|
+
type: "tool-result",
|
|
4557
|
+
toolCallId: msg.toolCallId,
|
|
4558
|
+
toolName: msg.toolName,
|
|
4559
|
+
output: { type: "text", value: msg.result }
|
|
4560
|
+
}
|
|
4561
|
+
]
|
|
4562
|
+
};
|
|
4563
|
+
});
|
|
4564
|
+
let res;
|
|
4565
|
+
try {
|
|
4566
|
+
res = await generateText({
|
|
4567
|
+
model,
|
|
4568
|
+
system: req.system,
|
|
4569
|
+
messages,
|
|
4570
|
+
tools: Object.keys(tools).length > 0 ? tools : void 0,
|
|
4571
|
+
toolChoice,
|
|
4572
|
+
stopWhen: stepCountIs(1)
|
|
4573
|
+
});
|
|
4574
|
+
} catch (e) {
|
|
4575
|
+
throw new Error(describeAiError(e));
|
|
4576
|
+
}
|
|
4577
|
+
const toolCalls = res.toolCalls.map((tc) => ({
|
|
4578
|
+
id: tc.toolCallId,
|
|
4579
|
+
name: tc.toolName,
|
|
4580
|
+
arguments: tc.input
|
|
4581
|
+
}));
|
|
4582
|
+
return {
|
|
4583
|
+
text: res.text,
|
|
4584
|
+
toolCalls,
|
|
4585
|
+
usage: {
|
|
4586
|
+
inputTokens: res.usage.inputTokens ?? 0,
|
|
4587
|
+
outputTokens: res.usage.outputTokens ?? 0
|
|
4588
|
+
},
|
|
4589
|
+
stopReason: toolCalls.length > 0 ? "tool_calls" : "end_turn"
|
|
4590
|
+
};
|
|
4591
|
+
}
|
|
4592
|
+
};
|
|
4593
|
+
function describeAiError(e) {
|
|
4594
|
+
const errors = e?.errors;
|
|
4595
|
+
const last = errors && errors.length > 0 ? errors[errors.length - 1] : e;
|
|
4596
|
+
const err = last;
|
|
4597
|
+
let msg = "LLM request failed";
|
|
4598
|
+
if (err?.statusCode) msg += ` (HTTP ${err.statusCode})`;
|
|
4599
|
+
const detail = err?.responseBody?.slice(0, 300) || (err?.message && err.message !== "Error" ? err.message : void 0);
|
|
4600
|
+
if (detail) msg += `: ${detail}`;
|
|
4601
|
+
if (err?.url) msg += ` [${err.url}]`;
|
|
4602
|
+
return msg;
|
|
4603
|
+
}
|
|
4604
|
+
__name(describeAiError, "describeAiError");
|
|
4605
|
+
|
|
4606
|
+
// src/agents/engineResolver.ts
|
|
4607
|
+
var _fileScriptedEngine;
|
|
4608
|
+
function resolveEngine(injected, secrets) {
|
|
4609
|
+
if (injected) return injected;
|
|
4610
|
+
const scriptPath = process.env.FAKE_LLM_SCRIPT;
|
|
4611
|
+
if (scriptPath) {
|
|
4612
|
+
if (!_fileScriptedEngine || _fileScriptedEngine.path !== scriptPath) {
|
|
4613
|
+
const script = JSON.parse(readFileSync2(scriptPath, "utf8"));
|
|
4614
|
+
_fileScriptedEngine = {
|
|
4615
|
+
path: scriptPath,
|
|
4616
|
+
engine: new ScriptedEngine(script)
|
|
4617
|
+
};
|
|
4618
|
+
}
|
|
4619
|
+
return _fileScriptedEngine.engine;
|
|
4620
|
+
}
|
|
4621
|
+
const key = secrets?.["ANTHROPIC_API_KEY"];
|
|
4622
|
+
if (!key) {
|
|
4623
|
+
throw new Error(
|
|
4624
|
+
"No LLM engine available \u2014 set the ANTHROPIC_API_KEY secret, or inject an engine in tests"
|
|
4625
|
+
);
|
|
4626
|
+
}
|
|
4627
|
+
if (key.startsWith("sk-ant-oat")) {
|
|
4628
|
+
return new AiSdkEngine({ authToken: key });
|
|
4629
|
+
}
|
|
4630
|
+
return new AiSdkEngine({ apiKey: key });
|
|
4631
|
+
}
|
|
4632
|
+
__name(resolveEngine, "resolveEngine");
|
|
4633
|
+
function resolveEngineForContext(context7) {
|
|
4634
|
+
const get = context7?.[ENGINE];
|
|
4635
|
+
if (typeof get !== "function") {
|
|
4636
|
+
throw new Error("An LLM engine is only available within a flow context");
|
|
4637
|
+
}
|
|
4638
|
+
return get();
|
|
4639
|
+
}
|
|
4640
|
+
__name(resolveEngineForContext, "resolveEngineForContext");
|
|
4641
|
+
|
|
4642
|
+
// src/flows/index.ts
|
|
4643
|
+
import KSUID2 from "ksuid";
|
|
4177
4644
|
var STEP_STATUS = /* @__PURE__ */ ((STEP_STATUS2) => {
|
|
4178
4645
|
STEP_STATUS2["NEW"] = "NEW";
|
|
4179
4646
|
STEP_STATUS2["RUNNING"] = "RUNNING";
|
|
@@ -4210,28 +4677,30 @@ var defaultOpts = {
|
|
|
4210
4677
|
async function insertNewStep(db, runId, name, stage) {
|
|
4211
4678
|
await db.transaction().execute(async (trx) => {
|
|
4212
4679
|
await trx.selectFrom("keel.flow_run").select("id").where("id", "=", runId).forUpdate().executeTakeFirst();
|
|
4213
|
-
const existing = await trx.selectFrom("keel.flow_step").select("id").where("run_id", "=", runId).where("name", "=", name).where("status", "
|
|
4680
|
+
const existing = await trx.selectFrom("keel.flow_step").select("id").where("run_id", "=", runId).where("name", "=", name).where("status", "in", ["NEW" /* NEW */, "RUNNING" /* RUNNING */]).executeTakeFirst();
|
|
4214
4681
|
if (existing) {
|
|
4215
4682
|
return;
|
|
4216
4683
|
}
|
|
4217
4684
|
await trx.insertInto("keel.flow_step").values({
|
|
4685
|
+
id: KSUID2.randomSync().string,
|
|
4218
4686
|
run_id: runId,
|
|
4219
4687
|
name,
|
|
4220
4688
|
stage,
|
|
4221
4689
|
status: "NEW" /* NEW */,
|
|
4222
4690
|
type: "FUNCTION" /* FUNCTION */
|
|
4223
|
-
}).
|
|
4691
|
+
}).returningAll().executeTakeFirst();
|
|
4224
4692
|
});
|
|
4225
4693
|
}
|
|
4226
4694
|
__name(insertNewStep, "insertNewStep");
|
|
4227
4695
|
function createFlowContext(runId, data, action, callback, element, spanId, ctx) {
|
|
4228
4696
|
const usedNames = /* @__PURE__ */ new Set();
|
|
4229
|
-
|
|
4697
|
+
const context7 = {
|
|
4230
4698
|
identity: ctx.identity,
|
|
4231
4699
|
env: ctx.env,
|
|
4232
4700
|
now: ctx.now,
|
|
4233
4701
|
secrets: ctx.secrets,
|
|
4234
4702
|
trace: ctx.trace,
|
|
4703
|
+
module: ctx.module,
|
|
4235
4704
|
complete: /* @__PURE__ */ __name((options) => {
|
|
4236
4705
|
return {
|
|
4237
4706
|
__type: "ui.complete",
|
|
@@ -4251,6 +4720,7 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4251
4720
|
if (usedNames.has(name)) {
|
|
4252
4721
|
span.setAttribute("step.status", "FAILED" /* FAILED */);
|
|
4253
4722
|
await db.insertInto("keel.flow_step").values({
|
|
4723
|
+
id: KSUID2.randomSync().string,
|
|
4254
4724
|
run_id: runId,
|
|
4255
4725
|
name,
|
|
4256
4726
|
stage: options.stage,
|
|
@@ -4263,7 +4733,7 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4263
4733
|
throw new Error(`Duplicate step name: ${name}`);
|
|
4264
4734
|
}
|
|
4265
4735
|
usedNames.add(name);
|
|
4266
|
-
const past = await db.selectFrom("keel.flow_step").where("run_id", "=", runId).where("name", "=", name).selectAll().execute();
|
|
4736
|
+
const past = await db.selectFrom("keel.flow_step").where("run_id", "=", runId).where("name", "=", name).selectAll().select(sql4`value::text`.as("valueRaw")).execute();
|
|
4267
4737
|
const newSteps = past.filter(
|
|
4268
4738
|
(step) => step.status === "NEW" /* NEW */
|
|
4269
4739
|
);
|
|
@@ -4290,7 +4760,13 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4290
4760
|
if (completedSteps.length === 1) {
|
|
4291
4761
|
span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
|
|
4292
4762
|
span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
|
|
4293
|
-
|
|
4763
|
+
const raw = completedSteps[0].valueRaw;
|
|
4764
|
+
return raw == null ? void 0 : JSON.parse(raw);
|
|
4765
|
+
}
|
|
4766
|
+
if (runningSteps.length >= 1) {
|
|
4767
|
+
span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
|
|
4768
|
+
span.setAttribute("step.status", "RUNNING" /* RUNNING */);
|
|
4769
|
+
throw new StepCreatedDisrupt();
|
|
4294
4770
|
}
|
|
4295
4771
|
if (newSteps.length === 1) {
|
|
4296
4772
|
let result = null;
|
|
@@ -4316,7 +4792,14 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4316
4792
|
endTime: /* @__PURE__ */ new Date(),
|
|
4317
4793
|
error: e instanceof Error ? e.message : "An error occurred"
|
|
4318
4794
|
}).where("id", "=", newSteps[0].id).returningAll().executeTakeFirst();
|
|
4319
|
-
if (
|
|
4795
|
+
if (e instanceof NonRetriableError) {
|
|
4796
|
+
span.setAttribute("step.status", "FAILED" /* FAILED */);
|
|
4797
|
+
if (options.onFailure) {
|
|
4798
|
+
await options.onFailure();
|
|
4799
|
+
}
|
|
4800
|
+
throw e;
|
|
4801
|
+
}
|
|
4802
|
+
if (failedSteps.length >= options.retries) {
|
|
4320
4803
|
span.setAttribute("step.status", "FAILED" /* FAILED */);
|
|
4321
4804
|
if (options.onFailure) {
|
|
4322
4805
|
await options.onFailure();
|
|
@@ -4340,11 +4823,6 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4340
4823
|
span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
|
|
4341
4824
|
return result;
|
|
4342
4825
|
}
|
|
4343
|
-
if (runningSteps.length >= 1) {
|
|
4344
|
-
span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
|
|
4345
|
-
span.setAttribute("step.status", "RUNNING" /* RUNNING */);
|
|
4346
|
-
throw new StepCreatedDisrupt();
|
|
4347
|
-
}
|
|
4348
4826
|
await insertNewStep(db, runId, name, options.stage);
|
|
4349
4827
|
span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
|
|
4350
4828
|
span.setAttribute("step.status", "NEW" /* NEW */);
|
|
@@ -4363,6 +4841,7 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4363
4841
|
if (usedNames.has(name)) {
|
|
4364
4842
|
span.setAttribute("step.status", "FAILED" /* FAILED */);
|
|
4365
4843
|
await db.insertInto("keel.flow_step").values({
|
|
4844
|
+
id: KSUID2.randomSync().string,
|
|
4366
4845
|
run_id: runId,
|
|
4367
4846
|
name,
|
|
4368
4847
|
stage: options.stage,
|
|
@@ -4377,11 +4856,12 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4377
4856
|
usedNames.add(name);
|
|
4378
4857
|
const { step, inserted } = await db.transaction().execute(async (trx) => {
|
|
4379
4858
|
await trx.selectFrom("keel.flow_run").select("id").where("id", "=", runId).forUpdate().executeTakeFirst();
|
|
4380
|
-
const existing = await trx.selectFrom("keel.flow_step").where("run_id", "=", runId).where("name", "=", name).selectAll().executeTakeFirst();
|
|
4859
|
+
const existing = await trx.selectFrom("keel.flow_step").where("run_id", "=", runId).where("name", "=", name).selectAll().select(sql4`value::text`.as("valueRaw")).executeTakeFirst();
|
|
4381
4860
|
if (existing) {
|
|
4382
4861
|
return { step: existing, inserted: false };
|
|
4383
4862
|
}
|
|
4384
4863
|
const created = await trx.insertInto("keel.flow_step").values({
|
|
4864
|
+
id: KSUID2.randomSync().string,
|
|
4385
4865
|
run_id: runId,
|
|
4386
4866
|
name,
|
|
4387
4867
|
stage: options.stage,
|
|
@@ -4394,9 +4874,11 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4394
4874
|
if (step && step.status === "COMPLETED" /* COMPLETED */) {
|
|
4395
4875
|
span.setAttribute(KEEL_INTERNAL_ATTR, KEEL_INTERNAL_CHILDREN);
|
|
4396
4876
|
span.setAttribute("step.status", "COMPLETED" /* COMPLETED */);
|
|
4877
|
+
const rawValue = step.valueRaw;
|
|
4878
|
+
const storedData = rawValue == null ? null : JSON.parse(rawValue);
|
|
4397
4879
|
const parsedData2 = await applyElementGetData(
|
|
4398
4880
|
options.content,
|
|
4399
|
-
transformRichDataTypes(
|
|
4881
|
+
transformRichDataTypes(storedData)
|
|
4400
4882
|
);
|
|
4401
4883
|
if (step.action) {
|
|
4402
4884
|
return { data: parsedData2, action: step.action };
|
|
@@ -4512,6 +4994,8 @@ function createFlowContext(runId, data, action, callback, element, spanId, ctx)
|
|
|
4512
4994
|
}
|
|
4513
4995
|
}
|
|
4514
4996
|
};
|
|
4997
|
+
context7[ENGINE] = () => resolveEngine(ctx.engine, ctx.secrets);
|
|
4998
|
+
return context7;
|
|
4515
4999
|
}
|
|
4516
5000
|
__name(createFlowContext, "createFlowContext");
|
|
4517
5001
|
function wait(milliseconds) {
|
|
@@ -4718,19 +5202,558 @@ async function handleFlow(request, config) {
|
|
|
4718
5202
|
__name(handleFlow, "handleFlow");
|
|
4719
5203
|
|
|
4720
5204
|
// src/index.ts
|
|
4721
|
-
import
|
|
5205
|
+
import KSUID4 from "ksuid";
|
|
5206
|
+
init_File();
|
|
5207
|
+
|
|
5208
|
+
// src/notifications/email-template.tsx
|
|
5209
|
+
import {
|
|
5210
|
+
Html,
|
|
5211
|
+
Head,
|
|
5212
|
+
Body,
|
|
5213
|
+
Container,
|
|
5214
|
+
Heading,
|
|
5215
|
+
Text,
|
|
5216
|
+
Button,
|
|
5217
|
+
Section
|
|
5218
|
+
} from "@react-email/components";
|
|
5219
|
+
import { render } from "@react-email/render";
|
|
5220
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5221
|
+
function renderBody(text) {
|
|
5222
|
+
return text.split("\n").flatMap((line, i) => i === 0 ? [line] : [/* @__PURE__ */ jsx("br", {}, `br-${i}`), line]);
|
|
5223
|
+
}
|
|
5224
|
+
__name(renderBody, "renderBody");
|
|
5225
|
+
var StockEmailTemplate = /* @__PURE__ */ __name(({ content }) => /* @__PURE__ */ jsxs(Html, { children: [
|
|
5226
|
+
/* @__PURE__ */ jsx(Head, {}),
|
|
5227
|
+
/* @__PURE__ */ jsx(
|
|
5228
|
+
Body,
|
|
5229
|
+
{
|
|
5230
|
+
style: {
|
|
5231
|
+
backgroundColor: "#f6f6f6",
|
|
5232
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
|
|
5233
|
+
},
|
|
5234
|
+
children: /* @__PURE__ */ jsxs(
|
|
5235
|
+
Container,
|
|
5236
|
+
{
|
|
5237
|
+
style: {
|
|
5238
|
+
backgroundColor: "#ffffff",
|
|
5239
|
+
margin: "40px auto",
|
|
5240
|
+
padding: "40px",
|
|
5241
|
+
maxWidth: "560px",
|
|
5242
|
+
borderRadius: "8px"
|
|
5243
|
+
},
|
|
5244
|
+
children: [
|
|
5245
|
+
content.title && /* @__PURE__ */ jsx(
|
|
5246
|
+
Heading,
|
|
5247
|
+
{
|
|
5248
|
+
style: {
|
|
5249
|
+
fontSize: "22px",
|
|
5250
|
+
fontWeight: 600,
|
|
5251
|
+
color: "#111827",
|
|
5252
|
+
margin: "0 0 16px"
|
|
5253
|
+
},
|
|
5254
|
+
children: content.title
|
|
5255
|
+
}
|
|
5256
|
+
),
|
|
5257
|
+
/* @__PURE__ */ jsx(
|
|
5258
|
+
Text,
|
|
5259
|
+
{
|
|
5260
|
+
style: {
|
|
5261
|
+
fontSize: "15px",
|
|
5262
|
+
lineHeight: "1.6",
|
|
5263
|
+
color: "#374151",
|
|
5264
|
+
margin: "0 0 24px"
|
|
5265
|
+
},
|
|
5266
|
+
children: renderBody(content.body)
|
|
5267
|
+
}
|
|
5268
|
+
),
|
|
5269
|
+
(content.actions ?? []).length > 0 && /* @__PURE__ */ jsx(Section, { style: { margin: "0 0 12px" }, children: content.actions.map((action, i) => /* @__PURE__ */ jsx(
|
|
5270
|
+
Button,
|
|
5271
|
+
{
|
|
5272
|
+
href: action.url,
|
|
5273
|
+
style: {
|
|
5274
|
+
backgroundColor: "#111827",
|
|
5275
|
+
color: "#ffffff",
|
|
5276
|
+
padding: "12px 24px",
|
|
5277
|
+
borderRadius: "6px",
|
|
5278
|
+
fontSize: "14px",
|
|
5279
|
+
fontWeight: 600,
|
|
5280
|
+
textDecoration: "none",
|
|
5281
|
+
display: "inline-block",
|
|
5282
|
+
// Lay buttons out side by side; space between adjacent buttons.
|
|
5283
|
+
marginRight: i < content.actions.length - 1 ? "12px" : "0"
|
|
5284
|
+
},
|
|
5285
|
+
children: action.label
|
|
5286
|
+
},
|
|
5287
|
+
i
|
|
5288
|
+
)) })
|
|
5289
|
+
]
|
|
5290
|
+
}
|
|
5291
|
+
)
|
|
5292
|
+
}
|
|
5293
|
+
)
|
|
5294
|
+
] }), "StockEmailTemplate");
|
|
5295
|
+
async function renderEmailHtml(content) {
|
|
5296
|
+
return render(/* @__PURE__ */ jsx(StockEmailTemplate, { content }), { pretty: false });
|
|
5297
|
+
}
|
|
5298
|
+
__name(renderEmailHtml, "renderEmailHtml");
|
|
5299
|
+
|
|
5300
|
+
// src/notifications/notify.ts
|
|
5301
|
+
import KSUID3 from "ksuid";
|
|
5302
|
+
function getApiUrl4() {
|
|
5303
|
+
const apiUrl = process.env.KEEL_API_URL;
|
|
5304
|
+
if (!apiUrl) {
|
|
5305
|
+
throw new Error("KEEL_API_URL environment variable is not set");
|
|
5306
|
+
}
|
|
5307
|
+
return apiUrl;
|
|
5308
|
+
}
|
|
5309
|
+
__name(getApiUrl4, "getApiUrl");
|
|
5310
|
+
function toArray(v) {
|
|
5311
|
+
if (v === void 0) return [];
|
|
5312
|
+
return Array.isArray(v) ? v : [v];
|
|
5313
|
+
}
|
|
5314
|
+
__name(toArray, "toArray");
|
|
5315
|
+
function normaliseGroup(group) {
|
|
5316
|
+
if (!group) return [];
|
|
5317
|
+
const refs = [];
|
|
5318
|
+
for (const email of toArray(group.emails)) {
|
|
5319
|
+
refs.push({ kind: "email", value: email });
|
|
5320
|
+
}
|
|
5321
|
+
for (const user of toArray(group.users)) {
|
|
5322
|
+
refs.push({
|
|
5323
|
+
kind: "user",
|
|
5324
|
+
value: typeof user === "string" ? user : user.id
|
|
5325
|
+
});
|
|
5326
|
+
}
|
|
5327
|
+
for (const identity of toArray(group.identities)) {
|
|
5328
|
+
refs.push({
|
|
5329
|
+
kind: "identity",
|
|
5330
|
+
value: typeof identity === "string" ? identity : identity.id
|
|
5331
|
+
});
|
|
5332
|
+
}
|
|
5333
|
+
for (const team of toArray(group.teams)) {
|
|
5334
|
+
refs.push({ kind: "team", value: team });
|
|
5335
|
+
}
|
|
5336
|
+
return refs;
|
|
5337
|
+
}
|
|
5338
|
+
__name(normaliseGroup, "normaliseGroup");
|
|
5339
|
+
async function notifyEmail(input) {
|
|
5340
|
+
return withSpan("notify.email", async () => {
|
|
5341
|
+
const id = KSUID3.randomSync().string;
|
|
5342
|
+
let rendered;
|
|
5343
|
+
let content;
|
|
5344
|
+
if (typeof input.content === "string") {
|
|
5345
|
+
rendered = input.content;
|
|
5346
|
+
content = void 0;
|
|
5347
|
+
} else {
|
|
5348
|
+
rendered = await renderEmailHtml(input.content);
|
|
5349
|
+
content = input.content;
|
|
5350
|
+
}
|
|
5351
|
+
const body = {
|
|
5352
|
+
id,
|
|
5353
|
+
subject: input.subject,
|
|
5354
|
+
rendered,
|
|
5355
|
+
content,
|
|
5356
|
+
recipients: {
|
|
5357
|
+
to: normaliseGroup(input.recipients.to),
|
|
5358
|
+
cc: normaliseGroup(input.recipients.cc),
|
|
5359
|
+
bcc: normaliseGroup(input.recipients.bcc)
|
|
5360
|
+
}
|
|
5361
|
+
};
|
|
5362
|
+
const response = await fetch(`${getApiUrl4()}/notifications/json/email`, {
|
|
5363
|
+
method: "POST",
|
|
5364
|
+
headers: { "Content-Type": "application/json" },
|
|
5365
|
+
body: JSON.stringify(body)
|
|
5366
|
+
});
|
|
5367
|
+
if (!response.ok) {
|
|
5368
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
5369
|
+
throw new Error(
|
|
5370
|
+
`Failed to send notification: ${response.status} ${response.statusText} - ${errorBody.message || JSON.stringify(errorBody)}`
|
|
5371
|
+
);
|
|
5372
|
+
}
|
|
5373
|
+
await response.body?.cancel();
|
|
5374
|
+
return id;
|
|
5375
|
+
});
|
|
5376
|
+
}
|
|
5377
|
+
__name(notifyEmail, "notifyEmail");
|
|
5378
|
+
function createNotifier() {
|
|
5379
|
+
return { email: notifyEmail };
|
|
5380
|
+
}
|
|
5381
|
+
__name(createNotifier, "createNotifier");
|
|
5382
|
+
|
|
5383
|
+
// src/experimental.ts
|
|
5384
|
+
var experimental_exports = {};
|
|
5385
|
+
__export(experimental_exports, {
|
|
5386
|
+
AiSdkEngine: () => AiSdkEngine,
|
|
5387
|
+
LlmFlowStep: () => LlmFlowStep,
|
|
5388
|
+
ScriptedEngine: () => ScriptedEngine,
|
|
5389
|
+
defineAgent: () => defineAgent,
|
|
5390
|
+
defineTool: () => defineTool,
|
|
5391
|
+
scriptedTurn: () => scriptedTurn
|
|
5392
|
+
});
|
|
5393
|
+
|
|
5394
|
+
// src/agents/schema.ts
|
|
5395
|
+
function canonicalStringify(value) {
|
|
5396
|
+
if (value === null || typeof value !== "object") {
|
|
5397
|
+
return JSON.stringify(value);
|
|
5398
|
+
}
|
|
5399
|
+
if (Array.isArray(value)) {
|
|
5400
|
+
return "[" + value.map((v) => v === void 0 ? "null" : canonicalStringify(v)).join(",") + "]";
|
|
5401
|
+
}
|
|
5402
|
+
const sorted = Object.keys(value).sort().filter((k) => value[k] !== void 0).map((k) => JSON.stringify(k) + ":" + canonicalStringify(value[k])).join(",");
|
|
5403
|
+
return "{" + sorted + "}";
|
|
5404
|
+
}
|
|
5405
|
+
__name(canonicalStringify, "canonicalStringify");
|
|
5406
|
+
function canonicalize(value) {
|
|
5407
|
+
if (value === void 0) return value;
|
|
5408
|
+
return JSON.parse(canonicalStringify(value));
|
|
5409
|
+
}
|
|
5410
|
+
__name(canonicalize, "canonicalize");
|
|
5411
|
+
async function validateSchema(schema, value) {
|
|
5412
|
+
let result = schema["~standard"].validate(value);
|
|
5413
|
+
if (result instanceof Promise) result = await result;
|
|
5414
|
+
if (result.issues) {
|
|
5415
|
+
const issues = result.issues.map((i) => {
|
|
5416
|
+
const path = (i.path ?? []).map(
|
|
5417
|
+
(p) => typeof p === "object" && p !== null && "key" in p ? String(p.key) : String(p)
|
|
5418
|
+
).join(".");
|
|
5419
|
+
return path ? `${path}: ${i.message}` : i.message;
|
|
5420
|
+
}).join("; ");
|
|
5421
|
+
return { ok: false, issues };
|
|
5422
|
+
}
|
|
5423
|
+
return { ok: true, value: result.value };
|
|
5424
|
+
}
|
|
5425
|
+
__name(validateSchema, "validateSchema");
|
|
5426
|
+
async function toJsonSchema(schema) {
|
|
5427
|
+
const vendor = schema["~standard"].vendor;
|
|
5428
|
+
if (vendor === "zod") {
|
|
5429
|
+
const zod = await import("zod");
|
|
5430
|
+
const toJSONSchema = zod.toJSONSchema ?? zod.z?.toJSONSchema;
|
|
5431
|
+
if (typeof toJSONSchema !== "function") {
|
|
5432
|
+
throw new Error(
|
|
5433
|
+
"Deriving a JSON schema from a zod schema requires zod v4"
|
|
5434
|
+
);
|
|
5435
|
+
}
|
|
5436
|
+
return toJSONSchema(schema);
|
|
5437
|
+
}
|
|
5438
|
+
throw new Error(
|
|
5439
|
+
`Unsupported schema vendor "${vendor}" \u2014 agent result/parameter schemas currently support Zod v4`
|
|
5440
|
+
);
|
|
5441
|
+
}
|
|
5442
|
+
__name(toJsonSchema, "toJsonSchema");
|
|
5443
|
+
|
|
5444
|
+
// src/agents/defineAgent.ts
|
|
5445
|
+
var AGENT_NAME_RE = /^[A-Za-z][A-Za-z0-9]*$/;
|
|
5446
|
+
var runCounters = /* @__PURE__ */ new WeakMap();
|
|
5447
|
+
function nextRunNumber(ctx, agentName) {
|
|
5448
|
+
let byName = runCounters.get(ctx);
|
|
5449
|
+
if (!byName) {
|
|
5450
|
+
byName = /* @__PURE__ */ new Map();
|
|
5451
|
+
runCounters.set(ctx, byName);
|
|
5452
|
+
}
|
|
5453
|
+
const n = (byName.get(agentName) ?? 0) + 1;
|
|
5454
|
+
byName.set(agentName, n);
|
|
5455
|
+
return n;
|
|
5456
|
+
}
|
|
5457
|
+
__name(nextRunNumber, "nextRunNumber");
|
|
5458
|
+
async function runAgent(config, ctx, inputs) {
|
|
5459
|
+
const engine = resolveEngineForContext(ctx);
|
|
5460
|
+
const runNumber = nextRunNumber(ctx, config.name);
|
|
5461
|
+
const prefix = `${config.name}#${runNumber}`;
|
|
5462
|
+
const promptContext = await config.beforeRun?.(ctx, { inputs }) ?? {};
|
|
5463
|
+
let system = config.instructions;
|
|
5464
|
+
if (config.result) {
|
|
5465
|
+
system += "\n\nWhen the task is complete you MUST call complete_task with the final result.";
|
|
5466
|
+
}
|
|
5467
|
+
const userTools = config.tools ?? [];
|
|
5468
|
+
const toolSpecs = await Promise.all(
|
|
5469
|
+
userTools.map(async (t) => ({
|
|
5470
|
+
name: t.name,
|
|
5471
|
+
description: t.usage ? `${t.description}
|
|
5472
|
+
${t.usage}` : t.description,
|
|
5473
|
+
parameters: await toJsonSchema(t.parameters)
|
|
5474
|
+
}))
|
|
5475
|
+
);
|
|
5476
|
+
if (config.result) {
|
|
5477
|
+
toolSpecs.push({
|
|
5478
|
+
name: "complete_task",
|
|
5479
|
+
description: "Call this when the task is complete, with the final result.",
|
|
5480
|
+
parameters: await toJsonSchema(config.result)
|
|
5481
|
+
});
|
|
5482
|
+
}
|
|
5483
|
+
const toolMap = new Map(
|
|
5484
|
+
userTools.map((t) => [t.name, t])
|
|
5485
|
+
);
|
|
5486
|
+
const messages = [
|
|
5487
|
+
{
|
|
5488
|
+
role: "user",
|
|
5489
|
+
content: config.prompt({ inputs, context: promptContext })
|
|
5490
|
+
}
|
|
5491
|
+
];
|
|
5492
|
+
const maxTurns = config.maxTurns ?? 10;
|
|
5493
|
+
const maxCompletionNudges = 2;
|
|
5494
|
+
let nudges = 0;
|
|
5495
|
+
for (let turn = 1; turn <= maxTurns; turn++) {
|
|
5496
|
+
const res = canonicalize(
|
|
5497
|
+
await ctx.step(
|
|
5498
|
+
`${prefix}/turn-${turn}/llm`,
|
|
5499
|
+
{ retries: 2, timeout: 3e5 },
|
|
5500
|
+
() => engine.turn({
|
|
5501
|
+
model: config.model,
|
|
5502
|
+
system,
|
|
5503
|
+
messages,
|
|
5504
|
+
...toolSpecs.length ? { tools: toolSpecs } : {}
|
|
5505
|
+
})
|
|
5506
|
+
)
|
|
5507
|
+
);
|
|
5508
|
+
messages.push({
|
|
5509
|
+
role: "assistant",
|
|
5510
|
+
content: res.text,
|
|
5511
|
+
toolCalls: res.toolCalls
|
|
5512
|
+
});
|
|
5513
|
+
const toolCalls = res.toolCalls ?? [];
|
|
5514
|
+
const completeCall = toolCalls.find((c) => c.name === "complete_task");
|
|
5515
|
+
const otherCalls = toolCalls.filter((c) => c.name !== "complete_task");
|
|
5516
|
+
if (completeCall && config.result && otherCalls.length === 0) {
|
|
5517
|
+
const v = await validateSchema(config.result, completeCall.arguments);
|
|
5518
|
+
if (v.ok) {
|
|
5519
|
+
await config.afterRun?.(ctx, v.value);
|
|
5520
|
+
return v.value;
|
|
5521
|
+
}
|
|
5522
|
+
messages.push({
|
|
5523
|
+
role: "tool",
|
|
5524
|
+
toolCallId: completeCall.id,
|
|
5525
|
+
toolName: "complete_task",
|
|
5526
|
+
result: `Validation failed: ${v.issues}. Call complete_task again with corrected arguments.`
|
|
5527
|
+
});
|
|
5528
|
+
continue;
|
|
5529
|
+
}
|
|
5530
|
+
if (res.stopReason === "end_turn") {
|
|
5531
|
+
if (!config.result) {
|
|
5532
|
+
await config.afterRun?.(ctx, res.text);
|
|
5533
|
+
return res.text;
|
|
5534
|
+
}
|
|
5535
|
+
if (++nudges > maxCompletionNudges) {
|
|
5536
|
+
throw new Error(
|
|
5537
|
+
`Agent "${config.name}" finished without calling complete_task after ${maxCompletionNudges} reminders`
|
|
5538
|
+
);
|
|
5539
|
+
}
|
|
5540
|
+
messages.push({
|
|
5541
|
+
role: "user",
|
|
5542
|
+
content: "You must call complete_task with the final result."
|
|
5543
|
+
});
|
|
5544
|
+
continue;
|
|
5545
|
+
}
|
|
5546
|
+
if (completeCall) {
|
|
5547
|
+
messages.push({
|
|
5548
|
+
role: "tool",
|
|
5549
|
+
toolCallId: completeCall.id,
|
|
5550
|
+
toolName: "complete_task",
|
|
5551
|
+
result: config.result ? "complete_task must be the only tool call in a turn. The other tools you called this turn have been run \u2014 review their results, then call complete_task on its own." : `Unknown tool "complete_task" \u2014 available tools: ${userTools.map((t) => t.name).join(", ")}`
|
|
5552
|
+
});
|
|
5553
|
+
}
|
|
5554
|
+
for (let i = 0; i < otherCalls.length; i++) {
|
|
5555
|
+
const call = otherCalls[i];
|
|
5556
|
+
const tool2 = toolMap.get(call.name);
|
|
5557
|
+
if (!tool2) {
|
|
5558
|
+
const available = userTools.map((t) => t.name).join(", ");
|
|
5559
|
+
messages.push({
|
|
5560
|
+
role: "tool",
|
|
5561
|
+
toolCallId: call.id,
|
|
5562
|
+
toolName: call.name,
|
|
5563
|
+
result: `Unknown tool "${call.name}" \u2014 available tools: ${available}`
|
|
5564
|
+
});
|
|
5565
|
+
continue;
|
|
5566
|
+
}
|
|
5567
|
+
const approved = await checkApproval(
|
|
5568
|
+
ctx,
|
|
5569
|
+
prefix,
|
|
5570
|
+
turn,
|
|
5571
|
+
i + 1,
|
|
5572
|
+
tool2,
|
|
5573
|
+
call.arguments
|
|
5574
|
+
);
|
|
5575
|
+
if (!approved) {
|
|
5576
|
+
messages.push({
|
|
5577
|
+
role: "tool",
|
|
5578
|
+
toolCallId: call.id,
|
|
5579
|
+
toolName: call.name,
|
|
5580
|
+
result: `The user rejected this tool call. Do not retry it; choose another course of action.`
|
|
5581
|
+
});
|
|
5582
|
+
continue;
|
|
5583
|
+
}
|
|
5584
|
+
const stepResult = canonicalize(
|
|
5585
|
+
await ctx.step(
|
|
5586
|
+
`${prefix}/turn-${turn}/tool-${i + 1}:${call.name}`,
|
|
5587
|
+
{ retries: 2, timeout: 6e4 },
|
|
5588
|
+
async () => {
|
|
5589
|
+
const v = await validateSchema(tool2.parameters, call.arguments);
|
|
5590
|
+
if (!v.ok) {
|
|
5591
|
+
return { ok: false, error: `Invalid arguments: ${v.issues}` };
|
|
5592
|
+
}
|
|
5593
|
+
return { ok: true, value: await tool2.execute(v.value) ?? null };
|
|
5594
|
+
}
|
|
5595
|
+
)
|
|
5596
|
+
);
|
|
5597
|
+
const resultText = stepResult.ok ? canonicalStringify(stepResult.value) : stepResult.error;
|
|
5598
|
+
messages.push({
|
|
5599
|
+
role: "tool",
|
|
5600
|
+
toolCallId: call.id,
|
|
5601
|
+
toolName: call.name,
|
|
5602
|
+
result: resultText
|
|
5603
|
+
});
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
throw new Error(`Agent "${config.name}" exceeded maxTurns (${maxTurns})`);
|
|
5607
|
+
}
|
|
5608
|
+
__name(runAgent, "runAgent");
|
|
5609
|
+
async function checkApproval(ctx, prefix, turn, index, tool2, args) {
|
|
5610
|
+
const needsApproval = tool2.approval === true || typeof tool2.approval === "function" && tool2.approval(args);
|
|
5611
|
+
if (!needsApproval) return true;
|
|
5612
|
+
const result = await ctx.ui.page(
|
|
5613
|
+
`${prefix}/turn-${turn}/approve-${index}:${tool2.name}`,
|
|
5614
|
+
{
|
|
5615
|
+
title: `Approve tool call: ${tool2.name}`,
|
|
5616
|
+
content: [
|
|
5617
|
+
ctx.ui.display.markdown({
|
|
5618
|
+
content: `The agent wants to call **${tool2.name}** with:
|
|
5619
|
+
|
|
5620
|
+
\`\`\`json
|
|
5621
|
+
${canonicalStringify(args)}
|
|
5622
|
+
\`\`\``
|
|
5623
|
+
})
|
|
5624
|
+
],
|
|
5625
|
+
actions: [
|
|
5626
|
+
{ label: "Approve", value: "approve" },
|
|
5627
|
+
{ label: "Reject", value: "reject" }
|
|
5628
|
+
]
|
|
5629
|
+
}
|
|
5630
|
+
);
|
|
5631
|
+
return result.action === "approve";
|
|
5632
|
+
}
|
|
5633
|
+
__name(checkApproval, "checkApproval");
|
|
5634
|
+
function defineAgent(config) {
|
|
5635
|
+
if (!AGENT_NAME_RE.test(config.name)) {
|
|
5636
|
+
throw new Error(
|
|
5637
|
+
`Invalid agent name "${config.name}" \u2014 must be alphanumeric PascalCase`
|
|
5638
|
+
);
|
|
5639
|
+
}
|
|
5640
|
+
const names = /* @__PURE__ */ new Set();
|
|
5641
|
+
for (const tool2 of config.tools ?? []) {
|
|
5642
|
+
if (names.has(tool2.name)) {
|
|
5643
|
+
throw new Error(
|
|
5644
|
+
`Duplicate tool "${tool2.name}" on agent "${config.name}"`
|
|
5645
|
+
);
|
|
5646
|
+
}
|
|
5647
|
+
names.add(tool2.name);
|
|
5648
|
+
}
|
|
5649
|
+
return Object.freeze({
|
|
5650
|
+
name: config.name,
|
|
5651
|
+
config,
|
|
5652
|
+
run: /* @__PURE__ */ __name((ctx, inputs) => runAgent(config, ctx, inputs), "run")
|
|
5653
|
+
});
|
|
5654
|
+
}
|
|
5655
|
+
__name(defineAgent, "defineAgent");
|
|
5656
|
+
|
|
5657
|
+
// src/agents/defineTool.ts
|
|
5658
|
+
var NAME_RE = /^[a-z][a-z0-9_]*$/;
|
|
5659
|
+
var RESERVED = /* @__PURE__ */ new Set(["complete_task"]);
|
|
5660
|
+
function defineTool(config) {
|
|
5661
|
+
if (!NAME_RE.test(config.name)) {
|
|
5662
|
+
throw new Error(
|
|
5663
|
+
`Invalid tool name "${config.name}" \u2014 must be snake_case ([a-z][a-z0-9_]*)`
|
|
5664
|
+
);
|
|
5665
|
+
}
|
|
5666
|
+
if (RESERVED.has(config.name)) {
|
|
5667
|
+
throw new Error(`Tool name "${config.name}" is reserved`);
|
|
5668
|
+
}
|
|
5669
|
+
return Object.freeze({ ...config });
|
|
5670
|
+
}
|
|
5671
|
+
__name(defineTool, "defineTool");
|
|
5672
|
+
|
|
5673
|
+
// src/agents/llm.ts
|
|
5674
|
+
async function LlmFlowStep(ctx, name, options) {
|
|
5675
|
+
const stepValue = await ctx.step(
|
|
5676
|
+
name,
|
|
5677
|
+
{ retries: 2, timeout: options.timeout ?? 3e5 },
|
|
5678
|
+
async () => {
|
|
5679
|
+
const engine = resolveEngineForContext(ctx);
|
|
5680
|
+
if (!options.result) {
|
|
5681
|
+
const res = await engine.turn({
|
|
5682
|
+
model: options.model,
|
|
5683
|
+
system: options.system,
|
|
5684
|
+
messages: [{ role: "user", content: options.prompt }]
|
|
5685
|
+
});
|
|
5686
|
+
return res.text;
|
|
5687
|
+
}
|
|
5688
|
+
const parameters = await toJsonSchema(options.result);
|
|
5689
|
+
const messages = [
|
|
5690
|
+
{ role: "user", content: options.prompt }
|
|
5691
|
+
];
|
|
5692
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
5693
|
+
const res = await engine.turn({
|
|
5694
|
+
model: options.model,
|
|
5695
|
+
system: options.system,
|
|
5696
|
+
messages,
|
|
5697
|
+
tools: [
|
|
5698
|
+
{
|
|
5699
|
+
name: "complete_task",
|
|
5700
|
+
description: "Return the final result of the task.",
|
|
5701
|
+
parameters
|
|
5702
|
+
}
|
|
5703
|
+
],
|
|
5704
|
+
toolChoice: { name: "complete_task" }
|
|
5705
|
+
});
|
|
5706
|
+
const call = res.toolCalls.find((c) => c.name === "complete_task");
|
|
5707
|
+
if (call) {
|
|
5708
|
+
const v = await validateSchema(options.result, call.arguments);
|
|
5709
|
+
if (v.ok) return v.value;
|
|
5710
|
+
messages.push(
|
|
5711
|
+
{
|
|
5712
|
+
role: "assistant",
|
|
5713
|
+
content: res.text,
|
|
5714
|
+
toolCalls: res.toolCalls
|
|
5715
|
+
},
|
|
5716
|
+
{
|
|
5717
|
+
role: "tool",
|
|
5718
|
+
toolCallId: call.id,
|
|
5719
|
+
toolName: "complete_task",
|
|
5720
|
+
result: `Validation failed: ${v.issues}. Call complete_task again with corrected arguments.`
|
|
5721
|
+
}
|
|
5722
|
+
);
|
|
5723
|
+
continue;
|
|
5724
|
+
}
|
|
5725
|
+
messages.push(
|
|
5726
|
+
{ role: "assistant", content: res.text },
|
|
5727
|
+
{
|
|
5728
|
+
role: "user",
|
|
5729
|
+
content: "You must call complete_task with the final result."
|
|
5730
|
+
}
|
|
5731
|
+
);
|
|
5732
|
+
}
|
|
5733
|
+
throw new NonRetriableError(
|
|
5734
|
+
`LlmFlowStep("${name}"): result failed validation after 3 attempts`
|
|
5735
|
+
);
|
|
5736
|
+
}
|
|
5737
|
+
);
|
|
5738
|
+
return canonicalize(stepValue);
|
|
5739
|
+
}
|
|
5740
|
+
__name(LlmFlowStep, "LlmFlowStep");
|
|
5741
|
+
|
|
5742
|
+
// src/index.ts
|
|
5743
|
+
import { z } from "zod";
|
|
4722
5744
|
var createTraceAPI2 = createTraceAPI;
|
|
4723
5745
|
function ksuid() {
|
|
4724
|
-
return
|
|
5746
|
+
return KSUID4.randomSync().string;
|
|
4725
5747
|
}
|
|
4726
5748
|
__name(ksuid, "ksuid");
|
|
5749
|
+
var notify = createNotifier();
|
|
4727
5750
|
export {
|
|
4728
5751
|
Duration,
|
|
4729
5752
|
ErrorPresets,
|
|
4730
5753
|
File,
|
|
4731
5754
|
FlowsAPI,
|
|
4732
5755
|
InlineFile,
|
|
4733
|
-
|
|
5756
|
+
KSUID4 as KSUID,
|
|
4734
5757
|
ModelAPI,
|
|
4735
5758
|
NonRetriableError,
|
|
4736
5759
|
PERMISSION_STATE,
|
|
@@ -4745,14 +5768,20 @@ export {
|
|
|
4745
5768
|
TaskAPI,
|
|
4746
5769
|
checkBuiltInPermissions,
|
|
4747
5770
|
createFlowContext,
|
|
5771
|
+
createIntegrationServer,
|
|
5772
|
+
createNotifier,
|
|
4748
5773
|
createTraceAPI2 as createTraceAPI,
|
|
5774
|
+
experimental_exports as experimental,
|
|
4749
5775
|
handleFlow,
|
|
4750
5776
|
handleJob,
|
|
4751
5777
|
handleRequest,
|
|
4752
5778
|
handleRoute,
|
|
4753
5779
|
handleSubscriber,
|
|
5780
|
+
insertNewStep,
|
|
4754
5781
|
ksuid,
|
|
5782
|
+
notify,
|
|
4755
5783
|
tracing_exports as tracing,
|
|
4756
|
-
useDatabase
|
|
5784
|
+
useDatabase,
|
|
5785
|
+
z
|
|
4757
5786
|
};
|
|
4758
5787
|
//# sourceMappingURL=index.js.map
|