@superblocksteam/sdk-api 2.0.157 → 2.0.158

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 (57) hide show
  1. package/dist/errors.d.ts +6 -6
  2. package/dist/errors.d.ts.map +1 -1
  3. package/dist/errors.js +23 -2
  4. package/dist/errors.js.map +1 -1
  5. package/dist/integrations/base/decode-worker-binary-response.d.ts +4 -0
  6. package/dist/integrations/base/decode-worker-binary-response.d.ts.map +1 -0
  7. package/dist/integrations/base/decode-worker-binary-response.js +49 -0
  8. package/dist/integrations/base/decode-worker-binary-response.js.map +1 -0
  9. package/dist/integrations/base/decode-worker-binary-response.test.d.ts +2 -0
  10. package/dist/integrations/base/decode-worker-binary-response.test.d.ts.map +1 -0
  11. package/dist/integrations/base/decode-worker-binary-response.test.js +81 -0
  12. package/dist/integrations/base/decode-worker-binary-response.test.js.map +1 -0
  13. package/dist/integrations/base/rest-api-client-base.d.ts +0 -27
  14. package/dist/integrations/base/rest-api-client-base.d.ts.map +1 -1
  15. package/dist/integrations/base/rest-api-client-base.js +23 -46
  16. package/dist/integrations/base/rest-api-client-base.js.map +1 -1
  17. package/dist/integrations/base/rest-api-integration-client.d.ts +7 -19
  18. package/dist/integrations/base/rest-api-integration-client.d.ts.map +1 -1
  19. package/dist/integrations/base/rest-api-integration-client.js +2 -29
  20. package/dist/integrations/base/rest-api-integration-client.js.map +1 -1
  21. package/dist/integrations/base/types.d.ts +36 -43
  22. package/dist/integrations/base/types.d.ts.map +1 -1
  23. package/dist/integrations/base/types.js +1 -26
  24. package/dist/integrations/base/types.js.map +1 -1
  25. package/dist/integrations/documentation-resolver.test.js +11 -5
  26. package/dist/integrations/documentation-resolver.test.js.map +1 -1
  27. package/dist/integrations/restapiintegration/client.test.d.ts +0 -10
  28. package/dist/integrations/restapiintegration/client.test.d.ts.map +1 -1
  29. package/dist/integrations/restapiintegration/client.test.js +146 -69
  30. package/dist/integrations/restapiintegration/client.test.js.map +1 -1
  31. package/dist/integrations/slack/client.test.js +26 -1
  32. package/dist/integrations/slack/client.test.js.map +1 -1
  33. package/package.json +2 -2
  34. package/src/errors.ts +33 -5
  35. package/src/integrations/anthropic/README.md +7 -0
  36. package/src/integrations/base/decode-worker-binary-response.test.ts +107 -0
  37. package/src/integrations/base/decode-worker-binary-response.ts +62 -0
  38. package/src/integrations/base/rest-api-client-base.ts +28 -56
  39. package/src/integrations/base/rest-api-integration-client.ts +14 -33
  40. package/src/integrations/base/types.ts +41 -45
  41. package/src/integrations/bigquery/README.md +1 -0
  42. package/src/integrations/box/README.md +3 -0
  43. package/src/integrations/cohere/README.md +7 -0
  44. package/src/integrations/documentation-resolver.test.ts +13 -6
  45. package/src/integrations/fireworks/README.md +7 -0
  46. package/src/integrations/gemini/README.md +8 -0
  47. package/src/integrations/groq/README.md +7 -0
  48. package/src/integrations/mistral/README.md +7 -0
  49. package/src/integrations/openai_v2/README.md +7 -0
  50. package/src/integrations/perplexity/README.md +7 -0
  51. package/src/integrations/restapiintegration/client.test.ts +207 -86
  52. package/src/integrations/restapiintegration/docs.manifest.json +5 -1
  53. package/src/integrations/restapiintegration/overlays/response-types-binary.md +51 -0
  54. package/src/integrations/s3/README.md +1 -0
  55. package/src/integrations/slack/client.test.ts +36 -1
  56. package/src/integrations/snowflakecortex/README.md +8 -0
  57. package/src/integrations/stabilityai/README.md +7 -0
@@ -0,0 +1,107 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { RestApiValidationError } from "../../errors.js";
4
+ import { decodeWorkerBinaryResponse } from "./decode-worker-binary-response.js";
5
+
6
+ describe("decodeWorkerBinaryResponse", () => {
7
+ it("copies a Uint8Array into a plain Uint8Array with the same bytes", () => {
8
+ const source = new Uint8Array([1, 2, 255]);
9
+
10
+ const result = decodeWorkerBinaryResponse(source);
11
+
12
+ expect(result).toBeInstanceOf(Uint8Array);
13
+ expect(result.constructor).toBe(Uint8Array);
14
+ expect(Array.from(result)).toEqual([1, 2, 255]);
15
+ source[0] = 9;
16
+ expect(result[0]).toBe(1);
17
+ });
18
+
19
+ it("converts worker Buffer JSON into a plain Uint8Array", () => {
20
+ const result = decodeWorkerBinaryResponse({
21
+ type: "Buffer",
22
+ data: [37, 80, 68, 70],
23
+ });
24
+
25
+ expect(result.constructor).toBe(Uint8Array);
26
+ expect(Array.from(result)).toEqual([37, 80, 68, 70]);
27
+ });
28
+
29
+ it("converts empty Buffer JSON into a zero-length Uint8Array", () => {
30
+ const result = decodeWorkerBinaryResponse({ type: "Buffer", data: [] });
31
+
32
+ expect(result).toBeInstanceOf(Uint8Array);
33
+ expect(result.byteLength).toBe(0);
34
+ });
35
+
36
+ it("rejects a missing discriminant", () => {
37
+ expect(() => decodeWorkerBinaryResponse({ data: [1] })).toThrow(
38
+ RestApiValidationError,
39
+ );
40
+ });
41
+
42
+ it("rejects a non-Buffer discriminant", () => {
43
+ expect(() =>
44
+ decodeWorkerBinaryResponse({ type: "Array", data: [1] }),
45
+ ).toThrow(RestApiValidationError);
46
+ });
47
+
48
+ it("rejects a non-array data field", () => {
49
+ expect(() =>
50
+ decodeWorkerBinaryResponse({ type: "Buffer", data: "00ff" }),
51
+ ).toThrow(RestApiValidationError);
52
+ });
53
+
54
+ it("rejects a byte outside 0 through 255", () => {
55
+ expect(() =>
56
+ decodeWorkerBinaryResponse({ type: "Buffer", data: [256] }),
57
+ ).toThrow(RestApiValidationError);
58
+ });
59
+
60
+ it("rejects a negative byte", () => {
61
+ expect(() =>
62
+ decodeWorkerBinaryResponse({ type: "Buffer", data: [-1] }),
63
+ ).toThrow(RestApiValidationError);
64
+ });
65
+
66
+ it("reports the index of a non-integer byte", () => {
67
+ try {
68
+ decodeWorkerBinaryResponse({ type: "Buffer", data: [1, 1.5] });
69
+ } catch (error) {
70
+ expect(error).toBeInstanceOf(RestApiValidationError);
71
+ if (!(error instanceof RestApiValidationError)) {
72
+ throw error;
73
+ }
74
+ expect(error.details.zodError.issues).toEqual([
75
+ expect.objectContaining({ path: ["data", 1] }),
76
+ ]);
77
+ return;
78
+ }
79
+ throw new Error("Expected binary response validation to fail");
80
+ });
81
+
82
+ it("redacts malformed binary data from validation errors", () => {
83
+ const data = Array.from({ length: 4096 }, () => 0);
84
+ data[4095] = 256;
85
+
86
+ try {
87
+ decodeWorkerBinaryResponse({ type: "Buffer", data });
88
+ } catch (error) {
89
+ expect(error).toBeInstanceOf(RestApiValidationError);
90
+ if (!(error instanceof RestApiValidationError)) {
91
+ throw error;
92
+ }
93
+ expect(error.details.data).toEqual({
94
+ dataType: "binary",
95
+ redacted: true,
96
+ });
97
+ return;
98
+ }
99
+ throw new Error("Expected malformed binary response to fail");
100
+ });
101
+
102
+ it("rejects a string payload", () => {
103
+ expect(() => decodeWorkerBinaryResponse("not-binary")).toThrow(
104
+ RestApiValidationError,
105
+ );
106
+ });
107
+ });
@@ -0,0 +1,62 @@
1
+ import { z } from "zod";
2
+
3
+ import {
4
+ REDACTED_BINARY_RESPONSE_DATA,
5
+ RestApiValidationError,
6
+ } from "../../errors.js";
7
+
8
+ export { REDACTED_BINARY_RESPONSE_DATA };
9
+
10
+ const workerBinaryResponseSchema = z.union([
11
+ z.instanceof(Uint8Array).transform((value) => new Uint8Array(value)),
12
+ z
13
+ .object({
14
+ data: z.unknown(),
15
+ type: z.literal("Buffer"),
16
+ })
17
+ .transform((value, context) => {
18
+ if (!Array.isArray(value.data)) {
19
+ context.addIssue({
20
+ code: z.ZodIssueCode.custom,
21
+ message: "data must be an array of bytes",
22
+ path: ["data"],
23
+ });
24
+ return z.NEVER;
25
+ }
26
+
27
+ const bytes = new Uint8Array(value.data.length);
28
+ for (let index = 0; index < value.data.length; index += 1) {
29
+ const item = value.data[index];
30
+ if (
31
+ typeof item !== "number" ||
32
+ !Number.isInteger(item) ||
33
+ item < 0 ||
34
+ item > 255
35
+ ) {
36
+ context.addIssue({
37
+ code: z.ZodIssueCode.custom,
38
+ message: "byte must be an integer from 0 through 255",
39
+ path: ["data", index],
40
+ });
41
+ return z.NEVER;
42
+ }
43
+ bytes[index] = item;
44
+ }
45
+
46
+ return bytes;
47
+ }),
48
+ ]);
49
+
50
+ export function decodeWorkerBinaryResponse(value: unknown): Uint8Array {
51
+ const result = workerBinaryResponseSchema.safeParse(value);
52
+ if (!result.success) {
53
+ throw new RestApiValidationError(
54
+ `Binary response is malformed: ${result.error.message}`,
55
+ {
56
+ data: REDACTED_BINARY_RESPONSE_DATA,
57
+ zodError: result.error,
58
+ },
59
+ );
60
+ }
61
+ return result.data;
62
+ }
@@ -1,13 +1,3 @@
1
- /**
2
- * Abstract base for REST API Integration clients.
3
- *
4
- * Owns the shared infrastructure that every REST-API-backed integration
5
- * needs: config storage, parameter helpers, body validation, request
6
- * construction, and query execution. Subclasses add their own
7
- * `apiRequest` with whatever response-handling strategy they need
8
- * (e.g. direct Zod validation, discriminated-union wrapping).
9
- */
10
-
11
1
  import type { PartialMessage } from "@bufbuild/protobuf";
12
2
  import { z } from "zod";
13
3
 
@@ -17,18 +7,12 @@ import type { Plugin as RestApiIntegrationPlugin } from "@superblocksteam/types/
17
7
  import { RestApiValidationError } from "../../errors.js";
18
8
  import type { QueryExecutor, TraceMetadata } from "../registry.js";
19
9
  import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
10
+ import { decodeWorkerBinaryResponse } from "./decode-worker-binary-response.js";
20
11
  import { REST_API_RESPONSE_TYPES } from "./types.js";
21
12
  import type { ApiRequestOptions } from "./types.js";
22
13
 
23
14
  export type RestApiRequest = PartialMessage<RestApiIntegrationPlugin>;
24
15
 
25
- /**
26
- * Shared base for all REST API Integration clients.
27
- *
28
- * Provides config fields, parameter helpers, body validation, request
29
- * building, and query execution. Does NOT define `apiRequest` — each
30
- * concrete subclass supplies its own return-type contract.
31
- */
32
16
  export abstract class RestApiClientBase implements IntegrationClientImpl {
33
17
  readonly name: string;
34
18
  readonly pluginId: string;
@@ -53,23 +37,11 @@ export abstract class RestApiClientBase implements IntegrationClientImpl {
53
37
  };
54
38
  }
55
39
 
56
- /**
57
- * Validate the request body, build the proto request, and execute it.
58
- *
59
- * Returns the raw (unvalidated) response from the orchestrator.
60
- * Subclasses call this, then apply their own response handling.
61
- *
62
- * @param options - Request configuration (see {@link ApiRequestOptions})
63
- * @param bodySchema - Optional Zod schema for body validation
64
- * @param metadata - Optional trace metadata for observability
65
- * @returns Raw response from the orchestrator
66
- */
67
40
  protected async executeApiRequest<TBody>(
68
41
  options: ApiRequestOptions<TBody>,
69
42
  bodySchema?: z.ZodSchema<TBody>,
70
43
  metadata?: TraceMetadata,
71
44
  ): Promise<unknown> {
72
- // Validate request body if both body and schema are present.
73
45
  if (options.body !== undefined && bodySchema) {
74
46
  const bodyParseResult = bodySchema.safeParse(options.body);
75
47
  if (!bodyParseResult.success) {
@@ -97,16 +69,12 @@ export abstract class RestApiClientBase implements IntegrationClientImpl {
97
69
  }
98
70
  }
99
71
 
100
- // The RestApiResponseType union is erased in the bundled JS user APIs
101
- // run as, so values outside it (including wire values the orchestrator
102
- // implements but the SDK deliberately does not expose, like "auto")
103
- // must also be rejected at runtime, before the request is issued.
104
72
  const responseTypeResult = z
105
73
  .enum(REST_API_RESPONSE_TYPES)
106
74
  .safeParse(options.responseType ?? "json");
107
75
  if (!responseTypeResult.success) {
108
76
  throw new RestApiValidationError(
109
- `Unsupported responseType ${JSON.stringify(options.responseType)} expected one of: ${REST_API_RESPONSE_TYPES.join(", ")}`,
77
+ `Unsupported responseType ${JSON.stringify(options.responseType)} - expected one of: ${REST_API_RESPONSE_TYPES.join(", ")}`,
110
78
  {
111
79
  zodError: responseTypeResult.error,
112
80
  data: options.responseType,
@@ -129,30 +97,34 @@ export abstract class RestApiClientBase implements IntegrationClientImpl {
129
97
  request.bodyType = "jsonBody";
130
98
  }
131
99
 
132
- const result = await this.executeQuery(
133
- request as Record<string, unknown>,
134
- undefined,
135
- metadata,
136
- );
137
-
138
- // Neither `null` nor `undefined` is a legitimate decode result for the
139
- // exposed response types: JSON parses to a value the schema sees, and
140
- // an empty text body decodes to "". Either value only arises from a
141
- // broken execution contract, so both throw. (If "auto" is ever exposed,
142
- // `null` becomes legitimate — it parses a literal `null` JSON body.)
100
+ const result = await this.executeQuery({ ...request }, undefined, metadata);
101
+
143
102
  if (result === null || result === undefined) {
144
- const nonNullResult = z.object({}).safeParse(result);
145
- if (!nonNullResult.success) {
146
- throw new RestApiValidationError(
147
- `Integration query returned ${String(result)} for responseType "${responseType}" — expected a response value`,
148
- {
149
- zodError: nonNullResult.error,
150
- data: result,
151
- },
152
- );
153
- }
103
+ throw new RestApiValidationError(
104
+ `Integration query returned ${String(result)} for responseType "${responseType}" - expected a response value`,
105
+ {
106
+ zodError: new z.ZodError([
107
+ {
108
+ code: z.ZodIssueCode.custom,
109
+ message: "response value is required",
110
+ path: [],
111
+ },
112
+ ]),
113
+ data: result,
114
+ },
115
+ );
154
116
  }
155
117
 
156
- return result;
118
+ switch (responseType) {
119
+ case "binary":
120
+ return decodeWorkerBinaryResponse(result);
121
+ case "json":
122
+ case "text":
123
+ return result;
124
+ default: {
125
+ const _exhaustive: never = responseType;
126
+ return _exhaustive;
127
+ }
128
+ }
157
129
  }
158
130
  }
@@ -1,14 +1,3 @@
1
- /**
2
- * Generic REST API Integration client with Zod response validation.
3
- *
4
- * Extends RestApiClientBase with an apiRequest() that validates the
5
- * full response against a caller-supplied Zod schema and throws
6
- * RestApiValidationError on mismatch. When no response schema is
7
- * provided, the raw result is returned without validation — useful for
8
- * non-JSON responses (e.g. XML), which can alternatively be validated
9
- * with a schema matching the decoded value (e.g. z.string()).
10
- */
11
-
12
1
  import { z } from "zod";
13
2
 
14
3
  import { RestApiValidationError } from "../../errors.js";
@@ -21,19 +10,14 @@ import type {
21
10
  SupportsApiRequest,
22
11
  } from "./types.js";
23
12
 
24
- /**
25
- * Base implementation for REST API Integration clients.
26
- *
27
- * All OpenAPI-based integration clients (except those with
28
- * integration-specific response handling) extend this class to
29
- * inherit the generic apiRequest() method with runtime Zod validation.
30
- */
31
13
  export abstract class RestApiIntegrationClient
32
14
  extends RestApiClientBase
33
15
  implements SupportsApiRequest
34
16
  {
35
17
  async apiRequest<TBody, TResponse>(
36
- options: ApiRequestOptions<TBody>,
18
+ options: ApiRequestOptions<TBody> & {
19
+ responseType?: Exclude<RestApiResponseType, "binary">;
20
+ },
37
21
  schema: ApiRequestSchema<TBody, TResponse> & {
38
22
  response: z.ZodSchema<TResponse>;
39
23
  },
@@ -41,7 +25,14 @@ export abstract class RestApiIntegrationClient
41
25
  ): Promise<TResponse>;
42
26
  async apiRequest<TBody>(
43
27
  options: ApiRequestOptions<TBody> & {
44
- responseType: Exclude<RestApiResponseType, "json">;
28
+ responseType: "binary";
29
+ },
30
+ schema?: ApiRequestSchema<TBody, Uint8Array>,
31
+ metadata?: TraceMetadata,
32
+ ): Promise<Uint8Array>;
33
+ async apiRequest<TBody>(
34
+ options: ApiRequestOptions<TBody> & {
35
+ responseType: "text";
45
36
  },
46
37
  schema?: ApiRequestSchema<TBody, unknown>,
47
38
  metadata?: TraceMetadata,
@@ -50,13 +41,10 @@ export abstract class RestApiIntegrationClient
50
41
  options: ApiRequestOptions<TBody>,
51
42
  schema?: ApiRequestSchema<TBody, TResponse>,
52
43
  metadata?: TraceMetadata,
53
- ): Promise<TResponse | unknown> {
54
- // The overloads make schema-less JSON unrepresentable in TypeScript,
55
- // but user API code executes as bundled JS with no type enforcement —
56
- // enforce the same contract at runtime, before issuing the request.
44
+ ): Promise<TResponse | Uint8Array | unknown> {
57
45
  if ((options.responseType ?? "json") === "json" && !schema?.response) {
58
46
  throw new RestApiValidationError(
59
- 'apiRequest() with responseType "json" requires a response schema provide one, or set responseType: "text" to receive a non-JSON payload unvalidated',
47
+ 'apiRequest() with responseType "json" requires a response schema. Provide one, or set responseType to "text" or "binary"',
60
48
  {
61
49
  zodError: new z.ZodError([
62
50
  {
@@ -76,13 +64,6 @@ export abstract class RestApiIntegrationClient
76
64
  metadata,
77
65
  );
78
66
 
79
- // Without a response schema the raw result is returned as-is (typed
80
- // unknown). Non-JSON responses (e.g. responseType "text" returning
81
- // XML) can either omit the schema or pass one matching the decoded
82
- // value, such as z.string(). Request-body validation is opt-in:
83
- // executeApiRequest only validates options.body when schema.body is
84
- // provided (unchanged from the previous contract, where schema.body
85
- // was already optional).
86
67
  if (!schema?.response) {
87
68
  return result;
88
69
  }
@@ -93,8 +74,8 @@ export abstract class RestApiIntegrationClient
93
74
  throw new RestApiValidationError(
94
75
  `Response validation failed: ${responseParseResult.error.message}`,
95
76
  {
96
- zodError: responseParseResult.error,
97
77
  data: result,
78
+ zodError: responseParseResult.error,
98
79
  },
99
80
  );
100
81
  }
@@ -1,43 +1,13 @@
1
- /**
2
- * Shared types for REST API Integration (OpenAPI) based clients.
3
- */
4
-
5
1
  import type { z } from "zod";
6
2
 
7
3
  import type { TraceMetadata } from "../registry.js";
8
4
 
9
- // Re-export for backwards compatibility
10
5
  export type { TraceMetadata };
11
6
 
12
- /**
13
- * How the orchestrator should decode the HTTP response body.
14
- *
15
- * - `"json"` (default): parse the response as JSON; fails on non-JSON bodies
16
- * - `"text"`: return the decoded response body as a string (XML, CSV, HTML, ...)
17
- *
18
- * The orchestrator also implements `"auto"`, `"binary"`, and `"raw"` wire
19
- * values; they are deliberately not exposed here (widening this union later
20
- * is backward-compatible, narrowing it is not):
21
- *
22
- * - `"auto"` returns parsed JSON whenever the body happens to parse, which
23
- * would let JSON responses bypass schema validation via the schema-less
24
- * overload
25
- * - `"binary"` would freeze the worker's internal Buffer JSON encoding
26
- * (`{ type: "Buffer", data: number[] }`, ~4x payload inflation) into the
27
- * public SDK contract
28
- * - `"raw"` is rejected by the orchestrator for non-streaming requests
29
- *
30
- * The runtime tuple backs the request-time guard in `executeApiRequest`:
31
- * the type-level union is erased in the bundled JS user APIs run as, so
32
- * values outside it must also be rejected at runtime.
33
- */
34
- export const REST_API_RESPONSE_TYPES = ["json", "text"] as const;
7
+ export const REST_API_RESPONSE_TYPES = ["binary", "json", "text"] as const;
35
8
 
36
9
  export type RestApiResponseType = (typeof REST_API_RESPONSE_TYPES)[number];
37
10
 
38
- /**
39
- * Options for making a generic REST API request.
40
- */
41
11
  export interface ApiRequestOptions<TBody = unknown> {
42
12
  /**
43
13
  * HTTP method for the request.
@@ -69,15 +39,14 @@ export interface ApiRequestOptions<TBody = unknown> {
69
39
  * How the response body should be decoded. Defaults to `"json"`.
70
40
  *
71
41
  * Use `"text"` for endpoints that return non-JSON payloads such as
72
- * XML. When requesting a text response, omit the response schema (or
73
- * use one matching the decoded string, e.g. `z.string()`).
42
+ * XML. Use `"binary"` for PDFs and other byte payloads. When
43
+ * requesting a text or binary response, omit the response schema (or
44
+ * use one matching the decoded value, e.g. `z.string()` or
45
+ * `z.instanceof(Uint8Array)`).
74
46
  */
75
47
  responseType?: RestApiResponseType;
76
48
  }
77
49
 
78
- /**
79
- * Schema configuration for API request validation.
80
- */
81
50
  export interface ApiRequestSchema<TBody = unknown, TResponse = unknown> {
82
51
  /**
83
52
  * Optional Zod schema for request body validation.
@@ -96,10 +65,9 @@ export interface ApiRequestSchema<TBody = unknown, TResponse = unknown> {
96
65
  * Interface for integration clients that support generic API requests.
97
66
  *
98
67
  * Providing a response schema gives type-safe, validated results. Omitting
99
- * it returns the raw response as `unknown`. For non-JSON payloads (e.g.
100
- * `responseType: "text"` for XML), either omit the schema or supply one
101
- * matching the decoded value (e.g. `z.string()` for text) — object schemas
102
- * only make sense for JSON-shaped results.
68
+ * it returns the decoded response: `Uint8Array` for `responseType: "binary"`,
69
+ * or `unknown` for `responseType: "text"`. JSON still requires a schema.
70
+ * Object schemas only make sense for JSON-shaped results.
103
71
  */
104
72
  export interface SupportsApiRequest {
105
73
  /**
@@ -136,22 +104,50 @@ export interface SupportsApiRequest {
136
104
  * ```
137
105
  */
138
106
  apiRequest<TBody, TResponse>(
139
- options: ApiRequestOptions<TBody>,
107
+ options: ApiRequestOptions<TBody> & {
108
+ responseType?: Exclude<RestApiResponseType, "binary">;
109
+ },
140
110
  schema: ApiRequestSchema<TBody, TResponse> & {
141
111
  response: z.ZodSchema<TResponse>;
142
112
  },
143
113
  metadata?: TraceMetadata,
144
114
  ): Promise<TResponse>;
145
115
 
116
+ /**
117
+ * Execute a generic API request that returns binary data.
118
+ *
119
+ * @param options - Request configuration; `responseType` must be "binary"
120
+ * @param schema - Optional Zod schemas for request body and decoded response validation
121
+ * @param metadata - Optional trace metadata for observability (label, description)
122
+ * @returns The decoded body as a `Uint8Array`
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * const pdf = await ctx.integrations.legacyApi.apiRequest({
127
+ * method: 'GET',
128
+ * path: '/file.pdf',
129
+ * responseType: 'binary',
130
+ * });
131
+ * // pdf is a Uint8Array
132
+ * ```
133
+ */
134
+ apiRequest<TBody>(
135
+ options: ApiRequestOptions<TBody> & {
136
+ responseType: "binary";
137
+ },
138
+ schema?: ApiRequestSchema<TBody, Uint8Array>,
139
+ metadata?: TraceMetadata,
140
+ ): Promise<Uint8Array>;
141
+
146
142
  /**
147
143
  * Execute a generic API request without response validation.
148
144
  *
149
- * This overload requires an explicit non-JSON `responseType` JSON
145
+ * This overload requires an explicit `responseType: "text"` - JSON
150
146
  * responses must always be consumed through the schema overload above,
151
- * so "unvalidated JSON" is unrepresentable. The decoded non-JSON
147
+ * so "unvalidated JSON" is unrepresentable. The decoded text
152
148
  * response is returned as-is, typed `unknown`.
153
149
  *
154
- * Note: request-body validation is opt-in it runs only when
150
+ * Note: request-body validation is opt-in - it runs only when
155
151
  * `schema.body` is provided. Omitting `schema` sends `options.body`
156
152
  * without validation.
157
153
  *
@@ -172,7 +168,7 @@ export interface SupportsApiRequest {
172
168
  */
173
169
  apiRequest<TBody>(
174
170
  options: ApiRequestOptions<TBody> & {
175
- responseType: Exclude<RestApiResponseType, "json">;
171
+ responseType: "text";
176
172
  },
177
173
  schema?: ApiRequestSchema<TBody, unknown>,
178
174
  metadata?: TraceMetadata,
@@ -27,6 +27,7 @@ const EventSchema = z.object({
27
27
  });
28
28
 
29
29
  export default api({
30
+ name: "GetEventCounts",
30
31
  integrations: {
31
32
  bigquery: bigquery(PROD_BIGQUERY),
32
33
  },
@@ -57,6 +57,9 @@ const ListItemsResponseSchema = z.object({
57
57
 
58
58
  export default api({
59
59
  name: "BoxExample",
60
+ integrations: {
61
+ box: box(PROD_BOX),
62
+ },
60
63
  input: z.object({
61
64
  folderId: z.string().default("0"), // "0" is root folder
62
65
  limit: z.number().default(100),
@@ -211,6 +211,13 @@ All methods accept an optional `metadata` parameter as the last argument for dia
211
211
 
212
212
  ## Common Pitfalls
213
213
 
214
+ ### Streaming Is Not Supported
215
+
216
+ `apiRequest()` does not support streaming or Server-Sent Events. Do not set
217
+ `stream: true` — streaming responses fail schema validation. Every call
218
+ returns the complete response; if a UI needs real-time token streaming,
219
+ handle it at the frontend layer, not through the SDK.
220
+
214
221
  ### No Specialized Methods
215
222
 
216
223
  ```typescript
@@ -839,17 +839,23 @@ describe("resolveIntegrationDocumentation", () => {
839
839
  });
840
840
 
841
841
  describe("restapiintegration responseType gating (real docs)", () => {
842
- // These resolve the actual shipped docs, not a fixture: the overlay
843
- // gating is the discoverability guard for the responseType feature
844
- // (agents whose worker sdk-api predates it must never be told the
845
- // option exists), so the real manifest content is the contract.
846
-
847
- it("documents responseType for agents whose sdk-api supports it", async () => {
842
+ it("documents text responseType for agents whose sdk-api supports text only", async () => {
848
843
  const docs = await resolveIntegrationDocumentation("restapiintegration", {
849
844
  sdkVersion: "0.0.3",
850
845
  });
851
846
 
852
847
  expect(docs).toContain('responseType: "text"');
848
+ expect(docs).not.toContain('responseType: "binary"');
849
+ expect(docs).not.toContain("does not support the `responseType`");
850
+ });
851
+
852
+ it("documents binary responseType only for agents whose sdk-api supports it", async () => {
853
+ const docs = await resolveIntegrationDocumentation("restapiintegration", {
854
+ sdkVersion: "0.0.4",
855
+ });
856
+
857
+ expect(docs).toContain('responseType: "text"');
858
+ expect(docs).toContain('responseType: "binary"');
853
859
  expect(docs).not.toContain("does not support the `responseType`");
854
860
  });
855
861
 
@@ -860,6 +866,7 @@ describe("resolveIntegrationDocumentation", () => {
860
866
 
861
867
  expect(docs).toContain("does not support the `responseType`");
862
868
  expect(docs).not.toContain('responseType: "text"');
869
+ expect(docs).not.toContain('responseType: "binary"');
863
870
  });
864
871
 
865
872
  it("stays silent about responseType when the agent reports no sdk-api version", async () => {
@@ -293,6 +293,13 @@ All methods accept an optional `metadata` parameter as the last argument for dia
293
293
 
294
294
  ## Common Pitfalls
295
295
 
296
+ ### Streaming Is Not Supported
297
+
298
+ `apiRequest()` does not support streaming or Server-Sent Events. Do not set
299
+ `stream: true` — streaming responses fail schema validation. Every call
300
+ returns the complete response; if a UI needs real-time token streaming,
301
+ handle it at the frontend layer, not through the SDK.
302
+
296
303
  ### No Specialized Methods
297
304
 
298
305
  ```typescript
@@ -265,6 +265,14 @@ All methods accept an optional `metadata` parameter as the last argument for dia
265
265
 
266
266
  ## Common Pitfalls
267
267
 
268
+ ### Streaming Is Not Supported
269
+
270
+ `apiRequest()` does not support streaming or Server-Sent Events. Use
271
+ `:generateContent`, never `:streamGenerateContent` or `alt=sse` — streaming
272
+ responses fail schema validation. Every call returns the complete response;
273
+ if a UI needs real-time token streaming, handle it at the frontend layer,
274
+ not through the SDK.
275
+
268
276
  ### No Specialized Methods
269
277
 
270
278
  ```typescript
@@ -251,6 +251,13 @@ All methods accept an optional `metadata` parameter as the last argument for dia
251
251
 
252
252
  ## Common Pitfalls
253
253
 
254
+ ### Streaming Is Not Supported
255
+
256
+ `apiRequest()` does not support streaming or Server-Sent Events. Do not set
257
+ `stream: true` — streaming responses fail schema validation. Every call
258
+ returns the complete response; if a UI needs real-time token streaming,
259
+ handle it at the frontend layer, not through the SDK.
260
+
254
261
  ### No Specialized Methods
255
262
 
256
263
  ```typescript
@@ -277,6 +277,13 @@ All methods accept an optional `metadata` parameter as the last argument for dia
277
277
 
278
278
  ## Common Pitfalls
279
279
 
280
+ ### Streaming Is Not Supported
281
+
282
+ `apiRequest()` does not support streaming or Server-Sent Events. Do not set
283
+ `stream: true` — streaming responses fail schema validation. Every call
284
+ returns the complete response; if a UI needs real-time token streaming,
285
+ handle it at the frontend layer, not through the SDK.
286
+
280
287
  ### No Specialized Methods
281
288
 
282
289
  ```typescript