mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-09-01 00:59:33 +00:00
feat(extensions): support text setup fields in web configure modal (#496)
* feat(extensions): support text setup fields in web configure modal * fix(extensions): use exported wasm setup schema types * fix(extensions): validate extension name in setup APIs * fix(extensions): restrict setup setting_path writes * refactor(web): use enum for setup field input type * fix: restore registry versions reverted during merge [skip-regression-check] The merge auto-resolved registry JSON conflicts in favor of the PR's older 0.2.0 versions. Restore discord, github, and web-search to 0.2.1 from staging. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: 您的GitHub用户名 <[email protected]> Co-authored-by: [email protected] <[email protected]> Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
@@ -708,6 +708,9 @@ pub struct ToolSetupSchema {
|
||||
/// Secrets the user must provide before the tool can be used.
|
||||
#[serde(default)]
|
||||
pub required_secrets: Vec<ToolSecretSetupSchema>,
|
||||
/// Non-secret fields the user can configure in the setup modal.
|
||||
#[serde(default)]
|
||||
pub required_fields: Vec<ToolFieldSetupSchema>,
|
||||
}
|
||||
|
||||
/// A single secret required during tool setup.
|
||||
@@ -722,6 +725,46 @@ pub struct ToolSecretSetupSchema {
|
||||
pub optional: bool,
|
||||
}
|
||||
|
||||
/// A non-secret field required during tool setup.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolFieldSetupSchema {
|
||||
/// Field name in setup payload.
|
||||
pub name: String,
|
||||
/// User-facing prompt shown in the setup modal.
|
||||
pub prompt: String,
|
||||
/// If true, the user may skip this field.
|
||||
#[serde(default)]
|
||||
pub optional: bool,
|
||||
/// Input type used in the setup modal.
|
||||
#[serde(default = "default_tool_setup_field_input_type")]
|
||||
pub input_type: ToolSetupFieldInputType,
|
||||
/// Optional dotted setting path to persist this value to.
|
||||
///
|
||||
/// Restricted by the host to extension-owned namespaces and a small
|
||||
/// allowlist of approved global settings.
|
||||
///
|
||||
/// Example: `extensions.switch-llm.provider`, `llm_backend`, or
|
||||
/// `selected_model`.
|
||||
#[serde(default)]
|
||||
pub setting_path: Option<String>,
|
||||
/// Whether changing this field requires a restart to fully apply.
|
||||
#[serde(default)]
|
||||
pub restart_required: bool,
|
||||
}
|
||||
|
||||
/// Input widget type for a setup field.
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ToolSetupFieldInputType {
|
||||
#[default]
|
||||
Text,
|
||||
Password,
|
||||
}
|
||||
|
||||
fn default_tool_setup_field_input_type() -> ToolSetupFieldInputType {
|
||||
ToolSetupFieldInputType::Text
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tools::wasm::capabilities_schema::{CapabilitiesFile, CredentialLocationSchema};
|
||||
@@ -1218,6 +1261,20 @@ mod tests {
|
||||
"prompt": "Google OAuth Client Secret",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"required_fields": [
|
||||
{
|
||||
"name": "llm_backend",
|
||||
"prompt": "LLM Provider",
|
||||
"setting_path": "llm_backend",
|
||||
"restart_required": true
|
||||
},
|
||||
{
|
||||
"name": "selected_model",
|
||||
"prompt": "Model Name",
|
||||
"input_type": "text",
|
||||
"setting_path": "selected_model"
|
||||
}
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
@@ -1230,6 +1287,48 @@ mod tests {
|
||||
assert!(!setup.required_secrets[0].optional);
|
||||
assert_eq!(setup.required_secrets[1].name, "google_oauth_client_secret");
|
||||
assert!(setup.required_secrets[1].optional);
|
||||
assert_eq!(setup.required_fields.len(), 2);
|
||||
assert_eq!(setup.required_fields[0].name, "llm_backend");
|
||||
assert_eq!(
|
||||
setup.required_fields[0].setting_path.as_deref(),
|
||||
Some("llm_backend")
|
||||
);
|
||||
assert!(setup.required_fields[0].restart_required);
|
||||
assert_eq!(
|
||||
setup.required_fields[0].input_type,
|
||||
crate::tools::wasm::capabilities_schema::ToolSetupFieldInputType::Text
|
||||
);
|
||||
assert_eq!(setup.required_fields[1].name, "selected_model");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tool_setup_field_input_type_defaults_to_text() {
|
||||
let json = r#"{
|
||||
"setup": {
|
||||
"required_fields": [
|
||||
{
|
||||
"name": "provider",
|
||||
"prompt": "Provider"
|
||||
},
|
||||
{
|
||||
"name": "token_hint",
|
||||
"prompt": "Token Hint",
|
||||
"input_type": "password"
|
||||
}
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
let caps = CapabilitiesFile::from_json(json).unwrap();
|
||||
let setup = caps.setup.unwrap();
|
||||
assert_eq!(
|
||||
setup.required_fields[0].input_type,
|
||||
crate::tools::wasm::capabilities_schema::ToolSetupFieldInputType::Text
|
||||
);
|
||||
assert_eq!(
|
||||
setup.required_fields[1].input_type,
|
||||
crate::tools::wasm::capabilities_schema::ToolSetupFieldInputType::Password
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -139,5 +139,5 @@ pub use loader::{
|
||||
// Capabilities schema (for parsing *.capabilities.json files)
|
||||
pub use capabilities_schema::{
|
||||
AuthCapabilitySchema, CapabilitiesFile, OAuthConfigSchema, RateLimitSchema,
|
||||
ValidationEndpointSchema,
|
||||
ToolFieldSetupSchema, ToolSetupFieldInputType, ToolSetupSchema, ValidationEndpointSchema,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user