@superblocksteam/sdk-api 2.0.165-next.0 → 2.0.166-next.0
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.
- package/README.md +1 -2
- package/dist/errors.d.ts +36 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +244 -8
- package/dist/errors.js.map +1 -1
- package/dist/errors.test.d.ts +2 -0
- package/dist/errors.test.d.ts.map +1 -0
- package/dist/errors.test.js +189 -0
- package/dist/errors.test.js.map +1 -0
- package/dist/integrations/base/decode-worker-binary-response.d.ts.map +1 -1
- package/dist/integrations/base/decode-worker-binary-response.js +2 -5
- package/dist/integrations/base/decode-worker-binary-response.js.map +1 -1
- package/dist/integrations/base/index.d.ts +2 -1
- package/dist/integrations/base/index.d.ts.map +1 -1
- package/dist/integrations/base/index.js +1 -0
- package/dist/integrations/base/index.js.map +1 -1
- package/dist/integrations/base/request-body.d.ts +29 -0
- package/dist/integrations/base/request-body.d.ts.map +1 -0
- package/dist/integrations/base/request-body.js +238 -0
- package/dist/integrations/base/request-body.js.map +1 -0
- package/dist/integrations/base/request-body.test.d.ts +2 -0
- package/dist/integrations/base/request-body.test.d.ts.map +1 -0
- package/dist/integrations/base/request-body.test.js +466 -0
- package/dist/integrations/base/request-body.test.js.map +1 -0
- package/dist/integrations/base/rest-api-client-base.d.ts.map +1 -1
- package/dist/integrations/base/rest-api-client-base.js +15 -14
- package/dist/integrations/base/rest-api-client-base.js.map +1 -1
- package/dist/integrations/base/rest-api-integration-client.d.ts +2 -2
- package/dist/integrations/base/rest-api-integration-client.d.ts.map +1 -1
- package/dist/integrations/base/rest-api-integration-client.js +5 -6
- package/dist/integrations/base/rest-api-integration-client.js.map +1 -1
- package/dist/integrations/base/types.d.ts +55 -11
- package/dist/integrations/base/types.d.ts.map +1 -1
- package/dist/integrations/base/types.js +7 -0
- package/dist/integrations/base/types.js.map +1 -1
- package/dist/integrations/cosmosdb/client.d.ts.map +1 -1
- package/dist/integrations/cosmosdb/client.js +2 -5
- package/dist/integrations/cosmosdb/client.js.map +1 -1
- package/dist/integrations/documentation-resolver.test.js +106 -1
- package/dist/integrations/documentation-resolver.test.js.map +1 -1
- package/dist/integrations/dynamodb/client.d.ts.map +1 -1
- package/dist/integrations/dynamodb/client.js +2 -5
- package/dist/integrations/dynamodb/client.js.map +1 -1
- package/dist/integrations/gcs/client.d.ts.map +1 -1
- package/dist/integrations/gcs/client.js +2 -5
- package/dist/integrations/gcs/client.js.map +1 -1
- package/dist/integrations/gsheets/client.d.ts.map +1 -1
- package/dist/integrations/gsheets/client.js +2 -5
- package/dist/integrations/gsheets/client.js.map +1 -1
- package/dist/integrations/mongodb/client.d.ts.map +1 -1
- package/dist/integrations/mongodb/client.js +2 -5
- package/dist/integrations/mongodb/client.js.map +1 -1
- package/dist/integrations/restapiintegration/client.body-types.test.d.ts +2 -0
- package/dist/integrations/restapiintegration/client.body-types.test.d.ts.map +1 -0
- package/dist/integrations/restapiintegration/client.body-types.test.js +227 -0
- package/dist/integrations/restapiintegration/client.body-types.test.js.map +1 -0
- package/dist/integrations/restapiintegration/client.test.js +56 -30
- package/dist/integrations/restapiintegration/client.test.js.map +1 -1
- package/dist/integrations/s3/client.d.ts.map +1 -1
- package/dist/integrations/s3/client.js +2 -5
- package/dist/integrations/s3/client.js.map +1 -1
- package/dist/integrations/salesforce/client.d.ts.map +1 -1
- package/dist/integrations/salesforce/client.js +2 -5
- package/dist/integrations/salesforce/client.js.map +1 -1
- package/dist/integrations/slack/client.d.ts.map +1 -1
- package/dist/integrations/slack/client.js +4 -13
- package/dist/integrations/slack/client.js.map +1 -1
- package/dist/integrations/slack/client.test.js +20 -0
- package/dist/integrations/slack/client.test.js.map +1 -1
- package/dist/integrations/utils.d.ts +8 -3
- package/dist/integrations/utils.d.ts.map +1 -1
- package/dist/integrations/utils.js +42 -1
- package/dist/integrations/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/errors.test.ts +257 -0
- package/src/errors.ts +304 -10
- package/src/integrations/base/decode-worker-binary-response.ts +5 -7
- package/src/integrations/base/index.ts +3 -0
- package/src/integrations/base/request-body.test.ts +565 -0
- package/src/integrations/base/request-body.ts +392 -0
- package/src/integrations/base/rest-api-client-base.ts +24 -19
- package/src/integrations/base/rest-api-integration-client.ts +14 -10
- package/src/integrations/base/types.ts +66 -11
- package/src/integrations/box/README.md +4 -60
- package/src/integrations/cosmosdb/client.ts +8 -7
- package/src/integrations/documentation-resolver.test.ts +130 -1
- package/src/integrations/dropbox/README.md +1 -71
- package/src/integrations/dropbox/docs.manifest.json +10 -1
- package/src/integrations/dropbox/overlays/upload-unsupported.md +3 -0
- package/src/integrations/dropbox/overlays/upload.md +89 -0
- package/src/integrations/dynamodb/client.ts +8 -7
- package/src/integrations/elasticsearch/README.md +0 -80
- package/src/integrations/elasticsearch/docs.manifest.json +10 -1
- package/src/integrations/elasticsearch/overlays/bulk-unsupported.md +3 -0
- package/src/integrations/elasticsearch/overlays/bulk.md +80 -0
- package/src/integrations/gcs/client.ts +8 -7
- package/src/integrations/googledrive/README.md +10 -36
- package/src/integrations/gsheets/client.ts +8 -7
- package/src/integrations/jira/README.md +0 -26
- package/src/integrations/jira/docs.manifest.json +10 -1
- package/src/integrations/jira/overlays/attachments-unsupported.md +3 -0
- package/src/integrations/jira/overlays/attachments.md +30 -0
- package/src/integrations/lakebase/README.md +1 -3
- package/src/integrations/mongodb/client.ts +8 -7
- package/src/integrations/postgres/README.md +1 -3
- package/src/integrations/restapiintegration/client.body-types.test.ts +323 -0
- package/src/integrations/restapiintegration/client.test.ts +68 -32
- package/src/integrations/restapiintegration/docs.manifest.json +8 -0
- package/src/integrations/restapiintegration/overlays/request-body-types-unsupported.md +7 -0
- package/src/integrations/restapiintegration/overlays/request-body-types.md +115 -0
- package/src/integrations/restapiintegration/overlays/response-types-binary.md +4 -8
- package/src/integrations/s3/client.ts +8 -7
- package/src/integrations/salesforce/client.ts +9 -7
- package/src/integrations/slack/client.test.ts +30 -0
- package/src/integrations/slack/client.ts +13 -19
- package/src/integrations/snowflakepostgres/README.md +1 -3
- package/src/integrations/stabilityai/README.md +0 -87
- package/src/integrations/stabilityai/docs.manifest.json +10 -1
- package/src/integrations/stabilityai/overlays/image-uploads-unsupported.md +3 -0
- package/src/integrations/stabilityai/overlays/image-uploads.md +120 -0
- package/src/integrations/stripe/README.md +0 -201
- package/src/integrations/stripe/docs.manifest.json +10 -1
- package/src/integrations/stripe/overlays/request-bodies-unsupported.md +3 -0
- package/src/integrations/stripe/overlays/request-bodies.md +204 -0
- package/src/integrations/utils.ts +51 -1
|
@@ -227,50 +227,55 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
227
227
|
expect(executeQuery).toHaveBeenCalled();
|
|
228
228
|
});
|
|
229
229
|
|
|
230
|
-
it("
|
|
230
|
+
it("returns decoded bytes and ignores a response schema passed at runtime", async () => {
|
|
231
|
+
// Models sandboxed API code: its Uint8Array is a different constructor,
|
|
232
|
+
// so a caller's z.instanceof(Uint8Array) rejects every SDK-decoded body.
|
|
233
|
+
const crossRealmInstanceof = z.custom<Uint8Array>(() => false);
|
|
231
234
|
const { client } = createClient(workerBufferPayload(PDF_BYTES));
|
|
232
235
|
|
|
233
|
-
const result = await
|
|
236
|
+
const result = await callApiRequest(
|
|
237
|
+
client,
|
|
234
238
|
{ method: "GET", path: "/file.pdf", responseType: "binary" },
|
|
235
|
-
{
|
|
236
|
-
response: z
|
|
237
|
-
.instanceof(Uint8Array)
|
|
238
|
-
.refine((bytes) => bytes[0] === 0x25)
|
|
239
|
-
.transform((bytes) => bytes.slice(1)),
|
|
240
|
-
},
|
|
239
|
+
{ response: crossRealmInstanceof },
|
|
241
240
|
);
|
|
242
241
|
|
|
243
|
-
expect(
|
|
242
|
+
expect(result).toBeInstanceOf(Uint8Array);
|
|
243
|
+
expect(result instanceof Uint8Array && Array.from(result)).toEqual(
|
|
244
|
+
PDF_BYTES,
|
|
245
|
+
);
|
|
244
246
|
});
|
|
245
247
|
|
|
246
|
-
it("
|
|
247
|
-
const { client } = createClient(
|
|
248
|
+
it("still validates the request body for binary responses", async () => {
|
|
249
|
+
const { client, executeQuery } = createClient(
|
|
250
|
+
workerBufferPayload(PDF_BYTES),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const dynamicBody: unknown = JSON.parse('{"pages":"many"}');
|
|
248
254
|
|
|
249
255
|
await expect(
|
|
250
256
|
client.apiRequest(
|
|
251
|
-
{ method: "GET", path: "/file.pdf", responseType: "binary" },
|
|
252
257
|
{
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
258
|
+
method: "POST",
|
|
259
|
+
path: "/render.pdf",
|
|
260
|
+
body: dynamicBody,
|
|
261
|
+
responseType: "binary",
|
|
256
262
|
},
|
|
263
|
+
{ body: z.object({ pages: z.number() }) },
|
|
257
264
|
),
|
|
258
265
|
).rejects.toThrow(RestApiValidationError);
|
|
266
|
+
expect(executeQuery).not.toHaveBeenCalled();
|
|
259
267
|
});
|
|
260
268
|
|
|
261
|
-
it("redacts binary data from
|
|
262
|
-
const bytes = Array.from({ length: 4096 }, () => 0);
|
|
269
|
+
it("redacts binary data from malformed-body errors", async () => {
|
|
270
|
+
const bytes = [...Array.from({ length: 4096 }, () => 0), 256];
|
|
263
271
|
const { client } = createClient(workerBufferPayload(bytes));
|
|
264
272
|
|
|
265
273
|
try {
|
|
266
|
-
await client.apiRequest(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
.refine((value) => value.byteLength < 1024),
|
|
272
|
-
},
|
|
273
|
-
);
|
|
274
|
+
await client.apiRequest({
|
|
275
|
+
method: "GET",
|
|
276
|
+
path: "/file.pdf",
|
|
277
|
+
responseType: "binary",
|
|
278
|
+
});
|
|
274
279
|
} catch (error) {
|
|
275
280
|
expect(error).toBeInstanceOf(RestApiValidationError);
|
|
276
281
|
if (!(error instanceof RestApiValidationError)) {
|
|
@@ -282,7 +287,30 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
282
287
|
});
|
|
283
288
|
return;
|
|
284
289
|
}
|
|
285
|
-
throw new Error("Expected binary
|
|
290
|
+
throw new Error("Expected malformed binary decoding to fail");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("keeps binary bytes out of the thrown message, not just details.zodError", async () => {
|
|
294
|
+
const bytes = [137, 80, 78, 71, 256];
|
|
295
|
+
const { client } = createClient(workerBufferPayload(bytes));
|
|
296
|
+
|
|
297
|
+
let caught: unknown;
|
|
298
|
+
try {
|
|
299
|
+
await client.apiRequest({
|
|
300
|
+
method: "GET",
|
|
301
|
+
path: "/file.png",
|
|
302
|
+
responseType: "binary",
|
|
303
|
+
});
|
|
304
|
+
} catch (error) {
|
|
305
|
+
caught = error;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
expect(caught).toBeInstanceOf(RestApiValidationError);
|
|
309
|
+
if (!(caught instanceof RestApiValidationError)) throw caught;
|
|
310
|
+
expect(caught.message).not.toContain("137");
|
|
311
|
+
expect(JSON.stringify(caught.details.zodError.issues)).not.toContain(
|
|
312
|
+
"137",
|
|
313
|
+
);
|
|
286
314
|
});
|
|
287
315
|
});
|
|
288
316
|
|
|
@@ -438,24 +466,32 @@ describe("RestApiIntegrationPluginClientImpl", () => {
|
|
|
438
466
|
await wrongBinary;
|
|
439
467
|
});
|
|
440
468
|
|
|
441
|
-
it("rejects
|
|
469
|
+
it("rejects response schemas on binary requests but accepts body schemas", async () => {
|
|
442
470
|
const { client: binaryClient } = createClient(
|
|
443
471
|
workerBufferPayload(PDF_BYTES),
|
|
444
472
|
);
|
|
445
473
|
|
|
446
|
-
// @ts-expect-error binary
|
|
447
|
-
binaryClient.apiRequest(
|
|
474
|
+
// @ts-expect-error binary responses do not take a response schema
|
|
475
|
+
const withResponse = binaryClient.apiRequest(
|
|
448
476
|
{
|
|
449
477
|
method: "GET",
|
|
450
478
|
path: "/file.pdf",
|
|
451
479
|
responseType: "binary",
|
|
452
480
|
},
|
|
481
|
+
{ response: z.instanceof(Uint8Array) },
|
|
482
|
+
);
|
|
483
|
+
await withResponse;
|
|
484
|
+
|
|
485
|
+
const withBody: Promise<Uint8Array> = binaryClient.apiRequest(
|
|
453
486
|
{
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
487
|
+
method: "POST",
|
|
488
|
+
path: "/render.pdf",
|
|
489
|
+
body: { pages: 1 },
|
|
490
|
+
responseType: "binary",
|
|
457
491
|
},
|
|
492
|
+
{ body: z.object({ pages: z.number() }) },
|
|
458
493
|
);
|
|
494
|
+
expect(Array.from(await withBody)).toEqual(PDF_BYTES);
|
|
459
495
|
});
|
|
460
496
|
|
|
461
497
|
it("rejects response types outside the public SDK contract", async () => {
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
{
|
|
14
14
|
"file": "overlays/response-types-unsupported.md",
|
|
15
15
|
"sdkVersionRange": "<0.0.3"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"file": "overlays/request-body-types.md",
|
|
19
|
+
"sdkVersionRange": ">=0.0.7"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"file": "overlays/request-body-types-unsupported.md",
|
|
23
|
+
"sdkVersionRange": "<0.0.7"
|
|
16
24
|
}
|
|
17
25
|
]
|
|
18
26
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
## Request Body Types
|
|
2
|
+
|
|
3
|
+
This organization's agent does not support the `bodyType` option on `apiRequest()` yet. Its worker runs an sdk-api version that always serializes request bodies as JSON, so raw strings, binary uploads, multipart/form-data, and application/x-www-form-urlencoded payloads cannot be sent from SDK APIs until the agent is upgraded.
|
|
4
|
+
|
|
5
|
+
Do not generate code that passes a `bodyType` value before that upgrade. It may typecheck against a newer local copy of the SDK but fail at runtime on this agent.
|
|
6
|
+
|
|
7
|
+
When the user needs a non-JSON request body, tell them the required action: an administrator must upgrade this organization's Superblocks agent (On-Premise Agent / OPA) to a newer version that supports the `bodyType` option. Until that upgrade happens, there is no code-level workaround.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
## Request Body Types
|
|
2
|
+
|
|
3
|
+
`apiRequest()` serializes request bodies as JSON by default. For endpoints that expect another encoding, pass `bodyType`:
|
|
4
|
+
|
|
5
|
+
- `"json"` for JSON payloads. This is the default.
|
|
6
|
+
- `"raw"` for caller-encoded string bodies such as XML, CSV, or plain text.
|
|
7
|
+
- `"formUrlencoded"` for `application/x-www-form-urlencoded` payloads.
|
|
8
|
+
- `"multipartForm"` for multipart fields and file parts.
|
|
9
|
+
- `"binary"` for byte payloads. Pass bytes such as a `Uint8Array`, `ArrayBuffer`, `DataView`, or `Buffer`.
|
|
10
|
+
|
|
11
|
+
### Form URL-Encoded Requests
|
|
12
|
+
|
|
13
|
+
Use `bodyType: "formUrlencoded"` for OAuth token exchanges and other APIs that expect HTML form encoding. Arrays repeat the key, so `tags: ["a", "b"]` encodes as `tags=a&tags=b`. APIs that expect bracket keys need the brackets in the key, and nested fields must be flattened with bracket notation:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
const token = await ctx.integrations.authApi.apiRequest(
|
|
17
|
+
{
|
|
18
|
+
method: "POST",
|
|
19
|
+
path: "/oauth/token",
|
|
20
|
+
bodyType: "formUrlencoded",
|
|
21
|
+
body: {
|
|
22
|
+
grant_type: "client_credentials",
|
|
23
|
+
client_id: input.clientId,
|
|
24
|
+
scope: "read:orders write:orders",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{ response: TokenResponseSchema },
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const order = await ctx.integrations.ordersApi.apiRequest(
|
|
31
|
+
{
|
|
32
|
+
method: "POST",
|
|
33
|
+
path: "/v1/orders",
|
|
34
|
+
bodyType: "formUrlencoded",
|
|
35
|
+
body: {
|
|
36
|
+
amount: 2000,
|
|
37
|
+
currency: "usd",
|
|
38
|
+
"metadata[order_id]": "6735",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{ response: OrderResponseSchema },
|
|
42
|
+
);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The SDK adds `Content-Type: application/x-www-form-urlencoded` unless you already provided a `Content-Type` header.
|
|
46
|
+
Fields whose value is `undefined` are omitted. Empty arrays are omitted. The body must encode at least one field.
|
|
47
|
+
|
|
48
|
+
### Binary Uploads
|
|
49
|
+
|
|
50
|
+
Use `bodyType: "binary"` for APIs that accept the request body as bytes, such as SharePoint-style upload endpoints. For an uploaded file, pass the `Uint8Array` returned by `await file.readContentsAsync("raw")` directly as the body:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
const file = input.upload.files[0];
|
|
54
|
+
|
|
55
|
+
await ctx.integrations.sharepoint.apiRequest(
|
|
56
|
+
{
|
|
57
|
+
method: "PUT",
|
|
58
|
+
path: `/sites/${siteId}/drive/root:/reports/${file.name}:/content`,
|
|
59
|
+
bodyType: "binary",
|
|
60
|
+
body: await file.readContentsAsync("raw"),
|
|
61
|
+
headers: { "Content-Type": file.type || "application/octet-stream" },
|
|
62
|
+
},
|
|
63
|
+
{ response: UploadResponseSchema },
|
|
64
|
+
);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
For bytes that do not come from an uploaded file, pass a byte container supported by the SDK, such as `Uint8Array`, `ArrayBuffer`, `DataView`, or `Buffer`. Do not convert file bytes to a string. `readContentsAsync("binary")` returns base64 text, not bytes. Never send that text as a file body. Use `readContentsAsync("raw")` instead. If only base64 text is available, decode it with `Buffer.from(text, "base64")` and send the result with `bodyType: "binary"`.
|
|
68
|
+
|
|
69
|
+
The SDK sends the exact byte window of a byte view, so `bytes.subarray(start, end)` uploads a slice. It adds `Content-Type: application/octet-stream` unless you already provided a `Content-Type` header such as `application/pdf`.
|
|
70
|
+
|
|
71
|
+
Binary request bodies must be non-empty; a zero-length body throws a validation error.
|
|
72
|
+
|
|
73
|
+
### Multipart Uploads
|
|
74
|
+
|
|
75
|
+
Use `bodyType: "multipartForm"` for endpoints that expect fields plus file parts. File parts must be wrapped as `{ value, filename }`; a bare binary value without that wrapper is rejected with a message naming the required `{ value, filename }` shape. A file part `value` is a string or bytes such as the `Uint8Array` returned by `await file.readContentsAsync("raw")`. `readContentsAsync("binary")` returns base64 text, not bytes; never pass that text as a file part value, it will be UTF-8 encoded a second time and silently corrupt the upload. Use `readContentsAsync("raw")` for file part bytes.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const invoice = input.upload.files[0];
|
|
79
|
+
|
|
80
|
+
const upload = await ctx.integrations.filesApi.apiRequest(
|
|
81
|
+
{
|
|
82
|
+
method: "POST",
|
|
83
|
+
path: "/files",
|
|
84
|
+
bodyType: "multipartForm",
|
|
85
|
+
body: {
|
|
86
|
+
folder: "invoices",
|
|
87
|
+
invoice: {
|
|
88
|
+
value: await invoice.readContentsAsync("raw"),
|
|
89
|
+
filename: invoice.name,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{ response: UploadResponseSchema },
|
|
94
|
+
);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Do not set `Content-Type` for multipart requests. The worker generates the multipart boundary during execution.
|
|
98
|
+
Do not set per-part `contentType`. Arrays repeat the key, so `files: [partA, partB]` sends two `files` parts. Fields whose value is `undefined` are omitted. Empty file parts are allowed.
|
|
99
|
+
|
|
100
|
+
### Raw String Bodies
|
|
101
|
+
|
|
102
|
+
Use `bodyType: "raw"` for pre-encoded text bodies. The SDK does not add a `Content-Type` header in raw mode, so provide the header expected by the API. Prefer `"json"`, `"formUrlencoded"`, `"multipartForm"`, or `"binary"` when the target API fits one of those shapes; reach for `"raw"` only when the API needs a pre-encoded format none of the typed body types cover, such as SOAP/XML or a signed payload:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
await ctx.integrations.legacyApi.apiRequest(
|
|
106
|
+
{
|
|
107
|
+
method: "POST",
|
|
108
|
+
path: "/soap",
|
|
109
|
+
bodyType: "raw",
|
|
110
|
+
body: `<Envelope><Body>${payload}</Body></Envelope>`,
|
|
111
|
+
headers: { "Content-Type": "text/xml" },
|
|
112
|
+
},
|
|
113
|
+
{ response: SoapResponseSchema },
|
|
114
|
+
);
|
|
115
|
+
```
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
- `"binary"` for PDFs and other byte payloads. The result is a `Uint8Array`
|
|
6
6
|
- `"text"` for XML, CSV, HTML, and other decoded strings
|
|
7
7
|
|
|
8
|
-
With `responseType: "text"`
|
|
8
|
+
With `responseType: "text"` the response schema may be omitted, and the raw string is returned typed `unknown`. With `responseType: "binary"` never pass a response schema. The SDK decodes and validates the bytes and returns a `Uint8Array`:
|
|
9
9
|
|
|
10
10
|
```typescript
|
|
11
11
|
const xml = await ctx.integrations.legacyApi.apiRequest({
|
|
@@ -23,7 +23,7 @@ const pdf = await ctx.integrations.legacyApi.apiRequest({
|
|
|
23
23
|
// pdf is a Uint8Array
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
To validate after decoding, pass a schema that matches the decoded
|
|
26
|
+
To validate a text response after decoding, pass a schema that matches the decoded string:
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
29
|
const xml = await ctx.integrations.legacyApi.apiRequest(
|
|
@@ -31,14 +31,10 @@ const xml = await ctx.integrations.legacyApi.apiRequest(
|
|
|
31
31
|
{ response: z.string() },
|
|
32
32
|
);
|
|
33
33
|
// xml is typed string
|
|
34
|
-
|
|
35
|
-
const pdf = await ctx.integrations.legacyApi.apiRequest(
|
|
36
|
-
{ method: "GET", path: "/file.pdf", responseType: "binary" },
|
|
37
|
-
{ response: z.instanceof(Uint8Array) },
|
|
38
|
-
);
|
|
39
|
-
// pdf is typed Uint8Array
|
|
40
34
|
```
|
|
41
35
|
|
|
36
|
+
Do not use `z.instanceof(Uint8Array)` anywhere in an API, including the response or output schema. API code runs in a sandbox where the bytes the SDK returns are not instances of the API code's `Uint8Array`, so that check always fails. Check binary content directly in `run()` instead, for example `if (pdf.byteLength === 0) throw new Error("Empty PDF")`.
|
|
37
|
+
|
|
42
38
|
Convert binary data to a JSON-safe representation before returning it from an SDK API:
|
|
43
39
|
|
|
44
40
|
```typescript
|
|
@@ -9,7 +9,10 @@ import type { z } from "zod";
|
|
|
9
9
|
|
|
10
10
|
import type { Plugin as S3Plugin } from "@superblocksteam/types/dist/src/plugins/s3/v1/plugin_pb";
|
|
11
11
|
|
|
12
|
-
import {
|
|
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 S3ClientImpl implements S3Client, IntegrationClientImpl {
|
|
|
71
74
|
const parseResult = schema.safeParse(result);
|
|
72
75
|
|
|
73
76
|
if (!parseResult.success) {
|
|
74
|
-
throw
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
data: result,
|
|
79
|
-
},
|
|
77
|
+
throw restApiValidationErrorFromZodError(
|
|
78
|
+
"Result validation failed",
|
|
79
|
+
parseResult.error,
|
|
80
|
+
result,
|
|
80
81
|
);
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import type { z } from "zod";
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
QueryValidationError,
|
|
12
|
+
RestApiValidationError,
|
|
13
|
+
restApiValidationErrorFromZodError,
|
|
14
|
+
} from "../../errors.js";
|
|
11
15
|
import { IntegrationError } from "../../runtime/errors.js";
|
|
12
16
|
import type { QueryExecutor, TraceMetadata } from "../registry.js";
|
|
13
17
|
import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
|
|
@@ -205,12 +209,10 @@ export class SalesforceClientImpl
|
|
|
205
209
|
|
|
206
210
|
const parseResult = schema.safeParse(result);
|
|
207
211
|
if (!parseResult.success) {
|
|
208
|
-
throw
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
data: result,
|
|
213
|
-
},
|
|
212
|
+
throw restApiValidationErrorFromZodError(
|
|
213
|
+
"Result validation failed",
|
|
214
|
+
parseResult.error,
|
|
215
|
+
result,
|
|
214
216
|
);
|
|
215
217
|
}
|
|
216
218
|
|
|
@@ -307,6 +307,36 @@ describe("SlackClientImpl", () => {
|
|
|
307
307
|
expect(request.params).toEqual([{ key: "unfurl_links", value: "false" }]);
|
|
308
308
|
});
|
|
309
309
|
|
|
310
|
+
it("sends formUrlencoded bodies through the shared request builder", async () => {
|
|
311
|
+
const { client, executeQuery } = createClient({
|
|
312
|
+
ok: true,
|
|
313
|
+
channel: "C123",
|
|
314
|
+
ts: "1234567890.123456",
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
await client.apiRequest(
|
|
318
|
+
{
|
|
319
|
+
method: "POST",
|
|
320
|
+
path: "/chat.postMessage",
|
|
321
|
+
body: { channel: "#alerts", text: "hello" },
|
|
322
|
+
bodyType: "formUrlencoded",
|
|
323
|
+
},
|
|
324
|
+
{ response: PostMessageSchema },
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
expect(executeQuery).toHaveBeenCalledWith(
|
|
328
|
+
expect.objectContaining({
|
|
329
|
+
body: expect.any(String),
|
|
330
|
+
bodyType: "rawBody",
|
|
331
|
+
headers: [
|
|
332
|
+
{ key: "Content-Type", value: "application/x-www-form-urlencoded" },
|
|
333
|
+
],
|
|
334
|
+
}),
|
|
335
|
+
undefined,
|
|
336
|
+
undefined,
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
|
|
310
340
|
it("passes trace metadata to executeQuery", async () => {
|
|
311
341
|
const { client, executeQuery } = createClient({
|
|
312
342
|
ok: true,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type { z } from "zod";
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { restApiValidationErrorFromZodError } from "../../errors.js";
|
|
12
12
|
import { RestApiClientBase } from "../base/rest-api-client-base.js";
|
|
13
13
|
import type { ApiRequestOptions } from "../base/types.js";
|
|
14
14
|
import type { TraceMetadata } from "../registry.js";
|
|
@@ -51,24 +51,20 @@ export class SlackClientImpl extends RestApiClientBase implements SlackClient {
|
|
|
51
51
|
"ok" in result &&
|
|
52
52
|
(result as Record<string, unknown>).ok === false
|
|
53
53
|
) {
|
|
54
|
-
throw
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
data: result,
|
|
59
|
-
},
|
|
54
|
+
throw restApiValidationErrorFromZodError(
|
|
55
|
+
"Slack returned ok: false but the response did not match the expected error shape",
|
|
56
|
+
errorResult.error,
|
|
57
|
+
result,
|
|
60
58
|
);
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
// Success responses must carry the Slack envelope discriminant.
|
|
64
62
|
const successEnvelopeResult = SlackSuccessEnvelopeSchema.safeParse(result);
|
|
65
63
|
if (!successEnvelopeResult.success) {
|
|
66
|
-
throw
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
data: result,
|
|
71
|
-
},
|
|
64
|
+
throw restApiValidationErrorFromZodError(
|
|
65
|
+
"Slack success response is missing the expected ok: true envelope",
|
|
66
|
+
successEnvelopeResult.error,
|
|
67
|
+
result,
|
|
72
68
|
);
|
|
73
69
|
}
|
|
74
70
|
|
|
@@ -81,12 +77,10 @@ export class SlackClientImpl extends RestApiClientBase implements SlackClient {
|
|
|
81
77
|
|
|
82
78
|
const payloadResult = schema.response.safeParse(payloadCandidate);
|
|
83
79
|
if (!payloadResult.success) {
|
|
84
|
-
throw
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
data: result,
|
|
89
|
-
},
|
|
80
|
+
throw restApiValidationErrorFromZodError(
|
|
81
|
+
"Response validation failed",
|
|
82
|
+
payloadResult.error,
|
|
83
|
+
result,
|
|
90
84
|
);
|
|
91
85
|
}
|
|
92
86
|
|
|
@@ -137,9 +137,7 @@ The `query()` method requires a Zod schema for runtime validation:
|
|
|
137
137
|
// WRONG - Missing schema parameter
|
|
138
138
|
const users = await ctx.integrations.db.query(
|
|
139
139
|
"SELECT * FROM users",
|
|
140
|
-
[
|
|
141
|
-
/* params */
|
|
142
|
-
], // This is wrong - params are 3rd argument
|
|
140
|
+
[/* params */], // This is wrong - params are 3rd argument
|
|
143
141
|
);
|
|
144
142
|
|
|
145
143
|
// CORRECT - Schema is the second parameter
|
|
@@ -79,84 +79,6 @@ export default api({
|
|
|
79
79
|
});
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
### Image-to-Image (Transform Existing Image)
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
const result = await ctx.integrations.stability.apiRequest(
|
|
86
|
-
{
|
|
87
|
-
method: "POST",
|
|
88
|
-
path: "/v1/generation/stable-diffusion-xl-1024-v1-0/image-to-image",
|
|
89
|
-
headers: {
|
|
90
|
-
"Content-Type": "multipart/form-data",
|
|
91
|
-
},
|
|
92
|
-
body: {
|
|
93
|
-
init_image: sourceImageBase64,
|
|
94
|
-
text_prompts: [{ text: "A vibrant oil painting style", weight: 1 }],
|
|
95
|
-
image_strength: 0.35, // How much to modify (0-1)
|
|
96
|
-
cfg_scale: 7,
|
|
97
|
-
samples: 1,
|
|
98
|
-
steps: 30,
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
{ response: GenerationResponseSchema },
|
|
102
|
-
);
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Upscale an Image
|
|
106
|
-
|
|
107
|
-
```typescript
|
|
108
|
-
const UpscaleResponseSchema = z.object({
|
|
109
|
-
artifacts: z.array(
|
|
110
|
-
z.object({
|
|
111
|
-
base64: z.string(),
|
|
112
|
-
finishReason: z.string(),
|
|
113
|
-
seed: z.number(),
|
|
114
|
-
}),
|
|
115
|
-
),
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const result = await ctx.integrations.stability.apiRequest(
|
|
119
|
-
{
|
|
120
|
-
method: "POST",
|
|
121
|
-
path: "/v1/generation/esrgan-v1-x2plus/image-to-image/upscale",
|
|
122
|
-
headers: {
|
|
123
|
-
"Content-Type": "multipart/form-data",
|
|
124
|
-
},
|
|
125
|
-
body: {
|
|
126
|
-
image: sourceImageBase64,
|
|
127
|
-
width: 2048, // Target width (optional)
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
{ response: UpscaleResponseSchema },
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
const upscaledImage = result.artifacts[0].base64;
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Inpainting (Edit Parts of an Image)
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
const result = await ctx.integrations.stability.apiRequest(
|
|
140
|
-
{
|
|
141
|
-
method: "POST",
|
|
142
|
-
path: "/v1/generation/stable-diffusion-xl-1024-v1-0/image-to-image/masking",
|
|
143
|
-
headers: {
|
|
144
|
-
"Content-Type": "multipart/form-data",
|
|
145
|
-
},
|
|
146
|
-
body: {
|
|
147
|
-
init_image: sourceImageBase64,
|
|
148
|
-
mask_image: maskImageBase64, // White areas will be regenerated
|
|
149
|
-
mask_source: "MASK_IMAGE_WHITE",
|
|
150
|
-
text_prompts: [{ text: "A golden retriever sitting", weight: 1 }],
|
|
151
|
-
cfg_scale: 7,
|
|
152
|
-
samples: 1,
|
|
153
|
-
steps: 30,
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
{ response: GenerationResponseSchema },
|
|
157
|
-
);
|
|
158
|
-
```
|
|
159
|
-
|
|
160
82
|
### Generate with Specific Style
|
|
161
83
|
|
|
162
84
|
```typescript
|
|
@@ -263,12 +185,6 @@ The engine/model ID is part of the URL path:
|
|
|
263
185
|
// Text-to-image with SDXL 1.0
|
|
264
186
|
const path = "/v1/generation/stable-diffusion-xl-1024-v1-0/text-to-image";
|
|
265
187
|
|
|
266
|
-
// Image-to-image
|
|
267
|
-
const path = "/v1/generation/stable-diffusion-xl-1024-v1-0/image-to-image";
|
|
268
|
-
|
|
269
|
-
// Upscaling
|
|
270
|
-
const path = "/v1/generation/esrgan-v1-x2plus/image-to-image/upscale";
|
|
271
|
-
|
|
272
188
|
// Available engines:
|
|
273
189
|
// - stable-diffusion-xl-1024-v1-0 (SDXL 1.0)
|
|
274
190
|
// - stable-diffusion-v1-6
|
|
@@ -402,9 +318,6 @@ const imageBase64 = result.artifacts[0].base64;
|
|
|
402
318
|
|
|
403
319
|
// To save or display, you may need to add data URL prefix
|
|
404
320
|
const dataUrl = `data:image/png;base64,${imageBase64}`;
|
|
405
|
-
|
|
406
|
-
// For input images (image-to-image), provide raw base64 without prefix
|
|
407
|
-
const inputImage = rawBase64WithoutPrefix;
|
|
408
321
|
```
|
|
409
322
|
|
|
410
323
|
### Seed for Reproducibility
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"pluginId": "stabilityai",
|
|
3
3
|
"base": "README.md",
|
|
4
|
-
"overlays": [
|
|
4
|
+
"overlays": [
|
|
5
|
+
{
|
|
6
|
+
"file": "overlays/image-uploads.md",
|
|
7
|
+
"sdkVersionRange": ">=0.0.7"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"file": "overlays/image-uploads-unsupported.md",
|
|
11
|
+
"sdkVersionRange": "<0.0.7"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
5
14
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
## Image-to-Image, Upscale, and Inpainting
|
|
2
|
+
|
|
3
|
+
The `/image-to-image`, `/image-to-image/upscale`, and `/image-to-image/masking` endpoints require `multipart/form-data`. This agent's sdk-api cannot upload images to them through `apiRequest()` because it serializes every body as JSON. Do not generate image-to-image, upscale, or inpainting code until the agent is upgraded. Text-to-image, account balance, and engine listing work as documented above.
|