@pikoloo/codex-proxy 1.0.6

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +199 -0
  3. package/bin/cli.js +118 -0
  4. package/docs/ACCOUNTS.md +202 -0
  5. package/docs/API.md +289 -0
  6. package/docs/ARCHITECTURE.md +129 -0
  7. package/docs/CLAUDE_INTEGRATION.md +163 -0
  8. package/docs/OAUTH.md +85 -0
  9. package/docs/OPENCLAW.md +34 -0
  10. package/docs/legal.md +11 -0
  11. package/images/dashboard-screenshot.png +0 -0
  12. package/images/demo-screenshot.png +0 -0
  13. package/images/f757093f-507b-4453-994e-f8275f8b07a9.png +0 -0
  14. package/package.json +61 -0
  15. package/public/css/style.css +1502 -0
  16. package/public/index.html +827 -0
  17. package/public/js/app.js +601 -0
  18. package/src/account-manager.js +528 -0
  19. package/src/account-rotation/index.js +93 -0
  20. package/src/account-rotation/rate-limits.js +293 -0
  21. package/src/account-rotation/strategies/base-strategy.js +48 -0
  22. package/src/account-rotation/strategies/index.js +31 -0
  23. package/src/account-rotation/strategies/round-robin-strategy.js +42 -0
  24. package/src/account-rotation/strategies/sticky-strategy.js +97 -0
  25. package/src/claude-config.js +153 -0
  26. package/src/cli/accounts.js +557 -0
  27. package/src/direct-api.js +164 -0
  28. package/src/format-converter.js +420 -0
  29. package/src/index.js +46 -0
  30. package/src/kilo-api.js +68 -0
  31. package/src/kilo-format-converter.js +285 -0
  32. package/src/kilo-models.js +103 -0
  33. package/src/kilo-streamer.js +243 -0
  34. package/src/middleware/credentials.js +116 -0
  35. package/src/middleware/sse.js +96 -0
  36. package/src/model-api.js +189 -0
  37. package/src/model-mapper.js +157 -0
  38. package/src/oauth.js +666 -0
  39. package/src/response-streamer.js +409 -0
  40. package/src/routes/accounts-route.js +332 -0
  41. package/src/routes/api-routes.js +98 -0
  42. package/src/routes/chat-route.js +229 -0
  43. package/src/routes/claude-config-route.js +121 -0
  44. package/src/routes/logs-route.js +43 -0
  45. package/src/routes/messages-route.js +203 -0
  46. package/src/routes/models-route.js +119 -0
  47. package/src/routes/settings-route.js +143 -0
  48. package/src/security.js +142 -0
  49. package/src/server-settings.js +56 -0
  50. package/src/server.js +58 -0
  51. package/src/signature-cache.js +106 -0
  52. package/src/thinking-utils.js +312 -0
  53. package/src/utils/logger.js +156 -0
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Kilo Format Converter
3
+ * Converts between Anthropic Messages API and OpenAI Chat Completions format
4
+ */
5
+
6
+ import { cleanCacheControl } from './thinking-utils.js';
7
+ import { toAnthropicToolId, toOpenAIToolId } from './format-converter.js';
8
+
9
+ function extractSystemPrompt(system) {
10
+ if (!system) return [];
11
+ if (typeof system === 'string') return [{ role: 'system', content: system }];
12
+ if (Array.isArray(system)) {
13
+ const text = system
14
+ .filter(block => block.type === 'text')
15
+ .map(block => block.text)
16
+ .join('\n\n');
17
+ return text ? [{ role: 'system', content: text }] : [];
18
+ }
19
+ return [];
20
+ }
21
+
22
+ function sanitizeSchema(schema) {
23
+ if (typeof schema !== 'object' || schema === null) {
24
+ return { type: 'object' };
25
+ }
26
+
27
+ const result = {};
28
+
29
+ for (const [key, value] of Object.entries(schema)) {
30
+ if (key === 'const') {
31
+ result.enum = [value];
32
+ continue;
33
+ }
34
+
35
+ if ([
36
+ '$schema', '$id', '$ref', '$defs', '$comment',
37
+ 'additionalItems', 'definitions', 'examples',
38
+ 'minLength', 'maxLength', 'pattern', 'format',
39
+ 'minItems', 'maxItems', 'minimum', 'maximum',
40
+ 'exclusiveMinimum', 'exclusiveMaximum',
41
+ 'allOf', 'anyOf', 'oneOf', 'not'
42
+ ].includes(key)) {
43
+ continue;
44
+ }
45
+
46
+ if (key === 'additionalProperties' && typeof value === 'boolean') {
47
+ continue;
48
+ }
49
+
50
+ if (key === 'type' && Array.isArray(value)) {
51
+ const nonNullTypes = value.filter(t => t !== 'null');
52
+ result.type = nonNullTypes.length > 0 ? nonNullTypes[0] : 'string';
53
+ continue;
54
+ }
55
+
56
+ if (key === 'properties' && value && typeof value === 'object') {
57
+ result.properties = {};
58
+ for (const [propKey, propValue] of Object.entries(value)) {
59
+ result.properties[propKey] = sanitizeSchema(propValue);
60
+ }
61
+ continue;
62
+ }
63
+
64
+ if (key === 'items') {
65
+ if (Array.isArray(value)) {
66
+ result.items = value.map(item => sanitizeSchema(item));
67
+ } else if (typeof value === 'object') {
68
+ result.items = sanitizeSchema(value);
69
+ } else {
70
+ result.items = value;
71
+ }
72
+ continue;
73
+ }
74
+
75
+ if (key === 'required' && Array.isArray(value)) {
76
+ result.required = value;
77
+ continue;
78
+ }
79
+
80
+ if (key === 'enum' && Array.isArray(value)) {
81
+ result.enum = value;
82
+ continue;
83
+ }
84
+
85
+ if (['type', 'description', 'title'].includes(key)) {
86
+ result[key] = value;
87
+ }
88
+ }
89
+
90
+ if (!result.type) {
91
+ result.type = 'object';
92
+ }
93
+
94
+ if (result.type === 'object' && !result.properties) {
95
+ result.properties = {};
96
+ }
97
+
98
+ return result;
99
+ }
100
+
101
+ function convertTools(tools) {
102
+ if (!Array.isArray(tools)) return undefined;
103
+ return tools.map(tool => ({
104
+ type: 'function',
105
+ function: {
106
+ name: tool.name,
107
+ description: tool.description || '',
108
+ parameters: sanitizeSchema(tool.input_schema || { type: 'object' })
109
+ }
110
+ }));
111
+ }
112
+
113
+ function convertToolChoice(toolChoice) {
114
+ if (!toolChoice) return undefined;
115
+ if (typeof toolChoice === 'string') return toolChoice;
116
+ if (toolChoice.type === 'tool' && toolChoice.name) {
117
+ return { type: 'function', function: { name: toolChoice.name } };
118
+ }
119
+ return undefined;
120
+ }
121
+
122
+ function normalizeTextBlocks(content) {
123
+ if (typeof content === 'string') return [content];
124
+ if (!Array.isArray(content)) return [];
125
+ return content.filter(block => block.type === 'text').map(block => block.text);
126
+ }
127
+
128
+ function normalizeToolResultContent(block) {
129
+ if (typeof block.content === 'string') return block.content;
130
+ if (Array.isArray(block.content)) {
131
+ return block.content.filter(c => c.type === 'text').map(c => c.text).join('\n');
132
+ }
133
+ if (block.content && typeof block.content === 'object') {
134
+ return JSON.stringify(block.content);
135
+ }
136
+ return '';
137
+ }
138
+
139
+ function convertMessages(messages = []) {
140
+ // Clean cache_control from messages first
141
+ const cleanedMessages = cleanCacheControl(messages);
142
+
143
+ const output = [];
144
+
145
+ for (const msg of cleanedMessages) {
146
+ if (msg.role === 'user') {
147
+ const textParts = normalizeTextBlocks(msg.content);
148
+ if (textParts.length > 0) {
149
+ output.push({ role: 'user', content: textParts.join('\n\n') });
150
+ }
151
+
152
+ if (Array.isArray(msg.content)) {
153
+ for (const block of msg.content) {
154
+ if (block.type === 'tool_result') {
155
+ output.push({
156
+ role: 'tool',
157
+ tool_call_id: toOpenAIToolId(block.tool_use_id),
158
+ content: normalizeToolResultContent(block)
159
+ });
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ if (msg.role === 'assistant') {
166
+ const textParts = normalizeTextBlocks(msg.content);
167
+ const toolCalls = [];
168
+
169
+ if (Array.isArray(msg.content)) {
170
+ for (const block of msg.content) {
171
+ if (block.type === 'tool_use') {
172
+ const openAIId = toOpenAIToolId(block.id);
173
+ toolCalls.push({
174
+ id: openAIId,
175
+ type: 'function',
176
+ function: {
177
+ name: block.name,
178
+ arguments: typeof block.input === 'string'
179
+ ? block.input
180
+ : JSON.stringify(block.input || {})
181
+ }
182
+ });
183
+ }
184
+ }
185
+ }
186
+
187
+ if (textParts.length > 0 || toolCalls.length > 0) {
188
+ const message = {
189
+ role: 'assistant',
190
+ content: textParts.length > 0 ? textParts.join('\n\n') : null
191
+ };
192
+
193
+ if (toolCalls.length > 0) {
194
+ message.tool_calls = toolCalls;
195
+ }
196
+
197
+ output.push(message);
198
+ }
199
+ }
200
+ }
201
+
202
+ return output;
203
+ }
204
+
205
+ export function convertAnthropicToOpenAIChat(anthropicRequest, targetModel) {
206
+ const { system, messages, tools, tool_choice, max_tokens, temperature, top_p, stop_sequences, stream } = anthropicRequest;
207
+
208
+ const convertedMessages = [
209
+ ...extractSystemPrompt(system),
210
+ ...convertMessages(messages || [])
211
+ ];
212
+
213
+ const request = {
214
+ model: targetModel,
215
+ messages: convertedMessages,
216
+ stream: stream !== false
217
+ };
218
+
219
+ if (typeof max_tokens === 'number') request.max_tokens = max_tokens;
220
+ if (typeof temperature === 'number') request.temperature = temperature;
221
+ if (typeof top_p === 'number') request.top_p = top_p;
222
+ if (Array.isArray(stop_sequences) && stop_sequences.length > 0) request.stop = stop_sequences;
223
+
224
+ const convertedTools = convertTools(tools);
225
+ if (convertedTools?.length) request.tools = convertedTools;
226
+
227
+ const convertedToolChoice = convertToolChoice(tool_choice);
228
+ if (convertedToolChoice) request.tool_choice = convertedToolChoice;
229
+
230
+ return request;
231
+ }
232
+
233
+ export function convertOpenAIChatToAnthropic(openAiResponse) {
234
+ const message = openAiResponse?.choices?.[0]?.message || {};
235
+ const content = [];
236
+
237
+ if (message.reasoning || message.reasoning_content) {
238
+ content.push({
239
+ type: 'thinking',
240
+ thinking: message.reasoning || message.reasoning_content,
241
+ signature: 'kilo-reasoning' // Placeholder signature
242
+ });
243
+ }
244
+
245
+ if (message.content) {
246
+ content.push({ type: 'text', text: message.content });
247
+ }
248
+
249
+ if (Array.isArray(message.tool_calls)) {
250
+ for (const call of message.tool_calls) {
251
+ let input = {};
252
+ try {
253
+ input = typeof call.function?.arguments === 'string'
254
+ ? JSON.parse(call.function.arguments)
255
+ : call.function?.arguments || {};
256
+ } catch (error) {
257
+ input = {};
258
+ }
259
+
260
+ content.push({
261
+ type: 'tool_use',
262
+ id: toAnthropicToolId(call.id),
263
+ name: call.function?.name || 'unknown',
264
+ input
265
+ });
266
+ }
267
+ }
268
+
269
+ const finishReason = openAiResponse?.choices?.[0]?.finish_reason;
270
+ const stopReason = finishReason === 'tool_calls' ? 'tool_use' : 'end_turn';
271
+
272
+ return {
273
+ content: content.length > 0 ? content : [{ type: 'text', text: '' }],
274
+ stopReason,
275
+ usage: {
276
+ input_tokens: openAiResponse?.usage?.prompt_tokens || 0,
277
+ output_tokens: openAiResponse?.usage?.completion_tokens || 0
278
+ }
279
+ };
280
+ }
281
+
282
+ export default {
283
+ convertAnthropicToOpenAIChat,
284
+ convertOpenAIChatToAnthropic
285
+ };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Kilo Models
3
+ * Fetches and caches available free models from the Kilo API.
4
+ */
5
+
6
+ const KILO_MODELS_URL = 'https://api.kilo.ai/api/openrouter/models';
7
+ const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
8
+
9
+ let cachedModels = null;
10
+ let cacheTimestamp = 0;
11
+
12
+ const KILO_HEADERS = {
13
+ Authorization: 'Bearer anonymous',
14
+ 'HTTP-Referer': 'https://kilo.ai'
15
+ };
16
+
17
+ /**
18
+ * Fetch all models from Kilo API and filter to free ones with tool support.
19
+ * Results are cached for 10 minutes.
20
+ * @returns {Promise<Array<{id: string, name: string, context_length: number}>>}
21
+ */
22
+ export async function fetchFreeModels() {
23
+ const now = Date.now();
24
+ if (cachedModels && (now - cacheTimestamp) < CACHE_TTL_MS) {
25
+ return cachedModels;
26
+ }
27
+
28
+ try {
29
+ const response = await fetch(KILO_MODELS_URL, {
30
+ headers: KILO_HEADERS
31
+ });
32
+
33
+ if (!response.ok) {
34
+ console.warn(`[KiloModels] Failed to fetch models: ${response.status}`);
35
+ return cachedModels || getHardcodedFallback();
36
+ }
37
+
38
+ const data = await response.json();
39
+ const allModels = data?.data || [];
40
+
41
+ const freeModels = allModels
42
+ .filter(m => m.isFree === true)
43
+ .filter(m => !m.id.includes('deprecated') && m.id !== 'kilo/auto-free')
44
+ .map(m => ({
45
+ id: m.id,
46
+ name: m.name || m.id,
47
+ context_length: m.context_length || 0,
48
+ supportsTools: (m.supported_parameters || []).includes('tools')
49
+ }));
50
+
51
+ cachedModels = freeModels;
52
+ cacheTimestamp = now;
53
+ console.log(`[KiloModels] Fetched ${freeModels.length} free models from API`);
54
+ return freeModels;
55
+ } catch (error) {
56
+ console.warn(`[KiloModels] Error fetching models: ${error.message}`);
57
+ return cachedModels || getHardcodedFallback();
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Get the list of free model IDs (just the id strings).
63
+ * @returns {Promise<string[]>}
64
+ */
65
+ export async function getFreeModelIds() {
66
+ const models = await fetchFreeModels();
67
+ return models.map(m => m.id);
68
+ }
69
+
70
+ /**
71
+ * Check if a given model ID is currently free.
72
+ * @param {string} modelId
73
+ * @returns {Promise<boolean>}
74
+ */
75
+ export async function isModelFree(modelId) {
76
+ const models = await fetchFreeModels();
77
+ return models.some(m => m.id === modelId);
78
+ }
79
+
80
+ /**
81
+ * Invalidate the cache (e.g. after a failed request).
82
+ */
83
+ export function invalidateCache() {
84
+ cachedModels = null;
85
+ cacheTimestamp = 0;
86
+ }
87
+
88
+ /**
89
+ * Hardcoded fallback in case the API is unreachable.
90
+ */
91
+ function getHardcodedFallback() {
92
+ return [
93
+ { id: 'minimax/minimax-m2.5:free', name: 'MiniMax M2.5', context_length: 204800, supportsTools: true },
94
+ { id: 'kilo-auto/free', name: 'Kilo Auto Free', context_length: 204800, supportsTools: true }
95
+ ];
96
+ }
97
+
98
+ export default {
99
+ fetchFreeModels,
100
+ getFreeModelIds,
101
+ isModelFree,
102
+ invalidateCache
103
+ };
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Kilo Streamer
3
+ * Streams OpenAI Chat Completions SSE and converts to Anthropic SSE events
4
+ */
5
+
6
+ import { generateMessageId, toAnthropicToolId } from './format-converter.js';
7
+
8
+ export async function* streamOpenAIChat(response, model) {
9
+ const messageId = generateMessageId();
10
+ let hasEmittedStart = false;
11
+ let blockIndex = 0;
12
+ let currentBlockType = null;
13
+ let currentToolCallId = null;
14
+ let currentToolName = null;
15
+ let pendingToolArgs = new Map();
16
+ let stopReason = 'end_turn';
17
+ let usage = { input_tokens: 0, output_tokens: 0 };
18
+
19
+ const reader = response.body.getReader();
20
+ const decoder = new TextDecoder();
21
+ let buffer = '';
22
+
23
+ const emitMessageStart = () => ({
24
+ event: 'message_start',
25
+ data: {
26
+ type: 'message_start',
27
+ message: {
28
+ id: messageId,
29
+ type: 'message',
30
+ role: 'assistant',
31
+ model,
32
+ content: [],
33
+ stop_reason: null,
34
+ stop_sequence: null,
35
+ usage: { input_tokens: 0, output_tokens: 0 }
36
+ }
37
+ }
38
+ });
39
+
40
+ const emitContentBlockStart = (contentBlock) => ({
41
+ event: 'content_block_start',
42
+ data: {
43
+ type: 'content_block_start',
44
+ index: blockIndex,
45
+ content_block: contentBlock
46
+ }
47
+ });
48
+
49
+ const emitContentBlockDelta = (delta) => ({
50
+ event: 'content_block_delta',
51
+ data: {
52
+ type: 'content_block_delta',
53
+ index: blockIndex,
54
+ delta
55
+ }
56
+ });
57
+
58
+ const emitContentBlockStop = () => ({
59
+ event: 'content_block_stop',
60
+ data: { type: 'content_block_stop', index: blockIndex }
61
+ });
62
+
63
+ const startTextBlock = () => {
64
+ currentBlockType = 'text';
65
+ currentToolCallId = null;
66
+ currentToolName = null;
67
+ return emitContentBlockStart({ type: 'text', text: '' });
68
+ };
69
+
70
+ const startThinkingBlock = () => {
71
+ currentBlockType = 'thinking';
72
+ currentToolCallId = null;
73
+ currentToolName = null;
74
+ return emitContentBlockStart({ type: 'thinking', thinking: '' });
75
+ };
76
+
77
+ const startToolBlock = (toolCall) => {
78
+ currentBlockType = 'tool_use';
79
+ const rawId = toolCall.id || `call_${Math.random().toString(36).slice(2)}`;
80
+ currentToolCallId = toAnthropicToolId(rawId);
81
+ currentToolName = toolCall.function?.name || 'tool';
82
+ stopReason = 'tool_use';
83
+ return emitContentBlockStart({
84
+ type: 'tool_use',
85
+ id: currentToolCallId,
86
+ name: currentToolName,
87
+ input: {}
88
+ });
89
+ };
90
+
91
+ const handleDelta = (delta) => {
92
+ const events = [];
93
+
94
+ // Handle reasoning/thinking content (from models like MiniMax M2.5)
95
+ const reasoningContent = delta.reasoning || delta.reasoning_content;
96
+ if (reasoningContent) {
97
+ if (!hasEmittedStart) {
98
+ hasEmittedStart = true;
99
+ events.push(emitMessageStart());
100
+ events.push(startThinkingBlock());
101
+ } else if (currentBlockType !== 'thinking') {
102
+ if (currentBlockType === 'thinking') {
103
+ events.push(emitContentBlockDelta({ type: 'signature_delta', signature: 'kilo-reasoning' }));
104
+ }
105
+ events.push(emitContentBlockStop());
106
+ blockIndex++;
107
+ events.push(startThinkingBlock());
108
+ }
109
+
110
+ events.push(emitContentBlockDelta({ type: 'thinking_delta', thinking: reasoningContent }));
111
+ }
112
+
113
+ const shouldStartText = (delta.content !== undefined && delta.content !== null) && (
114
+ delta.content.length > 0 || (!hasEmittedStart && !reasoningContent)
115
+ );
116
+
117
+ if (shouldStartText) {
118
+ if (!hasEmittedStart) {
119
+ hasEmittedStart = true;
120
+ events.push(emitMessageStart());
121
+ events.push(startTextBlock());
122
+ } else if (currentBlockType !== 'text') {
123
+ if (currentBlockType === 'thinking') {
124
+ events.push(emitContentBlockDelta({ type: 'signature_delta', signature: 'kilo-reasoning' }));
125
+ }
126
+ events.push(emitContentBlockStop());
127
+ blockIndex++;
128
+ events.push(startTextBlock());
129
+ }
130
+
131
+ if (delta.content.length > 0) {
132
+ events.push(emitContentBlockDelta({ type: 'text_delta', text: delta.content }));
133
+ }
134
+ }
135
+
136
+ if (Array.isArray(delta.tool_calls)) {
137
+ for (const toolCall of delta.tool_calls) {
138
+ if (!hasEmittedStart) {
139
+ hasEmittedStart = true;
140
+ events.push(emitMessageStart());
141
+ }
142
+
143
+ const toolId = toolCall.id ? toAnthropicToolId(toolCall.id) : currentToolCallId;
144
+
145
+ if (currentBlockType !== 'tool_use' || currentToolCallId !== toolId) {
146
+ if (currentBlockType) {
147
+ if (currentBlockType === 'thinking') {
148
+ events.push(emitContentBlockDelta({ type: 'signature_delta', signature: 'kilo-reasoning' }));
149
+ }
150
+ events.push(emitContentBlockStop());
151
+ blockIndex++;
152
+ }
153
+ events.push(startToolBlock(toolCall));
154
+ }
155
+
156
+ const argsDelta = toolCall.function?.arguments || '';
157
+ if (argsDelta) {
158
+ const callIdForArgs = toolCall.id || currentToolCallId;
159
+ const prev = pendingToolArgs.get(callIdForArgs) || '';
160
+ pendingToolArgs.set(callIdForArgs, prev + argsDelta);
161
+ events.push(emitContentBlockDelta({
162
+ type: 'input_json_delta',
163
+ partial_json: argsDelta
164
+ }));
165
+ }
166
+ }
167
+ }
168
+
169
+ return events;
170
+ };
171
+
172
+ while (true) {
173
+ const { done, value } = await reader.read();
174
+ if (done) break;
175
+
176
+ buffer += decoder.decode(value, { stream: true });
177
+ const lines = buffer.split('\n');
178
+ buffer = lines.pop() || '';
179
+
180
+ for (const line of lines) {
181
+ if (!line.startsWith('data:')) continue;
182
+ const jsonText = line.slice(5).trim();
183
+ if (!jsonText) continue;
184
+ if (jsonText === '[DONE]') continue;
185
+
186
+ try {
187
+ const chunk = JSON.parse(jsonText);
188
+
189
+ if (chunk.usage) {
190
+ usage = {
191
+ input_tokens: chunk.usage.prompt_tokens || 0,
192
+ output_tokens: chunk.usage.completion_tokens || 0
193
+ };
194
+ }
195
+
196
+ const choice = chunk.choices?.[0];
197
+ if (!choice) continue;
198
+
199
+ const events = handleDelta(choice.delta || {});
200
+ for (const evt of events) {
201
+ yield evt;
202
+ }
203
+
204
+ if (choice.finish_reason) {
205
+ stopReason = choice.finish_reason === 'tool_calls' ? 'tool_use' : 'end_turn';
206
+ }
207
+ } catch (err) {
208
+ // ignore malformed chunks
209
+ }
210
+ }
211
+ }
212
+
213
+ if (!hasEmittedStart) {
214
+ hasEmittedStart = true;
215
+ yield emitMessageStart();
216
+ yield emitContentBlockStart({ type: 'text', text: '' });
217
+ yield emitContentBlockDelta({ type: 'text_delta', text: '' });
218
+ yield emitContentBlockStop();
219
+ } else if (currentBlockType) {
220
+ if (currentBlockType === 'thinking') {
221
+ yield emitContentBlockDelta({ type: 'signature_delta', signature: 'kilo-reasoning' });
222
+ }
223
+ yield emitContentBlockStop();
224
+ }
225
+
226
+ yield {
227
+ event: 'message_delta',
228
+ data: {
229
+ type: 'message_delta',
230
+ delta: { stop_reason: stopReason, stop_sequence: null },
231
+ usage
232
+ }
233
+ };
234
+
235
+ yield {
236
+ event: 'message_stop',
237
+ data: { type: 'message_stop' }
238
+ };
239
+ }
240
+
241
+ export default {
242
+ streamOpenAIChat
243
+ };