@kaitranntt/ccs 4.4.0 → 5.0.1
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 +98 -7
- package/VERSION +1 -1
- package/config/base-agy.settings.json +10 -0
- package/config/base-codex.settings.json +10 -0
- package/config/base-gemini.settings.json +10 -0
- package/dist/auth/auth-commands.d.ts +52 -0
- package/dist/auth/auth-commands.d.ts.map +1 -0
- package/dist/auth/auth-commands.js +479 -0
- package/dist/auth/auth-commands.js.map +1 -0
- package/dist/auth/profile-detector.d.ts +68 -0
- package/dist/auth/profile-detector.d.ts.map +1 -0
- package/dist/auth/profile-detector.js +209 -0
- package/dist/auth/profile-detector.js.map +1 -0
- package/dist/auth/profile-registry.d.ts +60 -0
- package/dist/auth/profile-registry.d.ts.map +1 -0
- package/dist/auth/profile-registry.js +188 -0
- package/dist/auth/profile-registry.js.map +1 -0
- package/dist/ccs.d.ts +10 -0
- package/dist/ccs.d.ts.map +1 -0
- package/dist/ccs.js +320 -0
- package/dist/ccs.js.map +1 -0
- package/dist/cliproxy/auth-handler.d.ts +95 -0
- package/dist/cliproxy/auth-handler.d.ts.map +1 -0
- package/dist/cliproxy/auth-handler.js +443 -0
- package/dist/cliproxy/auth-handler.js.map +1 -0
- package/dist/cliproxy/base-config-loader.d.ts +42 -0
- package/dist/cliproxy/base-config-loader.d.ts.map +1 -0
- package/dist/cliproxy/base-config-loader.js +123 -0
- package/dist/cliproxy/base-config-loader.js.map +1 -0
- package/dist/cliproxy/binary-manager.d.ts +104 -0
- package/dist/cliproxy/binary-manager.d.ts.map +1 -0
- package/dist/cliproxy/binary-manager.js +567 -0
- package/dist/cliproxy/binary-manager.js.map +1 -0
- package/dist/cliproxy/cliproxy-executor.d.ts +33 -0
- package/dist/cliproxy/cliproxy-executor.d.ts.map +1 -0
- package/dist/cliproxy/cliproxy-executor.js +297 -0
- package/dist/cliproxy/cliproxy-executor.js.map +1 -0
- package/dist/cliproxy/config-generator.d.ts +89 -0
- package/dist/cliproxy/config-generator.d.ts.map +1 -0
- package/dist/cliproxy/config-generator.js +263 -0
- package/dist/cliproxy/config-generator.js.map +1 -0
- package/dist/cliproxy/index.d.ts +13 -0
- package/dist/cliproxy/index.d.ts.map +1 -0
- package/dist/cliproxy/index.js +62 -0
- package/dist/cliproxy/index.js.map +1 -0
- package/dist/cliproxy/platform-detector.d.ts +48 -0
- package/dist/cliproxy/platform-detector.d.ts.map +1 -0
- package/dist/cliproxy/platform-detector.js +118 -0
- package/dist/cliproxy/platform-detector.js.map +1 -0
- package/dist/cliproxy/types.d.ts +169 -0
- package/dist/cliproxy/types.d.ts.map +1 -0
- package/dist/cliproxy/types.js +7 -0
- package/dist/cliproxy/types.js.map +1 -0
- package/dist/commands/doctor-command.d.ts +10 -0
- package/dist/commands/doctor-command.d.ts.map +1 -0
- package/dist/commands/doctor-command.js +44 -0
- package/dist/commands/doctor-command.js.map +1 -0
- package/dist/commands/help-command.d.ts +5 -0
- package/dist/commands/help-command.d.ts.map +1 -0
- package/dist/commands/help-command.js +104 -0
- package/dist/commands/help-command.js.map +1 -0
- package/dist/commands/install-command.d.ts +14 -0
- package/dist/commands/install-command.d.ts.map +1 -0
- package/dist/commands/install-command.js +39 -0
- package/dist/commands/install-command.js.map +1 -0
- package/dist/commands/shell-completion-command.d.ts +10 -0
- package/dist/commands/shell-completion-command.d.ts.map +1 -0
- package/dist/commands/shell-completion-command.js +85 -0
- package/dist/commands/shell-completion-command.js.map +1 -0
- package/dist/commands/sync-command.d.ts +10 -0
- package/dist/commands/sync-command.d.ts.map +1 -0
- package/dist/commands/sync-command.js +59 -0
- package/dist/commands/sync-command.js.map +1 -0
- package/dist/commands/update-command.d.ts +12 -0
- package/dist/commands/update-command.d.ts.map +1 -0
- package/dist/commands/update-command.js +295 -0
- package/dist/commands/update-command.js.map +1 -0
- package/dist/commands/version-command.d.ts +10 -0
- package/dist/commands/version-command.d.ts.map +1 -0
- package/dist/commands/version-command.js +100 -0
- package/dist/commands/version-command.js.map +1 -0
- package/dist/delegation/delegation-handler.d.ts +60 -0
- package/dist/delegation/delegation-handler.d.ts.map +1 -0
- package/dist/delegation/delegation-handler.js +174 -0
- package/dist/delegation/delegation-handler.js.map +1 -0
- package/dist/delegation/headless-executor.d.ts +114 -0
- package/dist/delegation/headless-executor.d.ts.map +1 -0
- package/dist/delegation/headless-executor.js +562 -0
- package/dist/delegation/headless-executor.js.map +1 -0
- package/dist/delegation/result-formatter.d.ts +108 -0
- package/dist/delegation/result-formatter.d.ts.map +1 -0
- package/dist/delegation/result-formatter.js +391 -0
- package/dist/delegation/result-formatter.js.map +1 -0
- package/dist/delegation/session-manager.d.ts +58 -0
- package/dist/delegation/session-manager.d.ts.map +1 -0
- package/dist/delegation/session-manager.js +153 -0
- package/dist/delegation/session-manager.js.map +1 -0
- package/dist/delegation/settings-parser.d.ts +31 -0
- package/dist/delegation/settings-parser.d.ts.map +1 -0
- package/dist/delegation/settings-parser.js +107 -0
- package/dist/delegation/settings-parser.js.map +1 -0
- package/dist/glmt/delta-accumulator.d.ts +210 -0
- package/dist/glmt/delta-accumulator.d.ts.map +1 -0
- package/dist/glmt/delta-accumulator.js +351 -0
- package/dist/glmt/delta-accumulator.js.map +1 -0
- package/dist/glmt/glmt-proxy.d.ts +72 -0
- package/dist/glmt/glmt-proxy.d.ts.map +1 -0
- package/dist/glmt/glmt-proxy.js +427 -0
- package/dist/glmt/glmt-proxy.js.map +1 -0
- package/dist/glmt/glmt-transformer.d.ts +265 -0
- package/dist/glmt/glmt-transformer.d.ts.map +1 -0
- package/dist/glmt/glmt-transformer.js +832 -0
- package/dist/glmt/glmt-transformer.js.map +1 -0
- package/dist/glmt/locale-enforcer.d.ts +38 -0
- package/dist/glmt/locale-enforcer.d.ts.map +1 -0
- package/dist/glmt/locale-enforcer.js +69 -0
- package/dist/glmt/locale-enforcer.js.map +1 -0
- package/dist/glmt/reasoning-enforcer.d.ts +52 -0
- package/dist/glmt/reasoning-enforcer.d.ts.map +1 -0
- package/dist/glmt/reasoning-enforcer.js +151 -0
- package/dist/glmt/reasoning-enforcer.js.map +1 -0
- package/dist/glmt/sse-parser.d.ts +47 -0
- package/dist/glmt/sse-parser.d.ts.map +1 -0
- package/dist/glmt/sse-parser.js +93 -0
- package/dist/glmt/sse-parser.js.map +1 -0
- package/dist/management/doctor.d.ts +104 -0
- package/dist/management/doctor.d.ts.map +1 -0
- package/dist/management/doctor.js +673 -0
- package/dist/management/doctor.js.map +1 -0
- package/dist/management/instance-manager.d.ts +57 -0
- package/dist/management/instance-manager.d.ts.map +1 -0
- package/dist/management/instance-manager.js +195 -0
- package/dist/management/instance-manager.js.map +1 -0
- package/dist/management/recovery-manager.d.ts +39 -0
- package/dist/management/recovery-manager.d.ts.map +1 -0
- package/dist/management/recovery-manager.js +141 -0
- package/dist/management/recovery-manager.js.map +1 -0
- package/dist/management/shared-manager.d.ts +47 -0
- package/dist/management/shared-manager.d.ts.map +1 -0
- package/dist/management/shared-manager.js +388 -0
- package/dist/management/shared-manager.js.map +1 -0
- package/dist/types/cli.d.ts +50 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +16 -0
- package/dist/types/cli.js.map +1 -0
- package/dist/types/config.d.ts +51 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +26 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/delegation.d.ts +61 -0
- package/dist/types/delegation.d.ts.map +1 -0
- package/dist/types/delegation.js +6 -0
- package/dist/types/delegation.js.map +1 -0
- package/dist/types/glmt.d.ts +95 -0
- package/dist/types/glmt.d.ts.map +1 -0
- package/dist/types/glmt.js +7 -0
- package/dist/types/glmt.js.map +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +16 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/utils.d.ts +36 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/dist/types/utils.js +22 -0
- package/dist/types/utils.js.map +1 -0
- package/dist/utils/claude-detector.d.ts +14 -0
- package/dist/utils/claude-detector.d.ts.map +1 -0
- package/dist/utils/claude-detector.js +112 -0
- package/dist/utils/claude-detector.js.map +1 -0
- package/dist/utils/claude-dir-installer.d.ts +46 -0
- package/dist/utils/claude-dir-installer.d.ts.map +1 -0
- package/dist/utils/claude-dir-installer.js +289 -0
- package/dist/utils/claude-dir-installer.js.map +1 -0
- package/dist/utils/claude-symlink-manager.d.ts +61 -0
- package/dist/utils/claude-symlink-manager.d.ts.map +1 -0
- package/dist/utils/claude-symlink-manager.js +291 -0
- package/dist/utils/claude-symlink-manager.js.map +1 -0
- package/dist/utils/config-manager.d.ts +32 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +143 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/delegation-validator.d.ts +39 -0
- package/dist/utils/delegation-validator.d.ts.map +1 -0
- package/dist/utils/delegation-validator.js +161 -0
- package/dist/utils/delegation-validator.js.map +1 -0
- package/dist/utils/error-codes.d.ts +36 -0
- package/dist/utils/error-codes.d.ts.map +1 -0
- package/dist/utils/error-codes.js +63 -0
- package/dist/utils/error-codes.js.map +1 -0
- package/dist/utils/error-manager.d.ts +59 -0
- package/dist/utils/error-manager.d.ts.map +1 -0
- package/dist/utils/error-manager.js +228 -0
- package/dist/utils/error-manager.js.map +1 -0
- package/dist/utils/helpers.d.ts +27 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +150 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/package-manager-detector.d.ts +14 -0
- package/dist/utils/package-manager-detector.d.ts.map +1 -0
- package/dist/utils/package-manager-detector.js +162 -0
- package/dist/utils/package-manager-detector.js.map +1 -0
- package/dist/utils/progress-indicator.d.ts +52 -0
- package/dist/utils/progress-indicator.d.ts.map +1 -0
- package/dist/utils/progress-indicator.js +102 -0
- package/dist/utils/progress-indicator.js.map +1 -0
- package/dist/utils/prompt.d.ts +29 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +116 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/shell-completion.d.ts +52 -0
- package/dist/utils/shell-completion.d.ts.map +1 -0
- package/dist/utils/shell-completion.js +231 -0
- package/dist/utils/shell-completion.js.map +1 -0
- package/dist/utils/shell-executor.d.ts +15 -0
- package/dist/utils/shell-executor.d.ts.map +1 -0
- package/dist/utils/shell-executor.js +57 -0
- package/dist/utils/shell-executor.js.map +1 -0
- package/dist/utils/update-checker.d.ts +48 -0
- package/dist/utils/update-checker.d.ts.map +1 -0
- package/dist/utils/update-checker.js +241 -0
- package/dist/utils/update-checker.js.map +1 -0
- package/lib/ccs +21 -1907
- package/lib/ccs.ps1 +26 -1800
- package/lib/error-codes.ps1 +2 -1
- package/lib/prompt.ps1 +2 -2
- package/package.json +31 -11
- package/scripts/add-shebang.js +39 -0
- package/scripts/bump-version.sh +25 -37
- package/scripts/dev-install.sh +32 -11
- package/scripts/postinstall.js +29 -29
- package/bin/auth/auth-commands.js +0 -499
- package/bin/auth/profile-detector.js +0 -204
- package/bin/auth/profile-registry.js +0 -225
- package/bin/ccs.js +0 -1034
- package/bin/delegation/README.md +0 -191
- package/bin/delegation/delegation-handler.js +0 -212
- package/bin/delegation/headless-executor.js +0 -618
- package/bin/delegation/result-formatter.js +0 -485
- package/bin/delegation/session-manager.js +0 -157
- package/bin/delegation/settings-parser.js +0 -109
- package/bin/glmt/delta-accumulator.js +0 -276
- package/bin/glmt/glmt-proxy.js +0 -495
- package/bin/glmt/glmt-transformer.js +0 -999
- package/bin/glmt/locale-enforcer.js +0 -72
- package/bin/glmt/reasoning-enforcer.js +0 -173
- package/bin/glmt/sse-parser.js +0 -96
- package/bin/management/doctor.js +0 -721
- package/bin/management/instance-manager.js +0 -202
- package/bin/management/recovery-manager.js +0 -135
- package/bin/management/shared-manager.js +0 -402
- package/bin/utils/claude-detector.js +0 -73
- package/bin/utils/claude-dir-installer.js +0 -283
- package/bin/utils/claude-symlink-manager.js +0 -289
- package/bin/utils/config-manager.js +0 -103
- package/bin/utils/delegation-validator.js +0 -154
- package/bin/utils/error-codes.js +0 -59
- package/bin/utils/error-manager.js +0 -165
- package/bin/utils/helpers.js +0 -136
- package/bin/utils/progress-indicator.js +0 -111
- package/bin/utils/prompt.js +0 -134
- package/bin/utils/shell-completion.js +0 -256
- package/bin/utils/update-checker.js +0 -243
|
@@ -1,999 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const crypto = require('crypto');
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
const SSEParser = require('./sse-parser');
|
|
9
|
-
const DeltaAccumulator = require('./delta-accumulator');
|
|
10
|
-
const LocaleEnforcer = require('./locale-enforcer');
|
|
11
|
-
const ReasoningEnforcer = require('./reasoning-enforcer');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* GlmtTransformer - Convert between Anthropic and OpenAI formats with thinking and tool support
|
|
15
|
-
*
|
|
16
|
-
* Features:
|
|
17
|
-
* - Request: Anthropic → OpenAI (inject reasoning params, transform tools)
|
|
18
|
-
* - Response: OpenAI reasoning_content → Anthropic thinking blocks
|
|
19
|
-
* - Tool Support: Anthropic tools ↔ OpenAI function calling (bidirectional)
|
|
20
|
-
* - Streaming: Real-time tool calls with input_json deltas
|
|
21
|
-
* - Debug mode: Log raw data to ~/.ccs/logs/ (CCS_DEBUG=1)
|
|
22
|
-
* - Verbose mode: Console logging with timestamps
|
|
23
|
-
* - Validation: Self-test transformation results
|
|
24
|
-
*
|
|
25
|
-
* Usage:
|
|
26
|
-
* const transformer = new GlmtTransformer({ verbose: true, debugLog: true });
|
|
27
|
-
* const { openaiRequest, thinkingConfig } = transformer.transformRequest(req);
|
|
28
|
-
* const anthropicResponse = transformer.transformResponse(resp, thinkingConfig);
|
|
29
|
-
*
|
|
30
|
-
* Control Tags (in user prompt):
|
|
31
|
-
* <Thinking:On|Off> - Enable/disable reasoning
|
|
32
|
-
* <Effort:Low|Medium|High> - Control reasoning depth
|
|
33
|
-
*/
|
|
34
|
-
class GlmtTransformer {
|
|
35
|
-
static _warnedDeprecation = false;
|
|
36
|
-
|
|
37
|
-
constructor(config = {}) {
|
|
38
|
-
this.defaultThinking = config.defaultThinking ?? true;
|
|
39
|
-
this.verbose = config.verbose || false;
|
|
40
|
-
|
|
41
|
-
// CCS_DEBUG controls all debug logging (file + console)
|
|
42
|
-
const debugEnabled = process.env.CCS_DEBUG === '1';
|
|
43
|
-
this.debugLog = config.debugLog ?? debugEnabled;
|
|
44
|
-
this.debugMode = config.debugMode ?? debugEnabled;
|
|
45
|
-
|
|
46
|
-
this.debugLogDir = config.debugLogDir || path.join(os.homedir(), '.ccs', 'logs');
|
|
47
|
-
this.modelMaxTokens = {
|
|
48
|
-
'GLM-4.6': 128000,
|
|
49
|
-
'GLM-4.5': 96000,
|
|
50
|
-
'GLM-4.5-air': 16000
|
|
51
|
-
};
|
|
52
|
-
// Effort level thresholds (budget_tokens)
|
|
53
|
-
this.EFFORT_LOW_THRESHOLD = 2048;
|
|
54
|
-
this.EFFORT_HIGH_THRESHOLD = 8192;
|
|
55
|
-
|
|
56
|
-
// Initialize locale enforcer (always enforce English)
|
|
57
|
-
this.localeEnforcer = new LocaleEnforcer();
|
|
58
|
-
|
|
59
|
-
// Initialize reasoning enforcer (enabled by default for all GLMT usage)
|
|
60
|
-
this.reasoningEnforcer = new ReasoningEnforcer({
|
|
61
|
-
enabled: config.explicitReasoning ?? true
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Transform Anthropic request to OpenAI format
|
|
67
|
-
* @param {Object} anthropicRequest - Anthropic Messages API request
|
|
68
|
-
* @returns {Object} { openaiRequest, thinkingConfig }
|
|
69
|
-
*/
|
|
70
|
-
transformRequest(anthropicRequest) {
|
|
71
|
-
// Log original request
|
|
72
|
-
this._writeDebugLog('request-anthropic', anthropicRequest);
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
// 1. Extract thinking control from messages (tags like <Thinking:On|Off>)
|
|
76
|
-
const thinkingConfig = this._extractThinkingControl(
|
|
77
|
-
anthropicRequest.messages || []
|
|
78
|
-
);
|
|
79
|
-
const hasControlTags = this._hasThinkingTags(anthropicRequest.messages || []);
|
|
80
|
-
|
|
81
|
-
// 2. Detect "think" keywords in user prompts (Anthropic-style)
|
|
82
|
-
const keywordConfig = this._detectThinkKeywords(anthropicRequest.messages || []);
|
|
83
|
-
if (keywordConfig && !anthropicRequest.thinking && !hasControlTags) {
|
|
84
|
-
thinkingConfig.thinking = keywordConfig.thinking;
|
|
85
|
-
thinkingConfig.effort = keywordConfig.effort;
|
|
86
|
-
this.log(`Detected think keyword: ${keywordConfig.keyword}, effort=${keywordConfig.effort}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// 3. Check anthropicRequest.thinking parameter (takes precedence)
|
|
90
|
-
// Claude CLI sends this when alwaysThinkingEnabled is configured
|
|
91
|
-
if (anthropicRequest.thinking) {
|
|
92
|
-
if (anthropicRequest.thinking.type === 'enabled') {
|
|
93
|
-
thinkingConfig.thinking = true;
|
|
94
|
-
this.log('Claude CLI explicitly enabled thinking (overrides budget)');
|
|
95
|
-
} else if (anthropicRequest.thinking.type === 'disabled') {
|
|
96
|
-
thinkingConfig.thinking = false;
|
|
97
|
-
this.log('Claude CLI explicitly disabled thinking (overrides budget)');
|
|
98
|
-
} else {
|
|
99
|
-
this.log(`Warning: Unknown thinking type: ${anthropicRequest.thinking.type}`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
this.log(`Final thinking control: ${JSON.stringify(thinkingConfig)}`);
|
|
104
|
-
|
|
105
|
-
// 3. Map model
|
|
106
|
-
const glmModel = this._mapModel(anthropicRequest.model);
|
|
107
|
-
|
|
108
|
-
// 4. Inject locale instruction before sanitization
|
|
109
|
-
const messagesWithLocale = this.localeEnforcer.injectInstruction(
|
|
110
|
-
anthropicRequest.messages || []
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
// 4.5. Inject reasoning instruction (if enabled or thinking requested)
|
|
114
|
-
const messagesWithReasoning = this.reasoningEnforcer.injectInstruction(
|
|
115
|
-
messagesWithLocale,
|
|
116
|
-
thinkingConfig
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
// 5. Convert to OpenAI format
|
|
120
|
-
const openaiRequest = {
|
|
121
|
-
model: glmModel,
|
|
122
|
-
messages: this._sanitizeMessages(messagesWithReasoning),
|
|
123
|
-
max_tokens: this._getMaxTokens(glmModel),
|
|
124
|
-
stream: anthropicRequest.stream ?? false
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
// 5.5. Transform tools parameter if present
|
|
128
|
-
if (anthropicRequest.tools && anthropicRequest.tools.length > 0) {
|
|
129
|
-
openaiRequest.tools = this._transformTools(anthropicRequest.tools);
|
|
130
|
-
// Always use "auto" as Z.AI doesn't support other modes
|
|
131
|
-
openaiRequest.tool_choice = "auto";
|
|
132
|
-
this.log(`Transformed ${anthropicRequest.tools.length} tools for OpenAI format`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// 6. Preserve optional parameters
|
|
136
|
-
if (anthropicRequest.temperature !== undefined) {
|
|
137
|
-
openaiRequest.temperature = anthropicRequest.temperature;
|
|
138
|
-
}
|
|
139
|
-
if (anthropicRequest.top_p !== undefined) {
|
|
140
|
-
openaiRequest.top_p = anthropicRequest.top_p;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// 7. Handle streaming
|
|
144
|
-
// Keep stream parameter from request
|
|
145
|
-
if (anthropicRequest.stream !== undefined) {
|
|
146
|
-
openaiRequest.stream = anthropicRequest.stream;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// 8. Inject reasoning parameters
|
|
150
|
-
this._injectReasoningParams(openaiRequest, thinkingConfig);
|
|
151
|
-
|
|
152
|
-
// Log transformed request
|
|
153
|
-
this._writeDebugLog('request-openai', openaiRequest);
|
|
154
|
-
|
|
155
|
-
return { openaiRequest, thinkingConfig };
|
|
156
|
-
} catch (error) {
|
|
157
|
-
console.error('[glmt-transformer] Request transformation error:', error);
|
|
158
|
-
// Return original request with warning
|
|
159
|
-
return {
|
|
160
|
-
openaiRequest: anthropicRequest,
|
|
161
|
-
thinkingConfig: { thinking: false },
|
|
162
|
-
error: error.message
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Transform OpenAI response to Anthropic format
|
|
169
|
-
* @param {Object} openaiResponse - OpenAI Chat Completions response
|
|
170
|
-
* @param {Object} thinkingConfig - Config from request transformation
|
|
171
|
-
* @returns {Object} Anthropic Messages API response
|
|
172
|
-
*/
|
|
173
|
-
transformResponse(openaiResponse, thinkingConfig = {}) {
|
|
174
|
-
// Log original response
|
|
175
|
-
this._writeDebugLog('response-openai', openaiResponse);
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
const choice = openaiResponse.choices?.[0];
|
|
179
|
-
if (!choice) {
|
|
180
|
-
throw new Error('No choices in OpenAI response');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const message = choice.message;
|
|
184
|
-
const content = [];
|
|
185
|
-
|
|
186
|
-
// Add thinking block if reasoning_content exists
|
|
187
|
-
if (message.reasoning_content) {
|
|
188
|
-
const length = message.reasoning_content.length;
|
|
189
|
-
const lineCount = message.reasoning_content.split('\n').length;
|
|
190
|
-
const preview = message.reasoning_content
|
|
191
|
-
.substring(0, 100)
|
|
192
|
-
.replace(/\n/g, ' ')
|
|
193
|
-
.trim();
|
|
194
|
-
|
|
195
|
-
this.log(`Detected reasoning_content:`);
|
|
196
|
-
this.log(` Length: ${length} characters`);
|
|
197
|
-
this.log(` Lines: ${lineCount}`);
|
|
198
|
-
this.log(` Preview: ${preview}...`);
|
|
199
|
-
|
|
200
|
-
content.push({
|
|
201
|
-
type: 'thinking',
|
|
202
|
-
thinking: message.reasoning_content,
|
|
203
|
-
signature: this._generateThinkingSignature(message.reasoning_content)
|
|
204
|
-
});
|
|
205
|
-
} else {
|
|
206
|
-
this.log('No reasoning_content in OpenAI response');
|
|
207
|
-
this.log('Note: This is expected if thinking not requested or model cannot reason');
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Add text content
|
|
211
|
-
if (message.content) {
|
|
212
|
-
content.push({
|
|
213
|
-
type: 'text',
|
|
214
|
-
text: message.content
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Handle tool_calls if present
|
|
219
|
-
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
220
|
-
message.tool_calls.forEach(toolCall => {
|
|
221
|
-
let parsedInput;
|
|
222
|
-
try {
|
|
223
|
-
parsedInput = JSON.parse(toolCall.function.arguments || '{}');
|
|
224
|
-
} catch (parseError) {
|
|
225
|
-
this.log(`Warning: Invalid JSON in tool arguments: ${parseError.message}`);
|
|
226
|
-
parsedInput = { _error: 'Invalid JSON', _raw: toolCall.function.arguments };
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
content.push({
|
|
230
|
-
type: 'tool_use',
|
|
231
|
-
id: toolCall.id,
|
|
232
|
-
name: toolCall.function.name,
|
|
233
|
-
input: parsedInput
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const anthropicResponse = {
|
|
239
|
-
id: openaiResponse.id || 'msg_' + Date.now(),
|
|
240
|
-
type: 'message',
|
|
241
|
-
role: 'assistant',
|
|
242
|
-
content: content,
|
|
243
|
-
model: openaiResponse.model || 'glm-4.6',
|
|
244
|
-
stop_reason: this._mapStopReason(choice.finish_reason),
|
|
245
|
-
usage: {
|
|
246
|
-
input_tokens: openaiResponse.usage?.prompt_tokens || 0,
|
|
247
|
-
output_tokens: openaiResponse.usage?.completion_tokens || 0
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
// Validate transformation in verbose mode
|
|
252
|
-
if (this.verbose) {
|
|
253
|
-
const validation = this._validateTransformation(anthropicResponse);
|
|
254
|
-
this.log(`Transformation validation: ${validation.passed}/${validation.total} checks passed`);
|
|
255
|
-
if (!validation.valid) {
|
|
256
|
-
this.log(`Failed checks: ${JSON.stringify(validation.checks, null, 2)}`);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Log transformed response
|
|
261
|
-
this._writeDebugLog('response-anthropic', anthropicResponse);
|
|
262
|
-
|
|
263
|
-
return anthropicResponse;
|
|
264
|
-
} catch (error) {
|
|
265
|
-
console.error('[glmt-transformer] Response transformation error:', error);
|
|
266
|
-
// Return minimal valid response
|
|
267
|
-
return {
|
|
268
|
-
id: 'msg_error_' + Date.now(),
|
|
269
|
-
type: 'message',
|
|
270
|
-
role: 'assistant',
|
|
271
|
-
content: [{
|
|
272
|
-
type: 'text',
|
|
273
|
-
text: '[Transformation Error] ' + error.message
|
|
274
|
-
}],
|
|
275
|
-
stop_reason: 'end_turn',
|
|
276
|
-
usage: { input_tokens: 0, output_tokens: 0 }
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Sanitize messages for OpenAI API compatibility
|
|
283
|
-
* Convert tool_result blocks to separate tool messages
|
|
284
|
-
* Filter out thinking blocks
|
|
285
|
-
* @param {Array} messages - Messages array
|
|
286
|
-
* @returns {Array} Sanitized messages
|
|
287
|
-
* @private
|
|
288
|
-
*/
|
|
289
|
-
_sanitizeMessages(messages) {
|
|
290
|
-
const result = [];
|
|
291
|
-
|
|
292
|
-
for (const msg of messages) {
|
|
293
|
-
// If content is a string, add as-is
|
|
294
|
-
if (typeof msg.content === 'string') {
|
|
295
|
-
result.push(msg);
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// If content is an array, process blocks
|
|
300
|
-
if (Array.isArray(msg.content)) {
|
|
301
|
-
// Separate tool_result blocks from other content
|
|
302
|
-
const toolResults = msg.content.filter(block => block.type === 'tool_result');
|
|
303
|
-
const textBlocks = msg.content.filter(block => block.type === 'text');
|
|
304
|
-
const toolUseBlocks = msg.content.filter(block => block.type === 'tool_use');
|
|
305
|
-
|
|
306
|
-
// CRITICAL: Tool messages must come BEFORE user text in OpenAI API
|
|
307
|
-
// Convert tool_result blocks to OpenAI tool messages FIRST
|
|
308
|
-
for (const toolResult of toolResults) {
|
|
309
|
-
result.push({
|
|
310
|
-
role: 'tool',
|
|
311
|
-
tool_call_id: toolResult.tool_use_id,
|
|
312
|
-
content: typeof toolResult.content === 'string'
|
|
313
|
-
? toolResult.content
|
|
314
|
-
: JSON.stringify(toolResult.content)
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Add text content as user/assistant message AFTER tool messages
|
|
319
|
-
if (textBlocks.length > 0) {
|
|
320
|
-
const textContent = textBlocks.length === 1
|
|
321
|
-
? textBlocks[0].text
|
|
322
|
-
: textBlocks.map(b => b.text).join('\n');
|
|
323
|
-
|
|
324
|
-
result.push({
|
|
325
|
-
role: msg.role,
|
|
326
|
-
content: textContent
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Add tool_use blocks (assistant's tool calls) - skip for now, they're in assistant messages
|
|
331
|
-
// OpenAI handles these differently in response, not request
|
|
332
|
-
|
|
333
|
-
// If no content at all, add empty message (but not if we added tool messages)
|
|
334
|
-
if (textBlocks.length === 0 && toolResults.length === 0 && toolUseBlocks.length === 0) {
|
|
335
|
-
result.push({
|
|
336
|
-
role: msg.role,
|
|
337
|
-
content: ''
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
continue;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Fallback: return message as-is
|
|
345
|
-
result.push(msg);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
return result;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Transform Anthropic tools to OpenAI tools format
|
|
353
|
-
* @param {Array} anthropicTools - Anthropic tools array
|
|
354
|
-
* @returns {Array} OpenAI tools array
|
|
355
|
-
* @private
|
|
356
|
-
*/
|
|
357
|
-
_transformTools(anthropicTools) {
|
|
358
|
-
return anthropicTools.map(tool => ({
|
|
359
|
-
type: 'function',
|
|
360
|
-
function: {
|
|
361
|
-
name: tool.name,
|
|
362
|
-
description: tool.description,
|
|
363
|
-
parameters: tool.input_schema || {}
|
|
364
|
-
}
|
|
365
|
-
}));
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Check if messages contain thinking control tags
|
|
370
|
-
* @param {Array} messages - Messages array
|
|
371
|
-
* @returns {boolean} True if tags found
|
|
372
|
-
* @private
|
|
373
|
-
*/
|
|
374
|
-
_hasThinkingTags(messages) {
|
|
375
|
-
for (const msg of messages) {
|
|
376
|
-
if (msg.role !== 'user') continue;
|
|
377
|
-
const content = msg.content;
|
|
378
|
-
if (typeof content !== 'string') continue;
|
|
379
|
-
|
|
380
|
-
// Check for control tags
|
|
381
|
-
if (/<Thinking:(On|Off)>/i.test(content) || /<Effort:(Low|Medium|High)>/i.test(content)) {
|
|
382
|
-
return true;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
return false;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Extract thinking control tags from user messages
|
|
390
|
-
* @param {Array} messages - Messages array
|
|
391
|
-
* @returns {Object} { thinking: boolean, effort: string }
|
|
392
|
-
* @private
|
|
393
|
-
*/
|
|
394
|
-
_extractThinkingControl(messages) {
|
|
395
|
-
const config = {
|
|
396
|
-
thinking: this.defaultThinking,
|
|
397
|
-
effort: 'medium'
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
// Scan user messages for control tags
|
|
401
|
-
for (const msg of messages) {
|
|
402
|
-
if (msg.role !== 'user') continue;
|
|
403
|
-
|
|
404
|
-
const content = msg.content;
|
|
405
|
-
if (typeof content !== 'string') continue;
|
|
406
|
-
|
|
407
|
-
// Check for <Thinking:On|Off>
|
|
408
|
-
const thinkingMatch = content.match(/<Thinking:(On|Off)>/i);
|
|
409
|
-
if (thinkingMatch) {
|
|
410
|
-
config.thinking = thinkingMatch[1].toLowerCase() === 'on';
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Check for <Effort:Low|Medium|High>
|
|
414
|
-
const effortMatch = content.match(/<Effort:(Low|Medium|High)>/i);
|
|
415
|
-
if (effortMatch) {
|
|
416
|
-
config.effort = effortMatch[1].toLowerCase();
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return config;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Generate thinking signature for Claude Code UI
|
|
425
|
-
* @param {string} thinking - Thinking content
|
|
426
|
-
* @returns {Object} Signature object
|
|
427
|
-
* @private
|
|
428
|
-
*/
|
|
429
|
-
_generateThinkingSignature(thinking) {
|
|
430
|
-
// Generate signature hash
|
|
431
|
-
const hash = crypto.createHash('sha256')
|
|
432
|
-
.update(thinking)
|
|
433
|
-
.digest('hex')
|
|
434
|
-
.substring(0, 16);
|
|
435
|
-
|
|
436
|
-
return {
|
|
437
|
-
type: 'thinking_signature',
|
|
438
|
-
hash: hash,
|
|
439
|
-
length: thinking.length,
|
|
440
|
-
timestamp: Date.now()
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Detect Anthropic-style "think" keywords in user prompts
|
|
446
|
-
* Maps: "ultrathink" > "think harder" > "think hard" > "think"
|
|
447
|
-
* @param {Array} messages - Messages array
|
|
448
|
-
* @returns {Object|null} { thinking, effort, keyword } or null
|
|
449
|
-
* @private
|
|
450
|
-
*/
|
|
451
|
-
_detectThinkKeywords(messages) {
|
|
452
|
-
if (!messages || messages.length === 0) return null;
|
|
453
|
-
|
|
454
|
-
// Extract text from user messages
|
|
455
|
-
const text = messages
|
|
456
|
-
.filter(m => m.role === 'user')
|
|
457
|
-
.map(m => {
|
|
458
|
-
if (typeof m.content === 'string') return m.content;
|
|
459
|
-
if (Array.isArray(m.content)) {
|
|
460
|
-
return m.content
|
|
461
|
-
.filter(block => block.type === 'text')
|
|
462
|
-
.map(block => block.text || '')
|
|
463
|
-
.join(' ');
|
|
464
|
-
}
|
|
465
|
-
return '';
|
|
466
|
-
})
|
|
467
|
-
.join(' ');
|
|
468
|
-
|
|
469
|
-
// Priority: ultrathink > think harder > think hard > think
|
|
470
|
-
// Effort levels: max > high > medium > low (matches Anthropic's 4-tier system)
|
|
471
|
-
// Use word boundaries to avoid matching "thinking", "rethink", etc.
|
|
472
|
-
if (/\bultrathink\b/i.test(text)) {
|
|
473
|
-
return { thinking: true, effort: 'max', keyword: 'ultrathink' };
|
|
474
|
-
}
|
|
475
|
-
if (/\bthink\s+harder\b/i.test(text)) {
|
|
476
|
-
return { thinking: true, effort: 'high', keyword: 'think harder' };
|
|
477
|
-
}
|
|
478
|
-
if (/\bthink\s+hard\b/i.test(text)) {
|
|
479
|
-
return { thinking: true, effort: 'medium', keyword: 'think hard' };
|
|
480
|
-
}
|
|
481
|
-
if (/\bthink\b/i.test(text)) {
|
|
482
|
-
return { thinking: true, effort: 'low', keyword: 'think' };
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
return null; // No keywords detected
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Inject reasoning parameters into OpenAI request
|
|
490
|
-
* @param {Object} openaiRequest - OpenAI request to modify
|
|
491
|
-
* @param {Object} thinkingConfig - Thinking configuration
|
|
492
|
-
* @returns {Object} Modified request
|
|
493
|
-
* @private
|
|
494
|
-
*/
|
|
495
|
-
_injectReasoningParams(openaiRequest, thinkingConfig) {
|
|
496
|
-
// Always enable sampling for temperature/top_p to work
|
|
497
|
-
openaiRequest.do_sample = true;
|
|
498
|
-
|
|
499
|
-
// Add thinking-specific parameters if enabled
|
|
500
|
-
if (thinkingConfig.thinking) {
|
|
501
|
-
// Z.AI may support these parameters (based on research)
|
|
502
|
-
openaiRequest.reasoning = true;
|
|
503
|
-
openaiRequest.reasoning_effort = thinkingConfig.effort;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
return openaiRequest;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Map Anthropic model to GLM model
|
|
511
|
-
* @param {string} anthropicModel - Anthropic model name
|
|
512
|
-
* @returns {string} GLM model name
|
|
513
|
-
* @private
|
|
514
|
-
*/
|
|
515
|
-
_mapModel(anthropicModel) {
|
|
516
|
-
// Default to GLM-4.6 (latest and most capable)
|
|
517
|
-
return 'GLM-4.6';
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* Get max tokens for model
|
|
522
|
-
* @param {string} model - Model name
|
|
523
|
-
* @returns {number} Max tokens
|
|
524
|
-
* @private
|
|
525
|
-
*/
|
|
526
|
-
_getMaxTokens(model) {
|
|
527
|
-
return this.modelMaxTokens[model] || 128000;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Map OpenAI stop reason to Anthropic stop reason
|
|
532
|
-
* @param {string} openaiReason - OpenAI finish_reason
|
|
533
|
-
* @returns {string} Anthropic stop_reason
|
|
534
|
-
* @private
|
|
535
|
-
*/
|
|
536
|
-
_mapStopReason(openaiReason) {
|
|
537
|
-
const mapping = {
|
|
538
|
-
'stop': 'end_turn',
|
|
539
|
-
'length': 'max_tokens',
|
|
540
|
-
'tool_calls': 'tool_use',
|
|
541
|
-
'content_filter': 'stop_sequence'
|
|
542
|
-
};
|
|
543
|
-
return mapping[openaiReason] || 'end_turn';
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* Write debug log to file
|
|
548
|
-
* @param {string} type - 'request-anthropic', 'request-openai', 'response-openai', 'response-anthropic'
|
|
549
|
-
* @param {object} data - Data to log
|
|
550
|
-
* @private
|
|
551
|
-
*/
|
|
552
|
-
_writeDebugLog(type, data) {
|
|
553
|
-
if (!this.debugLog) return;
|
|
554
|
-
|
|
555
|
-
try {
|
|
556
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('.')[0];
|
|
557
|
-
const filename = `${timestamp}-${type}.json`;
|
|
558
|
-
const filepath = path.join(this.debugLogDir, filename);
|
|
559
|
-
|
|
560
|
-
// Ensure directory exists
|
|
561
|
-
fs.mkdirSync(this.debugLogDir, { recursive: true });
|
|
562
|
-
|
|
563
|
-
// Write file (pretty-printed)
|
|
564
|
-
fs.writeFileSync(filepath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
565
|
-
|
|
566
|
-
if (this.verbose) {
|
|
567
|
-
this.log(`Debug log written: ${filepath}`);
|
|
568
|
-
}
|
|
569
|
-
} catch (error) {
|
|
570
|
-
console.error(`[glmt-transformer] Failed to write debug log: ${error.message}`);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* Validate transformed Anthropic response
|
|
576
|
-
* @param {object} anthropicResponse - Response to validate
|
|
577
|
-
* @returns {object} Validation results
|
|
578
|
-
* @private
|
|
579
|
-
*/
|
|
580
|
-
_validateTransformation(anthropicResponse) {
|
|
581
|
-
const checks = {
|
|
582
|
-
hasContent: Boolean(anthropicResponse.content && anthropicResponse.content.length > 0),
|
|
583
|
-
hasThinking: anthropicResponse.content?.some(block => block.type === 'thinking') || false,
|
|
584
|
-
hasText: anthropicResponse.content?.some(block => block.type === 'text') || false,
|
|
585
|
-
validStructure: anthropicResponse.type === 'message' && anthropicResponse.role === 'assistant',
|
|
586
|
-
hasUsage: Boolean(anthropicResponse.usage)
|
|
587
|
-
};
|
|
588
|
-
|
|
589
|
-
const passed = Object.values(checks).filter(Boolean).length;
|
|
590
|
-
const total = Object.keys(checks).length;
|
|
591
|
-
|
|
592
|
-
return { checks, passed, total, valid: passed === total };
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
/**
|
|
596
|
-
* Transform OpenAI streaming delta to Anthropic events
|
|
597
|
-
* @param {Object} openaiEvent - Parsed SSE event from Z.AI
|
|
598
|
-
* @param {DeltaAccumulator} accumulator - State accumulator
|
|
599
|
-
* @returns {Array<Object>} Array of Anthropic SSE events
|
|
600
|
-
*/
|
|
601
|
-
transformDelta(openaiEvent, accumulator) {
|
|
602
|
-
const events = [];
|
|
603
|
-
|
|
604
|
-
// Debug logging for streaming deltas
|
|
605
|
-
if (this.debugLog && openaiEvent.data) {
|
|
606
|
-
this._writeDebugLog('delta-openai', openaiEvent.data);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// Handle [DONE] marker
|
|
610
|
-
// Only finalize if we haven't already (deferred finalization may have already triggered)
|
|
611
|
-
if (openaiEvent.event === 'done') {
|
|
612
|
-
if (!accumulator.finalized) {
|
|
613
|
-
return this.finalizeDelta(accumulator);
|
|
614
|
-
}
|
|
615
|
-
return []; // Already finalized
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// Usage update (appears in final chunk, may be before choice data)
|
|
619
|
-
// Process this BEFORE early returns to ensure we capture usage
|
|
620
|
-
if (openaiEvent.data?.usage) {
|
|
621
|
-
accumulator.updateUsage(openaiEvent.data.usage);
|
|
622
|
-
|
|
623
|
-
// If we have both usage AND finish_reason, finalize immediately
|
|
624
|
-
if (accumulator.finishReason) {
|
|
625
|
-
events.push(...this.finalizeDelta(accumulator));
|
|
626
|
-
return events; // Early return after finalization
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
const choice = openaiEvent.data?.choices?.[0];
|
|
631
|
-
if (!choice) return events;
|
|
632
|
-
|
|
633
|
-
const delta = choice.delta;
|
|
634
|
-
if (!delta) return events;
|
|
635
|
-
|
|
636
|
-
// Message start
|
|
637
|
-
if (!accumulator.messageStarted) {
|
|
638
|
-
if (openaiEvent.data.model) {
|
|
639
|
-
accumulator.model = openaiEvent.data.model;
|
|
640
|
-
}
|
|
641
|
-
events.push(this._createMessageStartEvent(accumulator));
|
|
642
|
-
accumulator.messageStarted = true;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// Role
|
|
646
|
-
if (delta.role) {
|
|
647
|
-
accumulator.role = delta.role;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// Reasoning content delta (Z.AI streams incrementally - confirmed in Phase 02)
|
|
651
|
-
if (delta.reasoning_content) {
|
|
652
|
-
const currentBlock = accumulator.getCurrentBlock();
|
|
653
|
-
|
|
654
|
-
// FIX: Enhanced debug logging for thinking block diagnostics
|
|
655
|
-
if (this.debugMode) {
|
|
656
|
-
console.error(`[GLMT-DEBUG] Reasoning delta: ${delta.reasoning_content.length} chars`);
|
|
657
|
-
console.error(`[GLMT-DEBUG] Current block: ${currentBlock?.type || 'none'}, index: ${currentBlock?.index ?? 'N/A'}`);
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
if (!currentBlock || currentBlock.type !== 'thinking') {
|
|
661
|
-
// Start thinking block
|
|
662
|
-
const block = accumulator.startBlock('thinking');
|
|
663
|
-
events.push(this._createContentBlockStartEvent(block));
|
|
664
|
-
|
|
665
|
-
if (this.debugMode) {
|
|
666
|
-
console.error(`[GLMT-DEBUG] Started new thinking block ${block.index}`);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
accumulator.addDelta(delta.reasoning_content);
|
|
671
|
-
events.push(this._createThinkingDeltaEvent(
|
|
672
|
-
accumulator.getCurrentBlock(),
|
|
673
|
-
delta.reasoning_content
|
|
674
|
-
));
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// Text content delta
|
|
678
|
-
if (delta.content) {
|
|
679
|
-
const currentBlock = accumulator.getCurrentBlock();
|
|
680
|
-
|
|
681
|
-
// Close thinking block if transitioning from thinking to text
|
|
682
|
-
if (currentBlock && currentBlock.type === 'thinking' && !currentBlock.stopped) {
|
|
683
|
-
const signatureEvent = this._createSignatureDeltaEvent(currentBlock);
|
|
684
|
-
if (signatureEvent) { // FIX: Handle null return from signature race guard
|
|
685
|
-
events.push(signatureEvent);
|
|
686
|
-
}
|
|
687
|
-
events.push(this._createContentBlockStopEvent(currentBlock));
|
|
688
|
-
accumulator.stopCurrentBlock();
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
if (!accumulator.getCurrentBlock() || accumulator.getCurrentBlock().type !== 'text') {
|
|
692
|
-
// Start text block
|
|
693
|
-
const block = accumulator.startBlock('text');
|
|
694
|
-
events.push(this._createContentBlockStartEvent(block));
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
accumulator.addDelta(delta.content);
|
|
698
|
-
events.push(this._createTextDeltaEvent(
|
|
699
|
-
accumulator.getCurrentBlock(),
|
|
700
|
-
delta.content
|
|
701
|
-
));
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// Check for planning loop after each thinking block completes
|
|
705
|
-
if (accumulator.checkForLoop()) {
|
|
706
|
-
this.log('WARNING: Planning loop detected - 3 consecutive thinking blocks with no tool calls');
|
|
707
|
-
this.log('Forcing early finalization to prevent unbounded planning');
|
|
708
|
-
|
|
709
|
-
// Close current block if any
|
|
710
|
-
const currentBlock = accumulator.getCurrentBlock();
|
|
711
|
-
if (currentBlock && !currentBlock.stopped) {
|
|
712
|
-
if (currentBlock.type === 'thinking') {
|
|
713
|
-
const signatureEvent = this._createSignatureDeltaEvent(currentBlock);
|
|
714
|
-
if (signatureEvent) { // FIX: Handle null return from signature race guard
|
|
715
|
-
events.push(signatureEvent);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
events.push(this._createContentBlockStopEvent(currentBlock));
|
|
719
|
-
accumulator.stopCurrentBlock();
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
// Force finalization
|
|
723
|
-
events.push(...this.finalizeDelta(accumulator));
|
|
724
|
-
return events;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// Tool calls deltas
|
|
728
|
-
if (delta.tool_calls && delta.tool_calls.length > 0) {
|
|
729
|
-
// Close current content block ONCE before processing any tool calls
|
|
730
|
-
const currentBlock = accumulator.getCurrentBlock();
|
|
731
|
-
if (currentBlock && !currentBlock.stopped) {
|
|
732
|
-
if (currentBlock.type === 'thinking') {
|
|
733
|
-
events.push(this._createSignatureDeltaEvent(currentBlock));
|
|
734
|
-
}
|
|
735
|
-
events.push(this._createContentBlockStopEvent(currentBlock));
|
|
736
|
-
accumulator.stopCurrentBlock();
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// Process each tool call delta
|
|
740
|
-
for (const toolCallDelta of delta.tool_calls) {
|
|
741
|
-
// Track tool call state
|
|
742
|
-
const isNewToolCall = !accumulator.toolCallsIndex[toolCallDelta.index];
|
|
743
|
-
accumulator.addToolCallDelta(toolCallDelta);
|
|
744
|
-
|
|
745
|
-
// Emit tool use events (start + input_json deltas)
|
|
746
|
-
if (isNewToolCall) {
|
|
747
|
-
// Start new tool_use block in accumulator
|
|
748
|
-
const block = accumulator.startBlock('tool_use');
|
|
749
|
-
const toolCall = accumulator.toolCallsIndex[toolCallDelta.index];
|
|
750
|
-
|
|
751
|
-
events.push({
|
|
752
|
-
event: 'content_block_start',
|
|
753
|
-
data: {
|
|
754
|
-
type: 'content_block_start',
|
|
755
|
-
index: block.index,
|
|
756
|
-
content_block: {
|
|
757
|
-
type: 'tool_use',
|
|
758
|
-
id: toolCall.id || `tool_${toolCallDelta.index}`,
|
|
759
|
-
name: toolCall.function.name || ''
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
// Emit input_json delta if arguments present
|
|
766
|
-
if (toolCallDelta.function?.arguments) {
|
|
767
|
-
const currentToolBlock = accumulator.getCurrentBlock();
|
|
768
|
-
if (currentToolBlock && currentToolBlock.type === 'tool_use') {
|
|
769
|
-
events.push({
|
|
770
|
-
event: 'content_block_delta',
|
|
771
|
-
data: {
|
|
772
|
-
type: 'content_block_delta',
|
|
773
|
-
index: currentToolBlock.index,
|
|
774
|
-
delta: {
|
|
775
|
-
type: 'input_json_delta',
|
|
776
|
-
partial_json: toolCallDelta.function.arguments
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
// Finish reason
|
|
786
|
-
if (choice.finish_reason) {
|
|
787
|
-
accumulator.finishReason = choice.finish_reason;
|
|
788
|
-
|
|
789
|
-
// If we have both finish_reason AND usage, finalize immediately
|
|
790
|
-
if (accumulator.usageReceived) {
|
|
791
|
-
events.push(...this.finalizeDelta(accumulator));
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// Debug logging for generated events
|
|
796
|
-
if (this.debugLog && events.length > 0) {
|
|
797
|
-
this._writeDebugLog('delta-anthropic-events', { events, accumulator: accumulator.getSummary() });
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
return events;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
/**
|
|
804
|
-
* Finalize streaming and generate closing events
|
|
805
|
-
* @param {DeltaAccumulator} accumulator - State accumulator
|
|
806
|
-
* @returns {Array<Object>} Final Anthropic SSE events
|
|
807
|
-
*/
|
|
808
|
-
finalizeDelta(accumulator) {
|
|
809
|
-
if (accumulator.finalized) {
|
|
810
|
-
return []; // Already finalized
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
const events = [];
|
|
814
|
-
|
|
815
|
-
// Close current content block if any (including tool_use blocks)
|
|
816
|
-
const currentBlock = accumulator.getCurrentBlock();
|
|
817
|
-
if (currentBlock && !currentBlock.stopped) {
|
|
818
|
-
if (currentBlock.type === 'thinking') {
|
|
819
|
-
const signatureEvent = this._createSignatureDeltaEvent(currentBlock);
|
|
820
|
-
if (signatureEvent) { // FIX: Handle null return from signature race guard
|
|
821
|
-
events.push(signatureEvent);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
events.push(this._createContentBlockStopEvent(currentBlock));
|
|
825
|
-
accumulator.stopCurrentBlock();
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
// No need to manually stop tool_use blocks - they're now tracked in contentBlocks
|
|
829
|
-
// and will be stopped by the logic above if they're the current block
|
|
830
|
-
|
|
831
|
-
// Message delta (stop reason + usage)
|
|
832
|
-
events.push({
|
|
833
|
-
event: 'message_delta',
|
|
834
|
-
data: {
|
|
835
|
-
type: 'message_delta',
|
|
836
|
-
delta: {
|
|
837
|
-
stop_reason: this._mapStopReason(accumulator.finishReason || 'stop')
|
|
838
|
-
},
|
|
839
|
-
usage: {
|
|
840
|
-
input_tokens: accumulator.inputTokens,
|
|
841
|
-
output_tokens: accumulator.outputTokens
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
});
|
|
845
|
-
|
|
846
|
-
// Message stop
|
|
847
|
-
events.push({
|
|
848
|
-
event: 'message_stop',
|
|
849
|
-
data: {
|
|
850
|
-
type: 'message_stop'
|
|
851
|
-
}
|
|
852
|
-
});
|
|
853
|
-
|
|
854
|
-
accumulator.finalized = true;
|
|
855
|
-
return events;
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
/**
|
|
859
|
-
* Create message_start event
|
|
860
|
-
* @private
|
|
861
|
-
*/
|
|
862
|
-
_createMessageStartEvent(accumulator) {
|
|
863
|
-
return {
|
|
864
|
-
event: 'message_start',
|
|
865
|
-
data: {
|
|
866
|
-
type: 'message_start',
|
|
867
|
-
message: {
|
|
868
|
-
id: accumulator.messageId,
|
|
869
|
-
type: 'message',
|
|
870
|
-
role: accumulator.role,
|
|
871
|
-
content: [],
|
|
872
|
-
model: accumulator.model || 'glm-4.6',
|
|
873
|
-
stop_reason: null,
|
|
874
|
-
usage: {
|
|
875
|
-
input_tokens: accumulator.inputTokens,
|
|
876
|
-
output_tokens: 0
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
};
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
/**
|
|
884
|
-
* Create content_block_start event
|
|
885
|
-
* @private
|
|
886
|
-
*/
|
|
887
|
-
_createContentBlockStartEvent(block) {
|
|
888
|
-
return {
|
|
889
|
-
event: 'content_block_start',
|
|
890
|
-
data: {
|
|
891
|
-
type: 'content_block_start',
|
|
892
|
-
index: block.index,
|
|
893
|
-
content_block: {
|
|
894
|
-
type: block.type,
|
|
895
|
-
[block.type]: ''
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
/**
|
|
902
|
-
* Create thinking_delta event
|
|
903
|
-
* @private
|
|
904
|
-
*/
|
|
905
|
-
_createThinkingDeltaEvent(block, delta) {
|
|
906
|
-
return {
|
|
907
|
-
event: 'content_block_delta',
|
|
908
|
-
data: {
|
|
909
|
-
type: 'content_block_delta',
|
|
910
|
-
index: block.index,
|
|
911
|
-
delta: {
|
|
912
|
-
type: 'thinking_delta',
|
|
913
|
-
thinking: delta
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
};
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
/**
|
|
920
|
-
* Create text_delta event
|
|
921
|
-
* @private
|
|
922
|
-
*/
|
|
923
|
-
_createTextDeltaEvent(block, delta) {
|
|
924
|
-
return {
|
|
925
|
-
event: 'content_block_delta',
|
|
926
|
-
data: {
|
|
927
|
-
type: 'content_block_delta',
|
|
928
|
-
index: block.index,
|
|
929
|
-
delta: {
|
|
930
|
-
type: 'text_delta',
|
|
931
|
-
text: delta
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
};
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
/**
|
|
938
|
-
* Create thinking signature delta event
|
|
939
|
-
* @private
|
|
940
|
-
*/
|
|
941
|
-
_createSignatureDeltaEvent(block) {
|
|
942
|
-
// FIX: Guard against empty content (signature timing race)
|
|
943
|
-
// In streaming mode, signature may be requested before content fully accumulated
|
|
944
|
-
if (!block.content || block.content.length === 0) {
|
|
945
|
-
if (this.verbose) {
|
|
946
|
-
this.log(`WARNING: Skipping signature for empty thinking block ${block.index}`);
|
|
947
|
-
this.log(`This indicates a race condition - signature requested before content accumulated`);
|
|
948
|
-
}
|
|
949
|
-
return null; // Return null instead of event
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
const signature = this._generateThinkingSignature(block.content);
|
|
953
|
-
|
|
954
|
-
// Enhanced logging for debugging
|
|
955
|
-
if (this.verbose) {
|
|
956
|
-
this.log(`Generating signature for block ${block.index}: ${block.content.length} chars`);
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
return {
|
|
960
|
-
event: 'content_block_delta',
|
|
961
|
-
data: {
|
|
962
|
-
type: 'content_block_delta',
|
|
963
|
-
index: block.index,
|
|
964
|
-
delta: {
|
|
965
|
-
type: 'thinking_signature_delta',
|
|
966
|
-
signature: signature
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
/**
|
|
973
|
-
* Create content_block_stop event
|
|
974
|
-
* @private
|
|
975
|
-
*/
|
|
976
|
-
_createContentBlockStopEvent(block) {
|
|
977
|
-
return {
|
|
978
|
-
event: 'content_block_stop',
|
|
979
|
-
data: {
|
|
980
|
-
type: 'content_block_stop',
|
|
981
|
-
index: block.index
|
|
982
|
-
}
|
|
983
|
-
};
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
/**
|
|
987
|
-
* Log message if verbose
|
|
988
|
-
* @param {string} message - Message to log
|
|
989
|
-
* @private
|
|
990
|
-
*/
|
|
991
|
-
log(message) {
|
|
992
|
-
if (this.verbose) {
|
|
993
|
-
const timestamp = new Date().toTimeString().split(' ')[0]; // HH:MM:SS
|
|
994
|
-
console.error(`[glmt-transformer] [${timestamp}] ${message}`);
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
module.exports = GlmtTransformer;
|