@a1hvdy/cc-openclaw 0.8.0 → 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 (72) hide show
  1. package/dist/src/index.d.ts +10 -1
  2. package/dist/src/index.js +47 -7
  3. package/dist/src/index.js.map +1 -1
  4. package/dist/src/lib/config-service.d.ts +106 -0
  5. package/dist/src/lib/config-service.js +217 -0
  6. package/dist/src/lib/config-service.js.map +1 -0
  7. package/dist/src/lib/config.d.ts +33 -14
  8. package/dist/src/lib/config.js +147 -34
  9. package/dist/src/lib/config.js.map +1 -1
  10. package/dist/src/lib/index.d.ts +1 -1
  11. package/dist/src/lib/index.js +4 -1
  12. package/dist/src/lib/index.js.map +1 -1
  13. package/dist/src/openai-compat/message-extractor.d.ts +79 -0
  14. package/dist/src/openai-compat/message-extractor.js +134 -0
  15. package/dist/src/openai-compat/message-extractor.js.map +1 -0
  16. package/dist/src/openai-compat/mode-flags.d.ts +34 -0
  17. package/dist/src/openai-compat/mode-flags.js +44 -0
  18. package/dist/src/openai-compat/mode-flags.js.map +1 -0
  19. package/dist/src/openai-compat/non-streaming-handler.d.ts +26 -0
  20. package/dist/src/openai-compat/non-streaming-handler.js +108 -0
  21. package/dist/src/openai-compat/non-streaming-handler.js.map +1 -0
  22. package/dist/src/openai-compat/openai-compat.d.ts +15 -166
  23. package/dist/src/openai-compat/openai-compat.js +59 -849
  24. package/dist/src/openai-compat/openai-compat.js.map +1 -1
  25. package/dist/src/openai-compat/prompts.d.ts +47 -0
  26. package/dist/src/openai-compat/prompts.js +119 -0
  27. package/dist/src/openai-compat/prompts.js.map +1 -0
  28. package/dist/src/openai-compat/response-formatter.d.ts +33 -0
  29. package/dist/src/openai-compat/response-formatter.js +74 -0
  30. package/dist/src/openai-compat/response-formatter.js.map +1 -0
  31. package/dist/src/openai-compat/session-key-resolver.d.ts +41 -0
  32. package/dist/src/openai-compat/session-key-resolver.js +78 -0
  33. package/dist/src/openai-compat/session-key-resolver.js.map +1 -0
  34. package/dist/src/openai-compat/status-reporter.d.ts +30 -0
  35. package/dist/src/openai-compat/status-reporter.js +81 -0
  36. package/dist/src/openai-compat/status-reporter.js.map +1 -0
  37. package/dist/src/openai-compat/streaming-handler.d.ts +41 -0
  38. package/dist/src/openai-compat/streaming-handler.js +294 -0
  39. package/dist/src/openai-compat/streaming-handler.js.map +1 -0
  40. package/dist/src/openai-compat/tool-calls-parser.d.ts +34 -0
  41. package/dist/src/openai-compat/tool-calls-parser.js +93 -0
  42. package/dist/src/openai-compat/tool-calls-parser.js.map +1 -0
  43. package/dist/src/openai-compat/tool-results-serializer.d.ts +60 -0
  44. package/dist/src/openai-compat/tool-results-serializer.js +56 -0
  45. package/dist/src/openai-compat/tool-results-serializer.js.map +1 -0
  46. package/dist/src/session-bootstrap/cwd-patch.js +20 -13
  47. package/dist/src/session-bootstrap/cwd-patch.js.map +1 -1
  48. package/dist/src/types/index.d.ts +15 -0
  49. package/dist/src/types/index.js +16 -0
  50. package/dist/src/types/index.js.map +1 -0
  51. package/dist/src/types/route.d.ts +41 -0
  52. package/dist/src/types/route.js +12 -0
  53. package/dist/src/types/route.js.map +1 -0
  54. package/dist/src/types/runtime-config.d.ts +161 -0
  55. package/dist/src/types/runtime-config.js +118 -0
  56. package/dist/src/types/runtime-config.js.map +1 -0
  57. package/dist/src/types/session.d.ts +48 -0
  58. package/dist/src/types/session.js +20 -0
  59. package/dist/src/types/session.js.map +1 -0
  60. package/dist/src/types/sse.d.ts +38 -0
  61. package/dist/src/types/sse.js +12 -0
  62. package/dist/src/types/sse.js.map +1 -0
  63. package/dist/src/types/tool-bridge.d.ts +81 -0
  64. package/dist/src/types/tool-bridge.js +34 -0
  65. package/dist/src/types/tool-bridge.js.map +1 -0
  66. package/dist/src/types/upstream.d.ts +652 -0
  67. package/dist/src/types/upstream.js +145 -0
  68. package/dist/src/types/upstream.js.map +1 -0
  69. package/package.json +3 -2
  70. package/dist/src/lib/route-flag.d.ts +0 -49
  71. package/dist/src/lib/route-flag.js +0 -52
  72. package/dist/src/lib/route-flag.js.map +0 -1
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Cluster B — Message extractor.
3
+ *
4
+ * Extracted verbatim from `openai-compat.ts:177-301` (Phase 4 Cluster B
5
+ * Module D). Single function that converts an OpenAI `messages[]` array
6
+ * into the inputs Claude CLI's bridge expects:
7
+ *
8
+ * - systemPrompt — joined `role:system` content, undefined if none
9
+ * - userMessage — last user message (with skill-inline applied)
10
+ * OR XML-wrapped tool-result block when the
11
+ * last non-system message is `role:tool`
12
+ * - userMessageBlocks — tool-stream mode only: native Anthropic
13
+ * content blocks (tool_result + text) for the
14
+ * stream-json input path
15
+ * - isNewConversation — drives session-create vs session-append
16
+ *
17
+ * Single call site in the orchestrator (handleChatCompletion line 482).
18
+ * The two types `UserMessageBlock` and `ExtractedMessage` are also re-
19
+ * exported from openai-compat.ts for callers across the orchestrator
20
+ * (formatCompletionResponse + helpers all consume them as parameters).
21
+ *
22
+ * Session-reset semantics:
23
+ * - X-Session-Reset: 1|true → always treated as new conversation
24
+ * - OPENAI_COMPAT_NEW_CONVO_HEURISTIC=1 (env) → legacy
25
+ * "system + single user ⇒ new conversation" rule, opt-in for
26
+ * callers that re-send the full transcript every turn
27
+ * - Default (env unset) → only the explicit header creates a new
28
+ * conversation; persistent CLI sessions survive across turns and
29
+ * benefit from Anthropic prompt caching (PR #40)
30
+ */
31
+ import { serializeToolResults, serializeToolResultsAsBlocks, } from './tool-results-serializer.js';
32
+ import { isToolStreamMode } from './mode-flags.js';
33
+ import { maybeInlineSkill } from './skill-resolver.js';
34
+ import { isOpenaiCompatNewConvoHeuristic } from '../lib/config.js';
35
+ /**
36
+ * Extract the relevant parts from an OpenAI messages array.
37
+ *
38
+ * Sessions are stateful — we only need the last user message. The tricky
39
+ * question is whether to start a fresh session or append to the existing one.
40
+ *
41
+ * Default mode (no env var): only honor an explicit `X-Session-Reset: 1`
42
+ * header. This is correct for clients that maintain their own conversation
43
+ * transcript and forward only the latest user turn (OpenClaw main agent
44
+ * loop, cron jobs, subagents). The previous heuristic
45
+ * (`nonSystemMessages.length <= 1`) fired on every such request, killing the
46
+ * persistent CLI every turn and preventing Anthropic prompt caching from
47
+ * ever warming. Originally diagnosed in PR #40 by @megayounus786.
48
+ *
49
+ * Legacy mode (`OPENAI_COMPAT_NEW_CONVO_HEURISTIC=1`): restore the old
50
+ * `system + single user ⇒ new conversation` rule, for clients that re-send
51
+ * the full transcript on every turn (ChatGPT-Next-Web, Open WebUI, data
52
+ * labeling tools, etc). They use the transcript shape itself as their only
53
+ * "start a new conversation" signal.
54
+ *
55
+ * The env var is read on every call so ops can flip it via launchctl setenv
56
+ * without restarting the server.
57
+ */
58
+ export function extractUserMessage(messages, headers) {
59
+ if (!messages || messages.length === 0) {
60
+ throw new Error('messages array is empty');
61
+ }
62
+ // Normalize content from any message: OpenAI API allows content as a string
63
+ // OR an array of content parts (e.g. multimodal messages with text + images).
64
+ // We need a string for the CLI, so arrays are joined.
65
+ const textOf = (m) => {
66
+ if (typeof m.content === 'string')
67
+ return m.content;
68
+ if (Array.isArray(m.content)) {
69
+ return m.content
70
+ .map((p) => p.text || '')
71
+ .filter(Boolean)
72
+ .join('');
73
+ }
74
+ return m.content != null ? String(m.content) : '';
75
+ };
76
+ // Extract system prompt if present
77
+ const systemMessages = messages.filter((m) => m.role === 'system');
78
+ const systemPrompt = systemMessages.length > 0 ? systemMessages.map(textOf).join('\n') : undefined;
79
+ // Handle tool result messages — only when the LAST non-system message is
80
+ // a tool role (meaning we're in an active tool-use cycle). If the last
81
+ // message is a user role, it's a follow-up in an existing conversation
82
+ // and the old tool results are already in the CLI's history.
83
+ const lastNonSystem = [...messages].reverse().find((m) => m.role !== 'system');
84
+ if (lastNonSystem?.role === 'tool') {
85
+ const userMessages = messages.filter((m) => m.role === 'user');
86
+ const lastUserText = userMessages.length > 0 ? textOf(userMessages[userMessages.length - 1]) : '';
87
+ // Phase 2 R4 wire-up: in tool-stream mode, emit native Anthropic
88
+ // tool_result blocks instead of XML-wrapped text. Claude CLI's
89
+ // stream-json input accepts content arrays directly.
90
+ if (isToolStreamMode()) {
91
+ const toolBlocks = serializeToolResultsAsBlocks(messages);
92
+ const userMessageBlocks = [...toolBlocks];
93
+ if (lastUserText) {
94
+ userMessageBlocks.push({ type: 'text', text: lastUserText });
95
+ }
96
+ // Keep userMessage populated as the legacy XML form for callers
97
+ // that don't yet handle the structured path. Both fields agree in
98
+ // intent; consumers should prefer userMessageBlocks when present.
99
+ const fallback = serializeToolResults(messages);
100
+ const userMessage = lastUserText ? `${fallback}\n\n${lastUserText}` : fallback;
101
+ return { systemPrompt, userMessage, userMessageBlocks, isNewConversation: false };
102
+ }
103
+ const toolResultBlock = serializeToolResults(messages);
104
+ const userMessage = lastUserText ? `${toolResultBlock}\n\n${lastUserText}` : toolResultBlock;
105
+ return { systemPrompt, userMessage, isNewConversation: false };
106
+ }
107
+ // Find last user message
108
+ const userMessages = messages.filter((m) => m.role === 'user');
109
+ if (userMessages.length === 0) {
110
+ throw new Error('No user message found in messages array');
111
+ }
112
+ const rawUserMessage = textOf(userMessages[userMessages.length - 1]);
113
+ // Workspace skill auto-inline: if the last user message is /<skill> [args]
114
+ // and ~/.openclaw/workspace/skills/*/SKILL.md has a matching `name:` in
115
+ // frontmatter, replace the user message with the SKILL.md body so the
116
+ // model has full skill context without needing the Read tool (cc-openclaw
117
+ // disables built-in tools by design — see the `sessionConfig.tools = ''`
118
+ // line below).
119
+ const userMessage = maybeInlineSkill(rawUserMessage) ?? rawUserMessage;
120
+ // 1. Explicit reset header — honored in both modes. Normalize trim+lowercase
121
+ // so callers using `TRUE`, ` 1 `, etc. don't silently fail.
122
+ const rawReset = headers?.['x-session-reset'];
123
+ const resetHeader = typeof rawReset === 'string' ? rawReset.trim().toLowerCase() : '';
124
+ if (resetHeader === 'true' || resetHeader === '1') {
125
+ return { systemPrompt, userMessage, isNewConversation: true };
126
+ }
127
+ // 2. Legacy heuristic — only when explicitly opted in via env var.
128
+ if (isOpenaiCompatNewConvoHeuristic()) {
129
+ const nonSystemMessages = messages.filter((m) => m.role !== 'system');
130
+ return { systemPrompt, userMessage, isNewConversation: nonSystemMessages.length <= 1 };
131
+ }
132
+ return { systemPrompt, userMessage, isNewConversation: false };
133
+ }
134
+ //# sourceMappingURL=message-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-extractor.js","sourceRoot":"","sources":["../../../src/openai-compat/message-extractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Cluster B beachhead — tool-mode flag predicates.
3
+ *
4
+ * Extracted from `openai-compat.ts` so that `session-key-resolver.ts`
5
+ * (and future Cluster B modules) can read these flags without creating
6
+ * a circular import back into the orchestrator.
7
+ *
8
+ * Behavior preserved bit-for-bit from the original definitions in
9
+ * `openai-compat.ts:126-142`. openai-compat.ts re-exports both
10
+ * functions for any external caller that previously imported them
11
+ * from there (none exist today, but the re-export is cheap insurance).
12
+ *
13
+ * Cluster B follow-on will fold these into the `IToolBridge` strategy
14
+ * pattern (PRP §5 Cluster B `bridges/factory.ts`); for now they
15
+ * remain free-function predicates.
16
+ */
17
+ /**
18
+ * Legacy mode: when set, tool definitions are prepended to every user
19
+ * message instead of being baked into the session system prompt. Defeats
20
+ * prompt caching but allows mutating the tool list mid-session.
21
+ *
22
+ * Truthy values: '1', 'true', 'yes' (case-insensitive, trimmed).
23
+ */
24
+ export declare function isToolsPerMessageModeEnabled(): boolean;
25
+ /**
26
+ * Phase 2 R5: tool-stream mode flag. When `CC_OPENCLAW_TOOL_STREAM=1`
27
+ * AND the caller provides `tools[]`, cc-openclaw skips the defensive
28
+ * "no tools" system prompt and does NOT clear `sessionConfig.tools`,
29
+ * allowing Claude CLI's native tool_use events to flow through the
30
+ * parser+translator pipeline.
31
+ *
32
+ * Strict opt-in: only true when env value is exactly `'1'`.
33
+ */
34
+ export declare function isToolStreamMode(): boolean;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Cluster B beachhead — tool-mode flag predicates.
3
+ *
4
+ * Extracted from `openai-compat.ts` so that `session-key-resolver.ts`
5
+ * (and future Cluster B modules) can read these flags without creating
6
+ * a circular import back into the orchestrator.
7
+ *
8
+ * Behavior preserved bit-for-bit from the original definitions in
9
+ * `openai-compat.ts:126-142`. openai-compat.ts re-exports both
10
+ * functions for any external caller that previously imported them
11
+ * from there (none exist today, but the re-export is cheap insurance).
12
+ *
13
+ * Cluster B follow-on will fold these into the `IToolBridge` strategy
14
+ * pattern (PRP §5 Cluster B `bridges/factory.ts`); for now they
15
+ * remain free-function predicates.
16
+ */
17
+ import { getOpenaiCompatToolsPerMessage } from '../lib/config.js';
18
+ /**
19
+ * Legacy mode: when set, tool definitions are prepended to every user
20
+ * message instead of being baked into the session system prompt. Defeats
21
+ * prompt caching but allows mutating the tool list mid-session.
22
+ *
23
+ * Truthy values: '1', 'true', 'yes' (case-insensitive, trimmed).
24
+ */
25
+ export function isToolsPerMessageModeEnabled() {
26
+ const v = getOpenaiCompatToolsPerMessage();
27
+ if (!v)
28
+ return false;
29
+ const t = v.trim().toLowerCase();
30
+ return t === '1' || t === 'true' || t === 'yes';
31
+ }
32
+ /**
33
+ * Phase 2 R5: tool-stream mode flag. When `CC_OPENCLAW_TOOL_STREAM=1`
34
+ * AND the caller provides `tools[]`, cc-openclaw skips the defensive
35
+ * "no tools" system prompt and does NOT clear `sessionConfig.tools`,
36
+ * allowing Claude CLI's native tool_use events to flow through the
37
+ * parser+translator pipeline.
38
+ *
39
+ * Strict opt-in: only true when env value is exactly `'1'`.
40
+ */
41
+ export function isToolStreamMode() {
42
+ return process.env.CC_OPENCLAW_TOOL_STREAM === '1';
43
+ }
44
+ //# sourceMappingURL=mode-flags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mode-flags.js","sourceRoot":"","sources":["../../../src/openai-compat/mode-flags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAElE;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B;IAC1C,MAAM,CAAC,GAAG,8BAA8B,EAAE,CAAC;IAC3C,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACjC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,KAAK,CAAC;AAClD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,OAAO,CAAC,GAAG,CAAC,uBAAuB,KAAK,GAAG,CAAC;AACrD,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Cluster B Phase 2 — Non-streaming HTTP handler.
3
+ *
4
+ * Extracted verbatim from `openai-compat.ts:508-615` (Module G).
5
+ * Handles the response path when the caller did NOT request `stream:true` —
6
+ * a single buffered Claude CLI session call, then one JSON object back to
7
+ * the client.
8
+ *
9
+ * Lifecycle:
10
+ * 1. reportStatus('thinking') → SessionManager.sendMessage(...)
11
+ * 2. On each tool_use event → push a status update + emit trajectory
12
+ * 3. On each tool_result event → emit trajectory only (no status)
13
+ * 4. v0.7.1: accumulate thinking-block content when surfaceThinking is on
14
+ * 5. On completion → read token stats, format response, write back
15
+ * 6. On error → reportStatus('idle','Request failed') + 500 with
16
+ * structured `formatError` telemetry
17
+ *
18
+ * Parameterized fully — no closure capture from openai-compat.ts. All
19
+ * dependencies are imported from their dedicated Cluster B modules
20
+ * (response-formatter, tool-calls-parser, status-reporter,
21
+ * message-extractor for types) or from lib/.
22
+ */
23
+ import type * as http from 'node:http';
24
+ import type { SessionManagerLike } from './openai-compat.js';
25
+ import type { UserMessageBlock } from './message-extractor.js';
26
+ export declare function handleNonStreaming(manager: SessionManagerLike, sessionName: string, model: string, userMessage: string | UserMessageBlock[], completionId: string, res: http.ServerResponse, hasTools: boolean): Promise<void>;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Cluster B Phase 2 — Non-streaming HTTP handler.
3
+ *
4
+ * Extracted verbatim from `openai-compat.ts:508-615` (Module G).
5
+ * Handles the response path when the caller did NOT request `stream:true` —
6
+ * a single buffered Claude CLI session call, then one JSON object back to
7
+ * the client.
8
+ *
9
+ * Lifecycle:
10
+ * 1. reportStatus('thinking') → SessionManager.sendMessage(...)
11
+ * 2. On each tool_use event → push a status update + emit trajectory
12
+ * 3. On each tool_result event → emit trajectory only (no status)
13
+ * 4. v0.7.1: accumulate thinking-block content when surfaceThinking is on
14
+ * 5. On completion → read token stats, format response, write back
15
+ * 6. On error → reportStatus('idle','Request failed') + 500 with
16
+ * structured `formatError` telemetry
17
+ *
18
+ * Parameterized fully — no closure capture from openai-compat.ts. All
19
+ * dependencies are imported from their dedicated Cluster B modules
20
+ * (response-formatter, tool-calls-parser, status-reporter,
21
+ * message-extractor for types) or from lib/.
22
+ */
23
+ import { reportStatus, getToolDescription } from './status-reporter.js';
24
+ import { parseToolCallsFromText } from './tool-calls-parser.js';
25
+ import { formatCompletionResponse } from './response-formatter.js';
26
+ import { getSurfaceThinkingEnabled } from '../lib/config.js';
27
+ import { emit as emitTrajectory } from '../lib/trajectory.js';
28
+ import { formatError, ERROR_CODES } from '../lib/error-formatter.js';
29
+ export async function handleNonStreaming(manager, sessionName, model,
30
+ // Phase 2 R4 wire-up: accepts native content-block arrays in tool-stream mode.
31
+ userMessage, completionId, res, hasTools) {
32
+ try {
33
+ reportStatus('thinking', 'Processing request...');
34
+ // v0.7.1: accumulate thinking-block content when surfaceThinking is on.
35
+ // Default OFF for privacy — empty string means no `reasoning` field
36
+ // gets attached to the response.
37
+ const surfaceThinking = getSurfaceThinkingEnabled();
38
+ let thinkingBuffer = '';
39
+ const result = await manager.sendMessage(sessionName, userMessage, {
40
+ onEvent: (event) => {
41
+ if (event.type === 'tool_use' && event.tool?.name) {
42
+ const desc = getToolDescription(event.tool.name, event.tool.input);
43
+ reportStatus('working', desc, event.tool.name);
44
+ // Pillar B v0.4.3: trajectory tool_use event. Emit tool name and
45
+ // input-arg keys (not values — keys leak no sensitive content
46
+ // while still letting offline analysis cluster tool-call shapes).
47
+ emitTrajectory('tool_use', {
48
+ name: event.tool.name,
49
+ inputKeys: event.tool.input ? Object.keys(event.tool.input) : [],
50
+ }, sessionName);
51
+ }
52
+ else if (event.type === 'tool_result') {
53
+ emitTrajectory('tool_result', {}, sessionName);
54
+ }
55
+ },
56
+ // v0.7.1: when surfaceThinking is on, accumulate extended-thinking text
57
+ // for the `reasoning` field on the OpenAI response. Subscribing to the
58
+ // callback always (cheap closure cost ~ none); only buffering when
59
+ // the env flag is set so the privacy-default-OFF promise holds.
60
+ onThinking: surfaceThinking
61
+ ? (text) => {
62
+ thinkingBuffer += text;
63
+ }
64
+ : undefined,
65
+ });
66
+ reportStatus('idle', 'Ready');
67
+ let tokensIn = 0;
68
+ let tokensOut = 0;
69
+ try {
70
+ const status = manager.getStatus(sessionName);
71
+ tokensIn = status.stats.tokensIn;
72
+ tokensOut = status.stats.tokensOut;
73
+ }
74
+ catch {
75
+ /* stats unavailable */
76
+ }
77
+ // v0.7.1: emit thinking_block trajectory event with token-count metadata
78
+ // only (never raw text). Fires when buffer is non-empty regardless of
79
+ // whether the response surfaces it — so observability is independent
80
+ // of the user-visible flag.
81
+ if (thinkingBuffer.length > 0) {
82
+ emitTrajectory('thinking_block', {
83
+ excerpt_chars: thinkingBuffer.length,
84
+ tokens_approx: Math.ceil(thinkingBuffer.length / 4),
85
+ }, sessionName);
86
+ }
87
+ // Parse tool_calls from response text when caller provided tools
88
+ if (hasTools) {
89
+ const parsed = parseToolCallsFromText(result.output);
90
+ const response = formatCompletionResponse(completionId, model, parsed.textContent ?? '', tokensIn, tokensOut, parsed.toolCalls.length > 0 ? parsed.toolCalls : undefined, surfaceThinking ? thinkingBuffer : undefined);
91
+ res.writeHead(200, { 'Content-Type': 'application/json' });
92
+ res.end(JSON.stringify(response));
93
+ }
94
+ else {
95
+ const response = formatCompletionResponse(completionId, model, result.output, tokensIn, tokensOut, undefined, surfaceThinking ? thinkingBuffer : undefined);
96
+ res.writeHead(200, { 'Content-Type': 'application/json' });
97
+ res.end(JSON.stringify(response));
98
+ }
99
+ }
100
+ catch (err) {
101
+ reportStatus('idle', 'Request failed');
102
+ // v0.4.3: route through formatError for errors_total + trajectory error.
103
+ formatError(err, { code: ERROR_CODES.SESSION_ERROR, sessionId: sessionName, details: { phase: 'handleNonStreaming' } });
104
+ res.writeHead(500, { 'Content-Type': 'application/json' });
105
+ res.end(JSON.stringify({ error: { message: err.message, type: 'server_error' } }));
106
+ }
107
+ }
108
+ //# sourceMappingURL=non-streaming-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"non-streaming-handler.js","sourceRoot":"","sources":["../../../src/openai-compat/non-streaming-handler.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,17 @@
6
6
  * as a drop-in backend. Stateful sessions maximize Anthropic prompt caching.
7
7
  */
8
8
  import * as http from 'node:http';
9
+ import { type UserMessageBlock } from './message-extractor.js';
10
+ export { isToolsPerMessageModeEnabled, isToolStreamMode } from './mode-flags.js';
11
+ export { resolveSessionKey, sessionNameFromKey } from './session-key-resolver.js';
12
+ export { noToolsSystemPrompt, buildSessionSystemPrompt, buildToolPromptBlock } from './prompts.js';
13
+ export { parseToolCallsFromText, type ParsedToolCalls } from './tool-calls-parser.js';
14
+ export { serializeToolResults, serializeToolResultsAsBlocks, type AnthropicToolResultBlock, } from './tool-results-serializer.js';
15
+ export { extractUserMessage, type UserMessageBlock, type ExtractedMessage, } from './message-extractor.js';
16
+ export { formatCompletionResponse, formatCompletionChunk } from './response-formatter.js';
17
+ export { reportStatus, getToolDescription } from './status-reporter.js';
18
+ export { handleNonStreaming } from './non-streaming-handler.js';
19
+ export { handleStreaming } from './streaming-handler.js';
9
20
  export interface OpenAIChatMessage {
10
21
  role: 'system' | 'user' | 'assistant' | 'tool';
11
22
  content: string | Array<{
@@ -96,171 +107,10 @@ export interface OpenAIChatCompletionChunk {
96
107
  total_tokens: number;
97
108
  };
98
109
  }
99
- /**
100
- * Derive a session key from the request.
101
- * Priority: X-Session-Id header > user field > sha1(model + systemPrompt) > "default"
102
- *
103
- * The system-prompt-hash fallback prevents the bug where every caller without
104
- * X-Session-Id or `user` collapses onto a single shared "openai-default"
105
- * plugin session. In multi-caller setups (OpenClaw routing the main agent,
106
- * cron jobs, and subagents through the same gateway) that previously meant
107
- * every request serialized against every other and frequently picked up the
108
- * wrong session's appendSystemPrompt — also a privacy leak across callers.
109
- *
110
- * The model is mixed into the hash so that two callers with the same system
111
- * prompt but different requested models don't collide and silently get
112
- * responses from the wrong model. Originally diagnosed in PR #40 by
113
- * @megayounus786.
114
- */
115
- /**
116
- * When set (to '1', 'true', 'yes'), the proxy preserves the pre-fix behavior:
117
- * - tools injected into every user message
118
- * - session key NOT fingerprinted by tools (same session across tool changes)
119
- * Default (unset) is the new behavior: tools embedded in session system prompt
120
- * at create time + session key fingerprinted by tools. The new behavior
121
- * eliminates periodic latency spikes but does not support mutating the tool
122
- * list within a single session (a new session is created when tools change).
123
- */
124
- export declare function isToolsPerMessageModeEnabled(): boolean;
125
- /**
126
- * Phase 2 R5: tool-stream mode flag. When `CC_OPENCLAW_TOOL_STREAM=1` AND the
127
- * caller provides `tools[]`, cc-openclaw skips the defensive "no tools"
128
- * system prompt and does NOT clear `sessionConfig.tools`, allowing Claude
129
- * CLI's native tool_use events to flow through the new parser+translator
130
- * pipeline (Phase 4 Pillar 0.5). Default off; opt-in for the new path.
131
- */
132
- export declare function isToolStreamMode(): boolean;
133
- /**
134
- * Generate the "no built-in tools" system prompt preamble.
135
- * The `toolLocation` parameter controls how the model is told where to find
136
- * tool definitions — 'system' means "in the <available_tools> block below"
137
- * (tools baked into system prompt), 'user' means "in <available_tools> tags
138
- * in the user message" (legacy per-turn injection).
139
- */
140
- export declare function noToolsSystemPrompt(toolLocation: 'system' | 'user'): string;
141
- /**
142
- * Build the full session system prompt for a Claude Code session with tools.
143
- * Exported for testability — called from `handleChatCompletion`.
144
- *
145
- * - Default mode: tools are embedded in the system prompt (cacheable by Anthropic).
146
- * - Legacy mode (OPENAI_COMPAT_TOOLS_PER_MESSAGE=1): tools are NOT embedded;
147
- * they'll be injected per-turn in the user message instead.
148
- */
149
- export declare function buildSessionSystemPrompt(tools: OpenAIChatCompletionRequest['tools'], callerSystemPrompt: string | undefined): string;
150
- export declare function resolveSessionKey(body: OpenAIChatCompletionRequest, headers: http.IncomingHttpHeaders): string;
151
- /** Build the full session name from a key */
152
- export declare function sessionNameFromKey(key: string): string;
153
- /**
154
- * Convert OpenAI tool definitions into a structured prompt block.
155
- * Injected into the user message so the CLI model sees tool definitions
156
- * and responds with <tool_calls> tags when it wants to invoke a function.
157
- */
158
- export declare function buildToolPromptBlock(tools: OpenAIChatCompletionRequest['tools']): string;
159
- export interface ParsedToolCalls {
160
- textContent: string | null;
161
- toolCalls: OpenAIToolCall[];
162
- }
163
- /**
164
- * Parse tool_calls from CLI text output.
165
- *
166
- * Looks for <tool_calls>[...]</tool_calls> tags in the response text.
167
- * Returns both the extracted text content (before/after tags) and any tool calls found.
168
- */
169
- export declare function parseToolCallsFromText(text: string): ParsedToolCalls;
170
- /**
171
- * Serialize tool result messages into a text block for the CLI model.
172
- * Converts OpenAI `tool` role messages into <tool_result> tags.
173
- *
174
- * Legacy path (CC_OPENCLAW_TOOL_STREAM=0). Used when the model receives
175
- * tool definitions via the system prompt's <available_tools> XML block
176
- * and emits <tool_calls> XML in response. Tool-stream mode (R4) uses
177
- * `serializeToolResultsAsBlocks()` instead, returning native Anthropic
178
- * `tool_result` content blocks that Claude CLI parses directly.
179
- */
180
- export declare function serializeToolResults(messages: OpenAIChatMessage[]): string;
181
- /**
182
- * Phase 2 R4: native Anthropic `tool_result` content blocks.
183
- *
184
- * In tool-stream mode (CC_OPENCLAW_TOOL_STREAM=1), this replaces
185
- * `serializeToolResults()` XML wrapping. Returns blocks shaped like:
186
- *
187
- * { type: 'tool_result', tool_use_id: 'toolu_X', content: '...' }
188
- *
189
- * Claude CLI's stream-json input format accepts these as user-message
190
- * content arrays. The model then continues generation with the tool
191
- * result in context, emitting either more `tool_use` events or final
192
- * text — no XML round-tripping required.
193
- *
194
- * Wiring into the live request path lands with Phase 4 Pillar 0.5
195
- * tasks R1+R2 (parser+translator response-side integration), where
196
- * `extractUserMessage` produces structured content arrays in
197
- * tool-stream mode.
198
- */
199
- export interface AnthropicToolResultBlock {
200
- type: 'tool_result';
201
- tool_use_id: string;
202
- content: string;
203
- }
204
- export declare function serializeToolResultsAsBlocks(messages: OpenAIChatMessage[]): AnthropicToolResultBlock[];
205
- /**
206
- * Phase 2 R4 wire-up: optional structured content for tool-stream mode.
207
- * When set, consumers should send this array to the subprocess instead of
208
- * the legacy XML-wrapped `userMessage` string. Each element is either a
209
- * native Anthropic `tool_result` block (for prior tool outputs) or a
210
- * `text` block (for the user's follow-up message).
211
- */
212
- export type UserMessageBlock = AnthropicToolResultBlock | {
213
- type: 'text';
214
- text: string;
215
- };
216
- export interface ExtractedMessage {
217
- systemPrompt: string | undefined;
218
- userMessage: string;
219
- /**
220
- * Populated only in tool-stream mode when the last non-system message
221
- * is a tool-role result. Carries native Anthropic tool_result blocks
222
- * + optional follow-up text. When undefined, consumers fall back to
223
- * the string `userMessage` (legacy XML-wrapped path).
224
- */
225
- userMessageBlocks?: UserMessageBlock[];
226
- isNewConversation: boolean;
227
- }
228
- /**
229
- * Extract the relevant parts from an OpenAI messages array.
230
- *
231
- * Sessions are stateful — we only need the last user message. The tricky
232
- * question is whether to start a fresh session or append to the existing one.
233
- *
234
- * Default mode (no env var): only honor an explicit `X-Session-Reset: 1`
235
- * header. This is correct for clients that maintain their own conversation
236
- * transcript and forward only the latest user turn (OpenClaw main agent
237
- * loop, cron jobs, subagents). The previous heuristic
238
- * (`nonSystemMessages.length <= 1`) fired on every such request, killing the
239
- * persistent CLI every turn and preventing Anthropic prompt caching from
240
- * ever warming. Originally diagnosed in PR #40 by @megayounus786.
241
- *
242
- * Legacy mode (`OPENAI_COMPAT_NEW_CONVO_HEURISTIC=1`): restore the old
243
- * `system + single user ⇒ new conversation` rule, for clients that re-send
244
- * the full transcript on every turn (ChatGPT-Next-Web, Open WebUI, data
245
- * labeling tools, etc). They use the transcript shape itself as their only
246
- * "start a new conversation" signal.
247
- *
248
- * The env var is read on every call so ops can flip it via launchctl setenv
249
- * without restarting the server.
250
- */
251
- export declare function extractUserMessage(messages: OpenAIChatMessage[], headers?: Record<string, string | string[] | undefined>): ExtractedMessage;
252
- export declare function formatCompletionResponse(id: string, model: string, text: string, tokensIn: number, tokensOut: number, toolCalls?: OpenAIToolCall[],
253
- /** v0.7.0: when present + non-empty, attached as `choices[0].message.reasoning`
254
- * (mirrors OpenAI o1/o3 schema). Caller must already be gated on
255
- * `getSurfaceThinkingEnabled()` from `lib/config.ts` — this function does
256
- * not re-check the flag. Pass empty string or undefined to omit. */
257
- reasoning?: string): OpenAIChatCompletionResponse;
258
- export declare function formatCompletionChunk(id: string, model: string, delta: {
259
- role?: string;
260
- content?: string;
261
- }, finishReason: string | null): OpenAIChatCompletionChunk;
262
- /** SessionManager-like interface to avoid circular imports */
263
- interface SessionManagerLike {
110
+ /** SessionManager-like interface to avoid circular imports. Exported so
111
+ * the extracted handlers (non-streaming-handler.ts, streaming-handler.ts)
112
+ * can type their `manager` parameter from a single canonical definition. */
113
+ export interface SessionManagerLike {
264
114
  startSession(config: Record<string, unknown>): Promise<{
265
115
  name: string;
266
116
  }>;
@@ -283,4 +133,3 @@ interface SessionManagerLike {
283
133
  compactSession(name: string): Promise<unknown>;
284
134
  }
285
135
  export declare function handleChatCompletion(manager: SessionManagerLike, body: Record<string, unknown>, headers: http.IncomingHttpHeaders, res: http.ServerResponse): Promise<void>;
286
- export {};