thinking-machines-lab/tinker/main 218k tokens More Tools
```
├── .gitignore
├── .python-version
├── .ruff.toml (200 tokens)
├── .stats.yml (100 tokens)
├── .sync_state
├── LICENSE (omitted)
├── README.md (100 tokens)
├── docs/
   ├── README.md (100 tokens)
   ├── api/
      ├── _meta.json
      ├── apifuture.md (700 tokens)
      ├── exceptions.md (600 tokens)
      ├── restclient.md (3.2k tokens)
      ├── samplingclient.md (1000 tokens)
      ├── serviceclient.md (1500 tokens)
      ├── trainingclient.md (2.4k tokens)
      ├── types.md (3.4k tokens)
   ├── images/
      ├── logo.png
      ├── logo.svg (14.6k tokens)
├── mypy.ini (300 tokens)
├── pydoc-markdown.yml (100 tokens)
├── pyproject.toml (900 tokens)
├── requirements-dev.lock (omitted)
├── scripts/
   ├── generate_docs.py (1800 tokens)
   ├── publish-pypi
├── src/
   ├── tinker/
      ├── __init__.py (600 tokens)
      ├── _base_client.py (9.9k tokens)
      ├── _client.py (2000 tokens)
      ├── _compat.py (1400 tokens)
      ├── _constants.py (100 tokens)
      ├── _exceptions.py (1100 tokens)
      ├── _files.py (700 tokens)
      ├── _models.py (3.5k tokens)
      ├── _qs.py (1000 tokens)
      ├── _resource.py (100 tokens)
      ├── _response.py (5.8k tokens)
      ├── _streaming.py (1700 tokens)
      ├── _types.py (1200 tokens)
      ├── _utils/
         ├── __init__.py (500 tokens)
         ├── _logs.py (200 tokens)
         ├── _proxy.py (400 tokens)
         ├── _reflection.py (300 tokens)
         ├── _resources_proxy.py (100 tokens)
         ├── _streams.py (100 tokens)
         ├── _sync.py (600 tokens)
         ├── _transform.py (3.2k tokens)
         ├── _typing.py (900 tokens)
         ├── _utils.py (2.4k tokens)
      ├── _version.py
      ├── cli/
         ├── AGENTS.md (2000 tokens)
         ├── CLAUDE.md
         ├── __main__.py (300 tokens)
         ├── client.py (1200 tokens)
         ├── commands/
            ├── __init__.py
            ├── checkpoint.py (8.9k tokens)
            ├── run.py (2.1k tokens)
            ├── version.py (100 tokens)
         ├── context.py (100 tokens)
         ├── exceptions.py (200 tokens)
         ├── lazy_group.py (600 tokens)
         ├── output.py (1300 tokens)
      ├── lib/
         ├── _auth_token_provider.py (800 tokens)
         ├── _jwt_auth.py (1100 tokens)
         ├── _jwt_auth_test.py (2k tokens)
         ├── _pydantic_conv.py (1300 tokens)
         ├── api_future_impl.py (4.8k tokens)
         ├── async_tinker_provider.py (200 tokens)
         ├── chunked_fwdbwd_helpers.py (800 tokens)
         ├── client_connection_pool_type.py (100 tokens)
         ├── internal_client_holder.py (5.6k tokens)
         ├── internal_client_holder_test.py (1300 tokens)
         ├── public_interfaces/
            ├── __init__.py (100 tokens)
            ├── api_future.py (900 tokens)
            ├── rest_client.py (7.9k tokens)
            ├── sampling_client.py (4.5k tokens)
            ├── service_client.py (3.9k tokens)
            ├── training_client.py (8.3k tokens)
         ├── queue_state_logger.py (300 tokens)
         ├── retry_handler.py (2.5k tokens)
         ├── retryable_exception.py
         ├── service_client_test.py (200 tokens)
         ├── sidecar.py (5.5k tokens)
         ├── sync_only.py (500 tokens)
         ├── telemetry.py (3.2k tokens)
         ├── telemetry_provider.py (100 tokens)
         ├── telemetry_test.py (6k tokens)
      ├── proto/
         ├── __init__.py
         ├── request_conv.py (900 tokens)
         ├── response_conv.py (1800 tokens)
         ├── tinker_public_pb2.py (1300 tokens)
         ├── tinker_public_pb2.pyi (3.8k tokens)
      ├── py.typed
      ├── resources/
         ├── __init__.py (100 tokens)
         ├── futures.py (700 tokens)
         ├── models.py (1200 tokens)
         ├── sampling.py (400 tokens)
         ├── service.py (1900 tokens)
         ├── telemetry.py (600 tokens)
         ├── training.py (1400 tokens)
         ├── weights.py (2.5k tokens)
      ├── types/
         ├── __init__.py (1200 tokens)
         ├── _pydantic_types/
            ├── __init__.py
            ├── datum.py (700 tokens)
            ├── forward_backward_input.py (100 tokens)
            ├── forward_backward_output.py (500 tokens)
            ├── forward_backward_request.py (100 tokens)
            ├── forward_request.py (100 tokens)
            ├── get_info_response.py (200 tokens)
            ├── get_sampler_response.py
            ├── load_weights_response.py (100 tokens)
            ├── model_input.py (300 tokens)
            ├── optim_step_response.py
            ├── sample_response.py (200 tokens)
            ├── sampled_sequence.py (100 tokens)
            ├── sampling_params.py (100 tokens)
            ├── save_weights_for_sampler_response.py (100 tokens)
            ├── stop_reason.py
            ├── tensor_data.py (1200 tokens)
         ├── audit_log_entry.py (100 tokens)
         ├── audit_log_response.py (100 tokens)
         ├── auth_token_response.py
         ├── checkpoint.py (400 tokens)
         ├── checkpoint_archive_url_response.py (100 tokens)
         ├── checkpoints_list_response.py (100 tokens)
         ├── client_config_request.py
         ├── client_config_response.py (400 tokens)
         ├── create_model_request.py (200 tokens)
         ├── create_model_response.py (100 tokens)
         ├── create_sampling_session_request.py (200 tokens)
         ├── create_sampling_session_response.py (100 tokens)
         ├── create_session_request.py (100 tokens)
         ├── create_session_response.py (100 tokens)
         ├── cursor.py (100 tokens)
         ├── datum.py (500 tokens)
         ├── encoded_text_chunk.py (100 tokens)
         ├── event_type.py
         ├── forward_backward_input.py (100 tokens)
         ├── forward_backward_output.py (400 tokens)
         ├── forward_backward_request.py (100 tokens)
         ├── forward_request.py (100 tokens)
         ├── future_retrieve_request.py (100 tokens)
         ├── future_retrieve_response.py (200 tokens)
         ├── generic_event.py (100 tokens)
         ├── get_info_request.py (100 tokens)
         ├── get_info_response.py
         ├── get_sampler_response.py
         ├── get_server_capabilities_response.py (100 tokens)
         ├── get_session_response.py (100 tokens)
         ├── health_response.py
         ├── image_asset_pointer_chunk.py (200 tokens)
         ├── image_chunk.py (300 tokens)
         ├── list_sessions_response.py
         ├── load_weights_request.py (200 tokens)
         ├── load_weights_response.py
         ├── lora_config.py (100 tokens)
         ├── loss_fn_inputs.py
         ├── loss_fn_output.py
         ├── loss_fn_type.py
         ├── model_id.py
         ├── model_input.py
         ├── model_input_chunk.py (100 tokens)
         ├── optim_step_request.py (200 tokens)
         ├── optim_step_response.py
         ├── request_error_category.py
         ├── request_failed_response.py (100 tokens)
         ├── request_id.py
         ├── sample_request.py (300 tokens)
         ├── sample_response.py (900 tokens)
         ├── sampled_sequence.py (400 tokens)
         ├── sampling_params.py
         ├── save_weights_for_sampler_request.py (200 tokens)
         ├── save_weights_for_sampler_response.py (100 tokens)
         ├── save_weights_request.py (200 tokens)
         ├── save_weights_response.py (100 tokens)
         ├── session_end_event.py (100 tokens)
         ├── session_heartbeat_request.py (100 tokens)
         ├── session_heartbeat_response.py
         ├── session_start_event.py (100 tokens)
         ├── severity.py
         ├── shared/
            ├── __init__.py
            ├── untyped_api_future.py (100 tokens)
         ├── stop_reason.py
         ├── telemetry_batch.py (100 tokens)
         ├── telemetry_event.py (100 tokens)
         ├── telemetry_response.py
         ├── telemetry_send_request.py (100 tokens)
         ├── tensor_data.py (1500 tokens)
         ├── tensor_dtype.py
         ├── topk_prompt_logprobs.py (100 tokens)
         ├── training_run.py (200 tokens)
         ├── training_runs_response.py (100 tokens)
         ├── try_again_response.py (100 tokens)
         ├── unhandled_exception_event.py (100 tokens)
         ├── unload_model_request.py (100 tokens)
         ├── unload_model_response.py (100 tokens)
         ├── weights_info_response.py (100 tokens)
├── tests/
   ├── __init__.py
   ├── conftest.py (100 tokens)
   ├── sample_file.txt
   ├── test_checkpoint_archive_url.py (1000 tokens)
   ├── test_checkpoint_delete.py (1700 tokens)
   ├── test_chunked_fwdbwd_helpers.py (900 tokens)
   ├── test_cli_output.py (1900 tokens)
   ├── test_conflict_recovery.py (600 tokens)
   ├── test_deepcopy.py (300 tokens)
   ├── test_extract_files.py (400 tokens)
   ├── test_files.py (300 tokens)
   ├── test_models.py (5.4k tokens)
   ├── test_proto_request_conv.py (2.9k tokens)
   ├── test_proto_response_conv.py (1700 tokens)
   ├── test_pydantic_conv.py (500 tokens)
   ├── test_qs.py (700 tokens)
   ├── test_required_args.py (600 tokens)
   ├── test_service_client.py (1600 tokens)
   ├── test_sidecar.py (7.8k tokens)
   ├── test_subprocess_sampling_client.py (2.9k tokens)
   ├── test_tensor_data.py (200 tokens)
   ├── test_training_client_custom_loss.py (1600 tokens)
   ├── test_transform.py (3.1k tokens)
   ├── test_utils/
      ├── test_proxy.py (200 tokens)
      ├── test_typing.py (500 tokens)
   ├── utils.py (900 tokens)
├── uv.lock (omitted)
```


## /.gitignore

```gitignore path="/.gitignore" 
.prism.log
_dev

__pycache__
.mypy_cache

dist

.venv
.idea

.env
.envrc
codegen.log
Brewfile.lock.json

```

## /.python-version

```python-version path="/.python-version" 
3.9.18

```

## /.ruff.toml

```toml path="/.ruff.toml" 
line-length = 100
force-exclude = true
exclude = [
  # Generated protobuf code
  "src/tinker/proto",
]

[lint]

# Same as monorepo but removed UP007
select = [
  # https://docs.astral.sh/ruff/rules
  # pyflakes, pycodestyle, isort
  "B905", # zip-without-explicit-strict
  "F",
  "E",
  "W",
  "I001",

  "PIE804", # unnecessary-dict-kwargs
  "PIE800", # unnecessary-spread
  "PIE796", #	non-unique-enums
  "PIE794", # duplicate-class-field-definition
  "PIE807", # reimplemented-container-builtin
  "PIE810", #multiple-starts-ends-with

  "FLY002",
  "COM818",
  "SIM",
  "Q000",

  #"UP007", # Use X | Y for type annotations
  #  Going to leave these 2 commented out for now as they play interestingly with chz.
  # We could consider using them though!
  # "TC001", # typing-only-first-party-import
  # "TC002", # typing-only-third-party-import
  "TC004", # runtime-import-in-type-checking-block
  "TC005", # empty-type-checking-block
]
ignore = ["E501", "E721", "SIM105", "SIM108", "SIM117"]
unfixable = ["B905"]
extend-safe-fixes = ["TC004", "TC005"]

```

## /.stats.yml

```yml path="/.stats.yml" 
configured_endpoints: 15
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/thinking-machines%2Ftinker-51afac49fce6fe0323489c3e809c5e2e50ce7384828362dcb0b7c2c7c9d77027.yml
openapi_spec_hash: ed32399a1f1754a8e66918aefbe9fd07
config_hash: 2ea282a8a1396267cb7c8a5e28467eb8

```

## /.sync_state

```sync_state path="/.sync_state" 
{
  "last_synced_sha": "55dcf638a1179450dae44cca4c643741d9b52ab0",
  "last_sync_time": "2026-05-31T00:48:52.326514"
}
```

## /README.md

<h1 align="center">Tinker Python SDK</h1>
<div align="center">
  <img src="https://github.com/thinking-machines-lab/tinker/blob/829c151ba7c6740a84db02e41f03825084c37fed/docs/images/logo.png" width="60%" />

  Documentation:
  <a href="http://tinker-docs.thinkingmachines.ai/">tinker-docs.thinkingmachines.ai</a>
</div>


## /docs/README.md

# Tinker SDK Documentation

This directory contains auto-generated API documentation for the Tinker Python SDK.

### Generate Docs

Run the documentation generation script:
```bash
uv run scripts/generate_docs.py
```

### Notes

* Only types/classes/methods with doc-string attached will have docs generated
* Please check in the generated artifacts


## /docs/api/_meta.json

```json path="/docs/api/_meta.json" 
{
  "serviceclient": "ServiceClient",
  "trainingclient": "TrainingClient",
  "samplingclient": "SamplingClient",
  "restclient": "RestClient",
  "apifuture": "APIFuture",
  "types": "Parameters",
  "exceptions": "Exceptions"
}

```

## /docs/api/apifuture.md

API Future classes for handling async operations with retry logic.

## `APIFuture` Objects

```python
class APIFuture(ABC, Generic[T])
```

Abstract base class for futures that can be awaited or accessed synchronously.

APIFuture provides a unified interface for handling async operations that can
be accessed both synchronously (via result()) and asynchronously (via await or result_async()).
This allows for flexible usage patterns in both sync and async contexts.

The future can be awaited directly in async contexts:
```python
result = await api_future  # Equivalent to await api_future.result_async()
```

Or accessed synchronously:
```python
result = api_future.result()  # Blocks until complete
```

Args:
- `T`: The type of the result value

Example:
```python
# In async context
future = training_client.forward_backward(data, "cross_entropy")
result = await future  # Or await future.result_async()

# In sync context
future = training_client.forward_backward(data, "cross_entropy")
result = future.result()
```

#### `result_async`

```python
async def result_async(timeout: float | None = None) -> T
```

Get the result asynchronously with optional timeout.

Args:
- `timeout`: Maximum time to wait in seconds. None means wait indefinitely.

Returns:
- The result value of type `T`

Raises:
    TimeoutError: If timeout is exceeded

#### `result`

```python
def result(timeout: float | None = None) -> T
```

Get the result synchronously with optional timeout.

Args:
- `timeout`: Maximum time to wait in seconds. None means wait indefinitely.

Returns:
- The result value of type `T`

Raises:
    TimeoutError: If timeout is exceeded

## `AwaitableConcurrentFuture` Objects

```python
class AwaitableConcurrentFuture(APIFuture[T])
```

Implementation of APIFuture that wraps a concurrent.futures.Future.

This class bridges Python's concurrent.futures with asyncio, allowing a
standard Future to be used in async contexts. It's commonly returned by
Tinker API methods to provide both sync and async access patterns.

Args:
- `future`: A concurrent.futures.Future to wrap

Example:
```python
# Internal usage - typically you receive these from API methods
concurrent_future = some_operation()
api_future = AwaitableConcurrentFuture(concurrent_future)

# Can be used synchronously
result = api_future.result()

# Or asynchronously
result = await api_future
```

#### `result`

```python
def result(timeout: float | None = None) -> T
```

Get the result synchronously with optional timeout.

Args:
- `timeout`: Maximum time to wait in seconds. None means wait indefinitely.

Returns:
- The result value of type `T`

Raises:
    TimeoutError: If timeout is exceeded
    Exception: Any exception raised by the underlying operation

Example:
```python
future = rest_client.get_training_run("run-id")
result = future.result(timeout=30)  # Wait up to 30 seconds
```

#### `result_async`

```python
async def result_async(timeout: float | None = None) -> T
```

Async version of result.

#### `future`

```python
def future() -> ConcurrentFuture[T]
```

Get the underlying concurrent.futures.Future.

Returns:
- The wrapped `ConcurrentFuture` object

Example:
```python
api_future = rest_client.get_training_run("run-id")
concurrent_future = api_future.future()
# Can now use standard concurrent.futures methods
if concurrent_future.done():
    result = concurrent_future.result()
```


## /docs/api/exceptions.md

## `TinkerError` Objects

```python
class TinkerError(Exception)
```

Base exception for all Tinker-related errors.

## `APIError` Objects

```python
class APIError(TinkerError)
```

Base class for all API-related errors.

#### `body`

The API response body.

If the API responded with a valid JSON structure then this property will be the
decoded result.

If it isn't a valid JSON structure then this will be the raw response.

If there was no response associated with this error then it will be `None`.

## `APIResponseValidationError` Objects

```python
class APIResponseValidationError(APIError)
```

Raised when API response doesn't match expected schema.

## `APIStatusError` Objects

```python
class APIStatusError(APIError)
```

Raised when an API response has a status code of 4xx or 5xx.

## `APIConnectionError` Objects

```python
class APIConnectionError(APIError)
```

Raised when a connection error occurs while making an API request.

## `APITimeoutError` Objects

```python
class APITimeoutError(APIConnectionError)
```

Raised when an API request times out.

## `BadRequestError` Objects

```python
class BadRequestError(APIStatusError)
```

HTTP 400: The request was invalid or malformed.

## `AuthenticationError` Objects

```python
class AuthenticationError(APIStatusError)
```

HTTP 401: Authentication credentials are missing or invalid.

## `PermissionDeniedError` Objects

```python
class PermissionDeniedError(APIStatusError)
```

HTTP 403: Insufficient permissions to access the resource.

## `NotFoundError` Objects

```python
class NotFoundError(APIStatusError)
```

HTTP 404: The requested resource was not found.

## `ConflictError` Objects

```python
class ConflictError(APIStatusError)
```

HTTP 409: The request conflicts with the current state of the resource.

## `UnprocessableEntityError` Objects

```python
class UnprocessableEntityError(APIStatusError)
```

HTTP 422: The request was well-formed but contains semantic errors.

## `RateLimitError` Objects

```python
class RateLimitError(APIStatusError)
```

HTTP 429: Too many requests, rate limit exceeded.

## `InternalServerError` Objects

```python
class InternalServerError(APIStatusError)
```

HTTP 500+: An error occurred on the server.

## `SidecarError` Objects

```python
class SidecarError(TinkerError)
```

Base exception for subprocess sidecar errors.

## `SidecarStartupError` Objects

```python
class SidecarStartupError(SidecarError)
```

Raised when the sidecar subprocess fails to start or times out.

## `SidecarDiedError` Objects

```python
class SidecarDiedError(SidecarError)
```

Raised when the sidecar subprocess exits unexpectedly while requests are pending.

## `SidecarIPCError` Objects

```python
class SidecarIPCError(SidecarError)
```

Raised when communication with the sidecar subprocess fails.

## `RequestFailedError` Objects

```python
class RequestFailedError(TinkerError)
```

Raised when an asynchronous request completes in a failed state.


## /docs/api/restclient.md

RestClient for Tinker API REST operations.

## `RestClient` Objects

```python
class RestClient(TelemetryProvider)
```

Client for REST API operations like listing checkpoints and metadata.

The RestClient provides access to various REST endpoints for querying
model information, checkpoints, and other resources. You typically get one
by calling `service_client.create_rest_client()`.

Key methods:
- list_checkpoints() - list available model checkpoints (both training and sampler)
- list_user_checkpoints() - list all checkpoints across all user's training runs
- get_training_run() - get model information and metadata as ModelEntry
- delete_checkpoint() - delete an existing checkpoint for a training run
- get_checkpoint_archive_url() - get signed URL to download checkpoint archive
- publish_checkpoint_from_tinker_path() - publish a checkpoint to make it public
- unpublish_checkpoint_from_tinker_path() - unpublish a checkpoint to make it private
- set_checkpoint_ttl_from_tinker_path() - set or remove TTL on a checkpoint

Args:
- `holder`: Internal client managing HTTP connections and async operations

Example:
```python
rest_client = service_client.create_rest_client()
training_run = rest_client.get_training_run("run-id").result()
print(f"Training Run: {training_run.training_run_id}, LoRA: {training_run.is_lora}")
checkpoints = rest_client.list_checkpoints("run-id").result()
print(f"Found {len(checkpoints.checkpoints)} checkpoints")
for checkpoint in checkpoints.checkpoints:
    print(f"  {checkpoint.checkpoint_type}: {checkpoint.checkpoint_id}")
```

#### `get_training_run`

```python
def get_training_run(
    training_run_id: types.ModelID,
    access_scope: Literal["owned", "accessible"] = "owned"
) -> ConcurrentFuture[types.TrainingRun]
```

Get training run info.

Args:
- `training_run_id`: The training run ID to get information for

Returns:
- A `Future` containing the training run information

Example:
```python
future = rest_client.get_training_run("run-id")
response = future.result()
print(f"Training Run ID: {response.training_run_id}, Base: {response.base_model}")
```

#### `get_training_run_async`

```python
async def get_training_run_async(
    training_run_id: types.ModelID,
    access_scope: Literal["owned",
                          "accessible"] = "owned") -> types.TrainingRun
```

Async version of get_training_run.

#### `get_training_run_by_tinker_path`

```python
def get_training_run_by_tinker_path(
    tinker_path: str,
    access_scope: Literal["owned", "accessible"] = "owned"
) -> ConcurrentFuture[types.TrainingRun]
```

Get training run info.

Args:
- `tinker_path`: The tinker path to the checkpoint

Returns:
- A `Future` containing the training run information

Example:
```python
future = rest_client.get_training_run_by_tinker_path("tinker://run-id/weights/checkpoint-001")
response = future.result()
print(f"Training Run ID: {response.training_run_id}, Base: {response.base_model}")
```

#### `get_training_run_by_tinker_path_async`

```python
async def get_training_run_by_tinker_path_async(
    tinker_path: str,
    access_scope: Literal["owned",
                          "accessible"] = "owned") -> types.TrainingRun
```

Async version of get_training_run_by_tinker_path.

#### `get_weights_info_by_tinker_path`

```python
def get_weights_info_by_tinker_path(
        tinker_path: str) -> APIFuture[types.WeightsInfoResponse]
```

Get checkpoint information from a tinker path.

Args:
- `tinker_path`: The tinker path to the checkpoint

Returns:
- An `APIFuture` containing the checkpoint information. The future is awaitable.

Example:
```python
future = rest_client.get_weights_info_by_tinker_path("tinker://run-id/weights/checkpoint-001")
response = future.result()  # or await future
print(f"Base Model: {response.base_model}, LoRA Rank: {response.lora_rank}")
```

#### `list_training_runs`

```python
def list_training_runs(
    limit: int = 20,
    offset: int = 0,
    access_scope: Literal["owned", "accessible"] = "owned"
) -> ConcurrentFuture[types.TrainingRunsResponse]
```

List training runs with pagination support.

Args:
- `limit`: Maximum number of training runs to return (default 20)
- `offset`: Offset for pagination (default 0)

Returns:
- A `Future` containing the `TrainingRunsResponse` with training runs and cursor info

Example:
```python
future = rest_client.list_training_runs(limit=50)
response = future.result()
print(f"Found {len(response.training_runs)} training runs")
print(f"Total: {response.cursor.total_count}")
# Get next page
next_page = rest_client.list_training_runs(limit=50, offset=50)
```

#### `list_training_runs_async`

```python
async def list_training_runs_async(
    limit: int = 20,
    offset: int = 0,
    access_scope: Literal["owned", "accessible"] = "owned"
) -> types.TrainingRunsResponse
```

Async version of list_training_runs.

#### `list_checkpoints`

```python
def list_checkpoints(
    training_run_id: types.ModelID
) -> ConcurrentFuture[types.CheckpointsListResponse]
```

List available checkpoints (both training and sampler).

Args:
- `training_run_id`: The training run ID to list checkpoints for

Returns:
- A `Future` containing the `CheckpointsListResponse` with available checkpoints

Example:
```python
future = rest_client.list_checkpoints("run-id")
response = future.result()
for checkpoint in response.checkpoints:
    if checkpoint.checkpoint_type == "training":
        print(f"Training checkpoint: {checkpoint.checkpoint_id}")
    elif checkpoint.checkpoint_type == "sampler":
        print(f"Sampler checkpoint: {checkpoint.checkpoint_id}")
```

#### `list_checkpoints_async`

```python
async def list_checkpoints_async(
        training_run_id: types.ModelID) -> types.CheckpointsListResponse
```

Async version of list_checkpoints.

#### `get_checkpoint_archive_url`

```python
def get_checkpoint_archive_url(
    training_run_id: types.ModelID, checkpoint_id: str
) -> ConcurrentFuture[types.CheckpointArchiveUrlResponse]
```

Get signed URL to download checkpoint archive.

Args:
- `training_run_id`: The training run ID to download weights for
- `checkpoint_id`: The checkpoint ID to download

Returns:
- A `Future` containing the `CheckpointArchiveUrlResponse` with signed URL and expiration

Example:
```python
future = rest_client.get_checkpoint_archive_url("run-id", "checkpoint-123")
response = future.result()
print(f"Download URL: {response.url}")
print(f"Expires at: {response.expires_at}")
# Use the URL to download the archive with your preferred HTTP client
```

#### `get_checkpoint_archive_url_async`

```python
async def get_checkpoint_archive_url_async(
        training_run_id: types.ModelID,
        checkpoint_id: str) -> types.CheckpointArchiveUrlResponse
```

Async version of get_checkpoint_archive_url.

#### `delete_checkpoint`

```python
def delete_checkpoint(training_run_id: types.ModelID,
                      checkpoint_id: str) -> ConcurrentFuture[None]
```

Delete a checkpoint for a training run.

#### `delete_checkpoint_async`

```python
async def delete_checkpoint_async(training_run_id: types.ModelID,
                                  checkpoint_id: str) -> None
```

Async version of delete_checkpoint.

#### `delete_checkpoint_from_tinker_path`

```python
def delete_checkpoint_from_tinker_path(
        tinker_path: str) -> ConcurrentFuture[None]
```

Delete a checkpoint referenced by a tinker path.

#### `delete_checkpoint_from_tinker_path_async`

```python
async def delete_checkpoint_from_tinker_path_async(tinker_path: str) -> None
```

Async version of delete_checkpoint_from_tinker_path.

#### `get_checkpoint_archive_url_from_tinker_path`

```python
def get_checkpoint_archive_url_from_tinker_path(
        tinker_path: str
) -> ConcurrentFuture[types.CheckpointArchiveUrlResponse]
```

Get signed URL to download checkpoint archive.

Args:
- `tinker_path`: The tinker path to the checkpoint

Returns:
- A `Future` containing the `CheckpointArchiveUrlResponse` with signed URL and expiration

#### `get_checkpoint_archive_url_from_tinker_path_async`

```python
async def get_checkpoint_archive_url_from_tinker_path_async(
        tinker_path: str) -> types.CheckpointArchiveUrlResponse
```

Async version of get_checkpoint_archive_url_from_tinker_path.

#### `publish_checkpoint_from_tinker_path`

```python
def publish_checkpoint_from_tinker_path(
        tinker_path: str) -> ConcurrentFuture[None]
```

Publish a checkpoint referenced by a tinker path to make it publicly accessible.

Only the exact owner of the training run can publish checkpoints.
Published checkpoints can be unpublished using the unpublish_checkpoint_from_tinker_path method.

Args:
- `tinker_path`: The tinker path to the checkpoint (e.g., "tinker://run-id/weights/0001")

Returns:
- A `Future` that completes when the checkpoint is published

Raises:
    HTTPException: 400 if checkpoint identifier is invalid
    HTTPException: 404 if checkpoint not found or user doesn't own the training run
    HTTPException: 409 if checkpoint is already public
    HTTPException: 500 if there's an error publishing the checkpoint

Example:
```python
future = rest_client.publish_checkpoint_from_tinker_path("tinker://run-id/weights/0001")
future.result()  # Wait for completion
print("Checkpoint published successfully")
```

#### `publish_checkpoint_from_tinker_path_async`

```python
async def publish_checkpoint_from_tinker_path_async(tinker_path: str) -> None
```

Async version of publish_checkpoint_from_tinker_path.

#### `unpublish_checkpoint_from_tinker_path`

```python
def unpublish_checkpoint_from_tinker_path(
        tinker_path: str) -> ConcurrentFuture[None]
```

Unpublish a checkpoint referenced by a tinker path to make it private again.

Only the exact owner of the training run can unpublish checkpoints.
This reverses the effect of publishing a checkpoint.

Args:
- `tinker_path`: The tinker path to the checkpoint (e.g., "tinker://run-id/weights/0001")

Returns:
- A `Future` that completes when the checkpoint is unpublished

Raises:
    HTTPException: 400 if checkpoint identifier is invalid
    HTTPException: 404 if checkpoint not found or user doesn't own the training run
    HTTPException: 409 if checkpoint is already private
    HTTPException: 500 if there's an error unpublishing the checkpoint

Example:
```python
future = rest_client.unpublish_checkpoint_from_tinker_path("tinker://run-id/weights/0001")
future.result()  # Wait for completion
print("Checkpoint unpublished successfully")
```

#### `unpublish_checkpoint_from_tinker_path_async`

```python
async def unpublish_checkpoint_from_tinker_path_async(
        tinker_path: str) -> None
```

Async version of unpublish_checkpoint_from_tinker_path.

#### `set_checkpoint_ttl_from_tinker_path`

```python
def set_checkpoint_ttl_from_tinker_path(
        tinker_path: str, ttl_seconds: int | None) -> ConcurrentFuture[None]
```

Set or remove the TTL on a checkpoint referenced by a tinker path.

If ttl_seconds is provided, the checkpoint will expire after that many seconds from now.
If ttl_seconds is None, any existing expiration will be removed.

Args:
- `tinker_path`: The tinker path to the checkpoint (e.g., "tinker://run-id/weights/0001")
- `ttl_seconds`: Number of seconds until expiration, or None to remove TTL

Returns:
- A `Future` that completes when the TTL is set

Raises:
    HTTPException: 400 if checkpoint identifier is invalid or ttl_seconds <= 0
    HTTPException: 404 if checkpoint not found or user doesn't own the training run
    HTTPException: 500 if there's an error setting the TTL

Example:
```python
future = rest_client.set_checkpoint_ttl_from_tinker_path("tinker://run-id/weights/0001", 86400)
future.result()  # Wait for completion
print("Checkpoint TTL set successfully")
```

#### `set_checkpoint_ttl_from_tinker_path_async`

```python
async def set_checkpoint_ttl_from_tinker_path_async(
        tinker_path: str, ttl_seconds: int | None) -> None
```

Async version of set_checkpoint_ttl_from_tinker_path.

#### `list_user_checkpoints`

```python
def list_user_checkpoints(
        limit: int = 100,
        offset: int = 0) -> ConcurrentFuture[types.CheckpointsListResponse]
```

List all checkpoints for the current user across all their training runs.

This method retrieves checkpoints from all training runs owned by the authenticated user,
sorted by time (newest first). It supports pagination for efficiently handling large
numbers of checkpoints.

Args:
- `limit`: Maximum number of checkpoints to return (default 100)
- `offset`: Offset for pagination (default 0)

Returns:
- A `Future` containing the `CheckpointsListResponse` with checkpoints and cursor info

Example:
```python
future = rest_client.list_user_checkpoints(limit=50)
response = future.result()
print(f"Found {len(response.checkpoints)} checkpoints")
print(f"Total: {response.cursor.total_count if response.cursor else 'Unknown'}")
for checkpoint in response.checkpoints:
    print(f"  {checkpoint.training_run_id}/{checkpoint.checkpoint_id}")
# Get next page if there are more checkpoints
if response.cursor and response.cursor.offset + response.cursor.limit < response.cursor.total_count:
    next_page = rest_client.list_user_checkpoints(limit=50, offset=50)
```

#### `list_user_checkpoints_async`

```python
async def list_user_checkpoints_async(limit: int = 100,
                                      offset: int = 0
                                      ) -> types.CheckpointsListResponse
```

Async version of list_user_checkpoints.

#### `get_session`

```python
def get_session(
    session_id: str,
    access_scope: Literal["owned", "accessible"] = "owned"
) -> ConcurrentFuture[types.GetSessionResponse]
```

Get session information including all training runs and samplers.

Args:
- `session_id`: The session ID to get information for

Returns:
- A `Future` containing the `GetSessionResponse` with training_run_ids and sampler_ids

Example:
```python
future = rest_client.get_session("session-id")
response = future.result()
print(f"Training runs: {len(response.training_run_ids)}")
print(f"Samplers: {len(response.sampler_ids)}")
```

#### `get_session_async`

```python
async def get_session_async(
    session_id: str,
    access_scope: Literal["owned", "accessible"] = "owned"
) -> types.GetSessionResponse
```

Async version of get_session.

#### `list_sessions`

```python
def list_sessions(
    limit: int = 20,
    offset: int = 0,
    access_scope: Literal["owned", "accessible"] = "owned"
) -> ConcurrentFuture[types.ListSessionsResponse]
```

List sessions with pagination support.

Args:
- `limit`: Maximum number of sessions to return (default 20)
- `offset`: Offset for pagination (default 0)

Returns:
- A `Future` containing the `ListSessionsResponse` with list of session IDs

Example:
```python
future = rest_client.list_sessions(limit=50)
response = future.result()
print(f"Found {len(response.sessions)} sessions")
# Get next page
next_page = rest_client.list_sessions(limit=50, offset=50)
```

#### `list_sessions_async`

```python
async def list_sessions_async(
    limit: int = 20,
    offset: int = 0,
    access_scope: Literal["owned", "accessible"] = "owned"
) -> types.ListSessionsResponse
```

Async version of list_sessions.

#### `get_sampler`

```python
def get_sampler(sampler_id: str) -> APIFuture[types.GetSamplerResponse]
```

Get sampler information.

Args:
- `sampler_id`: The sampler ID (sampling_session_id) to get information for

Returns:
- An `APIFuture` containing the `GetSamplerResponse` with sampler details

Example:
```python
# Sync usage
future = rest_client.get_sampler("session-id:sample:0")
response = future.result()
print(f"Base model: {response.base_model}")
print(f"Model path: {response.model_path}")

# Async usage
response = await rest_client.get_sampler("session-id:sample:0")
print(f"Base model: {response.base_model}")
```

#### `get_sampler_async`

```python
async def get_sampler_async(sampler_id: str) -> types.GetSamplerResponse
```

Async version of get_sampler.


## /docs/api/samplingclient.md

SamplingClient for Tinker API.

## `SamplingClient` Objects

```python
class SamplingClient(TelemetryProvider, QueueStateObserver)
```

Client for text generation and inference from trained or base models.

The SamplingClient lets you generate text tokens from either a base model or from weights
you've saved using a TrainingClient. You typically get one by calling
`service_client.create_sampling_client()` or `training_client.save_weights_and_get_sampling_client()`.

Key methods:
- sample() - generate text completions with customizable parameters
- compute_logprobs() - get log probabilities for prompt tokens

Create method parameters:
- `model_path`: Path to saved model weights (starts with 'tinker://')
- `base_model`: Name of base model to use for inference (e.g., 'Qwen/Qwen3-8B')
- `retry_config`: Configuration for retrying failed requests

Example:
```python
sampling_client = service_client.create_sampling_client(base_model="Qwen/Qwen3-8B")
prompt = types.ModelInput.from_ints(tokenizer.encode("The weather today is"))
params = types.SamplingParams(max_tokens=20, temperature=0.7)
future = sampling_client.sample(prompt=prompt, sampling_params=params, num_samples=1)
result = future.result()
```

Multi-processing support:
This class is picklable, so it can be passed to a separate process/worker to sample. It is also
safe to pass the same instance of SamplingClient to multiple processes/workers.

If you are using Tinker SDK with more than one process you should always create SamplingClient from
the main process and then pass it to the other processes/workers.
ServiceClient and TrainingClient should always be managed from the main process.

Subprocess isolation:
Set ``TINKER_SUBPROCESS_SAMPLING=1`` to run sample() and compute_logprobs() in a dedicated
subprocess, preventing GIL contention from CPU-heavy user code (grading, environment
interactions) from stalling networking IO and heartbeats. This is transparent — the same
API works with or without it.

#### `sample`

```python
def sample(
        prompt: types.ModelInput,
        num_samples: int,
        sampling_params: types.SamplingParams,
        include_prompt_logprobs: bool = False,
        topk_prompt_logprobs: int = 0
) -> ConcurrentFuture[types.SampleResponse]
```

Generate text completions from the model.

Args:
- `prompt`: The input tokens as ModelInput
- `num_samples`: Number of independent samples to generate
- `sampling_params`: Parameters controlling generation (temperature, max_tokens, etc.)
- `include_prompt_logprobs`: Whether to include log probabilities for prompt tokens
- `topk_prompt_logprobs`: Number of top token log probabilities to return per position

Returns:
- A `Future` containing the `SampleResponse` with generated text

Example:
```python
prompt = types.ModelInput.from_ints(tokenizer.encode("The weather today is"))
params = types.SamplingParams(max_tokens=20, temperature=0.7)
future = sampling_client.sample(prompt=prompt, sampling_params=params, num_samples=1)
result = future.result()
for sample in result.samples:
    print(tokenizer.decode(sample.tokens))
```

#### `sample_async`

```python
async def sample_async(prompt: types.ModelInput,
                       num_samples: int,
                       sampling_params: types.SamplingParams,
                       include_prompt_logprobs: bool = False,
                       topk_prompt_logprobs: int = 0) -> types.SampleResponse
```

Async version of sample.

#### `compute_logprobs`

```python
def compute_logprobs(
        prompt: types.ModelInput) -> ConcurrentFuture[list[float | None]]
```

Compute log probabilities for prompt tokens.

Args:
- `prompt`: The input tokens as ModelInput

Returns:
- A `Future` containing a list of log probabilities for each token in the prompt.
    None values indicate tokens where log probabilities couldn't be computed.

Example:
```python
prompt = types.ModelInput.from_ints(tokenizer.encode("Hello world"))
future = sampling_client.compute_logprobs(prompt)
logprobs = future.result()
for i, logprob in enumerate(logprobs):
    if logprob is not None:
        print(f"Token {i}: logprob = {logprob:.4f}")
```

#### `compute_logprobs_async`

```python
async def compute_logprobs_async(
        prompt: types.ModelInput) -> list[float | None]
```

Async version of compute_logprobs.

#### `get_tokenizer`

```python
def get_tokenizer() -> PreTrainedTokenizer
```

Get the tokenizer for the current model.

Returns:
- `PreTrainedTokenizer` compatible with the model

#### `get_base_model`

```python
def get_base_model() -> str
```

Get the base model name for the current sampling session.

#### `get_base_model_async`

```python
async def get_base_model_async() -> str
```

Async version of get_base_model.

#### `__reduce__`

```python
def __reduce__() -> tuple[Any, tuple[_SamplingClientPickleState]]
```

Enable pickling of SamplingClient for subprocess use.

Serializes into a ``_SamplingClientPickleState`` dataclass. The
``_sampling_client_sidecar_handle`` handle is deliberately omitted — only a
bool flag is stored. The unpickled copy creates its own handle via
the per-process sidecar singleton. Do not add ``__getstate__``
without preserving this behavior.


## /docs/api/serviceclient.md

ServiceClient for Tinker API.

## `ServiceClient` Objects

```python
class ServiceClient(TelemetryProvider)
```

The ServiceClient is the main entry point for the Tinker API. It provides methods to:
- Query server capabilities and health status
- Generate TrainingClient instances for model training workflows
- Generate SamplingClient instances for text generation and inference
- Generate RestClient instances for REST API operations like listing weights

Args:
    user_metadata: Optional metadata attached to the created session.
    project_id: Optional project ID to attach to the created session.
    **kwargs: advanced options passed to the underlying HTTP client,
             including API keys, headers, and connection settings.

Example:
```python
# Near instant
client = ServiceClient()

# Takes a moment as we initialize the model and assign resources
training_client = client.create_lora_training_client(base_model="Qwen/Qwen3-8B")

# Near-instant
sampling_client = client.create_sampling_client(base_model="Qwen/Qwen3-8B")

# Near-instant
rest_client = client.create_rest_client()
```

#### `get_server_capabilities`

```python
def get_server_capabilities() -> types.GetServerCapabilitiesResponse
```

Query the server's supported features and capabilities.

Returns:
- `GetServerCapabilitiesResponse` with available models, features, and limits

Example:
```python
capabilities = service_client.get_server_capabilities()
print(f"Supported models: {capabilities.supported_models}")
print(f"Max batch size: {capabilities.max_batch_size}")
```

#### `get_server_capabilities_async`

```python
async def get_server_capabilities_async(
) -> types.GetServerCapabilitiesResponse
```

Async version of get_server_capabilities.

#### `create_lora_training_client`

```python
def create_lora_training_client(
        base_model: str,
        rank: int = 32,
        seed: int | None = None,
        train_mlp: bool = True,
        train_attn: bool = True,
        train_unembed: bool = True,
        user_metadata: dict[str, str] | None = None) -> TrainingClient
```

Create a TrainingClient for LoRA fine-tuning.

Args:
- `base_model`: Name of the base model to fine-tune (e.g., "Qwen/Qwen3-8B")
- `rank`: LoRA rank controlling the size of adaptation matrices (default 32)
- `seed`: Random seed for initialization. None means random seed.
- `train_mlp`: Whether to train MLP layers (default True)
- `train_attn`: Whether to train attention layers (default True)
- `train_unembed`: Whether to train unembedding layers (default True)
- `user_metadata`: Optional metadata to attach to the training run

Returns:
- `TrainingClient` configured for LoRA training

Example:
```python
training_client = service_client.create_lora_training_client(
    base_model="Qwen/Qwen3-8B",
    rank=16,
    train_mlp=True,
    train_attn=True
)
# Now use training_client.forward_backward() to train
```

#### `create_lora_training_client_async`

```python
async def create_lora_training_client_async(
        base_model: str,
        rank: int = 32,
        seed: int | None = None,
        train_mlp: bool = True,
        train_attn: bool = True,
        train_unembed: bool = True,
        user_metadata: dict[str, str] | None = None) -> TrainingClient
```

Async version of create_lora_training_client.

#### `create_training_client_from_state`

```python
def create_training_client_from_state(
        path: str,
        user_metadata: dict[str, str] | None = None) -> TrainingClient
```

Create a TrainingClient from saved model weights.

This loads only the model weights, not optimizer state. To also restore
optimizer state (e.g., Adam momentum), use create_training_client_from_state_with_optimizer.

Args:
- `path`: Tinker path to saved weights (e.g., "tinker://run-id/weights/checkpoint-001")
- `user_metadata`: Optional metadata to attach to the new training run

Returns:
- `TrainingClient` loaded with the specified weights

Example:
```python
# Resume training from a checkpoint (weights only, optimizer resets)
training_client = service_client.create_training_client_from_state(
    "tinker://run-id/weights/checkpoint-001"
)
# Continue training from the loaded state
```

#### `create_training_client_from_state_async`

```python
async def create_training_client_from_state_async(
        path: str,
        user_metadata: dict[str, str] | None = None) -> TrainingClient
```

Async version of create_training_client_from_state.

#### `create_training_client_from_state_with_optimizer`

```python
def create_training_client_from_state_with_optimizer(
        path: str,
        user_metadata: dict[str, str] | None = None) -> TrainingClient
```

Create a TrainingClient from saved model weights and optimizer state.

This is similar to create_training_client_from_state but also restores
optimizer state (e.g., Adam momentum), which is useful for resuming
training exactly where it left off.

Args:
- `path`: Tinker path to saved weights (e.g., "tinker://run-id/weights/checkpoint-001")
- `user_metadata`: Optional metadata to attach to the new training run

Returns:
- `TrainingClient` loaded with the specified weights and optimizer state

Example:
```python
# Resume training from a checkpoint with optimizer state
training_client = service_client.create_training_client_from_state_with_optimizer(
    "tinker://run-id/weights/checkpoint-001"
)
# Continue training with restored optimizer momentum
```

#### `create_training_client_from_state_with_optimizer_async`

```python
async def create_training_client_from_state_with_optimizer_async(
        path: str,
        user_metadata: dict[str, str] | None = None) -> TrainingClient
```

Async version of create_training_client_from_state_with_optimizer.

#### `create_sampling_client`

```python
def create_sampling_client(
        model_path: str | None = None,
        base_model: str | None = None,
        retry_config: RetryConfig | None = None) -> SamplingClient
```

Create a SamplingClient for text generation.

Args:
- `model_path`: Path to saved model weights (e.g., "tinker://run-id/weights/checkpoint-001")
- `base_model`: Name of base model to use (e.g., "Qwen/Qwen3-8B")
- `retry_config`: Optional configuration for retrying failed requests

Returns:
- `SamplingClient` configured for text generation

Raises:
    ValueError: If neither model_path nor base_model is provided

Example:
```python
# Use a base model
sampling_client = service_client.create_sampling_client(
    base_model="Qwen/Qwen3-8B"
)

# Or use saved weights
sampling_client = service_client.create_sampling_client(
    model_path="tinker://run-id/weights/checkpoint-001"
)
```

#### `create_sampling_client_async`

```python
async def create_sampling_client_async(
        model_path: str | None = None,
        base_model: str | None = None,
        retry_config: RetryConfig | None = None) -> SamplingClient
```

Async version of create_sampling_client.

#### `create_rest_client`

```python
def create_rest_client() -> RestClient
```

Create a RestClient for REST API operations.

The RestClient provides access to various REST endpoints for querying
model information, checkpoints, sessions, and managing checkpoint visibility.

Returns:
- `RestClient` for accessing REST API endpoints

Example:
```python
rest_client = service_client.create_rest_client()

# List checkpoints for a training run
checkpoints = rest_client.list_checkpoints("run-id").result()

# Get training run info
training_run = rest_client.get_training_run("run-id").result()

# Publish a checkpoint
rest_client.publish_checkpoint_from_tinker_path(
    "tinker://run-id/weights/checkpoint-001"
).result()
```


## /docs/api/trainingclient.md

TrainingClient for Tinker API.

## `TrainingClient` Objects

```python
class TrainingClient(TelemetryProvider)
```

Client for training ML models with forward/backward passes and optimization.

The TrainingClient corresponds to a fine-tuned model that you can train and sample from.
You typically get one by calling `service_client.create_lora_training_client()`.
Key methods:
- forward_backward() - compute gradients for training
- optim_step() - update model parameters with Adam optimizer
- save_weights_and_get_sampling_client() - export trained model for inference

Args:
- `holder`: Internal client managing HTTP connections and async operations
- `model_id`: Unique identifier for the model to train. Required for training operations.

Example:
```python
training_client = service_client.create_lora_training_client(base_model="Qwen/Qwen3-8B")
fwdbwd_future = training_client.forward_backward(training_data, "cross_entropy")
optim_future = training_client.optim_step(types.AdamParams(learning_rate=1e-4))
fwdbwd_result = fwdbwd_future.result()  # Wait for gradients
optim_result = optim_future.result()    # Wait for parameter update
sampling_client = training_client.save_weights_and_get_sampling_client("my-model")
```

#### `forward`

```python
def forward(
    data: List[types.Datum],
    loss_fn: types.LossFnType,
    loss_fn_config: Dict[str, float] | None = None
) -> APIFuture[types.ForwardBackwardOutput]
```

Compute forward pass without gradients.

Args:
- `data`: List of training data samples
- `loss_fn`: Loss function type (e.g., "cross_entropy")
- `loss_fn_config`: Optional configuration for the loss function

Returns:
- `APIFuture` containing the forward pass outputs and loss

Example:
```python
data = [types.Datum(
    model_input=types.ModelInput.from_ints(tokenizer.encode("Hello")),
    loss_fn_inputs={"target_tokens": types.ModelInput.from_ints(tokenizer.encode("world"))}
)]
future = training_client.forward(data, "cross_entropy")
result = await future
print(f"Loss: {result.loss}")
```

#### `forward_async`

```python
async def forward_async(
    data: List[types.Datum],
    loss_fn: types.LossFnType,
    loss_fn_config: Dict[str, float] | None = None
) -> APIFuture[types.ForwardBackwardOutput]
```

Async version of forward.

#### `forward_backward`

```python
def forward_backward(
    data: List[types.Datum],
    loss_fn: types.LossFnType,
    loss_fn_config: Dict[str, float] | None = None
) -> APIFuture[types.ForwardBackwardOutput]
```

Compute forward pass and backward pass to calculate gradients.

Args:
- `data`: List of training data samples
- `loss_fn`: Loss function type (e.g., "cross_entropy")
- `loss_fn_config`: Optional configuration for the loss function

Returns:
- `APIFuture` containing the forward/backward outputs, loss, and gradients

Example:
```python
data = [types.Datum(
    model_input=types.ModelInput.from_ints(tokenizer.encode("Hello")),
    loss_fn_inputs={"target_tokens": types.ModelInput.from_ints(tokenizer.encode("world"))}
)]

# Compute gradients
fwdbwd_future = training_client.forward_backward(data, "cross_entropy")

# Update parameters
optim_future = training_client.optim_step(
    types.AdamParams(learning_rate=1e-4)
)

fwdbwd_result = await fwdbwd_future
print(f"Loss: {fwdbwd_result.loss}")
```

#### `forward_backward_async`

```python
async def forward_backward_async(
    data: List[types.Datum],
    loss_fn: types.LossFnType,
    loss_fn_config: Dict[str, float] | None = None
) -> APIFuture[types.ForwardBackwardOutput]
```

Async version of forward_backward.

#### `forward_backward_custom`

```python
def forward_backward_custom(
    data: List[types.Datum],
    loss_fn: CustomLossFnV1,
    *,
    loss_type_input: Literal["logprobs"] = "logprobs"
) -> APIFuture[types.ForwardBackwardOutput]
```

Compute forward/backward with a custom loss function.

Allows you to define custom loss functions that operate on log probabilities.
The custom function receives logprobs and computes loss and gradients.

Args:
- `data`: List of training data samples
- `loss_fn`: Custom loss function that takes (data, logprobs) and returns (loss, metrics)
- `loss_type_input`: Input space for `loss_fn`. Currently the only supported value is `"logprobs"`.

Returns:
- `APIFuture` containing the forward/backward outputs with custom loss

Example:
```python
def custom_loss(data, logprobs_list):
    # Custom loss computation
    loss = torch.mean(torch.stack([torch.mean(lp) for lp in logprobs_list]))
    metrics = {"custom_metric": loss.item()}
    return loss, metrics

future = training_client.forward_backward_custom(data, custom_loss)
result = future.result()
print(f"Custom loss: {result.loss}")
print(f"Metrics: {result.metrics}")
```

#### `forward_backward_custom_async`

```python
async def forward_backward_custom_async(
    data: List[types.Datum],
    loss_fn: CustomLossFnV1,
    *,
    loss_type_input: Literal["logprobs"] = "logprobs"
) -> APIFuture[types.ForwardBackwardOutput]
```

Async version of forward_backward_custom.

#### `optim_step`

```python
def optim_step(
        adam_params: types.AdamParams) -> APIFuture[types.OptimStepResponse]
```

Update model parameters using Adam optimizer.

The Adam optimizer used by tinker is identical
to [torch.optim.AdamW](https://docs.pytorch.org/docs/stable/generated/torch.optim.AdamW.html).
Note that unlike PyTorch, Tinker's default weight decay value is 0.0 (no weight decay).


Args:
- `adam_params`: Adam optimizer parameters (learning_rate, betas, eps, weight_decay)

Returns:
- `APIFuture` containing optimizer step response

Example:
```python
# First compute gradients
fwdbwd_future = training_client.forward_backward(data, "cross_entropy")

# Then update parameters
optim_future = training_client.optim_step(
    types.AdamParams(
        learning_rate=1e-4,
        weight_decay=0.01
    )
)

# Wait for both to complete
fwdbwd_result = await fwdbwd_future
optim_result = await optim_future
```

#### `optim_step_async`

```python
async def optim_step_async(
        adam_params: types.AdamParams) -> APIFuture[types.OptimStepResponse]
```

Async version of optim_step.

#### `save_state`

```python
def save_state(
        name: str,
        ttl_seconds: int | None = None
) -> APIFuture[types.SaveWeightsResponse]
```

Save model weights to persistent storage.

Args:
- `name`: Name for the saved checkpoint
- `ttl_seconds`: Optional TTL in seconds for the checkpoint (None = never expires)

Returns:
- `APIFuture` containing the save response with checkpoint path

Example:
```python
# Save after training
save_future = training_client.save_state("checkpoint-001")
result = await save_future
print(f"Saved to: {result.path}")
```

#### `save_state_async`

```python
async def save_state_async(
        name: str,
        ttl_seconds: int | None = None
) -> APIFuture[types.SaveWeightsResponse]
```

Async version of save_state.

#### `load_state`

```python
def load_state(path: str) -> APIFuture[types.LoadWeightsResponse]
```

Load model weights from a saved checkpoint.

This loads only the model weights, not optimizer state (e.g., Adam momentum).
To also restore optimizer state, use load_state_with_optimizer.

Args:
- `path`: Tinker path to saved weights (e.g., "tinker://run-id/weights/checkpoint-001")

Returns:
- `APIFuture` containing the load response

Example:
```python
# Load checkpoint to continue training (weights only, optimizer resets)
load_future = training_client.load_state("tinker://run-id/weights/checkpoint-001")
await load_future
# Continue training from loaded state
```

#### `load_state_async`

```python
async def load_state_async(path: str) -> APIFuture[types.LoadWeightsResponse]
```

Async version of load_state.

#### `load_state_with_optimizer`

```python
def load_state_with_optimizer(
        path: str) -> APIFuture[types.LoadWeightsResponse]
```

Load model weights and optimizer state from a checkpoint.

Args:
- `path`: Tinker path to saved weights (e.g., "tinker://run-id/weights/checkpoint-001")

Returns:
- `APIFuture` containing the load response

Example:
```python
# Resume training with optimizer state
load_future = training_client.load_state_with_optimizer(
    "tinker://run-id/weights/checkpoint-001"
)
await load_future
# Continue training with restored optimizer momentum
```

#### `load_state_with_optimizer_async`

```python
async def load_state_with_optimizer_async(
        path: str) -> APIFuture[types.LoadWeightsResponse]
```

Async version of load_state_with_optimizer.

#### `save_weights_for_sampler`

```python
def save_weights_for_sampler(
    name: str,
    ttl_seconds: int | None = None
) -> APIFuture[types.SaveWeightsForSamplerResponse]
```

Save model weights for use with a SamplingClient.

Args:
- `name`: Name for the saved sampler weights
- `ttl_seconds`: Optional TTL in seconds for the checkpoint (None = never expires)

Returns:
- `APIFuture` containing the save response with sampler path

Example:
```python
# Save weights for inference
save_future = training_client.save_weights_for_sampler("sampler-001")
result = await save_future
print(f"Sampler weights saved to: {result.path}")

# Use the path to create a sampling client
sampling_client = service_client.create_sampling_client(
    model_path=result.path
)
```

#### `save_weights_for_sampler_async`

```python
async def save_weights_for_sampler_async(
    name: str,
    ttl_seconds: int | None = None
) -> APIFuture[types.SaveWeightsForSamplerResponse]
```

Async version of save_weights_for_sampler.

#### `get_info`

```python
def get_info() -> types.GetInfoResponse
```

Get information about the current model.

Returns:
- `GetInfoResponse` with model configuration and metadata

Example:
```python
info = training_client.get_info()
print(f"Model ID: {info.model_data.model_id}")
print(f"Base model: {info.model_data.model_name}")
print(f"LoRA rank: {info.model_data.lora_rank}")
```

#### `get_info_async`

```python
async def get_info_async() -> types.GetInfoResponse
```

Async version of get_info.

#### `get_tokenizer`

```python
def get_tokenizer() -> PreTrainedTokenizer
```

Get the tokenizer for the current model.

Returns:
- `PreTrainedTokenizer` compatible with the model

Example:
```python
tokenizer = training_client.get_tokenizer()
tokens = tokenizer.encode("Hello world")
text = tokenizer.decode(tokens)
```

#### `create_sampling_client`

```python
def create_sampling_client(
        model_path: str,
        retry_config: RetryConfig | None = None) -> SamplingClient
```

Create a SamplingClient from saved weights.

Args:
- `model_path`: Tinker path to saved weights
- `retry_config`: Optional configuration for retrying failed requests

Returns:
- `SamplingClient` configured with the specified weights

Example:
```python
sampling_client = training_client.create_sampling_client(
    "tinker://run-id/weights/checkpoint-001"
)
# Use sampling_client for inference
```

#### `create_sampling_client_async`

```python
async def create_sampling_client_async(
        model_path: str,
        retry_config: RetryConfig | None = None) -> SamplingClient
```

Async version of create_sampling_client.

#### `save_weights_and_get_sampling_client`

```python
def save_weights_and_get_sampling_client(
        name: str | None = None,
        retry_config: RetryConfig | None = None) -> SamplingClient
```

Save current weights and create a SamplingClient for inference.

Args:
- `name`: Optional name for the saved weights (currently ignored for ephemeral saves)
- `retry_config`: Optional configuration for retrying failed requests

Returns:
- `SamplingClient` configured with the current model weights

Example:
```python
# After training, create a sampling client directly
sampling_client = training_client.save_weights_and_get_sampling_client()

# Now use it for inference
prompt = types.ModelInput.from_ints(tokenizer.encode("Hello"))
params = types.SamplingParams(max_tokens=20)
result = sampling_client.sample(prompt, 1, params).result()
```

#### `save_weights_and_get_sampling_client_async`

```python
async def save_weights_and_get_sampling_client_async(
        name: str | None = None,
        retry_config: RetryConfig | None = None) -> SamplingClient
```

Async version of save_weights_and_get_sampling_client.


## /docs/api/types.md

## `LoadWeightsResponse` Objects

```python
class LoadWeightsResponse(BaseModel)
```

#### `path`

A tinker URI for model weights at a specific step

## `WeightsInfoResponse` Objects

```python
class WeightsInfoResponse(BaseModel)
```

Minimal information for loading public checkpoints.

## `LoadWeightsRequest` Objects

```python
class LoadWeightsRequest(StrictBase)
```

#### `path`

A tinker URI for model weights at a specific step

#### `optimizer`

Whether to load optimizer state along with model weights

## `CreateModelRequest` Objects

```python
class CreateModelRequest(StrictBase)
```

#### `base_model`

The name of the base model to fine-tune (e.g., 'Qwen/Qwen3-8B').

#### `user_metadata`

Optional metadata about this model/training run, set by the end-user.

#### `lora_config`

LoRA configuration

## `UnhandledExceptionEvent` Objects

```python
class UnhandledExceptionEvent(BaseModel)
```

#### `event`

Telemetry event type

#### `severity`

Log severity level

#### `traceback`

Optional Python traceback string

## `Datum` Objects

```python
class Datum(StrictBase)
```

#### `loss_fn_inputs`

Dictionary mapping field names to tensor data

#### `convert_tensors`

```python
def convert_tensors(cls, data: Any) -> Any
```

Convert torch.Tensor and numpy arrays to TensorData in loss_fn_inputs during construction.

## `Checkpoint` Objects

```python
class Checkpoint(BaseModel)
```

#### `checkpoint_id`

The checkpoint ID

#### `checkpoint_type`

The type of checkpoint (training or sampler)

#### `time`

The time when the checkpoint was created

#### `tinker_path`

The tinker path to the checkpoint

#### `size_bytes`

The size of the checkpoint in bytes

#### `public`

Whether the checkpoint is publicly accessible

#### `expires_at`

When this checkpoint expires (None = never expires)

## `ParsedCheckpointTinkerPath` Objects

```python
class ParsedCheckpointTinkerPath(BaseModel)
```

#### `tinker_path`

The tinker path to the checkpoint

#### `training_run_id`

The training run ID

#### `checkpoint_type`

The type of checkpoint (training or sampler)

#### `checkpoint_id`

The checkpoint ID

#### `from_tinker_path`

```python
def from_tinker_path(cls, tinker_path: str) -> "ParsedCheckpointTinkerPath"
```

Parse a tinker path to an instance of ParsedCheckpointTinkerPath

## `SamplingParams` Objects

```python
class SamplingParams(BaseModel)
```

#### `max_tokens`

Maximum number of tokens to generate

#### `seed`

Random seed for reproducible generation

#### `stop`

Stop sequences for generation

#### `temperature`

Sampling temperature

#### `top_k`

Top-k sampling parameter (-1 for no limit)

#### `top_p`

Nucleus sampling probability

## `SaveWeightsForSamplerRequest` Objects

```python
class SaveWeightsForSamplerRequest(StrictBase)
```

#### `path`

A file/directory name for the weights

#### `ttl_seconds`

TTL in seconds for this checkpoint (None = never expires)
## `ModelInput` Objects

```python
class ModelInput(StrictBase)
```

#### `chunks`

Sequence of input chunks (formerly TokenSequence)

#### `from_ints`

```python
def from_ints(cls, tokens: List[int]) -> "ModelInput"
```

Create a ModelInput from a list of ints (tokens).

#### `to_ints`

```python
def to_ints() -> List[int]
```

Convert the ModelInput to a list of ints (tokens)
Throws exception if there are any non-token chunks

#### `length`

```python
def length() -> int
```

Return the total context length used by this ModelInput.

#### `empty`

```python
def empty(cls) -> "ModelInput"
```

Create an empty ModelInput.

#### `append`

```python
def append(chunk: ModelInputChunk) -> "ModelInput"
```

Add a new chunk, return a new ModelInput.

#### `append_int`

```python
def append_int(token: int) -> "ModelInput"
```

Add a new token, return a new ModelInput.

## `SessionEndEvent` Objects

```python
class SessionEndEvent(BaseModel)
```

#### `duration`

ISO 8601 duration string

#### `event`

Telemetry event type

#### `severity`

Log severity level

## `CreateSamplingSessionResponse` Objects

```python
class CreateSamplingSessionResponse(BaseModel)
```

#### `sampling_session_id`

The generated sampling session ID

## `CheckpointsListResponse` Objects

```python
class CheckpointsListResponse(BaseModel)
```

#### `checkpoints`

List of available model checkpoints for the model

#### `cursor`

Pagination cursor information (None for unpaginated responses)

## `SampleResponse` Objects

```python
class SampleResponse(BaseModel)
```

#### `prompt_logprobs`

If prompt_logprobs was set to true in the request, logprobs are computed for
every token in the prompt. The `prompt_logprobs` response contains a float32
value for every token in the prompt.

#### `topk_prompt_logprobs`

If topk_prompt_logprobs was set to a positive integer k in the request,
the top-k logprobs are computed for every token in the prompt. The
`topk_prompt_logprobs` response contains, for every token in the prompt,
a list of up to k (token_id, logprob) tuples.

## `FutureRetrieveRequest` Objects

```python
class FutureRetrieveRequest(StrictBase)
```

#### `request_id`

The ID of the request to retrieve

#### `allow_metadata_only`

When True, the server may return only response metadata (status and size)
instead of the full payload if the response exceeds the server's inline size limit.

## `ForwardBackwardOutput` Objects

```python
class ForwardBackwardOutput(BaseModel)
```

#### `loss_fn_output_type`

The class name of the loss function output records (e.g., 'TorchLossReturn', 'ArrayRecord').

#### `loss_fn_outputs`

Dictionary mapping field names to tensor data

#### `metrics`

Training metrics as key-value pairs.

The following metrics are recorded only during MoE (Mixture of Experts) training.
Note: Don't fixate on the exact values of these metrics at the start of training.
Different models on different data will have different initial values. How these
metrics evolve over training is what matters.

In the definitions below, *perfect balance* means ``total_tokens / num_experts``
— the number of tokens each expert would receive if routing were perfectly uniform.

- ``e_frac_with_tokens:mean``: Fraction of experts that received at least one token,
  averaged across layers. A value of 1.0 means every expert got work; 0.5 means half
  were idle. Decreasing over time is concerning (routing collapse).

- ``e_frac_oversubscribed:mean``: Fraction of experts receiving more tokens than
  perfect balance, averaged across layers. Increasing over time is concerning.

- ``e_max_violation:mean``: How much the most overloaded expert exceeds perfect
  balance, as a fraction of perfect balance, averaged across layers. Computed as
  ``(max_tokens - perfect_balance) / perfect_balance``. A value of 2.0 means the
  busiest expert got 3x the fair share. Increasing over time is concerning.

- ``e_max_violation:max``: Same as ``e_max_violation:mean`` but takes the max
  across layers instead of the mean. Shows the worst-case load imbalance in any
  single layer.

- ``e_min_violation:mean``: How much the least loaded expert is below perfect
  balance, as a fraction of perfect balance, averaged across layers. Computed as
  ``(min_tokens - perfect_balance) / perfect_balance``. A value of -0.5 means the
  least-used expert got half the fair share; -1.0 means it got nothing. Typically
  negative. Decreasing over time (more negative) is concerning.

## `ModelData` Objects

```python
class ModelData(BaseModel)
```

Metadata about a model's architecture and configuration.

#### `arch`

The model architecture identifier.

#### `model_name`

The human-readable model name.

#### `tokenizer_id`

The identifier of the tokenizer used by this model.

## `GetInfoResponse` Objects

```python
class GetInfoResponse(BaseModel)
```

Response containing information about a training client's model.

#### `type`

Response type identifier.

#### `model_data`

Detailed metadata about the model.

#### `model_id`

Unique identifier for the model.

#### `is_lora`

Whether this is a LoRA fine-tuned model.

#### `lora_rank`

The rank of the LoRA adaptation, if applicable.

#### `model_name`

The name of the model.

## `SaveWeightsResponse` Objects

```python
class SaveWeightsResponse(BaseModel)
```

#### `path`

A tinker URI for model weights at a specific step

## `LoraConfig` Objects

```python
class LoraConfig(StrictBase)
```

#### `rank`

LoRA rank (dimension of low-rank matrices)

#### `seed`

Seed used for initialization of LoRA weights.

Useful if you need deterministic or reproducible initialization of weights.

#### `train_unembed`

Whether to add lora to the unembedding layer

#### `train_mlp`

Whether to add loras to the MLP layers (including MoE layers)

#### `train_attn`

Whether to add loras to the attention layers

## `SaveWeightsForSamplerResponseInternal` Objects

```python
class SaveWeightsForSamplerResponseInternal(BaseModel)
```

#### `path`

A tinker URI for model weights for sampling at a specific step

#### `sampling_session_id`

The generated sampling session ID

## `SaveWeightsForSamplerResponse` Objects

```python
class SaveWeightsForSamplerResponse(BaseModel)
```

#### `path`

A tinker URI for model weights for sampling at a specific step

## `CreateSamplingSessionRequest` Objects

```python
class CreateSamplingSessionRequest(StrictBase)
```

#### `session_id`

The session ID to create the sampling session within

#### `sampling_session_seq_id`

Sequence ID for the sampling session within the session

#### `base_model`

Optional base model name to sample from.

Is inferred from model_path, if provided. If sampling against a base model, this
is required.

#### `model_path`

Optional tinker:// path to your model weights or LoRA weights.

If not provided, samples against the base model.

## `OptimStepResponse` Objects

```python
class OptimStepResponse(BaseModel)
```

#### `metrics`

Optimization step metrics as key-value pairs

## `SampleRequest` Objects

```python
class SampleRequest(StrictBase)
```

#### `num_samples`

Number of samples to generate

#### `base_model`

Optional base model name to sample from.

Is inferred from model_path, if provided. If sampling against a base model, this
is required.

#### `model_path`

Optional tinker:// path to your model weights or LoRA weights.

If not provided, samples against the base model.

#### `sampling_session_id`

Optional sampling session ID to use instead of model_path/base_model.

If provided along with seq_id, the model configuration will be loaded from the
sampling session. This is useful for multi-turn conversations.

#### `seq_id`

Sequence ID within the sampling session.

Required when sampling_session_id is provided. Used to generate deterministic
request IDs for the sampling request.

#### `prompt_logprobs`

If set to `true`, computes and returns logprobs on the prompt tokens.

Defaults to false.

#### `topk_prompt_logprobs`

If set to a positive integer, returns the top-k logprobs for each prompt token.

## `TrainingRun` Objects

```python
class TrainingRun(BaseModel)
```

#### `training_run_id`

The unique identifier for the training run

#### `base_model`

The base model name this model is derived from

#### `model_owner`

The owner/creator of this model

#### `is_lora`

Whether this model uses LoRA (Low-Rank Adaptation)

#### `corrupted`

Whether the model is in a corrupted state

#### `lora_rank`

The LoRA rank if this is a LoRA model, null otherwise

#### `last_request_time`

The timestamp of the last request made to this model

#### `last_checkpoint`

The most recent training checkpoint, if available

#### `last_sampler_checkpoint`

The most recent sampler checkpoint, if available

#### `user_metadata`

Optional metadata about this training run, set by the end-user

## `TelemetrySendRequest` Objects

```python
class TelemetrySendRequest(StrictBase)
```

#### `platform`

Host platform name

#### `sdk_version`

SDK version string

## `CheckpointArchiveUrlResponse` Objects

```python
class CheckpointArchiveUrlResponse(BaseModel)
```

#### `url`

Signed URL to download the checkpoint archive

#### `expires`

Unix timestamp when the signed URL expires, if available

## `SupportedModel` Objects

```python
class SupportedModel(BaseModel)
```

Information about a model supported by the server.

#### `model_name`

The name of the supported model.

## `GetServerCapabilitiesResponse` Objects

```python
class GetServerCapabilitiesResponse(BaseModel)
```

Response containing the server's supported models and capabilities.

#### `supported_models`

List of models available on the server.

## `SessionStartEvent` Objects

```python
class SessionStartEvent(BaseModel)
```

#### `event`

Telemetry event type

#### `severity`

Log severity level

## `GenericEvent` Objects

```python
class GenericEvent(BaseModel)
```

#### `event`

Telemetry event type

#### `event_name`

Low-cardinality event name

#### `severity`

Log severity level

#### `event_data`

Arbitrary structured JSON payload

## `TryAgainResponse` Objects

```python
class TryAgainResponse(BaseModel)
```

#### `request_id`

Request ID that is still pending

## `TrainingRunsResponse` Objects

```python
class TrainingRunsResponse(BaseModel)
```

#### `training_runs`

List of training runs

#### `cursor`

Pagination cursor information

## `ForwardBackwardInput` Objects

```python
class ForwardBackwardInput(StrictBase)
```

#### `data`

Array of input data for the forward/backward pass

#### `loss_fn`

Fully qualified function path for the loss function

#### `loss_fn_config`

Optional configuration parameters for the loss function (e.g., PPO clip thresholds, DPO beta)

## `ImageAssetPointerChunk` Objects

```python
class ImageAssetPointerChunk(StrictBase)
```

#### `format`

Image format

#### `location`

Path or URL to the image asset

#### `expected_tokens`

Expected number of tokens this image represents.
This is only advisory: the tinker backend will compute the number of tokens
from the image, and we can fail requests quickly if the tokens does not
match expected_tokens.

## `TelemetryBatch` Objects

```python
class TelemetryBatch(BaseModel)
```

#### `platform`

Host platform name

#### `sdk_version`

SDK version string

## `TensorData` Objects

```python
class TensorData(StrictBase)
```

#### `data`

Flattened tensor data as array of numbers.

#### `shape`

Optional.

The shape of the tensor (see PyTorch tensor.shape). The shape of a
one-dimensional list of length N is `(N,)`. Can usually be inferred if not
provided, and is generally inferred as a 1D tensor.

#### `to_numpy`

```python
def to_numpy() -> npt.NDArray[Any]
```

Convert TensorData to numpy array.

#### `to_torch`

```python
def to_torch() -> "torch.Tensor"
```

Convert TensorData to torch tensor.

## `EncodedTextChunk` Objects

```python
class EncodedTextChunk(StrictBase)
```

#### `tokens`

Array of token IDs

## `AdamParams` Objects

```python
class AdamParams(StrictBase)
```

#### `learning_rate`

Learning rate for the optimizer

#### `beta1`

Coefficient used for computing running averages of gradient

#### `beta2`

Coefficient used for computing running averages of gradient square

#### `eps`

Term added to the denominator to improve numerical stability

#### `weight_decay`

Weight decay for the optimizer. Uses decoupled weight decay.

#### `grad_clip_norm`

Maximum global gradient norm. If the global gradient norm is greater than this value, it will be clipped to this value. 0.0 means no clipping.

## `ImageChunk` Objects

```python
class ImageChunk(StrictBase)
```

#### `data`

Image data as bytes

#### `format`

Image format

#### `expected_tokens`

Expected number of tokens this image represents.
This is only advisory: the tinker backend will compute the number of tokens
from the image, and we can fail requests quickly if the tokens does not
match expected_tokens.

#### `validate_data`

```python
def validate_data(cls, value: Union[bytes, str]) -> bytes
```

Deserialize base64 string to bytes if needed.

#### `serialize_data`

```python
def serialize_data(value: bytes) -> str
```

Serialize bytes to base64 string for JSON.

## `SampledSequence` Objects

```python
class SampledSequence(BaseModel)
```

#### `stop_reason`

Reason why sampling stopped

#### `tokens`

List of generated token IDs

#### `logprobs`

Log probabilities for each token (optional)

## `Cursor` Objects

```python
class Cursor(BaseModel)
```

#### `offset`

The offset used for pagination

#### `limit`

The maximum number of items requested

#### `total_count`

The total number of items available

## `SaveWeightsRequest` Objects

```python
class SaveWeightsRequest(StrictBase)
```

#### `path`

A file/directory name for the weights

#### `ttl_seconds`

TTL in seconds for this checkpoint (None = never expires)


## /docs/images/logo.png

Binary file available at https://raw.githubusercontent.com/thinking-machines-lab/tinker/refs/heads/main/docs/images/logo.png

## /docs/images/logo.svg

```svg path="/docs/images/logo.svg" 
<svg width="820" height="512" viewBox="0 0 820 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="820" height="512" fill="white"/>
<rect x="618.852" y="46" width="2" height="48" transform="rotate(-45 618.852 46)" fill="#FFCE52"/>
<rect x="679.797" y="44.5859" width="2" height="48" transform="rotate(45 679.797 44.5859)" fill="#FFCE52"/>
<rect x="233" y="75" width="120" height="2" fill="#73BB83"/>
<rect x="463" y="75" width="120" height="2" fill="#73BB83"/>
<rect x="233" y="195" width="120" height="2" fill="#73BB83"/>
<rect x="463" y="195" width="120" height="2" fill="#73BB83"/>
<rect x="110" y="75" width="120" height="2" fill="#FF9D52"/>
<rect x="140" y="45" width="60" height="2" fill="#FF9D52"/>
<rect x="380" y="45" width="60" height="2" fill="#FF9D52"/>
<rect x="620" y="45" width="60" height="2" fill="#FF9D52"/>
<rect x="110" y="135" width="120" height="2" fill="#FF9D52"/>
<rect x="110" y="195" width="120" height="2" fill="#FF9D52"/>
<rect x="169" y="74" width="2" height="120" fill="#4196D8"/>
<rect x="230.805" y="134" width="2" height="90" transform="rotate(45 230.805 134)" fill="#D34F4F"/>
<rect x="174.234" y="80.4141" width="2" height="80" transform="rotate(-45 174.234 80.4141)" fill="#D34F4F"/>
<rect x="235.234" y="82.4141" width="2" height="160" transform="rotate(-45 235.234 82.4141)" fill="#D34F4F"/>
<rect x="235.234" y="202.414" width="2" height="160" transform="rotate(-45 235.234 202.414)" fill="#D34F4F"/>
<rect x="115.234" y="202.414" width="2" height="160" transform="rotate(-45 115.234 202.414)" fill="#4196D8"/>
<rect x="355.234" y="202.414" width="2" height="160" transform="rotate(-45 355.234 202.414)" fill="#4196D8"/>
<rect x="595.234" y="202.414" width="2" height="160" transform="rotate(-45 595.234 202.414)" fill="#4196D8"/>
<rect x="475.234" y="202.414" width="2" height="160" transform="rotate(-45 475.234 202.414)" fill="#D34F4F"/>
<rect x="473.234" y="80.4141" width="2" height="170" transform="rotate(-45 473.234 80.4141)" fill="#D34F4F"/>
<rect x="109.234" y="137" width="2" height="80" transform="rotate(-45 109.234 137)" fill="#D34F4F"/>
<rect x="167.805" y="75.5859" width="2" height="80" transform="rotate(45 167.805 75.5859)" fill="#D34F4F"/>
<rect x="139.797" y="44.5859" width="2" height="48" transform="rotate(45 139.797 44.5859)" fill="#FFCE52"/>
<rect x="138.852" y="46" width="2" height="48" transform="rotate(-45 138.852 46)" fill="#FFCE52"/>
<rect x="378.852" y="46" width="2" height="48" transform="rotate(-45 378.852 46)" fill="#FFCE52"/>
<rect x="198.844" y="46" width="2" height="48" transform="rotate(-45 198.844 46)" fill="#FFCE52"/>
<rect x="438.844" y="46" width="2" height="48" transform="rotate(-45 438.844 46)" fill="#FFCE52"/>
<rect x="678.844" y="46" width="2" height="48" transform="rotate(-45 678.844 46)" fill="#FFCE52"/>
<rect x="199.797" y="44.5859" width="2" height="48" transform="rotate(45 199.797 44.5859)" fill="#FFCE52"/>
<rect x="379.797" y="44.5859" width="2" height="48" transform="rotate(45 379.797 44.5859)" fill="#FFCE52"/>
<rect x="439.797" y="44.5859" width="2" height="48" transform="rotate(45 439.797 44.5859)" fill="#FFCE52"/>
<rect x="619.797" y="44.5859" width="2" height="48" transform="rotate(45 619.797 44.5859)" fill="#FFCE52"/>
<rect x="348.805" y="75.5859" width="2" height="170" transform="rotate(45 348.805 75.5859)" fill="#D34F4F"/>
<rect x="348.805" y="195.586" width="2" height="170" transform="rotate(45 348.805 195.586)" fill="#D34F4F"/>
<rect x="588.805" y="195.586" width="2" height="170" transform="rotate(45 588.805 195.586)" fill="#D34F4F"/>
<rect x="588.805" y="75.5859" width="2" height="170" transform="rotate(45 588.805 75.5859)" fill="#D34F4F"/>
<rect x="109" y="74" width="2" height="120" fill="#FF9D52"/>
<rect x="229" y="74" width="2" height="120" fill="#FF9D52"/>
<circle cx="110" cy="76" r="7.5" fill="url(#paint0_radial_781_319508)" stroke="white"/>
<circle cx="110" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="140" cy="76" r="7.5" fill="url(#paint1_radial_781_319508)" stroke="white"/>
<circle cx="140" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="170" cy="76" r="7.5" fill="url(#paint2_radial_781_319508)" stroke="white"/>
<circle cx="170" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="200" cy="76" r="7.5" fill="url(#paint3_radial_781_319508)" stroke="white"/>
<circle cx="200" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="230" cy="76" r="7.5" fill="url(#paint4_radial_781_319508)" stroke="white"/>
<circle cx="230" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="140" cy="136" r="7.5" fill="url(#paint5_radial_781_319508)" stroke="white"/>
<circle cx="140" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="200" cy="136" r="7.5" fill="url(#paint6_radial_781_319508)" stroke="white"/>
<circle cx="200" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="170" cy="136" r="7.5" fill="url(#paint7_radial_781_319508)" stroke="white"/>
<circle cx="170" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="230" cy="106" r="7.5" fill="url(#paint8_radial_781_319508)" stroke="white"/>
<circle cx="230" cy="106" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="110" cy="106" r="7.5" fill="url(#paint9_radial_781_319508)" stroke="white"/>
<circle cx="110" cy="106" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="230" cy="136" r="7.5" fill="url(#paint10_radial_781_319508)" stroke="white"/>
<circle cx="230" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="110" cy="136" r="7.5" fill="url(#paint11_radial_781_319508)" stroke="white"/>
<circle cx="110" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="230" cy="166" r="7.5" fill="url(#paint12_radial_781_319508)" stroke="white"/>
<circle cx="230" cy="166" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="110" cy="166" r="7.5" fill="url(#paint13_radial_781_319508)" stroke="white"/>
<circle cx="110" cy="166" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="290" cy="136" r="7.5" fill="url(#paint14_radial_781_319508)" stroke="white"/>
<circle cx="290" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="290" cy="256" r="7.5" fill="url(#paint15_radial_781_319508)" stroke="white"/>
<circle cx="290" cy="256" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="530" cy="256" r="7.5" fill="url(#paint16_radial_781_319508)" stroke="white"/>
<circle cx="530" cy="256" r="1" fill="black" fill-opacity="0.4"/>
<rect x="350" y="75" width="120" height="2" fill="#FF9D52"/>
<rect x="350" y="135" width="120" height="2" fill="#FF9D52"/>
<rect x="350" y="195" width="120" height="2" fill="#FF9D52"/>
<rect x="409" y="74" width="2" height="120" fill="#4196D8"/>
<rect x="470.805" y="134" width="2" height="80" transform="rotate(45 470.805 134)" fill="#D34F4F"/>
<rect x="414.234" y="81.4141" width="2" height="80" transform="rotate(-45 414.234 81.4141)" fill="#D34F4F"/>
<rect x="349.234" y="137" width="2" height="80" transform="rotate(-45 349.234 137)" fill="#D34F4F"/>
<rect x="408.789" y="75.5859" width="2" height="80" transform="rotate(45 408.789 75.5859)" fill="#D34F4F"/>
<rect x="349" y="74" width="2" height="120" fill="#FF9D52"/>
<rect x="349" y="204" width="2" height="120" fill="#73BB83"/>
<rect x="229" y="204" width="2" height="120" fill="#73BB83"/>
<rect x="109" y="204" width="2" height="120" fill="#73BB83"/>
<rect x="469" y="204" width="2" height="120" fill="#73BB83"/>
<rect x="709" y="204" width="2" height="120" fill="#73BB83"/>
<rect x="589" y="204" width="2" height="120" fill="#73BB83"/>
<rect x="469" y="74" width="2" height="120" fill="#FF9D52"/>
<circle cx="350" cy="76" r="7.5" fill="url(#paint17_radial_781_319508)" stroke="white"/>
<circle cx="350" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="380" cy="76" r="7.5" fill="url(#paint18_radial_781_319508)" stroke="white"/>
<circle cx="380" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="410" cy="76" r="7.5" fill="url(#paint19_radial_781_319508)" stroke="white"/>
<circle cx="410" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="440" cy="76" r="7.5" fill="url(#paint20_radial_781_319508)" stroke="white"/>
<circle cx="440" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="470" cy="76" r="7.5" fill="url(#paint21_radial_781_319508)" stroke="white"/>
<circle cx="470" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="380" cy="136" r="7.5" fill="url(#paint22_radial_781_319508)" stroke="white"/>
<circle cx="380" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="440" cy="136" r="7.5" fill="url(#paint23_radial_781_319508)" stroke="white"/>
<circle cx="440" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="410" cy="136" r="7.5" fill="url(#paint24_radial_781_319508)" stroke="white"/>
<circle cx="410" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="470" cy="106" r="7.5" fill="url(#paint25_radial_781_319508)" stroke="white"/>
<circle cx="470" cy="106" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="350" cy="106" r="7.5" fill="url(#paint26_radial_781_319508)" stroke="white"/>
<circle cx="350" cy="106" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="470" cy="136" r="7.5" fill="url(#paint27_radial_781_319508)" stroke="white"/>
<circle cx="470" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="350" cy="136" r="7.5" fill="url(#paint28_radial_781_319508)" stroke="white"/>
<circle cx="350" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="470" cy="166" r="7.5" fill="url(#paint29_radial_781_319508)" stroke="white"/>
<circle cx="470" cy="166" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="350" cy="166" r="7.5" fill="url(#paint30_radial_781_319508)" stroke="white"/>
<circle cx="350" cy="166" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="350" cy="196" r="7.5" fill="url(#paint31_radial_781_319508)" stroke="white"/>
<circle cx="350" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="380" cy="196" r="7.5" fill="url(#paint32_radial_781_319508)" stroke="white"/>
<circle cx="380" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="410" cy="196" r="7.5" fill="url(#paint33_radial_781_319508)" stroke="white"/>
<circle cx="410" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="440" cy="196" r="7.5" fill="url(#paint34_radial_781_319508)" stroke="white"/>
<circle cx="440" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="470" cy="196" r="7.5" fill="url(#paint35_radial_781_319508)" stroke="white"/>
<circle cx="470" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="530" cy="136" r="7.5" fill="url(#paint36_radial_781_319508)" stroke="white"/>
<circle cx="530" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<rect x="590" y="75" width="120" height="2" fill="#FF9D52"/>
<rect x="590" y="135" width="120" height="2" fill="#FF9D52"/>
<rect x="590" y="195" width="120" height="2" fill="#FF9D52"/>
<rect x="649" y="74" width="2" height="120" fill="#4196D8"/>
<rect x="710.805" y="134" width="2" height="80" transform="rotate(45 710.805 134)" fill="#D34F4F"/>
<rect x="654.234" y="81.4141" width="2" height="80" transform="rotate(-45 654.234 81.4141)" fill="#D34F4F"/>
<rect x="589.234" y="137" width="2" height="80" transform="rotate(-45 589.234 137)" fill="#D34F4F"/>
<rect x="646.086" y="78.5859" width="2" height="80" transform="rotate(45 646.086 78.5859)" fill="#D34F4F"/>
<rect x="589" y="74" width="2" height="120" fill="#FF9D52"/>
<rect x="709" y="74" width="2" height="120" fill="#FF9D52"/>
<circle cx="590" cy="76" r="7.5" fill="url(#paint37_radial_781_319508)" stroke="white"/>
<circle cx="590" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="620" cy="76" r="7.5" fill="url(#paint38_radial_781_319508)" stroke="white"/>
<circle cx="620" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="650" cy="76" r="7.5" fill="url(#paint39_radial_781_319508)" stroke="white"/>
<circle cx="650" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="680" cy="76" r="7.5" fill="url(#paint40_radial_781_319508)" stroke="white"/>
<circle cx="680" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="710" cy="76" r="7.5" fill="url(#paint41_radial_781_319508)" stroke="white"/>
<circle cx="710" cy="76" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="620" cy="136" r="7.5" fill="url(#paint42_radial_781_319508)" stroke="white"/>
<circle cx="620" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="680" cy="136" r="7.5" fill="url(#paint43_radial_781_319508)" stroke="white"/>
<circle cx="680" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="650" cy="136" r="7.5" fill="url(#paint44_radial_781_319508)" stroke="white"/>
<circle cx="650" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="710" cy="106" r="7.5" fill="url(#paint45_radial_781_319508)" stroke="white"/>
<circle cx="710" cy="106" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="590" cy="106" r="7.5" fill="url(#paint46_radial_781_319508)" stroke="white"/>
<circle cx="590" cy="106" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="710" cy="136" r="7.5" fill="url(#paint47_radial_781_319508)" stroke="white"/>
<circle cx="710" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="590" cy="136" r="7.5" fill="url(#paint48_radial_781_319508)" stroke="white"/>
<circle cx="590" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="710" cy="166" r="7.5" fill="url(#paint49_radial_781_319508)" stroke="white"/>
<circle cx="710" cy="166" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="590" cy="166" r="7.5" fill="url(#paint50_radial_781_319508)" stroke="white"/>
<circle cx="590" cy="166" r="1" fill="black" fill-opacity="0.4"/>
<rect x="59" y="134" width="2" height="250" fill="#FF9D52"/>
<rect x="113.195" y="199.129" width="2" height="80" transform="rotate(140.469 113.195 199.129)" fill="#FFCE52"/>
<rect x="59.5391" y="137.977" width="2" height="80" transform="rotate(-140.47 59.5391 137.977)" fill="#FFCE52"/>
<rect x="60.5391" y="256.977" width="2" height="80" transform="rotate(-140.47 60.5391 256.977)" fill="#FFCE52"/>
<rect x="61.5391" y="375.977" width="2" height="80" transform="rotate(-140.47 61.5391 375.977)" fill="#FFCE52"/>
<rect x="113.195" y="439.129" width="2" height="80" transform="rotate(140.469 113.195 439.129)" fill="#FFCE52"/>
<rect x="113.195" y="319.129" width="2" height="80" transform="rotate(140.469 113.195 319.129)" fill="#FFCE52"/>
<circle cx="60" cy="136" r="7.5" fill="url(#paint51_radial_781_319508)" stroke="white"/>
<circle cx="60" cy="136" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="60" cy="376" r="7.5" fill="url(#paint52_radial_781_319508)" stroke="white"/>
<circle cx="60" cy="376" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="60" cy="256" r="7.5" fill="url(#paint53_radial_781_319508)" stroke="white"/>
<circle cx="60" cy="256" r="1" fill="black" fill-opacity="0.4"/>
<rect x="761.461" y="367.383" width="2" height="230" transform="rotate(-180 761.461 367.383)" fill="#FF9D52"/>
<rect x="758.922" y="375.402" width="2" height="80" transform="rotate(39.53 758.922 375.402)" fill="#FFCE52"/>
<rect x="757.922" y="256.402" width="2" height="80" transform="rotate(39.53 757.922 256.402)" fill="#FFCE52"/>
<rect x="756.922" y="137.406" width="2" height="80" transform="rotate(39.53 756.922 137.406)" fill="#FFCE52"/>
<rect x="707.266" y="192.25" width="2" height="80" transform="rotate(-39.5305 707.266 192.25)" fill="#FFCE52"/>
<circle cx="110" cy="196" r="7.5" fill="url(#paint54_radial_781_319508)" stroke="white"/>
<circle cx="110" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="140" cy="196" r="7.5" fill="url(#paint55_radial_781_319508)" stroke="white"/>
<circle cx="140" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="170" cy="196" r="7.5" fill="url(#paint56_radial_781_319508)" stroke="white"/>
<circle cx="170" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="200" cy="196" r="7.5" fill="url(#paint57_radial_781_319508)" stroke="white"/>
<circle cx="200" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="230" cy="196" r="7.5" fill="url(#paint58_radial_781_319508)" stroke="white"/>
<circle cx="230" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<rect x="707.266" y="312.25" width="2" height="80" transform="rotate(-39.5305 707.266 312.25)" fill="#FFCE52"/>
<rect x="707.266" y="72.2539" width="2" height="80" transform="rotate(-39.5305 707.266 72.2539)" fill="#FFCE52"/>
<rect x="707.266" y="72.2539" width="2" height="80" transform="rotate(-39.5305 707.266 72.2539)" fill="#FFCE52"/>
<circle cx="760.461" cy="375.379" r="7.5" transform="rotate(-180 760.461 375.379)" fill="url(#paint59_radial_781_319508)" stroke="white"/>
<circle cx="760.461" cy="375.379" r="1" transform="rotate(-180 760.461 375.379)" fill="black" fill-opacity="0.4"/>
<circle cx="760.461" cy="255.379" r="7.5" transform="rotate(-180 760.461 255.379)" fill="url(#paint60_radial_781_319508)" stroke="white"/>
<circle cx="760.461" cy="255.379" r="1" transform="rotate(-180 760.461 255.379)" fill="black" fill-opacity="0.4"/>
<circle cx="760.461" cy="135.383" r="7.5" transform="rotate(-180 760.461 135.383)" fill="url(#paint61_radial_781_319508)" stroke="white"/>
<circle cx="760.461" cy="135.383" r="1" transform="rotate(-180 760.461 135.383)" fill="black" fill-opacity="0.4"/>
<circle cx="140" cy="46" r="7.5" fill="url(#paint62_radial_781_319508)" stroke="white"/>
<circle cx="140" cy="46" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="380" cy="46" r="7.5" fill="url(#paint63_radial_781_319508)" stroke="white"/>
<circle cx="380" cy="46" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="620" cy="46" r="7.5" fill="url(#paint64_radial_781_319508)" stroke="white"/>
<circle cx="620" cy="46" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="200" cy="46" r="7.5" fill="url(#paint65_radial_781_319508)" stroke="white"/>
<circle cx="200" cy="46" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="440" cy="46" r="7.5" fill="url(#paint66_radial_781_319508)" stroke="white"/>
<circle cx="440" cy="46" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="680" cy="46" r="7.5" fill="url(#paint67_radial_781_319508)" stroke="white"/>
<circle cx="680" cy="46" r="1" fill="black" fill-opacity="0.4"/>
<rect x="201.148" y="466" width="2" height="48" transform="rotate(135 201.148 466)" fill="#FFCE52"/>
<rect x="140.203" y="467.414" width="2" height="48" transform="rotate(-135 140.203 467.414)" fill="#FFCE52"/>
<rect x="587" y="437" width="120" height="2" transform="rotate(-180 587 437)" fill="#73BB83"/>
<rect x="357" y="437" width="120" height="2" transform="rotate(-180 357 437)" fill="#73BB83"/>
<rect x="587" y="317" width="120" height="2" transform="rotate(-180 587 317)" fill="#73BB83"/>
<rect x="357" y="317" width="120" height="2" transform="rotate(-180 357 317)" fill="#73BB83"/>
<rect x="710" y="437" width="120" height="2" transform="rotate(-180 710 437)" fill="#FF9D52"/>
<rect x="680" y="467" width="60" height="2" transform="rotate(-180 680 467)" fill="#FF9D52"/>
<rect x="440" y="467" width="60" height="2" transform="rotate(-180 440 467)" fill="#FF9D52"/>
<rect x="200" y="467" width="60" height="2" transform="rotate(-180 200 467)" fill="#FF9D52"/>
<rect x="710" y="377" width="120" height="2" transform="rotate(-180 710 377)" fill="#FF9D52"/>
<rect x="710" y="317" width="120" height="2" transform="rotate(-180 710 317)" fill="#FF9D52"/>
<rect x="651" y="438" width="2" height="120" transform="rotate(-180 651 438)" fill="#4196D8"/>
<rect x="589.195" y="378" width="2" height="90" transform="rotate(-135 589.195 378)" fill="#D34F4F"/>
<rect x="645.766" y="431.586" width="2" height="80" transform="rotate(135 645.766 431.586)" fill="#D34F4F"/>
<rect x="584.766" y="429.586" width="2" height="160" transform="rotate(135 584.766 429.586)" fill="#D34F4F"/>
<rect x="346.766" y="431.586" width="2" height="170" transform="rotate(135 346.766 431.586)" fill="#D34F4F"/>
<rect x="710.766" y="375" width="2" height="80" transform="rotate(135 710.766 375)" fill="#D34F4F"/>
<rect x="652.195" y="436.414" width="2" height="80" transform="rotate(-135 652.195 436.414)" fill="#D34F4F"/>
<rect x="680.203" y="467.414" width="2" height="48" transform="rotate(-135 680.203 467.414)" fill="#FFCE52"/>
<rect x="681.148" y="466" width="2" height="48" transform="rotate(135 681.148 466)" fill="#FFCE52"/>
<rect x="441.148" y="466" width="2" height="48" transform="rotate(135 441.148 466)" fill="#FFCE52"/>
<rect x="621.156" y="466" width="2" height="48" transform="rotate(135 621.156 466)" fill="#FFCE52"/>
<rect x="381.156" y="466" width="2" height="48" transform="rotate(135 381.156 466)" fill="#FFCE52"/>
<rect x="141.156" y="466" width="2" height="48" transform="rotate(135 141.156 466)" fill="#FFCE52"/>
<rect x="620.203" y="467.414" width="2" height="48" transform="rotate(-135 620.203 467.414)" fill="#FFCE52"/>
<rect x="440.203" y="467.414" width="2" height="48" transform="rotate(-135 440.203 467.414)" fill="#FFCE52"/>
<rect x="380.203" y="467.414" width="2" height="48" transform="rotate(-135 380.203 467.414)" fill="#FFCE52"/>
<rect x="200.203" y="467.414" width="2" height="48" transform="rotate(-135 200.203 467.414)" fill="#FFCE52"/>
<rect x="471.195" y="436.414" width="2" height="170" transform="rotate(-135 471.195 436.414)" fill="#D34F4F"/>
<rect x="231.195" y="436.414" width="2" height="170" transform="rotate(-135 231.195 436.414)" fill="#D34F4F"/>
<rect x="711" y="438" width="2" height="120" transform="rotate(-180 711 438)" fill="#FF9D52"/>
<rect x="591" y="438" width="2" height="120" transform="rotate(-180 591 438)" fill="#FF9D52"/>
<circle cx="710" cy="436" r="7.5" transform="rotate(-180 710 436)" fill="url(#paint68_radial_781_319508)" stroke="white"/>
<circle cx="710" cy="436" r="1" transform="rotate(-180 710 436)" fill="black" fill-opacity="0.4"/>
<circle cx="680" cy="436" r="7.5" transform="rotate(-180 680 436)" fill="url(#paint69_radial_781_319508)" stroke="white"/>
<circle cx="680" cy="436" r="1" transform="rotate(-180 680 436)" fill="black" fill-opacity="0.4"/>
<circle cx="650" cy="436" r="7.5" transform="rotate(-180 650 436)" fill="url(#paint70_radial_781_319508)" stroke="white"/>
<circle cx="650" cy="436" r="1" transform="rotate(-180 650 436)" fill="black" fill-opacity="0.4"/>
<circle cx="620" cy="436" r="7.5" transform="rotate(-180 620 436)" fill="url(#paint71_radial_781_319508)" stroke="white"/>
<circle cx="620" cy="436" r="1" transform="rotate(-180 620 436)" fill="black" fill-opacity="0.4"/>
<circle cx="590" cy="436" r="7.5" transform="rotate(-180 590 436)" fill="url(#paint72_radial_781_319508)" stroke="white"/>
<circle cx="590" cy="436" r="1" transform="rotate(-180 590 436)" fill="black" fill-opacity="0.4"/>
<circle cx="680" cy="376" r="7.5" transform="rotate(-180 680 376)" fill="url(#paint73_radial_781_319508)" stroke="white"/>
<circle cx="680" cy="376" r="1" transform="rotate(-180 680 376)" fill="black" fill-opacity="0.4"/>
<circle cx="620" cy="376" r="7.5" transform="rotate(-180 620 376)" fill="url(#paint74_radial_781_319508)" stroke="white"/>
<circle cx="620" cy="376" r="1" transform="rotate(-180 620 376)" fill="black" fill-opacity="0.4"/>
<circle cx="650" cy="376" r="7.5" transform="rotate(-180 650 376)" fill="url(#paint75_radial_781_319508)" stroke="white"/>
<circle cx="650" cy="376" r="1" transform="rotate(-180 650 376)" fill="black" fill-opacity="0.4"/>
<circle cx="590" cy="406" r="7.5" transform="rotate(-180 590 406)" fill="url(#paint76_radial_781_319508)" stroke="white"/>
<circle cx="590" cy="406" r="1" transform="rotate(-180 590 406)" fill="black" fill-opacity="0.4"/>
<circle cx="710" cy="406" r="7.5" transform="rotate(-180 710 406)" fill="url(#paint77_radial_781_319508)" stroke="white"/>
<circle cx="710" cy="406" r="1" transform="rotate(-180 710 406)" fill="black" fill-opacity="0.4"/>
<circle cx="590" cy="376" r="7.5" transform="rotate(-180 590 376)" fill="url(#paint78_radial_781_319508)" stroke="white"/>
<circle cx="590" cy="376" r="1" transform="rotate(-180 590 376)" fill="black" fill-opacity="0.4"/>
<circle cx="710" cy="376" r="7.5" transform="rotate(-180 710 376)" fill="url(#paint79_radial_781_319508)" stroke="white"/>
<circle cx="710" cy="376" r="1" transform="rotate(-180 710 376)" fill="black" fill-opacity="0.4"/>
<circle cx="590" cy="346" r="7.5" transform="rotate(-180 590 346)" fill="url(#paint80_radial_781_319508)" stroke="white"/>
<circle cx="590" cy="346" r="1" transform="rotate(-180 590 346)" fill="black" fill-opacity="0.4"/>
<circle cx="710" cy="346" r="7.5" transform="rotate(-180 710 346)" fill="url(#paint81_radial_781_319508)" stroke="white"/>
<circle cx="710" cy="346" r="1" transform="rotate(-180 710 346)" fill="black" fill-opacity="0.4"/>
<circle cx="710" cy="316" r="7.5" transform="rotate(-180 710 316)" fill="url(#paint82_radial_781_319508)" stroke="white"/>
<circle cx="710" cy="316" r="1" transform="rotate(-180 710 316)" fill="black" fill-opacity="0.4"/>
<circle cx="680" cy="316" r="7.5" transform="rotate(-180 680 316)" fill="url(#paint83_radial_781_319508)" stroke="white"/>
<circle cx="680" cy="316" r="1" transform="rotate(-180 680 316)" fill="black" fill-opacity="0.4"/>
<circle cx="650" cy="316" r="7.5" transform="rotate(-180 650 316)" fill="url(#paint84_radial_781_319508)" stroke="white"/>
<circle cx="650" cy="316" r="1" transform="rotate(-180 650 316)" fill="black" fill-opacity="0.4"/>
<circle cx="620" cy="316" r="7.5" transform="rotate(-180 620 316)" fill="url(#paint85_radial_781_319508)" stroke="white"/>
<circle cx="620" cy="316" r="1" transform="rotate(-180 620 316)" fill="black" fill-opacity="0.4"/>
<circle cx="590" cy="316" r="7.5" transform="rotate(-180 590 316)" fill="url(#paint86_radial_781_319508)" stroke="white"/>
<circle cx="590" cy="316" r="1" transform="rotate(-180 590 316)" fill="black" fill-opacity="0.4"/>
<circle cx="530" cy="376" r="7.5" transform="rotate(-180 530 376)" fill="url(#paint87_radial_781_319508)" stroke="white"/>
<circle cx="530" cy="376" r="1" transform="rotate(-180 530 376)" fill="black" fill-opacity="0.4"/>
<rect x="470" y="437" width="120" height="2" transform="rotate(-180 470 437)" fill="#FF9D52"/>
<rect x="470" y="377" width="120" height="2" transform="rotate(-180 470 377)" fill="#FF9D52"/>
<rect x="470" y="317" width="120" height="2" transform="rotate(-180 470 317)" fill="#FF9D52"/>
<rect x="411" y="438" width="2" height="120" transform="rotate(-180 411 438)" fill="#4196D8"/>
<rect x="349.195" y="378" width="2" height="80" transform="rotate(-135 349.195 378)" fill="#D34F4F"/>
<rect x="405.766" y="430.586" width="2" height="80" transform="rotate(135 405.766 430.586)" fill="#D34F4F"/>
<rect x="470.766" y="375" width="2" height="80" transform="rotate(135 470.766 375)" fill="#D34F4F"/>
<rect x="411.211" y="436.414" width="2" height="80" transform="rotate(-135 411.211 436.414)" fill="#D34F4F"/>
<rect x="471" y="438" width="2" height="120" transform="rotate(-180 471 438)" fill="#FF9D52"/>
<rect x="351" y="438" width="2" height="120" transform="rotate(-180 351 438)" fill="#FF9D52"/>
<circle cx="470" cy="436" r="7.5" transform="rotate(-180 470 436)" fill="url(#paint88_radial_781_319508)" stroke="white"/>
<circle cx="470" cy="436" r="1" transform="rotate(-180 470 436)" fill="black" fill-opacity="0.4"/>
<circle cx="440" cy="436" r="7.5" transform="rotate(-180 440 436)" fill="url(#paint89_radial_781_319508)" stroke="white"/>
<circle cx="440" cy="436" r="1" transform="rotate(-180 440 436)" fill="black" fill-opacity="0.4"/>
<circle cx="410" cy="436" r="7.5" transform="rotate(-180 410 436)" fill="url(#paint90_radial_781_319508)" stroke="white"/>
<circle cx="410" cy="436" r="1" transform="rotate(-180 410 436)" fill="black" fill-opacity="0.4"/>
<circle cx="380" cy="436" r="7.5" transform="rotate(-180 380 436)" fill="url(#paint91_radial_781_319508)" stroke="white"/>
<circle cx="380" cy="436" r="1" transform="rotate(-180 380 436)" fill="black" fill-opacity="0.4"/>
<circle cx="350" cy="436" r="7.5" transform="rotate(-180 350 436)" fill="url(#paint92_radial_781_319508)" stroke="white"/>
<circle cx="350" cy="436" r="1" transform="rotate(-180 350 436)" fill="black" fill-opacity="0.4"/>
<circle cx="440" cy="376" r="7.5" transform="rotate(-180 440 376)" fill="url(#paint93_radial_781_319508)" stroke="white"/>
<circle cx="440" cy="376" r="1" transform="rotate(-180 440 376)" fill="black" fill-opacity="0.4"/>
<circle cx="380" cy="376" r="7.5" transform="rotate(-180 380 376)" fill="url(#paint94_radial_781_319508)" stroke="white"/>
<circle cx="380" cy="376" r="1" transform="rotate(-180 380 376)" fill="black" fill-opacity="0.4"/>
<circle cx="410" cy="376" r="7.5" transform="rotate(-180 410 376)" fill="url(#paint95_radial_781_319508)" stroke="white"/>
<circle cx="410" cy="376" r="1" transform="rotate(-180 410 376)" fill="black" fill-opacity="0.4"/>
<circle cx="350" cy="406" r="7.5" transform="rotate(-180 350 406)" fill="url(#paint96_radial_781_319508)" stroke="white"/>
<circle cx="350" cy="406" r="1" transform="rotate(-180 350 406)" fill="black" fill-opacity="0.4"/>
<circle cx="470" cy="406" r="7.5" transform="rotate(-180 470 406)" fill="url(#paint97_radial_781_319508)" stroke="white"/>
<circle cx="470" cy="406" r="1" transform="rotate(-180 470 406)" fill="black" fill-opacity="0.4"/>
<circle cx="350" cy="376" r="7.5" transform="rotate(-180 350 376)" fill="url(#paint98_radial_781_319508)" stroke="white"/>
<circle cx="350" cy="376" r="1" transform="rotate(-180 350 376)" fill="black" fill-opacity="0.4"/>
<circle cx="470" cy="376" r="7.5" transform="rotate(-180 470 376)" fill="url(#paint99_radial_781_319508)" stroke="white"/>
<circle cx="470" cy="376" r="1" transform="rotate(-180 470 376)" fill="black" fill-opacity="0.4"/>
<circle cx="350" cy="346" r="7.5" transform="rotate(-180 350 346)" fill="url(#paint100_radial_781_319508)" stroke="white"/>
<circle cx="350" cy="346" r="1" transform="rotate(-180 350 346)" fill="black" fill-opacity="0.4"/>
<circle cx="470" cy="346" r="7.5" transform="rotate(-180 470 346)" fill="url(#paint101_radial_781_319508)" stroke="white"/>
<circle cx="470" cy="346" r="1" transform="rotate(-180 470 346)" fill="black" fill-opacity="0.4"/>
<circle cx="470" cy="316" r="7.5" transform="rotate(-180 470 316)" fill="url(#paint102_radial_781_319508)" stroke="white"/>
<circle cx="470" cy="316" r="1" transform="rotate(-180 470 316)" fill="black" fill-opacity="0.4"/>
<circle cx="440" cy="316" r="7.5" transform="rotate(-180 440 316)" fill="url(#paint103_radial_781_319508)" stroke="white"/>
<circle cx="440" cy="316" r="1" transform="rotate(-180 440 316)" fill="black" fill-opacity="0.4"/>
<circle cx="410" cy="316" r="7.5" transform="rotate(-180 410 316)" fill="url(#paint104_radial_781_319508)" stroke="white"/>
<circle cx="410" cy="316" r="1" transform="rotate(-180 410 316)" fill="black" fill-opacity="0.4"/>
<circle cx="380" cy="316" r="7.5" transform="rotate(-180 380 316)" fill="url(#paint105_radial_781_319508)" stroke="white"/>
<circle cx="380" cy="316" r="1" transform="rotate(-180 380 316)" fill="black" fill-opacity="0.4"/>
<circle cx="350" cy="316" r="7.5" transform="rotate(-180 350 316)" fill="url(#paint106_radial_781_319508)" stroke="white"/>
<circle cx="350" cy="316" r="1" transform="rotate(-180 350 316)" fill="black" fill-opacity="0.4"/>
<circle cx="290" cy="376" r="7.5" transform="rotate(-180 290 376)" fill="url(#paint107_radial_781_319508)" stroke="white"/>
<circle cx="290" cy="376" r="1" transform="rotate(-180 290 376)" fill="black" fill-opacity="0.4"/>
<rect x="230" y="437" width="120" height="2" transform="rotate(-180 230 437)" fill="#FF9D52"/>
<rect x="230" y="377" width="120" height="2" transform="rotate(-180 230 377)" fill="#FF9D52"/>
<rect x="230" y="317" width="120" height="2" transform="rotate(-180 230 317)" fill="#FF9D52"/>
<rect x="171" y="438" width="2" height="120" transform="rotate(-180 171 438)" fill="#4196D8"/>
<rect x="109.195" y="378" width="2" height="80" transform="rotate(-135 109.195 378)" fill="#D34F4F"/>
<rect x="165.766" y="430.586" width="2" height="80" transform="rotate(135 165.766 430.586)" fill="#D34F4F"/>
<rect x="230.766" y="375" width="2" height="80" transform="rotate(135 230.766 375)" fill="#D34F4F"/>
<rect x="173.914" y="433.414" width="2" height="80" transform="rotate(-135 173.914 433.414)" fill="#D34F4F"/>
<rect x="231" y="438" width="2" height="120" transform="rotate(-180 231 438)" fill="#FF9D52"/>
<rect x="111" y="438" width="2" height="120" transform="rotate(-180 111 438)" fill="#FF9D52"/>
<circle cx="230" cy="436" r="7.5" transform="rotate(-180 230 436)" fill="url(#paint108_radial_781_319508)" stroke="white"/>
<circle cx="230" cy="436" r="1" transform="rotate(-180 230 436)" fill="black" fill-opacity="0.4"/>
<circle cx="200" cy="436" r="7.5" transform="rotate(-180 200 436)" fill="url(#paint109_radial_781_319508)" stroke="white"/>
<circle cx="200" cy="436" r="1" transform="rotate(-180 200 436)" fill="black" fill-opacity="0.4"/>
<circle cx="170" cy="436" r="7.5" transform="rotate(-180 170 436)" fill="url(#paint110_radial_781_319508)" stroke="white"/>
<circle cx="170" cy="436" r="1" transform="rotate(-180 170 436)" fill="black" fill-opacity="0.4"/>
<circle cx="140" cy="436" r="7.5" transform="rotate(-180 140 436)" fill="url(#paint111_radial_781_319508)" stroke="white"/>
<circle cx="140" cy="436" r="1" transform="rotate(-180 140 436)" fill="black" fill-opacity="0.4"/>
<circle cx="110" cy="436" r="7.5" transform="rotate(-180 110 436)" fill="url(#paint112_radial_781_319508)" stroke="white"/>
<circle cx="110" cy="436" r="1" transform="rotate(-180 110 436)" fill="black" fill-opacity="0.4"/>
<circle cx="200" cy="376" r="7.5" transform="rotate(-180 200 376)" fill="url(#paint113_radial_781_319508)" stroke="white"/>
<circle cx="200" cy="376" r="1" transform="rotate(-180 200 376)" fill="black" fill-opacity="0.4"/>
<circle cx="140" cy="376" r="7.5" transform="rotate(-180 140 376)" fill="url(#paint114_radial_781_319508)" stroke="white"/>
<circle cx="140" cy="376" r="1" transform="rotate(-180 140 376)" fill="black" fill-opacity="0.4"/>
<circle cx="170" cy="376" r="7.5" transform="rotate(-180 170 376)" fill="url(#paint115_radial_781_319508)" stroke="white"/>
<circle cx="170" cy="376" r="1" transform="rotate(-180 170 376)" fill="black" fill-opacity="0.4"/>
<circle cx="110" cy="406" r="7.5" transform="rotate(-180 110 406)" fill="url(#paint116_radial_781_319508)" stroke="white"/>
<circle cx="110" cy="406" r="1" transform="rotate(-180 110 406)" fill="black" fill-opacity="0.4"/>
<circle cx="230" cy="406" r="7.5" transform="rotate(-180 230 406)" fill="url(#paint117_radial_781_319508)" stroke="white"/>
<circle cx="230" cy="406" r="1" transform="rotate(-180 230 406)" fill="black" fill-opacity="0.4"/>
<circle cx="110" cy="376" r="7.5" transform="rotate(-180 110 376)" fill="url(#paint118_radial_781_319508)" stroke="white"/>
<circle cx="110" cy="376" r="1" transform="rotate(-180 110 376)" fill="black" fill-opacity="0.4"/>
<circle cx="230" cy="376" r="7.5" transform="rotate(-180 230 376)" fill="url(#paint119_radial_781_319508)" stroke="white"/>
<circle cx="230" cy="376" r="1" transform="rotate(-180 230 376)" fill="black" fill-opacity="0.4"/>
<circle cx="110" cy="346" r="7.5" transform="rotate(-180 110 346)" fill="url(#paint120_radial_781_319508)" stroke="white"/>
<circle cx="110" cy="346" r="1" transform="rotate(-180 110 346)" fill="black" fill-opacity="0.4"/>
<circle cx="230" cy="346" r="7.5" transform="rotate(-180 230 346)" fill="url(#paint121_radial_781_319508)" stroke="white"/>
<circle cx="230" cy="346" r="1" transform="rotate(-180 230 346)" fill="black" fill-opacity="0.4"/>
<circle cx="230" cy="316" r="7.5" transform="rotate(-180 230 316)" fill="url(#paint122_radial_781_319508)" stroke="white"/>
<circle cx="230" cy="316" r="1" transform="rotate(-180 230 316)" fill="black" fill-opacity="0.4"/>
<circle cx="200" cy="316" r="7.5" transform="rotate(-180 200 316)" fill="url(#paint123_radial_781_319508)" stroke="white"/>
<circle cx="200" cy="316" r="1" transform="rotate(-180 200 316)" fill="black" fill-opacity="0.4"/>
<circle cx="170" cy="316" r="7.5" transform="rotate(-180 170 316)" fill="url(#paint124_radial_781_319508)" stroke="white"/>
<circle cx="170" cy="316" r="1" transform="rotate(-180 170 316)" fill="black" fill-opacity="0.4"/>
<circle cx="140" cy="316" r="7.5" transform="rotate(-180 140 316)" fill="url(#paint125_radial_781_319508)" stroke="white"/>
<circle cx="140" cy="316" r="1" transform="rotate(-180 140 316)" fill="black" fill-opacity="0.4"/>
<circle cx="110" cy="316" r="7.5" transform="rotate(-180 110 316)" fill="url(#paint126_radial_781_319508)" stroke="white"/>
<circle cx="110" cy="316" r="1" transform="rotate(-180 110 316)" fill="black" fill-opacity="0.4"/>
<circle cx="680" cy="466" r="7.5" transform="rotate(-180 680 466)" fill="url(#paint127_radial_781_319508)" stroke="white"/>
<circle cx="680" cy="466" r="1" transform="rotate(-180 680 466)" fill="black" fill-opacity="0.4"/>
<circle cx="440" cy="466" r="7.5" transform="rotate(-180 440 466)" fill="url(#paint128_radial_781_319508)" stroke="white"/>
<circle cx="440" cy="466" r="1" transform="rotate(-180 440 466)" fill="black" fill-opacity="0.4"/>
<circle cx="200" cy="466" r="7.5" transform="rotate(-180 200 466)" fill="url(#paint129_radial_781_319508)" stroke="white"/>
<circle cx="200" cy="466" r="1" transform="rotate(-180 200 466)" fill="black" fill-opacity="0.4"/>
<circle cx="620" cy="466" r="7.5" transform="rotate(-180 620 466)" fill="url(#paint130_radial_781_319508)" stroke="white"/>
<circle cx="620" cy="466" r="1" transform="rotate(-180 620 466)" fill="black" fill-opacity="0.4"/>
<circle cx="380" cy="466" r="7.5" transform="rotate(-180 380 466)" fill="url(#paint131_radial_781_319508)" stroke="white"/>
<circle cx="380" cy="466" r="1" transform="rotate(-180 380 466)" fill="black" fill-opacity="0.4"/>
<circle cx="140" cy="466" r="7.5" transform="rotate(-180 140 466)" fill="url(#paint132_radial_781_319508)" stroke="white"/>
<circle cx="140" cy="466" r="1" transform="rotate(-180 140 466)" fill="black" fill-opacity="0.4"/>
<circle cx="590" cy="196" r="7.5" fill="url(#paint133_radial_781_319508)" stroke="white"/>
<circle cx="590" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="620" cy="196" r="7.5" fill="url(#paint134_radial_781_319508)" stroke="white"/>
<circle cx="620" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="650" cy="196" r="7.5" fill="url(#paint135_radial_781_319508)" stroke="white"/>
<circle cx="650" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="680" cy="196" r="7.5" fill="url(#paint136_radial_781_319508)" stroke="white"/>
<circle cx="680" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<circle cx="710" cy="196" r="7.5" fill="url(#paint137_radial_781_319508)" stroke="white"/>
<circle cx="710" cy="196" r="1" fill="black" fill-opacity="0.4"/>
<defs>
<radialGradient id="paint0_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(110 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint1_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(140 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint2_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(170 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint3_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(200 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint4_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(230 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint5_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(140 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint6_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(200 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint7_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(170 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint8_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(230 106) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint9_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(110 106) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint10_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(230 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint11_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(110 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint12_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(230 166) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint13_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(110 166) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint14_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(290 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint15_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(290 256) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint16_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(530 256) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint17_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint18_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(380 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint19_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(410 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint20_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(440 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint21_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(470 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint22_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(380 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint23_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(440 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint24_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(410 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint25_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(470 106) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint26_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 106) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint27_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(470 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint28_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint29_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(470 166) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint30_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 166) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint31_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint32_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(380 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint33_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(410 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint34_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(440 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint35_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(470 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint36_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(530 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint37_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(590 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint38_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(620 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint39_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(650 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint40_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(680 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint41_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(710 76) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint42_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(620 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint43_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(680 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint44_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(650 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint45_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(710 106) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint46_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(590 106) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint47_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(710 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint48_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(590 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint49_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(710 166) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint50_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(590 166) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint51_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(60 136) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint52_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(60 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint53_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(60 256) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint54_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(110 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint55_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(140 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint56_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(170 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint57_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(200 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint58_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(230 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint59_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(760.461 375.379) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint60_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(760.461 255.379) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint61_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(760.461 135.383) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint62_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(140 46) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint63_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(380 46) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint64_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(620 46) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint65_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(200 46) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint66_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(440 46) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint67_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(680 46) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint68_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(710 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint69_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(680 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint70_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(650 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint71_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(620 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint72_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(590 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint73_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(680 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint74_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(620 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint75_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(650 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint76_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(590 406) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint77_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(710 406) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint78_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(590 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint79_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(710 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint80_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(590 346) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint81_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(710 346) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint82_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(710 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint83_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(680 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint84_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(650 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint85_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(620 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint86_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(590 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint87_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(530 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint88_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(470 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint89_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(440 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint90_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(410 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint91_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(380 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint92_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint93_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(440 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint94_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(380 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint95_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(410 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint96_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 406) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint97_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(470 406) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint98_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint99_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(470 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint100_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 346) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint101_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(470 346) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint102_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(470 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint103_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(440 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint104_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(410 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint105_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(380 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint106_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint107_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(290 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint108_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(230 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint109_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(200 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint110_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(170 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint111_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(140 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint112_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(110 436) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint113_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(200 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint114_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(140 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint115_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(170 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint116_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(110 406) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint117_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(230 406) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint118_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(110 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint119_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(230 376) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint120_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(110 346) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint121_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(230 346) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint122_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(230 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint123_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(200 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint124_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(170 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint125_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(140 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint126_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(110 316) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint127_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(680 466) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint128_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(440 466) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint129_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(200 466) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint130_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(620 466) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint131_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(380 466) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint132_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(140 466) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint133_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(590 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint134_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(620 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint135_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(650 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint136_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(680 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
<radialGradient id="paint137_radial_781_319508" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(710 196) rotate(90) scale(7.5)">
<stop stop-color="#B39351"/>
<stop offset="1" stop-color="#FFECC4"/>
</radialGradient>
</defs>
</svg>

```

## /mypy.ini

```ini path="/mypy.ini" 
[mypy]
pretty = True
show_error_codes = True

# Exclude _files.py because mypy isn't smart enough to apply
# the correct type narrowing and as this is an internal module
# it's fine to just use Pyright.
#
# We also exclude our `tests` as mypy doesn't always infer
# types correctly and Pyright will still catch any type errors.
exclude = ^(src/tinker/_files\.py|_dev/.*\.py|tests/.*)$

strict_equality = True
implicit_reexport = True
check_untyped_defs = True
no_implicit_optional = True

warn_return_any = True
warn_unreachable = True
warn_unused_configs = True

# Turn these options off as it could cause conflicts
# with the Pyright options.
warn_unused_ignores = False
warn_redundant_casts = False

disallow_any_generics = True
disallow_untyped_defs = True
disallow_untyped_calls = True
disallow_subclassing_any = True
disallow_incomplete_defs = True
disallow_untyped_decorators = True
cache_fine_grained = True

# By default, mypy reports an error if you assign a value to the result
# of a function call that doesn't return anything. We do this in our test
# cases:
# \`\`\`
# result = ...
# assert result is None
# \`\`\`
# Changing this codegen to make mypy happy would increase complexity
# and would not be worth it.
disable_error_code = func-returns-value,overload-cannot-match

# https://github.com/python/mypy/issues/12162
[mypy.overrides]
module = "black.files.*"
ignore_errors = true
ignore_missing_imports = true

```

## /pydoc-markdown.yml

```yml path="/pydoc-markdown.yml" 
# Pydoc-markdown configuration for Tinker Python SDK
# Generates MDX-compatible documentation for Nextra integration

loaders:
  - type: python
    search_path: []
    packages: []
    ignore_when_discovered: [__pycache__, tests, test_*, conftest, mock_*, _test*]

processors:
  - type: filter
    documented_only: true
    exclude_private: true
    exclude_special: true
    skip_empty_modules: true

renderer:
  type: markdown
  classdef_code_block: true
  code_headers: true
  code_lang: true
  escape_html_in_docstring: false
  insert_header_anchors: false
  render_module_header: false
  render_toc: false
  signature_code_block: true
  signature_with_decorators: false

```

## /pyproject.toml

```toml path="/pyproject.toml" 
[project]
name = "tinker"
version = "0.22.3"
description = "The official Python SDK for the tinker API"
readme = "README.md"
license = "Apache-2.0"
authors = [
{ name = "Tinker authors", email = "tinker@thinkingmachines.ai" },
]
keywords = ["tinker", "machine learning"]
dependencies = [
    "httpx[http2]>=0.23.0, <1",
    "pyqwest>=0.4.1",
    "pydantic>=1.9.0, <3",
    "typing-extensions>=4.10, <5",
    "anyio>=3.5.0, <5",
    "distro>=1.7.0, <2",
    "sniffio",
    "numpy",
    "protobuf>=4.21",
    "transformers",
    "rich>=13.0.0",
    "click>=8.0.0",
    "orjson>=3.10.0",
    "zstandard>=0.22.0",
]

requires-python = ">= 3.11"
classifiers = [
  "Typing :: Typed",
  "Intended Audience :: Developers",
  "Programming Language :: Python :: 3.9",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
  "Programming Language :: Python :: 3.13",
  "Operating System :: OS Independent",
  "Operating System :: POSIX",
  "Operating System :: MacOS",
  "Operating System :: POSIX :: Linux",
  "Operating System :: Microsoft :: Windows",
  "Topic :: Software Development :: Libraries :: Python Modules",
  "License :: OSI Approved :: Apache Software License"
]

[project.scripts]
tinker = "tinker.cli.__main__:cli"

[project.urls]
Homepage = "https://thinkingmachines.ai/tinker"
Repository = "https://github.com/thinking-machines-lab/tinker"
Documentation = "https://tinker-docs.thinkingmachines.ai/"

[project.optional-dependencies]
aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"]
torch = ["torch"]

[tool.uv]
managed = true
required-version = ">=0.5.0"
# version pins are in uv.lock

[dependency-groups]
dev = [
    "pyright==1.1.402",
    "mypy",
    "respx",
    "pytest",
    "pytest-asyncio",
    "ruff",
    "time-machine",
    "dirty-equals>=0.6.0",
    "importlib-metadata>=6.7.0",
    "rich>=13.7.1",
    "nest_asyncio==1.6.0",
    "pytest-xdist>=3.6.1",
]

[build-system]
requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"

[tool.hatch.build]
include = [
  "src/*"
]

[tool.hatch.build.targets.wheel]
packages = ["src/tinker"]

[tool.hatch.build.targets.sdist]
# Basically everything except hidden files/directories (such as .github, .devcontainers, .python-version, etc)
include = [
  "/*.toml",
  "/*.json",
  "/*.lock",
  "/*.md",
  "/mypy.ini",
  "/noxfile.py",
  "bin/*",
  "examples/*",
  "src/*",
  "tests/*",
]

[tool.hatch.metadata.hooks.fancy-pypi-readme]
content-type = "text/markdown"

[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
path = "README.md"

[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
# replace relative links with absolute links
pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)'
replacement = '[\1](https://github.com/stainless-sdks/tinker-python/tree/main/\g<2>)'

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--tb=short -n auto"
xfail_strict = true
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "session"
filterwarnings = [
  "error"
]

[tool.pyright]
# this enables practically every flag given by pyright.
# there are a couple of flags that are still disabled by
# default in strict mode as they are experimental and niche.
typeCheckingMode = "strict"
pythonVersion = "3.8"

exclude = [
    "_dev",
    ".venv",
    ".nox",
]

reportImplicitOverride = true
reportOverlappingOverload = false

reportImportCycles = false
reportPrivateUsage = false

[tool.deptry]
extend_exclude = [
    # Generated protobuf files import `google.protobuf` via the `protobuf` package
    "src/tinker/proto/.*",
    # Test files colocated with source — they legitimately import pytest (dev dep)
    ".*_test\\.py",
]
known_first_party = ["tinker"]

[tool.deptry.per_rule_ignores]
# DEP001: module imported but not declared. These are intentional lazy imports
# with install-on-demand UX, or transitive deps guaranteed by a declared parent.
DEP001 = [
    "huggingface_hub",  # lazy import in cli/commands/checkpoint.py, prompts user to install
    "pydantic_core",    # transitive, guaranteed by pydantic
    "tml_tokenizers",   # lazy import, internal tooling only
]
# DEP002: declared but unused. These are runtime-required transitives not directly imported.
DEP002 = [
    "protobuf",  # used via `google.protobuf` in generated code
    "aiohttp",   # optional [aiohttp] extra
]

```

## /scripts/generate_docs.py

```py path="/scripts/generate_docs.py" 
#!/usr/bin/env python3
# /// script
# dependencies = [
#   "pydoc-markdown>=4.8.0",
#   "pyyaml>=6.0",
# ]
# ///


import ast
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Dict, List


def cd_to_project_root():
    """Change to the project root (parent of the scripts directory)."""
    script_dir = Path(__file__).resolve().parent
    project_root = script_dir.parent
    os.chdir(project_root)
    print(f"Changed to project root: {project_root}")


class ModuleAnalyzer:
    """Analyze Python modules to extract public API information."""

    def __init__(self, src_path: Path):
        self.src_path = src_path

    def get_module_exports(self, module_path: Path) -> List[str]:
        """Extract __all__ exports from a module."""
        try:
            content = module_path.read_text()
            tree = ast.parse(content)

            for node in ast.walk(tree):
                if isinstance(node, ast.Assign):
                    for target in node.targets:
                        if (
                            isinstance(target, ast.Name)
                            and target.id == "__all__"
                            and isinstance(node.value, ast.List)
                        ):
                            return [
                                elt.s for elt in node.value.elts if isinstance(elt, ast.Str)
                            ] or [
                                elt.value
                                for elt in node.value.elts
                                if isinstance(elt, ast.Constant) and isinstance(elt.value, str)
                            ]
        except Exception as e:
            print(f"Warning: Could not parse {module_path}: {e}")
        return []

    def find_all_modules(self) -> Dict[str, Path]:
        """Find all Python modules in the package."""
        modules = {}
        tinker_path = self.src_path / "tinker"

        for py_file in tinker_path.rglob("*.py"):
            # Skip test files and private modules
            if any(part.startswith(("test", "_test")) for part in py_file.parts):
                continue
            if "__pycache__" in py_file.parts:
                continue

            # Calculate module name
            relative_path = py_file.relative_to(self.src_path)
            module_parts = list(relative_path.parts[:-1])  # Remove .py file
            module_parts.append(relative_path.stem)

            # Skip __init__ files in module name
            if module_parts[-1] == "__init__":
                module_parts = module_parts[:-1]

            module_name = ".".join(module_parts)
            if module_name:  # Skip empty module names
                modules[module_name] = py_file

        return modules


class DocumentationGenerator:
    """Generate documentation using pydoc-markdown."""

    def __init__(self, config_path: Path, output_dir: Path):
        self.config_path = config_path
        self.output_dir = output_dir
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.analyzer = ModuleAnalyzer(Path("src"))

    def run_pydoc_markdown(self, modules: List[str], output_file: Path) -> bool:
        """Run pydoc-markdown for specific modules."""
        try:
            # Invoke pydoc-markdown via uvx/uv tool run so the required
            # doc-generation dependencies are resolved consistently.
            uvx_path = shutil.which("uvx")
            if uvx_path:
                cmd = [
                    uvx_path,
                    "--from",
                    "pydoc-markdown>=4.8.0",
                    "--with",
                    "pyyaml>=6.0",
                    "--with",
                    "setuptools",
                    "pydoc-markdown",
                    "pydoc-markdown.yml",
                    "-I",
                    "src",
                ]
            else:
                uv_path = shutil.which("uv")
                if uv_path is None:
                    raise FileNotFoundError("Could not find `uvx` or `uv` on PATH")
                cmd = [
                    uv_path,
                    "tool",
                    "run",
                    "--from",
                    "pydoc-markdown>=4.8.0",
                    "--with",
                    "pyyaml>=6.0",
                    "--with",
                    "setuptools",
                    "pydoc-markdown",
                    "pydoc-markdown.yml",
                    "-I",
                    "src",
                ]

            # Add modules
            for module in modules:
                cmd.extend(["-m", module])

            # Run pydoc-markdown
            result = subprocess.run(cmd, capture_output=True, text=True, check=False)

            if result.returncode == 0:
                # Write output to file
                output_file.parent.mkdir(parents=True, exist_ok=True)
                output_file.write_text(result.stdout)
                print(f"Generated: {output_file}")
                return True
            else:
                print(f"Error generating {output_file}: {result.stderr}")
                return False

        except Exception as e:
            print(f"Exception generating {output_file}: {e}")
            return False

    def generate_public_interfaces(self):
        """Generate documentation for public interface classes."""
        print("\n=== Generating Public Interfaces Documentation ===")

        # Generate individual pages for each client
        client_modules = [
            ("ServiceClient", "tinker.lib.public_interfaces.service_client"),
            ("TrainingClient", "tinker.lib.public_interfaces.training_client"),
            ("SamplingClient", "tinker.lib.public_interfaces.sampling_client"),
            ("RestClient", "tinker.lib.public_interfaces.rest_client"),
            ("APIFuture", "tinker.lib.public_interfaces.api_future"),
        ]

        for class_name, module in client_modules:
            output_file = self.output_dir / f"{class_name.lower().replace('_', '-')}.md"
            self.run_pydoc_markdown([module], output_file)

    def generate_all_types(self):
        """Generate complete types reference."""
        print("\n=== Generating Complete Types Reference ===")

        # Get all type modules
        all_modules = self.analyzer.find_all_modules()
        type_modules = [m for m in all_modules if m.startswith("tinker.types")]

        if type_modules:
            output_file = self.output_dir / "types.md"
            self.run_pydoc_markdown(type_modules, output_file)

    def generate_exceptions(self):
        """Generate exception hierarchy documentation."""
        print("\n=== Generating Exception Documentation ===")

        output_file = self.output_dir / "exceptions.md"
        self.run_pydoc_markdown(["tinker._exceptions"], output_file)

    def generate_nextra_meta(self):
        """Generate _meta.json for Nextra navigation."""
        print("\n=== Generating Nextra Navigation Metadata ===")

        meta = {
            "serviceclient": "ServiceClient",
            "trainingclient": "TrainingClient",
            "samplingclient": "SamplingClient",
            "restclient": "RestClient",
            "apifuture": "APIFuture",
            "types": "Parameters",
            "exceptions": "Exceptions",
        }

        meta_file = self.output_dir / "_meta.json"
        meta_file.write_text(json.dumps(meta, indent=2))
        print(f"Generated: {meta_file}")

    def generate_all(self):
        """Generate all documentation."""
        print("Starting documentation generation...")
        print(f"Output directory: {self.output_dir}")

        # Generate documentation for each category
        self.generate_public_interfaces()
        self.generate_all_types()
        self.generate_exceptions()

        # Generate Nextra metadata
        self.generate_nextra_meta()

        print("\n=== Documentation Generation Complete ===")
        print(f"Markdown files generated in: {self.output_dir}")
        print("\nGenerated files:")
        for file in sorted(self.output_dir.rglob("*.md")):
            print(f"  - {file.relative_to(self.output_dir)}")


def main():
    """Main entry point."""
    # Change to project root first
    cd_to_project_root()

    # Paths
    project_root = Path.cwd()
    config_path = project_root / "pydoc-markdown.yml"
    output_dir = project_root / "docs" / "api"

    # Check if config exists
    if not config_path.exists():
        print(f"Error: Configuration file not found at {config_path}")
        print("Please run this script from the project root directory")
        sys.exit(1)

    # Create generator and run
    generator = DocumentationGenerator(config_path, output_dir)
    generator.generate_all()

    # Print usage instructions
    print("\n" + "=" * 50)
    print("To use these docs in your Nextra project:")
    print("1. Copy the docs/api directory to your Nextra project")
    print("2. The markdown files are ready to use with Nextra")
    print("3. Navigation structure is defined in _meta.json")
    print("\nTo regenerate docs after code changes:")
    print("  uv run scripts/generate_docs.py")


if __name__ == "__main__":
    main()

```

## /scripts/publish-pypi

``` path="/scripts/publish-pypi" 
#!/usr/bin/env bash

set -eux
rm -rf dist
mkdir -p dist
uv build
uv publish --token=$PYPI_TOKEN

```

## /src/tinker/__init__.py

```py path="/src/tinker/__init__.py" 
import typing as _t

from . import types
from ._client import RequestOptions, Timeout
from ._exceptions import (
    APIConnectionError,
    APIError,
    APIResponseValidationError,
    APIStatusError,
    APITimeoutError,
    AuthenticationError,
    BadRequestError,
    ConflictError,
    InternalServerError,
    NotFoundError,
    PermissionDeniedError,
    RateLimitError,
    RequestFailedError,
    SidecarDiedError,
    SidecarError,
    SidecarIPCError,
    SidecarStartupError,
    TinkerError,
    UnprocessableEntityError,
)
from ._response import APIResponse as APIResponse
from ._response import AsyncAPIResponse as AsyncAPIResponse
from ._utils._logs import setup_logging as _setup_logging
from ._version import __title__, __version__
from .lib.public_interfaces import APIFuture, SamplingClient, ServiceClient, TrainingClient

# Import commonly used types for easier access
from .types import (
    AdamParams,
    Checkpoint,
    CheckpointType,
    Datum,
    EncodedTextChunk,
    ForwardBackwardOutput,
    LoraConfig,
    ModelID,
    ModelInput,
    ModelInputChunk,
    OptimStepRequest,
    OptimStepResponse,
    ParsedCheckpointTinkerPath,
    SampledSequence,
    SampleRequest,
    SampleResponse,
    SamplingParams,
    StopReason,
    TensorData,
    TensorDtype,
    TrainingRun,
)

__all__ = [
    # Core clients
    "TrainingClient",
    "ServiceClient",
    "SamplingClient",
    "APIFuture",
    # Commonly used types
    "AdamParams",
    "Checkpoint",
    "CheckpointType",
    "Datum",
    "EncodedTextChunk",
    "ForwardBackwardOutput",
    "LoraConfig",
    "ModelID",
    "ModelInput",
    "ModelInputChunk",
    "OptimStepRequest",
    "OptimStepResponse",
    "ParsedCheckpointTinkerPath",
    "SampledSequence",
    "SampleRequest",
    "SampleResponse",
    "SamplingParams",
    "StopReason",
    "TensorData",
    "TensorDtype",
    "TrainingRun",
    # Client configuration
    "Timeout",
    "RequestOptions",
    # Exception types
    "TinkerError",
    "APIError",
    "APIStatusError",
    "APITimeoutError",
    "APIConnectionError",
    "APIResponseValidationError",
    "RequestFailedError",
    "BadRequestError",
    "AuthenticationError",
    "PermissionDeniedError",
    "NotFoundError",
    "ConflictError",
    "UnprocessableEntityError",
    "RateLimitError",
    "InternalServerError",
    "SidecarError",
    "SidecarStartupError",
    "SidecarDiedError",
    "SidecarIPCError",
    # Keep types module for advanced use
    "types",
    # Version info
    "__version__",
    "__title__",
]

if not _t.TYPE_CHECKING:
    from ._utils._resources_proxy import resources as resources

_setup_logging()

# Update the __module__ attribute for exported symbols so that
# error messages point to this module instead of the module
# it was originally defined in, e.g.
# tinker._exceptions.NotFoundError -> tinker.NotFoundError
__locals = locals()
for __name in __all__:
    if not __name.startswith("__"):
        try:
            __locals[__name].__module__ = "tinker"
        except (TypeError, AttributeError):
            # Some of our exported symbols are builtins which we can't set attributes for.
            pass

```

## /src/tinker/_base_client.py

```py path="/src/tinker/_base_client.py" 
from __future__ import annotations

import asyncio
import email
import email.utils
import inspect
import json
import logging
import platform
import sys
import time
import uuid
from random import random
from types import TracebackType
from typing import (
    TYPE_CHECKING,
    Any,
    AsyncIterator,
    Dict,
    Generator,
    Generic,
    Iterable,
    Mapping,
    Optional,
    Type,
    TypeVar,
    Union,
    cast,
    overload,
)

import anyio
import distro
import httpx
import pydantic
from httpx import URL
from pydantic import PrivateAttr
from typing_extensions import Literal, get_origin, override

from . import _exceptions
from ._compat import PYDANTIC_V2, model_copy, model_dump
from ._constants import (
    DEFAULT_CONNECTION_LIMITS,
    DEFAULT_MAX_RETRIES,
    DEFAULT_TIMEOUT,
    INITIAL_RETRY_DELAY,
    MAX_RETRY_DELAY,
    OVERRIDE_CAST_TO_HEADER,
    RAW_RESPONSE_HEADER,
)
from ._exceptions import (
    APIConnectionError,
    APIResponseValidationError,
    APIStatusError,
    APITimeoutError,
)
from ._files import async_to_httpx_files
from ._models import FinalRequestOptions, GenericModel, construct_type, validate_type
from ._qs import Querystring
from ._response import (
    AsyncAPIResponse,
    BaseAPIResponse,
    extract_response_type,
)
from ._streaming import AsyncStream, SSEBytesDecoder, SSEDecoder
from ._types import (
    NOT_GIVEN,
    AnyMapping,
    Body,
    Headers,
    HttpxRequestFiles,
    HttpxSendArgs,
    ModelBuilderProtocol,
    NotGiven,
    Omit,
    PostParser,
    Query,
    RequestFiles,
    RequestOptions,
    ResponseT,
    Timeout,
)
from ._utils import asyncify, is_dict, is_given, is_list, is_mapping, lru_cache

log: logging.Logger = logging.getLogger(__name__)

# TODO: make base page type vars covariant
AsyncPageT = TypeVar("AsyncPageT", bound="BaseAsyncPage[Any]")


_T = TypeVar("_T")
_T_co = TypeVar("_T_co", covariant=True)

_AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any])

if TYPE_CHECKING:
    from httpx._config import (
        DEFAULT_TIMEOUT_CONFIG,  # pyright: ignore[reportPrivateImportUsage]
    )

    HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG
else:
    try:
        from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
    except ImportError:
        # taken from https://github.com/encode/httpx/blob/3ba5fe0d7ac70222590e759c31442b1cab263791/httpx/_config.py#L366
        HTTPX_DEFAULT_TIMEOUT = Timeout(5.0)


class PageInfo:
    """Stores the necessary information to build the request to retrieve the next page.

    Either `url` or `params` must be set.
    """

    url: URL | NotGiven
    params: Query | NotGiven
    json: Body | NotGiven

    @overload
    def __init__(
        self,
        *,
        url: URL,
    ) -> None: ...

    @overload
    def __init__(
        self,
        *,
        params: Query,
    ) -> None: ...

    @overload
    def __init__(
        self,
        *,
        json: Body,
    ) -> None: ...

    def __init__(
        self,
        *,
        url: URL | NotGiven = NOT_GIVEN,
        json: Body | NotGiven = NOT_GIVEN,
        params: Query | NotGiven = NOT_GIVEN,
    ) -> None:
        self.url = url
        self.json = json
        self.params = params

    @override
    def __repr__(self) -> str:
        if self.url:
            return f"{self.__class__.__name__}(url={self.url})"
        if self.json:
            return f"{self.__class__.__name__}(json={self.json})"
        return f"{self.__class__.__name__}(params={self.params})"


class BasePage(GenericModel, Generic[_T]):
    """
    Defines the core interface for pagination.

    Type Args:
        ModelT: The pydantic model that represents an item in the response.

    Methods:
        has_next_page(): Check if there is another page available
        next_page_info(): Get the necessary information to make a request for the next page
    """

    _options: FinalRequestOptions = PrivateAttr()
    _model: Type[_T] = PrivateAttr()

    def has_next_page(self) -> bool:
        items = self._get_page_items()
        if not items:
            return False
        return self.next_page_info() is not None

    def next_page_info(self) -> Optional[PageInfo]: ...

    def _get_page_items(self) -> Iterable[_T]:  # type: ignore[empty-body]
        ...

    def _params_from_url(self, url: URL) -> httpx.QueryParams:
        # TODO: do we have to preprocess params here?
        return httpx.QueryParams(cast(Any, self._options.params)).merge(url.params)

    def _info_to_options(self, info: PageInfo) -> FinalRequestOptions:
        options = model_copy(self._options)
        options._strip_raw_response_header()

        if not isinstance(info.params, NotGiven):
            options.params = {**options.params, **info.params}
            return options

        if not isinstance(info.url, NotGiven):
            params = self._params_from_url(info.url)
            url = info.url.copy_with(params=params)
            options.params = dict(url.params)
            options.url = str(url)
            return options

        if not isinstance(info.json, NotGiven):
            if not is_mapping(info.json):
                raise TypeError("Pagination is only supported with mappings")

            if not options.json_data:
                options.json_data = {**info.json}
            else:
                if not is_mapping(options.json_data):
                    raise TypeError("Pagination is only supported with mappings")

                options.json_data = {**options.json_data, **info.json}
            return options

        raise ValueError("Unexpected PageInfo state")


class AsyncPaginator(Generic[_T, AsyncPageT]):
    def __init__(
        self,
        client: AsyncAPIClient,
        options: FinalRequestOptions,
        page_cls: Type[AsyncPageT],
        model: Type[_T],
    ) -> None:
        self._model = model
        self._client = client
        self._options = options
        self._page_cls = page_cls

    def __await__(self) -> Generator[Any, None, AsyncPageT]:
        return self._get_page().__await__()

    async def _get_page(self) -> AsyncPageT:
        def _parser(resp: AsyncPageT) -> AsyncPageT:
            resp._set_private_attributes(
                model=self._model,
                options=self._options,
                client=self._client,
            )
            return resp

        self._options.post_parser = _parser

        return await self._client.request(self._page_cls, self._options)

    async def __aiter__(self) -> AsyncIterator[_T]:
        # https://github.com/microsoft/pyright/issues/3464
        page = cast(
            AsyncPageT,
            await self,  # type: ignore
        )
        async for item in page:
            yield item


class BaseAsyncPage(BasePage[_T], Generic[_T]):
    _client: AsyncAPIClient = pydantic.PrivateAttr()

    def _set_private_attributes(
        self,
        model: Type[_T],
        client: AsyncAPIClient,
        options: FinalRequestOptions,
    ) -> None:
        if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None:
            self.__pydantic_private__ = {}

        self._model = model
        self._client = client
        self._options = options

    async def __aiter__(self) -> AsyncIterator[_T]:
        async for page in self.iter_pages():
            for item in page._get_page_items():
                yield item

    async def iter_pages(self: AsyncPageT) -> AsyncIterator[AsyncPageT]:
        page = self
        while True:
            yield page
            if page.has_next_page():
                page = await page.get_next_page()
            else:
                return

    async def get_next_page(self: AsyncPageT) -> AsyncPageT:
        info = self.next_page_info()
        if not info:
            raise RuntimeError(
                "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`."
            )

        options = self._info_to_options(info)
        return await self._client._request_api_list(
            self._model, page=self.__class__, options=options
        )


_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient])
_DefaultStreamT = TypeVar("_DefaultStreamT", bound=AsyncStream[Any])


class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
    _client: _HttpxClientT
    _version: str
    _base_url: URL
    max_retries: int
    timeout: Union[float, Timeout, None]
    _strict_response_validation: bool
    _idempotency_header: str | None
    _default_stream_cls: type[_DefaultStreamT] | None = None

    def __init__(
        self,
        *,
        version: str,
        base_url: str | URL,
        _strict_response_validation: bool,
        max_retries: int = DEFAULT_MAX_RETRIES,
        timeout: float | Timeout | None = DEFAULT_TIMEOUT,
        custom_headers: Mapping[str, str] | None = None,
        custom_query: Mapping[str, object] | None = None,
    ) -> None:
        self._version = version
        self._base_url = self._enforce_trailing_slash(URL(base_url))
        self.max_retries = max_retries
        self.timeout = timeout
        self._custom_headers = custom_headers or {}
        self._custom_query = custom_query or {}
        self._strict_response_validation = _strict_response_validation
        self._idempotency_header = None
        self._platform: Platform | None = None

        if max_retries is None:  # pyright: ignore[reportUnnecessaryComparison]
            raise TypeError(
                "max_retries cannot be None. If you want to disable retries, pass `0`; if you want unlimited retries, pass `math.inf` or a very high number; if you want the default behavior, pass `tinker.DEFAULT_MAX_RETRIES`"
            )

    def _enforce_trailing_slash(self, url: URL) -> URL:
        if url.raw_path.endswith(b"/"):
            return url
        return url.copy_with(raw_path=url.raw_path + b"/")

    def _make_status_error_from_response(
        self,
        response: httpx.Response,
    ) -> APIStatusError:
        if response.is_closed and not response.is_stream_consumed:
            # We can't read the response body as it has been closed
            # before it was read. This can happen if an event hook
            # raises a status error.
            body = None
            err_msg = f"Error code: {response.status_code}"
        else:
            err_text = response.text.strip()
            body = err_text

            try:
                body = json.loads(err_text)
                err_msg = f"Error code: {response.status_code} - {body}"
            except Exception:
                err_msg = err_text or f"Error code: {response.status_code}"

        return self._make_status_error(err_msg, body=body, response=response)

    def _make_status_error(
        self,
        err_msg: str,
        *,
        body: object,
        response: httpx.Response,
    ) -> _exceptions.APIStatusError:
        raise NotImplementedError()

    def _build_headers(
        self, options: FinalRequestOptions, *, retries_taken: int = 0
    ) -> httpx.Headers:
        custom_headers = options.headers or {}
        headers_dict = _merge_mappings(self.default_headers, custom_headers)
        self._validate_headers(headers_dict, custom_headers)

        # headers are case-insensitive while dictionaries are not.
        headers = httpx.Headers(headers_dict)

        idempotency_header = self._idempotency_header
        if idempotency_header and options.idempotency_key and idempotency_header not in headers:
            headers[idempotency_header] = options.idempotency_key

        # Don't set these headers if they were already set or removed by the caller. We check
        # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
        lower_custom_headers = [header.lower() for header in custom_headers]
        if "x-stainless-retry-count" not in lower_custom_headers:
            headers["x-stainless-retry-count"] = str(retries_taken)
        if "x-stainless-read-timeout" not in lower_custom_headers:
            timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout
            if isinstance(timeout, Timeout):
                timeout = timeout.read
            if timeout is not None:
                headers["x-stainless-read-timeout"] = str(timeout)

        return headers

    def _prepare_url(self, url: str) -> URL:
        """
        Merge a URL argument together with any 'base_url' on the client,
        to create the URL used for the outgoing request.
        """
        # Copied from httpx's `_merge_url` method.
        merge_url = URL(url)
        if merge_url.is_relative_url:
            merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/")
            return self.base_url.copy_with(raw_path=merge_raw_path)

        return merge_url

    def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder:
        return SSEDecoder()

    def _build_request(
        self,
        options: FinalRequestOptions,
        *,
        retries_taken: int = 0,
    ) -> httpx.Request:
        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                "Request options: %s", model_dump(options, exclude_unset=False, exclude_none=True)
            )

        kwargs: dict[str, Any] = {}

        json_data = options.json_data
        if options.extra_json is not None:
            if json_data is None:
                json_data = cast(Body, options.extra_json)
            elif is_mapping(json_data):
                json_data = _merge_mappings(json_data, options.extra_json)
            else:
                raise RuntimeError(
                    f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`"
                )

        headers = self._build_headers(options, retries_taken=retries_taken)
        params = _merge_mappings(self.default_query, options.params)
        content_type = headers.get("Content-Type")
        files = options.files

        # If the given Content-Type header is multipart/form-data then it
        # has to be removed so that httpx can generate the header with
        # additional information for us as it has to be in this form
        # for the server to be able to correctly parse the request:
        # multipart/form-data; boundary=---abc--
        if content_type is not None and content_type.startswith("multipart/form-data"):
            if "boundary" not in content_type:
                # only remove the header if the boundary hasn't been explicitly set
                # as the caller doesn't want httpx to come up with their own boundary
                headers.pop("Content-Type")

            # As we are now sending multipart/form-data instead of application/json
            # we need to tell httpx to use it, https://www.python-httpx.org/advanced/clients/#multipart-file-encoding
            if json_data:
                if not is_dict(json_data):
                    raise TypeError(
                        f"Expected query input to be a dictionary for multipart requests but got {type(json_data)} instead."
                    )
                kwargs["data"] = self._serialize_multipartform(json_data)

            # httpx determines whether or not to send a "multipart/form-data"
            # request based on the truthiness of the "files" argument.
            # This gets around that issue by generating a dict value that
            # evaluates to true.
            #
            # https://github.com/encode/httpx/discussions/2399#discussioncomment-3814186
            if not files:
                files = cast(HttpxRequestFiles, ForceMultipartDict())

        prepared_url = self._prepare_url(options.url)
        if "_" in prepared_url.host:
            # work around https://github.com/encode/httpx/discussions/2880
            kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}

        is_body_allowed = options.method.lower() != "get"

        if is_body_allowed:
            if isinstance(json_data, bytes):
                kwargs["content"] = json_data
            else:
                kwargs["json"] = json_data if is_given(json_data) else None
            kwargs["files"] = files
        else:
            headers.pop("Content-Type", None)
            kwargs.pop("data", None)

        # TODO: report this error to httpx
        return self._client.build_request(  # pyright: ignore[reportUnknownMemberType]
            headers=headers,
            timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout,
            method=options.method,
            url=prepared_url,
            # the `Query` type that we use is incompatible with qs'
            # `Params` type as it needs to be typed as `Mapping[str, object]`
            # so that passing a `TypedDict` doesn't cause an error.
            # https://github.com/microsoft/pyright/issues/3526#event-6715453066
            params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
            **kwargs,
        )

    def _serialize_multipartform(self, data: Mapping[object, object]) -> dict[str, object]:
        items = self.qs.stringify_items(
            # TODO: type ignore is required as stringify_items is well typed but we can't be
            # well typed without heavy validation.
            data,  # type: ignore
            array_format="brackets",
        )
        serialized: dict[str, object] = {}
        for key, value in items:
            existing = serialized.get(key)

            if not existing:
                serialized[key] = value
                continue

            # If a value has already been set for this key then that
            # means we're sending data like `array[]=[1, 2, 3]` and we
            # need to tell httpx that we want to send multiple values with
            # the same key which is done by using a list or a tuple.
            #
            # Note: 2d arrays should never result in the same key at both
            # levels so it's safe to assume that if the value is a list,
            # it was because we changed it to be a list.
            if is_list(existing):
                existing.append(value)
            else:
                serialized[key] = [existing, value]

        return serialized

    def _maybe_override_cast_to(
        self, cast_to: type[ResponseT], options: FinalRequestOptions
    ) -> type[ResponseT]:
        if not is_given(options.headers):
            return cast_to

        # make a copy of the headers so we don't mutate user-input
        headers = dict(options.headers)

        # we internally support defining a temporary header to override the
        # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response`
        # see _response.py for implementation details
        override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, NOT_GIVEN)
        if is_given(override_cast_to):
            options.headers = headers
            return cast(Type[ResponseT], override_cast_to)

        return cast_to

    def _should_stream_response_body(self, request: httpx.Request) -> bool:
        return request.headers.get(RAW_RESPONSE_HEADER) == "stream"  # type: ignore[no-any-return]

    def _process_response_data(
        self,
        *,
        data: object,
        cast_to: type[ResponseT],
        response: httpx.Response,
    ) -> ResponseT:
        if data is None:
            return cast(ResponseT, None)

        if cast_to is object:
            return cast(ResponseT, data)

        try:
            if inspect.isclass(cast_to) and issubclass(cast_to, ModelBuilderProtocol):
                return cast(ResponseT, cast_to.build(response=response, data=data))

            if self._strict_response_validation:
                return cast(ResponseT, validate_type(type_=cast_to, value=data))

            return cast(ResponseT, construct_type(type_=cast_to, value=data))
        except pydantic.ValidationError as err:
            raise APIResponseValidationError(response=response, body=data) from err

    @property
    def qs(self) -> Querystring:
        return Querystring()

    @property
    def custom_auth(self) -> httpx.Auth | None:
        return None

    @property
    def auth_headers(self) -> dict[str, str]:
        return {}

    @property
    def default_headers(self) -> dict[str, str | Omit]:
        return {
            "Accept": "application/json",
            "Content-Type": "application/json",
            "User-Agent": self.user_agent,
            **self.platform_headers(),
            **self.auth_headers,
            **self._custom_headers,
        }

    @property
    def default_query(self) -> dict[str, object]:
        return {
            **self._custom_query,
        }

    def _validate_headers(
        self,
        headers: Headers,  # noqa: ARG002
        custom_headers: Headers,  # noqa: ARG002
    ) -> None:
        """Validate the given default headers and custom headers.

        Does nothing by default.
        """
        return

    @property
    def user_agent(self) -> str:
        return f"{self.__class__.__name__}/Python {self._version}"

    @property
    def base_url(self) -> URL:
        return self._base_url

    @base_url.setter
    def base_url(self, url: URL | str) -> None:
        self._base_url = self._enforce_trailing_slash(url if isinstance(url, URL) else URL(url))

    def platform_headers(self) -> Dict[str, str]:
        # the actual implementation is in a separate `lru_cache` decorated
        # function because adding `lru_cache` to methods will leak memory
        # https://github.com/python/cpython/issues/88476
        return platform_headers(self._version, platform=self._platform)

    def _parse_retry_after_header(
        self, response_headers: Optional[httpx.Headers] = None
    ) -> float | None:
        """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified.

        About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
        See also  https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax
        """
        if response_headers is None:
            return None

        # First, try the non-standard `retry-after-ms` header for milliseconds,
        # which is more precise than integer-seconds `retry-after`
        try:
            retry_ms_header = response_headers.get("retry-after-ms", None)
            return float(retry_ms_header) / 1000
        except (TypeError, ValueError):
            pass

        # Next, try parsing `retry-after` header as seconds (allowing nonstandard floats).
        retry_header = response_headers.get("retry-after")
        try:
            # note: the spec indicates that this should only ever be an integer
            # but if someone sends a float there's no reason for us to not respect it
            return float(retry_header)
        except (TypeError, ValueError):
            pass

        # Last, try parsing `retry-after` as a date.
        retry_date_tuple = email.utils.parsedate_tz(retry_header)
        if retry_date_tuple is None:
            return None

        retry_date = email.utils.mktime_tz(retry_date_tuple)
        return float(retry_date - time.time())

    def _calculate_retry_timeout(
        self,
        remaining_retries: int,
        options: FinalRequestOptions,
        response_headers: Optional[httpx.Headers] = None,
    ) -> float:
        max_retries = options.get_max_retries(self.max_retries)

        # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says.
        retry_after = self._parse_retry_after_header(response_headers)
        if retry_after is not None and 0 < retry_after <= 60:
            return retry_after

        # Also cap retry count to 1000 to avoid any potential overflows with `pow`
        nb_retries = min(max_retries - remaining_retries, 1000)

        # Apply exponential backoff, but not more than the max.
        sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY)

        # Apply some jitter, plus-or-minus half a second.
        jitter = 1 - 0.25 * random()
        timeout = sleep_seconds * jitter
        return timeout if timeout >= 0 else 0

    def _should_retry(self, response: httpx.Response) -> bool:
        # Note: this is not a standard header
        should_retry_header = response.headers.get("x-should-retry")

        # If the server explicitly says whether or not to retry, obey.
        if should_retry_header == "true":
            log.debug("Retrying as header `x-should-retry` is set to `true`")
            return True
        if should_retry_header == "false":
            log.debug("Not retrying as header `x-should-retry` is set to `false`")
            return False

        # Retry on request timeouts.
        if response.status_code == 408:
            log.debug("Retrying due to status code %i", response.status_code)
            return True

        # Retry on lock timeouts.
        if response.status_code == 409:
            log.debug("Retrying due to status code %i", response.status_code)
            return True

        # Retry on rate limits.
        if response.status_code == 429:
            log.debug("Retrying due to status code %i", response.status_code)
            return True

        # Retry internal errors.
        if response.status_code >= 500:
            log.debug(
                "Retrying due to status code %i. text=%s", response.status_code, response.text
            )
            return True

        log.debug("Not retrying")
        return False

    def _idempotency_key(self) -> str:
        return f"stainless-python-retry-{uuid.uuid4()}"


def _default_pyqwest_transport() -> httpx.AsyncBaseTransport:
    # pyqwest is a reqwest/hyper-based HTTP backend exposed through an
    # httpx-compatible transport adapter. Gated by
    # ClientConfigResponse.use_pyqwest_transport so the server can flip every
    # client back to httpx's default transport without an SDK release.
    import pyqwest
    from pyqwest.httpx import AsyncPyqwestTransport

    return AsyncPyqwestTransport(transport=pyqwest.HTTPTransport())


class _DefaultAsyncHttpxClient(httpx.AsyncClient):
    def __init__(self, **kwargs: Any) -> None:
        kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
        kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
        kwargs.setdefault("follow_redirects", True)
        super().__init__(**kwargs)


try:
    import httpx_aiohttp
except ImportError:

    class _DefaultAioHttpClient(httpx.AsyncClient):
        def __init__(self, **_kwargs: Any) -> None:
            raise RuntimeError(
                "To use the aiohttp client you must have installed the package with the `aiohttp` extra"
            )
else:

    class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient):  # type: ignore
        def __init__(self, **kwargs: Any) -> None:
            kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
            kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
            kwargs.setdefault("follow_redirects", True)

            super().__init__(**kwargs)


if TYPE_CHECKING:
    DefaultAsyncHttpxClient = httpx.AsyncClient
    """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK
    uses internally.

    This is useful because overriding the `http_client` with your own instance of
    `httpx.AsyncClient` will result in httpx's defaults being used, not ours.
    """

    DefaultAioHttpClient = httpx.AsyncClient
    """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`."""
else:
    DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient
    DefaultAioHttpClient = _DefaultAioHttpClient


class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient):
    def __del__(self) -> None:
        if self.is_closed:
            return

        try:
            # TODO(someday): support non asyncio runtimes here
            asyncio.get_running_loop().create_task(self.aclose())
        except Exception:
            pass


class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
    _client: httpx.AsyncClient
    _default_stream_cls: type[AsyncStream[Any]] | None = None

    def __init__(
        self,
        *,
        version: str,
        base_url: str | URL,
        _strict_response_validation: bool,
        max_retries: int = DEFAULT_MAX_RETRIES,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.AsyncClient | None = None,
        custom_headers: Mapping[str, str] | None = None,
        custom_query: Mapping[str, object] | None = None,
        use_pyqwest: bool = True,
    ) -> None:
        if not is_given(timeout):
            # if the user passed in a custom http client with a non-default
            # timeout set then we use that timeout.
            #
            # note: there is an edge case here where the user passes in a client
            # where they've explicitly set the timeout to match the default timeout
            # as this check is structural, meaning that we'll think they didn't
            # pass in a timeout and will ignore it
            if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT:
                timeout = http_client.timeout
            else:
                timeout = DEFAULT_TIMEOUT

        if http_client is not None and not isinstance(http_client, httpx.AsyncClient):  # pyright: ignore[reportUnnecessaryIsInstance]
            raise TypeError(
                f"Invalid `http_client` argument; Expected an instance of `httpx.AsyncClient` but got {type(http_client)}"
            )

        super().__init__(
            version=version,
            base_url=base_url,
            # cast to a valid type because mypy doesn't understand our type narrowing
            timeout=cast(Timeout, timeout),
            max_retries=max_retries,
            custom_query=custom_query,
            custom_headers=custom_headers,
            _strict_response_validation=_strict_response_validation,
        )
        if http_client is not None:
            self._client = http_client
        else:
            default_kwargs: dict[str, Any] = {
                "base_url": base_url,
                # cast to a valid type because mypy doesn't understand our type narrowing
                "timeout": cast(Timeout, timeout),
                "http2": True,
            }
            if use_pyqwest:
                default_kwargs["transport"] = _default_pyqwest_transport()
            self._client = AsyncHttpxClientWrapper(**default_kwargs)

    def is_closed(self) -> bool:
        return self._client.is_closed

    async def close(self) -> None:
        """Close the underlying HTTPX client.

        The client will *not* be usable after this.
        """
        await self._client.aclose()

    async def __aenter__(self: _T) -> _T:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        await self.close()

    async def _prepare_options(
        self,
        options: FinalRequestOptions,  # noqa: ARG002
    ) -> FinalRequestOptions:
        """Hook for mutating the given options"""
        return options

    async def _prepare_request(
        self,
        request: httpx.Request,  # noqa: ARG002
    ) -> None:
        """This method is used as a callback for mutating the `Request` object
        after it has been constructed.
        This is useful for cases where you want to add certain headers based off of
        the request properties, e.g. `url`, `method` etc.
        """
        return None

    @overload
    async def request(
        self,
        cast_to: Type[ResponseT],
        options: FinalRequestOptions,
        *,
        stream: Literal[False] = False,
    ) -> ResponseT: ...

    @overload
    async def request(
        self,
        cast_to: Type[ResponseT],
        options: FinalRequestOptions,
        *,
        stream: Literal[True],
        stream_cls: type[_AsyncStreamT],
    ) -> _AsyncStreamT: ...

    @overload
    async def request(
        self,
        cast_to: Type[ResponseT],
        options: FinalRequestOptions,
        *,
        stream: bool,
        stream_cls: type[_AsyncStreamT] | None = None,
    ) -> ResponseT | _AsyncStreamT: ...

    async def request(
        self,
        cast_to: Type[ResponseT],
        options: FinalRequestOptions,
        *,
        stream: bool = False,
        stream_cls: type[_AsyncStreamT] | None = None,
    ) -> ResponseT | _AsyncStreamT:
        if self._platform is None:
            # `get_platform` can make blocking IO calls so we
            # execute it earlier while we are in an async context
            self._platform = await asyncify(get_platform)()

        cast_to = self._maybe_override_cast_to(cast_to, options)

        # create a copy of the options we were given so that if the
        # options are mutated later & we then retry, the retries are
        # given the original options
        input_options = model_copy(options)
        if input_options.idempotency_key is None and input_options.method.lower() != "get":
            # ensure the idempotency key is reused between requests
            input_options.idempotency_key = self._idempotency_key()

        response: httpx.Response | None = None
        max_retries = input_options.get_max_retries(self.max_retries)

        retries_taken = 0
        for retries_taken in range(max_retries + 1):
            options = model_copy(input_options)
            options = await self._prepare_options(options)

            remaining_retries = max_retries - retries_taken
            request = self._build_request(options, retries_taken=retries_taken)
            await self._prepare_request(request)

            kwargs: HttpxSendArgs = {}
            if self.custom_auth is not None:
                kwargs["auth"] = self.custom_auth

            if options.follow_redirects is not None:
                kwargs["follow_redirects"] = options.follow_redirects

            log.debug("Sending HTTP Request: %s %s", request.method, request.url)

            response = None
            try:
                response = await self._client.send(
                    request,
                    stream=stream or self._should_stream_response_body(request=request),
                    **kwargs,
                )
            except httpx.TimeoutException as err:
                log.debug("Encountered httpx.TimeoutException", exc_info=True)

                if remaining_retries > 0:
                    await self._sleep_for_retry(
                        retries_taken=retries_taken,
                        max_retries=max_retries,
                        options=input_options,
                        response=None,
                    )
                    continue

                log.debug("Raising timeout error")
                raise APITimeoutError(request=request) from err
            except Exception as err:
                log.debug("Encountered Exception", exc_info=True)

                if remaining_retries > 0:
                    await self._sleep_for_retry(
                        retries_taken=retries_taken,
                        max_retries=max_retries,
                        options=input_options,
                        response=None,
                    )
                    continue

                log.debug("Raising connection error")
                raise APIConnectionError(request=request) from err

            log.debug(
                'HTTP Response: %s %s "%i %s" %s',
                request.method,
                request.url,
                response.status_code,
                response.reason_phrase,
                response.headers,
            )

            try:
                response.raise_for_status()
            except httpx.HTTPStatusError as err:  # thrown on 4xx and 5xx status code
                log.debug("Encountered httpx.HTTPStatusError. Response_type=%s", cast_to)

                if remaining_retries > 0 and self._should_retry(err.response):
                    await err.response.aclose()
                    await self._sleep_for_retry(
                        retries_taken=retries_taken,
                        max_retries=max_retries,
                        options=input_options,
                        response=response,
                    )
                    continue

                # If the response is streamed then we need to explicitly read the response
                # to completion before attempting to access the response text.
                if not err.response.is_closed:
                    await err.response.aread()

                log.debug("Re-raising status error")
                raise self._make_status_error_from_response(err.response) from None

            break

        assert response is not None, "could not resolve response (should never happen)"
        return await self._process_response(
            cast_to=cast_to,
            options=options,
            response=response,
            stream=stream,
            stream_cls=stream_cls,
            retries_taken=retries_taken,
        )

    async def _sleep_for_retry(
        self,
        *,
        retries_taken: int,
        max_retries: int,
        options: FinalRequestOptions,
        response: httpx.Response | None,
    ) -> None:
        remaining_retries = max_retries - retries_taken
        if remaining_retries == 1:
            log.debug("1 retry left")
        else:
            log.debug("%i retries left", remaining_retries)

        timeout = self._calculate_retry_timeout(
            remaining_retries, options, response.headers if response else None
        )
        log.debug("Retrying request to %s in %f seconds", options.url, timeout)

        await anyio.sleep(timeout)

    async def _process_response(
        self,
        *,
        cast_to: Type[ResponseT],
        options: FinalRequestOptions,
        response: httpx.Response,
        stream: bool,
        stream_cls: type[AsyncStream[Any]] | None,
        retries_taken: int = 0,
    ) -> ResponseT:
        origin = get_origin(cast_to) or cast_to

        if (
            inspect.isclass(origin)
            and issubclass(origin, BaseAPIResponse)
            # we only want to actually return the custom BaseAPIResponse class if we're
            # returning the raw response, or if we're not streaming SSE, as if we're streaming
            # SSE then `cast_to` doesn't actively reflect the type we need to parse into
            and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
        ):
            if not issubclass(origin, AsyncAPIResponse):
                raise TypeError(
                    f"API Response types must subclass {AsyncAPIResponse}; Received {origin}"
                )

            response_cls = cast("type[BaseAPIResponse[Any]]", cast_to)
            return cast(
                "ResponseT",
                response_cls(
                    raw=response,
                    client=self,
                    cast_to=extract_response_type(response_cls),
                    stream=stream,
                    stream_cls=stream_cls,
                    options=options,
                    retries_taken=retries_taken,
                ),
            )

        if cast_to == httpx.Response:
            return cast(ResponseT, response)

        api_response = AsyncAPIResponse(
            raw=response,
            client=self,
            cast_to=cast("type[ResponseT]", cast_to),  # pyright: ignore[reportUnnecessaryCast]
            stream=stream,
            stream_cls=stream_cls,
            options=options,
            retries_taken=retries_taken,
        )
        if bool(response.request.headers.get(RAW_RESPONSE_HEADER)):
            return cast(ResponseT, api_response)

        return await api_response.parse()

    def _request_api_list(
        self,
        model: Type[_T],
        page: Type[AsyncPageT],
        options: FinalRequestOptions,
    ) -> AsyncPaginator[_T, AsyncPageT]:
        return AsyncPaginator(client=self, options=options, page_cls=page, model=model)

    @overload
    async def get(
        self,
        path: str,
        *,
        cast_to: Type[ResponseT],
        options: RequestOptions = {},
        stream: Literal[False] = False,
    ) -> ResponseT: ...

    @overload
    async def get(
        self,
        path: str,
        *,
        cast_to: Type[ResponseT],
        options: RequestOptions = {},
        stream: Literal[True],
        stream_cls: type[_AsyncStreamT],
    ) -> _AsyncStreamT: ...

    @overload
    async def get(
        self,
        path: str,
        *,
        cast_to: Type[ResponseT],
        options: RequestOptions = {},
        stream: bool,
        stream_cls: type[_AsyncStreamT] | None = None,
    ) -> ResponseT | _AsyncStreamT: ...

    async def get(
        self,
        path: str,
        *,
        cast_to: Type[ResponseT],
        options: RequestOptions = {},
        stream: bool = False,
        stream_cls: type[_AsyncStreamT] | None = None,
    ) -> ResponseT | _AsyncStreamT:
        opts = FinalRequestOptions.construct(method="get", url=path, **options)
        return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)

    @overload
    async def post(
        self,
        path: str,
        *,
        cast_to: Type[ResponseT],
        body: Body | None = None,
        files: RequestFiles | None = None,
        options: RequestOptions = {},
        stream: Literal[False] = False,
    ) -> ResponseT: ...

    @overload
    async def post(
        self,
        path: str,
        *,
        cast_to: Type[ResponseT],
        body: Body | None = None,
        files: RequestFiles | None = None,
        options: RequestOptions = {},
        stream: Literal[True],
        stream_cls: type[_AsyncStreamT],
    ) -> _AsyncStreamT: ...

    @overload
    async def post(
        self,
        path: str,
        *,
        cast_to: Type[ResponseT],
        body: Body | None = None,
        files: RequestFiles | None = None,
        options: RequestOptions = {},
        stream: bool,
        stream_cls: type[_AsyncStreamT] | None = None,
    ) -> ResponseT | _AsyncStreamT: ...

    async def post(
        self,
        path: str,
        *,
        cast_to: Type[ResponseT],
        body: Body | None = None,
        files: RequestFiles | None = None,
        options: RequestOptions = {},
        stream: bool = False,
        stream_cls: type[_AsyncStreamT] | None = None,
    ) -> ResponseT | _AsyncStreamT:
        opts = FinalRequestOptions.construct(
            method="post",
            url=path,
            json_data=body,
            files=await async_to_httpx_files(files),
            **options,
        )
        return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)

    async def patch(
        self,
        path: str,
        *,
        cast_to: Type[ResponseT],
        body: Body | None = None,
        options: RequestOptions = {},
    ) -> ResponseT:
        opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options)
        return await self.request(cast_to, opts)

    async def put(
        self,
        path: str,
        *,
        cast_to: Type[ResponseT],
        body: Body | None = None,
        files: RequestFiles | None = None,
        options: RequestOptions = {},
    ) -> ResponseT:
        opts = FinalRequestOptions.construct(
            method="put",
            url=path,
            json_data=body,
            files=await async_to_httpx_files(files),
            **options,
        )
        return await self.request(cast_to, opts)

    async def delete(
        self,
        path: str,
        *,
        cast_to: Type[ResponseT],
        body: Body | None = None,
        options: RequestOptions = {},
    ) -> ResponseT:
        opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
        return await self.request(cast_to, opts)

    def get_api_list(
        self,
        path: str,
        *,
        model: Type[_T],
        page: Type[AsyncPageT],
        body: Body | None = None,
        options: RequestOptions = {},
        method: str = "get",
    ) -> AsyncPaginator[_T, AsyncPageT]:
        opts = FinalRequestOptions.construct(method=method, url=path, json_data=body, **options)
        return self._request_api_list(model, page, opts)


def make_request_options(
    *,
    query: Query | None = None,
    extra_headers: Headers | None = None,
    extra_query: Query | None = None,
    extra_body: Body | None = None,
    idempotency_key: str | None = None,
    timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
    post_parser: PostParser | NotGiven = NOT_GIVEN,
) -> RequestOptions:
    """Create a dict of type RequestOptions without keys of NotGiven values."""
    options: RequestOptions = {}
    if extra_headers is not None:
        options["headers"] = extra_headers

    if extra_body is not None:
        options["extra_json"] = cast(AnyMapping, extra_body)

    if query is not None:
        options["params"] = query

    if extra_query is not None:
        options["params"] = {**options.get("params", {}), **extra_query}

    if not isinstance(timeout, NotGiven):
        options["timeout"] = timeout

    if idempotency_key is not None:
        options["idempotency_key"] = idempotency_key

    if is_given(post_parser):
        # internal
        options["post_parser"] = post_parser  # type: ignore

    return options


class ForceMultipartDict(Dict[str, None]):
    def __bool__(self) -> bool:
        return True


class OtherPlatform:
    def __init__(self, name: str) -> None:
        self.name = name

    @override
    def __str__(self) -> str:
        return f"Other:{self.name}"


Platform = Union[
    OtherPlatform,
    Literal[
        "MacOS",
        "Linux",
        "Windows",
        "FreeBSD",
        "OpenBSD",
        "iOS",
        "Android",
        "Unknown",
    ],
]


def get_platform() -> Platform:
    try:
        system = platform.system().lower()
        platform_name = platform.platform().lower()
    except Exception:
        return "Unknown"

    if "iphone" in platform_name or "ipad" in platform_name:
        # Tested using Python3IDE on an iPhone 11 and Pythonista on an iPad 7
        # system is Darwin and platform_name is a string like:
        # - Darwin-21.6.0-iPhone12,1-64bit
        # - Darwin-21.6.0-iPad7,11-64bit
        return "iOS"

    if system == "darwin":
        return "MacOS"

    if system == "windows":
        return "Windows"

    if "android" in platform_name:
        # Tested using Pydroid 3
        # system is Linux and platform_name is a string like 'Linux-5.10.81-android12-9-00001-geba40aecb3b7-ab8534902-aarch64-with-libc'
        return "Android"

    if system == "linux":
        # https://distro.readthedocs.io/en/latest/#distro.id
        distro_id = distro.id()
        if distro_id == "freebsd":
            return "FreeBSD"

        if distro_id == "openbsd":
            return "OpenBSD"

        return "Linux"

    if platform_name:
        return OtherPlatform(platform_name)

    return "Unknown"


@lru_cache(maxsize=None)
def platform_headers(version: str, *, platform: Platform | None) -> Dict[str, str]:
    return {
        "X-Stainless-Package-Version": version,
        "X-Stainless-OS": str(platform or get_platform()),
        "X-Stainless-Arch": str(get_architecture()),
        "X-Stainless-Runtime": get_python_runtime(),
        "X-Stainless-Runtime-Version": get_python_version(),
    }


class OtherArch:
    def __init__(self, name: str) -> None:
        self.name = name

    @override
    def __str__(self) -> str:
        return f"other:{self.name}"


Arch = Union[OtherArch, Literal["x32", "x64", "arm", "arm64", "unknown"]]


def get_python_runtime() -> str:
    try:
        return platform.python_implementation()
    except Exception:
        return "unknown"


def get_python_version() -> str:
    try:
        return platform.python_version()
    except Exception:
        return "unknown"


def get_architecture() -> Arch:
    try:
        machine = platform.machine().lower()
    except Exception:
        return "unknown"

    if machine in ("arm64", "aarch64"):
        return "arm64"

    # TODO: untested
    if machine == "arm":
        return "arm"

    if machine == "x86_64":
        return "x64"

    # TODO: untested
    if sys.maxsize <= 2**32:
        return "x32"

    if machine:
        return OtherArch(machine)

    return "unknown"


def _merge_mappings(
    obj1: Mapping[_T_co, Union[_T, Omit]],
    obj2: Mapping[_T_co, Union[_T, Omit]],
) -> Dict[_T_co, _T]:
    """Merge two mappings of the same type, removing any values that are instances of `Omit`.

    In cases with duplicate keys the second mapping takes precedence.
    """
    merged = {**obj1, **obj2}
    return {key: value for key, value in merged.items() if not isinstance(value, Omit)}

```

## /src/tinker/_client.py

```py path="/src/tinker/_client.py" 
from __future__ import annotations

import os
from typing import TYPE_CHECKING, Any, Mapping, Union

import httpx
from typing_extensions import Self, override

from . import _exceptions
from ._base_client import (
    DEFAULT_MAX_RETRIES,
    AsyncAPIClient,
)
from ._compat import cached_property
from ._exceptions import APIStatusError
from ._qs import Querystring
from ._streaming import AsyncStream as AsyncStream
from ._types import (
    NOT_GIVEN,
    NotGiven,
    Omit,
    ProxiesTypes,
    RequestOptions,
    Timeout,
    Transport,
)
from ._utils import get_async_library, is_given
from ._version import __version__
from .lib._auth_token_provider import ApiKeyAuthProvider, AuthTokenProvider
from .types.client_config_response import ClientConfigResponse

if TYPE_CHECKING:
    from .resources import futures, telemetry
    from .resources.futures import AsyncFuturesResource
    from .resources.models import AsyncModelsResource
    from .resources.sampling import AsyncSamplingResource
    from .resources.service import AsyncServiceResource
    from .resources.telemetry import AsyncTelemetryResource
    from .resources.training import AsyncTrainingResource
    from .resources.weights import AsyncWeightsResource

__all__ = [
    "Timeout",
    "Transport",
    "ProxiesTypes",
    "RequestOptions",
    "AsyncTinker",
]


class AsyncTinker(AsyncAPIClient):
    def __init__(
        self,
        *,
        api_key: str | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        # Configure a custom httpx client.
        # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
        # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details.
        http_client: httpx.AsyncClient | None = None,
        # Enable or disable schema validation for data returned by the API.
        # When enabled an error APIResponseValidationError is raised
        # if the API responds with invalid data for the expected schema.
        #
        # This parameter may be removed or changed in the future.
        # If you rely on this feature, please open a GitHub issue
        # outlining your use-case to help us decide if it should be
        # part of our public interface in the future.
        _strict_response_validation: bool = False,
        _auth: AuthTokenProvider | None = None,
        # Internal: server-resolved feature flags. Set by InternalClientHolder
        # after the one-off /client/config fetch.
        _client_config: ClientConfigResponse = ClientConfigResponse(),
    ) -> None:
        """Construct a new async AsyncTinker client instance.

        This automatically infers the `api_key` argument from the `TINKER_API_KEY` environment variable if it is not provided.
        """
        if _auth is not None:
            self._auth = _auth
        else:
            self._auth = ApiKeyAuthProvider(api_key=api_key)

        if base_url is None:
            base_url = os.environ.get("TINKER_BASE_URL")
        if base_url is None or base_url == "":
            base_url = "https://tinker.thinkingmachines.dev/services/tinker-prod"

        super().__init__(
            version=__version__,
            base_url=base_url,
            max_retries=max_retries,
            timeout=timeout,
            http_client=http_client,
            custom_headers=default_headers,
            custom_query=default_query,
            use_pyqwest=_client_config.use_pyqwest_transport,
            _strict_response_validation=_strict_response_validation,
        )

        self._client_config = _client_config

        self._idempotency_header = "X-Idempotency-Key"

    @cached_property
    def service(self) -> AsyncServiceResource:
        from .resources.service import AsyncServiceResource

        return AsyncServiceResource(self)

    @cached_property
    def training(self) -> AsyncTrainingResource:
        from .resources.training import AsyncTrainingResource

        return AsyncTrainingResource(self)

    @cached_property
    def models(self) -> AsyncModelsResource:
        from .resources.models import AsyncModelsResource

        return AsyncModelsResource(self)

    @cached_property
    def weights(self) -> AsyncWeightsResource:
        from .resources.weights import AsyncWeightsResource

        return AsyncWeightsResource(self)

    @cached_property
    def sampling(self) -> AsyncSamplingResource:
        from .resources.sampling import AsyncSamplingResource

        return AsyncSamplingResource(self)

    @cached_property
    def futures(self) -> AsyncFuturesResource:
        from .resources.futures import AsyncFuturesResource

        return AsyncFuturesResource(self)

    @cached_property
    def telemetry(self) -> AsyncTelemetryResource:
        from .resources.telemetry import AsyncTelemetryResource

        return AsyncTelemetryResource(self)

    @cached_property
    def with_raw_response(self) -> AsyncTinkerWithRawResponse:
        return AsyncTinkerWithRawResponse(self)

    @property
    @override
    def qs(self) -> Querystring:
        return Querystring(array_format="comma")

    @property
    @override
    def custom_auth(self) -> AuthTokenProvider:
        return self._auth

    @property
    @override
    def default_headers(self) -> dict[str, str | Omit]:
        return {
            **super().default_headers,
            "X-Stainless-Async": f"async:{get_async_library()}",
            **self._custom_headers,
        }

    def copy(
        self,
        *,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
        http_client: httpx.AsyncClient | None = None,
        max_retries: int | NotGiven = NOT_GIVEN,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError(
                "The `default_headers` and `set_default_headers` arguments are mutually exclusive"
            )

        if default_query is not None and set_default_query is not None:
            raise ValueError(
                "The `default_query` and `set_default_query` arguments are mutually exclusive"
            )

        headers = self._custom_headers
        if default_headers is not None:
            headers = {**headers, **default_headers}
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        http_client = http_client or self._client
        return self.__class__(
            _auth=self._auth,
            base_url=base_url or self.base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            **_extra_kwargs,
        )

    # Alias for `copy` for nicer inline usage, e.g.
    # client.with_options(timeout=10).foo.create(...)
    with_options = copy

    @override
    def _make_status_error(
        self,
        err_msg: str,
        *,
        body: object,
        response: httpx.Response,
    ) -> APIStatusError:
        if response.status_code == 400:
            return _exceptions.BadRequestError(err_msg, response=response, body=body)

        if response.status_code == 401:
            return _exceptions.AuthenticationError(err_msg, response=response, body=body)

        if response.status_code == 403:
            return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)

        if response.status_code == 404:
            return _exceptions.NotFoundError(err_msg, response=response, body=body)

        if response.status_code == 409:
            return _exceptions.ConflictError(err_msg, response=response, body=body)

        if response.status_code == 422:
            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)

        if response.status_code == 429:
            return _exceptions.RateLimitError(err_msg, response=response, body=body)

        if response.status_code >= 500:
            return _exceptions.InternalServerError(err_msg, response=response, body=body)
        return APIStatusError(err_msg, response=response, body=body)


class AsyncTinkerWithRawResponse:
    _client: AsyncTinker

    def __init__(self, client: AsyncTinker) -> None:
        self._client = client

    @cached_property
    def futures(self) -> futures.AsyncFuturesResourceWithRawResponse:
        from .resources.futures import AsyncFuturesResourceWithRawResponse

        return AsyncFuturesResourceWithRawResponse(self._client.futures)

    @cached_property
    def telemetry(self) -> telemetry.AsyncTelemetryResourceWithRawResponse:
        from .resources.telemetry import AsyncTelemetryResourceWithRawResponse

        return AsyncTelemetryResourceWithRawResponse(self._client.telemetry)

```

## /src/tinker/_compat.py

```py path="/src/tinker/_compat.py" 
from __future__ import annotations

from datetime import date, datetime
from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar, Union, cast, overload

import pydantic
from pydantic.fields import FieldInfo
from typing_extensions import Literal, Self

from ._types import IncEx, StrBytesIntFloat

_T = TypeVar("_T")
_ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel)

# --------------- Pydantic v2 compatibility ---------------

# Pyright incorrectly reports some of our functions as overriding a method when they don't
# pyright: reportIncompatibleMethodOverride=false

PYDANTIC_V2 = pydantic.VERSION.startswith("2.")

# v1 re-exports
if TYPE_CHECKING:

    def parse_date(value: date | StrBytesIntFloat) -> date:  # noqa: ARG001
        ...

    def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:  # noqa: ARG001
        ...

    def get_args(t: type[Any]) -> tuple[Any, ...]:  # noqa: ARG001
        ...

    def is_union(tp: type[Any] | None) -> bool:  # noqa: ARG001
        ...

    def get_origin(t: type[Any]) -> type[Any] | None:  # noqa: ARG001
        ...

    def is_literal_type(type_: type[Any]) -> bool:  # noqa: ARG001
        ...

    def is_typeddict(type_: type[Any]) -> bool:  # noqa: ARG001
        ...

else:
    if PYDANTIC_V2:
        from pydantic.v1.datetime_parse import parse_date as parse_date
        from pydantic.v1.datetime_parse import parse_datetime as parse_datetime
        from pydantic.v1.typing import get_args as get_args
        from pydantic.v1.typing import get_origin as get_origin
        from pydantic.v1.typing import is_literal_type as is_literal_type
        from pydantic.v1.typing import is_typeddict as is_typeddict
        from pydantic.v1.typing import is_union as is_union
    else:
        from pydantic.datetime_parse import parse_date as parse_date
        from pydantic.datetime_parse import parse_datetime as parse_datetime
        from pydantic.typing import get_args as get_args
        from pydantic.typing import get_origin as get_origin
        from pydantic.typing import is_literal_type as is_literal_type
        from pydantic.typing import is_typeddict as is_typeddict
        from pydantic.typing import is_union as is_union


# refactored config
if TYPE_CHECKING:
    from pydantic import ConfigDict as ConfigDict
else:
    if PYDANTIC_V2:
        from pydantic import ConfigDict
    else:
        # TODO: provide an error message here?
        ConfigDict = None


# renamed methods / properties
def parse_obj(model: type[_ModelT], value: object) -> _ModelT:
    if PYDANTIC_V2:
        return model.model_validate(value)
    else:
        return cast(_ModelT, model.parse_obj(value))  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]


def field_is_required(field: FieldInfo) -> bool:
    if PYDANTIC_V2:
        return field.is_required()
    return field.required  # type: ignore


def field_get_default(field: FieldInfo) -> Any:
    value = field.get_default()
    if PYDANTIC_V2:
        from pydantic_core import PydanticUndefined

        if value == PydanticUndefined:
            return None
        return value
    return value


def field_outer_type(field: FieldInfo) -> Any:
    if PYDANTIC_V2:
        return field.annotation
    return field.outer_type_  # type: ignore


def get_model_config(model: type[pydantic.BaseModel]) -> Any:
    if PYDANTIC_V2:
        return model.model_config
    return model.__config__  # type: ignore


def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]:
    if PYDANTIC_V2:
        return model.model_fields
    return model.__fields__  # type: ignore


def model_copy(
    model: _ModelT, *, deep: bool = False, update: dict[str, Any] | None = None
) -> _ModelT:
    if PYDANTIC_V2:
        return model.model_copy(deep=deep, update=update)  # type: ignore
    return model.copy(deep=deep, update=update)  # type: ignore


def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str:
    if PYDANTIC_V2:
        return model.model_dump_json(indent=indent)
    return model.json(indent=indent)  # type: ignore


def model_dump(
    model: pydantic.BaseModel,
    *,
    exclude: IncEx | None = None,
    exclude_unset: bool = False,
    exclude_defaults: bool = False,
    exclude_none: bool = False,
    warnings: bool = True,
    mode: Literal["json", "python"] = "python",
) -> dict[str, Any]:
    if PYDANTIC_V2 or hasattr(model, "model_dump"):
        return model.model_dump(
            mode=mode,
            exclude=exclude,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            # warnings are not supported in Pydantic v1
            warnings=warnings if PYDANTIC_V2 else True,
        )
    return cast(
        "dict[str, Any]",
        model.dict(  # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
            exclude=exclude,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
        ),
    )


def model_parse(model: type[_ModelT], data: Any) -> _ModelT:
    if PYDANTIC_V2:
        return model.model_validate(data)
    return model.parse_obj(data)  # pyright: ignore[reportDeprecated]


# generic models
if TYPE_CHECKING:

    class GenericModel(pydantic.BaseModel): ...

else:
    if PYDANTIC_V2:
        # there no longer needs to be a distinction in v2 but
        # we still have to create our own subclass to avoid
        # inconsistent MRO ordering errors
        class GenericModel(pydantic.BaseModel): ...

    else:
        import pydantic.generics

        class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ...


# cached properties
if TYPE_CHECKING:
    cached_property = property

    # we define a separate type (copied from typeshed)
    # that represents that `cached_property` is `set`able
    # at runtime, which differs from `@property`.
    #
    # this is a separate type as editors likely special case
    # `@property` and we don't want to cause issues just to have
    # more helpful internal types.

    class typed_cached_property(Generic[_T]):
        func: Callable[[Any], _T]
        attrname: str | None

        def __init__(self, func: Callable[[Any], _T]) -> None: ...

        @overload
        def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ...

        @overload
        def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ...

        def __get__(self, instance: object, owner: type[Any] | None = None) -> _T | Self:
            raise NotImplementedError()

        def __set_name__(self, owner: type[Any], name: str) -> None: ...

        # __set__ is not defined at runtime, but @cached_property is designed to be settable
        def __set__(self, instance: object, value: _T) -> None: ...
else:
    from functools import cached_property as cached_property

    typed_cached_property = cached_property

```

## /src/tinker/_constants.py

```py path="/src/tinker/_constants.py" 
import httpx

RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response"
OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to"

# default timeout is 1 minute
DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0)
DEFAULT_MAX_RETRIES = 10
DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=1000, max_keepalive_connections=20)

INITIAL_RETRY_DELAY = 0.5
MAX_RETRY_DELAY = 10.0

```

## /src/tinker/_exceptions.py

```py path="/src/tinker/_exceptions.py" 
from __future__ import annotations

from typing import TYPE_CHECKING

import httpx

__all__ = [
    "BadRequestError",
    "AuthenticationError",
    "PermissionDeniedError",
    "NotFoundError",
    "ConflictError",
    "UnprocessableEntityError",
    "RateLimitError",
    "InternalServerError",
    "RequestFailedError",
    "SidecarError",
    "SidecarStartupError",
    "SidecarDiedError",
    "SidecarIPCError",
]

if TYPE_CHECKING:
    from tinker.types import RequestErrorCategory


class TinkerError(Exception):
    """Base exception for all Tinker-related errors."""

    pass


class APIError(TinkerError):
    """Base class for all API-related errors."""

    message: str
    request: httpx.Request

    body: object | None
    """The API response body.

    If the API responded with a valid JSON structure then this property will be the
    decoded result.

    If it isn't a valid JSON structure then this will be the raw response.

    If there was no response associated with this error then it will be `None`.
    """

    def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None:  # noqa: ARG002
        super().__init__(message)
        self.request = request
        self.message = message
        self.body = body


class APIResponseValidationError(APIError):
    """Raised when API response doesn't match expected schema."""

    response: httpx.Response
    status_code: int

    def __init__(
        self, response: httpx.Response, body: object | None, message: str | None = None
    ) -> None:
        super().__init__(
            message or "Data returned by API invalid for expected schema.",
            response.request,
            body=body,
        )
        self.response = response
        self.status_code = response.status_code

    def __reduce__(self):
        # Return a tuple of (callable, args) to recreate the exception
        return (
            self.__class__,
            (self.response, self.body, self.message),  # positional args
            None,
        )


class APIStatusError(APIError):
    """Raised when an API response has a status code of 4xx or 5xx."""

    response: httpx.Response
    status_code: int

    def __init__(self, message: str, response: httpx.Response, body: object | None) -> None:
        super().__init__(message, response.request, body=body)
        self.response = response
        self.status_code = response.status_code

    def __reduce__(self):
        # Return a tuple of (callable, args) to recreate the exception
        return (
            self.__class__,
            (self.message, self.response, self.body),  # positional args
            None,
        )


class APIConnectionError(APIError):
    """Raised when a connection error occurs while making an API request."""

    def __init__(self, request: httpx.Request, message: str = "Connection error.") -> None:
        super().__init__(message, request, body=None)

    def __reduce__(self):
        # Return a tuple of (callable, args) to recreate the exception
        return (
            self.__class__,
            (self.request, self.message),  # positional args
            None,
        )


class APITimeoutError(APIConnectionError):
    """Raised when an API request times out."""

    def __init__(self, request: httpx.Request) -> None:
        super().__init__(request=request, message="Request timed out.")


class BadRequestError(APIStatusError):
    """HTTP 400: The request was invalid or malformed."""

    status_code: int = 400


class AuthenticationError(APIStatusError):
    """HTTP 401: Authentication credentials are missing or invalid."""

    status_code: int = 401


class PermissionDeniedError(APIStatusError):
    """HTTP 403: Insufficient permissions to access the resource."""

    status_code: int = 403


class NotFoundError(APIStatusError):
    """HTTP 404: The requested resource was not found."""

    status_code: int = 404


class ConflictError(APIStatusError):
    """HTTP 409: The request conflicts with the current state of the resource."""

    status_code: int = 409


class UnprocessableEntityError(APIStatusError):
    """HTTP 422: The request was well-formed but contains semantic errors."""

    status_code: int = 422


class RateLimitError(APIStatusError):
    """HTTP 429: Too many requests, rate limit exceeded."""

    status_code: int = 429


class InternalServerError(APIStatusError):
    """HTTP 500+: An error occurred on the server."""

    pass


class SidecarError(TinkerError):
    """Base exception for subprocess sidecar errors."""


class SidecarStartupError(SidecarError):
    """Raised when the sidecar subprocess fails to start or times out."""


class SidecarDiedError(SidecarError):
    """Raised when the sidecar subprocess exits unexpectedly while requests are pending."""


class SidecarIPCError(SidecarError):
    """Raised when communication with the sidecar subprocess fails."""


class RequestFailedError(TinkerError):
    """Raised when an asynchronous request completes in a failed state."""

    def __init__(
        self,
        message: str,
        request_id: str,
        category: "RequestErrorCategory",
    ) -> None:
        super().__init__(message)
        self.message: str = message
        self.request_id: str = request_id
        self.category: RequestErrorCategory = category

    def __reduce__(self):
        # Return a tuple of (callable, args) to recreate the exception
        return (
            self.__class__,
            (self.message, self.request_id, self.category),  # positional args
            None,
        )

```

## /src/tinker/_files.py

```py path="/src/tinker/_files.py" 
from __future__ import annotations

import io
import os
import pathlib
from typing import overload

import anyio
from typing_extensions import TypeGuard

from ._types import (
    Base64FileInput,
    FileContent,
    FileTypes,
    HttpxFileContent,
    HttpxFileTypes,
    HttpxRequestFiles,
    RequestFiles,
)
from ._utils._utils import is_mapping_t, is_sequence_t, is_tuple_t


def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
    return isinstance(obj, (io.IOBase, os.PathLike))


def is_file_content(obj: object) -> TypeGuard[FileContent]:
    return isinstance(obj, (bytes, tuple, io.IOBase, os.PathLike))


def assert_is_file_content(obj: object, *, key: str | None = None) -> None:
    if not is_file_content(obj):
        prefix = (
            f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`"
        )
        raise RuntimeError(
            f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead."
        ) from None


@overload
def to_httpx_files(files: None) -> None: ...


@overload
def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...


def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
    if files is None:
        return None

    if is_mapping_t(files):
        files = {key: _transform_file(file) for key, file in files.items()}
    elif is_sequence_t(files):
        files = [(key, _transform_file(file)) for key, file in files]
    else:
        raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")

    return files


def _transform_file(file: FileTypes) -> HttpxFileTypes:
    if is_file_content(file):
        if isinstance(file, os.PathLike):
            path = pathlib.Path(file)
            return (path.name, path.read_bytes())

        return file

    if is_tuple_t(file):
        return (file[0], read_file_content(file[1]), *file[2:])

    raise TypeError("Expected file types input to be a FileContent type or to be a tuple")


def read_file_content(file: FileContent) -> HttpxFileContent:
    if isinstance(file, os.PathLike):
        return pathlib.Path(file).read_bytes()
    return file


@overload
async def async_to_httpx_files(files: None) -> None: ...


@overload
async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...


async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
    if files is None:
        return None

    if is_mapping_t(files):
        files = {key: await _async_transform_file(file) for key, file in files.items()}
    elif is_sequence_t(files):
        files = [(key, await _async_transform_file(file)) for key, file in files]
    else:
        raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence")

    return files


async def _async_transform_file(file: FileTypes) -> HttpxFileTypes:
    if is_file_content(file):
        if isinstance(file, os.PathLike):
            path = anyio.Path(file)
            return (path.name, await path.read_bytes())

        return file

    if is_tuple_t(file):
        return (file[0], await async_read_file_content(file[1]), *file[2:])

    raise TypeError("Expected file types input to be a FileContent type or to be a tuple")


async def async_read_file_content(file: FileContent) -> HttpxFileContent:
    if isinstance(file, os.PathLike):
        return await anyio.Path(file).read_bytes()

    return file

```

## /src/tinker/_qs.py

```py path="/src/tinker/_qs.py" 
from __future__ import annotations

from typing import Any, List, Mapping, Tuple, TypeVar, Union
from urllib.parse import parse_qs, urlencode

from typing_extensions import Literal, get_args

from ._types import NOT_GIVEN, NotGiven, NotGivenOr
from ._utils import flatten

_T = TypeVar("_T")


ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
NestedFormat = Literal["dots", "brackets"]

PrimitiveData = Union[str, int, float, bool, None]
# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"]
# https://github.com/microsoft/pyright/issues/3555
Data = Union[PrimitiveData, List[Any], Tuple[Any], "Mapping[str, Any]"]
Params = Mapping[str, Data]


class Querystring:
    array_format: ArrayFormat
    nested_format: NestedFormat

    def __init__(
        self,
        *,
        array_format: ArrayFormat = "repeat",
        nested_format: NestedFormat = "brackets",
    ) -> None:
        self.array_format = array_format
        self.nested_format = nested_format

    def parse(self, query: str) -> Mapping[str, object]:
        # Note: custom format syntax is not supported yet
        return parse_qs(query)

    def stringify(
        self,
        params: Params,
        *,
        array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN,
        nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN,
    ) -> str:
        return urlencode(
            self.stringify_items(
                params,
                array_format=array_format,
                nested_format=nested_format,
            )
        )

    def stringify_items(
        self,
        params: Params,
        *,
        array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN,
        nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN,
    ) -> list[tuple[str, str]]:
        opts = Options(
            qs=self,
            array_format=array_format,
            nested_format=nested_format,
        )
        return flatten([self._stringify_item(key, value, opts) for key, value in params.items()])

    def _stringify_item(
        self,
        key: str,
        value: Data,
        opts: Options,
    ) -> list[tuple[str, str]]:
        if isinstance(value, Mapping):
            items: list[tuple[str, str]] = []
            nested_format = opts.nested_format
            for subkey, subvalue in value.items():
                items.extend(
                    self._stringify_item(
                        # TODO: error if unknown format
                        f"{key}.{subkey}" if nested_format == "dots" else f"{key}[{subkey}]",
                        subvalue,
                        opts,
                    )
                )
            return items

        if isinstance(value, (list, tuple)):
            array_format = opts.array_format
            if array_format == "comma":
                return [
                    (
                        key,
                        ",".join(
                            self._primitive_value_to_str(item) for item in value if item is not None
                        ),
                    ),
                ]
            elif array_format == "repeat":
                items = []
                for item in value:
                    items.extend(self._stringify_item(key, item, opts))
                return items
            elif array_format == "indices":
                raise NotImplementedError("The array indices format is not supported yet")
            elif array_format == "brackets":
                items = []
                key = key + "[]"
                for item in value:
                    items.extend(self._stringify_item(key, item, opts))
                return items
            else:
                raise NotImplementedError(
                    f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
                )

        serialised = self._primitive_value_to_str(value)
        if not serialised:
            return []
        return [(key, serialised)]

    def _primitive_value_to_str(self, value: PrimitiveData) -> str:
        # copied from httpx
        if value is True:
            return "true"
        elif value is False:
            return "false"
        elif value is None:
            return ""
        return str(value)


_qs = Querystring()
parse = _qs.parse
stringify = _qs.stringify
stringify_items = _qs.stringify_items


class Options:
    array_format: ArrayFormat
    nested_format: NestedFormat

    def __init__(
        self,
        qs: Querystring = _qs,
        *,
        array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN,
        nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN,
    ) -> None:
        self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format
        self.nested_format = (
            qs.nested_format if isinstance(nested_format, NotGiven) else nested_format
        )

```

## /src/tinker/_resource.py

```py path="/src/tinker/_resource.py" 
from __future__ import annotations

from typing import TYPE_CHECKING

import anyio

if TYPE_CHECKING:
    from ._client import AsyncTinker


class AsyncAPIResource:
    _client: AsyncTinker

    def __init__(self, client: AsyncTinker) -> None:
        self._client = client
        self._get = client.get
        self._post = client.post
        self._patch = client.patch
        self._put = client.put
        self._delete = client.delete
        self._get_api_list = client.get_api_list

    async def _sleep(self, seconds: float) -> None:
        await anyio.sleep(seconds)

```

## /src/tinker/_types.py

```py path="/src/tinker/_types.py" 
from __future__ import annotations

from os import PathLike
from typing import (
    IO,
    TYPE_CHECKING,
    Any,
    Callable,
    Dict,
    List,
    Mapping,
    Optional,
    Sequence,
    Tuple,
    Type,
    TypeVar,
    Union,
)

import httpx
import pydantic
from httpx import URL, AsyncBaseTransport, BaseTransport, Proxy, Response, Timeout
from typing_extensions import (
    Literal,
    Protocol,
    Set,
    TypeAlias,
    TypedDict,
    override,
    runtime_checkable,
)

if TYPE_CHECKING:
    from ._models import BaseModel
    from ._response import APIResponse, AsyncAPIResponse

Transport = BaseTransport
AsyncTransport = AsyncBaseTransport
Query = Mapping[str, object]
Body = object
AnyMapping = Mapping[str, object]
ModelT = TypeVar("ModelT", bound=pydantic.BaseModel)
_T = TypeVar("_T")


# Approximates httpx internal ProxiesTypes and RequestFiles types
# while adding support for `PathLike` instances
ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]]
ProxiesTypes = Union[str, Proxy, ProxiesDict]
if TYPE_CHECKING:
    Base64FileInput = Union[IO[bytes], PathLike[str]]
    FileContent = Union[IO[bytes], bytes, PathLike[str]]
else:
    Base64FileInput = Union[IO[bytes], PathLike]
    FileContent = Union[IO[bytes], bytes, PathLike]  # PathLike is not subscriptable in Python 3.8.
FileTypes = Union[
    # file (or bytes)
    FileContent,
    # (filename, file (or bytes))
    Tuple[Optional[str], FileContent],
    # (filename, file (or bytes), content_type)
    Tuple[Optional[str], FileContent, Optional[str]],
    # (filename, file (or bytes), content_type, headers)
    Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]],
]
RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]

# duplicate of the above but without our custom file support
HttpxFileContent = Union[IO[bytes], bytes]
HttpxFileTypes = Union[
    # file (or bytes)
    HttpxFileContent,
    # (filename, file (or bytes))
    Tuple[Optional[str], HttpxFileContent],
    # (filename, file (or bytes), content_type)
    Tuple[Optional[str], HttpxFileContent, Optional[str]],
    # (filename, file (or bytes), content_type, headers)
    Tuple[Optional[str], HttpxFileContent, Optional[str], Mapping[str, str]],
]
HttpxRequestFiles = Union[Mapping[str, HttpxFileTypes], Sequence[Tuple[str, HttpxFileTypes]]]

# Workaround to support (cast_to: Type[ResponseT]) -> ResponseT
# where ResponseT includes `None`. In order to support directly
# passing `None`, overloads would have to be defined for every
# method that uses `ResponseT` which would lead to an unacceptable
# amount of code duplication and make it unreadable. See _base_client.py
# for example usage.
#
# This unfortunately means that you will either have
# to import this type and pass it explicitly:
#
# from tinker import NoneType
# client.get('/foo', cast_to=NoneType)
#
# or build it yourself:
#
# client.get('/foo', cast_to=type(None))
if TYPE_CHECKING:
    NoneType: Type[None]
else:
    NoneType = type(None)


class RequestOptions(TypedDict, total=False):
    headers: Headers
    max_retries: int
    timeout: float | Timeout | None
    params: Query
    extra_json: AnyMapping
    idempotency_key: str
    follow_redirects: bool


# Sentinel class used until PEP 0661 is accepted
class NotGiven:
    """
    A sentinel singleton class used to distinguish omitted keyword arguments
    from those passed in with the value None (which may have different behavior).

    For example:

    \`\`\`py
    def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: ...


    get(timeout=1)  # 1s timeout
    get(timeout=None)  # No timeout
    get()  # Default timeout behavior, which may not be statically known at the method definition.
    \`\`\`
    """

    def __bool__(self) -> Literal[False]:
        return False

    @override
    def __repr__(self) -> str:
        return "NOT_GIVEN"


NotGivenOr = Union[_T, NotGiven]
NOT_GIVEN = NotGiven()


class Omit:
    """In certain situations you need to be able to represent a case where a default value has
    to be explicitly removed and `None` is not an appropriate substitute, for example:

    \`\`\`py
    # as the default `Content-Type` header is `application/json` that will be sent
    client.post("/upload/files", files={"file": b"my raw file content"})

    # you can't explicitly override the header as it has to be dynamically generated
    # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983'
    client.post(..., headers={"Content-Type": "multipart/form-data"})

    # instead you can remove the default `application/json` header by passing Omit
    client.post(..., headers={"Content-Type": Omit()})
    \`\`\`
    """

    def __bool__(self) -> Literal[False]:
        return False


@runtime_checkable
class ModelBuilderProtocol(Protocol):
    @classmethod
    def build(
        cls: type[_T],
        *,
        response: Response,
        data: object,
    ) -> _T: ...


Headers = Mapping[str, Union[str, Omit]]


class HeadersLikeProtocol(Protocol):
    def get(self, __key: str) -> str | None: ...


HeadersLike = Union[Headers, HeadersLikeProtocol]

ResponseT = TypeVar(
    "ResponseT",
    bound=Union[
        object,
        str,
        None,
        "BaseModel",
        List[Any],
        Dict[str, Any],
        Response,
        ModelBuilderProtocol,
        "APIResponse[Any]",
        "AsyncAPIResponse[Any]",
    ],
)

StrBytesIntFloat = Union[str, bytes, int, float]

# Note: copied from Pydantic
# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79
IncEx: TypeAlias = Union[
    Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]
]

PostParser = Callable[[Any], Any]


@runtime_checkable
class InheritsGeneric(Protocol):
    """Represents a type that has inherited from `Generic`

    The `__orig_bases__` property can be used to determine the resolved
    type variable for a given base class.
    """

    __orig_bases__: tuple[_GenericAlias]


class _GenericAlias(Protocol):
    __origin__: type[object]


class HttpxSendArgs(TypedDict, total=False):
    auth: httpx.Auth
    follow_redirects: bool

```

## /src/tinker/_utils/_logs.py

```py path="/src/tinker/_utils/_logs.py" 
import logging
import os

logger: logging.Logger = logging.getLogger("tinker")
httpx_logger: logging.Logger = logging.getLogger("httpx")


def _basic_config() -> None:
    # e.g. [2023-10-05 14:12:26 - tinker._base_client:818 - DEBUG] HTTP Request: POST http://127.0.0.1:4010/foo/bar "200 OK"
    logging.basicConfig(
        format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )


def setup_logging() -> None:
    # httpx prints out the HTTP requests its making out to INFO
    # log stream. The tinker API server communicates backpressure
    # to the SDK using certain 4xx error codes, which looks scary
    # to the user, but are actually harmless.
    #
    # Thus, we set the default httpx logging level to WARNING so
    # that they don't see a bunch of red herrings and get worried.
    httpx_logger.setLevel(logging.WARNING)

    env = os.environ.get("TINKER_LOG")
    if env == "debug":
        _basic_config()
        logger.setLevel(logging.DEBUG)
        httpx_logger.setLevel(logging.DEBUG)
    elif env == "info":
        _basic_config()
        logger.setLevel(logging.INFO)
        httpx_logger.setLevel(logging.INFO)

```

## /src/tinker/_utils/_streams.py

```py path="/src/tinker/_utils/_streams.py" 
from typing import Any

from typing_extensions import AsyncIterator, Iterator


def consume_sync_iterator(iterator: Iterator[Any]) -> None:
    for _ in iterator:
        ...


async def consume_async_iterator(iterator: AsyncIterator[Any]) -> None:
    async for _ in iterator:
        ...

```

## /src/tinker/_version.py

```py path="/src/tinker/_version.py" 
from importlib.metadata import version

__title__ = "tinker"
__version__ = version(__title__)

```

## /src/tinker/cli/CLAUDE.md

@AGENTS.md


## /tests/sample_file.txt

Hello, world!



The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.
Copied!