Merge branch 'main' into t3

This commit is contained in:
Cytown 2026-03-30 18:07:20 +08:00
commit f9bfa6b9a8
24 changed files with 340 additions and 270 deletions

View file

@ -11,24 +11,35 @@ PicoClaw uses a schema versioning system for `config.json` to ensure smooth upgr
- **Changes**: Added `version` field to Config struct - **Changes**: Added `version` field to Config struct
- **Migration**: No structural changes needed for existing configs - **Migration**: No structural changes needed for existing configs
### Version 2
- **Introduction**: Model enable/disable support and channel config unification
- **Changes**:
- Added `enabled` field to `ModelConfig` — allows disabling individual model entries without removing them
- During V1→V2 migration, `enabled` is auto-inferred: models with API keys or the reserved `local-model` name are enabled; others default to disabled
- Migrated legacy channel fields: Discord `mention_only``group_trigger.mention_only`, OneBot `group_trigger_prefix``group_trigger.prefixes`
- V0 configs now migrate directly to CurrentVersion (V2) instead of going through V1
- `makeBackup()` now uses date-only suffix (e.g., `config.json.20260330.bak`) and also backs up `.security.yml`
## How It Works ## How It Works
### Automatic Migration ### Automatic Migration
When you load a config file: When you load a config file:
1. The system first reads the `version` field from the JSON 1. The system first reads the `version` field from the JSON
2. Based on the detected version, it loads the appropriate config struct (`ConfigV0`, `ConfigV1`, etc.) 2. Based on the detected version, it loads the appropriate config struct (`configV0`, `configV1`, etc.)
3. If the loaded version is less than the latest, migrations are applied incrementally 3. If the loaded version is less than the latest, migrations are applied incrementally
4. The version number is updated automatically 4. Before saving, the system automatically creates a date-stamped backup of `config.json` and `.security.yml`
5. The migrated config is automatically saved back to disk 5. The version number is updated automatically
6. The migrated config is automatically saved back to disk
### Version Field ### Version Field
The `version` field in `config.json` indicates the schema version: The `version` field in `config.json` indicates the schema version:
- `0` or missing: Legacy config (no version field) - `0` or missing: Legacy config (no version field)
- `1`: Current version with versioning support - `1`: Previous version (will be auto-migrated to V2 on load)
- `2`: Current version
```json ```json
{ {
"version": 1, "version": 2,
"agents": {...}, "agents": {...},
... ...
} }
@ -54,25 +65,25 @@ type ConfigV2 struct {
### Step 2: Update Current Config Version ### Step 2: Update Current Config Version
```go ```go
const CurrentConfigVersion = 2 // Increment this const CurrentVersion = 2 // Increment this
``` ```
### Step 3: Add a Loader Function ### Step 3: Add a Loader Function
```go ```go
// loadConfigV2 loads a version 2 config // loadConfigV3 loads a version 3 config
func loadConfigV2(data []byte) (*Config, error) { func loadConfigV3(data []byte) (*Config, error) {
cfg := DefaultConfig() cfg := DefaultConfig()
// Parse to ConfigV2 struct // Parse to ConfigV3 struct
var v2 ConfigV2 var v3 ConfigV3
if err := json.Unmarshal(data, &v2); err != nil { if err := json.Unmarshal(data, &v3); err != nil {
return nil, err return nil, err
} }
// Convert to current Config // Convert to current Config
cfg.Version = v2.Version cfg.Version = v3.Version
cfg.Agents = v2.Agents cfg.Agents = v3.Agents
// ... map other fields // ... map other fields
return cfg, nil return cfg, nil
@ -82,29 +93,12 @@ func loadConfigV2(data []byte) (*Config, error) {
### Step 4: Add Migration Logic ### Step 4: Add Migration Logic
```go ```go
// applyMigration applies a single migration step from fromVersion to toVersion func (c *configV2) Migrate() (*Config, error) {
func applyMigration(cfg *Config, fromVersion, toVersion int) (*Config, error) { // Apply V2→V3 structural changes here
switch toVersion { migrated := &c.Config
case 1: migrated.Version = 3
// Migration from version 0 to 1 // Apply structural changes
return &Config{ return migrated, nil
Version: 1,
Agents: cfg.Agents,
// ... copy all fields
}, nil
case 2:
// Migration from version 1 to 2
// Example: Move or rename fields
migrated := *cfg
migrated.Version = 2
// Apply structural changes
if cfg.SomeOldField != "" {
migrated.SomeNewField = cfg.SomeOldField
}
return &migrated, nil
default:
return nil, fmt.Errorf("unsupported migration target version: %d", toVersion)
}
} }
``` ```
@ -120,7 +114,9 @@ func LoadConfig(path string) (*Config, error) {
case 1: case 1:
cfg, err = loadConfigV1(data) cfg, err = loadConfigV1(data)
case 2: case 2:
cfg, err = loadConfigV2(data) cfg, err = loadConfig(data)
case 3:
cfg, err = loadConfigV3(data)
default: default:
return nil, fmt.Errorf("unsupported config version: %d", versionInfo.Version) return nil, fmt.Errorf("unsupported config version: %d", versionInfo.Version)
} }
@ -134,22 +130,22 @@ func LoadConfig(path string) (*Config, error) {
Create a test in `config_migration_test.go`: Create a test in `config_migration_test.go`:
```go ```go
func TestMigrateV1ToV2(t *testing.T) { func TestMigrateV2ToV3(t *testing.T) {
// Create a version 1 config // Create a version 2 config
v1Config := Config{ v2Config := Config{
Version: 1, Version: 2,
// ... set up test data // ... set up test data
} }
// Apply migration // Apply migration
migrated, err := applyMigration(&v1Config, 1, 2) migrated, err := v2Config.Migrate()
if err != nil { if err != nil {
t.Fatalf("Migration failed: %v", err) t.Fatalf("Migration failed: %v", err)
} }
// Verify version is updated // Verify version is updated
if migrated.Version != 2 { if migrated.Version != 3 {
t.Errorf("Expected version 2, got %d", migrated.Version) t.Errorf("Expected version 3, got %d", migrated.Version)
} }
// Verify data is preserved/transformed correctly // Verify data is preserved/transformed correctly
@ -164,58 +160,60 @@ func TestMigrateV1ToV2(t *testing.T) {
3. **No Data Loss**: Migrations should preserve all user settings 3. **No Data Loss**: Migrations should preserve all user settings
4. **Idempotent**: Running the same migration multiple times should be safe 4. **Idempotent**: Running the same migration multiple times should be safe
5. **Auto-Save**: Migrated configs are automatically saved to update the user's file 5. **Auto-Save**: Migrated configs are automatically saved to update the user's file
6. **Test Thoroughly**: Test with real user config files 6. **Auto-Backup**: Before saving, the system creates a date-stamped backup of `config.json` and `.security.yml`
7. **Update Defaults**: Keep `defaults.go` in sync with the latest schema 7. **Test Thoroughly**: Test with real user config files
8. **Update Defaults**: Keep `defaults.go` in sync with the latest schema
## Example Migration ## Example Migration
### Scenario: Adding a new field with default value ### Scenario: Adding a new field with default value
Old config (version 1): Old config (version 2):
```json
{
"version": 1,
"agents": {
"defaults": {
"max_tokens": 32768
}
}
}
```
Migration to version 2:
```go
case 2:
migrated := *cfg
migrated.Version = 2
// Add new field with default value if not set
if migrated.Agents.Defaults.NewFeatureEnabled == false {
// Use default value
}
return &migrated, nil
```
New config (version 2):
```json ```json
{ {
"version": 2, "version": 2,
"agents": { "model_list": [
"defaults": { {
"max_tokens": 32768, "model_name": "gpt-5.4",
"new_feature_enabled": false "model": "openai/gpt-5.4"
} }
} ]
}
```
Migration to version 3:
```go
func (c *configV2) Migrate() (*Config, error) {
migrated := &c.Config
migrated.Version = 3
// Add new field with default value if not set
// ...
return migrated, nil
}
```
New config (version 3):
```json
{
"version": 3,
"model_list": [
{
"model_name": "gpt-5.4",
"model": "openai/gpt-5.4",
"new_option": true
}
]
} }
``` ```
## Troubleshooting ## Troubleshooting
### Config Not Upgrading ### Config Not Upgrading
- Check that `CurrentConfigVersion` is incremented - Check that `CurrentVersion` is incremented
- Verify migration logic in `applyMigration()` handles the target version - Verify migration logic handles the target version
- Ensure `migrateConfig()` is called in `LoadConfig()` - Ensure `Migrate()` is called in `LoadConfig()`
### Migration Errors ### Migration Errors
- Check error messages for specific migration failures - Check error messages for specific migration failures
@ -227,4 +225,5 @@ New config (version 2):
- Ensure all fields are copied during migration - Ensure all fields are copied during migration
- Check that the migration doesn't overwrite values with defaults unnecessarily - Check that the migration doesn't overwrite values with defaults unnecessarily
- Review the conversion logic in the loader functions - Review the conversion logic in the loader functions
- Check the auto-backup files (e.g., `config.json.20260330.bak`) to recover original data

View file

@ -479,8 +479,9 @@ This design also enables **multi-agent support** with flexible provider selectio
- **Different agents, different providers**: Each agent can use its own LLM provider - **Different agents, different providers**: Each agent can use its own LLM provider
- **Model fallbacks**: Configure primary and fallback models for resilience - **Model fallbacks**: Configure primary and fallback models for resilience
- **Load balancing**: Distribute requests across multiple endpoints - **Load balancing**: Distribute requests across multiple endpoints or keys
- **Centralized configuration**: Manage all providers in one place - **Centralized configuration**: Manage all providers in one place
- **Model enable/disable**: Use the `enabled` field to temporarily disable a model without removing its configuration
#### 🔒 Security Configuration (Recommended) #### 🔒 Security Configuration (Recommended)
@ -581,22 +582,22 @@ For complete documentation, see [`security_configuration.md`](security_configura
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key" "api_keys": ["sk-your-api-key"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-your-openai-key" "api_keys": ["sk-your-openai-key"]
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
}, },
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-zhipu-key" "api_keys": ["your-zhipu-key"]
} }
], ],
"agents": { "agents": {
@ -607,7 +608,9 @@ For complete documentation, see [`security_configuration.md`](security_configura
} }
``` ```
> **Security Note**: You can remove `api_key` fields from your config and store them in `.security.yml` instead. See [Security Configuration](#-security-configuration-recommended) above for details. > **Security Note**: You can remove `api_keys` fields from your config and store them in `.security.yml` instead. See [Security Configuration](#-security-configuration-recommended) above for details.
>
> **Note**: The `enabled` field can be set to `false` to disable a model entry without removing it. When omitted, it defaults to `true` during migration for models that have API keys.
#### Vendor-Specific Examples #### Vendor-Specific Examples
@ -684,7 +687,7 @@ For direct Anthropic API access or custom endpoints that only support Anthropic'
{ {
"model_name": "claude-opus-4-6", "model_name": "claude-opus-4-6",
"model": "anthropic-messages/claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6",
"api_key": "sk-ant-your-key", "api_keys": ["sk-ant-your-key"],
"api_base": "https://api.anthropic.com" "api_base": "https://api.anthropic.com"
} }
``` ```
@ -759,13 +762,13 @@ model_list:
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api1.example.com/v1", "api_base": "https://api1.example.com/v1",
"api_key": "sk-key1" "api_keys": ["sk-key1"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api2.example.com/v1", "api_base": "https://api2.example.com/v1",
"api_key": "sk-key2" "api_keys": ["sk-key2"]
} }
] ]
} }
@ -773,7 +776,7 @@ model_list:
#### Migration from Legacy `providers` Config #### Migration from Legacy `providers` Config
The old `providers` configuration is **deprecated** but still supported for backward compatibility. See [docs/migration/model-list-migration.md](../migration/model-list-migration.md) for the full guide. The old `providers` configuration is **deprecated** and has been removed in V2. Existing V0/V1 configs are auto-migrated. See [docs/migration/model-list-migration.md](../migration/model-list-migration.md) for the full guide.
### Provider Architecture ### Provider Architecture
@ -783,7 +786,7 @@ PicoClaw routes providers by protocol family:
- **Anthropic**: Claude-native API behavior. - **Anthropic**: Claude-native API behavior.
- **Codex/OAuth**: OpenAI OAuth/token authentication route. - **Codex/OAuth**: OpenAI OAuth/token authentication route.
This keeps the runtime lightweight while making new OpenAI-compatible backends mostly a config operation (`api_base` + `api_key`). This keeps the runtime lightweight while making new OpenAI-compatible backends mostly a config operation (`api_base` + `api_keys`).
<details> <details>
<summary><b>Zhipu (legacy providers format)</b></summary> <summary><b>Zhipu (legacy providers format)</b></summary>

View file

@ -1,6 +1,6 @@
# Credential Encryption # Credential Encryption
PicoClaw supports encrypting `api_key` values in `model_list` configuration entries. PicoClaw supports encrypting `api_key`/`api_keys` values in `model_list` configuration entries.
Encrypted keys are stored as `enc://<base64>` strings and decrypted automatically at startup. Encrypted keys are stored as `enc://<base64>` strings and decrypted automatically at startup.
--- ---
@ -42,6 +42,8 @@ enc://AAAA...base64...
## Supported `api_key` Formats ## Supported `api_key` Formats
The same formats apply to both `api_key` (singular) and individual elements in the `api_keys` (array) field:
| Format | Example | Behaviour | | Format | Example | Behaviour |
|--------|---------|-----------| |--------|---------|-----------|
| Plaintext | `sk-abc123` | Used as-is | | Plaintext | `sk-abc123` | Used as-is |

View file

@ -95,19 +95,19 @@ picoclaw onboard
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key", "api_keys": ["sk-your-api-key"],
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "your-api-key", "api_keys": ["your-api-key"],
"request_timeout": 300 "request_timeout": 300
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "your-anthropic-key" "api_keys": ["your-anthropic-key"]
} }
], ],
"tools": { "tools": {

View file

@ -334,15 +334,15 @@ Configurez plusieurs endpoints pour le même nom de modèle — PicoClaw effectu
```json ```json
{ {
"model_list": [ "model_list": [
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_key": "sk-key1" }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_keys": ["sk-key1"] },
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_keys": ["sk-key2"] }
] ]
} }
``` ```
#### Migration depuis l'ancienne config `providers` #### Migration depuis l'ancienne config `providers`
L'ancienne configuration `providers` est **dépréciée** mais toujours supportée. Voir [docs/migration/model-list-migration.md](../migration/model-list-migration.md). L'ancienne configuration `providers` est **dépréciée** et a été supprimée dans V2. Les configs V0/V1 existantes sont auto-migrées. Voir [docs/migration/model-list-migration.md](../migration/model-list-migration.md).
### Architecture des Providers ### Architecture des Providers

View file

@ -92,19 +92,19 @@ picoclaw onboard
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key", "api_keys": ["sk-your-api-key"],
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "your-api-key", "api_keys": ["your-api-key"],
"request_timeout": 300 "request_timeout": 300
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "your-anthropic-key" "api_keys": ["your-anthropic-key"]
} }
], ],
"tools": { "tools": {

View file

@ -73,22 +73,22 @@ Cette conception permet également le **support multi-agents** avec une sélecti
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key" "api_keys": ["sk-your-api-key"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-your-openai-key" "api_keys": ["sk-your-openai-key"]
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
}, },
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-zhipu-key" "api_keys": ["your-zhipu-key"]
} }
], ],
"agents": { "agents": {
@ -107,7 +107,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -117,7 +117,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -127,7 +127,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-key" "api_keys": ["your-key"]
} }
``` ```
@ -137,7 +137,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti
{ {
"model_name": "deepseek-chat", "model_name": "deepseek-chat",
"model": "deepseek/deepseek-chat", "model": "deepseek/deepseek-chat",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -147,7 +147,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
} }
``` ```
@ -161,7 +161,7 @@ Pour l'accès direct à l'API Anthropic ou les endpoints personnalisés qui ne p
{ {
"model_name": "claude-opus-4-6", "model_name": "claude-opus-4-6",
"model": "anthropic-messages/claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6",
"api_key": "sk-ant-your-key", "api_keys": ["sk-ant-your-key"],
"api_base": "https://api.anthropic.com" "api_base": "https://api.anthropic.com"
} }
``` ```
@ -189,7 +189,7 @@ Pour l'accès direct à l'API Anthropic ou les endpoints personnalisés qui ne p
"model_name": "my-custom-model", "model_name": "my-custom-model",
"model": "openai/custom-model", "model": "openai/custom-model",
"api_base": "https://my-proxy.com/v1", "api_base": "https://my-proxy.com/v1",
"api_key": "sk-...", "api_keys": ["sk-..."],
"request_timeout": 300 "request_timeout": 300
} }
``` ```
@ -201,7 +201,7 @@ Pour l'accès direct à l'API Anthropic ou les endpoints personnalisés qui ne p
"model_name": "lite-gpt4", "model_name": "lite-gpt4",
"model": "litellm/lite-gpt4", "model": "litellm/lite-gpt4",
"api_base": "http://localhost:4000/v1", "api_base": "http://localhost:4000/v1",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -218,13 +218,13 @@ Configurez plusieurs endpoints pour le même nom de modèle — PicoClaw effectu
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api1.example.com/v1", "api_base": "https://api1.example.com/v1",
"api_key": "sk-key1" "api_keys": ["sk-key1"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api2.example.com/v1", "api_base": "https://api2.example.com/v1",
"api_key": "sk-key2" "api_keys": ["sk-key2"]
} }
] ]
} }
@ -232,7 +232,7 @@ Configurez plusieurs endpoints pour le même nom de modèle — PicoClaw effectu
#### Migration depuis l'Ancienne Configuration `providers` #### Migration depuis l'Ancienne Configuration `providers`
L'ancienne configuration `providers` est **dépréciée** mais toujours prise en charge pour la compatibilité ascendante. L'ancienne configuration `providers` est **dépréciée** et a été supprimée dans V2. Les configs V0/V1 existantes sont auto-migrées.
**Ancienne configuration (dépréciée) :** **Ancienne configuration (dépréciée) :**
@ -257,11 +257,12 @@ L'ancienne configuration `providers` est **dépréciée** mais toujours prise en
```json ```json
{ {
"version": 2,
"model_list": [ "model_list": [
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-key" "api_keys": ["your-key"]
} }
], ],
"agents": { "agents": {

View file

@ -335,15 +335,15 @@ HEARTBEAT_OK を返信 ユーザーが直接結果を受信
```json ```json
{ {
"model_list": [ "model_list": [
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_key": "sk-key1" }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_keys": ["sk-key1"] },
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_keys": ["sk-key2"] }
] ]
} }
``` ```
#### 旧 `providers` 設定からの移行 #### 旧 `providers` 設定からの移行
`providers` 設定は**非推奨**ですが後方互換性のためサポートされています。[docs/migration/model-list-migration.md](../migration/model-list-migration.md) を参照してください。 `providers` 設定は**非推奨**となり、V2 で削除されました。既存の V0/V1 設定は自動的に移行されます。[docs/migration/model-list-migration.md](../migration/model-list-migration.md) を参照してください。
### Provider アーキテクチャ ### Provider アーキテクチャ

View file

@ -94,19 +94,19 @@ picoclaw onboard
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key", "api_keys": ["sk-your-api-key"],
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "your-api-key", "api_keys": ["your-api-key"],
"request_timeout": 300 "request_timeout": 300
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "your-anthropic-key" "api_keys": ["your-anthropic-key"]
} }
], ],
"tools": { "tools": {

View file

@ -73,22 +73,22 @@
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key" "api_keys": ["sk-your-api-key"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-your-openai-key" "api_keys": ["sk-your-openai-key"]
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
}, },
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-zhipu-key" "api_keys": ["your-zhipu-key"]
} }
], ],
"agents": { "agents": {
@ -107,7 +107,7 @@
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -117,7 +117,7 @@
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -127,7 +127,18 @@
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-key" "api_keys": ["your-key"]
}
```
**LiteLLM Proxy**
```json
{
"model_name": "lite-gpt4",
"model": "litellm/lite-gpt4",
"api_base": "http://localhost:4000/v1",
"api_keys": ["sk-..."]
} }
``` ```
@ -137,7 +148,7 @@
{ {
"model_name": "deepseek-chat", "model_name": "deepseek-chat",
"model": "deepseek/deepseek-chat", "model": "deepseek/deepseek-chat",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -147,7 +158,7 @@
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
} }
``` ```
@ -161,7 +172,7 @@ Anthropic API への直接アクセスや、Anthropic のネイティブメッ
{ {
"model_name": "claude-opus-4-6", "model_name": "claude-opus-4-6",
"model": "anthropic-messages/claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6",
"api_key": "sk-ant-your-key", "api_keys": ["sk-ant-your-key"],
"api_base": "https://api.anthropic.com" "api_base": "https://api.anthropic.com"
} }
``` ```
@ -189,7 +200,7 @@ Anthropic API への直接アクセスや、Anthropic のネイティブメッ
"model_name": "my-custom-model", "model_name": "my-custom-model",
"model": "openai/custom-model", "model": "openai/custom-model",
"api_base": "https://my-proxy.com/v1", "api_base": "https://my-proxy.com/v1",
"api_key": "sk-...", "api_keys": ["sk-..."],
"request_timeout": 300 "request_timeout": 300
} }
``` ```
@ -201,7 +212,7 @@ Anthropic API への直接アクセスや、Anthropic のネイティブメッ
"model_name": "lite-gpt4", "model_name": "lite-gpt4",
"model": "litellm/lite-gpt4", "model": "litellm/lite-gpt4",
"api_base": "http://localhost:4000/v1", "api_base": "http://localhost:4000/v1",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -218,13 +229,13 @@ PicoClaw はリクエスト送信前に外側の `litellm/` プレフィック
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api1.example.com/v1", "api_base": "https://api1.example.com/v1",
"api_key": "sk-key1" "api_keys": ["sk-key1"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api2.example.com/v1", "api_base": "https://api2.example.com/v1",
"api_key": "sk-key2" "api_keys": ["sk-key2"]
} }
] ]
} }
@ -232,7 +243,7 @@ PicoClaw はリクエスト送信前に外側の `litellm/` プレフィック
#### レガシー `providers` 設定からの移行 #### レガシー `providers` 設定からの移行
`providers` 設定形式は**非推奨**ですが、後方互換性のためまだサポートされています。 `providers` 設定形式は**非推奨**となり、V2 で削除されました。既存の V0/V1 設定は自動的に移行されます。
**旧設定(非推奨):** **旧設定(非推奨):**
@ -257,11 +268,12 @@ PicoClaw はリクエスト送信前に外側の `litellm/` プレフィック
```json ```json
{ {
"version": 2,
"model_list": [ "model_list": [
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-key" "api_keys": ["your-key"]
} }
], ],
"agents": { "agents": {
@ -282,7 +294,7 @@ PicoClaw はプロトコルファミリーごとに Provider をルーティン
- Anthropic プロトコルClaude ネイティブ API 動作。 - Anthropic プロトコルClaude ネイティブ API 動作。
- Codex/OAuth パスOpenAI OAuth/Token 認証ルート。 - Codex/OAuth パスOpenAI OAuth/Token 認証ルート。
これによりランタイムを軽量に保ちつつ、新しい OpenAI 互換バックエンドの追加をほぼ設定操作(`api_base` + `api_key`)のみで実現しています。 これによりランタイムを軽量に保ちつつ、新しい OpenAI 互換バックエンドの追加をほぼ設定操作(`api_base` + `api_keys`)のみで実現しています。
<details> <details>
<summary><b>Zhipu 設定例</b></summary> <summary><b>Zhipu 設定例</b></summary>

View file

@ -50,22 +50,23 @@ The new `model_list` configuration offers several advantages:
```json ```json
{ {
"version": 2,
"model_list": [ "model_list": [
{ {
"model_name": "gpt4", "model_name": "gpt4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-your-openai-key", "api_keys": ["sk-your-openai-key"],
"api_base": "https://api.openai.com/v1" "api_base": "https://api.openai.com/v1"
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
}, },
{ {
"model_name": "deepseek", "model_name": "deepseek",
"model": "deepseek/deepseek-chat", "model": "deepseek/deepseek-chat",
"api_key": "sk-your-deepseek-key" "api_keys": ["sk-your-deepseek-key"]
} }
], ],
"agents": { "agents": {
@ -76,6 +77,8 @@ The new `model_list` configuration offers several advantages:
} }
``` ```
> **Note**: The `enabled` field can be omitted — during V1→V2 migration it is auto-inferred (models with API keys or the `local-model` name are enabled by default). For new configs, you can explicitly set `"enabled": false` to disable a model entry without removing it.
## Protocol Prefixes ## Protocol Prefixes
The `model` field uses a protocol prefix format: `[protocol/]model-identifier` The `model` field uses a protocol prefix format: `[protocol/]model-identifier`
@ -111,7 +114,8 @@ The `model` field uses a protocol prefix format: `[protocol/]model-identifier`
| `model_name` | Yes | User-facing alias for the model | | `model_name` | Yes | User-facing alias for the model |
| `model` | Yes | Protocol and model identifier (e.g., `openai/gpt-5.4`) | | `model` | Yes | Protocol and model identifier (e.g., `openai/gpt-5.4`) |
| `api_base` | No | API endpoint URL | | `api_base` | No | API endpoint URL |
| `api_key` | No* | API authentication key | | `api_keys` | No | API authentication keys (array; supports multiple keys for load balancing) |
| `enabled` | No | Whether this model entry is active. Defaults to `true` during migration for models with API keys or named `local-model`. Set to `false` to disable. |
| `proxy` | No | HTTP proxy URL | | `proxy` | No | HTTP proxy URL |
| `auth_method` | No | Authentication method: `oauth`, `token` | | `auth_method` | No | Authentication method: `oauth`, `token` |
| `connect_mode` | No | Connection mode for CLI providers: `stdio`, `grpc` | | `connect_mode` | No | Connection mode for CLI providers: `stdio`, `grpc` |
@ -119,11 +123,13 @@ The `model` field uses a protocol prefix format: `[protocol/]model-identifier`
| `max_tokens_field` | No | Field name for max tokens | | `max_tokens_field` | No | Field name for max tokens |
| `request_timeout` | No | HTTP request timeout in seconds; `<=0` uses default `120s` | | `request_timeout` | No | HTTP request timeout in seconds; `<=0` uses default `120s` |
*`api_key` is required for HTTP-based protocols unless `api_base` points to a local server. > **Note**: `api_key` (singular) has been **removed** in V2 configs. Only `api_keys` (array) is supported. During migration from V0/V1, both `api_key` and `api_keys` are automatically merged into the new `api_keys` array.
## Load Balancing ## Load Balancing
Configure multiple endpoints for the same model to distribute load: There are two ways to configure load balancing:
### Option 1: Multiple API Keys in `api_keys` (Recommended)
```json ```json
{ {
@ -131,19 +137,45 @@ Configure multiple endpoints for the same model to distribute load:
{ {
"model_name": "gpt4", "model_name": "gpt4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-key1", "api_keys": ["sk-key1", "sk-key2", "sk-key3"],
"api_base": "https://api.openai.com/v1"
}
]
}
```
Or via `.security.yml`:
```yaml
model_list:
gpt4:
api_keys:
- "sk-key1"
- "sk-key2"
- "sk-key3"
```
### Option 2: Multiple Model Entries
```json
{
"model_list": [
{
"model_name": "gpt4",
"model": "openai/gpt-5.4",
"api_keys": ["sk-key1"],
"api_base": "https://api1.example.com/v1" "api_base": "https://api1.example.com/v1"
}, },
{ {
"model_name": "gpt4", "model_name": "gpt4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-key2", "api_keys": ["sk-key2"],
"api_base": "https://api2.example.com/v1" "api_base": "https://api2.example.com/v1"
}, },
{ {
"model_name": "gpt4", "model_name": "gpt4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-key3", "api_keys": ["sk-key3"],
"api_base": "https://api3.example.com/v1" "api_base": "https://api3.example.com/v1"
} }
] ]
@ -162,7 +194,7 @@ With `model_list`, adding a new provider requires zero code changes:
{ {
"model_name": "my-custom-llm", "model_name": "my-custom-llm",
"model": "openai/my-model-v1", "model": "openai/my-model-v1",
"api_key": "your-api-key", "api_keys": ["your-api-key"],
"api_base": "https://api.your-provider.com/v1" "api_base": "https://api.your-provider.com/v1"
} }
] ]
@ -173,11 +205,12 @@ Just specify `openai/` as the protocol (or omit it for the default), and provide
## Backward Compatibility ## Backward Compatibility
During the migration period, your existing `providers` configuration will continue to work: During the migration period, your existing V0/V1 config will be auto-migrated to V2:
1. If `model_list` is empty and `providers` has data, the system auto-converts internally 1. If `model_list` is empty and `providers` has data, the system auto-converts internally
2. A deprecation warning is logged: `"providers config is deprecated, please migrate to model_list"` 2. Both `api_key` (singular) and `api_keys` (array) in V0/V1 configs are merged into the new `api_keys` array
3. All existing functionality remains unchanged 3. A deprecation warning is logged: `"providers config is deprecated, please migrate to model_list"`
4. All existing functionality remains unchanged
## Migration Checklist ## Migration Checklist
@ -212,7 +245,7 @@ unknown protocol "xxx" in model "xxx/model-name"
api_key or api_base is required for HTTP-based protocol "xxx" api_key or api_base is required for HTTP-based protocol "xxx"
``` ```
**Solution**: Provide `api_key` and/or `api_base` for HTTP-based providers. **Solution**: Provide `api_keys` and/or `api_base` for HTTP-based providers.
## Need Help? ## Need Help?

View file

@ -91,19 +91,19 @@ picoclaw onboard
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key", "api_keys": ["sk-your-api-key"],
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "your-api-key", "api_keys": ["your-api-key"],
"request_timeout": 300 "request_timeout": 300
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "your-anthropic-key" "api_keys": ["your-anthropic-key"]
} }
], ],
"tools": { "tools": {

View file

@ -79,22 +79,22 @@ This design also enables **multi-agent support** with flexible provider selectio
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key" "api_keys": ["sk-your-api-key"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-your-openai-key" "api_keys": ["sk-your-openai-key"]
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
}, },
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-zhipu-key" "api_keys": ["your-zhipu-key"]
} }
], ],
"agents": { "agents": {
@ -117,7 +117,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
{ {
"model_name": "voice-gemini", "model_name": "voice-gemini",
"model": "gemini/gemini-2.5-flash", "model": "gemini/gemini-2.5-flash",
"api_key": "your-gemini-key" "api_keys": ["your-gemini-key"]
} }
], ],
"voice": { "voice": {
@ -140,7 +140,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -150,7 +150,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -160,7 +160,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-key" "api_keys": ["your-key"]
} }
``` ```
@ -170,7 +170,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "openai/glm-4.7", "model": "openai/glm-4.7",
"api_key": "your-z.ai-key" "api_keys": ["your-z.ai-key"],
"api_base": "https://api.z.ai/api/coding/paas/v4" "api_base": "https://api.z.ai/api/coding/paas/v4"
} }
``` ```
@ -181,7 +181,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
{ {
"model_name": "deepseek-chat", "model_name": "deepseek-chat",
"model": "deepseek/deepseek-chat", "model": "deepseek/deepseek-chat",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -191,7 +191,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
} }
``` ```
@ -205,7 +205,7 @@ For direct Anthropic API access or custom endpoints that only support Anthropic'
{ {
"model_name": "claude-opus-4-6", "model_name": "claude-opus-4-6",
"model": "anthropic-messages/claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6",
"api_key": "sk-ant-your-key", "api_keys": ["sk-ant-your-key"],
"api_base": "https://api.anthropic.com" "api_base": "https://api.anthropic.com"
} }
``` ```
@ -233,7 +233,7 @@ For direct Anthropic API access or custom endpoints that only support Anthropic'
"model_name": "my-custom-model", "model_name": "my-custom-model",
"model": "openai/custom-model", "model": "openai/custom-model",
"api_base": "https://my-proxy.com/v1", "api_base": "https://my-proxy.com/v1",
"api_key": "sk-...", "api_keys": ["sk-..."],
"request_timeout": 300 "request_timeout": 300
} }
``` ```
@ -245,7 +245,7 @@ For direct Anthropic API access or custom endpoints that only support Anthropic'
"model_name": "lite-gpt4", "model_name": "lite-gpt4",
"model": "litellm/lite-gpt4", "model": "litellm/lite-gpt4",
"api_base": "http://localhost:4000/v1", "api_base": "http://localhost:4000/v1",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -259,7 +259,7 @@ If the standard Zhipu endpoint (`https://open.bigmodel.cn/api/paas/v4`) returns
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "openai/glm-4.7", "model": "openai/glm-4.7",
"api_key": "your-zhipu-api-key", "api_keys": ["your-zhipu-api-key"],
"api_base": "https://api.z.ai/api/coding/paas/v4" "api_base": "https://api.z.ai/api/coding/paas/v4"
} }
``` ```
@ -277,13 +277,13 @@ Configure multiple endpoints for the same model name—PicoClaw will automatical
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api1.example.com/v1", "api_base": "https://api1.example.com/v1",
"api_key": "sk-key1" "api_keys": ["sk-key1"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api2.example.com/v1", "api_base": "https://api2.example.com/v1",
"api_key": "sk-key2" "api_keys": ["sk-key2"]
} }
] ]
} }
@ -302,17 +302,17 @@ It also applies cooldown tracking per candidate to avoid immediately retrying a
"model_name": "qwen-main", "model_name": "qwen-main",
"model": "openai/qwen3.5:cloud", "model": "openai/qwen3.5:cloud",
"api_base": "https://api.example.com/v1", "api_base": "https://api.example.com/v1",
"api_key": "sk-main" "api_keys": ["sk-main"]
}, },
{ {
"model_name": "deepseek-backup", "model_name": "deepseek-backup",
"model": "deepseek/deepseek-chat", "model": "deepseek/deepseek-chat",
"api_key": "sk-backup-1" "api_keys": ["sk-backup-1"]
}, },
{ {
"model_name": "gemini-backup", "model_name": "gemini-backup",
"model": "gemini/gemini-2.5-flash", "model": "gemini/gemini-2.5-flash",
"api_key": "sk-backup-2" "api_keys": ["sk-backup-2"]
} }
], ],
"agents": { "agents": {
@ -330,7 +330,7 @@ If you use key-level failover for the same model, PicoClaw can chain through add
#### Migration from Legacy `providers` Config #### Migration from Legacy `providers` Config
The old `providers` configuration is **deprecated** but still supported for backward compatibility. The old `providers` configuration is **deprecated** and has been removed in V2. Existing V0/V1 configs are auto-migrated.
**Old Config (deprecated):** **Old Config (deprecated):**
@ -355,11 +355,12 @@ The old `providers` configuration is **deprecated** but still supported for back
```json ```json
{ {
"version": 2,
"model_list": [ "model_list": [
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-key" "api_keys": ["your-key"]
} }
], ],
"agents": { "agents": {

View file

@ -335,15 +335,15 @@ Configure múltiplos endpoints para o mesmo nome de modelo — PicoClaw fará ro
```json ```json
{ {
"model_list": [ "model_list": [
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_key": "sk-key1" }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_keys": ["sk-key1"] },
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_keys": ["sk-key2"] }
] ]
} }
``` ```
#### Migração da Configuração Legada `providers` #### Migração da Configuração Legada `providers`
A configuração antiga `providers` está **depreciada** mas ainda é suportada. Veja [docs/migration/model-list-migration.md](../migration/model-list-migration.md). A configuração antiga `providers` está **depreciada** e foi removida no V2. Configs V0/V1 existentes são auto-migradas. Veja [docs/migration/model-list-migration.md](../migration/model-list-migration.md).
### Arquitetura de Providers ### Arquitetura de Providers

View file

@ -92,19 +92,19 @@ picoclaw onboard
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key", "api_keys": ["sk-your-api-key"],
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "your-api-key", "api_keys": ["your-api-key"],
"request_timeout": 300 "request_timeout": 300
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "your-anthropic-key" "api_keys": ["your-anthropic-key"]
} }
], ],
"tools": { "tools": {

View file

@ -73,22 +73,22 @@ Este design também permite **suporte multi-agente** com seleção flexível de
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key" "api_keys": ["sk-your-api-key"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-your-openai-key" "api_keys": ["sk-your-openai-key"]
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
}, },
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-zhipu-key" "api_keys": ["your-zhipu-key"]
} }
], ],
"agents": { "agents": {
@ -107,7 +107,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -117,7 +117,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -127,7 +127,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-key" "api_keys": ["your-key"]
} }
``` ```
@ -137,7 +137,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de
{ {
"model_name": "deepseek-chat", "model_name": "deepseek-chat",
"model": "deepseek/deepseek-chat", "model": "deepseek/deepseek-chat",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -147,7 +147,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
} }
``` ```
@ -161,7 +161,7 @@ Para acesso direto à API Anthropic ou endpoints personalizados que suportam ape
{ {
"model_name": "claude-opus-4-6", "model_name": "claude-opus-4-6",
"model": "anthropic-messages/claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6",
"api_key": "sk-ant-your-key", "api_keys": ["sk-ant-your-key"],
"api_base": "https://api.anthropic.com" "api_base": "https://api.anthropic.com"
} }
``` ```
@ -189,7 +189,7 @@ Para acesso direto à API Anthropic ou endpoints personalizados que suportam ape
"model_name": "my-custom-model", "model_name": "my-custom-model",
"model": "openai/custom-model", "model": "openai/custom-model",
"api_base": "https://my-proxy.com/v1", "api_base": "https://my-proxy.com/v1",
"api_key": "sk-...", "api_keys": ["sk-..."],
"request_timeout": 300 "request_timeout": 300
} }
``` ```
@ -201,7 +201,7 @@ Para acesso direto à API Anthropic ou endpoints personalizados que suportam ape
"model_name": "lite-gpt4", "model_name": "lite-gpt4",
"model": "litellm/lite-gpt4", "model": "litellm/lite-gpt4",
"api_base": "http://localhost:4000/v1", "api_base": "http://localhost:4000/v1",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -218,13 +218,13 @@ Configure múltiplos endpoints para o mesmo nome de modelo — o PicoClaw fará
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api1.example.com/v1", "api_base": "https://api1.example.com/v1",
"api_key": "sk-key1" "api_keys": ["sk-key1"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api2.example.com/v1", "api_base": "https://api2.example.com/v1",
"api_key": "sk-key2" "api_keys": ["sk-key2"]
} }
] ]
} }
@ -232,7 +232,7 @@ Configure múltiplos endpoints para o mesmo nome de modelo — o PicoClaw fará
#### Migração da Configuração Legacy `providers` #### Migração da Configuração Legacy `providers`
A configuração antiga `providers` está **descontinuada** mas ainda é suportada para compatibilidade retroativa. A configuração antiga `providers` está **descontinuada** e foi removida no V2. Configs V0/V1 existentes são auto-migradas.
**Configuração Antiga (descontinuada):** **Configuração Antiga (descontinuada):**
@ -257,11 +257,12 @@ A configuração antiga `providers` está **descontinuada** mas ainda é suporta
```json ```json
{ {
"version": 2,
"model_list": [ "model_list": [
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-key" "api_keys": ["your-key"]
} }
], ],
"agents": { "agents": {
@ -282,7 +283,7 @@ O PicoClaw roteia provedores por família de protocolo:
- Protocolo Anthropic: Comportamento nativo da API Claude. - Protocolo Anthropic: Comportamento nativo da API Claude.
- Caminho Codex/OAuth: Rota de autenticação OAuth/token da OpenAI. - Caminho Codex/OAuth: Rota de autenticação OAuth/token da OpenAI.
Isso mantém o runtime leve enquanto torna novos backends compatíveis com OpenAI basicamente uma operação de configuração (`api_base` + `api_key`). Isso mantém o runtime leve enquanto torna novos backends compatíveis com OpenAI basicamente uma operação de configuração (`api_base` + `api_keys`).
<details> <details>
<summary><b>Zhipu</b></summary> <summary><b>Zhipu</b></summary>

View file

@ -401,7 +401,7 @@ The pattern is: `PICOCLAW_<SECTION>_<KEY>_<FIELD>` with underscores separating p
3. **Set file permissions**: `chmod 600 ~/.picoclaw/.security.yml` 3. **Set file permissions**: `chmod 600 ~/.picoclaw/.security.yml`
4. **Use different keys** for different environments (dev, staging, production) 4. **Use different keys** for different environments (dev, staging, production)
5. **Rotate keys regularly** and update `.security.yml` 5. **Rotate keys regularly** and update `.security.yml`
6. **Backup securely**: Encrypt backups containing `.security.yml` 6. **Backup securely**: Encrypt backups containing `.security.yml`. Note that config migrations automatically create date-stamped backups (e.g., `config.json.20260330.bak` and `.security.yml.20260330.bak`)
7. **Review access**: Ensure only authorized users have read access to the file 7. **Review access**: Ensure only authorized users have read access to the file
## API ## API
@ -444,7 +444,7 @@ Returns the path to `.security.yml` relative to the config file.
```json ```json
{ {
"version": 1, "version": 2,
"agents": { "agents": {
"defaults": { "defaults": {
"workspace": "~/picoclaw-workspace", "workspace": "~/picoclaw-workspace",
@ -557,6 +557,8 @@ go test ./pkg/config -run TestSecurityConfig
### Step 1: Backup your config ### Step 1: Backup your config
The system automatically creates a date-stamped backup before saving a migrated config (e.g., `config.json.20260330.bak` and `.security.yml.20260330.bak`). If you prefer a manual backup:
```bash ```bash
cp ~/.picoclaw/config.json ~/.picoclaw/config.json.backup cp ~/.picoclaw/config.json ~/.picoclaw/config.json.backup
``` ```
@ -597,9 +599,11 @@ Test your models and channels to ensure everything works correctly.
### Step 8: Clean up (optional) ### Step 8: Clean up (optional)
If everything works, you can delete the backup: If everything works, you can delete the backups:
```bash ```bash
rm ~/.picoclaw/config.json.backup rm ~/.picoclaw/config.json.backup
# Also remove auto-generated date-stamped backups if desired:
rm ~/.picoclaw/config.json.20*.bak ~/.picoclaw/.security.yml.20*.bak
``` ```
## Advanced: Encrypted API Keys ## Advanced: Encrypted API Keys

View file

@ -335,15 +335,15 @@ Cấu hình nhiều endpoint cho cùng tên mô hình — PicoClaw sẽ tự đ
```json ```json
{ {
"model_list": [ "model_list": [
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_key": "sk-key1" }, { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_keys": ["sk-key1"] },
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } { "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_keys": ["sk-key2"] }
] ]
} }
``` ```
#### Di Chuyển Từ Cấu Hình `providers` #### Di Chuyển Từ Cấu Hình `providers`
Cấu hình `providers` cũ đã **bị deprecated** nhưng vẫn được hỗ trợ. Xem [docs/migration/model-list-migration.md](../migration/model-list-migration.md). Cấu hình `providers` cũ đã **bị deprecated** và đã được loại bỏ trong V2. Các cấu hình V0/V1 hiện có sẽ được tự động migrate. Xem [docs/migration/model-list-migration.md](../migration/model-list-migration.md).
### Kiến Trúc Provider ### Kiến Trúc Provider

View file

@ -92,19 +92,19 @@ picoclaw onboard
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key", "api_keys": ["sk-your-api-key"],
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "your-api-key", "api_keys": ["your-api-key"],
"request_timeout": 300 "request_timeout": 300
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "your-anthropic-key" "api_keys": ["your-anthropic-key"]
} }
], ],
"tools": { "tools": {

View file

@ -73,22 +73,22 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key" "api_keys": ["sk-your-api-key"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-your-openai-key" "api_keys": ["sk-your-openai-key"]
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
}, },
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-zhipu-key" "api_keys": ["your-zhipu-key"]
} }
], ],
"agents": { "agents": {
@ -107,7 +107,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -117,7 +117,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -127,7 +127,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-key" "api_keys": ["your-key"]
} }
``` ```
@ -137,7 +137,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
{ {
"model_name": "deepseek-chat", "model_name": "deepseek-chat",
"model": "deepseek/deepseek-chat", "model": "deepseek/deepseek-chat",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -147,7 +147,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
} }
``` ```
@ -161,7 +161,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
{ {
"model_name": "claude-opus-4-6", "model_name": "claude-opus-4-6",
"model": "anthropic-messages/claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6",
"api_key": "sk-ant-your-key", "api_keys": ["sk-ant-your-key"],
"api_base": "https://api.anthropic.com" "api_base": "https://api.anthropic.com"
} }
``` ```
@ -189,7 +189,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
"model_name": "my-custom-model", "model_name": "my-custom-model",
"model": "openai/custom-model", "model": "openai/custom-model",
"api_base": "https://my-proxy.com/v1", "api_base": "https://my-proxy.com/v1",
"api_key": "sk-...", "api_keys": ["sk-..."],
"request_timeout": 300 "request_timeout": 300
} }
``` ```
@ -201,7 +201,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
"model_name": "lite-gpt4", "model_name": "lite-gpt4",
"model": "litellm/lite-gpt4", "model": "litellm/lite-gpt4",
"api_base": "http://localhost:4000/v1", "api_base": "http://localhost:4000/v1",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -218,13 +218,13 @@ Cấu hình nhiều endpoint cho cùng tên mô hình — PicoClaw sẽ tự đ
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api1.example.com/v1", "api_base": "https://api1.example.com/v1",
"api_key": "sk-key1" "api_keys": ["sk-key1"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api2.example.com/v1", "api_base": "https://api2.example.com/v1",
"api_key": "sk-key2" "api_keys": ["sk-key2"]
} }
] ]
} }
@ -232,7 +232,7 @@ Cấu hình nhiều endpoint cho cùng tên mô hình — PicoClaw sẽ tự đ
#### Di Chuyển Từ Cấu Hình Legacy `providers` #### Di Chuyển Từ Cấu Hình Legacy `providers`
Cấu hình `providers` cũ đã **ngừng hỗ trợ** nhưng vẫn được hỗ trợ để tương thích ngược. Cấu hình `providers` cũ đã **bị deprecated** và đã được loại bỏ trong V2. Các cấu hình V0/V1 hiện có sẽ được tự động migrate.
**Cấu hình cũ (ngừng hỗ trợ):** **Cấu hình cũ (ngừng hỗ trợ):**
@ -257,11 +257,12 @@ Cấu hình `providers` cũ đã **ngừng hỗ trợ** nhưng vẫn được h
```json ```json
{ {
"version": 2,
"model_list": [ "model_list": [
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-key" "api_keys": ["your-key"]
} }
], ],
"agents": { "agents": {
@ -282,7 +283,7 @@ PicoClaw định tuyến provider theo họ giao thức:
- Giao thức Anthropic: Hành vi API native của Claude. - Giao thức Anthropic: Hành vi API native của Claude.
- Đường dẫn Codex/OAuth: Tuyến xác thực OAuth/token của OpenAI. - Đường dẫn Codex/OAuth: Tuyến xác thực OAuth/token của OpenAI.
Điều này giữ runtime nhẹ trong khi làm cho backend tương thích OpenAI mới chủ yếu là thao tác cấu hình (`api_base` + `api_key`). Điều này giữ runtime nhẹ trong khi làm cho backend tương thích OpenAI mới chủ yếu là thao tác cấu hình (`api_base` + `api_keys`).
<details> <details>
<summary><b>Zhipu</b></summary> <summary><b>Zhipu</b></summary>

View file

@ -386,22 +386,22 @@ Agent 读取 HEARTBEAT.md
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key" "api_keys": ["sk-your-api-key"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-your-openai-key" "api_keys": ["sk-your-openai-key"]
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
}, },
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-zhipu-key" "api_keys": ["your-zhipu-key"]
} }
], ],
"agents": { "agents": {
@ -421,7 +421,7 @@ Agent 读取 HEARTBEAT.md
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -434,7 +434,7 @@ Agent 读取 HEARTBEAT.md
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -447,7 +447,7 @@ Agent 读取 HEARTBEAT.md
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-key" "api_keys": ["your-key"]
} }
``` ```
@ -460,7 +460,7 @@ Agent 读取 HEARTBEAT.md
{ {
"model_name": "deepseek-chat", "model_name": "deepseek-chat",
"model": "deepseek/deepseek-chat", "model": "deepseek/deepseek-chat",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -473,7 +473,7 @@ Agent 读取 HEARTBEAT.md
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
} }
``` ```
@ -485,7 +485,7 @@ Agent 读取 HEARTBEAT.md
{ {
"model_name": "claude-opus-4-6", "model_name": "claude-opus-4-6",
"model": "anthropic-messages/claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6",
"api_key": "sk-ant-your-key", "api_keys": ["sk-ant-your-key"],
"api_base": "https://api.anthropic.com" "api_base": "https://api.anthropic.com"
} }
``` ```
@ -514,7 +514,7 @@ Agent 读取 HEARTBEAT.md
"model_name": "my-custom-model", "model_name": "my-custom-model",
"model": "openai/custom-model", "model": "openai/custom-model",
"api_base": "https://my-proxy.com/v1", "api_base": "https://my-proxy.com/v1",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -533,13 +533,13 @@ PicoClaw 只剥离最外层的 `litellm/` 前缀再发送请求,因此 `litell
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api1.example.com/v1", "api_base": "https://api1.example.com/v1",
"api_key": "sk-key1" "api_keys": ["sk-key1"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api2.example.com/v1", "api_base": "https://api2.example.com/v1",
"api_key": "sk-key2" "api_keys": ["sk-key2"]
} }
] ]
} }
@ -547,7 +547,7 @@ PicoClaw 只剥离最外层的 `litellm/` 前缀再发送请求,因此 `litell
#### 从旧版 `providers` 配置迁移 #### 从旧版 `providers` 配置迁移
旧版 `providers` 配置**已废弃**但仍向后兼容。完整迁移指南见 [docs/migration/model-list-migration.md](../migration/model-list-migration.md)。 旧版 `providers` 配置**已废弃**V2 中已移除。现有 V0/V1 配置会自动迁移。完整迁移指南见 [docs/migration/model-list-migration.md](../migration/model-list-migration.md)。
### Provider 架构 ### Provider 架构

View file

@ -94,19 +94,19 @@ picoclaw onboard
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key", "api_keys": ["sk-your-api-key"],
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3" "api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "your-api-key", "api_keys": ["your-api-key"],
"request_timeout": 300 "request_timeout": 300
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "your-anthropic-key" "api_keys": ["your-anthropic-key"]
} }
], ],
"tools": { "tools": {

View file

@ -75,22 +75,22 @@
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-your-api-key" "api_keys": ["sk-your-api-key"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-your-openai-key" "api_keys": ["sk-your-openai-key"]
}, },
{ {
"model_name": "claude-sonnet-4.6", "model_name": "claude-sonnet-4.6",
"model": "anthropic/claude-sonnet-4.6", "model": "anthropic/claude-sonnet-4.6",
"api_key": "sk-ant-your-key" "api_keys": ["sk-ant-your-key"]
}, },
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-zhipu-key" "api_keys": ["your-zhipu-key"]
} }
], ],
"agents": { "agents": {
@ -113,7 +113,7 @@
{ {
"model_name": "voice-gemini", "model_name": "voice-gemini",
"model": "gemini/gemini-2.5-flash", "model": "gemini/gemini-2.5-flash",
"api_key": "your-gemini-key" "api_keys": ["your-gemini-key"]
} }
], ],
"voice": { "voice": {
@ -136,7 +136,7 @@
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -146,7 +146,7 @@
{ {
"model_name": "ark-code-latest", "model_name": "ark-code-latest",
"model": "volcengine/ark-code-latest", "model": "volcengine/ark-code-latest",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -156,7 +156,7 @@
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-key" "api_keys": ["your-key"]
} }
``` ```
@ -166,7 +166,7 @@
{ {
"model_name": "deepseek-chat", "model_name": "deepseek-chat",
"model": "deepseek/deepseek-chat", "model": "deepseek/deepseek-chat",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -190,7 +190,7 @@
{ {
"model_name": "claude-opus-4-6", "model_name": "claude-opus-4-6",
"model": "anthropic-messages/claude-opus-4-6", "model": "anthropic-messages/claude-opus-4-6",
"api_key": "sk-ant-your-key", "api_keys": ["sk-ant-your-key"],
"api_base": "https://api.anthropic.com" "api_base": "https://api.anthropic.com"
} }
``` ```
@ -218,7 +218,7 @@
"model_name": "my-custom-model", "model_name": "my-custom-model",
"model": "openai/custom-model", "model": "openai/custom-model",
"api_base": "https://my-proxy.com/v1", "api_base": "https://my-proxy.com/v1",
"api_key": "sk-...", "api_keys": ["sk-..."],
"request_timeout": 300 "request_timeout": 300
} }
``` ```
@ -230,7 +230,7 @@
"model_name": "lite-gpt4", "model_name": "lite-gpt4",
"model": "litellm/lite-gpt4", "model": "litellm/lite-gpt4",
"api_base": "http://localhost:4000/v1", "api_base": "http://localhost:4000/v1",
"api_key": "sk-..." "api_keys": ["sk-..."]
} }
``` ```
@ -247,13 +247,13 @@ PicoClaw 在发送请求前仅去除外层 `litellm/` 前缀,因此 `litellm/l
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api1.example.com/v1", "api_base": "https://api1.example.com/v1",
"api_key": "sk-key1" "api_keys": ["sk-key1"]
}, },
{ {
"model_name": "gpt-5.4", "model_name": "gpt-5.4",
"model": "openai/gpt-5.4", "model": "openai/gpt-5.4",
"api_base": "https://api2.example.com/v1", "api_base": "https://api2.example.com/v1",
"api_key": "sk-key2" "api_keys": ["sk-key2"]
} }
] ]
} }
@ -272,17 +272,17 @@ PicoClaw 在发送请求前仅去除外层 `litellm/` 前缀,因此 `litellm/l
"model_name": "qwen-main", "model_name": "qwen-main",
"model": "openai/qwen3.5:cloud", "model": "openai/qwen3.5:cloud",
"api_base": "https://api.example.com/v1", "api_base": "https://api.example.com/v1",
"api_key": "sk-main" "api_keys": ["sk-main"]
}, },
{ {
"model_name": "deepseek-backup", "model_name": "deepseek-backup",
"model": "deepseek/deepseek-chat", "model": "deepseek/deepseek-chat",
"api_key": "sk-backup-1" "api_keys": ["sk-backup-1"]
}, },
{ {
"model_name": "gemini-backup", "model_name": "gemini-backup",
"model": "gemini/gemini-2.5-flash", "model": "gemini/gemini-2.5-flash",
"api_key": "sk-backup-2" "api_keys": ["sk-backup-2"]
} }
], ],
"agents": { "agents": {
@ -300,7 +300,7 @@ PicoClaw 在发送请求前仅去除外层 `litellm/` 前缀,因此 `litellm/l
#### 从旧的 `providers` 配置迁移 #### 从旧的 `providers` 配置迁移
旧的 `providers` 配置格式**已弃用**但为向后兼容仍支持 旧的 `providers` 配置格式**已弃用**V2 中已移除。现有 V0/V1 配置会自动迁移
**旧配置(已弃用):** **旧配置(已弃用):**
@ -325,11 +325,12 @@ PicoClaw 在发送请求前仅去除外层 `litellm/` 前缀,因此 `litellm/l
```json ```json
{ {
"version": 2,
"model_list": [ "model_list": [
{ {
"model_name": "glm-4.7", "model_name": "glm-4.7",
"model": "zhipu/glm-4.7", "model": "zhipu/glm-4.7",
"api_key": "your-key" "api_keys": ["your-key"]
} }
], ],
"agents": { "agents": {

View file

@ -107,11 +107,13 @@ func Run(debug bool, homePath, configPath string, allowEmptyStartup bool) error
logger.Fatalf("config pre-check failed: %v", err) logger.Fatalf("config pre-check failed: %v", err)
} }
logger.SetLevelFromString(cfg.Gateway.LogLevel) // Debug mode permanently overrides the config log level to DEBUG.
if debug { if debug {
logger.SetLevel(logger.DEBUG) logger.SetLevel(logger.DEBUG)
fmt.Println("🔍 Debug mode enabled") fmt.Println("🔍 Debug mode enabled")
} else {
logger.SetLevelFromString(cfg.Gateway.LogLevel)
logger.Infof("Log level set to %q", cfg.Gateway.LogLevel)
} }
// Enforce singleton: write PID file with generated token. // Enforce singleton: write PID file with generated token.
@ -201,7 +203,7 @@ func Run(debug bool, homePath, configPath string, allowEmptyStartup bool) error
logger.Warn("Config reload skipped: another reload is in progress") logger.Warn("Config reload skipped: another reload is in progress")
continue continue
} }
err := executeReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup) err := executeReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup, debug)
if err != nil { if err != nil {
logger.Errorf("Config reload failed: %v", err) logger.Errorf("Config reload failed: %v", err)
} }
@ -218,7 +220,7 @@ func Run(debug bool, homePath, configPath string, allowEmptyStartup bool) error
runningServices.reloading.Store(false) runningServices.reloading.Store(false)
continue continue
} }
err = executeReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup) err = executeReload(ctx, agentLoop, newCfg, &provider, runningServices, msgBus, allowEmptyStartup, debug)
if err != nil { if err != nil {
logger.Errorf("Manual reload failed: %v", err) logger.Errorf("Manual reload failed: %v", err)
} else { } else {
@ -243,12 +245,13 @@ func executeReload(
runningServices *services, runningServices *services,
msgBus *bus.MessageBus, msgBus *bus.MessageBus,
allowEmptyStartup bool, allowEmptyStartup bool,
debug bool,
) error { ) error {
defer runningServices.reloading.Store(false) defer runningServices.reloading.Store(false)
overridePicoToken(newCfg, runningServices.authToken) overridePicoToken(newCfg, runningServices.authToken)
return handleConfigReload(ctx, agentLoop, newCfg, provider, runningServices, msgBus, allowEmptyStartup) return handleConfigReload(ctx, agentLoop, newCfg, provider, runningServices, msgBus, allowEmptyStartup, debug)
} }
func createStartupProvider( func createStartupProvider(
@ -420,6 +423,7 @@ func handleConfigReload(
runningServices *services, runningServices *services,
msgBus *bus.MessageBus, msgBus *bus.MessageBus,
allowEmptyStartup bool, allowEmptyStartup bool,
debug bool,
) error { ) error {
logger.Info("🔄 Config file changed, reloading...") logger.Info("🔄 Config file changed, reloading...")
@ -468,6 +472,14 @@ func handleConfigReload(
} }
logger.Info(" ✓ Provider, configuration, and services reloaded successfully (thread-safe)") logger.Info(" ✓ Provider, configuration, and services reloaded successfully (thread-safe)")
// Debug mode permanently overrides the config log level to DEBUG.
if !debug {
// Update log level last so that reload-related info/warn logs above are not suppressed.
logger.SetLevelFromString(newCfg.Gateway.LogLevel)
logger.Infof("Log level changing from current to %q", newCfg.Gateway.LogLevel)
}
return nil return nil
} }