@introspection-sdk/introspection-node 0.5.0 → 0.5.2

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 (47) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/http.d.ts +6 -19
  3. package/dist/http.d.ts.map +1 -1
  4. package/dist/http.js +14 -139
  5. package/dist/http.js.map +1 -1
  6. package/dist/pagination.d.ts +7 -0
  7. package/dist/pagination.d.ts.map +1 -0
  8. package/dist/pagination.js +7 -0
  9. package/dist/pagination.js.map +1 -0
  10. package/dist/resources/experiments.d.ts +6 -4
  11. package/dist/resources/experiments.d.ts.map +1 -1
  12. package/dist/resources/experiments.js +11 -15
  13. package/dist/resources/experiments.js.map +1 -1
  14. package/dist/resources/recipes.d.ts +6 -4
  15. package/dist/resources/recipes.d.ts.map +1 -1
  16. package/dist/resources/recipes.js +11 -15
  17. package/dist/resources/recipes.js.map +1 -1
  18. package/dist/resources/runtimes.d.ts +6 -4
  19. package/dist/resources/runtimes.d.ts.map +1 -1
  20. package/dist/resources/runtimes.js +11 -15
  21. package/dist/resources/runtimes.js.map +1 -1
  22. package/dist/runner-resources/conversations.d.ts +14 -10
  23. package/dist/runner-resources/conversations.d.ts.map +1 -1
  24. package/dist/runner-resources/conversations.js +24 -30
  25. package/dist/runner-resources/conversations.js.map +1 -1
  26. package/dist/runner-resources/files.d.ts +14 -11
  27. package/dist/runner-resources/files.d.ts.map +1 -1
  28. package/dist/runner-resources/files.js +31 -36
  29. package/dist/runner-resources/files.js.map +1 -1
  30. package/dist/runner-resources/tasks.d.ts +6 -4
  31. package/dist/runner-resources/tasks.d.ts.map +1 -1
  32. package/dist/runner-resources/tasks.js +11 -15
  33. package/dist/runner-resources/tasks.js.map +1 -1
  34. package/dist/streaming.d.ts +4 -5
  35. package/dist/streaming.d.ts.map +1 -1
  36. package/dist/streaming.js +4 -64
  37. package/dist/streaming.js.map +1 -1
  38. package/package.json +16 -15
  39. package/src/http.ts +15 -145
  40. package/src/pagination.ts +10 -0
  41. package/src/resources/experiments.ts +15 -14
  42. package/src/resources/recipes.ts +15 -14
  43. package/src/resources/runtimes.ts +15 -14
  44. package/src/runner-resources/conversations.ts +37 -33
  45. package/src/runner-resources/files.ts +52 -37
  46. package/src/runner-resources/tasks.ts +15 -14
  47. package/src/streaming.ts +4 -55
package/src/http.ts CHANGED
@@ -1,8 +1,4 @@
1
- import {
2
- IntrospectionAPIError,
3
- NetworkError,
4
- apiErrorFromResponse,
5
- } from "@introspection-sdk/types";
1
+ import { BaseHttpClient } from "@introspection-sdk/http";
6
2
 
7
3
  export interface ResolvedApiConfig {
8
4
  /**
@@ -17,147 +13,21 @@ export interface ResolvedApiConfig {
17
13
  fetch?: typeof fetch;
18
14
  }
19
15
 
20
- function joinUrl(base: string, path: string): string {
21
- const b = base.replace(/\/+$/, "");
22
- const p = path.startsWith("/") ? path : `/${path}`;
23
- return `${b}${p}`;
24
- }
25
-
26
- function buildQuery(params?: Record<string, unknown>): string {
27
- if (!params) return "";
28
- const sp = new URLSearchParams();
29
- for (const [k, v] of Object.entries(params)) {
30
- if (v === undefined || v === null) continue;
31
- if (Array.isArray(v)) {
32
- for (const item of v) sp.append(k, String(item));
33
- } else {
34
- sp.set(k, String(v));
35
- }
36
- }
37
- const q = sp.toString();
38
- return q ? `?${q}` : "";
39
- }
40
-
41
16
  /**
42
- * Thin HTTP wrapper used by both the CP-bound IntrospectionClient and
43
- * each DP-bound Runner. Has no opinion on which one it is — the caller
44
- * picks the base URL and the token.
17
+ * Bearer-token HTTP client used by both the CP-bound IntrospectionClient
18
+ * and each DP-bound Runner. The request/response machinery lives in the
19
+ * shared {@link BaseHttpClient}; this only pins the auth strategy —
20
+ * `Authorization: Bearer <token>` on every request, no cookie credentials.
45
21
  */
46
- export class HttpClient {
47
- private readonly fetchImpl: typeof fetch;
48
-
49
- constructor(private readonly cfg: ResolvedApiConfig) {
50
- this.fetchImpl = cfg.fetch ?? globalThis.fetch;
51
- if (!this.fetchImpl) {
52
- throw new Error(
53
- "global fetch is unavailable; pass `advanced.fetch` or run on Node 18+",
54
- );
55
- }
56
- }
57
-
58
- private headers(extra?: Record<string, string>): Record<string, string> {
59
- return {
60
- Authorization: `Bearer ${this.cfg.token}`,
61
- ...(this.cfg.additionalHeaders ?? {}),
62
- ...(extra ?? {}),
63
- };
64
- }
65
-
66
- async request<T>(opts: {
67
- method: string;
68
- path: string;
69
- query?: Record<string, unknown>;
70
- body?: unknown;
71
- headers?: Record<string, string>;
72
- expect?: "json" | "empty" | "bytes" | "stream";
73
- }): Promise<T> {
74
- const url = joinUrl(this.cfg.apiUrl, opts.path) + buildQuery(opts.query);
75
- let body: BodyInit | undefined;
76
- const headers = this.headers(opts.headers);
77
- if (opts.body instanceof FormData) {
78
- body = opts.body;
79
- // let fetch set the multipart boundary
80
- } else if (opts.body !== undefined) {
81
- headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
82
- body = JSON.stringify(opts.body);
83
- }
84
- let res: Response;
85
- try {
86
- res = await this.fetchImpl(url, {
87
- method: opts.method,
88
- headers,
89
- body,
90
- });
91
- } catch (err) {
92
- throw new NetworkError({
93
- message: err instanceof Error ? err.message : "network request failed",
94
- code: null,
95
- requestId: null,
96
- body: err,
97
- });
98
- }
99
- if (!res.ok) throw await toApiError(res);
100
- const expect = opts.expect ?? "json";
101
- if (expect === "empty") return undefined as T;
102
- if (expect === "bytes") return new Uint8Array(await res.arrayBuffer()) as T;
103
- if (expect === "stream") return res.body as T;
104
- return (await res.json()) as T;
105
- }
106
-
107
- async stream(opts: {
108
- path: string;
109
- query?: Record<string, unknown>;
110
- }): Promise<Response> {
111
- const url = joinUrl(this.cfg.apiUrl, opts.path) + buildQuery(opts.query);
112
- let res: Response;
113
- try {
114
- res = await this.fetchImpl(url, {
115
- method: "GET",
116
- headers: this.headers({ Accept: "text/event-stream" }),
117
- });
118
- } catch (err) {
119
- throw new NetworkError({
120
- message: err instanceof Error ? err.message : "network request failed",
121
- code: null,
122
- requestId: null,
123
- body: err,
124
- });
125
- }
126
- if (!res.ok) throw await toApiError(res);
127
- return res;
128
- }
129
- }
130
-
131
- async function toApiError(res: Response): Promise<IntrospectionAPIError> {
132
- let body: unknown = undefined;
133
- let message = `HTTP ${res.status}`;
134
- let code: string | null = null;
135
- const ct = res.headers.get("content-type") ?? "";
136
- if (ct.includes("json")) {
137
- body = await res.json().catch(() => undefined);
138
- if (body && typeof body === "object") {
139
- const obj = body as Record<string, unknown>;
140
- if (typeof obj.detail === "string") message = obj.detail;
141
- if (typeof obj.code === "string") code = obj.code;
142
- if (typeof obj.message === "string" && message === `HTTP ${res.status}`) {
143
- message = obj.message;
144
- }
145
- }
146
- } else {
147
- body = await res.text().catch(() => undefined);
148
- }
149
- const retryAfterHeader = res.headers.get("retry-after");
150
- let retryAfter: number | null = null;
151
- if (retryAfterHeader) {
152
- const n = Number(retryAfterHeader);
153
- if (Number.isFinite(n)) retryAfter = n;
22
+ export class HttpClient extends BaseHttpClient {
23
+ constructor(cfg: ResolvedApiConfig) {
24
+ super({
25
+ apiUrl: cfg.apiUrl,
26
+ additionalHeaders: cfg.additionalHeaders,
27
+ fetch: cfg.fetch,
28
+ transport: {
29
+ authHeaders: () => ({ Authorization: `Bearer ${cfg.token}` }),
30
+ },
31
+ });
154
32
  }
155
- return apiErrorFromResponse({
156
- status: res.status,
157
- message,
158
- code,
159
- requestId: res.headers.get("x-request-id"),
160
- body,
161
- retryAfter,
162
- });
163
33
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Cursor pagination now lives in the shared `@introspection-sdk/http`
3
+ * core so the browser and Node SDKs share one `Paginator`. Re-exported
4
+ * here to keep the existing `./pagination.js` import path stable.
5
+ */
6
+ export {
7
+ Paginator,
8
+ cursorPaginate,
9
+ type PageSource,
10
+ } from "@introspection-sdk/http";
@@ -11,6 +11,7 @@ import type {
11
11
  } from "@introspection-sdk/types";
12
12
  import type { HttpClient } from "../http.js";
13
13
  import type { IntrospectionClient } from "../client.js";
14
+ import { Paginator, cursorPaginate } from "../pagination.js";
14
15
  import { Runner, type RunnerSource } from "../runner.js";
15
16
 
16
17
  interface ExperimentRunRequestBody {
@@ -37,21 +38,21 @@ export class ExperimentsApi {
37
38
  ) {}
38
39
 
39
40
  /**
40
- * Iterate experiments matching `params`. Pages are fetched lazily as
41
- * the iterator is consumed (`limit` sets the page size, `next` the
42
- * starting cursor); stop early to stop fetching.
41
+ * List experiments matching `params`. `await` the result for the first
42
+ * page, or `for await` it to stream every experiment across pages
43
+ * (fetched lazily — `limit` sets the page size, `next` the starting
44
+ * cursor; stop early to stop fetching).
43
45
  */
44
- async *list(params: ExperimentListParams): AsyncIterable<Experiment> {
45
- let next: string | undefined = params.next;
46
- do {
47
- const page = await this.http.request<Paginated<Experiment>>({
48
- method: "GET",
49
- path: "/v1/experiments",
50
- query: { ...params, next } as unknown as Record<string, unknown>,
51
- });
52
- for (const r of page.records) yield r;
53
- next = page.next ?? undefined;
54
- } while (next);
46
+ list(params: ExperimentListParams): Paginator<Experiment> {
47
+ return cursorPaginate(
48
+ (next) =>
49
+ this.http.request<Paginated<Experiment>>({
50
+ method: "GET",
51
+ path: "/v1/experiments",
52
+ query: { ...params, next } as unknown as Record<string, unknown>,
53
+ }),
54
+ params.next,
55
+ );
55
56
  }
56
57
 
57
58
  get(id: Uuid): Promise<Experiment> {
@@ -7,6 +7,7 @@ import type {
7
7
  Uuid,
8
8
  } from "@introspection-sdk/types";
9
9
  import type { HttpClient } from "../http.js";
10
+ import { Paginator, cursorPaginate } from "../pagination.js";
10
11
 
11
12
  /**
12
13
  * Programmatic CRUD for `/v1/recipes` on the CP.
@@ -19,21 +20,21 @@ export class RecipesApi {
19
20
  constructor(private readonly http: HttpClient) {}
20
21
 
21
22
  /**
22
- * Iterate recipes matching `params`. Pages are fetched lazily as the
23
- * iterator is consumed (`limit` sets the page size, `next` the
24
- * starting cursor); stop early to stop fetching.
23
+ * List recipes matching `params`. `await` the result for the first
24
+ * page, or `for await` it to stream every recipe across pages (fetched
25
+ * lazily — `limit` sets the page size, `next` the starting cursor; stop
26
+ * early to stop fetching).
25
27
  */
26
- async *list(params: RecipeListParams): AsyncIterable<Recipe> {
27
- let next: string | undefined = params.next;
28
- do {
29
- const page = await this.http.request<Paginated<Recipe>>({
30
- method: "GET",
31
- path: "/v1/recipes",
32
- query: { ...params, next } as unknown as Record<string, unknown>,
33
- });
34
- for (const r of page.records) yield r;
35
- next = page.next ?? undefined;
36
- } while (next);
28
+ list(params: RecipeListParams): Paginator<Recipe> {
29
+ return cursorPaginate(
30
+ (next) =>
31
+ this.http.request<Paginated<Recipe>>({
32
+ method: "GET",
33
+ path: "/v1/recipes",
34
+ query: { ...params, next } as unknown as Record<string, unknown>,
35
+ }),
36
+ params.next,
37
+ );
37
38
  }
38
39
 
39
40
  get(id: Uuid): Promise<Recipe> {
@@ -12,6 +12,7 @@ import type {
12
12
  import { NotFoundError } from "@introspection-sdk/types";
13
13
  import type { HttpClient } from "../http.js";
14
14
  import type { IntrospectionClient } from "../client.js";
15
+ import { Paginator, cursorPaginate } from "../pagination.js";
15
16
  import { Runner, type RunnerSource } from "../runner.js";
16
17
 
17
18
  const UUID_RE =
@@ -51,21 +52,21 @@ export class RuntimesApi {
51
52
  ) {}
52
53
 
53
54
  /**
54
- * Iterate runtimes matching `params`. Pages are fetched lazily as the
55
- * iterator is consumed (`limit` sets the page size, `next` the
56
- * starting cursor); stop early to stop fetching.
55
+ * List runtimes matching `params`. `await` the result for the first
56
+ * page, or `for await` it to stream every runtime across pages (fetched
57
+ * lazily — `limit` sets the page size, `next` the starting cursor; stop
58
+ * early to stop fetching).
57
59
  */
58
- async *list(params: RuntimeListParams): AsyncIterable<Runtime> {
59
- let next: string | undefined = params.next;
60
- do {
61
- const page = await this.http.request<Paginated<Runtime>>({
62
- method: "GET",
63
- path: "/v1/runtimes",
64
- query: { ...params, next } as unknown as Record<string, unknown>,
65
- });
66
- for (const r of page.records) yield r;
67
- next = page.next ?? undefined;
68
- } while (next);
60
+ list(params: RuntimeListParams): Paginator<Runtime> {
61
+ return cursorPaginate(
62
+ (next) =>
63
+ this.http.request<Paginated<Runtime>>({
64
+ method: "GET",
65
+ path: "/v1/runtimes",
66
+ query: { ...params, next } as unknown as Record<string, unknown>,
67
+ }),
68
+ params.next,
69
+ );
69
70
  }
70
71
 
71
72
  get(id: Uuid, params: { project_id?: Uuid }): Promise<Runtime> {
@@ -11,6 +11,7 @@ import type {
11
11
  ToolCallResponsePart,
12
12
  } from "@introspection-sdk/types";
13
13
  import { HttpClient } from "../http.js";
14
+ import { Paginator, cursorPaginate } from "../pagination.js";
14
15
 
15
16
  /** Includes requested when building a {@link ConversationResponse}. */
16
17
  const RESPONSE_INCLUDES: ConversationItemInclude[] = [
@@ -30,29 +31,34 @@ export class ConversationItemsApi {
30
31
  constructor(private readonly http: HttpClient) {}
31
32
 
32
33
  /**
33
- * Iterate items of a conversation. Pages are fetched lazily as the
34
- * iterator is consumed (`limit` sets the page size, `after` the
35
- * starting cursor); stop early to stop fetching. Pass
36
- * `order: "asc"` to walk the transcript from the start.
34
+ * List items of a conversation. `await` the result for the first page
35
+ * (an OpenAI-style {@link ConversationItemList} envelope), or
36
+ * `for await` it to stream every item across pages (fetched lazily —
37
+ * `limit` sets the page size, `after` the starting cursor; stop early to
38
+ * stop fetching). Pass `order: "asc"` to walk the transcript from the
39
+ * start.
37
40
  *
38
41
  * Items carry the turn-local delta in `input_messages` — only the
39
42
  * messages new to that turn. Use `get()` for the full input history.
40
43
  */
41
- async *list(
44
+ list(
42
45
  conversationId: string,
43
46
  params?: ConversationItemListParams,
44
- ): AsyncIterable<ConversationItem> {
45
- let after: string | undefined = params?.after;
46
- for (;;) {
47
- const page = await this.http.request<ConversationItemList>({
48
- method: "GET",
49
- path: `/v1/conversations/${encodeURIComponent(conversationId)}/items`,
50
- query: { ...params, after } as Record<string, unknown>,
51
- });
52
- for (const item of page.data) yield item;
53
- if (!page.has_more || page.last_id === null) return;
54
- after = page.last_id;
55
- }
47
+ ): Paginator<ConversationItem, ConversationItemList> {
48
+ return new Paginator(
49
+ {
50
+ fetch: (after) =>
51
+ this.http.request<ConversationItemList>({
52
+ method: "GET",
53
+ path: `/v1/conversations/${encodeURIComponent(conversationId)}/items`,
54
+ query: { ...params, after } as Record<string, unknown>,
55
+ }),
56
+ items: (page) => page.data,
57
+ next: (page) =>
58
+ page.has_more && page.last_id !== null ? page.last_id : undefined,
59
+ },
60
+ params?.after,
61
+ );
56
62
  }
57
63
 
58
64
  /**
@@ -90,23 +96,21 @@ export class ConversationsApi {
90
96
  }
91
97
 
92
98
  /**
93
- * Iterate conversation summaries matching `params`. Pages are fetched
94
- * lazily as the iterator is consumed (`limit` sets the page size,
95
- * `next` the starting cursor); stop early to stop fetching.
99
+ * List conversation summaries matching `params`. `await` the result for
100
+ * the first page, or `for await` it to stream every summary across
101
+ * pages (fetched lazily — `limit` sets the page size, `next` the
102
+ * starting cursor; stop early to stop fetching).
96
103
  */
97
- async *list(
98
- params?: ConversationListParams,
99
- ): AsyncIterable<ConversationSummary> {
100
- let next: string | undefined = params?.next;
101
- do {
102
- const page = await this.http.request<Paginated<ConversationSummary>>({
103
- method: "GET",
104
- path: "/v1/conversations",
105
- query: { ...params, next } as Record<string, unknown>,
106
- });
107
- for (const c of page.records) yield c;
108
- next = page.next ?? undefined;
109
- } while (next);
104
+ list(params?: ConversationListParams): Paginator<ConversationSummary> {
105
+ return cursorPaginate(
106
+ (next) =>
107
+ this.http.request<Paginated<ConversationSummary>>({
108
+ method: "GET",
109
+ path: "/v1/conversations",
110
+ query: { ...params, next } as Record<string, unknown>,
111
+ }),
112
+ params?.next,
113
+ );
110
114
  }
111
115
 
112
116
  /**
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  File as FileResource,
3
+ FileCreateOptions,
3
4
  FileCreateTextParams,
4
5
  FileListParams,
5
6
  FileType,
@@ -8,6 +9,7 @@ import type {
8
9
  Paginated,
9
10
  } from "@introspection-sdk/types";
10
11
  import { HttpClient } from "../http.js";
12
+ import { Paginator, cursorPaginate } from "../pagination.js";
11
13
 
12
14
  export type FileUploadBody =
13
15
  | { file: Blob; name?: string; file_type?: FileType }
@@ -22,23 +24,20 @@ export class FileVersionsApi {
22
24
  constructor(private readonly http: HttpClient) {}
23
25
 
24
26
  /**
25
- * Iterate versions of a file. Pages are fetched lazily as the
26
- * iterator is consumed; stop early to stop fetching.
27
+ * List versions of a file. `await` the result for the first page, or
28
+ * `for await` it to stream every version across pages (fetched lazily;
29
+ * stop early to stop fetching).
27
30
  */
28
- async *list(
29
- fileId: string,
30
- params?: ListParams,
31
- ): AsyncIterable<FileResource> {
32
- let next: string | undefined = params?.next;
33
- do {
34
- const page = await this.http.request<Paginated<FileResource>>({
35
- method: "GET",
36
- path: `/v1/files/${encodeURIComponent(fileId)}/versions`,
37
- query: { ...params, next } as Record<string, unknown>,
38
- });
39
- for (const f of page.records) yield f;
40
- next = page.next ?? undefined;
41
- } while (next);
31
+ list(fileId: string, params?: ListParams): Paginator<FileResource> {
32
+ return cursorPaginate(
33
+ (next) =>
34
+ this.http.request<Paginated<FileResource>>({
35
+ method: "GET",
36
+ path: `/v1/files/${encodeURIComponent(fileId)}/versions`,
37
+ query: { ...params, next } as Record<string, unknown>,
38
+ }),
39
+ params?.next,
40
+ );
42
41
  }
43
42
 
44
43
  get(fileId: string, versionId: string): Promise<FileResource> {
@@ -48,8 +47,12 @@ export class FileVersionsApi {
48
47
  });
49
48
  }
50
49
 
51
- create(fileId: string, body: FileUploadBody): Promise<FileResource> {
52
- const form = toFormData(body);
50
+ create(
51
+ fileId: string,
52
+ body: FileUploadBody,
53
+ options?: FileCreateOptions,
54
+ ): Promise<FileResource> {
55
+ const form = toFormData(body, options);
53
56
  return this.http.request<FileResource>({
54
57
  method: "POST",
55
58
  path: `/v1/files/${encodeURIComponent(fileId)}/versions`,
@@ -66,25 +69,28 @@ export class FilesApi {
66
69
  }
67
70
 
68
71
  /**
69
- * Iterate files matching `params`. Pages are fetched lazily as the
70
- * iterator is consumed (`limit` sets the page size, `next` the
71
- * starting cursor); stop early to stop fetching.
72
+ * List files matching `params`. `await` the result for the first page,
73
+ * or `for await` it to stream every file across pages (fetched lazily —
74
+ * `limit` sets the page size, `next` the starting cursor; stop early to
75
+ * stop fetching).
72
76
  */
73
- async *list(params?: FileListParams): AsyncIterable<FileResource> {
74
- let next: string | undefined = params?.next;
75
- do {
76
- const page = await this.http.request<Paginated<FileResource>>({
77
- method: "GET",
78
- path: "/v1/files",
79
- query: { ...params, next } as Record<string, unknown>,
80
- });
81
- for (const f of page.records) yield f;
82
- next = page.next ?? undefined;
83
- } while (next);
77
+ list(params?: FileListParams): Paginator<FileResource> {
78
+ return cursorPaginate(
79
+ (next) =>
80
+ this.http.request<Paginated<FileResource>>({
81
+ method: "GET",
82
+ path: "/v1/files",
83
+ query: { ...params, next } as Record<string, unknown>,
84
+ }),
85
+ params?.next,
86
+ );
84
87
  }
85
88
 
86
- upload(body: FileUploadBody): Promise<FileResource> {
87
- const form = toFormData(body);
89
+ upload(
90
+ body: FileUploadBody,
91
+ options?: FileCreateOptions,
92
+ ): Promise<FileResource> {
93
+ const form = toFormData(body, options);
88
94
  return this.http.request<FileResource>({
89
95
  method: "POST",
90
96
  path: "/v1/files",
@@ -92,11 +98,16 @@ export class FilesApi {
92
98
  });
93
99
  }
94
100
 
95
- createText(body: FileCreateTextParams): Promise<FileResource> {
101
+ createText(
102
+ body: FileCreateTextParams,
103
+ options?: FileCreateOptions,
104
+ ): Promise<FileResource> {
96
105
  return this.http.request<FileResource>({
97
106
  method: "POST",
98
107
  path: "/v1/files",
99
- body,
108
+ body: options?.visibility
109
+ ? { ...body, visibility: options.visibility }
110
+ : body,
100
111
  });
101
112
  }
102
113
 
@@ -140,7 +151,10 @@ export class FilesApi {
140
151
  }
141
152
  }
142
153
 
143
- function toFormData(body: FileUploadBody): FormData {
154
+ function toFormData(
155
+ body: FileUploadBody,
156
+ options?: FileCreateOptions,
157
+ ): FormData {
144
158
  const fd = new FormData();
145
159
  if ("file" in body && body.file instanceof Blob) {
146
160
  fd.append("file", body.file, body.name);
@@ -160,5 +174,6 @@ function toFormData(body: FileUploadBody): FormData {
160
174
  }
161
175
  if (body.name) fd.append("name", body.name);
162
176
  if (body.file_type) fd.append("file_type", body.file_type);
177
+ if (options?.visibility) fd.append("visibility", options.visibility);
163
178
  return fd;
164
179
  }
@@ -12,6 +12,7 @@ import type {
12
12
  TaskUpdateParams,
13
13
  } from "@introspection-sdk/types";
14
14
  import { HttpClient } from "../http.js";
15
+ import { Paginator, cursorPaginate } from "../pagination.js";
15
16
  import { parseSse } from "../streaming.js";
16
17
 
17
18
  export interface StartParams extends TaskCreateParams {
@@ -85,21 +86,21 @@ export class TasksApi {
85
86
  }
86
87
 
87
88
  /**
88
- * Iterate tasks matching `params`. Pages are fetched lazily as the
89
- * iterator is consumed (`limit` sets the page size, `next` the
90
- * starting cursor); stop early to stop fetching.
89
+ * List tasks matching `params`. `await` the result for the first page,
90
+ * or `for await` it to stream every task across pages (fetched lazily —
91
+ * `limit` sets the page size, `next` the starting cursor; stop early to
92
+ * stop fetching).
91
93
  */
92
- async *list(params?: TaskListParams): AsyncIterable<Task> {
93
- let next: string | undefined = params?.next;
94
- do {
95
- const page = await this.http.request<Paginated<Task>>({
96
- method: "GET",
97
- path: "/v1/tasks",
98
- query: { ...params, next } as Record<string, unknown>,
99
- });
100
- for (const t of page.records) yield t;
101
- next = page.next ?? undefined;
102
- } while (next);
94
+ list(params?: TaskListParams): Paginator<Task> {
95
+ return cursorPaginate(
96
+ (next) =>
97
+ this.http.request<Paginated<Task>>({
98
+ method: "GET",
99
+ path: "/v1/tasks",
100
+ query: { ...params, next } as Record<string, unknown>,
101
+ }),
102
+ params?.next,
103
+ );
103
104
  }
104
105
 
105
106
  create(body: TaskCreateParams): Promise<TaskCreateResponse> {