feat: add GLMT proxy and transformer tools

- Add GLMT proxy server for GLM model routing
- Add GLMT transformer for output format conversion
- Update CLI with new proxy and transformer commands
- Add comprehensive test suite for new functionality
- Update documentation and architecture guides
- Bump version and update changelog
This commit is contained in:
kaitranntt committed 2025-11-11 03:08:56 -05:00
1 parent daf075dc69
commit 657f99bfe8
21 files changed
+2597 -347

No files matched your search

+245
View File
@@ -0,0 +1,245 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const os = require('os');
const GlmtTransformer = require('../bin/glmt-transformer');
/**
* Manual test for debug mode file logging
*/
const logDir = path.join(os.homedir(), '.ccs', 'logs');
// Clean up any existing logs
console.log('Cleaning up existing logs...');
if (fs.existsSync(logDir)) {
fs.rmSync(logDir, { recursive: true, force: true });
}
console.log('\n=== Test 1: Debug Mode OFF (default) ===');
{
const transformer = new GlmtTransformer({ verbose: false });
console.log(`Debug logging: ${transformer.debugLog}`);
const input = {
model: 'claude-sonnet-4.5',
messages: [{ role: 'user', content: 'Test without debug' }]
};
const { openaiRequest } = transformer.transformRequest(input);
const openaiResponse = {
id: 'test-1',
model: 'GLM-4.6',
choices: [{
message: {
role: 'assistant',
content: 'Response without debug',
reasoning_content: 'Some reasoning...'
},
finish_reason: 'stop'
}],
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 }
};
transformer.transformResponse(openaiResponse, {});
// Check that no logs were created
const logExists = fs.existsSync(logDir);
console.log(`Log directory exists: ${logExists}`);
if (logExists) {
console.log('ERROR: Logs created when debug mode is OFF!');
process.exit(1);
} else {
console.log('✓ No logs created (correct)');
}
}
console.log('\n=== Test 2: Debug Mode ON (via config) ===');
{
const transformer = new GlmtTransformer({ verbose: true, debugLog: true });
console.log(`Debug logging: ${transformer.debugLog}`);
console.log(`Log directory: ${transformer.debugLogDir}`);
const input = {
model: 'claude-sonnet-4.5',
messages: [{ role: 'user', content: 'Test with debug' }]
};
const { openaiRequest } = transformer.transformRequest(input);
const openaiResponse = {
id: 'test-2',
model: 'GLM-4.6',
choices: [{
message: {
role: 'assistant',
content: 'Response with debug',
reasoning_content: 'Detailed reasoning process for debugging...'
},
finish_reason: 'stop'
}],
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 }
};
transformer.transformResponse(openaiResponse, {});
// Check that logs were created
const logExists = fs.existsSync(logDir);
console.log(`\nLog directory exists: ${logExists}`);
if (!logExists) {
console.log('ERROR: No logs created when debug mode is ON!');
process.exit(1);
}
const files = fs.readdirSync(logDir);
console.log(`Files created: ${files.length}`);
console.log(`Files:`, files);
if (files.length !== 4) {
console.log(`ERROR: Expected 4 files, got ${files.length}`);
process.exit(1);
}
// Check file names
const hasRequestAnthropic = files.some(f => f.includes('request-anthropic'));
const hasRequestOpenai = files.some(f => f.includes('request-openai'));
const hasResponseOpenai = files.some(f => f.includes('response-openai'));
const hasResponseAnthropic = files.some(f => f.includes('response-anthropic'));
console.log(`\nFile types found:`);
console.log(` request-anthropic: ${hasRequestAnthropic ? '✓' : '✗'}`);
console.log(` request-openai: ${hasRequestOpenai ? '✓' : '✗'}`);
console.log(` response-openai: ${hasResponseOpenai ? '✓' : '✗'}`);
console.log(` response-anthropic: ${hasResponseAnthropic ? '✓' : '✗'}`);
if (!hasRequestAnthropic || !hasRequestOpenai || !hasResponseOpenai || !hasResponseAnthropic) {
console.log('ERROR: Missing expected file types!');
process.exit(1);
}
// Check file contents
const responseOpenaiFile = files.find(f => f.includes('response-openai'));
const responseOpenaiPath = path.join(logDir, responseOpenaiFile);
const responseOpenaiData = JSON.parse(fs.readFileSync(responseOpenaiPath, 'utf8'));
console.log(`\nChecking response-openai.json for reasoning_content...`);
if (responseOpenaiData.choices[0].message.reasoning_content) {
console.log('✓ reasoning_content found in response-openai.json');
console.log(` Length: ${responseOpenaiData.choices[0].message.reasoning_content.length} chars`);
} else {
console.log('ERROR: reasoning_content NOT found in response-openai.json!');
process.exit(1);
}
const responseAnthropicFile = files.find(f => f.includes('response-anthropic'));
const responseAnthropicPath = path.join(logDir, responseAnthropicFile);
const responseAnthropicData = JSON.parse(fs.readFileSync(responseAnthropicPath, 'utf8'));
console.log(`\nChecking response-anthropic.json for thinking block...`);
const thinkingBlock = responseAnthropicData.content.find(b => b.type === 'thinking');
if (thinkingBlock) {
console.log('✓ Thinking block found in response-anthropic.json');
console.log(` Length: ${thinkingBlock.thinking.length} chars`);
} else {
console.log('ERROR: Thinking block NOT found in response-anthropic.json!');
process.exit(1);
}
console.log('\n✓ All checks passed for debug mode ON');
}
console.log('\n=== Test 3: Debug Mode via CCS_DEBUG_LOG=1 ===');
{
// Clean up
fs.rmSync(logDir, { recursive: true, force: true });
process.env.CCS_DEBUG_LOG = '1';
const transformer = new GlmtTransformer({ verbose: false });
console.log(`Debug logging: ${transformer.debugLog}`);
const input = {
model: 'claude-sonnet-4.5',
messages: [{ role: 'user', content: 'Test with env var' }]
};
transformer.transformRequest(input);
const openaiResponse = {
id: 'test-3',
model: 'GLM-4.6',
choices: [{
message: {
role: 'assistant',
content: 'Response',
reasoning_content: 'Reasoning...'
},
finish_reason: 'stop'
}],
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 }
};
transformer.transformResponse(openaiResponse, {});
const files = fs.readdirSync(logDir);
console.log(`Files created: ${files.length}`);
if (files.length === 4) {
console.log('✓ Debug mode enabled via CCS_DEBUG_LOG=1');
} else {
console.log(`ERROR: Expected 4 files, got ${files.length}`);
process.exit(1);
}
delete process.env.CCS_DEBUG_LOG;
}
console.log('\n=== Test 4: Error Handling (No Write Permission) ===');
{
// Create a read-only directory to test error handling
const readOnlyDir = path.join(os.tmpdir(), 'ccs-readonly-test');
if (fs.existsSync(readOnlyDir)) {
fs.chmodSync(readOnlyDir, 0o755);
fs.rmSync(readOnlyDir, { recursive: true, force: true });
}
fs.mkdirSync(readOnlyDir, { recursive: true });
fs.chmodSync(readOnlyDir, 0o444); // Read-only
const transformer = new GlmtTransformer({
debugLog: true,
debugLogDir: readOnlyDir,
verbose: false
});
console.log('Testing graceful error handling with read-only directory...');
const input = {
model: 'claude-sonnet-4.5',
messages: [{ role: 'user', content: 'Test error handling' }]
};
try {
transformer.transformRequest(input);
console.log('✓ No crash on write error (graceful handling)');
} catch (error) {
console.log('ERROR: Transformer crashed on write error!');
console.log(error);
process.exit(1);
} finally {
// Clean up
fs.chmodSync(readOnlyDir, 0o755);
fs.rmSync(readOnlyDir, { recursive: true, force: true });
}
}
// Clean up test logs
console.log('\n=== Cleaning up test logs ===');
if (fs.existsSync(logDir)) {
fs.rmSync(logDir, { recursive: true, force: true });
console.log(`Removed ${logDir}`);
}
console.log('\n=== ALL TESTS PASSED ===');
+170
View File
@@ -0,0 +1,170 @@
#!/usr/bin/env bash
#
# GLMT Integration Test Suite
# Tests proxy startup, configuration, and basic functionality
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CCS_DIR="$(dirname "$SCRIPT_DIR")"
echo "=== GLMT Integration Test Suite ==="
echo ""
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
PASSED=0
FAILED=0
test_pass() {
echo -e "${GREEN}✓${NC} $1"
((PASSED++))
}
test_fail() {
echo -e "${RED}✗${NC} $1"
((FAILED++))
}
test_info() {
echo -e "${YELLOW}ℹ${NC} $1"
}
# Test 1: Check GLMT profile exists in config
echo "Test 1: GLMT profile configuration"
if grep -q '"glmt"' ~/.ccs/config.json; then
test_pass "GLMT profile found in config.json"
else
test_fail "GLMT profile NOT found in config.json"
fi
# Test 2: Check GLMT settings file exists
echo "Test 2: GLMT settings file"
if [ -f ~/.ccs/glmt.settings.json ]; then
test_pass "GLMT settings file exists"
# Check if API key is configured
if grep -q "YOUR_GLM_API_KEY_HERE" ~/.ccs/glmt.settings.json; then
test_info "API key not configured (still has placeholder)"
else
test_pass "API key configured"
fi
else
test_fail "GLMT settings file NOT found"
fi
# Test 3: Check transformer module
echo "Test 3: Transformer module"
if [ -f "$CCS_DIR/bin/glmt-transformer.js" ]; then
test_pass "Transformer module exists"
# Test syntax
if node --check "$CCS_DIR/bin/glmt-transformer.js" 2>/dev/null; then
test_pass "Transformer syntax valid"
else
test_fail "Transformer syntax invalid"
fi
else
test_fail "Transformer module NOT found"
fi
# Test 4: Check proxy module
echo "Test 4: Proxy module"
if [ -f "$CCS_DIR/bin/glmt-proxy.js" ]; then
test_pass "Proxy module exists"
# Test syntax
if node --check "$CCS_DIR/bin/glmt-proxy.js" 2>/dev/null; then
test_pass "Proxy syntax valid"
else
test_fail "Proxy syntax invalid"
fi
else
test_fail "Proxy module NOT found"
fi
# Test 5: Test proxy startup
echo "Test 5: Proxy startup test"
test_info "Starting proxy in background..."
# Start proxy
node "$CCS_DIR/bin/glmt-proxy.js" &
PROXY_PID=$!
# Wait for PROXY_READY signal (with timeout)
TIMEOUT=5
PORT=""
for i in $(seq 1 $TIMEOUT); do
if ps -p $PROXY_PID > /dev/null 2>&1; then
sleep 0.2
# Check if proxy outputted anything (we can't easily capture it in background)
if [ $i -eq $TIMEOUT ]; then
test_fail "Proxy started but PROXY_READY signal not captured (this is OK - proxy works)"
PORT="unknown"
fi
else
test_fail "Proxy failed to start or exited immediately"
break
fi
done
if ps -p $PROXY_PID > /dev/null 2>&1; then
test_pass "Proxy process is running (PID: $PROXY_PID)"
# Kill proxy
kill $PROXY_PID 2>/dev/null || true
sleep 0.5
if ! ps -p $PROXY_PID > /dev/null 2>&1; then
test_pass "Proxy terminated gracefully"
else
test_fail "Proxy did not terminate (killing forcefully)"
kill -9 $PROXY_PID 2>/dev/null || true
fi
fi
# Test 6: Unit tests
echo "Test 6: Unit tests"
if [ -f "$CCS_DIR/tests/glmt-transformer.test.js" ]; then
test_info "Running transformer unit tests..."
if node "$CCS_DIR/tests/glmt-transformer.test.js" | grep -q "Passed: 12/12"; then
test_pass "All 12 unit tests passed"
else
test_fail "Some unit tests failed"
fi
else
test_fail "Unit test file NOT found"
fi
# Test 7: Help text
echo "Test 7: Help text verification"
if ccs --help | grep -q "ccs glmt"; then
test_pass "GLMT appears in help text"
else
test_fail "GLMT NOT in help text"
fi
# Summary
echo ""
echo "=== Test Summary ==="
echo -e "Passed: ${GREEN}$PASSED${NC}"
echo -e "Failed: ${RED}$FAILED${NC}"
echo ""
if [ $FAILED -eq 0 ]; then
echo -e "${GREEN}All tests passed!${NC}"
echo ""
echo "Next steps:"
echo " 1. Test with Claude Code: ccs glmt \"What is 2+2?\""
echo " 2. Test thinking tags: ccs glmt \"<Thinking:On> Explain recursion\""
echo " 3. Check thinking blocks appear in Claude Code UI"
exit 0
else
echo -e "${RED}Some tests failed. Please review above.${NC}"
exit 1
fi
+355
View File
@@ -0,0 +1,355 @@
#!/usr/bin/env node
'use strict';
const GlmtTransformer = require('../bin/glmt-transformer');
/**
* Simple test runner (no external dependencies)
*/
class TestRunner {
constructor() {
this.tests = [];
this.passed = 0;
this.failed = 0;
}
test(name, fn) {
this.tests.push({ name, fn });
}
async run() {
console.log('\n=== GLM Thinking Transformer Tests ===\n');
for (const { name, fn } of this.tests) {
try {
await fn();
console.log(`✓ ${name}`);
this.passed++;
} catch (error) {
console.error(`✗ ${name}`);
console.error(` Error: ${error.message}`);
this.failed++;
}
}
console.log(`\n=== Results ===`);
console.log(`Passed: ${this.passed}/${this.tests.length}`);
console.log(`Failed: ${this.failed}/${this.tests.length}`);
return this.failed === 0;
}
}
/**
* Simple assertion helpers
*/
function assertEqual(actual, expected, message) {
if (actual !== expected) {
throw new Error(
`${message || 'Assertion failed'}\n` +
` Expected: ${JSON.stringify(expected)}\n` +
` Actual: ${JSON.stringify(actual)}`
);
}
}
function assertExists(value, message) {
if (value === undefined || value === null) {
throw new Error(message || 'Value should exist');
}
}
function assertDeepEqual(actual, expected, message) {
const actualStr = JSON.stringify(actual);
const expectedStr = JSON.stringify(expected);
if (actualStr !== expectedStr) {
throw new Error(
`${message || 'Deep equality failed'}\n` +
` Expected: ${expectedStr}\n` +
` Actual: ${actualStr}`
);
}
}
// Test suite
const runner = new TestRunner();
// Test 1: Transform Anthropic request to OpenAI format
runner.test('transforms Anthropic request to OpenAI format', () => {
const transformer = new GlmtTransformer();
const input = {
model: 'claude-sonnet-4.5',
messages: [{ role: 'user', content: 'Hello' }],
max_tokens: 4096
};
const { openaiRequest } = transformer.transformRequest(input);
assertEqual(openaiRequest.model, 'GLM-4.6', 'Model should be GLM-4.6');
assertEqual(openaiRequest.do_sample, true, 'do_sample should be true');
assertEqual(openaiRequest.max_tokens, 128000, 'max_tokens should be 128000');
assertExists(openaiRequest.messages, 'messages should exist');
});
// Test 2: Extract thinking control tags
runner.test('extracts thinking control tags', () => {
const transformer = new GlmtTransformer();
const input = {
model: 'claude-sonnet-4.5',
messages: [{
role: 'user',
content: '<Thinking:On> <Effort:High> Solve this problem'
}]
};
const { thinkingConfig } = transformer.transformRequest(input);
assertEqual(thinkingConfig.thinking, true, 'thinking should be On');
assertEqual(thinkingConfig.effort, 'high', 'effort should be high');
});
// Test 3: Extract thinking off tag
runner.test('respects Thinking:Off tag', () => {
const transformer = new GlmtTransformer();
const input = {
model: 'claude-sonnet-4.5',
messages: [{
role: 'user',
content: '<Thinking:Off> Quick question'
}]
};
const { thinkingConfig } = transformer.transformRequest(input);
assertEqual(thinkingConfig.thinking, false, 'thinking should be Off');
});
// Test 4: Convert reasoning_content to thinking block
runner.test('converts reasoning_content to thinking block', () => {
const transformer = new GlmtTransformer();
const openaiResponse = {
id: 'chatcmpl-123',
model: 'GLM-4.6',
choices: [{
message: {
role: 'assistant',
content: 'Here is the answer',
reasoning_content: 'Let me think through this problem...'
},
finish_reason: 'stop'
}],
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 }
};
const result = transformer.transformResponse(openaiResponse, {});
assertEqual(result.content.length, 2, 'Should have 2 content blocks');
assertEqual(result.content[0].type, 'thinking', 'First block should be thinking');
assertEqual(
result.content[0].thinking,
'Let me think through this problem...',
'Thinking content should match'
);
assertEqual(result.content[1].type, 'text', 'Second block should be text');
assertEqual(
result.content[1].text,
'Here is the answer',
'Text content should match'
);
});
// Test 5: Handle response without reasoning_content
runner.test('handles response without reasoning_content', () => {
const transformer = new GlmtTransformer();
const openaiResponse = {
id: 'chatcmpl-123',
model: 'GLM-4.6',
choices: [{
message: {
role: 'assistant',
content: 'Simple answer'
},
finish_reason: 'stop'
}]
};
const result = transformer.transformResponse(openaiResponse, {});
assertEqual(result.content.length, 1, 'Should have 1 content block');
assertEqual(result.content[0].type, 'text', 'Block should be text');
assertEqual(result.content[0].text, 'Simple answer', 'Text should match');
});
// Test 6: Thinking signature generation
runner.test('generates thinking signature', () => {
const transformer = new GlmtTransformer();
const thinking = 'This is my reasoning process';
const signature = transformer._generateThinkingSignature(thinking);
assertExists(signature.type, 'signature.type should exist');
assertEqual(signature.type, 'thinking_signature', 'type should be thinking_signature');
assertExists(signature.hash, 'signature.hash should exist');
assertEqual(signature.hash.length, 16, 'hash should be 16 chars');
assertEqual(signature.length, thinking.length, 'length should match thinking length');
assertExists(signature.timestamp, 'timestamp should exist');
});
// Test 7: Stop reason mapping
runner.test('maps OpenAI stop reasons to Anthropic', () => {
const transformer = new GlmtTransformer();
assertEqual(transformer._mapStopReason('stop'), 'end_turn', 'stop → end_turn');
assertEqual(transformer._mapStopReason('length'), 'max_tokens', 'length → max_tokens');
assertEqual(transformer._mapStopReason('tool_calls'), 'tool_use', 'tool_calls → tool_use');
assertEqual(
transformer._mapStopReason('unknown'),
'end_turn',
'unknown → end_turn (default)'
);
});
// Test 8: Preserve temperature and top_p
runner.test('preserves temperature and top_p parameters', () => {
const transformer = new GlmtTransformer();
const input = {
model: 'claude-sonnet-4.5',
messages: [{ role: 'user', content: 'Test' }],
temperature: 0.7,
top_p: 0.9
};
const { openaiRequest } = transformer.transformRequest(input);
assertEqual(openaiRequest.temperature, 0.7, 'temperature should be preserved');
assertEqual(openaiRequest.top_p, 0.9, 'top_p should be preserved');
});
// Test 9: Default thinking enabled
runner.test('enables thinking by default', () => {
const transformer = new GlmtTransformer({ defaultThinking: true });
const input = {
model: 'claude-sonnet-4.5',
messages: [{ role: 'user', content: 'Test' }]
};
const { openaiRequest, thinkingConfig } = transformer.transformRequest(input);
assertEqual(thinkingConfig.thinking, true, 'thinking should be enabled by default');
assertEqual(openaiRequest.reasoning, true, 'reasoning should be in request');
});
// Test 10: Error handling in transformRequest
runner.test('handles errors in transformRequest gracefully', () => {
const transformer = new GlmtTransformer();
const invalidInput = null; // Invalid input
const { openaiRequest, thinkingConfig, error } = transformer.transformRequest(invalidInput);
assertExists(error, 'error should be present');
assertEqual(thinkingConfig.thinking, false, 'thinking should be disabled on error');
});
// Test 11: Error handling in transformResponse
runner.test('handles errors in transformResponse gracefully', () => {
const transformer = new GlmtTransformer();
const invalidResponse = {}; // Missing choices
const result = transformer.transformResponse(invalidResponse, {});
assertEqual(result.type, 'message', 'should return valid message type');
assertEqual(result.role, 'assistant', 'should be assistant role');
assertExists(result.content[0].text, 'should have error text');
});
// Test 12: Streaming disabled (not yet supported)
runner.test('disables streaming (not yet supported)', () => {
const transformer = new GlmtTransformer();
const input = {
model: 'claude-sonnet-4.5',
messages: [{ role: 'user', content: 'Test' }],
stream: true
};
const { openaiRequest } = transformer.transformRequest(input);
assertEqual(openaiRequest.stream, false, 'stream should be disabled');
});
// Test 13: Debug mode disabled by default
runner.test('debug mode disabled by default', () => {
const transformer = new GlmtTransformer();
assertEqual(transformer.debugLog, false, 'debugLog should be false by default');
});
// Test 14: Debug mode enabled via config
runner.test('debug mode enabled via config', () => {
const transformer = new GlmtTransformer({ debugLog: true });
assertEqual(transformer.debugLog, true, 'debugLog should be true when enabled');
});
// Test 15: Debug mode enabled via env var
runner.test('debug mode enabled via CCS_DEBUG_LOG=1', () => {
process.env.CCS_DEBUG_LOG = '1';
const transformer = new GlmtTransformer();
assertEqual(transformer.debugLog, true, 'debugLog should be true when CCS_DEBUG_LOG=1');
delete process.env.CCS_DEBUG_LOG;
});
// Test 16: Debug log directory path
runner.test('debug log directory uses ~/.ccs/logs by default', () => {
const transformer = new GlmtTransformer();
const os = require('os');
const path = require('path');
const expectedPath = path.join(os.homedir(), '.ccs', 'logs');
assertEqual(transformer.debugLogDir, expectedPath, 'debugLogDir should be ~/.ccs/logs');
});
// Test 17: Validate transformation checks
runner.test('validates transformation with all checks', () => {
const transformer = new GlmtTransformer();
const validResponse = {
type: 'message',
role: 'assistant',
content: [
{ type: 'thinking', thinking: 'reasoning...' },
{ type: 'text', text: 'answer' }
],
usage: { input_tokens: 10, output_tokens: 20 }
};
const validation = transformer._validateTransformation(validResponse);
assertEqual(validation.passed, 5, 'All 5 checks should pass');
assertEqual(validation.total, 5, 'Should have 5 total checks');
assertEqual(validation.valid, true, 'Should be valid');
assertEqual(validation.checks.hasContent, true, 'hasContent check');
assertEqual(validation.checks.hasThinking, true, 'hasThinking check');
assertEqual(validation.checks.hasText, true, 'hasText check');
assertEqual(validation.checks.validStructure, true, 'validStructure check');
assertEqual(validation.checks.hasUsage, true, 'hasUsage check');
});
// Test 18: Validate transformation with missing thinking
runner.test('validates transformation without thinking block', () => {
const transformer = new GlmtTransformer();
const response = {
type: 'message',
role: 'assistant',
content: [
{ type: 'text', text: 'answer' }
],
usage: { input_tokens: 10, output_tokens: 20 }
};
const validation = transformer._validateTransformation(response);
assertEqual(validation.passed, 4, '4 checks should pass (no thinking)');
assertEqual(validation.checks.hasThinking, false, 'hasThinking should be false');
assertEqual(validation.checks.hasText, true, 'hasText should be true');
});
// Run tests
runner.run().then(success => {
process.exit(success ? 0 : 1);
}).catch(error => {
console.error('Test runner error:', error);
process.exit(1);
});
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env node
'use strict';
const GlmtTransformer = require('../bin/glmt-transformer');
console.log('=== Performance Test: Debug Mode Impact ===\n');
const iterations = 1000;
const sampleRequest = {
model: 'claude-sonnet-4.5',
messages: [{ role: 'user', content: 'Test performance' }]
};
const sampleResponse = {
id: 'perf-test',
model: 'GLM-4.6',
choices: [{
message: {
role: 'assistant',
content: 'Quick response',
reasoning_content: 'Some reasoning content here...'
},
finish_reason: 'stop'
}],
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 }
};
// Test 1: Debug OFF
console.log(`Test 1: Debug OFF (${iterations} iterations)`);
const transformerOff = new GlmtTransformer({ debugLog: false, verbose: false });
const startOff = Date.now();
for (let i = 0; i < iterations; i++) {
transformerOff.transformRequest(sampleRequest);
transformerOff.transformResponse(sampleResponse, {});
}
const endOff = Date.now();
const timeOff = endOff - startOff;
console.log(` Total time: ${timeOff}ms`);
console.log(` Average per request: ${(timeOff / iterations).toFixed(2)}ms`);
// Test 2: Debug ON
console.log(`\nTest 2: Debug ON (${iterations} iterations)`);
const transformerOn = new GlmtTransformer({ debugLog: true, verbose: false });
const startOn = Date.now();
for (let i = 0; i < iterations; i++) {
transformerOn.transformRequest(sampleRequest);
transformerOn.transformResponse(sampleResponse, {});
}
const endOn = Date.now();
const timeOn = endOn - startOn;
console.log(` Total time: ${timeOn}ms`);
console.log(` Average per request: ${(timeOn / iterations).toFixed(2)}ms`);
// Calculate overhead
const overhead = timeOn - timeOff;
const overheadPercent = ((overhead / timeOff) * 100).toFixed(2);
console.log(`\n=== Results ===`);
console.log(`Debug OFF: ${timeOff}ms`);
console.log(`Debug ON: ${timeOn}ms`);
console.log(`Overhead: ${overhead}ms (${overheadPercent}%)`);
// Note: High overhead is expected due to file I/O
console.log(`\nNote: Debug mode is opt-in only and disabled by default.`);
console.log(`When disabled, there is NO performance impact (early return in _writeDebugLog).`);
// Cleanup
const fs = require('fs');
const path = require('path');
const os = require('os');
const logDir = path.join(os.homedir(), '.ccs', 'logs');
if (fs.existsSync(logDir)) {
fs.rmSync(logDir, { recursive: true, force: true });
console.log(`\nCleaned up ${iterations * 4} debug log files.`);
}
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env node
'use strict';
const GlmtTransformer = require('../bin/glmt-transformer');
console.log('=== Demo: Verbose Output with Reasoning Detection ===\n');
const transformer = new GlmtTransformer({ verbose: true, debugLog: false });
const openaiResponse = {
id: 'demo-response',
model: 'GLM-4.6',
choices: [{
message: {
role: 'assistant',
content: 'The answer is 42. This is based on careful analysis of the problem.',
reasoning_content: `Let me think through this problem step by step.
First, I need to understand what's being asked. The question relates to the fundamental nature of the universe.
Based on Douglas Adams' work, we know that after Deep Thought computed for 7.5 million years, it determined that the answer to the Ultimate Question of Life, the Universe, and Everything is 42.
However, the actual question itself was never properly formulated. This suggests that knowing the answer is meaningless without understanding the question.
In a practical sense, this teaches us an important lesson about problem-solving: we must first ensure we're asking the right questions before seeking answers.
Therefore, my response will acknowledge both the famous answer and the philosophical implications of the question-answer relationship.`
},
finish_reason: 'stop'
}],
usage: { prompt_tokens: 50, completion_tokens: 150, total_tokens: 200 }
};
console.log('Transforming OpenAI response with reasoning_content to Anthropic format...\n');
const result = transformer.transformResponse(openaiResponse, { thinking: true });
console.log('\n=== Transformation Complete ===');
console.log(`\nResult structure:`);
console.log(` Type: ${result.type}`);
console.log(` Role: ${result.role}`);
console.log(` Content blocks: ${result.content.length}`);
console.log(` - Block 1: ${result.content[0].type}`);
console.log(` - Block 2: ${result.content[1].type}`);
console.log(` Stop reason: ${result.stop_reason}`);
console.log(` Usage: input=${result.usage.prompt_tokens}, output=${result.usage.completion_tokens}`);