Skip to content
548 linesCodeBlameRaw
1/**
2 * Docs: the workspace's written knowledge, kept by the docs service
3 * (`services/docs`). Spaces hold trees of pages; each page is a CRDT
4 * document (Yjs) edited live over a socket, saved with a Markdown
5 * rendition that search, agents, export and the read view use. Plan and
6 * decisions: docs/WORKSPACE.md, "Docs".
7 *
8 * Wire shapes are snake_case end to end: the site, agents and the live
9 * socket all carry the same objects.
10 *
11 * Access, in one place:
12 *
13 * - A space has a kind: `workspace` (every member gets `default_role`),
14 * `team` (the team's members get `default_role`) or `private` (only the
15 * people, agents and teams listed as its members).
16 * - Members are listed with a role: view < comment < edit < manage. A
17 * person's role is the highest of the space's base role (when it applies
18 * to them) and every listing that names them or one of their teams.
19 * Workspace owners manage every workspace and team space; a private space
20 * is its members' alone.
21 * - An agent never sees or changes more than the person it acts for (the
22 * `viewer` on every agent call). It reads what that person can read,
23 * narrowed further to what every person in the `audience` can read; it
24 * suggests where that person can comment; and it edits directly only
25 * where that person can edit AND the space lets agents edit
26 * (`agent_mode: "edit"`). Otherwise its edit becomes a suggestion.
27 */
28import type { MemberProfile, Principal } from "./chat";
29import type { ServiceBinding } from "./clients";
30import type { User } from "./identity";
31import type { Result } from "./result";
32
33/** What someone may do in a space, weakest first. */
34export type DocRole = "view" | "comment" | "edit" | "manage";
35export const DOC_ROLES: readonly DocRole[] = ["view", "comment", "edit", "manage"];
36
37export const DOC_ROLE_LABELS: Record<DocRole, string> = {
38 view: "Can view",
39 comment: "Can comment",
40 edit: "Can edit",
41 manage: "Full access",
42};
43
44export const DOC_ROLE_SUMMARIES: Record<DocRole, string> = {
45 view: "Read pages and their history.",
46 comment: "Read pages and comment on them.",
47 edit: "Write and organize pages, and accept or reject suggestions.",
48 manage: "Edit, and change who has access and how agents work here.",
49};
50
51/** Who a space is for. */
52export type DocSpaceKind = "workspace" | "team" | "private";
53
54export const DOC_SPACE_KIND_LABELS: Record<DocSpaceKind, string> = {
55 workspace: "Everyone in the workspace",
56 team: "A team",
57 private: "Only its members",
58};
59
60/** How agents change pages in a space: as tracked suggestions (the default), or directly. */
61export type DocAgentMode = "suggest" | "edit";
62
63export const DOC_AGENT_MODE_LABELS: Record<DocAgentMode, string> = {
64 suggest: "Suggest changes",
65 edit: "Edit directly",
66};
67
68/** A key for a space member: `user:<id>`, `agent:<id>` or `team:<slug>`. */
69export type DocMemberKey = string;
70
71export type DocSpace = {
72 id: string;
73 workspace_id: string;
74 /** Unique in the workspace; in the URL: `/<workspace>/-/docs/<slug>`. */
75 slug: string;
76 name: string;
77 description: string | null;
78 /** An emoji, or null for the default book. */
79 icon: string | null;
80 kind: DocSpaceKind;
81 /** The team's slug, for a team space. */
82 team: string | null;
83 /** What every workspace member (workspace) or team member (team) gets; null for a private space. */
84 default_role: DocRole | null;
85 agent_mode: DocAgentMode;
86 /** The workspace's General space, made the first time Docs is opened. Can't be archived. */
87 is_default: boolean;
88 /** Projects (repositories, `owner/name`) the space is about: Docs filters by them. */
89 projects: string[];
90 created_by: Principal;
91 created_at: string;
92 archived_at: string | null;
93 /** The viewer's role in it. */
94 viewer_role: DocRole;
95 /** Pages in it that are not in the trash. */
96 page_count: number;
97};
98
99/** A space member, as the space's settings show it. */
100export type DocSpaceMember = {
101 key: DocMemberKey;
102 kind: "user" | "agent" | "team";
103 /** Username, agent handle or team slug. */
104 name: string;
105 display_name: string;
106 avatar: string | null;
107 avatar_seed?: string | null;
108 role: DocRole;
109};
110
111/** Enough to link to a page. */
112export type DocPageRef = {
113 id: string;
114 space_id: string;
115 space_slug: string;
116 title: string;
117 /** An emoji, or null for the default page icon. */
118 icon: string | null;
119 /** `<title-slug>-<id>`: the last part of the page's URL. */
120 slug: string;
121 /** `/<workspace>/-/docs/<space>/<slug>`. */
122 path: string;
123};
124
125export type DocPage = DocPageRef & {
126 parent_id: string | null;
127 position: number;
128 /** A cover image's URL, or a CSS gradient name (`gradient:<n>`); null for none. */
129 cover: string | null;
130 created_by: MemberProfile;
131 created_at: string;
132 updated_by: MemberProfile | null;
133 updated_at: string;
134 /** When it went to the trash; null when it is not there. */
135 archived_at: string | null;
136 has_children: boolean;
137 /** Projects (`owner/name`) the page is about, besides its space's. */
138 projects: string[];
139 owners: MemberProfile[];
140 /** The first lines of its text, for cards. */
141 excerpt: string;
142};
143
144/** A page as the sidebar's tree lists it: flat, ordered by `position` within each parent. */
145export type DocTreeNode = {
146 id: string;
147 parent_id: string | null;
148 position: number;
149 title: string;
150 icon: string | null;
151 slug: string;
152};
153
154export type DocsSidebarSpace = DocSpace & { pages: DocTreeNode[] };
155
156export type DocsSidebar = {
157 spaces: DocsSidebarSpace[];
158 favorites: DocPageRef[];
159 /** The pages the viewer opened last, newest first. */
160 recent: DocPageRef[];
161 /** Whether the viewer may make spaces (members of the workspace may). */
162 can_create_space: boolean;
163 trash_count: number;
164};
165
166export type DocsHome = {
167 /** Recently edited pages the viewer can read, newest first. */
168 recent: DocPage[];
169 /** Pages the viewer made or owns. */
170 mine: DocPage[];
171 spaces: DocSpace[];
172 /** Every project some space or page is linked to, for the filter. */
173 projects: string[];
174 /** The project the lists are filtered to, or null. */
175 project: string | null;
176};
177
178/** A tracked change an agent proposed. `blocks` targets name top-level block ids from `page_markdown`. */
179export type DocEditTarget =
180 /** Add to the end of the page. */
181 | { kind: "append" }
182 /** Replace the whole page. */
183 | { kind: "document" }
184 /** Replace a section: the heading whose text matches (case-insensitive) and everything under it, up to the next heading of the same or a higher level. The new Markdown should include the heading if it is to stay. */
185 | { kind: "section"; heading: string }
186 /** Replace top-level blocks `from_block` through `to_block`, inclusive (with any blocks nested under them). */
187 | { kind: "blocks"; from_block: string; to_block: string };
188
189export type DocSuggestionStatus = "open" | "accepted" | "rejected" | "stale";
190
191export type DocSuggestion = {
192 id: string;
193 page_id: string;
194 /** The agent that suggested it. */
195 author: MemberProfile;
196 /** The person it acted for. */
197 asked_by: MemberProfile | null;
198 target: DocEditTarget;
199 /** The target's Markdown when it was suggested. */
200 before_markdown: string;
201 /** What it proposes instead. */
202 after_markdown: string;
203 /** Why, in a line. */
204 note: string | null;
205 status: DocSuggestionStatus;
206 created_at: string;
207 decided_by: MemberProfile | null;
208 decided_at: string | null;
209 /** The top-level blocks it covers now, so the editor can mark them; empty for an append or when the target is gone. */
210 block_ids: string[];
211};
212
213export type DocVersionKind = "created" | "edit" | "agent" | "suggestion" | "restore";
214
215export type DocVersion = {
216 id: string;
217 page_id: string;
218 created_at: string;
219 /** Everyone whose changes are in it, people and agents. */
220 authors: MemberProfile[];
221 kind: DocVersionKind;
222 /** "Suggested by @inky, accepted by @ana"; "Restored from Oct 3, 14:02". */
223 note: string | null;
224};
225
226export type DocDiffLine = { op: "same" | "add" | "del"; text: string };
227
228export type DocVersionDetail = DocVersion & {
229 markdown: string;
230 /** Against the version before it (or nothing, for the first). */
231 diff: DocDiffLine[];
232};
233
234export type DocPageDetail = {
235 page: DocPage;
236 space: DocSpace;
237 /** The page's ancestors, root first. */
238 breadcrumbs: DocPageRef[];
239 /** As last saved: the read view, and what the editor shows before the socket connects. */
240 markdown: string;
241 role: DocRole;
242 backlinks: DocPageRef[];
243 children: DocPageRef[];
244 favorite: boolean;
245 /** When the viewer last opened it, before now. */
246 last_viewed_at: string | null;
247 suggestions: DocSuggestion[];
248};
249
250export type DocSearchHit = DocPageRef & {
251 space_name: string;
252 /** Text around the match; `[[` and `]]` mark matched words. */
253 snippet: string;
254 updated_at: string;
255 projects: string[];
256};
257
258export type DocTemplate = {
259 id: string;
260 name: string;
261 description: string;
262 icon: string;
263 /** Built in (meeting notes, RFC, ...) rather than saved by the workspace. */
264 builtin: boolean;
265 markdown: string;
266 created_by: Principal | null;
267};
268
269/** One top-level block, as agents see a page. */
270export type DocBlockOutline = {
271 id: string;
272 /** BlockNote's type: `heading`, `paragraph`, `bulletListItem`, `codeBlock`, `callout`, ... */
273 type: string;
274 /** For a heading. */
275 level: number | null;
276 /** The block and anything nested under it. */
277 markdown: string;
278};
279
280/** What an agent may do on a page, for the person it acts for. */
281export type DocAgentAbilities = { read: boolean; suggest: boolean; edit: boolean };
282
283export type DocAgentPage = {
284 page: DocPageRef & { updated_at: string };
285 space: { id: string; slug: string; name: string; agent_mode: DocAgentMode };
286 markdown: string;
287 blocks: DocBlockOutline[];
288 can: DocAgentAbilities;
289};
290
291/** Who will see what an agent says; it reads only what they all can. */
292export type DocAudience =
293 /** These people (by user id), e.g. a DM's or a private channel's members. */
294 | { kind: "people"; user_ids: string[] }
295 /** Everyone in the workspace, e.g. a public channel: only workspace-wide spaces. */
296 | { kind: "workspace" };
297
298/** What `apply_edit` did: applied it, or filed a suggestion because it may not edit there. */
299export type DocAgentEditResult =
300 | { mode: "applied"; version_id: string | null; page: DocPageRef }
301 | { mode: "suggested"; suggestion: DocSuggestion; page: DocPageRef };
302
303export type NewDocSpace = {
304 name: string;
305 slug?: string | null;
306 description?: string | null;
307 icon?: string | null;
308 kind: DocSpaceKind;
309 team?: string | null;
310 default_role?: DocRole | null;
311 agent_mode?: DocAgentMode | null;
312 projects?: string[] | null;
313};
314
315export type DocSpaceChange = Partial<Omit<NewDocSpace, "kind">> & { kind?: DocSpaceKind; archived?: boolean };
316
317export type NewDocPage = {
318 space_id: string;
319 parent_id?: string | null;
320 title?: string | null;
321 icon?: string | null;
322 /** A built-in template's id (`builtin:<name>`) or a saved one's. */
323 template_id?: string | null;
324 /** Starting Markdown, when there is no template. */
325 markdown?: string | null;
326 projects?: string[] | null;
327};
328
329export type DocPageChange = {
330 title?: string;
331 icon?: string | null;
332 cover?: string | null;
333 projects?: string[];
334 /** Member keys (`user:<id>`, `agent:<id>`). */
335 owners?: string[];
336};
337
338/** Where a page goes: under `parent_id` (null for the top of the space), before `before_id` (null for the end). */
339export type DocMove = { space_id?: string | null; parent_id: string | null; before_id?: string | null };
340
341export type DocSearchQuery = {
342 query: string;
343 space_id?: string | null;
344 /** `owner/name`: pages linked to it, or in a space linked to it. */
345 project?: string | null;
346 limit?: number | null;
347};
348
349/**
350 * The comment operations of the editor's thread store (BlockNote's
351 * `RESTYjsThreadStore`), applied by the page's room to the `threads` map in
352 * the page's Yjs document, so every open editor sees them at once.
353 */
354export type DocThreadAction =
355 | { op: "create"; body: unknown; metadata?: unknown; page_level?: boolean }
356 /** Anchors a thread to the text between two Yjs relative positions (JSON). */
357 | { op: "anchor"; thread_id: string; anchor: unknown; head: unknown }
358 | { op: "comment"; thread_id: string; body: unknown; metadata?: unknown }
359 | { op: "edit_comment"; thread_id: string; comment_id: string; body: unknown; metadata?: unknown }
360 | { op: "delete_comment"; thread_id: string; comment_id: string; soft?: boolean }
361 | { op: "delete_thread"; thread_id: string }
362 | { op: "resolve" | "unresolve"; thread_id: string }
363 | { op: "react" | "unreact"; thread_id: string; comment_id: string; emoji: string };
364
365/** A comment thread, as agents and the inbox see it. */
366export type DocThread = {
367 id: string;
368 /** The text it is on; null for a comment on the whole page. */
369 quote: string | null;
370 resolved: boolean;
371 comments: { id: string; author: MemberProfile; text: string; created_at: string }[];
372};
373
374/** A file put in a page: served from the usercontent origin. */
375export type DocFile = { id: string; url: string; name: string; content_type: string; bytes: number };
376
377/**
378 * What a page's live socket carries besides the Yjs protocol (binary
379 * frames: y-protocols sync and awareness). These are JSON text frames.
380 */
381export type DocsLiveEvent =
382 | { type: "page.updated"; page: DocPage }
383 | { type: "page.archived"; page_id: string }
384 | { type: "suggestion.created" | "suggestion.updated"; suggestion: DocSuggestion }
385 | { type: "version.created"; version: DocVersion }
386 /** The viewer's role changed, or their access ended (`role` null). */
387 | { type: "access"; role: DocRole | null };
388
389/**
390 * Header the site sets on a forwarded live socket and on uploads: the
391 * viewer, as JSON. Trusted only because the docs service is reachable
392 * through service bindings alone.
393 */
394export const DOCS_VIEWER_HEADER = "x-g1t-docs-viewer";
395
396/** The largest file a page takes, in bytes. */
397export const DOC_MAX_FILE_BYTES = 25 * 1024 * 1024;
398
399/** The name of the Yjs XML fragment that holds a page's blocks. */
400export const DOC_FRAGMENT = "document-store";
401/** The name of the Yjs map that holds a page's comment threads. */
402export const DOC_THREADS = "threads";
403
404export type DocsApi = {
405 // ── The site ─────────────────────────────────────────────────────────
406
407 /** Spaces with their page trees, favorites, recent pages. Makes the General space the first time. */
408 sidebar(workspace: string, viewer: User): Promise<Result<DocsSidebar>>;
409 home(workspace: string, viewer: User, options?: { project?: string | null }): Promise<Result<DocsHome>>;
410 space(workspace: string, spaceSlug: string, viewer: User): Promise<Result<{ space: DocSpace; members: DocSpaceMember[]; pages: DocPage[] }>>;
411 createSpace(workspace: string, viewer: User, input: NewDocSpace): Promise<Result<DocSpace>>;
412 /** Manage role only. */
413 updateSpace(workspace: string, spaceId: string, viewer: User, change: DocSpaceChange): Promise<Result<DocSpace>>;
414 /** Adds, changes (`role`) or removes (`role` null) a member. Manage role only; the last manager stays. */
415 setSpaceMember(workspace: string, spaceId: string, viewer: User, member: DocMemberKey, role: DocRole | null): Promise<Result<DocSpaceMember[]>>;
416
417 /** A page, and the viewer's role in it; records the view. Not found when they can't read it. */
418 page(workspace: string, pageId: string, viewer: User): Promise<Result<DocPageDetail>>;
419 createPage(workspace: string, viewer: User, input: NewDocPage): Promise<Result<DocPage>>;
420 updatePage(workspace: string, pageId: string, viewer: User, change: DocPageChange): Promise<Result<DocPage>>;
421 /** Edit role in both spaces. Refuses moving a page under itself. */
422 movePage(workspace: string, pageId: string, viewer: User, move: DocMove): Promise<Result<DocPage>>;
423 duplicatePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
424 /** To the trash, with every page under it. */
425 archivePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
426 restorePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
427 /** For good: only from the trash, manage role. */
428 deletePage(workspace: string, pageId: string, viewer: User): Promise<Result<boolean>>;
429 trash(workspace: string, viewer: User): Promise<Result<DocPage[]>>;
430 favorite(workspace: string, pageId: string, viewer: User, on: boolean): Promise<Result<boolean>>;
431
432 search(workspace: string, viewer: User, query: DocSearchQuery): Promise<Result<DocSearchHit[]>>;
433
434 versions(workspace: string, pageId: string, viewer: User): Promise<Result<DocVersion[]>>;
435 version(workspace: string, pageId: string, versionId: string, viewer: User): Promise<Result<DocVersionDetail>>;
436 /** Makes the page what it was then, as a new version. Edit role. */
437 restoreVersion(workspace: string, pageId: string, versionId: string, viewer: User): Promise<Result<DocVersion>>;
438
439 templates(workspace: string, viewer: User): Promise<Result<DocTemplate[]>>;
440 /** Saves a page as one of the workspace's templates. */
441 saveTemplate(workspace: string, viewer: User, input: { page_id: string; name: string; description?: string | null }): Promise<Result<DocTemplate>>;
442 deleteTemplate(workspace: string, templateId: string, viewer: User): Promise<Result<boolean>>;
443
444 exportPage(workspace: string, pageId: string, viewer: User): Promise<Result<{ filename: string; markdown: string }>>;
445 /** Every page in a space as Markdown, at paths that follow the tree. */
446 exportSpace(workspace: string, spaceId: string, viewer: User): Promise<Result<{ name: string; files: { path: string; markdown: string }[] }>>;
447
448 suggestions(workspace: string, pageId: string, viewer: User): Promise<Result<DocSuggestion[]>>;
449 /** Accept (applies it to the live document, attributed to both) or reject. Edit role. */
450 decideSuggestion(workspace: string, suggestionId: string, viewer: User, decision: "accept" | "reject"): Promise<Result<DocSuggestion>>;
451 acceptAll(workspace: string, pageId: string, viewer: User): Promise<Result<DocSuggestion[]>>;
452
453 /** A comment operation from the editor; comment role (deleting others' comments or threads needs edit). */
454 thread(workspace: string, pageId: string, viewer: User, action: DocThreadAction): Promise<Result<unknown>>;
455 threads(workspace: string, pageId: string, viewer: User): Promise<Result<DocThread[]>>;
456
457 // ── Agents (services/agents) ─────────────────────────────────────────
458 //
459 // Each takes the agent and the person it acts for (`viewer`, the asker).
460 // The service checks the agent is a live agent of the workspace and caps
461 // everything by the viewer's access; `audience`, when given, narrows
462 // reads further to what every person in it can read. A page the agent
463 // may not read is not found, exactly as one that does not exist.
464
465 /** Spaces the agent may read for the viewer (and audience), with what it may do in each. */
466 spacesForAgent(workspace: string, agentId: string, viewer: User, audience?: DocAudience | null): Promise<Result<(Pick<DocSpace, "id" | "slug" | "name" | "description" | "kind" | "agent_mode" | "projects"> & { can: DocAgentAbilities })[]>>;
467 /** A page's Markdown and its top-level blocks (ids for `blocks` targets). */
468 pageMarkdown(workspace: string, agentId: string, viewer: User, pageId: string, audience?: DocAudience | null): Promise<Result<DocAgentPage>>;
469 /** Full-text search over pages every reader can read; at most 20, best first. */
470 searchForAgent(workspace: string, agentId: string, viewer: User, query: DocSearchQuery, audience?: DocAudience | null): Promise<Result<DocSearchHit[]>>;
471 /** Files a tracked suggestion; people with edit access accept or reject it inline. Needs the viewer's comment role. Notifies the page's owners. */
472 suggestEdit(workspace: string, agentId: string, viewer: User, pageId: string, edit: { target: DocEditTarget; markdown: string; note?: string | null }): Promise<Result<DocSuggestion>>;
473 /**
474 * Applies an edit to the live document when the space lets agents edit
475 * and the viewer can edit; otherwise files it as a suggestion (and says
476 * so in `mode`). Attributed to the agent in the page's history.
477 */
478 applyEdit(workspace: string, agentId: string, viewer: User, pageId: string, edit: { target: DocEditTarget; markdown: string; note?: string | null }): Promise<Result<DocAgentEditResult>>;
479 /**
480 * A new page, written by the agent: in `space_id` (the General space
481 * when null), under `parent_id`. Needs the viewer's edit role there
482 * (whatever the space's agent mode: a new page changes nothing anyone
483 * wrote). `source` links where it came from ("write this up").
484 */
485 createPageAsAgent(
486 workspace: string,
487 agentId: string,
488 viewer: User,
489 input: { space_id?: string | null; parent_id?: string | null; title: string; icon?: string | null; markdown: string; source?: { title: string; href: string } | null },
490 ): Promise<Result<DocPageRef>>;
491 /** A page's comment threads, for an agent asked about them. */
492 threadsForAgent(workspace: string, agentId: string, viewer: User, pageId: string, audience?: DocAudience | null): Promise<Result<DocThread[]>>;
493};
494
495async function rpc<T>(service: ServiceBinding, method: string, args: object): Promise<T> {
496 const response = await service.fetch(`https://service/rpc/${method}`, {
497 method: "POST",
498 headers: { "content-type": "application/json" },
499 body: JSON.stringify(args),
500 });
501 if (!response.ok) throw new Error(`${method} failed with status ${response.status}`);
502 return (await response.json()) as T;
503}
504
505export function docsClient(service: ServiceBinding): DocsApi {
506 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
507 return {
508 sidebar: (workspace, viewer) => call("sidebar", { workspace, viewer }),
509 home: (workspace, viewer, options) => call("home", { workspace, viewer, project: options?.project ?? null }),
510 space: (workspace, spaceSlug, viewer) => call("space", { workspace, space: spaceSlug, viewer }),
511 createSpace: (workspace, viewer, input) => call("create_space", { workspace, viewer, input }),
512 updateSpace: (workspace, spaceId, viewer, change) => call("update_space", { workspace, space_id: spaceId, viewer, change }),
513 setSpaceMember: (workspace, spaceId, viewer, member, role) => call("set_space_member", { workspace, space_id: spaceId, viewer, member, role }),
514 page: (workspace, pageId, viewer) => call("page", { workspace, page_id: pageId, viewer }),
515 createPage: (workspace, viewer, input) => call("create_page", { workspace, viewer, input }),
516 updatePage: (workspace, pageId, viewer, change) => call("update_page", { workspace, page_id: pageId, viewer, change }),
517 movePage: (workspace, pageId, viewer, move) => call("move_page", { workspace, page_id: pageId, viewer, move }),
518 duplicatePage: (workspace, pageId, viewer) => call("duplicate_page", { workspace, page_id: pageId, viewer }),
519 archivePage: (workspace, pageId, viewer) => call("archive_page", { workspace, page_id: pageId, viewer }),
520 restorePage: (workspace, pageId, viewer) => call("restore_page", { workspace, page_id: pageId, viewer }),
521 deletePage: (workspace, pageId, viewer) => call("delete_page", { workspace, page_id: pageId, viewer }),
522 trash: (workspace, viewer) => call("trash", { workspace, viewer }),
523 favorite: (workspace, pageId, viewer, on) => call("favorite", { workspace, page_id: pageId, viewer, on }),
524 search: (workspace, viewer, query) => call("search", { workspace, viewer, query }),
525 versions: (workspace, pageId, viewer) => call("versions", { workspace, page_id: pageId, viewer }),
526 version: (workspace, pageId, versionId, viewer) => call("version", { workspace, page_id: pageId, version_id: versionId, viewer }),
527 restoreVersion: (workspace, pageId, versionId, viewer) => call("restore_version", { workspace, page_id: pageId, version_id: versionId, viewer }),
528 templates: (workspace, viewer) => call("templates", { workspace, viewer }),
529 saveTemplate: (workspace, viewer, input) => call("save_template", { workspace, viewer, input }),
530 deleteTemplate: (workspace, templateId, viewer) => call("delete_template", { workspace, template_id: templateId, viewer }),
531 exportPage: (workspace, pageId, viewer) => call("export_page", { workspace, page_id: pageId, viewer }),
532 exportSpace: (workspace, spaceId, viewer) => call("export_space", { workspace, space_id: spaceId, viewer }),
533 suggestions: (workspace, pageId, viewer) => call("suggestions", { workspace, page_id: pageId, viewer }),
534 decideSuggestion: (workspace, suggestionId, viewer, decision) => call("decide_suggestion", { workspace, suggestion_id: suggestionId, viewer, decision }),
535 acceptAll: (workspace, pageId, viewer) => call("accept_all", { workspace, page_id: pageId, viewer }),
536 thread: (workspace, pageId, viewer, action) => call("thread", { workspace, page_id: pageId, viewer, action }),
537 threads: (workspace, pageId, viewer) => call("threads", { workspace, page_id: pageId, viewer }),
538 spacesForAgent: (workspace, agentId, viewer, audience) => call("spaces_for_agent", { workspace, agent_id: agentId, viewer, audience: audience ?? null }),
539 pageMarkdown: (workspace, agentId, viewer, pageId, audience) =>
540 call("page_markdown", { workspace, agent_id: agentId, viewer, page_id: pageId, audience: audience ?? null }),
541 searchForAgent: (workspace, agentId, viewer, query, audience) => call("search_for_agent", { workspace, agent_id: agentId, viewer, query, audience: audience ?? null }),
542 suggestEdit: (workspace, agentId, viewer, pageId, edit) => call("suggest_edit", { workspace, agent_id: agentId, viewer, page_id: pageId, edit }),
543 applyEdit: (workspace, agentId, viewer, pageId, edit) => call("apply_edit", { workspace, agent_id: agentId, viewer, page_id: pageId, edit }),
544 createPageAsAgent: (workspace, agentId, viewer, input) => call("create_page_as_agent", { workspace, agent_id: agentId, viewer, input }),
545 threadsForAgent: (workspace, agentId, viewer, pageId, audience) =>
546 call("threads_for_agent", { workspace, agent_id: agentId, viewer, page_id: pageId, audience: audience ?? null }),
547 };
548}