@fluidframework/tree-agent 2.70.0-360374 → 2.70.0-360753
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 +52 -33
- package/dist/agent.js.map +1 -1
- package/dist/alpha.d.ts +5 -0
- package/dist/api.d.ts +24 -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/lib/agent.d.ts +30 -1
- package/lib/agent.d.ts.map +1 -1
- package/lib/agent.js +49 -32
- package/lib/agent.js.map +1 -1
- package/lib/alpha.d.ts +5 -0
- package/lib/api.d.ts +24 -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/package.json +10 -12
- package/src/agent.ts +88 -41
- package/src/api.ts +33 -24
- package/src/index.ts +8 -1
package/lib/agent.js
CHANGED
|
@@ -97,7 +97,7 @@ export class SharedTreeSemanticAgent {
|
|
|
97
97
|
message: `The maximum number of edits (${maxEditCount}) for this query has been exceeded.`,
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
|
-
const editResult = await applyTreeFunction(queryTree, editCode, this.options?.
|
|
100
|
+
const editResult = await applyTreeFunction(queryTree, editCode, this.options?.editor ?? defaultEditor, this.options?.logger);
|
|
101
101
|
rollbackEdits = editResult.type !== "success";
|
|
102
102
|
return editResult;
|
|
103
103
|
};
|
|
@@ -147,46 +147,21 @@ function constructTreeNode(schema, value) {
|
|
|
147
147
|
/**
|
|
148
148
|
* Applies the given function (as a string of JavaScript code or an actual function) to the given tree.
|
|
149
149
|
*/
|
|
150
|
-
async function applyTreeFunction(tree, editCode,
|
|
150
|
+
async function applyTreeFunction(tree, editCode, editor, logger) {
|
|
151
151
|
logger?.log(`### Editing Tool Invoked\n\n`);
|
|
152
152
|
logger?.log(`#### Generated Code\n\n\`\`\`javascript\n${editCode}\n\`\`\`\n\n`);
|
|
153
|
-
try {
|
|
154
|
-
await validateEdit(editCode);
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
logger?.log(`#### Code Validation Failed\n\n`);
|
|
158
|
-
logger?.log(`\`\`\`JSON\n${toErrorString(error)}\n\`\`\`\n\n`);
|
|
159
|
-
return {
|
|
160
|
-
type: "validationError",
|
|
161
|
-
message: `The generated code did not pass validation: ${toErrorString(error)}`,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
// Stick the tree schema constructors on an object passed to the function so that the LLM can create new nodes.
|
|
165
|
-
const create = {};
|
|
166
|
-
for (const schema of findNamedSchemas(tree.schema)) {
|
|
167
|
-
const name = getFriendlyName(schema);
|
|
168
|
-
create[name] = (input) => constructTreeNode(schema, input);
|
|
169
|
-
}
|
|
170
153
|
// Fork a branch to edit. If the edit fails or produces an error, we discard this branch, otherwise we merge it.
|
|
171
154
|
const editTree = tree.fork();
|
|
172
|
-
const
|
|
173
|
-
get root() {
|
|
174
|
-
return editTree.field;
|
|
175
|
-
},
|
|
176
|
-
set root(value) {
|
|
177
|
-
editTree.field = value;
|
|
178
|
-
},
|
|
179
|
-
create,
|
|
180
|
-
};
|
|
155
|
+
const boundEditor = bindEditorToSubtree(editTree, editor);
|
|
181
156
|
try {
|
|
182
|
-
await
|
|
157
|
+
await boundEditor(editCode);
|
|
183
158
|
}
|
|
184
159
|
catch (error) {
|
|
185
160
|
logger?.log(`#### Error\n\n`);
|
|
186
161
|
logger?.log(`\`\`\`JSON\n${toErrorString(error)}\n\`\`\`\n\n`);
|
|
187
162
|
editTree.branch.dispose();
|
|
188
163
|
return {
|
|
189
|
-
type: "
|
|
164
|
+
type: "editingError",
|
|
190
165
|
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)}`,
|
|
191
166
|
};
|
|
192
167
|
}
|
|
@@ -198,10 +173,52 @@ async function applyTreeFunction(tree, editCode, validateEdit, executeEdit, logg
|
|
|
198
173
|
message: `After running the code, the new state of the tree is:\n\n\`\`\`JSON\n${stringifyTree(tree.field)}\n\`\`\``,
|
|
199
174
|
};
|
|
200
175
|
}
|
|
201
|
-
|
|
202
|
-
|
|
176
|
+
/**
|
|
177
|
+
* The default {@link AsynchronousEditor | editor} implementation that simply uses `new Function` to run the provided code.
|
|
178
|
+
* @remarks This editor allows both synchronous and asynchronous code (i.e. the provided code may return a Promise).
|
|
179
|
+
* @example `await new Function("context", code)(context);`
|
|
180
|
+
* @alpha
|
|
181
|
+
*/
|
|
182
|
+
export const defaultEditor = async (context, code) => {
|
|
203
183
|
// eslint-disable-next-line no-new-func, @typescript-eslint/no-implied-eval
|
|
204
184
|
const fn = new Function("context", code);
|
|
205
185
|
await fn(context);
|
|
206
186
|
};
|
|
187
|
+
/**
|
|
188
|
+
* Binds the given {@link SynchronousEditor | editor} or {@link AsynchronousEditor | editor} to the given view or tree.
|
|
189
|
+
* @returns A function that takes a string of JavaScript code and executes it on the given view or tree using the given editor function.
|
|
190
|
+
* @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.
|
|
191
|
+
* @alpha
|
|
192
|
+
*/
|
|
193
|
+
export function bindEditorImpl(tree, editor) {
|
|
194
|
+
const subtree = new Subtree(tree);
|
|
195
|
+
return bindEditorToSubtree(subtree, editor);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Binds the given {@link SynchronousEditor | synchronous} or {@link AsynchronousEditor | asynchronous} editor to the given view or tree.
|
|
199
|
+
* @returns A function that takes a string of JavaScript code and executes it on the given view or tree using the given editor.
|
|
200
|
+
* @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.
|
|
201
|
+
* @alpha
|
|
202
|
+
* @privateRemarks This exists (as opposed to just exporting bindEditorImpl directly) so that API documentation links work correctly.
|
|
203
|
+
*/
|
|
204
|
+
export const bindEditor = bindEditorImpl;
|
|
205
|
+
function bindEditorToSubtree(tree, executeEdit) {
|
|
206
|
+
// Stick the tree schema constructors on an object passed to the function so that the LLM can create new nodes.
|
|
207
|
+
const create = {};
|
|
208
|
+
for (const schema of findNamedSchemas(tree.schema)) {
|
|
209
|
+
const name = getFriendlyName(schema);
|
|
210
|
+
create[name] = (input) => constructTreeNode(schema, input);
|
|
211
|
+
}
|
|
212
|
+
const context = {
|
|
213
|
+
get root() {
|
|
214
|
+
return tree.field;
|
|
215
|
+
},
|
|
216
|
+
set root(value) {
|
|
217
|
+
tree.field = value;
|
|
218
|
+
},
|
|
219
|
+
create,
|
|
220
|
+
};
|
|
221
|
+
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
222
|
+
return (code) => executeEdit(context, code);
|
|
223
|
+
}
|
|
207
224
|
//# sourceMappingURL=agent.js.map
|
package/lib/agent.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type {\n\tImplicitFieldSchema,\n\tTreeFieldFromImplicitField,\n\tTreeNodeSchema,\n} from \"@fluidframework/tree\";\nimport { TreeNode } from \"@fluidframework/tree\";\nimport type {\n\tReadableField,\n\tFactoryContentObject,\n\tInsertableContent,\n\tReadSchema,\n} from \"@fluidframework/tree/alpha\";\nimport { ObjectNodeSchema, Tree } from \"@fluidframework/tree/alpha\";\n\nimport type { SharedTreeChatModel, EditResult, SemanticAgentOptions, Logger } from \"./api.js\";\nimport { getPrompt, stringifyTree } from \"./prompt.js\";\nimport { Subtree } from \"./subtree.js\";\nimport {\n\tconstructNode,\n\tgetFriendlyName,\n\tllmDefault,\n\ttype TreeView,\n\tfindNamedSchemas,\n\ttoErrorString,\n} from \"./utils.js\";\n\n/**\n * The default maximum number of sequential edits the LLM can make before we assume it's stuck in a loop.\n * @remarks This can be overridden by passing {@link SemanticAgentOptions.maximumSequentialEdits | maximumSequentialEdits} to {@link createSemanticAgent}.\n */\nconst defaultMaxSequentialEdits = 20;\n\n/**\n * An agent that uses a {@link SharedTreeChatModel} to interact with a SharedTree.\n * @remarks This class forwards user queries to the chat model, and handles the application of any edits to the tree that the model requests.\n * @alpha @sealed\n */\nexport class SharedTreeSemanticAgent<TSchema extends ImplicitFieldSchema> {\n\t// Converted from ECMAScript private fields (#name) to TypeScript private members for easier debugger inspection.\n\tprivate readonly outerTree: Subtree<TSchema>;\n\t/**\n\t * Whether or not the outer tree has changed since the last query finished.\n\t */\n\tprivate outerTreeIsDirty = false;\n\n\tpublic constructor(\n\t\tprivate readonly client: SharedTreeChatModel,\n\t\ttree: TreeView<TSchema> | (ReadableField<TSchema> & TreeNode),\n\t\tprivate readonly options?: Readonly<SemanticAgentOptions>,\n\t) {\n\t\tif (tree instanceof TreeNode) {\n\t\t\tTree.on(tree, \"treeChanged\", () => (this.outerTreeIsDirty = true));\n\t\t} else {\n\t\t\ttree.events.on(\"changed\", () => (this.outerTreeIsDirty = true));\n\t\t}\n\n\t\tthis.outerTree = new Subtree(tree);\n\t\tconst prompt = getPrompt({\n\t\t\tsubtree: this.outerTree,\n\t\t\teditToolName: this.client.editToolName,\n\t\t\tdomainHints: this.options?.domainHints,\n\t\t});\n\t\tthis.options?.logger?.log(`# Fluid Framework SharedTree AI Agent Log\\n\\n`);\n\t\tconst now = new Date();\n\t\tconst formattedDate = now.toLocaleString(undefined, {\n\t\t\tweekday: \"long\",\n\t\t\tyear: \"numeric\",\n\t\t\tmonth: \"long\",\n\t\t\tday: \"numeric\",\n\t\t\thour: \"numeric\",\n\t\t\tminute: \"2-digit\",\n\t\t\tsecond: \"2-digit\",\n\t\t});\n\t\tthis.options?.logger?.log(`Agent created: **${formattedDate}**\\n\\n`);\n\t\tif (this.client.name !== undefined) {\n\t\t\tthis.options?.logger?.log(`Model: **${this.client.name}**\\n\\n`);\n\t\t}\n\t\tthis.client.appendContext?.(prompt);\n\t\tthis.options?.logger?.log(`## System Prompt\\n\\n${prompt}\\n\\n`);\n\t}\n\n\t/**\n\t * Given a user prompt, return a response.\n\t *\n\t * @param userPrompt - The prompt to send to the agent.\n\t * @returns The agent's response.\n\t */\n\tpublic async query(userPrompt: string): Promise<string> {\n\t\tthis.options?.logger?.log(`## User Query\\n\\n${userPrompt}\\n\\n`);\n\n\t\t// Notify the llm if the tree has changed since the last query, and if so, provide the new state of the tree.\n\t\tif (this.outerTreeIsDirty) {\n\t\t\tconst stringified = stringifyTree(this.outerTree.field);\n\t\t\tthis.client.appendContext?.(\n\t\t\t\t`The tree has changed since the last query. The new state of the tree is: \\n\\n\\`\\`\\`JSON\\n${stringified}\\n\\`\\`\\``,\n\t\t\t);\n\t\t\tthis.options?.logger?.log(\n\t\t\t\t`### Latest Tree State\\n\\nThe Tree was edited by a local or remote user since the previous query. The latest state is:\\n\\n\\`\\`\\`JSON\\n${stringified}\\n\\`\\`\\`\\n\\n`,\n\t\t\t);\n\t\t}\n\n\t\t// Fork a branch that will live for the lifetime of this query (which can be multiple LLM calls if the there are errors or the LLM decides to take multiple steps to accomplish a task).\n\t\t// The branch will be merged back into the outer branch if and only if the query succeeds.\n\t\tconst queryTree = this.outerTree.fork();\n\t\tconst maxEditCount = this.options?.maximumSequentialEdits ?? defaultMaxSequentialEdits;\n\t\tlet active = true;\n\t\tlet editCount = 0;\n\t\tlet rollbackEdits = false;\n\t\tconst { editToolName } = this.client;\n\t\tconst edit = async (editCode: string): Promise<EditResult> => {\n\t\t\tif (editToolName === undefined) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"disabledError\",\n\t\t\t\t\tmessage: \"Editing is not enabled for this model.\",\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (!active) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"expiredError\",\n\t\t\t\t\tmessage: `The query has already completed. Further edits are not allowed.`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (++editCount > maxEditCount) {\n\t\t\t\trollbackEdits = true;\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"tooManyEditsError\",\n\t\t\t\t\tmessage: `The maximum number of edits (${maxEditCount}) for this query has been exceeded.`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst editResult = await applyTreeFunction(\n\t\t\t\tqueryTree,\n\t\t\t\teditCode,\n\t\t\t\tthis.options?.validateEdit ?? defaultValidateEdit,\n\t\t\t\tthis.options?.executeEdit ?? defaultExecuteEdit,\n\t\t\t\tthis.options?.logger,\n\t\t\t);\n\n\t\t\trollbackEdits = editResult.type !== \"success\";\n\t\t\treturn editResult;\n\t\t};\n\n\t\tconst responseMessage = await this.client.query({\n\t\t\ttext: userPrompt,\n\t\t\tedit,\n\t\t});\n\t\tactive = false;\n\n\t\tif (!rollbackEdits) {\n\t\t\tthis.outerTree.branch.merge(queryTree.branch);\n\t\t\tthis.outerTreeIsDirty = false;\n\t\t}\n\t\tthis.options?.logger?.log(`## Response\\n\\n`);\n\t\tthis.options?.logger?.log(`${responseMessage}\\n\\n`);\n\t\treturn responseMessage;\n\t}\n}\n\n/**\n * Creates an unhydrated node of the given schema with the given value.\n * @remarks If the schema is an object with {@link llmDefault | default values}, this function populates the node with those defaults.\n */\nfunction constructTreeNode(schema: TreeNodeSchema, value: FactoryContentObject): TreeNode {\n\tif (schema instanceof ObjectNodeSchema) {\n\t\tconst inputWithDefaults: Record<string, InsertableContent | undefined> = {};\n\t\tfor (const [key, field] of schema.fields) {\n\t\t\tif (value[key] === undefined) {\n\t\t\t\tif (\n\t\t\t\t\ttypeof field.metadata.custom === \"object\" &&\n\t\t\t\t\tfield.metadata.custom !== null &&\n\t\t\t\t\tllmDefault in field.metadata.custom\n\t\t\t\t) {\n\t\t\t\t\tconst defaulter = field.metadata.custom[llmDefault];\n\t\t\t\t\tif (typeof defaulter === \"function\") {\n\t\t\t\t\t\tconst defaultValue: unknown = defaulter();\n\t\t\t\t\t\tif (defaultValue !== undefined) {\n\t\t\t\t\t\t\tinputWithDefaults[key] = defaultValue;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tinputWithDefaults[key] = value[key];\n\t\t\t}\n\t\t}\n\t\treturn constructNode(schema, inputWithDefaults);\n\t}\n\treturn constructNode(schema, value);\n}\n\n/**\n * Applies the given function (as a string of JavaScript code or an actual function) to the given tree.\n */\nasync function applyTreeFunction<TSchema extends ImplicitFieldSchema>(\n\ttree: Subtree<TSchema>,\n\teditCode: string,\n\tvalidateEdit: Required<SemanticAgentOptions>[\"validateEdit\"],\n\texecuteEdit: Required<SemanticAgentOptions>[\"executeEdit\"],\n\tlogger: Logger | undefined,\n): Promise<EditResult> {\n\tlogger?.log(`### Editing Tool Invoked\\n\\n`);\n\tlogger?.log(`#### Generated Code\\n\\n\\`\\`\\`javascript\\n${editCode}\\n\\`\\`\\`\\n\\n`);\n\n\ttry {\n\t\tawait validateEdit(editCode);\n\t} catch (error: unknown) {\n\t\tlogger?.log(`#### Code Validation Failed\\n\\n`);\n\t\tlogger?.log(`\\`\\`\\`JSON\\n${toErrorString(error)}\\n\\`\\`\\`\\n\\n`);\n\t\treturn {\n\t\t\ttype: \"validationError\",\n\t\t\tmessage: `The generated code did not pass validation: ${toErrorString(error)}`,\n\t\t};\n\t}\n\n\t// Stick the tree schema constructors on an object passed to the function so that the LLM can create new nodes.\n\tconst create: Record<string, (input: FactoryContentObject) => TreeNode> = {};\n\tfor (const schema of findNamedSchemas(tree.schema)) {\n\t\tconst name = getFriendlyName(schema);\n\t\tcreate[name] = (input: FactoryContentObject) => constructTreeNode(schema, input);\n\t}\n\n\t// Fork a branch to edit. If the edit fails or produces an error, we discard this branch, otherwise we merge it.\n\tconst editTree = tree.fork();\n\tconst context = {\n\t\tget root(): ReadableField<TSchema> {\n\t\t\treturn editTree.field;\n\t\t},\n\t\tset root(value: TreeFieldFromImplicitField<ReadSchema<TSchema>>) {\n\t\t\teditTree.field = value;\n\t\t},\n\t\tcreate,\n\t};\n\n\ttry {\n\t\tawait executeEdit(context, editCode);\n\t} catch (error: unknown) {\n\t\tlogger?.log(`#### Error\\n\\n`);\n\t\tlogger?.log(`\\`\\`\\`JSON\\n${toErrorString(error)}\\n\\`\\`\\`\\n\\n`);\n\t\teditTree.branch.dispose();\n\t\treturn {\n\t\t\ttype: \"executionError\",\n\t\t\tmessage: `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)}`,\n\t\t};\n\t}\n\n\ttree.branch.merge(editTree.branch);\n\tlogger?.log(`#### New Tree State\\n\\n`);\n\tlogger?.log(`${`\\`\\`\\`JSON\\n${stringifyTree(tree.field)}\\n\\`\\`\\``}\\n\\n`);\n\treturn {\n\t\ttype: \"success\",\n\t\tmessage: `After running the code, the new state of the tree is:\\n\\n\\`\\`\\`JSON\\n${stringifyTree(tree.field)}\\n\\`\\`\\``,\n\t};\n}\n\nconst defaultValidateEdit: Required<SemanticAgentOptions>[\"validateEdit\"] = () => {};\n\nconst defaultExecuteEdit: Required<SemanticAgentOptions>[\"executeEdit\"] = async (\n\tcontext,\n\tcode,\n) => {\n\t// eslint-disable-next-line no-new-func, @typescript-eslint/no-implied-eval\n\tconst fn = new Function(\"context\", code);\n\tawait fn(context);\n};\n"]}
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type {\n\tImplicitFieldSchema,\n\tTreeFieldFromImplicitField,\n\tTreeNodeSchema,\n} from \"@fluidframework/tree\";\nimport { TreeNode } from \"@fluidframework/tree\";\nimport type {\n\tReadableField,\n\tFactoryContentObject,\n\tInsertableContent,\n\tReadSchema,\n} from \"@fluidframework/tree/alpha\";\nimport { ObjectNodeSchema, Tree } from \"@fluidframework/tree/alpha\";\n\nimport type {\n\tSharedTreeChatModel,\n\tEditResult,\n\tSemanticAgentOptions,\n\tLogger,\n\tSynchronousEditor,\n\tAsynchronousEditor,\n} from \"./api.js\";\nimport { getPrompt, stringifyTree } from \"./prompt.js\";\nimport { Subtree } from \"./subtree.js\";\nimport {\n\tconstructNode,\n\tgetFriendlyName,\n\tllmDefault,\n\ttype TreeView,\n\tfindNamedSchemas,\n\ttoErrorString,\n} from \"./utils.js\";\n\n/**\n * The default maximum number of sequential edits the LLM can make before we assume it's stuck in a loop.\n * @remarks This can be overridden by passing {@link SemanticAgentOptions.maximumSequentialEdits | maximumSequentialEdits} to {@link createSemanticAgent}.\n */\nconst defaultMaxSequentialEdits = 20;\n\n/**\n * An agent that uses a {@link SharedTreeChatModel} to interact with a SharedTree.\n * @remarks This class forwards user queries to the chat model, and handles the application of any edits to the tree that the model requests.\n * @alpha @sealed\n */\nexport class SharedTreeSemanticAgent<TSchema extends ImplicitFieldSchema> {\n\t// Converted from ECMAScript private fields (#name) to TypeScript private members for easier debugger inspection.\n\tprivate readonly outerTree: Subtree<TSchema>;\n\t/**\n\t * Whether or not the outer tree has changed since the last query finished.\n\t */\n\tprivate outerTreeIsDirty = false;\n\n\tpublic constructor(\n\t\tprivate readonly client: SharedTreeChatModel,\n\t\ttree: TreeView<TSchema> | (ReadableField<TSchema> & TreeNode),\n\t\tprivate readonly options?: Readonly<SemanticAgentOptions>,\n\t) {\n\t\tif (tree instanceof TreeNode) {\n\t\t\tTree.on(tree, \"treeChanged\", () => (this.outerTreeIsDirty = true));\n\t\t} else {\n\t\t\ttree.events.on(\"changed\", () => (this.outerTreeIsDirty = true));\n\t\t}\n\n\t\tthis.outerTree = new Subtree(tree);\n\t\tconst prompt = getPrompt({\n\t\t\tsubtree: this.outerTree,\n\t\t\teditToolName: this.client.editToolName,\n\t\t\tdomainHints: this.options?.domainHints,\n\t\t});\n\t\tthis.options?.logger?.log(`# Fluid Framework SharedTree AI Agent Log\\n\\n`);\n\t\tconst now = new Date();\n\t\tconst formattedDate = now.toLocaleString(undefined, {\n\t\t\tweekday: \"long\",\n\t\t\tyear: \"numeric\",\n\t\t\tmonth: \"long\",\n\t\t\tday: \"numeric\",\n\t\t\thour: \"numeric\",\n\t\t\tminute: \"2-digit\",\n\t\t\tsecond: \"2-digit\",\n\t\t});\n\t\tthis.options?.logger?.log(`Agent created: **${formattedDate}**\\n\\n`);\n\t\tif (this.client.name !== undefined) {\n\t\t\tthis.options?.logger?.log(`Model: **${this.client.name}**\\n\\n`);\n\t\t}\n\t\tthis.client.appendContext?.(prompt);\n\t\tthis.options?.logger?.log(`## System Prompt\\n\\n${prompt}\\n\\n`);\n\t}\n\n\t/**\n\t * Given a user prompt, return a response.\n\t *\n\t * @param userPrompt - The prompt to send to the agent.\n\t * @returns The agent's response.\n\t */\n\tpublic async query(userPrompt: string): Promise<string> {\n\t\tthis.options?.logger?.log(`## User Query\\n\\n${userPrompt}\\n\\n`);\n\n\t\t// Notify the llm if the tree has changed since the last query, and if so, provide the new state of the tree.\n\t\tif (this.outerTreeIsDirty) {\n\t\t\tconst stringified = stringifyTree(this.outerTree.field);\n\t\t\tthis.client.appendContext?.(\n\t\t\t\t`The tree has changed since the last query. The new state of the tree is: \\n\\n\\`\\`\\`JSON\\n${stringified}\\n\\`\\`\\``,\n\t\t\t);\n\t\t\tthis.options?.logger?.log(\n\t\t\t\t`### Latest Tree State\\n\\nThe Tree was edited by a local or remote user since the previous query. The latest state is:\\n\\n\\`\\`\\`JSON\\n${stringified}\\n\\`\\`\\`\\n\\n`,\n\t\t\t);\n\t\t}\n\n\t\t// Fork a branch that will live for the lifetime of this query (which can be multiple LLM calls if the there are errors or the LLM decides to take multiple steps to accomplish a task).\n\t\t// The branch will be merged back into the outer branch if and only if the query succeeds.\n\t\tconst queryTree = this.outerTree.fork();\n\t\tconst maxEditCount = this.options?.maximumSequentialEdits ?? defaultMaxSequentialEdits;\n\t\tlet active = true;\n\t\tlet editCount = 0;\n\t\tlet rollbackEdits = false;\n\t\tconst { editToolName } = this.client;\n\t\tconst edit = async (editCode: string): Promise<EditResult> => {\n\t\t\tif (editToolName === undefined) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"disabledError\",\n\t\t\t\t\tmessage: \"Editing is not enabled for this model.\",\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (!active) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"expiredError\",\n\t\t\t\t\tmessage: `The query has already completed. Further edits are not allowed.`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (++editCount > maxEditCount) {\n\t\t\t\trollbackEdits = true;\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"tooManyEditsError\",\n\t\t\t\t\tmessage: `The maximum number of edits (${maxEditCount}) for this query has been exceeded.`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst editResult = await applyTreeFunction(\n\t\t\t\tqueryTree,\n\t\t\t\teditCode,\n\t\t\t\tthis.options?.editor ?? defaultEditor,\n\t\t\t\tthis.options?.logger,\n\t\t\t);\n\n\t\t\trollbackEdits = editResult.type !== \"success\";\n\t\t\treturn editResult;\n\t\t};\n\n\t\tconst responseMessage = await this.client.query({\n\t\t\ttext: userPrompt,\n\t\t\tedit,\n\t\t});\n\t\tactive = false;\n\n\t\tif (!rollbackEdits) {\n\t\t\tthis.outerTree.branch.merge(queryTree.branch);\n\t\t\tthis.outerTreeIsDirty = false;\n\t\t}\n\t\tthis.options?.logger?.log(`## Response\\n\\n`);\n\t\tthis.options?.logger?.log(`${responseMessage}\\n\\n`);\n\t\treturn responseMessage;\n\t}\n}\n\n/**\n * Creates an unhydrated node of the given schema with the given value.\n * @remarks If the schema is an object with {@link llmDefault | default values}, this function populates the node with those defaults.\n */\nfunction constructTreeNode(schema: TreeNodeSchema, value: FactoryContentObject): TreeNode {\n\tif (schema instanceof ObjectNodeSchema) {\n\t\tconst inputWithDefaults: Record<string, InsertableContent | undefined> = {};\n\t\tfor (const [key, field] of schema.fields) {\n\t\t\tif (value[key] === undefined) {\n\t\t\t\tif (\n\t\t\t\t\ttypeof field.metadata.custom === \"object\" &&\n\t\t\t\t\tfield.metadata.custom !== null &&\n\t\t\t\t\tllmDefault in field.metadata.custom\n\t\t\t\t) {\n\t\t\t\t\tconst defaulter = field.metadata.custom[llmDefault];\n\t\t\t\t\tif (typeof defaulter === \"function\") {\n\t\t\t\t\t\tconst defaultValue: unknown = defaulter();\n\t\t\t\t\t\tif (defaultValue !== undefined) {\n\t\t\t\t\t\t\tinputWithDefaults[key] = defaultValue;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tinputWithDefaults[key] = value[key];\n\t\t\t}\n\t\t}\n\t\treturn constructNode(schema, inputWithDefaults);\n\t}\n\treturn constructNode(schema, value);\n}\n\n/**\n * Applies the given function (as a string of JavaScript code or an actual function) to the given tree.\n */\nasync function applyTreeFunction<TSchema extends ImplicitFieldSchema>(\n\ttree: Subtree<TSchema>,\n\teditCode: string,\n\teditor: Required<SemanticAgentOptions>[\"editor\"],\n\tlogger: Logger | undefined,\n): Promise<EditResult> {\n\tlogger?.log(`### Editing Tool Invoked\\n\\n`);\n\tlogger?.log(`#### Generated Code\\n\\n\\`\\`\\`javascript\\n${editCode}\\n\\`\\`\\`\\n\\n`);\n\n\t// Fork a branch to edit. If the edit fails or produces an error, we discard this branch, otherwise we merge it.\n\tconst editTree = tree.fork();\n\tconst boundEditor = bindEditorToSubtree(editTree, editor);\n\ttry {\n\t\tawait boundEditor(editCode);\n\t} catch (error: unknown) {\n\t\tlogger?.log(`#### Error\\n\\n`);\n\t\tlogger?.log(`\\`\\`\\`JSON\\n${toErrorString(error)}\\n\\`\\`\\`\\n\\n`);\n\t\teditTree.branch.dispose();\n\t\treturn {\n\t\t\ttype: \"editingError\",\n\t\t\tmessage: `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)}`,\n\t\t};\n\t}\n\n\ttree.branch.merge(editTree.branch);\n\tlogger?.log(`#### New Tree State\\n\\n`);\n\tlogger?.log(`${`\\`\\`\\`JSON\\n${stringifyTree(tree.field)}\\n\\`\\`\\``}\\n\\n`);\n\treturn {\n\t\ttype: \"success\",\n\t\tmessage: `After running the code, the new state of the tree is:\\n\\n\\`\\`\\`JSON\\n${stringifyTree(tree.field)}\\n\\`\\`\\``,\n\t};\n}\n\n/**\n * The default {@link AsynchronousEditor | editor} implementation that simply uses `new Function` to run the provided code.\n * @remarks This editor allows both synchronous and asynchronous code (i.e. the provided code may return a Promise).\n * @example `await new Function(\"context\", code)(context);`\n * @alpha\n */\nexport const defaultEditor: AsynchronousEditor = async (context, code) => {\n\t// eslint-disable-next-line no-new-func, @typescript-eslint/no-implied-eval\n\tconst fn = new Function(\"context\", code);\n\tawait fn(context);\n};\n\n/**\n * Binds the given {@link SynchronousEditor | editor} to the given view or tree.\n * @returns A function that takes a string of JavaScript code and executes it on the given view or tree using the given editor function.\n * @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.\n * @alpha\n */\nexport function bindEditorImpl<TSchema extends ImplicitFieldSchema>(\n\ttree: TreeView<TSchema> | (ReadableField<TSchema> & TreeNode),\n\teditor: SynchronousEditor,\n): (code: string) => void;\n/**\n * Binds the given {@link AsynchronousEditor | editor} to the given view or tree.\n * @returns A function that takes a string of JavaScript code and executes it on the given view or tree using the given editor function.\n * @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.\n * @alpha\n */\nexport function bindEditorImpl<TSchema extends ImplicitFieldSchema>(\n\ttree: TreeView<TSchema> | (ReadableField<TSchema> & TreeNode),\n\teditor: AsynchronousEditor,\n): (code: string) => Promise<void>;\n/**\n * Binds the given {@link SynchronousEditor | editor} or {@link AsynchronousEditor | editor} to the given view or tree.\n * @returns A function that takes a string of JavaScript code and executes it on the given view or tree using the given editor function.\n * @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.\n * @alpha\n */\nexport function bindEditorImpl<TSchema extends ImplicitFieldSchema>(\n\ttree: TreeView<TSchema> | (ReadableField<TSchema> & TreeNode),\n\teditor: SynchronousEditor | AsynchronousEditor,\n): ((code: string) => void) | ((code: string) => Promise<void>) {\n\tconst subtree = new Subtree(tree);\n\treturn bindEditorToSubtree(subtree, editor);\n}\n\n/**\n * Binds the given {@link SynchronousEditor | synchronous} or {@link AsynchronousEditor | asynchronous} editor to the given view or tree.\n * @returns A function that takes a string of JavaScript code and executes it on the given view or tree using the given editor.\n * @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.\n * @alpha\n * @privateRemarks This exists (as opposed to just exporting bindEditorImpl directly) so that API documentation links work correctly.\n */\nexport const bindEditor = bindEditorImpl;\n\nfunction bindEditorToSubtree<TSchema extends ImplicitFieldSchema>(\n\ttree: Subtree<TSchema>,\n\texecuteEdit: SynchronousEditor | AsynchronousEditor,\n): (code: string) => void | Promise<void> {\n\t// Stick the tree schema constructors on an object passed to the function so that the LLM can create new nodes.\n\tconst create: Record<string, (input: FactoryContentObject) => TreeNode> = {};\n\tfor (const schema of findNamedSchemas(tree.schema)) {\n\t\tconst name = getFriendlyName(schema);\n\t\tcreate[name] = (input: FactoryContentObject) => constructTreeNode(schema, input);\n\t}\n\n\tconst context = {\n\t\tget root(): ReadableField<TSchema> {\n\t\t\treturn tree.field;\n\t\t},\n\t\tset root(value: TreeFieldFromImplicitField<ReadSchema<TSchema>>) {\n\t\t\ttree.field = value;\n\t\t},\n\t\tcreate,\n\t};\n\n\t// eslint-disable-next-line @typescript-eslint/promise-function-async\n\treturn (code: string) => executeEdit(context, code);\n}\n"]}
|
package/lib/alpha.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export {
|
|
|
18
18
|
// #region @alpha APIs
|
|
19
19
|
Arg,
|
|
20
20
|
ArgsTuple,
|
|
21
|
+
AsynchronousEditor,
|
|
21
22
|
BindableSchema,
|
|
22
23
|
Ctor,
|
|
23
24
|
EditResult,
|
|
@@ -31,8 +32,12 @@ export {
|
|
|
31
32
|
SharedTreeChatModel,
|
|
32
33
|
SharedTreeChatQuery,
|
|
33
34
|
SharedTreeSemanticAgent,
|
|
35
|
+
SynchronousEditor,
|
|
34
36
|
TreeView,
|
|
37
|
+
bindEditor,
|
|
38
|
+
bindEditorImpl,
|
|
35
39
|
buildFunc,
|
|
40
|
+
defaultEditor,
|
|
36
41
|
exposeMethodsSymbol,
|
|
37
42
|
llmDefault
|
|
38
43
|
// #endregion
|
package/lib/api.d.ts
CHANGED
|
@@ -14,6 +14,22 @@ export interface Logger {
|
|
|
14
14
|
*/
|
|
15
15
|
log(message: string): void;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* A synchronous function that executes a string of JavaScript code to perform an edit within a {@link SharedTreeSemanticAgent}.
|
|
19
|
+
* @param context - An object that must be provided to the generated code as a variable named "context" in its top-level scope.
|
|
20
|
+
* @param code - The JavaScript code that should be executed.
|
|
21
|
+
* @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.
|
|
22
|
+
* @alpha
|
|
23
|
+
*/
|
|
24
|
+
export type SynchronousEditor = (context: Record<string, unknown>, code: string) => void;
|
|
25
|
+
/**
|
|
26
|
+
* An asynchronous function that executes a string of JavaScript code to perform an edit within a {@link SharedTreeSemanticAgent}.
|
|
27
|
+
* @param context - An object that must be provided to the generated code as a variable named "context" in its top-level scope.
|
|
28
|
+
* @param code - The JavaScript code that should be executed.
|
|
29
|
+
* @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.
|
|
30
|
+
* @alpha
|
|
31
|
+
*/
|
|
32
|
+
export type AsynchronousEditor = (context: Record<string, unknown>, code: string) => Promise<void>;
|
|
17
33
|
/**
|
|
18
34
|
* Options used to parameterize the creation of a {@link SharedTreeSemanticAgent}.
|
|
19
35
|
* @alpha
|
|
@@ -24,22 +40,14 @@ export interface SemanticAgentOptions {
|
|
|
24
40
|
*/
|
|
25
41
|
domainHints?: string;
|
|
26
42
|
/**
|
|
27
|
-
*
|
|
28
|
-
* @remarks
|
|
29
|
-
* @
|
|
30
|
-
* @throws If the code is invalid, this function should throw an error with a human-readable message describing why it is invalid.
|
|
31
|
-
*/
|
|
32
|
-
validateEdit?: (code: string) => void | Promise<void>;
|
|
33
|
-
/**
|
|
34
|
-
* Evaluates/runs any generated JavaScript created by the {@link SharedTreeChatModel.editToolName | model's editing tool}.
|
|
35
|
-
* @remarks This happens only after the code has been successfully validated by the optional {@link SemanticAgentOptions.validateEdit | validateEdit} function.
|
|
36
|
-
* @param context - An object that must be provided to the generated code as a variable named "context" in its top-level scope.
|
|
37
|
-
* @param code - The generated JavaScript code as a string.
|
|
38
|
-
* @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.
|
|
39
|
-
* @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.
|
|
43
|
+
* Executes any generated JavaScript created by the {@link SharedTreeChatModel.editToolName | model's editing tool}.
|
|
44
|
+
* @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.
|
|
45
|
+
* @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.
|
|
40
46
|
* Use a library such as SES to provide a more secure implementation - see `@fluidframework/tree-agent-ses` for a drop-in implementation.
|
|
47
|
+
*
|
|
48
|
+
* 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.
|
|
41
49
|
*/
|
|
42
|
-
|
|
50
|
+
editor?: SynchronousEditor | AsynchronousEditor;
|
|
43
51
|
/**
|
|
44
52
|
* The maximum number of sequential edits the LLM can make before we assume it's stuck in a loop.
|
|
45
53
|
*/
|
|
@@ -59,12 +67,11 @@ export interface EditResult {
|
|
|
59
67
|
* @remarks
|
|
60
68
|
* - `success`: The edit was successfully applied.
|
|
61
69
|
* - `disabledError`: The model is not allowed to edit the tree (i.e. {@link SharedTreeChatModel.editToolName} was not provided).
|
|
62
|
-
* - `
|
|
63
|
-
* - `executionError`: An error was thrown while parsing or executing the provided JavaScript.
|
|
70
|
+
* - `editingError`: An error was thrown while parsing or executing the provided JavaScript.
|
|
64
71
|
* - `tooManyEditsError`: The {@link SharedTreeChatQuery.edit} function has been called more than the number of times specified by {@link SemanticAgentOptions.maximumSequentialEdits} for the same message.
|
|
65
72
|
* - `expiredError`: The {@link SharedTreeChatQuery.edit} function was called after the issuing query has already completed.
|
|
66
73
|
*/
|
|
67
|
-
type: "success" | "disabledError" | "
|
|
74
|
+
type: "success" | "disabledError" | "editingError" | "tooManyEditsError" | "expiredError";
|
|
68
75
|
/**
|
|
69
76
|
* A human-readable message describing the result of the edit attempt.
|
|
70
77
|
* @remarks In the case of an error, this message is appropriate to include in a model's chat history.
|
package/lib/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAE1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAE1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAMtF;;;GAGG;AACH,MAAM,WAAW,MAAM;IACtB;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AACzF;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,IAAI,EAAE,MAAM,KACR,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACpC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,iBAAiB,GAAG,kBAAkB,CAAC;IAChD;;OAEG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IAC1B;;;;;;;;OAQG;IACH,IAAI,EAAE,SAAS,GAAG,eAAe,GAAG,cAAc,GAAG,mBAAmB,GAAG,cAAc,CAAC;IAE1F;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAQhE;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACtC;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,aAAa,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrD;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,CAAC,OAAO,SAAS,mBAAmB,IAAI,CAAC,EAChE,IAAI,EACJ,MAAM,GACN,EAAE;IACF,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,QAAQ,CAAC,CAAC;CAClE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
package/lib/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA4FH;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IAC1C,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACjD,OAAO,KAAK,CAAC;IACd,CAAC;IACD,OAAO,CACN,OAAQ,KAAoB,CAAC,IAAI,KAAK,QAAQ;QAC9C,OAAQ,KAAoB,CAAC,OAAO,KAAK,QAAQ,CACjD,CAAC;AACH,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { ImplicitFieldSchema, TreeNode } from \"@fluidframework/tree\";\n// These are used for doc links\nimport type { FactoryContentObject, ReadableField } from \"@fluidframework/tree/alpha\";\n\n// This is used for doc links\n// eslint-disable-next-line unused-imports/no-unused-imports\nimport type { bindEditor, defaultEditor } from \"./agent.js\";\n\n/**\n * Logger interface for logging events from a {@link SharedTreeSemanticAgent}.\n * @alpha\n */\nexport interface Logger {\n\t/**\n\t * Log a message.\n\t */\n\tlog(message: string): void;\n}\n\n/**\n * A synchronous function that executes a string of JavaScript code to perform an edit within a {@link SharedTreeSemanticAgent}.\n * @param context - An object that must be provided to the generated code as a variable named \"context\" in its top-level scope.\n * @param code - The JavaScript code that should be executed.\n * @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.\n * @alpha\n */\nexport type SynchronousEditor = (context: Record<string, unknown>, code: string) => void;\n/**\n * An asynchronous function that executes a string of JavaScript code to perform an edit within a {@link SharedTreeSemanticAgent}.\n * @param context - An object that must be provided to the generated code as a variable named \"context\" in its top-level scope.\n * @param code - The JavaScript code that should be executed.\n * @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.\n * @alpha\n */\nexport type AsynchronousEditor = (\n\tcontext: Record<string, unknown>,\n\tcode: string,\n) => Promise<void>;\n\n/**\n * Options used to parameterize the creation of a {@link SharedTreeSemanticAgent}.\n * @alpha\n */\nexport interface SemanticAgentOptions {\n\t/**\n\t * Additional information about the application domain that will be included in the context provided to the {@link SharedTreeChatModel | model}.\n\t */\n\tdomainHints?: string;\n\t/**\n\t * Executes any generated JavaScript created by the {@link SharedTreeChatModel.editToolName | model's editing tool}.\n\t * @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.\n\t * @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.\n\t * Use a library such as SES to provide a more secure implementation - see `@fluidframework/tree-agent-ses` for a drop-in implementation.\n\t *\n\t * 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.\n\t */\n\teditor?: SynchronousEditor | AsynchronousEditor;\n\t/**\n\t * The maximum number of sequential edits the LLM can make before we assume it's stuck in a loop.\n\t */\n\tmaximumSequentialEdits?: number;\n\t/**\n\t * If supplied, generates human-readable markdown text describing the actions taken by the {@link SharedTreeSemanticAgent | agent} as it performs queries.\n\t */\n\tlogger?: Logger;\n}\n\n/**\n * A result from an edit attempt via the {@link SharedTreeChatQuery.edit} function.\n * @alpha\n */\nexport interface EditResult {\n\t/**\n\t * The type of the edit result.\n\t * @remarks\n\t * - `success`: The edit was successfully applied.\n\t * - `disabledError`: The model is not allowed to edit the tree (i.e. {@link SharedTreeChatModel.editToolName} was not provided).\n\t * - `editingError`: An error was thrown while parsing or executing the provided JavaScript.\n\t * - `tooManyEditsError`: The {@link SharedTreeChatQuery.edit} function has been called more than the number of times specified by {@link SemanticAgentOptions.maximumSequentialEdits} for the same message.\n\t * - `expiredError`: The {@link SharedTreeChatQuery.edit} function was called after the issuing query has already completed.\n\t */\n\ttype: \"success\" | \"disabledError\" | \"editingError\" | \"tooManyEditsError\" | \"expiredError\";\n\n\t/**\n\t * A human-readable message describing the result of the edit attempt.\n\t * @remarks In the case of an error, this message is appropriate to include in a model's chat history.\n\t */\n\tmessage: string;\n}\n\n/**\n * Type guard for {@link EditResult}.\n */\nexport function isEditResult(value: unknown): value is EditResult {\n\tif (value === null || typeof value !== \"object\") {\n\t\treturn false;\n\t}\n\treturn (\n\t\ttypeof (value as EditResult).type === \"string\" &&\n\t\ttypeof (value as EditResult).message === \"string\"\n\t);\n}\n\n/**\n * A query from a user to a {@link SharedTreeSemanticAgent}.\n * @remarks Processing a query may involve editing the SharedTree via the provided {@link SharedTreeChatQuery.edit} function.\n * @alpha\n */\nexport interface SharedTreeChatQuery {\n\t/**\n\t * The user's query.\n\t */\n\ttext: string;\n\t/**\n\t * Edit the tree with the provided JavaScript function code.\n\t * @remarks Attempting an edit may fail for a variety of reasons which are captured in the {@link EditResult | returned object}.\n\t * If an edit fails, the tree will not be modified and the model may attempt another edit if desired.\n\t * When the query ends, if the last edit attempt was successful, all edits made during the query will be merged into the agent's SharedTree.\n\t * Otherwise, all edits made during the query will be discarded.\n\t */\n\tedit(js: string): Promise<EditResult>;\n}\n\n/**\n * A plugin interface that handles queries from a {@link SharedTreeSemanticAgent}.\n * @remarks This wraps an underlying communication with an LLM and receives all necessary {@link SharedTreeChatModel.appendContext | context} from the {@link SharedTreeSemanticAgent | agent} for the LLM to properly analyze and edit the tree.\n * See `@fluidframework/tree-agent-langchain` for a drop-in implementation based on the LangChain library.\n * @alpha\n */\nexport interface SharedTreeChatModel {\n\t/**\n\t * A optional name of this chat model.\n\t * @remarks If supplied, this may be used in logging or debugging information.\n\t * @example \"gpt-5\"\n\t */\n\tname?: string;\n\t/**\n\t * The name of the tool that the model should use to edit the tree.\n\t * @remarks If supplied, this will be mentioned in the context provided to the model so that the underlying LLM will be encouraged to use it when a user query requires an edit.\n\t * The model should \"implement\" the tool by registering it with the underlying LLM API.\n\t * The tool should take an LLM-generated JavaScript function as input and supply it to the {@link SharedTreeChatQuery.edit | edit} function.\n\t * Instructions for generating the proper function signature and implementation will be provided by the {@link SharedTreeSemanticAgent | agent} via {@link SharedTreeChatModel.appendContext | context}.\n\t * If not supplied, the model will not be able to edit the tree (running the {@link SharedTreeChatQuery.edit | edit} function will fail).\n\t */\n\teditToolName?: string;\n\t/**\n\t * Add contextual information to the model that may be relevant to future queries.\n\t * @remarks In practice, this may be implemented by e.g. appending a \"system\" message to an LLM's chat/message history.\n\t * This context must be present in the context window of every {@link SharedTreeChatModel.query | query} for e.g. {@link SharedTreeChatModel.editToolName | editing} to work.\n\t * @param text - The message or context to append.\n\t */\n\tappendContext?(text: string): void;\n\t/**\n\t * Queries the chat model with a request from the user.\n\t * @remarks This model may simply return a text response to the query, or it may first call the {@link SharedTreeChatQuery.edit} function (potentially multiple times) to modify the tree in response to the query.\n\t */\n\tquery(message: SharedTreeChatQuery): Promise<string>;\n}\n\n/**\n * A function that edits a SharedTree.\n */\nexport type EditFunction<TSchema extends ImplicitFieldSchema> = ({\n\troot,\n\tcreate,\n}: {\n\troot: ReadableField<TSchema>;\n\tcreate: Record<string, (input: FactoryContentObject) => TreeNode>;\n}) => void | Promise<void>;\n"]}
|
package/lib/index.d.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
|
-
export { SharedTreeSemanticAgent } from "./agent.js";
|
|
11
|
-
export type { EditResult, SharedTreeChatModel, SharedTreeChatQuery, Logger, SemanticAgentOptions, } from "./api.js";
|
|
10
|
+
export { SharedTreeSemanticAgent, bindEditor, bindEditorImpl, defaultEditor, } from "./agent.js";
|
|
11
|
+
export type { EditResult, SharedTreeChatModel, SharedTreeChatQuery, Logger, SemanticAgentOptions, SynchronousEditor, AsynchronousEditor, } from "./api.js";
|
|
12
12
|
export { type TreeView, llmDefault } from "./utils.js";
|
|
13
13
|
export { buildFunc, exposeMethodsSymbol, type ArgsTuple, type ExposedMethods, type Arg, type FunctionDef, type MethodKeys, type BindableSchema, type Ctor, type Infer, type IExposedMethods, } from "./methodBinding.js";
|
|
14
14
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AAEH,OAAO,EACN,uBAAuB,EACvB,UAAU,EACV,cAAc,EACd,aAAa,GACb,MAAM,YAAY,CAAC;AACpB,YAAY,EACX,UAAU,EACV,mBAAmB,EACnB,mBAAmB,EACnB,MAAM,EACN,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,KAAK,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EACN,SAAS,EACT,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,GAAG,EACR,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,IAAI,EACT,KAAK,KAAK,EACV,KAAK,eAAe,GACpB,MAAM,oBAAoB,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
|
-
export { SharedTreeSemanticAgent } from "./agent.js";
|
|
10
|
+
export { SharedTreeSemanticAgent, bindEditor, bindEditorImpl, defaultEditor, } from "./agent.js";
|
|
11
11
|
export { llmDefault } from "./utils.js";
|
|
12
12
|
export { buildFunc, exposeMethodsSymbol, } from "./methodBinding.js";
|
|
13
13
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AAEH,OAAO,EACN,uBAAuB,EACvB,UAAU,EACV,cAAc,EACd,aAAa,GACb,MAAM,YAAY,CAAC;AAUpB,OAAO,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EACN,SAAS,EACT,mBAAmB,GAUnB,MAAM,oBAAoB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n/**\n * A library for creating AI agents to interact with a {@link SharedTree | https://fluidframework.com/docs/data-structures/tree/}.\n *\n * @packageDocumentation\n */\n\nexport {\n\tSharedTreeSemanticAgent,\n\tbindEditor,\n\tbindEditorImpl,\n\tdefaultEditor,\n} from \"./agent.js\";\nexport type {\n\tEditResult,\n\tSharedTreeChatModel,\n\tSharedTreeChatQuery,\n\tLogger,\n\tSemanticAgentOptions,\n\tSynchronousEditor,\n\tAsynchronousEditor,\n} from \"./api.js\";\nexport { type TreeView, llmDefault } from \"./utils.js\";\nexport {\n\tbuildFunc,\n\texposeMethodsSymbol,\n\ttype ArgsTuple,\n\ttype ExposedMethods,\n\ttype Arg,\n\ttype FunctionDef,\n\ttype MethodKeys,\n\ttype BindableSchema,\n\ttype Ctor,\n\ttype Infer,\n\ttype IExposedMethods,\n} from \"./methodBinding.js\";\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/tree-agent",
|
|
3
|
-
"version": "2.70.0-
|
|
3
|
+
"version": "2.70.0-360753",
|
|
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-
|
|
74
|
-
"@fluidframework/runtime-utils": "2.70.0-
|
|
75
|
-
"@fluidframework/telemetry-utils": "2.70.0-
|
|
76
|
-
"@fluidframework/tree": "2.70.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",
|
|
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-
|
|
83
|
+
"@fluid-internal/mocha-test-setup": "2.70.0-360753",
|
|
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
87
|
"@fluidframework/eslint-config-fluid": "^6.0.0",
|
|
88
|
-
"@fluidframework/id-compressor": "2.70.0-
|
|
89
|
-
"@fluidframework/runtime-utils": "2.70.0-
|
|
90
|
-
"@fluidframework/test-runtime-utils": "2.70.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",
|
|
91
91
|
"@langchain/anthropic": "^0.3.24",
|
|
92
92
|
"@langchain/core": "^0.3.78",
|
|
93
93
|
"@langchain/google-genai": "^0.2.16",
|
|
@@ -122,9 +122,7 @@
|
|
|
122
122
|
}
|
|
123
123
|
},
|
|
124
124
|
"typeValidation": {
|
|
125
|
-
"disabled": true
|
|
126
|
-
"broken": {},
|
|
127
|
-
"entrypoint": "internal"
|
|
125
|
+
"disabled": true
|
|
128
126
|
},
|
|
129
127
|
"scripts": {
|
|
130
128
|
"api": "fluid-build . --task api",
|
package/src/agent.ts
CHANGED
|
@@ -17,7 +17,14 @@ 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
|
+
} from "./api.js";
|
|
21
28
|
import { getPrompt, stringifyTree } from "./prompt.js";
|
|
22
29
|
import { Subtree } from "./subtree.js";
|
|
23
30
|
import {
|
|
@@ -137,8 +144,7 @@ export class SharedTreeSemanticAgent<TSchema extends ImplicitFieldSchema> {
|
|
|
137
144
|
const editResult = await applyTreeFunction(
|
|
138
145
|
queryTree,
|
|
139
146
|
editCode,
|
|
140
|
-
this.options?.
|
|
141
|
-
this.options?.executeEdit ?? defaultExecuteEdit,
|
|
147
|
+
this.options?.editor ?? defaultEditor,
|
|
142
148
|
this.options?.logger,
|
|
143
149
|
);
|
|
144
150
|
|
|
@@ -199,51 +205,23 @@ function constructTreeNode(schema: TreeNodeSchema, value: FactoryContentObject):
|
|
|
199
205
|
async function applyTreeFunction<TSchema extends ImplicitFieldSchema>(
|
|
200
206
|
tree: Subtree<TSchema>,
|
|
201
207
|
editCode: string,
|
|
202
|
-
|
|
203
|
-
executeEdit: Required<SemanticAgentOptions>["executeEdit"],
|
|
208
|
+
editor: Required<SemanticAgentOptions>["editor"],
|
|
204
209
|
logger: Logger | undefined,
|
|
205
210
|
): Promise<EditResult> {
|
|
206
211
|
logger?.log(`### Editing Tool Invoked\n\n`);
|
|
207
212
|
logger?.log(`#### Generated Code\n\n\`\`\`javascript\n${editCode}\n\`\`\`\n\n`);
|
|
208
213
|
|
|
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
214
|
// Fork a branch to edit. If the edit fails or produces an error, we discard this branch, otherwise we merge it.
|
|
228
215
|
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
|
-
|
|
216
|
+
const boundEditor = bindEditorToSubtree(editTree, editor);
|
|
239
217
|
try {
|
|
240
|
-
await
|
|
218
|
+
await boundEditor(editCode);
|
|
241
219
|
} catch (error: unknown) {
|
|
242
220
|
logger?.log(`#### Error\n\n`);
|
|
243
221
|
logger?.log(`\`\`\`JSON\n${toErrorString(error)}\n\`\`\`\n\n`);
|
|
244
222
|
editTree.branch.dispose();
|
|
245
223
|
return {
|
|
246
|
-
type: "
|
|
224
|
+
type: "editingError",
|
|
247
225
|
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
226
|
};
|
|
249
227
|
}
|
|
@@ -257,13 +235,82 @@ async function applyTreeFunction<TSchema extends ImplicitFieldSchema>(
|
|
|
257
235
|
};
|
|
258
236
|
}
|
|
259
237
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
238
|
+
/**
|
|
239
|
+
* The default {@link AsynchronousEditor | editor} implementation that simply uses `new Function` to run the provided code.
|
|
240
|
+
* @remarks This editor allows both synchronous and asynchronous code (i.e. the provided code may return a Promise).
|
|
241
|
+
* @example `await new Function("context", code)(context);`
|
|
242
|
+
* @alpha
|
|
243
|
+
*/
|
|
244
|
+
export const defaultEditor: AsynchronousEditor = async (context, code) => {
|
|
266
245
|
// eslint-disable-next-line no-new-func, @typescript-eslint/no-implied-eval
|
|
267
246
|
const fn = new Function("context", code);
|
|
268
247
|
await fn(context);
|
|
269
248
|
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Binds the given {@link SynchronousEditor | editor} to the given view or tree.
|
|
252
|
+
* @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
|
+
* @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.
|
|
254
|
+
* @alpha
|
|
255
|
+
*/
|
|
256
|
+
export function bindEditorImpl<TSchema extends ImplicitFieldSchema>(
|
|
257
|
+
tree: TreeView<TSchema> | (ReadableField<TSchema> & TreeNode),
|
|
258
|
+
editor: SynchronousEditor,
|
|
259
|
+
): (code: string) => void;
|
|
260
|
+
/**
|
|
261
|
+
* Binds the given {@link AsynchronousEditor | editor} to the given view or tree.
|
|
262
|
+
* @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
|
+
* @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.
|
|
264
|
+
* @alpha
|
|
265
|
+
*/
|
|
266
|
+
export function bindEditorImpl<TSchema extends ImplicitFieldSchema>(
|
|
267
|
+
tree: TreeView<TSchema> | (ReadableField<TSchema> & TreeNode),
|
|
268
|
+
editor: AsynchronousEditor,
|
|
269
|
+
): (code: string) => Promise<void>;
|
|
270
|
+
/**
|
|
271
|
+
* Binds the given {@link SynchronousEditor | editor} or {@link AsynchronousEditor | editor} to the given view or tree.
|
|
272
|
+
* @returns A function that takes a string of JavaScript code and executes it on the given view or tree using the given editor function.
|
|
273
|
+
* @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.
|
|
274
|
+
* @alpha
|
|
275
|
+
*/
|
|
276
|
+
export function bindEditorImpl<TSchema extends ImplicitFieldSchema>(
|
|
277
|
+
tree: TreeView<TSchema> | (ReadableField<TSchema> & TreeNode),
|
|
278
|
+
editor: SynchronousEditor | AsynchronousEditor,
|
|
279
|
+
): ((code: string) => void) | ((code: string) => Promise<void>) {
|
|
280
|
+
const subtree = new Subtree(tree);
|
|
281
|
+
return bindEditorToSubtree(subtree, editor);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Binds the given {@link SynchronousEditor | synchronous} or {@link AsynchronousEditor | asynchronous} editor to the given view or tree.
|
|
286
|
+
* @returns A function that takes a string of JavaScript code and executes it on the given view or tree using the given editor.
|
|
287
|
+
* @remarks This is useful for testing/debugging code execution without needing to set up a full {@link SharedTreeSemanticAgent | agent}.
|
|
288
|
+
* @alpha
|
|
289
|
+
* @privateRemarks This exists (as opposed to just exporting bindEditorImpl directly) so that API documentation links work correctly.
|
|
290
|
+
*/
|
|
291
|
+
export const bindEditor = bindEditorImpl;
|
|
292
|
+
|
|
293
|
+
function bindEditorToSubtree<TSchema extends ImplicitFieldSchema>(
|
|
294
|
+
tree: Subtree<TSchema>,
|
|
295
|
+
executeEdit: SynchronousEditor | AsynchronousEditor,
|
|
296
|
+
): (code: string) => void | Promise<void> {
|
|
297
|
+
// Stick the tree schema constructors on an object passed to the function so that the LLM can create new nodes.
|
|
298
|
+
const create: Record<string, (input: FactoryContentObject) => TreeNode> = {};
|
|
299
|
+
for (const schema of findNamedSchemas(tree.schema)) {
|
|
300
|
+
const name = getFriendlyName(schema);
|
|
301
|
+
create[name] = (input: FactoryContentObject) => constructTreeNode(schema, input);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const context = {
|
|
305
|
+
get root(): ReadableField<TSchema> {
|
|
306
|
+
return tree.field;
|
|
307
|
+
},
|
|
308
|
+
set root(value: TreeFieldFromImplicitField<ReadSchema<TSchema>>) {
|
|
309
|
+
tree.field = value;
|
|
310
|
+
},
|
|
311
|
+
create,
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
315
|
+
return (code: string) => executeEdit(context, code);
|
|
316
|
+
}
|