feat(glmt): add streaming with real-time thinking blocks

- reorganize bin/ into auth/, glmt/, management/, utils/
- add budget calculator and locale enforcer
- enhance test coverage with unit/integration separation
This commit is contained in:
kaitranntt committed 2025-11-11 15:39:00 -05:00
1 parent 80f9cc644e
commit 5fae92ac07
51 files changed
+3768 -823

No files matched your search

+184
View File
@@ -0,0 +1,184 @@
# CCS COMPREHENSIVE TEST SUITE - Master Orchestrator (PowerShell)
# Runs all test suites in the correct order
# Maintains backward compatibility with existing usage
$ErrorActionPreference = "Continue"
# Get the directory where this script is located
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
Write-Host "========================================" -ForegroundColor Yellow
Write-Host "CCS COMPREHENSIVE TEST SUITE" -ForegroundColor Yellow
Write-Host "========================================" -ForegroundColor Yellow
Write-Host ""
Write-Host "This master test suite runs:" -ForegroundColor Cyan
Write-Host " 1. Native Windows tests (PowerShell installation)" -ForegroundColor Gray
Write-Host " 2. npm package tests (if Node.js available)" -ForegroundColor Gray
Write-Host ""
# Track overall results
$OverallPass = 0
$OverallFail = 0
$OverallTotal = 0
function Test-Case {
param(
[string]$Name,
[scriptblock]$Test,
[string]$ExpectedBehavior
)
$script:TotalTests++
Write-Host ""
Write-Host "[$script:TotalTests] $Name" -ForegroundColor Cyan
Write-Host " Expected: $ExpectedBehavior" -ForegroundColor Gray
try {
$result = & $Test
if ($result) {
Write-Host " Result: PASS" -ForegroundColor Green
$script:PassCount++
return $true
} else {
Write-Host " Result: FAIL" -ForegroundColor Red
$script:FailCount++
return $false
}
} catch {
Write-Host " Result: FAIL" -ForegroundColor Red
Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Red
$script:FailCount++
return $false
}
}
# Function to run a test suite
function Run-TestSuite {
param(
[string]$SuiteName,
[scriptblock]$SuiteCommand
)
Write-Host ""
Write-Host "===== Running $SuiteName =====" -ForegroundColor Yellow
Write-Host ""
# Reset counters
$script:PassCount = 0
$script:FailCount = 0
$script:TotalTests = 0
try {
& $SuiteCommand
$suitePassed = $script:PassCount
$suiteFailed = $script:FailCount
$suiteTotal = $script:TotalTests
Write-Host "$SuiteName completed successfully" -ForegroundColor Green
Write-Host "Tests: $suitePassed/$suiteTotal passed" -ForegroundColor Cyan
# Add to overall totals
$script:OverallPass += $suitePassed
$script:OverallFail += $suiteFailed
$script:OverallTotal += $suiteTotal
return $suiteFailed -eq 0
} catch {
Write-Host "$SuiteName failed with exception" -ForegroundColor Red
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
$script:OverallFail++
return $false
}
}
# Test Suite 1: Native Windows Tests
$WindowsTestPath = Join-Path $ScriptDir "native\windows\edge-cases.ps1"
if (Test-Path $WindowsTestPath) {
if (Run-TestSuite "Native Windows Tests" { & $WindowsTestPath }) {
Write-Host "✓ Native Windows tests passed" -ForegroundColor Green
} else {
Write-Host "⚠ Native Windows tests had failures" -ForegroundColor Yellow
}
} else {
Write-Host "⚠ Native Windows tests not found, skipping" -ForegroundColor Yellow
}
# Test Suite 2: npm Package Tests
$NodeCommand = Get-Command node -ErrorAction SilentlyContinue
$PackageJsonPath = Join-Path $ScriptDir "..\package.json"
if ($NodeCommand -and (Test-Path $PackageJsonPath)) {
Write-Host ""
Write-Host "===== Running npm Package Tests =====" -ForegroundColor Yellow
Write-Host ""
try {
Push-Location (Split-Path -Parent $PackageJsonPath)
$npmResult = npm run test:npm 2>&1
$npmExitCode = $LASTEXITCODE
Pop-Location
if ($npmExitCode -eq 0) {
Write-Host "✓ npm package tests passed" -ForegroundColor Green
Write-Host "npm tests completed successfully" -ForegroundColor Cyan
} else {
Write-Host "⚠ npm package tests had failures" -ForegroundColor Yellow
Write-Host "npm output:" -ForegroundColor Gray
Write-Host $npmResult -ForegroundColor Gray
$script:OverallFail++
}
} catch {
Write-Host "⚠ npm package tests had errors" -ForegroundColor Yellow
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
$script:OverallFail++
}
} else {
if (-not $NodeCommand) {
Write-Host "⚠ Node.js not found, skipping npm tests" -ForegroundColor Yellow
Write-Host " Install Node.js to run npm package tests" -ForegroundColor Gray
} else {
Write-Host "⚠ package.json not found, skipping npm tests" -ForegroundColor Yellow
}
}
# Final Summary
Write-Host ""
Write-Host "========================================" -ForegroundColor Yellow
Write-Host "FINAL TEST RESULTS" -ForegroundColor Yellow
Write-Host "========================================" -ForegroundColor Yellow
Write-Host ""
if ($OverallTotal -gt 0) {
Write-Host "Total Tests: $OverallTotal" -ForegroundColor Cyan
Write-Host "Passed: $OverallPass" -ForegroundColor Green
if ($OverallFail -eq 0) {
Write-Host "Failed: $OverallFail" -ForegroundColor Green
} else {
Write-Host "Failed: $OverallFail" -ForegroundColor Red
}
$SuccessRate = if ($OverallTotal -gt 0) { [math]::Round(($OverallPass / $OverallTotal) * 100, 2) } else { 0 }
Write-Host "Success Rate: $SuccessRate%" -ForegroundColor $(if ($SuccessRate -ge 90) { "Green" } elseif ($SuccessRate -ge 70) { "Yellow" } else { "Red" })
Write-Host ""
if ($OverallFail -eq 0) {
Write-Host "========================================" -ForegroundColor Green
Write-Host "ALL TESTS PASSED!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host ""
Write-Host "CCS is ready for production use!" -ForegroundColor Green
exit 0
} else {
Write-Host "========================================" -ForegroundColor Yellow
Write-Host "SOME TESTS FAILED" -ForegroundColor Yellow
Write-Host "========================================" -ForegroundColor Yellow
Write-Host ""
Write-Host "Review failed tests above for details" -ForegroundColor Yellow
exit 1
}
} else {
Write-Host "No tests were executed" -ForegroundColor Yellow
exit 1
}
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env bash
# CCS COMPREHENSIVE TEST SUITE - Master Orchestrator
# Runs all test suites in the correct order
# Maintains backward compatibility with existing usage
set -e
# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source shared utilities
source "$SCRIPT_DIR/shared/helpers.sh"
# Print test header
print_test_header "CCS COMPREHENSIVE TEST SUITE"
echo ""
echo -e "${CYAN}This master test suite runs:${NC}"
echo -e "${GRAY} 1. Native Unix tests (traditional installation)${NC}"
echo -e "${GRAY} 2. npm package tests (Node.js framework)${NC}"
echo ""
# Track overall results
OVERALL_PASS=0
OVERALL_FAIL=0
OVERALL_TOTAL=0
# Function to run a test suite and capture results
run_test_suite() {
local suite_name="$1"
local suite_command="$2"
echo ""
print_section_header "Running $suite_name"
echo ""
# Reset counters for this suite
reset_test_counters
# Run the test suite and capture output
if bash -c "$suite_command"; then
# Suite passed
local suite_passed=$PASS_COUNT
local suite_failed=$FAIL_COUNT
local suite_total=$TOTAL_TESTS
echo -e "${GREEN}$suite_name completed successfully${NC}"
echo -e "${CYAN}Tests: $suite_passed/$suite_total passed${NC}"
# Add to overall totals
((OVERALL_PASS += suite_passed))
((OVERALL_FAIL += suite_failed))
((OVERALL_TOTAL += suite_total))
return 0
else
# Suite failed
local suite_passed=$PASS_COUNT
local suite_failed=$FAIL_COUNT
local suite_total=$TOTAL_TESTS
echo -e "${RED}$suite_name failed${NC}"
echo -e "${YELLOW}Tests: $suite_passed/$suite_total passed, $suite_failed failed${NC}"
# Add to overall totals
((OVERALL_PASS += suite_passed))
((OVERALL_FAIL += suite_failed))
((OVERALL_TOTAL += suite_total))
return 1
fi
}
# Test Suite 1: Native Unix Tests
if [[ -f "$SCRIPT_DIR/native/unix/edge-cases.sh" ]]; then
if run_test_suite "Native Unix Tests" "cd '$SCRIPT_DIR' && bash native/unix/edge-cases.sh"; then
echo -e "${GREEN}✓ Native Unix tests passed${NC}"
else
echo -e "${YELLOW}⚠ Native Unix tests had failures${NC}"
fi
else
echo -e "${YELLOW}⚠ Native Unix tests not found, skipping${NC}"
fi
# Test Suite 2: npm Package Tests
if command -v node &> /dev/null; then
if [[ -f "$SCRIPT_DIR/../package.json" ]]; then
echo ""
print_section_header "Running npm Package Tests"
echo ""
# Reset counters for npm tests
reset_test_counters
# Change to the project root directory and run npm tests
if cd "$SCRIPT_DIR/.." && npm run test:npm 2>/dev/null; then
echo -e "${GREEN}✓ npm package tests passed${NC}"
# Note: npm tests don't use our counter system, so we'll estimate
# We could parse npm test output, but for now just acknowledge success
echo -e "${CYAN}npm tests completed successfully${NC}"
else
echo -e "${YELLOW}⚠ npm package tests had failures or could not run${NC}"
((OVERALL_FAIL++)) # Count as at least one failure
fi
# Return to tests directory
cd "$SCRIPT_DIR"
else
echo -e "${YELLOW}⚠ package.json not found, skipping npm tests${NC}"
fi
else
echo -e "${YELLOW}⚠ Node.js not found, skipping npm tests${NC}"
echo -e "${GRAY} Install Node.js to run npm package tests${NC}"
fi
# Final Summary
echo ""
print_test_header "FINAL TEST RESULTS"
# Update final counters with actual totals
TOTAL_TESTS=$OVERALL_TOTAL
PASS_COUNT=$OVERALL_PASS
FAIL_COUNT=$OVERALL_FAIL
if [[ $TOTAL_TESTS -gt 0 ]]; then
print_test_summary
else
echo -e "${YELLOW}No tests were executed${NC}"
fi
print_final_result
+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
+202
View File
@@ -0,0 +1,202 @@
$ErrorActionPreference = "Stop"
# CCS v3.2.0 - Symlink Chain Validation Test (Windows)
# Tests double symlink chain: instance → shared → claude
$TestDir = "$env:TEMP\ccs-symlink-test-$PID"
$FakeClaude = "$TestDir\fake-claude"
$FakeShared = "$TestDir\fake-shared"
$FakeInstance = "$TestDir\fake-instance"
function Write-TestHeader {
Write-Host "╔════════════════════════════════════════╗"
Write-Host "║ CCS v3.2.0 Symlink Chain Test ║"
Write-Host "╚════════════════════════════════════════╝"
Write-Host ""
}
function Test-SymlinkChain {
Write-Host "[i] Test 1: Basic symlink chain"
New-Item -ItemType Directory -Path "$FakeClaude\commands" -Force | Out-Null
New-Item -ItemType Directory -Path "$FakeClaude\skills" -Force | Out-Null
New-Item -ItemType Directory -Path "$FakeClaude\agents" -Force | Out-Null
"# Test Command" | Out-File "$FakeClaude\commands\test-cmd.md"
"# Test Skill" | Out-File "$FakeClaude\skills\test-skill.md"
"# Test Agent" | Out-File "$FakeClaude\agents\test-agent.md"
New-Item -ItemType Directory -Path $FakeShared -Force | Out-Null
try {
# Test with symlinks (requires Developer Mode)
New-Item -ItemType SymbolicLink -Path "$FakeShared\commands" -Target "$FakeClaude\commands" -Force | Out-Null
New-Item -ItemType SymbolicLink -Path "$FakeShared\skills" -Target "$FakeClaude\skills" -Force | Out-Null
New-Item -ItemType SymbolicLink -Path "$FakeShared\agents" -Target "$FakeClaude\agents" -Force | Out-Null
New-Item -ItemType Directory -Path $FakeInstance -Force | Out-Null
New-Item -ItemType SymbolicLink -Path "$FakeInstance\commands" -Target "$FakeShared\commands" -Force | Out-Null
New-Item -ItemType SymbolicLink -Path "$FakeInstance\skills" -Target "$FakeShared\skills" -Force | Out-Null
New-Item -ItemType SymbolicLink -Path "$FakeInstance\agents" -Target "$FakeShared\agents" -Force | Out-Null
if ((Test-Path "$FakeInstance\commands\test-cmd.md") -and
(Test-Path "$FakeInstance\skills\test-skill.md") -and
(Test-Path "$FakeInstance\agents\test-agent.md")) {
Write-Host "[OK] Symlink chain works (Developer Mode enabled)" -ForegroundColor Green
return $true
}
} catch {
Write-Host "[!] Symlink failed - Developer Mode required" -ForegroundColor Yellow
Write-Host "[i] Testing copy fallback..." -ForegroundColor Yellow
# Test copy fallback
Copy-Item -Path "$FakeClaude\commands" -Destination "$FakeShared\commands" -Recurse -Force
Copy-Item -Path "$FakeClaude\skills" -Destination "$FakeShared\skills" -Recurse -Force
Copy-Item -Path "$FakeClaude\agents" -Destination "$FakeShared\agents" -Recurse -Force
Copy-Item -Path "$FakeShared\commands" -Destination "$FakeInstance\commands" -Recurse -Force
Copy-Item -Path "$FakeShared\skills" -Destination "$FakeInstance\skills" -Recurse -Force
Copy-Item -Path "$FakeShared\agents" -Destination "$FakeInstance\agents" -Recurse -Force
if ((Test-Path "$FakeInstance\commands\test-cmd.md") -and
(Test-Path "$FakeInstance\skills\test-skill.md") -and
(Test-Path "$FakeInstance\agents\test-agent.md")) {
Write-Host "[OK] Copy fallback works" -ForegroundColor Green
return $false # Symlinks not available
}
}
Write-Host "[X] Both symlink and copy failed" -ForegroundColor Red
throw "Test failed"
}
function Test-CircularDetection {
Write-Host "[i] Test 2: Circular symlink detection"
$CircularTest = "$TestDir\circular-test"
New-Item -ItemType Directory -Path "$CircularTest\shared" -Force | Out-Null
New-Item -ItemType Directory -Path "$CircularTest\claude" -Force | Out-Null
try {
# Create circular symlink: shared → claude → shared
New-Item -ItemType SymbolicLink -Path "$CircularTest\claude\commands" -Target "$CircularTest\shared" -Force -ErrorAction SilentlyContinue | Out-Null
$item = Get-Item "$CircularTest\claude\commands" -ErrorAction SilentlyContinue
if ($item -and $item.LinkType -eq "SymbolicLink") {
$target = $item.Target
if ($target -like "*\shared*") {
Write-Host "[OK] Circular symlink detected correctly" -ForegroundColor Green
return
}
}
} catch {
# Expected if symlinks not available
Write-Host "[i] Circular detection skipped (symlinks not available)" -ForegroundColor Yellow
return
}
Write-Host "[X] Circular symlink detection failed" -ForegroundColor Red
}
function Test-Performance {
Write-Host "[i] Test 3: Performance measurement"
$PerfTest = "$TestDir\perf-test"
New-Item -ItemType Directory -Path "$PerfTest\source" -Force | Out-Null
New-Item -ItemType Directory -Path "$PerfTest\target" -Force | Out-Null
try {
$start = Get-Date
for ($i = 1; $i -le 100; $i++) {
New-Item -ItemType SymbolicLink -Path "$PerfTest\target\link-$i" -Target "$PerfTest\source" -Force -ErrorAction Stop | Out-Null
}
$end = Get-Date
$duration = ($end - $start).TotalMilliseconds
$avg = [math]::Round($duration / 100, 2)
Write-Host "[OK] Avg symlink creation: ${avg}ms (target: <1ms)" -ForegroundColor Green
} catch {
Write-Host "[i] Performance test skipped (symlinks not available)" -ForegroundColor Yellow
}
}
function Test-BrokenSymlink {
Write-Host "[i] Test 4: Broken symlink detection"
$BrokenTest = "$TestDir\broken-test"
New-Item -ItemType Directory -Path "$BrokenTest\instance" -Force | Out-Null
try {
New-Item -ItemType SymbolicLink -Path "$BrokenTest\instance\commands" -Target "$BrokenTest\nonexistent" -Force -ErrorAction Stop | Out-Null
$item = Get-Item "$BrokenTest\instance\commands" -ErrorAction SilentlyContinue
if ($item -and $item.LinkType -eq "SymbolicLink" -and !(Test-Path "$BrokenTest\instance\commands")) {
Write-Host "[OK] Broken symlink detected correctly" -ForegroundColor Green
} else {
Write-Host "[X] Broken symlink detection failed" -ForegroundColor Red
}
} catch {
Write-Host "[i] Broken symlink test skipped (symlinks not available)" -ForegroundColor Yellow
}
}
function Test-LiveUpdates {
Write-Host "[i] Test 5: Live updates through chain"
try {
New-Item -ItemType Directory -Path "$FakeClaude\commands" -Force | Out-Null
New-Item -ItemType Directory -Path $FakeShared -Force | Out-Null
New-Item -ItemType Directory -Path $FakeInstance -Force | Out-Null
New-Item -ItemType SymbolicLink -Path "$FakeShared\commands" -Target "$FakeClaude\commands" -Force -ErrorAction Stop | Out-Null
New-Item -ItemType SymbolicLink -Path "$FakeInstance\commands" -Target "$FakeShared\commands" -Force -ErrorAction Stop | Out-Null
# Write to source
"# Original" | Out-File "$FakeClaude\commands\live-test.md"
# Verify accessible from instance
if (Test-Path "$FakeInstance\commands\live-test.md") {
$content = Get-Content "$FakeInstance\commands\live-test.md" -Raw
if ($content -match "# Original") {
Write-Host "[OK] Live updates work through chain" -ForegroundColor Green
} else {
Write-Host "[X] Content mismatch" -ForegroundColor Red
return
}
} else {
Write-Host "[X] File not accessible through chain" -ForegroundColor Red
return
}
# Update source
"# Updated" | Out-File "$FakeClaude\commands\live-test.md"
# Verify update visible from instance
$content = Get-Content "$FakeInstance\commands\live-test.md" -Raw
if ($content -match "# Updated") {
Write-Host "[OK] Live updates reflected instantly" -ForegroundColor Green
} else {
Write-Host "[X] Update not reflected" -ForegroundColor Red
}
} catch {
Write-Host "[i] Live updates test skipped (symlinks not available)" -ForegroundColor Yellow
}
}
try {
Write-TestHeader
Write-Host "Running validation tests..."
Write-Host ""
Test-SymlinkChain
Test-CircularDetection
Test-Performance
Test-BrokenSymlink
Test-LiveUpdates
Write-Host ""
Write-Host "╔════════════════════════════════════════╗"
Write-Host "║ All Tests Passed ✓ ║"
Write-Host "╚════════════════════════════════════════╝"
} finally {
Remove-Item $TestDir -Recurse -Force -ErrorAction SilentlyContinue
}
+173
View File
@@ -0,0 +1,173 @@
#!/usr/bin/env bash
set -euo pipefail
# CCS v3.2.0 - Symlink Chain Validation Test
# Tests double symlink chain: instance → shared → claude
TEST_DIR="/tmp/ccs-symlink-test-$$"
FAKE_CLAUDE="$TEST_DIR/fake-claude"
FAKE_SHARED="$TEST_DIR/fake-shared"
FAKE_INSTANCE="$TEST_DIR/fake-instance"
cleanup() {
rm -rf "$TEST_DIR"
}
trap cleanup EXIT
echo "╔════════════════════════════════════════╗"
echo "║ CCS v3.2.0 Symlink Chain Test ║"
echo "╚════════════════════════════════════════╝"
echo ""
# Test 1: Basic symlink chain
test_symlink_chain() {
echo "[i] Test 1: Basic symlink chain"
mkdir -p "$FAKE_CLAUDE/commands"
mkdir -p "$FAKE_CLAUDE/skills"
mkdir -p "$FAKE_CLAUDE/agents"
echo "# Test Command" > "$FAKE_CLAUDE/commands/test-cmd.md"
echo "# Test Skill" > "$FAKE_CLAUDE/skills/test-skill.md"
echo "# Test Agent" > "$FAKE_CLAUDE/agents/test-agent.md"
mkdir -p "$FAKE_SHARED"
ln -s "$FAKE_CLAUDE/commands" "$FAKE_SHARED/commands"
ln -s "$FAKE_CLAUDE/skills" "$FAKE_SHARED/skills"
ln -s "$FAKE_CLAUDE/agents" "$FAKE_SHARED/agents"
mkdir -p "$FAKE_INSTANCE"
ln -s "$FAKE_SHARED/commands" "$FAKE_INSTANCE/commands"
ln -s "$FAKE_SHARED/skills" "$FAKE_INSTANCE/skills"
ln -s "$FAKE_SHARED/agents" "$FAKE_INSTANCE/agents"
# Verify chain works for all directories
if [[ -f "$FAKE_INSTANCE/commands/test-cmd.md" ]] && \
[[ -f "$FAKE_INSTANCE/skills/test-skill.md" ]] && \
[[ -f "$FAKE_INSTANCE/agents/test-agent.md" ]]; then
echo "[OK] Symlink chain works for all directories"
else
echo "[X] Symlink chain broken"
return 1
fi
}
# Test 2: Circular symlink detection
test_circular_detection() {
echo "[i] Test 2: Circular symlink detection"
# Simulate circular symlink scenario
mkdir -p "$TEST_DIR/circular-test/shared"
mkdir -p "$TEST_DIR/circular-test/claude"
# Create circular symlink: shared → claude → shared
ln -s "$TEST_DIR/circular-test/shared" "$TEST_DIR/circular-test/claude/commands"
# Detect if target is already symlink pointing back
if [[ -L "$TEST_DIR/circular-test/claude/commands" ]]; then
target=$(readlink "$TEST_DIR/circular-test/claude/commands")
if [[ "$target" == *"/shared"* ]]; then
echo "[OK] Circular symlink detected correctly"
return 0
fi
fi
echo "[X] Circular symlink detection failed"
return 1
}
# Test 3: Performance measurement
test_performance() {
echo "[i] Test 3: Performance measurement"
mkdir -p "$TEST_DIR/perf-test/source"
mkdir -p "$TEST_DIR/perf-test/target"
start=$(date +%s%N 2>/dev/null || echo "0")
for i in {1..100}; do
ln -sf "$TEST_DIR/perf-test/source" "$TEST_DIR/perf-test/target/link-$i"
done
end=$(date +%s%N 2>/dev/null || echo "0")
if [[ "$start" != "0" ]] && [[ "$end" != "0" ]]; then
duration=$(( (end - start) / 1000000 )) # Convert to ms
avg=$(( duration / 100 ))
echo "[OK] Avg symlink creation: ${avg}ms (target: <1ms)"
else
echo "[i] Performance timing not available on this platform"
fi
}
# Test 4: Broken symlink detection
test_broken_symlink() {
echo "[i] Test 4: Broken symlink detection"
mkdir -p "$TEST_DIR/broken-test/instance"
ln -s "$TEST_DIR/broken-test/nonexistent" "$TEST_DIR/broken-test/instance/commands"
if [[ -L "$TEST_DIR/broken-test/instance/commands" ]] && \
[[ ! -e "$TEST_DIR/broken-test/instance/commands" ]]; then
echo "[OK] Broken symlink detected correctly"
else
echo "[X] Broken symlink detection failed"
return 1
fi
}
# Test 5: Live updates through chain
test_live_updates() {
echo "[i] Test 5: Live updates through chain"
# Clean test directories
rm -rf "$TEST_DIR/live-test"
mkdir -p "$TEST_DIR/live-test/claude/commands"
mkdir -p "$TEST_DIR/live-test/shared"
mkdir -p "$TEST_DIR/live-test/instance"
ln -s "$TEST_DIR/live-test/claude/commands" "$TEST_DIR/live-test/shared/commands"
ln -s "$TEST_DIR/live-test/shared/commands" "$TEST_DIR/live-test/instance/commands"
# Write to source
echo "# Original" > "$TEST_DIR/live-test/claude/commands/live-test.md"
# Verify accessible from instance
if [[ -f "$TEST_DIR/live-test/instance/commands/live-test.md" ]]; then
content=$(cat "$TEST_DIR/live-test/instance/commands/live-test.md")
if [[ "$content" == "# Original" ]]; then
echo "[OK] Live updates work through chain"
else
echo "[X] Content mismatch"
return 1
fi
else
echo "[X] File not accessible through chain"
return 1
fi
# Update source
echo "# Updated" > "$TEST_DIR/live-test/claude/commands/live-test.md"
# Verify update visible from instance
content=$(cat "$TEST_DIR/live-test/instance/commands/live-test.md")
if [[ "$content" == "# Updated" ]]; then
echo "[OK] Live updates reflected instantly"
else
echo "[X] Update not reflected"
return 1
fi
}
# Run all tests
echo "Running validation tests..."
echo ""
test_symlink_chain
test_circular_detection
test_performance
test_broken_symlink
test_live_updates
echo ""
echo "╔════════════════════════════════════════╗"
echo "║ All Tests Passed ✓ ║"
echo "╚════════════════════════════════════════╝"
+632
View File
@@ -0,0 +1,632 @@
#!/usr/bin/env node
'use strict';
const GlmtTransformer = require('../bin/glmt-transformer');
const DeltaAccumulator = require('../bin/delta-accumulator');
/**
* Token Counting Validation Tests
*
* Verifies:
* 1. message_delta includes both input_tokens and output_tokens
* 2. Token counts with simple prompts (no tools)
* 3. Token counts with tool calls
* 4. Token counts with thinking blocks + tools
* 5. Deferred finalization waits for usage data
* 6. Finalization happens when BOTH finish_reason AND usage are received
* 7. Graceful degradation if usage never arrives
* 8. No regressions in existing features
*/
class TestRunner {
constructor() {
this.tests = [];
this.passed = 0;
this.failed = 0;
}
test(name, fn) {
this.tests.push({ name, fn });
}
async run() {
console.log('\n=== Token Counting Validation 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}`);
if (error.stack) {
console.error(` Stack: ${error.stack.split('\n').slice(1, 3).join('\n')}`);
}
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;
}
}
// 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 assertTrue(value, message) {
if (!value) {
throw new Error(message || 'Expected true');
}
}
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}`
);
}
}
const runner = new TestRunner();
// ========================================
// Test 1: message_delta includes input_tokens and output_tokens
// ========================================
runner.test('message_delta includes both input_tokens and output_tokens', () => {
const transformer = new GlmtTransformer();
const accumulator = new DeltaAccumulator();
// Simulate usage data
accumulator.updateUsage({
prompt_tokens: 150,
completion_tokens: 75
});
accumulator.finishReason = 'stop';
const events = transformer.finalizeDelta(accumulator);
// Find message_delta event
const messageDelta = events.find(e => e.event === 'message_delta');
assertExists(messageDelta, 'message_delta event should exist');
assertExists(messageDelta.data.usage, 'usage should exist in message_delta');
assertEqual(messageDelta.data.usage.input_tokens, 150, 'input_tokens should be 150');
assertEqual(messageDelta.data.usage.output_tokens, 75, 'output_tokens should be 75');
});
// ========================================
// Test 2: Token counting with simple prompts (no tools)
// ========================================
runner.test('token counting with simple prompt (no tools)', () => {
const transformer = new GlmtTransformer();
const openaiResponse = {
id: 'chatcmpl-123',
model: 'GLM-4.6',
choices: [{
message: {
role: 'assistant',
content: 'Simple response'
},
finish_reason: 'stop'
}],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15
}
};
const result = transformer.transformResponse(openaiResponse, {});
assertExists(result.usage, 'usage should exist');
assertEqual(result.usage.input_tokens, 10, 'input_tokens should be 10');
assertEqual(result.usage.output_tokens, 5, 'output_tokens should be 5');
});
// ========================================
// Test 3: Token counting with tool calls
// ========================================
runner.test('token counting with tool calls', () => {
const transformer = new GlmtTransformer();
const openaiResponse = {
id: 'chatcmpl-456',
model: 'GLM-4.6',
choices: [{
message: {
role: 'assistant',
content: null,
tool_calls: [{
id: 'call_1',
type: 'function',
function: {
name: 'get_weather',
arguments: '{"location":"London"}'
}
}]
},
finish_reason: 'tool_calls'
}],
usage: {
prompt_tokens: 50,
completion_tokens: 25,
total_tokens: 75
}
};
const result = transformer.transformResponse(openaiResponse, {});
assertExists(result.usage, 'usage should exist');
assertEqual(result.usage.input_tokens, 50, 'input_tokens should be 50');
assertEqual(result.usage.output_tokens, 25, 'output_tokens should be 25');
assertEqual(result.stop_reason, 'tool_use', 'stop_reason should be tool_use');
assertTrue(result.content.some(b => b.type === 'tool_use'), 'should have tool_use block');
});
// ========================================
// Test 4: Token counting with thinking blocks + tools
// ========================================
runner.test('token counting with thinking blocks and tool calls', () => {
const transformer = new GlmtTransformer();
const openaiResponse = {
id: 'chatcmpl-789',
model: 'GLM-4.6',
choices: [{
message: {
role: 'assistant',
reasoning_content: 'Let me analyze this request...',
content: 'I need to call a tool',
tool_calls: [{
id: 'call_2',
type: 'function',
function: {
name: 'calculate',
arguments: '{"expression":"2+2"}'
}
}]
},
finish_reason: 'tool_calls'
}],
usage: {
prompt_tokens: 100,
completion_tokens: 80,
total_tokens: 180
}
};
const result = transformer.transformResponse(openaiResponse, {});
assertExists(result.usage, 'usage should exist');
assertEqual(result.usage.input_tokens, 100, 'input_tokens should be 100');
assertEqual(result.usage.output_tokens, 80, 'output_tokens should be 80');
assertTrue(result.content.some(b => b.type === 'thinking'), 'should have thinking block');
assertTrue(result.content.some(b => b.type === 'tool_use'), 'should have tool_use block');
});
// ========================================
// Test 5: Deferred finalization waits for usage data
// ========================================
runner.test('deferred finalization waits for usage data', () => {
const transformer = new GlmtTransformer();
const accumulator = new DeltaAccumulator();
// Simulate finish_reason arriving first
accumulator.finishReason = 'stop';
accumulator.messageStarted = true;
// Usage hasn't arrived yet - should NOT finalize
assertEqual(accumulator.usageReceived, false, 'usageReceived should be false initially');
// Simulate transformDelta with finish_reason but no usage
const openaiEvent1 = {
event: 'data',
data: {
choices: [{
delta: {},
finish_reason: 'stop'
}]
}
};
const events1 = transformer.transformDelta(openaiEvent1, accumulator);
// Should NOT have message_stop event yet
const hasMessageStop1 = events1.some(e => e.event === 'message_stop');
assertEqual(hasMessageStop1, false, 'should NOT finalize without usage');
assertEqual(accumulator.finalized, false, 'accumulator should NOT be finalized');
// Now usage arrives
const openaiEvent2 = {
event: 'data',
data: {
usage: {
prompt_tokens: 200,
completion_tokens: 100
}
}
};
const events2 = transformer.transformDelta(openaiEvent2, accumulator);
// Should NOW finalize since we have both finish_reason AND usage
const hasMessageStop2 = events2.some(e => e.event === 'message_stop');
assertEqual(hasMessageStop2, true, 'should finalize when usage arrives');
assertEqual(accumulator.finalized, true, 'accumulator should be finalized');
assertEqual(accumulator.usageReceived, true, 'usageReceived should be true');
});
// ========================================
// Test 6: Finalization happens when BOTH finish_reason AND usage received
// ========================================
runner.test('finalization waits for BOTH finish_reason AND usage', () => {
const transformer = new GlmtTransformer();
const accumulator = new DeltaAccumulator();
accumulator.messageStarted = true;
// Test case A: Usage arrives first
const openaiEvent1 = {
event: 'data',
data: {
usage: {
prompt_tokens: 50,
completion_tokens: 30
}
}
};
const events1 = transformer.transformDelta(openaiEvent1, accumulator);
assertEqual(accumulator.usageReceived, true, 'usage should be received');
assertEqual(accumulator.finalized, false, 'should NOT finalize with only usage');
// Test case B: finish_reason arrives second
const openaiEvent2 = {
event: 'data',
data: {
choices: [{
delta: {},
finish_reason: 'stop'
}]
}
};
const events2 = transformer.transformDelta(openaiEvent2, accumulator);
assertEqual(accumulator.finishReason, 'stop', 'finish_reason should be set');
assertEqual(accumulator.finalized, true, 'should finalize when both present');
const messageDelta = events2.find(e => e.event === 'message_delta');
assertExists(messageDelta, 'message_delta should exist');
assertEqual(messageDelta.data.usage.input_tokens, 50, 'input_tokens in message_delta');
assertEqual(messageDelta.data.usage.output_tokens, 30, 'output_tokens in message_delta');
});
// ========================================
// Test 7: Graceful degradation if usage never arrives
// ========================================
runner.test('graceful degradation when usage never arrives', () => {
const transformer = new GlmtTransformer();
const accumulator = new DeltaAccumulator();
accumulator.messageStarted = true;
// finish_reason arrives
accumulator.finishReason = 'stop';
// Simulate [DONE] event without usage
const doneEvent = {
event: 'done'
};
const events = transformer.transformDelta(doneEvent, accumulator);
// Should finalize with zero tokens (graceful degradation)
assertEqual(accumulator.finalized, true, 'should finalize on [DONE]');
const messageDelta = events.find(e => e.event === 'message_delta');
assertExists(messageDelta, 'message_delta should exist');
assertEqual(messageDelta.data.usage.input_tokens, 0, 'input_tokens should be 0');
assertEqual(messageDelta.data.usage.output_tokens, 0, 'output_tokens should be 0');
});
// ========================================
// Test 8: No regression - thinking blocks still work
// ========================================
runner.test('no regression: thinking blocks still work', () => {
const transformer = new GlmtTransformer();
const accumulator = new DeltaAccumulator();
// Start message
const event1 = {
event: 'data',
data: {
model: 'GLM-4.6',
choices: [{
delta: { role: 'assistant' }
}]
}
};
transformer.transformDelta(event1, accumulator);
// Thinking delta
const event2 = {
event: 'data',
data: {
choices: [{
delta: {
reasoning_content: 'Analyzing the problem...'
}
}]
}
};
const events2 = transformer.transformDelta(event2, accumulator);
// Check thinking block was created
const hasThinkingStart = events2.some(e =>
e.event === 'content_block_start' &&
e.data.content_block.type === 'thinking'
);
assertEqual(hasThinkingStart, true, 'thinking block should start');
const hasThinkingDelta = events2.some(e =>
e.event === 'content_block_delta' &&
e.data.delta.type === 'thinking_delta'
);
assertEqual(hasThinkingDelta, true, 'thinking delta should be emitted');
});
// ========================================
// Test 9: No regression - tool calls execute correctly
// ========================================
runner.test('no regression: tool calls execute correctly', () => {
const transformer = new GlmtTransformer();
const accumulator = new DeltaAccumulator();
accumulator.messageStarted = true;
// Tool call delta
const event = {
event: 'data',
data: {
choices: [{
delta: {
tool_calls: [{
index: 0,
id: 'call_abc',
type: 'function',
function: {
name: 'search',
arguments: '{"q":"test"}'
}
}]
}
}]
}
};
const events = transformer.transformDelta(event, accumulator);
const toolUseStart = events.find(e =>
e.event === 'content_block_start' &&
e.data.content_block.type === 'tool_use'
);
assertExists(toolUseStart, 'tool_use block should start');
assertEqual(toolUseStart.data.content_block.name, 'search', 'tool name should be search');
const inputJsonDelta = events.find(e =>
e.event === 'content_block_delta' &&
e.data.delta.type === 'input_json_delta'
);
assertExists(inputJsonDelta, 'input_json_delta should be emitted');
});
// ========================================
// Test 10: No regression - streaming still works
// ========================================
runner.test('no regression: streaming still works', () => {
const transformer = new GlmtTransformer();
const accumulator = new DeltaAccumulator();
// Message start
const event1 = {
event: 'data',
data: {
model: 'GLM-4.6',
choices: [{ delta: { role: 'assistant' } }]
}
};
const events1 = transformer.transformDelta(event1, accumulator);
assertTrue(events1.some(e => e.event === 'message_start'), 'message_start event');
// Text delta
const event2 = {
event: 'data',
data: {
choices: [{ delta: { content: 'Hello' } }]
}
};
const events2 = transformer.transformDelta(event2, accumulator);
assertTrue(events2.some(e => e.event === 'content_block_start'), 'content_block_start');
assertTrue(events2.some(e => e.event === 'content_block_delta'), 'content_block_delta');
// More text
const event3 = {
event: 'data',
data: {
choices: [{ delta: { content: ' world' } }]
}
};
const events3 = transformer.transformDelta(event3, accumulator);
const textDelta = events3.find(e => e.data?.delta?.type === 'text_delta');
assertExists(textDelta, 'text_delta should exist');
assertEqual(textDelta.data.delta.text, ' world', 'delta text should be " world"');
});
// ========================================
// Test 11: No regression - buffered mode still works
// ========================================
runner.test('no regression: buffered mode (non-streaming) works', () => {
const transformer = new GlmtTransformer();
const openaiResponse = {
id: 'chatcmpl-buffered',
model: 'GLM-4.6',
choices: [{
message: {
role: 'assistant',
reasoning_content: 'Thinking step by step...',
content: 'Final answer'
},
finish_reason: 'stop'
}],
usage: {
prompt_tokens: 20,
completion_tokens: 15,
total_tokens: 35
}
};
const result = transformer.transformResponse(openaiResponse, {});
assertEqual(result.type, 'message', 'type should be message');
assertEqual(result.role, 'assistant', 'role should be assistant');
assertTrue(result.content.some(b => b.type === 'thinking'), 'has thinking');
assertTrue(result.content.some(b => b.type === 'text'), 'has text');
assertEqual(result.usage.input_tokens, 20, 'input_tokens');
assertEqual(result.usage.output_tokens, 15, 'output_tokens');
});
// ========================================
// Test 12: usageReceived flag is set correctly
// ========================================
runner.test('usageReceived flag is set when usage data arrives', () => {
const accumulator = new DeltaAccumulator();
assertEqual(accumulator.usageReceived, false, 'initial value should be false');
accumulator.updateUsage({
prompt_tokens: 100,
completion_tokens: 50
});
assertEqual(accumulator.usageReceived, true, 'should be true after updateUsage');
assertEqual(accumulator.inputTokens, 100, 'inputTokens should be 100');
assertEqual(accumulator.outputTokens, 50, 'outputTokens should be 50');
});
// ========================================
// Test 13: Double finalization protection
// ========================================
runner.test('double finalization protection works', () => {
const transformer = new GlmtTransformer();
const accumulator = new DeltaAccumulator();
accumulator.messageStarted = true;
accumulator.finishReason = 'stop';
accumulator.updateUsage({ prompt_tokens: 10, completion_tokens: 5 });
// First finalization
const events1 = transformer.finalizeDelta(accumulator);
assertTrue(events1.length > 0, 'should return events on first finalization');
assertEqual(accumulator.finalized, true, 'should be finalized');
// Second finalization attempt
const events2 = transformer.finalizeDelta(accumulator);
assertEqual(events2.length, 0, 'should return empty array on second call');
});
// ========================================
// Test 14: Token counts in streaming with thinking + text + tools
// ========================================
runner.test('streaming: token counts with thinking + text + tools', () => {
const transformer = new GlmtTransformer();
const accumulator = new DeltaAccumulator();
// Message start
transformer.transformDelta({
event: 'data',
data: {
model: 'GLM-4.6',
choices: [{ delta: { role: 'assistant' } }]
}
}, accumulator);
// Thinking
transformer.transformDelta({
event: 'data',
data: {
choices: [{ delta: { reasoning_content: 'Thinking...' } }]
}
}, accumulator);
// Text
transformer.transformDelta({
event: 'data',
data: {
choices: [{ delta: { content: 'Answer' } }]
}
}, accumulator);
// Tool call
transformer.transformDelta({
event: 'data',
data: {
choices: [{
delta: {
tool_calls: [{
index: 0,
id: 'call_1',
type: 'function',
function: { name: 'tool', arguments: '{}' }
}]
}
}]
}
}, accumulator);
// Usage arrives
transformer.transformDelta({
event: 'data',
data: {
usage: { prompt_tokens: 300, completion_tokens: 200 }
}
}, accumulator);
// finish_reason arrives
const finalEvents = transformer.transformDelta({
event: 'data',
data: {
choices: [{ delta: {}, finish_reason: 'tool_calls' }]
}
}, accumulator);
// Verify message_delta has correct tokens
const messageDelta = finalEvents.find(e => e.event === 'message_delta');
assertExists(messageDelta, 'message_delta should exist');
assertEqual(messageDelta.data.usage.input_tokens, 300, 'input_tokens should be 300');
assertEqual(messageDelta.data.usage.output_tokens, 200, 'output_tokens should be 200');
assertEqual(messageDelta.data.delta.stop_reason, 'tool_use', 'stop_reason should be tool_use');
});
// Run all tests
runner.run().then(success => {
process.exit(success ? 0 : 1);
}).catch(error => {
console.error('Test runner error:', error);
process.exit(1);
});
+230
View File
@@ -0,0 +1,230 @@
#!/usr/bin/env node
'use strict';
const https = require('https');
const API_KEY = process.env.Z_AI_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN;
const MODEL = 'GLM-4.6';
if (!API_KEY || API_KEY === 'your-api-key-here') {
console.error('[ERROR] Z.AI API key not found');
console.error('[INFO] Set Z_AI_API_KEY or ANTHROPIC_AUTH_TOKEN environment variable');
console.error('[INFO] Example: export Z_AI_API_KEY=your-key-here');
process.exit(1);
}
// Test request with reasoning
const requestBody = JSON.stringify({
model: MODEL,
messages: [
{
role: 'user',
content: 'Solve this math problem step by step: What is 27 * 453? Show your reasoning process.'
}
],
stream: true,
reasoning: true,
reasoning_effort: 'medium',
max_tokens: 4096,
do_sample: true
});
const options = {
hostname: 'api.z.ai',
port: 443,
path: '/api/coding/paas/v4/chat/completions',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`,
'Content-Length': Buffer.byteLength(requestBody),
'User-Agent': 'CCS-GLMT-StreamingTest/1.0'
}
};
console.log('═══════════════════════════════════════════════════════════════');
console.log('Z.AI STREAMING BEHAVIOR TEST');
console.log('═══════════════════════════════════════════════════════════════');
console.log('[TEST] Initiating streaming request to Z.AI');
console.log('[TEST] Model:', MODEL);
console.log('[TEST] Stream: true');
console.log('[TEST] Reasoning: true');
console.log('[TEST] Reasoning Effort: medium');
console.log('[TEST] API Key:', API_KEY.substring(0, 10) + '...');
console.log('');
const startTime = Date.now();
let firstByteTime = null;
const req = https.request(options, (res) => {
console.log('[HTTP] Status:', res.statusCode);
console.log('[HTTP] Content-Type:', res.headers['content-type']);
console.log('');
if (res.statusCode !== 200) {
let errorBody = '';
res.on('data', chunk => errorBody += chunk);
res.on('end', () => {
console.error('[ERROR] Request failed:', res.statusCode, res.statusMessage);
console.error('[ERROR] Response:', errorBody);
process.exit(1);
});
return;
}
console.log('═══════════════════════════════════════════════════════════════');
console.log('SSE EVENTS (analyzing streaming behavior)');
console.log('═══════════════════════════════════════════════════════════════');
console.log('');
let buffer = '';
let eventCount = 0;
let reasoningEventCount = 0;
let reasoningInDelta = false;
let reasoningInMessage = false;
let totalReasoningChunks = [];
let textEventCount = 0;
res.on('data', (chunk) => {
if (!firstByteTime) {
firstByteTime = Date.now();
console.log('[TIMING] Time to first byte:', firstByteTime - startTime, 'ms');
console.log('');
}
buffer += chunk.toString();
const lines = buffer.split('\n');
// Keep incomplete line in buffer
buffer = lines.pop() || '';
lines.forEach(line => {
if (line.startsWith('data: ')) {
eventCount++;
const data = line.substring(6);
if (data === '[DONE]') {
console.log(`[EVENT ${eventCount}] [DONE]`);
console.log('');
return;
}
try {
const parsed = JSON.parse(data);
const choice = parsed.choices?.[0];
const delta = choice?.delta;
const message = choice?.message;
// Check for reasoning_content in delta
if (delta?.reasoning_content) {
reasoningInDelta = true;
reasoningEventCount++;
totalReasoningChunks.push(delta.reasoning_content);
console.log(`[EVENT ${eventCount}] *** REASONING IN DELTA ***`);
console.log(' Delta keys:', Object.keys(delta).join(', '));
console.log(' Reasoning chunk length:', delta.reasoning_content.length);
console.log(' Reasoning chunk preview:', JSON.stringify(delta.reasoning_content.substring(0, 80)));
console.log('');
}
// Check for reasoning_content in message (complete)
if (message?.reasoning_content) {
reasoningInMessage = true;
console.log(`[EVENT ${eventCount}] *** REASONING IN MESSAGE (COMPLETE) ***`);
console.log(' Message keys:', Object.keys(message).join(', '));
console.log(' Reasoning total length:', message.reasoning_content.length);
console.log(' Reasoning preview:', message.reasoning_content.substring(0, 100).replace(/\n/g, ' '));
console.log('');
}
// Check for text content in delta
if (delta?.content) {
textEventCount++;
if (textEventCount <= 3 || eventCount % 10 === 0) {
console.log(`[EVENT ${eventCount}] Text delta`);
console.log(' Content:', JSON.stringify(delta.content));
console.log('');
}
}
// Show finish_reason
if (choice?.finish_reason) {
console.log(`[EVENT ${eventCount}] Finish reason: ${choice.finish_reason}`);
console.log('');
}
// Show usage stats
if (parsed.usage) {
console.log(`[EVENT ${eventCount}] Usage:`, JSON.stringify(parsed.usage));
console.log('');
}
} catch (e) {
console.log(`[EVENT ${eventCount}] [PARSE ERROR]`, e.message);
console.log(' Raw:', data.substring(0, 100));
console.log('');
}
}
});
});
res.on('end', () => {
const totalTime = Date.now() - startTime;
console.log('═══════════════════════════════════════════════════════════════');
console.log('TEST RESULTS');
console.log('═══════════════════════════════════════════════════════════════');
console.log('');
console.log('[TIMING]');
console.log(' Time to first byte:', firstByteTime - startTime, 'ms');
console.log(' Total duration:', totalTime, 'ms');
console.log('');
console.log('[EVENTS]');
console.log(' Total events:', eventCount);
console.log(' Text content events:', textEventCount);
console.log(' Reasoning events:', reasoningEventCount);
console.log('');
console.log('[REASONING BEHAVIOR]');
console.log(' Reasoning in delta (incremental):', reasoningInDelta);
console.log(' Reasoning in message (complete):', reasoningInMessage);
if (totalReasoningChunks.length > 0) {
const totalReasoningLength = totalReasoningChunks.join('').length;
console.log(' Total reasoning chunks:', totalReasoningChunks.length);
console.log(' Total reasoning length:', totalReasoningLength);
}
console.log('');
console.log('═══════════════════════════════════════════════════════════════');
console.log('SCENARIO DETERMINATION');
console.log('═══════════════════════════════════════════════════════════════');
if (reasoningInDelta) {
console.log('');
console.log('[✓] SCENARIO A: Incremental Streaming');
console.log(' reasoning_content appears in delta objects');
console.log(' Multiple events contain reasoning chunks');
console.log(' RECOMMENDATION: Proceed with full streaming implementation');
} else if (reasoningInMessage) {
console.log('');
console.log('[!] SCENARIO B: Complete at End');
console.log(' reasoning_content only in final message object');
console.log(' No intermediate reasoning chunks');
console.log(' RECOMMENDATION: Hybrid streaming (text streams, reasoning buffered)');
} else {
console.log('');
console.log('[X] SCENARIO C: No Streaming Support');
console.log(' No reasoning_content in streaming mode');
console.log(' RECOMMENDATION: Keep buffered mode only');
}
console.log('');
console.log('═══════════════════════════════════════════════════════════════');
});
});
req.on('error', (error) => {
console.error('[ERROR]', error.message);
process.exit(1);
});
req.write(requestBody);
req.end();