@superatomai/sdk-node 0.0.7-s → 0.0.8-cp
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.d.mts +4159 -1029
- package/dist/index.d.ts +4159 -1029
- package/dist/index.js +23546 -14107
- package/dist/index.mjs +23537 -14157
- package/dist/userResponse/scripts/script-bootstrap.js +362 -67
- package/dist/userResponse/scripts/script-bootstrap.mjs +362 -67
- package/package.json +13 -6
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/userResponse/scripts/script-bootstrap.js.map +0 -1
- package/dist/userResponse/scripts/script-bootstrap.mjs.map +0 -1
|
@@ -5,8 +5,21 @@ import { pathToFileURL } from "url";
|
|
|
5
5
|
function encodeMessage(msg) {
|
|
6
6
|
return JSON.stringify(msg) + "\n";
|
|
7
7
|
}
|
|
8
|
+
var LineOverflowError = class extends Error {
|
|
9
|
+
constructor(buffered, limit) {
|
|
10
|
+
super(`IPC message exceeded ${limit} bytes without a newline (${buffered} buffered) \u2014 aborting to avoid unbounded memory.`);
|
|
11
|
+
this.buffered = buffered;
|
|
12
|
+
this.limit = limit;
|
|
13
|
+
this.name = "LineOverflowError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var DEFAULT_MAX_IPC_BYTES = (() => {
|
|
17
|
+
const envVal = Number(process.env.SCRIPT_MAX_IPC_BYTES);
|
|
18
|
+
return Number.isFinite(envVal) && envVal > 0 ? Math.floor(envVal) : 64 * 1024 * 1024;
|
|
19
|
+
})();
|
|
8
20
|
var LineSplitter = class {
|
|
9
|
-
constructor() {
|
|
21
|
+
constructor(maxBytes = DEFAULT_MAX_IPC_BYTES) {
|
|
22
|
+
this.maxBytes = maxBytes;
|
|
10
23
|
this.buffer = "";
|
|
11
24
|
}
|
|
12
25
|
push(chunk) {
|
|
@@ -18,6 +31,11 @@ var LineSplitter = class {
|
|
|
18
31
|
this.buffer = this.buffer.slice(idx + 1);
|
|
19
32
|
if (line.length > 0) lines.push(line);
|
|
20
33
|
}
|
|
34
|
+
if (this.buffer.length > this.maxBytes) {
|
|
35
|
+
const buffered = this.buffer.length;
|
|
36
|
+
this.buffer = "";
|
|
37
|
+
throw new LineOverflowError(buffered, this.maxBytes);
|
|
38
|
+
}
|
|
21
39
|
return lines;
|
|
22
40
|
}
|
|
23
41
|
/** Flush any remaining partial data (useful on stream close) */
|
|
@@ -28,8 +46,126 @@ var LineSplitter = class {
|
|
|
28
46
|
return rest;
|
|
29
47
|
}
|
|
30
48
|
};
|
|
31
|
-
|
|
32
|
-
|
|
49
|
+
|
|
50
|
+
// src/userResponse/scripts/fmt.ts
|
|
51
|
+
var DASH = "\u2014";
|
|
52
|
+
var DEFAULT_FMT_CONFIG = { locale: "en-IN", currency: "INR" };
|
|
53
|
+
var MONEY_TIERS = {
|
|
54
|
+
"en-IN": [[1e7, "Cr"], [1e5, "L"]],
|
|
55
|
+
"hi-IN": [[1e7, "Cr"], [1e5, "L"]]
|
|
56
|
+
};
|
|
57
|
+
var DEFAULT_MONEY_TIERS = [[1e9, "B"], [1e6, "M"]];
|
|
58
|
+
function moneyTiers(locale) {
|
|
59
|
+
return MONEY_TIERS[locale] ?? DEFAULT_MONEY_TIERS;
|
|
60
|
+
}
|
|
61
|
+
var COUNT_SCALE_MIN = 1e7;
|
|
62
|
+
function toNum(v) {
|
|
63
|
+
if (v === null || v === void 0 || v === "") return null;
|
|
64
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
65
|
+
return Number.isFinite(n) ? n : null;
|
|
66
|
+
}
|
|
67
|
+
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
68
|
+
var ISO_DATE = /^\d{4}-\d{2}-\d{2}([T ]|$)/;
|
|
69
|
+
function toDate(v) {
|
|
70
|
+
if (v === null || v === void 0 || v === "") return null;
|
|
71
|
+
if (v instanceof Date) return Number.isNaN(v.getTime()) ? null : v;
|
|
72
|
+
if (typeof v === "number") {
|
|
73
|
+
const d = new Date(v);
|
|
74
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
75
|
+
}
|
|
76
|
+
if (typeof v === "string" && ISO_DATE.test(v.trim())) {
|
|
77
|
+
const d = new Date(v.trim());
|
|
78
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function makeFmt(cfg = {}) {
|
|
83
|
+
const locale = cfg.locale || DEFAULT_FMT_CONFIG.locale;
|
|
84
|
+
const currency = cfg.currency || DEFAULT_FMT_CONFIG.currency;
|
|
85
|
+
const group = (n, decimals = 0) => n.toLocaleString(locale, { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
|
86
|
+
const symbol = (() => {
|
|
87
|
+
try {
|
|
88
|
+
const parts = new Intl.NumberFormat(locale, { style: "currency", currency }).formatToParts(0);
|
|
89
|
+
return parts.find((p) => p.type === "currency")?.value ?? "";
|
|
90
|
+
} catch {
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
})();
|
|
94
|
+
const scaled = (abs, tiers, decimals) => {
|
|
95
|
+
for (const [threshold, unit] of tiers) {
|
|
96
|
+
if (abs >= threshold) return `${group(abs / threshold, decimals)} ${unit}`;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
};
|
|
100
|
+
function money(v, decimals = 2) {
|
|
101
|
+
const n = toNum(v);
|
|
102
|
+
if (n === null) return DASH;
|
|
103
|
+
const sign = n < 0 ? "-" : "";
|
|
104
|
+
const abs = Math.abs(n);
|
|
105
|
+
const tiers = moneyTiers(locale);
|
|
106
|
+
for (let i = 0; i < tiers.length; i++) {
|
|
107
|
+
const [threshold, unit] = tiers[i];
|
|
108
|
+
if (abs < threshold) continue;
|
|
109
|
+
const value = Number((abs / threshold).toFixed(decimals));
|
|
110
|
+
const higher = tiers[i - 1];
|
|
111
|
+
if (higher && value * threshold >= higher[0]) {
|
|
112
|
+
return `${sign}${symbol}${group(abs / higher[0], decimals)} ${higher[1]}`;
|
|
113
|
+
}
|
|
114
|
+
return `${sign}${symbol}${group(abs / threshold, decimals)} ${unit}`;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
return new Intl.NumberFormat(locale, {
|
|
118
|
+
style: "currency",
|
|
119
|
+
currency,
|
|
120
|
+
minimumFractionDigits: Number.isInteger(n) ? 0 : decimals,
|
|
121
|
+
maximumFractionDigits: decimals
|
|
122
|
+
}).format(n);
|
|
123
|
+
} catch {
|
|
124
|
+
return `${sign}${symbol}${group(abs, Number.isInteger(abs) ? 0 : decimals)}`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function num(v, decimals) {
|
|
128
|
+
const n = toNum(v);
|
|
129
|
+
if (n === null) return DASH;
|
|
130
|
+
return group(n, decimals ?? (Number.isInteger(n) ? 0 : 2));
|
|
131
|
+
}
|
|
132
|
+
function count(v, decimals = 2) {
|
|
133
|
+
const n = toNum(v);
|
|
134
|
+
if (n === null) return DASH;
|
|
135
|
+
const abs = Math.abs(n);
|
|
136
|
+
const s = abs >= COUNT_SCALE_MIN ? scaled(abs, moneyTiers(locale), decimals) : null;
|
|
137
|
+
if (s) return `${n < 0 ? "-" : ""}${s}`;
|
|
138
|
+
return group(n, Number.isInteger(n) ? 0 : decimals);
|
|
139
|
+
}
|
|
140
|
+
function pct(part, whole, decimals = 1) {
|
|
141
|
+
const p = toNum(part), w = toNum(whole);
|
|
142
|
+
if (p === null || w === null || w === 0) return DASH;
|
|
143
|
+
return `${group(p / w * 100, decimals)}%`;
|
|
144
|
+
}
|
|
145
|
+
function delta(current, previous, decimals = 1) {
|
|
146
|
+
const c = toNum(current), p = toNum(previous);
|
|
147
|
+
if (c === null || p === null || p === 0) return DASH;
|
|
148
|
+
const change = (c - p) / Math.abs(p) * 100;
|
|
149
|
+
return `${change >= 0 ? "+" : "-"}${group(Math.abs(change), decimals)}%`;
|
|
150
|
+
}
|
|
151
|
+
function date(v) {
|
|
152
|
+
const d = toDate(v);
|
|
153
|
+
if (!d) return DASH;
|
|
154
|
+
return `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} ${d.getUTCFullYear()}`;
|
|
155
|
+
}
|
|
156
|
+
function range(from, to) {
|
|
157
|
+
const a = toDate(from), b = toDate(to);
|
|
158
|
+
if (!a || !b) return DASH;
|
|
159
|
+
const left = a.getUTCFullYear() === b.getUTCFullYear() ? `${a.getUTCDate()} ${MONTHS[a.getUTCMonth()]}` : date(a);
|
|
160
|
+
return `${left} \u2013 ${date(b)}`;
|
|
161
|
+
}
|
|
162
|
+
function list(items, conjunction = "and") {
|
|
163
|
+
const parts = (items || []).map((i) => String(i ?? "").trim()).filter(Boolean);
|
|
164
|
+
if (!parts.length) return DASH;
|
|
165
|
+
if (parts.length === 1) return parts[0];
|
|
166
|
+
return `${parts.slice(0, -1).join(", ")} ${conjunction} ${parts[parts.length - 1]}`;
|
|
167
|
+
}
|
|
168
|
+
return { money, num, count, pct, delta, date, range, list };
|
|
33
169
|
}
|
|
34
170
|
|
|
35
171
|
// src/userResponse/scripts/script-bootstrap.ts
|
|
@@ -38,8 +174,28 @@ if (!SCRIPT_ARG) {
|
|
|
38
174
|
sendError("Bootstrap was invoked without a script path.", "compile");
|
|
39
175
|
process.exit(1);
|
|
40
176
|
}
|
|
41
|
-
var
|
|
177
|
+
var BRIDGE_URL = process.env.SCRIPT_BRIDGE_URL;
|
|
178
|
+
var CONTEXT_TOKEN = process.env.SA_CONTEXT_TOKEN;
|
|
179
|
+
var observedPolicies = /* @__PURE__ */ new Set();
|
|
180
|
+
var bridgeHeaders = () => ({
|
|
181
|
+
"content-type": "application/json",
|
|
182
|
+
...CONTEXT_TOKEN ? { "x-sa-context": CONTEXT_TOKEN } : {}
|
|
183
|
+
});
|
|
42
184
|
var executedQueries = [];
|
|
185
|
+
var toolIdCallCounts = {};
|
|
186
|
+
var PREVIEW_MAX_ROWS = 10;
|
|
187
|
+
var PREVIEW_MAX_CHARS = 200;
|
|
188
|
+
var QUERY_ROW_CAP = 250;
|
|
189
|
+
function previewRows(data) {
|
|
190
|
+
return data.slice(0, PREVIEW_MAX_ROWS).map((row) => {
|
|
191
|
+
if (!row || typeof row !== "object") return row;
|
|
192
|
+
const out = {};
|
|
193
|
+
for (const [k, v] of Object.entries(row)) {
|
|
194
|
+
out[k] = typeof v === "string" && v.length > PREVIEW_MAX_CHARS ? v.slice(0, PREVIEW_MAX_CHARS) + "\u2026" : v;
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
43
199
|
var initResolve = null;
|
|
44
200
|
var initPromise = new Promise((resolve) => {
|
|
45
201
|
initResolve = resolve;
|
|
@@ -48,31 +204,87 @@ function buildCtx(init) {
|
|
|
48
204
|
return {
|
|
49
205
|
now: init.now,
|
|
50
206
|
async query(toolId, sql) {
|
|
51
|
-
|
|
207
|
+
if (!BRIDGE_URL) throw new Error("SCRIPT_BRIDGE_URL is not set \u2014 script bridge server is not running");
|
|
52
208
|
const startedAt = Date.now();
|
|
53
|
-
|
|
54
|
-
|
|
209
|
+
send({ type: "stream", chunk: `
|
|
210
|
+
\u{1F4DD} **Querying ${toolId}:**
|
|
211
|
+
\`\`\`sql
|
|
212
|
+
${sql}
|
|
213
|
+
\`\`\`
|
|
214
|
+
|
|
215
|
+
` });
|
|
216
|
+
send({ type: "stream", chunk: `__QUERY_TIMER_START_Executing query__` });
|
|
217
|
+
const res = await fetch(`${BRIDGE_URL}/query`, {
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: bridgeHeaders(),
|
|
220
|
+
body: JSON.stringify({ toolId, sql, limit: QUERY_ROW_CAP })
|
|
55
221
|
});
|
|
56
|
-
|
|
57
|
-
const result = await promise;
|
|
222
|
+
const json = await res.json();
|
|
58
223
|
const executionTimeMs = Date.now() - startedAt;
|
|
224
|
+
for (const p of json?.metadata?.appliedPolicies ?? []) observedPolicies.add(p);
|
|
225
|
+
send({ type: "stream", chunk: `__QUERY_TIMER_DONE_${(executionTimeMs / 1e3).toFixed(1)}__
|
|
226
|
+
|
|
227
|
+
` });
|
|
228
|
+
if (!res.ok || !json.success) {
|
|
229
|
+
const errMsg = json.error || `Bridge query failed (${res.status})`;
|
|
230
|
+
send({ type: "stream", chunk: `\u274C **Query failed on ${toolId}:** ${errMsg}
|
|
231
|
+
|
|
232
|
+
` });
|
|
233
|
+
throw new Error(errMsg);
|
|
234
|
+
}
|
|
235
|
+
let truncated = false;
|
|
236
|
+
let totalCount;
|
|
237
|
+
if (json.metadata?.truncated) {
|
|
238
|
+
truncated = true;
|
|
239
|
+
totalCount = typeof json.metadata.totalCount === "number" ? json.metadata.totalCount : void 0;
|
|
240
|
+
const total = json.metadata.totalCount ?? "more";
|
|
241
|
+
const hasGroupBy = /\bGROUP\s+BY\b/i.test(sql);
|
|
242
|
+
const explicitLimitMatch = sql.match(/\bLIMIT\s+(\d+)/i);
|
|
243
|
+
const hasDeliberateLimit = explicitLimitMatch != null && Number(explicitLimitMatch[1]) < QUERY_ROW_CAP;
|
|
244
|
+
if (hasGroupBy) {
|
|
245
|
+
send({ type: "stream", chunk: `\u2139\uFE0F Only the top ${QUERY_ROW_CAP} of ${total} grouped rows are returned (query is already aggregated via GROUP BY) \u2014 expected for a top-N request.
|
|
246
|
+
|
|
247
|
+
` });
|
|
248
|
+
} else if (hasDeliberateLimit) {
|
|
249
|
+
send({ type: "stream", chunk: `\u2139\uFE0F Only the ${explicitLimitMatch[1]} rows requested (of ${total} matching) are returned \u2014 expected for a top-N request with an explicit LIMIT.
|
|
250
|
+
|
|
251
|
+
` });
|
|
252
|
+
} else {
|
|
253
|
+
const msg = `ctx.query truncated the result of this query to ${QUERY_ROW_CAP} rows (it matched ${total}). Do NOT compute over raw rows \u2014 AGGREGATE IN SQL (GROUP BY / mode() / mode(...) FILTER (WHERE \u2026) / COUNT(...) FILTER (\u2026) / CASE) so the query returns the final, already-reduced rows, then have getData return those.`;
|
|
254
|
+
send({ type: "stream", chunk: `\u274C **Result truncated on ${toolId}:** ${msg}
|
|
255
|
+
|
|
256
|
+
` });
|
|
257
|
+
throw new Error(msg);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const data = json.data ?? [];
|
|
261
|
+
const count = json.count ?? data.length;
|
|
262
|
+
send({ type: "stream", chunk: `\u2705 **${count} rows from ${toolId}**
|
|
263
|
+
|
|
264
|
+
` });
|
|
265
|
+
if (data.length > 0) {
|
|
266
|
+
send({ type: "stream", chunk: `<DataTable>${JSON.stringify(previewRows(data))}</DataTable>
|
|
267
|
+
|
|
268
|
+
` });
|
|
269
|
+
}
|
|
270
|
+
const callNumber = toolIdCallCounts[toolId] = (toolIdCallCounts[toolId] ?? 0) + 1;
|
|
271
|
+
const sourceId = callNumber === 1 ? toolId : `${toolId}#${callNumber}`;
|
|
59
272
|
executedQueries.push({
|
|
60
|
-
sourceId
|
|
273
|
+
sourceId,
|
|
61
274
|
sourceName: toolId,
|
|
62
|
-
//
|
|
275
|
+
// enriched by runner after result arrives
|
|
63
276
|
sql,
|
|
64
|
-
data
|
|
65
|
-
count
|
|
66
|
-
|
|
67
|
-
|
|
277
|
+
data,
|
|
278
|
+
count,
|
|
279
|
+
executionTimeMs,
|
|
280
|
+
...json?.metadata?.appliedPolicies?.length ? { appliedPolicies: json.metadata.appliedPolicies } : {}
|
|
68
281
|
});
|
|
69
|
-
return { data
|
|
282
|
+
return { data, count, truncated, totalCount };
|
|
70
283
|
},
|
|
71
284
|
/**
|
|
72
285
|
* Call a tool with structured params (typically a direct tool whose
|
|
73
286
|
* fn(params) does not expect a SQL string). Returns whatever the tool
|
|
74
|
-
* returned — shape is up to the tool.
|
|
75
|
-
* against the authorized externalTools list before executing.
|
|
287
|
+
* returned — shape is up to the tool.
|
|
76
288
|
*
|
|
77
289
|
* Use this when you need a pre-built business-logic tool (e.g.,
|
|
78
290
|
* "Calculate Shelf Life") that takes structured input rather than SQL.
|
|
@@ -83,18 +295,15 @@ function buildCtx(init) {
|
|
|
83
295
|
* the array you want to chart.
|
|
84
296
|
*/
|
|
85
297
|
async runTool(toolId, params = {}) {
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
toolId,
|
|
92
|
-
sql: `-- runTool: ${toolId}`,
|
|
93
|
-
startedAt: Date.now()
|
|
94
|
-
});
|
|
298
|
+
if (!BRIDGE_URL) throw new Error("SCRIPT_BRIDGE_URL is not set \u2014 script bridge server is not running");
|
|
299
|
+
const res = await fetch(`${BRIDGE_URL}/run-tool`, {
|
|
300
|
+
method: "POST",
|
|
301
|
+
headers: bridgeHeaders(),
|
|
302
|
+
body: JSON.stringify({ toolId, params })
|
|
95
303
|
});
|
|
96
|
-
|
|
97
|
-
|
|
304
|
+
const json = await res.json();
|
|
305
|
+
if (!res.ok || !json.success) throw new Error(json.error || `Bridge run-tool failed (${res.status})`);
|
|
306
|
+
return json.result;
|
|
98
307
|
},
|
|
99
308
|
/**
|
|
100
309
|
* Register a computed dataset. Flows into executedQueries alongside
|
|
@@ -121,8 +330,33 @@ function buildCtx(init) {
|
|
|
121
330
|
}
|
|
122
331
|
};
|
|
123
332
|
}
|
|
333
|
+
function guardCtx(ctx) {
|
|
334
|
+
const PASSTHROUGH = /* @__PURE__ */ new Set([
|
|
335
|
+
"then",
|
|
336
|
+
"catch",
|
|
337
|
+
"finally",
|
|
338
|
+
"constructor",
|
|
339
|
+
"toJSON",
|
|
340
|
+
"inspect",
|
|
341
|
+
"valueOf",
|
|
342
|
+
"toString",
|
|
343
|
+
"hasOwnProperty",
|
|
344
|
+
"nodeType"
|
|
345
|
+
]);
|
|
346
|
+
return new Proxy(ctx, {
|
|
347
|
+
get(target, prop, receiver) {
|
|
348
|
+
if (typeof prop === "symbol" || prop in target || PASSTHROUGH.has(prop)) {
|
|
349
|
+
return Reflect.get(target, prop, receiver);
|
|
350
|
+
}
|
|
351
|
+
const available = Reflect.ownKeys(target).filter((k) => typeof k === "string").join(", ");
|
|
352
|
+
throw new TypeError(
|
|
353
|
+
`ctx.${String(prop)} does not exist. ctx has exactly: ${available}. There are no formatting/parsing/date helpers on ctx \u2014 interpolate ISO date strings straight into the SQL and do date arithmetic in SQL.`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}
|
|
124
358
|
function send(msg) {
|
|
125
|
-
process.stdout.write(encodeMessage(msg));
|
|
359
|
+
return process.stdout.write(encodeMessage(msg));
|
|
126
360
|
}
|
|
127
361
|
function sendError(message, phase, stack) {
|
|
128
362
|
send({ type: "error", message, stack, phase });
|
|
@@ -130,7 +364,15 @@ function sendError(message, phase, stack) {
|
|
|
130
364
|
var splitter = new LineSplitter();
|
|
131
365
|
process.stdin.setEncoding("utf-8");
|
|
132
366
|
process.stdin.on("data", (chunk) => {
|
|
133
|
-
|
|
367
|
+
let lines;
|
|
368
|
+
try {
|
|
369
|
+
lines = splitter.push(chunk);
|
|
370
|
+
} catch (err) {
|
|
371
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
372
|
+
sendError(msg, "runtime");
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
for (const line of lines) {
|
|
134
376
|
let msg;
|
|
135
377
|
try {
|
|
136
378
|
msg = JSON.parse(line);
|
|
@@ -140,55 +382,71 @@ process.stdin.on("data", (chunk) => {
|
|
|
140
382
|
handleMessage(msg);
|
|
141
383
|
}
|
|
142
384
|
});
|
|
143
|
-
process.stdin.on("end", () => {
|
|
144
|
-
});
|
|
145
385
|
function handleMessage(msg) {
|
|
146
386
|
switch (msg.type) {
|
|
147
387
|
case "init": {
|
|
148
388
|
if (initResolve) {
|
|
149
389
|
const now = new Date(msg.now || (/* @__PURE__ */ new Date()).toISOString());
|
|
150
390
|
const params = restoreParamTypes(msg.params || {}, msg.paramSchema);
|
|
151
|
-
initResolve({ params, now });
|
|
391
|
+
initResolve({ params, now, mode: msg.mode, data: msg.data, datasets: msg.datasets, fmt: msg.fmt, entities: msg.entities, appliedPolicies: msg.appliedPolicies });
|
|
152
392
|
initResolve = null;
|
|
153
393
|
}
|
|
154
394
|
break;
|
|
155
395
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function collectDatasets(queries) {
|
|
399
|
+
const out = {};
|
|
400
|
+
for (const q of queries) {
|
|
401
|
+
if (q.virtual && q.sourceId.startsWith("computed:")) {
|
|
402
|
+
out[q.sourceId.slice("computed:".length)] = q.data;
|
|
163
403
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
404
|
+
}
|
|
405
|
+
return out;
|
|
406
|
+
}
|
|
407
|
+
async function runComponents(mod, data, params, fmtCfg, datasets = {}, entities = [], scope = []) {
|
|
408
|
+
const getComponents = mod?.getComponents ?? mod?.default?.getComponents;
|
|
409
|
+
if (typeof getComponents !== "function") return {};
|
|
410
|
+
try {
|
|
411
|
+
const out = await getComponents(data, params, makeFmt(fmtCfg), datasets, entities, scope);
|
|
412
|
+
if (!Array.isArray(out)) {
|
|
413
|
+
return { componentsError: "getComponents must return an ARRAY of component specs" };
|
|
171
414
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
415
|
+
if (out.some((c) => !c || typeof c !== "object")) {
|
|
416
|
+
return { componentsError: "every entry getComponents returns must be a component spec object" };
|
|
417
|
+
}
|
|
418
|
+
return { components: out };
|
|
419
|
+
} catch (err) {
|
|
420
|
+
const e = err;
|
|
421
|
+
return { componentsError: e?.message || String(err) };
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function runAnalysis(mod, data, params, fmtCfg, datasets = {}, entities = [], scope = []) {
|
|
425
|
+
const getAnalysis = mod?.getAnalysis ?? mod?.default?.getAnalysis;
|
|
426
|
+
if (typeof getAnalysis !== "function") return {};
|
|
427
|
+
try {
|
|
428
|
+
const out = await getAnalysis(data, params, makeFmt(fmtCfg), datasets, entities, scope);
|
|
429
|
+
if (!out || typeof out !== "object") {
|
|
430
|
+
return { analysisError: "getAnalysis must return an object with { analysis, summary, method }" };
|
|
179
431
|
}
|
|
432
|
+
return {
|
|
433
|
+
analysis: {
|
|
434
|
+
analysis: typeof out.analysis === "string" ? out.analysis : void 0,
|
|
435
|
+
summary: Array.isArray(out.summary) ? out.summary.map((s) => String(s)) : void 0,
|
|
436
|
+
method: typeof out.method === "string" ? out.method : void 0
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
} catch (err) {
|
|
440
|
+
const e = err;
|
|
441
|
+
return { analysisError: e?.message || String(err) };
|
|
180
442
|
}
|
|
181
443
|
}
|
|
182
444
|
(async () => {
|
|
445
|
+
let mod;
|
|
183
446
|
let getData;
|
|
184
447
|
try {
|
|
185
|
-
|
|
186
|
-
getData = mod.getData ?? mod.default;
|
|
187
|
-
if (typeof getData !== "function") {
|
|
188
|
-
throw new Error(
|
|
189
|
-
`Script at ${SCRIPT_ARG} does not export getData \u2014 expected \`export async function getData(ctx, params)\``
|
|
190
|
-
);
|
|
191
|
-
}
|
|
448
|
+
mod = await import(pathToFileURL(SCRIPT_ARG).href);
|
|
449
|
+
getData = mod.getData ?? (typeof mod.default === "function" ? mod.default : mod.default?.getData);
|
|
192
450
|
} catch (err) {
|
|
193
451
|
const e = err;
|
|
194
452
|
sendError(e.message || String(err), "compile", e.stack);
|
|
@@ -196,7 +454,33 @@ function handleMessage(msg) {
|
|
|
196
454
|
return;
|
|
197
455
|
}
|
|
198
456
|
const init = await initPromise;
|
|
199
|
-
|
|
457
|
+
if (init.mode === "analysis" || init.mode === "components") {
|
|
458
|
+
const started = Date.now();
|
|
459
|
+
const rows = Array.isArray(init.data) ? init.data : [];
|
|
460
|
+
const ds = init.datasets ?? {};
|
|
461
|
+
const authored = init.mode === "analysis" ? await runAnalysis(mod, rows, init.params, init.fmt, ds, init.entities, init.appliedPolicies ?? []) : await runComponents(mod, rows, init.params, init.fmt, ds, init.entities, init.appliedPolicies ?? []);
|
|
462
|
+
const flushedAuthored = send({
|
|
463
|
+
type: "result",
|
|
464
|
+
data: [],
|
|
465
|
+
count: rows.length,
|
|
466
|
+
executedQueries: [],
|
|
467
|
+
executionTimeMs: Date.now() - started,
|
|
468
|
+
sampleRows: [],
|
|
469
|
+
...authored
|
|
470
|
+
});
|
|
471
|
+
if (flushedAuthored) process.exit(0);
|
|
472
|
+
else process.stdout.once("drain", () => process.exit(0));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (typeof getData !== "function") {
|
|
476
|
+
sendError(
|
|
477
|
+
`Script at ${SCRIPT_ARG} does not export getData \u2014 expected \`export async function getData(ctx, params)\``,
|
|
478
|
+
"compile"
|
|
479
|
+
);
|
|
480
|
+
process.exit(1);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const ctx = guardCtx(buildCtx(init));
|
|
200
484
|
const startedAt = Date.now();
|
|
201
485
|
try {
|
|
202
486
|
const raw = await getData(ctx, init.params);
|
|
@@ -204,17 +488,28 @@ function handleMessage(msg) {
|
|
|
204
488
|
const data = normalizeResultData(raw);
|
|
205
489
|
const count = typeof raw?.count === "number" ? raw.count : data.length;
|
|
206
490
|
const sampleRows = data.slice(0, 5);
|
|
491
|
+
const ds = collectDatasets(executedQueries);
|
|
492
|
+
const observed = [...observedPolicies];
|
|
493
|
+
const { analysis, analysisError } = await runAnalysis(mod, data, init.params, init.fmt, ds, init.entities, observed);
|
|
494
|
+
const { components, componentsError } = await runComponents(mod, data, init.params, init.fmt, ds, init.entities, observed);
|
|
207
495
|
const result = {
|
|
208
496
|
type: "result",
|
|
209
497
|
data,
|
|
210
498
|
count,
|
|
211
499
|
executedQueries,
|
|
212
500
|
executionTimeMs,
|
|
213
|
-
sampleRows
|
|
501
|
+
sampleRows,
|
|
502
|
+
analysis,
|
|
503
|
+
analysisError,
|
|
504
|
+
components,
|
|
505
|
+
componentsError
|
|
214
506
|
};
|
|
215
|
-
send(result);
|
|
216
|
-
|
|
217
|
-
|
|
507
|
+
const flushed = send(result);
|
|
508
|
+
if (flushed) {
|
|
509
|
+
process.exit(0);
|
|
510
|
+
} else {
|
|
511
|
+
process.stdout.once("drain", () => process.exit(0));
|
|
512
|
+
}
|
|
218
513
|
} catch (err) {
|
|
219
514
|
const e = err;
|
|
220
515
|
sendError(e.message || String(err), "runtime", e.stack);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superatomai/sdk-node",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8-cp",
|
|
4
4
|
"description": "Node.js TypeScript SDK for Superatom",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
-
"dist"
|
|
16
|
+
"dist",
|
|
17
|
+
"!dist/**/*.map"
|
|
17
18
|
],
|
|
18
19
|
"keywords": [
|
|
19
20
|
"superatom",
|
|
@@ -29,20 +30,26 @@
|
|
|
29
30
|
"tsup": "^8.0.1",
|
|
30
31
|
"tsx": "^4.20.6",
|
|
31
32
|
"typescript": "^5.3.3",
|
|
32
|
-
"ws": "^8.
|
|
33
|
+
"ws": "^8.21.0"
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
35
36
|
"@anthropic-ai/sdk": "^0.66.0",
|
|
37
|
+
"@earendil-works/pi-coding-agent": "^0.80.3",
|
|
36
38
|
"@google/generative-ai": "^0.21.0",
|
|
37
39
|
"groq-sdk": "^0.33.0",
|
|
38
40
|
"jsonrepair": "^3.13.1",
|
|
39
41
|
"openai": "^4.77.0",
|
|
40
|
-
"
|
|
42
|
+
"typebox": "^1.1.38",
|
|
43
|
+
"ws": "^8.21.0",
|
|
41
44
|
"zod": "^3.25.76"
|
|
42
45
|
},
|
|
46
|
+
"optionalDependencies": {
|
|
47
|
+
"@openai/codex-sdk": "0.149.1"
|
|
48
|
+
},
|
|
43
49
|
"scripts": {
|
|
44
|
-
"build": "tsup",
|
|
50
|
+
"build": "tsc --noEmit && tsup",
|
|
45
51
|
"dev": "tsup --watch",
|
|
46
|
-
"test": "tsx test-sdk.ts"
|
|
52
|
+
"test": "tsx test-sdk.ts",
|
|
53
|
+
"type-check": "tsc --noEmit"
|
|
47
54
|
}
|
|
48
55
|
}
|