@athenaintel/react 0.26.3 → 0.26.5
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 +18 -0
- package/dist/auth/AthenaAuthProvider.d.ts +1 -1
- package/dist/auth/AthenaSSOProvider.d.ts +1 -1
- package/dist/chat/StatewireApprovalCard.cjs +68 -9
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +0 -31
- package/dist/chat/StatewireApprovalCard.js +70 -11
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat/super-grouping/CollapsibleGroup.d.ts +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.d.ts +2 -2
- package/dist/chat/super-grouping/ToolTimeline.d.ts +1 -1
- package/dist/chat/use-statewire-approval-request.cjs +6 -1
- package/dist/chat/use-statewire-approval-request.cjs.map +1 -1
- package/dist/chat/use-statewire-approval-request.d.ts +20 -0
- package/dist/chat/use-statewire-approval-request.js +6 -1
- package/dist/chat/use-statewire-approval-request.js.map +1 -1
- package/dist/chat-ui/header/chat-header.cjs +4 -4
- package/dist/chat-ui/header/chat-header.cjs.map +1 -1
- package/dist/chat-ui/header/chat-header.d.ts +3 -3
- package/dist/chat-ui/header/chat-header.js +1 -1
- package/dist/chat-ui/header/chat-header.js.map +1 -1
- package/dist/chat-ui/header/conversation-history-panel.d.ts +1 -1
- package/dist/chat-ui/index.cjs +8 -7
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +2 -1
- package/dist/chat-ui/index.js +2 -1
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/markdown-storage.cjs +2 -1
- package/dist/chat-ui/markdown-storage.cjs.map +1 -1
- package/dist/chat-ui/markdown-storage.d.ts +3 -0
- package/dist/chat-ui/markdown-storage.js +2 -1
- package/dist/chat-ui/markdown-storage.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/components/mention-icon.d.ts +1 -1
- package/dist/chat-ui/mentions/suggestions/components/menu-breadcrumbs.d.ts +1 -1
- package/dist/chat-ui/mentions/suggestions/components/menu-item.d.ts +1 -1
- package/dist/chat-ui/mentions/suggestions/components/menu-list.d.ts +1 -1
- package/dist/chat-ui/mentions/suggestions/components/menu-skeleton.d.ts +1 -1
- package/dist/chat-ui/mentions/suggestions/components/menu-summary.d.ts +1 -1
- package/dist/chat-ui/mentions/suggestions/shims.d.ts +1 -1
- package/dist/chat-ui/messages/message-components.cjs +13 -59
- package/dist/chat-ui/messages/message-components.cjs.map +1 -1
- package/dist/chat-ui/messages/message-components.d.ts +2 -14
- package/dist/chat-ui/messages/message-components.js +2 -47
- package/dist/chat-ui/messages/message-components.js.map +1 -1
- package/dist/chat-ui/messages/runtime-message.cjs +1 -1
- package/dist/chat-ui/messages/runtime-message.cjs.map +1 -1
- package/dist/chat-ui/messages/runtime-message.js +1 -1
- package/dist/chat-ui/messages/runtime-message.js.map +1 -1
- package/dist/chat-ui/messages/thread-chrome.cjs +59 -0
- package/dist/chat-ui/messages/thread-chrome.cjs.map +1 -0
- package/dist/chat-ui/messages/thread-chrome.d.ts +14 -0
- package/dist/chat-ui/messages/thread-chrome.js +59 -0
- package/dist/chat-ui/messages/thread-chrome.js.map +1 -0
- package/dist/chat-ui/messages/wake-marker.cjs +1 -1
- package/dist/chat-ui/messages/wake-marker.cjs.map +1 -1
- package/dist/chat-ui/messages/wake-marker.js +1 -1
- package/dist/chat-ui/messages/wake-marker.js.map +1 -1
- package/dist/chat-ui/rich-text/chat-link.d.ts +2 -2
- package/dist/chat-ui/rich-text/chat-message-body.d.ts +1 -1
- package/dist/chat-ui/rich-text/chat-node-views.d.ts +5 -5
- package/dist/chat-ui/rich-text/chat-text-schema.cjs +9 -1
- package/dist/chat-ui/rich-text/chat-text-schema.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-text-schema.d.ts +7 -0
- package/dist/chat-ui/rich-text/chat-text-schema.js +9 -1
- package/dist/chat-ui/rich-text/chat-text-schema.js.map +1 -1
- package/dist/chat-ui/thread/chat-composer-dock.d.ts +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.cjs +4 -3
- package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.d.ts +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.js +2 -1
- package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
- package/dist/composer/AttachmentContext.d.ts +1 -1
- package/dist/composer/QuoteContext.d.ts +1 -1
- package/dist/lib/posthog/before-send.cjs +12 -0
- package/dist/lib/posthog/before-send.cjs.map +1 -1
- package/dist/lib/posthog/before-send.js +12 -0
- package/dist/lib/posthog/before-send.js.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +2 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +2 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
- package/dist/packages/agent-runtime/src/messages.cjs +15 -1
- package/dist/packages/agent-runtime/src/messages.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/messages.js +15 -1
- package/dist/packages/agent-runtime/src/messages.js.map +1 -1
- package/dist/packages/react/package.json.cjs +1 -1
- package/dist/packages/react/package.json.js +1 -1
- package/dist/packages/tiptap-markdown-table/src/markdown-table-serializer.cjs +114 -0
- package/dist/packages/tiptap-markdown-table/src/markdown-table-serializer.cjs.map +1 -0
- package/dist/packages/tiptap-markdown-table/src/markdown-table-serializer.js +114 -0
- package/dist/packages/tiptap-markdown-table/src/markdown-table-serializer.js.map +1 -0
- package/dist/provider/AthenaProvider.d.ts +1 -1
- package/dist/provider/PostHogProvider.d.ts +1 -1
- package/dist/threads/SessionList.d.ts +1 -1
- package/dist/threads/ThreadList.d.ts +1 -1
- package/dist/threads/statewire-thread-list.d.ts +1 -1
- package/dist/tools/primitives.d.ts +3 -3
- package/dist/tools/tool-fallback.d.ts +8 -8
- package/dist/ui/button.d.ts +1 -1
- package/dist/ui/collapsible.d.ts +3 -3
- package/dist/ui/popover.d.ts +3 -3
- package/dist/ui/tooltip.d.ts +4 -4
- package/dist/voice/use-athena-voice-session.cjs.map +1 -1
- package/dist/voice/use-athena-voice-session.d.ts +5 -0
- package/dist/voice/use-athena-voice-session.js.map +1 -1
- package/package.json +3 -2
|
@@ -191,7 +191,7 @@ export declare function AthenaStatewireThreadListProvider({ backendUrl, syncUrl,
|
|
|
191
191
|
includePullRequests?: boolean;
|
|
192
192
|
snapshotCache?: DeepAgentThreadSnapshotCache;
|
|
193
193
|
children: ReactNode;
|
|
194
|
-
}): import("react
|
|
194
|
+
}): import("react").JSX.Element;
|
|
195
195
|
/** How long after a thread's first message the title poll starts (exported for tests). */
|
|
196
196
|
export declare const TITLE_POLL_DELAY_MS = 5000;
|
|
197
197
|
/**
|
|
@@ -32,7 +32,7 @@ export interface ToolCardProps {
|
|
|
32
32
|
* `ToolFallback` shell), so a dedicated UI and a generic fallback share one
|
|
33
33
|
* anatomy there; with no provider it is the boxed card below, unchanged.
|
|
34
34
|
*/
|
|
35
|
-
export declare function ToolCard(props: ToolCardProps): import("react
|
|
35
|
+
export declare function ToolCard(props: ToolCardProps): import("react").JSX.Element;
|
|
36
36
|
/** Copies `{ tool_name, arguments, result }` as JSON — the tool spec a support ticket needs. */
|
|
37
37
|
export declare function CopyToolSpecButton({ toolName, args, argsText, result, className, }: {
|
|
38
38
|
toolName: string;
|
|
@@ -40,9 +40,9 @@ export declare function CopyToolSpecButton({ toolName, args, argsText, result, c
|
|
|
40
40
|
argsText?: string;
|
|
41
41
|
result?: unknown;
|
|
42
42
|
className?: string;
|
|
43
|
-
}): import("react
|
|
43
|
+
}): import("react").JSX.Element;
|
|
44
44
|
export declare function ExpandableSection({ label, children, defaultOpen, }: {
|
|
45
45
|
label?: string;
|
|
46
46
|
children: ReactNode;
|
|
47
47
|
defaultOpen?: boolean;
|
|
48
|
-
}): import("react
|
|
48
|
+
}): import("react").JSX.Element;
|
|
@@ -62,7 +62,7 @@ export type ToolFallbackRootProps = Omit<React.ComponentProps<typeof Collapsible
|
|
|
62
62
|
defaultOpen?: boolean;
|
|
63
63
|
appearance?: ToolAppearance;
|
|
64
64
|
};
|
|
65
|
-
declare function ToolFallbackRoot({ className, open: controlledOpen, onOpenChange: controlledOnOpenChange, defaultOpen, appearance: appearanceProp, children, ...props }: ToolFallbackRootProps): import("react
|
|
65
|
+
declare function ToolFallbackRoot({ className, open: controlledOpen, onOpenChange: controlledOnOpenChange, defaultOpen, appearance: appearanceProp, children, ...props }: ToolFallbackRootProps): import("react").JSX.Element;
|
|
66
66
|
export interface ToolFallbackTriggerProps extends Omit<React.ComponentProps<typeof CollapsibleTrigger>, "children"> {
|
|
67
67
|
toolName: string;
|
|
68
68
|
argsText?: string;
|
|
@@ -80,18 +80,18 @@ export interface ToolFallbackTriggerProps extends Omit<React.ComponentProps<type
|
|
|
80
80
|
/** A short count or state chip after the target ("8 results"). */
|
|
81
81
|
badge?: string;
|
|
82
82
|
}
|
|
83
|
-
declare function ToolFallbackTrigger({ toolName, argsText, result, status, isError, classification: givenClassification, displayName, activeLabel, icon, appearance: appearanceProp, secondary: secondaryProp, badge, className, ...props }: ToolFallbackTriggerProps): import("react
|
|
83
|
+
declare function ToolFallbackTrigger({ toolName, argsText, result, status, isError, classification: givenClassification, displayName, activeLabel, icon, appearance: appearanceProp, secondary: secondaryProp, badge, className, ...props }: ToolFallbackTriggerProps): import("react").JSX.Element;
|
|
84
84
|
declare function ToolFallbackContent({ className, children, appearance: appearanceProp, ...props }: React.ComponentProps<typeof CollapsibleContent> & {
|
|
85
85
|
appearance?: ToolAppearance;
|
|
86
|
-
}): import("react
|
|
86
|
+
}): import("react").JSX.Element;
|
|
87
87
|
declare function ToolFallbackArgs({ argsText, hideInternalIdArgs, className, ...props }: React.ComponentProps<"div"> & {
|
|
88
88
|
argsText?: string;
|
|
89
89
|
hideInternalIdArgs?: boolean;
|
|
90
|
-
}): import("react
|
|
90
|
+
}): import("react").JSX.Element | null;
|
|
91
91
|
declare function ToolFallbackResult({ result, classification, className, ...props }: React.ComponentProps<"div"> & {
|
|
92
92
|
result?: unknown;
|
|
93
93
|
classification?: ToolResultClassification;
|
|
94
|
-
}): import("react
|
|
94
|
+
}): import("react").JSX.Element | null;
|
|
95
95
|
/**
|
|
96
96
|
* The failure a status-incomplete call carries, for the expanded body. The
|
|
97
97
|
* always-visible failure line is {@link ToolFallbackNotice}; this repeats the
|
|
@@ -99,7 +99,7 @@ declare function ToolFallbackResult({ result, classification, className, ...prop
|
|
|
99
99
|
*/
|
|
100
100
|
declare function ToolFallbackError({ status, className, ...props }: React.ComponentProps<"div"> & {
|
|
101
101
|
status?: ToolCallMessagePartStatus;
|
|
102
|
-
}): import("react
|
|
102
|
+
}): import("react").JSX.Element | null;
|
|
103
103
|
/**
|
|
104
104
|
* The always-visible line under the header for a failed or paused call — the
|
|
105
105
|
* failure's code and message, or the runtime's pause notice — so a reader
|
|
@@ -108,7 +108,7 @@ declare function ToolFallbackError({ status, className, ...props }: React.Compon
|
|
|
108
108
|
declare function ToolFallbackNotice({ classification, appearance: appearanceProp, className, ...props }: React.ComponentProps<"div"> & {
|
|
109
109
|
classification: ToolResultClassification;
|
|
110
110
|
appearance?: ToolAppearance;
|
|
111
|
-
}): import("react
|
|
111
|
+
}): import("react").JSX.Element | null;
|
|
112
112
|
/**
|
|
113
113
|
* Sets the tool-card anatomy for every tool call rendered below it — generic
|
|
114
114
|
* fallbacks, dedicated tool UIs built on `ToolCard`, nested PTC sub-calls.
|
|
@@ -118,7 +118,7 @@ declare function ToolFallbackNotice({ classification, appearance: appearanceProp
|
|
|
118
118
|
export declare function ToolFallbackAppearanceProvider({ appearance, children, }: {
|
|
119
119
|
appearance: ToolAppearance;
|
|
120
120
|
children?: ReactNode;
|
|
121
|
-
}): import("react
|
|
121
|
+
}): import("react").JSX.Element;
|
|
122
122
|
declare const ToolFallback: ToolCallMessagePartComponent & ((props: ToolFallbackProps) => ReactNode) & {
|
|
123
123
|
Root: typeof ToolFallbackRoot;
|
|
124
124
|
Trigger: typeof ToolFallbackTrigger;
|
package/dist/ui/button.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ declare const buttonVariants: (props?: ({
|
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
|
|
8
8
|
asChild?: boolean;
|
|
9
|
-
}):
|
|
9
|
+
}): React.JSX.Element;
|
|
10
10
|
export { Button, buttonVariants };
|
package/dist/ui/collapsible.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Collapsible as CollapsiblePrimitive } from "radix-ui";
|
|
2
|
-
declare function Collapsible({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Root>): import("react
|
|
3
|
-
declare function CollapsibleTrigger({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>): import("react
|
|
4
|
-
declare function CollapsibleContent({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>): import("react
|
|
2
|
+
declare function Collapsible({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Root>): import("react").JSX.Element;
|
|
3
|
+
declare function CollapsibleTrigger({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>): import("react").JSX.Element;
|
|
4
|
+
declare function CollapsibleContent({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>): import("react").JSX.Element;
|
|
5
5
|
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
package/dist/ui/popover.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Popover as PopoverPrimitive } from "radix-ui";
|
|
3
|
-
declare function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>):
|
|
4
|
-
declare function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>):
|
|
5
|
-
declare function PopoverContent({ className, style, align, sideOffset, collisionPadding, ...props }: React.ComponentProps<typeof PopoverPrimitive.Content>):
|
|
3
|
+
declare function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>): React.JSX.Element;
|
|
4
|
+
declare function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>): React.JSX.Element;
|
|
5
|
+
declare function PopoverContent({ className, style, align, sideOffset, collisionPadding, ...props }: React.ComponentProps<typeof PopoverPrimitive.Content>): React.JSX.Element;
|
|
6
6
|
export { Popover, PopoverTrigger, PopoverContent };
|
package/dist/ui/tooltip.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Tooltip as TooltipPrimitive } from "radix-ui";
|
|
3
|
-
declare function TooltipProvider({ delayDuration, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>):
|
|
4
|
-
declare function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>):
|
|
5
|
-
declare function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>):
|
|
6
|
-
declare function TooltipContent({ className, sideOffset, children, ...props }: React.ComponentProps<typeof TooltipPrimitive.Content>):
|
|
3
|
+
declare function TooltipProvider({ delayDuration, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>): React.JSX.Element;
|
|
4
|
+
declare function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>): React.JSX.Element;
|
|
5
|
+
declare function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>): React.JSX.Element;
|
|
6
|
+
declare function TooltipContent({ className, sideOffset, children, ...props }: React.ComponentProps<typeof TooltipPrimitive.Content>): React.JSX.Element;
|
|
7
7
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-athena-voice-session.cjs","sources":["../../src/voice/use-athena-voice-session.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n Room,\n RoomEvent,\n Track,\n type Participant,\n type RemoteTrack,\n type TranscriptionSegment,\n} from 'livekit-client';\nimport { type ParentBridge, useParentBridge, type ParentBridgeState } from '../runtime/auth';\nimport { isCredentialRefusal } from '../runtime/credential-renewal';\n\n/**\n * Realtime connection details for one Athena voice session. This is the shape\n * the Athena voice session endpoint returns; the hook only reads these\n * fields, so any server that proxies that call can hand it straight back.\n */\nexport interface AthenaVoiceSessionCredentials {\n /** Realtime signalling URL (`wss://…`). */\n server_url: string;\n /** Short-lived participant JWT scoped to `room_name`. */\n token: string;\n room_name?: string;\n /**\n * Athena session asset the voice run is persisted into. Use it with\n * `GET /api/v0/sessions/{asset_id}/download` to retrieve the transcript\n * after the call.\n */\n asset_id?: string;\n}\n\nexport type AthenaVoiceSessionStatus =\n | 'idle'\n | 'connecting'\n | 'connected'\n | 'reconnecting'\n | 'ended'\n | 'error';\n\nexport type AthenaVoiceSpeaker = 'user' | 'agent';\n\n/** One committed (final) transcription segment from either side of the call. */\nexport interface AthenaVoiceTranscriptTurn {\n id: string;\n speaker: AthenaVoiceSpeaker;\n text: string;\n /** Segment start, in ms since the room's epoch. */\n startTime: number;\n endTime: number;\n language: string;\n}\n\n/**\n * A voice tool the Athena voice agent can run. These are voice tool\n * names, not the chat `Toolkits` ids: `'web_search_browse_toolkit'` is not a\n * voice tool, `'web_search'` is. Only these names are accepted.\n *\n * The agent also has tools that call back into the client over room RPC\n * (clipboard, selection, screen contents, opening assets). This hook registers\n * no RPC handlers, so they are not part of its contract.\n */\nexport type AthenaVoiceToolId =\n /** Web search. */\n | 'web_search'\n /** Legacy alias of `web_search`; prefer `web_search`. */\n | 'search_web'\n /** Fetch and read one URL. */\n | 'browse'\n /** The workspace knowledge base, where configured. */\n | 'search_knowledge_base'\n /** Same knowledge base, thorough long-running retrieval. */\n | 'search_knowledge_base_lr'\n /** Read Athena assets by id, as the viewer. */\n | 'read_full_asset'\n /** Search the viewer's Athena assets. */\n | 'search_assets'\n /** Hand a question to the general agent and speak its answer. */\n | 'ask_agent'\n /** Save a note for the user (a stub on the worker today). */\n | 'save_note'\n /** Current date and time, Eastern. */\n | 'get_current_time_est';\n\n/**\n * Voice agent configuration for a session the hook creates itself (see\n * `UseAthenaVoiceSessionOptions.session`). Mirrors the body of the Athena\n * voice session endpoint; every field is optional and falls back to the\n * server default.\n */\nexport interface AthenaVoiceSessionConfig {\n /** Persona / instructions for the voice agent. */\n system_prompt?: string;\n /** Realtime provider, e.g. `'openai'` or `'google'`. */\n provider?: string;\n /** Provider model id, e.g. `'gpt-realtime-2'`. */\n model?: string;\n /** Provider voice id, e.g. `'marin'` (OpenAI) or `'Aoede'` (Google). */\n voice?: string;\n temperature?: number;\n /** BCP-47 language tag; default `'en'`. */\n language?: string;\n /**\n * Voice tools the agent may call. Omit it for the server's default set\n * (`web_search`, `search_knowledge_base`, `ask_agent`, …); pass `[]` for a\n * pure conversation with no tool calls.\n */\n enabled_tools?: AthenaVoiceToolId[];\n /** IANA timezone of the speaker, e.g. `'America/New_York'`. */\n timezone?: string;\n}\n\nexport interface UseAthenaVoiceSessionOptions {\n /**\n * Creates the voice session. Omit it when the app is served from a\n * Marathon computer-asset URL: the hook then calls the Athena voice session\n * endpoint itself with the viewer's bearer token from `/_athena/auth`, so\n * the session is created as — and shows up in the sessions of — whoever is\n * using the app.\n *\n * Works for workspace members and external SSO viewers alike: the scoped\n * `athview_` viewer credential is authorized for the voice session route and\n * `agents/general/invoke` the same way it is for `/api/v0/sessions`.\n *\n * Supply it when the app runs anywhere else (standalone, custom auth): it\n * should run on the caller's own server so the Athena API key never reaches\n * the browser. The hook only reads the returned credentials.\n *\n * Decide once: switching between supplying and omitting it across renders\n * of the same component is not supported.\n */\n createSession?: (\n signal: AbortSignal,\n ) => Promise<AthenaVoiceSessionCredentials>;\n /**\n * Agent configuration for sessions the hook creates itself. Ignored when\n * `createSession` is supplied.\n */\n session?: AthenaVoiceSessionConfig;\n /** Called once per committed transcript turn, in arrival order. */\n onTranscript?: (turn: AthenaVoiceTranscriptTurn) => void;\n /** Called with interim (non-final) transcript text as it streams in. */\n onInterimTranscript?: (speaker: AthenaVoiceSpeaker, text: string) => void;\n onError?: (error: Error) => void;\n /**\n * Where to play the agent's audio. Defaults to a detached `<audio>` element\n * the hook creates and removes itself.\n */\n audioElement?: HTMLMediaElement | null;\n /** Enable the microphone as soon as the room connects. Default `true`. */\n autoEnableMicrophone?: boolean;\n /** @internal Overrides `new Room()`; used by tests. */\n createRoom?: () => Room;\n /** @internal Overrides `useParentBridge()`; used by tests. */\n useBridge?: () => ParentBridgeState & Partial<Pick<ParentBridge, 'renewToken'>>;\n}\n\nexport interface UseAthenaVoiceSessionReturn {\n status: AthenaVoiceSessionStatus;\n /** True between `start()` resolving the session and the room connecting. */\n isConnecting: boolean;\n isConnected: boolean;\n /** Who is speaking right now, or `null` when the room is quiet. */\n activeSpeaker: AthenaVoiceSpeaker | null;\n isMuted: boolean;\n /** Committed transcript turns so far, in arrival order. */\n transcript: AthenaVoiceTranscriptTurn[];\n /** Athena session asset id, once the session has been created. */\n sessionId: string | null;\n roomName: string | null;\n error: Error | null;\n /** Creates a session and joins the room. No-op while a session is live. */\n start: () => Promise<void>;\n /** Leaves the room and stops local tracks. Safe to call at any time. */\n stop: () => Promise<void>;\n mute: () => Promise<void>;\n unmute: () => Promise<void>;\n /** Clears `transcript`; does not affect the live session. */\n clearTranscript: () => void;\n}\n\nconst toError = (value: unknown): Error =>\n value instanceof Error ? value : new Error(String(value));\n\nconst NO_BRIDGE: ParentBridgeState = {\n token: null,\n statewireSyncUrl: null,\n backendBaseUrl: null,\n appUrl: null,\n hostAnswered: false,\n ready: true,\n};\n// Stands in for `useParentBridge` when the caller creates sessions itself, so\n// the hook never fetches `/_athena/auth` it would not use.\nconst useNoBridge = (): ParentBridgeState => NO_BRIDGE;\n\nconst NO_VIEWER_TOKEN_MESSAGE =\n 'useAthenaVoiceSession: no Athena viewer token. Either serve the app from ' +\n 'a Marathon computer-asset URL (which mints one via /_athena/auth) or pass ' +\n '`createSession` to create the session through your own server.';\n\n/**\n * Creates an Athena voice session as the current viewer. Exported so apps with\n * their own bridge state (or a server proxy holding a user bearer) can reuse\n * the exact request the hook makes.\n */\nexport const createAthenaVoiceSession = async ({\n backendBaseUrl,\n token,\n session = {},\n signal,\n}: {\n /** Bare Athena API base, e.g. `https://api.athenaintel.com`. */\n backendBaseUrl: string;\n /** Bearer token for the user the session belongs to. */\n token: string;\n session?: AthenaVoiceSessionConfig;\n signal?: AbortSignal;\n}): Promise<AthenaVoiceSessionCredentials> => {\n const url: string = `${backendBaseUrl.replace(/\\/+$/, '')}/api/orpheus/create-session`;\n const response: Response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify(session),\n signal,\n });\n if (!response.ok) {\n const detail: string = await response.text().catch(() => '');\n // `status` lets a caller tell a refused credential (401) from any other failure.\n throw Object.assign(\n new Error(\n `Athena voice session creation failed (${response.status})${detail ? `: ${detail}` : ''}`,\n ),\n { status: response.status },\n );\n }\n return (await response.json()) as AthenaVoiceSessionCredentials;\n};\n\nconst speakerOf = (\n participant: Participant | undefined,\n room: Room,\n): AthenaVoiceSpeaker =>\n participant !== undefined &&\n participant.identity === room.localParticipant.identity\n ? 'user'\n : 'agent';\n\n/**\n * Runs one Athena voice session from React.\n *\n * Owns the whole browser-side lifecycle the voice API leaves to the caller:\n * creating the session through the app's own server, joining the room,\n * publishing the microphone, playing the agent's audio, tracking who is\n * speaking, committing transcript turns from room transcription events, and\n * tearing everything down on `stop()` or unmount. A second `start()` while a\n * session is live (or still connecting) is ignored, so React StrictMode's\n * double-invoked effects cannot dispatch two agents into one room.\n */\nexport const useAthenaVoiceSession = (\n options: UseAthenaVoiceSessionOptions = {},\n): UseAthenaVoiceSessionReturn => {\n const {\n createSession,\n session,\n onTranscript,\n onInterimTranscript,\n onError,\n audioElement,\n autoEnableMicrophone = true,\n createRoom,\n useBridge = createSession ? useNoBridge : useParentBridge,\n } = options;\n\n // Viewer identity minted by Marathon (`/_athena/auth`) or the Athena app\n // iframe bridge. Only consulted when the caller did not supply `createSession`.\n const bridge: ParentBridgeState & Partial<Pick<ParentBridge, 'renewToken'>> = useBridge();\n const bridgeRef = useRef(bridge);\n bridgeRef.current = bridge;\n const bridgeReadyWaitersRef = useRef<Array<() => void>>([]);\n useEffect(() => {\n if (!bridge.ready) return;\n const waiters: Array<() => void> = bridgeReadyWaitersRef.current;\n bridgeReadyWaitersRef.current = [];\n for (const resolve of waiters) resolve();\n }, [bridge.ready]);\n\n const sessionRef = useRef<AthenaVoiceSessionConfig | undefined>(session);\n sessionRef.current = session;\n\n const createSessionAsViewer = useCallback(\n async (signal: AbortSignal): Promise<AthenaVoiceSessionCredentials> => {\n if (!bridgeRef.current.ready) {\n await new Promise<void>((resolve) => {\n bridgeReadyWaitersRef.current.push(resolve);\n signal.addEventListener('abort', () => resolve(), { once: true });\n });\n if (signal.aborted) throw new Error('aborted');\n }\n const { token, backendBaseUrl, renewToken } = bridgeRef.current;\n if (!token || !backendBaseUrl) throw new Error(NO_VIEWER_TOKEN_MESSAGE);\n const create = (viewerToken: string) =>\n createAthenaVoiceSession({\n backendBaseUrl,\n token: viewerToken,\n session: sessionRef.current,\n signal,\n });\n try {\n return await create(token);\n } catch (error) {\n // The host refused the viewer token: renew it through the bridge (a\n // Marathon re-mint or the parent window's next token) and try once more.\n if (!isCredentialRefusal(error) || !renewToken) throw error;\n const renewed = await renewToken({ force: true });\n if (!renewed || renewed === token) throw error;\n return create(renewed);\n }\n },\n [],\n );\n\n const [status, setStatus] = useState<AthenaVoiceSessionStatus>('idle');\n const [activeSpeaker, setActiveSpeaker] = useState<AthenaVoiceSpeaker | null>(\n null,\n );\n const [isMuted, setIsMuted] = useState<boolean>(false);\n const [transcript, setTranscript] = useState<AthenaVoiceTranscriptTurn[]>([]);\n const [sessionId, setSessionId] = useState<string | null>(null);\n const [roomName, setRoomName] = useState<string | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n // Latest callbacks without re-subscribing room listeners on every render.\n const callbacksRef = useRef({\n createSession: createSession ?? createSessionAsViewer,\n onTranscript,\n onInterimTranscript,\n onError,\n });\n callbacksRef.current = {\n createSession: createSession ?? createSessionAsViewer,\n onTranscript,\n onInterimTranscript,\n onError,\n };\n\n const roomRef = useRef<Room | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const attachedElementsRef = useRef<HTMLMediaElement[]>([]);\n const ownedAudioRef = useRef<HTMLMediaElement | null>(null);\n const committedSegmentIdsRef = useRef<Set<string>>(new Set());\n // Guards `start()` against re-entry from the moment it is called, before any\n // state update lands — a StrictMode double effect fires both calls\n // synchronously in the same tick.\n const startingRef = useRef<boolean>(false);\n\n const fail = useCallback((cause: unknown) => {\n const err: Error = toError(cause);\n setError(err);\n setStatus('error');\n callbacksRef.current.onError?.(err);\n }, []);\n\n const detachAudio = useCallback(() => {\n for (const element of attachedElementsRef.current) {\n element.srcObject = null;\n if (element === ownedAudioRef.current) element.remove();\n }\n attachedElementsRef.current = [];\n ownedAudioRef.current = null;\n }, []);\n\n const teardown = useCallback(async () => {\n abortRef.current?.abort();\n abortRef.current = null;\n const room: Room | null = roomRef.current;\n roomRef.current = null;\n startingRef.current = false;\n detachAudio();\n if (room) {\n room.removeAllListeners();\n await room.disconnect(true);\n }\n }, [detachAudio]);\n\n const stop = useCallback(async () => {\n const hadRoom: boolean =\n roomRef.current !== null || abortRef.current !== null;\n await teardown();\n setActiveSpeaker(null);\n if (hadRoom) setStatus('ended');\n }, [teardown]);\n\n const attachTrack = useCallback(\n (track: RemoteTrack) => {\n if (track.kind !== Track.Kind.Audio) return;\n if (audioElement) {\n track.attach(audioElement);\n attachedElementsRef.current.push(audioElement);\n return;\n }\n const element: HTMLMediaElement = track.attach();\n element.setAttribute('data-athena-voice-audio', '');\n document.body.appendChild(element);\n ownedAudioRef.current = element;\n attachedElementsRef.current.push(element);\n },\n [audioElement],\n );\n\n const handleTranscription = useCallback(\n (\n segments: TranscriptionSegment[],\n participant: Participant | undefined,\n room: Room,\n ) => {\n const speaker: AthenaVoiceSpeaker = speakerOf(participant, room);\n const committed: AthenaVoiceTranscriptTurn[] = [];\n for (const segment of segments) {\n if (!segment.final) {\n callbacksRef.current.onInterimTranscript?.(speaker, segment.text);\n continue;\n }\n if (committedSegmentIdsRef.current.has(segment.id)) continue;\n committedSegmentIdsRef.current.add(segment.id);\n committed.push({\n id: segment.id,\n speaker,\n text: segment.text,\n startTime: segment.startTime,\n endTime: segment.endTime,\n language: segment.language,\n });\n }\n if (committed.length === 0) return;\n setTranscript((previous) => [...previous, ...committed]);\n for (const turn of committed) callbacksRef.current.onTranscript?.(turn);\n },\n [],\n );\n\n const start = useCallback(async () => {\n if (startingRef.current || roomRef.current) return;\n startingRef.current = true;\n setError(null);\n setSessionId(null);\n setRoomName(null);\n setStatus('connecting');\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n let credentials: AthenaVoiceSessionCredentials;\n try {\n credentials = await callbacksRef.current.createSession(controller.signal);\n } catch (cause) {\n if (controller.signal.aborted) return;\n abortRef.current = null;\n startingRef.current = false;\n fail(cause);\n return;\n }\n if (controller.signal.aborted) return;\n\n setSessionId(credentials.asset_id ?? null);\n setRoomName(credentials.room_name ?? null);\n\n const room: Room = createRoom ? createRoom() : new Room();\n roomRef.current = room;\n\n room\n .on(RoomEvent.TrackSubscribed, attachTrack)\n .on(RoomEvent.ActiveSpeakersChanged, (speakers: Participant[]) => {\n const first: Participant | undefined = speakers[0];\n setActiveSpeaker(first ? speakerOf(first, room) : null);\n })\n .on(RoomEvent.TranscriptionReceived, (segments, participant) =>\n handleTranscription(segments, participant, room),\n )\n .on(RoomEvent.Reconnecting, () => setStatus('reconnecting'))\n .on(RoomEvent.Reconnected, () => setStatus('connected'))\n .on(RoomEvent.Disconnected, () => {\n if (roomRef.current !== room) return;\n roomRef.current = null;\n startingRef.current = false;\n detachAudio();\n setActiveSpeaker(null);\n setStatus('ended');\n })\n .on(RoomEvent.MediaDevicesError, fail);\n\n try {\n await room.connect(credentials.server_url, credentials.token);\n if (controller.signal.aborted) return;\n if (autoEnableMicrophone) {\n await room.localParticipant.setMicrophoneEnabled(true);\n setIsMuted(false);\n } else {\n setIsMuted(true);\n }\n if (controller.signal.aborted) return;\n abortRef.current = null;\n startingRef.current = false;\n setStatus('connected');\n } catch (cause) {\n if (controller.signal.aborted) return;\n await teardown();\n fail(cause);\n }\n }, [\n attachTrack,\n autoEnableMicrophone,\n createRoom,\n detachAudio,\n fail,\n handleTranscription,\n teardown,\n ]);\n\n const setMicrophone = useCallback(async (enabled: boolean) => {\n const room: Room | null = roomRef.current;\n if (!room) return;\n await room.localParticipant.setMicrophoneEnabled(enabled);\n setIsMuted(!enabled);\n }, []);\n const mute = useCallback(() => setMicrophone(false), [setMicrophone]);\n const unmute = useCallback(() => setMicrophone(true), [setMicrophone]);\n\n const clearTranscript = useCallback(() => {\n committedSegmentIdsRef.current.clear();\n setTranscript([]);\n }, []);\n\n // Unmount teardown is deferred one tick so StrictMode's synchronous\n // unmount/remount of the same instance does not abort an in-flight\n // `createSession` (which would dispatch a second voice agent on remount).\n const unmountTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(() => {\n if (unmountTimerRef.current !== null) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n return () => {\n unmountTimerRef.current = setTimeout(() => {\n unmountTimerRef.current = null;\n void teardown();\n }, 0);\n };\n }, [teardown]);\n\n return useMemo<UseAthenaVoiceSessionReturn>(\n () => ({\n status,\n isConnecting: status === 'connecting',\n isConnected: status === 'connected' || status === 'reconnecting',\n activeSpeaker,\n isMuted,\n transcript,\n sessionId,\n roomName,\n error,\n start,\n stop,\n mute,\n unmute,\n clearTranscript,\n }),\n [\n status,\n activeSpeaker,\n isMuted,\n transcript,\n sessionId,\n roomName,\n error,\n start,\n stop,\n mute,\n unmute,\n clearTranscript,\n ],\n );\n};\n"],"names":["useParentBridge","useRef","useEffect","useCallback","error","isCredentialRefusal","useState","Track","Room","RoomEvent","useMemo"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-athena-voice-session.cjs","sources":["../../src/voice/use-athena-voice-session.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n Room,\n RoomEvent,\n Track,\n type Participant,\n type RemoteTrack,\n type TranscriptionSegment,\n} from 'livekit-client';\nimport { type ParentBridge, useParentBridge, type ParentBridgeState } from '../runtime/auth';\nimport { isCredentialRefusal } from '../runtime/credential-renewal';\n\n/**\n * Realtime connection details for one Athena voice session. This is the shape\n * the Athena voice session endpoint returns; the hook only reads these\n * fields, so any server that proxies that call can hand it straight back.\n */\nexport interface AthenaVoiceSessionCredentials {\n /** Realtime signalling URL (`wss://…`). */\n server_url: string;\n /** Short-lived participant JWT scoped to `room_name`. */\n token: string;\n room_name?: string;\n /**\n * Athena session asset the voice run is persisted into. Use it with\n * `GET /api/v0/sessions/{asset_id}/download` to retrieve the transcript\n * after the call.\n */\n asset_id?: string;\n}\n\nexport type AthenaVoiceSessionStatus =\n | 'idle'\n | 'connecting'\n | 'connected'\n | 'reconnecting'\n | 'ended'\n | 'error';\n\nexport type AthenaVoiceSpeaker = 'user' | 'agent';\n\n/** One committed (final) transcription segment from either side of the call. */\nexport interface AthenaVoiceTranscriptTurn {\n id: string;\n speaker: AthenaVoiceSpeaker;\n text: string;\n /** Segment start, in ms since the room's epoch. */\n startTime: number;\n endTime: number;\n language: string;\n}\n\n/**\n * A voice tool the Athena voice agent can run. These are voice tool\n * names, not the chat `Toolkits` ids: `'web_search_browse_toolkit'` is not a\n * voice tool, `'web_search'` is. Only these names are accepted.\n *\n * The agent also has tools that call back into the client over room RPC\n * (clipboard, selection, screen contents, opening assets). This hook registers\n * no RPC handlers, so they are not part of its contract.\n */\nexport type AthenaVoiceToolId =\n /** Web search. */\n | 'web_search'\n /** Legacy alias of `web_search`; prefer `web_search`. */\n | 'search_web'\n /** Fetch and read one URL. */\n | 'browse'\n /** The workspace knowledge base, where configured. */\n | 'search_knowledge_base'\n /** Same knowledge base, thorough long-running retrieval. */\n | 'search_knowledge_base_lr'\n /** Read Athena assets by id, as the viewer. */\n | 'read_full_asset'\n /** Search the viewer's Athena assets. */\n | 'search_assets'\n /** Hand a question to the general agent and speak its answer. */\n | 'ask_agent'\n /** Save a note for the user (a stub on the worker today). */\n | 'save_note'\n /** Current date and time, Eastern. */\n | 'get_current_time_est';\n\n/**\n * Voice agent configuration for a session the hook creates itself (see\n * `UseAthenaVoiceSessionOptions.session`). Mirrors the body of the Athena\n * voice session endpoint; every field is optional and falls back to the\n * server default.\n */\nexport interface AthenaVoiceSessionConfig {\n /** Persona / instructions for the voice agent. */\n system_prompt?: string;\n /** Realtime provider, e.g. `'openai'` or `'google'`. */\n provider?: string;\n /** Provider model id, e.g. `'gpt-realtime-2'`. */\n model?: string;\n /** Provider voice id, e.g. `'marin'` (OpenAI) or `'Aoede'` (Google). */\n voice?: string;\n temperature?: number;\n /** BCP-47 language tag; default `'en'`. */\n language?: string;\n /**\n * Voice tools the agent may call. Omit it for the server's default set\n * (`web_search`, `search_knowledge_base`, `ask_agent`, …); pass `[]` for a\n * pure conversation with no tool calls.\n */\n enabled_tools?: AthenaVoiceToolId[];\n /** IANA timezone of the speaker, e.g. `'America/New_York'`. */\n timezone?: string;\n /**\n * Start without the agent's opening greeting: it stays quiet (no audio, no\n * transcript turn) until a participant speaks. Default `false`.\n */\n silent_start?: boolean;\n}\n\nexport interface UseAthenaVoiceSessionOptions {\n /**\n * Creates the voice session. Omit it when the app is served from a\n * Marathon computer-asset URL: the hook then calls the Athena voice session\n * endpoint itself with the viewer's bearer token from `/_athena/auth`, so\n * the session is created as — and shows up in the sessions of — whoever is\n * using the app.\n *\n * Works for workspace members and external SSO viewers alike: the scoped\n * `athview_` viewer credential is authorized for the voice session route and\n * `agents/general/invoke` the same way it is for `/api/v0/sessions`.\n *\n * Supply it when the app runs anywhere else (standalone, custom auth): it\n * should run on the caller's own server so the Athena API key never reaches\n * the browser. The hook only reads the returned credentials.\n *\n * Decide once: switching between supplying and omitting it across renders\n * of the same component is not supported.\n */\n createSession?: (\n signal: AbortSignal,\n ) => Promise<AthenaVoiceSessionCredentials>;\n /**\n * Agent configuration for sessions the hook creates itself. Ignored when\n * `createSession` is supplied.\n */\n session?: AthenaVoiceSessionConfig;\n /** Called once per committed transcript turn, in arrival order. */\n onTranscript?: (turn: AthenaVoiceTranscriptTurn) => void;\n /** Called with interim (non-final) transcript text as it streams in. */\n onInterimTranscript?: (speaker: AthenaVoiceSpeaker, text: string) => void;\n onError?: (error: Error) => void;\n /**\n * Where to play the agent's audio. Defaults to a detached `<audio>` element\n * the hook creates and removes itself.\n */\n audioElement?: HTMLMediaElement | null;\n /** Enable the microphone as soon as the room connects. Default `true`. */\n autoEnableMicrophone?: boolean;\n /** @internal Overrides `new Room()`; used by tests. */\n createRoom?: () => Room;\n /** @internal Overrides `useParentBridge()`; used by tests. */\n useBridge?: () => ParentBridgeState & Partial<Pick<ParentBridge, 'renewToken'>>;\n}\n\nexport interface UseAthenaVoiceSessionReturn {\n status: AthenaVoiceSessionStatus;\n /** True between `start()` resolving the session and the room connecting. */\n isConnecting: boolean;\n isConnected: boolean;\n /** Who is speaking right now, or `null` when the room is quiet. */\n activeSpeaker: AthenaVoiceSpeaker | null;\n isMuted: boolean;\n /** Committed transcript turns so far, in arrival order. */\n transcript: AthenaVoiceTranscriptTurn[];\n /** Athena session asset id, once the session has been created. */\n sessionId: string | null;\n roomName: string | null;\n error: Error | null;\n /** Creates a session and joins the room. No-op while a session is live. */\n start: () => Promise<void>;\n /** Leaves the room and stops local tracks. Safe to call at any time. */\n stop: () => Promise<void>;\n mute: () => Promise<void>;\n unmute: () => Promise<void>;\n /** Clears `transcript`; does not affect the live session. */\n clearTranscript: () => void;\n}\n\nconst toError = (value: unknown): Error =>\n value instanceof Error ? value : new Error(String(value));\n\nconst NO_BRIDGE: ParentBridgeState = {\n token: null,\n statewireSyncUrl: null,\n backendBaseUrl: null,\n appUrl: null,\n hostAnswered: false,\n ready: true,\n};\n// Stands in for `useParentBridge` when the caller creates sessions itself, so\n// the hook never fetches `/_athena/auth` it would not use.\nconst useNoBridge = (): ParentBridgeState => NO_BRIDGE;\n\nconst NO_VIEWER_TOKEN_MESSAGE =\n 'useAthenaVoiceSession: no Athena viewer token. Either serve the app from ' +\n 'a Marathon computer-asset URL (which mints one via /_athena/auth) or pass ' +\n '`createSession` to create the session through your own server.';\n\n/**\n * Creates an Athena voice session as the current viewer. Exported so apps with\n * their own bridge state (or a server proxy holding a user bearer) can reuse\n * the exact request the hook makes.\n */\nexport const createAthenaVoiceSession = async ({\n backendBaseUrl,\n token,\n session = {},\n signal,\n}: {\n /** Bare Athena API base, e.g. `https://api.athenaintel.com`. */\n backendBaseUrl: string;\n /** Bearer token for the user the session belongs to. */\n token: string;\n session?: AthenaVoiceSessionConfig;\n signal?: AbortSignal;\n}): Promise<AthenaVoiceSessionCredentials> => {\n const url: string = `${backendBaseUrl.replace(/\\/+$/, '')}/api/orpheus/create-session`;\n const response: Response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify(session),\n signal,\n });\n if (!response.ok) {\n const detail: string = await response.text().catch(() => '');\n // `status` lets a caller tell a refused credential (401) from any other failure.\n throw Object.assign(\n new Error(\n `Athena voice session creation failed (${response.status})${detail ? `: ${detail}` : ''}`,\n ),\n { status: response.status },\n );\n }\n return (await response.json()) as AthenaVoiceSessionCredentials;\n};\n\nconst speakerOf = (\n participant: Participant | undefined,\n room: Room,\n): AthenaVoiceSpeaker =>\n participant !== undefined &&\n participant.identity === room.localParticipant.identity\n ? 'user'\n : 'agent';\n\n/**\n * Runs one Athena voice session from React.\n *\n * Owns the whole browser-side lifecycle the voice API leaves to the caller:\n * creating the session through the app's own server, joining the room,\n * publishing the microphone, playing the agent's audio, tracking who is\n * speaking, committing transcript turns from room transcription events, and\n * tearing everything down on `stop()` or unmount. A second `start()` while a\n * session is live (or still connecting) is ignored, so React StrictMode's\n * double-invoked effects cannot dispatch two agents into one room.\n */\nexport const useAthenaVoiceSession = (\n options: UseAthenaVoiceSessionOptions = {},\n): UseAthenaVoiceSessionReturn => {\n const {\n createSession,\n session,\n onTranscript,\n onInterimTranscript,\n onError,\n audioElement,\n autoEnableMicrophone = true,\n createRoom,\n useBridge = createSession ? useNoBridge : useParentBridge,\n } = options;\n\n // Viewer identity minted by Marathon (`/_athena/auth`) or the Athena app\n // iframe bridge. Only consulted when the caller did not supply `createSession`.\n const bridge: ParentBridgeState & Partial<Pick<ParentBridge, 'renewToken'>> = useBridge();\n const bridgeRef = useRef(bridge);\n bridgeRef.current = bridge;\n const bridgeReadyWaitersRef = useRef<Array<() => void>>([]);\n useEffect(() => {\n if (!bridge.ready) return;\n const waiters: Array<() => void> = bridgeReadyWaitersRef.current;\n bridgeReadyWaitersRef.current = [];\n for (const resolve of waiters) resolve();\n }, [bridge.ready]);\n\n const sessionRef = useRef<AthenaVoiceSessionConfig | undefined>(session);\n sessionRef.current = session;\n\n const createSessionAsViewer = useCallback(\n async (signal: AbortSignal): Promise<AthenaVoiceSessionCredentials> => {\n if (!bridgeRef.current.ready) {\n await new Promise<void>((resolve) => {\n bridgeReadyWaitersRef.current.push(resolve);\n signal.addEventListener('abort', () => resolve(), { once: true });\n });\n if (signal.aborted) throw new Error('aborted');\n }\n const { token, backendBaseUrl, renewToken } = bridgeRef.current;\n if (!token || !backendBaseUrl) throw new Error(NO_VIEWER_TOKEN_MESSAGE);\n const create = (viewerToken: string) =>\n createAthenaVoiceSession({\n backendBaseUrl,\n token: viewerToken,\n session: sessionRef.current,\n signal,\n });\n try {\n return await create(token);\n } catch (error) {\n // The host refused the viewer token: renew it through the bridge (a\n // Marathon re-mint or the parent window's next token) and try once more.\n if (!isCredentialRefusal(error) || !renewToken) throw error;\n const renewed = await renewToken({ force: true });\n if (!renewed || renewed === token) throw error;\n return create(renewed);\n }\n },\n [],\n );\n\n const [status, setStatus] = useState<AthenaVoiceSessionStatus>('idle');\n const [activeSpeaker, setActiveSpeaker] = useState<AthenaVoiceSpeaker | null>(\n null,\n );\n const [isMuted, setIsMuted] = useState<boolean>(false);\n const [transcript, setTranscript] = useState<AthenaVoiceTranscriptTurn[]>([]);\n const [sessionId, setSessionId] = useState<string | null>(null);\n const [roomName, setRoomName] = useState<string | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n // Latest callbacks without re-subscribing room listeners on every render.\n const callbacksRef = useRef({\n createSession: createSession ?? createSessionAsViewer,\n onTranscript,\n onInterimTranscript,\n onError,\n });\n callbacksRef.current = {\n createSession: createSession ?? createSessionAsViewer,\n onTranscript,\n onInterimTranscript,\n onError,\n };\n\n const roomRef = useRef<Room | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const attachedElementsRef = useRef<HTMLMediaElement[]>([]);\n const ownedAudioRef = useRef<HTMLMediaElement | null>(null);\n const committedSegmentIdsRef = useRef<Set<string>>(new Set());\n // Guards `start()` against re-entry from the moment it is called, before any\n // state update lands — a StrictMode double effect fires both calls\n // synchronously in the same tick.\n const startingRef = useRef<boolean>(false);\n\n const fail = useCallback((cause: unknown) => {\n const err: Error = toError(cause);\n setError(err);\n setStatus('error');\n callbacksRef.current.onError?.(err);\n }, []);\n\n const detachAudio = useCallback(() => {\n for (const element of attachedElementsRef.current) {\n element.srcObject = null;\n if (element === ownedAudioRef.current) element.remove();\n }\n attachedElementsRef.current = [];\n ownedAudioRef.current = null;\n }, []);\n\n const teardown = useCallback(async () => {\n abortRef.current?.abort();\n abortRef.current = null;\n const room: Room | null = roomRef.current;\n roomRef.current = null;\n startingRef.current = false;\n detachAudio();\n if (room) {\n room.removeAllListeners();\n await room.disconnect(true);\n }\n }, [detachAudio]);\n\n const stop = useCallback(async () => {\n const hadRoom: boolean =\n roomRef.current !== null || abortRef.current !== null;\n await teardown();\n setActiveSpeaker(null);\n if (hadRoom) setStatus('ended');\n }, [teardown]);\n\n const attachTrack = useCallback(\n (track: RemoteTrack) => {\n if (track.kind !== Track.Kind.Audio) return;\n if (audioElement) {\n track.attach(audioElement);\n attachedElementsRef.current.push(audioElement);\n return;\n }\n const element: HTMLMediaElement = track.attach();\n element.setAttribute('data-athena-voice-audio', '');\n document.body.appendChild(element);\n ownedAudioRef.current = element;\n attachedElementsRef.current.push(element);\n },\n [audioElement],\n );\n\n const handleTranscription = useCallback(\n (\n segments: TranscriptionSegment[],\n participant: Participant | undefined,\n room: Room,\n ) => {\n const speaker: AthenaVoiceSpeaker = speakerOf(participant, room);\n const committed: AthenaVoiceTranscriptTurn[] = [];\n for (const segment of segments) {\n if (!segment.final) {\n callbacksRef.current.onInterimTranscript?.(speaker, segment.text);\n continue;\n }\n if (committedSegmentIdsRef.current.has(segment.id)) continue;\n committedSegmentIdsRef.current.add(segment.id);\n committed.push({\n id: segment.id,\n speaker,\n text: segment.text,\n startTime: segment.startTime,\n endTime: segment.endTime,\n language: segment.language,\n });\n }\n if (committed.length === 0) return;\n setTranscript((previous) => [...previous, ...committed]);\n for (const turn of committed) callbacksRef.current.onTranscript?.(turn);\n },\n [],\n );\n\n const start = useCallback(async () => {\n if (startingRef.current || roomRef.current) return;\n startingRef.current = true;\n setError(null);\n setSessionId(null);\n setRoomName(null);\n setStatus('connecting');\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n let credentials: AthenaVoiceSessionCredentials;\n try {\n credentials = await callbacksRef.current.createSession(controller.signal);\n } catch (cause) {\n if (controller.signal.aborted) return;\n abortRef.current = null;\n startingRef.current = false;\n fail(cause);\n return;\n }\n if (controller.signal.aborted) return;\n\n setSessionId(credentials.asset_id ?? null);\n setRoomName(credentials.room_name ?? null);\n\n const room: Room = createRoom ? createRoom() : new Room();\n roomRef.current = room;\n\n room\n .on(RoomEvent.TrackSubscribed, attachTrack)\n .on(RoomEvent.ActiveSpeakersChanged, (speakers: Participant[]) => {\n const first: Participant | undefined = speakers[0];\n setActiveSpeaker(first ? speakerOf(first, room) : null);\n })\n .on(RoomEvent.TranscriptionReceived, (segments, participant) =>\n handleTranscription(segments, participant, room),\n )\n .on(RoomEvent.Reconnecting, () => setStatus('reconnecting'))\n .on(RoomEvent.Reconnected, () => setStatus('connected'))\n .on(RoomEvent.Disconnected, () => {\n if (roomRef.current !== room) return;\n roomRef.current = null;\n startingRef.current = false;\n detachAudio();\n setActiveSpeaker(null);\n setStatus('ended');\n })\n .on(RoomEvent.MediaDevicesError, fail);\n\n try {\n await room.connect(credentials.server_url, credentials.token);\n if (controller.signal.aborted) return;\n if (autoEnableMicrophone) {\n await room.localParticipant.setMicrophoneEnabled(true);\n setIsMuted(false);\n } else {\n setIsMuted(true);\n }\n if (controller.signal.aborted) return;\n abortRef.current = null;\n startingRef.current = false;\n setStatus('connected');\n } catch (cause) {\n if (controller.signal.aborted) return;\n await teardown();\n fail(cause);\n }\n }, [\n attachTrack,\n autoEnableMicrophone,\n createRoom,\n detachAudio,\n fail,\n handleTranscription,\n teardown,\n ]);\n\n const setMicrophone = useCallback(async (enabled: boolean) => {\n const room: Room | null = roomRef.current;\n if (!room) return;\n await room.localParticipant.setMicrophoneEnabled(enabled);\n setIsMuted(!enabled);\n }, []);\n const mute = useCallback(() => setMicrophone(false), [setMicrophone]);\n const unmute = useCallback(() => setMicrophone(true), [setMicrophone]);\n\n const clearTranscript = useCallback(() => {\n committedSegmentIdsRef.current.clear();\n setTranscript([]);\n }, []);\n\n // Unmount teardown is deferred one tick so StrictMode's synchronous\n // unmount/remount of the same instance does not abort an in-flight\n // `createSession` (which would dispatch a second voice agent on remount).\n const unmountTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(() => {\n if (unmountTimerRef.current !== null) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n return () => {\n unmountTimerRef.current = setTimeout(() => {\n unmountTimerRef.current = null;\n void teardown();\n }, 0);\n };\n }, [teardown]);\n\n return useMemo<UseAthenaVoiceSessionReturn>(\n () => ({\n status,\n isConnecting: status === 'connecting',\n isConnected: status === 'connected' || status === 'reconnecting',\n activeSpeaker,\n isMuted,\n transcript,\n sessionId,\n roomName,\n error,\n start,\n stop,\n mute,\n unmute,\n clearTranscript,\n }),\n [\n status,\n activeSpeaker,\n isMuted,\n transcript,\n sessionId,\n roomName,\n error,\n start,\n stop,\n mute,\n unmute,\n clearTranscript,\n ],\n );\n};\n"],"names":["useParentBridge","useRef","useEffect","useCallback","error","isCredentialRefusal","useState","Track","Room","RoomEvent","useMemo"],"mappings":"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"}
|
|
@@ -86,6 +86,11 @@ export interface AthenaVoiceSessionConfig {
|
|
|
86
86
|
enabled_tools?: AthenaVoiceToolId[];
|
|
87
87
|
/** IANA timezone of the speaker, e.g. `'America/New_York'`. */
|
|
88
88
|
timezone?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Start without the agent's opening greeting: it stays quiet (no audio, no
|
|
91
|
+
* transcript turn) until a participant speaks. Default `false`.
|
|
92
|
+
*/
|
|
93
|
+
silent_start?: boolean;
|
|
89
94
|
}
|
|
90
95
|
export interface UseAthenaVoiceSessionOptions {
|
|
91
96
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-athena-voice-session.js","sources":["../../src/voice/use-athena-voice-session.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n Room,\n RoomEvent,\n Track,\n type Participant,\n type RemoteTrack,\n type TranscriptionSegment,\n} from 'livekit-client';\nimport { type ParentBridge, useParentBridge, type ParentBridgeState } from '../runtime/auth';\nimport { isCredentialRefusal } from '../runtime/credential-renewal';\n\n/**\n * Realtime connection details for one Athena voice session. This is the shape\n * the Athena voice session endpoint returns; the hook only reads these\n * fields, so any server that proxies that call can hand it straight back.\n */\nexport interface AthenaVoiceSessionCredentials {\n /** Realtime signalling URL (`wss://…`). */\n server_url: string;\n /** Short-lived participant JWT scoped to `room_name`. */\n token: string;\n room_name?: string;\n /**\n * Athena session asset the voice run is persisted into. Use it with\n * `GET /api/v0/sessions/{asset_id}/download` to retrieve the transcript\n * after the call.\n */\n asset_id?: string;\n}\n\nexport type AthenaVoiceSessionStatus =\n | 'idle'\n | 'connecting'\n | 'connected'\n | 'reconnecting'\n | 'ended'\n | 'error';\n\nexport type AthenaVoiceSpeaker = 'user' | 'agent';\n\n/** One committed (final) transcription segment from either side of the call. */\nexport interface AthenaVoiceTranscriptTurn {\n id: string;\n speaker: AthenaVoiceSpeaker;\n text: string;\n /** Segment start, in ms since the room's epoch. */\n startTime: number;\n endTime: number;\n language: string;\n}\n\n/**\n * A voice tool the Athena voice agent can run. These are voice tool\n * names, not the chat `Toolkits` ids: `'web_search_browse_toolkit'` is not a\n * voice tool, `'web_search'` is. Only these names are accepted.\n *\n * The agent also has tools that call back into the client over room RPC\n * (clipboard, selection, screen contents, opening assets). This hook registers\n * no RPC handlers, so they are not part of its contract.\n */\nexport type AthenaVoiceToolId =\n /** Web search. */\n | 'web_search'\n /** Legacy alias of `web_search`; prefer `web_search`. */\n | 'search_web'\n /** Fetch and read one URL. */\n | 'browse'\n /** The workspace knowledge base, where configured. */\n | 'search_knowledge_base'\n /** Same knowledge base, thorough long-running retrieval. */\n | 'search_knowledge_base_lr'\n /** Read Athena assets by id, as the viewer. */\n | 'read_full_asset'\n /** Search the viewer's Athena assets. */\n | 'search_assets'\n /** Hand a question to the general agent and speak its answer. */\n | 'ask_agent'\n /** Save a note for the user (a stub on the worker today). */\n | 'save_note'\n /** Current date and time, Eastern. */\n | 'get_current_time_est';\n\n/**\n * Voice agent configuration for a session the hook creates itself (see\n * `UseAthenaVoiceSessionOptions.session`). Mirrors the body of the Athena\n * voice session endpoint; every field is optional and falls back to the\n * server default.\n */\nexport interface AthenaVoiceSessionConfig {\n /** Persona / instructions for the voice agent. */\n system_prompt?: string;\n /** Realtime provider, e.g. `'openai'` or `'google'`. */\n provider?: string;\n /** Provider model id, e.g. `'gpt-realtime-2'`. */\n model?: string;\n /** Provider voice id, e.g. `'marin'` (OpenAI) or `'Aoede'` (Google). */\n voice?: string;\n temperature?: number;\n /** BCP-47 language tag; default `'en'`. */\n language?: string;\n /**\n * Voice tools the agent may call. Omit it for the server's default set\n * (`web_search`, `search_knowledge_base`, `ask_agent`, …); pass `[]` for a\n * pure conversation with no tool calls.\n */\n enabled_tools?: AthenaVoiceToolId[];\n /** IANA timezone of the speaker, e.g. `'America/New_York'`. */\n timezone?: string;\n}\n\nexport interface UseAthenaVoiceSessionOptions {\n /**\n * Creates the voice session. Omit it when the app is served from a\n * Marathon computer-asset URL: the hook then calls the Athena voice session\n * endpoint itself with the viewer's bearer token from `/_athena/auth`, so\n * the session is created as — and shows up in the sessions of — whoever is\n * using the app.\n *\n * Works for workspace members and external SSO viewers alike: the scoped\n * `athview_` viewer credential is authorized for the voice session route and\n * `agents/general/invoke` the same way it is for `/api/v0/sessions`.\n *\n * Supply it when the app runs anywhere else (standalone, custom auth): it\n * should run on the caller's own server so the Athena API key never reaches\n * the browser. The hook only reads the returned credentials.\n *\n * Decide once: switching between supplying and omitting it across renders\n * of the same component is not supported.\n */\n createSession?: (\n signal: AbortSignal,\n ) => Promise<AthenaVoiceSessionCredentials>;\n /**\n * Agent configuration for sessions the hook creates itself. Ignored when\n * `createSession` is supplied.\n */\n session?: AthenaVoiceSessionConfig;\n /** Called once per committed transcript turn, in arrival order. */\n onTranscript?: (turn: AthenaVoiceTranscriptTurn) => void;\n /** Called with interim (non-final) transcript text as it streams in. */\n onInterimTranscript?: (speaker: AthenaVoiceSpeaker, text: string) => void;\n onError?: (error: Error) => void;\n /**\n * Where to play the agent's audio. Defaults to a detached `<audio>` element\n * the hook creates and removes itself.\n */\n audioElement?: HTMLMediaElement | null;\n /** Enable the microphone as soon as the room connects. Default `true`. */\n autoEnableMicrophone?: boolean;\n /** @internal Overrides `new Room()`; used by tests. */\n createRoom?: () => Room;\n /** @internal Overrides `useParentBridge()`; used by tests. */\n useBridge?: () => ParentBridgeState & Partial<Pick<ParentBridge, 'renewToken'>>;\n}\n\nexport interface UseAthenaVoiceSessionReturn {\n status: AthenaVoiceSessionStatus;\n /** True between `start()` resolving the session and the room connecting. */\n isConnecting: boolean;\n isConnected: boolean;\n /** Who is speaking right now, or `null` when the room is quiet. */\n activeSpeaker: AthenaVoiceSpeaker | null;\n isMuted: boolean;\n /** Committed transcript turns so far, in arrival order. */\n transcript: AthenaVoiceTranscriptTurn[];\n /** Athena session asset id, once the session has been created. */\n sessionId: string | null;\n roomName: string | null;\n error: Error | null;\n /** Creates a session and joins the room. No-op while a session is live. */\n start: () => Promise<void>;\n /** Leaves the room and stops local tracks. Safe to call at any time. */\n stop: () => Promise<void>;\n mute: () => Promise<void>;\n unmute: () => Promise<void>;\n /** Clears `transcript`; does not affect the live session. */\n clearTranscript: () => void;\n}\n\nconst toError = (value: unknown): Error =>\n value instanceof Error ? value : new Error(String(value));\n\nconst NO_BRIDGE: ParentBridgeState = {\n token: null,\n statewireSyncUrl: null,\n backendBaseUrl: null,\n appUrl: null,\n hostAnswered: false,\n ready: true,\n};\n// Stands in for `useParentBridge` when the caller creates sessions itself, so\n// the hook never fetches `/_athena/auth` it would not use.\nconst useNoBridge = (): ParentBridgeState => NO_BRIDGE;\n\nconst NO_VIEWER_TOKEN_MESSAGE =\n 'useAthenaVoiceSession: no Athena viewer token. Either serve the app from ' +\n 'a Marathon computer-asset URL (which mints one via /_athena/auth) or pass ' +\n '`createSession` to create the session through your own server.';\n\n/**\n * Creates an Athena voice session as the current viewer. Exported so apps with\n * their own bridge state (or a server proxy holding a user bearer) can reuse\n * the exact request the hook makes.\n */\nexport const createAthenaVoiceSession = async ({\n backendBaseUrl,\n token,\n session = {},\n signal,\n}: {\n /** Bare Athena API base, e.g. `https://api.athenaintel.com`. */\n backendBaseUrl: string;\n /** Bearer token for the user the session belongs to. */\n token: string;\n session?: AthenaVoiceSessionConfig;\n signal?: AbortSignal;\n}): Promise<AthenaVoiceSessionCredentials> => {\n const url: string = `${backendBaseUrl.replace(/\\/+$/, '')}/api/orpheus/create-session`;\n const response: Response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify(session),\n signal,\n });\n if (!response.ok) {\n const detail: string = await response.text().catch(() => '');\n // `status` lets a caller tell a refused credential (401) from any other failure.\n throw Object.assign(\n new Error(\n `Athena voice session creation failed (${response.status})${detail ? `: ${detail}` : ''}`,\n ),\n { status: response.status },\n );\n }\n return (await response.json()) as AthenaVoiceSessionCredentials;\n};\n\nconst speakerOf = (\n participant: Participant | undefined,\n room: Room,\n): AthenaVoiceSpeaker =>\n participant !== undefined &&\n participant.identity === room.localParticipant.identity\n ? 'user'\n : 'agent';\n\n/**\n * Runs one Athena voice session from React.\n *\n * Owns the whole browser-side lifecycle the voice API leaves to the caller:\n * creating the session through the app's own server, joining the room,\n * publishing the microphone, playing the agent's audio, tracking who is\n * speaking, committing transcript turns from room transcription events, and\n * tearing everything down on `stop()` or unmount. A second `start()` while a\n * session is live (or still connecting) is ignored, so React StrictMode's\n * double-invoked effects cannot dispatch two agents into one room.\n */\nexport const useAthenaVoiceSession = (\n options: UseAthenaVoiceSessionOptions = {},\n): UseAthenaVoiceSessionReturn => {\n const {\n createSession,\n session,\n onTranscript,\n onInterimTranscript,\n onError,\n audioElement,\n autoEnableMicrophone = true,\n createRoom,\n useBridge = createSession ? useNoBridge : useParentBridge,\n } = options;\n\n // Viewer identity minted by Marathon (`/_athena/auth`) or the Athena app\n // iframe bridge. Only consulted when the caller did not supply `createSession`.\n const bridge: ParentBridgeState & Partial<Pick<ParentBridge, 'renewToken'>> = useBridge();\n const bridgeRef = useRef(bridge);\n bridgeRef.current = bridge;\n const bridgeReadyWaitersRef = useRef<Array<() => void>>([]);\n useEffect(() => {\n if (!bridge.ready) return;\n const waiters: Array<() => void> = bridgeReadyWaitersRef.current;\n bridgeReadyWaitersRef.current = [];\n for (const resolve of waiters) resolve();\n }, [bridge.ready]);\n\n const sessionRef = useRef<AthenaVoiceSessionConfig | undefined>(session);\n sessionRef.current = session;\n\n const createSessionAsViewer = useCallback(\n async (signal: AbortSignal): Promise<AthenaVoiceSessionCredentials> => {\n if (!bridgeRef.current.ready) {\n await new Promise<void>((resolve) => {\n bridgeReadyWaitersRef.current.push(resolve);\n signal.addEventListener('abort', () => resolve(), { once: true });\n });\n if (signal.aborted) throw new Error('aborted');\n }\n const { token, backendBaseUrl, renewToken } = bridgeRef.current;\n if (!token || !backendBaseUrl) throw new Error(NO_VIEWER_TOKEN_MESSAGE);\n const create = (viewerToken: string) =>\n createAthenaVoiceSession({\n backendBaseUrl,\n token: viewerToken,\n session: sessionRef.current,\n signal,\n });\n try {\n return await create(token);\n } catch (error) {\n // The host refused the viewer token: renew it through the bridge (a\n // Marathon re-mint or the parent window's next token) and try once more.\n if (!isCredentialRefusal(error) || !renewToken) throw error;\n const renewed = await renewToken({ force: true });\n if (!renewed || renewed === token) throw error;\n return create(renewed);\n }\n },\n [],\n );\n\n const [status, setStatus] = useState<AthenaVoiceSessionStatus>('idle');\n const [activeSpeaker, setActiveSpeaker] = useState<AthenaVoiceSpeaker | null>(\n null,\n );\n const [isMuted, setIsMuted] = useState<boolean>(false);\n const [transcript, setTranscript] = useState<AthenaVoiceTranscriptTurn[]>([]);\n const [sessionId, setSessionId] = useState<string | null>(null);\n const [roomName, setRoomName] = useState<string | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n // Latest callbacks without re-subscribing room listeners on every render.\n const callbacksRef = useRef({\n createSession: createSession ?? createSessionAsViewer,\n onTranscript,\n onInterimTranscript,\n onError,\n });\n callbacksRef.current = {\n createSession: createSession ?? createSessionAsViewer,\n onTranscript,\n onInterimTranscript,\n onError,\n };\n\n const roomRef = useRef<Room | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const attachedElementsRef = useRef<HTMLMediaElement[]>([]);\n const ownedAudioRef = useRef<HTMLMediaElement | null>(null);\n const committedSegmentIdsRef = useRef<Set<string>>(new Set());\n // Guards `start()` against re-entry from the moment it is called, before any\n // state update lands — a StrictMode double effect fires both calls\n // synchronously in the same tick.\n const startingRef = useRef<boolean>(false);\n\n const fail = useCallback((cause: unknown) => {\n const err: Error = toError(cause);\n setError(err);\n setStatus('error');\n callbacksRef.current.onError?.(err);\n }, []);\n\n const detachAudio = useCallback(() => {\n for (const element of attachedElementsRef.current) {\n element.srcObject = null;\n if (element === ownedAudioRef.current) element.remove();\n }\n attachedElementsRef.current = [];\n ownedAudioRef.current = null;\n }, []);\n\n const teardown = useCallback(async () => {\n abortRef.current?.abort();\n abortRef.current = null;\n const room: Room | null = roomRef.current;\n roomRef.current = null;\n startingRef.current = false;\n detachAudio();\n if (room) {\n room.removeAllListeners();\n await room.disconnect(true);\n }\n }, [detachAudio]);\n\n const stop = useCallback(async () => {\n const hadRoom: boolean =\n roomRef.current !== null || abortRef.current !== null;\n await teardown();\n setActiveSpeaker(null);\n if (hadRoom) setStatus('ended');\n }, [teardown]);\n\n const attachTrack = useCallback(\n (track: RemoteTrack) => {\n if (track.kind !== Track.Kind.Audio) return;\n if (audioElement) {\n track.attach(audioElement);\n attachedElementsRef.current.push(audioElement);\n return;\n }\n const element: HTMLMediaElement = track.attach();\n element.setAttribute('data-athena-voice-audio', '');\n document.body.appendChild(element);\n ownedAudioRef.current = element;\n attachedElementsRef.current.push(element);\n },\n [audioElement],\n );\n\n const handleTranscription = useCallback(\n (\n segments: TranscriptionSegment[],\n participant: Participant | undefined,\n room: Room,\n ) => {\n const speaker: AthenaVoiceSpeaker = speakerOf(participant, room);\n const committed: AthenaVoiceTranscriptTurn[] = [];\n for (const segment of segments) {\n if (!segment.final) {\n callbacksRef.current.onInterimTranscript?.(speaker, segment.text);\n continue;\n }\n if (committedSegmentIdsRef.current.has(segment.id)) continue;\n committedSegmentIdsRef.current.add(segment.id);\n committed.push({\n id: segment.id,\n speaker,\n text: segment.text,\n startTime: segment.startTime,\n endTime: segment.endTime,\n language: segment.language,\n });\n }\n if (committed.length === 0) return;\n setTranscript((previous) => [...previous, ...committed]);\n for (const turn of committed) callbacksRef.current.onTranscript?.(turn);\n },\n [],\n );\n\n const start = useCallback(async () => {\n if (startingRef.current || roomRef.current) return;\n startingRef.current = true;\n setError(null);\n setSessionId(null);\n setRoomName(null);\n setStatus('connecting');\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n let credentials: AthenaVoiceSessionCredentials;\n try {\n credentials = await callbacksRef.current.createSession(controller.signal);\n } catch (cause) {\n if (controller.signal.aborted) return;\n abortRef.current = null;\n startingRef.current = false;\n fail(cause);\n return;\n }\n if (controller.signal.aborted) return;\n\n setSessionId(credentials.asset_id ?? null);\n setRoomName(credentials.room_name ?? null);\n\n const room: Room = createRoom ? createRoom() : new Room();\n roomRef.current = room;\n\n room\n .on(RoomEvent.TrackSubscribed, attachTrack)\n .on(RoomEvent.ActiveSpeakersChanged, (speakers: Participant[]) => {\n const first: Participant | undefined = speakers[0];\n setActiveSpeaker(first ? speakerOf(first, room) : null);\n })\n .on(RoomEvent.TranscriptionReceived, (segments, participant) =>\n handleTranscription(segments, participant, room),\n )\n .on(RoomEvent.Reconnecting, () => setStatus('reconnecting'))\n .on(RoomEvent.Reconnected, () => setStatus('connected'))\n .on(RoomEvent.Disconnected, () => {\n if (roomRef.current !== room) return;\n roomRef.current = null;\n startingRef.current = false;\n detachAudio();\n setActiveSpeaker(null);\n setStatus('ended');\n })\n .on(RoomEvent.MediaDevicesError, fail);\n\n try {\n await room.connect(credentials.server_url, credentials.token);\n if (controller.signal.aborted) return;\n if (autoEnableMicrophone) {\n await room.localParticipant.setMicrophoneEnabled(true);\n setIsMuted(false);\n } else {\n setIsMuted(true);\n }\n if (controller.signal.aborted) return;\n abortRef.current = null;\n startingRef.current = false;\n setStatus('connected');\n } catch (cause) {\n if (controller.signal.aborted) return;\n await teardown();\n fail(cause);\n }\n }, [\n attachTrack,\n autoEnableMicrophone,\n createRoom,\n detachAudio,\n fail,\n handleTranscription,\n teardown,\n ]);\n\n const setMicrophone = useCallback(async (enabled: boolean) => {\n const room: Room | null = roomRef.current;\n if (!room) return;\n await room.localParticipant.setMicrophoneEnabled(enabled);\n setIsMuted(!enabled);\n }, []);\n const mute = useCallback(() => setMicrophone(false), [setMicrophone]);\n const unmute = useCallback(() => setMicrophone(true), [setMicrophone]);\n\n const clearTranscript = useCallback(() => {\n committedSegmentIdsRef.current.clear();\n setTranscript([]);\n }, []);\n\n // Unmount teardown is deferred one tick so StrictMode's synchronous\n // unmount/remount of the same instance does not abort an in-flight\n // `createSession` (which would dispatch a second voice agent on remount).\n const unmountTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(() => {\n if (unmountTimerRef.current !== null) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n return () => {\n unmountTimerRef.current = setTimeout(() => {\n unmountTimerRef.current = null;\n void teardown();\n }, 0);\n };\n }, [teardown]);\n\n return useMemo<UseAthenaVoiceSessionReturn>(\n () => ({\n status,\n isConnecting: status === 'connecting',\n isConnected: status === 'connected' || status === 'reconnecting',\n activeSpeaker,\n isMuted,\n transcript,\n sessionId,\n roomName,\n error,\n start,\n stop,\n mute,\n unmute,\n clearTranscript,\n }),\n [\n status,\n activeSpeaker,\n isMuted,\n transcript,\n sessionId,\n roomName,\n error,\n start,\n stop,\n mute,\n unmute,\n clearTranscript,\n ],\n );\n};\n"],"names":["error"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-athena-voice-session.js","sources":["../../src/voice/use-athena-voice-session.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n Room,\n RoomEvent,\n Track,\n type Participant,\n type RemoteTrack,\n type TranscriptionSegment,\n} from 'livekit-client';\nimport { type ParentBridge, useParentBridge, type ParentBridgeState } from '../runtime/auth';\nimport { isCredentialRefusal } from '../runtime/credential-renewal';\n\n/**\n * Realtime connection details for one Athena voice session. This is the shape\n * the Athena voice session endpoint returns; the hook only reads these\n * fields, so any server that proxies that call can hand it straight back.\n */\nexport interface AthenaVoiceSessionCredentials {\n /** Realtime signalling URL (`wss://…`). */\n server_url: string;\n /** Short-lived participant JWT scoped to `room_name`. */\n token: string;\n room_name?: string;\n /**\n * Athena session asset the voice run is persisted into. Use it with\n * `GET /api/v0/sessions/{asset_id}/download` to retrieve the transcript\n * after the call.\n */\n asset_id?: string;\n}\n\nexport type AthenaVoiceSessionStatus =\n | 'idle'\n | 'connecting'\n | 'connected'\n | 'reconnecting'\n | 'ended'\n | 'error';\n\nexport type AthenaVoiceSpeaker = 'user' | 'agent';\n\n/** One committed (final) transcription segment from either side of the call. */\nexport interface AthenaVoiceTranscriptTurn {\n id: string;\n speaker: AthenaVoiceSpeaker;\n text: string;\n /** Segment start, in ms since the room's epoch. */\n startTime: number;\n endTime: number;\n language: string;\n}\n\n/**\n * A voice tool the Athena voice agent can run. These are voice tool\n * names, not the chat `Toolkits` ids: `'web_search_browse_toolkit'` is not a\n * voice tool, `'web_search'` is. Only these names are accepted.\n *\n * The agent also has tools that call back into the client over room RPC\n * (clipboard, selection, screen contents, opening assets). This hook registers\n * no RPC handlers, so they are not part of its contract.\n */\nexport type AthenaVoiceToolId =\n /** Web search. */\n | 'web_search'\n /** Legacy alias of `web_search`; prefer `web_search`. */\n | 'search_web'\n /** Fetch and read one URL. */\n | 'browse'\n /** The workspace knowledge base, where configured. */\n | 'search_knowledge_base'\n /** Same knowledge base, thorough long-running retrieval. */\n | 'search_knowledge_base_lr'\n /** Read Athena assets by id, as the viewer. */\n | 'read_full_asset'\n /** Search the viewer's Athena assets. */\n | 'search_assets'\n /** Hand a question to the general agent and speak its answer. */\n | 'ask_agent'\n /** Save a note for the user (a stub on the worker today). */\n | 'save_note'\n /** Current date and time, Eastern. */\n | 'get_current_time_est';\n\n/**\n * Voice agent configuration for a session the hook creates itself (see\n * `UseAthenaVoiceSessionOptions.session`). Mirrors the body of the Athena\n * voice session endpoint; every field is optional and falls back to the\n * server default.\n */\nexport interface AthenaVoiceSessionConfig {\n /** Persona / instructions for the voice agent. */\n system_prompt?: string;\n /** Realtime provider, e.g. `'openai'` or `'google'`. */\n provider?: string;\n /** Provider model id, e.g. `'gpt-realtime-2'`. */\n model?: string;\n /** Provider voice id, e.g. `'marin'` (OpenAI) or `'Aoede'` (Google). */\n voice?: string;\n temperature?: number;\n /** BCP-47 language tag; default `'en'`. */\n language?: string;\n /**\n * Voice tools the agent may call. Omit it for the server's default set\n * (`web_search`, `search_knowledge_base`, `ask_agent`, …); pass `[]` for a\n * pure conversation with no tool calls.\n */\n enabled_tools?: AthenaVoiceToolId[];\n /** IANA timezone of the speaker, e.g. `'America/New_York'`. */\n timezone?: string;\n /**\n * Start without the agent's opening greeting: it stays quiet (no audio, no\n * transcript turn) until a participant speaks. Default `false`.\n */\n silent_start?: boolean;\n}\n\nexport interface UseAthenaVoiceSessionOptions {\n /**\n * Creates the voice session. Omit it when the app is served from a\n * Marathon computer-asset URL: the hook then calls the Athena voice session\n * endpoint itself with the viewer's bearer token from `/_athena/auth`, so\n * the session is created as — and shows up in the sessions of — whoever is\n * using the app.\n *\n * Works for workspace members and external SSO viewers alike: the scoped\n * `athview_` viewer credential is authorized for the voice session route and\n * `agents/general/invoke` the same way it is for `/api/v0/sessions`.\n *\n * Supply it when the app runs anywhere else (standalone, custom auth): it\n * should run on the caller's own server so the Athena API key never reaches\n * the browser. The hook only reads the returned credentials.\n *\n * Decide once: switching between supplying and omitting it across renders\n * of the same component is not supported.\n */\n createSession?: (\n signal: AbortSignal,\n ) => Promise<AthenaVoiceSessionCredentials>;\n /**\n * Agent configuration for sessions the hook creates itself. Ignored when\n * `createSession` is supplied.\n */\n session?: AthenaVoiceSessionConfig;\n /** Called once per committed transcript turn, in arrival order. */\n onTranscript?: (turn: AthenaVoiceTranscriptTurn) => void;\n /** Called with interim (non-final) transcript text as it streams in. */\n onInterimTranscript?: (speaker: AthenaVoiceSpeaker, text: string) => void;\n onError?: (error: Error) => void;\n /**\n * Where to play the agent's audio. Defaults to a detached `<audio>` element\n * the hook creates and removes itself.\n */\n audioElement?: HTMLMediaElement | null;\n /** Enable the microphone as soon as the room connects. Default `true`. */\n autoEnableMicrophone?: boolean;\n /** @internal Overrides `new Room()`; used by tests. */\n createRoom?: () => Room;\n /** @internal Overrides `useParentBridge()`; used by tests. */\n useBridge?: () => ParentBridgeState & Partial<Pick<ParentBridge, 'renewToken'>>;\n}\n\nexport interface UseAthenaVoiceSessionReturn {\n status: AthenaVoiceSessionStatus;\n /** True between `start()` resolving the session and the room connecting. */\n isConnecting: boolean;\n isConnected: boolean;\n /** Who is speaking right now, or `null` when the room is quiet. */\n activeSpeaker: AthenaVoiceSpeaker | null;\n isMuted: boolean;\n /** Committed transcript turns so far, in arrival order. */\n transcript: AthenaVoiceTranscriptTurn[];\n /** Athena session asset id, once the session has been created. */\n sessionId: string | null;\n roomName: string | null;\n error: Error | null;\n /** Creates a session and joins the room. No-op while a session is live. */\n start: () => Promise<void>;\n /** Leaves the room and stops local tracks. Safe to call at any time. */\n stop: () => Promise<void>;\n mute: () => Promise<void>;\n unmute: () => Promise<void>;\n /** Clears `transcript`; does not affect the live session. */\n clearTranscript: () => void;\n}\n\nconst toError = (value: unknown): Error =>\n value instanceof Error ? value : new Error(String(value));\n\nconst NO_BRIDGE: ParentBridgeState = {\n token: null,\n statewireSyncUrl: null,\n backendBaseUrl: null,\n appUrl: null,\n hostAnswered: false,\n ready: true,\n};\n// Stands in for `useParentBridge` when the caller creates sessions itself, so\n// the hook never fetches `/_athena/auth` it would not use.\nconst useNoBridge = (): ParentBridgeState => NO_BRIDGE;\n\nconst NO_VIEWER_TOKEN_MESSAGE =\n 'useAthenaVoiceSession: no Athena viewer token. Either serve the app from ' +\n 'a Marathon computer-asset URL (which mints one via /_athena/auth) or pass ' +\n '`createSession` to create the session through your own server.';\n\n/**\n * Creates an Athena voice session as the current viewer. Exported so apps with\n * their own bridge state (or a server proxy holding a user bearer) can reuse\n * the exact request the hook makes.\n */\nexport const createAthenaVoiceSession = async ({\n backendBaseUrl,\n token,\n session = {},\n signal,\n}: {\n /** Bare Athena API base, e.g. `https://api.athenaintel.com`. */\n backendBaseUrl: string;\n /** Bearer token for the user the session belongs to. */\n token: string;\n session?: AthenaVoiceSessionConfig;\n signal?: AbortSignal;\n}): Promise<AthenaVoiceSessionCredentials> => {\n const url: string = `${backendBaseUrl.replace(/\\/+$/, '')}/api/orpheus/create-session`;\n const response: Response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify(session),\n signal,\n });\n if (!response.ok) {\n const detail: string = await response.text().catch(() => '');\n // `status` lets a caller tell a refused credential (401) from any other failure.\n throw Object.assign(\n new Error(\n `Athena voice session creation failed (${response.status})${detail ? `: ${detail}` : ''}`,\n ),\n { status: response.status },\n );\n }\n return (await response.json()) as AthenaVoiceSessionCredentials;\n};\n\nconst speakerOf = (\n participant: Participant | undefined,\n room: Room,\n): AthenaVoiceSpeaker =>\n participant !== undefined &&\n participant.identity === room.localParticipant.identity\n ? 'user'\n : 'agent';\n\n/**\n * Runs one Athena voice session from React.\n *\n * Owns the whole browser-side lifecycle the voice API leaves to the caller:\n * creating the session through the app's own server, joining the room,\n * publishing the microphone, playing the agent's audio, tracking who is\n * speaking, committing transcript turns from room transcription events, and\n * tearing everything down on `stop()` or unmount. A second `start()` while a\n * session is live (or still connecting) is ignored, so React StrictMode's\n * double-invoked effects cannot dispatch two agents into one room.\n */\nexport const useAthenaVoiceSession = (\n options: UseAthenaVoiceSessionOptions = {},\n): UseAthenaVoiceSessionReturn => {\n const {\n createSession,\n session,\n onTranscript,\n onInterimTranscript,\n onError,\n audioElement,\n autoEnableMicrophone = true,\n createRoom,\n useBridge = createSession ? useNoBridge : useParentBridge,\n } = options;\n\n // Viewer identity minted by Marathon (`/_athena/auth`) or the Athena app\n // iframe bridge. Only consulted when the caller did not supply `createSession`.\n const bridge: ParentBridgeState & Partial<Pick<ParentBridge, 'renewToken'>> = useBridge();\n const bridgeRef = useRef(bridge);\n bridgeRef.current = bridge;\n const bridgeReadyWaitersRef = useRef<Array<() => void>>([]);\n useEffect(() => {\n if (!bridge.ready) return;\n const waiters: Array<() => void> = bridgeReadyWaitersRef.current;\n bridgeReadyWaitersRef.current = [];\n for (const resolve of waiters) resolve();\n }, [bridge.ready]);\n\n const sessionRef = useRef<AthenaVoiceSessionConfig | undefined>(session);\n sessionRef.current = session;\n\n const createSessionAsViewer = useCallback(\n async (signal: AbortSignal): Promise<AthenaVoiceSessionCredentials> => {\n if (!bridgeRef.current.ready) {\n await new Promise<void>((resolve) => {\n bridgeReadyWaitersRef.current.push(resolve);\n signal.addEventListener('abort', () => resolve(), { once: true });\n });\n if (signal.aborted) throw new Error('aborted');\n }\n const { token, backendBaseUrl, renewToken } = bridgeRef.current;\n if (!token || !backendBaseUrl) throw new Error(NO_VIEWER_TOKEN_MESSAGE);\n const create = (viewerToken: string) =>\n createAthenaVoiceSession({\n backendBaseUrl,\n token: viewerToken,\n session: sessionRef.current,\n signal,\n });\n try {\n return await create(token);\n } catch (error) {\n // The host refused the viewer token: renew it through the bridge (a\n // Marathon re-mint or the parent window's next token) and try once more.\n if (!isCredentialRefusal(error) || !renewToken) throw error;\n const renewed = await renewToken({ force: true });\n if (!renewed || renewed === token) throw error;\n return create(renewed);\n }\n },\n [],\n );\n\n const [status, setStatus] = useState<AthenaVoiceSessionStatus>('idle');\n const [activeSpeaker, setActiveSpeaker] = useState<AthenaVoiceSpeaker | null>(\n null,\n );\n const [isMuted, setIsMuted] = useState<boolean>(false);\n const [transcript, setTranscript] = useState<AthenaVoiceTranscriptTurn[]>([]);\n const [sessionId, setSessionId] = useState<string | null>(null);\n const [roomName, setRoomName] = useState<string | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n // Latest callbacks without re-subscribing room listeners on every render.\n const callbacksRef = useRef({\n createSession: createSession ?? createSessionAsViewer,\n onTranscript,\n onInterimTranscript,\n onError,\n });\n callbacksRef.current = {\n createSession: createSession ?? createSessionAsViewer,\n onTranscript,\n onInterimTranscript,\n onError,\n };\n\n const roomRef = useRef<Room | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const attachedElementsRef = useRef<HTMLMediaElement[]>([]);\n const ownedAudioRef = useRef<HTMLMediaElement | null>(null);\n const committedSegmentIdsRef = useRef<Set<string>>(new Set());\n // Guards `start()` against re-entry from the moment it is called, before any\n // state update lands — a StrictMode double effect fires both calls\n // synchronously in the same tick.\n const startingRef = useRef<boolean>(false);\n\n const fail = useCallback((cause: unknown) => {\n const err: Error = toError(cause);\n setError(err);\n setStatus('error');\n callbacksRef.current.onError?.(err);\n }, []);\n\n const detachAudio = useCallback(() => {\n for (const element of attachedElementsRef.current) {\n element.srcObject = null;\n if (element === ownedAudioRef.current) element.remove();\n }\n attachedElementsRef.current = [];\n ownedAudioRef.current = null;\n }, []);\n\n const teardown = useCallback(async () => {\n abortRef.current?.abort();\n abortRef.current = null;\n const room: Room | null = roomRef.current;\n roomRef.current = null;\n startingRef.current = false;\n detachAudio();\n if (room) {\n room.removeAllListeners();\n await room.disconnect(true);\n }\n }, [detachAudio]);\n\n const stop = useCallback(async () => {\n const hadRoom: boolean =\n roomRef.current !== null || abortRef.current !== null;\n await teardown();\n setActiveSpeaker(null);\n if (hadRoom) setStatus('ended');\n }, [teardown]);\n\n const attachTrack = useCallback(\n (track: RemoteTrack) => {\n if (track.kind !== Track.Kind.Audio) return;\n if (audioElement) {\n track.attach(audioElement);\n attachedElementsRef.current.push(audioElement);\n return;\n }\n const element: HTMLMediaElement = track.attach();\n element.setAttribute('data-athena-voice-audio', '');\n document.body.appendChild(element);\n ownedAudioRef.current = element;\n attachedElementsRef.current.push(element);\n },\n [audioElement],\n );\n\n const handleTranscription = useCallback(\n (\n segments: TranscriptionSegment[],\n participant: Participant | undefined,\n room: Room,\n ) => {\n const speaker: AthenaVoiceSpeaker = speakerOf(participant, room);\n const committed: AthenaVoiceTranscriptTurn[] = [];\n for (const segment of segments) {\n if (!segment.final) {\n callbacksRef.current.onInterimTranscript?.(speaker, segment.text);\n continue;\n }\n if (committedSegmentIdsRef.current.has(segment.id)) continue;\n committedSegmentIdsRef.current.add(segment.id);\n committed.push({\n id: segment.id,\n speaker,\n text: segment.text,\n startTime: segment.startTime,\n endTime: segment.endTime,\n language: segment.language,\n });\n }\n if (committed.length === 0) return;\n setTranscript((previous) => [...previous, ...committed]);\n for (const turn of committed) callbacksRef.current.onTranscript?.(turn);\n },\n [],\n );\n\n const start = useCallback(async () => {\n if (startingRef.current || roomRef.current) return;\n startingRef.current = true;\n setError(null);\n setSessionId(null);\n setRoomName(null);\n setStatus('connecting');\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n let credentials: AthenaVoiceSessionCredentials;\n try {\n credentials = await callbacksRef.current.createSession(controller.signal);\n } catch (cause) {\n if (controller.signal.aborted) return;\n abortRef.current = null;\n startingRef.current = false;\n fail(cause);\n return;\n }\n if (controller.signal.aborted) return;\n\n setSessionId(credentials.asset_id ?? null);\n setRoomName(credentials.room_name ?? null);\n\n const room: Room = createRoom ? createRoom() : new Room();\n roomRef.current = room;\n\n room\n .on(RoomEvent.TrackSubscribed, attachTrack)\n .on(RoomEvent.ActiveSpeakersChanged, (speakers: Participant[]) => {\n const first: Participant | undefined = speakers[0];\n setActiveSpeaker(first ? speakerOf(first, room) : null);\n })\n .on(RoomEvent.TranscriptionReceived, (segments, participant) =>\n handleTranscription(segments, participant, room),\n )\n .on(RoomEvent.Reconnecting, () => setStatus('reconnecting'))\n .on(RoomEvent.Reconnected, () => setStatus('connected'))\n .on(RoomEvent.Disconnected, () => {\n if (roomRef.current !== room) return;\n roomRef.current = null;\n startingRef.current = false;\n detachAudio();\n setActiveSpeaker(null);\n setStatus('ended');\n })\n .on(RoomEvent.MediaDevicesError, fail);\n\n try {\n await room.connect(credentials.server_url, credentials.token);\n if (controller.signal.aborted) return;\n if (autoEnableMicrophone) {\n await room.localParticipant.setMicrophoneEnabled(true);\n setIsMuted(false);\n } else {\n setIsMuted(true);\n }\n if (controller.signal.aborted) return;\n abortRef.current = null;\n startingRef.current = false;\n setStatus('connected');\n } catch (cause) {\n if (controller.signal.aborted) return;\n await teardown();\n fail(cause);\n }\n }, [\n attachTrack,\n autoEnableMicrophone,\n createRoom,\n detachAudio,\n fail,\n handleTranscription,\n teardown,\n ]);\n\n const setMicrophone = useCallback(async (enabled: boolean) => {\n const room: Room | null = roomRef.current;\n if (!room) return;\n await room.localParticipant.setMicrophoneEnabled(enabled);\n setIsMuted(!enabled);\n }, []);\n const mute = useCallback(() => setMicrophone(false), [setMicrophone]);\n const unmute = useCallback(() => setMicrophone(true), [setMicrophone]);\n\n const clearTranscript = useCallback(() => {\n committedSegmentIdsRef.current.clear();\n setTranscript([]);\n }, []);\n\n // Unmount teardown is deferred one tick so StrictMode's synchronous\n // unmount/remount of the same instance does not abort an in-flight\n // `createSession` (which would dispatch a second voice agent on remount).\n const unmountTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(() => {\n if (unmountTimerRef.current !== null) {\n clearTimeout(unmountTimerRef.current);\n unmountTimerRef.current = null;\n }\n return () => {\n unmountTimerRef.current = setTimeout(() => {\n unmountTimerRef.current = null;\n void teardown();\n }, 0);\n };\n }, [teardown]);\n\n return useMemo<UseAthenaVoiceSessionReturn>(\n () => ({\n status,\n isConnecting: status === 'connecting',\n isConnected: status === 'connected' || status === 'reconnecting',\n activeSpeaker,\n isMuted,\n transcript,\n sessionId,\n roomName,\n error,\n start,\n stop,\n mute,\n unmute,\n clearTranscript,\n }),\n [\n status,\n activeSpeaker,\n isMuted,\n transcript,\n sessionId,\n roomName,\n error,\n start,\n stop,\n mute,\n unmute,\n clearTranscript,\n ],\n );\n};\n"],"names":["error"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@athenaintel/react",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.5",
|
|
4
4
|
"description": "Athena React SDK — Build AI-powered chat applications with the Athena platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"@tiptap/starter-kit": "^3.31.3",
|
|
110
110
|
"@tiptap/suggestion": "^3.31.3",
|
|
111
111
|
"@y/websocket": "4.0.0-rc.2",
|
|
112
|
-
"@y/y": "14.0.0-rc.
|
|
112
|
+
"@y/y": "14.0.0-rc.28",
|
|
113
113
|
"assistant-stream": "0.3.43",
|
|
114
114
|
"class-variance-authority": "^0.7.1",
|
|
115
115
|
"cnfast": "0.0.8",
|
|
@@ -122,6 +122,7 @@
|
|
|
122
122
|
},
|
|
123
123
|
"devDependencies": {
|
|
124
124
|
"@athenaintel/agent-runtime": "workspace:*",
|
|
125
|
+
"@athenaintel/tiptap-markdown-table": "workspace:*",
|
|
125
126
|
"@happy-dom/global-registrator": "^20.10.6",
|
|
126
127
|
"@propelauth/react": "^2.1.1",
|
|
127
128
|
"@tailwindcss/cli": "^4.2.1",
|