@@ -6,30 +6,162 @@ import OSLog
66
77struct IOSGatewayChatTransport : OpenClawChatTransport {
88 private static let logger = Logger ( subsystem: " ai.openclaw " , category: " ios.chat.transport " )
9+ static let defaultChatSendTimeoutMs = 30000
910 private let gateway : GatewayNodeSession
1011
12+ private struct CreateSessionParams : Codable {
13+ var key : String
14+ var label : String ?
15+ var parentSessionKey : String ?
16+ }
17+
18+ private struct RunParams : Codable {
19+ var sessionKey : String
20+ var runId : String
21+ }
22+
23+ private struct ListSessionsParams : Codable {
24+ var includeGlobal : Bool
25+ var includeUnknown : Bool
26+ var limit : Int ?
27+ }
28+
29+ private struct SessionKeyParams : Codable {
30+ var key : String
31+ }
32+
33+ private struct ChatSendParams : Codable {
34+ var sessionKey : String
35+ var message : String
36+ var thinking : String
37+ var attachments : [ OpenClawChatAttachmentPayload ] ?
38+ var timeoutMs : Int
39+ var idempotencyKey : String
40+ }
41+
42+ private struct AgentWaitParams : Codable {
43+ var runId : String
44+ var timeoutMs : Int
45+ }
46+
47+ private struct AgentWaitResponse : Codable {
48+ var runId : String ?
49+ var status : String ?
50+ var error : String ?
51+ }
52+
53+ struct AgentWaitCompletion : Equatable {
54+ var runId : String
55+ var status : String
56+ var completed : Bool
57+ }
58+
59+ static func isAgentWaitCompletionStatus( _ status: String ) -> Bool {
60+ switch status. trimmingCharacters ( in: . whitespacesAndNewlines) . lowercased ( ) {
61+ case " ok " , " completed " , " success " , " succeeded " :
62+ true
63+ default :
64+ false
65+ }
66+ }
67+
1168 init ( gateway: GatewayNodeSession ) {
1269 self . gateway = gateway
1370 }
1471
15- func abortRun( sessionKey: String , runId: String ) async throws {
16- struct Params : Codable {
17- var sessionKey : String
18- var runId : String
72+ static func agentWaitRequestTimeoutSeconds( timeoutMs: Int ) -> Int {
73+ max ( 1 , Int ( ceil ( Double ( timeoutMs) / 1000.0 ) ) + 5 )
74+ }
75+
76+ static func makeListSessionsParamsJSON( limit: Int ? ) throws -> String {
77+ try self . encodeParams ( ListSessionsParams ( includeGlobal: true , includeUnknown: false , limit: limit) )
78+ }
79+
80+ static func makeChatSendParamsJSON(
81+ sessionKey: String ,
82+ message: String ,
83+ thinking: String ,
84+ idempotencyKey: String ,
85+ attachments: [ OpenClawChatAttachmentPayload ] ) throws -> String
86+ {
87+ let params = ChatSendParams (
88+ sessionKey: sessionKey,
89+ message: message,
90+ thinking: thinking,
91+ attachments: attachments. isEmpty ? nil : attachments,
92+ timeoutMs: self . defaultChatSendTimeoutMs,
93+ idempotencyKey: idempotencyKey)
94+ return try self . encodeParams ( params)
95+ }
96+
97+ static func decodeAgentWaitCompletion( _ data: Data , fallbackRunId: String ) throws -> AgentWaitCompletion {
98+ let decoded = try JSONDecoder ( ) . decode ( AgentWaitResponse . self, from: data)
99+ let status = ( decoded. status ?? " unknown " ) . lowercased ( )
100+ return AgentWaitCompletion (
101+ runId: decoded. runId ?? fallbackRunId,
102+ status: status,
103+ completed: self . isAgentWaitCompletionStatus ( status) )
104+ }
105+
106+ private static func makeCreateSessionParamsJSON(
107+ key: String ,
108+ label: String ? ,
109+ parentSessionKey: String ? ) throws -> String
110+ {
111+ let params = CreateSessionParams (
112+ key: key,
113+ label: label,
114+ parentSessionKey: parentSessionKey)
115+ return try self . encodeParams ( params)
116+ }
117+
118+ private static func makeRunParamsJSON( sessionKey: String , runId: String ) throws -> String {
119+ try self . encodeParams ( RunParams ( sessionKey: sessionKey, runId: runId) )
120+ }
121+
122+ private static func makeSessionKeyParamsJSON( _ sessionKey: String ) throws -> String {
123+ try self . encodeParams ( SessionKeyParams ( key: sessionKey) )
124+ }
125+
126+ private static func makeHistoryParamsJSON( sessionKey: String ) throws -> String {
127+ struct Params : Codable { var sessionKey : String }
128+ return try self . encodeParams ( Params ( sessionKey: sessionKey) )
129+ }
130+
131+ private static func makeAgentWaitParamsJSON( runId: String , timeoutMs: Int ) throws -> String {
132+ try self . encodeParams ( AgentWaitParams ( runId: runId, timeoutMs: timeoutMs) )
133+ }
134+
135+ private static func encodeParams( _ params: some Encodable ) throws -> String {
136+ let data = try JSONEncoder ( ) . encode ( params)
137+ guard let json = String ( bytes: data, encoding: . utf8) else {
138+ throw EncodingError . invalidValue (
139+ params,
140+ EncodingError . Context ( codingPath: [ ] , debugDescription: " Encoded gateway params were not UTF-8 " ) )
19141 }
20- let data = try JSONEncoder ( ) . encode ( Params ( sessionKey: sessionKey, runId: runId) )
21- let json = String ( data: data, encoding: . utf8)
142+ return json
143+ }
144+
145+ func createSession(
146+ key: String ,
147+ label: String ? ,
148+ parentSessionKey: String ? ) async throws -> OpenClawChatCreateSessionResponse
149+ {
150+ let json = try Self . makeCreateSessionParamsJSON (
151+ key: key,
152+ label: label,
153+ parentSessionKey: parentSessionKey)
154+ let res = try await self . gateway. request ( method: " sessions.create " , paramsJSON: json, timeoutSeconds: 15 )
155+ return try JSONDecoder ( ) . decode ( OpenClawChatCreateSessionResponse . self, from: res)
156+ }
157+
158+ func abortRun( sessionKey: String , runId: String ) async throws {
159+ let json = try Self . makeRunParamsJSON ( sessionKey: sessionKey, runId: runId)
22160 _ = try await self . gateway. request ( method: " chat.abort " , paramsJSON: json, timeoutSeconds: 10 )
23161 }
24162
25163 func listSessions( limit: Int ? ) async throws -> OpenClawChatSessionsListResponse {
26- struct Params : Codable {
27- var includeGlobal : Bool
28- var includeUnknown : Bool
29- var limit : Int ?
30- }
31- let data = try JSONEncoder ( ) . encode ( Params ( includeGlobal: true , includeUnknown: false , limit: limit) )
32- let json = String ( data: data, encoding: . utf8)
164+ let json = try Self . makeListSessionsParamsJSON ( limit: limit)
33165 let res = try await self . gateway. request ( method: " sessions.list " , paramsJSON: json, timeoutSeconds: 15 )
34166 return try JSONDecoder ( ) . decode ( OpenClawChatSessionsListResponse . self, from: res)
35167 }
@@ -40,23 +172,17 @@ struct IOSGatewayChatTransport: OpenClawChatTransport {
40172 }
41173
42174 func resetSession( sessionKey: String ) async throws {
43- struct Params : Codable { var key : String }
44- let data = try JSONEncoder ( ) . encode ( Params ( key: sessionKey) )
45- let json = String ( data: data, encoding: . utf8)
175+ let json = try Self . makeSessionKeyParamsJSON ( sessionKey)
46176 _ = try await self . gateway. request ( method: " sessions.reset " , paramsJSON: json, timeoutSeconds: 10 )
47177 }
48178
49179 func compactSession( sessionKey: String ) async throws {
50- struct Params : Codable { var key : String }
51- let data = try JSONEncoder ( ) . encode ( Params ( key: sessionKey) )
52- let json = String ( data: data, encoding: . utf8)
180+ let json = try Self . makeSessionKeyParamsJSON ( sessionKey)
53181 _ = try await self . gateway. request ( method: " sessions.compact " , paramsJSON: json, timeoutSeconds: 10 )
54182 }
55183
56184 func requestHistory( sessionKey: String ) async throws -> OpenClawChatHistoryPayload {
57- struct Params : Codable { var sessionKey : String }
58- let data = try JSONEncoder ( ) . encode ( Params ( sessionKey: sessionKey) )
59- let json = String ( data: data, encoding: . utf8)
185+ let json = try Self . makeHistoryParamsJSON ( sessionKey: sessionKey)
60186 let res = try await self . gateway. request ( method: " chat.history " , paramsJSON: json, timeoutSeconds: 15 )
61187 return try JSONDecoder ( ) . decode ( OpenClawChatHistoryPayload . self, from: res)
62188 }
@@ -73,35 +199,52 @@ struct IOSGatewayChatTransport: OpenClawChatTransport {
73199 + " len= \( message. count) attachments= \( attachments. count) "
74200 Self . logger. info (
75201 " \( startLogMessage, privacy: . public) " )
76- struct Params : Codable {
77- var sessionKey : String
78- var message : String
79- var thinking : String
80- var attachments : [ OpenClawChatAttachmentPayload ] ?
81- var timeoutMs : Int
82- var idempotencyKey : String
83- }
84-
85- let params = Params (
202+ GatewayDiagnostics . log ( startLogMessage)
203+ let json = try Self . makeChatSendParamsJSON (
86204 sessionKey: sessionKey,
87205 message: message,
88206 thinking: thinking,
89- attachments: attachments. isEmpty ? nil : attachments,
90- timeoutMs: 30000 ,
91- idempotencyKey: idempotencyKey)
92- let data = try JSONEncoder ( ) . encode ( params)
93- let json = String ( data: data, encoding: . utf8)
207+ idempotencyKey: idempotencyKey,
208+ attachments: attachments)
94209 do {
95210 let res = try await self . gateway. request ( method: " chat.send " , paramsJSON: json, timeoutSeconds: 35 )
96211 let decoded = try JSONDecoder ( ) . decode ( OpenClawChatSendResponse . self, from: res)
97212 Self . logger. info ( " chat.send ok runId= \( decoded. runId, privacy: . public) " )
213+ GatewayDiagnostics . log ( " chat.send ok runId= \( decoded. runId) status= \( decoded. status) " )
98214 return decoded
99215 } catch {
100216 Self . logger. error ( " chat.send failed \( error. localizedDescription, privacy: . public) " )
217+ GatewayDiagnostics . log ( " chat.send failed error= \( error. localizedDescription) " )
101218 throw error
102219 }
103220 }
104221
222+ func waitForRunCompletion( runId rawRunId: String , timeoutMs: Int ) async -> Bool {
223+ let runId = rawRunId. trimmingCharacters ( in: . whitespacesAndNewlines)
224+ guard !runId. isEmpty else { return false }
225+
226+ do {
227+ let json = try Self . makeAgentWaitParamsJSON ( runId: runId, timeoutMs: timeoutMs)
228+ let requestTimeoutSeconds = Self . agentWaitRequestTimeoutSeconds ( timeoutMs: timeoutMs)
229+ GatewayDiagnostics . log ( " agent.wait start runId= \( runId) " )
230+ let res = try await self . gateway. request (
231+ method: " agent.wait " ,
232+ paramsJSON: json,
233+ timeoutSeconds: requestTimeoutSeconds)
234+ let completion = try Self . decodeAgentWaitCompletion ( res, fallbackRunId: runId)
235+ GatewayDiagnostics . log ( " agent.wait completed runId= \( completion. runId) status= \( completion. status) " )
236+ if !completion. completed {
237+ Self . logger. warning (
238+ " agent.wait status \( completion. status, privacy: . public) runId= \( runId, privacy: . public) " )
239+ }
240+ return completion. completed
241+ } catch {
242+ Self . logger. warning ( " agent.wait failed \( error. localizedDescription, privacy: . public) " )
243+ GatewayDiagnostics . log ( " agent.wait failed runId= \( runId) error= \( error. localizedDescription) " )
244+ return false
245+ }
246+ }
247+
105248 func requestHealth( timeoutMs: Int ) async throws -> Bool {
106249 let seconds = max ( 1 , Int ( ceil ( Double ( timeoutMs) / 1000.0 ) ) )
107250 let res = try await self . gateway. request ( method: " health " , paramsJSON: nil , timeoutSeconds: seconds)
0 commit comments