@fluidframework/tree-agent 2.70.0-360753 → 2.70.0-361092

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/tree-agent",
3
- "version": "2.70.0-360753",
3
+ "version": "2.70.0-361092",
4
4
  "description": "Experimental package to simplify integrating AI into Fluid-based applications",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -70,24 +70,24 @@
70
70
  },
71
71
  "dependencies": {
72
72
  "@anthropic-ai/sdk": "^0.39.0",
73
- "@fluidframework/core-utils": "2.70.0-360753",
74
- "@fluidframework/runtime-utils": "2.70.0-360753",
75
- "@fluidframework/telemetry-utils": "2.70.0-360753",
76
- "@fluidframework/tree": "2.70.0-360753",
73
+ "@fluidframework/core-utils": "2.70.0-361092",
74
+ "@fluidframework/runtime-utils": "2.70.0-361092",
75
+ "@fluidframework/telemetry-utils": "2.70.0-361092",
76
+ "@fluidframework/tree": "2.70.0-361092",
77
77
  "uuid": "^11.1.0",
78
78
  "zod": "^3.25.32"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@arethetypeswrong/cli": "^0.17.1",
82
82
  "@biomejs/biome": "~1.9.3",
83
- "@fluid-internal/mocha-test-setup": "2.70.0-360753",
83
+ "@fluid-internal/mocha-test-setup": "2.70.0-361092",
84
84
  "@fluid-tools/build-cli": "^0.58.3",
85
85
  "@fluidframework/build-common": "^2.0.3",
86
86
  "@fluidframework/build-tools": "^0.58.3",
87
- "@fluidframework/eslint-config-fluid": "^6.0.0",
88
- "@fluidframework/id-compressor": "2.70.0-360753",
89
- "@fluidframework/runtime-utils": "2.70.0-360753",
90
- "@fluidframework/test-runtime-utils": "2.70.0-360753",
87
+ "@fluidframework/eslint-config-fluid": "^6.1.0",
88
+ "@fluidframework/id-compressor": "2.70.0-361092",
89
+ "@fluidframework/runtime-utils": "2.70.0-361092",
90
+ "@fluidframework/test-runtime-utils": "2.70.0-361092",
91
91
  "@langchain/anthropic": "^0.3.24",
92
92
  "@langchain/core": "^0.3.78",
93
93
  "@langchain/google-genai": "^0.2.16",
@@ -100,8 +100,8 @@
100
100
  "concurrently": "^8.2.1",
101
101
  "copyfiles": "^2.4.1",
102
102
  "cross-env": "^7.0.3",
103
- "eslint": "~8.55.0",
104
- "eslint-config-prettier": "~9.0.0",
103
+ "eslint": "~8.57.1",
104
+ "eslint-config-prettier": "~10.1.8",
105
105
  "mocha": "^10.8.2",
106
106
  "mocha-multi-reporters": "^1.5.1",
107
107
  "prettier": "~3.0.3",
package/src/agent.ts CHANGED
@@ -24,6 +24,7 @@ import type {
24
24
  Logger,
25
25
  SynchronousEditor,
26
26
  AsynchronousEditor,
27
+ Context,
27
28
  } from "./api.js";
28
29
  import { getPrompt, stringifyTree } from "./prompt.js";
29
30
  import { Subtree } from "./subtree.js";
@@ -248,25 +249,25 @@ export const defaultEditor: AsynchronousEditor = async (context, code) => {
248
249
  };
249
250
 
250
251
  /**
251
- * Binds the given {@link SynchronousEditor | editor} to the given view or tree.
252
+ * Binds the given {@link AsynchronousEditor | editor} to the given view or tree.
252
253
  * @returns A function that takes a string of JavaScript code and executes it on the given view or tree using the given editor function.
253
254
  * @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.
254
255
  * @alpha
255
256
  */
256
257
  export function bindEditorImpl<TSchema extends ImplicitFieldSchema>(
257
258
  tree: TreeView<TSchema> | (ReadableField<TSchema> & TreeNode),
258
- editor: SynchronousEditor,
259
- ): (code: string) => void;
259
+ editor: AsynchronousEditor,
260
+ ): (code: string) => Promise<void>;
260
261
  /**
261
- * Binds the given {@link AsynchronousEditor | editor} to the given view or tree.
262
+ * Binds the given {@link SynchronousEditor | editor} to the given view or tree.
262
263
  * @returns A function that takes a string of JavaScript code and executes it on the given view or tree using the given editor function.
263
264
  * @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.
264
265
  * @alpha
265
266
  */
266
267
  export function bindEditorImpl<TSchema extends ImplicitFieldSchema>(
267
268
  tree: TreeView<TSchema> | (ReadableField<TSchema> & TreeNode),
268
- editor: AsynchronousEditor,
269
- ): (code: string) => Promise<void>;
269
+ editor: SynchronousEditor,
270
+ ): (code: string) => void;
270
271
  /**
271
272
  * Binds the given {@link SynchronousEditor | editor} or {@link AsynchronousEditor | editor} to the given view or tree.
272
273
  * @returns A function that takes a string of JavaScript code and executes it on the given view or tree using the given editor function.
@@ -296,9 +297,11 @@ function bindEditorToSubtree<TSchema extends ImplicitFieldSchema>(
296
297
  ): (code: string) => void | Promise<void> {
297
298
  // Stick the tree schema constructors on an object passed to the function so that the LLM can create new nodes.
298
299
  const create: Record<string, (input: FactoryContentObject) => TreeNode> = {};
300
+ const is: Record<string, <T extends TreeNode>(input: unknown) => input is T> = {};
299
301
  for (const schema of findNamedSchemas(tree.schema)) {
300
302
  const name = getFriendlyName(schema);
301
303
  create[name] = (input: FactoryContentObject) => constructTreeNode(schema, input);
304
+ is[name] = <T extends TreeNode>(input: unknown): input is T => Tree.is(input, schema);
302
305
  }
303
306
 
304
307
  const context = {
@@ -309,7 +312,10 @@ function bindEditorToSubtree<TSchema extends ImplicitFieldSchema>(
309
312
  tree.field = value;
310
313
  },
311
314
  create,
312
- };
315
+ is,
316
+ parent: (child: TreeNode): TreeNode | undefined => Tree.parent(child),
317
+ key: (child: TreeNode): string | number => Tree.key(child),
318
+ } satisfies Context<TSchema>;
313
319
 
314
320
  // eslint-disable-next-line @typescript-eslint/promise-function-async
315
321
  return (code: string) => executeEdit(context, code);
package/src/api.ts CHANGED
@@ -22,6 +22,71 @@ export interface Logger {
22
22
  log(message: string): void;
23
23
  }
24
24
 
25
+ /**
26
+ * The context object available to generated code when editing a tree.
27
+ * @remarks This object is provided to JavaScript code executed by the {@link SynchronousEditor | editor} as a variable named `context`.
28
+ * It contains the current state of the tree and utilities for creating and inspecting tree nodes.
29
+ * @alpha
30
+ */
31
+ export interface Context<TSchema extends ImplicitFieldSchema> {
32
+ /**
33
+ * The root of the tree that can be read or modified.
34
+ * @remarks
35
+ * You can read properties and navigate through the tree starting from this root.
36
+ * You can also assign a new value to this property to replace the entire tree, as long as the new value is one of the types allowed at the root.
37
+ *
38
+ * Example: Read the current root with `const currentRoot = context.root;`
39
+ *
40
+ * Example: Replace the entire root with `context.root = context.create.MyRootType({ });`
41
+ */
42
+ root: ReadableField<TSchema>;
43
+
44
+ /**
45
+ * A collection of builder functions for creating new tree nodes.
46
+ * @remarks
47
+ * Each property on this object is named after a type in the tree schema.
48
+ * Call the corresponding function to create a new node of that type.
49
+ * Always use these builder functions when creating new nodes rather than plain JavaScript objects.
50
+ *
51
+ * Example: Create a new Person node with `context.create.Person({ name: "Alice", age: 30 })`
52
+ *
53
+ * Example: Create a new Task node with `context.create.Task({ title: "Buy groceries", completed: false })`
54
+ */
55
+ create: Record<string, (input: FactoryContentObject) => TreeNode>;
56
+
57
+ /**
58
+ * A collection of type-checking functions for tree nodes.
59
+ * @remarks
60
+ * Each property on this object is named after a type in the tree schema.
61
+ * Call the corresponding function to check if a node is of that specific type.
62
+ * This is useful when working with nodes that could be one of multiple types.
63
+ *
64
+ * Example: Check if a node is a Person with `if (context.is.Person(node)) { console.log(node.name); }`
65
+ */
66
+ is: Record<string, <T extends TreeNode>(input: T) => input is T>;
67
+
68
+ /**
69
+ * Returns the parent node of the given child node.
70
+ * @param child - The node whose parent you want to find.
71
+ * @returns The parent node, or `undefined` if the node is the root or is not in the tree.
72
+ * @remarks
73
+ * Example: Get the parent with `const parent = context.parent(childNode);`
74
+ */
75
+ parent(child: TreeNode): TreeNode | undefined;
76
+
77
+ /**
78
+ * Returns the key or index of the given node within its parent.
79
+ * @param child - The node whose key you want to find.
80
+ * @returns A string key if the node is in an object or map, or a numeric index if the node is in an array.
81
+ * @remarks
82
+ * For a node in an object, this might return a string like "firstName".
83
+ * For a node in an array, this might return a number like 0, 1, 2, etc.
84
+ *
85
+ * Example: `const key = context.key(childNode);`
86
+ */
87
+ key(child: TreeNode): string | number;
88
+ }
89
+
25
90
  /**
26
91
  * A synchronous function that executes a string of JavaScript code to perform an edit within a {@link SharedTreeSemanticAgent}.
27
92
  * @param context - An object that must be provided to the generated code as a variable named "context" in its top-level scope.
package/src/prompt.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import { oob } from "@fluidframework/core-utils/internal";
6
7
  import { NodeKind, Tree, TreeNode } from "@fluidframework/tree";
7
8
  import type { ImplicitFieldSchema, TreeMapNode } from "@fluidframework/tree";
8
9
  import type { ReadableField } from "@fluidframework/tree/alpha";
@@ -34,10 +35,20 @@ export function getPrompt<TRoot extends ImplicitFieldSchema>(args: {
34
35
  const mapInterfaceName = "TreeMap";
35
36
  const simpleSchema = getSimpleSchema(schema);
36
37
  // Inspect the schema to determine what kinds of nodes are possible - this will affect how much information we need to include in the prompt.
38
+ const rootTypes = [...normalizeFieldSchema(schema).allowedTypeSet];
39
+ const rootTypeUnion = `${rootTypes.map((t) => getFriendlyName(t)).join(" | ")}`;
40
+ let nodeTypeUnion: string | undefined;
37
41
  let hasArrays = false;
38
42
  let hasMaps = false;
39
43
  let exampleObjectName: string | undefined;
40
44
  for (const [definition, nodeSchema] of simpleSchema.definitions) {
45
+ if (nodeSchema.kind !== NodeKind.Leaf) {
46
+ nodeTypeUnion =
47
+ nodeTypeUnion === undefined
48
+ ? unqualifySchema(definition)
49
+ : `${nodeTypeUnion} | ${unqualifySchema(definition)}`;
50
+ }
51
+
41
52
  switch (nodeSchema.kind) {
42
53
  case NodeKind.Array: {
43
54
  hasArrays = true;
@@ -68,24 +79,81 @@ export function getPrompt<TRoot extends ImplicitFieldSchema>(args: {
68
79
  const stringified = stringifyTree(field);
69
80
  const details: SchemaDetails = { hasHelperMethods: false };
70
81
  const typescriptSchemaTypes = getZodSchemaAsTypeScript(domainTypes, details);
71
- const helperMethodExplanation = details.hasHelperMethods
72
- ? `Manipulating the data using the APIs described below is allowed, but when possible ALWAYS prefer to use any application helper methods exposed on the schema TypeScript types if the goal can be accomplished that way.
73
- It will often not be possible to fully accomplish the goal using those helpers. When this is the case, mutate the objects as normal, taking into account the following guidance.`
74
- : "";
75
82
 
76
- const builderExplanation =
83
+ const create =
84
+ exampleObjectName === undefined
85
+ ? ""
86
+ : `\n /**
87
+ * A collection of builder functions for creating new tree nodes.
88
+ * @remarks
89
+ * Each property on this object is named after a type in the tree schema.
90
+ * Call the corresponding function to create a new node of that type.
91
+ * Always use these builder functions when creating new nodes rather than plain JavaScript objects.
92
+ *
93
+ * For example:
94
+ *
95
+ * \`\`\`javascript
96
+ * // This creates a new ${exampleObjectName} object:
97
+ * const ${communize(exampleObjectName)} = context.create.${exampleObjectName}({ ...properties });
98
+ * // Don't do this:
99
+ * // const ${communize(exampleObjectName)} = { ...properties };
100
+ * \`\`\`
101
+ */
102
+ create: Record<string, <T extends TreeData>(input: T) => T>;\n`;
103
+
104
+ const isDocs =
77
105
  exampleObjectName === undefined
78
106
  ? ""
79
- : `When constructing new objects, you should wrap them in the appropriate builder function rather than simply making a javascript object.
80
- The builders are available on the \`create\` property on the context object and are named according to the type that they create.
81
- For example:
107
+ : `\n /**
108
+ * A collection of type-guard functions for data in the tree.
109
+ * @remarks
110
+ * Each property on this object is named after a type in the tree schema.
111
+ * Call the corresponding function to check if a node is of that specific type.
112
+ * This is useful when working with nodes that could be one of multiple types.
113
+ *
114
+ * ${`Example: Check if a node is a ${exampleObjectName} with \`if (context.is.${exampleObjectName}(node)) {}\``}
115
+ */
116
+ is: Record<string, <T extends TreeData>(input: unknown) => input is T>;\n`;
82
117
 
83
- \`\`\`javascript
84
- // This creates a new ${exampleObjectName} object:
85
- const ${communize(exampleObjectName)} = context.create.${exampleObjectName}({ /* ...properties... */ });
86
- // Don't do this:
87
- // const ${communize(exampleObjectName)} = { /* ...properties... */ };
88
- \`\`\`\n\n`;
118
+ const context = `\`\`\`typescript
119
+ ${nodeTypeUnion === undefined ? "" : `type TreeData = ${nodeTypeUnion};\n\n`} /**
120
+ * An object available to generated code which provides read and write access to the tree as well as utilities for creating and inspecting data in the tree.
121
+ * @remarks This object is available as a variable named \`context\` in the scope of the generated JavaScript snippet.
122
+ */
123
+ interface Context<TSchema extends ImplicitFieldSchema> {
124
+ /**
125
+ * The root of the tree that can be read or mutated.
126
+ * @remarks
127
+ * You can read properties and navigate through the tree starting from this root.
128
+ * You can also assign a new value to this property to replace the entire tree, as long as the new value is one of the types allowed at the root.
129
+ *
130
+ * Example: Read the current root with \`const currentRoot = context.root;\`
131
+ *${rootTypes.length > 0 ? ` Example: Replace the entire root with \`context.root = context.create.${getFriendlyName(rootTypes[0] ?? oob())}({ });\`\n *` : ""}/
132
+ root: ReadableField<TSchema>;
133
+ ${create}
134
+ ${isDocs}
135
+ /**
136
+ * Returns the parent object/array/map of the given object/array/map, if there is one.
137
+ * @returns The parent node, or \`undefined\` if the node is the root or is not in the tree.
138
+ * @remarks
139
+ * Example: Get the parent with \`const parent = context.parent(child);\`
140
+ */
141
+ parent(child: TreeData): TreeData | undefined;
142
+
143
+ /**
144
+ * Returns the property key or index of the given object/array/map within its parent.
145
+ * @returns A string key if the child is in an object or map, or a numeric index if the child is in an array.
146
+ *
147
+ * Example: \`const key = context.key(child);\`
148
+ */
149
+ key(child: TreeData): string | number;
150
+ }
151
+ \`\`\``;
152
+
153
+ const helperMethodExplanation = details.hasHelperMethods
154
+ ? `Manipulating the data using the APIs described below is allowed, but when possible ALWAYS prefer to use any application helper methods exposed on the schema TypeScript types if the goal can be accomplished that way.
155
+ It will often not be possible to fully accomplish the goal using those helpers. When this is the case, mutate the objects as normal, taking into account the following guidance.`
156
+ : "";
89
157
 
90
158
  const reinsertionExplanation = `Once non-primitive data has been removed from the tree (e.g. replaced via assignment, or removed from an array), that data cannot be re-inserted into the tree.
91
159
  Instead, it must be deep cloned and recreated.
@@ -160,7 +228,6 @@ ${getTreeMapNodeDocumentation(mapInterfaceName)}
160
228
 
161
229
  `;
162
230
 
163
- const rootTypes = normalizeFieldSchema(schema).allowedTypeSet;
164
231
  const editing = `If the user asks you to edit the tree, you should author a snippet of JavaScript code to accomplish the user-specified goal, following the instructions for editing detailed below.
165
232
  You must use the "${editToolName}" tool to run the generated code.
166
233
  After editing the tree, review the latest state of the tree to see if it satisfies the user's request.
@@ -173,14 +240,18 @@ If the user asks you to edit the document, you will write a snippet of JavaScrip
173
240
  The snippet may be synchronous or asynchronous (i.e. it may \`await\` functions if necessary).
174
241
  The snippet has a \`context\` variable in its scope.
175
242
  This \`context\` variable holds the current state of the tree in the \`root\` property.
176
- You may mutate any part of the root tree as necessary, taking into account the caveats around${hasArrays ? ` arrays${hasMaps ? " and" : ""}` : ""}${hasMaps ? " maps" : ""} detailed below.
177
- You may also set the \`root\` property of the context to be an entirely new value as long as it is one of the types allowed at the root of the tree (\`${Array.from(rootTypes.values(), (t) => getFriendlyName(t)).join(" | ")}\`).
243
+ You may mutate any part of this tree as necessary, taking into account the caveats around${hasArrays ? ` arrays${hasMaps ? " and" : ""}` : ""}${hasMaps ? " maps" : ""} detailed below.
244
+ You may also set the \`root\` property of the context to be an entirely new value as long as it is one of the types allowed at the root of the tree (\`${rootTypeUnion}\`).
245
+ You should also use the \`context\` object to create new data to insert into the tree, using the builder functions available on the \`create\` property.
246
+ There are other additional helper functions available on the \`context\` object to help you analyze the tree.
247
+ Here is the definition of the \`Context\` interface:
248
+ ${context}
178
249
  ${helperMethodExplanation}
179
250
  ${hasArrays ? arrayEditing : ""}${hasMaps ? mapEditing : ""}#### Additional Notes
180
251
 
181
252
  Before outputting the edit function, you should check that it is valid according to both the application tree's schema and any restrictions of the editing APIs described above.
182
253
 
183
- ${builderExplanation}${reinsertionExplanation}
254
+ ${reinsertionExplanation}
184
255
 
185
256
  Finally, double check that the edits would accomplish the user's request (if it is possible).
186
257