@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.js CHANGED
@@ -25,11 +25,11 @@ var MAX_REPORT_ID_STR_LENGTH = 40;
25
25
  var MAX_AI_PROFILE_NAME_LENGTH = 100;
26
26
  var MAX_NUMBER_OF_SCAN_IDS = 5;
27
27
  var MAX_NUMBER_OF_REPORT_IDS = 5;
28
- var MAX_NUMBER_OF_BATCH_SCAN_OBJECTS = 5;
28
+ var MAX_NUMBER_OF_BATCH_SCAN_OBJECTS = 20;
29
29
  var MAX_CONNECTION_POOL_SIZE = 100;
30
30
  var MAX_NUMBER_OF_RETRIES = 5;
31
31
  var HTTP_FORCE_RETRY_STATUS_CODES = [500, 502, 503, 504];
32
- var SDK_VERSION = "0.13.0";
32
+ var SDK_VERSION = "0.13.2";
33
33
  var USER_AGENT = `PAN-AIRS/${SDK_VERSION}-typescript-sdk`;
34
34
  var DEFAULT_MGMT_ENDPOINT = "https://api.sase.paloaltonetworks.com/aisec";
35
35
  var DEFAULT_TOKEN_ENDPOINT = "https://auth.apps.paloaltonetworks.com/oauth2/access_token";
@@ -129,11 +129,15 @@ var AISecSDKException = class _AISecSDKException extends Error {
129
129
  /**
130
130
  * @param message - Human-readable error description.
131
131
  * @param errorType - Classification of the error.
132
+ * @param metadata - Optional transport failure metadata.
132
133
  */
133
- constructor(message, errorType) {
134
+ constructor(message, errorType, metadata = {}) {
134
135
  super(errorType ? `${errorType}:${message}` : message);
135
136
  this.name = "AISecSDKException";
136
137
  this.errorType = errorType;
138
+ if (metadata.failureKind !== void 0) this.failureKind = metadata.failureKind;
139
+ if (metadata.statusCode !== void 0) this.statusCode = metadata.statusCode;
140
+ if (metadata.retryAfterMs !== void 0) this.retryAfterMs = metadata.retryAfterMs;
137
141
  if (Error.captureStackTrace) {
138
142
  Error.captureStackTrace(this, _AISecSDKException);
139
143
  }
@@ -275,6 +279,59 @@ function extractErrorMessage(body, status) {
275
279
  return body ? `API error ${status}: ${body}` : `API error ${status}`;
276
280
  }
277
281
  }
282
+ function parseRetryAfterHeader(value) {
283
+ if (value === null) return void 0;
284
+ const normalized = value.trim();
285
+ if (/^\d+$/.test(normalized)) {
286
+ const milliseconds = Number(normalized) * 1e3;
287
+ return Number.isFinite(milliseconds) ? milliseconds : void 0;
288
+ }
289
+ const weekday = "(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)";
290
+ const longWeekday = "(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)";
291
+ const month = "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)";
292
+ const httpDatePatterns = [
293
+ new RegExp(`^${weekday}, \\d{2} ${month} \\d{4} \\d{2}:\\d{2}:\\d{2} GMT$`),
294
+ new RegExp(`^${longWeekday}, \\d{2}-${month}-\\d{2} \\d{2}:\\d{2}:\\d{2} GMT$`),
295
+ new RegExp(`^${weekday} ${month} [ \\d]\\d \\d{2}:\\d{2}:\\d{2} \\d{4}$`)
296
+ ];
297
+ if (!httpDatePatterns.some((pattern) => pattern.test(normalized))) return void 0;
298
+ const timestamp = Date.parse(normalized);
299
+ if (!Number.isFinite(timestamp)) return void 0;
300
+ return Math.max(0, timestamp - Date.now());
301
+ }
302
+ function parseRetryAfterBody(body) {
303
+ try {
304
+ const parsed = JSON.parse(body);
305
+ const interval = parsed.retry_after?.interval;
306
+ const unit = parsed.retry_after?.unit;
307
+ if (typeof interval !== "number" || !Number.isFinite(interval) || interval < 0)
308
+ return void 0;
309
+ if (typeof unit !== "string") return void 0;
310
+ const unitMultipliers = {
311
+ ms: 1,
312
+ msec: 1,
313
+ msecs: 1,
314
+ millisecond: 1,
315
+ milliseconds: 1,
316
+ s: 1e3,
317
+ sec: 1e3,
318
+ secs: 1e3,
319
+ second: 1e3,
320
+ seconds: 1e3,
321
+ m: 6e4,
322
+ min: 6e4,
323
+ mins: 6e4,
324
+ minute: 6e4,
325
+ minutes: 6e4
326
+ };
327
+ const multiplier = unitMultipliers[unit.toLowerCase()];
328
+ if (multiplier === void 0) return void 0;
329
+ const milliseconds = interval * multiplier;
330
+ return Number.isFinite(milliseconds) ? milliseconds : void 0;
331
+ } catch {
332
+ return void 0;
333
+ }
334
+ }
278
335
  async function executeWithRetry(opts) {
279
336
  const { maxRetries, execute, onRetryableFailure } = opts;
280
337
  let lastError;
@@ -291,7 +348,8 @@ async function executeWithRetry(opts) {
291
348
  }
292
349
  throw new AISecSDKException(
293
350
  lastError.message ?? "Network error",
294
- "AISEC_CLIENT_SIDE_ERROR" /* CLIENT_SIDE_ERROR */
351
+ "AISEC_CLIENT_SIDE_ERROR" /* CLIENT_SIDE_ERROR */,
352
+ { failureKind: "network" }
295
353
  );
296
354
  }
297
355
  if (response.ok) return response;
@@ -308,11 +366,18 @@ async function executeWithRetry(opts) {
308
366
  }
309
367
  const errorText = await response.text();
310
368
  const errorMessage = extractErrorMessage(errorText, response.status);
311
- throw new AISecSDKException(errorMessage, classifyErrorType(response.status));
369
+ const retryAfterHeader = response.headers?.get?.("Retry-After") ?? null;
370
+ const headerRetryAfterMs = parseRetryAfterHeader(retryAfterHeader);
371
+ throw new AISecSDKException(errorMessage, classifyErrorType(response.status), {
372
+ failureKind: "http",
373
+ statusCode: response.status,
374
+ retryAfterMs: headerRetryAfterMs ?? parseRetryAfterBody(errorText)
375
+ });
312
376
  }
313
377
  throw new AISecSDKException(
314
378
  lastError?.message ?? "Max retries exceeded",
315
- "AISEC_CLIENT_SIDE_ERROR" /* CLIENT_SIDE_ERROR */
379
+ "AISEC_CLIENT_SIDE_ERROR" /* CLIENT_SIDE_ERROR */,
380
+ lastError ? { failureKind: "network" } : {}
316
381
  );
317
382
  }
318
383
 
@@ -445,6 +510,7 @@ var PromptDetectedSchema = z.object({
445
510
  injection: z.boolean().optional(),
446
511
  toxic_content: z.boolean().optional(),
447
512
  malicious_code: z.boolean().optional(),
513
+ source_code: z.boolean().optional(),
448
514
  agent: z.boolean().optional(),
449
515
  topic_violation: z.boolean().optional()
450
516
  }).passthrough();
@@ -749,6 +815,16 @@ var ThreatScanReportSchema = z14.object({
749
815
  }).passthrough();
750
816
 
751
817
  // src/scan/scanner.ts
818
+ function resolveNumRetries(opts) {
819
+ const numRetries = opts.numRetries ?? globalConfiguration.numRetries;
820
+ if (!Number.isFinite(numRetries) || !Number.isInteger(numRetries) || numRetries < 0 || numRetries > MAX_NUMBER_OF_RETRIES) {
821
+ throw new AISecSDKException(
822
+ `numRetries must be a finite integer between 0 and ${MAX_NUMBER_OF_RETRIES}`,
823
+ "AISEC_USER_REQUEST_PAYLOAD_ERROR" /* USER_REQUEST_PAYLOAD_ERROR */
824
+ );
825
+ }
826
+ return numRetries;
827
+ }
752
828
  var Scanner = class {
753
829
  /**
754
830
  * @internal
@@ -769,7 +845,7 @@ var Scanner = class {
769
845
  * Perform a synchronous content scan.
770
846
  * @param aiProfile - AI security profile to scan against.
771
847
  * @param content - Content to scan.
772
- * @param opts - Optional transaction/session IDs and metadata.
848
+ * @param opts - Optional transaction/session IDs, metadata, and per-call retry override.
773
849
  * @returns Scan response with verdict, action, and detection details.
774
850
  * @example
775
851
  * ```ts
@@ -788,6 +864,7 @@ var Scanner = class {
788
864
  * ```
789
865
  */
790
866
  async syncScan(aiProfile, content, opts = {}) {
867
+ const numRetries = resolveNumRetries(opts);
791
868
  if (opts.trId && opts.trId.length > MAX_TRANSACTION_ID_STR_LENGTH) {
792
869
  throw new AISecSDKException(
793
870
  `trId exceeds max length of ${MAX_TRANSACTION_ID_STR_LENGTH}`,
@@ -814,33 +891,52 @@ var Scanner = class {
814
891
  body,
815
892
  responseSchema: ScanResponseSchema,
816
893
  auth: this.buildAuth(),
817
- numRetries: globalConfiguration.numRetries
894
+ numRetries
818
895
  });
819
896
  }
820
897
  /**
821
- * Submit content for asynchronous scanning.
822
- * @param scanObjects - Array of scan objects (1–5 items).
823
- * @returns Response containing scan IDs for later querying.
898
+ * Submit one batch of content for asynchronous scanning.
899
+ *
900
+ * The call accepts 1–20 request objects and returns one batch receipt. A batch `scan_id` can fan
901
+ * out to several unordered result rows. Correlate each row by `(scan_id, req_id)`, never array
902
+ * position or `scan_id` alone. The SDK preserves the server's row order and cardinality.
903
+ *
904
+ * Setting `numRetries: 0` guarantees one SDK fetch attempt, but cannot guarantee exactly-once
905
+ * server submission after an ambiguous network or 5xx failure.
906
+ * @param scanObjects - Array of scan objects (1–20 items), each with its own `req_id`.
907
+ * @param opts - Optional per-call retry override.
908
+ * @returns One batch receipt containing the shared scan ID for later querying.
824
909
  * @example
825
910
  * ```ts
826
911
  * import { init, Scanner } from '@cdot65/prisma-airs-sdk';
827
912
  * init();
828
913
  * const scanner = new Scanner();
829
914
  *
830
- * const result = await scanner.asyncScan([
831
- * {
832
- * req_id: 1,
833
- * scan_req: {
834
- * ai_profile: { profile_name: 'my-profile' },
835
- * contents: [{ prompt: 'Tell me about machine learning.' }],
915
+ * const receipt = await scanner.asyncScan(
916
+ * [
917
+ * {
918
+ * req_id: 1,
919
+ * scan_req: {
920
+ * ai_profile: { profile_name: 'my-profile' },
921
+ * contents: [{ prompt: 'Tell me about machine learning.' }],
922
+ * },
836
923
  * },
837
- * },
838
- * ]);
839
- * // result =>
924
+ * {
925
+ * req_id: 2,
926
+ * scan_req: {
927
+ * ai_profile: { profile_name: 'my-profile' },
928
+ * contents: [{ prompt: 'What are neural networks?' }],
929
+ * },
930
+ * },
931
+ * ],
932
+ * { numRetries: 0 },
933
+ * );
934
+ * // receipt =>
840
935
  * // { received: '2024-01-01T00:00:00Z', scan_id: '550e...' }
841
936
  * ```
842
937
  */
843
- async asyncScan(scanObjects) {
938
+ async asyncScan(scanObjects, opts = {}) {
939
+ const numRetries = resolveNumRetries(opts);
844
940
  if (scanObjects.length < 1) {
845
941
  throw new AISecSDKException(
846
942
  "At least 1 scan object is required",
@@ -860,13 +956,17 @@ var Scanner = class {
860
956
  body: scanObjects,
861
957
  responseSchema: AsyncScanResponseSchema,
862
958
  auth: this.buildAuth(),
863
- numRetries: globalConfiguration.numRetries
959
+ numRetries
864
960
  });
865
961
  }
866
962
  /**
867
963
  * Query scan results by scan IDs.
964
+ *
965
+ * One scan ID can return several unordered rows. Correlate them using `(scan_id, req_id)`. The
966
+ * SDK does not sort, deduplicate, or collapse the response.
868
967
  * @param scanIds - Array of scan UUIDs (1–5 items).
869
- * @returns Array of scan results with status and response data.
968
+ * @param opts - Optional per-call retry override.
969
+ * @returns Every scan-result row in server order, with status and response data.
870
970
  * @example
871
971
  * ```ts
872
972
  * import { init, Scanner } from '@cdot65/prisma-airs-sdk';
@@ -876,12 +976,14 @@ var Scanner = class {
876
976
  * const results = await scanner.queryByScanIds([
877
977
  * '550e8400-e29b-41d4-a716-446655440000',
878
978
  * ]);
879
- * // results =>
880
- * // [{ scan_id: '550e8400-e29b-41d4-a716-446655440000', status: 'complete',
881
- * // result: { category: 'benign', action: 'allow', ... } }]
979
+ * // A single scan ID may produce multiple rows, for example req_id 2 then req_id 1.
980
+ * for (const row of results) {
981
+ * console.log(`${row.scan_id}:${row.req_id}`, row.status, row.result?.action);
982
+ * }
882
983
  * ```
883
984
  */
884
- async queryByScanIds(scanIds) {
985
+ async queryByScanIds(scanIds, opts = {}) {
986
+ const numRetries = resolveNumRetries(opts);
885
987
  if (scanIds.length < 1) {
886
988
  throw new AISecSDKException(
887
989
  "At least 1 scan_id is required",
@@ -906,13 +1008,17 @@ var Scanner = class {
906
1008
  params: { scan_ids: scanIds.join(",") },
907
1009
  responseSchema: z15.array(ScanIdResultSchema),
908
1010
  auth: this.buildAuth(),
909
- numRetries: globalConfiguration.numRetries
1011
+ numRetries
910
1012
  });
911
1013
  }
912
1014
  /**
913
1015
  * Query detailed threat reports by report IDs.
1016
+ *
1017
+ * One report ID can return several rows. Correlate them using `(report_id, req_id)`. The SDK
1018
+ * preserves server order and cardinality without sorting or deduplicating.
914
1019
  * @param reportIds - Array of report IDs (1–5 items).
915
- * @returns Array of threat scan reports with detection details.
1020
+ * @param opts - Optional per-call retry override.
1021
+ * @returns Every report row in server order, with detection details.
916
1022
  * @example
917
1023
  * ```ts
918
1024
  * import { init, Scanner } from '@cdot65/prisma-airs-sdk';
@@ -920,12 +1026,13 @@ var Scanner = class {
920
1026
  * const scanner = new Scanner();
921
1027
  *
922
1028
  * const reports = await scanner.queryByReportIds(['R000...']);
923
- * // reports =>
924
- * // [{ report_id: 'R000...', scan_id: '550e...',
925
- * // detection_results: [{ detection_service: 'pi', verdict: 'benign', action: 'allow' }] }]
1029
+ * for (const report of reports) {
1030
+ * console.log(`${report.report_id}:${report.req_id}`, report.detection_results);
1031
+ * }
926
1032
  * ```
927
1033
  */
928
- async queryByReportIds(reportIds) {
1034
+ async queryByReportIds(reportIds, opts = {}) {
1035
+ const numRetries = resolveNumRetries(opts);
929
1036
  if (reportIds.length < 1) {
930
1037
  throw new AISecSDKException(
931
1038
  "At least 1 report_id is required",
@@ -945,7 +1052,7 @@ var Scanner = class {
945
1052
  params: { report_ids: reportIds.join(",") },
946
1053
  responseSchema: z15.array(ThreatScanReportSchema),
947
1054
  auth: this.buildAuth(),
948
- numRetries: globalConfiguration.numRetries
1055
+ numRetries
949
1056
  });
950
1057
  }
951
1058
  };