sessioncast-cli 2.2.1 → 2.2.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.
@@ -6,4 +6,7 @@ export declare class LlmService {
6
6
  private callOllama;
7
7
  private convertOllamaToOpenAiFormat;
8
8
  private callOpenAi;
9
+ private callAnthropic;
10
+ private callClaudeCode;
11
+ private convertAnthropicToOpenAiFormat;
9
12
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LlmService = void 0;
4
+ const child_process_1 = require("child_process");
4
5
  class LlmService {
5
6
  constructor(config) {
6
7
  this.config = config || {
@@ -32,6 +33,10 @@ class LlmService {
32
33
  return await this.callOllama(actualModel, messages || [], temperature, maxTokens);
33
34
  case 'openai':
34
35
  return await this.callOpenAi(actualModel, messages || [], temperature, maxTokens);
36
+ case 'anthropic':
37
+ return await this.callAnthropic(actualModel, messages || [], temperature, maxTokens);
38
+ case 'claude-code':
39
+ return await this.callClaudeCode(actualModel, messages || [], maxTokens);
35
40
  default:
36
41
  return {
37
42
  id: '',
@@ -152,5 +157,202 @@ class LlmService {
152
157
  }
153
158
  return await response.json();
154
159
  }
160
+ async callAnthropic(model, messages, temperature, maxTokens) {
161
+ const baseUrl = this.config.baseUrl || 'https://api.anthropic.com';
162
+ const apiKey = this.config.apiKey;
163
+ if (!apiKey) {
164
+ return {
165
+ id: '',
166
+ object: 'error',
167
+ created: Date.now(),
168
+ model: '',
169
+ choices: [],
170
+ error: {
171
+ message: 'Anthropic API key not configured',
172
+ type: 'configuration_error'
173
+ }
174
+ };
175
+ }
176
+ // Separate system message from user/assistant messages
177
+ let systemPrompt;
178
+ const chatMessages = [];
179
+ for (const msg of messages) {
180
+ if (msg.role === 'system') {
181
+ systemPrompt = msg.content;
182
+ }
183
+ else {
184
+ chatMessages.push({ role: msg.role, content: msg.content });
185
+ }
186
+ }
187
+ const requestBody = {
188
+ model,
189
+ messages: chatMessages,
190
+ max_tokens: maxTokens || 1024
191
+ };
192
+ if (systemPrompt) {
193
+ requestBody.system = systemPrompt;
194
+ }
195
+ if (temperature !== undefined) {
196
+ requestBody.temperature = temperature;
197
+ }
198
+ const response = await fetch(`${baseUrl}/v1/messages`, {
199
+ method: 'POST',
200
+ headers: {
201
+ 'Content-Type': 'application/json',
202
+ 'x-api-key': apiKey,
203
+ 'anthropic-version': '2023-06-01'
204
+ },
205
+ body: JSON.stringify(requestBody)
206
+ });
207
+ if (!response.ok) {
208
+ const errorBody = await response.text();
209
+ throw new Error(`Anthropic returned status ${response.status}: ${errorBody}`);
210
+ }
211
+ const anthropicResponse = await response.json();
212
+ return this.convertAnthropicToOpenAiFormat(anthropicResponse, model);
213
+ }
214
+ async callClaudeCode(model, messages, maxTokens) {
215
+ // Build prompt from messages
216
+ const parts = [];
217
+ for (const msg of messages) {
218
+ if (msg.role === 'system') {
219
+ parts.push(`[System] ${msg.content}`);
220
+ }
221
+ else if (msg.role === 'user') {
222
+ parts.push(msg.content);
223
+ }
224
+ else if (msg.role === 'assistant') {
225
+ parts.push(`[Assistant] ${msg.content}`);
226
+ }
227
+ }
228
+ const prompt = parts.join('\n\n');
229
+ const args = ['-p', '--output-format', 'json', '--no-session-persistence'];
230
+ // Map model names: 'sonnet', 'opus', 'haiku' or full model ID
231
+ if (model && model !== 'default') {
232
+ args.push('--model', model);
233
+ }
234
+ return new Promise((resolve) => {
235
+ const env = { ...process.env };
236
+ delete env.CLAUDECODE;
237
+ delete env.CLAUDE_CODE;
238
+ const proc = (0, child_process_1.spawn)('claude', args, {
239
+ env,
240
+ stdio: ['pipe', 'pipe', 'pipe']
241
+ });
242
+ // Send prompt via stdin
243
+ proc.stdin.write(prompt);
244
+ proc.stdin.end();
245
+ let stdout = '';
246
+ let stderr = '';
247
+ proc.stdout.on('data', (data) => { stdout += data.toString(); });
248
+ proc.stderr.on('data', (data) => { stderr += data.toString(); });
249
+ const timer = setTimeout(() => {
250
+ proc.kill();
251
+ resolve({
252
+ id: '',
253
+ object: 'error',
254
+ created: Math.floor(Date.now() / 1000),
255
+ model,
256
+ choices: [],
257
+ error: { message: 'Claude Code timed out after 120s', type: 'timeout' }
258
+ });
259
+ }, 120000);
260
+ proc.on('close', (code) => {
261
+ clearTimeout(timer);
262
+ if (code !== 0) {
263
+ resolve({
264
+ id: '',
265
+ object: 'error',
266
+ created: Math.floor(Date.now() / 1000),
267
+ model,
268
+ choices: [],
269
+ error: {
270
+ message: `Claude Code exited with code ${code}${stderr ? `: ${stderr.trim()}` : ''}`,
271
+ type: 'internal_error'
272
+ }
273
+ });
274
+ return;
275
+ }
276
+ try {
277
+ // claude --output-format json returns a JSON array of events
278
+ // Find the "result" event which contains the final response
279
+ const events = JSON.parse(stdout);
280
+ const resultEvent = Array.isArray(events)
281
+ ? events.find((e) => e.type === 'result')
282
+ : events;
283
+ const content = resultEvent?.result || '';
284
+ const sessionId = resultEvent?.session_id || '';
285
+ const usage = resultEvent?.usage || {};
286
+ resolve({
287
+ id: sessionId || `claude-${Math.random().toString(36).substring(2, 10)}`,
288
+ object: 'chat.completion',
289
+ created: Math.floor(Date.now() / 1000),
290
+ model: model || 'claude-code',
291
+ choices: [{
292
+ index: 0,
293
+ message: {
294
+ role: 'assistant',
295
+ content
296
+ },
297
+ finish_reason: 'stop'
298
+ }],
299
+ usage: {
300
+ prompt_tokens: usage.input_tokens || 0,
301
+ completion_tokens: usage.output_tokens || 0,
302
+ total_tokens: (usage.input_tokens || 0) + (usage.output_tokens || 0)
303
+ }
304
+ });
305
+ }
306
+ catch (parseError) {
307
+ // Fallback: treat stdout as plain text
308
+ resolve({
309
+ id: `claude-${Math.random().toString(36).substring(2, 10)}`,
310
+ object: 'chat.completion',
311
+ created: Math.floor(Date.now() / 1000),
312
+ model: model || 'claude-code',
313
+ choices: [{
314
+ index: 0,
315
+ message: {
316
+ role: 'assistant',
317
+ content: stdout.trim()
318
+ },
319
+ finish_reason: 'stop'
320
+ }],
321
+ usage: {
322
+ prompt_tokens: 0,
323
+ completion_tokens: 0,
324
+ total_tokens: 0
325
+ }
326
+ });
327
+ }
328
+ });
329
+ });
330
+ }
331
+ convertAnthropicToOpenAiFormat(anthropicResponse, model) {
332
+ const content = (anthropicResponse.content || [])
333
+ .filter((block) => block.type === 'text')
334
+ .map((block) => block.text)
335
+ .join('');
336
+ const usage = anthropicResponse.usage || {};
337
+ return {
338
+ id: anthropicResponse.id || `msg-${Math.random().toString(36).substring(2, 10)}`,
339
+ object: 'chat.completion',
340
+ created: Math.floor(Date.now() / 1000),
341
+ model: anthropicResponse.model || model,
342
+ choices: [{
343
+ index: 0,
344
+ message: {
345
+ role: 'assistant',
346
+ content
347
+ },
348
+ finish_reason: anthropicResponse.stop_reason === 'end_turn' ? 'stop' : (anthropicResponse.stop_reason || 'stop')
349
+ }],
350
+ usage: {
351
+ prompt_tokens: usage.input_tokens || 0,
352
+ completion_tokens: usage.output_tokens || 0,
353
+ total_tokens: (usage.input_tokens || 0) + (usage.output_tokens || 0)
354
+ }
355
+ };
356
+ }
155
357
  }
156
358
  exports.LlmService = LlmService;
@@ -34,7 +34,7 @@ export interface ExecConfig {
34
34
  }
35
35
  export interface LlmConfig {
36
36
  enabled: boolean;
37
- provider: 'ollama' | 'openai';
37
+ provider: 'ollama' | 'openai' | 'anthropic' | 'claude-code';
38
38
  baseUrl: string;
39
39
  model: string;
40
40
  apiKey?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sessioncast-cli",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "description": "SessionCast CLI - Control your agents from anywhere",
5
5
  "main": "dist/index.js",
6
6
  "bin": {