cognitive-runtime 0.1.0 → 0.2.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/README.md CHANGED
@@ -21,7 +21,8 @@ Following the **Cognitive Runtime + Provider** architecture:
21
21
  │ └────────────────────────────────┘ │
22
22
  │ ┌────────────────────────────────┐ │
23
23
  │ │ Provider Abstraction │ │
24
- │ │ (gemini, openai, anthropic) │ │
24
+ │ │ (gemini, openai, anthropic, │ │
25
+ │ │ deepseek, minimax, qwen...) │ │
25
26
  │ └────────────────────────────────┘ │
26
27
  └─────────────────────────────────────┘
27
28
  ```
@@ -64,15 +65,24 @@ echo "review this code" | cog pipe --module code-reviewer
64
65
  cog doctor
65
66
  ```
66
67
 
67
- ## Configuration
68
+ ## Providers
68
69
 
69
- Set one of these environment variables:
70
+ | Provider | Environment Variable | Default Model |
71
+ |------------|------------------------|----------------------------------|
72
+ | Gemini | `GEMINI_API_KEY` | `gemini-2.5-flash-preview-05-20` |
73
+ | OpenAI | `OPENAI_API_KEY` | `gpt-4.1` |
74
+ | Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514` |
75
+ | DeepSeek | `DEEPSEEK_API_KEY` | `deepseek-chat` |
76
+ | MiniMax | `MINIMAX_API_KEY` | `MiniMax-Text-01` |
77
+ | Moonshot | `MOONSHOT_API_KEY` | `moonshot-v1-128k` |
78
+ | Qwen | `DASHSCOPE_API_KEY` | `qwen-max` |
79
+ | Ollama | `OLLAMA_HOST` | `llama3.3` (local) |
70
80
 
71
- ```bash
72
- export GEMINI_API_KEY="your-key" # Google Gemini
73
- export OPENAI_API_KEY="your-key" # OpenAI
74
- export ANTHROPIC_API_KEY="your-key" # Anthropic Claude
75
- ```
81
+ ### Provider Aliases
82
+
83
+ - `kimi` → Moonshot
84
+ - `tongyi` / `dashscope` → Qwen
85
+ - `local` → Ollama
76
86
 
77
87
  ## Module Search Paths
78
88
 
@@ -98,14 +108,6 @@ if (module) {
98
108
  }
99
109
  ```
100
110
 
101
- ## Providers
102
-
103
- | Provider | Environment Variable | Model Default |
104
- |------------|------------------------|------------------------|
105
- | Gemini | `GEMINI_API_KEY` | `gemini-2.0-flash` |
106
- | OpenAI | `OPENAI_API_KEY` | `gpt-4o` |
107
- | Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4` |
108
-
109
111
  ## Development
110
112
 
111
113
  ```bash
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@
10
10
  import { parseArgs } from 'node:util';
11
11
  import { getProvider, listProviders } from './providers/index.js';
12
12
  import { run, list, pipe, init } from './commands/index.js';
13
- const VERSION = '0.1.0';
13
+ const VERSION = '0.2.0';
14
14
  async function main() {
15
15
  const args = process.argv.slice(2);
16
16
  const command = args[0];
@@ -129,7 +129,7 @@ async function main() {
129
129
  console.log('Providers:');
130
130
  for (const p of listProviders()) {
131
131
  const status = p.configured ? '✓ configured' : '– not configured';
132
- console.log(` ${p.name}: ${status}`);
132
+ console.log(` ${p.name}: ${status} (${p.model})`);
133
133
  }
134
134
  console.log('');
135
135
  try {
@@ -189,9 +189,14 @@ EXAMPLES:
189
189
  cog doctor
190
190
 
191
191
  ENVIRONMENT:
192
- GEMINI_API_KEY Google Gemini API key
193
- OPENAI_API_KEY OpenAI API key
194
- ANTHROPIC_API_KEY Anthropic API key
192
+ GEMINI_API_KEY Google Gemini
193
+ OPENAI_API_KEY OpenAI
194
+ ANTHROPIC_API_KEY Anthropic Claude
195
+ DEEPSEEK_API_KEY DeepSeek
196
+ MINIMAX_API_KEY MiniMax
197
+ MOONSHOT_API_KEY Moonshot (Kimi)
198
+ DASHSCOPE_API_KEY Alibaba Qwen (通义千问)
199
+ OLLAMA_HOST Ollama local (default: localhost:11434)
195
200
  `);
196
201
  }
197
202
  main().catch(e => {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * DeepSeek Provider - DeepSeek API
3
+ */
4
+ import { BaseProvider } from './base.js';
5
+ import type { InvokeParams, InvokeResult } from '../types.js';
6
+ export declare class DeepSeekProvider extends BaseProvider {
7
+ name: string;
8
+ private apiKey;
9
+ private model;
10
+ private baseUrl;
11
+ constructor(apiKey?: string, model?: string);
12
+ isConfigured(): boolean;
13
+ invoke(params: InvokeParams): Promise<InvokeResult>;
14
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * DeepSeek Provider - DeepSeek API
3
+ */
4
+ import { BaseProvider } from './base.js';
5
+ export class DeepSeekProvider extends BaseProvider {
6
+ name = 'deepseek';
7
+ apiKey;
8
+ model;
9
+ baseUrl = 'https://api.deepseek.com/v1';
10
+ constructor(apiKey, model = 'deepseek-chat') {
11
+ super();
12
+ this.apiKey = apiKey || process.env.DEEPSEEK_API_KEY || '';
13
+ this.model = model;
14
+ }
15
+ isConfigured() {
16
+ return !!this.apiKey;
17
+ }
18
+ async invoke(params) {
19
+ if (!this.isConfigured()) {
20
+ throw new Error('DeepSeek API key not configured. Set DEEPSEEK_API_KEY environment variable.');
21
+ }
22
+ const url = `${this.baseUrl}/chat/completions`;
23
+ const body = {
24
+ model: this.model,
25
+ messages: params.messages.map(m => ({ role: m.role, content: m.content })),
26
+ temperature: params.temperature ?? 0.7,
27
+ max_tokens: params.maxTokens ?? 4096,
28
+ };
29
+ // Add JSON mode if schema provided
30
+ if (params.jsonSchema) {
31
+ body.response_format = { type: 'json_object' };
32
+ const lastUserIdx = params.messages.findLastIndex(m => m.role === 'user');
33
+ if (lastUserIdx >= 0) {
34
+ const messages = [...params.messages];
35
+ messages[lastUserIdx] = {
36
+ ...messages[lastUserIdx],
37
+ content: messages[lastUserIdx].content + this.buildJsonPrompt(params.jsonSchema),
38
+ };
39
+ body.messages = messages.map(m => ({ role: m.role, content: m.content }));
40
+ }
41
+ }
42
+ const response = await fetch(url, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ 'Authorization': `Bearer ${this.apiKey}`,
47
+ },
48
+ body: JSON.stringify(body),
49
+ });
50
+ if (!response.ok) {
51
+ const error = await response.text();
52
+ throw new Error(`DeepSeek API error: ${response.status} - ${error}`);
53
+ }
54
+ const data = await response.json();
55
+ const content = data.choices?.[0]?.message?.content || '';
56
+ return {
57
+ content,
58
+ usage: data.usage ? {
59
+ promptTokens: data.usage.prompt_tokens || 0,
60
+ completionTokens: data.usage.completion_tokens || 0,
61
+ totalTokens: data.usage.total_tokens || 0,
62
+ } : undefined,
63
+ };
64
+ }
65
+ }
@@ -7,7 +7,7 @@ export class GeminiProvider extends BaseProvider {
7
7
  apiKey;
8
8
  model;
9
9
  baseUrl = 'https://generativelanguage.googleapis.com/v1beta';
10
- constructor(apiKey, model = 'gemini-2.0-flash') {
10
+ constructor(apiKey, model = 'gemini-2.5-flash-preview-05-20') {
11
11
  super();
12
12
  this.apiKey = apiKey || process.env.GEMINI_API_KEY || '';
13
13
  this.model = model;
@@ -6,8 +6,14 @@ export { BaseProvider } from './base.js';
6
6
  export { GeminiProvider } from './gemini.js';
7
7
  export { OpenAIProvider } from './openai.js';
8
8
  export { AnthropicProvider } from './anthropic.js';
9
+ export { MiniMaxProvider } from './minimax.js';
10
+ export { DeepSeekProvider } from './deepseek.js';
11
+ export { MoonshotProvider } from './moonshot.js';
12
+ export { QwenProvider } from './qwen.js';
13
+ export { OllamaProvider } from './ollama.js';
9
14
  export declare function getProvider(name?: string): Provider;
10
15
  export declare function listProviders(): Array<{
11
16
  name: string;
12
17
  configured: boolean;
18
+ model: string;
13
19
  }>;
@@ -4,14 +4,33 @@
4
4
  import { GeminiProvider } from './gemini.js';
5
5
  import { OpenAIProvider } from './openai.js';
6
6
  import { AnthropicProvider } from './anthropic.js';
7
+ import { MiniMaxProvider } from './minimax.js';
8
+ import { DeepSeekProvider } from './deepseek.js';
9
+ import { MoonshotProvider } from './moonshot.js';
10
+ import { QwenProvider } from './qwen.js';
11
+ import { OllamaProvider } from './ollama.js';
7
12
  export { BaseProvider } from './base.js';
8
13
  export { GeminiProvider } from './gemini.js';
9
14
  export { OpenAIProvider } from './openai.js';
10
15
  export { AnthropicProvider } from './anthropic.js';
16
+ export { MiniMaxProvider } from './minimax.js';
17
+ export { DeepSeekProvider } from './deepseek.js';
18
+ export { MoonshotProvider } from './moonshot.js';
19
+ export { QwenProvider } from './qwen.js';
20
+ export { OllamaProvider } from './ollama.js';
11
21
  const providers = {
12
22
  gemini: () => new GeminiProvider(),
13
23
  openai: () => new OpenAIProvider(),
14
24
  anthropic: () => new AnthropicProvider(),
25
+ minimax: () => new MiniMaxProvider(),
26
+ deepseek: () => new DeepSeekProvider(),
27
+ moonshot: () => new MoonshotProvider(),
28
+ kimi: () => new MoonshotProvider(), // Alias
29
+ qwen: () => new QwenProvider(),
30
+ tongyi: () => new QwenProvider(), // Alias
31
+ dashscope: () => new QwenProvider(), // Alias
32
+ ollama: () => new OllamaProvider(),
33
+ local: () => new OllamaProvider(), // Alias
15
34
  };
16
35
  export function getProvider(name) {
17
36
  // Auto-detect if not specified
@@ -22,7 +41,16 @@ export function getProvider(name) {
22
41
  return new OpenAIProvider();
23
42
  if (process.env.ANTHROPIC_API_KEY)
24
43
  return new AnthropicProvider();
25
- throw new Error('No LLM provider configured. Set GEMINI_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY.');
44
+ if (process.env.DEEPSEEK_API_KEY)
45
+ return new DeepSeekProvider();
46
+ if (process.env.MINIMAX_API_KEY)
47
+ return new MiniMaxProvider();
48
+ if (process.env.MOONSHOT_API_KEY)
49
+ return new MoonshotProvider();
50
+ if (process.env.DASHSCOPE_API_KEY || process.env.QWEN_API_KEY)
51
+ return new QwenProvider();
52
+ // Ollama is always available as fallback if nothing else is configured
53
+ return new OllamaProvider();
26
54
  }
27
55
  const factory = providers[name.toLowerCase()];
28
56
  if (!factory) {
@@ -31,8 +59,14 @@ export function getProvider(name) {
31
59
  return factory();
32
60
  }
33
61
  export function listProviders() {
34
- return Object.entries(providers).map(([name, factory]) => ({
35
- name,
36
- configured: factory().isConfigured(),
37
- }));
62
+ return [
63
+ { name: 'gemini', configured: !!process.env.GEMINI_API_KEY, model: 'gemini-2.5-flash-preview-05-20' },
64
+ { name: 'openai', configured: !!process.env.OPENAI_API_KEY, model: 'gpt-4.1' },
65
+ { name: 'anthropic', configured: !!process.env.ANTHROPIC_API_KEY, model: 'claude-sonnet-4-20250514' },
66
+ { name: 'deepseek', configured: !!process.env.DEEPSEEK_API_KEY, model: 'deepseek-chat' },
67
+ { name: 'minimax', configured: !!process.env.MINIMAX_API_KEY, model: 'MiniMax-Text-01' },
68
+ { name: 'moonshot', configured: !!process.env.MOONSHOT_API_KEY, model: 'moonshot-v1-128k' },
69
+ { name: 'qwen', configured: !!(process.env.DASHSCOPE_API_KEY || process.env.QWEN_API_KEY), model: 'qwen-max' },
70
+ { name: 'ollama', configured: true, model: 'llama3.3 (local)' },
71
+ ];
38
72
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * MiniMax Provider - MiniMax API
3
+ */
4
+ import { BaseProvider } from './base.js';
5
+ import type { InvokeParams, InvokeResult } from '../types.js';
6
+ export declare class MiniMaxProvider extends BaseProvider {
7
+ name: string;
8
+ private apiKey;
9
+ private model;
10
+ private baseUrl;
11
+ constructor(apiKey?: string, model?: string);
12
+ isConfigured(): boolean;
13
+ invoke(params: InvokeParams): Promise<InvokeResult>;
14
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * MiniMax Provider - MiniMax API
3
+ */
4
+ import { BaseProvider } from './base.js';
5
+ export class MiniMaxProvider extends BaseProvider {
6
+ name = 'minimax';
7
+ apiKey;
8
+ model;
9
+ baseUrl = 'https://api.minimax.chat/v1';
10
+ constructor(apiKey, model = 'MiniMax-Text-01') {
11
+ super();
12
+ this.apiKey = apiKey || process.env.MINIMAX_API_KEY || '';
13
+ this.model = model;
14
+ }
15
+ isConfigured() {
16
+ return !!this.apiKey;
17
+ }
18
+ async invoke(params) {
19
+ if (!this.isConfigured()) {
20
+ throw new Error('MiniMax API key not configured. Set MINIMAX_API_KEY environment variable.');
21
+ }
22
+ const url = `${this.baseUrl}/text/chatcompletion_v2`;
23
+ const body = {
24
+ model: this.model,
25
+ messages: params.messages.map(m => ({ role: m.role, content: m.content })),
26
+ temperature: params.temperature ?? 0.7,
27
+ max_tokens: params.maxTokens ?? 4096,
28
+ };
29
+ // Add JSON mode if schema provided
30
+ if (params.jsonSchema) {
31
+ const lastUserIdx = params.messages.findLastIndex(m => m.role === 'user');
32
+ if (lastUserIdx >= 0) {
33
+ const messages = [...params.messages];
34
+ messages[lastUserIdx] = {
35
+ ...messages[lastUserIdx],
36
+ content: messages[lastUserIdx].content + this.buildJsonPrompt(params.jsonSchema),
37
+ };
38
+ body.messages = messages.map(m => ({ role: m.role, content: m.content }));
39
+ }
40
+ }
41
+ const response = await fetch(url, {
42
+ method: 'POST',
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ 'Authorization': `Bearer ${this.apiKey}`,
46
+ },
47
+ body: JSON.stringify(body),
48
+ });
49
+ if (!response.ok) {
50
+ const error = await response.text();
51
+ throw new Error(`MiniMax API error: ${response.status} - ${error}`);
52
+ }
53
+ const data = await response.json();
54
+ const content = data.choices?.[0]?.message?.content || '';
55
+ return {
56
+ content,
57
+ usage: data.usage ? {
58
+ promptTokens: data.usage.prompt_tokens || 0,
59
+ completionTokens: data.usage.completion_tokens || 0,
60
+ totalTokens: data.usage.total_tokens || 0,
61
+ } : undefined,
62
+ };
63
+ }
64
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Moonshot Provider - Moonshot AI (Kimi) API
3
+ */
4
+ import { BaseProvider } from './base.js';
5
+ import type { InvokeParams, InvokeResult } from '../types.js';
6
+ export declare class MoonshotProvider extends BaseProvider {
7
+ name: string;
8
+ private apiKey;
9
+ private model;
10
+ private baseUrl;
11
+ constructor(apiKey?: string, model?: string);
12
+ isConfigured(): boolean;
13
+ invoke(params: InvokeParams): Promise<InvokeResult>;
14
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Moonshot Provider - Moonshot AI (Kimi) API
3
+ */
4
+ import { BaseProvider } from './base.js';
5
+ export class MoonshotProvider extends BaseProvider {
6
+ name = 'moonshot';
7
+ apiKey;
8
+ model;
9
+ baseUrl = 'https://api.moonshot.cn/v1';
10
+ constructor(apiKey, model = 'moonshot-v1-128k') {
11
+ super();
12
+ this.apiKey = apiKey || process.env.MOONSHOT_API_KEY || '';
13
+ this.model = model;
14
+ }
15
+ isConfigured() {
16
+ return !!this.apiKey;
17
+ }
18
+ async invoke(params) {
19
+ if (!this.isConfigured()) {
20
+ throw new Error('Moonshot API key not configured. Set MOONSHOT_API_KEY environment variable.');
21
+ }
22
+ const url = `${this.baseUrl}/chat/completions`;
23
+ const body = {
24
+ model: this.model,
25
+ messages: params.messages.map(m => ({ role: m.role, content: m.content })),
26
+ temperature: params.temperature ?? 0.7,
27
+ max_tokens: params.maxTokens ?? 4096,
28
+ };
29
+ // Add JSON mode if schema provided
30
+ if (params.jsonSchema) {
31
+ body.response_format = { type: 'json_object' };
32
+ const lastUserIdx = params.messages.findLastIndex(m => m.role === 'user');
33
+ if (lastUserIdx >= 0) {
34
+ const messages = [...params.messages];
35
+ messages[lastUserIdx] = {
36
+ ...messages[lastUserIdx],
37
+ content: messages[lastUserIdx].content + this.buildJsonPrompt(params.jsonSchema),
38
+ };
39
+ body.messages = messages.map(m => ({ role: m.role, content: m.content }));
40
+ }
41
+ }
42
+ const response = await fetch(url, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ 'Authorization': `Bearer ${this.apiKey}`,
47
+ },
48
+ body: JSON.stringify(body),
49
+ });
50
+ if (!response.ok) {
51
+ const error = await response.text();
52
+ throw new Error(`Moonshot API error: ${response.status} - ${error}`);
53
+ }
54
+ const data = await response.json();
55
+ const content = data.choices?.[0]?.message?.content || '';
56
+ return {
57
+ content,
58
+ usage: data.usage ? {
59
+ promptTokens: data.usage.prompt_tokens || 0,
60
+ completionTokens: data.usage.completion_tokens || 0,
61
+ totalTokens: data.usage.total_tokens || 0,
62
+ } : undefined,
63
+ };
64
+ }
65
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Ollama Provider - Local LLM via Ollama
3
+ */
4
+ import { BaseProvider } from './base.js';
5
+ import type { InvokeParams, InvokeResult } from '../types.js';
6
+ export declare class OllamaProvider extends BaseProvider {
7
+ name: string;
8
+ private model;
9
+ private baseUrl;
10
+ constructor(model?: string, baseUrl?: string);
11
+ isConfigured(): boolean;
12
+ invoke(params: InvokeParams): Promise<InvokeResult>;
13
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Ollama Provider - Local LLM via Ollama
3
+ */
4
+ import { BaseProvider } from './base.js';
5
+ export class OllamaProvider extends BaseProvider {
6
+ name = 'ollama';
7
+ model;
8
+ baseUrl;
9
+ constructor(model = 'llama3.3', baseUrl = 'http://localhost:11434') {
10
+ super();
11
+ this.model = process.env.OLLAMA_MODEL || model;
12
+ this.baseUrl = process.env.OLLAMA_HOST || baseUrl;
13
+ }
14
+ isConfigured() {
15
+ return true; // Ollama doesn't need API key
16
+ }
17
+ async invoke(params) {
18
+ const url = `${this.baseUrl}/api/chat`;
19
+ let messages = params.messages.map(m => ({ role: m.role, content: m.content }));
20
+ // Add JSON mode if schema provided
21
+ if (params.jsonSchema) {
22
+ const lastUserIdx = messages.findLastIndex(m => m.role === 'user');
23
+ if (lastUserIdx >= 0) {
24
+ messages = [...messages];
25
+ messages[lastUserIdx] = {
26
+ ...messages[lastUserIdx],
27
+ content: messages[lastUserIdx].content + this.buildJsonPrompt(params.jsonSchema),
28
+ };
29
+ }
30
+ }
31
+ const body = {
32
+ model: this.model,
33
+ messages,
34
+ stream: false,
35
+ options: {
36
+ temperature: params.temperature ?? 0.7,
37
+ num_predict: params.maxTokens ?? 4096,
38
+ },
39
+ };
40
+ // Request JSON format
41
+ if (params.jsonSchema) {
42
+ body.format = 'json';
43
+ }
44
+ const response = await fetch(url, {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json' },
47
+ body: JSON.stringify(body),
48
+ });
49
+ if (!response.ok) {
50
+ const error = await response.text();
51
+ throw new Error(`Ollama API error: ${response.status} - ${error}`);
52
+ }
53
+ const data = await response.json();
54
+ const content = data.message?.content || '';
55
+ return {
56
+ content,
57
+ usage: {
58
+ promptTokens: data.prompt_eval_count || 0,
59
+ completionTokens: data.eval_count || 0,
60
+ totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0),
61
+ },
62
+ };
63
+ }
64
+ }
@@ -7,7 +7,7 @@ export class OpenAIProvider extends BaseProvider {
7
7
  apiKey;
8
8
  model;
9
9
  baseUrl;
10
- constructor(apiKey, model = 'gpt-4o', baseUrl = 'https://api.openai.com/v1') {
10
+ constructor(apiKey, model = 'gpt-4.1', baseUrl = 'https://api.openai.com/v1') {
11
11
  super();
12
12
  this.apiKey = apiKey || process.env.OPENAI_API_KEY || '';
13
13
  this.model = model;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Qwen Provider - Alibaba Tongyi Qianwen (通义千问) via DashScope API
3
+ */
4
+ import { BaseProvider } from './base.js';
5
+ import type { InvokeParams, InvokeResult } from '../types.js';
6
+ export declare class QwenProvider extends BaseProvider {
7
+ name: string;
8
+ private apiKey;
9
+ private model;
10
+ private baseUrl;
11
+ constructor(apiKey?: string, model?: string);
12
+ isConfigured(): boolean;
13
+ invoke(params: InvokeParams): Promise<InvokeResult>;
14
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Qwen Provider - Alibaba Tongyi Qianwen (通义千问) via DashScope API
3
+ */
4
+ import { BaseProvider } from './base.js';
5
+ export class QwenProvider extends BaseProvider {
6
+ name = 'qwen';
7
+ apiKey;
8
+ model;
9
+ baseUrl = 'https://dashscope.aliyuncs.com/compatible-mode/v1';
10
+ constructor(apiKey, model = 'qwen-max') {
11
+ super();
12
+ this.apiKey = apiKey || process.env.DASHSCOPE_API_KEY || process.env.QWEN_API_KEY || '';
13
+ this.model = model;
14
+ }
15
+ isConfigured() {
16
+ return !!this.apiKey;
17
+ }
18
+ async invoke(params) {
19
+ if (!this.isConfigured()) {
20
+ throw new Error('Qwen API key not configured. Set DASHSCOPE_API_KEY or QWEN_API_KEY environment variable.');
21
+ }
22
+ const url = `${this.baseUrl}/chat/completions`;
23
+ const body = {
24
+ model: this.model,
25
+ messages: params.messages.map(m => ({ role: m.role, content: m.content })),
26
+ temperature: params.temperature ?? 0.7,
27
+ max_tokens: params.maxTokens ?? 4096,
28
+ };
29
+ // Add JSON mode if schema provided
30
+ if (params.jsonSchema) {
31
+ body.response_format = { type: 'json_object' };
32
+ const lastUserIdx = params.messages.findLastIndex(m => m.role === 'user');
33
+ if (lastUserIdx >= 0) {
34
+ const messages = [...params.messages];
35
+ messages[lastUserIdx] = {
36
+ ...messages[lastUserIdx],
37
+ content: messages[lastUserIdx].content + this.buildJsonPrompt(params.jsonSchema),
38
+ };
39
+ body.messages = messages.map(m => ({ role: m.role, content: m.content }));
40
+ }
41
+ }
42
+ const response = await fetch(url, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ 'Authorization': `Bearer ${this.apiKey}`,
47
+ },
48
+ body: JSON.stringify(body),
49
+ });
50
+ if (!response.ok) {
51
+ const error = await response.text();
52
+ throw new Error(`Qwen API error: ${response.status} - ${error}`);
53
+ }
54
+ const data = await response.json();
55
+ const content = data.choices?.[0]?.message?.content || '';
56
+ return {
57
+ content,
58
+ usage: data.usage ? {
59
+ promptTokens: data.usage.prompt_tokens || 0,
60
+ completionTokens: data.usage.completion_tokens || 0,
61
+ totalTokens: data.usage.total_tokens || 0,
62
+ } : undefined,
63
+ };
64
+ }
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognitive-runtime",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Cognitive Runtime - Structured AI Task Execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/cli.ts CHANGED
@@ -13,7 +13,7 @@ import { getProvider, listProviders } from './providers/index.js';
13
13
  import { run, list, pipe, init } from './commands/index.js';
14
14
  import type { CommandContext } from './types.js';
15
15
 
16
- const VERSION = '0.1.0';
16
+ const VERSION = '0.2.0';
17
17
 
18
18
  async function main() {
19
19
  const args = process.argv.slice(2);
@@ -150,7 +150,7 @@ async function main() {
150
150
  console.log('Providers:');
151
151
  for (const p of listProviders()) {
152
152
  const status = p.configured ? '✓ configured' : '– not configured';
153
- console.log(` ${p.name}: ${status}`);
153
+ console.log(` ${p.name}: ${status} (${p.model})`);
154
154
  }
155
155
  console.log('');
156
156
 
@@ -211,9 +211,14 @@ EXAMPLES:
211
211
  cog doctor
212
212
 
213
213
  ENVIRONMENT:
214
- GEMINI_API_KEY Google Gemini API key
215
- OPENAI_API_KEY OpenAI API key
216
- ANTHROPIC_API_KEY Anthropic API key
214
+ GEMINI_API_KEY Google Gemini
215
+ OPENAI_API_KEY OpenAI
216
+ ANTHROPIC_API_KEY Anthropic Claude
217
+ DEEPSEEK_API_KEY DeepSeek
218
+ MINIMAX_API_KEY MiniMax
219
+ MOONSHOT_API_KEY Moonshot (Kimi)
220
+ DASHSCOPE_API_KEY Alibaba Qwen (通义千问)
221
+ OLLAMA_HOST Ollama local (default: localhost:11434)
217
222
  `);
218
223
  }
219
224
 
@@ -0,0 +1,82 @@
1
+ /**
2
+ * DeepSeek Provider - DeepSeek API
3
+ */
4
+
5
+ import { BaseProvider } from './base.js';
6
+ import type { InvokeParams, InvokeResult } from '../types.js';
7
+
8
+ export class DeepSeekProvider extends BaseProvider {
9
+ name = 'deepseek';
10
+ private apiKey: string;
11
+ private model: string;
12
+ private baseUrl = 'https://api.deepseek.com/v1';
13
+
14
+ constructor(apiKey?: string, model = 'deepseek-chat') {
15
+ super();
16
+ this.apiKey = apiKey || process.env.DEEPSEEK_API_KEY || '';
17
+ this.model = model;
18
+ }
19
+
20
+ isConfigured(): boolean {
21
+ return !!this.apiKey;
22
+ }
23
+
24
+ async invoke(params: InvokeParams): Promise<InvokeResult> {
25
+ if (!this.isConfigured()) {
26
+ throw new Error('DeepSeek API key not configured. Set DEEPSEEK_API_KEY environment variable.');
27
+ }
28
+
29
+ const url = `${this.baseUrl}/chat/completions`;
30
+
31
+ const body: Record<string, unknown> = {
32
+ model: this.model,
33
+ messages: params.messages.map(m => ({ role: m.role, content: m.content })),
34
+ temperature: params.temperature ?? 0.7,
35
+ max_tokens: params.maxTokens ?? 4096,
36
+ };
37
+
38
+ // Add JSON mode if schema provided
39
+ if (params.jsonSchema) {
40
+ body.response_format = { type: 'json_object' };
41
+ const lastUserIdx = params.messages.findLastIndex(m => m.role === 'user');
42
+ if (lastUserIdx >= 0) {
43
+ const messages = [...params.messages];
44
+ messages[lastUserIdx] = {
45
+ ...messages[lastUserIdx],
46
+ content: messages[lastUserIdx].content + this.buildJsonPrompt(params.jsonSchema),
47
+ };
48
+ body.messages = messages.map(m => ({ role: m.role, content: m.content }));
49
+ }
50
+ }
51
+
52
+ const response = await fetch(url, {
53
+ method: 'POST',
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ 'Authorization': `Bearer ${this.apiKey}`,
57
+ },
58
+ body: JSON.stringify(body),
59
+ });
60
+
61
+ if (!response.ok) {
62
+ const error = await response.text();
63
+ throw new Error(`DeepSeek API error: ${response.status} - ${error}`);
64
+ }
65
+
66
+ const data = await response.json() as {
67
+ choices?: Array<{ message?: { content?: string } }>;
68
+ usage?: { prompt_tokens?: number; completion_tokens?: number; total_tokens?: number };
69
+ };
70
+
71
+ const content = data.choices?.[0]?.message?.content || '';
72
+
73
+ return {
74
+ content,
75
+ usage: data.usage ? {
76
+ promptTokens: data.usage.prompt_tokens || 0,
77
+ completionTokens: data.usage.completion_tokens || 0,
78
+ totalTokens: data.usage.total_tokens || 0,
79
+ } : undefined,
80
+ };
81
+ }
82
+ }
@@ -11,7 +11,7 @@ export class GeminiProvider extends BaseProvider {
11
11
  private model: string;
12
12
  private baseUrl = 'https://generativelanguage.googleapis.com/v1beta';
13
13
 
14
- constructor(apiKey?: string, model = 'gemini-2.0-flash') {
14
+ constructor(apiKey?: string, model = 'gemini-2.5-flash-preview-05-20') {
15
15
  super();
16
16
  this.apiKey = apiKey || process.env.GEMINI_API_KEY || '';
17
17
  this.model = model;
@@ -6,16 +6,35 @@ import type { Provider } from '../types.js';
6
6
  import { GeminiProvider } from './gemini.js';
7
7
  import { OpenAIProvider } from './openai.js';
8
8
  import { AnthropicProvider } from './anthropic.js';
9
+ import { MiniMaxProvider } from './minimax.js';
10
+ import { DeepSeekProvider } from './deepseek.js';
11
+ import { MoonshotProvider } from './moonshot.js';
12
+ import { QwenProvider } from './qwen.js';
13
+ import { OllamaProvider } from './ollama.js';
9
14
 
10
15
  export { BaseProvider } from './base.js';
11
16
  export { GeminiProvider } from './gemini.js';
12
17
  export { OpenAIProvider } from './openai.js';
13
18
  export { AnthropicProvider } from './anthropic.js';
19
+ export { MiniMaxProvider } from './minimax.js';
20
+ export { DeepSeekProvider } from './deepseek.js';
21
+ export { MoonshotProvider } from './moonshot.js';
22
+ export { QwenProvider } from './qwen.js';
23
+ export { OllamaProvider } from './ollama.js';
14
24
 
15
25
  const providers: Record<string, () => Provider> = {
16
26
  gemini: () => new GeminiProvider(),
17
27
  openai: () => new OpenAIProvider(),
18
28
  anthropic: () => new AnthropicProvider(),
29
+ minimax: () => new MiniMaxProvider(),
30
+ deepseek: () => new DeepSeekProvider(),
31
+ moonshot: () => new MoonshotProvider(),
32
+ kimi: () => new MoonshotProvider(), // Alias
33
+ qwen: () => new QwenProvider(),
34
+ tongyi: () => new QwenProvider(), // Alias
35
+ dashscope: () => new QwenProvider(), // Alias
36
+ ollama: () => new OllamaProvider(),
37
+ local: () => new OllamaProvider(), // Alias
19
38
  };
20
39
 
21
40
  export function getProvider(name?: string): Provider {
@@ -24,7 +43,12 @@ export function getProvider(name?: string): Provider {
24
43
  if (process.env.GEMINI_API_KEY) return new GeminiProvider();
25
44
  if (process.env.OPENAI_API_KEY) return new OpenAIProvider();
26
45
  if (process.env.ANTHROPIC_API_KEY) return new AnthropicProvider();
27
- throw new Error('No LLM provider configured. Set GEMINI_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY.');
46
+ if (process.env.DEEPSEEK_API_KEY) return new DeepSeekProvider();
47
+ if (process.env.MINIMAX_API_KEY) return new MiniMaxProvider();
48
+ if (process.env.MOONSHOT_API_KEY) return new MoonshotProvider();
49
+ if (process.env.DASHSCOPE_API_KEY || process.env.QWEN_API_KEY) return new QwenProvider();
50
+ // Ollama is always available as fallback if nothing else is configured
51
+ return new OllamaProvider();
28
52
  }
29
53
 
30
54
  const factory = providers[name.toLowerCase()];
@@ -35,9 +59,15 @@ export function getProvider(name?: string): Provider {
35
59
  return factory();
36
60
  }
37
61
 
38
- export function listProviders(): Array<{ name: string; configured: boolean }> {
39
- return Object.entries(providers).map(([name, factory]) => ({
40
- name,
41
- configured: factory().isConfigured(),
42
- }));
62
+ export function listProviders(): Array<{ name: string; configured: boolean; model: string }> {
63
+ return [
64
+ { name: 'gemini', configured: !!process.env.GEMINI_API_KEY, model: 'gemini-2.5-flash-preview-05-20' },
65
+ { name: 'openai', configured: !!process.env.OPENAI_API_KEY, model: 'gpt-4.1' },
66
+ { name: 'anthropic', configured: !!process.env.ANTHROPIC_API_KEY, model: 'claude-sonnet-4-20250514' },
67
+ { name: 'deepseek', configured: !!process.env.DEEPSEEK_API_KEY, model: 'deepseek-chat' },
68
+ { name: 'minimax', configured: !!process.env.MINIMAX_API_KEY, model: 'MiniMax-Text-01' },
69
+ { name: 'moonshot', configured: !!process.env.MOONSHOT_API_KEY, model: 'moonshot-v1-128k' },
70
+ { name: 'qwen', configured: !!(process.env.DASHSCOPE_API_KEY || process.env.QWEN_API_KEY), model: 'qwen-max' },
71
+ { name: 'ollama', configured: true, model: 'llama3.3 (local)' },
72
+ ];
43
73
  }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * MiniMax Provider - MiniMax API
3
+ */
4
+
5
+ import { BaseProvider } from './base.js';
6
+ import type { InvokeParams, InvokeResult } from '../types.js';
7
+
8
+ export class MiniMaxProvider extends BaseProvider {
9
+ name = 'minimax';
10
+ private apiKey: string;
11
+ private model: string;
12
+ private baseUrl = 'https://api.minimax.chat/v1';
13
+
14
+ constructor(apiKey?: string, model = 'MiniMax-Text-01') {
15
+ super();
16
+ this.apiKey = apiKey || process.env.MINIMAX_API_KEY || '';
17
+ this.model = model;
18
+ }
19
+
20
+ isConfigured(): boolean {
21
+ return !!this.apiKey;
22
+ }
23
+
24
+ async invoke(params: InvokeParams): Promise<InvokeResult> {
25
+ if (!this.isConfigured()) {
26
+ throw new Error('MiniMax API key not configured. Set MINIMAX_API_KEY environment variable.');
27
+ }
28
+
29
+ const url = `${this.baseUrl}/text/chatcompletion_v2`;
30
+
31
+ const body: Record<string, unknown> = {
32
+ model: this.model,
33
+ messages: params.messages.map(m => ({ role: m.role, content: m.content })),
34
+ temperature: params.temperature ?? 0.7,
35
+ max_tokens: params.maxTokens ?? 4096,
36
+ };
37
+
38
+ // Add JSON mode if schema provided
39
+ if (params.jsonSchema) {
40
+ const lastUserIdx = params.messages.findLastIndex(m => m.role === 'user');
41
+ if (lastUserIdx >= 0) {
42
+ const messages = [...params.messages];
43
+ messages[lastUserIdx] = {
44
+ ...messages[lastUserIdx],
45
+ content: messages[lastUserIdx].content + this.buildJsonPrompt(params.jsonSchema),
46
+ };
47
+ body.messages = messages.map(m => ({ role: m.role, content: m.content }));
48
+ }
49
+ }
50
+
51
+ const response = await fetch(url, {
52
+ method: 'POST',
53
+ headers: {
54
+ 'Content-Type': 'application/json',
55
+ 'Authorization': `Bearer ${this.apiKey}`,
56
+ },
57
+ body: JSON.stringify(body),
58
+ });
59
+
60
+ if (!response.ok) {
61
+ const error = await response.text();
62
+ throw new Error(`MiniMax API error: ${response.status} - ${error}`);
63
+ }
64
+
65
+ const data = await response.json() as {
66
+ choices?: Array<{ message?: { content?: string } }>;
67
+ usage?: { prompt_tokens?: number; completion_tokens?: number; total_tokens?: number };
68
+ };
69
+
70
+ const content = data.choices?.[0]?.message?.content || '';
71
+
72
+ return {
73
+ content,
74
+ usage: data.usage ? {
75
+ promptTokens: data.usage.prompt_tokens || 0,
76
+ completionTokens: data.usage.completion_tokens || 0,
77
+ totalTokens: data.usage.total_tokens || 0,
78
+ } : undefined,
79
+ };
80
+ }
81
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Moonshot Provider - Moonshot AI (Kimi) API
3
+ */
4
+
5
+ import { BaseProvider } from './base.js';
6
+ import type { InvokeParams, InvokeResult } from '../types.js';
7
+
8
+ export class MoonshotProvider extends BaseProvider {
9
+ name = 'moonshot';
10
+ private apiKey: string;
11
+ private model: string;
12
+ private baseUrl = 'https://api.moonshot.cn/v1';
13
+
14
+ constructor(apiKey?: string, model = 'moonshot-v1-128k') {
15
+ super();
16
+ this.apiKey = apiKey || process.env.MOONSHOT_API_KEY || '';
17
+ this.model = model;
18
+ }
19
+
20
+ isConfigured(): boolean {
21
+ return !!this.apiKey;
22
+ }
23
+
24
+ async invoke(params: InvokeParams): Promise<InvokeResult> {
25
+ if (!this.isConfigured()) {
26
+ throw new Error('Moonshot API key not configured. Set MOONSHOT_API_KEY environment variable.');
27
+ }
28
+
29
+ const url = `${this.baseUrl}/chat/completions`;
30
+
31
+ const body: Record<string, unknown> = {
32
+ model: this.model,
33
+ messages: params.messages.map(m => ({ role: m.role, content: m.content })),
34
+ temperature: params.temperature ?? 0.7,
35
+ max_tokens: params.maxTokens ?? 4096,
36
+ };
37
+
38
+ // Add JSON mode if schema provided
39
+ if (params.jsonSchema) {
40
+ body.response_format = { type: 'json_object' };
41
+ const lastUserIdx = params.messages.findLastIndex(m => m.role === 'user');
42
+ if (lastUserIdx >= 0) {
43
+ const messages = [...params.messages];
44
+ messages[lastUserIdx] = {
45
+ ...messages[lastUserIdx],
46
+ content: messages[lastUserIdx].content + this.buildJsonPrompt(params.jsonSchema),
47
+ };
48
+ body.messages = messages.map(m => ({ role: m.role, content: m.content }));
49
+ }
50
+ }
51
+
52
+ const response = await fetch(url, {
53
+ method: 'POST',
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ 'Authorization': `Bearer ${this.apiKey}`,
57
+ },
58
+ body: JSON.stringify(body),
59
+ });
60
+
61
+ if (!response.ok) {
62
+ const error = await response.text();
63
+ throw new Error(`Moonshot API error: ${response.status} - ${error}`);
64
+ }
65
+
66
+ const data = await response.json() as {
67
+ choices?: Array<{ message?: { content?: string } }>;
68
+ usage?: { prompt_tokens?: number; completion_tokens?: number; total_tokens?: number };
69
+ };
70
+
71
+ const content = data.choices?.[0]?.message?.content || '';
72
+
73
+ return {
74
+ content,
75
+ usage: data.usage ? {
76
+ promptTokens: data.usage.prompt_tokens || 0,
77
+ completionTokens: data.usage.completion_tokens || 0,
78
+ totalTokens: data.usage.total_tokens || 0,
79
+ } : undefined,
80
+ };
81
+ }
82
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Ollama Provider - Local LLM via Ollama
3
+ */
4
+
5
+ import { BaseProvider } from './base.js';
6
+ import type { InvokeParams, InvokeResult } from '../types.js';
7
+
8
+ export class OllamaProvider extends BaseProvider {
9
+ name = 'ollama';
10
+ private model: string;
11
+ private baseUrl: string;
12
+
13
+ constructor(model = 'llama3.3', baseUrl = 'http://localhost:11434') {
14
+ super();
15
+ this.model = process.env.OLLAMA_MODEL || model;
16
+ this.baseUrl = process.env.OLLAMA_HOST || baseUrl;
17
+ }
18
+
19
+ isConfigured(): boolean {
20
+ return true; // Ollama doesn't need API key
21
+ }
22
+
23
+ async invoke(params: InvokeParams): Promise<InvokeResult> {
24
+ const url = `${this.baseUrl}/api/chat`;
25
+
26
+ let messages = params.messages.map(m => ({ role: m.role, content: m.content }));
27
+
28
+ // Add JSON mode if schema provided
29
+ if (params.jsonSchema) {
30
+ const lastUserIdx = messages.findLastIndex(m => m.role === 'user');
31
+ if (lastUserIdx >= 0) {
32
+ messages = [...messages];
33
+ messages[lastUserIdx] = {
34
+ ...messages[lastUserIdx],
35
+ content: messages[lastUserIdx].content + this.buildJsonPrompt(params.jsonSchema),
36
+ };
37
+ }
38
+ }
39
+
40
+ const body: Record<string, unknown> = {
41
+ model: this.model,
42
+ messages,
43
+ stream: false,
44
+ options: {
45
+ temperature: params.temperature ?? 0.7,
46
+ num_predict: params.maxTokens ?? 4096,
47
+ },
48
+ };
49
+
50
+ // Request JSON format
51
+ if (params.jsonSchema) {
52
+ body.format = 'json';
53
+ }
54
+
55
+ const response = await fetch(url, {
56
+ method: 'POST',
57
+ headers: { 'Content-Type': 'application/json' },
58
+ body: JSON.stringify(body),
59
+ });
60
+
61
+ if (!response.ok) {
62
+ const error = await response.text();
63
+ throw new Error(`Ollama API error: ${response.status} - ${error}`);
64
+ }
65
+
66
+ const data = await response.json() as {
67
+ message?: { content?: string };
68
+ prompt_eval_count?: number;
69
+ eval_count?: number;
70
+ };
71
+
72
+ const content = data.message?.content || '';
73
+
74
+ return {
75
+ content,
76
+ usage: {
77
+ promptTokens: data.prompt_eval_count || 0,
78
+ completionTokens: data.eval_count || 0,
79
+ totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0),
80
+ },
81
+ };
82
+ }
83
+ }
@@ -11,7 +11,7 @@ export class OpenAIProvider extends BaseProvider {
11
11
  private model: string;
12
12
  private baseUrl: string;
13
13
 
14
- constructor(apiKey?: string, model = 'gpt-4o', baseUrl = 'https://api.openai.com/v1') {
14
+ constructor(apiKey?: string, model = 'gpt-4.1', baseUrl = 'https://api.openai.com/v1') {
15
15
  super();
16
16
  this.apiKey = apiKey || process.env.OPENAI_API_KEY || '';
17
17
  this.model = model;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Qwen Provider - Alibaba Tongyi Qianwen (通义千问) via DashScope API
3
+ */
4
+
5
+ import { BaseProvider } from './base.js';
6
+ import type { InvokeParams, InvokeResult } from '../types.js';
7
+
8
+ export class QwenProvider extends BaseProvider {
9
+ name = 'qwen';
10
+ private apiKey: string;
11
+ private model: string;
12
+ private baseUrl = 'https://dashscope.aliyuncs.com/compatible-mode/v1';
13
+
14
+ constructor(apiKey?: string, model = 'qwen-max') {
15
+ super();
16
+ this.apiKey = apiKey || process.env.DASHSCOPE_API_KEY || process.env.QWEN_API_KEY || '';
17
+ this.model = model;
18
+ }
19
+
20
+ isConfigured(): boolean {
21
+ return !!this.apiKey;
22
+ }
23
+
24
+ async invoke(params: InvokeParams): Promise<InvokeResult> {
25
+ if (!this.isConfigured()) {
26
+ throw new Error('Qwen API key not configured. Set DASHSCOPE_API_KEY or QWEN_API_KEY environment variable.');
27
+ }
28
+
29
+ const url = `${this.baseUrl}/chat/completions`;
30
+
31
+ const body: Record<string, unknown> = {
32
+ model: this.model,
33
+ messages: params.messages.map(m => ({ role: m.role, content: m.content })),
34
+ temperature: params.temperature ?? 0.7,
35
+ max_tokens: params.maxTokens ?? 4096,
36
+ };
37
+
38
+ // Add JSON mode if schema provided
39
+ if (params.jsonSchema) {
40
+ body.response_format = { type: 'json_object' };
41
+ const lastUserIdx = params.messages.findLastIndex(m => m.role === 'user');
42
+ if (lastUserIdx >= 0) {
43
+ const messages = [...params.messages];
44
+ messages[lastUserIdx] = {
45
+ ...messages[lastUserIdx],
46
+ content: messages[lastUserIdx].content + this.buildJsonPrompt(params.jsonSchema),
47
+ };
48
+ body.messages = messages.map(m => ({ role: m.role, content: m.content }));
49
+ }
50
+ }
51
+
52
+ const response = await fetch(url, {
53
+ method: 'POST',
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ 'Authorization': `Bearer ${this.apiKey}`,
57
+ },
58
+ body: JSON.stringify(body),
59
+ });
60
+
61
+ if (!response.ok) {
62
+ const error = await response.text();
63
+ throw new Error(`Qwen API error: ${response.status} - ${error}`);
64
+ }
65
+
66
+ const data = await response.json() as {
67
+ choices?: Array<{ message?: { content?: string } }>;
68
+ usage?: { prompt_tokens?: number; completion_tokens?: number; total_tokens?: number };
69
+ };
70
+
71
+ const content = data.choices?.[0]?.message?.content || '';
72
+
73
+ return {
74
+ content,
75
+ usage: data.usage ? {
76
+ promptTokens: data.usage.prompt_tokens || 0,
77
+ completionTokens: data.usage.completion_tokens || 0,
78
+ totalTokens: data.usage.total_tokens || 0,
79
+ } : undefined,
80
+ };
81
+ }
82
+ }