Changeset 0.11.0 (#62)
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/events"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/providers"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/tools"
|
||||
)
|
||||
@@ -40,6 +44,7 @@ func streamWithToolLoop(
|
||||
cfg providers.ProviderConfig,
|
||||
req *providers.CompletionRequest,
|
||||
model, userID, channelID string,
|
||||
hub *events.Hub,
|
||||
) streamResult {
|
||||
// Set SSE headers
|
||||
c.Header("Content-Type", "text/event-stream")
|
||||
@@ -171,8 +176,25 @@ func streamWithToolLoop(
|
||||
Arguments: tc.Function.Arguments,
|
||||
}
|
||||
|
||||
log.Printf("🔧 Executing tool: %s (call %s)", call.Name, call.ID)
|
||||
toolResult := tools.ExecuteCall(c.Request.Context(), execCtx, call)
|
||||
var toolResult tools.ToolResult
|
||||
|
||||
// Check if tool is registered locally (server-side)
|
||||
if tools.Get(call.Name) != nil {
|
||||
log.Printf("🔧 Executing tool (server): %s (call %s)", call.Name, call.ID)
|
||||
toolResult = tools.ExecuteCall(c.Request.Context(), execCtx, call)
|
||||
} else if hub != nil && hub.IsConnected(userID) {
|
||||
// Route to browser via WebSocket bridge
|
||||
log.Printf("🔧 Executing tool (browser): %s (call %s)", call.Name, call.ID)
|
||||
toolResult = executeBrowserTool(hub, userID, call)
|
||||
} else {
|
||||
log.Printf("⚠️ Unknown tool: %s (call %s)", call.Name, call.ID)
|
||||
toolResult = tools.ToolResult{
|
||||
ToolCallID: call.ID,
|
||||
Name: call.Name,
|
||||
Content: `{"error":"unknown tool or browser not connected"}`,
|
||||
IsError: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Notify client about tool result
|
||||
resultJSON, _ := json.Marshal(map[string]interface{}{
|
||||
@@ -214,3 +236,68 @@ func streamWithToolLoop(
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const browserToolTimeout = 30 * time.Second
|
||||
|
||||
// executeBrowserTool sends a tool call to the user's browser via WebSocket
|
||||
// and blocks until a result is returned or the timeout elapses.
|
||||
func executeBrowserTool(hub *events.Hub, userID string, call tools.ToolCall) tools.ToolResult {
|
||||
b := make([]byte, 16)
|
||||
rand.Read(b)
|
||||
callID := hex.EncodeToString(b)
|
||||
|
||||
// Publish tool.call event to the user's browser
|
||||
payload, _ := json.Marshal(map[string]interface{}{
|
||||
"call_id": callID,
|
||||
"tool": call.Name,
|
||||
"arguments": json.RawMessage(call.Arguments),
|
||||
})
|
||||
|
||||
hub.SendToUser(userID, events.Event{
|
||||
Label: "tool.call." + callID,
|
||||
Payload: payload,
|
||||
Ts: time.Now().UnixMilli(),
|
||||
})
|
||||
|
||||
// Block until browser sends tool.result.{callID} or timeout
|
||||
event, ok := hub.GetBus().WaitFor("tool.result."+callID, browserToolTimeout)
|
||||
if !ok {
|
||||
log.Printf("⚠️ Browser tool %s timed out after %v (call %s)", call.Name, browserToolTimeout, callID)
|
||||
return tools.ToolResult{
|
||||
ToolCallID: call.ID,
|
||||
Name: call.Name,
|
||||
Content: `{"error":"browser tool timed out (30s) — browser may be disconnected"}`,
|
||||
IsError: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse result from the browser event payload
|
||||
var result struct {
|
||||
CallID string `json:"call_id"`
|
||||
Result string `json:"result"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
if err := json.Unmarshal(event.Payload, &result); err != nil {
|
||||
return tools.ToolResult{
|
||||
ToolCallID: call.ID,
|
||||
Name: call.Name,
|
||||
Content: `{"error":"invalid result from browser tool"}`,
|
||||
IsError: true,
|
||||
}
|
||||
}
|
||||
|
||||
if result.Error != "" {
|
||||
return tools.ToolResult{
|
||||
ToolCallID: call.ID,
|
||||
Name: call.Name,
|
||||
Content: result.Error,
|
||||
IsError: true,
|
||||
}
|
||||
}
|
||||
|
||||
return tools.ToolResult{
|
||||
ToolCallID: call.ID,
|
||||
Name: call.Name,
|
||||
Content: result.Result,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user