Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion samples/CameraAccess/CameraAccess/OpenClaw/ToolCallRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ class ToolCallRouter {
private var consecutiveFailures = 0
private let maxConsecutiveFailures = 3

/// Maximum time to wait for a single tool call before returning a timeout
/// failure to Gemini. Kept well below the URLSession 120s hard limit so the
/// conversation unblocks in a reasonable time even when OpenClaw is slow
/// (e.g. a web search that never finishes).
private let toolCallTimeoutSeconds: UInt64 = 60

init(bridge: OpenClawBridge) {
self.bridge = bridge
}
Expand Down Expand Up @@ -38,7 +44,26 @@ class ToolCallRouter {

let task = Task { @MainActor in
let taskDesc = call.args["task"] as? String ?? String(describing: call.args)
let result = await bridge.delegateTask(task: taskDesc, toolName: callName)
let timeoutSeconds = self.toolCallTimeoutSeconds

// Race the actual delegate call against a watchdog timer. Whichever
// finishes first wins; the other child task is cancelled immediately.
let result = await withTaskGroup(of: ToolResult.self) { group in
group.addTask {
await self.bridge.delegateTask(task: taskDesc, toolName: callName)
}
group.addTask {
try? await Task.sleep(nanoseconds: timeoutSeconds * 1_000_000_000)
NSLog("[ToolCall] Watchdog fired for %@ after %llu seconds", callId, timeoutSeconds)
return .failure(
"The gateway did not respond within \(timeoutSeconds) seconds. " +
"Tell the user the action timed out and suggest they try again."
)
}
let first = await group.next()!
group.cancelAll()
return first
}

guard !Task.isCancelled else {
NSLog("[ToolCall] Task %@ was cancelled, skipping response", callId)
Expand Down