dsh-agora 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/assets/agora/SKILL.md +113 -0
- package/assets/agora/references/cli/README.md +161 -0
- package/assets/agora/references/cli/automation.md +189 -0
- package/assets/agora/references/cli/doctor.md +129 -0
- package/assets/agora/references/cli/env.md +158 -0
- package/assets/agora/references/cli/install-auth.md +152 -0
- package/assets/agora/references/cli/projects.md +116 -0
- package/assets/agora/references/cli/quickstarts.md +117 -0
- package/assets/agora/references/cloud-recording/README.md +86 -0
- package/assets/agora/references/conversational-ai/README.md +285 -0
- package/assets/agora/references/conversational-ai/agent-client-toolkit-react.md +182 -0
- package/assets/agora/references/conversational-ai/agent-samples.md +101 -0
- package/assets/agora/references/conversational-ai/agent-toolkit-android.md +209 -0
- package/assets/agora/references/conversational-ai/agent-toolkit-ios.md +208 -0
- package/assets/agora/references/conversational-ai/agent-toolkit.md +201 -0
- package/assets/agora/references/conversational-ai/agent-ui-kit.md +63 -0
- package/assets/agora/references/conversational-ai/architecture.md +221 -0
- package/assets/agora/references/conversational-ai/auth-flow.md +154 -0
- package/assets/agora/references/conversational-ai/conversational-ai-studio.md +173 -0
- package/assets/agora/references/conversational-ai/go-sdk.md +184 -0
- package/assets/agora/references/conversational-ai/integration-from-quickstart.md +203 -0
- package/assets/agora/references/conversational-ai/python-sdk.md +122 -0
- package/assets/agora/references/conversational-ai/quickstarts.md +710 -0
- package/assets/agora/references/conversational-ai/server-custom-llm.md +45 -0
- package/assets/agora/references/conversational-ai/server-mcp.md +40 -0
- package/assets/agora/references/conversational-ai/server-sdk-rename.md +78 -0
- package/assets/agora/references/conversational-ai/server-sdks.md +128 -0
- package/assets/agora/references/doc-fetching.md +67 -0
- package/assets/agora/references/integration-patterns.md +201 -0
- package/assets/agora/references/mcp-tools.md +49 -0
- package/assets/agora/references/rtc/README.md +104 -0
- package/assets/agora/references/rtc/android.md +344 -0
- package/assets/agora/references/rtc/cross-platform-coordination.md +61 -0
- package/assets/agora/references/rtc/flutter.md +282 -0
- package/assets/agora/references/rtc/ios.md +306 -0
- package/assets/agora/references/rtc/nextjs.md +87 -0
- package/assets/agora/references/rtc/react-native.md +266 -0
- package/assets/agora/references/rtc/react.md +186 -0
- package/assets/agora/references/rtc/web.md +506 -0
- package/assets/agora/references/rtm/README.md +80 -0
- package/assets/agora/references/rtm/android.md +277 -0
- package/assets/agora/references/rtm/ios.md +231 -0
- package/assets/agora/references/rtm/web.md +348 -0
- package/assets/agora/references/server/README.md +22 -0
- package/assets/agora/references/server/tokens.md +74 -0
- package/assets/agora/references/server-gateway/README.md +80 -0
- package/assets/agora/references/server-gateway/linux-cpp.md +251 -0
- package/assets/agora/references/testing-guidance/SKILL.md +65 -0
- package/assets/agora/references/testing-guidance/completeness-gate.md +28 -0
- package/assets/agora/references/testing-guidance/convoai-rest.md +83 -0
- package/assets/agora/references/testing-guidance/mobile-rtm-and-renewal.md +109 -0
- package/assets/agora/references/testing-guidance/rtc-android.md +70 -0
- package/assets/agora/references/testing-guidance/rtc-ios.md +73 -0
- package/assets/agora/references/testing-guidance/rtc-react.md +51 -0
- package/assets/agora/references/testing-guidance/rtc-web.md +94 -0
- package/cordis.patch.yml +5 -0
- package/index.js +53 -0
- package/package.json +49 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# ConvoAI Agent Toolkit — Android (Kotlin)
|
|
2
|
+
|
|
3
|
+
The Android agent toolkit (`ConversationalAIAPIImpl`) wraps RTC + RTM to deliver AI transcripts, agent state, interrupts, and metrics. It is sourced from the Conversational AI Demo repo — not from the Agora SDK packages.
|
|
4
|
+
|
|
5
|
+
Source: `convoaiApi/IConversationalAIAPI.kt` and `ConversationalAIAPIImpl.kt` in the demo repo.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Setup](#setup)
|
|
10
|
+
- [Initialization](#initialization)
|
|
11
|
+
- [Audio Configuration](#audio-configuration)
|
|
12
|
+
- [Subscribing to Events](#subscribing-to-events)
|
|
13
|
+
- [Sending Messages to the Agent](#sending-messages-to-the-agent)
|
|
14
|
+
- [Interrupting the Agent](#interrupting-the-agent)
|
|
15
|
+
- [Handling Events](#handling-events)
|
|
16
|
+
- [Cleanup](#cleanup)
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
The toolkit takes an existing `RtcEngine` and `RtmClient` — initialize both SDKs first.
|
|
21
|
+
|
|
22
|
+
```kotlin
|
|
23
|
+
import io.agora.rtc2.RtcEngine
|
|
24
|
+
import io.agora.rtm.RtmClient
|
|
25
|
+
// Copy or import from the demo repo:
|
|
26
|
+
// io.agora.scene.convoai.convoaiApi.*
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Initialization
|
|
30
|
+
|
|
31
|
+
```kotlin
|
|
32
|
+
// 1. Create your RTC engine and RTM client first (standard SDK init)
|
|
33
|
+
val rtcEngine: RtcEngine = // ... your existing engine
|
|
34
|
+
val rtmClient: RtmClient = // ... your existing RTM client (already logged in)
|
|
35
|
+
|
|
36
|
+
// 2. Create the toolkit config
|
|
37
|
+
val config = ConversationalAIAPIConfig(
|
|
38
|
+
rtcEngine = rtcEngine,
|
|
39
|
+
rtmClient = rtmClient,
|
|
40
|
+
renderMode = TranscriptRenderMode.Word, // Word (word-by-word) or Text (full sentence)
|
|
41
|
+
enableLog = true,
|
|
42
|
+
enableRenderModeFallback = true // fall back to Text if server lacks word timestamps
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
// 3. Create the API instance
|
|
46
|
+
val api = ConversationalAIAPIImpl(config)
|
|
47
|
+
|
|
48
|
+
// 4. Register your event handler
|
|
49
|
+
api.addHandler(eventHandler)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Audio Configuration
|
|
53
|
+
|
|
54
|
+
**Must be called before `rtcEngine.joinChannel()`** to configure optimal AI audio settings.
|
|
55
|
+
|
|
56
|
+
```kotlin
|
|
57
|
+
// Standard ConvoAI mode
|
|
58
|
+
api.loadAudioSettings()
|
|
59
|
+
// Equivalent to: api.loadAudioSettings(Constants.AUDIO_SCENARIO_AI_CLIENT)
|
|
60
|
+
|
|
61
|
+
// If using Avatar (requires audio mixing)
|
|
62
|
+
api.loadAudioSettings(Constants.AUDIO_SCENARIO_DEFAULT)
|
|
63
|
+
|
|
64
|
+
// Then join RTC channel
|
|
65
|
+
rtcEngine.joinChannel(token, channelName, uid, channelMediaOptions)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Subscribing to Events
|
|
69
|
+
|
|
70
|
+
Subscribe after logging in to RTM and before the agent starts speaking.
|
|
71
|
+
|
|
72
|
+
```kotlin
|
|
73
|
+
api.subscribeMessage(channelName) { error ->
|
|
74
|
+
if (error != null) {
|
|
75
|
+
Log.e("ConvoAI", "Subscribe failed: ${error.errorMessage}")
|
|
76
|
+
return@subscribeMessage
|
|
77
|
+
}
|
|
78
|
+
Log.d("ConvoAI", "Subscribed — ready to receive agent events")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// When done
|
|
82
|
+
api.unsubscribeMessage(channelName) { error -> }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Sending Messages to the Agent
|
|
86
|
+
|
|
87
|
+
```kotlin
|
|
88
|
+
// Text message (default priority: INTERRUPT)
|
|
89
|
+
val textMsg = TextMessage(
|
|
90
|
+
text = "What is the weather today?",
|
|
91
|
+
priority = Priority.INTERRUPT,
|
|
92
|
+
responseInterruptable = true
|
|
93
|
+
)
|
|
94
|
+
api.chat(agentUserId, textMsg) { error ->
|
|
95
|
+
if (error != null) Log.e("ConvoAI", "Chat failed: ${error.errorMessage}")
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Text with APPEND priority (queue after current response)
|
|
99
|
+
val appendMsg = TextMessage(text = "And tomorrow?", priority = Priority.APPEND)
|
|
100
|
+
api.chat(agentUserId, appendMsg) { }
|
|
101
|
+
|
|
102
|
+
// Image message (URL-based; keep base64 under 32KB total)
|
|
103
|
+
val imageMsg = ImageMessage(uuid = UUID.randomUUID().toString(), imageUrl = "https://example.com/photo.jpg")
|
|
104
|
+
api.chat(agentUserId, imageMsg) { error -> }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Interrupting the Agent
|
|
108
|
+
|
|
109
|
+
```kotlin
|
|
110
|
+
api.interrupt(agentUserId) { error ->
|
|
111
|
+
if (error != null) Log.e("ConvoAI", "Interrupt failed: ${error.errorMessage}")
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Handling Events
|
|
116
|
+
|
|
117
|
+
Implement `IConversationalAIAPIEventHandler`:
|
|
118
|
+
|
|
119
|
+
```kotlin
|
|
120
|
+
val eventHandler = object : IConversationalAIAPIEventHandler {
|
|
121
|
+
|
|
122
|
+
// Agent state: IDLE | SILENT | LISTENING | THINKING | SPEAKING | UNKNOWN
|
|
123
|
+
override fun onAgentStateChanged(agentUserId: String, event: StateChangeEvent) {
|
|
124
|
+
Log.d("ConvoAI", "Agent $agentUserId state: ${event.state}, turn: ${event.turnId}")
|
|
125
|
+
runOnUiThread { updateStateIndicator(event.state) }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Transcript update (fires frequently — dedup by turnId if needed)
|
|
129
|
+
override fun onTranscriptUpdated(agentUserId: String, transcript: Transcript) {
|
|
130
|
+
// transcript.type: TranscriptType.AGENT or .USER
|
|
131
|
+
// transcript.status: IN_PROGRESS | END | INTERRUPTED | UNKNOWN
|
|
132
|
+
// transcript.renderMode: Word or Text
|
|
133
|
+
runOnUiThread { updateTranscriptUI(transcript) }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Agent interrupted mid-speech
|
|
137
|
+
override fun onAgentInterrupted(agentUserId: String, event: InterruptEvent) {
|
|
138
|
+
Log.d("ConvoAI", "Interrupted turn: ${event.turnId}")
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Performance metrics (LLM/TTS latency)
|
|
142
|
+
override fun onAgentMetrics(agentUserId: String, metric: Metric) {
|
|
143
|
+
Log.d("ConvoAI", "Metric: ${metric.type} ${metric.name} = ${metric.value}ms")
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Agent-side error (LLM/TTS failure)
|
|
147
|
+
override fun onAgentError(agentUserId: String, error: ModuleError) {
|
|
148
|
+
Log.e("ConvoAI", "Agent error: ${error.type} code=${error.code} ${error.message}")
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Message send error (e.g., image too large)
|
|
152
|
+
override fun onMessageError(agentUserId: String, error: MessageError) {
|
|
153
|
+
Log.e("ConvoAI", "Message error: ${error.chatMessageType} code=${error.code}")
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Message receipt (server acknowledged image/text)
|
|
157
|
+
override fun onMessageReceiptUpdated(agentUserId: String, receipt: MessageReceipt) {
|
|
158
|
+
Log.d("ConvoAI", "Receipt: ${receipt.type} turnId=${receipt.turnId}")
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Voiceprint registration status (technical preview)
|
|
162
|
+
override fun onAgentVoiceprintStateChanged(agentUserId: String, event: VoiceprintStateChangeEvent) {
|
|
163
|
+
Log.d("ConvoAI", "Voiceprint: ${event.status}")
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Internal debug messages — useful during development
|
|
167
|
+
override fun onDebugLog(log: String) {
|
|
168
|
+
Log.v("ConvoAI", log)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Cleanup
|
|
174
|
+
|
|
175
|
+
```kotlin
|
|
176
|
+
fun cleanup() {
|
|
177
|
+
api.unsubscribeMessage(channelName) { }
|
|
178
|
+
api.removeHandler(eventHandler)
|
|
179
|
+
api.destroy()
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Call `destroy()` when the conversation session ends. After this call the instance cannot be reused.
|
|
184
|
+
|
|
185
|
+
## Key Types Reference
|
|
186
|
+
|
|
187
|
+
| Type | Purpose |
|
|
188
|
+
|------|---------|
|
|
189
|
+
| `ConversationalAIAPIConfig` | Init config: `rtcEngine`, `rtmClient`, `renderMode`, `enableLog` |
|
|
190
|
+
| `ConversationalAIAPIImpl` | Concrete implementation — create one per session |
|
|
191
|
+
| `IConversationalAIAPIEventHandler` | Interface for receiving all events |
|
|
192
|
+
| `TextMessage` | Text to send: `text`, `priority`, `responseInterruptable` |
|
|
193
|
+
| `ImageMessage` | Image to send: `uuid`, `imageUrl` or `imageBase64` (≤32KB) |
|
|
194
|
+
| `Priority` | `INTERRUPT` / `APPEND` / `IGNORE` |
|
|
195
|
+
| `AgentState` | `IDLE` / `SILENT` / `LISTENING` / `THINKING` / `SPEAKING` |
|
|
196
|
+
| `Transcript` | `turnId`, `text`, `type` (AGENT/USER), `status`, `renderMode` |
|
|
197
|
+
| `TranscriptRenderMode` | `Word` (word-level) or `Text` (full sentence) |
|
|
198
|
+
| `StateChangeEvent` | `state`, `turnId`, `timestamp` |
|
|
199
|
+
| `Metric` | `type` (LLM/TTS), `name`, `value` (ms), `timestamp` |
|
|
200
|
+
| `ConversationalAIAPIError` | Sealed: `RtmError(code, msg)`, `RtcError(code, msg)`, `UnknownError(msg)` |
|
|
201
|
+
|
|
202
|
+
## Notes
|
|
203
|
+
|
|
204
|
+
- `onTranscriptUpdated` fires at high frequency. Deduplicate on `turnId` in your UI if needed.
|
|
205
|
+
- All callbacks are dispatched on the main thread — safe for UI updates.
|
|
206
|
+
- The `renderMode = Word` setting falls back to `Text` automatically if the server doesn't provide word timestamps (when `enableRenderModeFallback = true`).
|
|
207
|
+
- Agent state arrives via RTM presence events (REMOTE_STATE_CHANGED); transcripts arrive via RTM channel messages.
|
|
208
|
+
- Image payloads via `imageBase64` must keep the total JSON message under 32KB. Use `imageUrl` for larger images.
|
|
209
|
+
- Audio routing changes re-apply audio parameters automatically via `onAudioRouteChanged`.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# ConvoAI Agent Toolkit — iOS (Swift)
|
|
2
|
+
|
|
3
|
+
The iOS agent toolkit (`ConversationalAIAPIImpl`) wraps RTC + RTM to deliver AI transcripts, agent state, interrupts, and metrics. It is sourced from the Conversational AI Demo repo — not from the Agora SDK packages.
|
|
4
|
+
|
|
5
|
+
Source: `ConversationalAIAPI/ConversationalAIAPI.swift` and `ConversationalAIAPIImpl.swift` in the demo repo.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Setup](#setup)
|
|
10
|
+
- [Initialization](#initialization)
|
|
11
|
+
- [Audio Configuration](#audio-configuration)
|
|
12
|
+
- [Subscribing to Events](#subscribing-to-events)
|
|
13
|
+
- [Sending Messages to the Agent](#sending-messages-to-the-agent)
|
|
14
|
+
- [Interrupting the Agent](#interrupting-the-agent)
|
|
15
|
+
- [Handling Events](#handling-events)
|
|
16
|
+
- [Cleanup](#cleanup)
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
The toolkit takes an existing `AgoraRtcEngineKit` and `AgoraRtmClientKit` — initialize both SDKs first.
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
import AgoraRtcKit
|
|
24
|
+
import AgoraRtmKit
|
|
25
|
+
import ConversationalAIAPI // or copy files directly from the demo repo
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Initialization
|
|
29
|
+
|
|
30
|
+
```swift
|
|
31
|
+
// 1. Create your RTC engine and RTM client first (standard SDK init)
|
|
32
|
+
let rtcEngine: AgoraRtcEngineKit = // ... your existing engine
|
|
33
|
+
let rtmKit: AgoraRtmClientKit = // ... your existing RTM client (already logged in)
|
|
34
|
+
|
|
35
|
+
// 2. Create the toolkit config
|
|
36
|
+
let config = ConversationalAIAPIConfig(
|
|
37
|
+
rtcEngine: rtcEngine,
|
|
38
|
+
rtmEngine: rtmKit,
|
|
39
|
+
renderMode: .words, // .words (word-by-word) or .text (full sentence)
|
|
40
|
+
enableLog: true,
|
|
41
|
+
enableRenderModeFallback: true // fall back to .text if server lacks word timestamps
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
// 3. Create the API instance
|
|
45
|
+
let api = ConversationalAIAPIImpl(config: config)
|
|
46
|
+
|
|
47
|
+
// 4. Register your event handler
|
|
48
|
+
api.addHandler(handler: self) // self implements ConversationalAIAPIEventHandler
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Audio Configuration
|
|
52
|
+
|
|
53
|
+
**Must be called before `joinChannel`** on every join to configure optimal AI audio settings.
|
|
54
|
+
|
|
55
|
+
```swift
|
|
56
|
+
// Standard ConvoAI mode
|
|
57
|
+
api.loadAudioSettings()
|
|
58
|
+
// Equivalent to: api.loadAudioSettings(secnario: .aiClient)
|
|
59
|
+
|
|
60
|
+
// If using Avatar (requires audio mixing)
|
|
61
|
+
api.loadAudioSettings(secnario: .default)
|
|
62
|
+
|
|
63
|
+
// Then join RTC channel
|
|
64
|
+
rtcEngine.joinChannel(byToken: token, channelId: channelName, info: nil, uid: userId)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Subscribing to Events
|
|
68
|
+
|
|
69
|
+
Subscribe after logging in to RTM and before the agent starts speaking.
|
|
70
|
+
|
|
71
|
+
```swift
|
|
72
|
+
api.subscribeMessage(channelName: channelName) { error in
|
|
73
|
+
if let error = error {
|
|
74
|
+
print("Subscribe failed: \(error)")
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
print("Subscribed — ready to receive agent events")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// When done
|
|
81
|
+
api.unsubscribeMessage(channelName: channelName) { error in }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Sending Messages to the Agent
|
|
85
|
+
|
|
86
|
+
```swift
|
|
87
|
+
// Text message (default priority: INTERRUPT)
|
|
88
|
+
let textMsg = TextMessage(
|
|
89
|
+
text: "What is the weather today?",
|
|
90
|
+
priority: .interrupt,
|
|
91
|
+
responseInterruptable: true
|
|
92
|
+
)
|
|
93
|
+
api.chat(agentUserId: agentUid, message: textMsg) { error in
|
|
94
|
+
if let error = error { print("Chat failed: \(error)") }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Text with APPEND priority (queue after current response)
|
|
98
|
+
let appendMsg = TextMessage(text: "And tomorrow?", priority: .append)
|
|
99
|
+
api.chat(agentUserId: agentUid, message: appendMsg) { _ in }
|
|
100
|
+
|
|
101
|
+
// Image message (URL-based; keep base64 under 32KB total)
|
|
102
|
+
let imageMsg = ImageMessage(uuid: UUID().uuidString, imageUrl: "https://example.com/photo.jpg")
|
|
103
|
+
api.chat(agentUserId: agentUid, message: imageMsg) { error in }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Interrupting the Agent
|
|
107
|
+
|
|
108
|
+
```swift
|
|
109
|
+
api.interrupt(agentUserId: agentUid) { error in
|
|
110
|
+
if let error = error { print("Interrupt failed: \(error)") }
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Handling Events
|
|
115
|
+
|
|
116
|
+
Implement `ConversationalAIAPIEventHandler`:
|
|
117
|
+
|
|
118
|
+
```swift
|
|
119
|
+
extension YourViewController: ConversationalAIAPIEventHandler {
|
|
120
|
+
|
|
121
|
+
// Agent state: .silent | .listening | .thinking | .speaking | .idle | .unknown
|
|
122
|
+
func onAgentStateChanged(agentUserId: String, event: StateChangeEvent) {
|
|
123
|
+
print("Agent \(agentUserId) state: \(event.state), turn: \(event.turnId)")
|
|
124
|
+
DispatchQueue.main.async {
|
|
125
|
+
self.updateStateIndicator(event.state)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Transcript update (fires frequently — dedup by turnId if needed)
|
|
130
|
+
func onTranscriptUpdated(agentUserId: String, transcript: Transcript) {
|
|
131
|
+
// transcript.type: .agent or .user
|
|
132
|
+
// transcript.status: .inProgress | .end | .interrupted | .unknown
|
|
133
|
+
// transcript.renderMode: .words or .text
|
|
134
|
+
DispatchQueue.main.async {
|
|
135
|
+
self.updateTranscriptUI(transcript)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Agent interrupted mid-speech
|
|
140
|
+
func onAgentInterrupted(agentUserId: String, event: InterruptEvent) {
|
|
141
|
+
print("Interrupted turn: \(event.turnId)")
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Performance metrics (LLM/TTS latency)
|
|
145
|
+
func onAgentMetrics(agentUserId: String, metrics: Metric) {
|
|
146
|
+
print("Metric: \(metrics.type) \(metrics.name) = \(metrics.value)ms")
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Agent-side error (LLM/TTS failure)
|
|
150
|
+
func onAgentError(agentUserId: String, error: ModuleError) {
|
|
151
|
+
print("Agent error: \(error.type) code=\(error.code) \(error.message)")
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Message send error (e.g., image too large)
|
|
155
|
+
func onMessageError(agentUserId: String, error: MessageError) {
|
|
156
|
+
print("Message error: \(error.chatMessageType) code=\(error.code)")
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Message receipt (server acknowledged image/text)
|
|
160
|
+
func onMessageReceiptUpdated(agentUserId: String, messageReceipt: MessageReceipt) {
|
|
161
|
+
print("Receipt: \(messageReceipt.type) turnId=\(messageReceipt.turnId)")
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Voiceprint registration status (technical preview)
|
|
165
|
+
func onAgentVoiceprintStateChanged(agentUserId: String, event: VoiceprintStateChangeEvent) {
|
|
166
|
+
print("Voiceprint: \(event.status)")
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
func onDebugLog(log: String) {
|
|
170
|
+
// Internal debug messages — useful during development
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Cleanup
|
|
176
|
+
|
|
177
|
+
```swift
|
|
178
|
+
func cleanup() {
|
|
179
|
+
api.unsubscribeMessage(channelName: channelName) { _ in }
|
|
180
|
+
api.removeHandler(handler: self)
|
|
181
|
+
api.destroy()
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Call `destroy()` when the conversation session ends. After this call the instance cannot be reused.
|
|
186
|
+
|
|
187
|
+
## Key Types Reference
|
|
188
|
+
|
|
189
|
+
| Type | Purpose |
|
|
190
|
+
|------|---------|
|
|
191
|
+
| `ConversationalAIAPIConfig` | Init config: `rtcEngine`, `rtmEngine`, `renderMode`, `enableLog` |
|
|
192
|
+
| `ConversationalAIAPIImpl` | Concrete implementation — create one per session |
|
|
193
|
+
| `ConversationalAIAPIEventHandler` | Protocol for receiving all events |
|
|
194
|
+
| `TextMessage` | Text to send: `text`, `priority`, `responseInterruptable` |
|
|
195
|
+
| `ImageMessage` | Image to send: `uuid`, `imageUrl` or `imageBase64` (≤32KB) |
|
|
196
|
+
| `Priority` | `.interrupt` / `.append` / `.ignore` |
|
|
197
|
+
| `AgentState` | `.silent` / `.listening` / `.thinking` / `.speaking` |
|
|
198
|
+
| `Transcript` | `turnId`, `text`, `type` (agent/user), `status`, `renderMode` |
|
|
199
|
+
| `TranscriptRenderMode` | `.words` (word-level) or `.text` (full sentence) |
|
|
200
|
+
| `StateChangeEvent` | `state`, `turnId`, `timestamp` |
|
|
201
|
+
| `Metric` | `type` (LLM/TTS), `name`, `value` (ms), `timestamp` |
|
|
202
|
+
|
|
203
|
+
## Notes
|
|
204
|
+
|
|
205
|
+
- `onTranscriptUpdated` fires at high frequency. Deduplicate on `turnId` in your UI if needed.
|
|
206
|
+
- The `renderMode: .words` setting falls back to `.text` automatically if the server doesn't provide word timestamps (when `enableRenderModeFallback: true`).
|
|
207
|
+
- Agent state arrives via RTM presence events; transcripts arrive via RTM channel messages.
|
|
208
|
+
- Image payloads via `imageBase64` must keep the total JSON message under 32KB. Use `imageUrl` for larger images.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agora-agent-client-toolkit
|
|
3
|
+
description: |
|
|
4
|
+
Client-side TypeScript SDK for adding Agora Conversational AI features to applications
|
|
5
|
+
already using the Agora RTC SDK. Use when the user needs to integrate agora-agent-client-toolkit
|
|
6
|
+
or agora-agent-client-toolkit-react, receive transcripts, track agent state, send messages
|
|
7
|
+
to an AI agent, handle agent events, or build a ConvoAI front-end client. Triggers on
|
|
8
|
+
agora-agent-client-toolkit, AgoraVoiceAI, useConversationalAI, useTranscript, useAgentState,
|
|
9
|
+
agent transcript, agent state, TRANSCRIPT_UPDATED, AGENT_STATE_CHANGED, ConversationalAIProvider.
|
|
10
|
+
license: MIT
|
|
11
|
+
metadata:
|
|
12
|
+
author: agora
|
|
13
|
+
version: '1.0.0'
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Agent Client Toolkit
|
|
17
|
+
|
|
18
|
+
Client-side SDK for adding Agora Conversational AI features to applications already using the Agora RTC SDK. Runs in the browser — adds transcript rendering, agent state tracking, and RTM-based messaging controls on top of `agora-rtc-sdk-ng`.
|
|
19
|
+
|
|
20
|
+
**npm:** `agora-agent-client-toolkit` (core) · `agora-agent-client-toolkit-react` (React hooks)
|
|
21
|
+
**Repo:** <https://github.com/AgoraIO-Conversational-AI/agent-client-toolkit-ts>
|
|
22
|
+
|
|
23
|
+
> This toolkit is a **client add-on** — it does not start agents. Start agents via the ConvoAI REST API first. See [README.md](README.md) for the REST API.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install agora-agent-client-toolkit agora-rtc-sdk-ng agora-rtm
|
|
29
|
+
|
|
30
|
+
# React
|
|
31
|
+
npm install agora-agent-client-toolkit-react agora-rtc-react agora-rtm
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Initialization
|
|
35
|
+
|
|
36
|
+
`AgoraVoiceAI.init()` is **async** — always `await` it. Pass the RTC client you already have.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import AgoraRTC from 'agora-rtc-sdk-ng';
|
|
40
|
+
import AgoraRTM from 'agora-rtm';
|
|
41
|
+
import { AgoraVoiceAI } from 'agora-agent-client-toolkit';
|
|
42
|
+
|
|
43
|
+
// Your existing RTC + RTM setup
|
|
44
|
+
const rtcClient = AgoraRTC.createClient({ mode: 'rtc', codec: 'vp8' });
|
|
45
|
+
const rtmClient = new AgoraRTM.RTM('APP_ID', 'RTM_USER_ID'); // must match the RTM token subject; often String(rtcUid)
|
|
46
|
+
await rtmClient.login({ token: 'RTM_TOKEN' });
|
|
47
|
+
|
|
48
|
+
// Initialize the toolkit — pass your existing clients
|
|
49
|
+
const ai = await AgoraVoiceAI.init({
|
|
50
|
+
rtcEngine: rtcClient,
|
|
51
|
+
rtmConfig: { rtmEngine: rtmClient }, // optional — needed for sendText/interrupt
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Join + publish via RTC directly (toolkit does not wrap join/publish)
|
|
55
|
+
await rtcClient.join('APP_ID', 'CHANNEL', 'RTC_TOKEN', null);
|
|
56
|
+
const micTrack = await AgoraRTC.createMicrophoneAudioTrack();
|
|
57
|
+
await rtcClient.publish([micTrack]);
|
|
58
|
+
|
|
59
|
+
// Start receiving agent messages
|
|
60
|
+
ai.subscribeMessage('CHANNEL');
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Configuration
|
|
64
|
+
|
|
65
|
+
| Field | Type | Required | Description |
|
|
66
|
+
| -------------------- | -------------------------- | -------- | ------------------------------------------------------------------- |
|
|
67
|
+
| `rtcEngine` | `IAgoraRTCClient` | Yes | Your existing Agora RTC client |
|
|
68
|
+
| `rtmConfig` | `{ rtmEngine: RTMClient }` | No | Pass your RTM client for sendText/interrupt |
|
|
69
|
+
| `renderMode` | `TranscriptHelperMode` | No | `TEXT`, `WORD`, `CHUNK`, `AUTO` (default: `AUTO`) — see table below |
|
|
70
|
+
| `enableLog` | `boolean` | No | Debug logging (default: `false`) |
|
|
71
|
+
| `enableAgoraMetrics` | `boolean` | No | Load `@agora-js/report` for usage metrics |
|
|
72
|
+
|
|
73
|
+
### Render Modes
|
|
74
|
+
|
|
75
|
+
| Mode | Update cadence | Word timing in metadata | PTS required | When to use |
|
|
76
|
+
| ------- | ---------------------------- | --------------------------------------- | ------------------------------------ | ------------------------------------------------------------- |
|
|
77
|
+
| `TEXT` | Per sentence (`final: true`) | No | No | Lowest overhead; subtitles |
|
|
78
|
+
| `WORD` | Per word | Yes (`words[].start_ms`, `duration_ms`) | **Yes** (before RTC client creation) | Karaoke-style highlight |
|
|
79
|
+
| `CHUNK` | When all parts reassembled | No | No | Fragmented transport |
|
|
80
|
+
| `AUTO` | Detected from first message | Depends on detected mode | If WORD detected | Default; fine unless you need WORD and must pre-configure PTS |
|
|
81
|
+
|
|
82
|
+
## Events
|
|
83
|
+
|
|
84
|
+
Register handlers before calling `subscribeMessage()`. All 9 events:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { AgoraVoiceAIEvents } from 'agora-agent-client-toolkit';
|
|
88
|
+
|
|
89
|
+
// Transcript — delivers FULL history every time, replace don't append
|
|
90
|
+
ai.on(AgoraVoiceAIEvents.TRANSCRIPT_UPDATED, (transcript) => {
|
|
91
|
+
renderTranscript(transcript);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Agent state — requires RTM + enable_rtm: true in agent start config
|
|
95
|
+
ai.on(AgoraVoiceAIEvents.AGENT_STATE_CHANGED, (agentUserId, event) => {
|
|
96
|
+
// event.state: 'idle' | 'listening' | 'thinking' | 'speaking' | 'silent'
|
|
97
|
+
updateStatusUI(event.state);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Agent interrupted (user cut off agent's response)
|
|
101
|
+
ai.on(AgoraVoiceAIEvents.AGENT_INTERRUPTED, (agentUserId, event) => {
|
|
102
|
+
// event: { turnID: number, timestamp: number }
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Performance metrics — requires enable_metrics: true in agent start config
|
|
106
|
+
ai.on(AgoraVoiceAIEvents.AGENT_METRICS, (agentUserId, metrics) => {
|
|
107
|
+
// metrics: { type: ModuleType, name: string, value: number, timestamp: number }
|
|
108
|
+
// ModuleType: 'llm' | 'mllm' | 'tts' | 'context' | 'unknown'
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Agent pipeline error — requires enable_error_message: true in agent start config
|
|
112
|
+
ai.on(AgoraVoiceAIEvents.AGENT_ERROR, (agentUserId, error) => {
|
|
113
|
+
// error: { type: ModuleType, code: number, message: string, timestamp: number }
|
|
114
|
+
showErrorToast(error.message);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Message delivery receipt — requires RTM
|
|
118
|
+
ai.on(AgoraVoiceAIEvents.MESSAGE_RECEIPT_UPDATED, (agentUserId, receipt) => {});
|
|
119
|
+
|
|
120
|
+
// RTM message delivery failure
|
|
121
|
+
ai.on(AgoraVoiceAIEvents.MESSAGE_ERROR, (agentUserId, error) => {});
|
|
122
|
+
|
|
123
|
+
// Speech Activity Level registration status — requires RTM
|
|
124
|
+
ai.on(AgoraVoiceAIEvents.MESSAGE_SAL_STATUS, (agentUserId, salStatus) => {});
|
|
125
|
+
|
|
126
|
+
// Internal debug log
|
|
127
|
+
ai.on(AgoraVoiceAIEvents.DEBUG_LOG, (message) => {});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Sending Messages & Interrupting
|
|
131
|
+
|
|
132
|
+
Requires `rtmConfig` — throws `RTMRequiredError` if called without RTM.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import {
|
|
136
|
+
ChatMessageType,
|
|
137
|
+
ChatMessagePriority,
|
|
138
|
+
} from 'agora-agent-client-toolkit';
|
|
139
|
+
|
|
140
|
+
// Send text to the agent
|
|
141
|
+
await ai.sendText(agentUserId, {
|
|
142
|
+
messageType: ChatMessageType.TEXT,
|
|
143
|
+
text: 'What is the weather like today?',
|
|
144
|
+
priority: ChatMessagePriority.INTERRUPTED, // interrupts current agent speech
|
|
145
|
+
responseInterruptable: true,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Send image to the agent
|
|
149
|
+
await ai.sendImage(agentUserId, {
|
|
150
|
+
messageType: ChatMessageType.IMAGE,
|
|
151
|
+
uuid: crypto.randomUUID(), // caller-generated unique ID for receipt correlation
|
|
152
|
+
url: 'https://example.com/image.png', // or: base64: '<inline data>'
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Interrupt the agent's current speech
|
|
156
|
+
await ai.interrupt(agentUserId);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `ChatMessagePriority` values
|
|
160
|
+
|
|
161
|
+
| Value | Behavior |
|
|
162
|
+
| ------------- | ------------------------------------------------------------------------------------------------------ |
|
|
163
|
+
| `INTERRUPTED` | Sends the message and immediately interrupts any speech the agent is currently producing |
|
|
164
|
+
| `APPEND` | Queues the message to be processed after the agent finishes its current speech turn |
|
|
165
|
+
| `IGNORE` | Drops the message silently if the agent is busy — use for low-priority updates only relevant when idle |
|
|
166
|
+
|
|
167
|
+
`responseInterruptable: boolean` on `ChatMessageText` — when `true`, the agent's response to this message can itself be interrupted by subsequent user input.
|
|
168
|
+
|
|
169
|
+
### `ChatMessageImage` fields
|
|
170
|
+
|
|
171
|
+
| Field | Required | Description |
|
|
172
|
+
| ------------- | ----------------- | -------------------------------------------------------------------------------- |
|
|
173
|
+
| `messageType` | Yes | Must be `ChatMessageType.IMAGE` |
|
|
174
|
+
| `uuid` | Yes | Caller-generated unique ID for receipt correlation via `MESSAGE_RECEIPT_UPDATED` |
|
|
175
|
+
| `url` | One of url/base64 | Publicly accessible image URL |
|
|
176
|
+
| `base64` | One of url/base64 | Inline image data |
|
|
177
|
+
|
|
178
|
+
## Cleanup
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
ai.unsubscribe(); // stop receiving channel messages
|
|
182
|
+
ai.destroy(); // remove all event handlers, clear singleton
|
|
183
|
+
|
|
184
|
+
await rtmClient.logout(); // you manage RTM lifecycle
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Critical Rules
|
|
188
|
+
|
|
189
|
+
1. **`init()` is async** — always `await AgoraVoiceAI.init()`. Missing the await causes `getInstance()` to throw `NotInitializedError`.
|
|
190
|
+
2. **Register events before `subscribeMessage()`** — events from messages already in the stream will be missed otherwise.
|
|
191
|
+
3. **Transcript replaces, never appends** — `TRANSCRIPT_UPDATED` delivers the complete history every time. Set state to the full array, not `prev.concat(next)`.
|
|
192
|
+
4. **`AgoraVoiceAI` is a singleton** — calling `init()` twice replaces the first instance. Call `destroy()` before re-initializing.
|
|
193
|
+
5. **RTM is optional but required for several features** — `sendText`, `sendImage`, and `interrupt` throw `RTMRequiredError` without `rtmConfig`. `AGENT_STATE_CHANGED`, `MESSAGE_RECEIPT_UPDATED`, `MESSAGE_ERROR`, `MESSAGE_SAL_STATUS` only fire with RTM.
|
|
194
|
+
6. **Agent start config flags are required for some events** — `AGENT_STATE_CHANGED` requires `advanced_features.enable_rtm: true` AND `parameters.data_channel: "rtm"`. `AGENT_METRICS` requires `parameters.enable_metrics: true`. `AGENT_ERROR` requires `parameters.enable_error_message: true`.
|
|
195
|
+
7. **Toolkit does not wrap join/publish** — call `rtcClient.join()` and `rtcClient.publish()` yourself before `subscribeMessage()`.
|
|
196
|
+
8. **WORD mode requires PTS metadata enabled before RTC client creation** — if `renderMode` is `TranscriptHelperMode.WORD`, call `AgoraRTC.setParameter('ENABLE_AUDIO_PTS_METADATA', true)` before calling `AgoraRTC.createClient()`. Setting it after client creation produces no error — word-level timing data simply never arrives. This also applies when using `AUTO` mode if WORD is detected; prefer explicit `WORD` mode when karaoke display is required so the pre-configuration step is obvious.
|
|
197
|
+
9. **RTM identity must match the RTM token subject** — if your RTM token was minted for `String(rtcUid)`, create or log in the RTM client with that same identity. Do not mint an RTM token for one user and log in RTM with another random ID; that can show up as generic conversation-start failures instead of a clear auth error.
|
|
198
|
+
|
|
199
|
+
## React Hooks
|
|
200
|
+
|
|
201
|
+
For React integration, see **[agent-client-toolkit-react.md](agent-client-toolkit-react.md)**.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Agent UI Kit
|
|
2
|
+
|
|
3
|
+
React component library (`@agora/agent-ui-kit`) — pre-built UI for ConvoAI interfaces.
|
|
4
|
+
|
|
5
|
+
**Repo:** <https://github.com/AgoraIO-Conversational-AI/agent-ui-kit>
|
|
6
|
+
**npm:** `@agora/agent-ui-kit`
|
|
7
|
+
|
|
8
|
+
## Voice Components
|
|
9
|
+
|
|
10
|
+
- `MicButton` — microphone toggle
|
|
11
|
+
- `AgentVisualizer` — agent state indicator (not-joined/listening/talking/thinking)
|
|
12
|
+
- `AudioVisualizer` — audio level bars
|
|
13
|
+
- `LiveWaveform` — real-time waveform display
|
|
14
|
+
- `MicSelector` — microphone device picker
|
|
15
|
+
- `SimpleVisualizer` — simplified audio visualizer
|
|
16
|
+
- `MicButtonWithVisualizer` — combined mic toggle + audio visualization
|
|
17
|
+
|
|
18
|
+
> **[README — Voice Components](https://github.com/AgoraIO-Conversational-AI/agent-ui-kit#voice-components)**
|
|
19
|
+
|
|
20
|
+
## Chat Components
|
|
21
|
+
|
|
22
|
+
- `Conversation` — auto-scroll chat container
|
|
23
|
+
- `Message` — user/assistant message bubbles
|
|
24
|
+
- `ConvoTextStream` — typewriter streaming text effect
|
|
25
|
+
- `Response` — structured agent response display
|
|
26
|
+
|
|
27
|
+
> **[README — Chat Components](https://github.com/AgoraIO-Conversational-AI/agent-ui-kit#chat-components)**
|
|
28
|
+
|
|
29
|
+
## Video Components
|
|
30
|
+
|
|
31
|
+
- `LocalVideoPreview` — local camera with mirror mode
|
|
32
|
+
- `AvatarVideoDisplay` — remote avatar video with connection states
|
|
33
|
+
- `CameraSelector` — camera device picker
|
|
34
|
+
- `Avatar` — avatar video display with connection-aware states
|
|
35
|
+
|
|
36
|
+
> **[README — Video Components](https://github.com/AgoraIO-Conversational-AI/agent-ui-kit#video-components)**
|
|
37
|
+
|
|
38
|
+
## Settings Components
|
|
39
|
+
|
|
40
|
+
- `AgentSettings` — AIVAD toggle, language, prompt, greeting
|
|
41
|
+
- `SettingsDialog` — modal wrapper
|
|
42
|
+
|
|
43
|
+
> **[README — Settings Components](https://github.com/AgoraIO-Conversational-AI/agent-ui-kit#settings-components)**
|
|
44
|
+
|
|
45
|
+
## Layout Components
|
|
46
|
+
|
|
47
|
+
- `VideoGrid` — 2x2 desktop grid
|
|
48
|
+
- `MobileTabs` — tab switcher
|
|
49
|
+
- `VideoGridWithControls` — video grid with integrated media controls
|
|
50
|
+
|
|
51
|
+
> **[README — Layout Components](https://github.com/AgoraIO-Conversational-AI/agent-ui-kit#layout-components)**
|
|
52
|
+
|
|
53
|
+
## Hooks
|
|
54
|
+
|
|
55
|
+
- `useRTMSubscription` — generic RTM message subscription by object type
|
|
56
|
+
|
|
57
|
+
> **[README — Hooks](https://github.com/AgoraIO-Conversational-AI/agent-ui-kit#hooks)**
|
|
58
|
+
|
|
59
|
+
## Theming
|
|
60
|
+
|
|
61
|
+
Tailwind CSS-based theming.
|
|
62
|
+
|
|
63
|
+
> **[README — Theming](https://github.com/AgoraIO-Conversational-AI/agent-ui-kit#theming)**
|