opc-agent 4.0.42 → 4.0.44

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.
@@ -8,6 +8,12 @@ export interface LLMProvider {
8
8
  chat(messages: Message[], systemPrompt?: string, options?: ChatOptions): Promise<string>;
9
9
  chatStream(messages: Message[], systemPrompt?: string): AsyncIterable<string>;
10
10
  }
11
+ export declare function autoDetectProvider(): {
12
+ name: string;
13
+ model?: string;
14
+ baseUrl?: string;
15
+ apiKey?: string;
16
+ };
11
17
  export declare function createProvider(name?: string, model?: string, baseUrl?: string, apiKey?: string): LLMProvider;
12
18
  export declare const SUPPORTED_PROVIDERS: readonly ["openai", "ollama", "claude-cli", "deepseek", "qwen", "gemini", "dashscope", "zhipu", "moonshot"];
13
19
  //# sourceMappingURL=index.d.ts.map
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.SUPPORTED_PROVIDERS = void 0;
37
+ exports.autoDetectProvider = autoDetectProvider;
37
38
  exports.createProvider = createProvider;
38
39
  const https = __importStar(require("https"));
39
40
  const http = __importStar(require("http"));
@@ -507,7 +508,70 @@ class ClaudeCLIProvider {
507
508
  }
508
509
  }
509
510
  }
510
- function createProvider(name = 'openai', model, baseUrl, apiKey) {
511
+ const child_process_1 = require("child_process");
512
+ function detectClaudeCLI() {
513
+ try {
514
+ (0, child_process_1.execSync)('claude --version', { stdio: 'pipe', timeout: 3000 });
515
+ return true;
516
+ }
517
+ catch {
518
+ return false;
519
+ }
520
+ }
521
+ function detectOllama() {
522
+ try {
523
+ (0, child_process_1.execSync)('ollama --version', { stdio: 'pipe', timeout: 3000 });
524
+ // Also check if server is running
525
+ const res = (0, child_process_1.execSync)('curl -s http://localhost:11434/api/tags', { stdio: 'pipe', timeout: 3000 });
526
+ return res.toString().includes('models');
527
+ }
528
+ catch {
529
+ return false;
530
+ }
531
+ }
532
+ function detectApiKeys() {
533
+ if (process.env.ANTHROPIC_API_KEY)
534
+ return { provider: 'anthropic', key: process.env.ANTHROPIC_API_KEY };
535
+ if (process.env.OPENAI_API_KEY && process.env.OPENAI_API_KEY !== 'your-api-key-here')
536
+ return { provider: 'openai', key: process.env.OPENAI_API_KEY };
537
+ if (process.env.DEEPSEEK_API_KEY)
538
+ return { provider: 'deepseek', key: process.env.DEEPSEEK_API_KEY, baseUrl: 'https://api.deepseek.com/v1' };
539
+ if (process.env.DASHSCOPE_API_KEY)
540
+ return { provider: 'qwen', key: process.env.DASHSCOPE_API_KEY, baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1' };
541
+ if (process.env.GEMINI_API_KEY)
542
+ return { provider: 'gemini', key: process.env.GEMINI_API_KEY };
543
+ return null;
544
+ }
545
+ function autoDetectProvider() {
546
+ // 1. Claude CLI (zero config, Claude Max/Pro subscription)
547
+ if (detectClaudeCLI()) {
548
+ return { name: 'claude-cli' };
549
+ }
550
+ // 2. API keys from environment
551
+ const apiKey = detectApiKeys();
552
+ if (apiKey) {
553
+ return { name: apiKey.provider, apiKey: apiKey.key, baseUrl: apiKey.baseUrl };
554
+ }
555
+ // 3. Ollama (local, free)
556
+ if (detectOllama()) {
557
+ return { name: 'ollama', model: 'qwen2.5:7b' };
558
+ }
559
+ // 4. Nothing found
560
+ return { name: 'none' };
561
+ }
562
+ function createProvider(name = 'auto', model, baseUrl, apiKey) {
563
+ // Auto-detect if name is 'auto' or default openai with no real key
564
+ const needsAutoDetect = name === 'auto' || (name === 'openai' && !apiKey && (!process.env.OPENAI_API_KEY || process.env.OPENAI_API_KEY === 'your-api-key-here'));
565
+ if (needsAutoDetect) {
566
+ const detected = autoDetectProvider();
567
+ if (detected.name !== 'none') {
568
+ console.log(`[provider] Auto-detected: ${detected.name}`);
569
+ name = detected.name;
570
+ model = model || detected.model;
571
+ baseUrl = baseUrl || detected.baseUrl;
572
+ apiKey = apiKey || detected.apiKey;
573
+ }
574
+ }
511
575
  const finalModel = model || process.env.OPC_LLM_MODEL || 'gpt-4o-mini';
512
576
  // Claude CLI mode: use local claude command (Claude Max/Pro subscription)
513
577
  if (name === 'claude-cli' || process.env.OPC_LLM_PROVIDER === 'claude-cli') {
@@ -146,6 +146,7 @@ export declare const LongTermMemorySchema: z.ZodObject<{
146
146
  }, z.ZodTypeAny, "passthrough"> | undefined;
147
147
  collection?: string | undefined;
148
148
  }, {
149
+ provider?: "in-memory" | "deepbrain" | undefined;
149
150
  config?: z.objectInputType<{
150
151
  database: z.ZodOptional<z.ZodString>;
151
152
  embeddingProvider: z.ZodOptional<z.ZodString>;
@@ -153,7 +154,6 @@ export declare const LongTermMemorySchema: z.ZodObject<{
153
154
  autoRecall: z.ZodOptional<z.ZodBoolean>;
154
155
  evolveInterval: z.ZodOptional<z.ZodNumber>;
155
156
  }, z.ZodTypeAny, "passthrough"> | undefined;
156
- provider?: "in-memory" | "deepbrain" | undefined;
157
157
  collection?: string | undefined;
158
158
  }>;
159
159
  export declare const MemorySchema: z.ZodObject<{
@@ -191,6 +191,7 @@ export declare const MemorySchema: z.ZodObject<{
191
191
  }, z.ZodTypeAny, "passthrough"> | undefined;
192
192
  collection?: string | undefined;
193
193
  }, {
194
+ provider?: "in-memory" | "deepbrain" | undefined;
194
195
  config?: z.objectInputType<{
195
196
  database: z.ZodOptional<z.ZodString>;
196
197
  embeddingProvider: z.ZodOptional<z.ZodString>;
@@ -198,7 +199,6 @@ export declare const MemorySchema: z.ZodObject<{
198
199
  autoRecall: z.ZodOptional<z.ZodBoolean>;
199
200
  evolveInterval: z.ZodOptional<z.ZodNumber>;
200
201
  }, z.ZodTypeAny, "passthrough"> | undefined;
201
- provider?: "in-memory" | "deepbrain" | undefined;
202
202
  collection?: string | undefined;
203
203
  }>]>>;
204
204
  provider: z.ZodOptional<z.ZodString>;
@@ -220,6 +220,7 @@ export declare const MemorySchema: z.ZodObject<{
220
220
  provider?: string | undefined;
221
221
  shortTerm?: boolean | undefined;
222
222
  longTerm?: boolean | {
223
+ provider?: "in-memory" | "deepbrain" | undefined;
223
224
  config?: z.objectInputType<{
224
225
  database: z.ZodOptional<z.ZodString>;
225
226
  embeddingProvider: z.ZodOptional<z.ZodString>;
@@ -227,7 +228,6 @@ export declare const MemorySchema: z.ZodObject<{
227
228
  autoRecall: z.ZodOptional<z.ZodBoolean>;
228
229
  evolveInterval: z.ZodOptional<z.ZodNumber>;
229
230
  }, z.ZodTypeAny, "passthrough"> | undefined;
230
- provider?: "in-memory" | "deepbrain" | undefined;
231
231
  collection?: string | undefined;
232
232
  } | undefined;
233
233
  }>;
@@ -672,6 +672,7 @@ export declare const SpecSchema: z.ZodObject<{
672
672
  }, z.ZodTypeAny, "passthrough"> | undefined;
673
673
  collection?: string | undefined;
674
674
  }, {
675
+ provider?: "in-memory" | "deepbrain" | undefined;
675
676
  config?: z.objectInputType<{
676
677
  database: z.ZodOptional<z.ZodString>;
677
678
  embeddingProvider: z.ZodOptional<z.ZodString>;
@@ -679,7 +680,6 @@ export declare const SpecSchema: z.ZodObject<{
679
680
  autoRecall: z.ZodOptional<z.ZodBoolean>;
680
681
  evolveInterval: z.ZodOptional<z.ZodNumber>;
681
682
  }, z.ZodTypeAny, "passthrough"> | undefined;
682
- provider?: "in-memory" | "deepbrain" | undefined;
683
683
  collection?: string | undefined;
684
684
  }>]>>;
685
685
  provider: z.ZodOptional<z.ZodString>;
@@ -701,6 +701,7 @@ export declare const SpecSchema: z.ZodObject<{
701
701
  provider?: string | undefined;
702
702
  shortTerm?: boolean | undefined;
703
703
  longTerm?: boolean | {
704
+ provider?: "in-memory" | "deepbrain" | undefined;
704
705
  config?: z.objectInputType<{
705
706
  database: z.ZodOptional<z.ZodString>;
706
707
  embeddingProvider: z.ZodOptional<z.ZodString>;
@@ -708,7 +709,6 @@ export declare const SpecSchema: z.ZodObject<{
708
709
  autoRecall: z.ZodOptional<z.ZodBoolean>;
709
710
  evolveInterval: z.ZodOptional<z.ZodNumber>;
710
711
  }, z.ZodTypeAny, "passthrough"> | undefined;
711
- provider?: "in-memory" | "deepbrain" | undefined;
712
712
  collection?: string | undefined;
713
713
  } | undefined;
714
714
  }>>;
@@ -1064,6 +1064,10 @@ export declare const SpecSchema: z.ZodObject<{
1064
1064
  env?: Record<string, string> | undefined;
1065
1065
  }[] | undefined;
1066
1066
  } | undefined;
1067
+ provider?: {
1068
+ default: string;
1069
+ allowed: string[];
1070
+ } | undefined;
1067
1071
  auth?: {
1068
1072
  enabled: boolean;
1069
1073
  apiKeys: string[];
@@ -1080,10 +1084,6 @@ export declare const SpecSchema: z.ZodObject<{
1080
1084
  secret?: string | undefined;
1081
1085
  retryAttempts?: number | undefined;
1082
1086
  } | undefined;
1083
- provider?: {
1084
- default: string;
1085
- allowed: string[];
1086
- } | undefined;
1087
1087
  systemPrompt?: string | undefined;
1088
1088
  memory?: {
1089
1089
  shortTerm: boolean;
@@ -1179,6 +1179,10 @@ export declare const SpecSchema: z.ZodObject<{
1179
1179
  }[] | undefined;
1180
1180
  } | undefined;
1181
1181
  model?: string | undefined;
1182
+ provider?: {
1183
+ default?: string | undefined;
1184
+ allowed?: string[] | undefined;
1185
+ } | undefined;
1182
1186
  auth?: {
1183
1187
  enabled?: boolean | undefined;
1184
1188
  apiKeys?: string[] | undefined;
@@ -1195,10 +1199,6 @@ export declare const SpecSchema: z.ZodObject<{
1195
1199
  secret?: string | undefined;
1196
1200
  retryAttempts?: number | undefined;
1197
1201
  } | undefined;
1198
- provider?: {
1199
- default?: string | undefined;
1200
- allowed?: string[] | undefined;
1201
- } | undefined;
1202
1202
  systemPrompt?: string | undefined;
1203
1203
  skills?: {
1204
1204
  name: string;
@@ -1214,6 +1214,7 @@ export declare const SpecSchema: z.ZodObject<{
1214
1214
  provider?: string | undefined;
1215
1215
  shortTerm?: boolean | undefined;
1216
1216
  longTerm?: boolean | {
1217
+ provider?: "in-memory" | "deepbrain" | undefined;
1217
1218
  config?: z.objectInputType<{
1218
1219
  database: z.ZodOptional<z.ZodString>;
1219
1220
  embeddingProvider: z.ZodOptional<z.ZodString>;
@@ -1221,7 +1222,6 @@ export declare const SpecSchema: z.ZodObject<{
1221
1222
  autoRecall: z.ZodOptional<z.ZodBoolean>;
1222
1223
  evolveInterval: z.ZodOptional<z.ZodNumber>;
1223
1224
  }, z.ZodTypeAny, "passthrough"> | undefined;
1224
- provider?: "in-memory" | "deepbrain" | undefined;
1225
1225
  collection?: string | undefined;
1226
1226
  } | undefined;
1227
1227
  } | undefined;
@@ -1418,6 +1418,7 @@ export declare const OADSchema: z.ZodObject<{
1418
1418
  }, z.ZodTypeAny, "passthrough"> | undefined;
1419
1419
  collection?: string | undefined;
1420
1420
  }, {
1421
+ provider?: "in-memory" | "deepbrain" | undefined;
1421
1422
  config?: z.objectInputType<{
1422
1423
  database: z.ZodOptional<z.ZodString>;
1423
1424
  embeddingProvider: z.ZodOptional<z.ZodString>;
@@ -1425,7 +1426,6 @@ export declare const OADSchema: z.ZodObject<{
1425
1426
  autoRecall: z.ZodOptional<z.ZodBoolean>;
1426
1427
  evolveInterval: z.ZodOptional<z.ZodNumber>;
1427
1428
  }, z.ZodTypeAny, "passthrough"> | undefined;
1428
- provider?: "in-memory" | "deepbrain" | undefined;
1429
1429
  collection?: string | undefined;
1430
1430
  }>]>>;
1431
1431
  provider: z.ZodOptional<z.ZodString>;
@@ -1447,6 +1447,7 @@ export declare const OADSchema: z.ZodObject<{
1447
1447
  provider?: string | undefined;
1448
1448
  shortTerm?: boolean | undefined;
1449
1449
  longTerm?: boolean | {
1450
+ provider?: "in-memory" | "deepbrain" | undefined;
1450
1451
  config?: z.objectInputType<{
1451
1452
  database: z.ZodOptional<z.ZodString>;
1452
1453
  embeddingProvider: z.ZodOptional<z.ZodString>;
@@ -1454,7 +1455,6 @@ export declare const OADSchema: z.ZodObject<{
1454
1455
  autoRecall: z.ZodOptional<z.ZodBoolean>;
1455
1456
  evolveInterval: z.ZodOptional<z.ZodNumber>;
1456
1457
  }, z.ZodTypeAny, "passthrough"> | undefined;
1457
- provider?: "in-memory" | "deepbrain" | undefined;
1458
1458
  collection?: string | undefined;
1459
1459
  } | undefined;
1460
1460
  }>>;
@@ -1810,6 +1810,10 @@ export declare const OADSchema: z.ZodObject<{
1810
1810
  env?: Record<string, string> | undefined;
1811
1811
  }[] | undefined;
1812
1812
  } | undefined;
1813
+ provider?: {
1814
+ default: string;
1815
+ allowed: string[];
1816
+ } | undefined;
1813
1817
  auth?: {
1814
1818
  enabled: boolean;
1815
1819
  apiKeys: string[];
@@ -1826,10 +1830,6 @@ export declare const OADSchema: z.ZodObject<{
1826
1830
  secret?: string | undefined;
1827
1831
  retryAttempts?: number | undefined;
1828
1832
  } | undefined;
1829
- provider?: {
1830
- default: string;
1831
- allowed: string[];
1832
- } | undefined;
1833
1833
  systemPrompt?: string | undefined;
1834
1834
  memory?: {
1835
1835
  shortTerm: boolean;
@@ -1925,6 +1925,10 @@ export declare const OADSchema: z.ZodObject<{
1925
1925
  }[] | undefined;
1926
1926
  } | undefined;
1927
1927
  model?: string | undefined;
1928
+ provider?: {
1929
+ default?: string | undefined;
1930
+ allowed?: string[] | undefined;
1931
+ } | undefined;
1928
1932
  auth?: {
1929
1933
  enabled?: boolean | undefined;
1930
1934
  apiKeys?: string[] | undefined;
@@ -1941,10 +1945,6 @@ export declare const OADSchema: z.ZodObject<{
1941
1945
  secret?: string | undefined;
1942
1946
  retryAttempts?: number | undefined;
1943
1947
  } | undefined;
1944
- provider?: {
1945
- default?: string | undefined;
1946
- allowed?: string[] | undefined;
1947
- } | undefined;
1948
1948
  systemPrompt?: string | undefined;
1949
1949
  skills?: {
1950
1950
  name: string;
@@ -1960,6 +1960,7 @@ export declare const OADSchema: z.ZodObject<{
1960
1960
  provider?: string | undefined;
1961
1961
  shortTerm?: boolean | undefined;
1962
1962
  longTerm?: boolean | {
1963
+ provider?: "in-memory" | "deepbrain" | undefined;
1963
1964
  config?: z.objectInputType<{
1964
1965
  database: z.ZodOptional<z.ZodString>;
1965
1966
  embeddingProvider: z.ZodOptional<z.ZodString>;
@@ -1967,7 +1968,6 @@ export declare const OADSchema: z.ZodObject<{
1967
1968
  autoRecall: z.ZodOptional<z.ZodBoolean>;
1968
1969
  evolveInterval: z.ZodOptional<z.ZodNumber>;
1969
1970
  }, z.ZodTypeAny, "passthrough"> | undefined;
1970
- provider?: "in-memory" | "deepbrain" | undefined;
1971
1971
  collection?: string | undefined;
1972
1972
  } | undefined;
1973
1973
  } | undefined;
@@ -2085,6 +2085,10 @@ export declare const OADSchema: z.ZodObject<{
2085
2085
  env?: Record<string, string> | undefined;
2086
2086
  }[] | undefined;
2087
2087
  } | undefined;
2088
+ provider?: {
2089
+ default: string;
2090
+ allowed: string[];
2091
+ } | undefined;
2088
2092
  auth?: {
2089
2093
  enabled: boolean;
2090
2094
  apiKeys: string[];
@@ -2101,10 +2105,6 @@ export declare const OADSchema: z.ZodObject<{
2101
2105
  secret?: string | undefined;
2102
2106
  retryAttempts?: number | undefined;
2103
2107
  } | undefined;
2104
- provider?: {
2105
- default: string;
2106
- allowed: string[];
2107
- } | undefined;
2108
2108
  systemPrompt?: string | undefined;
2109
2109
  memory?: {
2110
2110
  shortTerm: boolean;
@@ -2217,6 +2217,10 @@ export declare const OADSchema: z.ZodObject<{
2217
2217
  }[] | undefined;
2218
2218
  } | undefined;
2219
2219
  model?: string | undefined;
2220
+ provider?: {
2221
+ default?: string | undefined;
2222
+ allowed?: string[] | undefined;
2223
+ } | undefined;
2220
2224
  auth?: {
2221
2225
  enabled?: boolean | undefined;
2222
2226
  apiKeys?: string[] | undefined;
@@ -2233,10 +2237,6 @@ export declare const OADSchema: z.ZodObject<{
2233
2237
  secret?: string | undefined;
2234
2238
  retryAttempts?: number | undefined;
2235
2239
  } | undefined;
2236
- provider?: {
2237
- default?: string | undefined;
2238
- allowed?: string[] | undefined;
2239
- } | undefined;
2240
2240
  systemPrompt?: string | undefined;
2241
2241
  skills?: {
2242
2242
  name: string;
@@ -2252,6 +2252,7 @@ export declare const OADSchema: z.ZodObject<{
2252
2252
  provider?: string | undefined;
2253
2253
  shortTerm?: boolean | undefined;
2254
2254
  longTerm?: boolean | {
2255
+ provider?: "in-memory" | "deepbrain" | undefined;
2255
2256
  config?: z.objectInputType<{
2256
2257
  database: z.ZodOptional<z.ZodString>;
2257
2258
  embeddingProvider: z.ZodOptional<z.ZodString>;
@@ -2259,7 +2260,6 @@ export declare const OADSchema: z.ZodObject<{
2259
2260
  autoRecall: z.ZodOptional<z.ZodBoolean>;
2260
2261
  evolveInterval: z.ZodOptional<z.ZodNumber>;
2261
2262
  }, z.ZodTypeAny, "passthrough"> | undefined;
2262
- provider?: "in-memory" | "deepbrain" | undefined;
2263
2263
  collection?: string | undefined;
2264
2264
  } | undefined;
2265
2265
  } | undefined;
@@ -733,37 +733,47 @@ class StudioServer {
733
733
  });
734
734
  const allMsgs = [{ role: 'system', content: agent.systemPrompt }, ...messages];
735
735
  const lastMsg = allMsgs[allMsgs.length - 1]?.content || '';
736
- // Try to call the real /v1/chat/completions endpoint
736
+ // Use createProvider directly to call LLM
737
737
  try {
738
- const completionReq = (0, http_1.request)({
739
- hostname: 'localhost',
740
- port: 3000,
741
- path: '/v1/chat/completions',
742
- method: 'POST',
743
- headers: { 'Content-Type': 'application/json' },
744
- }, (completionRes) => {
745
- const ct = completionRes.headers['content-type'] || '';
746
- if (completionRes.statusCode === 200 && (ct.includes('text/event-stream') || ct.includes('application/json'))) {
747
- completionRes.pipe(res);
738
+ const { createProvider } = require('../providers');
739
+ // Read OAD config for provider info
740
+ let providerName = agent.provider || process.env.OPC_LLM_PROVIDER;
741
+ if (!providerName) {
742
+ // Try reading from oad.yaml
743
+ try {
744
+ const oadPath = (0, path_1.join)(this.config.agentDir, 'oad.yaml');
745
+ if ((0, fs_1.existsSync)(oadPath)) {
746
+ const yaml = require('js-yaml');
747
+ const oad = yaml.load((0, fs_1.readFileSync)(oadPath, 'utf-8'));
748
+ providerName = oad?.spec?.provider?.default;
749
+ }
748
750
  }
749
- else {
750
- // Drain the response to avoid leak
751
- completionRes.resume();
752
- // Fallback to simulated response
753
- this.sendSimulatedResponse(res, lastMsg, agent);
751
+ catch { }
752
+ }
753
+ providerName = providerName || 'openai';
754
+ const provider = createProvider(providerName, agent.model);
755
+ let fullText = '';
756
+ try {
757
+ for await (const chunk of provider.chatStream(allMsgs, agent.systemPrompt)) {
758
+ const sseData = JSON.stringify({
759
+ choices: [{ delta: { content: chunk }, index: 0 }],
760
+ });
761
+ res.write(`data: ${sseData}\n\n`);
762
+ fullText += chunk;
754
763
  }
755
- });
756
- completionReq.on('error', () => {
757
- this.sendSimulatedResponse(res, lastMsg, agent);
758
- });
759
- completionReq.write(JSON.stringify({
760
- model: agent.model,
761
- messages: allMsgs,
762
- stream: true,
763
- }));
764
- completionReq.end();
764
+ }
765
+ catch (streamErr) {
766
+ if (!fullText) {
767
+ // No content streamed yet, send error
768
+ const errData = JSON.stringify({ error: streamErr.message });
769
+ res.write(`data: ${errData}\n\n`);
770
+ }
771
+ }
772
+ res.write('data: [DONE]\n\n');
773
+ res.end();
765
774
  }
766
- catch {
775
+ catch (err) {
776
+ // Fallback: try simulated response
767
777
  this.sendSimulatedResponse(res, lastMsg, agent);
768
778
  }
769
779
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opc-agent",
3
- "version": "4.0.42",
3
+ "version": "4.0.44",
4
4
  "description": "Open Agent Framework — Build, test, and run AI Agents for business workstations",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -513,7 +513,68 @@ class ClaudeCLIProvider implements LLMProvider {
513
513
  }
514
514
  }
515
515
 
516
- export function createProvider(name: string = 'openai', model?: string, baseUrl?: string, apiKey?: string): LLMProvider {
516
+ import { execSync } from 'child_process';
517
+
518
+ function detectClaudeCLI(): boolean {
519
+ try {
520
+ execSync('claude --version', { stdio: 'pipe', timeout: 3000 });
521
+ return true;
522
+ } catch { return false; }
523
+ }
524
+
525
+ function detectOllama(): boolean {
526
+ try {
527
+ execSync('ollama --version', { stdio: 'pipe', timeout: 3000 });
528
+ // Also check if server is running
529
+ const res = execSync('curl -s http://localhost:11434/api/tags', { stdio: 'pipe', timeout: 3000 });
530
+ return res.toString().includes('models');
531
+ } catch { return false; }
532
+ }
533
+
534
+ function detectApiKeys(): { provider: string; key: string; baseUrl?: string } | null {
535
+ if (process.env.ANTHROPIC_API_KEY) return { provider: 'anthropic', key: process.env.ANTHROPIC_API_KEY };
536
+ if (process.env.OPENAI_API_KEY && process.env.OPENAI_API_KEY !== 'your-api-key-here') return { provider: 'openai', key: process.env.OPENAI_API_KEY };
537
+ if (process.env.DEEPSEEK_API_KEY) return { provider: 'deepseek', key: process.env.DEEPSEEK_API_KEY, baseUrl: 'https://api.deepseek.com/v1' };
538
+ if (process.env.DASHSCOPE_API_KEY) return { provider: 'qwen', key: process.env.DASHSCOPE_API_KEY, baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1' };
539
+ if (process.env.GEMINI_API_KEY) return { provider: 'gemini', key: process.env.GEMINI_API_KEY };
540
+ return null;
541
+ }
542
+
543
+ export function autoDetectProvider(): { name: string; model?: string; baseUrl?: string; apiKey?: string } {
544
+ // 1. Claude CLI (zero config, Claude Max/Pro subscription)
545
+ if (detectClaudeCLI()) {
546
+ return { name: 'claude-cli' };
547
+ }
548
+
549
+ // 2. API keys from environment
550
+ const apiKey = detectApiKeys();
551
+ if (apiKey) {
552
+ return { name: apiKey.provider, apiKey: apiKey.key, baseUrl: apiKey.baseUrl };
553
+ }
554
+
555
+ // 3. Ollama (local, free)
556
+ if (detectOllama()) {
557
+ return { name: 'ollama', model: 'qwen2.5:7b' };
558
+ }
559
+
560
+ // 4. Nothing found
561
+ return { name: 'none' };
562
+ }
563
+
564
+ export function createProvider(name: string = 'auto', model?: string, baseUrl?: string, apiKey?: string): LLMProvider {
565
+ // Auto-detect if name is 'auto' or default openai with no real key
566
+ const needsAutoDetect = name === 'auto' || (name === 'openai' && !apiKey && (!process.env.OPENAI_API_KEY || process.env.OPENAI_API_KEY === 'your-api-key-here'));
567
+ if (needsAutoDetect) {
568
+ const detected = autoDetectProvider();
569
+ if (detected.name !== 'none') {
570
+ console.log(`[provider] Auto-detected: ${detected.name}`);
571
+ name = detected.name;
572
+ model = model || detected.model;
573
+ baseUrl = baseUrl || detected.baseUrl;
574
+ apiKey = apiKey || detected.apiKey;
575
+ }
576
+ }
577
+
517
578
  const finalModel = model || process.env.OPC_LLM_MODEL || 'gpt-4o-mini';
518
579
 
519
580
  // Claude CLI mode: use local claude command (Claude Max/Pro subscription)
@@ -735,35 +735,45 @@ class StudioServer {
735
735
  const allMsgs = [{ role: 'system', content: agent.systemPrompt }, ...messages];
736
736
  const lastMsg = allMsgs[allMsgs.length - 1]?.content || '';
737
737
 
738
- // Try to call the real /v1/chat/completions endpoint
738
+ // Use createProvider directly to call LLM
739
739
  try {
740
- const completionReq = httpRequest({
741
- hostname: 'localhost',
742
- port: 3000,
743
- path: '/v1/chat/completions',
744
- method: 'POST',
745
- headers: { 'Content-Type': 'application/json' },
746
- }, (completionRes) => {
747
- const ct = completionRes.headers['content-type'] || '';
748
- if (completionRes.statusCode === 200 && (ct.includes('text/event-stream') || ct.includes('application/json'))) {
749
- completionRes.pipe(res);
750
- } else {
751
- // Drain the response to avoid leak
752
- completionRes.resume();
753
- // Fallback to simulated response
754
- this.sendSimulatedResponse(res, lastMsg, agent);
740
+ const { createProvider } = require('../providers');
741
+ // Read OAD config for provider info
742
+ let providerName = agent.provider || process.env.OPC_LLM_PROVIDER;
743
+ if (!providerName) {
744
+ // Try reading from oad.yaml
745
+ try {
746
+ const oadPath = join(this.config.agentDir, 'oad.yaml');
747
+ if (existsSync(oadPath)) {
748
+ const yaml = require('js-yaml');
749
+ const oad = yaml.load(readFileSync(oadPath, 'utf-8'));
750
+ providerName = oad?.spec?.provider?.default;
751
+ }
752
+ } catch {}
753
+ }
754
+ providerName = providerName || 'openai';
755
+ const provider = createProvider(providerName, agent.model);
756
+
757
+ let fullText = '';
758
+ try {
759
+ for await (const chunk of provider.chatStream(allMsgs, agent.systemPrompt)) {
760
+ const sseData = JSON.stringify({
761
+ choices: [{ delta: { content: chunk }, index: 0 }],
762
+ });
763
+ res.write(`data: ${sseData}\n\n`);
764
+ fullText += chunk;
755
765
  }
756
- });
757
- completionReq.on('error', () => {
758
- this.sendSimulatedResponse(res, lastMsg, agent);
759
- });
760
- completionReq.write(JSON.stringify({
761
- model: agent.model,
762
- messages: allMsgs,
763
- stream: true,
764
- }));
765
- completionReq.end();
766
- } catch {
766
+ } catch (streamErr: any) {
767
+ if (!fullText) {
768
+ // No content streamed yet, send error
769
+ const errData = JSON.stringify({ error: streamErr.message });
770
+ res.write(`data: ${errData}\n\n`);
771
+ }
772
+ }
773
+ res.write('data: [DONE]\n\n');
774
+ res.end();
775
+ } catch (err: any) {
776
+ // Fallback: try simulated response
767
777
  this.sendSimulatedResponse(res, lastMsg, agent);
768
778
  }
769
779
  }