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