@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.
- package/dist/actions/delete-staged-dataset.d.ts +3 -1
- package/dist/actions/delete-staged-dataset.js +3 -26
- package/dist/actions/delete-staged-dataset.js.map +1 -1
- package/dist/actions/delete-workspace-resource.d.ts +13 -13
- package/dist/actions/list-staged-datasets.d.ts +1 -1
- package/dist/actions/list-staged-datasets.js +3 -28
- package/dist/actions/list-staged-datasets.js.map +1 -1
- package/dist/actions/provider-api-catalog.d.ts +6 -4
- package/dist/actions/provider-api-catalog.js +3 -17
- package/dist/actions/provider-api-catalog.js.map +1 -1
- package/dist/actions/provider-api-docs.d.ts +21 -2
- package/dist/actions/provider-api-docs.js +6 -51
- package/dist/actions/provider-api-docs.js.map +1 -1
- package/dist/actions/provider-api-register.d.ts +39 -15
- package/dist/actions/provider-api-register.js +4 -193
- package/dist/actions/provider-api-register.js.map +1 -1
- package/dist/actions/provider-api-request.d.ts +34 -2
- package/dist/actions/provider-api-request.js +6 -171
- package/dist/actions/provider-api-request.js.map +1 -1
- package/dist/actions/query-staged-dataset.d.ts +18 -1
- package/dist/actions/query-staged-dataset.d.ts.map +1 -1
- package/dist/actions/query-staged-dataset.js +3 -100
- package/dist/actions/query-staged-dataset.js.map +1 -1
- package/dist/actions/upsert-destination.d.ts +12 -12
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +26 -64
- package/dist/components/layout/Layout.js.map +1 -1
- package/package.json +4 -3
- package/src/actions/delete-staged-dataset.ts +3 -33
- package/src/actions/list-staged-datasets.ts +3 -30
- package/src/actions/provider-api-audit.spec.ts +2 -3
- package/src/actions/provider-api-catalog.ts +10 -23
- package/src/actions/provider-api-docs.ts +19 -68
- package/src/actions/provider-api-register.ts +5 -233
- package/src/actions/provider-api-request.ts +19 -232
- package/src/actions/query-staged-dataset.ts +3 -119
- package/src/components/layout/Layout.spec.tsx +7 -11
- package/src/components/layout/Layout.tsx +42 -145
- package/dist/actions/provider-api-audit.d.ts +0 -7
- package/dist/actions/provider-api-audit.d.ts.map +0 -1
- package/dist/actions/provider-api-audit.js +0 -74
- package/dist/actions/provider-api-audit.js.map +0 -1
- package/src/actions/provider-api-audit.ts +0 -88
|
@@ -1,126 +1,10 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
13
|
-
|
|
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
|
|
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("
|
|
187
|
+
const age = [...container.querySelectorAll("span")].find(
|
|
188
188
|
(element) => element.textContent === "5m",
|
|
189
189
|
);
|
|
190
|
-
expect(age?.className).toContain("
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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.
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
(
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
)
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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 +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
|
-
}
|