@agentorg/cli 0.0.6 → 0.0.7

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 (108) hide show
  1. package/README.md +10 -7
  2. package/dist/node_modules/@agentorg/computer/LICENSE +201 -0
  3. package/dist/node_modules/@agentorg/computer/NOTICE +2 -0
  4. package/dist/node_modules/@agentorg/computer/dist/THIRD_PARTY_LICENSES.txt +1321 -0
  5. package/dist/node_modules/@agentorg/computer/dist/sdk.js +24909 -0
  6. package/dist/node_modules/@agentorg/computer/dist/types/generated/schema.d.ts +860 -0
  7. package/dist/node_modules/@agentorg/computer/dist/types/sdk.d.ts +242 -0
  8. package/dist/node_modules/@agentorg/computer/package.json +12 -0
  9. package/dist/node_modules/@agentorg/computer-connector/dist/src/index.d.ts +1 -0
  10. package/dist/node_modules/@agentorg/computer-connector/dist/src/index.js +1 -0
  11. package/dist/node_modules/@agentorg/computer-connector/dist/src/provider.d.ts +24 -0
  12. package/dist/node_modules/@agentorg/computer-connector/dist/src/provider.js +482 -0
  13. package/dist/node_modules/@agentorg/computer-connector/dist/src/unit.d.ts +19 -0
  14. package/dist/node_modules/@agentorg/computer-connector/dist/src/unit.js +149 -0
  15. package/dist/node_modules/@agentorg/computer-connector/package.json +33 -0
  16. package/dist/node_modules/@agentorg/computer-connector/skills/browser/SKILL.md +31 -0
  17. package/dist/node_modules/@agentorg/computer-connector/skills/computer/SKILL.md +30 -0
  18. package/dist/node_modules/@agentorg/computer-connector/skills/files/SKILL.md +28 -0
  19. package/dist/node_modules/@agentorg/computer-connector/skills/terminal/SKILL.md +39 -0
  20. package/dist/node_modules/@agentorg/computer-connector/skills/web-research/SKILL.md +29 -0
  21. package/dist/node_modules/@agentorg/hub/dist/src/router.d.ts +4 -2
  22. package/dist/node_modules/@agentorg/hub/dist/src/router.js +3 -1
  23. package/dist/node_modules/@agentorg/hub/dist/src/validate.js +16 -2
  24. package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.d.ts +34 -47
  25. package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.js +25 -34
  26. package/dist/node_modules/@agentorg/interfaces/dist/src/chat.d.ts +90 -50
  27. package/dist/node_modules/@agentorg/interfaces/dist/src/chat.js +69 -34
  28. package/dist/node_modules/@agentorg/interfaces/dist/src/connection.d.ts +36 -0
  29. package/dist/node_modules/@agentorg/interfaces/dist/src/connection.js +31 -0
  30. package/dist/node_modules/@agentorg/interfaces/dist/src/folders.d.ts +19 -0
  31. package/dist/node_modules/@agentorg/interfaces/dist/src/folders.js +25 -0
  32. package/dist/node_modules/@agentorg/interfaces/dist/src/index.d.ts +6 -4
  33. package/dist/node_modules/@agentorg/interfaces/dist/src/index.js +6 -4
  34. package/dist/node_modules/@agentorg/interfaces/dist/src/llm-session.d.ts +2 -0
  35. package/dist/node_modules/@agentorg/interfaces/dist/src/llm-session.js +2 -1
  36. package/dist/node_modules/@agentorg/interfaces/dist/src/manager.d.ts +381 -27
  37. package/dist/node_modules/@agentorg/interfaces/dist/src/manager.js +179 -15
  38. package/dist/node_modules/@agentorg/interfaces/dist/src/units.d.ts +460 -0
  39. package/dist/node_modules/@agentorg/interfaces/dist/src/units.js +204 -0
  40. package/dist/node_modules/@agentorg/looper/dist/src/index.d.ts +5 -0
  41. package/dist/node_modules/@agentorg/looper/dist/src/index.js +5 -0
  42. package/dist/node_modules/@agentorg/looper/dist/src/loop.d.ts +75 -0
  43. package/dist/node_modules/@agentorg/looper/dist/src/loop.js +332 -0
  44. package/dist/node_modules/@agentorg/looper/dist/src/looper.d.ts +71 -0
  45. package/dist/node_modules/@agentorg/looper/dist/src/looper.js +282 -0
  46. package/dist/node_modules/@agentorg/looper/dist/src/record.d.ts +99 -0
  47. package/dist/node_modules/{assistant/dist/src/history.js → @agentorg/looper/dist/src/record.js} +89 -46
  48. package/dist/node_modules/@agentorg/looper/dist/src/tools.d.ts +11 -0
  49. package/dist/node_modules/@agentorg/looper/dist/src/tools.js +42 -0
  50. package/dist/node_modules/@agentorg/looper/dist/src/types.d.ts +147 -0
  51. package/dist/node_modules/@agentorg/looper/dist/src/types.js +32 -0
  52. package/dist/node_modules/@agentorg/looper/package.json +24 -0
  53. package/dist/node_modules/@agentorg/manager/dist/src/activity.d.ts +18 -0
  54. package/dist/node_modules/@agentorg/manager/dist/src/activity.js +65 -0
  55. package/dist/node_modules/@agentorg/manager/dist/src/control.d.ts +134 -0
  56. package/dist/node_modules/@agentorg/manager/dist/src/control.js +1115 -0
  57. package/dist/node_modules/@agentorg/manager/dist/src/form.d.ts +40 -0
  58. package/dist/node_modules/@agentorg/manager/dist/src/form.js +191 -0
  59. package/dist/node_modules/@agentorg/manager/dist/src/manager.d.ts +4 -0
  60. package/dist/node_modules/@agentorg/manager/dist/src/manager.js +341 -189
  61. package/dist/node_modules/@agentorg/manager/dist/src/skills.d.ts +2 -0
  62. package/dist/node_modules/@agentorg/manager/dist/src/skills.js +7 -0
  63. package/dist/node_modules/@agentorg/manager/dist/src/stack.d.ts +454 -0
  64. package/dist/node_modules/@agentorg/manager/dist/src/stack.js +917 -0
  65. package/dist/node_modules/@agentorg/mcp/dist/src/connection.d.ts +2 -1
  66. package/dist/node_modules/@agentorg/mcp/dist/src/connection.js +8 -2
  67. package/dist/node_modules/@agentorg/mcp/dist/src/index.d.ts +3 -0
  68. package/dist/node_modules/@agentorg/mcp/dist/src/index.js +3 -0
  69. package/dist/node_modules/@agentorg/mcp/dist/src/kinds.d.ts +108 -0
  70. package/dist/node_modules/@agentorg/mcp/dist/src/kinds.js +95 -0
  71. package/dist/node_modules/@agentorg/mcp/dist/src/provider.d.ts +2 -2
  72. package/dist/node_modules/@agentorg/mcp/dist/src/provider.js +48 -18
  73. package/dist/node_modules/@agentorg/mcp/dist/src/public.d.ts +6 -0
  74. package/dist/node_modules/@agentorg/mcp/dist/src/public.js +72 -0
  75. package/dist/node_modules/@agentorg/sdk/dist/src/filled.d.ts +21 -0
  76. package/dist/node_modules/@agentorg/sdk/dist/src/filled.js +24 -0
  77. package/dist/node_modules/@agentorg/sdk/dist/src/index.d.ts +1 -0
  78. package/dist/node_modules/@agentorg/sdk/dist/src/index.js +1 -0
  79. package/dist/node_modules/@agentorg/shell/dist/app.css +1289 -100
  80. package/dist/node_modules/@agentorg/shell/dist/app.js +207 -166
  81. package/dist/node_modules/@agentorg/shell/package.json +6 -1
  82. package/dist/node_modules/assistant/dist/src/chats.d.ts +92 -0
  83. package/dist/node_modules/assistant/dist/src/chats.js +357 -0
  84. package/dist/node_modules/assistant/dist/src/engine.d.ts +26 -7
  85. package/dist/node_modules/assistant/dist/src/engine.js +107 -35
  86. package/dist/node_modules/assistant/dist/src/local-store.js +1 -1
  87. package/dist/node_modules/assistant/dist/src/provider.d.ts +1 -1
  88. package/dist/node_modules/assistant/dist/src/provider.js +47 -19
  89. package/dist/node_modules/assistant/dist/src/tools.d.ts +11 -28
  90. package/dist/node_modules/assistant/dist/src/tools.js +19 -40
  91. package/dist/node_modules/assistant/package.json +1 -0
  92. package/dist/src/api.js +4 -0
  93. package/dist/src/folders.d.ts +2 -0
  94. package/dist/src/folders.js +45 -0
  95. package/dist/src/gateway.d.ts +7 -1
  96. package/dist/src/gateway.js +81 -12
  97. package/dist/src/live.d.ts +17 -0
  98. package/dist/src/live.js +109 -0
  99. package/dist/src/programs.d.ts +7 -0
  100. package/dist/src/programs.js +29 -2
  101. package/dist/src/signin.d.ts +12 -3
  102. package/dist/src/signin.js +30 -6
  103. package/package.json +2 -1
  104. package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.d.ts +0 -88
  105. package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.js +0 -78
  106. package/dist/node_modules/assistant/dist/src/agent.d.ts +0 -62
  107. package/dist/node_modules/assistant/dist/src/agent.js +0 -226
  108. package/dist/node_modules/assistant/dist/src/history.d.ts +0 -97
@@ -0,0 +1,282 @@
1
+ /**
2
+ * The looper: the loops of whoever hosts it, one for each thread a message comes in on. A thread is
3
+ * a name its host gives; its record is kept under `thread/<name>/` in the store, and its loop and
4
+ * record are made when the thread is first used and then stay in memory.
5
+ *
6
+ * Turns run side by side, each in a place. There are `PLACES` of them, and one more that only a
7
+ * thread already begun (its record holds something) may take, so new threads never keep a message
8
+ * from one that is under way. A turn that finds no place waits for one, in the order it came. While
9
+ * begun threads keep five turns running, a new thread goes on waiting. `WAITING` turns may wait;
10
+ * past that a message that would start a turn is refused `busy`, and nothing of it is kept.
11
+ *
12
+ * A thread is marked in the store while a turn of it hasn't ended. A looper that closes under a
13
+ * turn, or goes without closing, leaves the mark, and `resume` on the same store goes on with that
14
+ * turn, once: the turn that goes on is not marked. A turn that ends, fails or is stopped leaves
15
+ * no mark.
16
+ */
17
+ import { createLoop } from "./loop.js";
18
+ import { createRecord } from "./record.js";
19
+ import { Refusal, } from "./types.js";
20
+ /** The turns that run at once, not counting the one more for a thread already begun. */
21
+ export const PLACES = 4;
22
+ /** The turns that may wait for a place at once. */
23
+ export const WAITING = 16;
24
+ /** Where a thread's record is kept in the looper's store. */
25
+ const recordOf = (thread) => `thread/${encodeURIComponent(thread)}/`;
26
+ /** Where a thread is marked while a turn of it hasn't ended. */
27
+ const UNFINISHED = "unfinished/";
28
+ /** The store's keys under `prefix`, as a store of its own. */
29
+ function under(store, prefix) {
30
+ return {
31
+ get: ({ key }) => store.get({ key: prefix + key }),
32
+ set: ({ key, value }) => store.set({ key: prefix + key, value }),
33
+ delete: ({ key }) => store.delete({ key: prefix + key }),
34
+ async list({ prefix: inner = "", after, limit }) {
35
+ const page = await store.list({
36
+ prefix: prefix + inner,
37
+ ...(after === undefined ? {} : { after: prefix + after }),
38
+ ...(limit === undefined ? {} : { limit }),
39
+ });
40
+ return {
41
+ keys: page.keys.map((key) => key.slice(prefix.length)),
42
+ next: page.next === null ? null : page.next.slice(prefix.length),
43
+ };
44
+ },
45
+ };
46
+ }
47
+ /**
48
+ * Make a looper. Nothing is read or run until a thread is named: a message sent in it, or its
49
+ * messages asked for.
50
+ */
51
+ export function createLooper(options) {
52
+ const { store, out, log } = options;
53
+ const threads = new Map();
54
+ /** Set while it is stopped: no turn starts until it is started. */
55
+ let stopped = options.stopped ?? false;
56
+ /** Set when it closes: no turn starts after. */
57
+ let closed = false;
58
+ /** Set once the turns that were cut off have been looked for. */
59
+ let looked = false;
60
+ /** Refuse a turn that can't start now, whatever thread it is for. */
61
+ const mayStart = () => {
62
+ if (closed)
63
+ throw new Refusal("unavailable", "This agent is closing.");
64
+ if (stopped)
65
+ throw new Refusal("busy", "It is stopped. Start it to go on.");
66
+ };
67
+ /** Messages being recorded now, each about to start a turn: stopping waits for them. */
68
+ const starting = new Set();
69
+ const started = (call) => {
70
+ const done = call.then(() => void starting.delete(done), () => void starting.delete(done));
71
+ starting.add(done);
72
+ return call;
73
+ };
74
+ /** The turns that have a place, and those waiting for one, first come first. */
75
+ const placed = new Set();
76
+ const waiting = [];
77
+ const fits = (begun) => placed.size < PLACES + (begun ? 1 : 0);
78
+ const enter = (begun) => {
79
+ let take = () => { };
80
+ const place = {
81
+ entered: new Promise((resolve) => {
82
+ take = resolve;
83
+ }),
84
+ leave() {
85
+ take();
86
+ const queued = waiting.findIndex((one) => one.place === place);
87
+ if (queued !== -1)
88
+ waiting.splice(queued, 1);
89
+ if (!placed.delete(place))
90
+ return;
91
+ // Its place goes to whoever has waited longest for one they may take.
92
+ const next = waiting.find((one) => fits(one.begun));
93
+ if (!next)
94
+ return;
95
+ waiting.splice(waiting.indexOf(next), 1);
96
+ placed.add(next.place);
97
+ next.take();
98
+ },
99
+ };
100
+ if (fits(begun)) {
101
+ placed.add(place);
102
+ take();
103
+ }
104
+ else if (waiting.length < WAITING)
105
+ waiting.push({ place, begun, take });
106
+ else
107
+ throw new Refusal("busy", "Too many turns are waiting. Wait for one to start.");
108
+ return place;
109
+ };
110
+ const open = (thread) => {
111
+ const record = createRecord(under(store, recordOf(thread)), log);
112
+ const key = UNFINISHED + encodeURIComponent(thread);
113
+ const held = {
114
+ record,
115
+ /** What is being asked of its loop now: messages, going on. */
116
+ uses: 0,
117
+ /** Whether a turn of it is running, as its mark was last asked to say. */
118
+ marked: false,
119
+ /** Its mark's writes, one after another. */
120
+ writes: Promise.resolve(),
121
+ /** Keep its mark, or take it off, after any write before. */
122
+ mark(on) {
123
+ held.writes = held.writes
124
+ .then(() => (on ? store.set({ key, value: true }) : store.delete({ key })))
125
+ .then(() => { }, (error) => log.error("A thread's mark couldn't be written.", { thread, error: String(error) }));
126
+ },
127
+ loop: createLoop({
128
+ request: options.request,
129
+ model: options.model,
130
+ enter,
131
+ record,
132
+ out: {
133
+ message: (message) => out.message(thread, message),
134
+ delta: (id, text) => out.delta(thread, id, text),
135
+ status(status) {
136
+ const running = status.state === "thinking" || status.state === "tool";
137
+ // A turn that starts is marked, but one that goes on from a cut-off has that mark
138
+ // taken off: it goes on once. A turn that ends takes its mark off, but one cut by the
139
+ // looper closing keeps it, for whoever opens the store next.
140
+ if (running && !held.marked)
141
+ held.mark(!held.loop.resumed());
142
+ else if (!running && held.marked && !closed)
143
+ held.mark(false);
144
+ held.marked = running;
145
+ out.status(thread, status);
146
+ },
147
+ },
148
+ log,
149
+ }),
150
+ };
151
+ return held;
152
+ };
153
+ /**
154
+ * Ask something of a thread's loop. A thread first named by an ask that fails, with nothing else
155
+ * asked of it meanwhile, was never used: nothing is held for it.
156
+ */
157
+ const using = async (thread, ask) => {
158
+ const known = threads.has(thread);
159
+ const held = of(thread);
160
+ held.uses++;
161
+ try {
162
+ return await started(ask(held.loop));
163
+ }
164
+ catch (error) {
165
+ if (!known && held.uses === 1)
166
+ threads.delete(thread);
167
+ throw error;
168
+ }
169
+ finally {
170
+ held.uses--;
171
+ }
172
+ };
173
+ const of = (thread) => {
174
+ const found = threads.get(thread) ?? open(thread);
175
+ threads.set(thread, found);
176
+ return found;
177
+ };
178
+ /**
179
+ * Go on with each turn that was cut off when this store was last used: by a looper closing
180
+ * under it, or going without closing. Each goes on once, and waits for a place like any turn.
181
+ * One that can't go on now keeps its mark for the next time. A stopped looper goes on with
182
+ * none, and its marks go: so does one stopped before it was asked to. Whoever hosts the looper calls this when a request can be made;
183
+ * once the marks have been read, a later call does nothing.
184
+ */
185
+ async function resume() {
186
+ if (looked || closed)
187
+ return;
188
+ looked = true;
189
+ const keys = [];
190
+ try {
191
+ let after;
192
+ do {
193
+ const page = await store.list({ prefix: UNFINISHED, ...(after ? { after } : {}) });
194
+ keys.push(...page.keys);
195
+ after = page.next ?? undefined;
196
+ } while (after);
197
+ }
198
+ catch (error) {
199
+ looked = false;
200
+ throw error;
201
+ }
202
+ for (const key of keys) {
203
+ // Closed meanwhile: what is left stays marked, for whoever opens the store next.
204
+ if (closed)
205
+ return;
206
+ const thread = decodeURIComponent(key.slice(UNFINISHED.length));
207
+ const held = of(thread);
208
+ try {
209
+ if (!stopped)
210
+ await using(thread, (loop) => loop.resume());
211
+ // A thread in a turn now keeps the mark as that turn's, and a looper that has closed
212
+ // leaves it. Otherwise nothing goes on, and it goes.
213
+ if (!held.marked && !closed)
214
+ held.mark(false);
215
+ }
216
+ catch (error) {
217
+ log.warn("A turn that was cut off didn't go on.", { key, error: String(error) });
218
+ }
219
+ }
220
+ }
221
+ return {
222
+ /**
223
+ * Say a message in a thread. It is recorded and said at once. With no turn in the thread it
224
+ * starts one, which waits for a place if none is free; a turn running or waiting takes it at
225
+ * its next model request. Refused `busy` while `WAITING` turns already wait and it would start
226
+ * one more, or while the looper is stopped; and `unavailable` once it has closed.
227
+ */
228
+ async send(thread, text) {
229
+ mayStart();
230
+ const sent = await using(thread, (loop) => loop.send(text));
231
+ // Recorded as the looper closed, its turn was cut before it began: it is marked like one.
232
+ const held = threads.get(thread);
233
+ if (closed && held && !held.marked)
234
+ held.mark(true);
235
+ return sent;
236
+ },
237
+ /** Stop a thread's turn now. A call already running finishes, and its result is kept. */
238
+ cancel: async (thread) => threads.get(thread)?.loop.cancel(),
239
+ /** What a thread's loop is doing. */
240
+ state: (thread) => threads.get(thread)?.loop.state() ?? { state: "idle" },
241
+ /** A thread's messages before `before`, oldest first, as many as fit `limit`. */
242
+ page: (thread, before, limit) => of(thread).record.page(before, limit),
243
+ /**
244
+ * Stop: every turn is cut, running or waiting, and none starts until `start`. A call already
245
+ * running finishes first, so this answers when nothing more will be written. The records stay
246
+ * as they are, to be read. A turn that was cut off earlier no longer goes on.
247
+ */
248
+ async stop() {
249
+ stopped = true;
250
+ const loops = [...threads.values()].map(({ loop }) => loop);
251
+ await Promise.all(loops.map((loop) => loop.cancel()));
252
+ // A message being recorded as this was asked is recorded first, and a call still running ends.
253
+ await Promise.all(starting);
254
+ await Promise.all(loops.map((loop) => loop.drained()));
255
+ // What was cut off earlier and hasn't gone on doesn't after a stop either: its marks go.
256
+ await resume().catch((error) => log.warn("The turns that were cut off couldn't be looked for.", { error: String(error) }));
257
+ },
258
+ /** Take messages again. The turns `stop` cut stay cut. */
259
+ start() {
260
+ stopped = false;
261
+ },
262
+ resume,
263
+ /**
264
+ * Close for good: every turn is cut, and none starts after. Each stays marked, for `resume`
265
+ * in whoever opens the store next.
266
+ */
267
+ async close() {
268
+ closed = true;
269
+ await Promise.all([...threads.values()].map(({ loop }) => loop.cancel()));
270
+ },
271
+ /** Settles when every thread's latest turn has written all it will. */
272
+ async settled() {
273
+ await Promise.all([...threads.values()].map(({ loop }) => loop.settled()));
274
+ await Promise.all([...threads.values()].map(({ writes }) => writes));
275
+ },
276
+ /** Text the model writes while it answers `requestId`, for the reply it becomes. */
277
+ delta(requestId, text) {
278
+ for (const { loop } of threads.values())
279
+ loop.delta(requestId, text);
280
+ },
281
+ };
282
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The record a loop runs on, kept in a store: one key per message, holding the items a model reads
3
+ * for it, and for a tool message the line a person sees. It is read once, then kept in memory, and
4
+ * the oldest messages are deleted past a retention cap.
5
+ */
6
+ import { z } from "zod";
7
+ import { Item, type Log, type Shown, type Store } from "./types.js";
8
+ /** A message as the record keeps it. */
9
+ export declare const Entry: z.ZodObject<{
10
+ id: z.ZodString;
11
+ role: z.ZodEnum<{
12
+ assistant: "assistant";
13
+ tool: "tool";
14
+ user: "user";
15
+ }>;
16
+ text: z.ZodOptional<z.ZodString>;
17
+ at: z.ZodString;
18
+ items: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
19
+ type: z.ZodLiteral<"message">;
20
+ role: z.ZodEnum<{
21
+ assistant: "assistant";
22
+ user: "user";
23
+ }>;
24
+ content: z.ZodString;
25
+ }, z.core.$strip>, z.ZodObject<{
26
+ type: z.ZodLiteral<"function_call">;
27
+ callId: z.ZodString;
28
+ name: z.ZodString;
29
+ arguments: z.ZodString;
30
+ }, z.core.$strip>, z.ZodObject<{
31
+ type: z.ZodLiteral<"function_call_output">;
32
+ callId: z.ZodString;
33
+ output: z.ZodString;
34
+ }, z.core.$strip>], "type">>;
35
+ state: z.ZodOptional<z.ZodEnum<{
36
+ done: "done";
37
+ refused: "refused";
38
+ }>>;
39
+ joined: z.ZodOptional<z.ZodLiteral<true>>;
40
+ ahead: z.ZodOptional<z.ZodNumber>;
41
+ }, z.core.$strip>;
42
+ export type Entry = z.output<typeof Entry>;
43
+ /** Where a record's messages are kept in its store. */
44
+ export declare const MESSAGES = "msg/";
45
+ /** What the model is sent of the past: the newest exchanges up to this many bytes of JSON. */
46
+ export declare const INPUT_BUDGET = 400000;
47
+ /** At most this many messages are kept, in at most this many bytes, oldest deleted first. */
48
+ export declare const KEEP = 2000;
49
+ export declare const KEEP_BYTES: number;
50
+ /** The first `length` UTF-16 units of `text`, never ending inside a surrogate pair. */
51
+ export declare function head(text: string, length: number): string;
52
+ /** `text`, cut so its JSON fits `max` bytes, saying so when it is cut. */
53
+ export declare function fitted(text: string, max: number): string;
54
+ /** What the person sees of a message. */
55
+ export declare const textOf: (entry: Entry) => string;
56
+ /** A call's arguments as they are kept and shown: past their cap, a note in their place. */
57
+ export declare const argumentsOf: (args: string) => string;
58
+ /**
59
+ * A message as it is shown. A tool message whose call ran carries `tool`: the call's name, how it
60
+ * ended, its arguments and its output.
61
+ */
62
+ export declare function messageOf(entry: Entry): Shown;
63
+ /** Stored values read at once when what is kept is first loaded. */
64
+ export declare const READS = 16;
65
+ export declare function createRecord(store: Store, log: Log, limits?: {
66
+ keep: number;
67
+ keepBytes: number;
68
+ }): {
69
+ /**
70
+ * Keep a message, under `id` or its own number, and delete the oldest past the cap. A tool
71
+ * message the store refuses is still kept in memory, since its call has run. `read` is given
72
+ * for what came of a model request that had read the record up to that number: when a user
73
+ * message was said since, it is kept as `ahead` of it.
74
+ */
75
+ append(entry: Omit<Entry, "id" | "at">, id?: string, read?: number): Promise<Entry>;
76
+ /** Whether nothing is kept. */
77
+ empty(): Promise<boolean>;
78
+ /** Messages before `before`, oldest first, as many as fit `limit` and `PAGE_BUDGET`. */
79
+ page(before: string | undefined, limit: number): Promise<{
80
+ messages: Shown[];
81
+ more: boolean;
82
+ }>;
83
+ /**
84
+ * The conversation to send: the newest exchanges (each from the user message that starts a turn
85
+ * on) that fit the budget, and always the latest two, so "go on" after a long turn still has
86
+ * that turn. When those two are over the budget, their oldest function arguments and outputs
87
+ * are left out, each call still paired with an output. A message said during a turn comes after
88
+ * whatever is `ahead` of it, so the model reads it as not yet answered. `read` is the number
89
+ * up to which every user message was read, for `append` and `saidAfter`: one still being
90
+ * written is not read yet.
91
+ */
92
+ input(): Promise<{
93
+ items: Item[];
94
+ read: number;
95
+ }>;
96
+ /** Whether a user message has been stored after the one numbered `read`, which `input` gave. */
97
+ saidAfter(read: number): Promise<boolean>;
98
+ };
99
+ export type LoopRecord = ReturnType<typeof createRecord>;
@@ -1,11 +1,12 @@
1
1
  /**
2
- * The assistant's record of its chat, kept in its `store` slot: one key per message, holding the
3
- * Responses items it stands for, and for a tool message the line the person sees. It is read once,
4
- * then kept in memory, and the oldest messages are deleted past a retention cap.
2
+ * The record a loop runs on, kept in a store: one key per message, holding the items a model reads
3
+ * for it, and for a tool message the line a person sees. It is read once, then kept in memory, and
4
+ * the oldest messages are deleted past a retention cap.
5
5
  */
6
- import { Item } from "@agentorg/interfaces";
7
6
  import { z } from "zod";
8
- const Entry = z.object({
7
+ import { Item } from "./types.js";
8
+ /** A message as the record keeps it. */
9
+ export const Entry = z.object({
9
10
  id: z.string(),
10
11
  role: z.enum(["user", "assistant", "tool"]),
11
12
  /** What the person sees, for a tool message; the others show their message items. */
@@ -14,9 +15,21 @@ const Entry = z.object({
14
15
  items: z.array(Item),
15
16
  /** For a tool message whose call ran: how it ended. */
16
17
  state: z.enum(["done", "refused"]).optional(),
18
+ /** For a user message said while a turn was running: it is part of that turn. */
19
+ joined: z.literal(true).optional(),
20
+ /**
21
+ * For what came of a model request that had read the record up to the message of this number,
22
+ * when a user message was said after that: the model reads it ahead of that message, which it
23
+ * doesn't answer.
24
+ */
25
+ ahead: z.number().optional(),
17
26
  });
18
- const PREFIX = "msg/";
19
- const keyOf = (seq) => `${PREFIX}${String(seq).padStart(12, "0")}`;
27
+ /** Where a record's messages are kept in its store. */
28
+ export const MESSAGES = "msg/";
29
+ const keyOf = (seq) => `${MESSAGES}${String(seq).padStart(12, "0")}`;
30
+ const seqOf = (key) => Number(key.slice(MESSAGES.length)) || 0;
31
+ // The record's limits are fixed here for now, sized for a host that carries a model request, or a
32
+ // page of messages, in one piece of about a megabyte.
20
33
  /** What the model is sent of the past: the newest exchanges up to this many bytes of JSON. */
21
34
  export const INPUT_BUDGET = 400_000;
22
35
  /** At most this many messages are kept, in at most this many bytes, oldest deleted first. */
@@ -25,7 +38,7 @@ export const KEEP_BYTES = 16 * 1024 * 1024;
25
38
  /** A stored message's text, and a call's arguments, are cut to fit these, in bytes of JSON. */
26
39
  const MAX_TEXT = 192 * 1024;
27
40
  const MAX_ARGUMENTS = 16 * 1024;
28
- /** A history page stays well inside one frame. */
41
+ /** A page of messages stays under this, so whatever carries it can do so in one piece. */
29
42
  const PAGE_BUDGET = 512 * 1024;
30
43
  const encoder = new TextEncoder();
31
44
  const size = (value) => encoder.encode(JSON.stringify(value)).byteLength;
@@ -43,7 +56,7 @@ export function fitted(text, max) {
43
56
  kept = `${head(kept, Math.floor((kept.length * max) / bytes) - 16)}… (cut)`;
44
57
  return kept;
45
58
  }
46
- /** A chat message shows at most this much; the record keeps all of it. */
59
+ /** A message shows at most this much; the record keeps all of it. */
47
60
  const MAX_SHOWN = 100_000;
48
61
  /** What the person sees of a message. */
49
62
  export const textOf = (entry) => head(entry.text ??
@@ -51,8 +64,8 @@ export const textOf = (entry) => head(entry.text ??
51
64
  /** A call's arguments as they are kept and shown: past their cap, a note in their place. */
52
65
  export const argumentsOf = (args) => size(args) > MAX_ARGUMENTS ? ARGUMENTS_LEFT_OUT : args;
53
66
  /**
54
- * A message as the chat shows it. A tool message whose call ran carries `tool`: the call's name,
55
- * how it ended, its arguments and its output.
67
+ * A message as it is shown. A tool message whose call ran carries `tool`: the call's name, how it
68
+ * ended, its arguments and its output.
56
69
  */
57
70
  export function messageOf(entry) {
58
71
  const message = { id: entry.id, role: entry.role, text: textOf(entry), at: entry.at };
@@ -81,9 +94,11 @@ const kept = (entry) => ({
81
94
  return { ...item, arguments: argumentsOf(item.arguments) };
82
95
  }),
83
96
  });
84
- /** Stored messages read at once when the record is first loaded. */
85
- const READS = 16;
86
- export function createHistory(store, log, limits = { keep: KEEP, keepBytes: KEEP_BYTES }) {
97
+ /** Stored values read at once when what is kept is first loaded. */
98
+ export const READS = 16;
99
+ /** Whether a user message follows the one numbered `read`: one `stored` already, when asked so. */
100
+ const saidAfter = (records, read, stored = false) => records.some((record) => record.entry.role === "user" && seqOf(record.key) > read && !(stored && record.writing));
101
+ export function createRecord(store, log, limits = { keep: KEEP, keepBytes: KEEP_BYTES }) {
87
102
  let loaded;
88
103
  const load = () => {
89
104
  if (loaded)
@@ -94,14 +109,18 @@ export function createHistory(store, log, limits = { keep: KEEP, keepBytes: KEEP
94
109
  let bytes = 0;
95
110
  let after;
96
111
  do {
97
- const page = await store.list({ prefix: PREFIX, limit: 1000, ...(after ? { after } : {}) });
112
+ const page = await store.list({
113
+ prefix: MESSAGES,
114
+ limit: 1000,
115
+ ...(after ? { after } : {}),
116
+ });
98
117
  // A few at a time, in order: a long record shouldn't hold up the first send one read at
99
- // a time, nor flood the Hub.
118
+ // a time, nor flood its store.
100
119
  for (let at = 0; at < page.keys.length; at += READS) {
101
120
  const keys = page.keys.slice(at, at + READS);
102
121
  const values = await Promise.all(keys.map((key) => store.get({ key })));
103
122
  keys.forEach((key, i) => {
104
- last = Math.max(last, Number(key.slice(PREFIX.length)) || 0);
123
+ last = Math.max(last, seqOf(key));
105
124
  const parsed = Entry.safeParse(values[i]?.value);
106
125
  if (!parsed.success) {
107
126
  log.warn("A stored message didn't read back; it is left out.", { key });
@@ -126,21 +145,30 @@ export function createHistory(store, log, limits = { keep: KEEP, keepBytes: KEEP
126
145
  return {
127
146
  /**
128
147
  * Keep a message, under `id` or its own number, and delete the oldest past the cap. A tool
129
- * message the store refuses is still kept in memory, since its call has run.
148
+ * message the store refuses is still kept in memory, since its call has run. `read` is given
149
+ * for what came of a model request that had read the record up to that number: when a user
150
+ * message was said since, it is kept as `ahead` of it.
130
151
  */
131
- async append(entry, id) {
152
+ async append(entry, id, read) {
132
153
  const state = await load();
133
154
  const key = keyOf(++state.last);
134
155
  const record = {
135
156
  key,
136
- entry: kept({ id: id ?? key.slice(PREFIX.length), at: new Date().toISOString(), ...entry }),
157
+ entry: kept({
158
+ id: id ?? key.slice(MESSAGES.length),
159
+ at: new Date().toISOString(),
160
+ ...entry,
161
+ ...(read !== undefined && saidAfter(state.records, read) ? { ahead: read } : {}),
162
+ }),
137
163
  bytes: 0,
164
+ writing: entry.role === "user",
138
165
  };
139
166
  record.bytes = size(record.entry);
140
167
  state.records.push(record);
141
168
  state.bytes += record.bytes;
142
169
  try {
143
170
  await store.set({ key, value: record.entry });
171
+ record.writing = false;
144
172
  }
145
173
  catch (error) {
146
174
  if (entry.role !== "tool") {
@@ -163,25 +191,11 @@ export function createHistory(store, log, limits = { keep: KEEP, keepBytes: KEEP
163
191
  }
164
192
  return record.entry;
165
193
  },
166
- /** Forget everything after the message `id`: a retry takes the place of what followed it. */
167
- async dropAfter(id) {
168
- const state = await load();
169
- const at = state.records.findLastIndex((record) => record.entry.id === id);
170
- if (at === -1)
171
- return;
172
- for (const gone of state.records.splice(at + 1)) {
173
- state.bytes -= gone.bytes;
174
- await store.delete({ key: gone.key }).catch((error) => log.warn("A replaced message couldn't be deleted.", {
175
- key: gone.key,
176
- error: String(error),
177
- }));
178
- }
179
- },
180
- /** The person's last message, if any. */
181
- async lastUser() {
182
- return (await load()).records.findLast((record) => record.entry.role === "user")?.entry;
194
+ /** Whether nothing is kept. */
195
+ async empty() {
196
+ return (await load()).records.length === 0;
183
197
  },
184
- /** Messages before `before`, oldest first, as many as fit `limit` and one frame. */
198
+ /** Messages before `before`, oldest first, as many as fit `limit` and `PAGE_BUDGET`. */
185
199
  async page(before, limit) {
186
200
  const { records } = await load();
187
201
  const end = before === undefined
@@ -202,20 +216,45 @@ export function createHistory(store, log, limits = { keep: KEEP, keepBytes: KEEP
202
216
  return { messages, more: start > 0 };
203
217
  },
204
218
  /**
205
- * The conversation to send: the newest whole exchanges (each from a user message on) that fit
206
- * the budget, and always the latest one. A latest one that alone is over the budget has its
207
- * oldest function arguments and outputs left out, each call still paired with an output.
219
+ * The conversation to send: the newest exchanges (each from the user message that starts a turn
220
+ * on) that fit the budget, and always the latest two, so "go on" after a long turn still has
221
+ * that turn. When those two are over the budget, their oldest function arguments and outputs
222
+ * are left out, each call still paired with an output. A message said during a turn comes after
223
+ * whatever is `ahead` of it, so the model reads it as not yet answered. `read` is the number
224
+ * up to which every user message was read, for `append` and `saidAfter`: one still being
225
+ * written is not read yet.
208
226
  */
209
227
  async input() {
210
- const { records } = await load();
228
+ const { records, last } = await load();
229
+ const unwritten = records.find((record) => record.writing);
230
+ const read = unwritten ? seqOf(unwritten.key) - 1 : last;
211
231
  const exchanges = [];
212
- for (const { entry } of records) {
213
- if (entry.role === "user" || exchanges.length === 0)
232
+ /** Said during a turn, and not yet put: what is ahead of them goes first. */
233
+ const late = [];
234
+ const put = (entry) => {
235
+ if ((entry.role === "user" && !entry.joined) || exchanges.length === 0)
214
236
  exchanges.push([]);
215
237
  exchanges.at(-1)?.push(...entry.items);
238
+ };
239
+ /** Put the late messages numbered up to `read`. */
240
+ const putLate = (read) => {
241
+ while (late[0] && seqOf(late[0].key) <= read)
242
+ put(late.shift().entry);
243
+ };
244
+ for (const record of records) {
245
+ // Nothing to send: a user message still being written, or a line with no items.
246
+ if (record.writing || record.entry.items.length === 0)
247
+ continue;
248
+ if (record.entry.joined) {
249
+ late.push(record);
250
+ continue;
251
+ }
252
+ putLate(record.entry.ahead ?? last);
253
+ put(record.entry);
216
254
  }
255
+ putLate(last);
217
256
  let total = exchanges.reduce((sum, items) => sum + size(items), 0);
218
- while (exchanges.length > 1 && total > INPUT_BUDGET)
257
+ while (exchanges.length > 2 && total > INPUT_BUDGET)
219
258
  total -= size(exchanges.shift());
220
259
  const items = exchanges.flat();
221
260
  for (let i = 0; i < items.length && total > INPUT_BUDGET; i++) {
@@ -229,7 +268,11 @@ export function createHistory(store, log, limits = { keep: KEEP, keepBytes: KEEP
229
268
  items[i] = { ...item, arguments: ARGUMENTS_LEFT_OUT };
230
269
  }
231
270
  }
232
- return items;
271
+ return { items, read };
272
+ },
273
+ /** Whether a user message has been stored after the one numbered `read`, which `input` gave. */
274
+ async saidAfter(read) {
275
+ return saidAfter((await load()).records, read, true);
233
276
  },
234
277
  };
235
278
  }
@@ -0,0 +1,11 @@
1
+ /** Running the functions a model calls, and what is kept of their output. */
2
+ import { type Outcome, type Tool } from "./types.js";
3
+ /** A function's output keeps this many characters, so one result can't fill a request. */
4
+ export declare const MAX_OUTPUT = 30000;
5
+ /**
6
+ * `text`, or its first and last `MAX_OUTPUT / 2` characters with what was cut between them: a
7
+ * failing command's error is at the end, and what it was doing at the start.
8
+ */
9
+ export declare function capped(text: string): string;
10
+ /** Run a function call: the result, or the refusal, as the function's output. */
11
+ export declare function runTool(tools: Tool[], name: string, args: string): Promise<Outcome>;