meshapi 0.1.2__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. meshapi-0.1.4/CLAUDE.md +159 -0
  2. meshapi-0.1.4/PKG-INFO +458 -0
  3. meshapi-0.1.4/README.md +421 -0
  4. {meshapi-0.1.2 → meshapi-0.1.4}/TESTING.md +2 -2
  5. meshapi-0.1.4/livetests/compare.py +34 -0
  6. meshapi-0.1.4/livetests/config.py +43 -0
  7. meshapi-0.1.4/livetests/conftest.py +98 -0
  8. meshapi-0.1.4/livetests/pytest.ini +16 -0
  9. meshapi-0.1.4/livetests/requirements.txt +2 -0
  10. meshapi-0.1.4/livetests/responses.py +23 -0
  11. meshapi-0.1.4/livetests/test_chat.py +83 -0
  12. meshapi-0.1.4/livetests/test_errors.py +45 -0
  13. meshapi-0.1.4/livetests/test_feature_matrix.py +158 -0
  14. meshapi-0.1.4/livetests/test_inference_resources.py +156 -0
  15. meshapi-0.1.4/livetests/test_models.py +38 -0
  16. meshapi-0.1.4/livetests/test_rag.py +114 -0
  17. meshapi-0.1.4/livetests/test_realtime.py +128 -0
  18. meshapi-0.1.4/livetests/test_stream.py +73 -0
  19. meshapi-0.1.4/livetests/test_templates.py +61 -0
  20. meshapi-0.1.4/livetests/tool_call.py +101 -0
  21. {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/__init__.py +54 -7
  22. {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/_http.py +8 -0
  23. {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/_types.py +158 -26
  24. meshapi-0.1.4/meshapi/resources/images.py +38 -0
  25. meshapi-0.1.4/meshapi/resources/rag.py +117 -0
  26. meshapi-0.1.4/meshapi/resources/realtime.py +325 -0
  27. {meshapi-0.1.2 → meshapi-0.1.4}/pyproject.toml +3 -2
  28. meshapi-0.1.2/PKG-INFO +0 -519
  29. meshapi-0.1.2/README.md +0 -485
  30. meshapi-0.1.2/meshapi/resources/files.py +0 -44
  31. {meshapi-0.1.2 → meshapi-0.1.4}/.gitignore +0 -0
  32. {meshapi-0.1.2 → meshapi-0.1.4}/CHANGELOG.md +0 -0
  33. {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/_errors.py +0 -0
  34. {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/__init__.py +0 -0
  35. {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/batches.py +0 -0
  36. {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/chat.py +0 -0
  37. {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/compare.py +0 -0
  38. {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/embeddings.py +0 -0
  39. {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/models.py +0 -0
  40. {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/responses.py +0 -0
  41. {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/templates.py +0 -0
@@ -0,0 +1,159 @@
1
+ # MeshAPI Python SDK
2
+
3
+ Official Python client for the MeshAPI AI model gateway.
4
+
5
+ - **Package**: `meshapi`
6
+ - **Python**: 3.9+
7
+ - **Runtime dependencies**: `httpx>=0.27`, `pydantic>=2`
8
+ - **Build backend**: hatchling
9
+
10
+ ## Project layout
11
+
12
+ ```
13
+ python/
14
+ ├── meshapi/
15
+ │ ├── __init__.py # MeshAPI, AsyncMeshAPI, all public exports
16
+ │ ├── _types.py # All Pydantic request/response models
17
+ │ ├── _http.py # SyncHttpClient / AsyncHttpClient (httpx-based)
18
+ │ ├── _errors.py # MeshAPIError exception
19
+ │ └── resources/
20
+ │ ├── chat.py # /v1/chat/completions
21
+ │ ├── responses.py # /v1/responses
22
+ │ ├── embeddings.py # /v1/embeddings
23
+ │ ├── compare.py # /v1/compare
24
+ │ ├── files.py # /v1/files (batch file objects)
25
+ │ ├── rag.py # /v1/files RAG endpoints (upload, embed, search)
26
+ │ ├── batches.py # /v1/batches
27
+ │ ├── models.py # /v1/models
28
+ │ ├── templates.py # /v1/templates
29
+ │ └── images.py # /v1/images/generations
30
+ ├── tests/
31
+ │ ├── unit/ # Fast, no-network tests
32
+ │ ├── contract/ # Pydantic model parsing against local fixtures
33
+ │ └── integration/ # Full-stack tests against a running backend
34
+ ├── livetests/ # Live tests against a real backend
35
+ └── pyproject.toml
36
+ ```
37
+
38
+ ## Common tasks
39
+
40
+ ### Set up development environment
41
+
42
+ ```bash
43
+ python -m venv .venv
44
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
45
+ pip install -e ".[dev]"
46
+ ```
47
+
48
+ ### Unit and contract tests (no network)
49
+
50
+ ```bash
51
+ pytest tests/unit/ tests/contract/ -v
52
+ ```
53
+
54
+ ### Integration tests (requires a running backend)
55
+
56
+ ```bash
57
+ MESHAPI_BASE_URL=http://localhost:8000 MESHAPI_TOKEN=rsk_... pytest tests/integration/ -v
58
+ ```
59
+
60
+ ### Adding a new resource
61
+
62
+ 1. Add Pydantic models to `_types.py` under a clearly labelled section.
63
+ 2. Create `resources/<name>.py` with `<Name>Resource` and `Async<Name>Resource` classes.
64
+ 3. Both classes take their respective `SyncHttpClient` / `AsyncHttpClient` in `__init__`.
65
+ 4. Wire the resource into both `MeshAPI` and `AsyncMeshAPI` in `__init__.py`.
66
+ 5. Import the new types and resource classes in `__init__.py` and add them to `__all__`.
67
+ 6. Follow the pattern in `resources/templates.py`.
68
+
69
+ ---
70
+
71
+ ## Live tests
72
+
73
+ Live tests hit a real MeshAPI backend and live in `livetests/`. They use pytest with a shared `client` fixture from `conftest.py`.
74
+
75
+ ### Prerequisites
76
+
77
+ - A running MeshAPI instance (default `http://localhost:8000`), **or** point at the dev API.
78
+ - A valid data-plane API key (`rsk_...`).
79
+
80
+ ### Environment variables
81
+
82
+ Create `python/.env.livetest` (read automatically by the test harness) or export the variables in your shell before running tests.
83
+
84
+ | Variable | Required | Default | Description |
85
+ |----------|----------|---------|-------------|
86
+ | `MESHAPI_BASE_URL` | No | `http://localhost:8000` | Base URL of the MeshAPI gateway |
87
+ | `MESHAPI_TOKEN` | **Yes** | hardcoded dev key | Data-plane API key (`rsk_...`) |
88
+ | `MESHAPI_MODEL` | No | `openai/gpt-4o-mini` | Primary model used in chat/stream tests |
89
+ | `MESHAPI_SECOND_MODEL` | No | `anthropic/claude-haiku-4.5` | Second model for compare tests |
90
+ | `MESHAPI_EMBEDDINGS_MODEL` | No | `openai/text-embedding-3-small` | Model used in embeddings tests |
91
+ | `MESHAPI_IMAGE_GEN_MODEL` | No | _(skipped if unset)_ | Image generation model; test skipped if blank |
92
+ | `MESHAPI_IMAGE_URL` | No | _(skipped if unset)_ | Publicly accessible image URL for vision tests |
93
+ | `MESHAPI_INPUT_AUDIO_B64` | No | _(skipped if unset)_ | Base64-encoded audio for audio-input tests |
94
+ | `MESHAPI_INPUT_AUDIO_FORMAT` | No | `wav` | Format of the base64 audio (`wav`, `mp3`, etc.) |
95
+ | `MESHAPI_AUDIO_OUT_MODEL` | No | _(skipped if unset)_ | Model for audio-output tests; skipped if blank |
96
+ | `MESHAPI_REALTIME_MODEL` | No | `openai/gpt-realtime-mini` | Realtime-capable model used in WebSocket live tests |
97
+
98
+ Example `python/.env.livetest`:
99
+
100
+ ```env
101
+ MESHAPI_BASE_URL=https://api-dev.meshapi.ai
102
+ MESHAPI_TOKEN=rsk_your_key_here
103
+ MESHAPI_MODEL=openai/gpt-4o-mini
104
+ MESHAPI_EMBEDDINGS_MODEL=openai/text-embedding-3-small
105
+ ```
106
+
107
+ ### Install dependencies
108
+
109
+ ```bash
110
+ # From the python/ directory
111
+ pip install -e ".[dev]"
112
+ pip install httpx # required by the RAG live test for direct signed-URL PUT
113
+ ```
114
+
115
+ ### Run all live tests
116
+
117
+ ```bash
118
+ cd livetests
119
+ pytest -v
120
+ ```
121
+
122
+ ### Run a single live test file
123
+
124
+ ```bash
125
+ cd livetests
126
+ pytest test_rag.py -v
127
+ ```
128
+
129
+ ### Run a specific test function
130
+
131
+ ```bash
132
+ cd livetests
133
+ pytest test_rag.py::test_rag_upload_embed_search -v
134
+ ```
135
+
136
+ ### Available live test files
137
+
138
+ | File | What it tests |
139
+ |------|---------------|
140
+ | `test_chat.py` | Chat completions (basic, tools, multi-turn) |
141
+ | `test_stream.py` | Streaming chat and responses |
142
+ | `test_models.py` | Model listing |
143
+ | `test_templates.py` | Template CRUD lifecycle |
144
+ | `test_inference_resources.py` | Embeddings, responses |
145
+ | `test_errors.py` | 401/404 error handling |
146
+ | `test_feature_matrix.py` | Cross-model feature matrix |
147
+ | `test_rag.py` | RAG upload → embed → list → search |
148
+ | `test_realtime.py` | WebSocket connect/close, session.created, session.update, error envelopes, iterator API, async variants |
149
+
150
+ ### RAG live test notes
151
+
152
+ `test_rag_upload_embed_search` does the following:
153
+ 1. Calls `client.rag.init_upload` with `embed=False`.
154
+ 2. PUTs the file bytes directly to the returned `signed_url` via `httpx.put`.
155
+ 3. Waits up to 30 s for `upload_status=ready`.
156
+ 4. Calls `client.rag.embed` to trigger embedding.
157
+ 5. Polls up to 90 s for `embedding_status=ready`.
158
+ 6. Calls `client.rag.list` and asserts the file appears.
159
+ 7. Calls `client.rag.search` scoped to the file ID and asserts non-empty results.
meshapi-0.1.4/PKG-INFO ADDED
@@ -0,0 +1,458 @@
1
+ Metadata-Version: 2.4
2
+ Name: meshapi
3
+ Version: 0.1.4
4
+ Summary: Official Python SDK for the MeshAPI AI model gateway
5
+ Project-URL: Homepage, https://meshapi.ai
6
+ Project-URL: Documentation, https://developers.meshapi.ai
7
+ Project-URL: Repository, https://github.com/aifiesta/meshapi-python-sdk
8
+ Project-URL: Issues, https://github.com/aifiesta/meshapi-python-sdk/issues
9
+ Project-URL: Changelog, https://github.com/aifiesta/meshapi-python-sdk/blob/main/CHANGELOG.md
10
+ Author-email: MeshAPI <contact@meshapi.ai>
11
+ License: MIT
12
+ Keywords: ai-gateway,anthropic,batches,embeddings,llm,meshapi,openai,openai-compatible,prompt-templates,sdk
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.9
27
+ Requires-Dist: httpx>=0.27.0
28
+ Requires-Dist: pydantic>=2.0.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
31
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
32
+ Requires-Dist: respx>=0.21.0; extra == 'dev'
33
+ Requires-Dist: websockets>=12.0; extra == 'dev'
34
+ Provides-Extra: realtime
35
+ Requires-Dist: websockets>=12.0; extra == 'realtime'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # meshapi
39
+
40
+ Official Python SDK for [Mesh API](https://meshapi.ai), an AI model gateway that gives you instant access to 300+ LLMs through a single OpenAI-compatible API.
41
+
42
+ Code once with the chat completions signature you already know. Switch between OpenAI, Anthropic, Google, Meta, Mistral, DeepSeek, xAI, Alibaba and the rest by changing a model string. Streaming, tool calling, vision, embeddings, multi-model compare, batch jobs, RAG and prompt templates from a single client.
43
+
44
+ ```python
45
+ from meshapi import MeshAPI, ChatCompletionParams, ChatMessage
46
+
47
+ client = MeshAPI(base_url="https://api.meshapi.ai", token="rsk_...")
48
+
49
+ reply = client.chat.completions.create(
50
+ ChatCompletionParams(
51
+ model="anthropic/claude-sonnet-4.5",
52
+ messages=[ChatMessage(role="user", content="Write a haiku about Python.")],
53
+ )
54
+ )
55
+
56
+ print(reply.choices[0].message.content)
57
+ ```
58
+
59
+ Python 3.9+. Built on `httpx` and Pydantic v2. Sync and async clients with first-class type hints.
60
+
61
+ ## Install
62
+
63
+ ```bash
64
+ pip install meshapi
65
+ # or
66
+ uv add meshapi
67
+ # or
68
+ poetry add meshapi
69
+ ```
70
+
71
+ Get a key at [meshapi.ai](https://meshapi.ai). Data-plane keys are prefixed `rsk_`.
72
+
73
+ ## What you get
74
+
75
+ | | |
76
+ |---|---|
77
+ | **One Universal API** | Code once. A single `chat.completions.create` call works across 300+ base models. |
78
+ | **Sync and async** | Pick `MeshAPI` for scripts, `AsyncMeshAPI` for servers. Same surface, same params. |
79
+ | **Streaming + tool calling** | SSE streaming via `Iterator` / `AsyncIterator`, function calling, vision and audio content parts. |
80
+ | **Reasoning models** | First-class `responses` API with `reasoning.effort` and `max_output_tokens`. |
81
+ | **Embeddings** | Drop-in OpenAI-compatible embeddings endpoint. |
82
+ | **Multi-model compare** | Fire one prompt at N models in parallel and stream their replies side by side. |
83
+ | **RAG** | Upload files, embed them, and run vector search — all through the same client. |
84
+ | **Batches** | Async bulk inference jobs at discounted rates with inline request submission. |
85
+ | **Prompt templates** | Server-stored prompts with `{{variable}}` slots. Update prompts without redeploying. |
86
+ | **Provider fallbacks** | If a provider experiences downtime, the gateway falls back to another supported model. |
87
+ | **Structured errors** | `MeshAPIError` with `error_code`, `status`, `request_id`, `retry_after_seconds`. |
88
+ | **Type-safe** | Every request and response is a Pydantic v2 model. |
89
+
90
+ ## Authentication
91
+
92
+ ```python
93
+ client = MeshAPI(base_url="https://api.meshapi.ai", token="rsk_...")
94
+ ```
95
+
96
+ ## Chat completions
97
+
98
+ ```python
99
+ from meshapi import MeshAPI, ChatCompletionParams, ChatMessage
100
+
101
+ reply = client.chat.completions.create(
102
+ ChatCompletionParams(
103
+ model="openai/gpt-4o-mini",
104
+ messages=[
105
+ ChatMessage(role="system", content="You are a concise assistant."),
106
+ ChatMessage(role="user", content="What is the capital of France?"),
107
+ ],
108
+ temperature=0.7,
109
+ max_tokens=256,
110
+ )
111
+ )
112
+ print(reply.choices[0].message.content)
113
+ ```
114
+
115
+ ### Async
116
+
117
+ ```python
118
+ import asyncio
119
+ from meshapi import AsyncMeshAPI, ChatCompletionParams, ChatMessage
120
+
121
+ async def main():
122
+ async with AsyncMeshAPI(base_url="https://api.meshapi.ai", token="rsk_...") as client:
123
+ reply = await client.chat.completions.create(
124
+ ChatCompletionParams(
125
+ model="openai/gpt-4o-mini",
126
+ messages=[ChatMessage(role="user", content="Hello!")],
127
+ )
128
+ )
129
+ print(reply.choices[0].message.content)
130
+
131
+ asyncio.run(main())
132
+ ```
133
+
134
+ ### Streaming
135
+
136
+ ```python
137
+ for chunk in client.chat.completions.stream(
138
+ ChatCompletionParams(
139
+ model="openai/gpt-4o-mini",
140
+ messages=[ChatMessage(role="user", content="Write a haiku about Python.")],
141
+ )
142
+ ):
143
+ if chunk.choices and chunk.choices[0].delta:
144
+ print(chunk.choices[0].delta.content or "", end="", flush=True)
145
+ ```
146
+
147
+ ### Tool calling
148
+
149
+ ```python
150
+ from meshapi import Tool, ToolFunction
151
+
152
+ params = ChatCompletionParams(
153
+ model="openai/gpt-4o",
154
+ messages=[ChatMessage(role="user", content="What is the weather in Paris?")],
155
+ tools=[
156
+ Tool(
157
+ type="function",
158
+ function=ToolFunction(
159
+ name="get_weather",
160
+ description="Get current weather for a city",
161
+ parameters={
162
+ "type": "object",
163
+ "properties": {"city": {"type": "string"}},
164
+ "required": ["city"],
165
+ },
166
+ ),
167
+ )
168
+ ],
169
+ tool_choice="auto",
170
+ )
171
+ ```
172
+
173
+ ## Responses API (reasoning models)
174
+
175
+ ```python
176
+ from meshapi import ResponsesParams
177
+
178
+ reply = client.responses.create(
179
+ ResponsesParams(
180
+ model="openai/o4-mini",
181
+ input="Explain the halting problem in two sentences.",
182
+ reasoning={"effort": "medium"},
183
+ max_output_tokens=512,
184
+ )
185
+ )
186
+ ```
187
+
188
+ ## Embeddings
189
+
190
+ ```python
191
+ from meshapi import EmbeddingsParams
192
+
193
+ result = client.embeddings.create(
194
+ EmbeddingsParams(
195
+ model="openai/text-embedding-3-small",
196
+ input=["hello world", "goodbye world"],
197
+ )
198
+ )
199
+ print(len(result.data[0].embedding))
200
+ ```
201
+
202
+ ## Image generation
203
+
204
+ ```python
205
+ from meshapi import ImageGenerationParams
206
+
207
+ result = client.images.generate(
208
+ ImageGenerationParams(
209
+ model="openai/gpt-image-1",
210
+ prompt="A watercolor of a fox in a snowy forest",
211
+ n=1, size="1024x1024", quality="high", output_format="webp",
212
+ )
213
+ )
214
+ print(result.data[0].url)
215
+ ```
216
+
217
+ ## Compare (multi-model fanout)
218
+
219
+ ```python
220
+ from meshapi import CompareParams, ChatMessage
221
+
222
+ for event in client.compare.stream(
223
+ CompareParams(
224
+ models=["openai/gpt-4o-mini", "anthropic/claude-sonnet-4.5"],
225
+ messages=[ChatMessage(role="user", content="Summarise this in one sentence: ...")],
226
+ )
227
+ ):
228
+ if event.event == "delta":
229
+ print(event.data)
230
+ ```
231
+
232
+ ## Batches
233
+
234
+ Batch jobs accept inline requests — no separate file upload step required.
235
+
236
+ ```python
237
+ from meshapi import CreateBatchParams, BatchRequestItem
238
+
239
+ batch = client.batches.create(
240
+ CreateBatchParams(
241
+ requests=[
242
+ BatchRequestItem(
243
+ custom_id="req-1",
244
+ body={"model": "openai/gpt-5-nano",
245
+ "messages": [{"role": "user", "content": "Say hi."}]},
246
+ ),
247
+ BatchRequestItem(
248
+ custom_id="req-2",
249
+ body={"model": "openai/gpt-5-nano",
250
+ "messages": [{"role": "user", "content": "Say bye."}]},
251
+ ),
252
+ ],
253
+ metadata={"job": "my-batch"},
254
+ )
255
+ )
256
+
257
+ # Poll
258
+ status = client.batches.get(batch.id)
259
+ print(status.status)
260
+
261
+ # Cancel
262
+ client.batches.cancel(batch.id)
263
+ ```
264
+
265
+ ## RAG (Retrieval-Augmented Generation)
266
+
267
+ Upload files, embed them, and run vector search.
268
+
269
+ ```python
270
+ from meshapi import InitUploadRequest, BulkEmbedRequest, SearchRequest
271
+ import httpx, time
272
+
273
+ # 1. Initialise upload — get a signed URL
274
+ upload = client.rag.init_upload(
275
+ InitUploadRequest(file_name="handbook.pdf", mime_type="application/pdf")
276
+ )
277
+
278
+ # 2a. PUT file bytes to the signed URL yourself…
279
+ httpx.put(upload.signed_url, content=pdf_bytes,
280
+ headers={"Content-Type": "application/pdf"}).raise_for_status()
281
+
282
+ # 2b. …or use the convenience wrapper that does both steps:
283
+ upload = client.rag.upload_file(
284
+ file_name="handbook.pdf",
285
+ mime_type="application/pdf",
286
+ content=pdf_bytes,
287
+ )
288
+
289
+ # 3. Trigger embedding
290
+ client.rag.embed(BulkEmbedRequest(file_ids=[upload.file_id]))
291
+
292
+ # 4. Poll until ready
293
+ while True:
294
+ s = client.rag.get(upload.file_id)
295
+ if s.embedding_status == "ready":
296
+ break
297
+ time.sleep(3)
298
+
299
+ # 5. Search
300
+ results = client.rag.search(
301
+ SearchRequest(query="onboarding process", top_k=5)
302
+ )
303
+ for r in results.results:
304
+ print(f"{r.score:.4f} {r.text}")
305
+
306
+ # List files (paginated)
307
+ page = client.rag.list(limit=50)
308
+ print(f"{page.total} total files")
309
+ ```
310
+
311
+ ## Realtime (Speech-to-Speech WebSocket)
312
+
313
+ ```python
314
+ from meshapi import MeshAPI
315
+
316
+ client = MeshAPI(base_url="...", token="rsk_...")
317
+
318
+ # Sync — use as a context manager
319
+ with client.realtime.connect(model="openai/gpt-4o-realtime-preview") as session:
320
+ session.send({
321
+ "type": "session.update",
322
+ "session": {"instructions": "You are a helpful assistant."},
323
+ })
324
+ session.send_audio(pcm_bytes) # binary audio frame
325
+
326
+ for msg in session: # iterate until connection closes
327
+ print(msg.event["type"]) # "session.created", "response.done", …
328
+ if msg.audio: # binary audio frame from server
329
+ process_audio(msg.audio)
330
+ if msg.event and msg.event["type"] == "response.done":
331
+ break
332
+
333
+ # Async — identical API with await
334
+ from meshapi import AsyncMeshAPI
335
+
336
+ async with AsyncMeshAPI(base_url="...", token="rsk_...") as client:
337
+ async with client.realtime.connect(model="openai/gpt-4o-realtime-preview") as session:
338
+ await session.send({"type": "session.update", "session": {...}})
339
+ async for msg in session:
340
+ print(msg.event["type"])
341
+ ```
342
+
343
+ `RealtimeError` is raised for server-sent error envelopes (e.g. `insufficient_quota`, `idle_timeout`). Requires `websockets>=12.0` (included as a dependency).
344
+
345
+ ## Models
346
+
347
+ ```python
348
+ all_models = client.models.list()
349
+ free = client.models.free()
350
+ paid = client.models.paid()
351
+ ```
352
+
353
+ ## Prompt templates
354
+
355
+ ```python
356
+ from meshapi import CreateTemplateParams, UpdateTemplateParams
357
+
358
+ client.templates.create(
359
+ CreateTemplateParams(
360
+ name="support-agent",
361
+ system="You are a support agent for {{company}}. Be concise and friendly.",
362
+ model="openai/gpt-4o-mini",
363
+ variables=["company"],
364
+ )
365
+ )
366
+
367
+ # Use via chat
368
+ reply = client.chat.completions.create(
369
+ ChatCompletionParams(
370
+ messages=[ChatMessage(role="user", content="How do I reset my password?")],
371
+ template="support-agent",
372
+ variables={"company": "Acme Corp"},
373
+ )
374
+ )
375
+
376
+ # CRUD
377
+ templates = client.templates.list()
378
+ client.templates.update(template_id, UpdateTemplateParams(model="openai/gpt-4o"))
379
+ client.templates.delete(template_id)
380
+ ```
381
+
382
+ ## Error handling
383
+
384
+ ```python
385
+ from meshapi import MeshAPIError
386
+
387
+ try:
388
+ client.chat.completions.create(params)
389
+ except MeshAPIError as e:
390
+ print(f"[{e.status}] {e.error_code}: {e}")
391
+ print("Request ID:", e.request_id)
392
+ if e.error_code == "rate_limit_exceeded":
393
+ print(f"Retry after {e.retry_after_seconds}s")
394
+ ```
395
+
396
+ | Code | HTTP | Meaning |
397
+ |---|---|---|
398
+ | `unauthorized` | 401 | Invalid or missing key |
399
+ | `forbidden` | 403 | Key suspended |
400
+ | `not_found` / `model_not_found` | 404 | Resource or model not found |
401
+ | `spend_limit_exceeded` | 402 | Account balance at zero |
402
+ | `validation_error` | 422 | Bad request body |
403
+ | `rate_limit_exceeded` | 429 | RPM or RPD limit hit |
404
+ | `upstream_error` | 500 | Upstream or server error |
405
+ | `stream_interrupted` | n/a | Mid-stream connection dropped |
406
+
407
+ ## Retry and backoff
408
+
409
+ Retries on 429/502/503/504 with exponential backoff (default 3 retries, 500 ms base, 30 s max). **Streams do not retry.**
410
+
411
+ ```python
412
+ client = MeshAPI(base_url="...", token="rsk_...", max_retries=5, timeout=30.0)
413
+ ```
414
+
415
+ ## Type hints
416
+
417
+ ```python
418
+ from meshapi import (
419
+ MeshAPI, AsyncMeshAPI, MeshAPIError,
420
+ # chat
421
+ ChatCompletionParams, ChatCompletionResponse, ChatCompletionChunk,
422
+ ChatMessage, Tool, ToolFunction, ToolCall,
423
+ # responses
424
+ ResponsesParams, ResponsesResponse,
425
+ # embeddings
426
+ EmbeddingsParams, EmbeddingsResponse,
427
+ # compare
428
+ CompareParams, CompareStreamEvent,
429
+ # batches
430
+ BatchRequestItem, CreateBatchParams, BatchObject,
431
+ # RAG
432
+ InitUploadRequest, InitUploadResponse, UploadFileParams,
433
+ RagFileStatus, RagFileListResponse,
434
+ BulkEmbedRequest, BulkEmbedResponse,
435
+ SearchRequest, SearchResponse, SearchResult,
436
+ # models
437
+ ModelInfo, ModelPricing,
438
+ # templates
439
+ CreateTemplateParams, UpdateTemplateParams, TemplateSummary,
440
+ )
441
+ ```
442
+
443
+ ## Versioning
444
+
445
+ ```python
446
+ import meshapi
447
+ print(meshapi.__version__) # "0.1.0"
448
+ ```
449
+
450
+ ## About Mesh API
451
+
452
+ [Mesh API](https://meshapi.ai) is an AI model gateway that gives you instant access to 300+ LLMs through a single, unified API.
453
+
454
+ Documentation: [developers.meshapi.ai](https://developers.meshapi.ai)
455
+
456
+ ## License
457
+
458
+ [MIT](LICENSE)