@managoat/fountain-sdk 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +653 -0
  2. package/LICENSE +202 -0
  3. package/README.md +445 -0
  4. package/dist/client.d.ts +190 -0
  5. package/dist/client.js +225 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/config.d.ts +49 -0
  8. package/dist/config.js +87 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/conversation.d.ts +100 -0
  11. package/dist/conversation.js +189 -0
  12. package/dist/conversation.js.map +1 -0
  13. package/dist/errors.d.ts +102 -0
  14. package/dist/errors.js +197 -0
  15. package/dist/errors.js.map +1 -0
  16. package/dist/generated/openapi.d.ts +16654 -0
  17. package/dist/generated/openapi.js +6 -0
  18. package/dist/generated/openapi.js.map +1 -0
  19. package/dist/http.d.ts +37 -0
  20. package/dist/http.js +129 -0
  21. package/dist/http.js.map +1 -0
  22. package/dist/index.d.ts +14 -0
  23. package/dist/index.js +13 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/node.d.ts +2 -0
  26. package/dist/node.js +21 -0
  27. package/dist/node.js.map +1 -0
  28. package/dist/queue.d.ts +25 -0
  29. package/dist/queue.js +64 -0
  30. package/dist/queue.js.map +1 -0
  31. package/dist/resolve.d.ts +29 -0
  32. package/dist/resolve.js +89 -0
  33. package/dist/resolve.js.map +1 -0
  34. package/dist/resources.d.ts +126 -0
  35. package/dist/resources.js +206 -0
  36. package/dist/resources.js.map +1 -0
  37. package/dist/run.d.ts +81 -0
  38. package/dist/run.js +247 -0
  39. package/dist/run.js.map +1 -0
  40. package/dist/schemas.d.ts +90 -0
  41. package/dist/schemas.js +2 -0
  42. package/dist/schemas.js.map +1 -0
  43. package/dist/sse.d.ts +58 -0
  44. package/dist/sse.js +219 -0
  45. package/dist/sse.js.map +1 -0
  46. package/dist/team.d.ts +90 -0
  47. package/dist/team.js +183 -0
  48. package/dist/team.js.map +1 -0
  49. package/dist/turn.d.ts +46 -0
  50. package/dist/turn.js +205 -0
  51. package/dist/turn.js.map +1 -0
  52. package/dist/types.d.ts +144 -0
  53. package/dist/types.js +2 -0
  54. package/dist/types.js.map +1 -0
  55. package/package.json +61 -0
package/dist/sse.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ import type { HttpClient } from "./http.ts";
2
+ import type { LogEvent, Stream } from "./types.ts";
3
+ /**
4
+ * What a caller asks a stream for. `streams` takes the friendly array form
5
+ * here and is joined on the way to the query string.
6
+ */
7
+ export type StreamRequest = Omit<StreamOptions, "streams"> & {
8
+ streams?: Stream[] | string;
9
+ };
10
+ /** One `id:`/`event:`/`data:` message off the wire. */
11
+ export interface SseMessage {
12
+ id: string | null;
13
+ event: string;
14
+ data: string;
15
+ }
16
+ /** Split a byte stream into SSE messages. Comments (`: heartbeat`) are dropped. */
17
+ export declare function parseSse(body: ReadableStream<Uint8Array>): AsyncGenerator<SseMessage>;
18
+ export interface StreamOptions {
19
+ /** Resume after this event id. `0` replays the conversation from the start. */
20
+ after?: number;
21
+ signal?: AbortSignal;
22
+ /** How long one connection may sit idle before it is retried. */
23
+ idleTimeoutMs?: number;
24
+ /** Give up reconnecting after this many consecutive failures. */
25
+ maxRetries?: number;
26
+ /** Wait between reconnects, in ms. Exposed so tests do not sleep. */
27
+ retryDelayMs?: number;
28
+ /** Which streams to carry. Omitted means everything. */
29
+ streams?: string;
30
+ /** `false` drains the buffered events and closes instead of tailing live. */
31
+ wait?: boolean;
32
+ /**
33
+ * Ask for server-parsed `blocks` on each event.
34
+ *
35
+ * Every SSE endpoint takes it, so the wrappers here send it by default and
36
+ * a caller only sets this to stream the runtime's raw dialect instead.
37
+ */
38
+ blocks?: boolean;
39
+ }
40
+ /**
41
+ * The conversation's log feed as an async iterable of `LogEvent`, reconnecting
42
+ * on its own.
43
+ *
44
+ * The reconnect is not decoration. A Fountain deploy, a sandbox wake or an
45
+ * ordinary proxy timeout will end an SSE connection mid-turn; without a cursor
46
+ * the next connection either replays what the caller already saw or misses
47
+ * what arrived in the gap. `Last-Event-ID` is Fountain's answer — the server
48
+ * replays buffered events after that id, then tails live — so this loop tracks
49
+ * the last id it yielded and resumes there. A caller never sees the seam.
50
+ */
51
+ export declare function streamEvents(http: HttpClient, conversationId: string, opts?: StreamOptions): AsyncGenerator<LogEvent>;
52
+ /**
53
+ * The same reader, pointed at any of Fountain's SSE endpoints: one
54
+ * conversation, the whole team (`/api/team/stream`), or every conversation the
55
+ * caller owns (`/api/events/stream`). They share a format, a cursor header and
56
+ * a heartbeat, so they share this loop.
57
+ */
58
+ export declare function streamPath(http: HttpClient, path: string, opts?: StreamOptions): AsyncGenerator<LogEvent>;
package/dist/sse.js ADDED
@@ -0,0 +1,219 @@
1
+ import { FountainError, errorForStatus } from "./errors.js";
2
+ /** Split a byte stream into SSE messages. Comments (`: heartbeat`) are dropped. */
3
+ export async function* parseSse(body) {
4
+ const decoder = new TextDecoder();
5
+ const reader = body.getReader();
6
+ let buffer = "";
7
+ try {
8
+ while (true) {
9
+ const { done, value } = await reader.read();
10
+ if (done)
11
+ break;
12
+ buffer += decoder.decode(value, { stream: true });
13
+ let split;
14
+ // A message ends at a blank line; \r\n is legal and Fountain sends \n.
15
+ while ((split = indexOfBoundary(buffer)) !== -1) {
16
+ const { chunk, length } = boundaryAt(buffer, split);
17
+ buffer = buffer.slice(split + length);
18
+ const message = parseChunk(chunk);
19
+ if (message)
20
+ yield message;
21
+ }
22
+ }
23
+ }
24
+ finally {
25
+ // A consumer that breaks out mid-turn must not leave the socket open.
26
+ try {
27
+ await reader.cancel();
28
+ }
29
+ catch {
30
+ // Already closed, or the peer went away first.
31
+ }
32
+ reader.releaseLock();
33
+ }
34
+ const tail = parseChunk(buffer);
35
+ if (tail)
36
+ yield tail;
37
+ }
38
+ function indexOfBoundary(buffer) {
39
+ const lf = buffer.indexOf("\n\n");
40
+ const crlf = buffer.indexOf("\r\n\r\n");
41
+ if (lf === -1)
42
+ return crlf;
43
+ if (crlf === -1)
44
+ return lf;
45
+ return Math.min(lf, crlf);
46
+ }
47
+ function boundaryAt(buffer, index) {
48
+ const isCrlf = buffer.startsWith("\r\n\r\n", index);
49
+ return { chunk: buffer.slice(0, index), length: isCrlf ? 4 : 2 };
50
+ }
51
+ function parseChunk(chunk) {
52
+ let id = null;
53
+ let event = "message";
54
+ const data = [];
55
+ for (const rawLine of chunk.split("\n")) {
56
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
57
+ if (!line || line.startsWith(":"))
58
+ continue; // heartbeat comment
59
+ const colon = line.indexOf(":");
60
+ const field = colon === -1 ? line : line.slice(0, colon);
61
+ let value = colon === -1 ? "" : line.slice(colon + 1);
62
+ if (value.startsWith(" "))
63
+ value = value.slice(1);
64
+ if (field === "id")
65
+ id = value;
66
+ else if (field === "event")
67
+ event = value;
68
+ else if (field === "data")
69
+ data.push(value);
70
+ }
71
+ if (!data.length && id === null)
72
+ return null;
73
+ return { id, event, data: data.join("\n") };
74
+ }
75
+ /**
76
+ * The conversation's log feed as an async iterable of `LogEvent`, reconnecting
77
+ * on its own.
78
+ *
79
+ * The reconnect is not decoration. A Fountain deploy, a sandbox wake or an
80
+ * ordinary proxy timeout will end an SSE connection mid-turn; without a cursor
81
+ * the next connection either replays what the caller already saw or misses
82
+ * what arrived in the gap. `Last-Event-ID` is Fountain's answer — the server
83
+ * replays buffered events after that id, then tails live — so this loop tracks
84
+ * the last id it yielded and resumes there. A caller never sees the seam.
85
+ */
86
+ export function streamEvents(http, conversationId, opts = {}) {
87
+ // A conversation stream supports blocks, and a turn cannot be followed
88
+ // without them, so it is the default here.
89
+ return streamPath(http, `/api/conversations/${conversationId}/stream`, {
90
+ blocks: true,
91
+ ...opts,
92
+ });
93
+ }
94
+ /**
95
+ * The same reader, pointed at any of Fountain's SSE endpoints: one
96
+ * conversation, the whole team (`/api/team/stream`), or every conversation the
97
+ * caller owns (`/api/events/stream`). They share a format, a cursor header and
98
+ * a heartbeat, so they share this loop.
99
+ */
100
+ export async function* streamPath(http, path, opts = {}) {
101
+ let lastId = opts.after ?? 0;
102
+ let attempt = 0;
103
+ const maxRetries = opts.maxRetries ?? 5;
104
+ const retryDelayMs = opts.retryDelayMs ?? 500;
105
+ while (true) {
106
+ let response;
107
+ try {
108
+ response = await http.raw("GET", path, {
109
+ query: {
110
+ blocks: opts.blocks ? "true" : undefined,
111
+ streams: opts.streams,
112
+ wait: opts.wait === false ? "false" : undefined,
113
+ },
114
+ headers: lastId > 0 ? { "Last-Event-ID": String(lastId) } : {},
115
+ accept: "text/event-stream",
116
+ signal: opts.signal,
117
+ // The stream is meant to be held open; a request timeout would kill it.
118
+ timeoutMs: 0,
119
+ });
120
+ }
121
+ catch (error) {
122
+ if (opts.signal?.aborted)
123
+ return;
124
+ if (++attempt > maxRetries)
125
+ throw error;
126
+ await delay(retryDelayMs * attempt, opts.signal);
127
+ continue;
128
+ }
129
+ if (!response.ok) {
130
+ const text = await response.text().catch(() => "");
131
+ // 4xx will not fix itself: a bad key or a conversation this account
132
+ // cannot see returns the same answer however many times we ask.
133
+ if (response.status < 500 || ++attempt > maxRetries) {
134
+ throw errorForStatus(response.status, text ? safeJson(text) : null, "GET", path, response.headers);
135
+ }
136
+ await delay(retryDelayMs * attempt, opts.signal);
137
+ continue;
138
+ }
139
+ if (!response.body) {
140
+ throw new FountainError("SSE response had no body");
141
+ }
142
+ attempt = 0;
143
+ try {
144
+ for await (const message of parseSse(response.body)) {
145
+ if (opts.signal?.aborted)
146
+ return;
147
+ const id = Number(message.id);
148
+ if (Number.isFinite(id) && id > 0)
149
+ lastId = id;
150
+ const event = decodeEvent(message);
151
+ if (event)
152
+ yield event;
153
+ }
154
+ }
155
+ catch (error) {
156
+ if (opts.signal?.aborted)
157
+ return;
158
+ if (++attempt > maxRetries)
159
+ throw error;
160
+ await delay(retryDelayMs * attempt, opts.signal);
161
+ continue;
162
+ }
163
+ // A drain closes on purpose; there is nothing to reconnect to.
164
+ if (opts.wait === false)
165
+ return;
166
+ // The connection ended. Fountain closes an idle stream after 60s, which is
167
+ // normal for a conversation between turns — reconnect from the cursor.
168
+ if (opts.signal?.aborted)
169
+ return;
170
+ if (++attempt > maxRetries)
171
+ return;
172
+ await delay(retryDelayMs, opts.signal);
173
+ }
174
+ }
175
+ function decodeEvent(message) {
176
+ if (!message.data)
177
+ return null;
178
+ let payload;
179
+ try {
180
+ payload = JSON.parse(message.data);
181
+ }
182
+ catch {
183
+ return null;
184
+ }
185
+ if (!payload || typeof payload !== "object")
186
+ return null;
187
+ const event = payload;
188
+ const id = Number(message.id);
189
+ if (Number.isFinite(id) && id > 0)
190
+ event.id = id;
191
+ if (!event.kind && (message.event === "output" || message.event === "stage")) {
192
+ event.kind = message.event;
193
+ }
194
+ return event;
195
+ }
196
+ function safeJson(text) {
197
+ try {
198
+ return JSON.parse(text);
199
+ }
200
+ catch {
201
+ return text;
202
+ }
203
+ }
204
+ function delay(ms, signal) {
205
+ return new Promise((resolve) => {
206
+ if (signal?.aborted)
207
+ return resolve();
208
+ const timer = setTimeout(done, ms);
209
+ // Never hold the process open on a retry sleep.
210
+ timer.unref?.();
211
+ function done() {
212
+ signal?.removeEventListener("abort", done);
213
+ clearTimeout(timer);
214
+ resolve();
215
+ }
216
+ signal?.addEventListener("abort", done, { once: true });
217
+ });
218
+ }
219
+ //# sourceMappingURL=sse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.js","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"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"}
package/dist/team.d.ts ADDED
@@ -0,0 +1,90 @@
1
+ import type { HttpClient } from "./http.ts";
2
+ import type { Resolver } from "./resolve.ts";
3
+ import { Conversation } from "./conversation.ts";
4
+ import { Run, type RunOptions } from "./run.ts";
5
+ import { type StreamRequest } from "./sse.ts";
6
+ import type { ConversationRecord, ImageInput, Schedule, ScheduleInput, SchedulePatch, Stream, TeamAddInput, TeamCommsStatus, TeamEvent, Teammate } from "./types.ts";
7
+ export interface MessageOptions extends RunOptions {
8
+ images?: ImageInput[];
9
+ }
10
+ /**
11
+ * The team: agents you have hired, each with a standing conversation.
12
+ *
13
+ * This is the surface the applications built on Fountain actually use — ten of
14
+ * the eleven talk to `/api/team` and only some of them ever touch
15
+ * `/api/conversations`. The reason is that a teammate is a *durable* thing:
16
+ * one agent, one long-running sandbox, one thread you keep messaging, rather
17
+ * than a conversation you open and close. `message()` returns the same `Run`
18
+ * handle `fountain.run()` does, so a reply can be awaited or streamed.
19
+ */
20
+ export declare class Team {
21
+ private readonly http;
22
+ private readonly resolver;
23
+ /** Cron routines that run a teammate with a prompt. */
24
+ readonly schedules: TeamSchedules;
25
+ constructor(http: HttpClient, resolver: Resolver);
26
+ /** Everyone on the team, with their unread counts and last activity. */
27
+ list(): Promise<Teammate[]>;
28
+ /** One teammate, by agent name or id. */
29
+ get(agent: string): Promise<Teammate>;
30
+ /** Put an agent on the team. Idempotent for an agent already on it. */
31
+ add(agent: string, options?: Omit<TeamAddInput, "agent_id">): Promise<Teammate>;
32
+ /** Take a teammate off the team. The agent itself is untouched. */
33
+ remove(agent: string): Promise<void>;
34
+ /** Change the name the team page shows. `null` restores the agent's own name. */
35
+ rename(agent: string, name: string | null): Promise<Teammate>;
36
+ /**
37
+ * Say something to a teammate, in its standing conversation.
38
+ *
39
+ * Returns a `Run`: await it for the reply, iterate it for events, or ignore
40
+ * it and let the team stream carry the answer to a UI.
41
+ */
42
+ message(agent: string, prompt: string, options?: MessageOptions): Run;
43
+ /** The teammate's standing conversation, as a handle. */
44
+ conversation(agent: string): Promise<Conversation>;
45
+ /** Every conversation this teammate has had on the team. */
46
+ history(agent: string): Promise<ConversationRecord[]>;
47
+ /** Start a fresh thread on a new computer; the current one is retired. */
48
+ freshConversation(agent: string): Promise<ConversationRecord>;
49
+ /** Can teammates here be given an email address and a phone number? */
50
+ commsStatus(): Promise<TeamCommsStatus>;
51
+ /**
52
+ * The whole team's events on one connection.
53
+ *
54
+ * One stream for every teammate is the shape a team UI wants, and building
55
+ * it out of N per-conversation streams is what the apps did before this
56
+ * endpoint existed. Reconnects from the last event id on its own.
57
+ *
58
+ * Each payload is a conversation event plus `conversation_id` and
59
+ * `agent_id`, so a roster row can be found without a socket per teammate.
60
+ *
61
+ * Events carry server-parsed `blocks`, as on every other feed, so a client
62
+ * renders a transcript from this stream alone rather than re-parsing a
63
+ * runtime's dialect or opening a second connection per thread. The stream is
64
+ * multi-conversation, so the server picks the runtime per event from the
65
+ * conversation that produced it (#881).
66
+ */
67
+ stream(options?: StreamRequest): AsyncIterable<TeamEvent>;
68
+ private agentId;
69
+ }
70
+ /** Cron routines attached to a teammate. */
71
+ export declare class TeamSchedules {
72
+ private readonly http;
73
+ private readonly resolver;
74
+ constructor(http: HttpClient, resolver: Resolver);
75
+ /** Every routine on the team, or just one teammate's. */
76
+ list(agent?: string): Promise<Schedule[]>;
77
+ get(agent: string, id: string): Promise<Schedule>;
78
+ create(agent: string, input: ScheduleInput): Promise<Schedule>;
79
+ update(agent: string, id: string, patch: SchedulePatch): Promise<Schedule>;
80
+ delete(agent: string, id: string): Promise<void>;
81
+ /** Run it now, without waiting for its cron. */
82
+ run(agent: string, id: string): Promise<unknown>;
83
+ private agentId;
84
+ }
85
+ /** `streams: ["acp", "stage"]` is friendlier than a comma-joined string. */
86
+ export declare function normalizeStreams<T extends {
87
+ streams?: Stream[] | string;
88
+ }>(options: T): Omit<T, "streams"> & {
89
+ streams?: string;
90
+ };
package/dist/team.js ADDED
@@ -0,0 +1,183 @@
1
+ import { Conversation } from "./conversation.js";
2
+ import { Run } from "./run.js";
3
+ import { streamPath } from "./sse.js";
4
+ /**
5
+ * The team: agents you have hired, each with a standing conversation.
6
+ *
7
+ * This is the surface the applications built on Fountain actually use — ten of
8
+ * the eleven talk to `/api/team` and only some of them ever touch
9
+ * `/api/conversations`. The reason is that a teammate is a *durable* thing:
10
+ * one agent, one long-running sandbox, one thread you keep messaging, rather
11
+ * than a conversation you open and close. `message()` returns the same `Run`
12
+ * handle `fountain.run()` does, so a reply can be awaited or streamed.
13
+ */
14
+ export class Team {
15
+ http;
16
+ resolver;
17
+ /** Cron routines that run a teammate with a prompt. */
18
+ schedules;
19
+ constructor(http, resolver) {
20
+ this.http = http;
21
+ this.resolver = resolver;
22
+ this.schedules = new TeamSchedules(http, resolver);
23
+ }
24
+ /** Everyone on the team, with their unread counts and last activity. */
25
+ async list() {
26
+ return this.http.list("/api/team");
27
+ }
28
+ /** One teammate, by agent name or id. */
29
+ async get(agent) {
30
+ return this.http.data("GET", `/api/team/${await this.agentId(agent)}`);
31
+ }
32
+ /** Put an agent on the team. Idempotent for an agent already on it. */
33
+ async add(agent, options = {}) {
34
+ const body = { ...options, agent_id: await this.agentId(agent) };
35
+ return this.http.data("POST", "/api/team", { body });
36
+ }
37
+ /** Take a teammate off the team. The agent itself is untouched. */
38
+ async remove(agent) {
39
+ await this.http.request("DELETE", `/api/team/${await this.agentId(agent)}`);
40
+ }
41
+ /** Change the name the team page shows. `null` restores the agent's own name. */
42
+ async rename(agent, name) {
43
+ return this.http.data("PATCH", `/api/team/${await this.agentId(agent)}`, {
44
+ body: { name },
45
+ });
46
+ }
47
+ /**
48
+ * Say something to a teammate, in its standing conversation.
49
+ *
50
+ * Returns a `Run`: await it for the reply, iterate it for events, or ignore
51
+ * it and let the team stream carry the answer to a UI.
52
+ */
53
+ message(agent, prompt, options = {}) {
54
+ const body = { prompt };
55
+ if (options.images?.length)
56
+ body.images = options.images;
57
+ return new Run(this.http, {
58
+ start: async () => {
59
+ const agentId = await this.agentId(agent);
60
+ // Where the teammate's thread stands *before* the message, so the
61
+ // follower knows which turn is the reply and where to read from.
62
+ const before = await this.http
63
+ .data("GET", `/api/team/${agentId}`)
64
+ .catch(() => null);
65
+ const existing = before?.conversation?.id ?? null;
66
+ let after = 0;
67
+ let turnNumber = 1;
68
+ if (existing) {
69
+ const conversation = new Conversation(this.http, existing);
70
+ after = await conversation.cursor();
71
+ turnNumber = (await conversation.lastTurnNumber()) + 1;
72
+ }
73
+ const sent = await this.http.request("POST", `/api/team/${agentId}/messages`, { body });
74
+ const conversationId = sent?.conversation_id ?? existing;
75
+ if (!conversationId) {
76
+ throw new Error(`POST /api/team/${agentId}/messages returned no conversation id`);
77
+ }
78
+ // A fresh thread (the teammate had none, or the old one was retired)
79
+ // starts its own numbering, and there is no history to skip.
80
+ if (conversationId !== existing) {
81
+ after = 0;
82
+ turnNumber = 1;
83
+ }
84
+ const conversation = await this.http.data("GET", `/api/conversations/${conversationId}`);
85
+ return { conversation, turnNumber, after };
86
+ },
87
+ }, options);
88
+ }
89
+ /** The teammate's standing conversation, as a handle. */
90
+ async conversation(agent) {
91
+ const teammate = await this.get(agent);
92
+ const id = teammate.conversation?.id;
93
+ if (!id)
94
+ throw new Error(`${agent} has no conversation yet — send it a message first`);
95
+ return new Conversation(this.http, id);
96
+ }
97
+ /** Every conversation this teammate has had on the team. */
98
+ async history(agent) {
99
+ return this.http.list(`/api/team/${await this.agentId(agent)}/conversations`);
100
+ }
101
+ /** Start a fresh thread on a new computer; the current one is retired. */
102
+ async freshConversation(agent) {
103
+ return this.http.data("POST", `/api/team/${await this.agentId(agent)}/conversations`);
104
+ }
105
+ /** Can teammates here be given an email address and a phone number? */
106
+ async commsStatus() {
107
+ return this.http.data("GET", "/api/team/comms");
108
+ }
109
+ /**
110
+ * The whole team's events on one connection.
111
+ *
112
+ * One stream for every teammate is the shape a team UI wants, and building
113
+ * it out of N per-conversation streams is what the apps did before this
114
+ * endpoint existed. Reconnects from the last event id on its own.
115
+ *
116
+ * Each payload is a conversation event plus `conversation_id` and
117
+ * `agent_id`, so a roster row can be found without a socket per teammate.
118
+ *
119
+ * Events carry server-parsed `blocks`, as on every other feed, so a client
120
+ * renders a transcript from this stream alone rather than re-parsing a
121
+ * runtime's dialect or opening a second connection per thread. The stream is
122
+ * multi-conversation, so the server picks the runtime per event from the
123
+ * conversation that produced it (#881).
124
+ */
125
+ stream(options = {}) {
126
+ return streamPath(this.http, "/api/team/stream", {
127
+ blocks: true,
128
+ ...normalizeStreams(options),
129
+ });
130
+ }
131
+ async agentId(agent) {
132
+ const { id } = await this.resolver.resolve("/api/agents", "agent", agent);
133
+ return id;
134
+ }
135
+ }
136
+ /** Cron routines attached to a teammate. */
137
+ export class TeamSchedules {
138
+ http;
139
+ resolver;
140
+ constructor(http, resolver) {
141
+ this.http = http;
142
+ this.resolver = resolver;
143
+ }
144
+ /** Every routine on the team, or just one teammate's. */
145
+ async list(agent) {
146
+ if (!agent)
147
+ return this.http.list("/api/team/schedules");
148
+ return this.http.list(`/api/team/${await this.agentId(agent)}/schedules`);
149
+ }
150
+ async get(agent, id) {
151
+ return this.http.data("GET", `/api/team/${await this.agentId(agent)}/schedules/${id}`);
152
+ }
153
+ async create(agent, input) {
154
+ return this.http.data("POST", `/api/team/${await this.agentId(agent)}/schedules`, {
155
+ body: input,
156
+ });
157
+ }
158
+ async update(agent, id, patch) {
159
+ return this.http.data("PATCH", `/api/team/${await this.agentId(agent)}/schedules/${id}`, { body: patch });
160
+ }
161
+ async delete(agent, id) {
162
+ await this.http.request("DELETE", `/api/team/${await this.agentId(agent)}/schedules/${id}`);
163
+ }
164
+ /** Run it now, without waiting for its cron. */
165
+ async run(agent, id) {
166
+ return this.http.request("POST", `/api/team/${await this.agentId(agent)}/schedules/${id}/run`);
167
+ }
168
+ async agentId(agent) {
169
+ const { id } = await this.resolver.resolve("/api/agents", "agent", agent);
170
+ return id;
171
+ }
172
+ }
173
+ /** `streams: ["acp", "stage"]` is friendlier than a comma-joined string. */
174
+ export function normalizeStreams(options) {
175
+ const { streams, ...rest } = options;
176
+ return {
177
+ ...rest,
178
+ ...(streams === undefined
179
+ ? {}
180
+ : { streams: Array.isArray(streams) ? streams.join(",") : streams }),
181
+ };
182
+ }
183
+ //# sourceMappingURL=team.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.js","sourceRoot":"","sources":["../src/team.ts"],"names":[],"mappings":"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"}
package/dist/turn.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ import type { LogEvent, RunEvent, TurnState } from "./types.ts";
2
+ /**
3
+ * Folds the log feed into one turn's answer.
4
+ *
5
+ * The feed carries every turn of the conversation and every stream of each
6
+ * turn, so "what did the agent just say" is a filtering problem, not a
7
+ * concatenation one. This class is the filter:
8
+ *
9
+ * - a `stage`/`turn` event opens and closes the turn we are following, and
10
+ * is the only thing that says how it ended;
11
+ * - `output` events carry server-parsed `blocks`; only `text` is the answer,
12
+ * `tool_use` is noise worth naming, `thinking` is neither;
13
+ * - text that follows a tool call is a new message, so it gets a paragraph
14
+ * break — the rule that stops a transcript reading as one run-on sentence.
15
+ *
16
+ * The joining rules are ported from the Hermes plugin, which learned them
17
+ * against real runtimes: ACP streams one message as chunks that join with
18
+ * nothing, while a legacy stdout row is a whole message and joins as a
19
+ * paragraph.
20
+ */
21
+ export declare class TurnFollower {
22
+ readonly turnNumber: number;
23
+ turnId: string | null;
24
+ started: boolean;
25
+ state: TurnState | null;
26
+ exitCode: number | null;
27
+ reason: string | null;
28
+ private readonly chunks;
29
+ private readonly tools;
30
+ private breakBeforeText;
31
+ constructor(turnNumber: number, turnId?: string | null);
32
+ get text(): string;
33
+ get toolsUsed(): string[];
34
+ get finished(): boolean;
35
+ /** Fold one event in, and report what a streaming caller should be told. */
36
+ apply(event: LogEvent): RunEvent[];
37
+ private applyStage;
38
+ private matchesTurn;
39
+ private applyOutput;
40
+ private applyBlock;
41
+ /**
42
+ * ACP chunks are pieces of one message and join with nothing; anything after
43
+ * a tool call is a new message. A legacy row is a whole message either way.
44
+ */
45
+ private paragraphBreak;
46
+ }