@agwab/pi-workflow 0.1.2 → 0.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.
Files changed (34) hide show
  1. package/README.md +7 -13
  2. package/dist/compiler.d.ts +2 -0
  3. package/dist/compiler.js +27 -2
  4. package/dist/engine.d.ts +2 -0
  5. package/dist/engine.js +3 -2
  6. package/dist/extension.js +201 -16
  7. package/dist/store.js +1 -0
  8. package/dist/types.d.ts +3 -0
  9. package/dist/workflow-progress-health.d.ts +37 -0
  10. package/dist/workflow-progress-health.js +296 -0
  11. package/dist/workflow-runtime.d.ts +6 -0
  12. package/dist/workflow-runtime.js +33 -10
  13. package/dist/workflow-view.d.ts +2 -0
  14. package/dist/workflow-view.js +97 -18
  15. package/dist/workflow-web-source.js +32 -14
  16. package/docs/usage.md +1 -1
  17. package/package.json +6 -6
  18. package/src/compiler.ts +41 -2
  19. package/src/engine.ts +7 -16
  20. package/src/extension.ts +254 -22
  21. package/src/store.ts +1 -0
  22. package/src/types.ts +4 -0
  23. package/src/workflow-progress-health.ts +461 -0
  24. package/src/workflow-runtime.ts +50 -13
  25. package/src/workflow-view.ts +186 -41
  26. package/src/workflow-web-source.ts +192 -69
  27. package/workflows/deep-research/helpers/claim-evidence-gate.mjs +111 -37
  28. package/workflows/deep-research/helpers/final-audit-packet.mjs +191 -14
  29. package/workflows/deep-research/helpers/normalize-input-packet.mjs +159 -50
  30. package/workflows/deep-research/helpers/render-executive.mjs +671 -37
  31. package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +624 -0
  32. package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +2 -0
  33. package/workflows/deep-research/schemas/deep-research-final-synthesis-control.schema.json +110 -0
  34. package/workflows/deep-research/spec.json +41 -11
@@ -1,5 +1,12 @@
1
1
  import { createHash } from "node:crypto";
2
- import { appendFile, mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
2
+ import {
3
+ appendFile,
4
+ mkdir,
5
+ readFile,
6
+ readdir,
7
+ rename,
8
+ writeFile,
9
+ } from "node:fs/promises";
3
10
  import { isIP } from "node:net";
4
11
  import { dirname, resolve } from "node:path";
5
12
 
@@ -171,7 +178,9 @@ export function normalizeWorkflowWebSecurityPolicy(
171
178
  };
172
179
  }
173
180
 
174
- export function isWorkflowWebSourceTool(tool: string): tool is WorkflowWebSourceTool {
181
+ export function isWorkflowWebSourceTool(
182
+ tool: string,
183
+ ): tool is WorkflowWebSourceTool {
175
184
  return (WORKFLOW_WEB_SOURCE_TOOLS as readonly string[]).includes(tool);
176
185
  }
177
186
 
@@ -202,7 +211,9 @@ export function consumeWorkflowWebVisibleBudget(
202
211
  export function validateWorkflowWebUrl(
203
212
  url: string,
204
213
  security: WorkflowWebSecurityPolicy = DEFAULT_WORKFLOW_WEB_SECURITY_POLICY,
205
- ): { ok: true; normalizedUrl: string; domain: string } | { ok: false; reason: string } {
214
+ ):
215
+ | { ok: true; normalizedUrl: string; domain: string }
216
+ | { ok: false; reason: string } {
206
217
  let parsed: URL;
207
218
  try {
208
219
  parsed = new URL(url);
@@ -261,8 +272,8 @@ function sourceUrlDisplayCacheKey(url: string): string {
261
272
  if (parsed.pathname.length > 1 && parsed.pathname.endsWith("/")) {
262
273
  parsed.pathname = parsed.pathname.slice(0, -1);
263
274
  }
264
- const sortedParams = [...parsed.searchParams.entries()].sort(([left], [right]) =>
265
- left.localeCompare(right),
275
+ const sortedParams = [...parsed.searchParams.entries()].sort(
276
+ ([left], [right]) => left.localeCompare(right),
266
277
  );
267
278
  parsed.search = "";
268
279
  for (const [key, value] of sortedParams) {
@@ -283,8 +294,8 @@ function canonicalUrlForCache(url: string): string {
283
294
  if (parsed.pathname.length > 1 && parsed.pathname.endsWith("/")) {
284
295
  parsed.pathname = parsed.pathname.slice(0, -1);
285
296
  }
286
- const sortedParams = [...parsed.searchParams.entries()].sort(([left], [right]) =>
287
- left.localeCompare(right),
297
+ const sortedParams = [...parsed.searchParams.entries()].sort(
298
+ ([left], [right]) => left.localeCompare(right),
288
299
  );
289
300
  parsed.search = "";
290
301
  for (const [key, value] of sortedParams) {
@@ -392,13 +403,25 @@ export async function findWorkflowWebSourceByUrl(
392
403
  const targetDisplayKey = sourceUrlDisplayCacheKey(redactedUrl);
393
404
  const index = await readWorkflowWebSourceIndex(config);
394
405
  const existing = [...index.sources].reverse().find((entry) => {
395
- return sourceIndexEntryMatchesUrl(entry, url, redactedUrl, targetKey, targetDisplayKey);
406
+ return sourceIndexEntryMatchesUrl(
407
+ entry,
408
+ url,
409
+ redactedUrl,
410
+ targetKey,
411
+ targetDisplayKey,
412
+ );
396
413
  });
397
414
  if (existing) {
398
415
  const source = await readWorkflowWebSource(config, existing.sourceRef);
399
416
  if (source) return source;
400
417
  }
401
- return findWorkflowWebSourceByUrlFromSources(config, url, redactedUrl, targetKey, targetDisplayKey);
418
+ return findWorkflowWebSourceByUrlFromSources(
419
+ config,
420
+ url,
421
+ redactedUrl,
422
+ targetKey,
423
+ targetDisplayKey,
424
+ );
402
425
  }
403
426
 
404
427
  function sourceIndexEntryMatchesUrl(
@@ -409,7 +432,11 @@ function sourceIndexEntryMatchesUrl(
409
432
  targetDisplayKey: string,
410
433
  ): boolean {
411
434
  if (entry.urlKey) return entry.urlKey === targetKey;
412
- if (redactedUrlIdentityUnsafe(redactedUrl) || redactedUrlIdentityUnsafe(entry.redactedUrl) || redactedUrlIdentityUnsafe(entry.url)) {
435
+ if (
436
+ redactedUrlIdentityUnsafe(redactedUrl) ||
437
+ redactedUrlIdentityUnsafe(entry.redactedUrl) ||
438
+ redactedUrlIdentityUnsafe(entry.url)
439
+ ) {
413
440
  return false;
414
441
  }
415
442
  return (
@@ -421,7 +448,12 @@ function sourceIndexEntryMatchesUrl(
421
448
  }
422
449
 
423
450
  function redactedUrlIdentityUnsafe(url: string): boolean {
424
- return /REDACTED/.test(url) || /[?&#][^=]*(?:token|secret|password|signature|sig|key|auth|session|credential)[^=]*=/i.test(url);
451
+ return (
452
+ /REDACTED/.test(url) ||
453
+ /[?&#][^=]*(?:token|secret|password|signature|sig|key|auth|session|credential)[^=]*=/i.test(
454
+ url,
455
+ )
456
+ );
425
457
  }
426
458
 
427
459
  async function findWorkflowWebSourceByUrlFromSources(
@@ -446,7 +478,11 @@ async function findWorkflowWebSourceByUrlFromSources(
446
478
  if (source.urlKey === targetKey) return source;
447
479
  continue;
448
480
  }
449
- if (redactedUrlIdentityUnsafe(redactedUrl) || redactedUrlIdentityUnsafe(source.redactedUrl) || redactedUrlIdentityUnsafe(source.url)) {
481
+ if (
482
+ redactedUrlIdentityUnsafe(redactedUrl) ||
483
+ redactedUrlIdentityUnsafe(source.redactedUrl) ||
484
+ redactedUrlIdentityUnsafe(source.url)
485
+ ) {
450
486
  continue;
451
487
  }
452
488
  if (
@@ -522,19 +558,21 @@ export function readWorkflowWebSourceSnippet(options: {
522
558
  maxChars: number;
523
559
  budget: WorkflowWebVisibleBudget;
524
560
  }): WorkflowWebSourceReadResult {
525
- return readWorkflowWebSourceSnippets({
526
- source: options.source,
527
- requests: [
528
- {
529
- query: options.query,
530
- claim: options.claim,
531
- terms: options.terms,
532
- maxChars: options.maxChars,
533
- },
534
- ],
535
- maxChars: options.maxChars,
536
- budget: options.budget,
537
- })[0] ?? { status: "not_found", visibleChars: 0 };
561
+ return (
562
+ readWorkflowWebSourceSnippets({
563
+ source: options.source,
564
+ requests: [
565
+ {
566
+ query: options.query,
567
+ claim: options.claim,
568
+ terms: options.terms,
569
+ maxChars: options.maxChars,
570
+ },
571
+ ],
572
+ maxChars: options.maxChars,
573
+ budget: options.budget,
574
+ })[0] ?? { status: "not_found", visibleChars: 0 }
575
+ );
538
576
  }
539
577
 
540
578
  export function readWorkflowWebSourceSnippets(options: {
@@ -573,10 +611,13 @@ export function extractTextFromToolResult(result: unknown): string {
573
611
  .join("\n\n");
574
612
  }
575
613
 
576
- export function extractTitleFromToolResult(result: unknown): string | undefined {
614
+ export function extractTitleFromToolResult(
615
+ result: unknown,
616
+ ): string | undefined {
577
617
  if (!isRecord(result)) return undefined;
578
618
  const details = result.details;
579
- if (isRecord(details) && typeof details.title === "string") return details.title;
619
+ if (isRecord(details) && typeof details.title === "string")
620
+ return details.title;
580
621
  const text = extractTextFromToolResult(result);
581
622
  const heading = text.match(/^#\s+(.+)$/m)?.[1]?.trim();
582
623
  return heading ? heading.slice(0, 200) : undefined;
@@ -660,7 +701,9 @@ function shouldKeepFragmentForCache(hash: string): boolean {
660
701
  return raw.startsWith("/") || raw.startsWith("!") || raw.includes("?");
661
702
  }
662
703
 
663
- function sourceToIndexEntry(source: WorkflowWebSource): WorkflowWebSourceIndexEntry {
704
+ function sourceToIndexEntry(
705
+ source: WorkflowWebSource,
706
+ ): WorkflowWebSourceIndexEntry {
664
707
  return {
665
708
  sourceRef: source.sourceRef,
666
709
  createdAt: source.createdAt,
@@ -717,7 +760,10 @@ function readWorkflowWebSourceSnippetWithCache(options: {
717
760
  });
718
761
  }
719
762
  }
720
- const termNeedles = prepareTermNeedles(options.request.terms, options.request.claim);
763
+ const termNeedles = prepareTermNeedles(
764
+ options.request.terms,
765
+ options.request.claim,
766
+ );
721
767
  if (termNeedles.length === 0) return { status: "not_found", visibleChars: 0 };
722
768
  return snippetForTerms({
723
769
  text: options.source.text,
@@ -736,10 +782,19 @@ function snippetForTerms(options: {
736
782
  budget: WorkflowWebVisibleBudget;
737
783
  }): WorkflowWebSourceReadResult {
738
784
  const needles = options.terms
739
- .map((term) => ({ raw: term, normalized: normalizeForSearch(term).normalized }))
785
+ .map((term) => ({
786
+ raw: term,
787
+ normalized: normalizeForSearch(term).normalized,
788
+ }))
740
789
  .filter((term) => term.normalized.length > 0);
741
790
  if (needles.length === 0) return { status: "not_found", visibleChars: 0 };
742
- const candidates: Array<{ start: number; end: number; matchedTerms: string[]; missingTerms: string[]; score: number }> = [];
791
+ const candidates: Array<{
792
+ start: number;
793
+ end: number;
794
+ matchedTerms: string[];
795
+ missingTerms: string[];
796
+ score: number;
797
+ }> = [];
743
798
  for (const needle of needles) {
744
799
  let fromIndex = 0;
745
800
  let occurrenceCount = 0;
@@ -755,7 +810,9 @@ function snippetForTerms(options: {
755
810
  normalizedIndex + Math.max(0, needle.normalized.length - 1),
756
811
  );
757
812
  const end = (options.normalizedSource.map[endMapIndex] ?? start) + 1;
758
- candidates.push(scoreTermWindow(options.text, start, end, options.maxChars, needles));
813
+ candidates.push(
814
+ scoreTermWindow(options.text, start, end, options.maxChars, needles),
815
+ );
759
816
  fromIndex = normalizedIndex + Math.max(1, needle.normalized.length);
760
817
  occurrenceCount += 1;
761
818
  }
@@ -766,7 +823,11 @@ function snippetForTerms(options: {
766
823
  return right.matchedTerms.length - left.matchedTerms.length;
767
824
  })[0]!;
768
825
  const raw = redactInlineSecrets(options.text.slice(best.start, best.end));
769
- const consumed = consumeWorkflowWebVisibleBudget(options.budget, raw, options.maxChars);
826
+ const consumed = consumeWorkflowWebVisibleBudget(
827
+ options.budget,
828
+ raw,
829
+ options.maxChars,
830
+ );
770
831
  return {
771
832
  status: "matched",
772
833
  matchType: "terms",
@@ -787,7 +848,13 @@ function scoreTermWindow(
787
848
  matchEnd: number,
788
849
  maxChars: number,
789
850
  terms: Array<{ raw: string; normalized: string }>,
790
- ): { start: number; end: number; matchedTerms: string[]; missingTerms: string[]; score: number } {
851
+ ): {
852
+ start: number;
853
+ end: number;
854
+ matchedTerms: string[];
855
+ missingTerms: string[];
856
+ score: number;
857
+ } {
791
858
  const center = Math.floor((matchStart + matchEnd) / 2);
792
859
  const start = Math.max(0, center - Math.floor(maxChars / 2));
793
860
  const end = Math.min(text.length, start + maxChars);
@@ -799,7 +866,10 @@ function scoreTermWindow(
799
866
  .filter((term) => !windowNorm.includes(term.normalized))
800
867
  .map((term) => term.raw);
801
868
  const occurrenceScore = terms.reduce((score, term) => {
802
- return score + (windowNorm.includes(term.normalized) ? term.normalized.length : 0);
869
+ return (
870
+ score +
871
+ (windowNorm.includes(term.normalized) ? term.normalized.length : 0)
872
+ );
803
873
  }, 0);
804
874
  return {
805
875
  start,
@@ -810,19 +880,27 @@ function scoreTermWindow(
810
880
  };
811
881
  }
812
882
 
813
- function prepareTermNeedles(terms: string[] | undefined, claim: string | undefined): string[] {
814
- const explicitTerms = dedupeStrings((terms ?? []).map((term) => term.trim()).filter(Boolean));
883
+ function prepareTermNeedles(
884
+ terms: string[] | undefined,
885
+ claim: string | undefined,
886
+ ): string[] {
887
+ const explicitTerms = dedupeStrings(
888
+ (terms ?? []).map((term) => term.trim()).filter(Boolean),
889
+ );
815
890
  if (explicitTerms.length > 0) return explicitTerms.slice(0, 16);
816
891
  if (!claim?.trim()) return [];
817
892
  return extractClaimTerms(claim).slice(0, 16);
818
893
  }
819
894
 
820
895
  function extractClaimTerms(claim: string): string[] {
821
- const tokens = claim
822
- .match(/[\p{L}\p{N}][\p{L}\p{N}._/-]{2,}/gu)
823
- ?.map((token) => token.toLowerCase()) ?? [];
896
+ const tokens =
897
+ claim
898
+ .match(/[\p{L}\p{N}][\p{L}\p{N}._/-]{2,}/gu)
899
+ ?.map((token) => token.toLowerCase()) ?? [];
824
900
  const filtered = tokens.filter((token) => !SOURCE_READ_STOPWORDS.has(token));
825
- return dedupeStrings(filtered).sort((left, right) => right.length - left.length);
901
+ return dedupeStrings(filtered).sort(
902
+ (left, right) => right.length - left.length,
903
+ );
826
904
  }
827
905
 
828
906
  function dedupeStrings(values: string[]): string[] {
@@ -889,7 +967,10 @@ function snippetForMatch(options: {
889
967
  const slack = Math.max(0, options.maxChars - matchLength);
890
968
  const before = Math.floor(slack / 2);
891
969
  const snippetStart = Math.max(0, options.start - before);
892
- const snippetEnd = Math.min(options.text.length, snippetStart + options.maxChars);
970
+ const snippetEnd = Math.min(
971
+ options.text.length,
972
+ snippetStart + options.maxChars,
973
+ );
893
974
  const raw = redactInlineSecrets(options.text.slice(snippetStart, snippetEnd));
894
975
  const consumed = consumeWorkflowWebVisibleBudget(
895
976
  options.budget,
@@ -906,7 +987,10 @@ function snippetForMatch(options: {
906
987
  };
907
988
  }
908
989
 
909
- function normalizeForSearch(text: string): { normalized: string; map: number[] } {
990
+ function normalizeForSearch(text: string): {
991
+ normalized: string;
992
+ map: number[];
993
+ } {
910
994
  let normalized = "";
911
995
  const map: number[] = [];
912
996
  let previousWhitespace = false;
@@ -945,8 +1029,13 @@ async function readWorkflowWebSourceIndexFile(
945
1029
  config: WorkflowWebSourceCacheConfig,
946
1030
  ): Promise<WorkflowWebSourceIndex> {
947
1031
  try {
948
- const parsed = JSON.parse(await readFile(indexPath(config), "utf8")) as unknown;
949
- if (!isRecord(parsed) || parsed.schema !== WORKFLOW_WEB_SOURCE_INDEX_SCHEMA) {
1032
+ const parsed = JSON.parse(
1033
+ await readFile(indexPath(config), "utf8"),
1034
+ ) as unknown;
1035
+ if (
1036
+ !isRecord(parsed) ||
1037
+ parsed.schema !== WORKFLOW_WEB_SOURCE_INDEX_SCHEMA
1038
+ ) {
950
1039
  throw new Error("invalid index");
951
1040
  }
952
1041
  const sources = Array.isArray(parsed.sources)
@@ -957,7 +1046,10 @@ async function readWorkflowWebSourceIndexFile(
957
1046
  : [];
958
1047
  return {
959
1048
  schema: WORKFLOW_WEB_SOURCE_INDEX_SCHEMA,
960
- updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : new Date().toISOString(),
1049
+ updatedAt:
1050
+ typeof parsed.updatedAt === "string"
1051
+ ? parsed.updatedAt
1052
+ : new Date().toISOString(),
961
1053
  runId: typeof parsed.runId === "string" ? parsed.runId : config.runId,
962
1054
  sources: mergeSourceIndexEntries(sources),
963
1055
  };
@@ -998,7 +1090,11 @@ async function readWorkflowWebSourceIndexLedger(
998
1090
  if (!line.trim()) continue;
999
1091
  try {
1000
1092
  const parsed = JSON.parse(line) as unknown;
1001
- if (!isRecord(parsed) || parsed.schema !== WORKFLOW_WEB_SOURCE_INDEX_EVENT_SCHEMA) continue;
1093
+ if (
1094
+ !isRecord(parsed) ||
1095
+ parsed.schema !== WORKFLOW_WEB_SOURCE_INDEX_EVENT_SCHEMA
1096
+ )
1097
+ continue;
1002
1098
  const entry = sourceIndexEntryFromUnknown(parsed.entry);
1003
1099
  if (entry) entries.push(entry);
1004
1100
  } catch {
@@ -1008,9 +1104,15 @@ async function readWorkflowWebSourceIndexLedger(
1008
1104
  return entries;
1009
1105
  }
1010
1106
 
1011
- function sourceIndexEntryFromUnknown(value: unknown): WorkflowWebSourceIndexEntry | undefined {
1107
+ function sourceIndexEntryFromUnknown(
1108
+ value: unknown,
1109
+ ): WorkflowWebSourceIndexEntry | undefined {
1012
1110
  if (!isRecord(value)) return undefined;
1013
- if (typeof value.sourceRef !== "string" || !isWorkflowWebSourceRef(value.sourceRef)) return undefined;
1111
+ if (
1112
+ typeof value.sourceRef !== "string" ||
1113
+ !isWorkflowWebSourceRef(value.sourceRef)
1114
+ )
1115
+ return undefined;
1014
1116
  if (typeof value.createdAt !== "string") return undefined;
1015
1117
  if (typeof value.url !== "string") return undefined;
1016
1118
  if (typeof value.redactedUrl !== "string") return undefined;
@@ -1036,7 +1138,9 @@ function mergeSourceIndexEntries(
1036
1138
  ): WorkflowWebSourceIndexEntry[] {
1037
1139
  const bySourceRef = new Map<string, WorkflowWebSourceIndexEntry>();
1038
1140
  for (const entry of entries) bySourceRef.set(entry.sourceRef, entry);
1039
- return [...bySourceRef.values()].sort((left, right) => left.createdAt.localeCompare(right.createdAt));
1141
+ return [...bySourceRef.values()].sort((left, right) =>
1142
+ left.createdAt.localeCompare(right.createdAt),
1143
+ );
1040
1144
  }
1041
1145
 
1042
1146
  function emptyWorkflowWebSourceIndex(
@@ -1101,27 +1205,38 @@ function nonPublicIpReason(address: string): string | undefined {
1101
1205
  if (hexMapped) {
1102
1206
  const high = Number.parseInt(hexMapped[1]!, 16);
1103
1207
  const low = Number.parseInt(hexMapped[2]!, 16);
1104
- return nonPublicIpReason(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
1208
+ return nonPublicIpReason(
1209
+ `${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`,
1210
+ );
1105
1211
  }
1106
1212
  if (isIP(lower) === 4) {
1107
1213
  const parts = lower.split(".").map((part) => Number(part));
1108
- if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return "non_public_ip_blocked";
1214
+ if (
1215
+ parts.length !== 4 ||
1216
+ parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)
1217
+ )
1218
+ return "non_public_ip_blocked";
1109
1219
  const [a, b, c, d] = parts as [number, number, number, number];
1110
- if (a === 0 || a === 10 || a === 127 || a >= 224) return "non_public_ip_blocked";
1220
+ if (a === 0 || a === 10 || a === 127 || a >= 224)
1221
+ return "non_public_ip_blocked";
1111
1222
  if (a === 100 && b >= 64 && b <= 127) return "non_public_ip_blocked";
1112
1223
  if (a === 169 && b === 254) return "non_public_ip_blocked";
1113
1224
  if (a === 172 && b >= 16 && b <= 31) return "non_public_ip_blocked";
1114
1225
  if (a === 192 && b === 168) return "non_public_ip_blocked";
1115
- if (a === 192 && b === 0 && (c === 0 || c === 2)) return "non_public_ip_blocked";
1226
+ if (a === 192 && b === 0 && (c === 0 || c === 2))
1227
+ return "non_public_ip_blocked";
1116
1228
  if (a === 198 && (b === 18 || b === 19)) return "non_public_ip_blocked";
1117
1229
  if (a === 198 && b === 51 && c === 100) return "non_public_ip_blocked";
1118
1230
  if (a === 203 && b === 0 && c === 113) return "non_public_ip_blocked";
1119
- if (a === 255 && b === 255 && c === 255 && d === 255) return "non_public_ip_blocked";
1231
+ if (a === 255 && b === 255 && c === 255 && d === 255)
1232
+ return "non_public_ip_blocked";
1120
1233
  }
1121
1234
  if (isIP(lower) === 6) {
1122
1235
  if (lower === "::" || lower === "::1") return "non_public_ip_blocked";
1123
- if (lower.startsWith("fc") || lower.startsWith("fd")) return "non_public_ip_blocked";
1124
- if (lower.startsWith("fe80") || lower.startsWith("ff")) return "non_public_ip_blocked";
1236
+ if (lower.startsWith("fc") || lower.startsWith("fd"))
1237
+ return "non_public_ip_blocked";
1238
+ if (lower.startsWith("fe80") || lower.startsWith("ff"))
1239
+ return "non_public_ip_blocked";
1125
1240
  if (lower.startsWith("2001:db8")) return "non_public_ip_blocked";
1126
1241
  }
1127
1242
  return undefined;
@@ -1131,13 +1246,18 @@ function redactRecordForModel(
1131
1246
  value: Record<string, unknown>,
1132
1247
  ): Record<string, unknown> {
1133
1248
  return Object.fromEntries(
1134
- Object.entries(value).map(([key, item]) => [key, redactValueForModel(item)]),
1249
+ Object.entries(value).map(([key, item]) => [
1250
+ key,
1251
+ redactValueForModel(item),
1252
+ ]),
1135
1253
  );
1136
1254
  }
1137
1255
 
1138
1256
  function redactValueForModel(value: unknown): unknown {
1139
- if (typeof value === "string") return redactInlineSecrets(sanitizeUrlMaybe(value));
1140
- if (Array.isArray(value)) return value.map((item) => redactValueForModel(item));
1257
+ if (typeof value === "string")
1258
+ return redactInlineSecrets(sanitizeUrlMaybe(value));
1259
+ if (Array.isArray(value))
1260
+ return value.map((item) => redactValueForModel(item));
1141
1261
  if (!isRecord(value)) return value;
1142
1262
  return redactRecordForModel(value);
1143
1263
  }
@@ -1147,15 +1267,18 @@ function sanitizeUrlMaybe(value: string): string {
1147
1267
  }
1148
1268
 
1149
1269
  function redactInlineSecrets(value: string): string {
1150
- const withSanitizedUrls = value.replace(/https?:\/\/[^\s)\]}>"']+/gi, (match) => {
1151
- const trailing = match.match(/[.,;:!?]+$/)?.[0] ?? "";
1152
- const core = trailing ? match.slice(0, -trailing.length) : match;
1153
- try {
1154
- return `${sanitizeParsedUrlForModel(new URL(core))}${trailing}`;
1155
- } catch {
1156
- return match;
1157
- }
1158
- });
1270
+ const withSanitizedUrls = value.replace(
1271
+ /https?:\/\/[^\s)\]}>"']+/gi,
1272
+ (match) => {
1273
+ const trailing = match.match(/[.,;:!?]+$/)?.[0] ?? "";
1274
+ const core = trailing ? match.slice(0, -trailing.length) : match;
1275
+ try {
1276
+ return `${sanitizeParsedUrlForModel(new URL(core))}${trailing}`;
1277
+ } catch {
1278
+ return match;
1279
+ }
1280
+ },
1281
+ );
1159
1282
  return redactInlineSecretsNoUrls(withSanitizedUrls);
1160
1283
  }
1161
1284