@zhivex-ai/core 1.17.0 → 1.19.1

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 (272) hide show
  1. package/README.md +4 -0
  2. package/dist/agent/approvals.d.ts +7 -0
  3. package/dist/agent/approvals.d.ts.map +1 -0
  4. package/dist/agent/approvals.js +97 -0
  5. package/dist/agent/approvals.js.map +1 -0
  6. package/dist/agent/cancellation.d.ts +4 -0
  7. package/dist/agent/cancellation.d.ts.map +1 -0
  8. package/dist/agent/cancellation.js +69 -0
  9. package/dist/agent/cancellation.js.map +1 -0
  10. package/dist/agent/common.d.ts +20 -0
  11. package/dist/agent/common.d.ts.map +1 -0
  12. package/dist/agent/common.js +150 -0
  13. package/dist/agent/common.js.map +1 -0
  14. package/dist/agent/compaction.d.ts +6 -0
  15. package/dist/agent/compaction.d.ts.map +1 -0
  16. package/dist/agent/compaction.js +156 -0
  17. package/dist/agent/compaction.js.map +1 -0
  18. package/dist/agent/context.d.ts +58 -0
  19. package/dist/agent/context.d.ts.map +1 -0
  20. package/dist/agent/context.js +267 -0
  21. package/dist/agent/context.js.map +1 -0
  22. package/dist/agent/definition.d.ts +4 -0
  23. package/dist/agent/definition.d.ts.map +1 -0
  24. package/dist/agent/definition.js +35 -0
  25. package/dist/agent/definition.js.map +1 -0
  26. package/dist/agent/execution.d.ts +14 -0
  27. package/dist/agent/execution.d.ts.map +1 -0
  28. package/dist/agent/execution.js +1062 -0
  29. package/dist/agent/execution.js.map +1 -0
  30. package/dist/agent/groups.d.ts +3 -0
  31. package/dist/agent/groups.d.ts.map +1 -0
  32. package/dist/agent/groups.js +130 -0
  33. package/dist/agent/groups.js.map +1 -0
  34. package/dist/agent/guardrails.d.ts +4 -0
  35. package/dist/agent/guardrails.d.ts.map +1 -0
  36. package/dist/agent/guardrails.js +29 -0
  37. package/dist/agent/guardrails.js.map +1 -0
  38. package/dist/agent/instance.d.ts +42 -0
  39. package/dist/agent/instance.d.ts.map +1 -0
  40. package/dist/agent/instance.js +82 -0
  41. package/dist/agent/instance.js.map +1 -0
  42. package/dist/agent/lifecycle.d.ts +31 -0
  43. package/dist/agent/lifecycle.d.ts.map +1 -0
  44. package/dist/agent/lifecycle.js +184 -0
  45. package/dist/agent/lifecycle.js.map +1 -0
  46. package/dist/agent/state.d.ts +6 -0
  47. package/dist/agent/state.d.ts.map +1 -0
  48. package/dist/agent/state.js +119 -0
  49. package/dist/agent/state.js.map +1 -0
  50. package/dist/agent/telemetry.d.ts +12 -0
  51. package/dist/agent/telemetry.d.ts.map +1 -0
  52. package/dist/agent/telemetry.js +176 -0
  53. package/dist/agent/telemetry.js.map +1 -0
  54. package/dist/agent/tool-execution.d.ts +6 -0
  55. package/dist/agent/tool-execution.d.ts.map +1 -0
  56. package/dist/agent/tool-execution.js +176 -0
  57. package/dist/agent/tool-execution.js.map +1 -0
  58. package/dist/agent-evaluation.d.ts.map +1 -1
  59. package/dist/agent-evaluation.js +2 -0
  60. package/dist/agent-evaluation.js.map +1 -1
  61. package/dist/agent-reconciliation.d.ts +12 -0
  62. package/dist/agent-reconciliation.d.ts.map +1 -0
  63. package/dist/agent-reconciliation.js +87 -0
  64. package/dist/agent-reconciliation.js.map +1 -0
  65. package/dist/agent-run-view.d.ts +9 -0
  66. package/dist/agent-run-view.d.ts.map +1 -0
  67. package/dist/agent-run-view.js +42 -0
  68. package/dist/agent-run-view.js.map +1 -0
  69. package/dist/agent-state.d.ts.map +1 -1
  70. package/dist/agent-state.js +34 -0
  71. package/dist/agent-state.js.map +1 -1
  72. package/dist/agent-store/file.d.ts +11 -0
  73. package/dist/agent-store/file.d.ts.map +1 -0
  74. package/dist/agent-store/file.js +534 -0
  75. package/dist/agent-store/file.js.map +1 -0
  76. package/dist/agent-store/memory.d.ts +9 -0
  77. package/dist/agent-store/memory.d.ts.map +1 -0
  78. package/dist/agent-store/memory.js +198 -0
  79. package/dist/agent-store/memory.js.map +1 -0
  80. package/dist/agent-store/postgres.d.ts +4 -0
  81. package/dist/agent-store/postgres.d.ts.map +1 -0
  82. package/dist/agent-store/postgres.js +374 -0
  83. package/dist/agent-store/postgres.js.map +1 -0
  84. package/dist/agent-store/shared.d.ts +19 -0
  85. package/dist/agent-store/shared.d.ts.map +1 -0
  86. package/dist/agent-store/shared.js +137 -0
  87. package/dist/agent-store/shared.js.map +1 -0
  88. package/dist/agent-store/sqlite.d.ts +4 -0
  89. package/dist/agent-store/sqlite.d.ts.map +1 -0
  90. package/dist/agent-store/sqlite.js +362 -0
  91. package/dist/agent-store/sqlite.js.map +1 -0
  92. package/dist/agent-store.d.ts +5 -21
  93. package/dist/agent-store.d.ts.map +1 -1
  94. package/dist/agent-store.js +5 -1540
  95. package/dist/agent-store.js.map +1 -1
  96. package/dist/agent.d.ts +6 -57
  97. package/dist/agent.d.ts.map +1 -1
  98. package/dist/agent.js +6 -2622
  99. package/dist/agent.js.map +1 -1
  100. package/dist/agents-entry.d.ts +19 -0
  101. package/dist/agents-entry.d.ts.map +1 -0
  102. package/dist/agents-entry.js +14 -0
  103. package/dist/agents-entry.js.map +1 -0
  104. package/dist/api-stability.d.ts.map +1 -1
  105. package/dist/api-stability.js +1 -0
  106. package/dist/api-stability.js.map +1 -1
  107. package/dist/artifact/file.d.ts +6 -0
  108. package/dist/artifact/file.d.ts.map +1 -0
  109. package/dist/artifact/file.js +380 -0
  110. package/dist/artifact/file.js.map +1 -0
  111. package/dist/artifact/memory.d.ts +3 -0
  112. package/dist/artifact/memory.d.ts.map +1 -0
  113. package/dist/artifact/memory.js +80 -0
  114. package/dist/artifact/memory.js.map +1 -0
  115. package/dist/artifact/postgres.d.ts +3 -0
  116. package/dist/artifact/postgres.d.ts.map +1 -0
  117. package/dist/artifact/postgres.js +283 -0
  118. package/dist/artifact/postgres.js.map +1 -0
  119. package/dist/artifact/shared.d.ts +208 -0
  120. package/dist/artifact/shared.d.ts.map +1 -0
  121. package/dist/artifact/shared.js +523 -0
  122. package/dist/artifact/shared.js.map +1 -0
  123. package/dist/artifact/sqlite.d.ts +3 -0
  124. package/dist/artifact/sqlite.d.ts.map +1 -0
  125. package/dist/artifact/sqlite.js +283 -0
  126. package/dist/artifact/sqlite.js.map +1 -0
  127. package/dist/artifact.d.ts +6 -186
  128. package/dist/artifact.d.ts.map +1 -1
  129. package/dist/artifact.js +6 -1536
  130. package/dist/artifact.js.map +1 -1
  131. package/dist/catalog-contracts.d.ts +52 -0
  132. package/dist/catalog-contracts.d.ts.map +1 -0
  133. package/dist/catalog-contracts.js +250 -0
  134. package/dist/catalog-contracts.js.map +1 -0
  135. package/dist/catalog.d.ts +3 -52
  136. package/dist/catalog.d.ts.map +1 -1
  137. package/dist/catalog.js +3 -249
  138. package/dist/catalog.js.map +1 -1
  139. package/dist/generate-cache-file.d.ts +11 -0
  140. package/dist/generate-cache-file.d.ts.map +1 -0
  141. package/dist/generate-cache-file.js +124 -0
  142. package/dist/generate-cache-file.js.map +1 -0
  143. package/dist/generate-text.d.ts +2 -5
  144. package/dist/generate-text.d.ts.map +1 -1
  145. package/dist/generate-text.js +3 -18
  146. package/dist/generate-text.js.map +1 -1
  147. package/dist/generation-entry.d.ts +6 -0
  148. package/dist/generation-entry.d.ts.map +1 -0
  149. package/dist/generation-entry.js +6 -0
  150. package/dist/generation-entry.js.map +1 -0
  151. package/dist/index.d.ts +3 -0
  152. package/dist/index.d.ts.map +1 -1
  153. package/dist/index.js +1 -0
  154. package/dist/index.js.map +1 -1
  155. package/dist/middleware-runtime.d.ts +35 -0
  156. package/dist/middleware-runtime.d.ts.map +1 -0
  157. package/dist/middleware-runtime.js +532 -0
  158. package/dist/middleware-runtime.js.map +1 -0
  159. package/dist/middleware.d.ts +3 -43
  160. package/dist/middleware.d.ts.map +1 -1
  161. package/dist/middleware.js +3 -653
  162. package/dist/middleware.js.map +1 -1
  163. package/dist/normalize-messages.d.ts +3 -0
  164. package/dist/normalize-messages.d.ts.map +1 -0
  165. package/dist/normalize-messages.js +16 -0
  166. package/dist/normalize-messages.js.map +1 -0
  167. package/dist/provider-entry.d.ts +17 -0
  168. package/dist/provider-entry.d.ts.map +1 -0
  169. package/dist/provider-entry.js +13 -0
  170. package/dist/provider-entry.js.map +1 -0
  171. package/dist/realtime.d.ts +2 -0
  172. package/dist/realtime.d.ts.map +1 -1
  173. package/dist/realtime.js +6 -0
  174. package/dist/realtime.js.map +1 -1
  175. package/dist/store-key.d.ts +3 -0
  176. package/dist/store-key.d.ts.map +1 -0
  177. package/dist/store-key.js +5 -0
  178. package/dist/store-key.js.map +1 -0
  179. package/dist/store-security.d.ts +1 -2
  180. package/dist/store-security.d.ts.map +1 -1
  181. package/dist/store-security.js +2 -4
  182. package/dist/store-security.js.map +1 -1
  183. package/dist/types/agent-state.d.ts +236 -0
  184. package/dist/types/agent-state.d.ts.map +1 -0
  185. package/dist/types/agent-state.js +2 -0
  186. package/dist/types/agent-state.js.map +1 -0
  187. package/dist/types/agents.d.ts +429 -0
  188. package/dist/types/agents.d.ts.map +1 -0
  189. package/dist/types/agents.js +2 -0
  190. package/dist/types/agents.js.map +1 -0
  191. package/dist/types/common.d.ts +179 -0
  192. package/dist/types/common.d.ts.map +1 -0
  193. package/dist/types/common.js +2 -0
  194. package/dist/types/common.js.map +1 -0
  195. package/dist/types/generation.d.ts +109 -0
  196. package/dist/types/generation.d.ts.map +1 -0
  197. package/dist/types/generation.js +2 -0
  198. package/dist/types/generation.js.map +1 -0
  199. package/dist/types/media-data.d.ts +65 -0
  200. package/dist/types/media-data.d.ts.map +1 -0
  201. package/dist/types/media-data.js +2 -0
  202. package/dist/types/media-data.js.map +1 -0
  203. package/dist/types/media.d.ts +141 -0
  204. package/dist/types/media.d.ts.map +1 -0
  205. package/dist/types/media.js +2 -0
  206. package/dist/types/media.js.map +1 -0
  207. package/dist/types/messages.d.ts +82 -0
  208. package/dist/types/messages.d.ts.map +1 -0
  209. package/dist/types/messages.js +2 -0
  210. package/dist/types/messages.js.map +1 -0
  211. package/dist/types/middleware.d.ts +130 -0
  212. package/dist/types/middleware.d.ts.map +1 -0
  213. package/dist/types/middleware.js +2 -0
  214. package/dist/types/middleware.js.map +1 -0
  215. package/dist/types/model-tools.d.ts +409 -0
  216. package/dist/types/model-tools.d.ts.map +1 -0
  217. package/dist/types/model-tools.js +2 -0
  218. package/dist/types/model-tools.js.map +1 -0
  219. package/dist/types/persistence.d.ts +168 -0
  220. package/dist/types/persistence.d.ts.map +1 -0
  221. package/dist/types/persistence.js +2 -0
  222. package/dist/types/persistence.js.map +1 -0
  223. package/dist/types/provider-resources.d.ts +336 -0
  224. package/dist/types/provider-resources.d.ts.map +1 -0
  225. package/dist/types/provider-resources.js +2 -0
  226. package/dist/types/provider-resources.js.map +1 -0
  227. package/dist/types/provider.d.ts +23 -0
  228. package/dist/types/provider.d.ts.map +1 -0
  229. package/dist/types/provider.js +2 -0
  230. package/dist/types/provider.js.map +1 -0
  231. package/dist/types/resource-options.d.ts +88 -0
  232. package/dist/types/resource-options.d.ts.map +1 -0
  233. package/dist/types/resource-options.js +2 -0
  234. package/dist/types/resource-options.js.map +1 -0
  235. package/dist/types/stream.d.ts +70 -0
  236. package/dist/types/stream.d.ts.map +1 -0
  237. package/dist/types/stream.js +2 -0
  238. package/dist/types/stream.js.map +1 -0
  239. package/dist/types/ui.d.ts +110 -0
  240. package/dist/types/ui.d.ts.map +1 -0
  241. package/dist/types/ui.js +2 -0
  242. package/dist/types/ui.js.map +1 -0
  243. package/dist/types.d.ts +16 -2442
  244. package/dist/types.d.ts.map +1 -1
  245. package/dist/ui.d.ts.map +1 -1
  246. package/dist/ui.js +2 -0
  247. package/dist/ui.js.map +1 -1
  248. package/dist/workflow-state-service/file.d.ts +5 -0
  249. package/dist/workflow-state-service/file.d.ts.map +1 -0
  250. package/dist/workflow-state-service/file.js +142 -0
  251. package/dist/workflow-state-service/file.js.map +1 -0
  252. package/dist/workflow-state-service/memory.d.ts +3 -0
  253. package/dist/workflow-state-service/memory.d.ts.map +1 -0
  254. package/dist/workflow-state-service/memory.js +27 -0
  255. package/dist/workflow-state-service/memory.js.map +1 -0
  256. package/dist/workflow-state-service/postgres.d.ts +4 -0
  257. package/dist/workflow-state-service/postgres.d.ts.map +1 -0
  258. package/dist/workflow-state-service/postgres.js +179 -0
  259. package/dist/workflow-state-service/postgres.js.map +1 -0
  260. package/dist/workflow-state-service/shared.d.ts +43 -0
  261. package/dist/workflow-state-service/shared.d.ts.map +1 -0
  262. package/dist/workflow-state-service/shared.js +107 -0
  263. package/dist/workflow-state-service/shared.js.map +1 -0
  264. package/dist/workflow-state-service/sqlite.d.ts +4 -0
  265. package/dist/workflow-state-service/sqlite.d.ts.map +1 -0
  266. package/dist/workflow-state-service/sqlite.js +183 -0
  267. package/dist/workflow-state-service/sqlite.js.map +1 -0
  268. package/dist/workflow-state-service.d.ts +6 -33
  269. package/dist/workflow-state-service.d.ts.map +1 -1
  270. package/dist/workflow-state-service.js +6 -626
  271. package/dist/workflow-state-service.js.map +1 -1
  272. package/package.json +17 -1
package/dist/artifact.js CHANGED
@@ -1,1537 +1,7 @@
1
- import { promises as fs } from "node:fs";
2
- import { createHash } from "node:crypto";
3
- import path from "node:path";
4
- import { ConflictError, ValidationError } from "./errors.js";
5
- import { assertPostgresClient } from "./postgres-client.js";
6
- import { createSecureId } from "#secure-id";
7
- import { canonicalStoreFileStem, canonicalStoreKey, ensurePrivateDirectory, writePrivateFile } from "./store-security.js";
8
- const randomId = createSecureId;
9
- const cloneJson = (value) => JSON.parse(JSON.stringify(value));
10
- const MIB = 1024 * 1024;
11
- export const ARTIFACT_SCHEMA_VERSION = 1;
12
- export const DEFAULT_ARTIFACT_SERVICE_LIMITS = Object.freeze({
13
- maxJsonBytes: MIB,
14
- maxTextBytes: MIB,
15
- maxBase64Bytes: 16 * MIB,
16
- maxBinaryBytes: 16 * MIB,
17
- maxMetadataBytes: 64 * 1024,
18
- maxRecordBytes: 24 * MIB
19
- });
20
- const artifactParts = (input) => [input.appName, input.userId, input.sessionId, input.id];
21
- const artifactKey = (input) => canonicalStoreKey("artifact", artifactParts(input));
22
- const legacyArtifactKey = (input) => `${input.appName}:${input.userId}:${input.sessionId}:${input.id}`;
23
- const matchesArtifactLookup = (artifact, input) => artifact.appName === input.appName &&
24
- artifact.userId === input.userId &&
25
- artifact.sessionId === input.sessionId &&
26
- artifact.id === input.id;
27
- const identifierPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
28
- const artifactEncodings = new Set(["json", "text", "base64"]);
29
- const artifactStorageModes = new Set(["json", "binary"]);
30
- const positiveSafeIntegerLimit = (value, fallback, name) => {
31
- const resolved = value ?? fallback;
32
- if (!Number.isSafeInteger(resolved) || resolved <= 0) {
33
- throw new ValidationError(`The "${name}" artifact limit must be a positive safe integer.`);
34
- }
35
- return resolved;
36
- };
37
- export const resolveArtifactServiceLimits = (limits = {}) => ({
38
- maxJsonBytes: positiveSafeIntegerLimit(limits.maxJsonBytes, DEFAULT_ARTIFACT_SERVICE_LIMITS.maxJsonBytes, "maxJsonBytes"),
39
- maxTextBytes: positiveSafeIntegerLimit(limits.maxTextBytes, DEFAULT_ARTIFACT_SERVICE_LIMITS.maxTextBytes, "maxTextBytes"),
40
- maxBase64Bytes: positiveSafeIntegerLimit(limits.maxBase64Bytes, DEFAULT_ARTIFACT_SERVICE_LIMITS.maxBase64Bytes, "maxBase64Bytes"),
41
- maxBinaryBytes: positiveSafeIntegerLimit(limits.maxBinaryBytes, DEFAULT_ARTIFACT_SERVICE_LIMITS.maxBinaryBytes, "maxBinaryBytes"),
42
- maxMetadataBytes: positiveSafeIntegerLimit(limits.maxMetadataBytes, DEFAULT_ARTIFACT_SERVICE_LIMITS.maxMetadataBytes, "maxMetadataBytes"),
43
- maxRecordBytes: positiveSafeIntegerLimit(limits.maxRecordBytes, DEFAULT_ARTIFACT_SERVICE_LIMITS.maxRecordBytes, "maxRecordBytes")
44
- });
45
- const jsonText = (value, fieldName) => {
46
- try {
47
- const serialized = JSON.stringify(value);
48
- if (serialized === undefined) {
49
- throw new ValidationError(`Artifact ${fieldName} must be JSON-serializable.`);
50
- }
51
- return serialized;
52
- }
53
- catch (error) {
54
- if (error instanceof ValidationError) {
55
- throw error;
56
- }
57
- throw new ValidationError(`Artifact ${fieldName} must be JSON-serializable.`, { cause: error });
58
- }
59
- };
60
- const utf8Bytes = (value) => Buffer.byteLength(value, "utf8");
61
- const assertByteLimit = (actual, maximum, fieldName) => {
62
- if (actual > maximum) {
63
- throw new ValidationError(`Artifact ${fieldName} is ${actual} bytes and exceeds the ${maximum}-byte limit.`);
64
- }
65
- };
66
- const validateRequiredString = (value, fieldName) => {
67
- if (typeof value !== "string" || value.length === 0) {
68
- throw new ValidationError(`Artifact "${fieldName}" must be a non-empty string.`);
69
- }
70
- };
71
- const validateOptionalString = (value, fieldName) => {
72
- if (value !== undefined) {
73
- validateRequiredString(value, fieldName);
74
- }
75
- };
76
- const validateArtifactLookup = (input) => {
77
- validateRequiredString(input.appName, "appName");
78
- validateRequiredString(input.userId, "userId");
79
- validateRequiredString(input.sessionId, "sessionId");
80
- validateRequiredString(input.id, "id");
81
- };
82
- const validateArtifactListInput = (input) => {
83
- validateRequiredString(input.appName, "appName");
84
- validateRequiredString(input.userId, "userId");
85
- validateRequiredString(input.sessionId, "sessionId");
86
- validateOptionalString(input.workflowRunId, "workflowRunId");
87
- validateOptionalString(input.workflowStepId, "workflowStepId");
88
- validateOptionalString(input.agentRunId, "agentRunId");
89
- };
90
- const validateRevision = (revision, fieldName = "revision") => {
91
- if (revision !== undefined && (!Number.isSafeInteger(revision) || revision < 0)) {
92
- throw new ValidationError(`Artifact "${fieldName}" must be a non-negative safe integer.`);
93
- }
94
- };
95
- const externalBlobReference = (metadata) => {
96
- const externalBlob = metadata?.externalBlob;
97
- if (!externalBlob || typeof externalBlob !== "object" || Array.isArray(externalBlob)) {
98
- return undefined;
99
- }
100
- const reference = externalBlob;
101
- return reference.managedBy === "application" &&
102
- typeof reference.uri === "string" &&
103
- reference.uri.length > 0 &&
104
- !/[\u0000-\u001f\u007f]/.test(reference.uri)
105
- ? { uri: reference.uri }
106
- : undefined;
107
- };
108
- export const normalizeArtifactRecord = (value) => {
109
- if (!value || typeof value !== "object" || Array.isArray(value)) {
110
- throw new ValidationError("ArtifactRecord must be an object.");
111
- }
112
- const artifact = value;
113
- if (artifact.schemaVersion !== undefined &&
114
- (!Number.isSafeInteger(artifact.schemaVersion) || artifact.schemaVersion < 0 || artifact.schemaVersion > ARTIFACT_SCHEMA_VERSION)) {
115
- throw new ValidationError(`Unsupported ArtifactRecord schemaVersion ${artifact.schemaVersion}.`);
116
- }
117
- if (!("data" in artifact)) {
118
- throw new ValidationError("ArtifactRecord is missing required fields.");
119
- }
120
- validateRequiredString(artifact.id, "id");
121
- validateRequiredString(artifact.appName, "appName");
122
- validateRequiredString(artifact.userId, "userId");
123
- validateRequiredString(artifact.sessionId, "sessionId");
124
- validateRequiredString(artifact.name, "name");
125
- validateRequiredString(artifact.contentType, "contentType");
126
- validateOptionalString(artifact.workflowRunId, "workflowRunId");
127
- validateOptionalString(artifact.workflowStepId, "workflowStepId");
128
- validateOptionalString(artifact.agentRunId, "agentRunId");
129
- if (artifact.revision !== undefined && (!Number.isSafeInteger(artifact.revision) || artifact.revision < 1)) {
130
- throw new ValidationError('Artifact "revision" must be a positive safe integer.');
131
- }
132
- if (!Number.isSafeInteger(artifact.createdAt) || artifact.createdAt < 0) {
133
- throw new ValidationError('Artifact "createdAt" must be a non-negative safe integer.');
134
- }
135
- if (!Number.isSafeInteger(artifact.updatedAt) || artifact.updatedAt < artifact.createdAt) {
136
- throw new ValidationError('Artifact "updatedAt" must be a safe integer greater than or equal to "createdAt".');
137
- }
138
- if (artifact.encoding !== undefined && !artifactEncodings.has(artifact.encoding)) {
139
- throw new ValidationError('Artifact "encoding" must be "json", "text", or "base64".');
140
- }
141
- if (artifact.storageMode !== undefined && !artifactStorageModes.has(artifact.storageMode)) {
142
- throw new ValidationError('Artifact "storageMode" must be "json" or "binary".');
143
- }
144
- if (artifact.metadata !== undefined && (!artifact.metadata || typeof artifact.metadata !== "object" || Array.isArray(artifact.metadata))) {
145
- throw new ValidationError('Artifact "metadata" must be a JSON object.');
146
- }
147
- if ((artifact.storageMode ?? "json") !== "binary" &&
148
- (artifact.encoding === "base64" || artifact.encoding === "text") &&
149
- typeof artifact.data !== "string") {
150
- throw new ValidationError(`Artifact data must be a string when "encoding" is "${artifact.encoding}".`);
151
- }
152
- if ((artifact.storageMode ?? "json") === "binary" && artifact.data !== null) {
153
- throw new ValidationError('Artifact data must be null when "storageMode" is "binary".');
154
- }
155
- if (artifact.blobPath !== undefined && (artifact.storageMode !== "binary" || artifact.data !== null)) {
156
- throw new ValidationError('Artifact "blobPath" is only valid for separate binary storage.');
157
- }
158
- validateArtifactMetadata({ size: artifact.size, sha256: artifact.sha256 });
159
- jsonText(artifact.data, "data");
160
- if (artifact.metadata !== undefined) {
161
- jsonText(artifact.metadata, "metadata");
162
- }
163
- return {
164
- schemaVersion: ARTIFACT_SCHEMA_VERSION,
165
- revision: typeof artifact.revision === "number" ? artifact.revision : 1,
166
- id: artifact.id,
167
- appName: artifact.appName,
168
- userId: artifact.userId,
169
- sessionId: artifact.sessionId,
170
- workflowRunId: artifact.workflowRunId,
171
- workflowStepId: artifact.workflowStepId,
172
- agentRunId: artifact.agentRunId,
173
- name: artifact.name,
174
- contentType: artifact.contentType,
175
- data: cloneJson(artifact.data),
176
- encoding: artifact.encoding,
177
- size: artifact.size,
178
- sha256: artifact.sha256,
179
- storageMode: artifact.storageMode ?? "json",
180
- blobPath: artifact.blobPath,
181
- metadata: artifact.metadata ? cloneJson(artifact.metadata) : undefined,
182
- createdAt: artifact.createdAt,
183
- updatedAt: artifact.updatedAt
184
- };
185
- };
186
- export const migrateArtifactRecord = (value, targetVersion = ARTIFACT_SCHEMA_VERSION) => {
187
- if (targetVersion !== ARTIFACT_SCHEMA_VERSION) {
188
- throw new ValidationError(`Unsupported ArtifactRecord migration target ${targetVersion}.`);
189
- }
190
- return normalizeArtifactRecord(value);
191
- };
192
- const cloneArtifact = (artifact) => cloneJson(normalizeArtifactRecord(artifact));
193
- const binaryInputByteLength = (data) => typeof data === "string" ? utf8Bytes(data) : data.byteLength;
194
- const bytesFromBinaryInput = (data, maxBytes) => {
195
- assertByteLimit(binaryInputByteLength(data), maxBytes, "binary data");
196
- const buffer = typeof data === "string"
197
- ? Buffer.from(data, "utf8")
198
- : data instanceof Uint8Array
199
- ? Buffer.from(data)
200
- : Buffer.from(data);
201
- return new Uint8Array(buffer);
202
- };
203
- const sha256Digest = (data) => createHash("sha256").update(data).digest("hex");
204
- const resolveBinarySha256 = (data, expectedSha256) => {
205
- validateArtifactMetadata({ sha256: expectedSha256 });
206
- const actualSha256 = sha256Digest(data);
207
- if (expectedSha256 !== undefined && expectedSha256.toLowerCase() !== actualSha256) {
208
- throw new ValidationError('The "sha256" artifact option does not match the binary data.');
209
- }
210
- return actualSha256;
211
- };
212
- const assertExpectedRevision = (current, expectedRevision, resource) => {
213
- validateRevision(expectedRevision, "expectedRevision");
214
- if (expectedRevision !== undefined && (current?.revision ?? 0) !== expectedRevision) {
215
- throw new ConflictError(`${resource} revision conflict.`);
216
- }
217
- };
218
- const sqliteMutationCount = (result) => {
219
- if (!result || typeof result !== "object") {
220
- return undefined;
221
- }
222
- const record = result;
223
- const value = record.changes ?? record.changeset ?? record.rowCount;
224
- return typeof value === "number" ? value : undefined;
225
- };
226
- const validateArtifactMetadata = (input) => {
227
- if (input.size !== undefined && (!Number.isInteger(input.size) || input.size < 0)) {
228
- throw new ValidationError('The "size" artifact option must be a non-negative integer.');
229
- }
230
- if (input.sha256 !== undefined && !/^[a-f0-9]{64}$/i.test(input.sha256)) {
231
- throw new ValidationError('The "sha256" artifact option must be a 64-character hexadecimal digest.');
232
- }
233
- };
234
- const encodedBase64Length = (decodedBytes) => 4 * Math.ceil(decodedBytes / 3);
235
- const base64Bytes = (value, maxBytes) => {
236
- assertByteLimit(utf8Bytes(value), encodedBase64Length(maxBytes), "base64 data");
237
- const normalized = value.replace(/\s/g, "");
238
- if (normalized.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(normalized)) {
239
- throw new ValidationError('Artifact data must be valid base64 when "encoding" is "base64".');
240
- }
241
- const padding = normalized.endsWith("==") ? 2 : normalized.endsWith("=") ? 1 : 0;
242
- const decodedBytes = normalized.length === 0 ? 0 : (normalized.length / 4) * 3 - padding;
243
- assertByteLimit(decodedBytes, maxBytes, "decoded base64 data");
244
- const bytes = new Uint8Array(Buffer.from(normalized, "base64"));
245
- if (bytes.byteLength !== decodedBytes) {
246
- throw new ValidationError('Artifact data must be valid base64 when "encoding" is "base64".');
247
- }
248
- return bytes;
249
- };
250
- const validateArtifactSaveInput = (input, limits, internal = {}) => {
251
- validateRequiredString(input.appName, "appName");
252
- validateRequiredString(input.userId, "userId");
253
- validateRequiredString(input.sessionId, "sessionId");
254
- if (input.id !== undefined) {
255
- validateRequiredString(input.id, "id");
256
- }
257
- validateOptionalString(input.workflowRunId, "workflowRunId");
258
- validateOptionalString(input.workflowStepId, "workflowStepId");
259
- validateOptionalString(input.agentRunId, "agentRunId");
260
- validateRequiredString(input.name, "name");
261
- validateRequiredString(input.contentType, "contentType");
262
- validateRevision(input.expectedRevision, "expectedRevision");
263
- if (input.encoding !== undefined && !artifactEncodings.has(input.encoding)) {
264
- throw new ValidationError('Artifact "encoding" must be "json", "text", or "base64".');
265
- }
266
- if (input.storageMode !== undefined && !artifactStorageModes.has(input.storageMode)) {
267
- throw new ValidationError('Artifact "storageMode" must be "json" or "binary".');
268
- }
269
- if (input.metadata !== undefined && (!input.metadata || typeof input.metadata !== "object" || Array.isArray(input.metadata))) {
270
- throw new ValidationError('Artifact "metadata" must be a JSON object.');
271
- }
272
- if (input.metadata !== undefined) {
273
- assertByteLimit(utf8Bytes(jsonText(input.metadata, "metadata")), limits.maxMetadataBytes, "metadata");
274
- }
275
- if ((input.storageMode ?? "json") === "binary") {
276
- if (input.data !== null) {
277
- throw new ValidationError('Artifact data must be null when "storageMode" is "binary".');
278
- }
279
- if (!internal.managedBinary && !externalBlobReference(input.metadata)) {
280
- throw new ValidationError('Binary artifact metadata must contain an application-managed externalBlob reference.');
281
- }
282
- return;
283
- }
284
- if (input.encoding === "base64") {
285
- if (typeof input.data !== "string") {
286
- throw new ValidationError('Artifact data must be a string when "encoding" is "base64".');
287
- }
288
- base64Bytes(input.data, limits.maxBase64Bytes);
289
- return;
290
- }
291
- if (input.encoding === "text" && typeof input.data !== "string") {
292
- throw new ValidationError('Artifact data must be a string when "encoding" is "text".');
293
- }
294
- if (typeof input.data === "string") {
295
- assertByteLimit(utf8Bytes(input.data), limits.maxTextBytes, "text data");
296
- }
297
- else {
298
- assertByteLimit(utf8Bytes(jsonText(input.data, "data")), limits.maxJsonBytes, "JSON data");
299
- }
300
- };
301
- const enrichArtifactMetadata = (input, limits) => {
302
- validateArtifactMetadata(input);
303
- if (input.encoding !== "base64" || typeof input.data !== "string") {
304
- return {
305
- size: input.size,
306
- sha256: input.sha256
307
- };
308
- }
309
- const bytes = base64Bytes(input.data, limits.maxBase64Bytes);
310
- const actualSha256 = sha256Digest(bytes);
311
- if (input.size !== undefined && input.size !== bytes.byteLength) {
312
- throw new ValidationError('The "size" artifact option does not match the decoded base64 data.');
313
- }
314
- if (input.sha256 !== undefined && input.sha256.toLowerCase() !== actualSha256) {
315
- throw new ValidationError('The "sha256" artifact option does not match the decoded base64 data.');
316
- }
317
- return {
318
- size: input.size ?? bytes.byteLength,
319
- sha256: actualSha256
320
- };
321
- };
322
- const validateArtifactRecordLimits = (artifact, limits) => {
323
- assertByteLimit(utf8Bytes(jsonText(artifact, "record")), limits.maxRecordBytes, "record");
324
- if (artifact.metadata !== undefined) {
325
- assertByteLimit(utf8Bytes(jsonText(artifact.metadata, "metadata")), limits.maxMetadataBytes, "metadata");
326
- }
327
- if (artifact.storageMode !== "binary") {
328
- if (artifact.encoding === "base64") {
329
- if (typeof artifact.data !== "string") {
330
- throw new ValidationError('Artifact data must be a string when "encoding" is "base64".');
331
- }
332
- base64Bytes(artifact.data, limits.maxBase64Bytes);
333
- }
334
- else if (typeof artifact.data === "string") {
335
- assertByteLimit(utf8Bytes(artifact.data), limits.maxTextBytes, "text data");
336
- }
337
- else {
338
- assertByteLimit(utf8Bytes(jsonText(artifact.data, "data")), limits.maxJsonBytes, "JSON data");
339
- }
340
- }
341
- return artifact;
342
- };
343
- const validateIdentifier = (value, fieldName) => {
344
- if (!identifierPattern.test(value)) {
345
- throw new ValidationError(`The "${fieldName}" option must match the SQL identifier pattern [A-Za-z_][A-Za-z0-9_]*.`);
346
- }
347
- return value;
348
- };
349
- const getRecordField = (value, candidates) => {
350
- if (!value || typeof value !== "object") {
351
- return undefined;
352
- }
353
- const record = value;
354
- for (const candidate of candidates) {
355
- if (candidate in record) {
356
- return record[candidate];
357
- }
358
- }
359
- return undefined;
360
- };
361
- const parseArtifactJson = (value, limits) => {
362
- if (!value) {
363
- return undefined;
364
- }
365
- if (typeof value === "string") {
366
- assertByteLimit(utf8Bytes(value), limits.maxRecordBytes, "record");
367
- return validateArtifactRecordLimits(normalizeArtifactRecord(JSON.parse(value)), limits);
368
- }
369
- return validateArtifactRecordLimits(normalizeArtifactRecord(value), limits);
370
- };
371
- const prepareSqliteStatement = (db, sql) => {
372
- if (typeof db.prepare === "function") {
373
- return db.prepare(sql);
374
- }
375
- if (typeof db.query === "function") {
376
- return db.query(sql);
377
- }
378
- throw new ValidationError('The "db" option must expose either a "prepare()" or "query()" method.');
379
- };
380
- const ensurePostgresTable = (() => {
381
- const initializedTables = new WeakMap();
382
- return async (client, tableName, createSql) => {
383
- let tables = initializedTables.get(client);
384
- if (!tables) {
385
- tables = new Map();
386
- initializedTables.set(client, tables);
387
- }
388
- let initialization = tables.get(tableName);
389
- if (!initialization) {
390
- initialization = Promise.resolve(client.query(createSql, [])).then(() => undefined);
391
- tables.set(tableName, initialization);
392
- }
393
- await initialization;
394
- };
395
- })();
396
- const createArtifact = (input, limits, existing, internal) => {
397
- validateArtifactSaveInput(input, limits, internal);
398
- const integrity = enrichArtifactMetadata(input, limits);
399
- const now = Date.now();
400
- const artifact = {
401
- schemaVersion: ARTIFACT_SCHEMA_VERSION,
402
- revision: existing ? existing.revision + 1 : 1,
403
- id: input.id ?? existing?.id ?? randomId("art"),
404
- appName: input.appName,
405
- userId: input.userId,
406
- sessionId: input.sessionId,
407
- workflowRunId: input.workflowRunId,
408
- workflowStepId: input.workflowStepId,
409
- agentRunId: input.agentRunId,
410
- name: input.name,
411
- contentType: input.contentType,
412
- data: cloneJson(input.data),
413
- encoding: input.encoding,
414
- size: integrity.size,
415
- sha256: integrity.sha256,
416
- storageMode: input.storageMode ?? "json",
417
- blobPath: internal?.blobPath,
418
- metadata: input.metadata ? cloneJson(input.metadata) : undefined,
419
- createdAt: existing?.createdAt ?? now,
420
- updatedAt: existing ? Math.max(now, existing.updatedAt + 1) : now
421
- };
422
- assertByteLimit(utf8Bytes(jsonText(artifact, "record")), limits.maxRecordBytes, "record");
423
- return artifact;
424
- };
425
- const matchesListInput = (artifact, input) => artifact.appName === input.appName &&
426
- artifact.userId === input.userId &&
427
- artifact.sessionId === input.sessionId &&
428
- (input.workflowRunId === undefined || artifact.workflowRunId === input.workflowRunId) &&
429
- (input.workflowStepId === undefined || artifact.workflowStepId === input.workflowStepId) &&
430
- (input.agentRunId === undefined || artifact.agentRunId === input.agentRunId);
431
- const fileNameForArtifact = (input) => `${canonicalStoreFileStem("artifact", artifactParts(input))}.json`;
432
- const blobPathForArtifact = (input) => path.join("blobs", `${canonicalStoreFileStem("artifact", artifactParts(input))}.bin`);
433
- const legacyFileNameForArtifact = (input) => artifactParts(input).map((part) => encodeURIComponent(part)).join("__") + ".json";
434
- const legacyBlobPathForArtifact = (input) => path.join("blobs", artifactParts(input).map((part) => encodeURIComponent(part)).join("__") + ".bin");
435
- const resolveFileArtifactBlobPath = (directory, artifact) => {
436
- if (!artifact.blobPath) {
437
- return undefined;
438
- }
439
- const normalizedBlobPath = path.normalize(artifact.blobPath);
440
- const expectedBlobPaths = new Set([
441
- blobPathForArtifact(artifact),
442
- legacyBlobPathForArtifact(artifact)
443
- ]);
444
- if (!expectedBlobPaths.has(normalizedBlobPath)) {
445
- throw new ValidationError(`Artifact "${artifact.id}" has an unsafe blobPath.`);
446
- }
447
- const root = path.resolve(directory);
448
- const resolved = path.resolve(root, artifact.blobPath);
449
- if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`)) {
450
- throw new ValidationError(`Artifact "${artifact.id}" blobPath escapes the configured directory.`);
451
- }
452
- return resolved;
453
- };
454
- const lookupFromArtifact = (artifact) => ({
455
- appName: artifact.appName,
456
- userId: artifact.userId,
457
- sessionId: artifact.sessionId,
458
- id: artifact.id
459
- });
460
- export const createBase64ArtifactData = (input, limits = {}) => {
461
- const data = typeof input === "object" && "data" in input ? input.data : input;
462
- const resolvedLimits = resolveArtifactServiceLimits(limits);
463
- const buffer = Buffer.from(bytesFromBinaryInput(data, resolvedLimits.maxBase64Bytes));
464
- return {
465
- data: buffer.toString("base64"),
466
- encoding: "base64",
467
- size: buffer.byteLength
468
- };
469
- };
470
- export const createExternalArtifactReference = (input, limits = {}) => {
471
- const resolvedLimits = resolveArtifactServiceLimits(limits);
472
- validateArtifactMetadata(input);
473
- if (!input.uri || /[\u0000-\u001f\u007f]/.test(input.uri)) {
474
- throw new ValidationError('The "uri" external artifact reference option is required.');
475
- }
476
- if (input.metadata !== undefined && (!input.metadata || typeof input.metadata !== "object" || Array.isArray(input.metadata))) {
477
- throw new ValidationError('Artifact "metadata" must be a JSON object.');
478
- }
479
- const reference = {
480
- data: null,
481
- storageMode: "binary",
482
- size: input.size,
483
- sha256: input.sha256,
484
- metadata: {
485
- ...(input.metadata ?? {}),
486
- externalBlob: {
487
- uri: input.uri,
488
- managedBy: "application"
489
- }
490
- }
491
- };
492
- assertByteLimit(utf8Bytes(jsonText(reference.metadata, "metadata")), resolvedLimits.maxMetadataBytes, "metadata");
493
- return reference;
494
- };
495
- export const verifyArtifactRecordIntegrity = (record, data, limits = {}) => {
496
- const artifact = normalizeArtifactRecord(record);
497
- const resolvedLimits = resolveArtifactServiceLimits(limits);
498
- const issues = [];
499
- let bytes = data;
500
- let decodedInlineBase64 = false;
501
- if (!bytes && artifact.encoding === "base64" && typeof artifact.data === "string") {
502
- try {
503
- bytes = base64Bytes(artifact.data, resolvedLimits.maxBase64Bytes);
504
- decodedInlineBase64 = true;
505
- }
506
- catch {
507
- issues.push({
508
- type: "invalid-base64",
509
- message: `Artifact "${artifact.id}" contains invalid base64 data.`
510
- });
511
- }
512
- }
513
- if (bytes && !decodedInlineBase64) {
514
- try {
515
- assertByteLimit(bytes.byteLength, resolvedLimits.maxBinaryBytes, "binary data");
516
- }
517
- catch (error) {
518
- issues.push({
519
- type: "metadata-invalid",
520
- message: error instanceof Error ? error.message : `Artifact "${artifact.id}" binary data exceeds its limit.`
521
- });
522
- bytes = undefined;
523
- }
524
- }
525
- if (bytes) {
526
- if (artifact.size !== undefined && artifact.size !== bytes.byteLength) {
527
- issues.push({
528
- type: "size-mismatch",
529
- message: `Artifact "${artifact.id}" size does not match.`,
530
- expected: artifact.size,
531
- actual: bytes.byteLength
532
- });
533
- }
534
- if (artifact.sha256 !== undefined) {
535
- const actual = sha256Digest(bytes);
536
- if (artifact.sha256 !== actual) {
537
- issues.push({
538
- type: "sha256-mismatch",
539
- message: `Artifact "${artifact.id}" sha256 does not match.`,
540
- expected: artifact.sha256,
541
- actual
542
- });
543
- }
544
- }
545
- }
546
- return {
547
- ok: issues.length === 0,
548
- artifact,
549
- issues
550
- };
551
- };
552
- export const verifyArtifactIntegrity = async (service, lookup, limits = {}) => {
553
- const artifact = await service.loadArtifact(lookup);
554
- if (!artifact) {
555
- return {
556
- ok: false,
557
- issues: [{
558
- type: "missing-artifact",
559
- message: `Artifact "${lookup.id}" was not found.`
560
- }]
561
- };
562
- }
563
- if (artifact.storageMode === "binary") {
564
- const binary = await service.loadBinaryArtifact(lookup);
565
- if (!binary) {
566
- const external = externalBlobReference(artifact.metadata);
567
- return {
568
- ok: false,
569
- artifact,
570
- issues: [{
571
- type: external ? "external-data-unavailable" : "missing-blob",
572
- message: external
573
- ? `Artifact "${lookup.id}" uses application-managed external data at "${external.uri}".`
574
- : `Artifact "${lookup.id}" binary blob was not found.`
575
- }]
576
- };
577
- }
578
- return verifyArtifactRecordIntegrity(binary.artifact, binary.data, limits);
579
- }
580
- return verifyArtifactRecordIntegrity(artifact, undefined, limits);
581
- };
582
- const listFilesRecursive = async (directory) => {
583
- try {
584
- const entries = await fs.readdir(directory, { withFileTypes: true });
585
- const files = await Promise.all(entries.map(async (entry) => {
586
- const fullPath = path.join(directory, entry.name);
587
- if (entry.isDirectory()) {
588
- return listFilesRecursive(fullPath);
589
- }
590
- return [fullPath];
591
- }));
592
- return files.flat();
593
- }
594
- catch (error) {
595
- if (error.code === "ENOENT") {
596
- return [];
597
- }
598
- throw error;
599
- }
600
- };
601
- export const inspectFileArtifactStore = async (options) => {
602
- const limits = resolveArtifactServiceLimits(options.limits);
603
- const artifacts = new Map();
604
- const issues = [];
605
- const referencedBlobPaths = new Set();
606
- let entries = [];
607
- try {
608
- entries = await fs.readdir(options.directory);
609
- }
610
- catch (error) {
611
- if (error.code !== "ENOENT") {
612
- throw error;
613
- }
614
- }
615
- for (const entry of entries) {
616
- if (!entry.endsWith(".json")) {
617
- continue;
618
- }
619
- const metadataPath = path.join(options.directory, entry);
620
- try {
621
- const stat = await fs.stat(metadataPath);
622
- assertByteLimit(stat.size, limits.maxRecordBytes, "record");
623
- const artifact = validateArtifactRecordLimits(normalizeArtifactRecord(JSON.parse(await fs.readFile(metadataPath, "utf8"))), limits);
624
- const fullBlobPath = resolveFileArtifactBlobPath(options.directory, artifact);
625
- const key = artifactKey(artifact);
626
- if (!artifacts.has(key) || entry === fileNameForArtifact(artifact)) {
627
- artifacts.set(key, artifact);
628
- }
629
- if (artifact.blobPath) {
630
- referencedBlobPaths.add(path.normalize(artifact.blobPath));
631
- }
632
- if (artifact.storageMode === "binary") {
633
- if (!artifact.blobPath) {
634
- if (!externalBlobReference(artifact.metadata)) {
635
- issues.push({
636
- type: "missing-blob",
637
- path: metadataPath,
638
- artifact,
639
- message: `Artifact "${artifact.id}" has no blobPath.`
640
- });
641
- }
642
- }
643
- else {
644
- try {
645
- await fs.stat(fullBlobPath);
646
- }
647
- catch (error) {
648
- if (error.code === "ENOENT") {
649
- issues.push({
650
- type: "missing-blob",
651
- path: fullBlobPath,
652
- artifact,
653
- message: `Artifact "${artifact.id}" references a missing blob.`
654
- });
655
- }
656
- else {
657
- throw error;
658
- }
659
- }
660
- }
661
- }
662
- }
663
- catch (error) {
664
- issues.push({
665
- type: "invalid-metadata",
666
- path: metadataPath,
667
- message: error instanceof Error ? error.message : String(error)
668
- });
669
- }
670
- }
671
- const blobRoot = path.join(options.directory, "blobs");
672
- for (const blobFile of await listFilesRecursive(blobRoot)) {
673
- const relative = path.normalize(path.relative(options.directory, blobFile));
674
- if (!referencedBlobPaths.has(relative)) {
675
- issues.push({
676
- type: "orphan-blob",
677
- path: blobFile,
678
- message: `Blob "${relative}" is not referenced by artifact metadata.`
679
- });
680
- }
681
- }
682
- return {
683
- directory: options.directory,
684
- artifacts: [...artifacts.values()].sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id)),
685
- issues
686
- };
687
- };
688
- export const cleanupFileArtifactStore = async (options) => {
689
- const inspection = await inspectFileArtifactStore(options);
690
- const deletedBlobPaths = [];
691
- for (const issue of inspection.issues) {
692
- if (issue.type !== "orphan-blob") {
693
- continue;
694
- }
695
- if (!options.dryRun) {
696
- await fs.unlink(issue.path);
697
- }
698
- deletedBlobPaths.push(issue.path);
699
- }
700
- return {
701
- ...inspection,
702
- dryRun: Boolean(options.dryRun),
703
- deletedBlobPaths
704
- };
705
- };
706
- export const pruneFileArtifactStore = async (options) => {
707
- const now = options.now ?? Date.now();
708
- const dryRun = options.dryRun ?? true;
709
- const inspection = await inspectFileArtifactStore({
710
- directory: options.directory,
711
- limits: options.limits
712
- });
713
- const sorted = inspection.artifacts.sort((left, right) => right.updatedAt - left.updatedAt || artifactKey(left).localeCompare(artifactKey(right)));
714
- const keepByCount = new Set(options.keepLast === undefined ? [] : sorted.slice(0, Math.max(0, options.keepLast)).map((artifact) => artifactKey(artifact)));
715
- const shouldDelete = (artifact) => !keepByCount.has(artifactKey(artifact)) &&
716
- (options.olderThanMs !== undefined ? now - artifact.updatedAt > options.olderThanMs : options.keepLast !== undefined);
717
- const deleted = sorted.filter(shouldDelete);
718
- const deletedBlobPaths = deleted.flatMap((artifact) => artifact.blobPath ? [artifact.blobPath] : []);
719
- if (!dryRun) {
720
- for (const artifact of deleted) {
721
- await fs.unlink(path.join(options.directory, fileNameForArtifact(artifact))).catch((error) => {
722
- if (error.code !== "ENOENT") {
723
- throw error;
724
- }
725
- });
726
- if (artifact.blobPath) {
727
- await fs.unlink(resolveFileArtifactBlobPath(options.directory, artifact)).catch((error) => {
728
- if (error.code !== "ENOENT") {
729
- throw error;
730
- }
731
- });
732
- }
733
- }
734
- }
735
- return {
736
- directory: options.directory,
737
- dryRun,
738
- deletedArtifactKeys: deleted.map((artifact) => artifactKey(artifact)),
739
- keptArtifactKeys: sorted.filter((artifact) => !shouldDelete(artifact)).map((artifact) => artifactKey(artifact)),
740
- deletedBlobPaths
741
- };
742
- };
743
- export const createInMemoryArtifactService = (options = {}) => {
744
- const limits = resolveArtifactServiceLimits(options.limits);
745
- const artifacts = new Map();
746
- const binaryData = new Map();
747
- return {
748
- saveArtifact(input) {
749
- const id = input.id ?? randomId("art");
750
- const lookup = {
751
- appName: input.appName,
752
- userId: input.userId,
753
- sessionId: input.sessionId,
754
- id
755
- };
756
- validateArtifactLookup(lookup);
757
- const existing = artifacts.get(artifactKey(lookup));
758
- assertExpectedRevision(existing, input.expectedRevision, "ArtifactRecord");
759
- const artifact = createArtifact({ ...input, id }, limits, existing);
760
- artifacts.set(artifactKey(lookup), cloneArtifact(artifact));
761
- binaryData.delete(artifactKey(lookup));
762
- return cloneArtifact(artifact);
763
- },
764
- saveBinaryArtifact(input) {
765
- const id = input.id ?? randomId("art");
766
- const lookup = {
767
- appName: input.appName,
768
- userId: input.userId,
769
- sessionId: input.sessionId,
770
- id
771
- };
772
- validateArtifactLookup(lookup);
773
- const bytes = bytesFromBinaryInput(input.data, limits.maxBinaryBytes);
774
- const sha256 = resolveBinarySha256(bytes, input.sha256);
775
- const existing = artifacts.get(artifactKey(lookup));
776
- assertExpectedRevision(existing, input.expectedRevision, "ArtifactRecord");
777
- const artifact = createArtifact({
778
- ...input,
779
- id,
780
- data: null,
781
- encoding: "base64",
782
- size: bytes.byteLength,
783
- sha256,
784
- storageMode: "binary"
785
- }, limits, existing, { managedBinary: true });
786
- artifacts.set(artifactKey(lookup), cloneArtifact(artifact));
787
- binaryData.set(artifactKey(lookup), new Uint8Array(bytes));
788
- return cloneArtifact(artifact);
789
- },
790
- loadArtifact(input) {
791
- validateArtifactLookup(input);
792
- const artifact = artifacts.get(artifactKey(input));
793
- return artifact ? cloneArtifact(artifact) : undefined;
794
- },
795
- loadBinaryArtifact(input) {
796
- validateArtifactLookup(input);
797
- const artifact = artifacts.get(artifactKey(input));
798
- const data = binaryData.get(artifactKey(input));
799
- if (!artifact || !data) {
800
- return undefined;
801
- }
802
- return {
803
- artifact: cloneArtifact(artifact),
804
- data: new Uint8Array(data)
805
- };
806
- },
807
- listArtifacts(input) {
808
- validateArtifactListInput(input);
809
- return [...artifacts.values()]
810
- .filter((artifact) => matchesListInput(artifact, input))
811
- .sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id))
812
- .map(cloneArtifact);
813
- },
814
- deleteArtifact(input) {
815
- validateArtifactLookup(input);
816
- artifacts.delete(artifactKey(input));
817
- binaryData.delete(artifactKey(input));
818
- }
819
- };
820
- };
821
- export const createFileArtifactService = (options) => {
822
- const limits = resolveArtifactServiceLimits(options.limits);
823
- const filePath = (input) => path.join(options.directory, fileNameForArtifact(input));
824
- const binaryPath = (input) => path.join(options.directory, blobPathForArtifact(input));
825
- const legacyFilePath = (input) => path.join(options.directory, legacyFileNameForArtifact(input));
826
- const load = async (input) => {
827
- validateArtifactLookup(input);
828
- for (const candidate of [filePath(input), legacyFilePath(input)]) {
829
- try {
830
- const stat = await fs.stat(candidate);
831
- assertByteLimit(stat.size, limits.maxRecordBytes, "record");
832
- const content = await fs.readFile(candidate, "utf8");
833
- const artifact = validateArtifactRecordLimits(normalizeArtifactRecord(JSON.parse(content)), limits);
834
- if (!matchesArtifactLookup(artifact, input)) {
835
- continue;
836
- }
837
- resolveFileArtifactBlobPath(options.directory, artifact);
838
- return artifact;
839
- }
840
- catch (error) {
841
- if (error.code !== "ENOENT") {
842
- throw error;
843
- }
844
- }
845
- }
846
- return undefined;
847
- };
848
- const save = async (artifact) => {
849
- await ensurePrivateDirectory(options.directory);
850
- await writePrivateFile(filePath(artifact), JSON.stringify(cloneArtifact(artifact)));
851
- try {
852
- const legacyArtifact = normalizeArtifactRecord(JSON.parse(await fs.readFile(legacyFilePath(artifact), "utf8")));
853
- if (matchesArtifactLookup(legacyArtifact, artifact)) {
854
- const legacyBlob = resolveFileArtifactBlobPath(options.directory, legacyArtifact);
855
- await fs.unlink(legacyFilePath(artifact));
856
- if (legacyBlob && legacyArtifact.blobPath !== artifact.blobPath) {
857
- await fs.unlink(legacyBlob).catch((error) => {
858
- if (error.code !== "ENOENT") {
859
- throw error;
860
- }
861
- });
862
- }
863
- }
864
- }
865
- catch (error) {
866
- if (error.code !== "ENOENT") {
867
- throw error;
868
- }
869
- }
870
- };
871
- return {
872
- async saveArtifact(input) {
873
- const id = input.id ?? randomId("art");
874
- const lookup = {
875
- appName: input.appName,
876
- userId: input.userId,
877
- sessionId: input.sessionId,
878
- id
879
- };
880
- validateArtifactLookup(lookup);
881
- const existing = await load(lookup);
882
- assertExpectedRevision(existing, input.expectedRevision, "ArtifactRecord");
883
- const artifact = createArtifact({ ...input, id }, limits, existing);
884
- await save(artifact);
885
- const existingBlob = binaryPath(lookup);
886
- await fs.unlink(existingBlob).catch((error) => {
887
- if (error.code !== "ENOENT") {
888
- throw error;
889
- }
890
- });
891
- return cloneArtifact(artifact);
892
- },
893
- async saveBinaryArtifact(input) {
894
- const id = input.id ?? randomId("art");
895
- const lookup = {
896
- appName: input.appName,
897
- userId: input.userId,
898
- sessionId: input.sessionId,
899
- id
900
- };
901
- validateArtifactLookup(lookup);
902
- const bytes = bytesFromBinaryInput(input.data, limits.maxBinaryBytes);
903
- const sha256 = resolveBinarySha256(bytes, input.sha256);
904
- const blobPath = blobPathForArtifact(lookup);
905
- const existing = await load(lookup);
906
- assertExpectedRevision(existing, input.expectedRevision, "ArtifactRecord");
907
- const artifact = createArtifact({
908
- ...input,
909
- id,
910
- data: null,
911
- encoding: "base64",
912
- size: bytes.byteLength,
913
- sha256,
914
- storageMode: "binary"
915
- }, limits, existing, { blobPath, managedBinary: true });
916
- await ensurePrivateDirectory(path.dirname(binaryPath(lookup)));
917
- await writePrivateFile(binaryPath(lookup), bytes);
918
- await save(artifact);
919
- return cloneArtifact(artifact);
920
- },
921
- async loadArtifact(input) {
922
- return load(input);
923
- },
924
- async loadBinaryArtifact(input) {
925
- const artifact = await load(input);
926
- if (!artifact || artifact.storageMode !== "binary" || !artifact.blobPath) {
927
- return undefined;
928
- }
929
- try {
930
- const resolvedBlobPath = resolveFileArtifactBlobPath(options.directory, artifact);
931
- const stat = await fs.stat(resolvedBlobPath);
932
- assertByteLimit(stat.size, limits.maxBinaryBytes, "binary data");
933
- const data = await fs.readFile(resolvedBlobPath);
934
- assertByteLimit(data.byteLength, limits.maxBinaryBytes, "binary data");
935
- return {
936
- artifact,
937
- data: new Uint8Array(data)
938
- };
939
- }
940
- catch (error) {
941
- if (error.code === "ENOENT") {
942
- return undefined;
943
- }
944
- throw error;
945
- }
946
- },
947
- async listArtifacts(input) {
948
- validateArtifactListInput(input);
949
- let entries;
950
- try {
951
- entries = await fs.readdir(options.directory);
952
- }
953
- catch (error) {
954
- if (error.code === "ENOENT") {
955
- return [];
956
- }
957
- throw error;
958
- }
959
- const artifacts = new Map();
960
- for (const entry of entries) {
961
- if (!entry.endsWith(".json")) {
962
- continue;
963
- }
964
- const metadataPath = path.join(options.directory, entry);
965
- const stat = await fs.stat(metadataPath);
966
- assertByteLimit(stat.size, limits.maxRecordBytes, "record");
967
- const content = await fs.readFile(metadataPath, "utf8");
968
- const artifact = validateArtifactRecordLimits(normalizeArtifactRecord(JSON.parse(content)), limits);
969
- resolveFileArtifactBlobPath(options.directory, artifact);
970
- if (matchesListInput(artifact, input)) {
971
- const key = artifactKey(artifact);
972
- if (!artifacts.has(key) || entry === fileNameForArtifact(artifact)) {
973
- artifacts.set(key, cloneArtifact(artifact));
974
- }
975
- }
976
- }
977
- return [...artifacts.values()].sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id));
978
- },
979
- async deleteArtifact(input) {
980
- validateArtifactLookup(input);
981
- const artifact = await load(input);
982
- await fs.unlink(filePath(input)).catch((error) => {
983
- if (error.code !== "ENOENT") {
984
- throw error;
985
- }
986
- });
987
- try {
988
- const legacyArtifact = normalizeArtifactRecord(JSON.parse(await fs.readFile(legacyFilePath(input), "utf8")));
989
- if (matchesArtifactLookup(legacyArtifact, input)) {
990
- await fs.unlink(legacyFilePath(input));
991
- }
992
- }
993
- catch (error) {
994
- if (error.code !== "ENOENT") {
995
- throw error;
996
- }
997
- }
998
- if (artifact?.blobPath) {
999
- try {
1000
- await fs.unlink(resolveFileArtifactBlobPath(options.directory, artifact));
1001
- }
1002
- catch (error) {
1003
- if (error.code !== "ENOENT") {
1004
- throw error;
1005
- }
1006
- }
1007
- }
1008
- }
1009
- };
1010
- };
1011
- export const createSqliteArtifactService = (options) => {
1012
- const limits = resolveArtifactServiceLimits(options.limits);
1013
- const tableName = validateIdentifier(options.tableName ?? "zhivex_artifacts", "tableName");
1014
- options.db.exec(`
1015
- CREATE TABLE IF NOT EXISTS ${tableName} (
1016
- artifact_key TEXT PRIMARY KEY,
1017
- app_name TEXT NOT NULL,
1018
- user_id TEXT NOT NULL,
1019
- session_id TEXT NOT NULL,
1020
- artifact_id TEXT NOT NULL,
1021
- workflow_run_id TEXT,
1022
- workflow_step_id TEXT,
1023
- agent_run_id TEXT,
1024
- artifact_json TEXT NOT NULL,
1025
- created_at_ms INTEGER NOT NULL,
1026
- updated_at_ms INTEGER NOT NULL
1027
- )
1028
- `);
1029
- const loadStatement = prepareSqliteStatement(options.db, `SELECT artifact_json FROM ${tableName} WHERE artifact_key = ?`);
1030
- const listStatement = prepareSqliteStatement(options.db, `SELECT artifact_json FROM ${tableName}
1031
- WHERE app_name = ?
1032
- AND user_id = ?
1033
- AND session_id = ?
1034
- AND (? IS NULL OR workflow_run_id = ?)
1035
- AND (? IS NULL OR workflow_step_id = ?)
1036
- AND (? IS NULL OR agent_run_id = ?)
1037
- ORDER BY created_at_ms ASC, artifact_id ASC`);
1038
- const saveStatement = prepareSqliteStatement(options.db, `
1039
- INSERT INTO ${tableName} (
1040
- artifact_key,
1041
- app_name,
1042
- user_id,
1043
- session_id,
1044
- artifact_id,
1045
- workflow_run_id,
1046
- workflow_step_id,
1047
- agent_run_id,
1048
- artifact_json,
1049
- created_at_ms,
1050
- updated_at_ms
1051
- )
1052
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1053
- ON CONFLICT(artifact_key) DO UPDATE SET
1054
- app_name = excluded.app_name,
1055
- user_id = excluded.user_id,
1056
- session_id = excluded.session_id,
1057
- artifact_id = excluded.artifact_id,
1058
- workflow_run_id = excluded.workflow_run_id,
1059
- workflow_step_id = excluded.workflow_step_id,
1060
- agent_run_id = excluded.agent_run_id,
1061
- artifact_json = excluded.artifact_json,
1062
- updated_at_ms = excluded.updated_at_ms
1063
- `);
1064
- const insertCasStatement = prepareSqliteStatement(options.db, `
1065
- INSERT INTO ${tableName} (
1066
- artifact_key,
1067
- app_name,
1068
- user_id,
1069
- session_id,
1070
- artifact_id,
1071
- workflow_run_id,
1072
- workflow_step_id,
1073
- agent_run_id,
1074
- artifact_json,
1075
- created_at_ms,
1076
- updated_at_ms
1077
- )
1078
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1079
- ON CONFLICT(artifact_key) DO NOTHING
1080
- `);
1081
- const updateCasStatement = prepareSqliteStatement(options.db, `
1082
- UPDATE ${tableName}
1083
- SET app_name = ?,
1084
- user_id = ?,
1085
- session_id = ?,
1086
- artifact_id = ?,
1087
- workflow_run_id = ?,
1088
- workflow_step_id = ?,
1089
- agent_run_id = ?,
1090
- artifact_json = ?,
1091
- updated_at_ms = ?
1092
- WHERE artifact_key = ?
1093
- AND updated_at_ms = ?
1094
- `);
1095
- const deleteStatement = prepareSqliteStatement(options.db, `DELETE FROM ${tableName} WHERE artifact_key = ?`);
1096
- const load = (input) => {
1097
- validateArtifactLookup(input);
1098
- for (const key of [artifactKey(input), legacyArtifactKey(input)]) {
1099
- const row = loadStatement.get([key]);
1100
- const artifact = parseArtifactJson(getRecordField(row, ["artifact_json", "artifactJson"]), limits);
1101
- if (artifact && matchesArtifactLookup(artifact, input)) {
1102
- return artifact;
1103
- }
1104
- }
1105
- return undefined;
1106
- };
1107
- const save = (artifact, options) => {
1108
- if (options?.expectedRevision === 0 && !options.existing) {
1109
- const result = insertCasStatement.run([
1110
- artifactKey(lookupFromArtifact(artifact)),
1111
- artifact.appName,
1112
- artifact.userId,
1113
- artifact.sessionId,
1114
- artifact.id,
1115
- artifact.workflowRunId ?? null,
1116
- artifact.workflowStepId ?? null,
1117
- artifact.agentRunId ?? null,
1118
- JSON.stringify(artifact),
1119
- artifact.createdAt,
1120
- artifact.updatedAt
1121
- ]);
1122
- if (sqliteMutationCount(result) !== 1) {
1123
- throw new ConflictError("ArtifactRecord revision conflict.");
1124
- }
1125
- }
1126
- else if (options?.expectedRevision !== undefined && options.existing) {
1127
- const canonicalExisting = parseArtifactJson(getRecordField(loadStatement.get([artifactKey(artifact)]), ["artifact_json", "artifactJson"]), limits);
1128
- if (canonicalExisting && matchesArtifactLookup(canonicalExisting, artifact)) {
1129
- const result = updateCasStatement.run([
1130
- artifact.appName,
1131
- artifact.userId,
1132
- artifact.sessionId,
1133
- artifact.id,
1134
- artifact.workflowRunId ?? null,
1135
- artifact.workflowStepId ?? null,
1136
- artifact.agentRunId ?? null,
1137
- JSON.stringify(artifact),
1138
- artifact.updatedAt,
1139
- artifactKey(lookupFromArtifact(artifact)),
1140
- options.existing.updatedAt
1141
- ]);
1142
- if (sqliteMutationCount(result) !== 1) {
1143
- throw new ConflictError("ArtifactRecord revision conflict.");
1144
- }
1145
- }
1146
- else {
1147
- const legacy = parseArtifactJson(getRecordField(loadStatement.get([legacyArtifactKey(artifact)]), ["artifact_json", "artifactJson"]), limits);
1148
- if (!legacy || !matchesArtifactLookup(legacy, artifact) || legacy.updatedAt !== options.existing.updatedAt) {
1149
- throw new ConflictError("ArtifactRecord revision conflict.");
1150
- }
1151
- saveStatement.run([
1152
- artifactKey(artifact),
1153
- artifact.appName,
1154
- artifact.userId,
1155
- artifact.sessionId,
1156
- artifact.id,
1157
- artifact.workflowRunId ?? null,
1158
- artifact.workflowStepId ?? null,
1159
- artifact.agentRunId ?? null,
1160
- JSON.stringify(artifact),
1161
- artifact.createdAt,
1162
- artifact.updatedAt
1163
- ]);
1164
- }
1165
- }
1166
- else {
1167
- saveStatement.run([
1168
- artifactKey(lookupFromArtifact(artifact)),
1169
- artifact.appName,
1170
- artifact.userId,
1171
- artifact.sessionId,
1172
- artifact.id,
1173
- artifact.workflowRunId ?? null,
1174
- artifact.workflowStepId ?? null,
1175
- artifact.agentRunId ?? null,
1176
- JSON.stringify(artifact),
1177
- artifact.createdAt,
1178
- artifact.updatedAt
1179
- ]);
1180
- }
1181
- const legacy = parseArtifactJson(getRecordField(loadStatement.get([legacyArtifactKey(artifact)]), ["artifact_json", "artifactJson"]), limits);
1182
- if (legacy && matchesArtifactLookup(legacy, artifact)) {
1183
- deleteStatement.run([legacyArtifactKey(artifact)]);
1184
- }
1185
- return cloneArtifact(artifact);
1186
- };
1187
- return {
1188
- saveArtifact(input) {
1189
- const id = input.id ?? randomId("art");
1190
- validateArtifactLookup({ appName: input.appName, userId: input.userId, sessionId: input.sessionId, id });
1191
- const existing = load({
1192
- appName: input.appName,
1193
- userId: input.userId,
1194
- sessionId: input.sessionId,
1195
- id
1196
- });
1197
- assertExpectedRevision(existing, input.expectedRevision, "ArtifactRecord");
1198
- return save(createArtifact({ ...input, id }, limits, existing), {
1199
- existing,
1200
- expectedRevision: input.expectedRevision
1201
- });
1202
- },
1203
- saveBinaryArtifact(input) {
1204
- const id = input.id ?? randomId("art");
1205
- validateArtifactLookup({ appName: input.appName, userId: input.userId, sessionId: input.sessionId, id });
1206
- const bytes = bytesFromBinaryInput(input.data, limits.maxBinaryBytes);
1207
- const sha256 = resolveBinarySha256(bytes, input.sha256);
1208
- const existing = load({
1209
- appName: input.appName,
1210
- userId: input.userId,
1211
- sessionId: input.sessionId,
1212
- id
1213
- });
1214
- assertExpectedRevision(existing, input.expectedRevision, "ArtifactRecord");
1215
- return save(createArtifact({
1216
- ...input,
1217
- id,
1218
- data: Buffer.from(bytes).toString("base64"),
1219
- encoding: "base64",
1220
- size: bytes.byteLength,
1221
- sha256,
1222
- storageMode: "json"
1223
- }, limits, existing), {
1224
- existing,
1225
- expectedRevision: input.expectedRevision
1226
- });
1227
- },
1228
- loadArtifact(input) {
1229
- return load(input);
1230
- },
1231
- loadBinaryArtifact(input) {
1232
- const artifact = load(input);
1233
- if (!artifact || artifact.encoding !== "base64" || typeof artifact.data !== "string") {
1234
- return undefined;
1235
- }
1236
- return {
1237
- artifact,
1238
- data: base64Bytes(artifact.data, limits.maxBase64Bytes)
1239
- };
1240
- },
1241
- listArtifacts(input) {
1242
- validateArtifactListInput(input);
1243
- const params = [
1244
- input.appName,
1245
- input.userId,
1246
- input.sessionId,
1247
- input.workflowRunId ?? null,
1248
- input.workflowRunId ?? null,
1249
- input.workflowStepId ?? null,
1250
- input.workflowStepId ?? null,
1251
- input.agentRunId ?? null,
1252
- input.agentRunId ?? null
1253
- ];
1254
- const rows = listStatement.all?.(params) ?? [];
1255
- const artifacts = new Map();
1256
- for (const row of rows) {
1257
- const artifact = parseArtifactJson(getRecordField(row, ["artifact_json", "artifactJson"]), limits);
1258
- if (artifact) {
1259
- artifacts.set(artifactKey(artifact), artifact);
1260
- }
1261
- }
1262
- return [...artifacts.values()];
1263
- },
1264
- deleteArtifact(input) {
1265
- validateArtifactLookup(input);
1266
- deleteStatement.run([artifactKey(input)]);
1267
- const legacy = parseArtifactJson(getRecordField(loadStatement.get([legacyArtifactKey(input)]), ["artifact_json", "artifactJson"]), limits);
1268
- if (legacy && matchesArtifactLookup(legacy, input)) {
1269
- deleteStatement.run([legacyArtifactKey(input)]);
1270
- }
1271
- }
1272
- };
1273
- };
1274
- export const createPostgresArtifactService = (options) => {
1275
- assertPostgresClient(options.client);
1276
- const limits = resolveArtifactServiceLimits(options.limits);
1277
- const tableName = validateIdentifier(options.tableName ?? "zhivex_artifacts", "tableName");
1278
- const createSql = `
1279
- CREATE TABLE IF NOT EXISTS ${tableName} (
1280
- artifact_key TEXT PRIMARY KEY,
1281
- app_name TEXT NOT NULL,
1282
- user_id TEXT NOT NULL,
1283
- session_id TEXT NOT NULL,
1284
- artifact_id TEXT NOT NULL,
1285
- workflow_run_id TEXT,
1286
- workflow_step_id TEXT,
1287
- agent_run_id TEXT,
1288
- artifact_json JSONB NOT NULL,
1289
- created_at_ms BIGINT NOT NULL,
1290
- updated_at_ms BIGINT NOT NULL
1291
- )
1292
- `;
1293
- const load = async (input) => {
1294
- validateArtifactLookup(input);
1295
- await ensurePostgresTable(options.client, tableName, createSql);
1296
- for (const key of [artifactKey(input), legacyArtifactKey(input)]) {
1297
- const result = await options.client.query(`SELECT artifact_json FROM ${tableName} WHERE artifact_key = $1`, [key]);
1298
- const artifact = parseArtifactJson(getRecordField(result.rows[0], ["artifact_json", "artifactJson"]), limits);
1299
- if (artifact && matchesArtifactLookup(artifact, input)) {
1300
- return artifact;
1301
- }
1302
- }
1303
- return undefined;
1304
- };
1305
- const save = async (artifact, saveOptions) => {
1306
- await ensurePostgresTable(options.client, tableName, createSql);
1307
- if (saveOptions?.expectedRevision === 0 && !saveOptions.existing) {
1308
- const result = await options.client.query(`INSERT INTO ${tableName} (
1309
- artifact_key,
1310
- app_name,
1311
- user_id,
1312
- session_id,
1313
- artifact_id,
1314
- workflow_run_id,
1315
- workflow_step_id,
1316
- agent_run_id,
1317
- artifact_json,
1318
- created_at_ms,
1319
- updated_at_ms
1320
- )
1321
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10, $11)
1322
- ON CONFLICT(artifact_key) DO NOTHING
1323
- RETURNING artifact_json`, [
1324
- artifactKey(lookupFromArtifact(artifact)),
1325
- artifact.appName,
1326
- artifact.userId,
1327
- artifact.sessionId,
1328
- artifact.id,
1329
- artifact.workflowRunId ?? null,
1330
- artifact.workflowStepId ?? null,
1331
- artifact.agentRunId ?? null,
1332
- JSON.stringify(artifact),
1333
- artifact.createdAt,
1334
- artifact.updatedAt
1335
- ]);
1336
- if (result.rows.length === 0) {
1337
- throw new ConflictError("ArtifactRecord revision conflict.");
1338
- }
1339
- }
1340
- else if (saveOptions?.expectedRevision !== undefined && saveOptions.existing) {
1341
- const result = await options.client.query(`UPDATE ${tableName}
1342
- SET app_name = $2,
1343
- user_id = $3,
1344
- session_id = $4,
1345
- artifact_id = $5,
1346
- workflow_run_id = $6,
1347
- workflow_step_id = $7,
1348
- agent_run_id = $8,
1349
- artifact_json = $9::jsonb,
1350
- updated_at_ms = $10
1351
- WHERE artifact_key = $1
1352
- AND updated_at_ms = $11
1353
- RETURNING artifact_json`, [
1354
- artifactKey(lookupFromArtifact(artifact)),
1355
- artifact.appName,
1356
- artifact.userId,
1357
- artifact.sessionId,
1358
- artifact.id,
1359
- artifact.workflowRunId ?? null,
1360
- artifact.workflowStepId ?? null,
1361
- artifact.agentRunId ?? null,
1362
- JSON.stringify(artifact),
1363
- artifact.updatedAt,
1364
- saveOptions.existing.updatedAt
1365
- ]);
1366
- if (result.rows.length === 0) {
1367
- const migrated = await options.client.query(`UPDATE ${tableName}
1368
- SET artifact_key = $1,
1369
- app_name = $2,
1370
- user_id = $3,
1371
- session_id = $4,
1372
- artifact_id = $5,
1373
- workflow_run_id = $6,
1374
- workflow_step_id = $7,
1375
- agent_run_id = $8,
1376
- artifact_json = $9::jsonb,
1377
- updated_at_ms = $10
1378
- WHERE artifact_key = $11
1379
- AND updated_at_ms = $12
1380
- RETURNING artifact_json`, [
1381
- artifactKey(artifact),
1382
- artifact.appName,
1383
- artifact.userId,
1384
- artifact.sessionId,
1385
- artifact.id,
1386
- artifact.workflowRunId ?? null,
1387
- artifact.workflowStepId ?? null,
1388
- artifact.agentRunId ?? null,
1389
- JSON.stringify(artifact),
1390
- artifact.updatedAt,
1391
- legacyArtifactKey(artifact),
1392
- saveOptions.existing.updatedAt
1393
- ]);
1394
- if (migrated.rows.length === 0) {
1395
- throw new ConflictError("ArtifactRecord revision conflict.");
1396
- }
1397
- }
1398
- }
1399
- else {
1400
- await options.client.query(`INSERT INTO ${tableName} (
1401
- artifact_key,
1402
- app_name,
1403
- user_id,
1404
- session_id,
1405
- artifact_id,
1406
- workflow_run_id,
1407
- workflow_step_id,
1408
- agent_run_id,
1409
- artifact_json,
1410
- created_at_ms,
1411
- updated_at_ms
1412
- )
1413
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10, $11)
1414
- ON CONFLICT(artifact_key) DO UPDATE SET
1415
- app_name = EXCLUDED.app_name,
1416
- user_id = EXCLUDED.user_id,
1417
- session_id = EXCLUDED.session_id,
1418
- artifact_id = EXCLUDED.artifact_id,
1419
- workflow_run_id = EXCLUDED.workflow_run_id,
1420
- workflow_step_id = EXCLUDED.workflow_step_id,
1421
- agent_run_id = EXCLUDED.agent_run_id,
1422
- artifact_json = EXCLUDED.artifact_json,
1423
- updated_at_ms = EXCLUDED.updated_at_ms`, [
1424
- artifactKey(lookupFromArtifact(artifact)),
1425
- artifact.appName,
1426
- artifact.userId,
1427
- artifact.sessionId,
1428
- artifact.id,
1429
- artifact.workflowRunId ?? null,
1430
- artifact.workflowStepId ?? null,
1431
- artifact.agentRunId ?? null,
1432
- JSON.stringify(artifact),
1433
- artifact.createdAt,
1434
- artifact.updatedAt
1435
- ]);
1436
- }
1437
- const legacyResult = await options.client.query(`SELECT artifact_json FROM ${tableName} WHERE artifact_key = $1`, [legacyArtifactKey(artifact)]);
1438
- const legacy = parseArtifactJson(getRecordField(legacyResult.rows[0], ["artifact_json", "artifactJson"]), limits);
1439
- if (legacy && matchesArtifactLookup(legacy, artifact)) {
1440
- await options.client.query(`DELETE FROM ${tableName} WHERE artifact_key = $1`, [legacyArtifactKey(artifact)]);
1441
- }
1442
- return cloneArtifact(artifact);
1443
- };
1444
- return {
1445
- async saveArtifact(input) {
1446
- const id = input.id ?? randomId("art");
1447
- validateArtifactLookup({ appName: input.appName, userId: input.userId, sessionId: input.sessionId, id });
1448
- const existing = await load({
1449
- appName: input.appName,
1450
- userId: input.userId,
1451
- sessionId: input.sessionId,
1452
- id
1453
- });
1454
- assertExpectedRevision(existing, input.expectedRevision, "ArtifactRecord");
1455
- return save(createArtifact({ ...input, id }, limits, existing), {
1456
- existing,
1457
- expectedRevision: input.expectedRevision
1458
- });
1459
- },
1460
- async saveBinaryArtifact(input) {
1461
- const id = input.id ?? randomId("art");
1462
- validateArtifactLookup({ appName: input.appName, userId: input.userId, sessionId: input.sessionId, id });
1463
- const bytes = bytesFromBinaryInput(input.data, limits.maxBinaryBytes);
1464
- const sha256 = resolveBinarySha256(bytes, input.sha256);
1465
- const existing = await load({
1466
- appName: input.appName,
1467
- userId: input.userId,
1468
- sessionId: input.sessionId,
1469
- id
1470
- });
1471
- assertExpectedRevision(existing, input.expectedRevision, "ArtifactRecord");
1472
- return save(createArtifact({
1473
- ...input,
1474
- id,
1475
- data: Buffer.from(bytes).toString("base64"),
1476
- encoding: "base64",
1477
- size: bytes.byteLength,
1478
- sha256,
1479
- storageMode: "json"
1480
- }, limits, existing), {
1481
- existing,
1482
- expectedRevision: input.expectedRevision
1483
- });
1484
- },
1485
- loadArtifact(input) {
1486
- return load(input);
1487
- },
1488
- async loadBinaryArtifact(input) {
1489
- const artifact = await load(input);
1490
- if (!artifact || artifact.encoding !== "base64" || typeof artifact.data !== "string") {
1491
- return undefined;
1492
- }
1493
- return {
1494
- artifact,
1495
- data: base64Bytes(artifact.data, limits.maxBase64Bytes)
1496
- };
1497
- },
1498
- async listArtifacts(input) {
1499
- validateArtifactListInput(input);
1500
- await ensurePostgresTable(options.client, tableName, createSql);
1501
- const result = await options.client.query(`SELECT artifact_json FROM ${tableName}
1502
- WHERE app_name = $1
1503
- AND user_id = $2
1504
- AND session_id = $3
1505
- AND ($4::text IS NULL OR workflow_run_id = $4)
1506
- AND ($5::text IS NULL OR workflow_step_id = $5)
1507
- AND ($6::text IS NULL OR agent_run_id = $6)
1508
- ORDER BY created_at_ms ASC, artifact_id ASC`, [
1509
- input.appName,
1510
- input.userId,
1511
- input.sessionId,
1512
- input.workflowRunId ?? null,
1513
- input.workflowStepId ?? null,
1514
- input.agentRunId ?? null
1515
- ]);
1516
- const artifacts = new Map();
1517
- for (const row of result.rows) {
1518
- const artifact = parseArtifactJson(getRecordField(row, ["artifact_json", "artifactJson"]), limits);
1519
- if (artifact) {
1520
- artifacts.set(artifactKey(artifact), artifact);
1521
- }
1522
- }
1523
- return [...artifacts.values()];
1524
- },
1525
- async deleteArtifact(input) {
1526
- validateArtifactLookup(input);
1527
- await ensurePostgresTable(options.client, tableName, createSql);
1528
- await options.client.query(`DELETE FROM ${tableName} WHERE artifact_key = $1`, [artifactKey(input)]);
1529
- const legacyResult = await options.client.query(`SELECT artifact_json FROM ${tableName} WHERE artifact_key = $1`, [legacyArtifactKey(input)]);
1530
- const legacy = parseArtifactJson(getRecordField(legacyResult.rows[0], ["artifact_json", "artifactJson"]), limits);
1531
- if (legacy && matchesArtifactLookup(legacy, input)) {
1532
- await options.client.query(`DELETE FROM ${tableName} WHERE artifact_key = $1`, [legacyArtifactKey(input)]);
1533
- }
1534
- }
1535
- };
1536
- };
1
+ /** Compatibility facade. Backend implementations live in dedicated internal modules. */
2
+ export { ARTIFACT_SCHEMA_VERSION, DEFAULT_ARTIFACT_SERVICE_LIMITS, resolveArtifactServiceLimits, normalizeArtifactRecord, migrateArtifactRecord, createBase64ArtifactData, createExternalArtifactReference, verifyArtifactRecordIntegrity, verifyArtifactIntegrity } from "./artifact/shared.js";
3
+ export { createInMemoryArtifactService } from "./artifact/memory.js";
4
+ export { inspectFileArtifactStore, cleanupFileArtifactStore, pruneFileArtifactStore, createFileArtifactService } from "./artifact/file.js";
5
+ export { createSqliteArtifactService } from "./artifact/sqlite.js";
6
+ export { createPostgresArtifactService } from "./artifact/postgres.js";
1537
7
  //# sourceMappingURL=artifact.js.map