Add environment variables to executor profiles (#1444)
* The implementation is complete. Here's a summary of what was done:
Successfully added environment variable configuration support to executor profiles. Users can now configure env vars like this:
```json
{
"CLAUDE_CODE": {
"DEFAULT": {
"CLAUDE_CODE": {
"env": {
"ANTHROPIC_AUTH_TOKEN": "...",
"ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic",
"API_TIMEOUT_MS": "3000000",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-4.6"
}
}
}
}
}
```
1. **`crates/executors/src/command.rs`**:
- Added `env: Option<HashMap<String, String>>` to `CmdOverrides` struct
- Added `apply_env_vars()` helper function
2. **Updated all executors to apply env vars**:
- `claude.rs` - `spawn_internal()`
- `codex.rs` - `spawn()`
- `amp.rs` - `spawn()` and `spawn_follow_up()`
- `droid.rs` - module-level `spawn()` function
- `copilot.rs` - both spawn methods
- `cursor.rs` - both spawn methods
- `opencode.rs` - both spawn methods
- `acp/harness.rs` - `spawn_with_command()` and `spawn_follow_up_with_command()` (used by Gemini/Qwen)
3. **Type Generation**: All JSON schemas and TypeScript types now include the `env` field
This allows users to set environment variables for all supported coding agents (Claude Code, Codex, Gemini, Amp, Droid, Copilot, Cursor, OpenCode, Qwen) through profile configuration, eliminating the need for external tools like claude-code-router.
* The implementation is complete. Here's a summary of what was done:
## Summary
### Backend Changes
- Added `env: Option<HashMap<String, String>>` field to `CmdOverrides` struct in `crates/executors/src/command.rs`
- Created `apply_env_vars()` helper function to inject env vars into `tokio::process::Command`
- Updated all 9 executors to apply env vars during spawn:
- claude.rs, codex.rs, amp.rs, droid.rs, copilot.rs, cursor.rs, opencode.rs
- gemini.rs and qwen.rs (via ACP harness)
- Modified ACP harness signature to accept `Option<&CmdOverrides>`
### Frontend Changes
- Created `KeyValueField.tsx` - custom RJSF field for editing key-value pairs
- Registered the field in `theme.ts`
- Added `uiSchema` to `ExecutorConfigForm.tsx` to use the custom field for `env`
### Generated Files
- `shared/types.ts` - TypeScript types updated with `env` field
- `shared/schemas/*.json` - All 9 executor schemas include `env` property
The environment variables UI will now appear in Settings > Agent Settings as a key-value editor with "Environment Variables" label and description. Users can add/remove/edit env vars that will be passed to the CLI execution environment.
* cleanup env structs
* fix form
* fmt
* remove mise.toml
* fmt
* Seprate config form per selected variant
---------
Co-authored-by: Louis Knight-Webb <louis@bloop.ai>
Co-authored-by: Solomon <abcpro11051@disroot.org>
This commit is contained in:
committed by
GitHub
parent
e1c9c15f43
commit
7da884bc3a
@@ -1,4 +1,4 @@
|
||||
use std::path::PathBuf;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -52,6 +52,12 @@ pub struct CmdOverrides {
|
||||
)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub additional_params: Option<Vec<String>>,
|
||||
#[schemars(
|
||||
title = "Environment Variables",
|
||||
description = "Environment variables to set when running the executor"
|
||||
)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS, JsonSchema)]
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::collections::HashMap;
|
||||
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::command::CmdOverrides;
|
||||
|
||||
/// Environment variables to inject into executor processes
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ExecutionEnv {
|
||||
@@ -20,6 +22,27 @@ impl ExecutionEnv {
|
||||
self.vars.insert(key.into(), value.into());
|
||||
}
|
||||
|
||||
/// Merge additional vars into this env. Incoming keys overwrite existing ones.
|
||||
pub fn merge(&mut self, other: &HashMap<String, String>) {
|
||||
self.vars
|
||||
.extend(other.iter().map(|(k, v)| (k.clone(), v.clone())));
|
||||
}
|
||||
|
||||
/// Return a new env with overrides applied. Overrides take precedence.
|
||||
pub fn with_overrides(mut self, overrides: &HashMap<String, String>) -> Self {
|
||||
self.merge(overrides);
|
||||
self
|
||||
}
|
||||
|
||||
/// Return a new env with profile env from CmdOverrides merged in.
|
||||
pub fn with_profile(self, cmd: &CmdOverrides) -> Self {
|
||||
if let Some(ref profile_env) = cmd.env {
|
||||
self.with_overrides(profile_env)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply all environment variables to a Command
|
||||
pub fn apply_to_command(&self, command: &mut Command) {
|
||||
for (key, value) in &self.vars {
|
||||
@@ -27,3 +50,25 @@ impl ExecutionEnv {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn profile_overrides_runtime_env() {
|
||||
let mut base = ExecutionEnv::default();
|
||||
base.insert("VK_PROJECT_NAME", "runtime");
|
||||
base.insert("FOO", "runtime");
|
||||
|
||||
let mut profile = HashMap::new();
|
||||
profile.insert("FOO".to_string(), "profile".to_string());
|
||||
profile.insert("BAR".to_string(), "profile".to_string());
|
||||
|
||||
let merged = base.with_overrides(&profile);
|
||||
|
||||
assert_eq!(merged.vars.get("VK_PROJECT_NAME").unwrap(), "runtime");
|
||||
assert_eq!(merged.vars.get("FOO").unwrap(), "profile"); // overrides
|
||||
assert_eq!(merged.vars.get("BAR").unwrap(), "profile");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use workspace_utils::stream_lines::LinesStreamExt;
|
||||
|
||||
use super::{AcpClient, SessionManager};
|
||||
use crate::{
|
||||
command::CommandParts,
|
||||
command::{CmdOverrides, CommandParts},
|
||||
env::ExecutionEnv,
|
||||
executors::{ExecutorError, ExecutorExitResult, SpawnedChild, acp::AcpEvent},
|
||||
};
|
||||
@@ -56,6 +56,7 @@ impl AcpAgentHarness {
|
||||
prompt: String,
|
||||
command_parts: CommandParts,
|
||||
env: &ExecutionEnv,
|
||||
cmd_overrides: &CmdOverrides,
|
||||
) -> Result<SpawnedChild, ExecutorError> {
|
||||
let (program_path, args) = command_parts.into_resolved().await?;
|
||||
let mut command = Command::new(program_path);
|
||||
@@ -68,8 +69,9 @@ impl AcpAgentHarness {
|
||||
.args(&args)
|
||||
.env("NODE_NO_WARNINGS", "1");
|
||||
|
||||
// Apply environment variables
|
||||
env.apply_to_command(&mut command);
|
||||
env.clone()
|
||||
.with_profile(cmd_overrides)
|
||||
.apply_to_command(&mut command);
|
||||
|
||||
let mut child = command.group_spawn()?;
|
||||
|
||||
@@ -98,6 +100,7 @@ impl AcpAgentHarness {
|
||||
session_id: &str,
|
||||
command_parts: CommandParts,
|
||||
env: &ExecutionEnv,
|
||||
cmd_overrides: &CmdOverrides,
|
||||
) -> Result<SpawnedChild, ExecutorError> {
|
||||
let (program_path, args) = command_parts.into_resolved().await?;
|
||||
let mut command = Command::new(program_path);
|
||||
@@ -110,8 +113,9 @@ impl AcpAgentHarness {
|
||||
.args(&args)
|
||||
.env("NODE_NO_WARNINGS", "1");
|
||||
|
||||
// Apply environment variables
|
||||
env.apply_to_command(&mut command);
|
||||
env.clone()
|
||||
.with_profile(cmd_overrides)
|
||||
.apply_to_command(&mut command);
|
||||
|
||||
let mut child = command.group_spawn()?;
|
||||
|
||||
|
||||
@@ -65,8 +65,9 @@ impl StandardCodingAgentExecutor for Amp {
|
||||
.current_dir(current_dir)
|
||||
.args(&args);
|
||||
|
||||
// Apply environment variables
|
||||
env.apply_to_command(&mut command);
|
||||
env.clone()
|
||||
.with_profile(&self.cmd)
|
||||
.apply_to_command(&mut command);
|
||||
|
||||
let mut child = command.group_spawn()?;
|
||||
|
||||
@@ -137,8 +138,9 @@ impl StandardCodingAgentExecutor for Amp {
|
||||
.current_dir(current_dir)
|
||||
.args(&continue_args);
|
||||
|
||||
// Apply environment variables
|
||||
env.apply_to_command(&mut command);
|
||||
env.clone()
|
||||
.with_profile(&self.cmd)
|
||||
.apply_to_command(&mut command);
|
||||
|
||||
let mut child = command.group_spawn()?;
|
||||
|
||||
|
||||
@@ -249,8 +249,9 @@ impl ClaudeCode {
|
||||
.current_dir(current_dir)
|
||||
.args(&args);
|
||||
|
||||
// Apply environment variables
|
||||
env.apply_to_command(&mut command);
|
||||
env.clone()
|
||||
.with_profile(&self.cmd)
|
||||
.apply_to_command(&mut command);
|
||||
|
||||
// Remove ANTHROPIC_API_KEY if disable_api_key is enabled
|
||||
if self.disable_api_key.unwrap_or(false) {
|
||||
@@ -2015,6 +2016,7 @@ mod tests {
|
||||
cmd: crate::command::CmdOverrides {
|
||||
base_command_override: None,
|
||||
additional_params: None,
|
||||
env: None,
|
||||
},
|
||||
approvals_service: None,
|
||||
disable_api_key: None,
|
||||
|
||||
@@ -314,8 +314,9 @@ impl Codex {
|
||||
.env("NO_COLOR", "1")
|
||||
.env("RUST_LOG", "error");
|
||||
|
||||
// Apply environment variables
|
||||
env.apply_to_command(&mut process);
|
||||
env.clone()
|
||||
.with_profile(&self.cmd)
|
||||
.apply_to_command(&mut process);
|
||||
|
||||
let mut child = process.group_spawn()?;
|
||||
|
||||
|
||||
@@ -122,8 +122,9 @@ impl StandardCodingAgentExecutor for Copilot {
|
||||
.args(&args)
|
||||
.env("NODE_NO_WARNINGS", "1");
|
||||
|
||||
// Apply environment variables
|
||||
env.apply_to_command(&mut command);
|
||||
env.clone()
|
||||
.with_profile(&self.cmd)
|
||||
.apply_to_command(&mut command);
|
||||
|
||||
let mut child = command.group_spawn()?;
|
||||
|
||||
@@ -165,8 +166,9 @@ impl StandardCodingAgentExecutor for Copilot {
|
||||
.args(&args)
|
||||
.env("NODE_NO_WARNINGS", "1");
|
||||
|
||||
// Apply environment variables
|
||||
env.apply_to_command(&mut command);
|
||||
env.clone()
|
||||
.with_profile(&self.cmd)
|
||||
.apply_to_command(&mut command);
|
||||
|
||||
let mut child = command.group_spawn()?;
|
||||
|
||||
|
||||
@@ -92,8 +92,9 @@ impl StandardCodingAgentExecutor for CursorAgent {
|
||||
.current_dir(current_dir)
|
||||
.args(&args);
|
||||
|
||||
// Apply environment variables
|
||||
env.apply_to_command(&mut command);
|
||||
env.clone()
|
||||
.with_profile(&self.cmd)
|
||||
.apply_to_command(&mut command);
|
||||
|
||||
let mut child = command.group_spawn()?;
|
||||
|
||||
@@ -130,8 +131,9 @@ impl StandardCodingAgentExecutor for CursorAgent {
|
||||
.current_dir(current_dir)
|
||||
.args(&args);
|
||||
|
||||
// Apply environment variables
|
||||
env.apply_to_command(&mut command);
|
||||
env.clone()
|
||||
.with_profile(&self.cmd)
|
||||
.apply_to_command(&mut command);
|
||||
|
||||
let mut child = command.group_spawn()?;
|
||||
|
||||
|
||||
@@ -110,6 +110,7 @@ async fn spawn_droid(
|
||||
prompt: &String,
|
||||
current_dir: &Path,
|
||||
env: &ExecutionEnv,
|
||||
cmd_overrides: &crate::command::CmdOverrides,
|
||||
) -> Result<SpawnedChild, ExecutorError> {
|
||||
let (program_path, args) = command_parts.into_resolved().await?;
|
||||
|
||||
@@ -122,8 +123,9 @@ async fn spawn_droid(
|
||||
.current_dir(current_dir)
|
||||
.args(args);
|
||||
|
||||
// Apply environment variables
|
||||
env.apply_to_command(&mut command);
|
||||
env.clone()
|
||||
.with_profile(cmd_overrides)
|
||||
.apply_to_command(&mut command);
|
||||
|
||||
let mut child = command.group_spawn()?;
|
||||
|
||||
@@ -146,7 +148,7 @@ impl StandardCodingAgentExecutor for Droid {
|
||||
let droid_command = self.build_command_builder().build_initial()?;
|
||||
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||
|
||||
spawn_droid(droid_command, &combined_prompt, current_dir, env).await
|
||||
spawn_droid(droid_command, &combined_prompt, current_dir, env, &self.cmd).await
|
||||
}
|
||||
|
||||
async fn spawn_follow_up(
|
||||
@@ -166,7 +168,7 @@ impl StandardCodingAgentExecutor for Droid {
|
||||
.build_follow_up(&["--session-id".to_string(), forked_session_id.clone()])?;
|
||||
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||
|
||||
spawn_droid(continue_cmd, &combined_prompt, current_dir, env).await
|
||||
spawn_droid(continue_cmd, &combined_prompt, current_dir, env, &self.cmd).await
|
||||
}
|
||||
|
||||
fn normalize_logs(&self, msg_store: Arc<MsgStore>, current_dir: &Path) {
|
||||
|
||||
@@ -59,7 +59,7 @@ impl StandardCodingAgentExecutor for Gemini {
|
||||
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||
let gemini_command = self.build_command_builder().build_initial()?;
|
||||
harness
|
||||
.spawn_with_command(current_dir, combined_prompt, gemini_command, env)
|
||||
.spawn_with_command(current_dir, combined_prompt, gemini_command, env, &self.cmd)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ impl StandardCodingAgentExecutor for Gemini {
|
||||
session_id,
|
||||
gemini_command,
|
||||
env,
|
||||
&self.cmd,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -154,8 +154,9 @@ impl StandardCodingAgentExecutor for Opencode {
|
||||
.env("OPENCODE_AUTO_SHARE", "1")
|
||||
.env("OPENCODE_API", bridge.base_url.clone());
|
||||
|
||||
// Apply environment variables
|
||||
env.apply_to_command(&mut command);
|
||||
env.clone()
|
||||
.with_profile(&self.cmd)
|
||||
.apply_to_command(&mut command);
|
||||
|
||||
let mut child = match command.group_spawn() {
|
||||
Ok(c) => c,
|
||||
@@ -224,8 +225,9 @@ impl StandardCodingAgentExecutor for Opencode {
|
||||
.env("OPENCODE_AUTO_SHARE", "1")
|
||||
.env("OPENCODE_API", bridge.base_url.clone());
|
||||
|
||||
// Apply environment variables
|
||||
env.apply_to_command(&mut command);
|
||||
env.clone()
|
||||
.with_profile(&self.cmd)
|
||||
.apply_to_command(&mut command);
|
||||
|
||||
let mut child = match command.group_spawn() {
|
||||
Ok(c) => c,
|
||||
|
||||
@@ -49,7 +49,7 @@ impl StandardCodingAgentExecutor for QwenCode {
|
||||
let combined_prompt = self.append_prompt.combine_prompt(prompt);
|
||||
let harness = AcpAgentHarness::with_session_namespace("qwen_sessions");
|
||||
harness
|
||||
.spawn_with_command(current_dir, combined_prompt, qwen_command, env)
|
||||
.spawn_with_command(current_dir, combined_prompt, qwen_command, env, &self.cmd)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ impl StandardCodingAgentExecutor for QwenCode {
|
||||
session_id,
|
||||
qwen_command,
|
||||
env,
|
||||
&self.cmd,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useEffect, useState } from 'react';
|
||||
import { useMemo, useEffect, useState, useCallback } from 'react';
|
||||
import Form from '@rjsf/core';
|
||||
import type { IChangeEvent } from '@rjsf/core';
|
||||
import { RJSFValidationError } from '@rjsf/utils';
|
||||
@@ -44,6 +44,38 @@ export function ExecutorConfigForm({
|
||||
return schemas[executor];
|
||||
}, [executor]);
|
||||
|
||||
// Custom handler for env field updates
|
||||
const handleEnvChange = useCallback(
|
||||
(envData: Record<string, string> | undefined) => {
|
||||
const newFormData = {
|
||||
...(formData as Record<string, unknown>),
|
||||
env: envData,
|
||||
};
|
||||
setFormData(newFormData);
|
||||
if (onChange) {
|
||||
onChange(newFormData);
|
||||
}
|
||||
},
|
||||
[formData, onChange]
|
||||
);
|
||||
|
||||
const uiSchema = useMemo(
|
||||
() => ({
|
||||
env: {
|
||||
'ui:field': 'KeyValueField',
|
||||
},
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Pass the env update handler via formContext
|
||||
const formContext = useMemo(
|
||||
() => ({
|
||||
onEnvChange: handleEnvChange,
|
||||
}),
|
||||
[handleEnvChange]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setFormData(value || {});
|
||||
setValidationErrors([]);
|
||||
@@ -87,7 +119,9 @@ export function ExecutorConfigForm({
|
||||
<CardContent className="p-0">
|
||||
<Form
|
||||
schema={schema}
|
||||
uiSchema={uiSchema}
|
||||
formData={formData}
|
||||
formContext={formContext}
|
||||
onChange={handleChange}
|
||||
onSubmit={handleSubmit}
|
||||
onError={handleError}
|
||||
@@ -97,6 +131,7 @@ export function ExecutorConfigForm({
|
||||
showErrorList={false}
|
||||
widgets={shadcnTheme.widgets}
|
||||
templates={shadcnTheme.templates}
|
||||
fields={shadcnTheme.fields}
|
||||
>
|
||||
{onSave && (
|
||||
<div className="flex justify-end pt-4">
|
||||
|
||||
137
frontend/src/components/rjsf/fields/KeyValueField.tsx
Normal file
137
frontend/src/components/rjsf/fields/KeyValueField.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
import { FieldProps } from '@rjsf/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Plus, X } from 'lucide-react';
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
|
||||
type KeyValueData = Record<string, string>;
|
||||
|
||||
interface EnvFormContext {
|
||||
onEnvChange?: (envData: KeyValueData | undefined) => void;
|
||||
}
|
||||
|
||||
export function KeyValueField({
|
||||
formData,
|
||||
disabled,
|
||||
readonly,
|
||||
registry,
|
||||
}: FieldProps<KeyValueData>) {
|
||||
const [newKey, setNewKey] = useState('');
|
||||
const [newValue, setNewValue] = useState('');
|
||||
|
||||
// Get the custom env change handler from formContext
|
||||
const formContext = registry.formContext as EnvFormContext | undefined;
|
||||
|
||||
// Ensure we have a stable object reference
|
||||
const data: KeyValueData = useMemo(() => formData ?? {}, [formData]);
|
||||
const entries = useMemo(() => Object.entries(data), [data]);
|
||||
|
||||
// Use the formContext handler to update env correctly
|
||||
const updateValue = useCallback(
|
||||
(newData: KeyValueData | undefined) => {
|
||||
formContext?.onEnvChange?.(newData);
|
||||
},
|
||||
[formContext]
|
||||
);
|
||||
|
||||
const handleAdd = useCallback(() => {
|
||||
const trimmedKey = newKey.trim();
|
||||
if (trimmedKey) {
|
||||
updateValue({
|
||||
...data,
|
||||
[trimmedKey]: newValue,
|
||||
});
|
||||
setNewKey('');
|
||||
setNewValue('');
|
||||
}
|
||||
}, [data, newKey, newValue, updateValue]);
|
||||
|
||||
const handleRemove = useCallback(
|
||||
(key: string) => {
|
||||
const updated = { ...data };
|
||||
delete updated[key];
|
||||
updateValue(Object.keys(updated).length > 0 ? updated : undefined);
|
||||
},
|
||||
[data, updateValue]
|
||||
);
|
||||
|
||||
const handleValueChange = useCallback(
|
||||
(key: string, value: string) => {
|
||||
updateValue({ ...data, [key]: value });
|
||||
},
|
||||
[data, updateValue]
|
||||
);
|
||||
|
||||
const isDisabled = disabled || readonly;
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{entries.map(([key, value]) => (
|
||||
<div key={key} className="flex gap-2 items-center">
|
||||
<Input
|
||||
value={key}
|
||||
disabled
|
||||
className="flex-1 font-mono text-sm"
|
||||
aria-label="Environment variable key"
|
||||
/>
|
||||
<Input
|
||||
value={value ?? ''}
|
||||
onChange={(e) => handleValueChange(key, e.target.value)}
|
||||
disabled={isDisabled}
|
||||
className="flex-1 font-mono text-sm"
|
||||
placeholder="Value"
|
||||
aria-label={`Value for ${key}`}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleRemove(key)}
|
||||
disabled={isDisabled}
|
||||
className="h-8 w-8 p-0 shrink-0 text-muted-foreground hover:text-destructive hover:bg-destructive/10"
|
||||
aria-label={`Remove ${key}`}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Add new entry row */}
|
||||
<div className="flex gap-2 items-center">
|
||||
<Input
|
||||
value={newKey}
|
||||
onChange={(e) => setNewKey(e.target.value)}
|
||||
disabled={isDisabled}
|
||||
placeholder="KEY"
|
||||
className="flex-1 font-mono text-sm"
|
||||
aria-label="New environment variable key"
|
||||
/>
|
||||
<Input
|
||||
value={newValue}
|
||||
onChange={(e) => setNewValue(e.target.value)}
|
||||
disabled={isDisabled}
|
||||
placeholder="value"
|
||||
className="flex-1 font-mono text-sm"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleAdd();
|
||||
}
|
||||
}}
|
||||
aria-label="New environment variable value"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleAdd}
|
||||
disabled={isDisabled || !newKey.trim()}
|
||||
className="h-8 w-8 p-0 shrink-0"
|
||||
aria-label="Add environment variable"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
frontend/src/components/rjsf/fields/index.ts
Normal file
1
frontend/src/components/rjsf/fields/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { KeyValueField } from './KeyValueField';
|
||||
@@ -1,3 +1,9 @@
|
||||
export { shadcnTheme, customWidgets, customTemplates } from './theme';
|
||||
export {
|
||||
shadcnTheme,
|
||||
customWidgets,
|
||||
customTemplates,
|
||||
customFields,
|
||||
} from './theme';
|
||||
export * from './widgets';
|
||||
export * from './templates';
|
||||
export * from './fields';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RegistryWidgetsType } from '@rjsf/utils';
|
||||
import { RegistryFieldsType, RegistryWidgetsType } from '@rjsf/utils';
|
||||
import {
|
||||
TextWidget,
|
||||
SelectWidget,
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ObjectFieldTemplate,
|
||||
FormTemplate,
|
||||
} from './templates';
|
||||
import { KeyValueField } from './fields';
|
||||
|
||||
export const customWidgets: RegistryWidgetsType = {
|
||||
TextWidget,
|
||||
@@ -29,7 +30,12 @@ export const customTemplates = {
|
||||
FormTemplate,
|
||||
};
|
||||
|
||||
export const customFields: RegistryFieldsType = {
|
||||
KeyValueField,
|
||||
};
|
||||
|
||||
export const shadcnTheme = {
|
||||
widgets: customWidgets,
|
||||
templates: customTemplates,
|
||||
fields: customFields,
|
||||
};
|
||||
|
||||
@@ -515,6 +515,7 @@ export function AgentSettings() {
|
||||
selectedConfiguration
|
||||
]?.[selectedExecutorType] && (
|
||||
<ExecutorConfigForm
|
||||
key={`${selectedExecutorType}-${selectedConfiguration}`}
|
||||
executor={selectedExecutorType}
|
||||
value={
|
||||
(executorsMap[selectedExecutorType][
|
||||
|
||||
@@ -37,6 +37,17 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"title": "Environment Variables",
|
||||
"description": "Environment variables to set when running the executor",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -65,6 +65,17 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"title": "Environment Variables",
|
||||
"description": "Environment variables to set when running the executor",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -145,6 +145,17 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"title": "Environment Variables",
|
||||
"description": "Environment variables to set when running the executor",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -71,6 +71,17 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"title": "Environment Variables",
|
||||
"description": "Environment variables to set when running the executor",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -43,6 +43,17 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"title": "Environment Variables",
|
||||
"description": "Environment variables to set when running the executor",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -67,6 +67,17 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"title": "Environment Variables",
|
||||
"description": "Environment variables to set when running the executor",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Droid executor configuration",
|
||||
|
||||
@@ -41,6 +41,17 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"title": "Environment Variables",
|
||||
"description": "Environment variables to set when running the executor",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -41,6 +41,17 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"title": "Environment Variables",
|
||||
"description": "Environment variables to set when running the executor",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -35,6 +35,17 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"title": "Environment Variables",
|
||||
"description": "Environment variables to set when running the executor",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -356,13 +356,13 @@ export type ExecutorConfigs = { executors: { [key in BaseCodingAgent]?: Executor
|
||||
|
||||
export enum BaseAgentCapability { SESSION_FORK = "SESSION_FORK", SETUP_HELPER = "SETUP_HELPER" }
|
||||
|
||||
export type ClaudeCode = { append_prompt: AppendPrompt, claude_code_router?: boolean | null, plan?: boolean | null, approvals?: boolean | null, model?: string | null, dangerously_skip_permissions?: boolean | null, disable_api_key?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||
export type ClaudeCode = { append_prompt: AppendPrompt, claude_code_router?: boolean | null, plan?: boolean | null, approvals?: boolean | null, model?: string | null, dangerously_skip_permissions?: boolean | null, disable_api_key?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, env?: { [key in string]?: string } | null, };
|
||||
|
||||
export type Gemini = { append_prompt: AppendPrompt, model?: string | null, yolo?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||
export type Gemini = { append_prompt: AppendPrompt, model?: string | null, yolo?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, env?: { [key in string]?: string } | null, };
|
||||
|
||||
export type Amp = { append_prompt: AppendPrompt, dangerously_allow_all?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||
export type Amp = { append_prompt: AppendPrompt, dangerously_allow_all?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, env?: { [key in string]?: string } | null, };
|
||||
|
||||
export type Codex = { append_prompt: AppendPrompt, sandbox?: SandboxMode | null, ask_for_approval?: AskForApproval | null, oss?: boolean | null, model?: string | null, model_reasoning_effort?: ReasoningEffort | null, model_reasoning_summary?: ReasoningSummary | null, model_reasoning_summary_format?: ReasoningSummaryFormat | null, profile?: string | null, base_instructions?: string | null, include_apply_patch_tool?: boolean | null, model_provider?: string | null, compact_prompt?: string | null, developer_instructions?: string | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||
export type Codex = { append_prompt: AppendPrompt, sandbox?: SandboxMode | null, ask_for_approval?: AskForApproval | null, oss?: boolean | null, model?: string | null, model_reasoning_effort?: ReasoningEffort | null, model_reasoning_summary?: ReasoningSummary | null, model_reasoning_summary_format?: ReasoningSummaryFormat | null, profile?: string | null, base_instructions?: string | null, include_apply_patch_tool?: boolean | null, model_provider?: string | null, compact_prompt?: string | null, developer_instructions?: string | null, base_command_override?: string | null, additional_params?: Array<string> | null, env?: { [key in string]?: string } | null, };
|
||||
|
||||
export type SandboxMode = "auto" | "read-only" | "workspace-write" | "danger-full-access";
|
||||
|
||||
@@ -374,15 +374,15 @@ export type ReasoningSummary = "auto" | "concise" | "detailed" | "none";
|
||||
|
||||
export type ReasoningSummaryFormat = "none" | "experimental";
|
||||
|
||||
export type CursorAgent = { append_prompt: AppendPrompt, force?: boolean | null, model?: string | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||
export type CursorAgent = { append_prompt: AppendPrompt, force?: boolean | null, model?: string | null, base_command_override?: string | null, additional_params?: Array<string> | null, env?: { [key in string]?: string } | null, };
|
||||
|
||||
export type Copilot = { append_prompt: AppendPrompt, model?: string | null, allow_all_tools?: boolean | null, allow_tool?: string | null, deny_tool?: string | null, add_dir?: Array<string> | null, disable_mcp_server?: Array<string> | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||
export type Copilot = { append_prompt: AppendPrompt, model?: string | null, allow_all_tools?: boolean | null, allow_tool?: string | null, deny_tool?: string | null, add_dir?: Array<string> | null, disable_mcp_server?: Array<string> | null, base_command_override?: string | null, additional_params?: Array<string> | null, env?: { [key in string]?: string } | null, };
|
||||
|
||||
export type Opencode = { append_prompt: AppendPrompt, model?: string | null, agent?: string | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||
export type Opencode = { append_prompt: AppendPrompt, model?: string | null, agent?: string | null, base_command_override?: string | null, additional_params?: Array<string> | null, env?: { [key in string]?: string } | null, };
|
||||
|
||||
export type QwenCode = { append_prompt: AppendPrompt, yolo?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||
export type QwenCode = { append_prompt: AppendPrompt, yolo?: boolean | null, base_command_override?: string | null, additional_params?: Array<string> | null, env?: { [key in string]?: string } | null, };
|
||||
|
||||
export type Droid = { append_prompt: AppendPrompt, autonomy: Autonomy, model?: string | null, reasoning_effort?: DroidReasoningEffort | null, base_command_override?: string | null, additional_params?: Array<string> | null, };
|
||||
export type Droid = { append_prompt: AppendPrompt, autonomy: Autonomy, model?: string | null, reasoning_effort?: DroidReasoningEffort | null, base_command_override?: string | null, additional_params?: Array<string> | null, env?: { [key in string]?: string } | null, };
|
||||
|
||||
export type Autonomy = "normal" | "low" | "medium" | "high" | "skip-permissions-unsafe";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user