@arke-institute/sdk 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/content/index.cjs +591 -0
- package/dist/content/index.cjs.map +1 -0
- package/dist/content/index.d.cts +516 -0
- package/dist/content/index.d.ts +516 -0
- package/dist/content/index.js +558 -0
- package/dist/content/index.js.map +1 -0
- package/dist/edit/index.cjs +1503 -0
- package/dist/edit/index.cjs.map +1 -0
- package/dist/edit/index.d.cts +78 -0
- package/dist/edit/index.d.ts +78 -0
- package/dist/edit/index.js +1447 -0
- package/dist/edit/index.js.map +1 -0
- package/dist/{errors-BrNZWPE7.d.cts → errors-3L7IiHcr.d.cts} +3 -0
- package/dist/{errors-CCyp5KCg.d.ts → errors-BTe8GKRQ.d.ts} +3 -0
- package/dist/errors-CT7yzKkU.d.cts +874 -0
- package/dist/errors-CT7yzKkU.d.ts +874 -0
- package/dist/graph/index.cjs +427 -0
- package/dist/graph/index.cjs.map +1 -0
- package/dist/graph/index.d.cts +485 -0
- package/dist/graph/index.d.ts +485 -0
- package/dist/graph/index.js +396 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/index.cjs +2726 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +2708 -14
- package/dist/index.js.map +1 -1
- package/dist/query/index.cjs +356 -0
- package/dist/query/index.cjs.map +1 -0
- package/dist/query/index.d.cts +636 -0
- package/dist/query/index.d.ts +636 -0
- package/dist/query/index.js +328 -0
- package/dist/query/index.js.map +1 -0
- package/dist/upload/index.cjs +3 -14
- package/dist/upload/index.cjs.map +1 -1
- package/dist/upload/index.d.cts +2 -2
- package/dist/upload/index.d.ts +2 -2
- package/dist/upload/index.js +3 -14
- package/dist/upload/index.js.map +1 -1
- package/package.json +26 -1
|
@@ -0,0 +1,1503 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/edit/index.ts
|
|
31
|
+
var edit_exports = {};
|
|
32
|
+
__export(edit_exports, {
|
|
33
|
+
BackendError: () => BackendError,
|
|
34
|
+
CASConflictError: () => CASConflictError,
|
|
35
|
+
ContentNotFoundError: () => ContentNotFoundError,
|
|
36
|
+
DEFAULT_RETRY_CONFIG: () => DEFAULT_RETRY_CONFIG,
|
|
37
|
+
DeleteError: () => DeleteError,
|
|
38
|
+
DiffEngine: () => DiffEngine,
|
|
39
|
+
EditClient: () => EditClient,
|
|
40
|
+
EditError: () => EditError,
|
|
41
|
+
EditSession: () => EditSession,
|
|
42
|
+
EntityExistsError: () => EntityExistsError,
|
|
43
|
+
EntityNotFoundError: () => EntityNotFoundError,
|
|
44
|
+
IPFSError: () => IPFSError,
|
|
45
|
+
MergeError: () => MergeError,
|
|
46
|
+
NetworkError: () => NetworkError,
|
|
47
|
+
PermissionError: () => PermissionError,
|
|
48
|
+
PromptBuilder: () => PromptBuilder,
|
|
49
|
+
ReprocessError: () => ReprocessError,
|
|
50
|
+
UndeleteError: () => UndeleteError,
|
|
51
|
+
UnmergeError: () => UnmergeError,
|
|
52
|
+
ValidationError: () => ValidationError
|
|
53
|
+
});
|
|
54
|
+
module.exports = __toCommonJS(edit_exports);
|
|
55
|
+
|
|
56
|
+
// src/edit/types.ts
|
|
57
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
58
|
+
maxRetries: 10,
|
|
59
|
+
baseDelay: 100,
|
|
60
|
+
maxDelay: 5e3,
|
|
61
|
+
jitterFactor: 0.3
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/edit/errors.ts
|
|
65
|
+
var EditError = class extends Error {
|
|
66
|
+
constructor(message, code = "UNKNOWN_ERROR", details) {
|
|
67
|
+
super(message);
|
|
68
|
+
this.code = code;
|
|
69
|
+
this.details = details;
|
|
70
|
+
this.name = "EditError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var EntityNotFoundError = class extends EditError {
|
|
74
|
+
constructor(id) {
|
|
75
|
+
super(`Entity not found: ${id}`, "ENTITY_NOT_FOUND", { id });
|
|
76
|
+
this.name = "EntityNotFoundError";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var CASConflictError = class extends EditError {
|
|
80
|
+
constructor(id, expectedTip, actualTip) {
|
|
81
|
+
super(
|
|
82
|
+
`CAS conflict: entity ${id} was modified (expected ${expectedTip}, got ${actualTip})`,
|
|
83
|
+
"CAS_CONFLICT",
|
|
84
|
+
{ id, expectedTip, actualTip }
|
|
85
|
+
);
|
|
86
|
+
this.name = "CASConflictError";
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var EntityExistsError = class extends EditError {
|
|
90
|
+
constructor(id) {
|
|
91
|
+
super(`Entity already exists: ${id}`, "ENTITY_EXISTS", { id });
|
|
92
|
+
this.name = "EntityExistsError";
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var MergeError = class extends EditError {
|
|
96
|
+
constructor(message, sourceId, targetId) {
|
|
97
|
+
super(message, "MERGE_ERROR", { sourceId, targetId });
|
|
98
|
+
this.name = "MergeError";
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var UnmergeError = class extends EditError {
|
|
102
|
+
constructor(message, sourceId, targetId) {
|
|
103
|
+
super(message, "UNMERGE_ERROR", { sourceId, targetId });
|
|
104
|
+
this.name = "UnmergeError";
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
var DeleteError = class extends EditError {
|
|
108
|
+
constructor(message, id) {
|
|
109
|
+
super(message, "DELETE_ERROR", { id });
|
|
110
|
+
this.name = "DeleteError";
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var UndeleteError = class extends EditError {
|
|
114
|
+
constructor(message, id) {
|
|
115
|
+
super(message, "UNDELETE_ERROR", { id });
|
|
116
|
+
this.name = "UndeleteError";
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var ReprocessError = class extends EditError {
|
|
120
|
+
constructor(message, batchId) {
|
|
121
|
+
super(message, "REPROCESS_ERROR", { batchId });
|
|
122
|
+
this.name = "ReprocessError";
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
var ValidationError = class extends EditError {
|
|
126
|
+
constructor(message, field) {
|
|
127
|
+
super(message, "VALIDATION_ERROR", { field });
|
|
128
|
+
this.name = "ValidationError";
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
var PermissionError = class extends EditError {
|
|
132
|
+
constructor(message, id) {
|
|
133
|
+
super(message, "PERMISSION_DENIED", { id });
|
|
134
|
+
this.name = "PermissionError";
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
var NetworkError = class extends EditError {
|
|
138
|
+
constructor(message, statusCode) {
|
|
139
|
+
super(message, "NETWORK_ERROR", { statusCode });
|
|
140
|
+
this.name = "NetworkError";
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var ContentNotFoundError = class extends EditError {
|
|
144
|
+
constructor(cid) {
|
|
145
|
+
super(`Content not found: ${cid}`, "CONTENT_NOT_FOUND", { cid });
|
|
146
|
+
this.name = "ContentNotFoundError";
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var IPFSError = class extends EditError {
|
|
150
|
+
constructor(message) {
|
|
151
|
+
super(message, "IPFS_ERROR");
|
|
152
|
+
this.name = "IPFSError";
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var BackendError = class extends EditError {
|
|
156
|
+
constructor(message) {
|
|
157
|
+
super(message, "BACKEND_ERROR");
|
|
158
|
+
this.name = "BackendError";
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// src/edit/client.ts
|
|
163
|
+
var RETRYABLE_STATUS_CODES = [409, 503];
|
|
164
|
+
var RETRYABLE_ERRORS = ["ECONNRESET", "ETIMEDOUT", "fetch failed"];
|
|
165
|
+
var EditClient = class {
|
|
166
|
+
constructor(config) {
|
|
167
|
+
this.gatewayUrl = config.gatewayUrl.replace(/\/$/, "");
|
|
168
|
+
this.authToken = config.authToken;
|
|
169
|
+
this.network = config.network || "main";
|
|
170
|
+
this.userId = config.userId;
|
|
171
|
+
this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config.retryConfig };
|
|
172
|
+
this.statusUrlTransform = config.statusUrlTransform;
|
|
173
|
+
this.apiPrefix = config.apiPrefix ?? "/api";
|
|
174
|
+
}
|
|
175
|
+
// ===========================================================================
|
|
176
|
+
// Configuration Methods
|
|
177
|
+
// ===========================================================================
|
|
178
|
+
/**
|
|
179
|
+
* Update the auth token (useful for token refresh)
|
|
180
|
+
*/
|
|
181
|
+
setAuthToken(token) {
|
|
182
|
+
this.authToken = token;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Set the network (main or test)
|
|
186
|
+
*/
|
|
187
|
+
setNetwork(network) {
|
|
188
|
+
this.network = network;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Set the user ID for permission checks
|
|
192
|
+
*/
|
|
193
|
+
setUserId(userId) {
|
|
194
|
+
this.userId = userId;
|
|
195
|
+
}
|
|
196
|
+
// ===========================================================================
|
|
197
|
+
// Internal Helpers
|
|
198
|
+
// ===========================================================================
|
|
199
|
+
/**
|
|
200
|
+
* Build URL with API prefix
|
|
201
|
+
*/
|
|
202
|
+
buildUrl(path) {
|
|
203
|
+
return `${this.gatewayUrl}${this.apiPrefix}${path}`;
|
|
204
|
+
}
|
|
205
|
+
sleep(ms) {
|
|
206
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
207
|
+
}
|
|
208
|
+
getHeaders(contentType = "application/json") {
|
|
209
|
+
const headers = {};
|
|
210
|
+
if (contentType) {
|
|
211
|
+
headers["Content-Type"] = contentType;
|
|
212
|
+
}
|
|
213
|
+
if (this.authToken) {
|
|
214
|
+
headers["Authorization"] = `Bearer ${this.authToken}`;
|
|
215
|
+
}
|
|
216
|
+
headers["X-Arke-Network"] = this.network;
|
|
217
|
+
if (this.userId) {
|
|
218
|
+
headers["X-User-Id"] = this.userId;
|
|
219
|
+
}
|
|
220
|
+
return headers;
|
|
221
|
+
}
|
|
222
|
+
calculateDelay(attempt) {
|
|
223
|
+
const { baseDelay, maxDelay, jitterFactor } = this.retryConfig;
|
|
224
|
+
const exponentialDelay = baseDelay * Math.pow(2, attempt);
|
|
225
|
+
const cappedDelay = Math.min(exponentialDelay, maxDelay);
|
|
226
|
+
const jitter = cappedDelay * jitterFactor * (Math.random() * 2 - 1);
|
|
227
|
+
return Math.max(0, cappedDelay + jitter);
|
|
228
|
+
}
|
|
229
|
+
isRetryableStatus(status) {
|
|
230
|
+
return RETRYABLE_STATUS_CODES.includes(status);
|
|
231
|
+
}
|
|
232
|
+
isRetryableError(error) {
|
|
233
|
+
const message = error.message.toLowerCase();
|
|
234
|
+
return RETRYABLE_ERRORS.some((e) => message.includes(e.toLowerCase()));
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Execute a fetch with exponential backoff retry on transient errors
|
|
238
|
+
*/
|
|
239
|
+
async fetchWithRetry(url, options, context) {
|
|
240
|
+
let lastError = null;
|
|
241
|
+
for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
|
|
242
|
+
try {
|
|
243
|
+
const response = await fetch(url, options);
|
|
244
|
+
if (this.isRetryableStatus(response.status) && attempt < this.retryConfig.maxRetries) {
|
|
245
|
+
const delay = this.calculateDelay(attempt);
|
|
246
|
+
lastError = new Error(`${context}: ${response.status} ${response.statusText}`);
|
|
247
|
+
await this.sleep(delay);
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
return response;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
lastError = error;
|
|
253
|
+
if (this.isRetryableError(lastError) && attempt < this.retryConfig.maxRetries) {
|
|
254
|
+
const delay = this.calculateDelay(attempt);
|
|
255
|
+
await this.sleep(delay);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
throw new NetworkError(lastError.message);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
throw lastError || new NetworkError("Request failed after retries");
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Handle common error responses and throw appropriate error types
|
|
265
|
+
*/
|
|
266
|
+
async handleErrorResponse(response, context) {
|
|
267
|
+
let errorData = {};
|
|
268
|
+
try {
|
|
269
|
+
errorData = await response.json();
|
|
270
|
+
} catch {
|
|
271
|
+
}
|
|
272
|
+
const message = errorData.message || `${context}: ${response.statusText}`;
|
|
273
|
+
const errorCode = errorData.error || "";
|
|
274
|
+
switch (response.status) {
|
|
275
|
+
case 400:
|
|
276
|
+
throw new ValidationError(message);
|
|
277
|
+
case 403:
|
|
278
|
+
throw new PermissionError(message);
|
|
279
|
+
case 404:
|
|
280
|
+
throw new EntityNotFoundError(message);
|
|
281
|
+
case 409:
|
|
282
|
+
if (errorCode === "CAS_FAILURE") {
|
|
283
|
+
const details = errorData.details;
|
|
284
|
+
throw new CASConflictError(
|
|
285
|
+
context,
|
|
286
|
+
details?.expect || "unknown",
|
|
287
|
+
details?.actual || "unknown"
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (errorCode === "CONFLICT") {
|
|
291
|
+
throw new EntityExistsError(message);
|
|
292
|
+
}
|
|
293
|
+
throw new EditError(message, errorCode, errorData.details);
|
|
294
|
+
case 503:
|
|
295
|
+
if (errorCode === "IPFS_ERROR") {
|
|
296
|
+
throw new IPFSError(message);
|
|
297
|
+
}
|
|
298
|
+
if (errorCode === "BACKEND_ERROR") {
|
|
299
|
+
throw new BackendError(message);
|
|
300
|
+
}
|
|
301
|
+
throw new NetworkError(message, response.status);
|
|
302
|
+
default:
|
|
303
|
+
throw new EditError(message, errorCode || "API_ERROR", { status: response.status });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// ===========================================================================
|
|
307
|
+
// Entity CRUD Operations
|
|
308
|
+
// ===========================================================================
|
|
309
|
+
/**
|
|
310
|
+
* Create a new entity
|
|
311
|
+
*/
|
|
312
|
+
async createEntity(request) {
|
|
313
|
+
const url = this.buildUrl("/entities");
|
|
314
|
+
const response = await this.fetchWithRetry(
|
|
315
|
+
url,
|
|
316
|
+
{
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: this.getHeaders(),
|
|
319
|
+
body: JSON.stringify(request)
|
|
320
|
+
},
|
|
321
|
+
"Create entity"
|
|
322
|
+
);
|
|
323
|
+
if (!response.ok) {
|
|
324
|
+
await this.handleErrorResponse(response, "Create entity");
|
|
325
|
+
}
|
|
326
|
+
return response.json();
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get an entity by ID
|
|
330
|
+
*/
|
|
331
|
+
async getEntity(id) {
|
|
332
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}`);
|
|
333
|
+
const response = await this.fetchWithRetry(
|
|
334
|
+
url,
|
|
335
|
+
{ headers: this.getHeaders() },
|
|
336
|
+
`Get entity ${id}`
|
|
337
|
+
);
|
|
338
|
+
if (!response.ok) {
|
|
339
|
+
await this.handleErrorResponse(response, `Get entity ${id}`);
|
|
340
|
+
}
|
|
341
|
+
return response.json();
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* List entities with pagination
|
|
345
|
+
*/
|
|
346
|
+
async listEntities(options = {}) {
|
|
347
|
+
const params = new URLSearchParams();
|
|
348
|
+
if (options.limit) params.set("limit", options.limit.toString());
|
|
349
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
350
|
+
if (options.include_metadata) params.set("include_metadata", "true");
|
|
351
|
+
const queryString = params.toString();
|
|
352
|
+
const url = this.buildUrl(`/entities${queryString ? `?${queryString}` : ""}`);
|
|
353
|
+
const response = await this.fetchWithRetry(url, { headers: this.getHeaders() }, "List entities");
|
|
354
|
+
if (!response.ok) {
|
|
355
|
+
await this.handleErrorResponse(response, "List entities");
|
|
356
|
+
}
|
|
357
|
+
return response.json();
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Update an entity (append new version)
|
|
361
|
+
*/
|
|
362
|
+
async updateEntity(id, update) {
|
|
363
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions`);
|
|
364
|
+
const response = await this.fetchWithRetry(
|
|
365
|
+
url,
|
|
366
|
+
{
|
|
367
|
+
method: "POST",
|
|
368
|
+
headers: this.getHeaders(),
|
|
369
|
+
body: JSON.stringify(update)
|
|
370
|
+
},
|
|
371
|
+
`Update entity ${id}`
|
|
372
|
+
);
|
|
373
|
+
if (!response.ok) {
|
|
374
|
+
await this.handleErrorResponse(response, `Update entity ${id}`);
|
|
375
|
+
}
|
|
376
|
+
return response.json();
|
|
377
|
+
}
|
|
378
|
+
// ===========================================================================
|
|
379
|
+
// Version Operations
|
|
380
|
+
// ===========================================================================
|
|
381
|
+
/**
|
|
382
|
+
* List version history for an entity
|
|
383
|
+
*/
|
|
384
|
+
async listVersions(id, options = {}) {
|
|
385
|
+
const params = new URLSearchParams();
|
|
386
|
+
if (options.limit) params.set("limit", options.limit.toString());
|
|
387
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
388
|
+
const queryString = params.toString();
|
|
389
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions${queryString ? `?${queryString}` : ""}`);
|
|
390
|
+
const response = await this.fetchWithRetry(url, { headers: this.getHeaders() }, `List versions for ${id}`);
|
|
391
|
+
if (!response.ok) {
|
|
392
|
+
await this.handleErrorResponse(response, `List versions for ${id}`);
|
|
393
|
+
}
|
|
394
|
+
return response.json();
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Get a specific version of an entity
|
|
398
|
+
*/
|
|
399
|
+
async getVersion(id, selector) {
|
|
400
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/versions/${encodeURIComponent(selector)}`);
|
|
401
|
+
const response = await this.fetchWithRetry(
|
|
402
|
+
url,
|
|
403
|
+
{ headers: this.getHeaders() },
|
|
404
|
+
`Get version ${selector} for ${id}`
|
|
405
|
+
);
|
|
406
|
+
if (!response.ok) {
|
|
407
|
+
await this.handleErrorResponse(response, `Get version ${selector} for ${id}`);
|
|
408
|
+
}
|
|
409
|
+
return response.json();
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Resolve an entity ID to its current tip CID (fast lookup)
|
|
413
|
+
*/
|
|
414
|
+
async resolve(id) {
|
|
415
|
+
const url = this.buildUrl(`/resolve/${encodeURIComponent(id)}`);
|
|
416
|
+
const response = await this.fetchWithRetry(
|
|
417
|
+
url,
|
|
418
|
+
{ headers: this.getHeaders() },
|
|
419
|
+
`Resolve ${id}`
|
|
420
|
+
);
|
|
421
|
+
if (!response.ok) {
|
|
422
|
+
await this.handleErrorResponse(response, `Resolve ${id}`);
|
|
423
|
+
}
|
|
424
|
+
return response.json();
|
|
425
|
+
}
|
|
426
|
+
// ===========================================================================
|
|
427
|
+
// Hierarchy Operations
|
|
428
|
+
// ===========================================================================
|
|
429
|
+
/**
|
|
430
|
+
* Update parent-child hierarchy relationships
|
|
431
|
+
*/
|
|
432
|
+
async updateHierarchy(request) {
|
|
433
|
+
const apiRequest = {
|
|
434
|
+
parent_pi: request.parent_id,
|
|
435
|
+
expect_tip: request.expect_tip,
|
|
436
|
+
add_children: request.add_children,
|
|
437
|
+
remove_children: request.remove_children,
|
|
438
|
+
note: request.note
|
|
439
|
+
};
|
|
440
|
+
const url = this.buildUrl("/hierarchy");
|
|
441
|
+
const response = await this.fetchWithRetry(
|
|
442
|
+
url,
|
|
443
|
+
{
|
|
444
|
+
method: "POST",
|
|
445
|
+
headers: this.getHeaders(),
|
|
446
|
+
body: JSON.stringify(apiRequest)
|
|
447
|
+
},
|
|
448
|
+
`Update hierarchy for ${request.parent_id}`
|
|
449
|
+
);
|
|
450
|
+
if (!response.ok) {
|
|
451
|
+
await this.handleErrorResponse(response, `Update hierarchy for ${request.parent_id}`);
|
|
452
|
+
}
|
|
453
|
+
return response.json();
|
|
454
|
+
}
|
|
455
|
+
// ===========================================================================
|
|
456
|
+
// Merge Operations
|
|
457
|
+
// ===========================================================================
|
|
458
|
+
/**
|
|
459
|
+
* Merge source entity into target entity
|
|
460
|
+
*/
|
|
461
|
+
async mergeEntity(sourceId, request) {
|
|
462
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(sourceId)}/merge`);
|
|
463
|
+
const response = await this.fetchWithRetry(
|
|
464
|
+
url,
|
|
465
|
+
{
|
|
466
|
+
method: "POST",
|
|
467
|
+
headers: this.getHeaders(),
|
|
468
|
+
body: JSON.stringify(request)
|
|
469
|
+
},
|
|
470
|
+
`Merge ${sourceId} into ${request.target_id}`
|
|
471
|
+
);
|
|
472
|
+
if (!response.ok) {
|
|
473
|
+
try {
|
|
474
|
+
const error = await response.json();
|
|
475
|
+
throw new MergeError(
|
|
476
|
+
error.message || `Merge failed: ${response.statusText}`,
|
|
477
|
+
sourceId,
|
|
478
|
+
request.target_id
|
|
479
|
+
);
|
|
480
|
+
} catch (e) {
|
|
481
|
+
if (e instanceof MergeError) throw e;
|
|
482
|
+
await this.handleErrorResponse(response, `Merge ${sourceId}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return response.json();
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Unmerge (restore) a previously merged entity
|
|
489
|
+
*/
|
|
490
|
+
async unmergeEntity(sourceId, request) {
|
|
491
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(sourceId)}/unmerge`);
|
|
492
|
+
const response = await this.fetchWithRetry(
|
|
493
|
+
url,
|
|
494
|
+
{
|
|
495
|
+
method: "POST",
|
|
496
|
+
headers: this.getHeaders(),
|
|
497
|
+
body: JSON.stringify(request)
|
|
498
|
+
},
|
|
499
|
+
`Unmerge ${sourceId}`
|
|
500
|
+
);
|
|
501
|
+
if (!response.ok) {
|
|
502
|
+
try {
|
|
503
|
+
const error = await response.json();
|
|
504
|
+
throw new UnmergeError(
|
|
505
|
+
error.message || `Unmerge failed: ${response.statusText}`,
|
|
506
|
+
sourceId,
|
|
507
|
+
request.target_id
|
|
508
|
+
);
|
|
509
|
+
} catch (e) {
|
|
510
|
+
if (e instanceof UnmergeError) throw e;
|
|
511
|
+
await this.handleErrorResponse(response, `Unmerge ${sourceId}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return response.json();
|
|
515
|
+
}
|
|
516
|
+
// ===========================================================================
|
|
517
|
+
// Delete Operations
|
|
518
|
+
// ===========================================================================
|
|
519
|
+
/**
|
|
520
|
+
* Soft delete an entity (creates tombstone, preserves history)
|
|
521
|
+
*/
|
|
522
|
+
async deleteEntity(id, request) {
|
|
523
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/delete`);
|
|
524
|
+
const response = await this.fetchWithRetry(
|
|
525
|
+
url,
|
|
526
|
+
{
|
|
527
|
+
method: "POST",
|
|
528
|
+
headers: this.getHeaders(),
|
|
529
|
+
body: JSON.stringify(request)
|
|
530
|
+
},
|
|
531
|
+
`Delete ${id}`
|
|
532
|
+
);
|
|
533
|
+
if (!response.ok) {
|
|
534
|
+
try {
|
|
535
|
+
const error = await response.json();
|
|
536
|
+
throw new DeleteError(error.message || `Delete failed: ${response.statusText}`, id);
|
|
537
|
+
} catch (e) {
|
|
538
|
+
if (e instanceof DeleteError) throw e;
|
|
539
|
+
await this.handleErrorResponse(response, `Delete ${id}`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return response.json();
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Restore a deleted entity
|
|
546
|
+
*/
|
|
547
|
+
async undeleteEntity(id, request) {
|
|
548
|
+
const url = this.buildUrl(`/entities/${encodeURIComponent(id)}/undelete`);
|
|
549
|
+
const response = await this.fetchWithRetry(
|
|
550
|
+
url,
|
|
551
|
+
{
|
|
552
|
+
method: "POST",
|
|
553
|
+
headers: this.getHeaders(),
|
|
554
|
+
body: JSON.stringify(request)
|
|
555
|
+
},
|
|
556
|
+
`Undelete ${id}`
|
|
557
|
+
);
|
|
558
|
+
if (!response.ok) {
|
|
559
|
+
try {
|
|
560
|
+
const error = await response.json();
|
|
561
|
+
throw new UndeleteError(error.message || `Undelete failed: ${response.statusText}`, id);
|
|
562
|
+
} catch (e) {
|
|
563
|
+
if (e instanceof UndeleteError) throw e;
|
|
564
|
+
await this.handleErrorResponse(response, `Undelete ${id}`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return response.json();
|
|
568
|
+
}
|
|
569
|
+
// ===========================================================================
|
|
570
|
+
// Content Operations
|
|
571
|
+
// ===========================================================================
|
|
572
|
+
/**
|
|
573
|
+
* Upload files to IPFS
|
|
574
|
+
*/
|
|
575
|
+
async upload(files) {
|
|
576
|
+
let formData;
|
|
577
|
+
if (files instanceof FormData) {
|
|
578
|
+
formData = files;
|
|
579
|
+
} else {
|
|
580
|
+
formData = new FormData();
|
|
581
|
+
const fileArray = Array.isArray(files) ? files : [files];
|
|
582
|
+
for (const file of fileArray) {
|
|
583
|
+
if (file instanceof File) {
|
|
584
|
+
formData.append("file", file, file.name);
|
|
585
|
+
} else {
|
|
586
|
+
formData.append("file", file, "file");
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
const url = this.buildUrl("/upload");
|
|
591
|
+
const response = await this.fetchWithRetry(
|
|
592
|
+
url,
|
|
593
|
+
{
|
|
594
|
+
method: "POST",
|
|
595
|
+
headers: this.getHeaders(null),
|
|
596
|
+
// No Content-Type for multipart
|
|
597
|
+
body: formData
|
|
598
|
+
},
|
|
599
|
+
"Upload files"
|
|
600
|
+
);
|
|
601
|
+
if (!response.ok) {
|
|
602
|
+
await this.handleErrorResponse(response, "Upload files");
|
|
603
|
+
}
|
|
604
|
+
return response.json();
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Upload text content and return CID
|
|
608
|
+
*/
|
|
609
|
+
async uploadContent(content, filename) {
|
|
610
|
+
const blob = new Blob([content], { type: "text/plain" });
|
|
611
|
+
const file = new File([blob], filename, { type: "text/plain" });
|
|
612
|
+
const [result] = await this.upload(file);
|
|
613
|
+
return result.cid;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Download file content by CID
|
|
617
|
+
*/
|
|
618
|
+
async getContent(cid) {
|
|
619
|
+
const url = this.buildUrl(`/cat/${encodeURIComponent(cid)}`);
|
|
620
|
+
const response = await this.fetchWithRetry(
|
|
621
|
+
url,
|
|
622
|
+
{ headers: this.getHeaders() },
|
|
623
|
+
`Get content ${cid}`
|
|
624
|
+
);
|
|
625
|
+
if (response.status === 404) {
|
|
626
|
+
throw new ContentNotFoundError(cid);
|
|
627
|
+
}
|
|
628
|
+
if (!response.ok) {
|
|
629
|
+
await this.handleErrorResponse(response, `Get content ${cid}`);
|
|
630
|
+
}
|
|
631
|
+
return response.text();
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Download a DAG node (JSON) by CID
|
|
635
|
+
*/
|
|
636
|
+
async getDag(cid) {
|
|
637
|
+
const url = this.buildUrl(`/dag/${encodeURIComponent(cid)}`);
|
|
638
|
+
const response = await this.fetchWithRetry(
|
|
639
|
+
url,
|
|
640
|
+
{ headers: this.getHeaders() },
|
|
641
|
+
`Get DAG ${cid}`
|
|
642
|
+
);
|
|
643
|
+
if (response.status === 404) {
|
|
644
|
+
throw new ContentNotFoundError(cid);
|
|
645
|
+
}
|
|
646
|
+
if (!response.ok) {
|
|
647
|
+
await this.handleErrorResponse(response, `Get DAG ${cid}`);
|
|
648
|
+
}
|
|
649
|
+
return response.json();
|
|
650
|
+
}
|
|
651
|
+
// ===========================================================================
|
|
652
|
+
// Arke Origin Operations
|
|
653
|
+
// ===========================================================================
|
|
654
|
+
/**
|
|
655
|
+
* Get the Arke origin block (genesis entity)
|
|
656
|
+
*/
|
|
657
|
+
async getArke() {
|
|
658
|
+
const url = this.buildUrl("/arke");
|
|
659
|
+
const response = await this.fetchWithRetry(
|
|
660
|
+
url,
|
|
661
|
+
{ headers: this.getHeaders() },
|
|
662
|
+
"Get Arke"
|
|
663
|
+
);
|
|
664
|
+
if (!response.ok) {
|
|
665
|
+
await this.handleErrorResponse(response, "Get Arke");
|
|
666
|
+
}
|
|
667
|
+
return response.json();
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Initialize the Arke origin block (creates if doesn't exist)
|
|
671
|
+
*/
|
|
672
|
+
async initArke() {
|
|
673
|
+
const url = this.buildUrl("/arke/init");
|
|
674
|
+
const response = await this.fetchWithRetry(
|
|
675
|
+
url,
|
|
676
|
+
{
|
|
677
|
+
method: "POST",
|
|
678
|
+
headers: this.getHeaders()
|
|
679
|
+
},
|
|
680
|
+
"Init Arke"
|
|
681
|
+
);
|
|
682
|
+
if (!response.ok) {
|
|
683
|
+
await this.handleErrorResponse(response, "Init Arke");
|
|
684
|
+
}
|
|
685
|
+
return response.json();
|
|
686
|
+
}
|
|
687
|
+
// ===========================================================================
|
|
688
|
+
// Reprocess API Operations (via /reprocess/*)
|
|
689
|
+
// ===========================================================================
|
|
690
|
+
/**
|
|
691
|
+
* Trigger reprocessing for an entity
|
|
692
|
+
*/
|
|
693
|
+
async reprocess(request) {
|
|
694
|
+
const response = await this.fetchWithRetry(
|
|
695
|
+
`${this.gatewayUrl}/reprocess/reprocess`,
|
|
696
|
+
{
|
|
697
|
+
method: "POST",
|
|
698
|
+
headers: this.getHeaders(),
|
|
699
|
+
body: JSON.stringify({
|
|
700
|
+
pi: request.pi,
|
|
701
|
+
phases: request.phases,
|
|
702
|
+
cascade: request.cascade,
|
|
703
|
+
options: request.options
|
|
704
|
+
})
|
|
705
|
+
},
|
|
706
|
+
`Reprocess ${request.pi}`
|
|
707
|
+
);
|
|
708
|
+
if (response.status === 403) {
|
|
709
|
+
const error = await response.json().catch(() => ({}));
|
|
710
|
+
throw new PermissionError(
|
|
711
|
+
error.message || `Permission denied to reprocess ${request.pi}`,
|
|
712
|
+
request.pi
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
if (!response.ok) {
|
|
716
|
+
const error = await response.json().catch(() => ({}));
|
|
717
|
+
throw new ReprocessError(error.message || `Reprocess failed: ${response.statusText}`);
|
|
718
|
+
}
|
|
719
|
+
return response.json();
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Get reprocessing status by batch ID
|
|
723
|
+
*/
|
|
724
|
+
async getReprocessStatus(statusUrl, isFirstPoll = false) {
|
|
725
|
+
const fetchUrl = this.statusUrlTransform ? this.statusUrlTransform(statusUrl) : statusUrl;
|
|
726
|
+
const delay = isFirstPoll ? 3e3 : this.retryConfig.baseDelay;
|
|
727
|
+
if (isFirstPoll) {
|
|
728
|
+
await this.sleep(delay);
|
|
729
|
+
}
|
|
730
|
+
const response = await this.fetchWithRetry(
|
|
731
|
+
fetchUrl,
|
|
732
|
+
{ headers: this.getHeaders() },
|
|
733
|
+
"Get reprocess status"
|
|
734
|
+
);
|
|
735
|
+
if (!response.ok) {
|
|
736
|
+
throw new EditError(
|
|
737
|
+
`Failed to fetch reprocess status: ${response.statusText}`,
|
|
738
|
+
"STATUS_ERROR",
|
|
739
|
+
{ status: response.status }
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
return response.json();
|
|
743
|
+
}
|
|
744
|
+
// ===========================================================================
|
|
745
|
+
// Utility Methods
|
|
746
|
+
// ===========================================================================
|
|
747
|
+
/**
|
|
748
|
+
* Execute an operation with automatic CAS retry
|
|
749
|
+
*/
|
|
750
|
+
async withCAS(id, operation, maxRetries = 3) {
|
|
751
|
+
let lastError = null;
|
|
752
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
753
|
+
try {
|
|
754
|
+
const entity = await this.getEntity(id);
|
|
755
|
+
return await operation(entity);
|
|
756
|
+
} catch (error) {
|
|
757
|
+
if (error instanceof CASConflictError && attempt < maxRetries - 1) {
|
|
758
|
+
lastError = error;
|
|
759
|
+
const delay = this.calculateDelay(attempt);
|
|
760
|
+
await this.sleep(delay);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
throw error;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
throw lastError || new EditError("withCAS failed after retries");
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
// src/edit/diff.ts
|
|
771
|
+
var Diff = __toESM(require("diff"), 1);
|
|
772
|
+
var DiffEngine = class {
|
|
773
|
+
/**
|
|
774
|
+
* Compute diff between two strings
|
|
775
|
+
*/
|
|
776
|
+
static diff(original, modified) {
|
|
777
|
+
const changes = Diff.diffLines(original, modified);
|
|
778
|
+
const diffs = [];
|
|
779
|
+
let lineNumber = 1;
|
|
780
|
+
for (const change of changes) {
|
|
781
|
+
if (change.added) {
|
|
782
|
+
diffs.push({
|
|
783
|
+
type: "addition",
|
|
784
|
+
modified: change.value.trimEnd(),
|
|
785
|
+
lineNumber
|
|
786
|
+
});
|
|
787
|
+
} else if (change.removed) {
|
|
788
|
+
diffs.push({
|
|
789
|
+
type: "deletion",
|
|
790
|
+
original: change.value.trimEnd(),
|
|
791
|
+
lineNumber
|
|
792
|
+
});
|
|
793
|
+
} else {
|
|
794
|
+
const lines = change.value.split("\n").length - 1;
|
|
795
|
+
lineNumber += lines;
|
|
796
|
+
}
|
|
797
|
+
if (change.added) {
|
|
798
|
+
lineNumber += change.value.split("\n").length - 1;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return diffs;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Compute word-level diff for more granular changes
|
|
805
|
+
*/
|
|
806
|
+
static diffWords(original, modified) {
|
|
807
|
+
const changes = Diff.diffWords(original, modified);
|
|
808
|
+
const diffs = [];
|
|
809
|
+
for (const change of changes) {
|
|
810
|
+
if (change.added) {
|
|
811
|
+
diffs.push({
|
|
812
|
+
type: "addition",
|
|
813
|
+
modified: change.value
|
|
814
|
+
});
|
|
815
|
+
} else if (change.removed) {
|
|
816
|
+
diffs.push({
|
|
817
|
+
type: "deletion",
|
|
818
|
+
original: change.value
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return diffs;
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Create a ComponentDiff from original and modified content
|
|
826
|
+
*/
|
|
827
|
+
static createComponentDiff(componentName, original, modified) {
|
|
828
|
+
const diffs = this.diff(original, modified);
|
|
829
|
+
const hasChanges = diffs.length > 0;
|
|
830
|
+
let summary;
|
|
831
|
+
if (!hasChanges) {
|
|
832
|
+
summary = "No changes";
|
|
833
|
+
} else {
|
|
834
|
+
const additions = diffs.filter((d) => d.type === "addition").length;
|
|
835
|
+
const deletions = diffs.filter((d) => d.type === "deletion").length;
|
|
836
|
+
const parts = [];
|
|
837
|
+
if (additions > 0) parts.push(`${additions} addition${additions > 1 ? "s" : ""}`);
|
|
838
|
+
if (deletions > 0) parts.push(`${deletions} deletion${deletions > 1 ? "s" : ""}`);
|
|
839
|
+
summary = parts.join(", ");
|
|
840
|
+
}
|
|
841
|
+
return {
|
|
842
|
+
componentName,
|
|
843
|
+
diffs,
|
|
844
|
+
summary,
|
|
845
|
+
hasChanges
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Format diffs for AI prompt consumption
|
|
850
|
+
*/
|
|
851
|
+
static formatForPrompt(diffs) {
|
|
852
|
+
if (diffs.length === 0) {
|
|
853
|
+
return "No changes detected.";
|
|
854
|
+
}
|
|
855
|
+
const lines = [];
|
|
856
|
+
for (const diff of diffs) {
|
|
857
|
+
const linePrefix = diff.lineNumber ? `Line ${diff.lineNumber}: ` : "";
|
|
858
|
+
if (diff.type === "addition") {
|
|
859
|
+
lines.push(`${linePrefix}+ ${diff.modified}`);
|
|
860
|
+
} else if (diff.type === "deletion") {
|
|
861
|
+
lines.push(`${linePrefix}- ${diff.original}`);
|
|
862
|
+
} else if (diff.type === "change") {
|
|
863
|
+
lines.push(`${linePrefix}"${diff.original}" \u2192 "${diff.modified}"`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return lines.join("\n");
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Format component diffs for AI prompt
|
|
870
|
+
*/
|
|
871
|
+
static formatComponentDiffsForPrompt(componentDiffs) {
|
|
872
|
+
const sections = [];
|
|
873
|
+
for (const cd of componentDiffs) {
|
|
874
|
+
if (!cd.hasChanges) continue;
|
|
875
|
+
sections.push(`## Changes to ${cd.componentName}:`);
|
|
876
|
+
sections.push(this.formatForPrompt(cd.diffs));
|
|
877
|
+
sections.push("");
|
|
878
|
+
}
|
|
879
|
+
return sections.join("\n");
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Create a unified diff view
|
|
883
|
+
*/
|
|
884
|
+
static unifiedDiff(original, modified, options) {
|
|
885
|
+
const filename = options?.filename || "content";
|
|
886
|
+
const patch = Diff.createPatch(filename, original, modified, "", "", {
|
|
887
|
+
context: options?.context ?? 3
|
|
888
|
+
});
|
|
889
|
+
return patch;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Extract corrections from diffs (specific text replacements)
|
|
893
|
+
*/
|
|
894
|
+
static extractCorrections(original, modified, sourceFile) {
|
|
895
|
+
const wordDiffs = Diff.diffWords(original, modified);
|
|
896
|
+
const corrections = [];
|
|
897
|
+
let i = 0;
|
|
898
|
+
while (i < wordDiffs.length) {
|
|
899
|
+
const current = wordDiffs[i];
|
|
900
|
+
if (current.removed && i + 1 < wordDiffs.length && wordDiffs[i + 1].added) {
|
|
901
|
+
const removed = current.value.trim();
|
|
902
|
+
const added = wordDiffs[i + 1].value.trim();
|
|
903
|
+
if (removed && added && removed !== added) {
|
|
904
|
+
corrections.push({
|
|
905
|
+
original: removed,
|
|
906
|
+
corrected: added,
|
|
907
|
+
sourceFile
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
i += 2;
|
|
911
|
+
} else {
|
|
912
|
+
i++;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return corrections;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Check if two strings are meaningfully different
|
|
919
|
+
* (ignoring whitespace differences)
|
|
920
|
+
*/
|
|
921
|
+
static hasSignificantChanges(original, modified) {
|
|
922
|
+
const normalizedOriginal = original.replace(/\s+/g, " ").trim();
|
|
923
|
+
const normalizedModified = modified.replace(/\s+/g, " ").trim();
|
|
924
|
+
return normalizedOriginal !== normalizedModified;
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
// src/edit/prompts.ts
|
|
929
|
+
var PromptBuilder = class {
|
|
930
|
+
/**
|
|
931
|
+
* Build prompt for AI-first mode (user provides instructions)
|
|
932
|
+
*/
|
|
933
|
+
static buildAIPrompt(userPrompt, component, entityContext, currentContent) {
|
|
934
|
+
const sections = [];
|
|
935
|
+
sections.push(`## Instructions for ${component}`);
|
|
936
|
+
sections.push(userPrompt);
|
|
937
|
+
sections.push("");
|
|
938
|
+
sections.push("## Entity Context");
|
|
939
|
+
sections.push(`- PI: ${entityContext.pi}`);
|
|
940
|
+
sections.push(`- Current version: ${entityContext.ver}`);
|
|
941
|
+
if (entityContext.parentPi) {
|
|
942
|
+
sections.push(`- Parent: ${entityContext.parentPi}`);
|
|
943
|
+
}
|
|
944
|
+
if (entityContext.childrenCount > 0) {
|
|
945
|
+
sections.push(`- Children: ${entityContext.childrenCount}`);
|
|
946
|
+
}
|
|
947
|
+
sections.push("");
|
|
948
|
+
if (currentContent) {
|
|
949
|
+
sections.push(`## Current ${component} content for reference:`);
|
|
950
|
+
sections.push("```");
|
|
951
|
+
sections.push(currentContent.slice(0, 2e3));
|
|
952
|
+
if (currentContent.length > 2e3) {
|
|
953
|
+
sections.push("... [truncated]");
|
|
954
|
+
}
|
|
955
|
+
sections.push("```");
|
|
956
|
+
}
|
|
957
|
+
return sections.join("\n");
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Build prompt incorporating manual edits and diffs
|
|
961
|
+
*/
|
|
962
|
+
static buildEditReviewPrompt(componentDiffs, corrections, component, userInstructions) {
|
|
963
|
+
const sections = [];
|
|
964
|
+
sections.push("## Manual Edits Made");
|
|
965
|
+
sections.push("");
|
|
966
|
+
sections.push("The following manual edits were made to this entity:");
|
|
967
|
+
sections.push("");
|
|
968
|
+
const diffContent = DiffEngine.formatComponentDiffsForPrompt(componentDiffs);
|
|
969
|
+
if (diffContent) {
|
|
970
|
+
sections.push(diffContent);
|
|
971
|
+
}
|
|
972
|
+
if (corrections.length > 0) {
|
|
973
|
+
sections.push("## Corrections Identified");
|
|
974
|
+
sections.push("");
|
|
975
|
+
for (const correction of corrections) {
|
|
976
|
+
const source = correction.sourceFile ? ` (in ${correction.sourceFile})` : "";
|
|
977
|
+
sections.push(`- "${correction.original}" \u2192 "${correction.corrected}"${source}`);
|
|
978
|
+
}
|
|
979
|
+
sections.push("");
|
|
980
|
+
}
|
|
981
|
+
sections.push("## Instructions");
|
|
982
|
+
if (userInstructions) {
|
|
983
|
+
sections.push(userInstructions);
|
|
984
|
+
} else {
|
|
985
|
+
sections.push(
|
|
986
|
+
`Update the ${component} to accurately reflect these changes. Ensure any corrections are incorporated and the content is consistent.`
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
sections.push("");
|
|
990
|
+
sections.push("## Guidance");
|
|
991
|
+
switch (component) {
|
|
992
|
+
case "pinax":
|
|
993
|
+
sections.push(
|
|
994
|
+
"Update metadata fields to reflect any corrections. Pay special attention to dates, names, and other factual information that may have been corrected."
|
|
995
|
+
);
|
|
996
|
+
break;
|
|
997
|
+
case "description":
|
|
998
|
+
sections.push(
|
|
999
|
+
"Regenerate the description incorporating the changes. Maintain the overall tone and structure while ensuring accuracy based on the corrections."
|
|
1000
|
+
);
|
|
1001
|
+
break;
|
|
1002
|
+
case "cheimarros":
|
|
1003
|
+
sections.push(
|
|
1004
|
+
"Update the knowledge graph to reflect any new or corrected entities, relationships, and facts identified in the changes."
|
|
1005
|
+
);
|
|
1006
|
+
break;
|
|
1007
|
+
}
|
|
1008
|
+
return sections.join("\n");
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Build cascade-aware prompt additions
|
|
1012
|
+
*/
|
|
1013
|
+
static buildCascadePrompt(basePrompt, cascadeContext) {
|
|
1014
|
+
const sections = [basePrompt];
|
|
1015
|
+
sections.push("");
|
|
1016
|
+
sections.push("## Cascade Context");
|
|
1017
|
+
sections.push("");
|
|
1018
|
+
sections.push(
|
|
1019
|
+
"This edit is part of a cascading update. After updating this entity, parent entities will also be updated to reflect these changes."
|
|
1020
|
+
);
|
|
1021
|
+
sections.push("");
|
|
1022
|
+
if (cascadeContext.path.length > 1) {
|
|
1023
|
+
sections.push(`Cascade path: ${cascadeContext.path.join(" \u2192 ")}`);
|
|
1024
|
+
sections.push(`Depth: ${cascadeContext.depth}`);
|
|
1025
|
+
}
|
|
1026
|
+
if (cascadeContext.stopAtPi) {
|
|
1027
|
+
sections.push(`Cascade will stop at: ${cascadeContext.stopAtPi}`);
|
|
1028
|
+
}
|
|
1029
|
+
sections.push("");
|
|
1030
|
+
sections.push(
|
|
1031
|
+
"Ensure the content accurately represents the source material so parent aggregations will be correct."
|
|
1032
|
+
);
|
|
1033
|
+
return sections.join("\n");
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Build a general prompt combining multiple instructions
|
|
1037
|
+
*/
|
|
1038
|
+
static buildCombinedPrompt(generalPrompt, componentPrompt, component) {
|
|
1039
|
+
const sections = [];
|
|
1040
|
+
if (generalPrompt) {
|
|
1041
|
+
sections.push("## General Instructions");
|
|
1042
|
+
sections.push(generalPrompt);
|
|
1043
|
+
sections.push("");
|
|
1044
|
+
}
|
|
1045
|
+
if (componentPrompt) {
|
|
1046
|
+
sections.push(`## Specific Instructions for ${component}`);
|
|
1047
|
+
sections.push(componentPrompt);
|
|
1048
|
+
sections.push("");
|
|
1049
|
+
}
|
|
1050
|
+
if (sections.length === 0) {
|
|
1051
|
+
return `Regenerate the ${component} based on the current entity content.`;
|
|
1052
|
+
}
|
|
1053
|
+
return sections.join("\n");
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Build prompt for correction-based updates
|
|
1057
|
+
*/
|
|
1058
|
+
static buildCorrectionPrompt(corrections) {
|
|
1059
|
+
if (corrections.length === 0) {
|
|
1060
|
+
return "";
|
|
1061
|
+
}
|
|
1062
|
+
const sections = [];
|
|
1063
|
+
sections.push("## Corrections Applied");
|
|
1064
|
+
sections.push("");
|
|
1065
|
+
sections.push("The following corrections were made to the source content:");
|
|
1066
|
+
sections.push("");
|
|
1067
|
+
for (const correction of corrections) {
|
|
1068
|
+
const source = correction.sourceFile ? ` in ${correction.sourceFile}` : "";
|
|
1069
|
+
sections.push(`- "${correction.original}" was corrected to "${correction.corrected}"${source}`);
|
|
1070
|
+
if (correction.context) {
|
|
1071
|
+
sections.push(` Context: ${correction.context}`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
sections.push("");
|
|
1075
|
+
sections.push(
|
|
1076
|
+
"Update the metadata and description to reflect these corrections. Previous content may have contained errors based on the incorrect text."
|
|
1077
|
+
);
|
|
1078
|
+
return sections.join("\n");
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Get component-specific regeneration guidance
|
|
1082
|
+
*/
|
|
1083
|
+
static getComponentGuidance(component) {
|
|
1084
|
+
switch (component) {
|
|
1085
|
+
case "pinax":
|
|
1086
|
+
return "Extract and structure metadata including: institution, creator, title, date range, subjects, type, and other relevant fields. Ensure accuracy based on the source content.";
|
|
1087
|
+
case "description":
|
|
1088
|
+
return "Generate a clear, informative description that summarizes the entity content. Focus on what the material contains, its historical significance, and context. Write for a general audience unless otherwise specified.";
|
|
1089
|
+
case "cheimarros":
|
|
1090
|
+
return "Extract entities (people, places, organizations, events) and their relationships. Build a knowledge graph that captures the key facts and connections in the content.";
|
|
1091
|
+
default:
|
|
1092
|
+
return "";
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
// src/edit/session.ts
|
|
1098
|
+
var DEFAULT_SCOPE = {
|
|
1099
|
+
components: [],
|
|
1100
|
+
cascade: false
|
|
1101
|
+
};
|
|
1102
|
+
var DEFAULT_POLL_OPTIONS = {
|
|
1103
|
+
intervalMs: 2e3,
|
|
1104
|
+
timeoutMs: 3e5
|
|
1105
|
+
// 5 minutes
|
|
1106
|
+
};
|
|
1107
|
+
var EditSession = class {
|
|
1108
|
+
constructor(client, pi, config) {
|
|
1109
|
+
this.entity = null;
|
|
1110
|
+
this.loadedComponents = {};
|
|
1111
|
+
// AI mode state
|
|
1112
|
+
this.prompts = {};
|
|
1113
|
+
// Manual mode state
|
|
1114
|
+
this.editedContent = {};
|
|
1115
|
+
this.corrections = [];
|
|
1116
|
+
// Scope
|
|
1117
|
+
this.scope = { ...DEFAULT_SCOPE };
|
|
1118
|
+
// Execution state
|
|
1119
|
+
this.submitting = false;
|
|
1120
|
+
this.result = null;
|
|
1121
|
+
this.statusUrl = null;
|
|
1122
|
+
this.client = client;
|
|
1123
|
+
this.pi = pi;
|
|
1124
|
+
this.mode = config?.mode ?? "ai-prompt";
|
|
1125
|
+
this.aiReviewEnabled = config?.aiReviewEnabled ?? true;
|
|
1126
|
+
}
|
|
1127
|
+
// ===========================================================================
|
|
1128
|
+
// Loading
|
|
1129
|
+
// ===========================================================================
|
|
1130
|
+
/**
|
|
1131
|
+
* Load the entity and its key components
|
|
1132
|
+
*/
|
|
1133
|
+
async load() {
|
|
1134
|
+
this.entity = await this.client.getEntity(this.pi);
|
|
1135
|
+
const priorityComponents = ["description.md", "pinax.json", "cheimarros.json"];
|
|
1136
|
+
await Promise.all(
|
|
1137
|
+
priorityComponents.map(async (name) => {
|
|
1138
|
+
const cid = this.entity.components[name];
|
|
1139
|
+
if (cid) {
|
|
1140
|
+
try {
|
|
1141
|
+
this.loadedComponents[name] = await this.client.getContent(cid);
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
})
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Load a specific component on demand
|
|
1150
|
+
*/
|
|
1151
|
+
async loadComponent(name) {
|
|
1152
|
+
if (this.loadedComponents[name]) {
|
|
1153
|
+
return this.loadedComponents[name];
|
|
1154
|
+
}
|
|
1155
|
+
if (!this.entity) {
|
|
1156
|
+
throw new ValidationError("Session not loaded");
|
|
1157
|
+
}
|
|
1158
|
+
const cid = this.entity.components[name];
|
|
1159
|
+
if (!cid) {
|
|
1160
|
+
return void 0;
|
|
1161
|
+
}
|
|
1162
|
+
const content = await this.client.getContent(cid);
|
|
1163
|
+
this.loadedComponents[name] = content;
|
|
1164
|
+
return content;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Get the loaded entity
|
|
1168
|
+
*/
|
|
1169
|
+
getEntity() {
|
|
1170
|
+
if (!this.entity) {
|
|
1171
|
+
throw new ValidationError("Session not loaded. Call load() first.");
|
|
1172
|
+
}
|
|
1173
|
+
return this.entity;
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Get loaded component content
|
|
1177
|
+
*/
|
|
1178
|
+
getComponents() {
|
|
1179
|
+
return { ...this.loadedComponents };
|
|
1180
|
+
}
|
|
1181
|
+
// ===========================================================================
|
|
1182
|
+
// AI Prompt Mode
|
|
1183
|
+
// ===========================================================================
|
|
1184
|
+
/**
|
|
1185
|
+
* Set a prompt for AI regeneration
|
|
1186
|
+
*/
|
|
1187
|
+
setPrompt(target, prompt) {
|
|
1188
|
+
if (this.mode === "manual-only") {
|
|
1189
|
+
throw new ValidationError("Cannot set prompts in manual-only mode");
|
|
1190
|
+
}
|
|
1191
|
+
this.prompts[target] = prompt;
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Get all prompts
|
|
1195
|
+
*/
|
|
1196
|
+
getPrompts() {
|
|
1197
|
+
return { ...this.prompts };
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Clear a prompt
|
|
1201
|
+
*/
|
|
1202
|
+
clearPrompt(target) {
|
|
1203
|
+
delete this.prompts[target];
|
|
1204
|
+
}
|
|
1205
|
+
// ===========================================================================
|
|
1206
|
+
// Manual Edit Mode
|
|
1207
|
+
// ===========================================================================
|
|
1208
|
+
/**
|
|
1209
|
+
* Set edited content for a component
|
|
1210
|
+
*/
|
|
1211
|
+
setContent(componentName, content) {
|
|
1212
|
+
if (this.mode === "ai-prompt") {
|
|
1213
|
+
throw new ValidationError("Cannot set content in ai-prompt mode");
|
|
1214
|
+
}
|
|
1215
|
+
this.editedContent[componentName] = content;
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Get all edited content
|
|
1219
|
+
*/
|
|
1220
|
+
getEditedContent() {
|
|
1221
|
+
return { ...this.editedContent };
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Clear edited content for a component
|
|
1225
|
+
*/
|
|
1226
|
+
clearContent(componentName) {
|
|
1227
|
+
delete this.editedContent[componentName];
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Add a correction (for OCR fixes, etc.)
|
|
1231
|
+
*/
|
|
1232
|
+
addCorrection(original, corrected, sourceFile) {
|
|
1233
|
+
this.corrections.push({ original, corrected, sourceFile });
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Get all corrections
|
|
1237
|
+
*/
|
|
1238
|
+
getCorrections() {
|
|
1239
|
+
return [...this.corrections];
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Clear corrections
|
|
1243
|
+
*/
|
|
1244
|
+
clearCorrections() {
|
|
1245
|
+
this.corrections = [];
|
|
1246
|
+
}
|
|
1247
|
+
// ===========================================================================
|
|
1248
|
+
// Scope Configuration
|
|
1249
|
+
// ===========================================================================
|
|
1250
|
+
/**
|
|
1251
|
+
* Set the edit scope
|
|
1252
|
+
*/
|
|
1253
|
+
setScope(scope) {
|
|
1254
|
+
this.scope = { ...this.scope, ...scope };
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Get the current scope
|
|
1258
|
+
*/
|
|
1259
|
+
getScope() {
|
|
1260
|
+
return { ...this.scope };
|
|
1261
|
+
}
|
|
1262
|
+
// ===========================================================================
|
|
1263
|
+
// Preview & Summary
|
|
1264
|
+
// ===========================================================================
|
|
1265
|
+
/**
|
|
1266
|
+
* Get diffs for manual changes
|
|
1267
|
+
*/
|
|
1268
|
+
getDiff() {
|
|
1269
|
+
const diffs = [];
|
|
1270
|
+
for (const [name, edited] of Object.entries(this.editedContent)) {
|
|
1271
|
+
const original = this.loadedComponents[name] || "";
|
|
1272
|
+
if (DiffEngine.hasSignificantChanges(original, edited)) {
|
|
1273
|
+
diffs.push(DiffEngine.createComponentDiff(name, original, edited));
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return diffs;
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Preview what prompts will be sent to AI
|
|
1280
|
+
*/
|
|
1281
|
+
previewPrompt() {
|
|
1282
|
+
const result = {};
|
|
1283
|
+
if (!this.entity) return result;
|
|
1284
|
+
const entityContext = {
|
|
1285
|
+
pi: this.entity.id,
|
|
1286
|
+
ver: this.entity.ver,
|
|
1287
|
+
parentPi: this.entity.parent_pi,
|
|
1288
|
+
childrenCount: this.entity.children_pi?.length ?? 0,
|
|
1289
|
+
currentContent: this.loadedComponents
|
|
1290
|
+
};
|
|
1291
|
+
for (const component of this.scope.components) {
|
|
1292
|
+
let prompt;
|
|
1293
|
+
if (this.mode === "ai-prompt") {
|
|
1294
|
+
const componentPrompt = this.prompts[component];
|
|
1295
|
+
const generalPrompt = this.prompts["general"];
|
|
1296
|
+
const combined = PromptBuilder.buildCombinedPrompt(generalPrompt, componentPrompt, component);
|
|
1297
|
+
prompt = PromptBuilder.buildAIPrompt(
|
|
1298
|
+
combined,
|
|
1299
|
+
component,
|
|
1300
|
+
entityContext,
|
|
1301
|
+
this.loadedComponents[`${component}.json`] || this.loadedComponents[`${component}.md`]
|
|
1302
|
+
);
|
|
1303
|
+
} else {
|
|
1304
|
+
const diffs = this.getDiff();
|
|
1305
|
+
const userInstructions = this.prompts["general"] || this.prompts[component];
|
|
1306
|
+
prompt = PromptBuilder.buildEditReviewPrompt(diffs, this.corrections, component, userInstructions);
|
|
1307
|
+
}
|
|
1308
|
+
if (this.scope.cascade) {
|
|
1309
|
+
prompt = PromptBuilder.buildCascadePrompt(prompt, {
|
|
1310
|
+
path: [this.entity.id, this.entity.parent_pi || "root"].filter(Boolean),
|
|
1311
|
+
depth: 0,
|
|
1312
|
+
stopAtPi: this.scope.stopAtPi
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
result[component] = prompt;
|
|
1316
|
+
}
|
|
1317
|
+
return result;
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Get a summary of pending changes
|
|
1321
|
+
*/
|
|
1322
|
+
getChangeSummary() {
|
|
1323
|
+
const diffs = this.getDiff();
|
|
1324
|
+
const hasManualEdits = diffs.some((d) => d.hasChanges);
|
|
1325
|
+
return {
|
|
1326
|
+
mode: this.mode,
|
|
1327
|
+
hasManualEdits,
|
|
1328
|
+
editedComponents: Object.keys(this.editedContent),
|
|
1329
|
+
corrections: [...this.corrections],
|
|
1330
|
+
prompts: { ...this.prompts },
|
|
1331
|
+
scope: { ...this.scope },
|
|
1332
|
+
willRegenerate: [...this.scope.components],
|
|
1333
|
+
willCascade: this.scope.cascade,
|
|
1334
|
+
willSave: hasManualEdits,
|
|
1335
|
+
willReprocess: this.scope.components.length > 0
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
// ===========================================================================
|
|
1339
|
+
// Execution
|
|
1340
|
+
// ===========================================================================
|
|
1341
|
+
/**
|
|
1342
|
+
* Submit changes (saves first if manual edits, then reprocesses)
|
|
1343
|
+
*/
|
|
1344
|
+
async submit(note) {
|
|
1345
|
+
if (this.submitting) {
|
|
1346
|
+
throw new ValidationError("Submit already in progress");
|
|
1347
|
+
}
|
|
1348
|
+
if (!this.entity) {
|
|
1349
|
+
throw new ValidationError("Session not loaded. Call load() first.");
|
|
1350
|
+
}
|
|
1351
|
+
this.submitting = true;
|
|
1352
|
+
this.result = {};
|
|
1353
|
+
try {
|
|
1354
|
+
const diffs = this.getDiff();
|
|
1355
|
+
const hasManualEdits = diffs.some((d) => d.hasChanges);
|
|
1356
|
+
if (hasManualEdits) {
|
|
1357
|
+
const componentUpdates = {};
|
|
1358
|
+
for (const [name, content] of Object.entries(this.editedContent)) {
|
|
1359
|
+
const original = this.loadedComponents[name] || "";
|
|
1360
|
+
if (DiffEngine.hasSignificantChanges(original, content)) {
|
|
1361
|
+
const cid = await this.client.uploadContent(content, name);
|
|
1362
|
+
componentUpdates[name] = cid;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
const version = await this.client.updateEntity(this.pi, {
|
|
1366
|
+
expect_tip: this.entity.manifest_cid,
|
|
1367
|
+
components: componentUpdates,
|
|
1368
|
+
note
|
|
1369
|
+
});
|
|
1370
|
+
this.result.saved = {
|
|
1371
|
+
pi: version.id,
|
|
1372
|
+
newVersion: version.ver,
|
|
1373
|
+
newTip: version.tip
|
|
1374
|
+
};
|
|
1375
|
+
this.entity.manifest_cid = version.tip;
|
|
1376
|
+
this.entity.ver = version.ver;
|
|
1377
|
+
}
|
|
1378
|
+
if (this.scope.components.length > 0) {
|
|
1379
|
+
const customPrompts = this.buildCustomPrompts();
|
|
1380
|
+
const reprocessResult = await this.client.reprocess({
|
|
1381
|
+
pi: this.pi,
|
|
1382
|
+
phases: this.scope.components,
|
|
1383
|
+
cascade: this.scope.cascade,
|
|
1384
|
+
options: {
|
|
1385
|
+
stop_at_pi: this.scope.stopAtPi,
|
|
1386
|
+
custom_prompts: customPrompts,
|
|
1387
|
+
custom_note: note
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
1390
|
+
this.result.reprocess = reprocessResult;
|
|
1391
|
+
this.statusUrl = reprocessResult.status_url;
|
|
1392
|
+
}
|
|
1393
|
+
return this.result;
|
|
1394
|
+
} finally {
|
|
1395
|
+
this.submitting = false;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Wait for reprocessing to complete
|
|
1400
|
+
*/
|
|
1401
|
+
async waitForCompletion(options) {
|
|
1402
|
+
const opts = { ...DEFAULT_POLL_OPTIONS, ...options };
|
|
1403
|
+
if (!this.statusUrl) {
|
|
1404
|
+
return {
|
|
1405
|
+
phase: "complete",
|
|
1406
|
+
saveComplete: true
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
const startTime = Date.now();
|
|
1410
|
+
let isFirstPoll = true;
|
|
1411
|
+
while (true) {
|
|
1412
|
+
const status = await this.client.getReprocessStatus(this.statusUrl, isFirstPoll);
|
|
1413
|
+
isFirstPoll = false;
|
|
1414
|
+
const editStatus = {
|
|
1415
|
+
phase: status.status === "DONE" ? "complete" : status.status === "ERROR" ? "error" : "reprocessing",
|
|
1416
|
+
saveComplete: true,
|
|
1417
|
+
reprocessStatus: status,
|
|
1418
|
+
error: status.error
|
|
1419
|
+
};
|
|
1420
|
+
if (opts.onProgress) {
|
|
1421
|
+
opts.onProgress(editStatus);
|
|
1422
|
+
}
|
|
1423
|
+
if (status.status === "DONE" || status.status === "ERROR") {
|
|
1424
|
+
return editStatus;
|
|
1425
|
+
}
|
|
1426
|
+
if (Date.now() - startTime > opts.timeoutMs) {
|
|
1427
|
+
return {
|
|
1428
|
+
phase: "error",
|
|
1429
|
+
saveComplete: true,
|
|
1430
|
+
reprocessStatus: status,
|
|
1431
|
+
error: "Timeout waiting for reprocessing to complete"
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
await new Promise((resolve) => setTimeout(resolve, opts.intervalMs));
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Get current status without waiting
|
|
1439
|
+
*/
|
|
1440
|
+
async getStatus() {
|
|
1441
|
+
if (!this.statusUrl) {
|
|
1442
|
+
return {
|
|
1443
|
+
phase: this.result?.saved ? "complete" : "idle",
|
|
1444
|
+
saveComplete: !!this.result?.saved
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
const status = await this.client.getReprocessStatus(this.statusUrl);
|
|
1448
|
+
return {
|
|
1449
|
+
phase: status.status === "DONE" ? "complete" : status.status === "ERROR" ? "error" : "reprocessing",
|
|
1450
|
+
saveComplete: true,
|
|
1451
|
+
reprocessStatus: status,
|
|
1452
|
+
error: status.error
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
// ===========================================================================
|
|
1456
|
+
// Private Helpers
|
|
1457
|
+
// ===========================================================================
|
|
1458
|
+
buildCustomPrompts() {
|
|
1459
|
+
const custom = {};
|
|
1460
|
+
if (this.mode === "ai-prompt") {
|
|
1461
|
+
if (this.prompts["general"]) custom.general = this.prompts["general"];
|
|
1462
|
+
if (this.prompts["pinax"]) custom.pinax = this.prompts["pinax"];
|
|
1463
|
+
if (this.prompts["description"]) custom.description = this.prompts["description"];
|
|
1464
|
+
if (this.prompts["cheimarros"]) custom.cheimarros = this.prompts["cheimarros"];
|
|
1465
|
+
} else {
|
|
1466
|
+
const diffs = this.getDiff();
|
|
1467
|
+
const diffContext = DiffEngine.formatComponentDiffsForPrompt(diffs);
|
|
1468
|
+
const correctionContext = PromptBuilder.buildCorrectionPrompt(this.corrections);
|
|
1469
|
+
const basePrompt = [diffContext, correctionContext, this.prompts["general"]].filter(Boolean).join("\n\n");
|
|
1470
|
+
if (basePrompt) {
|
|
1471
|
+
custom.general = basePrompt;
|
|
1472
|
+
}
|
|
1473
|
+
if (this.prompts["pinax"]) custom.pinax = this.prompts["pinax"];
|
|
1474
|
+
if (this.prompts["description"]) custom.description = this.prompts["description"];
|
|
1475
|
+
if (this.prompts["cheimarros"]) custom.cheimarros = this.prompts["cheimarros"];
|
|
1476
|
+
}
|
|
1477
|
+
return custom;
|
|
1478
|
+
}
|
|
1479
|
+
};
|
|
1480
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1481
|
+
0 && (module.exports = {
|
|
1482
|
+
BackendError,
|
|
1483
|
+
CASConflictError,
|
|
1484
|
+
ContentNotFoundError,
|
|
1485
|
+
DEFAULT_RETRY_CONFIG,
|
|
1486
|
+
DeleteError,
|
|
1487
|
+
DiffEngine,
|
|
1488
|
+
EditClient,
|
|
1489
|
+
EditError,
|
|
1490
|
+
EditSession,
|
|
1491
|
+
EntityExistsError,
|
|
1492
|
+
EntityNotFoundError,
|
|
1493
|
+
IPFSError,
|
|
1494
|
+
MergeError,
|
|
1495
|
+
NetworkError,
|
|
1496
|
+
PermissionError,
|
|
1497
|
+
PromptBuilder,
|
|
1498
|
+
ReprocessError,
|
|
1499
|
+
UndeleteError,
|
|
1500
|
+
UnmergeError,
|
|
1501
|
+
ValidationError
|
|
1502
|
+
});
|
|
1503
|
+
//# sourceMappingURL=index.cjs.map
|