@@ -8,12 +8,21 @@ import (
88 "log"
99 "net/http"
1010 "os"
11- "strings"
11+ "strings"
1212 "time"
1313
1414 "github.com/google/uuid"
1515)
1616
17+ // Internal configuration for therapy session calls
18+ type therapyConfig struct {
19+ baseURL string
20+ token string
21+ userID string
22+ sessionID string
23+ locale string
24+ }
25+
1726// Start therapy session
1827func startTherapySession (session * Session ) {
1928 setOutputText ("start_therapy_session" , session )
@@ -25,75 +34,113 @@ func startTherapySession(session *Session) {
2534// HTTP client to therapy session endpoint
2635func callTherapySessionEndpoint (text string , session * Session ) * string {
2736 //coverage:ignore
28- // Resolve base URL and token
37+ cfg := buildTherapyConfig (session )
38+ if cfg == nil {
39+ return nil
40+ }
41+
42+ client := buildTherapyHTTPClient ()
43+
44+ if ! initTherapySession (client , cfg ) {
45+ return nil
46+ }
47+
48+ return sendTherapyMessage (client , cfg , text )
49+ }
50+
51+ // Relay a user message to the therapy session backend and append the reply
52+ func relayTherapyMessage (text string , session * Session ) {
53+ //coverage:ignore
54+ // Send immediate typing acknowledgement is already enabled via IsTyping
55+ reply := callTherapySessionEndpoint (text , session )
56+ if reply != nil && * reply != "" {
57+ setOutputRawText (* reply , session )
58+ }
59+ }
60+
61+ // Build an HTTP client configured for therapy session calls
62+ func buildTherapyHTTPClient () * http.Client {
63+ return & http.Client {Timeout : 120 * time .Second }
64+ }
65+
66+ // Build configuration from environment and session; ensures a session ID exists
67+ func buildTherapyConfig (session * Session ) * therapyConfig {
2968 baseURL := os .Getenv ("CAPY_THERAPY_SESSION_URL" )
3069 if baseURL == "" {
3170 log .Printf ("[TherapySession] missing CAPY_THERAPY_SESSION_URL" )
3271 return nil
3372 }
73+
3474 token := os .Getenv ("CAPY_AGENT_TOKEN" )
3575 if token == "" {
3676 log .Printf ("[TherapySession] missing CAPY_AGENT_TOKEN" )
3777 return nil
3878 }
3979
40- // Ensure therapy session identifier on the user entity
4180 if session .User .TherapySessionId == nil || * session .User .TherapySessionId == "" {
4281 newID := uuid .NewString ()
4382 session .User .TherapySessionId = & newID
4483 }
4584
4685 userID := session .User .ID
47- therapySessionID := * session .User .TherapySessionId
86+ sessionID := * session .User .TherapySessionId
4887 locale := session .Locale ().String ()
4988
50- client := & http.Client {Timeout : 120 * time .Second }
89+ return & therapyConfig {
90+ baseURL : baseURL ,
91+ token : token ,
92+ userID : userID ,
93+ sessionID : sessionID ,
94+ locale : locale ,
95+ }
96+ }
5197
52- // 1) Create/init the therapy session
53- initURL := fmt .Sprintf ("%s/apps/capymind_agent/users/%s/sessions/%s" , baseURL , userID , therapySessionID )
98+ // Initialize or validate the therapy session on the backend
99+ func initTherapySession (client * http.Client , cfg * therapyConfig ) bool {
100+ initURL := fmt .Sprintf ("%s/apps/capymind_agent/users/%s/sessions/%s" , cfg .baseURL , cfg .userID , cfg .sessionID )
54101 initBody := map [string ]any {
55102 "state" : map [string ]any {
56- "preferred_language" : locale ,
103+ "preferred_language" : cfg . locale ,
57104 },
58105 }
59106 initBodyBytes , _ := json .Marshal (initBody )
60- initReq , err := http .NewRequest ("POST" , initURL , bytes .NewBuffer (initBodyBytes ))
107+
108+ req , err := http .NewRequest ("POST" , initURL , bytes .NewBuffer (initBodyBytes ))
61109 if err != nil {
62110 log .Printf ("[TherapySession] init request build error: %v" , err )
63- return nil
111+ return false
64112 }
65- initReq .Header .Set ("Authorization" , "Bearer " + token )
66- initReq .Header .Set ("Content-Type" , "application/json" )
67-
68- initResp , err := client .Do (initReq )
69- if err != nil {
70- log .Printf ("[TherapySession] init request error: %v" , err )
71- return nil
72- }
73- defer initResp .Body .Close ()
74- proceed := false
75- if initResp .StatusCode >= 200 && initResp .StatusCode < 300 {
76- proceed = true
77- } else {
78- body , _ := io .ReadAll (initResp .Body )
79- // Allow existing session scenario to proceed
80- if initResp .StatusCode == 400 && strings .Contains (string (body ), "Session already exists" ) {
81- log .Printf ("[TherapySession] init session exists, proceeding: %s" , therapySessionID )
82- proceed = true
83- } else {
84- log .Printf ("[TherapySession] init non-2xx: %d body=%s" , initResp .StatusCode , string (body ))
85- }
86- }
87- if ! proceed {
88- return nil
89- }
90-
91- // 2) Send user message via run_sse
92- runURL := fmt .Sprintf ("%s/run_sse" , baseURL )
113+ req .Header .Set ("Authorization" , "Bearer " + cfg .token )
114+ req .Header .Set ("Content-Type" , "application/json" )
115+
116+ resp , err := client .Do (req )
117+ if err != nil {
118+ log .Printf ("[TherapySession] init request error: %v" , err )
119+ return false
120+ }
121+ defer resp .Body .Close ()
122+
123+ if resp .StatusCode >= 200 && resp .StatusCode < 300 {
124+ return true
125+ }
126+
127+ body , _ := io .ReadAll (resp .Body )
128+ if resp .StatusCode == 400 && strings .Contains (string (body ), "Session already exists" ) {
129+ log .Printf ("[TherapySession] init session exists, proceeding: %s" , cfg .sessionID )
130+ return true
131+ }
132+
133+ log .Printf ("[TherapySession] init non-2xx: %d body=%s" , resp .StatusCode , string (body ))
134+ return false
135+ }
136+
137+ // Send the user's message to the run_sse endpoint and parse the reply
138+ func sendTherapyMessage (client * http.Client , cfg * therapyConfig , text string ) * string {
139+ runURL := fmt .Sprintf ("%s/run_sse" , cfg .baseURL )
93140 runBody := map [string ]any {
94141 "app_name" : "capymind_agent" ,
95- "user_id" : userID ,
96- "session_id" : therapySessionID ,
142+ "user_id" : cfg . userID ,
143+ "session_id" : cfg . sessionID ,
97144 "new_message" : map [string ]any {
98145 "role" : "user" ,
99146 "parts" : []map [string ]string {
@@ -103,83 +150,78 @@ func callTherapySessionEndpoint(text string, session *Session) *string {
103150 "streaming" : false ,
104151 }
105152 runBodyBytes , _ := json .Marshal (runBody )
106- runReq , err := http .NewRequest ("POST" , runURL , bytes .NewBuffer (runBodyBytes ))
153+
154+ req , err := http .NewRequest ("POST" , runURL , bytes .NewBuffer (runBodyBytes ))
107155 if err != nil {
108156 log .Printf ("[TherapySession] run request build error: %v" , err )
109157 return nil
110158 }
111- runReq .Header .Set ("Authorization" , "Bearer " + token )
112- runReq .Header .Set ("Content-Type" , "application/json" )
159+ req .Header .Set ("Authorization" , "Bearer " + cfg . token )
160+ req .Header .Set ("Content-Type" , "application/json" )
113161
114- runResp , err := client .Do (runReq )
162+ resp , err := client .Do (req )
115163 if err != nil {
116164 log .Printf ("[TherapySession] run request error: %v" , err )
117165 return nil
118166 }
119- defer runResp .Body .Close ()
120- runRespBody , err := io .ReadAll (runResp .Body )
167+ defer resp .Body .Close ()
168+
169+ respBody , err := io .ReadAll (resp .Body )
121170 if err != nil {
122171 log .Printf ("[TherapySession] run read error: %v" , err )
123172 return nil
124173 }
125- if runResp .StatusCode < 200 || runResp .StatusCode >= 300 {
126- log .Printf ("[TherapySession] run non-2xx: %d body=%s" , runResp .StatusCode , string (runRespBody ))
174+ if resp .StatusCode < 200 || resp .StatusCode >= 300 {
175+ log .Printf ("[TherapySession] run non-2xx: %d body=%s" , resp .StatusCode , string (respBody ))
176+ return nil
177+ }
178+
179+ respStr := string (respBody )
180+ if respStr == "" {
127181 return nil
128182 }
129- respStr := string (runRespBody )
130- if respStr == "" {
131- return nil
132- }
133-
134- // Try to extract plain text from JSON response
135- // Support responses that are either raw JSON or lines prefixed with "data: "
136- extractJSON := func (s string ) string {
137- s = strings .TrimSpace (s )
138- if strings .HasPrefix (s , "data:" ) {
139- // If multiple lines, pick the last data line
140- lines := strings .Split (s , "\n " )
141- for i := len (lines ) - 1 ; i >= 0 ; i -- {
142- line := strings .TrimSpace (lines [i ])
143- if strings .HasPrefix (line , "data:" ) {
144- return strings .TrimSpace (strings .TrimPrefix (line , "data:" ))
145- }
146- }
147- return strings .TrimSpace (strings .TrimPrefix (lines [len (lines )- 1 ], "data:" ))
148- }
149- return s
150- }
151-
152- type runSseContentPart struct {
153- Text string `json:"text"`
154- }
155- type runSseContent struct {
156- Parts []runSseContentPart `json:"parts"`
157- }
158- type runSseResponse struct {
159- Content runSseContent `json:"content"`
160- }
161-
162- jsonCandidate := extractJSON (respStr )
163- var parsed runSseResponse
164- if err := json .Unmarshal ([]byte (jsonCandidate ), & parsed ); err == nil {
165- if len (parsed .Content .Parts ) > 0 && parsed .Content .Parts [0 ].Text != "" {
166- onlyText := parsed .Content .Parts [0 ].Text
167- return & onlyText
168- }
169- }
170-
171- // Fallback: return body as-is
172- return & respStr
183+ return parseRunResponse (respStr )
173184}
174185
175- // Relay a user message to the therapy session backend and append the reply
176- func relayTherapyMessage (text string , session * Session ) {
177- //coverage:ignore
178- // Send immediate typing acknowledgement is already enabled via IsTyping
179- reply := callTherapySessionEndpoint (text , session )
180- if reply != nil && * reply != "" {
181- setOutputRawText (* reply , session )
186+ // Parse a run_sse HTTP response body, extracting plain text if present
187+ func parseRunResponse (respStr string ) * string {
188+ extractJSON := func (s string ) string {
189+ s = strings .TrimSpace (s )
190+ if strings .HasPrefix (s , "data:" ) {
191+ // If multiple lines, pick the last data line
192+ lines := strings .Split (s , "\n " )
193+ for i := len (lines ) - 1 ; i >= 0 ; i -- {
194+ line := strings .TrimSpace (lines [i ])
195+ if strings .HasPrefix (line , "data:" ) {
196+ return strings .TrimSpace (strings .TrimPrefix (line , "data:" ))
197+ }
198+ }
199+ return strings .TrimSpace (strings .TrimPrefix (lines [len (lines )- 1 ], "data:" ))
200+ }
201+ return s
182202 }
203+
204+ type runSseContentPart struct {
205+ Text string `json:"text"`
206+ }
207+ type runSseContent struct {
208+ Parts []runSseContentPart `json:"parts"`
209+ }
210+ type runSseResponse struct {
211+ Content runSseContent `json:"content"`
212+ }
213+
214+ jsonCandidate := extractJSON (respStr )
215+ var parsed runSseResponse
216+ if err := json .Unmarshal ([]byte (jsonCandidate ), & parsed ); err == nil {
217+ if len (parsed .Content .Parts ) > 0 && parsed .Content .Parts [0 ].Text != "" {
218+ onlyText := parsed .Content .Parts [0 ].Text
219+ return & onlyText
220+ }
221+ }
222+
223+ // Fallback: return body as-is
224+ return & respStr
183225}
184226
185227// End the therapy session and notify the user
0 commit comments