diff --git a/Cargo.lock b/Cargo.lock index 620fa5b66c74..b4f3c4aae229 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3519,7 +3519,7 @@ dependencies = [ [[package]] name = "goose-test" -version = "1.1.0" +version = "1.3.0" dependencies = [ "clap", "serde_json", diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs index d1c0305239c0..e44a3362af9d 100644 --- a/crates/goose-server/src/openapi.rs +++ b/crates/goose-server/src/openapi.rs @@ -369,6 +369,10 @@ impl<'__s> ToSchema<'__s> for AnnotatedSchema { super::routes::config_management::upsert_permissions, super::routes::agent::get_tools, super::routes::agent::add_sub_recipes, + super::routes::agent::extend_prompt, + super::routes::agent::update_agent_provider, + super::routes::agent::update_router_tool_selector, + super::routes::agent::update_session_config, super::routes::reply::confirm_permission, super::routes::context::manage_context, super::routes::session::list_sessions, @@ -464,6 +468,12 @@ impl<'__s> ToSchema<'__s> for AnnotatedSchema { goose::agents::types::SuccessCheck, super::routes::agent::AddSubRecipesRequest, super::routes::agent::AddSubRecipesResponse, + super::routes::agent::ExtendPromptRequest, + super::routes::agent::ExtendPromptResponse, + super::routes::agent::UpdateProviderRequest, + super::routes::agent::SessionConfigRequest, + super::routes::agent::GetToolsQuery, + super::routes::agent::ErrorResponse, )) )] pub struct ApiDoc; diff --git a/crates/goose-server/src/routes/agent.rs b/crates/goose-server/src/routes/agent.rs index 8ce58b00b588..eb674e30b68b 100644 --- a/crates/goose-server/src/routes/agent.rs +++ b/crates/goose-server/src/routes/agent.rs @@ -16,22 +16,15 @@ use goose::{ }; use goose::{config::Config, recipe::SubRecipe}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::sync::Arc; -#[derive(Serialize)] -struct VersionsResponse { - available_versions: Vec, - default_version: String, -} - -#[derive(Deserialize)] -struct ExtendPromptRequest { +#[derive(Deserialize, utoipa::ToSchema)] +pub struct ExtendPromptRequest { extension: String, } -#[derive(Serialize)] -struct ExtendPromptResponse { +#[derive(Serialize, utoipa::ToSchema)] +pub struct ExtendPromptResponse { success: bool, } @@ -45,66 +38,35 @@ pub struct AddSubRecipesResponse { success: bool, } -#[derive(Deserialize)] -struct ProviderFile { - name: String, - description: String, - models: Vec, - required_keys: Vec, -} - -#[derive(Serialize)] -struct ProviderDetails { - name: String, - description: String, - models: Vec, - required_keys: Vec, -} - -#[derive(Serialize)] -struct ProviderList { - id: String, - details: ProviderDetails, -} - -#[derive(Deserialize)] -struct UpdateProviderRequest { +#[derive(Deserialize, utoipa::ToSchema)] +pub struct UpdateProviderRequest { provider: String, model: Option, } -#[derive(Deserialize)] -struct SessionConfigRequest { +#[derive(Deserialize, utoipa::ToSchema)] +pub struct SessionConfigRequest { response: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub struct GetToolsQuery { extension_name: Option, } -#[derive(Serialize)] -struct ErrorResponse { +#[derive(Serialize, utoipa::ToSchema)] +pub struct ErrorResponse { error: String, } -async fn get_versions() -> Json { - let versions = ["goose".to_string()]; - let default_version = "goose".to_string(); - - Json(VersionsResponse { - available_versions: versions.iter().map(|v| v.to_string()).collect(), - default_version, - }) -} - #[utoipa::path( post, path = "/agent/add_sub_recipes", request_body = AddSubRecipesRequest, responses( - (status = 200, description = "added sub recipes to agent successfully", body = AddSubRecipesResponse), + (status = 200, description = "Added sub recipes to agent successfully", body = AddSubRecipesResponse), (status = 401, description = "Unauthorized - invalid secret key"), + (status = 424, description = "Agent not initialized"), ), )] async fn add_sub_recipes( @@ -122,6 +84,16 @@ async fn add_sub_recipes( Ok(Json(AddSubRecipesResponse { success: true })) } +#[utoipa::path( + post, + path = "/agent/prompt", + request_body = ExtendPromptRequest, + responses( + (status = 200, description = "Extended system prompt successfully", body = ExtendPromptResponse), + (status = 401, description = "Unauthorized - invalid secret key"), + (status = 424, description = "Agent not initialized"), + ), +)] async fn extend_prompt( State(state): State>, headers: HeaderMap, @@ -137,29 +109,6 @@ async fn extend_prompt( Ok(Json(ExtendPromptResponse { success: true })) } -async fn list_providers() -> Json> { - let contents = include_str!("providers_and_keys.json"); - - let providers: HashMap = - serde_json::from_str(contents).expect("Failed to parse providers_and_keys.json"); - - let response: Vec = providers - .into_iter() - .map(|(id, provider)| ProviderList { - id, - details: ProviderDetails { - name: provider.name, - description: provider.description, - models: provider.models, - required_keys: provider.required_keys, - }, - }) - .collect(); - - // Return the response as JSON. - Json(response) -} - #[utoipa::path( get, path = "/agent/tools", @@ -224,10 +173,12 @@ async fn get_tools( #[utoipa::path( post, path = "/agent/update_provider", + request_body = UpdateProviderRequest, responses( - (status = 200, description = "Update provider completed", body = String), + (status = 200, description = "Provider updated successfully"), (status = 400, description = "Bad request - missing or invalid parameters"), (status = 401, description = "Unauthorized - invalid secret key"), + (status = 424, description = "Agent not initialized"), (status = 500, description = "Internal server error") ) )] @@ -269,6 +220,8 @@ async fn update_agent_provider( path = "/agent/update_router_tool_selector", responses( (status = 200, description = "Tool selection strategy updated successfully", body = String), + (status = 401, description = "Unauthorized - invalid secret key"), + (status = 424, description = "Agent not initialized"), (status = 500, description = "Internal server error") ) )] @@ -307,8 +260,11 @@ async fn update_router_tool_selector( #[utoipa::path( post, path = "/agent/session_config", + request_body = SessionConfigRequest, responses( (status = 200, description = "Session config updated successfully", body = String), + (status = 401, description = "Unauthorized - invalid secret key"), + (status = 424, description = "Agent not initialized"), (status = 500, description = "Internal server error") ) )] @@ -344,8 +300,6 @@ async fn update_session_config( pub fn routes(state: Arc) -> Router { Router::new() - .route("/agent/versions", get(get_versions)) - .route("/agent/providers", get(list_providers)) .route("/agent/prompt", post(extend_prompt)) .route("/agent/tools", get(get_tools)) .route("/agent/update_provider", post(update_agent_provider)) diff --git a/crates/goose-server/src/routes/config_management.rs b/crates/goose-server/src/routes/config_management.rs index 9dc2d0eb912f..3270e9708b97 100644 --- a/crates/goose-server/src/routes/config_management.rs +++ b/crates/goose-server/src/routes/config_management.rs @@ -58,9 +58,7 @@ pub struct ConfigResponse { #[derive(Debug, Serialize, Deserialize, ToSchema)] pub struct ProviderDetails { pub name: String, - pub metadata: ProviderMetadata, - pub is_configured: bool, } diff --git a/crates/goose-server/src/routes/providers_and_keys.json b/crates/goose-server/src/routes/providers_and_keys.json deleted file mode 100644 index 422856a6d5b0..000000000000 --- a/crates/goose-server/src/routes/providers_and_keys.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "openai": { - "name": "OpenAI", - "description": "Use GPT-4 and other OpenAI models", - "models": ["gpt-4o", "gpt-4-turbo","o1"], - "required_keys": ["OPENAI_API_KEY", "OPENAI_HOST", "OPENAI_BASE_PATH"] - }, - "anthropic": { - "name": "Anthropic", - "description": "Use Claude and other Anthropic models", - "models": ["claude-3.5-sonnet-2"], - "required_keys": ["ANTHROPIC_API_KEY", "ANTHROPIC_HOST"] - }, - "databricks": { - "name": "Databricks", - "description": "Connect to LLMs via Databricks", - "models": ["goose"], - "required_keys": ["DATABRICKS_HOST"] - }, - "gcp_vertex_ai": { - "name": "GCP Vertex AI", - "description": "Use Vertex AI platform models", - "models": ["claude-3-5-haiku@20241022", "claude-3-5-sonnet@20240620", "claude-3-5-sonnet-v2@20241022", "claude-3-7-sonnet@20250219", "claude-sonnet-4@20250514", "claude-opus-4@20250514", "gemini-1.5-pro-002", "gemini-2.0-flash-001", "gemini-2.0-pro-exp-02-05", "gemini-2.5-pro-exp-03-25", "gemini-2.5-flash-preview-05-20", "gemini-2.5-pro-preview-05-06", "gemini-2.5-flash", "gemini-2.5-pro"], - "required_keys": ["GCP_PROJECT_ID", "GCP_LOCATION"] - }, - "google": { - "name": "Google", - "description": "Lorem ipsum", - "models": ["gemini-1.5-flash"], - "required_keys": ["GOOGLE_API_KEY"] - }, - "groq": { - "name": "Groq", - "description": "Lorem ipsum", - "models": ["llama-3.3-70b-versatile"], - "required_keys": ["GROQ_API_KEY"] - }, - "ollama": { - "name": "Ollama", - "description": "Lorem ipsum", - "models": ["qwen2.5"], - "required_keys": ["OLLAMA_HOST"] - }, - "openrouter": { - "name": "OpenRouter", - "description": "Lorem ipsum", - "models": [], - "required_keys": ["OPENROUTER_API_KEY"] - }, - "azure_openai": { - "name": "Azure OpenAI", - "description": "Connect to Azure OpenAI Service. If no API key is provided, Azure credential chain will be used.", - "models": ["gpt-4o", "gpt-4o-mini"], - "required_keys": ["AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_DEPLOYMENT_NAME"] - }, - "aws_bedrock": { - "name": "AWS Bedrock", - "description": "Connect to LLMs via AWS Bedrock", - "models": ["us.anthropic.claude-3-7-sonnet-20250219-v1:0"], - "required_keys": ["AWS_PROFILE"] - }, - "xai": { - "name": "Xai", - "description": "Lorem ipsum", - "models": ["grok-3"], - "required_keys": ["XAI_API_KEY"] - }, -} diff --git a/crates/goose-server/src/routes/session.rs b/crates/goose-server/src/routes/session.rs index c2c34e00574b..f9afb0e41b8c 100644 --- a/crates/goose-server/src/routes/session.rs +++ b/crates/goose-server/src/routes/session.rs @@ -1,5 +1,5 @@ use super::utils::verify_secret_key; -use chrono::{DateTime, Datelike}; +use chrono::DateTime; use std::collections::HashMap; use std::sync::Arc; @@ -260,55 +260,6 @@ async fn get_session_insights( Ok(Json(insights)) } -#[utoipa::path( - get, - path = "/sessions/activity-heatmap", - responses( - (status = 200, description = "Activity heatmap data", body = [ActivityHeatmapCell]), - (status = 401, description = "Unauthorized - Invalid or missing API key"), - (status = 500, description = "Internal server error") - ), - security(("api_key" = [])), - tag = "Session Management" -)] -async fn get_activity_heatmap( - State(state): State>, - headers: HeaderMap, -) -> Result>, StatusCode> { - verify_secret_key(&headers, &state)?; - - let sessions = get_valid_sorted_sessions(SortOrder::Descending) - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - - // Only sessions with a description - let sessions: Vec = sessions - .into_iter() - .filter(|session| !session.metadata.description.is_empty()) - .collect(); - - // Map: (week, day) -> count - let mut heatmap: std::collections::HashMap<(usize, usize), usize> = - std::collections::HashMap::new(); - - for session in &sessions { - if let Ok(date) = - chrono::NaiveDateTime::parse_from_str(&session.modified, "%Y-%m-%d %H:%M:%S UTC") - { - let date = date.date(); - let week = date.iso_week().week() as usize - 1; // 0-based week - let day = date.weekday().num_days_from_sunday() as usize; // 0=Sun, 6=Sat - *heatmap.entry((week, day)).or_insert(0) += 1; - } - } - - let mut result = Vec::new(); - for ((week, day), count) in heatmap { - result.push(ActivityHeatmapCell { week, day, count }); - } - - Ok(Json(result)) -} - #[utoipa::path( put, path = "/sessions/{session_id}/metadata", @@ -365,7 +316,6 @@ pub fn routes(state: Arc) -> Router { .route("/sessions", get(list_sessions)) .route("/sessions/{session_id}", get(get_session_history)) .route("/sessions/insights", get(get_session_insights)) - .route("/sessions/activity-heatmap", get(get_activity_heatmap)) .route( "/sessions/{session_id}/metadata", put(update_session_metadata), diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 589b3987dbb2..dbe2a5ba0f8b 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -31,7 +31,7 @@ }, "responses": { "200": { - "description": "added sub recipes to agent successfully", + "description": "Added sub recipes to agent successfully", "content": { "application/json": { "schema": { @@ -42,6 +42,84 @@ }, "401": { "description": "Unauthorized - invalid secret key" + }, + "424": { + "description": "Agent not initialized" + } + } + } + }, + "/agent/prompt": { + "post": { + "tags": [ + "super::routes::agent" + ], + "operationId": "extend_prompt", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExtendPromptRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Extended system prompt successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExtendPromptResponse" + } + } + } + }, + "401": { + "description": "Unauthorized - invalid secret key" + }, + "424": { + "description": "Agent not initialized" + } + } + } + }, + "/agent/session_config": { + "post": { + "tags": [ + "super::routes::agent" + ], + "operationId": "update_session_config", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionConfigRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Session config updated successfully", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Unauthorized - invalid secret key" + }, + "424": { + "description": "Agent not initialized" + }, + "500": { + "description": "Internal server error" } } } @@ -90,6 +168,70 @@ } } }, + "/agent/update_provider": { + "post": { + "tags": [ + "super::routes::agent" + ], + "operationId": "update_agent_provider", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProviderRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Provider updated successfully" + }, + "400": { + "description": "Bad request - missing or invalid parameters" + }, + "401": { + "description": "Unauthorized - invalid secret key" + }, + "424": { + "description": "Agent not initialized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/agent/update_router_tool_selector": { + "post": { + "tags": [ + "super::routes::agent" + ], + "operationId": "update_router_tool_selector", + "responses": { + "200": { + "description": "Tool selection strategy updated successfully", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Unauthorized - invalid secret key" + }, + "424": { + "description": "Agent not initialized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, "/config": { "get": { "tags": [ @@ -1446,6 +1588,39 @@ "description": "A map of environment variables to set, e.g. API_KEY -> some_secret, HOST -> host" } }, + "ErrorResponse": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + } + }, + "ExtendPromptRequest": { + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "type": "string" + } + } + }, + "ExtendPromptResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean" + } + } + }, "ExtensionConfig": { "oneOf": [ { @@ -1799,6 +1974,15 @@ } } }, + "GetToolsQuery": { + "type": "object", + "properties": { + "extension_name": { + "type": "string", + "nullable": true + } + } + }, "ImageContent": { "type": "object", "required": [ @@ -2618,6 +2802,19 @@ } } }, + "SessionConfigRequest": { + "type": "object", + "properties": { + "response": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + } + ], + "nullable": true + } + } + }, "SessionDisplayInfo": { "type": "object", "required": [ @@ -3095,6 +3292,21 @@ } } }, + "UpdateProviderRequest": { + "type": "object", + "required": [ + "provider" + ], + "properties": { + "model": { + "type": "string", + "nullable": true + }, + "provider": { + "type": "string" + } + } + }, "UpdateScheduleRequest": { "type": "object", "required": [ diff --git a/ui/desktop/src/agent/index.ts b/ui/desktop/src/agent/index.ts index 6268dd4f32b0..79ba9e7696fc 100644 --- a/ui/desktop/src/agent/index.ts +++ b/ui/desktop/src/agent/index.ts @@ -1,4 +1,4 @@ -import { getApiUrl } from '../config'; +import { updateAgentProvider } from '../api'; interface initializeAgentProps { model: string; @@ -6,23 +6,15 @@ interface initializeAgentProps { } export async function initializeAgent({ model, provider }: initializeAgentProps) { - const response = await fetch(getApiUrl('/agent/update_provider'), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Secret-Key': await window.electron.getSecretKey(), - }, - body: JSON.stringify({ + const response = await updateAgentProvider({ + body: { provider: provider.toLowerCase().replace(/ /g, '_'), model: model, - }), + }, }); - if (!response.ok) { - const responseText = await response.text(); - throw new Error( - `Failed to initialize agent: ${response.status} ${response.statusText} - ${responseText}` - ); + if (response.error) { + throw new Error(`Failed to initialize agent: ${response.error}`); } return response; diff --git a/ui/desktop/src/api/sdk.gen.ts b/ui/desktop/src/api/sdk.gen.ts index d3b39f08533a..055b9d829e2e 100644 --- a/ui/desktop/src/api/sdk.gen.ts +++ b/ui/desktop/src/api/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { AddSubRecipesData, AddSubRecipesResponse2, GetToolsData, GetToolsResponse, ReadAllConfigData, ReadAllConfigResponse, BackupConfigData, BackupConfigResponse, GetExtensionsData, GetExtensionsResponse, AddExtensionData, AddExtensionResponse, RemoveExtensionData, RemoveExtensionResponse, InitConfigData, InitConfigResponse, UpsertPermissionsData, UpsertPermissionsResponse, ProvidersData, ProvidersResponse2, ReadConfigData, RecoverConfigData, RecoverConfigResponse, RemoveConfigData, RemoveConfigResponse, UpsertConfigData, UpsertConfigResponse, ValidateConfigData, ValidateConfigResponse, ConfirmPermissionData, ManageContextData, ManageContextResponse, CreateRecipeData, CreateRecipeResponse2, DecodeRecipeData, DecodeRecipeResponse2, EncodeRecipeData, EncodeRecipeResponse2, CreateScheduleData, CreateScheduleResponse, DeleteScheduleData, DeleteScheduleResponse, ListSchedulesData, ListSchedulesResponse2, UpdateScheduleData, UpdateScheduleResponse, InspectRunningJobData, InspectRunningJobResponse, KillRunningJobData, PauseScheduleData, PauseScheduleResponse, RunNowHandlerData, RunNowHandlerResponse, SessionsHandlerData, SessionsHandlerResponse, UnpauseScheduleData, UnpauseScheduleResponse, ListSessionsData, ListSessionsResponse, GetSessionHistoryData, GetSessionHistoryResponse } from './types.gen'; +import type { AddSubRecipesData, AddSubRecipesResponse2, ExtendPromptData, ExtendPromptResponse2, UpdateSessionConfigData, UpdateSessionConfigResponse, GetToolsData, GetToolsResponse, UpdateAgentProviderData, UpdateRouterToolSelectorData, UpdateRouterToolSelectorResponse, ReadAllConfigData, ReadAllConfigResponse, BackupConfigData, BackupConfigResponse, GetExtensionsData, GetExtensionsResponse, AddExtensionData, AddExtensionResponse, RemoveExtensionData, RemoveExtensionResponse, InitConfigData, InitConfigResponse, UpsertPermissionsData, UpsertPermissionsResponse, ProvidersData, ProvidersResponse2, ReadConfigData, RecoverConfigData, RecoverConfigResponse, RemoveConfigData, RemoveConfigResponse, UpsertConfigData, UpsertConfigResponse, ValidateConfigData, ValidateConfigResponse, ConfirmPermissionData, ManageContextData, ManageContextResponse, CreateRecipeData, CreateRecipeResponse2, DecodeRecipeData, DecodeRecipeResponse2, EncodeRecipeData, EncodeRecipeResponse2, CreateScheduleData, CreateScheduleResponse, DeleteScheduleData, DeleteScheduleResponse, ListSchedulesData, ListSchedulesResponse2, UpdateScheduleData, UpdateScheduleResponse, InspectRunningJobData, InspectRunningJobResponse, KillRunningJobData, PauseScheduleData, PauseScheduleResponse, RunNowHandlerData, RunNowHandlerResponse, SessionsHandlerData, SessionsHandlerResponse, UnpauseScheduleData, UnpauseScheduleResponse, ListSessionsData, ListSessionsResponse, GetSessionHistoryData, GetSessionHistoryResponse } from './types.gen'; import { client as _heyApiClient } from './client.gen'; export type Options = ClientOptions & { @@ -29,6 +29,28 @@ export const addSubRecipes = (options: Opt }); }; +export const extendPrompt = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/agent/prompt', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +export const updateSessionConfig = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/agent/session_config', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + export const getTools = (options?: Options) => { return (options?.client ?? _heyApiClient).get({ url: '/agent/tools', @@ -36,6 +58,24 @@ export const getTools = (options?: Options }); }; +export const updateAgentProvider = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/agent/update_provider', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +export const updateRouterToolSelector = (options?: Options) => { + return (options?.client ?? _heyApiClient).post({ + url: '/agent/update_router_tool_selector', + ...options + }); +}; + export const readAllConfig = (options?: Options) => { return (options?.client ?? _heyApiClient).get({ url: '/config', diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index eeb5a691b218..6cb9687f03c9 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -143,6 +143,18 @@ export type Envs = { [key: string]: string; }; +export type ErrorResponse = { + error: string; +}; + +export type ExtendPromptRequest = { + extension: string; +}; + +export type ExtendPromptResponse = { + success: boolean; +}; + /** * Represents the different types of MCP extensions that can be added to the manager */ @@ -273,6 +285,10 @@ export type FrontendToolRequest = { }; }; +export type GetToolsQuery = { + extension_name?: string | null; +}; + export type ImageContent = { annotations?: Annotations | { [key: string]: unknown; @@ -576,6 +592,10 @@ export type ScheduledJob = { source: string; }; +export type SessionConfigRequest = { + response?: Response | null; +}; + export type SessionDisplayInfo = { accumulatedInputTokens?: number | null; accumulatedOutputTokens?: number | null; @@ -772,6 +792,11 @@ export type ToolResponse = { }; }; +export type UpdateProviderRequest = { + model?: string | null; + provider: string; +}; + export type UpdateScheduleRequest = { cron: string; }; @@ -798,17 +823,79 @@ export type AddSubRecipesErrors = { * Unauthorized - invalid secret key */ 401: unknown; + /** + * Agent not initialized + */ + 424: unknown; }; export type AddSubRecipesResponses = { /** - * added sub recipes to agent successfully + * Added sub recipes to agent successfully */ 200: AddSubRecipesResponse; }; export type AddSubRecipesResponse2 = AddSubRecipesResponses[keyof AddSubRecipesResponses]; +export type ExtendPromptData = { + body: ExtendPromptRequest; + path?: never; + query?: never; + url: '/agent/prompt'; +}; + +export type ExtendPromptErrors = { + /** + * Unauthorized - invalid secret key + */ + 401: unknown; + /** + * Agent not initialized + */ + 424: unknown; +}; + +export type ExtendPromptResponses = { + /** + * Extended system prompt successfully + */ + 200: ExtendPromptResponse; +}; + +export type ExtendPromptResponse2 = ExtendPromptResponses[keyof ExtendPromptResponses]; + +export type UpdateSessionConfigData = { + body: SessionConfigRequest; + path?: never; + query?: never; + url: '/agent/session_config'; +}; + +export type UpdateSessionConfigErrors = { + /** + * Unauthorized - invalid secret key + */ + 401: unknown; + /** + * Agent not initialized + */ + 424: unknown; + /** + * Internal server error + */ + 500: unknown; +}; + +export type UpdateSessionConfigResponses = { + /** + * Session config updated successfully + */ + 200: string; +}; + +export type UpdateSessionConfigResponse = UpdateSessionConfigResponses[keyof UpdateSessionConfigResponses]; + export type GetToolsData = { body?: never; path?: never; @@ -845,6 +932,70 @@ export type GetToolsResponses = { export type GetToolsResponse = GetToolsResponses[keyof GetToolsResponses]; +export type UpdateAgentProviderData = { + body: UpdateProviderRequest; + path?: never; + query?: never; + url: '/agent/update_provider'; +}; + +export type UpdateAgentProviderErrors = { + /** + * Bad request - missing or invalid parameters + */ + 400: unknown; + /** + * Unauthorized - invalid secret key + */ + 401: unknown; + /** + * Agent not initialized + */ + 424: unknown; + /** + * Internal server error + */ + 500: unknown; +}; + +export type UpdateAgentProviderResponses = { + /** + * Provider updated successfully + */ + 200: unknown; +}; + +export type UpdateRouterToolSelectorData = { + body?: never; + path?: never; + query?: never; + url: '/agent/update_router_tool_selector'; +}; + +export type UpdateRouterToolSelectorErrors = { + /** + * Unauthorized - invalid secret key + */ + 401: unknown; + /** + * Agent not initialized + */ + 424: unknown; + /** + * Internal server error + */ + 500: unknown; +}; + +export type UpdateRouterToolSelectorResponses = { + /** + * Tool selection strategy updated successfully + */ + 200: string; +}; + +export type UpdateRouterToolSelectorResponse = UpdateRouterToolSelectorResponses[keyof UpdateRouterToolSelectorResponses]; + export type ReadAllConfigData = { body?: never; path?: never; diff --git a/ui/desktop/src/components/common/ActivityHeatmap.tsx b/ui/desktop/src/components/common/ActivityHeatmap.tsx deleted file mode 100644 index e0b116ff406b..000000000000 --- a/ui/desktop/src/components/common/ActivityHeatmap.tsx +++ /dev/null @@ -1,276 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/Tooltip'; -import { getApiUrl } from '../../config'; - -interface ActivityHeatmapCell { - week: number; - day: number; - count: number; - date?: string; // Add date for better display in tooltips -} - -// Days of the week for labeling -const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; -// Number of weeks in a year -const WEEKS_IN_YEAR = 52; - -export function ActivityHeatmap() { - const [heatmapData, setHeatmapData] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [currentYear] = useState(new Date().getFullYear()); - - // Calculate the intensity for coloring cells - const getColorIntensity = (count: number, maxCount: number) => { - if (count === 0) return 'bg-background-muted/30'; - - const normalizedCount = count / maxCount; - - if (normalizedCount < 0.25) return 'bg-background-accent/20'; - if (normalizedCount < 0.5) return 'bg-background-accent/40'; - if (normalizedCount < 0.75) return 'bg-background-accent/60'; - return 'bg-background-accent/80'; - }; - - useEffect(() => { - const fetchHeatmapData = async () => { - try { - setLoading(true); - const response = await fetch(getApiUrl('/sessions/activity-heatmap'), { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - 'X-Secret-Key': await window.electron.getSecretKey(), - }, - }); - - if (!response.ok) { - throw new Error(`Failed to fetch heatmap data: ${response.status}`); - } - - const data = await response.json(); - setHeatmapData(data); - setError(null); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load heatmap data'); - } finally { - setLoading(false); - } - }; - - fetchHeatmapData(); - }, []); - - // Find the maximum count for scaling - const maxCount = Math.max( - 1, // Avoid division by zero - ...heatmapData.map((cell) => cell.count) - ); - - // Create a calendar grid from Jan 1st of current year to today - const prepareGridData = () => { - // Get current date - const now = new Date(); - const startOfYear = new Date(currentYear, 0, 1); // Jan 1st of current year - - // Calculate weeks to display - now showing full year (52 weeks) - // const weeksToDisplay = Math.ceil((daysSinceStartOfYear + getStartDayOfYear()) / 7); - const weeksToDisplay = WEEKS_IN_YEAR; - - // Create a map to lookup counts easily - const dataMap = new Map(); - heatmapData.forEach((cell) => { - dataMap.set(`${cell.week}-${cell.day}`, cell.count); - }); - - // Build the grid - const grid = []; - - // Fill grid with dates and activity data - for (let week = 0; week < weeksToDisplay; week++) { - const weekCells = []; - - for (let day = 0; day < 7; day++) { - // Convert week and day to a real date - const cellDate = new Date(startOfYear); - cellDate.setDate(cellDate.getDate() + week * 7 + day - getStartDayOfYear()); - - // Only include dates up to today for real data - const isFuture = cellDate > now; - - // Format the date string - const dateStr = cellDate.toLocaleDateString(undefined, { - month: 'short', - day: 'numeric', - }); - - // Get count from data if available - let count = 0; - - // Try to find a matching date in our data - // This requires matching the specific week number (from ISO week) and day - if (!isFuture) { - for (const cell of heatmapData) { - if (cell.week === getWeekNumber(cellDate) && cell.day === day) { - count = cell.count; - break; - } - } - } - - weekCells.push({ - week, - day, - count, - date: dateStr, - }); - } - - grid.push(weekCells); - } - - return grid; - }; - - // Helper to get day of week (0-6) of Jan 1st for current year - const getStartDayOfYear = () => { - return new Date(currentYear, 0, 1).getDay(); - }; - - // Get ISO week number for a date - const getWeekNumber = (date: Date) => { - const d = new Date(date); - d.setHours(0, 0, 0, 0); - d.setDate(d.getDate() + 3 - ((d.getDay() + 6) % 7)); - const week1 = new Date(d.getFullYear(), 0, 4); - return ( - 1 + - Math.round(((d.getTime() - week1.getTime()) / 86400000 - 3 + ((week1.getDay() + 6) % 7)) / 7) - ); - }; - - const grid = prepareGridData(); - - if (loading) { - return ( -
-
Loading activity data...
-
- ); - } - - if (error) { - return ( -
-
Error loading activity data
-
- ); - } - - // Get month labels - now showing all months - const getMonthLabels = () => { - const allMonths = [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec', - ]; - - return allMonths.map((month, i) => { - // Calculate position based on days in month and start day of year - const monthIndex = i; - const daysBeforeMonth = getDaysBeforeMonth(monthIndex); - const position = (daysBeforeMonth - getStartDayOfYear()) / 7; - - return ( -
- {month} -
- ); - }); - }; - - // Helper to calculate days before a month in current year - const getDaysBeforeMonth = (monthIndex: number) => { - const days = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; - // Adjust for leap year - if (monthIndex > 1 && isLeapYear(currentYear)) { - return days[monthIndex] + 1; - } - return days[monthIndex]; - }; - - // Helper to check if year is a leap year - const isLeapYear = (year: number) => { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; - }; - - return ( -
- {/* Month labels */} -
{getMonthLabels()}
- -
- {/* Day labels - now right-aligned */} -
- {DAYS.map((day, index) => ( -
- {index % 2 === 0 ? day : ''} -
- ))} -
- - {/* Grid - with smaller squares */} -
- {grid.map((week, weekIndex) => ( -
- {week.map((cell) => ( - - - -
- - - {cell.date ? ( -

- {cell.count} sessions on {cell.date} -

- ) : ( -

No data

- )} -
- - - ))} -
- ))} -
-
-
- ); -} diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index ea9721056f6c..387998f15004 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -1807,15 +1807,6 @@ app.whenReady().then(async () => { callback({ cancel: false, requestHeaders: details.requestHeaders }); }); - // Test error feature - only enabled with GOOSE_TEST_ERROR=true - if (process.env.GOOSE_TEST_ERROR === 'true') { - console.log('Test error feature enabled, will throw error in 5 seconds'); - setTimeout(() => { - console.log('Throwing test error now...'); - throw new Error('Test error: This is a simulated fatal error after 5 seconds'); - }, 5000); - } - // Create tray if enabled in settings const settings = loadSettings(); if (settings.showMenuBarIcon) { diff --git a/ui/desktop/src/utils/providerUtils.ts b/ui/desktop/src/utils/providerUtils.ts index 5a7a0fa20c02..49677131d854 100644 --- a/ui/desktop/src/utils/providerUtils.ts +++ b/ui/desktop/src/utils/providerUtils.ts @@ -15,6 +15,8 @@ import { RecipeParameter, SubRecipe, addExtension as apiAddExtension, + updateSessionConfig, + extendPrompt, } from '../api'; import { addSubRecipesToAgent } from '../recipe/add_sub_recipe_on_agent'; @@ -94,21 +96,13 @@ export const updateSystemPromptWithParameters = async ( const substitutedInstructions = substituteParameters(originalInstructions, recipeParameters); // Update the system prompt with substituted instructions - const response = await fetch(getApiUrl('/agent/prompt'), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Secret-Key': await window.electron.getSecretKey(), - }, - body: JSON.stringify({ + const response = await extendPrompt({ + body: { extension: `${desktopPromptBot}\nIMPORTANT instructions for you to operate as agent:\n${substitutedInstructions}`, - }), + }, }); - - if (!response.ok) { - console.warn( - `Failed to update system prompt with parameters: ${response.status} ${response.statusText}` - ); + if (response.error) { + console.warn(`Failed to update system prompt with parameters: ${response.error}`); } } catch (error) { console.error('Error updating system prompt with parameters:', error); @@ -245,18 +239,13 @@ export const initializeSystem = async ( } // Configure session with response config if present if (responseConfig?.json_schema) { - const sessionConfigResponse = await fetch(getApiUrl('/agent/session_config'), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Secret-Key': await window.electron.getSecretKey(), - }, - body: JSON.stringify({ + const sessionConfigResponse = await updateSessionConfig({ + body: { response: responseConfig, - }), + }, }); - if (!sessionConfigResponse.ok) { - console.warn(`Failed to configure session: ${sessionConfigResponse.statusText}`); + if (sessionConfigResponse.error) { + console.warn(`Failed to configure session: ${sessionConfigResponse.error}`); } }