on main help sign in
fix(agent): cancelation logic
5e698b2Kujtim Hoxha
go.mod+18−24
@@ -15,6 +15,7 @@ require (
15 github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250910155747-997384b0b35e
15 github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20250910155747-997384b0b35e
16 github.com/charmbracelet/catwalk v0.6.4-0.20251002104711-f8c6c1e5b4a5
16 github.com/charmbracelet/catwalk v0.6.4-0.20251002104711-f8c6c1e5b4a5
17 github.com/charmbracelet/fang v0.4.2
17 github.com/charmbracelet/fang v0.4.2
18+ github.com/charmbracelet/fantasy v0.0.0-20251003110041-a581de4d1d81
18 github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018
19 github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018
19 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250917201909-41ff0bf215ea
20 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250917201909-41ff0bf215ea
20 github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706
21 github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706
@@ -50,30 +51,8 @@ require (
50 cloud.google.com/go v0.116.0 // indirect
51 cloud.google.com/go v0.116.0 // indirect
51 cloud.google.com/go/auth v0.9.3 // indirect
52 cloud.google.com/go/auth v0.9.3 // indirect
52 cloud.google.com/go/compute/metadata v0.5.0 // indirect
53 cloud.google.com/go/compute/metadata v0.5.0 // indirect
53− github.com/charmbracelet/x/json v0.2.0 // indirect
54− github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
55− github.com/google/go-cmp v0.7.0 // indirect
56− github.com/google/s2a-go v0.1.8 // indirect
57− github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
58− github.com/gorilla/websocket v1.5.3 // indirect
59− github.com/openai/openai-go/v2 v2.3.0 // indirect
60− go.opencensus.io v0.24.0 // indirect
61− golang.org/x/crypto v0.41.0 // indirect
62− google.golang.org/genai v1.26.0 // indirect
63− google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
64− google.golang.org/grpc v1.66.2 // indirect
65− google.golang.org/protobuf v1.36.8 // indirect
66− gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
67−)
68−
69−require (
70− github.com/anthropics/anthropic-sdk-go v1.12.0 // indirect
71− github.com/charmbracelet/fantasy v0.0.0-20251003071236-5d39f0348e5d
72− github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
73−)
74−
75−require (
76 github.com/andybalholm/cascadia v1.3.3 // indirect
54 github.com/andybalholm/cascadia v1.3.3 // indirect
55+ github.com/anthropics/anthropic-sdk-go v1.12.0 // indirect
77 github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
56 github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
78 github.com/aymerick/douceur v0.2.0 // indirect
57 github.com/aymerick/douceur v0.2.0 // indirect
79 github.com/bahlo/generic-list-go v0.2.0 // indirect
58 github.com/bahlo/generic-list-go v0.2.0 // indirect
@@ -82,6 +61,7 @@ require (
82 github.com/charmbracelet/ultraviolet v0.0.0-20250915111650-81d4262876ef
61 github.com/charmbracelet/ultraviolet v0.0.0-20250915111650-81d4262876ef
83 github.com/charmbracelet/x/cellbuf v0.0.14-0.20250811133356-e0c5dbe5ea4a // indirect
62 github.com/charmbracelet/x/cellbuf v0.0.14-0.20250811133356-e0c5dbe5ea4a // indirect
84 github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5
63 github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5
64+ github.com/charmbracelet/x/json v0.2.0 // indirect
85 github.com/charmbracelet/x/powernap v0.0.0-20250919153222-1038f7e6fef4
65 github.com/charmbracelet/x/powernap v0.0.0-20250919153222-1038f7e6fef4
86 github.com/charmbracelet/x/term v0.2.1
66 github.com/charmbracelet/x/term v0.2.1
87 github.com/charmbracelet/x/termios v0.1.1 // indirect
67 github.com/charmbracelet/x/termios v0.1.1 // indirect
@@ -93,7 +73,13 @@ require (
93 github.com/dustin/go-humanize v1.0.1 // indirect
73 github.com/dustin/go-humanize v1.0.1 // indirect
94 github.com/fsnotify/fsnotify v1.9.0 // indirect
74 github.com/fsnotify/fsnotify v1.9.0 // indirect
95 github.com/go-logfmt/logfmt v0.6.0 // indirect
75 github.com/go-logfmt/logfmt v0.6.0 // indirect
76+ github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
77+ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
78+ github.com/google/go-cmp v0.7.0 // indirect
79+ github.com/google/s2a-go v0.1.8 // indirect
80+ github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
96 github.com/gorilla/css v1.0.1 // indirect
81 github.com/gorilla/css v1.0.1 // indirect
82+ github.com/gorilla/websocket v1.5.3 // indirect
97 github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
83 github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
98 github.com/inconshreveable/mousetrap v1.1.0 // indirect
84 github.com/inconshreveable/mousetrap v1.1.0 // indirect
99 github.com/klauspost/compress v1.18.0 // indirect
85 github.com/klauspost/compress v1.18.0 // indirect
@@ -112,6 +98,7 @@ require (
112 github.com/muesli/mango-pflag v0.1.0 // indirect
98 github.com/muesli/mango-pflag v0.1.0 // indirect
113 github.com/muesli/roff v0.1.0 // indirect
99 github.com/muesli/roff v0.1.0 // indirect
114 github.com/ncruces/julianday v1.0.0 // indirect
100 github.com/ncruces/julianday v1.0.0 // indirect
101+ github.com/openai/openai-go/v2 v2.3.0 // indirect
115 github.com/pierrec/lz4/v4 v4.1.22 // indirect
102 github.com/pierrec/lz4/v4 v4.1.22 // indirect
116 github.com/pmezard/go-difflib v1.0.0 // indirect
103 github.com/pmezard/go-difflib v1.0.0 // indirect
117 github.com/posthog/posthog-go v1.6.10
104 github.com/posthog/posthog-go v1.6.10
@@ -132,14 +119,21 @@ require (
132 github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
119 github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
133 github.com/yuin/goldmark v1.7.8 // indirect
120 github.com/yuin/goldmark v1.7.8 // indirect
134 github.com/yuin/goldmark-emoji v1.0.5 // indirect
121 github.com/yuin/goldmark-emoji v1.0.5 // indirect
122+ go.opencensus.io v0.24.0 // indirect
135 go.uber.org/multierr v1.11.0 // indirect
123 go.uber.org/multierr v1.11.0 // indirect
124+ golang.org/x/crypto v0.41.0 // indirect
136 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
125 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
137− golang.org/x/image v0.26.0 // indirect
126+ golang.org/x/image v0.27.0 // indirect
138 golang.org/x/net v0.43.0 // indirect
127 golang.org/x/net v0.43.0 // indirect
139 golang.org/x/sync v0.17.0 // indirect
128 golang.org/x/sync v0.17.0 // indirect
140 golang.org/x/sys v0.36.0 // indirect
129 golang.org/x/sys v0.36.0 // indirect
141 golang.org/x/term v0.34.0 // indirect
130 golang.org/x/term v0.34.0 // indirect
142 golang.org/x/text v0.29.0
131 golang.org/x/text v0.29.0
132+ google.golang.org/genai v1.26.0 // indirect
133+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
134+ google.golang.org/grpc v1.66.2 // indirect
135+ google.golang.org/protobuf v1.36.8 // indirect
136+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
143 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
137 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
144 gopkg.in/yaml.v3 v3.0.1 // indirect
138 gopkg.in/yaml.v3 v3.0.1 // indirect
145 mvdan.cc/sh/moreinterp v0.0.0-20250902163504-3cf4fd5717a5
139 mvdan.cc/sh/moreinterp v0.0.0-20250902163504-3cf4fd5717a5
go.sum+6−8
@@ -51,14 +51,12 @@ github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqI
51github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
51github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
52github.com/charmbracelet/fang v0.4.2 h1:nWr7Tb82/TTNNGMGG35aTZ1X68loAOQmpb0qxkKXjas=
52github.com/charmbracelet/fang v0.4.2 h1:nWr7Tb82/TTNNGMGG35aTZ1X68loAOQmpb0qxkKXjas=
53github.com/charmbracelet/fang v0.4.2/go.mod h1:wHJKQYO5ReYsxx+yZl+skDtrlKO/4LLEQ6EXsdHhRhg=
53github.com/charmbracelet/fang v0.4.2/go.mod h1:wHJKQYO5ReYsxx+yZl+skDtrlKO/4LLEQ6EXsdHhRhg=
54−github.com/charmbracelet/fantasy v0.0.0-20251002051643-c96822199d77 h1:YHuUqaojkeu00YtQeXPqM/1RNJH/jqGNaQYFwa7JQTk=
55−github.com/charmbracelet/fantasy v0.0.0-20251002051643-c96822199d77/go.mod h1:RZotHpq44tKZDe6Vf0kk1iDqnUgH7Scx+K/7uJ9Qwnw=
56−github.com/charmbracelet/fantasy v0.0.0-20251003054629-33bf06cef92c h1:+gCA3Sv7g1jnZ96Em7j9u61H2O/1SkBAZ1LM9yd2bM8=
57−github.com/charmbracelet/fantasy v0.0.0-20251003054629-33bf06cef92c/go.mod h1:RZotHpq44tKZDe6Vf0kk1iDqnUgH7Scx+K/7uJ9Qwnw=
58−github.com/charmbracelet/fantasy v0.0.0-20251003055851-3196e9fa7380 h1:UYWO3cutUTV9ZLOH4hrXFy9hg4E1pVbLZsZLA1OA3+g=
59−github.com/charmbracelet/fantasy v0.0.0-20251003055851-3196e9fa7380/go.mod h1:RZotHpq44tKZDe6Vf0kk1iDqnUgH7Scx+K/7uJ9Qwnw=
60github.com/charmbracelet/fantasy v0.0.0-20251003071236-5d39f0348e5d h1:HS9qu7CgQGPnPcNoHIZXGWGFS8SaxaBXYytLdakm+jY=
54github.com/charmbracelet/fantasy v0.0.0-20251003071236-5d39f0348e5d h1:HS9qu7CgQGPnPcNoHIZXGWGFS8SaxaBXYytLdakm+jY=
61github.com/charmbracelet/fantasy v0.0.0-20251003071236-5d39f0348e5d/go.mod h1:RZotHpq44tKZDe6Vf0kk1iDqnUgH7Scx+K/7uJ9Qwnw=
55github.com/charmbracelet/fantasy v0.0.0-20251003071236-5d39f0348e5d/go.mod h1:RZotHpq44tKZDe6Vf0kk1iDqnUgH7Scx+K/7uJ9Qwnw=
56+github.com/charmbracelet/fantasy v0.0.0-20251003104828-c9bb5e0cf237 h1:OgqeFnDwgYOVwLoOZpvmBFEh+j7G4f5gOzkozHUMbqo=
57+github.com/charmbracelet/fantasy v0.0.0-20251003104828-c9bb5e0cf237/go.mod h1:RZotHpq44tKZDe6Vf0kk1iDqnUgH7Scx+K/7uJ9Qwnw=
58+github.com/charmbracelet/fantasy v0.0.0-20251003110041-a581de4d1d81 h1:TFQ9P46252Db/B/eDLHtL5Le9Fm0d6LtYDr59mquN7w=
59+github.com/charmbracelet/fantasy v0.0.0-20251003110041-a581de4d1d81/go.mod h1:RZotHpq44tKZDe6Vf0kk1iDqnUgH7Scx+K/7uJ9Qwnw=
62github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018 h1:PU4Zvpagsk5sgaDxn5W4sxHuLp9QRMBZB3bFSk40A4w=
60github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018 h1:PU4Zvpagsk5sgaDxn5W4sxHuLp9QRMBZB3bFSk40A4w=
63github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018/go.mod h1:Z/GLmp9fzaqX4ze3nXG7StgWez5uBM5XtlLHK8V/qSk=
61github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018/go.mod h1:Z/GLmp9fzaqX4ze3nXG7StgWez5uBM5XtlLHK8V/qSk=
64github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250917201909-41ff0bf215ea h1:g1HfUgSMvye8mgecMD1mPscpt+pzJoDEiSA+p2QXzdQ=
62github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250917201909-41ff0bf215ea h1:g1HfUgSMvye8mgecMD1mPscpt+pzJoDEiSA+p2QXzdQ=
@@ -328,8 +326,8 @@ golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sU
328golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
326golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
329golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
327golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
330golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
328golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
331−golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
329+golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
332−golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
330+golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
333golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
331golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
334golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
332golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
335golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
333golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
internal/agent/agent.go+48−14
@@ -3,6 +3,7 @@ package agent
3import (
3import (
4 "context"
4 "context"
5 _ "embed"
5 _ "embed"
6+ "encoding/json"
6 "errors"
7 "errors"
7 "fmt"
8 "fmt"
8 "log/slog"
9 "log/slog"
@@ -177,7 +178,7 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*ai.Agen
177 // Before each step create the new assistant message
178 // Before each step create the new assistant message
178 PrepareStep: func(callContext context.Context, options ai.PrepareStepFunctionOptions) (_ context.Context, prepared ai.PrepareStepResult, err error) {
179 PrepareStep: func(callContext context.Context, options ai.PrepareStepFunctionOptions) (_ context.Context, prepared ai.PrepareStepResult, err error) {
179 var assistantMsg message.Message
180 var assistantMsg message.Message
180− assistantMsg, err = a.messages.Create(genCtx, call.SessionID, message.CreateMessageParams{
181+ assistantMsg, err = a.messages.Create(callContext, call.SessionID, message.CreateMessageParams{
181 Role: message.Assistant,
182 Role: message.Assistant,
182 Parts: []message.ContentPart{},
183 Parts: []message.ContentPart{},
183 Model: a.largeModel.ModelCfg.Model,
184 Model: a.largeModel.ModelCfg.Model,
@@ -187,7 +188,7 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*ai.Agen
187 return callContext, prepared, err
188 return callContext, prepared, err
188 }
189 }
189
190
190− callContext = context.WithValue(ctx, tools.MessageIDContextKey, assistantMsg.ID)
191+ callContext = context.WithValue(callContext, tools.MessageIDContextKey, assistantMsg.ID)
191
192
192 currentAssistant = &assistantMsg
193 currentAssistant = &assistantMsg
193
194
@@ -200,7 +201,7 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*ai.Agen
200 queuedCalls, _ := a.messageQueue.Get(call.SessionID)
201 queuedCalls, _ := a.messageQueue.Get(call.SessionID)
201 a.messageQueue.Del(call.SessionID)
202 a.messageQueue.Del(call.SessionID)
202 for _, queued := range queuedCalls {
203 for _, queued := range queuedCalls {
203− userMessage, createErr := a.createUserMessage(genCtx, queued)
204+ userMessage, createErr := a.createUserMessage(callContext, queued)
204 if createErr != nil {
205 if createErr != nil {
205 return callContext, prepared, createErr
206 return callContext, prepared, createErr
206 }
207 }
@@ -291,12 +292,15 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*ai.Agen
291 IsError: isError,
292 IsError: isError,
292 Metadata: result.ClientMetadata,
293 Metadata: result.ClientMetadata,
293 }
294 }
294− a.messages.Create(context.Background(), currentAssistant.SessionID, message.CreateMessageParams{
295+ _, err := a.messages.Create(context.Background(), currentAssistant.SessionID, message.CreateMessageParams{
295 Role: message.Tool,
296 Role: message.Tool,
296 Parts: []message.ContentPart{
297 Parts: []message.ContentPart{
297 toolResult,
298 toolResult,
298 },
299 },
299 })
300 })
301+ if err != nil {
302+ return err
303+ }
300 return a.messages.Update(genCtx, *currentAssistant)
304 return a.messages.Update(genCtx, *currentAssistant)
301 },
305 },
302 OnStepFinish: func(stepResult ai.StepResult) error {
306 OnStepFinish: func(stepResult ai.StepResult) error {
@@ -328,16 +332,29 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*ai.Agen
328 }
332 }
329 toolCalls := currentAssistant.ToolCalls()
333 toolCalls := currentAssistant.ToolCalls()
330 toolResults := currentAssistant.ToolResults()
334 toolResults := currentAssistant.ToolResults()
335+ // INFO: we use the parent context here because the genCtx has been cancelled
336+ msgs, createErr := a.messages.List(ctx, currentSession.ID)
337+ if createErr != nil {
338+ return nil, createErr
339+ }
331 for _, tc := range toolCalls {
340 for _, tc := range toolCalls {
332 if !tc.Finished {
341 if !tc.Finished {
333 tc.Finished = true
342 tc.Finished = true
334 tc.Input = "{}"
343 tc.Input = "{}"
335 }
344 }
336 currentAssistant.AddToolCall(tc)
345 currentAssistant.AddToolCall(tc)
346+
337 found := false
347 found := false
348+ for _, msg := range msgs {
349+ if msg.Role == message.Tool {
338− for _, tr := range toolResults {
350+ for _, tr := range toolResults {
339− if tr.ToolCallID == tc.ID {
351+ if tr.ToolCallID == tc.ID {
340− found = true
352+ found = true
353+ break
354+ }
355+ }
356+ }
357+ if found {
341 break
358 break
342 }
359 }
343 }
360 }
@@ -348,12 +365,21 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*ai.Agen
348 } else if isPermissionErr {
365 } else if isPermissionErr {
349 content = "Permission denied"
366 content = "Permission denied"
350 }
367 }
351− currentAssistant.AddToolResult(message.ToolResult{
368+ toolResult := message.ToolResult{
352 ToolCallID: tc.ID,
369 ToolCallID: tc.ID,
353 Name: tc.Name,
370 Name: tc.Name,
354 Content: content,
371 Content: content,
355 IsError: true,
372 IsError: true,
373+ }
374+ _, createErr = a.messages.Create(context.Background(), currentAssistant.SessionID, message.CreateMessageParams{
375+ Role: message.Tool,
376+ Parts: []message.ContentPart{
377+ toolResult,
378+ },
356 })
379 })
380+ if createErr != nil {
381+ return nil, createErr
382+ }
357 }
383 }
358 }
384 }
359 if isCancelErr {
385 if isCancelErr {
@@ -363,13 +389,11 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*ai.Agen
363 } else {
389 } else {
364 currentAssistant.AddFinish(message.FinishReasonError, "API Error", err.Error())
390 currentAssistant.AddFinish(message.FinishReasonError, "API Error", err.Error())
365 }
391 }
366− // INFO: we use the parent context here because the genCtx might have been cancelled
392+ // INFO: we use the parent context here because the genCtx has been cancelled
367 updateErr := a.messages.Update(ctx, *currentAssistant)
393 updateErr := a.messages.Update(ctx, *currentAssistant)
368 if updateErr != nil {
394 if updateErr != nil {
369 return nil, updateErr
395 return nil, updateErr
370 }
396 }
371− }
372− if err != nil {
373 return nil, err
397 return nil, err
374 }
398 }
375 wg.Wait()
399 wg.Wait()
@@ -541,22 +565,32 @@ func (a *sessionAgent) generateTitle(ctx context.Context, session *session.Sessi
541 return
565 return
542 }
566 }
543
567
568+ var maxOutput int64 = 40
569+ if a.smallModel.CatwalkCfg.CanReason {
570+ maxOutput = a.smallModel.CatwalkCfg.DefaultMaxTokens
571+ }
572+
544 agent := ai.NewAgent(a.smallModel.Model,
573 agent := ai.NewAgent(a.smallModel.Model,
545− ai.WithSystemPrompt(string(titlePrompt)),
574+ ai.WithSystemPrompt(string(titlePrompt)+"\n /no_think"),
546− ai.WithMaxOutputTokens(40),
575+ ai.WithMaxOutputTokens(maxOutput),
547 )
576 )
548
577
549 resp, err := agent.Stream(ctx, ai.AgentStreamCall{
578 resp, err := agent.Stream(ctx, ai.AgentStreamCall{
550− Prompt: fmt.Sprintf("Generate a concise title for the following content:\n\n%s", prompt),
579+ Prompt: fmt.Sprintf("Generate a concise title for the following content:\n\n%s\n <think>\n\n</think>", prompt),
551 })
580 })
552 if err != nil {
581 if err != nil {
553 slog.Error("error generating title", "err", err)
582 slog.Error("error generating title", "err", err)
554 return
583 return
555 }
584 }
556
585
586+ data, _ := json.Marshal(resp)
587+
588+ slog.Info("Title Response")
589+ slog.Info(string(data))
557 title := resp.Response.Content.Text()
590 title := resp.Response.Content.Text()
558
591
559 title = strings.ReplaceAll(title, "\n", " ")
592 title = strings.ReplaceAll(title, "\n", " ")
593+ slog.Info(title)
560
594
561 // remove thinking tags if present
595 // remove thinking tags if present
562 if idx := strings.Index(title, "</think>"); idx > 0 {
596 if idx := strings.Index(title, "</think>"); idx > 0 {
internal/tui/page/chat/chat.go+6
@@ -2,6 +2,7 @@ package chat
2
2
3import (
3import (
4 "context"
4 "context"
5+ "errors"
5 "fmt"
6 "fmt"
6 "time"
7 "time"
7
8
@@ -751,6 +752,11 @@ func (p *chatPage) sendMessage(text string, attachments []message.Attachment) te
751 cmds = append(cmds, func() tea.Msg {
752 cmds = append(cmds, func() tea.Msg {
752 _, err := p.app.AgentCoordinator.Run(context.Background(), session.ID, text, attachments...)
753 _, err := p.app.AgentCoordinator.Run(context.Background(), session.ID, text, attachments...)
753 if err != nil {
754 if err != nil {
755+ isCancelErr := errors.Is(err, context.Canceled)
756+ isPermissionErr := errors.Is(err, permission.ErrorPermissionDenied)
757+ if isCancelErr || isPermissionErr {
758+ return nil
759+ }
754 return util.InfoMsg{
760 return util.InfoMsg{
755 Type: util.InfoTypeError,
761 Type: util.InfoTypeError,
756 Msg: err.Error(),
762 Msg: err.Error(),