Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:26:08 +08:00
commit 8f22ddf339
295 changed files with 59710 additions and 0 deletions

View File

@@ -0,0 +1,247 @@
# Agent: meta.config.router
## Purpose
Configure Claude Code Router for Betty to support multi-model LLM routing across environments. This agent creates or previews a `config.json` file at `~/.claude-code-router/config.json` with model providers, routing profiles, and audit metadata.
## Version
0.1.0
## Status
active
## Reasoning Mode
oneshot
## Capabilities
- Generate multi-model LLM router configurations
- Validate router configuration inputs for correctness
- Apply configurations to filesystem with audit trails
- Support multiple output modes (preview, file, both)
- Work across local, cloud, and CI environments
- Ensure deterministic and portable configurations
## Skills Available
- `config.validate.router` - Validates router configuration inputs
- `config.generate.router` - Generates router configuration JSON
- `audit.log` - Records audit events for configuration changes
## Inputs
### llm_backends (required)
- **Type**: List of objects
- **Description**: Backend provider configurations
- **Schema**:
```json
[
{
"name": "string (e.g., openrouter, ollama, claude)",
"api_base_url": "string (API endpoint URL)",
"api_key": "string (optional for local providers)",
"models": ["string (model identifiers)"]
}
]
```
### routing_rules (required)
- **Type**: Dictionary
- **Description**: Mapping of Claude routing contexts to provider/model pairs
- **Contexts**: default, think, background, longContext
- **Schema**:
```json
{
"default": { "provider": "string", "model": "string" },
"think": { "provider": "string", "model": "string" },
"background": { "provider": "string", "model": "string" },
"longContext": { "provider": "string", "model": "string" }
}
```
### output_mode (optional)
- **Type**: enum
- **Values**: "preview" | "file" | "both"
- **Default**: "preview"
- **Description**: Output mode for configuration
### apply_config (optional)
- **Type**: boolean
- **Default**: false
- **Description**: Write config to disk if true
### metadata (optional)
- **Type**: object
- **Description**: Optional audit metadata (initiator, environment, etc.)
## Outputs
### routing_config
- **Type**: object
- **Description**: Rendered router config as JSON
### write_status
- **Type**: string
- **Values**: "success" | "skipped" | "error"
- **Description**: Status of file write operation
### audit_id
- **Type**: string
- **Description**: Unique trace ID for configuration event
## Behavior
1. Validates inputs via `config.validate.router`
2. Constructs valid router config using `config.generate.router`
3. If `apply_config=true` and `output_mode≠preview`, writes config to: `~/.claude-code-router/config.json`
4. Outputs JSON config regardless of write action
5. Logs audit record via `audit.log` with:
- timestamp
- initiator
- hash of input
- environment fingerprint
## Usage Example
```bash
# Preview configuration (no file write)
/meta/config.router --routing_config_path=router-config.yaml
# Apply configuration to disk
/meta/config.router --routing_config_path=router-config.yaml --apply_config=true
# Both preview and write
/meta/config.router --routing_config_path=router-config.yaml --apply_config=true --output_mode=both
```
## Example Input (YAML)
```yaml
llm_backends:
- name: openrouter
api_base_url: https://openrouter.ai/api/v1
api_key: ${OPENROUTER_API_KEY}
models:
- anthropic/claude-3.5-sonnet
- openai/gpt-4
- name: ollama
api_base_url: http://localhost:11434/v1
models:
- llama3.1:70b
- codellama:34b
routing_rules:
default:
provider: openrouter
model: anthropic/claude-3.5-sonnet
think:
provider: openrouter
model: anthropic/claude-3.5-sonnet
background:
provider: ollama
model: llama3.1:70b
longContext:
provider: openrouter
model: anthropic/claude-3.5-sonnet
metadata:
initiator: user@example.com
environment: production
purpose: Multi-model routing for development
```
## Example Output
```json
{
"version": "1.0.0",
"generated_at": "2025-11-01T12:34:56Z",
"backends": [
{
"name": "openrouter",
"api_base_url": "https://openrouter.ai/api/v1",
"api_key": "${OPENROUTER_API_KEY}",
"models": [
"anthropic/claude-3.5-sonnet",
"openai/gpt-4"
]
},
{
"name": "ollama",
"api_base_url": "http://localhost:11434/v1",
"models": [
"llama3.1:70b",
"codellama:34b"
]
}
],
"routing": {
"default": {
"provider": "openrouter",
"model": "anthropic/claude-3.5-sonnet"
},
"think": {
"provider": "openrouter",
"model": "anthropic/claude-3.5-sonnet"
},
"background": {
"provider": "ollama",
"model": "llama3.1:70b"
},
"longContext": {
"provider": "openrouter",
"model": "anthropic/claude-3.5-sonnet"
}
},
"metadata": {
"generated_by": "meta.config.router",
"schema_version": "1.0.0",
"initiator": "user@example.com",
"environment": "production",
"purpose": "Multi-model routing for development"
}
}
```
## Permissions
- `filesystem:read` - Read router config input files
- `filesystem:write` - Write config to ~/.claude-code-router/config.json
## Artifacts
### Consumes
- `router-config-input` - User-provided router configuration inputs
### Produces
- `llm-router-config` - Complete Claude Code Router configuration file
- `audit-log-entry` - Audit trail entry for configuration events
## Tags
llm, router, configuration, meta, infra, openrouter, claude, ollama, multi-model
## Environments
- local
- cloud
- ci
## Requires Human Approval
false
## Notes
- The config is deterministic and portable across environments
- API keys can use environment variable substitution (e.g., ${OPENROUTER_API_KEY})
- Local providers (localhost/127.0.0.1) don't require API keys
- All configuration changes are audited for traceability
- The agent supports preview mode to verify configuration before applying

View File

@@ -0,0 +1,92 @@
name: meta.config.router
version: 0.1.0
description: |
Configure Claude Code Router for Betty to support multi-model LLM routing across environments.
This agent creates or previews a config.json file at ~/.claude-code-router/config.json with
model providers, routing profiles, and audit metadata. Works across local, cloud, or CI-based
environments with built-in validation, output rendering, config application, and auditing.
status: active
reasoning_mode: oneshot
capabilities:
- Generate multi-model LLM router configurations
- Validate router configuration inputs for correctness
- Apply configurations to filesystem with audit trails
- Support multiple output modes (preview, file, both)
- Work across local, cloud, and CI environments
- Ensure deterministic and portable configurations
skills_available:
- config.validate.router
- config.generate.router
- audit.log
permissions:
- filesystem:read
- filesystem:write
artifact_metadata:
consumes:
- type: router-config-input
description: User-provided router configuration inputs (backends, routing rules, metadata)
file_pattern: "*-router-input.{json,yaml}"
content_type: application/json
required: true
produces:
- type: llm-router-config
description: Complete Claude Code Router configuration file
file_pattern: "config.json"
content_type: application/json
schema: schemas/router-config.json
- type: audit-log-entry
description: Audit trail entry for configuration events
file_pattern: "audit_log.json"
content_type: application/json
system_prompt: |
You are the meta.config.router agent for the Betty Framework.
Your responsibilities:
1. Validate router configuration inputs using config.validate.router
2. Generate valid router config JSON using config.generate.router
3. Write config to ~/.claude-code-router/config.json when apply_config=true
4. Provide preview, file write, or both modes based on output_mode
5. Log audit records with timestamp, initiator, and environment fingerprint
Inputs you expect:
- llm_backends: List of provider configs (name, api_base_url, api_key, models)
- routing_rules: Mapping of routing contexts (default, think, background, longContext)
- output_mode: "preview" | "file" | "both" (default: preview)
- apply_config: boolean (write to disk if true)
- metadata: Optional audit metadata (initiator, environment, etc.)
Outputs you generate:
- routing_config: Complete router configuration JSON
- write_status: "success" | "skipped" | "error"
- audit_id: Unique trace ID for the configuration event
Workflow:
1. Call config.validate.router with llm_backends and routing_rules
2. If validation fails, return errors and exit
3. Call config.generate.router to create the config JSON
4. If apply_config=true and output_mode≠preview, write to ~/.claude-code-router/config.json
5. Call audit.log to record the configuration event
6. Return config, write status, and audit ID
Environment awareness:
- Detect local vs cloud vs CI environment
- Adjust file paths accordingly
- Include environment fingerprint in audit metadata
tags:
- llm
- router
- configuration
- meta
- infra
- openrouter
- claude
- ollama
- multi-model

View File

@@ -0,0 +1,323 @@
#!/usr/bin/env python3
"""
Agent: meta.config.router
Configure Claude Code Router for multi-model LLM support
"""
import json
import sys
import os
import hashlib
import uuid
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, List
import subprocess
import yaml
class MetaConfigRouter:
"""Configure Claude Code Router for Betty Framework"""
def __init__(self):
self.betty_root = Path(__file__).parent.parent.parent
self.skills_root = self.betty_root / "skills"
self.audit_log_path = self.betty_root / "registry" / "audit_log.json"
def run(
self,
routing_config_path: str,
apply_config: bool = False,
output_mode: str = "preview"
) -> Dict[str, Any]:
"""
Main execution method
Args:
routing_config_path: Path to router config input file (YAML or JSON)
apply_config: Whether to write config to disk
output_mode: "preview" | "file" | "both"
Returns:
Result with routing_config, write_status, and audit_id
"""
print(f"🔧 meta.config.router v0.1.0")
print(f"📋 Config input: {routing_config_path}")
print(f"📝 Output mode: {output_mode}")
print(f"💾 Apply config: {apply_config}")
print()
# Load input config
config_input = self._load_config_input(routing_config_path)
# Extract inputs
llm_backends = config_input.get("llm_backends", [])
routing_rules = config_input.get("routing_rules", {})
config_options = config_input.get("config_options", {})
metadata = config_input.get("metadata", {}) # For audit logging only
# Step 1: Validate inputs
print("🔍 Validating router configuration...")
validation_result = self._validate_config(llm_backends, routing_rules)
if not validation_result["valid"]:
print("❌ Validation failed:")
for error in validation_result["errors"]:
print(f" - {error}")
return {
"success": False,
"errors": validation_result["errors"],
"warnings": validation_result["warnings"]
}
if validation_result["warnings"]:
print("⚠️ Warnings:")
for warning in validation_result["warnings"]:
print(f" - {warning}")
print("✅ Validation passed")
print()
# Step 2: Generate router config
print("🏗️ Generating router configuration...")
router_config = self._generate_config(
llm_backends,
routing_rules,
config_options
)
print("✅ Configuration generated")
print()
# Step 3: Write config if requested
write_status = "skipped"
config_path = None
if apply_config and output_mode != "preview":
print("💾 Writing configuration to disk...")
config_path, write_status = self._write_config(router_config)
if write_status == "success":
print(f"✅ Configuration written to: {config_path}")
else:
print(f"❌ Failed to write configuration")
print()
# Step 4: Log audit record
print("📝 Logging audit record...")
audit_id = self._log_audit(
config_input=config_input,
write_status=write_status,
metadata=metadata
)
print(f"✅ Audit ID: {audit_id}")
print()
# Step 5: Output results
result = {
"success": True,
"routing_config": router_config,
"write_status": write_status,
"audit_id": audit_id
}
if config_path:
result["config_path"] = str(config_path)
# Display preview if requested
if output_mode in ["preview", "both"]:
print("📄 Router Configuration Preview:")
print("" * 80)
print(json.dumps(router_config, indent=2))
print("" * 80)
print()
return result
def _load_config_input(self, config_path: str) -> Dict[str, Any]:
"""Load router config input from YAML or JSON file"""
path = Path(config_path)
if not path.exists():
raise FileNotFoundError(f"Config file not found: {config_path}")
with open(path, 'r') as f:
if path.suffix in ['.yaml', '.yml']:
return yaml.safe_load(f)
else:
return json.load(f)
def _validate_config(
self,
llm_backends: List[Dict[str, Any]],
routing_rules: Dict[str, Any]
) -> Dict[str, Any]:
"""Validate router configuration using config.validate.router skill"""
validator_script = self.skills_root / "config.validate.router" / "validate_router.py"
config_json = json.dumps({
"llm_backends": llm_backends,
"routing_rules": routing_rules
})
try:
result = subprocess.run(
[sys.executable, str(validator_script), config_json],
capture_output=True,
text=True,
check=False
)
return json.loads(result.stdout)
except Exception as e:
return {
"valid": False,
"errors": [f"Validation error: {e}"],
"warnings": []
}
def _generate_config(
self,
llm_backends: List[Dict[str, Any]],
routing_rules: Dict[str, Any],
config_options: Dict[str, Any]
) -> Dict[str, Any]:
"""Generate router configuration using config.generate.router skill"""
generator_script = self.skills_root / "config.generate.router" / "generate_router.py"
input_json = json.dumps({
"llm_backends": llm_backends,
"routing_rules": routing_rules,
"config_options": config_options
})
try:
result = subprocess.run(
[sys.executable, str(generator_script), input_json],
capture_output=True,
text=True,
check=True
)
return json.loads(result.stdout)
except Exception as e:
raise RuntimeError(f"Config generation failed: {e}")
def _write_config(self, router_config: Dict[str, Any]) -> tuple[Path, str]:
"""Write router config to ~/.claude-code-router/config.json"""
try:
config_dir = Path.home() / ".claude-code-router"
config_dir.mkdir(parents=True, exist_ok=True)
config_path = config_dir / "config.json"
with open(config_path, 'w') as f:
json.dump(router_config, f, indent=2)
return config_path, "success"
except Exception as e:
print(f"Error writing config: {e}")
return None, "error"
def _log_audit(
self,
config_input: Dict[str, Any],
write_status: str,
metadata: Dict[str, Any]
) -> str:
"""Log audit record for configuration event"""
audit_id = str(uuid.uuid4())
# Calculate hash of input
input_hash = hashlib.sha256(
json.dumps(config_input, sort_keys=True).encode()
).hexdigest()[:16]
audit_entry = {
"audit_id": audit_id,
"timestamp": datetime.utcnow().isoformat() + "Z",
"agent": "meta.config.router",
"version": "0.1.0",
"action": "router_config_generated",
"write_status": write_status,
"input_hash": input_hash,
"environment": self._detect_environment(),
"initiator": metadata.get("initiator", "unknown"),
"metadata": metadata
}
# Append to audit log
try:
if self.audit_log_path.exists():
with open(self.audit_log_path, 'r') as f:
audit_log = json.load(f)
else:
audit_log = []
audit_log.append(audit_entry)
with open(self.audit_log_path, 'w') as f:
json.dump(audit_log, f, indent=2)
except Exception as e:
print(f"Warning: Failed to write audit log: {e}")
return audit_id
def _detect_environment(self) -> str:
"""Detect execution environment (local, cloud, ci)"""
if os.getenv("CI"):
return "ci"
elif os.getenv("CLOUD_ENV"):
return "cloud"
else:
return "local"
def main():
"""CLI entrypoint"""
if len(sys.argv) < 2:
print("Usage: meta_config_router.py <routing_config_path> [--apply_config] [--output_mode=<mode>]")
print()
print("Arguments:")
print(" routing_config_path Path to router config input file (YAML or JSON)")
print(" --apply_config Write config to ~/.claude-code-router/config.json")
print(" --output_mode=MODE Output mode: preview, file, or both (default: preview)")
sys.exit(1)
# Parse arguments
routing_config_path = sys.argv[1]
apply_config = "--apply_config" in sys.argv or "--apply-config" in sys.argv
output_mode = "preview"
for arg in sys.argv[2:]:
if arg.startswith("--output_mode=") or arg.startswith("--output-mode="):
output_mode = arg.split("=")[1]
# Run agent
agent = MetaConfigRouter()
try:
result = agent.run(
routing_config_path=routing_config_path,
apply_config=apply_config,
output_mode=output_mode
)
if result["success"]:
print("✅ meta.config.router completed successfully")
print(f"📋 Audit ID: {result['audit_id']}")
print(f"💾 Write status: {result['write_status']}")
sys.exit(0)
else:
print("❌ meta.config.router failed")
for error in result.get("errors", []):
print(f" - {error}")
sys.exit(1)
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()