@tiny-fish/sdk 0.0.1 → 0.0.3
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/dist/_utils/client.d.ts +5 -1
- package/dist/_utils/client.d.ts.map +1 -1
- package/dist/_utils/client.js +22 -4
- package/dist/_utils/client.js.map +1 -1
- package/dist/agent/index.d.ts +4 -3
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +35 -16
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/types.d.ts +25 -12
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/runs/index.d.ts.map +1 -1
- package/dist/runs/index.js +2 -1
- package/dist/runs/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -4
- package/src/_utils/client.ts +0 -219
- package/src/_utils/errors.ts +0 -153
- package/src/_utils/index.ts +0 -31
- package/src/_utils/resource.ts +0 -9
- package/src/_utils/sse.ts +0 -72
- package/src/agent/index.ts +0 -210
- package/src/agent/types.ts +0 -190
- package/src/client.ts +0 -19
- package/src/index.ts +0 -64
- package/src/runs/index.ts +0 -47
- package/src/runs/types.ts +0 -81
- package/src/version.ts +0 -2
package/src/_utils/client.ts
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base HTTP client — handles auth, headers, retries, and error mapping.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import pRetry, { AbortError } from "p-retry";
|
|
6
|
-
|
|
7
|
-
import { VERSION } from "../version.js";
|
|
8
|
-
import {
|
|
9
|
-
APIConnectionError,
|
|
10
|
-
APIStatusError,
|
|
11
|
-
APITimeoutError,
|
|
12
|
-
AuthenticationError,
|
|
13
|
-
BadRequestError,
|
|
14
|
-
ConflictError,
|
|
15
|
-
InternalServerError,
|
|
16
|
-
NotFoundError,
|
|
17
|
-
PermissionDeniedError,
|
|
18
|
-
RateLimitError,
|
|
19
|
-
RequestTimeoutError,
|
|
20
|
-
SDKError,
|
|
21
|
-
UnprocessableEntityError,
|
|
22
|
-
} from "./errors.js";
|
|
23
|
-
|
|
24
|
-
export interface ClientOptions {
|
|
25
|
-
apiKey?: string;
|
|
26
|
-
baseURL?: string;
|
|
27
|
-
timeout?: number;
|
|
28
|
-
maxRetries?: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const DEFAULT_BASE_URL = "https://agent.tinyfish.ai";
|
|
32
|
-
const DEFAULT_TIMEOUT = 600_000; // 10 minutes, matches Python SDK
|
|
33
|
-
const DEFAULT_MAX_RETRIES = 2;
|
|
34
|
-
|
|
35
|
-
export class BaseClient {
|
|
36
|
-
readonly apiKey: string;
|
|
37
|
-
readonly baseURL: string;
|
|
38
|
-
readonly timeout: number;
|
|
39
|
-
readonly maxRetries: number;
|
|
40
|
-
|
|
41
|
-
constructor(options: ClientOptions = {}) {
|
|
42
|
-
const apiKey = options.apiKey ?? process.env["TINYFISH_API_KEY"];
|
|
43
|
-
if (!apiKey) {
|
|
44
|
-
throw new SDKError(
|
|
45
|
-
"Missing API key. Pass `apiKey` to the constructor or set the TINYFISH_API_KEY environment variable.",
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
this.apiKey = apiKey;
|
|
50
|
-
this.baseURL = options.baseURL ?? DEFAULT_BASE_URL;
|
|
51
|
-
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
52
|
-
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
protected _buildHeaders(): Record<string, string> {
|
|
56
|
-
return {
|
|
57
|
-
"X-API-Key": this.apiKey,
|
|
58
|
-
"Content-Type": "application/json",
|
|
59
|
-
Accept: "application/json",
|
|
60
|
-
"User-Agent": `tinyfish-typescript/${VERSION}`,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Core request method with retry and error mapping.
|
|
66
|
-
* All public methods (get, post, postStream) delegate here.
|
|
67
|
-
*/
|
|
68
|
-
private async _request(method: string, path: string, body?: unknown): Promise<Response> {
|
|
69
|
-
const url = `${this.baseURL}${path}`;
|
|
70
|
-
const headers = this._buildHeaders();
|
|
71
|
-
|
|
72
|
-
return pRetry(
|
|
73
|
-
async () => {
|
|
74
|
-
try {
|
|
75
|
-
const response = await fetch(url, {
|
|
76
|
-
method,
|
|
77
|
-
headers,
|
|
78
|
-
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
79
|
-
signal: AbortSignal.timeout(this.timeout),
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
if (response.ok) {
|
|
83
|
-
return response;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const sdkError = await this._makeStatusError(response);
|
|
87
|
-
|
|
88
|
-
// Non-retryable status codes abort immediately
|
|
89
|
-
const status = response.status;
|
|
90
|
-
if (status !== 408 && status !== 429 && status < 500) {
|
|
91
|
-
throw new AbortError(sdkError);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Retryable — let p-retry handle backoff
|
|
95
|
-
throw sdkError;
|
|
96
|
-
} catch (error) {
|
|
97
|
-
// Re-throw AbortError and SDK errors as-is
|
|
98
|
-
if (error instanceof AbortError || error instanceof SDKError) {
|
|
99
|
-
throw error;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// AbortSignal.timeout throws a DOMException with name "TimeoutError"
|
|
103
|
-
if (error instanceof DOMException && error.name === "TimeoutError") {
|
|
104
|
-
throw new APITimeoutError(`Request timed out after ${this.timeout}ms`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Network failures (DNS, refused, etc.)
|
|
108
|
-
throw new APIConnectionError(
|
|
109
|
-
error instanceof Error ? error.message : "Connection failed",
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
retries: this.maxRetries,
|
|
115
|
-
minTimeout: 500,
|
|
116
|
-
maxTimeout: 8_000,
|
|
117
|
-
factor: 2,
|
|
118
|
-
},
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Extracts a human-readable error message from the response body.
|
|
124
|
-
* Tries JSON body → error.message → message, falls back to text, then HTTP status.
|
|
125
|
-
* Matches Python SDK's _parse_error_message.
|
|
126
|
-
*/
|
|
127
|
-
private async _parseErrorMessage(response: Response): Promise<string> {
|
|
128
|
-
try {
|
|
129
|
-
const text = await response.text();
|
|
130
|
-
try {
|
|
131
|
-
const body = JSON.parse(text) as Record<string, unknown> | null | undefined;
|
|
132
|
-
const errorObj = body?.["error"] as Record<string, unknown> | null | undefined;
|
|
133
|
-
const msg = (errorObj?.["message"] as string) ?? (body?.["message"] as string);
|
|
134
|
-
if (typeof msg === "string" && msg.length > 0) {
|
|
135
|
-
return msg;
|
|
136
|
-
}
|
|
137
|
-
} catch {
|
|
138
|
-
// Not JSON — fall through to use raw text
|
|
139
|
-
}
|
|
140
|
-
if (text.length > 0) {
|
|
141
|
-
return text;
|
|
142
|
-
}
|
|
143
|
-
} catch {
|
|
144
|
-
// Body read failed entirely
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return `HTTP ${response.status}`;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** Maps HTTP status codes to typed error classes. Matches Python SDK's _make_status_error. */
|
|
151
|
-
private async _makeStatusError(response: Response): Promise<SDKError> {
|
|
152
|
-
const msg = await this._parseErrorMessage(response);
|
|
153
|
-
const opts = { response };
|
|
154
|
-
|
|
155
|
-
switch (response.status) {
|
|
156
|
-
case 400:
|
|
157
|
-
return new BadRequestError(msg, opts);
|
|
158
|
-
case 401:
|
|
159
|
-
return new AuthenticationError(msg, opts);
|
|
160
|
-
case 403:
|
|
161
|
-
return new PermissionDeniedError(msg, opts);
|
|
162
|
-
case 404:
|
|
163
|
-
return new NotFoundError(msg, opts);
|
|
164
|
-
case 408:
|
|
165
|
-
return new RequestTimeoutError(msg, opts);
|
|
166
|
-
case 409:
|
|
167
|
-
return new ConflictError(msg, opts);
|
|
168
|
-
case 422:
|
|
169
|
-
return new UnprocessableEntityError(msg, opts);
|
|
170
|
-
case 429:
|
|
171
|
-
return new RateLimitError(msg, opts);
|
|
172
|
-
default:
|
|
173
|
-
if (response.status >= 500) {
|
|
174
|
-
return new InternalServerError(msg, {
|
|
175
|
-
response,
|
|
176
|
-
statusCode: response.status,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
return new APIStatusError(msg, {
|
|
180
|
-
response,
|
|
181
|
-
statusCode: response.status,
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
async get<T>(
|
|
187
|
-
path: string,
|
|
188
|
-
options?: { params?: Record<string, string | number | boolean | undefined> },
|
|
189
|
-
): Promise<T> {
|
|
190
|
-
let url = path;
|
|
191
|
-
if (options?.params) {
|
|
192
|
-
const filtered: Record<string, string> = {};
|
|
193
|
-
for (const [key, value] of Object.entries(options.params)) {
|
|
194
|
-
if (value !== undefined) {
|
|
195
|
-
filtered[key] = String(value);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
const searchParams = new URLSearchParams(filtered);
|
|
199
|
-
url = `${path}?${searchParams.toString()}`;
|
|
200
|
-
}
|
|
201
|
-
const response = await this._request("GET", url);
|
|
202
|
-
return (await response.json()) as T;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async post<T>(path: string, options?: { json?: unknown }): Promise<T> {
|
|
206
|
-
const response = await this._request("POST", path, options?.json);
|
|
207
|
-
return (await response.json()) as T;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
async postStream(path: string, options?: { json?: unknown }): Promise<ReadableStream<string>> {
|
|
211
|
-
const response = await this._request("POST", path, options?.json);
|
|
212
|
-
|
|
213
|
-
if (!response.body) {
|
|
214
|
-
throw new SDKError("Response body is null — server did not return a stream");
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return response.body.pipeThrough(new TextDecoderStream());
|
|
218
|
-
}
|
|
219
|
-
}
|
package/src/_utils/errors.ts
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exception hierarchy for SDK errors.
|
|
3
|
-
*
|
|
4
|
-
* Hierarchy:
|
|
5
|
-
* SDKError
|
|
6
|
-
* +- SSEParseError
|
|
7
|
-
* +- APIError
|
|
8
|
-
* +- APIConnectionError
|
|
9
|
-
* | +- APITimeoutError
|
|
10
|
-
* +- APIStatusError
|
|
11
|
-
* +- BadRequestError (400)
|
|
12
|
-
* +- AuthenticationError (401)
|
|
13
|
-
* +- PermissionDeniedError (403)
|
|
14
|
-
* +- NotFoundError (404)
|
|
15
|
-
* +- RequestTimeoutError (408)
|
|
16
|
-
* +- ConflictError (409)
|
|
17
|
-
* +- UnprocessableEntityError (422)
|
|
18
|
-
* +- RateLimitError (429)
|
|
19
|
-
* +- InternalServerError (500+)
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export class SDKError extends Error {
|
|
23
|
-
constructor(message: string) {
|
|
24
|
-
super(message);
|
|
25
|
-
this.name = "SDKError";
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class SSEParseError extends SDKError {
|
|
30
|
-
readonly line: string;
|
|
31
|
-
|
|
32
|
-
constructor(message: string, line: string) {
|
|
33
|
-
super(message);
|
|
34
|
-
this.name = "SSEParseError";
|
|
35
|
-
this.line = line;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export class APIError extends SDKError {
|
|
40
|
-
readonly request: Request | undefined;
|
|
41
|
-
readonly response: Response | undefined;
|
|
42
|
-
|
|
43
|
-
constructor(
|
|
44
|
-
message: string,
|
|
45
|
-
options?: { request?: Request | undefined; response?: Response | undefined },
|
|
46
|
-
) {
|
|
47
|
-
super(message);
|
|
48
|
-
this.name = "APIError";
|
|
49
|
-
this.request = options?.request;
|
|
50
|
-
this.response = options?.response;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export class APIConnectionError extends APIError {
|
|
55
|
-
constructor(message: string, options?: { request?: Request | undefined }) {
|
|
56
|
-
super(message, options);
|
|
57
|
-
this.name = "APIConnectionError";
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export class APITimeoutError extends APIConnectionError {
|
|
62
|
-
constructor(message: string, options?: { request?: Request | undefined }) {
|
|
63
|
-
super(message, options);
|
|
64
|
-
this.name = "APITimeoutError";
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export class APIStatusError extends APIError {
|
|
69
|
-
readonly statusCode: number;
|
|
70
|
-
|
|
71
|
-
constructor(
|
|
72
|
-
message: string,
|
|
73
|
-
options: { response: Response; statusCode: number; request?: Request | undefined },
|
|
74
|
-
) {
|
|
75
|
-
super(message, { request: options.request, response: options.response });
|
|
76
|
-
this.name = "APIStatusError";
|
|
77
|
-
this.statusCode = options.statusCode;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export class BadRequestError extends APIStatusError {
|
|
82
|
-
constructor(message: string, options: { response: Response; request?: Request | undefined }) {
|
|
83
|
-
super(message, { ...options, statusCode: 400 });
|
|
84
|
-
this.name = "BadRequestError";
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export class AuthenticationError extends APIStatusError {
|
|
89
|
-
constructor(message: string, options: { response: Response; request?: Request | undefined }) {
|
|
90
|
-
super(message, { ...options, statusCode: 401 });
|
|
91
|
-
this.name = "AuthenticationError";
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export class PermissionDeniedError extends APIStatusError {
|
|
96
|
-
constructor(message: string, options: { response: Response; request?: Request | undefined }) {
|
|
97
|
-
super(message, { ...options, statusCode: 403 });
|
|
98
|
-
this.name = "PermissionDeniedError";
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export class NotFoundError extends APIStatusError {
|
|
103
|
-
constructor(message: string, options: { response: Response; request?: Request | undefined }) {
|
|
104
|
-
super(message, { ...options, statusCode: 404 });
|
|
105
|
-
this.name = "NotFoundError";
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export class RequestTimeoutError extends APIStatusError {
|
|
110
|
-
constructor(message: string, options: { response: Response; request?: Request | undefined }) {
|
|
111
|
-
super(message, { ...options, statusCode: 408 });
|
|
112
|
-
this.name = "RequestTimeoutError";
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export class ConflictError extends APIStatusError {
|
|
117
|
-
constructor(message: string, options: { response: Response; request?: Request | undefined }) {
|
|
118
|
-
super(message, { ...options, statusCode: 409 });
|
|
119
|
-
this.name = "ConflictError";
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export class UnprocessableEntityError extends APIStatusError {
|
|
124
|
-
constructor(message: string, options: { response: Response; request?: Request | undefined }) {
|
|
125
|
-
super(message, { ...options, statusCode: 422 });
|
|
126
|
-
this.name = "UnprocessableEntityError";
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export class RateLimitError extends APIStatusError {
|
|
131
|
-
constructor(message: string, options: { response: Response; request?: Request | undefined }) {
|
|
132
|
-
super(message, { ...options, statusCode: 429 });
|
|
133
|
-
this.name = "RateLimitError";
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export class InternalServerError extends APIStatusError {
|
|
138
|
-
constructor(
|
|
139
|
-
message: string,
|
|
140
|
-
options: {
|
|
141
|
-
response: Response;
|
|
142
|
-
statusCode?: number | undefined;
|
|
143
|
-
request?: Request | undefined;
|
|
144
|
-
},
|
|
145
|
-
) {
|
|
146
|
-
super(message, {
|
|
147
|
-
response: options.response,
|
|
148
|
-
statusCode: options.statusCode ?? 500,
|
|
149
|
-
request: options.request,
|
|
150
|
-
});
|
|
151
|
-
this.name = "InternalServerError";
|
|
152
|
-
}
|
|
153
|
-
}
|
package/src/_utils/index.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reusable SDK foundation.
|
|
3
|
-
*
|
|
4
|
-
* Provides base classes for building HTTP API SDKs:
|
|
5
|
-
* - Client: HTTP client with auth and request plumbing
|
|
6
|
-
* - Resource: Base class for API resource groupings
|
|
7
|
-
* - Errors: Complete error hierarchy
|
|
8
|
-
* - SSE: Server-sent events parser
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export { BaseClient } from "./client.js";
|
|
12
|
-
export type { ClientOptions } from "./client.js";
|
|
13
|
-
export { APIResource } from "./resource.js";
|
|
14
|
-
export { parseSSEStream } from "./sse.js";
|
|
15
|
-
export {
|
|
16
|
-
SDKError,
|
|
17
|
-
SSEParseError,
|
|
18
|
-
APIError,
|
|
19
|
-
APIConnectionError,
|
|
20
|
-
APITimeoutError,
|
|
21
|
-
APIStatusError,
|
|
22
|
-
BadRequestError,
|
|
23
|
-
AuthenticationError,
|
|
24
|
-
PermissionDeniedError,
|
|
25
|
-
NotFoundError,
|
|
26
|
-
RequestTimeoutError,
|
|
27
|
-
ConflictError,
|
|
28
|
-
UnprocessableEntityError,
|
|
29
|
-
RateLimitError,
|
|
30
|
-
InternalServerError,
|
|
31
|
-
} from "./errors.js";
|
package/src/_utils/resource.ts
DELETED
package/src/_utils/sse.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SSE line-stream parser.
|
|
3
|
-
* Converts a ReadableStream of text chunks into parsed JSON event objects.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { SSEParseError } from "./errors.js";
|
|
7
|
-
|
|
8
|
-
const DATA_PREFIX = "data:";
|
|
9
|
-
|
|
10
|
-
/** Split a ReadableStream<string> of arbitrary text chunks into lines. */
|
|
11
|
-
async function* splitLines(stream: ReadableStream<string>): AsyncGenerator<string> {
|
|
12
|
-
const reader = stream.getReader();
|
|
13
|
-
let buffer = "";
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
while (true) {
|
|
17
|
-
const { done, value } = await reader.read();
|
|
18
|
-
if (done) break;
|
|
19
|
-
|
|
20
|
-
buffer += value;
|
|
21
|
-
const lines = buffer.split("\n");
|
|
22
|
-
// Last element is either empty (if chunk ended with \n) or a partial line
|
|
23
|
-
buffer = lines.pop() ?? "";
|
|
24
|
-
|
|
25
|
-
for (const line of lines) {
|
|
26
|
-
yield line;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Flush any remaining partial line
|
|
31
|
-
if (buffer.length > 0) {
|
|
32
|
-
yield buffer;
|
|
33
|
-
}
|
|
34
|
-
} finally {
|
|
35
|
-
reader.releaseLock();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Parse an SSE text stream into JSON event objects.
|
|
41
|
-
*
|
|
42
|
-
* Mirrors Python SDK's `parse_sse_line_stream`:
|
|
43
|
-
* - Strips each line
|
|
44
|
-
* - Skips empty lines and comment lines (`:` prefix)
|
|
45
|
-
* - Extracts `data:` payloads and parses as JSON
|
|
46
|
-
* - Ignores `event:`, `id:`, `retry:` lines
|
|
47
|
-
*/
|
|
48
|
-
export async function* parseSSEStream(
|
|
49
|
-
stream: ReadableStream<string>,
|
|
50
|
-
): AsyncGenerator<Record<string, unknown>> {
|
|
51
|
-
for await (const rawLine of splitLines(stream)) {
|
|
52
|
-
const line = rawLine.trim();
|
|
53
|
-
|
|
54
|
-
// Skip empty lines and comments
|
|
55
|
-
if (!line || line.startsWith(":")) {
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Parse data lines
|
|
60
|
-
if (line.startsWith(DATA_PREFIX)) {
|
|
61
|
-
const dataStr = line.slice(DATA_PREFIX.length).trim();
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
yield JSON.parse(dataStr) as Record<string, unknown>;
|
|
65
|
-
} catch {
|
|
66
|
-
throw new SSEParseError("Malformed JSON in SSE event", line);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Ignore event:, id:, retry: lines
|
|
71
|
-
}
|
|
72
|
-
}
|
package/src/agent/index.ts
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser automation resource.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { APIResource } from "../_utils/resource.js";
|
|
6
|
-
import { parseSSEStream } from "../_utils/sse.js";
|
|
7
|
-
import type {
|
|
8
|
-
AgentRunAsyncResponse,
|
|
9
|
-
AgentRunParams,
|
|
10
|
-
AgentRunResponse,
|
|
11
|
-
AgentRunWithStreamingResponse,
|
|
12
|
-
BrowserProfile,
|
|
13
|
-
CompleteEvent,
|
|
14
|
-
HeartbeatEvent,
|
|
15
|
-
ProgressEvent,
|
|
16
|
-
ProxyConfig,
|
|
17
|
-
RunError,
|
|
18
|
-
RunStatus,
|
|
19
|
-
StartedEvent,
|
|
20
|
-
StreamOptions,
|
|
21
|
-
StreamingUrlEvent,
|
|
22
|
-
} from "./types.js";
|
|
23
|
-
|
|
24
|
-
/** JSON body sent to automation endpoints. */
|
|
25
|
-
interface AgentRunBody {
|
|
26
|
-
goal: string;
|
|
27
|
-
url: string;
|
|
28
|
-
browser_profile?: BrowserProfile;
|
|
29
|
-
proxy_config?: ProxyConfig;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Build the JSON request body from typed params.
|
|
34
|
-
* Optional fields are omitted rather than sent as undefined/null.
|
|
35
|
-
*/
|
|
36
|
-
function buildRunBody(params: AgentRunParams): AgentRunBody {
|
|
37
|
-
const body: AgentRunBody = { goal: params.goal, url: params.url };
|
|
38
|
-
if (params.browser_profile !== undefined) body.browser_profile = params.browser_profile;
|
|
39
|
-
if (params.proxy_config !== undefined) body.proxy_config = params.proxy_config;
|
|
40
|
-
return body;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Async-iterable wrapper around an SSE event stream, this is what we return when they call the stream endpoint */
|
|
44
|
-
export class AgentStream {
|
|
45
|
-
private _generator: AsyncGenerator<AgentRunWithStreamingResponse>;
|
|
46
|
-
|
|
47
|
-
constructor(generator: AsyncGenerator<AgentRunWithStreamingResponse>) {
|
|
48
|
-
this._generator = generator;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
[Symbol.asyncIterator](): AsyncGenerator<AgentRunWithStreamingResponse> {
|
|
52
|
-
return this._generator;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Abort the underlying stream. */
|
|
56
|
-
async close(): Promise<void> {
|
|
57
|
-
await this._generator.return(undefined);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Validate raw SSE data into a typed event. Returns null for malformed or unknown events. */
|
|
62
|
-
function validateEvent(data: Record<string, unknown>): AgentRunWithStreamingResponse | null {
|
|
63
|
-
const type = data["type"];
|
|
64
|
-
if (typeof type !== "string") return null;
|
|
65
|
-
|
|
66
|
-
switch (type) {
|
|
67
|
-
case "STARTED": {
|
|
68
|
-
if (typeof data["runId"] !== "string" || typeof data["timestamp"] !== "string") return null;
|
|
69
|
-
return { type: "STARTED", runId: data["runId"], timestamp: data["timestamp"] };
|
|
70
|
-
}
|
|
71
|
-
case "STREAMING_URL": {
|
|
72
|
-
if (
|
|
73
|
-
typeof data["runId"] !== "string" ||
|
|
74
|
-
typeof data["streamingUrl"] !== "string" ||
|
|
75
|
-
typeof data["timestamp"] !== "string"
|
|
76
|
-
)
|
|
77
|
-
return null;
|
|
78
|
-
return {
|
|
79
|
-
type: "STREAMING_URL",
|
|
80
|
-
runId: data["runId"],
|
|
81
|
-
streamingUrl: data["streamingUrl"],
|
|
82
|
-
timestamp: data["timestamp"],
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
case "PROGRESS": {
|
|
86
|
-
if (
|
|
87
|
-
typeof data["runId"] !== "string" ||
|
|
88
|
-
typeof data["purpose"] !== "string" ||
|
|
89
|
-
typeof data["timestamp"] !== "string"
|
|
90
|
-
)
|
|
91
|
-
return null;
|
|
92
|
-
return {
|
|
93
|
-
type: "PROGRESS",
|
|
94
|
-
runId: data["runId"],
|
|
95
|
-
purpose: data["purpose"],
|
|
96
|
-
timestamp: data["timestamp"],
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
case "HEARTBEAT": {
|
|
100
|
-
if (typeof data["timestamp"] !== "string") return null;
|
|
101
|
-
return { type: "HEARTBEAT", timestamp: data["timestamp"] };
|
|
102
|
-
}
|
|
103
|
-
case "COMPLETE": {
|
|
104
|
-
if (
|
|
105
|
-
typeof data["runId"] !== "string" ||
|
|
106
|
-
typeof data["status"] !== "string" ||
|
|
107
|
-
typeof data["timestamp"] !== "string"
|
|
108
|
-
)
|
|
109
|
-
return null;
|
|
110
|
-
return {
|
|
111
|
-
type: "COMPLETE",
|
|
112
|
-
runId: data["runId"],
|
|
113
|
-
status: data["status"] as RunStatus,
|
|
114
|
-
timestamp: data["timestamp"],
|
|
115
|
-
resultJson: (data["resultJson"] as Record<string, unknown> | null) ?? null,
|
|
116
|
-
error: (data["error"] as RunError | null) ?? null,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
default:
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Validate raw event data, fire the matching callback, and return the typed event. */
|
|
125
|
-
function dispatchEvent(
|
|
126
|
-
data: Record<string, unknown>,
|
|
127
|
-
options: StreamOptions,
|
|
128
|
-
): AgentRunWithStreamingResponse | null {
|
|
129
|
-
const event = validateEvent(data);
|
|
130
|
-
if (!event) return null;
|
|
131
|
-
|
|
132
|
-
switch (event.type) {
|
|
133
|
-
case "STARTED":
|
|
134
|
-
options.onStarted?.(event);
|
|
135
|
-
break;
|
|
136
|
-
case "STREAMING_URL":
|
|
137
|
-
options.onStreamingUrl?.(event);
|
|
138
|
-
break;
|
|
139
|
-
case "PROGRESS":
|
|
140
|
-
options.onProgress?.(event);
|
|
141
|
-
break;
|
|
142
|
-
case "HEARTBEAT":
|
|
143
|
-
options.onHeartbeat?.(event);
|
|
144
|
-
break;
|
|
145
|
-
case "COMPLETE":
|
|
146
|
-
options.onComplete?.(event);
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return event;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** Browser automation methods. */
|
|
154
|
-
export class AgentResource extends APIResource {
|
|
155
|
-
/**
|
|
156
|
-
* Run a browser automation.
|
|
157
|
-
*
|
|
158
|
-
* Returns a promise that resolves with the completed agent run output.
|
|
159
|
-
*/
|
|
160
|
-
async run(params: AgentRunParams): Promise<AgentRunResponse> {
|
|
161
|
-
return this._client.post<AgentRunResponse>("/v1/automation/run", {
|
|
162
|
-
json: buildRunBody(params),
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Queue a browser automation and return immediately.
|
|
168
|
-
*
|
|
169
|
-
* Does not wait for the run to complete — returns a `run_id` straight away.
|
|
170
|
-
* Use `client.runs.get(run_id)` to poll for the result.
|
|
171
|
-
*/
|
|
172
|
-
async queue(params: AgentRunParams): Promise<AgentRunAsyncResponse> {
|
|
173
|
-
return this._client.post<AgentRunAsyncResponse>("/v1/automation/run-async", {
|
|
174
|
-
json: buildRunBody(params),
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Stream live events from a browser automation run.
|
|
180
|
-
*
|
|
181
|
-
* Returns an `AgentStream` that yields typed SSE events in real time:
|
|
182
|
-
* STARTED → STREAMING_URL → PROGRESS (repeated) → COMPLETE.
|
|
183
|
-
*
|
|
184
|
-
* Use the `on*` callbacks for a reactive style, or iterate over
|
|
185
|
-
* the stream for a sequential style:
|
|
186
|
-
*
|
|
187
|
-
* ```ts
|
|
188
|
-
* const stream = await client.agent.stream({ goal: "...", url: "..." });
|
|
189
|
-
* for await (const event of stream) {
|
|
190
|
-
* if (event.type === "PROGRESS") console.log(event.purpose);
|
|
191
|
-
* }
|
|
192
|
-
* ```
|
|
193
|
-
*/
|
|
194
|
-
async stream(params: AgentRunParams, options: StreamOptions = {}): Promise<AgentStream> {
|
|
195
|
-
const raw = await this._client.postStream("/v1/automation/run-sse", {
|
|
196
|
-
json: buildRunBody(params),
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
async function* generate(): AsyncGenerator<AgentRunWithStreamingResponse> {
|
|
200
|
-
for await (const data of parseSSEStream(raw)) {
|
|
201
|
-
const event = dispatchEvent(data, options);
|
|
202
|
-
if (event) {
|
|
203
|
-
yield event;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return new AgentStream(generate());
|
|
209
|
-
}
|
|
210
|
-
}
|