@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.
- package/README.md +5 -0
- package/dist/.last_build +0 -0
- package/dist/a2a-server.mjs +415698 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/src/agent/executor.d.ts +41 -0
- package/dist/src/agent/executor.js +408 -0
- package/dist/src/agent/executor.js.map +1 -0
- package/dist/src/agent/task.d.ts +67 -0
- package/dist/src/agent/task.js +799 -0
- package/dist/src/agent/task.js.map +1 -0
- package/dist/src/agent/task.test.d.ts +7 -0
- package/dist/src/agent/task.test.js +435 -0
- package/dist/src/agent/task.test.js.map +1 -0
- package/dist/src/agent/task.token.test.d.ts +7 -0
- package/dist/src/agent/task.token.test.js +53 -0
- package/dist/src/agent/task.token.test.js.map +1 -0
- package/dist/src/auth/llmAuthManager.d.ts +39 -0
- package/dist/src/auth/llmAuthManager.js +209 -0
- package/dist/src/auth/llmAuthManager.js.map +1 -0
- package/dist/src/auth/llmAuthManager.test.d.ts +7 -0
- package/dist/src/auth/llmAuthManager.test.js +92 -0
- package/dist/src/auth/llmAuthManager.test.js.map +1 -0
- package/dist/src/commands/command-registry.d.ts +16 -0
- package/dist/src/commands/command-registry.js +35 -0
- package/dist/src/commands/command-registry.js.map +1 -0
- package/dist/src/commands/command-registry.test.d.ts +7 -0
- package/dist/src/commands/command-registry.test.js +100 -0
- package/dist/src/commands/command-registry.test.js.map +1 -0
- package/dist/src/commands/extensions.d.ts +19 -0
- package/dist/src/commands/extensions.js +26 -0
- package/dist/src/commands/extensions.js.map +1 -0
- package/dist/src/commands/extensions.test.d.ts +7 -0
- package/dist/src/commands/extensions.test.js +70 -0
- package/dist/src/commands/extensions.test.js.map +1 -0
- package/dist/src/commands/init.d.ts +16 -0
- package/dist/src/commands/init.js +111 -0
- package/dist/src/commands/init.js.map +1 -0
- package/dist/src/commands/init.test.d.ts +7 -0
- package/dist/src/commands/init.test.js +146 -0
- package/dist/src/commands/init.test.js.map +1 -0
- package/dist/src/commands/restore.d.ts +21 -0
- package/dist/src/commands/restore.js +126 -0
- package/dist/src/commands/restore.js.map +1 -0
- package/dist/src/commands/restore.test.d.ts +7 -0
- package/dist/src/commands/restore.test.js +111 -0
- package/dist/src/commands/restore.test.js.map +1 -0
- package/dist/src/commands/types.d.ts +33 -0
- package/dist/src/commands/types.js +8 -0
- package/dist/src/commands/types.js.map +1 -0
- package/dist/src/config/config.d.ts +24 -0
- package/dist/src/config/config.js +140 -0
- package/dist/src/config/config.js.map +1 -0
- package/dist/src/config/extension.d.ts +12 -0
- package/dist/src/config/extension.js +105 -0
- package/dist/src/config/extension.js.map +1 -0
- package/dist/src/config/settings.d.ts +15 -0
- package/dist/src/config/settings.js +20 -0
- package/dist/src/config/settings.js.map +1 -0
- package/dist/src/config/settings.test.d.ts +7 -0
- package/dist/src/config/settings.test.js +170 -0
- package/dist/src/config/settings.test.js.map +1 -0
- package/dist/src/http/app.d.ts +17 -0
- package/dist/src/http/app.js +399 -0
- package/dist/src/http/app.js.map +1 -0
- package/dist/src/http/app.test.d.ts +7 -0
- package/dist/src/http/app.test.js +1048 -0
- package/dist/src/http/app.test.js.map +1 -0
- package/dist/src/http/auth.d.ts +21 -0
- package/dist/src/http/auth.js +55 -0
- package/dist/src/http/auth.js.map +1 -0
- package/dist/src/http/auth.test.d.ts +7 -0
- package/dist/src/http/auth.test.js +53 -0
- package/dist/src/http/auth.test.js.map +1 -0
- package/dist/src/http/authRoutes.test.d.ts +7 -0
- package/dist/src/http/authRoutes.test.js +169 -0
- package/dist/src/http/authRoutes.test.js.map +1 -0
- package/dist/src/http/cors.d.ts +8 -0
- package/dist/src/http/cors.js +96 -0
- package/dist/src/http/cors.js.map +1 -0
- package/dist/src/http/cors.test.d.ts +7 -0
- package/dist/src/http/cors.test.js +62 -0
- package/dist/src/http/cors.test.js.map +1 -0
- package/dist/src/http/deferredAuth.test.d.ts +7 -0
- package/dist/src/http/deferredAuth.test.js +45 -0
- package/dist/src/http/deferredAuth.test.js.map +1 -0
- package/dist/src/http/endpoints.test.d.ts +7 -0
- package/dist/src/http/endpoints.test.js +149 -0
- package/dist/src/http/endpoints.test.js.map +1 -0
- package/dist/src/http/llmAuthMiddleware.d.ts +9 -0
- package/dist/src/http/llmAuthMiddleware.js +37 -0
- package/dist/src/http/llmAuthMiddleware.js.map +1 -0
- package/dist/src/http/relay.d.ts +28 -0
- package/dist/src/http/relay.js +342 -0
- package/dist/src/http/relay.js.map +1 -0
- package/dist/src/http/relay.test.d.ts +7 -0
- package/dist/src/http/relay.test.js +149 -0
- package/dist/src/http/relay.test.js.map +1 -0
- package/dist/src/http/replay.d.ts +19 -0
- package/dist/src/http/replay.js +90 -0
- package/dist/src/http/replay.js.map +1 -0
- package/dist/src/http/replay.test.d.ts +7 -0
- package/dist/src/http/replay.test.js +78 -0
- package/dist/src/http/replay.test.js.map +1 -0
- package/dist/src/http/requestStorage.d.ts +11 -0
- package/dist/src/http/requestStorage.js +9 -0
- package/dist/src/http/requestStorage.js.map +1 -0
- package/dist/src/http/routes/auth.d.ts +9 -0
- package/dist/src/http/routes/auth.js +125 -0
- package/dist/src/http/routes/auth.js.map +1 -0
- package/dist/src/http/server.d.ts +8 -0
- package/dist/src/http/server.js +28 -0
- package/dist/src/http/server.js.map +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +11 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/persistence/gcs.d.ts +25 -0
- package/dist/src/persistence/gcs.js +248 -0
- package/dist/src/persistence/gcs.js.map +1 -0
- package/dist/src/persistence/gcs.test.d.ts +7 -0
- package/dist/src/persistence/gcs.test.js +335 -0
- package/dist/src/persistence/gcs.test.js.map +1 -0
- package/dist/src/persistence/remoteAuthStore.d.ts +21 -0
- package/dist/src/persistence/remoteAuthStore.js +74 -0
- package/dist/src/persistence/remoteAuthStore.js.map +1 -0
- package/dist/src/types.d.ts +100 -0
- package/dist/src/types.js +49 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/envAliases.d.ts +7 -0
- package/dist/src/utils/envAliases.js +9 -0
- package/dist/src/utils/envAliases.js.map +1 -0
- package/dist/src/utils/executor_utils.d.ts +8 -0
- package/dist/src/utils/executor_utils.js +42 -0
- package/dist/src/utils/executor_utils.js.map +1 -0
- package/dist/src/utils/logger.d.ts +9 -0
- package/dist/src/utils/logger.js +26 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/redactSecrets.d.ts +16 -0
- package/dist/src/utils/redactSecrets.js +72 -0
- package/dist/src/utils/redactSecrets.js.map +1 -0
- package/dist/src/utils/redactSecrets.test.d.ts +7 -0
- package/dist/src/utils/redactSecrets.test.js +62 -0
- package/dist/src/utils/redactSecrets.test.js.map +1 -0
- package/dist/src/utils/testing_utils.d.ts +48 -0
- package/dist/src/utils/testing_utils.js +173 -0
- package/dist/src/utils/testing_utils.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/web-client/app.js +526 -0
- package/dist/web-client/index.html +43 -0
- package/dist/web-client/package.json +10 -0
- package/dist/web-client/relay-client.js +330 -0
- package/dist/web-client/style.css +189 -0
- package/package.json +53 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* Portions Copyright 2025 TerminaI Authors
|
|
5
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
6
|
+
*/
|
|
7
|
+
export * from './agent/executor.js';
|
|
8
|
+
export * from './http/app.js';
|
|
9
|
+
export * from './persistence/remoteAuthStore.js';
|
|
10
|
+
export * from './types.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,kCAAkC,CAAC;AACjD,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
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 { Task as SDKTask } from '@a2a-js/sdk';
|
|
8
|
+
import type { TaskStore } from '@a2a-js/sdk/server';
|
|
9
|
+
export declare class GCSTaskStore implements TaskStore {
|
|
10
|
+
private storage;
|
|
11
|
+
private bucketName;
|
|
12
|
+
private bucketInitialized;
|
|
13
|
+
constructor(bucketName: string);
|
|
14
|
+
private initializeBucket;
|
|
15
|
+
private ensureBucketInitialized;
|
|
16
|
+
private getObjectPath;
|
|
17
|
+
save(task: SDKTask): Promise<void>;
|
|
18
|
+
load(taskId: string): Promise<SDKTask | undefined>;
|
|
19
|
+
}
|
|
20
|
+
export declare class NoOpTaskStore implements TaskStore {
|
|
21
|
+
private realStore;
|
|
22
|
+
constructor(realStore: TaskStore);
|
|
23
|
+
save(task: SDKTask): Promise<void>;
|
|
24
|
+
load(taskId: string): Promise<SDKTask | undefined>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* Portions Copyright 2025 TerminaI Authors
|
|
5
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
6
|
+
*/
|
|
7
|
+
import { Storage } from '@google-cloud/storage';
|
|
8
|
+
import { gzipSync, gunzipSync } from 'node:zlib';
|
|
9
|
+
import * as tar from 'tar';
|
|
10
|
+
import * as fse from 'fs-extra';
|
|
11
|
+
import { promises as fsPromises, createReadStream } from 'node:fs';
|
|
12
|
+
import { tmpdir } from 'node:os';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { logger } from '../utils/logger.js';
|
|
15
|
+
import { setTargetDir } from '../config/config.js';
|
|
16
|
+
import { getPersistedState } from '../types.js';
|
|
17
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
18
|
+
const getTmpArchiveFilename = (taskId) => `task-${taskId}-workspace-${uuidv4()}.tar.gz`;
|
|
19
|
+
// Validate the taskId to prevent path traversal attacks by ensuring it only contains safe characters.
|
|
20
|
+
const isTaskIdValid = (taskId) => {
|
|
21
|
+
// Allow only alphanumeric characters, dashes, and underscores, and ensure it's not empty.
|
|
22
|
+
const validTaskIdRegex = /^[a-zA-Z0-9_-]+$/;
|
|
23
|
+
return validTaskIdRegex.test(taskId);
|
|
24
|
+
};
|
|
25
|
+
export class GCSTaskStore {
|
|
26
|
+
storage;
|
|
27
|
+
bucketName;
|
|
28
|
+
bucketInitialized;
|
|
29
|
+
constructor(bucketName) {
|
|
30
|
+
if (!bucketName) {
|
|
31
|
+
throw new Error('GCS bucket name is required.');
|
|
32
|
+
}
|
|
33
|
+
this.storage = new Storage();
|
|
34
|
+
this.bucketName = bucketName;
|
|
35
|
+
logger.info(`GCSTaskStore initializing with bucket: ${this.bucketName}`);
|
|
36
|
+
// Prerequisites: user account or service account must have storage admin IAM role
|
|
37
|
+
// and the bucket name must be unique.
|
|
38
|
+
this.bucketInitialized = this.initializeBucket();
|
|
39
|
+
}
|
|
40
|
+
async initializeBucket() {
|
|
41
|
+
try {
|
|
42
|
+
const [buckets] = await this.storage.getBuckets();
|
|
43
|
+
const exists = buckets.some((bucket) => bucket.name === this.bucketName);
|
|
44
|
+
if (!exists) {
|
|
45
|
+
logger.info(`Bucket ${this.bucketName} does not exist in the list. Attempting to create...`);
|
|
46
|
+
try {
|
|
47
|
+
await this.storage.createBucket(this.bucketName);
|
|
48
|
+
logger.info(`Bucket ${this.bucketName} created successfully.`);
|
|
49
|
+
}
|
|
50
|
+
catch (createError) {
|
|
51
|
+
logger.info(`Failed to create bucket ${this.bucketName}: ${createError}`);
|
|
52
|
+
throw new Error(`Failed to create GCS bucket ${this.bucketName}: ${createError}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
logger.info(`Bucket ${this.bucketName} exists.`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
logger.info(`Error during bucket initialization for ${this.bucketName}: ${error}`);
|
|
61
|
+
throw new Error(`Failed to initialize GCS bucket ${this.bucketName}: ${error}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async ensureBucketInitialized() {
|
|
65
|
+
await this.bucketInitialized;
|
|
66
|
+
}
|
|
67
|
+
getObjectPath(taskId, type) {
|
|
68
|
+
if (!isTaskIdValid(taskId)) {
|
|
69
|
+
throw new Error(`Invalid taskId: ${taskId}`);
|
|
70
|
+
}
|
|
71
|
+
return `tasks/${taskId}/${type}.tar.gz`;
|
|
72
|
+
}
|
|
73
|
+
async save(task) {
|
|
74
|
+
await this.ensureBucketInitialized();
|
|
75
|
+
const taskId = task.id;
|
|
76
|
+
const persistedState = getPersistedState(task.metadata);
|
|
77
|
+
if (!persistedState) {
|
|
78
|
+
throw new Error(`Task ${taskId} is missing persisted state in metadata.`);
|
|
79
|
+
}
|
|
80
|
+
const workDir = process.cwd();
|
|
81
|
+
const metadataObjectPath = this.getObjectPath(taskId, 'metadata');
|
|
82
|
+
const workspaceObjectPath = this.getObjectPath(taskId, 'workspace');
|
|
83
|
+
const dataToStore = task.metadata;
|
|
84
|
+
try {
|
|
85
|
+
const jsonString = JSON.stringify(dataToStore);
|
|
86
|
+
const compressedMetadata = gzipSync(Buffer.from(jsonString));
|
|
87
|
+
const metadataFile = this.storage
|
|
88
|
+
.bucket(this.bucketName)
|
|
89
|
+
.file(metadataObjectPath);
|
|
90
|
+
await metadataFile.save(compressedMetadata, {
|
|
91
|
+
contentType: 'application/gzip',
|
|
92
|
+
});
|
|
93
|
+
logger.info(`Task ${taskId} metadata saved to GCS: gs://${this.bucketName}/${metadataObjectPath}`);
|
|
94
|
+
if (await fse.pathExists(workDir)) {
|
|
95
|
+
const entries = await fsPromises.readdir(workDir);
|
|
96
|
+
if (entries.length > 0) {
|
|
97
|
+
const tmpArchiveFile = join(tmpdir(), getTmpArchiveFilename(taskId));
|
|
98
|
+
try {
|
|
99
|
+
await tar.c({
|
|
100
|
+
gzip: true,
|
|
101
|
+
file: tmpArchiveFile,
|
|
102
|
+
cwd: workDir,
|
|
103
|
+
portable: true,
|
|
104
|
+
}, entries);
|
|
105
|
+
if (!(await fse.pathExists(tmpArchiveFile))) {
|
|
106
|
+
throw new Error(`tar.c command failed to create ${tmpArchiveFile}`);
|
|
107
|
+
}
|
|
108
|
+
const workspaceFile = this.storage
|
|
109
|
+
.bucket(this.bucketName)
|
|
110
|
+
.file(workspaceObjectPath);
|
|
111
|
+
const sourceStream = createReadStream(tmpArchiveFile);
|
|
112
|
+
const destStream = workspaceFile.createWriteStream({
|
|
113
|
+
contentType: 'application/gzip',
|
|
114
|
+
resumable: true,
|
|
115
|
+
});
|
|
116
|
+
await new Promise((resolve, reject) => {
|
|
117
|
+
sourceStream.on('error', (err) => {
|
|
118
|
+
logger.error(`Error in source stream for ${tmpArchiveFile}:`, err);
|
|
119
|
+
// Attempt to close destStream if source fails
|
|
120
|
+
if (!destStream.destroyed) {
|
|
121
|
+
destStream.destroy(err);
|
|
122
|
+
}
|
|
123
|
+
reject(err);
|
|
124
|
+
});
|
|
125
|
+
destStream.on('error', (err) => {
|
|
126
|
+
logger.error(`Error in GCS dest stream for ${workspaceObjectPath}:`, err);
|
|
127
|
+
reject(err);
|
|
128
|
+
});
|
|
129
|
+
destStream.on('finish', () => {
|
|
130
|
+
logger.info(`GCS destStream finished for ${workspaceObjectPath}`);
|
|
131
|
+
resolve();
|
|
132
|
+
});
|
|
133
|
+
logger.info(`Piping ${tmpArchiveFile} to GCS object ${workspaceObjectPath}`);
|
|
134
|
+
sourceStream.pipe(destStream);
|
|
135
|
+
});
|
|
136
|
+
logger.info(`Task ${taskId} workspace saved to GCS: gs://${this.bucketName}/${workspaceObjectPath}`);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
logger.error(`Error during workspace save process for ${taskId}:`, error);
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
logger.info(`Cleaning up temporary file: ${tmpArchiveFile}`);
|
|
144
|
+
try {
|
|
145
|
+
if (await fse.pathExists(tmpArchiveFile)) {
|
|
146
|
+
await fse.remove(tmpArchiveFile);
|
|
147
|
+
logger.info(`Successfully removed temporary file: ${tmpArchiveFile}`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
logger.warn(`Temporary file not found for cleanup: ${tmpArchiveFile}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (removeError) {
|
|
154
|
+
logger.error(`Error removing temporary file ${tmpArchiveFile}:`, removeError);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
logger.info(`Workspace directory ${workDir} is empty, skipping workspace save for task ${taskId}.`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
logger.info(`Workspace directory ${workDir} not found, skipping workspace save for task ${taskId}.`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
logger.error(`Failed to save task ${taskId} to GCS:`, error);
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async load(taskId) {
|
|
172
|
+
await this.ensureBucketInitialized();
|
|
173
|
+
const metadataObjectPath = this.getObjectPath(taskId, 'metadata');
|
|
174
|
+
const workspaceObjectPath = this.getObjectPath(taskId, 'workspace');
|
|
175
|
+
try {
|
|
176
|
+
const metadataFile = this.storage
|
|
177
|
+
.bucket(this.bucketName)
|
|
178
|
+
.file(metadataObjectPath);
|
|
179
|
+
const [metadataExists] = await metadataFile.exists();
|
|
180
|
+
if (!metadataExists) {
|
|
181
|
+
logger.info(`Task ${taskId} metadata not found in GCS.`);
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const [compressedMetadata] = await metadataFile.download();
|
|
185
|
+
const jsonData = gunzipSync(compressedMetadata).toString();
|
|
186
|
+
const loadedMetadata = JSON.parse(jsonData);
|
|
187
|
+
logger.info(`Task ${taskId} metadata loaded from GCS.`);
|
|
188
|
+
const persistedState = getPersistedState(loadedMetadata);
|
|
189
|
+
if (!persistedState) {
|
|
190
|
+
throw new Error(`Loaded metadata for task ${taskId} is missing internal persisted state.`);
|
|
191
|
+
}
|
|
192
|
+
const agentSettings = persistedState._agentSettings;
|
|
193
|
+
const workDir = setTargetDir(agentSettings);
|
|
194
|
+
await fse.ensureDir(workDir);
|
|
195
|
+
const workspaceFile = this.storage
|
|
196
|
+
.bucket(this.bucketName)
|
|
197
|
+
.file(workspaceObjectPath);
|
|
198
|
+
const [workspaceExists] = await workspaceFile.exists();
|
|
199
|
+
if (workspaceExists) {
|
|
200
|
+
const tmpArchiveFile = join(tmpdir(), getTmpArchiveFilename(taskId));
|
|
201
|
+
try {
|
|
202
|
+
await workspaceFile.download({ destination: tmpArchiveFile });
|
|
203
|
+
await tar.x({ file: tmpArchiveFile, cwd: workDir });
|
|
204
|
+
logger.info(`Task ${taskId} workspace restored from GCS to ${workDir}`);
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
if (await fse.pathExists(tmpArchiveFile)) {
|
|
208
|
+
await fse.remove(tmpArchiveFile);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
logger.info(`Task ${taskId} workspace archive not found in GCS.`);
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
id: taskId,
|
|
217
|
+
contextId: loadedMetadata._contextId || uuidv4(),
|
|
218
|
+
kind: 'task',
|
|
219
|
+
status: {
|
|
220
|
+
state: persistedState._taskState,
|
|
221
|
+
timestamp: new Date().toISOString(),
|
|
222
|
+
},
|
|
223
|
+
metadata: loadedMetadata,
|
|
224
|
+
history: [],
|
|
225
|
+
artifacts: [],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
logger.error(`Failed to load task ${taskId} from GCS:`, error);
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
export class NoOpTaskStore {
|
|
235
|
+
realStore;
|
|
236
|
+
constructor(realStore) {
|
|
237
|
+
this.realStore = realStore;
|
|
238
|
+
}
|
|
239
|
+
async save(task) {
|
|
240
|
+
logger.info(`[NoOpTaskStore] save called for task ${task.id} - IGNORED`);
|
|
241
|
+
return Promise.resolve();
|
|
242
|
+
}
|
|
243
|
+
async load(taskId) {
|
|
244
|
+
logger.info(`[NoOpTaskStore] load called for task ${taskId}, delegating to real store.`);
|
|
245
|
+
return this.realStore.load(taskId);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=gcs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gcs.js","sourceRoot":"","sources":["../../../src/persistence/gcs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* Portions Copyright 2025 TerminaI Authors
|
|
5
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
6
|
+
*/
|
|
7
|
+
import { Storage } from '@google-cloud/storage';
|
|
8
|
+
import * as fse from 'fs-extra';
|
|
9
|
+
import * as tar from 'tar';
|
|
10
|
+
import { gzipSync, gunzipSync } from 'node:zlib';
|
|
11
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
12
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
13
|
+
import { GCSTaskStore, NoOpTaskStore } from './gcs.js';
|
|
14
|
+
import { logger } from '../utils/logger.js';
|
|
15
|
+
import * as configModule from '../config/config.js';
|
|
16
|
+
import { getPersistedState, METADATA_KEY } from '../types.js';
|
|
17
|
+
// Mock dependencies
|
|
18
|
+
const fsMocks = vi.hoisted(() => ({
|
|
19
|
+
readdir: vi.fn(),
|
|
20
|
+
createReadStream: vi.fn(),
|
|
21
|
+
}));
|
|
22
|
+
vi.mock('@google-cloud/storage');
|
|
23
|
+
vi.mock('fs-extra', () => ({
|
|
24
|
+
pathExists: vi.fn(),
|
|
25
|
+
readdir: vi.fn(),
|
|
26
|
+
remove: vi.fn(),
|
|
27
|
+
ensureDir: vi.fn(),
|
|
28
|
+
createReadStream: vi.fn(),
|
|
29
|
+
}));
|
|
30
|
+
vi.mock('node:fs', async () => {
|
|
31
|
+
const actual = await vi.importActual('node:fs');
|
|
32
|
+
return {
|
|
33
|
+
...actual,
|
|
34
|
+
promises: {
|
|
35
|
+
...actual.promises,
|
|
36
|
+
readdir: fsMocks.readdir,
|
|
37
|
+
},
|
|
38
|
+
createReadStream: fsMocks.createReadStream,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
vi.mock('fs', async () => {
|
|
42
|
+
const actual = await vi.importActual('node:fs');
|
|
43
|
+
return {
|
|
44
|
+
...actual,
|
|
45
|
+
promises: {
|
|
46
|
+
...actual.promises,
|
|
47
|
+
readdir: fsMocks.readdir,
|
|
48
|
+
},
|
|
49
|
+
createReadStream: fsMocks.createReadStream,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
vi.mock('tar', async () => {
|
|
53
|
+
const actualFs = await vi.importActual('node:fs');
|
|
54
|
+
return {
|
|
55
|
+
c: vi.fn(({ file }) => {
|
|
56
|
+
if (file) {
|
|
57
|
+
actualFs.writeFileSync(file, Buffer.from('dummy tar content'));
|
|
58
|
+
}
|
|
59
|
+
return Promise.resolve();
|
|
60
|
+
}),
|
|
61
|
+
x: vi.fn().mockResolvedValue(undefined),
|
|
62
|
+
t: vi.fn().mockResolvedValue(undefined),
|
|
63
|
+
r: vi.fn().mockResolvedValue(undefined),
|
|
64
|
+
u: vi.fn().mockResolvedValue(undefined),
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
vi.mock('zlib');
|
|
68
|
+
vi.mock('uuid');
|
|
69
|
+
vi.mock('../utils/logger.js', () => ({
|
|
70
|
+
logger: {
|
|
71
|
+
info: vi.fn(),
|
|
72
|
+
warn: vi.fn(),
|
|
73
|
+
error: vi.fn(),
|
|
74
|
+
debug: vi.fn(),
|
|
75
|
+
},
|
|
76
|
+
}));
|
|
77
|
+
vi.mock('../config/config.js', () => ({
|
|
78
|
+
setTargetDir: vi.fn(),
|
|
79
|
+
}));
|
|
80
|
+
vi.mock('node:stream/promises', () => ({
|
|
81
|
+
pipeline: vi.fn(),
|
|
82
|
+
}));
|
|
83
|
+
vi.mock('../types.js', async (importOriginal) => {
|
|
84
|
+
const actual = await importOriginal();
|
|
85
|
+
return {
|
|
86
|
+
...actual,
|
|
87
|
+
getPersistedState: vi.fn(),
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
const mockStorage = Storage;
|
|
91
|
+
const mockFse = fse;
|
|
92
|
+
const mockCreateReadStream = fsMocks.createReadStream;
|
|
93
|
+
const mockTar = tar;
|
|
94
|
+
const mockGzipSync = gzipSync;
|
|
95
|
+
const mockGunzipSync = gunzipSync;
|
|
96
|
+
const mockUuidv4 = uuidv4;
|
|
97
|
+
const mockSetTargetDir = configModule.setTargetDir;
|
|
98
|
+
const mockGetPersistedState = getPersistedState;
|
|
99
|
+
const TEST_METADATA_KEY = METADATA_KEY || '__persistedState';
|
|
100
|
+
describe('GCSTaskStore', () => {
|
|
101
|
+
let bucketName;
|
|
102
|
+
let mockBucket;
|
|
103
|
+
let mockFile;
|
|
104
|
+
let mockWriteStream;
|
|
105
|
+
let mockStorageInstance;
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
vi.clearAllMocks();
|
|
108
|
+
bucketName = 'test-bucket';
|
|
109
|
+
mockWriteStream = {
|
|
110
|
+
emit: vi.fn().mockReturnValue(true),
|
|
111
|
+
removeListener: vi.fn().mockReturnValue(mockWriteStream),
|
|
112
|
+
on: vi.fn((event, cb) => {
|
|
113
|
+
if (event === 'finish')
|
|
114
|
+
setTimeout(cb, 0); // Simulate async finish
|
|
115
|
+
return mockWriteStream;
|
|
116
|
+
}),
|
|
117
|
+
once: vi.fn((event, cb) => {
|
|
118
|
+
if (event === 'finish')
|
|
119
|
+
setTimeout(cb, 0); // Simulate async finish return mockWriteStream;
|
|
120
|
+
}),
|
|
121
|
+
destroy: vi.fn(),
|
|
122
|
+
write: vi.fn().mockReturnValue(true),
|
|
123
|
+
end: vi.fn(),
|
|
124
|
+
destroyed: false,
|
|
125
|
+
};
|
|
126
|
+
mockFile = {
|
|
127
|
+
save: vi.fn().mockResolvedValue(undefined),
|
|
128
|
+
download: vi.fn().mockResolvedValue([Buffer.from('')]),
|
|
129
|
+
exists: vi.fn().mockResolvedValue([true]),
|
|
130
|
+
createWriteStream: vi.fn().mockReturnValue(mockWriteStream),
|
|
131
|
+
};
|
|
132
|
+
mockBucket = {
|
|
133
|
+
exists: vi.fn().mockResolvedValue([true]),
|
|
134
|
+
file: vi.fn().mockReturnValue(mockFile),
|
|
135
|
+
name: bucketName,
|
|
136
|
+
};
|
|
137
|
+
mockStorageInstance = {
|
|
138
|
+
bucket: vi.fn().mockReturnValue(mockBucket),
|
|
139
|
+
getBuckets: vi.fn().mockResolvedValue([[{ name: bucketName }]]),
|
|
140
|
+
createBucket: vi.fn().mockResolvedValue([mockBucket]),
|
|
141
|
+
};
|
|
142
|
+
mockStorage.mockReturnValue(mockStorageInstance);
|
|
143
|
+
mockUuidv4.mockReturnValue('test-uuid');
|
|
144
|
+
mockSetTargetDir.mockReturnValue('/tmp/workdir');
|
|
145
|
+
mockGetPersistedState.mockReturnValue({
|
|
146
|
+
_agentSettings: {},
|
|
147
|
+
_taskState: 'submitted',
|
|
148
|
+
});
|
|
149
|
+
fse.pathExists.mockResolvedValue(true);
|
|
150
|
+
fsMocks.readdir.mockResolvedValue(['file1.txt']);
|
|
151
|
+
mockFse.remove.mockResolvedValue(undefined);
|
|
152
|
+
mockFse.ensureDir.mockResolvedValue(undefined);
|
|
153
|
+
mockGzipSync.mockReturnValue(Buffer.from('compressed'));
|
|
154
|
+
mockGunzipSync.mockReturnValue(Buffer.from('{}'));
|
|
155
|
+
mockCreateReadStream.mockReturnValue({ on: vi.fn(), pipe: vi.fn() });
|
|
156
|
+
mockFse.createReadStream.mockReturnValue({
|
|
157
|
+
on: vi.fn(),
|
|
158
|
+
pipe: vi.fn(),
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
describe('Constructor & Initialization', () => {
|
|
162
|
+
it('should initialize and check bucket existence', async () => {
|
|
163
|
+
const store = new GCSTaskStore(bucketName);
|
|
164
|
+
await store['ensureBucketInitialized']();
|
|
165
|
+
expect(mockStorage).toHaveBeenCalledTimes(1);
|
|
166
|
+
expect(mockStorageInstance.getBuckets).toHaveBeenCalled();
|
|
167
|
+
expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('Bucket test-bucket exists'));
|
|
168
|
+
});
|
|
169
|
+
it('should create bucket if it does not exist', async () => {
|
|
170
|
+
mockStorageInstance.getBuckets.mockResolvedValue([[]]);
|
|
171
|
+
const store = new GCSTaskStore(bucketName);
|
|
172
|
+
await store['ensureBucketInitialized']();
|
|
173
|
+
expect(mockStorageInstance.createBucket).toHaveBeenCalledWith(bucketName);
|
|
174
|
+
expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('Bucket test-bucket created successfully'));
|
|
175
|
+
});
|
|
176
|
+
it('should throw if bucket creation fails', async () => {
|
|
177
|
+
mockStorageInstance.getBuckets.mockResolvedValue([[]]);
|
|
178
|
+
mockStorageInstance.createBucket.mockRejectedValue(new Error('Create failed'));
|
|
179
|
+
const store = new GCSTaskStore(bucketName);
|
|
180
|
+
await expect(store['ensureBucketInitialized']()).rejects.toThrow('Failed to create GCS bucket test-bucket: Error: Create failed');
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
describe('save', () => {
|
|
184
|
+
const mockTask = {
|
|
185
|
+
id: 'task1',
|
|
186
|
+
contextId: 'ctx1',
|
|
187
|
+
kind: 'task',
|
|
188
|
+
status: { state: 'working' },
|
|
189
|
+
metadata: {},
|
|
190
|
+
};
|
|
191
|
+
it('should save metadata and workspace', async () => {
|
|
192
|
+
const store = new GCSTaskStore(bucketName);
|
|
193
|
+
await store.save(mockTask);
|
|
194
|
+
expect(mockFile.save).toHaveBeenCalledTimes(1);
|
|
195
|
+
expect(mockTar.c).toHaveBeenCalledTimes(1);
|
|
196
|
+
expect(mockFse.remove).toHaveBeenCalledTimes(1);
|
|
197
|
+
expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('metadata saved to GCS'));
|
|
198
|
+
expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('workspace saved to GCS'));
|
|
199
|
+
});
|
|
200
|
+
it('should handle tar creation failure', async () => {
|
|
201
|
+
mockFse.pathExists.mockImplementation(async (path) => !path.toString().includes('task-task1-workspace-test-uuid.tar.gz'));
|
|
202
|
+
const store = new GCSTaskStore(bucketName);
|
|
203
|
+
await expect(store.save(mockTask)).rejects.toThrow('tar.c command failed to create');
|
|
204
|
+
});
|
|
205
|
+
it('should throw an error if taskId contains path traversal sequences', async () => {
|
|
206
|
+
const store = new GCSTaskStore('test-bucket');
|
|
207
|
+
const maliciousTask = {
|
|
208
|
+
id: '../../../malicious-task',
|
|
209
|
+
metadata: {
|
|
210
|
+
_internal: {
|
|
211
|
+
agentSettings: {
|
|
212
|
+
cacheDir: '/tmp/cache',
|
|
213
|
+
dataDir: '/tmp/data',
|
|
214
|
+
logDir: '/tmp/logs',
|
|
215
|
+
tempDir: '/tmp/temp',
|
|
216
|
+
},
|
|
217
|
+
taskState: 'working',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
kind: 'task',
|
|
221
|
+
status: {
|
|
222
|
+
state: 'working',
|
|
223
|
+
timestamp: new Date().toISOString(),
|
|
224
|
+
},
|
|
225
|
+
contextId: 'test-context',
|
|
226
|
+
history: [],
|
|
227
|
+
artifacts: [],
|
|
228
|
+
};
|
|
229
|
+
await expect(store.save(maliciousTask)).rejects.toThrow('Invalid taskId: ../../../malicious-task');
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
describe('load', () => {
|
|
233
|
+
it('should load task metadata and workspace', async () => {
|
|
234
|
+
mockGunzipSync.mockReturnValue(Buffer.from(JSON.stringify({
|
|
235
|
+
[TEST_METADATA_KEY]: {
|
|
236
|
+
_agentSettings: {},
|
|
237
|
+
_taskState: 'submitted',
|
|
238
|
+
},
|
|
239
|
+
_contextId: 'ctx1',
|
|
240
|
+
})));
|
|
241
|
+
mockFile.download.mockResolvedValue([Buffer.from('compressed metadata')]);
|
|
242
|
+
mockFile.download.mockResolvedValueOnce([
|
|
243
|
+
Buffer.from('compressed metadata'),
|
|
244
|
+
]);
|
|
245
|
+
mockBucket.file = vi.fn((path) => {
|
|
246
|
+
const newMockFile = { ...mockFile };
|
|
247
|
+
if (path.includes('metadata')) {
|
|
248
|
+
newMockFile.download = vi
|
|
249
|
+
.fn()
|
|
250
|
+
.mockResolvedValue([Buffer.from('compressed metadata')]);
|
|
251
|
+
newMockFile.exists = vi.fn().mockResolvedValue([true]);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
newMockFile.download = vi
|
|
255
|
+
.fn()
|
|
256
|
+
.mockResolvedValue([Buffer.from('compressed workspace')]);
|
|
257
|
+
newMockFile.exists = vi.fn().mockResolvedValue([true]);
|
|
258
|
+
}
|
|
259
|
+
return newMockFile;
|
|
260
|
+
});
|
|
261
|
+
const store = new GCSTaskStore(bucketName);
|
|
262
|
+
const task = await store.load('task1');
|
|
263
|
+
expect(task).toBeDefined();
|
|
264
|
+
expect(task?.id).toBe('task1');
|
|
265
|
+
expect(mockBucket.file).toHaveBeenCalledWith('tasks/task1/metadata.tar.gz');
|
|
266
|
+
expect(mockBucket.file).toHaveBeenCalledWith('tasks/task1/workspace.tar.gz');
|
|
267
|
+
expect(mockTar.x).toHaveBeenCalledTimes(1);
|
|
268
|
+
expect(mockFse.remove).toHaveBeenCalledTimes(1);
|
|
269
|
+
});
|
|
270
|
+
it('should return undefined if metadata not found', async () => {
|
|
271
|
+
mockFile.exists.mockResolvedValue([false]);
|
|
272
|
+
const store = new GCSTaskStore(bucketName);
|
|
273
|
+
const task = await store.load('task1');
|
|
274
|
+
expect(task).toBeUndefined();
|
|
275
|
+
expect(mockBucket.file).toHaveBeenCalledWith('tasks/task1/metadata.tar.gz');
|
|
276
|
+
});
|
|
277
|
+
it('should load metadata even if workspace not found', async () => {
|
|
278
|
+
mockGunzipSync.mockReturnValue(Buffer.from(JSON.stringify({
|
|
279
|
+
[TEST_METADATA_KEY]: {
|
|
280
|
+
_agentSettings: {},
|
|
281
|
+
_taskState: 'submitted',
|
|
282
|
+
},
|
|
283
|
+
_contextId: 'ctx1',
|
|
284
|
+
})));
|
|
285
|
+
mockBucket.file = vi.fn((path) => {
|
|
286
|
+
const newMockFile = { ...mockFile };
|
|
287
|
+
if (path.includes('workspace.tar.gz')) {
|
|
288
|
+
newMockFile.exists = vi.fn().mockResolvedValue([false]);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
newMockFile.exists = vi.fn().mockResolvedValue([true]);
|
|
292
|
+
newMockFile.download = vi
|
|
293
|
+
.fn()
|
|
294
|
+
.mockResolvedValue([Buffer.from('compressed metadata')]);
|
|
295
|
+
}
|
|
296
|
+
return newMockFile;
|
|
297
|
+
});
|
|
298
|
+
const store = new GCSTaskStore(bucketName);
|
|
299
|
+
const task = await store.load('task1');
|
|
300
|
+
expect(task).toBeDefined();
|
|
301
|
+
expect(mockTar.x).not.toHaveBeenCalled();
|
|
302
|
+
expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('workspace archive not found'));
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
it('should throw an error if taskId contains path traversal sequences', async () => {
|
|
306
|
+
const store = new GCSTaskStore('test-bucket');
|
|
307
|
+
const maliciousTaskId = '../../../malicious-task';
|
|
308
|
+
await expect(store.load(maliciousTaskId)).rejects.toThrow(`Invalid taskId: ${maliciousTaskId}`);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
describe('NoOpTaskStore', () => {
|
|
312
|
+
let realStore;
|
|
313
|
+
let noOpStore;
|
|
314
|
+
beforeEach(() => {
|
|
315
|
+
// Create a mock of the real store to delegate to
|
|
316
|
+
realStore = {
|
|
317
|
+
save: vi.fn(),
|
|
318
|
+
load: vi.fn().mockResolvedValue({ id: 'task-123' }),
|
|
319
|
+
};
|
|
320
|
+
noOpStore = new NoOpTaskStore(realStore);
|
|
321
|
+
});
|
|
322
|
+
it("should not call the real store's save method", async () => {
|
|
323
|
+
const mockTask = { id: 'test-task' };
|
|
324
|
+
await noOpStore.save(mockTask);
|
|
325
|
+
expect(realStore.save).not.toHaveBeenCalled();
|
|
326
|
+
});
|
|
327
|
+
it('should delegate the load method to the real store', async () => {
|
|
328
|
+
const taskId = 'task-123';
|
|
329
|
+
const result = await noOpStore.load(taskId);
|
|
330
|
+
expect(realStore.load).toHaveBeenCalledWith(taskId);
|
|
331
|
+
expect(result).toBeDefined();
|
|
332
|
+
expect(result?.id).toBe(taskId);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
//# sourceMappingURL=gcs.test.js.map
|