jeopi-cli 16.6.3 → 16.6.5

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.
@@ -94,7 +94,7 @@ export declare class ModelRegistry {
94
94
  * Drives the same SQLite model cache as built-in providers, so the default
95
95
  * `online-if-uncached` strategy fetches at most once per cache TTL (24 h).
96
96
  */
97
- refreshRuntimeProviders(strategy?: ModelRefreshStrategy): Promise<void>;
97
+ refreshRuntimeProviders(strategy?: ModelRefreshStrategy, excludedProviders?: ReadonlySet<string>): Promise<void>;
98
98
  /**
99
99
  * Get any error from loading custom models config (undefined if no error).
100
100
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "jeopi-cli",
4
- "version": "16.6.3",
4
+ "version": "16.6.5",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/akillness/jeopi",
7
7
  "author": "Can Boluk",
@@ -57,17 +57,17 @@
57
57
  "@agentclientprotocol/sdk": "0.25.0",
58
58
  "@babel/parser": "^7.29.7",
59
59
  "@mozilla/readability": "^0.6.0",
60
- "jeopi-hashline": "16.6.3",
61
- "jeopi-stats": "16.6.3",
62
- "jeopi-agent-core": "16.6.3",
63
- "jeopi-ai": "16.6.3",
64
- "jeopi-catalog": "16.6.3",
65
- "jeopi-mnemopi": "16.6.3",
66
- "jeopi-natives": "16.6.3",
67
- "jeopi-tui": "16.6.3",
68
- "jeopi-utils": "16.6.3",
69
- "jeopi-wire": "16.6.3",
70
- "jeopi-snapcompact": "16.6.3",
60
+ "jeopi-hashline": "16.6.5",
61
+ "jeopi-stats": "16.6.5",
62
+ "jeopi-agent-core": "16.6.5",
63
+ "jeopi-ai": "16.6.5",
64
+ "jeopi-catalog": "16.6.5",
65
+ "jeopi-mnemopi": "16.6.5",
66
+ "jeopi-natives": "16.6.5",
67
+ "jeopi-tui": "16.6.5",
68
+ "jeopi-utils": "16.6.5",
69
+ "jeopi-wire": "16.6.5",
70
+ "jeopi-snapcompact": "16.6.5",
71
71
  "@opentelemetry/api": "^1.9.1",
72
72
  "@opentelemetry/context-async-hooks": "^2.7.1",
73
73
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -823,14 +823,30 @@ export class ModelRegistry {
823
823
  this.#backgroundRefresh = refreshPromise;
824
824
  }
825
825
 
826
+ #resolveProviderId(providerId: string): string | undefined {
827
+ const candidates = new Set<string>([
828
+ ...STARTUP_MODEL_CACHE_PROVIDER_IDS,
829
+ ...this.#models.map(model => model.provider),
830
+ ...this.#discoverableProviders.map(provider => provider.provider),
831
+ ...this.#providerOverrides.keys(),
832
+ ...this.#customProviderApiKeys.keys(),
833
+ ...this.#runtimeProviderApiKeys.keys(),
834
+ ...this.#runtimeProviderOverrides.keys(),
835
+ ...this.#runtimeModelManagers.keys(),
836
+ ]);
837
+ return [...candidates].find(candidate => candidate.toLowerCase() === providerId.toLowerCase());
838
+ }
839
+
826
840
  async refreshProvider(providerId: string, strategy: ModelRefreshStrategy = "online"): Promise<void> {
827
841
  this.#reloadStaticModels();
842
+ const canonicalProviderId = this.#resolveProviderId(providerId);
843
+ if (!canonicalProviderId) return;
828
844
  for (const selector of this.#suppressedSelectors.keys()) {
829
- if (selector.startsWith(`${providerId}/`)) {
845
+ if (selector.startsWith(`${canonicalProviderId}/`)) {
830
846
  this.#suppressedSelectors.delete(selector);
831
847
  }
832
848
  }
833
- await this.#refreshRuntimeDiscoveries(strategy, new Set([providerId]));
849
+ await this.#refreshRuntimeDiscoveries(strategy, new Set([canonicalProviderId]));
834
850
  }
835
851
 
836
852
  /**
@@ -892,11 +908,18 @@ export class ModelRegistry {
892
908
  * Drives the same SQLite model cache as built-in providers, so the default
893
909
  * `online-if-uncached` strategy fetches at most once per cache TTL (24 h).
894
910
  */
895
- async refreshRuntimeProviders(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
911
+ async refreshRuntimeProviders(
912
+ strategy: ModelRefreshStrategy = "online-if-uncached",
913
+ excludedProviders?: ReadonlySet<string>,
914
+ ): Promise<void> {
896
915
  if (this.#runtimeModelManagers.size === 0) {
897
916
  return;
898
917
  }
899
- await this.#refreshRuntimeDiscoveries(strategy, new Set(this.#runtimeModelManagers.keys()));
918
+ const providers = new Set(
919
+ [...this.#runtimeModelManagers.keys()].filter(provider => !excludedProviders?.has(provider.toLowerCase())),
920
+ );
921
+ if (providers.size === 0) return;
922
+ await this.#refreshRuntimeDiscoveries(strategy, providers);
900
923
  }
901
924
 
902
925
  #reloadStaticModels(): void {
@@ -1620,7 +1643,7 @@ export class ModelRegistry {
1620
1643
  ): Promise<BuiltInDiscoveryResult> {
1621
1644
  // Skip providers already handled by configured discovery (e.g. user-configured ollama with discovery.type)
1622
1645
  const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(p => p.provider));
1623
- const managerOptions = (await this.#collectBuiltInModelManagerOptions()).filter(opts => {
1646
+ const managerOptions = (await this.#collectBuiltInModelManagerOptions(providerFilter)).filter(opts => {
1624
1647
  if (configuredDiscoveryProviders.has(opts.providerId)) {
1625
1648
  return false;
1626
1649
  }
@@ -1643,7 +1666,7 @@ export class ModelRegistry {
1643
1666
  return { models, authoritativeProviders };
1644
1667
  }
1645
1668
 
1646
- async #collectBuiltInModelManagerOptions(): Promise<ModelManagerOptions<Api>[]> {
1669
+ async #collectBuiltInModelManagerOptions(providerFilter?: ReadonlySet<string>): Promise<ModelManagerOptions<Api>[]> {
1647
1670
  const specialProviderDescriptors: Array<{
1648
1671
  providerId: string;
1649
1672
  resolveKey: (value: string | undefined) => string | undefined;
@@ -1712,10 +1735,14 @@ export class ModelRegistry {
1712
1735
  ];
1713
1736
  const disabledProviders = getDisabledProviderIdsFromSettings();
1714
1737
  const standardProviderDescriptors = PROVIDER_DESCRIPTORS.filter(
1715
- descriptor => !disabledProviders.has(descriptor.providerId),
1738
+ descriptor =>
1739
+ !disabledProviders.has(descriptor.providerId) &&
1740
+ (providerFilter === undefined || providerFilter.has(descriptor.providerId)),
1716
1741
  );
1717
1742
  const enabledSpecialProviderDescriptors = specialProviderDescriptors.filter(
1718
- descriptor => !disabledProviders.has(descriptor.providerId),
1743
+ descriptor =>
1744
+ !disabledProviders.has(descriptor.providerId) &&
1745
+ (providerFilter === undefined || providerFilter.has(descriptor.providerId)),
1719
1746
  );
1720
1747
  // Use peekApiKey to avoid OAuth token refresh during discovery.
1721
1748
  // The token is only needed if the dynamic fetch fires (cache miss),
@@ -1764,6 +1791,7 @@ export class ModelRegistry {
1764
1791
  }
1765
1792
  // Append runtime model managers registered by extensions via fetchDynamicModels.
1766
1793
  for (const { options: managerOpts } of this.#runtimeModelManagers.values()) {
1794
+ if (providerFilter !== undefined && !providerFilter.has(managerOpts.providerId)) continue;
1767
1795
  options.push(managerOpts);
1768
1796
  }
1769
1797
  return options;
package/src/main.ts CHANGED
@@ -34,6 +34,7 @@ import { findConfigFile } from "./config";
34
34
  import { ModelRegistry } from "./config/model-registry";
35
35
  import {
36
36
  getModelMatchPreferences,
37
+ parseModelString,
37
38
  resolveCliModel,
38
39
  resolveModelRoleValue,
39
40
  resolveModelScope,
@@ -861,18 +862,35 @@ async function buildSessionOptions(
861
862
  modelRegistry,
862
863
  preferences: modelMatchPreferences,
863
864
  });
864
- if (resolved.warning) {
865
+ const requestedSelector =
866
+ parsed.provider && !parsed.model.toLowerCase().startsWith(`${parsed.provider.toLowerCase()}/`)
867
+ ? `${parsed.provider}/${parsed.model}`
868
+ : parsed.model;
869
+ const parsedSelector = parseModelString(requestedSelector, {
870
+ allowMaxAlias: true,
871
+ allowAutoAlias: true,
872
+ isLiteralModelId: (provider, id) => modelRegistry.find(provider, id) !== undefined,
873
+ });
874
+ const exactSelector = parsedSelector && !/[?*[\]]/.test(parsedSelector.id);
875
+ const exactModel = exactSelector ? modelRegistry.find(parsedSelector.provider, parsedSelector.id) : undefined;
876
+ const resolvedExactSelector =
877
+ parsedSelector &&
878
+ resolved.selector?.toLowerCase() === `${parsedSelector.provider}/${parsedSelector.id}`.toLowerCase();
879
+ const deferSelection = resolved.error || (exactSelector && !exactModel && !resolvedExactSelector);
880
+ if (resolved.warning && !deferSelection) {
865
881
  process.stderr.write(`${chalk.yellow(`Warning: ${resolved.warning}`)}\n`);
866
882
  }
867
- if (resolved.error) {
868
- if (!parsed.provider && !parsed.model.includes(":")) {
869
- // Model not found in built-in registry — defer resolution to after extensions load
870
- // (extensions may register additional providers/models via registerProvider)
871
- options.modelPattern = parsed.model;
872
- } else {
883
+ if (deferSelection) {
884
+ if (
885
+ resolved.error &&
886
+ parsedSelector?.provider.toLowerCase() !== "google-antigravity" &&
887
+ (parsed.provider || parsed.model.includes(":"))
888
+ ) {
873
889
  process.stderr.write(`${chalk.red(resolved.error)}\n`);
874
890
  process.exit(1);
875
891
  }
892
+ // Register extension overrides before discovering missing account models.
893
+ options.modelPattern = requestedSelector;
876
894
  } else if (resolved.model) {
877
895
  options.model = resolved.model;
878
896
  activeSettings.overrideModelRoles({
@@ -1416,7 +1434,7 @@ export async function runRootCommand(
1416
1434
  notifs.push({ kind: "error", message: modelRegistryError.message });
1417
1435
  }
1418
1436
 
1419
- if (!isInteractive && !session.model) {
1437
+ if (!session.model && (!isInteractive || sessionOptions.modelPattern !== undefined)) {
1420
1438
  if (modelFallbackMessage) {
1421
1439
  process.stderr.write(`${chalk.red(modelFallbackMessage)}\n`);
1422
1440
  } else {
package/src/sdk.ts CHANGED
@@ -1255,6 +1255,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1255
1255
 
1256
1256
  const hasExplicitModel = options.model !== undefined || options.modelPattern !== undefined;
1257
1257
  const modelMatchPreferences = getModelMatchPreferences(settings);
1258
+ const sessionModelStrings =
1259
+ !hasExplicitModel && hasExistingSession
1260
+ ? getRestorableSessionModels(existingSession.models, sessionManager.getLastModelChangeRole())
1261
+ : [];
1258
1262
  const allowedModels = await logger.time("resolveAllowedModels", () =>
1259
1263
  resolveAllowedModels(modelRegistry, settings, modelMatchPreferences),
1260
1264
  );
@@ -1271,10 +1275,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1271
1275
  // host preconnect) can use the restored model; extension-registered
1272
1276
  // providers aren't visible yet, so we retry the preferred candidates once
1273
1277
  // extensions register below.
1274
- const sessionModelStrings =
1275
- !hasExplicitModel && hasExistingSession
1276
- ? getRestorableSessionModels(existingSession.models, sessionManager.getLastModelChangeRole())
1277
- : [];
1278
1278
  let restoredSessionModelIndex = -1;
1279
1279
  let restoredSessionThinkingLevel: ConfiguredThinkingLevel | undefined;
1280
1280
  if (!hasExplicitModel && !model && sessionModelStrings.length > 0) {
@@ -1319,6 +1319,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1319
1319
  }
1320
1320
 
1321
1321
  const taskDepth = options.taskDepth ?? 0;
1322
+ let deferredThinkingLevel: ConfiguredThinkingLevel | undefined;
1322
1323
 
1323
1324
  // Resolves the session/agent thinking level using the same precedence we
1324
1325
  // apply at startup: explicit option → persisted session entry → restored
@@ -1327,7 +1328,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1327
1328
  // role reclaim so the final model's own defaults aren't masked by an earlier
1328
1329
  // fallback model's.
1329
1330
  const pickInitialThinkingLevel = (selectedModel: Model | undefined): ConfiguredThinkingLevel | undefined => {
1330
- let level = options.thinkingLevel;
1331
+ let level = options.thinkingLevel ?? deferredThinkingLevel;
1331
1332
  if (level === undefined && hasExistingSession && hasThinkingEntry) {
1332
1333
  level =
1333
1334
  parseConfiguredThinkingLevel(existingSession.configuredThinkingLevel) ??
@@ -1402,11 +1403,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1402
1403
  },
1403
1404
  );
1404
1405
 
1405
- // Resolve contextFiles up-front (it's needed before tool creation). The
1406
- // workspace tree scan is slow on large repos and we MUST NOT block startup on
1407
- // it. On timeout we forward `undefined` to ToolSession; buildSystemPromptInternal
1408
- // will re-race the same promise through its own withDeadline path. Background
1409
- // work continues so caches still warm.
1406
+ // Resolve contextFiles before tool creation. Bound optional startup discovery;
1407
+ // late work may still populate caches or system-prompt fallback data.
1410
1408
  const raceWithDeadline = async <T>(name: string, work: Promise<T>): Promise<T | undefined> => {
1411
1409
  let timedOut = false;
1412
1410
  const result = await Promise.race([
@@ -1417,7 +1415,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1417
1415
  }),
1418
1416
  ]);
1419
1417
  if (timedOut) {
1420
- logger.warn("Startup scan exceeded deadline; deferring to system prompt fallback", {
1418
+ logger.warn("Startup work exceeded deadline; continuing with available data", {
1421
1419
  name,
1422
1420
  timeoutMs: STARTUP_SCAN_DEADLINE_MS,
1423
1421
  cwd,
@@ -1914,12 +1912,50 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1914
1912
  }
1915
1913
  extensionsResult.runtime.pendingProviderRegistrations = [];
1916
1914
  }
1915
+ // Resolve account-only models after extension overrides own their endpoints.
1916
+ const discoverySelectors = options.modelPattern
1917
+ ? [options.modelPattern]
1918
+ : !hasExplicitModel
1919
+ ? [...sessionModelStrings, settings.getModelRole("default")]
1920
+ : [];
1921
+ const refreshedProviders = new Set<string>();
1922
+ for (const selector of discoverySelectors) {
1923
+ if (!selector) continue;
1924
+ const parsed = parseModelString(selector, {
1925
+ allowMaxAlias: true,
1926
+ allowAutoAlias: true,
1927
+ isLiteralModelId: (provider, id) => modelRegistry.find(provider, id) !== undefined,
1928
+ });
1929
+ if (!parsed || /[?*[\]]/.test(parsed.id)) continue;
1930
+ const existing = modelRegistry.find(parsed.provider, parsed.id);
1931
+ if (existing && hasModelAuth(existing)) break;
1932
+ if (!existing && selector.includes("@")) {
1933
+ const routed = parseModelPattern(selector, modelRegistry.getAll(), modelMatchPreferences);
1934
+ if (routed.upstream && routed.model && hasModelAuth(routed.model)) break;
1935
+ }
1936
+ const providerKey = parsed.provider.toLowerCase();
1937
+ if (existing || refreshedProviders.has(providerKey)) continue;
1938
+ refreshedProviders.add(providerKey);
1939
+ try {
1940
+ await raceWithDeadline(
1941
+ `refreshProvider:${parsed.provider}`,
1942
+ modelRegistry.refreshProvider(parsed.provider, "online"),
1943
+ );
1944
+ } catch (error) {
1945
+ logger.debug("startup model discovery failed", {
1946
+ provider: parsed.provider,
1947
+ error: error instanceof Error ? error.message : String(error),
1948
+ });
1949
+ }
1950
+ const discovered = modelRegistry.find(parsed.provider, parsed.id);
1951
+ if (discovered && hasModelAuth(discovered)) break;
1952
+ }
1917
1953
  // Discover runtime (extension) provider catalogs now that they are
1918
1954
  // registered. The startup refreshInBackground() ran before extensions
1919
1955
  // loaded, so dynamic extension providers are only discovered here. Runs in
1920
1956
  // the background (cache-aware) so startup is never blocked on the fetch; the
1921
1957
  // model list re-renders when the catalog arrives, like other dynamic providers.
1922
- void modelRegistry.refreshRuntimeProviders().catch(error => {
1958
+ void modelRegistry.refreshRuntimeProviders("online-if-uncached", refreshedProviders).catch(error => {
1923
1959
  logger.warn("runtime provider discovery failed", {
1924
1960
  error: error instanceof Error ? error.message : String(error),
1925
1961
  });
@@ -1964,14 +2000,28 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1964
2000
  }
1965
2001
  }
1966
2002
  }
2003
+ if (!hasExplicitModel && restoredSessionModelIndex < 0 && refreshedProviders.size > 0) {
2004
+ model = undefined;
2005
+ }
1967
2006
  // Resolve deferred --model pattern now that extension models are registered.
1968
2007
  if (!model && options.modelPattern) {
1969
2008
  const availableModels = modelRegistry.getAll();
1970
2009
  const matchPreferences = getModelMatchPreferences(settings);
1971
- const { model: resolved } = parseModelPattern(options.modelPattern, availableModels, matchPreferences);
2010
+ const { model: resolved, thinkingLevel: selectorThinkingLevel } = parseModelPattern(
2011
+ options.modelPattern,
2012
+ availableModels,
2013
+ matchPreferences,
2014
+ { allowInvalidThinkingSelectorFallback: false },
2015
+ );
1972
2016
  if (resolved) {
1973
2017
  model = resolved;
1974
2018
  modelFallbackMessage = undefined;
2019
+ deferredThinkingLevel = selectorThinkingLevel;
2020
+ thinkingLevel = pickInitialThinkingLevel(resolved);
2021
+ autoThinking = thinkingLevel === AUTO_THINKING;
2022
+ effectiveThinkingLevel = autoThinking
2023
+ ? resolveProvisionalAutoLevel(resolved)
2024
+ : resolveThinkingLevelForModel(resolved, concreteThinkingLevel(thinkingLevel));
1975
2025
  } else {
1976
2026
  modelFallbackMessage = `Model "${options.modelPattern}" not found`;
1977
2027
  }
@@ -1994,7 +2044,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1994
2044
  // default with a bundled provider's default whenever a stray
1995
2045
  // `OPENAI_API_KEY`/`ANTHROPIC_API_KEY` is in the environment.
1996
2046
  // (issue #3569)
1997
- if (!hasExplicitModel && !defaultRoleSpec.model) {
2047
+ if (!hasExplicitModel && (!defaultRoleSpec.model || refreshedProviders.size > 0)) {
1998
2048
  const reResolvedRoleSpec = resolveModelRoleValue(settings.getModelRole("default"), fallbackCandidates, {
1999
2049
  settings,
2000
2050
  matchPreferences: modelMatchPreferences,