@axiom-lattice/gateway 2.1.89 → 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/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);
@@ -7105,7 +7852,7 @@ function createAuditLoggerMiddleware() {
7105
7852
  }
7106
7853
 
7107
7854
  // src/index.ts
7108
- var import_core31 = require("@axiom-lattice/core");
7855
+ var import_core32 = require("@axiom-lattice/core");
7109
7856
 
7110
7857
  // src/swagger.ts
7111
7858
  var import_swagger = __toESM(require("@fastify/swagger"));
@@ -7428,8 +8175,8 @@ _AgentTaskConsumer.agent_run_endpoint = "http://localhost:4001/api/runs";
7428
8175
  var AgentTaskConsumer = _AgentTaskConsumer;
7429
8176
 
7430
8177
  // src/index.ts
7431
- var import_core32 = require("@axiom-lattice/core");
7432
- var import_protocols4 = require("@axiom-lattice/protocols");
8178
+ var import_core33 = require("@axiom-lattice/core");
8179
+ var import_protocols5 = require("@axiom-lattice/protocols");
7433
8180
  var import_meta = {};
7434
8181
  process.on("unhandledRejection", (reason, promise) => {
7435
8182
  console.error("\u672A\u5904\u7406\u7684Promise\u62D2\u7EDD:", reason);
@@ -7437,18 +8184,18 @@ process.on("unhandledRejection", (reason, promise) => {
7437
8184
  var DEFAULT_LOGGER_CONFIG = {
7438
8185
  name: "default",
7439
8186
  description: "Default logger for lattice-gateway service",
7440
- type: import_protocols4.LoggerType.PINO,
8187
+ type: import_protocols5.LoggerType.PINO,
7441
8188
  serviceName: "lattice/gateway",
7442
8189
  loggerName: "lattice/gateway"
7443
8190
  };
7444
8191
  var loggerLattice = initializeLogger(DEFAULT_LOGGER_CONFIG);
7445
8192
  var logger3 = loggerLattice.client;
7446
8193
  function initializeLogger(config) {
7447
- if (import_core32.loggerLatticeManager.hasLattice("default")) {
7448
- import_core32.loggerLatticeManager.removeLattice("default");
8194
+ if (import_core33.loggerLatticeManager.hasLattice("default")) {
8195
+ import_core33.loggerLatticeManager.removeLattice("default");
7449
8196
  }
7450
- (0, import_core32.registerLoggerLattice)("default", config);
7451
- return (0, import_core32.getLoggerLattice)("default");
8197
+ (0, import_core33.registerLoggerLattice)("default", config);
8198
+ return (0, import_core33.getLoggerLattice)("default");
7452
8199
  }
7453
8200
  var app = (0, import_fastify.default)({
7454
8201
  logger: false,
@@ -7490,6 +8237,12 @@ app.addHook("preHandler", async (request, reply) => {
7490
8237
  error: "Unauthorized - Missing or invalid token"
7491
8238
  });
7492
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
+ });
7493
8246
  app.addHook("onRequest", (request, reply, done) => {
7494
8247
  const context = {
7495
8248
  "x-tenant-id": getHeaderValue(request.headers["x-tenant-id"]),
@@ -7551,7 +8304,7 @@ app.setErrorHandler((error, request, reply) => {
7551
8304
  });
7552
8305
  function getConfiguredSandboxProvider() {
7553
8306
  const sandboxProviderType = process.env.SANDBOX_PROVIDER_TYPE || "microsandbox-remote";
7554
- return (0, import_core32.createSandboxProvider)({
8307
+ return (0, import_core33.createSandboxProvider)({
7555
8308
  type: sandboxProviderType,
7556
8309
  remoteBaseURL: process.env.SANDBOX_BASE_URL,
7557
8310
  microsandboxServiceBaseURL: process.env.MICROSANDBOX_SERVICE_BASE_URL,
@@ -7583,7 +8336,7 @@ var start = async (config) => {
7583
8336
  const { getStoreLattice: getStoreLattice16 } = await import("@axiom-lattice/core");
7584
8337
  const bindingStore = getStoreLattice16("default", "channelBinding").store;
7585
8338
  const installationStore = getStoreLattice16("default", "channelInstallation").store;
7586
- (0, import_core31.setBindingRegistry)(bindingStore);
8339
+ (0, import_core32.setBindingRegistry)(bindingStore);
7587
8340
  const adapterRegistry = new ChannelAdapterRegistry();
7588
8341
  adapterRegistry.register(larkChannelAdapter);
7589
8342
  const router = new MessageRouter({
@@ -7597,11 +8350,19 @@ var start = async (config) => {
7597
8350
  installationStore
7598
8351
  });
7599
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
+ }
7600
8361
  } catch {
7601
8362
  }
7602
8363
  registerLatticeRoutes(app, channelDeps);
7603
- if (!import_core32.sandboxLatticeManager.hasLattice("default")) {
7604
- import_core32.sandboxLatticeManager.registerLattice("default", getConfiguredSandboxProvider());
8364
+ if (!import_core33.sandboxLatticeManager.hasLattice("default")) {
8365
+ import_core33.sandboxLatticeManager.registerLattice("default", getConfiguredSandboxProvider());
7605
8366
  logger3.info("Registered sandbox manager from env configuration");
7606
8367
  }
7607
8368
  const target_port = config?.port || Number(process.env.PORT) || 4001;
@@ -7622,7 +8383,7 @@ var start = async (config) => {
7622
8383
  }
7623
8384
  try {
7624
8385
  logger3.info("Starting agent instance recovery...");
7625
- const restoreStats = await import_core32.agentInstanceManager.restore();
8386
+ const restoreStats = await import_core33.agentInstanceManager.restore();
7626
8387
  logger3.info(`Agent recovery complete: ${restoreStats.restored} threads restored, ${restoreStats.errors} errors`);
7627
8388
  } catch (error) {
7628
8389
  logger3.error("Agent recovery failed", { error });