@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/adapters/nitro.d.ts +11 -3
  3. package/dist/adapters/nitro.js +2 -1
  4. package/dist/adapters/nitro.js.map +1 -1
  5. package/dist/code/CodeSurface.vue.d.ts +3 -3
  6. package/dist/document/DocumentSurface.vue.d.ts +3 -3
  7. package/dist/features/ai/index.d.ts +2 -2
  8. package/dist/features/ai/index.js +2 -2
  9. package/dist/features/ai/protocol/chat.d.ts +5 -0
  10. package/dist/features/ai/protocol/chat.js +7 -4
  11. package/dist/features/ai/protocol/chat.js.map +1 -1
  12. package/dist/features/ai/protocol/index.d.ts +2 -2
  13. package/dist/features/ai/protocol/index.js +2 -2
  14. package/dist/features/composites/components/CompositeInspector.js +8 -8
  15. package/dist/features/composites/vue.js +1 -1
  16. package/dist/markdown/MarkdownSurface.vue.d.ts +7 -7
  17. package/dist/server/chat-agent.d.ts +85 -0
  18. package/dist/server/chat-agent.js +61 -0
  19. package/dist/server/chat-agent.js.map +1 -0
  20. package/dist/server/document-tools.d.ts +22 -0
  21. package/dist/server/document-tools.js +190 -0
  22. package/dist/server/document-tools.js.map +1 -0
  23. package/dist/server/handlers/ai.d.ts +2 -1
  24. package/dist/server/handlers/ai.js +232 -121
  25. package/dist/server/handlers/ai.js.map +1 -1
  26. package/dist/server/handlers/context.d.ts +4 -1
  27. package/dist/server/handlers/context.js.map +1 -1
  28. package/dist/server/index.d.ts +7 -3
  29. package/dist/server/index.js +5 -2
  30. package/dist/server/workspace-tools.d.ts +1 -0
  31. package/dist/server/workspace-tools.js +8 -3
  32. package/dist/server/workspace-tools.js.map +1 -1
  33. package/dist/tree/DocumentTree.js +1 -1
  34. package/dist/tree/use-tree-model.js +1 -1
  35. package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
  36. package/dist/vue/SimpleEditor.vue.d.ts +4 -4
  37. package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
  38. package/dist/vue/compose.js +1 -1
  39. package/dist/vue/index.js +1 -1
  40. package/package.json +1 -1
@@ -0,0 +1,85 @@
1
+ import { AgentOp } from "../core/agent-op-types.js";
2
+ import { AGENT_TOOL, AgentOpProblem } from "../features/ai/protocol/agent-ops.js";
3
+ import { ChatBlockModel, ChatToolCall, DocumentChatMessage } from "../features/ai/protocol/chat.js";
4
+ import "../features/ai/protocol/index.js";
5
+ import { ToolContext, ToolRegistry } from "./tools.js";
6
+ import { JSONSchema7 } from "ai";
7
+ //#region src/server/chat-agent.d.ts
8
+ /** The `edit_document` tool as an agent offers it to its model: name, description and JSON schema of the input. */
9
+ export interface ChatAgentEditTool {
10
+ name: typeof AGENT_TOOL;
11
+ description: string;
12
+ inputSchema: JSONSchema7;
13
+ }
14
+ /** What `ChatAgentTurn.checkEdit` answers for one `edit_document` call. */
15
+ export interface ChatAgentEdit {
16
+ /** The valid operations, numbered on from the request's `nextOp` across the calls of the turn. */
17
+ ops: AgentOp[];
18
+ /** The operations dropped from this call, with the reason. */
19
+ problems: AgentOpProblem[];
20
+ /** The tool result for the model: which operations are shown for review and, with problems, what to correct and send again. */
21
+ feedback: string;
22
+ }
23
+ /** One chat turn as the agent receives it: everything the built-in loop would give its model, prepared by the server. */
24
+ export interface ChatAgentTurn {
25
+ /** The user `ports.identity` authenticated for the request. */
26
+ user: object;
27
+ /** The Document open in the chat (`documentId` of the request); absent for a chat without a Document. */
28
+ documentId?: string;
29
+ /** The conversation as the client sent it; the last message is the user's new message. */
30
+ messages: readonly DocumentChatMessage[];
31
+ /** The open Document as block lines (`[id] markdown`), the snapshot the client sent. */
32
+ document: string;
33
+ /** Block IDs edits are limited to (the selection, as far as the document has them); null: the whole document. */
34
+ scope: readonly string[] | null;
35
+ /** Block types, host blocks, `convert` targets and preferred structure, as the system prompt describes them. */
36
+ blockModel: ChatBlockModel;
37
+ /** The read-only tools of the chat (`EditorServerOptions.tools`); run a call with `tools.providers.get(name)!.handle(toolContext, input)`. */
38
+ tools: ToolRegistry;
39
+ /** The context the tools run with (ports, user, the request's abort signal, live content, open Document). */
40
+ toolContext: ToolContext;
41
+ /** The system prompt of the built-in loop (`documentChatSystem(blockModel, tools.infos)`): citations, editing through `edit_document`, the block model. */
42
+ system: string;
43
+ /** The `edit_document` tool to offer the model; with the host's change sets its operations may name other Documents. */
44
+ editTool: ChatAgentEditTool;
45
+ /**
46
+ * Checks the input of one `edit_document` call like the built-in loop does (`checkAgentOps`), together with the operations
47
+ * the turn's earlier calls accepted (the per-turn limit, a block edited twice, an operation inside a replaced block); an
48
+ * operation naming another Document is checked against it when the host keeps change sets. Hand `feedback` back to the
49
+ * model as the tool result (with problems it asks for corrected versions of only the dropped operations), and yield
50
+ * `{ type: 'edit', ops, dropped: problems }` so the client shows the operations for review.
51
+ */
52
+ checkEdit(input: unknown): Promise<ChatAgentEdit>;
53
+ /** Aborts when the client cancels the chat. */
54
+ signal: AbortSignal;
55
+ }
56
+ /** What an agent streams for one turn: answer text (deltas), a read-only tool call it ran, operations `checkEdit` accepted. */
57
+ export type ChatAgentPart = {
58
+ type: 'text';
59
+ text: string;
60
+ } | {
61
+ type: 'tool';
62
+ call: ChatToolCall;
63
+ } | {
64
+ type: 'edit';
65
+ ops: AgentOp[];
66
+ dropped: AgentOpProblem[];
67
+ };
68
+ /** A host-provided agent that answers document chat turns in place of the built-in model loop. A thrown error ends the turn as the stream's `error` event. */
69
+ export interface ChatAgent {
70
+ turn(input: ChatAgentTurn): AsyncIterable<ChatAgentPart>;
71
+ }
72
+ /**
73
+ * JSON schema of the `edit_document` input: the list `ops`. Deliberately loose (only `type` is required), so a malformed
74
+ * call reaches `checkAgentOps` and gets a precise retry message. With `workspace` (the host keeps change sets and a mutator)
75
+ * an operation may name another Document by `documentId`.
76
+ */
77
+ export declare function documentEditInputSchema({ workspace }?: {
78
+ workspace?: boolean;
79
+ }): JSONSchema7;
80
+ /** The `edit_document` tool of the document chat (name, description, `documentEditInputSchema`), as the built-in loop and `ChatAgentTurn.editTool` offer it. */
81
+ export declare function documentEditTool({ workspace }?: {
82
+ workspace?: boolean;
83
+ }): ChatAgentEditTool;
84
+ //#endregion
85
+ //# sourceMappingURL=chat-agent.d.ts.map
@@ -0,0 +1,61 @@
1
+ import { AGENT_OP_TYPES } from "../core/agent-op-types.js";
2
+ import { AGENT_TOOL } from "../features/ai/protocol/agent-ops.js";
3
+ //#region src/server/chat-agent.ts
4
+ /**
5
+ * JSON schema of the `edit_document` input: the list `ops`. Deliberately loose (only `type` is required), so a malformed
6
+ * call reaches `checkAgentOps` and gets a precise retry message. With `workspace` (the host keeps change sets and a mutator)
7
+ * an operation may name another Document by `documentId`.
8
+ */
9
+ function documentEditInputSchema({ workspace = false } = {}) {
10
+ return {
11
+ type: "object",
12
+ properties: { ops: {
13
+ type: "array",
14
+ description: "Die Operationen in Dokumentreihenfolge.",
15
+ items: {
16
+ type: "object",
17
+ properties: {
18
+ type: {
19
+ type: "string",
20
+ enum: [...AGENT_OP_TYPES],
21
+ description: "update: Blockinhalt ersetzen; delete: Block löschen; add: Blöcke einfügen; convert: Blocktyp ändern."
22
+ },
23
+ ...workspace && { documentId: {
24
+ type: "string",
25
+ 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."
26
+ } },
27
+ id: {
28
+ type: "string",
29
+ description: "update/delete/convert: ID des Blocks aus dem Dokument."
30
+ },
31
+ afterId: {
32
+ type: ["string", "null"],
33
+ description: "add (Pflicht): ID des Blocks, nach dem eingefügt wird; für den Dokumentanfang ausdrücklich null."
34
+ },
35
+ markdown: {
36
+ type: "string",
37
+ description: "update/add: der neue Inhalt als Markdown, ohne Block-ID."
38
+ },
39
+ to: {
40
+ type: "string",
41
+ description: "convert: der neue Blocktyp, einer der im Systemprompt erlaubten Werte (z. B. paragraph, heading2, composite:task)."
42
+ }
43
+ },
44
+ required: ["type"]
45
+ }
46
+ } },
47
+ required: ["ops"]
48
+ };
49
+ }
50
+ /** The `edit_document` tool of the document chat (name, description, `documentEditInputSchema`), as the built-in loop and `ChatAgentTurn.editTool` offer it. */
51
+ function documentEditTool({ workspace = false } = {}) {
52
+ return {
53
+ name: AGENT_TOOL,
54
+ description: workspace ? "Schlägt Änderungen vor. Operationen am geöffneten Dokument zeigt die Nutzerin bzw. der Nutzer im Dokument zur Prüfung an; Operationen mit documentId (anderes Dokument des Arbeitsbereichs) landen in einem Änderungssatz, den die Nutzerin bzw. der Nutzer je Dokument prüft und übernimmt oder ablehnt." : "Schlägt Änderungen am Dokument vor. Jede Operation wird der Nutzerin bzw. dem Nutzer im Dokument zur Prüfung angezeigt.",
55
+ inputSchema: documentEditInputSchema({ workspace })
56
+ };
57
+ }
58
+ //#endregion
59
+ export { documentEditInputSchema, documentEditTool };
60
+
61
+ //# sourceMappingURL=chat-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-agent.js","names":[],"sources":["../../src/server/chat-agent.ts"],"sourcesContent":["// i18n-ignore-file: model-facing tool description and input schema (German, like the chat's system prompt)\nimport type { JSONSchema7 } from 'ai'\nimport { AGENT_OP_TYPES, AGENT_TOOL, type AgentOp, type AgentOpProblem, type ChatBlockModel, type ChatToolCall, type DocumentChatMessage } from '../features/ai/protocol'\nimport type { ToolContext, ToolRegistry } from './tools'\n\n// The document chat with a host-provided agent instead of the built-in model loop (`EditorServerOptions.chatAgent`).\n// `POST /api/ai/chat` keeps everything around the loop: it parses and checks the request, builds the document snapshot,\n// checks the proposed operations and streams the same SSE events to the client. The agent only answers the turn: text,\n// the read-only tool calls it ran, and the operations it proposed through `checkEdit`.\n\n/** The `edit_document` tool as an agent offers it to its model: name, description and JSON schema of the input. */\nexport interface ChatAgentEditTool {\n name: typeof AGENT_TOOL\n description: string\n inputSchema: JSONSchema7\n}\n\n/** What `ChatAgentTurn.checkEdit` answers for one `edit_document` call. */\nexport interface ChatAgentEdit {\n /** The valid operations, numbered on from the request's `nextOp` across the calls of the turn. */\n ops: AgentOp[]\n /** The operations dropped from this call, with the reason. */\n problems: AgentOpProblem[]\n /** The tool result for the model: which operations are shown for review and, with problems, what to correct and send again. */\n feedback: string\n}\n\n/** One chat turn as the agent receives it: everything the built-in loop would give its model, prepared by the server. */\nexport interface ChatAgentTurn {\n /** The user `ports.identity` authenticated for the request. */\n user: object\n /** The Document open in the chat (`documentId` of the request); absent for a chat without a Document. */\n documentId?: string\n /** The conversation as the client sent it; the last message is the user's new message. */\n messages: readonly DocumentChatMessage[]\n /** The open Document as block lines (`[id] markdown`), the snapshot the client sent. */\n document: string\n /** Block IDs edits are limited to (the selection, as far as the document has them); null: the whole document. */\n scope: readonly string[] | null\n /** Block types, host blocks, `convert` targets and preferred structure, as the system prompt describes them. */\n blockModel: ChatBlockModel\n /** The read-only tools of the chat (`EditorServerOptions.tools`); run a call with `tools.providers.get(name)!.handle(toolContext, input)`. */\n tools: ToolRegistry\n /** The context the tools run with (ports, user, the request's abort signal, live content, open Document). */\n toolContext: ToolContext\n /** The system prompt of the built-in loop (`documentChatSystem(blockModel, tools.infos)`): citations, editing through `edit_document`, the block model. */\n system: string\n /** The `edit_document` tool to offer the model; with the host's change sets its operations may name other Documents. */\n editTool: ChatAgentEditTool\n /**\n * Checks the input of one `edit_document` call like the built-in loop does (`checkAgentOps`), together with the operations\n * the turn's earlier calls accepted (the per-turn limit, a block edited twice, an operation inside a replaced block); an\n * operation naming another Document is checked against it when the host keeps change sets. Hand `feedback` back to the\n * model as the tool result (with problems it asks for corrected versions of only the dropped operations), and yield\n * `{ type: 'edit', ops, dropped: problems }` so the client shows the operations for review.\n */\n checkEdit(input: unknown): Promise<ChatAgentEdit>\n /** Aborts when the client cancels the chat. */\n signal: AbortSignal\n}\n\n/** What an agent streams for one turn: answer text (deltas), a read-only tool call it ran, operations `checkEdit` accepted. */\nexport type ChatAgentPart =\n | { type: 'text', text: string }\n | { type: 'tool', call: ChatToolCall }\n | { type: 'edit', ops: AgentOp[], dropped: AgentOpProblem[] }\n\n/** A host-provided agent that answers document chat turns in place of the built-in model loop. A thrown error ends the turn as the stream's `error` event. */\nexport interface ChatAgent {\n turn(input: ChatAgentTurn): AsyncIterable<ChatAgentPart>\n}\n\n/**\n * JSON schema of the `edit_document` input: the list `ops`. Deliberately loose (only `type` is required), so a malformed\n * call reaches `checkAgentOps` and gets a precise retry message. With `workspace` (the host keeps change sets and a mutator)\n * an operation may name another Document by `documentId`.\n */\nexport function documentEditInputSchema({ workspace = false }: { workspace?: boolean } = {}): JSONSchema7 {\n return {\n type: 'object',\n properties: {\n ops: {\n type: 'array',\n description: 'Die Operationen in Dokumentreihenfolge.',\n items: {\n type: 'object',\n properties: {\n type: { type: 'string', enum: [...AGENT_OP_TYPES], description: 'update: Blockinhalt ersetzen; delete: Block löschen; add: Blöcke einfügen; convert: Blocktyp ändern.' },\n ...(workspace && { documentId: { type: 'string', 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.' } }),\n id: { type: 'string', description: 'update/delete/convert: ID des Blocks aus dem Dokument.' },\n afterId: { type: ['string', 'null'], description: 'add (Pflicht): ID des Blocks, nach dem eingefügt wird; für den Dokumentanfang ausdrücklich null.' },\n markdown: { type: 'string', description: 'update/add: der neue Inhalt als Markdown, ohne Block-ID.' },\n to: { type: 'string', description: 'convert: der neue Blocktyp, einer der im Systemprompt erlaubten Werte (z. B. paragraph, heading2, composite:task).' },\n },\n required: ['type'],\n },\n },\n },\n required: ['ops'],\n }\n}\n\n/** The `edit_document` tool of the document chat (name, description, `documentEditInputSchema`), as the built-in loop and `ChatAgentTurn.editTool` offer it. */\nexport function documentEditTool({ workspace = false }: { workspace?: boolean } = {}): ChatAgentEditTool {\n return {\n name: AGENT_TOOL,\n description: workspace\n ? 'Schlägt Änderungen vor. Operationen am geöffneten Dokument zeigt die Nutzerin bzw. der Nutzer im Dokument zur Prüfung an; Operationen mit documentId (anderes Dokument des Arbeitsbereichs) landen in einem Änderungssatz, den die Nutzerin bzw. der Nutzer je Dokument prüft und übernimmt oder ablehnt.'\n : 'Schlägt Änderungen am Dokument vor. Jede Operation wird der Nutzerin bzw. dem Nutzer im Dokument zur Prüfung angezeigt.',\n inputSchema: documentEditInputSchema({ workspace }),\n }\n}\n"],"mappings":";;;;;;;;AA6EA,SAAgB,wBAAwB,EAAE,YAAY,UAAmC,CAAC,GAAgB;CACxG,OAAO;EACL,MAAM;EACN,YAAY,EACV,KAAK;GACH,MAAM;GACN,aAAa;GACb,OAAO;IACL,MAAM;IACN,YAAY;KACV,MAAM;MAAE,MAAM;MAAU,MAAM,CAAC,GAAG,cAAc;MAAG,aAAa;KAAuG;KACvK,GAAI,aAAa,EAAE,YAAY;MAAE,MAAM;MAAU,aAAa;KAAwO,EAAE;KACxS,IAAI;MAAE,MAAM;MAAU,aAAa;KAAyD;KAC5F,SAAS;MAAE,MAAM,CAAC,UAAU,MAAM;MAAG,aAAa;KAAmG;KACrJ,UAAU;MAAE,MAAM;MAAU,aAAa;KAA2D;KACpG,IAAI;MAAE,MAAM;MAAU,aAAa;KAAqH;IAC1J;IACA,UAAU,CAAC,MAAM;GACnB;EACF,EACF;EACA,UAAU,CAAC,KAAK;CAClB;AACF;;AAGA,SAAgB,iBAAiB,EAAE,YAAY,UAAmC,CAAC,GAAsB;CACvG,OAAO;EACL,MAAM;EACN,aAAa,YACT,8SACA;EACJ,aAAa,wBAAwB,EAAE,UAAU,CAAC;CACpD;AACF"}
@@ -0,0 +1,22 @@
1
+ import { ToolProvider } from "./tools.js";
2
+ import { WorkspaceToolsOptions } from "./workspace-tools.js";
3
+ //#region src/server/document-tools.d.ts
4
+ /** The Markdown serializer `read_blocks` writes with: the options of `createWorkspaceTools`. */
5
+ export type DocumentToolsOptions = WorkspaceToolsOptions;
6
+ /**
7
+ * The read-only document tools of the document chat, for `EditorServerOptions.tools` / `provideEditorServer({ tools })`, next
8
+ * to `createWorkspaceTools`:
9
+ *
10
+ * - `document_outline(documentId?)`: the tree of all Blocks of the open Document (or of another one the user may read): per
11
+ * Block its ID, node type, heading level or Block type and title, the IDs of the Blocks directly inside it and the length of
12
+ * its text;
13
+ * - `read_blocks(documentId?, blockIds)`: up to 50 Blocks of one Document as Markdown, each cut like `read_block`
14
+ * (`READ_BLOCK_LIMIT`), all together at most eight times that.
15
+ *
16
+ * Both read the current content (live content of an open Document first, else `loadCurrentContent`) and need no index. An
17
+ * unreadable Document and an unknown Block answer the same sentence as `read_block`, so the tools never confirm that a
18
+ * Document or Block exists where the user may not look.
19
+ */
20
+ export declare function createDocumentTools(options?: DocumentToolsOptions): ToolProvider[];
21
+ //#endregion
22
+ //# sourceMappingURL=document-tools.d.ts.map
@@ -0,0 +1,190 @@
1
+ import { TYPED_BLOCK_TITLE, isTypedBlock, typeIdOf } from "../core/typed-block.js";
2
+ import { loadCurrentContent } from "./stored-content.js";
3
+ import { createBlockMarkdown } from "./block-index.js";
4
+ import { mayReadDocument } from "./readable-documents.js";
5
+ import { INLINE_NODES, findBlock } from "./document-blocks.js";
6
+ import { READ_BLOCK_LIMIT, parsed, unreadableBlock } from "./workspace-tools.js";
7
+ import { z } from "zod";
8
+ import { jsonSchema, tool } from "ai";
9
+ //#region src/server/document-tools.ts
10
+ /** Block IDs `read_blocks` reads in one call. */
11
+ const READ_BLOCKS_MAX = 50;
12
+ /** Characters of all Blocks one `read_blocks` call returns; the Blocks after the one that reaches it are left out with a note. */
13
+ const READ_BLOCKS_TOTAL_LIMIT = 8 * READ_BLOCK_LIMIT;
14
+ const outlineInput = z.object({ documentId: z.string().min(1).max(200).optional() });
15
+ const readBlocksInput = z.object({
16
+ documentId: z.string().min(1).max(200).optional(),
17
+ blockIds: z.array(z.string().min(1).max(100)).min(1).max(READ_BLOCKS_MAX)
18
+ });
19
+ const NO_DOCUMENT = "Im Chat ist kein Dokument geöffnet: gib documentId an.";
20
+ /** The current content of `documentId` (live first, else stored), `null` when the user may not read it or it does not exist. */
21
+ async function readableContent(ctx, documentId) {
22
+ if (!await mayReadDocument(ctx.ports, ctx.user, documentId)) return null;
23
+ return ctx.liveContent(documentId) ?? await loadCurrentContent(ctx.ports.documents, documentId);
24
+ }
25
+ const attrString = (node, key) => typeof node.attrs?.[key] === "string" ? node.attrs[key] : "";
26
+ /** The plain text of an inline node or a textblock's inline content, chips as their label. */
27
+ function inlineText(node) {
28
+ switch (node.type) {
29
+ case "text": return node.text ?? "";
30
+ case "hardBreak": return " ";
31
+ case "mention": return `@${attrString(node, "label") || attrString(node, "id")}`;
32
+ case "tag": return `#${attrString(node, "name")}`;
33
+ case "reference": return attrString(node, "label");
34
+ default: return (node.content ?? []).map(inlineText).join("");
35
+ }
36
+ }
37
+ /** The title of a Block as the Block index has it: a Typed block's title row, a heading's text, `''` for any other Block. */
38
+ function titleOf(node) {
39
+ if (isTypedBlock(node)) return (node.content ?? []).filter((child) => child.type === TYPED_BLOCK_TITLE).map(inlineText).join("").trim();
40
+ return node.type === "heading" ? inlineText(node).trim() : "";
41
+ }
42
+ /**
43
+ * Every Block of `content` (every non-inline node with an `id`, the rule of `blockLinesOfContent`), in document order, a Block
44
+ * before the Blocks nested in it. A Block's `children` are the Blocks directly inside it, through nodes without an ID (a list
45
+ * without one, say).
46
+ */
47
+ function outlineOf(content) {
48
+ const entries = [];
49
+ const walk = (node) => {
50
+ let chars = 0;
51
+ const blocks = [];
52
+ for (const child of node.content ?? []) {
53
+ if (child.type !== void 0 && INLINE_NODES.has(child.type)) {
54
+ chars += inlineText(child).length;
55
+ continue;
56
+ }
57
+ const id = child.attrs?.id;
58
+ if (typeof id !== "string" || !id) {
59
+ const inner = walk(child);
60
+ chars += inner.chars;
61
+ blocks.push(...inner.blocks);
62
+ continue;
63
+ }
64
+ const entry = {
65
+ id,
66
+ type: child.type ?? "",
67
+ chars: 0
68
+ };
69
+ if (child.type === "heading" && typeof child.attrs?.level === "number") entry.level = child.attrs.level;
70
+ if (isTypedBlock(child)) entry.blockType = typeIdOf(child);
71
+ const title = titleOf(child);
72
+ if (title) entry.title = title;
73
+ entries.push(entry);
74
+ const inner = walk(child);
75
+ entry.chars = inner.chars;
76
+ if (inner.blocks.length) entry.children = inner.blocks;
77
+ chars += inner.chars;
78
+ blocks.push(id);
79
+ }
80
+ return {
81
+ chars,
82
+ blocks
83
+ };
84
+ };
85
+ walk(content);
86
+ return entries;
87
+ }
88
+ /**
89
+ * The read-only document tools of the document chat, for `EditorServerOptions.tools` / `provideEditorServer({ tools })`, next
90
+ * to `createWorkspaceTools`:
91
+ *
92
+ * - `document_outline(documentId?)`: the tree of all Blocks of the open Document (or of another one the user may read): per
93
+ * Block its ID, node type, heading level or Block type and title, the IDs of the Blocks directly inside it and the length of
94
+ * its text;
95
+ * - `read_blocks(documentId?, blockIds)`: up to 50 Blocks of one Document as Markdown, each cut like `read_block`
96
+ * (`READ_BLOCK_LIMIT`), all together at most eight times that.
97
+ *
98
+ * Both read the current content (live content of an open Document first, else `loadCurrentContent`) and need no index. An
99
+ * unreadable Document and an unknown Block answer the same sentence as `read_block`, so the tools never confirm that a
100
+ * Document or Block exists where the user may not look.
101
+ */
102
+ function createDocumentTools(options = {}) {
103
+ let markdown = options.markdown;
104
+ const markdownOf = () => markdown ??= createBlockMarkdown({
105
+ ...options.schema && { schema: options.schema },
106
+ ...options.blockTypes && { blockTypes: options.blockTypes }
107
+ });
108
+ return [{
109
+ name: "document_outline",
110
+ tool: tool({
111
+ description: "Liefert die Gliederung eines Dokuments als JSON: alle Blöcke in Dokumentreihenfolge, je mit id, type (Knotentyp), level (bei Überschriften), blockType (bei typisierten Blöcken), title (Überschriftentext bzw. Titel des typisierten Blocks), children (IDs der direkt enthaltenen Blöcke) und chars (Textlänge in Zeichen, enthaltene Blöcke eingeschlossen). Ohne documentId das im Chat geöffnete Dokument in seinem aktuellen Stand; mit documentId ein anderes lesbares Dokument. Den Inhalt einzelner Blöcke liest du mit read_blocks.",
112
+ inputSchema: jsonSchema({
113
+ type: "object",
114
+ properties: { documentId: {
115
+ type: "string",
116
+ description: "ID des Dokuments; ohne Angabe das geöffnete Dokument."
117
+ } }
118
+ })
119
+ }),
120
+ async handle(ctx, input) {
121
+ const checked = parsed(outlineInput, input);
122
+ if ("problem" in checked) return checked.problem;
123
+ const documentId = checked.value.documentId ?? ctx.documentId;
124
+ if (!documentId) return NO_DOCUMENT;
125
+ const content = await readableContent(ctx, documentId);
126
+ if (!content) return `Das Dokument „${documentId}“ ist nicht lesbar oder existiert nicht.`;
127
+ const blocks = outlineOf(content);
128
+ if (!blocks.length) return `Das Dokument „${documentId}“ hat keine Blöcke.`;
129
+ return JSON.stringify({
130
+ documentId,
131
+ blocks
132
+ });
133
+ }
134
+ }, {
135
+ name: "read_blocks",
136
+ tool: tool({
137
+ description: `Liest Blöcke eines Dokuments als Markdown, bis zu ${READ_BLOCKS_MAX} auf einmal, in der angegebenen Reihenfolge. Ohne documentId aus dem im Chat geöffneten Dokument in seinem aktuellen Stand, mit documentId aus einem anderen lesbaren Dokument. Ein Block enthält die in ihm verschachtelten Blöcke. Block-IDs stammen aus document_outline oder find_blocks. Ein Block über ${READ_BLOCK_LIMIT} Zeichen wird gekürzt; ab ${READ_BLOCKS_TOTAL_LIMIT} Zeichen insgesamt werden die übrigen Blöcke ausgelassen und sind mit einem weiteren Aufruf zu lesen.`,
138
+ inputSchema: jsonSchema({
139
+ type: "object",
140
+ properties: {
141
+ documentId: {
142
+ type: "string",
143
+ description: "ID des Dokuments; ohne Angabe das geöffnete Dokument."
144
+ },
145
+ blockIds: {
146
+ type: "array",
147
+ items: { type: "string" },
148
+ minItems: 1,
149
+ maxItems: READ_BLOCKS_MAX,
150
+ description: "IDs der Blöcke im Dokument."
151
+ }
152
+ },
153
+ required: ["blockIds"]
154
+ })
155
+ }),
156
+ async handle(ctx, input) {
157
+ const checked = parsed(readBlocksInput, input);
158
+ if ("problem" in checked) return checked.problem;
159
+ const documentId = checked.value.documentId ?? ctx.documentId;
160
+ if (!documentId) return NO_DOCUMENT;
161
+ const blockIds = [...new Set(checked.value.blockIds)];
162
+ const content = await readableContent(ctx, documentId);
163
+ const parts = [];
164
+ let total = 0;
165
+ for (const [i, blockId] of blockIds.entries()) {
166
+ if (total >= READ_BLOCKS_TOTAL_LIMIT) {
167
+ const rest = blockIds.slice(i);
168
+ parts.push(`[… ${rest.length} weitere Blöcke ausgelassen, die Grenze von ${READ_BLOCKS_TOTAL_LIMIT} Zeichen ist erreicht: ${rest.join(", ")}]`);
169
+ break;
170
+ }
171
+ const node = content && findBlock(content, blockId);
172
+ if (!node) {
173
+ parts.push(unreadableBlock(documentId, blockId));
174
+ continue;
175
+ }
176
+ const title = titleOf(node);
177
+ const head = `Dokument „${documentId}“, Block „${blockId}“ (${isTypedBlock(node) ? typeIdOf(node) : node.type ?? ""}${title ? `, ${title}` : ""}):`;
178
+ const body = markdownOf()([node]).trim();
179
+ const shown = body.length > 6e3 ? `${body.slice(0, READ_BLOCK_LIMIT)}\n\n[… gekürzt, ${body.length - READ_BLOCK_LIMIT} Zeichen fehlen]` : body;
180
+ total += Math.min(body.length, READ_BLOCK_LIMIT);
181
+ parts.push(`${head}\n\n${shown}`);
182
+ }
183
+ return parts.join("\n\n---\n\n");
184
+ }
185
+ }];
186
+ }
187
+ //#endregion
188
+ export { createDocumentTools };
189
+
190
+ //# sourceMappingURL=document-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-tools.js","names":[],"sources":["../../src/server/document-tools.ts"],"sourcesContent":["// i18n-ignore-file: model-facing tool descriptions and results (German, like the chat's system prompt)\nimport type { JSONContent } from '@tiptap/core'\nimport { jsonSchema, tool } from 'ai'\nimport { z } from 'zod'\nimport type { BlockMarkdown } from '../core/block-index'\nimport { isTypedBlock, TYPED_BLOCK_TITLE, typeIdOf } from '../core/typed-block'\nimport { createBlockMarkdown } from './block-index'\nimport { findBlock, INLINE_NODES } from './document-blocks'\nimport { mayReadDocument } from './readable-documents'\nimport { loadCurrentContent } from './stored-content'\nimport type { ToolContext, ToolProvider } from './tools'\nimport { parsed, READ_BLOCK_LIMIT, unreadableBlock, type WorkspaceToolsOptions } from './workspace-tools'\n\n// Read-only tools of the document chat that look into one Document as a whole: its tree of Blocks and several Blocks at once.\n// Unlike `createWorkspaceTools` they read the Document's content directly (no Block index): the live content of a Document\n// open in the collaboration server first, else the stored content in the current shape.\n\n/** Block IDs `read_blocks` reads in one call. */\nconst READ_BLOCKS_MAX = 50\n/** Characters of all Blocks one `read_blocks` call returns; the Blocks after the one that reaches it are left out with a note. */\nconst READ_BLOCKS_TOTAL_LIMIT = 8 * READ_BLOCK_LIMIT\n\n/** The Markdown serializer `read_blocks` writes with: the options of `createWorkspaceTools`. */\nexport type DocumentToolsOptions = WorkspaceToolsOptions\n\nconst outlineInput = z.object({\n documentId: z.string().min(1).max(200).optional(),\n})\nconst readBlocksInput = z.object({\n documentId: z.string().min(1).max(200).optional(),\n blockIds: z.array(z.string().min(1).max(100)).min(1).max(READ_BLOCKS_MAX),\n})\n\nconst NO_DOCUMENT = 'Im Chat ist kein Dokument geöffnet: gib documentId an.'\n\n/** The current content of `documentId` (live first, else stored), `null` when the user may not read it or it does not exist. */\nasync function readableContent(ctx: ToolContext, documentId: string): Promise<JSONContent | null> {\n if (!await mayReadDocument(ctx.ports, ctx.user, documentId)) return null\n return ctx.liveContent(documentId) ?? await loadCurrentContent(ctx.ports.documents, documentId)\n}\n\nconst attrString = (node: JSONContent, key: string): string => typeof node.attrs?.[key] === 'string' ? node.attrs[key] as string : ''\n\n/** The plain text of an inline node or a textblock's inline content, chips as their label. */\nfunction inlineText(node: JSONContent): string {\n switch (node.type) {\n case 'text': return node.text ?? ''\n case 'hardBreak': return ' '\n case 'mention': return `@${attrString(node, 'label') || attrString(node, 'id')}`\n case 'tag': return `#${attrString(node, 'name')}`\n case 'reference': return attrString(node, 'label')\n default: return (node.content ?? []).map(inlineText).join('')\n }\n}\n\n/** The title of a Block as the Block index has it: a Typed block's title row, a heading's text, `''` for any other Block. */\nfunction titleOf(node: JSONContent): string {\n if (isTypedBlock(node)) return (node.content ?? []).filter(child => child.type === TYPED_BLOCK_TITLE).map(inlineText).join('').trim()\n return node.type === 'heading' ? inlineText(node).trim() : ''\n}\n\n/** One Block of `document_outline`. */\ninterface OutlineEntry {\n id: string\n /** Node type (`paragraph`, `heading`, `typedBlock`, …). */\n type: string\n /** Level of a heading. */\n level?: number\n /** Block type id of a Typed block. */\n blockType?: string\n /** Title of a Typed block, text of a heading. */\n title?: string\n /** IDs of the Blocks directly inside this one, in document order. */\n children?: string[]\n /** Characters of the Block's text, nested Blocks included. */\n chars: number\n}\n\n/**\n * Every Block of `content` (every non-inline node with an `id`, the rule of `blockLinesOfContent`), in document order, a Block\n * before the Blocks nested in it. A Block's `children` are the Blocks directly inside it, through nodes without an ID (a list\n * without one, say).\n */\nfunction outlineOf(content: JSONContent): OutlineEntry[] {\n const entries: OutlineEntry[] = []\n const walk = (node: JSONContent): { chars: number, blocks: string[] } => {\n let chars = 0\n const blocks: string[] = []\n for (const child of node.content ?? []) {\n if (child.type !== undefined && INLINE_NODES.has(child.type)) {\n chars += inlineText(child).length\n continue\n }\n const id = child.attrs?.id\n if (typeof id !== 'string' || !id) {\n const inner = walk(child)\n chars += inner.chars\n blocks.push(...inner.blocks)\n continue\n }\n const entry: OutlineEntry = { id, type: child.type ?? '', chars: 0 }\n if (child.type === 'heading' && typeof child.attrs?.level === 'number') entry.level = child.attrs.level\n if (isTypedBlock(child)) entry.blockType = typeIdOf(child)\n const title = titleOf(child)\n if (title) entry.title = title\n entries.push(entry)\n const inner = walk(child)\n entry.chars = inner.chars\n if (inner.blocks.length) entry.children = inner.blocks\n chars += inner.chars\n blocks.push(id)\n }\n return { chars, blocks }\n }\n walk(content)\n return entries\n}\n\n/**\n * The read-only document tools of the document chat, for `EditorServerOptions.tools` / `provideEditorServer({ tools })`, next\n * to `createWorkspaceTools`:\n *\n * - `document_outline(documentId?)`: the tree of all Blocks of the open Document (or of another one the user may read): per\n * Block its ID, node type, heading level or Block type and title, the IDs of the Blocks directly inside it and the length of\n * its text;\n * - `read_blocks(documentId?, blockIds)`: up to 50 Blocks of one Document as Markdown, each cut like `read_block`\n * (`READ_BLOCK_LIMIT`), all together at most eight times that.\n *\n * Both read the current content (live content of an open Document first, else `loadCurrentContent`) and need no index. An\n * unreadable Document and an unknown Block answer the same sentence as `read_block`, so the tools never confirm that a\n * Document or Block exists where the user may not look.\n */\nexport function createDocumentTools(options: DocumentToolsOptions = {}): ToolProvider[] {\n let markdown = options.markdown\n const markdownOf = (): BlockMarkdown => markdown ??= createBlockMarkdown({\n ...(options.schema && { schema: options.schema }),\n ...(options.blockTypes && { blockTypes: options.blockTypes }),\n })\n\n const documentOutline: ToolProvider = {\n name: 'document_outline',\n tool: tool({\n description: 'Liefert die Gliederung eines Dokuments als JSON: alle Blöcke in Dokumentreihenfolge, je mit id, type (Knotentyp), level (bei Überschriften), blockType (bei typisierten Blöcken), title (Überschriftentext bzw. Titel des typisierten Blocks), children (IDs der direkt enthaltenen Blöcke) und chars (Textlänge in Zeichen, enthaltene Blöcke eingeschlossen). Ohne documentId das im Chat geöffnete Dokument in seinem aktuellen Stand; mit documentId ein anderes lesbares Dokument. Den Inhalt einzelner Blöcke liest du mit read_blocks.',\n inputSchema: jsonSchema<{ documentId?: string }>({\n type: 'object',\n properties: {\n documentId: { type: 'string', description: 'ID des Dokuments; ohne Angabe das geöffnete Dokument.' },\n },\n }),\n }),\n async handle(ctx, input) {\n const checked = parsed(outlineInput, input)\n if ('problem' in checked) return checked.problem\n const documentId = checked.value.documentId ?? ctx.documentId\n if (!documentId) return NO_DOCUMENT\n const content = await readableContent(ctx, documentId)\n // The same sentence for an unreadable and a missing Document: the tool never confirms that a Document exists the user may not read.\n if (!content) return `Das Dokument „${documentId}“ ist nicht lesbar oder existiert nicht.`\n const blocks = outlineOf(content)\n if (!blocks.length) return `Das Dokument „${documentId}“ hat keine Blöcke.`\n return JSON.stringify({ documentId, blocks })\n },\n }\n\n const readBlocks: ToolProvider = {\n name: 'read_blocks',\n tool: tool({\n description: `Liest Blöcke eines Dokuments als Markdown, bis zu ${READ_BLOCKS_MAX} auf einmal, in der angegebenen Reihenfolge. Ohne documentId aus dem im Chat geöffneten Dokument in seinem aktuellen Stand, mit documentId aus einem anderen lesbaren Dokument. Ein Block enthält die in ihm verschachtelten Blöcke. Block-IDs stammen aus document_outline oder find_blocks. Ein Block über ${READ_BLOCK_LIMIT} Zeichen wird gekürzt; ab ${READ_BLOCKS_TOTAL_LIMIT} Zeichen insgesamt werden die übrigen Blöcke ausgelassen und sind mit einem weiteren Aufruf zu lesen.`,\n inputSchema: jsonSchema<{ documentId?: string, blockIds: string[] }>({\n type: 'object',\n properties: {\n documentId: { type: 'string', description: 'ID des Dokuments; ohne Angabe das geöffnete Dokument.' },\n blockIds: { type: 'array', items: { type: 'string' }, minItems: 1, maxItems: READ_BLOCKS_MAX, description: 'IDs der Blöcke im Dokument.' },\n },\n required: ['blockIds'],\n }),\n }),\n async handle(ctx, input) {\n const checked = parsed(readBlocksInput, input)\n if ('problem' in checked) return checked.problem\n const documentId = checked.value.documentId ?? ctx.documentId\n if (!documentId) return NO_DOCUMENT\n const blockIds = [...new Set(checked.value.blockIds)]\n const content = await readableContent(ctx, documentId)\n const parts: string[] = []\n let total = 0\n for (const [i, blockId] of blockIds.entries()) {\n if (total >= READ_BLOCKS_TOTAL_LIMIT) {\n const rest = blockIds.slice(i)\n parts.push(`[… ${rest.length} weitere Blöcke ausgelassen, die Grenze von ${READ_BLOCKS_TOTAL_LIMIT} Zeichen ist erreicht: ${rest.join(', ')}]`)\n break\n }\n const node = content && findBlock(content, blockId)\n if (!node) {\n parts.push(unreadableBlock(documentId, blockId))\n continue\n }\n const title = titleOf(node)\n const head = `Dokument „${documentId}“, Block „${blockId}“ (${isTypedBlock(node) ? typeIdOf(node) : node.type ?? ''}${title ? `, ${title}` : ''}):`\n const body = markdownOf()([node]).trim()\n const shown = body.length > READ_BLOCK_LIMIT ? `${body.slice(0, READ_BLOCK_LIMIT)}\\n\\n[… gekürzt, ${body.length - READ_BLOCK_LIMIT} Zeichen fehlen]` : body\n total += Math.min(body.length, READ_BLOCK_LIMIT)\n parts.push(`${head}\\n\\n${shown}`)\n }\n return parts.join('\\n\\n---\\n\\n')\n },\n }\n\n return [documentOutline, readBlocks]\n}\n"],"mappings":"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"}
@@ -130,7 +130,8 @@ export declare const chatBodySchema: z.ZodObject<{
130
130
  * Operations of the open Document (`documentId` absent or the body's) are `ops`; operations naming another Document the
131
131
  * user may edit (`mayEditDocument`) become one `ChangeSet` (`ChangeSetStore`) for the user to review per Document, when the
132
132
  * host has the `changeSets` port and a mutator; else such an operation is a `document` problem the model retries. All
133
- * operations share one numbering (`op<n>`).
133
+ * operations share one numbering (`op<n>`). With a host agent (`AiHandlerContext.chatAgent` answers one for the user) the
134
+ * agent answers the turn in place of the model loop (`agentChat`), with the same checks and events.
134
135
  */
135
136
  export declare function chatPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse>;
136
137
  /** Short inline continuation (ghost text) of the text before the caret. Plain JSON: `{ text }`, empty when nothing fits. */