@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.
- package/dist/src/command-router/cc-handler.js +11 -3
- package/dist/src/command-router/cc-handler.js.map +1 -1
- package/dist/src/engines/persistent-session.d.ts +1 -0
- package/dist/src/engines/persistent-session.js +35 -1
- package/dist/src/engines/persistent-session.js.map +1 -1
- package/dist/src/index.d.ts +10 -1
- package/dist/src/index.js +47 -7
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/config-service.d.ts +106 -0
- package/dist/src/lib/config-service.js +217 -0
- package/dist/src/lib/config-service.js.map +1 -0
- package/dist/src/lib/config.d.ts +33 -14
- package/dist/src/lib/config.js +147 -34
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/index.d.ts +1 -1
- package/dist/src/lib/index.js +4 -1
- package/dist/src/lib/index.js.map +1 -1
- package/dist/src/openai-compat/message-extractor.d.ts +79 -0
- package/dist/src/openai-compat/message-extractor.js +134 -0
- package/dist/src/openai-compat/message-extractor.js.map +1 -0
- package/dist/src/openai-compat/mode-flags.d.ts +34 -0
- package/dist/src/openai-compat/mode-flags.js +44 -0
- package/dist/src/openai-compat/mode-flags.js.map +1 -0
- package/dist/src/openai-compat/non-streaming-handler.d.ts +26 -0
- package/dist/src/openai-compat/non-streaming-handler.js +108 -0
- package/dist/src/openai-compat/non-streaming-handler.js.map +1 -0
- package/dist/src/openai-compat/openai-compat.d.ts +15 -166
- package/dist/src/openai-compat/openai-compat.js +72 -817
- package/dist/src/openai-compat/openai-compat.js.map +1 -1
- package/dist/src/openai-compat/prompts.d.ts +47 -0
- package/dist/src/openai-compat/prompts.js +119 -0
- package/dist/src/openai-compat/prompts.js.map +1 -0
- package/dist/src/openai-compat/response-formatter.d.ts +33 -0
- package/dist/src/openai-compat/response-formatter.js +74 -0
- package/dist/src/openai-compat/response-formatter.js.map +1 -0
- package/dist/src/openai-compat/session-key-resolver.d.ts +41 -0
- package/dist/src/openai-compat/session-key-resolver.js +78 -0
- package/dist/src/openai-compat/session-key-resolver.js.map +1 -0
- package/dist/src/openai-compat/status-reporter.d.ts +30 -0
- package/dist/src/openai-compat/status-reporter.js +81 -0
- package/dist/src/openai-compat/status-reporter.js.map +1 -0
- package/dist/src/openai-compat/streaming-handler.d.ts +41 -0
- package/dist/src/openai-compat/streaming-handler.js +294 -0
- package/dist/src/openai-compat/streaming-handler.js.map +1 -0
- package/dist/src/openai-compat/tool-calls-parser.d.ts +34 -0
- package/dist/src/openai-compat/tool-calls-parser.js +93 -0
- package/dist/src/openai-compat/tool-calls-parser.js.map +1 -0
- package/dist/src/openai-compat/tool-results-serializer.d.ts +60 -0
- package/dist/src/openai-compat/tool-results-serializer.js +56 -0
- package/dist/src/openai-compat/tool-results-serializer.js.map +1 -0
- package/dist/src/session/session-manager.js +12 -0
- package/dist/src/session/session-manager.js.map +1 -1
- package/dist/src/session-bootstrap/cwd-patch.js +30 -13
- package/dist/src/session-bootstrap/cwd-patch.js.map +1 -1
- package/dist/src/types/index.d.ts +15 -0
- package/dist/src/types/index.js +16 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/route.d.ts +41 -0
- package/dist/src/types/route.js +12 -0
- package/dist/src/types/route.js.map +1 -0
- package/dist/src/types/runtime-config.d.ts +161 -0
- package/dist/src/types/runtime-config.js +118 -0
- package/dist/src/types/runtime-config.js.map +1 -0
- package/dist/src/types/session.d.ts +48 -0
- package/dist/src/types/session.js +20 -0
- package/dist/src/types/session.js.map +1 -0
- package/dist/src/types/sse.d.ts +38 -0
- package/dist/src/types/sse.js +12 -0
- package/dist/src/types/sse.js.map +1 -0
- package/dist/src/types/tool-bridge.d.ts +81 -0
- package/dist/src/types/tool-bridge.js +34 -0
- package/dist/src/types/tool-bridge.js.map +1 -0
- package/dist/src/types/upstream.d.ts +652 -0
- package/dist/src/types/upstream.js +145 -0
- package/dist/src/types/upstream.js.map +1 -0
- package/package.json +3 -2
- package/dist/src/lib/route-flag.d.ts +0 -49
- package/dist/src/lib/route-flag.js +0 -52
- 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
|
};
|