Skip to content

Commit bef6817

Browse files
authored
Merge branch 'main' into support-deployments
2 parents aabaa0d + f280b0b commit bef6817

File tree

14 files changed

+322
-26
lines changed

14 files changed

+322
-26
lines changed

.github/workflows/ci.yml

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,6 @@ jobs:
6464
# See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases
6565
node-version: [18.x, 20.x]
6666
suite: [commonjs, esm, typescript]
67-
exclude:
68-
- suite: cloudflare-worker
69-
node-version: 18.x # Only test Cloudflare suite with the latest Node version
7067
fail-fast: false
7168

7269
steps:
@@ -84,6 +81,36 @@ jobs:
8481
npm --prefix integration/${{ matrix.suite }} install "./${{ needs.build.outputs.tarball-name }}"
8582
npm --prefix integration/${{ matrix.suite }} test
8683
84+
integration-browser:
85+
needs: [test, build]
86+
runs-on: ubuntu-latest
87+
88+
env:
89+
REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }}
90+
91+
strategy:
92+
matrix:
93+
browser: ["chromium", "firefox", "webkit"]
94+
suite: ["browser"]
95+
fail-fast: false
96+
97+
steps:
98+
- uses: actions/checkout@v3
99+
- uses: actions/download-artifact@v3
100+
with:
101+
name: package-tarball
102+
- name: Use Node.js ${{ matrix.node-version }}
103+
uses: actions/setup-node@v3
104+
with:
105+
node-version: ${{ matrix.node-version }}
106+
cache: "npm"
107+
- run: |
108+
cd integration/${{ matrix.suite }}
109+
npm install
110+
npm install "../../${{ needs.build.outputs.tarball-name }}"
111+
npm exec -- playwright install ${{ matrix.browser }}
112+
npm exec -- playwright install-deps ${{ matrix.browser }}
113+
npm exec -- playwright test --browser ${{ matrix.browser }}
87114
88115
integration-edge:
89116
needs: [test, build]
@@ -139,4 +166,4 @@ jobs:
139166
cd integration/${{ matrix.suite }}
140167
bun uninstall replicate
141168
bun install "file:../../${{ needs.build.outputs.tarball-name }}"
142-
bun test
169+
bun test --timeout 30000

README.md

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ app.get('/webhooks/replicate', async (c) => {
110110
const prediction = await c.req.json();
111111
console.log(prediction);
112112
//=> {"id": "xyz", "status": "successful", ... }
113-
113+
114114
// Acknowledge the webhook.
115115
c.status(200);
116116
c.json({ok: true});
@@ -217,15 +217,15 @@ Run a model and await the result. Unlike [`replicate.prediction.create`](#replic
217217
const output = await replicate.run(identifier, options, progress);
218218
```
219219

220-
| name | type | description |
221-
| ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
222-
| `identifier` | string | **Required**. The model version identifier in the format `{owner}/{name}:{version}`, for example `stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f` |
223-
| `options.input` | object | **Required**. An object with the model inputs. |
224-
| `options.wait` | object | Options for waiting for the prediction to finish |
225-
| `options.wait.interval` | number | Polling interval in milliseconds. Defaults to 500 |
226-
| `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output |
227-
| `options.webhook_events_filter` | string[] | An array of events which should trigger [webhooks](https://replicate.com/docs/webhooks). Allowable values are `start`, `output`, `logs`, and `completed` |
228-
| `options.signal` | object | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the prediction |
220+
| name | type | description |
221+
| ------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
222+
| `identifier` | string | **Required**. The model version identifier in the format `{owner}/{name}:{version}`, for example `stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f` |
223+
| `options.input` | object | **Required**. An object with the model inputs. |
224+
| `options.wait` | object | Options for waiting for the prediction to finish |
225+
| `options.wait.interval` | number | Polling interval in milliseconds. Defaults to 500 |
226+
| `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output |
227+
| `options.webhook_events_filter` | string[] | An array of events which should trigger [webhooks](https://replicate.com/docs/webhooks). Allowable values are `start`, `output`, `logs`, and `completed` |
228+
| `options.signal` | object | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the prediction |
229229
| `progress` | function | Callback function that receives the prediction object as it's updated. The function is called when the prediction is created, each time it's updated while polling for completion, and when it's completed. |
230230

231231
Throws `Error` if the prediction failed.
@@ -246,7 +246,7 @@ Example that logs progress as the model is running:
246246
const model = "stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f";
247247
const input = { prompt: "a 19th century portrait of a raccoon gentleman wearing a suit" };
248248
const onProgress = (prediction) => {
249-
const last_log_line = prediction.logs.split("\n").pop()
249+
const last_log_line = prediction.logs.split("\n").pop()
250250
console.log({id: prediction.id, log: last_log_line})
251251
}
252252
const output = await replicate.run(model, { input }, onProgress)
@@ -875,6 +875,21 @@ The `Replicate` constructor and all `replicate.*` methods are fully typed.
875875

876876
We have a few dependencies that have been bundled into the vendor directory rather than adding external npm dependencies.
877877

878-
These have been generated using bundlejs.com and copied into the appropriate directory along with the license and repository information.
878+
These have been generated using bundlejs.com and copied into the appropriate directory along with the license and repository information.
879879

880880
* [eventsource-parser/stream](https://bundlejs.com/?bundle&q=eventsource-parser%40latest%2Fstream&config=%7B%22esbuild%22%3A%7B%22format%22%3A%22cjs%22%2C%22minify%22%3Afalse%2C%22platform%22%3A%22neutral%22%7D%7D)
881+
* [streams-text-encoding/text-decoder-stream](https://bundlejs.com/?q=%40stardazed%2Fstreams-text-encoding&treeshake=%5B%7B+TextDecoderStream+%7D%5D&config=%7B%22esbuild%22%3A%7B%22format%22%3A%22cjs%22%2C%22minify%22%3Afalse%7D%7D)
882+
883+
> [!NOTE]
884+
> The vendored implementation of `TextDecoderStream` requires
885+
> the following patch to be applied to the output of bundlejs.com:
886+
>
887+
> ```diff
888+
> constructor(label, options) {
889+
> - this[decDecoder] = new TextDecoder(label, options);
890+
> - this[decTransform] = new TransformStream(new TextDecodeTransformer(this[decDecoder]));
891+
> + const decoder = new TextDecoder(label || "utf-8", options || {});
892+
> + this[decDecoder] = decoder;
893+
> + this[decTransform] = new TransformStream(new TextDecodeTransformer(decoder));
894+
> }
895+
> ```

index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const {
55
withAutomaticRetries,
66
validateWebhook,
77
parseProgressFromLogs,
8+
streamAsyncIterator,
89
} = require("./lib/util");
910

1011
const accounts = require("./lib/accounts");
@@ -299,7 +300,8 @@ class Replicate {
299300
fetch: this.fetch,
300301
options: { signal },
301302
});
302-
yield* stream;
303+
304+
yield* streamAsyncIterator(stream);
303305
} else {
304306
throw new Error("Prediction does not support streaming");
305307
}

integration/browser/.npmrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package-lock=false
2+
audit=false
3+
fund=false

integration/browser/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Browser integration tests
2+
3+
Uses [`playwright`](https://playwright.dev/docs) to run a basic integration test against the three most common browser engines, Firefox, Chromium and WebKit.
4+
5+
It uses the `replicate/canary` model for the moment, which requires a Replicate API token available in the environment under `REPLICATE_API_TOKEN`.
6+
7+
The entire suite is a single `main()` function that calls a single model exercising the streaming API.
8+
9+
The test uses `esbuild` within the test generate a browser friendly version of the `index.js` file which is loaded into the given browser and calls the `main()` function asserting the response content.
10+
11+
## CORS
12+
13+
The Replicate API doesn't support Cross Origin Resource Sharing at this time. We work around this in Playwright by intercepting the request in a `page.route` handler. We don't modify the request/response, but this seems to work around the restriction.
14+
15+
## Setup
16+
17+
npm install
18+
19+
## Local
20+
21+
The following command will run the tests across all browsers.
22+
23+
npm test
24+
25+
To run against the default browser (chromium) run:
26+
27+
npm exec playwright test
28+
29+
Or, specify a browser with:
30+
31+
npm exec playwright test --browser firefox
32+
33+
## Debugging
34+
35+
Running `playwright test` with the `--debug` flag opens a browser window with a debugging interface, and a breakpoint set at the start of the test. It can also be connected directly to VSCode.
36+
37+
npm exec playwright test --debug
38+
39+
The browser.js file is injected into the page via a script tag, to be able to set breakpoints in this file you'll need to use a `debugger` statement and open the devtools in the spawned browser window before continuing the test suite.

integration/browser/index.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Replicate from "replicate";
2+
3+
/**
4+
* @param {string} - token the REPLICATE_API_TOKEN
5+
*/
6+
window.main = async (token) => {
7+
const replicate = new Replicate({ auth: token });
8+
const stream = replicate.stream(
9+
"replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272",
10+
{
11+
input: {
12+
text: "Betty Browser",
13+
},
14+
}
15+
);
16+
17+
const output = [];
18+
for await (const event of stream) {
19+
output.push(String(event));
20+
}
21+
return output.join("");
22+
};

integration/browser/index.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { test, expect } from "@playwright/test";
2+
import { build } from "esbuild";
3+
4+
// Convert the source file from commonjs to a browser script.
5+
const result = await build({
6+
entryPoints: ["index.js"],
7+
bundle: true,
8+
platform: "browser",
9+
external: ["node:crypto"],
10+
write: false,
11+
});
12+
const source = new TextDecoder().decode(result.outputFiles[0].contents);
13+
14+
// https://playwright.dev/docs/network#modify-requests
15+
16+
test("browser", async ({ page }) => {
17+
// Patch the API endpoint to work around CORS for now.
18+
await page.route(
19+
"https://api.replicate.com/v1/predictions",
20+
async (route) => {
21+
// Fetch original response.
22+
const response = await route.fetch();
23+
// Add a prefix to the title.
24+
return route.fulfill({ response });
25+
}
26+
);
27+
28+
await page.addScriptTag({ content: source });
29+
const result = await page.evaluate(
30+
(token) => window.main(token),
31+
[process.env.REPLICATE_API_TOKEN]
32+
);
33+
expect(result).toBe("hello there, Betty Browser");
34+
});

integration/browser/package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "replicate-app-browser",
3+
"private": true,
4+
"version": "0.0.0",
5+
"description": "",
6+
"main": "index.js",
7+
"type": "module",
8+
"scripts": {
9+
"test": "playwright test --browser all"
10+
},
11+
"license": "ISC",
12+
"dependencies": {
13+
"replicate": "../../"
14+
},
15+
"devDependencies": {
16+
"@playwright/test": "^1.42.1",
17+
"esbuild": "^0.20.1"
18+
}
19+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { defineConfig } from "@playwright/test";
2+
3+
export default defineConfig({});

integration/bun/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ import type {
1919

2020
test("main", async () => {
2121
const output = await main();
22-
expect(output as any).toEqual("hello Brünnhilde Bun");
22+
expect(output).toContain("Brünnhilde Bun");
2323
});

0 commit comments

Comments
 (0)