@agent-native/dispatch 0.15.2 → 0.15.4

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 (43) hide show
  1. package/dist/actions/delete-staged-dataset.d.ts +3 -1
  2. package/dist/actions/delete-staged-dataset.js +3 -26
  3. package/dist/actions/delete-staged-dataset.js.map +1 -1
  4. package/dist/actions/delete-workspace-resource.d.ts +13 -13
  5. package/dist/actions/list-staged-datasets.d.ts +1 -1
  6. package/dist/actions/list-staged-datasets.js +3 -28
  7. package/dist/actions/list-staged-datasets.js.map +1 -1
  8. package/dist/actions/provider-api-catalog.d.ts +6 -4
  9. package/dist/actions/provider-api-catalog.js +3 -17
  10. package/dist/actions/provider-api-catalog.js.map +1 -1
  11. package/dist/actions/provider-api-docs.d.ts +21 -2
  12. package/dist/actions/provider-api-docs.js +6 -51
  13. package/dist/actions/provider-api-docs.js.map +1 -1
  14. package/dist/actions/provider-api-register.d.ts +39 -15
  15. package/dist/actions/provider-api-register.js +4 -193
  16. package/dist/actions/provider-api-register.js.map +1 -1
  17. package/dist/actions/provider-api-request.d.ts +34 -2
  18. package/dist/actions/provider-api-request.js +6 -171
  19. package/dist/actions/provider-api-request.js.map +1 -1
  20. package/dist/actions/query-staged-dataset.d.ts +18 -1
  21. package/dist/actions/query-staged-dataset.d.ts.map +1 -1
  22. package/dist/actions/query-staged-dataset.js +3 -100
  23. package/dist/actions/query-staged-dataset.js.map +1 -1
  24. package/dist/actions/upsert-destination.d.ts +12 -12
  25. package/dist/components/layout/Layout.d.ts.map +1 -1
  26. package/dist/components/layout/Layout.js +26 -64
  27. package/dist/components/layout/Layout.js.map +1 -1
  28. package/package.json +4 -3
  29. package/src/actions/delete-staged-dataset.ts +3 -33
  30. package/src/actions/list-staged-datasets.ts +3 -30
  31. package/src/actions/provider-api-audit.spec.ts +2 -3
  32. package/src/actions/provider-api-catalog.ts +10 -23
  33. package/src/actions/provider-api-docs.ts +19 -68
  34. package/src/actions/provider-api-register.ts +5 -233
  35. package/src/actions/provider-api-request.ts +19 -232
  36. package/src/actions/query-staged-dataset.ts +3 -119
  37. package/src/components/layout/Layout.spec.tsx +7 -11
  38. package/src/components/layout/Layout.tsx +42 -145
  39. package/dist/actions/provider-api-audit.d.ts +0 -7
  40. package/dist/actions/provider-api-audit.d.ts.map +0 -1
  41. package/dist/actions/provider-api-audit.js +0 -74
  42. package/dist/actions/provider-api-audit.js.map +0 -1
  43. package/src/actions/provider-api-audit.ts +0 -88
@@ -1,126 +1,10 @@
1
- import { defineAction } from "@agent-native/core";
2
- import { runAggregateQuery } from "@agent-native/core/provider-api/staged-datasets-aggregate";
3
- import {
4
- getStagedDatasetMeta,
5
- getStagedDatasetRows,
6
- } from "@agent-native/core/provider-api/staged-datasets-store";
7
- import { getCredentialContext } from "@agent-native/core/server/request-context";
8
- import { z } from "zod";
1
+ import { createQueryStagedDatasetAction } from "@agent-native/core/provider-api/actions/staged-datasets";
9
2
 
10
3
  import { DISPATCH_APP_ID } from "../server/lib/provider-api.js";
11
4
 
12
- const WhereSchema = z.object({
13
- column: z.string().min(1),
14
- op: z.enum([
15
- "equals",
16
- "not_equals",
17
- "contains",
18
- "not_contains",
19
- "gt",
20
- "gte",
21
- "lt",
22
- "lte",
23
- "exists",
24
- "not_exists",
25
- ]),
26
- value: z.unknown().optional(),
27
- });
28
-
29
- const AggregateFieldSchema = z.object({
30
- column: z.string().min(1).describe("Column to aggregate."),
31
- op: z
32
- .enum(["sum", "avg", "count", "min", "max", "count_distinct"])
33
- .describe("Aggregation function."),
34
- as: z
35
- .string()
36
- .optional()
37
- .describe("Output column name. Default: {op}_{column}."),
38
- });
39
-
40
- export default defineAction({
5
+ export default createQueryStagedDatasetAction({
6
+ appId: DISPATCH_APP_ID,
41
7
  description:
42
8
  "Run a filter/aggregate/project query over a staged dataset previously written by provider-api-request (stageAs). Use after staging provider records, messages, documents, issues, events, or search results to count, group, filter, or project rows without re-fetching provider APIs.",
43
- schema: z.object({
44
- datasetId: z
45
- .string()
46
- .min(1)
47
- .describe(
48
- "Dataset id from provider-api-request stageAs result, or from list-staged-datasets.",
49
- ),
50
- where: z
51
- .array(WhereSchema)
52
- .optional()
53
- .describe(
54
- "Row-level filters (AND). Ops: equals, not_equals, contains, not_contains, gt, gte, lt, lte, exists, not_exists.",
55
- ),
56
- groupBy: z
57
- .array(z.string().min(1))
58
- .optional()
59
- .describe(
60
- "Column(s) to group by. Omit for a single aggregate over all rows.",
61
- ),
62
- aggregate: z
63
- .array(AggregateFieldSchema)
64
- .optional()
65
- .describe(
66
- "Aggregation operations. When set, non-group columns are aggregated. Omit to return raw rows.",
67
- ),
68
- select: z
69
- .array(z.string().min(1))
70
- .optional()
71
- .describe("Column projection when aggregate is empty."),
72
- orderBy: z.string().optional().describe("Sort output by this column."),
73
- orderDir: z
74
- .enum(["asc", "desc"])
75
- .optional()
76
- .describe("Sort direction (default asc)."),
77
- limit: z.coerce
78
- .number()
79
- .int()
80
- .min(1)
81
- .max(10_000)
82
- .optional()
83
- .describe("Maximum rows to return (default all, max 10000)."),
84
- }),
85
9
  http: false,
86
- readOnly: true,
87
- run: async (args) => {
88
- const ctx = getCredentialContext();
89
- if (!ctx) {
90
- throw new Error("No authenticated context for query-staged-dataset.");
91
- }
92
-
93
- const meta = await getStagedDatasetMeta({
94
- id: args.datasetId,
95
- appId: DISPATCH_APP_ID,
96
- ownerEmail: ctx.userEmail,
97
- });
98
- if (!meta) {
99
- throw new Error(
100
- `Dataset ${args.datasetId} not found (or belongs to a different owner/app).`,
101
- );
102
- }
103
-
104
- const rows = await getStagedDatasetRows({
105
- id: args.datasetId,
106
- appId: DISPATCH_APP_ID,
107
- ownerEmail: ctx.userEmail,
108
- });
109
-
110
- const result = runAggregateQuery(rows, {
111
- where: args.where,
112
- groupBy: args.groupBy,
113
- aggregate: args.aggregate,
114
- select: args.select,
115
- orderBy: args.orderBy,
116
- orderDir: args.orderDir,
117
- limit: args.limit,
118
- });
119
-
120
- return {
121
- dataset: { id: meta.id, name: meta.name, totalRows: meta.rowCount },
122
- rowCount: result.length,
123
- rows: result,
124
- };
125
- },
126
10
  });
@@ -168,7 +168,7 @@ describe("Dispatch NavContent", () => {
168
168
  expect(lists[0].querySelector("a")?.className).toContain("h-8 w-8");
169
169
  });
170
170
 
171
- it("uses the quieter Analytics-style chat history and retains thread actions", async () => {
171
+ it("uses the shared chat history rail and retains thread actions", async () => {
172
172
  await act(async () => {
173
173
  root.render(
174
174
  <MemoryRouter initialEntries={["/chat/active-thread"]}>
@@ -184,18 +184,14 @@ describe("Dispatch NavContent", () => {
184
184
  expect(container.textContent).toContain("Earlier Dispatch work");
185
185
  expect(container.textContent).toContain("New chat");
186
186
  expect(container.textContent).toContain("5m");
187
- const age = [...container.querySelectorAll("time")].find(
187
+ const age = [...container.querySelectorAll("span")].find(
188
188
  (element) => element.textContent === "5m",
189
189
  );
190
- expect(age?.className).toContain("w-8");
191
- expect(age?.className).toContain("shrink-0");
192
- expect(age?.className).toContain("whitespace-nowrap");
193
- expect(age?.className).toContain("tabular-nums");
194
- expect(
195
- [...container.querySelectorAll("div")].some((element) =>
196
- element.className.includes("group/item"),
197
- ),
198
- ).toBe(true);
190
+ expect(age?.className).toContain("an-chat-history-row__timestamp");
191
+ const historyList = container.querySelector(
192
+ '[data-agent-native="chat-history-list"]',
193
+ );
194
+ expect(historyList?.className).toContain("an-chat-history--rail");
199
195
  expect(
200
196
  container.querySelector('img[src="/agent-native-icon-light.svg"]')
201
197
  ?.parentElement?.className,
@@ -13,6 +13,10 @@ import { useActionQuery } from "@agent-native/core/client/hooks";
13
13
  import { useT } from "@agent-native/core/client/i18n";
14
14
  import { InvitationBanner, OrgSwitcher } from "@agent-native/core/client/org";
15
15
  import { FeedbackButton } from "@agent-native/core/client/ui";
16
+ import {
17
+ ChatHistoryList,
18
+ type ChatHistoryItem,
19
+ } from "@agent-native/toolkit/chat-history";
16
20
  import {
17
21
  IconActivity,
18
22
  IconArrowUpRight,
@@ -22,8 +26,6 @@ import {
22
26
  IconBrandTelegram,
23
27
  IconKey,
24
28
  IconChevronDown,
25
- IconDots,
26
- IconEdit,
27
29
  IconLayersSubtract,
28
30
  IconMessageQuestion,
29
31
  IconMessages,
@@ -42,10 +44,8 @@ import {
42
44
  import {
43
45
  useEffect,
44
46
  useMemo,
45
- useRef,
46
47
  useState,
47
48
  type ComponentType,
48
- type FormEvent,
49
49
  type ReactNode,
50
50
  } from "react";
51
51
  import { NavLink, useLocation, useNavigate } from "react-router";
@@ -57,7 +57,6 @@ import {
57
57
  DropdownMenuItem,
58
58
  DropdownMenuTrigger,
59
59
  } from "../ui/dropdown-menu";
60
- import { Input } from "../ui/input";
61
60
  import { Sheet, SheetContent, SheetDescription, SheetTitle } from "../ui/sheet";
62
61
  import { Skeleton } from "../ui/skeleton";
63
62
  import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
@@ -348,10 +347,6 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
348
347
  renameThread,
349
348
  refreshThreads,
350
349
  } = useChatThreads(undefined, "dispatch", undefined, { autoCreate: false });
351
- const [renamingThreadId, setRenamingThreadId] = useState<string | null>(null);
352
- const [renameDraft, setRenameDraft] = useState("");
353
- const renameInputRef = useRef<HTMLInputElement | null>(null);
354
- const committingRenameRef = useRef(false);
355
350
 
356
351
  const visibleThreads = useMemo(
357
352
  () =>
@@ -363,6 +358,22 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
363
358
  .slice(0, 8),
364
359
  [activeThreadId, threads],
365
360
  );
361
+ const localPathname = localDispatchPath(location.pathname);
362
+ const displayedActiveThreadId =
363
+ threadIdFromPath(localPathname) ??
364
+ (localPathname === "/chat" ? null : activeThreadId);
365
+ const chatItems: ChatHistoryItem[] = visibleThreads.map((thread) => {
366
+ const title = threadTitle(thread, t("dispatch.sidebar.newChat"));
367
+ return {
368
+ id: thread.id,
369
+ title: <span title={title}>{title}</span>,
370
+ titleText: title,
371
+ timestamp:
372
+ thread.id === displayedActiveThreadId
373
+ ? ""
374
+ : formatThreadAge(threadUpdatedAt(thread)),
375
+ };
376
+ });
366
377
 
367
378
  useEffect(() => {
368
379
  const refresh = () => refreshThreads();
@@ -383,14 +394,6 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
383
394
  };
384
395
  }, [refreshThreads]);
385
396
 
386
- useEffect(() => {
387
- if (!renamingThreadId) return;
388
- requestAnimationFrame(() => {
389
- renameInputRef.current?.focus();
390
- renameInputRef.current?.select();
391
- });
392
- }, [renamingThreadId]);
393
-
394
397
  function openThread(threadId: string, options?: { isNew?: boolean }) {
395
398
  switchThread(threadId);
396
399
  navigateWithAgentChatViewTransition(
@@ -414,35 +417,6 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
414
417
  if (threadId) openThread(threadId, { isNew: true });
415
418
  }
416
419
 
417
- function startRenameThread(thread: ChatThreadSummary) {
418
- committingRenameRef.current = false;
419
- setRenameDraft(threadTitle(thread, t("dispatch.sidebar.newChat")));
420
- setRenamingThreadId(thread.id);
421
- }
422
-
423
- function cancelRenameThread() {
424
- committingRenameRef.current = true;
425
- setRenamingThreadId(null);
426
- setRenameDraft("");
427
- }
428
-
429
- async function commitRenameThread() {
430
- if (committingRenameRef.current) return;
431
- const threadId = renamingThreadId;
432
- const title = renameDraft.trim();
433
- if (!threadId) return;
434
- committingRenameRef.current = true;
435
- setRenamingThreadId(null);
436
- setRenameDraft("");
437
- if (title) await renameThread(threadId, title);
438
- committingRenameRef.current = false;
439
- }
440
-
441
- function handleRenameSubmit(event: FormEvent<HTMLFormElement>) {
442
- event.preventDefault();
443
- void commitRenameThread();
444
- }
445
-
446
420
  return (
447
421
  <div className="ms-4 min-w-0 space-y-0.5">
448
422
  {chatsLoading &&
@@ -456,105 +430,28 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
456
430
  <Skeleton className="h-3 w-3/4 rounded" />
457
431
  </div>
458
432
  ))}
459
- {visibleThreads.map((thread) => {
460
- const localPathname = localDispatchPath(location.pathname);
461
- const isActive =
462
- thread.id ===
463
- (threadIdFromPath(localPathname) ??
464
- (localPathname === "/chat" ? null : activeThreadId));
465
- const isRenaming = thread.id === renamingThreadId;
466
- const title = threadTitle(thread, t("dispatch.sidebar.newChat"));
467
- return (
468
- <div
469
- key={thread.id}
470
- className={cn(
471
- "group/item relative flex min-w-0 items-center rounded-lg transition-colors",
472
- isActive
473
- ? "bg-sidebar-accent text-sidebar-accent-foreground"
474
- : "text-muted-foreground hover:bg-sidebar-accent/50 hover:text-foreground",
475
- )}
476
- >
477
- {isRenaming ? (
478
- <form
479
- onSubmit={handleRenameSubmit}
480
- className="flex min-w-0 flex-1 items-center px-1"
481
- >
482
- <Input
483
- ref={renameInputRef}
484
- value={renameDraft}
485
- onChange={(event) => setRenameDraft(event.target.value)}
486
- onBlur={() => void commitRenameThread()}
487
- onKeyDown={(event) => {
488
- if (event.key === "Escape") {
489
- event.preventDefault();
490
- cancelRenameThread();
491
- }
492
- }}
493
- maxLength={160}
494
- aria-label={t("dispatch.sidebar.renameThread", { title })}
495
- className="h-6 min-w-0 rounded-sm border-sidebar-border bg-background px-1.5 text-xs"
496
- />
497
- </form>
498
- ) : (
499
- <>
500
- <Tooltip>
501
- <TooltipTrigger asChild>
502
- <button
503
- type="button"
504
- onClick={() => openThread(thread.id)}
505
- className="flex min-w-0 flex-1 cursor-pointer items-center gap-2 px-2 py-1.5 pe-1 text-start text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring"
506
- >
507
- <span className="block min-w-0 flex-1 truncate">
508
- {title}
509
- </span>
510
- <time className="w-8 shrink-0 whitespace-nowrap text-end text-[11px] tabular-nums text-muted-foreground/60 transition-opacity group-hover/item:opacity-0 group-focus-within/item:opacity-0">
511
- {isActive
512
- ? ""
513
- : formatThreadAge(threadUpdatedAt(thread))}
514
- </time>
515
- </button>
516
- </TooltipTrigger>
517
- <TooltipContent side="right">{title}</TooltipContent>
518
- </Tooltip>
519
- <div className="pointer-events-none absolute end-1 top-1/2 flex -translate-y-1/2 items-center gap-0.5">
520
- <DropdownMenu>
521
- <Tooltip>
522
- <TooltipTrigger asChild>
523
- <DropdownMenuTrigger asChild>
524
- <button
525
- type="button"
526
- aria-label={t("dispatch.sidebar.chatOptions", {
527
- title,
528
- })}
529
- className="pointer-events-auto rounded p-0.5 text-muted-foreground/50 opacity-0 transition-[color,opacity] hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover/item:opacity-100 group-focus-within/item:opacity-100 data-[state=open]:opacity-100 data-[state=open]:text-foreground"
530
- >
531
- <IconDots className="size-3" />
532
- </button>
533
- </DropdownMenuTrigger>
534
- </TooltipTrigger>
535
- <TooltipContent side="right">
536
- {t("dispatch.sidebar.chatOptions", { title })}
537
- </TooltipContent>
538
- </Tooltip>
539
- <DropdownMenuContent
540
- align="start"
541
- side="right"
542
- className="w-44"
543
- >
544
- <DropdownMenuItem
545
- onSelect={() => startRenameThread(thread)}
546
- >
547
- <IconEdit className="size-3.5" />
548
- {t("dispatch.sidebar.renameChat")}
549
- </DropdownMenuItem>
550
- </DropdownMenuContent>
551
- </DropdownMenu>
552
- </div>
553
- </>
554
- )}
555
- </div>
556
- );
557
- })}
433
+ {visibleThreads.length > 0 && (
434
+ <ChatHistoryList
435
+ items={chatItems}
436
+ activeId={displayedActiveThreadId}
437
+ onSelect={(threadId) => openThread(threadId)}
438
+ renameMaxLength={160}
439
+ onRename={(threadId, title) => void renameThread(threadId, title)}
440
+ labels={{
441
+ options: (item) =>
442
+ t("dispatch.sidebar.chatOptions", {
443
+ title: item.titleText ?? "",
444
+ }),
445
+ renameInput: (item) =>
446
+ t("dispatch.sidebar.renameThread", {
447
+ title: item.titleText ?? "",
448
+ }),
449
+ rename: t("dispatch.sidebar.renameChat"),
450
+ }}
451
+ variant="rail"
452
+ className="min-w-0"
453
+ />
454
+ )}
558
455
  <button
559
456
  type="button"
560
457
  onClick={() => void handleNewChat()}
@@ -1,7 +0,0 @@
1
- export declare function sanitizeProviderApiAuditPath(path: unknown): string;
2
- export declare function buildProviderApiAuditSummary(args: {
3
- method?: unknown;
4
- provider?: unknown;
5
- path?: unknown;
6
- }): string;
7
- //# sourceMappingURL=provider-api-audit.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"provider-api-audit.d.ts","sourceRoot":"","sources":["../../src/actions/provider-api-audit.ts"],"names":[],"mappings":"AA8DA,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAclE;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,GAAG,MAAM,CAKT"}
@@ -1,74 +0,0 @@
1
- const REDACTED = "[redacted]";
2
- const CREDENTIAL_NAME = /^(?:api[-_]?key|access[-_]?token|auth(?:orization)?|bearer|credential|password|secret|signature|sig|token|x-amz-credential|x-amz-signature)$/i;
3
- function decodePathSegment(segment) {
4
- try {
5
- return decodeURIComponent(segment);
6
- }
7
- catch {
8
- return segment;
9
- }
10
- }
11
- function looksLikeCredentialValue(segment) {
12
- const value = decodePathSegment(segment);
13
- if (/^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(value)) {
14
- return true;
15
- }
16
- if (/^(?:sk|pk|rk|ghp|github_pat|xox[baprs]|ya29|AIza)[-_][A-Za-z0-9_-]+$/i.test(value)) {
17
- return true;
18
- }
19
- return value.length >= 32 && /^[A-Za-z0-9._~-]+$/.test(value);
20
- }
21
- function sanitizePathname(pathname) {
22
- let redactNext = false;
23
- return pathname
24
- .split("/")
25
- .map((segment) => {
26
- if (!segment)
27
- return segment;
28
- const decoded = decodePathSegment(segment);
29
- if (redactNext) {
30
- redactNext = false;
31
- return REDACTED;
32
- }
33
- const assignment = decoded.match(/^([^=:]+)([=:])(.+)$/);
34
- if (assignment && CREDENTIAL_NAME.test(assignment[1])) {
35
- return `${assignment[1]}${assignment[2]}${REDACTED}`;
36
- }
37
- if (CREDENTIAL_NAME.test(decoded)) {
38
- redactNext = true;
39
- return segment;
40
- }
41
- return looksLikeCredentialValue(segment) ? REDACTED : segment;
42
- })
43
- .join("/");
44
- }
45
- function sanitizeQuery(search) {
46
- if (!search)
47
- return "";
48
- const redacted = Array.from(new URLSearchParams(search).keys(), (key) => {
49
- const safeKey = encodeURIComponent(key.slice(0, 100));
50
- return `${safeKey}=${REDACTED}`;
51
- });
52
- return redacted.length > 0 ? `?${redacted.join("&")}` : "";
53
- }
54
- export function sanitizeProviderApiAuditPath(path) {
55
- const raw = String(path ?? "");
56
- const withoutFragment = raw.split("#", 1)[0] ?? "";
57
- try {
58
- const url = new URL(withoutFragment);
59
- return `${url.protocol}//${url.host}${sanitizePathname(url.pathname)}${sanitizeQuery(url.search)}`;
60
- }
61
- catch {
62
- const queryIndex = withoutFragment.indexOf("?");
63
- const pathname = queryIndex >= 0 ? withoutFragment.slice(0, queryIndex) : withoutFragment;
64
- const search = queryIndex >= 0 ? withoutFragment.slice(queryIndex + 1) : "";
65
- return `${sanitizePathname(pathname)}${sanitizeQuery(search)}`;
66
- }
67
- }
68
- export function buildProviderApiAuditSummary(args) {
69
- const method = String(args.method || "GET").toUpperCase();
70
- const provider = String(args.provider ?? "");
71
- const path = sanitizeProviderApiAuditPath(args.path);
72
- return `${method} ${provider} ${path}`.slice(0, 200);
73
- }
74
- //# sourceMappingURL=provider-api-audit.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"provider-api-audit.js","sourceRoot":"","sources":["../../src/actions/provider-api-audit.ts"],"names":[],"mappings":"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","sourcesContent":["const REDACTED = \"[redacted]\";\n\nconst CREDENTIAL_NAME =\n /^(?:api[-_]?key|access[-_]?token|auth(?:orization)?|bearer|credential|password|secret|signature|sig|token|x-amz-credential|x-amz-signature)$/i;\n\nfunction decodePathSegment(segment: string): string {\n try {\n return decodeURIComponent(segment);\n } catch {\n return segment;\n }\n}\n\nfunction looksLikeCredentialValue(segment: string): boolean {\n const value = decodePathSegment(segment);\n if (/^eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/.test(value)) {\n return true;\n }\n if (\n /^(?:sk|pk|rk|ghp|github_pat|xox[baprs]|ya29|AIza)[-_][A-Za-z0-9_-]+$/i.test(\n value,\n )\n ) {\n return true;\n }\n return value.length >= 32 && /^[A-Za-z0-9._~-]+$/.test(value);\n}\n\nfunction sanitizePathname(pathname: string): string {\n let redactNext = false;\n return pathname\n .split(\"/\")\n .map((segment) => {\n if (!segment) return segment;\n const decoded = decodePathSegment(segment);\n if (redactNext) {\n redactNext = false;\n return REDACTED;\n }\n\n const assignment = decoded.match(/^([^=:]+)([=:])(.+)$/);\n if (assignment && CREDENTIAL_NAME.test(assignment[1]!)) {\n return `${assignment[1]}${assignment[2]}${REDACTED}`;\n }\n if (CREDENTIAL_NAME.test(decoded)) {\n redactNext = true;\n return segment;\n }\n return looksLikeCredentialValue(segment) ? REDACTED : segment;\n })\n .join(\"/\");\n}\n\nfunction sanitizeQuery(search: string): string {\n if (!search) return \"\";\n const redacted = Array.from(new URLSearchParams(search).keys(), (key) => {\n const safeKey = encodeURIComponent(key.slice(0, 100));\n return `${safeKey}=${REDACTED}`;\n });\n return redacted.length > 0 ? `?${redacted.join(\"&\")}` : \"\";\n}\n\nexport function sanitizeProviderApiAuditPath(path: unknown): string {\n const raw = String(path ?? \"\");\n const withoutFragment = raw.split(\"#\", 1)[0] ?? \"\";\n\n try {\n const url = new URL(withoutFragment);\n return `${url.protocol}//${url.host}${sanitizePathname(url.pathname)}${sanitizeQuery(url.search)}`;\n } catch {\n const queryIndex = withoutFragment.indexOf(\"?\");\n const pathname =\n queryIndex >= 0 ? withoutFragment.slice(0, queryIndex) : withoutFragment;\n const search = queryIndex >= 0 ? withoutFragment.slice(queryIndex + 1) : \"\";\n return `${sanitizePathname(pathname)}${sanitizeQuery(search)}`;\n }\n}\n\nexport function buildProviderApiAuditSummary(args: {\n method?: unknown;\n provider?: unknown;\n path?: unknown;\n}): string {\n const method = String(args.method || \"GET\").toUpperCase();\n const provider = String(args.provider ?? \"\");\n const path = sanitizeProviderApiAuditPath(args.path);\n return `${method} ${provider} ${path}`.slice(0, 200);\n}\n"]}
@@ -1,88 +0,0 @@
1
- const REDACTED = "[redacted]";
2
-
3
- const CREDENTIAL_NAME =
4
- /^(?:api[-_]?key|access[-_]?token|auth(?:orization)?|bearer|credential|password|secret|signature|sig|token|x-amz-credential|x-amz-signature)$/i;
5
-
6
- function decodePathSegment(segment: string): string {
7
- try {
8
- return decodeURIComponent(segment);
9
- } catch {
10
- return segment;
11
- }
12
- }
13
-
14
- function looksLikeCredentialValue(segment: string): boolean {
15
- const value = decodePathSegment(segment);
16
- if (/^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(value)) {
17
- return true;
18
- }
19
- if (
20
- /^(?:sk|pk|rk|ghp|github_pat|xox[baprs]|ya29|AIza)[-_][A-Za-z0-9_-]+$/i.test(
21
- value,
22
- )
23
- ) {
24
- return true;
25
- }
26
- return value.length >= 32 && /^[A-Za-z0-9._~-]+$/.test(value);
27
- }
28
-
29
- function sanitizePathname(pathname: string): string {
30
- let redactNext = false;
31
- return pathname
32
- .split("/")
33
- .map((segment) => {
34
- if (!segment) return segment;
35
- const decoded = decodePathSegment(segment);
36
- if (redactNext) {
37
- redactNext = false;
38
- return REDACTED;
39
- }
40
-
41
- const assignment = decoded.match(/^([^=:]+)([=:])(.+)$/);
42
- if (assignment && CREDENTIAL_NAME.test(assignment[1]!)) {
43
- return `${assignment[1]}${assignment[2]}${REDACTED}`;
44
- }
45
- if (CREDENTIAL_NAME.test(decoded)) {
46
- redactNext = true;
47
- return segment;
48
- }
49
- return looksLikeCredentialValue(segment) ? REDACTED : segment;
50
- })
51
- .join("/");
52
- }
53
-
54
- function sanitizeQuery(search: string): string {
55
- if (!search) return "";
56
- const redacted = Array.from(new URLSearchParams(search).keys(), (key) => {
57
- const safeKey = encodeURIComponent(key.slice(0, 100));
58
- return `${safeKey}=${REDACTED}`;
59
- });
60
- return redacted.length > 0 ? `?${redacted.join("&")}` : "";
61
- }
62
-
63
- export function sanitizeProviderApiAuditPath(path: unknown): string {
64
- const raw = String(path ?? "");
65
- const withoutFragment = raw.split("#", 1)[0] ?? "";
66
-
67
- try {
68
- const url = new URL(withoutFragment);
69
- return `${url.protocol}//${url.host}${sanitizePathname(url.pathname)}${sanitizeQuery(url.search)}`;
70
- } catch {
71
- const queryIndex = withoutFragment.indexOf("?");
72
- const pathname =
73
- queryIndex >= 0 ? withoutFragment.slice(0, queryIndex) : withoutFragment;
74
- const search = queryIndex >= 0 ? withoutFragment.slice(queryIndex + 1) : "";
75
- return `${sanitizePathname(pathname)}${sanitizeQuery(search)}`;
76
- }
77
- }
78
-
79
- export function buildProviderApiAuditSummary(args: {
80
- method?: unknown;
81
- provider?: unknown;
82
- path?: unknown;
83
- }): string {
84
- const method = String(args.method || "GET").toUpperCase();
85
- const provider = String(args.provider ?? "");
86
- const path = sanitizeProviderApiAuditPath(args.path);
87
- return `${method} ${provider} ${path}`.slice(0, 200);
88
- }