@axiom-lattice/gateway 3.0.2 → 3.0.3

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 (155) hide show
  1. package/dist/index.js +31 -9
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +31 -9
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +9 -6
  6. package/.eslintrc.json +0 -22
  7. package/.turbo/turbo-build.log +0 -46
  8. package/AGENTS.md +0 -112
  9. package/CHANGELOG.md +0 -1190
  10. package/LOGGER_CONFIG.md +0 -135
  11. package/RESUME_STREAM_CONTENT_BASED.md +0 -326
  12. package/RESUME_STREAM_README.md +0 -388
  13. package/jest.config.js +0 -72
  14. package/public/sdk/README.md +0 -695
  15. package/public/sdk/dashboard-engine-skill.md +0 -1122
  16. package/public/sdk/dashboard-single-file-spec.md +0 -357
  17. package/public/sdk/data-query-sdk-skill.md +0 -307
  18. package/public/sdk/data-query-sdk.d.ts +0 -252
  19. package/public/sdk/data-query-sdk.js +0 -970
  20. package/public/sdk/occupancy-dashboard.html +0 -363
  21. package/public/sdk/test-dashboard.html +0 -690
  22. package/scripts/seed-lark-installation.ts +0 -54
  23. package/src/__tests__/__mocks__/e2b.ts +0 -1
  24. package/src/__tests__/a2a.test.ts +0 -428
  25. package/src/__tests__/channel-installations.test.ts +0 -199
  26. package/src/__tests__/data-query.test.ts +0 -77
  27. package/src/__tests__/sandbox-provider-registration.test.ts +0 -74
  28. package/src/__tests__/setup.ts +0 -5
  29. package/src/__tests__/workspace.test.ts +0 -400
  30. package/src/channels/__tests__/routes.test.ts +0 -50
  31. package/src/channels/lark/LarkChannelAdapter.ts +0 -217
  32. package/src/channels/lark/README.md +0 -187
  33. package/src/channels/lark/__tests__/controller.test.ts +0 -136
  34. package/src/channels/lark/__tests__/mapping-service.test.ts +0 -118
  35. package/src/channels/lark/__tests__/parser.test.ts +0 -72
  36. package/src/channels/lark/__tests__/sender.test.ts +0 -37
  37. package/src/channels/lark/__tests__/verification.test.ts +0 -157
  38. package/src/channels/lark/controller.ts +0 -48
  39. package/src/channels/lark/mapping-service.ts +0 -138
  40. package/src/channels/lark/parser.ts +0 -68
  41. package/src/channels/lark/routes.ts +0 -22
  42. package/src/channels/lark/sender.ts +0 -58
  43. package/src/channels/lark/types.ts +0 -20
  44. package/src/channels/lark/verification.ts +0 -32
  45. package/src/channels/registry.ts +0 -20
  46. package/src/channels/routes.ts +0 -30
  47. package/src/channels/wechat/WechatChannelAdapter.ts +0 -294
  48. package/src/channels/wechat/__tests__/context-store.test.ts +0 -148
  49. package/src/channels/wechat/context-store.ts +0 -54
  50. package/src/channels/wechat/controller.ts +0 -59
  51. package/src/channels/wechat/routes.ts +0 -7
  52. package/src/channels/wechat/types.ts +0 -62
  53. package/src/channels/wechat/wechat-client.ts +0 -162
  54. package/src/config/local-a2a-templates.ts +0 -59
  55. package/src/config.ts +0 -124
  56. package/src/controllers/__tests__/assistant-local-a2a.test.ts +0 -147
  57. package/src/controllers/__tests__/local-a2a-templates.test.ts +0 -112
  58. package/src/controllers/__tests__/mime.test.ts +0 -74
  59. package/src/controllers/__tests__/resources.test.ts +0 -122
  60. package/src/controllers/__tests__/run.test.ts +0 -115
  61. package/src/controllers/__tests__/tasks.test.ts +0 -215
  62. package/src/controllers/__tests__/workflow-tracking.test.ts +0 -113
  63. package/src/controllers/agent_task.ts +0 -72
  64. package/src/controllers/assistant.ts +0 -490
  65. package/src/controllers/auth.ts +0 -524
  66. package/src/controllers/channel-bindings.ts +0 -135
  67. package/src/controllers/channel-installations.ts +0 -409
  68. package/src/controllers/collections.ts +0 -176
  69. package/src/controllers/config.ts +0 -126
  70. package/src/controllers/connections.ts +0 -181
  71. package/src/controllers/data-query.ts +0 -236
  72. package/src/controllers/database-configs.ts +0 -440
  73. package/src/controllers/embeddings.ts +0 -22
  74. package/src/controllers/entries.ts +0 -167
  75. package/src/controllers/eval.ts +0 -513
  76. package/src/controllers/export-import.ts +0 -166
  77. package/src/controllers/health.ts +0 -36
  78. package/src/controllers/local-a2a-templates.ts +0 -18
  79. package/src/controllers/local-a2a.ts +0 -149
  80. package/src/controllers/mcp-configs.ts +0 -673
  81. package/src/controllers/memory.ts +0 -257
  82. package/src/controllers/menu-items.ts +0 -114
  83. package/src/controllers/metrics-configs.ts +0 -1061
  84. package/src/controllers/middleware-types.ts +0 -15
  85. package/src/controllers/models.ts +0 -199
  86. package/src/controllers/personal-assistant.ts +0 -191
  87. package/src/controllers/resources.ts +0 -477
  88. package/src/controllers/run.ts +0 -338
  89. package/src/controllers/sandbox.ts +0 -255
  90. package/src/controllers/schedules.ts +0 -336
  91. package/src/controllers/skills.ts +0 -256
  92. package/src/controllers/tasks.ts +0 -213
  93. package/src/controllers/tenants.ts +0 -121
  94. package/src/controllers/thread_status.ts +0 -66
  95. package/src/controllers/threads.ts +0 -260
  96. package/src/controllers/tools.ts +0 -139
  97. package/src/controllers/users.ts +0 -135
  98. package/src/controllers/workflow-tracking.ts +0 -693
  99. package/src/controllers/workspace.ts +0 -879
  100. package/src/export_registrations/a2a-api-key.registration.ts +0 -89
  101. package/src/export_registrations/agent.registration.ts +0 -80
  102. package/src/export_registrations/binding.registration.ts +0 -106
  103. package/src/export_registrations/channel-installation.registration.ts +0 -88
  104. package/src/export_registrations/collection.registration.ts +0 -85
  105. package/src/export_registrations/connection.registration.ts +0 -97
  106. package/src/export_registrations/database-config.registration.ts +0 -94
  107. package/src/export_registrations/eval.registration.ts +0 -334
  108. package/src/export_registrations/index.ts +0 -31
  109. package/src/export_registrations/mcp-config.registration.ts +0 -97
  110. package/src/export_registrations/menu.registration.ts +0 -91
  111. package/src/export_registrations/metrics-config.registration.ts +0 -94
  112. package/src/export_registrations/skill.registration.ts +0 -88
  113. package/src/export_registrations/task.registration.ts +0 -99
  114. package/src/index.ts +0 -497
  115. package/src/logger/Logger.ts +0 -186
  116. package/src/router/MessageContext.ts +0 -14
  117. package/src/router/MessageRouter.ts +0 -490
  118. package/src/router/middlewares/auditLogger.ts +0 -34
  119. package/src/router/middlewares/deduplication.ts +0 -38
  120. package/src/router/middlewares/index.ts +0 -3
  121. package/src/router/middlewares/rateLimit.ts +0 -46
  122. package/src/routes/__tests__/a2a-test-connection.test.ts +0 -177
  123. package/src/routes/a2a-bridge.ts +0 -266
  124. package/src/routes/a2a-test-connection.ts +0 -126
  125. package/src/routes/a2a.ts +0 -851
  126. package/src/routes/channel-bindings.ts +0 -11
  127. package/src/routes/channel-installations.ts +0 -33
  128. package/src/routes/index.ts +0 -640
  129. package/src/routes/menu-items.ts +0 -10
  130. package/src/routes/resource-routes.ts +0 -35
  131. package/src/routes/stt.ts +0 -102
  132. package/src/schemas/data-query.ts +0 -69
  133. package/src/schemas/index.ts +0 -852
  134. package/src/services/ExportImportService.ts +0 -296
  135. package/src/services/__tests__/ExportImportService.test.ts +0 -130
  136. package/src/services/__tests__/eval-runner.test.ts +0 -131
  137. package/src/services/__tests__/local-a2a.test.ts +0 -124
  138. package/src/services/agent_task_consumer.ts +0 -348
  139. package/src/services/collection_service.ts +0 -258
  140. package/src/services/eval-runner.ts +0 -283
  141. package/src/services/local-a2a.ts +0 -354
  142. package/src/services/queue_service.ts +0 -104
  143. package/src/services/sandbox_service.ts +0 -223
  144. package/src/services/skill_service.ts +0 -97
  145. package/src/services/supabase.ts +0 -60
  146. package/src/swagger.ts +0 -77
  147. package/src/types/index.ts +0 -118
  148. package/src/types/koa.d.ts +0 -7
  149. package/src/utils/__tests__/local-a2a-runtime.test.ts +0 -77
  150. package/src/utils/__tests__/path-containment.test.ts +0 -35
  151. package/src/utils/a2a-url.ts +0 -20
  152. package/src/utils/local-a2a-runtime.ts +0 -107
  153. package/src/utils/mime.ts +0 -44
  154. package/src/utils/path-containment.ts +0 -38
  155. package/tsconfig.json +0 -26
@@ -1,162 +0,0 @@
1
- import { randomBytes } from "crypto";
2
-
3
- const DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com";
4
-
5
- function getBaseUrl(): string {
6
- return process.env.WECHAT_ILINK_BASE_URL || DEFAULT_BASE_URL;
7
- }
8
-
9
- function generateWechatUin(): string {
10
- const buf = Buffer.alloc(4);
11
- buf.writeUInt32BE(Math.floor(Math.random() * 0xffffffff), 0);
12
- return buf.toString("base64url");
13
- }
14
-
15
- async function apiGet<T>(path: string, extraHeaders?: Record<string, string>): Promise<T> {
16
- const url = `${getBaseUrl()}${path}`;
17
- const headers: Record<string, string> = { ...extraHeaders };
18
- const res = await fetch(url, { headers });
19
- if (!res.ok) {
20
- throw new Error(`iLink GET ${path} failed: ${res.status} ${res.statusText}`);
21
- }
22
- return res.json() as Promise<T>;
23
- }
24
-
25
- async function apiGetAuth<T>(path: string, botToken: string, extraHeaders?: Record<string, string>): Promise<T> {
26
- const url = `${getBaseUrl()}${path}`;
27
- const headers: Record<string, string> = {
28
- "AuthorizationType": "ilink_bot_token",
29
- "X-WECHAT-UIN": generateWechatUin(),
30
- "Authorization": `Bearer ${botToken}`,
31
- ...extraHeaders,
32
- };
33
- const res = await fetch(url, { headers });
34
- if (!res.ok) {
35
- throw new Error(`iLink GET ${path} failed: ${res.status} ${res.statusText}`);
36
- }
37
- return res.json() as Promise<T>;
38
- }
39
-
40
- async function apiPost<T>(path: string, body: unknown, botToken: string, signal?: AbortSignal): Promise<T> {
41
- const url = `${getBaseUrl()}${path}`;
42
- const headers: Record<string, string> = {
43
- "Content-Type": "application/json",
44
- "AuthorizationType": "ilink_bot_token",
45
- "X-WECHAT-UIN": generateWechatUin(),
46
- "Authorization": `Bearer ${botToken}`,
47
- };
48
-
49
- const res = await fetch(url, {
50
- method: "POST",
51
- headers,
52
- body: JSON.stringify(body),
53
- signal,
54
- });
55
- if (!res.ok) {
56
- throw new Error(`iLink POST ${path} failed: ${res.status} ${res.statusText}`);
57
- }
58
- return res.json() as Promise<T>;
59
- }
60
-
61
- export async function getUpdates(
62
- botToken: string,
63
- syncBuffer: string,
64
- signal?: AbortSignal,
65
- ): Promise<{ msgs: Array<{
66
- message_type?: number;
67
- from_user_id?: string;
68
- client_id?: string;
69
- item_list?: Array<{ type?: number; text_item?: { text?: string } }>;
70
- context_token?: string;
71
- create_time_ms?: number;
72
- }>; syncBuffer: string }> {
73
- const data = await apiPost<{
74
- ret?: number;
75
- errcode?: number;
76
- errmsg?: string;
77
- msgs?: Array<{
78
- message_type?: number;
79
- from_user_id?: string;
80
- client_id?: string;
81
- item_list?: Array<{ type?: number; text_item?: { text?: string } }>;
82
- context_token?: string;
83
- create_time_ms?: number;
84
- }>;
85
- get_updates_buf?: string;
86
- }>("/ilink/bot/getupdates", {
87
- get_updates_buf: syncBuffer,
88
- base_info: { channel_version: "1.0.2" },
89
- }, botToken, signal);
90
-
91
- // Check API-level error codes (HTTP 200 can still be an error)
92
- if ((data.ret !== undefined && data.ret !== 0) || (data.errcode !== undefined && data.errcode !== 0)) {
93
- throw new Error(`iLink getUpdates error: ret=${data.ret} errcode=${data.errcode} errmsg=${data.errmsg ?? ""}`);
94
- }
95
-
96
- return {
97
- msgs: data.msgs ?? [],
98
- syncBuffer: data.get_updates_buf ?? syncBuffer,
99
- };
100
- }
101
-
102
- function generateClientId(): string {
103
- return `axiom-wechat:${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
104
- }
105
-
106
- export async function sendMessage(
107
- botToken: string,
108
- toUserId: string,
109
- text: string,
110
- contextToken: string,
111
- ): Promise<void> {
112
- await apiPost<unknown>("/ilink/bot/sendmessage", {
113
- msg: {
114
- from_user_id: "",
115
- to_user_id: toUserId,
116
- client_id: generateClientId(),
117
- message_type: 2, // MSG_TYPE_BOT
118
- message_state: 2, // MSG_STATE_FINISH
119
- item_list: [{ type: 1, text_item: { text } }],
120
- context_token: contextToken,
121
- },
122
- base_info: { channel_version: "1.0.2" },
123
- }, botToken);
124
- }
125
-
126
- export async function getQrCode(): Promise<{ qrcode: string; qrcodeImgUrl: string }> {
127
- const data = await apiGet<{ qrcode?: string; qrcode_img_content?: string }>(
128
- "/ilink/bot/get_bot_qrcode?bot_type=3",
129
- );
130
- if (!data.qrcode || !data.qrcode_img_content) {
131
- throw new Error("Failed to get WeChat QR code");
132
- }
133
- return { qrcode: data.qrcode, qrcodeImgUrl: data.qrcode_img_content };
134
- }
135
-
136
- export async function getQrCodeStatus(
137
- qrcode: string,
138
- signal?: AbortSignal,
139
- ): Promise<{
140
- status: "wait" | "scaned" | "confirmed" | "expired";
141
- botToken?: string;
142
- uin?: string;
143
- botId?: string;
144
- baseUrl?: string;
145
- }> {
146
- const data = await apiGet<{
147
- status?: string;
148
- bot_token?: string;
149
- ilink_bot_id?: string;
150
- ilink_user_id?: string;
151
- baseurl?: string;
152
- }>(`/ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`, {
153
- "iLink-App-ClientVersion": "1",
154
- });
155
- return {
156
- status: (data.status as "wait" | "scaned" | "confirmed" | "expired") || "wait",
157
- botToken: data.bot_token,
158
- uin: data.ilink_user_id,
159
- botId: data.ilink_bot_id,
160
- baseUrl: data.baseurl,
161
- };
162
- }
@@ -1,59 +0,0 @@
1
- import { cliA2AEntryPath } from "@axiom-lattice/cli-a2a/cli-path";
2
- import type { LocalA2ATemplateDefinition } from "@axiom-lattice/protocols";
3
-
4
- type LocalProviderKey = "claude" | "codex" | "opencode";
5
-
6
- function buildCliA2ACommand(
7
- provider: LocalProviderKey,
8
- agentName: string,
9
- agentDescription: string,
10
- ): LocalA2ATemplateDefinition["command"] {
11
- return {
12
- executable: process.execPath,
13
- args: [
14
- cliA2AEntryPath,
15
- "--provider",
16
- provider,
17
- "--hostname",
18
- "localhost",
19
- "--advertise-host",
20
- "localhost",
21
- "--agent-name",
22
- agentName,
23
- "--agent-description",
24
- agentDescription,
25
- "--log-level",
26
- "info",
27
- ],
28
- };
29
- }
30
-
31
- export const LOCAL_A2A_TEMPLATES: LocalA2ATemplateDefinition[] = [
32
- {
33
- key: "claude",
34
- name: "Claude Code",
35
- command: buildCliA2ACommand(
36
- "claude",
37
- "Claude Code",
38
- "Local Claude Code wrapper exposed through A2A.",
39
- ),
40
- },
41
- {
42
- key: "codex",
43
- name: "Codex",
44
- command: buildCliA2ACommand(
45
- "codex",
46
- "Codex",
47
- "Local Codex wrapper exposed through A2A.",
48
- ),
49
- },
50
- {
51
- key: "opencode",
52
- name: "OpenCode",
53
- command: buildCliA2ACommand(
54
- "opencode",
55
- "OpenCode",
56
- "Local OpenCode wrapper exposed through A2A.",
57
- ),
58
- },
59
- ];
package/src/config.ts DELETED
@@ -1,124 +0,0 @@
1
- /**
2
- * Configuration service
3
- * Manages environment variables and supports dynamic updates
4
- */
5
-
6
- export interface GatewayConfig {
7
- port?: number;
8
- queueServiceType?: string;
9
- redisUrl?: string;
10
- redisPassword?: string;
11
- queueName?: string;
12
- [key: string]: any; // Allow additional config keys
13
- }
14
-
15
- /**
16
- * Get configuration from environment variables
17
- * Supports dynamic updates via updateConfig method
18
- */
19
- class ConfigService {
20
- private config: GatewayConfig;
21
-
22
- constructor() {
23
- this.config = this.loadFromEnv();
24
- }
25
-
26
- /**
27
- * Load configuration from environment variables
28
- */
29
- private loadFromEnv(): GatewayConfig {
30
- return {
31
- port: process.env.PORT ? Number(process.env.PORT) : undefined,
32
- queueServiceType: process.env.QUEUE_SERVICE_TYPE,
33
- redisUrl: process.env.REDIS_URL,
34
- redisPassword: process.env.REDIS_PASSWORD,
35
- queueName: process.env.QUEUE_NAME,
36
- };
37
- }
38
-
39
- /**
40
- * Update configuration from JSON object
41
- * This will update both the internal config and process.env
42
- */
43
- updateConfig(jsonConfig: Record<string, any>): void {
44
- // Update process.env for all provided keys
45
- for (const [key, value] of Object.entries(jsonConfig)) {
46
- if (value !== null && value !== undefined) {
47
- // Convert nested objects to environment variable format
48
- if (typeof value === "object" && !Array.isArray(value)) {
49
- // Handle nested objects like supabase: { url: "...", key: "..." }
50
- for (const [nestedKey, nestedValue] of Object.entries(value)) {
51
- const envKey = `${key.toUpperCase()}_${nestedKey.toUpperCase()}`;
52
- process.env[envKey] = String(nestedValue);
53
- }
54
- } else {
55
- // Handle flat keys
56
- process.env[key.toUpperCase()] = String(value);
57
- }
58
- }
59
- }
60
-
61
- // Reload config from updated environment variables
62
- this.config = this.loadFromEnv();
63
-
64
- // Deep merge the JSON config into our config object
65
- this.config = this.deepMerge(this.config, jsonConfig);
66
- }
67
-
68
- /**
69
- * Deep merge two objects
70
- */
71
- private deepMerge(target: any, source: any): any {
72
- const output = { ...target };
73
- if (this.isObject(target) && this.isObject(source)) {
74
- Object.keys(source).forEach((key) => {
75
- if (this.isObject(source[key])) {
76
- if (!(key in target)) {
77
- Object.assign(output, { [key]: source[key] });
78
- } else {
79
- output[key] = this.deepMerge(target[key], source[key]);
80
- }
81
- } else {
82
- Object.assign(output, { [key]: source[key] });
83
- }
84
- });
85
- }
86
- return output;
87
- }
88
-
89
- /**
90
- * Check if value is a plain object
91
- */
92
- private isObject(item: any): boolean {
93
- return item && typeof item === "object" && !Array.isArray(item);
94
- }
95
-
96
- /**
97
- * Get current configuration
98
- */
99
- getConfig(): GatewayConfig {
100
- return { ...this.config };
101
- }
102
- }
103
-
104
- // Export singleton instance
105
- export const configService = new ConfigService();
106
-
107
- // Export config getter for backward compatibility
108
- export const config = {
109
- get port() {
110
- return configService.getConfig().port;
111
- },
112
- get queueServiceType() {
113
- return configService.getConfig().queueServiceType;
114
- },
115
- get redisUrl() {
116
- return configService.getConfig().redisUrl;
117
- },
118
- get redisPassword() {
119
- return configService.getConfig().redisPassword;
120
- },
121
- get queueName() {
122
- return configService.getConfig().queueName;
123
- },
124
- };
@@ -1,147 +0,0 @@
1
- import type { FastifyReply, FastifyRequest } from "fastify";
2
- import { AgentType } from "@axiom-lattice/protocols";
3
-
4
- const mockGetStoreLattice = jest.fn();
5
- const mockPublish = jest.fn();
6
-
7
- jest.mock("@axiom-lattice/core", () => ({
8
- agentInstanceManager: {},
9
- agentLatticeManager: {
10
- getAllAgentConfigsByTenant: jest.fn(),
11
- getAgentConfigWithTenant: jest.fn(),
12
- },
13
- eventBus: {
14
- publish: (...args: unknown[]) => mockPublish(...args),
15
- },
16
- getStoreLattice: (...args: unknown[]) => mockGetStoreLattice(...args),
17
- }));
18
-
19
- describe("createAssistant local A2A validation", () => {
20
- let createAssistant: typeof import("../assistant").createAssistant;
21
-
22
- beforeEach(async () => {
23
- jest.clearAllMocks();
24
- ({ createAssistant } = await import("../assistant"));
25
- });
26
-
27
- function mockReply(): FastifyReply {
28
- return {
29
- status: jest.fn().mockReturnThis(),
30
- send: jest.fn(),
31
- } as unknown as FastifyReply;
32
- }
33
-
34
- it("rejects an empty local runtime executable before upsert", async () => {
35
- const reply = mockReply();
36
- const request = {
37
- headers: { "x-tenant-id": "tenant-1" },
38
- body: {
39
- name: "Incomplete Local A2A",
40
- graphDefinition: {
41
- key: "incomplete-local-a2a",
42
- name: "Incomplete Local A2A",
43
- type: AgentType.A2A_REMOTE,
44
- agentCardUrl: "http://localhost:4301/.well-known/agent-card.json",
45
- localRuntime: {
46
- agentCardUrl: "http://localhost:4301/.well-known/agent-card.json",
47
- command: { executable: " ", args: [] },
48
- },
49
- },
50
- },
51
- } as unknown as FastifyRequest;
52
-
53
- await createAssistant(request as Parameters<typeof createAssistant>[0], reply);
54
-
55
- expect(reply.status).toHaveBeenCalledWith(400);
56
- expect(reply.send).toHaveBeenCalledWith({
57
- success: false,
58
- message: "Local managed A2A command executable is required",
59
- });
60
- expect(mockGetStoreLattice).not.toHaveBeenCalled();
61
- });
62
-
63
- it("rejects local runtime port mismatches before upsert", async () => {
64
- const reply = mockReply();
65
- const request = {
66
- headers: { "x-tenant-id": "tenant-1" },
67
- body: {
68
- name: "Bad Local A2A",
69
- graphDefinition: {
70
- key: "bad-local-a2a",
71
- name: "Bad Local A2A",
72
- type: AgentType.A2A_REMOTE,
73
- agentCardUrl: "http://localhost:4301/.well-known/agent-card.json",
74
- localRuntime: {
75
- agentCardUrl: "http://localhost:4301/.well-known/agent-card.json",
76
- command: {
77
- executable: "node",
78
- args: ["--port", "4302"],
79
- },
80
- },
81
- },
82
- },
83
- } as unknown as FastifyRequest;
84
-
85
- await createAssistant(request as Parameters<typeof createAssistant>[0], reply);
86
-
87
- expect(reply.status).toHaveBeenCalledWith(400);
88
- expect(reply.send).toHaveBeenCalledWith({
89
- success: false,
90
- message: expect.stringContaining("command port (4302) does not match agentCardUrl port (4301)"),
91
- });
92
- expect(mockGetStoreLattice).not.toHaveBeenCalled();
93
- });
94
-
95
- it("rejects local runtime port collisions across assistants", async () => {
96
- const reply = mockReply();
97
- const getAllAssistants = jest.fn().mockResolvedValue([
98
- {
99
- id: "existing-local-a2a",
100
- name: "Existing Local A2A",
101
- graphDefinition: {
102
- type: AgentType.A2A_REMOTE,
103
- agentCardUrl: "http://127.0.0.1:4301/.well-known/agent-card.json",
104
- localRuntime: {
105
- agentCardUrl: "http://127.0.0.1:4301/.well-known/agent-card.json",
106
- command: {
107
- executable: "node",
108
- args: ["packages/cli-a2a/dist/cli.js", "--provider", "codex", "--port", "4301"],
109
- },
110
- },
111
- },
112
- },
113
- ]);
114
- mockGetStoreLattice.mockReturnValueOnce({
115
- store: { getAllAssistants },
116
- });
117
-
118
- const request = {
119
- headers: { "x-tenant-id": "tenant-1" },
120
- body: {
121
- name: "New Local A2A",
122
- graphDefinition: {
123
- key: "new-local-a2a",
124
- name: "New Local A2A",
125
- type: AgentType.A2A_REMOTE,
126
- agentCardUrl: "http://localhost:4301/custom-agent-card",
127
- localRuntime: {
128
- agentCardUrl: "http://localhost:4301/custom-agent-card",
129
- command: {
130
- executable: "node",
131
- args: ["packages/cli-a2a/dist/cli.js", "--provider", "claude", "--port", "4301"],
132
- },
133
- },
134
- },
135
- },
136
- } as unknown as FastifyRequest;
137
-
138
- await createAssistant(request as Parameters<typeof createAssistant>[0], reply);
139
-
140
- expect(getAllAssistants).toHaveBeenCalledWith("tenant-1");
141
- expect(reply.status).toHaveBeenCalledWith(409);
142
- expect(reply.send).toHaveBeenCalledWith({
143
- success: false,
144
- message: expect.stringContaining("port 4301"),
145
- });
146
- });
147
- });
@@ -1,112 +0,0 @@
1
- import fastify, { FastifyInstance } from "fastify";
2
- import { createRequire } from "node:module";
3
- import { dirname, join } from "node:path";
4
- import { getLocalA2ATemplates } from "../local-a2a-templates";
5
-
6
- describe("Local A2A templates (static config)", () => {
7
- let app: FastifyInstance;
8
-
9
- beforeEach(() => {
10
- app = fastify();
11
- app.get("/api/local-a2a/templates", getLocalA2ATemplates);
12
- });
13
-
14
- afterEach(async () => {
15
- await app.close();
16
- });
17
-
18
- it("returns 3 static templates", async () => {
19
- const response = await app.inject({
20
- method: "GET",
21
- url: "/api/local-a2a/templates",
22
- });
23
-
24
- expect(response.statusCode).toBe(200);
25
- const body = JSON.parse(response.body);
26
- expect(body.success).toBe(true);
27
- expect(body.data.records).toHaveLength(3);
28
- expect(body.data.total).toBe(3);
29
- expect(body.data.records.map((template: { key: string }) => template.key).sort()).toEqual([
30
- "claude",
31
- "codex",
32
- "opencode",
33
- ]);
34
- });
35
-
36
- it("each template has key, name, and command properties", async () => {
37
- const response = await app.inject({
38
- method: "GET",
39
- url: "/api/local-a2a/templates",
40
- });
41
-
42
- const body = JSON.parse(response.body);
43
- for (const template of body.data.records) {
44
- expect(template).toHaveProperty("key");
45
- expect(typeof template.key).toBe("string");
46
- expect(template).toHaveProperty("name");
47
- expect(typeof template.name).toBe("string");
48
- expect(template).toHaveProperty("command");
49
- expect(template.command).toHaveProperty("executable");
50
- expect(Array.isArray(template.command.args)).toBe(true);
51
- }
52
- });
53
-
54
- it("starts the cli-a2a wrapper instead of provider CLIs directly", async () => {
55
- const response = await app.inject({
56
- method: "GET",
57
- url: "/api/local-a2a/templates",
58
- });
59
-
60
- const body = JSON.parse(response.body);
61
- const packagePath = createRequire(__filename).resolve(
62
- "@axiom-lattice/cli-a2a/package.json",
63
- );
64
- const expectedEntryPath = join(dirname(packagePath), "dist", "cli.js");
65
- for (const template of body.data.records) {
66
- expect(template.command.executable).toBe(process.execPath);
67
- expect(template.command.args[0]).toBe(expectedEntryPath);
68
- expect(template.command.args).toEqual(expect.arrayContaining([
69
- "--provider",
70
- template.key,
71
- ]));
72
- expect(template.command.args).not.toContain("--serve");
73
- }
74
- });
75
-
76
- it("does not include agentCardUrl or port in any template", async () => {
77
- const response = await app.inject({
78
- method: "GET",
79
- url: "/api/local-a2a/templates",
80
- });
81
-
82
- const body = JSON.parse(response.body);
83
- for (const template of body.data.records) {
84
- expect(template).not.toHaveProperty("agentCardUrl");
85
- expect(template).not.toHaveProperty("port");
86
- expect(template).not.toHaveProperty("template");
87
- expect(template).not.toHaveProperty("id");
88
- expect(template.command).not.toHaveProperty("port");
89
- // args should not contain a port flag
90
- const hasPortFlag = template.command.args.some(
91
- (arg: string) => arg === "--port" || arg === "-p" || /^--port=/.test(arg) || /^-p=/.test(arg),
92
- );
93
- expect(hasPortFlag).toBe(false);
94
- }
95
- });
96
-
97
- it("response shape matches expected structure", async () => {
98
- const response = await app.inject({
99
- method: "GET",
100
- url: "/api/local-a2a/templates",
101
- });
102
-
103
- const body = JSON.parse(response.body);
104
- expect(body).toEqual({
105
- success: true,
106
- data: {
107
- records: expect.any(Array),
108
- total: 3,
109
- },
110
- });
111
- });
112
- });
@@ -1,74 +0,0 @@
1
- import { getContentTypeFromFilename, getFilenameFromPath } from "../../utils/mime";
2
-
3
- describe("getContentTypeFromFilename", () => {
4
- it("returns text/html for .html", () => {
5
- expect(getContentTypeFromFilename("index.html")).toBe("text/html");
6
- });
7
-
8
- it("returns text/css for .css", () => {
9
- expect(getContentTypeFromFilename("styles.css")).toBe("text/css");
10
- });
11
-
12
- it("returns application/javascript for .js", () => {
13
- expect(getContentTypeFromFilename("app.js")).toBe("application/javascript");
14
- });
15
-
16
- it("returns application/json for .json", () => {
17
- expect(getContentTypeFromFilename("data.json")).toBe("application/json");
18
- });
19
-
20
- it("returns image/png for .png", () => {
21
- expect(getContentTypeFromFilename("logo.png")).toBe("image/png");
22
- });
23
-
24
- it("returns image/svg+xml for .svg", () => {
25
- expect(getContentTypeFromFilename("icon.svg")).toBe("image/svg+xml");
26
- });
27
-
28
- it("returns application/pdf for .pdf", () => {
29
- expect(getContentTypeFromFilename("report.pdf")).toBe("application/pdf");
30
- });
31
-
32
- it("returns Office MIME types", () => {
33
- expect(getContentTypeFromFilename("sheet.xlsx")).toContain("spreadsheet");
34
- expect(getContentTypeFromFilename("sheet.xls")).toContain("excel");
35
- });
36
-
37
- it("returns application/octet-stream for unknown extensions", () => {
38
- expect(getContentTypeFromFilename("file.unknown")).toBe("application/octet-stream");
39
- });
40
-
41
- it("returns application/octet-stream for no extension", () => {
42
- expect(getContentTypeFromFilename("README")).toBe("application/octet-stream");
43
- });
44
-
45
- it("handles paths with directory separators", () => {
46
- expect(getContentTypeFromFilename("/app/static/script.js")).toBe("application/javascript");
47
- });
48
-
49
- it("handles uppercase extensions", () => {
50
- expect(getContentTypeFromFilename("LOGO.PNG")).toBe("image/png");
51
- });
52
- });
53
-
54
- describe("getFilenameFromPath", () => {
55
- it("extracts filename from path", () => {
56
- expect(getFilenameFromPath("/project/uploads/foo.html")).toBe("foo.html");
57
- });
58
-
59
- it("extracts filename from simple path", () => {
60
- expect(getFilenameFromPath("foo.html")).toBe("foo.html");
61
- });
62
-
63
- it("handles trailing slashes", () => {
64
- expect(getFilenameFromPath("/project/uploads/")).toBe("uploads");
65
- });
66
-
67
- it("returns download for empty path", () => {
68
- expect(getFilenameFromPath("")).toBe("download");
69
- });
70
-
71
- it("returns download for root path", () => {
72
- expect(getFilenameFromPath("/")).toBe("download");
73
- });
74
- });