@tai42/api-client 0.1.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/LICENSE +202 -0
- package/README.md +28 -0
- package/dist/agents.d.ts +265 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +200 -0
- package/dist/agents.js.map +1 -0
- package/dist/client.d.ts +1316 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +468 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +43 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +63 -0
- package/dist/errors.js.map +1 -0
- package/dist/fleet-report.d.ts +61 -0
- package/dist/fleet-report.d.ts.map +1 -0
- package/dist/fleet-report.js +42 -0
- package/dist/fleet-report.js.map +1 -0
- package/dist/http.d.ts +69 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +152 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas.d.ts +5469 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +1103 -0
- package/dist/schemas.js.map +1 -0
- package/dist/sse.d.ts +27 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +84 -0
- package/dist/sse.js.map +1 -0
- package/dist/tool-runs.d.ts +112 -0
- package/dist/tool-runs.d.ts.map +1 -0
- package/dist/tool-runs.js +70 -0
- package/dist/tool-runs.js.map +1 -0
- package/package.json +51 -0
- package/src/agents.ts +248 -0
- package/src/client.ts +837 -0
- package/src/errors.ts +67 -0
- package/src/fleet-report.ts +94 -0
- package/src/http.ts +198 -0
- package/src/index.ts +142 -0
- package/src/schemas.ts +1341 -0
- package/src/sse.ts +90 -0
- package/src/tool-runs.ts +103 -0
package/src/sse.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A manual SSE frame parser over `fetch` + `ReadableStream` — never the
|
|
3
|
+
* `EventSource` API, which cannot send the auth header. Handles frames
|
|
4
|
+
* split across stream chunks and multi-line `data:` fields.
|
|
5
|
+
*/
|
|
6
|
+
export interface SseFrame {
|
|
7
|
+
readonly event: string;
|
|
8
|
+
/** The concatenated `data:` lines (joined by "\n"). */
|
|
9
|
+
readonly data: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Incrementally parse SSE text. Feed it chunks; it yields complete frames and
|
|
14
|
+
* retains any partial trailing frame across calls. A frame ends on a blank line.
|
|
15
|
+
*/
|
|
16
|
+
export class SseFrameParser {
|
|
17
|
+
private buffer = '';
|
|
18
|
+
|
|
19
|
+
/** Push a decoded text chunk; return every complete frame it now contains. */
|
|
20
|
+
push(chunk: string): SseFrame[] {
|
|
21
|
+
this.buffer += chunk;
|
|
22
|
+
const frames: SseFrame[] = [];
|
|
23
|
+
let sep = this.findSeparator();
|
|
24
|
+
while (sep !== -1) {
|
|
25
|
+
const raw = this.buffer.slice(0, sep.index);
|
|
26
|
+
this.buffer = this.buffer.slice(sep.index + sep.length);
|
|
27
|
+
const frame = parseFrame(raw);
|
|
28
|
+
if (frame) frames.push(frame);
|
|
29
|
+
sep = this.findSeparator();
|
|
30
|
+
}
|
|
31
|
+
return frames;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private findSeparator(): { index: number; length: number } | -1 {
|
|
35
|
+
const lf = this.buffer.indexOf('\n\n');
|
|
36
|
+
const crlf = this.buffer.indexOf('\r\n\r\n');
|
|
37
|
+
if (crlf !== -1 && (lf === -1 || crlf < lf)) return { index: crlf, length: 4 };
|
|
38
|
+
if (lf !== -1) return { index: lf, length: 2 };
|
|
39
|
+
return -1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parseFrame(raw: string): SseFrame | null {
|
|
44
|
+
let event = 'message';
|
|
45
|
+
const dataLines: string[] = [];
|
|
46
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
47
|
+
if (line.startsWith(':')) continue; // comment
|
|
48
|
+
const colon = line.indexOf(':');
|
|
49
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
50
|
+
// A single leading space after the colon is stripped per the SSE spec.
|
|
51
|
+
let value = colon === -1 ? '' : line.slice(colon + 1);
|
|
52
|
+
if (value.startsWith(' ')) value = value.slice(1);
|
|
53
|
+
if (field === 'event') event = value;
|
|
54
|
+
else if (field === 'data') dataLines.push(value);
|
|
55
|
+
}
|
|
56
|
+
if (dataLines.length === 0 && event === 'message') return null;
|
|
57
|
+
return { event, data: dataLines.join('\n') };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Consume an authed SSE endpoint as an async iterator of frames. The caller
|
|
62
|
+
* supplies the fetch (with the auth header + abort signal) and handles
|
|
63
|
+
* reconnect/backlog-replay semantics.
|
|
64
|
+
*/
|
|
65
|
+
export async function* readSseFrames(
|
|
66
|
+
response: Response,
|
|
67
|
+
signal?: AbortSignal,
|
|
68
|
+
): AsyncGenerator<SseFrame> {
|
|
69
|
+
if (!response.body) throw new Error('SSE response has no body');
|
|
70
|
+
const reader = response.body.getReader();
|
|
71
|
+
const decoder = new TextDecoder();
|
|
72
|
+
const parser = new SseFrameParser();
|
|
73
|
+
try {
|
|
74
|
+
for (;;) {
|
|
75
|
+
// An abort is a loud termination, never a silent iterator end: surface the
|
|
76
|
+
// signal's AbortError so the caller can tell a cancelled stream apart from a
|
|
77
|
+
// clean close. This matches fetch/ReadableStream, which reject an in-flight
|
|
78
|
+
// read on abort; the caller's own abort guard swallows it where cancellation
|
|
79
|
+
// is the expected outcome.
|
|
80
|
+
signal?.throwIfAborted();
|
|
81
|
+
const { value, done } = await reader.read();
|
|
82
|
+
if (done) break;
|
|
83
|
+
for (const frame of parser.push(decoder.decode(value, { stream: true }))) {
|
|
84
|
+
yield frame;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} finally {
|
|
88
|
+
reader.releaseLock();
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/tool-runs.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background tool-runs transport — the `/api/tool-runs*` surface.
|
|
3
|
+
*
|
|
4
|
+
* This is a SELF-CONTAINED api-client module (its own zod, not the shared
|
|
5
|
+
* `schemas.ts`), so the tools feature owns these wire types end to end. The
|
|
6
|
+
* `createApiClient` object wires three thin methods onto it (`submitToolRun`,
|
|
7
|
+
* `getToolRun`, `listToolRuns`) since only it holds the `ApiConfig` (base URL +
|
|
8
|
+
* auth token).
|
|
9
|
+
*
|
|
10
|
+
* The background surface detaches a run from its HTTP request: submit returns a
|
|
11
|
+
* `run_id` immediately (202), then the caller POLLS the single-run GET until the
|
|
12
|
+
* status is terminal (`succeeded | failed | lost`). The list GET returns the
|
|
13
|
+
* recent runs for one tool — id/status/timestamps only, never `result`/`error`.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
|
|
17
|
+
import { apiRequest, encodeSegment } from './http';
|
|
18
|
+
import type { ApiConfig } from './http';
|
|
19
|
+
|
|
20
|
+
/** A run's lifecycle state. `lost` = the server restarted mid-run; the result is
|
|
21
|
+
* unrecoverable. `running` is the only non-terminal state. */
|
|
22
|
+
export const toolRunStatus = z.enum(['running', 'succeeded', 'failed', 'lost']);
|
|
23
|
+
|
|
24
|
+
/** `POST /api/tool-runs` → the handle for the detached run. */
|
|
25
|
+
export const toolRunSubmitResult = z.object({
|
|
26
|
+
run_id: z.string(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/** `GET /api/tool-runs/{run_id}` → the full record. `result` is arbitrary JSON
|
|
30
|
+
* (present only on `succeeded`); `error` is present only on `failed`. */
|
|
31
|
+
export const toolRunRecord = z.object({
|
|
32
|
+
run_id: z.string(),
|
|
33
|
+
tool_name: z.string(),
|
|
34
|
+
status: toolRunStatus,
|
|
35
|
+
started_at: z.string(),
|
|
36
|
+
finished_at: z.string().optional(),
|
|
37
|
+
result: z.unknown().optional(),
|
|
38
|
+
error: z.string().optional(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/** One entry of `GET /api/tool-runs?tool_name=...` — id/status/timestamps only,
|
|
42
|
+
* deliberately carrying NO `result`/`error` (the list is not secret-bearing). */
|
|
43
|
+
export const toolRunListItem = z.object({
|
|
44
|
+
run_id: z.string(),
|
|
45
|
+
tool_name: z.string(),
|
|
46
|
+
status: toolRunStatus,
|
|
47
|
+
started_at: z.string(),
|
|
48
|
+
finished_at: z.string().optional(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const toolRunList = z.array(toolRunListItem);
|
|
52
|
+
|
|
53
|
+
export type ToolRunStatus = z.infer<typeof toolRunStatus>;
|
|
54
|
+
export type ToolRunSubmitResult = z.infer<typeof toolRunSubmitResult>;
|
|
55
|
+
export type ToolRunRecord = z.infer<typeof toolRunRecord>;
|
|
56
|
+
export type ToolRunListItem = z.infer<typeof toolRunListItem>;
|
|
57
|
+
|
|
58
|
+
/** Body for `POST /api/tool-runs`. */
|
|
59
|
+
export interface SubmitToolRunArgs {
|
|
60
|
+
readonly tool_name: string;
|
|
61
|
+
readonly arguments?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Submit a tool for detached background execution; resolves with its `run_id`. */
|
|
65
|
+
export function submitToolRun(
|
|
66
|
+
config: ApiConfig,
|
|
67
|
+
args: SubmitToolRunArgs,
|
|
68
|
+
signal?: AbortSignal,
|
|
69
|
+
): Promise<ToolRunSubmitResult> {
|
|
70
|
+
return apiRequest(config, '/api/tool-runs', toolRunSubmitResult, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
body: { tool_name: args.tool_name, arguments: args.arguments ?? {} },
|
|
73
|
+
signal,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Read one background run's current record (the poll target). */
|
|
78
|
+
export function getToolRun(
|
|
79
|
+
config: ApiConfig,
|
|
80
|
+
runId: string,
|
|
81
|
+
signal?: AbortSignal,
|
|
82
|
+
): Promise<ToolRunRecord> {
|
|
83
|
+
return apiRequest(config, `/api/tool-runs/${encodeSegment(runId)}`, toolRunRecord, {
|
|
84
|
+
signal,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** List the recent background runs for one tool (newest first). */
|
|
89
|
+
export function listToolRuns(
|
|
90
|
+
config: ApiConfig,
|
|
91
|
+
toolName: string,
|
|
92
|
+
signal?: AbortSignal,
|
|
93
|
+
): Promise<ToolRunListItem[]> {
|
|
94
|
+
return apiRequest(config, '/api/tool-runs', toolRunList, {
|
|
95
|
+
query: { tool_name: toolName },
|
|
96
|
+
signal,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Whether a run has reached a terminal state (polling stops here). */
|
|
101
|
+
export function isTerminalRunStatus(status: ToolRunStatus): boolean {
|
|
102
|
+
return status !== 'running';
|
|
103
|
+
}
|