@fluidframework/tree-agent 2.70.0-360374 → 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/api-report/tree-agent.alpha.api.md +20 -3
- package/dist/agent.d.ts +30 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +57 -33
- package/dist/agent.js.map +1 -1
- package/dist/alpha.d.ts +5 -0
- package/dist/api.d.ts +84 -17
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +83 -17
- package/dist/prompt.js.map +1 -1
- package/lib/agent.d.ts +30 -1
- package/lib/agent.d.ts.map +1 -1
- package/lib/agent.js +54 -32
- package/lib/agent.js.map +1 -1
- package/lib/alpha.d.ts +5 -0
- package/lib/api.d.ts +84 -17
- package/lib/api.d.ts.map +1 -1
- package/lib/api.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/prompt.d.ts.map +1 -1
- package/lib/prompt.js +82 -16
- package/lib/prompt.js.map +1 -1
- package/package.json +13 -15
- package/src/agent.ts +94 -41
- package/src/api.ts +98 -24
- package/src/index.ts +8 -1
- package/src/prompt.ts +89 -18
package/src/agent.ts
CHANGED
|
@@ -17,7 +17,15 @@ import type {
|
|
|
17
17
|
} from "@fluidframework/tree/alpha";
|
|
18
18
|
import { ObjectNodeSchema, Tree } from "@fluidframework/tree/alpha";
|
|
19
19
|
|
|
20
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
SharedTreeChatModel,
|
|
22
|
+
EditResult,
|
|
23
|
+
SemanticAgentOptions,
|
|
24
|
+
Logger,
|
|
25
|
+
SynchronousEditor,
|
|
26
|
+
AsynchronousEditor,
|
|
27
|
+
Context,
|
|
28
|
+
} from "./api.js";
|
|
21
29
|
import { getPrompt, stringifyTree } from "./prompt.js";
|
|
22
30
|
import { Subtree } from "./subtree.js";
|
|
23
31
|
import {
|
|
@@ -137,8 +145,7 @@ export class SharedTreeSemanticAgent<TSchema extends ImplicitFieldSchema> {
|
|
|
137
145
|
const editResult = await applyTreeFunction(
|
|
138
146
|
queryTree,
|
|
139
147
|
editCode,
|
|
140
|
-
this.options?.
|
|
141
|
-
this.options?.executeEdit ?? defaultExecuteEdit,
|
|
148
|
+
this.options?.editor ?? defaultEditor,
|
|
142
149
|
this.options?.logger,
|
|
143
150
|
);
|
|
144
151
|
|
|
@@ -199,51 +206,23 @@ function constructTreeNode(schema: TreeNodeSchema, value: FactoryContentObject):
|
|
|
199
206
|
async function applyTreeFunction<TSchema extends ImplicitFieldSchema>(
|
|
200
207
|
tree: Subtree<TSchema>,
|
|
201
208
|
editCode: string,
|
|
202
|
-
|
|
203
|
-
executeEdit: Required<SemanticAgentOptions>["executeEdit"],
|
|
209
|
+
editor: Required<SemanticAgentOptions>["editor"],
|
|
204
210
|
logger: Logger | undefined,
|
|
205
211
|
): Promise<EditResult> {
|
|
206
212
|
logger?.log(`### Editing Tool Invoked\n\n`);
|
|
207
213
|
logger?.log(`#### Generated Code\n\n\`\`\`javascript\n${editCode}\n\`\`\`\n\n`);
|
|
208
214
|
|
|
209
|
-
try {
|
|
210
|
-
await validateEdit(editCode);
|
|
211
|
-
} catch (error: unknown) {
|
|
212
|
-
logger?.log(`#### Code Validation Failed\n\n`);
|
|
213
|
-
logger?.log(`\`\`\`JSON\n${toErrorString(error)}\n\`\`\`\n\n`);
|
|
214
|
-
return {
|
|
215
|
-
type: "validationError",
|
|
216
|
-
message: `The generated code did not pass validation: ${toErrorString(error)}`,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Stick the tree schema constructors on an object passed to the function so that the LLM can create new nodes.
|
|
221
|
-
const create: Record<string, (input: FactoryContentObject) => TreeNode> = {};
|
|
222
|
-
for (const schema of findNamedSchemas(tree.schema)) {
|
|
223
|
-
const name = getFriendlyName(schema);
|
|
224
|
-
create[name] = (input: FactoryContentObject) => constructTreeNode(schema, input);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
215
|
// Fork a branch to edit. If the edit fails or produces an error, we discard this branch, otherwise we merge it.
|
|
228
216
|
const editTree = tree.fork();
|
|
229
|
-
const
|
|
230
|
-
get root(): ReadableField<TSchema> {
|
|
231
|
-
return editTree.field;
|
|
232
|
-
},
|
|
233
|
-
set root(value: TreeFieldFromImplicitField<ReadSchema<TSchema>>) {
|
|
234
|
-
editTree.field = value;
|
|
235
|
-
},
|
|
236
|
-
create,
|
|
237
|
-
};
|
|
238
|
-
|
|
217
|
+
const boundEditor = bindEditorToSubtree(editTree, editor);
|
|
239
218
|
try {
|
|
240
|
-
await
|
|
219
|
+
await boundEditor(editCode);
|
|
241
220
|
} catch (error: unknown) {
|
|
242
221
|
logger?.log(`#### Error\n\n`);
|
|
243
222
|
logger?.log(`\`\`\`JSON\n${toErrorString(error)}\n\`\`\`\n\n`);
|
|
244
223
|
editTree.branch.dispose();
|
|
245
224
|
return {
|
|
246
|
-
type: "
|
|
225
|
+
type: "editingError",
|
|
247
226
|
message: `Running the generated code produced an error. The state of the tree will be reset to its previous state as it was before the code ran. Please try again. Here is the error: ${toErrorString(error)}`,
|
|
248
227
|
};
|
|
249
228
|
}
|
|
@@ -257,13 +236,87 @@ async function applyTreeFunction<TSchema extends ImplicitFieldSchema>(
|
|
|
257
236
|
};
|
|
258
237
|
}
|
|
259
238
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
239
|
+
/**
|
|
240
|
+
* The default {@link AsynchronousEditor | editor} implementation that simply uses `new Function` to run the provided code.
|
|
241
|
+
* @remarks This editor allows both synchronous and asynchronous code (i.e. the provided code may return a Promise).
|
|
242
|
+
* @example `await new Function("context", code)(context);`
|
|
243
|
+
* @alpha
|
|
244
|
+
*/
|
|
245
|
+
export const defaultEditor: AsynchronousEditor = async (context, code) => {
|
|
266
246
|
// eslint-disable-next-line no-new-func, @typescript-eslint/no-implied-eval
|
|
267
247
|
const fn = new Function("context", code);
|
|
268
248
|
await fn(context);
|
|
269
249
|
};
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Binds the given {@link AsynchronousEditor | editor} to the given view or tree.
|
|
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.
|
|
254
|
+
* @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.
|
|
255
|
+
* @alpha
|
|
256
|
+
*/
|
|
257
|
+
export function bindEditorImpl<TSchema extends ImplicitFieldSchema>(
|
|
258
|
+
tree: TreeView<TSchema> | (ReadableField<TSchema> & TreeNode),
|
|
259
|
+
editor: AsynchronousEditor,
|
|
260
|
+
): (code: string) => Promise<void>;
|
|
261
|
+
/**
|
|
262
|
+
* Binds the given {@link SynchronousEditor | editor} to the given view or tree.
|
|
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.
|
|
264
|
+
* @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.
|
|
265
|
+
* @alpha
|
|
266
|
+
*/
|
|
267
|
+
export function bindEditorImpl<TSchema extends ImplicitFieldSchema>(
|
|
268
|
+
tree: TreeView<TSchema> | (ReadableField<TSchema> & TreeNode),
|
|
269
|
+
editor: SynchronousEditor,
|
|
270
|
+
): (code: string) => void;
|
|
271
|
+
/**
|
|
272
|
+
* Binds the given {@link SynchronousEditor | editor} or {@link AsynchronousEditor | editor} to the given view or tree.
|
|
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.
|
|
274
|
+
* @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.
|
|
275
|
+
* @alpha
|
|
276
|
+
*/
|
|
277
|
+
export function bindEditorImpl<TSchema extends ImplicitFieldSchema>(
|
|
278
|
+
tree: TreeView<TSchema> | (ReadableField<TSchema> & TreeNode),
|
|
279
|
+
editor: SynchronousEditor | AsynchronousEditor,
|
|
280
|
+
): ((code: string) => void) | ((code: string) => Promise<void>) {
|
|
281
|
+
const subtree = new Subtree(tree);
|
|
282
|
+
return bindEditorToSubtree(subtree, editor);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Binds the given {@link SynchronousEditor | synchronous} or {@link AsynchronousEditor | asynchronous} editor to the given view or tree.
|
|
287
|
+
* @returns A function that takes a string of JavaScript code and executes it on the given view or tree using the given editor.
|
|
288
|
+
* @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.
|
|
289
|
+
* @alpha
|
|
290
|
+
* @privateRemarks This exists (as opposed to just exporting bindEditorImpl directly) so that API documentation links work correctly.
|
|
291
|
+
*/
|
|
292
|
+
export const bindEditor = bindEditorImpl;
|
|
293
|
+
|
|
294
|
+
function bindEditorToSubtree<TSchema extends ImplicitFieldSchema>(
|
|
295
|
+
tree: Subtree<TSchema>,
|
|
296
|
+
executeEdit: SynchronousEditor | AsynchronousEditor,
|
|
297
|
+
): (code: string) => void | Promise<void> {
|
|
298
|
+
// Stick the tree schema constructors on an object passed to the function so that the LLM can create new nodes.
|
|
299
|
+
const create: Record<string, (input: FactoryContentObject) => TreeNode> = {};
|
|
300
|
+
const is: Record<string, <T extends TreeNode>(input: unknown) => input is T> = {};
|
|
301
|
+
for (const schema of findNamedSchemas(tree.schema)) {
|
|
302
|
+
const name = getFriendlyName(schema);
|
|
303
|
+
create[name] = (input: FactoryContentObject) => constructTreeNode(schema, input);
|
|
304
|
+
is[name] = <T extends TreeNode>(input: unknown): input is T => Tree.is(input, schema);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const context = {
|
|
308
|
+
get root(): ReadableField<TSchema> {
|
|
309
|
+
return tree.field;
|
|
310
|
+
},
|
|
311
|
+
set root(value: TreeFieldFromImplicitField<ReadSchema<TSchema>>) {
|
|
312
|
+
tree.field = value;
|
|
313
|
+
},
|
|
314
|
+
create,
|
|
315
|
+
is,
|
|
316
|
+
parent: (child: TreeNode): TreeNode | undefined => Tree.parent(child),
|
|
317
|
+
key: (child: TreeNode): string | number => Tree.key(child),
|
|
318
|
+
} satisfies Context<TSchema>;
|
|
319
|
+
|
|
320
|
+
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
321
|
+
return (code: string) => executeEdit(context, code);
|
|
322
|
+
}
|
package/src/api.ts
CHANGED
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { ImplicitFieldSchema, TreeNode } from "@fluidframework/tree";
|
|
7
|
-
//
|
|
7
|
+
// These are used for doc links
|
|
8
8
|
import type { FactoryContentObject, ReadableField } from "@fluidframework/tree/alpha";
|
|
9
9
|
|
|
10
|
+
// This is used for doc links
|
|
11
|
+
// eslint-disable-next-line unused-imports/no-unused-imports
|
|
12
|
+
import type { bindEditor, defaultEditor } from "./agent.js";
|
|
13
|
+
|
|
10
14
|
/**
|
|
11
15
|
* Logger interface for logging events from a {@link SharedTreeSemanticAgent}.
|
|
12
16
|
* @alpha
|
|
@@ -18,6 +22,91 @@ export interface Logger {
|
|
|
18
22
|
log(message: string): void;
|
|
19
23
|
}
|
|
20
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
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A synchronous function that executes a string of JavaScript code to perform an edit within a {@link SharedTreeSemanticAgent}.
|
|
92
|
+
* @param context - An object that must be provided to the generated code as a variable named "context" in its top-level scope.
|
|
93
|
+
* @param code - The JavaScript code that should be executed.
|
|
94
|
+
* @remarks To simulate the execution of an editor outside of an {@link SharedTreeSemanticAgent | agent}, you can use {@link bindEditor | bindEditor} to bind an editor to a specific subtree.
|
|
95
|
+
* @alpha
|
|
96
|
+
*/
|
|
97
|
+
export type SynchronousEditor = (context: Record<string, unknown>, code: string) => void;
|
|
98
|
+
/**
|
|
99
|
+
* An asynchronous function that executes a string of JavaScript code to perform an edit within a {@link SharedTreeSemanticAgent}.
|
|
100
|
+
* @param context - An object that must be provided to the generated code as a variable named "context" in its top-level scope.
|
|
101
|
+
* @param code - The JavaScript code that should be executed.
|
|
102
|
+
* @remarks To simulate the execution of an editor outside of an {@link SharedTreeSemanticAgent | agent}, you can use {@link bindEditor | bindEditor} to bind an editor to a specific subtree.
|
|
103
|
+
* @alpha
|
|
104
|
+
*/
|
|
105
|
+
export type AsynchronousEditor = (
|
|
106
|
+
context: Record<string, unknown>,
|
|
107
|
+
code: string,
|
|
108
|
+
) => Promise<void>;
|
|
109
|
+
|
|
21
110
|
/**
|
|
22
111
|
* Options used to parameterize the creation of a {@link SharedTreeSemanticAgent}.
|
|
23
112
|
* @alpha
|
|
@@ -28,22 +117,14 @@ export interface SemanticAgentOptions {
|
|
|
28
117
|
*/
|
|
29
118
|
domainHints?: string;
|
|
30
119
|
/**
|
|
31
|
-
*
|
|
32
|
-
* @remarks
|
|
33
|
-
* @
|
|
34
|
-
* @throws If the code is invalid, this function should throw an error with a human-readable message describing why it is invalid.
|
|
35
|
-
*/
|
|
36
|
-
validateEdit?: (code: string) => void | Promise<void>;
|
|
37
|
-
/**
|
|
38
|
-
* Evaluates/runs any generated JavaScript created by the {@link SharedTreeChatModel.editToolName | model's editing tool}.
|
|
39
|
-
* @remarks This happens only after the code has been successfully validated by the optional {@link SemanticAgentOptions.validateEdit | validateEdit} function.
|
|
40
|
-
* @param context - An object that must be provided to the generated code as a variable named "context" in its top-level scope.
|
|
41
|
-
* @param code - The generated JavaScript code as a string.
|
|
42
|
-
* @throws If an error is thrown while executing the code, it will be caught and the message will be forwarded to the model for debugging.
|
|
43
|
-
* @remarks If this function is not provided, the generated code will be executed using a simple `eval` call, which may not provide sufficient security guarantees for some environments.
|
|
120
|
+
* Executes any generated JavaScript created by the {@link SharedTreeChatModel.editToolName | model's editing tool}.
|
|
121
|
+
* @remarks If an error is thrown while executing the code, it will be caught and the message will be forwarded to the {@link SharedTreeChatModel | model} for debugging.
|
|
122
|
+
* @remarks If this function is not provided, the generated code will be executed using a {@link defaultEditor | simple default} which may not provide sufficient security guarantees for some environments.
|
|
44
123
|
* Use a library such as SES to provide a more secure implementation - see `@fluidframework/tree-agent-ses` for a drop-in implementation.
|
|
124
|
+
*
|
|
125
|
+
* To simulate the execution of an editor outside of an {@link SharedTreeSemanticAgent | agent}, you can use {@link bindEditor | bindEditor} to bind an editor to a specific subtree.
|
|
45
126
|
*/
|
|
46
|
-
|
|
127
|
+
editor?: SynchronousEditor | AsynchronousEditor;
|
|
47
128
|
/**
|
|
48
129
|
* The maximum number of sequential edits the LLM can make before we assume it's stuck in a loop.
|
|
49
130
|
*/
|
|
@@ -64,18 +145,11 @@ export interface EditResult {
|
|
|
64
145
|
* @remarks
|
|
65
146
|
* - `success`: The edit was successfully applied.
|
|
66
147
|
* - `disabledError`: The model is not allowed to edit the tree (i.e. {@link SharedTreeChatModel.editToolName} was not provided).
|
|
67
|
-
* - `
|
|
68
|
-
* - `executionError`: An error was thrown while parsing or executing the provided JavaScript.
|
|
148
|
+
* - `editingError`: An error was thrown while parsing or executing the provided JavaScript.
|
|
69
149
|
* - `tooManyEditsError`: The {@link SharedTreeChatQuery.edit} function has been called more than the number of times specified by {@link SemanticAgentOptions.maximumSequentialEdits} for the same message.
|
|
70
150
|
* - `expiredError`: The {@link SharedTreeChatQuery.edit} function was called after the issuing query has already completed.
|
|
71
151
|
*/
|
|
72
|
-
type:
|
|
73
|
-
| "success"
|
|
74
|
-
| "disabledError"
|
|
75
|
-
| "validationError"
|
|
76
|
-
| "executionError"
|
|
77
|
-
| "tooManyEditsError"
|
|
78
|
-
| "expiredError";
|
|
152
|
+
type: "success" | "disabledError" | "editingError" | "tooManyEditsError" | "expiredError";
|
|
79
153
|
|
|
80
154
|
/**
|
|
81
155
|
* A human-readable message describing the result of the edit attempt.
|
package/src/index.ts
CHANGED
|
@@ -9,13 +9,20 @@
|
|
|
9
9
|
* @packageDocumentation
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
export {
|
|
12
|
+
export {
|
|
13
|
+
SharedTreeSemanticAgent,
|
|
14
|
+
bindEditor,
|
|
15
|
+
bindEditorImpl,
|
|
16
|
+
defaultEditor,
|
|
17
|
+
} from "./agent.js";
|
|
13
18
|
export type {
|
|
14
19
|
EditResult,
|
|
15
20
|
SharedTreeChatModel,
|
|
16
21
|
SharedTreeChatQuery,
|
|
17
22
|
Logger,
|
|
18
23
|
SemanticAgentOptions,
|
|
24
|
+
SynchronousEditor,
|
|
25
|
+
AsynchronousEditor,
|
|
19
26
|
} from "./api.js";
|
|
20
27
|
export { type TreeView, llmDefault } from "./utils.js";
|
|
21
28
|
export {
|
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
|
|
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
|
-
:
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
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 (\`${
|
|
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
|
-
${
|
|
254
|
+
${reinsertionExplanation}
|
|
184
255
|
|
|
185
256
|
Finally, double check that the edits would accomplish the user's request (if it is possible).
|
|
186
257
|
|