@agent-native/toolkit 0.6.0 → 0.8.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.
- package/README.md +17 -1
- package/agent-native.eject.json +411 -0
- package/dist/chat-history/ChatHistoryList.d.ts +103 -0
- package/dist/chat-history/ChatHistoryList.d.ts.map +1 -0
- package/dist/chat-history/ChatHistoryList.js +109 -0
- package/dist/chat-history/ChatHistoryList.js.map +1 -0
- package/dist/chat-history/ChatHistoryList.spec.d.ts +2 -0
- package/dist/chat-history/ChatHistoryList.spec.d.ts.map +1 -0
- package/dist/chat-history/ChatHistoryList.spec.js +283 -0
- package/dist/chat-history/ChatHistoryList.spec.js.map +1 -0
- package/dist/chat-history/index.d.ts +2 -0
- package/dist/chat-history/index.d.ts.map +1 -0
- package/dist/chat-history/index.js +2 -0
- package/dist/chat-history/index.js.map +1 -0
- package/dist/chat-history.css +314 -0
- package/package.json +20 -1
- package/src/app-shell/header-actions.tsx +84 -0
- package/src/app-shell/index.ts +1 -0
- package/src/chat-history/ChatHistoryList.spec.tsx +453 -0
- package/src/chat-history/ChatHistoryList.tsx +469 -0
- package/src/chat-history/index.ts +7 -0
- package/src/chat-history.css +314 -0
- package/src/collab-ui/AgentPresenceChip.tsx +69 -0
- package/src/collab-ui/LiveCursorOverlay.tsx +241 -0
- package/src/collab-ui/PresenceBar.tsx +346 -0
- package/src/collab-ui/RecentEditHighlights.tsx +225 -0
- package/src/collab-ui/RemoteSelectionRings.tsx +227 -0
- package/src/collab-ui/agent-identity.ts +16 -0
- package/src/collab-ui/index.ts +39 -0
- package/src/collab-ui/lead-client.ts +32 -0
- package/src/collab-ui/types.spec.ts +18 -0
- package/src/collab-ui/types.ts +96 -0
- package/src/composer/AgentComposerFrame.tsx +59 -0
- package/src/composer/ComposerPlusMenu.tsx +983 -0
- package/src/composer/MentionPopover.tsx +412 -0
- package/src/composer/PastedTextChip.tsx +150 -0
- package/src/composer/PromptComposer.spec.ts +73 -0
- package/src/composer/PromptComposer.tsx +718 -0
- package/src/composer/RealtimeVoiceMode.spec.tsx +771 -0
- package/src/composer/RealtimeVoiceMode.tsx +856 -0
- package/src/composer/TiptapComposer.spec.ts +390 -0
- package/src/composer/TiptapComposer.tsx +2936 -0
- package/src/composer/VoiceButton.spec.ts +33 -0
- package/src/composer/VoiceButton.tsx +255 -0
- package/src/composer/asset-picker-url.spec.ts +56 -0
- package/src/composer/asset-picker-url.ts +80 -0
- package/src/composer/attachment-accept.spec.ts +22 -0
- package/src/composer/attachment-accept.ts +71 -0
- package/src/composer/draft-key.spec.ts +19 -0
- package/src/composer/draft-key.ts +7 -0
- package/src/composer/extensions/FileReference.tsx +55 -0
- package/src/composer/extensions/MentionReference.tsx +93 -0
- package/src/composer/extensions/SkillReference.tsx +56 -0
- package/src/composer/index.ts +79 -0
- package/src/composer/pasted-text.spec.ts +162 -0
- package/src/composer/pasted-text.ts +160 -0
- package/src/composer/prompt-attachments.spec.ts +80 -0
- package/src/composer/prompt-attachments.ts +93 -0
- package/src/composer/realtime-voice-audio-level.spec.ts +32 -0
- package/src/composer/realtime-voice-audio-level.ts +64 -0
- package/src/composer/realtime-voice-transcript.spec.ts +95 -0
- package/src/composer/realtime-voice-transcript.ts +134 -0
- package/src/composer/runtime-adapters.tsx +343 -0
- package/src/composer/types.ts +66 -0
- package/src/composer/use-file-search.ts +64 -0
- package/src/composer/use-mention-search.ts +90 -0
- package/src/composer/use-skills.ts +45 -0
- package/src/composer/useRealtimeVoiceMode.spec.ts +818 -0
- package/src/composer/useRealtimeVoiceMode.tsx +2117 -0
- package/src/composer/useVoiceDictation.spec.ts +45 -0
- package/src/composer/useVoiceDictation.ts +1191 -0
- package/src/context-ui/ContextMeter.tsx +101 -0
- package/src/context-ui/ContextSegmentRow.tsx +184 -0
- package/src/context-ui/ContextTreemap.tsx +125 -0
- package/src/context-ui/ContextXRayPanel.tsx +377 -0
- package/src/context-ui/format.ts +42 -0
- package/src/context-ui/index.ts +15 -0
- package/src/context-ui/types.ts +43 -0
- package/src/design-tweaks/index.ts +1 -0
- package/src/design-tweaks/visual-style-controls.tsx +1084 -0
- package/src/editor/BubbleToolbar.tsx +253 -0
- package/src/editor/CodeBlockNode.tsx +254 -0
- package/src/editor/DragHandle.spec.ts +588 -0
- package/src/editor/DragHandle.ts +1437 -0
- package/src/editor/ImageExtension.roundtrip.spec.ts +117 -0
- package/src/editor/ImageExtension.ts +306 -0
- package/src/editor/RegistryBlockContext.tsx +107 -0
- package/src/editor/RegistryBlockNode.spec.ts +279 -0
- package/src/editor/RegistryBlockNode.tsx +652 -0
- package/src/editor/RichMarkdownEditor.collab.spec.ts +79 -0
- package/src/editor/RichMarkdownEditor.roundtrip.spec.ts +226 -0
- package/src/editor/RichMarkdownEditor.tsx +81 -0
- package/src/editor/RunId.ts +61 -0
- package/src/editor/SharedRichEditor.tsx +321 -0
- package/src/editor/SlashCommandMenu.tsx +310 -0
- package/src/editor/disableHistory.spec.ts +85 -0
- package/src/editor/extensions.ts +402 -0
- package/src/editor/gfmDoc.spec.ts +230 -0
- package/src/editor/gfmDoc.ts +92 -0
- package/src/editor/index.ts +93 -0
- package/src/editor/registrySlashCommands.ts +131 -0
- package/src/editor/structuralReorderUndo.spec.ts +144 -0
- package/src/editor/surgical-apply.spec.ts +279 -0
- package/src/editor/surgical-apply.ts +192 -0
- package/src/editor/useCollabReconcile.concurrent.spec.ts +422 -0
- package/src/editor/useCollabReconcile.idempotent.spec.ts +324 -0
- package/src/editor/useCollabReconcile.ts +589 -0
- package/src/editor.css +510 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-mobile.tsx +21 -0
- package/src/hooks/use-toast.ts +188 -0
- package/src/index.ts +8 -0
- package/src/onboarding/index.ts +1 -0
- package/src/provider.tsx +47 -0
- package/src/sharing/VisibilityBadge.spec.ts +96 -0
- package/src/sharing/VisibilityBadge.tsx +37 -0
- package/src/sharing/index.ts +4 -0
- package/src/styles.css +21 -0
- package/src/ui/accordion.tsx +56 -0
- package/src/ui/alert-dialog.tsx +141 -0
- package/src/ui/alert.tsx +59 -0
- package/src/ui/aspect-ratio.tsx +5 -0
- package/src/ui/avatar.tsx +48 -0
- package/src/ui/badge.tsx +37 -0
- package/src/ui/breadcrumb.tsx +115 -0
- package/src/ui/button.tsx +78 -0
- package/src/ui/calendar.tsx +214 -0
- package/src/ui/card.tsx +86 -0
- package/src/ui/carousel.tsx +260 -0
- package/src/ui/chart.tsx +375 -0
- package/src/ui/checkbox.tsx +28 -0
- package/src/ui/collapsible.tsx +9 -0
- package/src/ui/command.spec.tsx +51 -0
- package/src/ui/command.tsx +174 -0
- package/src/ui/context-menu.tsx +202 -0
- package/src/ui/date-picker.tsx +64 -0
- package/src/ui/dialog.spec.tsx +43 -0
- package/src/ui/dialog.tsx +146 -0
- package/src/ui/drawer.tsx +126 -0
- package/src/ui/dropdown-menu.tsx +218 -0
- package/src/ui/form.tsx +177 -0
- package/src/ui/hover-card.tsx +27 -0
- package/src/ui/index.ts +52 -0
- package/src/ui/input-otp.tsx +69 -0
- package/src/ui/input.tsx +22 -0
- package/src/ui/label.tsx +24 -0
- package/src/ui/logo.tsx +36 -0
- package/src/ui/menubar.tsx +235 -0
- package/src/ui/navigation-menu.tsx +128 -0
- package/src/ui/pagination.tsx +121 -0
- package/src/ui/popover.tsx +44 -0
- package/src/ui/progress.tsx +26 -0
- package/src/ui/radio-group.tsx +42 -0
- package/src/ui/resizable.tsx +43 -0
- package/src/ui/scroll-area.tsx +46 -0
- package/src/ui/select.tsx +158 -0
- package/src/ui/separator.tsx +29 -0
- package/src/ui/sheet.tsx +160 -0
- package/src/ui/sidebar.tsx +777 -0
- package/src/ui/skeleton.tsx +18 -0
- package/src/ui/slider.tsx +26 -0
- package/src/ui/sonner.tsx +58 -0
- package/src/ui/spinner.tsx +17 -0
- package/src/ui/switch.tsx +27 -0
- package/src/ui/table.tsx +117 -0
- package/src/ui/tabs.tsx +53 -0
- package/src/ui/textarea.tsx +23 -0
- package/src/ui/toast.tsx +127 -0
- package/src/ui/toaster.tsx +33 -0
- package/src/ui/toggle-group.tsx +59 -0
- package/src/ui/toggle.tsx +43 -0
- package/src/ui/tooltip.tsx +53 -0
- package/src/ui/use-toast.ts +3 -0
- package/src/utils.ts +8 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export interface RealtimeVoiceAudioLevels {
|
|
2
|
+
input: number;
|
|
3
|
+
output: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface RealtimeVoiceAudioLevelStore {
|
|
7
|
+
getSnapshot: () => RealtimeVoiceAudioLevels;
|
|
8
|
+
subscribe: (listener: () => void) => () => void;
|
|
9
|
+
set: (levels: RealtimeVoiceAudioLevels) => void;
|
|
10
|
+
reset: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const SILENT_LEVELS: RealtimeVoiceAudioLevels = { input: 0, output: 0 };
|
|
14
|
+
|
|
15
|
+
function clampLevel(value: number): number {
|
|
16
|
+
if (!Number.isFinite(value)) return 0;
|
|
17
|
+
return Math.max(0, Math.min(1, value));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function normalizeRealtimeVoiceRms(samples: Uint8Array): number {
|
|
21
|
+
if (samples.length === 0) return 0;
|
|
22
|
+
let sumSquares = 0;
|
|
23
|
+
for (const sample of samples) {
|
|
24
|
+
const normalized = (sample - 128) / 128;
|
|
25
|
+
sumSquares += normalized * normalized;
|
|
26
|
+
}
|
|
27
|
+
const rms = Math.sqrt(sumSquares / samples.length);
|
|
28
|
+
return clampLevel(Math.max(0, rms - 0.012) * 3.2);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function smoothRealtimeVoiceLevel(
|
|
32
|
+
previous: number,
|
|
33
|
+
next: number,
|
|
34
|
+
): number {
|
|
35
|
+
const target = clampLevel(next);
|
|
36
|
+
const factor = target > previous ? 0.55 : 0.2;
|
|
37
|
+
return clampLevel(previous + (target - previous) * factor);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createRealtimeVoiceAudioLevelStore(): RealtimeVoiceAudioLevelStore {
|
|
41
|
+
let snapshot = SILENT_LEVELS;
|
|
42
|
+
const listeners = new Set<() => void>();
|
|
43
|
+
|
|
44
|
+
const set = (levels: RealtimeVoiceAudioLevels) => {
|
|
45
|
+
const next = {
|
|
46
|
+
input: clampLevel(levels.input),
|
|
47
|
+
output: clampLevel(levels.output),
|
|
48
|
+
};
|
|
49
|
+
if (next.input === snapshot.input && next.output === snapshot.output)
|
|
50
|
+
return;
|
|
51
|
+
snapshot = next;
|
|
52
|
+
for (const listener of listeners) listener();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
getSnapshot: () => snapshot,
|
|
57
|
+
subscribe(listener) {
|
|
58
|
+
listeners.add(listener);
|
|
59
|
+
return () => listeners.delete(listener);
|
|
60
|
+
},
|
|
61
|
+
set,
|
|
62
|
+
reset: () => set(SILENT_LEVELS),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
appendRealtimeVoiceTranscriptToRepository,
|
|
5
|
+
createRealtimeVoiceTranscriptRegistry,
|
|
6
|
+
type RealtimeVoiceTranscriptMessage,
|
|
7
|
+
} from "./realtime-voice-transcript.js";
|
|
8
|
+
|
|
9
|
+
function transcript(
|
|
10
|
+
overrides: Partial<RealtimeVoiceTranscriptMessage> = {},
|
|
11
|
+
): RealtimeVoiceTranscriptMessage {
|
|
12
|
+
return {
|
|
13
|
+
id: "voice-1",
|
|
14
|
+
threadId: "thread-1",
|
|
15
|
+
role: "user",
|
|
16
|
+
text: "Open sources",
|
|
17
|
+
createdAt: "2026-07-10T20:00:00.000Z",
|
|
18
|
+
...overrides,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("realtime voice transcript registry", () => {
|
|
23
|
+
it("captures the latest active thread and buffers until its sink mounts", () => {
|
|
24
|
+
const registry = createRealtimeVoiceTranscriptRegistry();
|
|
25
|
+
registry.register({
|
|
26
|
+
threadId: "thread-old",
|
|
27
|
+
active: true,
|
|
28
|
+
append: vi.fn(() => true),
|
|
29
|
+
});
|
|
30
|
+
registry.register({
|
|
31
|
+
threadId: "thread-1",
|
|
32
|
+
active: true,
|
|
33
|
+
// Simulate the chat surface being selected but not yet ready to apply
|
|
34
|
+
// the completed transcript. The registry must retain it for the sink
|
|
35
|
+
// that mounts once the surface is ready.
|
|
36
|
+
append: vi.fn(() => false),
|
|
37
|
+
});
|
|
38
|
+
expect(registry.activeThreadId()).toBe("thread-1");
|
|
39
|
+
|
|
40
|
+
registry.publish(transcript());
|
|
41
|
+
expect(registry.pendingCount()).toBe(1);
|
|
42
|
+
const append = vi.fn(() => true);
|
|
43
|
+
registry.register({ threadId: "thread-1", active: false, append });
|
|
44
|
+
expect(append).toHaveBeenCalledOnce();
|
|
45
|
+
expect(registry.pendingCount()).toBe(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("deduplicates completed provider transcript events", () => {
|
|
49
|
+
const registry = createRealtimeVoiceTranscriptRegistry();
|
|
50
|
+
const append = vi.fn(() => true);
|
|
51
|
+
registry.register({ threadId: "thread-1", active: true, append });
|
|
52
|
+
registry.publish(transcript());
|
|
53
|
+
registry.publish(transcript());
|
|
54
|
+
expect(append).toHaveBeenCalledOnce();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("appendRealtimeVoiceTranscriptToRepository", () => {
|
|
59
|
+
it("links completed user and assistant messages without starting a run", () => {
|
|
60
|
+
const first = appendRealtimeVoiceTranscriptToRepository({}, transcript());
|
|
61
|
+
const second = appendRealtimeVoiceTranscriptToRepository(
|
|
62
|
+
first.repository,
|
|
63
|
+
transcript({
|
|
64
|
+
id: "voice-2",
|
|
65
|
+
role: "assistant",
|
|
66
|
+
text: "Opening Sources.",
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
expect(second.repository.headId).toBe("voice-2");
|
|
70
|
+
expect(second.repository.messages).toMatchObject([
|
|
71
|
+
{
|
|
72
|
+
parentId: null,
|
|
73
|
+
message: {
|
|
74
|
+
id: "voice-1",
|
|
75
|
+
role: "user",
|
|
76
|
+
metadata: { custom: { source: "realtime-voice" } },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
parentId: "voice-1",
|
|
81
|
+
message: {
|
|
82
|
+
id: "voice-2",
|
|
83
|
+
role: "assistant",
|
|
84
|
+
status: { type: "complete", reason: "stop" },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
]);
|
|
88
|
+
expect(
|
|
89
|
+
appendRealtimeVoiceTranscriptToRepository(
|
|
90
|
+
second.repository,
|
|
91
|
+
transcript({ id: "voice-2" }),
|
|
92
|
+
).appended,
|
|
93
|
+
).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
export type RealtimeVoiceTranscriptRole = "user" | "assistant";
|
|
2
|
+
|
|
3
|
+
export interface RealtimeVoiceTranscriptMessage {
|
|
4
|
+
id: string;
|
|
5
|
+
threadId: string;
|
|
6
|
+
role: RealtimeVoiceTranscriptRole;
|
|
7
|
+
text: string;
|
|
8
|
+
createdAt: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RealtimeVoiceTranscriptSink {
|
|
12
|
+
threadId: string;
|
|
13
|
+
active: boolean;
|
|
14
|
+
append: (message: RealtimeVoiceTranscriptMessage) => boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RealtimeVoiceTranscriptRegistry {
|
|
18
|
+
activeThreadId: () => string | undefined;
|
|
19
|
+
publish: (message: RealtimeVoiceTranscriptMessage) => void;
|
|
20
|
+
register: (sink: RealtimeVoiceTranscriptSink) => () => void;
|
|
21
|
+
pendingCount: () => number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function appendRealtimeVoiceTranscriptToRepository(
|
|
25
|
+
value: unknown,
|
|
26
|
+
transcript: RealtimeVoiceTranscriptMessage,
|
|
27
|
+
): { appended: boolean; repository: Record<string, unknown> } {
|
|
28
|
+
const repository =
|
|
29
|
+
value && typeof value === "object" && !Array.isArray(value)
|
|
30
|
+
? (value as Record<string, unknown>)
|
|
31
|
+
: {};
|
|
32
|
+
const messages = Array.isArray(repository.messages)
|
|
33
|
+
? [...repository.messages]
|
|
34
|
+
: [];
|
|
35
|
+
const duplicate = messages.some((entry) => {
|
|
36
|
+
if (!entry || typeof entry !== "object") return false;
|
|
37
|
+
const record = entry as Record<string, unknown>;
|
|
38
|
+
const message =
|
|
39
|
+
record.message && typeof record.message === "object"
|
|
40
|
+
? (record.message as Record<string, unknown>)
|
|
41
|
+
: record;
|
|
42
|
+
return message.id === transcript.id;
|
|
43
|
+
});
|
|
44
|
+
if (duplicate) return { appended: false, repository };
|
|
45
|
+
|
|
46
|
+
const parentId =
|
|
47
|
+
typeof repository.headId === "string" ? repository.headId : null;
|
|
48
|
+
const createdAt = new Date(transcript.createdAt);
|
|
49
|
+
const message = {
|
|
50
|
+
id: transcript.id,
|
|
51
|
+
role: transcript.role,
|
|
52
|
+
content: [{ type: "text", text: transcript.text }],
|
|
53
|
+
createdAt: Number.isNaN(createdAt.getTime()) ? new Date() : createdAt,
|
|
54
|
+
metadata: {
|
|
55
|
+
custom: {
|
|
56
|
+
source: "realtime-voice",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
...(transcript.role === "assistant"
|
|
60
|
+
? { status: { type: "complete", reason: "stop" } }
|
|
61
|
+
: {}),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
appended: true,
|
|
66
|
+
repository: {
|
|
67
|
+
...repository,
|
|
68
|
+
messages: [...messages, { message, parentId }],
|
|
69
|
+
headId: transcript.id,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function createRealtimeVoiceTranscriptRegistry(): RealtimeVoiceTranscriptRegistry {
|
|
75
|
+
const sinks = new Map<
|
|
76
|
+
symbol,
|
|
77
|
+
RealtimeVoiceTranscriptSink & { order: number }
|
|
78
|
+
>();
|
|
79
|
+
const pending = new Map<string, RealtimeVoiceTranscriptMessage>();
|
|
80
|
+
const delivered = new Set<string>();
|
|
81
|
+
let order = 0;
|
|
82
|
+
|
|
83
|
+
const flush = () => {
|
|
84
|
+
for (const [id, message] of pending) {
|
|
85
|
+
let consumed = false;
|
|
86
|
+
for (const sink of sinks.values()) {
|
|
87
|
+
if (sink.threadId !== message.threadId) continue;
|
|
88
|
+
try {
|
|
89
|
+
consumed = sink.append(message) || consumed;
|
|
90
|
+
} catch {
|
|
91
|
+
// Keep the completed transcript queued until the chat can import it.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!consumed) continue;
|
|
95
|
+
pending.delete(id);
|
|
96
|
+
delivered.add(id);
|
|
97
|
+
if (delivered.size > 1_000) {
|
|
98
|
+
const oldest = delivered.values().next().value;
|
|
99
|
+
if (typeof oldest === "string") delivered.delete(oldest);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
activeThreadId() {
|
|
106
|
+
let active: (RealtimeVoiceTranscriptSink & { order: number }) | undefined;
|
|
107
|
+
for (const sink of sinks.values()) {
|
|
108
|
+
if (!sink.active || (active && sink.order <= active.order)) continue;
|
|
109
|
+
active = sink;
|
|
110
|
+
}
|
|
111
|
+
return active?.threadId;
|
|
112
|
+
},
|
|
113
|
+
publish(message) {
|
|
114
|
+
const id = message.id.trim();
|
|
115
|
+
const threadId = message.threadId.trim();
|
|
116
|
+
const text = message.text.trim();
|
|
117
|
+
if (!id || !threadId || !text || delivered.has(id) || pending.has(id)) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
pending.set(id, { ...message, id, threadId, text });
|
|
121
|
+
flush();
|
|
122
|
+
},
|
|
123
|
+
register(sink) {
|
|
124
|
+
const token = Symbol("realtime-voice-transcript-sink");
|
|
125
|
+
sinks.set(token, { ...sink, order: ++order });
|
|
126
|
+
flush();
|
|
127
|
+
return () => sinks.delete(token);
|
|
128
|
+
},
|
|
129
|
+
pendingCount: () => pending.size,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const realtimeVoiceTranscriptRegistry =
|
|
134
|
+
createRealtimeVoiceTranscriptRegistry();
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useContext,
|
|
4
|
+
type ComponentType,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
} from "react";
|
|
7
|
+
|
|
8
|
+
export type ComposerTranslate = (
|
|
9
|
+
key: string,
|
|
10
|
+
options?: Record<string, unknown>,
|
|
11
|
+
) => string;
|
|
12
|
+
|
|
13
|
+
export type ReasoningEffort =
|
|
14
|
+
| "auto"
|
|
15
|
+
| "none"
|
|
16
|
+
| "minimal"
|
|
17
|
+
| "low"
|
|
18
|
+
| "medium"
|
|
19
|
+
| "high"
|
|
20
|
+
| "xhigh"
|
|
21
|
+
| "max";
|
|
22
|
+
|
|
23
|
+
export interface EngineModelGroup {
|
|
24
|
+
engine: string;
|
|
25
|
+
label: string;
|
|
26
|
+
models: string[];
|
|
27
|
+
configured: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ComposerModelState {
|
|
31
|
+
selectedModel: string;
|
|
32
|
+
selectedEngine: string;
|
|
33
|
+
selectedEffort: ReasoningEffort;
|
|
34
|
+
availableModels: EngineModelGroup[];
|
|
35
|
+
isLoading: boolean;
|
|
36
|
+
onModelChange: (model: string, engine: string) => void;
|
|
37
|
+
onEffortChange: (effort: ReasoningEffort) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ComposerBuilderConnectFlow {
|
|
41
|
+
hasFetchedStatus: boolean;
|
|
42
|
+
configured: boolean;
|
|
43
|
+
envManaged: boolean;
|
|
44
|
+
connecting: boolean;
|
|
45
|
+
statusResolved: boolean;
|
|
46
|
+
start: () => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface AgentChatContextItem {
|
|
50
|
+
key: string;
|
|
51
|
+
title: string;
|
|
52
|
+
context: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ComposerAgentChatMessage {
|
|
56
|
+
message: string;
|
|
57
|
+
context?: string;
|
|
58
|
+
mode?: "plan" | "act";
|
|
59
|
+
submit?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ComposerAgentChatContextSetOptions extends AgentChatContextItem {
|
|
63
|
+
openSidebar?: boolean;
|
|
64
|
+
focus?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ComposerAgentChatOpenThreadRequest {
|
|
68
|
+
threadId: string;
|
|
69
|
+
onlyIfActiveThreadId?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ComposerBuilderConnectFlowOptions {
|
|
73
|
+
enabled?: boolean;
|
|
74
|
+
popupUrl?: string;
|
|
75
|
+
trackingSource?: string;
|
|
76
|
+
trackingFlow?: string;
|
|
77
|
+
onConnected?: (state: { orgName: string | null }) => void | Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface AgentComposerReference {
|
|
81
|
+
label: string;
|
|
82
|
+
icon?: string;
|
|
83
|
+
source?: string;
|
|
84
|
+
refType: string;
|
|
85
|
+
refId?: string | null;
|
|
86
|
+
refPath?: string | null;
|
|
87
|
+
slotKey?: string;
|
|
88
|
+
slotLabel?: string;
|
|
89
|
+
metadata?: Record<string, unknown>;
|
|
90
|
+
clearsSlots?: string[];
|
|
91
|
+
relatedReferences?: AgentComposerReference[];
|
|
92
|
+
}
|
|
93
|
+
export interface AgentComposerReferenceInsertPayload extends AgentComposerReference {
|
|
94
|
+
insertMessageId: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface VoiceContextPack {
|
|
98
|
+
surface?: string;
|
|
99
|
+
mode?: string;
|
|
100
|
+
snippets?: Array<{ label: string; value: string }>;
|
|
101
|
+
terms?: Array<{ term: string; replacement?: string }>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface ComposerRuntimeAdapters {
|
|
105
|
+
resolvePath?: (path: string) => string;
|
|
106
|
+
translate?: ComposerTranslate;
|
|
107
|
+
models?: {
|
|
108
|
+
useChatModels?: (options: { enabled: boolean }) => ComposerModelState;
|
|
109
|
+
useAgentEngineConfigured?: (enabled: boolean) => {
|
|
110
|
+
missing: boolean;
|
|
111
|
+
state: string;
|
|
112
|
+
};
|
|
113
|
+
fetchAgentEngineConfiguredState?: (
|
|
114
|
+
enabled: boolean,
|
|
115
|
+
options: { timeoutMs: number },
|
|
116
|
+
) => Promise<"missing" | "configured" | string>;
|
|
117
|
+
BuilderSetupCard?: ComponentType<any>;
|
|
118
|
+
BuilderSetupContent?: ComponentType<any>;
|
|
119
|
+
reasoning?: {
|
|
120
|
+
defaultEffort?: ReasoningEffort;
|
|
121
|
+
getOptionsForModel?: (model?: string) => ReasoningEffort[];
|
|
122
|
+
label?: (effort: ReasoningEffort) => string;
|
|
123
|
+
resolve?: (model: string, effort?: ReasoningEffort) => ReasoningEffort;
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
agentChat?: {
|
|
127
|
+
sendToAgentChat?: (input: ComposerAgentChatMessage) => void;
|
|
128
|
+
setContextItem?: (input: ComposerAgentChatContextSetOptions) => void;
|
|
129
|
+
requestThreadOpen?: (input: ComposerAgentChatOpenThreadRequest) => void;
|
|
130
|
+
formatContextItems?: (
|
|
131
|
+
items: readonly AgentChatContextItem[] | undefined,
|
|
132
|
+
) => string;
|
|
133
|
+
normalizeReference?: (reference: unknown) => AgentComposerReference | null;
|
|
134
|
+
StaleIndexBoundary?: ComponentType<any>;
|
|
135
|
+
};
|
|
136
|
+
builder?: {
|
|
137
|
+
useConnectFlow?: (
|
|
138
|
+
options: ComposerBuilderConnectFlowOptions,
|
|
139
|
+
) => ComposerBuilderConnectFlow;
|
|
140
|
+
tryDelegateBuildRequest?: (text: string) => boolean;
|
|
141
|
+
isTrustedFrameMessage?: (event: MessageEvent) => boolean;
|
|
142
|
+
isTrustedBuilderMessage?: (event: MessageEvent) => boolean;
|
|
143
|
+
};
|
|
144
|
+
resources?: {
|
|
145
|
+
useOrg?: () => {
|
|
146
|
+
data?: { orgId?: string | null; role?: string | null } | null;
|
|
147
|
+
};
|
|
148
|
+
isMcpIntegrationAvailable?: () => boolean;
|
|
149
|
+
useCreateMcpServer?: () => {
|
|
150
|
+
mutateAsync: (input: any) => Promise<unknown>;
|
|
151
|
+
};
|
|
152
|
+
McpIntegrationDialog?: ComponentType<any>;
|
|
153
|
+
};
|
|
154
|
+
voice?: {
|
|
155
|
+
useProviderStatus?: () => {
|
|
156
|
+
status: { builder?: boolean; openai?: boolean } | null;
|
|
157
|
+
refresh: () => void;
|
|
158
|
+
};
|
|
159
|
+
getBrowserTabId?: () => string;
|
|
160
|
+
readAppState?: <T>(
|
|
161
|
+
key: string,
|
|
162
|
+
) => T | null | undefined | Promise<T | null | undefined>;
|
|
163
|
+
setAppState?: (key: string, value: unknown) => void | Promise<unknown>;
|
|
164
|
+
subscribeSidebarState?: (
|
|
165
|
+
listener: (detail: { open?: boolean } | undefined) => void,
|
|
166
|
+
) => () => void;
|
|
167
|
+
applyContextReplacements?: (
|
|
168
|
+
text: string,
|
|
169
|
+
context: VoiceContextPack | undefined,
|
|
170
|
+
) => string;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const identityPath = (path: string) => path;
|
|
175
|
+
const fallbackTranslate: ComposerTranslate = (key, options) =>
|
|
176
|
+
typeof options?.defaultValue === "string" ? options.defaultValue : key;
|
|
177
|
+
const fallbackModels = {
|
|
178
|
+
useChatModels: () => ({
|
|
179
|
+
selectedModel: "auto",
|
|
180
|
+
selectedEngine: "auto",
|
|
181
|
+
selectedEffort: "medium" as ReasoningEffort,
|
|
182
|
+
availableModels: [],
|
|
183
|
+
isLoading: false,
|
|
184
|
+
onModelChange: () => {},
|
|
185
|
+
onEffortChange: () => {},
|
|
186
|
+
}),
|
|
187
|
+
useAgentEngineConfigured: () => ({ missing: false, state: "configured" }),
|
|
188
|
+
fetchAgentEngineConfiguredState: async () => "configured",
|
|
189
|
+
};
|
|
190
|
+
const FragmentBoundary: ComponentType<{ children?: ReactNode }> = ({
|
|
191
|
+
children,
|
|
192
|
+
}) => <>{children}</>;
|
|
193
|
+
const fallbackBuilderFlow = {
|
|
194
|
+
hasFetchedStatus: false,
|
|
195
|
+
configured: false,
|
|
196
|
+
envManaged: false,
|
|
197
|
+
connecting: false,
|
|
198
|
+
statusResolved: false,
|
|
199
|
+
start: () => {},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const fallbackAdapters: Required<Pick<ComposerRuntimeAdapters, "resolvePath">> &
|
|
203
|
+
ComposerRuntimeAdapters = {
|
|
204
|
+
resolvePath: identityPath,
|
|
205
|
+
translate: fallbackTranslate,
|
|
206
|
+
models: fallbackModels,
|
|
207
|
+
agentChat: {
|
|
208
|
+
sendToAgentChat: () => {},
|
|
209
|
+
setContextItem: () => {},
|
|
210
|
+
requestThreadOpen: () => {},
|
|
211
|
+
formatContextItems: () => "",
|
|
212
|
+
normalizeReference: (reference) =>
|
|
213
|
+
reference &&
|
|
214
|
+
typeof reference === "object" &&
|
|
215
|
+
typeof (reference as AgentComposerReference).label === "string" &&
|
|
216
|
+
typeof (reference as AgentComposerReference).refType === "string"
|
|
217
|
+
? (reference as AgentComposerReference)
|
|
218
|
+
: null,
|
|
219
|
+
StaleIndexBoundary: FragmentBoundary,
|
|
220
|
+
},
|
|
221
|
+
builder: {
|
|
222
|
+
useConnectFlow: () => fallbackBuilderFlow,
|
|
223
|
+
tryDelegateBuildRequest: () => false,
|
|
224
|
+
isTrustedFrameMessage: () => false,
|
|
225
|
+
isTrustedBuilderMessage: () => false,
|
|
226
|
+
},
|
|
227
|
+
resources: {
|
|
228
|
+
useOrg: () => ({ data: null }),
|
|
229
|
+
isMcpIntegrationAvailable: () => false,
|
|
230
|
+
useCreateMcpServer: () => ({ mutateAsync: async () => undefined }),
|
|
231
|
+
},
|
|
232
|
+
voice: {
|
|
233
|
+
useProviderStatus: () => ({ status: null, refresh: () => {} }),
|
|
234
|
+
getBrowserTabId: () => "",
|
|
235
|
+
readAppState: () => undefined,
|
|
236
|
+
setAppState: () => {},
|
|
237
|
+
subscribeSidebarState: () => () => {},
|
|
238
|
+
applyContextReplacements: applyVoiceContextReplacements,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const ComposerRuntimeAdaptersContext =
|
|
243
|
+
createContext<ComposerRuntimeAdapters>(fallbackAdapters);
|
|
244
|
+
|
|
245
|
+
export function ComposerRuntimeAdaptersProvider({
|
|
246
|
+
adapters,
|
|
247
|
+
children,
|
|
248
|
+
}: {
|
|
249
|
+
adapters: ComposerRuntimeAdapters;
|
|
250
|
+
children: ReactNode;
|
|
251
|
+
}) {
|
|
252
|
+
return (
|
|
253
|
+
<ComposerRuntimeAdaptersContext.Provider
|
|
254
|
+
value={{
|
|
255
|
+
...fallbackAdapters,
|
|
256
|
+
...adapters,
|
|
257
|
+
models: { ...fallbackModels, ...adapters.models },
|
|
258
|
+
agentChat: { ...fallbackAdapters.agentChat, ...adapters.agentChat },
|
|
259
|
+
builder: { ...fallbackAdapters.builder, ...adapters.builder },
|
|
260
|
+
resources: { ...fallbackAdapters.resources, ...adapters.resources },
|
|
261
|
+
voice: { ...fallbackAdapters.voice, ...adapters.voice },
|
|
262
|
+
}}
|
|
263
|
+
>
|
|
264
|
+
{children}
|
|
265
|
+
</ComposerRuntimeAdaptersContext.Provider>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function useComposerRuntimeAdapters() {
|
|
270
|
+
return useContext(ComposerRuntimeAdaptersContext);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export const DEFAULT_REASONING_EFFORT: ReasoningEffort = "medium";
|
|
274
|
+
export function getReasoningEffortOptionsForModel(
|
|
275
|
+
_model?: string,
|
|
276
|
+
): ReasoningEffort[] {
|
|
277
|
+
return ["low", "medium", "high"];
|
|
278
|
+
}
|
|
279
|
+
export function reasoningEffortLabel(effort: ReasoningEffort): string {
|
|
280
|
+
return effort === "xhigh"
|
|
281
|
+
? "Extra high"
|
|
282
|
+
: effort[0].toUpperCase() + effort.slice(1);
|
|
283
|
+
}
|
|
284
|
+
export function resolveReasoningEffortSelection(
|
|
285
|
+
_model: string,
|
|
286
|
+
effort?: ReasoningEffort,
|
|
287
|
+
): ReasoningEffort {
|
|
288
|
+
return effort ?? DEFAULT_REASONING_EFFORT;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export const AGENT_CHAT_INSERT_REFERENCE_EVENT =
|
|
292
|
+
"agent-native:insert-composer-reference";
|
|
293
|
+
export const AGENT_CHAT_INSERT_REFERENCE_MESSAGE_TYPE =
|
|
294
|
+
"agent-native:insert-composer-reference";
|
|
295
|
+
|
|
296
|
+
export function formatPromptContextItems(
|
|
297
|
+
items: AgentChatContextItem[] | undefined,
|
|
298
|
+
): string {
|
|
299
|
+
return (
|
|
300
|
+
items
|
|
301
|
+
?.map((item) => item.context ?? item.title ?? "")
|
|
302
|
+
.filter(Boolean)
|
|
303
|
+
.join("\n\n") ?? ""
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function applyVoiceContextReplacements(
|
|
308
|
+
text: string,
|
|
309
|
+
context: VoiceContextPack | undefined,
|
|
310
|
+
): string {
|
|
311
|
+
const isAlphaNumeric = (value: string | undefined) =>
|
|
312
|
+
value != null && /[\p{L}\p{N}_]/u.test(value);
|
|
313
|
+
const isSafeBoundary = (
|
|
314
|
+
value: string,
|
|
315
|
+
index: number,
|
|
316
|
+
direction: "before" | "after",
|
|
317
|
+
) => {
|
|
318
|
+
const char = value[index];
|
|
319
|
+
if (char == null) return true;
|
|
320
|
+
if (isAlphaNumeric(char) || "@/\\:".includes(char)) return false;
|
|
321
|
+
if (".-+".includes(char)) {
|
|
322
|
+
const neighbor =
|
|
323
|
+
direction === "before" ? value[index - 1] : value[index + 1];
|
|
324
|
+
return !isAlphaNumeric(neighbor);
|
|
325
|
+
}
|
|
326
|
+
return true;
|
|
327
|
+
};
|
|
328
|
+
let next = text;
|
|
329
|
+
for (const term of context?.terms ?? []) {
|
|
330
|
+
const source = term.term.trim();
|
|
331
|
+
const replacement = term.replacement?.trim();
|
|
332
|
+
if (!replacement || source.length < 2 || replacement === source) continue;
|
|
333
|
+
const escaped = source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
334
|
+
next = next.replace(new RegExp(escaped, "giu"), (match, offset: number) => {
|
|
335
|
+
const end = offset + match.length;
|
|
336
|
+
return isSafeBoundary(next, offset - 1, "before") &&
|
|
337
|
+
isSafeBoundary(next, end, "after")
|
|
338
|
+
? replacement
|
|
339
|
+
: match;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return next;
|
|
343
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export interface FileResult {
|
|
2
|
+
path: string;
|
|
3
|
+
name: string;
|
|
4
|
+
source: "codebase" | "resource";
|
|
5
|
+
type: "file" | "folder";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface SkillResult {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
path: string;
|
|
12
|
+
source: "codebase" | "resource";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MentionItem {
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
icon?: string;
|
|
20
|
+
source: string;
|
|
21
|
+
refType: string;
|
|
22
|
+
refPath?: string;
|
|
23
|
+
refId?: string;
|
|
24
|
+
section?: string;
|
|
25
|
+
slotKey?: string;
|
|
26
|
+
slotLabel?: string;
|
|
27
|
+
metadata?: Record<string, unknown>;
|
|
28
|
+
clearsSlots?: string[];
|
|
29
|
+
relatedReferences?: MentionReferenceInsert[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Reference {
|
|
33
|
+
type: "file" | "skill" | "mention" | "agent" | "custom-agent";
|
|
34
|
+
path: string;
|
|
35
|
+
name: string;
|
|
36
|
+
source: string;
|
|
37
|
+
refType?: string;
|
|
38
|
+
refId?: string;
|
|
39
|
+
slotKey?: string;
|
|
40
|
+
slotLabel?: string;
|
|
41
|
+
metadata?: Record<string, unknown>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface MentionReferenceInsert {
|
|
45
|
+
label: string;
|
|
46
|
+
icon?: string;
|
|
47
|
+
source?: string;
|
|
48
|
+
refType: string;
|
|
49
|
+
refId?: string | null;
|
|
50
|
+
refPath?: string | null;
|
|
51
|
+
slotKey?: string;
|
|
52
|
+
slotLabel?: string;
|
|
53
|
+
metadata?: Record<string, unknown>;
|
|
54
|
+
clearsSlots?: string[];
|
|
55
|
+
relatedReferences?: MentionReferenceInsert[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface SlashCommand {
|
|
59
|
+
name: string;
|
|
60
|
+
description: string;
|
|
61
|
+
icon?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type ComposerMode = "skill" | "job" | "automation" | "extension";
|
|
65
|
+
|
|
66
|
+
export type AgentComposerLayoutVariant = "default" | "compact" | "hero";
|