agentic-documents 0.1.0 → 0.3.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 +58 -0
- package/README.md +64 -4
- package/dist/chunk-FZA5LQV3.js +150 -0
- package/dist/chunk-GYQFTF2E.js +389 -0
- package/dist/chunk-IU3YIQ4Y.js +232 -0
- package/dist/chunk-R5XUTBC3.js +177 -0
- package/dist/{chunk-BSXUNQDR.js → chunk-TFCRPEEM.js} +1 -1
- package/dist/chunk-YBBQNCMC.js +744 -0
- package/dist/cli.js +47 -11
- package/dist/index.d.ts +4 -4
- package/dist/index.js +10 -8
- package/dist/live-worker.d.ts +20 -0
- package/dist/live-worker.js +27 -0
- package/dist/live.d.ts +73 -0
- package/dist/live.js +12 -0
- package/dist/{mcp-2VZIGHIX.js → mcp-P2C75IIG.js} +4 -2
- package/dist/{service-eYISo6-b.d.ts → service-BKkxEnaJ.d.ts} +24 -2
- package/dist/worker.d.ts +19 -6
- package/dist/worker.js +11 -158
- package/package.json +13 -2
- package/dist/chunk-4YVWD5PH.js +0 -1123
- package/dist/chunk-LRPLNXOF.js +0 -67
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_LIVE_BYTES
|
|
3
|
+
} from "./chunk-FZA5LQV3.js";
|
|
4
|
+
import {
|
|
5
|
+
AgenticDoc,
|
|
6
|
+
DOC_ERROR_CODES,
|
|
7
|
+
DocError,
|
|
8
|
+
PRINCIPAL_KINDS
|
|
9
|
+
} from "./chunk-YBBQNCMC.js";
|
|
10
|
+
|
|
11
|
+
// src/live.ts
|
|
12
|
+
import * as Y from "yjs";
|
|
13
|
+
import { SyncClient } from "agentic-realtime/client";
|
|
14
|
+
async function refusal(response, fallback) {
|
|
15
|
+
const { error } = await response.json().catch(() => ({}));
|
|
16
|
+
const code = DOC_ERROR_CODES.find((known) => known === error?.code) ?? (response.status >= 500 ? "MISCONFIGURED" : "INVALID_INPUT");
|
|
17
|
+
return new DocError(code, typeof error?.message === "string" ? error.message : `${fallback} (${response.status})`);
|
|
18
|
+
}
|
|
19
|
+
var base = (server) => server.replace(/\/$/, "");
|
|
20
|
+
async function whoAmI(server, token) {
|
|
21
|
+
const response = await fetch(`${base(server)}/api/v1/me`, { headers: { authorization: `Bearer ${token}` } }).catch((error) => {
|
|
22
|
+
throw unreachable(error);
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
if (response.status === 401 || response.status === 403) throw new DocError("FORBIDDEN", `the server refused the token (${response.status})`);
|
|
26
|
+
throw await refusal(response, "the server could not say who the token is");
|
|
27
|
+
}
|
|
28
|
+
const body = await response.json();
|
|
29
|
+
const { kind, name, scopes, role } = body;
|
|
30
|
+
if (!PRINCIPAL_KINDS.includes(kind) || typeof name !== "string" || role !== "editor" && role !== "viewer") {
|
|
31
|
+
throw new DocError("INVALID_INPUT", "the server sent an identity this client does not know");
|
|
32
|
+
}
|
|
33
|
+
const granted = Array.isArray(scopes) ? scopes.filter((s) => typeof s === "string") : [];
|
|
34
|
+
return { author: { kind, name, ...granted.length ? { scopes: granted } : {} }, role };
|
|
35
|
+
}
|
|
36
|
+
var LiveDoc = class _LiveDoc {
|
|
37
|
+
constructor(doc, me, client, api, refusedAtStart) {
|
|
38
|
+
this.doc = doc;
|
|
39
|
+
this.me = me;
|
|
40
|
+
this.client = client;
|
|
41
|
+
this.api = api;
|
|
42
|
+
this.refusedAtStart = refusedAtStart;
|
|
43
|
+
}
|
|
44
|
+
doc;
|
|
45
|
+
me;
|
|
46
|
+
client;
|
|
47
|
+
api;
|
|
48
|
+
refusedAtStart;
|
|
49
|
+
static async join(options) {
|
|
50
|
+
const me = await whoAmI(options.server, options.token);
|
|
51
|
+
const url = new URL(`${base(options.server)}/api/v1/docs/${encodeURIComponent(options.id)}/ws`);
|
|
52
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
53
|
+
const client = await SyncClient.connect({
|
|
54
|
+
url: url.toString(),
|
|
55
|
+
headers: { authorization: `Bearer ${options.token}` },
|
|
56
|
+
presence: { name: options.name ?? me.author.name },
|
|
57
|
+
reconnect: options.reconnect
|
|
58
|
+
});
|
|
59
|
+
const api = (path, init = {}) => fetch(`${base(options.server)}/api/v1/docs/${encodeURIComponent(options.id)}${path}`, {
|
|
60
|
+
...init,
|
|
61
|
+
headers: { ...init.headers, authorization: `Bearer ${options.token}` }
|
|
62
|
+
});
|
|
63
|
+
try {
|
|
64
|
+
if (options.seed !== false) {
|
|
65
|
+
const stored = await api("/state");
|
|
66
|
+
if (stored.ok) {
|
|
67
|
+
const state = new Uint8Array(await stored.arrayBuffer());
|
|
68
|
+
if (state.length > MAX_LIVE_BYTES) throw new DocError("INVALID_INPUT", `the stored document is over ${MAX_LIVE_BYTES} bytes, the most a live room takes`);
|
|
69
|
+
Y.applyUpdate(client.doc, state);
|
|
70
|
+
} else if (stored.status !== 404) throw await refusal(stored, "could not read the stored document");
|
|
71
|
+
await client.flush();
|
|
72
|
+
if (me.role === "editor" && client.denied.length > 0) throw new DocError("FORBIDDEN", `the room refused the stored document: ${client.denied[0]}`);
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
await client.disconnect();
|
|
76
|
+
throw error instanceof DocError ? error : new DocError("INVALID_INPUT", `could not open the room: ${error instanceof Error ? error.message : String(error)}`);
|
|
77
|
+
}
|
|
78
|
+
return new _LiveDoc(new AgenticDoc(client.doc, options.doc), me, client, api, client.denied.length);
|
|
79
|
+
}
|
|
80
|
+
/** Saves the room's state to the stored document, through the Blobs port. A viewer cannot save. */
|
|
81
|
+
async save() {
|
|
82
|
+
if (this.me.role === "viewer") throw new DocError("FORBIDDEN", "this token is a viewer; it cannot change a document");
|
|
83
|
+
await this.client.flush();
|
|
84
|
+
const saved = await this.api("/checkpoint", { method: "POST", headers: { "content-type": "application/octet-stream" }, body: new Uint8Array(this.doc.encode()) });
|
|
85
|
+
if (!saved.ok) throw await refusal(saved, "the server did not save the document");
|
|
86
|
+
}
|
|
87
|
+
/** The other people and agents in the room. `machine` is true for an agent. */
|
|
88
|
+
get peers() {
|
|
89
|
+
return this.client.peers;
|
|
90
|
+
}
|
|
91
|
+
/** Why the room dropped an update, in order. */
|
|
92
|
+
get denied() {
|
|
93
|
+
return this.client.denied.slice(this.refusedAtStart);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Runs one change as the token's author and waits until the room has it. A viewer fails before the change.
|
|
97
|
+
* After a refusal from the room the local copy still holds the change. Leave and join again.
|
|
98
|
+
*/
|
|
99
|
+
async edit(run) {
|
|
100
|
+
if (this.me.role === "viewer") throw new DocError("FORBIDDEN", "this token is a viewer; it cannot change a document");
|
|
101
|
+
const seen = this.denied.length;
|
|
102
|
+
const result = run(this.doc, this.me.author);
|
|
103
|
+
await this.client.flush();
|
|
104
|
+
if (this.denied.length > seen) throw new DocError("FORBIDDEN", `the room refused the edit: ${this.denied[seen]}`);
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
/** Waits until the room has every change made so far. */
|
|
108
|
+
flush() {
|
|
109
|
+
return this.client.flush();
|
|
110
|
+
}
|
|
111
|
+
async leave() {
|
|
112
|
+
await this.client.disconnect();
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
function unreachable(error) {
|
|
116
|
+
if (error instanceof DocError) return error;
|
|
117
|
+
return new DocError("MISCONFIGURED", `could not reach the live server: ${error instanceof Error ? error.message : String(error)}`);
|
|
118
|
+
}
|
|
119
|
+
var LiveService = class {
|
|
120
|
+
constructor(options) {
|
|
121
|
+
this.options = options;
|
|
122
|
+
}
|
|
123
|
+
options;
|
|
124
|
+
async join(id) {
|
|
125
|
+
try {
|
|
126
|
+
return await LiveDoc.join({ ...this.options, id });
|
|
127
|
+
} catch (error) {
|
|
128
|
+
throw unreachable(error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** The room is only a copy, so a new document is made in the store. A room seeds from it on the next join. */
|
|
132
|
+
async create(id, markdown) {
|
|
133
|
+
const response = await fetch(`${base(this.options.server)}/api/v1/docs`, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: { authorization: `Bearer ${this.options.token}`, "content-type": "application/json" },
|
|
136
|
+
body: JSON.stringify({ id, markdown })
|
|
137
|
+
}).catch((error) => {
|
|
138
|
+
throw unreachable(error);
|
|
139
|
+
});
|
|
140
|
+
if (!response.ok) throw await refusal(response, "the server refused the request");
|
|
141
|
+
}
|
|
142
|
+
async read(id, run) {
|
|
143
|
+
const live = await this.join(id);
|
|
144
|
+
try {
|
|
145
|
+
return run(this.exists(live, id));
|
|
146
|
+
} finally {
|
|
147
|
+
await live.leave();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async write(id, run) {
|
|
151
|
+
const live = await this.join(id);
|
|
152
|
+
try {
|
|
153
|
+
this.exists(live, id);
|
|
154
|
+
const result = await live.edit((doc) => run(doc));
|
|
155
|
+
try {
|
|
156
|
+
await live.save();
|
|
157
|
+
} catch (error) {
|
|
158
|
+
const why = error instanceof Error ? error.message : String(error);
|
|
159
|
+
throw new DocError(error instanceof DocError ? error.code : "MISCONFIGURED", `the room has the change, but it is not saved: ${why}. Do not repeat the change.`);
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
} finally {
|
|
163
|
+
await live.leave();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// A document that nobody made has an empty room and an empty log.
|
|
167
|
+
exists(live, id) {
|
|
168
|
+
if (live.doc.edits().length === 0) throw new DocError("NO_DOCUMENT", `no document with id "${id}"`);
|
|
169
|
+
return live.doc;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export {
|
|
174
|
+
whoAmI,
|
|
175
|
+
LiveDoc,
|
|
176
|
+
LiveService
|
|
177
|
+
};
|