@@ -52,6 +52,20 @@ claims = {
5252Map<String , String > claims = new HashMap<> ();
5353claims. put(" x-ably-clientId" , " user-123" );
5454```
55+
56+ { /* Swift example test harness
57+ ID: accepting-user-input-1
58+ To verify: copy this comment into a Swift file, paste the example code into the function body, run `swift build`
59+
60+ func example() {
61+ // --- example code starts here ---
62+ */ }
63+ ``` swift
64+ let claims = [
65+ " x-ably-clientId" : " user-123"
66+ ]
67+ ```
68+ { /* --- end example code --- */ }
5569</Code >
5670
5771The ` clientId ` is automatically attached to every message the user publishes, so agents can trust this identity.
@@ -91,6 +105,32 @@ channel.subscribe("user-input", message -> {
91105 processAndRespond(channel, text, promptId, userId);
92106});
93107```
108+
109+ { /* Swift example test harness
110+ ID: accepting-user-input-2
111+ To verify: copy this comment into a Swift file, paste the example code into the function body, run `swift build`
112+
113+ func example_accepting_user_input_2(
114+ channel: ARTRealtimeChannel,
115+ processAndRespond: @escaping (_ channel: ARTRealtimeChannel, _ text: String, _ promptID: String, _ userID: String) -> Void
116+ ) {
117+ // --- example code starts here ---
118+ */ }
119+ ``` swift
120+ channel.subscribe (" user-input" ) { message in
121+ guard let userID = message.clientId else { return }
122+ // promptId is a user-generated UUID for correlating responses
123+ guard let data = message.data as? [String : Any ],
124+ let promptID = data[" promptId" ] as? String ,
125+ let text = data[" text" ] as? String else {
126+ return
127+ }
128+
129+ print (" Received prompt from user \( userID ) " )
130+ processAndRespond (channel, text, promptID, userID)
131+ }
132+ ```
133+ { /* --- end example code --- */ }
94134</Code >
95135
96136### Verify by role <a id = " verify-role" />
@@ -114,6 +154,20 @@ claims = {
114154Map<String , String > claims = new HashMap<> ();
115155claims. put(" ably.channel.*" , " user" );
116156```
157+
158+ { /* Swift example test harness
159+ ID: accepting-user-input-3
160+ To verify: copy this comment into a Swift file, paste the example code into the function body, run `swift build`
161+
162+ func example() {
163+ // --- example code starts here ---
164+ */ }
165+ ``` swift
166+ let claims = [
167+ " ably.channel.*" : " user"
168+ ]
169+ ```
170+ { /* --- end example code --- */ }
117171</Code >
118172
119173The user claim is automatically attached to every message the user publishes, so agents can trust this role information.
@@ -164,6 +218,36 @@ channel.subscribe("user-input", message -> {
164218 processAndRespond (channel, text, promptId);
165219});
166220` ` `
221+
222+ {/* Swift example test harness
223+ ID: accepting-user-input-4
224+ To verify: copy this comment into a Swift file, paste the example code into the function body, run ` swift build`
225+
226+ func example_accepting_user_input_4(
227+ channel: ARTRealtimeChannel,
228+ processAndRespond: @escaping (_ channel: ARTRealtimeChannel, _ text: String, _ promptID: String) -> Void
229+ ) {
230+ // --- example code starts here ---
231+ */}
232+ ` ` ` swift
233+ channel .subscribe (" user-input" ) { message in
234+ let role = (try ? message .extras ? .toJSON ())? [" userClaim" ] as? String
235+ // promptId is a user-generated UUID for correlating responses
236+ guard let data = message .data as? [String : Any],
237+ let promptID = data[" promptId" ] as? String ,
238+ let text = data[" text" ] as? String else {
239+ return
240+ }
241+
242+ guard role == " user" else {
243+ print (" Ignoring message from non-user" )
244+ return
245+ }
246+
247+ processAndRespond (channel, text, promptID)
248+ }
249+ ` ` `
250+ {/* --- end example code --- */}
167251</Code>
168252
169253## Publish user input <a id="publish"/>
@@ -204,6 +288,24 @@ data.addProperty("promptId", promptId);
204288data .addProperty (" text" , " What is the weather like today?" );
205289channel .publish (" user-input" , data);
206290` ` `
291+
292+ {/* Swift example test harness
293+ ID: accepting-user-input-5
294+ To verify: copy this comment into a Swift file, paste the example code into the function body, run ` swift build`
295+
296+ func example_accepting_user_input_5(ably: ARTRealtime) {
297+ // --- example code starts here ---
298+ */}
299+ ` ` ` swift
300+ let channel = ably .channels .get (" {{RANDOM_CHANNEL_NAME}}" )
301+
302+ let promptID = UUID ().uuidString
303+ channel .publish (" user-input" , data: [
304+ " promptId" : promptID,
305+ " text" : " What is the weather like today?"
306+ ])
307+ ` ` `
308+ {/* --- end example code --- */}
207309</Code>
208310
209311<Aside data-type="note">
@@ -270,6 +372,37 @@ channel.subscribe("user-input", message -> {
270372 processAndRespond (channel, text, promptId);
271373});
272374` ` `
375+
376+ {/* Swift example test harness
377+ ID: accepting-user-input-6
378+ To verify: copy this comment into a Swift file, paste the example code into the function body, run ` swift build`
379+
380+ func example_accepting_user_input_6(
381+ processAndRespond: @escaping (_ channel: ARTRealtimeChannel, _ text: String, _ promptID: String) -> Void
382+ ) {
383+ // --- example code starts here ---
384+ */}
385+ ` ` ` swift
386+ import Ably
387+
388+ let ably = ARTRealtime (key: " {{API_KEY}}" )
389+ let channel = ably .channels .get (" {{RANDOM_CHANNEL_NAME}}" )
390+
391+ channel .subscribe (" user-input" ) { message in
392+ guard let data = message .data as? [String : Any],
393+ let promptID = data[" promptId" ] as? String ,
394+ let text = data[" text" ] as? String else {
395+ return
396+ }
397+ guard let userID = message .clientId else { return }
398+
399+ print (" Received prompt from \( userID): \( text)" )
400+
401+ // Process the prompt and generate a response
402+ processAndRespond (channel, text, promptID)
403+ }
404+ ` ` `
405+ {/* --- end example code --- */}
273406</Code>
274407
275408<Aside data-type="note">
@@ -334,6 +467,42 @@ void processAndRespond(Channel channel, String prompt, String promptId) {
334467 channel .publish (message);
335468}
336469` ` `
470+
471+ {/* Swift example test harness
472+ ID: accepting-user-input-7
473+ To verify: copy this comment into a Swift file, paste the example code into the function body, run ` swift build`
474+
475+ func example_accepting_user_input_7(
476+ channel: ARTRealtimeChannel,
477+ generateAIResponse: @Sendable @escaping (_ prompt: String) async throws -> String
478+ ) {
479+ // --- example code starts here ---
480+ */}
481+ ` ` ` swift
482+ func processAndRespond (channel: ARTRealtimeChannel, prompt: String , promptID: String ) async throws {
483+ // Generate the response (e.g., call your AI model)
484+ let response = try await generateAIResponse (prompt)
485+
486+ // Publish the response with the promptId for correlation
487+ let message = ARTMessage (name: " agent-response" , data: response)
488+ message .extras = [
489+ " headers" : [
490+ " promptId" : promptID
491+ ]
492+ ] as ARTJsonCompatible
493+
494+ try await withCheckedThrowingContinuation { (continuation: CheckedContinuation< Void, Error > ) in
495+ channel .publish ([message]) { _, error in
496+ if let error {
497+ continuation .resume (throwing: error)
498+ } else {
499+ continuation .resume ()
500+ }
501+ }
502+ }
503+ }
504+ ` ` `
505+ {/* --- end example code --- */}
337506</Code>
338507
339508The user's client can then match responses to their original prompts:
@@ -415,6 +584,51 @@ channel.subscribe("agent-response", message -> {
415584 }
416585});
417586` ` `
587+
588+ {/* Swift example test harness
589+ ID: accepting-user-input-8
590+ To verify: copy this comment into a Swift file, paste the example code into the function body, run ` swift build`
591+
592+ @MainActor
593+ func example_accepting_user_input_8(channel: ARTRealtimeChannel) {
594+ // --- example code starts here ---
595+ */}
596+ ` ` ` swift
597+ var pendingPrompts: [String : String ] = [: ]
598+
599+ // Send a prompt and track it
600+ func sendPrompt (text: String ) async throws - > String {
601+ let promptID = UUID ().uuidString
602+ pendingPrompts[promptID] = text
603+ try await withCheckedThrowingContinuation { (continuation: CheckedContinuation< Void, Error > ) in
604+ channel .publish (" user-input" , data: [" promptId" : promptID, " text" : text]) { error in
605+ if let error {
606+ continuation .resume (throwing: error)
607+ } else {
608+ continuation .resume ()
609+ }
610+ }
611+ }
612+ return promptID
613+ }
614+
615+ // Handle responses
616+ channel .subscribe (" agent-response" ) { message in
617+ MainActor .assumeIsolated {
618+ guard let extras = (try ? message .extras ? .toJSON ()) as? [String : Any],
619+ let headers = extras[" headers" ] as? [String : Any],
620+ let promptID = headers[" promptId" ] as? String else {
621+ return
622+ }
623+
624+ if let originalPrompt = pendingPrompts[promptID] {
625+ print (" Response for \"\( originalPrompt)\" : \( message.data ?? " " )" )
626+ pendingPrompts .removeValue (forKey: promptID)
627+ }
628+ }
629+ }
630+ ` ` `
631+ {/* --- end example code --- */}
418632</Code>
419633
420634<Aside data-type="note">
@@ -515,6 +729,67 @@ void streamResponse(Channel channel, String prompt, String promptId) throws Exce
515729 }
516730}
517731` ` `
732+
733+ {/* Swift example test harness
734+ ID: accepting-user-input-9
735+ To verify: copy this comment into a Swift file, paste the example code into the function body, run ` swift build`
736+
737+ func example_accepting_user_input_9(
738+ channel: ARTRealtimeChannel,
739+ generateTokens: @Sendable @escaping (_ prompt: String) -> any AsyncSequence<String, Never> & Sendable
740+ ) {
741+ // --- example code starts here ---
742+ */}
743+ ` ` ` swift
744+ func streamResponse (channel: ARTRealtimeChannel, prompt: String , promptID: String ) async throws {
745+ // Create initial message for message-per-response pattern
746+ let initialMessage = ARTMessage (name: " agent-response" , data: " " )
747+ initialMessage .extras = [
748+ " headers" : [
749+ " promptId" : promptID
750+ ]
751+ ] as ARTJsonCompatible
752+
753+ let msgSerial: String = try await withCheckedThrowingContinuation { continuation in
754+ channel .publish ([initialMessage]) { publishResult, error in
755+ if let error {
756+ continuation .resume (throwing: error)
757+ return
758+ }
759+
760+ guard let serial = publishResult? .serials .first ? .value else {
761+ continuation .resume (throwing: NSError (domain: " ExampleDomain" , code: 0 , userInfo: [NSLocalizedDescriptionKey: " No serial returned" ]))
762+ return
763+ }
764+
765+ continuation .resume (returning: serial)
766+ }
767+ }
768+
769+ // Stream tokens by appending to the message
770+ for await token in generateTokens (prompt ) {
771+ let messageToAppend = ARTMessage ()
772+ messageToAppend .serial = msgSerial
773+ messageToAppend .data = token
774+ messageToAppend .extras = [
775+ " headers" : [
776+ " promptId" : promptID
777+ ]
778+ ] as ARTJsonCompatible
779+
780+ try await withCheckedThrowingContinuation { (continuation: CheckedContinuation< Void, Error > ) in
781+ channel .append (messageToAppend, operation: nil, params: nil) { _, error in
782+ if let error {
783+ continuation .resume (throwing: error)
784+ } else {
785+ continuation .resume ()
786+ }
787+ }
788+ }
789+ }
790+ }
791+ ` ` `
792+ {/* --- end example code --- */}
518793</Code>
519794
520795<Aside data-type="note">
@@ -592,4 +867,42 @@ channel.subscribe("user-input", message -> {
592867 }
593868});
594869` ` `
870+
871+ {/* Swift example test harness
872+ ID: accepting-user-input-10
873+ To verify: copy this comment into a Swift file, paste the example code into the function body, run ` swift build`
874+
875+ @MainActor
876+ func example_accepting_user_input_10(
877+ channel: ARTRealtimeChannel,
878+ streamResponse: @escaping @MainActor (_ channel: ARTRealtimeChannel, _ text: String, _ promptID: String) async -> Void
879+ ) {
880+ // --- example code starts here ---
881+ */}
882+ ` ` ` swift
883+ // Agent handling multiple concurrent prompts
884+ var activeRequests: [String : (userID: String , text: String )] = [: ]
885+
886+ channel .subscribe (" user-input" ) { message in
887+ MainActor .assumeIsolated {
888+ guard let data = message .data as? [String : Any],
889+ let promptID = data[" promptId" ] as? String ,
890+ let text = data[" text" ] as? String else {
891+ return
892+ }
893+ guard let userID = message .clientId else { return }
894+
895+ // Track active request
896+ activeRequests[promptID] = (userID: userID, text: text)
897+
898+ Task { @MainActor in
899+ defer {
900+ activeRequests .removeValue (forKey: promptID)
901+ }
902+ await streamResponse (channel, text, promptID)
903+ }
904+ }
905+ }
906+ ` ` `
907+ {/* --- end example code --- */}
595908</Code>
0 commit comments