@axiom-lattice/gateway 2.1.88 → 2.1.90
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/.turbo/turbo-build.log +14 -12
- package/AGENTS.md +62 -0
- package/CHANGELOG.md +22 -0
- package/dist/a2a-ERG5RMUW.mjs +567 -0
- package/dist/a2a-ERG5RMUW.mjs.map +1 -0
- package/dist/index.d.mts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +879 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +294 -16
- package/dist/index.mjs.map +1 -1
- package/jest.config.js +1 -1
- package/package.json +6 -6
- package/src/__tests__/a2a.test.ts +346 -0
- package/src/index.ts +19 -0
- package/src/router/MessageRouter.ts +169 -17
- package/src/routes/a2a-bridge.ts +266 -0
- package/src/routes/a2a.ts +779 -0
- package/src/routes/index.ts +5 -0
package/dist/index.js
CHANGED
|
@@ -68,6 +68,578 @@ var init_sender = __esm({
|
|
|
68
68
|
}
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
// src/routes/a2a.ts
|
|
72
|
+
var a2a_exports = {};
|
|
73
|
+
__export(a2a_exports, {
|
|
74
|
+
refreshStoreKeyMap: () => refreshStoreKeyMap,
|
|
75
|
+
registerA2ARoutes: () => registerA2ARoutes,
|
|
76
|
+
setA2AKeyStore: () => setA2AKeyStore
|
|
77
|
+
});
|
|
78
|
+
function setA2AKeyStore(store) {
|
|
79
|
+
_a2aKeyStore = store;
|
|
80
|
+
}
|
|
81
|
+
async function refreshStoreKeyMap() {
|
|
82
|
+
if (!_a2aKeyStore) return;
|
|
83
|
+
_storeKeyMap = await _a2aKeyStore.loadIntoMap();
|
|
84
|
+
}
|
|
85
|
+
function parseEnvKeyMap() {
|
|
86
|
+
const keysEnv = process.env.A2A_API_KEYS || "";
|
|
87
|
+
const defaultTenantId = process.env.A2A_DEFAULT_TENANT_ID || "a2a-default-tenant";
|
|
88
|
+
const defaultProjectId = process.env.A2A_DEFAULT_PROJECT_ID || "";
|
|
89
|
+
const defaultWorkspaceId = process.env.A2A_DEFAULT_WORKSPACE_ID || "";
|
|
90
|
+
const map = /* @__PURE__ */ new Map();
|
|
91
|
+
if (keysEnv) {
|
|
92
|
+
keysEnv.split(",").forEach((entry) => {
|
|
93
|
+
const trimmed = entry.trim();
|
|
94
|
+
if (!trimmed) return;
|
|
95
|
+
const parts = trimmed.split(":");
|
|
96
|
+
const key = parts[0];
|
|
97
|
+
if (!key) return;
|
|
98
|
+
map.set(key, {
|
|
99
|
+
key,
|
|
100
|
+
tenantId: parts[1]?.trim() || defaultTenantId,
|
|
101
|
+
projectId: parts[2]?.trim() || defaultProjectId || void 0,
|
|
102
|
+
workspaceId: parts[3]?.trim() || defaultWorkspaceId || void 0
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return map;
|
|
107
|
+
}
|
|
108
|
+
function getA2AConfig() {
|
|
109
|
+
const envMap = parseEnvKeyMap();
|
|
110
|
+
const apiKeyMap = new Map([..._storeKeyMap, ...envMap]);
|
|
111
|
+
return {
|
|
112
|
+
agentName: process.env.A2A_AGENT_NAME || "Axiom Lattice Agent",
|
|
113
|
+
agentDescription: process.env.A2A_AGENT_DESCRIPTION || "AI agent powered by Axiom Lattice framework",
|
|
114
|
+
organization: process.env.A2A_ORGANIZATION || "Axiom Lattice",
|
|
115
|
+
organizationUrl: process.env.A2A_ORGANIZATION_URL || "https://axiom-lattice.ai",
|
|
116
|
+
version: process.env.A2A_VERSION || "1.0.0",
|
|
117
|
+
defaultAssistantId: process.env.A2A_DEFAULT_AGENT_ID || "",
|
|
118
|
+
defaultTenantId: process.env.A2A_DEFAULT_TENANT_ID || "a2a-default-tenant",
|
|
119
|
+
defaultProjectId: process.env.A2A_DEFAULT_PROJECT_ID || "",
|
|
120
|
+
defaultWorkspaceId: process.env.A2A_DEFAULT_WORKSPACE_ID || "",
|
|
121
|
+
apiKeyMap
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function authenticateA2A(request) {
|
|
125
|
+
const config = getA2AConfig();
|
|
126
|
+
const noAuthRequired = config.apiKeyMap.size === 0;
|
|
127
|
+
const token = request.headers.authorization?.startsWith("Bearer ") ? request.headers.authorization.substring(7) : request.headers["x-api-key"];
|
|
128
|
+
if (!token) {
|
|
129
|
+
return {
|
|
130
|
+
authenticated: noAuthRequired,
|
|
131
|
+
tenantId: config.defaultTenantId,
|
|
132
|
+
projectId: config.defaultProjectId || void 0,
|
|
133
|
+
workspaceId: config.defaultWorkspaceId || void 0
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const entry = config.apiKeyMap.get(token);
|
|
137
|
+
if (entry) {
|
|
138
|
+
return {
|
|
139
|
+
authenticated: true,
|
|
140
|
+
apiKey: token,
|
|
141
|
+
tenantId: entry.tenantId,
|
|
142
|
+
projectId: entry.projectId,
|
|
143
|
+
workspaceId: entry.workspaceId,
|
|
144
|
+
source: request.headers.authorization?.startsWith("Bearer ") ? "bearer" : "x-api-key"
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return { authenticated: false };
|
|
148
|
+
}
|
|
149
|
+
function requireAuth(request, reply) {
|
|
150
|
+
const auth = authenticateA2A(request);
|
|
151
|
+
if (!auth.authenticated) {
|
|
152
|
+
reply.status(401).send({
|
|
153
|
+
error: "Unauthorized",
|
|
154
|
+
message: "Valid API key required via Bearer token or X-API-Key header"
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return auth;
|
|
158
|
+
}
|
|
159
|
+
function buildAgentCard(baseUrl) {
|
|
160
|
+
const config = getA2AConfig();
|
|
161
|
+
return {
|
|
162
|
+
name: config.agentName,
|
|
163
|
+
description: config.agentDescription,
|
|
164
|
+
url: `${baseUrl}/api/a2a`,
|
|
165
|
+
provider: {
|
|
166
|
+
organization: config.organization,
|
|
167
|
+
url: config.organizationUrl
|
|
168
|
+
},
|
|
169
|
+
version: config.version,
|
|
170
|
+
capabilities: {
|
|
171
|
+
streaming: true,
|
|
172
|
+
pushNotifications: false,
|
|
173
|
+
stateTransitionHistory: false
|
|
174
|
+
},
|
|
175
|
+
defaultInputModes: ["text", "text/plain"],
|
|
176
|
+
defaultOutputModes: ["text", "text/plain", "text/markdown"],
|
|
177
|
+
skills: []
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function extractTextFromParts(parts) {
|
|
181
|
+
return parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
182
|
+
}
|
|
183
|
+
function buildTextPart(text) {
|
|
184
|
+
return { type: "text", text };
|
|
185
|
+
}
|
|
186
|
+
function isoNow() {
|
|
187
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
188
|
+
}
|
|
189
|
+
async function streamChunksAsA2A(stream, taskId, sendEvent, onInterrupt) {
|
|
190
|
+
let accumulatedText = "";
|
|
191
|
+
for await (const chunk of stream) {
|
|
192
|
+
const chunkText = chunk.data?.content || "";
|
|
193
|
+
const chunkType = chunk.type;
|
|
194
|
+
if (chunkType === import_protocols4.MessageChunkTypes.INTERRUPT) {
|
|
195
|
+
sendEvent("status-update", {
|
|
196
|
+
id: taskId,
|
|
197
|
+
status: {
|
|
198
|
+
state: "input-required",
|
|
199
|
+
message: {
|
|
200
|
+
role: "agent",
|
|
201
|
+
parts: [buildTextPart(accumulatedText)]
|
|
202
|
+
},
|
|
203
|
+
timestamp: isoNow()
|
|
204
|
+
},
|
|
205
|
+
final: false
|
|
206
|
+
});
|
|
207
|
+
onInterrupt?.();
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (chunkType === import_protocols4.MessageChunkTypes.AI || chunkType === import_protocols4.MessageChunkTypes.TOOL) {
|
|
211
|
+
if (chunkText) {
|
|
212
|
+
accumulatedText += chunkText;
|
|
213
|
+
sendEvent("status-update", {
|
|
214
|
+
id: taskId,
|
|
215
|
+
status: {
|
|
216
|
+
state: "working",
|
|
217
|
+
message: {
|
|
218
|
+
role: "agent",
|
|
219
|
+
parts: [buildTextPart(chunkText)]
|
|
220
|
+
},
|
|
221
|
+
timestamp: isoNow()
|
|
222
|
+
},
|
|
223
|
+
final: false
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return accumulatedText;
|
|
229
|
+
}
|
|
230
|
+
async function getTaskStatus(taskId) {
|
|
231
|
+
const record = taskStore.get(taskId);
|
|
232
|
+
if (!record) return null;
|
|
233
|
+
return {
|
|
234
|
+
id: record.id,
|
|
235
|
+
status: {
|
|
236
|
+
state: record.status,
|
|
237
|
+
timestamp: record.updatedAt
|
|
238
|
+
},
|
|
239
|
+
artifacts: [],
|
|
240
|
+
history: record.history
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
async function handleAgentCard(request, reply) {
|
|
244
|
+
const protocol = request.headers["x-forwarded-proto"] || request.protocol;
|
|
245
|
+
const host = request.hostname;
|
|
246
|
+
const baseUrl = `${protocol}://${host}`;
|
|
247
|
+
const card = buildAgentCard(baseUrl);
|
|
248
|
+
reply.status(200).send(card);
|
|
249
|
+
}
|
|
250
|
+
async function handleTasksGet(request, reply) {
|
|
251
|
+
const auth = requireAuth(request, reply);
|
|
252
|
+
if (!auth.authenticated) return;
|
|
253
|
+
const task = await getTaskStatus(request.params.taskId);
|
|
254
|
+
if (!task) {
|
|
255
|
+
reply.status(404).send({ error: "Task not found" });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
reply.status(200).send(task);
|
|
259
|
+
}
|
|
260
|
+
async function handleTasksSend(request, reply) {
|
|
261
|
+
const auth = requireAuth(request, reply);
|
|
262
|
+
if (!auth.authenticated) return;
|
|
263
|
+
const config = getA2AConfig();
|
|
264
|
+
const body = request.body;
|
|
265
|
+
if (!body.message || !body.message.parts || body.message.parts.length === 0) {
|
|
266
|
+
reply.status(400).send({ error: "message.parts is required" });
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const text = extractTextFromParts(body.message.parts);
|
|
270
|
+
if (!text.trim()) {
|
|
271
|
+
reply.status(400).send({ error: "message must contain text content" });
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const assistantId = request.headers["x-a2a-agent-id"] || config.defaultAssistantId;
|
|
275
|
+
if (!assistantId) {
|
|
276
|
+
reply.status(500).send({
|
|
277
|
+
error: "No agent configured",
|
|
278
|
+
message: "Set A2A_DEFAULT_AGENT_ID env var or provide x-a2a-agent-id header"
|
|
279
|
+
});
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const taskId = body.id || (0, import_uuid9.v4)();
|
|
283
|
+
const threadId = (0, import_uuid9.v4)();
|
|
284
|
+
const tenantId = auth.tenantId || config.defaultTenantId;
|
|
285
|
+
const now = isoNow();
|
|
286
|
+
const record = {
|
|
287
|
+
id: taskId,
|
|
288
|
+
threadId,
|
|
289
|
+
assistantId,
|
|
290
|
+
tenantId,
|
|
291
|
+
projectId: auth.projectId || void 0,
|
|
292
|
+
workspaceId: auth.workspaceId || void 0,
|
|
293
|
+
status: "working",
|
|
294
|
+
createdAt: now,
|
|
295
|
+
updatedAt: now,
|
|
296
|
+
history: [
|
|
297
|
+
{
|
|
298
|
+
role: "user",
|
|
299
|
+
parts: body.message.parts
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
};
|
|
303
|
+
taskStore.set(taskId, record);
|
|
304
|
+
let agent;
|
|
305
|
+
try {
|
|
306
|
+
agent = import_core31.agentInstanceManager.getAgent({
|
|
307
|
+
assistant_id: assistantId,
|
|
308
|
+
thread_id: threadId,
|
|
309
|
+
tenant_id: tenantId,
|
|
310
|
+
workspace_id: auth.workspaceId || void 0,
|
|
311
|
+
project_id: auth.projectId || void 0
|
|
312
|
+
});
|
|
313
|
+
} catch (err) {
|
|
314
|
+
record.status = "failed";
|
|
315
|
+
record.updatedAt = isoNow();
|
|
316
|
+
reply.status(500).send({
|
|
317
|
+
id: taskId,
|
|
318
|
+
error: "Failed to initialize agent",
|
|
319
|
+
message: err instanceof Error ? err.message : String(err)
|
|
320
|
+
});
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
reply.hijack();
|
|
324
|
+
reply.raw.writeHead(200, {
|
|
325
|
+
"Content-Type": "text/event-stream",
|
|
326
|
+
"Cache-Control": "no-cache",
|
|
327
|
+
Connection: "keep-alive",
|
|
328
|
+
"Access-Control-Allow-Origin": "*"
|
|
329
|
+
});
|
|
330
|
+
const sendEvent = (event, data) => {
|
|
331
|
+
reply.raw.write(`event: ${event}
|
|
332
|
+
data: ${JSON.stringify(data)}
|
|
333
|
+
|
|
334
|
+
`);
|
|
335
|
+
};
|
|
336
|
+
try {
|
|
337
|
+
const result = await agent.addMessage({
|
|
338
|
+
input: { message: text }
|
|
339
|
+
});
|
|
340
|
+
record.messageId = result.messageId;
|
|
341
|
+
const stream = agent.chunkStream(result.messageId, [
|
|
342
|
+
import_protocols4.MessageChunkTypes.MESSAGE_COMPLETED
|
|
343
|
+
]);
|
|
344
|
+
const accumulatedText = await streamChunksAsA2A(stream, taskId, sendEvent, () => {
|
|
345
|
+
record.status = "input-required";
|
|
346
|
+
record.updatedAt = isoNow();
|
|
347
|
+
});
|
|
348
|
+
record.status = "completed";
|
|
349
|
+
record.updatedAt = isoNow();
|
|
350
|
+
record.history.push({
|
|
351
|
+
role: "agent",
|
|
352
|
+
parts: [buildTextPart(accumulatedText)]
|
|
353
|
+
});
|
|
354
|
+
sendEvent("status-update", {
|
|
355
|
+
id: taskId,
|
|
356
|
+
status: {
|
|
357
|
+
state: "completed",
|
|
358
|
+
timestamp: isoNow()
|
|
359
|
+
},
|
|
360
|
+
final: true
|
|
361
|
+
});
|
|
362
|
+
} catch (err) {
|
|
363
|
+
record.status = "failed";
|
|
364
|
+
record.updatedAt = isoNow();
|
|
365
|
+
sendEvent("error", {
|
|
366
|
+
code: "EXECUTION_ERROR",
|
|
367
|
+
message: err instanceof Error ? err.message : String(err)
|
|
368
|
+
});
|
|
369
|
+
sendEvent("status-update", {
|
|
370
|
+
id: taskId,
|
|
371
|
+
status: {
|
|
372
|
+
state: "failed",
|
|
373
|
+
timestamp: isoNow()
|
|
374
|
+
},
|
|
375
|
+
final: true
|
|
376
|
+
});
|
|
377
|
+
} finally {
|
|
378
|
+
reply.raw.end();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async function handleTasksCancel(request, reply) {
|
|
382
|
+
const auth = requireAuth(request, reply);
|
|
383
|
+
if (!auth.authenticated) return;
|
|
384
|
+
const { taskId } = request.params;
|
|
385
|
+
const record = taskStore.get(taskId);
|
|
386
|
+
if (!record) {
|
|
387
|
+
reply.status(404).send({ error: "Task not found" });
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (record.status === "completed" || record.status === "failed" || record.status === "canceled") {
|
|
391
|
+
reply.status(409).send({
|
|
392
|
+
error: "Task is already in a terminal state",
|
|
393
|
+
task: { id: record.id, status: record.status }
|
|
394
|
+
});
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
const agent = import_core31.agentInstanceManager.getAgent({
|
|
399
|
+
assistant_id: record.assistantId,
|
|
400
|
+
thread_id: record.threadId,
|
|
401
|
+
tenant_id: record.tenantId,
|
|
402
|
+
workspace_id: record.workspaceId,
|
|
403
|
+
project_id: record.projectId
|
|
404
|
+
});
|
|
405
|
+
await agent.abort();
|
|
406
|
+
} catch (err) {
|
|
407
|
+
console.warn({
|
|
408
|
+
event: "a2a:cancel:no_agent",
|
|
409
|
+
taskId,
|
|
410
|
+
threadId: record.threadId,
|
|
411
|
+
error: err instanceof Error ? err.message : String(err)
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
record.status = "canceled";
|
|
415
|
+
record.updatedAt = isoNow();
|
|
416
|
+
reply.status(200).send({
|
|
417
|
+
id: taskId,
|
|
418
|
+
status: {
|
|
419
|
+
state: "canceled",
|
|
420
|
+
timestamp: record.updatedAt
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
async function handleTasksStream(request, reply) {
|
|
425
|
+
const auth = requireAuth(request, reply);
|
|
426
|
+
if (!auth.authenticated) return;
|
|
427
|
+
const { taskId } = request.params;
|
|
428
|
+
const record = taskStore.get(taskId);
|
|
429
|
+
if (!record) {
|
|
430
|
+
reply.status(404).send({ error: "Task not found" });
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
let agent;
|
|
434
|
+
try {
|
|
435
|
+
agent = import_core31.agentInstanceManager.getAgent({
|
|
436
|
+
assistant_id: record.assistantId,
|
|
437
|
+
thread_id: record.threadId,
|
|
438
|
+
tenant_id: record.tenantId,
|
|
439
|
+
workspace_id: record.workspaceId,
|
|
440
|
+
project_id: record.projectId
|
|
441
|
+
});
|
|
442
|
+
} catch {
|
|
443
|
+
reply.status(404).send({
|
|
444
|
+
error: "Agent session not found. The task may have already completed."
|
|
445
|
+
});
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
reply.hijack();
|
|
449
|
+
reply.raw.writeHead(200, {
|
|
450
|
+
"Content-Type": "text/event-stream",
|
|
451
|
+
"Cache-Control": "no-cache",
|
|
452
|
+
Connection: "keep-alive",
|
|
453
|
+
"Access-Control-Allow-Origin": "*"
|
|
454
|
+
});
|
|
455
|
+
const sendEvent = (event, data) => {
|
|
456
|
+
reply.raw.write(`event: ${event}
|
|
457
|
+
data: ${JSON.stringify(data)}
|
|
458
|
+
|
|
459
|
+
`);
|
|
460
|
+
};
|
|
461
|
+
if (!record.messageId) {
|
|
462
|
+
sendEvent("status-update", {
|
|
463
|
+
id: taskId,
|
|
464
|
+
status: {
|
|
465
|
+
state: record.status,
|
|
466
|
+
timestamp: record.updatedAt
|
|
467
|
+
},
|
|
468
|
+
final: true
|
|
469
|
+
});
|
|
470
|
+
reply.raw.end();
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
const stream = agent.chunkStream(record.messageId, [
|
|
475
|
+
import_protocols4.MessageChunkTypes.MESSAGE_COMPLETED
|
|
476
|
+
]);
|
|
477
|
+
await streamChunksAsA2A(stream, taskId, sendEvent);
|
|
478
|
+
sendEvent("status-update", {
|
|
479
|
+
id: taskId,
|
|
480
|
+
status: {
|
|
481
|
+
state: record.status,
|
|
482
|
+
timestamp: record.updatedAt
|
|
483
|
+
},
|
|
484
|
+
final: true
|
|
485
|
+
});
|
|
486
|
+
} catch (err) {
|
|
487
|
+
sendEvent("error", {
|
|
488
|
+
code: "STREAM_ERROR",
|
|
489
|
+
message: err instanceof Error ? err.message : String(err)
|
|
490
|
+
});
|
|
491
|
+
} finally {
|
|
492
|
+
reply.raw.end();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
async function handleApiKeyList(request, reply) {
|
|
496
|
+
const auth = requireAuth(request, reply);
|
|
497
|
+
if (!auth.authenticated) return;
|
|
498
|
+
if (!_a2aKeyStore) {
|
|
499
|
+
reply.status(501).send({ error: "Key store not configured" });
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const records = await _a2aKeyStore.list({
|
|
503
|
+
tenantId: request.query.tenantId,
|
|
504
|
+
limit: request.query.limit ? parseInt(request.query.limit, 10) : void 0,
|
|
505
|
+
offset: request.query.offset ? parseInt(request.query.offset, 10) : void 0
|
|
506
|
+
});
|
|
507
|
+
reply.status(200).send({
|
|
508
|
+
success: true,
|
|
509
|
+
data: {
|
|
510
|
+
records: records.map((r) => ({ ...r, key: r.key.slice(0, 8) + "..." })),
|
|
511
|
+
total: records.length
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
async function handleApiKeyCreate(request, reply) {
|
|
516
|
+
const auth = requireAuth(request, reply);
|
|
517
|
+
if (!auth.authenticated) return;
|
|
518
|
+
if (!_a2aKeyStore) {
|
|
519
|
+
reply.status(501).send({ error: "Key store not configured" });
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const body = request.body;
|
|
523
|
+
const tenantId = body.tenantId || request.headers["x-tenant-id"] || auth.tenantId;
|
|
524
|
+
const projectId = body.projectId || request.headers["x-project-id"] || auth.projectId || "default";
|
|
525
|
+
const workspaceId = body.workspaceId || request.headers["x-workspace-id"] || auth.workspaceId;
|
|
526
|
+
if (!tenantId) {
|
|
527
|
+
reply.status(400).send({
|
|
528
|
+
error: "tenantId is required",
|
|
529
|
+
message: "Provide via body.tenantId, x-tenant-id header, or authenticate with a scoped API key"
|
|
530
|
+
});
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const record = await _a2aKeyStore.create({
|
|
534
|
+
tenantId,
|
|
535
|
+
projectId,
|
|
536
|
+
workspaceId,
|
|
537
|
+
label: body.label
|
|
538
|
+
});
|
|
539
|
+
await refreshStoreKeyMap();
|
|
540
|
+
reply.status(201).send({ success: true, data: record });
|
|
541
|
+
}
|
|
542
|
+
async function handleApiKeyDelete(request, reply) {
|
|
543
|
+
const auth = requireAuth(request, reply);
|
|
544
|
+
if (!auth.authenticated) return;
|
|
545
|
+
if (!_a2aKeyStore) {
|
|
546
|
+
reply.status(501).send({ error: "Key store not configured" });
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
await _a2aKeyStore.delete(request.params.id);
|
|
550
|
+
await refreshStoreKeyMap();
|
|
551
|
+
reply.status(200).send({ success: true });
|
|
552
|
+
}
|
|
553
|
+
async function handleApiKeyDisable(request, reply) {
|
|
554
|
+
const auth = requireAuth(request, reply);
|
|
555
|
+
if (!auth.authenticated) return;
|
|
556
|
+
if (!_a2aKeyStore) {
|
|
557
|
+
reply.status(501).send({ error: "Key store not configured" });
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
await _a2aKeyStore.disable(request.params.id);
|
|
561
|
+
await refreshStoreKeyMap();
|
|
562
|
+
reply.status(200).send({ success: true });
|
|
563
|
+
}
|
|
564
|
+
async function handleApiKeyEnable(request, reply) {
|
|
565
|
+
const auth = requireAuth(request, reply);
|
|
566
|
+
if (!auth.authenticated) return;
|
|
567
|
+
if (!_a2aKeyStore) {
|
|
568
|
+
reply.status(501).send({ error: "Key store not configured" });
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
await _a2aKeyStore.enable(request.params.id);
|
|
572
|
+
await refreshStoreKeyMap();
|
|
573
|
+
reply.status(200).send({ success: true });
|
|
574
|
+
}
|
|
575
|
+
async function handleApiKeyRotate(request, reply) {
|
|
576
|
+
const auth = requireAuth(request, reply);
|
|
577
|
+
if (!auth.authenticated) return;
|
|
578
|
+
if (!_a2aKeyStore) {
|
|
579
|
+
reply.status(501).send({ error: "Key store not configured" });
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const record = await _a2aKeyStore.rotate(request.params.id);
|
|
583
|
+
await refreshStoreKeyMap();
|
|
584
|
+
reply.status(200).send({ success: true, data: record });
|
|
585
|
+
}
|
|
586
|
+
function registerA2ARoutes(app2) {
|
|
587
|
+
app2.get("/api/a2a/.well-known/agent.json", handleAgentCard);
|
|
588
|
+
app2.get("/api/a2a/.well-known/agent-card.json", handleAgentCard);
|
|
589
|
+
app2.post(
|
|
590
|
+
"/api/a2a/tasks/send",
|
|
591
|
+
handleTasksSend
|
|
592
|
+
);
|
|
593
|
+
app2.get(
|
|
594
|
+
"/api/a2a/tasks/:taskId",
|
|
595
|
+
handleTasksGet
|
|
596
|
+
);
|
|
597
|
+
app2.get(
|
|
598
|
+
"/api/a2a/tasks/:taskId/stream",
|
|
599
|
+
handleTasksStream
|
|
600
|
+
);
|
|
601
|
+
app2.post(
|
|
602
|
+
"/api/a2a/tasks/:taskId/cancel",
|
|
603
|
+
handleTasksCancel
|
|
604
|
+
);
|
|
605
|
+
app2.get(
|
|
606
|
+
"/api/a2a/keys",
|
|
607
|
+
handleApiKeyList
|
|
608
|
+
);
|
|
609
|
+
app2.post(
|
|
610
|
+
"/api/a2a/keys",
|
|
611
|
+
handleApiKeyCreate
|
|
612
|
+
);
|
|
613
|
+
app2.delete(
|
|
614
|
+
"/api/a2a/keys/:id",
|
|
615
|
+
handleApiKeyDelete
|
|
616
|
+
);
|
|
617
|
+
app2.post(
|
|
618
|
+
"/api/a2a/keys/:id/disable",
|
|
619
|
+
handleApiKeyDisable
|
|
620
|
+
);
|
|
621
|
+
app2.post(
|
|
622
|
+
"/api/a2a/keys/:id/enable",
|
|
623
|
+
handleApiKeyEnable
|
|
624
|
+
);
|
|
625
|
+
app2.post(
|
|
626
|
+
"/api/a2a/keys/:id/rotate",
|
|
627
|
+
handleApiKeyRotate
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
var import_uuid9, import_core31, import_protocols4, taskStore, _a2aKeyStore, _storeKeyMap;
|
|
631
|
+
var init_a2a = __esm({
|
|
632
|
+
"src/routes/a2a.ts"() {
|
|
633
|
+
"use strict";
|
|
634
|
+
import_uuid9 = require("uuid");
|
|
635
|
+
import_core31 = require("@axiom-lattice/core");
|
|
636
|
+
import_protocols4 = require("@axiom-lattice/protocols");
|
|
637
|
+
taskStore = /* @__PURE__ */ new Map();
|
|
638
|
+
_a2aKeyStore = null;
|
|
639
|
+
_storeKeyMap = /* @__PURE__ */ new Map();
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
|
|
71
643
|
// src/index.ts
|
|
72
644
|
var index_exports = {};
|
|
73
645
|
__export(index_exports, {
|
|
@@ -6464,8 +7036,183 @@ function registerChannelBindingRoutes(app2) {
|
|
|
6464
7036
|
app2.delete("/api/channel-bindings/:id", deleteBinding);
|
|
6465
7037
|
}
|
|
6466
7038
|
|
|
7039
|
+
// src/routes/a2a-bridge.ts
|
|
7040
|
+
var import_uuid8 = require("uuid");
|
|
7041
|
+
var log = {
|
|
7042
|
+
info(msg, ctx) {
|
|
7043
|
+
console.log(JSON.stringify({ level: "info", msg, ...ctx }));
|
|
7044
|
+
},
|
|
7045
|
+
warn(msg, ctx) {
|
|
7046
|
+
console.warn(JSON.stringify({ level: "warn", msg, ...ctx }));
|
|
7047
|
+
},
|
|
7048
|
+
error(msg, ctx) {
|
|
7049
|
+
console.error(JSON.stringify({ level: "error", msg, ...ctx }));
|
|
7050
|
+
}
|
|
7051
|
+
};
|
|
7052
|
+
var connections = /* @__PURE__ */ new Map();
|
|
7053
|
+
var pending = /* @__PURE__ */ new Map();
|
|
7054
|
+
function getAgentIdFromPath(path3) {
|
|
7055
|
+
const match = path3.match(/^\/api\/a2a\/bridge\/proxy\/([^/]+)/);
|
|
7056
|
+
return match ? match[1] : null;
|
|
7057
|
+
}
|
|
7058
|
+
function handleBridgeConnection(socket, _request) {
|
|
7059
|
+
let agentId = null;
|
|
7060
|
+
log.info("Bridge WebSocket connected");
|
|
7061
|
+
socket.on("message", (raw) => {
|
|
7062
|
+
let msg;
|
|
7063
|
+
try {
|
|
7064
|
+
msg = JSON.parse(raw.toString());
|
|
7065
|
+
} catch {
|
|
7066
|
+
log.warn("Bridge invalid message");
|
|
7067
|
+
return;
|
|
7068
|
+
}
|
|
7069
|
+
switch (msg.type) {
|
|
7070
|
+
case "agent:register": {
|
|
7071
|
+
agentId = msg.agentId;
|
|
7072
|
+
if (!agentId) {
|
|
7073
|
+
socket.send(JSON.stringify({ type: "error", message: "agentId required" }));
|
|
7074
|
+
socket.close();
|
|
7075
|
+
return;
|
|
7076
|
+
}
|
|
7077
|
+
connections.set(agentId, {
|
|
7078
|
+
ws: socket,
|
|
7079
|
+
agentId,
|
|
7080
|
+
agentCard: msg.agentCard ?? {},
|
|
7081
|
+
connectedAt: Date.now()
|
|
7082
|
+
});
|
|
7083
|
+
log.info("Agent registered via bridge", { agentId });
|
|
7084
|
+
socket.send(JSON.stringify({ type: "agent:registered", agentId }));
|
|
7085
|
+
break;
|
|
7086
|
+
}
|
|
7087
|
+
case "http:response": {
|
|
7088
|
+
const requestId = msg.requestId;
|
|
7089
|
+
const p = pending.get(requestId);
|
|
7090
|
+
if (p) {
|
|
7091
|
+
clearTimeout(p.timer);
|
|
7092
|
+
pending.delete(requestId);
|
|
7093
|
+
p.resolve({
|
|
7094
|
+
status: msg.status ?? 200,
|
|
7095
|
+
headers: msg.headers ?? {},
|
|
7096
|
+
body: msg.body ?? ""
|
|
7097
|
+
});
|
|
7098
|
+
}
|
|
7099
|
+
break;
|
|
7100
|
+
}
|
|
7101
|
+
case "http:error": {
|
|
7102
|
+
const requestId = msg.requestId;
|
|
7103
|
+
const p = pending.get(requestId);
|
|
7104
|
+
if (p) {
|
|
7105
|
+
clearTimeout(p.timer);
|
|
7106
|
+
pending.delete(requestId);
|
|
7107
|
+
p.reject(new Error(msg.message ?? "Agent error"));
|
|
7108
|
+
}
|
|
7109
|
+
break;
|
|
7110
|
+
}
|
|
7111
|
+
}
|
|
7112
|
+
});
|
|
7113
|
+
socket.on("close", () => {
|
|
7114
|
+
if (agentId) {
|
|
7115
|
+
connections.delete(agentId);
|
|
7116
|
+
log.info("Bridge WebSocket disconnected", { agentId });
|
|
7117
|
+
for (const [reqId, p] of pending) {
|
|
7118
|
+
if (p.agentId === agentId) {
|
|
7119
|
+
clearTimeout(p.timer);
|
|
7120
|
+
p.reject(new Error("Agent disconnected"));
|
|
7121
|
+
pending.delete(reqId);
|
|
7122
|
+
}
|
|
7123
|
+
}
|
|
7124
|
+
}
|
|
7125
|
+
});
|
|
7126
|
+
socket.on("error", (err) => {
|
|
7127
|
+
log.error("Bridge WebSocket error", { agentId, error: err.message });
|
|
7128
|
+
});
|
|
7129
|
+
}
|
|
7130
|
+
async function handleBridgeProxy(request, reply) {
|
|
7131
|
+
const agentId = getAgentIdFromPath(request.url);
|
|
7132
|
+
if (!agentId) {
|
|
7133
|
+
reply.status(400).send({ error: "Agent ID not found in path" });
|
|
7134
|
+
return;
|
|
7135
|
+
}
|
|
7136
|
+
const conn = connections.get(agentId);
|
|
7137
|
+
if (!conn) {
|
|
7138
|
+
reply.status(503).send({
|
|
7139
|
+
error: "Agent not connected",
|
|
7140
|
+
message: `Agent "${agentId}" is not connected via WebSocket bridge`
|
|
7141
|
+
});
|
|
7142
|
+
return;
|
|
7143
|
+
}
|
|
7144
|
+
const requestId = (0, import_uuid8.v4)();
|
|
7145
|
+
const subPath = request.url.replace(`/api/a2a/bridge/proxy/${agentId}`, "") || "/";
|
|
7146
|
+
let body;
|
|
7147
|
+
if (request.method === "POST" || request.method === "PUT") {
|
|
7148
|
+
body = typeof request.body === "string" ? request.body : JSON.stringify(request.body ?? {});
|
|
7149
|
+
}
|
|
7150
|
+
const bridgeRequest = {
|
|
7151
|
+
type: "http:request",
|
|
7152
|
+
requestId,
|
|
7153
|
+
method: request.method,
|
|
7154
|
+
path: subPath,
|
|
7155
|
+
headers: {
|
|
7156
|
+
"content-type": request.headers["content-type"] ?? "application/json",
|
|
7157
|
+
"accept": request.headers["accept"] ?? "*/*"
|
|
7158
|
+
},
|
|
7159
|
+
body: body ?? ""
|
|
7160
|
+
};
|
|
7161
|
+
const timeout = 3e5;
|
|
7162
|
+
try {
|
|
7163
|
+
const response = await new Promise((resolve, reject) => {
|
|
7164
|
+
const timer = setTimeout(() => {
|
|
7165
|
+
pending.delete(requestId);
|
|
7166
|
+
reject(new Error(`Bridge request timeout after ${timeout}ms`));
|
|
7167
|
+
}, timeout);
|
|
7168
|
+
pending.set(requestId, { resolve, reject, timer, agentId });
|
|
7169
|
+
try {
|
|
7170
|
+
if (conn.ws.readyState !== conn.ws.OPEN) {
|
|
7171
|
+
pending.delete(requestId);
|
|
7172
|
+
reject(new Error("WebSocket not open"));
|
|
7173
|
+
return;
|
|
7174
|
+
}
|
|
7175
|
+
conn.ws.send(JSON.stringify(bridgeRequest));
|
|
7176
|
+
} catch (err) {
|
|
7177
|
+
clearTimeout(timer);
|
|
7178
|
+
pending.delete(requestId);
|
|
7179
|
+
reject(err);
|
|
7180
|
+
}
|
|
7181
|
+
});
|
|
7182
|
+
for (const [key, value] of Object.entries(response.headers)) {
|
|
7183
|
+
if (!["content-length", "transfer-encoding", "connection"].includes(key.toLowerCase())) {
|
|
7184
|
+
reply.header(key, value);
|
|
7185
|
+
}
|
|
7186
|
+
}
|
|
7187
|
+
reply.status(response.status);
|
|
7188
|
+
try {
|
|
7189
|
+
const parsed = JSON.parse(response.body);
|
|
7190
|
+
reply.send(parsed);
|
|
7191
|
+
} catch {
|
|
7192
|
+
reply.send(response.body);
|
|
7193
|
+
}
|
|
7194
|
+
} catch (err) {
|
|
7195
|
+
log.error("Bridge proxy error", { agentId, error: err.message });
|
|
7196
|
+
reply.status(502).send({
|
|
7197
|
+
error: "Bridge proxy error",
|
|
7198
|
+
message: err.message
|
|
7199
|
+
});
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
7202
|
+
function registerA2ABridgeRoutes(app2) {
|
|
7203
|
+
app2.get("/api/a2a/bridge", { websocket: true }, (socket, req) => {
|
|
7204
|
+
handleBridgeConnection(socket, req);
|
|
7205
|
+
});
|
|
7206
|
+
app2.route({
|
|
7207
|
+
method: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
7208
|
+
url: "/api/a2a/bridge/proxy/*",
|
|
7209
|
+
handler: handleBridgeProxy
|
|
7210
|
+
});
|
|
7211
|
+
}
|
|
7212
|
+
|
|
6467
7213
|
// src/routes/index.ts
|
|
6468
7214
|
var registerLatticeRoutes = (app2, channelDeps) => {
|
|
7215
|
+
registerA2ABridgeRoutes(app2);
|
|
6469
7216
|
app2.post("/api/runs", createRun);
|
|
6470
7217
|
app2.post("/api/resume_stream", resumeStream);
|
|
6471
7218
|
app2.post("/api/assistants/:assistantId/threads/:threadId/abort", abortRun);
|
|
@@ -6689,14 +7436,39 @@ var BindingNotFoundError = class extends Error {
|
|
|
6689
7436
|
};
|
|
6690
7437
|
var MessageRouter = class {
|
|
6691
7438
|
constructor(config) {
|
|
7439
|
+
/**
|
|
7440
|
+
* Tracks reply subscriptions per thread+channel to avoid duplicate
|
|
7441
|
+
* `subscribeOnce` registrations and ensure proper cleanup.
|
|
7442
|
+
*
|
|
7443
|
+
* Key format: `{threadId}:{adapterChannel}:reply`
|
|
7444
|
+
*/
|
|
7445
|
+
this._replySubs = /* @__PURE__ */ new Map();
|
|
6692
7446
|
this.middlewares = [...config.middlewares];
|
|
6693
7447
|
this.bindingRegistry = config.bindingRegistry;
|
|
6694
7448
|
this.adapterRegistry = config.adapterRegistry;
|
|
6695
7449
|
this.installationStore = config.installationStore;
|
|
6696
7450
|
}
|
|
7451
|
+
/**
|
|
7452
|
+
* Register an additional middleware at the end of the chain.
|
|
7453
|
+
*
|
|
7454
|
+
* @param middleware - A {@link MessageMiddleware} function
|
|
7455
|
+
*/
|
|
6697
7456
|
use(middleware) {
|
|
6698
7457
|
this.middlewares.push(middleware);
|
|
6699
7458
|
}
|
|
7459
|
+
/**
|
|
7460
|
+
* Dispatch an inbound channel message to the bound agent.
|
|
7461
|
+
*
|
|
7462
|
+
* Full pipeline: middleware chain → binding resolution → thread lifecycle
|
|
7463
|
+
* → agent.addMessage() → (async) reply via {@link ChannelAdapter.sendReply}.
|
|
7464
|
+
*
|
|
7465
|
+
* If the message has a {@link InboundMessage.replyTarget}, the router subscribes
|
|
7466
|
+
* to the agent's `reply:ready` event before enqueuing the message, and sends
|
|
7467
|
+
* the AI response back to the channel when it arrives.
|
|
7468
|
+
*
|
|
7469
|
+
* @param message - Normalized inbound message from a channel adapter
|
|
7470
|
+
* @returns {@link DispatchResult} with success status, bindingId, and threadId
|
|
7471
|
+
*/
|
|
6700
7472
|
async dispatch(message) {
|
|
6701
7473
|
const ctx = {
|
|
6702
7474
|
inboundMessage: message,
|
|
@@ -6843,32 +7615,96 @@ var MessageRouter = class {
|
|
|
6843
7615
|
workspace_id: ctx.binding.workspaceId || "",
|
|
6844
7616
|
project_id: ctx.binding.projectId || ""
|
|
6845
7617
|
});
|
|
6846
|
-
const addResult = await agent.addMessage({
|
|
6847
|
-
input: { message: message.content.text },
|
|
6848
|
-
custom_run_config: message.content.metadata || {}
|
|
6849
|
-
});
|
|
6850
|
-
console.log({
|
|
6851
|
-
event: "dispatch:complete",
|
|
6852
|
-
agentId: ctx.binding.agentId,
|
|
6853
|
-
threadId,
|
|
6854
|
-
messageId: addResult?.messageId,
|
|
6855
|
-
result: JSON.stringify(addResult)
|
|
6856
|
-
}, "Agent dispatch complete \u2014 messageId = " + (addResult?.messageId || "N/A"));
|
|
6857
7618
|
if (message.replyTarget) {
|
|
7619
|
+
const replySubKey = `${threadId}:${message.replyTarget.adapterChannel}:reply`;
|
|
6858
7620
|
const adapter = this.adapterRegistry.get(message.replyTarget.adapterChannel);
|
|
6859
7621
|
if (adapter) {
|
|
6860
7622
|
const installation = await this.installationStore.getInstallationById(
|
|
6861
7623
|
message.channelInstallationId
|
|
6862
7624
|
);
|
|
6863
7625
|
if (installation) {
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
7626
|
+
const existing = this._replySubs.get(replySubKey);
|
|
7627
|
+
if (!existing || existing.count === 0) {
|
|
7628
|
+
const timer = setTimeout(() => {
|
|
7629
|
+
const entry = this._replySubs.get(replySubKey);
|
|
7630
|
+
if (entry) {
|
|
7631
|
+
this._replySubs.delete(replySubKey);
|
|
7632
|
+
console.warn({
|
|
7633
|
+
event: "dispatch:reply:timeout",
|
|
7634
|
+
threadId,
|
|
7635
|
+
channel: message.replyTarget.adapterChannel
|
|
7636
|
+
}, "Reply subscription timed out \u2014 no reply:ready fired within 1h");
|
|
7637
|
+
}
|
|
7638
|
+
}, 36e5);
|
|
7639
|
+
this._replySubs.set(replySubKey, { count: 1, timer });
|
|
7640
|
+
console.log({
|
|
7641
|
+
event: "dispatch:reply:subscribed",
|
|
7642
|
+
threadId,
|
|
7643
|
+
channel: message.replyTarget.adapterChannel,
|
|
7644
|
+
senderId: message.sender.id
|
|
7645
|
+
}, "Subscribed to reply:ready for thread");
|
|
7646
|
+
agent.subscribeOnce("reply:ready", (data) => {
|
|
7647
|
+
const messages = data.state?.values?.messages;
|
|
7648
|
+
const lastAI = messages?.filter((m) => m.type === "ai" || m.getType?.() === "ai").pop();
|
|
7649
|
+
const replyText = lastAI?.content ?? "";
|
|
7650
|
+
const entry = this._replySubs.get(replySubKey);
|
|
7651
|
+
if (entry) {
|
|
7652
|
+
entry.count--;
|
|
7653
|
+
if (entry.count <= 0) {
|
|
7654
|
+
clearTimeout(entry.timer);
|
|
7655
|
+
this._replySubs.delete(replySubKey);
|
|
7656
|
+
}
|
|
7657
|
+
}
|
|
7658
|
+
if (replyText) {
|
|
7659
|
+
console.log({
|
|
7660
|
+
event: "dispatch:reply:sending",
|
|
7661
|
+
threadId,
|
|
7662
|
+
channel: message.replyTarget.adapterChannel,
|
|
7663
|
+
replyLength: replyText.length
|
|
7664
|
+
}, "Sending channel reply");
|
|
7665
|
+
adapter.sendReply(message.replyTarget, { text: replyText }, installation).then(() => {
|
|
7666
|
+
console.log({
|
|
7667
|
+
event: "dispatch:reply:sent",
|
|
7668
|
+
threadId,
|
|
7669
|
+
channel: message.replyTarget.adapterChannel
|
|
7670
|
+
}, "Channel reply sent successfully");
|
|
7671
|
+
}).catch((err) => console.error({
|
|
7672
|
+
event: "dispatch:reply:failed",
|
|
7673
|
+
threadId,
|
|
7674
|
+
channel: message.replyTarget.adapterChannel,
|
|
7675
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7676
|
+
}, "Failed to send channel reply"));
|
|
7677
|
+
} else {
|
|
7678
|
+
console.warn({
|
|
7679
|
+
event: "dispatch:reply:empty",
|
|
7680
|
+
threadId,
|
|
7681
|
+
channel: message.replyTarget.adapterChannel
|
|
7682
|
+
}, "Agent produced no text output \u2014 skipping reply");
|
|
7683
|
+
}
|
|
7684
|
+
});
|
|
7685
|
+
} else {
|
|
7686
|
+
existing.count++;
|
|
7687
|
+
console.log({
|
|
7688
|
+
event: "dispatch:reply:incremented",
|
|
7689
|
+
threadId,
|
|
7690
|
+
channel: message.replyTarget.adapterChannel,
|
|
7691
|
+
count: existing.count
|
|
7692
|
+
}, "Incremented reply counter for thread (already subscribed)");
|
|
7693
|
+
}
|
|
6869
7694
|
}
|
|
6870
7695
|
}
|
|
6871
7696
|
}
|
|
7697
|
+
const addResult = await agent.addMessage({
|
|
7698
|
+
input: { message: message.content.text },
|
|
7699
|
+
custom_run_config: message.replyTarget ? { ...message.content.metadata || {}, _replyTarget: message.replyTarget } : message.content.metadata || {}
|
|
7700
|
+
});
|
|
7701
|
+
console.log({
|
|
7702
|
+
event: "dispatch:complete",
|
|
7703
|
+
agentId: ctx.binding.agentId,
|
|
7704
|
+
threadId,
|
|
7705
|
+
messageId: addResult?.messageId,
|
|
7706
|
+
result: JSON.stringify(addResult)
|
|
7707
|
+
}, "Agent dispatch complete \u2014 messageId = " + (addResult?.messageId || "N/A"));
|
|
6872
7708
|
});
|
|
6873
7709
|
return {
|
|
6874
7710
|
success: true,
|
|
@@ -7016,7 +7852,7 @@ function createAuditLoggerMiddleware() {
|
|
|
7016
7852
|
}
|
|
7017
7853
|
|
|
7018
7854
|
// src/index.ts
|
|
7019
|
-
var
|
|
7855
|
+
var import_core32 = require("@axiom-lattice/core");
|
|
7020
7856
|
|
|
7021
7857
|
// src/swagger.ts
|
|
7022
7858
|
var import_swagger = __toESM(require("@fastify/swagger"));
|
|
@@ -7339,8 +8175,8 @@ _AgentTaskConsumer.agent_run_endpoint = "http://localhost:4001/api/runs";
|
|
|
7339
8175
|
var AgentTaskConsumer = _AgentTaskConsumer;
|
|
7340
8176
|
|
|
7341
8177
|
// src/index.ts
|
|
7342
|
-
var
|
|
7343
|
-
var
|
|
8178
|
+
var import_core33 = require("@axiom-lattice/core");
|
|
8179
|
+
var import_protocols5 = require("@axiom-lattice/protocols");
|
|
7344
8180
|
var import_meta = {};
|
|
7345
8181
|
process.on("unhandledRejection", (reason, promise) => {
|
|
7346
8182
|
console.error("\u672A\u5904\u7406\u7684Promise\u62D2\u7EDD:", reason);
|
|
@@ -7348,18 +8184,18 @@ process.on("unhandledRejection", (reason, promise) => {
|
|
|
7348
8184
|
var DEFAULT_LOGGER_CONFIG = {
|
|
7349
8185
|
name: "default",
|
|
7350
8186
|
description: "Default logger for lattice-gateway service",
|
|
7351
|
-
type:
|
|
8187
|
+
type: import_protocols5.LoggerType.PINO,
|
|
7352
8188
|
serviceName: "lattice/gateway",
|
|
7353
8189
|
loggerName: "lattice/gateway"
|
|
7354
8190
|
};
|
|
7355
8191
|
var loggerLattice = initializeLogger(DEFAULT_LOGGER_CONFIG);
|
|
7356
8192
|
var logger3 = loggerLattice.client;
|
|
7357
8193
|
function initializeLogger(config) {
|
|
7358
|
-
if (
|
|
7359
|
-
|
|
8194
|
+
if (import_core33.loggerLatticeManager.hasLattice("default")) {
|
|
8195
|
+
import_core33.loggerLatticeManager.removeLattice("default");
|
|
7360
8196
|
}
|
|
7361
|
-
(0,
|
|
7362
|
-
return (0,
|
|
8197
|
+
(0, import_core33.registerLoggerLattice)("default", config);
|
|
8198
|
+
return (0, import_core33.getLoggerLattice)("default");
|
|
7363
8199
|
}
|
|
7364
8200
|
var app = (0, import_fastify.default)({
|
|
7365
8201
|
logger: false,
|
|
@@ -7401,6 +8237,12 @@ app.addHook("preHandler", async (request, reply) => {
|
|
|
7401
8237
|
error: "Unauthorized - Missing or invalid token"
|
|
7402
8238
|
});
|
|
7403
8239
|
});
|
|
8240
|
+
app.addHook("onRequest", (request, reply, done) => {
|
|
8241
|
+
if (!request.headers["x-project-id"]) {
|
|
8242
|
+
request.headers["x-project-id"] = "default";
|
|
8243
|
+
}
|
|
8244
|
+
done();
|
|
8245
|
+
});
|
|
7404
8246
|
app.addHook("onRequest", (request, reply, done) => {
|
|
7405
8247
|
const context = {
|
|
7406
8248
|
"x-tenant-id": getHeaderValue(request.headers["x-tenant-id"]),
|
|
@@ -7462,7 +8304,7 @@ app.setErrorHandler((error, request, reply) => {
|
|
|
7462
8304
|
});
|
|
7463
8305
|
function getConfiguredSandboxProvider() {
|
|
7464
8306
|
const sandboxProviderType = process.env.SANDBOX_PROVIDER_TYPE || "microsandbox-remote";
|
|
7465
|
-
return (0,
|
|
8307
|
+
return (0, import_core33.createSandboxProvider)({
|
|
7466
8308
|
type: sandboxProviderType,
|
|
7467
8309
|
remoteBaseURL: process.env.SANDBOX_BASE_URL,
|
|
7468
8310
|
microsandboxServiceBaseURL: process.env.MICROSANDBOX_SERVICE_BASE_URL,
|
|
@@ -7494,7 +8336,7 @@ var start = async (config) => {
|
|
|
7494
8336
|
const { getStoreLattice: getStoreLattice16 } = await import("@axiom-lattice/core");
|
|
7495
8337
|
const bindingStore = getStoreLattice16("default", "channelBinding").store;
|
|
7496
8338
|
const installationStore = getStoreLattice16("default", "channelInstallation").store;
|
|
7497
|
-
(0,
|
|
8339
|
+
(0, import_core32.setBindingRegistry)(bindingStore);
|
|
7498
8340
|
const adapterRegistry = new ChannelAdapterRegistry();
|
|
7499
8341
|
adapterRegistry.register(larkChannelAdapter);
|
|
7500
8342
|
const router = new MessageRouter({
|
|
@@ -7508,11 +8350,19 @@ var start = async (config) => {
|
|
|
7508
8350
|
installationStore
|
|
7509
8351
|
});
|
|
7510
8352
|
channelDeps = { router, installationStore };
|
|
8353
|
+
try {
|
|
8354
|
+
const a2aKeyStore = getStoreLattice16("default", "a2aApiKey").store;
|
|
8355
|
+
const a2a = await Promise.resolve().then(() => (init_a2a(), a2a_exports));
|
|
8356
|
+
a2a.setA2AKeyStore(a2aKeyStore);
|
|
8357
|
+
await a2a.refreshStoreKeyMap();
|
|
8358
|
+
logger3.info("A2A key store initialized");
|
|
8359
|
+
} catch {
|
|
8360
|
+
}
|
|
7511
8361
|
} catch {
|
|
7512
8362
|
}
|
|
7513
8363
|
registerLatticeRoutes(app, channelDeps);
|
|
7514
|
-
if (!
|
|
7515
|
-
|
|
8364
|
+
if (!import_core33.sandboxLatticeManager.hasLattice("default")) {
|
|
8365
|
+
import_core33.sandboxLatticeManager.registerLattice("default", getConfiguredSandboxProvider());
|
|
7516
8366
|
logger3.info("Registered sandbox manager from env configuration");
|
|
7517
8367
|
}
|
|
7518
8368
|
const target_port = config?.port || Number(process.env.PORT) || 4001;
|
|
@@ -7533,7 +8383,7 @@ var start = async (config) => {
|
|
|
7533
8383
|
}
|
|
7534
8384
|
try {
|
|
7535
8385
|
logger3.info("Starting agent instance recovery...");
|
|
7536
|
-
const restoreStats = await
|
|
8386
|
+
const restoreStats = await import_core33.agentInstanceManager.restore();
|
|
7537
8387
|
logger3.info(`Agent recovery complete: ${restoreStats.restored} threads restored, ${restoreStats.errors} errors`);
|
|
7538
8388
|
} catch (error) {
|
|
7539
8389
|
logger3.error("Agent recovery failed", { error });
|