@axiom-lattice/gateway 2.1.89 → 2.1.91

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @axiom-lattice/gateway@2.1.89 build /home/runner/work/agentic/agentic/packages/gateway
2
+ > @axiom-lattice/gateway@2.1.91 build /home/runner/work/agentic/agentic/packages/gateway
3
3
  > tsup src/index.ts --format cjs,esm --dts --clean --sourcemap
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -11,22 +11,24 @@
11
11
  ESM Build start
12
12
  [warn] ▲ [WARNING] "import.meta" is not available with the "cjs" output format and will be empty [empty-import-meta]
13
13
 
14
- src/index.ts:175:33:
15
-  175 │ const __filename = fileURLToPath(import.meta.url);
14
+ src/index.ts:183:33:
15
+  183 │ const __filename = fileURLToPath(import.meta.url);
16
16
  ╵ ~~~~~~~~~~~
17
17
 
18
18
  You need to set the output format to "esm" for "import.meta" to work correctly.
19
19
 
20
20
 
21
- CJS dist/index.js 246.20 KB
22
- CJS dist/index.js.map 515.19 KB
23
- CJS ⚡️ Build success in 415ms
24
- ESM dist/index.mjs 241.30 KB
21
+ CJS dist/index.js 268.14 KB
22
+ CJS dist/index.js.map 560.68 KB
23
+ CJS ⚡️ Build success in 480ms
24
+ ESM dist/index.mjs 246.84 KB
25
25
  ESM dist/sender-PX32VSHB.mjs 873.00 B
26
+ ESM dist/a2a-ERG5RMUW.mjs 15.95 KB
26
27
  ESM dist/sender-PX32VSHB.mjs.map 2.07 KB
27
- ESM dist/index.mjs.map 513.63 KB
28
- ESM ⚡️ Build success in 415ms
28
+ ESM dist/a2a-ERG5RMUW.mjs.map 32.14 KB
29
+ ESM dist/index.mjs.map 526.83 KB
30
+ ESM ⚡️ Build success in 483ms
29
31
  DTS Build start
30
- DTS ⚡️ Build success in 14563ms
32
+ DTS ⚡️ Build success in 14738ms
31
33
  DTS dist/index.d.ts 7.57 KB
32
34
  DTS dist/index.d.mts 7.57 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @axiom-lattice/gateway
2
2
 
3
+ ## 2.1.91
4
+
5
+ ### Patch Changes
6
+
7
+ - ec290af: sqlit store
8
+ - Updated dependencies [ec290af]
9
+ - @axiom-lattice/core@2.1.79
10
+ - @axiom-lattice/protocols@2.1.41
11
+ - @axiom-lattice/agent-eval@2.1.73
12
+ - @axiom-lattice/pg-stores@1.0.70
13
+ - @axiom-lattice/queue-redis@1.0.40
14
+
15
+ ## 2.1.90
16
+
17
+ ### Patch Changes
18
+
19
+ - b7cd409: enhance more features a2a, cli
20
+ - Updated dependencies [b7cd409]
21
+ - @axiom-lattice/pg-stores@1.0.69
22
+ - @axiom-lattice/protocols@2.1.40
23
+ - @axiom-lattice/core@2.1.78
24
+ - @axiom-lattice/agent-eval@2.1.72
25
+ - @axiom-lattice/queue-redis@1.0.39
26
+
3
27
  ## 2.1.89
4
28
 
5
29
  ### Patch Changes
@@ -0,0 +1,567 @@
1
+ // src/routes/a2a.ts
2
+ import { v4 } from "uuid";
3
+ import {
4
+ agentInstanceManager
5
+ } from "@axiom-lattice/core";
6
+ import { MessageChunkTypes } from "@axiom-lattice/protocols";
7
+ var taskStore = /* @__PURE__ */ new Map();
8
+ var _a2aKeyStore = null;
9
+ var _storeKeyMap = /* @__PURE__ */ new Map();
10
+ function setA2AKeyStore(store) {
11
+ _a2aKeyStore = store;
12
+ }
13
+ async function refreshStoreKeyMap() {
14
+ if (!_a2aKeyStore) return;
15
+ _storeKeyMap = await _a2aKeyStore.loadIntoMap();
16
+ }
17
+ function parseEnvKeyMap() {
18
+ const keysEnv = process.env.A2A_API_KEYS || "";
19
+ const defaultTenantId = process.env.A2A_DEFAULT_TENANT_ID || "a2a-default-tenant";
20
+ const defaultProjectId = process.env.A2A_DEFAULT_PROJECT_ID || "";
21
+ const defaultWorkspaceId = process.env.A2A_DEFAULT_WORKSPACE_ID || "";
22
+ const map = /* @__PURE__ */ new Map();
23
+ if (keysEnv) {
24
+ keysEnv.split(",").forEach((entry) => {
25
+ const trimmed = entry.trim();
26
+ if (!trimmed) return;
27
+ const parts = trimmed.split(":");
28
+ const key = parts[0];
29
+ if (!key) return;
30
+ map.set(key, {
31
+ key,
32
+ tenantId: parts[1]?.trim() || defaultTenantId,
33
+ projectId: parts[2]?.trim() || defaultProjectId || void 0,
34
+ workspaceId: parts[3]?.trim() || defaultWorkspaceId || void 0
35
+ });
36
+ });
37
+ }
38
+ return map;
39
+ }
40
+ function getA2AConfig() {
41
+ const envMap = parseEnvKeyMap();
42
+ const apiKeyMap = new Map([..._storeKeyMap, ...envMap]);
43
+ return {
44
+ agentName: process.env.A2A_AGENT_NAME || "Axiom Lattice Agent",
45
+ agentDescription: process.env.A2A_AGENT_DESCRIPTION || "AI agent powered by Axiom Lattice framework",
46
+ organization: process.env.A2A_ORGANIZATION || "Axiom Lattice",
47
+ organizationUrl: process.env.A2A_ORGANIZATION_URL || "https://axiom-lattice.ai",
48
+ version: process.env.A2A_VERSION || "1.0.0",
49
+ defaultAssistantId: process.env.A2A_DEFAULT_AGENT_ID || "",
50
+ defaultTenantId: process.env.A2A_DEFAULT_TENANT_ID || "a2a-default-tenant",
51
+ defaultProjectId: process.env.A2A_DEFAULT_PROJECT_ID || "",
52
+ defaultWorkspaceId: process.env.A2A_DEFAULT_WORKSPACE_ID || "",
53
+ apiKeyMap
54
+ };
55
+ }
56
+ function authenticateA2A(request) {
57
+ const config = getA2AConfig();
58
+ const noAuthRequired = config.apiKeyMap.size === 0;
59
+ const token = request.headers.authorization?.startsWith("Bearer ") ? request.headers.authorization.substring(7) : request.headers["x-api-key"];
60
+ if (!token) {
61
+ return {
62
+ authenticated: noAuthRequired,
63
+ tenantId: config.defaultTenantId,
64
+ projectId: config.defaultProjectId || void 0,
65
+ workspaceId: config.defaultWorkspaceId || void 0
66
+ };
67
+ }
68
+ const entry = config.apiKeyMap.get(token);
69
+ if (entry) {
70
+ return {
71
+ authenticated: true,
72
+ apiKey: token,
73
+ tenantId: entry.tenantId,
74
+ projectId: entry.projectId,
75
+ workspaceId: entry.workspaceId,
76
+ source: request.headers.authorization?.startsWith("Bearer ") ? "bearer" : "x-api-key"
77
+ };
78
+ }
79
+ return { authenticated: false };
80
+ }
81
+ function requireAuth(request, reply) {
82
+ const auth = authenticateA2A(request);
83
+ if (!auth.authenticated) {
84
+ reply.status(401).send({
85
+ error: "Unauthorized",
86
+ message: "Valid API key required via Bearer token or X-API-Key header"
87
+ });
88
+ }
89
+ return auth;
90
+ }
91
+ function buildAgentCard(baseUrl) {
92
+ const config = getA2AConfig();
93
+ return {
94
+ name: config.agentName,
95
+ description: config.agentDescription,
96
+ url: `${baseUrl}/api/a2a`,
97
+ provider: {
98
+ organization: config.organization,
99
+ url: config.organizationUrl
100
+ },
101
+ version: config.version,
102
+ capabilities: {
103
+ streaming: true,
104
+ pushNotifications: false,
105
+ stateTransitionHistory: false
106
+ },
107
+ defaultInputModes: ["text", "text/plain"],
108
+ defaultOutputModes: ["text", "text/plain", "text/markdown"],
109
+ skills: []
110
+ };
111
+ }
112
+ function extractTextFromParts(parts) {
113
+ return parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
114
+ }
115
+ function buildTextPart(text) {
116
+ return { type: "text", text };
117
+ }
118
+ function isoNow() {
119
+ return (/* @__PURE__ */ new Date()).toISOString();
120
+ }
121
+ async function streamChunksAsA2A(stream, taskId, sendEvent, onInterrupt) {
122
+ let accumulatedText = "";
123
+ for await (const chunk of stream) {
124
+ const chunkText = chunk.data?.content || "";
125
+ const chunkType = chunk.type;
126
+ if (chunkType === MessageChunkTypes.INTERRUPT) {
127
+ sendEvent("status-update", {
128
+ id: taskId,
129
+ status: {
130
+ state: "input-required",
131
+ message: {
132
+ role: "agent",
133
+ parts: [buildTextPart(accumulatedText)]
134
+ },
135
+ timestamp: isoNow()
136
+ },
137
+ final: false
138
+ });
139
+ onInterrupt?.();
140
+ continue;
141
+ }
142
+ if (chunkType === MessageChunkTypes.AI || chunkType === MessageChunkTypes.TOOL) {
143
+ if (chunkText) {
144
+ accumulatedText += chunkText;
145
+ sendEvent("status-update", {
146
+ id: taskId,
147
+ status: {
148
+ state: "working",
149
+ message: {
150
+ role: "agent",
151
+ parts: [buildTextPart(chunkText)]
152
+ },
153
+ timestamp: isoNow()
154
+ },
155
+ final: false
156
+ });
157
+ }
158
+ }
159
+ }
160
+ return accumulatedText;
161
+ }
162
+ async function getTaskStatus(taskId) {
163
+ const record = taskStore.get(taskId);
164
+ if (!record) return null;
165
+ return {
166
+ id: record.id,
167
+ status: {
168
+ state: record.status,
169
+ timestamp: record.updatedAt
170
+ },
171
+ artifacts: [],
172
+ history: record.history
173
+ };
174
+ }
175
+ async function handleAgentCard(request, reply) {
176
+ const protocol = request.headers["x-forwarded-proto"] || request.protocol;
177
+ const host = request.hostname;
178
+ const baseUrl = `${protocol}://${host}`;
179
+ const card = buildAgentCard(baseUrl);
180
+ reply.status(200).send(card);
181
+ }
182
+ async function handleTasksGet(request, reply) {
183
+ const auth = requireAuth(request, reply);
184
+ if (!auth.authenticated) return;
185
+ const task = await getTaskStatus(request.params.taskId);
186
+ if (!task) {
187
+ reply.status(404).send({ error: "Task not found" });
188
+ return;
189
+ }
190
+ reply.status(200).send(task);
191
+ }
192
+ async function handleTasksSend(request, reply) {
193
+ const auth = requireAuth(request, reply);
194
+ if (!auth.authenticated) return;
195
+ const config = getA2AConfig();
196
+ const body = request.body;
197
+ if (!body.message || !body.message.parts || body.message.parts.length === 0) {
198
+ reply.status(400).send({ error: "message.parts is required" });
199
+ return;
200
+ }
201
+ const text = extractTextFromParts(body.message.parts);
202
+ if (!text.trim()) {
203
+ reply.status(400).send({ error: "message must contain text content" });
204
+ return;
205
+ }
206
+ const assistantId = request.headers["x-a2a-agent-id"] || config.defaultAssistantId;
207
+ if (!assistantId) {
208
+ reply.status(500).send({
209
+ error: "No agent configured",
210
+ message: "Set A2A_DEFAULT_AGENT_ID env var or provide x-a2a-agent-id header"
211
+ });
212
+ return;
213
+ }
214
+ const taskId = body.id || v4();
215
+ const threadId = v4();
216
+ const tenantId = auth.tenantId || config.defaultTenantId;
217
+ const now = isoNow();
218
+ const record = {
219
+ id: taskId,
220
+ threadId,
221
+ assistantId,
222
+ tenantId,
223
+ projectId: auth.projectId || void 0,
224
+ workspaceId: auth.workspaceId || void 0,
225
+ status: "working",
226
+ createdAt: now,
227
+ updatedAt: now,
228
+ history: [
229
+ {
230
+ role: "user",
231
+ parts: body.message.parts
232
+ }
233
+ ]
234
+ };
235
+ taskStore.set(taskId, record);
236
+ let agent;
237
+ try {
238
+ agent = agentInstanceManager.getAgent({
239
+ assistant_id: assistantId,
240
+ thread_id: threadId,
241
+ tenant_id: tenantId,
242
+ workspace_id: auth.workspaceId || void 0,
243
+ project_id: auth.projectId || void 0
244
+ });
245
+ } catch (err) {
246
+ record.status = "failed";
247
+ record.updatedAt = isoNow();
248
+ reply.status(500).send({
249
+ id: taskId,
250
+ error: "Failed to initialize agent",
251
+ message: err instanceof Error ? err.message : String(err)
252
+ });
253
+ return;
254
+ }
255
+ reply.hijack();
256
+ reply.raw.writeHead(200, {
257
+ "Content-Type": "text/event-stream",
258
+ "Cache-Control": "no-cache",
259
+ Connection: "keep-alive",
260
+ "Access-Control-Allow-Origin": "*"
261
+ });
262
+ const sendEvent = (event, data) => {
263
+ reply.raw.write(`event: ${event}
264
+ data: ${JSON.stringify(data)}
265
+
266
+ `);
267
+ };
268
+ try {
269
+ const result = await agent.addMessage({
270
+ input: { message: text }
271
+ });
272
+ record.messageId = result.messageId;
273
+ const stream = agent.chunkStream(result.messageId, [
274
+ MessageChunkTypes.MESSAGE_COMPLETED
275
+ ]);
276
+ const accumulatedText = await streamChunksAsA2A(stream, taskId, sendEvent, () => {
277
+ record.status = "input-required";
278
+ record.updatedAt = isoNow();
279
+ });
280
+ record.status = "completed";
281
+ record.updatedAt = isoNow();
282
+ record.history.push({
283
+ role: "agent",
284
+ parts: [buildTextPart(accumulatedText)]
285
+ });
286
+ sendEvent("status-update", {
287
+ id: taskId,
288
+ status: {
289
+ state: "completed",
290
+ timestamp: isoNow()
291
+ },
292
+ final: true
293
+ });
294
+ } catch (err) {
295
+ record.status = "failed";
296
+ record.updatedAt = isoNow();
297
+ sendEvent("error", {
298
+ code: "EXECUTION_ERROR",
299
+ message: err instanceof Error ? err.message : String(err)
300
+ });
301
+ sendEvent("status-update", {
302
+ id: taskId,
303
+ status: {
304
+ state: "failed",
305
+ timestamp: isoNow()
306
+ },
307
+ final: true
308
+ });
309
+ } finally {
310
+ reply.raw.end();
311
+ }
312
+ }
313
+ async function handleTasksCancel(request, reply) {
314
+ const auth = requireAuth(request, reply);
315
+ if (!auth.authenticated) return;
316
+ const { taskId } = request.params;
317
+ const record = taskStore.get(taskId);
318
+ if (!record) {
319
+ reply.status(404).send({ error: "Task not found" });
320
+ return;
321
+ }
322
+ if (record.status === "completed" || record.status === "failed" || record.status === "canceled") {
323
+ reply.status(409).send({
324
+ error: "Task is already in a terminal state",
325
+ task: { id: record.id, status: record.status }
326
+ });
327
+ return;
328
+ }
329
+ try {
330
+ const agent = agentInstanceManager.getAgent({
331
+ assistant_id: record.assistantId,
332
+ thread_id: record.threadId,
333
+ tenant_id: record.tenantId,
334
+ workspace_id: record.workspaceId,
335
+ project_id: record.projectId
336
+ });
337
+ await agent.abort();
338
+ } catch (err) {
339
+ console.warn({
340
+ event: "a2a:cancel:no_agent",
341
+ taskId,
342
+ threadId: record.threadId,
343
+ error: err instanceof Error ? err.message : String(err)
344
+ });
345
+ }
346
+ record.status = "canceled";
347
+ record.updatedAt = isoNow();
348
+ reply.status(200).send({
349
+ id: taskId,
350
+ status: {
351
+ state: "canceled",
352
+ timestamp: record.updatedAt
353
+ }
354
+ });
355
+ }
356
+ async function handleTasksStream(request, reply) {
357
+ const auth = requireAuth(request, reply);
358
+ if (!auth.authenticated) return;
359
+ const { taskId } = request.params;
360
+ const record = taskStore.get(taskId);
361
+ if (!record) {
362
+ reply.status(404).send({ error: "Task not found" });
363
+ return;
364
+ }
365
+ let agent;
366
+ try {
367
+ agent = agentInstanceManager.getAgent({
368
+ assistant_id: record.assistantId,
369
+ thread_id: record.threadId,
370
+ tenant_id: record.tenantId,
371
+ workspace_id: record.workspaceId,
372
+ project_id: record.projectId
373
+ });
374
+ } catch {
375
+ reply.status(404).send({
376
+ error: "Agent session not found. The task may have already completed."
377
+ });
378
+ return;
379
+ }
380
+ reply.hijack();
381
+ reply.raw.writeHead(200, {
382
+ "Content-Type": "text/event-stream",
383
+ "Cache-Control": "no-cache",
384
+ Connection: "keep-alive",
385
+ "Access-Control-Allow-Origin": "*"
386
+ });
387
+ const sendEvent = (event, data) => {
388
+ reply.raw.write(`event: ${event}
389
+ data: ${JSON.stringify(data)}
390
+
391
+ `);
392
+ };
393
+ if (!record.messageId) {
394
+ sendEvent("status-update", {
395
+ id: taskId,
396
+ status: {
397
+ state: record.status,
398
+ timestamp: record.updatedAt
399
+ },
400
+ final: true
401
+ });
402
+ reply.raw.end();
403
+ return;
404
+ }
405
+ try {
406
+ const stream = agent.chunkStream(record.messageId, [
407
+ MessageChunkTypes.MESSAGE_COMPLETED
408
+ ]);
409
+ await streamChunksAsA2A(stream, taskId, sendEvent);
410
+ sendEvent("status-update", {
411
+ id: taskId,
412
+ status: {
413
+ state: record.status,
414
+ timestamp: record.updatedAt
415
+ },
416
+ final: true
417
+ });
418
+ } catch (err) {
419
+ sendEvent("error", {
420
+ code: "STREAM_ERROR",
421
+ message: err instanceof Error ? err.message : String(err)
422
+ });
423
+ } finally {
424
+ reply.raw.end();
425
+ }
426
+ }
427
+ async function handleApiKeyList(request, reply) {
428
+ const auth = requireAuth(request, reply);
429
+ if (!auth.authenticated) return;
430
+ if (!_a2aKeyStore) {
431
+ reply.status(501).send({ error: "Key store not configured" });
432
+ return;
433
+ }
434
+ const records = await _a2aKeyStore.list({
435
+ tenantId: request.query.tenantId,
436
+ limit: request.query.limit ? parseInt(request.query.limit, 10) : void 0,
437
+ offset: request.query.offset ? parseInt(request.query.offset, 10) : void 0
438
+ });
439
+ reply.status(200).send({
440
+ success: true,
441
+ data: {
442
+ records: records.map((r) => ({ ...r, key: r.key.slice(0, 8) + "..." })),
443
+ total: records.length
444
+ }
445
+ });
446
+ }
447
+ async function handleApiKeyCreate(request, reply) {
448
+ const auth = requireAuth(request, reply);
449
+ if (!auth.authenticated) return;
450
+ if (!_a2aKeyStore) {
451
+ reply.status(501).send({ error: "Key store not configured" });
452
+ return;
453
+ }
454
+ const body = request.body;
455
+ const tenantId = body.tenantId || request.headers["x-tenant-id"] || auth.tenantId;
456
+ const projectId = body.projectId || request.headers["x-project-id"] || auth.projectId || "default";
457
+ const workspaceId = body.workspaceId || request.headers["x-workspace-id"] || auth.workspaceId;
458
+ if (!tenantId) {
459
+ reply.status(400).send({
460
+ error: "tenantId is required",
461
+ message: "Provide via body.tenantId, x-tenant-id header, or authenticate with a scoped API key"
462
+ });
463
+ return;
464
+ }
465
+ const record = await _a2aKeyStore.create({
466
+ tenantId,
467
+ projectId,
468
+ workspaceId,
469
+ label: body.label
470
+ });
471
+ await refreshStoreKeyMap();
472
+ reply.status(201).send({ success: true, data: record });
473
+ }
474
+ async function handleApiKeyDelete(request, reply) {
475
+ const auth = requireAuth(request, reply);
476
+ if (!auth.authenticated) return;
477
+ if (!_a2aKeyStore) {
478
+ reply.status(501).send({ error: "Key store not configured" });
479
+ return;
480
+ }
481
+ await _a2aKeyStore.delete(request.params.id);
482
+ await refreshStoreKeyMap();
483
+ reply.status(200).send({ success: true });
484
+ }
485
+ async function handleApiKeyDisable(request, reply) {
486
+ const auth = requireAuth(request, reply);
487
+ if (!auth.authenticated) return;
488
+ if (!_a2aKeyStore) {
489
+ reply.status(501).send({ error: "Key store not configured" });
490
+ return;
491
+ }
492
+ await _a2aKeyStore.disable(request.params.id);
493
+ await refreshStoreKeyMap();
494
+ reply.status(200).send({ success: true });
495
+ }
496
+ async function handleApiKeyEnable(request, reply) {
497
+ const auth = requireAuth(request, reply);
498
+ if (!auth.authenticated) return;
499
+ if (!_a2aKeyStore) {
500
+ reply.status(501).send({ error: "Key store not configured" });
501
+ return;
502
+ }
503
+ await _a2aKeyStore.enable(request.params.id);
504
+ await refreshStoreKeyMap();
505
+ reply.status(200).send({ success: true });
506
+ }
507
+ async function handleApiKeyRotate(request, reply) {
508
+ const auth = requireAuth(request, reply);
509
+ if (!auth.authenticated) return;
510
+ if (!_a2aKeyStore) {
511
+ reply.status(501).send({ error: "Key store not configured" });
512
+ return;
513
+ }
514
+ const record = await _a2aKeyStore.rotate(request.params.id);
515
+ await refreshStoreKeyMap();
516
+ reply.status(200).send({ success: true, data: record });
517
+ }
518
+ function registerA2ARoutes(app) {
519
+ app.get("/api/a2a/.well-known/agent.json", handleAgentCard);
520
+ app.get("/api/a2a/.well-known/agent-card.json", handleAgentCard);
521
+ app.post(
522
+ "/api/a2a/tasks/send",
523
+ handleTasksSend
524
+ );
525
+ app.get(
526
+ "/api/a2a/tasks/:taskId",
527
+ handleTasksGet
528
+ );
529
+ app.get(
530
+ "/api/a2a/tasks/:taskId/stream",
531
+ handleTasksStream
532
+ );
533
+ app.post(
534
+ "/api/a2a/tasks/:taskId/cancel",
535
+ handleTasksCancel
536
+ );
537
+ app.get(
538
+ "/api/a2a/keys",
539
+ handleApiKeyList
540
+ );
541
+ app.post(
542
+ "/api/a2a/keys",
543
+ handleApiKeyCreate
544
+ );
545
+ app.delete(
546
+ "/api/a2a/keys/:id",
547
+ handleApiKeyDelete
548
+ );
549
+ app.post(
550
+ "/api/a2a/keys/:id/disable",
551
+ handleApiKeyDisable
552
+ );
553
+ app.post(
554
+ "/api/a2a/keys/:id/enable",
555
+ handleApiKeyEnable
556
+ );
557
+ app.post(
558
+ "/api/a2a/keys/:id/rotate",
559
+ handleApiKeyRotate
560
+ );
561
+ }
562
+ export {
563
+ refreshStoreKeyMap,
564
+ registerA2ARoutes,
565
+ setA2AKeyStore
566
+ };
567
+ //# sourceMappingURL=a2a-ERG5RMUW.mjs.map