@arke-institute/sdk 0.1.1 → 0.1.2

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 (41) hide show
  1. package/dist/content/index.cjs +506 -0
  2. package/dist/content/index.cjs.map +1 -0
  3. package/dist/content/index.d.cts +403 -0
  4. package/dist/content/index.d.ts +403 -0
  5. package/dist/content/index.js +473 -0
  6. package/dist/content/index.js.map +1 -0
  7. package/dist/edit/index.cjs +1029 -0
  8. package/dist/edit/index.cjs.map +1 -0
  9. package/dist/edit/index.d.cts +78 -0
  10. package/dist/edit/index.d.ts +78 -0
  11. package/dist/edit/index.js +983 -0
  12. package/dist/edit/index.js.map +1 -0
  13. package/dist/{errors-BrNZWPE7.d.cts → errors-3L7IiHcr.d.cts} +3 -0
  14. package/dist/errors-B82BMmRP.d.cts +343 -0
  15. package/dist/errors-B82BMmRP.d.ts +343 -0
  16. package/dist/{errors-CCyp5KCg.d.ts → errors-BTe8GKRQ.d.ts} +3 -0
  17. package/dist/graph/index.cjs +433 -0
  18. package/dist/graph/index.cjs.map +1 -0
  19. package/dist/graph/index.d.cts +456 -0
  20. package/dist/graph/index.d.ts +456 -0
  21. package/dist/graph/index.js +402 -0
  22. package/dist/graph/index.js.map +1 -0
  23. package/dist/index.cjs +2126 -14
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +5 -1
  26. package/dist/index.d.ts +5 -1
  27. package/dist/index.js +2108 -14
  28. package/dist/index.js.map +1 -1
  29. package/dist/query/index.cjs +289 -0
  30. package/dist/query/index.cjs.map +1 -0
  31. package/dist/query/index.d.cts +541 -0
  32. package/dist/query/index.d.ts +541 -0
  33. package/dist/query/index.js +261 -0
  34. package/dist/query/index.js.map +1 -0
  35. package/dist/upload/index.cjs +3 -14
  36. package/dist/upload/index.cjs.map +1 -1
  37. package/dist/upload/index.d.cts +2 -2
  38. package/dist/upload/index.d.ts +2 -2
  39. package/dist/upload/index.js +3 -14
  40. package/dist/upload/index.js.map +1 -1
  41. package/package.json +26 -1
@@ -0,0 +1,1029 @@
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
+ CASConflictError: () => CASConflictError,
34
+ DiffEngine: () => DiffEngine,
35
+ EditClient: () => EditClient,
36
+ EditError: () => EditError,
37
+ EditSession: () => EditSession,
38
+ EntityNotFoundError: () => EntityNotFoundError,
39
+ PermissionError: () => PermissionError,
40
+ PromptBuilder: () => PromptBuilder,
41
+ ReprocessError: () => ReprocessError,
42
+ ValidationError: () => ValidationError
43
+ });
44
+ module.exports = __toCommonJS(edit_exports);
45
+
46
+ // src/edit/errors.ts
47
+ var EditError = class extends Error {
48
+ constructor(message, code = "UNKNOWN_ERROR", details) {
49
+ super(message);
50
+ this.code = code;
51
+ this.details = details;
52
+ this.name = "EditError";
53
+ }
54
+ };
55
+ var EntityNotFoundError = class extends EditError {
56
+ constructor(pi) {
57
+ super(`Entity not found: ${pi}`, "ENTITY_NOT_FOUND", { pi });
58
+ this.name = "EntityNotFoundError";
59
+ }
60
+ };
61
+ var CASConflictError = class extends EditError {
62
+ constructor(pi, expectedTip, actualTip) {
63
+ super(
64
+ `CAS conflict: entity ${pi} was modified (expected ${expectedTip}, got ${actualTip})`,
65
+ "CAS_CONFLICT",
66
+ { pi, expectedTip, actualTip }
67
+ );
68
+ this.name = "CASConflictError";
69
+ }
70
+ };
71
+ var ReprocessError = class extends EditError {
72
+ constructor(message, batchId) {
73
+ super(message, "REPROCESS_ERROR", { batchId });
74
+ this.name = "ReprocessError";
75
+ }
76
+ };
77
+ var ValidationError = class extends EditError {
78
+ constructor(message, field) {
79
+ super(message, "VALIDATION_ERROR", { field });
80
+ this.name = "ValidationError";
81
+ }
82
+ };
83
+ var PermissionError = class extends EditError {
84
+ constructor(message, pi) {
85
+ super(message, "PERMISSION_DENIED", { pi });
86
+ this.name = "PermissionError";
87
+ }
88
+ };
89
+
90
+ // src/edit/client.ts
91
+ var DEFAULT_RETRY_OPTIONS = {
92
+ maxRetries: 5,
93
+ initialDelayMs: 2e3,
94
+ // Start with 2s delay (orchestrator needs time to initialize)
95
+ maxDelayMs: 3e4,
96
+ // Cap at 30s
97
+ backoffMultiplier: 2
98
+ // Double each retry
99
+ };
100
+ var EditClient = class {
101
+ constructor(config) {
102
+ this.gatewayUrl = config.gatewayUrl.replace(/\/$/, "");
103
+ this.authToken = config.authToken;
104
+ this.statusUrlTransform = config.statusUrlTransform;
105
+ }
106
+ /**
107
+ * Update the auth token (useful for token refresh)
108
+ */
109
+ setAuthToken(token) {
110
+ this.authToken = token;
111
+ }
112
+ /**
113
+ * Sleep for a given number of milliseconds
114
+ */
115
+ sleep(ms) {
116
+ return new Promise((resolve) => setTimeout(resolve, ms));
117
+ }
118
+ /**
119
+ * Execute a fetch with exponential backoff retry on transient errors
120
+ */
121
+ async fetchWithRetry(url, options, retryOptions = DEFAULT_RETRY_OPTIONS) {
122
+ let lastError = null;
123
+ let delay = retryOptions.initialDelayMs;
124
+ for (let attempt = 0; attempt <= retryOptions.maxRetries; attempt++) {
125
+ try {
126
+ const response = await fetch(url, options);
127
+ if (response.status >= 500 && attempt < retryOptions.maxRetries) {
128
+ lastError = new Error(`Server error: ${response.status} ${response.statusText}`);
129
+ await this.sleep(delay);
130
+ delay = Math.min(delay * retryOptions.backoffMultiplier, retryOptions.maxDelayMs);
131
+ continue;
132
+ }
133
+ return response;
134
+ } catch (error) {
135
+ lastError = error;
136
+ if (attempt < retryOptions.maxRetries) {
137
+ await this.sleep(delay);
138
+ delay = Math.min(delay * retryOptions.backoffMultiplier, retryOptions.maxDelayMs);
139
+ }
140
+ }
141
+ }
142
+ throw lastError || new Error("Request failed after retries");
143
+ }
144
+ getHeaders() {
145
+ const headers = {
146
+ "Content-Type": "application/json"
147
+ };
148
+ if (this.authToken) {
149
+ headers["Authorization"] = `Bearer ${this.authToken}`;
150
+ }
151
+ return headers;
152
+ }
153
+ /**
154
+ * Handle common error responses
155
+ */
156
+ handleErrorResponse(response, context) {
157
+ if (response.status === 403) {
158
+ throw new PermissionError(`Permission denied: ${context}`);
159
+ }
160
+ throw new EditError(
161
+ `${context}: ${response.statusText}`,
162
+ "API_ERROR",
163
+ { status: response.status }
164
+ );
165
+ }
166
+ // ===========================================================================
167
+ // IPFS Wrapper Operations (via /api/*)
168
+ // ===========================================================================
169
+ /**
170
+ * Fetch an entity by PI
171
+ */
172
+ async getEntity(pi) {
173
+ const response = await fetch(`${this.gatewayUrl}/api/entities/${pi}`, {
174
+ headers: this.getHeaders()
175
+ });
176
+ if (response.status === 404) {
177
+ throw new EntityNotFoundError(pi);
178
+ }
179
+ if (!response.ok) {
180
+ this.handleErrorResponse(response, `Failed to fetch entity ${pi}`);
181
+ }
182
+ return response.json();
183
+ }
184
+ /**
185
+ * Fetch content by CID
186
+ */
187
+ async getContent(cid) {
188
+ const response = await fetch(`${this.gatewayUrl}/api/cat/${cid}`, {
189
+ headers: this.getHeaders()
190
+ });
191
+ if (!response.ok) {
192
+ this.handleErrorResponse(response, `Failed to fetch content ${cid}`);
193
+ }
194
+ return response.text();
195
+ }
196
+ /**
197
+ * Upload content and get CID
198
+ */
199
+ async uploadContent(content, filename) {
200
+ const formData = new FormData();
201
+ const blob = new Blob([content], { type: "text/plain" });
202
+ formData.append("file", blob, filename);
203
+ const headers = {};
204
+ if (this.authToken) {
205
+ headers["Authorization"] = `Bearer ${this.authToken}`;
206
+ }
207
+ const response = await fetch(`${this.gatewayUrl}/api/upload`, {
208
+ method: "POST",
209
+ headers,
210
+ body: formData
211
+ });
212
+ if (!response.ok) {
213
+ this.handleErrorResponse(response, "Failed to upload content");
214
+ }
215
+ const result = await response.json();
216
+ return result[0].cid;
217
+ }
218
+ /**
219
+ * Update an entity with new components
220
+ */
221
+ async updateEntity(pi, update) {
222
+ const response = await fetch(`${this.gatewayUrl}/api/entities/${pi}/versions`, {
223
+ method: "POST",
224
+ headers: this.getHeaders(),
225
+ body: JSON.stringify({
226
+ expect_tip: update.expect_tip,
227
+ components: update.components,
228
+ components_remove: update.components_remove,
229
+ note: update.note
230
+ })
231
+ });
232
+ if (response.status === 409) {
233
+ const entity = await this.getEntity(pi);
234
+ throw new CASConflictError(
235
+ pi,
236
+ update.expect_tip,
237
+ entity.manifest_cid
238
+ );
239
+ }
240
+ if (!response.ok) {
241
+ this.handleErrorResponse(response, `Failed to update entity ${pi}`);
242
+ }
243
+ return response.json();
244
+ }
245
+ // ===========================================================================
246
+ // Reprocess API Operations (via /reprocess/*)
247
+ // ===========================================================================
248
+ /**
249
+ * Trigger reprocessing for an entity
250
+ */
251
+ async reprocess(request) {
252
+ const response = await fetch(`${this.gatewayUrl}/reprocess/reprocess`, {
253
+ method: "POST",
254
+ headers: this.getHeaders(),
255
+ body: JSON.stringify({
256
+ pi: request.pi,
257
+ phases: request.phases,
258
+ cascade: request.cascade,
259
+ options: request.options
260
+ })
261
+ });
262
+ if (response.status === 403) {
263
+ const error = await response.json().catch(() => ({}));
264
+ throw new PermissionError(
265
+ error.message || `Permission denied to reprocess ${request.pi}`,
266
+ request.pi
267
+ );
268
+ }
269
+ if (!response.ok) {
270
+ const error = await response.json().catch(() => ({}));
271
+ throw new ReprocessError(
272
+ error.message || `Reprocess failed: ${response.statusText}`,
273
+ void 0
274
+ );
275
+ }
276
+ return response.json();
277
+ }
278
+ /**
279
+ * Get reprocessing status by batch ID
280
+ *
281
+ * Uses exponential backoff retry to handle transient 500 errors
282
+ * that occur when the orchestrator is initializing.
283
+ *
284
+ * @param statusUrl - The status URL returned from reprocess()
285
+ * @param isFirstPoll - If true, uses a longer initial delay (orchestrator warmup)
286
+ */
287
+ async getReprocessStatus(statusUrl, isFirstPoll = false) {
288
+ const retryOptions = isFirstPoll ? { ...DEFAULT_RETRY_OPTIONS, initialDelayMs: 3e3 } : DEFAULT_RETRY_OPTIONS;
289
+ const fetchUrl = this.statusUrlTransform ? this.statusUrlTransform(statusUrl) : statusUrl;
290
+ const response = await this.fetchWithRetry(
291
+ fetchUrl,
292
+ { headers: this.getHeaders() },
293
+ retryOptions
294
+ );
295
+ if (!response.ok) {
296
+ throw new EditError(
297
+ `Failed to fetch reprocess status: ${response.statusText}`,
298
+ "STATUS_ERROR",
299
+ { status: response.status }
300
+ );
301
+ }
302
+ return response.json();
303
+ }
304
+ };
305
+
306
+ // src/edit/diff.ts
307
+ var Diff = __toESM(require("diff"), 1);
308
+ var DiffEngine = class {
309
+ /**
310
+ * Compute diff between two strings
311
+ */
312
+ static diff(original, modified) {
313
+ const changes = Diff.diffLines(original, modified);
314
+ const diffs = [];
315
+ let lineNumber = 1;
316
+ for (const change of changes) {
317
+ if (change.added) {
318
+ diffs.push({
319
+ type: "addition",
320
+ modified: change.value.trimEnd(),
321
+ lineNumber
322
+ });
323
+ } else if (change.removed) {
324
+ diffs.push({
325
+ type: "deletion",
326
+ original: change.value.trimEnd(),
327
+ lineNumber
328
+ });
329
+ } else {
330
+ const lines = change.value.split("\n").length - 1;
331
+ lineNumber += lines;
332
+ }
333
+ if (change.added) {
334
+ lineNumber += change.value.split("\n").length - 1;
335
+ }
336
+ }
337
+ return diffs;
338
+ }
339
+ /**
340
+ * Compute word-level diff for more granular changes
341
+ */
342
+ static diffWords(original, modified) {
343
+ const changes = Diff.diffWords(original, modified);
344
+ const diffs = [];
345
+ for (const change of changes) {
346
+ if (change.added) {
347
+ diffs.push({
348
+ type: "addition",
349
+ modified: change.value
350
+ });
351
+ } else if (change.removed) {
352
+ diffs.push({
353
+ type: "deletion",
354
+ original: change.value
355
+ });
356
+ }
357
+ }
358
+ return diffs;
359
+ }
360
+ /**
361
+ * Create a ComponentDiff from original and modified content
362
+ */
363
+ static createComponentDiff(componentName, original, modified) {
364
+ const diffs = this.diff(original, modified);
365
+ const hasChanges = diffs.length > 0;
366
+ let summary;
367
+ if (!hasChanges) {
368
+ summary = "No changes";
369
+ } else {
370
+ const additions = diffs.filter((d) => d.type === "addition").length;
371
+ const deletions = diffs.filter((d) => d.type === "deletion").length;
372
+ const parts = [];
373
+ if (additions > 0) parts.push(`${additions} addition${additions > 1 ? "s" : ""}`);
374
+ if (deletions > 0) parts.push(`${deletions} deletion${deletions > 1 ? "s" : ""}`);
375
+ summary = parts.join(", ");
376
+ }
377
+ return {
378
+ componentName,
379
+ diffs,
380
+ summary,
381
+ hasChanges
382
+ };
383
+ }
384
+ /**
385
+ * Format diffs for AI prompt consumption
386
+ */
387
+ static formatForPrompt(diffs) {
388
+ if (diffs.length === 0) {
389
+ return "No changes detected.";
390
+ }
391
+ const lines = [];
392
+ for (const diff of diffs) {
393
+ const linePrefix = diff.lineNumber ? `Line ${diff.lineNumber}: ` : "";
394
+ if (diff.type === "addition") {
395
+ lines.push(`${linePrefix}+ ${diff.modified}`);
396
+ } else if (diff.type === "deletion") {
397
+ lines.push(`${linePrefix}- ${diff.original}`);
398
+ } else if (diff.type === "change") {
399
+ lines.push(`${linePrefix}"${diff.original}" \u2192 "${diff.modified}"`);
400
+ }
401
+ }
402
+ return lines.join("\n");
403
+ }
404
+ /**
405
+ * Format component diffs for AI prompt
406
+ */
407
+ static formatComponentDiffsForPrompt(componentDiffs) {
408
+ const sections = [];
409
+ for (const cd of componentDiffs) {
410
+ if (!cd.hasChanges) continue;
411
+ sections.push(`## Changes to ${cd.componentName}:`);
412
+ sections.push(this.formatForPrompt(cd.diffs));
413
+ sections.push("");
414
+ }
415
+ return sections.join("\n");
416
+ }
417
+ /**
418
+ * Create a unified diff view
419
+ */
420
+ static unifiedDiff(original, modified, options) {
421
+ const filename = options?.filename || "content";
422
+ const patch = Diff.createPatch(filename, original, modified, "", "", {
423
+ context: options?.context ?? 3
424
+ });
425
+ return patch;
426
+ }
427
+ /**
428
+ * Extract corrections from diffs (specific text replacements)
429
+ */
430
+ static extractCorrections(original, modified, sourceFile) {
431
+ const wordDiffs = Diff.diffWords(original, modified);
432
+ const corrections = [];
433
+ let i = 0;
434
+ while (i < wordDiffs.length) {
435
+ const current = wordDiffs[i];
436
+ if (current.removed && i + 1 < wordDiffs.length && wordDiffs[i + 1].added) {
437
+ const removed = current.value.trim();
438
+ const added = wordDiffs[i + 1].value.trim();
439
+ if (removed && added && removed !== added) {
440
+ corrections.push({
441
+ original: removed,
442
+ corrected: added,
443
+ sourceFile
444
+ });
445
+ }
446
+ i += 2;
447
+ } else {
448
+ i++;
449
+ }
450
+ }
451
+ return corrections;
452
+ }
453
+ /**
454
+ * Check if two strings are meaningfully different
455
+ * (ignoring whitespace differences)
456
+ */
457
+ static hasSignificantChanges(original, modified) {
458
+ const normalizedOriginal = original.replace(/\s+/g, " ").trim();
459
+ const normalizedModified = modified.replace(/\s+/g, " ").trim();
460
+ return normalizedOriginal !== normalizedModified;
461
+ }
462
+ };
463
+
464
+ // src/edit/prompts.ts
465
+ var PromptBuilder = class {
466
+ /**
467
+ * Build prompt for AI-first mode (user provides instructions)
468
+ */
469
+ static buildAIPrompt(userPrompt, component, entityContext, currentContent) {
470
+ const sections = [];
471
+ sections.push(`## Instructions for ${component}`);
472
+ sections.push(userPrompt);
473
+ sections.push("");
474
+ sections.push("## Entity Context");
475
+ sections.push(`- PI: ${entityContext.pi}`);
476
+ sections.push(`- Current version: ${entityContext.ver}`);
477
+ if (entityContext.parentPi) {
478
+ sections.push(`- Parent: ${entityContext.parentPi}`);
479
+ }
480
+ if (entityContext.childrenCount > 0) {
481
+ sections.push(`- Children: ${entityContext.childrenCount}`);
482
+ }
483
+ sections.push("");
484
+ if (currentContent) {
485
+ sections.push(`## Current ${component} content for reference:`);
486
+ sections.push("```");
487
+ sections.push(currentContent.slice(0, 2e3));
488
+ if (currentContent.length > 2e3) {
489
+ sections.push("... [truncated]");
490
+ }
491
+ sections.push("```");
492
+ }
493
+ return sections.join("\n");
494
+ }
495
+ /**
496
+ * Build prompt incorporating manual edits and diffs
497
+ */
498
+ static buildEditReviewPrompt(componentDiffs, corrections, component, userInstructions) {
499
+ const sections = [];
500
+ sections.push("## Manual Edits Made");
501
+ sections.push("");
502
+ sections.push("The following manual edits were made to this entity:");
503
+ sections.push("");
504
+ const diffContent = DiffEngine.formatComponentDiffsForPrompt(componentDiffs);
505
+ if (diffContent) {
506
+ sections.push(diffContent);
507
+ }
508
+ if (corrections.length > 0) {
509
+ sections.push("## Corrections Identified");
510
+ sections.push("");
511
+ for (const correction of corrections) {
512
+ const source = correction.sourceFile ? ` (in ${correction.sourceFile})` : "";
513
+ sections.push(`- "${correction.original}" \u2192 "${correction.corrected}"${source}`);
514
+ }
515
+ sections.push("");
516
+ }
517
+ sections.push("## Instructions");
518
+ if (userInstructions) {
519
+ sections.push(userInstructions);
520
+ } else {
521
+ sections.push(
522
+ `Update the ${component} to accurately reflect these changes. Ensure any corrections are incorporated and the content is consistent.`
523
+ );
524
+ }
525
+ sections.push("");
526
+ sections.push("## Guidance");
527
+ switch (component) {
528
+ case "pinax":
529
+ sections.push(
530
+ "Update metadata fields to reflect any corrections. Pay special attention to dates, names, and other factual information that may have been corrected."
531
+ );
532
+ break;
533
+ case "description":
534
+ sections.push(
535
+ "Regenerate the description incorporating the changes. Maintain the overall tone and structure while ensuring accuracy based on the corrections."
536
+ );
537
+ break;
538
+ case "cheimarros":
539
+ sections.push(
540
+ "Update the knowledge graph to reflect any new or corrected entities, relationships, and facts identified in the changes."
541
+ );
542
+ break;
543
+ }
544
+ return sections.join("\n");
545
+ }
546
+ /**
547
+ * Build cascade-aware prompt additions
548
+ */
549
+ static buildCascadePrompt(basePrompt, cascadeContext) {
550
+ const sections = [basePrompt];
551
+ sections.push("");
552
+ sections.push("## Cascade Context");
553
+ sections.push("");
554
+ sections.push(
555
+ "This edit is part of a cascading update. After updating this entity, parent entities will also be updated to reflect these changes."
556
+ );
557
+ sections.push("");
558
+ if (cascadeContext.path.length > 1) {
559
+ sections.push(`Cascade path: ${cascadeContext.path.join(" \u2192 ")}`);
560
+ sections.push(`Depth: ${cascadeContext.depth}`);
561
+ }
562
+ if (cascadeContext.stopAtPi) {
563
+ sections.push(`Cascade will stop at: ${cascadeContext.stopAtPi}`);
564
+ }
565
+ sections.push("");
566
+ sections.push(
567
+ "Ensure the content accurately represents the source material so parent aggregations will be correct."
568
+ );
569
+ return sections.join("\n");
570
+ }
571
+ /**
572
+ * Build a general prompt combining multiple instructions
573
+ */
574
+ static buildCombinedPrompt(generalPrompt, componentPrompt, component) {
575
+ const sections = [];
576
+ if (generalPrompt) {
577
+ sections.push("## General Instructions");
578
+ sections.push(generalPrompt);
579
+ sections.push("");
580
+ }
581
+ if (componentPrompt) {
582
+ sections.push(`## Specific Instructions for ${component}`);
583
+ sections.push(componentPrompt);
584
+ sections.push("");
585
+ }
586
+ if (sections.length === 0) {
587
+ return `Regenerate the ${component} based on the current entity content.`;
588
+ }
589
+ return sections.join("\n");
590
+ }
591
+ /**
592
+ * Build prompt for correction-based updates
593
+ */
594
+ static buildCorrectionPrompt(corrections) {
595
+ if (corrections.length === 0) {
596
+ return "";
597
+ }
598
+ const sections = [];
599
+ sections.push("## Corrections Applied");
600
+ sections.push("");
601
+ sections.push("The following corrections were made to the source content:");
602
+ sections.push("");
603
+ for (const correction of corrections) {
604
+ const source = correction.sourceFile ? ` in ${correction.sourceFile}` : "";
605
+ sections.push(`- "${correction.original}" was corrected to "${correction.corrected}"${source}`);
606
+ if (correction.context) {
607
+ sections.push(` Context: ${correction.context}`);
608
+ }
609
+ }
610
+ sections.push("");
611
+ sections.push(
612
+ "Update the metadata and description to reflect these corrections. Previous content may have contained errors based on the incorrect text."
613
+ );
614
+ return sections.join("\n");
615
+ }
616
+ /**
617
+ * Get component-specific regeneration guidance
618
+ */
619
+ static getComponentGuidance(component) {
620
+ switch (component) {
621
+ case "pinax":
622
+ return "Extract and structure metadata including: institution, creator, title, date range, subjects, type, and other relevant fields. Ensure accuracy based on the source content.";
623
+ case "description":
624
+ 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.";
625
+ case "cheimarros":
626
+ return "Extract entities (people, places, organizations, events) and their relationships. Build a knowledge graph that captures the key facts and connections in the content.";
627
+ default:
628
+ return "";
629
+ }
630
+ }
631
+ };
632
+
633
+ // src/edit/session.ts
634
+ var DEFAULT_SCOPE = {
635
+ components: [],
636
+ cascade: false
637
+ };
638
+ var DEFAULT_POLL_OPTIONS = {
639
+ intervalMs: 2e3,
640
+ timeoutMs: 3e5
641
+ // 5 minutes
642
+ };
643
+ var EditSession = class {
644
+ constructor(client, pi, config) {
645
+ this.entity = null;
646
+ this.loadedComponents = {};
647
+ // AI mode state
648
+ this.prompts = {};
649
+ // Manual mode state
650
+ this.editedContent = {};
651
+ this.corrections = [];
652
+ // Scope
653
+ this.scope = { ...DEFAULT_SCOPE };
654
+ // Execution state
655
+ this.submitting = false;
656
+ this.result = null;
657
+ this.statusUrl = null;
658
+ this.client = client;
659
+ this.pi = pi;
660
+ this.mode = config?.mode ?? "ai-prompt";
661
+ this.aiReviewEnabled = config?.aiReviewEnabled ?? true;
662
+ }
663
+ // ===========================================================================
664
+ // Loading
665
+ // ===========================================================================
666
+ /**
667
+ * Load the entity and its key components
668
+ */
669
+ async load() {
670
+ this.entity = await this.client.getEntity(this.pi);
671
+ const priorityComponents = ["description.md", "pinax.json", "cheimarros.json"];
672
+ await Promise.all(
673
+ priorityComponents.map(async (name) => {
674
+ const cid = this.entity.components[name];
675
+ if (cid) {
676
+ try {
677
+ this.loadedComponents[name] = await this.client.getContent(cid);
678
+ } catch {
679
+ }
680
+ }
681
+ })
682
+ );
683
+ }
684
+ /**
685
+ * Load a specific component on demand
686
+ */
687
+ async loadComponent(name) {
688
+ if (this.loadedComponents[name]) {
689
+ return this.loadedComponents[name];
690
+ }
691
+ if (!this.entity) {
692
+ throw new ValidationError("Session not loaded");
693
+ }
694
+ const cid = this.entity.components[name];
695
+ if (!cid) {
696
+ return void 0;
697
+ }
698
+ const content = await this.client.getContent(cid);
699
+ this.loadedComponents[name] = content;
700
+ return content;
701
+ }
702
+ /**
703
+ * Get the loaded entity
704
+ */
705
+ getEntity() {
706
+ if (!this.entity) {
707
+ throw new ValidationError("Session not loaded. Call load() first.");
708
+ }
709
+ return this.entity;
710
+ }
711
+ /**
712
+ * Get loaded component content
713
+ */
714
+ getComponents() {
715
+ return { ...this.loadedComponents };
716
+ }
717
+ // ===========================================================================
718
+ // AI Prompt Mode
719
+ // ===========================================================================
720
+ /**
721
+ * Set a prompt for AI regeneration
722
+ */
723
+ setPrompt(target, prompt) {
724
+ if (this.mode === "manual-only") {
725
+ throw new ValidationError("Cannot set prompts in manual-only mode");
726
+ }
727
+ this.prompts[target] = prompt;
728
+ }
729
+ /**
730
+ * Get all prompts
731
+ */
732
+ getPrompts() {
733
+ return { ...this.prompts };
734
+ }
735
+ /**
736
+ * Clear a prompt
737
+ */
738
+ clearPrompt(target) {
739
+ delete this.prompts[target];
740
+ }
741
+ // ===========================================================================
742
+ // Manual Edit Mode
743
+ // ===========================================================================
744
+ /**
745
+ * Set edited content for a component
746
+ */
747
+ setContent(componentName, content) {
748
+ if (this.mode === "ai-prompt") {
749
+ throw new ValidationError("Cannot set content in ai-prompt mode");
750
+ }
751
+ this.editedContent[componentName] = content;
752
+ }
753
+ /**
754
+ * Get all edited content
755
+ */
756
+ getEditedContent() {
757
+ return { ...this.editedContent };
758
+ }
759
+ /**
760
+ * Clear edited content for a component
761
+ */
762
+ clearContent(componentName) {
763
+ delete this.editedContent[componentName];
764
+ }
765
+ /**
766
+ * Add a correction (for OCR fixes, etc.)
767
+ */
768
+ addCorrection(original, corrected, sourceFile) {
769
+ this.corrections.push({ original, corrected, sourceFile });
770
+ }
771
+ /**
772
+ * Get all corrections
773
+ */
774
+ getCorrections() {
775
+ return [...this.corrections];
776
+ }
777
+ /**
778
+ * Clear corrections
779
+ */
780
+ clearCorrections() {
781
+ this.corrections = [];
782
+ }
783
+ // ===========================================================================
784
+ // Scope Configuration
785
+ // ===========================================================================
786
+ /**
787
+ * Set the edit scope
788
+ */
789
+ setScope(scope) {
790
+ this.scope = { ...this.scope, ...scope };
791
+ }
792
+ /**
793
+ * Get the current scope
794
+ */
795
+ getScope() {
796
+ return { ...this.scope };
797
+ }
798
+ // ===========================================================================
799
+ // Preview & Summary
800
+ // ===========================================================================
801
+ /**
802
+ * Get diffs for manual changes
803
+ */
804
+ getDiff() {
805
+ const diffs = [];
806
+ for (const [name, edited] of Object.entries(this.editedContent)) {
807
+ const original = this.loadedComponents[name] || "";
808
+ if (DiffEngine.hasSignificantChanges(original, edited)) {
809
+ diffs.push(DiffEngine.createComponentDiff(name, original, edited));
810
+ }
811
+ }
812
+ return diffs;
813
+ }
814
+ /**
815
+ * Preview what prompts will be sent to AI
816
+ */
817
+ previewPrompt() {
818
+ const result = {};
819
+ if (!this.entity) return result;
820
+ const entityContext = {
821
+ pi: this.entity.pi,
822
+ ver: this.entity.ver,
823
+ parentPi: this.entity.parent_pi,
824
+ childrenCount: this.entity.children_pi.length,
825
+ currentContent: this.loadedComponents
826
+ };
827
+ for (const component of this.scope.components) {
828
+ let prompt;
829
+ if (this.mode === "ai-prompt") {
830
+ const componentPrompt = this.prompts[component];
831
+ const generalPrompt = this.prompts["general"];
832
+ const combined = PromptBuilder.buildCombinedPrompt(generalPrompt, componentPrompt, component);
833
+ prompt = PromptBuilder.buildAIPrompt(
834
+ combined,
835
+ component,
836
+ entityContext,
837
+ this.loadedComponents[`${component}.json`] || this.loadedComponents[`${component}.md`]
838
+ );
839
+ } else {
840
+ const diffs = this.getDiff();
841
+ const userInstructions = this.prompts["general"] || this.prompts[component];
842
+ prompt = PromptBuilder.buildEditReviewPrompt(diffs, this.corrections, component, userInstructions);
843
+ }
844
+ if (this.scope.cascade) {
845
+ prompt = PromptBuilder.buildCascadePrompt(prompt, {
846
+ path: [this.entity.pi, this.entity.parent_pi || "root"].filter(Boolean),
847
+ depth: 0,
848
+ stopAtPi: this.scope.stopAtPi
849
+ });
850
+ }
851
+ result[component] = prompt;
852
+ }
853
+ return result;
854
+ }
855
+ /**
856
+ * Get a summary of pending changes
857
+ */
858
+ getChangeSummary() {
859
+ const diffs = this.getDiff();
860
+ const hasManualEdits = diffs.some((d) => d.hasChanges);
861
+ return {
862
+ mode: this.mode,
863
+ hasManualEdits,
864
+ editedComponents: Object.keys(this.editedContent),
865
+ corrections: [...this.corrections],
866
+ prompts: { ...this.prompts },
867
+ scope: { ...this.scope },
868
+ willRegenerate: [...this.scope.components],
869
+ willCascade: this.scope.cascade,
870
+ willSave: hasManualEdits,
871
+ willReprocess: this.scope.components.length > 0
872
+ };
873
+ }
874
+ // ===========================================================================
875
+ // Execution
876
+ // ===========================================================================
877
+ /**
878
+ * Submit changes (saves first if manual edits, then reprocesses)
879
+ */
880
+ async submit(note) {
881
+ if (this.submitting) {
882
+ throw new ValidationError("Submit already in progress");
883
+ }
884
+ if (!this.entity) {
885
+ throw new ValidationError("Session not loaded. Call load() first.");
886
+ }
887
+ this.submitting = true;
888
+ this.result = {};
889
+ try {
890
+ const diffs = this.getDiff();
891
+ const hasManualEdits = diffs.some((d) => d.hasChanges);
892
+ if (hasManualEdits) {
893
+ const componentUpdates = {};
894
+ for (const [name, content] of Object.entries(this.editedContent)) {
895
+ const original = this.loadedComponents[name] || "";
896
+ if (DiffEngine.hasSignificantChanges(original, content)) {
897
+ const cid = await this.client.uploadContent(content, name);
898
+ componentUpdates[name] = cid;
899
+ }
900
+ }
901
+ const version = await this.client.updateEntity(this.pi, {
902
+ expect_tip: this.entity.manifest_cid,
903
+ components: componentUpdates,
904
+ note
905
+ });
906
+ this.result.saved = {
907
+ pi: version.pi,
908
+ newVersion: version.ver,
909
+ newTip: version.tip
910
+ };
911
+ this.entity.manifest_cid = version.tip;
912
+ this.entity.ver = version.ver;
913
+ }
914
+ if (this.scope.components.length > 0) {
915
+ const customPrompts = this.buildCustomPrompts();
916
+ const reprocessResult = await this.client.reprocess({
917
+ pi: this.pi,
918
+ phases: this.scope.components,
919
+ cascade: this.scope.cascade,
920
+ options: {
921
+ stop_at_pi: this.scope.stopAtPi,
922
+ custom_prompts: customPrompts,
923
+ custom_note: note
924
+ }
925
+ });
926
+ this.result.reprocess = reprocessResult;
927
+ this.statusUrl = reprocessResult.status_url;
928
+ }
929
+ return this.result;
930
+ } finally {
931
+ this.submitting = false;
932
+ }
933
+ }
934
+ /**
935
+ * Wait for reprocessing to complete
936
+ */
937
+ async waitForCompletion(options) {
938
+ const opts = { ...DEFAULT_POLL_OPTIONS, ...options };
939
+ if (!this.statusUrl) {
940
+ return {
941
+ phase: "complete",
942
+ saveComplete: true
943
+ };
944
+ }
945
+ const startTime = Date.now();
946
+ let isFirstPoll = true;
947
+ while (true) {
948
+ const status = await this.client.getReprocessStatus(this.statusUrl, isFirstPoll);
949
+ isFirstPoll = false;
950
+ const editStatus = {
951
+ phase: status.status === "DONE" ? "complete" : status.status === "ERROR" ? "error" : "reprocessing",
952
+ saveComplete: true,
953
+ reprocessStatus: status,
954
+ error: status.error
955
+ };
956
+ if (opts.onProgress) {
957
+ opts.onProgress(editStatus);
958
+ }
959
+ if (status.status === "DONE" || status.status === "ERROR") {
960
+ return editStatus;
961
+ }
962
+ if (Date.now() - startTime > opts.timeoutMs) {
963
+ return {
964
+ phase: "error",
965
+ saveComplete: true,
966
+ reprocessStatus: status,
967
+ error: "Timeout waiting for reprocessing to complete"
968
+ };
969
+ }
970
+ await new Promise((resolve) => setTimeout(resolve, opts.intervalMs));
971
+ }
972
+ }
973
+ /**
974
+ * Get current status without waiting
975
+ */
976
+ async getStatus() {
977
+ if (!this.statusUrl) {
978
+ return {
979
+ phase: this.result?.saved ? "complete" : "idle",
980
+ saveComplete: !!this.result?.saved
981
+ };
982
+ }
983
+ const status = await this.client.getReprocessStatus(this.statusUrl);
984
+ return {
985
+ phase: status.status === "DONE" ? "complete" : status.status === "ERROR" ? "error" : "reprocessing",
986
+ saveComplete: true,
987
+ reprocessStatus: status,
988
+ error: status.error
989
+ };
990
+ }
991
+ // ===========================================================================
992
+ // Private Helpers
993
+ // ===========================================================================
994
+ buildCustomPrompts() {
995
+ const custom = {};
996
+ if (this.mode === "ai-prompt") {
997
+ if (this.prompts["general"]) custom.general = this.prompts["general"];
998
+ if (this.prompts["pinax"]) custom.pinax = this.prompts["pinax"];
999
+ if (this.prompts["description"]) custom.description = this.prompts["description"];
1000
+ if (this.prompts["cheimarros"]) custom.cheimarros = this.prompts["cheimarros"];
1001
+ } else {
1002
+ const diffs = this.getDiff();
1003
+ const diffContext = DiffEngine.formatComponentDiffsForPrompt(diffs);
1004
+ const correctionContext = PromptBuilder.buildCorrectionPrompt(this.corrections);
1005
+ const basePrompt = [diffContext, correctionContext, this.prompts["general"]].filter(Boolean).join("\n\n");
1006
+ if (basePrompt) {
1007
+ custom.general = basePrompt;
1008
+ }
1009
+ if (this.prompts["pinax"]) custom.pinax = this.prompts["pinax"];
1010
+ if (this.prompts["description"]) custom.description = this.prompts["description"];
1011
+ if (this.prompts["cheimarros"]) custom.cheimarros = this.prompts["cheimarros"];
1012
+ }
1013
+ return custom;
1014
+ }
1015
+ };
1016
+ // Annotate the CommonJS export names for ESM import in node:
1017
+ 0 && (module.exports = {
1018
+ CASConflictError,
1019
+ DiffEngine,
1020
+ EditClient,
1021
+ EditError,
1022
+ EditSession,
1023
+ EntityNotFoundError,
1024
+ PermissionError,
1025
+ PromptBuilder,
1026
+ ReprocessError,
1027
+ ValidationError
1028
+ });
1029
+ //# sourceMappingURL=index.cjs.map