@hileeon/mcc 0.1.8 → 0.1.9

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 (86) hide show
  1. package/README.md +226 -127
  2. package/dist/accounts/store.d.ts +1 -0
  3. package/dist/accounts/store.d.ts.map +1 -1
  4. package/dist/accounts/store.js.map +1 -1
  5. package/dist/commands/launch.d.ts +9 -0
  6. package/dist/commands/launch.d.ts.map +1 -0
  7. package/dist/commands/launch.js +158 -0
  8. package/dist/commands/launch.js.map +1 -0
  9. package/dist/commands/mcp.d.ts +9 -0
  10. package/dist/commands/mcp.d.ts.map +1 -0
  11. package/dist/commands/mcp.js +112 -0
  12. package/dist/commands/mcp.js.map +1 -0
  13. package/dist/commands/profile.d.ts +8 -0
  14. package/dist/commands/profile.d.ts.map +1 -0
  15. package/dist/commands/profile.js +125 -0
  16. package/dist/commands/profile.js.map +1 -0
  17. package/dist/core/model-router.d.ts.map +1 -1
  18. package/dist/core/model-router.js +5 -2
  19. package/dist/core/model-router.js.map +1 -1
  20. package/dist/{dashboard-server.d.ts → dashboard/server.d.ts} +1 -1
  21. package/dist/dashboard/server.d.ts.map +1 -0
  22. package/dist/{dashboard-server.js → dashboard/server.js} +169 -51
  23. package/dist/dashboard/server.js.map +1 -0
  24. package/dist/mcc.d.ts +4 -2
  25. package/dist/mcc.d.ts.map +1 -1
  26. package/dist/mcc.js +121 -408
  27. package/dist/mcc.js.map +1 -1
  28. package/dist/mcp/mcp-config.d.ts +17 -1
  29. package/dist/mcp/mcp-config.d.ts.map +1 -1
  30. package/dist/mcp/mcp-config.js +50 -17
  31. package/dist/mcp/mcp-config.js.map +1 -1
  32. package/dist/proxy/proxy-daemon.d.ts.map +1 -1
  33. package/dist/proxy/proxy-daemon.js +17 -2
  34. package/dist/proxy/proxy-daemon.js.map +1 -1
  35. package/dist/proxy/proxy-entry.js +5 -3
  36. package/dist/proxy/proxy-entry.js.map +1 -1
  37. package/dist/proxy/proxy-server.d.ts.map +1 -1
  38. package/dist/proxy/proxy-server.js +32 -6
  39. package/dist/proxy/proxy-server.js.map +1 -1
  40. package/dist/shared/config.d.ts +15 -0
  41. package/dist/shared/config.d.ts.map +1 -0
  42. package/dist/shared/config.js +79 -0
  43. package/dist/shared/config.js.map +1 -0
  44. package/dist/shared/logger.d.ts +23 -18
  45. package/dist/shared/logger.d.ts.map +1 -1
  46. package/dist/shared/logger.js +17 -178
  47. package/dist/shared/logger.js.map +1 -1
  48. package/dist/shared/provider-preset-catalog.d.ts +6 -2
  49. package/dist/shared/provider-preset-catalog.d.ts.map +1 -1
  50. package/dist/shared/provider-preset-catalog.js +47 -26
  51. package/dist/shared/provider-preset-catalog.js.map +1 -1
  52. package/dist/ui/assets/index-ClqmrjNk.js +40 -0
  53. package/dist/ui/assets/index-CwMwQ-Z4.css +1 -0
  54. package/dist/ui/index.html +21 -13
  55. package/dist/update.d.ts +31 -0
  56. package/dist/update.d.ts.map +1 -0
  57. package/dist/update.js +196 -0
  58. package/dist/update.js.map +1 -0
  59. package/lib/mcp/mcc-image-analysis-server.cjs +454 -454
  60. package/lib/mcp/mcc-websearch-server.cjs +339 -339
  61. package/lib/mcp-hooks/image-analysis-runtime.cjs +510 -510
  62. package/lib/mcp-hooks/image-analyzer-transformer.cjs +526 -526
  63. package/lib/mcp-hooks/websearch-transformer.cjs +1597 -1421
  64. package/lib/proxy/config/config-loader-facade.js +24 -24
  65. package/lib/proxy/glmt/delta-accumulator.js +362 -362
  66. package/lib/proxy/glmt/glmt-transformer.js +203 -203
  67. package/lib/proxy/glmt/index.js +40 -40
  68. package/lib/proxy/glmt/locale-enforcer.js +68 -68
  69. package/lib/proxy/glmt/pipeline/content-transformer.js +161 -161
  70. package/lib/proxy/glmt/pipeline/index.js +19 -19
  71. package/lib/proxy/glmt/pipeline/request-transformer.js +115 -115
  72. package/lib/proxy/glmt/pipeline/response-builder.js +204 -204
  73. package/lib/proxy/glmt/pipeline/stream-parser.js +233 -233
  74. package/lib/proxy/glmt/pipeline/tool-call-handler.js +77 -77
  75. package/lib/proxy/glmt/pipeline/types.js +5 -5
  76. package/lib/proxy/glmt/reasoning-enforcer.js +150 -150
  77. package/lib/proxy/glmt/sse-parser.js +101 -101
  78. package/lib/proxy/services/logging.js +13 -13
  79. package/lib/proxy/transformers/request-transformer.js +471 -471
  80. package/lib/proxy/transformers/sse-stream-transformer.js +198 -198
  81. package/lib/shared/logger.cjs +156 -138
  82. package/package.json +58 -41
  83. package/dist/dashboard-server.d.ts.map +0 -1
  84. package/dist/dashboard-server.js.map +0 -1
  85. package/dist/ui/assets/index-B16lhKZ6.js +0 -40
  86. package/dist/ui/assets/index-jEfiB6-h.css +0 -1
@@ -1,363 +1,363 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- /**
4
- * DeltaAccumulator - Maintain state across streaming deltas
5
- *
6
- * Tracks:
7
- * - Message metadata (id, model, role)
8
- * - Content blocks (thinking, text)
9
- * - Current block index
10
- * - Accumulated content
11
- *
12
- * Usage:
13
- * const acc = new DeltaAccumulator(thinkingConfig);
14
- * const events = transformer.transformDelta(openaiEvent, acc);
15
- */
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.DeltaAccumulator = void 0;
18
- class DeltaAccumulator {
19
- constructor(_thinkingConfig = {}, options = {}) {
20
- // ========== Finish Reason ==========
21
- this.finishReason = null;
22
- this.usageReceived = false;
23
- this.messageId = 'msg_' + Date.now() + '_' + Math.random().toString(36).substring(7);
24
- this.model = null;
25
- this.role = 'assistant';
26
- // Content blocks
27
- this.contentBlocks = [];
28
- this.currentBlockIndex = -1;
29
- // Tool calls tracking
30
- this.toolCalls = [];
31
- this.toolCallsIndex = {};
32
- // Buffers
33
- this.thinkingBuffer = '';
34
- this.textBuffer = '';
35
- // C-02 Fix: Limits to prevent unbounded accumulation
36
- this.maxBlocks = options.maxBlocks || 100;
37
- this.maxBufferSize = options.maxBufferSize || 10 * 1024 * 1024; // 10MB
38
- // Loop detection configuration
39
- this.loopDetectionThreshold = options.loopDetectionThreshold || 3;
40
- this.loopDetected = false;
41
- // State flags
42
- this.messageStarted = false;
43
- this.finalized = false;
44
- // Statistics
45
- this.inputTokens = 0;
46
- this.outputTokens = 0;
47
- }
48
- /**
49
- * Get current content block
50
- * @returns Current block or null
51
- */
52
- getCurrentBlock() {
53
- if (this.currentBlockIndex >= 0 && this.currentBlockIndex < this.contentBlocks.length) {
54
- return this.contentBlocks[this.currentBlockIndex];
55
- }
56
- return null;
57
- }
58
- /**
59
- * Start new content block
60
- * @param type - Block type ('thinking', 'text', or 'tool_use')
61
- * @returns New block
62
- */
63
- startBlock(type) {
64
- // C-02 Fix: Enforce max blocks limit
65
- if (this.contentBlocks.length >= this.maxBlocks) {
66
- throw new Error(`Maximum ${this.maxBlocks} content blocks exceeded (DoS protection)`);
67
- }
68
- this.currentBlockIndex++;
69
- const block = {
70
- index: this.currentBlockIndex,
71
- type: type,
72
- content: '',
73
- started: true,
74
- stopped: false,
75
- };
76
- this.contentBlocks.push(block);
77
- // Reset buffer for new block (tool_use doesn't use buffers)
78
- if (type === 'thinking') {
79
- this.thinkingBuffer = '';
80
- }
81
- else if (type === 'text') {
82
- this.textBuffer = '';
83
- }
84
- return block;
85
- }
86
- /**
87
- * Add delta to current block
88
- * @param delta - Content delta
89
- */
90
- addDelta(delta) {
91
- const block = this.getCurrentBlock();
92
- if (!block) {
93
- // FIX: Guard against null block (should never happen, but defensive)
94
- console.error('[DeltaAccumulator] ERROR: addDelta called with no current block');
95
- return;
96
- }
97
- if (block.type === 'thinking') {
98
- // C-02 Fix: Enforce buffer size limit
99
- if (this.thinkingBuffer.length + delta.length > this.maxBufferSize) {
100
- throw new Error(`Thinking buffer exceeded ${this.maxBufferSize} bytes (DoS protection)`);
101
- }
102
- this.thinkingBuffer += delta;
103
- block.content = this.thinkingBuffer;
104
- // FIX: Verify assignment succeeded (paranoid check for race conditions)
105
- if (block.content.length !== this.thinkingBuffer.length) {
106
- console.error('[DeltaAccumulator] ERROR: Block content assignment failed');
107
- console.error(`Expected: ${this.thinkingBuffer.length}, Got: ${block.content.length}`);
108
- }
109
- }
110
- else if (block.type === 'text') {
111
- // C-02 Fix: Enforce buffer size limit
112
- if (this.textBuffer.length + delta.length > this.maxBufferSize) {
113
- throw new Error(`Text buffer exceeded ${this.maxBufferSize} bytes (DoS protection)`);
114
- }
115
- this.textBuffer += delta;
116
- block.content = this.textBuffer;
117
- }
118
- }
119
- /**
120
- * Mark current block as stopped
121
- */
122
- stopCurrentBlock() {
123
- const block = this.getCurrentBlock();
124
- if (block) {
125
- block.stopped = true;
126
- // FIX: Log block closure for debugging (helps diagnose timing issues)
127
- if (block.type === 'thinking' && process.env.CCS_DEBUG === '1') {
128
- console.error(`[DeltaAccumulator] Stopped thinking block ${block.index}: ${block.content?.length || 0} chars`);
129
- }
130
- }
131
- }
132
- /**
133
- * Update usage statistics
134
- * @param usage - Usage object from OpenAI
135
- */
136
- updateUsage(usage) {
137
- if (usage) {
138
- this.inputTokens = usage.prompt_tokens || usage.input_tokens || 0;
139
- this.outputTokens = usage.completion_tokens || usage.output_tokens || 0;
140
- }
141
- }
142
- /**
143
- * Add or update tool call delta
144
- * @param toolCallDelta - Tool call delta from OpenAI
145
- */
146
- addToolCallDelta(toolCallDelta) {
147
- const index = toolCallDelta.index;
148
- if (!this.toolCallsIndex[index]) {
149
- const toolCall = {
150
- index: index,
151
- id: '',
152
- type: 'function',
153
- function: {
154
- name: '',
155
- arguments: '',
156
- },
157
- blockIndex: -1,
158
- };
159
- this.toolCalls.push(toolCall);
160
- this.toolCallsIndex[index] = toolCall;
161
- }
162
- const toolCall = this.toolCallsIndex[index];
163
- if (toolCallDelta.id) {
164
- toolCall.id = toolCallDelta.id;
165
- }
166
- if (toolCallDelta.type) {
167
- toolCall.type = toolCallDelta.type;
168
- }
169
- if (toolCallDelta.function?.name) {
170
- toolCall.function.name += toolCallDelta.function.name;
171
- }
172
- if (toolCallDelta.function?.arguments) {
173
- toolCall.function.arguments += toolCallDelta.function.arguments;
174
- }
175
- }
176
- setToolCallBlockIndex(toolCallIndex, blockIndex) {
177
- const toolCall = this.toolCallsIndex[toolCallIndex];
178
- if (toolCall) {
179
- toolCall.blockIndex = blockIndex;
180
- }
181
- }
182
- getToolCallBlockIndex(toolCallIndex) {
183
- const toolCall = this.toolCallsIndex[toolCallIndex];
184
- if (!toolCall || toolCall.blockIndex < 0) {
185
- throw new Error(`Tool call ${toolCallIndex} does not have an assigned content block`);
186
- }
187
- return toolCall.blockIndex;
188
- }
189
- getUnstoppedBlocks() {
190
- return this.contentBlocks.filter((b) => !b.stopped);
191
- }
192
- /**
193
- * Get all tool calls
194
- * @returns Tool calls array
195
- */
196
- getToolCalls() {
197
- return this.toolCalls;
198
- }
199
- /**
200
- * Check for planning loop pattern
201
- * Loop = N consecutive thinking blocks with no tool calls
202
- * @returns True if loop detected
203
- */
204
- checkForLoop() {
205
- // Already detected loop
206
- if (this.loopDetected) {
207
- return true;
208
- }
209
- // Need minimum blocks to detect pattern
210
- if (this.contentBlocks.length < this.loopDetectionThreshold) {
211
- return false;
212
- }
213
- // Get last N blocks
214
- const recentBlocks = this.contentBlocks.slice(-this.loopDetectionThreshold);
215
- // Check if all recent blocks are thinking blocks
216
- const allThinking = recentBlocks.every((b) => b.type === 'thinking');
217
- // Check if no tool calls have been made at all
218
- const noToolCalls = this.toolCalls.length === 0;
219
- // Loop detected if: all recent blocks are thinking AND no tool calls yet
220
- if (allThinking && noToolCalls) {
221
- this.loopDetected = true;
222
- return true;
223
- }
224
- return false;
225
- }
226
- /**
227
- * Reset loop detection state (for testing)
228
- */
229
- resetLoopDetection() {
230
- this.loopDetected = false;
231
- }
232
- /**
233
- * Get summary of accumulated state
234
- * @returns Summary
235
- */
236
- getSummary() {
237
- return {
238
- messageId: this.messageId,
239
- model: this.model,
240
- role: this.role,
241
- blockCount: this.contentBlocks.length,
242
- currentIndex: this.currentBlockIndex,
243
- toolCallCount: this.toolCalls.length,
244
- messageStarted: this.messageStarted,
245
- finalized: this.finalized,
246
- loopDetected: this.loopDetected,
247
- usage: {
248
- input_tokens: this.inputTokens,
249
- output_tokens: this.outputTokens,
250
- },
251
- };
252
- }
253
- // ========== State Getters ==========
254
- /**
255
- * Check if message has been finalized
256
- */
257
- isFinalized() {
258
- return this.finalized;
259
- }
260
- /**
261
- * Check if message has started
262
- */
263
- isMessageStarted() {
264
- return this.messageStarted;
265
- }
266
- /**
267
- * Get message ID
268
- */
269
- getMessageId() {
270
- return this.messageId;
271
- }
272
- /**
273
- * Get model name
274
- */
275
- getModel() {
276
- return this.model;
277
- }
278
- /**
279
- * Get role
280
- */
281
- getRole() {
282
- return this.role;
283
- }
284
- /**
285
- * Get input tokens
286
- */
287
- getInputTokens() {
288
- return this.inputTokens;
289
- }
290
- /**
291
- * Get output tokens
292
- */
293
- getOutputTokens() {
294
- return this.outputTokens;
295
- }
296
- // ========== State Setters ==========
297
- /**
298
- * Set model name
299
- */
300
- setModel(model) {
301
- this.model = model;
302
- }
303
- /**
304
- * Set message started flag
305
- */
306
- setMessageStarted(started) {
307
- this.messageStarted = started;
308
- }
309
- /**
310
- * Set role
311
- */
312
- setRole(role) {
313
- this.role = role;
314
- }
315
- /**
316
- * Set finalized flag
317
- */
318
- setFinalized(finalized) {
319
- this.finalized = finalized;
320
- }
321
- /**
322
- * Set finish reason
323
- */
324
- setFinishReason(reason) {
325
- this.finishReason = reason;
326
- }
327
- /**
328
- * Get finish reason
329
- */
330
- getFinishReason() {
331
- return this.finishReason;
332
- }
333
- /**
334
- * Check if usage stats have been received
335
- */
336
- hasUsageReceived() {
337
- return this.usageReceived;
338
- }
339
- /**
340
- * Mark usage as received
341
- */
342
- setUsageReceived(received) {
343
- this.usageReceived = received;
344
- }
345
- // ========== Tool Call Helpers ==========
346
- /**
347
- * Check if there are any tool calls, or check if a specific index exists
348
- */
349
- hasToolCall(index) {
350
- if (index === undefined) {
351
- return this.toolCalls.length > 0;
352
- }
353
- return this.toolCallsIndex[index] !== undefined;
354
- }
355
- /**
356
- * Get tool call by index
357
- */
358
- getToolCall(index) {
359
- return this.toolCallsIndex[index];
360
- }
361
- }
362
- exports.DeltaAccumulator = DeltaAccumulator;
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * DeltaAccumulator - Maintain state across streaming deltas
5
+ *
6
+ * Tracks:
7
+ * - Message metadata (id, model, role)
8
+ * - Content blocks (thinking, text)
9
+ * - Current block index
10
+ * - Accumulated content
11
+ *
12
+ * Usage:
13
+ * const acc = new DeltaAccumulator(thinkingConfig);
14
+ * const events = transformer.transformDelta(openaiEvent, acc);
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.DeltaAccumulator = void 0;
18
+ class DeltaAccumulator {
19
+ constructor(_thinkingConfig = {}, options = {}) {
20
+ // ========== Finish Reason ==========
21
+ this.finishReason = null;
22
+ this.usageReceived = false;
23
+ this.messageId = 'msg_' + Date.now() + '_' + Math.random().toString(36).substring(7);
24
+ this.model = null;
25
+ this.role = 'assistant';
26
+ // Content blocks
27
+ this.contentBlocks = [];
28
+ this.currentBlockIndex = -1;
29
+ // Tool calls tracking
30
+ this.toolCalls = [];
31
+ this.toolCallsIndex = {};
32
+ // Buffers
33
+ this.thinkingBuffer = '';
34
+ this.textBuffer = '';
35
+ // C-02 Fix: Limits to prevent unbounded accumulation
36
+ this.maxBlocks = options.maxBlocks || 100;
37
+ this.maxBufferSize = options.maxBufferSize || 10 * 1024 * 1024; // 10MB
38
+ // Loop detection configuration
39
+ this.loopDetectionThreshold = options.loopDetectionThreshold || 3;
40
+ this.loopDetected = false;
41
+ // State flags
42
+ this.messageStarted = false;
43
+ this.finalized = false;
44
+ // Statistics
45
+ this.inputTokens = 0;
46
+ this.outputTokens = 0;
47
+ }
48
+ /**
49
+ * Get current content block
50
+ * @returns Current block or null
51
+ */
52
+ getCurrentBlock() {
53
+ if (this.currentBlockIndex >= 0 && this.currentBlockIndex < this.contentBlocks.length) {
54
+ return this.contentBlocks[this.currentBlockIndex];
55
+ }
56
+ return null;
57
+ }
58
+ /**
59
+ * Start new content block
60
+ * @param type - Block type ('thinking', 'text', or 'tool_use')
61
+ * @returns New block
62
+ */
63
+ startBlock(type) {
64
+ // C-02 Fix: Enforce max blocks limit
65
+ if (this.contentBlocks.length >= this.maxBlocks) {
66
+ throw new Error(`Maximum ${this.maxBlocks} content blocks exceeded (DoS protection)`);
67
+ }
68
+ this.currentBlockIndex++;
69
+ const block = {
70
+ index: this.currentBlockIndex,
71
+ type: type,
72
+ content: '',
73
+ started: true,
74
+ stopped: false,
75
+ };
76
+ this.contentBlocks.push(block);
77
+ // Reset buffer for new block (tool_use doesn't use buffers)
78
+ if (type === 'thinking') {
79
+ this.thinkingBuffer = '';
80
+ }
81
+ else if (type === 'text') {
82
+ this.textBuffer = '';
83
+ }
84
+ return block;
85
+ }
86
+ /**
87
+ * Add delta to current block
88
+ * @param delta - Content delta
89
+ */
90
+ addDelta(delta) {
91
+ const block = this.getCurrentBlock();
92
+ if (!block) {
93
+ // FIX: Guard against null block (should never happen, but defensive)
94
+ console.error('[DeltaAccumulator] ERROR: addDelta called with no current block');
95
+ return;
96
+ }
97
+ if (block.type === 'thinking') {
98
+ // C-02 Fix: Enforce buffer size limit
99
+ if (this.thinkingBuffer.length + delta.length > this.maxBufferSize) {
100
+ throw new Error(`Thinking buffer exceeded ${this.maxBufferSize} bytes (DoS protection)`);
101
+ }
102
+ this.thinkingBuffer += delta;
103
+ block.content = this.thinkingBuffer;
104
+ // FIX: Verify assignment succeeded (paranoid check for race conditions)
105
+ if (block.content.length !== this.thinkingBuffer.length) {
106
+ console.error('[DeltaAccumulator] ERROR: Block content assignment failed');
107
+ console.error(`Expected: ${this.thinkingBuffer.length}, Got: ${block.content.length}`);
108
+ }
109
+ }
110
+ else if (block.type === 'text') {
111
+ // C-02 Fix: Enforce buffer size limit
112
+ if (this.textBuffer.length + delta.length > this.maxBufferSize) {
113
+ throw new Error(`Text buffer exceeded ${this.maxBufferSize} bytes (DoS protection)`);
114
+ }
115
+ this.textBuffer += delta;
116
+ block.content = this.textBuffer;
117
+ }
118
+ }
119
+ /**
120
+ * Mark current block as stopped
121
+ */
122
+ stopCurrentBlock() {
123
+ const block = this.getCurrentBlock();
124
+ if (block) {
125
+ block.stopped = true;
126
+ // FIX: Log block closure for debugging (helps diagnose timing issues)
127
+ if (block.type === 'thinking' && process.env.CCS_DEBUG === '1') {
128
+ console.error(`[DeltaAccumulator] Stopped thinking block ${block.index}: ${block.content?.length || 0} chars`);
129
+ }
130
+ }
131
+ }
132
+ /**
133
+ * Update usage statistics
134
+ * @param usage - Usage object from OpenAI
135
+ */
136
+ updateUsage(usage) {
137
+ if (usage) {
138
+ this.inputTokens = usage.prompt_tokens || usage.input_tokens || 0;
139
+ this.outputTokens = usage.completion_tokens || usage.output_tokens || 0;
140
+ }
141
+ }
142
+ /**
143
+ * Add or update tool call delta
144
+ * @param toolCallDelta - Tool call delta from OpenAI
145
+ */
146
+ addToolCallDelta(toolCallDelta) {
147
+ const index = toolCallDelta.index;
148
+ if (!this.toolCallsIndex[index]) {
149
+ const toolCall = {
150
+ index: index,
151
+ id: '',
152
+ type: 'function',
153
+ function: {
154
+ name: '',
155
+ arguments: '',
156
+ },
157
+ blockIndex: -1,
158
+ };
159
+ this.toolCalls.push(toolCall);
160
+ this.toolCallsIndex[index] = toolCall;
161
+ }
162
+ const toolCall = this.toolCallsIndex[index];
163
+ if (toolCallDelta.id) {
164
+ toolCall.id = toolCallDelta.id;
165
+ }
166
+ if (toolCallDelta.type) {
167
+ toolCall.type = toolCallDelta.type;
168
+ }
169
+ if (toolCallDelta.function?.name) {
170
+ toolCall.function.name += toolCallDelta.function.name;
171
+ }
172
+ if (toolCallDelta.function?.arguments) {
173
+ toolCall.function.arguments += toolCallDelta.function.arguments;
174
+ }
175
+ }
176
+ setToolCallBlockIndex(toolCallIndex, blockIndex) {
177
+ const toolCall = this.toolCallsIndex[toolCallIndex];
178
+ if (toolCall) {
179
+ toolCall.blockIndex = blockIndex;
180
+ }
181
+ }
182
+ getToolCallBlockIndex(toolCallIndex) {
183
+ const toolCall = this.toolCallsIndex[toolCallIndex];
184
+ if (!toolCall || toolCall.blockIndex < 0) {
185
+ throw new Error(`Tool call ${toolCallIndex} does not have an assigned content block`);
186
+ }
187
+ return toolCall.blockIndex;
188
+ }
189
+ getUnstoppedBlocks() {
190
+ return this.contentBlocks.filter((b) => !b.stopped);
191
+ }
192
+ /**
193
+ * Get all tool calls
194
+ * @returns Tool calls array
195
+ */
196
+ getToolCalls() {
197
+ return this.toolCalls;
198
+ }
199
+ /**
200
+ * Check for planning loop pattern
201
+ * Loop = N consecutive thinking blocks with no tool calls
202
+ * @returns True if loop detected
203
+ */
204
+ checkForLoop() {
205
+ // Already detected loop
206
+ if (this.loopDetected) {
207
+ return true;
208
+ }
209
+ // Need minimum blocks to detect pattern
210
+ if (this.contentBlocks.length < this.loopDetectionThreshold) {
211
+ return false;
212
+ }
213
+ // Get last N blocks
214
+ const recentBlocks = this.contentBlocks.slice(-this.loopDetectionThreshold);
215
+ // Check if all recent blocks are thinking blocks
216
+ const allThinking = recentBlocks.every((b) => b.type === 'thinking');
217
+ // Check if no tool calls have been made at all
218
+ const noToolCalls = this.toolCalls.length === 0;
219
+ // Loop detected if: all recent blocks are thinking AND no tool calls yet
220
+ if (allThinking && noToolCalls) {
221
+ this.loopDetected = true;
222
+ return true;
223
+ }
224
+ return false;
225
+ }
226
+ /**
227
+ * Reset loop detection state (for testing)
228
+ */
229
+ resetLoopDetection() {
230
+ this.loopDetected = false;
231
+ }
232
+ /**
233
+ * Get summary of accumulated state
234
+ * @returns Summary
235
+ */
236
+ getSummary() {
237
+ return {
238
+ messageId: this.messageId,
239
+ model: this.model,
240
+ role: this.role,
241
+ blockCount: this.contentBlocks.length,
242
+ currentIndex: this.currentBlockIndex,
243
+ toolCallCount: this.toolCalls.length,
244
+ messageStarted: this.messageStarted,
245
+ finalized: this.finalized,
246
+ loopDetected: this.loopDetected,
247
+ usage: {
248
+ input_tokens: this.inputTokens,
249
+ output_tokens: this.outputTokens,
250
+ },
251
+ };
252
+ }
253
+ // ========== State Getters ==========
254
+ /**
255
+ * Check if message has been finalized
256
+ */
257
+ isFinalized() {
258
+ return this.finalized;
259
+ }
260
+ /**
261
+ * Check if message has started
262
+ */
263
+ isMessageStarted() {
264
+ return this.messageStarted;
265
+ }
266
+ /**
267
+ * Get message ID
268
+ */
269
+ getMessageId() {
270
+ return this.messageId;
271
+ }
272
+ /**
273
+ * Get model name
274
+ */
275
+ getModel() {
276
+ return this.model;
277
+ }
278
+ /**
279
+ * Get role
280
+ */
281
+ getRole() {
282
+ return this.role;
283
+ }
284
+ /**
285
+ * Get input tokens
286
+ */
287
+ getInputTokens() {
288
+ return this.inputTokens;
289
+ }
290
+ /**
291
+ * Get output tokens
292
+ */
293
+ getOutputTokens() {
294
+ return this.outputTokens;
295
+ }
296
+ // ========== State Setters ==========
297
+ /**
298
+ * Set model name
299
+ */
300
+ setModel(model) {
301
+ this.model = model;
302
+ }
303
+ /**
304
+ * Set message started flag
305
+ */
306
+ setMessageStarted(started) {
307
+ this.messageStarted = started;
308
+ }
309
+ /**
310
+ * Set role
311
+ */
312
+ setRole(role) {
313
+ this.role = role;
314
+ }
315
+ /**
316
+ * Set finalized flag
317
+ */
318
+ setFinalized(finalized) {
319
+ this.finalized = finalized;
320
+ }
321
+ /**
322
+ * Set finish reason
323
+ */
324
+ setFinishReason(reason) {
325
+ this.finishReason = reason;
326
+ }
327
+ /**
328
+ * Get finish reason
329
+ */
330
+ getFinishReason() {
331
+ return this.finishReason;
332
+ }
333
+ /**
334
+ * Check if usage stats have been received
335
+ */
336
+ hasUsageReceived() {
337
+ return this.usageReceived;
338
+ }
339
+ /**
340
+ * Mark usage as received
341
+ */
342
+ setUsageReceived(received) {
343
+ this.usageReceived = received;
344
+ }
345
+ // ========== Tool Call Helpers ==========
346
+ /**
347
+ * Check if there are any tool calls, or check if a specific index exists
348
+ */
349
+ hasToolCall(index) {
350
+ if (index === undefined) {
351
+ return this.toolCalls.length > 0;
352
+ }
353
+ return this.toolCallsIndex[index] !== undefined;
354
+ }
355
+ /**
356
+ * Get tool call by index
357
+ */
358
+ getToolCall(index) {
359
+ return this.toolCallsIndex[index];
360
+ }
361
+ }
362
+ exports.DeltaAccumulator = DeltaAccumulator;
363
363
  //# sourceMappingURL=delta-accumulator.js.map