@superblocksteam/sdk-api 0.0.12-body-types → 0.0.13

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 (152) hide show
  1. package/README.md +23 -0
  2. package/dist/errors.d.ts +36 -3
  3. package/dist/errors.d.ts.map +1 -1
  4. package/dist/errors.js +231 -38
  5. package/dist/errors.js.map +1 -1
  6. package/dist/errors.test.d.ts +2 -0
  7. package/dist/errors.test.d.ts.map +1 -0
  8. package/dist/errors.test.js +189 -0
  9. package/dist/errors.test.js.map +1 -0
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/integrations/anthropic/types.d.ts +1 -1
  14. package/dist/integrations/base/decode-worker-binary-response.d.ts.map +1 -1
  15. package/dist/integrations/base/decode-worker-binary-response.js +2 -5
  16. package/dist/integrations/base/decode-worker-binary-response.js.map +1 -1
  17. package/dist/integrations/base/index.d.ts +1 -1
  18. package/dist/integrations/base/index.d.ts.map +1 -1
  19. package/dist/integrations/base/request-body.d.ts +18 -10
  20. package/dist/integrations/base/request-body.d.ts.map +1 -1
  21. package/dist/integrations/base/request-body.js +131 -215
  22. package/dist/integrations/base/request-body.js.map +1 -1
  23. package/dist/integrations/base/request-body.test.js +306 -164
  24. package/dist/integrations/base/request-body.test.js.map +1 -1
  25. package/dist/integrations/base/rest-api-client-base.d.ts.map +1 -1
  26. package/dist/integrations/base/rest-api-client-base.js +13 -26
  27. package/dist/integrations/base/rest-api-client-base.js.map +1 -1
  28. package/dist/integrations/base/rest-api-integration-client.d.ts.map +1 -1
  29. package/dist/integrations/base/rest-api-integration-client.js +2 -5
  30. package/dist/integrations/base/rest-api-integration-client.js.map +1 -1
  31. package/dist/integrations/base/types.d.ts +0 -8
  32. package/dist/integrations/base/types.d.ts.map +1 -1
  33. package/dist/integrations/cosmosdb/client.d.ts.map +1 -1
  34. package/dist/integrations/cosmosdb/client.js +2 -5
  35. package/dist/integrations/cosmosdb/client.js.map +1 -1
  36. package/dist/integrations/documentation-resolver.test.js +88 -2
  37. package/dist/integrations/documentation-resolver.test.js.map +1 -1
  38. package/dist/integrations/documentation.test.js +10 -0
  39. package/dist/integrations/documentation.test.js.map +1 -1
  40. package/dist/integrations/dynamodb/client.d.ts +6 -2
  41. package/dist/integrations/dynamodb/client.d.ts.map +1 -1
  42. package/dist/integrations/dynamodb/client.js +85 -15
  43. package/dist/integrations/dynamodb/client.js.map +1 -1
  44. package/dist/integrations/dynamodb/client.test.d.ts +8 -0
  45. package/dist/integrations/dynamodb/client.test.d.ts.map +1 -0
  46. package/dist/integrations/dynamodb/client.test.js +198 -0
  47. package/dist/integrations/dynamodb/client.test.js.map +1 -0
  48. package/dist/integrations/dynamodb/index.d.ts +1 -1
  49. package/dist/integrations/dynamodb/index.d.ts.map +1 -1
  50. package/dist/integrations/dynamodb/index.js.map +1 -1
  51. package/dist/integrations/dynamodb/types.d.ts +27 -1
  52. package/dist/integrations/dynamodb/types.d.ts.map +1 -1
  53. package/dist/integrations/gcs/client.d.ts.map +1 -1
  54. package/dist/integrations/gcs/client.js +2 -5
  55. package/dist/integrations/gcs/client.js.map +1 -1
  56. package/dist/integrations/gsheets/client.d.ts.map +1 -1
  57. package/dist/integrations/gsheets/client.js +2 -5
  58. package/dist/integrations/gsheets/client.js.map +1 -1
  59. package/dist/integrations/index.d.ts +1 -1
  60. package/dist/integrations/index.d.ts.map +1 -1
  61. package/dist/integrations/index.js.map +1 -1
  62. package/dist/integrations/mongodb/client.d.ts.map +1 -1
  63. package/dist/integrations/mongodb/client.js +2 -5
  64. package/dist/integrations/mongodb/client.js.map +1 -1
  65. package/dist/integrations/restapiintegration/client.body-types.test.js +107 -25
  66. package/dist/integrations/restapiintegration/client.body-types.test.js.map +1 -1
  67. package/dist/integrations/restapiintegration/client.test.js +16 -0
  68. package/dist/integrations/restapiintegration/client.test.js.map +1 -1
  69. package/dist/integrations/s3/client.d.ts.map +1 -1
  70. package/dist/integrations/s3/client.js +2 -5
  71. package/dist/integrations/s3/client.js.map +1 -1
  72. package/dist/integrations/salesforce/client.d.ts.map +1 -1
  73. package/dist/integrations/salesforce/client.js +2 -5
  74. package/dist/integrations/salesforce/client.js.map +1 -1
  75. package/dist/integrations/slack/client.d.ts.map +1 -1
  76. package/dist/integrations/slack/client.js +4 -13
  77. package/dist/integrations/slack/client.js.map +1 -1
  78. package/dist/integrations/slack/client.test.js +2 -5
  79. package/dist/integrations/slack/client.test.js.map +1 -1
  80. package/dist/integrations/utils.d.ts +8 -3
  81. package/dist/integrations/utils.d.ts.map +1 -1
  82. package/dist/integrations/utils.js +42 -1
  83. package/dist/integrations/utils.js.map +1 -1
  84. package/dist/runtime/context.d.ts +6 -0
  85. package/dist/runtime/context.d.ts.map +1 -1
  86. package/dist/runtime/context.js +2 -1
  87. package/dist/runtime/context.js.map +1 -1
  88. package/dist/runtime/executor.d.ts +6 -0
  89. package/dist/runtime/executor.d.ts.map +1 -1
  90. package/dist/runtime/executor.js +1 -0
  91. package/dist/runtime/executor.js.map +1 -1
  92. package/dist/types.d.ts +10 -0
  93. package/dist/types.d.ts.map +1 -1
  94. package/package.json +3 -3
  95. package/src/errors.test.ts +257 -0
  96. package/src/errors.ts +289 -46
  97. package/src/index.ts +1 -0
  98. package/src/integrations/anthropic/README.md +12 -59
  99. package/src/integrations/anthropic/types.ts +1 -1
  100. package/src/integrations/base/decode-worker-binary-response.ts +5 -7
  101. package/src/integrations/base/index.ts +0 -5
  102. package/src/integrations/base/request-body.test.ts +358 -195
  103. package/src/integrations/base/request-body.ts +170 -326
  104. package/src/integrations/base/rest-api-client-base.ts +22 -31
  105. package/src/integrations/base/rest-api-integration-client.ts +8 -7
  106. package/src/integrations/base/types.ts +0 -18
  107. package/src/integrations/box/README.md +4 -60
  108. package/src/integrations/cosmosdb/client.ts +8 -7
  109. package/src/integrations/documentation-resolver.test.ts +108 -2
  110. package/src/integrations/documentation.test.ts +12 -0
  111. package/src/integrations/dropbox/README.md +1 -71
  112. package/src/integrations/dropbox/docs.manifest.json +10 -1
  113. package/src/integrations/dropbox/overlays/upload-unsupported.md +3 -0
  114. package/src/integrations/dropbox/overlays/upload.md +89 -0
  115. package/src/integrations/dynamodb/README.md +33 -12
  116. package/src/integrations/dynamodb/client.test.ts +254 -0
  117. package/src/integrations/dynamodb/client.ts +129 -23
  118. package/src/integrations/dynamodb/index.ts +5 -1
  119. package/src/integrations/dynamodb/types.ts +33 -1
  120. package/src/integrations/elasticsearch/README.md +0 -80
  121. package/src/integrations/elasticsearch/docs.manifest.json +10 -1
  122. package/src/integrations/elasticsearch/overlays/bulk-unsupported.md +3 -0
  123. package/src/integrations/elasticsearch/overlays/bulk.md +80 -0
  124. package/src/integrations/gcs/client.ts +8 -7
  125. package/src/integrations/googledrive/README.md +10 -36
  126. package/src/integrations/gsheets/client.ts +8 -7
  127. package/src/integrations/index.ts +1 -0
  128. package/src/integrations/jira/README.md +0 -26
  129. package/src/integrations/jira/docs.manifest.json +10 -1
  130. package/src/integrations/jira/overlays/attachments-unsupported.md +3 -0
  131. package/src/integrations/jira/overlays/attachments.md +30 -0
  132. package/src/integrations/mongodb/client.ts +8 -7
  133. package/src/integrations/restapiintegration/client.body-types.test.ts +135 -26
  134. package/src/integrations/restapiintegration/client.test.ts +23 -0
  135. package/src/integrations/restapiintegration/docs.manifest.json +2 -2
  136. package/src/integrations/restapiintegration/overlays/request-body-types.md +26 -17
  137. package/src/integrations/s3/client.ts +8 -7
  138. package/src/integrations/salesforce/client.ts +9 -7
  139. package/src/integrations/slack/client.test.ts +2 -5
  140. package/src/integrations/slack/client.ts +13 -19
  141. package/src/integrations/stabilityai/README.md +0 -87
  142. package/src/integrations/stabilityai/docs.manifest.json +10 -1
  143. package/src/integrations/stabilityai/overlays/image-uploads-unsupported.md +3 -0
  144. package/src/integrations/stabilityai/overlays/image-uploads.md +120 -0
  145. package/src/integrations/stripe/README.md +0 -201
  146. package/src/integrations/stripe/docs.manifest.json +10 -1
  147. package/src/integrations/stripe/overlays/request-bodies-unsupported.md +3 -0
  148. package/src/integrations/stripe/overlays/request-bodies.md +204 -0
  149. package/src/integrations/utils.ts +51 -1
  150. package/src/runtime/context.ts +9 -0
  151. package/src/runtime/executor.ts +8 -0
  152. package/src/types.ts +11 -0
@@ -4,9 +4,13 @@ import { z } from "zod";
4
4
  import type { Property } from "@superblocksteam/types/dist/src/common/v1/plugin_pb";
5
5
  import type { Plugin as RestApiIntegrationPlugin } from "@superblocksteam/types/dist/src/plugins/restapiintegration/v1/plugin_pb";
6
6
 
7
- import { RestApiValidationError } from "../../errors.js";
7
+ import {
8
+ RestApiValidationError,
9
+ restApiValidationErrorFromZodError,
10
+ } from "../../errors.js";
8
11
  import type { QueryExecutor, TraceMetadata } from "../registry.js";
9
12
  import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
13
+ import { describeOptionValue, validationError } from "../utils.js";
10
14
  import { decodeWorkerBinaryResponse } from "./decode-worker-binary-response.js";
11
15
  import { encodeRequestBody } from "./request-body.js";
12
16
  import { REST_API_RESPONSE_TYPES } from "./types.js";
@@ -46,12 +50,10 @@ export abstract class RestApiClientBase implements IntegrationClientImpl {
46
50
  if (options.body !== undefined && bodySchema) {
47
51
  const bodyParseResult = bodySchema.safeParse(options.body);
48
52
  if (!bodyParseResult.success) {
49
- throw new RestApiValidationError(
50
- `Request body validation failed: ${bodyParseResult.error.message}`,
51
- {
52
- zodError: bodyParseResult.error,
53
- data: options.body,
54
- },
53
+ throw restApiValidationErrorFromZodError(
54
+ "Request body validation failed",
55
+ bodyParseResult.error,
56
+ options.body,
55
57
  );
56
58
  }
57
59
  }
@@ -74,16 +76,22 @@ export abstract class RestApiClientBase implements IntegrationClientImpl {
74
76
  .enum(REST_API_RESPONSE_TYPES)
75
77
  .safeParse(options.responseType ?? "json");
76
78
  if (!responseTypeResult.success) {
77
- throw new RestApiValidationError(
78
- `Unsupported responseType ${JSON.stringify(options.responseType)} - expected one of: ${REST_API_RESPONSE_TYPES.join(", ")}`,
79
- {
80
- zodError: responseTypeResult.error,
81
- data: options.responseType,
82
- },
79
+ throw validationError(
80
+ `Unsupported responseType ${describeOptionValue(options.responseType)} - expected one of: ${REST_API_RESPONSE_TYPES.join(", ")}`,
81
+ ["responseType"],
83
82
  );
84
83
  }
85
84
  const responseType = responseTypeResult.data;
86
85
 
86
+ const { wire: bodyWire, contentType } = encodeRequestBody({
87
+ body: options.body,
88
+ bodyType: options.bodyType,
89
+ headers: options.headers,
90
+ });
91
+ if (contentType !== undefined) {
92
+ headers.push(this.createParam("Content-Type", contentType));
93
+ }
94
+
87
95
  const request: RestApiRequest = {
88
96
  openApiAction: "genericHttpRequest",
89
97
  httpMethod: options.method.toUpperCase(),
@@ -91,26 +99,9 @@ export abstract class RestApiClientBase implements IntegrationClientImpl {
91
99
  headers,
92
100
  params,
93
101
  responseType,
102
+ ...bodyWire,
94
103
  };
95
104
 
96
- const bodyPatch = encodeRequestBody({
97
- body: options.body,
98
- bodyType: options.bodyType,
99
- headers: options.headers,
100
- });
101
- if (bodyPatch.body !== undefined) {
102
- request.body = bodyPatch.body;
103
- }
104
- if (bodyPatch.formData !== undefined) {
105
- request.formData = [...bodyPatch.formData];
106
- }
107
- if (bodyPatch.bodyType !== undefined) {
108
- request.bodyType = bodyPatch.bodyType;
109
- }
110
- for (const { key, value } of bodyPatch.injectedHeaders) {
111
- headers.push(this.createParam(key, value));
112
- }
113
-
114
105
  const result = await this.executeQuery(request, undefined, metadata);
115
106
 
116
107
  if (result === null || result === undefined) {
@@ -1,6 +1,9 @@
1
1
  import { z } from "zod";
2
2
 
3
- import { RestApiValidationError } from "../../errors.js";
3
+ import {
4
+ RestApiValidationError,
5
+ restApiValidationErrorFromZodError,
6
+ } from "../../errors.js";
4
7
  import type { TraceMetadata } from "../registry.js";
5
8
  import { RestApiClientBase } from "./rest-api-client-base.js";
6
9
  import type {
@@ -71,12 +74,10 @@ export abstract class RestApiIntegrationClient
71
74
  const responseParseResult = schema.response.safeParse(result);
72
75
 
73
76
  if (!responseParseResult.success) {
74
- throw new RestApiValidationError(
75
- `Response validation failed: ${responseParseResult.error.message}`,
76
- {
77
- data: result,
78
- zodError: responseParseResult.error,
79
- },
77
+ throw restApiValidationErrorFromZodError(
78
+ "Response validation failed",
79
+ responseParseResult.error,
80
+ result,
80
81
  );
81
82
  }
82
83
 
@@ -18,24 +18,6 @@ export const REST_API_BODY_TYPES = [
18
18
 
19
19
  export type RestApiBodyType = (typeof REST_API_BODY_TYPES)[number];
20
20
 
21
- export type BinaryBody = ArrayBuffer | ArrayBufferView;
22
-
23
- export type MultipartFormValue =
24
- | string
25
- | number
26
- | boolean
27
- | { value: string | BinaryBody; filename: string };
28
-
29
- export type MultipartFormBody = Record<string, MultipartFormValue>;
30
-
31
- export type FormUrlencodedValue =
32
- | string
33
- | number
34
- | boolean
35
- | readonly (string | number | boolean)[];
36
-
37
- export type FormUrlencodedBody = Record<string, FormUrlencodedValue>;
38
-
39
21
  export interface ApiRequestOptions<TBody = unknown> {
40
22
  /**
41
23
  * HTTP method for the request.
@@ -1,6 +1,6 @@
1
1
  # Box Client
2
2
 
3
- Upload, download, and manage files in Box.
3
+ Download and manage files in Box. File uploads are not available through this integration; see [File Uploads Are Not Available](#file-uploads-are-not-available).
4
4
 
5
5
  ## Methods
6
6
 
@@ -163,37 +163,6 @@ const content = await ctx.integrations.box.apiRequest(
163
163
  );
164
164
  ```
165
165
 
166
- ### Upload a File
167
-
168
- ```typescript
169
- const UploadResponseSchema = z.object({
170
- entries: z.array(FileInfoSchema),
171
- total_count: z.number(),
172
- });
173
-
174
- // For files < 50MB, use direct upload
175
- const result = await ctx.integrations.box.apiRequest(
176
- {
177
- method: "POST",
178
- path: "/files/content",
179
- headers: {
180
- "Content-Type": "multipart/form-data",
181
- },
182
- body: {
183
- attributes: JSON.stringify({
184
- name: "new-file.txt",
185
- parent: { id: folderId },
186
- }),
187
- file: fileContent,
188
- },
189
- },
190
- { response: UploadResponseSchema },
191
- );
192
-
193
- const uploadedFile = result.entries[0];
194
- console.log(`Uploaded: ${uploadedFile.name} (${uploadedFile.id})`);
195
- ```
196
-
197
166
  ### Create a Folder
198
167
 
199
168
  ```typescript
@@ -399,7 +368,7 @@ All methods accept an optional `metadata` parameter as the last argument for dia
399
368
  ```typescript
400
369
  // WRONG - These methods do not exist
401
370
  await box.listFiles({ ... });
402
- await box.uploadFile({ ... });
371
+ await box.createFolder({ ... });
403
372
 
404
373
  // CORRECT - Use apiRequest
405
374
  await ctx.integrations.box.apiRequest(
@@ -457,20 +426,9 @@ const result = await ctx.integrations.box.apiRequest(
457
426
  );
458
427
  ```
459
428
 
460
- ### Upload Returns Array
461
-
462
- File uploads return an array in `entries`:
429
+ ### File Uploads Are Not Available
463
430
 
464
- ```typescript
465
- // Response structure
466
- const schema = z.object({
467
- entries: z.array(FileInfoSchema), // Array even for single file
468
- total_count: z.number(),
469
- });
470
-
471
- // Access the uploaded file
472
- const uploadedFile = result.entries[0];
473
- ```
431
+ Box serves every upload endpoint (`/files/content`, `/files/upload_sessions`) from `https://upload.box.com/api/2.0`. This integration's base URL is fixed by the Box integration template to `https://api.box.com/2.0` and `apiRequest` cannot target another host, so uploads through `ctx.integrations.box` fail. Do not generate upload code for this integration. If an app must upload to Box, ask the user to configure a REST API integration with base URL `https://upload.box.com/api/2.0` using the same OAuth client, then call it through that integration.
474
432
 
475
433
  ### If-Match Header for Updates
476
434
 
@@ -521,20 +479,6 @@ async function getAllItems(box: BoxClient, folderId: string) {
521
479
  }
522
480
  ```
523
481
 
524
- ### File Size Limits
525
-
526
- Different upload methods for different file sizes:
527
-
528
- ```typescript
529
- // < 50MB - Use /files/content (simple upload)
530
- // > 50MB - Use chunked upload session
531
-
532
- // Chunked upload for large files:
533
- // 1. POST /files/upload_sessions - Create session
534
- // 2. PUT /files/upload_sessions/:id - Upload parts
535
- // 3. POST /files/upload_sessions/:id/commit - Finalize
536
- ```
537
-
538
482
  ### Shared Link Access Levels
539
483
 
540
484
  ```typescript
@@ -11,7 +11,10 @@ import type { z } from "zod";
11
11
 
12
12
  import type { Plugin as CosmosDBPlugin } from "@superblocksteam/types/dist/src/plugins/cosmosdb/v1/plugin_pb";
13
13
 
14
- import { RestApiValidationError } from "../../errors.js";
14
+ import {
15
+ RestApiValidationError,
16
+ restApiValidationErrorFromZodError,
17
+ } from "../../errors.js";
15
18
  import { IntegrationError } from "../../runtime/errors.js";
16
19
  import type { QueryExecutor, TraceMetadata } from "../registry.js";
17
20
  import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
@@ -80,12 +83,10 @@ export class CosmosDBClientImpl
80
83
  const parseResult = schema.safeParse(result);
81
84
 
82
85
  if (!parseResult.success) {
83
- throw new RestApiValidationError(
84
- `Result validation failed: ${parseResult.error.message}`,
85
- {
86
- zodError: parseResult.error,
87
- data: result,
88
- },
86
+ throw restApiValidationErrorFromZodError(
87
+ "Result validation failed",
88
+ parseResult.error,
89
+ result,
89
90
  );
90
91
  }
91
92
 
@@ -861,7 +861,7 @@ describe("resolveIntegrationDocumentation", () => {
861
861
 
862
862
  it("documents request bodyType only for agents whose sdk-api supports it", async () => {
863
863
  const docs = await resolveIntegrationDocumentation("restapiintegration", {
864
- sdkVersion: "0.0.5",
864
+ sdkVersion: "0.0.7",
865
865
  });
866
866
 
867
867
  expect(docs).toContain('responseType: "binary"');
@@ -871,9 +871,23 @@ describe("resolveIntegrationDocumentation", () => {
871
871
  expect(docs).not.toContain("does not support the `bodyType`");
872
872
  });
873
873
 
874
+ it("documents binary request bodies with direct raw file bytes", async () => {
875
+ const docs = await resolveIntegrationDocumentation("restapiintegration", {
876
+ sdkVersion: "0.0.7",
877
+ });
878
+
879
+ expect(docs).not.toContain("Buffer.from(raw");
880
+ expect(docs).not.toContain("Buffer.isBuffer(raw");
881
+ expect(docs).toContain(
882
+ 'pass the `Uint8Array` returned by `await file.readContentsAsync("raw")` directly as the body',
883
+ );
884
+ expect(docs).toContain('bodyType: "binary"');
885
+ expect(docs).toContain('body: await file.readContentsAsync("raw")');
886
+ });
887
+
874
888
  it("notes bodyType non-support before sdk-api request body support", async () => {
875
889
  const docs = await resolveIntegrationDocumentation("restapiintegration", {
876
- sdkVersion: "0.0.4",
890
+ sdkVersion: "0.0.6",
877
891
  });
878
892
 
879
893
  expect(docs).toContain('responseType: "binary"');
@@ -899,6 +913,98 @@ describe("resolveIntegrationDocumentation", () => {
899
913
  });
900
914
  });
901
915
 
916
+ describe("non-JSON request body gating (real docs)", () => {
917
+ it.each([
918
+ {
919
+ pluginId: "jira",
920
+ supportedMarker: 'bodyType: "multipartForm"',
921
+ unsupportedMarker: "cannot upload",
922
+ expectFileRead: true,
923
+ },
924
+ {
925
+ pluginId: "stabilityai",
926
+ supportedMarker: 'bodyType: "multipartForm"',
927
+ unsupportedMarker: "cannot upload",
928
+ expectFileRead: true,
929
+ },
930
+ {
931
+ pluginId: "dropbox",
932
+ supportedMarker: 'bodyType: "binary"',
933
+ unsupportedMarker: "cannot upload",
934
+ expectFileRead: true,
935
+ },
936
+ {
937
+ pluginId: "elasticsearch",
938
+ supportedMarker: 'bodyType: "raw"',
939
+ unsupportedMarker: "cannot call `/_bulk`",
940
+ expectFileRead: false,
941
+ },
942
+ {
943
+ pluginId: "stripe",
944
+ supportedMarker: 'bodyType: "formUrlencoded"',
945
+ unsupportedMarker: "cannot send that encoding",
946
+ expectFileRead: false,
947
+ },
948
+ ])(
949
+ "gates $pluginId docs on sdk-api body type support",
950
+ async ({
951
+ pluginId,
952
+ supportedMarker,
953
+ unsupportedMarker,
954
+ expectFileRead,
955
+ }) => {
956
+ const supported = await resolveIntegrationDocumentation(pluginId, {
957
+ sdkVersion: "0.0.7",
958
+ });
959
+ expect(supported).toContain(supportedMarker);
960
+ expect(supported).not.toContain(unsupportedMarker);
961
+ if (expectFileRead) {
962
+ expect(supported).toContain('readContentsAsync("raw")');
963
+ }
964
+
965
+ const unsupported = await resolveIntegrationDocumentation(pluginId, {
966
+ sdkVersion: "0.0.6",
967
+ });
968
+ expect(unsupported).toContain(unsupportedMarker);
969
+ expect(unsupported).not.toContain("bodyType");
970
+
971
+ const unversioned = await resolveIntegrationDocumentation(pluginId);
972
+ expect(unversioned).not.toContain("bodyType");
973
+ expect(unversioned).not.toContain(unsupportedMarker);
974
+
975
+ for (const docs of [supported, unsupported, unversioned]) {
976
+ expect(docs).not.toContain("body: fileContent");
977
+ expect(docs).not.toContain("file: fileContent");
978
+ expect(docs).not.toContain('"Content-Type": "multipart/form-data"');
979
+ expect(docs).not.toContain('body: "File content here"');
980
+ }
981
+ },
982
+ );
983
+
984
+ it.each([
985
+ { pluginId: "box", host: "upload.box.com/api/2.0" },
986
+ { pluginId: "googledrive", host: "googleapis.com/upload/drive/v3" },
987
+ ])(
988
+ "tells $pluginId callers uploads are unavailable at every sdk-api version",
989
+ async ({ pluginId, host }) => {
990
+ for (const options of [
991
+ { sdkVersion: "0.0.7" as const },
992
+ { sdkVersion: "0.0.6" as const },
993
+ undefined,
994
+ ]) {
995
+ const docs = await resolveIntegrationDocumentation(pluginId, options);
996
+ expect(docs).toContain("Uploads Are Not Available");
997
+ expect(docs).toContain(host);
998
+ expect(docs).not.toContain("bodyType");
999
+ expect(docs).not.toContain('readContentsAsync("raw")');
1000
+ expect(docs).not.toContain("file: fileContent");
1001
+ expect(docs).not.toContain('body: "File content here"');
1002
+ expect(docs).not.toContain('path: "/upload/drive/v3/files"');
1003
+ }
1004
+ },
1005
+ );
1006
+ });
1007
+
902
1008
  it("blocks overlay paths outside the plugin directory", async () => {
903
1009
  const integrationsDirectory = createPluginDocsFixture("dropbox", {
904
1010
  "README.md": "base",
@@ -118,3 +118,15 @@ describe("SQL integration documentation", () => {
118
118
  },
119
119
  );
120
120
  });
121
+
122
+ describe("Anthropic integration documentation", () => {
123
+ it("uses claude-sonnet-5-20250715 instead of Claude 3.5 snapshot IDs", () => {
124
+ const readme = readIntegrationFile("anthropic", "README.md");
125
+ const typeDocs = readIntegrationFile("anthropic", "types.ts");
126
+
127
+ expect(readme).toContain("claude-sonnet-5-20250715");
128
+ expect(typeDocs).toContain("claude-sonnet-5-20250715");
129
+ expect(readme).not.toMatch(/claude-3-5-sonnet-\d{8}/);
130
+ expect(typeDocs).not.toMatch(/claude-3-5-sonnet-\d{8}/);
131
+ });
132
+ });
@@ -123,41 +123,6 @@ const result = await ctx.integrations.dropbox.apiRequest(
123
123
  );
124
124
  ```
125
125
 
126
- ### Upload a File
127
-
128
- ```typescript
129
- const UploadResponseSchema = z.object({
130
- name: z.string(),
131
- path_lower: z.string(),
132
- path_display: z.string(),
133
- id: z.string(),
134
- client_modified: z.string(),
135
- server_modified: z.string(),
136
- size: z.number(),
137
- content_hash: z.string(),
138
- });
139
-
140
- const result = await ctx.integrations.dropbox.apiRequest(
141
- {
142
- method: "POST",
143
- path: "/2/files/upload",
144
- headers: {
145
- "Dropbox-API-Arg": JSON.stringify({
146
- path: "/Documents/new-file.txt",
147
- mode: "add", // add, overwrite, or update
148
- autorename: true,
149
- mute: false,
150
- }),
151
- "Content-Type": "application/octet-stream",
152
- },
153
- body: "File content here",
154
- },
155
- { response: UploadResponseSchema },
156
- );
157
-
158
- console.log(`Uploaded: ${result.path_display}`);
159
- ```
160
-
161
126
  ### Create a Folder
162
127
 
163
128
  ```typescript
@@ -369,27 +334,9 @@ const path = "/Documents/file.txt";
369
334
  const path = "";
370
335
  ```
371
336
 
372
- ### Upload Headers
373
-
374
- File uploads require special headers:
375
-
376
- ```typescript
377
- // Upload requires Dropbox-API-Arg header
378
- const headers = {
379
- "Dropbox-API-Arg": JSON.stringify({
380
- path: "/path/to/file.txt",
381
- mode: "add",
382
- }),
383
- "Content-Type": "application/octet-stream",
384
- };
385
-
386
- // The body is raw file content, not JSON
387
- const body = "Raw file content";
388
- ```
389
-
390
337
  ### Download Headers
391
338
 
392
- Downloads also use special headers:
339
+ Downloads use special headers, not the body, to carry parameters:
393
340
 
394
341
  ```typescript
395
342
  // Download parameters go in header, not body
@@ -453,23 +400,6 @@ async function listAllFiles(dropbox: DropboxClient, path: string) {
453
400
  }
454
401
  ```
455
402
 
456
- ### Write Conflict Modes
457
-
458
- Choose the right mode for uploads:
459
-
460
- ```typescript
461
- const modes = {
462
- add: "Never overwrite, fail if file exists",
463
- overwrite: "Always overwrite existing file",
464
- update: "Update only if rev matches (optimistic locking)",
465
- };
466
-
467
- const body = {
468
- path: "/file.txt",
469
- mode: "add", // or { ".tag": "update", "update": revisionId }
470
- };
471
- ```
472
-
473
403
  ### Case Sensitivity
474
404
 
475
405
  Dropbox paths are case-insensitive but preserve case:
@@ -1,5 +1,14 @@
1
1
  {
2
2
  "pluginId": "dropbox",
3
3
  "base": "README.md",
4
- "overlays": []
4
+ "overlays": [
5
+ {
6
+ "file": "overlays/upload.md",
7
+ "sdkVersionRange": ">=0.0.7"
8
+ },
9
+ {
10
+ "file": "overlays/upload-unsupported.md",
11
+ "sdkVersionRange": "<0.0.7"
12
+ }
13
+ ]
5
14
  }
@@ -0,0 +1,3 @@
1
+ ## Upload a File
2
+
3
+ This agent's sdk-api cannot upload files to Dropbox through `apiRequest()` because it serializes every body as JSON, and `/2/files/upload` expects the raw file bytes. Do not generate upload code until the agent is upgraded. Listing, metadata, download, folder, and sharing calls work as documented above.
@@ -0,0 +1,89 @@
1
+ ## Upload a File
2
+
3
+ `/2/files/upload` takes the file bytes as the request body and the upload parameters in the `Dropbox-API-Arg` header. Pass `bodyType: "binary"` with the `Uint8Array` returned by `await file.readContentsAsync("raw")`. The SDK adds `Content-Type: application/octet-stream`, which Dropbox requires. `Dropbox-API-Arg` must be ASCII-only, so escape non-ASCII filenames before building it (Dropbox rejects a raw UTF-8 header).
4
+
5
+ ```typescript
6
+ const UploadResponseSchema = z.object({
7
+ name: z.string(),
8
+ path_lower: z.string(),
9
+ path_display: z.string(),
10
+ id: z.string(),
11
+ client_modified: z.string(),
12
+ server_modified: z.string(),
13
+ size: z.number(),
14
+ content_hash: z.string(),
15
+ });
16
+
17
+ const file = input.upload.files[0];
18
+
19
+ // Dropbox-API-Arg is an HTTP header value, which must be ASCII-only.
20
+ // Escape any non-ASCII character (e.g. accented letters) as \uXXXX.
21
+ function toDropboxApiArg(value: unknown): string {
22
+ return JSON.stringify(value).replace(
23
+ /[\u0080-\uffff]/g,
24
+ (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`,
25
+ );
26
+ }
27
+
28
+ const result = await ctx.integrations.dropbox.apiRequest(
29
+ {
30
+ method: "POST",
31
+ path: "/2/files/upload",
32
+ headers: {
33
+ "Dropbox-API-Arg": toDropboxApiArg({
34
+ path: `/Documents/${file.name}`,
35
+ mode: "add", // add, overwrite, or update
36
+ autorename: true,
37
+ mute: false,
38
+ }),
39
+ },
40
+ bodyType: "binary",
41
+ body: await file.readContentsAsync("raw"),
42
+ },
43
+ { response: UploadResponseSchema },
44
+ );
45
+
46
+ console.log(`Uploaded: ${result.path_display}`);
47
+ ```
48
+
49
+ For text generated in the API rather than an uploaded file, use `bodyType: "raw"` with a string body and set `Content-Type: application/octet-stream` yourself, since raw mode adds no headers:
50
+
51
+ ```typescript
52
+ const notesText = ["# Meeting notes", "", ...input.notes].join("\n");
53
+
54
+ await ctx.integrations.dropbox.apiRequest(
55
+ {
56
+ method: "POST",
57
+ path: "/2/files/upload",
58
+ headers: {
59
+ "Dropbox-API-Arg": JSON.stringify({
60
+ path: "/Documents/notes.txt",
61
+ mode: "overwrite",
62
+ }),
63
+ "Content-Type": "application/octet-stream",
64
+ },
65
+ bodyType: "raw",
66
+ body: notesText,
67
+ },
68
+ { response: UploadResponseSchema },
69
+ );
70
+ ```
71
+
72
+ Do not send the file as a JSON body. Without `bodyType`, `apiRequest()` rejects a `Uint8Array`/`Buffer` body outright, and JSON-encodes a string body instead of sending it raw, so Dropbox would store the quoted JSON text instead of the file content. `readContentsAsync("binary")` returns base64 text, not bytes; use `readContentsAsync("raw")` for uploads.
73
+
74
+ ### Write Conflict Modes
75
+
76
+ Choose the right `mode` for uploads:
77
+
78
+ ```typescript
79
+ const modes = {
80
+ add: "Never overwrite, fail if file exists",
81
+ overwrite: "Always overwrite existing file",
82
+ update: "Update only if rev matches (optimistic locking)",
83
+ };
84
+
85
+ const dropboxApiArg = {
86
+ path: "/file.txt",
87
+ mode: "add", // or { ".tag": "update", "update": revisionId }
88
+ };
89
+ ```