@superatomai/sdk-node 0.0.92-dsp → 0.0.94-dsp
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +942 -942
- package/dist/index.d.mts +212 -800
- package/dist/index.d.ts +212 -800
- package/dist/index.js +411 -10
- package/dist/index.mjs +407 -8
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2909,6 +2909,7 @@ async function handleDataRequestInner(data, collections, sendMessage, tools) {
|
|
|
2909
2909
|
let requestId;
|
|
2910
2910
|
let collection;
|
|
2911
2911
|
let op;
|
|
2912
|
+
const fromId = typeof data?.from?.id === "string" ? data.from.id : void 0;
|
|
2912
2913
|
try {
|
|
2913
2914
|
const dataRequest = DataRequestMessageSchema.parse(data);
|
|
2914
2915
|
const { id, payload } = dataRequest;
|
|
@@ -2919,13 +2920,13 @@ async function handleDataRequestInner(data, collections, sendMessage, tools) {
|
|
|
2919
2920
|
if (!collections[collection]) {
|
|
2920
2921
|
sendDataResponse(id, collection, op, null, {
|
|
2921
2922
|
error: `Collection '${collection}' not found`
|
|
2922
|
-
}, sendMessage);
|
|
2923
|
+
}, sendMessage, fromId);
|
|
2923
2924
|
return;
|
|
2924
2925
|
}
|
|
2925
2926
|
if (!collections[collection][op]) {
|
|
2926
2927
|
sendDataResponse(id, collection, op, null, {
|
|
2927
2928
|
error: `Operation '${op}' not found for collection '${collection}'`
|
|
2928
|
-
}, sendMessage);
|
|
2929
|
+
}, sendMessage, fromId);
|
|
2929
2930
|
return;
|
|
2930
2931
|
}
|
|
2931
2932
|
const startTime = performance.now();
|
|
@@ -2997,21 +2998,22 @@ async function handleDataRequestInner(data, collections, sendMessage, tools) {
|
|
|
2997
2998
|
logger.warn(`UIBlock ${uiBlockId} not found in threads`);
|
|
2998
2999
|
}
|
|
2999
3000
|
}
|
|
3000
|
-
sendDataResponse(id, collection, op, result, { executionMs }, sendMessage);
|
|
3001
|
+
sendDataResponse(id, collection, op, result, { executionMs }, sendMessage, fromId);
|
|
3001
3002
|
} catch (error) {
|
|
3002
3003
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3003
3004
|
logger.error("Failed to handle data request:", error);
|
|
3004
3005
|
if (requestId) {
|
|
3005
3006
|
sendDataResponse(requestId, collection || "unknown", op || "unknown", null, {
|
|
3006
3007
|
error: errorMsg
|
|
3007
|
-
}, sendMessage);
|
|
3008
|
+
}, sendMessage, fromId);
|
|
3008
3009
|
}
|
|
3009
3010
|
}
|
|
3010
3011
|
}
|
|
3011
|
-
function sendDataResponse(id, collection, op, data, meta, sendMessage) {
|
|
3012
|
+
function sendDataResponse(id, collection, op, data, meta, sendMessage, fromId) {
|
|
3012
3013
|
const response = {
|
|
3013
3014
|
id,
|
|
3014
3015
|
from: { type: "data-agent" },
|
|
3016
|
+
to: fromId ? { id: fromId } : void 0,
|
|
3015
3017
|
type: "DATA_RES",
|
|
3016
3018
|
payload: {
|
|
3017
3019
|
collection,
|
|
@@ -24714,6 +24716,14 @@ function readCodexConfig(env) {
|
|
|
24714
24716
|
return "";
|
|
24715
24717
|
}
|
|
24716
24718
|
}
|
|
24719
|
+
var CODEX_PATH_ENV = "SA_CODEX_PATH";
|
|
24720
|
+
function codexPathOverride(env = process.env, exists = fs8.existsSync) {
|
|
24721
|
+
const p = env[CODEX_PATH_ENV]?.trim();
|
|
24722
|
+
if (!p) return void 0;
|
|
24723
|
+
if (exists(p)) return p;
|
|
24724
|
+
console.warn(`[codex] ${CODEX_PATH_ENV}=${p} does not exist; using the codex bundled with @openai/codex-sdk`);
|
|
24725
|
+
return void 0;
|
|
24726
|
+
}
|
|
24717
24727
|
function codexDefaultProvider(env = process.env, configToml) {
|
|
24718
24728
|
const choice = env.CODEX_MODEL_PROVIDER?.trim();
|
|
24719
24729
|
if (choice && choice !== "openrouter") return null;
|
|
@@ -24775,7 +24785,9 @@ function createCodexSession(opts) {
|
|
|
24775
24785
|
if (thread) return thread;
|
|
24776
24786
|
const { Codex } = await loadCodex();
|
|
24777
24787
|
const config2 = codexRunConfig(opts, provider);
|
|
24788
|
+
const codexPath = codexPathOverride();
|
|
24778
24789
|
const codex = new Codex({
|
|
24790
|
+
...codexPath ? { codexPathOverride: codexPath } : {},
|
|
24779
24791
|
...opts.apiKey ? { apiKey: opts.apiKey } : {},
|
|
24780
24792
|
...opts.baseUrl ? { baseUrl: opts.baseUrl } : {},
|
|
24781
24793
|
env: buildEnv(opts, provider),
|
|
@@ -28233,6 +28245,355 @@ var ReportManager = class {
|
|
|
28233
28245
|
}
|
|
28234
28246
|
};
|
|
28235
28247
|
|
|
28248
|
+
// src/parcels/index.ts
|
|
28249
|
+
import { createHash as createHash3 } from "crypto";
|
|
28250
|
+
var PARCEL_THRESHOLD_BYTES = 256 * 1024;
|
|
28251
|
+
var MAX_INLINE_BYTES = 900 * 1024;
|
|
28252
|
+
var MAX_PARCEL_BYTES = 95 * 1024 * 1024;
|
|
28253
|
+
var NEVER_PARCEL = /* @__PURE__ */ new Set(["PING", "PONG", "BUNDLE_CHUNK", "BUNDLE_CSS_CHUNK"]);
|
|
28254
|
+
var RETRY_DELAYS_MS = [500, 1500];
|
|
28255
|
+
var REQUEST_TIMEOUT_MS = 6e4;
|
|
28256
|
+
var MAX_DEPTH = 32;
|
|
28257
|
+
var PARCEL_FIELD = "__parcel";
|
|
28258
|
+
var FILE_FIELD = "__parcelFile";
|
|
28259
|
+
var isRef = (ref) => !!ref && typeof ref === "object" && typeof ref.projectId === "string" && typeof ref.hash === "string" && typeof ref.ext === "string" && typeof ref.size === "number";
|
|
28260
|
+
function parcelRefOf(payload) {
|
|
28261
|
+
const ref = payload?.[PARCEL_FIELD];
|
|
28262
|
+
return isRef(ref) ? ref : null;
|
|
28263
|
+
}
|
|
28264
|
+
var fileRefOf = (value) => {
|
|
28265
|
+
const ref = value?.[FILE_FIELD];
|
|
28266
|
+
return typeof ref?.contentType === "string" && isRef(ref) ? ref : null;
|
|
28267
|
+
};
|
|
28268
|
+
var sha256Hex = (bytes) => createHash3("sha256").update(bytes).digest("hex");
|
|
28269
|
+
var sleep = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
28270
|
+
var ParcelHttpError = class extends Error {
|
|
28271
|
+
constructor(message, status) {
|
|
28272
|
+
super(message);
|
|
28273
|
+
this.status = status;
|
|
28274
|
+
}
|
|
28275
|
+
};
|
|
28276
|
+
var retryable = (err) => !(err instanceof ParcelHttpError) || err.status >= 500 || err.status === 408 || err.status === 429;
|
|
28277
|
+
async function withRetries(what, run) {
|
|
28278
|
+
let lastError;
|
|
28279
|
+
for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
28280
|
+
try {
|
|
28281
|
+
return await run();
|
|
28282
|
+
} catch (err) {
|
|
28283
|
+
lastError = err;
|
|
28284
|
+
if (!retryable(err) || attempt === RETRY_DELAYS_MS.length) break;
|
|
28285
|
+
logger.warn(`[parcels] ${what} failed (attempt ${attempt + 1}), retrying: ${err?.message ?? err}`);
|
|
28286
|
+
await sleep(RETRY_DELAYS_MS[attempt] ?? 0);
|
|
28287
|
+
}
|
|
28288
|
+
}
|
|
28289
|
+
throw lastError;
|
|
28290
|
+
}
|
|
28291
|
+
var ParcelClient = class {
|
|
28292
|
+
constructor(opts) {
|
|
28293
|
+
this.opts = opts;
|
|
28294
|
+
this.base = opts.apiUrl.replace(/\/+$/, "");
|
|
28295
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
28296
|
+
this.timeoutMs = opts.timeoutMs ?? REQUEST_TIMEOUT_MS;
|
|
28297
|
+
}
|
|
28298
|
+
url(ref) {
|
|
28299
|
+
return `${this.base}/parcels/${encodeURIComponent(ref.projectId)}/${ref.hash}.${ref.ext}`;
|
|
28300
|
+
}
|
|
28301
|
+
async call(url, init) {
|
|
28302
|
+
const res = await this.fetchImpl(url, {
|
|
28303
|
+
...init,
|
|
28304
|
+
headers: { ...init.headers, Authorization: `Bearer ${this.opts.apiKey}` },
|
|
28305
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
28306
|
+
});
|
|
28307
|
+
if (!res.ok) {
|
|
28308
|
+
const text = await res.text().catch(() => "");
|
|
28309
|
+
throw new ParcelHttpError(`HTTP ${res.status}${text ? ` ${text.slice(0, 200)}` : ""}`, res.status);
|
|
28310
|
+
}
|
|
28311
|
+
return res;
|
|
28312
|
+
}
|
|
28313
|
+
/** Stores the bytes and returns the receipt for them. */
|
|
28314
|
+
async upload(bytes, ext = "json", contentType = "application/json") {
|
|
28315
|
+
if (bytes.byteLength > MAX_PARCEL_BYTES) {
|
|
28316
|
+
throw new Error(`Parcel too large (${bytes.byteLength} bytes, limit ${MAX_PARCEL_BYTES})`);
|
|
28317
|
+
}
|
|
28318
|
+
const ref = { projectId: this.opts.projectId, hash: sha256Hex(bytes), ext, size: bytes.byteLength };
|
|
28319
|
+
await withRetries(
|
|
28320
|
+
"upload",
|
|
28321
|
+
() => this.call(this.url(ref), { method: "PUT", headers: { "Content-Type": contentType }, body: bytes })
|
|
28322
|
+
);
|
|
28323
|
+
return ref;
|
|
28324
|
+
}
|
|
28325
|
+
/** The bytes behind a receipt, checked against its hash. */
|
|
28326
|
+
async download(ref) {
|
|
28327
|
+
return withRetries("download", async () => {
|
|
28328
|
+
const res = await this.call(this.url(ref), { method: "GET" });
|
|
28329
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
28330
|
+
if (sha256Hex(bytes) !== ref.hash) {
|
|
28331
|
+
throw new ParcelHttpError("Parcel content does not match its hash", 502);
|
|
28332
|
+
}
|
|
28333
|
+
return bytes;
|
|
28334
|
+
});
|
|
28335
|
+
}
|
|
28336
|
+
};
|
|
28337
|
+
var ParcelFile = class {
|
|
28338
|
+
constructor(ref, client) {
|
|
28339
|
+
this.ref = ref;
|
|
28340
|
+
this.client = client;
|
|
28341
|
+
this.name = ref.name;
|
|
28342
|
+
this.size = ref.size;
|
|
28343
|
+
this.contentType = ref.contentType;
|
|
28344
|
+
}
|
|
28345
|
+
/** The file's bytes, checked against their hash. Throws if they cannot be fetched. */
|
|
28346
|
+
async download() {
|
|
28347
|
+
if (!this.client) throw new Error("Cannot download files: SA_API_URL or the API key is not set");
|
|
28348
|
+
return this.client.download(this.ref);
|
|
28349
|
+
}
|
|
28350
|
+
toJSON() {
|
|
28351
|
+
return { [FILE_FIELD]: this.ref };
|
|
28352
|
+
}
|
|
28353
|
+
};
|
|
28354
|
+
var isBinary = (v) => v instanceof Uint8Array || v instanceof ArrayBuffer;
|
|
28355
|
+
var isPlain = (v) => {
|
|
28356
|
+
if (!v || typeof v !== "object") return false;
|
|
28357
|
+
const proto = Object.getPrototypeOf(v);
|
|
28358
|
+
return proto === Object.prototype || proto === null;
|
|
28359
|
+
};
|
|
28360
|
+
function hasBinary(value, depth = 0) {
|
|
28361
|
+
if (isBinary(value)) return true;
|
|
28362
|
+
if (depth >= MAX_DEPTH) return false;
|
|
28363
|
+
if (Array.isArray(value)) return value.some((v) => hasBinary(v, depth + 1));
|
|
28364
|
+
if (isPlain(value)) return Object.values(value).some((v) => hasBinary(v, depth + 1));
|
|
28365
|
+
return false;
|
|
28366
|
+
}
|
|
28367
|
+
function hasFileRef(value, depth = 0) {
|
|
28368
|
+
if (fileRefOf(value)) return true;
|
|
28369
|
+
if (depth >= MAX_DEPTH) return false;
|
|
28370
|
+
if (Array.isArray(value)) return value.some((v) => hasFileRef(v, depth + 1));
|
|
28371
|
+
if (isPlain(value)) return Object.values(value).some((v) => hasFileRef(v, depth + 1));
|
|
28372
|
+
return false;
|
|
28373
|
+
}
|
|
28374
|
+
var extOf = (name) => typeof name === "string" && /\.([a-z0-9]{1,10})$/i.exec(name)?.[1]?.toLowerCase() || "bin";
|
|
28375
|
+
async function uploadFiles(value, client, parent, depth = 0) {
|
|
28376
|
+
if (isBinary(value)) {
|
|
28377
|
+
const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
28378
|
+
const name = parent?.name ?? parent?.fileName;
|
|
28379
|
+
const contentType = String(parent?.contentType ?? parent?.mimeType ?? "application/octet-stream");
|
|
28380
|
+
const ref = await client.upload(bytes, extOf(name), contentType);
|
|
28381
|
+
const fileRef = { ...ref, contentType, ...typeof name === "string" ? { name } : {} };
|
|
28382
|
+
return { [FILE_FIELD]: fileRef };
|
|
28383
|
+
}
|
|
28384
|
+
if (depth >= MAX_DEPTH) return value;
|
|
28385
|
+
if (Array.isArray(value)) {
|
|
28386
|
+
const out = [];
|
|
28387
|
+
for (const v of value) out.push(await uploadFiles(v, client, void 0, depth + 1));
|
|
28388
|
+
return out;
|
|
28389
|
+
}
|
|
28390
|
+
if (isPlain(value)) {
|
|
28391
|
+
const out = {};
|
|
28392
|
+
for (const [k, v] of Object.entries(value)) out[k] = await uploadFiles(v, client, value, depth + 1);
|
|
28393
|
+
return out;
|
|
28394
|
+
}
|
|
28395
|
+
return value;
|
|
28396
|
+
}
|
|
28397
|
+
function toFileHandles(value, client, depth = 0) {
|
|
28398
|
+
const ref = fileRefOf(value);
|
|
28399
|
+
if (ref) return new ParcelFile(ref, client);
|
|
28400
|
+
if (depth >= MAX_DEPTH) return value;
|
|
28401
|
+
if (Array.isArray(value)) return value.map((v) => toFileHandles(v, client, depth + 1));
|
|
28402
|
+
if (isPlain(value)) {
|
|
28403
|
+
const out = {};
|
|
28404
|
+
for (const [k, v] of Object.entries(value)) out[k] = toFileHandles(v, client, depth + 1);
|
|
28405
|
+
return out;
|
|
28406
|
+
}
|
|
28407
|
+
return value;
|
|
28408
|
+
}
|
|
28409
|
+
var asError = (message, reason) => ({
|
|
28410
|
+
...message,
|
|
28411
|
+
payload: { error: reason, parcelError: true }
|
|
28412
|
+
});
|
|
28413
|
+
var recipientOf = (message) => {
|
|
28414
|
+
const id = message.to?.id;
|
|
28415
|
+
return typeof id === "string" ? id : "";
|
|
28416
|
+
};
|
|
28417
|
+
var InOrder = class {
|
|
28418
|
+
/** `onIdle` runs each time the last waiting task finishes. */
|
|
28419
|
+
constructor(onIdle) {
|
|
28420
|
+
this.onIdle = onIdle;
|
|
28421
|
+
this.pending = 0;
|
|
28422
|
+
this.chain = Promise.resolve();
|
|
28423
|
+
}
|
|
28424
|
+
get busy() {
|
|
28425
|
+
return this.pending > 0;
|
|
28426
|
+
}
|
|
28427
|
+
run(task) {
|
|
28428
|
+
this.pending++;
|
|
28429
|
+
this.chain = this.chain.then(task).catch((err) => logger.error("[parcels] queue fault:", err)).finally(() => {
|
|
28430
|
+
if (--this.pending === 0) this.onIdle?.();
|
|
28431
|
+
});
|
|
28432
|
+
}
|
|
28433
|
+
idle() {
|
|
28434
|
+
return this.chain;
|
|
28435
|
+
}
|
|
28436
|
+
};
|
|
28437
|
+
var OutgoingParcels = class {
|
|
28438
|
+
constructor(client, jsonParcels, deliver, threshold = PARCEL_THRESHOLD_BYTES) {
|
|
28439
|
+
this.client = client;
|
|
28440
|
+
this.jsonParcels = jsonParcels;
|
|
28441
|
+
this.deliver = deliver;
|
|
28442
|
+
this.threshold = threshold;
|
|
28443
|
+
/** Only recipients with something waiting; a queue is dropped once it empties. */
|
|
28444
|
+
this.queues = /* @__PURE__ */ new Map();
|
|
28445
|
+
this.warnedNoClient = false;
|
|
28446
|
+
}
|
|
28447
|
+
/**
|
|
28448
|
+
* Same contract as the SDK's `send`: never throws, returns whether the message
|
|
28449
|
+
* went out (or, for a queued one, was accepted).
|
|
28450
|
+
*/
|
|
28451
|
+
send(message) {
|
|
28452
|
+
if (NEVER_PARCEL.has(message.type)) return this.now(message);
|
|
28453
|
+
let files = false;
|
|
28454
|
+
try {
|
|
28455
|
+
files = hasBinary(message.payload);
|
|
28456
|
+
} catch {
|
|
28457
|
+
}
|
|
28458
|
+
if (files && !this.client) {
|
|
28459
|
+
if (!this.warnedNoClient) {
|
|
28460
|
+
this.warnedNoClient = true;
|
|
28461
|
+
logger.warn("[parcels] a message carries a file but SA_API_URL or the API key is missing \u2014 sent as-is");
|
|
28462
|
+
}
|
|
28463
|
+
files = false;
|
|
28464
|
+
}
|
|
28465
|
+
if (!files) {
|
|
28466
|
+
if (!this.jsonParcels || !this.client) return this.now(message);
|
|
28467
|
+
let wire;
|
|
28468
|
+
try {
|
|
28469
|
+
wire = JSON.stringify(message);
|
|
28470
|
+
} catch {
|
|
28471
|
+
return this.now(message);
|
|
28472
|
+
}
|
|
28473
|
+
if (Buffer.byteLength(wire, "utf8") < this.threshold) return this.now(message, wire);
|
|
28474
|
+
}
|
|
28475
|
+
this.queueFor(message).run(async () => {
|
|
28476
|
+
const out = await this.prepare(message, files);
|
|
28477
|
+
this.deliver(out.message, out.wire);
|
|
28478
|
+
});
|
|
28479
|
+
return true;
|
|
28480
|
+
}
|
|
28481
|
+
/** Delivers now, unless something for the same recipient is already waiting — then behind it. */
|
|
28482
|
+
now(message, wire) {
|
|
28483
|
+
if (!this.queues.get(recipientOf(message))?.busy) return this.deliver(message, wire);
|
|
28484
|
+
this.queueFor(message).run(async () => {
|
|
28485
|
+
this.deliver(message, wire);
|
|
28486
|
+
});
|
|
28487
|
+
return true;
|
|
28488
|
+
}
|
|
28489
|
+
queueFor(message) {
|
|
28490
|
+
const key = recipientOf(message);
|
|
28491
|
+
let queue = this.queues.get(key);
|
|
28492
|
+
if (!queue) {
|
|
28493
|
+
queue = new InOrder(() => this.queues.delete(key));
|
|
28494
|
+
this.queues.set(key, queue);
|
|
28495
|
+
}
|
|
28496
|
+
return queue;
|
|
28497
|
+
}
|
|
28498
|
+
/** Resolves when everything queued so far has been handed to the socket. For tests and shutdown. */
|
|
28499
|
+
async idle() {
|
|
28500
|
+
await Promise.all(Array.from(this.queues.values(), (queue) => queue.idle()));
|
|
28501
|
+
}
|
|
28502
|
+
async prepare(message, files) {
|
|
28503
|
+
let current = message;
|
|
28504
|
+
if (files) {
|
|
28505
|
+
const started2 = Date.now();
|
|
28506
|
+
try {
|
|
28507
|
+
current = { ...message, payload: await uploadFiles(message.payload, this.client) };
|
|
28508
|
+
logger.info(`[parcels] ${message.type} ${message.id ?? ""}: file(s) uploaded (${Date.now() - started2}ms)`);
|
|
28509
|
+
} catch (err) {
|
|
28510
|
+
const reason = err?.message ?? String(err);
|
|
28511
|
+
logger.error(`[parcels] file upload failed for ${message.type}, sending an error instead: ${reason}`);
|
|
28512
|
+
return { message: asError(message, `Could not send a file: ${reason}`) };
|
|
28513
|
+
}
|
|
28514
|
+
}
|
|
28515
|
+
let wire;
|
|
28516
|
+
try {
|
|
28517
|
+
wire = JSON.stringify(current);
|
|
28518
|
+
} catch {
|
|
28519
|
+
return { message: current };
|
|
28520
|
+
}
|
|
28521
|
+
const size = Buffer.byteLength(wire, "utf8");
|
|
28522
|
+
if (!this.jsonParcels || size < this.threshold) return { message: current, wire };
|
|
28523
|
+
const started = Date.now();
|
|
28524
|
+
try {
|
|
28525
|
+
const bytes = Buffer.from(JSON.stringify(current.payload ?? null), "utf8");
|
|
28526
|
+
const ref = await this.client.upload(bytes);
|
|
28527
|
+
logger.info(
|
|
28528
|
+
`[parcels] ${message.type} ${message.id ?? ""} \u2192 parcel ${ref.hash.slice(0, 12)} (${ref.size} bytes, ${Date.now() - started}ms)`
|
|
28529
|
+
);
|
|
28530
|
+
return { message: { ...current, payload: { [PARCEL_FIELD]: ref } } };
|
|
28531
|
+
} catch (err) {
|
|
28532
|
+
const reason = err?.message ?? String(err);
|
|
28533
|
+
if (size <= MAX_INLINE_BYTES) {
|
|
28534
|
+
logger.warn(`[parcels] upload failed for ${message.type}, sending inline (${size} bytes): ${reason}`);
|
|
28535
|
+
return { message: current, wire };
|
|
28536
|
+
}
|
|
28537
|
+
logger.error(`[parcels] upload failed for ${message.type} (${size} bytes), sending an error instead: ${reason}`);
|
|
28538
|
+
return { message: asError(message, `The message was too large to deliver (${size} bytes): ${reason}`) };
|
|
28539
|
+
}
|
|
28540
|
+
}
|
|
28541
|
+
};
|
|
28542
|
+
var IncomingParcels = class {
|
|
28543
|
+
constructor(client, dispatch) {
|
|
28544
|
+
this.client = client;
|
|
28545
|
+
this.dispatch = dispatch;
|
|
28546
|
+
this.queue = new InOrder();
|
|
28547
|
+
}
|
|
28548
|
+
/**
|
|
28549
|
+
* `raw` is the message's text when it is at hand: a message that never
|
|
28550
|
+
* mentions a file placeholder is not searched for one.
|
|
28551
|
+
*/
|
|
28552
|
+
receive(message, raw) {
|
|
28553
|
+
const ref = parcelRefOf(message?.payload);
|
|
28554
|
+
if (!ref) {
|
|
28555
|
+
const withFiles = raw === void 0 || raw.includes(FILE_FIELD) ? this.withFiles(message) : message;
|
|
28556
|
+
if (this.queue.busy) {
|
|
28557
|
+
this.queue.run(async () => this.dispatch(withFiles));
|
|
28558
|
+
} else {
|
|
28559
|
+
this.dispatch(withFiles);
|
|
28560
|
+
}
|
|
28561
|
+
return;
|
|
28562
|
+
}
|
|
28563
|
+
this.queue.run(async () => this.dispatch(await this.restore(message, ref)));
|
|
28564
|
+
}
|
|
28565
|
+
idle() {
|
|
28566
|
+
return this.queue.idle();
|
|
28567
|
+
}
|
|
28568
|
+
withFiles(message) {
|
|
28569
|
+
try {
|
|
28570
|
+
return hasFileRef(message.payload) ? { ...message, payload: toFileHandles(message.payload, this.client) } : message;
|
|
28571
|
+
} catch {
|
|
28572
|
+
return message;
|
|
28573
|
+
}
|
|
28574
|
+
}
|
|
28575
|
+
async restore(message, ref) {
|
|
28576
|
+
if (!this.client) {
|
|
28577
|
+
logger.error(`[parcels] received a parcel for ${message.type} but SA_API_URL or the API key is not set`);
|
|
28578
|
+
return asError(message, "This engine cannot download parcels: SA_API_URL or the API key is not set.");
|
|
28579
|
+
}
|
|
28580
|
+
const started = Date.now();
|
|
28581
|
+
try {
|
|
28582
|
+
const bytes = await this.client.download(ref);
|
|
28583
|
+
const payload = JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
28584
|
+
logger.info(
|
|
28585
|
+
`[parcels] ${message.type} ${message.id ?? ""} \u2190 parcel ${ref.hash.slice(0, 12)} (${ref.size} bytes, ${Date.now() - started}ms)`
|
|
28586
|
+
);
|
|
28587
|
+
return this.withFiles({ ...message, payload });
|
|
28588
|
+
} catch (err) {
|
|
28589
|
+
const reason = err?.message ?? String(err);
|
|
28590
|
+
logger.error(`[parcels] download failed for ${message.type} (${ref.hash.slice(0, 12)}): ${reason}`);
|
|
28591
|
+
return asError(message, `Could not download the message content: ${reason}`);
|
|
28592
|
+
}
|
|
28593
|
+
}
|
|
28594
|
+
};
|
|
28595
|
+
var parcelsFlagFromEnv = () => !/^(off|false|0|no)$/i.test((process.env.SA_PARCELS ?? "").trim());
|
|
28596
|
+
|
|
28236
28597
|
// src/userResponse/scripts/fmt.ts
|
|
28237
28598
|
var DASH = "\u2014";
|
|
28238
28599
|
var DEFAULT_FMT_CONFIG = { locale: "en-IN", currency: "INR" };
|
|
@@ -29584,6 +29945,18 @@ var SuperatomSDK = class {
|
|
|
29584
29945
|
this.type = config2.type || "data-agent";
|
|
29585
29946
|
this.bundleDir = config2.bundleDir;
|
|
29586
29947
|
this.url = config2.url || process.env.SA_WEBSOCKET_URL || DEFAULT_WS_URL;
|
|
29948
|
+
const apiUrl = config2.apiUrl || process.env.SA_API_URL;
|
|
29949
|
+
this.parcelClient = apiUrl && this.apiKey ? new ParcelClient({ apiUrl, projectId: this.projectId, apiKey: this.apiKey }) : null;
|
|
29950
|
+
const jsonParcels = config2.parcels ?? parcelsFlagFromEnv();
|
|
29951
|
+
if (jsonParcels && !this.parcelClient) {
|
|
29952
|
+
logger.warn("[parcels] parcels are on but SA_API_URL or the API key is missing \u2014 large messages go inline");
|
|
29953
|
+
}
|
|
29954
|
+
this.outgoing = new OutgoingParcels(
|
|
29955
|
+
this.parcelClient,
|
|
29956
|
+
jsonParcels,
|
|
29957
|
+
(message, wire) => this.deliver(message, wire)
|
|
29958
|
+
);
|
|
29959
|
+
this.incoming = new IncomingParcels(this.parcelClient, (parsed) => this.dispatchMessage(parsed));
|
|
29587
29960
|
this.anthropicApiKey = config2.ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY || "";
|
|
29588
29961
|
this.groqApiKey = config2.GROQ_API_KEY || process.env.GROQ_API_KEY || "";
|
|
29589
29962
|
this.geminiApiKey = config2.GEMINI_API_KEY || process.env.GEMINI_API_KEY || "";
|
|
@@ -29735,11 +30108,24 @@ var SuperatomSDK = class {
|
|
|
29735
30108
|
});
|
|
29736
30109
|
}
|
|
29737
30110
|
/**
|
|
29738
|
-
* Handle incoming WebSocket messages
|
|
30111
|
+
* Handle incoming WebSocket messages.
|
|
30112
|
+
*
|
|
30113
|
+
* Parsed here, then handed to the parcel layer, which restores a parcel's
|
|
30114
|
+
* payload (and holds later messages back until it has) before routing.
|
|
29739
30115
|
*/
|
|
29740
30116
|
handleMessage(data) {
|
|
30117
|
+
let parsed;
|
|
30118
|
+
try {
|
|
30119
|
+
parsed = JSON.parse(data);
|
|
30120
|
+
} catch (error) {
|
|
30121
|
+
logger.error("Failed to parse incoming message:", error);
|
|
30122
|
+
return;
|
|
30123
|
+
}
|
|
30124
|
+
this.incoming.receive(parsed, data);
|
|
30125
|
+
}
|
|
30126
|
+
/** Routes one parsed message — everything `handleMessage` did after parsing. */
|
|
30127
|
+
dispatchMessage(parsed) {
|
|
29741
30128
|
try {
|
|
29742
|
-
const parsed = JSON.parse(data);
|
|
29743
30129
|
const message = IncomingMessageSchema.parse(parsed);
|
|
29744
30130
|
logger.debug("Received message:", message.type);
|
|
29745
30131
|
switch (message.type) {
|
|
@@ -29929,8 +30315,19 @@ var SuperatomSDK = class {
|
|
|
29929
30315
|
* Send a message to the Superatom service.
|
|
29930
30316
|
* Returns true if the message was sent, false if the WebSocket is not connected.
|
|
29931
30317
|
* Does NOT throw on closed connections — callers can check the return value if needed.
|
|
30318
|
+
*
|
|
30319
|
+
* Large payloads and files go as parcels (see ./parcels): a file is any
|
|
30320
|
+
* Buffer / Uint8Array / ArrayBuffer inside the payload, with an optional
|
|
30321
|
+
* `name` and `contentType` beside it — e.g. `payload: { file: { name, bytes } }`.
|
|
30322
|
+
* Such a message goes out once its upload is done (true = accepted), and later
|
|
30323
|
+
* messages keep their order. A received file arrives as a `ParcelFile` handle
|
|
30324
|
+
* whose `download()` fetches the bytes.
|
|
29932
30325
|
*/
|
|
29933
30326
|
send(message) {
|
|
30327
|
+
return this.outgoing.send(message);
|
|
30328
|
+
}
|
|
30329
|
+
/** Puts one message on the socket, or in the reconnect outbox. `wire` is its JSON, when already made. */
|
|
30330
|
+
deliver(message, wire) {
|
|
29934
30331
|
if (!this.ws || !this.connected) {
|
|
29935
30332
|
logger.warn("WebSocket is not connected, queueing message for reconnect:", message.type);
|
|
29936
30333
|
this.queuePendingMessage(message);
|
|
@@ -29942,7 +30339,7 @@ var SuperatomSDK = class {
|
|
|
29942
30339
|
return false;
|
|
29943
30340
|
}
|
|
29944
30341
|
try {
|
|
29945
|
-
const payload = JSON.stringify(message);
|
|
30342
|
+
const payload = wire ?? JSON.stringify(message);
|
|
29946
30343
|
this.ws.send(payload);
|
|
29947
30344
|
return true;
|
|
29948
30345
|
} catch (error) {
|
|
@@ -30285,6 +30682,8 @@ export {
|
|
|
30285
30682
|
MainAgent,
|
|
30286
30683
|
MissingRequestContextError,
|
|
30287
30684
|
NUMERIC_KEYS,
|
|
30685
|
+
PARCEL_THRESHOLD_BYTES,
|
|
30686
|
+
ParcelFile,
|
|
30288
30687
|
STORAGE_CONFIG,
|
|
30289
30688
|
ScriptMatcher,
|
|
30290
30689
|
ScriptStore,
|