@silmaril-security/sdk 0.7.7 → 0.7.8

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
@@ -36,6 +36,19 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
36
36
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
37
37
 
38
38
  // src/exceptions.ts
39
+ function isReviewCompatibilityError(error) {
40
+ if (typeof error !== "object" && typeof error !== "function" || error === null) {
41
+ return false;
42
+ }
43
+ try {
44
+ return Object.getOwnPropertyDescriptor(
45
+ error,
46
+ REVIEW_COMPATIBILITY_ERROR_BRAND
47
+ )?.value === true;
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
39
52
  function parseErrorBody(body) {
40
53
  try {
41
54
  const parsed = JSON.parse(body);
@@ -52,7 +65,7 @@ function parseErrorBody(body) {
52
65
  return { details: void 0, error: void 0, apiMessage: void 0 };
53
66
  }
54
67
  }
55
- var MAX_PROMPT_DISPLAY_LEN, FirewallBlockedException, PromptBlockedException, SilmarilApiError;
68
+ var MAX_PROMPT_DISPLAY_LEN, FirewallBlockedException, PromptBlockedException, REVIEW_COMPATIBILITY_ERROR_BRAND, ReviewCompatibilityError, SilmarilApiError;
56
69
  var init_exceptions = __esm({
57
70
  "src/exceptions.ts"() {
58
71
  "use strict";
@@ -88,6 +101,27 @@ var init_exceptions = __esm({
88
101
  }
89
102
  };
90
103
  PromptBlockedException = FirewallBlockedException;
104
+ REVIEW_COMPATIBILITY_ERROR_BRAND = /* @__PURE__ */ Symbol.for(
105
+ "@silmaril-security/sdk/ReviewCompatibilityError"
106
+ );
107
+ ReviewCompatibilityError = class _ReviewCompatibilityError extends Error {
108
+ requestedReview;
109
+ acknowledgedReview;
110
+ constructor(requestedReview, acknowledgedReview) {
111
+ const detail = acknowledgedReview === void 0 ? "the response omitted its acknowledgement" : "the response acknowledgement did not match";
112
+ super(`Silmaril review compatibility error: ${detail} for requested ${requestedReview} review`);
113
+ this.name = "ReviewCompatibilityError";
114
+ this.requestedReview = requestedReview;
115
+ this.acknowledgedReview = acknowledgedReview;
116
+ Object.defineProperty(this, REVIEW_COMPATIBILITY_ERROR_BRAND, {
117
+ value: true,
118
+ enumerable: false,
119
+ configurable: false,
120
+ writable: false
121
+ });
122
+ Object.setPrototypeOf(this, _ReviewCompatibilityError.prototype);
123
+ }
124
+ };
91
125
  SilmarilApiError = class _SilmarilApiError extends Error {
92
126
  status;
93
127
  statusText;
@@ -304,6 +338,9 @@ async function createLangChainHandler(firewall, options = {}) {
304
338
  ...toolName !== void 0 ? { toolName } : {}
305
339
  });
306
340
  } catch (err) {
341
+ if (isReviewCompatibilityError(err)) {
342
+ throw err;
343
+ }
307
344
  if (!failOpen) {
308
345
  throw err;
309
346
  }
@@ -458,6 +495,7 @@ __export(src_exports, {
458
495
  Outcome: () => Outcome,
459
496
  PRIMARY_OUTCOMES: () => PRIMARY_OUTCOMES,
460
497
  PromptBlockedException: () => PromptBlockedException,
498
+ ReviewCompatibilityError: () => ReviewCompatibilityError,
461
499
  SilmarilApiError: () => SilmarilApiError,
462
500
  isHarmfulOutcome: () => isHarmfulOutcome,
463
501
  isPrimaryOutcome: () => isPrimaryOutcome,
@@ -818,12 +856,32 @@ function sanitizeText(text) {
818
856
  }
819
857
 
820
858
  // src/firewall.ts
821
- var SDK_VERSION = "0.7.7";
859
+ var SDK_VERSION = "0.7.8";
822
860
  var DEFAULT_TIMEOUT_MS = 1e4;
823
861
  var DEFAULT_MAX_RETRIES = 5;
824
862
  var MAX_BACKOFF_SECONDS = 30;
825
863
  var MAX_ERROR_BODY_BYTES = 1 << 16;
826
864
  var MAX_TIMEOUT_MS = 2147483647;
865
+ function validateReview(value, name) {
866
+ if (value === void 0) {
867
+ return void 0;
868
+ }
869
+ if (value !== "standard" && value !== "deep") {
870
+ throw new Error(`Firewall: ${name} must be standard or deep`);
871
+ }
872
+ return value;
873
+ }
874
+ function resolveReview(perCallReview, clientReview) {
875
+ return perCallReview === void 0 ? clientReview : validateReview(perCallReview, "review");
876
+ }
877
+ function verifyReviewAcknowledgement(acknowledgedReview, requestedReview) {
878
+ if (requestedReview !== void 0 && acknowledgedReview !== requestedReview) {
879
+ throw new ReviewCompatibilityError(requestedReview, acknowledgedReview);
880
+ }
881
+ }
882
+ function reviewAcknowledgementFromResponse(response) {
883
+ return isRecord2(response) ? response.review : void 0;
884
+ }
827
885
  function resolveMode(value, requestedMode) {
828
886
  let responseMode;
829
887
  if (value === "shadow" || value === "warn" || value === "block") {
@@ -836,7 +894,7 @@ function resolveMode(value, requestedMode) {
836
894
  function legacyMode(shadowMode) {
837
895
  return shadowMode === void 0 ? void 0 : shadowMode ? "shadow" : "block";
838
896
  }
839
- function blockResultFromResponse(data, requestedMode) {
897
+ function blockResultFromResponse(data, requestedMode, requestedReview) {
840
898
  if (data.prediction !== "BENIGN" && data.prediction !== "MALICIOUS") {
841
899
  throw new Error("Firewall: response prediction must be BENIGN or MALICIOUS");
842
900
  }
@@ -845,6 +903,9 @@ function blockResultFromResponse(data, requestedMode) {
845
903
  score: Number(data.score),
846
904
  threshold: Number(data.threshold)
847
905
  };
906
+ if (requestedReview !== void 0) {
907
+ result.review = requestedReview;
908
+ }
848
909
  const mode = resolveMode(data.mode, requestedMode);
849
910
  if (mode !== void 0) {
850
911
  result.mode = mode;
@@ -1119,6 +1180,7 @@ var Firewall = class {
1119
1180
  timeoutMs;
1120
1181
  shadowMode;
1121
1182
  mode;
1183
+ review;
1122
1184
  headers;
1123
1185
  constructor(options) {
1124
1186
  if (!options.apiKey) {
@@ -1139,6 +1201,7 @@ var Firewall = class {
1139
1201
  );
1140
1202
  }
1141
1203
  this.mode = options.mode ?? legacyMode(options.shadowMode);
1204
+ this.review = validateReview(options.review, "review");
1142
1205
  this.shadowMode = this.mode === "shadow";
1143
1206
  this.headers = Object.freeze({
1144
1207
  "x-api-key": this.apiKey,
@@ -1178,9 +1241,13 @@ var Firewall = class {
1178
1241
  texts: texts.map((text) => sanitizeText(text))
1179
1242
  };
1180
1243
  const requestedMode = options.mode ?? this.mode;
1244
+ const requestedReview = resolveReview(options.review, this.review);
1181
1245
  if (requestedMode !== void 0) {
1182
1246
  payload.mode = requestedMode;
1183
1247
  }
1248
+ if (requestedReview !== void 0) {
1249
+ payload.review = requestedReview;
1250
+ }
1184
1251
  if (options.hooks && options.hooks.length > 0) {
1185
1252
  payload.hooks = options.hooks.map((h) => String(h));
1186
1253
  }
@@ -1200,6 +1267,7 @@ var Firewall = class {
1200
1267
  );
1201
1268
  }
1202
1269
  const data = await this.postWithRetry(payload, options.signal);
1270
+ verifyReviewAcknowledgement(reviewAcknowledgementFromResponse(data), requestedReview);
1203
1271
  if (!Array.isArray(data.predictions)) {
1204
1272
  throw new Error("Firewall: response predictions must be an array");
1205
1273
  }
@@ -1208,7 +1276,9 @@ var Firewall = class {
1208
1276
  `Firewall: response predictions length ${data.predictions.length} does not match submitted texts length ${payload.texts.length}`
1209
1277
  );
1210
1278
  }
1211
- return data.predictions.map((p) => blockResultFromResponse(p, requestedMode));
1279
+ return data.predictions.map(
1280
+ (p) => blockResultFromResponse(p, requestedMode, requestedReview)
1281
+ );
1212
1282
  }
1213
1283
  asLangChainHandler(options = {}) {
1214
1284
  return Promise.resolve().then(() => (init_langchain(), langchain_exports)).then(
@@ -1258,9 +1328,13 @@ var Firewall = class {
1258
1328
  async classifySingle(text, options, metadataInfo) {
1259
1329
  const payload = { text };
1260
1330
  const requestedMode = options.mode ?? this.mode;
1331
+ const requestedReview = resolveReview(options.review, this.review);
1261
1332
  if (requestedMode !== void 0) {
1262
1333
  payload.mode = requestedMode;
1263
1334
  }
1335
+ if (requestedReview !== void 0) {
1336
+ payload.review = requestedReview;
1337
+ }
1264
1338
  if (options.hook !== void 0) {
1265
1339
  payload.hook = options.hook;
1266
1340
  }
@@ -1275,7 +1349,8 @@ var Firewall = class {
1275
1349
  ...options.governance === void 0 ? {} : { governance: options.governance }
1276
1350
  });
1277
1351
  const data = await this.postWithRetry(payload, options.signal);
1278
- return blockResultFromResponse(data, requestedMode);
1352
+ verifyReviewAcknowledgement(reviewAcknowledgementFromResponse(data), requestedReview);
1353
+ return blockResultFromResponse(data, requestedMode, requestedReview);
1279
1354
  }
1280
1355
  };
1281
1356
 
@@ -1299,6 +1374,7 @@ init_hooks();
1299
1374
  Outcome,
1300
1375
  PRIMARY_OUTCOMES,
1301
1376
  PromptBlockedException,
1377
+ ReviewCompatibilityError,
1302
1378
  SilmarilApiError,
1303
1379
  isHarmfulOutcome,
1304
1380
  isPrimaryOutcome,