@cdot65/prisma-airs-sdk 0.13.0 → 0.13.2

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
@@ -589,11 +589,11 @@ var MAX_REPORT_ID_STR_LENGTH = 40;
589
589
  var MAX_AI_PROFILE_NAME_LENGTH = 100;
590
590
  var MAX_NUMBER_OF_SCAN_IDS = 5;
591
591
  var MAX_NUMBER_OF_REPORT_IDS = 5;
592
- var MAX_NUMBER_OF_BATCH_SCAN_OBJECTS = 5;
592
+ var MAX_NUMBER_OF_BATCH_SCAN_OBJECTS = 20;
593
593
  var MAX_CONNECTION_POOL_SIZE = 100;
594
594
  var MAX_NUMBER_OF_RETRIES = 5;
595
595
  var HTTP_FORCE_RETRY_STATUS_CODES = [500, 502, 503, 504];
596
- var SDK_VERSION = "0.13.0";
596
+ var SDK_VERSION = "0.13.2";
597
597
  var USER_AGENT = `PAN-AIRS/${SDK_VERSION}-typescript-sdk`;
598
598
  var DEFAULT_MGMT_ENDPOINT = "https://api.sase.paloaltonetworks.com/aisec";
599
599
  var DEFAULT_TOKEN_ENDPOINT = "https://auth.apps.paloaltonetworks.com/oauth2/access_token";
@@ -693,11 +693,15 @@ var AISecSDKException = class _AISecSDKException extends Error {
693
693
  /**
694
694
  * @param message - Human-readable error description.
695
695
  * @param errorType - Classification of the error.
696
+ * @param metadata - Optional transport failure metadata.
696
697
  */
697
- constructor(message, errorType) {
698
+ constructor(message, errorType, metadata = {}) {
698
699
  super(errorType ? `${errorType}:${message}` : message);
699
700
  this.name = "AISecSDKException";
700
701
  this.errorType = errorType;
702
+ if (metadata.failureKind !== void 0) this.failureKind = metadata.failureKind;
703
+ if (metadata.statusCode !== void 0) this.statusCode = metadata.statusCode;
704
+ if (metadata.retryAfterMs !== void 0) this.retryAfterMs = metadata.retryAfterMs;
701
705
  if (Error.captureStackTrace) {
702
706
  Error.captureStackTrace(this, _AISecSDKException);
703
707
  }
@@ -839,6 +843,59 @@ function extractErrorMessage(body, status) {
839
843
  return body ? `API error ${status}: ${body}` : `API error ${status}`;
840
844
  }
841
845
  }
846
+ function parseRetryAfterHeader(value) {
847
+ if (value === null) return void 0;
848
+ const normalized = value.trim();
849
+ if (/^\d+$/.test(normalized)) {
850
+ const milliseconds = Number(normalized) * 1e3;
851
+ return Number.isFinite(milliseconds) ? milliseconds : void 0;
852
+ }
853
+ const weekday = "(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)";
854
+ const longWeekday = "(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)";
855
+ const month = "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)";
856
+ const httpDatePatterns = [
857
+ new RegExp(`^${weekday}, \\d{2} ${month} \\d{4} \\d{2}:\\d{2}:\\d{2} GMT$`),
858
+ new RegExp(`^${longWeekday}, \\d{2}-${month}-\\d{2} \\d{2}:\\d{2}:\\d{2} GMT$`),
859
+ new RegExp(`^${weekday} ${month} [ \\d]\\d \\d{2}:\\d{2}:\\d{2} \\d{4}$`)
860
+ ];
861
+ if (!httpDatePatterns.some((pattern) => pattern.test(normalized))) return void 0;
862
+ const timestamp = Date.parse(normalized);
863
+ if (!Number.isFinite(timestamp)) return void 0;
864
+ return Math.max(0, timestamp - Date.now());
865
+ }
866
+ function parseRetryAfterBody(body) {
867
+ try {
868
+ const parsed = JSON.parse(body);
869
+ const interval = parsed.retry_after?.interval;
870
+ const unit = parsed.retry_after?.unit;
871
+ if (typeof interval !== "number" || !Number.isFinite(interval) || interval < 0)
872
+ return void 0;
873
+ if (typeof unit !== "string") return void 0;
874
+ const unitMultipliers = {
875
+ ms: 1,
876
+ msec: 1,
877
+ msecs: 1,
878
+ millisecond: 1,
879
+ milliseconds: 1,
880
+ s: 1e3,
881
+ sec: 1e3,
882
+ secs: 1e3,
883
+ second: 1e3,
884
+ seconds: 1e3,
885
+ m: 6e4,
886
+ min: 6e4,
887
+ mins: 6e4,
888
+ minute: 6e4,
889
+ minutes: 6e4
890
+ };
891
+ const multiplier = unitMultipliers[unit.toLowerCase()];
892
+ if (multiplier === void 0) return void 0;
893
+ const milliseconds = interval * multiplier;
894
+ return Number.isFinite(milliseconds) ? milliseconds : void 0;
895
+ } catch {
896
+ return void 0;
897
+ }
898
+ }
842
899
  async function executeWithRetry(opts) {
843
900
  const { maxRetries, execute, onRetryableFailure } = opts;
844
901
  let lastError;
@@ -855,7 +912,8 @@ async function executeWithRetry(opts) {
855
912
  }
856
913
  throw new AISecSDKException(
857
914
  lastError.message ?? "Network error",
858
- "AISEC_CLIENT_SIDE_ERROR" /* CLIENT_SIDE_ERROR */
915
+ "AISEC_CLIENT_SIDE_ERROR" /* CLIENT_SIDE_ERROR */,
916
+ { failureKind: "network" }
859
917
  );
860
918
  }
861
919
  if (response.ok) return response;
@@ -872,11 +930,18 @@ async function executeWithRetry(opts) {
872
930
  }
873
931
  const errorText = await response.text();
874
932
  const errorMessage = extractErrorMessage(errorText, response.status);
875
- throw new AISecSDKException(errorMessage, classifyErrorType(response.status));
933
+ const retryAfterHeader = response.headers?.get?.("Retry-After") ?? null;
934
+ const headerRetryAfterMs = parseRetryAfterHeader(retryAfterHeader);
935
+ throw new AISecSDKException(errorMessage, classifyErrorType(response.status), {
936
+ failureKind: "http",
937
+ statusCode: response.status,
938
+ retryAfterMs: headerRetryAfterMs ?? parseRetryAfterBody(errorText)
939
+ });
876
940
  }
877
941
  throw new AISecSDKException(
878
942
  lastError?.message ?? "Max retries exceeded",
879
- "AISEC_CLIENT_SIDE_ERROR" /* CLIENT_SIDE_ERROR */
943
+ "AISEC_CLIENT_SIDE_ERROR" /* CLIENT_SIDE_ERROR */,
944
+ lastError ? { failureKind: "network" } : {}
880
945
  );
881
946
  }
882
947
 
@@ -1009,6 +1074,7 @@ var PromptDetectedSchema = import_zod.z.object({
1009
1074
  injection: import_zod.z.boolean().optional(),
1010
1075
  toxic_content: import_zod.z.boolean().optional(),
1011
1076
  malicious_code: import_zod.z.boolean().optional(),
1077
+ source_code: import_zod.z.boolean().optional(),
1012
1078
  agent: import_zod.z.boolean().optional(),
1013
1079
  topic_violation: import_zod.z.boolean().optional()
1014
1080
  }).passthrough();
@@ -1313,6 +1379,16 @@ var ThreatScanReportSchema = import_zod14.z.object({
1313
1379
  }).passthrough();
1314
1380
 
1315
1381
  // src/scan/scanner.ts
1382
+ function resolveNumRetries(opts) {
1383
+ const numRetries = opts.numRetries ?? globalConfiguration.numRetries;
1384
+ if (!Number.isFinite(numRetries) || !Number.isInteger(numRetries) || numRetries < 0 || numRetries > MAX_NUMBER_OF_RETRIES) {
1385
+ throw new AISecSDKException(
1386
+ `numRetries must be a finite integer between 0 and ${MAX_NUMBER_OF_RETRIES}`,
1387
+ "AISEC_USER_REQUEST_PAYLOAD_ERROR" /* USER_REQUEST_PAYLOAD_ERROR */
1388
+ );
1389
+ }
1390
+ return numRetries;
1391
+ }
1316
1392
  var Scanner = class {
1317
1393
  /**
1318
1394
  * @internal
@@ -1333,7 +1409,7 @@ var Scanner = class {
1333
1409
  * Perform a synchronous content scan.
1334
1410
  * @param aiProfile - AI security profile to scan against.
1335
1411
  * @param content - Content to scan.
1336
- * @param opts - Optional transaction/session IDs and metadata.
1412
+ * @param opts - Optional transaction/session IDs, metadata, and per-call retry override.
1337
1413
  * @returns Scan response with verdict, action, and detection details.
1338
1414
  * @example
1339
1415
  * ```ts
@@ -1352,6 +1428,7 @@ var Scanner = class {
1352
1428
  * ```
1353
1429
  */
1354
1430
  async syncScan(aiProfile, content, opts = {}) {
1431
+ const numRetries = resolveNumRetries(opts);
1355
1432
  if (opts.trId && opts.trId.length > MAX_TRANSACTION_ID_STR_LENGTH) {
1356
1433
  throw new AISecSDKException(
1357
1434
  `trId exceeds max length of ${MAX_TRANSACTION_ID_STR_LENGTH}`,
@@ -1378,33 +1455,52 @@ var Scanner = class {
1378
1455
  body,
1379
1456
  responseSchema: ScanResponseSchema,
1380
1457
  auth: this.buildAuth(),
1381
- numRetries: globalConfiguration.numRetries
1458
+ numRetries
1382
1459
  });
1383
1460
  }
1384
1461
  /**
1385
- * Submit content for asynchronous scanning.
1386
- * @param scanObjects - Array of scan objects (1–5 items).
1387
- * @returns Response containing scan IDs for later querying.
1462
+ * Submit one batch of content for asynchronous scanning.
1463
+ *
1464
+ * The call accepts 1–20 request objects and returns one batch receipt. A batch `scan_id` can fan
1465
+ * out to several unordered result rows. Correlate each row by `(scan_id, req_id)`, never array
1466
+ * position or `scan_id` alone. The SDK preserves the server's row order and cardinality.
1467
+ *
1468
+ * Setting `numRetries: 0` guarantees one SDK fetch attempt, but cannot guarantee exactly-once
1469
+ * server submission after an ambiguous network or 5xx failure.
1470
+ * @param scanObjects - Array of scan objects (1–20 items), each with its own `req_id`.
1471
+ * @param opts - Optional per-call retry override.
1472
+ * @returns One batch receipt containing the shared scan ID for later querying.
1388
1473
  * @example
1389
1474
  * ```ts
1390
1475
  * import { init, Scanner } from '@cdot65/prisma-airs-sdk';
1391
1476
  * init();
1392
1477
  * const scanner = new Scanner();
1393
1478
  *
1394
- * const result = await scanner.asyncScan([
1395
- * {
1396
- * req_id: 1,
1397
- * scan_req: {
1398
- * ai_profile: { profile_name: 'my-profile' },
1399
- * contents: [{ prompt: 'Tell me about machine learning.' }],
1479
+ * const receipt = await scanner.asyncScan(
1480
+ * [
1481
+ * {
1482
+ * req_id: 1,
1483
+ * scan_req: {
1484
+ * ai_profile: { profile_name: 'my-profile' },
1485
+ * contents: [{ prompt: 'Tell me about machine learning.' }],
1486
+ * },
1400
1487
  * },
1401
- * },
1402
- * ]);
1403
- * // result =>
1488
+ * {
1489
+ * req_id: 2,
1490
+ * scan_req: {
1491
+ * ai_profile: { profile_name: 'my-profile' },
1492
+ * contents: [{ prompt: 'What are neural networks?' }],
1493
+ * },
1494
+ * },
1495
+ * ],
1496
+ * { numRetries: 0 },
1497
+ * );
1498
+ * // receipt =>
1404
1499
  * // { received: '2024-01-01T00:00:00Z', scan_id: '550e...' }
1405
1500
  * ```
1406
1501
  */
1407
- async asyncScan(scanObjects) {
1502
+ async asyncScan(scanObjects, opts = {}) {
1503
+ const numRetries = resolveNumRetries(opts);
1408
1504
  if (scanObjects.length < 1) {
1409
1505
  throw new AISecSDKException(
1410
1506
  "At least 1 scan object is required",
@@ -1424,13 +1520,17 @@ var Scanner = class {
1424
1520
  body: scanObjects,
1425
1521
  responseSchema: AsyncScanResponseSchema,
1426
1522
  auth: this.buildAuth(),
1427
- numRetries: globalConfiguration.numRetries
1523
+ numRetries
1428
1524
  });
1429
1525
  }
1430
1526
  /**
1431
1527
  * Query scan results by scan IDs.
1528
+ *
1529
+ * One scan ID can return several unordered rows. Correlate them using `(scan_id, req_id)`. The
1530
+ * SDK does not sort, deduplicate, or collapse the response.
1432
1531
  * @param scanIds - Array of scan UUIDs (1–5 items).
1433
- * @returns Array of scan results with status and response data.
1532
+ * @param opts - Optional per-call retry override.
1533
+ * @returns Every scan-result row in server order, with status and response data.
1434
1534
  * @example
1435
1535
  * ```ts
1436
1536
  * import { init, Scanner } from '@cdot65/prisma-airs-sdk';
@@ -1440,12 +1540,14 @@ var Scanner = class {
1440
1540
  * const results = await scanner.queryByScanIds([
1441
1541
  * '550e8400-e29b-41d4-a716-446655440000',
1442
1542
  * ]);
1443
- * // results =>
1444
- * // [{ scan_id: '550e8400-e29b-41d4-a716-446655440000', status: 'complete',
1445
- * // result: { category: 'benign', action: 'allow', ... } }]
1543
+ * // A single scan ID may produce multiple rows, for example req_id 2 then req_id 1.
1544
+ * for (const row of results) {
1545
+ * console.log(`${row.scan_id}:${row.req_id}`, row.status, row.result?.action);
1546
+ * }
1446
1547
  * ```
1447
1548
  */
1448
- async queryByScanIds(scanIds) {
1549
+ async queryByScanIds(scanIds, opts = {}) {
1550
+ const numRetries = resolveNumRetries(opts);
1449
1551
  if (scanIds.length < 1) {
1450
1552
  throw new AISecSDKException(
1451
1553
  "At least 1 scan_id is required",
@@ -1470,13 +1572,17 @@ var Scanner = class {
1470
1572
  params: { scan_ids: scanIds.join(",") },
1471
1573
  responseSchema: import_zod15.z.array(ScanIdResultSchema),
1472
1574
  auth: this.buildAuth(),
1473
- numRetries: globalConfiguration.numRetries
1575
+ numRetries
1474
1576
  });
1475
1577
  }
1476
1578
  /**
1477
1579
  * Query detailed threat reports by report IDs.
1580
+ *
1581
+ * One report ID can return several rows. Correlate them using `(report_id, req_id)`. The SDK
1582
+ * preserves server order and cardinality without sorting or deduplicating.
1478
1583
  * @param reportIds - Array of report IDs (1–5 items).
1479
- * @returns Array of threat scan reports with detection details.
1584
+ * @param opts - Optional per-call retry override.
1585
+ * @returns Every report row in server order, with detection details.
1480
1586
  * @example
1481
1587
  * ```ts
1482
1588
  * import { init, Scanner } from '@cdot65/prisma-airs-sdk';
@@ -1484,12 +1590,13 @@ var Scanner = class {
1484
1590
  * const scanner = new Scanner();
1485
1591
  *
1486
1592
  * const reports = await scanner.queryByReportIds(['R000...']);
1487
- * // reports =>
1488
- * // [{ report_id: 'R000...', scan_id: '550e...',
1489
- * // detection_results: [{ detection_service: 'pi', verdict: 'benign', action: 'allow' }] }]
1593
+ * for (const report of reports) {
1594
+ * console.log(`${report.report_id}:${report.req_id}`, report.detection_results);
1595
+ * }
1490
1596
  * ```
1491
1597
  */
1492
- async queryByReportIds(reportIds) {
1598
+ async queryByReportIds(reportIds, opts = {}) {
1599
+ const numRetries = resolveNumRetries(opts);
1493
1600
  if (reportIds.length < 1) {
1494
1601
  throw new AISecSDKException(
1495
1602
  "At least 1 report_id is required",
@@ -1509,7 +1616,7 @@ var Scanner = class {
1509
1616
  params: { report_ids: reportIds.join(",") },
1510
1617
  responseSchema: import_zod15.z.array(ThreatScanReportSchema),
1511
1618
  auth: this.buildAuth(),
1512
- numRetries: globalConfiguration.numRetries
1619
+ numRetries
1513
1620
  });
1514
1621
  }
1515
1622
  };