jinzd-ai-cli 0.4.31 → 0.4.32

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.
@@ -2948,7 +2948,8 @@ var ENV_KEY_MAP = {
2948
2948
  kimi: "AICLI_API_KEY_KIMI",
2949
2949
  openai: "AICLI_API_KEY_OPENAI",
2950
2950
  openrouter: "AICLI_API_KEY_OPENROUTER",
2951
- "google-search": "AICLI_API_KEY_GOOGLESEARCH"
2951
+ "google-search": "AICLI_API_KEY_GOOGLESEARCH",
2952
+ ollama: "AICLI_API_KEY_OLLAMA"
2952
2953
  };
2953
2954
  var EnvLoader = class {
2954
2955
  /**
@@ -7,7 +7,7 @@ import {
7
7
  ProviderNotFoundError,
8
8
  RateLimitError,
9
9
  schemaToJsonSchema
10
- } from "./chunk-XI3XPJEV.js";
10
+ } from "./chunk-C2E47GOR.js";
11
11
  import {
12
12
  APP_NAME,
13
13
  CONFIG_DIR_NAME,
@@ -2095,6 +2095,103 @@ var OpenRouterProvider = class extends OpenAICompatibleProvider {
2095
2095
  };
2096
2096
  };
2097
2097
 
2098
+ // src/providers/ollama.ts
2099
+ var OllamaProvider = class extends OpenAICompatibleProvider {
2100
+ defaultBaseUrl = "http://localhost:11434/v1";
2101
+ defaultTimeout = 12e4;
2102
+ // 本地推理可能较慢,默认 2 分钟
2103
+ /** 动态模型列表,initialize 时从 Ollama 获取 */
2104
+ dynamicModels = [];
2105
+ info = {
2106
+ id: "ollama",
2107
+ displayName: "Ollama (Local)",
2108
+ defaultModel: "",
2109
+ // 动态设置
2110
+ apiKeyEnvVar: "",
2111
+ requiresApiKey: false,
2112
+ baseUrl: this.defaultBaseUrl,
2113
+ models: []
2114
+ };
2115
+ async initialize(apiKey, options) {
2116
+ const baseUrl = options?.baseUrl ?? this.defaultBaseUrl;
2117
+ const ollamaHost = baseUrl.replace(/\/v1\/?$/, "");
2118
+ this.dynamicModels = await this.fetchModels(ollamaHost);
2119
+ if (this.dynamicModels.length === 0) {
2120
+ throw new Error("Ollama is running but no models are installed. Run `ollama pull <model>` to install one.");
2121
+ }
2122
+ const preferred = ["llama3.1", "llama3", "qwen2.5", "qwen2", "deepseek-r1", "mistral", "gemma2"];
2123
+ const defaultModel = this.dynamicModels.find(
2124
+ (m) => preferred.some((p) => m.id.startsWith(p))
2125
+ )?.id ?? this.dynamicModels[0].id;
2126
+ Object.assign(this.info, {
2127
+ defaultModel,
2128
+ baseUrl,
2129
+ models: this.dynamicModels
2130
+ });
2131
+ await super.initialize(apiKey || "ollama", { ...options, baseUrl });
2132
+ }
2133
+ /**
2134
+ * 从 Ollama /api/tags 获取本地模型列表。
2135
+ * 如果 Ollama 未运行则抛出错误。
2136
+ */
2137
+ async fetchModels(ollamaHost) {
2138
+ const url = `${ollamaHost}/api/tags`;
2139
+ let response;
2140
+ try {
2141
+ response = await fetch(url, { signal: AbortSignal.timeout(5e3) });
2142
+ } catch {
2143
+ throw new Error(
2144
+ `Cannot connect to Ollama at ${ollamaHost}. Make sure Ollama is running (https://ollama.com).`
2145
+ );
2146
+ }
2147
+ if (!response.ok) {
2148
+ throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
2149
+ }
2150
+ const data = await response.json();
2151
+ if (!data.models || data.models.length === 0) {
2152
+ return [];
2153
+ }
2154
+ return data.models.map((m) => {
2155
+ const paramSize = m.details?.parameter_size ?? "";
2156
+ const sizeGB = (m.size / 1e9).toFixed(1);
2157
+ return {
2158
+ id: m.name,
2159
+ displayName: `${m.name} (${paramSize || sizeGB + "GB"})`,
2160
+ contextWindow: this.estimateContextWindow(m.name),
2161
+ supportsStreaming: true,
2162
+ supportsThinking: false
2163
+ };
2164
+ });
2165
+ }
2166
+ /** 根据模型名估算上下文窗口(Ollama 模型元数据不含此信息) */
2167
+ estimateContextWindow(modelName) {
2168
+ const name = modelName.toLowerCase();
2169
+ if (name.includes("llama3") || name.includes("llama-3")) return 131072;
2170
+ if (name.includes("qwen2.5") || name.includes("qwen3")) return 131072;
2171
+ if (name.includes("qwen2") || name.includes("qwen-2")) return 32768;
2172
+ if (name.includes("deepseek")) return 65536;
2173
+ if (name.includes("gemma2") || name.includes("gemma-2")) return 8192;
2174
+ if (name.includes("mistral")) return 32768;
2175
+ if (name.includes("phi")) return 16384;
2176
+ if (name.includes("codellama") || name.includes("code-llama")) return 16384;
2177
+ return 8192;
2178
+ }
2179
+ /** 动态返回当前 Ollama 安装的模型 */
2180
+ async listModels() {
2181
+ return this.dynamicModels.length > 0 ? this.dynamicModels : this.info.models;
2182
+ }
2183
+ /** Ollama 无需验证 API Key,只检查服务是否可达 */
2184
+ async validateApiKey() {
2185
+ try {
2186
+ const ollamaHost = this.info.baseUrl.replace(/\/v1\/?$/, "");
2187
+ await fetch(`${ollamaHost}/api/tags`, { signal: AbortSignal.timeout(5e3) });
2188
+ return true;
2189
+ } catch {
2190
+ return false;
2191
+ }
2192
+ }
2193
+ };
2194
+
2098
2195
  // src/providers/custom.ts
2099
2196
  var CustomProvider = class extends OpenAICompatibleProvider {
2100
2197
  defaultBaseUrl;
@@ -2137,7 +2234,8 @@ var BUILT_IN_PROVIDERS = [
2137
2234
  ZhipuProvider,
2138
2235
  KimiProvider,
2139
2236
  OpenAIProvider,
2140
- OpenRouterProvider
2237
+ OpenRouterProvider,
2238
+ OllamaProvider
2141
2239
  ];
2142
2240
  var ProviderRegistry = class {
2143
2241
  providers = /* @__PURE__ */ new Map();
@@ -2147,10 +2245,14 @@ var ProviderRegistry = class {
2147
2245
  for (const ProviderClass of BUILT_IN_PROVIDERS) {
2148
2246
  const provider = new ProviderClass();
2149
2247
  const apiKey = getApiKey(provider.info.id);
2150
- if (apiKey) {
2248
+ if (apiKey || !provider.info.requiresApiKey) {
2151
2249
  const options = getOptions?.(provider.info.id) ?? {};
2152
- await provider.initialize(apiKey, options);
2153
- this.providers.set(provider.info.id, provider);
2250
+ try {
2251
+ await provider.initialize(apiKey ?? "", options);
2252
+ this.providers.set(provider.info.id, provider);
2253
+ } catch (err) {
2254
+ if (provider.info.requiresApiKey) throw err;
2255
+ }
2154
2256
  }
2155
2257
  }
2156
2258
  this.customConfigs = customProviderConfigs ?? [];
@@ -387,7 +387,7 @@ ${content}`);
387
387
  }
388
388
  }
389
389
  async function runTaskMode(config, providers, configManager, topic) {
390
- const { TaskOrchestrator } = await import("./task-orchestrator-JOQMPOOR.js");
390
+ const { TaskOrchestrator } = await import("./task-orchestrator-V7BBE2VP.js");
391
391
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
392
392
  let interrupted = false;
393
393
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  saveDevState,
25
25
  sessionHasMeaningfulContent,
26
26
  setupProxy
27
- } from "./chunk-ZYM2FGYT.js";
27
+ } from "./chunk-FSAKLKZH.js";
28
28
  import {
29
29
  ToolExecutor,
30
30
  ToolRegistry,
@@ -37,7 +37,7 @@ import {
37
37
  spawnAgentContext,
38
38
  theme,
39
39
  undoStack
40
- } from "./chunk-XI3XPJEV.js";
40
+ } from "./chunk-C2E47GOR.js";
41
41
  import {
42
42
  fileCheckpoints
43
43
  } from "./chunk-4BKXL7SM.js";
@@ -5397,7 +5397,7 @@ program.command("web").description("Start Web UI server with browser-based chat
5397
5397
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
5398
5398
  process.exit(1);
5399
5399
  }
5400
- const { startWebServer } = await import("./server-WFEWHSQR.js");
5400
+ const { startWebServer } = await import("./server-T3ABTKH3.js");
5401
5401
  await startWebServer({ port, host: options.host });
5402
5402
  });
5403
5403
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -5630,7 +5630,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
5630
5630
  }),
5631
5631
  config.get("customProviders")
5632
5632
  );
5633
- const { startHub } = await import("./hub-ZGCGCIOP.js");
5633
+ const { startHub } = await import("./hub-J6CX3YDH.js");
5634
5634
  await startHub(
5635
5635
  {
5636
5636
  topic: topic ?? "",
@@ -15,7 +15,7 @@ import {
15
15
  hadPreviousWriteToolCalls,
16
16
  loadDevState,
17
17
  setupProxy
18
- } from "./chunk-ZYM2FGYT.js";
18
+ } from "./chunk-FSAKLKZH.js";
19
19
  import {
20
20
  AuthManager
21
21
  } from "./chunk-BYNY5JPB.js";
@@ -33,7 +33,7 @@ import {
33
33
  spawnAgentContext,
34
34
  truncateOutput,
35
35
  undoStack
36
- } from "./chunk-XI3XPJEV.js";
36
+ } from "./chunk-C2E47GOR.js";
37
37
  import "./chunk-4BKXL7SM.js";
38
38
  import {
39
39
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -4,7 +4,7 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-XI3XPJEV.js";
7
+ } from "./chunk-C2E47GOR.js";
8
8
  import "./chunk-4BKXL7SM.js";
9
9
  import {
10
10
  SUBAGENT_ALLOWED_TOOLS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.31",
3
+ "version": "0.4.32",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",