synapsai-python 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

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.
synapsai/__init__.py CHANGED
@@ -5,12 +5,13 @@ A scalable and maintainable Python client library for interacting with SynapsAI
5
5
  compatible with OpenAI API patterns.
6
6
  """
7
7
 
8
- from .client import SynapsAI, AsyncSynapsAI
8
+ from .client import SynapsAI, AsyncSynapsAI, DEFAULT_UPLOAD_BASE_URL
9
9
  from .types import *
10
10
  from .resources import *
11
11
 
12
- __version__ = "0.1.0"
12
+ __version__ = "0.1.2"
13
13
  __all__ = [
14
14
  "SynapsAI",
15
15
  "AsyncSynapsAI",
16
+ "DEFAULT_UPLOAD_BASE_URL",
16
17
  ]
synapsai/cli.py ADDED
@@ -0,0 +1,168 @@
1
+ # Copyright 2026 SynapsAI Technologies Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Command-line interface for SynapsAI uploads."""
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import os
21
+ from typing import List, Optional
22
+
23
+ import click
24
+
25
+ from .client import SynapsAI
26
+ from .exceptions import APIError
27
+
28
+
29
+ def _resolve_api_key(api_key: Optional[str]) -> str:
30
+ value = api_key or os.environ.get("SYNAPSAI_API_KEY")
31
+ if not value:
32
+ raise click.ClickException(
33
+ "API key required. Pass --api-key or set SYNAPSAI_API_KEY."
34
+ )
35
+ return value
36
+
37
+
38
+ def _normalize_base_url(host: str) -> str:
39
+ value = host.strip().rstrip("/")
40
+ if not value.startswith("http://") and not value.startswith("https://"):
41
+ value = f"https://{value}"
42
+ if not value.endswith("/v1"):
43
+ value = f"{value}/v1"
44
+ return value
45
+
46
+
47
+ @click.group()
48
+ @click.version_option(version="0.1.0", prog_name="synapsai")
49
+ def main() -> None:
50
+ """SynapsAI Cloud CLI."""
51
+
52
+
53
+ @main.command("upload-model")
54
+ @click.argument("path", type=click.Path(exists=True, file_okay=True, dir_okay=True))
55
+ @click.option("--artifact-id", required=True, help="Existing model artifact id to upload into.")
56
+ @click.option(
57
+ "--host",
58
+ default=None,
59
+ help="API host (default: SYNAPSAI_API_BASE or https://api.synapsai.cloud/v1).",
60
+ )
61
+ @click.option("--api-key", default=None, help="API key (default: SYNAPSAI_API_KEY).")
62
+ @click.option("--timeout", default=3600.0, show_default=True, type=float, help="Request timeout seconds.")
63
+ def upload_model(
64
+ path: str,
65
+ artifact_id: str,
66
+ host: Optional[str],
67
+ api_key: Optional[str],
68
+ timeout: float,
69
+ ) -> None:
70
+ """Upload model files into an existing artifact on the API.
71
+
72
+ PATH may be a single file or a directory of model weights/config files.
73
+ Starts an upload session, streams files, then completes ingest.
74
+ Uses the same host as the rest of the API (``SYNAPSAI_API_BASE`` or
75
+ ``https://api.synapsai.cloud/v1``).
76
+ """
77
+ base_url = _normalize_base_url(host) if host else None
78
+ client = SynapsAI(api_key=_resolve_api_key(api_key), base_url=base_url, timeout=timeout)
79
+
80
+ def on_progress(relative: str, index: int, total: int) -> None:
81
+ click.echo(f"[{index}/{total}] uploading {relative}")
82
+
83
+ try:
84
+ artifact = client.model_artifacts.upload(
85
+ path,
86
+ artifact_id=artifact_id,
87
+ on_progress=on_progress,
88
+ )
89
+ except (APIError, OSError, ValueError) as exc:
90
+ raise click.ClickException(str(exc)) from exc
91
+
92
+ click.echo(
93
+ json.dumps(
94
+ {
95
+ "id": artifact.id,
96
+ "display_name": artifact.display_name,
97
+ "pipeline": artifact.pipeline,
98
+ "status": artifact.status,
99
+ "size_gb": artifact.size_gb,
100
+ },
101
+ indent=2,
102
+ )
103
+ )
104
+
105
+
106
+ @main.command("upload-vector-store-files")
107
+ @click.argument("vector_store_id")
108
+ @click.argument("paths", nargs=-1, required=True, type=click.Path(exists=True))
109
+ @click.option(
110
+ "--host",
111
+ default=None,
112
+ help="API host (default: SYNAPSAI_API_BASE or https://api.synapsai.cloud/v1).",
113
+ )
114
+ @click.option("--api-key", default=None, help="API key (default: SYNAPSAI_API_KEY).")
115
+ @click.option(
116
+ "--attributes",
117
+ default=None,
118
+ help="Optional JSON object applied as metadata attributes on each upload.",
119
+ )
120
+ @click.option("--timeout", default=600.0, show_default=True, type=float, help="Request timeout seconds.")
121
+ def upload_vector_store_files(
122
+ vector_store_id: str,
123
+ paths: List[str],
124
+ host: Optional[str],
125
+ api_key: Optional[str],
126
+ attributes: Optional[str],
127
+ timeout: float,
128
+ ) -> None:
129
+ """Upload files or directories into a vector store.
130
+
131
+ Each PATH may be an individual file or a directory (recursively uploaded).
132
+ """
133
+ parsed_attributes = None
134
+ if attributes:
135
+ try:
136
+ parsed_attributes = json.loads(attributes)
137
+ except json.JSONDecodeError as exc:
138
+ raise click.ClickException("--attributes must be valid JSON") from exc
139
+ if not isinstance(parsed_attributes, dict):
140
+ raise click.ClickException("--attributes must be a JSON object")
141
+
142
+ base_url = None
143
+ if host:
144
+ base_url = _normalize_base_url(host)
145
+
146
+ client = SynapsAI(
147
+ api_key=_resolve_api_key(api_key),
148
+ base_url=base_url,
149
+ timeout=timeout,
150
+ )
151
+
152
+ try:
153
+ results = client.vector_stores.files.upload_paths(
154
+ vector_store_id,
155
+ list(paths),
156
+ attributes=parsed_attributes,
157
+ )
158
+ except (APIError, OSError, ValueError) as exc:
159
+ raise click.ClickException(str(exc)) from exc
160
+
161
+ for item in results:
162
+ click.echo(f"{item.id}\t{item.status}\t{item.usage_bytes}")
163
+
164
+ click.echo(f"Uploaded {len(results)} file(s) to vector store {vector_store_id}")
165
+
166
+
167
+ if __name__ == "__main__":
168
+ main()
synapsai/client.py CHANGED
@@ -49,6 +49,14 @@ from .resources import (
49
49
  AsyncFillMaskResource,
50
50
  RerankResource,
51
51
  AsyncRerankResource,
52
+ VectorStoresResource,
53
+ AsyncVectorStoresResource,
54
+ ModelArtifactsResource,
55
+ AsyncModelArtifactsResource,
56
+ AgentsResource,
57
+ AsyncAgentsResource,
58
+ ResponsesResource,
59
+ AsyncResponsesResource,
52
60
  )
53
61
  from .exceptions import APIError, AuthenticationError
54
62
  from .utils import build_url
@@ -56,6 +64,9 @@ from .logging import get_logger
56
64
 
57
65
  logger = get_logger(__name__)
58
66
 
67
+ DEFAULT_BASE_URL = "https://api.synapsai.cloud/v1"
68
+
69
+
59
70
  class BaseClient:
60
71
  """Base client with common functionality"""
61
72
 
@@ -91,7 +102,7 @@ class BaseClient:
91
102
  base_url = os.environ.get("SYNAPSAI_API_BASE")
92
103
 
93
104
  if base_url is None:
94
- base_url = "https://api.synapsai.cloud/v1"
105
+ base_url = DEFAULT_BASE_URL
95
106
 
96
107
  self.api_key = api_key
97
108
  self.base_url = base_url.rstrip("/")
@@ -208,6 +219,10 @@ class SynapsAI(BaseClient):
208
219
  feature_extraction: FeatureExtractionResource
209
220
  fill_mask: FillMaskResource
210
221
  rerank: RerankResource
222
+ vector_stores: VectorStoresResource
223
+ model_artifacts: ModelArtifactsResource
224
+ agents: AgentsResource
225
+ responses: ResponsesResource
211
226
 
212
227
  def __init__(self, api_key: Optional[str] = None, **kwargs):
213
228
  super().__init__(api_key=api_key, **kwargs)
@@ -232,6 +247,10 @@ class SynapsAI(BaseClient):
232
247
  self.feature_extraction = FeatureExtractionResource(self)
233
248
  self.fill_mask = FillMaskResource(self)
234
249
  self.rerank = RerankResource(self)
250
+ self.vector_stores = VectorStoresResource(self)
251
+ self.model_artifacts = ModelArtifactsResource(self)
252
+ self.agents = AgentsResource(self)
253
+ self.responses = ResponsesResource(self)
235
254
 
236
255
  def __enter__(self):
237
256
  return self
@@ -243,13 +262,56 @@ class SynapsAI(BaseClient):
243
262
  """Close the HTTP client"""
244
263
  self._client.close()
245
264
 
265
+ def _prepare_request_kwargs(
266
+ self,
267
+ method: str,
268
+ endpoint: str,
269
+ json_data: Optional[Dict[str, Any]] = None,
270
+ data: Optional[Any] = None,
271
+ files: Optional[Any] = None,
272
+ content: Optional[Any] = None,
273
+ params: Optional[Dict[str, Any]] = None,
274
+ headers: Optional[Dict[str, Any]] = None,
275
+ stream: bool = False,
276
+ ) -> Dict[str, Any]:
277
+ """Build httpx request kwargs, clearing Content-Type for multipart/raw bodies."""
278
+ url = build_url(self.base_url, endpoint)
279
+ kwargs: Dict[str, Any] = {
280
+ "method": method,
281
+ "url": url,
282
+ "timeout": self.timeout,
283
+ }
284
+ if json_data is not None:
285
+ kwargs["json"] = json_data
286
+ if data is not None:
287
+ kwargs["data"] = data
288
+ if files is not None:
289
+ kwargs["files"] = files
290
+ if content is not None:
291
+ kwargs["content"] = content
292
+ if params is not None:
293
+ kwargs["params"] = params
294
+ if stream:
295
+ kwargs["stream"] = True
296
+
297
+ request_headers = dict(headers or {})
298
+ if files is not None or content is not None:
299
+ # Let httpx set multipart/raw content-type; drop JSON default.
300
+ request_headers.setdefault("Content-Type", None)
301
+ if request_headers:
302
+ kwargs["headers"] = request_headers
303
+ return kwargs
304
+
246
305
  def _request(
247
306
  self,
248
307
  method: str,
249
308
  endpoint: str,
250
309
  json_data: Optional[Dict[str, Any]] = None,
251
- data: Optional[Dict[str, Any]] = None,
252
- files: Optional[Dict[str, Any]] = None,
310
+ data: Optional[Any] = None,
311
+ files: Optional[Any] = None,
312
+ content: Optional[Any] = None,
313
+ params: Optional[Dict[str, Any]] = None,
314
+ headers: Optional[Dict[str, Any]] = None,
253
315
  stream: bool = False,
254
316
  ) -> httpx.Response:
255
317
  """
@@ -257,24 +319,17 @@ class SynapsAI(BaseClient):
257
319
 
258
320
  Retries on network errors, timeouts, and server-side errors (429, 5xx).
259
321
  """
260
- url = build_url(self.base_url, endpoint)
261
-
262
- # prepare kwargs used for every attempt
263
- base_kwargs = {
264
- "method": method,
265
- "url": url,
266
- "timeout": self.timeout,
267
- }
268
-
269
- if json_data:
270
- base_kwargs["json"] = json_data
271
- if data:
272
- base_kwargs["data"] = data
273
- if files:
274
- base_kwargs["files"] = files
275
- if stream:
276
- # note: for streaming we still attempt to (re)establish the stream on failures opening it
277
- base_kwargs["stream"] = True
322
+ base_kwargs = self._prepare_request_kwargs(
323
+ method=method,
324
+ endpoint=endpoint,
325
+ json_data=json_data,
326
+ data=data,
327
+ files=files,
328
+ content=content,
329
+ params=params,
330
+ headers=headers,
331
+ stream=stream,
332
+ )
278
333
 
279
334
  attempt = 0
280
335
  last_exc: Optional[BaseException] = None
@@ -325,26 +380,71 @@ class SynapsAI(BaseClient):
325
380
  self,
326
381
  endpoint: str,
327
382
  json_data: Optional[Dict[str, Any]] = None,
328
- data: Optional[Dict[str, Any]] = None,
329
- files: Optional[Dict[str, Any]] = None,
383
+ data: Optional[Any] = None,
384
+ files: Optional[Any] = None,
385
+ content: Optional[Any] = None,
386
+ params: Optional[Dict[str, Any]] = None,
387
+ headers: Optional[Dict[str, Any]] = None,
330
388
  ) -> httpx.Response:
331
389
  """Make a POST request"""
332
- return self._request("POST", endpoint, json_data, data, files)
333
-
334
- def _get(self, endpoint: str) -> httpx.Response:
390
+ return self._request(
391
+ "POST",
392
+ endpoint,
393
+ json_data=json_data,
394
+ data=data,
395
+ files=files,
396
+ content=content,
397
+ params=params,
398
+ headers=headers,
399
+ )
400
+
401
+ def _put(
402
+ self,
403
+ endpoint: str,
404
+ json_data: Optional[Dict[str, Any]] = None,
405
+ data: Optional[Any] = None,
406
+ files: Optional[Any] = None,
407
+ content: Optional[Any] = None,
408
+ params: Optional[Dict[str, Any]] = None,
409
+ headers: Optional[Dict[str, Any]] = None,
410
+ ) -> httpx.Response:
411
+ """Make a PUT request"""
412
+ return self._request(
413
+ "PUT",
414
+ endpoint,
415
+ json_data=json_data,
416
+ data=data,
417
+ files=files,
418
+ content=content,
419
+ params=params,
420
+ headers=headers,
421
+ )
422
+
423
+ def _get(
424
+ self,
425
+ endpoint: str,
426
+ params: Optional[Dict[str, Any]] = None,
427
+ ) -> httpx.Response:
335
428
  """Make a GET request"""
336
- return self._request("GET", endpoint)
429
+ return self._request("GET", endpoint, params=params)
337
430
 
338
- def _delete(self, endpoint: str) -> httpx.Response:
431
+ def _delete(
432
+ self,
433
+ endpoint: str,
434
+ params: Optional[Dict[str, Any]] = None,
435
+ ) -> httpx.Response:
339
436
  """Make a DELETE request"""
340
- return self._request("DELETE", endpoint)
437
+ return self._request("DELETE", endpoint, params=params)
341
438
 
342
439
  def _stream_response(
343
440
  self,
344
441
  endpoint: str,
345
442
  json_data: Optional[Dict[str, Any]] = None,
346
- data: Optional[Dict[str, Any]] = None,
347
- files: Optional[Dict[str, Any]] = None,
443
+ data: Optional[Any] = None,
444
+ files: Optional[Any] = None,
445
+ content: Optional[Any] = None,
446
+ params: Optional[Dict[str, Any]] = None,
447
+ headers: Optional[Dict[str, Any]] = None,
348
448
  ) -> Iterator[Dict[str, Any]]:
349
449
  """
350
450
  Stream response data from a POST endpoint.
@@ -352,15 +452,16 @@ class SynapsAI(BaseClient):
352
452
  The attempt to establish the stream will be retried using the same backoff rules.
353
453
  Once the stream is established, streaming errors are raised as-is.
354
454
  """
355
- url = build_url(self.base_url, endpoint)
356
- base_kwargs = {
357
- "method": "POST",
358
- "url": url,
359
- "timeout": self.timeout,
360
- "json": json_data,
361
- "data": data,
362
- "files": files,
363
- }
455
+ base_kwargs = self._prepare_request_kwargs(
456
+ method="POST",
457
+ endpoint=endpoint,
458
+ json_data=json_data,
459
+ data=data,
460
+ files=files,
461
+ content=content,
462
+ params=params,
463
+ headers=headers,
464
+ )
364
465
 
365
466
  attempt = 0
366
467
  while attempt < self.max_retries:
@@ -385,8 +486,10 @@ class SynapsAI(BaseClient):
385
486
  line = line.strip()
386
487
  if not line:
387
488
  continue
388
- if line.startswith("data: "):
389
- data_line = line[6:] # Remove "data: " prefix
489
+ if line.startswith("data:"):
490
+ data_line = line[5:]
491
+ if data_line.startswith(" "):
492
+ data_line = data_line[1:]
390
493
  if data_line == "[DONE]":
391
494
  return
392
495
  try:
@@ -410,10 +513,10 @@ class SynapsAI(BaseClient):
410
513
 
411
514
  raise APIError("Failed to establish stream after retries")
412
515
 
413
- def _get_stream(self, endpoint: str) -> httpx.Response:
516
+ def _get_stream(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> httpx.Response:
414
517
  """Make a streaming GET request for binary content (e.g. video download)."""
415
518
  url = build_url(self.base_url, endpoint)
416
- request = self._client.build_request("GET", url, timeout=self.timeout)
519
+ request = self._client.build_request("GET", url, timeout=self.timeout, params=params)
417
520
  response = self._client.send(request, stream=True)
418
521
  if response.status_code >= 400:
419
522
  response.read()
@@ -435,6 +538,10 @@ class AsyncSynapsAI(BaseClient):
435
538
  feature_extraction: AsyncFeatureExtractionResource
436
539
  fill_mask: AsyncFillMaskResource
437
540
  rerank: AsyncRerankResource
541
+ vector_stores: AsyncVectorStoresResource
542
+ model_artifacts: AsyncModelArtifactsResource
543
+ agents: AsyncAgentsResource
544
+ responses: AsyncResponsesResource
438
545
 
439
546
  def __init__(self, api_key: Optional[str] = None, **kwargs):
440
547
  super().__init__(api_key=api_key, **kwargs)
@@ -458,6 +565,10 @@ class AsyncSynapsAI(BaseClient):
458
565
  self.feature_extraction = AsyncFeatureExtractionResource(self)
459
566
  self.fill_mask = AsyncFillMaskResource(self)
460
567
  self.rerank = AsyncRerankResource(self)
568
+ self.vector_stores = AsyncVectorStoresResource(self)
569
+ self.model_artifacts = AsyncModelArtifactsResource(self)
570
+ self.agents = AsyncAgentsResource(self)
571
+ self.responses = AsyncResponsesResource(self)
461
572
 
462
573
  async def __aenter__(self):
463
574
  return self
@@ -474,24 +585,23 @@ class AsyncSynapsAI(BaseClient):
474
585
  method: str,
475
586
  endpoint: str,
476
587
  json_data: Optional[Dict[str, Any]] = None,
477
- data: Optional[Dict[str, Any]] = None,
478
- files: Optional[Dict[str, Any]] = None,
588
+ data: Optional[Any] = None,
589
+ files: Optional[Any] = None,
590
+ content: Optional[Any] = None,
591
+ params: Optional[Dict[str, Any]] = None,
592
+ headers: Optional[Dict[str, Any]] = None,
479
593
  ) -> httpx.Response:
480
594
  """Make an async request to the API with retries."""
481
- url = build_url(self.base_url, endpoint)
482
-
483
- base_kwargs: Dict[str, Any] = {
484
- "method": method,
485
- "url": url,
486
- "timeout": self.timeout,
487
- }
488
-
489
- if json_data:
490
- base_kwargs["json"] = json_data
491
- if data:
492
- base_kwargs["data"] = data
493
- if files:
494
- base_kwargs["files"] = files
595
+ base_kwargs = self._prepare_request_kwargs(
596
+ method=method,
597
+ endpoint=endpoint,
598
+ json_data=json_data,
599
+ data=data,
600
+ files=files,
601
+ content=content,
602
+ params=params,
603
+ headers=headers,
604
+ )
495
605
 
496
606
  attempt = 0
497
607
  last_exc: Optional[BaseException] = None
@@ -536,26 +646,71 @@ class AsyncSynapsAI(BaseClient):
536
646
  self,
537
647
  endpoint: str,
538
648
  json_data: Optional[Dict[str, Any]] = None,
539
- data: Optional[Dict[str, Any]] = None,
540
- files: Optional[Dict[str, Any]] = None,
649
+ data: Optional[Any] = None,
650
+ files: Optional[Any] = None,
651
+ content: Optional[Any] = None,
652
+ params: Optional[Dict[str, Any]] = None,
653
+ headers: Optional[Dict[str, Any]] = None,
541
654
  ) -> httpx.Response:
542
655
  """Make an async POST request"""
543
- return await self._request("POST", endpoint, json_data, data, files)
544
-
545
- async def _get(self, endpoint: str) -> httpx.Response:
656
+ return await self._request(
657
+ "POST",
658
+ endpoint,
659
+ json_data=json_data,
660
+ data=data,
661
+ files=files,
662
+ content=content,
663
+ params=params,
664
+ headers=headers,
665
+ )
666
+
667
+ async def _put(
668
+ self,
669
+ endpoint: str,
670
+ json_data: Optional[Dict[str, Any]] = None,
671
+ data: Optional[Any] = None,
672
+ files: Optional[Any] = None,
673
+ content: Optional[Any] = None,
674
+ params: Optional[Dict[str, Any]] = None,
675
+ headers: Optional[Dict[str, Any]] = None,
676
+ ) -> httpx.Response:
677
+ """Make an async PUT request"""
678
+ return await self._request(
679
+ "PUT",
680
+ endpoint,
681
+ json_data=json_data,
682
+ data=data,
683
+ files=files,
684
+ content=content,
685
+ params=params,
686
+ headers=headers,
687
+ )
688
+
689
+ async def _get(
690
+ self,
691
+ endpoint: str,
692
+ params: Optional[Dict[str, Any]] = None,
693
+ ) -> httpx.Response:
546
694
  """Make a GET request"""
547
- return await self._request("GET", endpoint=endpoint)
695
+ return await self._request("GET", endpoint=endpoint, params=params)
548
696
 
549
- async def _delete(self, endpoint: str) -> httpx.Response:
697
+ async def _delete(
698
+ self,
699
+ endpoint: str,
700
+ params: Optional[Dict[str, Any]] = None,
701
+ ) -> httpx.Response:
550
702
  """Make a DELETE request"""
551
- return await self._request("DELETE", endpoint=endpoint)
703
+ return await self._request("DELETE", endpoint=endpoint, params=params)
552
704
 
553
705
  async def _stream_response(
554
706
  self,
555
707
  endpoint: str,
556
708
  json_data: Optional[Dict[str, Any]] = None,
557
- data: Optional[Dict[str, Any]] = None,
558
- files: Optional[Dict[str, Any]] = None,
709
+ data: Optional[Any] = None,
710
+ files: Optional[Any] = None,
711
+ content: Optional[Any] = None,
712
+ params: Optional[Dict[str, Any]] = None,
713
+ headers: Optional[Dict[str, Any]] = None,
559
714
  ) -> AsyncIterator[Dict[str, Any]]:
560
715
  """
561
716
  Stream response data from a POST endpoint (async).
@@ -563,16 +718,16 @@ class AsyncSynapsAI(BaseClient):
563
718
  The attempt to establish the stream will be retried using the same backoff rules.
564
719
  Once the stream is established, streaming errors are raised as-is.
565
720
  """
566
- url = build_url(self.base_url, endpoint)
567
-
568
- base_kwargs = {
569
- "method": "POST",
570
- "url": url,
571
- "timeout": self.timeout,
572
- "json": json_data,
573
- "data": data,
574
- "files": files,
575
- }
721
+ base_kwargs = self._prepare_request_kwargs(
722
+ method="POST",
723
+ endpoint=endpoint,
724
+ json_data=json_data,
725
+ data=data,
726
+ files=files,
727
+ content=content,
728
+ params=params,
729
+ headers=headers,
730
+ )
576
731
 
577
732
  attempt = 0
578
733
  while attempt < self.max_retries:
@@ -595,8 +750,10 @@ class AsyncSynapsAI(BaseClient):
595
750
  line = raw_line.strip()
596
751
  if not line:
597
752
  continue
598
- if line.startswith("data: "):
599
- data_line = line[6:] # Remove "data: " prefix
753
+ if line.startswith("data:"):
754
+ data_line = line[5:]
755
+ if data_line.startswith(" "):
756
+ data_line = data_line[1:]
600
757
  if data_line == "[DONE]":
601
758
  return
602
759
  try:
@@ -617,10 +774,14 @@ class AsyncSynapsAI(BaseClient):
617
774
 
618
775
  raise APIError("Failed to establish stream after retries")
619
776
 
620
- async def _get_stream(self, endpoint: str) -> httpx.Response:
777
+ async def _get_stream(
778
+ self,
779
+ endpoint: str,
780
+ params: Optional[Dict[str, Any]] = None,
781
+ ) -> httpx.Response:
621
782
  """Make an async streaming GET request for binary content (e.g. video download)."""
622
783
  url = build_url(self.base_url, endpoint)
623
- request = self._client.build_request("GET", url, timeout=self.timeout)
784
+ request = self._client.build_request("GET", url, timeout=self.timeout, params=params)
624
785
  response = await self._client.send(request, stream=True)
625
786
  if response.status_code >= 400:
626
787
  await response.aread()