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 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(`${LOG_PREFIX}: NETRA_OTLP_ENDPOINT is required`);
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(`${LOG_PREFIX}: Failed to create HTTP client:`, 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
- `${LOG_PREFIX}: Invalid timeout '${timeoutStr}', using default ${DEFAULT_TIMEOUT}ms`
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(`${LOG_PREFIX}: Client not initialized`);
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(`${LOG_PREFIX}: No user messages returned from create_run`);
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(`${LOG_PREFIX}: Failed to create simulation run:`, errorMsg);
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(`${LOG_PREFIX}: Client not initialized`);
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(`${LOG_PREFIX}: No user messages in continue response`);
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(`${LOG_PREFIX}: Failed to trigger conversation:`, errorMsg);
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(`${LOG_PREFIX}: Client not initialized`);
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(`${LOG_PREFIX}: Reported failure - ${error}`);
25298
+ Logger.info(`${LOG_PREFIX2}: Reported failure - ${error}`);
25174
25299
  } catch (err) {
25175
25300
  const errorMsg = this._extractErrorMessage(err);
25176
- Logger.error(`${LOG_PREFIX}: Failed to report failure:`, errorMsg);
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
- `${LOG_PREFIX}: Client not initialized; cannot post run status`
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(`${LOG_PREFIX}: Completed test run successfully`);
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
- `${LOG_PREFIX}: Failed to post run status for run '${runId}':`,
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(