fix: ensure retry-config and success-criteria are populated in openapi spec (#3575)

This commit is contained in:
Prem Pillai
2025-07-22 19:39:35 +10:00
committed by GitHub
parent 9101bfece7
commit f21b9017b8
7 changed files with 171 additions and 10 deletions

View File

@@ -132,6 +132,15 @@ jobs:
export CARGO_INCREMENTAL=0
cargo clippy --jobs 2 -- -D warnings
- name: Install Node.js Dependencies for OpenAPI Check
run: source ../../bin/activate-hermit && npm ci
working-directory: ui/desktop
- name: Check OpenAPI Schema is Up-to-Date
run: |
source ./bin/activate-hermit
just check-openapi-schema
desktop-lint:
name: Lint Electron Desktop App
runs-on: macos-latest
@@ -147,6 +156,7 @@ jobs:
run: source ../../bin/activate-hermit && npm run lint:check
working-directory: ui/desktop
# Faster Desktop App build for PRs only
bundle-desktop-unsigned:
uses: ./.github/workflows/bundle-desktop.yml

View File

@@ -184,6 +184,10 @@ run-server:
@echo "Running server..."
cargo run -p goose-server
# Check if OpenAPI schema is up-to-date
check-openapi-schema: generate-openapi
./scripts/check-openapi-schema.sh
# Generate OpenAPI specification without starting the UI
generate-openapi:
@echo "Generating OpenAPI schema..."
@@ -460,4 +464,3 @@ kotlin-example:
-Djna.library.path=$HOME/Development/goose/target/debug \
-classpath "example.jar:libs/kotlin-stdlib-1.9.0.jar:libs/kotlinx-coroutines-core-jvm-1.7.3.jar:libs/jna-5.13.0.jar" \
UsageKt

View File

@@ -393,6 +393,8 @@ derive_utoipa!(Annotations as AnnotationsSchema);
goose::recipe::RecipeParameterRequirement,
goose::recipe::Response,
goose::recipe::SubRecipe,
goose::agents::types::RetryConfig,
goose::agents::types::SuccessCheck,
))
)]
pub struct ApiDoc;

View File

@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::{mpsc, Mutex};
use utoipa::ToSchema;
/// Type alias for the tool result channel receiver
pub type ToolResultReceiver = Arc<Mutex<mpsc::Receiver<(String, ToolResult<Vec<Content>>)>>>;
@@ -16,7 +17,7 @@ pub const DEFAULT_RETRY_TIMEOUT_SECONDS: u64 = 300;
pub const DEFAULT_ON_FAILURE_TIMEOUT_SECONDS: u64 = 600;
/// Configuration for retry logic in recipe execution
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct RetryConfig {
/// Maximum number of retry attempts before giving up
pub max_retries: u32,
@@ -59,7 +60,7 @@ impl RetryConfig {
}
/// A single success check to validate recipe completion
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(tag = "type")]
pub enum SuccessCheck {
/// Execute a shell command and check its exit status

28
scripts/check-openapi-schema.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -e
# Check if OpenAPI schema is up-to-date
# This script generates the OpenAPI schema and compares it with the committed version
echo "🔍 Checking OpenAPI schema is up-to-date..."
# Check if the generated schema differs from the committed version
echo "🔍 Comparing generated schema with committed version..."
if ! git diff --exit-code ui/desktop/openapi.json ui/desktop/src/api/; then
echo ""
echo "❌ OpenAPI schema is out of date!"
echo ""
echo "The generated OpenAPI schema differs from the committed version."
echo "This usually means that API types were added or modified without updating the schema."
echo ""
echo "To fix this issue:"
echo "1. Run 'just generate-openapi' locally"
echo "2. Commit the changes to ui/desktop/openapi.json and ui/desktop/src/api/"
echo "3. Push your changes"
echo ""
echo "Changes detected:"
git diff ui/desktop/openapi.json ui/desktop/src/api/
exit 1
fi
echo "✅ OpenAPI schema is up-to-date"

View File

@@ -2183,7 +2183,7 @@
},
"Recipe": {
"type": "object",
"description": "A Recipe represents a personalized, user-generated agent configuration that defines\nspecific behaviors and capabilities within the Goose system.\n\n# Fields\n\n## Required Fields\n* `version` - Semantic version of the Recipe file format (defaults to \"1.0.0\")\n* `title` - Short, descriptive name of the Recipe\n* `description` - Detailed description explaining the Recipe's purpose and functionality\n* `Instructions` - Instructions that defines the Recipe's behavior\n\n## Optional Fields\n* `prompt` - the initial prompt to the session to start with\n* `extensions` - List of extension configurations required by the Recipe\n* `context` - Supplementary context information for the Recipe\n* `activities` - Activity labels that appear when loading the Recipe\n* `author` - Information about the Recipe's creator and metadata\n* `parameters` - Additional parameters for the Recipe\n* `response` - Response configuration including JSON schema validation\n\n# Example\n\n\nuse goose::recipe::Recipe;\n\n// Using the builder pattern\nlet recipe = Recipe::builder()\n.title(\"Example Agent\")\n.description(\"An example Recipe configuration\")\n.instructions(\"Act as a helpful assistant\")\n.build()\n.expect(\"Missing required fields\");\n\n// Or using struct initialization\nlet recipe = Recipe {\nversion: \"1.0.0\".to_string(),\ntitle: \"Example Agent\".to_string(),\ndescription: \"An example Recipe configuration\".to_string(),\ninstructions: Some(\"Act as a helpful assistant\".to_string()),\nprompt: None,\nextensions: None,\ncontext: None,\nactivities: None,\nauthor: None,\nsettings: None,\nparameters: None,\nresponse: None,\nsub_recipes: None,\n};\n",
"description": "A Recipe represents a personalized, user-generated agent configuration that defines\nspecific behaviors and capabilities within the Goose system.\n\n# Fields\n\n## Required Fields\n* `version` - Semantic version of the Recipe file format (defaults to \"1.0.0\")\n* `title` - Short, descriptive name of the Recipe\n* `description` - Detailed description explaining the Recipe's purpose and functionality\n* `Instructions` - Instructions that defines the Recipe's behavior\n\n## Optional Fields\n* `prompt` - the initial prompt to the session to start with\n* `extensions` - List of extension configurations required by the Recipe\n* `context` - Supplementary context information for the Recipe\n* `activities` - Activity labels that appear when loading the Recipe\n* `author` - Information about the Recipe's creator and metadata\n* `parameters` - Additional parameters for the Recipe\n* `response` - Response configuration including JSON schema validation\n* `retry` - Retry configuration for automated validation and recovery\n# Example\n\n\nuse goose::recipe::Recipe;\n\n// Using the builder pattern\nlet recipe = Recipe::builder()\n.title(\"Example Agent\")\n.description(\"An example Recipe configuration\")\n.instructions(\"Act as a helpful assistant\")\n.build()\n.expect(\"Missing required fields\");\n\n// Or using struct initialization\nlet recipe = Recipe {\nversion: \"1.0.0\".to_string(),\ntitle: \"Example Agent\".to_string(),\ndescription: \"An example Recipe configuration\".to_string(),\ninstructions: Some(\"Act as a helpful assistant\".to_string()),\nprompt: None,\nextensions: None,\ncontext: None,\nactivities: None,\nauthor: None,\nsettings: None,\nparameters: None,\nresponse: None,\nsub_recipes: None,\nretry: None,\n};\n",
"required": [
"title",
"description"
@@ -2244,6 +2244,14 @@
],
"nullable": true
},
"retry": {
"allOf": [
{
"$ref": "#/components/schemas/RetryConfig"
}
],
"nullable": true
},
"settings": {
"allOf": [
{
@@ -2373,6 +2381,48 @@
}
}
},
"RetryConfig": {
"type": "object",
"description": "Configuration for retry logic in recipe execution",
"required": [
"max_retries",
"checks"
],
"properties": {
"checks": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SuccessCheck"
},
"description": "List of success checks to validate recipe completion"
},
"max_retries": {
"type": "integer",
"format": "int32",
"description": "Maximum number of retry attempts before giving up",
"minimum": 0
},
"on_failure": {
"type": "string",
"description": "Optional shell command to run on failure for cleanup",
"nullable": true
},
"on_failure_timeout_seconds": {
"type": "integer",
"format": "int64",
"description": "Timeout in seconds for on_failure commands (default: 600 seconds)",
"nullable": true,
"minimum": 0
},
"timeout_seconds": {
"type": "integer",
"format": "int64",
"description": "Timeout in seconds for individual shell commands (default: 300 seconds)",
"nullable": true,
"minimum": 0
}
}
},
"Role": {
"oneOf": [
{
@@ -2685,6 +2735,34 @@
}
}
},
"SuccessCheck": {
"oneOf": [
{
"type": "object",
"description": "Execute a shell command and check its exit status",
"required": [
"command",
"type"
],
"properties": {
"command": {
"type": "string",
"description": "The shell command to execute"
},
"type": {
"type": "string",
"enum": [
"Shell"
]
}
}
}
],
"description": "A single success check to validate recipe completion",
"discriminator": {
"propertyName": "type"
}
},
"SummarizationRequested": {
"type": "object",
"required": [

View File

@@ -396,7 +396,7 @@ export type ProvidersResponse = {
* * `author` - Information about the Recipe's creator and metadata
* * `parameters` - Additional parameters for the Recipe
* * `response` - Response configuration including JSON schema validation
*
* * `retry` - Retry configuration for automated validation and recovery
* # Example
*
*
@@ -425,6 +425,7 @@ export type ProvidersResponse = {
* parameters: None,
* response: None,
* sub_recipes: None,
* retry: None,
* };
*
*/
@@ -438,6 +439,7 @@ export type Recipe = {
parameters?: Array<RecipeParameter> | null;
prompt?: string | null;
response?: Response | null;
retry?: RetryConfig | null;
settings?: Settings | null;
sub_recipes?: Array<SubRecipe> | null;
title: string;
@@ -474,6 +476,32 @@ export type Response = {
json_schema?: unknown;
};
/**
* Configuration for retry logic in recipe execution
*/
export type RetryConfig = {
/**
* List of success checks to validate recipe completion
*/
checks: Array<SuccessCheck>;
/**
* Maximum number of retry attempts before giving up
*/
max_retries: number;
/**
* Optional shell command to run on failure for cleanup
*/
on_failure?: string | null;
/**
* Timeout in seconds for on_failure commands (default: 600 seconds)
*/
on_failure_timeout_seconds?: number | null;
/**
* Timeout in seconds for individual shell commands (default: 300 seconds)
*/
timeout_seconds?: number | null;
};
export type Role = string;
export type RunNowResponse = {
@@ -602,6 +630,17 @@ export type SubRecipe = {
} | null;
};
/**
* Execute a shell command and check its exit status
*/
export type SuccessCheck = {
/**
* The shell command to execute
*/
command: string;
type: 'Shell';
};
export type SummarizationRequested = {
msg: string;
};