@agentorg/cli 0.0.5 → 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.
- package/README.md +23 -104
- package/dist/node_modules/@agentorg/computer/LICENSE +201 -0
- package/dist/node_modules/@agentorg/computer/NOTICE +2 -0
- package/dist/node_modules/@agentorg/computer/dist/THIRD_PARTY_LICENSES.txt +1321 -0
- package/dist/node_modules/@agentorg/computer/dist/sdk.js +24909 -0
- package/dist/node_modules/@agentorg/computer/dist/types/generated/schema.d.ts +860 -0
- package/dist/node_modules/@agentorg/computer/dist/types/sdk.d.ts +242 -0
- package/dist/node_modules/@agentorg/computer/package.json +12 -0
- package/dist/node_modules/@agentorg/computer-connector/dist/src/index.d.ts +1 -0
- package/dist/node_modules/@agentorg/computer-connector/dist/src/index.js +1 -0
- package/dist/node_modules/@agentorg/computer-connector/dist/src/provider.d.ts +24 -0
- package/dist/node_modules/@agentorg/computer-connector/dist/src/provider.js +482 -0
- package/dist/node_modules/@agentorg/computer-connector/dist/src/unit.d.ts +19 -0
- package/dist/node_modules/@agentorg/computer-connector/dist/src/unit.js +149 -0
- package/dist/node_modules/@agentorg/computer-connector/package.json +33 -0
- package/dist/node_modules/@agentorg/computer-connector/skills/browser/SKILL.md +31 -0
- package/dist/node_modules/@agentorg/computer-connector/skills/computer/SKILL.md +30 -0
- package/dist/node_modules/@agentorg/computer-connector/skills/files/SKILL.md +28 -0
- package/dist/node_modules/@agentorg/computer-connector/skills/terminal/SKILL.md +39 -0
- package/dist/node_modules/@agentorg/computer-connector/skills/web-research/SKILL.md +29 -0
- package/dist/node_modules/@agentorg/hub/dist/src/hub-actor.d.ts +2 -7
- package/dist/node_modules/@agentorg/hub/dist/src/hub-actor.js +4 -144
- package/dist/node_modules/@agentorg/hub/dist/src/index.d.ts +1 -3
- package/dist/node_modules/@agentorg/hub/dist/src/index.js +1 -3
- package/dist/node_modules/@agentorg/hub/dist/src/router.d.ts +9 -58
- package/dist/node_modules/@agentorg/hub/dist/src/router.js +41 -414
- package/dist/node_modules/@agentorg/hub/dist/src/state.d.ts +17 -317
- package/dist/node_modules/@agentorg/hub/dist/src/state.js +42 -536
- package/dist/node_modules/@agentorg/hub/dist/src/validate.d.ts +1 -3
- package/dist/node_modules/@agentorg/hub/dist/src/validate.js +19 -17
- package/dist/node_modules/@agentorg/hub/package.json +0 -3
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.d.ts +119 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.js +96 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-settings.d.ts +42 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-settings.js +53 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/chat.d.ts +93 -38
- package/dist/node_modules/@agentorg/interfaces/dist/src/chat.js +71 -22
- package/dist/node_modules/@agentorg/interfaces/dist/src/connection.d.ts +36 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/connection.js +31 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/folders.d.ts +19 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/folders.js +25 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/hub.d.ts +16 -286
- package/dist/node_modules/@agentorg/interfaces/dist/src/hub.js +17 -159
- package/dist/node_modules/@agentorg/interfaces/dist/src/index.d.ts +9 -9
- package/dist/node_modules/@agentorg/interfaces/dist/src/index.js +9 -9
- package/dist/node_modules/@agentorg/interfaces/dist/src/kv.d.ts +2 -2
- package/dist/node_modules/@agentorg/interfaces/dist/src/kv.js +2 -2
- package/dist/node_modules/@agentorg/interfaces/dist/src/llm-session.d.ts +2 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/llm-session.js +2 -1
- package/dist/node_modules/@agentorg/interfaces/dist/src/manager.d.ts +626 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/manager.js +349 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/tools.d.ts +39 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/tools.js +43 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/units.d.ts +460 -0
- package/dist/node_modules/@agentorg/interfaces/dist/src/units.js +204 -0
- package/dist/node_modules/@agentorg/looper/dist/src/index.d.ts +5 -0
- package/dist/node_modules/@agentorg/looper/dist/src/index.js +5 -0
- package/dist/node_modules/@agentorg/looper/dist/src/loop.d.ts +75 -0
- package/dist/node_modules/@agentorg/looper/dist/src/loop.js +332 -0
- package/dist/node_modules/@agentorg/looper/dist/src/looper.d.ts +71 -0
- package/dist/node_modules/@agentorg/looper/dist/src/looper.js +282 -0
- package/dist/node_modules/@agentorg/looper/dist/src/record.d.ts +99 -0
- package/dist/node_modules/@agentorg/looper/dist/src/record.js +278 -0
- package/dist/node_modules/@agentorg/looper/dist/src/tools.d.ts +11 -0
- package/dist/node_modules/@agentorg/looper/dist/src/tools.js +42 -0
- package/dist/node_modules/@agentorg/looper/dist/src/types.d.ts +147 -0
- package/dist/node_modules/@agentorg/looper/dist/src/types.js +32 -0
- package/dist/node_modules/@agentorg/looper/package.json +24 -0
- package/dist/node_modules/@agentorg/manager/dist/src/activity.d.ts +18 -0
- package/dist/node_modules/@agentorg/manager/dist/src/activity.js +65 -0
- package/dist/node_modules/@agentorg/manager/dist/src/control.d.ts +134 -0
- package/dist/node_modules/@agentorg/manager/dist/src/control.js +1115 -0
- package/dist/node_modules/@agentorg/manager/dist/src/form.d.ts +40 -0
- package/dist/node_modules/@agentorg/manager/dist/src/form.js +191 -0
- package/dist/node_modules/@agentorg/manager/dist/src/index.d.ts +1 -0
- package/dist/node_modules/@agentorg/manager/dist/src/index.js +1 -0
- package/dist/node_modules/@agentorg/manager/dist/src/manager.d.ts +25 -0
- package/dist/node_modules/@agentorg/manager/dist/src/manager.js +680 -0
- package/dist/node_modules/@agentorg/manager/dist/src/skills.d.ts +27 -0
- package/dist/node_modules/@agentorg/manager/dist/src/skills.js +152 -0
- package/dist/node_modules/@agentorg/manager/dist/src/stack.d.ts +454 -0
- package/dist/node_modules/@agentorg/manager/dist/src/stack.js +917 -0
- package/dist/node_modules/@agentorg/manager/package.json +30 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/connection.d.ts +34 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/connection.js +296 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/index.d.ts +4 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/index.js +4 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/kinds.d.ts +158 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/kinds.js +164 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/provider.d.ts +22 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/provider.js +197 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/public.d.ts +6 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/public.js +72 -0
- package/dist/node_modules/@agentorg/mcp/package.json +31 -0
- package/dist/node_modules/@agentorg/sdk/dist/src/cli.js +3 -23
- package/dist/node_modules/@agentorg/sdk/dist/src/client.d.ts +4 -25
- package/dist/node_modules/@agentorg/sdk/dist/src/client.js +5 -55
- package/dist/node_modules/@agentorg/sdk/dist/src/errors.d.ts +1 -1
- package/dist/node_modules/@agentorg/sdk/dist/src/errors.js +3 -3
- package/dist/node_modules/@agentorg/sdk/dist/src/filled.d.ts +21 -0
- package/dist/node_modules/@agentorg/sdk/dist/src/filled.js +24 -0
- package/dist/node_modules/@agentorg/sdk/dist/src/index.d.ts +3 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/index.js +2 -1
- package/dist/node_modules/@agentorg/sdk/dist/src/interface.d.ts +6 -34
- package/dist/node_modules/@agentorg/sdk/dist/src/interface.js +3 -24
- package/dist/node_modules/@agentorg/sdk/dist/src/module-host.d.ts +8 -17
- package/dist/node_modules/@agentorg/sdk/dist/src/module-host.js +15 -87
- package/dist/node_modules/@agentorg/sdk/dist/src/module.d.ts +10 -76
- package/dist/node_modules/@agentorg/sdk/dist/src/module.js +3 -23
- package/dist/node_modules/@agentorg/sdk/dist/src/person-commands.js +0 -5
- package/dist/node_modules/@agentorg/sdk/dist/src/runtime.d.ts +3 -27
- package/dist/node_modules/@agentorg/sdk/dist/src/runtime.js +5 -100
- package/dist/node_modules/@agentorg/sdk/dist/src/testing.d.ts +7 -8
- package/dist/node_modules/@agentorg/sdk/dist/src/testing.js +12 -24
- package/dist/node_modules/@agentorg/sdk/package.json +4 -37
- package/dist/node_modules/@agentorg/shell/dist/app.css +4624 -739
- package/dist/node_modules/@agentorg/shell/dist/app.js +245 -202
- package/dist/node_modules/@agentorg/shell/package.json +23 -10
- package/dist/node_modules/@agentorg/wire/dist/src/frames.d.ts +2 -3
- package/dist/node_modules/@agentorg/wire/dist/src/frames.js +2 -3
- package/dist/node_modules/@agentorg/wire/dist/src/ops.d.ts +5 -34
- package/dist/node_modules/@agentorg/wire/dist/src/ops.js +2 -17
- package/dist/node_modules/assistant/dist/src/chats.d.ts +92 -0
- package/dist/node_modules/assistant/dist/src/chats.js +357 -0
- package/dist/node_modules/assistant/dist/src/engine.d.ts +42 -0
- package/dist/node_modules/assistant/dist/src/engine.js +160 -0
- package/dist/node_modules/assistant/dist/src/local-store.d.ts +3 -0
- package/dist/node_modules/assistant/dist/src/local-store.js +58 -0
- package/dist/node_modules/assistant/dist/src/provider.d.ts +21 -0
- package/dist/node_modules/assistant/dist/src/provider.js +259 -0
- package/dist/node_modules/assistant/dist/src/settings.d.ts +17 -0
- package/dist/node_modules/assistant/dist/src/settings.js +43 -0
- package/dist/node_modules/assistant/dist/src/tools.d.ts +57 -0
- package/dist/node_modules/assistant/dist/src/tools.js +102 -0
- package/dist/node_modules/assistant/package.json +30 -0
- package/dist/node_modules/chatgpt/dist/src/account.d.ts +52 -0
- package/dist/node_modules/chatgpt/dist/src/account.js +270 -0
- package/dist/node_modules/chatgpt/dist/src/encoding.d.ts +7 -0
- package/dist/node_modules/chatgpt/dist/src/encoding.js +20 -0
- package/dist/node_modules/chatgpt/dist/src/file-store.d.ts +9 -0
- package/dist/node_modules/chatgpt/dist/src/file-store.js +68 -0
- package/dist/node_modules/chatgpt/dist/src/index.d.ts +1 -0
- package/dist/node_modules/chatgpt/dist/src/index.js +1 -0
- package/dist/node_modules/chatgpt/dist/src/jwt.d.ts +15 -0
- package/dist/node_modules/chatgpt/dist/src/jwt.js +37 -0
- package/dist/node_modules/chatgpt/dist/src/oauth.d.ts +43 -0
- package/dist/node_modules/chatgpt/dist/src/oauth.js +104 -0
- package/dist/node_modules/chatgpt/dist/src/program.d.ts +23 -0
- package/dist/node_modules/chatgpt/dist/src/program.js +41 -0
- package/dist/node_modules/chatgpt/dist/src/responses.d.ts +54 -0
- package/dist/node_modules/chatgpt/dist/src/responses.js +300 -0
- package/dist/node_modules/chatgpt/dist/src/serve.d.ts +65 -0
- package/dist/node_modules/chatgpt/dist/src/serve.js +46 -0
- package/dist/node_modules/chatgpt/package.json +33 -0
- package/dist/src/api.d.ts +5 -34
- package/dist/src/api.js +10 -68
- package/dist/src/cli.js +3 -7
- package/dist/src/folders.d.ts +2 -0
- package/dist/src/folders.js +45 -0
- package/dist/src/gateway.d.ts +13 -8
- package/dist/src/gateway.js +119 -296
- package/dist/src/index.d.ts +4 -5
- package/dist/src/index.js +3 -4
- package/dist/src/live.d.ts +17 -0
- package/dist/src/live.js +109 -0
- package/dist/src/notes.d.ts +11 -0
- package/dist/src/notes.js +64 -0
- package/dist/src/oauth.d.ts +16 -0
- package/dist/src/oauth.js +46 -0
- package/dist/src/programs.d.ts +36 -0
- package/dist/src/programs.js +198 -0
- package/dist/src/signin.d.ts +12 -3
- package/dist/src/signin.js +30 -6
- package/package.json +4 -11
- package/dist/bundled/index.json +0 -6
- package/dist/bundled/sha256-0e91cc3d0e9762b81abc2427b5496b20f383abcd6fb97d928a8d50e956b54d1f.agentorg +0 -0
- package/dist/bundled/sha256-1f64ca62a0209d767d6aa6c4feb50b0653d60f10bebbc301f79bd3a37e0153c1.agentorg +0 -0
- package/dist/bundled/sha256-4055f8fddf6b90f3d356543f554b2a9e9a2c4a8827a8774f8f87952dc1692331.agentorg +0 -0
- package/dist/bundled/sha256-e356ea3c1899138a8494baf521556831006f38534fa9e92ae873e7e6956c3a46.agentorg +0 -0
- package/dist/node_modules/@agentorg/hub/dist/src/build.d.ts +0 -17
- package/dist/node_modules/@agentorg/hub/dist/src/build.js +0 -120
- package/dist/node_modules/@agentorg/hub/dist/src/logs.d.ts +0 -33
- package/dist/node_modules/@agentorg/hub/dist/src/logs.js +0 -162
- package/dist/node_modules/@agentorg/hub/dist/src/net.d.ts +0 -41
- package/dist/node_modules/@agentorg/hub/dist/src/net.js +0 -265
- package/dist/node_modules/@agentorg/hub/dist/src/processes.d.ts +0 -78
- package/dist/node_modules/@agentorg/hub/dist/src/processes.js +0 -485
- package/dist/node_modules/@agentorg/hub/dist/src/storage.d.ts +0 -38
- package/dist/node_modules/@agentorg/hub/dist/src/storage.js +0 -194
- package/dist/node_modules/@agentorg/interfaces/dist/src/http-egress.d.ts +0 -6
- package/dist/node_modules/@agentorg/interfaces/dist/src/http-egress.js +0 -7
- package/dist/node_modules/@agentorg/interfaces/dist/src/installer.d.ts +0 -191
- package/dist/node_modules/@agentorg/interfaces/dist/src/installer.js +0 -136
- package/dist/node_modules/@agentorg/interfaces/dist/src/layout.d.ts +0 -47
- package/dist/node_modules/@agentorg/interfaces/dist/src/layout.js +0 -52
- package/dist/node_modules/@agentorg/interfaces/dist/src/rules.d.ts +0 -35
- package/dist/node_modules/@agentorg/interfaces/dist/src/rules.js +0 -37
- package/dist/node_modules/@agentorg/interfaces/dist/src/storage-admin.d.ts +0 -23
- package/dist/node_modules/@agentorg/interfaces/dist/src/storage-admin.js +0 -31
- package/dist/node_modules/@agentorg/interfaces/dist/src/ui-shell.d.ts +0 -14
- package/dist/node_modules/@agentorg/interfaces/dist/src/ui-shell.js +0 -26
- package/dist/node_modules/@agentorg/interfaces/dist/src/ui-view.d.ts +0 -27
- package/dist/node_modules/@agentorg/interfaces/dist/src/ui-view.js +0 -26
- package/dist/node_modules/@agentorg/kit/dist/src/chat.d.ts +0 -37
- package/dist/node_modules/@agentorg/kit/dist/src/chat.js +0 -131
- package/dist/node_modules/@agentorg/kit/dist/src/components.d.ts +0 -94
- package/dist/node_modules/@agentorg/kit/dist/src/components.js +0 -66
- package/dist/node_modules/@agentorg/kit/dist/src/icons.d.ts +0 -17
- package/dist/node_modules/@agentorg/kit/dist/src/icons.js +0 -30
- package/dist/node_modules/@agentorg/kit/dist/src/index.d.ts +0 -6
- package/dist/node_modules/@agentorg/kit/dist/src/index.js +0 -10
- package/dist/node_modules/@agentorg/kit/dist/src/markdown.d.ts +0 -9
- package/dist/node_modules/@agentorg/kit/dist/src/markdown.js +0 -14
- package/dist/node_modules/@agentorg/kit/dist/src/resizable.d.ts +0 -5
- package/dist/node_modules/@agentorg/kit/dist/src/resizable.js +0 -8
- package/dist/node_modules/@agentorg/kit/dist/src/styles.d.ts +0 -11
- package/dist/node_modules/@agentorg/kit/dist/src/styles.js +0 -166
- package/dist/node_modules/@agentorg/kit/package.json +0 -44
- package/dist/node_modules/@agentorg/sdk/dist/frame/runtime.js +0 -753
- package/dist/node_modules/@agentorg/sdk/dist/host/runner.js +0 -21963
- package/dist/node_modules/@agentorg/sdk/dist/src/build.d.ts +0 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/build.js +0 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/client-http.d.ts +0 -24
- package/dist/node_modules/@agentorg/sdk/dist/src/client-http.js +0 -247
- package/dist/node_modules/@agentorg/sdk/dist/src/describe.d.ts +0 -13
- package/dist/node_modules/@agentorg/sdk/dist/src/describe.js +0 -16
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-base.d.ts +0 -8
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-base.js +0 -64
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-entry.d.ts +0 -1
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-entry.js +0 -35
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-host.d.ts +0 -57
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-host.js +0 -293
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-protocol.d.ts +0 -124
- package/dist/node_modules/@agentorg/sdk/dist/src/frame-protocol.js +0 -16
- package/dist/node_modules/@agentorg/sdk/dist/src/frame.d.ts +0 -25
- package/dist/node_modules/@agentorg/sdk/dist/src/frame.js +0 -248
- package/dist/node_modules/@agentorg/sdk/dist/src/host-main.d.ts +0 -1
- package/dist/node_modules/@agentorg/sdk/dist/src/host-main.js +0 -60
- package/dist/node_modules/@agentorg/sdk/dist/src/host.d.ts +0 -25
- package/dist/node_modules/@agentorg/sdk/dist/src/host.js +0 -92
- package/dist/node_modules/@agentorg/sdk/dist/src/manifest-api.d.ts +0 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/manifest-api.js +0 -2
- package/dist/node_modules/@agentorg/sdk/dist/src/manifest.d.ts +0 -56
- package/dist/node_modules/@agentorg/sdk/dist/src/manifest.js +0 -165
- package/dist/node_modules/@agentorg/sdk/dist/src/package.d.ts +0 -60
- package/dist/node_modules/@agentorg/sdk/dist/src/package.js +0 -538
- package/dist/node_modules/@agentorg/sdk/dist/src/tar.d.ts +0 -7
- package/dist/node_modules/@agentorg/sdk/dist/src/tar.js +0 -71
- package/dist/node_modules/@agentorg/shell/dist/agent-bindings.js +0 -59
- package/dist/node_modules/@agentorg/shell/src/agent-bindings.ts +0 -107
- package/dist/src/frames.d.ts +0 -23
- package/dist/src/frames.js +0 -78
- package/dist/src/layout.d.ts +0 -44
- package/dist/src/layout.js +0 -90
- package/dist/src/model.d.ts +0 -54
- package/dist/src/model.js +0 -299
- package/dist/src/rules.d.ts +0 -71
- package/dist/src/rules.js +0 -157
- package/dist/src/transfer.d.ts +0 -30
- package/dist/src/transfer.js +0 -110
|
@@ -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>;
|
|
@@ -0,0 +1,278 @@
|
|
|
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 } from "./types.js";
|
|
8
|
+
/** A message as the record keeps it. */
|
|
9
|
+
export const Entry = z.object({
|
|
10
|
+
id: z.string(),
|
|
11
|
+
role: z.enum(["user", "assistant", "tool"]),
|
|
12
|
+
/** What the person sees, for a tool message; the others show their message items. */
|
|
13
|
+
text: z.string().optional(),
|
|
14
|
+
at: z.string(),
|
|
15
|
+
items: z.array(Item),
|
|
16
|
+
/** For a tool message whose call ran: how it ended. */
|
|
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(),
|
|
26
|
+
});
|
|
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.
|
|
33
|
+
/** What the model is sent of the past: the newest exchanges up to this many bytes of JSON. */
|
|
34
|
+
export const INPUT_BUDGET = 400_000;
|
|
35
|
+
/** At most this many messages are kept, in at most this many bytes, oldest deleted first. */
|
|
36
|
+
export const KEEP = 2000;
|
|
37
|
+
export const KEEP_BYTES = 16 * 1024 * 1024;
|
|
38
|
+
/** A stored message's text, and a call's arguments, are cut to fit these, in bytes of JSON. */
|
|
39
|
+
const MAX_TEXT = 192 * 1024;
|
|
40
|
+
const MAX_ARGUMENTS = 16 * 1024;
|
|
41
|
+
/** A page of messages stays under this, so whatever carries it can do so in one piece. */
|
|
42
|
+
const PAGE_BUDGET = 512 * 1024;
|
|
43
|
+
const encoder = new TextEncoder();
|
|
44
|
+
const size = (value) => encoder.encode(JSON.stringify(value)).byteLength;
|
|
45
|
+
const LEFT_OUT = "(Left out: the conversation was too large to send it.)";
|
|
46
|
+
const ARGUMENTS_LEFT_OUT = '{"left_out":"too large to keep"}';
|
|
47
|
+
/** The first `length` UTF-16 units of `text`, never ending inside a surrogate pair. */
|
|
48
|
+
export function head(text, length) {
|
|
49
|
+
const cut = text.slice(0, length);
|
|
50
|
+
return /[\uD800-\uDBFF]$/.test(cut) ? cut.slice(0, -1) : cut;
|
|
51
|
+
}
|
|
52
|
+
/** `text`, cut so its JSON fits `max` bytes, saying so when it is cut. */
|
|
53
|
+
export function fitted(text, max) {
|
|
54
|
+
let kept = text;
|
|
55
|
+
for (let bytes = size(kept); bytes > max; bytes = size(kept))
|
|
56
|
+
kept = `${head(kept, Math.floor((kept.length * max) / bytes) - 16)}… (cut)`;
|
|
57
|
+
return kept;
|
|
58
|
+
}
|
|
59
|
+
/** A message shows at most this much; the record keeps all of it. */
|
|
60
|
+
const MAX_SHOWN = 100_000;
|
|
61
|
+
/** What the person sees of a message. */
|
|
62
|
+
export const textOf = (entry) => head(entry.text ??
|
|
63
|
+
entry.items.flatMap((item) => (item.type === "message" ? [item.content] : [])).join(""), MAX_SHOWN);
|
|
64
|
+
/** A call's arguments as they are kept and shown: past their cap, a note in their place. */
|
|
65
|
+
export const argumentsOf = (args) => size(args) > MAX_ARGUMENTS ? ARGUMENTS_LEFT_OUT : args;
|
|
66
|
+
/**
|
|
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.
|
|
69
|
+
*/
|
|
70
|
+
export function messageOf(entry) {
|
|
71
|
+
const message = { id: entry.id, role: entry.role, text: textOf(entry), at: entry.at };
|
|
72
|
+
const call = entry.items.find((item) => item.type === "function_call");
|
|
73
|
+
if (entry.role !== "tool" || !entry.state || !call)
|
|
74
|
+
return message;
|
|
75
|
+
const output = entry.items.find((item) => item.type === "function_call_output");
|
|
76
|
+
return {
|
|
77
|
+
...message,
|
|
78
|
+
tool: {
|
|
79
|
+
name: head(call.name, 128),
|
|
80
|
+
state: entry.state,
|
|
81
|
+
input: head(call.arguments, MAX_SHOWN),
|
|
82
|
+
...(output ? { output: head(output.output, MAX_SHOWN) } : {}),
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** A message as it is stored: each text once, and nothing past its cap. */
|
|
87
|
+
const kept = (entry) => ({
|
|
88
|
+
...entry,
|
|
89
|
+
items: entry.items.map((item) => {
|
|
90
|
+
if (item.type === "message")
|
|
91
|
+
return { ...item, content: fitted(item.content, MAX_TEXT) };
|
|
92
|
+
if (item.type === "function_call_output")
|
|
93
|
+
return { ...item, output: fitted(item.output, MAX_TEXT) };
|
|
94
|
+
return { ...item, arguments: argumentsOf(item.arguments) };
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
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 }) {
|
|
102
|
+
let loaded;
|
|
103
|
+
const load = () => {
|
|
104
|
+
if (loaded)
|
|
105
|
+
return loaded;
|
|
106
|
+
const attempt = (async () => {
|
|
107
|
+
const records = [];
|
|
108
|
+
let last = 0;
|
|
109
|
+
let bytes = 0;
|
|
110
|
+
let after;
|
|
111
|
+
do {
|
|
112
|
+
const page = await store.list({
|
|
113
|
+
prefix: MESSAGES,
|
|
114
|
+
limit: 1000,
|
|
115
|
+
...(after ? { after } : {}),
|
|
116
|
+
});
|
|
117
|
+
// A few at a time, in order: a long record shouldn't hold up the first send one read at
|
|
118
|
+
// a time, nor flood its store.
|
|
119
|
+
for (let at = 0; at < page.keys.length; at += READS) {
|
|
120
|
+
const keys = page.keys.slice(at, at + READS);
|
|
121
|
+
const values = await Promise.all(keys.map((key) => store.get({ key })));
|
|
122
|
+
keys.forEach((key, i) => {
|
|
123
|
+
last = Math.max(last, seqOf(key));
|
|
124
|
+
const parsed = Entry.safeParse(values[i]?.value);
|
|
125
|
+
if (!parsed.success) {
|
|
126
|
+
log.warn("A stored message didn't read back; it is left out.", { key });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const record = { key, entry: parsed.data, bytes: size(parsed.data) };
|
|
130
|
+
records.push(record);
|
|
131
|
+
bytes += record.bytes;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
after = page.next ?? undefined;
|
|
135
|
+
} while (after);
|
|
136
|
+
return { records, last, bytes };
|
|
137
|
+
})();
|
|
138
|
+
loaded = attempt;
|
|
139
|
+
attempt.catch(() => {
|
|
140
|
+
if (loaded === attempt)
|
|
141
|
+
loaded = undefined;
|
|
142
|
+
});
|
|
143
|
+
return attempt;
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
/**
|
|
147
|
+
* Keep a message, under `id` or its own number, and delete the oldest past the cap. A tool
|
|
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.
|
|
151
|
+
*/
|
|
152
|
+
async append(entry, id, read) {
|
|
153
|
+
const state = await load();
|
|
154
|
+
const key = keyOf(++state.last);
|
|
155
|
+
const record = {
|
|
156
|
+
key,
|
|
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
|
+
}),
|
|
163
|
+
bytes: 0,
|
|
164
|
+
writing: entry.role === "user",
|
|
165
|
+
};
|
|
166
|
+
record.bytes = size(record.entry);
|
|
167
|
+
state.records.push(record);
|
|
168
|
+
state.bytes += record.bytes;
|
|
169
|
+
try {
|
|
170
|
+
await store.set({ key, value: record.entry });
|
|
171
|
+
record.writing = false;
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (entry.role !== "tool") {
|
|
175
|
+
state.records.splice(state.records.indexOf(record), 1);
|
|
176
|
+
state.bytes -= record.bytes;
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
log.error("A tool message couldn't be stored; it is kept until a restart.", {
|
|
180
|
+
error: String(error),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
while (state.records.length > 1 &&
|
|
184
|
+
(state.records.length > limits.keep || state.bytes > limits.keepBytes)) {
|
|
185
|
+
const oldest = state.records.shift();
|
|
186
|
+
state.bytes -= oldest.bytes;
|
|
187
|
+
await store.delete({ key: oldest.key }).catch((error) => log.warn("An old message couldn't be deleted.", {
|
|
188
|
+
key: oldest.key,
|
|
189
|
+
error: String(error),
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
return record.entry;
|
|
193
|
+
},
|
|
194
|
+
/** Whether nothing is kept. */
|
|
195
|
+
async empty() {
|
|
196
|
+
return (await load()).records.length === 0;
|
|
197
|
+
},
|
|
198
|
+
/** Messages before `before`, oldest first, as many as fit `limit` and `PAGE_BUDGET`. */
|
|
199
|
+
async page(before, limit) {
|
|
200
|
+
const { records } = await load();
|
|
201
|
+
const end = before === undefined
|
|
202
|
+
? records.length
|
|
203
|
+
: records.findIndex((record) => record.entry.id === before);
|
|
204
|
+
const messages = [];
|
|
205
|
+
let bytes = 0;
|
|
206
|
+
let start = Math.max(end, 0);
|
|
207
|
+
while (start > 0 && messages.length < limit) {
|
|
208
|
+
const { entry } = records[start - 1];
|
|
209
|
+
const message = messageOf(entry);
|
|
210
|
+
bytes += size(message);
|
|
211
|
+
if (bytes > PAGE_BUDGET && messages.length > 0)
|
|
212
|
+
break;
|
|
213
|
+
messages.unshift(message);
|
|
214
|
+
start--;
|
|
215
|
+
}
|
|
216
|
+
return { messages, more: start > 0 };
|
|
217
|
+
},
|
|
218
|
+
/**
|
|
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.
|
|
226
|
+
*/
|
|
227
|
+
async input() {
|
|
228
|
+
const { records, last } = await load();
|
|
229
|
+
const unwritten = records.find((record) => record.writing);
|
|
230
|
+
const read = unwritten ? seqOf(unwritten.key) - 1 : last;
|
|
231
|
+
const exchanges = [];
|
|
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)
|
|
236
|
+
exchanges.push([]);
|
|
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);
|
|
254
|
+
}
|
|
255
|
+
putLate(last);
|
|
256
|
+
let total = exchanges.reduce((sum, items) => sum + size(items), 0);
|
|
257
|
+
while (exchanges.length > 2 && total > INPUT_BUDGET)
|
|
258
|
+
total -= size(exchanges.shift());
|
|
259
|
+
const items = exchanges.flat();
|
|
260
|
+
for (let i = 0; i < items.length && total > INPUT_BUDGET; i++) {
|
|
261
|
+
const item = items[i];
|
|
262
|
+
if (item.type === "function_call_output" && item.output !== LEFT_OUT) {
|
|
263
|
+
total -= size(item.output) - size(LEFT_OUT);
|
|
264
|
+
items[i] = { ...item, output: LEFT_OUT };
|
|
265
|
+
}
|
|
266
|
+
else if (item.type === "function_call" && item.arguments !== ARGUMENTS_LEFT_OUT) {
|
|
267
|
+
total -= size(item.arguments) - size(ARGUMENTS_LEFT_OUT);
|
|
268
|
+
items[i] = { ...item, arguments: ARGUMENTS_LEFT_OUT };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
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);
|
|
276
|
+
},
|
|
277
|
+
};
|
|
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>;
|