@a1hvdy/cc-openclaw 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/src/command-router/cc-handler.js +11 -3
  2. package/dist/src/command-router/cc-handler.js.map +1 -1
  3. package/dist/src/engines/persistent-session.d.ts +1 -0
  4. package/dist/src/engines/persistent-session.js +35 -1
  5. package/dist/src/engines/persistent-session.js.map +1 -1
  6. package/dist/src/index.d.ts +10 -1
  7. package/dist/src/index.js +47 -7
  8. package/dist/src/index.js.map +1 -1
  9. package/dist/src/lib/config-service.d.ts +106 -0
  10. package/dist/src/lib/config-service.js +217 -0
  11. package/dist/src/lib/config-service.js.map +1 -0
  12. package/dist/src/lib/config.d.ts +33 -14
  13. package/dist/src/lib/config.js +147 -34
  14. package/dist/src/lib/config.js.map +1 -1
  15. package/dist/src/lib/index.d.ts +1 -1
  16. package/dist/src/lib/index.js +4 -1
  17. package/dist/src/lib/index.js.map +1 -1
  18. package/dist/src/openai-compat/message-extractor.d.ts +79 -0
  19. package/dist/src/openai-compat/message-extractor.js +134 -0
  20. package/dist/src/openai-compat/message-extractor.js.map +1 -0
  21. package/dist/src/openai-compat/mode-flags.d.ts +34 -0
  22. package/dist/src/openai-compat/mode-flags.js +44 -0
  23. package/dist/src/openai-compat/mode-flags.js.map +1 -0
  24. package/dist/src/openai-compat/non-streaming-handler.d.ts +26 -0
  25. package/dist/src/openai-compat/non-streaming-handler.js +108 -0
  26. package/dist/src/openai-compat/non-streaming-handler.js.map +1 -0
  27. package/dist/src/openai-compat/openai-compat.d.ts +15 -166
  28. package/dist/src/openai-compat/openai-compat.js +72 -817
  29. package/dist/src/openai-compat/openai-compat.js.map +1 -1
  30. package/dist/src/openai-compat/prompts.d.ts +47 -0
  31. package/dist/src/openai-compat/prompts.js +119 -0
  32. package/dist/src/openai-compat/prompts.js.map +1 -0
  33. package/dist/src/openai-compat/response-formatter.d.ts +33 -0
  34. package/dist/src/openai-compat/response-formatter.js +74 -0
  35. package/dist/src/openai-compat/response-formatter.js.map +1 -0
  36. package/dist/src/openai-compat/session-key-resolver.d.ts +41 -0
  37. package/dist/src/openai-compat/session-key-resolver.js +78 -0
  38. package/dist/src/openai-compat/session-key-resolver.js.map +1 -0
  39. package/dist/src/openai-compat/status-reporter.d.ts +30 -0
  40. package/dist/src/openai-compat/status-reporter.js +81 -0
  41. package/dist/src/openai-compat/status-reporter.js.map +1 -0
  42. package/dist/src/openai-compat/streaming-handler.d.ts +41 -0
  43. package/dist/src/openai-compat/streaming-handler.js +294 -0
  44. package/dist/src/openai-compat/streaming-handler.js.map +1 -0
  45. package/dist/src/openai-compat/tool-calls-parser.d.ts +34 -0
  46. package/dist/src/openai-compat/tool-calls-parser.js +93 -0
  47. package/dist/src/openai-compat/tool-calls-parser.js.map +1 -0
  48. package/dist/src/openai-compat/tool-results-serializer.d.ts +60 -0
  49. package/dist/src/openai-compat/tool-results-serializer.js +56 -0
  50. package/dist/src/openai-compat/tool-results-serializer.js.map +1 -0
  51. package/dist/src/session/session-manager.js +12 -0
  52. package/dist/src/session/session-manager.js.map +1 -1
  53. package/dist/src/session-bootstrap/cwd-patch.js +30 -13
  54. package/dist/src/session-bootstrap/cwd-patch.js.map +1 -1
  55. package/dist/src/types/index.d.ts +15 -0
  56. package/dist/src/types/index.js +16 -0
  57. package/dist/src/types/index.js.map +1 -0
  58. package/dist/src/types/route.d.ts +41 -0
  59. package/dist/src/types/route.js +12 -0
  60. package/dist/src/types/route.js.map +1 -0
  61. package/dist/src/types/runtime-config.d.ts +161 -0
  62. package/dist/src/types/runtime-config.js +118 -0
  63. package/dist/src/types/runtime-config.js.map +1 -0
  64. package/dist/src/types/session.d.ts +48 -0
  65. package/dist/src/types/session.js +20 -0
  66. package/dist/src/types/session.js.map +1 -0
  67. package/dist/src/types/sse.d.ts +38 -0
  68. package/dist/src/types/sse.js +12 -0
  69. package/dist/src/types/sse.js.map +1 -0
  70. package/dist/src/types/tool-bridge.d.ts +81 -0
  71. package/dist/src/types/tool-bridge.js +34 -0
  72. package/dist/src/types/tool-bridge.js.map +1 -0
  73. package/dist/src/types/upstream.d.ts +652 -0
  74. package/dist/src/types/upstream.js +145 -0
  75. package/dist/src/types/upstream.js.map +1 -0
  76. package/package.json +3 -2
  77. package/dist/src/lib/route-flag.d.ts +0 -49
  78. package/dist/src/lib/route-flag.js +0 -52
  79. package/dist/src/lib/route-flag.js.map +0 -1
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Cluster B Phase 2 — Streaming SSE HTTP handler.
3
+ *
4
+ * Extracted verbatim from `openai-compat.ts:514-775` (Module H).
5
+ * Handles the response path when the caller requested `stream:true`.
6
+ * Emits Server-Sent Events with intermediate `delta` chunks and a final
7
+ * chunk carrying `finish_reason` + token usage.
8
+ *
9
+ * Three dispatch paths (decided at runtime by `useToolStream + hasTools`):
10
+ *
11
+ * 1. **Tool-stream mode** (`isToolStreamMode() && hasTools`)
12
+ * Session-manager's pre-parsed `tool_use` events are bridged
13
+ * directly to OpenAI `tool_calls` SSE deltas — two chunks per
14
+ * tool use (start + arguments). Text deltas stream inline.
15
+ *
16
+ * 2. **Legacy tools mode** (`hasTools && !useToolStream`)
17
+ * Buffer the full response text, post-stream-parse `<tool_calls>`
18
+ * XML via `parseToolCallsFromText`, emit text + tool_call chunks
19
+ * with `finish_reason='tool_calls'`.
20
+ *
21
+ * 3. **No-tools** (`!hasTools`)
22
+ * Stream text deltas inline, emit final chunk with
23
+ * `finish_reason='stop'`.
24
+ *
25
+ * v0.7.2 backstop: if no visible payload was streamed (no text, no
26
+ * tool_calls), emit a minimal "Done." text chunk before finalizing so
27
+ * the upstream "incomplete terminal response" classifier sees content.
28
+ *
29
+ * Heartbeat: 30s SSE keepalive comment (`: keepalive`) prevents proxies
30
+ * from closing the connection during long Claude CLI thinking phases.
31
+ * Cleaned up in the `finally` block.
32
+ *
33
+ * Client disconnect: `res.on('close')` flips `clientDisconnected` so
34
+ * writes after disconnect short-circuit instead of throwing.
35
+ *
36
+ * Parameterized fully — no closure capture from openai-compat.ts.
37
+ */
38
+ import type * as http from 'node:http';
39
+ import type { SessionManagerLike } from './openai-compat.js';
40
+ import type { UserMessageBlock } from './message-extractor.js';
41
+ export declare function handleStreaming(manager: SessionManagerLike, sessionName: string, model: string, userMessage: string | UserMessageBlock[], completionId: string, res: http.ServerResponse, hasTools: boolean): Promise<void>;
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Cluster B Phase 2 — Streaming SSE HTTP handler.
3
+ *
4
+ * Extracted verbatim from `openai-compat.ts:514-775` (Module H).
5
+ * Handles the response path when the caller requested `stream:true`.
6
+ * Emits Server-Sent Events with intermediate `delta` chunks and a final
7
+ * chunk carrying `finish_reason` + token usage.
8
+ *
9
+ * Three dispatch paths (decided at runtime by `useToolStream + hasTools`):
10
+ *
11
+ * 1. **Tool-stream mode** (`isToolStreamMode() && hasTools`)
12
+ * Session-manager's pre-parsed `tool_use` events are bridged
13
+ * directly to OpenAI `tool_calls` SSE deltas — two chunks per
14
+ * tool use (start + arguments). Text deltas stream inline.
15
+ *
16
+ * 2. **Legacy tools mode** (`hasTools && !useToolStream`)
17
+ * Buffer the full response text, post-stream-parse `<tool_calls>`
18
+ * XML via `parseToolCallsFromText`, emit text + tool_call chunks
19
+ * with `finish_reason='tool_calls'`.
20
+ *
21
+ * 3. **No-tools** (`!hasTools`)
22
+ * Stream text deltas inline, emit final chunk with
23
+ * `finish_reason='stop'`.
24
+ *
25
+ * v0.7.2 backstop: if no visible payload was streamed (no text, no
26
+ * tool_calls), emit a minimal "Done." text chunk before finalizing so
27
+ * the upstream "incomplete terminal response" classifier sees content.
28
+ *
29
+ * Heartbeat: 30s SSE keepalive comment (`: keepalive`) prevents proxies
30
+ * from closing the connection during long Claude CLI thinking phases.
31
+ * Cleaned up in the `finally` block.
32
+ *
33
+ * Client disconnect: `res.on('close')` flips `clientDisconnected` so
34
+ * writes after disconnect short-circuit instead of throwing.
35
+ *
36
+ * Parameterized fully — no closure capture from openai-compat.ts.
37
+ */
38
+ import { reportStatus, getToolDescription } from './status-reporter.js';
39
+ import { parseToolCallsFromText } from './tool-calls-parser.js';
40
+ import { formatCompletionChunk } from './response-formatter.js';
41
+ import { isToolStreamMode } from './mode-flags.js';
42
+ import { emit as emitTrajectory } from '../lib/trajectory.js';
43
+ import { formatError, ERROR_CODES } from '../lib/error-formatter.js';
44
+ export async function handleStreaming(manager, sessionName, model,
45
+ // Phase 2 R4 wire-up: accepts native content-block arrays in tool-stream mode.
46
+ userMessage, completionId, res, hasTools) {
47
+ res.writeHead(200, {
48
+ 'Content-Type': 'text/event-stream',
49
+ 'Cache-Control': 'no-cache',
50
+ Connection: 'keep-alive',
51
+ 'X-Accel-Buffering': 'no',
52
+ });
53
+ let clientDisconnected = false;
54
+ res.on('close', () => {
55
+ clientDisconnected = true;
56
+ });
57
+ const writeSSE = (data) => {
58
+ if (!clientDisconnected) {
59
+ try {
60
+ res.write(`data: ${data}\n\n`);
61
+ }
62
+ catch {
63
+ clientDisconnected = true;
64
+ }
65
+ }
66
+ };
67
+ // Initial chunk with role
68
+ writeSSE(JSON.stringify(formatCompletionChunk(completionId, model, { role: 'assistant' }, null)));
69
+ // SSE keepalive heartbeat
70
+ const heartbeatTimer = setInterval(() => {
71
+ if (!clientDisconnected) {
72
+ try {
73
+ res.write(': keepalive\n\n');
74
+ }
75
+ catch {
76
+ clientDisconnected = true;
77
+ }
78
+ }
79
+ }, 30_000);
80
+ // Phase 2 R1+R2: in tool-stream mode, bridge session-manager's pre-parsed
81
+ // tool_use events directly to OpenAI tool_calls SSE deltas. Skips the
82
+ // legacy "buffer text + regex-parse <tool_calls> XML" path entirely.
83
+ // Per memory project_cc_openclaw_session_manager_preparses.md:
84
+ // session-manager has already stripped Claude CLI's NDJSON envelope, so
85
+ // we don't need cli-stream-parser here — onEvent is the parser output.
86
+ const useToolStream = isToolStreamMode() && hasTools;
87
+ // When tools are present (legacy mode), buffer the full response to parse
88
+ // for <tool_calls> XML. Without tools — or in tool-stream mode — stream
89
+ // text chunks directly for low latency.
90
+ let bufferedText = '';
91
+ let toolCallsEmitted = 0;
92
+ // v0.7.2 streaming-path backstop: track whether *any* visible content
93
+ // (text chunk OR tool_calls SSE chunk) was ever streamed. If the model
94
+ // uses only CLI built-in tools (Bash/Read/Write) without producing text,
95
+ // bufferedText stays empty AND no caller-visible tool_calls get streamed,
96
+ // resulting in zero content payloads — which OpenClaw upstream's
97
+ // result-fallback-classifier rejects as an "incomplete terminal response".
98
+ // This flag drives a final-chunk backstop in each finalization branch.
99
+ let streamedAnything = false;
100
+ try {
101
+ reportStatus('thinking', 'Processing request...');
102
+ await manager.sendMessage(sessionName, userMessage, {
103
+ onChunk: (chunk) => {
104
+ if (useToolStream || !hasTools) {
105
+ // Stream text deltas immediately. Tool-stream mode interleaves
106
+ // text and tool_calls chunks naturally — Claude CLI emits text
107
+ // between tool_use blocks, OpenClaw client handles that fine.
108
+ if (chunk.length > 0)
109
+ streamedAnything = true;
110
+ writeSSE(JSON.stringify(formatCompletionChunk(completionId, model, { content: chunk }, null)));
111
+ }
112
+ else {
113
+ // Legacy hasTools mode: buffer for XML <tool_calls> parsing post-stream.
114
+ bufferedText += chunk;
115
+ }
116
+ },
117
+ onEvent: (event) => {
118
+ if (event.type === 'tool_result') {
119
+ // Pillar B v0.4.3: streaming tool_result trajectory event.
120
+ emitTrajectory('tool_result', {}, sessionName);
121
+ return;
122
+ }
123
+ if (event.type === 'tool_use' && event.tool?.name) {
124
+ reportStatus('working', getToolDescription(event.tool.name, event.tool.input), event.tool.name);
125
+ // Pillar B v0.4.3: streaming tool_use trajectory event. Same
126
+ // privacy-preserving inputKeys-only payload as handleNonStreaming.
127
+ emitTrajectory('tool_use', {
128
+ name: event.tool.name,
129
+ inputKeys: event.tool.input ? Object.keys(event.tool.input) : [],
130
+ }, sessionName);
131
+ if (useToolStream) {
132
+ // R1+R2 bridge: session-manager event → OpenAI tool_calls SSE.
133
+ // Emit two chunks per tool_use (per OpenAI streaming spec):
134
+ // 1. id + name + empty arguments
135
+ // 2. arguments (JSON-stringified input)
136
+ const toolUseId = event.tool.id ||
137
+ `toolu_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
138
+ const idx = toolCallsEmitted;
139
+ const argsJson = event.tool.input != null ? JSON.stringify(event.tool.input) : '{}';
140
+ const startChunk = {
141
+ id: completionId,
142
+ object: 'chat.completion.chunk',
143
+ created: Math.floor(Date.now() / 1000),
144
+ model,
145
+ choices: [
146
+ {
147
+ index: 0,
148
+ delta: {
149
+ tool_calls: [
150
+ {
151
+ index: idx,
152
+ id: toolUseId,
153
+ type: 'function',
154
+ function: { name: event.tool.name, arguments: '' },
155
+ },
156
+ ],
157
+ },
158
+ finish_reason: null,
159
+ },
160
+ ],
161
+ };
162
+ const argsChunk = {
163
+ id: completionId,
164
+ object: 'chat.completion.chunk',
165
+ created: Math.floor(Date.now() / 1000),
166
+ model,
167
+ choices: [
168
+ {
169
+ index: 0,
170
+ delta: {
171
+ tool_calls: [
172
+ {
173
+ index: idx,
174
+ function: { arguments: argsJson },
175
+ },
176
+ ],
177
+ },
178
+ finish_reason: null,
179
+ },
180
+ ],
181
+ };
182
+ writeSSE(JSON.stringify(startChunk));
183
+ writeSSE(JSON.stringify(argsChunk));
184
+ toolCallsEmitted += 1;
185
+ streamedAnything = true;
186
+ }
187
+ }
188
+ },
189
+ });
190
+ reportStatus('idle', 'Ready');
191
+ // Get token usage for final chunk
192
+ let usage;
193
+ try {
194
+ const status = manager.getStatus(sessionName);
195
+ usage = {
196
+ prompt_tokens: status.stats.tokensIn,
197
+ completion_tokens: status.stats.tokensOut,
198
+ total_tokens: status.stats.tokensIn + status.stats.tokensOut,
199
+ };
200
+ }
201
+ catch {
202
+ /* best effort */
203
+ }
204
+ // v0.7.2 streaming-path backstop: if nothing visible was streamed AND
205
+ // bufferedText (legacy mode) is also empty, emit a minimal "Done." text
206
+ // chunk before the finish chunk so the upstream classifier sees a
207
+ // payload. Skip when tool_calls were emitted — those are openai-spec
208
+ // valid as the only payload (multi-turn tool-use sessions).
209
+ const noVisiblePayload = !streamedAnything && bufferedText.length === 0 && toolCallsEmitted === 0;
210
+ if (noVisiblePayload) {
211
+ writeSSE(JSON.stringify(formatCompletionChunk(completionId, model, { content: 'Done.' }, null)));
212
+ streamedAnything = true;
213
+ }
214
+ if (useToolStream) {
215
+ // R1+R2: tool-stream mode — text + tool_calls already streamed inline.
216
+ // Just emit the final chunk with the right finish_reason.
217
+ const finishReason = toolCallsEmitted > 0 ? 'tool_calls' : 'stop';
218
+ const finalChunk = formatCompletionChunk(completionId, model, {}, finishReason);
219
+ if (usage)
220
+ finalChunk.usage = usage;
221
+ writeSSE(JSON.stringify(finalChunk));
222
+ }
223
+ else if (hasTools && bufferedText) {
224
+ const parsed = parseToolCallsFromText(bufferedText);
225
+ if (parsed.toolCalls.length > 0) {
226
+ // Emit text content if any
227
+ if (parsed.textContent) {
228
+ writeSSE(JSON.stringify(formatCompletionChunk(completionId, model, { content: parsed.textContent }, null)));
229
+ }
230
+ // Emit tool_call chunks
231
+ for (let i = 0; i < parsed.toolCalls.length; i++) {
232
+ const tc = parsed.toolCalls[i];
233
+ writeSSE(JSON.stringify({
234
+ id: completionId,
235
+ object: 'chat.completion.chunk',
236
+ created: Math.floor(Date.now() / 1000),
237
+ model,
238
+ choices: [
239
+ {
240
+ index: 0,
241
+ delta: {
242
+ tool_calls: [
243
+ {
244
+ index: i,
245
+ id: tc.id,
246
+ type: 'function',
247
+ function: { name: tc.function.name, arguments: tc.function.arguments },
248
+ },
249
+ ],
250
+ },
251
+ finish_reason: null,
252
+ },
253
+ ],
254
+ }));
255
+ }
256
+ // Final chunk with tool_calls finish reason
257
+ const finalChunk = formatCompletionChunk(completionId, model, {}, 'tool_calls');
258
+ if (usage)
259
+ finalChunk.usage = usage;
260
+ writeSSE(JSON.stringify(finalChunk));
261
+ }
262
+ else {
263
+ // No tool calls — emit buffered text as content
264
+ writeSSE(JSON.stringify(formatCompletionChunk(completionId, model, { content: bufferedText }, null)));
265
+ const finalChunk = formatCompletionChunk(completionId, model, {}, 'stop');
266
+ if (usage)
267
+ finalChunk.usage = usage;
268
+ writeSSE(JSON.stringify(finalChunk));
269
+ }
270
+ }
271
+ else {
272
+ // No tools — standard finish
273
+ const finalChunk = formatCompletionChunk(completionId, model, {}, 'stop');
274
+ if (usage)
275
+ finalChunk.usage = usage;
276
+ writeSSE(JSON.stringify(finalChunk));
277
+ }
278
+ writeSSE('[DONE]');
279
+ }
280
+ catch (err) {
281
+ reportStatus('idle', 'Request failed');
282
+ // v0.4.3: route through formatError for errors_total + trajectory error.
283
+ formatError(err, { code: ERROR_CODES.SESSION_ERROR, sessionId: sessionName, details: { phase: 'handleStreaming' } });
284
+ writeSSE(JSON.stringify({ error: { message: err.message, type: 'server_error' } }));
285
+ writeSSE('[DONE]');
286
+ }
287
+ finally {
288
+ clearInterval(heartbeatTimer);
289
+ }
290
+ if (!clientDisconnected) {
291
+ res.end();
292
+ }
293
+ }
294
+ //# sourceMappingURL=streaming-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming-handler.js","sourceRoot":"","sources":["../../../src/openai-compat/streaming-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Cluster B — Tool-calls parser.
3
+ *
4
+ * Extracted verbatim from `openai-compat.ts:147-221` (Phase 4 Cluster B
5
+ * Module B). Reads model output text and pulls out any `<tool_calls>`
6
+ * XML blocks, converting them to OpenAI-compatible `OpenAIToolCall[]`.
7
+ *
8
+ * This is the response-side counterpart to `prompts.ts:buildToolPromptBlock`
9
+ * — the prompt tells the model "wrap your tool calls in `<tool_calls>`",
10
+ * and this parser is what reads those tool calls back out of the model's
11
+ * text response on the legacy (non-stream) tool path.
12
+ *
13
+ * Behavior preserved bit-for-bit. Two call sites in the orchestrator
14
+ * (handleChatCompletion lines 950 + 1181) — one on the buffered final
15
+ * output path, one on the streaming-buffered-text path.
16
+ *
17
+ * Robustness contract:
18
+ * - Multiple `<tool_calls>` blocks in one response → all merged
19
+ * - JSON parse fail on a block → that block kept as text content
20
+ * - `arguments` field as string or object → both normalized to string
21
+ * - `<tool_result>` echoes from the model → stripped from textContent
22
+ */
23
+ import type { OpenAIToolCall } from './openai-compat.js';
24
+ export interface ParsedToolCalls {
25
+ textContent: string | null;
26
+ toolCalls: OpenAIToolCall[];
27
+ }
28
+ /**
29
+ * Parse tool_calls from CLI text output.
30
+ *
31
+ * Looks for <tool_calls>[...]</tool_calls> tags in the response text.
32
+ * Returns both the extracted text content (before/after tags) and any tool calls found.
33
+ */
34
+ export declare function parseToolCallsFromText(text: string): ParsedToolCalls;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Cluster B — Tool-calls parser.
3
+ *
4
+ * Extracted verbatim from `openai-compat.ts:147-221` (Phase 4 Cluster B
5
+ * Module B). Reads model output text and pulls out any `<tool_calls>`
6
+ * XML blocks, converting them to OpenAI-compatible `OpenAIToolCall[]`.
7
+ *
8
+ * This is the response-side counterpart to `prompts.ts:buildToolPromptBlock`
9
+ * — the prompt tells the model "wrap your tool calls in `<tool_calls>`",
10
+ * and this parser is what reads those tool calls back out of the model's
11
+ * text response on the legacy (non-stream) tool path.
12
+ *
13
+ * Behavior preserved bit-for-bit. Two call sites in the orchestrator
14
+ * (handleChatCompletion lines 950 + 1181) — one on the buffered final
15
+ * output path, one on the streaming-buffered-text path.
16
+ *
17
+ * Robustness contract:
18
+ * - Multiple `<tool_calls>` blocks in one response → all merged
19
+ * - JSON parse fail on a block → that block kept as text content
20
+ * - `arguments` field as string or object → both normalized to string
21
+ * - `<tool_result>` echoes from the model → stripped from textContent
22
+ */
23
+ import { randomUUID } from 'node:crypto';
24
+ /**
25
+ * Parse tool_calls from CLI text output.
26
+ *
27
+ * Looks for <tool_calls>[...]</tool_calls> tags in the response text.
28
+ * Returns both the extracted text content (before/after tags) and any tool calls found.
29
+ */
30
+ export function parseToolCallsFromText(text) {
31
+ // Match ALL <tool_calls> blocks (model may output multiple)
32
+ const tagRegex = /<tool_calls>\s*([\s\S]*?)\s*<\/tool_calls>/g;
33
+ const allCalls = [];
34
+ let lastIndex = 0;
35
+ const textParts = [];
36
+ let m;
37
+ while ((m = tagRegex.exec(text)) !== null) {
38
+ // Collect text before this block
39
+ const before = text.slice(lastIndex, m.index).trim();
40
+ if (before)
41
+ textParts.push(before);
42
+ lastIndex = m.index + m[0].length;
43
+ try {
44
+ const parsed = JSON.parse(m[1].trim());
45
+ const arr = Array.isArray(parsed) ? parsed : [parsed];
46
+ for (const raw of arr) {
47
+ const call = raw;
48
+ if (!call || typeof call !== 'object' || typeof call.name !== 'string')
49
+ continue;
50
+ let args;
51
+ if (typeof call.arguments === 'string') {
52
+ try {
53
+ JSON.parse(call.arguments);
54
+ args = call.arguments;
55
+ }
56
+ catch {
57
+ args = JSON.stringify({ input: call.arguments });
58
+ }
59
+ }
60
+ else {
61
+ args = JSON.stringify(call.arguments ?? {});
62
+ }
63
+ allCalls.push({
64
+ id: `call_${randomUUID().replace(/-/g, '').slice(0, 24)}`,
65
+ type: 'function',
66
+ function: { name: call.name, arguments: args },
67
+ });
68
+ }
69
+ }
70
+ catch {
71
+ // One block failed — keep its text as content
72
+ textParts.push(m[0]);
73
+ }
74
+ }
75
+ // Collect text after last block
76
+ const after = text.slice(lastIndex).trim();
77
+ if (after)
78
+ textParts.push(after);
79
+ // Strip <tool_result> and <tool_results> tags that the model may echo back
80
+ // from the serialized tool results we injected earlier.
81
+ const stripToolResultTags = (s) => s
82
+ .replace(/<tool_results?>[\s\S]*?<\/tool_results?>/g, '')
83
+ .replace(/<tool_results?[^>]*>/g, '')
84
+ .trim();
85
+ if (allCalls.length > 0) {
86
+ const raw = textParts.join('\n').trim();
87
+ const cleaned = raw ? stripToolResultTags(raw) : null;
88
+ return { textContent: cleaned || null, toolCalls: allCalls };
89
+ }
90
+ const cleaned = text ? stripToolResultTags(text) : null;
91
+ return { textContent: cleaned || null, toolCalls: [] };
92
+ }
93
+ //# sourceMappingURL=tool-calls-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-calls-parser.js","sourceRoot":"","sources":["../../../src/openai-compat/tool-calls-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAQzC;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,4DAA4D;IAC5D,MAAM,QAAQ,GAAG,6CAA6C,CAAC;IAC/D,MAAM,QAAQ,GAAqB,EAAE,CAAC;IACtC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,IAAI,CAAyB,CAAC;IAE9B,OAAO,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1C,iCAAiC;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,IAAI,MAAM;YAAE,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,SAAS,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QAElC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAY,CAAC;YAClD,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACtD,KAAK,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;gBACtB,MAAM,IAAI,GAAG,GAA8B,CAAC;gBAC5C,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;oBAAE,SAAS;gBACjF,IAAI,IAAY,CAAC;gBACjB,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;oBACvC,IAAI,CAAC;wBACH,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;wBAC3B,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;oBACxB,CAAC;oBAAC,MAAM,CAAC;wBACP,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;oBACnD,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;gBAC9C,CAAC;gBACD,QAAQ,CAAC,IAAI,CAAC;oBACZ,EAAE,EAAE,QAAQ,UAAU,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE;oBACzD,IAAI,EAAE,UAAmB;oBACzB,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE;iBAC/C,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;YAC9C,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,gCAAgC;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3C,IAAI,KAAK;QAAE,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAEjC,2EAA2E;IAC3E,wDAAwD;IACxD,MAAM,mBAAmB,GAAG,CAAC,CAAS,EAAU,EAAE,CAChD,CAAC;SACE,OAAO,CAAC,2CAA2C,EAAE,EAAE,CAAC;SACxD,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC;SACpC,IAAI,EAAE,CAAC;IAEZ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACtD,OAAO,EAAE,WAAW,EAAE,OAAO,IAAI,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IAC/D,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,OAAO,EAAE,WAAW,EAAE,OAAO,IAAI,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;AACzD,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Cluster B — Tool-result serializers.
3
+ *
4
+ * Extracted verbatim from `openai-compat.ts:153-211` (Phase 4 Cluster B
5
+ * Module C). Two functions that serialize OpenAI `role:tool` messages
6
+ * into the format Claude CLI expects, picked between by the runtime
7
+ * `CC_OPENCLAW_TOOL_STREAM` flag:
8
+ *
9
+ * - `serializeToolResults(messages)` — legacy XML path. Wraps each
10
+ * tool message in `<tool_result tool_call_id="...">` and concatenates
11
+ * into a single `<tool_results>` block. Used when the model receives
12
+ * tool definitions via the system prompt's `<available_tools>` XML.
13
+ *
14
+ * - `serializeToolResultsAsBlocks(messages)` — Phase 2 R4 native path.
15
+ * Returns an array of Anthropic `tool_result` content blocks shaped
16
+ * as `{ type, tool_use_id, content }`. Used when Claude CLI parses
17
+ * tool results natively (CC_OPENCLAW_TOOL_STREAM=1).
18
+ *
19
+ * - `AnthropicToolResultBlock` — interface for the native shape.
20
+ *
21
+ * Both serializers are pure. Currently only called by
22
+ * `message-extractor.ts:extractUserMessage` (Module D); the orchestrator
23
+ * itself does not call them directly anymore.
24
+ */
25
+ import type { OpenAIChatMessage } from './openai-compat.js';
26
+ /**
27
+ * Serialize tool result messages into a text block for the CLI model.
28
+ * Converts OpenAI `tool` role messages into <tool_result> tags.
29
+ *
30
+ * Legacy path (CC_OPENCLAW_TOOL_STREAM=0). Used when the model receives
31
+ * tool definitions via the system prompt's <available_tools> XML block
32
+ * and emits <tool_calls> XML in response. Tool-stream mode (R4) uses
33
+ * `serializeToolResultsAsBlocks()` instead, returning native Anthropic
34
+ * `tool_result` content blocks that Claude CLI parses directly.
35
+ */
36
+ export declare function serializeToolResults(messages: OpenAIChatMessage[]): string;
37
+ /**
38
+ * Phase 2 R4: native Anthropic `tool_result` content blocks.
39
+ *
40
+ * In tool-stream mode (CC_OPENCLAW_TOOL_STREAM=1), this replaces
41
+ * `serializeToolResults()` XML wrapping. Returns blocks shaped like:
42
+ *
43
+ * { type: 'tool_result', tool_use_id: 'toolu_X', content: '...' }
44
+ *
45
+ * Claude CLI's stream-json input format accepts these as user-message
46
+ * content arrays. The model then continues generation with the tool
47
+ * result in context, emitting either more `tool_use` events or final
48
+ * text — no XML round-tripping required.
49
+ *
50
+ * Wiring into the live request path lands with Phase 4 Pillar 0.5
51
+ * tasks R1+R2 (parser+translator response-side integration), where
52
+ * `extractUserMessage` produces structured content arrays in
53
+ * tool-stream mode.
54
+ */
55
+ export interface AnthropicToolResultBlock {
56
+ type: 'tool_result';
57
+ tool_use_id: string;
58
+ content: string;
59
+ }
60
+ export declare function serializeToolResultsAsBlocks(messages: OpenAIChatMessage[]): AnthropicToolResultBlock[];
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Cluster B — Tool-result serializers.
3
+ *
4
+ * Extracted verbatim from `openai-compat.ts:153-211` (Phase 4 Cluster B
5
+ * Module C). Two functions that serialize OpenAI `role:tool` messages
6
+ * into the format Claude CLI expects, picked between by the runtime
7
+ * `CC_OPENCLAW_TOOL_STREAM` flag:
8
+ *
9
+ * - `serializeToolResults(messages)` — legacy XML path. Wraps each
10
+ * tool message in `<tool_result tool_call_id="...">` and concatenates
11
+ * into a single `<tool_results>` block. Used when the model receives
12
+ * tool definitions via the system prompt's `<available_tools>` XML.
13
+ *
14
+ * - `serializeToolResultsAsBlocks(messages)` — Phase 2 R4 native path.
15
+ * Returns an array of Anthropic `tool_result` content blocks shaped
16
+ * as `{ type, tool_use_id, content }`. Used when Claude CLI parses
17
+ * tool results natively (CC_OPENCLAW_TOOL_STREAM=1).
18
+ *
19
+ * - `AnthropicToolResultBlock` — interface for the native shape.
20
+ *
21
+ * Both serializers are pure. Currently only called by
22
+ * `message-extractor.ts:extractUserMessage` (Module D); the orchestrator
23
+ * itself does not call them directly anymore.
24
+ */
25
+ /**
26
+ * Serialize tool result messages into a text block for the CLI model.
27
+ * Converts OpenAI `tool` role messages into <tool_result> tags.
28
+ *
29
+ * Legacy path (CC_OPENCLAW_TOOL_STREAM=0). Used when the model receives
30
+ * tool definitions via the system prompt's <available_tools> XML block
31
+ * and emits <tool_calls> XML in response. Tool-stream mode (R4) uses
32
+ * `serializeToolResultsAsBlocks()` instead, returning native Anthropic
33
+ * `tool_result` content blocks that Claude CLI parses directly.
34
+ */
35
+ export function serializeToolResults(messages) {
36
+ const toolMessages = messages.filter((m) => m.role === 'tool');
37
+ if (!toolMessages.length)
38
+ return '';
39
+ const results = toolMessages
40
+ .map((m) => {
41
+ const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content);
42
+ return `<tool_result tool_call_id="${m.tool_call_id || 'unknown'}">\n${content}\n</tool_result>`;
43
+ })
44
+ .join('\n\n');
45
+ return `<tool_results>\n${results}\n</tool_results>\n\nAbove are the results of the tool calls you requested. Continue your response based on these results.`;
46
+ }
47
+ export function serializeToolResultsAsBlocks(messages) {
48
+ return messages
49
+ .filter((m) => m.role === 'tool')
50
+ .map((m) => ({
51
+ type: 'tool_result',
52
+ tool_use_id: m.tool_call_id || 'unknown',
53
+ content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
54
+ }));
55
+ }
56
+ //# sourceMappingURL=tool-results-serializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-results-serializer.js","sourceRoot":"","sources":["../../../src/openai-compat/tool-results-serializer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAA6B;IAChE,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IAC/D,IAAI,CAAC,YAAY,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAEpC,MAAM,OAAO,GAAG,YAAY;SACzB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACtF,OAAO,8BAA8B,CAAC,CAAC,YAAY,IAAI,SAAS,OAAO,OAAO,kBAAkB,CAAC;IACnG,CAAC,CAAC;SACD,IAAI,CAAC,MAAM,CAAC,CAAC;IAEhB,OAAO,mBAAmB,OAAO,4HAA4H,CAAC;AAChK,CAAC;AA0BD,MAAM,UAAU,4BAA4B,CAC1C,QAA6B;IAE7B,OAAO,QAAQ;SACZ,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;SAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,IAAI,EAAE,aAAsB;QAC5B,WAAW,EAAE,CAAC,CAAC,YAAY,IAAI,SAAS;QACxC,OAAO,EAAE,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;KAC/E,CAAC,CAAC,CAAC;AACR,CAAC"}
@@ -189,6 +189,18 @@ export class SessionManager {
189
189
  permissionMode: config.permissionMode || this.pluginConfig.defaultPermissionMode,
190
190
  effort: config.effort || this.pluginConfig.defaultEffort,
191
191
  model: config.model || persisted?.model || this.pluginConfig.defaultModel,
192
+ // v0.7.5 unification: default --include-partial-messages ON for every
193
+ // claude.exe spawn. Without this flag the engine emits text only as
194
+ // one aggregate `assistant` event; cc-handler's synchronous sendMessage
195
+ // return path doesn't surface that as content payloads, so the openclaw
196
+ // embedded agent rejects the turn as "incomplete terminal response
197
+ // (format)". Adopting the openai-compat path's working behavior as the
198
+ // canonical default unifies all 10 startSession callers (openai-compat,
199
+ // cc-handler fresh + rehydrate, prewarm, council, internal recovery,
200
+ // hot-reload) without per-site changes. Callers that genuinely need it
201
+ // OFF can pass `includePartialMessages: false` and override via the
202
+ // `...config` spread below.
203
+ includePartialMessages: true,
192
204
  ...config,
193
205
  ...(resumeId ? { resumeSessionId: resumeId } : {}),
194
206
  };