├── .editorconfig ├── .git-blame-ignore-revs ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── biome.json ├── index.d.ts ├── index.js ├── index.test.ts ├── integration ├── browser │ ├── .npmrc │ ├── README.md │ ├── index.js │ ├── index.test.js │ ├── package.json │ └── playwright.config.ts ├── bun │ ├── .gitignore │ ├── bun.lockb │ ├── index.test.ts │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── cloudflare-worker │ ├── .gitignore │ ├── .npmrc │ ├── index.js │ ├── index.test.js │ ├── package.json │ └── wrangler.toml ├── commonjs │ ├── .npmrc │ ├── index.js │ ├── index.test.js │ └── package.json ├── deno │ ├── deno.json │ ├── deno.lock │ ├── index.test.ts │ ├── index.ts │ └── package.json ├── esm │ ├── .npmrc │ ├── index.js │ ├── index.test.js │ └── package.json ├── next │ ├── .npmrc │ ├── middleware.ts │ ├── package.json │ └── pages │ │ └── index.js └── typescript │ ├── .npmrc │ ├── index.test.ts │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── jest.config.js ├── jsconfig.json ├── lib ├── accounts.js ├── collections.js ├── deployments.js ├── error.js ├── files.js ├── hardware.js ├── identifier.js ├── models.js ├── predictions.js ├── stream.js ├── trainings.js ├── util.js └── webhooks.js ├── package-lock.json ├── package.json ├── tsconfig.json └── vendor ├── eventsource-parser └── stream.js └── streams-text-encoding └── text-decoder-stream.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | # editorconfig-tools is unable to ignore longs strings or urls 11 | max_line_length = off 12 | 13 | [*.{js,ts}] 14 | quote_type = single 15 | 16 | [CHANGELOG.md] 17 | indent_size = false 18 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Apply automatic formatting 2 | 66d81afa121fb205cfbe46cfe7e2845183b1b237 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | suite: [node] 16 | # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases 17 | node-version: [18.x, 20.x, 22.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: "npm" 26 | - run: npm ci 27 | - run: npm run test 28 | - run: npm run check 29 | - run: npm run lint 30 | 31 | 32 | # Build a production tarball and use it to run the integration 33 | build: 34 | runs-on: ubuntu-latest 35 | 36 | strategy: 37 | matrix: 38 | # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases 39 | node-version: [20.x] 40 | 41 | outputs: 42 | tarball-name: ${{ steps.pack.outputs.tarball-name }} 43 | 44 | steps: 45 | - uses: actions/checkout@v4 46 | - name: Use Node.js ${{ matrix.node-version }} 47 | uses: actions/setup-node@v4 48 | with: 49 | node-version: ${{ matrix.node-version }} 50 | cache: "npm" 51 | - name: Build tarball 52 | id: pack 53 | run: | 54 | echo "tarball-name=$(npm --loglevel error pack)" >> $GITHUB_OUTPUT 55 | - uses: actions/upload-artifact@v4 56 | with: 57 | name: package-tarball 58 | path: ${{ steps.pack.outputs.tarball-name }} 59 | 60 | 61 | integration-node: 62 | needs: [test, build] 63 | runs-on: ubuntu-latest 64 | 65 | env: 66 | REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} 67 | 68 | strategy: 69 | matrix: 70 | suite: [commonjs, esm, typescript] 71 | # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases 72 | node-version: [18.x, 20.x] 73 | fail-fast: false 74 | 75 | steps: 76 | - uses: actions/checkout@v4 77 | - uses: actions/download-artifact@v4.1.7 78 | with: 79 | name: package-tarball 80 | - name: Use Node.js ${{ matrix.node-version }} 81 | uses: actions/setup-node@v4 82 | with: 83 | node-version: ${{ matrix.node-version }} 84 | cache: "npm" 85 | - run: | 86 | npm --prefix integration/${{ matrix.suite }} install 87 | npm --prefix integration/${{ matrix.suite }} install "./${{ needs.build.outputs.tarball-name }}" 88 | npm --prefix integration/${{ matrix.suite }} test 89 | 90 | integration-browser: 91 | needs: [test, build] 92 | runs-on: ubuntu-latest 93 | 94 | env: 95 | REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} 96 | 97 | strategy: 98 | matrix: 99 | suite: ["browser"] 100 | browser: ["chromium", "firefox", "webkit"] 101 | node-version: [20.x] 102 | fail-fast: false 103 | 104 | steps: 105 | - uses: actions/checkout@v4 106 | - uses: actions/download-artifact@v4.1.7 107 | with: 108 | name: package-tarball 109 | - name: Use Node.js ${{ matrix.node-version }} 110 | uses: actions/setup-node@v4 111 | with: 112 | node-version: ${{ matrix.node-version }} 113 | cache: "npm" 114 | - run: | 115 | cd integration/${{ matrix.suite }} 116 | npm install 117 | npm install "../../${{ needs.build.outputs.tarball-name }}" 118 | npm exec -- playwright install ${{ matrix.browser }} 119 | npm exec -- playwright install-deps ${{ matrix.browser }} 120 | npm exec -- playwright test --browser ${{ matrix.browser }} 121 | 122 | integration-edge: 123 | needs: [test, build] 124 | runs-on: ubuntu-latest 125 | 126 | env: 127 | REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} 128 | 129 | strategy: 130 | matrix: 131 | suite: [cloudflare-worker] 132 | node-version: [20.x] 133 | 134 | steps: 135 | - uses: actions/checkout@v4 136 | - uses: actions/download-artifact@v4.1.7 137 | with: 138 | name: package-tarball 139 | - name: Use Node.js ${{ matrix.node-version }} 140 | uses: actions/setup-node@v4 141 | with: 142 | node-version: ${{ matrix.node-version }} 143 | cache: "npm" 144 | - run: | 145 | test "${{ matrix.suite }}" = "cloudflare-worker" && echo "REPLICATE_API_TOKEN=${{ secrets.REPLICATE_API_TOKEN }}" > integration/${{ matrix.suite }}/.dev.vars 146 | npm --prefix integration/${{ matrix.suite }} install 147 | npm --prefix integration/${{ matrix.suite }} install "./${{ needs.build.outputs.tarball-name }}" 148 | npm --prefix integration/${{ matrix.suite }} test 149 | 150 | integration-bun: 151 | needs: [test, build] 152 | runs-on: ubuntu-latest 153 | 154 | env: 155 | REPLICATE_API_TOKEN: ${{ secrets.REPLICATE_API_TOKEN }} 156 | 157 | strategy: 158 | matrix: 159 | suite: [bun] 160 | bun-version: [1.0.11] 161 | 162 | steps: 163 | - uses: actions/checkout@v4 164 | - uses: actions/download-artifact@v4.1.7 165 | with: 166 | name: package-tarball 167 | - name: Use Bun ${{ matrix.bun-version }} 168 | uses: oven-sh/setup-bun@v1 169 | with: 170 | bun-version: ${{ matrix.bun-version }} 171 | - run: | 172 | cd integration/${{ matrix.suite }} 173 | bun uninstall replicate 174 | bun install "file:../../${{ needs.build.outputs.tarball-name }}" 175 | retries=3 176 | for ((i=0; i=18`). Then run: 10 | 11 | ``` 12 | npm install 13 | npm test 14 | ``` 15 | 16 | ## Releases 17 | 18 | To cut a new release, run: 19 | 20 | ``` 21 | cd replicate-js 22 | git checkout main 23 | git pull 24 | npx np minor 25 | ``` 26 | 27 | This will: 28 | 29 | - Run tests locally 30 | - Bump the version in `package.json` 31 | - Commit and tag the release 32 | - Push the commit and tag to GitHub 33 | - Publish the package to npm 34 | - Create a GitHub release 35 | 36 | ## Vendored Dependencies 37 | 38 | We have a few dependencies that have been bundled into the vendor directory rather than adding external npm dependencies. 39 | 40 | These have been generated using bundlejs.com and copied into the appropriate directory along with the license and repository information. 41 | 42 | * [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) 43 | * [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) 44 | 45 | > [!NOTE] 46 | > The vendored implementation of `TextDecoderStream` requires 47 | > the following patch to be applied to the output of bundlejs.com: 48 | > 49 | > ```diff 50 | > constructor(label, options) { 51 | > - this[decDecoder] = new TextDecoder(label, options); 52 | > - this[decTransform] = new TransformStream(new TextDecodeTransformer(this[decDecoder])); 53 | > + const decoder = new TextDecoder(label || "utf-8", options || {}); 54 | > + this[decDecoder] = decoder; 55 | > + this[decTransform] = new TransformStream(new TextDecodeTransformer(decoder)); 56 | > } 57 | > ``` 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 Replicate, Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Replicate Node.js client 2 | 3 | A Node.js client for [Replicate](https://replicate.com). 4 | It lets you run models from your Node.js code, 5 | and everything else you can do with 6 | [Replicate's HTTP API](https://replicate.com/docs/reference/http). 7 | 8 | > [!IMPORTANT] 9 | > This library can't interact with Replicate's API directly from a browser. 10 | > For more information about how to build a web application 11 | > check out our ["Build a website with Next.js"](https://replicate.com/docs/get-started/nextjs) guide. 12 | 13 | ## Supported platforms 14 | 15 | - [Node.js](https://nodejs.org) >= 18 16 | - [Bun](https://bun.sh) >= 1.0 17 | - [Deno](https://deno.com) >= 1.28 18 | 19 | You can also use this client library on most serverless platforms, including 20 | [Cloudflare Workers](https://developers.cloudflare.com/workers/), 21 | [Vercel functions](https://vercel.com/docs/functions), and 22 | [AWS Lambda](https://aws.amazon.com/lambda/). 23 | 24 | ## Installation 25 | 26 | Install it from npm: 27 | 28 | ```bash 29 | npm install replicate 30 | ``` 31 | 32 | ## Usage 33 | 34 | Import or require the package: 35 | 36 | ```js 37 | // CommonJS (default or using .cjs extension) 38 | const Replicate = require("replicate"); 39 | 40 | // ESM (where `"module": true` in package.json or using .mjs extension) 41 | import Replicate from "replicate"; 42 | ``` 43 | 44 | Instantiate the client: 45 | 46 | ```js 47 | const replicate = new Replicate({ 48 | // get your token from https://replicate.com/account/api-tokens 49 | auth: "my api token", // defaults to process.env.REPLICATE_API_TOKEN 50 | }); 51 | ``` 52 | 53 | Run a model and await the result: 54 | 55 | ```js 56 | const model = "stability-ai/stable-diffusion:27b93a2413e7f36cd83da926f3656280b2931564ff050bf9575f1fdf9bcd7478"; 57 | const input = { 58 | prompt: "a 19th century portrait of a raccoon gentleman wearing a suit", 59 | }; 60 | const [output] = await replicate.run(model, { input }); 61 | // FileOutput('https://replicate.delivery/pbxt/GtQb3Sgve42ZZyVnt8xjquFk9EX5LP0fF68NTIWlgBMUpguQA/out-0.png') 62 | 63 | console.log(output.url()); // 'https://replicate.delivery/pbxt/GtQb3Sgve42ZZyVnt8xjquFk9EX5LP0fF68NTIWlgBMUpguQA/out-0.png' 64 | console.log(output.blob()); // Blob 65 | ``` 66 | 67 | > [!NOTE] 68 | > A model that outputs file data returns a `FileOutput` object by default. This is an implementation 69 | > of `ReadableStream` that returns the file contents. It has a `.blob()` method for accessing a 70 | > `Blob` representation and a `.url()` method that will return the underlying data-source. 71 | > 72 | > We recommend accessing file data directly either as readable stream or via `.blob()` as the 73 | > `.url()` property may not always return a HTTP URL in future. 74 | 75 | You can also run a model in the background: 76 | 77 | ```js 78 | let prediction = await replicate.predictions.create({ 79 | version: "27b93a2413e7f36cd83da926f3656280b2931564ff050bf9575f1fdf9bcd7478", 80 | input: { 81 | prompt: "painting of a cat by andy warhol", 82 | }, 83 | }); 84 | ``` 85 | 86 | Then fetch the prediction result later: 87 | 88 | ```js 89 | prediction = await replicate.predictions.get(prediction.id); 90 | ``` 91 | 92 | Or wait for the prediction to finish: 93 | 94 | ```js 95 | prediction = await replicate.wait(prediction); 96 | console.log(prediction.output); 97 | // ['https://replicate.delivery/pbxt/RoaxeXqhL0xaYyLm6w3bpGwF5RaNBjADukfFnMbhOyeoWBdhA/out-0.png'] 98 | ``` 99 | 100 | To run a model that takes a file input you can pass either 101 | a URL to a publicly accessible file on the Internet 102 | or a handle to a file on your local device. 103 | 104 | ```js 105 | const fs = require("node:fs/promises"); 106 | 107 | // Or when using ESM. 108 | // import fs from "node:fs/promises"; 109 | 110 | const model = "nightmareai/real-esrgan:42fed1c4974146d4d2414e2be2c5277c7fcf05fcc3a73abf41610695738c1d7b"; 111 | const input = { 112 | image: await fs.readFile("path/to/image.png"), 113 | }; 114 | const [output] = await replicate.run(model, { input }); 115 | // FileOutput('https://replicate.delivery/mgxm/e7b0e122-9daa-410e-8cde-006c7308ff4d/output.png') 116 | ``` 117 | 118 | > [!NOTE] 119 | > File handle inputs are automatically uploaded to Replicate. 120 | > See [`replicate.files.create`](#replicatefilescreate) for more information. 121 | > The maximum size for uploaded files is 100MiB. 122 | > To run a model with a larger file as an input, 123 | > upload the file to your own storage provider 124 | > and pass a publicly accessible URL. 125 | 126 | ## TypeScript usage 127 | 128 | This library exports TypeScript definitions. You can import them like this: 129 | 130 | ```ts 131 | import Replicate, { type Prediction } from 'replicate'; 132 | ``` 133 | 134 | Here's an example that uses the `Prediction` type with a custom `onProgress` callback: 135 | 136 | ```ts 137 | import Replicate, { type Prediction } from 'replicate'; 138 | 139 | const replicate = new Replicate(); 140 | const model = "black-forest-labs/flux-schnell"; 141 | const prompt = "a 19th century portrait of a raccoon gentleman wearing a suit"; 142 | function onProgress(prediction: Prediction) { 143 | console.log({ prediction }); 144 | } 145 | 146 | const output = await replicate.run(model, { input: { prompt } }, onProgress) 147 | console.log({ output }) 148 | ``` 149 | 150 | See the full list of exported types in [index.d.ts](./index.d.ts). 151 | 152 | ### Webhooks 153 | 154 | Webhooks provide real-time updates about your prediction. Specify an endpoint when you create a prediction, and Replicate will send HTTP POST requests to that URL when the prediction is created, updated, and finished. 155 | 156 | It is possible to provide a URL to the predictions.create() function that will be requested by Replicate when the prediction status changes. This is an alternative to polling. 157 | 158 | To receive webhooks you’ll need a web server. The following example uses Hono, a web standards based server, but this pattern applies to most frameworks. 159 | 160 |
161 | See example 162 | 163 | ```js 164 | import { serve } from '@hono/node-server'; 165 | import { Hono } from 'hono'; 166 | 167 | const app = new Hono(); 168 | app.get('/webhooks/replicate', async (c) => { 169 | // Get the prediction from the request. 170 | const prediction = await c.req.json(); 171 | console.log(prediction); 172 | //=> {"id": "xyz", "status": "successful", ... } 173 | 174 | // Acknowledge the webhook. 175 | c.status(200); 176 | c.json({ok: true}); 177 | })); 178 | 179 | serve(app, (info) => { 180 | console.log(`Listening on http://localhost:${info.port}`) 181 | //=> Listening on http://localhost:3000 182 | }); 183 | ``` 184 | 185 |
186 | 187 | Create the prediction passing in the webhook URL to `webhook` and specify which events you want to receive in `webhook_events_filter` out of "start", "output", ”logs” and "completed": 188 | 189 | ```js 190 | const Replicate = require("replicate"); 191 | const replicate = new Replicate(); 192 | 193 | const input = { 194 | image: "https://replicate.delivery/pbxt/KWDkejqLfER3jrroDTUsSvBWFaHtapPxfg4xxZIqYmfh3zXm/Screenshot%202024-02-28%20at%2022.14.00.png", 195 | denoising_strength: 0.5, 196 | instant_id_strength: 0.8 197 | }; 198 | 199 | const callbackURL = `https://my.app/webhooks/replicate`; 200 | await replicate.predictions.create({ 201 | version: "19deaef633fd44776c82edf39fd60e95a7250b8ececf11a725229dc75a81f9ca", 202 | input: input, 203 | webhook: callbackURL, 204 | webhook_events_filter: ["completed"], 205 | }); 206 | 207 | // The server will now handle the event and log: 208 | // => {"id": "xyz", "status": "successful", ... } 209 | ``` 210 | 211 | ## Verifying webhooks 212 | 213 | To prevent unauthorized requests, Replicate signs every webhook and its metadata with a unique key for each user or organization. You can use this signature to verify the webhook indeed comes from Replicate before you process it. 214 | 215 | This client includes a `validateWebhook` convenience function that you can use to validate webhooks. 216 | 217 | To validate webhooks: 218 | 219 | 1. Check out the [webhooks guide](https://replicate.com/docs/webhooks) to get started. 220 | 1. [Retrieve your webhook signing secret](https://replicate.com/docs/webhooks#retrieving-the-webhook-signing-key) and store it in your enviroment. 221 | 1. Update your webhook handler to call `validateWebhook(request, secret)`, where `request` is an instance of a [web-standard `Request` object](https://developer.mozilla.org/en-US/docs/Web/API/object), and `secret` is the signing secret for your environment. 222 | 223 | Here's an example of how to validate webhooks using Next.js: 224 | 225 | ```js 226 | import { NextResponse } from 'next/server'; 227 | import { validateWebhook } from 'replicate'; 228 | 229 | export async function POST(request) { 230 | const secret = process.env.REPLICATE_WEBHOOK_SIGNING_SECRET; 231 | 232 | if (!secret) { 233 | console.log("Skipping webhook validation. To validate webhooks, set REPLICATE_WEBHOOK_SIGNING_SECRET") 234 | const body = await request.json(); 235 | console.log(body); 236 | return NextResponse.json({ detail: "Webhook received (but not validated)" }, { status: 200 }); 237 | } 238 | 239 | const webhookIsValid = await validateWebhook(request.clone(), secret); 240 | 241 | if (!webhookIsValid) { 242 | return NextResponse.json({ detail: "Webhook is invalid" }, { status: 401 }); 243 | } 244 | 245 | // process validated webhook here... 246 | console.log("Webhook is valid!"); 247 | const body = await request.json(); 248 | console.log(body); 249 | 250 | return NextResponse.json({ detail: "Webhook is valid" }, { status: 200 }); 251 | } 252 | ``` 253 | 254 | If your environment doesn't support `Request` objects, you can pass the required information to `validateWebhook` directly: 255 | 256 | ```js 257 | const requestData = { 258 | id: "123", // the `Webhook-Id` header 259 | timestamp: "0123456", // the `Webhook-Timestamp` header 260 | signature: "xyz", // the `Webhook-Signature` header 261 | body: "{...}", // the request body as a string, ArrayBuffer or ReadableStream 262 | secret: "shhh", // the webhook secret, obtained from the `replicate.webhooks.defaul.secret` endpoint 263 | }; 264 | const webhookIsValid = await validateWebhook(requestData); 265 | ``` 266 | 267 | > [!NOTE] 268 | > The `validateWebhook` function uses the global `crypto` API available in most JavaScript runtimes. Node <= 18 does not provide this global so in this case you need to either call node with the `--no-experimental-global-webcrypto` or provide the `webcrypto` module manually. 269 | > ```js 270 | > const crypto = require("node:crypto").webcrypto; 271 | > const webhookIsValid = await valdiateWebhook(requestData, crypto); 272 | > ``` 273 | 274 | ## TypeScript 275 | 276 | The `Replicate` constructor and all `replicate.*` methods are fully typed. 277 | 278 | Currently in order to support the module format used by `replicate` you'll need to set `esModuleInterop` to `true` in your tsconfig.json. 279 | 280 | ## API 281 | 282 | ### Constructor 283 | 284 | ```js 285 | const replicate = new Replicate(options); 286 | ``` 287 | 288 | | name | type | description | 289 | | ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------- | 290 | | `options.auth` | string | **Required**. API access token | 291 | | `options.userAgent` | string | Identifier of your app. Defaults to `replicate-javascript/${packageJSON.version}` | 292 | | `options.baseUrl` | string | Defaults to https://api.replicate.com/v1 | 293 | | `options.fetch` | function | Fetch function to use. Defaults to `globalThis.fetch` | 294 | | `options.fileEncodingStrategy` | string | Determines the file encoding strategy to use. Possible values: `"default"`, `"upload"`, or `"data-uri"`. Defaults to `"default"` | 295 | | `options.useFileOutput` | boolean | Determines if the `replicate.run()` method should convert file output into `FileOutput` objects | 296 | 297 | 298 | The client makes requests to Replicate's API using 299 | [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch). 300 | By default, the `globalThis.fetch` function is used, 301 | which is available on [Node.js 18](https://nodejs.org/en/blog/announcements/v18-release-announce#fetch-experimental) and later, 302 | as well as 303 | [Cloudflare Workers](https://developers.cloudflare.com/workers/runtime-apis/fetch/), 304 | [Vercel Functions](https://vercel.com/docs/functions), 305 | and other environments. 306 | 307 | On earlier versions of Node.js 308 | and other environments where global fetch isn't available, 309 | you can install a fetch function from an external package like 310 | [cross-fetch](https://www.npmjs.com/package/cross-fetch) 311 | and pass it to the `fetch` option in the constructor. 312 | 313 | ```js 314 | const Replicate = require("replicate"); 315 | const fetch = require("fetch"); 316 | 317 | // Using ESM: 318 | // import Replicate from "replicate"; 319 | // import fetch from "cross-fetch"; 320 | 321 | const replicate = new Replicate({ fetch }); 322 | ``` 323 | 324 | You can also use the `fetch` option to add custom behavior to client requests, 325 | such as injecting headers or adding log statements. 326 | 327 | ```js 328 | const customFetch = (url, options) => { 329 | const headers = options && options.headers ? { ...options.headers } : {}; 330 | headers["X-Custom-Header"] = "some value"; 331 | 332 | console.log("fetch", { url, ...options, headers }); 333 | 334 | return fetch(url, { ...options, headers }); 335 | }; 336 | 337 | const replicate = new Replicate({ fetch: customFetch }); 338 | ``` 339 | 340 | ### `replicate.run` 341 | 342 | Run a model and await the result. Unlike [`replicate.prediction.create`](#replicatepredictionscreate), this method returns only the prediction output rather than the entire prediction object. 343 | 344 | ```js 345 | const output = await replicate.run(identifier, options, progress); 346 | ``` 347 | 348 | | name | type | description | 349 | | ------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 350 | | `identifier` | string | **Required**. The model version identifier in the format `{owner}/{name}:{version}`, for example `stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f` | 351 | | `options.input` | object | **Required**. An object with the model inputs. | 352 | | `options.wait` | object | Options for waiting for the prediction to finish | 353 | | `options.wait.mode` | `"poll" \| "block"` | `"block"` holds the request open, `"poll"` makes repeated requests to fetch the prediction. Defaults to `"block"` | 354 | | `options.wait.interval` | number | Polling interval in milliseconds. Defaults to 500 | 355 | | `options.wait.timeout` | number | In `"block"` mode determines how long the request will be held open until falling back to polling. Defaults to 60 | 356 | | `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | 357 | | `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` | 358 | | `options.signal` | object | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the prediction | 359 | | `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. | 360 | 361 | Throws `Error` if the prediction failed. 362 | 363 | Returns `Promise` which resolves with the output of running the model. 364 | 365 | > [!NOTE] 366 | > Currently the TypeScript return type of `replicate.run()` is `Promise` this is 367 | > misleading as a model can return array types as well as primative types like strings, 368 | > numbers and booleans. 369 | 370 | Example: 371 | 372 | ```js 373 | const model = "stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f"; 374 | const input = { prompt: "a 19th century portrait of a raccoon gentleman wearing a suit" }; 375 | const output = await replicate.run(model, { input }); 376 | ``` 377 | 378 | Example that logs progress as the model is running: 379 | 380 | ```js 381 | const model = "stability-ai/sdxl:8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f"; 382 | const input = { prompt: "a 19th century portrait of a raccoon gentleman wearing a suit" }; 383 | const onProgress = (prediction) => { 384 | const last_log_line = prediction.logs.split("\n").pop() 385 | console.log({id: prediction.id, log: last_log_line}) 386 | } 387 | const output = await replicate.run(model, { input }, onProgress) 388 | ``` 389 | 390 | #### Sync vs. Async API (`"poll"` vs. `"block"`) 391 | 392 | The `replicate.run()` API takes advantage of the [Replicate sync API](https://replicate.com/docs/topics/predictions/create-a-prediction) 393 | which is optimized for low latency requests to file models like `black-forest-labs/flux-dev` and 394 | `black-forest-labs/flux-schnell`. When creating a prediction this will hold a connection open to the 395 | server and return a `FileObject` containing the generated file as quickly as possible. 396 | 397 | ### `replicate.stream` 398 | 399 | Run a model and stream its output. Unlike [`replicate.prediction.create`](#replicatepredictionscreate), this method returns only the prediction output rather than the entire prediction object. 400 | 401 | ```js 402 | for await (const event of replicate.stream(identifier, options)) { /* ... */ } 403 | ``` 404 | 405 | | name | type | description | 406 | | ------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | 407 | | `identifier` | string | **Required**. The model version identifier in the format `{owner}/{name}` or `{owner}/{name}:{version}`, for example `meta/llama-2-70b-chat` | 408 | | `options.input` | object | **Required**. An object with the model inputs. | 409 | | `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | 410 | | `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` | 411 | | `options.signal` | object | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the prediction | 412 | 413 | Throws `Error` if the prediction failed. 414 | 415 | Returns `AsyncGenerator` which yields the events of running the model. 416 | 417 | Example: 418 | 419 | ```js 420 | const model = "meta/llama-2-70b-chat"; 421 | const options = { 422 | input: { 423 | prompt: "Write a poem about machine learning in the style of Mary Oliver.", 424 | }, 425 | // webhook: "https://smee.io/dMUlmOMkzeyRGjW" // optional 426 | }; 427 | const output = []; 428 | 429 | for await (const { event, data } of replicate.stream(model, options)) { 430 | if (event === "output") { 431 | output.push(data); 432 | } 433 | } 434 | 435 | console.log(output.join("").trim()); 436 | ``` 437 | 438 | ### Server-sent events 439 | 440 | A stream generates server-sent events with the following properties: 441 | 442 | | name | type | description | 443 | | ------- | ------ | ---------------------------------------------------------------------------- | 444 | | `event` | string | The type of event. Possible values are `output`, `logs`, `error`, and `done` | 445 | | `data` | string | The event data | 446 | | `id` | string | The event id | 447 | | `retry` | number | The number of milliseconds to wait before reconnecting to the server | 448 | 449 | As the prediction runs, the generator yields `output` and `logs` events. If an error occurs, the generator yields an `error` event with a JSON object containing the error message set to the `data` property. When the prediction is done, the generator yields a `done` event with an empty JSON object set to the `data` property. 450 | 451 | Events with the `output` event type have their `toString()` method overridden to return the event data as a string. Other event types return an empty string. 452 | 453 | ### `replicate.models.get` 454 | 455 | Get metadata for a public model or a private model that you own. 456 | 457 | ```js 458 | const response = await replicate.models.get(model_owner, model_name); 459 | ``` 460 | 461 | | name | type | description | 462 | | ------------- | ------ | ----------------------------------------------------------------------- | 463 | | `model_owner` | string | **Required**. The name of the user or organization that owns the model. | 464 | | `model_name` | string | **Required**. The name of the model. | 465 | 466 | ```jsonc 467 | { 468 | "url": "https://replicate.com/replicate/hello-world", 469 | "owner": "replicate", 470 | "name": "hello-world", 471 | "description": "A tiny model that says hello", 472 | "visibility": "public", 473 | "github_url": "https://github.com/replicate/cog-examples", 474 | "paper_url": null, 475 | "license_url": null, 476 | "latest_version": { 477 | /* ... */ 478 | } 479 | } 480 | ``` 481 | 482 | ### `replicate.models.list` 483 | 484 | Get a paginated list of all public models. 485 | 486 | ```js 487 | const response = await replicate.models.list(); 488 | ``` 489 | 490 | ```jsonc 491 | { 492 | "next": null, 493 | "previous": null, 494 | "results": [ 495 | { 496 | "url": "https://replicate.com/replicate/hello-world", 497 | "owner": "replicate", 498 | "name": "hello-world", 499 | "description": "A tiny model that says hello", 500 | "visibility": "public", 501 | "github_url": "https://github.com/replicate/cog-examples", 502 | "paper_url": null, 503 | "license_url": null, 504 | "run_count": 5681081, 505 | "cover_image_url": "...", 506 | "default_example": { 507 | /* ... */ 508 | }, 509 | "latest_version": { 510 | /* ... */ 511 | } 512 | } 513 | ] 514 | } 515 | ``` 516 | 517 | ### `replicate.models.search` 518 | 519 | Search for public models on Replicate. 520 | 521 | ```js 522 | const response = await replicate.models.search(query); 523 | ``` 524 | 525 | | name | type | description | 526 | | ------- | ------ | -------------------------------------- | 527 | | `query` | string | **Required**. The search query string. | 528 | 529 | ### `replicate.models.create` 530 | 531 | Create a new public or private model. 532 | 533 | ```js 534 | const response = await replicate.models.create(model_owner, model_name, options); 535 | ``` 536 | 537 | | name | type | description | 538 | | ------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 539 | | `model_owner` | string | **Required**. The name of the user or organization that will own the model. This must be the same as the user or organization that is making the API request. In other words, the API token used in the request must belong to this user or organization. | 540 | | `model_name` | string | **Required**. The name of the model. This must be unique among all models owned by the user or organization. | 541 | | `options.visibility` | string | **Required**. Whether the model should be public or private. A public model can be viewed and run by anyone, whereas a private model can be viewed and run only by the user or organization members that own the model. | 542 | | `options.hardware` | string | **Required**. The SKU for the hardware used to run the model. Possible values can be found by calling [`replicate.hardware.list()`](#replicatehardwarelist). | 543 | | `options.description` | string | A description of the model. | 544 | | `options.github_url` | string | A URL for the model's source code on GitHub. | 545 | | `options.paper_url` | string | A URL for the model's paper. | 546 | | `options.license_url` | string | A URL for the model's license. | 547 | | `options.cover_image_url` | string | A URL for the model's cover image. This should be an image file. | 548 | 549 | ### `replicate.hardware.list` 550 | 551 | List available hardware for running models on Replicate. 552 | 553 | ```js 554 | const response = await replicate.hardware.list() 555 | ``` 556 | 557 | ```jsonc 558 | [ 559 | {"name": "CPU", "sku": "cpu" }, 560 | {"name": "Nvidia T4 GPU", "sku": "gpu-t4" }, 561 | {"name": "Nvidia A40 GPU", "sku": "gpu-a40-small" }, 562 | {"name": "Nvidia A40 (Large) GPU", "sku": "gpu-a40-large" }, 563 | ] 564 | ``` 565 | 566 | ### `replicate.models.versions.list` 567 | 568 | Get a list of all published versions of a model, including input and output schemas for each version. 569 | 570 | ```js 571 | const response = await replicate.models.versions.list(model_owner, model_name); 572 | ``` 573 | 574 | | name | type | description | 575 | | ------------- | ------ | ----------------------------------------------------------------------- | 576 | | `model_owner` | string | **Required**. The name of the user or organization that owns the model. | 577 | | `model_name` | string | **Required**. The name of the model. | 578 | 579 | ```jsonc 580 | { 581 | "previous": null, 582 | "next": null, 583 | "results": [ 584 | { 585 | "id": "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", 586 | "created_at": "2022-04-26T19:29:04.418669Z", 587 | "cog_version": "0.3.0", 588 | "openapi_schema": { 589 | /* ... */ 590 | } 591 | }, 592 | { 593 | "id": "e2e8c39e0f77177381177ba8c4025421ec2d7e7d3c389a9b3d364f8de560024f", 594 | "created_at": "2022-03-21T13:01:04.418669Z", 595 | "cog_version": "0.3.0", 596 | "openapi_schema": { 597 | /* ... */ 598 | } 599 | } 600 | ] 601 | } 602 | ``` 603 | 604 | ### `replicate.models.versions.get` 605 | 606 | Get metadata for a specific version of a model. 607 | 608 | ```js 609 | const response = await replicate.models.versions.get(model_owner, model_name, version_id); 610 | ``` 611 | 612 | | name | type | description | 613 | | ------------- | ------ | ----------------------------------------------------------------------- | 614 | | `model_owner` | string | **Required**. The name of the user or organization that owns the model. | 615 | | `model_name` | string | **Required**. The name of the model. | 616 | | `version_id` | string | **Required**. The model version | 617 | 618 | ```jsonc 619 | { 620 | "id": "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", 621 | "created_at": "2022-04-26T19:29:04.418669Z", 622 | "cog_version": "0.3.0", 623 | "openapi_schema": { 624 | /* ... */ 625 | } 626 | } 627 | ``` 628 | 629 | ### `replicate.collections.get` 630 | 631 | Get a list of curated model collections. See [replicate.com/collections](https://replicate.com/collections). 632 | 633 | ```js 634 | const response = await replicate.collections.get(collection_slug); 635 | ``` 636 | 637 | | name | type | description | 638 | | ----------------- | ------ | ------------------------------------------------------------------------------ | 639 | | `collection_slug` | string | **Required**. The slug of the collection. See http://replicate.com/collections | 640 | 641 | ### `replicate.predictions.create` 642 | 643 | Run a model with inputs you provide. 644 | 645 | ```js 646 | const response = await replicate.predictions.create(options); 647 | ``` 648 | 649 | | name | type | description | 650 | | ------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | 651 | | `options.input` | object | **Required**. An object with the model's inputs | 652 | | `options.model` | string | The name of the model, e.g. `black-forest-labs/flux-schnell`. This is required if you're running an [official model](https://replicate.com/explore#official-models). | 653 | | `options.version` | string | The 64-character [model version id](https://replicate.com/docs/topics/models/versions), e.g. `80537f9eead1a5bfa72d5ac6ea6414379be41d4d4f6679fd776e9535d1eb58bb`. This is required if you're not specifying a `model`. | 654 | | `options.wait` | number | Wait up to 60s for the prediction to finish before returning. Disabled by default. See [Synchronous predictions](https://replicate.com/docs/topics/predictions/create-a-prediction#sync-mode) for more information. | 655 | | `options.stream` | boolean | Requests a URL for streaming output output | 656 | | `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | 657 | | `options.webhook_events_filter` | string[] | You can change which events trigger webhook requests by specifying webhook events (`start` \| `output` \| `logs` \| `completed`) | 658 | 659 | ```jsonc 660 | { 661 | "id": "ufawqhfynnddngldkgtslldrkq", 662 | "version": "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", 663 | "status": "succeeded", 664 | "input": { 665 | "text": "Alice" 666 | }, 667 | "output": null, 668 | "error": null, 669 | "logs": null, 670 | "metrics": {}, 671 | "created_at": "2022-04-26T22:13:06.224088Z", 672 | "started_at": null, 673 | "completed_at": null, 674 | "urls": { 675 | "get": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", 676 | "cancel": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel", 677 | "stream": "https://streaming.api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq" // Present only if `options.stream` is `true` 678 | } 679 | } 680 | ``` 681 | 682 | #### Streaming 683 | 684 | Specify the `stream` option when creating a prediction 685 | to request a URL to receive streaming output using 686 | [server-sent events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). 687 | 688 | If the requested model version supports streaming, 689 | then the returned prediction will have a `stream` entry in its `urls` property 690 | with a URL that you can use to construct an 691 | [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource). 692 | 693 | ```js 694 | if (prediction && prediction.urls && prediction.urls.stream) { 695 | const source = new EventSource(prediction.urls.stream, { withCredentials: true }); 696 | 697 | source.addEventListener("output", (e) => { 698 | console.log("output", e.data); 699 | }); 700 | 701 | source.addEventListener("error", (e) => { 702 | console.error("error", JSON.parse(e.data)); 703 | }); 704 | 705 | source.addEventListener("done", (e) => { 706 | source.close(); 707 | console.log("done", JSON.parse(e.data)); 708 | }); 709 | } 710 | ``` 711 | 712 | A prediction's event stream consists of the following event types: 713 | 714 | | event | format | description | 715 | | -------- | ---------- | ---------------------------------------------- | 716 | | `output` | plain text | Emitted when the prediction returns new output | 717 | | `error` | JSON | Emitted when the prediction returns an error | 718 | | `done` | JSON | Emitted when the prediction finishes | 719 | 720 | A `done` event is emitted when a prediction finishes successfully, 721 | is cancelled, or produces an error. 722 | 723 | ### `replicate.predictions.get` 724 | 725 | ```js 726 | const response = await replicate.predictions.get(prediction_id); 727 | ``` 728 | 729 | | name | type | description | 730 | | --------------- | ------ | ------------------------------- | 731 | | `prediction_id` | number | **Required**. The prediction id | 732 | 733 | ```jsonc 734 | { 735 | "id": "ufawqhfynnddngldkgtslldrkq", 736 | "version": "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", 737 | "urls": { 738 | "get": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", 739 | "cancel": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel" 740 | }, 741 | "status": "starting", 742 | "input": { 743 | "text": "Alice" 744 | }, 745 | "output": null, 746 | "error": null, 747 | "logs": null, 748 | "metrics": {}, 749 | "created_at": "2022-04-26T22:13:06.224088Z", 750 | "started_at": null, 751 | "completed_at": null 752 | } 753 | ``` 754 | 755 | ### `replicate.predictions.cancel` 756 | 757 | Stop a running prediction before it finishes. 758 | 759 | ```js 760 | const response = await replicate.predictions.cancel(prediction_id); 761 | ``` 762 | 763 | | name | type | description | 764 | | --------------- | ------ | ------------------------------- | 765 | | `prediction_id` | number | **Required**. The prediction id | 766 | 767 | ```jsonc 768 | { 769 | "id": "ufawqhfynnddngldkgtslldrkq", 770 | "version": "5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa", 771 | "urls": { 772 | "get": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq", 773 | "cancel": "https://api.replicate.com/v1/predictions/ufawqhfynnddngldkgtslldrkq/cancel" 774 | }, 775 | "status": "canceled", 776 | "input": { 777 | "text": "Alice" 778 | }, 779 | "output": null, 780 | "error": null, 781 | "logs": null, 782 | "metrics": {}, 783 | "created_at": "2022-04-26T22:13:06.224088Z", 784 | "started_at": "2022-04-26T22:13:06.224088Z", 785 | "completed_at": "2022-04-26T22:13:06.224088Z" 786 | } 787 | ``` 788 | 789 | ### `replicate.predictions.list` 790 | 791 | Get a paginated list of all the predictions you've created. 792 | 793 | ```js 794 | const response = await replicate.predictions.list(); 795 | ``` 796 | 797 | `replicate.predictions.list()` takes no arguments. 798 | 799 | ```jsonc 800 | { 801 | "previous": null, 802 | "next": "https://api.replicate.com/v1/predictions?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", 803 | "results": [ 804 | { 805 | "id": "jpzd7hm5gfcapbfyt4mqytarku", 806 | "version": "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", 807 | "urls": { 808 | "get": "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku", 809 | "cancel": "https://api.replicate.com/v1/predictions/jpzd7hm5gfcapbfyt4mqytarku/cancel" 810 | }, 811 | "source": "web", 812 | "status": "succeeded", 813 | "created_at": "2022-04-26T20:00:40.658234Z", 814 | "started_at": "2022-04-26T20:00:84.583803Z", 815 | "completed_at": "2022-04-26T20:02:27.648305Z" 816 | } 817 | /* ... */ 818 | ] 819 | } 820 | ``` 821 | 822 | ### `replicate.trainings.create` 823 | 824 | Use the [training API](https://replicate.com/docs/fine-tuning) to fine-tune language models 825 | to make them better at a particular task. 826 | To see what **language models** currently support fine-tuning, 827 | check out Replicate's [collection of trainable language models](https://replicate.com/collections/trainable-language-models). 828 | 829 | If you're looking to fine-tune **image models**, 830 | check out Replicate's [guide to fine-tuning image models](https://replicate.com/docs/guides/fine-tune-an-image-model). 831 | 832 | ```js 833 | const response = await replicate.trainings.create(model_owner, model_name, version_id, options); 834 | ``` 835 | 836 | | name | type | description | 837 | | ------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | 838 | | `model_owner` | string | **Required**. The name of the user or organization that owns the model. | 839 | | `model_name` | string | **Required**. The name of the model. | 840 | | `version` | string | **Required**. The model version | 841 | | `options.destination` | string | **Required**. The destination for the trained version in the form `{username}/{model_name}` | 842 | | `options.input` | object | **Required**. An object with the model's inputs | 843 | | `options.webhook` | string | An HTTPS URL for receiving a webhook when the training has new output | 844 | | `options.webhook_events_filter` | string[] | You can change which events trigger webhook requests by specifying webhook events (`start` \| `output` \| `logs` \| `completed`) | 845 | 846 | ```jsonc 847 | { 848 | "id": "zz4ibbonubfz7carwiefibzgga", 849 | "version": "3ae0799123a1fe11f8c89fd99632f843fc5f7a761630160521c4253149754523", 850 | "status": "starting", 851 | "input": { 852 | "text": "..." 853 | }, 854 | "output": null, 855 | "error": null, 856 | "logs": null, 857 | "started_at": null, 858 | "created_at": "2023-03-28T21:47:58.566434Z", 859 | "completed_at": null 860 | } 861 | ``` 862 | 863 | > **Warning** 864 | > If you try to fine-tune a model that doesn't support training, 865 | > you'll get a `400 Bad Request` response from the server. 866 | 867 | ### `replicate.trainings.get` 868 | 869 | Get metadata and status of a training. 870 | 871 | ```js 872 | const response = await replicate.trainings.get(training_id); 873 | ``` 874 | 875 | | name | type | description | 876 | | ------------- | ------ | ----------------------------- | 877 | | `training_id` | number | **Required**. The training id | 878 | 879 | ```jsonc 880 | { 881 | "id": "zz4ibbonubfz7carwiefibzgga", 882 | "version": "3ae0799123a1fe11f8c89fd99632f843fc5f7a761630160521c4253149754523", 883 | "status": "succeeded", 884 | "input": { 885 | "data": "..." 886 | "param1": "..." 887 | }, 888 | "output": { 889 | "version": "..." 890 | }, 891 | "error": null, 892 | "logs": null, 893 | "webhook_completed": null, 894 | "started_at": "2023-03-28T21:48:02.402755Z", 895 | "created_at": "2023-03-28T21:47:58.566434Z", 896 | "completed_at": "2023-03-28T02:49:48.492023Z" 897 | } 898 | ``` 899 | 900 | ### `replicate.trainings.cancel` 901 | 902 | Stop a running training job before it finishes. 903 | 904 | ```js 905 | const response = await replicate.trainings.cancel(training_id); 906 | ``` 907 | 908 | | name | type | description | 909 | | ------------- | ------ | ----------------------------- | 910 | | `training_id` | number | **Required**. The training id | 911 | 912 | ```jsonc 913 | { 914 | "id": "zz4ibbonubfz7carwiefibzgga", 915 | "version": "3ae0799123a1fe11f8c89fd99632f843fc5f7a761630160521c4253149754523", 916 | "status": "canceled", 917 | "input": { 918 | "data": "..." 919 | "param1": "..." 920 | }, 921 | "output": { 922 | "version": "..." 923 | }, 924 | "error": null, 925 | "logs": null, 926 | "webhook_completed": null, 927 | "started_at": "2023-03-28T21:48:02.402755Z", 928 | "created_at": "2023-03-28T21:47:58.566434Z", 929 | "completed_at": "2023-03-28T02:49:48.492023Z" 930 | } 931 | ``` 932 | 933 | ### `replicate.trainings.list` 934 | 935 | Get a paginated list of all the trainings you've run. 936 | 937 | ```js 938 | const response = await replicate.trainings.list(); 939 | ``` 940 | 941 | `replicate.trainings.list()` takes no arguments. 942 | 943 | ```jsonc 944 | { 945 | "previous": null, 946 | "next": "https://api.replicate.com/v1/trainings?cursor=cD0yMDIyLTAxLTIxKzIzJTNBMTglM0EyNC41MzAzNTclMkIwMCUzQTAw", 947 | "results": [ 948 | { 949 | "id": "jpzd7hm5gfcapbfyt4mqytarku", 950 | "version": "b21cbe271e65c1718f2999b038c18b45e21e4fba961181fbfae9342fc53b9e05", 951 | "urls": { 952 | "get": "https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku", 953 | "cancel": "https://api.replicate.com/v1/trainings/jpzd7hm5gfcapbfyt4mqytarku/cancel" 954 | }, 955 | "source": "web", 956 | "status": "succeeded", 957 | "created_at": "2022-04-26T20:00:40.658234Z", 958 | "started_at": "2022-04-26T20:00:84.583803Z", 959 | "completed_at": "2022-04-26T20:02:27.648305Z" 960 | } 961 | /* ... */ 962 | ] 963 | } 964 | ``` 965 | 966 | ### `replicate.deployments.predictions.create` 967 | 968 | Run a model using your own custom deployment. 969 | 970 | Deployments allow you to run a model with a private, fixed API endpoint. You can configure the version of the model, the hardware it runs on, and how it scales. See the [deployments guide](https://replicate.com/docs/deployments) to learn more and get started. 971 | 972 | ```js 973 | const response = await replicate.deployments.predictions.create(deployment_owner, deployment_name, options); 974 | ``` 975 | 976 | | name | type | description | 977 | | ------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | 978 | | `deployment_owner` | string | **Required**. The name of the user or organization that owns the deployment | 979 | | `deployment_name` | string | **Required**. The name of the deployment | 980 | | `options.input` | object | **Required**. An object with the model's inputs | 981 | | `options.webhook` | string | An HTTPS URL for receiving a webhook when the prediction has new output | 982 | | `options.webhook_events_filter` | string[] | You can change which events trigger webhook requests by specifying webhook events (`start` \| `output` \| `logs` \| `completed`) | 983 | 984 | Use `replicate.wait` to wait for a prediction to finish, 985 | or `replicate.predictions.cancel` to cancel a prediction before it finishes. 986 | 987 | ### `replicate.deployments.list` 988 | 989 | List your deployments. 990 | 991 | ```js 992 | const response = await replicate.deployments.list(); 993 | ``` 994 | 995 | ```jsonc 996 | { 997 | "next": null, 998 | "previous": null, 999 | "results": [ 1000 | { 1001 | "owner": "acme", 1002 | "name": "my-app-image-generator", 1003 | "current_release": { /* ... */ } 1004 | } 1005 | /* ... */ 1006 | ] 1007 | } 1008 | ``` 1009 | 1010 | ### `replicate.deployments.create` 1011 | 1012 | Create a new deployment. 1013 | 1014 | ```js 1015 | const response = await replicate.deployments.create(options); 1016 | ``` 1017 | 1018 | | name | type | description | 1019 | | ----------------------- | ------ | -------------------------------------------------------------------------------- | 1020 | | `options.name` | string | Required. Name of the new deployment | 1021 | | `options.model` | string | Required. Name of the model in the format `{username}/{model_name}` | 1022 | | `options.version` | string | Required. ID of the model version | 1023 | | `options.hardware` | string | Required. SKU of the hardware to run the deployment on (`cpu`, `gpu-a100`, etc.) | 1024 | | `options.min_instances` | number | Minimum number of instances to run. Defaults to 0 | 1025 | | `options.max_instances` | number | Maximum number of instances to scale up to based on traffic. Defaults to 1 | 1026 | 1027 | ```jsonc 1028 | { 1029 | "owner": "acme", 1030 | "name": "my-app-image-generator", 1031 | "current_release": { 1032 | "number": 1, 1033 | "model": "stability-ai/sdxl", 1034 | "version": "da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf", 1035 | "created_at": "2024-03-14T11:43:32.049157Z", 1036 | "created_by": { 1037 | "type": "organization", 1038 | "username": "acme", 1039 | "name": "Acme, Inc.", 1040 | "github_url": "https://github.com/replicate" 1041 | }, 1042 | "configuration": { 1043 | "hardware": "gpu-a100", 1044 | "min_instances": 1, 1045 | "max_instances": 0 1046 | } 1047 | } 1048 | } 1049 | ``` 1050 | 1051 | ### `replicate.deployments.update` 1052 | 1053 | Update an existing deployment. 1054 | 1055 | ```js 1056 | const response = await replicate.deployments.update(deploymentOwner, deploymentName, options); 1057 | ``` 1058 | 1059 | | name | type | description | 1060 | | ----------------------- | ------ | -------------------------------------------------------------------------------- | 1061 | | `deploymentOwner` | string | Required. Owner of the deployment | 1062 | | `deploymentName` | string | Required. Name of the deployment to update | 1063 | | `options.model` | string | Name of the model in the format `{username}/{model_name}` | 1064 | | `options.version` | string | ID of the model version | 1065 | | `options.hardware` | string | Required. SKU of the hardware to run the deployment on (`cpu`, `gpu-a100`, etc.) | 1066 | | `options.min_instances` | number | Minimum number of instances to run | 1067 | | `options.max_instances` | number | Maximum number of instances to scale up to | 1068 | 1069 | ```jsonc 1070 | { 1071 | "owner": "acme", 1072 | "name": "my-app-image-generator", 1073 | "current_release": { 1074 | "number": 2, 1075 | "model": "stability-ai/sdxl", 1076 | "version": "39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b", 1077 | "created_at": "2024-03-14T11:43:32.049157Z", 1078 | "created_by": { 1079 | "type": "organization", 1080 | "username": "acme", 1081 | "name": "Acme, Inc.", 1082 | "github_url": "https://github.com/replicate" 1083 | }, 1084 | "configuration": { 1085 | "hardware": "gpu-a100", 1086 | "min_instances": 1, 1087 | "max_instances": 0 1088 | } 1089 | } 1090 | } 1091 | ``` 1092 | 1093 | ### `replicate.files.create` 1094 | 1095 | Upload a file to Replicate. 1096 | 1097 | > [!TIP] 1098 | > The client library calls this endpoint automatically to upload the contents of 1099 | > file handles provided as prediction and training inputs. 1100 | > You don't need to call this method directly unless you want more control. 1101 | > For example, you might want to reuse a file across multiple predictions 1102 | > without re-uploading it each time, 1103 | > or you may want to set custom metadata on the file resource. 1104 | > 1105 | > You can configure how a client handles file handle inputs 1106 | > by setting the `fileEncodingStrategy` option in the 1107 | > [client constructor](#constructor). 1108 | 1109 | ```js 1110 | const response = await replicate.files.create(file, metadata); 1111 | ``` 1112 | 1113 | | name | type | description | 1114 | | ---------- | --------------------- | ---------------------------------------------------------- | 1115 | | `file` | Blob, File, or Buffer | **Required**. The file to upload. | 1116 | | `metadata` | object | Optional. User-provided metadata associated with the file. | 1117 | 1118 | ```jsonc 1119 | { 1120 | "id": "MTQzODcyMDct0YjZkLWE1ZGYtMmRjZTViNWIwOGEyNjNhNS0", 1121 | "name": "photo.webp", 1122 | "content_type": "image/webp", 1123 | "size": 96936, 1124 | "etag": "f211779ff7502705bbf42e9874a17ab3", 1125 | "checksums": { 1126 | "sha256": "7282eb6991fa4f38d80c312dc207d938c156d714c94681623aedac846488e7d3", 1127 | "md5": "f211779ff7502705bbf42e9874a17ab3" 1128 | }, 1129 | "metadata": { 1130 | "customer_reference_id": "123" 1131 | }, 1132 | "created_at": "2024-06-28T10:16:04.062Z", 1133 | "expires_at": "2024-06-29T10:16:04.062Z", 1134 | "urls": { 1135 | "get": "https://api.replicate.com/v1/files/MTQzODcyMDct0YjZkLWE1ZGYtMmRjZTViNWIwOGEyNjNhNS0" 1136 | } 1137 | } 1138 | ``` 1139 | 1140 | Files uploaded to Replicate using this endpoint expire after 24 hours. 1141 | 1142 | Pass the `urls.get` property of a file resource 1143 | to use it as an input when running a model on Replicate. 1144 | The value of `urls.get` is opaque, 1145 | and shouldn't be inferred from other attributes. 1146 | 1147 | The contents of a file are only made accessible to a model running on Replicate, 1148 | and only when passed as a prediction or training input 1149 | by the user or organization who created the file. 1150 | 1151 | ### `replicate.files.list` 1152 | 1153 | List all files you've uploaded. 1154 | 1155 | ```js 1156 | const response = await replicate.files.list(); 1157 | ``` 1158 | 1159 | ### `replicate.files.get` 1160 | 1161 | Get metadata for a specific file. 1162 | 1163 | ```js 1164 | const response = await replicate.files.get(file_id); 1165 | ``` 1166 | 1167 | | name | type | description | 1168 | | --------- | ------ | --------------------------------- | 1169 | | `file_id` | string | **Required**. The ID of the file. | 1170 | 1171 | ### `replicate.files.delete` 1172 | 1173 | Delete a file. 1174 | 1175 | Files uploaded using the `replicate.files.create` method expire after 24 hours. 1176 | You can use this method to delete them sooner. 1177 | 1178 | ```js 1179 | const response = await replicate.files.delete(file_id); 1180 | ``` 1181 | 1182 | | name | type | description | 1183 | | --------- | ------ | --------------------------------- | 1184 | | `file_id` | string | **Required**. The ID of the file. | 1185 | 1186 | ### `replicate.paginate` 1187 | 1188 | Pass another method as an argument to iterate over results 1189 | that are spread across multiple pages. 1190 | 1191 | This method is implemented as an 1192 | [async generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator), 1193 | which you can use in a for loop or iterate over manually. 1194 | 1195 | ```js 1196 | // iterate over paginated results in a for loop 1197 | for await (const page of replicate.paginate(replicate.predictions.list)) { 1198 | /* do something with page of results */ 1199 | } 1200 | 1201 | // iterate over paginated results one at a time 1202 | let paginator = replicate.paginate(replicate.predictions.list); 1203 | const page1 = await paginator.next(); 1204 | const page2 = await paginator.next(); 1205 | // etc. 1206 | ``` 1207 | 1208 | ### `replicate.request` 1209 | 1210 | Low-level method used by the Replicate client to interact with API endpoints. 1211 | 1212 | ```js 1213 | const response = await replicate.request(route, parameters); 1214 | ``` 1215 | 1216 | | name | type | description | 1217 | | -------------------- | ------------------- | ----------- | 1218 | | `options.route` | `string` | Required. REST API endpoint path. 1219 | | `options.params` | `object` | URL query parameters for the given route. | 1220 | | `options.method` | `string` | HTTP method for the given route. | 1221 | | `options.headers` | `object` | Additional HTTP headers for the given route. | 1222 | | `options.data` | `object \| FormData` | Request body. | 1223 | | `options.signal` | `AbortSignal` | Optional `AbortSignal`. | 1224 | 1225 | The `replicate.request()` method is used by the other methods 1226 | to interact with the Replicate API. 1227 | You can call this method directly to make other requests to the API. 1228 | 1229 | The method accepts an `AbortSignal` which can be used to cancel the request in flight. 1230 | 1231 | ### `FileOutput` 1232 | 1233 | `FileOutput` is a `ReadableStream` instance that represents a model file output. It can be used to stream file data to disk or as a `Response` body to an HTTP request. 1234 | 1235 | ```javascript 1236 | const [output] = await replicate.run("black-forest-labs/flux-schnell", { 1237 | input: { prompt: "astronaut riding a rocket like a horse" } 1238 | }); 1239 | 1240 | // To access the file URL: 1241 | console.log(output.url()); //=> "http://example.com" 1242 | 1243 | // To write the file to disk: 1244 | fs.writeFile("my-image.png", output); 1245 | 1246 | // To stream the file back to a browser: 1247 | return new Response(output); 1248 | 1249 | // To read the file in chunks: 1250 | for await (const chunk of output) { 1251 | console.log(chunk); // UInt8Array 1252 | } 1253 | ``` 1254 | 1255 | You can opt out of FileOutput by passing `useFileOutput: false` to the `Replicate` constructor: 1256 | 1257 | ```javascript 1258 | const replicate = new Replicate({ useFileOutput: false }); 1259 | ``` 1260 | 1261 | | method | returns | description | 1262 | | -------------------- | ------ | ------------------------------------------------------------ | 1263 | | `blob()` | object | A `Blob` instance containing the binary file | 1264 | | `url()` | string | A `URL` object pointing to the underlying data source. Please note that this may not always be an HTTP URL in future. | 1265 | 1266 | ## Troubleshooting 1267 | 1268 | ### Predictions hanging in Next.js 1269 | 1270 | Next.js App Router adds some extensions to `fetch` to make it cache responses. To disable this behavior, set the `cache` option to `"no-store"` on the Replicate client's fetch object: 1271 | 1272 | ```js 1273 | replicate = new Replicate({/*...*/}) 1274 | replicate.fetch = (url, options) => { 1275 | return fetch(url, { ...options, cache: "no-store" }); 1276 | }; 1277 | ``` 1278 | 1279 | Alternatively you can use Next.js [`noStore`](https://github.com/replicate/replicate-javascript/issues/136#issuecomment-1847442879) to opt out of caching for your component. 1280 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.0.0/schema.json", 3 | "files": { 4 | "ignore": [ 5 | ".wrangler", 6 | "node_modules", 7 | "vendor/*" 8 | ] 9 | }, 10 | "formatter": { 11 | "indentStyle": "space", 12 | "indentWidth": 2 13 | }, 14 | "javascript": { 15 | "formatter": { 16 | "trailingComma": "es5" 17 | } 18 | }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "a11y": { 23 | "useAltText": "off", 24 | "useMediaCaption": "off", 25 | "noSvgWithoutTitle": "off" 26 | }, 27 | "complexity": { 28 | "useLiteralKeys": "off", 29 | "useOptionalChain": "off" 30 | }, 31 | "performance": { 32 | "noAccumulatingSpread": "off" 33 | }, 34 | "suspicious": { 35 | "noArrayIndexKey": "off", 36 | "noExplicitAny": "off" 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "replicate" { 2 | type Status = "starting" | "processing" | "succeeded" | "failed" | "canceled"; 3 | type Visibility = "public" | "private"; 4 | type WebhookEventType = "start" | "output" | "logs" | "completed"; 5 | 6 | export interface ApiError extends Error { 7 | request: Request; 8 | response: Response; 9 | } 10 | 11 | export interface FileOutput extends ReadableStream { 12 | blob(): Promise; 13 | url(): URL; 14 | toString(): string; 15 | } 16 | 17 | export interface Account { 18 | type: "user" | "organization"; 19 | username: string; 20 | name: string; 21 | github_url?: string; 22 | } 23 | 24 | export interface Collection { 25 | name: string; 26 | slug: string; 27 | description: string; 28 | models?: Model[]; 29 | } 30 | 31 | export interface Deployment { 32 | owner: string; 33 | name: string; 34 | current_release: { 35 | number: number; 36 | model: string; 37 | version: string; 38 | created_at: string; 39 | created_by: Account; 40 | configuration: { 41 | hardware: string; 42 | min_instances: number; 43 | max_instances: number; 44 | }; 45 | }; 46 | } 47 | 48 | export interface FileObject { 49 | id: string; 50 | name: string; 51 | content_type: string; 52 | size: number; 53 | etag: string; 54 | checksum: string; 55 | metadata: Record; 56 | created_at: string; 57 | expires_at: string | null; 58 | urls: { 59 | get: string; 60 | }; 61 | } 62 | 63 | export interface Hardware { 64 | sku: string; 65 | name: string; 66 | } 67 | 68 | export interface Model { 69 | url: string; 70 | owner: string; 71 | name: string; 72 | description?: string; 73 | visibility: "public" | "private"; 74 | github_url?: string; 75 | paper_url?: string; 76 | license_url?: string; 77 | run_count: number; 78 | cover_image_url?: string; 79 | default_example?: Prediction; 80 | latest_version?: ModelVersion; 81 | } 82 | 83 | export interface ModelVersion { 84 | id: string; 85 | created_at: string; 86 | cog_version: string; 87 | openapi_schema: object; 88 | } 89 | 90 | export interface Prediction { 91 | id: string; 92 | status: Status; 93 | model: string; 94 | version: string; 95 | input: object; 96 | output?: any; // TODO: this should be `unknown` 97 | source: "api" | "web"; 98 | error?: unknown; 99 | logs?: string; 100 | metrics?: { 101 | predict_time?: number; 102 | }; 103 | webhook?: string; 104 | webhook_events_filter?: WebhookEventType[]; 105 | created_at: string; 106 | started_at?: string; 107 | completed_at?: string; 108 | urls: { 109 | get: string; 110 | cancel: string; 111 | stream?: string; 112 | }; 113 | } 114 | 115 | export type Training = Prediction; 116 | 117 | export type FileEncodingStrategy = "default" | "upload" | "data-uri"; 118 | 119 | export interface Page { 120 | previous?: string; 121 | next?: string; 122 | results: T[]; 123 | } 124 | 125 | export interface ServerSentEvent { 126 | event: string; 127 | data: string; 128 | id?: string; 129 | retry?: number; 130 | } 131 | 132 | export interface WebhookSecret { 133 | key: string; 134 | } 135 | 136 | export default class Replicate { 137 | constructor(options?: { 138 | auth?: string; 139 | userAgent?: string; 140 | baseUrl?: string; 141 | fetch?: ( 142 | input: Request | string, 143 | init?: RequestInit 144 | ) => Promise; 145 | fileEncodingStrategy?: FileEncodingStrategy; 146 | useFileOutput?: boolean; 147 | }); 148 | 149 | auth: string; 150 | userAgent?: string; 151 | baseUrl?: string; 152 | fetch: (input: Request | string, init?: RequestInit) => Promise; 153 | fileEncodingStrategy: FileEncodingStrategy; 154 | 155 | run( 156 | identifier: `${string}/${string}` | `${string}/${string}:${string}`, 157 | options: { 158 | input: object; 159 | wait?: 160 | | { mode: "block"; interval?: number; timeout?: number } 161 | | { mode: "poll"; interval?: number }; 162 | webhook?: string; 163 | webhook_events_filter?: WebhookEventType[]; 164 | signal?: AbortSignal; 165 | }, 166 | progress?: (prediction: Prediction) => void 167 | ): Promise; 168 | 169 | stream( 170 | identifier: `${string}/${string}` | `${string}/${string}:${string}`, 171 | options: { 172 | input: object; 173 | webhook?: string; 174 | webhook_events_filter?: WebhookEventType[]; 175 | signal?: AbortSignal; 176 | } 177 | ): AsyncGenerator; 178 | 179 | request( 180 | route: string | URL, 181 | options: { 182 | method?: string; 183 | headers?: object | Headers; 184 | params?: object; 185 | data?: object; 186 | signal?: AbortSignal; 187 | } 188 | ): Promise; 189 | 190 | paginate( 191 | endpoint: () => Promise>, 192 | options?: { signal?: AbortSignal } 193 | ): AsyncGenerator; 194 | 195 | wait( 196 | prediction: Prediction, 197 | options?: { 198 | interval?: number; 199 | }, 200 | stop?: (prediction: Prediction) => Promise 201 | ): Promise; 202 | 203 | accounts: { 204 | current(options?: { signal?: AbortSignal }): Promise; 205 | }; 206 | 207 | collections: { 208 | list(options?: { signal?: AbortSignal }): Promise>; 209 | get( 210 | collection_slug: string, 211 | options?: { signal?: AbortSignal } 212 | ): Promise; 213 | }; 214 | 215 | deployments: { 216 | predictions: { 217 | create( 218 | deployment_owner: string, 219 | deployment_name: string, 220 | options: { 221 | input: object; 222 | /** @deprecated */ 223 | stream?: boolean; 224 | webhook?: string; 225 | webhook_events_filter?: WebhookEventType[]; 226 | wait?: number | boolean; 227 | signal?: AbortSignal; 228 | } 229 | ): Promise; 230 | }; 231 | get( 232 | deployment_owner: string, 233 | deployment_name: string, 234 | options?: { signal?: AbortSignal } 235 | ): Promise; 236 | create( 237 | deployment_config: { 238 | name: string; 239 | model: string; 240 | version: string; 241 | hardware: string; 242 | min_instances: number; 243 | max_instances: number; 244 | }, 245 | options?: { signal?: AbortSignal } 246 | ): Promise; 247 | update( 248 | deployment_owner: string, 249 | deployment_name: string, 250 | deployment_config: { 251 | version?: string; 252 | hardware?: string; 253 | min_instances?: number; 254 | max_instances?: number; 255 | } & ( 256 | | { version: string } 257 | | { hardware: string } 258 | | { min_instances: number } 259 | | { max_instances: number } 260 | ), 261 | options?: { signal?: AbortSignal } 262 | ): Promise; 263 | delete( 264 | deployment_owner: string, 265 | deployment_name: string, 266 | options?: { signal?: AbortSignal } 267 | ): Promise; 268 | list(options?: { signal?: AbortSignal }): Promise>; 269 | }; 270 | 271 | files: { 272 | create( 273 | file: Blob | Buffer, 274 | metadata?: Record, 275 | options?: { signal?: AbortSignal } 276 | ): Promise; 277 | list(options?: { signal?: AbortSignal }): Promise>; 278 | get( 279 | file_id: string, 280 | options?: { signal?: AbortSignal } 281 | ): Promise; 282 | delete( 283 | file_id: string, 284 | options?: { signal?: AbortSignal } 285 | ): Promise; 286 | }; 287 | 288 | hardware: { 289 | list(options?: { signal?: AbortSignal }): Promise; 290 | }; 291 | 292 | models: { 293 | get( 294 | model_owner: string, 295 | model_name: string, 296 | options?: { signal?: AbortSignal } 297 | ): Promise; 298 | list(options?: { signal?: AbortSignal }): Promise>; 299 | create( 300 | model_owner: string, 301 | model_name: string, 302 | options: { 303 | visibility: Visibility; 304 | hardware: string; 305 | description?: string; 306 | github_url?: string; 307 | paper_url?: string; 308 | license_url?: string; 309 | cover_image_url?: string; 310 | signal?: AbortSignal; 311 | } 312 | ): Promise; 313 | versions: { 314 | list( 315 | model_owner: string, 316 | model_name: string, 317 | options?: { signal?: AbortSignal } 318 | ): Promise>; 319 | get( 320 | model_owner: string, 321 | model_name: string, 322 | version_id: string, 323 | options?: { signal?: AbortSignal } 324 | ): Promise; 325 | }; 326 | search( 327 | query: string, 328 | options?: { signal?: AbortSignal } 329 | ): Promise>; 330 | }; 331 | 332 | predictions: { 333 | create( 334 | options: { 335 | model?: string; 336 | version?: string; 337 | input: object; 338 | /** @deprecated */ 339 | stream?: boolean; 340 | webhook?: string; 341 | webhook_events_filter?: WebhookEventType[]; 342 | wait?: boolean | number; 343 | signal?: AbortSignal; 344 | } & ({ version: string } | { model: string }) 345 | ): Promise; 346 | get( 347 | prediction_id: string, 348 | options?: { signal?: AbortSignal } 349 | ): Promise; 350 | cancel( 351 | prediction_id: string, 352 | options?: { signal?: AbortSignal } 353 | ): Promise; 354 | list(options?: { signal?: AbortSignal }): Promise>; 355 | }; 356 | 357 | trainings: { 358 | create( 359 | model_owner: string, 360 | model_name: string, 361 | version_id: string, 362 | options: { 363 | destination: `${string}/${string}`; 364 | input: object; 365 | webhook?: string; 366 | webhook_events_filter?: WebhookEventType[]; 367 | signal?: AbortSignal; 368 | } 369 | ): Promise; 370 | get( 371 | training_id: string, 372 | options?: { signal?: AbortSignal } 373 | ): Promise; 374 | cancel( 375 | training_id: string, 376 | options?: { signal?: AbortSignal } 377 | ): Promise; 378 | list(options?: { signal?: AbortSignal }): Promise>; 379 | }; 380 | 381 | webhooks: { 382 | default: { 383 | secret: { 384 | get(options?: { signal?: AbortSignal }): Promise; 385 | }; 386 | }; 387 | }; 388 | } 389 | 390 | export function validateWebhook( 391 | request: Request, 392 | secret: string, 393 | crypto?: Crypto 394 | ): Promise; 395 | 396 | export function validateWebhook( 397 | requestData: { 398 | id: string; 399 | timestamp: string; 400 | signature: string; 401 | body: string; 402 | secret: string; 403 | }, 404 | crypto?: Crypto 405 | ): Promise; 406 | 407 | export function parseProgressFromLogs(logs: Prediction | string): { 408 | percentage: number; 409 | current: number; 410 | total: number; 411 | } | null; 412 | } 413 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const ApiError = require("./lib/error"); 2 | const ModelVersionIdentifier = require("./lib/identifier"); 3 | const { createReadableStream, createFileOutput } = require("./lib/stream"); 4 | const { 5 | transform, 6 | withAutomaticRetries, 7 | validateWebhook, 8 | parseProgressFromLogs, 9 | streamAsyncIterator, 10 | } = require("./lib/util"); 11 | 12 | const accounts = require("./lib/accounts"); 13 | const collections = require("./lib/collections"); 14 | const deployments = require("./lib/deployments"); 15 | const files = require("./lib/files"); 16 | const hardware = require("./lib/hardware"); 17 | const models = require("./lib/models"); 18 | const predictions = require("./lib/predictions"); 19 | const trainings = require("./lib/trainings"); 20 | const webhooks = require("./lib/webhooks"); 21 | 22 | const packageJSON = require("./package.json"); 23 | 24 | /** 25 | * Replicate API client library 26 | * 27 | * @see https://replicate.com/docs/reference/http 28 | * @example 29 | * // Create a new Replicate API client instance 30 | * const Replicate = require("replicate"); 31 | * const replicate = new Replicate({ 32 | * // get your token from https://replicate.com/account 33 | * auth: process.env.REPLICATE_API_TOKEN, 34 | * userAgent: "my-app/1.2.3" 35 | * }); 36 | * 37 | * // Run a model and await the result: 38 | * const model = 'owner/model:version-id' 39 | * const input = {text: 'Hello, world!'} 40 | * const output = await replicate.run(model, { input }); 41 | */ 42 | class Replicate { 43 | /** 44 | * Create a new Replicate API client instance. 45 | * 46 | * @param {object} options - Configuration options for the client 47 | * @param {string} options.auth - API access token. Defaults to the `REPLICATE_API_TOKEN` environment variable. 48 | * @param {string} options.userAgent - Identifier of your app 49 | * @param {string} [options.baseUrl] - Defaults to https://api.replicate.com/v1 50 | * @param {Function} [options.fetch] - Fetch function to use. Defaults to `globalThis.fetch` 51 | * @param {boolean} [options.useFileOutput] - Set to `false` to disable `FileOutput` objects from `run` instead of URLs, defaults to true. 52 | * @param {"default" | "upload" | "data-uri"} [options.fileEncodingStrategy] - Determines the file encoding strategy to use 53 | */ 54 | constructor(options = {}) { 55 | this.auth = 56 | options.auth || 57 | (typeof process !== "undefined" ? process.env.REPLICATE_API_TOKEN : null); 58 | this.userAgent = 59 | options.userAgent || `replicate-javascript/${packageJSON.version}`; 60 | this.baseUrl = options.baseUrl || "https://api.replicate.com/v1"; 61 | this.fetch = options.fetch || globalThis.fetch; 62 | this.fileEncodingStrategy = options.fileEncodingStrategy || "default"; 63 | this.useFileOutput = options.useFileOutput === false ? false : true; 64 | 65 | this.accounts = { 66 | current: accounts.current.bind(this), 67 | }; 68 | 69 | this.collections = { 70 | list: collections.list.bind(this), 71 | get: collections.get.bind(this), 72 | }; 73 | 74 | this.deployments = { 75 | get: deployments.get.bind(this), 76 | create: deployments.create.bind(this), 77 | update: deployments.update.bind(this), 78 | delete: deployments.delete.bind(this), 79 | list: deployments.list.bind(this), 80 | predictions: { 81 | create: deployments.predictions.create.bind(this), 82 | }, 83 | }; 84 | 85 | this.files = { 86 | create: files.create.bind(this), 87 | get: files.get.bind(this), 88 | list: files.list.bind(this), 89 | delete: files.delete.bind(this), 90 | }; 91 | 92 | this.hardware = { 93 | list: hardware.list.bind(this), 94 | }; 95 | 96 | this.models = { 97 | get: models.get.bind(this), 98 | list: models.list.bind(this), 99 | create: models.create.bind(this), 100 | versions: { 101 | list: models.versions.list.bind(this), 102 | get: models.versions.get.bind(this), 103 | }, 104 | search: models.search.bind(this), 105 | }; 106 | 107 | this.predictions = { 108 | create: predictions.create.bind(this), 109 | get: predictions.get.bind(this), 110 | cancel: predictions.cancel.bind(this), 111 | list: predictions.list.bind(this), 112 | }; 113 | 114 | this.trainings = { 115 | create: trainings.create.bind(this), 116 | get: trainings.get.bind(this), 117 | cancel: trainings.cancel.bind(this), 118 | list: trainings.list.bind(this), 119 | }; 120 | 121 | this.webhooks = { 122 | default: { 123 | secret: { 124 | get: webhooks.default.secret.get.bind(this), 125 | }, 126 | }, 127 | }; 128 | } 129 | 130 | /** 131 | * Run a model and wait for its output. 132 | * 133 | * @param {string} ref - Required. The model version identifier in the format "owner/name" or "owner/name:version" 134 | * @param {object} options 135 | * @param {object} options.input - Required. An object with the model inputs 136 | * @param {{mode: "block", timeout?: number, interval?: number} | {mode: "poll", interval?: number }} [options.wait] - Options for waiting for the prediction to finish. If `wait` is explicitly true, the function will block and wait for the prediction to finish. 137 | * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output 138 | * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) 139 | * @param {AbortSignal} [options.signal] - AbortSignal to cancel the prediction 140 | * @param {Function} [progress] - Callback function that receives the prediction object as it's updated. The function is called when the prediction is created, each time its updated while polling for completion, and when it's completed. 141 | * @throws {Error} If the reference is invalid 142 | * @throws {Error} If the prediction failed 143 | * @returns {Promise} - Resolves with the output of running the model 144 | */ 145 | async run(ref, options, progress) { 146 | const { wait = { mode: "block" }, signal, ...data } = options; 147 | 148 | const identifier = ModelVersionIdentifier.parse(ref); 149 | 150 | let prediction; 151 | if (identifier.version) { 152 | prediction = await this.predictions.create({ 153 | ...data, 154 | version: identifier.version, 155 | wait: wait.mode === "block" ? wait.timeout ?? true : false, 156 | }); 157 | } else if (identifier.owner && identifier.name) { 158 | prediction = await this.predictions.create({ 159 | ...data, 160 | model: `${identifier.owner}/${identifier.name}`, 161 | wait: wait.mode === "block" ? wait.timeout ?? true : false, 162 | }); 163 | } else { 164 | throw new Error("Invalid model version identifier"); 165 | } 166 | 167 | // Call progress callback with the initial prediction object 168 | if (progress) { 169 | progress(prediction); 170 | } 171 | 172 | const isDone = wait.mode === "block" && prediction.status !== "starting"; 173 | if (!isDone) { 174 | prediction = await this.wait( 175 | prediction, 176 | { interval: wait.mode === "poll" ? wait.interval : undefined }, 177 | async (updatedPrediction) => { 178 | // Call progress callback with the updated prediction object 179 | if (progress) { 180 | progress(updatedPrediction); 181 | } 182 | 183 | // We handle the cancel later in the function. 184 | if (signal && signal.aborted) { 185 | return true; // stop polling 186 | } 187 | 188 | return false; // continue polling 189 | } 190 | ); 191 | } 192 | 193 | if (signal && signal.aborted) { 194 | prediction = await this.predictions.cancel(prediction.id); 195 | } 196 | 197 | // Call progress callback with the completed prediction object 198 | if (progress) { 199 | progress(prediction); 200 | } 201 | 202 | if (prediction.status === "failed") { 203 | throw new Error(`Prediction failed: ${prediction.error}`); 204 | } 205 | 206 | return transform(prediction.output, (value) => { 207 | if ( 208 | typeof value === "string" && 209 | (value.startsWith("https:") || value.startsWith("data:")) 210 | ) { 211 | return this.useFileOutput 212 | ? createFileOutput({ url: value, fetch: this.fetch }) 213 | : value; 214 | } 215 | return value; 216 | }); 217 | } 218 | 219 | /** 220 | * Make a request to the Replicate API. 221 | * 222 | * @param {string} route - REST API endpoint path 223 | * @param {object} options - Request parameters 224 | * @param {string} [options.method] - HTTP method. Defaults to GET 225 | * @param {object} [options.params] - Query parameters 226 | * @param {object|Headers} [options.headers] - HTTP headers 227 | * @param {object} [options.data] - Body parameters 228 | * @param {AbortSignal} [options.signal] - AbortSignal to cancel the request 229 | * @returns {Promise} - Resolves with the response object 230 | * @throws {ApiError} If the request failed 231 | */ 232 | async request(route, options) { 233 | const { auth, baseUrl, userAgent } = this; 234 | 235 | let url; 236 | if (route instanceof URL) { 237 | url = route; 238 | } else { 239 | url = new URL( 240 | route.startsWith("/") ? route.slice(1) : route, 241 | baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/` 242 | ); 243 | } 244 | 245 | const { method = "GET", params = {}, data, signal } = options; 246 | 247 | for (const [key, value] of Object.entries(params)) { 248 | url.searchParams.append(key, value); 249 | } 250 | 251 | const headers = { 252 | "Content-Type": "application/json", 253 | "User-Agent": userAgent, 254 | }; 255 | if (auth) { 256 | headers["Authorization"] = `Bearer ${auth}`; 257 | } 258 | if (options.headers) { 259 | for (const [key, value] of Object.entries(options.headers)) { 260 | headers[key] = value; 261 | } 262 | } 263 | 264 | let body = undefined; 265 | if (data instanceof FormData) { 266 | body = data; 267 | // biome-ignore lint/performance/noDelete: 268 | delete headers["Content-Type"]; // Use automatic content type header 269 | } else if (data) { 270 | body = JSON.stringify(data); 271 | } 272 | 273 | const init = { 274 | method, 275 | headers, 276 | body, 277 | signal, 278 | }; 279 | 280 | const shouldRetry = 281 | method === "GET" 282 | ? (response) => response.status === 429 || response.status >= 500 283 | : (response) => response.status === 429; 284 | 285 | // Workaround to fix `TypeError: Illegal invocation` error in Cloudflare Workers 286 | // https://github.com/replicate/replicate-javascript/issues/134 287 | const _fetch = this.fetch; // eslint-disable-line no-underscore-dangle 288 | const response = await withAutomaticRetries(async () => _fetch(url, init), { 289 | shouldRetry, 290 | }); 291 | 292 | if (!response.ok) { 293 | const request = new Request(url, init); 294 | const responseText = await response.text(); 295 | throw new ApiError( 296 | `Request to ${url} failed with status ${response.status} ${response.statusText}: ${responseText}.`, 297 | request, 298 | response 299 | ); 300 | } 301 | 302 | return response; 303 | } 304 | 305 | /** 306 | * Stream a model and wait for its output. 307 | * 308 | * @param {string} identifier - Required. The model version identifier in the format "{owner}/{name}:{version}" 309 | * @param {object} options 310 | * @param {object} options.input - Required. An object with the model inputs 311 | * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output 312 | * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) 313 | * @param {AbortSignal} [options.signal] - AbortSignal to cancel the prediction 314 | * @throws {Error} If the prediction failed 315 | * @yields {ServerSentEvent} Each streamed event from the prediction 316 | */ 317 | async *stream(ref, options) { 318 | const { wait, signal, ...data } = options; 319 | 320 | const identifier = ModelVersionIdentifier.parse(ref); 321 | 322 | let prediction; 323 | if (identifier.version) { 324 | prediction = await this.predictions.create({ 325 | ...data, 326 | version: identifier.version, 327 | }); 328 | } else if (identifier.owner && identifier.name) { 329 | prediction = await this.predictions.create({ 330 | ...data, 331 | model: `${identifier.owner}/${identifier.name}`, 332 | }); 333 | } else { 334 | throw new Error("Invalid model version identifier"); 335 | } 336 | 337 | if (prediction.urls && prediction.urls.stream) { 338 | const stream = createReadableStream({ 339 | url: prediction.urls.stream, 340 | fetch: this.fetch, 341 | ...(signal ? { options: { signal } } : {}), 342 | }); 343 | 344 | yield* streamAsyncIterator(stream); 345 | } else { 346 | throw new Error("Prediction does not support streaming"); 347 | } 348 | } 349 | 350 | /** 351 | * Paginate through a list of results. 352 | * 353 | * @generator 354 | * @example 355 | * for await (const page of replicate.paginate(replicate.predictions.list) { 356 | * console.log(page); 357 | * } 358 | * @param {Function} endpoint - Function that returns a promise for the next page of results 359 | * @param {object} [options] 360 | * @param {AbortSignal} [options.signal] - AbortSignal to cancel the request. 361 | * @yields {object[]} Each page of results 362 | */ 363 | async *paginate(endpoint, options = {}) { 364 | const response = await endpoint(); 365 | yield response.results; 366 | if (response.next && !(options.signal && options.signal.aborted)) { 367 | const nextPage = () => 368 | this.request(response.next, { 369 | method: "GET", 370 | signal: options.signal, 371 | }).then((r) => r.json()); 372 | yield* this.paginate(nextPage, options); 373 | } 374 | } 375 | 376 | /** 377 | * Wait for a prediction to finish. 378 | * 379 | * If the prediction has already finished, 380 | * this function returns immediately. 381 | * Otherwise, it polls the API until the prediction finishes. 382 | * 383 | * @async 384 | * @param {object} prediction - Prediction object 385 | * @param {object} options - Options 386 | * @param {number} [options.interval] - Polling interval in milliseconds. Defaults to 500 387 | * @param {Function} [stop] - Async callback function that is called after each polling attempt. Receives the prediction object as an argument. Return false to cancel polling. 388 | * @throws {Error} If the prediction doesn't complete within the maximum number of attempts 389 | * @throws {Error} If the prediction failed 390 | * @returns {Promise} Resolves with the completed prediction object 391 | */ 392 | async wait(prediction, options, stop) { 393 | const { id } = prediction; 394 | if (!id) { 395 | throw new Error("Invalid prediction"); 396 | } 397 | 398 | if ( 399 | prediction.status === "succeeded" || 400 | prediction.status === "failed" || 401 | prediction.status === "canceled" 402 | ) { 403 | return prediction; 404 | } 405 | 406 | // eslint-disable-next-line no-promise-executor-return 407 | const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); 408 | 409 | const interval = (options && options.interval) || 500; 410 | 411 | let updatedPrediction = await this.predictions.get(id); 412 | 413 | while ( 414 | updatedPrediction.status !== "succeeded" && 415 | updatedPrediction.status !== "failed" && 416 | updatedPrediction.status !== "canceled" 417 | ) { 418 | /* eslint-disable no-await-in-loop */ 419 | if (stop && (await stop(updatedPrediction)) === true) { 420 | break; 421 | } 422 | 423 | await sleep(interval); 424 | updatedPrediction = await this.predictions.get(prediction.id); 425 | /* eslint-enable no-await-in-loop */ 426 | } 427 | 428 | if (updatedPrediction.status === "failed") { 429 | throw new Error(`Prediction failed: ${updatedPrediction.error}`); 430 | } 431 | 432 | return updatedPrediction; 433 | } 434 | } 435 | 436 | module.exports = Replicate; 437 | module.exports.validateWebhook = validateWebhook; 438 | module.exports.parseProgressFromLogs = parseProgressFromLogs; 439 | -------------------------------------------------------------------------------- /integration/browser/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | audit=false 3 | fund=false 4 | -------------------------------------------------------------------------------- /integration/browser/README.md: -------------------------------------------------------------------------------- 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. 40 | -------------------------------------------------------------------------------- /integration/browser/index.js: -------------------------------------------------------------------------------- 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 | }; 23 | -------------------------------------------------------------------------------- /integration/browser/index.test.js: -------------------------------------------------------------------------------- 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 | }); 35 | -------------------------------------------------------------------------------- /integration/browser/package.json: -------------------------------------------------------------------------------- 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 | } 20 | -------------------------------------------------------------------------------- /integration/browser/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "@playwright/test"; 2 | 3 | export default defineConfig({}); 4 | -------------------------------------------------------------------------------- /integration/bun/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /integration/bun/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replicate/replicate-javascript/f8ab2e8ead2997ac0f2e257652037483b7a3ff52/integration/bun/bun.lockb -------------------------------------------------------------------------------- /integration/bun/index.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "bun:test"; 2 | import main from "./index.js"; 3 | 4 | // Verify exported types. 5 | import type { 6 | Status, 7 | Visibility, 8 | WebhookEventType, 9 | ApiError, 10 | Collection, 11 | Hardware, 12 | Model, 13 | ModelVersion, 14 | Prediction, 15 | Training, 16 | Page, 17 | ServerSentEvent, 18 | } from "replicate"; 19 | 20 | test("main", async () => { 21 | const output = await main(); 22 | expect(output).toEqual("hello there, Brünnhilde Bun"); 23 | }); 24 | -------------------------------------------------------------------------------- /integration/bun/index.ts: -------------------------------------------------------------------------------- 1 | import Replicate from "replicate"; 2 | 3 | const replicate = new Replicate({ 4 | auth: process.env.REPLICATE_API_TOKEN, 5 | }); 6 | 7 | export default async function main() { 8 | const model = 9 | "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272"; 10 | const options = { 11 | input: { 12 | text: "Brünnhilde Bun", 13 | }, 14 | }; 15 | const output = []; 16 | 17 | for await (const { event, data } of replicate.stream(model, options)) { 18 | console.log({ event, data }); 19 | if (event === "output") { 20 | output.push(data); 21 | } 22 | } 23 | 24 | return output.join("").trim(); 25 | } 26 | -------------------------------------------------------------------------------- /integration/bun/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "replicate-app-bun", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Bun integration tests", 6 | "main": "index.js", 7 | "type": "module", 8 | "dependencies": { 9 | "replicate": "file:../../" 10 | }, 11 | "devDependencies": { 12 | "@types/bun": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /integration/bun/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "nodenext", 5 | "inlineSourceMap": true, 6 | "outDir": "./dist", 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "skipDefaultLibCheck": true, 13 | "skipLibCheck": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /integration/cloudflare-worker/.gitignore: -------------------------------------------------------------------------------- 1 | .wrangler 2 | node_modules 3 | .dev.vars 4 | 5 | -------------------------------------------------------------------------------- /integration/cloudflare-worker/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | audit=false 3 | fund=false 4 | -------------------------------------------------------------------------------- /integration/cloudflare-worker/index.js: -------------------------------------------------------------------------------- 1 | import Replicate from "replicate"; 2 | 3 | export default { 4 | async fetch(_request, env, _ctx) { 5 | const replicate = new Replicate({ auth: env.REPLICATE_API_TOKEN }); 6 | 7 | try { 8 | const controller = new AbortController(); 9 | const output = replicate.stream( 10 | "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", 11 | { 12 | input: { 13 | text: "Colin CloudFlare", 14 | }, 15 | signal: controller.signal, 16 | } 17 | ); 18 | const stream = new ReadableStream({ 19 | async start(controller) { 20 | for await (const event of output) { 21 | controller.enqueue(new TextEncoder().encode(`${event}`)); 22 | } 23 | controller.enqueue(new TextEncoder().encode("\n")); 24 | controller.close(); 25 | }, 26 | }); 27 | 28 | return new Response(stream); 29 | } catch (err) { 30 | return Response("", { status: 500 }); 31 | } 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /integration/cloudflare-worker/index.test.js: -------------------------------------------------------------------------------- 1 | // https://developers.cloudflare.com/workers/wrangler/api/#unstable_dev 2 | import { unstable_dev as dev } from "wrangler"; 3 | import { test, after, before, describe } from "node:test"; 4 | import assert from "node:assert"; 5 | 6 | describe("CloudFlare Worker", () => { 7 | /** @type {import("wrangler").UnstableDevWorker} */ 8 | let worker; 9 | 10 | before(async () => { 11 | worker = await dev("./index.js", { 12 | port: 3000, 13 | experimental: { disableExperimentalWarning: true }, 14 | }); 15 | }); 16 | 17 | after(async () => { 18 | if (!worker) { 19 | // If no worker the before hook failed to run and the process will hang. 20 | process.exit(1); 21 | } 22 | await worker.stop(); 23 | }); 24 | 25 | test("worker streams back a response", { timeout: 5000 }, async () => { 26 | const resp = await worker.fetch(); 27 | const text = await resp.text(); 28 | 29 | assert.ok(resp.ok, `expected status to be 2xx but got ${resp.status}`); 30 | assert( 31 | text.length > 0, 32 | "expected body to have content but got body.length of 0" 33 | ); 34 | assert( 35 | text.includes("Colin CloudFlare"), 36 | `expected body to include "Colin CloudFlare" but got ${JSON.stringify( 37 | text 38 | )}` 39 | ); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /integration/cloudflare-worker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "replicate-app-cloudflare", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "deploy": "wrangler deploy", 8 | "dev": "wrangler dev", 9 | "start": "wrangler dev", 10 | "test": "node --test index.test.js" 11 | }, 12 | "dependencies": { 13 | "replicate": "file:../../" 14 | }, 15 | "devDependencies": { 16 | "wrangler": "^3.32.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /integration/cloudflare-worker/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "cloudflare-worker" 2 | main = "index.js" 3 | compatibility_date = "2024-03-04" 4 | compatibility_flags = [ "nodejs_compat" ] 5 | -------------------------------------------------------------------------------- /integration/commonjs/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | audit=false 3 | fund=false 4 | -------------------------------------------------------------------------------- /integration/commonjs/index.js: -------------------------------------------------------------------------------- 1 | const Replicate = require("replicate"); 2 | 3 | const replicate = new Replicate({ 4 | auth: process.env.REPLICATE_API_TOKEN, 5 | }); 6 | 7 | module.exports = async function main() { 8 | const output = await replicate.run( 9 | "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", 10 | { 11 | input: { 12 | text: "Claire CommonJS", 13 | }, 14 | } 15 | ); 16 | return output.join("").trim(); 17 | }; 18 | -------------------------------------------------------------------------------- /integration/commonjs/index.test.js: -------------------------------------------------------------------------------- 1 | const { test } = require("node:test"); 2 | const assert = require("node:assert"); 3 | const main = require("./index"); 4 | 5 | test("main", async () => { 6 | const output = await main(); 7 | assert.equal(output, "hello there, Claire CommonJS"); 8 | }); 9 | -------------------------------------------------------------------------------- /integration/commonjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "replicate-app-commonjs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "CommonJS integration tests", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "node --test ./index.test.js" 9 | }, 10 | "dependencies": { 11 | "replicate": "file:../../" 12 | } 13 | } -------------------------------------------------------------------------------- /integration/deno/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "replicate": "npm:replicate" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /integration/deno/deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3", 3 | "packages": { 4 | "specifiers": { 5 | "jsr:@std/assert": "jsr:@std/assert@0.226.0", 6 | "jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.1", 7 | "npm:replicate": "npm:replicate@0.31.0" 8 | }, 9 | "jsr": { 10 | "@std/assert@0.226.0": { 11 | "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3", 12 | "dependencies": [ 13 | "jsr:@std/internal@^1.0.0" 14 | ] 15 | }, 16 | "@std/internal@1.0.1": { 17 | "integrity": "6f8c7544d06a11dd256c8d6ba54b11ed870aac6c5aeafff499892662c57673e6" 18 | } 19 | }, 20 | "npm": { 21 | "abort-controller@3.0.0": { 22 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 23 | "dependencies": { 24 | "event-target-shim": "event-target-shim@5.0.1" 25 | } 26 | }, 27 | "base64-js@1.5.1": { 28 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 29 | "dependencies": {} 30 | }, 31 | "buffer@6.0.3": { 32 | "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", 33 | "dependencies": { 34 | "base64-js": "base64-js@1.5.1", 35 | "ieee754": "ieee754@1.2.1" 36 | } 37 | }, 38 | "event-target-shim@5.0.1": { 39 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", 40 | "dependencies": {} 41 | }, 42 | "events@3.3.0": { 43 | "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", 44 | "dependencies": {} 45 | }, 46 | "ieee754@1.2.1": { 47 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 48 | "dependencies": {} 49 | }, 50 | "process@0.11.10": { 51 | "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", 52 | "dependencies": {} 53 | }, 54 | "readable-stream@4.5.2": { 55 | "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", 56 | "dependencies": { 57 | "abort-controller": "abort-controller@3.0.0", 58 | "buffer": "buffer@6.0.3", 59 | "events": "events@3.3.0", 60 | "process": "process@0.11.10", 61 | "string_decoder": "string_decoder@1.3.0" 62 | } 63 | }, 64 | "replicate@0.31.0": { 65 | "integrity": "sha512-BQl52LqndfY2sLQ384jyspaJI5ia301+IN1zOBbKZa2dB5EnayUxS0ynFueOdwo/4qRfQTR0GKJwpKFK/mb3zw==", 66 | "dependencies": { 67 | "readable-stream": "readable-stream@4.5.2" 68 | } 69 | }, 70 | "safe-buffer@5.2.1": { 71 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 72 | "dependencies": {} 73 | }, 74 | "string_decoder@1.3.0": { 75 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 76 | "dependencies": { 77 | "safe-buffer": "safe-buffer@5.2.1" 78 | } 79 | } 80 | } 81 | }, 82 | "remote": {}, 83 | "workspace": { 84 | "dependencies": [ 85 | "npm:replicate" 86 | ] 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /integration/deno/index.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "jsr:@std/assert"; 2 | import main from "./index.ts"; 3 | 4 | // Verify exported types. 5 | import type { 6 | Status, 7 | Visibility, 8 | WebhookEventType, 9 | ApiError, 10 | Collection, 11 | Hardware, 12 | Model, 13 | ModelVersion, 14 | Prediction, 15 | Training, 16 | Page, 17 | ServerSentEvent, 18 | } from "replicate"; 19 | 20 | Deno.test({ 21 | name: "main", 22 | async fn() { 23 | const output = await main(); 24 | assertEquals({ output }, { output: "hello there, Deno the dinosaur" }); 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /integration/deno/index.ts: -------------------------------------------------------------------------------- 1 | import Replicate from "replicate"; 2 | 3 | const replicate = new Replicate({ 4 | auth: Deno.env.get("REPLICATE_API_TOKEN"), 5 | }); 6 | 7 | export default async function main() { 8 | const output = (await replicate.run( 9 | "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", 10 | { 11 | input: { 12 | text: "Deno the dinosaur", 13 | }, 14 | } 15 | )) as string[]; 16 | return output.join("").trim(); 17 | } 18 | -------------------------------------------------------------------------------- /integration/deno/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /integration/esm/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | audit=false 3 | fund=false 4 | -------------------------------------------------------------------------------- /integration/esm/index.js: -------------------------------------------------------------------------------- 1 | import Replicate from "replicate"; 2 | 3 | const replicate = new Replicate({ 4 | auth: process.env.REPLICATE_API_TOKEN, 5 | }); 6 | 7 | export default async function main() { 8 | const output = await replicate.run( 9 | "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", 10 | { 11 | input: { 12 | text: "Evelyn ESM", 13 | }, 14 | } 15 | ); 16 | return Array.isArray(output) ? output.join("").trim() : String(output).trim(); 17 | } 18 | -------------------------------------------------------------------------------- /integration/esm/index.test.js: -------------------------------------------------------------------------------- 1 | import { test } from "node:test"; 2 | import assert from "node:assert"; 3 | import main from "./index.js"; 4 | 5 | test("main", async () => { 6 | const output = await main(); 7 | assert.equal(output, "hello there, Evelyn ESM"); 8 | }); 9 | -------------------------------------------------------------------------------- /integration/esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "replicate-app-esm", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "ESM (ECMAScript Modules) integration tests", 6 | "main": "index.js", 7 | "type": "module", 8 | "scripts": { 9 | "test": "node --test ./index.test.js" 10 | }, 11 | "dependencies": { 12 | "replicate": "file:../../" 13 | } 14 | } -------------------------------------------------------------------------------- /integration/next/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | audit=false 3 | fund=false 4 | -------------------------------------------------------------------------------- /integration/next/middleware.ts: -------------------------------------------------------------------------------- 1 | // NOTE: This file currently doesn't do anything other than 2 | // validate that `next build` works as expected. We can 3 | // extend it in future to support actual middleware tests. 4 | import { NextRequest } from "next/server"; 5 | import Replicate from "replicate"; 6 | 7 | // Limit the middleware to paths starting with `/api/` 8 | export const config = { 9 | matcher: "/api/:function*", 10 | }; 11 | 12 | const replicate = new Replicate(); 13 | 14 | export function middleware(request: NextRequest) { 15 | const output = replicate.run("foo/bar"); 16 | return Response.json({ output }, 200); 17 | } 18 | -------------------------------------------------------------------------------- /integration/next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "replicate-next", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next", 7 | "build": "rm -rf .next && next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "next": "^14.2.3", 12 | "replicate": "../../" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /integration/next/pages/index.js: -------------------------------------------------------------------------------- 1 | export default () => ( 2 |
3 |

Welcome to Next.js

4 |
5 | ); 6 | -------------------------------------------------------------------------------- /integration/typescript/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | audit=false 3 | fund=false 4 | -------------------------------------------------------------------------------- /integration/typescript/index.test.ts: -------------------------------------------------------------------------------- 1 | import { test } from "node:test"; 2 | import assert from "node:assert"; 3 | import main from "./index.js"; 4 | 5 | // Verify exported types. 6 | import type { 7 | Status, 8 | Visibility, 9 | WebhookEventType, 10 | ApiError, 11 | Collection, 12 | Hardware, 13 | Model, 14 | ModelVersion, 15 | Prediction, 16 | Training, 17 | Page, 18 | ServerSentEvent, 19 | } from "replicate"; 20 | 21 | test("main", async () => { 22 | const output = await main(); 23 | assert.equal(output, "hello there, Tracy TypeScript"); 24 | }); 25 | -------------------------------------------------------------------------------- /integration/typescript/index.ts: -------------------------------------------------------------------------------- 1 | import Replicate from "replicate"; 2 | 3 | const replicate = new Replicate({ 4 | auth: process.env.REPLICATE_API_TOKEN, 5 | }); 6 | 7 | export default async function main() { 8 | const output = (await replicate.run( 9 | "replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272", 10 | { 11 | input: { 12 | text: "Tracy TypeScript", 13 | }, 14 | } 15 | )) as string[]; 16 | return output.join("").trim(); 17 | } 18 | -------------------------------------------------------------------------------- /integration/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "replicate-app-typescript", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "TypeScript integration tests", 6 | "main": "index.js", 7 | "type": "module", 8 | "scripts": { 9 | "test": "tsc && node --test ./dist/index.test.js" 10 | }, 11 | "dependencies": { 12 | "@types/node": "^20.11.0", 13 | "replicate": "file:../../", 14 | "typescript": "^5.3.3" 15 | } 16 | } -------------------------------------------------------------------------------- /integration/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2018", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "nodenext", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line jsdoc/valid-types 2 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 3 | module.exports = { 4 | preset: "ts-jest", 5 | testEnvironment: "node", 6 | testPathIgnorePatterns: ["integration"], 7 | transform: { 8 | "^.+\\.ts?$": [ 9 | "ts-jest", 10 | { 11 | tsconfig: "tsconfig.json", 12 | }, 13 | ], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "checkJs": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "target": "ES2020", 7 | "resolveJsonModule": true, 8 | "strictNullChecks": true, 9 | "strictFunctionTypes": true 10 | }, 11 | "exclude": [ 12 | "node_modules", 13 | "**/node_modules/*" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /lib/accounts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the current account 3 | * 4 | * @param {object} [options] 5 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 6 | * @returns {Promise} Resolves with the current account 7 | */ 8 | async function getCurrentAccount({ signal } = {}) { 9 | const response = await this.request("/account", { 10 | method: "GET", 11 | signal, 12 | }); 13 | 14 | return response.json(); 15 | } 16 | 17 | module.exports = { 18 | current: getCurrentAccount, 19 | }; 20 | -------------------------------------------------------------------------------- /lib/collections.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Fetch a model collection 3 | * 4 | * @param {string} collection_slug - Required. The slug of the collection. See http://replicate.com/collections 5 | * @param {object} [options] 6 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 7 | * @returns {Promise} - Resolves with the collection data 8 | */ 9 | async function getCollection(collection_slug, { signal } = {}) { 10 | const response = await this.request(`/collections/${collection_slug}`, { 11 | method: "GET", 12 | signal, 13 | }); 14 | 15 | return response.json(); 16 | } 17 | 18 | /** 19 | * Fetch a list of model collections 20 | * 21 | * @param {object} [options] 22 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 23 | * @returns {Promise} - Resolves with the collections data 24 | */ 25 | async function listCollections({ signal } = {}) { 26 | const response = await this.request("/collections", { 27 | method: "GET", 28 | signal, 29 | }); 30 | 31 | return response.json(); 32 | } 33 | 34 | module.exports = { get: getCollection, list: listCollections }; 35 | -------------------------------------------------------------------------------- /lib/deployments.js: -------------------------------------------------------------------------------- 1 | const { transformFileInputs } = require("./util"); 2 | 3 | /** 4 | * Create a new prediction with a deployment 5 | * 6 | * @param {string} deployment_owner - Required. The username of the user or organization who owns the deployment 7 | * @param {string} deployment_name - Required. The name of the deployment 8 | * @param {object} options 9 | * @param {object} options.input - Required. An object with the model inputs 10 | * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output 11 | * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) 12 | * @param {boolean|integer} [options.wait] - Whether to wait until the prediction is completed before returning. If an integer is provided, it will wait for that many seconds. Defaults to false 13 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 14 | * @returns {Promise} Resolves with the created prediction data 15 | */ 16 | async function createPrediction(deployment_owner, deployment_name, options) { 17 | const { input, wait, signal, ...data } = options; 18 | 19 | if (data.webhook) { 20 | try { 21 | // eslint-disable-next-line no-new 22 | new URL(data.webhook); 23 | } catch (err) { 24 | throw new Error("Invalid webhook URL"); 25 | } 26 | } 27 | 28 | const headers = {}; 29 | if (wait) { 30 | if (typeof wait === "number") { 31 | const n = Math.max(1, Math.ceil(Number(wait)) || 1); 32 | headers["Prefer"] = `wait=${n}`; 33 | } else { 34 | headers["Prefer"] = "wait"; 35 | } 36 | } 37 | 38 | const response = await this.request( 39 | `/deployments/${deployment_owner}/${deployment_name}/predictions`, 40 | { 41 | method: "POST", 42 | headers, 43 | data: { 44 | ...data, 45 | input: await transformFileInputs( 46 | this, 47 | input, 48 | this.fileEncodingStrategy 49 | ), 50 | }, 51 | signal, 52 | } 53 | ); 54 | 55 | return response.json(); 56 | } 57 | 58 | /** 59 | * Get a deployment 60 | * 61 | * @param {string} deployment_owner - Required. The username of the user or organization who owns the deployment 62 | * @param {string} deployment_name - Required. The name of the deployment 63 | * @param {object] [options] 64 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 65 | * @returns {Promise} Resolves with the deployment data 66 | */ 67 | async function getDeployment( 68 | deployment_owner, 69 | deployment_name, 70 | { signal } = {} 71 | ) { 72 | const response = await this.request( 73 | `/deployments/${deployment_owner}/${deployment_name}`, 74 | { 75 | method: "GET", 76 | signal, 77 | } 78 | ); 79 | 80 | return response.json(); 81 | } 82 | 83 | /** 84 | * @typedef {Object} DeploymentCreateRequest - Request body for `deployments.create` 85 | * @property {string} name - the name of the deployment 86 | * @property {string} model - the full name of the model that you want to deploy e.g. stability-ai/sdxl 87 | * @property {string} version - the 64-character string ID of the model version that you want to deploy 88 | * @property {string} hardware - the SKU for the hardware used to run the model, via `replicate.hardware.list()` 89 | * @property {number} min_instances - the minimum number of instances for scaling 90 | * @property {number} max_instances - the maximum number of instances for scaling 91 | */ 92 | 93 | /** 94 | * Create a deployment 95 | * 96 | * @param {DeploymentCreateRequest} deployment_config - Required. The deployment config. 97 | * @param {object} [options] 98 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 99 | * @returns {Promise} Resolves with the deployment data 100 | */ 101 | async function createDeployment(deployment_config, { signal } = {}) { 102 | const response = await this.request("/deployments", { 103 | method: "POST", 104 | data: deployment_config, 105 | signal, 106 | }); 107 | 108 | return response.json(); 109 | } 110 | 111 | /** 112 | * @typedef {Object} DeploymentUpdateRequest - Request body for `deployments.update` 113 | * @property {string} version - the 64-character string ID of the model version that you want to deploy 114 | * @property {string} hardware - the SKU for the hardware used to run the model, via `replicate.hardware.list()` 115 | * @property {number} min_instances - the minimum number of instances for scaling 116 | * @property {number} max_instances - the maximum number of instances for scaling 117 | */ 118 | 119 | /** 120 | * Update an existing deployment 121 | * 122 | * @param {string} deployment_owner - Required. The username of the user or organization who owns the deployment 123 | * @param {string} deployment_name - Required. The name of the deployment 124 | * @param {DeploymentUpdateRequest} deployment_config - Required. The deployment changes. 125 | * @param {object} [options] 126 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 127 | * @returns {Promise} Resolves with the deployment data 128 | */ 129 | async function updateDeployment( 130 | deployment_owner, 131 | deployment_name, 132 | deployment_config, 133 | { signal } = {} 134 | ) { 135 | const response = await this.request( 136 | `/deployments/${deployment_owner}/${deployment_name}`, 137 | { 138 | method: "PATCH", 139 | data: deployment_config, 140 | signal, 141 | } 142 | ); 143 | 144 | return response.json(); 145 | } 146 | 147 | /** 148 | * Delete a deployment 149 | * 150 | * @param {string} deployment_owner - Required. The username of the user or organization who owns the deployment 151 | * @param {string} deployment_name - Required. The name of the deployment 152 | * @param {object} [options] 153 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 154 | * @returns {Promise} Resolves with true if the deployment was deleted 155 | */ 156 | async function deleteDeployment( 157 | deployment_owner, 158 | deployment_name, 159 | { signal } = {} 160 | ) { 161 | const response = await this.request( 162 | `/deployments/${deployment_owner}/${deployment_name}`, 163 | { 164 | method: "DELETE", 165 | signal, 166 | } 167 | ); 168 | 169 | return response.status === 204; 170 | } 171 | 172 | /** 173 | * List all deployments 174 | * 175 | * @param {object} [options] 176 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 177 | * @returns {Promise} - Resolves with a page of deployments 178 | */ 179 | async function listDeployments({ signal } = {}) { 180 | const response = await this.request("/deployments", { 181 | method: "GET", 182 | signal, 183 | }); 184 | 185 | return response.json(); 186 | } 187 | 188 | module.exports = { 189 | predictions: { 190 | create: createPrediction, 191 | }, 192 | get: getDeployment, 193 | create: createDeployment, 194 | update: updateDeployment, 195 | list: listDeployments, 196 | delete: deleteDeployment, 197 | }; 198 | -------------------------------------------------------------------------------- /lib/error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A representation of an API error. 3 | */ 4 | class ApiError extends Error { 5 | /** 6 | * Creates a representation of an API error. 7 | * 8 | * @param {string} message - Error message 9 | * @param {Request} request - HTTP request 10 | * @param {Response} response - HTTP response 11 | * @returns {ApiError} - An instance of ApiError 12 | */ 13 | constructor(message, request, response) { 14 | super(message); 15 | this.name = "ApiError"; 16 | this.request = request; 17 | this.response = response; 18 | } 19 | } 20 | 21 | module.exports = ApiError; 22 | -------------------------------------------------------------------------------- /lib/files.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create a file 3 | * 4 | * @param {object} file - Required. The file object. 5 | * @param {object} metadata - Optional. User-provided metadata associated with the file. 6 | * @param {object} [options] 7 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 8 | * @returns {Promise} - Resolves with the file data 9 | */ 10 | async function createFile(file, metadata = {}, { signal } = {}) { 11 | const form = new FormData(); 12 | 13 | let filename; 14 | let blob; 15 | if (file instanceof Blob) { 16 | filename = file.name || `blob_${Date.now()}`; 17 | blob = file; 18 | } else if (Buffer.isBuffer(file)) { 19 | filename = `buffer_${Date.now()}`; 20 | const bytes = new Uint8Array(file); 21 | blob = new Blob([bytes], { 22 | type: "application/octet-stream", 23 | name: filename, 24 | }); 25 | } else { 26 | throw new Error("Invalid file argument, must be a Blob, File or Buffer"); 27 | } 28 | 29 | form.append("content", blob, filename); 30 | form.append( 31 | "metadata", 32 | new Blob([JSON.stringify(metadata)], { type: "application/json" }) 33 | ); 34 | 35 | const response = await this.request("/files", { 36 | method: "POST", 37 | data: form, 38 | headers: { 39 | "Content-Type": "multipart/form-data", 40 | }, 41 | signal, 42 | }); 43 | 44 | return response.json(); 45 | } 46 | 47 | /** 48 | * List all files 49 | * 50 | * @param {object} [options] 51 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 52 | * @returns {Promise} - Resolves with the files data 53 | */ 54 | async function listFiles({ signal } = {}) { 55 | const response = await this.request("/files", { 56 | method: "GET", 57 | signal, 58 | }); 59 | 60 | return response.json(); 61 | } 62 | 63 | /** 64 | * Get a file 65 | * 66 | * @param {string} file_id - Required. The ID of the file. 67 | * @param {object} [options] 68 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 69 | * @returns {Promise} - Resolves with the file data 70 | */ 71 | async function getFile(file_id, { signal } = {}) { 72 | const response = await this.request(`/files/${file_id}`, { 73 | method: "GET", 74 | signal, 75 | }); 76 | 77 | return response.json(); 78 | } 79 | 80 | /** 81 | * Delete a file 82 | * 83 | * @param {string} file_id - Required. The ID of the file. 84 | * @param {object} [options] 85 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 86 | * @returns {Promise} - Resolves with true if the file was deleted 87 | */ 88 | async function deleteFile(file_id, { signal } = {}) { 89 | const response = await this.request(`/files/${file_id}`, { 90 | method: "DELETE", 91 | signal, 92 | }); 93 | 94 | return response.status === 204; 95 | } 96 | 97 | module.exports = { 98 | create: createFile, 99 | list: listFiles, 100 | get: getFile, 101 | delete: deleteFile, 102 | }; 103 | -------------------------------------------------------------------------------- /lib/hardware.js: -------------------------------------------------------------------------------- 1 | /** 2 | * List hardware 3 | * 4 | * @param {object} [options] 5 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 6 | * @returns {Promise} Resolves with the array of hardware 7 | */ 8 | async function listHardware({ signal } = {}) { 9 | const response = await this.request("/hardware", { 10 | method: "GET", 11 | signal, 12 | }); 13 | 14 | return response.json(); 15 | } 16 | 17 | module.exports = { 18 | list: listHardware, 19 | }; 20 | -------------------------------------------------------------------------------- /lib/identifier.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A reference to a model version in the format `owner/name` or `owner/name:version`. 3 | */ 4 | class ModelVersionIdentifier { 5 | /* 6 | * @param {string} Required. The model owner. 7 | * @param {string} Required. The model name. 8 | * @param {string} The model version. 9 | */ 10 | constructor(owner, name, version = null) { 11 | this.owner = owner; 12 | this.name = name; 13 | this.version = version; 14 | } 15 | 16 | /* 17 | * Parse a reference to a model version 18 | * 19 | * @param {string} 20 | * @returns {ModelVersionIdentifier} 21 | * @throws {Error} If the reference is invalid. 22 | */ 23 | static parse(ref) { 24 | const match = ref.match( 25 | /^(?[^/]+)\/(?[^/:]+)(:(?.+))?$/ 26 | ); 27 | if (!match) { 28 | throw new Error( 29 | `Invalid reference to model version: ${ref}. Expected format: owner/name or owner/name:version` 30 | ); 31 | } 32 | 33 | const { owner, name, version } = match.groups; 34 | 35 | return new ModelVersionIdentifier(owner, name, version); 36 | } 37 | } 38 | 39 | module.exports = ModelVersionIdentifier; 40 | -------------------------------------------------------------------------------- /lib/models.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get information about a model 3 | * 4 | * @param {string} model_owner - Required. The name of the user or organization that owns the model 5 | * @param {string} model_name - Required. The name of the model 6 | * @param {object} [options] 7 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 8 | * @returns {Promise} Resolves with the model data 9 | */ 10 | async function getModel(model_owner, model_name, { signal } = {}) { 11 | const response = await this.request(`/models/${model_owner}/${model_name}`, { 12 | method: "GET", 13 | signal, 14 | }); 15 | 16 | return response.json(); 17 | } 18 | 19 | /** 20 | * List model versions 21 | * 22 | * @param {string} model_owner - Required. The name of the user or organization that owns the model 23 | * @param {string} model_name - Required. The name of the model 24 | * @param {object} [options] 25 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 26 | * @returns {Promise} Resolves with the list of model versions 27 | */ 28 | async function listModelVersions(model_owner, model_name, { signal } = {}) { 29 | const response = await this.request( 30 | `/models/${model_owner}/${model_name}/versions`, 31 | { 32 | method: "GET", 33 | signal, 34 | } 35 | ); 36 | 37 | return response.json(); 38 | } 39 | 40 | /** 41 | * Get a specific model version 42 | * 43 | * @param {string} model_owner - Required. The name of the user or organization that owns the model 44 | * @param {string} model_name - Required. The name of the model 45 | * @param {string} version_id - Required. The model version 46 | * @param {object} [options] 47 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 48 | * @returns {Promise} Resolves with the model version data 49 | */ 50 | async function getModelVersion( 51 | model_owner, 52 | model_name, 53 | version_id, 54 | { signal } = {} 55 | ) { 56 | const response = await this.request( 57 | `/models/${model_owner}/${model_name}/versions/${version_id}`, 58 | { 59 | method: "GET", 60 | signal, 61 | } 62 | ); 63 | 64 | return response.json(); 65 | } 66 | 67 | /** 68 | * List all public models 69 | * 70 | * @param {object} [options] 71 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 72 | * @returns {Promise} Resolves with the model version data 73 | */ 74 | async function listModels({ signal } = {}) { 75 | const response = await this.request("/models", { 76 | method: "GET", 77 | signal, 78 | }); 79 | 80 | return response.json(); 81 | } 82 | 83 | /** 84 | * Create a new model 85 | * 86 | * @param {string} model_owner - Required. The name of the user or organization that will own the model. This must be the same as the user or organization that is making the API request. In other words, the API token used in the request must belong to this user or organization. 87 | * @param {string} model_name - Required. The name of the model. This must be unique among all models owned by the user or organization. 88 | * @param {object} options 89 | * @param {("public"|"private")} options.visibility - Required. Whether the model should be public or private. A public model can be viewed and run by anyone, whereas a private model can be viewed and run only by the user or organization members that own the model. 90 | * @param {string} options.hardware - Required. The SKU for the hardware used to run the model. Possible values can be found by calling `Replicate.hardware.list()`. 91 | * @param {string} options.description - A description of the model. 92 | * @param {string} options.github_url - A URL for the model's source code on GitHub. 93 | * @param {string} options.paper_url - A URL for the model's paper. 94 | * @param {string} options.license_url - A URL for the model's license. 95 | * @param {string} options.cover_image_url - A URL for the model's cover image. This should be an image file. 96 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 97 | * @returns {Promise} Resolves with the model version data 98 | */ 99 | async function createModel(model_owner, model_name, options) { 100 | const { signal, ...rest } = options; 101 | const data = { owner: model_owner, name: model_name, ...rest }; 102 | 103 | const response = await this.request("/models", { 104 | method: "POST", 105 | data, 106 | signal, 107 | }); 108 | 109 | return response.json(); 110 | } 111 | 112 | /** 113 | * Search for public models 114 | * 115 | * @param {string} query - The search query 116 | * @param {object} [options] 117 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 118 | * @returns {Promise} Resolves with a page of models matching the search query 119 | */ 120 | async function search(query, { signal } = {}) { 121 | const response = await this.request("/models", { 122 | method: "QUERY", 123 | headers: { 124 | "Content-Type": "text/plain", 125 | }, 126 | data: query, 127 | signal, 128 | }); 129 | 130 | return response.json(); 131 | } 132 | 133 | module.exports = { 134 | get: getModel, 135 | list: listModels, 136 | create: createModel, 137 | versions: { list: listModelVersions, get: getModelVersion }, 138 | search, 139 | }; 140 | -------------------------------------------------------------------------------- /lib/predictions.js: -------------------------------------------------------------------------------- 1 | const { transformFileInputs } = require("./util"); 2 | 3 | /** 4 | * Create a new prediction 5 | * 6 | * @param {object} options 7 | * @param {string} options.model - The model. 8 | * @param {string} options.version - The model version. 9 | * @param {object} options.input - Required. An object with the model inputs 10 | * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the prediction has new output 11 | * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) 12 | * @param {boolean|integer} [options.wait] - Whether to wait until the prediction is completed before returning. If an integer is provided, it will wait for that many seconds. Defaults to false 13 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 14 | * @returns {Promise} Resolves with the created prediction 15 | */ 16 | async function createPrediction(options) { 17 | const { model, version, input, wait, signal, ...data } = options; 18 | 19 | if (data.webhook) { 20 | try { 21 | // eslint-disable-next-line no-new 22 | new URL(data.webhook); 23 | } catch (err) { 24 | throw new Error("Invalid webhook URL"); 25 | } 26 | } 27 | 28 | const headers = {}; 29 | if (wait) { 30 | if (typeof wait === "number") { 31 | const n = Math.max(1, Math.ceil(Number(wait)) || 1); 32 | headers["Prefer"] = `wait=${n}`; 33 | } else { 34 | headers["Prefer"] = "wait"; 35 | } 36 | } 37 | 38 | let response; 39 | if (version) { 40 | response = await this.request("/predictions", { 41 | method: "POST", 42 | headers, 43 | data: { 44 | ...data, 45 | input: await transformFileInputs( 46 | this, 47 | input, 48 | this.fileEncodingStrategy 49 | ), 50 | version, 51 | }, 52 | signal, 53 | }); 54 | } else if (model) { 55 | response = await this.request(`/models/${model}/predictions`, { 56 | method: "POST", 57 | headers, 58 | data: { 59 | ...data, 60 | input: await transformFileInputs( 61 | this, 62 | input, 63 | this.fileEncodingStrategy 64 | ), 65 | }, 66 | signal, 67 | }); 68 | } else { 69 | throw new Error("Either model or version must be specified"); 70 | } 71 | 72 | return response.json(); 73 | } 74 | 75 | /** 76 | * Fetch a prediction by ID 77 | * 78 | * @param {number} prediction_id - Required. The prediction ID 79 | * @param {object} [options] 80 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 81 | * @returns {Promise} Resolves with the prediction data 82 | */ 83 | async function getPrediction(prediction_id, { signal } = {}) { 84 | const response = await this.request(`/predictions/${prediction_id}`, { 85 | method: "GET", 86 | signal, 87 | }); 88 | 89 | return response.json(); 90 | } 91 | 92 | /** 93 | * Cancel a prediction by ID 94 | * 95 | * @param {string} prediction_id - Required. The training ID 96 | * @param {object} [options] 97 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 98 | * @returns {Promise} Resolves with the data for the training 99 | */ 100 | async function cancelPrediction(prediction_id, { signal } = {}) { 101 | const response = await this.request(`/predictions/${prediction_id}/cancel`, { 102 | method: "POST", 103 | signal, 104 | }); 105 | 106 | return response.json(); 107 | } 108 | 109 | /** 110 | * List all predictions 111 | * 112 | * @param {object} [options] 113 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 114 | * @returns {Promise} - Resolves with a page of predictions 115 | */ 116 | async function listPredictions({ signal } = {}) { 117 | const response = await this.request("/predictions", { 118 | method: "GET", 119 | signal, 120 | }); 121 | 122 | return response.json(); 123 | } 124 | 125 | module.exports = { 126 | create: createPrediction, 127 | get: getPrediction, 128 | cancel: cancelPrediction, 129 | list: listPredictions, 130 | }; 131 | -------------------------------------------------------------------------------- /lib/stream.js: -------------------------------------------------------------------------------- 1 | // Attempt to use readable-stream if available, attempt to use the built-in stream module. 2 | 3 | const ApiError = require("./error"); 4 | const { streamAsyncIterator } = require("./util"); 5 | const { 6 | EventSourceParserStream, 7 | } = require("../vendor/eventsource-parser/stream"); 8 | const { TextDecoderStream } = 9 | typeof globalThis.TextDecoderStream === "undefined" 10 | ? require("../vendor/streams-text-encoding/text-decoder-stream") 11 | : globalThis; 12 | 13 | /** 14 | * A server-sent event. 15 | */ 16 | class ServerSentEvent { 17 | /** 18 | * Create a new server-sent event. 19 | * 20 | * @param {string} event The event name. 21 | * @param {string} data The event data. 22 | * @param {string} id The event ID. 23 | * @param {number} retry The retry time. 24 | */ 25 | constructor(event, data, id, retry) { 26 | this.event = event; 27 | this.data = data; 28 | this.id = id; 29 | this.retry = retry; 30 | } 31 | 32 | /** 33 | * Convert the event to a string. 34 | */ 35 | toString() { 36 | if (this.event === "output") { 37 | return this.data; 38 | } 39 | 40 | return ""; 41 | } 42 | } 43 | 44 | /** 45 | * Create a new stream of server-sent events. 46 | * 47 | * @param {object} config 48 | * @param {string} config.url The URL to connect to. 49 | * @param {typeof fetch} [config.fetch] The URL to connect to. 50 | * @param {object} [config.options] The EventSource options. 51 | * @param {boolean} [config.options.useFileOutput] Whether to use the file output stream. 52 | * @returns {ReadableStream & AsyncIterable} 53 | */ 54 | function createReadableStream({ url, fetch, options = {} }) { 55 | const { useFileOutput = true, headers = {}, ...initOptions } = options; 56 | 57 | return new ReadableStream({ 58 | async start(controller) { 59 | const init = { 60 | ...initOptions, 61 | headers: { 62 | ...headers, 63 | Accept: "text/event-stream", 64 | }, 65 | }; 66 | const response = await fetch(url, init); 67 | 68 | if (!response.ok) { 69 | const text = await response.text(); 70 | const request = new Request(url, init); 71 | controller.error( 72 | new ApiError( 73 | `Request to ${url} failed with status ${response.status}: ${text}`, 74 | request, 75 | response 76 | ) 77 | ); 78 | } 79 | 80 | const stream = response.body 81 | .pipeThrough(new TextDecoderStream()) 82 | .pipeThrough(new EventSourceParserStream()); 83 | 84 | for await (const event of streamAsyncIterator(stream)) { 85 | if (event.event === "error") { 86 | controller.error(new Error(event.data)); 87 | break; 88 | } 89 | 90 | let data = event.data; 91 | if ( 92 | useFileOutput && 93 | typeof data === "string" && 94 | (data.startsWith("https:") || data.startsWith("data:")) 95 | ) { 96 | data = createFileOutput({ data, fetch }); 97 | } 98 | controller.enqueue(new ServerSentEvent(event.event, data, event.id)); 99 | 100 | if (event.event === "done") { 101 | break; 102 | } 103 | } 104 | 105 | controller.close(); 106 | }, 107 | }); 108 | } 109 | 110 | /** 111 | * Create a new readable stream for an output file 112 | * created by running a Replicate model. 113 | * 114 | * @param {object} config 115 | * @param {string} config.url The URL to connect to. 116 | * @param {typeof fetch} [config.fetch] The fetch function. 117 | * @returns {ReadableStream} 118 | */ 119 | function createFileOutput({ url, fetch }) { 120 | let type = "application/octet-stream"; 121 | 122 | class FileOutput extends ReadableStream { 123 | async blob() { 124 | const chunks = []; 125 | for await (const chunk of this) { 126 | chunks.push(chunk); 127 | } 128 | return new Blob(chunks, { type }); 129 | } 130 | 131 | url() { 132 | return new URL(url); 133 | } 134 | 135 | toString() { 136 | return url; 137 | } 138 | } 139 | 140 | return new FileOutput({ 141 | async start(controller) { 142 | const response = await fetch(url); 143 | 144 | if (!response.ok) { 145 | const text = await response.text(); 146 | const request = new Request(url, init); 147 | controller.error( 148 | new ApiError( 149 | `Request to ${url} failed with status ${response.status}: ${text}`, 150 | request, 151 | response 152 | ) 153 | ); 154 | } 155 | 156 | if (response.headers.get("Content-Type")) { 157 | type = response.headers.get("Content-Type"); 158 | } 159 | 160 | try { 161 | for await (const chunk of streamAsyncIterator(response.body)) { 162 | controller.enqueue(chunk); 163 | } 164 | controller.close(); 165 | } catch (err) { 166 | controller.error(err); 167 | } 168 | }, 169 | }); 170 | } 171 | 172 | module.exports = { 173 | createFileOutput, 174 | createReadableStream, 175 | ServerSentEvent, 176 | }; 177 | -------------------------------------------------------------------------------- /lib/trainings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create a new training 3 | * 4 | * @param {string} model_owner - Required. The username of the user or organization who owns the model 5 | * @param {string} model_name - Required. The name of the model 6 | * @param {string} version_id - Required. The version ID 7 | * @param {object} options 8 | * @param {string} options.destination - Required. The destination for the trained version in the form "{username}/{model_name}" 9 | * @param {object} options.input - Required. An object with the model inputs 10 | * @param {string} [options.webhook] - An HTTPS URL for receiving a webhook when the training updates 11 | * @param {string[]} [options.webhook_events_filter] - You can change which events trigger webhook requests by specifying webhook events (`start`|`output`|`logs`|`completed`) 12 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 13 | * @returns {Promise} Resolves with the data for the created training 14 | */ 15 | async function createTraining(model_owner, model_name, version_id, options) { 16 | const { signal, ...data } = options; 17 | 18 | if (data.webhook) { 19 | try { 20 | // eslint-disable-next-line no-new 21 | new URL(data.webhook); 22 | } catch (err) { 23 | throw new Error("Invalid webhook URL"); 24 | } 25 | } 26 | 27 | const response = await this.request( 28 | `/models/${model_owner}/${model_name}/versions/${version_id}/trainings`, 29 | { 30 | method: "POST", 31 | data, 32 | signal, 33 | } 34 | ); 35 | 36 | return response.json(); 37 | } 38 | 39 | /** 40 | * Fetch a training by ID 41 | * 42 | * @param {string} training_id - Required. The training ID 43 | * @param {object} [options] 44 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 45 | * @returns {Promise} Resolves with the data for the training 46 | */ 47 | async function getTraining(training_id, { signal } = {}) { 48 | const response = await this.request(`/trainings/${training_id}`, { 49 | method: "GET", 50 | signal, 51 | }); 52 | 53 | return response.json(); 54 | } 55 | 56 | /** 57 | * Cancel a training by ID 58 | * 59 | * @param {string} training_id - Required. The training ID 60 | * @param {object} [options] 61 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 62 | * @returns {Promise} Resolves with the data for the training 63 | */ 64 | async function cancelTraining(training_id, { signal } = {}) { 65 | const response = await this.request(`/trainings/${training_id}/cancel`, { 66 | method: "POST", 67 | signal, 68 | }); 69 | 70 | return response.json(); 71 | } 72 | 73 | /** 74 | * List all trainings 75 | * 76 | * @param {object} [options] 77 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 78 | * @returns {Promise} - Resolves with a page of trainings 79 | */ 80 | async function listTrainings({ signal } = {}) { 81 | const response = await this.request("/trainings", { 82 | method: "GET", 83 | signal, 84 | }); 85 | 86 | return response.json(); 87 | } 88 | 89 | module.exports = { 90 | create: createTraining, 91 | get: getTraining, 92 | cancel: cancelTraining, 93 | list: listTrainings, 94 | }; 95 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | const ApiError = require("./error"); 2 | const { create: createFile } = require("./files"); 3 | 4 | /** 5 | * @see {@link validateWebhook} 6 | * @overload 7 | * @param {object} requestData - The request data 8 | * @param {string} requestData.id - The webhook ID header from the incoming request. 9 | * @param {string} requestData.timestamp - The webhook timestamp header from the incoming request. 10 | * @param {string} requestData.body - The raw body of the incoming webhook request. 11 | * @param {string} requestData.secret - The webhook secret, obtained from `replicate.webhooks.defaul.secret` method. 12 | * @param {string} requestData.signature - The webhook signature header from the incoming request, comprising one or more space-delimited signatures. 13 | * @param {Crypto} [crypto] - An optional `Crypto` implementation that conforms to the [browser Crypto interface](https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto) 14 | */ 15 | 16 | /** 17 | * @see {@link validateWebhook} 18 | * @overload 19 | * @param {object} requestData - The request object 20 | * @param {object} requestData.headers - The request headers 21 | * @param {string} requestData.headers["webhook-id"] - The webhook ID header from the incoming request 22 | * @param {string} requestData.headers["webhook-timestamp"] - The webhook timestamp header from the incoming request 23 | * @param {string} requestData.headers["webhook-signature"] - The webhook signature header from the incoming request, comprising one or more space-delimited signatures 24 | * @param {string} requestData.body - The raw body of the incoming webhook request 25 | * @param {string} secret - The webhook secret, obtained from `replicate.webhooks.defaul.secret` method 26 | * @param {Crypto} [crypto] - An optional `Crypto` implementation that conforms to the [browser Crypto interface](https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto) 27 | */ 28 | 29 | /** 30 | * Validate a webhook signature 31 | * 32 | * @returns {Promise} - True if the signature is valid 33 | * @throws {Error} - If the request is missing required headers, body, or secret 34 | */ 35 | async function validateWebhook(requestData, secretOrCrypto, customCrypto) { 36 | let id; 37 | let body; 38 | let timestamp; 39 | let signature; 40 | let secret; 41 | let crypto = globalThis.crypto; 42 | 43 | if (requestData && requestData.headers && requestData.body) { 44 | if (typeof requestData.headers.get === "function") { 45 | // Headers object (e.g. Fetch API Headers) 46 | id = requestData.headers.get("webhook-id"); 47 | timestamp = requestData.headers.get("webhook-timestamp"); 48 | signature = requestData.headers.get("webhook-signature"); 49 | } else { 50 | // Plain object with header key-value pairs 51 | id = requestData.headers["webhook-id"]; 52 | timestamp = requestData.headers["webhook-timestamp"]; 53 | signature = requestData.headers["webhook-signature"]; 54 | } 55 | body = requestData.body; 56 | if (typeof secretOrCrypto !== "string") { 57 | throw new Error( 58 | "Unexpected value for secret passed to validateWebhook, expected a string" 59 | ); 60 | } 61 | 62 | secret = secretOrCrypto; 63 | if (customCrypto) { 64 | crypto = customCrypto; 65 | } 66 | } else { 67 | id = requestData.id; 68 | body = requestData.body; 69 | timestamp = requestData.timestamp; 70 | signature = requestData.signature; 71 | secret = requestData.secret; 72 | if (secretOrCrypto) { 73 | crypto = secretOrCrypto; 74 | } 75 | } 76 | 77 | if (body instanceof ReadableStream || body.readable) { 78 | try { 79 | body = await new Response(body).text(); 80 | } catch (err) { 81 | throw new Error(`Error reading body: ${err.message}`); 82 | } 83 | } else if (isTypedArray(body)) { 84 | body = await new Blob([body]).text(); 85 | } else if (typeof body === "object") { 86 | body = JSON.stringify(body); 87 | } else if (typeof body !== "string") { 88 | throw new Error("Invalid body type"); 89 | } 90 | 91 | if (!id || !timestamp || !signature) { 92 | throw new Error("Missing required webhook headers"); 93 | } 94 | 95 | if (!body) { 96 | throw new Error("Missing required body"); 97 | } 98 | 99 | if (!secret) { 100 | throw new Error("Missing required secret"); 101 | } 102 | 103 | if (!crypto) { 104 | throw new Error( 105 | 'Missing `crypto` implementation. If using Node 18 pass in require("node:crypto").webcrypto' 106 | ); 107 | } 108 | 109 | const signedContent = `${id}.${timestamp}.${body}`; 110 | 111 | const computedSignature = await createHMACSHA256( 112 | secret.split("_").pop(), 113 | signedContent, 114 | crypto 115 | ); 116 | 117 | const expectedSignatures = signature 118 | .split(" ") 119 | .map((sig) => sig.split(",")[1]); 120 | 121 | return expectedSignatures.some( 122 | (expectedSignature) => expectedSignature === computedSignature 123 | ); 124 | } 125 | 126 | /** 127 | * @param {string} secret - base64 encoded string 128 | * @param {string} data - text body of request 129 | * @param {Crypto} crypto - an implementation of the web Crypto api 130 | */ 131 | async function createHMACSHA256(secret, data, crypto) { 132 | const encoder = new TextEncoder(); 133 | const key = await crypto.subtle.importKey( 134 | "raw", 135 | base64ToBytes(secret), 136 | { name: "HMAC", hash: "SHA-256" }, 137 | false, 138 | ["sign"] 139 | ); 140 | 141 | const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(data)); 142 | return bytesToBase64(signature); 143 | } 144 | 145 | /** 146 | * Convert a base64 encoded string into bytes. 147 | * 148 | * @param {string} the base64 encoded string 149 | * @return {Uint8Array} 150 | * 151 | * Two functions for encoding/decoding base64 strings using web standards. Not 152 | * intended to be used to encode/decode arbitrary string data. 153 | * See: https://developer.mozilla.org/en-US/docs/Glossary/Base64#javascript_support 154 | * See: https://stackoverflow.com/a/31621532 155 | * 156 | * Performance might take a hit because of the conversion to string and then to binary, 157 | * if this is the case we might want to look at an alternative solution. 158 | * See: https://jsben.ch/wnaZC 159 | */ 160 | function base64ToBytes(base64) { 161 | return Uint8Array.from(atob(base64), (m) => m.codePointAt(0)); 162 | } 163 | 164 | /** 165 | * Convert a base64 encoded string into bytes. 166 | * 167 | * See {@link base64ToBytes} for caveats. 168 | * 169 | * @param {Uint8Array | ArrayBuffer} the base64 encoded string 170 | * @return {string} 171 | */ 172 | function bytesToBase64(bytes) { 173 | return btoa(String.fromCharCode.apply(null, new Uint8Array(bytes))); 174 | } 175 | 176 | /** 177 | * Automatically retry a request if it fails with an appropriate status code. 178 | * 179 | * A GET request is retried if it fails with a 429 or 5xx status code. 180 | * A non-GET request is retried only if it fails with a 429 status code. 181 | * 182 | * If the response sets a Retry-After header, 183 | * the request is retried after the number of seconds specified in the header. 184 | * Otherwise, the request is retried after the specified interval, 185 | * with exponential backoff and jitter. 186 | * 187 | * @param {Function} request - A function that returns a Promise that resolves with a Response object 188 | * @param {object} options 189 | * @param {Function} [options.shouldRetry] - A function that returns true if the request should be retried 190 | * @param {number} [options.maxRetries] - Maximum number of retries. Defaults to 5 191 | * @param {number} [options.interval] - Interval between retries in milliseconds. Defaults to 500 192 | * @returns {Promise} - Resolves with the response object 193 | * @throws {ApiError} If the request failed 194 | */ 195 | async function withAutomaticRetries(request, options = {}) { 196 | const shouldRetry = options.shouldRetry || (() => false); 197 | const maxRetries = options.maxRetries || 5; 198 | const interval = options.interval || 500; 199 | const jitter = options.jitter || 100; 200 | 201 | // eslint-disable-next-line no-promise-executor-return 202 | const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); 203 | 204 | let attempts = 0; 205 | do { 206 | let delay = interval * 2 ** attempts + Math.random() * jitter; 207 | 208 | /* eslint-disable no-await-in-loop */ 209 | try { 210 | const response = await request(); 211 | if (response.ok || !shouldRetry(response)) { 212 | return response; 213 | } 214 | } catch (error) { 215 | if (error instanceof ApiError) { 216 | const retryAfter = error.response.headers.get("Retry-After"); 217 | if (retryAfter) { 218 | if (!Number.isInteger(retryAfter)) { 219 | // Retry-After is a date 220 | const date = new Date(retryAfter); 221 | if (!Number.isNaN(date.getTime())) { 222 | delay = date.getTime() - new Date().getTime(); 223 | } 224 | } else { 225 | // Retry-After is a number of seconds 226 | delay = retryAfter * 1000; 227 | } 228 | } 229 | } 230 | } 231 | 232 | if (Number.isInteger(maxRetries) && maxRetries > 0) { 233 | if (Number.isInteger(delay) && delay > 0) { 234 | await sleep(interval * 2 ** (options.maxRetries - maxRetries)); 235 | } 236 | attempts += 1; 237 | } 238 | } while (attempts < maxRetries); 239 | 240 | return request(); 241 | } 242 | 243 | /** 244 | * Walks the inputs and, for any File or Blob, tries to upload it to Replicate 245 | * and replaces the input with the URL of the uploaded file. 246 | * 247 | * @param {Replicate} client - The client used to upload the file 248 | * @param {object} inputs - The inputs to transform 249 | * @param {"default" | "upload" | "data-uri"} strategy - Whether to upload files to Replicate, encode as dataURIs or try both. 250 | * @returns {Promise} - The transformed inputs 251 | * @throws {ApiError} If the request to upload the file fails 252 | */ 253 | async function transformFileInputs(client, inputs, strategy) { 254 | switch (strategy) { 255 | case "data-uri": 256 | return await transformFileInputsToBase64EncodedDataURIs(client, inputs); 257 | case "upload": 258 | return await transformFileInputsToReplicateFileURLs(client, inputs); 259 | case "default": 260 | try { 261 | return await transformFileInputsToReplicateFileURLs(client, inputs); 262 | } catch (error) { 263 | if ( 264 | error instanceof ApiError && 265 | error.response.status >= 400 && 266 | error.response.status < 500 267 | ) { 268 | throw error; 269 | } 270 | return await transformFileInputsToBase64EncodedDataURIs(inputs); 271 | } 272 | default: 273 | throw new Error(`Unexpected file upload strategy: ${strategy}`); 274 | } 275 | } 276 | 277 | /** 278 | * Walks the inputs and, for any File or Blob, tries to upload it to Replicate 279 | * and replaces the input with the URL of the uploaded file. 280 | * 281 | * @param {Replicate} client - The client used to upload the file 282 | * @param {object} inputs - The inputs to transform 283 | * @returns {Promise} - The transformed inputs 284 | * @throws {ApiError} If the request to upload the file fails 285 | */ 286 | async function transformFileInputsToReplicateFileURLs(client, inputs) { 287 | return await transform(inputs, async (value) => { 288 | if (value instanceof Blob || value instanceof Buffer) { 289 | const file = await createFile.call(client, value); 290 | return file.urls.get; 291 | } 292 | 293 | return value; 294 | }); 295 | } 296 | 297 | const MAX_DATA_URI_SIZE = 10_000_000; 298 | 299 | /** 300 | * Walks the inputs and transforms any binary data found into a 301 | * base64-encoded data URI. 302 | * 303 | * @param {object} inputs - The inputs to transform 304 | * @returns {Promise} - The transformed inputs 305 | * @throws {Error} If the size of inputs exceeds a given threshold set by MAX_DATA_URI_SIZE 306 | */ 307 | async function transformFileInputsToBase64EncodedDataURIs(inputs) { 308 | let totalBytes = 0; 309 | return await transform(inputs, async (value) => { 310 | let buffer; 311 | let mime; 312 | 313 | if (value instanceof Blob) { 314 | // Currently, we use a NodeJS only API for base64 encoding, as 315 | // we move to support the browser we could support either using 316 | // btoa (which does string encoding), the FileReader API or 317 | // a JavaScript implementation like base64-js. 318 | // See: https://developer.mozilla.org/en-US/docs/Glossary/Base64 319 | // See: https://github.com/beatgammit/base64-js 320 | buffer = await value.arrayBuffer(); 321 | mime = value.type; 322 | } else if (isTypedArray(value)) { 323 | buffer = value; 324 | } else { 325 | return value; 326 | } 327 | 328 | totalBytes += buffer.byteLength; 329 | if (totalBytes > MAX_DATA_URI_SIZE) { 330 | throw new Error( 331 | `Combined filesize of prediction ${totalBytes} bytes exceeds 10mb limit for inline encoding, please provide URLs instead` 332 | ); 333 | } 334 | 335 | const data = bytesToBase64(buffer); 336 | mime = mime || "application/octet-stream"; 337 | 338 | return `data:${mime};base64,${data}`; 339 | }); 340 | } 341 | 342 | // Walk a JavaScript object and transform the leaf values. 343 | async function transform(value, mapper) { 344 | if (Array.isArray(value)) { 345 | const copy = []; 346 | for (const val of value) { 347 | const transformed = await transform(val, mapper); 348 | copy.push(transformed); 349 | } 350 | return copy; 351 | } 352 | 353 | if (isPlainObject(value)) { 354 | const copy = {}; 355 | for (const key of Object.keys(value)) { 356 | copy[key] = await transform(value[key], mapper); 357 | } 358 | return copy; 359 | } 360 | 361 | return await mapper(value); 362 | } 363 | 364 | function isTypedArray(arr) { 365 | return ( 366 | arr instanceof Int8Array || 367 | arr instanceof Int16Array || 368 | arr instanceof Int32Array || 369 | arr instanceof Uint8Array || 370 | arr instanceof Uint8ClampedArray || 371 | arr instanceof Uint16Array || 372 | arr instanceof Uint32Array || 373 | arr instanceof Float32Array || 374 | arr instanceof Float64Array 375 | ); 376 | } 377 | 378 | // Test for a plain JS object. 379 | // Source: lodash.isPlainObject 380 | function isPlainObject(value) { 381 | const isObjectLike = typeof value === "object" && value !== null; 382 | if (!isObjectLike || String(value) !== "[object Object]") { 383 | return false; 384 | } 385 | const proto = Object.getPrototypeOf(value); 386 | if (proto === null) { 387 | return true; 388 | } 389 | const Ctor = 390 | Object.prototype.hasOwnProperty.call(proto, "constructor") && 391 | proto.constructor; 392 | return ( 393 | typeof Ctor === "function" && 394 | Ctor instanceof Ctor && 395 | Function.prototype.toString.call(Ctor) === 396 | Function.prototype.toString.call(Object) 397 | ); 398 | } 399 | 400 | /** 401 | * Parse progress from prediction logs. 402 | * 403 | * This function supports log statements in the following format, 404 | * which are generated by https://github.com/tqdm/tqdm and similar libraries: 405 | * 406 | * ``` 407 | * 76%|████████████████████████████ | 7568/10000 [00:33<00:10, 229.00it/s] 408 | * ``` 409 | * 410 | * @example 411 | * const progress = parseProgressFromLogs("76%|████████████████████████████ | 7568/10000 [00:33<00:10, 229.00it/s]"); 412 | * console.log(progress); 413 | * // { 414 | * // percentage: 0.76, 415 | * // current: 7568, 416 | * // total: 10000, 417 | * // } 418 | * 419 | * @param {object|string} input - A prediction object or string. 420 | * @returns {(object|null)} - An object with the percentage, current, and total, or null if no progress can be parsed. 421 | */ 422 | function parseProgressFromLogs(input) { 423 | const logs = typeof input === "object" && input.logs ? input.logs : input; 424 | if (!logs || typeof logs !== "string") { 425 | return null; 426 | } 427 | 428 | const pattern = /^\s*(\d+)%\s*\|.+?\|\s*(\d+)\/(\d+)/; 429 | const lines = logs.split("\n").reverse(); 430 | 431 | for (const line of lines) { 432 | const matches = line.match(pattern); 433 | 434 | if (matches && matches.length === 4) { 435 | return { 436 | percentage: parseInt(matches[1], 10) / 100, 437 | current: parseInt(matches[2], 10), 438 | total: parseInt(matches[3], 10), 439 | }; 440 | } 441 | } 442 | 443 | return null; 444 | } 445 | 446 | /** 447 | * Helper to make any `ReadableStream` iterable, this is supported 448 | * by most server runtimes but browsers still haven't implemented 449 | * it yet. 450 | * See: https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#browser_compatibility 451 | * 452 | * @template T 453 | * @param {ReadableStream} stream an instance of a `ReadableStream` 454 | * @yields {T} a chunk/event from the stream 455 | */ 456 | async function* streamAsyncIterator(stream) { 457 | const reader = stream.getReader(); 458 | try { 459 | while (true) { 460 | const { done, value } = await reader.read(); 461 | if (done) return; 462 | yield value; 463 | } 464 | } finally { 465 | reader.releaseLock(); 466 | } 467 | } 468 | 469 | module.exports = { 470 | transform, 471 | transformFileInputs, 472 | validateWebhook, 473 | withAutomaticRetries, 474 | parseProgressFromLogs, 475 | streamAsyncIterator, 476 | }; 477 | -------------------------------------------------------------------------------- /lib/webhooks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the default webhook signing secret 3 | * 4 | * @param {object} [options] 5 | * @param {AbortSignal} [options.signal] - An optional AbortSignal 6 | * @returns {Promise} Resolves with the signing secret for the default webhook 7 | */ 8 | async function getDefaultWebhookSecret({ signal } = {}) { 9 | const response = await this.request("/webhooks/default/secret", { 10 | method: "GET", 11 | signal, 12 | }); 13 | 14 | return response.json(); 15 | } 16 | 17 | module.exports = { 18 | default: { 19 | secret: { 20 | get: getDefaultWebhookSecret, 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "replicate", 3 | "version": "1.1.0-0", 4 | "description": "JavaScript client for Replicate", 5 | "repository": "github:replicate/replicate-javascript", 6 | "homepage": "https://github.com/replicate/replicate-javascript#readme", 7 | "bugs": "https://github.com/replicate/replicate-javascript/issues", 8 | "license": "Apache-2.0", 9 | "main": "index.js", 10 | "type": "commonjs", 11 | "types": "index.d.ts", 12 | "files": [ 13 | "CONTRIBUTING.md", 14 | "LICENSE", 15 | "README.md", 16 | "index.d.ts", 17 | "index.js", 18 | "lib/**/*.js", 19 | "vendor/**/*", 20 | "package.json" 21 | ], 22 | "engines": { 23 | "node": ">=18.0.0", 24 | "npm": ">=7.19.0", 25 | "git": ">=2.11.0", 26 | "yarn": ">=1.7.0" 27 | }, 28 | "scripts": { 29 | "check": "tsc", 30 | "format": "biome format . --write", 31 | "lint-biome": "biome lint .", 32 | "lint-publint": "publint", 33 | "lint": "npm run lint-biome && npm run lint-publint", 34 | "test": "jest" 35 | }, 36 | "optionalDependencies": { 37 | "readable-stream": ">=4.0.0" 38 | }, 39 | "devDependencies": { 40 | "@biomejs/biome": "^1.4.1", 41 | "@types/jest": "^29.5.3", 42 | "@typescript-eslint/eslint-plugin": "^5.56.0", 43 | "cross-fetch": "^3.1.5", 44 | "jest": "^29.7.0", 45 | "nock": "^14.0.0-beta.6", 46 | "publint": "^0.2.7", 47 | "ts-jest": "^29.1.0", 48 | "typescript": "^5.0.2" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "noEmit": true, 5 | "strict": true, 6 | "allowJs": true 7 | }, 8 | "exclude": ["**/node_modules", "integration"] 9 | } 10 | -------------------------------------------------------------------------------- /vendor/eventsource-parser/stream.js: -------------------------------------------------------------------------------- 1 | // Source: https://github.com/rexxars/eventsource-parser/tree/v1.1.2 2 | // 3 | // MIT License 4 | // 5 | // Copyright (c) 2024 Espen Hovlandsdal 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | var __defProp = Object.defineProperty; 25 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 26 | var __getOwnPropNames = Object.getOwnPropertyNames; 27 | var __hasOwnProp = Object.prototype.hasOwnProperty; 28 | var __export = (target, all) => { 29 | for (var name in all) 30 | __defProp(target, name, { get: all[name], enumerable: true }); 31 | }; 32 | var __copyProps = (to, from, except, desc) => { 33 | if ((from && typeof from === "object") || typeof from === "function") { 34 | for (let key of __getOwnPropNames(from)) 35 | if (!__hasOwnProp.call(to, key) && key !== except) 36 | __defProp(to, key, { 37 | get: () => from[key], 38 | enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable, 39 | }); 40 | } 41 | return to; 42 | }; 43 | var __toCommonJS = (mod) => 44 | __copyProps(__defProp({}, "__esModule", { value: true }), mod); 45 | 46 | // /input.ts 47 | var input_exports = {}; 48 | __export(input_exports, { 49 | EventSourceParserStream: () => EventSourceParserStream, 50 | }); 51 | module.exports = __toCommonJS(input_exports); 52 | 53 | // http-url:https://unpkg.com/eventsource-parser@1.1.2/dist/index.js 54 | function createParser(onParse) { 55 | let isFirstChunk; 56 | let buffer; 57 | let startingPosition; 58 | let startingFieldLength; 59 | let eventId; 60 | let eventName; 61 | let data; 62 | reset(); 63 | return { 64 | feed, 65 | reset, 66 | }; 67 | function reset() { 68 | isFirstChunk = true; 69 | buffer = ""; 70 | startingPosition = 0; 71 | startingFieldLength = -1; 72 | eventId = void 0; 73 | eventName = void 0; 74 | data = ""; 75 | } 76 | function feed(chunk) { 77 | buffer = buffer ? buffer + chunk : chunk; 78 | if (isFirstChunk && hasBom(buffer)) { 79 | buffer = buffer.slice(BOM.length); 80 | } 81 | isFirstChunk = false; 82 | const length = buffer.length; 83 | let position = 0; 84 | let discardTrailingNewline = false; 85 | while (position < length) { 86 | if (discardTrailingNewline) { 87 | if (buffer[position] === "\n") { 88 | ++position; 89 | } 90 | discardTrailingNewline = false; 91 | } 92 | let lineLength = -1; 93 | let fieldLength = startingFieldLength; 94 | let character; 95 | for ( 96 | let index = startingPosition; 97 | lineLength < 0 && index < length; 98 | ++index 99 | ) { 100 | character = buffer[index]; 101 | if (character === ":" && fieldLength < 0) { 102 | fieldLength = index - position; 103 | } else if (character === "\r") { 104 | discardTrailingNewline = true; 105 | lineLength = index - position; 106 | } else if (character === "\n") { 107 | lineLength = index - position; 108 | } 109 | } 110 | if (lineLength < 0) { 111 | startingPosition = length - position; 112 | startingFieldLength = fieldLength; 113 | break; 114 | } else { 115 | startingPosition = 0; 116 | startingFieldLength = -1; 117 | } 118 | parseEventStreamLine(buffer, position, fieldLength, lineLength); 119 | position += lineLength + 1; 120 | } 121 | if (position === length) { 122 | buffer = ""; 123 | } else if (position > 0) { 124 | buffer = buffer.slice(position); 125 | } 126 | } 127 | function parseEventStreamLine(lineBuffer, index, fieldLength, lineLength) { 128 | if (lineLength === 0) { 129 | if (data.length > 0) { 130 | onParse({ 131 | type: "event", 132 | id: eventId, 133 | event: eventName || void 0, 134 | data: data.slice(0, -1), 135 | // remove trailing newline 136 | }); 137 | data = ""; 138 | eventId = void 0; 139 | } 140 | eventName = void 0; 141 | return; 142 | } 143 | const noValue = fieldLength < 0; 144 | const field = lineBuffer.slice( 145 | index, 146 | index + (noValue ? lineLength : fieldLength) 147 | ); 148 | let step = 0; 149 | if (noValue) { 150 | step = lineLength; 151 | } else if (lineBuffer[index + fieldLength + 1] === " ") { 152 | step = fieldLength + 2; 153 | } else { 154 | step = fieldLength + 1; 155 | } 156 | const position = index + step; 157 | const valueLength = lineLength - step; 158 | const value = lineBuffer.slice(position, position + valueLength).toString(); 159 | if (field === "data") { 160 | data += value ? "".concat(value, "\n") : "\n"; 161 | } else if (field === "event") { 162 | eventName = value; 163 | } else if (field === "id" && !value.includes("\0")) { 164 | eventId = value; 165 | } else if (field === "retry") { 166 | const retry = parseInt(value, 10); 167 | if (!Number.isNaN(retry)) { 168 | onParse({ 169 | type: "reconnect-interval", 170 | value: retry, 171 | }); 172 | } 173 | } 174 | } 175 | } 176 | var BOM = [239, 187, 191]; 177 | function hasBom(buffer) { 178 | return BOM.every((charCode, index) => buffer.charCodeAt(index) === charCode); 179 | } 180 | 181 | // http-url:https://unpkg.com/eventsource-parser@1.1.2/dist/stream.js 182 | var EventSourceParserStream = class extends TransformStream { 183 | constructor() { 184 | let parser; 185 | super({ 186 | start(controller) { 187 | parser = createParser((event) => { 188 | if (event.type === "event") { 189 | controller.enqueue(event); 190 | } 191 | }); 192 | }, 193 | transform(chunk) { 194 | parser.feed(chunk); 195 | }, 196 | }); 197 | } 198 | }; 199 | -------------------------------------------------------------------------------- /vendor/streams-text-encoding/text-decoder-stream.js: -------------------------------------------------------------------------------- 1 | // Adapted from https://github.com/stardazed/sd-streams 2 | // 3 | // MIT License 4 | // 5 | // Copyright (c) 2018-Present @zenmumbler 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | var __defProp = Object.defineProperty; 25 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 26 | var __getOwnPropNames = Object.getOwnPropertyNames; 27 | var __hasOwnProp = Object.prototype.hasOwnProperty; 28 | var __export = (target, all) => { 29 | for (var name in all) 30 | __defProp(target, name, { get: all[name], enumerable: true }); 31 | }; 32 | var __copyProps = (to, from, except, desc) => { 33 | if (from && typeof from === "object" || typeof from === "function") { 34 | for (let key of __getOwnPropNames(from)) 35 | if (!__hasOwnProp.call(to, key) && key !== except) 36 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 37 | } 38 | return to; 39 | }; 40 | var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 41 | 42 | // /input.ts 43 | var input_exports = {}; 44 | __export(input_exports, { 45 | TextDecoderStream: () => TextDecoderStream 46 | }); 47 | module.exports = __toCommonJS(input_exports); 48 | 49 | // http-url:https://unpkg.com/@stardazed/streams-text-encoding@1.0.2/dist/sd-streams-text-encoding.esm.js 50 | var decDecoder = Symbol("decDecoder"); 51 | var decTransform = Symbol("decTransform"); 52 | var TextDecodeTransformer = class { 53 | constructor(decoder) { 54 | this.decoder_ = decoder; 55 | } 56 | transform(chunk, controller) { 57 | if (!(chunk instanceof ArrayBuffer || ArrayBuffer.isView(chunk))) { 58 | throw new TypeError("Input data must be a BufferSource"); 59 | } 60 | const text = this.decoder_.decode(chunk, { stream: true }); 61 | if (text.length !== 0) { 62 | controller.enqueue(text); 63 | } 64 | } 65 | flush(controller) { 66 | const text = this.decoder_.decode(); 67 | if (text.length !== 0) { 68 | controller.enqueue(text); 69 | } 70 | } 71 | }; 72 | var TextDecoderStream = class { 73 | constructor(label, options) { 74 | const decoder = new TextDecoder(label || "utf-8", options || {}); 75 | this[decDecoder] = decoder; 76 | this[decTransform] = new TransformStream(new TextDecodeTransformer(decoder)); 77 | } 78 | get encoding() { 79 | return this[decDecoder].encoding; 80 | } 81 | get fatal() { 82 | return this[decDecoder].fatal; 83 | } 84 | get ignoreBOM() { 85 | return this[decDecoder].ignoreBOM; 86 | } 87 | get readable() { 88 | return this[decTransform].readable; 89 | } 90 | get writable() { 91 | return this[decTransform].writable; 92 | } 93 | }; 94 | var encEncoder = Symbol("encEncoder"); 95 | var encTransform = Symbol("encTransform"); 96 | --------------------------------------------------------------------------------