@ocis/myagent-cli 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +357 -0
- package/dist/agent/context.d.ts +33 -0
- package/dist/agent/context.js +169 -0
- package/dist/agent/modes.d.ts +21 -0
- package/dist/agent/modes.js +84 -0
- package/dist/agent/prompt-builder.d.ts +9 -0
- package/dist/agent/prompt-builder.js +31 -0
- package/dist/agent/sessions.d.ts +38 -0
- package/dist/agent/sessions.js +130 -0
- package/dist/agent/todo.d.ts +18 -0
- package/dist/agent/todo.js +61 -0
- package/dist/agent/turn.d.ts +309 -0
- package/dist/agent/turn.js +1253 -0
- package/dist/approval/policy.d.ts +81 -0
- package/dist/approval/policy.js +157 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.js +156 -0
- package/dist/git/status.d.ts +89 -0
- package/dist/git/status.js +226 -0
- package/dist/headless.d.ts +72 -0
- package/dist/headless.js +330 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +511 -0
- package/dist/protocol/client.d.ts +123 -0
- package/dist/protocol/client.js +250 -0
- package/dist/protocol/sse-frames.d.ts +6 -0
- package/dist/protocol/sse-frames.js +75 -0
- package/dist/protocol/types.d.ts +200 -0
- package/dist/protocol/types.js +8 -0
- package/dist/runtime.d.ts +38 -0
- package/dist/runtime.js +166 -0
- package/dist/sanitize.d.ts +1 -0
- package/dist/sanitize.js +21 -0
- package/dist/skills/discovery.d.ts +24 -0
- package/dist/skills/discovery.js +109 -0
- package/dist/tools/binary.d.ts +2 -0
- package/dist/tools/binary.js +22 -0
- package/dist/tools/diff.d.ts +1 -0
- package/dist/tools/diff.js +49 -0
- package/dist/tools/find.d.ts +2 -0
- package/dist/tools/find.js +61 -0
- package/dist/tools/fs.d.ts +2 -0
- package/dist/tools/fs.js +276 -0
- package/dist/tools/glob.d.ts +6 -0
- package/dist/tools/glob.js +131 -0
- package/dist/tools/grep.d.ts +3 -0
- package/dist/tools/grep.js +228 -0
- package/dist/tools/paths.d.ts +27 -0
- package/dist/tools/paths.js +124 -0
- package/dist/tools/registry.d.ts +13 -0
- package/dist/tools/registry.js +38 -0
- package/dist/tools/shell.d.ts +2 -0
- package/dist/tools/shell.js +136 -0
- package/dist/tools/skills.d.ts +2 -0
- package/dist/tools/skills.js +36 -0
- package/dist/tools/todo.d.ts +2 -0
- package/dist/tools/todo.js +43 -0
- package/dist/tools/transfer.d.ts +2 -0
- package/dist/tools/transfer.js +145 -0
- package/dist/tools/truncate.d.ts +12 -0
- package/dist/tools/truncate.js +46 -0
- package/dist/tools/types.d.ts +85 -0
- package/dist/tools/types.js +63 -0
- package/dist/ui/app.d.ts +39 -0
- package/dist/ui/app.js +1061 -0
- package/dist/ui/colors.d.ts +100 -0
- package/dist/ui/colors.js +169 -0
- package/dist/ui/components.d.ts +267 -0
- package/dist/ui/components.js +811 -0
- package/dist/ui/diff.d.ts +37 -0
- package/dist/ui/diff.js +143 -0
- package/dist/ui/format.d.ts +28 -0
- package/dist/ui/format.js +76 -0
- package/dist/ui/help.d.ts +6 -0
- package/dist/ui/help.js +45 -0
- package/dist/ui/highlight.d.ts +20 -0
- package/dist/ui/highlight.js +210 -0
- package/dist/ui/logo.d.ts +24 -0
- package/dist/ui/logo.js +106 -0
- package/dist/ui/model-list.d.ts +10 -0
- package/dist/ui/model-list.js +33 -0
- package/dist/ui/quit-confirm.d.ts +8 -0
- package/dist/ui/quit-confirm.js +40 -0
- package/dist/ui/select-popup.d.ts +30 -0
- package/dist/ui/select-popup.js +54 -0
- package/dist/ui/theme.d.ts +4 -0
- package/dist/ui/theme.js +41 -0
- package/dist/ui/tool-view.d.ts +20 -0
- package/dist/ui/tool-view.js +326 -0
- package/package.json +44 -0
- package/skills/git-commit/SKILL.md +27 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// HTTP client for the MyAgent integration API.
|
|
3
|
+
//
|
|
4
|
+
// One instance per configured server; stateless apart from configuration.
|
|
5
|
+
// Streaming responses are read with readSseFrames (the run stream and the
|
|
6
|
+
// single-frame input ack); non-streaming endpoints return parsed JSON.
|
|
7
|
+
// Tests inject a `fetch` implementation; production uses global fetch with the
|
|
8
|
+
// API key attached.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
import { readSseFrames } from "./sse-frames.js";
|
|
11
|
+
/** Error thrown for non-2xx integration API responses. */
|
|
12
|
+
export class IntegrationApiError extends Error {
|
|
13
|
+
status;
|
|
14
|
+
code;
|
|
15
|
+
details;
|
|
16
|
+
constructor(message, status,
|
|
17
|
+
/** Machine-readable discriminator from the error body (e.g. `tool_call_expired`). */
|
|
18
|
+
code,
|
|
19
|
+
/** Additive machine-readable context (e.g. `{ expiredIds }`). */
|
|
20
|
+
details) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.status = status;
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.details = details;
|
|
25
|
+
this.name = "IntegrationApiError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class IntegrationClient {
|
|
29
|
+
baseUrl;
|
|
30
|
+
integrationId;
|
|
31
|
+
apiKey;
|
|
32
|
+
fetchImpl;
|
|
33
|
+
constructor(opts) {
|
|
34
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
35
|
+
this.integrationId = opts.integrationId;
|
|
36
|
+
this.apiKey = opts.apiKey;
|
|
37
|
+
this.fetchImpl = opts.fetch ?? fetch;
|
|
38
|
+
}
|
|
39
|
+
url(path) {
|
|
40
|
+
return `${this.baseUrl}/integrations/${encodeURIComponent(this.integrationId)}${path}`;
|
|
41
|
+
}
|
|
42
|
+
headers(json = false) {
|
|
43
|
+
const headers = { Authorization: `Bearer ${this.apiKey}` };
|
|
44
|
+
if (json)
|
|
45
|
+
headers["Content-Type"] = "application/json";
|
|
46
|
+
return headers;
|
|
47
|
+
}
|
|
48
|
+
// -------------------------------------------------------------------------
|
|
49
|
+
// AG-UI run surface
|
|
50
|
+
// -------------------------------------------------------------------------
|
|
51
|
+
/**
|
|
52
|
+
* Start a run and stream its AG-UI events. The stream stays open through
|
|
53
|
+
* client-tool waits (heartbeats keep it alive), so it is the single line for
|
|
54
|
+
* the whole run: collect `CUSTOM_TOOL_CALL` events, execute the calls, then
|
|
55
|
+
* submit results with sendRunMessages — the continuation's events keep
|
|
56
|
+
* flowing on this same stream.
|
|
57
|
+
*
|
|
58
|
+
* The thread id arrives in the X-Thread-Id response header, before the first
|
|
59
|
+
* event, and is also latched through `onThreadId`.
|
|
60
|
+
*/
|
|
61
|
+
async startRun(req, opts) {
|
|
62
|
+
const res = await this.fetchImpl(this.url("/v1/runs"), {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: this.headers(true),
|
|
65
|
+
body: JSON.stringify(req),
|
|
66
|
+
signal: opts.signal,
|
|
67
|
+
});
|
|
68
|
+
if (!res.ok)
|
|
69
|
+
throw await this.apiError(res);
|
|
70
|
+
const threadId = res.headers.get("X-Thread-Id") ?? undefined;
|
|
71
|
+
if (threadId)
|
|
72
|
+
opts.onThreadId?.(threadId);
|
|
73
|
+
if (!res.body)
|
|
74
|
+
throw new IntegrationApiError("Server returned an empty response body.", res.status);
|
|
75
|
+
let error;
|
|
76
|
+
try {
|
|
77
|
+
await readSseFrames(res.body, (frame) => opts.onEvent(frame));
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (err instanceof Error && err.name === "AbortError")
|
|
81
|
+
throw err;
|
|
82
|
+
error = err instanceof Error ? err.message : String(err);
|
|
83
|
+
}
|
|
84
|
+
return { threadId, error };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Continue the thread's active run: user/system messages steer it, tool-role
|
|
88
|
+
* messages resume it (tool results are awaited by the agent). The server
|
|
89
|
+
* answers with a single SSE frame — INPUT_ACCEPTED, or INPUT_REJECTED
|
|
90
|
+
* (thrown as IntegrationApiError with the machine-readable `code`). Run
|
|
91
|
+
* events keep flowing on the run stream, never on this response.
|
|
92
|
+
*
|
|
93
|
+
* Returns the persisted message id for steers (absent for tool-result
|
|
94
|
+
* resumes) — the caller correlates the later QUEUED_MESSAGE_DELIVERED event
|
|
95
|
+
* with it.
|
|
96
|
+
*/
|
|
97
|
+
async sendRunMessages(threadId, messages, opts = {}) {
|
|
98
|
+
const res = await this.fetchImpl(this.url(`/v1/runs/${encodeURIComponent(threadId)}/messages`), {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: this.headers(true),
|
|
101
|
+
body: JSON.stringify({ messages, ...(opts.tools?.length ? { tools: opts.tools } : {}) }),
|
|
102
|
+
signal: opts.signal,
|
|
103
|
+
});
|
|
104
|
+
if (!res.ok)
|
|
105
|
+
throw await this.apiError(res);
|
|
106
|
+
if (!res.body)
|
|
107
|
+
throw new IntegrationApiError("Server returned an empty response body.", res.status);
|
|
108
|
+
const acks = [];
|
|
109
|
+
// One frame is the whole answer: stop reading instead of waiting for the
|
|
110
|
+
// connection to close (a proxy that holds it open would hang the caller).
|
|
111
|
+
await readSseFrames(res.body, (frame) => {
|
|
112
|
+
acks.push(frame);
|
|
113
|
+
return "stop";
|
|
114
|
+
});
|
|
115
|
+
const ack = acks[0];
|
|
116
|
+
if (!ack)
|
|
117
|
+
throw new IntegrationApiError("The server did not acknowledge the input.", res.status);
|
|
118
|
+
if (ack.type === "INPUT_REJECTED") {
|
|
119
|
+
throw new IntegrationApiError(ack.message, res.status, ack.code, ack.details);
|
|
120
|
+
}
|
|
121
|
+
return ack.messageId;
|
|
122
|
+
}
|
|
123
|
+
// -------------------------------------------------------------------------
|
|
124
|
+
// Non-streaming endpoints
|
|
125
|
+
// -------------------------------------------------------------------------
|
|
126
|
+
async getStatus(opts = {}) {
|
|
127
|
+
return this.json(this.url(""), { method: "GET", signal: opts.signal });
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Models this integration accepts (default first, `default: true` marks it).
|
|
131
|
+
* A forced-model integration returns exactly one entry — there is nothing to
|
|
132
|
+
* choose. Used by the `/model` picker.
|
|
133
|
+
*/
|
|
134
|
+
async listModels() {
|
|
135
|
+
const body = await this.json(this.url("/v1/models"), { method: "GET" });
|
|
136
|
+
return body.data ?? [];
|
|
137
|
+
}
|
|
138
|
+
async listSessions() {
|
|
139
|
+
return this.json(this.url("/sessions"), { method: "GET" });
|
|
140
|
+
}
|
|
141
|
+
async getSession(threadId, opts = {}) {
|
|
142
|
+
const params = new URLSearchParams();
|
|
143
|
+
if (opts.history)
|
|
144
|
+
params.set("include_history", "true");
|
|
145
|
+
if (opts.historyLimit)
|
|
146
|
+
params.set("history_limit", String(opts.historyLimit));
|
|
147
|
+
const query = params.size > 0 ? `?${params}` : "";
|
|
148
|
+
return this.json(this.url(`/sessions/${encodeURIComponent(threadId)}${query}`), { method: "GET" });
|
|
149
|
+
}
|
|
150
|
+
async patchSessionTitle(threadId, title, opts = {}) {
|
|
151
|
+
await this.json(this.url(`/sessions/${encodeURIComponent(threadId)}`), {
|
|
152
|
+
method: "PATCH",
|
|
153
|
+
body: JSON.stringify({ title }),
|
|
154
|
+
signal: opts.signal,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
async deleteSession(threadId) {
|
|
158
|
+
await this.json(this.url(`/sessions/${encodeURIComponent(threadId)}`), { method: "DELETE" });
|
|
159
|
+
}
|
|
160
|
+
async stopSession(threadId) {
|
|
161
|
+
await this.json(this.url(`/sessions/${encodeURIComponent(threadId)}/stop`), { method: "POST" });
|
|
162
|
+
}
|
|
163
|
+
async compactSession(threadId, focus) {
|
|
164
|
+
await this.json(this.url(`/sessions/${encodeURIComponent(threadId)}/compact`), {
|
|
165
|
+
method: "POST",
|
|
166
|
+
body: JSON.stringify(focus ? { focus } : {}),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
// -------------------------------------------------------------------------
|
|
170
|
+
// Workspace files (per-integration path — see integration-api.md)
|
|
171
|
+
// -------------------------------------------------------------------------
|
|
172
|
+
/** GET /integrations/<id>/workspace?path= — list a workspace directory. */
|
|
173
|
+
async workspaceList(path, opts = {}) {
|
|
174
|
+
const params = new URLSearchParams({ path });
|
|
175
|
+
return this.json(this.url(`/workspace?${params}`), { method: "GET", signal: opts.signal });
|
|
176
|
+
}
|
|
177
|
+
/** GET /integrations/<id>/workspace/read?path= — read one file (base64 when binary). */
|
|
178
|
+
async workspaceRead(path, opts = {}) {
|
|
179
|
+
const params = new URLSearchParams({ path });
|
|
180
|
+
return this.json(this.url(`/workspace/read?${params}`), { method: "GET", signal: opts.signal });
|
|
181
|
+
}
|
|
182
|
+
/** PUT /integrations/<id>/workspace/write — create or overwrite one file. */
|
|
183
|
+
async workspaceWrite(path, content, binary, opts = {}) {
|
|
184
|
+
return this.json(this.url("/workspace/write"), {
|
|
185
|
+
method: "PUT",
|
|
186
|
+
body: JSON.stringify({ path, content, binary }),
|
|
187
|
+
signal: opts.signal,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Subscribe to a session's AG-UI event stream (SSE). Resolves when the
|
|
192
|
+
* stream ends (server close or network drop) — the caller reconnects with
|
|
193
|
+
* backoff. AbortError propagates so a deliberate unsubscribe is
|
|
194
|
+
* distinguishable from a drop.
|
|
195
|
+
*
|
|
196
|
+
* Events are delivered verbatim: `MESSAGES_SNAPSHOT` first (transcript +
|
|
197
|
+
* status), then live AG-UI events including `TOOL_CALL_RESULT` for
|
|
198
|
+
* client-executed calls (an error result means the server released the call).
|
|
199
|
+
*/
|
|
200
|
+
async openSessionEvents(threadId, opts) {
|
|
201
|
+
const res = await this.fetchImpl(this.url(`/sessions/${encodeURIComponent(threadId)}/events`), {
|
|
202
|
+
method: "GET",
|
|
203
|
+
headers: { ...this.headers(false), Accept: "text/event-stream" },
|
|
204
|
+
signal: opts.signal,
|
|
205
|
+
});
|
|
206
|
+
if (!res.ok)
|
|
207
|
+
throw await this.apiError(res);
|
|
208
|
+
if (!res.body)
|
|
209
|
+
throw new IntegrationApiError("Server returned an empty response body.", res.status);
|
|
210
|
+
try {
|
|
211
|
+
await readSseFrames(res.body, opts.onEvent);
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
if (err instanceof Error && err.name === "AbortError")
|
|
215
|
+
throw err;
|
|
216
|
+
// Network drop — the caller reconnects.
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async json(url, init) {
|
|
220
|
+
const res = await this.fetchImpl(url, {
|
|
221
|
+
...init,
|
|
222
|
+
headers: this.headers(init.body !== undefined),
|
|
223
|
+
});
|
|
224
|
+
if (!res.ok)
|
|
225
|
+
throw await this.apiError(res);
|
|
226
|
+
return await res.json();
|
|
227
|
+
}
|
|
228
|
+
/** Build an IntegrationApiError from a non-OK response, preserving the
|
|
229
|
+
* machine-readable `error.code` (e.g. `tool_call_expired`). */
|
|
230
|
+
async apiError(res) {
|
|
231
|
+
let message = `HTTP ${res.status}`;
|
|
232
|
+
let code;
|
|
233
|
+
let details;
|
|
234
|
+
try {
|
|
235
|
+
const body = await res.json();
|
|
236
|
+
if (body.error?.message)
|
|
237
|
+
message = body.error.message;
|
|
238
|
+
code = body.error?.code;
|
|
239
|
+
details = body.error?.details;
|
|
240
|
+
}
|
|
241
|
+
catch { /* non-JSON error body */ }
|
|
242
|
+
if (res.status === 404) {
|
|
243
|
+
message += " (check the integration id and that the integration is enabled)";
|
|
244
|
+
}
|
|
245
|
+
else if (res.status === 401) {
|
|
246
|
+
message += " (check the API key)";
|
|
247
|
+
}
|
|
248
|
+
return new IntegrationApiError(message, res.status, code, details);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read an SSE body, invoking `onFrame` with each parsed `data:` payload.
|
|
3
|
+
* Returning `"stop"` from `onFrame` ends the read (and cancels the reader) —
|
|
4
|
+
* a single-frame endpoint must not wait for the whole connection to close.
|
|
5
|
+
*/
|
|
6
|
+
export declare function readSseFrames(body: ReadableStream<Uint8Array>, onFrame: (frame: Record<string, unknown>) => void | "stop"): Promise<void>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// AG-UI SSE frame reader.
|
|
3
|
+
//
|
|
4
|
+
// The AG-UI surface streams `data: {json}` frames verbatim (no OpenAI chunk
|
|
5
|
+
// mapping): the run stream and the session events channel share this reader.
|
|
6
|
+
// `: comment` lines (heartbeats) and unknown fields are ignored; malformed
|
|
7
|
+
// JSON is skipped — a single bad frame must not kill an observation stream.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
/**
|
|
10
|
+
* Cap for one un-terminated frame. Frames are bounded in practice (the widest
|
|
11
|
+
* is a MESSAGES_SNAPSHOT), so the cap only bites on a stuck or hostile stream
|
|
12
|
+
* that never sends the blank-line terminator. The partial is dropped and the
|
|
13
|
+
* reader resynchronizes at the next frame boundary rather than growing the
|
|
14
|
+
* buffer without limit.
|
|
15
|
+
*/
|
|
16
|
+
const MAX_FRAME_CHARS = 8 * 1024 * 1024;
|
|
17
|
+
/**
|
|
18
|
+
* Read an SSE body, invoking `onFrame` with each parsed `data:` payload.
|
|
19
|
+
* Returning `"stop"` from `onFrame` ends the read (and cancels the reader) —
|
|
20
|
+
* a single-frame endpoint must not wait for the whole connection to close.
|
|
21
|
+
*/
|
|
22
|
+
export async function readSseFrames(body, onFrame) {
|
|
23
|
+
const reader = body.getReader();
|
|
24
|
+
const decoder = new TextDecoder();
|
|
25
|
+
let buffer = "";
|
|
26
|
+
/** Set when an oversized partial was dropped: skip up to the next boundary. */
|
|
27
|
+
let resyncing = false;
|
|
28
|
+
const dispatch = (frame) => {
|
|
29
|
+
if (!frame.startsWith("data: "))
|
|
30
|
+
return; // heartbeat comments
|
|
31
|
+
try {
|
|
32
|
+
return onFrame(JSON.parse(frame.slice(6)));
|
|
33
|
+
}
|
|
34
|
+
catch { /* malformed frame — ignore */ }
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
while (true) {
|
|
38
|
+
const { done, value } = await reader.read();
|
|
39
|
+
if (done)
|
|
40
|
+
break;
|
|
41
|
+
buffer += decoder.decode(value, { stream: true });
|
|
42
|
+
let idx;
|
|
43
|
+
let stop = false;
|
|
44
|
+
while ((idx = buffer.indexOf("\n\n")) >= 0) {
|
|
45
|
+
const frame = buffer.slice(0, idx);
|
|
46
|
+
buffer = buffer.slice(idx + 2);
|
|
47
|
+
if (resyncing) {
|
|
48
|
+
resyncing = false; // the dropped frame ends here
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (dispatch(frame) === "stop") {
|
|
52
|
+
stop = true;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (stop)
|
|
57
|
+
return;
|
|
58
|
+
if (buffer.length > MAX_FRAME_CHARS) {
|
|
59
|
+
buffer = "";
|
|
60
|
+
resyncing = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// EOF flush: a stream can deliver a complete `data:` payload but be cut
|
|
64
|
+
// before its blank-line terminator (a server crash mid-write still flushes
|
|
65
|
+
// the bytes it already wrote). Recover that final frame — malformed JSON
|
|
66
|
+
// is skipped by the same rule as inside the loop.
|
|
67
|
+
if (buffer && !resyncing)
|
|
68
|
+
dispatch(buffer);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
// An early return (or a throw) leaves the body open otherwise — the
|
|
72
|
+
// connection would stay alive until the process exits.
|
|
73
|
+
reader.cancel().catch(() => { });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/** Token usage/cost numbers as carried by the AG-UI `RUN_USAGE` event. */
|
|
2
|
+
export interface AgentUsage {
|
|
3
|
+
input: number;
|
|
4
|
+
output: number;
|
|
5
|
+
cacheRead: number;
|
|
6
|
+
cacheWrite: number;
|
|
7
|
+
totalTokens: number;
|
|
8
|
+
turns: number;
|
|
9
|
+
cost?: {
|
|
10
|
+
input: number;
|
|
11
|
+
output: number;
|
|
12
|
+
cacheRead?: number;
|
|
13
|
+
cacheWrite?: number;
|
|
14
|
+
total: number;
|
|
15
|
+
};
|
|
16
|
+
/** Current context size (last LLM call's input tokens). */
|
|
17
|
+
contextTokens: number;
|
|
18
|
+
/** Model context window in tokens (0 = unknown). */
|
|
19
|
+
contextWindow: number;
|
|
20
|
+
}
|
|
21
|
+
/** A client-executed tool call requested by the agent. */
|
|
22
|
+
export interface ClientToolCall {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
/** Raw JSON argument string from the model. */
|
|
26
|
+
arguments: string;
|
|
27
|
+
}
|
|
28
|
+
/** One server-executed tool call (informational stream only). */
|
|
29
|
+
export interface ServerToolEvent {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
phase: "start" | "result";
|
|
33
|
+
arguments?: string;
|
|
34
|
+
result?: unknown;
|
|
35
|
+
isError?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface IntegrationStatus {
|
|
38
|
+
id: string;
|
|
39
|
+
label: string;
|
|
40
|
+
enabled: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** One directory entry from GET /integrations/workspace. */
|
|
43
|
+
export interface WorkspaceEntry {
|
|
44
|
+
name: string;
|
|
45
|
+
path: string;
|
|
46
|
+
type: "file" | "directory";
|
|
47
|
+
size: number;
|
|
48
|
+
lastModified: string | null;
|
|
49
|
+
}
|
|
50
|
+
/** Result of GET /integrations/workspace/read. Content is base64 when binary. */
|
|
51
|
+
export interface WorkspaceFile {
|
|
52
|
+
path: string;
|
|
53
|
+
content: string;
|
|
54
|
+
size: number;
|
|
55
|
+
binary: boolean;
|
|
56
|
+
}
|
|
57
|
+
/** Result of PUT /integrations/workspace/write. */
|
|
58
|
+
export interface WorkspaceWriteResult {
|
|
59
|
+
ok: boolean;
|
|
60
|
+
size: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* One entry of GET /v1/models. The list is ordered with myagent's default
|
|
64
|
+
* model first; `default` marks that entry explicitly. When the integration
|
|
65
|
+
* forces a model there is exactly one entry.
|
|
66
|
+
*/
|
|
67
|
+
export interface ModelInfo {
|
|
68
|
+
id: string;
|
|
69
|
+
label?: string;
|
|
70
|
+
default?: boolean;
|
|
71
|
+
}
|
|
72
|
+
export interface SessionSummary {
|
|
73
|
+
thread_id: string;
|
|
74
|
+
title: string;
|
|
75
|
+
status: "idle" | "running" | "compacting" | "error";
|
|
76
|
+
message_count: number;
|
|
77
|
+
created_at: number;
|
|
78
|
+
updated_at: number;
|
|
79
|
+
}
|
|
80
|
+
export interface ThinkingInfo {
|
|
81
|
+
levels: string[];
|
|
82
|
+
default: string;
|
|
83
|
+
reasoning: boolean;
|
|
84
|
+
}
|
|
85
|
+
export interface SessionUsage {
|
|
86
|
+
tokenUsage: {
|
|
87
|
+
input: number;
|
|
88
|
+
output: number;
|
|
89
|
+
cacheRead?: number;
|
|
90
|
+
cacheWrite?: number;
|
|
91
|
+
totalTokens: number;
|
|
92
|
+
turns: number;
|
|
93
|
+
cost?: AgentUsage["cost"];
|
|
94
|
+
};
|
|
95
|
+
contextTokens: number;
|
|
96
|
+
contextWindow: number;
|
|
97
|
+
}
|
|
98
|
+
export interface HistoryToolCall {
|
|
99
|
+
id: string;
|
|
100
|
+
name: string;
|
|
101
|
+
arguments: unknown;
|
|
102
|
+
result: unknown;
|
|
103
|
+
isError: boolean;
|
|
104
|
+
source: "server" | "client";
|
|
105
|
+
}
|
|
106
|
+
export interface HistoryMessage {
|
|
107
|
+
role: "user" | "assistant";
|
|
108
|
+
text: string;
|
|
109
|
+
createdAt: number;
|
|
110
|
+
toolCalls?: HistoryToolCall[];
|
|
111
|
+
}
|
|
112
|
+
export interface PendingToolCall {
|
|
113
|
+
id: string;
|
|
114
|
+
name: string;
|
|
115
|
+
arguments: string;
|
|
116
|
+
}
|
|
117
|
+
export interface SessionDetail {
|
|
118
|
+
thread_id: string;
|
|
119
|
+
status: SessionSummary["status"];
|
|
120
|
+
message_count: number;
|
|
121
|
+
title: string;
|
|
122
|
+
created_at: number;
|
|
123
|
+
updated_at: number;
|
|
124
|
+
model?: string | null;
|
|
125
|
+
usage?: SessionUsage | null;
|
|
126
|
+
thinking?: ThinkingInfo;
|
|
127
|
+
history?: HistoryMessage[];
|
|
128
|
+
pending_tool_calls?: PendingToolCall[];
|
|
129
|
+
/** Unix ms: the previous run was interrupted by a server restart. */
|
|
130
|
+
last_run_interrupted_at?: number;
|
|
131
|
+
}
|
|
132
|
+
/** OpenAI-style error body from the integration API. */
|
|
133
|
+
export interface ApiErrorBody {
|
|
134
|
+
error?: {
|
|
135
|
+
message?: string;
|
|
136
|
+
type?: string;
|
|
137
|
+
code?: string;
|
|
138
|
+
details?: Record<string, unknown>;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* One verbatim AG-UI event from the run stream (or the session events
|
|
143
|
+
* channel) — the server forwards these without a lossy mapping. Only the
|
|
144
|
+
* types/fields this client consumes are modelled; unknown ones are ignored.
|
|
145
|
+
*/
|
|
146
|
+
export interface AguiEvent {
|
|
147
|
+
type: string;
|
|
148
|
+
[key: string]: unknown;
|
|
149
|
+
}
|
|
150
|
+
/** A custom (client-executed) tool definition on the AG-UI surface. */
|
|
151
|
+
export interface AguiToolDef {
|
|
152
|
+
name: string;
|
|
153
|
+
description: string;
|
|
154
|
+
parameters: Record<string, unknown>;
|
|
155
|
+
}
|
|
156
|
+
/** A user or system message on the AG-UI input surface. */
|
|
157
|
+
export interface AguiUserMessage {
|
|
158
|
+
role: "user" | "system";
|
|
159
|
+
content: string;
|
|
160
|
+
}
|
|
161
|
+
/** A client tool result — resumes the run awaiting that call. */
|
|
162
|
+
export interface AguiToolResult {
|
|
163
|
+
role: "tool";
|
|
164
|
+
toolCallId: string;
|
|
165
|
+
content: string;
|
|
166
|
+
isError?: boolean;
|
|
167
|
+
images?: Array<{
|
|
168
|
+
data: string;
|
|
169
|
+
mimeType: string;
|
|
170
|
+
}>;
|
|
171
|
+
}
|
|
172
|
+
export type AguiInputMessage = AguiUserMessage | AguiToolResult;
|
|
173
|
+
/** Body of POST /v1/runs — starts a run and streams its events. */
|
|
174
|
+
export interface AguiRunRequest {
|
|
175
|
+
threadId?: string;
|
|
176
|
+
messages: AguiUserMessage[];
|
|
177
|
+
tools?: AguiToolDef[];
|
|
178
|
+
/** Client-choice model for a new thread. */
|
|
179
|
+
model?: string;
|
|
180
|
+
thinkingLevel?: string;
|
|
181
|
+
}
|
|
182
|
+
/** Result of a run stream that ended (server close or drop). */
|
|
183
|
+
export interface RunStreamResult {
|
|
184
|
+
/** Thread id from the X-Thread-Id response header. */
|
|
185
|
+
threadId?: string;
|
|
186
|
+
/** Set when the connection dropped (network error) instead of closing cleanly. */
|
|
187
|
+
error?: string;
|
|
188
|
+
}
|
|
189
|
+
/** Single-frame ack of POST /v1/runs/:threadId/messages. */
|
|
190
|
+
export type AguiInputAck = {
|
|
191
|
+
type: "INPUT_ACCEPTED";
|
|
192
|
+
/** Steer only: the persisted message id — correlates the later
|
|
193
|
+
* QUEUED_MESSAGE_DELIVERED event with the submitted input. */
|
|
194
|
+
messageId?: string;
|
|
195
|
+
} | {
|
|
196
|
+
type: "INPUT_REJECTED";
|
|
197
|
+
message: string;
|
|
198
|
+
code?: string;
|
|
199
|
+
details?: Record<string, unknown>;
|
|
200
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Wire types for the MyAgent integration API
|
|
3
|
+
//
|
|
4
|
+
// Kept local (not imported from @agent-assistant/shared) because the CLI is a
|
|
5
|
+
// standalone app outside the Bun workspace — it must compile without the
|
|
6
|
+
// monorepo's node_modules. Only the fields this client consumes are modelled.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare const isBun: boolean;
|
|
2
|
+
export declare function fileExists(path: string): Promise<boolean>;
|
|
3
|
+
export declare function readTextFile(path: string): Promise<string>;
|
|
4
|
+
export declare function readJsonFile<T>(path: string): Promise<T>;
|
|
5
|
+
export declare function writeFile(path: string, data: string): Promise<void>;
|
|
6
|
+
/** Resolve an executable on PATH (Bun.which on Bun, a PATH scan on Node). */
|
|
7
|
+
export declare function which(cmd: string): string | null;
|
|
8
|
+
/** Read all of stdin as UTF-8 text. Callers check `process.stdin.isTTY` first. */
|
|
9
|
+
export declare function readStdinText(): Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* True when the caller's module is the process entry point. `import.meta` is
|
|
12
|
+
* module-scoped, so the caller must pass its own (`isMainModule(import.meta)`).
|
|
13
|
+
* Bun and Node ≥24.2 expose `import.meta.main`; older Node falls back to
|
|
14
|
+
* comparing the resolved entry path (realpath, so npm/bun bin symlinks match).
|
|
15
|
+
*/
|
|
16
|
+
export declare function isMainModule(meta: ImportMeta): boolean;
|
|
17
|
+
export interface ProcessStdin {
|
|
18
|
+
write(data: Uint8Array): void;
|
|
19
|
+
end(): void;
|
|
20
|
+
}
|
|
21
|
+
export interface ProcessHandle {
|
|
22
|
+
pid: number;
|
|
23
|
+
stdout: ReadableStream<Uint8Array>;
|
|
24
|
+
stderr: ReadableStream<Uint8Array>;
|
|
25
|
+
stdin: ProcessStdin;
|
|
26
|
+
exited: Promise<number>;
|
|
27
|
+
kill(signal?: number | NodeJS.Signals): void;
|
|
28
|
+
}
|
|
29
|
+
export interface SpawnOptions {
|
|
30
|
+
cwd?: string;
|
|
31
|
+
env?: Record<string, string>;
|
|
32
|
+
stdin?: "pipe" | "ignore";
|
|
33
|
+
stdout?: "pipe" | "ignore";
|
|
34
|
+
stderr?: "pipe" | "ignore";
|
|
35
|
+
/** POSIX: run in a new process group so `process.kill(-pid)` reaches the tree. */
|
|
36
|
+
detached?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare function spawnProcess(args: string[], opts?: SpawnOptions): ProcessHandle;
|