@superblocksteam/sdk-api 0.0.12 → 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 (125) hide show
  1. package/dist/errors.d.ts +36 -3
  2. package/dist/errors.d.ts.map +1 -1
  3. package/dist/errors.js +244 -8
  4. package/dist/errors.js.map +1 -1
  5. package/dist/errors.test.d.ts +2 -0
  6. package/dist/errors.test.d.ts.map +1 -0
  7. package/dist/errors.test.js +189 -0
  8. package/dist/errors.test.js.map +1 -0
  9. package/dist/integrations/anthropic/types.d.ts +1 -1
  10. package/dist/integrations/base/decode-worker-binary-response.d.ts.map +1 -1
  11. package/dist/integrations/base/decode-worker-binary-response.js +2 -5
  12. package/dist/integrations/base/decode-worker-binary-response.js.map +1 -1
  13. package/dist/integrations/base/index.d.ts +2 -1
  14. package/dist/integrations/base/index.d.ts.map +1 -1
  15. package/dist/integrations/base/index.js +1 -0
  16. package/dist/integrations/base/index.js.map +1 -1
  17. package/dist/integrations/base/request-body.d.ts +29 -0
  18. package/dist/integrations/base/request-body.d.ts.map +1 -0
  19. package/dist/integrations/base/request-body.js +238 -0
  20. package/dist/integrations/base/request-body.js.map +1 -0
  21. package/dist/integrations/base/request-body.test.d.ts +2 -0
  22. package/dist/integrations/base/request-body.test.d.ts.map +1 -0
  23. package/dist/integrations/base/request-body.test.js +466 -0
  24. package/dist/integrations/base/request-body.test.js.map +1 -0
  25. package/dist/integrations/base/rest-api-client-base.d.ts.map +1 -1
  26. package/dist/integrations/base/rest-api-client-base.js +15 -14
  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 +32 -5
  32. package/dist/integrations/base/types.d.ts.map +1 -1
  33. package/dist/integrations/base/types.js +7 -0
  34. package/dist/integrations/base/types.js.map +1 -1
  35. package/dist/integrations/cosmosdb/client.d.ts.map +1 -1
  36. package/dist/integrations/cosmosdb/client.js +2 -5
  37. package/dist/integrations/cosmosdb/client.js.map +1 -1
  38. package/dist/integrations/documentation-resolver.test.js +106 -1
  39. package/dist/integrations/documentation-resolver.test.js.map +1 -1
  40. package/dist/integrations/documentation.test.js +10 -0
  41. package/dist/integrations/documentation.test.js.map +1 -1
  42. package/dist/integrations/dynamodb/client.d.ts.map +1 -1
  43. package/dist/integrations/dynamodb/client.js +2 -5
  44. package/dist/integrations/dynamodb/client.js.map +1 -1
  45. package/dist/integrations/gcs/client.d.ts.map +1 -1
  46. package/dist/integrations/gcs/client.js +2 -5
  47. package/dist/integrations/gcs/client.js.map +1 -1
  48. package/dist/integrations/gsheets/client.d.ts.map +1 -1
  49. package/dist/integrations/gsheets/client.js +2 -5
  50. package/dist/integrations/gsheets/client.js.map +1 -1
  51. package/dist/integrations/mongodb/client.d.ts.map +1 -1
  52. package/dist/integrations/mongodb/client.js +2 -5
  53. package/dist/integrations/mongodb/client.js.map +1 -1
  54. package/dist/integrations/restapiintegration/client.body-types.test.d.ts +2 -0
  55. package/dist/integrations/restapiintegration/client.body-types.test.d.ts.map +1 -0
  56. package/dist/integrations/restapiintegration/client.body-types.test.js +227 -0
  57. package/dist/integrations/restapiintegration/client.body-types.test.js.map +1 -0
  58. package/dist/integrations/restapiintegration/client.test.js +16 -0
  59. package/dist/integrations/restapiintegration/client.test.js.map +1 -1
  60. package/dist/integrations/s3/client.d.ts.map +1 -1
  61. package/dist/integrations/s3/client.js +2 -5
  62. package/dist/integrations/s3/client.js.map +1 -1
  63. package/dist/integrations/salesforce/client.d.ts.map +1 -1
  64. package/dist/integrations/salesforce/client.js +2 -5
  65. package/dist/integrations/salesforce/client.js.map +1 -1
  66. package/dist/integrations/slack/client.d.ts.map +1 -1
  67. package/dist/integrations/slack/client.js +4 -13
  68. package/dist/integrations/slack/client.js.map +1 -1
  69. package/dist/integrations/slack/client.test.js +20 -0
  70. package/dist/integrations/slack/client.test.js.map +1 -1
  71. package/dist/integrations/utils.d.ts +8 -3
  72. package/dist/integrations/utils.d.ts.map +1 -1
  73. package/dist/integrations/utils.js +42 -1
  74. package/dist/integrations/utils.js.map +1 -1
  75. package/package.json +3 -3
  76. package/src/errors.test.ts +257 -0
  77. package/src/errors.ts +304 -10
  78. package/src/integrations/anthropic/README.md +12 -59
  79. package/src/integrations/anthropic/types.ts +1 -1
  80. package/src/integrations/base/decode-worker-binary-response.ts +5 -7
  81. package/src/integrations/base/index.ts +2 -0
  82. package/src/integrations/base/request-body.test.ts +565 -0
  83. package/src/integrations/base/request-body.ts +392 -0
  84. package/src/integrations/base/rest-api-client-base.ts +24 -19
  85. package/src/integrations/base/rest-api-integration-client.ts +8 -7
  86. package/src/integrations/base/types.ts +41 -5
  87. package/src/integrations/box/README.md +4 -60
  88. package/src/integrations/cosmosdb/client.ts +8 -7
  89. package/src/integrations/documentation-resolver.test.ts +130 -1
  90. package/src/integrations/documentation.test.ts +12 -0
  91. package/src/integrations/dropbox/README.md +1 -71
  92. package/src/integrations/dropbox/docs.manifest.json +10 -1
  93. package/src/integrations/dropbox/overlays/upload-unsupported.md +3 -0
  94. package/src/integrations/dropbox/overlays/upload.md +89 -0
  95. package/src/integrations/dynamodb/client.ts +8 -7
  96. package/src/integrations/elasticsearch/README.md +0 -80
  97. package/src/integrations/elasticsearch/docs.manifest.json +10 -1
  98. package/src/integrations/elasticsearch/overlays/bulk-unsupported.md +3 -0
  99. package/src/integrations/elasticsearch/overlays/bulk.md +80 -0
  100. package/src/integrations/gcs/client.ts +8 -7
  101. package/src/integrations/googledrive/README.md +10 -36
  102. package/src/integrations/gsheets/client.ts +8 -7
  103. package/src/integrations/jira/README.md +0 -26
  104. package/src/integrations/jira/docs.manifest.json +10 -1
  105. package/src/integrations/jira/overlays/attachments-unsupported.md +3 -0
  106. package/src/integrations/jira/overlays/attachments.md +30 -0
  107. package/src/integrations/mongodb/client.ts +8 -7
  108. package/src/integrations/restapiintegration/client.body-types.test.ts +323 -0
  109. package/src/integrations/restapiintegration/client.test.ts +23 -0
  110. package/src/integrations/restapiintegration/docs.manifest.json +8 -0
  111. package/src/integrations/restapiintegration/overlays/request-body-types-unsupported.md +7 -0
  112. package/src/integrations/restapiintegration/overlays/request-body-types.md +115 -0
  113. package/src/integrations/s3/client.ts +8 -7
  114. package/src/integrations/salesforce/client.ts +9 -7
  115. package/src/integrations/slack/client.test.ts +30 -0
  116. package/src/integrations/slack/client.ts +13 -19
  117. package/src/integrations/stabilityai/README.md +0 -87
  118. package/src/integrations/stabilityai/docs.manifest.json +10 -1
  119. package/src/integrations/stabilityai/overlays/image-uploads-unsupported.md +3 -0
  120. package/src/integrations/stabilityai/overlays/image-uploads.md +120 -0
  121. package/src/integrations/stripe/README.md +0 -201
  122. package/src/integrations/stripe/docs.manifest.json +10 -1
  123. package/src/integrations/stripe/overlays/request-bodies-unsupported.md +3 -0
  124. package/src/integrations/stripe/overlays/request-bodies.md +204 -0
  125. package/src/integrations/utils.ts +51 -1
@@ -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
 
@@ -859,6 +859,42 @@ describe("resolveIntegrationDocumentation", () => {
859
859
  expect(docs).not.toContain("does not support the `responseType`");
860
860
  });
861
861
 
862
+ it("documents request bodyType only for agents whose sdk-api supports it", async () => {
863
+ const docs = await resolveIntegrationDocumentation("restapiintegration", {
864
+ sdkVersion: "0.0.7",
865
+ });
866
+
867
+ expect(docs).toContain('responseType: "binary"');
868
+ expect(docs).toContain('bodyType: "formUrlencoded"');
869
+ expect(docs).toContain('bodyType: "multipartForm"');
870
+ expect(docs).toContain('bodyType: "binary"');
871
+ expect(docs).not.toContain("does not support the `bodyType`");
872
+ });
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
+
888
+ it("notes bodyType non-support before sdk-api request body support", async () => {
889
+ const docs = await resolveIntegrationDocumentation("restapiintegration", {
890
+ sdkVersion: "0.0.6",
891
+ });
892
+
893
+ expect(docs).toContain('responseType: "binary"');
894
+ expect(docs).toContain("does not support the `bodyType` option");
895
+ expect(docs).not.toContain('bodyType: "formUrlencoded"');
896
+ });
897
+
862
898
  it("notes non-support instead of documenting responseType on older agents", async () => {
863
899
  const docs = await resolveIntegrationDocumentation("restapiintegration", {
864
900
  sdkVersion: "0.0.2",
@@ -869,13 +905,106 @@ describe("resolveIntegrationDocumentation", () => {
869
905
  expect(docs).not.toContain('responseType: "binary"');
870
906
  });
871
907
 
872
- it("stays silent about responseType when the agent reports no sdk-api version", async () => {
908
+ it("stays silent about responseType and request bodyType when the agent reports no sdk-api version", async () => {
873
909
  const docs = await resolveIntegrationDocumentation("restapiintegration");
874
910
 
875
911
  expect(docs).not.toContain("responseType");
912
+ expect(docs).not.toContain("bodyType");
876
913
  });
877
914
  });
878
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
+
879
1008
  it("blocks overlay paths outside the plugin directory", async () => {
880
1009
  const integrationsDirectory = createPluginDocsFixture("dropbox", {
881
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
+ ```
@@ -8,7 +8,10 @@
8
8
 
9
9
  import type { z } from "zod";
10
10
 
11
- import { RestApiValidationError } from "../../errors.js";
11
+ import {
12
+ RestApiValidationError,
13
+ restApiValidationErrorFromZodError,
14
+ } from "../../errors.js";
12
15
  import { IntegrationError } from "../../runtime/errors.js";
13
16
  import type { QueryExecutor, TraceMetadata } from "../registry.js";
14
17
  import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
@@ -85,12 +88,10 @@ export class DynamoDBClientImpl
85
88
  const parseResult = schema.safeParse(result);
86
89
 
87
90
  if (!parseResult.success) {
88
- throw new RestApiValidationError(
89
- `Result validation failed: ${parseResult.error.message}`,
90
- {
91
- zodError: parseResult.error,
92
- data: result,
93
- },
91
+ throw restApiValidationErrorFromZodError(
92
+ "Result validation failed",
93
+ parseResult.error,
94
+ result,
94
95
  );
95
96
  }
96
97
 
@@ -226,70 +226,6 @@ const result = await ctx.integrations.es.apiRequest(
226
226
  );
227
227
  ```
228
228
 
229
- ### Bulk Operations
230
-
231
- ```typescript
232
- const BulkResponseSchema = z.object({
233
- took: z.number(),
234
- errors: z.boolean(),
235
- items: z.array(
236
- z.object({
237
- index: z
238
- .object({
239
- _index: z.string(),
240
- _id: z.string(),
241
- status: z.number(),
242
- result: z.string().optional(),
243
- error: z
244
- .object({
245
- type: z.string(),
246
- reason: z.string(),
247
- })
248
- .optional(),
249
- })
250
- .optional(),
251
- delete: z
252
- .object({
253
- _index: z.string(),
254
- _id: z.string(),
255
- status: z.number(),
256
- result: z.string().optional(),
257
- })
258
- .optional(),
259
- }),
260
- ),
261
- });
262
-
263
- // Bulk index
264
- const result = await ctx.integrations.es.apiRequest(
265
- {
266
- method: "POST",
267
- path: "/_bulk",
268
- headers: {
269
- "Content-Type": "application/x-ndjson",
270
- },
271
- body:
272
- [
273
- { index: { _index: "products", _id: "1" } },
274
- { name: "Product 1", price: 10 },
275
- { index: { _index: "products", _id: "2" } },
276
- { name: "Product 2", price: 20 },
277
- ]
278
- .map(JSON.stringify)
279
- .join("\n") + "\n",
280
- },
281
- { response: BulkResponseSchema },
282
- );
283
-
284
- if (result.errors) {
285
- result.items.forEach((item) => {
286
- if (item.index?.error) {
287
- console.error(`Failed to index: ${item.index.error.reason}`);
288
- }
289
- });
290
- }
291
- ```
292
-
293
229
  ### Aggregations
294
230
 
295
231
  ```typescript
@@ -493,22 +429,6 @@ const body = {
493
429
  };
494
430
  ```
495
431
 
496
- ### Bulk Format
497
-
498
- Bulk operations require NDJSON format:
499
-
500
- ```typescript
501
- // WRONG - Regular JSON array
502
- const body = [{ index: { _index: "products" } }, { name: "Product" }];
503
-
504
- // CORRECT - Newline-delimited JSON
505
- const body = '{"index":{"_index":"products"}}\n{"name":"Product"}\n';
506
-
507
- // Or build programmatically
508
- const operations = [{ index: { _index: "products" } }, { name: "Product" }];
509
- const body = operations.map(JSON.stringify).join("\n") + "\n";
510
- ```
511
-
512
432
  ### Total Hits Accuracy
513
433
 
514
434
  For large result sets, total is approximate by default:
@@ -1,5 +1,14 @@
1
1
  {
2
2
  "pluginId": "elasticsearch",
3
3
  "base": "README.md",
4
- "overlays": []
4
+ "overlays": [
5
+ {
6
+ "file": "overlays/bulk.md",
7
+ "sdkVersionRange": ">=0.0.7"
8
+ },
9
+ {
10
+ "file": "overlays/bulk-unsupported.md",
11
+ "sdkVersionRange": "<0.0.7"
12
+ }
13
+ ]
5
14
  }
@@ -0,0 +1,3 @@
1
+ ## Bulk Operations
2
+
3
+ This agent's sdk-api cannot call `/_bulk` through `apiRequest()` because it serializes every body as JSON, and the bulk API requires newline-delimited JSON. Do not generate bulk code until the agent is upgraded; index, update, or delete documents one call at a time as documented above.
@@ -0,0 +1,80 @@
1
+ ## Bulk Operations
2
+
3
+ `/_bulk` expects newline-delimited JSON, so pass `bodyType: "raw"` with the pre-built NDJSON string and set `Content-Type: application/x-ndjson`. Raw mode adds no headers of its own. Without `bodyType`, `apiRequest()` would JSON-encode the string a second time and Elasticsearch would reject it.
4
+
5
+ Bulk operations require the request bodies to be newline-delimited JSON, not a JSON array:
6
+
7
+ ```typescript
8
+ // WRONG - Regular JSON array
9
+ const body = [{ index: { _index: "products" } }, { name: "Product" }];
10
+
11
+ // CORRECT - Newline-delimited JSON
12
+ const body = '{"index":{"_index":"products"}}\n{"name":"Product"}\n';
13
+
14
+ // Or build programmatically
15
+ const operations = [{ index: { _index: "products" } }, { name: "Product" }];
16
+ const body = operations.map(JSON.stringify).join("\n") + "\n";
17
+ ```
18
+
19
+ ```typescript
20
+ const BulkResponseSchema = z.object({
21
+ took: z.number(),
22
+ errors: z.boolean(),
23
+ items: z.array(
24
+ z.object({
25
+ index: z
26
+ .object({
27
+ _index: z.string(),
28
+ _id: z.string(),
29
+ status: z.number(),
30
+ result: z.string().optional(),
31
+ error: z
32
+ .object({
33
+ type: z.string(),
34
+ reason: z.string(),
35
+ })
36
+ .optional(),
37
+ })
38
+ .optional(),
39
+ delete: z
40
+ .object({
41
+ _index: z.string(),
42
+ _id: z.string(),
43
+ status: z.number(),
44
+ result: z.string().optional(),
45
+ })
46
+ .optional(),
47
+ }),
48
+ ),
49
+ });
50
+
51
+ // Bulk index
52
+ const result = await ctx.integrations.es.apiRequest(
53
+ {
54
+ method: "POST",
55
+ path: "/_bulk",
56
+ headers: {
57
+ "Content-Type": "application/x-ndjson",
58
+ },
59
+ bodyType: "raw",
60
+ body:
61
+ [
62
+ { index: { _index: "products", _id: "1" } },
63
+ { name: "Product 1", price: 10 },
64
+ { index: { _index: "products", _id: "2" } },
65
+ { name: "Product 2", price: 20 },
66
+ ]
67
+ .map(JSON.stringify)
68
+ .join("\n") + "\n",
69
+ },
70
+ { response: BulkResponseSchema },
71
+ );
72
+
73
+ if (result.errors) {
74
+ result.items.forEach((item) => {
75
+ if (item.index?.error) {
76
+ console.error(`Failed to index: ${item.index.error.reason}`);
77
+ }
78
+ });
79
+ }
80
+ ```
@@ -9,7 +9,10 @@ import type { z } from "zod";
9
9
 
10
10
  import type { Plugin as GCSPlugin } from "@superblocksteam/types/dist/src/plugins/gcs/v1/plugin_pb";
11
11
 
12
- import { RestApiValidationError } from "../../errors.js";
12
+ import {
13
+ RestApiValidationError,
14
+ restApiValidationErrorFromZodError,
15
+ } from "../../errors.js";
13
16
  import { IntegrationError } from "../../runtime/errors.js";
14
17
  import type { QueryExecutor, TraceMetadata } from "../registry.js";
15
18
  import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
@@ -71,12 +74,10 @@ export class GCSClientImpl implements GCSClient, IntegrationClientImpl {
71
74
  const parseResult = schema.safeParse(result);
72
75
 
73
76
  if (!parseResult.success) {
74
- throw new RestApiValidationError(
75
- `Result validation failed: ${parseResult.error.message}`,
76
- {
77
- zodError: parseResult.error,
78
- data: result,
79
- },
77
+ throw restApiValidationErrorFromZodError(
78
+ "Result validation failed",
79
+ parseResult.error,
80
+ result,
80
81
  );
81
82
  }
82
83