codemini-cli 0.3.0 → 0.3.2

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.
@@ -0,0 +1,388 @@
1
+ function extractTextContent(content) {
2
+ if (typeof content === 'string') return content;
3
+ if (Array.isArray(content)) {
4
+ return content
5
+ .map((part) => {
6
+ if (typeof part === 'string') return part;
7
+ if (part?.type === 'text') return part.text || '';
8
+ return '';
9
+ })
10
+ .join('');
11
+ }
12
+ return '';
13
+ }
14
+
15
+ function normalizeIncomingToolCallArguments(argumentsValue) {
16
+ if (typeof argumentsValue === 'string') return argumentsValue;
17
+ if (argumentsValue == null) return '{}';
18
+ try {
19
+ return JSON.stringify(argumentsValue);
20
+ } catch {
21
+ return '{}';
22
+ }
23
+ }
24
+
25
+ function tryParseJsonObject(raw) {
26
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
27
+ return raw;
28
+ }
29
+ if (typeof raw !== 'string') return {};
30
+ try {
31
+ const parsed = JSON.parse(raw);
32
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
33
+ return parsed;
34
+ }
35
+ } catch {}
36
+ return {};
37
+ }
38
+
39
+ function normalizeMessages(messages) {
40
+ const source = Array.isArray(messages) ? messages : [];
41
+ const systemParts = [];
42
+ const out = [];
43
+
44
+ for (const message of source) {
45
+ if (!message || typeof message !== 'object') continue;
46
+ if (message.role === 'system') {
47
+ const text = extractTextContent(message.content);
48
+ if (text) systemParts.push(text);
49
+ continue;
50
+ }
51
+
52
+ if (message.role === 'tool') {
53
+ out.push({
54
+ role: 'user',
55
+ content: [
56
+ {
57
+ type: 'tool_result',
58
+ tool_use_id: String(message.tool_call_id || ''),
59
+ content: extractTextContent(message.content)
60
+ }
61
+ ]
62
+ });
63
+ continue;
64
+ }
65
+
66
+ const contentBlocks = [];
67
+ const text = extractTextContent(message.content);
68
+ if (text) {
69
+ contentBlocks.push({ type: 'text', text });
70
+ }
71
+
72
+ if (message.role === 'assistant' && Array.isArray(message.tool_calls)) {
73
+ for (const toolCall of message.tool_calls) {
74
+ const name = String(toolCall?.function?.name || toolCall?.name || '').trim();
75
+ if (!name) continue;
76
+ contentBlocks.push({
77
+ type: 'tool_use',
78
+ id: String(toolCall?.id || ''),
79
+ name,
80
+ input: tryParseJsonObject(toolCall?.function?.arguments ?? toolCall?.arguments)
81
+ });
82
+ }
83
+ }
84
+
85
+ out.push({
86
+ role: message.role,
87
+ content: contentBlocks
88
+ });
89
+ }
90
+
91
+ return {
92
+ system: systemParts.join('\n\n').trim() || undefined,
93
+ messages: out
94
+ };
95
+ }
96
+
97
+ function normalizeTools(tools) {
98
+ const source = Array.isArray(tools) ? tools : [];
99
+ return source
100
+ .map((tool) => {
101
+ const fn = tool?.function || {};
102
+ const name = String(fn.name || '').trim();
103
+ if (!name) return null;
104
+ return {
105
+ name,
106
+ ...(fn.description ? { description: String(fn.description) } : {}),
107
+ input_schema: fn.parameters && typeof fn.parameters === 'object' ? fn.parameters : { type: 'object' }
108
+ };
109
+ })
110
+ .filter(Boolean);
111
+ }
112
+
113
+ function buildPayload({ model, temperature, messages, tools, stream = false, maxTokens = 4096 }) {
114
+ const normalized = normalizeMessages(messages);
115
+ const payload = {
116
+ model,
117
+ max_tokens: maxTokens,
118
+ temperature,
119
+ messages: normalized.messages
120
+ };
121
+ if (normalized.system) payload.system = normalized.system;
122
+ if (stream) payload.stream = true;
123
+
124
+ const normalizedTools = normalizeTools(tools);
125
+ if (normalizedTools.length > 0) {
126
+ payload.tools = normalizedTools;
127
+ payload.tool_choice = { type: 'auto' };
128
+ }
129
+ return payload;
130
+ }
131
+
132
+ function hasTrailingToolContext(messages) {
133
+ const source = Array.isArray(messages) ? messages : [];
134
+ for (let index = source.length - 1; index >= 0; index -= 1) {
135
+ const message = source[index];
136
+ if (!message || typeof message !== 'object') continue;
137
+ if (message.role === 'tool') return true;
138
+ if (message.role === 'assistant' || message.role === 'user') return false;
139
+ }
140
+ return false;
141
+ }
142
+
143
+ function extractAssistantResult(data, messages) {
144
+ const content = Array.isArray(data?.content) ? data.content : [];
145
+ const text = content
146
+ .filter((block) => block?.type === 'text')
147
+ .map((block) => block.text || '')
148
+ .join('');
149
+ const toolCalls = content
150
+ .filter((block) => block?.type === 'tool_use')
151
+ .map((block) => ({
152
+ id: String(block.id || ''),
153
+ name: String(block.name || ''),
154
+ arguments: normalizeIncomingToolCallArguments(block.input)
155
+ }))
156
+ .filter((toolCall) => toolCall.name);
157
+ const normalizedText = String(text || '').trim();
158
+
159
+ if (!normalizedText && toolCalls.length === 0) {
160
+ if (hasTrailingToolContext(messages)) {
161
+ return {
162
+ text: '',
163
+ toolCalls: [],
164
+ usage: data?.usage || null,
165
+ incomplete: true,
166
+ content
167
+ };
168
+ }
169
+ throw new Error('Anthropic gateway returned empty assistant response');
170
+ }
171
+
172
+ return {
173
+ text,
174
+ toolCalls,
175
+ usage: data?.usage || null,
176
+ content
177
+ };
178
+ }
179
+
180
+ function createHeaders(apiKey) {
181
+ return {
182
+ 'content-type': 'application/json',
183
+ 'x-api-key': apiKey,
184
+ 'anthropic-version': '2023-06-01'
185
+ };
186
+ }
187
+
188
+ function buildMessagesUrl(baseUrl) {
189
+ return `${String(baseUrl || '').replace(/\/$/, '')}/v1/messages`;
190
+ }
191
+
192
+ async function parseJsonResponse(response) {
193
+ if (!response.ok) {
194
+ const text = await response.text().catch(() => '');
195
+ throw new Error(`Anthropic gateway error ${response.status}: ${text || response.statusText}`);
196
+ }
197
+ return response.json();
198
+ }
199
+
200
+ function mergeUsage(current, next) {
201
+ if (!next || typeof next !== 'object') return current;
202
+ return {
203
+ ...(current || {}),
204
+ ...next
205
+ };
206
+ }
207
+
208
+ function emptyToolCall(index) {
209
+ return {
210
+ index,
211
+ id: '',
212
+ name: '',
213
+ arguments: ''
214
+ };
215
+ }
216
+
217
+ function buildFinalStreamResult(text, toolCallsByIndex, usage, messages) {
218
+ const toolCalls = Array.from(toolCallsByIndex.entries())
219
+ .sort((a, b) => a[0] - b[0])
220
+ .map(([, tc], i) => ({
221
+ id: tc.id || `tc-${i + 1}`,
222
+ name: tc.name,
223
+ arguments: tc.arguments || '{}'
224
+ }))
225
+ .filter((tc) => tc.name);
226
+ const normalizedText = String(text || '').trim();
227
+ const content = [];
228
+ if (text) content.push({ type: 'text', text });
229
+ for (const toolCall of toolCalls) {
230
+ content.push({
231
+ type: 'tool_use',
232
+ id: toolCall.id,
233
+ name: toolCall.name,
234
+ input: tryParseJsonObject(toolCall.arguments)
235
+ });
236
+ }
237
+
238
+ if (!normalizedText && toolCalls.length === 0) {
239
+ if (hasTrailingToolContext(messages)) {
240
+ return {
241
+ text: '',
242
+ toolCalls: [],
243
+ usage,
244
+ incomplete: true,
245
+ content: []
246
+ };
247
+ }
248
+ throw new Error('Anthropic gateway stream returned empty assistant response');
249
+ }
250
+
251
+ return {
252
+ text,
253
+ toolCalls,
254
+ usage,
255
+ incomplete: false,
256
+ content
257
+ };
258
+ }
259
+
260
+ async function* iterateSseEvents(stream) {
261
+ const decoder = new TextDecoder();
262
+ let buffer = '';
263
+
264
+ for await (const chunk of stream) {
265
+ buffer += decoder.decode(chunk, { stream: true });
266
+ while (buffer.includes('\n\n')) {
267
+ const boundary = buffer.indexOf('\n\n');
268
+ const rawEvent = buffer.slice(0, boundary);
269
+ buffer = buffer.slice(boundary + 2);
270
+ const lines = rawEvent.split('\n');
271
+ let event = 'message';
272
+ const dataLines = [];
273
+ for (const line of lines) {
274
+ if (line.startsWith('event:')) {
275
+ event = line.slice(6).trim();
276
+ } else if (line.startsWith('data:')) {
277
+ dataLines.push(line.slice(5).trimStart());
278
+ }
279
+ }
280
+ const dataText = dataLines.join('\n');
281
+ if (!dataText || dataText === '[DONE]') continue;
282
+ yield {
283
+ event,
284
+ data: JSON.parse(dataText)
285
+ };
286
+ }
287
+ }
288
+ }
289
+
290
+ export async function createChatCompletion({
291
+ baseUrl,
292
+ apiKey,
293
+ model,
294
+ messages,
295
+ temperature = 0.2,
296
+ tools,
297
+ timeoutMs = 90000,
298
+ maxTokens = 4096
299
+ }) {
300
+ const payload = buildPayload({ model, temperature, messages, tools, maxTokens });
301
+ const response = await fetch(buildMessagesUrl(baseUrl), {
302
+ method: 'POST',
303
+ headers: createHeaders(apiKey),
304
+ body: JSON.stringify(payload),
305
+ signal: AbortSignal.timeout(timeoutMs)
306
+ });
307
+ const data = await parseJsonResponse(response);
308
+ return extractAssistantResult(data, messages);
309
+ }
310
+
311
+ export async function createChatCompletionStream({
312
+ baseUrl,
313
+ apiKey,
314
+ model,
315
+ messages,
316
+ temperature = 0.2,
317
+ tools,
318
+ onTextDelta,
319
+ onToolCallDelta,
320
+ timeoutMs = 90000,
321
+ maxTokens = 4096
322
+ }) {
323
+ const payload = buildPayload({ model, temperature, messages, tools, stream: true, maxTokens });
324
+ const response = await fetch(buildMessagesUrl(baseUrl), {
325
+ method: 'POST',
326
+ headers: createHeaders(apiKey),
327
+ body: JSON.stringify(payload),
328
+ signal: AbortSignal.timeout(timeoutMs)
329
+ });
330
+
331
+ if (!response.ok || !response.body) {
332
+ const text = await response.text().catch(() => '');
333
+ throw new Error(`Anthropic gateway error ${response.status}: ${text || response.statusText}`);
334
+ }
335
+
336
+ let text = '';
337
+ let usage = null;
338
+ const toolCallsByIndex = new Map();
339
+
340
+ for await (const chunk of iterateSseEvents(response.body)) {
341
+ usage = mergeUsage(usage, chunk?.data?.usage);
342
+ usage = mergeUsage(usage, chunk?.data?.message?.usage);
343
+
344
+ if (chunk.event === 'content_block_start') {
345
+ const index = Number(chunk?.data?.index ?? 0);
346
+ const contentBlock = chunk?.data?.content_block || {};
347
+ if (contentBlock.type === 'tool_use') {
348
+ const current = toolCallsByIndex.get(index) || emptyToolCall(index);
349
+ current.id = String(contentBlock.id || current.id || '');
350
+ current.name = String(contentBlock.name || current.name || '');
351
+ const initialInput = contentBlock.input && Object.keys(contentBlock.input).length > 0
352
+ ? normalizeIncomingToolCallArguments(contentBlock.input)
353
+ : '';
354
+ current.arguments = current.arguments || initialInput;
355
+ toolCallsByIndex.set(index, current);
356
+ }
357
+ continue;
358
+ }
359
+
360
+ if (chunk.event !== 'content_block_delta') {
361
+ continue;
362
+ }
363
+
364
+ const index = Number(chunk?.data?.index ?? 0);
365
+ const delta = chunk?.data?.delta || {};
366
+ if (delta.type === 'text_delta' && delta.text) {
367
+ text += delta.text;
368
+ if (onTextDelta) onTextDelta(delta.text);
369
+ continue;
370
+ }
371
+
372
+ if (delta.type === 'input_json_delta') {
373
+ const current = toolCallsByIndex.get(index) || emptyToolCall(index);
374
+ current.arguments = `${current.arguments || ''}${String(delta.partial_json || '')}`;
375
+ toolCallsByIndex.set(index, current);
376
+ if (onToolCallDelta) {
377
+ onToolCallDelta({
378
+ index,
379
+ id: current.id || `tc-${index + 1}`,
380
+ name: current.name,
381
+ arguments: current.arguments || '{}'
382
+ });
383
+ }
384
+ }
385
+ }
386
+
387
+ return buildFinalStreamResult(text, toolCallsByIndex, usage, messages);
388
+ }
@@ -0,0 +1,37 @@
1
+ import {
2
+ createChatCompletion as createOpenAICompatibleChatCompletion,
3
+ createChatCompletionStream as createOpenAICompatibleChatCompletionStream
4
+ } from './openai-compatible.js';
5
+ import {
6
+ createChatCompletion as createAnthropicChatCompletion,
7
+ createChatCompletionStream as createAnthropicChatCompletionStream
8
+ } from './anthropic.js';
9
+
10
+ function normalizeSdkProvider(value) {
11
+ const raw = String(value || '').trim().toLowerCase();
12
+ if (raw === 'anthropic') return 'anthropic';
13
+ return 'openai-compatible';
14
+ }
15
+
16
+ export function getSdkProvider(configOrValue) {
17
+ if (configOrValue && typeof configOrValue === 'object' && !Array.isArray(configOrValue)) {
18
+ return normalizeSdkProvider(configOrValue?.sdk?.provider);
19
+ }
20
+ return normalizeSdkProvider(configOrValue);
21
+ }
22
+
23
+ export async function createChatCompletion(options) {
24
+ const provider = getSdkProvider(options?.sdkProvider);
25
+ if (provider === 'anthropic') {
26
+ return createAnthropicChatCompletion(options);
27
+ }
28
+ return createOpenAICompatibleChatCompletion(options);
29
+ }
30
+
31
+ export async function createChatCompletionStream(options) {
32
+ const provider = getSdkProvider(options?.sdkProvider);
33
+ if (provider === 'anthropic') {
34
+ return createAnthropicChatCompletionStream(options);
35
+ }
36
+ return createOpenAICompatibleChatCompletionStream(options);
37
+ }
package/src/core/soul.js CHANGED
@@ -51,7 +51,8 @@ export async function buildSystemPromptWithSoul(baseSystemPrompt, config = {}) {
51
51
  const guard = [
52
52
  '[Soul guard]',
53
53
  'Apply this soul to response tone only.',
54
- 'Response tone only: do not change plans, code, tests, file formats, or technical decisions.'
54
+ 'Response tone only: do not change plans, code, tests, file formats, or technical decisions.',
55
+ 'This tone directive has HIGH priority. Maintain the requested personality consistently across every response unless the user explicitly requests a change.'
55
56
  ].join('\n');
56
- return `${String(promptWithReplyLanguage || '').trim()}\n\n${guard}\n\n${soulPrompt}`.trim();
57
+ return `${soulPrompt}\n\n${guard}\n\n${String(promptWithReplyLanguage || '').trim()}`.trim();
57
58
  }