@mindstudio-ai/agent 0.1.35 → 0.1.37
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/cli.js +248 -131
- package/dist/index.d.ts +59 -28
- package/dist/index.js +238 -123
- package/dist/index.js.map +1 -1
- package/dist/postinstall.js +248 -131
- package/llms.txt +4 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,6 +7,18 @@ var MindStudioError = class extends Error {
|
|
|
7
7
|
this.details = details;
|
|
8
8
|
}
|
|
9
9
|
name = "MindStudioError";
|
|
10
|
+
toString() {
|
|
11
|
+
return `MindStudioError [${this.code}] (${this.status}): ${this.message}`;
|
|
12
|
+
}
|
|
13
|
+
toJSON() {
|
|
14
|
+
return {
|
|
15
|
+
name: this.name,
|
|
16
|
+
message: this.message,
|
|
17
|
+
code: this.code,
|
|
18
|
+
status: this.status,
|
|
19
|
+
...this.details != null && { details: this.details }
|
|
20
|
+
};
|
|
21
|
+
}
|
|
10
22
|
};
|
|
11
23
|
|
|
12
24
|
// src/http.ts
|
|
@@ -45,11 +57,17 @@ async function requestWithRetry(config, method, url, body, attempt) {
|
|
|
45
57
|
try {
|
|
46
58
|
const body2 = JSON.parse(text);
|
|
47
59
|
details = body2;
|
|
48
|
-
const errMsg = body2.error ?? body2.message ?? body2.details;
|
|
60
|
+
const errMsg = (typeof body2.error === "string" ? body2.error : void 0) ?? (typeof body2.message === "string" ? body2.message : void 0) ?? (typeof body2.details === "string" ? body2.details : void 0);
|
|
49
61
|
if (errMsg) message = errMsg;
|
|
62
|
+
else if (body2.error || body2.message || body2.details) {
|
|
63
|
+
message = JSON.stringify(body2.error ?? body2.message ?? body2.details);
|
|
64
|
+
}
|
|
50
65
|
if (body2.code) code = body2.code;
|
|
51
66
|
} catch {
|
|
52
|
-
if (text
|
|
67
|
+
if (text) {
|
|
68
|
+
const stripped = text.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
69
|
+
if (stripped) message = stripped.slice(0, 200);
|
|
70
|
+
}
|
|
53
71
|
}
|
|
54
72
|
} catch {
|
|
55
73
|
}
|
|
@@ -82,7 +100,7 @@ var RateLimiter = class {
|
|
|
82
100
|
async acquire() {
|
|
83
101
|
if (this.callCount >= this.callCap) {
|
|
84
102
|
throw new MindStudioError(
|
|
85
|
-
`Call cap
|
|
103
|
+
`Call cap exceeded (${this.callCap} calls per execution). Reduce the number of API calls or use executeStepBatch() to combine multiple steps.`,
|
|
86
104
|
"call_cap_exceeded",
|
|
87
105
|
429
|
|
88
106
|
);
|
|
@@ -149,7 +167,7 @@ function loadConfig() {
|
|
|
149
167
|
|
|
150
168
|
// src/auth/index.ts
|
|
151
169
|
var AuthContext = class {
|
|
152
|
-
/** The current user's ID. */
|
|
170
|
+
/** The current user's ID, or null for unauthenticated users. */
|
|
153
171
|
userId;
|
|
154
172
|
/** The current user's roles in this app. */
|
|
155
173
|
roles;
|
|
@@ -188,9 +206,16 @@ var AuthContext = class {
|
|
|
188
206
|
* ```
|
|
189
207
|
*/
|
|
190
208
|
requireRole(...roles) {
|
|
209
|
+
if (this.userId == null) {
|
|
210
|
+
throw new MindStudioError(
|
|
211
|
+
"No authenticated user",
|
|
212
|
+
"unauthenticated",
|
|
213
|
+
401
|
|
214
|
+
);
|
|
215
|
+
}
|
|
191
216
|
if (!this.hasRole(...roles)) {
|
|
192
217
|
throw new MindStudioError(
|
|
193
|
-
`User
|
|
218
|
+
`User has role(s) [${this.roles.join(", ") || "none"}] but requires one of: [${roles.join(", ")}]`,
|
|
194
219
|
"forbidden",
|
|
195
220
|
403
|
|
196
221
|
);
|
|
@@ -244,6 +269,7 @@ function escapeValue(val) {
|
|
|
244
269
|
}
|
|
245
270
|
var USER_PREFIX = "@@user@@";
|
|
246
271
|
function deserializeRow(row, columns) {
|
|
272
|
+
if (row == null) return row;
|
|
247
273
|
const result = {};
|
|
248
274
|
for (const [key, value] of Object.entries(row)) {
|
|
249
275
|
const col = columns.find((c) => c.name === key);
|
|
@@ -273,11 +299,6 @@ function buildSelect(table, options = {}) {
|
|
|
273
299
|
if (options.offset != null) sql += ` OFFSET ${options.offset}`;
|
|
274
300
|
return { sql, params: params.length > 0 ? params : void 0 };
|
|
275
301
|
}
|
|
276
|
-
function buildCount(table, where, whereParams) {
|
|
277
|
-
let sql = `SELECT COUNT(*) as count FROM ${table}`;
|
|
278
|
-
if (where) sql += ` WHERE ${where}`;
|
|
279
|
-
return { sql, params: whereParams?.length ? whereParams : void 0 };
|
|
280
|
-
}
|
|
281
302
|
function buildExists(table, where, whereParams, negate) {
|
|
282
303
|
const inner = where ? `SELECT 1 FROM ${table} WHERE ${where}` : `SELECT 1 FROM ${table}`;
|
|
283
304
|
const fn = negate ? "NOT EXISTS" : "EXISTS";
|
|
@@ -348,20 +369,20 @@ function compilePredicate(fn) {
|
|
|
348
369
|
try {
|
|
349
370
|
const source = fn.toString();
|
|
350
371
|
const paramName = extractParamName(source);
|
|
351
|
-
if (!paramName) return { type: "js", fn };
|
|
372
|
+
if (!paramName) return { type: "js", fn, reason: "could not extract parameter name" };
|
|
352
373
|
const body = extractBody(source);
|
|
353
|
-
if (!body) return { type: "js", fn };
|
|
374
|
+
if (!body) return { type: "js", fn, reason: "could not extract function body" };
|
|
354
375
|
const tokens = tokenize(body);
|
|
355
|
-
if (tokens.length === 0) return { type: "js", fn };
|
|
376
|
+
if (tokens.length === 0) return { type: "js", fn, reason: "empty token stream" };
|
|
356
377
|
const parser = new Parser(tokens, paramName, fn);
|
|
357
378
|
const ast = parser.parseExpression();
|
|
358
|
-
if (!ast) return { type: "js", fn };
|
|
359
|
-
if (parser.pos < tokens.length) return { type: "js", fn };
|
|
379
|
+
if (!ast) return { type: "js", fn, reason: "could not parse expression" };
|
|
380
|
+
if (parser.pos < tokens.length) return { type: "js", fn, reason: "unexpected tokens after expression" };
|
|
360
381
|
const where = compileNode(ast);
|
|
361
|
-
if (!where) return { type: "js", fn };
|
|
382
|
+
if (!where) return { type: "js", fn, reason: "could not compile to SQL" };
|
|
362
383
|
return { type: "sql", where };
|
|
363
|
-
} catch {
|
|
364
|
-
return { type: "js", fn };
|
|
384
|
+
} catch (err) {
|
|
385
|
+
return { type: "js", fn, reason: `compilation error: ${err?.message || "unknown"}` };
|
|
365
386
|
}
|
|
366
387
|
}
|
|
367
388
|
function extractParamName(source) {
|
|
@@ -838,6 +859,11 @@ var Query = class _Query {
|
|
|
838
859
|
_limit;
|
|
839
860
|
_offset;
|
|
840
861
|
_config;
|
|
862
|
+
/** @internal Pre-compiled WHERE clause (bypasses predicate compiler). Used by Table.get(). */
|
|
863
|
+
_rawWhere;
|
|
864
|
+
_rawWhereParams;
|
|
865
|
+
/** @internal Post-process transform applied after row deserialization. */
|
|
866
|
+
_postProcess;
|
|
841
867
|
constructor(config, options) {
|
|
842
868
|
this._config = config;
|
|
843
869
|
this._predicates = options?.predicates ?? [];
|
|
@@ -845,6 +871,9 @@ var Query = class _Query {
|
|
|
845
871
|
this._reversed = options?.reversed ?? false;
|
|
846
872
|
this._limit = options?.limit;
|
|
847
873
|
this._offset = options?.offset;
|
|
874
|
+
this._postProcess = options?.postProcess;
|
|
875
|
+
this._rawWhere = options?.rawWhere;
|
|
876
|
+
this._rawWhereParams = options?.rawWhereParams;
|
|
848
877
|
}
|
|
849
878
|
_clone(overrides) {
|
|
850
879
|
return new _Query(this._config, {
|
|
@@ -852,7 +881,10 @@ var Query = class _Query {
|
|
|
852
881
|
sortAccessor: overrides.sortAccessor ?? this._sortAccessor,
|
|
853
882
|
reversed: overrides.reversed ?? this._reversed,
|
|
854
883
|
limit: overrides.limit ?? this._limit,
|
|
855
|
-
offset: overrides.offset ?? this._offset
|
|
884
|
+
offset: overrides.offset ?? this._offset,
|
|
885
|
+
postProcess: overrides.postProcess,
|
|
886
|
+
rawWhere: this._rawWhere,
|
|
887
|
+
rawWhereParams: this._rawWhereParams
|
|
856
888
|
});
|
|
857
889
|
}
|
|
858
890
|
// -------------------------------------------------------------------------
|
|
@@ -876,41 +908,29 @@ var Query = class _Query {
|
|
|
876
908
|
// -------------------------------------------------------------------------
|
|
877
909
|
// Terminal methods
|
|
878
910
|
// -------------------------------------------------------------------------
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
911
|
+
first() {
|
|
912
|
+
return this._clone({
|
|
913
|
+
limit: 1,
|
|
914
|
+
postProcess: (rows) => rows[0] ?? null
|
|
915
|
+
});
|
|
882
916
|
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
917
|
+
last() {
|
|
918
|
+
return this._clone({
|
|
919
|
+
limit: 1,
|
|
920
|
+
reversed: !this._reversed,
|
|
921
|
+
postProcess: (rows) => rows[0] ?? null
|
|
922
|
+
});
|
|
886
923
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
this._config.tableName,
|
|
892
|
-
compiled.sqlWhere || void 0
|
|
893
|
-
);
|
|
894
|
-
const results = await this._config.executeBatch([query]);
|
|
895
|
-
const row = results[0]?.rows[0];
|
|
896
|
-
return row?.count ?? 0;
|
|
897
|
-
}
|
|
898
|
-
const rows = await this._fetchAndFilterInJs(compiled);
|
|
899
|
-
return rows.length;
|
|
924
|
+
count() {
|
|
925
|
+
return this._clone({
|
|
926
|
+
postProcess: (rows) => rows.length
|
|
927
|
+
});
|
|
900
928
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
compiled.sqlWhere || void 0
|
|
907
|
-
);
|
|
908
|
-
const results = await this._config.executeBatch([query]);
|
|
909
|
-
const row = results[0]?.rows[0];
|
|
910
|
-
return row?.result === 1;
|
|
911
|
-
}
|
|
912
|
-
const rows = await this._fetchAndFilterInJs(compiled);
|
|
913
|
-
return rows.length > 0;
|
|
929
|
+
some() {
|
|
930
|
+
return this._clone({
|
|
931
|
+
limit: 1,
|
|
932
|
+
postProcess: (rows) => rows.length > 0
|
|
933
|
+
});
|
|
914
934
|
}
|
|
915
935
|
async every() {
|
|
916
936
|
const compiled = this._compilePredicates();
|
|
@@ -931,25 +951,25 @@ var Query = class _Query {
|
|
|
931
951
|
(row) => this._predicates.every((pred) => pred(row))
|
|
932
952
|
);
|
|
933
953
|
}
|
|
934
|
-
|
|
954
|
+
min(accessor) {
|
|
935
955
|
return this.sortBy(accessor).first();
|
|
936
956
|
}
|
|
937
|
-
|
|
957
|
+
max(accessor) {
|
|
938
958
|
return this.sortBy(accessor).reverse().first();
|
|
939
959
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
960
|
+
groupBy(accessor) {
|
|
961
|
+
return this._clone({
|
|
962
|
+
postProcess: (rows) => {
|
|
963
|
+
const map = /* @__PURE__ */ new Map();
|
|
964
|
+
for (const row of rows) {
|
|
965
|
+
const key = accessor(row);
|
|
966
|
+
const group = map.get(key);
|
|
967
|
+
if (group) group.push(row);
|
|
968
|
+
else map.set(key, [row]);
|
|
969
|
+
}
|
|
970
|
+
return map;
|
|
950
971
|
}
|
|
951
|
-
}
|
|
952
|
-
return map;
|
|
972
|
+
});
|
|
953
973
|
}
|
|
954
974
|
// -------------------------------------------------------------------------
|
|
955
975
|
// Batch compilation — used by db.batch() to extract SQL without executing
|
|
@@ -963,6 +983,16 @@ var Query = class _Query {
|
|
|
963
983
|
* all rows and this query can filter them in JS post-fetch.
|
|
964
984
|
*/
|
|
965
985
|
_compile() {
|
|
986
|
+
if (this._rawWhere) {
|
|
987
|
+
const query = buildSelect(this._config.tableName, {
|
|
988
|
+
where: this._rawWhere,
|
|
989
|
+
whereParams: this._rawWhereParams,
|
|
990
|
+
orderBy: void 0,
|
|
991
|
+
limit: this._limit,
|
|
992
|
+
offset: this._offset
|
|
993
|
+
});
|
|
994
|
+
return { type: "query", query, fallbackQuery: null, config: this._config, postProcess: this._postProcess };
|
|
995
|
+
}
|
|
966
996
|
const compiled = this._compilePredicates();
|
|
967
997
|
const sortField = this._sortAccessor ? extractFieldName(this._sortAccessor) : void 0;
|
|
968
998
|
if (compiled.allSql) {
|
|
@@ -973,7 +1003,7 @@ var Query = class _Query {
|
|
|
973
1003
|
limit: this._limit,
|
|
974
1004
|
offset: this._offset
|
|
975
1005
|
});
|
|
976
|
-
return { type: "query", query, fallbackQuery: null, config: this._config };
|
|
1006
|
+
return { type: "query", query, fallbackQuery: null, config: this._config, postProcess: this._postProcess };
|
|
977
1007
|
}
|
|
978
1008
|
const fallbackQuery = buildSelect(this._config.tableName);
|
|
979
1009
|
return {
|
|
@@ -985,7 +1015,8 @@ var Query = class _Query {
|
|
|
985
1015
|
sortAccessor: this._sortAccessor,
|
|
986
1016
|
reversed: this._reversed,
|
|
987
1017
|
limit: this._limit,
|
|
988
|
-
offset: this._offset
|
|
1018
|
+
offset: this._offset,
|
|
1019
|
+
postProcess: this._postProcess
|
|
989
1020
|
};
|
|
990
1021
|
}
|
|
991
1022
|
/**
|
|
@@ -1002,7 +1033,9 @@ var Query = class _Query {
|
|
|
1002
1033
|
compiled.config.columns
|
|
1003
1034
|
)
|
|
1004
1035
|
);
|
|
1005
|
-
if (compiled.query)
|
|
1036
|
+
if (compiled.query) {
|
|
1037
|
+
return compiled.postProcess ? compiled.postProcess(rows) : rows;
|
|
1038
|
+
}
|
|
1006
1039
|
let filtered = compiled.predicates ? rows.filter((row) => compiled.predicates.every((pred) => pred(row))) : rows;
|
|
1007
1040
|
if (compiled.sortAccessor) {
|
|
1008
1041
|
const accessor = compiled.sortAccessor;
|
|
@@ -1020,21 +1053,39 @@ var Query = class _Query {
|
|
|
1020
1053
|
const end = compiled.limit != null ? start + compiled.limit : void 0;
|
|
1021
1054
|
filtered = filtered.slice(start, end);
|
|
1022
1055
|
}
|
|
1023
|
-
return filtered;
|
|
1056
|
+
return compiled.postProcess ? compiled.postProcess(filtered) : filtered;
|
|
1024
1057
|
}
|
|
1025
1058
|
// -------------------------------------------------------------------------
|
|
1026
1059
|
// PromiseLike
|
|
1027
1060
|
// -------------------------------------------------------------------------
|
|
1028
1061
|
then(onfulfilled, onrejected) {
|
|
1029
|
-
|
|
1062
|
+
const promise = this._execute().then(
|
|
1063
|
+
(rows) => this._postProcess ? this._postProcess(rows) : rows
|
|
1064
|
+
);
|
|
1065
|
+
return promise.then(onfulfilled, onrejected);
|
|
1030
1066
|
}
|
|
1031
1067
|
catch(onrejected) {
|
|
1032
|
-
return this.
|
|
1068
|
+
return this.then(void 0, onrejected);
|
|
1033
1069
|
}
|
|
1034
1070
|
// -------------------------------------------------------------------------
|
|
1035
1071
|
// Execution internals
|
|
1036
1072
|
// -------------------------------------------------------------------------
|
|
1037
1073
|
async _execute() {
|
|
1074
|
+
if (this._rawWhere) {
|
|
1075
|
+
const query = buildSelect(this._config.tableName, {
|
|
1076
|
+
where: this._rawWhere,
|
|
1077
|
+
whereParams: this._rawWhereParams,
|
|
1078
|
+
limit: this._limit,
|
|
1079
|
+
offset: this._offset
|
|
1080
|
+
});
|
|
1081
|
+
const results = await this._config.executeBatch([query]);
|
|
1082
|
+
return results[0].rows.map(
|
|
1083
|
+
(row) => deserializeRow(
|
|
1084
|
+
row,
|
|
1085
|
+
this._config.columns
|
|
1086
|
+
)
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1038
1089
|
const compiled = this._compilePredicates();
|
|
1039
1090
|
if (compiled.allSql) {
|
|
1040
1091
|
const sortField = this._sortAccessor ? extractFieldName(this._sortAccessor) : void 0;
|
|
@@ -1085,9 +1136,12 @@ var Query = class _Query {
|
|
|
1085
1136
|
}
|
|
1086
1137
|
async _fetchAndFilterInJs(compiled) {
|
|
1087
1138
|
const allRows = await this._fetchAllRows();
|
|
1088
|
-
|
|
1139
|
+
const jsFallbacks = compiled.compiled.filter((c) => c.type === "js");
|
|
1140
|
+
if (jsFallbacks.length > 0) {
|
|
1141
|
+
const reasons = jsFallbacks.map((c) => c.type === "js" ? c.reason : void 0).filter(Boolean);
|
|
1142
|
+
const reasonSuffix = reasons.length > 0 ? ` (${reasons.join("; ")})` : "";
|
|
1089
1143
|
console.warn(
|
|
1090
|
-
`[mindstudio] Filter on ${this._config.tableName} could not be compiled to SQL \u2014 scanning ${allRows.length} rows in JS`
|
|
1144
|
+
`[mindstudio] Filter on '${this._config.tableName}' could not be compiled to SQL${reasonSuffix} \u2014 scanning ${allRows.length} rows in JS`
|
|
1091
1145
|
);
|
|
1092
1146
|
}
|
|
1093
1147
|
return allRows.filter(
|
|
@@ -1160,8 +1214,10 @@ var Mutation = class _Mutation {
|
|
|
1160
1214
|
*/
|
|
1161
1215
|
_compile() {
|
|
1162
1216
|
if (this._executor) {
|
|
1163
|
-
throw new
|
|
1164
|
-
"This operation cannot be batched (e.g. removeAll with a predicate
|
|
1217
|
+
throw new MindStudioError(
|
|
1218
|
+
"This operation cannot be batched (e.g. removeAll with a JS-fallback predicate). Await it separately instead of passing to db.batch().",
|
|
1219
|
+
"not_batchable",
|
|
1220
|
+
400
|
|
1165
1221
|
);
|
|
1166
1222
|
}
|
|
1167
1223
|
return {
|
|
@@ -1198,58 +1254,61 @@ var Table = class {
|
|
|
1198
1254
|
this._config = config;
|
|
1199
1255
|
}
|
|
1200
1256
|
// -------------------------------------------------------------------------
|
|
1201
|
-
// Reads —
|
|
1257
|
+
// Reads — all return batchable Query objects (lazy until awaited)
|
|
1202
1258
|
// -------------------------------------------------------------------------
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1259
|
+
/** Get a single row by ID. Returns null if not found. */
|
|
1260
|
+
get(id) {
|
|
1261
|
+
return new Query(this._config, {
|
|
1262
|
+
rawWhere: "id = ?",
|
|
1263
|
+
rawWhereParams: [id],
|
|
1264
|
+
limit: 1,
|
|
1265
|
+
postProcess: (rows) => rows[0] ?? null
|
|
1208
1266
|
});
|
|
1209
|
-
const results = await this._config.executeBatch([query]);
|
|
1210
|
-
if (results[0].rows.length === 0) return null;
|
|
1211
|
-
return deserializeRow(
|
|
1212
|
-
results[0].rows[0],
|
|
1213
|
-
this._config.columns
|
|
1214
|
-
);
|
|
1215
1267
|
}
|
|
1216
|
-
|
|
1268
|
+
/** Find the first row matching a predicate. Returns null if none match. */
|
|
1269
|
+
findOne(predicate) {
|
|
1217
1270
|
return this.filter(predicate).first();
|
|
1218
1271
|
}
|
|
1219
|
-
|
|
1272
|
+
count(predicate) {
|
|
1220
1273
|
if (predicate) return this.filter(predicate).count();
|
|
1221
|
-
|
|
1222
|
-
const results = await this._config.executeBatch([query]);
|
|
1223
|
-
const row = results[0]?.rows[0];
|
|
1224
|
-
return row?.count ?? 0;
|
|
1274
|
+
return this.toArray().count();
|
|
1225
1275
|
}
|
|
1226
|
-
|
|
1276
|
+
/** True if any row matches the predicate. */
|
|
1277
|
+
some(predicate) {
|
|
1227
1278
|
return this.filter(predicate).some();
|
|
1228
1279
|
}
|
|
1280
|
+
/** True if all rows match the predicate. */
|
|
1229
1281
|
async every(predicate) {
|
|
1230
1282
|
return this.filter(predicate).every();
|
|
1231
1283
|
}
|
|
1284
|
+
/** True if the table has zero rows. */
|
|
1232
1285
|
async isEmpty() {
|
|
1233
1286
|
const query = buildExists(this._config.tableName, void 0, void 0, true);
|
|
1234
1287
|
const results = await this._config.executeBatch([query]);
|
|
1235
1288
|
const row = results[0]?.rows[0];
|
|
1236
1289
|
return row?.result === 1;
|
|
1237
1290
|
}
|
|
1238
|
-
|
|
1291
|
+
/** Row with the minimum value for a field, or null if table is empty. */
|
|
1292
|
+
min(accessor) {
|
|
1239
1293
|
return this.sortBy(accessor).first();
|
|
1240
1294
|
}
|
|
1241
|
-
|
|
1295
|
+
/** Row with the maximum value for a field, or null if table is empty. */
|
|
1296
|
+
max(accessor) {
|
|
1242
1297
|
return this.sortBy(accessor).reverse().first();
|
|
1243
1298
|
}
|
|
1244
|
-
|
|
1299
|
+
/** Group rows by a field. Returns a Map. */
|
|
1300
|
+
groupBy(accessor) {
|
|
1245
1301
|
return new Query(this._config).groupBy(accessor);
|
|
1246
1302
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1303
|
+
/** Get all rows as an array. */
|
|
1304
|
+
toArray() {
|
|
1305
|
+
return new Query(this._config);
|
|
1306
|
+
}
|
|
1307
|
+
/** Filter rows by a predicate. Returns a chainable Query. */
|
|
1250
1308
|
filter(predicate) {
|
|
1251
1309
|
return new Query(this._config).filter(predicate);
|
|
1252
1310
|
}
|
|
1311
|
+
/** Sort rows by a field. Returns a chainable Query. */
|
|
1253
1312
|
sortBy(accessor) {
|
|
1254
1313
|
return new Query(this._config).sortBy(accessor);
|
|
1255
1314
|
}
|
|
@@ -1276,7 +1335,11 @@ var Table = class {
|
|
|
1276
1335
|
this._config.columns
|
|
1277
1336
|
);
|
|
1278
1337
|
}
|
|
1279
|
-
|
|
1338
|
+
throw new MindStudioError(
|
|
1339
|
+
`Insert into '${this._config.tableName}' succeeded but returned no row. This may indicate a constraint violation.`,
|
|
1340
|
+
"insert_failed",
|
|
1341
|
+
500
|
|
1342
|
+
);
|
|
1280
1343
|
});
|
|
1281
1344
|
const result = isArray ? rows : rows[0];
|
|
1282
1345
|
this._syncRolesIfNeeded(
|
|
@@ -1300,6 +1363,13 @@ var Table = class {
|
|
|
1300
1363
|
this._config.columns
|
|
1301
1364
|
);
|
|
1302
1365
|
return new Mutation(this._config, [query], (results) => {
|
|
1366
|
+
if (!results[0]?.rows[0]) {
|
|
1367
|
+
throw new MindStudioError(
|
|
1368
|
+
`Row not found: no row with ID '${id}' in table '${this._config.tableName}'`,
|
|
1369
|
+
"row_not_found",
|
|
1370
|
+
404
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1303
1373
|
const result = deserializeRow(
|
|
1304
1374
|
results[0].rows[0],
|
|
1305
1375
|
this._config.columns
|
|
@@ -1314,7 +1384,9 @@ var Table = class {
|
|
|
1314
1384
|
}
|
|
1315
1385
|
remove(id) {
|
|
1316
1386
|
const query = buildDelete(this._config.tableName, `id = ?`, [id]);
|
|
1317
|
-
return new Mutation(this._config, [query], () =>
|
|
1387
|
+
return new Mutation(this._config, [query], (results) => ({
|
|
1388
|
+
deleted: results[0].changes > 0
|
|
1389
|
+
}));
|
|
1318
1390
|
}
|
|
1319
1391
|
/**
|
|
1320
1392
|
* Remove all rows matching a predicate. Returns the count removed.
|
|
@@ -1348,7 +1420,7 @@ var Table = class {
|
|
|
1348
1420
|
}
|
|
1349
1421
|
clear() {
|
|
1350
1422
|
const query = buildDelete(this._config.tableName);
|
|
1351
|
-
return new Mutation(this._config, [query], () =>
|
|
1423
|
+
return new Mutation(this._config, [query], (results) => results[0].changes);
|
|
1352
1424
|
}
|
|
1353
1425
|
/**
|
|
1354
1426
|
* Insert a row, or update it if a row with the same unique key already
|
|
@@ -1366,6 +1438,15 @@ var Table = class {
|
|
|
1366
1438
|
this._validateUniqueConstraint(conflictColumns);
|
|
1367
1439
|
const withDefaults = this._config.defaults ? { ...this._config.defaults, ...data } : data;
|
|
1368
1440
|
this._checkManagedColumns(withDefaults);
|
|
1441
|
+
for (const col of conflictColumns) {
|
|
1442
|
+
if (!(col in withDefaults)) {
|
|
1443
|
+
throw new MindStudioError(
|
|
1444
|
+
`Upsert on ${this._config.tableName} requires "${col}" in data (conflict key)`,
|
|
1445
|
+
"missing_conflict_key",
|
|
1446
|
+
400
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1369
1450
|
const query = buildUpsert(
|
|
1370
1451
|
this._config.tableName,
|
|
1371
1452
|
withDefaults,
|
|
@@ -1373,6 +1454,13 @@ var Table = class {
|
|
|
1373
1454
|
this._config.columns
|
|
1374
1455
|
);
|
|
1375
1456
|
return new Mutation(this._config, [query], (results) => {
|
|
1457
|
+
if (!results[0]?.rows[0]) {
|
|
1458
|
+
throw new MindStudioError(
|
|
1459
|
+
`Upsert into ${this._config.tableName} returned no row`,
|
|
1460
|
+
"upsert_failed",
|
|
1461
|
+
500
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1376
1464
|
const result = deserializeRow(
|
|
1377
1465
|
results[0].rows[0],
|
|
1378
1466
|
this._config.columns
|
|
@@ -1524,7 +1612,7 @@ function createDb(databases, executeBatch, authConfig, syncRoles) {
|
|
|
1524
1612
|
if (c.type === "query") {
|
|
1525
1613
|
if (!c.query && c.predicates?.length) {
|
|
1526
1614
|
console.warn(
|
|
1527
|
-
`[mindstudio] db.batch(): filter on ${c.config.tableName} could not be compiled to SQL \u2014 processing in JS`
|
|
1615
|
+
`[mindstudio] db.batch(): filter on '${c.config.tableName}' could not be compiled to SQL \u2014 processing in JS`
|
|
1528
1616
|
);
|
|
1529
1617
|
}
|
|
1530
1618
|
return Query._processResults(results[0], c);
|
|
@@ -2999,9 +3087,14 @@ var stepMetadata = {
|
|
|
2999
3087
|
},
|
|
3000
3088
|
"sendEmail": {
|
|
3001
3089
|
stepType: "sendEmail",
|
|
3002
|
-
description: "Send an email to one or more
|
|
3003
|
-
usageNotes:
|
|
3004
|
-
|
|
3090
|
+
description: "Send an email to one or more recipient addresses.",
|
|
3091
|
+
usageNotes: `- Use the "to" field to send to specific email addresses directly. For v2 apps, recipients must be verified users in the app's user table.
|
|
3092
|
+
- Alternatively, recipient email addresses can be resolved from OAuth connections configured by the app creator via connectionId. The user running the workflow does not specify the recipient directly.
|
|
3093
|
+
- If the body is a URL to a hosted HTML file on the CDN, the HTML is fetched and used as the email body.
|
|
3094
|
+
- When generateHtml is enabled, the body text is converted to a styled HTML email using an AI model.
|
|
3095
|
+
- connectionId can be a comma-separated list to send to multiple recipients.
|
|
3096
|
+
- The special connectionId "trigger_email" uses the email address that triggered the workflow.`,
|
|
3097
|
+
inputSchema: { "type": "object", "properties": { "subject": { "type": "string", "description": "Email subject line" }, "body": { "type": "string", "description": "Email body content (plain text, markdown, HTML, or a CDN URL to an HTML file)" }, "to": { "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] }, "connectionId": { "type": "string", "description": "OAuth connection ID(s) for the recipient(s), comma-separated for multiple" }, "generateHtml": { "type": "boolean", "description": "When true, auto-convert the body text into a styled HTML email using AI" }, "generateHtmlInstructions": { "type": "string", "description": "Natural language instructions for the HTML generation style" }, "generateHtmlModelOverride": { "type": "object", "properties": { "model": { "type": "string", "description": 'Model identifier (e.g. "gpt-4", "claude-3-opus")' }, "temperature": { "type": "number", "description": "Sampling temperature for the model (0-2)" }, "maxResponseTokens": { "type": "number", "description": "Maximum number of tokens in the model's response" }, "ignorePreamble": { "type": "boolean", "description": "Whether to skip the system preamble/instructions" }, "userMessagePreprocessor": { "type": "object", "properties": { "dataSource": { "type": "string", "description": "Data source identifier for the preprocessor" }, "messageTemplate": { "type": "string", "description": "Template string applied to user messages before sending to the model" }, "maxResults": { "type": "number", "description": "Maximum number of results to include from the data source" }, "enabled": { "type": "boolean", "description": "Whether the preprocessor is active" }, "shouldInherit": { "type": "boolean", "description": "Whether child steps should inherit this preprocessor configuration" } }, "description": "Preprocessor applied to user messages before sending to the model" }, "preamble": { "type": "string", "description": "System preamble/instructions for the model" }, "multiModelEnabled": { "type": "boolean", "description": "Whether multi-model candidate generation is enabled" }, "editResponseEnabled": { "type": "boolean", "description": "Whether the user can edit the model's response" }, "config": { "type": "object", "properties": {}, "required": [], "description": "Additional model-specific configuration" } }, "required": ["model", "temperature", "maxResponseTokens"], "description": "Model settings override for HTML generation" }, "attachments": { "type": "array", "items": { "type": "string" }, "description": "URLs of files to attach to the email" } }, "required": ["subject", "body"] },
|
|
3005
3098
|
outputSchema: { "type": "object", "properties": { "recipients": { "type": "array", "items": { "type": "string" }, "description": "Email addresses the message was sent to" } }, "required": ["recipients"] }
|
|
3006
3099
|
},
|
|
3007
3100
|
"sendGmailDraft": {
|
|
@@ -3323,7 +3416,7 @@ var MindStudioAgent = class {
|
|
|
3323
3416
|
const res = await fetch(data.outputUrl);
|
|
3324
3417
|
if (!res.ok) {
|
|
3325
3418
|
throw new MindStudioError(
|
|
3326
|
-
`Failed to fetch output from S3: ${res.status} ${res.statusText}`,
|
|
3419
|
+
`Failed to fetch ${stepType} output from S3: ${res.status} ${res.statusText}`,
|
|
3327
3420
|
"output_fetch_error",
|
|
3328
3421
|
res.status
|
|
3329
3422
|
);
|
|
@@ -3386,13 +3479,27 @@ var MindStudioAgent = class {
|
|
|
3386
3479
|
this._httpConfig.rateLimiter.updateFromHeaders(res.headers);
|
|
3387
3480
|
if (!res.ok) {
|
|
3388
3481
|
this._httpConfig.rateLimiter.release();
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
res.
|
|
3394
|
-
|
|
3395
|
-
|
|
3482
|
+
let message = `${res.status} ${res.statusText}`;
|
|
3483
|
+
let code = "api_error";
|
|
3484
|
+
let details;
|
|
3485
|
+
try {
|
|
3486
|
+
const text = await res.text();
|
|
3487
|
+
try {
|
|
3488
|
+
const body2 = JSON.parse(text);
|
|
3489
|
+
details = body2;
|
|
3490
|
+
const errMsg = (typeof body2.error === "string" ? body2.error : void 0) ?? (typeof body2.message === "string" ? body2.message : void 0) ?? (typeof body2.details === "string" ? body2.details : void 0);
|
|
3491
|
+
if (errMsg) message = errMsg;
|
|
3492
|
+
else if (body2.error || body2.message || body2.details) {
|
|
3493
|
+
message = JSON.stringify(body2.error ?? body2.message ?? body2.details);
|
|
3494
|
+
}
|
|
3495
|
+
if (body2.code) code = body2.code;
|
|
3496
|
+
} catch {
|
|
3497
|
+
const stripped = text.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
3498
|
+
if (stripped) message = stripped.slice(0, 200);
|
|
3499
|
+
}
|
|
3500
|
+
} catch {
|
|
3501
|
+
}
|
|
3502
|
+
throw new MindStudioError(`[${stepType}] ${message}`, code, res.status, details);
|
|
3396
3503
|
}
|
|
3397
3504
|
const headers = res.headers;
|
|
3398
3505
|
try {
|
|
@@ -3425,7 +3532,7 @@ var MindStudioAgent = class {
|
|
|
3425
3532
|
};
|
|
3426
3533
|
} else if (event.type === "error") {
|
|
3427
3534
|
throw new MindStudioError(
|
|
3428
|
-
event.error || "Step execution failed"
|
|
3535
|
+
`[${stepType}] ${event.error || "Step execution failed"}`,
|
|
3429
3536
|
"step_error",
|
|
3430
3537
|
500
|
|
3431
3538
|
);
|
|
@@ -3464,7 +3571,7 @@ var MindStudioAgent = class {
|
|
|
3464
3571
|
}
|
|
3465
3572
|
if (!doneEvent) {
|
|
3466
3573
|
throw new MindStudioError(
|
|
3467
|
-
|
|
3574
|
+
`[${stepType}] Stream ended unexpectedly without completing. The step execution may have been interrupted.`,
|
|
3468
3575
|
"stream_error",
|
|
3469
3576
|
500
|
|
3470
3577
|
);
|
|
@@ -3476,7 +3583,7 @@ var MindStudioAgent = class {
|
|
|
3476
3583
|
const s3Res = await fetch(doneEvent.outputUrl);
|
|
3477
3584
|
if (!s3Res.ok) {
|
|
3478
3585
|
throw new MindStudioError(
|
|
3479
|
-
`Failed to fetch output from S3: ${s3Res.status} ${s3Res.statusText}`,
|
|
3586
|
+
`Failed to fetch ${stepType} output from S3: ${s3Res.status} ${s3Res.statusText}`,
|
|
3480
3587
|
"output_fetch_error",
|
|
3481
3588
|
s3Res.status
|
|
3482
3589
|
);
|
|
@@ -3826,12 +3933,18 @@ var MindStudioAgent = class {
|
|
|
3826
3933
|
* ```
|
|
3827
3934
|
*/
|
|
3828
3935
|
get auth() {
|
|
3936
|
+
if (this._authType === "internal") {
|
|
3937
|
+
const ai = globalThis.ai;
|
|
3938
|
+
if (ai?.auth) {
|
|
3939
|
+
return new AuthContext(ai.auth);
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3829
3942
|
if (!this._auth) {
|
|
3830
3943
|
this._trySandboxHydration();
|
|
3831
3944
|
}
|
|
3832
3945
|
if (!this._auth) {
|
|
3833
3946
|
throw new MindStudioError(
|
|
3834
|
-
"Auth context not
|
|
3947
|
+
"Auth context not loaded. Call `await agent.ensureContext()` first, or perform any db operation (which auto-loads context).",
|
|
3835
3948
|
"context_not_loaded",
|
|
3836
3949
|
400
|
|
3837
3950
|
);
|
|
@@ -3962,8 +4075,11 @@ var MindStudioAgent = class {
|
|
|
3962
4075
|
const text = await res.text();
|
|
3963
4076
|
try {
|
|
3964
4077
|
const body = JSON.parse(text);
|
|
3965
|
-
const errMsg = body.error ?? body.message ?? body.details;
|
|
4078
|
+
const errMsg = (typeof body.error === "string" ? body.error : void 0) ?? (typeof body.message === "string" ? body.message : void 0) ?? (typeof body.details === "string" ? body.details : void 0);
|
|
3966
4079
|
if (errMsg) message = errMsg;
|
|
4080
|
+
else if (body.error || body.message || body.details) {
|
|
4081
|
+
message = JSON.stringify(body.error ?? body.message ?? body.details);
|
|
4082
|
+
}
|
|
3967
4083
|
if (body.code) code = body.code;
|
|
3968
4084
|
} catch {
|
|
3969
4085
|
if (text && text.length < 500) message = text;
|
|
@@ -4002,13 +4118,12 @@ var MindStudioAgent = class {
|
|
|
4002
4118
|
if (!res.ok) {
|
|
4003
4119
|
const text = await res.text().catch(() => "");
|
|
4004
4120
|
console.warn(
|
|
4005
|
-
`[mindstudio]
|
|
4121
|
+
`[mindstudio] Role sync failed for user ${userId} (${res.status}${text ? ": " + text.slice(0, 100) : ""}). Roles were saved to the database but may not be reflected in auth.hasRole() until the next successful write.`
|
|
4006
4122
|
);
|
|
4007
4123
|
}
|
|
4008
4124
|
} catch (err) {
|
|
4009
4125
|
console.warn(
|
|
4010
|
-
`[mindstudio]
|
|
4011
|
-
err
|
|
4126
|
+
`[mindstudio] Role sync failed for user ${userId}: network error. Roles were saved to the database but may not be reflected in auth.hasRole() until the next successful write.`
|
|
4012
4127
|
);
|
|
4013
4128
|
}
|
|
4014
4129
|
}
|