@fluidframework/ai-collab 2.40.0-336023 → 2.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +156 -0
- package/api-report/ai-collab.alpha.api.md +88 -0
- package/dist/aiCollabApi.d.ts +9 -0
- package/dist/aiCollabApi.d.ts.map +1 -1
- package/dist/aiCollabApi.js.map +1 -1
- package/dist/alpha.d.ts +12 -0
- package/dist/diffTypes.d.ts +200 -0
- package/dist/diffTypes.d.ts.map +1 -0
- package/dist/diffTypes.js +7 -0
- package/dist/diffTypes.js.map +1 -0
- package/dist/explicit-strategy/agentEditReducer.d.ts +25 -3
- package/dist/explicit-strategy/agentEditReducer.d.ts.map +1 -1
- package/dist/explicit-strategy/agentEditReducer.js +239 -15
- package/dist/explicit-strategy/agentEditReducer.js.map +1 -1
- package/dist/explicit-strategy/index.d.ts +3 -0
- package/dist/explicit-strategy/index.d.ts.map +1 -1
- package/dist/explicit-strategy/index.js +6 -2
- package/dist/explicit-strategy/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/lib/aiCollabApi.d.ts +9 -0
- package/lib/aiCollabApi.d.ts.map +1 -1
- package/lib/aiCollabApi.js.map +1 -1
- package/lib/alpha.d.ts +12 -0
- package/lib/diffTypes.d.ts +200 -0
- package/lib/diffTypes.d.ts.map +1 -0
- package/lib/diffTypes.js +6 -0
- package/lib/diffTypes.js.map +1 -0
- package/lib/explicit-strategy/agentEditReducer.d.ts +25 -3
- package/lib/explicit-strategy/agentEditReducer.d.ts.map +1 -1
- package/lib/explicit-strategy/agentEditReducer.js +239 -18
- package/lib/explicit-strategy/agentEditReducer.js.map +1 -1
- package/lib/explicit-strategy/index.d.ts +3 -0
- package/lib/explicit-strategy/index.d.ts.map +1 -1
- package/lib/explicit-strategy/index.js +6 -2
- package/lib/explicit-strategy/index.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/package.json +9 -9
- package/src/aiCollabApi.ts +10 -0
- package/src/diffTypes.ts +211 -0
- package/src/explicit-strategy/agentEditReducer.ts +296 -19
- package/src/explicit-strategy/index.ts +10 -2
- package/src/index.ts +15 -0
|
@@ -23,6 +23,18 @@ import {
|
|
|
23
23
|
} from "@fluidframework/tree/internal";
|
|
24
24
|
import { closest } from "fastest-levenshtein";
|
|
25
25
|
|
|
26
|
+
import type {
|
|
27
|
+
ArrayRangeRemoveDiff,
|
|
28
|
+
ArraySingleRemoveDiff,
|
|
29
|
+
InsertDiff,
|
|
30
|
+
ModifyDiff,
|
|
31
|
+
MoveRangeDiff,
|
|
32
|
+
MoveSingleDiff,
|
|
33
|
+
NodePath,
|
|
34
|
+
RemoveNodeDiff,
|
|
35
|
+
Diff,
|
|
36
|
+
} from "../diffTypes.js";
|
|
37
|
+
|
|
26
38
|
import {
|
|
27
39
|
type TreeEdit,
|
|
28
40
|
type ObjectTarget,
|
|
@@ -34,6 +46,9 @@ import {
|
|
|
34
46
|
type TreeEditValue,
|
|
35
47
|
typeField,
|
|
36
48
|
type Modify,
|
|
49
|
+
type Remove,
|
|
50
|
+
type Move,
|
|
51
|
+
objectIdKey,
|
|
37
52
|
} from "./agentEditTypes.js";
|
|
38
53
|
import type { IdGenerator } from "./idGenerator.js";
|
|
39
54
|
import type { JsonValue } from "./jsonTypes.js";
|
|
@@ -63,7 +78,10 @@ function populateDefaults(
|
|
|
63
78
|
}
|
|
64
79
|
}
|
|
65
80
|
|
|
66
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Gets the schema identifier of the given content, including primitive values.
|
|
83
|
+
*/
|
|
84
|
+
export function getSchemaIdentifier(content: TreeEditValue): string {
|
|
67
85
|
switch (typeof content) {
|
|
68
86
|
case "boolean": {
|
|
69
87
|
return SchemaFactory.boolean.identifier;
|
|
@@ -84,6 +102,7 @@ function getSchemaIdentifier(content: TreeEditValue): string | undefined {
|
|
|
84
102
|
if (isFluidHandle(content)) {
|
|
85
103
|
return SchemaFactory.handle.identifier;
|
|
86
104
|
}
|
|
105
|
+
|
|
87
106
|
return content[typeField];
|
|
88
107
|
}
|
|
89
108
|
default: {
|
|
@@ -92,7 +111,10 @@ function getSchemaIdentifier(content: TreeEditValue): string | undefined {
|
|
|
92
111
|
}
|
|
93
112
|
}
|
|
94
113
|
|
|
95
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Converts a tree node from a {@link TreeEdit} to a {@link TreeEditObject} with the proper object IDs.
|
|
116
|
+
*/
|
|
117
|
+
export function contentWithIds(content: TreeNode, idGenerator: IdGenerator): TreeEditObject {
|
|
96
118
|
return JSON.parse(toDecoratedJson(idGenerator, content)) as TreeEditObject;
|
|
97
119
|
}
|
|
98
120
|
|
|
@@ -104,7 +126,7 @@ export function applyAgentEdit(
|
|
|
104
126
|
idGenerator: IdGenerator,
|
|
105
127
|
definitionMap: ReadonlyMap<string, SimpleNodeSchema>,
|
|
106
128
|
validator?: (edit: TreeNode) => void,
|
|
107
|
-
): TreeEdit {
|
|
129
|
+
): { edit: TreeEdit; diff: Diff } {
|
|
108
130
|
assertObjectIdsExist(treeEdit, idGenerator);
|
|
109
131
|
switch (treeEdit.type) {
|
|
110
132
|
case "insert": {
|
|
@@ -125,10 +147,14 @@ export function applyAgentEdit(
|
|
|
125
147
|
const simpleNodeSchema = allowedType as unknown as new (dummy: unknown) => TreeNode;
|
|
126
148
|
const insertNode = new simpleNodeSchema(treeEdit.content);
|
|
127
149
|
validator?.(insertNode);
|
|
150
|
+
|
|
128
151
|
array.insertAt(index, insertNode as unknown as IterableTreeArrayContent<never>);
|
|
129
152
|
return {
|
|
130
|
-
|
|
131
|
-
|
|
153
|
+
edit: {
|
|
154
|
+
...treeEdit,
|
|
155
|
+
content: contentWithIds(insertNode, idGenerator),
|
|
156
|
+
},
|
|
157
|
+
diff: createInsertDiff(insertNode, treeEdit.explanation, idGenerator),
|
|
132
158
|
};
|
|
133
159
|
}
|
|
134
160
|
}
|
|
@@ -136,6 +162,7 @@ export function applyAgentEdit(
|
|
|
136
162
|
}
|
|
137
163
|
case "remove": {
|
|
138
164
|
const source = treeEdit.source;
|
|
165
|
+
let diff: RemoveNodeDiff | ArraySingleRemoveDiff | ArrayRangeRemoveDiff;
|
|
139
166
|
if (isObjectTarget(source)) {
|
|
140
167
|
const node = getNodeFromTarget(source, idGenerator);
|
|
141
168
|
const parentNode = Tree.parent(node);
|
|
@@ -146,7 +173,9 @@ export function applyAgentEdit(
|
|
|
146
173
|
);
|
|
147
174
|
} else if (Tree.schema(parentNode).kind === NodeKind.Array) {
|
|
148
175
|
const nodeIndex = Tree.key(node) as number;
|
|
149
|
-
|
|
176
|
+
const parentArrayNode = parentNode as TreeArrayNode;
|
|
177
|
+
diff = createRemoveDiff(treeEdit, idGenerator);
|
|
178
|
+
parentArrayNode.removeAt(nodeIndex);
|
|
150
179
|
} else {
|
|
151
180
|
const fieldKey = Tree.key(node);
|
|
152
181
|
const parentSchema = Tree.schema(parentNode);
|
|
@@ -154,6 +183,7 @@ export function applyAgentEdit(
|
|
|
154
183
|
(parentSchema.info as Record<string, ImplicitFieldSchema>)[fieldKey] ??
|
|
155
184
|
fail("Expected field schema");
|
|
156
185
|
if (fieldSchema instanceof FieldSchema && fieldSchema.kind === FieldKind.Optional) {
|
|
186
|
+
diff = createRemoveDiff(treeEdit, idGenerator);
|
|
157
187
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
158
188
|
(parentNode as any)[fieldKey] = undefined;
|
|
159
189
|
} else {
|
|
@@ -164,9 +194,13 @@ export function applyAgentEdit(
|
|
|
164
194
|
}
|
|
165
195
|
} else if (isRange(source)) {
|
|
166
196
|
const { array, startIndex, endIndex } = getRangeInfo(source, idGenerator);
|
|
197
|
+
diff = createRemoveDiff(treeEdit, idGenerator);
|
|
167
198
|
array.removeRange(startIndex, endIndex);
|
|
199
|
+
} else {
|
|
200
|
+
throw new UsageError("Invalid source for remove edit");
|
|
168
201
|
}
|
|
169
|
-
|
|
202
|
+
|
|
203
|
+
return { edit: treeEdit, diff };
|
|
170
204
|
}
|
|
171
205
|
case "modify": {
|
|
172
206
|
const node = getNodeFromTarget(treeEdit.target, idGenerator);
|
|
@@ -187,11 +221,11 @@ export function applyAgentEdit(
|
|
|
187
221
|
}
|
|
188
222
|
|
|
189
223
|
const modification = treeEdit.modification;
|
|
190
|
-
|
|
191
224
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
192
225
|
const schemaIdentifier = (modification as any)[typeField];
|
|
193
226
|
|
|
194
227
|
let insertedObject: TreeNode | undefined;
|
|
228
|
+
const diff = createModifyDiff(treeEdit, idGenerator);
|
|
195
229
|
// if fieldSchema is a LeafnodeSchema, we can check that it's a valid type and set the field.
|
|
196
230
|
if (isPrimitive(modification)) {
|
|
197
231
|
try {
|
|
@@ -224,6 +258,7 @@ export function applyAgentEdit(
|
|
|
224
258
|
populateDefaults(modification, definitionMap);
|
|
225
259
|
const constructedModification = new simpleSchema(modification);
|
|
226
260
|
validator?.(constructedModification);
|
|
261
|
+
|
|
227
262
|
insertedObject = constructedModification;
|
|
228
263
|
|
|
229
264
|
if (Array.isArray(modification)) {
|
|
@@ -235,12 +270,9 @@ export function applyAgentEdit(
|
|
|
235
270
|
0xa76 /* the modification must be an array node */,
|
|
236
271
|
);
|
|
237
272
|
field.removeRange(0);
|
|
238
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
239
|
-
(node as any)[treeEdit.field] = constructedModification;
|
|
240
|
-
} else {
|
|
241
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
242
|
-
(node as any)[treeEdit.field] = constructedModification;
|
|
243
273
|
}
|
|
274
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
275
|
+
(node as any)[treeEdit.field] = constructedModification;
|
|
244
276
|
}
|
|
245
277
|
// If the fieldSchema is of type FieldSchema, we can check its allowed types and set the field.
|
|
246
278
|
else if (fieldSchema instanceof FieldSchema) {
|
|
@@ -266,11 +298,15 @@ export function applyAgentEdit(
|
|
|
266
298
|
}
|
|
267
299
|
}
|
|
268
300
|
}
|
|
301
|
+
|
|
269
302
|
return insertedObject === undefined
|
|
270
|
-
? treeEdit
|
|
303
|
+
? { edit: treeEdit, diff }
|
|
271
304
|
: {
|
|
272
|
-
|
|
273
|
-
|
|
305
|
+
edit: {
|
|
306
|
+
...treeEdit,
|
|
307
|
+
modification: contentWithIds(insertedObject, idGenerator),
|
|
308
|
+
},
|
|
309
|
+
diff,
|
|
274
310
|
};
|
|
275
311
|
}
|
|
276
312
|
case "move": {
|
|
@@ -281,7 +317,7 @@ export function applyAgentEdit(
|
|
|
281
317
|
destination,
|
|
282
318
|
idGenerator,
|
|
283
319
|
);
|
|
284
|
-
|
|
320
|
+
const diff: MoveSingleDiff | MoveRangeDiff = createMoveDiff(treeEdit, idGenerator);
|
|
285
321
|
if (isObjectTarget(source)) {
|
|
286
322
|
const sourceNode = getNodeFromTarget(source, idGenerator);
|
|
287
323
|
const sourceIndex = Tree.key(sourceNode) as number;
|
|
@@ -329,8 +365,10 @@ export function applyAgentEdit(
|
|
|
329
365
|
sourceEndIndex,
|
|
330
366
|
array,
|
|
331
367
|
);
|
|
368
|
+
} else {
|
|
369
|
+
throw new Error("Invalid source for move edit");
|
|
332
370
|
}
|
|
333
|
-
return treeEdit;
|
|
371
|
+
return { edit: treeEdit, diff };
|
|
334
372
|
}
|
|
335
373
|
default: {
|
|
336
374
|
fail("invalid tree edit");
|
|
@@ -408,7 +446,10 @@ interface RangeInfo {
|
|
|
408
446
|
endIndex: number;
|
|
409
447
|
}
|
|
410
448
|
|
|
411
|
-
|
|
449
|
+
/**
|
|
450
|
+
* Gets information about the range of nodes being targeted by an {@link Range}
|
|
451
|
+
*/
|
|
452
|
+
export function getRangeInfo(range: Range, idGenerator: IdGenerator): RangeInfo {
|
|
412
453
|
const { array: arrayFrom, index: startIndex } = getPlaceInfo(range.from, idGenerator);
|
|
413
454
|
const { array: arrayTo, index: endIndex } = getPlaceInfo(range.to, idGenerator);
|
|
414
455
|
|
|
@@ -552,6 +593,242 @@ function assertObjectIdsExist(treeEdit: TreeEdit, idGenerator: IdGenerator): voi
|
|
|
552
593
|
}
|
|
553
594
|
}
|
|
554
595
|
|
|
596
|
+
const createNodePathRecursive = (
|
|
597
|
+
node: TreeNode | undefined,
|
|
598
|
+
idGenerator: IdGenerator,
|
|
599
|
+
currentPath: NodePath,
|
|
600
|
+
): NodePath => {
|
|
601
|
+
if (node === undefined) {
|
|
602
|
+
return currentPath;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
currentPath.push({
|
|
606
|
+
shortId: Tree.shortId(node),
|
|
607
|
+
schemaIdentifier: Tree.schema(node).identifier,
|
|
608
|
+
parentField: Tree.key(node),
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
const parentNode = Tree.parent(node);
|
|
612
|
+
return createNodePathRecursive(parentNode, idGenerator, currentPath);
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Creates a diff for an Insert TreeEdit.
|
|
617
|
+
*
|
|
618
|
+
* @remarks
|
|
619
|
+
* This function is only invoked within the "insert" case block.
|
|
620
|
+
*
|
|
621
|
+
* This must only be called AFTER an insertion is made.
|
|
622
|
+
* It generates the insert diff after the node has been successfully inserted, as the node's index may
|
|
623
|
+
* be required to support undoing the insert operation, and we don't know that index until the insert has been made.
|
|
624
|
+
*/
|
|
625
|
+
function createInsertDiff(
|
|
626
|
+
newlyInsertedNode: TreeNode,
|
|
627
|
+
aiExplanation: string,
|
|
628
|
+
idGenerator: IdGenerator,
|
|
629
|
+
): InsertDiff {
|
|
630
|
+
return {
|
|
631
|
+
type: "insert",
|
|
632
|
+
nodePath: createNodePathRecursive(newlyInsertedNode, idGenerator, []),
|
|
633
|
+
nodeContent: JSON.parse(JSON.stringify(newlyInsertedNode)),
|
|
634
|
+
aiExplanation,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Returns an object identical to the input except that the special 'objectIdKey' field (only intended for use by the LLM agent) is removed if present.
|
|
640
|
+
* @remarks The input object is not modified.
|
|
641
|
+
*/
|
|
642
|
+
function removeAgentObjectIdField(oldValue: unknown): unknown {
|
|
643
|
+
if (typeof oldValue === "object" && oldValue !== null && !Array.isArray(oldValue)) {
|
|
644
|
+
const { [objectIdKey]: _, ...rest } = oldValue as Record<string, unknown>;
|
|
645
|
+
return rest;
|
|
646
|
+
}
|
|
647
|
+
return oldValue;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Creates a diff for a Modify TreeEdit.
|
|
652
|
+
*
|
|
653
|
+
* @remarks
|
|
654
|
+
* This function must only be called BEFORE a modify edit is applied.
|
|
655
|
+
* For move operations, the diff is created before the node(s) have been successfully moved,
|
|
656
|
+
* since the original index is needed to restore the node(s) if the move operation need to undo.
|
|
657
|
+
*/
|
|
658
|
+
function createModifyDiff(treeEdit: Modify, idGenerator: IdGenerator): ModifyDiff {
|
|
659
|
+
const targetNode = getNodeFromTarget(treeEdit.target, idGenerator);
|
|
660
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
661
|
+
const targetNodeAtField: unknown = (targetNode as any)[treeEdit.field];
|
|
662
|
+
|
|
663
|
+
if (isPrimitive(targetNodeAtField)) {
|
|
664
|
+
return {
|
|
665
|
+
type: "modify",
|
|
666
|
+
nodePath: createNodePathRecursive(targetNode, idGenerator, [
|
|
667
|
+
{
|
|
668
|
+
shortId: undefined,
|
|
669
|
+
parentField: treeEdit.field,
|
|
670
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
671
|
+
schemaIdentifier: getSchemaIdentifier(treeEdit.modification)!,
|
|
672
|
+
},
|
|
673
|
+
]),
|
|
674
|
+
newValue: treeEdit.modification,
|
|
675
|
+
oldValue: targetNodeAtField,
|
|
676
|
+
aiExplanation: treeEdit.explanation,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return {
|
|
681
|
+
type: "modify",
|
|
682
|
+
nodePath: createNodePathRecursive(targetNodeAtField as TreeNode, idGenerator, []),
|
|
683
|
+
newValue: treeEdit.modification,
|
|
684
|
+
oldValue: removeAgentObjectIdField(JSON.parse(JSON.stringify(targetNodeAtField))),
|
|
685
|
+
aiExplanation: treeEdit.explanation,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Creates a diff for a Remove TreeEdit.
|
|
691
|
+
*
|
|
692
|
+
* @remarks
|
|
693
|
+
* This function must only be called BEFORE a remove edit is applied.
|
|
694
|
+
* It generates the remove diff before the node has been successfully removed, as the node's index may
|
|
695
|
+
* be required to support undoing the remove operation, and we don't know that index until the remove has been made.
|
|
696
|
+
*/
|
|
697
|
+
function createRemoveDiff(
|
|
698
|
+
treeEdit: Remove,
|
|
699
|
+
idGenerator: IdGenerator,
|
|
700
|
+
): RemoveNodeDiff | ArraySingleRemoveDiff | ArrayRangeRemoveDiff {
|
|
701
|
+
const source = treeEdit.source;
|
|
702
|
+
if (isObjectTarget(source)) {
|
|
703
|
+
const node = getNodeFromTarget(source, idGenerator);
|
|
704
|
+
const parentNode = Tree.parent(node);
|
|
705
|
+
if (parentNode === undefined) {
|
|
706
|
+
throw new Error("Unexpectedly received a root node as the target of a remove edit");
|
|
707
|
+
} else if (Tree.schema(parentNode).kind === NodeKind.Array) {
|
|
708
|
+
const nodeIndex = Tree.key(node) as number;
|
|
709
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
710
|
+
const targetRemovedNode = (parentNode as TreeArrayNode).at(nodeIndex)!;
|
|
711
|
+
|
|
712
|
+
if (isPrimitive(targetRemovedNode)) {
|
|
713
|
+
// Note that this cause should not be possible, still putting the error here in case things change so that this function is updated properly
|
|
714
|
+
throw new Error(
|
|
715
|
+
"Unexpectedly recieved a primitive node as the target of a remove edit",
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return {
|
|
720
|
+
type: "remove",
|
|
721
|
+
removalType: "remove-array-single",
|
|
722
|
+
nodePath: createNodePathRecursive(targetRemovedNode as TreeNode, idGenerator, []),
|
|
723
|
+
aiExplanation: treeEdit.explanation,
|
|
724
|
+
nodeContent: removeAgentObjectIdField(JSON.parse(JSON.stringify(targetRemovedNode))),
|
|
725
|
+
};
|
|
726
|
+
} else {
|
|
727
|
+
const fieldKey = Tree.key(node);
|
|
728
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
729
|
+
const targetNodeAtField: unknown = (parentNode as any)[fieldKey];
|
|
730
|
+
|
|
731
|
+
if (isPrimitive(targetNodeAtField)) {
|
|
732
|
+
// Note that this cause should not be possible, still putting the error here in case things change so that this function is updated properly
|
|
733
|
+
throw new Error(
|
|
734
|
+
"Unexpectedly recieved a primitive node as the target of a remove field edit",
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return {
|
|
739
|
+
type: "remove",
|
|
740
|
+
removalType: "remove-node",
|
|
741
|
+
nodePath: createNodePathRecursive(targetNodeAtField as TreeNode, idGenerator, []),
|
|
742
|
+
aiExplanation: treeEdit.explanation,
|
|
743
|
+
nodeContent: removeAgentObjectIdField(JSON.parse(JSON.stringify(targetNodeAtField))),
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
} else if (isRange(source)) {
|
|
747
|
+
const { array, startIndex, endIndex } = getRangeInfo(source, idGenerator);
|
|
748
|
+
const removedNodePaths: NodePath[] = [];
|
|
749
|
+
const removedNodes: TreeNode[] = [];
|
|
750
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
751
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
752
|
+
const nodeToRemove = array.at(i)!;
|
|
753
|
+
if (!isPrimitive(nodeToRemove)) {
|
|
754
|
+
removedNodePaths.push(
|
|
755
|
+
createNodePathRecursive(nodeToRemove as TreeNode, idGenerator, []),
|
|
756
|
+
);
|
|
757
|
+
removedNodes.push(nodeToRemove as TreeNode);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return {
|
|
761
|
+
type: "remove",
|
|
762
|
+
removalType: "remove-array-range",
|
|
763
|
+
nodePaths: removedNodePaths,
|
|
764
|
+
aiExplanation: treeEdit.explanation,
|
|
765
|
+
nodeContents: removedNodes.map((node) =>
|
|
766
|
+
removeAgentObjectIdField(JSON.parse(JSON.stringify(node))),
|
|
767
|
+
),
|
|
768
|
+
};
|
|
769
|
+
} else {
|
|
770
|
+
throw new Error("Invalid source encountered when trying to create diff for remove edit");
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Creates a diff for a Move TreeEdit.
|
|
776
|
+
*
|
|
777
|
+
* @remarks
|
|
778
|
+
* This function must only be called BEFORE a move edit is applied.
|
|
779
|
+
* For move operations, the diff is created before the node(s) have been successfully moved,
|
|
780
|
+
* since the original index is needed to restore the node(s) if the move operation need to undo.
|
|
781
|
+
*/
|
|
782
|
+
function createMoveDiff(
|
|
783
|
+
treeEdit: Move,
|
|
784
|
+
idGenerator: IdGenerator,
|
|
785
|
+
): MoveSingleDiff | MoveRangeDiff {
|
|
786
|
+
const source = treeEdit.source;
|
|
787
|
+
const destination = treeEdit.destination;
|
|
788
|
+
const { array: destinationArrayNode } = getPlaceInfo(destination, idGenerator);
|
|
789
|
+
|
|
790
|
+
if (isObjectTarget(source)) {
|
|
791
|
+
const node = getNodeFromTarget(source, idGenerator);
|
|
792
|
+
return {
|
|
793
|
+
type: "move",
|
|
794
|
+
moveType: "move-single",
|
|
795
|
+
sourceNodePath: createNodePathRecursive(node, idGenerator, []),
|
|
796
|
+
destinationNodePath: createNodePathRecursive(destinationArrayNode, idGenerator, []),
|
|
797
|
+
aiExplanation: treeEdit.explanation,
|
|
798
|
+
nodeContent: removeAgentObjectIdField(JSON.parse(JSON.stringify(node))),
|
|
799
|
+
};
|
|
800
|
+
} else if (isRange(source)) {
|
|
801
|
+
const {
|
|
802
|
+
array,
|
|
803
|
+
startIndex: sourceStartIndex,
|
|
804
|
+
endIndex: sourceEndIndex,
|
|
805
|
+
} = getRangeInfo(source, idGenerator);
|
|
806
|
+
|
|
807
|
+
const movedNodePaths: NodePath[] = [];
|
|
808
|
+
const movedNodes: TreeNode[] = [];
|
|
809
|
+
for (let i = sourceStartIndex; i < sourceEndIndex; i++) {
|
|
810
|
+
const nodeToMove = array.at(i);
|
|
811
|
+
if (!isPrimitive(nodeToMove)) {
|
|
812
|
+
movedNodePaths.push(createNodePathRecursive(nodeToMove as TreeNode, idGenerator, []));
|
|
813
|
+
movedNodes.push(nodeToMove as TreeNode);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
return {
|
|
818
|
+
type: "move",
|
|
819
|
+
moveType: "move-range",
|
|
820
|
+
sourceNodePaths: movedNodePaths,
|
|
821
|
+
destinationNodePath: createNodePathRecursive(destinationArrayNode, idGenerator, []),
|
|
822
|
+
aiExplanation: treeEdit.explanation,
|
|
823
|
+
nodeContents: movedNodes.map((node) =>
|
|
824
|
+
removeAgentObjectIdField(JSON.parse(JSON.stringify(node))),
|
|
825
|
+
),
|
|
826
|
+
};
|
|
827
|
+
} else {
|
|
828
|
+
throw new Error("Invalid source for move edit");
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
555
832
|
interface SchemaInfo {
|
|
556
833
|
treeNodeSchema: TreeNodeSchema;
|
|
557
834
|
simpleNodeSchema: new (dummy: unknown) => TreeNode;
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
TokenLimits,
|
|
26
26
|
TokenUsage,
|
|
27
27
|
} from "../aiCollabApi.js";
|
|
28
|
+
import type { Diff } from "../diffTypes.js";
|
|
28
29
|
|
|
29
30
|
import { applyAgentEdit } from "./agentEditReducer.js";
|
|
30
31
|
import type { EditWrapper, TreeEdit } from "./agentEditTypes.js";
|
|
@@ -100,6 +101,7 @@ export interface GenerateTreeEditsOptions {
|
|
|
100
101
|
interface GenerateTreeEditsSuccessResponse {
|
|
101
102
|
status: "success";
|
|
102
103
|
tokensUsed: TokenUsage;
|
|
104
|
+
readonly diffs: readonly Diff[];
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
interface GenerateTreeEditsErrorResponse {
|
|
@@ -111,6 +113,7 @@ interface GenerateTreeEditsErrorResponse {
|
|
|
111
113
|
| "aborted"
|
|
112
114
|
| "unexpectedError";
|
|
113
115
|
tokensUsed: TokenUsage;
|
|
116
|
+
readonly diffs: readonly Diff[];
|
|
114
117
|
}
|
|
115
118
|
|
|
116
119
|
/**
|
|
@@ -128,6 +131,8 @@ export async function generateTreeEdits(
|
|
|
128
131
|
): Promise<GenerateTreeEditsSuccessResponse | GenerateTreeEditsErrorResponse> {
|
|
129
132
|
const idGenerator = new IdGenerator();
|
|
130
133
|
const editLog: EditLog = [];
|
|
134
|
+
const diffs: Diff[] = [];
|
|
135
|
+
|
|
131
136
|
let editCount = 0;
|
|
132
137
|
let sequentialErrorCount = 0;
|
|
133
138
|
|
|
@@ -165,8 +170,8 @@ export async function generateTreeEdits(
|
|
|
165
170
|
simpleSchema.definitions,
|
|
166
171
|
options.validator,
|
|
167
172
|
);
|
|
168
|
-
|
|
169
|
-
|
|
173
|
+
editLog.push({ edit: { ...result.edit } });
|
|
174
|
+
diffs.push(result.diff);
|
|
170
175
|
sequentialErrorCount = 0;
|
|
171
176
|
|
|
172
177
|
options.debugEventLogHandler?.({
|
|
@@ -201,6 +206,7 @@ export async function generateTreeEdits(
|
|
|
201
206
|
editCount > 0 && sequentialErrorCount < editCount ? "partial-failure" : "failure",
|
|
202
207
|
errorMessage: "unexpectedError",
|
|
203
208
|
tokensUsed,
|
|
209
|
+
diffs,
|
|
204
210
|
};
|
|
205
211
|
|
|
206
212
|
if (options.limiters?.abortController?.signal.aborted === true) {
|
|
@@ -250,6 +256,7 @@ export async function generateTreeEdits(
|
|
|
250
256
|
editCount > 0 && sequentialErrorCount < editCount ? "partial-failure" : "failure",
|
|
251
257
|
errorMessage: "tokenLimitExceeded",
|
|
252
258
|
tokensUsed,
|
|
259
|
+
diffs,
|
|
253
260
|
};
|
|
254
261
|
}
|
|
255
262
|
throw error;
|
|
@@ -266,6 +273,7 @@ export async function generateTreeEdits(
|
|
|
266
273
|
return {
|
|
267
274
|
status: "success",
|
|
268
275
|
tokensUsed,
|
|
276
|
+
diffs,
|
|
269
277
|
};
|
|
270
278
|
}
|
|
271
279
|
|
package/src/index.ts
CHANGED
|
@@ -56,4 +56,19 @@ export {
|
|
|
56
56
|
type EventFlowDebugEvent,
|
|
57
57
|
} from "./aiCollabApi.js";
|
|
58
58
|
|
|
59
|
+
export {
|
|
60
|
+
type DiffBase,
|
|
61
|
+
type Diff,
|
|
62
|
+
type NodePath,
|
|
63
|
+
type InsertDiff,
|
|
64
|
+
type ModifyDiff,
|
|
65
|
+
type RemoveDiff,
|
|
66
|
+
type RemoveNodeDiff,
|
|
67
|
+
type ArraySingleRemoveDiff,
|
|
68
|
+
type ArrayRangeRemoveDiff,
|
|
69
|
+
type MoveDiff,
|
|
70
|
+
type MoveSingleDiff,
|
|
71
|
+
type MoveRangeDiff,
|
|
72
|
+
} from "./diffTypes.js";
|
|
73
|
+
|
|
59
74
|
export { aiCollab } from "./aiCollab.js";
|