agentid-sdk 0.1.24 → 0.1.26

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.
@@ -1149,6 +1149,79 @@ var PIIManager = class {
1149
1149
  }
1150
1150
  };
1151
1151
 
1152
+ // src/context-intent.ts
1153
+ var EDUCATIONAL_MARKERS = [
1154
+ "explain",
1155
+ "analysis",
1156
+ "analyze",
1157
+ "analyse",
1158
+ "review",
1159
+ "summarize",
1160
+ "summarise",
1161
+ "summary",
1162
+ "plain-language explanation",
1163
+ "red flags",
1164
+ "security workshop",
1165
+ "training",
1166
+ "lesson",
1167
+ "quoted attack",
1168
+ "quoted example",
1169
+ "attack example",
1170
+ "security event",
1171
+ "security log",
1172
+ "audit log",
1173
+ "incident report",
1174
+ "trace timeline"
1175
+ ];
1176
+ var WARNING_MARKERS = [
1177
+ "dangerous",
1178
+ "risky",
1179
+ "risk",
1180
+ "warning",
1181
+ "warn",
1182
+ "malicious",
1183
+ "security indicator",
1184
+ "red flags"
1185
+ ];
1186
+ var NON_EXECUTION_MARKERS = [
1187
+ "do not execute",
1188
+ "do not run",
1189
+ "do not return commands",
1190
+ "do not output commands",
1191
+ "do not provide executable steps",
1192
+ "for explanation only",
1193
+ "non-executable"
1194
+ ];
1195
+ function normalizeIntentInput(input) {
1196
+ return input.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/\s+/g, " ").trim();
1197
+ }
1198
+ function hasAnyMarker(input, markers) {
1199
+ return markers.some((marker) => input.includes(marker));
1200
+ }
1201
+ function classifyInjectionContextIntent(input) {
1202
+ const normalized = normalizeIntentInput(input ?? "");
1203
+ if (!normalized) {
1204
+ return {
1205
+ educational: false,
1206
+ quotedExample: false,
1207
+ warningOnly: false,
1208
+ nonExecutableConstraint: false,
1209
+ promptInjectionExempt: false
1210
+ };
1211
+ }
1212
+ const educational = hasAnyMarker(normalized, EDUCATIONAL_MARKERS);
1213
+ const warningOnly = hasAnyMarker(normalized, WARNING_MARKERS);
1214
+ const nonExecutableConstraint = hasAnyMarker(normalized, NON_EXECUTION_MARKERS);
1215
+ const quotedExample = /["'`]/.test(input) || normalized.includes("quoted") || normalized.includes("quote") || normalized.includes("phrase") || normalized.includes("attack example");
1216
+ return {
1217
+ educational,
1218
+ quotedExample,
1219
+ warningOnly,
1220
+ nonExecutableConstraint,
1221
+ promptInjectionExempt: educational && (quotedExample || warningOnly || nonExecutableConstraint || normalized.includes("prompt injection indicator") || normalized.includes("jailbreak prompt"))
1222
+ };
1223
+ }
1224
+
1152
1225
  // src/security.ts
1153
1226
  var MAX_ANALYSIS_WINDOW = 8192;
1154
1227
  var WINDOW_SLICE_SIZE = 4e3;
@@ -1156,6 +1229,7 @@ var WORD_BOUNDARY_SCAN = 120;
1156
1229
  var AI_TIMEOUT_MS = 2e3;
1157
1230
  var TELEMETRY_SNIPPET_LIMIT = 4e3;
1158
1231
  var AI_OPENAI_MODEL = "gpt-4o-mini";
1232
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1159
1233
  var EN_STOPWORDS = /* @__PURE__ */ new Set([
1160
1234
  "the",
1161
1235
  "and",
@@ -1203,6 +1277,25 @@ var piiSingleton = null;
1203
1277
  function normalizeBaseUrl(baseUrl) {
1204
1278
  return (baseUrl || "").replace(/\/+$/, "");
1205
1279
  }
1280
+ function createPseudoUuidV4() {
1281
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
1282
+ const random = Math.floor(Math.random() * 16);
1283
+ const nibble = char === "x" ? random : random & 3 | 8;
1284
+ return nibble.toString(16);
1285
+ });
1286
+ }
1287
+ function isUuidLike(value) {
1288
+ return typeof value === "string" && UUID_RE.test(value);
1289
+ }
1290
+ function createEventId(seed) {
1291
+ if (isUuidLike(seed)) {
1292
+ return seed;
1293
+ }
1294
+ if (typeof globalThis.crypto?.randomUUID === "function") {
1295
+ return globalThis.crypto.randomUUID();
1296
+ }
1297
+ return createPseudoUuidV4();
1298
+ }
1206
1299
  function getSharedPIIManager() {
1207
1300
  if (!piiSingleton) {
1208
1301
  piiSingleton = new PIIManager();
@@ -1291,6 +1384,10 @@ function findRegexMatch(prompt) {
1291
1384
  if (!prompt) {
1292
1385
  return null;
1293
1386
  }
1387
+ const contextIntent = classifyInjectionContextIntent(prompt);
1388
+ if (contextIntent.promptInjectionExempt) {
1389
+ return null;
1390
+ }
1294
1391
  const normalizedPrompt = normalizeForHeuristics(prompt);
1295
1392
  for (const rule of HEURISTIC_RULES) {
1296
1393
  try {
@@ -1416,13 +1513,16 @@ async function reportSecurityEvent(options) {
1416
1513
  const snippet = truncateSnippet(options.snippet);
1417
1514
  const snippetHash = snippet ? await sha256Hex(snippet) : "";
1418
1515
  const inputValue = options.storePii ? snippet : snippetHash;
1516
+ const eventId = createEventId(options.eventId ?? options.clientEventId);
1419
1517
  const metadata = {
1420
1518
  source: options.source,
1421
1519
  detector: options.detector,
1422
1520
  trigger_rule: options.triggerRule,
1423
1521
  language: options.language,
1424
1522
  ai_scan_status: options.aiStatus ?? null,
1425
- reason: options.reason ?? null
1523
+ reason: options.reason ?? null,
1524
+ client_event_id: eventId,
1525
+ ...options.telemetryMetadata ?? {}
1426
1526
  };
1427
1527
  if (options.storePii) {
1428
1528
  metadata.snippet = snippet;
@@ -1430,11 +1530,14 @@ async function reportSecurityEvent(options) {
1430
1530
  metadata.snippet_hash = snippetHash;
1431
1531
  }
1432
1532
  const payload = {
1533
+ event_id: eventId,
1534
+ system_id: options.systemId,
1433
1535
  input: inputValue,
1434
1536
  output: "",
1435
1537
  model: "agentid.local_injection_scanner",
1436
1538
  event_type: options.outcome === "blocked" ? "security_block" : "security_alert",
1437
1539
  severity: options.outcome === "blocked" ? "error" : "warning",
1540
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1438
1541
  metadata
1439
1542
  };
1440
1543
  void fetch(`${normalizeBaseUrl(options.baseUrl)}/ingest`, {
@@ -1464,7 +1567,11 @@ var InjectionScanner = class _InjectionScanner {
1464
1567
  aiScanEnabled: options?.aiScanEnabled,
1465
1568
  storePii: options?.storePii,
1466
1569
  piiManager: options?.piiManager,
1467
- source: options?.source
1570
+ source: options?.source,
1571
+ systemId: options?.systemId,
1572
+ eventId: options?.eventId,
1573
+ clientEventId: options?.clientEventId,
1574
+ telemetryMetadata: options?.telemetryMetadata
1468
1575
  });
1469
1576
  }
1470
1577
  async scan(params) {
@@ -1476,6 +1583,11 @@ var InjectionScanner = class _InjectionScanner {
1476
1583
  const storePii = params.storePii === true;
1477
1584
  const aiScanEnabled = params.aiScanEnabled !== false;
1478
1585
  const language = detectLanguageTag(prompt);
1586
+ const scanStartedAt = Date.now();
1587
+ const buildTelemetryMetadata = () => ({
1588
+ ...params.telemetryMetadata ?? {},
1589
+ sdk_local_scan_ms: Math.max(0, Date.now() - scanStartedAt)
1590
+ });
1479
1591
  const regexMatch = findRegexMatch(prompt);
1480
1592
  if (regexMatch) {
1481
1593
  await reportSecurityEvent({
@@ -1487,7 +1599,11 @@ var InjectionScanner = class _InjectionScanner {
1487
1599
  triggerRule: regexMatch.rule,
1488
1600
  snippet: regexMatch.snippet,
1489
1601
  storePii,
1490
- language
1602
+ language,
1603
+ systemId: params.systemId,
1604
+ eventId: params.eventId,
1605
+ clientEventId: params.clientEventId,
1606
+ telemetryMetadata: buildTelemetryMetadata()
1491
1607
  });
1492
1608
  throw new Error(`AgentID: Prompt injection blocked (${regexMatch.rule})`);
1493
1609
  }
@@ -1508,7 +1624,11 @@ var InjectionScanner = class _InjectionScanner {
1508
1624
  storePii,
1509
1625
  language,
1510
1626
  aiStatus: "skipped",
1511
- reason: "ai_scan_disabled_for_high_risk_language"
1627
+ reason: "ai_scan_disabled_for_high_risk_language",
1628
+ systemId: params.systemId,
1629
+ eventId: params.eventId,
1630
+ clientEventId: params.clientEventId,
1631
+ telemetryMetadata: buildTelemetryMetadata()
1512
1632
  });
1513
1633
  return;
1514
1634
  }
@@ -1527,7 +1647,11 @@ var InjectionScanner = class _InjectionScanner {
1527
1647
  storePii,
1528
1648
  language,
1529
1649
  aiStatus: aiResult.status,
1530
- reason: aiResult.reason
1650
+ reason: aiResult.reason,
1651
+ systemId: params.systemId,
1652
+ eventId: params.eventId,
1653
+ clientEventId: params.clientEventId,
1654
+ telemetryMetadata: buildTelemetryMetadata()
1531
1655
  });
1532
1656
  return;
1533
1657
  }
@@ -1543,7 +1667,11 @@ var InjectionScanner = class _InjectionScanner {
1543
1667
  storePii,
1544
1668
  language,
1545
1669
  aiStatus: aiResult.status,
1546
- reason: aiResult.reason
1670
+ reason: aiResult.reason,
1671
+ systemId: params.systemId,
1672
+ eventId: params.eventId,
1673
+ clientEventId: params.clientEventId,
1674
+ telemetryMetadata: buildTelemetryMetadata()
1547
1675
  });
1548
1676
  throw new Error(`AgentID: Prompt injection blocked (${aiResult.reason})`);
1549
1677
  }
@@ -1555,7 +1683,7 @@ function getInjectionScanner() {
1555
1683
 
1556
1684
  // src/sdk-version.ts
1557
1685
  var FALLBACK_SDK_VERSION = "js-0.0.0-dev";
1558
- var AGENTID_SDK_VERSION_HEADER = "js-0.1.24".trim().length > 0 ? "js-0.1.24" : FALLBACK_SDK_VERSION;
1686
+ var AGENTID_SDK_VERSION_HEADER = "js-0.1.26".trim().length > 0 ? "js-0.1.26" : FALLBACK_SDK_VERSION;
1559
1687
 
1560
1688
  // src/local-security-enforcer.ts
1561
1689
  var DEFAULT_FAIL_OPEN_CONFIG = {
@@ -1565,6 +1693,7 @@ var DEFAULT_FAIL_OPEN_CONFIG = {
1565
1693
  block_on_heuristic: false,
1566
1694
  inject_transparency_metadata: false,
1567
1695
  block_pii_leakage: false,
1696
+ enable_sdk_pii_masking: false,
1568
1697
  block_db_access: false,
1569
1698
  block_code_execution: false,
1570
1699
  block_toxicity: false
@@ -1667,8 +1796,8 @@ var LocalSecurityEnforcer = class {
1667
1796
 
1668
1797
  // src/capability-config.ts
1669
1798
  var CONFIG_TTL_MS = 5 * 60 * 1e3;
1670
- var CONFIG_TIMEOUT_MS = 8e3;
1671
- var CONFIG_RETRY_DELAY_MS = 1e3;
1799
+ var CONFIG_TIMEOUT_MS = 1500;
1800
+ var CONFIG_RETRY_DELAY_MS = 150;
1672
1801
  var MAX_CAPABILITY_CACHE_ENTRIES = 500;
1673
1802
  var CapabilityConfigFetchError = class extends Error {
1674
1803
  constructor(message, params) {
@@ -1752,6 +1881,11 @@ function normalizeCapabilityConfig(payload) {
1752
1881
  false
1753
1882
  ),
1754
1883
  block_pii_leakage: readBooleanField(body, "block_pii_leakage", "block_pii"),
1884
+ enable_sdk_pii_masking: readOptionalBooleanField(
1885
+ body,
1886
+ "enable_sdk_pii_masking",
1887
+ false
1888
+ ),
1755
1889
  block_db_access: readBooleanField(body, "block_db_access", "block_db"),
1756
1890
  block_code_execution: readBooleanField(
1757
1891
  body,
@@ -2004,6 +2138,19 @@ function normalizeIngestTimeoutMs(value) {
2004
2138
  }
2005
2139
  return rounded;
2006
2140
  }
2141
+ function setFiniteDurationMetadata(metadata, key, value) {
2142
+ if (typeof value === "number" && Number.isFinite(value)) {
2143
+ metadata[key] = Math.max(0, Math.trunc(value));
2144
+ }
2145
+ }
2146
+ function buildSdkTimingMetadata(params) {
2147
+ const metadata = {};
2148
+ setFiniteDurationMetadata(metadata, "sdk_config_fetch_ms", params.sdkConfigFetchMs);
2149
+ setFiniteDurationMetadata(metadata, "sdk_local_scan_ms", params.sdkLocalScanMs);
2150
+ setFiniteDurationMetadata(metadata, "sdk_guard_ms", params.sdkGuardMs);
2151
+ setFiniteDurationMetadata(metadata, "sdk_ingest_ms", params.sdkIngestMs);
2152
+ return metadata;
2153
+ }
2007
2154
  function resolveConfiguredApiKey(value) {
2008
2155
  const explicit = typeof value === "string" ? value.trim() : "";
2009
2156
  const fromEnv = globalThis.process?.env?.AGENTID_API_KEY ?? "";
@@ -2017,13 +2164,32 @@ function isInfrastructureGuardReason(reason) {
2017
2164
  if (!reason) return false;
2018
2165
  return reason === "system_failure" || reason === "system_failure_db_unavailable" || reason === "logging_failed" || reason === "server_error" || reason === "guard_unreachable" || reason === "api_key_pepper_missing" || reason === "encryption_key_missing";
2019
2166
  }
2020
- function isUuidLike(value) {
2167
+ function isGuardFailureEligibleForLocalFallback(reason) {
2168
+ return reason === "network_error_strict_mode" || reason === "server_error" || isInfrastructureGuardReason(reason);
2169
+ }
2170
+ function isFailCloseIngestReason(reason) {
2171
+ if (!reason) return false;
2172
+ return reason === "system_failure" || reason === "system_failure_db_unavailable" || reason === "server_error" || reason === "redis_rate_limit_unavailable" || reason === "redis_quota_unavailable" || reason === "redis_token_quota_unavailable";
2173
+ }
2174
+ function shouldEscalateIngestFailure(params) {
2175
+ if (!params.strictMode) {
2176
+ return false;
2177
+ }
2178
+ if (params.status === null) {
2179
+ return true;
2180
+ }
2181
+ if (params.status >= 500) {
2182
+ return true;
2183
+ }
2184
+ return isFailCloseIngestReason(params.reason);
2185
+ }
2186
+ function isUuidLike2(value) {
2021
2187
  if (!value) return false;
2022
2188
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
2023
2189
  value
2024
2190
  );
2025
2191
  }
2026
- function createPseudoUuidV4() {
2192
+ function createPseudoUuidV42() {
2027
2193
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (token) => {
2028
2194
  const rand = Math.floor(Math.random() * 16);
2029
2195
  const value = token === "x" ? rand : rand & 3 | 8;
@@ -2034,19 +2200,28 @@ function sanitizeIngestText(value) {
2034
2200
  const text = typeof value === "string" ? value : String(value ?? "");
2035
2201
  return text.slice(0, MAX_INGEST_TEXT_CHARS);
2036
2202
  }
2037
- function createEventId(seed) {
2038
- if (isUuidLike(seed)) return seed;
2203
+ function normalizeExpectedLanguages(value) {
2204
+ if (!Array.isArray(value)) {
2205
+ return void 0;
2206
+ }
2207
+ const normalized = [...new Set(
2208
+ value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter((entry) => entry.length > 0)
2209
+ )];
2210
+ return normalized.length > 0 ? normalized : void 0;
2211
+ }
2212
+ function createEventId2(seed) {
2213
+ if (isUuidLike2(seed)) return seed;
2039
2214
  if (typeof globalThis.crypto?.randomUUID === "function") {
2040
2215
  return globalThis.crypto.randomUUID();
2041
2216
  }
2042
- return createPseudoUuidV4();
2217
+ return createPseudoUuidV42();
2043
2218
  }
2044
2219
  function createCorrelationId(seed) {
2045
- if (isUuidLike(seed)) return seed;
2220
+ if (isUuidLike2(seed)) return seed;
2046
2221
  if (typeof globalThis.crypto?.randomUUID === "function") {
2047
2222
  return globalThis.crypto.randomUUID();
2048
2223
  }
2049
- return createPseudoUuidV4();
2224
+ return createPseudoUuidV42();
2050
2225
  }
2051
2226
  async function waitForRetry(attemptIndex) {
2052
2227
  const delay = GUARD_RETRY_DELAYS_MS[attemptIndex];
@@ -2183,28 +2358,60 @@ var SecurityBlockError = class extends Error {
2183
2358
  this.reason = reason;
2184
2359
  }
2185
2360
  };
2361
+ var DependencyError = class extends Error {
2362
+ constructor(params) {
2363
+ const statusLabel = typeof params.status === "number" ? String(params.status) : "network";
2364
+ super(`AgentID dependency failure (${params.dependency}): ${params.reason} [${statusLabel}]`);
2365
+ this.name = "DependencyError";
2366
+ this.dependency = params.dependency;
2367
+ this.reason = params.reason;
2368
+ this.status = params.status;
2369
+ }
2370
+ };
2186
2371
  var AgentID = class {
2187
2372
  constructor(config = {}) {
2188
2373
  this.injectionScanner = getInjectionScanner();
2189
2374
  this.recentGuardVerdicts = /* @__PURE__ */ new Map();
2190
2375
  this.apiKey = resolveConfiguredApiKey(config.apiKey);
2191
2376
  this.baseUrl = normalizeBaseUrl3(config.baseUrl ?? "https://app.getagentid.com/api/v1");
2192
- this.piiMasking = Boolean(config.piiMasking);
2377
+ this.configuredPiiMasking = typeof config.piiMasking === "boolean" ? config.piiMasking : null;
2193
2378
  this.checkInjection = config.checkInjection !== false;
2379
+ this.clientFastFail = config.clientFastFail === true || config.client_fast_fail === true;
2194
2380
  this.aiScanEnabled = config.aiScanEnabled !== false;
2195
2381
  this.storePii = config.storePii === true;
2196
2382
  this.strictMode = config.strictMode === true;
2383
+ if (typeof config.failureMode !== "undefined" && config.failureMode !== "fail_open" && config.failureMode !== "fail_close") {
2384
+ throw new Error("AgentID invalid failureMode. Use 'fail_open' or 'fail_close'.");
2385
+ }
2386
+ if (this.strictMode && config.failureMode === "fail_open") {
2387
+ throw new Error("AgentID strictMode=true conflicts with failureMode='fail_open'.");
2388
+ }
2389
+ this.configuredFailureMode = this.strictMode ? "fail_close" : config.failureMode ?? null;
2197
2390
  this.guardTimeoutMs = normalizeGuardTimeoutMs(config.guardTimeoutMs);
2198
2391
  this.ingestTimeoutMs = normalizeIngestTimeoutMs(config.ingestTimeoutMs);
2199
2392
  this.pii = new PIIManager();
2200
2393
  this.localEnforcer = new LocalSecurityEnforcer(this.pii);
2201
2394
  void this.getCapabilityConfig();
2202
2395
  }
2203
- buildClientCapabilities(framework = "js_sdk", hasFeedbackHandler = false) {
2396
+ get piiMasking() {
2397
+ return this.configuredPiiMasking ?? void 0;
2398
+ }
2399
+ resolveEffectivePiiMasking(config) {
2400
+ if (this.configuredPiiMasking !== null) {
2401
+ return this.configuredPiiMasking;
2402
+ }
2403
+ return config?.enable_sdk_pii_masking === true;
2404
+ }
2405
+ getEffectivePiiMasking(options) {
2406
+ return this.resolveEffectivePiiMasking(this.getCachedCapabilityConfig(options));
2407
+ }
2408
+ buildClientCapabilities(framework = "js_sdk", hasFeedbackHandler = false, capabilityConfig) {
2204
2409
  return {
2205
2410
  capabilities: {
2206
2411
  has_feedback_handler: hasFeedbackHandler,
2207
- pii_masking_enabled: this.piiMasking,
2412
+ pii_masking_enabled: this.resolveEffectivePiiMasking(
2413
+ capabilityConfig ?? this.getCachedCapabilityConfig()
2414
+ ),
2208
2415
  framework
2209
2416
  }
2210
2417
  };
@@ -2218,17 +2425,17 @@ var AgentID = class {
2218
2425
  }
2219
2426
  resolveClientEventId(requestBody) {
2220
2427
  const directClientEventId = requestBody.client_event_id;
2221
- if (typeof directClientEventId === "string" && isUuidLike(directClientEventId)) {
2428
+ if (typeof directClientEventId === "string" && isUuidLike2(directClientEventId)) {
2222
2429
  return directClientEventId;
2223
2430
  }
2224
2431
  const metadata = requestBody.metadata;
2225
2432
  if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
2226
2433
  const metadataClientEventId = metadata.client_event_id;
2227
- if (typeof metadataClientEventId === "string" && isUuidLike(metadataClientEventId)) {
2434
+ if (typeof metadataClientEventId === "string" && isUuidLike2(metadataClientEventId)) {
2228
2435
  return metadataClientEventId;
2229
2436
  }
2230
2437
  }
2231
- return createEventId();
2438
+ return createEventId2();
2232
2439
  }
2233
2440
  buildGuardCacheKey(params) {
2234
2441
  if (!params.system_id || !params.input) {
@@ -2272,6 +2479,14 @@ var AgentID = class {
2272
2479
  force
2273
2480
  });
2274
2481
  }
2482
+ async getCapabilityConfigWithTelemetry(force = false, options) {
2483
+ const configStartedAt = Date.now();
2484
+ const capabilityConfig = await this.getCapabilityConfig(force, options);
2485
+ return {
2486
+ capabilityConfig,
2487
+ sdkConfigFetchMs: Math.max(0, Date.now() - configStartedAt)
2488
+ };
2489
+ }
2275
2490
  getCachedCapabilityConfig(options) {
2276
2491
  const effectiveApiKey = this.resolveApiKey(options?.apiKey);
2277
2492
  return getCachedCapabilityConfig({
@@ -2280,12 +2495,32 @@ var AgentID = class {
2280
2495
  });
2281
2496
  }
2282
2497
  async resolveEffectiveStrictMode(options) {
2283
- if (this.strictMode) {
2498
+ if (this.strictMode || this.configuredFailureMode === "fail_close") {
2284
2499
  return true;
2285
2500
  }
2501
+ if (this.configuredFailureMode === "fail_open") {
2502
+ return false;
2503
+ }
2286
2504
  const config = await this.getCapabilityConfig(false, options);
2287
2505
  return config.strict_security_mode || config.failure_mode === "fail_close";
2288
2506
  }
2507
+ maybeRaiseStrictIngestDependencyError(params) {
2508
+ if (params.result.ok) {
2509
+ return;
2510
+ }
2511
+ if (!shouldEscalateIngestFailure({
2512
+ strictMode: params.strictMode,
2513
+ status: params.result.status,
2514
+ reason: params.result.reason
2515
+ })) {
2516
+ return;
2517
+ }
2518
+ throw new DependencyError({
2519
+ dependency: "ingest",
2520
+ reason: params.result.reason ?? "ingest_failed",
2521
+ status: params.result.status
2522
+ });
2523
+ }
2289
2524
  shouldRunLocalInjectionScan(config) {
2290
2525
  if (!this.checkInjection) {
2291
2526
  return false;
@@ -2295,37 +2530,45 @@ var AgentID = class {
2295
2530
  }
2296
2531
  return config.block_on_heuristic;
2297
2532
  }
2298
- async prepareInputForDispatch(params, options) {
2299
- const effectiveApiKey = this.resolveApiKey(options?.apiKey);
2300
- const capabilityConfig = await this.getCapabilityConfig(false, options);
2301
- if (!params.skipInjectionScan && params.input && this.shouldRunLocalInjectionScan(capabilityConfig)) {
2533
+ async applyLocalPolicyChecks(params) {
2534
+ const localScanStartedAt = Date.now();
2535
+ if (params.runPromptInjectionCheck && params.input && this.shouldRunLocalInjectionScan(params.capabilityConfig)) {
2302
2536
  await this.injectionScanner.scan({
2303
2537
  prompt: params.input,
2304
- apiKey: effectiveApiKey,
2538
+ apiKey: params.apiKey,
2305
2539
  baseUrl: this.baseUrl,
2306
2540
  aiScanEnabled: this.aiScanEnabled,
2307
2541
  storePii: this.storePii,
2308
2542
  piiManager: this.pii,
2309
- source: "js_sdk"
2543
+ source: "js_sdk",
2544
+ systemId: params.systemId,
2545
+ eventId: params.clientEventId,
2546
+ clientEventId: params.clientEventId,
2547
+ telemetryMetadata: buildSdkTimingMetadata({
2548
+ sdkConfigFetchMs: params.sdkConfigFetchMs
2549
+ })
2310
2550
  });
2311
2551
  }
2312
2552
  try {
2313
2553
  const enforced = this.localEnforcer.enforce({
2314
2554
  input: params.input,
2315
2555
  stream: params.stream,
2316
- config: capabilityConfig
2556
+ config: params.capabilityConfig
2317
2557
  });
2558
+ const sdkLocalScanMs = Math.max(0, Date.now() - localScanStartedAt);
2318
2559
  for (const event of enforced.events) {
2319
2560
  this.logSecurityPolicyViolation({
2320
2561
  systemId: params.systemId,
2321
2562
  violationType: event.violationType,
2322
2563
  actionTaken: event.actionTaken,
2323
- apiKey: effectiveApiKey
2564
+ apiKey: params.apiKey,
2565
+ sdkConfigFetchMs: params.sdkConfigFetchMs,
2566
+ sdkLocalScanMs
2324
2567
  });
2325
2568
  }
2326
2569
  return {
2327
2570
  sanitizedInput: enforced.sanitizedInput,
2328
- capabilityConfig
2571
+ sdkLocalScanMs
2329
2572
  };
2330
2573
  } catch (error) {
2331
2574
  if (error instanceof SecurityPolicyViolationError) {
@@ -2333,17 +2576,76 @@ var AgentID = class {
2333
2576
  systemId: params.systemId,
2334
2577
  violationType: error.violationType,
2335
2578
  actionTaken: error.actionTaken,
2336
- apiKey: effectiveApiKey
2579
+ apiKey: params.apiKey,
2580
+ sdkConfigFetchMs: params.sdkConfigFetchMs,
2581
+ sdkLocalScanMs: Math.max(0, Date.now() - localScanStartedAt)
2337
2582
  });
2338
2583
  }
2339
2584
  throw error;
2340
2585
  }
2341
2586
  }
2587
+ async prepareInputForDispatch(params, options) {
2588
+ const effectiveApiKey = this.resolveApiKey(options?.apiKey);
2589
+ const { capabilityConfig, sdkConfigFetchMs } = await this.getCapabilityConfigWithTelemetry(
2590
+ false,
2591
+ options
2592
+ );
2593
+ if (!this.clientFastFail) {
2594
+ return {
2595
+ sanitizedInput: params.input,
2596
+ capabilityConfig,
2597
+ sdkConfigFetchMs,
2598
+ sdkLocalScanMs: 0
2599
+ };
2600
+ }
2601
+ const enforced = await this.applyLocalPolicyChecks({
2602
+ input: params.input,
2603
+ systemId: params.systemId,
2604
+ stream: params.stream,
2605
+ capabilityConfig,
2606
+ apiKey: effectiveApiKey,
2607
+ clientEventId: params.clientEventId,
2608
+ sdkConfigFetchMs,
2609
+ runPromptInjectionCheck: !params.skipInjectionScan
2610
+ });
2611
+ return {
2612
+ sanitizedInput: enforced.sanitizedInput,
2613
+ capabilityConfig,
2614
+ sdkConfigFetchMs,
2615
+ sdkLocalScanMs: enforced.sdkLocalScanMs
2616
+ };
2617
+ }
2618
+ async applyLocalFallbackForGuardFailure(params, options) {
2619
+ const effectiveApiKey = this.resolveApiKey(options?.apiKey);
2620
+ const resolvedConfig = params.capabilityConfig && typeof params.sdkConfigFetchMs === "number" ? {
2621
+ capabilityConfig: params.capabilityConfig,
2622
+ sdkConfigFetchMs: params.sdkConfigFetchMs
2623
+ } : await this.getCapabilityConfigWithTelemetry(false, options);
2624
+ const enforced = await this.applyLocalPolicyChecks({
2625
+ input: params.input,
2626
+ systemId: params.systemId,
2627
+ stream: params.stream,
2628
+ capabilityConfig: resolvedConfig.capabilityConfig,
2629
+ apiKey: effectiveApiKey,
2630
+ clientEventId: params.clientEventId,
2631
+ sdkConfigFetchMs: resolvedConfig.sdkConfigFetchMs,
2632
+ runPromptInjectionCheck: true
2633
+ });
2634
+ return {
2635
+ sanitizedInput: enforced.sanitizedInput,
2636
+ capabilityConfig: resolvedConfig.capabilityConfig,
2637
+ sdkConfigFetchMs: resolvedConfig.sdkConfigFetchMs,
2638
+ sdkLocalScanMs: enforced.sdkLocalScanMs
2639
+ };
2640
+ }
2342
2641
  async scanPromptInjection(input, options) {
2343
2642
  if (!input) {
2344
2643
  return;
2345
2644
  }
2346
- const capabilityConfig = await this.getCapabilityConfig(false, options);
2645
+ const { capabilityConfig, sdkConfigFetchMs } = await this.getCapabilityConfigWithTelemetry(
2646
+ false,
2647
+ options
2648
+ );
2347
2649
  if (!this.shouldRunLocalInjectionScan(capabilityConfig)) {
2348
2650
  return;
2349
2651
  }
@@ -2355,7 +2657,13 @@ var AgentID = class {
2355
2657
  aiScanEnabled: this.aiScanEnabled,
2356
2658
  storePii: this.storePii,
2357
2659
  piiManager: this.pii,
2358
- source: "js_sdk"
2660
+ source: "js_sdk",
2661
+ systemId: options?.systemId,
2662
+ eventId: options?.clientEventId,
2663
+ clientEventId: options?.clientEventId,
2664
+ telemetryMetadata: buildSdkTimingMetadata({
2665
+ sdkConfigFetchMs
2666
+ })
2359
2667
  });
2360
2668
  }
2361
2669
  withMaskedOpenAIRequest(req, maskedText) {
@@ -2404,7 +2712,11 @@ var AgentID = class {
2404
2712
  system_id: params.systemId,
2405
2713
  violation_type: params.violationType,
2406
2714
  input_snippet: "[REDACTED_SAMPLE]",
2407
- action_taken: params.actionTaken
2715
+ action_taken: params.actionTaken,
2716
+ ...buildSdkTimingMetadata({
2717
+ sdkConfigFetchMs: params.sdkConfigFetchMs,
2718
+ sdkLocalScanMs: params.sdkLocalScanMs
2719
+ })
2408
2720
  }
2409
2721
  }, { apiKey: params.apiKey });
2410
2722
  }
@@ -2430,6 +2742,42 @@ var AgentID = class {
2430
2742
  { apiKey: params.apiKey }
2431
2743
  );
2432
2744
  }
2745
+ async finalizeIngestTelemetry(params) {
2746
+ const controller = new AbortController();
2747
+ const timeoutId = setTimeout(
2748
+ () => controller.abort(),
2749
+ Math.min(this.ingestTimeoutMs, 2e3)
2750
+ );
2751
+ try {
2752
+ const response = await fetch(`${this.baseUrl}/ingest/finalize`, {
2753
+ method: "POST",
2754
+ headers: {
2755
+ "Content-Type": "application/json",
2756
+ "x-agentid-api-key": params.apiKey,
2757
+ "x-correlation-id": params.clientEventId,
2758
+ "X-AgentID-SDK-Version": AGENTID_SDK_VERSION_HEADER
2759
+ },
2760
+ body: JSON.stringify({
2761
+ client_event_id: params.clientEventId,
2762
+ system_id: params.systemId,
2763
+ sdk_ingest_ms: params.sdkIngestMs
2764
+ }),
2765
+ signal: controller.signal
2766
+ });
2767
+ if (!response.ok) {
2768
+ console.warn(
2769
+ `[AgentID] Ingest telemetry finalize failed (status=${response.status}, client_event_id=${params.clientEventId}).`
2770
+ );
2771
+ }
2772
+ } catch (error) {
2773
+ const label = error && typeof error === "object" && error.name === "AbortError" ? "timeout" : "network";
2774
+ console.warn(
2775
+ `[AgentID] Ingest telemetry finalize failed (${label}, client_event_id=${params.clientEventId}).`
2776
+ );
2777
+ } finally {
2778
+ clearTimeout(timeoutId);
2779
+ }
2780
+ }
2433
2781
  /**
2434
2782
  * GUARD: Checks limits, PII, and security before execution.
2435
2783
  * strictMode=false (default): FAIL-OPEN on connectivity/timeouts.
@@ -2587,15 +2935,16 @@ var AgentID = class {
2587
2935
  }
2588
2936
  return withGuardLatency({ allowed: true, reason: "guard_unreachable" });
2589
2937
  }
2590
- async sendIngest(params, options) {
2938
+ async sendIngest(params, options, internal) {
2939
+ const ingestStartedAt = Date.now();
2591
2940
  const effectiveApiKey = this.resolveApiKey(options?.apiKey);
2592
- const eventId = createEventId(params.event_id);
2941
+ const transportEventId = createEventId2(internal?.transportEventId ?? params.event_id);
2593
2942
  const timestamp = params.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
2594
2943
  const metadata = {
2595
2944
  ...params.metadata ?? {}
2596
2945
  };
2597
- const metadataClientEventId = typeof metadata.client_event_id === "string" && isUuidLike(metadata.client_event_id) ? metadata.client_event_id : null;
2598
- const canonicalClientEventId = metadataClientEventId ?? eventId;
2946
+ const metadataClientEventId = typeof metadata.client_event_id === "string" && isUuidLike2(metadata.client_event_id) ? metadata.client_event_id : null;
2947
+ const canonicalClientEventId = metadataClientEventId ?? transportEventId;
2599
2948
  if (!metadataClientEventId) {
2600
2949
  metadata.client_event_id = canonicalClientEventId;
2601
2950
  }
@@ -2605,7 +2954,7 @@ var AgentID = class {
2605
2954
  void this.getCapabilityConfig(false, { apiKey: effectiveApiKey }).catch(() => void 0);
2606
2955
  const payload = {
2607
2956
  ...params,
2608
- event_id: canonicalClientEventId,
2957
+ event_id: internal?.transportEventId ? transportEventId : canonicalClientEventId,
2609
2958
  timestamp,
2610
2959
  input: sanitizeIngestText(params.input),
2611
2960
  output: sanitizeIngestText(params.output),
@@ -2630,6 +2979,14 @@ var AgentID = class {
2630
2979
  });
2631
2980
  const responseBody = await safeReadJson2(response);
2632
2981
  if (response.ok) {
2982
+ if (!internal?.disableFinalize) {
2983
+ void this.finalizeIngestTelemetry({
2984
+ apiKey: effectiveApiKey,
2985
+ clientEventId: canonicalClientEventId,
2986
+ systemId: params.system_id,
2987
+ sdkIngestMs: Math.max(0, Date.now() - ingestStartedAt)
2988
+ });
2989
+ }
2633
2990
  return { ok: true, status: response.status, reason: null };
2634
2991
  }
2635
2992
  const reason = responseBody && typeof responseBody === "object" && typeof responseBody.reason === "string" ? responseBody.reason ?? null : null;
@@ -2757,8 +3114,15 @@ var AgentID = class {
2757
3114
  * Returns a Promise so callers can await persistence when needed.
2758
3115
  */
2759
3116
  async log(params, options) {
2760
- const result = await this.sendIngest(params, options);
3117
+ const effectiveApiKey = this.resolveApiKey(options?.apiKey);
3118
+ const requestOptions = { apiKey: effectiveApiKey };
3119
+ const effectiveStrictMode = await this.resolveEffectiveStrictMode(requestOptions);
3120
+ const result = await this.sendIngest(params, requestOptions);
2761
3121
  if (!result.ok) {
3122
+ this.maybeRaiseStrictIngestDependencyError({
3123
+ strictMode: effectiveStrictMode,
3124
+ result
3125
+ });
2762
3126
  console.warn(
2763
3127
  `[AgentID] Ingest telemetry failed (status=${result.status ?? "network"}, reason=${result.reason ?? "unknown"}).`
2764
3128
  );
@@ -2784,6 +3148,9 @@ var AgentID = class {
2784
3148
  */
2785
3149
  wrapOpenAI(openai, options) {
2786
3150
  const systemId = options.system_id;
3151
+ const expectedLanguages = normalizeExpectedLanguages(
3152
+ options.expected_languages ?? options.expectedLanguages
3153
+ );
2787
3154
  const adapter = new OpenAIAdapter();
2788
3155
  const wrapChatCompletions = (chatObj) => {
2789
3156
  if (!chatObj || typeof chatObj !== "object") return chatObj;
@@ -2810,6 +3177,8 @@ var AgentID = class {
2810
3177
  const requestLevelApiKey = options.resolveApiKey?.(req) ?? options.apiKey ?? options.api_key;
2811
3178
  const effectiveApiKey = this.resolveApiKey(requestLevelApiKey);
2812
3179
  const requestOptions = { apiKey: effectiveApiKey };
3180
+ const clientEventId = this.resolveClientEventId(req);
3181
+ const effectiveStrictMode = await this.resolveEffectiveStrictMode(requestOptions);
2813
3182
  const stream = adapter.isStream(req);
2814
3183
  let capabilityConfig = this.getCachedCapabilityConfig(requestOptions);
2815
3184
  const userText = adapter.extractInput(req);
@@ -2818,16 +3187,19 @@ var AgentID = class {
2818
3187
  let createArgs = normalizedCreateArgs;
2819
3188
  let mapping = {};
2820
3189
  let shouldDeanonymize = false;
3190
+ let sdkConfigFetchMs = 0;
3191
+ let sdkLocalScanMs = 0;
2821
3192
  if (userText) {
2822
- await this.scanPromptInjection(userText, requestOptions);
2823
3193
  const prepared = await this.prepareInputForDispatch({
2824
3194
  input: userText,
2825
3195
  systemId,
2826
3196
  stream,
2827
- skipInjectionScan: true
3197
+ clientEventId
2828
3198
  }, requestOptions);
2829
3199
  capabilityConfig = prepared.capabilityConfig;
2830
3200
  maskedText = prepared.sanitizedInput;
3201
+ sdkConfigFetchMs = prepared.sdkConfigFetchMs ?? 0;
3202
+ sdkLocalScanMs = prepared.sdkLocalScanMs ?? 0;
2831
3203
  if (maskedText !== userText) {
2832
3204
  maskedReq = this.withMaskedOpenAIRequest(
2833
3205
  req,
@@ -2837,7 +3209,8 @@ var AgentID = class {
2837
3209
  nextCreateArgs[0] = maskedReq;
2838
3210
  createArgs = nextCreateArgs;
2839
3211
  }
2840
- if (!capabilityConfig.block_pii_leakage && this.piiMasking) {
3212
+ const effectivePiiMasking = this.resolveEffectivePiiMasking(capabilityConfig);
3213
+ if (!capabilityConfig.block_pii_leakage && effectivePiiMasking) {
2841
3214
  if (stream) {
2842
3215
  console.warn("AgentID: PII masking is disabled for streaming responses.");
2843
3216
  } else {
@@ -2860,19 +3233,54 @@ var AgentID = class {
2860
3233
  "AgentID: No user message found. Security guard requires string input."
2861
3234
  );
2862
3235
  }
2863
- const clientEventId = this.resolveClientEventId(req);
2864
3236
  const verdict = await this.guard({
2865
3237
  input: maskedText,
2866
3238
  system_id: systemId,
2867
3239
  model: adapter.getModelName(maskedReq),
2868
3240
  user_id: options.user_id,
2869
3241
  client_event_id: clientEventId,
2870
- client_capabilities: this.buildClientCapabilities("openai", false)
3242
+ expected_languages: expectedLanguages,
3243
+ client_capabilities: this.buildClientCapabilities(
3244
+ "openai",
3245
+ false,
3246
+ capabilityConfig
3247
+ )
2871
3248
  }, requestOptions);
3249
+ let localFallbackApplied = false;
3250
+ let localFallbackReason = null;
2872
3251
  if (!verdict.allowed) {
2873
- throw new SecurityBlockError(verdict.reason ?? "guard_denied");
3252
+ if (effectiveStrictMode && isGuardFailureEligibleForLocalFallback(verdict.reason)) {
3253
+ localFallbackApplied = true;
3254
+ localFallbackReason = verdict.reason ?? "guard_unreachable";
3255
+ if (sdkLocalScanMs === 0) {
3256
+ const fallback = await this.applyLocalPolicyChecks({
3257
+ input: maskedText,
3258
+ systemId,
3259
+ stream,
3260
+ capabilityConfig,
3261
+ apiKey: effectiveApiKey,
3262
+ clientEventId,
3263
+ sdkConfigFetchMs,
3264
+ runPromptInjectionCheck: true
3265
+ });
3266
+ maskedText = fallback.sanitizedInput;
3267
+ sdkLocalScanMs = fallback.sdkLocalScanMs;
3268
+ }
3269
+ } else {
3270
+ throw new SecurityBlockError(verdict.reason ?? "guard_denied");
3271
+ }
2874
3272
  }
2875
- const canonicalClientEventId = typeof verdict.client_event_id === "string" && isUuidLike(verdict.client_event_id) ? verdict.client_event_id : clientEventId;
3273
+ const currentRequestInput = adapter.extractInput(maskedReq);
3274
+ if (maskedText !== currentRequestInput) {
3275
+ maskedReq = this.withMaskedOpenAIRequest(
3276
+ req,
3277
+ maskedText
3278
+ );
3279
+ const nextCreateArgs = [...normalizedCreateArgs];
3280
+ nextCreateArgs[0] = maskedReq;
3281
+ createArgs = nextCreateArgs;
3282
+ }
3283
+ const canonicalClientEventId = typeof verdict.client_event_id === "string" && isUuidLike2(verdict.client_event_id) ? verdict.client_event_id : clientEventId;
2876
3284
  const guardEventId = typeof verdict.guard_event_id === "string" && verdict.guard_event_id.length > 0 ? verdict.guard_event_id : null;
2877
3285
  const guardLatencyMs = typeof verdict.guard_latency_ms === "number" && Number.isFinite(verdict.guard_latency_ms) ? Math.max(0, Math.trunc(verdict.guard_latency_ms)) : Math.max(0, Date.now() - pipelineStartedAt);
2878
3286
  const transparency = coerceTransparencyMetadata(verdict.transparency);
@@ -2921,16 +3329,31 @@ var AgentID = class {
2921
3329
  simulated_decision: verdict.simulated_decision ?? null,
2922
3330
  simulated_output_decision: isShadowMode && result.outputMasked ? "masked" : "allowed",
2923
3331
  response_streamed: true,
3332
+ sdk_local_fallback_applied: localFallbackApplied,
3333
+ sdk_local_fallback_reason: localFallbackReason,
2924
3334
  guard_latency_ms: guardLatencyMs,
2925
3335
  model_latency_ms: modelLatencyMs2,
2926
3336
  total_pipeline_latency_ms: totalPipelineLatencyMs2,
2927
3337
  guard_event_id: guardEventId,
2928
3338
  client_event_id: canonicalClientEventId,
2929
- transparency
3339
+ transparency,
3340
+ ...buildSdkTimingMetadata({
3341
+ sdkConfigFetchMs,
3342
+ sdkLocalScanMs,
3343
+ sdkGuardMs: guardLatencyMs
3344
+ })
2930
3345
  },
2931
- client_capabilities: this.buildClientCapabilities("openai", false)
3346
+ client_capabilities: this.buildClientCapabilities(
3347
+ "openai",
3348
+ false,
3349
+ capabilityConfig
3350
+ )
2932
3351
  }, requestOptions);
2933
3352
  if (!ingestResult.ok) {
3353
+ this.maybeRaiseStrictIngestDependencyError({
3354
+ strictMode: effectiveStrictMode,
3355
+ result: ingestResult
3356
+ });
2934
3357
  console.warn(
2935
3358
  `[AgentID] Stream ingest telemetry failed (status=${ingestResult.status ?? "network"}, reason=${ingestResult.reason ?? "unknown"}).`
2936
3359
  );
@@ -2972,22 +3395,37 @@ var AgentID = class {
2972
3395
  simulated_decision: verdict.simulated_decision ?? null,
2973
3396
  simulated_output_decision: isShadowMode && wrappedCompletion.outputMasked ? "masked" : "allowed",
2974
3397
  response_streamed: false,
3398
+ sdk_local_fallback_applied: localFallbackApplied,
3399
+ sdk_local_fallback_reason: localFallbackReason,
2975
3400
  guard_latency_ms: guardLatencyMs,
2976
3401
  model_latency_ms: modelLatencyMs,
2977
3402
  total_pipeline_latency_ms: totalPipelineLatencyMs,
2978
3403
  guard_event_id: guardEventId,
2979
3404
  client_event_id: canonicalClientEventId,
2980
- transparency
3405
+ transparency,
3406
+ ...buildSdkTimingMetadata({
3407
+ sdkConfigFetchMs,
3408
+ sdkLocalScanMs,
3409
+ sdkGuardMs: guardLatencyMs
3410
+ })
2981
3411
  },
2982
- client_capabilities: this.buildClientCapabilities("openai", false)
3412
+ client_capabilities: this.buildClientCapabilities(
3413
+ "openai",
3414
+ false,
3415
+ capabilityConfig
3416
+ )
2983
3417
  }, requestOptions);
2984
3418
  if (!ingestResult.ok) {
3419
+ this.maybeRaiseStrictIngestDependencyError({
3420
+ strictMode: effectiveStrictMode,
3421
+ result: ingestResult
3422
+ });
2985
3423
  console.warn(
2986
3424
  `[AgentID] Ingest telemetry failed (status=${ingestResult.status ?? "network"}, reason=${ingestResult.reason ?? "unknown"}).`
2987
3425
  );
2988
3426
  }
2989
3427
  }
2990
- if (!capabilityConfig.block_pii_leakage && this.piiMasking && shouldDeanonymize) {
3428
+ if (!capabilityConfig.block_pii_leakage && this.resolveEffectivePiiMasking(capabilityConfig) && shouldDeanonymize) {
2991
3429
  const deanon = this.pii.deanonymize(adapter.extractOutput(res), mapping);
2992
3430
  try {
2993
3431
  if (Array.isArray(res?.choices)) {
@@ -3030,5 +3468,6 @@ export {
3030
3468
  InjectionScanner,
3031
3469
  getInjectionScanner,
3032
3470
  SecurityBlockError,
3471
+ DependencyError,
3033
3472
  AgentID
3034
3473
  };