@kaitranntt/ccs 3.2.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -124
- package/VERSION +1 -1
- package/bin/ccs.js +120 -4
- package/bin/delta-accumulator.js +155 -0
- package/bin/glmt-proxy.js +467 -0
- package/bin/glmt-transformer.js +684 -0
- package/bin/sse-parser.js +96 -0
- package/config/base-glmt.settings.json +17 -0
- package/lib/ccs +2 -1
- package/lib/ccs.ps1 +2 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +105 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SSEParser - Parse Server-Sent Events (SSE) stream
|
|
6
|
+
*
|
|
7
|
+
* Handles:
|
|
8
|
+
* - Incomplete events across chunks
|
|
9
|
+
* - Multiple events in single chunk
|
|
10
|
+
* - Malformed data (skip gracefully)
|
|
11
|
+
* - [DONE] marker
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* const parser = new SSEParser();
|
|
15
|
+
* stream.on('data', chunk => {
|
|
16
|
+
* const events = parser.parse(chunk);
|
|
17
|
+
* events.forEach(event => { ... });
|
|
18
|
+
* });
|
|
19
|
+
*/
|
|
20
|
+
class SSEParser {
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.buffer = '';
|
|
23
|
+
this.eventCount = 0;
|
|
24
|
+
this.maxBufferSize = options.maxBufferSize || 1024 * 1024; // 1MB default
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse chunk and extract SSE events
|
|
29
|
+
* @param {Buffer|string} chunk - Data chunk from stream
|
|
30
|
+
* @returns {Array<Object>} Array of parsed events
|
|
31
|
+
*/
|
|
32
|
+
parse(chunk) {
|
|
33
|
+
this.buffer += chunk.toString();
|
|
34
|
+
|
|
35
|
+
// C-01 Fix: Prevent unbounded buffer growth (DoS protection)
|
|
36
|
+
if (this.buffer.length > this.maxBufferSize) {
|
|
37
|
+
throw new Error(`SSE buffer exceeded ${this.maxBufferSize} bytes (DoS protection)`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const lines = this.buffer.split('\n');
|
|
41
|
+
|
|
42
|
+
// Keep incomplete line in buffer
|
|
43
|
+
this.buffer = lines.pop() || '';
|
|
44
|
+
|
|
45
|
+
const events = [];
|
|
46
|
+
let currentEvent = { event: 'message', data: '' };
|
|
47
|
+
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
if (line.startsWith('event: ')) {
|
|
50
|
+
currentEvent.event = line.substring(7).trim();
|
|
51
|
+
} else if (line.startsWith('data: ')) {
|
|
52
|
+
const data = line.substring(6);
|
|
53
|
+
|
|
54
|
+
if (data === '[DONE]') {
|
|
55
|
+
this.eventCount++;
|
|
56
|
+
events.push({
|
|
57
|
+
event: 'done',
|
|
58
|
+
data: null,
|
|
59
|
+
index: this.eventCount
|
|
60
|
+
});
|
|
61
|
+
currentEvent = { event: 'message', data: '' };
|
|
62
|
+
} else {
|
|
63
|
+
try {
|
|
64
|
+
currentEvent.data = JSON.parse(data);
|
|
65
|
+
this.eventCount++;
|
|
66
|
+
currentEvent.index = this.eventCount;
|
|
67
|
+
events.push(currentEvent);
|
|
68
|
+
currentEvent = { event: 'message', data: '' };
|
|
69
|
+
} catch (e) {
|
|
70
|
+
// H-01 Fix: Log parse errors for debugging
|
|
71
|
+
if (typeof console !== 'undefined' && console.error) {
|
|
72
|
+
console.error('[SSEParser] Malformed JSON event:', e.message, 'Data:', data.substring(0, 100));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} else if (line.startsWith('id: ')) {
|
|
77
|
+
currentEvent.id = line.substring(4).trim();
|
|
78
|
+
} else if (line.startsWith('retry: ')) {
|
|
79
|
+
currentEvent.retry = parseInt(line.substring(7), 10);
|
|
80
|
+
}
|
|
81
|
+
// Empty lines separate events (already handled by JSON parsing)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return events;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reset parser state (for reuse)
|
|
89
|
+
*/
|
|
90
|
+
reset() {
|
|
91
|
+
this.buffer = '';
|
|
92
|
+
this.eventCount = 0;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = SSEParser;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"ANTHROPIC_BASE_URL": "https://api.z.ai/api/coding/paas/v4/chat/completions",
|
|
4
|
+
"ANTHROPIC_AUTH_TOKEN": "YOUR_GLM_API_KEY_HERE",
|
|
5
|
+
"ANTHROPIC_MODEL": "glm-4.6",
|
|
6
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL": "glm-4.6",
|
|
7
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-4.6",
|
|
8
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-4.6",
|
|
9
|
+
"ANTHROPIC_TEMPERATURE": "0.2",
|
|
10
|
+
"ANTHROPIC_MAX_TOKENS": "65536",
|
|
11
|
+
"MAX_THINKING_TOKENS": "32768",
|
|
12
|
+
"ENABLE_STREAMING": "true",
|
|
13
|
+
"ANTHROPIC_SAFE_MODE": "false",
|
|
14
|
+
"API_TIMEOUT_MS": "3000000"
|
|
15
|
+
},
|
|
16
|
+
"alwaysThinkingEnabled": true
|
|
17
|
+
}
|
package/lib/ccs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
# Version (updated by scripts/bump-version.sh)
|
|
5
|
-
CCS_VERSION="3.
|
|
5
|
+
CCS_VERSION="3.4.0"
|
|
6
6
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
7
|
readonly CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
|
|
8
8
|
readonly PROFILES_JSON="$HOME/.ccs/profiles.json"
|
|
@@ -51,6 +51,7 @@ show_help() {
|
|
|
51
51
|
echo -e "${CYAN}Model Switching:${RESET}"
|
|
52
52
|
echo -e " ${YELLOW}ccs${RESET} Use default Claude account"
|
|
53
53
|
echo -e " ${YELLOW}ccs glm${RESET} Switch to GLM 4.6 model"
|
|
54
|
+
echo -e " ${YELLOW}ccs glmt${RESET} Switch to GLM with thinking mode"
|
|
54
55
|
echo -e " ${YELLOW}ccs kimi${RESET} Switch to Kimi for Coding"
|
|
55
56
|
echo -e " ${YELLOW}ccs glm${RESET} \"debug this code\" Use GLM and run command"
|
|
56
57
|
echo ""
|
package/lib/ccs.ps1
CHANGED
|
@@ -12,7 +12,7 @@ param(
|
|
|
12
12
|
$ErrorActionPreference = "Stop"
|
|
13
13
|
|
|
14
14
|
# Version (updated by scripts/bump-version.sh)
|
|
15
|
-
$CcsVersion = "3.
|
|
15
|
+
$CcsVersion = "3.4.0"
|
|
16
16
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
17
17
|
$ConfigFile = if ($env:CCS_CONFIG) { $env:CCS_CONFIG } else { "$env:USERPROFILE\.ccs\config.json" }
|
|
18
18
|
$ProfilesJson = "$env:USERPROFILE\.ccs\profiles.json"
|
|
@@ -107,6 +107,7 @@ function Show-Help {
|
|
|
107
107
|
Write-ColorLine "Model Switching:" "Cyan"
|
|
108
108
|
Write-ColorLine " ccs Use default Claude account" "Yellow"
|
|
109
109
|
Write-ColorLine " ccs glm Switch to GLM 4.6 model" "Yellow"
|
|
110
|
+
Write-ColorLine " ccs glmt Switch to GLM with thinking mode" "Yellow"
|
|
110
111
|
Write-ColorLine " ccs kimi Switch to Kimi for Coding" "Yellow"
|
|
111
112
|
Write-ColorLine " ccs glm 'debug this code' Use GLM and run command" "Yellow"
|
|
112
113
|
Write-Host ""
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -33,6 +33,7 @@ function validateConfiguration() {
|
|
|
33
33
|
const requiredFiles = [
|
|
34
34
|
{ path: path.join(ccsDir, 'config.json'), name: 'config.json' },
|
|
35
35
|
{ path: path.join(ccsDir, 'glm.settings.json'), name: 'glm.settings.json' },
|
|
36
|
+
{ path: path.join(ccsDir, 'glmt.settings.json'), name: 'glmt.settings.json' },
|
|
36
37
|
{ path: path.join(ccsDir, 'kimi.settings.json'), name: 'kimi.settings.json' }
|
|
37
38
|
];
|
|
38
39
|
|
|
@@ -108,6 +109,7 @@ function createConfigFiles() {
|
|
|
108
109
|
const config = {
|
|
109
110
|
profiles: {
|
|
110
111
|
glm: '~/.ccs/glm.settings.json',
|
|
112
|
+
glmt: '~/.ccs/glmt.settings.json',
|
|
111
113
|
kimi: '~/.ccs/kimi.settings.json',
|
|
112
114
|
default: '~/.claude/settings.json'
|
|
113
115
|
}
|
|
@@ -120,7 +122,21 @@ function createConfigFiles() {
|
|
|
120
122
|
|
|
121
123
|
console.log('[OK] Created config: ~/.ccs/config.json');
|
|
122
124
|
} else {
|
|
123
|
-
|
|
125
|
+
// Update existing config with glmt if missing (migration for v3.x users)
|
|
126
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
127
|
+
// Ensure profiles object exists
|
|
128
|
+
if (!config.profiles) {
|
|
129
|
+
config.profiles = {};
|
|
130
|
+
}
|
|
131
|
+
if (!config.profiles.glmt) {
|
|
132
|
+
config.profiles.glmt = '~/.ccs/glmt.settings.json';
|
|
133
|
+
const tmpPath = `${configPath}.tmp`;
|
|
134
|
+
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
135
|
+
fs.renameSync(tmpPath, configPath);
|
|
136
|
+
console.log('[OK] Updated config with GLMT profile');
|
|
137
|
+
} else {
|
|
138
|
+
console.log('[OK] Config exists: ~/.ccs/config.json (preserved)');
|
|
139
|
+
}
|
|
124
140
|
}
|
|
125
141
|
|
|
126
142
|
// Create glm.settings.json if missing
|
|
@@ -152,6 +168,94 @@ function createConfigFiles() {
|
|
|
152
168
|
console.log('[OK] GLM profile exists: ~/.ccs/glm.settings.json (preserved)');
|
|
153
169
|
}
|
|
154
170
|
|
|
171
|
+
// Create glmt.settings.json if missing
|
|
172
|
+
const glmtSettingsPath = path.join(ccsDir, 'glmt.settings.json');
|
|
173
|
+
if (!fs.existsSync(glmtSettingsPath)) {
|
|
174
|
+
const glmtSettings = {
|
|
175
|
+
env: {
|
|
176
|
+
ANTHROPIC_BASE_URL: 'https://api.z.ai/api/coding/paas/v4/chat/completions',
|
|
177
|
+
ANTHROPIC_AUTH_TOKEN: 'YOUR_GLM_API_KEY_HERE',
|
|
178
|
+
ANTHROPIC_MODEL: 'glm-4.6',
|
|
179
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: 'glm-4.6',
|
|
180
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: 'glm-4.6',
|
|
181
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'glm-4.6',
|
|
182
|
+
ANTHROPIC_TEMPERATURE: '0.2',
|
|
183
|
+
ANTHROPIC_MAX_TOKENS: '65536',
|
|
184
|
+
MAX_THINKING_TOKENS: '32768',
|
|
185
|
+
ENABLE_STREAMING: 'true',
|
|
186
|
+
ANTHROPIC_SAFE_MODE: 'false',
|
|
187
|
+
API_TIMEOUT_MS: '3000000'
|
|
188
|
+
},
|
|
189
|
+
alwaysThinkingEnabled: true
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Atomic write
|
|
193
|
+
const tmpPath = `${glmtSettingsPath}.tmp`;
|
|
194
|
+
fs.writeFileSync(tmpPath, JSON.stringify(glmtSettings, null, 2) + '\n', 'utf8');
|
|
195
|
+
fs.renameSync(tmpPath, glmtSettingsPath);
|
|
196
|
+
|
|
197
|
+
console.log('[OK] Created GLMT profile: ~/.ccs/glmt.settings.json');
|
|
198
|
+
console.log('');
|
|
199
|
+
console.log(' [!] Configure GLMT API key:');
|
|
200
|
+
console.log(' 1. Get key from: https://api.z.ai');
|
|
201
|
+
console.log(' 2. Edit: ~/.ccs/glmt.settings.json');
|
|
202
|
+
console.log(' 3. Replace: YOUR_GLM_API_KEY_HERE');
|
|
203
|
+
console.log(' Note: GLMT enables GLM thinking mode (reasoning)');
|
|
204
|
+
console.log(' Defaults: Temperature 0.2, thinking enabled, 50min timeout');
|
|
205
|
+
} else {
|
|
206
|
+
console.log('[OK] GLMT profile exists: ~/.ccs/glmt.settings.json (preserved)');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Migrate existing GLMT configs to include new defaults (v3.3.0)
|
|
210
|
+
if (fs.existsSync(glmtSettingsPath)) {
|
|
211
|
+
try {
|
|
212
|
+
const existing = JSON.parse(fs.readFileSync(glmtSettingsPath, 'utf8'));
|
|
213
|
+
let updated = false;
|
|
214
|
+
|
|
215
|
+
// Ensure env object exists
|
|
216
|
+
if (!existing.env) {
|
|
217
|
+
existing.env = {};
|
|
218
|
+
updated = true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Add missing env vars (preserve existing values)
|
|
222
|
+
const envDefaults = {
|
|
223
|
+
ANTHROPIC_TEMPERATURE: '0.2',
|
|
224
|
+
ANTHROPIC_MAX_TOKENS: '65536',
|
|
225
|
+
MAX_THINKING_TOKENS: '32768',
|
|
226
|
+
ENABLE_STREAMING: 'true',
|
|
227
|
+
ANTHROPIC_SAFE_MODE: 'false',
|
|
228
|
+
API_TIMEOUT_MS: '3000000'
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
for (const [key, value] of Object.entries(envDefaults)) {
|
|
232
|
+
if (existing.env[key] === undefined) {
|
|
233
|
+
existing.env[key] = value;
|
|
234
|
+
updated = true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Add alwaysThinkingEnabled if missing
|
|
239
|
+
if (existing.alwaysThinkingEnabled === undefined) {
|
|
240
|
+
existing.alwaysThinkingEnabled = true;
|
|
241
|
+
updated = true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Write back if updated
|
|
245
|
+
if (updated) {
|
|
246
|
+
const tmpPath = `${glmtSettingsPath}.tmp`;
|
|
247
|
+
fs.writeFileSync(tmpPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
|
|
248
|
+
fs.renameSync(tmpPath, glmtSettingsPath);
|
|
249
|
+
console.log('[OK] Migrated GLMT config with new defaults (v3.3.0)');
|
|
250
|
+
console.log(' Added: temperature, max_tokens, thinking settings, alwaysThinkingEnabled');
|
|
251
|
+
}
|
|
252
|
+
} catch (err) {
|
|
253
|
+
console.warn('[!] GLMT config migration failed:', err.message);
|
|
254
|
+
console.warn(' Existing config preserved, may be missing new defaults');
|
|
255
|
+
console.warn(' You can manually add fields or delete file to regenerate');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
155
259
|
// Create kimi.settings.json if missing
|
|
156
260
|
const kimiSettingsPath = path.join(ccsDir, 'kimi.settings.json');
|
|
157
261
|
if (!fs.existsSync(kimiSettingsPath)) {
|