@tiny-fish/sdk 0.0.1

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 (62) hide show
  1. package/README.md +5 -0
  2. package/dist/_utils/client.d.ts +40 -0
  3. package/dist/_utils/client.d.ts.map +1 -0
  4. package/dist/_utils/client.js +168 -0
  5. package/dist/_utils/client.js.map +1 -0
  6. package/dist/_utils/errors.d.ts +109 -0
  7. package/dist/_utils/errors.d.ts.map +1 -0
  8. package/dist/_utils/errors.js +123 -0
  9. package/dist/_utils/errors.js.map +1 -0
  10. package/dist/_utils/index.d.ts +15 -0
  11. package/dist/_utils/index.d.ts.map +1 -0
  12. package/dist/_utils/index.js +14 -0
  13. package/dist/_utils/index.js.map +1 -0
  14. package/dist/_utils/resource.d.ts +6 -0
  15. package/dist/_utils/resource.d.ts.map +1 -0
  16. package/dist/_utils/resource.js +7 -0
  17. package/dist/_utils/resource.js.map +1 -0
  18. package/dist/_utils/sse.d.ts +15 -0
  19. package/dist/_utils/sse.d.ts.map +1 -0
  20. package/dist/_utils/sse.js +62 -0
  21. package/dist/_utils/sse.js.map +1 -0
  22. package/dist/agent/index.d.ts +47 -0
  23. package/dist/agent/index.d.ts.map +1 -0
  24. package/dist/agent/index.js +168 -0
  25. package/dist/agent/index.js.map +1 -0
  26. package/dist/agent/types.d.ts +163 -0
  27. package/dist/agent/types.d.ts.map +1 -0
  28. package/dist/agent/types.js +61 -0
  29. package/dist/agent/types.js.map +1 -0
  30. package/dist/client.d.ts +13 -0
  31. package/dist/client.d.ts.map +1 -0
  32. package/dist/client.js +16 -0
  33. package/dist/client.js.map +1 -0
  34. package/dist/index.d.ts +13 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +15 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/runs/index.d.ts +25 -0
  39. package/dist/runs/index.d.ts.map +1 -0
  40. package/dist/runs/index.js +47 -0
  41. package/dist/runs/index.js.map +1 -0
  42. package/dist/runs/types.d.ts +75 -0
  43. package/dist/runs/types.d.ts.map +1 -0
  44. package/dist/runs/types.js +10 -0
  45. package/dist/runs/types.js.map +1 -0
  46. package/dist/version.d.ts +2 -0
  47. package/dist/version.d.ts.map +1 -0
  48. package/dist/version.js +3 -0
  49. package/dist/version.js.map +1 -0
  50. package/package.json +43 -0
  51. package/src/_utils/client.ts +219 -0
  52. package/src/_utils/errors.ts +153 -0
  53. package/src/_utils/index.ts +31 -0
  54. package/src/_utils/resource.ts +9 -0
  55. package/src/_utils/sse.ts +72 -0
  56. package/src/agent/index.ts +210 -0
  57. package/src/agent/types.ts +190 -0
  58. package/src/client.ts +19 -0
  59. package/src/index.ts +64 -0
  60. package/src/runs/index.ts +47 -0
  61. package/src/runs/types.ts +81 -0
  62. package/src/version.ts +2 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Runs management request/response types.
3
+ */
4
+ import type { RunError, RunStatus } from "../agent/types.js";
5
+ /** Sort order for list queries. */
6
+ export declare enum SortDirection {
7
+ ASC = "asc",
8
+ DESC = "desc"
9
+ }
10
+ /** Browser configuration used for a run. */
11
+ export interface BrowserConfig {
12
+ /** Whether proxy was enabled. */
13
+ proxy_enabled: boolean | null;
14
+ /** Country code for proxy. */
15
+ proxy_country_code: string | null;
16
+ }
17
+ /** A single automation run with full details. */
18
+ export interface Run {
19
+ /** Unique identifier for the run. */
20
+ run_id: string;
21
+ /** Current status of the run. */
22
+ status: RunStatus;
23
+ /** Natural language goal for this automation run. */
24
+ goal: string;
25
+ /** ISO 8601 timestamp when run was created. */
26
+ created_at: string;
27
+ /** ISO 8601 timestamp when run started executing. */
28
+ started_at: string | null;
29
+ /** ISO 8601 timestamp when run finished executing. */
30
+ finished_at: string | null;
31
+ /** Number of steps taken during the automation. */
32
+ num_of_steps: number;
33
+ /** Extracted data from the automation run. `null` if not completed or failed. */
34
+ result: Record<string, unknown> | null;
35
+ /** Error details. `null` if the run succeeded or is still running. */
36
+ error: RunError | null;
37
+ /** URL to watch live browser session (available while running). */
38
+ streaming_url: string | null;
39
+ /** Browser configuration used for the run. */
40
+ browser_config: BrowserConfig | null;
41
+ }
42
+ /** Pagination metadata for list responses. */
43
+ export interface PaginationInfo {
44
+ /** Total number of runs matching the current filters. */
45
+ total: number;
46
+ /** Whether there are more results after this page. */
47
+ has_more: boolean;
48
+ /** Cursor for fetching next page. `null` if no more results. */
49
+ next_cursor: string | null;
50
+ }
51
+ /** Paginated list of automation runs. */
52
+ export interface RunListResponse {
53
+ /** Array of runs. */
54
+ data: Run[];
55
+ /** Pagination information. */
56
+ pagination: PaginationInfo;
57
+ }
58
+ /** Parameters for listing runs. */
59
+ export interface RunListParams {
60
+ /** Pagination cursor from a previous response's `next_cursor` field. */
61
+ cursor?: string;
62
+ /** Maximum number of runs to return. */
63
+ limit?: number;
64
+ /** Filter by run status. */
65
+ status?: RunStatus;
66
+ /** Filter by goal text. */
67
+ goal?: string;
68
+ /** Filter runs created after this ISO timestamp. */
69
+ created_after?: string;
70
+ /** Filter runs created before this ISO timestamp. */
71
+ created_before?: string;
72
+ /** Sort order — `"asc"` or `"desc"`. */
73
+ sort_direction?: SortDirection;
74
+ }
75
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/runs/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE7D,mCAAmC;AACnC,oBAAY,aAAa;IACxB,GAAG,QAAQ;IACX,IAAI,SAAS;CACb;AAED,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC7B,iCAAiC;IACjC,aAAa,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,8BAA8B;IAC9B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,iDAAiD;AACjD,MAAM,WAAW,GAAG;IACnB,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,MAAM,EAAE,SAAS,CAAC;IAClB,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,sDAAsD;IACtD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,mDAAmD;IACnD,YAAY,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACvC,sEAAsE;IACtE,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,mEAAmE;IACnE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,8CAA8C;IAC9C,cAAc,EAAE,aAAa,GAAG,IAAI,CAAC;CACrC;AAED,8CAA8C;AAC9C,MAAM,WAAW,cAAc;IAC9B,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,QAAQ,EAAE,OAAO,CAAC;IAClB,gEAAgE;IAChE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,yCAAyC;AACzC,MAAM,WAAW,eAAe;IAC/B,qBAAqB;IACrB,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,8BAA8B;IAC9B,UAAU,EAAE,cAAc,CAAC;CAC3B;AAED,mCAAmC;AACnC,MAAM,WAAW,aAAa;IAC7B,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wCAAwC;IACxC,cAAc,CAAC,EAAE,aAAa,CAAC;CAC/B"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Runs management request/response types.
3
+ */
4
+ /** Sort order for list queries. */
5
+ export var SortDirection;
6
+ (function (SortDirection) {
7
+ SortDirection["ASC"] = "asc";
8
+ SortDirection["DESC"] = "desc";
9
+ })(SortDirection || (SortDirection = {}));
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/runs/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,mCAAmC;AACnC,MAAM,CAAN,IAAY,aAGX;AAHD,WAAY,aAAa;IACxB,4BAAW,CAAA;IACX,8BAAa,CAAA;AACd,CAAC,EAHW,aAAa,KAAb,aAAa,QAGxB"}
@@ -0,0 +1,2 @@
1
+ export declare const VERSION = "0.0.1";
2
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,OAAO,UAAU,CAAC"}
@@ -0,0 +1,3 @@
1
+ // Auto-generated by scripts/generate-version.ts — do not edit manually.
2
+ export const VERSION = "0.0.1";
3
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@tiny-fish/sdk",
3
+ "version": "0.0.1",
4
+ "description": "TinyFish TypeScript SDK — State-of-the-art web agents in an API",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "scripts": {
22
+ "generate:version": "bun scripts/generate-version.ts",
23
+ "prebuild": "bun run generate:version",
24
+ "build": "bunx tsc -p tsconfig.build.json",
25
+ "prepack": "bun run build",
26
+ "typecheck": "bunx tsc --noEmit",
27
+ "lint": "biome check src/ tests/ scripts/ playground/",
28
+ "format": "biome format --write src/ tests/ scripts/ playground/",
29
+ "test": "bun test"
30
+ },
31
+ "devDependencies": {
32
+ "@biomejs/biome": "^1.9.0",
33
+ "@types/bun": "^1.3.10",
34
+ "typescript": "^5"
35
+ },
36
+ "dependencies": {
37
+ "p-retry": "^7.1.1"
38
+ },
39
+ "publishConfig": {
40
+ "registry": "https://registry.npmjs.org/",
41
+ "access": "restricted"
42
+ }
43
+ }
@@ -0,0 +1,219 @@
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
+ }
@@ -0,0 +1,153 @@
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
+ }
@@ -0,0 +1,31 @@
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";
@@ -0,0 +1,9 @@
1
+ import type { BaseClient } from "./client.js";
2
+
3
+ export class APIResource {
4
+ protected _client: BaseClient;
5
+
6
+ constructor(client: BaseClient) {
7
+ this._client = client;
8
+ }
9
+ }
@@ -0,0 +1,72 @@
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
+ }