diff --git a/go.mod b/go.mod index a029a78e8803a423611cfaebb0d86e84b59a1c95..0cb1bbe7dbf35a45cc4d3fa250f30628f704e83e 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( mvdan.cc/sh/v3 v3.12.1-0.20250902163504-3cf4fd5717a5 ) -replace charm.land/fantasy => ../fantasy/ +replace charm.land/fantasy => ../../fantasy/main/ require ( cloud.google.com/go v0.116.0 // indirect diff --git a/internal/agent/agent.go b/internal/agent/agent.go index cafc50c301ac97ec384bfd85e952628db74cc554..fb822c3812b391c77a215d7d2895fc8236ae4163 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -15,6 +15,7 @@ import ( "charm.land/fantasy" "charm.land/fantasy/providers/anthropic" "charm.land/fantasy/providers/bedrock" + "charm.land/fantasy/providers/google" "charm.land/fantasy/providers/openai" "github.com/charmbracelet/catwalk/pkg/catwalk" "github.com/charmbracelet/crush/internal/agent/tools" @@ -243,6 +244,11 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*fantasy currentAssistant.AppendReasoningSignature(reasoning.Signature) } } + if googleData, ok := reasoning.ProviderMetadata[google.Name]; ok { + if reasoning, ok := googleData.(*google.ReasoningMetadata); ok { + currentAssistant.AppendReasoningSignature(reasoning.Signature) + } + } if openaiData, ok := reasoning.ProviderMetadata[openai.Name]; ok { if reasoning, ok := openaiData.(*openai.ResponsesReasoningMetadata); ok { currentAssistant.SetReasoningResponsesData(reasoning) diff --git a/internal/agent/coordinator.go b/internal/agent/coordinator.go index ef70b7cd12ef275b07a5beb6c37a3840a99d2b83..a7cbca927368d4aa3428a6f333fb5e83ed5a5521 100644 --- a/internal/agent/coordinator.go +++ b/internal/agent/coordinator.go @@ -218,6 +218,13 @@ func getProviderOptions(model Model, tp catwalk.Type) fantasy.ProviderOptions { options[openrouter.Name] = parsed } case google.Name: + _, hasReasoning := mergedOptions["thinking_config"] + if !hasReasoning { + mergedOptions["thinking_config"] = map[string]any{ + "thinking_budget": 2000, + "include_thoughts": true, + } + } parsed, err := google.ParseOptions(mergedOptions) if err == nil { options[google.Name] = parsed diff --git a/internal/agent/tools/ls.go b/internal/agent/tools/ls.go index 58433e6b2eaccc9ed59d3f4a4a57435de2842fdb..f0cce167ce4380801d9eb80ed955d7261fa41201 100644 --- a/internal/agent/tools/ls.go +++ b/internal/agent/tools/ls.go @@ -16,7 +16,7 @@ import ( ) type LSParams struct { - Path string `json:"path" description:"The path to the directory to list (defaults to current working directory)"` + Path string `json:"path,omitempty" description:"The path to the directory to list (defaults to current working directory)"` Ignore []string `json:"ignore,omitempty" description:"List of glob patterns to ignore"` Depth int `json:"depth,omitempty" description:"The maximum depth to traverse"` } diff --git a/internal/message/content.go b/internal/message/content.go index 84187ed5c830d0a9a5a00e94c04b2b6c6b5c2440..7f35678230759ab3dcfc13287d340d6f0327d722 100644 --- a/internal/message/content.go +++ b/internal/message/content.go @@ -9,6 +9,7 @@ import ( "charm.land/fantasy" "charm.land/fantasy/providers/anthropic" + "charm.land/fantasy/providers/google" "charm.land/fantasy/providers/openai" "github.com/charmbracelet/catwalk/pkg/catwalk" ) @@ -41,11 +42,12 @@ type ContentPart interface { } type ReasoningContent struct { - Thinking string `json:"thinking"` - Signature string `json:"signature"` - ResponsesData *openai.ResponsesReasoningMetadata `json:"responses_data"` - StartedAt int64 `json:"started_at,omitempty"` - FinishedAt int64 `json:"finished_at,omitempty"` + Thinking string `json:"thinking"` + Signature string `json:"signature"` + ThoughtSignature string `json:"thought_signature"` // Used for google + ResponsesData *openai.ResponsesReasoningMetadata `json:"responses_data"` + StartedAt int64 `json:"started_at,omitempty"` + FinishedAt int64 `json:"finished_at,omitempty"` } func (tc ReasoningContent) String() string { @@ -259,6 +261,22 @@ func (m *Message) AppendReasoningContent(delta string) { } } +func (m *Message) AppendThoughtSignature(signature string) { + for i, part := range m.Parts { + if c, ok := part.(ReasoningContent); ok { + m.Parts[i] = ReasoningContent{ + Thinking: c.Thinking, + ThoughtSignature: c.ThoughtSignature + signature, + Signature: c.Signature, + StartedAt: c.StartedAt, + FinishedAt: c.FinishedAt, + } + return + } + } + m.Parts = append(m.Parts, ReasoningContent{ThoughtSignature: signature}) +} + func (m *Message) AppendReasoningSignature(signature string) { for i, part := range m.Parts { if c, ok := part.(ReasoningContent); ok { @@ -443,6 +461,11 @@ func (m *Message) ToAIMessage() []fantasy.Message { if reasoning.ResponsesData != nil { reasoningPart.ProviderOptions[openai.Name] = reasoning.ResponsesData } + if reasoning.ThoughtSignature != "" { + reasoningPart.ProviderOptions[google.Name] = &google.ReasoningMetadata{ + Signature: reasoning.ThoughtSignature, + } + } parts = append(parts, reasoningPart) } for _, call := range m.ToolCalls() {