@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.
- package/dist/index.js +31 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +31 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -6
- package/.eslintrc.json +0 -22
- package/.turbo/turbo-build.log +0 -46
- package/AGENTS.md +0 -112
- package/CHANGELOG.md +0 -1190
- package/LOGGER_CONFIG.md +0 -135
- package/RESUME_STREAM_CONTENT_BASED.md +0 -326
- package/RESUME_STREAM_README.md +0 -388
- package/jest.config.js +0 -72
- package/public/sdk/README.md +0 -695
- package/public/sdk/dashboard-engine-skill.md +0 -1122
- package/public/sdk/dashboard-single-file-spec.md +0 -357
- package/public/sdk/data-query-sdk-skill.md +0 -307
- package/public/sdk/data-query-sdk.d.ts +0 -252
- package/public/sdk/data-query-sdk.js +0 -970
- package/public/sdk/occupancy-dashboard.html +0 -363
- package/public/sdk/test-dashboard.html +0 -690
- package/scripts/seed-lark-installation.ts +0 -54
- package/src/__tests__/__mocks__/e2b.ts +0 -1
- package/src/__tests__/a2a.test.ts +0 -428
- package/src/__tests__/channel-installations.test.ts +0 -199
- package/src/__tests__/data-query.test.ts +0 -77
- package/src/__tests__/sandbox-provider-registration.test.ts +0 -74
- package/src/__tests__/setup.ts +0 -5
- package/src/__tests__/workspace.test.ts +0 -400
- package/src/channels/__tests__/routes.test.ts +0 -50
- package/src/channels/lark/LarkChannelAdapter.ts +0 -217
- package/src/channels/lark/README.md +0 -187
- package/src/channels/lark/__tests__/controller.test.ts +0 -136
- package/src/channels/lark/__tests__/mapping-service.test.ts +0 -118
- package/src/channels/lark/__tests__/parser.test.ts +0 -72
- package/src/channels/lark/__tests__/sender.test.ts +0 -37
- package/src/channels/lark/__tests__/verification.test.ts +0 -157
- package/src/channels/lark/controller.ts +0 -48
- package/src/channels/lark/mapping-service.ts +0 -138
- package/src/channels/lark/parser.ts +0 -68
- package/src/channels/lark/routes.ts +0 -22
- package/src/channels/lark/sender.ts +0 -58
- package/src/channels/lark/types.ts +0 -20
- package/src/channels/lark/verification.ts +0 -32
- package/src/channels/registry.ts +0 -20
- package/src/channels/routes.ts +0 -30
- package/src/channels/wechat/WechatChannelAdapter.ts +0 -294
- package/src/channels/wechat/__tests__/context-store.test.ts +0 -148
- package/src/channels/wechat/context-store.ts +0 -54
- package/src/channels/wechat/controller.ts +0 -59
- package/src/channels/wechat/routes.ts +0 -7
- package/src/channels/wechat/types.ts +0 -62
- package/src/channels/wechat/wechat-client.ts +0 -162
- package/src/config/local-a2a-templates.ts +0 -59
- package/src/config.ts +0 -124
- package/src/controllers/__tests__/assistant-local-a2a.test.ts +0 -147
- package/src/controllers/__tests__/local-a2a-templates.test.ts +0 -112
- package/src/controllers/__tests__/mime.test.ts +0 -74
- package/src/controllers/__tests__/resources.test.ts +0 -122
- package/src/controllers/__tests__/run.test.ts +0 -115
- package/src/controllers/__tests__/tasks.test.ts +0 -215
- package/src/controllers/__tests__/workflow-tracking.test.ts +0 -113
- package/src/controllers/agent_task.ts +0 -72
- package/src/controllers/assistant.ts +0 -490
- package/src/controllers/auth.ts +0 -524
- package/src/controllers/channel-bindings.ts +0 -135
- package/src/controllers/channel-installations.ts +0 -409
- package/src/controllers/collections.ts +0 -176
- package/src/controllers/config.ts +0 -126
- package/src/controllers/connections.ts +0 -181
- package/src/controllers/data-query.ts +0 -236
- package/src/controllers/database-configs.ts +0 -440
- package/src/controllers/embeddings.ts +0 -22
- package/src/controllers/entries.ts +0 -167
- package/src/controllers/eval.ts +0 -513
- package/src/controllers/export-import.ts +0 -166
- package/src/controllers/health.ts +0 -36
- package/src/controllers/local-a2a-templates.ts +0 -18
- package/src/controllers/local-a2a.ts +0 -149
- package/src/controllers/mcp-configs.ts +0 -673
- package/src/controllers/memory.ts +0 -257
- package/src/controllers/menu-items.ts +0 -114
- package/src/controllers/metrics-configs.ts +0 -1061
- package/src/controllers/middleware-types.ts +0 -15
- package/src/controllers/models.ts +0 -199
- package/src/controllers/personal-assistant.ts +0 -191
- package/src/controllers/resources.ts +0 -477
- package/src/controllers/run.ts +0 -338
- package/src/controllers/sandbox.ts +0 -255
- package/src/controllers/schedules.ts +0 -336
- package/src/controllers/skills.ts +0 -256
- package/src/controllers/tasks.ts +0 -213
- package/src/controllers/tenants.ts +0 -121
- package/src/controllers/thread_status.ts +0 -66
- package/src/controllers/threads.ts +0 -260
- package/src/controllers/tools.ts +0 -139
- package/src/controllers/users.ts +0 -135
- package/src/controllers/workflow-tracking.ts +0 -693
- package/src/controllers/workspace.ts +0 -879
- package/src/export_registrations/a2a-api-key.registration.ts +0 -89
- package/src/export_registrations/agent.registration.ts +0 -80
- package/src/export_registrations/binding.registration.ts +0 -106
- package/src/export_registrations/channel-installation.registration.ts +0 -88
- package/src/export_registrations/collection.registration.ts +0 -85
- package/src/export_registrations/connection.registration.ts +0 -97
- package/src/export_registrations/database-config.registration.ts +0 -94
- package/src/export_registrations/eval.registration.ts +0 -334
- package/src/export_registrations/index.ts +0 -31
- package/src/export_registrations/mcp-config.registration.ts +0 -97
- package/src/export_registrations/menu.registration.ts +0 -91
- package/src/export_registrations/metrics-config.registration.ts +0 -94
- package/src/export_registrations/skill.registration.ts +0 -88
- package/src/export_registrations/task.registration.ts +0 -99
- package/src/index.ts +0 -497
- package/src/logger/Logger.ts +0 -186
- package/src/router/MessageContext.ts +0 -14
- package/src/router/MessageRouter.ts +0 -490
- package/src/router/middlewares/auditLogger.ts +0 -34
- package/src/router/middlewares/deduplication.ts +0 -38
- package/src/router/middlewares/index.ts +0 -3
- package/src/router/middlewares/rateLimit.ts +0 -46
- package/src/routes/__tests__/a2a-test-connection.test.ts +0 -177
- package/src/routes/a2a-bridge.ts +0 -266
- package/src/routes/a2a-test-connection.ts +0 -126
- package/src/routes/a2a.ts +0 -851
- package/src/routes/channel-bindings.ts +0 -11
- package/src/routes/channel-installations.ts +0 -33
- package/src/routes/index.ts +0 -640
- package/src/routes/menu-items.ts +0 -10
- package/src/routes/resource-routes.ts +0 -35
- package/src/routes/stt.ts +0 -102
- package/src/schemas/data-query.ts +0 -69
- package/src/schemas/index.ts +0 -852
- package/src/services/ExportImportService.ts +0 -296
- package/src/services/__tests__/ExportImportService.test.ts +0 -130
- package/src/services/__tests__/eval-runner.test.ts +0 -131
- package/src/services/__tests__/local-a2a.test.ts +0 -124
- package/src/services/agent_task_consumer.ts +0 -348
- package/src/services/collection_service.ts +0 -258
- package/src/services/eval-runner.ts +0 -283
- package/src/services/local-a2a.ts +0 -354
- package/src/services/queue_service.ts +0 -104
- package/src/services/sandbox_service.ts +0 -223
- package/src/services/skill_service.ts +0 -97
- package/src/services/supabase.ts +0 -60
- package/src/swagger.ts +0 -77
- package/src/types/index.ts +0 -118
- package/src/types/koa.d.ts +0 -7
- package/src/utils/__tests__/local-a2a-runtime.test.ts +0 -77
- package/src/utils/__tests__/path-containment.test.ts +0 -35
- package/src/utils/a2a-url.ts +0 -20
- package/src/utils/local-a2a-runtime.ts +0 -107
- package/src/utils/mime.ts +0 -44
- package/src/utils/path-containment.ts +0 -38
- 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
|
-
});
|