├── Brewfile ├── src ├── resources.ts ├── version.ts ├── _shims │ ├── auto │ │ ├── types.js │ │ ├── types.mjs │ │ ├── runtime.ts │ │ ├── types-deno.ts │ │ ├── types-node.ts │ │ ├── runtime-bun.ts │ │ ├── runtime-deno.ts │ │ ├── runtime-node.ts │ │ └── types.d.ts │ ├── node-types.js │ ├── node-types.mjs │ ├── web-types.js │ ├── web-types.mjs │ ├── manual-types.js │ ├── manual-types.mjs │ ├── MultipartBody.ts │ ├── index.mjs │ ├── manual-types.d.ts │ ├── index.js │ ├── bun-runtime.ts │ ├── node-types.d.ts │ ├── web-types.d.ts │ ├── registry.ts │ ├── README.md │ ├── index-deno.ts │ ├── node-runtime.ts │ ├── index.d.ts │ └── web-runtime.ts ├── resources │ ├── chat.ts │ ├── chat │ │ ├── index.ts │ │ └── chat.ts │ ├── index.ts │ ├── models.ts │ └── completions.ts ├── lib │ └── .keep ├── resource.ts ├── shims │ ├── node.ts │ └── web.ts ├── error.ts ├── index.ts ├── uploads.ts └── streaming.ts ├── .release-please-manifest.json ├── .prettierignore ├── .prettierrc.json ├── .gitignore ├── tsc-multi.json ├── scripts ├── format ├── lint ├── utils │ ├── git-swap.sh │ ├── fix-index-exports.cjs │ ├── check-is-in-git-install.sh │ ├── make-dist-package-json.cjs │ ├── check-version.cjs │ ├── upload-artifact.sh │ └── postprocess-files.cjs ├── bootstrap ├── mock ├── fast-format ├── test └── build ├── examples └── .keep ├── .stats.yml ├── .eslintrc.js ├── tsconfig.dist-src.json ├── tsconfig.deno.json ├── tsconfig.build.json ├── .devcontainer └── devcontainer.json ├── jest.config.ts ├── tests ├── responses.test.ts ├── stringifyQuery.test.ts ├── api-resources │ ├── completions.test.ts │ ├── chat │ │ └── completions.test.ts │ └── models.test.ts ├── form.test.ts ├── uploads.test.ts └── index.test.ts ├── api.md ├── tsconfig.json ├── SECURITY.md ├── release-please-config.json ├── bin └── publish-npm ├── CONTRIBUTING.md ├── .github └── workflows │ └── ci.yml ├── package.json ├── LICENSE └── README.md /Brewfile: -------------------------------------------------------------------------------- 1 | brew "node" 2 | -------------------------------------------------------------------------------- /src/resources.ts: -------------------------------------------------------------------------------- 1 | export * from './resources/index'; 2 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "1.59.0" 3 | } 4 | -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | export const VERSION = '1.59.0'; // x-release-please-version 2 | -------------------------------------------------------------------------------- /src/_shims/auto/types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/auto/types.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/node-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/node-types.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/web-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/web-types.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/manual-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/manual-types.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/resources/chat.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | export * from './chat/index'; 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | /ecosystem-tests/*/** 3 | /node_modules 4 | /deno 5 | 6 | # don't format tsc output, will break source maps 7 | /dist 8 | -------------------------------------------------------------------------------- /src/_shims/auto/runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../web-runtime'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/types-deno.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../web-types'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/types-node.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../node-types'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/runtime-bun.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../bun-runtime'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/runtime-deno.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../web-runtime'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/runtime-node.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../node-runtime'; 5 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "experimentalTernaries": true, 4 | "printWidth": 110, 5 | "singleQuote": true, 6 | "trailingComma": "all" 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .prism.log 2 | node_modules 3 | yarn-error.log 4 | codegen.log 5 | Brewfile.lock.json 6 | dist 7 | dist-deno 8 | /*.tgz 9 | .idea/ 10 | .eslintcache 11 | 12 | -------------------------------------------------------------------------------- /tsc-multi.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { "extname": ".js", "module": "commonjs" }, 4 | { "extname": ".mjs", "module": "esnext" } 5 | ], 6 | "projects": ["tsconfig.build.json"] 7 | } 8 | -------------------------------------------------------------------------------- /scripts/format: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | echo "==> Running eslint --fix" 8 | ESLINT_USE_FLAT_CONFIG="false" ./node_modules/.bin/eslint --fix --ext ts,js . 9 | -------------------------------------------------------------------------------- /src/lib/.keep: -------------------------------------------------------------------------------- 1 | File generated from our OpenAPI spec by Stainless. 2 | 3 | This directory can be used to store custom files to expand the SDK. 4 | It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. 5 | -------------------------------------------------------------------------------- /examples/.keep: -------------------------------------------------------------------------------- 1 | File generated from our OpenAPI spec by Stainless. 2 | 3 | This directory can be used to store example files demonstrating usage of this SDK. 4 | It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. 5 | -------------------------------------------------------------------------------- /scripts/lint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | echo "==> Running eslint" 8 | ESLINT_USE_FLAT_CONFIG="false" ./node_modules/.bin/eslint --ext ts,js . 9 | 10 | echo "==> Running tsc" 11 | ./node_modules/.bin/tsc --noEmit 12 | -------------------------------------------------------------------------------- /src/_shims/MultipartBody.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export class MultipartBody { 5 | constructor(public body: any) {} 6 | get [Symbol.toStringTag](): string { 7 | return 'MultipartBody'; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.stats.yml: -------------------------------------------------------------------------------- 1 | configured_endpoints: 4 2 | openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cerebras%2Fcerebras-cloud-8405ccbb7f3a19ebe6549ef75b2decfbb8db21603b2db9dc280699373dad0392.yml 3 | openapi_spec_hash: 17d5c90a32d6896d88b186dc9b26a898 4 | config_hash: 524c038fba9c9b850e7ec8eee16f5c42 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['@typescript-eslint', 'unused-imports', 'prettier'], 4 | rules: { 5 | 'no-unused-vars': 'off', 6 | 'prettier/prettier': 'error', 7 | 'unused-imports/no-unused-imports': 'error', 8 | }, 9 | root: true, 10 | }; 11 | -------------------------------------------------------------------------------- /src/resource.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import type { Cerebras } from './index'; 4 | 5 | export abstract class APIResource { 6 | protected _client: Cerebras; 7 | 8 | constructor(client: Cerebras) { 9 | this._client = client; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/resources/chat/index.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | export { Chat } from './chat'; 4 | export { 5 | Completions, 6 | type ChatCompletion, 7 | type ChatCompletionCreateParams, 8 | type ChatCompletionCreateParamsNonStreaming, 9 | type ChatCompletionCreateParamsStreaming, 10 | } from './completions'; 11 | -------------------------------------------------------------------------------- /tsconfig.dist-src.json: -------------------------------------------------------------------------------- 1 | { 2 | // this config is included in the published src directory to prevent TS errors 3 | // from appearing when users go to source, and VSCode opens the source .ts file 4 | // via declaration maps 5 | "include": ["index.ts"], 6 | "compilerOptions": { 7 | "target": "es2015", 8 | "lib": ["DOM"], 9 | "moduleResolution": "node" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["dist-deno"], 4 | "exclude": [], 5 | "compilerOptions": { 6 | "rootDir": "./dist-deno", 7 | "lib": ["es2020", "DOM"], 8 | "noEmit": true, 9 | "declaration": true, 10 | "declarationMap": true, 11 | "outDir": "dist-deno", 12 | "pretty": true, 13 | "sourceMap": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/_shims/index.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import * as shims from './registry.mjs'; 5 | import * as auto from '@cerebras/cerebras_cloud_sdk/_shims/auto/runtime'; 6 | export const init = () => { 7 | if (!shims.kind) shims.setShims(auto.getRuntime(), { auto: true }); 8 | }; 9 | export * from './registry.mjs'; 10 | 11 | init(); 12 | -------------------------------------------------------------------------------- /src/resources/index.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | export * from './chat/index'; 4 | export { Completions, type Completion, type CompletionCreateParams } from './completions'; 5 | export { 6 | Models, 7 | type ModelRetrieveResponse, 8 | type ModelListResponse, 9 | type ModelRetrieveParams, 10 | type ModelListParams, 11 | } from './models'; 12 | -------------------------------------------------------------------------------- /src/_shims/manual-types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | /** 5 | * Types will get added to this namespace when you import one of the following: 6 | * 7 | * import '@cerebras/cerebras_cloud_sdk/shims/node' 8 | * import '@cerebras/cerebras_cloud_sdk/shims/web' 9 | * 10 | * Importing more than one will cause type and runtime errors. 11 | */ 12 | export namespace manual {} 13 | -------------------------------------------------------------------------------- /scripts/utils/git-swap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -exuo pipefail 3 | # the package is published to NPM from ./dist 4 | # we want the final file structure for git installs to match the npm installs, so we 5 | 6 | # delete everything except ./dist and ./node_modules 7 | find . -maxdepth 1 -mindepth 1 ! -name 'dist' ! -name 'node_modules' -exec rm -rf '{}' + 8 | 9 | # move everything from ./dist to . 10 | mv dist/* . 11 | 12 | # delete the now-empty ./dist 13 | rmdir dist 14 | -------------------------------------------------------------------------------- /scripts/utils/fix-index-exports.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const indexJs = 5 | process.env['DIST_PATH'] ? 6 | path.resolve(process.env['DIST_PATH'], 'index.js') 7 | : path.resolve(__dirname, '..', '..', 'dist', 'index.js'); 8 | 9 | let before = fs.readFileSync(indexJs, 'utf8'); 10 | let after = before.replace( 11 | /^\s*exports\.default\s*=\s*(\w+)/m, 12 | 'exports = module.exports = $1;\nexports.default = $1', 13 | ); 14 | fs.writeFileSync(indexJs, after, 'utf8'); 15 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["dist/src"], 4 | "exclude": ["dist/src/_shims/*-deno.ts"], 5 | "compilerOptions": { 6 | "rootDir": "./dist/src", 7 | "paths": { 8 | "@cerebras/cerebras_cloud_sdk/*": ["./dist/src/*"], 9 | "@cerebras/cerebras_cloud_sdk": ["./dist/src/index.ts"] 10 | }, 11 | "noEmit": false, 12 | "declaration": true, 13 | "declarationMap": true, 14 | "outDir": "dist", 15 | "pretty": true, 16 | "sourceMap": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/utils/check-is-in-git-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Check if you happen to call prepare for a repository that's already in node_modules. 3 | [ "$(basename "$(dirname "$PWD")")" = 'node_modules' ] || 4 | # The name of the containing directory that 'npm` uses, which looks like 5 | # $HOME/.npm/_cacache/git-cloneXXXXXX 6 | [ "$(basename "$(dirname "$PWD")")" = 'tmp' ] || 7 | # The name of the containing directory that 'yarn` uses, which looks like 8 | # $(yarn cache dir)/.tmp/XXXXX 9 | [ "$(basename "$(dirname "$PWD")")" = '.tmp' ] 10 | -------------------------------------------------------------------------------- /src/_shims/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | const shims = require('./registry'); 5 | const auto = require('@cerebras/cerebras_cloud_sdk/_shims/auto/runtime'); 6 | exports.init = () => { 7 | if (!shims.kind) shims.setShims(auto.getRuntime(), { auto: true }); 8 | }; 9 | for (const property of Object.keys(shims)) { 10 | Object.defineProperty(exports, property, { 11 | get() { 12 | return shims[property]; 13 | }, 14 | }); 15 | } 16 | 17 | exports.init(); 18 | -------------------------------------------------------------------------------- /src/_shims/bun-runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import { type Shims } from './registry'; 5 | import { getRuntime as getWebRuntime } from './web-runtime'; 6 | import { ReadStream as FsReadStream } from 'node:fs'; 7 | 8 | export function getRuntime(): Shims { 9 | const runtime = getWebRuntime(); 10 | function isFsReadStream(value: any): value is FsReadStream { 11 | return value instanceof FsReadStream; 12 | } 13 | return { ...runtime, isFsReadStream }; 14 | } 15 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/debian 3 | { 4 | "name": "Development", 5 | "image": "mcr.microsoft.com/devcontainers/typescript-node:latest", 6 | "features": { 7 | "ghcr.io/devcontainers/features/node:1": {} 8 | }, 9 | "postCreateCommand": "yarn install", 10 | "customizations": { 11 | "vscode": { 12 | "extensions": ["esbenp.prettier-vscode"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scripts/bootstrap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then 8 | brew bundle check >/dev/null 2>&1 || { 9 | echo -n "==> Install Homebrew dependencies? (y/N): " 10 | read -r response 11 | case "$response" in 12 | [yY][eE][sS]|[yY]) 13 | brew bundle 14 | ;; 15 | *) 16 | ;; 17 | esac 18 | echo 19 | } 20 | fi 21 | 22 | echo "==> Installing Node dependencies…" 23 | 24 | PACKAGE_MANAGER=$(command -v yarn >/dev/null 2>&1 && echo "yarn" || echo "npm") 25 | 26 | $PACKAGE_MANAGER install 27 | -------------------------------------------------------------------------------- /scripts/utils/make-dist-package-json.cjs: -------------------------------------------------------------------------------- 1 | const pkgJson = require(process.env['PKG_JSON_PATH'] || '../../package.json'); 2 | 3 | function processExportMap(m) { 4 | for (const key in m) { 5 | const value = m[key]; 6 | if (typeof value === 'string') m[key] = value.replace(/^\.\/dist\//, './'); 7 | else processExportMap(value); 8 | } 9 | } 10 | processExportMap(pkgJson.exports); 11 | 12 | for (const key of ['types', 'main', 'module']) { 13 | if (typeof pkgJson[key] === 'string') pkgJson[key] = pkgJson[key].replace(/^(\.\/)?dist\//, './'); 14 | } 15 | 16 | delete pkgJson.devDependencies; 17 | delete pkgJson.scripts.prepack; 18 | delete pkgJson.scripts.prepublishOnly; 19 | delete pkgJson.scripts.prepare; 20 | 21 | console.log(JSON.stringify(pkgJson, null, 2)); 22 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { JestConfigWithTsJest } from 'ts-jest'; 2 | 3 | const config: JestConfigWithTsJest = { 4 | preset: 'ts-jest/presets/default-esm', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], 8 | }, 9 | moduleNameMapper: { 10 | '^@cerebras/cerebras_cloud_sdk$': '/src/index.ts', 11 | '^@cerebras/cerebras_cloud_sdk/_shims/auto/(.*)$': '/src/_shims/auto/$1-node', 12 | '^@cerebras/cerebras_cloud_sdk/(.*)$': '/src/$1', 13 | }, 14 | modulePathIgnorePatterns: [ 15 | '/ecosystem-tests/', 16 | '/dist/', 17 | '/deno/', 18 | '/deno_tests/', 19 | ], 20 | testPathIgnorePatterns: ['scripts'], 21 | }; 22 | 23 | export default config; 24 | -------------------------------------------------------------------------------- /scripts/utils/check-version.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const main = () => { 5 | const pkg = require('../../package.json'); 6 | const version = pkg['version']; 7 | if (!version) throw 'The version property is not set in the package.json file'; 8 | if (typeof version !== 'string') { 9 | throw `Unexpected type for the package.json version field; got ${typeof version}, expected string`; 10 | } 11 | 12 | const versionFile = path.resolve(__dirname, '..', '..', 'src', 'version.ts'); 13 | const contents = fs.readFileSync(versionFile, 'utf8'); 14 | const output = contents.replace(/(export const VERSION = ')(.*)(')/g, `$1${version}$3`); 15 | fs.writeFileSync(versionFile, output); 16 | }; 17 | 18 | if (require.main === module) { 19 | main(); 20 | } 21 | -------------------------------------------------------------------------------- /src/resources/chat/chat.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../../resource'; 4 | import * as CompletionsAPI from './completions'; 5 | import { 6 | ChatCompletion, 7 | ChatCompletionCreateParams, 8 | CompletionCreateParams, 9 | Completions, 10 | } from './completions'; 11 | 12 | export class Chat extends APIResource { 13 | completions: CompletionsAPI.Completions = new CompletionsAPI.Completions(this._client); 14 | } 15 | 16 | Chat.Completions = Completions; 17 | 18 | export declare namespace Chat { 19 | export { 20 | Completions as Completions, 21 | type ChatCompletion as ChatCompletion, 22 | type ChatCompletionCreateParams as ChatCompletionCreateParams, 23 | type CompletionCreateParams as CompletionCreateParams, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /scripts/utils/upload-artifact.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -exuo pipefail 3 | 4 | RESPONSE=$(curl -X POST "$URL" \ 5 | -H "Authorization: Bearer $AUTH" \ 6 | -H "Content-Type: application/json") 7 | 8 | SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') 9 | 10 | if [[ "$SIGNED_URL" == "null" ]]; then 11 | echo -e "\033[31mFailed to get signed URL.\033[0m" 12 | exit 1 13 | fi 14 | 15 | TARBALL=$(cd dist && npm pack --silent) 16 | 17 | UPLOAD_RESPONSE=$(curl -v -X PUT \ 18 | -H "Content-Type: application/gzip" \ 19 | --data-binary "@dist/$TARBALL" "$SIGNED_URL" 2>&1) 20 | 21 | if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then 22 | echo -e "\033[32mUploaded build to Stainless storage.\033[0m" 23 | echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/cerebras-cloud-node/$SHA'\033[0m" 24 | else 25 | echo -e "\033[31mFailed to upload artifact.\033[0m" 26 | exit 1 27 | fi 28 | -------------------------------------------------------------------------------- /scripts/mock: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | if [[ -n "$1" && "$1" != '--'* ]]; then 8 | URL="$1" 9 | shift 10 | else 11 | URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" 12 | fi 13 | 14 | # Check if the URL is empty 15 | if [ -z "$URL" ]; then 16 | echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" 17 | exit 1 18 | fi 19 | 20 | echo "==> Starting mock server with URL ${URL}" 21 | 22 | # Run prism mock on the given spec 23 | if [ "$1" == "--daemon" ]; then 24 | npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & 25 | 26 | # Wait for server to come online 27 | echo -n "Waiting for server" 28 | while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do 29 | echo -n "." 30 | sleep 0.1 31 | done 32 | 33 | if grep -q "✖ fatal" ".prism.log"; then 34 | cat .prism.log 35 | exit 1 36 | fi 37 | 38 | echo 39 | else 40 | npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" 41 | fi 42 | -------------------------------------------------------------------------------- /tests/responses.test.ts: -------------------------------------------------------------------------------- 1 | import { createResponseHeaders } from '@cerebras/cerebras_cloud_sdk/core'; 2 | import { Headers } from '@cerebras/cerebras_cloud_sdk/_shims/index'; 3 | 4 | describe('response parsing', () => { 5 | // TODO: test unicode characters 6 | test('headers are case agnostic', async () => { 7 | const headers = createResponseHeaders(new Headers({ 'Content-Type': 'foo', Accept: 'text/plain' })); 8 | expect(headers['content-type']).toEqual('foo'); 9 | expect(headers['Content-type']).toEqual('foo'); 10 | expect(headers['Content-Type']).toEqual('foo'); 11 | expect(headers['accept']).toEqual('text/plain'); 12 | expect(headers['Accept']).toEqual('text/plain'); 13 | expect(headers['Hello-World']).toBeUndefined(); 14 | }); 15 | 16 | test('duplicate headers are concatenated', () => { 17 | const headers = createResponseHeaders( 18 | new Headers([ 19 | ['Content-Type', 'text/xml'], 20 | ['Content-Type', 'application/json'], 21 | ]), 22 | ); 23 | expect(headers['content-type']).toBe('text/xml, application/json'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/stringifyQuery.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { Cerebras } from '@cerebras/cerebras_cloud_sdk'; 4 | 5 | const { stringifyQuery } = Cerebras.prototype as any; 6 | 7 | describe(stringifyQuery, () => { 8 | for (const [input, expected] of [ 9 | [{ a: '1', b: 2, c: true }, 'a=1&b=2&c=true'], 10 | [{ a: null, b: false, c: undefined }, 'a=&b=false'], 11 | [{ 'a/b': 1.28341 }, `${encodeURIComponent('a/b')}=1.28341`], 12 | [ 13 | { 'a/b': 'c/d', 'e=f': 'g&h' }, 14 | `${encodeURIComponent('a/b')}=${encodeURIComponent('c/d')}&${encodeURIComponent( 15 | 'e=f', 16 | )}=${encodeURIComponent('g&h')}`, 17 | ], 18 | ]) { 19 | it(`${JSON.stringify(input)} -> ${expected}`, () => { 20 | expect(stringifyQuery(input)).toEqual(expected); 21 | }); 22 | } 23 | 24 | for (const value of [[], {}, new Date()]) { 25 | it(`${JSON.stringify(value)} -> `, () => { 26 | expect(() => stringifyQuery({ value })).toThrow(`Cannot stringify type ${typeof value}`); 27 | }); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /api.md: -------------------------------------------------------------------------------- 1 | # Chat 2 | 3 | ## Completions 4 | 5 | Types: 6 | 7 | - ChatCompletion 8 | 9 | Methods: 10 | 11 | - client.chat.completions.create({ ...params }) -> ChatCompletion 12 | 13 | # Completions 14 | 15 | Types: 16 | 17 | - Completion 18 | 19 | Methods: 20 | 21 | - client.completions.create({ ...params }) -> Completion 22 | 23 | # Models 24 | 25 | Types: 26 | 27 | - ModelRetrieveResponse 28 | - ModelListResponse 29 | 30 | Methods: 31 | 32 | - client.models.retrieve(modelId, { ...params }) -> ModelRetrieveResponse 33 | - client.models.list({ ...params }) -> ModelListResponse 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "tests", "examples"], 3 | "exclude": ["src/_shims/**/*-deno.ts"], 4 | "compilerOptions": { 5 | "target": "es2020", 6 | "lib": ["es2020"], 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "paths": { 11 | "@cerebras/cerebras_cloud_sdk/_shims/auto/*": ["./src/_shims/auto/*-node"], 12 | "@cerebras/cerebras_cloud_sdk/*": ["./src/*"], 13 | "@cerebras/cerebras_cloud_sdk": ["./src/index.ts"] 14 | }, 15 | "noEmit": true, 16 | 17 | "resolveJsonModule": true, 18 | 19 | "forceConsistentCasingInFileNames": true, 20 | 21 | "strict": true, 22 | "noImplicitAny": true, 23 | "strictNullChecks": true, 24 | "strictFunctionTypes": true, 25 | "strictBindCallApply": true, 26 | "strictPropertyInitialization": true, 27 | "noImplicitThis": true, 28 | "noImplicitReturns": true, 29 | "alwaysStrict": true, 30 | "exactOptionalPropertyTypes": true, 31 | "noUncheckedIndexedAccess": true, 32 | "noImplicitOverride": true, 33 | "noPropertyAccessFromIndexSignature": true, 34 | "isolatedModules": false, 35 | 36 | "skipLibCheck": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting Security Issues 4 | 5 | This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. 6 | 7 | To report a security issue, please contact the Stainless team at security@stainless.com. 8 | 9 | ## Responsible Disclosure 10 | 11 | We appreciate the efforts of security researchers and individuals who help us maintain the security of 12 | SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible 13 | disclosure practices by allowing us a reasonable amount of time to investigate and address the issue 14 | before making any information public. 15 | 16 | ## Reporting Non-SDK Related Security Issues 17 | 18 | If you encounter security issues that are not directly related to SDKs but pertain to the services 19 | or products provided by Cerebras, please follow the respective company's security reporting guidelines. 20 | 21 | ### Cerebras Terms and Policies 22 | 23 | Please contact support@cerebras.ai for any questions or concerns regarding the security of our services. 24 | 25 | --- 26 | 27 | Thank you for helping us keep the SDKs and systems they interact with secure. 28 | -------------------------------------------------------------------------------- /scripts/fast-format: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | echo "Script started with $# arguments" 6 | echo "Arguments: $*" 7 | echo "Script location: $(dirname "$0")" 8 | 9 | cd "$(dirname "$0")/.." 10 | echo "Changed to directory: $(pwd)" 11 | 12 | if [ $# -eq 0 ]; then 13 | echo "Usage: $0 [additional-formatter-args...]" 14 | echo "The file should contain one file path per line" 15 | exit 1 16 | fi 17 | 18 | FILE_LIST="$1" 19 | 20 | echo "Looking for file: $FILE_LIST" 21 | 22 | if [ ! -f "$FILE_LIST" ]; then 23 | echo "Error: File '$FILE_LIST' not found" 24 | exit 1 25 | fi 26 | 27 | echo "==> Running eslint --fix" 28 | ESLINT_FILES="$(grep '\.ts$' "$FILE_LIST" || true)" 29 | if ! [ -z "$ESLINT_FILES" ]; then 30 | echo "$ESLINT_FILES" | ESLINT_USE_FLAT_CONFIG="false" xargs ./node_modules/.bin/eslint --cache --fix 31 | fi 32 | 33 | echo "==> Running prettier --write" 34 | # format things eslint didn't 35 | PRETTIER_FILES="$(grep '\.\(js\|json\)$' "$FILE_LIST" || true)" 36 | if ! [ -z "$PRETTIER_FILES" ]; then 37 | echo "$PRETTIER_FILES" | xargs ./node_modules/.bin/prettier \ 38 | --write --cache --cache-strategy metadata --no-error-on-unmatched-pattern \ 39 | '!**/dist' '!**/*.ts' '!**/*.mts' '!**/*.cts' '!**/*.js' '!**/*.mjs' '!**/*.cjs' 40 | fi 41 | -------------------------------------------------------------------------------- /src/_shims/node-types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import * as nf from 'node-fetch'; 5 | import * as fd from 'formdata-node'; 6 | 7 | export { type Agent } from 'node:http'; 8 | export { type Readable } from 'node:stream'; 9 | export { type ReadStream as FsReadStream } from 'node:fs'; 10 | export { ReadableStream } from 'node:stream/web'; 11 | 12 | export const fetch: typeof nf.default; 13 | 14 | export type Request = nf.Request; 15 | export type RequestInfo = nf.RequestInfo; 16 | export type RequestInit = nf.RequestInit; 17 | 18 | export type Response = nf.Response; 19 | export type ResponseInit = nf.ResponseInit; 20 | export type ResponseType = nf.ResponseType; 21 | export type BodyInit = nf.BodyInit; 22 | export type Headers = nf.Headers; 23 | export type HeadersInit = nf.HeadersInit; 24 | 25 | type EndingType = 'native' | 'transparent'; 26 | export interface BlobPropertyBag { 27 | endings?: EndingType; 28 | type?: string; 29 | } 30 | 31 | export interface FilePropertyBag extends BlobPropertyBag { 32 | lastModified?: number; 33 | } 34 | 35 | export type FileFromPathOptions = Omit; 36 | 37 | export type FormData = fd.FormData; 38 | export const FormData: typeof fd.FormData; 39 | export type File = fd.File; 40 | export const File: typeof fd.File; 41 | export type Blob = fd.Blob; 42 | export const Blob: typeof fd.Blob; 43 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | ".": {} 4 | }, 5 | "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", 6 | "include-v-in-tag": true, 7 | "include-component-in-tag": false, 8 | "versioning": "prerelease", 9 | "prerelease": true, 10 | "bump-minor-pre-major": true, 11 | "bump-patch-for-minor-pre-major": false, 12 | "pull-request-header": "Automated Release PR", 13 | "pull-request-title-pattern": "release: ${version}", 14 | "changelog-sections": [ 15 | { 16 | "type": "feat", 17 | "section": "Features" 18 | }, 19 | { 20 | "type": "fix", 21 | "section": "Bug Fixes" 22 | }, 23 | { 24 | "type": "perf", 25 | "section": "Performance Improvements" 26 | }, 27 | { 28 | "type": "revert", 29 | "section": "Reverts" 30 | }, 31 | { 32 | "type": "chore", 33 | "section": "Chores" 34 | }, 35 | { 36 | "type": "docs", 37 | "section": "Documentation" 38 | }, 39 | { 40 | "type": "style", 41 | "section": "Styles" 42 | }, 43 | { 44 | "type": "refactor", 45 | "section": "Refactors" 46 | }, 47 | { 48 | "type": "test", 49 | "section": "Tests", 50 | "hidden": true 51 | }, 52 | { 53 | "type": "build", 54 | "section": "Build System" 55 | }, 56 | { 57 | "type": "ci", 58 | "section": "Continuous Integration", 59 | "hidden": true 60 | } 61 | ], 62 | "release-type": "node", 63 | "extra-files": ["src/version.ts", "README.md"] 64 | } 65 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | RED='\033[0;31m' 8 | GREEN='\033[0;32m' 9 | YELLOW='\033[0;33m' 10 | NC='\033[0m' # No Color 11 | 12 | function prism_is_running() { 13 | curl --silent "http://localhost:4010" >/dev/null 2>&1 14 | } 15 | 16 | kill_server_on_port() { 17 | pids=$(lsof -t -i tcp:"$1" || echo "") 18 | if [ "$pids" != "" ]; then 19 | kill "$pids" 20 | echo "Stopped $pids." 21 | fi 22 | } 23 | 24 | function is_overriding_api_base_url() { 25 | [ -n "$TEST_API_BASE_URL" ] 26 | } 27 | 28 | if ! is_overriding_api_base_url && ! prism_is_running ; then 29 | # When we exit this script, make sure to kill the background mock server process 30 | trap 'kill_server_on_port 4010' EXIT 31 | 32 | # Start the dev server 33 | ./scripts/mock --daemon 34 | fi 35 | 36 | if is_overriding_api_base_url ; then 37 | echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" 38 | echo 39 | elif ! prism_is_running ; then 40 | echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" 41 | echo -e "running against your OpenAPI spec." 42 | echo 43 | echo -e "To run the server, pass in the path or url of your OpenAPI" 44 | echo -e "spec to the prism command:" 45 | echo 46 | echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" 47 | echo 48 | 49 | exit 1 50 | else 51 | echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" 52 | echo 53 | fi 54 | 55 | echo "==> Running tests" 56 | ./node_modules/.bin/jest "$@" 57 | -------------------------------------------------------------------------------- /src/shims/node.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import * as types from '../_shims/node-types'; 3 | import { setShims } from '../_shims/registry'; 4 | import { getRuntime } from '../_shims/node-runtime'; 5 | setShims(getRuntime()); 6 | 7 | declare module '../_shims/manual-types' { 8 | export namespace manual { 9 | // @ts-ignore 10 | export type Agent = types.Agent; 11 | // @ts-ignore 12 | export import fetch = types.fetch; 13 | // @ts-ignore 14 | export type Request = types.Request; 15 | // @ts-ignore 16 | export type RequestInfo = types.RequestInfo; 17 | // @ts-ignore 18 | export type RequestInit = types.RequestInit; 19 | // @ts-ignore 20 | export type Response = types.Response; 21 | // @ts-ignore 22 | export type ResponseInit = types.ResponseInit; 23 | // @ts-ignore 24 | export type ResponseType = types.ResponseType; 25 | // @ts-ignore 26 | export type BodyInit = types.BodyInit; 27 | // @ts-ignore 28 | export type Headers = types.Headers; 29 | // @ts-ignore 30 | export type HeadersInit = types.HeadersInit; 31 | // @ts-ignore 32 | export type BlobPropertyBag = types.BlobPropertyBag; 33 | // @ts-ignore 34 | export type FilePropertyBag = types.FilePropertyBag; 35 | // @ts-ignore 36 | export type FileFromPathOptions = types.FileFromPathOptions; 37 | // @ts-ignore 38 | export import FormData = types.FormData; 39 | // @ts-ignore 40 | export import File = types.File; 41 | // @ts-ignore 42 | export import Blob = types.Blob; 43 | // @ts-ignore 44 | export type Readable = types.Readable; 45 | // @ts-ignore 46 | export type FsReadStream = types.FsReadStream; 47 | // @ts-ignore 48 | export import ReadableStream = types.ReadableStream; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/shims/web.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import * as types from '../_shims/web-types'; 3 | import { setShims } from '../_shims/registry'; 4 | import { getRuntime } from '../_shims/web-runtime'; 5 | setShims(getRuntime({ manuallyImported: true })); 6 | 7 | declare module '../_shims/manual-types' { 8 | export namespace manual { 9 | // @ts-ignore 10 | export type Agent = types.Agent; 11 | // @ts-ignore 12 | export import fetch = types.fetch; 13 | // @ts-ignore 14 | export type Request = types.Request; 15 | // @ts-ignore 16 | export type RequestInfo = types.RequestInfo; 17 | // @ts-ignore 18 | export type RequestInit = types.RequestInit; 19 | // @ts-ignore 20 | export type Response = types.Response; 21 | // @ts-ignore 22 | export type ResponseInit = types.ResponseInit; 23 | // @ts-ignore 24 | export type ResponseType = types.ResponseType; 25 | // @ts-ignore 26 | export type BodyInit = types.BodyInit; 27 | // @ts-ignore 28 | export type Headers = types.Headers; 29 | // @ts-ignore 30 | export type HeadersInit = types.HeadersInit; 31 | // @ts-ignore 32 | export type BlobPropertyBag = types.BlobPropertyBag; 33 | // @ts-ignore 34 | export type FilePropertyBag = types.FilePropertyBag; 35 | // @ts-ignore 36 | export type FileFromPathOptions = types.FileFromPathOptions; 37 | // @ts-ignore 38 | export import FormData = types.FormData; 39 | // @ts-ignore 40 | export import File = types.File; 41 | // @ts-ignore 42 | export import Blob = types.Blob; 43 | // @ts-ignore 44 | export type Readable = types.Readable; 45 | // @ts-ignore 46 | export type FsReadStream = types.FsReadStream; 47 | // @ts-ignore 48 | export import ReadableStream = types.ReadableStream; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/api-resources/completions.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Cerebras({ 7 | apiKey: 'My API Key', 8 | baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', 9 | }); 10 | 11 | describe('resource completions', () => { 12 | test('create: only required params', async () => { 13 | const responsePromise = client.completions.create({ model: 'model', prompt: 'string' }); 14 | const rawResponse = await responsePromise.asResponse(); 15 | expect(rawResponse).toBeInstanceOf(Response); 16 | const response = await responsePromise; 17 | expect(response).not.toBeInstanceOf(Response); 18 | const dataAndResponse = await responsePromise.withResponse(); 19 | expect(dataAndResponse.data).toBe(response); 20 | expect(dataAndResponse.response).toBe(rawResponse); 21 | }); 22 | 23 | test('create: required and optional params', async () => { 24 | const response = await client.completions.create({ 25 | model: 'model', 26 | prompt: 'string', 27 | best_of: 0, 28 | echo: true, 29 | frequency_penalty: -2, 30 | grammar_root: 'grammar_root', 31 | logit_bias: {}, 32 | logprobs: 0, 33 | max_tokens: 0, 34 | min_tokens: 0, 35 | n: 0, 36 | presence_penalty: -2, 37 | return_raw_tokens: true, 38 | seed: 0, 39 | stop: 'string', 40 | stream: true, 41 | stream_options: { include_usage: true }, 42 | suffix: 'suffix', 43 | temperature: 0, 44 | top_p: 0, 45 | user: 'user', 46 | 'CF-RAY': 'CF-RAY', 47 | 'X-Amz-Cf-Id': 'X-Amz-Cf-Id', 48 | 'X-delay-time': 0, 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /bin/publish-npm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN" 6 | 7 | yarn build 8 | cd dist 9 | 10 | # Get package name and version from package.json 11 | PACKAGE_NAME="$(jq -r -e '.name' ./package.json)" 12 | VERSION="$(jq -r -e '.version' ./package.json)" 13 | 14 | # Get latest version from npm 15 | # 16 | # If the package doesn't exist, npm will return: 17 | # { 18 | # "error": { 19 | # "code": "E404", 20 | # "summary": "Unpublished on 2025-06-05T09:54:53.528Z", 21 | # "detail": "'the_package' is not in this registry..." 22 | # } 23 | # } 24 | NPM_INFO="$(npm view "$PACKAGE_NAME" version --json 2>/dev/null || true)" 25 | 26 | # Check if we got an E404 error 27 | if echo "$NPM_INFO" | jq -e '.error.code == "E404"' > /dev/null 2>&1; then 28 | # Package doesn't exist yet, no last version 29 | LAST_VERSION="" 30 | elif echo "$NPM_INFO" | jq -e '.error' > /dev/null 2>&1; then 31 | # Report other errors 32 | echo "ERROR: npm returned unexpected data:" 33 | echo "$NPM_INFO" 34 | exit 1 35 | else 36 | # Success - get the version 37 | LAST_VERSION=$(echo "$NPM_INFO" | jq -r '.') # strip quotes 38 | fi 39 | 40 | # Check if current version is pre-release (e.g. alpha / beta / rc) 41 | CURRENT_IS_PRERELEASE=false 42 | if [[ "$VERSION" =~ -([a-zA-Z]+) ]]; then 43 | CURRENT_IS_PRERELEASE=true 44 | CURRENT_TAG="${BASH_REMATCH[1]}" 45 | fi 46 | 47 | # Check if last version is a stable release 48 | LAST_IS_STABLE_RELEASE=true 49 | if [[ -z "$LAST_VERSION" || "$LAST_VERSION" =~ -([a-zA-Z]+) ]]; then 50 | LAST_IS_STABLE_RELEASE=false 51 | fi 52 | 53 | # Use a corresponding alpha/beta tag if there already is a stable release and we're publishing a prerelease. 54 | if $CURRENT_IS_PRERELEASE && $LAST_IS_STABLE_RELEASE; then 55 | TAG="$CURRENT_TAG" 56 | else 57 | TAG="latest" 58 | fi 59 | 60 | # Publish with the appropriate tag 61 | yarn publish --tag "$TAG" 62 | -------------------------------------------------------------------------------- /tests/form.test.ts: -------------------------------------------------------------------------------- 1 | import { multipartFormRequestOptions, createForm } from '@cerebras/cerebras_cloud_sdk/core'; 2 | import { Blob } from '@cerebras/cerebras_cloud_sdk/_shims/index'; 3 | import { toFile } from '@cerebras/cerebras_cloud_sdk'; 4 | 5 | describe('form data validation', () => { 6 | test('valid values do not error', async () => { 7 | await multipartFormRequestOptions({ 8 | body: { 9 | foo: 'foo', 10 | string: 1, 11 | bool: true, 12 | file: await toFile(Buffer.from('some-content')), 13 | blob: new Blob(['Some content'], { type: 'text/plain' }), 14 | }, 15 | }); 16 | }); 17 | 18 | test('null', async () => { 19 | await expect(() => 20 | multipartFormRequestOptions({ 21 | body: { 22 | null: null, 23 | }, 24 | }), 25 | ).rejects.toThrow(TypeError); 26 | }); 27 | 28 | test('undefined is stripped', async () => { 29 | const form = await createForm({ 30 | foo: undefined, 31 | bar: 'baz', 32 | }); 33 | expect(form.has('foo')).toBe(false); 34 | expect(form.get('bar')).toBe('baz'); 35 | }); 36 | 37 | test('nested undefined property is stripped', async () => { 38 | const form = await createForm({ 39 | bar: { 40 | baz: undefined, 41 | }, 42 | }); 43 | expect(Array.from(form.entries())).toEqual([]); 44 | 45 | const form2 = await createForm({ 46 | bar: { 47 | foo: 'string', 48 | baz: undefined, 49 | }, 50 | }); 51 | expect(Array.from(form2.entries())).toEqual([['bar[foo]', 'string']]); 52 | }); 53 | 54 | test('nested undefined array item is stripped', async () => { 55 | const form = await createForm({ 56 | bar: [undefined, undefined], 57 | }); 58 | expect(Array.from(form.entries())).toEqual([]); 59 | 60 | const form2 = await createForm({ 61 | bar: [undefined, 'foo'], 62 | }); 63 | expect(Array.from(form2.entries())).toEqual([['bar[]', 'foo']]); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /scripts/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | node scripts/utils/check-version.cjs 8 | 9 | # Build into dist and will publish the package from there, 10 | # so that src/resources/foo.ts becomes /resources/foo.js 11 | # This way importing from `"@cerebras/cerebras_cloud_sdk/resources/foo"` works 12 | # even with `"moduleResolution": "node"` 13 | 14 | rm -rf dist; mkdir dist 15 | # Copy src to dist/src and build from dist/src into dist, so that 16 | # the source map for index.js.map will refer to ./src/index.ts etc 17 | cp -rp src README.md dist 18 | rm dist/src/_shims/*-deno.ts dist/src/_shims/auto/*-deno.ts 19 | for file in LICENSE CHANGELOG.md; do 20 | if [ -e "${file}" ]; then cp "${file}" dist; fi 21 | done 22 | if [ -e "bin/cli" ]; then 23 | mkdir dist/bin 24 | cp -p "bin/cli" dist/bin/; 25 | fi 26 | # this converts the export map paths for the dist directory 27 | # and does a few other minor things 28 | node scripts/utils/make-dist-package-json.cjs > dist/package.json 29 | 30 | # build to .js/.mjs/.d.ts files 31 | ./node_modules/.bin/tsc-multi 32 | # copy over handwritten .js/.mjs/.d.ts files 33 | cp src/_shims/*.{d.ts,js,mjs,md} dist/_shims 34 | cp src/_shims/auto/*.{d.ts,js,mjs} dist/_shims/auto 35 | # we need to add exports = module.exports = Cerebras to index.js; 36 | # No way to get that from index.ts because it would cause compile errors 37 | # when building .mjs 38 | node scripts/utils/fix-index-exports.cjs 39 | # with "moduleResolution": "nodenext", if ESM resolves to index.d.ts, 40 | # it'll have TS errors on the default import. But if it resolves to 41 | # index.d.mts the default import will work (even though both files have 42 | # the same export default statement) 43 | cp dist/index.d.ts dist/index.d.mts 44 | cp tsconfig.dist-src.json dist/src/tsconfig.json 45 | 46 | node scripts/utils/postprocess-files.cjs 47 | 48 | # make sure that nothing crashes when we require the output CJS or 49 | # import the output ESM 50 | (cd dist && node -e 'require("@cerebras/cerebras_cloud_sdk")') 51 | (cd dist && node -e 'import("@cerebras/cerebras_cloud_sdk")' --input-type=module) 52 | 53 | if [ -e ./scripts/build-deno ] 54 | then 55 | ./scripts/build-deno 56 | fi 57 | -------------------------------------------------------------------------------- /tests/api-resources/chat/completions.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Cerebras({ 7 | apiKey: 'My API Key', 8 | baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', 9 | }); 10 | 11 | describe('resource completions', () => { 12 | test('create: only required params', async () => { 13 | const responsePromise = client.chat.completions.create({ model: 'model' }); 14 | const rawResponse = await responsePromise.asResponse(); 15 | expect(rawResponse).toBeInstanceOf(Response); 16 | const response = await responsePromise; 17 | expect(response).not.toBeInstanceOf(Response); 18 | const dataAndResponse = await responsePromise.withResponse(); 19 | expect(dataAndResponse.data).toBe(response); 20 | expect(dataAndResponse.response).toBe(rawResponse); 21 | }); 22 | 23 | test('create: required and optional params', async () => { 24 | const response = await client.chat.completions.create({ 25 | model: 'model', 26 | disable_reasoning: true, 27 | frequency_penalty: -2, 28 | logit_bias: {}, 29 | logprobs: true, 30 | max_completion_tokens: 0, 31 | max_tokens: 0, 32 | messages: [{ content: 'string', name: 'name', role: 'system' }], 33 | min_completion_tokens: 0, 34 | min_tokens: 0, 35 | n: 0, 36 | parallel_tool_calls: true, 37 | prediction: { content: 'string', type: 'content' }, 38 | presence_penalty: -2, 39 | reasoning_effort: 'low', 40 | response_format: { type: 'text' }, 41 | seed: 0, 42 | service_tier: 'auto', 43 | stop: 'string', 44 | stream: false, 45 | stream_options: { include_usage: true }, 46 | temperature: 0, 47 | tool_choice: 'none', 48 | tools: [ 49 | { 50 | function: { name: 'name', description: 'description', parameters: {}, strict: true }, 51 | type: 'type', 52 | }, 53 | ], 54 | top_logprobs: 0, 55 | top_p: 0, 56 | user: 'user', 57 | 'CF-RAY': 'CF-RAY', 58 | 'X-Amz-Cf-Id': 'X-Amz-Cf-Id', 59 | 'X-delay-time': 0, 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /tests/uploads.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { toFile, type ResponseLike } from '@cerebras/cerebras_cloud_sdk/uploads'; 3 | import { File } from '@cerebras/cerebras_cloud_sdk/_shims/index'; 4 | 5 | class MyClass { 6 | name: string = 'foo'; 7 | } 8 | 9 | function mockResponse({ url, content }: { url: string; content?: Blob }): ResponseLike { 10 | return { 11 | url, 12 | blob: async () => content as any, 13 | }; 14 | } 15 | 16 | describe('toFile', () => { 17 | it('throws a helpful error for mismatched types', async () => { 18 | await expect( 19 | // @ts-expect-error intentionally mismatched type 20 | toFile({ foo: 'string' }), 21 | ).rejects.toThrowErrorMatchingInlineSnapshot( 22 | `"Unexpected data type: object; constructor: Object; props: ["foo"]"`, 23 | ); 24 | 25 | await expect( 26 | // @ts-expect-error intentionally mismatched type 27 | toFile(new MyClass()), 28 | ).rejects.toThrowErrorMatchingInlineSnapshot( 29 | `"Unexpected data type: object; constructor: MyClass; props: ["name"]"`, 30 | ); 31 | }); 32 | 33 | it('disallows string at the type-level', async () => { 34 | // @ts-expect-error we intentionally do not type support for `string` 35 | // to help people avoid passing a file path 36 | const file = await toFile('contents'); 37 | expect(file.text()).resolves.toEqual('contents'); 38 | }); 39 | 40 | it('extracts a file name from a Response', async () => { 41 | const response = mockResponse({ url: 'https://example.com/my/audio.mp3' }); 42 | const file = await toFile(response); 43 | expect(file.name).toEqual('audio.mp3'); 44 | }); 45 | 46 | it('extracts a file name from a File', async () => { 47 | const input = new File(['foo'], 'input.jsonl'); 48 | const file = await toFile(input); 49 | expect(file.name).toEqual('input.jsonl'); 50 | }); 51 | 52 | it('extracts a file name from a ReadStream', async () => { 53 | const input = fs.createReadStream('tests/uploads.test.ts'); 54 | const file = await toFile(input); 55 | expect(file.name).toEqual('uploads.test.ts'); 56 | }); 57 | 58 | it('does not copy File objects', async () => { 59 | const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' }); 60 | const file = await toFile(input); 61 | expect(file).toBe(input); 62 | expect(file.name).toEqual('input.jsonl'); 63 | expect(file.type).toBe('jsonl'); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/_shims/web-types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export type Agent = any; 5 | 6 | declare const _fetch: typeof fetch; 7 | export { _fetch as fetch }; 8 | 9 | type _Request = Request; 10 | export { _Request as Request }; 11 | 12 | type _RequestInfo = RequestInfo; 13 | export { type _RequestInfo as RequestInfo }; 14 | 15 | type _RequestInit = RequestInit; 16 | export { type _RequestInit as RequestInit }; 17 | 18 | type _Response = Response; 19 | export { _Response as Response }; 20 | 21 | type _ResponseInit = ResponseInit; 22 | export { type _ResponseInit as ResponseInit }; 23 | 24 | type _ResponseType = ResponseType; 25 | export { type _ResponseType as ResponseType }; 26 | 27 | type _BodyInit = BodyInit; 28 | export { type _BodyInit as BodyInit }; 29 | 30 | type _Headers = Headers; 31 | export { _Headers as Headers }; 32 | 33 | type _HeadersInit = HeadersInit; 34 | export { type _HeadersInit as HeadersInit }; 35 | 36 | type EndingType = 'native' | 'transparent'; 37 | 38 | export interface BlobPropertyBag { 39 | endings?: EndingType; 40 | type?: string; 41 | } 42 | 43 | export interface FilePropertyBag extends BlobPropertyBag { 44 | lastModified?: number; 45 | } 46 | 47 | export type FileFromPathOptions = Omit; 48 | 49 | type _FormData = FormData; 50 | declare const _FormData: typeof FormData; 51 | export { _FormData as FormData }; 52 | 53 | type _File = File; 54 | declare const _File: typeof File; 55 | export { _File as File }; 56 | 57 | type _Blob = Blob; 58 | declare const _Blob: typeof Blob; 59 | export { _Blob as Blob }; 60 | 61 | export declare class Readable { 62 | readable: boolean; 63 | readonly readableEnded: boolean; 64 | readonly readableFlowing: boolean | null; 65 | readonly readableHighWaterMark: number; 66 | readonly readableLength: number; 67 | readonly readableObjectMode: boolean; 68 | destroyed: boolean; 69 | read(size?: number): any; 70 | pause(): this; 71 | resume(): this; 72 | isPaused(): boolean; 73 | destroy(error?: Error): this; 74 | [Symbol.asyncIterator](): AsyncIterableIterator; 75 | } 76 | 77 | export declare class FsReadStream extends Readable { 78 | path: {}; // node type is string | Buffer 79 | } 80 | 81 | type _ReadableStream = ReadableStream; 82 | declare const _ReadableStream: typeof ReadableStream; 83 | export { _ReadableStream as ReadableStream }; 84 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Setting up the environment 2 | 3 | This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install). 4 | Other package managers may work but are not officially supported for development. 5 | 6 | To set up the repository, run: 7 | 8 | ```sh 9 | $ yarn 10 | $ yarn build 11 | ``` 12 | 13 | This will install all the required dependencies and build output files to `dist/`. 14 | 15 | ## Modifying/Adding code 16 | 17 | Most of the SDK is generated code. Modifications to code will be persisted between generations, but may 18 | result in merge conflicts between manual patches and changes from the generator. The generator will never 19 | modify the contents of the `src/lib/` and `examples/` directories. 20 | 21 | ## Adding and running examples 22 | 23 | All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. 24 | 25 | ```ts 26 | // add an example to examples/.ts 27 | 28 | #!/usr/bin/env -S npm run tsn -T 29 | … 30 | ``` 31 | 32 | ```sh 33 | $ chmod +x examples/.ts 34 | # run the example against your api 35 | $ yarn tsn -T examples/.ts 36 | ``` 37 | 38 | ## Using the repository from source 39 | 40 | If you’d like to use the repository from source, you can either install from git or link to a cloned repository: 41 | 42 | To install via git: 43 | 44 | ```sh 45 | $ npm install git+ssh://git@github.com:Cerebras/cerebras-cloud-sdk-node.git 46 | ``` 47 | 48 | Alternatively, to link a local copy of the repo: 49 | 50 | ```sh 51 | # Clone 52 | $ git clone https://www.github.com/Cerebras/cerebras-cloud-sdk-node 53 | $ cd cerebras-cloud-sdk-node-private 54 | 55 | # With yarn 56 | $ yarn link 57 | $ cd ../my-package 58 | $ yarn link @cerebras/cerebras_cloud_sdk 59 | 60 | # With pnpm 61 | $ pnpm link --global 62 | $ cd ../my-package 63 | $ pnpm link -—global @cerebras/cerebras_cloud_sdk 64 | ``` 65 | 66 | ## Running tests 67 | 68 | Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. 69 | 70 | ```sh 71 | $ npx prism mock path/to/your/openapi.yml 72 | ``` 73 | 74 | ```sh 75 | $ yarn run test 76 | ``` 77 | 78 | ## Linting and formatting 79 | 80 | This repository uses [prettier](https://www.npmjs.com/package/prettier) and 81 | [eslint](https://www.npmjs.com/package/eslint) to format the code in the repository. 82 | 83 | To lint: 84 | 85 | ```sh 86 | $ yarn lint 87 | ``` 88 | 89 | To format and fix all lint issues automatically: 90 | 91 | ```sh 92 | $ yarn fix 93 | ``` 94 | -------------------------------------------------------------------------------- /src/_shims/registry.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import { type RequestOptions } from '../core'; 5 | 6 | export interface Shims { 7 | kind: string; 8 | fetch: any; 9 | Request: any; 10 | Response: any; 11 | Headers: any; 12 | FormData: any; 13 | Blob: any; 14 | File: any; 15 | ReadableStream: any; 16 | getMultipartRequestOptions: >( 17 | form: Shims['FormData'], 18 | opts: RequestOptions, 19 | ) => Promise>; 20 | getDefaultAgent: (url: string) => any; 21 | fileFromPath: 22 | | ((path: string, filename?: string, options?: {}) => Promise) 23 | | ((path: string, options?: {}) => Promise); 24 | isFsReadStream: (value: any) => boolean; 25 | } 26 | 27 | export let auto = false; 28 | export let kind: Shims['kind'] | undefined = undefined; 29 | export let fetch: Shims['fetch'] | undefined = undefined; 30 | export let Request: Shims['Request'] | undefined = undefined; 31 | export let Response: Shims['Response'] | undefined = undefined; 32 | export let Headers: Shims['Headers'] | undefined = undefined; 33 | export let FormData: Shims['FormData'] | undefined = undefined; 34 | export let Blob: Shims['Blob'] | undefined = undefined; 35 | export let File: Shims['File'] | undefined = undefined; 36 | export let ReadableStream: Shims['ReadableStream'] | undefined = undefined; 37 | export let getMultipartRequestOptions: Shims['getMultipartRequestOptions'] | undefined = undefined; 38 | export let getDefaultAgent: Shims['getDefaultAgent'] | undefined = undefined; 39 | export let fileFromPath: Shims['fileFromPath'] | undefined = undefined; 40 | export let isFsReadStream: Shims['isFsReadStream'] | undefined = undefined; 41 | 42 | export function setShims(shims: Shims, options: { auto: boolean } = { auto: false }) { 43 | if (auto) { 44 | throw new Error( 45 | `you must \`import '@cerebras/cerebras_cloud_sdk/shims/${shims.kind}'\` before importing anything else from @cerebras/cerebras_cloud_sdk`, 46 | ); 47 | } 48 | if (kind) { 49 | throw new Error( 50 | `can't \`import '@cerebras/cerebras_cloud_sdk/shims/${shims.kind}'\` after \`import '@cerebras/cerebras_cloud_sdk/shims/${kind}'\``, 51 | ); 52 | } 53 | auto = options.auto; 54 | kind = shims.kind; 55 | fetch = shims.fetch; 56 | Request = shims.Request; 57 | Response = shims.Response; 58 | Headers = shims.Headers; 59 | FormData = shims.FormData; 60 | Blob = shims.Blob; 61 | File = shims.File; 62 | ReadableStream = shims.ReadableStream; 63 | getMultipartRequestOptions = shims.getMultipartRequestOptions; 64 | getDefaultAgent = shims.getDefaultAgent; 65 | fileFromPath = shims.fileFromPath; 66 | isFsReadStream = shims.isFsReadStream; 67 | } 68 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches-ignore: 5 | - 'generated' 6 | - 'codegen/**' 7 | - 'integrated/**' 8 | - 'stl-preview-head/**' 9 | - 'stl-preview-base/**' 10 | pull_request: 11 | branches-ignore: 12 | - 'stl-preview-head/**' 13 | - 'stl-preview-base/**' 14 | 15 | jobs: 16 | lint: 17 | timeout-minutes: 10 18 | name: lint 19 | runs-on: ${{ github.repository == 'stainless-sdks/cerebras-cloud-node' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} 20 | if: github.event_name == 'push' || github.event.pull_request.head.repo.fork 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: Set up Node 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: '18' 28 | 29 | - name: Bootstrap 30 | run: ./scripts/bootstrap 31 | 32 | - name: Check types 33 | run: ./scripts/lint 34 | 35 | build: 36 | timeout-minutes: 5 37 | name: build 38 | runs-on: ${{ github.repository == 'stainless-sdks/cerebras-cloud-node' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} 39 | if: github.event_name == 'push' || github.event.pull_request.head.repo.fork 40 | permissions: 41 | contents: read 42 | id-token: write 43 | steps: 44 | - uses: actions/checkout@v4 45 | 46 | - name: Set up Node 47 | uses: actions/setup-node@v4 48 | with: 49 | node-version: '18' 50 | 51 | - name: Bootstrap 52 | run: ./scripts/bootstrap 53 | 54 | - name: Check build 55 | run: ./scripts/build 56 | 57 | - name: Get GitHub OIDC Token 58 | if: github.repository == 'stainless-sdks/cerebras-cloud-node' 59 | id: github-oidc 60 | uses: actions/github-script@v6 61 | with: 62 | script: core.setOutput('github_token', await core.getIDToken()); 63 | 64 | - name: Upload tarball 65 | if: github.repository == 'stainless-sdks/cerebras-cloud-node' 66 | env: 67 | URL: https://pkg.stainless.com/s 68 | AUTH: ${{ steps.github-oidc.outputs.github_token }} 69 | SHA: ${{ github.sha }} 70 | run: ./scripts/utils/upload-artifact.sh 71 | test: 72 | timeout-minutes: 10 73 | name: test 74 | runs-on: ${{ github.repository == 'stainless-sdks/cerebras-cloud-node' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} 75 | if: github.event_name == 'push' || github.event.pull_request.head.repo.fork 76 | steps: 77 | - uses: actions/checkout@v4 78 | 79 | - name: Set up Node 80 | uses: actions/setup-node@v4 81 | with: 82 | node-version: '20' 83 | 84 | - name: Bootstrap 85 | run: ./scripts/bootstrap 86 | 87 | - name: Run tests 88 | run: ./scripts/test 89 | -------------------------------------------------------------------------------- /tests/api-resources/models.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Cerebras({ 7 | apiKey: 'My API Key', 8 | baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', 9 | }); 10 | 11 | describe('resource models', () => { 12 | test('retrieve', async () => { 13 | const responsePromise = client.models.retrieve('model_id'); 14 | const rawResponse = await responsePromise.asResponse(); 15 | expect(rawResponse).toBeInstanceOf(Response); 16 | const response = await responsePromise; 17 | expect(response).not.toBeInstanceOf(Response); 18 | const dataAndResponse = await responsePromise.withResponse(); 19 | expect(dataAndResponse.data).toBe(response); 20 | expect(dataAndResponse.response).toBe(rawResponse); 21 | }); 22 | 23 | test('retrieve: request options instead of params are passed correctly', async () => { 24 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 25 | await expect(client.models.retrieve('model_id', { path: '/_stainless_unknown_path' })).rejects.toThrow( 26 | Cerebras.NotFoundError, 27 | ); 28 | }); 29 | 30 | test('retrieve: request options and params are passed correctly', async () => { 31 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 32 | await expect( 33 | client.models.retrieve( 34 | 'model_id', 35 | { 'CF-RAY': 'CF-RAY', 'X-Amz-Cf-Id': 'X-Amz-Cf-Id' }, 36 | { path: '/_stainless_unknown_path' }, 37 | ), 38 | ).rejects.toThrow(Cerebras.NotFoundError); 39 | }); 40 | 41 | test('list', async () => { 42 | const responsePromise = client.models.list(); 43 | const rawResponse = await responsePromise.asResponse(); 44 | expect(rawResponse).toBeInstanceOf(Response); 45 | const response = await responsePromise; 46 | expect(response).not.toBeInstanceOf(Response); 47 | const dataAndResponse = await responsePromise.withResponse(); 48 | expect(dataAndResponse.data).toBe(response); 49 | expect(dataAndResponse.response).toBe(rawResponse); 50 | }); 51 | 52 | test('list: request options instead of params are passed correctly', async () => { 53 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 54 | await expect(client.models.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( 55 | Cerebras.NotFoundError, 56 | ); 57 | }); 58 | 59 | test('list: request options and params are passed correctly', async () => { 60 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 61 | await expect( 62 | client.models.list( 63 | { 'CF-RAY': 'CF-RAY', 'X-Amz-Cf-Id': 'X-Amz-Cf-Id' }, 64 | { path: '/_stainless_unknown_path' }, 65 | ), 66 | ).rejects.toThrow(Cerebras.NotFoundError); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /src/_shims/README.md: -------------------------------------------------------------------------------- 1 | # 👋 Wondering what everything in here does? 2 | 3 | `@cerebras/cerebras_cloud_sdk` supports a wide variety of runtime environments like Node.js, Deno, Bun, browsers, and various 4 | edge runtimes, as well as both CommonJS (CJS) and EcmaScript Modules (ESM). 5 | 6 | To do this, `@cerebras/cerebras_cloud_sdk` provides shims for either using `node-fetch` when in Node (because `fetch` is still experimental there) or the global `fetch` API built into the environment when not in Node. 7 | 8 | It uses [conditional exports](https://nodejs.org/api/packages.html#conditional-exports) to 9 | automatically select the correct shims for each environment. However, conditional exports are a fairly new 10 | feature and not supported everywhere. For instance, the TypeScript `"moduleResolution": "node"` 11 | 12 | setting doesn't consult the `exports` map, compared to `"moduleResolution": "nodeNext"`, which does. 13 | Unfortunately that's still the default setting, and it can result in errors like 14 | getting the wrong raw `Response` type from `.asResponse()`, for example. 15 | 16 | The user can work around these issues by manually importing one of: 17 | 18 | - `import '@cerebras/cerebras_cloud_sdk/shims/node'` 19 | - `import '@cerebras/cerebras_cloud_sdk/shims/web'` 20 | 21 | All of the code here in `_shims` handles selecting the automatic default shims or manual overrides. 22 | 23 | ### How it works - Runtime 24 | 25 | Runtime shims get installed by calling `setShims` exported by `@cerebras/cerebras_cloud_sdk/_shims/registry`. 26 | 27 | Manually importing `@cerebras/cerebras_cloud_sdk/shims/node` or `@cerebras/cerebras_cloud_sdk/shims/web`, calls `setShims` with the respective runtime shims. 28 | 29 | All client code imports shims from `@cerebras/cerebras_cloud_sdk/_shims/index`, which: 30 | 31 | - checks if shims have been set manually 32 | - if not, calls `setShims` with the shims from `@cerebras/cerebras_cloud_sdk/_shims/auto/runtime` 33 | - re-exports the installed shims from `@cerebras/cerebras_cloud_sdk/_shims/registry`. 34 | 35 | `@cerebras/cerebras_cloud_sdk/_shims/auto/runtime` exports web runtime shims. 36 | If the `node` export condition is set, the export map replaces it with `@cerebras/cerebras_cloud_sdk/_shims/auto/runtime-node`. 37 | 38 | ### How it works - Type time 39 | 40 | All client code imports shim types from `@cerebras/cerebras_cloud_sdk/_shims/index`, which selects the manual types from `@cerebras/cerebras_cloud_sdk/_shims/manual-types` if they have been declared, otherwise it exports the auto types from `@cerebras/cerebras_cloud_sdk/_shims/auto/types`. 41 | 42 | `@cerebras/cerebras_cloud_sdk/_shims/manual-types` exports an empty namespace. 43 | Manually importing `@cerebras/cerebras_cloud_sdk/shims/node` or `@cerebras/cerebras_cloud_sdk/shims/web` merges declarations into this empty namespace, so they get picked up by `@cerebras/cerebras_cloud_sdk/_shims/index`. 44 | 45 | `@cerebras/cerebras_cloud_sdk/_shims/auto/types` exports web type definitions. 46 | If the `node` export condition is set, the export map replaces it with `@cerebras/cerebras_cloud_sdk/_shims/auto/types-node`, though TS only picks this up if `"moduleResolution": "nodenext"` or `"moduleResolution": "bundler"`. 47 | -------------------------------------------------------------------------------- /src/_shims/index-deno.ts: -------------------------------------------------------------------------------- 1 | import { MultipartBody } from './MultipartBody'; 2 | import { type RequestOptions } from '../core'; 3 | 4 | export const kind: string = 'web'; 5 | 6 | export type Agent = any; 7 | 8 | const _fetch = fetch; 9 | type _fetch = typeof fetch; 10 | export { _fetch as fetch }; 11 | 12 | const _Request = Request; 13 | type _Request = Request; 14 | export { _Request as Request }; 15 | 16 | type _RequestInfo = RequestInfo; 17 | export { type _RequestInfo as RequestInfo }; 18 | 19 | type _RequestInit = RequestInit; 20 | export { type _RequestInit as RequestInit }; 21 | 22 | const _Response = Response; 23 | type _Response = Response; 24 | export { _Response as Response }; 25 | 26 | type _ResponseInit = ResponseInit; 27 | export { type _ResponseInit as ResponseInit }; 28 | 29 | type _ResponseType = ResponseType; 30 | export { type _ResponseType as ResponseType }; 31 | 32 | type _BodyInit = BodyInit; 33 | export { type _BodyInit as BodyInit }; 34 | 35 | const _Headers = Headers; 36 | type _Headers = Headers; 37 | export { _Headers as Headers }; 38 | 39 | type _HeadersInit = HeadersInit; 40 | export { type _HeadersInit as HeadersInit }; 41 | 42 | type EndingType = 'native' | 'transparent'; 43 | 44 | export interface BlobPropertyBag { 45 | endings?: EndingType; 46 | type?: string; 47 | } 48 | 49 | export interface FilePropertyBag extends BlobPropertyBag { 50 | lastModified?: number; 51 | } 52 | 53 | export type FileFromPathOptions = Omit; 54 | 55 | const _FormData = FormData; 56 | type _FormData = FormData; 57 | export { _FormData as FormData }; 58 | 59 | const _File = File; 60 | type _File = File; 61 | export { _File as File }; 62 | 63 | const _Blob = Blob; 64 | type _Blob = Blob; 65 | export { _Blob as Blob }; 66 | 67 | export async function getMultipartRequestOptions>( 68 | form: FormData, 69 | opts: RequestOptions, 70 | ): Promise> { 71 | return { 72 | ...opts, 73 | body: new MultipartBody(form) as any, 74 | }; 75 | } 76 | 77 | export function getDefaultAgent(url: string) { 78 | return undefined; 79 | } 80 | export function fileFromPath() { 81 | throw new Error( 82 | 'The `fileFromPath` function is only supported in Node. See the README for more details: https://www.github.com/Cerebras/cerebras-cloud-sdk-node#file-uploads', 83 | ); 84 | } 85 | 86 | export const isFsReadStream = (value: any) => false; 87 | 88 | export declare class Readable { 89 | readable: boolean; 90 | readonly readableEnded: boolean; 91 | readonly readableFlowing: boolean | null; 92 | readonly readableHighWaterMark: number; 93 | readonly readableLength: number; 94 | readonly readableObjectMode: boolean; 95 | destroyed: boolean; 96 | read(size?: number): any; 97 | pause(): this; 98 | resume(): this; 99 | isPaused(): boolean; 100 | destroy(error?: Error): this; 101 | [Symbol.asyncIterator](): AsyncIterableIterator; 102 | } 103 | 104 | export declare class FsReadStream extends Readable { 105 | path: {}; // node type is string | Buffer 106 | } 107 | 108 | const _ReadableStream = ReadableStream; 109 | type _ReadableStream = ReadableStream; 110 | export { _ReadableStream as ReadableStream }; 111 | 112 | export const init = () => {}; 113 | -------------------------------------------------------------------------------- /src/resources/models.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../resource'; 4 | import { isRequestOptions } from '../core'; 5 | import * as Core from '../core'; 6 | 7 | export class Models extends APIResource { 8 | /** 9 | * Get Model 10 | */ 11 | retrieve( 12 | modelId: string, 13 | params?: ModelRetrieveParams, 14 | options?: Core.RequestOptions, 15 | ): Core.APIPromise; 16 | retrieve(modelId: string, options?: Core.RequestOptions): Core.APIPromise; 17 | retrieve( 18 | modelId: string, 19 | params: ModelRetrieveParams | Core.RequestOptions = {}, 20 | options?: Core.RequestOptions, 21 | ): Core.APIPromise { 22 | if (isRequestOptions(params)) { 23 | return this.retrieve(modelId, {}, params); 24 | } 25 | const { 'CF-RAY': cfRay, 'X-Amz-Cf-Id': xAmzCfId } = params; 26 | return this._client.get(`/v1/models/${modelId}`, { 27 | ...options, 28 | headers: { 29 | ...(cfRay != null ? { 'CF-RAY': cfRay } : undefined), 30 | ...(xAmzCfId != null ? { 'X-Amz-Cf-Id': xAmzCfId } : undefined), 31 | ...options?.headers, 32 | }, 33 | }); 34 | } 35 | 36 | /** 37 | * List Models 38 | */ 39 | list(params?: ModelListParams, options?: Core.RequestOptions): Core.APIPromise; 40 | list(options?: Core.RequestOptions): Core.APIPromise; 41 | list( 42 | params: ModelListParams | Core.RequestOptions = {}, 43 | options?: Core.RequestOptions, 44 | ): Core.APIPromise { 45 | if (isRequestOptions(params)) { 46 | return this.list({}, params); 47 | } 48 | const { 'CF-RAY': cfRay, 'X-Amz-Cf-Id': xAmzCfId } = params; 49 | return this._client.get('/v1/models', { 50 | ...options, 51 | headers: { 52 | ...(cfRay != null ? { 'CF-RAY': cfRay } : undefined), 53 | ...(xAmzCfId != null ? { 'X-Amz-Cf-Id': xAmzCfId } : undefined), 54 | ...options?.headers, 55 | }, 56 | }); 57 | } 58 | } 59 | 60 | export interface ModelRetrieveResponse { 61 | id: string; 62 | 63 | created?: number; 64 | 65 | object?: 'model'; 66 | 67 | owned_by?: string; 68 | 69 | [k: string]: unknown; 70 | } 71 | 72 | export interface ModelListResponse { 73 | data: Array; 74 | 75 | object?: 'list'; 76 | 77 | [k: string]: unknown; 78 | } 79 | 80 | export namespace ModelListResponse { 81 | export interface Data { 82 | id: string; 83 | 84 | created?: number; 85 | 86 | object?: 'model'; 87 | 88 | owned_by?: string; 89 | 90 | [k: string]: unknown; 91 | } 92 | } 93 | 94 | export interface ModelRetrieveParams { 95 | 'CF-RAY'?: string; 96 | 97 | 'X-Amz-Cf-Id'?: string; 98 | } 99 | 100 | export interface ModelListParams { 101 | 'CF-RAY'?: string; 102 | 103 | 'X-Amz-Cf-Id'?: string; 104 | } 105 | 106 | export declare namespace Models { 107 | export { 108 | type ModelRetrieveResponse as ModelRetrieveResponse, 109 | type ModelListResponse as ModelListResponse, 110 | type ModelRetrieveParams as ModelRetrieveParams, 111 | type ModelListParams as ModelListParams, 112 | }; 113 | } 114 | -------------------------------------------------------------------------------- /src/_shims/node-runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import * as nf from 'node-fetch'; 5 | import * as fd from 'formdata-node'; 6 | import { type File, type FilePropertyBag } from 'formdata-node'; 7 | import KeepAliveAgent from 'agentkeepalive'; 8 | import { AbortController as AbortControllerPolyfill } from 'abort-controller'; 9 | import { ReadStream as FsReadStream } from 'node:fs'; 10 | import { type Agent } from 'node:http'; 11 | import { FormDataEncoder } from 'form-data-encoder'; 12 | import { Readable } from 'node:stream'; 13 | import { type RequestOptions } from '../core'; 14 | import { MultipartBody } from './MultipartBody'; 15 | import { type Shims } from './registry'; 16 | import { ReadableStream } from 'node:stream/web'; 17 | 18 | type FileFromPathOptions = Omit; 19 | 20 | let fileFromPathWarned = false; 21 | 22 | /** 23 | * @deprecated use fs.createReadStream('./my/file.txt') instead 24 | */ 25 | async function fileFromPath(path: string): Promise; 26 | async function fileFromPath(path: string, filename?: string): Promise; 27 | async function fileFromPath(path: string, options?: FileFromPathOptions): Promise; 28 | async function fileFromPath(path: string, filename?: string, options?: FileFromPathOptions): Promise; 29 | async function fileFromPath(path: string, ...args: any[]): Promise { 30 | // this import fails in environments that don't handle export maps correctly, like old versions of Jest 31 | const { fileFromPath: _fileFromPath } = await import('formdata-node/file-from-path'); 32 | 33 | if (!fileFromPathWarned) { 34 | console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path)}) instead`); 35 | fileFromPathWarned = true; 36 | } 37 | // @ts-ignore 38 | return await _fileFromPath(path, ...args); 39 | } 40 | 41 | const defaultHttpAgent: Agent = new KeepAliveAgent({ keepAlive: true, timeout: 5 * 60 * 1000 }); 42 | const defaultHttpsAgent: Agent = new KeepAliveAgent.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1000 }); 43 | 44 | async function getMultipartRequestOptions>( 45 | form: fd.FormData, 46 | opts: RequestOptions, 47 | ): Promise> { 48 | const encoder = new FormDataEncoder(form); 49 | const readable = Readable.from(encoder); 50 | const body = new MultipartBody(readable); 51 | const headers = { 52 | ...opts.headers, 53 | ...encoder.headers, 54 | 'Content-Length': encoder.contentLength, 55 | }; 56 | 57 | return { ...opts, body: body as any, headers }; 58 | } 59 | 60 | export function getRuntime(): Shims { 61 | // Polyfill global object if needed. 62 | if (typeof AbortController === 'undefined') { 63 | // @ts-expect-error (the types are subtly different, but compatible in practice) 64 | globalThis.AbortController = AbortControllerPolyfill; 65 | } 66 | return { 67 | kind: 'node', 68 | fetch: nf.default, 69 | Request: nf.Request, 70 | Response: nf.Response, 71 | Headers: nf.Headers, 72 | FormData: fd.FormData, 73 | Blob: fd.Blob, 74 | File: fd.File, 75 | ReadableStream, 76 | getMultipartRequestOptions, 77 | getDefaultAgent: (url: string): Agent => (url.startsWith('https') ? defaultHttpsAgent : defaultHttpAgent), 78 | fileFromPath, 79 | isFsReadStream: (value: any): value is FsReadStream => value instanceof FsReadStream, 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /src/_shims/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import { manual } from './manual-types'; 5 | import * as auto from '@cerebras/cerebras_cloud_sdk/_shims/auto/types'; 6 | import { type RequestOptions } from '../core'; 7 | 8 | type SelectType = unknown extends Manual ? Auto : Manual; 9 | 10 | export const kind: string; 11 | 12 | // @ts-ignore 13 | export type Agent = SelectType; 14 | 15 | // @ts-ignore 16 | export const fetch: SelectType; 17 | 18 | // @ts-ignore 19 | export type Request = SelectType; 20 | // @ts-ignore 21 | export type RequestInfo = SelectType; 22 | // @ts-ignore 23 | export type RequestInit = SelectType; 24 | 25 | // @ts-ignore 26 | export type Response = SelectType; 27 | // @ts-ignore 28 | export type ResponseInit = SelectType; 29 | // @ts-ignore 30 | export type ResponseType = SelectType; 31 | // @ts-ignore 32 | export type BodyInit = SelectType; 33 | // @ts-ignore 34 | export type Headers = SelectType; 35 | // @ts-ignore 36 | export const Headers: SelectType; 37 | // @ts-ignore 38 | export type HeadersInit = SelectType; 39 | 40 | // @ts-ignore 41 | export type BlobPropertyBag = SelectType; 42 | // @ts-ignore 43 | export type FilePropertyBag = SelectType; 44 | // @ts-ignore 45 | export type FileFromPathOptions = SelectType; 46 | // @ts-ignore 47 | export type FormData = SelectType; 48 | // @ts-ignore 49 | export const FormData: SelectType; 50 | // @ts-ignore 51 | export type File = SelectType; 52 | // @ts-ignore 53 | export const File: SelectType; 54 | // @ts-ignore 55 | export type Blob = SelectType; 56 | // @ts-ignore 57 | export const Blob: SelectType; 58 | 59 | // @ts-ignore 60 | export type Readable = SelectType; 61 | // @ts-ignore 62 | export type FsReadStream = SelectType; 63 | // @ts-ignore 64 | export type ReadableStream = SelectType; 65 | // @ts-ignore 66 | export const ReadableStream: SelectType; 67 | 68 | export function getMultipartRequestOptions>( 69 | form: FormData, 70 | opts: RequestOptions, 71 | ): Promise>; 72 | 73 | export function getDefaultAgent(url: string): any; 74 | 75 | // @ts-ignore 76 | export type FileFromPathOptions = SelectType; 77 | 78 | export function fileFromPath(path: string, options?: FileFromPathOptions): Promise; 79 | export function fileFromPath(path: string, filename?: string, options?: FileFromPathOptions): Promise; 80 | 81 | export function isFsReadStream(value: any): value is FsReadStream; 82 | 83 | export const init: () => void; 84 | -------------------------------------------------------------------------------- /src/_shims/auto/types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export type Agent = any; 5 | 6 | // @ts-ignore 7 | declare const _fetch: unknown extends typeof fetch ? never : typeof fetch; 8 | export { _fetch as fetch }; 9 | 10 | // @ts-ignore 11 | type _Request = unknown extends Request ? never : Request; 12 | export { _Request as Request }; 13 | 14 | // @ts-ignore 15 | type _RequestInfo = unknown extends RequestInfo ? never : RequestInfo; 16 | export { type _RequestInfo as RequestInfo }; 17 | 18 | // @ts-ignore 19 | type _RequestInit = unknown extends RequestInit ? never : RequestInit; 20 | export { type _RequestInit as RequestInit }; 21 | 22 | // @ts-ignore 23 | type _Response = unknown extends Response ? never : Response; 24 | export { _Response as Response }; 25 | 26 | // @ts-ignore 27 | type _ResponseInit = unknown extends ResponseInit ? never : ResponseInit; 28 | export { type _ResponseInit as ResponseInit }; 29 | 30 | // @ts-ignore 31 | type _ResponseType = unknown extends ResponseType ? never : ResponseType; 32 | export { type _ResponseType as ResponseType }; 33 | 34 | // @ts-ignore 35 | type _BodyInit = unknown extends BodyInit ? never : BodyInit; 36 | export { type _BodyInit as BodyInit }; 37 | 38 | // @ts-ignore 39 | type _Headers = unknown extends Headers ? never : Headers; 40 | export { _Headers as Headers }; 41 | 42 | // @ts-ignore 43 | type _HeadersInit = unknown extends HeadersInit ? never : HeadersInit; 44 | export { type _HeadersInit as HeadersInit }; 45 | 46 | type EndingType = 'native' | 'transparent'; 47 | 48 | export interface BlobPropertyBag { 49 | endings?: EndingType; 50 | type?: string; 51 | } 52 | 53 | export interface FilePropertyBag extends BlobPropertyBag { 54 | lastModified?: number; 55 | } 56 | 57 | export type FileFromPathOptions = Omit; 58 | 59 | // @ts-ignore 60 | type _FormData = unknown extends FormData ? never : FormData; 61 | // @ts-ignore 62 | declare const _FormData: unknown extends typeof FormData ? never : typeof FormData; 63 | export { _FormData as FormData }; 64 | 65 | // @ts-ignore 66 | type _File = unknown extends File ? never : File; 67 | // @ts-ignore 68 | declare const _File: unknown extends typeof File ? never : typeof File; 69 | export { _File as File }; 70 | 71 | // @ts-ignore 72 | type _Blob = unknown extends Blob ? never : Blob; 73 | // @ts-ignore 74 | declare const _Blob: unknown extends typeof Blob ? never : typeof Blob; 75 | export { _Blob as Blob }; 76 | 77 | export declare class Readable { 78 | readable: boolean; 79 | readonly readableEnded: boolean; 80 | readonly readableFlowing: boolean | null; 81 | readonly readableHighWaterMark: number; 82 | readonly readableLength: number; 83 | readonly readableObjectMode: boolean; 84 | destroyed: boolean; 85 | read(size?: number): any; 86 | pause(): this; 87 | resume(): this; 88 | isPaused(): boolean; 89 | destroy(error?: Error): this; 90 | [Symbol.asyncIterator](): AsyncIterableIterator; 91 | } 92 | 93 | export declare class FsReadStream extends Readable { 94 | path: {}; // node type is string | Buffer 95 | } 96 | 97 | // @ts-ignore 98 | type _ReadableStream = unknown extends ReadableStream ? never : ReadableStream; 99 | // @ts-ignore 100 | declare const _ReadableStream: unknown extends typeof ReadableStream ? never : typeof ReadableStream; 101 | export { _ReadableStream as ReadableStream }; 102 | -------------------------------------------------------------------------------- /src/_shims/web-runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import { MultipartBody } from './MultipartBody'; 5 | import { type RequestOptions } from '../core'; 6 | import { type Shims } from './registry'; 7 | 8 | export function getRuntime({ manuallyImported }: { manuallyImported?: boolean } = {}): Shims { 9 | const recommendation = 10 | manuallyImported ? 11 | `You may need to use polyfills` 12 | : `Add one of these imports before your first \`import … from '@cerebras/cerebras_cloud_sdk'\`: 13 | - \`import '@cerebras/cerebras_cloud_sdk/shims/node'\` (if you're running on Node) 14 | - \`import '@cerebras/cerebras_cloud_sdk/shims/web'\` (otherwise) 15 | `; 16 | 17 | let _fetch, _Request, _Response, _Headers; 18 | try { 19 | // @ts-ignore 20 | _fetch = fetch; 21 | // @ts-ignore 22 | _Request = Request; 23 | // @ts-ignore 24 | _Response = Response; 25 | // @ts-ignore 26 | _Headers = Headers; 27 | } catch (error) { 28 | throw new Error( 29 | `this environment is missing the following Web Fetch API type: ${ 30 | (error as any).message 31 | }. ${recommendation}`, 32 | ); 33 | } 34 | 35 | return { 36 | kind: 'web', 37 | fetch: _fetch, 38 | Request: _Request, 39 | Response: _Response, 40 | Headers: _Headers, 41 | FormData: 42 | // @ts-ignore 43 | typeof FormData !== 'undefined' ? FormData : ( 44 | class FormData { 45 | // @ts-ignore 46 | constructor() { 47 | throw new Error( 48 | `file uploads aren't supported in this environment yet as 'FormData' is undefined. ${recommendation}`, 49 | ); 50 | } 51 | } 52 | ), 53 | Blob: 54 | typeof Blob !== 'undefined' ? Blob : ( 55 | class Blob { 56 | constructor() { 57 | throw new Error( 58 | `file uploads aren't supported in this environment yet as 'Blob' is undefined. ${recommendation}`, 59 | ); 60 | } 61 | } 62 | ), 63 | File: 64 | // @ts-ignore 65 | typeof File !== 'undefined' ? File : ( 66 | class File { 67 | // @ts-ignore 68 | constructor() { 69 | throw new Error( 70 | `file uploads aren't supported in this environment yet as 'File' is undefined. ${recommendation}`, 71 | ); 72 | } 73 | } 74 | ), 75 | ReadableStream: 76 | // @ts-ignore 77 | typeof ReadableStream !== 'undefined' ? ReadableStream : ( 78 | class ReadableStream { 79 | // @ts-ignore 80 | constructor() { 81 | throw new Error( 82 | `streaming isn't supported in this environment yet as 'ReadableStream' is undefined. ${recommendation}`, 83 | ); 84 | } 85 | } 86 | ), 87 | getMultipartRequestOptions: async >( 88 | // @ts-ignore 89 | form: FormData, 90 | opts: RequestOptions, 91 | ): Promise> => ({ 92 | ...opts, 93 | body: new MultipartBody(form) as any, 94 | }), 95 | getDefaultAgent: (url: string) => undefined, 96 | fileFromPath: () => { 97 | throw new Error( 98 | 'The `fileFromPath` function is only supported in Node. See the README for more details: https://www.github.com/Cerebras/cerebras-cloud-sdk-node#file-uploads', 99 | ); 100 | }, 101 | isFsReadStream: (value: any) => false, 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cerebras/cerebras_cloud_sdk", 3 | "version": "1.59.0", 4 | "description": "The official TypeScript library for the Cerebras API", 5 | "author": "Cerebras ", 6 | "types": "dist/index.d.ts", 7 | "main": "dist/index.js", 8 | "type": "commonjs", 9 | "repository": "github:Cerebras/cerebras-cloud-sdk-node", 10 | "license": "Apache-2.0", 11 | "packageManager": "yarn@1.22.22", 12 | "files": [ 13 | "**/*" 14 | ], 15 | "private": false, 16 | "publishConfig": { 17 | "access": "public" 18 | }, 19 | "scripts": { 20 | "test": "./scripts/test", 21 | "build": "./scripts/build", 22 | "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", 23 | "format": "prettier --write --cache --cache-strategy metadata . !dist", 24 | "prepare": "if ./scripts/utils/check-is-in-git-install.sh; then ./scripts/build && ./scripts/utils/git-swap.sh; fi", 25 | "tsn": "ts-node -r tsconfig-paths/register", 26 | "lint": "./scripts/lint", 27 | "fix": "./scripts/format" 28 | }, 29 | "dependencies": { 30 | "@types/node": "^18.11.18", 31 | "@types/node-fetch": "^2.6.4", 32 | "abort-controller": "^3.0.0", 33 | "agentkeepalive": "^4.2.1", 34 | "form-data-encoder": "1.7.2", 35 | "formdata-node": "^4.3.2", 36 | "node-fetch": "^2.6.7" 37 | }, 38 | "devDependencies": { 39 | "@swc/core": "^1.3.102", 40 | "@swc/jest": "^0.2.29", 41 | "@types/jest": "^29.4.0", 42 | "@typescript-eslint/eslint-plugin": "^6.7.0", 43 | "@typescript-eslint/parser": "^6.7.0", 44 | "eslint": "^8.49.0", 45 | "eslint-plugin-prettier": "^5.0.1", 46 | "eslint-plugin-unused-imports": "^3.0.0", 47 | "iconv-lite": "^0.6.3", 48 | "jest": "^29.4.0", 49 | "prettier": "^3.0.0", 50 | "ts-jest": "^29.1.0", 51 | "ts-node": "^10.5.0", 52 | "tsc-multi": "^1.1.0", 53 | "tsconfig-paths": "^4.0.0", 54 | "typescript": "^4.8.2" 55 | }, 56 | "sideEffects": [ 57 | "./_shims/index.js", 58 | "./_shims/index.mjs", 59 | "./shims/node.js", 60 | "./shims/node.mjs", 61 | "./shims/web.js", 62 | "./shims/web.mjs" 63 | ], 64 | "exports": { 65 | "./_shims/auto/*": { 66 | "deno": { 67 | "types": "./dist/_shims/auto/*.d.ts", 68 | "require": "./dist/_shims/auto/*.js", 69 | "default": "./dist/_shims/auto/*.mjs" 70 | }, 71 | "bun": { 72 | "types": "./dist/_shims/auto/*.d.ts", 73 | "require": "./dist/_shims/auto/*-bun.js", 74 | "default": "./dist/_shims/auto/*-bun.mjs" 75 | }, 76 | "browser": { 77 | "types": "./dist/_shims/auto/*.d.ts", 78 | "require": "./dist/_shims/auto/*.js", 79 | "default": "./dist/_shims/auto/*.mjs" 80 | }, 81 | "worker": { 82 | "types": "./dist/_shims/auto/*.d.ts", 83 | "require": "./dist/_shims/auto/*.js", 84 | "default": "./dist/_shims/auto/*.mjs" 85 | }, 86 | "workerd": { 87 | "types": "./dist/_shims/auto/*.d.ts", 88 | "require": "./dist/_shims/auto/*.js", 89 | "default": "./dist/_shims/auto/*.mjs" 90 | }, 91 | "node": { 92 | "types": "./dist/_shims/auto/*-node.d.ts", 93 | "require": "./dist/_shims/auto/*-node.js", 94 | "default": "./dist/_shims/auto/*-node.mjs" 95 | }, 96 | "types": "./dist/_shims/auto/*.d.ts", 97 | "require": "./dist/_shims/auto/*.js", 98 | "default": "./dist/_shims/auto/*.mjs" 99 | }, 100 | ".": { 101 | "require": { 102 | "types": "./dist/index.d.ts", 103 | "default": "./dist/index.js" 104 | }, 105 | "types": "./dist/index.d.mts", 106 | "default": "./dist/index.mjs" 107 | }, 108 | "./*.mjs": { 109 | "types": "./dist/*.d.ts", 110 | "default": "./dist/*.mjs" 111 | }, 112 | "./*.js": { 113 | "types": "./dist/*.d.ts", 114 | "default": "./dist/*.js" 115 | }, 116 | "./*": { 117 | "types": "./dist/*.d.ts", 118 | "require": "./dist/*.js", 119 | "default": "./dist/*.mjs" 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/error.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { castToError, Headers } from './core'; 4 | 5 | export class CerebrasError extends Error {} 6 | 7 | export class APIError< 8 | TStatus extends number | undefined = number | undefined, 9 | THeaders extends Headers | undefined = Headers | undefined, 10 | TError extends Object | undefined = Object | undefined, 11 | > extends CerebrasError { 12 | /** HTTP status for the response that caused the error */ 13 | readonly status: TStatus; 14 | /** HTTP headers for the response that caused the error */ 15 | readonly headers: THeaders; 16 | /** JSON body of the response that caused the error */ 17 | readonly error: TError; 18 | 19 | constructor(status: TStatus, error: TError, message: string | undefined, headers: THeaders) { 20 | super(`${APIError.makeMessage(status, error, message)}`); 21 | this.status = status; 22 | this.headers = headers; 23 | this.error = error; 24 | } 25 | 26 | private static makeMessage(status: number | undefined, error: any, message: string | undefined) { 27 | const msg = 28 | error?.message ? 29 | typeof error.message === 'string' ? 30 | error.message 31 | : JSON.stringify(error.message) 32 | : error ? JSON.stringify(error) 33 | : message; 34 | 35 | if (status && msg) { 36 | return `${status} ${msg}`; 37 | } 38 | if (status) { 39 | return `${status} status code (no body)`; 40 | } 41 | if (msg) { 42 | return msg; 43 | } 44 | return '(no status code or body)'; 45 | } 46 | 47 | static generate( 48 | status: number | undefined, 49 | errorResponse: Object | undefined, 50 | message: string | undefined, 51 | headers: Headers | undefined, 52 | ): APIError { 53 | if (status === undefined || headers === undefined) { 54 | return new APIConnectionError({ message, cause: castToError(errorResponse) }); 55 | } 56 | 57 | const error = errorResponse as Record; 58 | 59 | if (status === 400) { 60 | return new BadRequestError(status, error, message, headers); 61 | } 62 | 63 | if (status === 401) { 64 | return new AuthenticationError(status, error, message, headers); 65 | } 66 | 67 | if (status === 403) { 68 | return new PermissionDeniedError(status, error, message, headers); 69 | } 70 | 71 | if (status === 404) { 72 | return new NotFoundError(status, error, message, headers); 73 | } 74 | 75 | if (status === 409) { 76 | return new ConflictError(status, error, message, headers); 77 | } 78 | 79 | if (status === 422) { 80 | return new UnprocessableEntityError(status, error, message, headers); 81 | } 82 | 83 | if (status === 429) { 84 | return new RateLimitError(status, error, message, headers); 85 | } 86 | 87 | if (status >= 500) { 88 | return new InternalServerError(status, error, message, headers); 89 | } 90 | 91 | return new APIError(status, error, message, headers); 92 | } 93 | } 94 | 95 | export class APIUserAbortError extends APIError { 96 | constructor({ message }: { message?: string } = {}) { 97 | super(undefined, undefined, message || 'Request was aborted.', undefined); 98 | } 99 | } 100 | 101 | export class APIConnectionError extends APIError { 102 | constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) { 103 | super(undefined, undefined, message || 'Connection error.', undefined); 104 | // in some environments the 'cause' property is already declared 105 | // @ts-ignore 106 | if (cause) this.cause = cause; 107 | } 108 | } 109 | 110 | export class APIConnectionTimeoutError extends APIConnectionError { 111 | constructor({ message }: { message?: string } = {}) { 112 | super({ message: message ?? 'Request timed out.' }); 113 | } 114 | } 115 | 116 | export class BadRequestError extends APIError<400, Headers> {} 117 | 118 | export class AuthenticationError extends APIError<401, Headers> {} 119 | 120 | export class PermissionDeniedError extends APIError<403, Headers> {} 121 | 122 | export class NotFoundError extends APIError<404, Headers> {} 123 | 124 | export class ConflictError extends APIError<409, Headers> {} 125 | 126 | export class UnprocessableEntityError extends APIError<422, Headers> {} 127 | 128 | export class RateLimitError extends APIError<429, Headers> {} 129 | 130 | export class InternalServerError extends APIError {} 131 | -------------------------------------------------------------------------------- /scripts/utils/postprocess-files.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { parse } = require('@typescript-eslint/parser'); 4 | 5 | const pkgImportPath = process.env['PKG_IMPORT_PATH'] ?? '@cerebras/cerebras_cloud_sdk/'; 6 | 7 | const distDir = 8 | process.env['DIST_PATH'] ? 9 | path.resolve(process.env['DIST_PATH']) 10 | : path.resolve(__dirname, '..', '..', 'dist'); 11 | const distSrcDir = path.join(distDir, 'src'); 12 | 13 | /** 14 | * Quick and dirty AST traversal 15 | */ 16 | function traverse(node, visitor) { 17 | if (!node || typeof node.type !== 'string') return; 18 | visitor.node?.(node); 19 | visitor[node.type]?.(node); 20 | for (const key in node) { 21 | const value = node[key]; 22 | if (Array.isArray(value)) { 23 | for (const elem of value) traverse(elem, visitor); 24 | } else if (value instanceof Object) { 25 | traverse(value, visitor); 26 | } 27 | } 28 | } 29 | 30 | /** 31 | * Helper method for replacing arbitrary ranges of text in input code. 32 | * 33 | * The `replacer` is a function that will be called with a mini-api. For example: 34 | * 35 | * replaceRanges('foobar', ({ replace }) => replace([0, 3], 'baz')) // 'bazbar' 36 | * 37 | * The replaced ranges must not be overlapping. 38 | */ 39 | function replaceRanges(code, replacer) { 40 | const replacements = []; 41 | replacer({ replace: (range, replacement) => replacements.push({ range, replacement }) }); 42 | 43 | if (!replacements.length) return code; 44 | replacements.sort((a, b) => a.range[0] - b.range[0]); 45 | const overlapIndex = replacements.findIndex( 46 | (r, index) => index > 0 && replacements[index - 1].range[1] > r.range[0], 47 | ); 48 | if (overlapIndex >= 0) { 49 | throw new Error( 50 | `replacements overlap: ${JSON.stringify(replacements[overlapIndex - 1])} and ${JSON.stringify( 51 | replacements[overlapIndex], 52 | )}`, 53 | ); 54 | } 55 | 56 | const parts = []; 57 | let end = 0; 58 | for (const { 59 | range: [from, to], 60 | replacement, 61 | } of replacements) { 62 | if (from > end) parts.push(code.substring(end, from)); 63 | parts.push(replacement); 64 | end = to; 65 | } 66 | if (end < code.length) parts.push(code.substring(end)); 67 | return parts.join(''); 68 | } 69 | 70 | /** 71 | * Like calling .map(), where the iteratee is called on the path in every import or export from statement. 72 | * @returns the transformed code 73 | */ 74 | function mapModulePaths(code, iteratee) { 75 | const ast = parse(code, { range: true }); 76 | return replaceRanges(code, ({ replace }) => 77 | traverse(ast, { 78 | node(node) { 79 | switch (node.type) { 80 | case 'ImportDeclaration': 81 | case 'ExportNamedDeclaration': 82 | case 'ExportAllDeclaration': 83 | case 'ImportExpression': 84 | if (node.source) { 85 | const { range, value } = node.source; 86 | const transformed = iteratee(value); 87 | if (transformed !== value) { 88 | replace(range, JSON.stringify(transformed)); 89 | } 90 | } 91 | } 92 | }, 93 | }), 94 | ); 95 | } 96 | 97 | async function* walk(dir) { 98 | for await (const d of await fs.promises.opendir(dir)) { 99 | const entry = path.join(dir, d.name); 100 | if (d.isDirectory()) yield* walk(entry); 101 | else if (d.isFile()) yield entry; 102 | } 103 | } 104 | 105 | async function postprocess() { 106 | for await (const file of walk(path.resolve(__dirname, '..', '..', 'dist'))) { 107 | if (!/\.([cm]?js|(\.d)?[cm]?ts)$/.test(file)) continue; 108 | 109 | const code = await fs.promises.readFile(file, 'utf8'); 110 | 111 | let transformed = mapModulePaths(code, (importPath) => { 112 | if (file.startsWith(distSrcDir)) { 113 | if (importPath.startsWith(pkgImportPath)) { 114 | // convert self-references in dist/src to relative paths 115 | let relativePath = path.relative( 116 | path.dirname(file), 117 | path.join(distSrcDir, importPath.substring(pkgImportPath.length)), 118 | ); 119 | if (!relativePath.startsWith('.')) relativePath = `./${relativePath}`; 120 | return relativePath; 121 | } 122 | return importPath; 123 | } 124 | if (importPath.startsWith('.')) { 125 | // add explicit file extensions to relative imports 126 | const { dir, name } = path.parse(importPath); 127 | const ext = /\.mjs$/.test(file) ? '.mjs' : '.js'; 128 | return `${dir}/${name}${ext}`; 129 | } 130 | return importPath; 131 | }); 132 | 133 | if (file.startsWith(distSrcDir) && !file.endsWith('_shims/index.d.ts')) { 134 | // strip out `unknown extends Foo ? never :` shim guards in dist/src 135 | // to prevent errors from appearing in Go To Source 136 | transformed = transformed.replace( 137 | new RegExp('unknown extends (typeof )?\\S+ \\? \\S+ :\\s*'.replace(/\s+/, '\\s+'), 'gm'), 138 | // replace with same number of characters to avoid breaking source maps 139 | (match) => ' '.repeat(match.length), 140 | ); 141 | } 142 | 143 | if (file.endsWith('.d.ts')) { 144 | // work around bad tsc behavior 145 | // if we have `import { type Readable } from '@cerebras/cerebras_cloud_sdk/_shims/index'`, 146 | // tsc sometimes replaces `Readable` with `import("stream").Readable` inline 147 | // in the output .d.ts 148 | transformed = transformed.replace(/import\("stream"\).Readable/g, 'Readable'); 149 | } 150 | 151 | // strip out lib="dom" and types="node" references; these are needed at build time, 152 | // but would pollute the user's TS environment 153 | transformed = transformed.replace( 154 | /^ *\/\/\/ * ' '.repeat(match.length - 1) + '\n', 157 | ); 158 | 159 | if (transformed !== code) { 160 | await fs.promises.writeFile(file, transformed, 'utf8'); 161 | console.error(`wrote ${path.relative(process.cwd(), file)}`); 162 | } 163 | } 164 | } 165 | postprocess(); 166 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { type Agent } from './_shims/index'; 4 | import * as Core from './core'; 5 | import * as Errors from './error'; 6 | import * as Uploads from './uploads'; 7 | import * as API from './resources/index'; 8 | import { Completion, CompletionCreateParams, Completions } from './resources/completions'; 9 | import { 10 | ModelListParams, 11 | ModelListResponse, 12 | ModelRetrieveParams, 13 | ModelRetrieveResponse, 14 | Models, 15 | } from './resources/models'; 16 | import { Chat } from './resources/chat/chat'; 17 | import { ChatCompletion, ChatCompletionCreateParams } from './resources/chat/completions'; 18 | 19 | export interface ClientOptions { 20 | /** 21 | * Defaults to process.env['CEREBRAS_API_KEY']. 22 | */ 23 | apiKey?: string | undefined; 24 | 25 | /** 26 | * Override the default base URL for the API, e.g., "https://api.example.com/v2/" 27 | * 28 | * Defaults to process.env['CEREBRAS_BASE_URL']. 29 | */ 30 | baseURL?: string | null | undefined; 31 | 32 | /** 33 | * The maximum amount of time (in milliseconds) that the client should wait for a response 34 | * from the server before timing out a single request. 35 | * 36 | * Note that request timeouts are retried by default, so in a worst-case scenario you may wait 37 | * much longer than this timeout before the promise succeeds or fails. 38 | * 39 | * @unit milliseconds 40 | */ 41 | timeout?: number | undefined; 42 | 43 | /** 44 | * An HTTP agent used to manage HTTP(S) connections. 45 | * 46 | * If not provided, an agent will be constructed by default in the Node.js environment, 47 | * otherwise no agent is used. 48 | */ 49 | httpAgent?: Agent | undefined; 50 | 51 | /** 52 | * Specify a custom `fetch` function implementation. 53 | * 54 | * If not provided, we use `node-fetch` on Node.js and otherwise expect that `fetch` is 55 | * defined globally. 56 | */ 57 | fetch?: Core.Fetch | undefined; 58 | 59 | /** 60 | * The maximum number of times that the client will retry a request in case of a 61 | * temporary failure, like a network error or a 5XX error from the server. 62 | * 63 | * @default 2 64 | */ 65 | maxRetries?: number | undefined; 66 | 67 | /** 68 | * Default headers to include with every request to the API. 69 | * 70 | * These can be removed in individual requests by explicitly setting the 71 | * header to `undefined` or `null` in request options. 72 | */ 73 | defaultHeaders?: Core.Headers | undefined; 74 | 75 | /** 76 | * Default query parameters to include with every request to the API. 77 | * 78 | * These can be removed in individual requests by explicitly setting the 79 | * param to `undefined` in request options. 80 | */ 81 | defaultQuery?: Core.DefaultQuery | undefined; 82 | 83 | /** 84 | * When true, a request is sent to `/models` in the constructor to open a TCP 85 | * connection with the API server. This way, the first "real" request will have 86 | * less latency since it can reuse the already existing socket connection. 87 | * 88 | * @default true 89 | */ 90 | warmTCPConnection?: boolean; 91 | } 92 | 93 | /** 94 | * API Client for interfacing with the Cerebras API. 95 | */ 96 | export class Cerebras extends Core.APIClient { 97 | apiKey: string; 98 | 99 | private _options: ClientOptions; 100 | 101 | /** 102 | * API Client for interfacing with the Cerebras API. 103 | * 104 | * @param {string | undefined} [opts.apiKey=process.env['CEREBRAS_API_KEY'] ?? undefined] 105 | * @param {string} [opts.baseURL=process.env['CEREBRAS_BASE_URL'] ?? https://api.cerebras.ai] - Override the default base URL for the API. 106 | * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. 107 | * @param {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections. 108 | * @param {Core.Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. 109 | * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. 110 | * @param {Core.Headers} opts.defaultHeaders - Default headers to include with every request to the API. 111 | * @param {Core.DefaultQuery} opts.defaultQuery - Default query parameters to include with every request to the API. 112 | * @param {boolean | undefined} opts.warmTCPConnection - Whether to warm TCP connection in the constructor. 113 | */ 114 | constructor({ 115 | baseURL = Core.readEnv('CEREBRAS_BASE_URL'), 116 | apiKey = Core.readEnv('CEREBRAS_API_KEY'), 117 | warmTCPConnection = true, 118 | ...opts 119 | }: ClientOptions = {}) { 120 | if (apiKey === undefined) { 121 | throw new Errors.CerebrasError( 122 | "The CEREBRAS_API_KEY environment variable is missing or empty; either provide it, or instantiate the Cerebras client with an apiKey option, like new Cerebras({ apiKey: 'My API Key' }).", 123 | ); 124 | } 125 | 126 | const options: ClientOptions = { 127 | apiKey, 128 | ...opts, 129 | baseURL: baseURL || `https://api.cerebras.ai`, 130 | }; 131 | 132 | super({ 133 | baseURL: options.baseURL!, 134 | baseURLOverridden: baseURL ? baseURL !== 'https://api.cerebras.ai' : false, 135 | timeout: options.timeout ?? 60000 /* 1 minute */, 136 | httpAgent: options.httpAgent, 137 | maxRetries: options.maxRetries, 138 | fetch: options.fetch, 139 | }); 140 | 141 | this._options = options; 142 | 143 | this.apiKey = apiKey; 144 | 145 | if (warmTCPConnection) { 146 | // Since this runs async, it's possible for DEBUG messages to 147 | // be printed after test ends, which will cause a warming. 148 | // 149 | // Doesn't seem to be an easy way to block until this promise is fulfilled. 150 | (async () => { 151 | try { 152 | await this.get('/v1/tcp_warming', { 153 | timeout: 1000, 154 | maxRetries: 0, 155 | }); 156 | } catch (e) { 157 | Core.debug(`TCP Warming had exception: ${e}`); 158 | } 159 | })(); 160 | } 161 | } 162 | 163 | chat: API.Chat = new API.Chat(this); 164 | completions: API.Completions = new API.Completions(this); 165 | models: API.Models = new API.Models(this); 166 | 167 | /** 168 | * Check whether the base URL is set to its default. 169 | */ 170 | #baseURLOverridden(): boolean { 171 | return this.baseURL !== 'https://api.cerebras.ai'; 172 | } 173 | 174 | protected override defaultQuery(): Core.DefaultQuery | undefined { 175 | return this._options.defaultQuery; 176 | } 177 | 178 | protected override defaultHeaders(opts: Core.FinalRequestOptions): Core.Headers { 179 | return { 180 | ...super.defaultHeaders(opts), 181 | ...this._options.defaultHeaders, 182 | }; 183 | } 184 | 185 | protected override authHeaders(opts: Core.FinalRequestOptions): Core.Headers { 186 | return { Authorization: `Bearer ${this.apiKey}` }; 187 | } 188 | 189 | static Cerebras = this; 190 | static DEFAULT_TIMEOUT = 60000; // 1 minute 191 | 192 | static CerebrasError = Errors.CerebrasError; 193 | static APIError = Errors.APIError; 194 | static APIConnectionError = Errors.APIConnectionError; 195 | static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; 196 | static APIUserAbortError = Errors.APIUserAbortError; 197 | static NotFoundError = Errors.NotFoundError; 198 | static ConflictError = Errors.ConflictError; 199 | static RateLimitError = Errors.RateLimitError; 200 | static BadRequestError = Errors.BadRequestError; 201 | static AuthenticationError = Errors.AuthenticationError; 202 | static InternalServerError = Errors.InternalServerError; 203 | static PermissionDeniedError = Errors.PermissionDeniedError; 204 | static UnprocessableEntityError = Errors.UnprocessableEntityError; 205 | 206 | static toFile = Uploads.toFile; 207 | static fileFromPath = Uploads.fileFromPath; 208 | } 209 | 210 | Cerebras.Chat = Chat; 211 | Cerebras.Completions = Completions; 212 | Cerebras.Models = Models; 213 | 214 | export declare namespace Cerebras { 215 | export type RequestOptions = Core.RequestOptions; 216 | 217 | export { 218 | Chat as Chat, 219 | type ChatCompletion as ChatCompletion, 220 | type ChatCompletionCreateParams as ChatCompletionCreateParams, 221 | }; 222 | 223 | export { 224 | Completions as Completions, 225 | type Completion as Completion, 226 | type CompletionCreateParams as CompletionCreateParams, 227 | }; 228 | 229 | export { 230 | Models as Models, 231 | type ModelRetrieveResponse as ModelRetrieveResponse, 232 | type ModelListResponse as ModelListResponse, 233 | type ModelRetrieveParams as ModelRetrieveParams, 234 | type ModelListParams as ModelListParams, 235 | }; 236 | } 237 | 238 | export { toFile, fileFromPath } from './uploads'; 239 | export { 240 | CerebrasError, 241 | APIError, 242 | APIConnectionError, 243 | APIConnectionTimeoutError, 244 | APIUserAbortError, 245 | NotFoundError, 246 | ConflictError, 247 | RateLimitError, 248 | BadRequestError, 249 | AuthenticationError, 250 | InternalServerError, 251 | PermissionDeniedError, 252 | UnprocessableEntityError, 253 | } from './error'; 254 | 255 | export default Cerebras; 256 | -------------------------------------------------------------------------------- /src/uploads.ts: -------------------------------------------------------------------------------- 1 | import { type RequestOptions } from './core'; 2 | import { 3 | FormData, 4 | File, 5 | type Blob, 6 | type FilePropertyBag, 7 | getMultipartRequestOptions, 8 | type FsReadStream, 9 | isFsReadStream, 10 | } from './_shims/index'; 11 | import { MultipartBody } from './_shims/MultipartBody'; 12 | export { fileFromPath } from './_shims/index'; 13 | 14 | type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | Uint8Array | DataView; 15 | export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | Uint8Array | DataView; 16 | 17 | /** 18 | * Typically, this is a native "File" class. 19 | * 20 | * We provide the {@link toFile} utility to convert a variety of objects 21 | * into the File class. 22 | * 23 | * For convenience, you can also pass a fetch Response, or in Node, 24 | * the result of fs.createReadStream(). 25 | */ 26 | export type Uploadable = FileLike | ResponseLike | FsReadStream; 27 | 28 | /** 29 | * Intended to match web.Blob, node.Blob, node-fetch.Blob, etc. 30 | */ 31 | export interface BlobLike { 32 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ 33 | readonly size: number; 34 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ 35 | readonly type: string; 36 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ 37 | text(): Promise; 38 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ 39 | slice(start?: number, end?: number): BlobLike; 40 | // unfortunately @types/node-fetch@^2.6.4 doesn't type the arrayBuffer method 41 | } 42 | 43 | /** 44 | * Intended to match web.File, node.File, node-fetch.File, etc. 45 | */ 46 | export interface FileLike extends BlobLike { 47 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ 48 | readonly lastModified: number; 49 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ 50 | readonly name: string; 51 | } 52 | 53 | /** 54 | * Intended to match web.Response, node.Response, node-fetch.Response, etc. 55 | */ 56 | export interface ResponseLike { 57 | url: string; 58 | blob(): Promise; 59 | } 60 | 61 | export const isResponseLike = (value: any): value is ResponseLike => 62 | value != null && 63 | typeof value === 'object' && 64 | typeof value.url === 'string' && 65 | typeof value.blob === 'function'; 66 | 67 | export const isFileLike = (value: any): value is FileLike => 68 | value != null && 69 | typeof value === 'object' && 70 | typeof value.name === 'string' && 71 | typeof value.lastModified === 'number' && 72 | isBlobLike(value); 73 | 74 | /** 75 | * The BlobLike type omits arrayBuffer() because @types/node-fetch@^2.6.4 lacks it; but this check 76 | * adds the arrayBuffer() method type because it is available and used at runtime 77 | */ 78 | export const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise } => 79 | value != null && 80 | typeof value === 'object' && 81 | typeof value.size === 'number' && 82 | typeof value.type === 'string' && 83 | typeof value.text === 'function' && 84 | typeof value.slice === 'function' && 85 | typeof value.arrayBuffer === 'function'; 86 | 87 | export const isUploadable = (value: any): value is Uploadable => { 88 | return isFileLike(value) || isResponseLike(value) || isFsReadStream(value); 89 | }; 90 | 91 | export type ToFileInput = Uploadable | Exclude | AsyncIterable; 92 | 93 | /** 94 | * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats 95 | * @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s 96 | * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible 97 | * @param {Object=} options additional properties 98 | * @param {string=} options.type the MIME type of the content 99 | * @param {number=} options.lastModified the last modified timestamp 100 | * @returns a {@link File} with the given properties 101 | */ 102 | export async function toFile( 103 | value: ToFileInput | PromiseLike, 104 | name?: string | null | undefined, 105 | options?: FilePropertyBag | undefined, 106 | ): Promise { 107 | // If it's a promise, resolve it. 108 | value = await value; 109 | 110 | // If we've been given a `File` we don't need to do anything 111 | if (isFileLike(value)) { 112 | return value; 113 | } 114 | 115 | if (isResponseLike(value)) { 116 | const blob = await value.blob(); 117 | name ||= new URL(value.url).pathname.split(/[\\/]/).pop() ?? 'unknown_file'; 118 | 119 | // we need to convert the `Blob` into an array buffer because the `Blob` class 120 | // that `node-fetch` defines is incompatible with the web standard which results 121 | // in `new File` interpreting it as a string instead of binary data. 122 | const data = isBlobLike(blob) ? [(await blob.arrayBuffer()) as any] : [blob]; 123 | 124 | return new File(data, name, options); 125 | } 126 | 127 | const bits = await getBytes(value); 128 | 129 | name ||= getName(value) ?? 'unknown_file'; 130 | 131 | if (!options?.type) { 132 | const type = (bits[0] as any)?.type; 133 | if (typeof type === 'string') { 134 | options = { ...options, type }; 135 | } 136 | } 137 | 138 | return new File(bits, name, options); 139 | } 140 | 141 | async function getBytes(value: ToFileInput): Promise> { 142 | let parts: Array = []; 143 | if ( 144 | typeof value === 'string' || 145 | ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc. 146 | value instanceof ArrayBuffer 147 | ) { 148 | parts.push(value); 149 | } else if (isBlobLike(value)) { 150 | parts.push(await value.arrayBuffer()); 151 | } else if ( 152 | isAsyncIterableIterator(value) // includes Readable, ReadableStream, etc. 153 | ) { 154 | for await (const chunk of value) { 155 | parts.push(chunk as BlobPart); // TODO, consider validating? 156 | } 157 | } else { 158 | throw new Error( 159 | `Unexpected data type: ${typeof value}; constructor: ${value?.constructor 160 | ?.name}; props: ${propsForError(value)}`, 161 | ); 162 | } 163 | 164 | return parts; 165 | } 166 | 167 | function propsForError(value: any): string { 168 | const props = Object.getOwnPropertyNames(value); 169 | return `[${props.map((p) => `"${p}"`).join(', ')}]`; 170 | } 171 | 172 | function getName(value: any): string | undefined { 173 | return ( 174 | getStringFromMaybeBuffer(value.name) || 175 | getStringFromMaybeBuffer(value.filename) || 176 | // For fs.ReadStream 177 | getStringFromMaybeBuffer(value.path)?.split(/[\\/]/).pop() 178 | ); 179 | } 180 | 181 | const getStringFromMaybeBuffer = (x: string | Buffer | unknown): string | undefined => { 182 | if (typeof x === 'string') return x; 183 | if (typeof Buffer !== 'undefined' && x instanceof Buffer) return String(x); 184 | return undefined; 185 | }; 186 | 187 | const isAsyncIterableIterator = (value: any): value is AsyncIterableIterator => 188 | value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function'; 189 | 190 | export const isMultipartBody = (body: any): body is MultipartBody => 191 | body && typeof body === 'object' && body.body && body[Symbol.toStringTag] === 'MultipartBody'; 192 | 193 | /** 194 | * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value. 195 | * Otherwise returns the request as is. 196 | */ 197 | export const maybeMultipartFormRequestOptions = async >( 198 | opts: RequestOptions, 199 | ): Promise> => { 200 | if (!hasUploadableValue(opts.body)) return opts; 201 | 202 | const form = await createForm(opts.body); 203 | return getMultipartRequestOptions(form, opts); 204 | }; 205 | 206 | export const multipartFormRequestOptions = async >( 207 | opts: RequestOptions, 208 | ): Promise> => { 209 | const form = await createForm(opts.body); 210 | return getMultipartRequestOptions(form, opts); 211 | }; 212 | 213 | export const createForm = async >(body: T | undefined): Promise => { 214 | const form = new FormData(); 215 | await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value))); 216 | return form; 217 | }; 218 | 219 | const hasUploadableValue = (value: unknown): boolean => { 220 | if (isUploadable(value)) return true; 221 | if (Array.isArray(value)) return value.some(hasUploadableValue); 222 | if (value && typeof value === 'object') { 223 | for (const k in value) { 224 | if (hasUploadableValue((value as any)[k])) return true; 225 | } 226 | } 227 | return false; 228 | }; 229 | 230 | const addFormValue = async (form: FormData, key: string, value: unknown): Promise => { 231 | if (value === undefined) return; 232 | if (value == null) { 233 | throw new TypeError( 234 | `Received null for "${key}"; to pass null in FormData, you must use the string 'null'`, 235 | ); 236 | } 237 | 238 | // TODO: make nested formats configurable 239 | if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { 240 | form.append(key, String(value)); 241 | } else if (isUploadable(value)) { 242 | const file = await toFile(value); 243 | form.append(key, file as File); 244 | } else if (Array.isArray(value)) { 245 | await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry))); 246 | } else if (typeof value === 'object') { 247 | await Promise.all( 248 | Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)), 249 | ); 250 | } else { 251 | throw new TypeError( 252 | `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`, 253 | ); 254 | } 255 | }; 256 | -------------------------------------------------------------------------------- /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 2025 Cerebras 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 | -------------------------------------------------------------------------------- /src/resources/completions.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../resource'; 4 | import * as Core from '../core'; 5 | import { Stream } from '../streaming'; 6 | 7 | export class Completions extends APIResource { 8 | /** 9 | * Completions 10 | * 11 | * @example 12 | * ```ts 13 | * const completion = await client.completions.create({ 14 | * model: 'model', 15 | * prompt: 'string', 16 | * }); 17 | * ``` 18 | */ 19 | create( 20 | params: CompletionCreateParamsNonStreaming, 21 | options?: Core.RequestOptions, 22 | ): Core.APIPromise; 23 | create( 24 | params: CompletionCreateParamsStreaming, 25 | options?: Core.RequestOptions, 26 | ): Core.APIPromise>; 27 | create( 28 | params: CompletionCreateParamsBase, 29 | options?: Core.RequestOptions, 30 | ): Core.APIPromise | Completion>; 31 | create( 32 | params: CompletionCreateParams, 33 | options?: Core.RequestOptions, 34 | ): Core.APIPromise | Core.APIPromise> { 35 | const { 'CF-RAY': cfRay, 'X-Amz-Cf-Id': xAmzCfId, 'X-delay-time': xDelayTime, ...body } = params; 36 | return this._client.post('/v1/completions', { 37 | body, 38 | ...options, 39 | stream: body.stream ?? false, 40 | headers: { 41 | ...(cfRay != null ? { 'CF-RAY': cfRay } : undefined), 42 | ...(xAmzCfId != null ? { 'X-Amz-Cf-Id': xAmzCfId } : undefined), 43 | ...(xDelayTime?.toString() != null ? { 'X-delay-time': xDelayTime?.toString() } : undefined), 44 | ...options?.headers, 45 | }, 46 | }) as Core.APIPromise | Core.APIPromise>; 47 | } 48 | } 49 | 50 | export type Completion = 51 | | Completion.CompletionResponse 52 | | Completion.CompletionChunkResponse 53 | | Completion.ErrorChunkResponse; 54 | 55 | export namespace Completion { 56 | export interface CompletionResponse { 57 | id: string; 58 | 59 | choices: Array; 60 | 61 | created: number; 62 | 63 | model: string; 64 | 65 | object: 'text_completion'; 66 | 67 | system_fingerprint: string; 68 | 69 | time_info?: CompletionResponse.TimeInfo | null; 70 | 71 | usage?: CompletionResponse.Usage | null; 72 | 73 | [k: string]: unknown; 74 | } 75 | 76 | export namespace CompletionResponse { 77 | export interface Choice { 78 | index: number; 79 | 80 | finish_reason?: 'stop' | 'length' | 'content_filter' | null; 81 | 82 | logprobs?: Choice.Logprobs | null; 83 | 84 | text?: string | null; 85 | 86 | tokens?: Array | null; 87 | 88 | [k: string]: unknown; 89 | } 90 | 91 | export namespace Choice { 92 | export interface Logprobs { 93 | text_offset?: Array | null; 94 | 95 | token_logprobs?: Array | null; 96 | 97 | tokens?: Array | null; 98 | 99 | top_logprobs?: Array<{ [key: string]: number }> | null; 100 | 101 | [k: string]: unknown; 102 | } 103 | } 104 | 105 | export interface TimeInfo { 106 | completion_time?: number; 107 | 108 | prompt_time?: number; 109 | 110 | queue_time?: number; 111 | 112 | total_time?: number; 113 | 114 | [k: string]: unknown; 115 | } 116 | 117 | export interface Usage { 118 | completion_tokens?: number; 119 | 120 | completion_tokens_details?: Usage.CompletionTokensDetails | null; 121 | 122 | prompt_tokens?: number; 123 | 124 | prompt_tokens_details?: Usage.PromptTokensDetails | null; 125 | 126 | total_tokens?: number; 127 | 128 | [k: string]: unknown; 129 | } 130 | 131 | export namespace Usage { 132 | export interface CompletionTokensDetails { 133 | accepted_prediction_tokens?: number | null; 134 | 135 | rejected_prediction_tokens?: number | null; 136 | 137 | [k: string]: unknown; 138 | } 139 | 140 | export interface PromptTokensDetails { 141 | cached_tokens?: number; 142 | 143 | [k: string]: unknown; 144 | } 145 | } 146 | } 147 | 148 | export interface CompletionChunkResponse { 149 | id: string; 150 | 151 | created: number; 152 | 153 | model: string; 154 | 155 | object: 'chat.completion.chunk' | 'text_completion'; 156 | 157 | system_fingerprint: string; 158 | 159 | choices?: Array | null; 160 | 161 | service_tier?: string | null; 162 | 163 | time_info?: CompletionChunkResponse.TimeInfo | null; 164 | 165 | usage?: CompletionChunkResponse.Usage | null; 166 | 167 | [k: string]: unknown; 168 | } 169 | 170 | export namespace CompletionChunkResponse { 171 | export interface Choice { 172 | index: number; 173 | 174 | delta?: Choice.Delta | null; 175 | 176 | finish_reason?: 'stop' | 'length' | 'content_filter' | 'tool_calls' | null; 177 | 178 | logprobs?: Choice.Logprobs | null; 179 | 180 | text?: string | null; 181 | 182 | tokens?: Array | null; 183 | 184 | [k: string]: unknown; 185 | } 186 | 187 | export namespace Choice { 188 | export interface Delta { 189 | content?: string | null; 190 | 191 | reasoning?: string | null; 192 | 193 | role?: 'assistant' | 'user' | 'system' | 'tool' | null; 194 | 195 | tokens?: Array | null; 196 | 197 | tool_calls?: Array | null; 198 | 199 | [k: string]: unknown; 200 | } 201 | 202 | export namespace Delta { 203 | /** 204 | * Streaming only. Represents a function call in an assistant tool call. 205 | */ 206 | export interface ToolCall { 207 | /** 208 | * Streaming only. Represents a function in an assistant tool call. 209 | */ 210 | function: ToolCall.Function; 211 | 212 | type: 'function'; 213 | 214 | id?: string | null; 215 | 216 | index?: number | null; 217 | 218 | [k: string]: unknown; 219 | } 220 | 221 | export namespace ToolCall { 222 | /** 223 | * Streaming only. Represents a function in an assistant tool call. 224 | */ 225 | export interface Function { 226 | arguments?: string | null; 227 | 228 | name?: string | null; 229 | 230 | [k: string]: unknown; 231 | } 232 | } 233 | } 234 | 235 | export interface Logprobs { 236 | text_offset?: Array | null; 237 | 238 | token_logprobs?: Array | null; 239 | 240 | tokens?: Array | null; 241 | 242 | top_logprobs?: Array<{ [key: string]: number }> | null; 243 | 244 | [k: string]: unknown; 245 | } 246 | } 247 | 248 | export interface TimeInfo { 249 | completion_time?: number; 250 | 251 | prompt_time?: number; 252 | 253 | queue_time?: number; 254 | 255 | total_time?: number; 256 | 257 | [k: string]: unknown; 258 | } 259 | 260 | export interface Usage { 261 | completion_tokens?: number; 262 | 263 | completion_tokens_details?: Usage.CompletionTokensDetails | null; 264 | 265 | prompt_tokens?: number; 266 | 267 | prompt_tokens_details?: Usage.PromptTokensDetails | null; 268 | 269 | total_tokens?: number; 270 | 271 | [k: string]: unknown; 272 | } 273 | 274 | export namespace Usage { 275 | export interface CompletionTokensDetails { 276 | accepted_prediction_tokens?: number | null; 277 | 278 | rejected_prediction_tokens?: number | null; 279 | 280 | [k: string]: unknown; 281 | } 282 | 283 | export interface PromptTokensDetails { 284 | cached_tokens?: number; 285 | 286 | [k: string]: unknown; 287 | } 288 | } 289 | } 290 | 291 | export interface ErrorChunkResponse { 292 | error: ErrorChunkResponse.Error; 293 | 294 | status_code: number; 295 | 296 | [k: string]: unknown; 297 | } 298 | 299 | export namespace ErrorChunkResponse { 300 | export interface Error { 301 | id?: string | null; 302 | 303 | code?: string | null; 304 | 305 | message?: string | null; 306 | 307 | param?: string | null; 308 | 309 | type?: string | null; 310 | 311 | [k: string]: unknown; 312 | } 313 | } 314 | } 315 | 316 | // This enables us to do matching against the parameter to overload the function and know what the 317 | // return type will be (whether with or without streaming). 318 | export type CompletionCreateParams = CompletionCreateParamsNonStreaming | CompletionCreateParamsStreaming; 319 | 320 | export interface CompletionCreateParamsNonStreaming extends CompletionCreateParamsBase { 321 | stream?: false | null; 322 | } 323 | 324 | export interface CompletionCreateParamsStreaming extends CompletionCreateParamsBase { 325 | stream: true; 326 | } 327 | 328 | export interface CompletionCreateParamsBase { 329 | /** 330 | * Body param: 331 | */ 332 | model: string; 333 | 334 | /** 335 | * Body param: The prompt(s) to generate completions for, encoded as a string, 336 | * array of strings, array of tokens, or array of token arrays. 337 | */ 338 | prompt: string | Array | Array | Array>; 339 | 340 | /** 341 | * Body param: Generates `best_of` completions server-side and returns the "best" 342 | * (the one with the highest log probability per token). Results cannot be 343 | * streamed. When used with `n`, `best_of` controls the number of candidate 344 | * completions and `n` specifies how many to return – `best_of` must be greater 345 | * than `n`. **Note:** Because this parameter generates many completions, it can 346 | * quickly consume your token quota. Use carefully and ensure that you have 347 | * reasonable settings for `max_tokens` and `stop` 348 | */ 349 | best_of?: number | null; 350 | 351 | /** 352 | * Body param: Echo back the prompt in addition to the completion 353 | */ 354 | echo?: boolean | null; 355 | 356 | /** 357 | * Body param: Number between -2.0 and 2.0. Positive values penalize new tokens 358 | * based on their existing frequency in the text so far, decreasing the model's 359 | * likelihood to repeat the same line verbatim. 360 | */ 361 | frequency_penalty?: number | null; 362 | 363 | /** 364 | * Body param: The grammar root used for structured output generation. 365 | */ 366 | grammar_root?: string | null; 367 | 368 | /** 369 | * Body param: Modify the likelihood of specified tokens appearing in the 370 | * completion. 371 | * 372 | * Accepts a JSON object that maps tokens (specified by their token ID in the 373 | * tokenizer) to an associated bias value from -100 to 100. Mathematically, the 374 | * bias is added to the logits generated by the model prior to sampling. The exact 375 | * effect will vary per model, but values between -1 and 1 should decrease or 376 | * increase likelihood of selection; values like -100 or 100 should result in a ban 377 | * or exclusive selection of the relevant token. 378 | */ 379 | logit_bias?: unknown | null; 380 | 381 | /** 382 | * Body param: Include the log probabilities on the logprobs most likely output 383 | * tokens, as well the chosen tokens. For example, if logprobs is 5, the API will 384 | * return a list of the 5 most likely tokens. The API will always return the 385 | * logprob of the sampled token, so there may be up to logprobs+1 elements in the 386 | * response. 387 | */ 388 | logprobs?: number | null; 389 | 390 | /** 391 | * Body param: The maximum number of tokens that can be generated in the chat 392 | * completion. The total length of input tokens and generated tokens is limited by 393 | * the model's context length. 394 | */ 395 | max_tokens?: number | null; 396 | 397 | /** 398 | * Body param: The minimum number of tokens to generate for a completion. If not 399 | * specified or set to 0, the model will generate as many tokens as it deems 400 | * necessary. Setting to -1 sets to max sequence length. 401 | */ 402 | min_tokens?: number | null; 403 | 404 | /** 405 | * Body param: How many chat completion choices to generate for each input message. 406 | * Note that you will be charged based on the number of generated tokens across all 407 | * of the choices. Keep n as 1 to minimize costs. 408 | */ 409 | n?: number | null; 410 | 411 | /** 412 | * Body param: Number between -2.0 and 2.0. Positive values penalize new tokens 413 | * based on whether they appear in the text so far, increasing the model's 414 | * likelihood to talk about new topics. 415 | */ 416 | presence_penalty?: number | null; 417 | 418 | /** 419 | * Body param: Return raw tokens instead of text 420 | */ 421 | return_raw_tokens?: boolean | null; 422 | 423 | /** 424 | * Body param: If specified, our system will make a best effort to sample 425 | * deterministically, such that repeated requests with the same `seed` and 426 | * parameters should return the same result. Determinism is not guaranteed. 427 | */ 428 | seed?: number | null; 429 | 430 | /** 431 | * Body param: Up to 4 sequences where the API will stop generating further tokens. 432 | * The returned text will not contain the stop sequence. 433 | */ 434 | stop?: string | Array | null; 435 | 436 | /** 437 | * Body param: 438 | */ 439 | stream?: boolean | null; 440 | 441 | /** 442 | * Body param: Options for streaming. 443 | */ 444 | stream_options?: CompletionCreateParams.StreamOptions | null; 445 | 446 | /** 447 | * Body param: The suffix that comes after a completion of inserted text. (OpenAI 448 | * feature, not supported) 449 | */ 450 | suffix?: string | null; 451 | 452 | /** 453 | * Body param: What sampling temperature to use, between 0 and 1.5. Higher values 454 | * like 0.8 will make the output more random, while lower values like 0.2 will make 455 | * it more focused and deterministic. We generally recommend altering this or 456 | * `top_p` but not both. 457 | */ 458 | temperature?: number | null; 459 | 460 | /** 461 | * Body param: An alternative to sampling with temperature, called nucleus 462 | * sampling, where the model considers the results of the tokens with top_p 463 | * probability mass. So 0.1 means only the tokens comprising the top 10% 464 | * probability mass are considered. We generally recommend altering this or 465 | * `temperature` but not both. 466 | */ 467 | top_p?: number | null; 468 | 469 | /** 470 | * Body param: A unique identifier representing your end-user, which can help 471 | * Cerebras to monitor and detect abuse. 472 | */ 473 | user?: string | null; 474 | 475 | /** 476 | * Header param: 477 | */ 478 | 'CF-RAY'?: string; 479 | 480 | /** 481 | * Header param: 482 | */ 483 | 'X-Amz-Cf-Id'?: string; 484 | 485 | /** 486 | * Header param: 487 | */ 488 | 'X-delay-time'?: number; 489 | } 490 | 491 | export namespace CompletionCreateParams { 492 | /** 493 | * Options for streaming. 494 | */ 495 | export interface StreamOptions { 496 | include_usage?: boolean | null; 497 | 498 | [k: string]: unknown; 499 | } 500 | } 501 | 502 | export declare namespace Completions { 503 | export { type Completion as Completion, type CompletionCreateParams as CompletionCreateParams }; 504 | } 505 | -------------------------------------------------------------------------------- /src/streaming.ts: -------------------------------------------------------------------------------- 1 | import { ReadableStream, type Response } from './_shims/index'; 2 | import { APIError, CerebrasError } from './error'; 3 | 4 | type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined; 5 | 6 | export type ServerSentEvent = { 7 | event: string | null; 8 | data: string; 9 | raw: string[]; 10 | }; 11 | 12 | export class Stream implements AsyncIterable { 13 | controller: AbortController; 14 | 15 | constructor( 16 | private iterator: () => AsyncIterator, 17 | controller: AbortController, 18 | ) { 19 | this.controller = controller; 20 | } 21 | 22 | static fromSSEResponse(response: Response, controller: AbortController) { 23 | let consumed = false; 24 | 25 | async function* iterator(): AsyncIterator { 26 | if (consumed) { 27 | throw new Error('Cannot iterate over a consumed stream, use `.tee()` to split the stream.'); 28 | } 29 | consumed = true; 30 | let done = false; 31 | try { 32 | for await (const sse of _iterSSEMessages(response, controller)) { 33 | if (done) continue; 34 | 35 | if (sse.data.startsWith('[DONE]')) { 36 | done = true; 37 | continue; 38 | } 39 | 40 | if (sse.event === null) { 41 | let data; 42 | 43 | try { 44 | data = JSON.parse(sse.data); 45 | } catch (e) { 46 | console.error(`Could not parse message into JSON:`, sse.data); 47 | console.error(`From chunk:`, sse.raw); 48 | throw e; 49 | } 50 | 51 | if (data && data.error) { 52 | // Status code of zero is generic error message. 53 | const status = data.status_code || 0; 54 | throw APIError.generate(status, data.error, undefined, undefined); 55 | } 56 | 57 | yield data; 58 | } else { 59 | let data; 60 | try { 61 | data = JSON.parse(sse.data); 62 | } catch (e) { 63 | console.error(`Could not parse message into JSON:`, sse.data); 64 | console.error(`From chunk:`, sse.raw); 65 | throw e; 66 | } 67 | // TODO: Is this where the error should be thrown? 68 | if (sse.event == 'error') { 69 | // Status code of zero is generic error message. 70 | const status = data.status_code || 0; 71 | throw APIError.generate(status, data.error, undefined, undefined); 72 | } 73 | yield { event: sse.event, data: data } as any; 74 | } 75 | } 76 | done = true; 77 | } catch (e) { 78 | // If the user calls `stream.controller.abort()`, we should exit without throwing. 79 | if (e instanceof Error && e.name === 'AbortError') return; 80 | throw e; 81 | } finally { 82 | // If the user `break`s, abort the ongoing request. 83 | if (!done) controller.abort(); 84 | } 85 | } 86 | 87 | return new Stream(iterator, controller); 88 | } 89 | 90 | /** 91 | * Generates a Stream from a newline-separated ReadableStream 92 | * where each item is a JSON value. 93 | */ 94 | static fromReadableStream(readableStream: ReadableStream, controller: AbortController) { 95 | let consumed = false; 96 | 97 | async function* iterLines(): AsyncGenerator { 98 | const lineDecoder = new LineDecoder(); 99 | 100 | const iter = readableStreamAsyncIterable(readableStream); 101 | for await (const chunk of iter) { 102 | for (const line of lineDecoder.decode(chunk)) { 103 | yield line; 104 | } 105 | } 106 | 107 | for (const line of lineDecoder.flush()) { 108 | yield line; 109 | } 110 | } 111 | 112 | async function* iterator(): AsyncIterator { 113 | if (consumed) { 114 | throw new Error('Cannot iterate over a consumed stream, use `.tee()` to split the stream.'); 115 | } 116 | consumed = true; 117 | let done = false; 118 | try { 119 | for await (const line of iterLines()) { 120 | if (done) continue; 121 | if (line) yield JSON.parse(line); 122 | } 123 | done = true; 124 | } catch (e) { 125 | // If the user calls `stream.controller.abort()`, we should exit without throwing. 126 | if (e instanceof Error && e.name === 'AbortError') return; 127 | throw e; 128 | } finally { 129 | // If the user `break`s, abort the ongoing request. 130 | if (!done) controller.abort(); 131 | } 132 | } 133 | 134 | return new Stream(iterator, controller); 135 | } 136 | 137 | [Symbol.asyncIterator](): AsyncIterator { 138 | return this.iterator(); 139 | } 140 | 141 | /** 142 | * Splits the stream into two streams which can be 143 | * independently read from at different speeds. 144 | */ 145 | tee(): [Stream, Stream] { 146 | const left: Array>> = []; 147 | const right: Array>> = []; 148 | const iterator = this.iterator(); 149 | 150 | const teeIterator = (queue: Array>>): AsyncIterator => { 151 | return { 152 | next: () => { 153 | if (queue.length === 0) { 154 | const result = iterator.next(); 155 | left.push(result); 156 | right.push(result); 157 | } 158 | return queue.shift()!; 159 | }, 160 | }; 161 | }; 162 | 163 | return [ 164 | new Stream(() => teeIterator(left), this.controller), 165 | new Stream(() => teeIterator(right), this.controller), 166 | ]; 167 | } 168 | 169 | /** 170 | * Converts this stream to a newline-separated ReadableStream of 171 | * JSON stringified values in the stream 172 | * which can be turned back into a Stream with `Stream.fromReadableStream()`. 173 | */ 174 | toReadableStream(): ReadableStream { 175 | const self = this; 176 | let iter: AsyncIterator; 177 | const encoder = new TextEncoder(); 178 | 179 | return new ReadableStream({ 180 | async start() { 181 | iter = self[Symbol.asyncIterator](); 182 | }, 183 | async pull(ctrl: any) { 184 | try { 185 | const { value, done } = await iter.next(); 186 | if (done) return ctrl.close(); 187 | 188 | const bytes = encoder.encode(JSON.stringify(value) + '\n'); 189 | 190 | ctrl.enqueue(bytes); 191 | } catch (err) { 192 | ctrl.error(err); 193 | } 194 | }, 195 | async cancel() { 196 | await iter.return?.(); 197 | }, 198 | }); 199 | } 200 | } 201 | 202 | export async function* _iterSSEMessages( 203 | response: Response, 204 | controller: AbortController, 205 | ): AsyncGenerator { 206 | if (!response.body) { 207 | controller.abort(); 208 | throw new CerebrasError(`Attempted to iterate over a response with no body`); 209 | } 210 | 211 | const sseDecoder = new SSEDecoder(); 212 | const lineDecoder = new LineDecoder(); 213 | 214 | const iter = readableStreamAsyncIterable(response.body); 215 | for await (const sseChunk of iterSSEChunks(iter)) { 216 | for (const line of lineDecoder.decode(sseChunk)) { 217 | const sse = sseDecoder.decode(line); 218 | if (sse) yield sse; 219 | } 220 | } 221 | 222 | for (const line of lineDecoder.flush()) { 223 | const sse = sseDecoder.decode(line); 224 | if (sse) yield sse; 225 | } 226 | } 227 | 228 | /** 229 | * Given an async iterable iterator, iterates over it and yields full 230 | * SSE chunks, i.e. yields when a double new-line is encountered. 231 | */ 232 | async function* iterSSEChunks(iterator: AsyncIterableIterator): AsyncGenerator { 233 | let data = new Uint8Array(); 234 | 235 | for await (const chunk of iterator) { 236 | if (chunk == null) { 237 | continue; 238 | } 239 | 240 | const binaryChunk = 241 | chunk instanceof ArrayBuffer ? new Uint8Array(chunk) 242 | : typeof chunk === 'string' ? new TextEncoder().encode(chunk) 243 | : chunk; 244 | 245 | let newData = new Uint8Array(data.length + binaryChunk.length); 246 | newData.set(data); 247 | newData.set(binaryChunk, data.length); 248 | data = newData; 249 | 250 | let patternIndex; 251 | while ((patternIndex = findDoubleNewlineIndex(data)) !== -1) { 252 | yield data.slice(0, patternIndex); 253 | data = data.slice(patternIndex); 254 | } 255 | } 256 | 257 | if (data.length > 0) { 258 | yield data; 259 | } 260 | } 261 | 262 | function findDoubleNewlineIndex(buffer: Uint8Array): number { 263 | // This function searches the buffer for the end patterns (\r\r, \n\n, \r\n\r\n) 264 | // and returns the index right after the first occurrence of any pattern, 265 | // or -1 if none of the patterns are found. 266 | const newline = 0x0a; // \n 267 | const carriage = 0x0d; // \r 268 | 269 | for (let i = 0; i < buffer.length - 2; i++) { 270 | if (buffer[i] === newline && buffer[i + 1] === newline) { 271 | // \n\n 272 | return i + 2; 273 | } 274 | if (buffer[i] === carriage && buffer[i + 1] === carriage) { 275 | // \r\r 276 | return i + 2; 277 | } 278 | if ( 279 | buffer[i] === carriage && 280 | buffer[i + 1] === newline && 281 | i + 3 < buffer.length && 282 | buffer[i + 2] === carriage && 283 | buffer[i + 3] === newline 284 | ) { 285 | // \r\n\r\n 286 | return i + 4; 287 | } 288 | } 289 | 290 | return -1; 291 | } 292 | 293 | class SSEDecoder { 294 | private data: string[]; 295 | private event: string | null; 296 | private chunks: string[]; 297 | 298 | constructor() { 299 | this.event = null; 300 | this.data = []; 301 | this.chunks = []; 302 | } 303 | 304 | decode(line: string) { 305 | if (line.endsWith('\r')) { 306 | line = line.substring(0, line.length - 1); 307 | } 308 | 309 | if (!line) { 310 | // empty line and we didn't previously encounter any messages 311 | if (!this.event && !this.data.length) return null; 312 | 313 | const sse: ServerSentEvent = { 314 | event: this.event, 315 | data: this.data.join('\n'), 316 | raw: this.chunks, 317 | }; 318 | 319 | this.event = null; 320 | this.data = []; 321 | this.chunks = []; 322 | 323 | return sse; 324 | } 325 | 326 | this.chunks.push(line); 327 | 328 | if (line.startsWith(':')) { 329 | return null; 330 | } 331 | 332 | let [fieldname, _, value] = partition(line, ':'); 333 | 334 | if (value.startsWith(' ')) { 335 | value = value.substring(1); 336 | } 337 | 338 | if (fieldname === 'event') { 339 | this.event = value; 340 | } else if (fieldname === 'data') { 341 | this.data.push(value); 342 | } 343 | 344 | return null; 345 | } 346 | } 347 | 348 | /** 349 | * A re-implementation of httpx's `LineDecoder` in Python that handles incrementally 350 | * reading lines from text. 351 | * 352 | * https://github.com/encode/httpx/blob/920333ea98118e9cf617f246905d7b202510941c/httpx/_decoders.py#L258 353 | */ 354 | class LineDecoder { 355 | // prettier-ignore 356 | static NEWLINE_CHARS = new Set(['\n', '\r']); 357 | static NEWLINE_REGEXP = /\r\n|[\n\r]/g; 358 | 359 | buffer: string[]; 360 | trailingCR: boolean; 361 | textDecoder: any; // TextDecoder found in browsers; not typed to avoid pulling in either "dom" or "node" types. 362 | 363 | constructor() { 364 | this.buffer = []; 365 | this.trailingCR = false; 366 | } 367 | 368 | decode(chunk: Bytes): string[] { 369 | let text = this.decodeText(chunk); 370 | 371 | if (this.trailingCR) { 372 | text = '\r' + text; 373 | this.trailingCR = false; 374 | } 375 | if (text.endsWith('\r')) { 376 | this.trailingCR = true; 377 | text = text.slice(0, -1); 378 | } 379 | 380 | if (!text) { 381 | return []; 382 | } 383 | 384 | const trailingNewline = LineDecoder.NEWLINE_CHARS.has(text[text.length - 1] || ''); 385 | let lines = text.split(LineDecoder.NEWLINE_REGEXP); 386 | 387 | // if there is a trailing new line then the last entry will be an empty 388 | // string which we don't care about 389 | if (trailingNewline) { 390 | lines.pop(); 391 | } 392 | 393 | if (lines.length === 1 && !trailingNewline) { 394 | this.buffer.push(lines[0]!); 395 | return []; 396 | } 397 | 398 | if (this.buffer.length > 0) { 399 | lines = [this.buffer.join('') + lines[0], ...lines.slice(1)]; 400 | this.buffer = []; 401 | } 402 | 403 | if (!trailingNewline) { 404 | this.buffer = [lines.pop() || '']; 405 | } 406 | 407 | return lines; 408 | } 409 | 410 | decodeText(bytes: Bytes): string { 411 | if (bytes == null) return ''; 412 | if (typeof bytes === 'string') return bytes; 413 | 414 | // Node: 415 | if (typeof Buffer !== 'undefined') { 416 | if (bytes instanceof Buffer) { 417 | return bytes.toString(); 418 | } 419 | if (bytes instanceof Uint8Array) { 420 | return Buffer.from(bytes).toString(); 421 | } 422 | 423 | throw new CerebrasError( 424 | `Unexpected: received non-Uint8Array (${bytes.constructor.name}) stream chunk in an environment with a global "Buffer" defined, which this library assumes to be Node. Please report this error.`, 425 | ); 426 | } 427 | 428 | // Browser 429 | if (typeof TextDecoder !== 'undefined') { 430 | if (bytes instanceof Uint8Array || bytes instanceof ArrayBuffer) { 431 | this.textDecoder ??= new TextDecoder('utf8'); 432 | return this.textDecoder.decode(bytes); 433 | } 434 | 435 | throw new CerebrasError( 436 | `Unexpected: received non-Uint8Array/ArrayBuffer (${ 437 | (bytes as any).constructor.name 438 | }) in a web platform. Please report this error.`, 439 | ); 440 | } 441 | 442 | throw new CerebrasError( 443 | `Unexpected: neither Buffer nor TextDecoder are available as globals. Please report this error.`, 444 | ); 445 | } 446 | 447 | flush(): string[] { 448 | if (!this.buffer.length && !this.trailingCR) { 449 | return []; 450 | } 451 | 452 | const lines = [this.buffer.join('')]; 453 | this.buffer = []; 454 | this.trailingCR = false; 455 | return lines; 456 | } 457 | } 458 | 459 | /** This is an internal helper function that's just used for testing */ 460 | export function _decodeChunks(chunks: string[]): string[] { 461 | const decoder = new LineDecoder(); 462 | const lines: string[] = []; 463 | for (const chunk of chunks) { 464 | lines.push(...decoder.decode(chunk)); 465 | } 466 | 467 | return lines; 468 | } 469 | 470 | function partition(str: string, delimiter: string): [string, string, string] { 471 | const index = str.indexOf(delimiter); 472 | if (index !== -1) { 473 | return [str.substring(0, index), delimiter, str.substring(index + delimiter.length)]; 474 | } 475 | 476 | return [str, '', '']; 477 | } 478 | 479 | /** 480 | * Most browsers don't yet have async iterable support for ReadableStream, 481 | * and Node has a very different way of reading bytes from its "ReadableStream". 482 | * 483 | * This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490 484 | */ 485 | export function readableStreamAsyncIterable(stream: any): AsyncIterableIterator { 486 | if (stream[Symbol.asyncIterator]) return stream; 487 | 488 | const reader = stream.getReader(); 489 | return { 490 | async next() { 491 | try { 492 | const result = await reader.read(); 493 | if (result?.done) reader.releaseLock(); // release lock when stream becomes closed 494 | return result; 495 | } catch (e) { 496 | reader.releaseLock(); // release lock when stream becomes errored 497 | throw e; 498 | } 499 | }, 500 | async return() { 501 | const cancelPromise = reader.cancel(); 502 | reader.releaseLock(); 503 | await cancelPromise; 504 | return { done: true, value: undefined }; 505 | }, 506 | [Symbol.asyncIterator]() { 507 | return this; 508 | }, 509 | }; 510 | } 511 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cerebras Node API Library 2 | 3 | [![NPM version](https://img.shields.io/npm/v/@cerebras/cerebras_cloud_sdk.svg)](https://npmjs.org/package/@cerebras/cerebras_cloud_sdk) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@cerebras/cerebras_cloud_sdk) 4 | 5 | This library provides convenient access to the Cerebras REST API from server-side TypeScript or JavaScript. 6 | 7 | The REST API documentation can be found on [inference-docs.cerebras.ai](https://inference-docs.cerebras.ai). The full API of this library can be found in [api.md](api.md). 8 | 9 | It is generated with [Stainless](https://www.stainless.com/). 10 | 11 | > [!NOTE] 12 | > This SDK has a mechanism that sends a few requests to `/v1/tcp_warming` upon construction to reduce the TTFT. If this behaviour is not desired, set `warmTCPConnection=false` in the constructor. 13 | > 14 | > If you are repeatedly reconstructing the SDK instance it will lead to poor performance. It is recommended that you construct the SDK once and reuse the instance if possible. 15 | 16 | ## About Cerebras 17 | 18 | At Cerebras, we've developed the world's largest and fastest AI processor, the Wafer-Scale Engine-3 (WSE-3). The Cerebras CS-3 system, powered by the WSE-3, represents a new class of AI supercomputer that sets the standard for generative AI training and inference with unparalleled performance and scalability. 19 | 20 | With Cerebras as your inference provider, you can: 21 | - Achieve unprecedented speed for AI inference workloads 22 | - Build commercially with high throughput 23 | - Effortlessly scale your AI workloads with our seamless clustering technology 24 | 25 | Our CS-3 systems can be quickly and easily clustered to create the largest AI supercomputers in the world, making it simple to place and run the largest models. Leading corporations, research institutions, and governments are already using Cerebras solutions to develop proprietary models and train popular open-source models. 26 | 27 | Want to experience the power of Cerebras? Check out our [website](https://cerebras.net) for more resources and explore options for accessing our technology through the Cerebras Cloud or on-premise deployments! 28 | 29 | ## Installation 30 | ``` 31 | npm install @cerebras/cerebras_cloud_sdk 32 | ``` 33 | 34 | ## API Key 35 | Get an API Key from [cloud.cerebras.ai](https://cloud.cerebras.ai/) and add it to your environment variables: 36 | ``` 37 | export CEREBRAS_API_KEY="your-api-key-here" 38 | ``` 39 | 40 | ## Usage 41 | 42 | The full API of this library can be found in [api.md](api.md). 43 | 44 | ### Chat Completion 45 | 46 | ```ts 47 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 48 | 49 | const client = new Cerebras({ 50 | apiKey: process.env['CEREBRAS_API_KEY'], // This is the default and can be omitted 51 | }); 52 | 53 | async function main() { 54 | const chatCompletion = await client.chat.completions.create({ 55 | model: 'llama3.1-8b', 56 | messages: [{ role: 'user', content: 'Why is fast inference important?' }], 57 | }); 58 | 59 | console.log(chatCompletion?.choices[0]?.message); 60 | } 61 | 62 | main(); 63 | ``` 64 | 65 | ### Text Completion 66 | 67 | ```ts 68 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 69 | 70 | const client = new Cerebras({ 71 | apiKey: process.env['CEREBRAS_API_KEY'], // This is the default and can be omitted 72 | }); 73 | 74 | async function main() { 75 | const completion = await client.completions.create({ 76 | prompt: "It was a dark and stormy ", 77 | model: 'llama3.1-8b', 78 | }); 79 | 80 | console.log(completion?.choices[0]?.text); 81 | } 82 | 83 | main(); 84 | ``` 85 | 86 | ## Streaming responses 87 | 88 | We provide support for streaming responses using Server Sent Events (SSE). 89 | 90 | Note that when streaming, `usage` and `time_info` will be information will only be included in the final chunk. 91 | 92 | ### Chat Completion 93 | 94 | ```ts 95 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 96 | 97 | const client = new Cerebras({ 98 | apiKey: process.env['CEREBRAS_API_KEY'], // This is the default and can be omitted 99 | }); 100 | 101 | async function main() { 102 | const stream = await client.chat.completions.create({ 103 | messages: [{ role: 'user', content: 'Why is fast inference important?' }], 104 | model: 'llama3.1-8b', 105 | stream: true, 106 | }); 107 | for await (const chunk of stream) { 108 | process.stdout.write(chunk.choices[0]?.delta?.content || ''); 109 | } 110 | } 111 | 112 | main(); 113 | ``` 114 | 115 | ### Text Completion 116 | 117 | ```ts 118 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 119 | 120 | const client = new Cerebras({ 121 | apiKey: process.env['CEREBRAS_API_KEY'], // This is the default and can be omitted 122 | }); 123 | 124 | async function main() { 125 | const stream = await client.completions.create({ 126 | prompt: "It was a dark and stormy ", 127 | model: 'llama3.1-8b', 128 | max_tokens: 10, 129 | stream: true, 130 | }); 131 | for await (const chunk of stream) { 132 | process.stdout.write(chunk.choices[0]?.text || ''); 133 | } 134 | } 135 | 136 | main(); 137 | ``` 138 | 139 | If you need to cancel a stream, you can `break` from the loop 140 | or call `stream.controller.abort()`. 141 | 142 | ### Request & Response types 143 | 144 | This library includes TypeScript definitions for all request params and response fields. You may import and use them like so: 145 | 146 | 147 | ```ts 148 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 149 | 150 | const client = new Cerebras({ 151 | apiKey: process.env['CEREBRAS_API_KEY'], // This is the default and can be omitted 152 | }); 153 | 154 | const params: Cerebras.Chat.ChatCompletionCreateParams = { 155 | model: 'llama3.1-8b', 156 | messages: [{ role: 'user', content: 'Why is fast inference important?' }], 157 | }; 158 | const chatCompletion: Cerebras.Chat.ChatCompletion = await client.chat.completions.create(params); 159 | ``` 160 | 161 | Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors. 162 | 163 | ## Handling errors 164 | 165 | When the library is unable to connect to the API, 166 | or if the API returns a non-success status code (i.e., 4xx or 5xx response), 167 | a subclass of `APIError` will be thrown: 168 | 169 | 170 | ```ts 171 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 172 | 173 | const client = new Cerebras({ 174 | apiKey: process.env['CEREBRAS_API_KEY'], // This is the default and can be omitted 175 | }); 176 | 177 | async function main() { 178 | const chatCompletion = await client.chat.completions 179 | .create({ 180 | model: 'some-model-that-doesnt-exist' as any, 181 | messages: [{ role: 'user', content: 'This should cause an error!' }], // Ask TS to ignore the obviously invalid model name... Do not do this! 182 | }) 183 | .catch(async (err) => { 184 | if (err instanceof Cerebras.APIError) { 185 | console.log(err.status); // 400 186 | console.log(err.name); // BadRequestError 187 | console.log(err.headers); // {server: 'nginx', ...} 188 | console.log(err); // Full exception 189 | } else { 190 | throw err; 191 | } 192 | }); 193 | } 194 | 195 | main(); 196 | ``` 197 | 198 | Error codes are as follows: 199 | 200 | | Status Code | Error Type | 201 | | ----------- | -------------------------- | 202 | | 400 | `BadRequestError` | 203 | | 401 | `AuthenticationError` | 204 | | 403 | `PermissionDeniedError` | 205 | | 404 | `NotFoundError` | 206 | | 422 | `UnprocessableEntityError` | 207 | | 429 | `RateLimitError` | 208 | | >=500 | `InternalServerError` | 209 | | N/A | `APIConnectionError` | 210 | 211 | ### Retries 212 | 213 | Certain errors will be automatically retried 2 times by default, with a short exponential backoff. 214 | Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, 215 | 429 Rate Limit, and >=500 Internal errors will all be retried by default. 216 | 217 | You can use the `maxRetries` option to configure or disable this: 218 | 219 | 220 | ```js 221 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 222 | 223 | // Configure the default for all requests: 224 | const client = new Cerebras({ 225 | maxRetries: 0, // default is 2 226 | }); 227 | 228 | // Or, configure per-request: 229 | await client.chat.completions.create({ model: 'llama3.1-8b', messages: [{ role: 'user', content: 'Why is fast inference important?' }] }, { 230 | maxRetries: 5, 231 | }); 232 | ``` 233 | 234 | ### Timeouts 235 | 236 | Requests time out after 1 minute by default. You can configure this with a `timeout` option: 237 | 238 | 239 | ```ts 240 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 241 | 242 | // Configure the default for all requests: 243 | const client = new Cerebras({ 244 | timeout: 20 * 1000, // 20 seconds (default is 1 minute) 245 | }); 246 | 247 | // Override per-request: 248 | await client.chat.completions.create({ model: 'llama3.1-8b', messages: [{ role: 'user', content: 'Why is fast inference important?' }] }, { 249 | timeout: 5 * 1000, 250 | }); 251 | ``` 252 | 253 | On timeout, an `APIConnectionTimeoutError` is thrown. 254 | 255 | Note that requests which time out will be [retried twice by default](#retries). 256 | 257 | ## Advanced Usage 258 | 259 | ### Accessing raw Response data (e.g., headers) 260 | 261 | The "raw" `Response` returned by `fetch()` can be accessed through the `.asResponse()` method on the `APIPromise` type that all methods return. 262 | 263 | You can also use the `.withResponse()` method to get the raw `Response` along with the parsed data. 264 | 265 | 266 | ```ts 267 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 268 | 269 | const client = new Cerebras(); 270 | 271 | const response = await client.chat.completions 272 | .create({ model: 'llama3.1-8b', messages: [{ role: 'user', content: 'Why is fast inference important?' }] }) 273 | .asResponse(); 274 | console.log(response.headers.get('X-My-Header')); 275 | console.log(response.statusText); // access the underlying Response object 276 | 277 | const { data: chatCompletion, response: raw } = await client.chat.completions 278 | .create({ model: 'llama3.1-8b', messages: [{ role: 'user', content: 'Why is fast inference important?' }] }) 279 | .withResponse(); 280 | console.log(raw.headers.get('X-My-Header')); 281 | console.log(chatCompletion); 282 | ``` 283 | 284 | ### Making custom/undocumented requests 285 | 286 | This library is typed for convenient access to the documented API. If you need to access undocumented 287 | endpoints, params, or response properties, the library can still be used. 288 | 289 | #### Undocumented endpoints 290 | 291 | To make requests to undocumented endpoints, you can use `client.get`, `client.post`, and other HTTP verbs. 292 | Options on the client, such as retries, will be respected when making these requests. 293 | 294 | ```ts 295 | await client.post('/some/path', { 296 | body: { some_prop: 'foo' }, 297 | query: { some_query_arg: 'bar' }, 298 | }); 299 | ``` 300 | 301 | #### Undocumented request params 302 | 303 | To make requests using undocumented parameters, you may use `// @ts-expect-error` on the undocumented 304 | parameter. This library doesn't validate at runtime that the request matches the type, so any extra values you 305 | send will be sent as-is. 306 | 307 | ```ts 308 | client.foo.create({ 309 | foo: 'my_param', 310 | bar: 12, 311 | // @ts-expect-error baz is not yet public 312 | baz: 'undocumented option', 313 | }); 314 | ``` 315 | 316 | For requests with the `GET` verb, any extra params will be in the query, all other requests will send the 317 | extra param in the body. 318 | 319 | If you want to explicitly send an extra argument, you can do so with the `query`, `body`, and `headers` request 320 | options. 321 | 322 | #### Undocumented response properties 323 | 324 | To access undocumented response properties, you may access the response object with `// @ts-expect-error` on 325 | the response object, or cast the response object to the requisite type. Like the request params, we do not 326 | validate or strip extra properties from the response from the API. 327 | 328 | ### Customizing the fetch client 329 | 330 | By default, this library uses `node-fetch` in Node, and expects a global `fetch` function in other environments. 331 | 332 | If you would prefer to use a global, web-standards-compliant `fetch` function even in a Node environment, 333 | (for example, if you are running Node with `--experimental-fetch` or using NextJS which polyfills with `undici`), 334 | add the following import before your first import `from "Cerebras"`: 335 | 336 | ```ts 337 | // Tell TypeScript and the package to use the global web fetch instead of node-fetch. 338 | // Note, despite the name, this does not add any polyfills, but expects them to be provided if needed. 339 | import '@cerebras/cerebras_cloud_sdk/shims/web'; 340 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 341 | ``` 342 | 343 | To do the inverse, add `import "@cerebras/cerebras_cloud_sdk/shims/node"` (which does import polyfills). 344 | This can also be useful if you are getting the wrong TypeScript types for `Response` ([more details](https://github.com/Cerebras/cerebras-cloud-sdk-node/tree/main/src/_shims#readme)). 345 | 346 | ### Logging and middleware 347 | 348 | You may also provide a custom `fetch` function when instantiating the client, 349 | which can be used to inspect or alter the `Request` or `Response` before/after each request: 350 | 351 | ```ts 352 | import { fetch } from 'undici'; // as one example 353 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 354 | 355 | const client = new Cerebras({ 356 | fetch: async (url: RequestInfo, init?: RequestInit): Promise => { 357 | console.log('About to make a request', url, init); 358 | const response = await fetch(url, init); 359 | console.log('Got response', response); 360 | return response; 361 | }, 362 | }); 363 | ``` 364 | 365 | Note that if given a `DEBUG=true` environment variable, this library will log all requests and responses automatically. 366 | This is intended for debugging purposes only and may change in the future without notice. 367 | 368 | ### Configuring an HTTP(S) Agent (e.g., for proxies) 369 | 370 | By default, this library uses a stable agent for all http/https requests to reuse TCP connections, eliminating many TCP & TLS handshakes and shaving around 100ms off most requests. 371 | 372 | If you would like to disable or customize this behavior, for example to use the API behind a proxy, you can pass an `httpAgent` which is used for all requests (be they http or https), for example: 373 | 374 | 375 | ```ts 376 | import http from 'http'; 377 | import { HttpsProxyAgent } from 'https-proxy-agent'; 378 | 379 | // Configure the default for all requests: 380 | const client = new Cerebras({ 381 | httpAgent: new HttpsProxyAgent(process.env.PROXY_URL), 382 | }); 383 | 384 | // Override per-request: 385 | await client.chat.completions.create( 386 | { model: 'llama3.1-8b', messages: [{ role: 'user', content: 'Why is fast inference important?' }] }, 387 | { 388 | httpAgent: new http.Agent({ keepAlive: false }), 389 | }, 390 | ); 391 | ``` 392 | 393 | ## Semantic versioning 394 | 395 | This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: 396 | 397 | 1. Changes that only affect static types, without breaking runtime behavior. 398 | 2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ 399 | 3. Changes that we do not expect to impact the vast majority of users in practice. 400 | 401 | We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. 402 | 403 | We are keen for your feedback; please open an [issue](https://www.github.com/Cerebras/cerebras-cloud-sdk-node/issues) with questions, bugs, or suggestions. 404 | 405 | ## Requirements 406 | 407 | TypeScript >= 4.5 is supported. 408 | 409 | The following runtimes are supported: 410 | 411 | - Web browsers (Up-to-date Chrome, Firefox, Safari, Edge, and more) 412 | - Node.js 18 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions. 413 | - Deno v1.28.0 or higher. 414 | - Bun 1.0 or later. 415 | - Cloudflare Workers. 416 | - Vercel Edge Runtime. 417 | - Jest 28 or greater with the `"node"` environment (`"jsdom"` is not supported at this time). 418 | - Nitro v2.6 or greater. 419 | 420 | Note that React Native is not supported at this time. 421 | 422 | If you are interested in other runtime environments, please open or upvote an issue on GitHub. 423 | 424 | ## Contributing 425 | 426 | See [the contributing documentation](./CONTRIBUTING.md). 427 | 428 | -------------------------------------------------------------------------------- /tests/index.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Cerebras from '@cerebras/cerebras_cloud_sdk'; 4 | import { APIUserAbortError } from '@cerebras/cerebras_cloud_sdk'; 5 | import { Headers } from '@cerebras/cerebras_cloud_sdk/core'; 6 | import defaultFetch, { Response, type RequestInit, type RequestInfo } from 'node-fetch'; 7 | 8 | describe('instantiate client', () => { 9 | const env = process.env; 10 | 11 | beforeEach(() => { 12 | jest.resetModules(); 13 | process.env = { ...env }; 14 | 15 | console.warn = jest.fn(); 16 | }); 17 | 18 | afterEach(() => { 19 | process.env = env; 20 | }); 21 | 22 | describe('defaultHeaders', () => { 23 | const client = new Cerebras({ 24 | baseURL: 'http://localhost:5000/', 25 | defaultHeaders: { 'X-My-Default-Header': '2' }, 26 | apiKey: 'My API Key', 27 | }); 28 | 29 | test('they are used in the request', async () => { 30 | const { req } = await client.buildRequest({ path: '/foo', method: 'post' }); 31 | expect((req.headers as Headers)['x-my-default-header']).toEqual('2'); 32 | }); 33 | 34 | test('can ignore `undefined` and leave the default', async () => { 35 | const { req } = await client.buildRequest({ 36 | path: '/foo', 37 | method: 'post', 38 | headers: { 'X-My-Default-Header': undefined }, 39 | }); 40 | expect((req.headers as Headers)['x-my-default-header']).toEqual('2'); 41 | }); 42 | 43 | test('can be removed with `null`', async () => { 44 | const { req } = await client.buildRequest({ 45 | path: '/foo', 46 | method: 'post', 47 | headers: { 'X-My-Default-Header': null }, 48 | }); 49 | expect(req.headers as Headers).not.toHaveProperty('x-my-default-header'); 50 | }); 51 | }); 52 | 53 | describe('defaultQuery', () => { 54 | test('with null query params given', () => { 55 | const client = new Cerebras({ 56 | baseURL: 'http://localhost:5000/', 57 | defaultQuery: { apiVersion: 'foo' }, 58 | apiKey: 'My API Key', 59 | }); 60 | expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo'); 61 | }); 62 | 63 | test('multiple default query params', () => { 64 | const client = new Cerebras({ 65 | baseURL: 'http://localhost:5000/', 66 | defaultQuery: { apiVersion: 'foo', hello: 'world' }, 67 | apiKey: 'My API Key', 68 | }); 69 | expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo&hello=world'); 70 | }); 71 | 72 | test('overriding with `undefined`', () => { 73 | const client = new Cerebras({ 74 | baseURL: 'http://localhost:5000/', 75 | defaultQuery: { hello: 'world' }, 76 | apiKey: 'My API Key', 77 | }); 78 | expect(client.buildURL('/foo', { hello: undefined })).toEqual('http://localhost:5000/foo'); 79 | }); 80 | }); 81 | 82 | test('custom fetch', async () => { 83 | const client = new Cerebras({ 84 | baseURL: 'http://localhost:5000/', 85 | apiKey: 'My API Key', 86 | fetch: (url) => { 87 | return Promise.resolve( 88 | new Response(JSON.stringify({ url, custom: true }), { 89 | headers: { 'Content-Type': 'application/json' }, 90 | }), 91 | ); 92 | }, 93 | }); 94 | 95 | const response = await client.get('/foo'); 96 | expect(response).toEqual({ url: 'http://localhost:5000/foo', custom: true }); 97 | }); 98 | 99 | test('explicit global fetch', async () => { 100 | // make sure the global fetch type is assignable to our Fetch type 101 | const client = new Cerebras({ 102 | baseURL: 'http://localhost:5000/', 103 | apiKey: 'My API Key', 104 | fetch: defaultFetch, 105 | }); 106 | }); 107 | 108 | test('custom signal', async () => { 109 | const client = new Cerebras({ 110 | baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', 111 | apiKey: 'My API Key', 112 | fetch: (...args) => { 113 | return new Promise((resolve, reject) => 114 | setTimeout( 115 | () => 116 | defaultFetch(...args) 117 | .then(resolve) 118 | .catch(reject), 119 | 300, 120 | ), 121 | ); 122 | }, 123 | }); 124 | 125 | const controller = new AbortController(); 126 | setTimeout(() => controller.abort(), 200); 127 | 128 | const spy = jest.spyOn(client, 'request'); 129 | 130 | await expect(client.get('/foo', { signal: controller.signal })).rejects.toThrowError(APIUserAbortError); 131 | expect(spy).toHaveBeenCalledTimes(1); 132 | }); 133 | 134 | test('normalized method', async () => { 135 | let capturedRequest: RequestInit | undefined; 136 | const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { 137 | capturedRequest = init; 138 | return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); 139 | }; 140 | 141 | const client = new Cerebras({ 142 | baseURL: 'http://localhost:5000/', 143 | apiKey: 'My API Key', 144 | fetch: testFetch, 145 | }); 146 | 147 | await client.patch('/foo'); 148 | expect(capturedRequest?.method).toEqual('PATCH'); 149 | }); 150 | 151 | describe('baseUrl', () => { 152 | test('trailing slash', () => { 153 | const client = new Cerebras({ baseURL: 'http://localhost:5000/custom/path/', apiKey: 'My API Key' }); 154 | expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); 155 | }); 156 | 157 | test('no trailing slash', () => { 158 | const client = new Cerebras({ baseURL: 'http://localhost:5000/custom/path', apiKey: 'My API Key' }); 159 | expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); 160 | }); 161 | 162 | afterEach(() => { 163 | process.env['CEREBRAS_BASE_URL'] = undefined; 164 | }); 165 | 166 | test('explicit option', () => { 167 | const client = new Cerebras({ baseURL: 'https://example.com', apiKey: 'My API Key' }); 168 | expect(client.baseURL).toEqual('https://example.com'); 169 | }); 170 | 171 | test('env variable', () => { 172 | process.env['CEREBRAS_BASE_URL'] = 'https://example.com/from_env'; 173 | const client = new Cerebras({ apiKey: 'My API Key' }); 174 | expect(client.baseURL).toEqual('https://example.com/from_env'); 175 | }); 176 | 177 | test('empty env variable', () => { 178 | process.env['CEREBRAS_BASE_URL'] = ''; // empty 179 | const client = new Cerebras({ apiKey: 'My API Key' }); 180 | expect(client.baseURL).toEqual('https://api.cerebras.ai'); 181 | }); 182 | 183 | test('blank env variable', () => { 184 | process.env['CEREBRAS_BASE_URL'] = ' '; // blank 185 | const client = new Cerebras({ apiKey: 'My API Key' }); 186 | expect(client.baseURL).toEqual('https://api.cerebras.ai'); 187 | }); 188 | 189 | test('in request options', () => { 190 | const client = new Cerebras({ apiKey: 'My API Key' }); 191 | expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( 192 | 'http://localhost:5000/option/foo', 193 | ); 194 | }); 195 | 196 | test('in request options overridden by client options', () => { 197 | const client = new Cerebras({ apiKey: 'My API Key', baseURL: 'http://localhost:5000/client' }); 198 | expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( 199 | 'http://localhost:5000/client/foo', 200 | ); 201 | }); 202 | 203 | test('in request options overridden by env variable', () => { 204 | process.env['CEREBRAS_BASE_URL'] = 'http://localhost:5000/env'; 205 | const client = new Cerebras({ apiKey: 'My API Key' }); 206 | expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( 207 | 'http://localhost:5000/env/foo', 208 | ); 209 | }); 210 | }); 211 | 212 | test('maxRetries option is correctly set', () => { 213 | const client = new Cerebras({ maxRetries: 4, apiKey: 'My API Key' }); 214 | expect(client.maxRetries).toEqual(4); 215 | 216 | // default 217 | const client2 = new Cerebras({ apiKey: 'My API Key' }); 218 | expect(client2.maxRetries).toEqual(2); 219 | }); 220 | 221 | test('with environment variable arguments', () => { 222 | // set options via env var 223 | process.env['CEREBRAS_API_KEY'] = 'My API Key'; 224 | const client = new Cerebras(); 225 | expect(client.apiKey).toBe('My API Key'); 226 | }); 227 | 228 | test('with overridden environment variable arguments', () => { 229 | // set options via env var 230 | process.env['CEREBRAS_API_KEY'] = 'another My API Key'; 231 | const client = new Cerebras({ apiKey: 'My API Key' }); 232 | expect(client.apiKey).toBe('My API Key'); 233 | }); 234 | }); 235 | 236 | describe('request building', () => { 237 | const client = new Cerebras({ apiKey: 'My API Key' }); 238 | 239 | describe('Content-Length', () => { 240 | test('handles multi-byte characters', async () => { 241 | const { req } = await client.buildRequest({ path: '/foo', method: 'post', body: { value: '—' } }); 242 | expect((req.headers as Record)['content-length']).toEqual('20'); 243 | }); 244 | 245 | test('handles standard characters', async () => { 246 | const { req } = await client.buildRequest({ path: '/foo', method: 'post', body: { value: 'hello' } }); 247 | expect((req.headers as Record)['content-length']).toEqual('22'); 248 | }); 249 | }); 250 | 251 | describe('custom headers', () => { 252 | test('handles undefined', async () => { 253 | const { req } = await client.buildRequest({ 254 | path: '/foo', 255 | method: 'post', 256 | body: { value: 'hello' }, 257 | headers: { 'X-Foo': 'baz', 'x-foo': 'bar', 'x-Foo': undefined, 'x-baz': 'bam', 'X-Baz': null }, 258 | }); 259 | expect((req.headers as Record)['x-foo']).toEqual('bar'); 260 | expect((req.headers as Record)['x-Foo']).toEqual(undefined); 261 | expect((req.headers as Record)['X-Foo']).toEqual(undefined); 262 | expect((req.headers as Record)['x-baz']).toEqual(undefined); 263 | }); 264 | }); 265 | }); 266 | 267 | describe('retries', () => { 268 | test('retry on timeout', async () => { 269 | let count = 0; 270 | const testFetch = async (url: RequestInfo, { signal }: RequestInit = {}): Promise => { 271 | if (count++ === 0) { 272 | return new Promise( 273 | (resolve, reject) => signal?.addEventListener('abort', () => reject(new Error('timed out'))), 274 | ); 275 | } 276 | return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); 277 | }; 278 | 279 | const client = new Cerebras({ 280 | apiKey: 'My API Key', 281 | timeout: 10, 282 | fetch: testFetch, 283 | warmTCPConnection: false, 284 | }); 285 | 286 | expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); 287 | expect(count).toEqual(2); 288 | expect( 289 | await client 290 | .request({ path: '/foo', method: 'get' }) 291 | .asResponse() 292 | .then((r) => r.text()), 293 | ).toEqual(JSON.stringify({ a: 1 })); 294 | expect(count).toEqual(3); 295 | }); 296 | 297 | test('retry count header', async () => { 298 | let count = 0; 299 | let capturedRequest: RequestInit | undefined; 300 | const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { 301 | count++; 302 | if (count <= 2) { 303 | return new Response(undefined, { 304 | status: 429, 305 | headers: { 306 | 'Retry-After': '0.1', 307 | }, 308 | }); 309 | } 310 | capturedRequest = init; 311 | return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); 312 | }; 313 | 314 | const client = new Cerebras({ 315 | apiKey: 'My API Key', 316 | fetch: testFetch, 317 | maxRetries: 4, 318 | warmTCPConnection: false, 319 | }); 320 | 321 | expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); 322 | 323 | expect((capturedRequest!.headers as Headers)['x-stainless-retry-count']).toEqual('2'); 324 | expect(count).toEqual(3); 325 | }); 326 | 327 | test('omit retry count header', async () => { 328 | let count = 0; 329 | let capturedRequest: RequestInit | undefined; 330 | const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { 331 | count++; 332 | if (count <= 2) { 333 | return new Response(undefined, { 334 | status: 429, 335 | headers: { 336 | 'Retry-After': '0.1', 337 | }, 338 | }); 339 | } 340 | capturedRequest = init; 341 | return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); 342 | }; 343 | const client = new Cerebras({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); 344 | 345 | expect( 346 | await client.request({ 347 | path: '/foo', 348 | method: 'get', 349 | headers: { 'X-Stainless-Retry-Count': null }, 350 | }), 351 | ).toEqual({ a: 1 }); 352 | 353 | expect(capturedRequest!.headers as Headers).not.toHaveProperty('x-stainless-retry-count'); 354 | }); 355 | 356 | test('omit retry count header by default', async () => { 357 | let count = 0; 358 | let capturedRequest: RequestInit | undefined; 359 | const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { 360 | count++; 361 | if (count <= 2) { 362 | return new Response(undefined, { 363 | status: 429, 364 | headers: { 365 | 'Retry-After': '0.1', 366 | }, 367 | }); 368 | } 369 | capturedRequest = init; 370 | return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); 371 | }; 372 | const client = new Cerebras({ 373 | apiKey: 'My API Key', 374 | fetch: testFetch, 375 | maxRetries: 4, 376 | defaultHeaders: { 'X-Stainless-Retry-Count': null }, 377 | }); 378 | 379 | expect( 380 | await client.request({ 381 | path: '/foo', 382 | method: 'get', 383 | }), 384 | ).toEqual({ a: 1 }); 385 | 386 | expect(capturedRequest!.headers as Headers).not.toHaveProperty('x-stainless-retry-count'); 387 | }); 388 | 389 | test('overwrite retry count header', async () => { 390 | let count = 0; 391 | let capturedRequest: RequestInit | undefined; 392 | const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { 393 | count++; 394 | if (count <= 2) { 395 | return new Response(undefined, { 396 | status: 429, 397 | headers: { 398 | 'Retry-After': '0.1', 399 | }, 400 | }); 401 | } 402 | capturedRequest = init; 403 | return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); 404 | }; 405 | const client = new Cerebras({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); 406 | 407 | expect( 408 | await client.request({ 409 | path: '/foo', 410 | method: 'get', 411 | headers: { 'X-Stainless-Retry-Count': '42' }, 412 | }), 413 | ).toEqual({ a: 1 }); 414 | 415 | expect((capturedRequest!.headers as Headers)['x-stainless-retry-count']).toBe('42'); 416 | }); 417 | 418 | test('retry on 429 with retry-after', async () => { 419 | let count = 0; 420 | const testFetch = async (url: RequestInfo, { signal }: RequestInit = {}): Promise => { 421 | if (count++ === 0) { 422 | return new Response(undefined, { 423 | status: 429, 424 | headers: { 425 | 'Retry-After': '0.1', 426 | }, 427 | }); 428 | } 429 | return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); 430 | }; 431 | 432 | const client = new Cerebras({ apiKey: 'My API Key', fetch: testFetch, warmTCPConnection: false }); 433 | 434 | expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); 435 | expect(count).toEqual(2); 436 | expect( 437 | await client 438 | .request({ path: '/foo', method: 'get' }) 439 | .asResponse() 440 | .then((r) => r.text()), 441 | ).toEqual(JSON.stringify({ a: 1 })); 442 | expect(count).toEqual(3); 443 | }); 444 | 445 | test('retry on 429 with retry-after-ms', async () => { 446 | let count = 0; 447 | const testFetch = async (url: RequestInfo, { signal }: RequestInit = {}): Promise => { 448 | if (count++ === 0) { 449 | return new Response(undefined, { 450 | status: 429, 451 | headers: { 452 | 'Retry-After-Ms': '10', 453 | }, 454 | }); 455 | } 456 | return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); 457 | }; 458 | 459 | const client = new Cerebras({ apiKey: 'My API Key', fetch: testFetch, warmTCPConnection: false }); 460 | 461 | expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); 462 | expect(count).toEqual(2); 463 | expect( 464 | await client 465 | .request({ path: '/foo', method: 'get' }) 466 | .asResponse() 467 | .then((r) => r.text()), 468 | ).toEqual(JSON.stringify({ a: 1 })); 469 | expect(count).toEqual(3); 470 | }); 471 | }); 472 | --------------------------------------------------------------------------------