ai_co_discuss 0.2.3 → 0.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.
package/docs/runner.md CHANGED
@@ -77,7 +77,7 @@ python server.py --host 127.0.0.1 --local-launch
77
77
 
78
78
  ### 本机模型目录
79
79
 
80
- 网页的自动 CLI 模型选择通过 `POST /api/local/models` 读取本机软件目录。输入为 `adapter`、可选完整 `executable`;未传路径时沿用该适配器的本机配置。CLI 对应 `codiscuss launch models --file FILE`。返回 `{adapter, models:[{id,label,efforts?}], source, error?}`,只含模型元数据;没有列表时显示原因并允许使用软件默认,不要求手填 ID。页面按适配器和路径缓存,模型支持的档位随目录返回;App、纯网页和手动会话在原软件设置。
80
+ 网页的自动 CLI 模型选择通过 `POST /api/local/models` 读取本机软件目录。输入为 `adapter`、可选完整 `executable`;未传路径时沿用该适配器的本机配置。CLI 对应 `codiscuss launch models --file FILE`。返回 `{adapter, models:[{id,label,efforts?}], source, error?}`,只含模型元数据;没有列表时显示原因并允许使用软件默认,不要求手填 ID。部分 CLI 列表较慢(`agy models` 实测约 15 秒),本机服务把成功结果按适配器、路径与可执行文件大小/修改时间缓存在服务目录的 `model-catalogs.json`,24 小时内复用;可执行文件变化或点“重试”(`refresh: true`)时重新查询,查询失败不覆盖上次成功的列表。卡片加入后即在后台预读,不等翻面;页面同样按适配器和路径缓存,模型支持的档位随目录返回;App、纯网页和手动会话在原软件设置。
81
81
 
82
82
  - Codex:临时运行 `app-server`,依次 `initialize`、`initialized`、分页 `model/list`,不创建 thread 或 turn。见 [官方协议](https://learn.chatgpt.com/docs/app-server#list-models-modellist)。
83
83
  - Claude:临时以 `--safe-mode --no-session-persistence` 开启流式控制通道,仅发送 initialize,读取软件返回的 models 和 supportedEffortLevels;禁用自定义启动扩展,不发送用户消息。协议依据 [官方客户端初始化](https://github.com/anthropics/claude-agent-sdk-python/blob/main/src/claude_agent_sdk/_internal/query.py)。旧版本不支持时显示查询失败。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai_co_discuss",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "bin": {
5
5
  "codiscuss": "runner/codiscuss.mjs"
6
6
  },
@@ -52,6 +52,28 @@ conversation 还须 current_session_id,当前会话接手首个主持任务。
52
52
  详见 docs/runner.md 的“两种讨论入口与启动配置”。
53
53
  `;
54
54
 
55
+ /** Some CLIs take seconds to list models; reuse a good list for a day unless the executable changes or the user retries. */
56
+ const MODEL_CACHE_MS = 24 * 60 * 60 * 1000;
57
+
58
+ /** Return the software catalog, served from the private service home when still valid. */
59
+ export async function cached_software_models(home, profile, { refresh = false, query = software_models } = {}) {
60
+ const path = join(home, 'model-catalogs.json');
61
+ let stamp = '';
62
+ try { const file = statSync(profile.executable); stamp = `${file.size}:${file.mtimeMs}`; } catch {}
63
+ const key = JSON.stringify([profile.adapter, profile.executable || '', stamp]);
64
+ let cache = {};
65
+ try { cache = JSON.parse(readFileSync(path, 'utf8')); } catch {}
66
+ const hit = cache[key];
67
+ if (!refresh && hit && Date.now() - hit.at < MODEL_CACHE_MS) return hit.result;
68
+ const result = await query(profile);
69
+ if (!result.error && result.models.length) {
70
+ const prefix = JSON.stringify([profile.adapter, profile.executable || '']).slice(0, -1);
71
+ for (const old of Object.keys(cache)) if (old.startsWith(prefix)) delete cache[old];
72
+ atomic_json(path, { ...cache, [key]: { at: Date.now(), result } });
73
+ }
74
+ return result;
75
+ }
76
+
55
77
  /** Read private defaults without selecting a global room or identity. */
56
78
  export function launch_settings(home) {
57
79
  const path = join(home, 'launch-settings.json');
@@ -585,7 +607,7 @@ export async function launch_main(args = process.argv.slice(2), input_text) {
585
607
  else if (positionals[0] === 'room-models' && input) result = room_models(home, input);
586
608
  else if (positionals[0] === 'models' && input) {
587
609
  const saved = Object.values(launch_settings(home).participants).find(profile => profile.adapter === input.adapter);
588
- result = await software_models({ adapter: input.adapter, executable: input.executable || saved?.executable });
610
+ result = await cached_software_models(home, { adapter: input.adapter, executable: input.executable || saved?.executable }, { refresh: input.refresh === true });
589
611
  }
590
612
  else if (positionals[0] === 'room-member' && input) result = await room_member(home, input);
591
613
  else if (positionals[0] === 'start' && input) result = await launch_discussion(home, input, { dry_run: values['dry-run'], browser_request: values.browser });
package/web/app.js CHANGED
@@ -1591,7 +1591,7 @@ async function load_launch_models(name, force = false) {
1591
1591
  const catalogs = state.launch.model_catalogs ||= {}, key = model_catalog_key(profile);
1592
1592
  if (catalogs[key]?.loading || (!force && catalogs[key])) return;
1593
1593
  const current = { models: [], loading: true, error: "", source: "" };
1594
- const input = { adapter: profile.adapter, ...(profile.executable ? { executable: profile.executable } : {}) };
1594
+ const input = { adapter: profile.adapter, ...(profile.executable ? { executable: profile.executable } : {}), ...(force ? { refresh: true } : {}) };
1595
1595
  catalogs[key] = current;
1596
1596
  publish_view();
1597
1597
  try {