@skaile/forge-editor 6.3.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/adapters/nitro.d.ts +11 -3
- package/dist/adapters/nitro.js +2 -1
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/code/CodeSurface.vue.d.ts +3 -3
- package/dist/document/DocumentSurface.vue.d.ts +3 -3
- package/dist/features/ai/index.d.ts +2 -2
- package/dist/features/ai/index.js +2 -2
- package/dist/features/ai/protocol/chat.d.ts +5 -0
- package/dist/features/ai/protocol/chat.js +7 -4
- package/dist/features/ai/protocol/chat.js.map +1 -1
- package/dist/features/ai/protocol/index.d.ts +2 -2
- package/dist/features/ai/protocol/index.js +2 -2
- package/dist/features/composites/components/CompositeInspector.js +8 -8
- package/dist/features/composites/vue.js +1 -1
- package/dist/markdown/MarkdownSurface.vue.d.ts +7 -7
- package/dist/server/chat-agent.d.ts +85 -0
- package/dist/server/chat-agent.js +61 -0
- package/dist/server/chat-agent.js.map +1 -0
- package/dist/server/document-tools.d.ts +22 -0
- package/dist/server/document-tools.js +190 -0
- package/dist/server/document-tools.js.map +1 -0
- package/dist/server/handlers/ai.d.ts +2 -1
- package/dist/server/handlers/ai.js +232 -121
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/handlers/context.d.ts +4 -1
- package/dist/server/handlers/context.js.map +1 -1
- package/dist/server/index.d.ts +7 -3
- package/dist/server/index.js +5 -2
- package/dist/server/workspace-tools.d.ts +1 -0
- package/dist/server/workspace-tools.js +8 -3
- package/dist/server/workspace-tools.js.map +1 -1
- package/dist/tree/DocumentTree.js +1 -1
- package/dist/tree/use-tree-model.js +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
- package/dist/vue/SimpleEditor.vue.d.ts +4 -4
- package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
- package/dist/vue/compose.js +1 -1
- package/dist/vue/index.js +1 -1
- package/package.json +1 -1
|
@@ -6,8 +6,8 @@ import { AI_ID_PATTERN, aiBlockSchema } from "../../features/ai/protocol/schemas
|
|
|
6
6
|
import { generateBodySchema, generateProblem, runGenerate } from "../../features/ai/protocol/generate.js";
|
|
7
7
|
import { AGENT_OP_OUTCOMES, AGENT_OP_TYPES } from "../../core/agent-op-types.js";
|
|
8
8
|
import { readBlockLines } from "../../features/ai/protocol/block-lines.js";
|
|
9
|
-
import { AGENT_LIMITS, AGENT_TOOL, agentOpTarget, agentRetryMessage, agentUnreadableProblem, checkAgentOps } from "../../features/ai/protocol/agent-ops.js";
|
|
10
|
-
import { CHAT_LIMITS, documentChatMessages } from "../../features/ai/protocol/chat.js";
|
|
9
|
+
import { AGENT_LIMITS, AGENT_TOOL, agentOpTarget, agentRetryMessage, agentShownMessage, agentUnreadableProblem, checkAgentOps } from "../../features/ai/protocol/agent-ops.js";
|
|
10
|
+
import { CHAT_LIMITS, documentChatMessages, documentChatSystem } from "../../features/ai/protocol/chat.js";
|
|
11
11
|
import { TranscriptCleanupUnreadableError, runTranscriptCleanup, transcriptCleanupRequestSchema } from "../../integrations/transcript-ai/protocol.js";
|
|
12
12
|
import { blockExcerptText } from "../../core/block-snippet.js";
|
|
13
13
|
import { AGENT_OP_ID } from "../../core/change-set.js";
|
|
@@ -16,9 +16,10 @@ import { aiBlockTypesOf } from "../../features/composites/block-types.js";
|
|
|
16
16
|
import { ASSET_DOCUMENT_ID, ASSET_NAME } from "../../features/media/assets.js";
|
|
17
17
|
import { loadCurrentContent } from "../stored-content.js";
|
|
18
18
|
import { HttpError } from "../errors.js";
|
|
19
|
+
import { documentEditTool } from "../chat-agent.js";
|
|
19
20
|
import { mayEditDocument } from "../readable-documents.js";
|
|
20
|
-
import { toolRegistry } from "../tools.js";
|
|
21
21
|
import { blockExcerptOf, blockLinesOfContent } from "../document-blocks.js";
|
|
22
|
+
import { toolRegistry } from "../tools.js";
|
|
22
23
|
import { authenticated, enforceRateLimit, eventStream, json, port, readBody } from "./context.js";
|
|
23
24
|
import { DOI_PATTERN } from "../crossref.js";
|
|
24
25
|
import { MAX_TRANSCRIBE_BYTES, RasterError, bibliographyText, describeImage, extractBibliography, isScanned, joinPages, ocrPages, pdfPagesText, transcribe } from "../extract.js";
|
|
@@ -141,57 +142,108 @@ const chatBodySchema = z.object({
|
|
|
141
142
|
structure: z.enum(STRUCTURE_MODES)
|
|
142
143
|
});
|
|
143
144
|
/**
|
|
144
|
-
* The tools of one chat turn: `edit_document` (deliberately loose, so malformed calls reach `checkAgentOps`
|
|
145
|
-
* retry message) and, with `reads`, the registry's read-only tools. With `workspace` (the host keeps change
|
|
146
|
-
* the operations of `edit_document` may name another Document by `documentId`.
|
|
145
|
+
* The tools of one chat turn: `edit_document` (`documentEditTool`, deliberately loose, so malformed calls reach `checkAgentOps`
|
|
146
|
+
* and get a precise retry message) and, with `reads`, the registry's read-only tools. With `workspace` (the host keeps change
|
|
147
|
+
* sets and a mutator) the operations of `edit_document` may name another Document by `documentId`.
|
|
147
148
|
*/
|
|
148
149
|
function agentTools(registry, { workspace, reads }) {
|
|
150
|
+
const edit = documentEditTool({ workspace });
|
|
149
151
|
return {
|
|
150
152
|
[AGENT_TOOL]: tool({
|
|
151
|
-
description:
|
|
152
|
-
inputSchema: jsonSchema(
|
|
153
|
-
type: "object",
|
|
154
|
-
properties: { ops: {
|
|
155
|
-
type: "array",
|
|
156
|
-
description: "Die Operationen in Dokumentreihenfolge.",
|
|
157
|
-
items: {
|
|
158
|
-
type: "object",
|
|
159
|
-
properties: {
|
|
160
|
-
type: {
|
|
161
|
-
type: "string",
|
|
162
|
-
enum: [...AGENT_OP_TYPES],
|
|
163
|
-
description: "update: Blockinhalt ersetzen; delete: Block löschen; add: Blöcke einfügen; convert: Blocktyp ändern."
|
|
164
|
-
},
|
|
165
|
-
...workspace && { documentId: {
|
|
166
|
-
type: "string",
|
|
167
|
-
description: "Nur für Blöcke eines anderen Dokuments: dessen ID (zusammen mit den Block-IDs aus find_blocks oder read_block). Es muss ein Dokument sein, das die Nutzerin bzw. der Nutzer bearbeiten darf. Ohne Angabe gilt das geöffnete Dokument."
|
|
168
|
-
} },
|
|
169
|
-
id: {
|
|
170
|
-
type: "string",
|
|
171
|
-
description: "update/delete/convert: ID des Blocks aus dem Dokument."
|
|
172
|
-
},
|
|
173
|
-
afterId: {
|
|
174
|
-
type: ["string", "null"],
|
|
175
|
-
description: "add (Pflicht): ID des Blocks, nach dem eingefügt wird; für den Dokumentanfang ausdrücklich null."
|
|
176
|
-
},
|
|
177
|
-
markdown: {
|
|
178
|
-
type: "string",
|
|
179
|
-
description: "update/add: der neue Inhalt als Markdown, ohne Block-ID."
|
|
180
|
-
},
|
|
181
|
-
to: {
|
|
182
|
-
type: "string",
|
|
183
|
-
description: "convert: der neue Blocktyp, einer der im Systemprompt erlaubten Werte (z. B. paragraph, heading2, composite:task)."
|
|
184
|
-
}
|
|
185
|
-
},
|
|
186
|
-
required: ["type"]
|
|
187
|
-
}
|
|
188
|
-
} },
|
|
189
|
-
required: ["ops"]
|
|
190
|
-
})
|
|
153
|
+
description: edit.description,
|
|
154
|
+
inputSchema: jsonSchema(edit.inputSchema)
|
|
191
155
|
}),
|
|
192
156
|
...reads && registry.tools
|
|
193
157
|
};
|
|
194
158
|
}
|
|
159
|
+
/** Checks a chat request beyond its schema (400 for an empty message or a selection without blocks of the document) and answers where its operations may point (`checkAgentOps`). */
|
|
160
|
+
function opContextOf({ messages, document, scope, targets }) {
|
|
161
|
+
if (!messages.at(-1).content.trim()) throw new HttpError(400, "Nachricht fehlt");
|
|
162
|
+
const lines = readBlockLines(document);
|
|
163
|
+
const allowed = scope ? new Set(scope.filter((id) => lines.ids.has(id))) : null;
|
|
164
|
+
if (allowed && !allowed.size) throw new HttpError(400, "Die Auswahl enthält keine Blöcke des Dokuments");
|
|
165
|
+
return {
|
|
166
|
+
lines,
|
|
167
|
+
allowed,
|
|
168
|
+
targets: new Set(targets)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/** Loads the Documents operations name, once per request (the model's read and its edit see the same content); `null` for one the user may not edit or that does not exist. */
|
|
172
|
+
function namedDocumentLoader(ctx, user, toolContext) {
|
|
173
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
174
|
+
return (id) => {
|
|
175
|
+
let entry = loaded.get(id);
|
|
176
|
+
if (!entry) {
|
|
177
|
+
entry = (async () => {
|
|
178
|
+
if (!await mayEditDocument(ctx.ports, user, id)) return null;
|
|
179
|
+
const content = toolContext.liveContent(id) ?? await loadCurrentContent(ctx.ports.documents, id);
|
|
180
|
+
return content && {
|
|
181
|
+
content,
|
|
182
|
+
lines: blockLinesOfContent(content)
|
|
183
|
+
};
|
|
184
|
+
})();
|
|
185
|
+
loaded.set(id, entry);
|
|
186
|
+
}
|
|
187
|
+
return entry;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** The lines of the other Documents `raw` names, as far as the user may edit them; empty without `documentOf` (no change sets: such an operation is a `document` problem). */
|
|
191
|
+
async function namedDocumentLines(raw, openDocumentId, documentOf) {
|
|
192
|
+
const documents = /* @__PURE__ */ new Map();
|
|
193
|
+
if (!documentOf) return documents;
|
|
194
|
+
const named = new Set(raw.flatMap((op) => typeof op === "object" && op !== null && "documentId" in op && typeof op.documentId === "string" ? [op.documentId.trim()] : []));
|
|
195
|
+
named.delete(openDocumentId ?? "");
|
|
196
|
+
for (const id of [...named].slice(0, AGENT_LIMITS.ops)) {
|
|
197
|
+
const entry = id ? await documentOf(id) : null;
|
|
198
|
+
if (entry) documents.set(id, entry.lines);
|
|
199
|
+
}
|
|
200
|
+
return documents;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Streams the operations of a turn, numbered from `op<nextOp>`: the open Document's as `ops` (the editor's in-document review),
|
|
204
|
+
* the others' as one stored `change-set`. Without an answer text and without a valid operation the user reads that none was possible.
|
|
205
|
+
*/
|
|
206
|
+
async function emitProposal(emit, proposal) {
|
|
207
|
+
const { all, dropped, retries, answered, user, changeSets, documentOf } = proposal;
|
|
208
|
+
const ops = all.filter((op) => op.documentId === void 0);
|
|
209
|
+
const foreign = all.filter((op) => op.documentId !== void 0);
|
|
210
|
+
if (!answered && !all.length) emit("delta", JSON.stringify({ text: "Ich konnte keine gültigen Änderungen vorschlagen." }));
|
|
211
|
+
if (foreign.length) {
|
|
212
|
+
const hunks = await Promise.all(foreign.map(async (op) => {
|
|
213
|
+
const documentId = op.documentId;
|
|
214
|
+
const target = agentOpTarget(op);
|
|
215
|
+
const entry = await documentOf(documentId);
|
|
216
|
+
const excerpt = target === null || !entry ? null : blockExcerptOf(entry.content, target);
|
|
217
|
+
return {
|
|
218
|
+
op: {
|
|
219
|
+
...op,
|
|
220
|
+
documentId
|
|
221
|
+
},
|
|
222
|
+
status: "pending",
|
|
223
|
+
target: excerpt && {
|
|
224
|
+
type: excerpt.type,
|
|
225
|
+
text: blockExcerptText(excerpt.text)
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}));
|
|
229
|
+
emit("change-set", JSON.stringify(await changeSets.create({
|
|
230
|
+
by: changeSetOwner(user),
|
|
231
|
+
hunks
|
|
232
|
+
})));
|
|
233
|
+
}
|
|
234
|
+
emit("ops", JSON.stringify({
|
|
235
|
+
ops,
|
|
236
|
+
dropped,
|
|
237
|
+
retries
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
/** `final` and the problems of earlier attempts whose block no operation of `all` targets: an operation a corrected list leaves out counts as dropped too. */
|
|
241
|
+
function droppedOf(all, final, earlier) {
|
|
242
|
+
const targets = new Set(all.map((op) => agentOpTarget(op)));
|
|
243
|
+
const dropped = [...final];
|
|
244
|
+
for (const problem of earlier) if (problem.id !== null && !targets.has(problem.id) && !dropped.some((d) => d.id === problem.id && d.type === problem.type)) dropped.push(problem);
|
|
245
|
+
return dropped;
|
|
246
|
+
}
|
|
195
247
|
/**
|
|
196
248
|
* Document chat: answers the last message of `messages` about `document`, or proposes edit operations through the
|
|
197
249
|
* `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, an `add` without `afterId`, an
|
|
@@ -203,24 +255,33 @@ function agentTools(registry, { workspace, reads }) {
|
|
|
203
255
|
* Operations of the open Document (`documentId` absent or the body's) are `ops`; operations naming another Document the
|
|
204
256
|
* user may edit (`mayEditDocument`) become one `ChangeSet` (`ChangeSetStore`) for the user to review per Document, when the
|
|
205
257
|
* host has the `changeSets` port and a mutator; else such an operation is a `document` problem the model retries. All
|
|
206
|
-
* operations share one numbering (`op<n>`).
|
|
258
|
+
* operations share one numbering (`op<n>`). With a host agent (`AiHandlerContext.chatAgent` answers one for the user) the
|
|
259
|
+
* agent answers the turn in place of the model loop (`agentChat`), with the same checks and events.
|
|
207
260
|
*/
|
|
208
261
|
async function chatPost(ctx, request) {
|
|
209
262
|
const user = await enforceRateLimit(ctx, request);
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const context = {
|
|
218
|
-
lines,
|
|
219
|
-
allowed,
|
|
220
|
-
targets: new Set(targets)
|
|
263
|
+
const body = await readBody(request, chatBodySchema);
|
|
264
|
+
const { messages, document, documentId: openDocumentId, nextOp, model, blockTypes: clientBlockTypes, blocks, targets, structure } = body;
|
|
265
|
+
const blockModel = {
|
|
266
|
+
blockTypes: ctx.blockTypes ? aiBlockTypesOf(ctx.blockTypes) : clientBlockTypes,
|
|
267
|
+
blocks,
|
|
268
|
+
targets,
|
|
269
|
+
structure
|
|
221
270
|
};
|
|
222
271
|
const registry = ctx.tools ?? NO_TOOLS;
|
|
223
272
|
const changeSets = ctx.mutator ? ctx.ports.changeSets : void 0;
|
|
273
|
+
const agent = await ctx.chatAgent?.(user) ?? null;
|
|
274
|
+
if (agent) return agentChat(ctx, request, agent, {
|
|
275
|
+
user,
|
|
276
|
+
body,
|
|
277
|
+
blockModel,
|
|
278
|
+
context: opContextOf(body),
|
|
279
|
+
registry,
|
|
280
|
+
changeSets
|
|
281
|
+
});
|
|
282
|
+
const languageModel = await ctx.ai.resolveModel(model, user);
|
|
283
|
+
const context = opContextOf(body);
|
|
284
|
+
const { allowed } = context;
|
|
224
285
|
const workspace = changeSets !== void 0;
|
|
225
286
|
const editTools = agentTools(registry, {
|
|
226
287
|
workspace,
|
|
@@ -232,12 +293,7 @@ async function chatPost(ctx, request) {
|
|
|
232
293
|
});
|
|
233
294
|
return eventStream(request, async (emit, signal) => {
|
|
234
295
|
try {
|
|
235
|
-
let conversation = documentChatMessages(messages, document, allowed && [...allowed],
|
|
236
|
-
blockTypes,
|
|
237
|
-
blocks,
|
|
238
|
-
targets,
|
|
239
|
-
structure
|
|
240
|
-
}, registry.infos);
|
|
296
|
+
let conversation = documentChatMessages(messages, document, allowed && [...allowed], blockModel, registry.infos);
|
|
241
297
|
let answered = false;
|
|
242
298
|
let result = null;
|
|
243
299
|
const retried = [];
|
|
@@ -250,22 +306,7 @@ async function chatPost(ctx, request) {
|
|
|
250
306
|
liveContent: (id) => ctx.collab?.liveContent(id) ?? null,
|
|
251
307
|
...openDocumentId && { documentId: openDocumentId }
|
|
252
308
|
};
|
|
253
|
-
const
|
|
254
|
-
const documentOf = (id) => {
|
|
255
|
-
let entry = loaded.get(id);
|
|
256
|
-
if (!entry) {
|
|
257
|
-
entry = (async () => {
|
|
258
|
-
if (!await mayEditDocument(ctx.ports, user, id)) return null;
|
|
259
|
-
const content = toolContext.liveContent(id) ?? await loadCurrentContent(ctx.ports.documents, id);
|
|
260
|
-
return content && {
|
|
261
|
-
content,
|
|
262
|
-
lines: blockLinesOfContent(content)
|
|
263
|
-
};
|
|
264
|
-
})();
|
|
265
|
-
loaded.set(id, entry);
|
|
266
|
-
}
|
|
267
|
-
return entry;
|
|
268
|
-
};
|
|
309
|
+
const documentOf = namedDocumentLoader(ctx, user, toolContext);
|
|
269
310
|
for (;;) {
|
|
270
311
|
const calls = [];
|
|
271
312
|
const reads = [];
|
|
@@ -346,15 +387,7 @@ async function chatPost(ctx, request) {
|
|
|
346
387
|
if (!calls.length) break;
|
|
347
388
|
const unreadable = calls.some((call) => call.invalid || !Array.isArray(call.input?.ops));
|
|
348
389
|
const raw = calls.flatMap((call) => call.input?.ops).filter((op) => op !== void 0);
|
|
349
|
-
const documents =
|
|
350
|
-
if (changeSets) {
|
|
351
|
-
const named = new Set(raw.flatMap((op) => typeof op?.documentId === "string" ? [op.documentId.trim()] : []));
|
|
352
|
-
named.delete(openDocumentId ?? "");
|
|
353
|
-
for (const id of [...named].slice(0, AGENT_LIMITS.ops)) {
|
|
354
|
-
const entry = id ? await documentOf(id) : null;
|
|
355
|
-
if (entry) documents.set(id, entry.lines);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
390
|
+
const documents = await namedDocumentLines(raw, openDocumentId, changeSets ? documentOf : null);
|
|
358
391
|
const checked = checkAgentOps(raw, {
|
|
359
392
|
...context,
|
|
360
393
|
...openDocumentId && { documentId: openDocumentId },
|
|
@@ -399,40 +432,118 @@ async function chatPost(ctx, request) {
|
|
|
399
432
|
...op,
|
|
400
433
|
op: `op${nextOp + i}`
|
|
401
434
|
}));
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
435
|
+
await emitProposal(emit, {
|
|
436
|
+
all,
|
|
437
|
+
dropped: droppedOf(all, result.problems, retried),
|
|
438
|
+
retries,
|
|
439
|
+
answered,
|
|
440
|
+
user,
|
|
441
|
+
changeSets,
|
|
442
|
+
documentOf
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
emit("done", "{}");
|
|
446
|
+
} catch (error) {
|
|
447
|
+
if (!signal.aborted) {
|
|
448
|
+
const { statusMessage, message } = error;
|
|
449
|
+
emit("error", JSON.stringify({ message: statusMessage ?? message ?? "Unbekannter Fehler" }));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* The chat turn of a host's `ChatAgent`: the agent gets the request as `ChatAgentTurn` (snapshot, scope, block model, the
|
|
456
|
+
* read-only tools with their context, the built-in system prompt and `edit_document`) and streams text, the tool calls it ran
|
|
457
|
+
* and the operations `checkEdit` accepted. They become the built-in loop's events: `delta`, `tool`, then once the proposal of
|
|
458
|
+
* all edits (`change-set` for other Documents, `ops` numbered from `nextOp`), `done`; a thrown error is `error`. `checkEdit`
|
|
459
|
+
* checks each `edit_document` call against the operations of the earlier calls of the turn too (the limit, a block edited twice,
|
|
460
|
+
* an operation inside a block an earlier one replaces), and numbers on from them.
|
|
461
|
+
*/
|
|
462
|
+
function agentChat(ctx, request, agent, chat) {
|
|
463
|
+
const { user, body: { messages, document, documentId: openDocumentId, nextOp }, blockModel, context, registry, changeSets } = chat;
|
|
464
|
+
return eventStream(request, async (emit, signal) => {
|
|
465
|
+
try {
|
|
466
|
+
const toolContext = {
|
|
467
|
+
ports: ctx.ports,
|
|
468
|
+
user,
|
|
469
|
+
signal,
|
|
470
|
+
liveContent: (id) => ctx.collab?.liveContent(id) ?? null,
|
|
471
|
+
...openDocumentId && { documentId: openDocumentId }
|
|
472
|
+
};
|
|
473
|
+
const documentOf = namedDocumentLoader(ctx, user, toolContext);
|
|
474
|
+
const shown = [];
|
|
475
|
+
const checkEdit = async (input) => {
|
|
476
|
+
const value = typeof input === "object" && input !== null && "ops" in input ? input.ops : void 0;
|
|
477
|
+
const raw = [...shown, ...Array.isArray(value) ? value.filter((op) => op !== void 0) : []];
|
|
478
|
+
const documents = await namedDocumentLines(raw, openDocumentId, changeSets ? documentOf : null);
|
|
479
|
+
const checked = checkAgentOps(raw, {
|
|
480
|
+
...context,
|
|
481
|
+
...openDocumentId && { documentId: openDocumentId },
|
|
482
|
+
documents
|
|
483
|
+
});
|
|
484
|
+
const covered = checked.problems.filter((problem) => problem.index <= shown.length).length;
|
|
485
|
+
const fresh = checked.valid.slice(shown.length - covered);
|
|
486
|
+
const problems = checked.problems.filter((problem) => problem.index > shown.length).map((problem) => ({
|
|
487
|
+
...problem,
|
|
488
|
+
index: problem.index - shown.length
|
|
489
|
+
}));
|
|
490
|
+
if (!Array.isArray(value)) problems.unshift(agentUnreadableProblem());
|
|
491
|
+
const ops = fresh.map((op, i) => ({
|
|
492
|
+
...op,
|
|
493
|
+
op: `op${nextOp + shown.length + i}`
|
|
494
|
+
}));
|
|
495
|
+
shown.push(...fresh);
|
|
496
|
+
const message = agentShownMessage(ops, 0);
|
|
497
|
+
return {
|
|
432
498
|
ops,
|
|
433
|
-
|
|
434
|
-
|
|
499
|
+
problems,
|
|
500
|
+
feedback: problems.length ? `${message}\n${agentRetryMessage(problems, !!context.allowed, { partial: true })}` : message
|
|
501
|
+
};
|
|
502
|
+
};
|
|
503
|
+
const proposed = [];
|
|
504
|
+
const attempts = [];
|
|
505
|
+
let answered = false;
|
|
506
|
+
for await (const part of agent.turn({
|
|
507
|
+
user,
|
|
508
|
+
...openDocumentId && { documentId: openDocumentId },
|
|
509
|
+
messages,
|
|
510
|
+
document,
|
|
511
|
+
scope: context.allowed && [...context.allowed],
|
|
512
|
+
blockModel,
|
|
513
|
+
tools: registry,
|
|
514
|
+
toolContext,
|
|
515
|
+
system: documentChatSystem(blockModel, registry.infos),
|
|
516
|
+
editTool: documentEditTool({ workspace: changeSets !== void 0 }),
|
|
517
|
+
checkEdit,
|
|
518
|
+
signal
|
|
519
|
+
})) if (part.type === "text") {
|
|
520
|
+
if (!part.text) continue;
|
|
521
|
+
answered = true;
|
|
522
|
+
emit("delta", JSON.stringify({ text: part.text }));
|
|
523
|
+
} else if (part.type === "tool") emit("tool", JSON.stringify({
|
|
524
|
+
name: part.call.name,
|
|
525
|
+
input: toolInput(part.call.input),
|
|
526
|
+
output: part.call.output.slice(0, CHAT_LIMITS.toolOutput)
|
|
527
|
+
}));
|
|
528
|
+
else {
|
|
529
|
+
proposed.push(...part.ops);
|
|
530
|
+
attempts.push(part.dropped);
|
|
531
|
+
}
|
|
532
|
+
if (attempts.length) {
|
|
533
|
+
const all = proposed.map((op, i) => ({
|
|
534
|
+
...op,
|
|
535
|
+
op: `op${nextOp + i}`
|
|
435
536
|
}));
|
|
537
|
+
const retries = attempts.slice(1).filter((_, i) => attempts[i].length > 0).length;
|
|
538
|
+
await emitProposal(emit, {
|
|
539
|
+
all,
|
|
540
|
+
dropped: droppedOf(all, attempts.at(-1), attempts.slice(0, -1).flat()),
|
|
541
|
+
retries,
|
|
542
|
+
answered,
|
|
543
|
+
user,
|
|
544
|
+
changeSets,
|
|
545
|
+
documentOf
|
|
546
|
+
});
|
|
436
547
|
}
|
|
437
548
|
emit("done", "{}");
|
|
438
549
|
} catch (error) {
|