feat(ui): Phase 8 UI/UX optimization and system documentation update
## Sidebar Enhancement - Change tabs to icon + small label layout for better space utilization - Add Teams tab with team collaboration entry point ## Settings Page Improvements - Connect theme toggle to gatewayStore.saveQuickConfig for persistence - Remove OpenFang backend download section, simplify UI - Add time range filter to UsageStats (7d/30d/all) - Add stat cards with icons (sessions, messages, input/output tokens) - Add token usage overview bar chart - Add 8 ZCLAW system skill definitions with categories ## Bug Fixes - Fix ChannelList duplicate content with deduplication logic - Integrate CreateTriggerModal in TriggersPanel - Add independent SecurityStatusPanel with 12 default enabled layers - Change workflow view to use SchedulerPanel as unified entry ## New Components - CreateTriggerModal: Event trigger creation modal - HandApprovalModal: Hand approval workflow dialog - HandParamsForm: Enhanced Hand parameter form - SecurityLayersPanel: 16-layer security status display ## Architecture - Add TOML config parsing support (toml-utils.ts, config-parser.ts) - Add request timeout and retry mechanism (request-helper.ts) - Add secure token storage (secure-storage.ts, secure_storage.rs) ## Tests - Add unit tests for config-parser, toml-utils, request-helper - Add team-client and teamStore tests ## Documentation - Update SYSTEM_ANALYSIS.md with Phase 8 completion - UI completion: 100% (30/30 components) - API coverage: 93% (63/68 endpoints) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
595
desktop/scripts/test-api-connection.mjs
Normal file
595
desktop/scripts/test-api-connection.mjs
Normal file
@@ -0,0 +1,595 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* OpenFang Backend API Connection Test Script
|
||||
*
|
||||
* Tests all API endpoints used by the ZCLAW desktop client against
|
||||
* the OpenFang Kernel backend.
|
||||
*
|
||||
* Usage:
|
||||
* node desktop/scripts/test-api-connection.mjs [options]
|
||||
*
|
||||
* Options:
|
||||
* --url=URL Base URL for OpenFang API (default: http://127.0.0.1:50051)
|
||||
* --verbose Show detailed output
|
||||
* --json Output results as JSON
|
||||
* --timeout=MS Request timeout in milliseconds (default: 5000)
|
||||
*/
|
||||
|
||||
import { WebSocket } from 'ws';
|
||||
|
||||
// Configuration
|
||||
const DEFAULT_BASE_URL = 'http://127.0.0.1:50051';
|
||||
const DEFAULT_TIMEOUT = 5000;
|
||||
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
const config = {
|
||||
baseUrl: DEFAULT_BASE_URL,
|
||||
verbose: false,
|
||||
json: false,
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
};
|
||||
|
||||
for (const arg of args) {
|
||||
if (arg.startsWith('--url=')) {
|
||||
config.baseUrl = arg.slice(6);
|
||||
} else if (arg === '--verbose' || arg === '-v') {
|
||||
config.verbose = true;
|
||||
} else if (arg === '--json') {
|
||||
config.json = true;
|
||||
} else if (arg.startsWith('--timeout=')) {
|
||||
config.timeout = parseInt(arg.slice(10), 10);
|
||||
} else if (arg === '--help' || arg === '-h') {
|
||||
console.log(`
|
||||
OpenFang API Connection Tester
|
||||
|
||||
Usage: node test-api-connection.mjs [options]
|
||||
|
||||
Options:
|
||||
--url=URL Base URL for OpenFang API (default: ${DEFAULT_BASE_URL})
|
||||
--verbose Show detailed output including response bodies
|
||||
--json Output results as JSON for programmatic processing
|
||||
--timeout=MS Request timeout in milliseconds (default: ${DEFAULT_TIMEOUT})
|
||||
--help, -h Show this help message
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Test result tracking
|
||||
const results = {
|
||||
timestamp: new Date().toISOString(),
|
||||
baseUrl: config.baseUrl,
|
||||
summary: {
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
skipped: 0,
|
||||
},
|
||||
categories: {},
|
||||
errors: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Make an HTTP request with timeout
|
||||
*/
|
||||
async function makeRequest(method, path, body = null, expectedStatus = null) {
|
||||
const url = `${config.baseUrl}${path}`;
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), config.timeout);
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const fetchOptions = {
|
||||
method,
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
if (body && (method === 'POST' || method === 'PUT')) {
|
||||
fetchOptions.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const response = await fetch(url, fetchOptions);
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
let responseBody = null;
|
||||
|
||||
try {
|
||||
responseBody = await response.json();
|
||||
} catch {
|
||||
responseBody = await response.text().catch(() => null);
|
||||
}
|
||||
|
||||
const statusMatch = expectedStatus ? response.status === expectedStatus : response.status < 500;
|
||||
|
||||
return {
|
||||
success: statusMatch,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
duration,
|
||||
body: responseBody,
|
||||
error: null,
|
||||
};
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
return {
|
||||
success: false,
|
||||
status: 0,
|
||||
statusText: 'Network Error',
|
||||
duration,
|
||||
body: null,
|
||||
error: error.name === 'AbortError' ? 'Timeout' : error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a single endpoint and record results
|
||||
*/
|
||||
async function testEndpoint(category, method, path, testOptions = {}) {
|
||||
const { body = null, expectedStatus = null, description = '' } = testOptions;
|
||||
|
||||
results.summary.total++;
|
||||
|
||||
if (!results.categories[category]) {
|
||||
results.categories[category] = { total: 0, passed: 0, failed: 0, tests: [] };
|
||||
}
|
||||
results.categories[category].total++;
|
||||
|
||||
const result = await makeRequest(method, path, body, expectedStatus);
|
||||
|
||||
const testResult = {
|
||||
method,
|
||||
path,
|
||||
description,
|
||||
status: result.status,
|
||||
statusText: result.statusText,
|
||||
duration: result.duration,
|
||||
success: result.success,
|
||||
error: result.error,
|
||||
};
|
||||
|
||||
if (config.verbose && result.body) {
|
||||
testResult.responseBody = result.body;
|
||||
}
|
||||
|
||||
results.categories[category].tests.push(testResult);
|
||||
|
||||
if (result.success) {
|
||||
results.summary.passed++;
|
||||
results.categories[category].passed++;
|
||||
} else {
|
||||
results.summary.failed++;
|
||||
results.categories[category].failed++;
|
||||
|
||||
if (result.error) {
|
||||
results.errors.push({
|
||||
category,
|
||||
method,
|
||||
path,
|
||||
error: result.error,
|
||||
status: result.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Print progress
|
||||
if (!config.json) {
|
||||
const statusIcon = result.success ? '[PASS]' : '[FAIL]';
|
||||
const statusColor = result.success ? '\x1b[32m' : '\x1b[31m';
|
||||
const reset = '\x1b[0m';
|
||||
const dim = '\x1b[2m';
|
||||
|
||||
console.log(
|
||||
`${statusColor}${statusIcon}${reset} ${method.padEnd(6)} ${path} ${dim}${result.status} ${result.duration}ms${reset}`
|
||||
);
|
||||
|
||||
if (config.verbose && result.body) {
|
||||
const bodyStr = JSON.stringify(result.body, null, 2);
|
||||
const indented = bodyStr.split('\n').join('\n ');
|
||||
console.log(` Response: ${indented}`);
|
||||
}
|
||||
|
||||
if (!result.success && result.error) {
|
||||
console.log(` Error: ${result.error}`);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test WebSocket connection
|
||||
*/
|
||||
async function testWebSocketConnection(url) {
|
||||
return new Promise((resolve) => {
|
||||
const startTime = Date.now();
|
||||
let resolved = false;
|
||||
|
||||
const ws = new WebSocket(url);
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
ws.close();
|
||||
resolve({
|
||||
method: 'CONNECT',
|
||||
path: '/ws',
|
||||
description: 'WebSocket connection test',
|
||||
status: 0,
|
||||
statusText: 'Timeout',
|
||||
duration: Date.now() - startTime,
|
||||
success: false,
|
||||
error: 'Connection timeout',
|
||||
});
|
||||
}
|
||||
}, config.timeout);
|
||||
|
||||
ws.on('open', () => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
clearTimeout(timeout);
|
||||
ws.close();
|
||||
resolve({
|
||||
method: 'CONNECT',
|
||||
path: '/ws',
|
||||
description: 'WebSocket connection test',
|
||||
status: 101,
|
||||
statusText: 'Switching Protocols',
|
||||
duration: Date.now() - startTime,
|
||||
success: true,
|
||||
error: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', (error) => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
clearTimeout(timeout);
|
||||
resolve({
|
||||
method: 'CONNECT',
|
||||
path: '/ws',
|
||||
description: 'WebSocket connection test',
|
||||
status: 0,
|
||||
statusText: 'Connection Failed',
|
||||
duration: Date.now() - startTime,
|
||||
success: false,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Print test summary
|
||||
*/
|
||||
function printSummary() {
|
||||
console.log(`\n`);
|
||||
console.log(`=== Test Summary ===`);
|
||||
console.log(`\n`);
|
||||
|
||||
const categories = Object.entries(results.categories);
|
||||
const maxCategoryLen = Math.max(...categories.map(([name]) => name.length));
|
||||
|
||||
for (const [name, data] of categories) {
|
||||
const passRate = data.total > 0 ? ((data.passed / data.total) * 100).toFixed(0) : '0';
|
||||
const statusIcon = data.failed === 0 ? '\x1b[32m' : '\x1b[31m';
|
||||
const reset = '\x1b[0m';
|
||||
|
||||
console.log(
|
||||
`${statusIcon}${name.padEnd(maxCategoryLen + 1)}${reset} ` +
|
||||
`${data.passed}/${data.total} passed (${passRate}%)`
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`\n`);
|
||||
const totalPassRate = results.summary.total > 0
|
||||
? ((results.summary.passed / results.summary.total) * 100).toFixed(0)
|
||||
: '0';
|
||||
console.log(
|
||||
`Total: ${results.summary.passed}/${results.summary.total} passed ` +
|
||||
`(${totalPassRate}%)`
|
||||
);
|
||||
|
||||
if (results.errors.length > 0) {
|
||||
console.log(`\n\x1b[31m=== Errors ===\x1b[0m\n`);
|
||||
for (const error of results.errors) {
|
||||
console.log(` [${error.category}] ${error.method} ${error.path}`);
|
||||
console.log(` Status: ${error.status || 'N/A'}`);
|
||||
console.log(` Error: ${error.error}`);
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
|
||||
// JSON output if requested
|
||||
if (config.json) {
|
||||
console.log(`\n=== JSON Output ===\n`);
|
||||
console.log(JSON.stringify(results, null, 2));
|
||||
}
|
||||
|
||||
// Exit with appropriate code
|
||||
process.exit(results.summary.failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all API tests
|
||||
*/
|
||||
async function runAllTests() {
|
||||
console.log(`\n=== OpenFang API Connection Test ===`);
|
||||
console.log(`Base URL: ${config.baseUrl}`);
|
||||
console.log(`Timeout: ${config.timeout}ms`);
|
||||
console.log(`\n`);
|
||||
|
||||
// =========================================
|
||||
// Health & System Endpoints
|
||||
// =========================================
|
||||
console.log(`\n--- Health & System ---`);
|
||||
|
||||
await testEndpoint('System', 'GET', '/api/health', {
|
||||
expectedStatus: 200,
|
||||
description: 'Health check endpoint',
|
||||
});
|
||||
|
||||
await testEndpoint('System', 'GET', '/api/version', {
|
||||
description: 'Version information',
|
||||
});
|
||||
|
||||
// =========================================
|
||||
// Agent Endpoints
|
||||
// =========================================
|
||||
console.log(`\n--- Agent API ---`);
|
||||
|
||||
await testEndpoint('Agents', 'GET', '/api/agents', {
|
||||
expectedStatus: 200,
|
||||
description: 'List all agents',
|
||||
});
|
||||
|
||||
// Test agent creation (will fail if not authenticated)
|
||||
await testEndpoint('Agents', 'POST', '/api/agents', {
|
||||
body: {
|
||||
name: 'test-agent',
|
||||
role: 'assistant',
|
||||
},
|
||||
description: 'Create agent (expect 401 without auth)',
|
||||
});
|
||||
|
||||
// =========================================
|
||||
// Team API Endpoints
|
||||
// =========================================
|
||||
console.log(`\n--- Team API ---`);
|
||||
|
||||
await testEndpoint('Teams', 'GET', '/api/teams', {
|
||||
description: 'List all teams',
|
||||
});
|
||||
|
||||
await testEndpoint('Teams', 'POST', '/api/teams', {
|
||||
body: {
|
||||
name: 'Test Team',
|
||||
pattern: 'sequential',
|
||||
memberAgents: [],
|
||||
},
|
||||
description: 'Create team (expect 401 without auth)',
|
||||
});
|
||||
|
||||
await testEndpoint('Teams', 'GET', '/api/teams/test-team-id', {
|
||||
description: 'Get team by ID',
|
||||
});
|
||||
|
||||
await testEndpoint('Teams', 'PUT', '/api/teams/test-team-id', {
|
||||
body: { name: 'Updated Team' },
|
||||
description: 'Update team',
|
||||
});
|
||||
|
||||
await testEndpoint('Teams', 'DELETE', '/api/teams/test-team-id', {
|
||||
description: 'Delete team',
|
||||
});
|
||||
|
||||
// Team member endpoints
|
||||
await testEndpoint('Teams', 'POST', '/api/teams/test-team-id/members', {
|
||||
body: { agentId: 'test-agent', role: 'developer' },
|
||||
description: 'Add team member',
|
||||
});
|
||||
|
||||
await testEndpoint('Teams', 'DELETE', '/api/teams/test-team-id/members/test-member-id', {
|
||||
description: 'Remove team member',
|
||||
});
|
||||
|
||||
// Team task endpoints
|
||||
await testEndpoint('Teams', 'GET', '/api/teams/test-team-id/metrics', {
|
||||
description: 'Get team metrics',
|
||||
});
|
||||
|
||||
await testEndpoint('Teams', 'GET', '/api/teams/test-team-id/events', {
|
||||
description: 'Get team events',
|
||||
});
|
||||
|
||||
// =========================================
|
||||
// Config API Endpoints
|
||||
// =========================================
|
||||
console.log(`\n--- Config API ---`);
|
||||
|
||||
await testEndpoint('Config', 'GET', '/api/config', {
|
||||
description: 'Get full configuration',
|
||||
});
|
||||
|
||||
await testEndpoint('Config', 'GET', '/api/config/quick', {
|
||||
description: 'Get quick configuration',
|
||||
});
|
||||
|
||||
await testEndpoint('Config', 'PUT', '/api/config/quick', {
|
||||
body: { default_model: 'gpt-4' },
|
||||
description: 'Update quick configuration',
|
||||
});
|
||||
|
||||
// =========================================
|
||||
// Trigger API Endpoints
|
||||
// =========================================
|
||||
console.log(`\n--- Trigger API ---`);
|
||||
|
||||
await testEndpoint('Triggers', 'GET', '/api/triggers', {
|
||||
description: 'List all triggers',
|
||||
});
|
||||
|
||||
await testEndpoint('Triggers', 'POST', '/api/triggers', {
|
||||
body: {
|
||||
type: 'schedule',
|
||||
enabled: true,
|
||||
schedule: '0 * * * *',
|
||||
},
|
||||
description: 'Create trigger',
|
||||
});
|
||||
|
||||
await testEndpoint('Triggers', 'GET', '/api/triggers/test-trigger-id', {
|
||||
description: 'Get trigger by ID',
|
||||
});
|
||||
|
||||
await testEndpoint('Triggers', 'PUT', '/api/triggers/test-trigger-id', {
|
||||
body: { enabled: false },
|
||||
description: 'Update trigger',
|
||||
});
|
||||
|
||||
await testEndpoint('Triggers', 'DELETE', '/api/triggers/test-trigger-id', {
|
||||
description: 'Delete trigger',
|
||||
});
|
||||
|
||||
// =========================================
|
||||
// Audit API Endpoints
|
||||
// =========================================
|
||||
console.log(`\n--- Audit API ---`);
|
||||
|
||||
await testEndpoint('Audit', 'GET', '/api/audit/logs', {
|
||||
description: 'Get audit logs',
|
||||
});
|
||||
|
||||
await testEndpoint('Audit', 'GET', '/api/audit/logs?limit=10', {
|
||||
description: 'Get audit logs with limit',
|
||||
});
|
||||
|
||||
await testEndpoint('Audit', 'GET', '/api/audit/verify/test-log-id', {
|
||||
description: 'Verify audit chain for log ID',
|
||||
});
|
||||
|
||||
// =========================================
|
||||
// Skills & Plugins API Endpoints
|
||||
// =========================================
|
||||
console.log(`\n--- Skills & Plugins ---`);
|
||||
|
||||
await testEndpoint('Skills', 'GET', '/api/skills', {
|
||||
description: 'List all skills',
|
||||
});
|
||||
|
||||
await testEndpoint('Plugins', 'GET', '/api/plugins/status', {
|
||||
description: 'Get plugin status',
|
||||
});
|
||||
|
||||
// =========================================
|
||||
// Hands API Endpoints
|
||||
// =========================================
|
||||
console.log(`\n--- Hands API ---`);
|
||||
|
||||
await testEndpoint('Hands', 'GET', '/api/hands', {
|
||||
description: 'List all hands',
|
||||
});
|
||||
|
||||
await testEndpoint('Hands', 'POST', '/api/hands/researcher/trigger', {
|
||||
body: { query: 'test query' },
|
||||
description: 'Trigger researcher hand',
|
||||
});
|
||||
|
||||
// =========================================
|
||||
// Workflow API Endpoints
|
||||
// =========================================
|
||||
console.log(`\n--- Workflow API ---`);
|
||||
|
||||
await testEndpoint('Workflows', 'GET', '/api/workflows', {
|
||||
description: 'List all workflows',
|
||||
});
|
||||
|
||||
await testEndpoint('Workflows', 'POST', '/api/workflows', {
|
||||
body: {
|
||||
name: 'Test Workflow',
|
||||
steps: [],
|
||||
},
|
||||
description: 'Create workflow',
|
||||
});
|
||||
|
||||
await testEndpoint('Workflows', 'GET', '/api/workflows/test-workflow-id', {
|
||||
description: 'Get workflow by ID',
|
||||
});
|
||||
|
||||
// =========================================
|
||||
// Stats API Endpoints
|
||||
// =========================================
|
||||
console.log(`\n--- Stats API ---`);
|
||||
|
||||
await testEndpoint('Stats', 'GET', '/api/stats/usage', {
|
||||
description: 'Get usage statistics',
|
||||
});
|
||||
|
||||
await testEndpoint('Stats', 'GET', '/api/stats/sessions', {
|
||||
description: 'Get session statistics',
|
||||
});
|
||||
|
||||
// =========================================
|
||||
// Channel API Endpoints
|
||||
// =========================================
|
||||
console.log(`\n--- Channels API ---`);
|
||||
|
||||
await testEndpoint('Channels', 'GET', '/api/channels', {
|
||||
description: 'List all channels',
|
||||
});
|
||||
|
||||
// =========================================
|
||||
// WebSocket Endpoint
|
||||
// =========================================
|
||||
console.log(`\n--- WebSocket ---`);
|
||||
|
||||
// WebSocket test is different - we need to check if the endpoint exists
|
||||
const wsUrl = `${config.baseUrl.replace(/^http/, 'ws')}/ws`;
|
||||
const wsResult = await testWebSocketConnection(wsUrl);
|
||||
results.categories['WebSocket'] = {
|
||||
total: 1,
|
||||
passed: wsResult.success ? 1 : 0,
|
||||
failed: wsResult.success ? 0 : 1,
|
||||
tests: [wsResult],
|
||||
};
|
||||
results.summary.total++;
|
||||
|
||||
if (wsResult.success) {
|
||||
results.summary.passed++;
|
||||
if (!config.json) {
|
||||
console.log(`\x1b[32m[PASS]\x1b[0m CONNECT /ws \x1b[2m${wsResult.status} ${wsResult.duration}ms\x1b[0m`);
|
||||
}
|
||||
} else {
|
||||
results.summary.failed++;
|
||||
results.errors.push({
|
||||
category: 'WebSocket',
|
||||
method: 'CONNECT',
|
||||
path: '/ws',
|
||||
error: wsResult.error,
|
||||
status: wsResult.status,
|
||||
});
|
||||
if (!config.json) {
|
||||
console.log(`\x1b[31m[FAIL]\x1b[0m CONNECT /ws \x1b[2m${wsResult.status} ${wsResult.duration}ms\x1b[0m`);
|
||||
console.log(` Error: ${wsResult.error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Print summary
|
||||
printSummary();
|
||||
}
|
||||
|
||||
// Run tests
|
||||
runAllTests().catch((error) => {
|
||||
console.error('Failed to run tests:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
104
desktop/scripts/test-toml-parsing.mjs
Normal file
104
desktop/scripts/test-toml-parsing.mjs
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Test script to verify TOML parsing with actual config files
|
||||
*/
|
||||
|
||||
import TOML from 'smol-toml';
|
||||
|
||||
// Use inline TOML strings for testing
|
||||
const MAIN_CONFIG_TOML = `
|
||||
[server]
|
||||
host = "127.0.0.1"
|
||||
port = 4200
|
||||
websocket_port = 4200
|
||||
websocket_path = "/ws"
|
||||
|
||||
[agent.defaults]
|
||||
workspace = "~/.openfang/workspace"
|
||||
default_model = "gpt-4"
|
||||
|
||||
[llm]
|
||||
default_provider = "openai"
|
||||
default_model = "gpt-4"
|
||||
`;
|
||||
|
||||
const CHINESE_PROVIDERS_TOML = `
|
||||
[[llm.providers]]
|
||||
name = "zhipu"
|
||||
display_name = "Zhipu GLM"
|
||||
api_key = "\${ZHIPU_API_KEY}"
|
||||
base_url = "https://open.bigmodel.cn/api/paas/v4"
|
||||
|
||||
[[llm.providers.models]]
|
||||
id = "glm-4-plus"
|
||||
alias = "GLM-4-Plus"
|
||||
context_window = 128000
|
||||
|
||||
[[llm.providers]]
|
||||
name = "qwen"
|
||||
display_name = "Qwen"
|
||||
api_key = "\${QWEN_API_KEY}"
|
||||
base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
|
||||
[[llm.providers.models]]
|
||||
id = "qwen-max"
|
||||
alias = "Qwen-Max"
|
||||
`;
|
||||
|
||||
console.log('=== Testing TOML Parsing ===\n');
|
||||
|
||||
// Test 1: Parse main config
|
||||
try {
|
||||
console.log('\n--- Test 1: Main config.toml ---');
|
||||
const mainConfig = TOML.parse(MAIN_CONFIG_TOML);
|
||||
console.log('Parsed successfully!');
|
||||
console.log('Server config:', JSON.stringify(mainConfig.server, null, 2));
|
||||
console.log('Agent defaults:', JSON.stringify(mainConfig.agent?.defaults, null, 2));
|
||||
console.log('LLM config:', JSON.stringify(mainConfig.llm, null, 2));
|
||||
|
||||
// Check required fields
|
||||
if (!mainConfig.server?.host) throw new Error('Missing server.host');
|
||||
if (!mainConfig.server?.port) throw new Error('Missing server.port');
|
||||
if (!mainConfig.agent?.defaults?.workspace) throw new Error('Missing agent.defaults.workspace');
|
||||
if (!mainConfig.agent?.defaults?.default_model) throw new Error('Missing agent.defaults.default_model');
|
||||
if (!mainConfig.llm?.default_provider) throw new Error('Missing llm.default_provider');
|
||||
if (!mainConfig.llm?.default_model) throw new Error('Missing llm.default_model');
|
||||
|
||||
console.log('All required fields present!');
|
||||
} catch (error) {
|
||||
console.error('Failed to parse main config:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Test 2: Parse chinese-providers.toml
|
||||
try {
|
||||
console.log('\n--- Test 2: chinese-providers.toml ---');
|
||||
const chineseProviders = TOML.parse(CHINESE_PROVIDERS_TOML);
|
||||
console.log('Parsed successfully!');
|
||||
console.log('Number of providers:', chineseProviders.llm?.providers?.length || 0);
|
||||
|
||||
// List providers
|
||||
if (chineseProviders.llm?.providers) {
|
||||
console.log('\nProviders found:');
|
||||
chineseProviders.llm.providers.forEach((provider, index) => {
|
||||
console.log(` ${index + 1}. ${provider.name} (${provider.display_name || 'N/A'})`);
|
||||
if (provider.models) {
|
||||
console.log(` Models: ${provider.models.length}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Check for environment variable references
|
||||
const envVarPattern = /\$\{([^}]+)\}/g;
|
||||
const envVars = CHINESE_PROVIDERS_TOML.match(envVarPattern);
|
||||
if (envVars) {
|
||||
console.log('\nEnvironment variables referenced:');
|
||||
const uniqueVars = [...new Set(envVars)];
|
||||
uniqueVars.forEach(v => console.log(` - ${v}`));
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to parse chinese-providers:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('\n=== All TOML parsing tests passed! ===\n');
|
||||
Reference in New Issue
Block a user