@starlink-awaken/agentmesh 1.2.6 → 1.3.0

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.
@@ -1,268 +1,230 @@
1
- port: 3000
2
- wsPort: 3001
3
- host: "0.0.0.0"
4
- dataDir: "./data"
5
- logDir: "./logs"
6
-
7
- routing:
8
- defaultAgent: "claude-code"
9
-
10
- rules:
11
- # Claude Code - 通用代码任务
12
- - name: code-generation
13
- keywords:
14
- - write code
15
- - generate code
16
- - create function
17
- - create class
18
- - 写代码
19
- - 生成代码
20
- agent: claude-code
21
- priority: 10
22
-
23
- - name: code-review
24
- keywords:
25
- - review
26
- - code review
27
- - pr review
28
- - review code
29
- - 代码审查
30
- - review code
31
- agent: claude-code
32
- priority: 15
33
-
34
- - name: debugging
35
- keywords:
36
- - debug
37
- - fix bug
38
- - error
39
- - bug
40
- - 调试
41
- - 修复错误
42
- agent: claude-code
43
- priority: 15
44
-
45
- - name: refactoring
46
- keywords:
47
- - refactor
48
- - 重构
49
- - improve code
50
- agent: claude-code
51
- priority: 10
52
-
53
- - name: documentation
54
- keywords:
55
- - docs
56
- - document
57
- - 文档
58
- - write docs
59
- agent: claude-code
60
- priority: 10
61
-
62
- # OpenClaw - 浏览器自动化
63
- - name: browser-automation
64
- keywords:
65
- - browser
66
- - scrape
67
- - click
68
- - screenshot
69
- - web automation
70
- - 浏览器
71
- - 爬虫
72
- - 截图
73
- agent: openclaw
74
- priority: 15
75
-
76
- - name: web-scraping
77
- keywords:
78
- - scrap
79
- - crawl
80
- - extract data
81
- - 抓取
82
- - 采集
83
- agent: openclaw
84
- priority: 15
85
-
86
- # Cursor - AI 编程
87
- - name: cursor-task
88
- keywords:
89
- - cursor
90
- - cursor task
91
- agent: cursor
92
- priority: 12
93
-
94
- # Windsurf - Flow 状态编程
95
- - name: windsurf-task
96
- keywords:
97
- - windsurf
98
- - flow state
99
- agent: windsurf
100
- priority: 12
101
-
102
- # Qwen Code - 中文编程
103
- - name: qwen-task
104
- keywords:
105
- - qwen
106
- - 通义千问
107
- agent: qwen-code
108
- priority: 12
109
-
110
- # Gemini - 多模态
111
- - name: gemini-task
112
- keywords:
113
- - gemini
114
- - multimodal
115
- - 多模态
116
- agent: gemini
117
- priority: 12
118
-
119
- # Droid - Android 开发
120
- - name: android-development
121
- keywords:
122
- - android
123
- - apk
124
- - mobile
125
- - 安卓
126
- - 手机应用
127
- agent: droid
128
- priority: 15
129
-
130
- # Aider - Git 集成编辑
131
- - name: aider-task
132
- keywords:
133
- - aider
134
- - git edit
135
- - refactor git
136
- agent: aider
137
- priority: 12
138
-
139
- # Cline - 自主编程
140
- - name: cline-task
141
- keywords:
142
- - cline
143
- - autonomous
144
- agent: cline
145
- priority: 12
146
-
147
- # Roo Code
148
- - name: roo-code-task
149
- keywords:
150
- - roo-code
151
- - roo
152
- agent: roo-code
153
- priority: 12
154
-
155
- # 多 Agent 协作
156
- - name: multi-agent
157
- keywords:
158
- - collaborate
159
- - team
160
- - together
161
- - multiple agents
162
- - 协作
163
- - 多个
164
- strategy: broadcast
165
- agents:
166
- - claude-code
167
- - openclaw
168
- priority: 20
169
-
170
- # =============================================================================
171
- # 模型网关配置 — 多 Provider 路由 + 配额感知 Fallback
172
- # =============================================================================
1
+ agents:
2
+ - capabilities:
3
+ - code-generation
4
+ - code-review
5
+ - debugging
6
+ - refactoring
7
+ - documentation
8
+ - file-operations
9
+ id: claude-code
10
+ name: Claude Code
11
+ type: claude-code
12
+ - capabilities:
13
+ - browser-automation
14
+ - web-scraping
15
+ - form-filling
16
+ - ui-testing
17
+ id: openclaw
18
+ name: OpenClaw
19
+ type: openclaw
20
+ dataDir: ./data
21
+ host: 0.0.0.0
22
+ logDir: ./logs
173
23
  models:
174
24
  default_model: deepseek-chat
175
-
176
- # 默认配置(各 Provider 可覆盖)
177
25
  defaults:
178
26
  circuit_breaker:
179
27
  failure_threshold: 3
180
- reset_timeout_ms: 30000
181
28
  half_open_max_requests: 1
29
+ reset_timeout_ms: 30000
182
30
  retry:
183
- max_retries: 3
184
31
  base_delay_ms: 500
185
32
  max_delay_ms: 10000
186
- retryable_statuses: [429, 500, 502, 503, 504]
187
-
33
+ max_retries: 3
34
+ retryable_statuses:
35
+ - 429
36
+ - 500
37
+ - 502
38
+ - 503
39
+ - 504
40
+ fallback_chain:
41
+ - deepseek
42
+ - openrouter
43
+ - ollama
44
+ model_routing:
45
+ claude:
46
+ - openrouter
47
+ codestral:
48
+ - ollama
49
+ deepseek:
50
+ - deepseek
51
+ gemini:
52
+ - openrouter
53
+ gpt-:
54
+ - openai
55
+ - deepseek
56
+ gpt-5.3-codex:
57
+ - deepseek
58
+ llama:
59
+ - ollama
60
+ o1:
61
+ - openai
62
+ - deepseek
63
+ o4:
64
+ - openai
65
+ - deepseek
66
+ qwen:
67
+ - ollama
188
68
  providers:
189
69
  deepseek:
190
- base_url: https://api.deepseek.com/v1
191
70
  api_key_env: DEEPSEEK_API_KEY
71
+ base_url: https://api.deepseek.com/v1
72
+ models:
73
+ - deepseek-chat
74
+ - deepseek-reasoner
75
+ - deepseek-v4-pro
76
+ - deepseek-v4-flash
77
+ ollama:
78
+ api_key: ollama
79
+ base_url: http://127.0.0.1:11434/v1
192
80
  models:
193
- - deepseek-chat
194
- - deepseek-reasoner
195
- - deepseek-v4-pro
196
- - deepseek-v4-flash
81
+ - qwen3:14b
82
+ - codestral:22b
83
+ - llama3.1:8b
197
84
  openai:
198
- base_url: https://api.openai.com/v1
199
85
  api_key_env: OPENAI_API_KEY
86
+ base_url: https://api.openai.com/v1
200
87
  models:
201
- - gpt-5.1
202
- - gpt-5.1-codex
203
- - o4-mini
88
+ - gpt-5.1
89
+ - gpt-5.1-codex
90
+ - o4-mini
204
91
  openrouter:
205
- base_url: https://openrouter.ai/api/v1
206
92
  api_key_env: OPENROUTER_API_KEY
207
- ollama:
208
- base_url: http://127.0.0.1:11434/v1
209
- api_key: ollama
210
- models:
211
- - qwen3:14b
212
- - codestral:22b
213
- - llama3.1:8b
214
-
215
- # Fallback 链:主 Provider 不可用时依次尝试
216
- fallback_chain:
217
- - deepseek
218
- - openrouter
219
- - ollama
220
-
221
- # 模型名 → 优先 Provider 列表
222
- model_routing:
223
- "deepseek":
224
- - deepseek
225
- "gpt-":
226
- - openai
227
- - deepseek
228
- "o1":
229
- - openai
230
- - deepseek
231
- "o4":
232
- - openai
233
- - deepseek
234
- "claude":
235
- - openrouter
236
- "gemini":
237
- - openrouter
238
- "qwen":
239
- - ollama
240
- "codestral":
241
- - ollama
242
- "llama":
243
- - ollama
244
-
245
- # =============================================================================
246
- # Agent 配置
247
- # =============================================================================
248
- agents:
249
- # 内置 Agent(在代码中注册)
250
- - id: claude-code
251
- name: Claude Code
252
- type: claude-code
253
- capabilities:
254
- - code-generation
255
- - code-review
256
- - debugging
257
- - refactoring
258
- - documentation
259
- - file-operations
260
-
261
- - id: openclaw
262
- name: OpenClaw
263
- type: openclaw
264
- capabilities:
265
- - browser-automation
266
- - web-scraping
267
- - form-filling
268
- - ui-testing
93
+ base_url: https://openrouter.ai/api/v1
94
+ port: 3000
95
+ routing:
96
+ defaultAgent: claude-code
97
+ rules:
98
+ - agent: claude-code
99
+ keywords:
100
+ - write code
101
+ - generate code
102
+ - create function
103
+ - create class
104
+ - 写代码
105
+ - 生成代码
106
+ name: code-generation
107
+ priority: 10
108
+ - agent: claude-code
109
+ keywords:
110
+ - review
111
+ - code review
112
+ - pr review
113
+ - review code
114
+ - 代码审查
115
+ - review code
116
+ name: code-review
117
+ priority: 15
118
+ - agent: claude-code
119
+ keywords:
120
+ - debug
121
+ - fix bug
122
+ - error
123
+ - bug
124
+ - 调试
125
+ - 修复错误
126
+ name: debugging
127
+ priority: 15
128
+ - agent: claude-code
129
+ keywords:
130
+ - refactor
131
+ - 重构
132
+ - improve code
133
+ name: refactoring
134
+ priority: 10
135
+ - agent: claude-code
136
+ keywords:
137
+ - docs
138
+ - document
139
+ - 文档
140
+ - write docs
141
+ name: documentation
142
+ priority: 10
143
+ - agent: openclaw
144
+ keywords:
145
+ - browser
146
+ - scrape
147
+ - click
148
+ - screenshot
149
+ - web automation
150
+ - 浏览器
151
+ - 爬虫
152
+ - 截图
153
+ name: browser-automation
154
+ priority: 15
155
+ - agent: openclaw
156
+ keywords:
157
+ - scrap
158
+ - crawl
159
+ - extract data
160
+ - 抓取
161
+ - 采集
162
+ name: web-scraping
163
+ priority: 15
164
+ - agent: cursor
165
+ keywords:
166
+ - cursor
167
+ - cursor task
168
+ name: cursor-task
169
+ priority: 12
170
+ - agent: windsurf
171
+ keywords:
172
+ - windsurf
173
+ - flow state
174
+ name: windsurf-task
175
+ priority: 12
176
+ - agent: qwen-code
177
+ keywords:
178
+ - qwen
179
+ - 通义千问
180
+ name: qwen-task
181
+ priority: 12
182
+ - agent: gemini
183
+ keywords:
184
+ - gemini
185
+ - multimodal
186
+ - 多模态
187
+ name: gemini-task
188
+ priority: 12
189
+ - agent: droid
190
+ keywords:
191
+ - android
192
+ - apk
193
+ - mobile
194
+ - 安卓
195
+ - 手机应用
196
+ name: android-development
197
+ priority: 15
198
+ - agent: aider
199
+ keywords:
200
+ - aider
201
+ - git edit
202
+ - refactor git
203
+ name: aider-task
204
+ priority: 12
205
+ - agent: cline
206
+ keywords:
207
+ - cline
208
+ - autonomous
209
+ name: cline-task
210
+ priority: 12
211
+ - agent: roo-code
212
+ keywords:
213
+ - roo-code
214
+ - roo
215
+ name: roo-code-task
216
+ priority: 12
217
+ - agents:
218
+ - claude-code
219
+ - openclaw
220
+ keywords:
221
+ - collaborate
222
+ - team
223
+ - together
224
+ - multiple agents
225
+ - 协作
226
+ - 多个
227
+ name: multi-agent
228
+ priority: 20
229
+ strategy: broadcast
230
+ wsPort: 3001
@@ -85,9 +85,11 @@ const codexDesktopAdapter = {
85
85
  },
86
86
  generateConfig(gwUrl) {
87
87
  const path = this.getConfigPath();
88
+ const catalogPath = join(HOME, '.codex', 'model-catalogs', 'agentmesh-models.json');
88
89
  const section = {
89
90
  model: 'deepseek-v4-pro',
90
91
  model_provider: 'agentmesh',
92
+ model_catalog_json: catalogPath,
91
93
  };
92
94
  const providerSection = {
93
95
  name: 'Agent Mesh Gateway',
@@ -98,7 +100,12 @@ const codexDesktopAdapter = {
98
100
  return {
99
101
  path,
100
102
  format: 'toml',
101
- content: { model: section.model, model_provider: section.model_provider, model_providers: { agentmesh: providerSection } },
103
+ content: {
104
+ model: section.model,
105
+ model_provider: section.model_provider,
106
+ model_catalog_json: section.model_catalog_json,
107
+ model_providers: { agentmesh: providerSection },
108
+ },
102
109
  };
103
110
  },
104
111
  hasGatewayConfig(config) {
@@ -300,6 +307,30 @@ export async function connectTools(targetTools, opts = {}) {
300
307
  break;
301
308
  }
302
309
  }
310
+ // Codex Desktop: 注入模型到 models_cache.json
311
+ if (adapter.name === 'codex-desktop' && !opts.dryRun) {
312
+ try {
313
+ const cachePath = join(HOME, '.codex', 'models_cache.json');
314
+ if (existsSync(cachePath)) {
315
+ const cache = JSON.parse(readFileSync(cachePath, 'utf-8'));
316
+ const slugs = new Set(cache.models?.map((m) => m.slug) || []);
317
+ const newModels = [
318
+ { slug: 'deepseek-v4-pro', display_name: 'DeepSeek V4 Pro', description: 'DeepSeek V4 Pro via Agent Mesh — 强推理,代码生成', default_reasoning_level: 'high', supported_reasoning_levels: [{ effort: 'low', description: '快速响应' }, { effort: 'medium', description: '平衡速度与推理' }, { effort: 'high', description: '深度推理' }], visibility: 'list', supported_in_api: true, priority: 10, service_tiers: [], additional_speed_tiers: [] },
319
+ { slug: 'deepseek-v4-flash', display_name: 'DeepSeek V4 Flash', description: 'DeepSeek V4 Flash via Agent Mesh — 快速、便宜', default_reasoning_level: 'low', supported_reasoning_levels: [{ effort: 'low', description: '快速响应' }], visibility: 'list', supported_in_api: true, priority: 20, service_tiers: [], additional_speed_tiers: [] },
320
+ ];
321
+ let added = 0;
322
+ for (const m of newModels) {
323
+ if (!slugs.has(m.slug)) {
324
+ cache.models.push(m);
325
+ added++;
326
+ }
327
+ }
328
+ if (added > 0)
329
+ writeFileSync(cachePath, JSON.stringify(cache, null, 2) + '\n');
330
+ }
331
+ }
332
+ catch { }
333
+ }
303
334
  results.push({
304
335
  tool: adapter.name,
305
336
  status: 'ok',
package/dist/src/cli.js CHANGED
@@ -7,7 +7,7 @@ import { existsSync, readFileSync } from 'node:fs';
7
7
  import { resolve, dirname, join } from 'node:path';
8
8
  import { initLogger } from './core/logger.js';
9
9
  const PROJECT_ROOT = resolve(dirname(import.meta.dir), '..');
10
- const VERSION = '1.2.6';
10
+ const VERSION = '1.3.0';
11
11
  const BANNER = `
12
12
  █████╗ ██████╗ ███████╗███╗ ██╗████████╗
13
13
  ██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝
@@ -250,6 +250,42 @@ async function cmdConfig(args) {
250
250
  }
251
251
  }
252
252
  }
253
+ async function cmdStatus() {
254
+ try {
255
+ const [health, models, stats] = await Promise.all([
256
+ apiRequest('/health'),
257
+ apiRequest('/v1/models').catch(() => ({ data: [] })),
258
+ apiRequest('/model-gateway/stats').catch(() => null),
259
+ ]);
260
+ console.log(`
261
+ ╔═══════════════════════════════════════════════════╗
262
+ ║ Agent Mesh Gateway Status ║
263
+ ╠═══════════════════════════════════════════════════╣
264
+ ║ Status: ${health.status} ║
265
+ ║ Agents: ${String(health.agents?.length || 0).padStart(2)} online ║
266
+ ║ Models: ${String(models.data?.length || 0).padStart(2)} available ║
267
+ ║ Uptime: ${stats?.uptime_seconds ? Math.floor(stats.uptime_seconds) + 's' : 'N/A'} ║
268
+ ╠═══════════════════════════════════════════════════╣`);
269
+ if (stats?.providers) {
270
+ console.log('║ Provider Metrics: ║');
271
+ for (const [name, m] of Object.entries(stats.providers)) {
272
+ console.log(`║ ${name.padEnd(12)} reqs:${String(m.requests).padStart(5)} ok:${(m.success_rate || 'N/A').padStart(6)} avg:${String(m.avg_latency_ms || 0).padStart(4)}ms ║`);
273
+ }
274
+ }
275
+ if (stats?.recent?.length) {
276
+ console.log('╠═══════════════════════════════════════════════════╣');
277
+ console.log('║ Recent: ║');
278
+ for (const r of stats.recent.slice(0, 5)) {
279
+ const time = new Date(r.time).toLocaleTimeString();
280
+ console.log(`║ ${r.status >= 400 ? '❌' : '✅'} ${time} ${r.model} → ${r.actual} ${r.latency_ms}ms ║`);
281
+ }
282
+ }
283
+ console.log('╚═══════════════════════════════════════════════════╝\n');
284
+ }
285
+ catch {
286
+ console.error('\n ❌ Gateway not reachable. Start: agentmesh start\n');
287
+ }
288
+ }
253
289
  async function cmdDoctor() {
254
290
  console.log('\n 🔍 Agent Mesh Gateway Diagnostics\n');
255
291
  const checks = [];
@@ -326,9 +362,12 @@ async function main() {
326
362
  await runSetup();
327
363
  break;
328
364
  case 'health':
329
- case 'status':
330
365
  await cmdHealth();
331
366
  break;
367
+ case 'status':
368
+ case 'info':
369
+ await cmdStatus();
370
+ break;
332
371
  case 'models':
333
372
  case 'model':
334
373
  await cmdModels();
@@ -0,0 +1,28 @@
1
+ interface RequestLog {
2
+ timestamp: number;
3
+ model: string;
4
+ provider: string;
5
+ actualModel: string;
6
+ latencyMs: number;
7
+ status: number;
8
+ error?: string;
9
+ streaming: boolean;
10
+ }
11
+ export declare function recordRequest(log: RequestLog): void;
12
+ export declare function getMetrics(): {
13
+ uptime_seconds: number;
14
+ total_requests: number;
15
+ total_failures: number;
16
+ providers: Record<string, any>;
17
+ recent: {
18
+ time: string;
19
+ model: string;
20
+ provider: string;
21
+ actual: string;
22
+ latency_ms: number;
23
+ status: number;
24
+ streaming: boolean;
25
+ error: string | undefined;
26
+ }[];
27
+ };
28
+ export {};
@@ -0,0 +1,60 @@
1
+ // 网关运行时指标收集
2
+ const providerMetrics = new Map();
3
+ const recentRequests = [];
4
+ const MAX_RECENT = 200;
5
+ function getOrInitProvider(name) {
6
+ if (!providerMetrics.has(name)) {
7
+ providerMetrics.set(name, { requests: 0, success: 0, failures: 0, totalLatencyMs: 0 });
8
+ }
9
+ return providerMetrics.get(name);
10
+ }
11
+ export function recordRequest(log) {
12
+ const m = getOrInitProvider(log.provider);
13
+ m.requests++;
14
+ m.totalLatencyMs += log.latencyMs;
15
+ if (log.status >= 200 && log.status < 400) {
16
+ m.success++;
17
+ m.lastSuccessTime = log.timestamp;
18
+ }
19
+ else {
20
+ m.failures++;
21
+ m.lastError = log.error;
22
+ m.lastErrorTime = log.timestamp;
23
+ }
24
+ recentRequests.unshift(log);
25
+ if (recentRequests.length > MAX_RECENT)
26
+ recentRequests.pop();
27
+ }
28
+ export function getMetrics() {
29
+ const providers = {};
30
+ let totalRequests = 0;
31
+ let totalFailures = 0;
32
+ for (const [name, m] of providerMetrics) {
33
+ totalRequests += m.requests;
34
+ totalFailures += m.failures;
35
+ providers[name] = {
36
+ requests: m.requests,
37
+ success_rate: m.requests > 0 ? ((m.success / m.requests) * 100).toFixed(1) + '%' : 'N/A',
38
+ avg_latency_ms: m.requests > 0 ? Math.round(m.totalLatencyMs / m.requests) : 0,
39
+ last_success: m.lastSuccessTime ? new Date(m.lastSuccessTime).toISOString() : null,
40
+ last_error: m.lastError || null,
41
+ last_error_time: m.lastErrorTime ? new Date(m.lastErrorTime).toISOString() : null,
42
+ };
43
+ }
44
+ return {
45
+ uptime_seconds: Math.round(process.uptime()),
46
+ total_requests: totalRequests,
47
+ total_failures: totalFailures,
48
+ providers,
49
+ recent: recentRequests.slice(0, 20).map(r => ({
50
+ time: new Date(r.timestamp).toISOString(),
51
+ model: r.model,
52
+ provider: r.provider,
53
+ actual: r.actualModel,
54
+ latency_ms: r.latencyMs,
55
+ status: r.status,
56
+ streaming: r.streaming,
57
+ error: r.error,
58
+ })),
59
+ };
60
+ }
@@ -2,3 +2,4 @@ import type { ModelGatewayConfig, ResolvedProvider } from './types.js';
2
2
  export declare function initModelRouter(cfg: ModelGatewayConfig): void;
3
3
  export declare function getConfig(): ModelGatewayConfig;
4
4
  export declare function resolveProvider(model: string): ResolvedProvider | null;
5
+ export declare function remapModel(model: string, providerName: string): string;
@@ -1,5 +1,15 @@
1
1
  import { isProviderAvailable } from './quota.js';
2
2
  import { circuitBreakerRegistry } from './circuit-breaker.js';
3
+ // 模型名重映射:对外模型名 → 实际 Provider 的模型名
4
+ const MODEL_ALIASES = {
5
+ deepseek: {
6
+ 'gpt-5.3-codex': 'deepseek-v4-pro',
7
+ 'gpt-5.4': 'deepseek-v4-pro',
8
+ 'gpt-5.5': 'deepseek-v4-pro',
9
+ 'o4-mini': 'deepseek-v4-flash',
10
+ 'claude-sonnet-4-6': 'deepseek-v4-pro',
11
+ },
12
+ };
3
13
  let config;
4
14
  export function initModelRouter(cfg) {
5
15
  config = cfg;
@@ -68,6 +78,9 @@ export function resolveProvider(model) {
68
78
  }
69
79
  return null;
70
80
  }
81
+ export function remapModel(model, providerName) {
82
+ return MODEL_ALIASES[providerName]?.[model] || model;
83
+ }
71
84
  function resolveApiKey(_name, providerCfg) {
72
85
  if (providerCfg.api_key && providerCfg.api_key !== '') {
73
86
  return providerCfg.api_key;
@@ -1,8 +1,9 @@
1
- import { resolveProvider, getConfig } from './router.js';
1
+ import { resolveProvider, getConfig, remapModel } from './router.js';
2
2
  import { callChatCompletions, callResponsesApi } from './providers.js';
3
3
  import { getQuotaSummary, probeQuota } from './quota.js';
4
4
  import { circuitBreakerRegistry } from './circuit-breaker.js';
5
5
  import { checkAllProviders } from './health.js';
6
+ import { getMetrics, recordRequest } from './metrics.js';
6
7
  export async function modelGatewayRoutes(fastify) {
7
8
  // 健康检查 + 配额总览
8
9
  fastify.get('/model-gateway/health', async (_req, _reply) => {
@@ -44,14 +45,16 @@ export async function modelGatewayRoutes(fastify) {
44
45
  if (!body || !body.messages) {
45
46
  return reply.code(400).send({ error: { message: 'messages is required' } });
46
47
  }
47
- const model = body.model || 'deepseek-chat';
48
- const provider = resolveProvider(model);
48
+ const originalModel = body.model || 'deepseek-chat';
49
+ const provider = resolveProvider(originalModel);
49
50
  if (!provider) {
50
51
  return reply.code(503).send({
51
52
  error: { message: 'No available provider. Check API keys and quota.' },
52
53
  });
53
54
  }
54
- console.log(`[ModelGW] ${model} ${provider.name} (${body.stream ? 'stream' : 'sync'})`);
55
+ const model = remapModel(originalModel, provider.name);
56
+ const reqStart = Date.now();
57
+ console.log(`[ModelGW] ${originalModel} → ${provider.name}/${model} (${body.stream ? 'stream' : 'sync'})`);
55
58
  try {
56
59
  const upstreamResp = await callChatCompletions(provider, {
57
60
  model,
@@ -64,12 +67,14 @@ export async function modelGatewayRoutes(fastify) {
64
67
  });
65
68
  if (!upstreamResp.ok && upstreamResp.status !== 200) {
66
69
  const errText = await upstreamResp.text();
70
+ recordRequest({ timestamp: Date.now(), model: originalModel, provider: provider.name, actualModel: model, latencyMs: Date.now() - reqStart, status: upstreamResp.status, error: errText.slice(0, 200), streaming: !!body.stream });
67
71
  console.error(`[ModelGW] ${provider.name} error ${upstreamResp.status}: ${errText.slice(0, 200)}`);
68
72
  return reply.code(upstreamResp.status).send({
69
73
  error: { message: `${provider.name}: ${errText.slice(0, 500)}` },
70
74
  });
71
75
  }
72
76
  if (body.stream) {
77
+ recordRequest({ timestamp: Date.now(), model: originalModel, provider: provider.name, actualModel: model, latencyMs: Date.now() - reqStart, status: 200, streaming: true });
73
78
  return reply.headers({
74
79
  'Content-Type': 'text/event-stream',
75
80
  'Cache-Control': 'no-cache',
@@ -77,9 +82,11 @@ export async function modelGatewayRoutes(fastify) {
77
82
  }).send(upstreamResp.body);
78
83
  }
79
84
  const data = await upstreamResp.json();
85
+ recordRequest({ timestamp: Date.now(), model: originalModel, provider: provider.name, actualModel: model, latencyMs: Date.now() - reqStart, status: 200, streaming: false });
80
86
  reply.send(data);
81
87
  }
82
88
  catch (err) {
89
+ recordRequest({ timestamp: Date.now(), model: originalModel, provider: provider.name, actualModel: model, latencyMs: Date.now() - reqStart, status: 502, error: err.message, streaming: !!body.stream });
83
90
  console.error(`[ModelGW] Error calling ${provider.name}:`, err.message);
84
91
  reply.code(502).send({
85
92
  error: { message: `Provider error: ${err.message}` },
@@ -92,14 +99,16 @@ export async function modelGatewayRoutes(fastify) {
92
99
  if (!body || !body.input) {
93
100
  return reply.code(400).send({ error: { message: 'input is required' } });
94
101
  }
95
- const model = body.model || 'deepseek-chat';
96
- const provider = resolveProvider(model);
102
+ const originalModel = body.model || 'deepseek-chat';
103
+ const provider = resolveProvider(originalModel);
97
104
  if (!provider) {
98
105
  return reply.code(503).send({
99
106
  error: { message: 'No available provider. Check API keys and quota.' },
100
107
  });
101
108
  }
102
- console.log(`[ModelGW:Responses] ${model} ${provider.name}`);
109
+ const model = remapModel(originalModel, provider.name);
110
+ body.model = model;
111
+ console.log(`[ModelGW:Responses] ${originalModel} → ${provider.name}/${model}`);
103
112
  try {
104
113
  const upstreamResp = await callResponsesApi(provider, body);
105
114
  if (!upstreamResp.ok) {
@@ -144,6 +153,10 @@ export async function modelGatewayRoutes(fastify) {
144
153
  });
145
154
  }
146
155
  });
156
+ // 运行时统计
157
+ fastify.get('/model-gateway/stats', async (_request, reply) => {
158
+ reply.send(getMetrics());
159
+ });
147
160
  // Provider 健康检查 + 熔断器状态
148
161
  fastify.get('/model-gateway/health/:provider', async (request, reply) => {
149
162
  const { provider } = request.params;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starlink-awaken/agentmesh",
3
- "version": "1.2.6",
3
+ "version": "1.3.0",
4
4
  "description": "Unified Agent Gateway - Multi-Agent Scheduler and Router",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",