evolcore 0.0.21 → 0.0.23
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/AGENTS.md +62 -0
- package/CHANGELOG.md +53 -0
- package/README.md +2 -0
- package/bin/codex-managed-acl.ps1 +155 -0
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +376 -96
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +1 -1
- package/dist/agents/claude-runner.js +44 -226
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +154 -0
- package/dist/agents/codex-runner.js +1292 -219
- package/dist/agents/ecagent-runner.js +10 -31
- package/dist/agents/gemini-runner.js +2 -6
- package/dist/aun/aid/store.js +2 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +51 -4
- package/dist/aun/outbox.js +7 -0
- package/dist/aun/rpc/client.js +8 -1
- package/dist/channels/aun.js +240 -36
- package/dist/channels/wechat.js +81 -10
- package/dist/cli/aun-commands.js +48 -4
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/daemon-commands.js +169 -51
- package/dist/cli/index.js +20 -0
- package/dist/cli/init.js +2 -10
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/task-context.js +16 -2
- package/dist/cli/trigger-command.js +22 -5
- package/dist/config/config-field-policy.js +3 -0
- package/dist/config/config-manager.js +32 -5
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +15 -0
- package/dist/core/auth/auth-gateway.js +8 -0
- package/dist/core/auth/authorization-audit.js +70 -6
- package/dist/core/auth/operation-catalog.js +4 -0
- package/dist/core/auth/remote-access-store.js +135 -0
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +93 -25
- package/dist/core/command/command-handler.js +62 -5
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +150 -33
- package/dist/core/command/menu-protocol.js +120 -1
- package/dist/core/command/slash-gate.js +3 -3
- package/dist/core/command/slash-handler.js +83 -40
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +32 -0
- package/dist/core/handoff/runtime.js +23 -3
- package/dist/core/message/im-renderer.js +7 -3
- package/dist/core/message/message-bridge.js +60 -2
- package/dist/core/message/message-log.js +33 -0
- package/dist/core/message/message-queue.js +41 -15
- package/dist/core/message/response-engine.js +246 -114
- package/dist/core/model/catalog-service.js +308 -0
- package/dist/core/model/model-catalog.js +124 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +1 -1
- package/dist/core/permission/approval-gateway.js +16 -1
- package/dist/core/permission/ec-command-parser.js +272 -70
- package/dist/core/permission/protected-paths.js +74 -14
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +290 -30
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/session-manager.js +30 -0
- package/dist/core/session/session-renew.js +18 -1
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/index.js +104 -34
- package/dist/ipc.js +218 -96
- package/dist/paths.js +18 -0
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/feedback.js +14 -2
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +86 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/trigger/scheduler.js +20 -3
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +7 -0
- package/dist/utils/logger.js +9 -4
- package/dist/utils/npm-ops.js +47 -15
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/evolcore/INDEX.md +3 -2
- package/kits/docs/evolcore/agent-create.md +159 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/group-collaboration.md +256 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/schemas/_meta.json +3 -2
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +10 -2
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +11 -3
|
@@ -1,359 +1,149 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* model
|
|
3
|
-
*
|
|
4
|
-
* 目录来源按 baseagent 隔离:Codex 预创建优先使用 CLI 动态目录;
|
|
5
|
-
* 其他场景探测本 baseagent 的 {baseUrl}/v1/models 与 {baseUrl}/models,
|
|
6
|
-
* 最后只回退到同 baseagent 的内置目录。
|
|
7
|
-
*
|
|
8
|
-
* baseUrl/apiKey 复用 baseagent 凭据解析链(与发消息同源),命令不接收 url/key 参数。
|
|
9
|
-
* 单模型详情(info)现阶段全部为 mock,{baseUrl}/models 接口补全字段后自动接真实数据。
|
|
10
|
-
*
|
|
11
|
-
* 详见 docs/model-command-design.md。
|
|
2
|
+
* Public model catalog facade. Online consumers query the daemon; its service
|
|
3
|
+
* owns refreshes and snapshots. Runners in the daemon use that same instance.
|
|
12
4
|
*/
|
|
13
|
-
import
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { resolveAnthropicConfig, resolveEcagentConfig, resolveOpenaiConfig, loadCodexSettings } from '../../agents/baseagent.js';
|
|
14
7
|
import { buildModelRequestHeaders } from '../../agents/request-identity.js';
|
|
15
|
-
import { appendRequestPath, appendV1RequestPath } from '../../agents/request-identity.js';
|
|
16
8
|
import { activeBaseagent } from './config-scope.js';
|
|
17
9
|
import { resolveEffective } from '../../config/config-manager.js';
|
|
18
10
|
import { loadDefaults } from '../../config-store.js';
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
11
|
+
import net from 'node:net';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import { getWindowsInstanceSocketPathCandidates, resolvePaths } from '../../paths.js';
|
|
14
|
+
import { ModelCatalogService, catalogEfforts, resolveCatalogModel } from './catalog-service.js';
|
|
15
|
+
export * from './catalog-service.js';
|
|
16
|
+
let daemonOwner = false;
|
|
17
|
+
export function setModelCatalogDaemonOwner(value) { daemonOwner = value; }
|
|
18
|
+
/** Only a refused/missing socket means offline. A busy/rejecting daemon must
|
|
19
|
+
* never cause another process to create an independent online catalog. */
|
|
20
|
+
export async function queryDaemonCatalog(self, baseagent, refresh = false) {
|
|
21
|
+
const paths = resolvePaths();
|
|
22
|
+
let controlToken;
|
|
28
23
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
headers: buildModelRequestHeaders({ baseagent: 'ecagent', baseUrl: r.baseUrl, agentAid: self, configuredHeaders: r.headers }),
|
|
48
|
-
queryParams: r.queryParams,
|
|
24
|
+
controlToken = fs.readFileSync(paths.controlToken, 'utf8').trim();
|
|
25
|
+
}
|
|
26
|
+
catch { /* Server returns the authorization error. */ }
|
|
27
|
+
const candidates = process.platform === 'win32' ? getWindowsInstanceSocketPathCandidates(paths.root) : [paths.instanceSocket];
|
|
28
|
+
for (const socket of [...new Set([paths.instanceSocket, ...candidates])]) {
|
|
29
|
+
const response = await new Promise((resolve, reject) => {
|
|
30
|
+
const connection = net.connect(socket);
|
|
31
|
+
let buffer = '';
|
|
32
|
+
let done = false;
|
|
33
|
+
const finish = (error, value = null) => {
|
|
34
|
+
if (done)
|
|
35
|
+
return;
|
|
36
|
+
done = true;
|
|
37
|
+
connection.destroy();
|
|
38
|
+
if (error)
|
|
39
|
+
reject(error);
|
|
40
|
+
else
|
|
41
|
+
resolve(value);
|
|
49
42
|
};
|
|
43
|
+
connection.setTimeout(25_000, () => finish(new Error('模型目录 daemon 查询超时')));
|
|
44
|
+
connection.on('connect', () => connection.write(JSON.stringify({ type: 'model.catalog', self, baseagent, refresh, controlToken }) + '\n'));
|
|
45
|
+
connection.on('error', (error) => {
|
|
46
|
+
if (error.code === 'ENOENT' || error.code === 'ECONNREFUSED')
|
|
47
|
+
finish();
|
|
48
|
+
else
|
|
49
|
+
finish(new Error('无法连接模型目录 daemon'));
|
|
50
|
+
});
|
|
51
|
+
connection.on('data', chunk => {
|
|
52
|
+
buffer += chunk.toString();
|
|
53
|
+
if (buffer.length > 8 * 1024 * 1024)
|
|
54
|
+
return finish(new Error('模型目录响应过大'));
|
|
55
|
+
const end = buffer.indexOf('\n');
|
|
56
|
+
if (end < 0)
|
|
57
|
+
return;
|
|
58
|
+
try {
|
|
59
|
+
const data = JSON.parse(buffer.slice(0, end));
|
|
60
|
+
if (!data.ok || !data.snapshot)
|
|
61
|
+
return finish(new Error(data.error || 'daemon 不支持统一模型目录,请更新并重启'));
|
|
62
|
+
finish(undefined, data.snapshot);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
finish(new Error('模型目录 daemon 返回无效响应'));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
connection.on('end', () => finish(new Error('模型目录 daemon 提前关闭连接')));
|
|
69
|
+
});
|
|
70
|
+
if (response)
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const service = new ModelCatalogService({
|
|
76
|
+
storageDir: () => path.join(path.dirname(resolvePaths().claudeModelCache), 'model-catalog'),
|
|
77
|
+
legacyAliasFile: () => resolvePaths().claudeModelCache,
|
|
78
|
+
native: async (context) => (await import('../../agents/codex/catalog.js')).codexNativeCatalog(context),
|
|
79
|
+
});
|
|
80
|
+
export function resolveCatalogContext(self, ba) {
|
|
81
|
+
const baseagent = ba || activeBaseagent(self);
|
|
82
|
+
const context = { scope: self || '<defaults>', baseagent };
|
|
83
|
+
try {
|
|
84
|
+
const config = self ? resolveEffective({ self }, { cache: true, expand: true }) : { baseagents: loadDefaults()?.baseagents ?? {} };
|
|
85
|
+
const block = (config.baseagents ?? {});
|
|
86
|
+
const cfg = { agents: block };
|
|
87
|
+
const resolved = baseagent === 'codex' ? resolveOpenaiConfig(cfg, block.codex)
|
|
88
|
+
: baseagent === 'claude' ? resolveAnthropicConfig(cfg, block.claude)
|
|
89
|
+
: baseagent === 'ecagent' ? resolveEcagentConfig(cfg, block.ecagent) : undefined;
|
|
90
|
+
if (resolved) {
|
|
91
|
+
Object.assign(context, { baseUrl: resolved.baseUrl, apiKey: resolved.apiKey, queryParams: resolved.queryParams,
|
|
92
|
+
provider: 'provider' in resolved ? resolved.provider : undefined,
|
|
93
|
+
headers: buildModelRequestHeaders({ baseagent: baseagent, baseUrl: resolved.baseUrl, agentAid: self, configuredHeaders: resolved.headers }) });
|
|
50
94
|
}
|
|
51
|
-
if (baseagent === 'gemini')
|
|
52
|
-
return {};
|
|
53
|
-
if (baseagent !== 'claude')
|
|
54
|
-
return {};
|
|
55
|
-
const c = block.claude;
|
|
56
|
-
const r = resolveAnthropicConfig({ agents: { claude: c } }, c);
|
|
57
|
-
return {
|
|
58
|
-
baseUrl: r.baseUrl,
|
|
59
|
-
apiKey: r.apiKey,
|
|
60
|
-
agentAid: self,
|
|
61
|
-
headers: buildModelRequestHeaders({ baseagent: 'claude', baseUrl: r.baseUrl, agentAid: self, configuredHeaders: r.headers }),
|
|
62
|
-
queryParams: r.queryParams,
|
|
63
|
-
};
|
|
64
95
|
}
|
|
65
96
|
catch {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return typeof v === 'string' ? v : undefined;
|
|
76
|
-
}
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
/** 从对象条目里提取厂商(兼容 owned_by/owner/provider)。 */
|
|
80
|
-
function pickOwner(item) {
|
|
81
|
-
if (item && typeof item === 'object') {
|
|
82
|
-
const v = item.owned_by ?? item.owner ?? item.provider;
|
|
83
|
-
return typeof v === 'string' ? v : undefined;
|
|
97
|
+
// Codex account login may not have an API key. Native listing still works,
|
|
98
|
+
// but do not replace a configured Agent gateway with the host connection.
|
|
99
|
+
const defaults = self ? resolveEffective({ self }, { cache: true, expand: true }) : { baseagents: loadDefaults()?.baseagents ?? {} };
|
|
100
|
+
const block = defaults.baseagents?.[baseagent];
|
|
101
|
+
const settings = baseagent === 'codex' ? loadCodexSettings() : undefined;
|
|
102
|
+
if (baseagent === 'codex' && !block?.baseUrl && !process.env.OPENAI_BASE_URL && !settings?.baseUrl && !block?.apiKey)
|
|
103
|
+
Object.assign(context, { provider: settings?.provider });
|
|
104
|
+
else
|
|
105
|
+
Object.assign(context, { configUnavailable: true, baseUrl: block?.baseUrl, headers: block?.headers, queryParams: block?.queryParams, apiKey: block?.apiKey });
|
|
84
106
|
}
|
|
85
|
-
return
|
|
107
|
+
return context;
|
|
86
108
|
}
|
|
87
|
-
/**
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
* - 条目:字符串 或 {id|name|model, owned_by|owner|provider, created}
|
|
91
|
-
*/
|
|
92
|
-
function genericParse(json) {
|
|
93
|
-
const arr = Array.isArray(json) ? json
|
|
94
|
-
: Array.isArray(json?.data) ? json.data
|
|
95
|
-
: Array.isArray(json?.models) ? json.models
|
|
96
|
-
: Array.isArray(json?.data?.models) ? json.data.models
|
|
97
|
-
: [];
|
|
98
|
-
const out = [];
|
|
99
|
-
for (const item of arr) {
|
|
100
|
-
const id = pickId(item);
|
|
101
|
-
if (!id)
|
|
102
|
-
continue;
|
|
103
|
-
out.push({ id, owned_by: pickOwner(item), created: typeof item?.created === 'number' ? item.created : undefined });
|
|
104
|
-
}
|
|
105
|
-
return out;
|
|
109
|
+
/** Used by the IPC handler and the owner process. Never calls IPC recursively. */
|
|
110
|
+
export async function getLocalCatalog(self, ba, refresh = false) {
|
|
111
|
+
return service.get(resolveCatalogContext(self, ba), refresh);
|
|
106
112
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
* name: 'some-gateway',
|
|
113
|
-
* match: (url) => url.includes('some-gateway.example.com'),
|
|
114
|
-
* parse: (json) => (json?.result?.modelList ?? []).map((m: any) => ({ id: m.code, owned_by: m.vendor })),
|
|
115
|
-
* }
|
|
116
|
-
*/
|
|
117
|
-
const GATEWAY_PARSERS = [];
|
|
118
|
-
/** 选择 parser 并解析(专用优先,否则通用容错)。 */
|
|
119
|
-
export function parseModelList(json, url) {
|
|
120
|
-
for (const rule of GATEWAY_PARSERS) {
|
|
121
|
-
try {
|
|
122
|
-
if (rule.match(url, json))
|
|
123
|
-
return rule.parse(json);
|
|
124
|
-
}
|
|
125
|
-
catch { /* 专用 parser 失败则继续尝试通用解析 */ }
|
|
113
|
+
export async function getCatalog(self, ba, refresh = false) {
|
|
114
|
+
if (!daemonOwner) {
|
|
115
|
+
const snapshot = await queryDaemonCatalog(self, ba, refresh);
|
|
116
|
+
if (snapshot)
|
|
117
|
+
return snapshot;
|
|
126
118
|
}
|
|
127
|
-
return
|
|
119
|
+
return getLocalCatalog(self, ba, refresh);
|
|
128
120
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
const controller = new AbortController();
|
|
136
|
-
timer = setTimeout(() => controller.abort(), MODEL_LIST_TIMEOUT_MS);
|
|
137
|
-
const resp = await fetch(url, {
|
|
138
|
-
signal: controller.signal,
|
|
139
|
-
headers: {
|
|
140
|
-
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
141
|
-
...requestHeaders,
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
if (!resp.ok)
|
|
145
|
-
return null;
|
|
146
|
-
const json = await resp.json();
|
|
147
|
-
const entries = parseModelList(json, url);
|
|
148
|
-
return entries.length > 0 ? entries : null;
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
finally {
|
|
154
|
-
if (timer)
|
|
155
|
-
clearTimeout(timer);
|
|
156
|
-
}
|
|
121
|
+
/** Connection-bound runner wrapper; effective Agent scope wins when available. */
|
|
122
|
+
export function getRunnerCatalog(baseagent, self, connection) {
|
|
123
|
+
if (self)
|
|
124
|
+
return getCatalog(self, baseagent);
|
|
125
|
+
return service.get({ ...connection, scope: '<defaults>', baseagent });
|
|
157
126
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
{ id: 'claude-opus-4-7', owned_by: 'anthropic' },
|
|
161
|
-
{ id: 'claude-opus-4-6', owned_by: 'anthropic' },
|
|
162
|
-
{ id: 'claude-sonnet-4-6', owned_by: 'anthropic' },
|
|
163
|
-
{ id: 'claude-haiku-4-5-20251001', owned_by: 'anthropic' },
|
|
164
|
-
{ id: 'deepseek-v4-pro', owned_by: 'deepseek' },
|
|
165
|
-
{ id: 'deepseek-v4-flash', owned_by: 'deepseek' },
|
|
166
|
-
{ id: 'kimi-k2.6', owned_by: 'moonshot' },
|
|
167
|
-
{ id: 'kimi-k2.5', owned_by: 'moonshot' },
|
|
168
|
-
{ id: 'glm-5.1', owned_by: 'zhipu' },
|
|
169
|
-
{ id: 'glm-5', owned_by: 'zhipu' },
|
|
170
|
-
{ id: 'glm-4.7', owned_by: 'zhipu' },
|
|
171
|
-
{ id: 'MiniMax-M2.7', owned_by: 'minimax' },
|
|
172
|
-
];
|
|
173
|
-
const CODEX_BUILTIN_CATALOG = [
|
|
174
|
-
{ id: 'gpt-5.5', owned_by: 'openai' },
|
|
175
|
-
{ id: 'gpt-5.4', owned_by: 'openai' },
|
|
176
|
-
{ id: 'gpt-5.4-mini', owned_by: 'openai' },
|
|
177
|
-
{ id: 'gpt-5.3-codex', owned_by: 'openai' },
|
|
178
|
-
{ id: 'gpt-5.2', owned_by: 'openai' },
|
|
179
|
-
];
|
|
180
|
-
const GEMINI_BUILTIN_CATALOG = [
|
|
181
|
-
{ id: 'gemini-2.5-pro', owned_by: 'google' },
|
|
182
|
-
{ id: 'gemini-2.5-flash', owned_by: 'google' },
|
|
183
|
-
{ id: 'gemini-2.5-flash-lite', owned_by: 'google' },
|
|
184
|
-
];
|
|
185
|
-
const catalogCache = new Map();
|
|
186
|
-
const catalogInFlight = new Map();
|
|
187
|
-
export function clearModelCatalogCache() {
|
|
188
|
-
catalogCache.clear();
|
|
189
|
-
catalogInFlight.clear();
|
|
127
|
+
export function peekRunnerCatalog(baseagent, self, connection) {
|
|
128
|
+
return service.peek(self ? resolveCatalogContext(self, baseagent) : { ...connection, scope: '<defaults>', baseagent });
|
|
190
129
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return CODEX_BUILTIN_CATALOG.map(model => ({ ...model }));
|
|
195
|
-
if (baseagent === 'gemini')
|
|
196
|
-
return GEMINI_BUILTIN_CATALOG.map(model => ({ ...model }));
|
|
197
|
-
if (baseagent === 'ecagent')
|
|
198
|
-
return [{ id: 'claude-sonnet-4-6', owned_by: 'anthropic' }];
|
|
199
|
-
return MOCK_CATALOG.map(model => ({ ...model }));
|
|
200
|
-
}
|
|
201
|
-
function codexCliCatalog() {
|
|
202
|
-
try {
|
|
203
|
-
const output = execCodexCliSync(['debug', 'models'], {
|
|
204
|
-
encoding: 'utf-8',
|
|
205
|
-
timeout: CODEX_CLI_CATALOG_TIMEOUT_MS,
|
|
206
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
207
|
-
});
|
|
208
|
-
const payload = JSON.parse(String(output));
|
|
209
|
-
const models = Array.isArray(payload?.models)
|
|
210
|
-
? payload.models
|
|
211
|
-
.filter((model) => model?.visibility === 'list' && typeof model?.slug === 'string')
|
|
212
|
-
.map((model) => ({ id: model.slug, owned_by: 'openai' }))
|
|
213
|
-
: [];
|
|
214
|
-
return models.length > 0 ? models : null;
|
|
215
|
-
}
|
|
216
|
-
catch {
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
130
|
+
export async function getModelEfforts(model, self, ba) {
|
|
131
|
+
const baseagent = ba || activeBaseagent(self);
|
|
132
|
+
return catalogEfforts(await getCatalog(self, baseagent), model, baseagent);
|
|
219
133
|
}
|
|
134
|
+
export function clearModelCatalogCache() { service.clear(); }
|
|
220
135
|
export function modelMatches(agent, candidate, current) {
|
|
221
136
|
if (!current)
|
|
222
137
|
return false;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const resolve = typeof agent?.resolveModelId === 'function'
|
|
226
|
-
? (value) => agent.resolveModelId(value) ?? value
|
|
227
|
-
: (value) => value;
|
|
228
|
-
const resolvedCandidate = resolve(candidate);
|
|
229
|
-
const resolvedCurrent = resolve(current);
|
|
230
|
-
return candidate === resolvedCurrent || resolvedCandidate === current || resolvedCandidate === resolvedCurrent;
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* 远端模型目录接口(临时)。本地 baseUrl 的 /v1/models、/models 尚未实现,
|
|
234
|
-
* 自动降级到此接口拉取真实可用模型列表;待本地接口就绪后此级自然不再命中。
|
|
235
|
-
*/
|
|
236
|
-
const REMOTE_CATALOG_URL = 'https://mg-new.evolai.cn/claude-proxy/models';
|
|
237
|
-
/**
|
|
238
|
-
* 各 baseagent 的稳定别名(cc/SDK 自动解析到最新版本)。
|
|
239
|
-
* 这些是一等公民——defaults.json 默认就存别名(如 "opus"),必须可选可校验。
|
|
240
|
-
*/
|
|
241
|
-
const KNOWN_ALIASES = {
|
|
242
|
-
claude: ['opus', 'sonnet', 'haiku'],
|
|
243
|
-
codex: [],
|
|
244
|
-
gemini: [],
|
|
245
|
-
};
|
|
246
|
-
/** 把别名作为虚拟条目并入目录头部(owned_by 标 alias,便于展示与校验)。 */
|
|
247
|
-
function withAliases(models, ba) {
|
|
248
|
-
const aliases = KNOWN_ALIASES[ba] || [];
|
|
249
|
-
const existing = new Set(models.map(m => m.id));
|
|
250
|
-
const aliasEntries = aliases
|
|
251
|
-
.filter(a => !existing.has(a))
|
|
252
|
-
.map(a => ({ id: a, owned_by: 'alias' }));
|
|
253
|
-
return [...aliasEntries, ...models];
|
|
138
|
+
const resolve = (value) => agent?.resolveModelId?.(value) ?? value;
|
|
139
|
+
return resolve(candidate) === resolve(current);
|
|
254
140
|
}
|
|
255
|
-
/** 拉取模型目录,并按 self + baseagent 缓存短时间结果。 */
|
|
256
|
-
export async function getCatalog(self, ba) {
|
|
257
|
-
const baseagent = ba || activeBaseagent(self);
|
|
258
|
-
const cacheKey = `${self ?? '<defaults>'}:${baseagent}`;
|
|
259
|
-
const cached = catalogCache.get(cacheKey);
|
|
260
|
-
if (cached && cached.expiresAt > Date.now())
|
|
261
|
-
return cached.value;
|
|
262
|
-
const pending = catalogInFlight.get(cacheKey);
|
|
263
|
-
if (pending)
|
|
264
|
-
return pending;
|
|
265
|
-
const promise = loadCatalog(self, baseagent)
|
|
266
|
-
.then(value => {
|
|
267
|
-
catalogCache.set(cacheKey, { value, expiresAt: Date.now() + MODEL_CATALOG_TTL_MS });
|
|
268
|
-
return value;
|
|
269
|
-
})
|
|
270
|
-
.finally(() => catalogInFlight.delete(cacheKey));
|
|
271
|
-
catalogInFlight.set(cacheKey, promise);
|
|
272
|
-
return promise;
|
|
273
|
-
}
|
|
274
|
-
async function loadCatalog(self, baseagent) {
|
|
275
|
-
// Pre-create has no runner yet. Mirror CodexRunner's dynamic CLI fallback
|
|
276
|
-
// without changing existing self-scoped catalog resolution.
|
|
277
|
-
if (!self && baseagent === 'codex') {
|
|
278
|
-
const cliModels = codexCliCatalog();
|
|
279
|
-
if (cliModels)
|
|
280
|
-
return { models: cliModels, source: 'codex-cli' };
|
|
281
|
-
}
|
|
282
|
-
const { baseUrl, apiKey, headers, queryParams } = resolveCreds(self, baseagent);
|
|
283
|
-
if (baseUrl) {
|
|
284
|
-
try {
|
|
285
|
-
const urls = [...new Set([
|
|
286
|
-
appendV1RequestPath(baseUrl, 'models', queryParams),
|
|
287
|
-
appendRequestPath(baseUrl, 'models', queryParams),
|
|
288
|
-
])];
|
|
289
|
-
// Probe compatible routes concurrently so a dead route does not add
|
|
290
|
-
// another full timeout before the other route is attempted.
|
|
291
|
-
const results = await Promise.all(urls.map(url => fetchModelList(url, apiKey, headers)));
|
|
292
|
-
const hitIndex = results.findIndex((models) => !!models);
|
|
293
|
-
if (hitIndex >= 0) {
|
|
294
|
-
return {
|
|
295
|
-
models: withAliases(results[hitIndex], baseagent),
|
|
296
|
-
source: hitIndex === 0 ? 'v1/models' : 'models',
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
catch {
|
|
301
|
-
// Invalid configured URLs behave like other unavailable gateways.
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
// This remote directory is Claude-only. Never use it for Codex or Gemini.
|
|
305
|
-
if (baseagent === 'claude') {
|
|
306
|
-
const remote = await fetchModelList(REMOTE_CATALOG_URL, undefined, buildModelRequestHeaders({
|
|
307
|
-
baseagent: 'claude',
|
|
308
|
-
baseUrl: REMOTE_CATALOG_URL,
|
|
309
|
-
}));
|
|
310
|
-
if (remote)
|
|
311
|
-
return { models: withAliases(remote, baseagent), source: 'remote' };
|
|
312
|
-
}
|
|
313
|
-
return { models: withAliases(builtinCatalog(baseagent), baseagent), source: 'builtin' };
|
|
314
|
-
}
|
|
315
|
-
/** claude 系判定:完整 ID 或别名(opus/sonnet/haiku)。 */
|
|
316
|
-
function isClaudeFamily(id) {
|
|
317
|
-
return /^claude-/.test(id) || ['opus', 'sonnet', 'haiku'].includes(id);
|
|
318
|
-
}
|
|
319
|
-
/** 单模型详情(现阶段 mock)。 */
|
|
320
141
|
export async function getModelInfo(modelId, self, ba) {
|
|
321
|
-
const
|
|
322
|
-
const
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const owner = claudeFamily
|
|
329
|
-
? 'anthropic'
|
|
330
|
-
: (rawOwner && !NON_VENDOR.has(rawOwner) ? rawOwner : inferOwner(modelId));
|
|
331
|
-
return {
|
|
332
|
-
id: modelId,
|
|
333
|
-
owned_by: owner,
|
|
334
|
-
context_window: claudeFamily ? 200000 : 128000,
|
|
335
|
-
max_output_tokens: 8192,
|
|
336
|
-
pricing: { input_per_mtok: null, output_per_mtok: null, currency: 'USD' },
|
|
337
|
-
modalities: ['text'],
|
|
338
|
-
supports_effort: claudeFamily,
|
|
339
|
-
status: entry ? 'available' : 'unknown',
|
|
340
|
-
mocked: true,
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
function inferOwner(id) {
|
|
344
|
-
if (/^claude-/.test(id))
|
|
345
|
-
return 'anthropic';
|
|
346
|
-
if (/^gpt-/.test(id))
|
|
347
|
-
return 'openai';
|
|
348
|
-
if (/^gemini-/.test(id))
|
|
349
|
-
return 'google';
|
|
350
|
-
if (/^deepseek-/.test(id))
|
|
351
|
-
return 'deepseek';
|
|
352
|
-
if (/^kimi-/.test(id))
|
|
353
|
-
return 'moonshot';
|
|
354
|
-
if (/^glm-/.test(id))
|
|
355
|
-
return 'zhipu';
|
|
356
|
-
if (/^MiniMax-/i.test(id))
|
|
357
|
-
return 'minimax';
|
|
358
|
-
return 'unknown';
|
|
142
|
+
const baseagent = ba || activeBaseagent(self);
|
|
143
|
+
const catalog = await getCatalog(self, baseagent);
|
|
144
|
+
const entry = catalog.models.find(m => m.id === resolveCatalogModel(catalog, modelId));
|
|
145
|
+
return { id: modelId, owned_by: entry?.owned_by || 'unknown',
|
|
146
|
+
context_window: entry?.contextWindow ?? null, max_output_tokens: entry?.maxOutputTokens ?? null,
|
|
147
|
+
pricing: null, modalities: [], supports_effort: catalogEfforts(catalog, modelId, baseagent).length > 0,
|
|
148
|
+
status: entry ? entry.verification : 'not-listed', mocked: false };
|
|
359
149
|
}
|
|
@@ -152,8 +152,8 @@ export async function runModelCheck(input) {
|
|
|
152
152
|
}
|
|
153
153
|
const catalog = await getCatalog(input.self, input.baseagent);
|
|
154
154
|
const modelIds = catalog.models.filter(model => model.owned_by !== 'alias').map(model => model.id);
|
|
155
|
-
log(catalog.
|
|
156
|
-
? { label: '模型列表', ok: false, detail:
|
|
155
|
+
log(catalog.freshness !== 'fresh'
|
|
156
|
+
? { label: '模型列表', ok: false, detail: `目录已降级,来源 ${catalog.source}(${modelIds.length} 个)` }
|
|
157
157
|
: { label: '模型列表', ok: true, detail: `来源: ${catalog.source},共 ${modelIds.length} 个模型` });
|
|
158
158
|
const selector = { self: input.self, peerKey: input.peerKey };
|
|
159
159
|
const modelResolution = resolveEffectiveFieldWithSource(`baseagents.${input.baseagent}.model`, selector, { cache: true });
|
|
@@ -176,9 +176,8 @@ export async function runModelCheck(input) {
|
|
|
176
176
|
for (const entry of catalog.models) {
|
|
177
177
|
if (entry.owned_by !== 'alias')
|
|
178
178
|
continue;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
aliases.set(entry.id, matched.id);
|
|
179
|
+
if (entry.aliasFor)
|
|
180
|
+
aliases.set(entry.id, entry.aliasFor);
|
|
182
181
|
}
|
|
183
182
|
const resolvedConfiguredModel = configuredModel ? aliases.get(configuredModel) ?? configuredModel : undefined;
|
|
184
183
|
const targets = [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { catalogEfforts, getCatalog, resolveCatalogModel } from './model-catalog.js';
|
|
2
2
|
import { ConfigTarget, read } from '../../config/config-manager.js';
|
|
3
3
|
export const MAX_FALLBACK_MODELS = 3;
|
|
4
4
|
function isRecord(value) {
|
|
@@ -40,27 +40,20 @@ export function configuredFallbackModels(baseagent) {
|
|
|
40
40
|
}
|
|
41
41
|
return { candidates, rejected };
|
|
42
42
|
}
|
|
43
|
-
function
|
|
44
|
-
if (!effort)
|
|
45
|
-
return true;
|
|
46
|
-
if (baseagent === 'codex')
|
|
47
|
-
return getCodexEfforts(model).includes(effort);
|
|
48
|
-
if (baseagent === 'claude')
|
|
49
|
-
return ['low', 'medium', 'high', 'xhigh', 'max'].includes(effort);
|
|
50
|
-
if (baseagent === 'ecagent')
|
|
51
|
-
return ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'].includes(effort);
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
export function eligibleFallbackModels(input) {
|
|
43
|
+
export async function eligibleFallbackModels(input) {
|
|
55
44
|
const configured = configuredFallbackModels(input.baseagent);
|
|
56
45
|
const candidates = [];
|
|
57
46
|
const rejected = [...configured.rejected];
|
|
58
|
-
|
|
47
|
+
if (!configured.candidates.length)
|
|
48
|
+
return { candidates, rejected };
|
|
49
|
+
const catalog = await (input.catalog?.() ?? getCatalog(input.selfAid, input.baseagent));
|
|
50
|
+
const resolve = (model) => resolveCatalogModel(catalog, model);
|
|
51
|
+
const key = (model) => resolve(model).trim();
|
|
59
52
|
const seen = new Set();
|
|
60
53
|
if (input.currentModel)
|
|
61
54
|
seen.add(key(input.currentModel));
|
|
62
55
|
for (const candidate of configured.candidates) {
|
|
63
|
-
const resolvedModel =
|
|
56
|
+
const resolvedModel = resolve(candidate.model);
|
|
64
57
|
const candidateKey = key(candidate.model);
|
|
65
58
|
if (seen.has(candidateKey)) {
|
|
66
59
|
rejected.push({ model: candidate.model, reason: candidateKey === key(input.currentModel ?? '') ? 'current_model' : 'duplicate' });
|
|
@@ -70,7 +63,7 @@ export function eligibleFallbackModels(input) {
|
|
|
70
63
|
rejected.push({ model: candidate.model, reason: 'not_allowed' });
|
|
71
64
|
continue;
|
|
72
65
|
}
|
|
73
|
-
if (!
|
|
66
|
+
if (candidate.effort && !catalogEfforts(catalog, resolvedModel, input.baseagent).includes(candidate.effort)) {
|
|
74
67
|
rejected.push({ model: candidate.model, reason: 'unsupported_effort' });
|
|
75
68
|
continue;
|
|
76
69
|
}
|
|
@@ -49,7 +49,7 @@ export function validateModelSelectionForRole(opts) {
|
|
|
49
49
|
: opts.models?.includes(opts.requestedModel)
|
|
50
50
|
? opts.requestedModel
|
|
51
51
|
: undefined;
|
|
52
|
-
if (opts.models &&
|
|
52
|
+
if (opts.models && !catalogModel) {
|
|
53
53
|
return {
|
|
54
54
|
ok: false,
|
|
55
55
|
code: 'INVALID_VALUE',
|