@starlink-awaken/agentmesh 1.0.3 → 1.2.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +60 -41
  2. package/README.zh-CN.md +137 -167
  3. package/config/gateway.yaml +78 -0
  4. package/dist/src/cli/connect.d.ts +26 -0
  5. package/dist/src/cli/connect.js +544 -0
  6. package/dist/src/cli/setup.d.ts +2 -0
  7. package/dist/src/cli/setup.js +97 -0
  8. package/dist/src/cli.js +410 -0
  9. package/dist/src/core/logger.d.ts +14 -0
  10. package/dist/src/core/logger.js +57 -0
  11. package/dist/{index.js → src/index.js} +40 -8
  12. package/dist/src/model-gateway/circuit-breaker.d.ts +21 -0
  13. package/dist/src/model-gateway/circuit-breaker.js +86 -0
  14. package/dist/src/model-gateway/health.d.ts +12 -0
  15. package/dist/src/model-gateway/health.js +80 -0
  16. package/dist/src/model-gateway/providers.d.ts +4 -0
  17. package/dist/src/model-gateway/providers.js +113 -0
  18. package/dist/src/model-gateway/quota.d.ts +4 -0
  19. package/dist/src/model-gateway/quota.js +107 -0
  20. package/dist/src/model-gateway/rate-limit.d.ts +12 -0
  21. package/dist/src/model-gateway/rate-limit.js +51 -0
  22. package/dist/src/model-gateway/retry.d.ts +14 -0
  23. package/dist/src/model-gateway/retry.js +48 -0
  24. package/dist/src/model-gateway/router.d.ts +4 -0
  25. package/dist/src/model-gateway/router.js +79 -0
  26. package/dist/src/model-gateway/routes.d.ts +2 -0
  27. package/dist/src/model-gateway/routes.js +172 -0
  28. package/dist/src/model-gateway/types.d.ts +47 -0
  29. package/dist/src/model-gateway/types.js +1 -0
  30. package/dist/tests/core/context-manager.test.d.ts +1 -0
  31. package/dist/tests/core/context-manager.test.js +35 -0
  32. package/dist/tests/core/router.test.d.ts +1 -0
  33. package/dist/tests/core/router.test.js +79 -0
  34. package/dist/tests/model-gateway/circuit-breaker.test.d.ts +1 -0
  35. package/dist/tests/model-gateway/circuit-breaker.test.js +84 -0
  36. package/dist/tests/model-gateway/providers.test.d.ts +1 -0
  37. package/dist/tests/model-gateway/providers.test.js +80 -0
  38. package/dist/tests/model-gateway/quota.test.d.ts +1 -0
  39. package/dist/tests/model-gateway/quota.test.js +60 -0
  40. package/dist/tests/model-gateway/rate-limit.test.d.ts +1 -0
  41. package/dist/tests/model-gateway/rate-limit.test.js +42 -0
  42. package/dist/tests/model-gateway/retry.test.d.ts +1 -0
  43. package/dist/tests/model-gateway/retry.test.js +47 -0
  44. package/dist/tests/model-gateway/router.test.d.ts +1 -0
  45. package/dist/tests/model-gateway/router.test.js +108 -0
  46. package/dist/tests/model-gateway/routes.test.d.ts +1 -0
  47. package/dist/tests/model-gateway/routes.test.js +83 -0
  48. package/docs/api.md +187 -460
  49. package/docs/architecture.md +138 -0
  50. package/docs/configuration.md +188 -0
  51. package/package.json +3 -1
  52. package/dist/cli.js +0 -246
  53. /package/dist/{adapters → src/adapters}/base.d.ts +0 -0
  54. /package/dist/{adapters → src/adapters}/base.js +0 -0
  55. /package/dist/{adapters → src/adapters}/claude-code.d.ts +0 -0
  56. /package/dist/{adapters → src/adapters}/claude-code.js +0 -0
  57. /package/dist/{adapters → src/adapters}/openclaw.d.ts +0 -0
  58. /package/dist/{adapters → src/adapters}/openclaw.js +0 -0
  59. /package/dist/{adapters → src/adapters}/process.d.ts +0 -0
  60. /package/dist/{adapters → src/adapters}/process.js +0 -0
  61. /package/dist/{cli.d.ts → src/cli.d.ts} +0 -0
  62. /package/dist/{core → src/core}/agent-registry.d.ts +0 -0
  63. /package/dist/{core → src/core}/agent-registry.js +0 -0
  64. /package/dist/{core → src/core}/config.d.ts +0 -0
  65. /package/dist/{core → src/core}/config.js +0 -0
  66. /package/dist/{core → src/core}/context-manager.d.ts +0 -0
  67. /package/dist/{core → src/core}/context-manager.js +0 -0
  68. /package/dist/{core → src/core}/event-bus.d.ts +0 -0
  69. /package/dist/{core → src/core}/event-bus.js +0 -0
  70. /package/dist/{core → src/core}/metrics.d.ts +0 -0
  71. /package/dist/{core → src/core}/metrics.js +0 -0
  72. /package/dist/{core → src/core}/router.d.ts +0 -0
  73. /package/dist/{core → src/core}/router.js +0 -0
  74. /package/dist/{core → src/core}/task-manager.d.ts +0 -0
  75. /package/dist/{core → src/core}/task-manager.js +0 -0
  76. /package/dist/{core → src/core}/vector-store.d.ts +0 -0
  77. /package/dist/{core → src/core}/vector-store.js +0 -0
  78. /package/dist/{index.d.ts → src/index.d.ts} +0 -0
  79. /package/dist/{routes → src/routes}/api.d.ts +0 -0
  80. /package/dist/{routes → src/routes}/api.js +0 -0
  81. /package/dist/{routes → src/routes}/websocket.d.ts +0 -0
  82. /package/dist/{routes → src/routes}/websocket.js +0 -0
  83. /package/dist/{types → src/types}/index.d.ts +0 -0
  84. /package/dist/{types → src/types}/index.js +0 -0
@@ -0,0 +1,47 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import { configureRetry, getRetryConfig, getRetryDelay, isRetryable, } from '../../src/model-gateway/retry.js';
3
+ describe('retry config', () => {
4
+ test('default config values', () => {
5
+ const cfg = getRetryConfig();
6
+ expect(cfg.maxRetries).toBe(3);
7
+ expect(cfg.baseDelayMs).toBe(500);
8
+ expect(cfg.maxDelayMs).toBe(10000);
9
+ });
10
+ test('retryable status codes', () => {
11
+ expect(isRetryable(429)).toBe(true);
12
+ expect(isRetryable(500)).toBe(true);
13
+ expect(isRetryable(502)).toBe(true);
14
+ expect(isRetryable(503)).toBe(true);
15
+ expect(isRetryable(504)).toBe(true);
16
+ expect(isRetryable(400)).toBe(false);
17
+ expect(isRetryable(401)).toBe(false);
18
+ expect(isRetryable(404)).toBe(false);
19
+ expect(isRetryable(200)).toBe(false);
20
+ });
21
+ test('config overrides', () => {
22
+ configureRetry({ maxRetries: 5, baseDelayMs: 1000 });
23
+ const cfg = getRetryConfig();
24
+ expect(cfg.maxRetries).toBe(5);
25
+ expect(cfg.baseDelayMs).toBe(1000);
26
+ // Reset
27
+ configureRetry({ maxRetries: 3, baseDelayMs: 500 });
28
+ });
29
+ });
30
+ describe('retry delay', () => {
31
+ test('exponential backoff with base 500', () => {
32
+ const delay0 = getRetryDelay(0); // 500 * 2^0 = 500ms ± jitter
33
+ const delay1 = getRetryDelay(1); // 500 * 2^1 = 1000ms ± jitter
34
+ const delay2 = getRetryDelay(2); // 500 * 2^2 = 2000ms ± jitter
35
+ // 由于 jitter (+/-25%),检查范围
36
+ expect(delay0).toBeGreaterThanOrEqual(375);
37
+ expect(delay0).toBeLessThanOrEqual(750);
38
+ expect(delay1).toBeGreaterThanOrEqual(750);
39
+ expect(delay1).toBeLessThanOrEqual(1500);
40
+ expect(delay2).toBeGreaterThanOrEqual(1500);
41
+ expect(delay2).toBeLessThanOrEqual(3000);
42
+ });
43
+ test('capped at maxDelayMs', () => {
44
+ const delay = getRetryDelay(10); // 500 * 2^10 = 512000ms, capped at 10000ms
45
+ expect(delay).toBeLessThanOrEqual(10000);
46
+ });
47
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,108 @@
1
+ import { describe, test, expect, beforeEach } from 'bun:test';
2
+ import { initModelRouter, resolveProvider } from '../../src/model-gateway/router.js';
3
+ const testConfig = {
4
+ default_model: 'deepseek-chat',
5
+ providers: {
6
+ deepseek: {
7
+ base_url: 'https://api.deepseek.com/v1',
8
+ api_key: 'sk-test-deepseek',
9
+ models: ['deepseek-chat', 'deepseek-v4-pro'],
10
+ },
11
+ openai: {
12
+ base_url: 'https://api.openai.com/v1',
13
+ api_key: 'sk-test-openai',
14
+ models: ['gpt-5.1'],
15
+ },
16
+ openrouter: {
17
+ base_url: 'https://openrouter.ai/api/v1',
18
+ api_key: 'sk-test-or',
19
+ },
20
+ ollama: {
21
+ base_url: 'http://127.0.0.1:11434/v1',
22
+ api_key: 'ollama',
23
+ models: ['qwen3:14b'],
24
+ },
25
+ },
26
+ fallback_chain: ['deepseek', 'openrouter', 'ollama'],
27
+ model_routing: {
28
+ 'gpt-': ['openai', 'deepseek'],
29
+ deepseek: ['deepseek'],
30
+ claude: ['openrouter'],
31
+ qwen: ['ollama'],
32
+ },
33
+ };
34
+ describe('model router', () => {
35
+ beforeEach(() => {
36
+ initModelRouter(testConfig);
37
+ });
38
+ test('routes deepseek models to deepseek provider', () => {
39
+ const provider = resolveProvider('deepseek-chat');
40
+ expect(provider).toBeDefined();
41
+ expect(provider.name).toBe('deepseek');
42
+ expect(provider.base_url).toBe('https://api.deepseek.com/v1');
43
+ expect(provider.api_key).toBe('sk-test-deepseek');
44
+ });
45
+ test('routes gpt models to openai first', () => {
46
+ const provider = resolveProvider('gpt-5.1');
47
+ expect(provider).toBeDefined();
48
+ expect(provider.name).toBe('openai');
49
+ });
50
+ test('routes claude models to openrouter', () => {
51
+ const provider = resolveProvider('claude-sonnet-4-6');
52
+ expect(provider).toBeDefined();
53
+ expect(provider.name).toBe('openrouter');
54
+ });
55
+ test('routes qwen models to ollama', () => {
56
+ const provider = resolveProvider('qwen3:14b');
57
+ expect(provider).toBeDefined();
58
+ expect(provider.name).toBe('ollama');
59
+ });
60
+ test('unknown models fall through to fallback chain', () => {
61
+ const provider = resolveProvider('codestral:22b');
62
+ expect(provider).toBeDefined();
63
+ expect(provider.name).toBe('deepseek');
64
+ });
65
+ test('returns null when module has no config (fresh import)', () => {
66
+ // resolveProvider 返回 null 当 config 未被 initModelRouter 设置
67
+ // 但在 beforeEach 中已经 init,所以这个测试验证已知行为
68
+ const provider = resolveProvider('any-model');
69
+ expect(provider).toBeDefined();
70
+ });
71
+ test('skips provider without api key in fallback', () => {
72
+ const cfgWithEmpty = {
73
+ ...testConfig,
74
+ fallback_chain: ['empty_provider', 'deepseek'],
75
+ providers: {
76
+ ...testConfig.providers,
77
+ empty_provider: {
78
+ base_url: 'https://nope.example.com/v1',
79
+ api_key: '',
80
+ models: [],
81
+ },
82
+ },
83
+ };
84
+ initModelRouter(cfgWithEmpty);
85
+ const provider = resolveProvider('unknown-model');
86
+ expect(provider.name).toBe('deepseek');
87
+ });
88
+ test('resolves api key from env var', () => {
89
+ Bun.env.TEST_PROVIDER_KEY = 'sk-from-env';
90
+ const cfgWithEnv = {
91
+ ...testConfig,
92
+ fallback_chain: ['env_provider'],
93
+ providers: {
94
+ ...testConfig.providers,
95
+ env_provider: {
96
+ base_url: 'https://env.example.com/v1',
97
+ api_key_env: 'TEST_PROVIDER_KEY',
98
+ models: [],
99
+ },
100
+ },
101
+ };
102
+ initModelRouter(cfgWithEnv);
103
+ const provider = resolveProvider('unknown-model');
104
+ expect(provider).toBeDefined();
105
+ expect(provider.api_key).toBe('sk-from-env');
106
+ delete Bun.env.TEST_PROVIDER_KEY;
107
+ });
108
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,83 @@
1
+ import { describe, test, expect, beforeAll } from 'bun:test';
2
+ import Fastify from 'fastify';
3
+ import { modelGatewayRoutes } from '../../src/model-gateway/routes.js';
4
+ import { initModelRouter } from '../../src/model-gateway/router.js';
5
+ const testConfig = {
6
+ default_model: 'deepseek-chat',
7
+ providers: {
8
+ deepseek: {
9
+ base_url: 'https://api.deepseek.com/v1',
10
+ api_key: 'sk-test',
11
+ models: ['deepseek-chat', 'deepseek-v4-pro'],
12
+ },
13
+ ollama: {
14
+ base_url: 'http://127.0.0.1:11434/v1',
15
+ api_key: 'ollama',
16
+ models: ['qwen3:14b'],
17
+ },
18
+ },
19
+ fallback_chain: ['deepseek', 'ollama'],
20
+ model_routing: {
21
+ deepseek: ['deepseek'],
22
+ qwen: ['ollama'],
23
+ },
24
+ };
25
+ describe('model gateway routes', () => {
26
+ let fastify;
27
+ beforeAll(async () => {
28
+ initModelRouter(testConfig);
29
+ fastify = Fastify({ logger: false });
30
+ await fastify.register(modelGatewayRoutes);
31
+ await fastify.ready();
32
+ });
33
+ test('GET /v1/models returns model list', async () => {
34
+ const resp = await fastify.inject({ method: 'GET', url: '/v1/models' });
35
+ expect(resp.statusCode).toBe(200);
36
+ const body = JSON.parse(resp.body);
37
+ expect(body.object).toBe('list');
38
+ expect(Array.isArray(body.data)).toBe(true);
39
+ expect(body.data.length).toBeGreaterThanOrEqual(2);
40
+ const modelIds = body.data.map((m) => m.id);
41
+ expect(modelIds).toContain('deepseek-chat');
42
+ expect(modelIds).toContain('qwen3:14b');
43
+ });
44
+ // 这些端点依赖 codexbar(外部进程,15s+),单元测试 skip,集成测试单独运行
45
+ test.skip('GET /model-gateway/health returns status', async () => {
46
+ const resp = await fastify.inject({ method: 'GET', url: '/model-gateway/health' });
47
+ expect([200, 500]).toContain(resp.statusCode);
48
+ });
49
+ test.skip('GET /model-gateway/quota returns quota data', async () => {
50
+ const resp = await fastify.inject({ method: 'GET', url: '/model-gateway/quota' });
51
+ expect([200, 500]).toContain(resp.statusCode);
52
+ });
53
+ test('POST /v1/chat/completions requires messages', async () => {
54
+ const resp = await fastify.inject({
55
+ method: 'POST',
56
+ url: '/v1/chat/completions',
57
+ payload: { model: 'test' },
58
+ });
59
+ expect(resp.statusCode).toBe(400);
60
+ const body = JSON.parse(resp.body);
61
+ expect(body.error).toBeDefined();
62
+ });
63
+ test('POST /v1/responses requires input', async () => {
64
+ const resp = await fastify.inject({
65
+ method: 'POST',
66
+ url: '/v1/responses',
67
+ payload: { model: 'test' },
68
+ });
69
+ expect(resp.statusCode).toBe(400);
70
+ });
71
+ test('POST /v1/chat/completions resolves provider and attempts call', async () => {
72
+ const resp = await fastify.inject({
73
+ method: 'POST',
74
+ url: '/v1/chat/completions',
75
+ payload: {
76
+ model: 'deepseek-chat',
77
+ messages: [{ role: 'user', content: 'Hello' }],
78
+ },
79
+ });
80
+ // Any status that isn't a crash (500) is acceptable in test env
81
+ expect([200, 401, 502, 503]).toContain(resp.statusCode);
82
+ });
83
+ });