forked from zeroclaw-labs/zeroclaw
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathschema.rs
More file actions
7652 lines (6751 loc) Β· 259 KB
/
schema.rs
File metadata and controls
7652 lines (6751 loc) Β· 259 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
use crate::config::traits::ChannelConfig;
use crate::providers::{is_glm_alias, is_zai_alias};
use crate::security::{AutonomyLevel, DomainMatcher};
use anyhow::{Context, Result};
use directories::UserDirs;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{OnceLock, RwLock};
#[cfg(unix)]
use tokio::fs::File;
use tokio::fs::{self, OpenOptions};
use tokio::io::AsyncWriteExt;
const SUPPORTED_PROXY_SERVICE_KEYS: &[&str] = &[
"provider.anthropic",
"provider.compatible",
"provider.copilot",
"provider.gemini",
"provider.glm",
"provider.ollama",
"provider.openai",
"provider.openrouter",
"channel.dingtalk",
"channel.discord",
"channel.feishu",
"channel.lark",
"channel.matrix",
"channel.mattermost",
"channel.nextcloud_talk",
"channel.qq",
"channel.signal",
"channel.slack",
"channel.telegram",
"channel.wati",
"channel.whatsapp",
"tool.browser",
"tool.composio",
"tool.http_request",
"tool.pushover",
"memory.embeddings",
"tunnel.custom",
"transcription.groq",
];
const SUPPORTED_PROXY_SERVICE_SELECTORS: &[&str] = &[
"provider.*",
"channel.*",
"tool.*",
"memory.*",
"tunnel.*",
"transcription.*",
];
static RUNTIME_PROXY_CONFIG: OnceLock<RwLock<ProxyConfig>> = OnceLock::new();
static RUNTIME_PROXY_CLIENT_CACHE: OnceLock<RwLock<HashMap<String, reqwest::Client>>> =
OnceLock::new();
// ββ Top-level config ββββββββββββββββββββββββββββββββββββββββββββββ
/// Top-level ZeroClaw configuration, loaded from `config.toml`.
///
/// Resolution order: `ZEROCLAW_WORKSPACE` env β `active_workspace.toml` marker β `~/.zeroclaw/config.toml`.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct Config {
/// Workspace directory - computed from home, not serialized
#[serde(skip)]
pub workspace_dir: PathBuf,
/// Path to config.toml - computed from home, not serialized
#[serde(skip)]
pub config_path: PathBuf,
/// API key for the selected provider. Overridden by `ZEROCLAW_API_KEY` or `API_KEY` env vars.
pub api_key: Option<String>,
/// Base URL override for provider API (e.g. "http://10.0.0.1:11434" for remote Ollama)
pub api_url: Option<String>,
/// Default provider ID or alias (e.g. `"openrouter"`, `"ollama"`, `"anthropic"`). Default: `"openrouter"`.
#[serde(alias = "model_provider")]
pub default_provider: Option<String>,
/// Default model routed through the selected provider (e.g. `"anthropic/claude-sonnet-4-6"`).
#[serde(alias = "model")]
pub default_model: Option<String>,
/// Optional named provider profiles keyed by id (Codex app-server compatible layout).
#[serde(default)]
pub model_providers: HashMap<String, ModelProviderConfig>,
/// Default model temperature (0.0β2.0). Default: `0.7`.
#[serde(default = "default_temperature")]
pub default_temperature: f64,
/// Observability backend configuration (`[observability]`).
#[serde(default)]
pub observability: ObservabilityConfig,
/// Autonomy and security policy configuration (`[autonomy]`).
#[serde(default)]
pub autonomy: AutonomyConfig,
/// Security subsystem configuration (`[security]`).
#[serde(default)]
pub security: SecurityConfig,
/// Runtime adapter configuration (`[runtime]`). Controls native vs Docker execution.
#[serde(default)]
pub runtime: RuntimeConfig,
/// Reliability settings: retries, fallback providers, backoff (`[reliability]`).
#[serde(default)]
pub reliability: ReliabilityConfig,
/// Scheduler configuration for periodic task execution (`[scheduler]`).
#[serde(default)]
pub scheduler: SchedulerConfig,
/// Agent orchestration settings (`[agent]`).
#[serde(default)]
pub agent: AgentConfig,
/// Skills loading and community repository behavior (`[skills]`).
#[serde(default)]
pub skills: SkillsConfig,
/// Model routing rules β route `hint:<name>` to specific provider+model combos.
#[serde(default)]
pub model_routes: Vec<ModelRouteConfig>,
/// Embedding routing rules β route `hint:<name>` to specific provider+model combos.
#[serde(default)]
pub embedding_routes: Vec<EmbeddingRouteConfig>,
/// Automatic query classification β maps user messages to model hints.
#[serde(default)]
pub query_classification: QueryClassificationConfig,
/// Heartbeat configuration for periodic health pings (`[heartbeat]`).
#[serde(default)]
pub heartbeat: HeartbeatConfig,
/// Cron job configuration (`[cron]`).
#[serde(default)]
pub cron: CronConfig,
/// Channel configurations: Telegram, Discord, Slack, etc. (`[channels_config]`).
#[serde(default)]
pub channels_config: ChannelsConfig,
/// Memory backend configuration: sqlite, markdown, embeddings (`[memory]`).
#[serde(default)]
pub memory: MemoryConfig,
/// Persistent storage provider configuration (`[storage]`).
#[serde(default)]
pub storage: StorageConfig,
/// Tunnel configuration for exposing the gateway publicly (`[tunnel]`).
#[serde(default)]
pub tunnel: TunnelConfig,
/// Gateway server configuration: host, port, pairing, rate limits (`[gateway]`).
#[serde(default)]
pub gateway: GatewayConfig,
/// Composio managed OAuth tools integration (`[composio]`).
#[serde(default)]
pub composio: ComposioConfig,
/// Secrets encryption configuration (`[secrets]`).
#[serde(default)]
pub secrets: SecretsConfig,
/// Browser automation configuration (`[browser]`).
#[serde(default)]
pub browser: BrowserConfig,
/// HTTP request tool configuration (`[http_request]`).
#[serde(default)]
pub http_request: HttpRequestConfig,
/// Multimodal (image) handling configuration (`[multimodal]`).
#[serde(default)]
pub multimodal: MultimodalConfig,
/// Web fetch tool configuration (`[web_fetch]`).
#[serde(default)]
pub web_fetch: WebFetchConfig,
/// Web search tool configuration (`[web_search]`).
#[serde(default)]
pub web_search: WebSearchConfig,
/// Proxy configuration for outbound HTTP/HTTPS/SOCKS5 traffic (`[proxy]`).
#[serde(default)]
pub proxy: ProxyConfig,
/// Identity format configuration: OpenClaw or AIEOS (`[identity]`).
#[serde(default)]
pub identity: IdentityConfig,
/// Cost tracking and budget enforcement configuration (`[cost]`).
#[serde(default)]
pub cost: CostConfig,
/// Peripheral board configuration for hardware integration (`[peripherals]`).
#[serde(default)]
pub peripherals: PeripheralsConfig,
/// Delegate agent configurations for multi-agent workflows.
#[serde(default)]
pub agents: HashMap<String, DelegateAgentConfig>,
/// Hooks configuration (lifecycle hooks and built-in hook toggles).
#[serde(default)]
pub hooks: HooksConfig,
/// Hardware configuration (wizard-driven physical world setup).
#[serde(default)]
pub hardware: HardwareConfig,
/// Voice transcription configuration (Whisper API via Groq).
#[serde(default)]
pub transcription: TranscriptionConfig,
}
/// Named provider profile definition compatible with Codex app-server style config.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
pub struct ModelProviderConfig {
/// Optional provider type/name override (e.g. "openai", "openai-codex", or custom profile id).
#[serde(default)]
pub name: Option<String>,
/// Optional base URL for OpenAI-compatible endpoints.
#[serde(default)]
pub base_url: Option<String>,
/// Provider protocol variant ("responses" or "chat_completions").
#[serde(default)]
pub wire_api: Option<String>,
/// If true, load OpenAI auth material (OPENAI_API_KEY or ~/.codex/auth.json).
#[serde(default)]
pub requires_openai_auth: bool,
}
// ββ Delegate Agents ββββββββββββββββββββββββββββββββββββββββββββββ
/// Configuration for a delegate sub-agent used by the `delegate` tool.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct DelegateAgentConfig {
/// Provider name (e.g. "ollama", "openrouter", "anthropic")
pub provider: String,
/// Model name
pub model: String,
/// Optional system prompt for the sub-agent
#[serde(default)]
pub system_prompt: Option<String>,
/// Optional API key override
#[serde(default)]
pub api_key: Option<String>,
/// Temperature override
#[serde(default)]
pub temperature: Option<f64>,
/// Max recursion depth for nested delegation
#[serde(default = "default_max_depth")]
pub max_depth: u32,
/// Enable agentic sub-agent mode (multi-turn tool-call loop).
#[serde(default)]
pub agentic: bool,
/// Allowlist of tool names available to the sub-agent in agentic mode.
#[serde(default)]
pub allowed_tools: Vec<String>,
/// Maximum tool-call iterations in agentic mode.
#[serde(default = "default_max_tool_iterations")]
pub max_iterations: usize,
}
fn default_temperature() -> f64 {
0.7
}
fn default_max_depth() -> u32 {
3
}
fn default_max_tool_iterations() -> usize {
10
}
// ββ Hardware Config (wizard-driven) βββββββββββββββββββββββββββββ
/// Hardware transport mode.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
pub enum HardwareTransport {
#[default]
None,
Native,
Serial,
Probe,
}
impl std::fmt::Display for HardwareTransport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Native => write!(f, "native"),
Self::Serial => write!(f, "serial"),
Self::Probe => write!(f, "probe"),
}
}
}
/// Wizard-driven hardware configuration for physical world interaction.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct HardwareConfig {
/// Whether hardware access is enabled
#[serde(default)]
pub enabled: bool,
/// Transport mode
#[serde(default)]
pub transport: HardwareTransport,
/// Serial port path (e.g. "/dev/ttyACM0")
#[serde(default)]
pub serial_port: Option<String>,
/// Serial baud rate
#[serde(default = "default_baud_rate")]
pub baud_rate: u32,
/// Probe target chip (e.g. "STM32F401RE")
#[serde(default)]
pub probe_target: Option<String>,
/// Enable workspace datasheet RAG (index PDF schematics for AI pin lookups)
#[serde(default)]
pub workspace_datasheets: bool,
}
fn default_baud_rate() -> u32 {
115_200
}
impl HardwareConfig {
/// Return the active transport mode.
pub fn transport_mode(&self) -> HardwareTransport {
self.transport.clone()
}
}
impl Default for HardwareConfig {
fn default() -> Self {
Self {
enabled: false,
transport: HardwareTransport::None,
serial_port: None,
baud_rate: default_baud_rate(),
probe_target: None,
workspace_datasheets: false,
}
}
}
// ββ Transcription ββββββββββββββββββββββββββββββββββββββββββββββββ
fn default_transcription_api_url() -> String {
"https://api.groq.com/openai/v1/audio/transcriptions".into()
}
fn default_transcription_model() -> String {
"whisper-large-v3-turbo".into()
}
fn default_transcription_max_duration_secs() -> u64 {
120
}
/// Voice transcription configuration (Whisper API via Groq).
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct TranscriptionConfig {
/// Enable voice transcription for channels that support it.
#[serde(default)]
pub enabled: bool,
/// Whisper API endpoint URL.
#[serde(default = "default_transcription_api_url")]
pub api_url: String,
/// Whisper model name.
#[serde(default = "default_transcription_model")]
pub model: String,
/// Optional language hint (ISO-639-1, e.g. "en", "ru").
#[serde(default)]
pub language: Option<String>,
/// Maximum voice duration in seconds (messages longer than this are skipped).
#[serde(default = "default_transcription_max_duration_secs")]
pub max_duration_secs: u64,
}
impl Default for TranscriptionConfig {
fn default() -> Self {
Self {
enabled: false,
api_url: default_transcription_api_url(),
model: default_transcription_model(),
language: None,
max_duration_secs: default_transcription_max_duration_secs(),
}
}
}
/// Agent orchestration configuration (`[agent]` section).
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct AgentConfig {
/// When true: bootstrap_max_chars=6000, rag_chunk_limit=2. Use for 13B or smaller models.
#[serde(default)]
pub compact_context: bool,
/// Maximum tool-call loop turns per user message. Default: `10`.
/// Setting to `0` falls back to the safe default of `10`.
#[serde(default = "default_agent_max_tool_iterations")]
pub max_tool_iterations: usize,
/// Maximum conversation history messages retained per session. Default: `50`.
#[serde(default = "default_agent_max_history_messages")]
pub max_history_messages: usize,
/// Enable parallel tool execution within a single iteration. Default: `false`.
#[serde(default)]
pub parallel_tools: bool,
/// Tool dispatch strategy (e.g. `"auto"`). Default: `"auto"`.
#[serde(default = "default_agent_tool_dispatcher")]
pub tool_dispatcher: String,
}
fn default_agent_max_tool_iterations() -> usize {
10
}
fn default_agent_max_history_messages() -> usize {
50
}
fn default_agent_tool_dispatcher() -> String {
"auto".into()
}
impl Default for AgentConfig {
fn default() -> Self {
Self {
compact_context: false,
max_tool_iterations: default_agent_max_tool_iterations(),
max_history_messages: default_agent_max_history_messages(),
parallel_tools: false,
tool_dispatcher: default_agent_tool_dispatcher(),
}
}
}
/// Skills loading configuration (`[skills]` section).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum SkillsPromptInjectionMode {
/// Inline full skill instructions and tool metadata into the system prompt.
#[default]
Full,
/// Inline only compact skill metadata (name/description/location) and load details on demand.
Compact,
}
fn parse_skills_prompt_injection_mode(raw: &str) -> Option<SkillsPromptInjectionMode> {
match raw.trim().to_ascii_lowercase().as_str() {
"full" => Some(SkillsPromptInjectionMode::Full),
"compact" => Some(SkillsPromptInjectionMode::Compact),
_ => None,
}
}
/// Skills loading configuration (`[skills]` section).
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SkillsConfig {
/// Enable loading and syncing the community open-skills repository.
/// Default: `false` (opt-in).
#[serde(default)]
pub open_skills_enabled: bool,
/// Optional path to a local open-skills repository.
/// If unset, defaults to `$HOME/open-skills` when enabled.
#[serde(default)]
pub open_skills_dir: Option<String>,
/// Controls how skills are injected into the system prompt.
/// `full` preserves legacy behavior. `compact` keeps context small and loads skills on demand.
#[serde(default)]
pub prompt_injection_mode: SkillsPromptInjectionMode,
}
impl Default for SkillsConfig {
fn default() -> Self {
Self {
open_skills_enabled: false,
open_skills_dir: None,
prompt_injection_mode: SkillsPromptInjectionMode::default(),
}
}
}
/// Multimodal (image) handling configuration (`[multimodal]` section).
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct MultimodalConfig {
/// Maximum number of image attachments accepted per request.
#[serde(default = "default_multimodal_max_images")]
pub max_images: usize,
/// Maximum image payload size in MiB before base64 encoding.
#[serde(default = "default_multimodal_max_image_size_mb")]
pub max_image_size_mb: usize,
/// Allow fetching remote image URLs (http/https). Disabled by default.
#[serde(default)]
pub allow_remote_fetch: bool,
}
fn default_multimodal_max_images() -> usize {
4
}
fn default_multimodal_max_image_size_mb() -> usize {
5
}
impl MultimodalConfig {
/// Clamp configured values to safe runtime bounds.
pub fn effective_limits(&self) -> (usize, usize) {
let max_images = self.max_images.clamp(1, 16);
let max_image_size_mb = self.max_image_size_mb.clamp(1, 20);
(max_images, max_image_size_mb)
}
}
impl Default for MultimodalConfig {
fn default() -> Self {
Self {
max_images: default_multimodal_max_images(),
max_image_size_mb: default_multimodal_max_image_size_mb(),
allow_remote_fetch: false,
}
}
}
// ββ Identity (AIEOS / OpenClaw format) ββββββββββββββββββββββββββ
/// Identity format configuration (`[identity]` section).
///
/// Supports `"openclaw"` (default) or `"aieos"` identity documents.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct IdentityConfig {
/// Identity format: "openclaw" (default) or "aieos"
#[serde(default = "default_identity_format")]
pub format: String,
/// Path to AIEOS JSON file (relative to workspace)
#[serde(default)]
pub aieos_path: Option<String>,
/// Inline AIEOS JSON (alternative to file path)
#[serde(default)]
pub aieos_inline: Option<String>,
}
fn default_identity_format() -> String {
"openclaw".into()
}
impl Default for IdentityConfig {
fn default() -> Self {
Self {
format: default_identity_format(),
aieos_path: None,
aieos_inline: None,
}
}
}
// ββ Cost tracking and budget enforcement βββββββββββββββββββββββββββ
/// Cost tracking and budget enforcement configuration (`[cost]` section).
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct CostConfig {
/// Enable cost tracking (default: false)
#[serde(default)]
pub enabled: bool,
/// Daily spending limit in USD (default: 10.00)
#[serde(default = "default_daily_limit")]
pub daily_limit_usd: f64,
/// Monthly spending limit in USD (default: 100.00)
#[serde(default = "default_monthly_limit")]
pub monthly_limit_usd: f64,
/// Warn when spending reaches this percentage of limit (default: 80)
#[serde(default = "default_warn_percent")]
pub warn_at_percent: u8,
/// Allow requests to exceed budget with --override flag (default: false)
#[serde(default)]
pub allow_override: bool,
/// Per-model pricing (USD per 1M tokens)
#[serde(default)]
pub prices: std::collections::HashMap<String, ModelPricing>,
}
/// Per-model pricing entry (USD per 1M tokens).
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ModelPricing {
/// Input price per 1M tokens
#[serde(default)]
pub input: f64,
/// Output price per 1M tokens
#[serde(default)]
pub output: f64,
}
fn default_daily_limit() -> f64 {
10.0
}
fn default_monthly_limit() -> f64 {
100.0
}
fn default_warn_percent() -> u8 {
80
}
impl Default for CostConfig {
fn default() -> Self {
Self {
enabled: false,
daily_limit_usd: default_daily_limit(),
monthly_limit_usd: default_monthly_limit(),
warn_at_percent: default_warn_percent(),
allow_override: false,
prices: get_default_pricing(),
}
}
}
/// Default pricing for popular models (USD per 1M tokens)
fn get_default_pricing() -> std::collections::HashMap<String, ModelPricing> {
let mut prices = std::collections::HashMap::new();
// Anthropic models
prices.insert(
"anthropic/claude-sonnet-4-20250514".into(),
ModelPricing {
input: 3.0,
output: 15.0,
},
);
prices.insert(
"anthropic/claude-opus-4-20250514".into(),
ModelPricing {
input: 15.0,
output: 75.0,
},
);
prices.insert(
"anthropic/claude-3.5-sonnet".into(),
ModelPricing {
input: 3.0,
output: 15.0,
},
);
prices.insert(
"anthropic/claude-3-haiku".into(),
ModelPricing {
input: 0.25,
output: 1.25,
},
);
// OpenAI models
prices.insert(
"openai/gpt-4o".into(),
ModelPricing {
input: 5.0,
output: 15.0,
},
);
prices.insert(
"openai/gpt-4o-mini".into(),
ModelPricing {
input: 0.15,
output: 0.60,
},
);
prices.insert(
"openai/o1-preview".into(),
ModelPricing {
input: 15.0,
output: 60.0,
},
);
// Google models
prices.insert(
"google/gemini-2.0-flash".into(),
ModelPricing {
input: 0.10,
output: 0.40,
},
);
prices.insert(
"google/gemini-1.5-pro".into(),
ModelPricing {
input: 1.25,
output: 5.0,
},
);
prices
}
// ββ Peripherals (hardware: STM32, RPi GPIO, etc.) ββββββββββββββββββββββββ
/// Peripheral board integration configuration (`[peripherals]` section).
///
/// Boards become agent tools when enabled.
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
pub struct PeripheralsConfig {
/// Enable peripheral support (boards become agent tools)
#[serde(default)]
pub enabled: bool,
/// Board configurations (nucleo-f401re, rpi-gpio, etc.)
#[serde(default)]
pub boards: Vec<PeripheralBoardConfig>,
/// Path to datasheet docs (relative to workspace) for RAG retrieval.
/// Place .md/.txt files named by board (e.g. nucleo-f401re.md, rpi-gpio.md).
#[serde(default)]
pub datasheet_dir: Option<String>,
}
/// Configuration for a single peripheral board (e.g. STM32, RPi GPIO).
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct PeripheralBoardConfig {
/// Board type: "nucleo-f401re", "rpi-gpio", "esp32", etc.
pub board: String,
/// Transport: "serial", "native", "websocket"
#[serde(default = "default_peripheral_transport")]
pub transport: String,
/// Path for serial: "/dev/ttyACM0", "/dev/ttyUSB0"
#[serde(default)]
pub path: Option<String>,
/// Baud rate for serial (default: 115200)
#[serde(default = "default_peripheral_baud")]
pub baud: u32,
}
fn default_peripheral_transport() -> String {
"serial".into()
}
fn default_peripheral_baud() -> u32 {
115_200
}
impl Default for PeripheralBoardConfig {
fn default() -> Self {
Self {
board: String::new(),
transport: default_peripheral_transport(),
path: None,
baud: default_peripheral_baud(),
}
}
}
// ββ Gateway security βββββββββββββββββββββββββββββββββββββββββββββ
/// Gateway server configuration (`[gateway]` section).
///
/// Controls the HTTP gateway for webhook and pairing endpoints.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct GatewayConfig {
/// Gateway port (default: 42617)
#[serde(default = "default_gateway_port")]
pub port: u16,
/// Gateway host (default: 127.0.0.1)
#[serde(default = "default_gateway_host")]
pub host: String,
/// Require pairing before accepting requests (default: true)
#[serde(default = "default_true")]
pub require_pairing: bool,
/// Allow binding to non-localhost without a tunnel (default: false)
#[serde(default)]
pub allow_public_bind: bool,
/// Paired bearer tokens (managed automatically, not user-edited)
#[serde(default)]
pub paired_tokens: Vec<String>,
/// Max `/pair` requests per minute per client key.
#[serde(default = "default_pair_rate_limit")]
pub pair_rate_limit_per_minute: u32,
/// Max `/webhook` requests per minute per client key.
#[serde(default = "default_webhook_rate_limit")]
pub webhook_rate_limit_per_minute: u32,
/// Trust proxy-forwarded client IP headers (`X-Forwarded-For`, `X-Real-IP`).
/// Disabled by default; enable only behind a trusted reverse proxy.
#[serde(default)]
pub trust_forwarded_headers: bool,
/// Maximum distinct client keys tracked by gateway rate limiter maps.
#[serde(default = "default_gateway_rate_limit_max_keys")]
pub rate_limit_max_keys: usize,
/// TTL for webhook idempotency keys.
#[serde(default = "default_idempotency_ttl_secs")]
pub idempotency_ttl_secs: u64,
/// Maximum distinct idempotency keys retained in memory.
#[serde(default = "default_gateway_idempotency_max_keys")]
pub idempotency_max_keys: usize,
}
fn default_gateway_port() -> u16 {
42617
}
fn default_gateway_host() -> String {
"127.0.0.1".into()
}
fn default_pair_rate_limit() -> u32 {
10
}
fn default_webhook_rate_limit() -> u32 {
60
}
fn default_idempotency_ttl_secs() -> u64 {
300
}
fn default_gateway_rate_limit_max_keys() -> usize {
10_000
}
fn default_gateway_idempotency_max_keys() -> usize {
10_000
}
fn default_true() -> bool {
true
}
impl Default for GatewayConfig {
fn default() -> Self {
Self {
port: default_gateway_port(),
host: default_gateway_host(),
require_pairing: true,
allow_public_bind: false,
paired_tokens: Vec::new(),
pair_rate_limit_per_minute: default_pair_rate_limit(),
webhook_rate_limit_per_minute: default_webhook_rate_limit(),
trust_forwarded_headers: false,
rate_limit_max_keys: default_gateway_rate_limit_max_keys(),
idempotency_ttl_secs: default_idempotency_ttl_secs(),
idempotency_max_keys: default_gateway_idempotency_max_keys(),
}
}
}
// ββ Composio (managed tool surface) βββββββββββββββββββββββββββββ
/// Composio managed OAuth tools integration (`[composio]` section).
///
/// Provides access to 1000+ OAuth-connected tools via the Composio platform.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ComposioConfig {
/// Enable Composio integration for 1000+ OAuth tools
#[serde(default, alias = "enable")]
pub enabled: bool,
/// Composio API key (stored encrypted when secrets.encrypt = true)
#[serde(default)]
pub api_key: Option<String>,
/// Default entity ID for multi-user setups
#[serde(default = "default_entity_id")]
pub entity_id: String,
}
fn default_entity_id() -> String {
"default".into()
}
impl Default for ComposioConfig {
fn default() -> Self {
Self {
enabled: false,
api_key: None,
entity_id: default_entity_id(),
}
}
}
// ββ Secrets (encrypted credential store) ββββββββββββββββββββββββ
/// Secrets encryption configuration (`[secrets]` section).
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SecretsConfig {
/// Enable encryption for API keys and tokens in config.toml
#[serde(default = "default_true")]
pub encrypt: bool,
}
impl Default for SecretsConfig {
fn default() -> Self {
Self { encrypt: true }
}
}
// ββ Browser (friendly-service browsing only) βββββββββββββββββββ
/// Computer-use sidecar configuration (`[browser.computer_use]` section).
///
/// Delegates OS-level mouse, keyboard, and screenshot actions to a local sidecar.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct BrowserComputerUseConfig {
/// Sidecar endpoint for computer-use actions (OS-level mouse/keyboard/screenshot)
#[serde(default = "default_browser_computer_use_endpoint")]
pub endpoint: String,
/// Optional bearer token for computer-use sidecar
#[serde(default)]
pub api_key: Option<String>,
/// Per-action request timeout in milliseconds
#[serde(default = "default_browser_computer_use_timeout_ms")]
pub timeout_ms: u64,
/// Allow remote/public endpoint for computer-use sidecar (default: false)
#[serde(default)]
pub allow_remote_endpoint: bool,
/// Optional window title/process allowlist forwarded to sidecar policy
#[serde(default)]
pub window_allowlist: Vec<String>,
/// Optional X-axis boundary for coordinate-based actions
#[serde(default)]
pub max_coordinate_x: Option<i64>,
/// Optional Y-axis boundary for coordinate-based actions
#[serde(default)]
pub max_coordinate_y: Option<i64>,
}
fn default_browser_computer_use_endpoint() -> String {
"http://127.0.0.1:8787/v1/actions".into()
}
fn default_browser_computer_use_timeout_ms() -> u64 {
15_000
}
impl Default for BrowserComputerUseConfig {
fn default() -> Self {
Self {
endpoint: default_browser_computer_use_endpoint(),
api_key: None,
timeout_ms: default_browser_computer_use_timeout_ms(),
allow_remote_endpoint: false,
window_allowlist: Vec::new(),
max_coordinate_x: None,
max_coordinate_y: None,
}
}
}
/// Browser automation configuration (`[browser]` section).
///
/// Controls the `browser_open` tool and browser automation backends.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct BrowserConfig {
/// Enable `browser_open` tool (opens URLs in the system browser without scraping)
#[serde(default)]
pub enabled: bool,
/// Allowed domains for `browser_open` (exact or subdomain match)
#[serde(default)]
pub allowed_domains: Vec<String>,
/// Browser session name (for agent-browser automation)
#[serde(default)]
pub session_name: Option<String>,
/// Browser automation backend: "agent_browser" | "rust_native" | "computer_use" | "auto"
#[serde(default = "default_browser_backend")]
pub backend: String,
/// Headless mode for rust-native backend
#[serde(default = "default_true")]
pub native_headless: bool,
/// WebDriver endpoint URL for rust-native backend (e.g. http://127.0.0.1:9515)
#[serde(default = "default_browser_webdriver_url")]
pub native_webdriver_url: String,
/// Optional Chrome/Chromium executable path for rust-native backend
#[serde(default)]
pub native_chrome_path: Option<String>,
/// Computer-use sidecar configuration
#[serde(default)]
pub computer_use: BrowserComputerUseConfig,
}
fn default_browser_backend() -> String {
"agent_browser".into()
}
fn default_browser_webdriver_url() -> String {
"http://127.0.0.1:9515".into()
}
impl Default for BrowserConfig {
fn default() -> Self {