Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ resolver = "2"

[workspace.package]
edition = "2021"
version = "1.8.0"
version = "1.9.1"
authors = ["Block <[email protected]>"]
license = "Apache-2.0"
repository = "https://github.com/block/goose"
Expand Down
3 changes: 1 addition & 2 deletions crates/goose/src/context_mgmt/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ pub fn get_messages_token_counts_async(
token_counter: &AsyncTokenCounter,
messages: &[Message],
) -> Vec<usize> {
// Calculate current token count of each message, use count_chat_tokens to ensure we
// capture the full content of the message, include ToolRequests and ToolResponses
messages
.iter()
.filter(|m| m.is_agent_visible())
.map(|msg| token_counter.count_chat_tokens("", std::slice::from_ref(msg), &[]))
.collect()
}
Expand Down
21 changes: 20 additions & 1 deletion crates/goose/src/conversation/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::conversation::message::{Message, MessageContent};
use crate::conversation::message::{Message, MessageContent, MessageMetadata};
use rmcp::model::Role;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
Expand Down Expand Up @@ -102,6 +102,25 @@ impl Conversation {
self.0.clear();
}

pub fn filtered_messages<F>(&self, filter: F) -> Vec<Message>
where
F: Fn(&MessageMetadata) -> bool,
{
self.0
.iter()
.filter(|msg| filter(&msg.metadata))
.cloned()
.collect()
}

pub fn agent_visible_messages(&self) -> Vec<Message> {
self.filtered_messages(|meta| meta.agent_visible)
}

pub fn user_visible_messages(&self) -> Vec<Message> {
self.filtered_messages(|meta| meta.user_visible)
}

fn validate(self) -> Result<Self, InvalidConversation> {
let (_messages, issues) = fix_messages(self.0.clone());
if !issues.is_empty() {
Expand Down
23 changes: 3 additions & 20 deletions crates/goose/src/providers/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,28 +328,18 @@ pub trait Provider: Send + Sync {
) -> Result<(Message, ProviderUsage), ProviderError>;

// Default implementation: use the provider's configured model
// This method filters messages to only include agent_visible ones
async fn complete(
&self,
system: &str,
messages: &[Message],
tools: &[Tool],
) -> Result<(Message, ProviderUsage), ProviderError> {
let model_config = self.get_model_config();

// Filter messages to only include agent_visible ones
let agent_visible_messages: Vec<Message> = messages
.iter()
.filter(|m| m.is_agent_visible())
.cloned()
.collect();

self.complete_with_model(&model_config, system, &agent_visible_messages, tools)
self.complete_with_model(&model_config, system, messages, tools)
.await
}

// Check if a fast model is configured, otherwise fall back to regular model
// This method filters messages to only include agent_visible ones
async fn complete_fast(
&self,
system: &str,
Expand All @@ -359,15 +349,8 @@ pub trait Provider: Send + Sync {
let model_config = self.get_model_config();
let fast_config = model_config.use_fast_model();

// Filter messages to only include agent_visible ones
let agent_visible_messages: Vec<Message> = messages
.iter()
.filter(|m| m.is_agent_visible())
.cloned()
.collect();

match self
.complete_with_model(&fast_config, system, &agent_visible_messages, tools)
.complete_with_model(&fast_config, system, messages, tools)
.await
{
Ok(result) => Ok(result),
Expand All @@ -379,7 +362,7 @@ pub trait Provider: Send + Sync {
e,
model_config.model_name
);
self.complete_with_model(&model_config, system, &agent_visible_messages, tools)
self.complete_with_model(&model_config, system, messages, tools)
.await
} else {
Err(e)
Expand Down
1 change: 1 addition & 0 deletions crates/goose/src/providers/bedrock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ impl BedrockProvider {
.set_messages(Some(
messages
.iter()
.filter(|m| m.is_agent_visible())
.map(to_bedrock_message)
.collect::<Result<_>>()?,
));
Expand Down
2 changes: 1 addition & 1 deletion crates/goose/src/providers/claude_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ impl ClaudeCodeProvider {
fn messages_to_claude_format(&self, _system: &str, messages: &[Message]) -> Result<Value> {
let mut claude_messages = Vec::new();

for message in messages {
for message in messages.iter().filter(|m| m.is_agent_visible()) {
let role = match message.role {
Role::User => "user",
Role::Assistant => "assistant",
Expand Down
2 changes: 1 addition & 1 deletion crates/goose/src/providers/cursor_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl CursorAgentProvider {
full_prompt.push_str("\n\n");

// Add conversation history
for message in messages {
for message in messages.iter().filter(|m| m.is_agent_visible()) {
let role_prefix = match message.role {
Role::User => "Human: ",
Role::Assistant => "Assistant: ",
Expand Down
2 changes: 1 addition & 1 deletion crates/goose/src/providers/databricks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const DEFAULT_SCOPES: &[&str] = &["all-apis", "offline_access"];
const DEFAULT_TIMEOUT_SECS: u64 = 600;

pub const DATABRICKS_DEFAULT_MODEL: &str = "databricks-claude-sonnet-4";
const DATABRICKS_DEFAULT_FAST_MODEL: &str = "gemini-1-5-flash";
const DATABRICKS_DEFAULT_FAST_MODEL: &str = "gemini-2-5-flash";
pub const DATABRICKS_KNOWN_MODELS: &[&str] = &[
"databricks-claude-3-7-sonnet",
"databricks-meta-llama-3-3-70b-instruct",
Expand Down
3 changes: 1 addition & 2 deletions crates/goose/src/providers/formats/anthropic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ const DATA_FIELD: &str = "data";
pub fn format_messages(messages: &[Message]) -> Vec<Value> {
let mut anthropic_messages = Vec::new();

// Convert messages to Anthropic format
for message in messages {
for message in messages.iter().filter(|m| m.is_agent_visible()) {
let role = match message.role {
Role::User => USER_ROLE,
Role::Assistant => ASSISTANT_ROLE,
Expand Down
2 changes: 1 addition & 1 deletion crates/goose/src/providers/formats/databricks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct DatabricksMessage {
/// even though the message structure is otherwise following openai, the enum switches this
fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<DatabricksMessage> {
let mut result = Vec::new();
for message in messages {
for message in messages.iter().filter(|m| m.is_agent_visible()) {
let mut converted = DatabricksMessage {
content: Value::Null,
role: match message.role {
Expand Down
1 change: 1 addition & 0 deletions crates/goose/src/providers/formats/google.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use std::ops::Deref;
pub fn format_messages(messages: &[Message]) -> Vec<Value> {
messages
.iter()
.filter(|m| m.is_agent_visible())
.filter(|message| {
message
.content
Expand Down
2 changes: 1 addition & 1 deletion crates/goose/src/providers/formats/openai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ struct StreamingChunk {
/// even though the message structure is otherwise following openai, the enum switches this
pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<Value> {
let mut messages_spec = Vec::new();
for message in messages {
for message in messages.iter().filter(|m| m.is_agent_visible()) {
let mut converted = json!({
"role": message.role
});
Expand Down
2 changes: 1 addition & 1 deletion crates/goose/src/providers/formats/snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub fn format_messages(messages: &[Message]) -> Vec<Value> {
let mut snowflake_messages = Vec::new();

// Convert messages to Snowflake format
for message in messages {
for message in messages.iter().filter(|m| m.is_agent_visible()) {
let role = match message.role {
Role::User => "user",
Role::Assistant => "assistant",
Expand Down
2 changes: 1 addition & 1 deletion crates/goose/src/providers/gemini_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ impl GeminiCliProvider {
full_prompt.push_str("\n\n");

// Add conversation history
for message in messages {
for message in messages.iter().filter(|m| m.is_agent_visible()) {
let role_prefix = match message.role {
Role::User => "Human: ",
Role::Assistant => "Assistant: ",
Expand Down
4 changes: 3 additions & 1 deletion crates/goose/src/token_counter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ impl AsyncTokenCounter {
let line = format!("{}:{}", name, description);
func_token_count += self.count_tokens(&line);

if let serde_json::Value::Object(properties) = &tool.input_schema["properties"] {
if let Some(serde_json::Value::Object(properties)) =
tool.input_schema.get("properties")
{
if !properties.is_empty() {
func_token_count += prop_init;
for (key, value) in properties {
Expand Down
2 changes: 1 addition & 1 deletion crates/goose/tests/pricing_integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ async fn test_pricing_cache_performance() {
("anthropic", "claude-sonnet-4"),
("openai", "gpt-4o"),
("openai", "gpt-4o-mini"),
("google", "gemini-flash-1.5"),
("google", "gemini-2.5-flash"),
("anthropic", "claude-opus-4"),
];

Expand Down
2 changes: 1 addition & 1 deletion ui/desktop/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"license": {
"name": "Apache-2.0"
},
"version": "1.8.0"
"version": "1.9.0"
},
"paths": {
"/agent/add_sub_recipes": {
Expand Down
4 changes: 2 additions & 2 deletions ui/desktop/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ui/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "goose-app",
"productName": "Goose",
"version": "1.8.0",
"version": "1.9.1",
"description": "Goose App",
"engines": {
"node": "^22.17.1"
Expand Down
2 changes: 1 addition & 1 deletion ui/desktop/src/components/alerts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ export interface Alert {
compactButtonDisabled?: boolean;
onCompact?: () => void;
compactIcon?: React.ReactNode;
autoCompactThreshold?: number; // Add this for showing the auto-compact threshold
autoCompactThreshold?: number;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useRef, useEffect, useCallback, useState } from 'react';
import { FaCircle } from 'react-icons/fa';
import { isEqual } from 'lodash';
import { cn } from '../../utils';
import { Alert, AlertType } from '../alerts';
import { AlertBox } from '../alerts';
Expand Down Expand Up @@ -104,10 +105,10 @@ export default function BottomMenuAlertPopover({ alerts }: AlertPopoverProps) {
return;
}

// Find new or changed alerts
// Find new or changed alerts using deep comparison
const changedAlerts = alerts.filter((alert, index) => {
const prevAlert = previousAlertsRef.current[index];
return !prevAlert || prevAlert.type !== alert.type || prevAlert.message !== alert.message;
return !prevAlert || !isEqual(prevAlert, alert);
});

previousAlertsRef.current = alerts;
Expand Down
50 changes: 20 additions & 30 deletions ui/desktop/src/recipe/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,40 +187,30 @@ function openApiSchemaToZod(schema: Record<string, unknown>): z.ZodTypeAny {
shape[propName] = openApiSchemaToZod(propSchema as Record<string, unknown>);
}

// Make optional properties optional
if (schema.required && Array.isArray(schema.required)) {
const optionalShape: Record<string, z.ZodTypeAny> = {};
for (const [propName, zodSchema] of Object.entries(shape)) {
if (schema.required.includes(propName)) {
optionalShape[propName] = zodSchema;
} else {
optionalShape[propName] = zodSchema.optional();
}
}
let objectSchema = z.object(optionalShape);

if (schema.additionalProperties === true) {
return schema.nullable
? objectSchema.passthrough().nullable()
: objectSchema.passthrough();
} else if (schema.additionalProperties === false) {
return schema.nullable ? objectSchema.strict().nullable() : objectSchema.strict();
// Make optional properties optional based on required array
const optionalShape: Record<string, z.ZodTypeAny> = {};
const requiredFields =
schema.required && Array.isArray(schema.required) ? schema.required : [];

for (const [propName, zodSchema] of Object.entries(shape)) {
if (requiredFields.includes(propName)) {
optionalShape[propName] = zodSchema;
} else {
optionalShape[propName] = zodSchema.optional();
}
}

return schema.nullable ? objectSchema.nullable() : objectSchema;
} else {
let objectSchema = z.object(shape);
let objectSchema = z.object(optionalShape);

if (schema.additionalProperties === true) {
return schema.nullable
? objectSchema.passthrough().nullable()
: objectSchema.passthrough();
} else if (schema.additionalProperties === false) {
return schema.nullable ? objectSchema.strict().nullable() : objectSchema.strict();
}

return schema.nullable ? objectSchema.nullable() : objectSchema;
if (schema.additionalProperties === true) {
return schema.nullable
? objectSchema.passthrough().nullable()
: objectSchema.passthrough();
} else if (schema.additionalProperties === false) {
return schema.nullable ? objectSchema.strict().nullable() : objectSchema.strict();
}

return schema.nullable ? objectSchema.nullable() : objectSchema;
}
return schema.nullable ? z.record(z.any()).nullable() : z.record(z.any());

Expand Down
Loading