on main help sign in
feat: add support for google reasoning
92944bbkujtimiihoxha
go.mod+1−1
@@ -47,7 +47,7 @@ require (
47 mvdan.cc/sh/v3 v3.12.1-0.20250902163504-3cf4fd5717a5
47 mvdan.cc/sh/v3 v3.12.1-0.20250902163504-3cf4fd5717a5
48)
48)
49
49
50−replace charm.land/fantasy => ../fantasy/
50+replace charm.land/fantasy => ../../fantasy/main/
51
51
52require (
52require (
53 cloud.google.com/go v0.116.0 // indirect
53 cloud.google.com/go v0.116.0 // indirect
internal/agent/agent.go+6
@@ -15,6 +15,7 @@ import (
15 "charm.land/fantasy"
15 "charm.land/fantasy"
16 "charm.land/fantasy/providers/anthropic"
16 "charm.land/fantasy/providers/anthropic"
17 "charm.land/fantasy/providers/bedrock"
17 "charm.land/fantasy/providers/bedrock"
18+ "charm.land/fantasy/providers/google"
18 "charm.land/fantasy/providers/openai"
19 "charm.land/fantasy/providers/openai"
19 "github.com/charmbracelet/catwalk/pkg/catwalk"
20 "github.com/charmbracelet/catwalk/pkg/catwalk"
20 "github.com/charmbracelet/crush/internal/agent/tools"
21 "github.com/charmbracelet/crush/internal/agent/tools"
@@ -243,6 +244,11 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*fantasy
243 currentAssistant.AppendReasoningSignature(reasoning.Signature)
244 currentAssistant.AppendReasoningSignature(reasoning.Signature)
244 }
245 }
245 }
246 }
247+ if googleData, ok := reasoning.ProviderMetadata[google.Name]; ok {
248+ if reasoning, ok := googleData.(*google.ReasoningMetadata); ok {
249+ currentAssistant.AppendReasoningSignature(reasoning.Signature)
250+ }
251+ }
246 if openaiData, ok := reasoning.ProviderMetadata[openai.Name]; ok {
252 if openaiData, ok := reasoning.ProviderMetadata[openai.Name]; ok {
247 if reasoning, ok := openaiData.(*openai.ResponsesReasoningMetadata); ok {
253 if reasoning, ok := openaiData.(*openai.ResponsesReasoningMetadata); ok {
248 currentAssistant.SetReasoningResponsesData(reasoning)
254 currentAssistant.SetReasoningResponsesData(reasoning)
internal/agent/coordinator.go+7
@@ -218,6 +218,13 @@ func getProviderOptions(model Model, tp catwalk.Type) fantasy.ProviderOptions {
218 options[openrouter.Name] = parsed
218 options[openrouter.Name] = parsed
219 }
219 }
220 case google.Name:
220 case google.Name:
221+ _, hasReasoning := mergedOptions["thinking_config"]
222+ if !hasReasoning {
223+ mergedOptions["thinking_config"] = map[string]any{
224+ "thinking_budget": 2000,
225+ "include_thoughts": true,
226+ }
227+ }
221 parsed, err := google.ParseOptions(mergedOptions)
228 parsed, err := google.ParseOptions(mergedOptions)
222 if err == nil {
229 if err == nil {
223 options[google.Name] = parsed
230 options[google.Name] = parsed
internal/agent/tools/ls.go+1−1
@@ -16,7 +16,7 @@ import (
16)
16)
17
17
18type LSParams struct {
18type LSParams struct {
19− Path string `json:"path" description:"The path to the directory to list (defaults to current working directory)"`
19+ Path string `json:"path,omitempty" description:"The path to the directory to list (defaults to current working directory)"`
20 Ignore []string `json:"ignore,omitempty" description:"List of glob patterns to ignore"`
20 Ignore []string `json:"ignore,omitempty" description:"List of glob patterns to ignore"`
21 Depth int `json:"depth,omitempty" description:"The maximum depth to traverse"`
21 Depth int `json:"depth,omitempty" description:"The maximum depth to traverse"`
22}
22}
internal/message/content.go+28−5
@@ -9,6 +9,7 @@ import (
9
9
10 "charm.land/fantasy"
10 "charm.land/fantasy"
11 "charm.land/fantasy/providers/anthropic"
11 "charm.land/fantasy/providers/anthropic"
12+ "charm.land/fantasy/providers/google"
12 "charm.land/fantasy/providers/openai"
13 "charm.land/fantasy/providers/openai"
13 "github.com/charmbracelet/catwalk/pkg/catwalk"
14 "github.com/charmbracelet/catwalk/pkg/catwalk"
14)
15)
@@ -41,11 +42,12 @@ type ContentPart interface {
41}
42}
42
43
43type ReasoningContent struct {
44type ReasoningContent struct {
44− Thinking string `json:"thinking"`
45+ Thinking string `json:"thinking"`
45− Signature string `json:"signature"`
46+ Signature string `json:"signature"`
47+ ThoughtSignature string `json:"thought_signature"` // Used for google
46− ResponsesData *openai.ResponsesReasoningMetadata `json:"responses_data"`
48+ ResponsesData *openai.ResponsesReasoningMetadata `json:"responses_data"`
47− StartedAt int64 `json:"started_at,omitempty"`
49+ StartedAt int64 `json:"started_at,omitempty"`
48− FinishedAt int64 `json:"finished_at,omitempty"`
50+ FinishedAt int64 `json:"finished_at,omitempty"`
49}
51}
50
52
51func (tc ReasoningContent) String() string {
53func (tc ReasoningContent) String() string {
@@ -259,6 +261,22 @@ func (m *Message) AppendReasoningContent(delta string) {
259 }
261 }
260}
262}
261
263
264+func (m *Message) AppendThoughtSignature(signature string) {
265+ for i, part := range m.Parts {
266+ if c, ok := part.(ReasoningContent); ok {
267+ m.Parts[i] = ReasoningContent{
268+ Thinking: c.Thinking,
269+ ThoughtSignature: c.ThoughtSignature + signature,
270+ Signature: c.Signature,
271+ StartedAt: c.StartedAt,
272+ FinishedAt: c.FinishedAt,
273+ }
274+ return
275+ }
276+ }
277+ m.Parts = append(m.Parts, ReasoningContent{ThoughtSignature: signature})
278+}
279+
262func (m *Message) AppendReasoningSignature(signature string) {
280func (m *Message) AppendReasoningSignature(signature string) {
263 for i, part := range m.Parts {
281 for i, part := range m.Parts {
264 if c, ok := part.(ReasoningContent); ok {
282 if c, ok := part.(ReasoningContent); ok {
@@ -443,6 +461,11 @@ func (m *Message) ToAIMessage() []fantasy.Message {
443 if reasoning.ResponsesData != nil {
461 if reasoning.ResponsesData != nil {
444 reasoningPart.ProviderOptions[openai.Name] = reasoning.ResponsesData
462 reasoningPart.ProviderOptions[openai.Name] = reasoning.ResponsesData
445 }
463 }
464+ if reasoning.ThoughtSignature != "" {
465+ reasoningPart.ProviderOptions[google.Name] = &google.ReasoningMetadata{
466+ Signature: reasoning.ThoughtSignature,
467+ }
468+ }
446 parts = append(parts, reasoningPart)
469 parts = append(parts, reasoningPart)
447 }
470 }
448 for _, call := range m.ToolCalls() {
471 for _, call := range m.ToolCalls() {