Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Contributing to Bytez

Thank you for your interest in contributing to Bytez! This guide will help you get started.

## Development Setup

### Prerequisites

- Node.js 18+ (for JavaScript SDK)
- Python 3.8+ (for Python SDK)
- Julia 1.10+ (for Julia SDK)

### JavaScript SDK

```bash
cd sdk/javascript
yarn install # or npm install
```

**Run tests:**

```bash
# Unit tests (fast, no API key required)
npm run test:unit

# Integration tests (requires BYTEZ_KEY env var)
npm test
```

**Build:**

```bash
npm run build
```

### Python SDK

```bash
cd sdk/python
pip install -e .
pip install requests
```

**Run tests:**

```bash
python test.py
```

### Documentation

The documentation site uses Mintlify. To preview locally:

```bash
cd docs
yarn install
yarn start
```

## Pull Request Guidelines

### Before You Start

1. Check existing issues and PRs to avoid duplicate work
2. For significant changes, open an issue first to discuss the approach
3. Keep PRs focused—one logical change per PR

### Code Quality

- **JavaScript/TypeScript:** Follow existing code style, run `npm run lint`
- **Python:** Follow PEP 8, keep code simple and readable
- **Tests:** Add unit tests for new utilities, update integration tests if changing SDK behavior
- **Documentation:** Update relevant `.mdx` files if changing public APIs

### Commit Messages

Use clear, descriptive commit messages:

```
Fix query string bug in list.models()

- Use URLSearchParams for proper encoding
- Add unit tests for edge cases
- Fixes issue where both task and modelId produced invalid URLs
```

### PR Checklist

- [ ] Code follows existing style
- [ ] Tests added/updated
- [ ] Documentation updated (if needed)
- [ ] No unnecessary changes (formatting, whitespace)
- [ ] Commit history is clean

## Testing Philosophy

- **Unit tests:** Fast, isolated, test pure functions without I/O
- **Integration tests:** Test real API calls, but should be optional (require API key)
- **Add unit tests when possible:** Helps catch regressions quickly

## Questions?

- Open an issue for questions about contributing
- Join our [Discord](https://discord.com/invite/Z723PfCFWf) for real-time discussion

We appreciate your contributions!
1 change: 1 addition & 0 deletions sdk/javascript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"build": "pkgroll",
"dev": "tsx src/index.node.ts",
"test": "tsx ./tests/tasks.test.ts",
"test:unit": "node --test 'src/**/*.test.ts'",
"lint": "eslint ./src/*.ts",
"update": "yarn upgrade-interactive"
},
Expand Down
8 changes: 3 additions & 5 deletions sdk/javascript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Model from "./model";
// interfaces
import { ListModels } from "./interface/List";
import { Response } from "./interface/Client";
// utils
import { buildListModelsPath } from "./utils/query";

/**
* API Client for interfacing with the Bytez API.
Expand All @@ -16,11 +18,7 @@ export default class Bytez {
list = {
/** Lists available models, and provides basic information about each one, such as RAM required */
models: (options?: ListModels): Promise<Response> =>
this.#client.request(
`list/models${options?.task ? `?task=${options.task}` : ""}${
options?.modelId ? `?modelId=${options.modelId}` : ""
}`
) as Promise<Response>,
this.#client.request(buildListModelsPath(options)) as Promise<Response>,
/** List available tasks */
tasks: (): Promise<Response> =>
this.#client.request("list/tasks") as Promise<Response>
Expand Down
46 changes: 46 additions & 0 deletions sdk/javascript/src/utils/query.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { buildListModelsPath } from "./query";

describe("buildListModelsPath", () => {
it("returns base path when no options provided", () => {
assert.equal(buildListModelsPath(), "list/models");
assert.equal(buildListModelsPath({}), "list/models");
});

it("adds single query parameter correctly", () => {
assert.equal(
buildListModelsPath({ task: "chat" }),
"list/models?task=chat"
);

assert.equal(
buildListModelsPath({ modelId: "openai-community/gpt2" }),
"list/models?modelId=openai-community%2Fgpt2"
);
});

it("adds multiple query parameters with & separator", () => {
const result = buildListModelsPath({
task: "text-generation",
modelId: "openai-community/gpt2"
});

// URLSearchParams guarantees deterministic order in modern environments
assert.match(result, /^list\/models\?/);
assert.match(result, /task=text-generation/);
assert.match(result, /modelId=openai-community%2Fgpt2/);
assert.match(result, /&/);

// Ensure we don't have double question marks (the bug this fixes)
assert.equal(result.split("?").length - 1, 1, "Should have exactly one '?'");
});

it("URL-encodes special characters", () => {
const result = buildListModelsPath({
modelId: "org/model with spaces"
});

assert.match(result, /modelId=org%2Fmodel\+with\+spaces/);
});
});
28 changes: 28 additions & 0 deletions sdk/javascript/src/utils/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ListModels } from "../interface/List";

/**
* Build the path for `list/models` with proper query-string encoding.
*
* Isolated into a pure function for:
* - Unit testability without mocking HTTP
* - Clear separation of concerns (URL construction vs. network)
* - Easy reasoning about edge cases
*/
export function buildListModelsPath(options?: ListModels): string {
const base = "list/models";

if (!options) return base;

const params: Record<string, string> = {};

if (options.task) params.task = String(options.task);
if (options.modelId) params.modelId = String(options.modelId);

const entries = Object.entries(params);

if (entries.length === 0) return base;

const search = new URLSearchParams(entries).toString();

return `${base}?${search}`;
}