@fluidframework/ai-collab 2.10.0-306579

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/.eslintrc.cjs +26 -0
  2. package/CHANGELOG.md +9 -0
  3. package/LICENSE +21 -0
  4. package/README.md +280 -0
  5. package/alpha.d.ts +11 -0
  6. package/api-extractor/api-extractor-lint-alpha.cjs.json +5 -0
  7. package/api-extractor/api-extractor-lint-alpha.esm.json +5 -0
  8. package/api-extractor/api-extractor-lint-bundle.json +5 -0
  9. package/api-extractor/api-extractor-lint-index.cjs.json +5 -0
  10. package/api-extractor/api-extractor-lint-index.esm.json +5 -0
  11. package/api-extractor/api-extractor-lint-public.cjs.json +5 -0
  12. package/api-extractor/api-extractor-lint-public.esm.json +5 -0
  13. package/api-extractor-lint.json +4 -0
  14. package/api-extractor.json +4 -0
  15. package/api-report/ai-collab.alpha.api.md +164 -0
  16. package/api-report/ai-collab.beta.api.md +7 -0
  17. package/api-report/ai-collab.public.api.md +7 -0
  18. package/biome.jsonc +4 -0
  19. package/dist/aiCollab.d.ts +65 -0
  20. package/dist/aiCollab.d.ts.map +1 -0
  21. package/dist/aiCollab.js +81 -0
  22. package/dist/aiCollab.js.map +1 -0
  23. package/dist/aiCollabApi.d.ts +173 -0
  24. package/dist/aiCollabApi.d.ts.map +1 -0
  25. package/dist/aiCollabApi.js +7 -0
  26. package/dist/aiCollabApi.js.map +1 -0
  27. package/dist/alpha.d.ts +41 -0
  28. package/dist/explicit-strategy/agentEditReducer.d.ts +12 -0
  29. package/dist/explicit-strategy/agentEditReducer.d.ts.map +1 -0
  30. package/dist/explicit-strategy/agentEditReducer.js +394 -0
  31. package/dist/explicit-strategy/agentEditReducer.js.map +1 -0
  32. package/dist/explicit-strategy/agentEditTypes.d.ts +158 -0
  33. package/dist/explicit-strategy/agentEditTypes.d.ts.map +1 -0
  34. package/dist/explicit-strategy/agentEditTypes.js +50 -0
  35. package/dist/explicit-strategy/agentEditTypes.js.map +1 -0
  36. package/dist/explicit-strategy/idGenerator.d.ts +22 -0
  37. package/dist/explicit-strategy/idGenerator.d.ts.map +1 -0
  38. package/dist/explicit-strategy/idGenerator.js +74 -0
  39. package/dist/explicit-strategy/idGenerator.js.map +1 -0
  40. package/dist/explicit-strategy/index.d.ts +51 -0
  41. package/dist/explicit-strategy/index.d.ts.map +1 -0
  42. package/dist/explicit-strategy/index.js +223 -0
  43. package/dist/explicit-strategy/index.js.map +1 -0
  44. package/dist/explicit-strategy/jsonTypes.d.ts +23 -0
  45. package/dist/explicit-strategy/jsonTypes.d.ts.map +1 -0
  46. package/dist/explicit-strategy/jsonTypes.js +7 -0
  47. package/dist/explicit-strategy/jsonTypes.js.map +1 -0
  48. package/dist/explicit-strategy/promptGeneration.d.ts +51 -0
  49. package/dist/explicit-strategy/promptGeneration.d.ts.map +1 -0
  50. package/dist/explicit-strategy/promptGeneration.js +218 -0
  51. package/dist/explicit-strategy/promptGeneration.js.map +1 -0
  52. package/dist/explicit-strategy/typeGeneration.d.ts +15 -0
  53. package/dist/explicit-strategy/typeGeneration.d.ts.map +1 -0
  54. package/dist/explicit-strategy/typeGeneration.js +264 -0
  55. package/dist/explicit-strategy/typeGeneration.js.map +1 -0
  56. package/dist/explicit-strategy/utils.d.ts +37 -0
  57. package/dist/explicit-strategy/utils.d.ts.map +1 -0
  58. package/dist/explicit-strategy/utils.js +47 -0
  59. package/dist/explicit-strategy/utils.js.map +1 -0
  60. package/dist/implicit-strategy/index.d.ts +8 -0
  61. package/dist/implicit-strategy/index.d.ts.map +1 -0
  62. package/dist/implicit-strategy/index.js +18 -0
  63. package/dist/implicit-strategy/index.js.map +1 -0
  64. package/dist/implicit-strategy/sharedTreeBranchManager.d.ts +63 -0
  65. package/dist/implicit-strategy/sharedTreeBranchManager.d.ts.map +1 -0
  66. package/dist/implicit-strategy/sharedTreeBranchManager.js +212 -0
  67. package/dist/implicit-strategy/sharedTreeBranchManager.js.map +1 -0
  68. package/dist/implicit-strategy/sharedTreeDiff.d.ts +102 -0
  69. package/dist/implicit-strategy/sharedTreeDiff.d.ts.map +1 -0
  70. package/dist/implicit-strategy/sharedTreeDiff.js +522 -0
  71. package/dist/implicit-strategy/sharedTreeDiff.js.map +1 -0
  72. package/dist/implicit-strategy/utils.d.ts +21 -0
  73. package/dist/implicit-strategy/utils.d.ts.map +1 -0
  74. package/dist/implicit-strategy/utils.js +49 -0
  75. package/dist/implicit-strategy/utils.js.map +1 -0
  76. package/dist/index.d.ts +16 -0
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/index.js +24 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/package.json +3 -0
  81. package/dist/public.d.ts +19 -0
  82. package/eslintrc.cjs +11 -0
  83. package/internal.d.ts +11 -0
  84. package/lib/aiCollab.d.ts +65 -0
  85. package/lib/aiCollab.d.ts.map +1 -0
  86. package/lib/aiCollab.js +77 -0
  87. package/lib/aiCollab.js.map +1 -0
  88. package/lib/aiCollabApi.d.ts +173 -0
  89. package/lib/aiCollabApi.d.ts.map +1 -0
  90. package/lib/aiCollabApi.js +6 -0
  91. package/lib/aiCollabApi.js.map +1 -0
  92. package/lib/alpha.d.ts +41 -0
  93. package/lib/explicit-strategy/agentEditReducer.d.ts +12 -0
  94. package/lib/explicit-strategy/agentEditReducer.d.ts.map +1 -0
  95. package/lib/explicit-strategy/agentEditReducer.js +390 -0
  96. package/lib/explicit-strategy/agentEditReducer.js.map +1 -0
  97. package/lib/explicit-strategy/agentEditTypes.d.ts +158 -0
  98. package/lib/explicit-strategy/agentEditTypes.d.ts.map +1 -0
  99. package/lib/explicit-strategy/agentEditTypes.js +47 -0
  100. package/lib/explicit-strategy/agentEditTypes.js.map +1 -0
  101. package/lib/explicit-strategy/idGenerator.d.ts +22 -0
  102. package/lib/explicit-strategy/idGenerator.d.ts.map +1 -0
  103. package/lib/explicit-strategy/idGenerator.js +70 -0
  104. package/lib/explicit-strategy/idGenerator.js.map +1 -0
  105. package/lib/explicit-strategy/index.d.ts +51 -0
  106. package/lib/explicit-strategy/index.d.ts.map +1 -0
  107. package/lib/explicit-strategy/index.js +219 -0
  108. package/lib/explicit-strategy/index.js.map +1 -0
  109. package/lib/explicit-strategy/jsonTypes.d.ts +23 -0
  110. package/lib/explicit-strategy/jsonTypes.d.ts.map +1 -0
  111. package/lib/explicit-strategy/jsonTypes.js +6 -0
  112. package/lib/explicit-strategy/jsonTypes.js.map +1 -0
  113. package/lib/explicit-strategy/promptGeneration.d.ts +51 -0
  114. package/lib/explicit-strategy/promptGeneration.d.ts.map +1 -0
  115. package/lib/explicit-strategy/promptGeneration.js +208 -0
  116. package/lib/explicit-strategy/promptGeneration.js.map +1 -0
  117. package/lib/explicit-strategy/typeGeneration.d.ts +15 -0
  118. package/lib/explicit-strategy/typeGeneration.d.ts.map +1 -0
  119. package/lib/explicit-strategy/typeGeneration.js +260 -0
  120. package/lib/explicit-strategy/typeGeneration.js.map +1 -0
  121. package/lib/explicit-strategy/utils.d.ts +37 -0
  122. package/lib/explicit-strategy/utils.d.ts.map +1 -0
  123. package/lib/explicit-strategy/utils.js +41 -0
  124. package/lib/explicit-strategy/utils.js.map +1 -0
  125. package/lib/implicit-strategy/index.d.ts +8 -0
  126. package/lib/implicit-strategy/index.d.ts.map +1 -0
  127. package/lib/implicit-strategy/index.js +8 -0
  128. package/lib/implicit-strategy/index.js.map +1 -0
  129. package/lib/implicit-strategy/sharedTreeBranchManager.d.ts +63 -0
  130. package/lib/implicit-strategy/sharedTreeBranchManager.d.ts.map +1 -0
  131. package/lib/implicit-strategy/sharedTreeBranchManager.js +213 -0
  132. package/lib/implicit-strategy/sharedTreeBranchManager.js.map +1 -0
  133. package/lib/implicit-strategy/sharedTreeDiff.d.ts +102 -0
  134. package/lib/implicit-strategy/sharedTreeDiff.d.ts.map +1 -0
  135. package/lib/implicit-strategy/sharedTreeDiff.js +515 -0
  136. package/lib/implicit-strategy/sharedTreeDiff.js.map +1 -0
  137. package/lib/implicit-strategy/utils.d.ts +21 -0
  138. package/lib/implicit-strategy/utils.d.ts.map +1 -0
  139. package/lib/implicit-strategy/utils.js +43 -0
  140. package/lib/implicit-strategy/utils.js.map +1 -0
  141. package/lib/index.d.ts +16 -0
  142. package/lib/index.d.ts.map +1 -0
  143. package/lib/index.js +15 -0
  144. package/lib/index.js.map +1 -0
  145. package/lib/public.d.ts +19 -0
  146. package/lib/tsdoc-metadata.json +11 -0
  147. package/mocharc.cjs +14 -0
  148. package/package.json +165 -0
  149. package/prettier.config.cjs +8 -0
  150. package/src/aiCollab.ts +86 -0
  151. package/src/aiCollabApi.ts +184 -0
  152. package/src/explicit-strategy/agentEditReducer.ts +498 -0
  153. package/src/explicit-strategy/agentEditTypes.ts +177 -0
  154. package/src/explicit-strategy/idGenerator.ts +90 -0
  155. package/src/explicit-strategy/index.ts +364 -0
  156. package/src/explicit-strategy/jsonTypes.ts +27 -0
  157. package/src/explicit-strategy/promptGeneration.ts +294 -0
  158. package/src/explicit-strategy/typeGeneration.ts +374 -0
  159. package/src/explicit-strategy/utils.ts +60 -0
  160. package/src/implicit-strategy/README.md +4 -0
  161. package/src/implicit-strategy/index.ts +21 -0
  162. package/src/implicit-strategy/sharedTreeBranchManager.ts +294 -0
  163. package/src/implicit-strategy/sharedTreeDiff.ts +735 -0
  164. package/src/implicit-strategy/utils.ts +54 -0
  165. package/src/index.ts +39 -0
  166. package/tsconfig.cjs.json +7 -0
  167. package/tsconfig.json +12 -0
  168. package/tsdoc.json +4 -0
@@ -0,0 +1,90 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { assert, oob } from "@fluidframework/core-utils/internal";
7
+ import { Tree, NodeKind } from "@fluidframework/tree/internal";
8
+ import type {
9
+ TreeNode,
10
+ ImplicitFieldSchema,
11
+ TreeArrayNode,
12
+ TreeFieldFromImplicitField,
13
+ } from "@fluidframework/tree/internal";
14
+
15
+ /**
16
+ * Given a tree, generates a set of LLM-friendly, unique IDs for each node in the tree.
17
+ * @remarks The ability to uniquely and stably in the tree is important for the LLM and this library to create and distinguish between different types certain {@link TreeEdit}s.
18
+ */
19
+ export class IdGenerator {
20
+ private readonly idCountMap = new Map<string, number>();
21
+ private readonly prefixMap = new Map<string, string>();
22
+ private readonly nodeToIdMap = new Map<TreeNode, string>();
23
+ private readonly idToNodeMap = new Map<string, TreeNode>();
24
+
25
+ public constructor() {}
26
+
27
+ public getOrCreateId(node: TreeNode): string {
28
+ const existingID = this.nodeToIdMap.get(node);
29
+ if (existingID !== undefined) {
30
+ return existingID;
31
+ }
32
+
33
+ const schema = Tree.schema(node).identifier;
34
+ const id = this.generateID(schema);
35
+ this.nodeToIdMap.set(node, id);
36
+ this.idToNodeMap.set(id, node);
37
+
38
+ return id;
39
+ }
40
+
41
+ public getNode(id: string): TreeNode | undefined {
42
+ return this.idToNodeMap.get(id);
43
+ }
44
+
45
+ public getId(node: TreeNode): string | undefined {
46
+ return this.nodeToIdMap.get(node);
47
+ }
48
+
49
+ public assignIds(node: TreeFieldFromImplicitField<ImplicitFieldSchema>): string | undefined {
50
+ if (typeof node === "object" && node !== null) {
51
+ const schema = Tree.schema(node as unknown as TreeNode);
52
+ if (schema.kind === NodeKind.Array) {
53
+ for (const element of node as unknown as TreeArrayNode) {
54
+ this.assignIds(element);
55
+ }
56
+ } else {
57
+ // TODO: SharedTree Team needs to either publish TreeNode as a class to use .instanceof() or a typeguard.
58
+ // Uncomment this assertion back once we have a typeguard ready.
59
+ // assert(isTreeNode(node), "Non-TreeNode value in tree.");
60
+ const objId = this.getOrCreateId(node as TreeNode);
61
+ for (const key of Object.keys(node)) {
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
63
+ this.assignIds((node as unknown as any)[key]);
64
+ }
65
+ return objId;
66
+ }
67
+ }
68
+ return undefined;
69
+ }
70
+
71
+ private generateID(schema: string): string {
72
+ const segments = schema.split(".");
73
+
74
+ // If there's no period, the schema itself is the last segment
75
+ const lastSegment = segments[segments.length - 1] ?? oob();
76
+ const prefix = segments.length > 1 ? segments.slice(0, -1).join(".") : "";
77
+
78
+ // Check if the last segment already exists with a different prefix
79
+ assert(
80
+ !this.prefixMap.has(lastSegment) || this.prefixMap.get(lastSegment) === prefix,
81
+ "Different scopes not supported yet.",
82
+ );
83
+
84
+ this.prefixMap.set(lastSegment, prefix);
85
+ const count = this.idCountMap.get(lastSegment) ?? 1;
86
+ this.idCountMap.set(lastSegment, count + 1);
87
+
88
+ return `${lastSegment}${count}`;
89
+ }
90
+ }
@@ -0,0 +1,364 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import {
7
+ getSimpleSchema,
8
+ Tree,
9
+ type SimpleTreeSchema,
10
+ type TreeNode,
11
+ } from "@fluidframework/tree/internal";
12
+ // eslint-disable-next-line import/no-internal-modules
13
+ import { zodResponseFormat } from "openai/helpers/zod";
14
+ import type {
15
+ ChatCompletionCreateParams,
16
+ // eslint-disable-next-line import/no-internal-modules
17
+ } from "openai/resources/index.mjs";
18
+ import { z } from "zod";
19
+
20
+ import type { OpenAiClientOptions, TokenLimits, TokenUsage } from "../aiCollabApi.js";
21
+
22
+ import { applyAgentEdit } from "./agentEditReducer.js";
23
+ import type { EditWrapper, TreeEdit } from "./agentEditTypes.js";
24
+ import { IdGenerator } from "./idGenerator.js";
25
+ import {
26
+ getEditingSystemPrompt,
27
+ getPlanningSystemPrompt,
28
+ getReviewSystemPrompt,
29
+ toDecoratedJson,
30
+ type EditLog,
31
+ } from "./promptGeneration.js";
32
+ import { generateGenericEditTypes } from "./typeGeneration.js";
33
+ import { fail } from "./utils.js";
34
+
35
+ const DEBUG_LOG: string[] = [];
36
+
37
+ /**
38
+ * {@link generateTreeEdits} options.
39
+ *
40
+ * @internal
41
+ */
42
+ export interface GenerateTreeEditsOptions {
43
+ openAI: OpenAiClientOptions;
44
+ treeNode: TreeNode;
45
+ prompt: {
46
+ systemRoleContext: string;
47
+ userAsk: string;
48
+ };
49
+ limiters?: {
50
+ abortController?: AbortController;
51
+ maxSequentialErrors?: number;
52
+ maxModelCalls?: number;
53
+ tokenLimits?: TokenLimits;
54
+ };
55
+ finalReviewStep?: boolean;
56
+ validator?: (newContent: TreeNode) => void;
57
+ dumpDebugLog?: boolean;
58
+ planningStep?: boolean;
59
+ }
60
+
61
+ interface GenerateTreeEditsSuccessResponse {
62
+ status: "success";
63
+ tokensUsed: TokenUsage;
64
+ }
65
+
66
+ interface GenerateTreeEditsErrorResponse {
67
+ status: "failure" | "partial-failure";
68
+ errorMessage: "tokenLimitExceeded" | "tooManyErrors" | "tooManyModelCalls" | "aborted";
69
+ tokensUsed: TokenUsage;
70
+ }
71
+
72
+ /**
73
+ * Prompts the provided LLM client to generate valid tree edits.
74
+ * Applies those edits to the provided tree branch before returning.
75
+ *
76
+ * @remarks
77
+ * - Optional root nodes are not supported
78
+ * - Primitive root nodes are not supported
79
+ *
80
+ * @internal
81
+ */
82
+ export async function generateTreeEdits(
83
+ options: GenerateTreeEditsOptions,
84
+ ): Promise<GenerateTreeEditsSuccessResponse | GenerateTreeEditsErrorResponse> {
85
+ const idGenerator = new IdGenerator();
86
+ const editLog: EditLog = [];
87
+ let editCount = 0;
88
+ let sequentialErrorCount = 0;
89
+
90
+ const simpleSchema = getSimpleSchema(Tree.schema(options.treeNode));
91
+
92
+ const tokensUsed = { inputTokens: 0, outputTokens: 0 };
93
+
94
+ try {
95
+ for await (const edit of generateEdits(
96
+ options,
97
+ simpleSchema,
98
+ idGenerator,
99
+ editLog,
100
+ options.limiters?.tokenLimits,
101
+ tokensUsed,
102
+ )) {
103
+ try {
104
+ const result = applyAgentEdit(
105
+ edit,
106
+ idGenerator,
107
+ simpleSchema.definitions,
108
+ options.validator,
109
+ );
110
+ const explanation = result.explanation;
111
+ editLog.push({ edit: { ...result, explanation } });
112
+ sequentialErrorCount = 0;
113
+ } catch (error: unknown) {
114
+ if (error instanceof Error) {
115
+ sequentialErrorCount += 1;
116
+ editLog.push({ edit, error: error.message });
117
+ DEBUG_LOG?.push(`Error: ${error.message}`);
118
+ } else {
119
+ throw error;
120
+ }
121
+ }
122
+
123
+ const responseStatus =
124
+ editCount > 0 && sequentialErrorCount < editCount ? "partial-failure" : "failure";
125
+
126
+ if (options.limiters?.abortController?.signal.aborted === true) {
127
+ return {
128
+ status: responseStatus,
129
+ errorMessage: "aborted",
130
+ tokensUsed,
131
+ };
132
+ }
133
+
134
+ if (
135
+ sequentialErrorCount >
136
+ (options.limiters?.maxSequentialErrors ?? Number.POSITIVE_INFINITY)
137
+ ) {
138
+ return {
139
+ status: responseStatus,
140
+ errorMessage: "tooManyErrors",
141
+ tokensUsed,
142
+ };
143
+ }
144
+
145
+ if (++editCount >= (options.limiters?.maxModelCalls ?? Number.POSITIVE_INFINITY)) {
146
+ return {
147
+ status: responseStatus,
148
+ errorMessage: "tooManyModelCalls",
149
+ tokensUsed,
150
+ };
151
+ }
152
+ }
153
+ } catch (error: unknown) {
154
+ if (error instanceof Error) {
155
+ DEBUG_LOG?.push(`Error: ${error.message}`);
156
+ }
157
+
158
+ if (options.dumpDebugLog ?? false) {
159
+ console.log(DEBUG_LOG.join("\n\n"));
160
+ DEBUG_LOG.length = 0;
161
+ }
162
+
163
+ if (error instanceof TokenLimitExceededError) {
164
+ return {
165
+ status:
166
+ editCount > 0 && sequentialErrorCount < editCount ? "partial-failure" : "failure",
167
+ errorMessage: "tokenLimitExceeded",
168
+ tokensUsed,
169
+ };
170
+ }
171
+ throw error;
172
+ }
173
+
174
+ if (options.dumpDebugLog ?? false) {
175
+ console.log(DEBUG_LOG.join("\n\n"));
176
+ DEBUG_LOG.length = 0;
177
+ }
178
+
179
+ return {
180
+ status: "success",
181
+ tokensUsed,
182
+ };
183
+ }
184
+
185
+ interface ReviewResult {
186
+ goalAccomplished: "yes" | "no";
187
+ }
188
+
189
+ /**
190
+ * Generates a single {@link TreeEdit} from an LLM.
191
+ *
192
+ * @remarks
193
+ * The design of this async generator function is such that which each iteration of this functions values,
194
+ * an LLM will be prompted to generate the next value (a {@link TreeEdit}) based on the users ask.
195
+ * Once the LLM believes it has completed the user's ask, it will no longer return an edit and as a result
196
+ * this generator will no longer yield a next value.
197
+ */
198
+ async function* generateEdits(
199
+ options: GenerateTreeEditsOptions,
200
+ simpleSchema: SimpleTreeSchema,
201
+ idGenerator: IdGenerator,
202
+ editLog: EditLog,
203
+ tokenLimits: TokenLimits | undefined,
204
+ tokensUsed: TokenUsage,
205
+ ): AsyncGenerator<TreeEdit> {
206
+ const [types, rootTypeName] = generateGenericEditTypes(simpleSchema, true);
207
+
208
+ let plan: string | undefined;
209
+ if (options.planningStep !== undefined) {
210
+ const planningPromt = getPlanningSystemPrompt(
211
+ options.treeNode,
212
+ options.prompt.userAsk,
213
+ options.prompt.systemRoleContext,
214
+ );
215
+ DEBUG_LOG?.push(planningPromt);
216
+ plan = await getStringFromLlm(planningPromt, options.openAI, tokensUsed);
217
+ DEBUG_LOG?.push(`AI Generated the following plan: ${planningPromt}`);
218
+ }
219
+
220
+ const originalDecoratedJson =
221
+ (options.finalReviewStep ?? false)
222
+ ? toDecoratedJson(idGenerator, options.treeNode)
223
+ : undefined;
224
+ // reviewed is implicitly true if finalReviewStep is false
225
+ let hasReviewed = (options.finalReviewStep ?? false) ? false : true;
226
+ async function getNextEdit(): Promise<TreeEdit | undefined> {
227
+ const systemPrompt = getEditingSystemPrompt(
228
+ options.prompt.userAsk,
229
+ idGenerator,
230
+ options.treeNode,
231
+ editLog,
232
+ options.prompt.systemRoleContext,
233
+ plan,
234
+ );
235
+
236
+ DEBUG_LOG?.push(systemPrompt);
237
+
238
+ const schema = types[rootTypeName] ?? fail("Root type not found.");
239
+ const wrapper = await getStructuredOutputFromLlm<EditWrapper>(
240
+ systemPrompt,
241
+ options.openAI,
242
+ schema,
243
+ "A JSON object that represents an edit to a JSON tree.",
244
+ tokensUsed,
245
+ );
246
+
247
+ // eslint-disable-next-line unicorn/no-null
248
+ DEBUG_LOG?.push(JSON.stringify(wrapper, null, 2));
249
+ if (wrapper === undefined) {
250
+ DEBUG_LOG?.push("Failed to get response");
251
+ return undefined;
252
+ }
253
+
254
+ if (wrapper.edit === null) {
255
+ DEBUG_LOG?.push("No more edits.");
256
+ if ((options.finalReviewStep ?? false) && !hasReviewed) {
257
+ const reviewResult = await reviewGoal();
258
+ if (reviewResult === undefined) {
259
+ DEBUG_LOG?.push("Failed to get review response");
260
+ return undefined;
261
+ }
262
+ // eslint-disable-next-line require-atomic-updates
263
+ hasReviewed = true;
264
+ if (reviewResult.goalAccomplished === "yes") {
265
+ return undefined;
266
+ } else {
267
+ // eslint-disable-next-line require-atomic-updates
268
+ editLog.length = 0;
269
+ return getNextEdit();
270
+ }
271
+ }
272
+ } else {
273
+ return wrapper.edit;
274
+ }
275
+ }
276
+
277
+ async function reviewGoal(): Promise<ReviewResult | undefined> {
278
+ const systemPrompt = getReviewSystemPrompt(
279
+ options.prompt.userAsk,
280
+ idGenerator,
281
+ options.treeNode,
282
+ originalDecoratedJson ?? fail("Original decorated tree not provided."),
283
+ options.prompt.systemRoleContext,
284
+ );
285
+
286
+ DEBUG_LOG?.push(systemPrompt);
287
+
288
+ const schema = z.object({
289
+ goalAccomplished: z
290
+ .enum(["yes", "no"])
291
+ .describe('Whether the user\'s goal was met in the "after" tree.'),
292
+ });
293
+ return getStructuredOutputFromLlm<ReviewResult>(systemPrompt, options.openAI, schema);
294
+ }
295
+
296
+ let edit = await getNextEdit();
297
+ while (edit !== undefined) {
298
+ yield edit;
299
+ if (tokensUsed.inputTokens > (tokenLimits?.inputTokens ?? Number.POSITIVE_INFINITY)) {
300
+ throw new TokenLimitExceededError("Input token limit exceeded.");
301
+ }
302
+ if (tokensUsed.outputTokens > (tokenLimits?.outputTokens ?? Number.POSITIVE_INFINITY)) {
303
+ throw new TokenLimitExceededError("Output token limit exceeded.");
304
+ }
305
+ edit = await getNextEdit();
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Calls the LLM to generate a structured output response based on the provided prompt.
311
+ */
312
+ async function getStructuredOutputFromLlm<T>(
313
+ prompt: string,
314
+ openAi: OpenAiClientOptions,
315
+ structuredOutputSchema: Zod.ZodTypeAny,
316
+ description?: string,
317
+ tokensUsed?: TokenUsage,
318
+ ): Promise<T | undefined> {
319
+ const response_format = zodResponseFormat(structuredOutputSchema, "SharedTreeAI", {
320
+ description,
321
+ });
322
+
323
+ const body: ChatCompletionCreateParams = {
324
+ messages: [{ role: "system", content: prompt }],
325
+ model: openAi.modelName ?? "gpt-4o",
326
+ response_format,
327
+ };
328
+
329
+ const result = await openAi.client.beta.chat.completions.parse(body);
330
+
331
+ if (result.usage !== undefined && tokensUsed !== undefined) {
332
+ tokensUsed.inputTokens += result.usage?.prompt_tokens;
333
+ tokensUsed.outputTokens += result.usage?.completion_tokens;
334
+ }
335
+
336
+ // TODO: fix types so this isn't null and doesn't need a cast
337
+ // The type should be derived from the zod schema
338
+ return result.choices[0]?.message.parsed as T | undefined;
339
+ }
340
+
341
+ /**
342
+ * Calls the LLM to generate a response based on the provided prompt.
343
+ */
344
+ async function getStringFromLlm(
345
+ prompt: string,
346
+ openAi: OpenAiClientOptions,
347
+ tokensUsed?: TokenUsage,
348
+ ): Promise<string | undefined> {
349
+ const body: ChatCompletionCreateParams = {
350
+ messages: [{ role: "system", content: prompt }],
351
+ model: openAi.modelName ?? "gpt-4o",
352
+ };
353
+
354
+ const result = await openAi.client.chat.completions.create(body);
355
+
356
+ if (result.usage !== undefined && tokensUsed !== undefined) {
357
+ tokensUsed.inputTokens += result.usage?.prompt_tokens;
358
+ tokensUsed.outputTokens += result.usage?.completion_tokens;
359
+ }
360
+
361
+ return result.choices[0]?.message.content ?? undefined;
362
+ }
363
+
364
+ class TokenLimitExceededError extends Error {}
@@ -0,0 +1,27 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * Primitive JSON Types
8
+ */
9
+ // eslint-disable-next-line @rushstack/no-new-null
10
+ export type JsonPrimitive = string | number | boolean | null;
11
+
12
+ /**
13
+ * A JSON Object, a collection of key to {@link JsonValue} pairs
14
+ */
15
+ // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
16
+ export interface JsonObject {
17
+ [key: string]: JsonValue;
18
+ }
19
+ /**
20
+ * An Array of {@link JsonValue}
21
+ */
22
+ export type JsonArray = JsonValue[];
23
+
24
+ /**
25
+ * A union type of all possible JSON values, including primitives, objects, and arrays
26
+ */
27
+ export type JsonValue = JsonPrimitive | JsonObject | JsonArray;