Skip to content

Commit 85abe9a

Browse files
committed
feat: tool will reach to client and client can react on tools, it will support action on web later
1 parent 3745165 commit 85abe9a

24 files changed

Lines changed: 4271 additions & 569 deletions

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rapidaai/react",
3-
"version": "1.1.67",
3+
"version": "1.1.68",
44
"description": "An easy to use react client for building generative ai application using Rapida platform.",
55
"repository": {
66
"type": "git",
@@ -127,4 +127,4 @@
127127
"ts-protoc-gen": "^0.15.1-pre.a71b34e",
128128
"typed-emitter": "^2.1.0"
129129
}
130-
}
130+
}

readme.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,11 @@ const agent = new VoiceAgent(
443443
onConfiguration: (cfg) => console.log("Config:", cfg),
444444
onInterrupt: () => console.log("Interrupted"),
445445
onInitialization: (init) => console.log("Session started:", init),
446-
onDirective: (dir) => console.log("Directive:", dir),
446+
onToolCall: (toolCall) => {
447+
console.log("Tool call:", toolCall);
448+
// Return a result to send back to server, or void to skip
449+
return { status: "completed" };
450+
},
447451
}
448452
);
449453
```
@@ -1092,7 +1096,7 @@ const updated = await UpdateNotificationSetting(config, request, authHeader);
10921096
| `InputOptions` | Input channel config: `channels`, `channel`, `device`, `iceServers`. |
10931097
| `OutputOptions` | Output channel config: `channels`, `channel`, `device`. |
10941098
| `UserIdentifier` | User identity: `id` and optional `name`. |
1095-
| `AgentCallback` | Callback interface: `onAssistantMessage`, `onUserMessage`, `onConfiguration`, `onInterrupt`, `onDirective`, `onInitialization`, `onConnectionStateChange`, `onConnected`, `onDisconnected`, `onError`. |
1099+
| `AgentCallback` | Callback interface: `onAssistantMessage`, `onUserMessage`, `onConfiguration`, `onInterrupt`, `onToolCall`, `onToolCallResult`, `onInitialization`, `onConnectionStateChange`, `onConnected`, `onDisconnected`, `onError`. |
10961100
| `Channel` | Enum: `Channel.Audio`, `Channel.Text` |
10971101
| `ConnectionState` | Enum: `ConnectionState.Disconnected`, `ConnectionState.Connecting`, `ConnectionState.Connected` |
10981102
| `Message` | Message object: `id`, `role`, `messages[]`, `feedback?`, `time`, `status` |

src/agents/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ export function describeResponse(res: WebTalkResponse): string {
8080
if (res.hasAssistant()) parts.push(`Assistant("${res.getAssistant()?.getText()?.substring(0, 80) ?? ""}"`);
8181
if (res.hasUser()) parts.push(`User("${res.getUser()?.getText()?.substring(0, 80) ?? ""}"`);
8282
if (res.hasInterruption()) parts.push("Interruption");
83-
if (res.hasDirective()) parts.push("Directive");
83+
if (res.hasToolcall?.()) parts.push("ToolCall");
84+
if (res.hasToolcallresult?.()) parts.push("ToolCallResult");
8485
if (res.hasSignaling()) parts.push("Signaling");
8586
return parts.length > 0 ? parts.join(" + ") : "Empty";
8687
}

src/agents/voice-agent.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import {
4040
ConversationUserMessage,
4141
} from "@/rapida/types/agent-callback";
4242
import { ConnectionState } from "@/rapida/types/connection-state";
43-
import { ConversationDirective } from "../clients/protos/talk-api_pb";
4443

4544
const LOG_PREFIX = "[Rapida:VoiceAgent]";
4645

@@ -144,12 +143,31 @@ export class VoiceAgent extends Agent {
144143

145144
},
146145

147-
onDirective: (directive) => {
148-
// console.log(`${LOG_PREFIX} callback -> onDirective`, directive);
149-
if (directive && directive.type === ConversationDirective.DirectiveType.END_CONVERSATION) {
150-
this.disconnect();
146+
onToolCall: (toolCall) => {
147+
const sendResult = (result: Record<string, string>) => {
148+
this.webrtcTransport?.sendToolCallResult(
149+
toolCall.id, toolCall.toolid, toolCall.name, toolCall.action, result,
150+
);
151+
};
152+
153+
for (const cb of this.agentCallbacks) {
154+
const r = cb.onToolCall?.(toolCall);
155+
if (r && typeof (r as Promise<unknown>).then === "function") {
156+
(r as Promise<Record<string, string>>).then((res) => { if (res) sendResult(res); });
157+
return;
158+
}
159+
if (r) {
160+
sendResult(r as Record<string, string>);
161+
return;
162+
}
151163
}
152164
},
165+
166+
onToolCallResult: (result) => {
167+
this.agentCallbacks.forEach((cb) => {
168+
cb.onToolCallResult?.(result);
169+
});
170+
},
153171
onConversationEvent: (event) => {
154172
this.agentCallbacks.forEach((cb) => {
155173
cb.onConversationEvent?.(event);

src/audio/grpc-signaling-manager.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import { AgentCallback } from "@/rapida/types/agent-callback";
2828
import { WebTalk } from "@/rapida/clients/webrtc";
2929
import {
3030
ConversationUserMessage,
31+
ConversationToolCallResult,
32+
ToolCallActionMap,
3133
} from "@/rapida/clients/protos/talk-api_pb";
3234
import {
3335
WebTalkRequest,
@@ -195,6 +197,36 @@ export class GrpcSignalingManager {
195197
}
196198
}
197199

200+
/** Send a tool call result back to the server */
201+
sendToolCallResult(
202+
id: string,
203+
toolId: string,
204+
name: string,
205+
action: ToolCallActionMap[keyof ToolCallActionMap],
206+
result: Record<string, string>,
207+
): void {
208+
if (!this.grpcStream) return;
209+
this.ensureInitializationSent();
210+
211+
try {
212+
const request = new WebTalkRequest();
213+
const toolCallResult = new ConversationToolCallResult();
214+
toolCallResult.setId(id);
215+
toolCallResult.setToolid(toolId);
216+
toolCallResult.setName(name);
217+
toolCallResult.setAction(action);
218+
const resultMap = toolCallResult.getResultMap();
219+
for (const [k, v] of Object.entries(result)) {
220+
resultMap.set(k, v);
221+
}
222+
request.setToolcallresult(toolCallResult);
223+
this.grpcStream.write(request);
224+
} catch (error) {
225+
console.error(`${LOG_PREFIX} Failed to send tool call result`, error);
226+
this.callbacks.onError?.(new Error(`Failed to send tool call result: ${error}`));
227+
}
228+
}
229+
198230
/** Send an SDP answer via ClientSignaling */
199231
sendWebRTCAnswer(sdp: string): void {
200232
if (!this.grpcStream) return;

src/audio/message-protocol-handler.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import type { AudioMediaManager } from "./audio-media-manager";
4343
* - Parse each response and route to the correct manager / callback
4444
* - Handle ServerSignaling (config, SDP, ICE, ready, clear, error)
4545
* - Handle conversation init/config responses
46-
* - Handle assistant / user / interruption / directive messages
46+
* - Handle assistant / user / interruption / tool call messages
4747
*
4848
* This is pure dispatch logic — it holds no state of its own.
4949
*/
@@ -115,10 +115,21 @@ export class MessageProtocolHandler {
115115
if (interruption) this.callbacks.onInterrupt?.(interruption.toObject());
116116
}
117117

118-
// Directive / action
119-
if (response.hasDirective()) {
120-
const directive = response.getDirective();
121-
if (directive) this.callbacks.onDirective?.(directive.toObject());
118+
// Tool call (server invokes a tool — client may need to act)
119+
if (response.hasToolcall()) {
120+
const toolCall = response.getToolcall();
121+
if (toolCall) this.callbacks.onToolCall?.(toolCall.toObject());
122+
}
123+
124+
// Tool call result (server-side tool completed — informational)
125+
if (response.hasToolcallresult()) {
126+
const result = response.getToolcallresult();
127+
if (result) this.callbacks.onToolCallResult?.(result.toObject());
128+
}
129+
130+
// Disconnection (server requests disconnect)
131+
if (response.hasDisconnection()) {
132+
this.callbacks.onDisconnected?.();
122133
}
123134

124135
// Pipeline conversation event (STT, TTS, LLM, session, etc.)

src/audio/webrtc-grpc-transport.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ export class WebRTCGrpcTransport {
209209
sendConversationInitialization(): void { this.signaling.sendConversationInitialization(); }
210210
sendConversationConfiguration(): void { this.signaling.sendConversationConfiguration(); }
211211
sendText(text: string): void { this.signaling.sendText(text); }
212+
sendToolCallResult(id: string, toolId: string, name: string, action: 0 | 1 | 2, result: Record<string, string>): void {
213+
this.signaling.sendToolCallResult(id, toolId, name, action, result);
214+
}
212215

213216
// ---------------------------------------------------------------------------
214217
// Audio controls (delegated to audio & peer managers)

src/clients/protos/agentkit_pb.d.ts

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -90,20 +90,15 @@ export class TalkOutput extends jspb.Message {
9090
getAssistant(): talk_api_pb.ConversationAssistantMessage | undefined;
9191
setAssistant(value?: talk_api_pb.ConversationAssistantMessage): void;
9292

93-
hasTool(): boolean;
94-
clearTool(): void;
95-
getTool(): talk_api_pb.ConversationToolCall | undefined;
96-
setTool(value?: talk_api_pb.ConversationToolCall): void;
93+
hasToolcall(): boolean;
94+
clearToolcall(): void;
95+
getToolcall(): talk_api_pb.ConversationToolCall | undefined;
96+
setToolcall(value?: talk_api_pb.ConversationToolCall): void;
9797

98-
hasToolresult(): boolean;
99-
clearToolresult(): void;
100-
getToolresult(): talk_api_pb.ConversationToolResult | undefined;
101-
setToolresult(value?: talk_api_pb.ConversationToolResult): void;
102-
103-
hasDirective(): boolean;
104-
clearDirective(): void;
105-
getDirective(): talk_api_pb.ConversationDirective | undefined;
106-
setDirective(value?: talk_api_pb.ConversationDirective): void;
98+
hasToolcallresult(): boolean;
99+
clearToolcallresult(): void;
100+
getToolcallresult(): talk_api_pb.ConversationToolCallResult | undefined;
101+
setToolcallresult(value?: talk_api_pb.ConversationToolCallResult): void;
107102

108103
hasError(): boolean;
109104
clearError(): void;
@@ -128,9 +123,8 @@ export namespace TalkOutput {
128123
initialization?: talk_api_pb.ConversationInitialization.AsObject,
129124
interruption?: talk_api_pb.ConversationInterruption.AsObject,
130125
assistant?: talk_api_pb.ConversationAssistantMessage.AsObject,
131-
tool?: talk_api_pb.ConversationToolCall.AsObject,
132-
toolresult?: talk_api_pb.ConversationToolResult.AsObject,
133-
directive?: talk_api_pb.ConversationDirective.AsObject,
126+
toolcall?: talk_api_pb.ConversationToolCall.AsObject,
127+
toolcallresult?: talk_api_pb.ConversationToolCallResult.AsObject,
134128
error?: common_pb.Error.AsObject,
135129
}
136130

@@ -139,9 +133,8 @@ export namespace TalkOutput {
139133
INITIALIZATION = 9,
140134
INTERRUPTION = 10,
141135
ASSISTANT = 12,
142-
TOOL = 13,
143-
TOOLRESULT = 14,
144-
DIRECTIVE = 16,
136+
TOOLCALL = 13,
137+
TOOLCALLRESULT = 14,
145138
ERROR = 15,
146139
}
147140
}

0 commit comments

Comments
 (0)