@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.
Files changed (262) hide show
  1. package/README.md +98 -7
  2. package/VERSION +1 -1
  3. package/config/base-agy.settings.json +10 -0
  4. package/config/base-codex.settings.json +10 -0
  5. package/config/base-gemini.settings.json +10 -0
  6. package/dist/auth/auth-commands.d.ts +52 -0
  7. package/dist/auth/auth-commands.d.ts.map +1 -0
  8. package/dist/auth/auth-commands.js +479 -0
  9. package/dist/auth/auth-commands.js.map +1 -0
  10. package/dist/auth/profile-detector.d.ts +68 -0
  11. package/dist/auth/profile-detector.d.ts.map +1 -0
  12. package/dist/auth/profile-detector.js +209 -0
  13. package/dist/auth/profile-detector.js.map +1 -0
  14. package/dist/auth/profile-registry.d.ts +60 -0
  15. package/dist/auth/profile-registry.d.ts.map +1 -0
  16. package/dist/auth/profile-registry.js +188 -0
  17. package/dist/auth/profile-registry.js.map +1 -0
  18. package/dist/ccs.d.ts +10 -0
  19. package/dist/ccs.d.ts.map +1 -0
  20. package/dist/ccs.js +320 -0
  21. package/dist/ccs.js.map +1 -0
  22. package/dist/cliproxy/auth-handler.d.ts +95 -0
  23. package/dist/cliproxy/auth-handler.d.ts.map +1 -0
  24. package/dist/cliproxy/auth-handler.js +443 -0
  25. package/dist/cliproxy/auth-handler.js.map +1 -0
  26. package/dist/cliproxy/base-config-loader.d.ts +42 -0
  27. package/dist/cliproxy/base-config-loader.d.ts.map +1 -0
  28. package/dist/cliproxy/base-config-loader.js +123 -0
  29. package/dist/cliproxy/base-config-loader.js.map +1 -0
  30. package/dist/cliproxy/binary-manager.d.ts +104 -0
  31. package/dist/cliproxy/binary-manager.d.ts.map +1 -0
  32. package/dist/cliproxy/binary-manager.js +567 -0
  33. package/dist/cliproxy/binary-manager.js.map +1 -0
  34. package/dist/cliproxy/cliproxy-executor.d.ts +33 -0
  35. package/dist/cliproxy/cliproxy-executor.d.ts.map +1 -0
  36. package/dist/cliproxy/cliproxy-executor.js +297 -0
  37. package/dist/cliproxy/cliproxy-executor.js.map +1 -0
  38. package/dist/cliproxy/config-generator.d.ts +89 -0
  39. package/dist/cliproxy/config-generator.d.ts.map +1 -0
  40. package/dist/cliproxy/config-generator.js +263 -0
  41. package/dist/cliproxy/config-generator.js.map +1 -0
  42. package/dist/cliproxy/index.d.ts +13 -0
  43. package/dist/cliproxy/index.d.ts.map +1 -0
  44. package/dist/cliproxy/index.js +62 -0
  45. package/dist/cliproxy/index.js.map +1 -0
  46. package/dist/cliproxy/platform-detector.d.ts +48 -0
  47. package/dist/cliproxy/platform-detector.d.ts.map +1 -0
  48. package/dist/cliproxy/platform-detector.js +118 -0
  49. package/dist/cliproxy/platform-detector.js.map +1 -0
  50. package/dist/cliproxy/types.d.ts +169 -0
  51. package/dist/cliproxy/types.d.ts.map +1 -0
  52. package/dist/cliproxy/types.js +7 -0
  53. package/dist/cliproxy/types.js.map +1 -0
  54. package/dist/commands/doctor-command.d.ts +10 -0
  55. package/dist/commands/doctor-command.d.ts.map +1 -0
  56. package/dist/commands/doctor-command.js +44 -0
  57. package/dist/commands/doctor-command.js.map +1 -0
  58. package/dist/commands/help-command.d.ts +5 -0
  59. package/dist/commands/help-command.d.ts.map +1 -0
  60. package/dist/commands/help-command.js +104 -0
  61. package/dist/commands/help-command.js.map +1 -0
  62. package/dist/commands/install-command.d.ts +14 -0
  63. package/dist/commands/install-command.d.ts.map +1 -0
  64. package/dist/commands/install-command.js +39 -0
  65. package/dist/commands/install-command.js.map +1 -0
  66. package/dist/commands/shell-completion-command.d.ts +10 -0
  67. package/dist/commands/shell-completion-command.d.ts.map +1 -0
  68. package/dist/commands/shell-completion-command.js +85 -0
  69. package/dist/commands/shell-completion-command.js.map +1 -0
  70. package/dist/commands/sync-command.d.ts +10 -0
  71. package/dist/commands/sync-command.d.ts.map +1 -0
  72. package/dist/commands/sync-command.js +59 -0
  73. package/dist/commands/sync-command.js.map +1 -0
  74. package/dist/commands/update-command.d.ts +12 -0
  75. package/dist/commands/update-command.d.ts.map +1 -0
  76. package/dist/commands/update-command.js +295 -0
  77. package/dist/commands/update-command.js.map +1 -0
  78. package/dist/commands/version-command.d.ts +10 -0
  79. package/dist/commands/version-command.d.ts.map +1 -0
  80. package/dist/commands/version-command.js +100 -0
  81. package/dist/commands/version-command.js.map +1 -0
  82. package/dist/delegation/delegation-handler.d.ts +60 -0
  83. package/dist/delegation/delegation-handler.d.ts.map +1 -0
  84. package/dist/delegation/delegation-handler.js +174 -0
  85. package/dist/delegation/delegation-handler.js.map +1 -0
  86. package/dist/delegation/headless-executor.d.ts +114 -0
  87. package/dist/delegation/headless-executor.d.ts.map +1 -0
  88. package/dist/delegation/headless-executor.js +562 -0
  89. package/dist/delegation/headless-executor.js.map +1 -0
  90. package/dist/delegation/result-formatter.d.ts +108 -0
  91. package/dist/delegation/result-formatter.d.ts.map +1 -0
  92. package/dist/delegation/result-formatter.js +391 -0
  93. package/dist/delegation/result-formatter.js.map +1 -0
  94. package/dist/delegation/session-manager.d.ts +58 -0
  95. package/dist/delegation/session-manager.d.ts.map +1 -0
  96. package/dist/delegation/session-manager.js +153 -0
  97. package/dist/delegation/session-manager.js.map +1 -0
  98. package/dist/delegation/settings-parser.d.ts +31 -0
  99. package/dist/delegation/settings-parser.d.ts.map +1 -0
  100. package/dist/delegation/settings-parser.js +107 -0
  101. package/dist/delegation/settings-parser.js.map +1 -0
  102. package/dist/glmt/delta-accumulator.d.ts +210 -0
  103. package/dist/glmt/delta-accumulator.d.ts.map +1 -0
  104. package/dist/glmt/delta-accumulator.js +351 -0
  105. package/dist/glmt/delta-accumulator.js.map +1 -0
  106. package/dist/glmt/glmt-proxy.d.ts +72 -0
  107. package/dist/glmt/glmt-proxy.d.ts.map +1 -0
  108. package/dist/glmt/glmt-proxy.js +427 -0
  109. package/dist/glmt/glmt-proxy.js.map +1 -0
  110. package/dist/glmt/glmt-transformer.d.ts +265 -0
  111. package/dist/glmt/glmt-transformer.d.ts.map +1 -0
  112. package/dist/glmt/glmt-transformer.js +832 -0
  113. package/dist/glmt/glmt-transformer.js.map +1 -0
  114. package/dist/glmt/locale-enforcer.d.ts +38 -0
  115. package/dist/glmt/locale-enforcer.d.ts.map +1 -0
  116. package/dist/glmt/locale-enforcer.js +69 -0
  117. package/dist/glmt/locale-enforcer.js.map +1 -0
  118. package/dist/glmt/reasoning-enforcer.d.ts +52 -0
  119. package/dist/glmt/reasoning-enforcer.d.ts.map +1 -0
  120. package/dist/glmt/reasoning-enforcer.js +151 -0
  121. package/dist/glmt/reasoning-enforcer.js.map +1 -0
  122. package/dist/glmt/sse-parser.d.ts +47 -0
  123. package/dist/glmt/sse-parser.d.ts.map +1 -0
  124. package/dist/glmt/sse-parser.js +93 -0
  125. package/dist/glmt/sse-parser.js.map +1 -0
  126. package/dist/management/doctor.d.ts +104 -0
  127. package/dist/management/doctor.d.ts.map +1 -0
  128. package/dist/management/doctor.js +673 -0
  129. package/dist/management/doctor.js.map +1 -0
  130. package/dist/management/instance-manager.d.ts +57 -0
  131. package/dist/management/instance-manager.d.ts.map +1 -0
  132. package/dist/management/instance-manager.js +195 -0
  133. package/dist/management/instance-manager.js.map +1 -0
  134. package/dist/management/recovery-manager.d.ts +39 -0
  135. package/dist/management/recovery-manager.d.ts.map +1 -0
  136. package/dist/management/recovery-manager.js +141 -0
  137. package/dist/management/recovery-manager.js.map +1 -0
  138. package/dist/management/shared-manager.d.ts +47 -0
  139. package/dist/management/shared-manager.d.ts.map +1 -0
  140. package/dist/management/shared-manager.js +388 -0
  141. package/dist/management/shared-manager.js.map +1 -0
  142. package/dist/types/cli.d.ts +50 -0
  143. package/dist/types/cli.d.ts.map +1 -0
  144. package/dist/types/cli.js +16 -0
  145. package/dist/types/cli.js.map +1 -0
  146. package/dist/types/config.d.ts +51 -0
  147. package/dist/types/config.d.ts.map +1 -0
  148. package/dist/types/config.js +26 -0
  149. package/dist/types/config.js.map +1 -0
  150. package/dist/types/delegation.d.ts +61 -0
  151. package/dist/types/delegation.d.ts.map +1 -0
  152. package/dist/types/delegation.js +6 -0
  153. package/dist/types/delegation.js.map +1 -0
  154. package/dist/types/glmt.d.ts +95 -0
  155. package/dist/types/glmt.d.ts.map +1 -0
  156. package/dist/types/glmt.js +7 -0
  157. package/dist/types/glmt.js.map +1 -0
  158. package/dist/types/index.d.ts +13 -0
  159. package/dist/types/index.d.ts.map +1 -0
  160. package/dist/types/index.js +16 -0
  161. package/dist/types/index.js.map +1 -0
  162. package/dist/types/utils.d.ts +36 -0
  163. package/dist/types/utils.d.ts.map +1 -0
  164. package/dist/types/utils.js +22 -0
  165. package/dist/types/utils.js.map +1 -0
  166. package/dist/utils/claude-detector.d.ts +14 -0
  167. package/dist/utils/claude-detector.d.ts.map +1 -0
  168. package/dist/utils/claude-detector.js +112 -0
  169. package/dist/utils/claude-detector.js.map +1 -0
  170. package/dist/utils/claude-dir-installer.d.ts +46 -0
  171. package/dist/utils/claude-dir-installer.d.ts.map +1 -0
  172. package/dist/utils/claude-dir-installer.js +289 -0
  173. package/dist/utils/claude-dir-installer.js.map +1 -0
  174. package/dist/utils/claude-symlink-manager.d.ts +61 -0
  175. package/dist/utils/claude-symlink-manager.d.ts.map +1 -0
  176. package/dist/utils/claude-symlink-manager.js +291 -0
  177. package/dist/utils/claude-symlink-manager.js.map +1 -0
  178. package/dist/utils/config-manager.d.ts +32 -0
  179. package/dist/utils/config-manager.d.ts.map +1 -0
  180. package/dist/utils/config-manager.js +143 -0
  181. package/dist/utils/config-manager.js.map +1 -0
  182. package/dist/utils/delegation-validator.d.ts +39 -0
  183. package/dist/utils/delegation-validator.d.ts.map +1 -0
  184. package/dist/utils/delegation-validator.js +161 -0
  185. package/dist/utils/delegation-validator.js.map +1 -0
  186. package/dist/utils/error-codes.d.ts +36 -0
  187. package/dist/utils/error-codes.d.ts.map +1 -0
  188. package/dist/utils/error-codes.js +63 -0
  189. package/dist/utils/error-codes.js.map +1 -0
  190. package/dist/utils/error-manager.d.ts +59 -0
  191. package/dist/utils/error-manager.d.ts.map +1 -0
  192. package/dist/utils/error-manager.js +228 -0
  193. package/dist/utils/error-manager.js.map +1 -0
  194. package/dist/utils/helpers.d.ts +27 -0
  195. package/dist/utils/helpers.d.ts.map +1 -0
  196. package/dist/utils/helpers.js +150 -0
  197. package/dist/utils/helpers.js.map +1 -0
  198. package/dist/utils/package-manager-detector.d.ts +14 -0
  199. package/dist/utils/package-manager-detector.d.ts.map +1 -0
  200. package/dist/utils/package-manager-detector.js +162 -0
  201. package/dist/utils/package-manager-detector.js.map +1 -0
  202. package/dist/utils/progress-indicator.d.ts +52 -0
  203. package/dist/utils/progress-indicator.d.ts.map +1 -0
  204. package/dist/utils/progress-indicator.js +102 -0
  205. package/dist/utils/progress-indicator.js.map +1 -0
  206. package/dist/utils/prompt.d.ts +29 -0
  207. package/dist/utils/prompt.d.ts.map +1 -0
  208. package/dist/utils/prompt.js +116 -0
  209. package/dist/utils/prompt.js.map +1 -0
  210. package/dist/utils/shell-completion.d.ts +52 -0
  211. package/dist/utils/shell-completion.d.ts.map +1 -0
  212. package/dist/utils/shell-completion.js +231 -0
  213. package/dist/utils/shell-completion.js.map +1 -0
  214. package/dist/utils/shell-executor.d.ts +15 -0
  215. package/dist/utils/shell-executor.d.ts.map +1 -0
  216. package/dist/utils/shell-executor.js +57 -0
  217. package/dist/utils/shell-executor.js.map +1 -0
  218. package/dist/utils/update-checker.d.ts +48 -0
  219. package/dist/utils/update-checker.d.ts.map +1 -0
  220. package/dist/utils/update-checker.js +241 -0
  221. package/dist/utils/update-checker.js.map +1 -0
  222. package/lib/ccs +21 -1907
  223. package/lib/ccs.ps1 +26 -1800
  224. package/lib/error-codes.ps1 +2 -1
  225. package/lib/prompt.ps1 +2 -2
  226. package/package.json +31 -11
  227. package/scripts/add-shebang.js +39 -0
  228. package/scripts/bump-version.sh +25 -37
  229. package/scripts/dev-install.sh +32 -11
  230. package/scripts/postinstall.js +29 -29
  231. package/bin/auth/auth-commands.js +0 -499
  232. package/bin/auth/profile-detector.js +0 -204
  233. package/bin/auth/profile-registry.js +0 -225
  234. package/bin/ccs.js +0 -1034
  235. package/bin/delegation/README.md +0 -191
  236. package/bin/delegation/delegation-handler.js +0 -212
  237. package/bin/delegation/headless-executor.js +0 -618
  238. package/bin/delegation/result-formatter.js +0 -485
  239. package/bin/delegation/session-manager.js +0 -157
  240. package/bin/delegation/settings-parser.js +0 -109
  241. package/bin/glmt/delta-accumulator.js +0 -276
  242. package/bin/glmt/glmt-proxy.js +0 -495
  243. package/bin/glmt/glmt-transformer.js +0 -999
  244. package/bin/glmt/locale-enforcer.js +0 -72
  245. package/bin/glmt/reasoning-enforcer.js +0 -173
  246. package/bin/glmt/sse-parser.js +0 -96
  247. package/bin/management/doctor.js +0 -721
  248. package/bin/management/instance-manager.js +0 -202
  249. package/bin/management/recovery-manager.js +0 -135
  250. package/bin/management/shared-manager.js +0 -402
  251. package/bin/utils/claude-detector.js +0 -73
  252. package/bin/utils/claude-dir-installer.js +0 -283
  253. package/bin/utils/claude-symlink-manager.js +0 -289
  254. package/bin/utils/config-manager.js +0 -103
  255. package/bin/utils/delegation-validator.js +0 -154
  256. package/bin/utils/error-codes.js +0 -59
  257. package/bin/utils/error-manager.js +0 -165
  258. package/bin/utils/helpers.js +0 -136
  259. package/bin/utils/progress-indicator.js +0 -111
  260. package/bin/utils/prompt.js +0 -134
  261. package/bin/utils/shell-completion.js +0 -256
  262. 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;