diff --git a/Cargo.lock b/Cargo.lock index b351444a38b5..7ed4711a34c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4279,12 +4279,14 @@ dependencies = [ "fs-err", "futures", "goose", + "goose-acp-macros", "goose-mcp", "goose-test-support", "http-body-util", "regex", "rmcp 0.15.0", "sacp", + "schemars 1.2.1", "serde", "serde_json", "tempfile", @@ -4299,6 +4301,15 @@ dependencies = [ "wiremock", ] +[[package]] +name = "goose-acp-macros" +version = "1.24.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "goose-cli" version = "1.24.0" diff --git a/crates/goose-acp-macros/Cargo.toml b/crates/goose-acp-macros/Cargo.toml new file mode 100644 index 000000000000..eeda799a4953 --- /dev/null +++ b/crates/goose-acp-macros/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "goose-acp-macros" +edition.workspace = true +version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +description.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = { version = "2", features = ["full", "extra-traits"] } + +[lints] +workspace = true diff --git a/crates/goose-acp-macros/src/lib.rs b/crates/goose-acp-macros/src/lib.rs new file mode 100644 index 000000000000..48fa06f99e51 --- /dev/null +++ b/crates/goose-acp-macros/src/lib.rs @@ -0,0 +1,284 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{ + parse_macro_input, FnArg, GenericArgument, ImplItem, ItemImpl, Lit, Pat, PathArguments, + ReturnType, Type, +}; + +/// Marks an impl block as containing `#[custom_method("...")]`-annotated handlers. +/// +/// Generates two methods on the impl: +/// +/// 1. `handle_custom_request` — a dispatcher that: +/// - Prefixes each method name with `_goose/` +/// - Parses JSON params into the handler's typed parameter (if any) +/// - Serializes the handler's return value to JSON +/// +/// 2. `custom_method_schemas` — returns a `Vec` with +/// JSON Schema for each method's params and response types. Types that +/// implement `schemars::JsonSchema` get a full schema; `serde_json::Value` +/// params/responses produce `None`. +/// +/// # Handler signatures +/// +/// Handlers may take zero or one parameter (beyond `&self`): +/// +/// ```ignore +/// // No params — called for requests with no/empty params +/// #[custom_method("session/list")] +/// async fn on_list_sessions(&self) -> Result { .. } +/// +/// // Typed params — JSON params auto-deserialized +/// #[custom_method("session/get")] +/// async fn on_get_session(&self, req: GetSessionRequest) -> Result { .. } +/// ``` +/// +/// The return type must be `Result` where `T: Serialize`. +#[proc_macro_attribute] +pub fn custom_methods(_attr: TokenStream, item: TokenStream) -> TokenStream { + let mut impl_block = parse_macro_input!(item as ItemImpl); + + let mut routes: Vec = Vec::new(); + + // Collect all #[custom_method("...")] annotations and strip them. + for item in &mut impl_block.items { + if let ImplItem::Fn(method) = item { + let mut route_name = None; + method.attrs.retain(|attr| { + if attr.path().is_ident("custom_method") { + if let Ok(meta_list) = attr.meta.require_list() { + if let Ok(Lit::Str(s)) = meta_list.parse_args::() { + route_name = Some(s.value()); + } + } + false // strip the attribute + } else { + true // keep other attributes + } + }); + + if let Some(name) = route_name { + let fn_ident = method.sig.ident.clone(); + + let param_type = extract_param_type(&method.sig); + let return_type = extract_return_type(&method.sig); + let ok_type = extract_result_ok_type(&method.sig); + + routes.push(Route { + method_name: name, + fn_ident, + param_type, + return_type, + ok_type, + }); + } + } + } + + // Generate the dispatch arms. + let arms: Vec<_> = routes + .iter() + .map(|route| { + let full_method = format!("_goose/{}", route.method_name); + let fn_ident = &route.fn_ident; + + match &route.param_type { + Some(_) => { + quote! { + #full_method => { + let req = serde_json::from_value(params) + .map_err(|e| sacp::Error::invalid_params().data(e.to_string()))?; + let result = self.#fn_ident(req).await?; + serde_json::to_value(&result) + .map_err(|e| sacp::Error::internal_error().data(e.to_string())) + } + } + } + None => { + quote! { + #full_method => { + let result = self.#fn_ident().await?; + serde_json::to_value(&result) + .map_err(|e| sacp::Error::internal_error().data(e.to_string())) + } + } + } + } + }) + .collect(); + + // Generate schema entries for each route using SchemaGenerator for $ref dedup. + let schema_entries: Vec<_> = routes + .iter() + .map(|route| { + let full_method = format!("_goose/{}", route.method_name); + + let params_expr = if let Some(pt) = &route.param_type { + if is_json_value(pt) { + quote! { None } + } else { + quote! { Some(generator.subschema_for::<#pt>()) } + } + } else { + quote! { None } + }; + + let response_expr = if let Some(ok_ty) = &route.ok_type { + if is_json_value(ok_ty) { + quote! { None } + } else { + quote! { Some(generator.subschema_for::<#ok_ty>()) } + } + } else { + quote! { None } + }; + + let params_name_expr = if let Some(pt) = &route.param_type { + if is_json_value(pt) { + quote! { None } + } else { + let name = type_name(pt); + quote! { Some(#name.to_string()) } + } + } else { + quote! { None } + }; + + let response_name_expr = if let Some(ok_ty) = &route.ok_type { + if is_json_value(ok_ty) { + quote! { None } + } else { + let name = type_name(ok_ty); + quote! { Some(#name.to_string()) } + } + } else { + quote! { None } + }; + + quote! { + crate::custom_requests::CustomMethodSchema { + method: #full_method.to_string(), + params_schema: #params_expr, + params_type_name: #params_name_expr, + response_schema: #response_expr, + response_type_name: #response_name_expr, + } + } + }) + .collect(); + + // Generate the handle_custom_request method. + let dispatcher = quote! { + async fn handle_custom_request( + &self, + method: &str, + params: serde_json::Value, + ) -> Result { + match method { + #(#arms)* + _ => Err(sacp::Error::method_not_found()), + } + } + }; + + // Generate the custom_method_schemas method. + let schemas_fn = quote! { + pub fn custom_method_schemas(generator: &mut schemars::SchemaGenerator) -> Vec { + vec![ + #(#schema_entries),* + ] + } + }; + + // Append the generated methods to the impl block. + let dispatcher_item: ImplItem = + syn::parse2(dispatcher).expect("generated dispatcher must parse"); + impl_block.items.push(dispatcher_item); + + let schemas_item: ImplItem = syn::parse2(schemas_fn).expect("generated schemas fn must parse"); + impl_block.items.push(schemas_item); + + TokenStream::from(quote! { #impl_block }) +} + +struct Route { + method_name: String, + fn_ident: syn::Ident, + param_type: Option, + #[allow(dead_code)] + return_type: Option, + ok_type: Option, +} + +/// Extract the type of the first non-self parameter, if any. +fn extract_param_type(sig: &syn::Signature) -> Option { + for input in &sig.inputs { + if let FnArg::Typed(pat_type) = input { + if let Pat::Ident(pat_ident) = &*pat_type.pat { + if pat_ident.ident == "self" { + continue; + } + } + return Some((*pat_type.ty).clone()); + } + } + None +} + +/// Extract the full return type (e.g. `Result`). +fn extract_return_type(sig: &syn::Signature) -> Option { + if let ReturnType::Type(_, ty) = &sig.output { + Some((**ty).clone()) + } else { + None + } +} + +/// Extract `T` from `Result` in the return type. +fn extract_result_ok_type(sig: &syn::Signature) -> Option { + let ty = match &sig.output { + ReturnType::Type(_, ty) => ty, + _ => return None, + }; + + // Peel through the type to find a path ending in `Result`. + if let Type::Path(type_path) = ty.as_ref() { + let last_seg = type_path.path.segments.last()?; + if last_seg.ident == "Result" { + if let PathArguments::AngleBracketed(args) = &last_seg.arguments { + // First generic argument is the Ok type. + if let Some(GenericArgument::Type(ok_ty)) = args.args.first() { + return Some(ok_ty.clone()); + } + } + } + } + None +} + +/// Extract the last segment name from a type path (e.g. `GetSessionRequest` from +/// `crate::custom_requests::GetSessionRequest` or just `GetSessionRequest`). +fn type_name(ty: &Type) -> String { + if let Type::Path(type_path) = ty { + if let Some(seg) = type_path.path.segments.last() { + return seg.ident.to_string(); + } + } + quote::quote!(#ty).to_string() +} + +/// Check if a type is `serde_json::Value` (matches `Value` or `serde_json::Value`). +fn is_json_value(ty: &Type) -> bool { + if let Type::Path(type_path) = ty { + let segments: Vec<_> = type_path + .path + .segments + .iter() + .map(|s| s.ident.to_string()) + .collect(); + let strs: Vec<&str> = segments.iter().map(|s| s.as_str()).collect(); + matches!(strs.as_slice(), ["serde_json", "Value"] | ["Value"]) + } else { + false + } +} diff --git a/crates/goose-acp/Cargo.toml b/crates/goose-acp/Cargo.toml index 261920bcd4aa..69be0e5af957 100644 --- a/crates/goose-acp/Cargo.toml +++ b/crates/goose-acp/Cargo.toml @@ -11,6 +11,10 @@ description.workspace = true name = "goose-acp-server" path = "src/bin/server.rs" +[[bin]] +name = "generate-acp-schema" +path = "src/bin/generate_acp_schema.rs" + [lints] workspace = true @@ -33,13 +37,15 @@ url = { workspace = true } # HTTP server dependencies axum = { workspace = true, features = ["ws"] } clap = { workspace = true } -serde = { workspace = true } +serde = { workspace = true, features = ["derive"] } tower-http = { workspace = true, features = ["cors"] } tracing-subscriber = { workspace = true, features = ["env-filter", "json"] } async-stream = { workspace = true } bytes = { workspace = true } http-body-util = "0.1.3" uuid = { workspace = true, features = ["v7"] } +schemars = { workspace = true, features = ["derive"] } +goose-acp-macros = { version = "1.24.0", path = "../goose-acp-macros" } [dev-dependencies] assert-json-diff = "2.0.2" diff --git a/crates/goose-acp/acp-meta.json b/crates/goose-acp/acp-meta.json new file mode 100644 index 000000000000..cbd9dd62f111 --- /dev/null +++ b/crates/goose-acp/acp-meta.json @@ -0,0 +1,59 @@ +{ + "methods": [ + { + "method": "extensions/add", + "requestType": "AddExtensionRequest", + "responseType": "EmptyResponse" + }, + { + "method": "extensions/remove", + "requestType": "RemoveExtensionRequest", + "responseType": "EmptyResponse" + }, + { + "method": "tools", + "requestType": "GetToolsRequest", + "responseType": "GetToolsResponse" + }, + { + "method": "resource/read", + "requestType": "ReadResourceRequest", + "responseType": "ReadResourceResponse" + }, + { + "method": "working_dir/update", + "requestType": "UpdateWorkingDirRequest", + "responseType": "EmptyResponse" + }, + { + "method": "session/list", + "requestType": null, + "responseType": "ListSessionsResponse" + }, + { + "method": "session/get", + "requestType": "GetSessionRequest", + "responseType": "GetSessionResponse" + }, + { + "method": "session/delete", + "requestType": "DeleteSessionRequest", + "responseType": "EmptyResponse" + }, + { + "method": "session/export", + "requestType": "ExportSessionRequest", + "responseType": "ExportSessionResponse" + }, + { + "method": "session/import", + "requestType": "ImportSessionRequest", + "responseType": "ImportSessionResponse" + }, + { + "method": "config/extensions", + "requestType": null, + "responseType": "GetExtensionsResponse" + } + ] +} diff --git a/crates/goose-acp/acp-schema.json b/crates/goose-acp/acp-schema.json new file mode 100644 index 000000000000..f090ccf919cf --- /dev/null +++ b/crates/goose-acp/acp-schema.json @@ -0,0 +1,520 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "GooseExtensions", + "$defs": { + "AddExtensionRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string" + }, + "config": { + "description": "Extension configuration (see ExtensionConfig variants: Stdio, StreamableHttp, Builtin, Platform)." + } + }, + "required": [ + "session_id", + "config" + ], + "description": "Add an extension to an active session.\nMethod: `_agent/extensions/add`", + "x-side": "agent", + "x-method": "extensions/add" + }, + "EmptyResponse": { + "type": "object", + "description": "Empty success response for operations that return no data.", + "x-side": "agent" + }, + "RemoveExtensionRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "session_id", + "name" + ], + "description": "Remove an extension from an active session.\nMethod: `_agent/extensions/remove`", + "x-side": "agent", + "x-method": "extensions/remove" + }, + "GetToolsRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string" + } + }, + "required": [ + "session_id" + ], + "description": "List all tools available in a session.\nMethod: `_agent/tools`", + "x-side": "agent", + "x-method": "tools" + }, + "GetToolsResponse": { + "type": "object", + "properties": { + "tools": { + "type": "array", + "items": true, + "description": "Array of tool info objects with `name`, `description`, `parameters`, and optional `permission`." + } + }, + "required": [ + "tools" + ], + "x-side": "agent", + "x-method": "tools" + }, + "ReadResourceRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "extension_name": { + "type": "string" + } + }, + "required": [ + "session_id", + "uri", + "extension_name" + ], + "description": "Read a resource from an extension.\nMethod: `_agent/resource/read`", + "x-side": "agent", + "x-method": "resource/read" + }, + "ReadResourceResponse": { + "type": "object", + "properties": { + "result": { + "description": "The resource result from the extension (MCP ReadResourceResult)." + } + }, + "required": [ + "result" + ], + "x-side": "agent", + "x-method": "resource/read" + }, + "UpdateWorkingDirRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string" + }, + "working_dir": { + "type": "string" + } + }, + "required": [ + "session_id", + "working_dir" + ], + "description": "Update the working directory for a session.\nMethod: `_agent/working_dir/update`", + "x-side": "agent", + "x-method": "working_dir/update" + }, + "ListSessionsResponse": { + "type": "object", + "properties": { + "sessions": { + "type": "array", + "items": true + } + }, + "required": [ + "sessions" + ], + "description": "List all sessions.\nMethod: `_session/list`", + "x-side": "agent", + "x-method": "session/list" + }, + "GetSessionRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string" + }, + "include_messages": { + "type": "boolean", + "default": false + } + }, + "required": [ + "session_id" + ], + "description": "Get a session by ID.\nMethod: `_session/get`", + "x-side": "agent", + "x-method": "session/get" + }, + "GetSessionResponse": { + "type": "object", + "properties": { + "session": { + "description": "The session object with id, name, working_dir, timestamps, tokens, etc." + } + }, + "required": [ + "session" + ], + "description": "Get a session response.", + "x-side": "agent", + "x-method": "session/get" + }, + "DeleteSessionRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string" + } + }, + "required": [ + "session_id" + ], + "description": "Delete a session.\nMethod: `_session/delete`", + "x-side": "agent", + "x-method": "session/delete" + }, + "ExportSessionRequest": { + "type": "object", + "properties": { + "session_id": { + "type": "string" + } + }, + "required": [ + "session_id" + ], + "description": "Export a session as a JSON string.\nMethod: `_session/export`", + "x-side": "agent", + "x-method": "session/export" + }, + "ExportSessionResponse": { + "type": "object", + "properties": { + "data": { + "type": "string" + } + }, + "required": [ + "data" + ], + "x-side": "agent", + "x-method": "session/export" + }, + "ImportSessionRequest": { + "type": "object", + "properties": { + "data": { + "type": "string" + } + }, + "required": [ + "data" + ], + "description": "Import a session from a JSON string.\nMethod: `_session/import`", + "x-side": "agent", + "x-method": "session/import" + }, + "ImportSessionResponse": { + "type": "object", + "properties": { + "session": { + "description": "The imported session object." + } + }, + "required": [ + "session" + ], + "x-side": "agent", + "x-method": "session/import" + }, + "GetExtensionsResponse": { + "type": "object", + "properties": { + "extensions": { + "type": "array", + "items": true, + "description": "Array of ExtensionEntry objects with `enabled` flag and config details." + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "extensions", + "warnings" + ], + "description": "List configured extensions and any warnings.\nMethod: `_config/extensions`", + "x-side": "agent", + "x-method": "config/extensions" + }, + "ExtRequest": { + "properties": { + "id": { + "type": "string" + }, + "method": { + "type": "string" + }, + "params": { + "anyOf": [ + { + "anyOf": [ + { + "allOf": [ + { + "$ref": "#/$defs/AddExtensionRequest" + } + ], + "description": "Params for _goose/extensions/add", + "title": "AddExtensionRequest" + }, + { + "allOf": [ + { + "$ref": "#/$defs/RemoveExtensionRequest" + } + ], + "description": "Params for _goose/extensions/remove", + "title": "RemoveExtensionRequest" + }, + { + "allOf": [ + { + "$ref": "#/$defs/GetToolsRequest" + } + ], + "description": "Params for _goose/tools", + "title": "GetToolsRequest" + }, + { + "allOf": [ + { + "$ref": "#/$defs/ReadResourceRequest" + } + ], + "description": "Params for _goose/resource/read", + "title": "ReadResourceRequest" + }, + { + "allOf": [ + { + "$ref": "#/$defs/UpdateWorkingDirRequest" + } + ], + "description": "Params for _goose/working_dir/update", + "title": "UpdateWorkingDirRequest" + }, + { + "allOf": [ + { + "$ref": "#/$defs/GetSessionRequest" + } + ], + "description": "Params for _goose/session/get", + "title": "GetSessionRequest" + }, + { + "allOf": [ + { + "$ref": "#/$defs/DeleteSessionRequest" + } + ], + "description": "Params for _goose/session/delete", + "title": "DeleteSessionRequest" + }, + { + "allOf": [ + { + "$ref": "#/$defs/ExportSessionRequest" + } + ], + "description": "Params for _goose/session/export", + "title": "ExportSessionRequest" + }, + { + "allOf": [ + { + "$ref": "#/$defs/ImportSessionRequest" + } + ], + "description": "Params for _goose/session/import", + "title": "ImportSessionRequest" + } + ] + }, + { + "description": "Untyped params", + "type": [ + "object", + "null" + ] + } + ] + } + }, + "required": [ + "id", + "method" + ], + "type": "object", + "x-docs-ignore": true + }, + "ExtResponse": { + "anyOf": [ + { + "properties": { + "id": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "anyOf": [ + { + "allOf": [ + { + "$ref": "#/$defs/EmptyResponse" + } + ], + "title": "EmptyResponse" + }, + { + "allOf": [ + { + "$ref": "#/$defs/GetToolsResponse" + } + ], + "title": "GetToolsResponse" + }, + { + "allOf": [ + { + "$ref": "#/$defs/ReadResourceResponse" + } + ], + "title": "ReadResourceResponse" + }, + { + "allOf": [ + { + "$ref": "#/$defs/ListSessionsResponse" + } + ], + "title": "ListSessionsResponse" + }, + { + "allOf": [ + { + "$ref": "#/$defs/GetSessionResponse" + } + ], + "title": "GetSessionResponse" + }, + { + "allOf": [ + { + "$ref": "#/$defs/ExportSessionResponse" + } + ], + "title": "ExportSessionResponse" + }, + { + "allOf": [ + { + "$ref": "#/$defs/ImportSessionResponse" + } + ], + "title": "ImportSessionResponse" + }, + { + "allOf": [ + { + "$ref": "#/$defs/GetExtensionsResponse" + } + ], + "title": "GetExtensionsResponse" + } + ] + }, + { + "description": "Untyped result" + } + ] + } + }, + "required": [ + "id" + ], + "title": "Success", + "type": "object" + }, + { + "properties": { + "error": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "data": {} + }, + "required": [ + "code", + "message" + ] + }, + "id": { + "type": "string" + } + }, + "required": [ + "id", + "error" + ], + "title": "Error", + "type": "object" + } + ], + "x-docs-ignore": true + } + }, + "anyOf": [ + { + "allOf": [ + { + "$ref": "#/$defs/ExtRequest" + } + ], + "description": "Extension request (client → agent)", + "title": "Request" + }, + { + "allOf": [ + { + "$ref": "#/$defs/ExtResponse" + } + ], + "description": "Extension response (agent → client)", + "title": "Response" + } + ] +} diff --git a/crates/goose-acp/src/bin/generate_acp_schema.rs b/crates/goose-acp/src/bin/generate_acp_schema.rs new file mode 100644 index 000000000000..688d4f4a7046 --- /dev/null +++ b/crates/goose-acp/src/bin/generate_acp_schema.rs @@ -0,0 +1,188 @@ +use goose_acp::server::GooseAcpAgent; +use schemars::SchemaGenerator; +use serde_json::{json, Map, Value}; +use std::collections::{BTreeSet, HashMap}; +use std::env; +use std::fs; +use std::path::PathBuf; + +fn main() { + let mut generator = SchemaGenerator::default(); + let methods = GooseAcpAgent::custom_method_schemas(&mut generator); + + // Collect $defs from the generator (all types referenced via subschema_for). + let mut defs: Map = generator + .take_definitions(true) + .into_iter() + .map(|(k, v)| (k, serde_json::to_value(v).unwrap_or(json!({})))) + .collect(); + + // Strip the `_goose/` prefix to get the bare method name for x-method. + fn bare_method(full: &str) -> &str { + full.strip_prefix("_goose/").unwrap_or(full) + } + + // Track which types map to which methods so we can detect shared types. + let mut type_methods: HashMap> = HashMap::new(); + for m in &methods { + let method = bare_method(&m.method).to_string(); + if let Some(name) = &m.params_type_name { + type_methods + .entry(name.clone()) + .or_default() + .push(method.clone()); + } + if let Some(name) = &m.response_type_name { + type_methods + .entry(name.clone()) + .or_default() + .push(method.clone()); + } + } + + // Annotate $defs entries with x-method/x-side. Only set x-method for types + // used by exactly one method (shared types like EmptyResponse skip x-method). + for (name, methods_list) in &type_methods { + if let Some(def) = defs.get_mut(name) { + if let Some(obj) = def.as_object_mut() { + obj.insert("x-side".into(), json!("agent")); + if methods_list.len() == 1 { + obj.insert("x-method".into(), json!(methods_list[0])); + } + } + } + } + + // Build ExtRequest.params and ExtResponse.result anyOf arrays, + // deduplicating response variants (e.g. EmptyResponse appears once). + let mut request_variants: Vec = Vec::new(); + let mut response_variants: Vec = Vec::new(); + let mut seen_response_types: BTreeSet = BTreeSet::new(); + + for m in &methods { + if let Some(name) = &m.params_type_name { + request_variants.push(json!({ + "allOf": [{ "$ref": format!("#/$defs/{name}") }], + "description": format!("Params for {}", m.method), + "title": name, + })); + } + + if let Some(name) = &m.response_type_name { + if seen_response_types.insert(name.clone()) { + response_variants.push(json!({ + "allOf": [{ "$ref": format!("#/$defs/{name}") }], + "title": name, + })); + } + } + } + + // Build ExtRequest — mirrors AgentRequest structure. + defs.insert( + "ExtRequest".into(), + json!({ + "properties": { + "id": { "type": "string" }, + "method": { "type": "string" }, + "params": { + "anyOf": [ + { "anyOf": request_variants }, + { "description": "Untyped params", "type": ["object", "null"] }, + ] + } + }, + "required": ["id", "method"], + "type": "object", + "x-docs-ignore": true, + }), + ); + + // Build ExtResponse — mirrors AgentResponse structure. + defs.insert( + "ExtResponse".into(), + json!({ + "anyOf": [ + { + "properties": { + "id": { "type": "string" }, + "result": { + "anyOf": [ + { "anyOf": response_variants }, + { "description": "Untyped result" }, + ] + } + }, + "required": ["id"], + "title": "Success", + "type": "object", + }, + { + "properties": { + "error": { + "type": "object", + "properties": { + "code": { "type": "integer" }, + "message": { "type": "string" }, + "data": {} + }, + "required": ["code", "message"], + }, + "id": { "type": "string" }, + }, + "required": ["id", "error"], + "title": "Error", + "type": "object", + } + ], + "x-docs-ignore": true, + }), + ); + + // Assemble the root schema document. + let root = json!({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "GooseExtensions", + "$defs": defs, + "anyOf": [ + { + "allOf": [{ "$ref": "#/$defs/ExtRequest" }], + "description": "Extension request (client → agent)", + "title": "Request", + }, + { + "allOf": [{ "$ref": "#/$defs/ExtResponse" }], + "description": "Extension response (agent → client)", + "title": "Response", + } + ], + }); + + let json_str = serde_json::to_string_pretty(&root).expect("failed to serialize schema"); + + let package_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let package_path = PathBuf::from(&package_dir); + + let schema_path = package_path.join("acp-schema.json"); + fs::write(&schema_path, format!("{json_str}\n")).expect("failed to write schema file"); + eprintln!("Generated ACP schema at {}", schema_path.display()); + + // Build meta.json with method→type mappings (consumed by TS codegen). + let method_entries: Vec = methods + .iter() + .map(|m| { + json!({ + "method": bare_method(&m.method), + "requestType": m.params_type_name, + "responseType": m.response_type_name, + }) + }) + .collect(); + let meta = json!({ "methods": method_entries }); + let meta_str = serde_json::to_string_pretty(&meta).expect("failed to serialize meta"); + let meta_path = package_path.join("acp-meta.json"); + fs::write(&meta_path, format!("{meta_str}\n")).expect("failed to write meta file"); + eprintln!("Generated ACP meta at {}", meta_path.display()); + + println!("{json_str}"); +} diff --git a/crates/goose-acp/src/custom_requests.rs b/crates/goose-acp/src/custom_requests.rs new file mode 100644 index 000000000000..04025a4353fd --- /dev/null +++ b/crates/goose-acp/src/custom_requests.rs @@ -0,0 +1,130 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Schema descriptor for a single custom method, produced by the +/// `#[custom_methods]` macro's generated `custom_method_schemas()` function. +/// +/// `params_schema` / `response_schema` hold `$ref` pointers or inline schemas +/// produced by `SchemaGenerator::subschema_for`. All referenced types are +/// collected in the generator's `$defs` map. +/// +/// `params_type_name` / `response_type_name` carry the Rust struct name so the +/// binary can key `$defs` entries and annotate them with `x-method` / `x-side`. +#[derive(Debug, Serialize)] +pub struct CustomMethodSchema { + pub method: String, + pub params_schema: Option, + pub params_type_name: Option, + pub response_schema: Option, + pub response_type_name: Option, +} + +/// Add an extension to an active session. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct AddExtensionRequest { + pub session_id: String, + /// Extension configuration (see ExtensionConfig variants: Stdio, StreamableHttp, Builtin, Platform). + pub config: serde_json::Value, +} + +/// Remove an extension from an active session. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct RemoveExtensionRequest { + pub session_id: String, + pub name: String, +} + +/// List all tools available in a session. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct GetToolsRequest { + pub session_id: String, +} + +#[derive(Debug, Serialize, JsonSchema)] +pub struct GetToolsResponse { + /// Array of tool info objects with `name`, `description`, `parameters`, and optional `permission`. + pub tools: Vec, +} + +/// Read a resource from an extension. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct ReadResourceRequest { + pub session_id: String, + pub uri: String, + pub extension_name: String, +} + +#[derive(Debug, Serialize, JsonSchema)] +pub struct ReadResourceResponse { + /// The resource result from the extension (MCP ReadResourceResult). + pub result: serde_json::Value, +} + +/// Update the working directory for a session. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct UpdateWorkingDirRequest { + pub session_id: String, + pub working_dir: String, +} + +/// Get a session by ID. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct GetSessionRequest { + pub session_id: String, + #[serde(default)] + pub include_messages: bool, +} + +/// Get a session response. +#[derive(Debug, Serialize, JsonSchema)] +pub struct GetSessionResponse { + /// The session object with id, name, working_dir, timestamps, tokens, etc. + pub session: serde_json::Value, +} + +/// List all sessions. +#[derive(Debug, Serialize, JsonSchema)] +pub struct ListSessionsResponse { + pub sessions: Vec, +} + +/// Delete a session. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct DeleteSessionRequest { + pub session_id: String, +} + +/// Export a session as a JSON string. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct ExportSessionRequest { + pub session_id: String, +} + +#[derive(Debug, Serialize, JsonSchema)] +pub struct ExportSessionResponse { + pub data: String, +} + +/// Import a session from a JSON string. +#[derive(Debug, Deserialize, JsonSchema)] +pub struct ImportSessionRequest { + pub data: String, +} + +#[derive(Debug, Serialize, JsonSchema)] +pub struct ImportSessionResponse { + /// The imported session object. + pub session: serde_json::Value, +} + +/// List configured extensions and any warnings. +#[derive(Debug, Serialize, JsonSchema)] +pub struct GetExtensionsResponse { + /// Array of ExtensionEntry objects with `enabled` flag and config details. + pub extensions: Vec, + pub warnings: Vec, +} + +/// Empty success response for operations that return no data. +#[derive(Debug, Serialize, JsonSchema)] +pub struct EmptyResponse {} diff --git a/crates/goose-acp/src/lib.rs b/crates/goose-acp/src/lib.rs index e2830d61237b..4772a8e47ad0 100644 --- a/crates/goose-acp/src/lib.rs +++ b/crates/goose-acp/src/lib.rs @@ -1,6 +1,7 @@ #![recursion_limit = "256"] mod adapters; +pub mod custom_requests; pub mod server; pub mod server_factory; pub mod transport; diff --git a/crates/goose-acp/src/server.rs b/crates/goose-acp/src/server.rs index 197fcc929cea..ecdc9742401d 100644 --- a/crates/goose-acp/src/server.rs +++ b/crates/goose-acp/src/server.rs @@ -1,3 +1,4 @@ +use crate::custom_requests::*; use anyhow::Result; use fs_err as fs; use goose::agents::extension::{Envs, PLATFORM_EXTENSIONS}; @@ -17,6 +18,7 @@ use goose::providers::base::Provider; use goose::providers::provider_registry::ProviderConstructor; use goose::session::session_manager::SessionType; use goose::session::{Session, SessionManager}; +use goose_acp_macros::custom_methods; use rmcp::model::{CallToolResult, RawContent, ResourceContents, Role}; use sacp::schema::{ AgentCapabilities, AuthMethod, AuthenticateRequest, AuthenticateResponse, BlobResourceContents, @@ -984,6 +986,192 @@ impl GooseAcpAgent { } } +#[custom_methods] +impl GooseAcpAgent { + #[custom_method("extensions/add")] + async fn on_add_extension( + &self, + req: AddExtensionRequest, + ) -> Result { + let config: ExtensionConfig = serde_json::from_value(req.config) + .map_err(|e| sacp::Error::invalid_params().data(format!("bad config: {e}")))?; + let agent = self.get_agent_for_session(&req.session_id).await?; + agent + .add_extension(config, &req.session_id) + .await + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + Ok(EmptyResponse {}) + } + + #[custom_method("extensions/remove")] + async fn on_remove_extension( + &self, + req: RemoveExtensionRequest, + ) -> Result { + let agent = self.get_agent_for_session(&req.session_id).await?; + agent + .remove_extension(&req.name, &req.session_id) + .await + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + Ok(EmptyResponse {}) + } + + #[custom_method("tools")] + async fn on_get_tools(&self, req: GetToolsRequest) -> Result { + let agent = self.get_agent_for_session(&req.session_id).await?; + let tools = agent.list_tools(&req.session_id, None).await; + let tools_json = tools + .into_iter() + .map(|t| serde_json::to_value(&t)) + .collect::, _>>() + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + Ok(GetToolsResponse { tools: tools_json }) + } + + #[custom_method("resource/read")] + async fn on_read_resource( + &self, + req: ReadResourceRequest, + ) -> Result { + let agent = self.get_agent_for_session(&req.session_id).await?; + let cancel_token = CancellationToken::new(); + let result = agent + .extension_manager + .read_resource(&req.session_id, &req.uri, &req.extension_name, cancel_token) + .await + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + let result_json = serde_json::to_value(&result) + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + Ok(ReadResourceResponse { + result: result_json, + }) + } + + #[custom_method("working_dir/update")] + async fn on_update_working_dir( + &self, + req: UpdateWorkingDirRequest, + ) -> Result { + let working_dir = req.working_dir.trim().to_string(); + if working_dir.is_empty() { + return Err(sacp::Error::invalid_params().data("working directory cannot be empty")); + } + let path = std::path::PathBuf::from(&working_dir); + if !path.exists() || !path.is_dir() { + return Err(sacp::Error::invalid_params().data("invalid directory path")); + } + self.session_manager + .update(&req.session_id) + .working_dir(path) + .apply() + .await + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + Ok(EmptyResponse {}) + } + + #[custom_method("session/list")] + async fn on_list_sessions(&self) -> Result { + let sessions = self + .session_manager + .list_sessions() + .await + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + let sessions_json = sessions + .into_iter() + .map(|s| serde_json::to_value(&s)) + .collect::, _>>() + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + Ok(ListSessionsResponse { + sessions: sessions_json, + }) + } + + #[custom_method("session/get")] + async fn on_get_session( + &self, + req: GetSessionRequest, + ) -> Result { + let session = self + .session_manager + .get_session(&req.session_id, req.include_messages) + .await + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + let session_json = serde_json::to_value(&session) + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + Ok(GetSessionResponse { + session: session_json, + }) + } + + #[custom_method("session/delete")] + async fn on_delete_session( + &self, + req: DeleteSessionRequest, + ) -> Result { + self.session_manager + .delete_session(&req.session_id) + .await + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + Ok(EmptyResponse {}) + } + + #[custom_method("session/export")] + async fn on_export_session( + &self, + req: ExportSessionRequest, + ) -> Result { + let data = self + .session_manager + .export_session(&req.session_id) + .await + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + Ok(ExportSessionResponse { data }) + } + + #[custom_method("session/import")] + async fn on_import_session( + &self, + req: ImportSessionRequest, + ) -> Result { + let session = self + .session_manager + .import_session(&req.data) + .await + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + let session_json = serde_json::to_value(&session) + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + Ok(ImportSessionResponse { + session: session_json, + }) + } + + #[custom_method("config/extensions")] + async fn on_get_extensions(&self) -> Result { + let extensions = goose::config::extensions::get_all_extensions(); + let warnings = goose::config::extensions::get_warnings(); + let extensions_json = extensions + .into_iter() + .map(|e| serde_json::to_value(&e)) + .collect::, _>>() + .map_err(|e| sacp::Error::internal_error().data(e.to_string()))?; + Ok(GetExtensionsResponse { + extensions: extensions_json, + warnings, + }) + } + + async fn get_agent_for_session(&self, session_id: &str) -> Result, sacp::Error> { + self.sessions + .lock() + .await + .get(session_id) + .map(|s| Arc::clone(&s.agent)) + .ok_or_else(|| { + sacp::Error::invalid_params().data(format!("no active session: {session_id}")) + }) + } +} + pub struct GooseAcpHandler { pub agent: Arc, } @@ -1051,7 +1239,9 @@ impl JrMessageHandler for GooseAcpHandler { self.agent.on_cancel(notif).await }) .await - // HACK: sacp doesn't support session/set_model yet, so we handle it as untyped JSON. + // Handle methods not yet in the sacp typed API. + // - session/set_model: typed support pending in sacp + // - _: custom requests that will eventually route to goose-server .otherwise({ let agent = self.agent.clone(); |message: MessageCx| async move { @@ -1069,6 +1259,13 @@ impl JrMessageHandler for GooseAcpHandler { request_cx.respond(json)?; Ok(()) } + MessageCx::Request(req, request_cx) if req.method.starts_with('_') => { + match agent.handle_custom_request(&req.method, req.params).await { + Ok(json) => request_cx.respond(json)?, + Err(e) => request_cx.respond_with_error(e)?, + } + Ok(()) + } _ => Err(sacp::Error::method_not_found()), } } diff --git a/crates/goose-acp/tests/custom_requests_test.rs b/crates/goose-acp/tests/custom_requests_test.rs new file mode 100644 index 000000000000..0f7c7678a929 --- /dev/null +++ b/crates/goose-acp/tests/custom_requests_test.rs @@ -0,0 +1,170 @@ +#[allow(dead_code)] +mod common_tests; + +use common_tests::fixtures::server::ClientToAgentConnection; +use common_tests::fixtures::{run_test, Connection, Session, TestConnectionConfig}; +use goose_test_support::ExpectedSessionId; + +use common_tests::fixtures::OpenAiFixture; + +/// Send an untyped custom request and return the result or error. +async fn send_custom( + cx: &sacp::JrConnectionCx, + method: &str, + params: serde_json::Value, +) -> Result { + let msg = sacp::UntypedMessage::new(method, params).unwrap(); + cx.send_request(msg).block_task().await +} + +#[test] +fn test_custom_session_list() { + run_test(async { + let openai = OpenAiFixture::new(vec![], ExpectedSessionId::default()).await; + let mut conn = ClientToAgentConnection::new(TestConnectionConfig::default(), openai).await; + + let (session, _models) = conn.new_session().await; + let session_id = session.session_id().0.clone(); + + // Verify the session exists via _session/get + let get_result = send_custom( + conn.cx(), + "_goose/session/get", + serde_json::json!({ "session_id": session_id }), + ) + .await; + assert!( + get_result.is_ok(), + "session should exist via get: {:?}", + get_result + ); + let get_response = get_result.unwrap(); + assert_eq!( + get_response + .get("session") + .and_then(|s| s.get("id")) + .and_then(|v| v.as_str()), + Some(session_id.as_ref()), + ); + + // Verify _session/list returns a valid response + // Note: list_sessions uses INNER JOIN on messages, so a fresh session + // with no messages won't appear. We just verify the call succeeds. + let result = send_custom(conn.cx(), "_goose/session/list", serde_json::json!({})).await; + assert!(result.is_ok(), "expected ok, got: {:?}", result); + let response = result.unwrap(); + let sessions = response.get("sessions").expect("missing 'sessions' field"); + assert!(sessions.is_array(), "sessions should be array"); + }); +} + +#[test] +fn test_custom_session_get() { + run_test(async { + let openai = OpenAiFixture::new(vec![], ExpectedSessionId::default()).await; + let mut conn = ClientToAgentConnection::new(TestConnectionConfig::default(), openai).await; + + let (session, _models) = conn.new_session().await; + let session_id = session.session_id().0.clone(); + + let result = send_custom( + conn.cx(), + "_goose/session/get", + serde_json::json!({ + "session_id": session_id, + }), + ) + .await; + assert!(result.is_ok(), "expected ok, got: {:?}", result); + + let response = result.unwrap(); + let returned_session = response.get("session").expect("missing 'session' field"); + assert_eq!( + returned_session.get("id").and_then(|v| v.as_str()), + Some(session_id.as_ref()) + ); + }); +} + +#[test] +fn test_custom_session_delete() { + run_test(async { + let openai = OpenAiFixture::new(vec![], ExpectedSessionId::default()).await; + let mut conn = ClientToAgentConnection::new(TestConnectionConfig::default(), openai).await; + + let (session, _models) = conn.new_session().await; + let session_id = session.session_id().0.clone(); + + let result = send_custom( + conn.cx(), + "_goose/session/delete", + serde_json::json!({ "session_id": session_id }), + ) + .await; + assert!(result.is_ok(), "delete failed: {:?}", result); + + let result = send_custom( + conn.cx(), + "_goose/session/get", + serde_json::json!({ "session_id": session_id }), + ) + .await; + assert!(result.is_err(), "expected error for deleted session"); + }); +} + +#[test] +fn test_custom_get_tools() { + run_test(async { + let openai = OpenAiFixture::new(vec![], ExpectedSessionId::default()).await; + let mut conn = ClientToAgentConnection::new(TestConnectionConfig::default(), openai).await; + + let (session, _models) = conn.new_session().await; + let session_id = session.session_id().0.clone(); + + let result = send_custom( + conn.cx(), + "_goose/tools", + serde_json::json!({ "session_id": session_id }), + ) + .await; + assert!(result.is_ok(), "expected ok, got: {:?}", result); + + let response = result.unwrap(); + let tools = response.get("tools").expect("missing 'tools' field"); + assert!(tools.is_array(), "tools should be array"); + }); +} + +#[test] +fn test_custom_get_extensions() { + run_test(async { + let openai = OpenAiFixture::new(vec![], ExpectedSessionId::default()).await; + let conn = ClientToAgentConnection::new(TestConnectionConfig::default(), openai).await; + + let result = + send_custom(conn.cx(), "_goose/config/extensions", serde_json::json!({})).await; + assert!(result.is_ok(), "expected ok, got: {:?}", result); + + let response = result.unwrap(); + assert!( + response.get("extensions").is_some(), + "missing 'extensions' field" + ); + assert!( + response.get("warnings").is_some(), + "missing 'warnings' field" + ); + }); +} + +#[test] +fn test_custom_unknown_method() { + run_test(async { + let openai = OpenAiFixture::new(vec![], ExpectedSessionId::default()).await; + let conn = ClientToAgentConnection::new(TestConnectionConfig::default(), openai).await; + + let result = send_custom(conn.cx(), "_unknown/method", serde_json::json!({})).await; + assert!(result.is_err(), "expected method_not_found error"); + }); +} diff --git a/crates/goose-acp/tests/fixtures/server.rs b/crates/goose-acp/tests/fixtures/server.rs index 1432e3431da5..1d02eb66251e 100644 --- a/crates/goose-acp/tests/fixtures/server.rs +++ b/crates/goose-acp/tests/fixtures/server.rs @@ -34,6 +34,13 @@ pub struct ClientToAgentSession { notify: Arc, } +impl ClientToAgentConnection { + #[allow(dead_code)] + pub fn cx(&self) -> &JrConnectionCx { + &self.cx + } +} + #[async_trait] impl Connection for ClientToAgentConnection { type Session = ClientToAgentSession; diff --git a/ui/acp/generate-schema.ts b/ui/acp/generate-schema.ts new file mode 100644 index 000000000000..ee63176c2daa --- /dev/null +++ b/ui/acp/generate-schema.ts @@ -0,0 +1,230 @@ +#!/usr/bin/env node +/** + * Generates TypeScript types + Zod validators for Goose custom extension methods. + * + * Usage: + * npm run generate # build Rust schema, then generate TS + */ + +import { createClient } from "@hey-api/openapi-ts"; +import { execSync } from "child_process"; +import * as fs from "fs/promises"; +import { dirname, resolve } from "path"; +import { fileURLToPath } from "url"; +import * as prettier from "prettier"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const ROOT = resolve(__dirname, "../.."); +const SCHEMA_PATH = resolve(ROOT, "crates/goose-acp/acp-schema.json"); +const META_PATH = resolve(ROOT, "crates/goose-acp/acp-meta.json"); +const OUTPUT_DIR = resolve(__dirname, "src/generated"); + +main().catch((err) => { + console.error(err); + process.exit(1); +}); + +async function main() { + const schemaSrc = await fs.readFile(SCHEMA_PATH, "utf8"); + const jsonSchema = JSON.parse( + // Convert JSON Schema $defs refs to OpenAPI component refs + schemaSrc.replaceAll("#/$defs/", "#/components/schemas/"), + ); + + const metaSrc = await fs.readFile(META_PATH, "utf8"); + const meta = JSON.parse(metaSrc); + + await createClient({ + input: { + openapi: "3.1.0", + info: { + title: "Goose Extensions", + version: "1.0.0", + }, + components: { + schemas: jsonSchema.$defs, + }, + }, + output: { + path: OUTPUT_DIR, + }, + plugins: ["zod", "@hey-api/typescript"], + }); + + await postProcessTypes(); + await postProcessIndex(meta); + + await generateClient(meta); + + console.log(`\nGenerated Goose extension schema in ${OUTPUT_DIR}`); +} + +async function postProcessTypes() { + const tsPath = resolve(OUTPUT_DIR, "types.gen.ts"); + let src = await fs.readFile(tsPath, "utf8"); + // Remove the ClientOptions type block injected by @hey-api (not part of our schema) + src = src.replace(/\nexport type ClientOptions =[\s\S]*?^};\n/m, "\n"); + await fs.writeFile(tsPath, src); +} + +async function postProcessIndex(meta: { methods: unknown[] }) { + const indexPath = resolve(OUTPUT_DIR, "index.ts"); + let src = await fs.readFile(indexPath, "utf8"); + + // Strip ClientOptions from re-exports + src = src.replace(/,?\s*ClientOptions\s*,?/g, (match) => { + if (match.startsWith(",") && match.endsWith(",")) return ","; + if (match.startsWith(",")) return ""; + return ""; + }); + + // Fix bare relative imports to use .js extensions (required by nodenext consumers) + src = fixRelativeImports(src); + + // Append method constants + const methodConstants = await prettier.format( + ` +export const GOOSE_EXT_METHODS = ${JSON.stringify(meta.methods, null, 2)} as const; + +export type GooseExtMethod = (typeof GOOSE_EXT_METHODS)[number]; +`, + { parser: "typescript" }, + ); + + await fs.writeFile(indexPath, `${src}\n${methodConstants}`); + + // Also fix imports in zod.gen.ts (it may import from types.gen) + for (const file of ["zod.gen.ts", "types.gen.ts"]) { + const filePath = resolve(OUTPUT_DIR, file); + try { + const content = await fs.readFile(filePath, "utf8"); + const fixed = fixRelativeImports(content); + if (fixed !== content) { + await fs.writeFile(filePath, fixed); + } + } catch { + // File may not exist + } + } +} + +function fixRelativeImports(src: string): string { + return src.replace( + /from\s+['"](\.[^'"]+)['"]/g, + (_match, importPath: string) => { + if (importPath.endsWith(".js") || importPath.endsWith(".json")) { + return `from '${importPath}'`; + } + return `from '${importPath}.js'`; + }, + ); +} + +interface MethodMeta { + method: string; + requestType: string | null; + responseType: string | null; +} + +/** + * Convert a method path like "session/list" or "working_dir/update" to camelCase "sessionList", "workingDirUpdate". + */ +function methodToCamelCase(method: string): string { + return method + .split(/[/_]/) + .map((part, i) => + i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1), + ) + .join(""); +} + +/** + * Generate a typed GooseClient class that wraps ClientSideConnection.extMethod() + * with proper TypeScript types and Zod runtime validation. + */ +async function generateClient(meta: { methods: MethodMeta[] }) { + const typeImports = new Set(); + const zodImports = new Set(); + + const methodDefs: string[] = []; + + for (const m of meta.methods) { + const fnName = methodToCamelCase(m.method); + const fullMethod = `_goose/${m.method}`; + + // Build param type and arg + let paramType = ""; + let paramArg = ""; + let callParams = "{}"; + if (m.requestType) { + typeImports.add(m.requestType); + paramType = m.requestType; + paramArg = `params: ${paramType}`; + callParams = "params"; + } + + // Build return type and validation + let returnType: string; + let bodyLines: string[]; + + if (m.responseType && m.responseType !== "EmptyResponse") { + typeImports.add(m.responseType); + const zodName = `z${m.responseType}`; + zodImports.add(zodName); + returnType = m.responseType; + bodyLines = [ + `const raw = await this.conn.extMethod("${fullMethod}", ${callParams});`, + `return ${zodName}.parse(raw) as ${returnType};`, + ]; + } else if (m.responseType === "EmptyResponse") { + returnType = "void"; + bodyLines = [ + `await this.conn.extMethod("${fullMethod}", ${callParams});`, + ]; + } else { + // Both request and response are untyped (serde_json::Value) + returnType = "Record"; + bodyLines = [ + `return await this.conn.extMethod("${fullMethod}", ${callParams ? callParams : "{}"});`, + ]; + } + + methodDefs.push(` + async ${fnName}(${paramArg}): Promise<${returnType}> { + ${bodyLines.join("\n ")} + }`); + } + + const typeImportLine = typeImports.size + ? `import type { ${[...typeImports].sort().join(", ")} } from "./types.gen.js";` + : ""; + const zodImportLine = zodImports.size + ? `import { ${[...zodImports].sort().join(", ")} } from "./zod.gen.js";` + : ""; + + let src = `// This file is auto-generated — do not edit manually. + +export interface ExtMethodProvider { + extMethod(method: string, params: Record): Promise>; +} + +${typeImportLine} +${zodImportLine} + +/** + * Typed client for Goose custom extension methods. + * Wraps an ExtMethodProvider (e.g. ClientSideConnection) with proper types and Zod validation. + */ +export class GooseClient { + constructor(private conn: ExtMethodProvider) {} +${methodDefs.join("\n")} +} +`; + + src = await prettier.format(src, { parser: "typescript" }); + src = fixRelativeImports(src); + + const clientPath = resolve(OUTPUT_DIR, "client.gen.ts"); + await fs.writeFile(clientPath, src); +} diff --git a/ui/acp/package-lock.json b/ui/acp/package-lock.json new file mode 100644 index 000000000000..3eb6dc181f85 --- /dev/null +++ b/ui/acp/package-lock.json @@ -0,0 +1,1277 @@ +{ + "name": "goose-acp-types", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "goose-acp-types", + "version": "0.1.0", + "dependencies": { + "zod": "^3.25.76" + }, + "devDependencies": { + "@agentclientprotocol/sdk": "^0.14.1", + "@hey-api/openapi-ts": "^0.92.3", + "prettier": "^3.8.1", + "tsx": "^4.21.0", + "typescript": "~5.9.3" + }, + "peerDependencies": { + "@agentclientprotocol/sdk": "*" + } + }, + "node_modules/@agentclientprotocol/sdk": { + "version": "0.14.1", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@agentclientprotocol/sdk/-/sdk-0.14.1.tgz", + "integrity": "sha512-b6r3PS3Nly+Wyw9U+0nOr47bV8tfS476EgyEMhoKvJCZLbgqoDFN7DJwkxL88RR0aiOqOYV1ZnESHqb+RmdH8w==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hey-api/codegen-core": { + "version": "0.7.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@hey-api/codegen-core/-/codegen-core-0.7.0.tgz", + "integrity": "sha512-HglL4B4QwpzocE+c8qDU6XK8zMf8W8Pcv0RpFDYxHuYALWLTnpDUuEsglC7NQ4vC1maoXsBpMbmwpco0N4QviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hey-api/types": "0.1.3", + "ansi-colors": "4.1.3", + "c12": "3.3.3", + "color-support": "1.1.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "typescript": ">=5.5.3" + } + }, + "node_modules/@hey-api/json-schema-ref-parser": { + "version": "1.3.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.3.0.tgz", + "integrity": "sha512-3tQJ8N2egHXZjQWUeceoWrl88APWjo7gRrQ/L4HWJKnh6HowczCv7yNNFeSusPoWGV6HGdoFiCvq6UsLkrwKhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "7.1.3", + "@types/json-schema": "7.0.15", + "js-yaml": "4.1.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/openapi-ts": { + "version": "0.92.4", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@hey-api/openapi-ts/-/openapi-ts-0.92.4.tgz", + "integrity": "sha512-RA3wnL7Odr5xczuS3xpvnPClgJ/K8jivK3hvD8J0m5GBuvJFkZ1A1xp+6Ve1G0BV8p4LwxwgN1Qhb+4BFsLfMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hey-api/codegen-core": "0.7.0", + "@hey-api/json-schema-ref-parser": "1.3.0", + "@hey-api/shared": "0.2.0", + "@hey-api/types": "0.1.3", + "ansi-colors": "4.1.3", + "color-support": "1.1.3", + "commander": "14.0.3" + }, + "bin": { + "openapi-ts": "bin/run.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "typescript": ">=5.5.3" + } + }, + "node_modules/@hey-api/shared": { + "version": "0.2.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@hey-api/shared/-/shared-0.2.0.tgz", + "integrity": "sha512-t7C+65ES12OqAE5k6DB/y5nDuTjydtqdxf/Qe4zflVn2AzGs7hO/7KjXvGXZYnpNVF7QISAcj0LEObASU9I53Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hey-api/codegen-core": "0.7.0", + "@hey-api/json-schema-ref-parser": "1.3.0", + "@hey-api/types": "0.1.3", + "ansi-colors": "4.1.3", + "cross-spawn": "7.0.6", + "open": "11.0.0", + "semver": "7.7.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "typescript": ">=5.5.3" + } + }, + "node_modules/@hey-api/types": { + "version": "0.1.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@hey-api/types/-/types-0.1.3.tgz", + "integrity": "sha512-mZaiPOWH761yD4GjDQvtjS2ZYLu5o5pI1TVSvV/u7cmbybv51/FVtinFBeaE1kFQCKZ8OQpn2ezjLBJrKsGATw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=5.5.3" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c12": { + "version": "3.3.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/c12/-/c12-3.3.3.tgz", + "integrity": "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^17.2.3", + "exsolve": "^1.0.8", + "giget": "^2.0.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.0.0", + "pkg-types": "^2.3.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "engines": { + "node": ">=20" + } + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/nypm": { + "version": "0.6.5", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/nypm/-/nypm-0.6.5.tgz", + "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.2.0", + "pathe": "^2.0.3", + "tinyexec": "^1.0.2" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/nypm/node_modules/citty": { + "version": "0.2.1", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/citty/-/citty-0.2.1.tgz", + "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "dev": true, + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://global.block-artifacts.com/artifactory/api/npm/square-npm/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/ui/acp/package.json b/ui/acp/package.json new file mode 100644 index 000000000000..94b98b968ede --- /dev/null +++ b/ui/acp/package.json @@ -0,0 +1,26 @@ +{ + "name": "goose-acp-types", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "generate": "tsx generate-schema.ts", + "lint": "tsc --noEmit", + "format": "prettier --write src/" + }, + "dependencies": { + "zod": "^3.25.76" + }, + "peerDependencies": { + "@agentclientprotocol/sdk": "*" + }, + "devDependencies": { + "@agentclientprotocol/sdk": "^0.14.1", + "@hey-api/openapi-ts": "^0.92.3", + "prettier": "^3.8.1", + "tsx": "^4.21.0", + "typescript": "~5.9.3" + } +} diff --git a/ui/acp/src/generated/client.gen.ts b/ui/acp/src/generated/client.gen.ts new file mode 100644 index 000000000000..db0ae5eb21d0 --- /dev/null +++ b/ui/acp/src/generated/client.gen.ts @@ -0,0 +1,129 @@ +// This file is auto-generated — do not edit manually. + +export interface ExtMethodProvider { + extMethod( + method: string, + params: Record, + ): Promise>; +} + +import type { + AddExtensionRequest, + DeleteSessionRequest, + ExportSessionRequest, + ExportSessionResponse, + GetExtensionsResponse, + GetSessionRequest, + GetSessionResponse, + GetToolsRequest, + GetToolsResponse, + ImportSessionRequest, + ImportSessionResponse, + ListSessionsResponse, + ReadResourceRequest, + ReadResourceResponse, + RemoveExtensionRequest, + UpdateWorkingDirRequest, +} from './types.gen.js'; +import { + zExportSessionResponse, + zGetExtensionsResponse, + zGetSessionResponse, + zGetToolsResponse, + zImportSessionResponse, + zListSessionsResponse, + zReadResourceResponse, +} from './zod.gen.js'; + +/** + * Typed client for Goose custom extension methods. + * Wraps an ExtMethodProvider (e.g. ClientSideConnection) with proper types and Zod validation. + */ +export class GooseClient { + constructor(private conn: ExtMethodProvider) {} + + async extensionsAdd(params: AddExtensionRequest): Promise { + await this.conn.extMethod("_goose/extensions/add", params); + } + + async extensionsRemove(params: RemoveExtensionRequest): Promise { + await this.conn.extMethod("_goose/extensions/remove", params); + } + + async tools(params: GetToolsRequest): Promise { + const raw = await this.conn.extMethod("_goose/tools", params); + return zGetToolsResponse.parse(raw) as GetToolsResponse; + } + + async resourceRead( + params: ReadResourceRequest, + ): Promise { + const raw = await this.conn.extMethod("_goose/resource/read", params); + return zReadResourceResponse.parse(raw) as ReadResourceResponse; + } + + async workingDirUpdate(params: UpdateWorkingDirRequest): Promise { + await this.conn.extMethod("_goose/working_dir/update", params); + } + + async sessionList(): Promise { + const raw = await this.conn.extMethod("_goose/session/list", {}); + return zListSessionsResponse.parse(raw) as ListSessionsResponse; + } + + async sessionGet(params: GetSessionRequest): Promise { + const raw = await this.conn.extMethod("_goose/session/get", params); + return zGetSessionResponse.parse(raw) as GetSessionResponse; + } + + async sessionDelete(params: DeleteSessionRequest): Promise { + await this.conn.extMethod("_goose/session/delete", params); + } + + async sessionExport( + params: ExportSessionRequest, + ): Promise { + const raw = await this.conn.extMethod("_goose/session/export", params); + return zExportSessionResponse.parse(raw) as ExportSessionResponse; + } + + async sessionImport( + params: ImportSessionRequest, + ): Promise { + const raw = await this.conn.extMethod("_goose/session/import", params); + return zImportSessionResponse.parse(raw) as ImportSessionResponse; + } + + async configExtensions(): Promise { + const raw = await this.conn.extMethod("_goose/config/extensions", {}); + return zGetExtensionsResponse.parse(raw) as GetExtensionsResponse; + } + + async toolCall(): Promise> { + return await this.conn.extMethod("_goose/tool/call", {}); + } + + async providerUpdate(): Promise> { + return await this.conn.extMethod("_goose/provider/update", {}); + } + + async containerSet(): Promise> { + return await this.conn.extMethod("_goose/container/set", {}); + } + + async appsList(): Promise> { + return await this.conn.extMethod("_goose/apps/list", {}); + } + + async appsExport(): Promise> { + return await this.conn.extMethod("_goose/apps/export", {}); + } + + async appsImport(): Promise> { + return await this.conn.extMethod("_goose/apps/import", {}); + } + + async configProviders(): Promise> { + return await this.conn.extMethod("_goose/config/providers", {}); + } +} diff --git a/ui/acp/src/generated/index.ts b/ui/acp/src/generated/index.ts new file mode 100644 index 000000000000..b44b56842762 --- /dev/null +++ b/ui/acp/src/generated/index.ts @@ -0,0 +1,98 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { AddExtensionRequest, DeleteSessionRequest, EmptyResponse, ExportSessionRequest, ExportSessionResponse, ExtRequest, ExtResponse, GetExtensionsResponse, GetSessionRequest, GetSessionResponse, GetToolsRequest, GetToolsResponse, ImportSessionRequest, ImportSessionResponse, ListSessionsResponse, ReadResourceRequest, ReadResourceResponse, RemoveExtensionRequest, UpdateWorkingDirRequest } from './types.gen.js'; + +export const GOOSE_EXT_METHODS = [ + { + method: "extensions/add", + requestType: "AddExtensionRequest", + responseType: "EmptyResponse", + }, + { + method: "extensions/remove", + requestType: "RemoveExtensionRequest", + responseType: "EmptyResponse", + }, + { + method: "tools", + requestType: "GetToolsRequest", + responseType: "GetToolsResponse", + }, + { + method: "resource/read", + requestType: "ReadResourceRequest", + responseType: "ReadResourceResponse", + }, + { + method: "working_dir/update", + requestType: "UpdateWorkingDirRequest", + responseType: "EmptyResponse", + }, + { + method: "session/list", + requestType: null, + responseType: "ListSessionsResponse", + }, + { + method: "session/get", + requestType: "GetSessionRequest", + responseType: "GetSessionResponse", + }, + { + method: "session/delete", + requestType: "DeleteSessionRequest", + responseType: "EmptyResponse", + }, + { + method: "session/export", + requestType: "ExportSessionRequest", + responseType: "ExportSessionResponse", + }, + { + method: "session/import", + requestType: "ImportSessionRequest", + responseType: "ImportSessionResponse", + }, + { + method: "config/extensions", + requestType: null, + responseType: "GetExtensionsResponse", + }, + { + method: "tool/call", + requestType: null, + responseType: null, + }, + { + method: "provider/update", + requestType: null, + responseType: null, + }, + { + method: "container/set", + requestType: null, + responseType: null, + }, + { + method: "apps/list", + requestType: null, + responseType: null, + }, + { + method: "apps/export", + requestType: null, + responseType: null, + }, + { + method: "apps/import", + requestType: null, + responseType: null, + }, + { + method: "config/providers", + requestType: null, + responseType: null, + }, +] as const; + +export type GooseExtMethod = (typeof GOOSE_EXT_METHODS)[number]; diff --git a/ui/acp/src/generated/types.gen.ts b/ui/acp/src/generated/types.gen.ts new file mode 100644 index 000000000000..e404c8f82c98 --- /dev/null +++ b/ui/acp/src/generated/types.gen.ts @@ -0,0 +1,165 @@ +// This file is auto-generated by @hey-api/openapi-ts + + +/** + * Add an extension to an active session. + * Method: `_agent/extensions/add` + */ +export type AddExtensionRequest = { + session_id: string; + /** + * Extension configuration (see ExtensionConfig variants: Stdio, StreamableHttp, Builtin, Platform). + */ + config: unknown; +}; + +/** + * Empty success response for operations that return no data. + */ +export type EmptyResponse = { + [key: string]: unknown; +}; + +/** + * Remove an extension from an active session. + * Method: `_agent/extensions/remove` + */ +export type RemoveExtensionRequest = { + session_id: string; + name: string; +}; + +/** + * List all tools available in a session. + * Method: `_agent/tools` + */ +export type GetToolsRequest = { + session_id: string; +}; + +export type GetToolsResponse = { + /** + * Array of tool info objects with `name`, `description`, `parameters`, and optional `permission`. + */ + tools: Array; +}; + +/** + * Read a resource from an extension. + * Method: `_agent/resource/read` + */ +export type ReadResourceRequest = { + session_id: string; + uri: string; + extension_name: string; +}; + +export type ReadResourceResponse = { + /** + * The resource result from the extension (MCP ReadResourceResult). + */ + result: unknown; +}; + +/** + * Update the working directory for a session. + * Method: `_agent/working_dir/update` + */ +export type UpdateWorkingDirRequest = { + session_id: string; + working_dir: string; +}; + +/** + * List all sessions. + * Method: `_session/list` + */ +export type ListSessionsResponse = { + sessions: Array; +}; + +/** + * Get a session by ID. + * Method: `_session/get` + */ +export type GetSessionRequest = { + session_id: string; + include_messages?: boolean; +}; + +/** + * Get a session response. + */ +export type GetSessionResponse = { + /** + * The session object with id, name, working_dir, timestamps, tokens, etc. + */ + session: unknown; +}; + +/** + * Delete a session. + * Method: `_session/delete` + */ +export type DeleteSessionRequest = { + session_id: string; +}; + +/** + * Export a session as a JSON string. + * Method: `_session/export` + */ +export type ExportSessionRequest = { + session_id: string; +}; + +export type ExportSessionResponse = { + data: string; +}; + +/** + * Import a session from a JSON string. + * Method: `_session/import` + */ +export type ImportSessionRequest = { + data: string; +}; + +export type ImportSessionResponse = { + /** + * The imported session object. + */ + session: unknown; +}; + +/** + * List configured extensions and any warnings. + * Method: `_config/extensions` + */ +export type GetExtensionsResponse = { + /** + * Array of ExtensionEntry objects with `enabled` flag and config details. + */ + extensions: Array; + warnings: Array; +}; + +export type ExtRequest = { + id: string; + method: string; + params?: AddExtensionRequest | RemoveExtensionRequest | GetToolsRequest | ReadResourceRequest | UpdateWorkingDirRequest | GetSessionRequest | DeleteSessionRequest | ExportSessionRequest | ImportSessionRequest | { + [key: string]: unknown; + } | null; +}; + +export type ExtResponse = { + id: string; + result?: EmptyResponse | GetToolsResponse | ReadResourceResponse | ListSessionsResponse | GetSessionResponse | ExportSessionResponse | ImportSessionResponse | GetExtensionsResponse | unknown; +} | { + error: { + code: number; + message: string; + data?: unknown; + }; + id: string; +}; diff --git a/ui/acp/src/generated/zod.gen.ts b/ui/acp/src/generated/zod.gen.ts new file mode 100644 index 000000000000..24cc5277007c --- /dev/null +++ b/ui/acp/src/generated/zod.gen.ts @@ -0,0 +1,175 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +/** + * Add an extension to an active session. + * Method: `_agent/extensions/add` + */ +export const zAddExtensionRequest = z.object({ + session_id: z.string(), + config: z.unknown() +}); + +/** + * Empty success response for operations that return no data. + */ +export const zEmptyResponse = z.record(z.unknown()); + +/** + * Remove an extension from an active session. + * Method: `_agent/extensions/remove` + */ +export const zRemoveExtensionRequest = z.object({ + session_id: z.string(), + name: z.string() +}); + +/** + * List all tools available in a session. + * Method: `_agent/tools` + */ +export const zGetToolsRequest = z.object({ + session_id: z.string() +}); + +export const zGetToolsResponse = z.object({ + tools: z.array(z.unknown()) +}); + +/** + * Read a resource from an extension. + * Method: `_agent/resource/read` + */ +export const zReadResourceRequest = z.object({ + session_id: z.string(), + uri: z.string(), + extension_name: z.string() +}); + +export const zReadResourceResponse = z.object({ + result: z.unknown() +}); + +/** + * Update the working directory for a session. + * Method: `_agent/working_dir/update` + */ +export const zUpdateWorkingDirRequest = z.object({ + session_id: z.string(), + working_dir: z.string() +}); + +/** + * List all sessions. + * Method: `_session/list` + */ +export const zListSessionsResponse = z.object({ + sessions: z.array(z.unknown()) +}); + +/** + * Get a session by ID. + * Method: `_session/get` + */ +export const zGetSessionRequest = z.object({ + session_id: z.string(), + include_messages: z.boolean().optional().default(false) +}); + +/** + * Get a session response. + */ +export const zGetSessionResponse = z.object({ + session: z.unknown() +}); + +/** + * Delete a session. + * Method: `_session/delete` + */ +export const zDeleteSessionRequest = z.object({ + session_id: z.string() +}); + +/** + * Export a session as a JSON string. + * Method: `_session/export` + */ +export const zExportSessionRequest = z.object({ + session_id: z.string() +}); + +export const zExportSessionResponse = z.object({ + data: z.string() +}); + +/** + * Import a session from a JSON string. + * Method: `_session/import` + */ +export const zImportSessionRequest = z.object({ + data: z.string() +}); + +export const zImportSessionResponse = z.object({ + session: z.unknown() +}); + +/** + * List configured extensions and any warnings. + * Method: `_config/extensions` + */ +export const zGetExtensionsResponse = z.object({ + extensions: z.array(z.unknown()), + warnings: z.array(z.string()) +}); + +export const zExtRequest = z.object({ + id: z.string(), + method: z.string(), + params: z.union([ + z.union([ + zAddExtensionRequest, + zRemoveExtensionRequest, + zGetToolsRequest, + zReadResourceRequest, + zUpdateWorkingDirRequest, + zGetSessionRequest, + zDeleteSessionRequest, + zExportSessionRequest, + zImportSessionRequest + ]), + z.union([ + z.record(z.unknown()), + z.null() + ]) + ]).optional() +}); + +export const zExtResponse = z.union([ + z.object({ + id: z.string(), + result: z.union([ + z.union([ + zEmptyResponse, + zGetToolsResponse, + zReadResourceResponse, + zListSessionsResponse, + zGetSessionResponse, + zExportSessionResponse, + zImportSessionResponse, + zGetExtensionsResponse + ]), + z.unknown() + ]).optional() + }), + z.object({ + error: z.object({ + code: z.number().int(), + message: z.string(), + data: z.unknown().optional() + }), + id: z.string() + }) +]); diff --git a/ui/acp/src/index.ts b/ui/acp/src/index.ts new file mode 100644 index 000000000000..04ae78458ede --- /dev/null +++ b/ui/acp/src/index.ts @@ -0,0 +1,3 @@ +export * from "./generated/index.js"; +export * from "./generated/zod.gen.js"; +export { GooseClient } from "./generated/client.gen.js"; diff --git a/ui/acp/tsconfig.json b/ui/acp/tsconfig.json new file mode 100644 index 000000000000..8c18285d40de --- /dev/null +++ b/ui/acp/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src"] +} diff --git a/ui/desktop/package-lock.json b/ui/desktop/package-lock.json index 50127d4567ac..3b46f36a53eb 100644 --- a/ui/desktop/package-lock.json +++ b/ui/desktop/package-lock.json @@ -36,6 +36,7 @@ "electron-updater": "^6.7.3", "electron-window-state": "^5.0.3", "express": "^5.2.1", + "goose-acp-types": "file:../acp", "katex": "^0.16.28", "lodash": "^4.17.23", "lucide-react": "^0.563.0", @@ -120,6 +121,19 @@ "npm": "^11.6.1" } }, + "../acp": { + "name": "goose-acp-types", + "version": "0.1.0", + "dependencies": { + "zod": "^3.25.76" + }, + "devDependencies": { + "@hey-api/openapi-ts": "^0.92.3", + "prettier": "^3.8.1", + "tsx": "^4.21.0", + "typescript": "~5.9.3" + } + }, "node_modules/@acemir/cssom": { "version": "0.9.31", "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", @@ -219,7 +233,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -589,7 +602,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" }, @@ -630,7 +642,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" } @@ -1088,7 +1099,6 @@ "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chalk": "^4.1.1", "fs-extra": "^9.0.1", @@ -2722,7 +2732,6 @@ "integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@inquirer/checkbox": "^3.0.1", "@inquirer/confirm": "^4.0.1", @@ -3198,7 +3207,6 @@ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "license": "MIT", - "peer": true, "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -6523,7 +6531,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -6811,7 +6820,6 @@ "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -6853,7 +6861,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6864,7 +6871,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -7005,7 +7011,6 @@ "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", @@ -7391,7 +7396,6 @@ "integrity": "sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "4.0.18", "fflate": "^0.8.2", @@ -7640,7 +7644,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7713,7 +7716,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -8254,7 +8256,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -9557,7 +9558,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dom-helpers": { "version": "5.2.1", @@ -9615,7 +9617,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", @@ -10623,7 +10624,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -11107,7 +11107,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -11986,6 +11985,10 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/goose-acp-types": { + "resolved": "../acp", + "link": true + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -12337,7 +12340,6 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -13394,7 +13396,6 @@ "integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@acemir/cssom": "^0.9.31", "@asamuzakjp/dom-selector": "^6.7.6", @@ -14637,6 +14638,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -14657,7 +14659,6 @@ "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", @@ -17033,7 +17034,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -17135,6 +17135,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -17150,6 +17151,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -17505,7 +17507,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -17515,7 +17516,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -17537,7 +17537,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-markdown": { "version": "10.1.0", @@ -19454,8 +19455,7 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tailwindcss-animate": { "version": "1.0.7", @@ -19986,7 +19986,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20411,7 +20410,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -20502,7 +20500,6 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", @@ -21151,7 +21148,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/ui/desktop/package.json b/ui/desktop/package.json index 78b2f52914bf..25e40d6d01a1 100644 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -42,6 +42,7 @@ "start-alpha-gui": "ALPHA=true npm run start-gui" }, "dependencies": { + "goose-acp-types": "file:../acp", "@mcp-ui/client": "^6.1.0", "@modelcontextprotocol/ext-apps": "^1.0.1", "@radix-ui/react-accordion": "^1.2.12",