orchid-ai 1.2.2 → 1.2.4

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 (38) hide show
  1. package/README.md +3 -3
  2. package/dist/cli/components/ChatPanel.d.ts +2 -1
  3. package/dist/cli/components/Conversation.d.ts +2 -1
  4. package/dist/cli/components/ModelSwitcher.d.ts +2 -1
  5. package/dist/cli/hooks/useModelSwitcher.d.ts +7 -4
  6. package/dist/cli/hooks/useResolvedDefaultModel.d.ts +21 -0
  7. package/dist/cli/hooks/useStreamingAI.d.ts +3 -2
  8. package/dist/cli/index.d.ts +1 -0
  9. package/dist/cli/server/contextual-service.d.ts +49 -1
  10. package/dist/cli/server/intent-detection.d.ts +2 -0
  11. package/dist/cli/server/utils.d.ts +0 -12
  12. package/dist/cli/types/types.d.ts +2 -1
  13. package/dist/components/ChatPanel.d.ts +2 -1
  14. package/dist/components/Conversation.d.ts +2 -1
  15. package/dist/components/ModelSwitcher.d.ts +2 -1
  16. package/dist/hooks/useModelSwitcher.d.ts +7 -4
  17. package/dist/hooks/useResolvedDefaultModel.d.ts +21 -0
  18. package/dist/hooks/useStreamingAI.d.ts +3 -2
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.esm.js +425 -39
  21. package/dist/index.js +425 -38
  22. package/dist/server/components/ChatPanel.d.ts +2 -1
  23. package/dist/server/components/Conversation.d.ts +2 -1
  24. package/dist/server/components/ModelSwitcher.d.ts +2 -1
  25. package/dist/server/contextual-service.d.ts +49 -1
  26. package/dist/server/hooks/useModelSwitcher.d.ts +7 -4
  27. package/dist/server/hooks/useResolvedDefaultModel.d.ts +21 -0
  28. package/dist/server/hooks/useStreamingAI.d.ts +3 -2
  29. package/dist/server/index.esm.js +507 -151
  30. package/dist/server/index.js +473 -99
  31. package/dist/server/intent-detection.d.ts +2 -0
  32. package/dist/server/server/contextual-service.d.ts +49 -1
  33. package/dist/server/server/intent-detection.d.ts +2 -0
  34. package/dist/server/server/utils.d.ts +0 -12
  35. package/dist/server/types/types.d.ts +2 -1
  36. package/dist/server/utils.d.ts +0 -12
  37. package/dist/types/types.d.ts +2 -1
  38. package/package.json +1 -1
@@ -1,7 +1,9 @@
1
1
  import express from 'express';
2
2
  import http from 'http';
3
- import path$a from 'path';
4
- import fs$8 from 'fs';
3
+ import * as path$a from 'path';
4
+ import path__default from 'path';
5
+ import * as fs$8 from 'fs';
6
+ import fs__default from 'fs';
5
7
  import require$$0 from 'os';
6
8
  import require$$0$1 from 'util';
7
9
  import require$$0$2 from 'stream';
@@ -79,7 +81,7 @@ var path$9 = {};
79
81
  Object.defineProperty(path$9, "__esModule", { value: true });
80
82
  path$9.convertPosixPathToPattern = path$9.convertWindowsPathToPattern = path$9.convertPathToPattern = path$9.escapePosixPath = path$9.escapeWindowsPath = path$9.escape = path$9.removeLeadingDotSegment = path$9.makeAbsolute = path$9.unixify = void 0;
81
83
  const os = require$$0;
82
- const path$8 = path$a;
84
+ const path$8 = path__default;
83
85
  const IS_WINDOWS_PLATFORM = os.platform() === 'win32';
84
86
  const LEADING_DOT_SEGMENT_CHARACTERS_COUNT = 2; // ./ or .\\
85
87
  /**
@@ -319,7 +321,7 @@ var isGlob$1 = function isGlob(str, options) {
319
321
  };
320
322
 
321
323
  var isGlob = isGlob$1;
322
- var pathPosixDirname = path$a.posix.dirname;
324
+ var pathPosixDirname = path__default.posix.dirname;
323
325
  var isWin32 = require$$0.platform() === 'win32';
324
326
 
325
327
  var slash = '/';
@@ -1758,7 +1760,7 @@ var braces_1 = braces$1;
1758
1760
 
1759
1761
  var utils$f = {};
1760
1762
 
1761
- const path$7 = path$a;
1763
+ const path$7 = path__default;
1762
1764
  const WIN_SLASH = '\\\\/';
1763
1765
  const WIN_NO_SLASH = `[^${WIN_SLASH}]`;
1764
1766
 
@@ -1938,7 +1940,7 @@ var constants$3 = {
1938
1940
 
1939
1941
  (function (exports) {
1940
1942
 
1941
- const path = path$a;
1943
+ const path = path__default;
1942
1944
  const win32 = process.platform === 'win32';
1943
1945
  const {
1944
1946
  REGEX_BACKSLASH,
@@ -3482,7 +3484,7 @@ parse$1.fastpaths = (input, options) => {
3482
3484
 
3483
3485
  var parse_1 = parse$1;
3484
3486
 
3485
- const path$6 = path$a;
3487
+ const path$6 = path__default;
3486
3488
  const scan = scan_1;
3487
3489
  const parse = parse_1;
3488
3490
  const utils$c = utils$f;
@@ -4300,7 +4302,7 @@ var micromatch_1 = micromatch$1;
4300
4302
 
4301
4303
  Object.defineProperty(pattern$1, "__esModule", { value: true });
4302
4304
  pattern$1.isAbsolute = pattern$1.partitionAbsoluteAndRelative = pattern$1.removeDuplicateSlashes = pattern$1.matchAny = pattern$1.convertPatternsToRe = pattern$1.makeRe = pattern$1.getPatternParts = pattern$1.expandBraceExpansion = pattern$1.expandPatternsWithBraceExpansion = pattern$1.isAffectDepthOfReadingPattern = pattern$1.endsWithSlashGlobStar = pattern$1.hasGlobStar = pattern$1.getBaseDirectory = pattern$1.isPatternRelatedToParentDirectory = pattern$1.getPatternsOutsideCurrentDirectory = pattern$1.getPatternsInsideCurrentDirectory = pattern$1.getPositivePatterns = pattern$1.getNegativePatterns = pattern$1.isPositivePattern = pattern$1.isNegativePattern = pattern$1.convertToNegativePattern = pattern$1.convertToPositivePattern = pattern$1.isDynamicPattern = pattern$1.isStaticPattern = void 0;
4303
- const path$5 = path$a;
4305
+ const path$5 = path__default;
4304
4306
  const globParent = globParent$1;
4305
4307
  const micromatch = micromatch_1;
4306
4308
  const GLOBSTAR = '**';
@@ -4893,7 +4895,7 @@ var fs$5 = {};
4893
4895
  (function (exports) {
4894
4896
  Object.defineProperty(exports, "__esModule", { value: true });
4895
4897
  exports.createFileSystemAdapter = exports.FILE_SYSTEM_ADAPTER = void 0;
4896
- const fs = fs$8;
4898
+ const fs = fs__default;
4897
4899
  exports.FILE_SYSTEM_ADAPTER = {
4898
4900
  lstat: fs.lstat,
4899
4901
  stat: fs.stat,
@@ -5244,7 +5246,7 @@ var fs$1 = {};
5244
5246
  (function (exports) {
5245
5247
  Object.defineProperty(exports, "__esModule", { value: true });
5246
5248
  exports.createFileSystemAdapter = exports.FILE_SYSTEM_ADAPTER = void 0;
5247
- const fs = fs$8;
5249
+ const fs = fs__default;
5248
5250
  exports.FILE_SYSTEM_ADAPTER = {
5249
5251
  lstat: fs.lstat,
5250
5252
  stat: fs.stat,
@@ -5263,7 +5265,7 @@ var fs$1 = {};
5263
5265
  } (fs$1));
5264
5266
 
5265
5267
  Object.defineProperty(settings$2, "__esModule", { value: true });
5266
- const path$3 = path$a;
5268
+ const path$3 = path__default;
5267
5269
  const fsStat$3 = out$1;
5268
5270
  const fs = fs$1;
5269
5271
  let Settings$1 = class Settings {
@@ -5947,7 +5949,7 @@ sync$3.default = SyncProvider;
5947
5949
  var settings$1 = {};
5948
5950
 
5949
5951
  Object.defineProperty(settings$1, "__esModule", { value: true });
5950
- const path$2 = path$a;
5952
+ const path$2 = path__default;
5951
5953
  const fsScandir = out$2;
5952
5954
  class Settings {
5953
5955
  constructor(_options = {}) {
@@ -6009,7 +6011,7 @@ function getSettings(settingsOrOptions = {}) {
6009
6011
  var reader = {};
6010
6012
 
6011
6013
  Object.defineProperty(reader, "__esModule", { value: true });
6012
- const path$1 = path$a;
6014
+ const path$1 = path__default;
6013
6015
  const fsStat$2 = out$1;
6014
6016
  const utils$6 = utils$k;
6015
6017
  class Reader {
@@ -6419,7 +6421,7 @@ class EntryTransformer {
6419
6421
  entry.default = EntryTransformer;
6420
6422
 
6421
6423
  Object.defineProperty(provider, "__esModule", { value: true });
6422
- const path = path$a;
6424
+ const path = path__default;
6423
6425
  const deep_1 = deep;
6424
6426
  const entry_1 = entry$1;
6425
6427
  const error_1 = error;
@@ -6597,7 +6599,7 @@ var settings = {};
6597
6599
  (function (exports) {
6598
6600
  Object.defineProperty(exports, "__esModule", { value: true });
6599
6601
  exports.DEFAULT_FILE_SYSTEM_ADAPTER = void 0;
6600
- const fs = fs$8;
6602
+ const fs = fs__default;
6601
6603
  const os = require$$0;
6602
6604
  /**
6603
6605
  * The `os.cpus` method can return zero. We expect the number of cores to be greater than zero.
@@ -6759,7 +6761,7 @@ var fg = /*@__PURE__*/getDefaultExportFromCjs(out);
6759
6761
 
6760
6762
  const DEFAULT_CONFIG = {
6761
6763
  service: 'claude',
6762
- model: 'claude-sonnet-4-20250514',
6764
+ model: 'claude-sonnet-4-5-20250929', // Updated to use newer 4.5 model
6763
6765
  temperature: 0.7,
6764
6766
  maxTokens: 4096,
6765
6767
  chatLevel: 'none',
@@ -7156,7 +7158,7 @@ const noneChatInstructions = genericInstructions +
7156
7158
  "action": "Create John Smith",
7157
7159
  "actionType": "create",
7158
7160
  "path": "/users/add",
7159
- "formState": {"name": "John Smith", "email": "john@example.com", "role": "user"}
7161
+ "formState": {"firstName": "John", "lastName": "Smith", "email": "john@example.com", "role": "user"}
7160
7162
  }
7161
7163
  ]
7162
7164
 
@@ -7583,7 +7585,7 @@ class CommandTrainingCollector {
7583
7585
  if (files.length > 0) {
7584
7586
  this.log(`✅ Found ${files.length} ${type}:`);
7585
7587
  files.forEach((file) => {
7586
- this.log(` 📄 ${path$a.relative(this.rootDir, file.filePath)}`);
7588
+ this.log(` 📄 ${path__default.relative(this.rootDir, file.filePath)}`);
7587
7589
  });
7588
7590
  }
7589
7591
  else {
@@ -7818,9 +7820,9 @@ class CommandTrainingCollector {
7818
7820
  const results = await Promise.all(expandedPaths.map(async (filePath) => {
7819
7821
  try {
7820
7822
  // Read and process the file
7821
- const content = await fs$8.promises.readFile(filePath, 'utf8');
7823
+ const content = await fs__default.promises.readFile(filePath, 'utf8');
7822
7824
  const extractedInfo = this.extractFileInfo(fieldName, content);
7823
- const relativePath = path$a.relative(this.rootDir, filePath);
7825
+ const relativePath = path__default.relative(this.rootDir, filePath);
7824
7826
  // Log what we found in the file
7825
7827
  const foundItems = Object.keys(extractedInfo).length;
7826
7828
  if (foundItems > 0) {
@@ -7855,8 +7857,8 @@ class CommandTrainingCollector {
7855
7857
  for (const pattern of patterns) {
7856
7858
  try {
7857
7859
  // Check if it's a direct file path first
7858
- const fullPath = path$a.join(this.rootDir, pattern);
7859
- const exists = await fs$8.promises
7860
+ const fullPath = path__default.join(this.rootDir, pattern);
7861
+ const exists = await fs__default.promises
7860
7862
  .access(fullPath)
7861
7863
  .then(() => true)
7862
7864
  .catch(() => false);
@@ -7875,7 +7877,7 @@ class CommandTrainingCollector {
7875
7877
  });
7876
7878
  if (matches.length > 0) {
7877
7879
  this.log(`✅ Found ${matches.length} files matching pattern: ${pattern}`);
7878
- matches.forEach((match) => this.log(` - ${path$a.relative(this.rootDir, match)}`));
7880
+ matches.forEach((match) => this.log(` - ${path__default.relative(this.rootDir, match)}`));
7879
7881
  }
7880
7882
  else {
7881
7883
  this.log(`⚠️ No files found matching pattern: ${pattern}`);
@@ -8348,20 +8350,35 @@ function getModelSupportsImages(modelId, provider) {
8348
8350
  openai: {
8349
8351
  'gpt-4o': true,
8350
8352
  'gpt-4o-mini': true,
8351
- 'gpt-4-vision-preview': true,
8352
8353
  'gpt-4-turbo': true,
8353
8354
  'gpt-4-turbo-2024-04-09': true,
8355
+ 'gpt-4-turbo-preview': true,
8356
+ 'gpt-4-vision-preview': true,
8357
+ 'gpt-4-0125-preview': true,
8358
+ 'gpt-4-1106-preview': true,
8359
+ 'gpt-5-nano-2025-08-07': false, // Note: add when known
8360
+ 'gpt-5-search-api-2025-10-14': false, // Note: add when known
8361
+ 'gpt-4o-transcribe-diarize': false, // Audio model, not images
8354
8362
  'gpt-3.5-turbo': false,
8355
8363
  'gpt-4': false,
8364
+ 'gpt-4-0613': false,
8356
8365
  },
8357
- // Claude models - all Claude 3+ models support images
8366
+ // Claude models - all Claude 3+ and 4+ models support images
8358
8367
  claude: {
8368
+ // Claude 3.x models
8359
8369
  'claude-3-haiku-20240307': true,
8360
8370
  'claude-3-sonnet-20240229': true,
8361
8371
  'claude-3-opus-20240229': true,
8362
8372
  'claude-3-5-haiku-20241022': true,
8363
8373
  'claude-3-5-sonnet-20241022': true,
8364
8374
  'claude-3-7-sonnet-20250219': true,
8375
+ // Claude 4.x models (newer naming)
8376
+ 'claude-opus-4-20250514': true,
8377
+ 'claude-opus-4-1-20250805': true,
8378
+ 'claude-sonnet-4-20250514': true,
8379
+ 'claude-sonnet-4-5-20250929': true,
8380
+ 'claude-haiku-4-5-20251001': true,
8381
+ // Claude 2.x and older - no image support
8365
8382
  'claude-2.1': false,
8366
8383
  'claude-2.0': false,
8367
8384
  'claude-instant-1.2': false,
@@ -8371,6 +8388,10 @@ function getModelSupportsImages(modelId, provider) {
8371
8388
  'gemini-1.5-flash': true,
8372
8389
  'gemini-1.5-pro': true,
8373
8390
  'gemini-2.0-flash-exp': true,
8391
+ 'gemini-2.0-flash-thinking-exp-1219': true,
8392
+ 'gemini-2.5-pro': true,
8393
+ 'gemini-2.5-flash': true,
8394
+ 'gemini-2.5-flash-lite': true,
8374
8395
  'gemini-pro': false,
8375
8396
  'gemini-pro-vision': true,
8376
8397
  },
@@ -8382,9 +8403,16 @@ function getModelSupportsImages(modelId, provider) {
8382
8403
  if (modelId in providerSupport) {
8383
8404
  return providerSupport[modelId];
8384
8405
  }
8385
- // For Claude models, assume true if it's a claude-3+ model
8386
- if (provider?.toLowerCase() === 'claude' && modelId.startsWith('claude-3')) {
8387
- return true;
8406
+ // For Claude models, assume true if it's a claude-3+ or claude-4+ model
8407
+ // Also handle claude-sonnet-4, claude-opus-4, claude-haiku-4 pattern
8408
+ if (provider?.toLowerCase() === 'claude') {
8409
+ if (modelId.startsWith('claude-3') || modelId.startsWith('claude-4')) {
8410
+ return true;
8411
+ }
8412
+ // Handle newer naming like claude-sonnet-4, claude-opus-4, claude-haiku-4
8413
+ if (modelId.match(/claude-(?:sonnet|opus|haiku)-\d/)) {
8414
+ return true;
8415
+ }
8388
8416
  }
8389
8417
  // For OpenAI, assume false unless explicitly listed
8390
8418
  if (provider?.toLowerCase() === 'openai') {
@@ -8397,6 +8425,32 @@ function getModelSupportsImages(modelId, provider) {
8397
8425
  return false;
8398
8426
  }
8399
8427
  function getDefaultModelsForProvider(provider) {
8428
+ // Try to get from cache first
8429
+ try {
8430
+ // Dynamic import to avoid circular dependency
8431
+ const { ContextualCommandService } = require('./contextual-service.js');
8432
+ const cachedModels = ContextualCommandService.getCachedModelsSync();
8433
+ if (cachedModels && cachedModels.length > 0) {
8434
+ const providerModels = cachedModels.filter((m) => m.provider.toLowerCase() === provider.toLowerCase());
8435
+ if (providerModels.length > 0) {
8436
+ // Sort by computeWeight (lowest = cheapest/fastest first)
8437
+ const sorted = providerModels.sort((a, b) => (a.computeWeight ?? 0.5) - (b.computeWeight ?? 0.5));
8438
+ // Return top 3 models for the provider
8439
+ return sorted.slice(0, 3).map((model) => ({
8440
+ id: model.id,
8441
+ name: model.name,
8442
+ provider: model.provider,
8443
+ available: model.available,
8444
+ supportsImages: model.supportsImages ?? getModelSupportsImages(model.id, model.provider),
8445
+ computeWeight: model.computeWeight,
8446
+ }));
8447
+ }
8448
+ }
8449
+ }
8450
+ catch (error) {
8451
+ // Silently fall through to defaults
8452
+ }
8453
+ // Fallback to hardcoded defaults if cache unavailable
8400
8454
  const defaultModels = {
8401
8455
  openai: [
8402
8456
  {
@@ -8416,18 +8470,18 @@ function getDefaultModelsForProvider(provider) {
8416
8470
  ],
8417
8471
  claude: [
8418
8472
  {
8419
- id: 'claude-3-5-haiku-20241022',
8420
- name: 'Claude 3.5 Haiku',
8473
+ id: 'claude-haiku-4-5-20251001',
8474
+ name: 'Claude 4.5 Haiku',
8421
8475
  provider: 'claude',
8422
8476
  available: true,
8423
- supportsImages: getModelSupportsImages('claude-3-5-haiku-20241022', 'claude'),
8477
+ supportsImages: getModelSupportsImages('claude-haiku-4-5-20251001', 'claude'),
8424
8478
  },
8425
8479
  {
8426
- id: 'claude-3-7-sonnet-20250219',
8427
- name: 'Claude 3.7 Sonnet',
8480
+ id: 'claude-sonnet-4-5-20250929',
8481
+ name: 'Claude 4.5 Sonnet',
8428
8482
  provider: 'claude',
8429
8483
  available: true,
8430
- supportsImages: getModelSupportsImages('claude-3-7-sonnet-20250219', 'claude'),
8484
+ supportsImages: getModelSupportsImages('claude-sonnet-4-5-20250929', 'claude'),
8431
8485
  },
8432
8486
  ],
8433
8487
  gemini: [
@@ -10207,7 +10261,7 @@ class CommandService {
10207
10261
  // Default models for each provider
10208
10262
  const defaultModels = {
10209
10263
  openai: ['gpt-3.5-turbo', 'gpt-4'],
10210
- claude: ['claude-3-opus-20240229', 'claude-3-sonnet-20240229'],
10264
+ claude: ['claude-haiku-4-5-20251001', 'claude-sonnet-4-5-20250929'],
10211
10265
  gemini: ['gemini-pro'],
10212
10266
  };
10213
10267
  // Configure each provider that has an API key
@@ -10383,8 +10437,8 @@ class CommandService {
10383
10437
  return;
10384
10438
  }
10385
10439
  // Write to file
10386
- const configPath = path$a.join(process.cwd(), 'ai-command-config.json');
10387
- await fs$8.promises.writeFile(configPath, JSON.stringify(configToWrite, null, 2), 'utf8');
10440
+ const configPath = path__default.join(process.cwd(), 'ai-command-config.json');
10441
+ await fs__default.promises.writeFile(configPath, JSON.stringify(configToWrite, null, 2), 'utf8');
10388
10442
  this.log(`✅ CommandService: Config written to file: ${configPath}`);
10389
10443
  }
10390
10444
  catch (error) {
@@ -10394,7 +10448,7 @@ class CommandService {
10394
10448
  async writeFailedJsonToFile(failedJson, errorMessage) {
10395
10449
  try {
10396
10450
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
10397
- const debugPath = path$a.join(process.cwd(), `failed-json-${timestamp}.json`);
10451
+ const debugPath = path__default.join(process.cwd(), `failed-json-${timestamp}.json`);
10398
10452
  const debugData = {
10399
10453
  timestamp: new Date().toISOString(),
10400
10454
  error: errorMessage,
@@ -10403,7 +10457,7 @@ class CommandService {
10403
10457
  // Also include a prettified version for easier reading
10404
10458
  prettyJson: this.tryPrettifyJson(failedJson),
10405
10459
  };
10406
- await fs$8.promises.writeFile(debugPath, JSON.stringify(debugData, null, 2));
10460
+ await fs__default.promises.writeFile(debugPath, JSON.stringify(debugData, null, 2));
10407
10461
  this.log('📁 Failed JSON written to file:', debugPath);
10408
10462
  }
10409
10463
  catch (error) {
@@ -12729,22 +12783,24 @@ async function setupCommandServer(options = {}) {
12729
12783
  // Create the command configuration
12730
12784
  // Build providers configuration with all available API keys
12731
12785
  const availableProviders = {};
12786
+ // These model lists are just placeholders - the actual models will be auto-discovered
12787
+ // from the provider APIs and cached. The models here are only used for legacy compatibility.
12732
12788
  if (openaiApiKey) {
12733
12789
  availableProviders.openai = {
12734
12790
  apiKey: openaiApiKey,
12735
- models: ['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo'],
12791
+ models: [], // Models will be auto-discovered from API
12736
12792
  };
12737
12793
  }
12738
12794
  if (claudeApiKey) {
12739
12795
  availableProviders.claude = {
12740
12796
  apiKey: claudeApiKey,
12741
- models: ['claude-3-opus', 'claude-3-sonnet', 'claude-3-haiku'],
12797
+ models: [], // Models will be auto-discovered from API
12742
12798
  };
12743
12799
  }
12744
12800
  if (geminiApiKey) {
12745
12801
  availableProviders.gemini = {
12746
12802
  apiKey: geminiApiKey,
12747
- models: ['gemini-pro', 'gemini-pro-vision'],
12803
+ models: [], // Models will be auto-discovered from API
12748
12804
  };
12749
12805
  }
12750
12806
  // Add custom providers if provided
@@ -13239,6 +13295,9 @@ async function collectTrainingData(config) {
13239
13295
  */
13240
13296
  class ContextualCommandService {
13241
13297
  config;
13298
+ modelsCache = null;
13299
+ cacheFilePath;
13300
+ CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
13242
13301
  verboseLogging;
13243
13302
  usage;
13244
13303
  constructor(config) {
@@ -13254,8 +13313,55 @@ class ContextualCommandService {
13254
13313
  currentProvider: '',
13255
13314
  currentModel: '',
13256
13315
  };
13316
+ // Set up cache file path (store in node_modules/.cache or process.cwd())
13317
+ const cacheDir = path$a.join(process.cwd(), '.cache', 'orchid-ai');
13318
+ if (!fs$8.existsSync(cacheDir)) {
13319
+ fs$8.mkdirSync(cacheDir, { recursive: true });
13320
+ }
13321
+ this.cacheFilePath = path$a.join(cacheDir, 'models-cache.json');
13322
+ // Load cache on initialization
13323
+ this.loadModelsCache();
13257
13324
  this.autoConfigureProviders();
13258
13325
  }
13326
+ /**
13327
+ * Load models cache from file if it exists and is fresh
13328
+ */
13329
+ loadModelsCache() {
13330
+ try {
13331
+ if (fs$8.existsSync(this.cacheFilePath)) {
13332
+ const cacheData = fs$8.readFileSync(this.cacheFilePath, 'utf8');
13333
+ const cache = JSON.parse(cacheData);
13334
+ const age = Date.now() - cache.timestamp;
13335
+ if (age < this.CACHE_TTL_MS) {
13336
+ this.modelsCache = cache;
13337
+ this.log(`📦 Loaded models cache (age: ${Math.round(age / 1000 / 60)} minutes, ${cache.models.length} models)`);
13338
+ }
13339
+ else {
13340
+ this.log(`⏰ Models cache expired (age: ${Math.round(age / 1000 / 60 / 60)} hours), will refresh`);
13341
+ }
13342
+ }
13343
+ }
13344
+ catch (error) {
13345
+ this.log('⚠️ Could not load models cache:', error instanceof Error ? error.message : String(error));
13346
+ }
13347
+ }
13348
+ /**
13349
+ * Save models cache to file
13350
+ */
13351
+ saveModelsCache(models) {
13352
+ try {
13353
+ const cache = {
13354
+ timestamp: Date.now(),
13355
+ models,
13356
+ };
13357
+ fs$8.writeFileSync(this.cacheFilePath, JSON.stringify(cache, null, 2), 'utf8');
13358
+ this.modelsCache = cache;
13359
+ this.log(`💾 Saved models cache (${models.length} models) to ${this.cacheFilePath}`);
13360
+ }
13361
+ catch (error) {
13362
+ this.log('⚠️ Could not save models cache:', error instanceof Error ? error.message : String(error));
13363
+ }
13364
+ }
13259
13365
  autoConfigureProviders() {
13260
13366
  const apiKeys = {
13261
13367
  openai: this.config.openaiApiKey || process.env.OPENAI_API_KEY,
@@ -13479,8 +13585,6 @@ ${additionalContext}
13479
13585
  `;
13480
13586
  }
13481
13587
  const fullPrompt = `${baseInstructions}${schemaInstructions}${contextSection}`;
13482
- // Debug: Log the system prompt being sent to AI
13483
- this.log('🔍 [SYSTEM PROMPT DEBUG] Full system prompt:', fullPrompt);
13484
13588
  return fullPrompt;
13485
13589
  }
13486
13590
  /**
@@ -13551,14 +13655,48 @@ ${additionalContext}
13551
13655
  async streamSuggestions(request, onData, onDone, onError) {
13552
13656
  const { command, schema, chatHistory = [], modelSelection, chatLevel = this.config.chatLevel || 'none', additionalContext, images = [], } = request;
13553
13657
  try {
13554
- this.log('🚀 [CONTEXTUAL STREAM] Starting with schema:', schema?.title || 'none');
13555
13658
  // Create model for this request
13556
- const requestModel = modelSelection
13557
- ? this.createModelForRequest(modelSelection)
13558
- : this.createModelForRequest({
13559
- provider: DEFAULT_CONFIG.service,
13560
- model: DEFAULT_CONFIG.model,
13561
- });
13659
+ let finalModelSelection = modelSelection;
13660
+ if (!finalModelSelection) {
13661
+ // Use tier-based default (fast/cheapest tier)
13662
+ const defaultProvider = Object.keys(this.config.providers || {})[0] || DEFAULT_CONFIG.service;
13663
+ this.log(`⚠️ [MODEL SELECTION] No modelSelection provided, using default provider: ${defaultProvider}`);
13664
+ const defaultModel = await this.getDefaultModelByTier(defaultProvider, 'fast');
13665
+ // Use dynamic default instead of hardcoded DEFAULT_CONFIG.model
13666
+ const fallbackModel = defaultModel || await this.getLatestDefaultModel(defaultProvider);
13667
+ finalModelSelection = {
13668
+ provider: defaultProvider,
13669
+ model: fallbackModel,
13670
+ };
13671
+ this.log(`📋 [MODEL SELECTION] Using default model for ${defaultProvider}: ${finalModelSelection.model}`);
13672
+ }
13673
+ else if (finalModelSelection.provider && !finalModelSelection.model) {
13674
+ // Provider specified but no model - auto-select from tier
13675
+ const tier = finalModelSelection.tier || 'fast';
13676
+ const selectedModel = await this.getDefaultModelByTier(finalModelSelection.provider, tier);
13677
+ if (!selectedModel) {
13678
+ }
13679
+ // Use dynamic default instead of hardcoded DEFAULT_CONFIG.model
13680
+ const fallbackModel = selectedModel || await this.getLatestDefaultModel(finalModelSelection.provider);
13681
+ finalModelSelection = {
13682
+ provider: finalModelSelection.provider,
13683
+ model: fallbackModel,
13684
+ };
13685
+ }
13686
+ else if (finalModelSelection.model) {
13687
+ }
13688
+ // Ensure model is always defined before creating request model
13689
+ if (!finalModelSelection.model) {
13690
+ finalModelSelection.model = await this.getLatestDefaultModel(finalModelSelection.provider || DEFAULT_CONFIG.service);
13691
+ }
13692
+ // Ensure provider is also defined
13693
+ if (!finalModelSelection.provider) {
13694
+ finalModelSelection.provider = DEFAULT_CONFIG.service;
13695
+ }
13696
+ const requestModel = this.createModelForRequest({
13697
+ provider: finalModelSelection.provider,
13698
+ model: finalModelSelection.model,
13699
+ });
13562
13700
  // Build system prompt with schema context
13563
13701
  const systemPrompt = this.buildContextualSystemPrompt(schema, chatLevel, additionalContext);
13564
13702
  // Build messages array
@@ -13919,12 +14057,37 @@ ${additionalContext}
13919
14057
  const { command, schema, chatHistory = [], modelSelection, chatLevel = this.config.chatLevel || 'none', additionalContext, } = request;
13920
14058
  try {
13921
14059
  // Create model for this request
13922
- const requestModel = modelSelection
13923
- ? this.createModelForRequest(modelSelection)
13924
- : this.createModelForRequest({
13925
- provider: DEFAULT_CONFIG.service,
13926
- model: DEFAULT_CONFIG.model,
13927
- });
14060
+ let finalModelSelection = modelSelection;
14061
+ if (!finalModelSelection) {
14062
+ // Use tier-based default (fast/cheapest tier)
14063
+ const defaultProvider = DEFAULT_CONFIG.service;
14064
+ const defaultModel = await this.getDefaultModelByTier(defaultProvider, 'fast');
14065
+ finalModelSelection = {
14066
+ provider: defaultProvider,
14067
+ model: defaultModel || DEFAULT_CONFIG.model,
14068
+ };
14069
+ this.log(`📋 Using default model for ${defaultProvider}: ${finalModelSelection.model}`);
14070
+ }
14071
+ else if (finalModelSelection.provider && !finalModelSelection.model) {
14072
+ // Provider specified but no model - auto-select from tier
14073
+ const tier = finalModelSelection.tier || 'fast';
14074
+ const defaultModel = await this.getDefaultModelByTier(finalModelSelection.provider, tier);
14075
+ finalModelSelection = {
14076
+ provider: finalModelSelection.provider,
14077
+ model: defaultModel || DEFAULT_CONFIG.model,
14078
+ };
14079
+ if (defaultModel) {
14080
+ this.log(`📋 Auto-selected ${tier} tier model for ${finalModelSelection.provider}: ${defaultModel}`);
14081
+ }
14082
+ }
14083
+ // Ensure model is always defined before creating request model
14084
+ if (!finalModelSelection.model) {
14085
+ finalModelSelection.model = DEFAULT_CONFIG.model;
14086
+ }
14087
+ const requestModel = this.createModelForRequest({
14088
+ provider: finalModelSelection.provider,
14089
+ model: finalModelSelection.model,
14090
+ });
13928
14091
  // Build system prompt
13929
14092
  const systemPrompt = this.buildContextualSystemPrompt(schema, chatLevel, additionalContext);
13930
14093
  // Build messages
@@ -13995,9 +14158,55 @@ ${additionalContext}
13995
14158
  }
13996
14159
  }
13997
14160
  /**
13998
- * Get available models
14161
+ * Static utility to read cached models from file (for use in other services)
14162
+ */
14163
+ static getCachedModelsSync() {
14164
+ try {
14165
+ // Use the same cache location as instance method
14166
+ const cacheDir = path$a.join(process.cwd(), '.cache', 'orchid-ai');
14167
+ const cacheFilePath = path$a.join(cacheDir, 'models-cache.json');
14168
+ if (!fs$8.existsSync(cacheFilePath)) {
14169
+ return [];
14170
+ }
14171
+ const cacheContent = fs$8.readFileSync(cacheFilePath, 'utf8');
14172
+ const cache = JSON.parse(cacheContent);
14173
+ // Check if cache is still valid (24 hours)
14174
+ const age = Date.now() - cache.timestamp;
14175
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
14176
+ if (age < CACHE_TTL_MS && cache.models && cache.models.length > 0) {
14177
+ return cache.models;
14178
+ }
14179
+ return [];
14180
+ }
14181
+ catch (error) {
14182
+ // Silently fail - return empty array
14183
+ return [];
14184
+ }
14185
+ }
14186
+ /**
14187
+ * Static utility to get the fastest model for a provider from cache
14188
+ */
14189
+ static getFastestModelSync(provider) {
14190
+ const cachedModels = this.getCachedModelsSync();
14191
+ const providerModels = cachedModels
14192
+ .filter(m => m.provider.toLowerCase() === provider.toLowerCase())
14193
+ .sort((a, b) => (a.computeWeight ?? 0.5) - (b.computeWeight ?? 0.5));
14194
+ return providerModels.length > 0 ? providerModels[0].id : null;
14195
+ }
14196
+ /**
14197
+ * Get available models from API or use cached/defaults
14198
+ * Uses file-based cache that refreshes every 24 hours
13999
14199
  */
14000
14200
  async getAvailableModels() {
14201
+ // Check if we have a valid cache
14202
+ if (this.modelsCache) {
14203
+ const age = Date.now() - this.modelsCache.timestamp;
14204
+ if (age < this.CACHE_TTL_MS) {
14205
+ this.log(`📦 Using cached models (${this.modelsCache.models.length} models, ${Math.round(age / 1000 / 60)} minutes old)`);
14206
+ return this.modelsCache.models;
14207
+ }
14208
+ this.log(`⏰ Cache expired (${Math.round(age / 1000 / 60 / 60)} hours old), fetching fresh models...`);
14209
+ }
14001
14210
  try {
14002
14211
  const allModels = [];
14003
14212
  if (this.config.providers) {
@@ -14011,16 +14220,40 @@ ${additionalContext}
14011
14220
  case 'claude':
14012
14221
  try {
14013
14222
  const claudeModels = await tempModel.models.list();
14014
- providerModels = claudeModels.data.map((model) => ({
14015
- id: model.id,
14016
- name: model.display_name || model.id,
14017
- provider: providerName,
14018
- available: true,
14019
- }));
14223
+ providerModels = claudeModels.data
14224
+ .filter((model) => model.id.includes('claude'))
14225
+ .map((model) => {
14226
+ // Determine compute weight based on model name (lower = cheaper)
14227
+ // Check in priority order: haiku (cheapest), sonnet (medium), opus (most expensive)
14228
+ let computeWeight = 0.5; // default balanced for unknown models
14229
+ const modelIdLower = model.id.toLowerCase();
14230
+ if (modelIdLower.includes('haiku')) {
14231
+ computeWeight = 0.2; // Cheapest - Haiku models (e.g., claude-3-5-haiku-20241022)
14232
+ }
14233
+ else if (modelIdLower.includes('sonnet')) {
14234
+ computeWeight = 0.6; // Medium cost - Sonnet models (e.g., claude-sonnet-4-20250514)
14235
+ }
14236
+ else if (modelIdLower.includes('opus')) {
14237
+ computeWeight = 1.2; // Most expensive - Opus models (e.g., claude-opus-4-20250514)
14238
+ }
14239
+ // Note: Models not matching above patterns default to 0.5 (balanced)
14240
+ // Auto-detect image support for this model
14241
+ const supportsImages = getModelSupportsImages(model.id, providerName);
14242
+ return {
14243
+ id: model.id,
14244
+ name: model.display_name || model.id,
14245
+ provider: providerName,
14246
+ available: true,
14247
+ computeWeight,
14248
+ supportsImages,
14249
+ };
14250
+ });
14020
14251
  }
14021
14252
  catch {
14253
+ // Fallback defaults with image support
14022
14254
  providerModels = [
14023
- { id: 'claude-3-5-haiku-20241022', name: 'Claude 3.5 Haiku', provider: providerName, available: true },
14255
+ { id: 'claude-3-5-haiku-20241022', name: 'Claude 3.5 Haiku', provider: providerName, available: true, computeWeight: 0.2, supportsImages: true },
14256
+ { id: 'claude-3-5-sonnet-20241022', name: 'Claude 3.5 Sonnet', provider: providerName, available: true, computeWeight: 0.6, supportsImages: true },
14024
14257
  ];
14025
14258
  }
14026
14259
  break;
@@ -14028,26 +14261,62 @@ ${additionalContext}
14028
14261
  try {
14029
14262
  const openaiModels = await tempModel.models.list();
14030
14263
  providerModels = openaiModels.data
14031
- .filter((model) => model.id.includes('gpt'))
14032
- .map((model) => ({
14264
+ .filter((model) => model.id.includes('gpt') && !model.id.includes('instruct'))
14265
+ .map((model) => {
14266
+ // Determine compute weight
14267
+ let computeWeight = 0.5;
14268
+ if (model.id.includes('gpt-4-turbo') || model.id.includes('gpt-4o')) {
14269
+ computeWeight = model.id.includes('mini') ? 0.3 : 1.0;
14270
+ }
14271
+ else if (model.id.includes('gpt-3.5')) {
14272
+ computeWeight = 0.2;
14273
+ }
14274
+ // Auto-detect image support for this model
14275
+ const supportsImages = getModelSupportsImages(model.id, providerName);
14276
+ return {
14277
+ id: model.id,
14278
+ name: model.id,
14279
+ provider: providerName,
14280
+ available: true,
14281
+ computeWeight,
14282
+ supportsImages,
14283
+ };
14284
+ });
14285
+ }
14286
+ catch {
14287
+ // Fallback defaults with image support
14288
+ providerModels = [
14289
+ { id: 'gpt-4o-mini', name: 'GPT-4o Mini', provider: providerName, available: true, computeWeight: 0.3, supportsImages: true },
14290
+ { id: 'gpt-4o', name: 'GPT-4o', provider: providerName, available: true, computeWeight: 1.0, supportsImages: true },
14291
+ ];
14292
+ }
14293
+ break;
14294
+ case 'gemini':
14295
+ try {
14296
+ // Gemini models are statically defined as API doesn't provide list endpoint
14297
+ // Auto-detect image support for each model
14298
+ const geminiModelList = [
14299
+ { id: 'gemini-2.0-flash-exp', name: 'Gemini 2.0 Flash Exp', computeWeight: 0.2 },
14300
+ { id: 'gemini-1.5-flash', name: 'Gemini 1.5 Flash', computeWeight: 0.2 },
14301
+ { id: 'gemini-2.0-flash-thinking-exp-1219', name: 'Gemini 2.0 Flash Thinking', computeWeight: 0.5 },
14302
+ { id: 'gemini-1.5-pro', name: 'Gemini 1.5 Pro', computeWeight: 0.8 },
14303
+ { id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro', computeWeight: 1.0 },
14304
+ ];
14305
+ providerModels = geminiModelList.map(model => ({
14033
14306
  id: model.id,
14034
- name: model.id,
14307
+ name: model.name,
14035
14308
  provider: providerName,
14036
14309
  available: true,
14310
+ computeWeight: model.computeWeight,
14311
+ supportsImages: getModelSupportsImages(model.id, providerName),
14037
14312
  }));
14038
14313
  }
14039
14314
  catch {
14040
14315
  providerModels = [
14041
- { id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo', provider: providerName, available: true },
14042
- { id: 'gpt-4o-mini', name: 'GPT-4O Mini', provider: providerName, available: true },
14316
+ { id: 'gemini-1.5-flash', name: 'Gemini 1.5 Flash', provider: providerName, available: true, computeWeight: 0.2, supportsImages: true },
14043
14317
  ];
14044
14318
  }
14045
14319
  break;
14046
- case 'gemini':
14047
- providerModels = [
14048
- { id: 'gemini-1.5-flash', name: 'Gemini 1.5 Flash', provider: providerName, available: true },
14049
- ];
14050
- break;
14051
14320
  }
14052
14321
  allModels.push(...providerModels);
14053
14322
  }
@@ -14056,13 +14325,120 @@ ${additionalContext}
14056
14325
  }
14057
14326
  }
14058
14327
  }
14328
+ // Save to cache for future use
14329
+ if (allModels.length > 0) {
14330
+ this.saveModelsCache(allModels);
14331
+ }
14059
14332
  return allModels;
14060
14333
  }
14061
14334
  catch (error) {
14062
14335
  this.handleError(error);
14336
+ // If we have a stale cache, use it as fallback
14337
+ if (this.modelsCache) {
14338
+ this.log('⚠️ Failed to fetch models, using stale cache as fallback');
14339
+ return this.modelsCache.models;
14340
+ }
14063
14341
  return [];
14064
14342
  }
14065
14343
  }
14344
+ /**
14345
+ * Get the latest default model for a provider (balanced tier, or fallback to hardcoded)
14346
+ * This should be used instead of DEFAULT_CONFIG.model when possible
14347
+ */
14348
+ async getLatestDefaultModel(provider = DEFAULT_CONFIG.service) {
14349
+ try {
14350
+ const balancedModel = await this.getDefaultModelByTier(provider, 'balanced');
14351
+ if (balancedModel) {
14352
+ return balancedModel;
14353
+ }
14354
+ }
14355
+ catch (error) {
14356
+ this.log(`⚠️ Could not get latest default model for ${provider}, using fallback`);
14357
+ }
14358
+ // Fallback to hardcoded defaults if dynamic lookup fails (using latest 4-5 models)
14359
+ const fallbackDefaults = {
14360
+ claude: 'claude-sonnet-4-5-20250929',
14361
+ openai: 'gpt-4o',
14362
+ gemini: 'gemini-2.0-flash-thinking-exp-1219',
14363
+ };
14364
+ return fallbackDefaults[provider.toLowerCase()] || DEFAULT_CONFIG.model;
14365
+ }
14366
+ /**
14367
+ * Get default models by tier for a provider
14368
+ * @param provider - Provider name
14369
+ * @param tier - 'fast' (lowest computeWeight = cheapest), 'balanced' (medium), or 'powerful' (highest = most expensive)
14370
+ */
14371
+ async getDefaultModelByTier(provider, tier = 'fast') {
14372
+ const availableModels = await this.getAvailableModels();
14373
+ const providerModels = availableModels.filter((m) => m.provider.toLowerCase() === provider.toLowerCase());
14374
+ if (providerModels.length === 0) {
14375
+ this.log(`⚠️ [getDefaultModelByTier] No models found for provider ${provider}, using fallback`);
14376
+ // Fallback to hardcoded defaults (using latest 4-5 models)
14377
+ const defaults = {
14378
+ claude: {
14379
+ fast: 'claude-haiku-4-5-20251001',
14380
+ balanced: 'claude-sonnet-4-5-20250929',
14381
+ powerful: 'claude-opus-4-20250514',
14382
+ },
14383
+ openai: {
14384
+ fast: 'gpt-4o-mini',
14385
+ balanced: 'gpt-4o',
14386
+ powerful: 'gpt-4-turbo',
14387
+ },
14388
+ gemini: {
14389
+ fast: 'gemini-1.5-flash',
14390
+ balanced: 'gemini-2.0-flash-thinking-exp-1219',
14391
+ powerful: 'gemini-2.5-pro',
14392
+ },
14393
+ };
14394
+ const fallback = defaults[provider.toLowerCase()]?.[tier];
14395
+ if (fallback) {
14396
+ this.log(`⚠️ Using fallback model for ${provider} ${tier}: ${fallback}`);
14397
+ return fallback;
14398
+ }
14399
+ return null;
14400
+ }
14401
+ // Sort by computeWeight (ascending - lowest weight = cheapest)
14402
+ const sorted = providerModels.sort((a, b) => {
14403
+ const weightA = a.computeWeight ?? 0.5;
14404
+ const weightB = b.computeWeight ?? 0.5;
14405
+ return weightA - weightB;
14406
+ });
14407
+ // Select based on tier
14408
+ switch (tier) {
14409
+ case 'fast':
14410
+ // Cheapest model (lowest weight) - should be Haiku for Claude
14411
+ const cheapest = sorted[0];
14412
+ return cheapest.id;
14413
+ case 'powerful':
14414
+ // Most expensive/powerful model (highest weight) - should be Opus for Claude
14415
+ const mostExpensive = sorted[sorted.length - 1];
14416
+ return mostExpensive.id;
14417
+ case 'balanced':
14418
+ default:
14419
+ // Get middle model (rounded down)
14420
+ const midIndex = Math.floor(sorted.length / 2);
14421
+ const balanced = sorted[midIndex];
14422
+ return balanced.id;
14423
+ }
14424
+ }
14425
+ /**
14426
+ * Get latest models from each tier for all configured providers
14427
+ */
14428
+ async getLatestModelsByTier() {
14429
+ const result = {};
14430
+ if (!this.config.providers)
14431
+ return result;
14432
+ for (const providerName of Object.keys(this.config.providers)) {
14433
+ const fast = await this.getDefaultModelByTier(providerName, 'fast');
14434
+ const balanced = await this.getDefaultModelByTier(providerName, 'balanced');
14435
+ const powerful = await this.getDefaultModelByTier(providerName, 'powerful');
14436
+ if (fast && balanced && powerful) {
14437
+ result[providerName] = { fast, balanced, powerful };
14438
+ }
14439
+ }
14440
+ return result;
14441
+ }
14066
14442
  /**
14067
14443
  * Get usage statistics
14068
14444
  */
@@ -14360,6 +14736,7 @@ class IntentDetectionPlugin {
14360
14736
  }
14361
14737
  /**
14362
14738
  * Resolve model name with dynamic discovery support
14739
+ * Uses cached models when available, falls back to defaults
14363
14740
  */
14364
14741
  resolveModelName() {
14365
14742
  const { provider, model, enableDynamicModelDiscovery } = this.config;
@@ -14367,66 +14744,45 @@ class IntentDetectionPlugin {
14367
14744
  if (model && model !== 'auto') {
14368
14745
  return model;
14369
14746
  }
14370
- // Default models for each provider
14747
+ // Try to get from cache first
14748
+ if (enableDynamicModelDiscovery) {
14749
+ const cachedFastest = ContextualCommandService.getFastestModelSync(provider);
14750
+ if (cachedFastest) {
14751
+ this.log(`✅ [MODEL RESOLUTION] Using cached fastest model for ${provider}: ${cachedFastest}`);
14752
+ return cachedFastest;
14753
+ }
14754
+ }
14755
+ // Fallback to defaults if cache unavailable (using latest 4-5 models)
14371
14756
  const defaultModels = {
14372
14757
  openai: 'gpt-4o-mini',
14373
- claude: 'claude-3-haiku-20240307',
14374
- gemini: 'gemini-flash-latest', // Correct model name
14758
+ claude: 'claude-haiku-4-5-20251001',
14759
+ gemini: 'gemini-1.5-flash',
14375
14760
  };
14376
- // If dynamic discovery is disabled, use defaults
14377
- if (!enableDynamicModelDiscovery) {
14378
- return defaultModels[provider.toLowerCase()] || 'gpt-3.5-turbo';
14379
- }
14380
- // For 'auto' or dynamic discovery, we'll resolve at runtime
14381
- return defaultModels[provider.toLowerCase()] || 'gpt-3.5-turbo';
14761
+ const defaultModel = defaultModels[provider.toLowerCase()] || 'gpt-3.5-turbo';
14762
+ this.log(`⚠️ [MODEL RESOLUTION] Using fallback default for ${provider}: ${defaultModel}`);
14763
+ return defaultModel;
14382
14764
  }
14383
14765
  /**
14384
14766
  * Dynamically discover and select the best model for the provider
14767
+ * Uses cached models when available
14385
14768
  */
14386
14769
  async discoverBestModel() {
14387
- const { provider, apiKey } = this.config;
14388
- try {
14389
- switch (provider.toLowerCase()) {
14390
- case 'gemini': {
14391
- const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`);
14392
- const data = await response.json();
14393
- if (data.models) {
14394
- const geminiModels = data.models
14395
- .filter((m) => m.name.includes('gemini'))
14396
- .map((m) => m.name.replace('models/', ''));
14397
- // Priority order: latest flash > any flash > latest pro > any pro
14398
- const latestFlash = geminiModels.find((m) => m === 'gemini-flash-latest');
14399
- const anyFlash = geminiModels.find((m) => m.includes('flash') && !m.includes('pro'));
14400
- const latestPro = geminiModels.find((m) => m === 'gemini-pro-latest');
14401
- const anyPro = geminiModels.find((m) => m.includes('pro'));
14402
- const selectedModel = latestFlash || anyFlash || latestPro || anyPro || 'gemini-1.5-flash';
14403
- this.log(`🔍 [DYNAMIC DISCOVERY] Found ${geminiModels.length} Gemini models, selected: ${selectedModel}`);
14404
- return selectedModel;
14405
- }
14406
- break;
14407
- }
14408
- case 'openai': {
14409
- // OpenAI models are more stable, use known fast models
14410
- const fastModels = ['gpt-4o-mini', 'gpt-3.5-turbo', 'gpt-4o'];
14411
- return fastModels[0]; // gpt-4o-mini is fastest
14412
- }
14413
- case 'claude': {
14414
- // Claude models are stable, use known fast models
14415
- const fastModels = ['claude-3-haiku-20240307', 'claude-3-5-haiku-20241022'];
14416
- return fastModels[0]; // haiku is fastest
14417
- }
14418
- }
14419
- }
14420
- catch (error) {
14421
- this.log('⚠️ Dynamic model discovery failed, using default:', error);
14422
- }
14423
- // Fallback to defaults
14770
+ const { provider } = this.config;
14771
+ // First, try to get from cache (fastest model for the provider)
14772
+ const cachedFastest = ContextualCommandService.getFastestModelSync(provider);
14773
+ if (cachedFastest) {
14774
+ this.log(`✅ [DYNAMIC DISCOVERY] Using cached fastest model for ${provider}: ${cachedFastest}`);
14775
+ return cachedFastest;
14776
+ }
14777
+ // Fallback to defaults if cache unavailable (using latest 4-5 models)
14424
14778
  const defaults = {
14425
14779
  openai: 'gpt-4o-mini',
14426
- claude: 'claude-3-haiku-20240307',
14427
- gemini: 'gemini-flash-latest', // Correct model name
14780
+ claude: 'claude-haiku-4-5-20251001',
14781
+ gemini: 'gemini-1.5-flash',
14428
14782
  };
14429
- return defaults[provider.toLowerCase()] || 'gpt-3.5-turbo';
14783
+ const defaultModel = defaults[provider.toLowerCase()] || 'gpt-3.5-turbo';
14784
+ this.log(`⚠️ [DYNAMIC DISCOVERY] Cache unavailable, using fallback: ${defaultModel}`);
14785
+ return defaultModel;
14430
14786
  }
14431
14787
  createModelInstance() {
14432
14788
  const { provider, apiKey } = this.config;
@@ -14838,7 +15194,7 @@ class TypeScriptAnalyzer {
14838
15194
  * Extract type definitions from a TypeScript file
14839
15195
  */
14840
15196
  extractTypes(filePath) {
14841
- const content = fs$8.readFileSync(filePath, 'utf8');
15197
+ const content = fs__default.readFileSync(filePath, 'utf8');
14842
15198
  const types = [];
14843
15199
  // Extract interfaces
14844
15200
  const interfaceMatches = content.matchAll(/(?:\/\*\*[\s\S]*?\*\/\s*)?export\s+interface\s+(\w+)(?:\s+extends\s+([\w,\s]+))?\s*\{([\s\S]*?)\n\}/g);
@@ -15084,7 +15440,7 @@ class MonasteryAnalyzer {
15084
15440
  */
15085
15441
  extractModel(filePath) {
15086
15442
  try {
15087
- const content = fs$8.readFileSync(filePath, 'utf8');
15443
+ const content = fs__default.readFileSync(filePath, 'utf8');
15088
15444
  // Extract model name from filename
15089
15445
  const fileName = filePath.split('/').pop()?.replace(/\.(js|ts)$/, '') || 'unknown';
15090
15446
  const modelName = fileName.charAt(0).toUpperCase() + fileName.slice(1);
@@ -15604,13 +15960,13 @@ class SchemaGenerator extends CommandTrainingCollector {
15604
15960
  }
15605
15961
  }
15606
15962
  // Look in pages directory (common pattern)
15607
- const pagesDir = path$a.join(this.getRootDir(), 'pages');
15608
- if (fs$8.existsSync(pagesDir)) {
15963
+ const pagesDir = path__default.join(this.getRootDir(), 'pages');
15964
+ if (fs__default.existsSync(pagesDir)) {
15609
15965
  try {
15610
- const pageFiles = fs$8.readdirSync(pagesDir, { recursive: true });
15966
+ const pageFiles = fs__default.readdirSync(pagesDir, { recursive: true });
15611
15967
  for (const file of pageFiles) {
15612
15968
  if (typeof file === 'string' && file.endsWith('.tsx')) {
15613
- tsxFiles.push(path$a.join('pages', file));
15969
+ tsxFiles.push(path__default.join('pages', file));
15614
15970
  }
15615
15971
  }
15616
15972
  }
@@ -15776,12 +16132,12 @@ Provide a single sentence that describes what users can do in this tab.`;
15776
16132
  async saveConfig(config, outputPath) {
15777
16133
  this.log(`\n💾 Saving config to: ${outputPath}`);
15778
16134
  // Ensure output directory exists
15779
- const dir = path$a.dirname(outputPath);
15780
- if (!fs$8.existsSync(dir)) {
15781
- fs$8.mkdirSync(dir, { recursive: true });
16135
+ const dir = path__default.dirname(outputPath);
16136
+ if (!fs__default.existsSync(dir)) {
16137
+ fs__default.mkdirSync(dir, { recursive: true });
15782
16138
  }
15783
16139
  // Write config file
15784
- fs$8.writeFileSync(outputPath, JSON.stringify(config, null, 2), 'utf8');
16140
+ fs__default.writeFileSync(outputPath, JSON.stringify(config, null, 2), 'utf8');
15785
16141
  this.log(` ✅ Config saved successfully!`);
15786
16142
  }
15787
16143
  /**
@@ -15791,11 +16147,11 @@ Provide a single sentence that describes what users can do in this tab.`;
15791
16147
  this.log('\n📝 Generating schemas/index.ts...');
15792
16148
  // 1. Scan for Monastery models (server/models/*.js)
15793
16149
  const monasteryModels = [];
15794
- const modelsDir = path$a.join(this.getRootDir(), 'server/models');
15795
- if (fs$8.existsSync(modelsDir)) {
15796
- const modelFiles = fs$8.readdirSync(modelsDir).filter(f => f.endsWith('.js') || f.endsWith('.ts'));
16150
+ const modelsDir = path__default.join(this.getRootDir(), 'server/models');
16151
+ if (fs__default.existsSync(modelsDir)) {
16152
+ const modelFiles = fs__default.readdirSync(modelsDir).filter(f => f.endsWith('.js') || f.endsWith('.ts'));
15797
16153
  for (const file of modelFiles) {
15798
- const filePath = path$a.join(modelsDir, file);
16154
+ const filePath = path__default.join(modelsDir, file);
15799
16155
  const extracted = this.monasteryAnalyzer.extractModel(filePath);
15800
16156
  if (extracted) {
15801
16157
  monasteryModels.push(extracted);
@@ -15809,7 +16165,7 @@ Provide a single sentence that describes what users can do in this tab.`;
15809
16165
  try {
15810
16166
  const types = this.tsAnalyzer.extractTypes(typeFile.filePath);
15811
16167
  extractedTypes.push(...types);
15812
- this.log(` ✅ Extracted ${types.length} types from ${path$a.basename(typeFile.filePath)}`);
16168
+ this.log(` ✅ Extracted ${types.length} types from ${path__default.basename(typeFile.filePath)}`);
15813
16169
  }
15814
16170
  catch (error) {
15815
16171
  this.log(` ⚠️ Failed to extract types from ${typeFile.filePath}: ${error.message}`);
@@ -16039,7 +16395,7 @@ module.exports = {
16039
16395
  * Helper function to run schema generation
16040
16396
  */
16041
16397
  async function generateSchemas(projectRoot, config, options = {}) {
16042
- const { outputDir = path$a.join(projectRoot, '.orchid-ai'), clientOutputFile = 'schemas.ts', serverOutputFile = 'command-config.json', schemasOutputPath = path$a.join(projectRoot, 'schemas.ts'), // Default: root level
16398
+ const { outputDir = path__default.join(projectRoot, '.orchid-ai'), clientOutputFile = 'schemas.ts', serverOutputFile = 'command-config.json', schemasOutputPath = path__default.join(projectRoot, 'schemas.ts'), // Default: root level
16043
16399
  separateSchemaFiles = false, verboseLogging = false, useAI = false, aiService = null, interactive = false, promptCallback, } = options;
16044
16400
  console.log('🚀 Starting schema generation for project:', projectRoot);
16045
16401
  // Create generator
@@ -16047,34 +16403,34 @@ async function generateSchemas(projectRoot, config, options = {}) {
16047
16403
  // Generate config
16048
16404
  const generatedConfig = await generator.generateConfig();
16049
16405
  // Save server config (JSON)
16050
- const serverConfigPath = path$a.join(outputDir, serverOutputFile);
16406
+ const serverConfigPath = path__default.join(outputDir, serverOutputFile);
16051
16407
  await generator.saveConfig(generatedConfig, serverConfigPath);
16052
16408
  // Generate schemas file(s)
16053
16409
  const schemasIndexContent = await generator.generateSchemasIndexFile(generatedConfig);
16054
16410
  if (separateSchemaFiles) {
16055
16411
  // Generate individual files per schema
16056
- const schemasDir = path$a.dirname(schemasOutputPath);
16057
- if (!fs$8.existsSync(schemasDir)) {
16058
- fs$8.mkdirSync(schemasDir, { recursive: true });
16412
+ const schemasDir = path__default.dirname(schemasOutputPath);
16413
+ if (!fs__default.existsSync(schemasDir)) {
16414
+ fs__default.mkdirSync(schemasDir, { recursive: true });
16059
16415
  console.log(`📁 Created schemas directory: ${schemasDir}`);
16060
16416
  }
16061
16417
  // TODO: Implement individual file generation
16062
16418
  console.log('⚠️ Separate schema files not yet implemented, using single file');
16063
- fs$8.writeFileSync(schemasOutputPath, schemasIndexContent, 'utf8');
16419
+ fs__default.writeFileSync(schemasOutputPath, schemasIndexContent, 'utf8');
16064
16420
  }
16065
16421
  else {
16066
16422
  // Generate single schemas file
16067
- const schemasDir = path$a.dirname(schemasOutputPath);
16068
- if (!fs$8.existsSync(schemasDir)) {
16069
- fs$8.mkdirSync(schemasDir, { recursive: true });
16423
+ const schemasDir = path__default.dirname(schemasOutputPath);
16424
+ if (!fs__default.existsSync(schemasDir)) {
16425
+ fs__default.mkdirSync(schemasDir, { recursive: true });
16070
16426
  }
16071
- fs$8.writeFileSync(schemasOutputPath, schemasIndexContent, 'utf8');
16427
+ fs__default.writeFileSync(schemasOutputPath, schemasIndexContent, 'utf8');
16072
16428
  console.log(`📄 Generated schemas file: ${schemasOutputPath}`);
16073
16429
  }
16074
16430
  // Save client config (TypeScript) - legacy format
16075
- const clientConfigPath = path$a.join(outputDir, clientOutputFile);
16431
+ const clientConfigPath = path__default.join(outputDir, clientOutputFile);
16076
16432
  const clientConfig = generator.generateClientConfig(generatedConfig);
16077
- fs$8.writeFileSync(clientConfigPath, clientConfig, 'utf8');
16433
+ fs__default.writeFileSync(clientConfigPath, clientConfig, 'utf8');
16078
16434
  console.log('\n✨ Schema generation complete!');
16079
16435
  console.log(`📁 Output directory: ${outputDir}`);
16080
16436
  console.log(`📄 Schemas file: schemas/index.ts`);