@agentorg/cli 0.0.6 → 0.0.8
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 +10 -7
- 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/router.d.ts +4 -2
- package/dist/node_modules/@agentorg/hub/dist/src/router.js +3 -1
- package/dist/node_modules/@agentorg/hub/dist/src/validate.js +16 -2
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.d.ts +34 -47
- package/dist/node_modules/@agentorg/interfaces/dist/src/agent-provider.js +25 -34
- package/dist/node_modules/@agentorg/interfaces/dist/src/chat.d.ts +90 -50
- package/dist/node_modules/@agentorg/interfaces/dist/src/chat.js +69 -34
- 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/index.d.ts +6 -4
- package/dist/node_modules/@agentorg/interfaces/dist/src/index.js +6 -4
- 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 +381 -27
- package/dist/node_modules/@agentorg/interfaces/dist/src/manager.js +179 -15
- 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 +89 -0
- package/dist/node_modules/@agentorg/looper/dist/src/loop.js +366 -0
- package/dist/node_modules/@agentorg/looper/dist/src/looper.d.ts +81 -0
- package/dist/node_modules/@agentorg/looper/dist/src/looper.js +574 -0
- package/dist/node_modules/@agentorg/looper/dist/src/main.d.ts +34 -0
- package/dist/node_modules/@agentorg/looper/dist/src/main.js +116 -0
- package/dist/node_modules/@agentorg/looper/dist/src/record.d.ts +181 -0
- package/dist/node_modules/@agentorg/looper/dist/src/record.js +433 -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/manager.d.ts +4 -0
- package/dist/node_modules/@agentorg/manager/dist/src/manager.js +341 -189
- package/dist/node_modules/@agentorg/manager/dist/src/skills.d.ts +2 -0
- package/dist/node_modules/@agentorg/manager/dist/src/skills.js +7 -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/mcp/dist/src/connection.d.ts +2 -1
- package/dist/node_modules/@agentorg/mcp/dist/src/connection.js +8 -2
- package/dist/node_modules/@agentorg/mcp/dist/src/index.d.ts +3 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/index.js +3 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/kinds.d.ts +108 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/kinds.js +95 -0
- package/dist/node_modules/@agentorg/mcp/dist/src/provider.d.ts +2 -2
- package/dist/node_modules/@agentorg/mcp/dist/src/provider.js +48 -18
- 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/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 +1 -0
- package/dist/node_modules/@agentorg/sdk/dist/src/index.js +1 -0
- package/dist/node_modules/@agentorg/shell/dist/app.css +1306 -181
- package/dist/node_modules/@agentorg/shell/dist/app.js +207 -166
- package/dist/node_modules/@agentorg/shell/package.json +6 -1
- package/dist/node_modules/assistant/dist/src/chats.d.ts +94 -0
- package/dist/node_modules/assistant/dist/src/chats.js +355 -0
- package/dist/node_modules/assistant/dist/src/engine.d.ts +26 -7
- package/dist/node_modules/assistant/dist/src/engine.js +107 -35
- package/dist/node_modules/assistant/dist/src/local-store.js +1 -1
- package/dist/node_modules/assistant/dist/src/provider.d.ts +1 -1
- package/dist/node_modules/assistant/dist/src/provider.js +47 -19
- package/dist/node_modules/assistant/dist/src/tools.d.ts +11 -28
- package/dist/node_modules/assistant/dist/src/tools.js +19 -40
- package/dist/node_modules/assistant/package.json +1 -0
- package/dist/src/api.js +4 -0
- package/dist/src/folders.d.ts +2 -0
- package/dist/src/folders.js +45 -0
- package/dist/src/gateway.d.ts +7 -1
- package/dist/src/gateway.js +81 -12
- package/dist/src/live.d.ts +17 -0
- package/dist/src/live.js +109 -0
- package/dist/src/programs.d.ts +7 -0
- package/dist/src/programs.js +29 -2
- package/dist/src/signin.d.ts +12 -3
- package/dist/src/signin.js +30 -6
- package/package.json +2 -1
- package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.d.ts +0 -88
- package/dist/node_modules/@agentorg/interfaces/dist/src/mcp-provider.js +0 -78
- package/dist/node_modules/assistant/dist/src/agent.d.ts +0 -62
- package/dist/node_modules/assistant/dist/src/agent.js +0 -226
- package/dist/node_modules/assistant/dist/src/history.d.ts +0 -97
- package/dist/node_modules/assistant/dist/src/history.js +0 -235
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A loop: a turn sends its record and the tools to the model, runs each function the model calls,
|
|
3
|
+
* and sends the results back, until the model answers without calling anything, or the turn has
|
|
4
|
+
* made its `MAX_CALLS` calls. A message said while a turn runs is recorded at once, and the turn
|
|
5
|
+
* takes it at its next model request. A loop is one piece of work's: its messages come in the
|
|
6
|
+
* threads the work follows, and what comes of a request is kept in the thread of the newest
|
|
7
|
+
* message that request read.
|
|
8
|
+
*/
|
|
9
|
+
import { type LoopRecord } from "./record.js";
|
|
10
|
+
import { type Log, type Model, type Shown, type Status, type Tool } from "./types.js";
|
|
11
|
+
/** A brake on a turn, not a budget: nothing approves a call, and the person can stop it. */
|
|
12
|
+
export declare const MAX_CALLS = 200;
|
|
13
|
+
/** Where a loop says what it does, each message with the thread it is shown in. */
|
|
14
|
+
export interface LoopOut {
|
|
15
|
+
message(thread: string, message: Shown): void;
|
|
16
|
+
/** Text of the reply `id` as the model writes it. */
|
|
17
|
+
delta(thread: string, id: string, text: string): void;
|
|
18
|
+
/** What it is doing. */
|
|
19
|
+
status(status: Status): void;
|
|
20
|
+
/** A message in `thread` got no answer, and why: said before the `error` status. */
|
|
21
|
+
failed(thread: string, problem: {
|
|
22
|
+
code: string;
|
|
23
|
+
message: string;
|
|
24
|
+
}): void;
|
|
25
|
+
}
|
|
26
|
+
/** A turn's place among the turns running: `entered` settles when it has it, or has left. */
|
|
27
|
+
export interface Place {
|
|
28
|
+
entered: Promise<void>;
|
|
29
|
+
/** Give the place back, or stop waiting for it. */
|
|
30
|
+
leave(): void;
|
|
31
|
+
}
|
|
32
|
+
export declare function createLoop(options: {
|
|
33
|
+
/**
|
|
34
|
+
* Read for each model request, since any of it may change between two: what the model is told,
|
|
35
|
+
* what it can call, and which model to ask, none meaning the model's own default.
|
|
36
|
+
*/
|
|
37
|
+
request(): Promise<{
|
|
38
|
+
instructions: string;
|
|
39
|
+
tools: Tool[];
|
|
40
|
+
model?: string | undefined;
|
|
41
|
+
}>;
|
|
42
|
+
model: Model;
|
|
43
|
+
/**
|
|
44
|
+
* Ask for a place for a turn about to start, or a turn in the line for one. `begun` says whether
|
|
45
|
+
* its record held anything before it. It throws when the turn can't be taken now.
|
|
46
|
+
*/
|
|
47
|
+
enter(begun: boolean): Place;
|
|
48
|
+
record: LoopRecord;
|
|
49
|
+
out: LoopOut;
|
|
50
|
+
log: Log;
|
|
51
|
+
}): {
|
|
52
|
+
/**
|
|
53
|
+
* Say a message in a thread the work follows. With no turn it starts one, whose progress is
|
|
54
|
+
* said through `out`. In a turn, running or waiting for its place, it is recorded and said at
|
|
55
|
+
* once, and the turn takes it at its next model request.
|
|
56
|
+
*/
|
|
57
|
+
send: (text: string, thread: string) => Promise<{
|
|
58
|
+
messageId: string;
|
|
59
|
+
}>;
|
|
60
|
+
/**
|
|
61
|
+
* Take the messages waiting in `thread`, which the work has come to follow, as its own, and
|
|
62
|
+
* answer them: in the turn running, or in one this starts. With none waiting no turn starts.
|
|
63
|
+
* When a cancel came first nothing is taken, and it says so with `false`.
|
|
64
|
+
*/
|
|
65
|
+
adopt: (thread: string) => Promise<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Go on with a turn that was cut off before it ended, as by a restart: the thread it was
|
|
68
|
+
* answering in shows one line saying so, and the model is told once to check what is already
|
|
69
|
+
* done. Nothing goes on in work with nothing kept, in work that has had a turn since the loop
|
|
70
|
+
* was made, or when a cancel comes first.
|
|
71
|
+
*/
|
|
72
|
+
resume: () => Promise<void>;
|
|
73
|
+
/** Whether the turn running now goes on from one that was cut off. */
|
|
74
|
+
resumed: () => boolean;
|
|
75
|
+
/** The status now. */
|
|
76
|
+
state: () => Status;
|
|
77
|
+
/**
|
|
78
|
+
* Stop the turn now: its model request is cancelled, no further call starts, and a new turn
|
|
79
|
+
* may begin at once. A call already running finishes, and its result is kept. A turn waiting
|
|
80
|
+
* for its place never starts.
|
|
81
|
+
*/
|
|
82
|
+
cancel(): Promise<void>;
|
|
83
|
+
/** Settles when the latest turn has written all it will. */
|
|
84
|
+
settled: () => Promise<void>;
|
|
85
|
+
/** Settles when a stopped turn's running call has ended: at once, if none is running. */
|
|
86
|
+
drained: () => Promise<void>;
|
|
87
|
+
/** Streamed text of the model request running now, for the reply it becomes. */
|
|
88
|
+
delta(requestId: string, text: string): void;
|
|
89
|
+
};
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A loop: a turn sends its record and the tools to the model, runs each function the model calls,
|
|
3
|
+
* and sends the results back, until the model answers without calling anything, or the turn has
|
|
4
|
+
* made its `MAX_CALLS` calls. A message said while a turn runs is recorded at once, and the turn
|
|
5
|
+
* takes it at its next model request. A loop is one piece of work's: its messages come in the
|
|
6
|
+
* threads the work follows, and what comes of a request is kept in the thread of the newest
|
|
7
|
+
* message that request read.
|
|
8
|
+
*/
|
|
9
|
+
import { argumentsOf, head, messageOf, userEntry } from "./record.js";
|
|
10
|
+
import { capped, runTool } from "./tools.js";
|
|
11
|
+
import { codeOf, Refusal, } from "./types.js";
|
|
12
|
+
/** A brake on a turn, not a budget: nothing approves a call, and the person can stop it. */
|
|
13
|
+
export const MAX_CALLS = 200;
|
|
14
|
+
/** What a thread shows, and its model is told, when a turn that was cut off goes on. */
|
|
15
|
+
const CUT_OFF = {
|
|
16
|
+
shown: "Cut off by a restart: going on from where it stopped.",
|
|
17
|
+
told: "This turn was cut off by a restart. Check what is already done before you repeat anything, then go on.",
|
|
18
|
+
};
|
|
19
|
+
export function createLoop(options) {
|
|
20
|
+
const { model, record, out, log } = options;
|
|
21
|
+
let turn;
|
|
22
|
+
/** A cancelled turn whose running call hasn't settled: the next turn waits for its records. */
|
|
23
|
+
let draining;
|
|
24
|
+
/** Settles when the latest turn's loop has ended. */
|
|
25
|
+
let lastEnded;
|
|
26
|
+
/** The latest status, for a view that asks. */
|
|
27
|
+
let latest = { state: "idle" };
|
|
28
|
+
/** Set once a turn has started here: nothing that was cut off earlier is left to go on with. */
|
|
29
|
+
let began = false;
|
|
30
|
+
/** Counts the cancels, so that what was asked before one can tell when it is taken after it. */
|
|
31
|
+
let cancels = 0;
|
|
32
|
+
/** What is being taken now: the next thing asked is taken after it. */
|
|
33
|
+
let taking = Promise.resolve();
|
|
34
|
+
/**
|
|
35
|
+
* Take what was asked in the order it was asked, one at a time: a message, going on, a turn's
|
|
36
|
+
* end. `cancelled` says whether a cancel came between its asking and its taking.
|
|
37
|
+
*/
|
|
38
|
+
const inOrder = (take) => {
|
|
39
|
+
const at = cancels;
|
|
40
|
+
const taken = taking.then(() => take(at !== cancels));
|
|
41
|
+
taking = taken.catch(() => { });
|
|
42
|
+
return taken;
|
|
43
|
+
};
|
|
44
|
+
const say = (next) => {
|
|
45
|
+
latest = next;
|
|
46
|
+
out.status(next);
|
|
47
|
+
};
|
|
48
|
+
/** A turn's status, while it is still the turn: a cancelled one has already said idle. */
|
|
49
|
+
const status = (current, next) => {
|
|
50
|
+
if (turn === current)
|
|
51
|
+
say(next);
|
|
52
|
+
};
|
|
53
|
+
/** Run a turn on the record as it stands; its progress is said through `out`. */
|
|
54
|
+
const begin = (current) => {
|
|
55
|
+
current.ended = placed(current)
|
|
56
|
+
.catch((error) => {
|
|
57
|
+
if (current.cancelled)
|
|
58
|
+
return;
|
|
59
|
+
log.error("A turn failed.", { error: String(error) });
|
|
60
|
+
// Every thread it took a message in, and the one it was answering in, is told.
|
|
61
|
+
const detail = codeOf(error);
|
|
62
|
+
for (const thread of new Set([...current.took, current.thread ?? record.id]))
|
|
63
|
+
out.failed(thread, detail);
|
|
64
|
+
status(current, { state: "error", detail });
|
|
65
|
+
})
|
|
66
|
+
.finally(() => {
|
|
67
|
+
current.place?.leave();
|
|
68
|
+
if (turn === current)
|
|
69
|
+
turn = undefined;
|
|
70
|
+
})
|
|
71
|
+
.catch((error) => log.error("A turn's status failed.", { error: String(error) }));
|
|
72
|
+
lastEnded = current.ended;
|
|
73
|
+
};
|
|
74
|
+
const show = ({ entry, thread }) => out.message(thread, messageOf(entry));
|
|
75
|
+
/** A call starting: shown as running, under the id its record will have when it ends. */
|
|
76
|
+
const running = (call, id, thread) => out.message(thread, {
|
|
77
|
+
id,
|
|
78
|
+
role: "tool",
|
|
79
|
+
text: `${call.name}: running`,
|
|
80
|
+
at: new Date().toISOString(),
|
|
81
|
+
tool: { name: head(call.name, 128), state: "running", input: argumentsOf(call.arguments) },
|
|
82
|
+
});
|
|
83
|
+
/**
|
|
84
|
+
* Record a function call with its output, keeping every call paired, as the API needs. Its end
|
|
85
|
+
* is shown even if the record can't be written, so no call is left shown as running.
|
|
86
|
+
*/
|
|
87
|
+
const answered = async (call, result, id, read, thread) => {
|
|
88
|
+
const entry = {
|
|
89
|
+
role: "tool",
|
|
90
|
+
text: `${call.name}: ${result.ok ? "done" : result.code}`,
|
|
91
|
+
state: result.ok ? "done" : "refused",
|
|
92
|
+
items: [
|
|
93
|
+
call,
|
|
94
|
+
{
|
|
95
|
+
type: "function_call_output",
|
|
96
|
+
callId: call.callId,
|
|
97
|
+
output: capped(result.output),
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
try {
|
|
102
|
+
show(await record.append(entry, { thread, id, read }));
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
out.message(thread, messageOf({
|
|
106
|
+
...entry,
|
|
107
|
+
id,
|
|
108
|
+
at: new Date().toISOString(),
|
|
109
|
+
items: [{ ...call, arguments: argumentsOf(call.arguments) }, ...entry.items.slice(1)],
|
|
110
|
+
}));
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Note calls that won't run, in one message however many there are. They never ran, so the
|
|
116
|
+
* record leaves them out: what the model is sent next holds only calls with their outputs.
|
|
117
|
+
*/
|
|
118
|
+
const skipped = async (calls, code, thread) => {
|
|
119
|
+
if (calls.length === 0)
|
|
120
|
+
return;
|
|
121
|
+
const text = calls.length === 1 ? `${calls[0]?.name}: ${code}` : `${calls.length} calls: ${code}`;
|
|
122
|
+
show(await record.append({ role: "tool", text, items: [] }, { thread }));
|
|
123
|
+
};
|
|
124
|
+
async function run(current) {
|
|
125
|
+
let calls = 0;
|
|
126
|
+
for (;;) {
|
|
127
|
+
const { instructions, tools, model: chosen } = await options.request();
|
|
128
|
+
const { items: input, read, thread } = await record.input();
|
|
129
|
+
if (current.cancelled)
|
|
130
|
+
return;
|
|
131
|
+
current.thread = thread;
|
|
132
|
+
const requestId = crypto.randomUUID();
|
|
133
|
+
const replyId = crypto.randomUUID();
|
|
134
|
+
current.requestId = requestId;
|
|
135
|
+
current.replyId = replyId;
|
|
136
|
+
let output;
|
|
137
|
+
try {
|
|
138
|
+
({ output } = await model.respond({
|
|
139
|
+
requestId,
|
|
140
|
+
...(chosen ? { model: chosen } : {}),
|
|
141
|
+
instructions,
|
|
142
|
+
input,
|
|
143
|
+
tools: tools.map(({ run: _, ...tool }) => tool),
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
current.requestId = undefined;
|
|
148
|
+
}
|
|
149
|
+
if (current.cancelled)
|
|
150
|
+
return;
|
|
151
|
+
const texts = output.flatMap((item) => (item.type === "message" ? [item.content] : []));
|
|
152
|
+
const called = output.filter((item) => item.type === "function_call");
|
|
153
|
+
if (texts.length > 0 || called.length === 0)
|
|
154
|
+
show(await record.append({
|
|
155
|
+
role: "assistant",
|
|
156
|
+
items: texts.map((content) => ({ type: "message", role: "assistant", content })),
|
|
157
|
+
}, { thread, id: replyId, read }));
|
|
158
|
+
if (called.length === 0) {
|
|
159
|
+
if (current.cancelled)
|
|
160
|
+
return;
|
|
161
|
+
// The turn's end is taken in order with what was asked, so a message being recorded now
|
|
162
|
+
// is recorded first. One said since this request read the record is answered before the
|
|
163
|
+
// turn ends. With none the turn is over at once: the next message taken starts another.
|
|
164
|
+
current.ending = true;
|
|
165
|
+
const over = await inOrder(async () => {
|
|
166
|
+
const more = await record.saidAfter(read);
|
|
167
|
+
if (current.cancelled)
|
|
168
|
+
return true;
|
|
169
|
+
if (more)
|
|
170
|
+
return false;
|
|
171
|
+
current.place?.leave();
|
|
172
|
+
status(current, { state: "idle" });
|
|
173
|
+
turn = undefined;
|
|
174
|
+
return true;
|
|
175
|
+
});
|
|
176
|
+
current.ending = false;
|
|
177
|
+
if (over)
|
|
178
|
+
return;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
for (const [i, call] of called.entries()) {
|
|
182
|
+
if (current.cancelled || calls >= MAX_CALLS) {
|
|
183
|
+
const rest = called.slice(i);
|
|
184
|
+
await skipped(rest, current.cancelled ? "cancelled" : "limit", thread);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
calls++;
|
|
188
|
+
status(current, { state: "tool" });
|
|
189
|
+
// A call that has started runs to its end, and its real result is kept: it may have
|
|
190
|
+
// taken effect.
|
|
191
|
+
const id = crypto.randomUUID();
|
|
192
|
+
running(call, id, thread);
|
|
193
|
+
const result = await runTool(tools, call.name, call.arguments);
|
|
194
|
+
await answered(call, result, id, read, thread);
|
|
195
|
+
}
|
|
196
|
+
if (current.cancelled)
|
|
197
|
+
return;
|
|
198
|
+
if (calls >= MAX_CALLS)
|
|
199
|
+
throw new Refusal("limit", `This turn stopped after ${MAX_CALLS} tool calls.`);
|
|
200
|
+
status(current, { state: "thinking" });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** A turn from its place to its end: it waits for the place already shown as thinking. */
|
|
204
|
+
async function placed(current) {
|
|
205
|
+
status(current, { state: "thinking" });
|
|
206
|
+
await current.place?.entered;
|
|
207
|
+
current.waiting = false;
|
|
208
|
+
if (current.cancelled)
|
|
209
|
+
return;
|
|
210
|
+
// Where it answers until a request says: so a failure before one names its thread.
|
|
211
|
+
current.thread = await record.where();
|
|
212
|
+
await run(current);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Take a turn's place, or its turn in the line for one, before anything of it is kept. A turn
|
|
216
|
+
* cancelled already takes none: it runs nothing.
|
|
217
|
+
*/
|
|
218
|
+
const enter = (current, begun) => {
|
|
219
|
+
if (current.cancelled)
|
|
220
|
+
return;
|
|
221
|
+
current.place = options.enter(begun);
|
|
222
|
+
current.waiting = true;
|
|
223
|
+
};
|
|
224
|
+
const said = async (text, thread, joined) => {
|
|
225
|
+
const kept = await record.append({ ...userEntry(text), ...(joined ? { joined: true } : {}) }, { thread });
|
|
226
|
+
show(kept);
|
|
227
|
+
return { messageId: kept.entry.id };
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Start a turn: `open` takes its place and keeps what opens it, then its loop runs. When `open`
|
|
231
|
+
* gives nothing, no turn starts. A turn `cancelled` before it started keeps what opens it and
|
|
232
|
+
* runs nothing.
|
|
233
|
+
*/
|
|
234
|
+
const start = async (cancelled, open) => {
|
|
235
|
+
const current = { cancelled, took: new Set() };
|
|
236
|
+
if (!cancelled)
|
|
237
|
+
turn = current;
|
|
238
|
+
began = true;
|
|
239
|
+
try {
|
|
240
|
+
// A cancelled turn's last call lands in the record before anything new does.
|
|
241
|
+
await draining;
|
|
242
|
+
draining = undefined;
|
|
243
|
+
const begun = await open(current);
|
|
244
|
+
if (begun)
|
|
245
|
+
begin(current);
|
|
246
|
+
else if (turn === current)
|
|
247
|
+
turn = undefined;
|
|
248
|
+
return begun?.opened;
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
current.place?.leave();
|
|
252
|
+
if (turn === current)
|
|
253
|
+
turn = undefined;
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
return {
|
|
258
|
+
/**
|
|
259
|
+
* Say a message in a thread the work follows. With no turn it starts one, whose progress is
|
|
260
|
+
* said through `out`. In a turn, running or waiting for its place, it is recorded and said at
|
|
261
|
+
* once, and the turn takes it at its next model request.
|
|
262
|
+
*/
|
|
263
|
+
send: (text, thread) => inOrder(async (cancelled) => {
|
|
264
|
+
if (turn) {
|
|
265
|
+
turn.took.add(thread);
|
|
266
|
+
return said(text, thread, true);
|
|
267
|
+
}
|
|
268
|
+
return (await start(cancelled, async (current) => {
|
|
269
|
+
enter(current, await record.begun());
|
|
270
|
+
current.took.add(thread);
|
|
271
|
+
return { opened: await said(text, thread, false) };
|
|
272
|
+
}));
|
|
273
|
+
}),
|
|
274
|
+
/**
|
|
275
|
+
* Take the messages waiting in `thread`, which the work has come to follow, as its own, and
|
|
276
|
+
* answer them: in the turn running, or in one this starts. With none waiting no turn starts.
|
|
277
|
+
* When a cancel came first nothing is taken, and it says so with `false`.
|
|
278
|
+
*/
|
|
279
|
+
adopt: (thread) => inOrder(async (cancelled) => {
|
|
280
|
+
if (cancelled)
|
|
281
|
+
return false;
|
|
282
|
+
if (turn) {
|
|
283
|
+
turn.took.add(thread);
|
|
284
|
+
await record.adopt(thread, true);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
await start(false, async (current) => {
|
|
288
|
+
enter(current, await record.begun());
|
|
289
|
+
current.took.add(thread);
|
|
290
|
+
if ((await record.adopt(thread, false)) > 0)
|
|
291
|
+
return { opened: undefined };
|
|
292
|
+
current.place?.leave();
|
|
293
|
+
return undefined;
|
|
294
|
+
});
|
|
295
|
+
return true;
|
|
296
|
+
}),
|
|
297
|
+
/**
|
|
298
|
+
* Go on with a turn that was cut off before it ended, as by a restart: the thread it was
|
|
299
|
+
* answering in shows one line saying so, and the model is told once to check what is already
|
|
300
|
+
* done. Nothing goes on in work with nothing kept, in work that has had a turn since the loop
|
|
301
|
+
* was made, or when a cancel comes first.
|
|
302
|
+
*/
|
|
303
|
+
resume: () => inOrder(async (cancelled) => {
|
|
304
|
+
if (began || cancelled)
|
|
305
|
+
return;
|
|
306
|
+
await start(false, async (current) => {
|
|
307
|
+
if ((await record.empty()) || current.cancelled)
|
|
308
|
+
return undefined;
|
|
309
|
+
enter(current, true);
|
|
310
|
+
current.resumed = true;
|
|
311
|
+
// In the thread of the message it was answering.
|
|
312
|
+
const thread = await record.where();
|
|
313
|
+
current.took.add(thread);
|
|
314
|
+
show(await record.append({
|
|
315
|
+
role: "tool",
|
|
316
|
+
text: CUT_OFF.shown,
|
|
317
|
+
items: [{ type: "message", role: "user", content: CUT_OFF.told }],
|
|
318
|
+
}, { thread }));
|
|
319
|
+
return { opened: undefined };
|
|
320
|
+
});
|
|
321
|
+
}),
|
|
322
|
+
/** Whether the turn running now goes on from one that was cut off. */
|
|
323
|
+
resumed: () => turn?.resumed === true,
|
|
324
|
+
/** The status now. */
|
|
325
|
+
state: () => latest,
|
|
326
|
+
/**
|
|
327
|
+
* Stop the turn now: its model request is cancelled, no further call starts, and a new turn
|
|
328
|
+
* may begin at once. A call already running finishes, and its result is kept. A turn waiting
|
|
329
|
+
* for its place never starts.
|
|
330
|
+
*/
|
|
331
|
+
async cancel() {
|
|
332
|
+
// Counted with no turn too: a message asked but not yet taken starts none after this.
|
|
333
|
+
cancels++;
|
|
334
|
+
const current = turn;
|
|
335
|
+
if (!current)
|
|
336
|
+
return;
|
|
337
|
+
current.cancelled = true;
|
|
338
|
+
turn = undefined;
|
|
339
|
+
// A turn that will write nothing more gives its place back now: one still waiting for it,
|
|
340
|
+
// one the model is answering, or one waiting for its end. Any other keeps its place until it
|
|
341
|
+
// has written what it will, and what comes next waits for that: the reply it is storing,
|
|
342
|
+
// or the result of its running call.
|
|
343
|
+
const quiet = current.requestId !== undefined || current.ending === true;
|
|
344
|
+
if (current.waiting || quiet)
|
|
345
|
+
current.place?.leave();
|
|
346
|
+
if (!quiet)
|
|
347
|
+
draining = current.ended ?? draining;
|
|
348
|
+
say({ state: "idle" });
|
|
349
|
+
if (current.requestId)
|
|
350
|
+
await model.cancel({ requestId: current.requestId }).catch(() => { });
|
|
351
|
+
},
|
|
352
|
+
/** Settles when the latest turn has written all it will. */
|
|
353
|
+
settled: async () => {
|
|
354
|
+
await lastEnded;
|
|
355
|
+
},
|
|
356
|
+
/** Settles when a stopped turn's running call has ended: at once, if none is running. */
|
|
357
|
+
drained: async () => {
|
|
358
|
+
await draining;
|
|
359
|
+
},
|
|
360
|
+
/** Streamed text of the model request running now, for the reply it becomes. */
|
|
361
|
+
delta(requestId, text) {
|
|
362
|
+
if (turn?.requestId === requestId && turn.replyId)
|
|
363
|
+
out.delta(turn.thread ?? record.id, turn.replyId, text);
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type Log, type Model, type Shown, type Status, type Store, type Tool } from "./types.js";
|
|
2
|
+
/** The turns that run at once, not counting the one more for work already begun. */
|
|
3
|
+
export declare const PLACES = 4;
|
|
4
|
+
/** The turns that may wait for a place at once. */
|
|
5
|
+
export declare const WAITING = 16;
|
|
6
|
+
/** Where a looper says what it does. */
|
|
7
|
+
export interface LooperOut {
|
|
8
|
+
/** A message, with the thread it is shown in. */
|
|
9
|
+
message(thread: string, message: Shown): void;
|
|
10
|
+
/** Text of the reply `id` as the model writes it. */
|
|
11
|
+
delta(thread: string, id: string, text: string): void;
|
|
12
|
+
/** Whether anything is at work, the main loop included: said when it changes. */
|
|
13
|
+
working(on: boolean): void;
|
|
14
|
+
/** A message in `thread` got no answer, and why. */
|
|
15
|
+
failed(thread: string, problem: {
|
|
16
|
+
code: string;
|
|
17
|
+
message: string;
|
|
18
|
+
}): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Make a looper. Nothing is read or run until a thread is named: a message sent in it, or its
|
|
22
|
+
* messages asked for.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createLooper(options: {
|
|
25
|
+
store: Store;
|
|
26
|
+
model: Model;
|
|
27
|
+
/**
|
|
28
|
+
* Read for each model request, since any of it may change between two: what the model is told,
|
|
29
|
+
* what it can call, and which model to ask, none meaning the model's own default.
|
|
30
|
+
*/
|
|
31
|
+
request(): Promise<{
|
|
32
|
+
instructions: string;
|
|
33
|
+
tools: Tool[];
|
|
34
|
+
model?: string | undefined;
|
|
35
|
+
}>;
|
|
36
|
+
out: LooperOut;
|
|
37
|
+
/** Whether it starts stopped: no turn starts until `start`. */
|
|
38
|
+
stopped?: boolean;
|
|
39
|
+
log: Log;
|
|
40
|
+
}): {
|
|
41
|
+
/**
|
|
42
|
+
* Say a message in a thread. It is recorded and said at once. In a thread a piece of work
|
|
43
|
+
* follows it is that work's: with no turn of it running it starts one, which waits for a place
|
|
44
|
+
* if none is free, and a turn running or waiting takes it at its next model request. In a
|
|
45
|
+
* thread nothing follows, the main loop says whose it is first. Refused `busy` while `WAITING`
|
|
46
|
+
* turns already wait and it would start one more, or while the looper is stopped; and
|
|
47
|
+
* `unavailable` once it has closed.
|
|
48
|
+
*/
|
|
49
|
+
send(thread: string, text: string): Promise<{
|
|
50
|
+
messageId: string;
|
|
51
|
+
}>;
|
|
52
|
+
/** Stop a piece of work's turn now. A call already running finishes, and its result is kept. */
|
|
53
|
+
cancel: (work: string) => Promise<void | undefined>;
|
|
54
|
+
/** What a piece of work's loop is doing. */
|
|
55
|
+
state: (work: string) => Status;
|
|
56
|
+
/** A thread's messages before `before`, oldest first, as many as fit `limit`. */
|
|
57
|
+
page: (thread: string, before: string | undefined, limit: number) => Promise<{
|
|
58
|
+
messages: Shown[];
|
|
59
|
+
more: boolean;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Stop: every turn is cut, running or waiting, the main loop is cut too, and nothing starts
|
|
63
|
+
* until `start`. A call already running finishes first, so this answers when nothing more will
|
|
64
|
+
* be written. The records stay as they are, to be read. What was cut off earlier no longer
|
|
65
|
+
* goes on, and messages waiting for a piece of work wait for their thread's next.
|
|
66
|
+
*/
|
|
67
|
+
stop(): Promise<void>;
|
|
68
|
+
/** Take messages again. The turns `stop` cut stay cut. */
|
|
69
|
+
start(): void;
|
|
70
|
+
resume: () => Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Close for good: every turn is cut, and none starts after. Each stays marked, as does each
|
|
73
|
+
* thread where messages wait, for `resume` in whoever opens the store next.
|
|
74
|
+
*/
|
|
75
|
+
close(): Promise<void>;
|
|
76
|
+
/** Settles when every piece of work's latest turn has written all it will. */
|
|
77
|
+
settled(): Promise<void>;
|
|
78
|
+
/** Text the model writes while it answers `requestId`, for the reply it becomes. */
|
|
79
|
+
delta(requestId: string, text: string): void;
|
|
80
|
+
};
|
|
81
|
+
export type Looper = ReturnType<typeof createLooper>;
|