@google/adk 0.3.0 → 0.4.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 (183) hide show
  1. package/dist/cjs/a2a/part_converter_utils.js +210 -0
  2. package/dist/cjs/agents/active_streaming_tool.js +1 -1
  3. package/dist/cjs/agents/base_agent.js +3 -3
  4. package/dist/cjs/agents/base_llm_processor.js +1 -1
  5. package/dist/cjs/agents/callback_context.js +1 -1
  6. package/dist/cjs/agents/content_processor_utils.js +1 -1
  7. package/dist/cjs/agents/functions.js +2 -1
  8. package/dist/cjs/agents/instructions.js +1 -1
  9. package/dist/cjs/agents/invocation_context.js +1 -1
  10. package/dist/cjs/agents/live_request_queue.js +1 -1
  11. package/dist/cjs/agents/llm_agent.js +58 -40
  12. package/dist/cjs/agents/loop_agent.js +1 -1
  13. package/dist/cjs/agents/parallel_agent.js +1 -1
  14. package/dist/cjs/agents/readonly_context.js +13 -1
  15. package/dist/cjs/agents/run_config.js +2 -1
  16. package/dist/cjs/agents/sequential_agent.js +1 -1
  17. package/dist/cjs/agents/transcription_entry.js +1 -1
  18. package/dist/cjs/artifacts/base_artifact_service.js +1 -1
  19. package/dist/cjs/artifacts/file_artifact_service.js +491 -0
  20. package/dist/cjs/artifacts/gcs_artifact_service.js +127 -48
  21. package/dist/cjs/artifacts/in_memory_artifact_service.js +54 -6
  22. package/dist/cjs/artifacts/registry.js +55 -0
  23. package/dist/cjs/auth/auth_credential.js +1 -1
  24. package/dist/cjs/auth/auth_handler.js +1 -1
  25. package/dist/cjs/auth/auth_schemes.js +1 -1
  26. package/dist/cjs/auth/auth_tool.js +1 -1
  27. package/dist/cjs/auth/credential_service/base_credential_service.js +1 -1
  28. package/dist/cjs/auth/credential_service/in_memory_credential_service.js +1 -1
  29. package/dist/cjs/auth/exchanger/base_credential_exchanger.js +1 -1
  30. package/dist/cjs/auth/exchanger/credential_exchanger_registry.js +1 -1
  31. package/dist/cjs/code_executors/base_code_executor.js +1 -1
  32. package/dist/cjs/code_executors/built_in_code_executor.js +1 -1
  33. package/dist/cjs/code_executors/code_execution_utils.js +1 -1
  34. package/dist/cjs/code_executors/code_executor_context.js +1 -1
  35. package/dist/cjs/common.js +14 -1
  36. package/dist/cjs/events/event.js +33 -4
  37. package/dist/cjs/events/event_actions.js +2 -2
  38. package/dist/cjs/events/structured_events.js +105 -0
  39. package/dist/cjs/examples/base_example_provider.js +1 -1
  40. package/dist/cjs/examples/example.js +1 -1
  41. package/dist/cjs/examples/example_util.js +1 -1
  42. package/dist/cjs/index.js +54 -83
  43. package/dist/cjs/index_web.js +1 -1
  44. package/dist/cjs/memory/base_memory_service.js +1 -1
  45. package/dist/cjs/memory/in_memory_memory_service.js +1 -1
  46. package/dist/cjs/memory/memory_entry.js +1 -1
  47. package/dist/cjs/models/apigee_llm.js +182 -0
  48. package/dist/cjs/models/base_llm.js +1 -1
  49. package/dist/cjs/models/base_llm_connection.js +1 -1
  50. package/dist/cjs/models/gemini_llm_connection.js +1 -1
  51. package/dist/cjs/models/google_llm.js +70 -51
  52. package/dist/cjs/models/llm_request.js +1 -1
  53. package/dist/cjs/models/llm_response.js +1 -1
  54. package/dist/cjs/models/registry.js +3 -1
  55. package/dist/cjs/plugins/base_plugin.js +1 -1
  56. package/dist/cjs/plugins/logging_plugin.js +1 -1
  57. package/dist/cjs/plugins/plugin_manager.js +1 -1
  58. package/dist/cjs/plugins/security_plugin.js +1 -1
  59. package/dist/cjs/runner/in_memory_runner.js +1 -1
  60. package/dist/cjs/runner/runner.js +32 -1
  61. package/dist/cjs/sessions/base_session_service.js +53 -3
  62. package/dist/cjs/sessions/database_session_service.js +364 -0
  63. package/dist/cjs/sessions/db/operations.js +114 -0
  64. package/dist/cjs/sessions/db/schema.js +204 -0
  65. package/dist/cjs/sessions/in_memory_session_service.js +24 -22
  66. package/dist/cjs/sessions/registry.js +49 -0
  67. package/dist/cjs/sessions/session.js +1 -1
  68. package/dist/cjs/sessions/state.js +1 -1
  69. package/dist/cjs/telemetry/google_cloud.js +1 -1
  70. package/dist/cjs/telemetry/setup.js +1 -1
  71. package/dist/cjs/telemetry/tracing.js +1 -1
  72. package/dist/cjs/tools/agent_tool.js +1 -1
  73. package/dist/cjs/tools/base_tool.js +1 -1
  74. package/dist/cjs/tools/base_toolset.js +1 -1
  75. package/dist/cjs/tools/forwarding_artifact_service.js +17 -1
  76. package/dist/cjs/tools/function_tool.js +1 -1
  77. package/dist/cjs/tools/google_search_tool.js +1 -1
  78. package/dist/cjs/tools/long_running_tool.js +1 -1
  79. package/dist/cjs/tools/mcp/mcp_session_manager.js +1 -1
  80. package/dist/cjs/tools/mcp/mcp_tool.js +1 -1
  81. package/dist/cjs/tools/mcp/mcp_toolset.js +1 -1
  82. package/dist/cjs/tools/tool_confirmation.js +1 -1
  83. package/dist/cjs/tools/tool_context.js +1 -1
  84. package/dist/cjs/utils/client_labels.js +1 -1
  85. package/dist/cjs/utils/env_aware_utils.js +10 -1
  86. package/dist/cjs/utils/gemini_schema_util.js +1 -1
  87. package/dist/cjs/utils/logger.js +1 -1
  88. package/dist/cjs/utils/model_name.js +1 -1
  89. package/dist/cjs/utils/object_notation_utils.js +78 -0
  90. package/dist/cjs/utils/simple_zod_to_json.js +1 -1
  91. package/dist/cjs/utils/variant_utils.js +3 -9
  92. package/dist/cjs/version.js +2 -2
  93. package/dist/esm/a2a/part_converter_utils.js +171 -0
  94. package/dist/esm/agents/base_agent.js +2 -2
  95. package/dist/esm/agents/functions.js +1 -0
  96. package/dist/esm/agents/llm_agent.js +58 -40
  97. package/dist/esm/agents/readonly_context.js +12 -0
  98. package/dist/esm/agents/run_config.js +1 -0
  99. package/dist/esm/artifacts/file_artifact_service.js +451 -0
  100. package/dist/esm/artifacts/gcs_artifact_service.js +126 -47
  101. package/dist/esm/artifacts/in_memory_artifact_service.js +51 -4
  102. package/dist/esm/artifacts/registry.js +28 -0
  103. package/dist/esm/common.js +9 -1
  104. package/dist/esm/events/event.js +29 -2
  105. package/dist/esm/events/event_actions.js +1 -1
  106. package/dist/esm/events/structured_events.js +74 -0
  107. package/dist/esm/index.js +18 -88
  108. package/dist/esm/models/apigee_llm.js +152 -0
  109. package/dist/esm/models/google_llm.js +67 -49
  110. package/dist/esm/models/registry.js +2 -0
  111. package/dist/esm/runner/runner.js +31 -0
  112. package/dist/esm/sessions/base_session_service.js +49 -1
  113. package/dist/esm/sessions/database_session_service.js +350 -0
  114. package/dist/esm/sessions/db/operations.js +87 -0
  115. package/dist/esm/sessions/db/schema.js +172 -0
  116. package/dist/esm/sessions/in_memory_session_service.js +23 -21
  117. package/dist/esm/sessions/registry.js +25 -0
  118. package/dist/esm/tools/forwarding_artifact_service.js +16 -0
  119. package/dist/esm/utils/env_aware_utils.js +8 -0
  120. package/dist/esm/utils/object_notation_utils.js +47 -0
  121. package/dist/esm/utils/variant_utils.js +1 -7
  122. package/dist/esm/version.js +1 -1
  123. package/dist/types/a2a/part_converter_utils.d.ts +47 -0
  124. package/dist/types/agents/llm_agent.d.ts +11 -11
  125. package/dist/types/agents/readonly_context.d.ts +8 -0
  126. package/dist/types/agents/run_config.d.ts +6 -0
  127. package/dist/types/artifacts/base_artifact_service.d.ts +31 -0
  128. package/dist/types/artifacts/file_artifact_service.d.ts +43 -0
  129. package/dist/types/artifacts/gcs_artifact_service.d.ts +3 -1
  130. package/dist/types/artifacts/in_memory_artifact_service.d.ts +5 -2
  131. package/dist/types/artifacts/registry.d.ts +7 -0
  132. package/dist/types/common.d.ts +11 -2
  133. package/dist/types/events/event.d.ts +15 -1
  134. package/dist/types/events/event_actions.d.ts +1 -1
  135. package/dist/types/events/structured_events.d.ts +106 -0
  136. package/dist/types/index.d.ts +5 -1
  137. package/dist/types/models/apigee_llm.d.ts +59 -0
  138. package/dist/types/models/google_llm.d.ts +5 -2
  139. package/dist/types/runner/runner.d.ts +15 -0
  140. package/dist/types/sessions/base_session_service.d.ts +20 -0
  141. package/dist/types/sessions/database_session_service.d.ts +31 -0
  142. package/dist/types/sessions/db/operations.d.ts +29 -0
  143. package/dist/types/sessions/db/schema.d.ts +45 -0
  144. package/dist/types/sessions/in_memory_session_service.d.ts +4 -1
  145. package/dist/types/sessions/registry.d.ts +7 -0
  146. package/dist/types/tools/forwarding_artifact_service.d.ts +3 -1
  147. package/dist/types/utils/env_aware_utils.d.ts +7 -0
  148. package/dist/types/utils/object_notation_utils.d.ts +21 -0
  149. package/dist/types/version.d.ts +1 -1
  150. package/dist/web/a2a/part_converter_utils.js +171 -0
  151. package/dist/web/agents/base_agent.js +2 -2
  152. package/dist/web/agents/functions.js +1 -0
  153. package/dist/web/agents/llm_agent.js +79 -59
  154. package/dist/web/agents/readonly_context.js +12 -0
  155. package/dist/web/agents/run_config.js +2 -1
  156. package/dist/web/artifacts/file_artifact_service.js +506 -0
  157. package/dist/web/artifacts/gcs_artifact_service.js +123 -46
  158. package/dist/web/artifacts/in_memory_artifact_service.js +51 -4
  159. package/dist/web/artifacts/registry.js +28 -0
  160. package/dist/web/common.js +9 -1
  161. package/dist/web/events/event.js +29 -2
  162. package/dist/web/events/event_actions.js +1 -1
  163. package/dist/web/events/structured_events.js +74 -0
  164. package/dist/web/index.js +18 -8
  165. package/dist/web/models/apigee_llm.js +219 -0
  166. package/dist/web/models/google_llm.js +67 -46
  167. package/dist/web/models/registry.js +2 -0
  168. package/dist/web/runner/runner.js +33 -0
  169. package/dist/web/sessions/base_session_service.js +49 -1
  170. package/dist/web/sessions/database_session_service.js +368 -0
  171. package/dist/web/sessions/db/operations.js +87 -0
  172. package/dist/web/sessions/db/schema.js +172 -0
  173. package/dist/web/sessions/in_memory_session_service.js +23 -21
  174. package/dist/web/sessions/registry.js +25 -0
  175. package/dist/web/tools/forwarding_artifact_service.js +16 -0
  176. package/dist/web/utils/env_aware_utils.js +8 -0
  177. package/dist/web/utils/object_notation_utils.js +47 -0
  178. package/dist/web/utils/variant_utils.js +1 -7
  179. package/dist/web/version.js +1 -1
  180. package/package.json +13 -4
  181. package/dist/cjs/index.js.map +0 -7
  182. package/dist/esm/index.js.map +0 -7
  183. package/dist/web/index.js.map +0 -7
@@ -0,0 +1,451 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import * as fs from "fs/promises";
7
+ import * as path from "path";
8
+ import { fileURLToPath, pathToFileURL } from "url";
9
+ import { logger } from "../utils/logger.js";
10
+ const USER_NAMESPACE_PREFIX = "user:";
11
+ class FileArtifactService {
12
+ constructor(rootDirOrUri) {
13
+ try {
14
+ const rootDir = rootDirOrUri.startsWith("file://") ? fileURLToPath(rootDirOrUri) : rootDirOrUri;
15
+ this.rootDir = path.resolve(rootDir);
16
+ } catch (e) {
17
+ throw new Error(`Invalid root directory: ${rootDirOrUri}`, { cause: e });
18
+ }
19
+ }
20
+ async saveArtifact({
21
+ userId,
22
+ sessionId,
23
+ filename,
24
+ artifact,
25
+ customMetadata
26
+ }) {
27
+ if (!artifact.inlineData && !artifact.text) {
28
+ throw new Error("Artifact must have either inlineData or text content.");
29
+ }
30
+ const artifactDir = getArtifactDir(
31
+ this.rootDir,
32
+ userId,
33
+ sessionId,
34
+ filename
35
+ );
36
+ await fs.mkdir(artifactDir, { recursive: true });
37
+ const versions = await getArtifactVersionsFromDir(artifactDir);
38
+ const nextVersion = versions.length > 0 ? versions[versions.length - 1] + 1 : 0;
39
+ const versionsDir = getVersionsDir(artifactDir);
40
+ const versionDir = path.join(versionsDir, nextVersion.toString());
41
+ await fs.mkdir(versionDir, { recursive: true });
42
+ const storedFilename = path.basename(artifactDir);
43
+ const contentPath = path.join(versionDir, storedFilename);
44
+ let mimeType;
45
+ if (artifact.inlineData) {
46
+ const data = artifact.inlineData.data || "";
47
+ await fs.writeFile(contentPath, Buffer.from(data, "base64"));
48
+ mimeType = artifact.inlineData.mimeType || "application/octet-stream";
49
+ } else if (artifact.text !== void 0) {
50
+ await fs.writeFile(contentPath, artifact.text, "utf-8");
51
+ }
52
+ const canonicalUri = await getCanonicalUri(
53
+ this.rootDir,
54
+ userId,
55
+ sessionId,
56
+ filename,
57
+ nextVersion
58
+ );
59
+ const metadata = {
60
+ fileName: filename,
61
+ mimeType,
62
+ version: nextVersion,
63
+ canonicalUri,
64
+ customMetadata
65
+ };
66
+ await writeMetadata(path.join(versionDir, "metadata.json"), metadata);
67
+ return nextVersion;
68
+ }
69
+ async loadArtifact({
70
+ userId,
71
+ sessionId,
72
+ filename,
73
+ version
74
+ }) {
75
+ try {
76
+ const artifactDir = getArtifactDir(
77
+ this.rootDir,
78
+ userId,
79
+ sessionId,
80
+ filename
81
+ );
82
+ try {
83
+ await fs.access(artifactDir);
84
+ } catch (e) {
85
+ logger.warn(
86
+ `[FileArtifactService] loadArtifact: Artifact ${filename} not found`,
87
+ e
88
+ );
89
+ return void 0;
90
+ }
91
+ const versions = await getArtifactVersionsFromDir(artifactDir);
92
+ if (versions.length === 0) {
93
+ return void 0;
94
+ }
95
+ let versionToLoad;
96
+ if (version === void 0) {
97
+ versionToLoad = versions[versions.length - 1];
98
+ } else {
99
+ if (!versions.includes(version)) {
100
+ logger.warn(
101
+ `[FileArtifactService] loadArtifact: Artifact ${filename} version ${version} not found`
102
+ );
103
+ return void 0;
104
+ }
105
+ versionToLoad = version;
106
+ }
107
+ const versionDir = path.join(
108
+ getVersionsDir(artifactDir),
109
+ versionToLoad.toString()
110
+ );
111
+ const metadataPath = path.join(versionDir, "metadata.json");
112
+ const metadata = await readMetadata(metadataPath);
113
+ const storedFilename = path.basename(artifactDir);
114
+ let contentPath = path.join(versionDir, storedFilename);
115
+ if (metadata.canonicalUri) {
116
+ const uriPath = fileUriToPath(metadata.canonicalUri);
117
+ if (uriPath) {
118
+ try {
119
+ await fs.access(uriPath);
120
+ contentPath = uriPath;
121
+ } catch {
122
+ logger.warn(
123
+ `[FileArtifactService] loadArtifact: Artifact ${filename} missing at ${uriPath}, falling back to content path ${contentPath}`
124
+ );
125
+ }
126
+ }
127
+ }
128
+ if (metadata.mimeType) {
129
+ try {
130
+ const data = await fs.readFile(contentPath);
131
+ return {
132
+ inlineData: {
133
+ mimeType: metadata.mimeType,
134
+ data: data.toString("base64")
135
+ }
136
+ };
137
+ } catch {
138
+ logger.warn(
139
+ `[FileArtifactService] loadArtifact: Artifact ${filename} missing at ${contentPath}`
140
+ );
141
+ return void 0;
142
+ }
143
+ }
144
+ try {
145
+ const text = await fs.readFile(contentPath, "utf-8");
146
+ return { text };
147
+ } catch {
148
+ logger.warn(
149
+ `[FileArtifactService] loadArtifact: Text artifact ${filename} missing at ${contentPath}`
150
+ );
151
+ return void 0;
152
+ }
153
+ } catch (e) {
154
+ logger.error(
155
+ `[FileArtifactService] loadArtifact: Error loading artifact ${filename}`,
156
+ e
157
+ );
158
+ return void 0;
159
+ }
160
+ }
161
+ async listArtifactKeys({
162
+ userId,
163
+ sessionId
164
+ }) {
165
+ const filenames = /* @__PURE__ */ new Set();
166
+ const userRoot = getUserRoot(this.rootDir, userId);
167
+ const sessionRoot = getSessionArtifactsDir(userRoot, sessionId);
168
+ for await (const artifactDir of iterateArtifactDirs(sessionRoot)) {
169
+ const metadata = await getLatestMetadata(artifactDir);
170
+ if (metadata == null ? void 0 : metadata.fileName) {
171
+ filenames.add(metadata.fileName);
172
+ } else {
173
+ const rel = path.relative(sessionRoot, artifactDir);
174
+ filenames.add(asPosixPath(rel));
175
+ }
176
+ }
177
+ const artifactsRoot = getUserArtifactsDir(userRoot);
178
+ for await (const artifactDir of iterateArtifactDirs(artifactsRoot)) {
179
+ const metadata = await getLatestMetadata(artifactDir);
180
+ if (metadata == null ? void 0 : metadata.fileName) {
181
+ filenames.add(metadata.fileName);
182
+ } else {
183
+ const rel = path.relative(artifactsRoot, artifactDir);
184
+ filenames.add(`${USER_NAMESPACE_PREFIX}${asPosixPath(rel)}`);
185
+ }
186
+ }
187
+ return Array.from(filenames).sort();
188
+ }
189
+ async deleteArtifact({
190
+ userId,
191
+ sessionId,
192
+ filename
193
+ }) {
194
+ try {
195
+ const artifactDir = getArtifactDir(
196
+ this.rootDir,
197
+ userId,
198
+ sessionId,
199
+ filename
200
+ );
201
+ await fs.rm(artifactDir, { recursive: true, force: true });
202
+ } catch (e) {
203
+ logger.warn(
204
+ `[FileArtifactService] deleteArtifact: Failed to delete artifact ${filename}`,
205
+ e
206
+ );
207
+ }
208
+ }
209
+ async listVersions({
210
+ userId,
211
+ sessionId,
212
+ filename
213
+ }) {
214
+ try {
215
+ const artifactDir = getArtifactDir(
216
+ this.rootDir,
217
+ userId,
218
+ sessionId,
219
+ filename
220
+ );
221
+ return await getArtifactVersionsFromDir(artifactDir);
222
+ } catch (e) {
223
+ logger.warn(
224
+ `[FileArtifactService] listVersions: Failed to list versions for artifact ${filename}`,
225
+ e
226
+ );
227
+ return [];
228
+ }
229
+ }
230
+ async listArtifactVersions({
231
+ userId,
232
+ sessionId,
233
+ filename
234
+ }) {
235
+ try {
236
+ const artifactDir = getArtifactDir(
237
+ this.rootDir,
238
+ userId,
239
+ sessionId,
240
+ filename
241
+ );
242
+ const versions = await getArtifactVersionsFromDir(artifactDir);
243
+ const artifactVersions = [];
244
+ for (const version of versions) {
245
+ const metadataPath = path.join(
246
+ getVersionsDir(artifactDir),
247
+ version.toString(),
248
+ "metadata.json"
249
+ );
250
+ try {
251
+ const metadata = await readMetadata(metadataPath);
252
+ artifactVersions.push(metadata);
253
+ } catch (e) {
254
+ logger.warn(
255
+ `[FileArtifactService] listArtifactVersions: Failed to read artifact version ${version} at ${artifactDir}`,
256
+ e
257
+ );
258
+ }
259
+ }
260
+ return artifactVersions;
261
+ } catch (e) {
262
+ logger.warn(
263
+ `[FileArtifactService] listArtifactVersions: Failed to list artifact versions for userId: ${userId} sessionId: ${sessionId} filename: ${filename}`,
264
+ e
265
+ );
266
+ return [];
267
+ }
268
+ }
269
+ async getArtifactVersion({
270
+ userId,
271
+ sessionId,
272
+ filename,
273
+ version
274
+ }) {
275
+ try {
276
+ const artifactDir = getArtifactDir(
277
+ this.rootDir,
278
+ userId,
279
+ sessionId,
280
+ filename
281
+ );
282
+ const versions = await getArtifactVersionsFromDir(artifactDir);
283
+ if (versions.length === 0) {
284
+ return void 0;
285
+ }
286
+ let versionToRead;
287
+ if (version === void 0) {
288
+ versionToRead = versions[versions.length - 1];
289
+ } else {
290
+ if (!versions.includes(version)) {
291
+ return void 0;
292
+ }
293
+ versionToRead = version;
294
+ }
295
+ const metadataPath = path.join(
296
+ getVersionsDir(artifactDir),
297
+ versionToRead.toString(),
298
+ "metadata.json"
299
+ );
300
+ return await readMetadata(metadataPath);
301
+ } catch (e) {
302
+ logger.warn(
303
+ `[FileArtifactService] getArtifactVersion: Failed to get artifact version for userId: ${userId} sessionId: ${sessionId} filename: ${filename} version: ${version}`,
304
+ e
305
+ );
306
+ return void 0;
307
+ }
308
+ }
309
+ }
310
+ function getUserRoot(rootDir, userId) {
311
+ return path.join(rootDir, "users", userId);
312
+ }
313
+ function isUserScoped(sessionId, filename) {
314
+ return !sessionId || filename.startsWith(USER_NAMESPACE_PREFIX);
315
+ }
316
+ function getUserArtifactsDir(userRoot) {
317
+ return path.join(userRoot, "artifacts");
318
+ }
319
+ function getSessionArtifactsDir(baseRoot, sessionId) {
320
+ return path.join(baseRoot, "sessions", sessionId, "artifacts");
321
+ }
322
+ function getVersionsDir(artifactDir) {
323
+ return path.join(artifactDir, "versions");
324
+ }
325
+ function getArtifactDir(rootDir, userId, sessionId, filename) {
326
+ const userRoot = getUserRoot(rootDir, userId);
327
+ let scopeRoot;
328
+ if (isUserScoped(sessionId, filename)) {
329
+ scopeRoot = getUserArtifactsDir(userRoot);
330
+ } else {
331
+ if (!sessionId) {
332
+ throw new Error(
333
+ "Session ID must be provided for session-scoped artifacts."
334
+ );
335
+ }
336
+ scopeRoot = getSessionArtifactsDir(userRoot, sessionId);
337
+ }
338
+ let cleanFilename = filename;
339
+ if (cleanFilename.startsWith(USER_NAMESPACE_PREFIX)) {
340
+ cleanFilename = cleanFilename.substring(USER_NAMESPACE_PREFIX.length);
341
+ }
342
+ cleanFilename = cleanFilename.trim();
343
+ if (path.isAbsolute(cleanFilename)) {
344
+ throw new Error(`Absolute artifact filename ${filename} is not permitted.`);
345
+ }
346
+ const artifactDir = path.resolve(scopeRoot, cleanFilename);
347
+ const relative = path.relative(scopeRoot, artifactDir);
348
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
349
+ throw new Error(`Artifact filename ${filename} escapes storage directory.`);
350
+ }
351
+ if (relative === "" || relative === ".") {
352
+ return path.join(scopeRoot, "artifact");
353
+ }
354
+ return artifactDir;
355
+ }
356
+ async function getArtifactVersionsFromDir(artifactDir) {
357
+ const versionsDir = getVersionsDir(artifactDir);
358
+ try {
359
+ const files = await fs.readdir(versionsDir, { withFileTypes: true });
360
+ const versions = files.filter((dirent) => dirent.isDirectory()).map((dirent) => parseInt(dirent.name, 10)).filter((v) => !isNaN(v));
361
+ return versions.sort((a, b) => a - b);
362
+ } catch (e) {
363
+ logger.warn(
364
+ `[FileArtifactService] getArtifactVersionsFromDir: Failed to list artifact versions from ${artifactDir}`,
365
+ e
366
+ );
367
+ return [];
368
+ }
369
+ }
370
+ async function getCanonicalUri(rootDir, userId, sessionId, filename, version) {
371
+ const artifactDir = await getArtifactDir(
372
+ rootDir,
373
+ userId,
374
+ sessionId,
375
+ filename
376
+ );
377
+ const storedFilename = path.basename(artifactDir);
378
+ const versionsDir = getVersionsDir(artifactDir);
379
+ const payloadPath = path.join(
380
+ versionsDir,
381
+ version.toString(),
382
+ storedFilename
383
+ );
384
+ return pathToFileURL(payloadPath).toString();
385
+ }
386
+ async function writeMetadata(metadataPath, metadata) {
387
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
388
+ }
389
+ async function readMetadata(metadataPath) {
390
+ const content = await fs.readFile(metadataPath, "utf-8");
391
+ return JSON.parse(content);
392
+ }
393
+ async function getLatestMetadata(artifactDir) {
394
+ const versions = await getArtifactVersionsFromDir(artifactDir);
395
+ if (versions.length === 0) {
396
+ return void 0;
397
+ }
398
+ const latestVersion = versions[versions.length - 1];
399
+ const metadataPath = path.join(
400
+ getVersionsDir(artifactDir),
401
+ latestVersion.toString(),
402
+ "metadata.json"
403
+ );
404
+ try {
405
+ return await readMetadata(metadataPath);
406
+ } catch (e) {
407
+ logger.warn(
408
+ `[FileArtifactService] getLatestMetadata: Failed to read metadata from ${metadataPath}`,
409
+ e
410
+ );
411
+ return void 0;
412
+ }
413
+ }
414
+ async function* iterateArtifactDirs(dir) {
415
+ try {
416
+ const entries = await fs.readdir(dir, { withFileTypes: true });
417
+ const hasVersions = entries.some(
418
+ (e) => e.isDirectory() && e.name === "versions"
419
+ );
420
+ if (hasVersions) {
421
+ yield dir;
422
+ return;
423
+ }
424
+ for (const entry of entries) {
425
+ if (entry.isDirectory()) {
426
+ const subdir = path.join(dir, entry.name);
427
+ for await (const foundDir of iterateArtifactDirs(subdir)) {
428
+ yield foundDir;
429
+ }
430
+ }
431
+ }
432
+ } catch (_e) {
433
+ }
434
+ }
435
+ function fileUriToPath(uri) {
436
+ try {
437
+ return fileURLToPath(uri);
438
+ } catch (e) {
439
+ logger.warn(
440
+ `[FileArtifactService] fileUriToPath: Failed to convert file URI to path: ${uri}`,
441
+ e
442
+ );
443
+ return void 0;
444
+ }
445
+ }
446
+ function asPosixPath(p) {
447
+ return p.split(path.sep).join("/");
448
+ }
449
+ export {
450
+ FileArtifactService
451
+ };
@@ -5,11 +5,15 @@
5
5
  */
6
6
  import { Storage } from "@google-cloud/storage";
7
7
  import { createPartFromBase64, createPartFromText } from "@google/genai";
8
+ import { logger } from "../utils/logger.js";
8
9
  class GcsArtifactService {
9
10
  constructor(bucket) {
10
11
  this.bucket = new Storage().bucket(bucket);
11
12
  }
12
13
  async saveArtifact(request) {
14
+ if (!request.artifact.inlineData && !request.artifact.text) {
15
+ throw new Error("Artifact must have either inlineData or text content.");
16
+ }
13
17
  const versions = await this.listVersions(request);
14
18
  const version = versions.length > 0 ? Math.max(...versions) + 1 : 0;
15
19
  const file = this.bucket.file(
@@ -18,62 +22,69 @@ class GcsArtifactService {
18
22
  version
19
23
  })
20
24
  );
25
+ const metadata = request.customMetadata || {};
21
26
  if (request.artifact.inlineData) {
22
- await file.save(JSON.stringify(request.artifact.inlineData.data), {
23
- contentType: request.artifact.inlineData.mimeType
24
- });
25
- return version;
26
- }
27
- if (request.artifact.text) {
28
- await file.save(request.artifact.text, {
29
- contentType: "text/plain"
30
- });
27
+ await file.save(
28
+ Buffer.from(request.artifact.inlineData.data || "", "base64"),
29
+ {
30
+ contentType: request.artifact.inlineData.mimeType,
31
+ metadata
32
+ }
33
+ );
31
34
  return version;
32
35
  }
33
- throw new Error("Artifact must have either inlineData or text.");
36
+ await file.save(request.artifact.text, {
37
+ contentType: "text/plain",
38
+ metadata
39
+ });
40
+ return version;
34
41
  }
35
42
  async loadArtifact(request) {
36
- let version = request.version;
37
- if (version === void 0) {
38
- const versions = await this.listVersions(request);
39
- if (versions.length === 0) {
40
- return void 0;
43
+ try {
44
+ let version = request.version;
45
+ if (version === void 0) {
46
+ const versions = await this.listVersions(request);
47
+ if (versions.length === 0) {
48
+ return void 0;
49
+ }
50
+ version = Math.max(...versions);
41
51
  }
42
- version = Math.max(...versions);
43
- }
44
- const file = this.bucket.file(
45
- getFileName({
46
- ...request,
47
- version
48
- })
49
- );
50
- const [[metadata], [rawDataBuffer]] = await Promise.all([
51
- file.getMetadata(),
52
- file.download()
53
- ]);
54
- if (metadata.contentType === "text/plain") {
55
- return createPartFromText(rawDataBuffer.toString("utf-8"));
52
+ const file = this.bucket.file(
53
+ getFileName({
54
+ ...request,
55
+ version
56
+ })
57
+ );
58
+ const [[metadata], [rawDataBuffer]] = await Promise.all([
59
+ file.getMetadata(),
60
+ file.download()
61
+ ]);
62
+ if (metadata.contentType === "text/plain") {
63
+ return createPartFromText(rawDataBuffer.toString("utf-8"));
64
+ }
65
+ return createPartFromBase64(
66
+ rawDataBuffer.toString("base64"),
67
+ metadata.contentType
68
+ );
69
+ } catch (e) {
70
+ logger.warn(
71
+ `[GcsArtifactService] loadArtifact: Failed to load artifact ${request.filename}`,
72
+ e
73
+ );
74
+ return void 0;
56
75
  }
57
- return createPartFromBase64(
58
- rawDataBuffer.toString("base64"),
59
- metadata.contentType
60
- );
61
76
  }
62
77
  async listArtifactKeys(request) {
63
- const fileNames = [];
64
78
  const sessionPrefix = `${request.appName}/${request.userId}/${request.sessionId}/`;
65
79
  const usernamePrefix = `${request.appName}/${request.userId}/user/`;
66
80
  const [[sessionFiles], [userSessionFiles]] = await Promise.all([
67
81
  this.bucket.getFiles({ prefix: sessionPrefix }),
68
82
  this.bucket.getFiles({ prefix: usernamePrefix })
69
83
  ]);
70
- for (const file of sessionFiles) {
71
- fileNames.push(file.name.split("/").pop());
72
- }
73
- for (const file of userSessionFiles) {
74
- fileNames.push(file.name.split("/").pop());
75
- }
76
- return fileNames.sort((a, b) => a.localeCompare(b));
84
+ return [
85
+ ...extractArtifactKeys(sessionFiles, sessionPrefix),
86
+ ...extractArtifactKeys(userSessionFiles, usernamePrefix, "user:")
87
+ ].sort((a, b) => a.localeCompare(b));
77
88
  }
78
89
  async deleteArtifact(request) {
79
90
  const versions = await this.listVersions(request);
@@ -92,13 +103,62 @@ class GcsArtifactService {
92
103
  }
93
104
  async listVersions(request) {
94
105
  const prefix = getFileName(request);
95
- const [files] = await this.bucket.getFiles({ prefix });
106
+ const searchPrefix = prefix + "/";
107
+ const [files] = await this.bucket.getFiles({ prefix: searchPrefix });
96
108
  const versions = [];
97
109
  for (const file of files) {
98
110
  const version = file.name.split("/").pop();
99
- versions.push(parseInt(version, 10));
111
+ const v = parseInt(version, 10);
112
+ if (!isNaN(v)) {
113
+ versions.push(v);
114
+ }
115
+ }
116
+ return versions.sort((a, b) => a - b);
117
+ }
118
+ async listArtifactVersions(request) {
119
+ const versions = await this.listVersions(request);
120
+ const artifactVersions = [];
121
+ for (const version of versions) {
122
+ const artifactVersion = await this.getArtifactVersion({
123
+ ...request,
124
+ version
125
+ });
126
+ if (artifactVersion) {
127
+ artifactVersions.push(artifactVersion);
128
+ }
129
+ }
130
+ return artifactVersions;
131
+ }
132
+ async getArtifactVersion(request) {
133
+ try {
134
+ let version = request.version;
135
+ if (version === void 0) {
136
+ const versions = await this.listVersions(request);
137
+ if (versions.length === 0) {
138
+ return void 0;
139
+ }
140
+ version = Math.max(...versions);
141
+ }
142
+ const file = this.bucket.file(
143
+ getFileName({
144
+ ...request,
145
+ version
146
+ })
147
+ );
148
+ const [metadata] = await file.getMetadata();
149
+ return {
150
+ version,
151
+ mimeType: metadata.contentType,
152
+ customMetadata: metadata.metadata,
153
+ canonicalUri: file.publicUrl()
154
+ };
155
+ } catch (e) {
156
+ logger.warn(
157
+ `[GcsArtifactService] getArtifactVersion: Failed to get artifact version for userId: ${request.userId} sessionId: ${request.sessionId} filename: ${request.filename} version: ${request.version}`,
158
+ e
159
+ );
160
+ return void 0;
100
161
  }
101
- return versions;
102
162
  }
103
163
  }
104
164
  function getFileName({
@@ -108,10 +168,29 @@ function getFileName({
108
168
  filename,
109
169
  version
110
170
  }) {
111
- if (filename.startsWith("user:")) {
112
- return `${appName}/${userId}/user/${filename}/${version}`;
171
+ const isUser = filename.startsWith("user:");
172
+ const cleanFilename = isUser ? filename.substring(5) : filename;
173
+ const prefix = isUser ? `${appName}/${userId}/user/${cleanFilename}` : `${appName}/${userId}/${sessionId}/${cleanFilename}`;
174
+ return version !== void 0 ? `${prefix}/${version}` : prefix;
175
+ }
176
+ function extractArtifactKeys(files, fileNamePrefix, keyPrefix = "") {
177
+ const keys = /* @__PURE__ */ new Set();
178
+ for (const file of files) {
179
+ if (!file.name.startsWith(fileNamePrefix)) {
180
+ continue;
181
+ }
182
+ const relative = file.name.substring(fileNamePrefix.length);
183
+ const name = getFileNameFromPath(relative);
184
+ keys.add(`${keyPrefix}${name}`);
185
+ }
186
+ return [...keys];
187
+ }
188
+ function getFileNameFromPath(filePath) {
189
+ const parts = filePath.split("/");
190
+ if (parts.length < 2) {
191
+ return filePath;
113
192
  }
114
- return `${appName}/${userId}/${sessionId}/${filename}/${version}`;
193
+ return parts.slice(0, -1).join("/");
115
194
  }
116
195
  export {
117
196
  GcsArtifactService