netra-sdk 1.1.0 → 1.2.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 +146 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -3
- package/dist/index.d.ts +35 -3
- package/dist/index.js +146 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -24997,7 +24997,130 @@ async function uninstrumentAll() {
|
|
|
24997
24997
|
Logger.debug("Failed to uninstrument undici:", e);
|
|
24998
24998
|
}
|
|
24999
24999
|
}
|
|
25000
|
+
|
|
25001
|
+
// src/simulation/task.ts
|
|
25002
|
+
var BaseTask = class {
|
|
25003
|
+
};
|
|
25004
|
+
|
|
25005
|
+
// src/simulation/utils.ts
|
|
25000
25006
|
var LOG_PREFIX = "netra.simulation";
|
|
25007
|
+
var DEFAULT_FILE_DOWNLOAD_TIMEOUT_S = 30;
|
|
25008
|
+
var MAX_FILE_DOWNLOAD_WORKERS = 8;
|
|
25009
|
+
var _cachedFileDownloadTimeoutMs = null;
|
|
25010
|
+
function validateSimulationInputs(datasetId, task2) {
|
|
25011
|
+
if (!datasetId) {
|
|
25012
|
+
Logger.error(`${LOG_PREFIX}: dataset_id is required`);
|
|
25013
|
+
return false;
|
|
25014
|
+
}
|
|
25015
|
+
if (!(task2 instanceof BaseTask)) {
|
|
25016
|
+
Logger.error(`${LOG_PREFIX}: task must be a BaseTask instance`);
|
|
25017
|
+
return false;
|
|
25018
|
+
}
|
|
25019
|
+
return true;
|
|
25020
|
+
}
|
|
25021
|
+
function _getFileDownloadTimeout() {
|
|
25022
|
+
if (_cachedFileDownloadTimeoutMs !== null) {
|
|
25023
|
+
return _cachedFileDownloadTimeoutMs;
|
|
25024
|
+
}
|
|
25025
|
+
const envVal = process.env.NETRA_SIMULATION_FILE_DOWNLOAD_TIMEOUT;
|
|
25026
|
+
if (envVal) {
|
|
25027
|
+
const parsed = parseFloat(envVal);
|
|
25028
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
25029
|
+
_cachedFileDownloadTimeoutMs = parsed * 1e3;
|
|
25030
|
+
return _cachedFileDownloadTimeoutMs;
|
|
25031
|
+
}
|
|
25032
|
+
Logger.warn(
|
|
25033
|
+
`${LOG_PREFIX}: Invalid file download timeout '${envVal}', using default ${DEFAULT_FILE_DOWNLOAD_TIMEOUT_S}s`
|
|
25034
|
+
);
|
|
25035
|
+
}
|
|
25036
|
+
_cachedFileDownloadTimeoutMs = DEFAULT_FILE_DOWNLOAD_TIMEOUT_S * 1e3;
|
|
25037
|
+
return _cachedFileDownloadTimeoutMs;
|
|
25038
|
+
}
|
|
25039
|
+
async function _downloadSingleFile(fileData, timeoutMs, signal) {
|
|
25040
|
+
try {
|
|
25041
|
+
const response = await axios__default.default.get(fileData.downloadUrl, {
|
|
25042
|
+
responseType: "arraybuffer",
|
|
25043
|
+
timeout: timeoutMs,
|
|
25044
|
+
signal
|
|
25045
|
+
});
|
|
25046
|
+
const encoded = Buffer.from(response.data).toString("base64");
|
|
25047
|
+
return {
|
|
25048
|
+
fileName: fileData.fileName,
|
|
25049
|
+
contentType: fileData.contentType,
|
|
25050
|
+
description: fileData.description,
|
|
25051
|
+
data: encoded
|
|
25052
|
+
};
|
|
25053
|
+
} catch (error) {
|
|
25054
|
+
if (axios__default.default.isCancel(error)) {
|
|
25055
|
+
throw new Error(
|
|
25056
|
+
`Download of '${fileData.fileName}' was cancelled`
|
|
25057
|
+
);
|
|
25058
|
+
}
|
|
25059
|
+
const status = axios__default.default.isAxiosError(error) ? error.response?.status : void 0;
|
|
25060
|
+
const reason = status ? `HTTP ${status}` : error instanceof Error ? error.message : String(error);
|
|
25061
|
+
Logger.error(
|
|
25062
|
+
`${LOG_PREFIX}: Failed to download file '${fileData.fileName}': ${reason}`
|
|
25063
|
+
);
|
|
25064
|
+
throw new Error(
|
|
25065
|
+
`Failed to download file '${fileData.fileName}': ${reason}`
|
|
25066
|
+
);
|
|
25067
|
+
}
|
|
25068
|
+
}
|
|
25069
|
+
function parseFiles(rawFiles) {
|
|
25070
|
+
if (!rawFiles) {
|
|
25071
|
+
return [];
|
|
25072
|
+
}
|
|
25073
|
+
const parsed = [];
|
|
25074
|
+
for (const entry of rawFiles) {
|
|
25075
|
+
const fileName = entry.fileName || "";
|
|
25076
|
+
const downloadUrl = entry.downloadUrl || "";
|
|
25077
|
+
if (!fileName || !downloadUrl) {
|
|
25078
|
+
Logger.warn(
|
|
25079
|
+
`${LOG_PREFIX}: Skipping malformed file attachment (missing fileName or downloadUrl)`
|
|
25080
|
+
);
|
|
25081
|
+
continue;
|
|
25082
|
+
}
|
|
25083
|
+
parsed.push({
|
|
25084
|
+
fileName,
|
|
25085
|
+
contentType: entry.contentType || "",
|
|
25086
|
+
description: entry.description || void 0,
|
|
25087
|
+
downloadUrl
|
|
25088
|
+
});
|
|
25089
|
+
}
|
|
25090
|
+
return parsed;
|
|
25091
|
+
}
|
|
25092
|
+
async function processFiles(files) {
|
|
25093
|
+
if (!files || files.length === 0) {
|
|
25094
|
+
return null;
|
|
25095
|
+
}
|
|
25096
|
+
const timeoutMs = _getFileDownloadTimeout();
|
|
25097
|
+
const limit = pLimit__default.default(MAX_FILE_DOWNLOAD_WORKERS);
|
|
25098
|
+
const controller = new AbortController();
|
|
25099
|
+
const downloadPromises = files.map(
|
|
25100
|
+
(file) => limit(() => _downloadSingleFile(file, timeoutMs, controller.signal))
|
|
25101
|
+
);
|
|
25102
|
+
try {
|
|
25103
|
+
return await Promise.all(downloadPromises);
|
|
25104
|
+
} catch (error) {
|
|
25105
|
+
controller.abort();
|
|
25106
|
+
limit.clearQueue();
|
|
25107
|
+
throw error;
|
|
25108
|
+
}
|
|
25109
|
+
}
|
|
25110
|
+
async function executeTask2(task2, message, sessionId, rawFiles) {
|
|
25111
|
+
const processedFiles = rawFiles && rawFiles.length > 0 ? await processFiles(rawFiles) : null;
|
|
25112
|
+
const result = task2.run(message, sessionId, processedFiles);
|
|
25113
|
+
const resolvedResult = result instanceof Promise ? await result : result;
|
|
25114
|
+
if (typeof resolvedResult === "object" && resolvedResult !== null && "message" in resolvedResult && "sessionId" in resolvedResult) {
|
|
25115
|
+
return [resolvedResult.message, resolvedResult.sessionId];
|
|
25116
|
+
}
|
|
25117
|
+
throw new Error(
|
|
25118
|
+
`Task must return TaskResult, got ${typeof resolvedResult}`
|
|
25119
|
+
);
|
|
25120
|
+
}
|
|
25121
|
+
|
|
25122
|
+
// src/simulation/client.ts
|
|
25123
|
+
var LOG_PREFIX2 = "netra.simulation";
|
|
25001
25124
|
var DEFAULT_TIMEOUT = 1e4;
|
|
25002
25125
|
var SimulationHttpClient = class {
|
|
25003
25126
|
constructor(config2) {
|
|
@@ -25010,7 +25133,7 @@ var SimulationHttpClient = class {
|
|
|
25010
25133
|
_createClient(config2) {
|
|
25011
25134
|
const endpoint = (config2.otlpEndpoint || "").trim();
|
|
25012
25135
|
if (!endpoint) {
|
|
25013
|
-
Logger.error(`${
|
|
25136
|
+
Logger.error(`${LOG_PREFIX2}: NETRA_OTLP_ENDPOINT is required`);
|
|
25014
25137
|
return null;
|
|
25015
25138
|
}
|
|
25016
25139
|
const baseURL = this._resolveBaseUrl(endpoint);
|
|
@@ -25032,7 +25155,7 @@ var SimulationHttpClient = class {
|
|
|
25032
25155
|
);
|
|
25033
25156
|
return instance;
|
|
25034
25157
|
} catch (error) {
|
|
25035
|
-
Logger.error(`${
|
|
25158
|
+
Logger.error(`${LOG_PREFIX2}: Failed to create HTTP client:`, error);
|
|
25036
25159
|
return null;
|
|
25037
25160
|
}
|
|
25038
25161
|
}
|
|
@@ -25067,7 +25190,7 @@ var SimulationHttpClient = class {
|
|
|
25067
25190
|
const timeout = parseFloat(timeoutStr);
|
|
25068
25191
|
if (isNaN(timeout)) {
|
|
25069
25192
|
Logger.warn(
|
|
25070
|
-
`${
|
|
25193
|
+
`${LOG_PREFIX2}: Invalid timeout '${timeoutStr}', using default ${DEFAULT_TIMEOUT}ms`
|
|
25071
25194
|
);
|
|
25072
25195
|
return DEFAULT_TIMEOUT;
|
|
25073
25196
|
}
|
|
@@ -25078,7 +25201,7 @@ var SimulationHttpClient = class {
|
|
|
25078
25201
|
*/
|
|
25079
25202
|
async createRun(name, datasetId, context17) {
|
|
25080
25203
|
if (!this.client) {
|
|
25081
|
-
Logger.error(`${
|
|
25204
|
+
Logger.error(`${LOG_PREFIX2}: Client not initialized`);
|
|
25082
25205
|
return null;
|
|
25083
25206
|
}
|
|
25084
25207
|
try {
|
|
@@ -25093,7 +25216,7 @@ var SimulationHttpClient = class {
|
|
|
25093
25216
|
const responseData = data.data || {};
|
|
25094
25217
|
const userMessages = responseData.userMessages || [];
|
|
25095
25218
|
if (userMessages.length === 0) {
|
|
25096
|
-
Logger.warn(`${
|
|
25219
|
+
Logger.warn(`${LOG_PREFIX2}: No user messages returned from create_run`);
|
|
25097
25220
|
return null;
|
|
25098
25221
|
}
|
|
25099
25222
|
const runId = responseData.id || "";
|
|
@@ -25101,7 +25224,8 @@ var SimulationHttpClient = class {
|
|
|
25101
25224
|
(msg) => ({
|
|
25102
25225
|
runItemId: msg.testRunItemId || "",
|
|
25103
25226
|
message: msg.userMessage || "",
|
|
25104
|
-
turnId: msg.turnId || ""
|
|
25227
|
+
turnId: msg.turnId || "",
|
|
25228
|
+
files: parseFiles(msg.attachments)
|
|
25105
25229
|
})
|
|
25106
25230
|
);
|
|
25107
25231
|
return {
|
|
@@ -25110,7 +25234,7 @@ var SimulationHttpClient = class {
|
|
|
25110
25234
|
};
|
|
25111
25235
|
} catch (error) {
|
|
25112
25236
|
const errorMsg = this._extractErrorMessage(error);
|
|
25113
|
-
Logger.error(`${
|
|
25237
|
+
Logger.error(`${LOG_PREFIX2}: Failed to create simulation run:`, errorMsg);
|
|
25114
25238
|
return null;
|
|
25115
25239
|
}
|
|
25116
25240
|
}
|
|
@@ -25119,7 +25243,7 @@ var SimulationHttpClient = class {
|
|
|
25119
25243
|
*/
|
|
25120
25244
|
async triggerConversation(message, turnId, sessionId, traceId) {
|
|
25121
25245
|
if (!this.client) {
|
|
25122
|
-
Logger.error(`${
|
|
25246
|
+
Logger.error(`${LOG_PREFIX2}: Client not initialized`);
|
|
25123
25247
|
return null;
|
|
25124
25248
|
}
|
|
25125
25249
|
try {
|
|
@@ -25142,7 +25266,7 @@ var SimulationHttpClient = class {
|
|
|
25142
25266
|
}
|
|
25143
25267
|
const userMessages = responseData.userMessages || [];
|
|
25144
25268
|
if (userMessages.length === 0) {
|
|
25145
|
-
Logger.warn(`${
|
|
25269
|
+
Logger.warn(`${LOG_PREFIX2}: No user messages in continue response`);
|
|
25146
25270
|
return null;
|
|
25147
25271
|
}
|
|
25148
25272
|
const nextMsg = userMessages[0];
|
|
@@ -25150,11 +25274,12 @@ var SimulationHttpClient = class {
|
|
|
25150
25274
|
decision,
|
|
25151
25275
|
nextTurnId: nextMsg.turnId || "",
|
|
25152
25276
|
nextUserMessage: nextMsg.userMessage || "",
|
|
25153
|
-
nextRunItemId: nextMsg.testRunItemId || ""
|
|
25277
|
+
nextRunItemId: nextMsg.testRunItemId || "",
|
|
25278
|
+
nextFiles: parseFiles(nextMsg.attachments)
|
|
25154
25279
|
};
|
|
25155
25280
|
} catch (error) {
|
|
25156
25281
|
const errorMsg = this._extractErrorMessage(error);
|
|
25157
|
-
Logger.error(`${
|
|
25282
|
+
Logger.error(`${LOG_PREFIX2}: Failed to trigger conversation:`, errorMsg);
|
|
25158
25283
|
return null;
|
|
25159
25284
|
}
|
|
25160
25285
|
}
|
|
@@ -25163,17 +25288,17 @@ var SimulationHttpClient = class {
|
|
|
25163
25288
|
*/
|
|
25164
25289
|
async reportFailure(runId, runItemId, error) {
|
|
25165
25290
|
if (!this.client) {
|
|
25166
|
-
Logger.error(`${
|
|
25291
|
+
Logger.error(`${LOG_PREFIX2}: Client not initialized`);
|
|
25167
25292
|
return;
|
|
25168
25293
|
}
|
|
25169
25294
|
try {
|
|
25170
25295
|
const url = `/evaluations/run/${runId}/item/${runItemId}/status`;
|
|
25171
25296
|
const payload = { status: "failed", failureReason: error };
|
|
25172
25297
|
await this.client.patch(url, payload);
|
|
25173
|
-
Logger.info(`${
|
|
25298
|
+
Logger.info(`${LOG_PREFIX2}: Reported failure - ${error}`);
|
|
25174
25299
|
} catch (err) {
|
|
25175
25300
|
const errorMsg = this._extractErrorMessage(err);
|
|
25176
|
-
Logger.error(`${
|
|
25301
|
+
Logger.error(`${LOG_PREFIX2}: Failed to report failure:`, errorMsg);
|
|
25177
25302
|
}
|
|
25178
25303
|
}
|
|
25179
25304
|
/**
|
|
@@ -25182,7 +25307,7 @@ var SimulationHttpClient = class {
|
|
|
25182
25307
|
async postRunStatus(runId, status) {
|
|
25183
25308
|
if (!this.client) {
|
|
25184
25309
|
Logger.error(
|
|
25185
|
-
`${
|
|
25310
|
+
`${LOG_PREFIX2}: Client not initialized; cannot post run status`
|
|
25186
25311
|
);
|
|
25187
25312
|
return { success: false };
|
|
25188
25313
|
}
|
|
@@ -25192,14 +25317,14 @@ var SimulationHttpClient = class {
|
|
|
25192
25317
|
const response = await this.client.post(url, payload);
|
|
25193
25318
|
const data = response.data;
|
|
25194
25319
|
if (data && typeof data === "object" && "data" in data) {
|
|
25195
|
-
Logger.info(`${
|
|
25320
|
+
Logger.info(`${LOG_PREFIX2}: Completed test run successfully`);
|
|
25196
25321
|
return data.data || {};
|
|
25197
25322
|
}
|
|
25198
25323
|
return data;
|
|
25199
25324
|
} catch (error) {
|
|
25200
25325
|
const errorMsg = this._extractErrorMessage(error);
|
|
25201
25326
|
Logger.error(
|
|
25202
|
-
`${
|
|
25327
|
+
`${LOG_PREFIX2}: Failed to post run status for run '${runId}':`,
|
|
25203
25328
|
errorMsg
|
|
25204
25329
|
);
|
|
25205
25330
|
return { success: false };
|
|
@@ -25223,34 +25348,6 @@ var SimulationHttpClient = class {
|
|
|
25223
25348
|
}
|
|
25224
25349
|
};
|
|
25225
25350
|
|
|
25226
|
-
// src/simulation/task.ts
|
|
25227
|
-
var BaseTask = class {
|
|
25228
|
-
};
|
|
25229
|
-
|
|
25230
|
-
// src/simulation/utils.ts
|
|
25231
|
-
var LOG_PREFIX2 = "netra.simulation";
|
|
25232
|
-
function validateSimulationInputs(datasetId, task2) {
|
|
25233
|
-
if (!datasetId) {
|
|
25234
|
-
Logger.error(`${LOG_PREFIX2}: dataset_id is required`);
|
|
25235
|
-
return false;
|
|
25236
|
-
}
|
|
25237
|
-
if (!(task2 instanceof BaseTask)) {
|
|
25238
|
-
Logger.error(`${LOG_PREFIX2}: task must be a BaseTask instance`);
|
|
25239
|
-
return false;
|
|
25240
|
-
}
|
|
25241
|
-
return true;
|
|
25242
|
-
}
|
|
25243
|
-
async function executeTask2(task2, message, sessionId) {
|
|
25244
|
-
const result = task2.run(message, sessionId);
|
|
25245
|
-
const resolvedResult = result instanceof Promise ? await result : result;
|
|
25246
|
-
if (typeof resolvedResult === "object" && resolvedResult !== null && "message" in resolvedResult && "sessionId" in resolvedResult) {
|
|
25247
|
-
return [resolvedResult.message, resolvedResult.sessionId];
|
|
25248
|
-
}
|
|
25249
|
-
throw new Error(
|
|
25250
|
-
`Task must return TaskResult, got ${typeof resolvedResult}`
|
|
25251
|
-
);
|
|
25252
|
-
}
|
|
25253
|
-
|
|
25254
25351
|
// src/simulation/api.ts
|
|
25255
25352
|
var LOG_PREFIX3 = "netra.simulation";
|
|
25256
25353
|
var SPAN_NAME = "Netra.Simulation.TestRun";
|
|
@@ -25384,17 +25481,18 @@ var Simulation = class {
|
|
|
25384
25481
|
* Execute a multi-turn conversation for a single simulation item.
|
|
25385
25482
|
*/
|
|
25386
25483
|
async _executeConversation(runId, runItem, task2) {
|
|
25387
|
-
const { runItemId, message: initialMessage, turnId: initialTurnId } = runItem;
|
|
25484
|
+
const { runItemId, message: initialMessage, turnId: initialTurnId, files: initialFiles } = runItem;
|
|
25388
25485
|
let message = initialMessage;
|
|
25389
25486
|
let turnId = initialTurnId;
|
|
25390
25487
|
let sessionId = null;
|
|
25488
|
+
let rawFiles = initialFiles ?? [];
|
|
25391
25489
|
while (true) {
|
|
25392
25490
|
const span2 = new SpanWrapper(SPAN_NAME, {}, LOG_PREFIX3);
|
|
25393
25491
|
span2.start();
|
|
25394
25492
|
try {
|
|
25395
25493
|
const traceId = span2.getCurrentSpan()?.spanContext().traceId ?? "";
|
|
25396
25494
|
const [responseMessage, taskSessionId] = await span2.withActive(
|
|
25397
|
-
() => executeTask2(task2, message, sessionId)
|
|
25495
|
+
() => executeTask2(task2, message, sessionId, rawFiles)
|
|
25398
25496
|
);
|
|
25399
25497
|
if (taskSessionId) {
|
|
25400
25498
|
sessionId = taskSessionId;
|
|
@@ -25428,6 +25526,7 @@ var Simulation = class {
|
|
|
25428
25526
|
}
|
|
25429
25527
|
message = response.nextUserMessage;
|
|
25430
25528
|
turnId = response.nextTurnId;
|
|
25529
|
+
rawFiles = response.nextFiles || [];
|
|
25431
25530
|
} catch (error) {
|
|
25432
25531
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
25433
25532
|
Logger.error(
|