@terminai/a2a-server 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/README.md +5 -0
  2. package/dist/.last_build +0 -0
  3. package/dist/a2a-server.mjs +415698 -0
  4. package/dist/index.d.ts +7 -0
  5. package/dist/index.js +8 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/agent/executor.d.ts +41 -0
  8. package/dist/src/agent/executor.js +408 -0
  9. package/dist/src/agent/executor.js.map +1 -0
  10. package/dist/src/agent/task.d.ts +67 -0
  11. package/dist/src/agent/task.js +799 -0
  12. package/dist/src/agent/task.js.map +1 -0
  13. package/dist/src/agent/task.test.d.ts +7 -0
  14. package/dist/src/agent/task.test.js +435 -0
  15. package/dist/src/agent/task.test.js.map +1 -0
  16. package/dist/src/agent/task.token.test.d.ts +7 -0
  17. package/dist/src/agent/task.token.test.js +53 -0
  18. package/dist/src/agent/task.token.test.js.map +1 -0
  19. package/dist/src/auth/llmAuthManager.d.ts +39 -0
  20. package/dist/src/auth/llmAuthManager.js +209 -0
  21. package/dist/src/auth/llmAuthManager.js.map +1 -0
  22. package/dist/src/auth/llmAuthManager.test.d.ts +7 -0
  23. package/dist/src/auth/llmAuthManager.test.js +92 -0
  24. package/dist/src/auth/llmAuthManager.test.js.map +1 -0
  25. package/dist/src/commands/command-registry.d.ts +16 -0
  26. package/dist/src/commands/command-registry.js +35 -0
  27. package/dist/src/commands/command-registry.js.map +1 -0
  28. package/dist/src/commands/command-registry.test.d.ts +7 -0
  29. package/dist/src/commands/command-registry.test.js +100 -0
  30. package/dist/src/commands/command-registry.test.js.map +1 -0
  31. package/dist/src/commands/extensions.d.ts +19 -0
  32. package/dist/src/commands/extensions.js +26 -0
  33. package/dist/src/commands/extensions.js.map +1 -0
  34. package/dist/src/commands/extensions.test.d.ts +7 -0
  35. package/dist/src/commands/extensions.test.js +70 -0
  36. package/dist/src/commands/extensions.test.js.map +1 -0
  37. package/dist/src/commands/init.d.ts +16 -0
  38. package/dist/src/commands/init.js +111 -0
  39. package/dist/src/commands/init.js.map +1 -0
  40. package/dist/src/commands/init.test.d.ts +7 -0
  41. package/dist/src/commands/init.test.js +146 -0
  42. package/dist/src/commands/init.test.js.map +1 -0
  43. package/dist/src/commands/restore.d.ts +21 -0
  44. package/dist/src/commands/restore.js +126 -0
  45. package/dist/src/commands/restore.js.map +1 -0
  46. package/dist/src/commands/restore.test.d.ts +7 -0
  47. package/dist/src/commands/restore.test.js +111 -0
  48. package/dist/src/commands/restore.test.js.map +1 -0
  49. package/dist/src/commands/types.d.ts +33 -0
  50. package/dist/src/commands/types.js +8 -0
  51. package/dist/src/commands/types.js.map +1 -0
  52. package/dist/src/config/config.d.ts +24 -0
  53. package/dist/src/config/config.js +140 -0
  54. package/dist/src/config/config.js.map +1 -0
  55. package/dist/src/config/extension.d.ts +12 -0
  56. package/dist/src/config/extension.js +105 -0
  57. package/dist/src/config/extension.js.map +1 -0
  58. package/dist/src/config/settings.d.ts +15 -0
  59. package/dist/src/config/settings.js +20 -0
  60. package/dist/src/config/settings.js.map +1 -0
  61. package/dist/src/config/settings.test.d.ts +7 -0
  62. package/dist/src/config/settings.test.js +170 -0
  63. package/dist/src/config/settings.test.js.map +1 -0
  64. package/dist/src/http/app.d.ts +17 -0
  65. package/dist/src/http/app.js +399 -0
  66. package/dist/src/http/app.js.map +1 -0
  67. package/dist/src/http/app.test.d.ts +7 -0
  68. package/dist/src/http/app.test.js +1048 -0
  69. package/dist/src/http/app.test.js.map +1 -0
  70. package/dist/src/http/auth.d.ts +21 -0
  71. package/dist/src/http/auth.js +55 -0
  72. package/dist/src/http/auth.js.map +1 -0
  73. package/dist/src/http/auth.test.d.ts +7 -0
  74. package/dist/src/http/auth.test.js +53 -0
  75. package/dist/src/http/auth.test.js.map +1 -0
  76. package/dist/src/http/authRoutes.test.d.ts +7 -0
  77. package/dist/src/http/authRoutes.test.js +169 -0
  78. package/dist/src/http/authRoutes.test.js.map +1 -0
  79. package/dist/src/http/cors.d.ts +8 -0
  80. package/dist/src/http/cors.js +96 -0
  81. package/dist/src/http/cors.js.map +1 -0
  82. package/dist/src/http/cors.test.d.ts +7 -0
  83. package/dist/src/http/cors.test.js +62 -0
  84. package/dist/src/http/cors.test.js.map +1 -0
  85. package/dist/src/http/deferredAuth.test.d.ts +7 -0
  86. package/dist/src/http/deferredAuth.test.js +45 -0
  87. package/dist/src/http/deferredAuth.test.js.map +1 -0
  88. package/dist/src/http/endpoints.test.d.ts +7 -0
  89. package/dist/src/http/endpoints.test.js +149 -0
  90. package/dist/src/http/endpoints.test.js.map +1 -0
  91. package/dist/src/http/llmAuthMiddleware.d.ts +9 -0
  92. package/dist/src/http/llmAuthMiddleware.js +37 -0
  93. package/dist/src/http/llmAuthMiddleware.js.map +1 -0
  94. package/dist/src/http/relay.d.ts +28 -0
  95. package/dist/src/http/relay.js +342 -0
  96. package/dist/src/http/relay.js.map +1 -0
  97. package/dist/src/http/relay.test.d.ts +7 -0
  98. package/dist/src/http/relay.test.js +149 -0
  99. package/dist/src/http/relay.test.js.map +1 -0
  100. package/dist/src/http/replay.d.ts +19 -0
  101. package/dist/src/http/replay.js +90 -0
  102. package/dist/src/http/replay.js.map +1 -0
  103. package/dist/src/http/replay.test.d.ts +7 -0
  104. package/dist/src/http/replay.test.js +78 -0
  105. package/dist/src/http/replay.test.js.map +1 -0
  106. package/dist/src/http/requestStorage.d.ts +11 -0
  107. package/dist/src/http/requestStorage.js +9 -0
  108. package/dist/src/http/requestStorage.js.map +1 -0
  109. package/dist/src/http/routes/auth.d.ts +9 -0
  110. package/dist/src/http/routes/auth.js +125 -0
  111. package/dist/src/http/routes/auth.js.map +1 -0
  112. package/dist/src/http/server.d.ts +8 -0
  113. package/dist/src/http/server.js +28 -0
  114. package/dist/src/http/server.js.map +1 -0
  115. package/dist/src/index.d.ts +10 -0
  116. package/dist/src/index.js +11 -0
  117. package/dist/src/index.js.map +1 -0
  118. package/dist/src/persistence/gcs.d.ts +25 -0
  119. package/dist/src/persistence/gcs.js +248 -0
  120. package/dist/src/persistence/gcs.js.map +1 -0
  121. package/dist/src/persistence/gcs.test.d.ts +7 -0
  122. package/dist/src/persistence/gcs.test.js +335 -0
  123. package/dist/src/persistence/gcs.test.js.map +1 -0
  124. package/dist/src/persistence/remoteAuthStore.d.ts +21 -0
  125. package/dist/src/persistence/remoteAuthStore.js +74 -0
  126. package/dist/src/persistence/remoteAuthStore.js.map +1 -0
  127. package/dist/src/types.d.ts +100 -0
  128. package/dist/src/types.js +49 -0
  129. package/dist/src/types.js.map +1 -0
  130. package/dist/src/utils/envAliases.d.ts +7 -0
  131. package/dist/src/utils/envAliases.js +9 -0
  132. package/dist/src/utils/envAliases.js.map +1 -0
  133. package/dist/src/utils/executor_utils.d.ts +8 -0
  134. package/dist/src/utils/executor_utils.js +42 -0
  135. package/dist/src/utils/executor_utils.js.map +1 -0
  136. package/dist/src/utils/logger.d.ts +9 -0
  137. package/dist/src/utils/logger.js +26 -0
  138. package/dist/src/utils/logger.js.map +1 -0
  139. package/dist/src/utils/redactSecrets.d.ts +16 -0
  140. package/dist/src/utils/redactSecrets.js +72 -0
  141. package/dist/src/utils/redactSecrets.js.map +1 -0
  142. package/dist/src/utils/redactSecrets.test.d.ts +7 -0
  143. package/dist/src/utils/redactSecrets.test.js +62 -0
  144. package/dist/src/utils/redactSecrets.test.js.map +1 -0
  145. package/dist/src/utils/testing_utils.d.ts +48 -0
  146. package/dist/src/utils/testing_utils.js +173 -0
  147. package/dist/src/utils/testing_utils.js.map +1 -0
  148. package/dist/tsconfig.tsbuildinfo +1 -0
  149. package/dist/web-client/app.js +526 -0
  150. package/dist/web-client/index.html +43 -0
  151. package/dist/web-client/package.json +10 -0
  152. package/dist/web-client/relay-client.js +330 -0
  153. package/dist/web-client/style.css +189 -0
  154. package/package.json +53 -0
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * Portions Copyright 2025 TerminaI Authors
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
8
+ import { createMockConfig, TEST_REMOTE_TOKEN } from '../utils/testing_utils.js';
9
+ const loadConfigSpy = vi.hoisted(() => vi.fn());
10
+ vi.mock('../config/config.js', async (importOriginal) => {
11
+ const actual = await importOriginal();
12
+ return {
13
+ ...actual,
14
+ loadConfig: loadConfigSpy,
15
+ };
16
+ });
17
+ describe('deferred auth default (Task 18)', () => {
18
+ beforeEach(() => {
19
+ process.env['NODE_ENV'] = 'test';
20
+ process.env['GEMINI_WEB_REMOTE_TOKEN'] = TEST_REMOTE_TOKEN;
21
+ delete process.env['TERMINAI_A2A_DEFER_AUTH'];
22
+ delete process.env['GEMINI_A2A_DEFER_AUTH'];
23
+ delete process.env['TERMINAI_SIDECAR'];
24
+ loadConfigSpy.mockResolvedValue(createMockConfig({
25
+ refreshAuth: vi.fn().mockResolvedValue(undefined),
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ getWebRemoteRelayUrl: vi.fn().mockReturnValue(undefined),
28
+ }));
29
+ });
30
+ afterEach(() => {
31
+ delete process.env['GEMINI_WEB_REMOTE_TOKEN'];
32
+ delete process.env['TERMINAI_SIDECAR'];
33
+ vi.resetAllMocks();
34
+ });
35
+ it('enables deferLlmAuth when TERMINAI_SIDECAR=1', async () => {
36
+ process.env['TERMINAI_SIDECAR'] = '1';
37
+ const { createApp } = await import('./app.js');
38
+ await createApp();
39
+ // Signature: loadConfig(loadedSettings, extensionLoader, taskId, targetDir?, { deferLlmAuth })
40
+ const lastCall = loadConfigSpy.mock.calls.at(-1);
41
+ expect(lastCall?.[2]).toBe('a2a-server');
42
+ expect(lastCall?.[4]).toEqual({ deferLlmAuth: true });
43
+ });
44
+ });
45
+ //# sourceMappingURL=deferredAuth.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deferredAuth.test.js","sourceRoot":"","sources":["../../../src/http/deferredAuth.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAGhF,MAAM,aAAa,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAEhD,EAAE,CAAC,IAAI,CAAC,qBAAqB,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;IACtD,MAAM,MAAM,GAAG,MAAM,cAAc,EAAwC,CAAC;IAC5E,OAAO;QACL,GAAG,MAAM;QACT,UAAU,EAAE,aAAa;KAC1B,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,GAAG,iBAAiB,CAAC;QAE3D,OAAO,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QAC9C,OAAO,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QAC5C,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAEvC,aAAa,CAAC,iBAAiB,CAC7B,gBAAgB,CAAC;YACf,WAAW,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC;YACjD,8DAA8D;YAC9D,oBAAoB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,SAAS,CAAC;SACzD,CAAW,CACb,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QAC9C,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QACvC,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,GAAG,GAAG,CAAC;QACtC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;QAE/C,MAAM,SAAS,EAAE,CAAC;QAElB,+FAA+F;QAC/F,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACzC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * Portions Copyright 2025 TerminaI Authors
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
7
+ export {};
@@ -0,0 +1,149 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * Portions Copyright 2025 TerminaI Authors
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
7
+ import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
8
+ import request from 'supertest';
9
+ import * as fs from 'node:fs';
10
+ import * as path from 'node:path';
11
+ import * as os from 'node:os';
12
+ import { createApp, updateCoderAgentCardUrl } from './app.js';
13
+ import { createAuthHeader, createMockConfig, createSignedHeaders, TEST_REMOTE_TOKEN, canListenOnLocalhost, listenOnLocalhost, closeServer, } from '../utils/testing_utils.js';
14
+ import { debugLogger } from '@terminai/core';
15
+ // Mock the logger to avoid polluting test output
16
+ // Comment out to help debug
17
+ vi.mock('../utils/logger.js', () => ({
18
+ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
19
+ }));
20
+ // Mock Task.create to avoid its complex setup
21
+ vi.mock('../agent/task.js', () => {
22
+ class MockTask {
23
+ id;
24
+ contextId;
25
+ taskState = 'submitted';
26
+ config = {
27
+ getContentGeneratorConfig: vi
28
+ .fn()
29
+ .mockReturnValue({ model: 'gemini-pro' }),
30
+ };
31
+ geminiClient = {
32
+ initialize: vi.fn().mockResolvedValue(undefined),
33
+ };
34
+ constructor(id, contextId) {
35
+ this.id = id;
36
+ this.contextId = contextId;
37
+ }
38
+ static create = vi
39
+ .fn()
40
+ .mockImplementation((id, contextId) => Promise.resolve(new MockTask(id, contextId)));
41
+ getMetadata = vi.fn().mockImplementation(async () => ({
42
+ id: this.id,
43
+ contextId: this.contextId,
44
+ taskState: this.taskState,
45
+ model: 'gemini-pro',
46
+ mcpServers: [],
47
+ availableTools: [],
48
+ }));
49
+ }
50
+ return { Task: MockTask };
51
+ });
52
+ vi.mock('../config/config.js', async () => {
53
+ const actual = await vi.importActual('../config/config.js');
54
+ return {
55
+ ...actual,
56
+ loadConfig: vi
57
+ .fn()
58
+ .mockImplementation(async () => createMockConfig({})),
59
+ };
60
+ });
61
+ const CAN_LISTEN = await canListenOnLocalhost();
62
+ const describeIfListen = CAN_LISTEN ? describe : describe.skip;
63
+ describeIfListen('Agent Server Endpoints', () => {
64
+ let app;
65
+ let server;
66
+ let testWorkspace;
67
+ const createTask = (contextId) => request(app)
68
+ .post('/tasks')
69
+ .set(createSignedHeaders('POST', '/tasks', {
70
+ contextId,
71
+ agentSettings: {
72
+ kind: 'agent-settings',
73
+ workspacePath: testWorkspace,
74
+ },
75
+ }))
76
+ .set('Content-Type', 'application/json')
77
+ .send({
78
+ contextId,
79
+ agentSettings: {
80
+ kind: 'agent-settings',
81
+ workspacePath: testWorkspace,
82
+ },
83
+ });
84
+ beforeAll(async () => {
85
+ process.env['GEMINI_WEB_REMOTE_TOKEN'] = TEST_REMOTE_TOKEN;
86
+ // Create a unique temporary directory for the workspace to avoid conflicts
87
+ testWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-agent-test-'));
88
+ app = await createApp();
89
+ server = await listenOnLocalhost(app);
90
+ const port = server.address().port;
91
+ updateCoderAgentCardUrl(port);
92
+ });
93
+ afterAll(async () => {
94
+ if (server) {
95
+ await closeServer(server);
96
+ }
97
+ delete process.env['GEMINI_WEB_REMOTE_TOKEN'];
98
+ if (testWorkspace) {
99
+ try {
100
+ fs.rmSync(testWorkspace, { recursive: true, force: true });
101
+ }
102
+ catch (e) {
103
+ debugLogger.warn(`Could not remove temp dir '${testWorkspace}':`, e);
104
+ }
105
+ }
106
+ });
107
+ it('should create a new task via POST /tasks', async () => {
108
+ const response = await createTask('test-context');
109
+ expect(response.status).toBe(201);
110
+ expect(response.body).toBeTypeOf('string'); // Should return the task ID
111
+ }, 7000);
112
+ it('should get metadata for a specific task via GET /tasks/:taskId/metadata', async () => {
113
+ const createResponse = await createTask('test-context-2');
114
+ const taskId = createResponse.body;
115
+ const response = await request(app)
116
+ .get(`/tasks/${taskId}/metadata`)
117
+ .set(createAuthHeader());
118
+ expect(response.status).toBe(200);
119
+ expect(response.body.metadata.id).toBe(taskId);
120
+ }, 6000);
121
+ it('should get metadata for all tasks via GET /tasks/metadata', async () => {
122
+ const createResponse = await createTask('test-context-3');
123
+ const taskId = createResponse.body;
124
+ const response = await request(app)
125
+ .get('/tasks/metadata')
126
+ .set(createAuthHeader());
127
+ expect(response.status).toBe(200);
128
+ expect(Array.isArray(response.body)).toBe(true);
129
+ expect(response.body.length).toBeGreaterThan(0);
130
+ const taskMetadata = response.body.find((m) => m.id === taskId);
131
+ expect(taskMetadata).toBeDefined();
132
+ });
133
+ it('should return 404 for a non-existent task', async () => {
134
+ const response = await request(app)
135
+ .get('/tasks/fake-task/metadata')
136
+ .set(createAuthHeader());
137
+ expect(response.status).toBe(404);
138
+ });
139
+ it('should return agent metadata via GET /.well-known/agent-card.json', async () => {
140
+ const response = await request(app)
141
+ .get('/.well-known/agent-card.json')
142
+ .set(createAuthHeader());
143
+ const port = server.address().port;
144
+ expect(response.status).toBe(200);
145
+ expect(response.body.name).toBe('Gemini SDLC Agent');
146
+ expect(response.body.url).toBe(`http://localhost:${port}/`);
147
+ });
148
+ });
149
+ //# sourceMappingURL=endpoints.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoints.test.js","sourceRoot":"","sources":["../../../src/http/endpoints.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * Portions Copyright 2025 TerminaI Authors
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
7
+ import type { Request, Response, NextFunction } from 'express';
8
+ import { LlmAuthManager } from '../auth/llmAuthManager.js';
9
+ export declare function createLlmAuthMiddleware(authManager: LlmAuthManager): (_req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * Portions Copyright 2025 TerminaI Authors
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
7
+ import { LlmAuthManager } from '../auth/llmAuthManager.js';
8
+ export function createLlmAuthMiddleware(authManager) {
9
+ return (_req, res, next) => {
10
+ void authManager
11
+ .getStatus()
12
+ .then((check) => {
13
+ if (check.status !== 'ok') {
14
+ res.status(503).json({
15
+ error: 'Authentication required',
16
+ code: 'AUTH_REQUIRED',
17
+ details: check,
18
+ });
19
+ return;
20
+ }
21
+ next();
22
+ })
23
+ .catch((err) => {
24
+ // Fail closed: if status check fails, treat as auth required.
25
+ res.status(503).json({
26
+ error: 'Authentication required',
27
+ code: 'AUTH_REQUIRED',
28
+ details: {
29
+ status: 'error',
30
+ authType: null,
31
+ message: err instanceof Error ? err.message : 'Auth status check failed',
32
+ },
33
+ });
34
+ });
35
+ };
36
+ }
37
+ //# sourceMappingURL=llmAuthMiddleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llmAuthMiddleware.js","sourceRoot":"","sources":["../../../src/http/llmAuthMiddleware.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,MAAM,UAAU,uBAAuB,CAAC,WAA2B;IACjE,OAAO,CAAC,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC1D,KAAK,WAAW;aACb,SAAS,EAAE;aACX,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;YACd,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;gBAC1B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACnB,KAAK,EAAE,yBAAyB;oBAChC,IAAI,EAAE,eAAe;oBACrB,OAAO,EAAE,KAAK;iBACf,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YACD,IAAI,EAAE,CAAC;QACT,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACb,8DAA8D;YAC9D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,KAAK,EAAE,yBAAyB;gBAChC,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE;oBACP,MAAM,EAAE,OAAO;oBACf,QAAQ,EAAE,IAAI;oBACd,OAAO,EACL,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,0BAA0B;iBAClE;aACF,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * Portions Copyright 2025 TerminaI Authors
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
7
+ import type { DefaultRequestHandler } from '@a2a-js/sdk/server';
8
+ export type RelayEnvelope = {
9
+ v: 1 | 2;
10
+ type: 'HELLO' | 'HELLO_ACK' | 'PAIR' | 'PAIR_ACK' | 'RPC' | 'EVENT' | 'ERROR' | 'PING' | 'PONG' | 'CLOSE';
11
+ dir: 'c2h' | 'h2c';
12
+ seq: number;
13
+ ts: number;
14
+ epoch?: string;
15
+ payload: unknown;
16
+ };
17
+ export type RelayEnvelopeV1 = RelayEnvelope;
18
+ export interface RelaySession {
19
+ sessionId: string;
20
+ key: Buffer;
21
+ shareUrl: string;
22
+ reconnectAttempts: number;
23
+ pairingRequired: boolean;
24
+ pairingCode?: string;
25
+ }
26
+ export declare function createRelaySession(relayUrl: string): RelaySession;
27
+ export declare function connectToRelay(relayUrl: string, requestHandler: DefaultRequestHandler): Promise<void>;
28
+ export declare function runRelayConnection(session: RelaySession, relayUrl: string, requestHandler: DefaultRequestHandler): Promise<void>;
@@ -0,0 +1,342 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * Portions Copyright 2025 TerminaI Authors
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
7
+ import { WebSocket } from 'ws';
8
+ import crypto from 'node:crypto';
9
+ import { v4 as uuidv4 } from 'uuid';
10
+ import { logger } from '../utils/logger.js';
11
+ // Protocol version constants
12
+ const PROTOCOL_VERSIONS = {
13
+ V1: 1,
14
+ V2: 2,
15
+ };
16
+ // Allow v1 fallback via environment variable (for transitional deployments)
17
+ const ALLOW_INSECURE_V1 = process.env['ALLOW_INSECURE_RELAY_V1'] === 'true';
18
+ export function createRelaySession(relayUrl) {
19
+ const sessionId = uuidv4();
20
+ // Generate 256-bit key for AES-GCM
21
+ const key = crypto.randomBytes(32);
22
+ const keyBase64 = key.toString('base64');
23
+ // Construct user-friendly URL (Key is in hash, so it's never sent to server)
24
+ // Default published Web Client URL: https://terminai.org/remote
25
+ const webClientUrl = process.env['GEMINI_WEB_CLIENT_URL'] || 'https://terminai.org/remote';
26
+ const pairingRequired = true; // Always require pairing for security
27
+ const pairingCode = Math.floor(100000 + Math.random() * 900000).toString(); // 6-digit code
28
+ const shareUrl = `${webClientUrl}#session=${sessionId}&key=${encodeURIComponent(keyBase64)}&relay=${encodeURIComponent(relayUrl)}`;
29
+ const printUrl = process.env['PRINT_RELAY_URL'] === 'true';
30
+ if (printUrl) {
31
+ logger.info(`[Relay] Remote Access URL: ${shareUrl}`);
32
+ logger.info('[Relay] (Share this URL securely. The key is in the hash and never verified by the server)');
33
+ }
34
+ else {
35
+ logger.info(JSON.stringify({
36
+ event: 'session_created',
37
+ sessionIdHash: sessionId.slice(0, 8),
38
+ pairingCodeRequired: pairingRequired,
39
+ timestamp: Date.now(),
40
+ }));
41
+ }
42
+ logger.info(`[Relay] Pairing Code: ${pairingCode} (required for first connection)`);
43
+ return {
44
+ sessionId,
45
+ key,
46
+ shareUrl,
47
+ reconnectAttempts: 0,
48
+ pairingRequired,
49
+ pairingCode,
50
+ };
51
+ }
52
+ export async function connectToRelay(relayUrl, requestHandler) {
53
+ const session = createRelaySession(relayUrl);
54
+ return runRelayConnection(session, relayUrl, requestHandler);
55
+ }
56
+ /**
57
+ * Build AAD string based on protocol version
58
+ */
59
+ function buildAad(sessionId, dir, version, epoch) {
60
+ if (version === 2 && epoch) {
61
+ return `terminai-relay|v=2|session=${sessionId}|epoch=${epoch}|dir=${dir}`;
62
+ }
63
+ return `terminai-relay|v=1|session=${sessionId}|dir=${dir}`;
64
+ }
65
+ /**
66
+ * Encrypt an envelope for sending to client
67
+ */
68
+ function encryptEnvelope(envelope, key, sessionId, version, epoch) {
69
+ const envelopeBuffer = Buffer.from(JSON.stringify(envelope), 'utf8');
70
+ const iv = crypto.randomBytes(12);
71
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
72
+ const aad = buildAad(sessionId, 'h2c', version, epoch);
73
+ cipher.setAAD(Buffer.from(aad, 'utf8'));
74
+ let ciphertext = cipher.update(envelopeBuffer);
75
+ ciphertext = Buffer.concat([ciphertext, cipher.final()]);
76
+ const tag = cipher.getAuthTag();
77
+ return Buffer.concat([iv, tag, ciphertext]);
78
+ }
79
+ export async function runRelayConnection(session, relayUrl, requestHandler) {
80
+ // Create new connection state with fresh epoch
81
+ const connState = {
82
+ inboundMaxSeq: 0,
83
+ outboundSeq: 0,
84
+ handshakeState: 'WAIT_HELLO',
85
+ protocolVersion: 2, // Default to v2, will be negotiated in handshake
86
+ epoch: crypto.randomBytes(8).toString('hex'), // New epoch per connection
87
+ };
88
+ logger.info(JSON.stringify({
89
+ event: 'relay_connect_attempt',
90
+ sessionIdHash: session.sessionId.slice(0, 8),
91
+ epoch: connState.epoch.slice(0, 8),
92
+ timestamp: Date.now(),
93
+ }));
94
+ const ws = new WebSocket(`${relayUrl}?role=host&session=${session.sessionId}`);
95
+ ws.on('open', () => {
96
+ logger.info(JSON.stringify({
97
+ event: 'relay_connected',
98
+ sessionIdHash: session.sessionId.slice(0, 8),
99
+ timestamp: Date.now(),
100
+ }));
101
+ session.reconnectAttempts = 0;
102
+ });
103
+ ws.on('message', async (data) => {
104
+ try {
105
+ // Handle relay control messages (unencrypted JSON strings)
106
+ if (typeof data === 'string') {
107
+ try {
108
+ const ctrl = JSON.parse(data);
109
+ if (ctrl.type === 'RELAY_STATUS') {
110
+ logger.info(JSON.stringify({
111
+ event: 'relay_status',
112
+ status: ctrl.status,
113
+ sessionIdHash: session.sessionId.slice(0, 8),
114
+ timestamp: Date.now(),
115
+ }));
116
+ // No action needed for now - client handles reconnect
117
+ }
118
+ }
119
+ catch {
120
+ // Not JSON, silently ignore
121
+ }
122
+ return;
123
+ }
124
+ // Encrypted messages must be Buffer
125
+ if (!Buffer.isBuffer(data)) {
126
+ return;
127
+ }
128
+ const iv = data.subarray(0, 12);
129
+ const ciphertext = data.subarray(12);
130
+ const tag = ciphertext.subarray(0, 16);
131
+ const actualCiphertext = ciphertext.subarray(16);
132
+ // For HELLO, we need to try both v1 and v2 AAD since we don't know client version yet
133
+ let envelope;
134
+ let usedVersion = 2;
135
+ // Try v2 AAD first (with epoch), then v1
136
+ const aadV2 = buildAad(session.sessionId, 'c2h', 2, connState.epoch);
137
+ const aadV1 = buildAad(session.sessionId, 'c2h', 1);
138
+ for (const { aad, version } of [
139
+ { aad: aadV2, version: 2 },
140
+ { aad: aadV1, version: 1 },
141
+ ]) {
142
+ try {
143
+ const decipher = crypto.createDecipheriv('aes-256-gcm', session.key, iv);
144
+ decipher.setAAD(Buffer.from(aad, 'utf8'));
145
+ decipher.setAuthTag(tag);
146
+ let decrypted = decipher.update(actualCiphertext);
147
+ decrypted = Buffer.concat([decrypted, decipher.final()]);
148
+ envelope = JSON.parse(decrypted.toString('utf8'));
149
+ usedVersion = version;
150
+ break;
151
+ }
152
+ catch {
153
+ // Try next AAD
154
+ continue;
155
+ }
156
+ }
157
+ if (!envelope) {
158
+ logger.warn(JSON.stringify({
159
+ event: 'decrypt_failed',
160
+ sessionIdHash: session.sessionId.slice(0, 8),
161
+ timestamp: Date.now(),
162
+ }));
163
+ return;
164
+ }
165
+ // Validate envelope sequence
166
+ if (envelope.dir !== 'c2h' ||
167
+ envelope.seq !== connState.inboundMaxSeq + 1) {
168
+ logger.warn(JSON.stringify({
169
+ event: 'invalid_envelope',
170
+ reason: envelope.seq !== connState.inboundMaxSeq + 1
171
+ ? 'seq_mismatch'
172
+ : 'invalid_dir',
173
+ expected: connState.inboundMaxSeq + 1,
174
+ got: envelope.seq,
175
+ sessionIdHash: session.sessionId.slice(0, 8),
176
+ timestamp: Date.now(),
177
+ }));
178
+ return;
179
+ }
180
+ connState.inboundMaxSeq = envelope.seq;
181
+ if (envelope.type === 'HELLO') {
182
+ // Version negotiation
183
+ const clientProtocols = envelope.payload
184
+ .protocols || [1];
185
+ const supportedVersions = ALLOW_INSECURE_V1
186
+ ? [PROTOCOL_VERSIONS.V2, PROTOCOL_VERSIONS.V1]
187
+ : [PROTOCOL_VERSIONS.V2];
188
+ const selectedVersion = supportedVersions.find((v) => clientProtocols.includes(v));
189
+ if (!selectedVersion) {
190
+ // Client too old, send error
191
+ const errorEnvelope = {
192
+ v: usedVersion,
193
+ type: 'ERROR',
194
+ dir: 'h2c',
195
+ seq: ++connState.outboundSeq,
196
+ ts: Date.now(),
197
+ payload: {
198
+ code: 'VERSION_MISMATCH',
199
+ message: 'Client too old, update required. Server requires protocol v2.',
200
+ },
201
+ };
202
+ const errorPayload = encryptEnvelope(errorEnvelope, session.key, session.sessionId, usedVersion, connState.epoch);
203
+ ws.send(errorPayload);
204
+ ws.close(1002, 'Protocol version mismatch');
205
+ return;
206
+ }
207
+ connState.protocolVersion = selectedVersion;
208
+ connState.handshakeState = 'READY';
209
+ const ackEnvelope = {
210
+ v: selectedVersion,
211
+ type: 'HELLO_ACK',
212
+ dir: 'h2c',
213
+ seq: ++connState.outboundSeq,
214
+ ts: Date.now(),
215
+ epoch: selectedVersion === 2 ? connState.epoch : undefined,
216
+ payload: {
217
+ selectedVersion,
218
+ requiresPairing: session.pairingRequired,
219
+ ...(selectedVersion === 2 ? { epoch: connState.epoch } : {}),
220
+ },
221
+ };
222
+ const ackPayload = encryptEnvelope(ackEnvelope, session.key, session.sessionId, selectedVersion, selectedVersion === 2 ? connState.epoch : undefined);
223
+ ws.send(ackPayload);
224
+ logger.info(JSON.stringify({
225
+ event: 'handshake_complete',
226
+ protocolVersion: selectedVersion,
227
+ sessionIdHash: session.sessionId.slice(0, 8),
228
+ timestamp: Date.now(),
229
+ }));
230
+ return;
231
+ }
232
+ if (connState.handshakeState !== 'READY') {
233
+ logger.warn(JSON.stringify({
234
+ event: 'message_before_handshake',
235
+ sessionIdHash: session.sessionId.slice(0, 8),
236
+ timestamp: Date.now(),
237
+ }));
238
+ return;
239
+ }
240
+ // For v2, validate epoch in subsequent messages
241
+ if (connState.protocolVersion === 2 &&
242
+ envelope.epoch !== connState.epoch) {
243
+ logger.warn(JSON.stringify({
244
+ event: 'epoch_mismatch',
245
+ sessionIdHash: session.sessionId.slice(0, 8),
246
+ timestamp: Date.now(),
247
+ }));
248
+ return;
249
+ }
250
+ if (envelope.type === 'PAIR') {
251
+ const code = envelope.payload.code;
252
+ const success = code === session.pairingCode;
253
+ if (success) {
254
+ session.pairingRequired = false;
255
+ logger.info(JSON.stringify({
256
+ event: 'pairing_success',
257
+ sessionIdHash: session.sessionId.slice(0, 8),
258
+ timestamp: Date.now(),
259
+ }));
260
+ }
261
+ else {
262
+ logger.warn(JSON.stringify({
263
+ event: 'pairing_failure',
264
+ sessionIdHash: session.sessionId.slice(0, 8),
265
+ timestamp: Date.now(),
266
+ }));
267
+ }
268
+ // Send pairing result back to client
269
+ const pairResultEnvelope = {
270
+ v: connState.protocolVersion,
271
+ type: success ? 'PAIR_ACK' : 'ERROR',
272
+ dir: 'h2c',
273
+ seq: ++connState.outboundSeq,
274
+ ts: Date.now(),
275
+ epoch: connState.protocolVersion === 2 ? connState.epoch : undefined,
276
+ payload: {
277
+ success,
278
+ message: success ? 'Paired successfully' : 'Invalid pairing code',
279
+ },
280
+ };
281
+ const pairResultPayload = encryptEnvelope(pairResultEnvelope, session.key, session.sessionId, connState.protocolVersion, connState.protocolVersion === 2 ? connState.epoch : undefined);
282
+ ws.send(pairResultPayload);
283
+ return;
284
+ }
285
+ if (envelope.type === 'RPC' && !session.pairingRequired) {
286
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
287
+ const result = await requestHandler.handle(envelope.payload);
288
+ let response;
289
+ if (result && typeof result[Symbol.asyncIterator] === 'function') {
290
+ const responses = [];
291
+ for await (const chunk of result) {
292
+ responses.push(chunk);
293
+ }
294
+ response = responses[responses.length - 1];
295
+ }
296
+ else {
297
+ response = result;
298
+ }
299
+ // Encrypt Response
300
+ const respEnvelope = {
301
+ v: connState.protocolVersion,
302
+ type: 'RPC',
303
+ dir: 'h2c',
304
+ seq: ++connState.outboundSeq,
305
+ ts: Date.now(),
306
+ epoch: connState.protocolVersion === 2 ? connState.epoch : undefined,
307
+ payload: response,
308
+ };
309
+ const responsePayload = encryptEnvelope(respEnvelope, session.key, session.sessionId, connState.protocolVersion, connState.protocolVersion === 2 ? connState.epoch : undefined);
310
+ ws.send(responsePayload);
311
+ }
312
+ }
313
+ catch (e) {
314
+ logger.error(JSON.stringify({
315
+ event: 'relay_message_error',
316
+ sessionIdHash: session.sessionId.slice(0, 8),
317
+ error: e.message,
318
+ timestamp: Date.now(),
319
+ }));
320
+ }
321
+ });
322
+ ws.on('error', (e) => {
323
+ logger.error(JSON.stringify({
324
+ event: 'relay_ws_error',
325
+ sessionIdHash: session.sessionId.slice(0, 8),
326
+ error: e.message,
327
+ timestamp: Date.now(),
328
+ }));
329
+ });
330
+ ws.on('close', () => {
331
+ session.reconnectAttempts++;
332
+ const delay = Math.min(5000 * Math.pow(2, session.reconnectAttempts - 1), 60000);
333
+ logger.warn(JSON.stringify({
334
+ event: 'relay_disconnected',
335
+ sessionIdHash: session.sessionId.slice(0, 8),
336
+ retryDelay: delay,
337
+ timestamp: Date.now(),
338
+ }));
339
+ setTimeout(() => runRelayConnection(session, relayUrl, requestHandler), delay);
340
+ });
341
+ }
342
+ //# sourceMappingURL=relay.js.map