sdkrouter 0.1.1__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.
Files changed (96) hide show
  1. sdkrouter/__init__.py +110 -0
  2. sdkrouter/_api/__init__.py +28 -0
  3. sdkrouter/_api/client.py +204 -0
  4. sdkrouter/_api/generated/__init__.py +21 -0
  5. sdkrouter/_api/generated/cdn/__init__.py +209 -0
  6. sdkrouter/_api/generated/cdn/cdn__api__cdn/__init__.py +7 -0
  7. sdkrouter/_api/generated/cdn/cdn__api__cdn/client.py +133 -0
  8. sdkrouter/_api/generated/cdn/cdn__api__cdn/models.py +163 -0
  9. sdkrouter/_api/generated/cdn/cdn__api__cdn/sync_client.py +132 -0
  10. sdkrouter/_api/generated/cdn/client.py +75 -0
  11. sdkrouter/_api/generated/cdn/logger.py +256 -0
  12. sdkrouter/_api/generated/cdn/pyproject.toml +55 -0
  13. sdkrouter/_api/generated/cdn/retry.py +272 -0
  14. sdkrouter/_api/generated/cdn/sync_client.py +58 -0
  15. sdkrouter/_api/generated/cleaner/__init__.py +212 -0
  16. sdkrouter/_api/generated/cleaner/cleaner__api__cleaner/__init__.py +7 -0
  17. sdkrouter/_api/generated/cleaner/cleaner__api__cleaner/client.py +83 -0
  18. sdkrouter/_api/generated/cleaner/cleaner__api__cleaner/models.py +117 -0
  19. sdkrouter/_api/generated/cleaner/cleaner__api__cleaner/sync_client.py +82 -0
  20. sdkrouter/_api/generated/cleaner/client.py +75 -0
  21. sdkrouter/_api/generated/cleaner/enums.py +55 -0
  22. sdkrouter/_api/generated/cleaner/logger.py +256 -0
  23. sdkrouter/_api/generated/cleaner/pyproject.toml +55 -0
  24. sdkrouter/_api/generated/cleaner/retry.py +272 -0
  25. sdkrouter/_api/generated/cleaner/sync_client.py +58 -0
  26. sdkrouter/_api/generated/keys/__init__.py +212 -0
  27. sdkrouter/_api/generated/keys/client.py +75 -0
  28. sdkrouter/_api/generated/keys/enums.py +64 -0
  29. sdkrouter/_api/generated/keys/keys__api__keys/__init__.py +7 -0
  30. sdkrouter/_api/generated/keys/keys__api__keys/client.py +150 -0
  31. sdkrouter/_api/generated/keys/keys__api__keys/models.py +152 -0
  32. sdkrouter/_api/generated/keys/keys__api__keys/sync_client.py +149 -0
  33. sdkrouter/_api/generated/keys/logger.py +256 -0
  34. sdkrouter/_api/generated/keys/pyproject.toml +55 -0
  35. sdkrouter/_api/generated/keys/retry.py +272 -0
  36. sdkrouter/_api/generated/keys/sync_client.py +58 -0
  37. sdkrouter/_api/generated/models/__init__.py +209 -0
  38. sdkrouter/_api/generated/models/client.py +75 -0
  39. sdkrouter/_api/generated/models/logger.py +256 -0
  40. sdkrouter/_api/generated/models/models__api__llm_models/__init__.py +7 -0
  41. sdkrouter/_api/generated/models/models__api__llm_models/client.py +99 -0
  42. sdkrouter/_api/generated/models/models__api__llm_models/models.py +206 -0
  43. sdkrouter/_api/generated/models/models__api__llm_models/sync_client.py +99 -0
  44. sdkrouter/_api/generated/models/pyproject.toml +55 -0
  45. sdkrouter/_api/generated/models/retry.py +272 -0
  46. sdkrouter/_api/generated/models/sync_client.py +58 -0
  47. sdkrouter/_api/generated/shortlinks/__init__.py +209 -0
  48. sdkrouter/_api/generated/shortlinks/client.py +75 -0
  49. sdkrouter/_api/generated/shortlinks/logger.py +256 -0
  50. sdkrouter/_api/generated/shortlinks/pyproject.toml +55 -0
  51. sdkrouter/_api/generated/shortlinks/retry.py +272 -0
  52. sdkrouter/_api/generated/shortlinks/shortlinks__api__shortlinks/__init__.py +7 -0
  53. sdkrouter/_api/generated/shortlinks/shortlinks__api__shortlinks/client.py +137 -0
  54. sdkrouter/_api/generated/shortlinks/shortlinks__api__shortlinks/models.py +153 -0
  55. sdkrouter/_api/generated/shortlinks/shortlinks__api__shortlinks/sync_client.py +136 -0
  56. sdkrouter/_api/generated/shortlinks/sync_client.py +58 -0
  57. sdkrouter/_api/generated/vision/__init__.py +212 -0
  58. sdkrouter/_api/generated/vision/client.py +75 -0
  59. sdkrouter/_api/generated/vision/enums.py +40 -0
  60. sdkrouter/_api/generated/vision/logger.py +256 -0
  61. sdkrouter/_api/generated/vision/pyproject.toml +55 -0
  62. sdkrouter/_api/generated/vision/retry.py +272 -0
  63. sdkrouter/_api/generated/vision/sync_client.py +58 -0
  64. sdkrouter/_api/generated/vision/vision__api__vision/__init__.py +7 -0
  65. sdkrouter/_api/generated/vision/vision__api__vision/client.py +65 -0
  66. sdkrouter/_api/generated/vision/vision__api__vision/models.py +138 -0
  67. sdkrouter/_api/generated/vision/vision__api__vision/sync_client.py +65 -0
  68. sdkrouter/_client.py +432 -0
  69. sdkrouter/_config.py +74 -0
  70. sdkrouter/_constants.py +21 -0
  71. sdkrouter/_internal/__init__.py +1 -0
  72. sdkrouter/_types/__init__.py +30 -0
  73. sdkrouter/_types/cdn.py +27 -0
  74. sdkrouter/_types/models.py +26 -0
  75. sdkrouter/_types/ocr.py +24 -0
  76. sdkrouter/_types/parsed.py +101 -0
  77. sdkrouter/_types/shortlinks.py +27 -0
  78. sdkrouter/_types/vision.py +29 -0
  79. sdkrouter/_version.py +3 -0
  80. sdkrouter/helpers/__init__.py +13 -0
  81. sdkrouter/helpers/formatting.py +15 -0
  82. sdkrouter/helpers/html.py +100 -0
  83. sdkrouter/helpers/json_cleaner.py +53 -0
  84. sdkrouter/tools/__init__.py +129 -0
  85. sdkrouter/tools/cdn.py +285 -0
  86. sdkrouter/tools/cleaner.py +186 -0
  87. sdkrouter/tools/keys.py +215 -0
  88. sdkrouter/tools/models.py +196 -0
  89. sdkrouter/tools/shortlinks.py +165 -0
  90. sdkrouter/tools/vision.py +173 -0
  91. sdkrouter/utils/__init__.py +27 -0
  92. sdkrouter/utils/parsing.py +109 -0
  93. sdkrouter/utils/tokens.py +375 -0
  94. sdkrouter-0.1.1.dist-info/METADATA +411 -0
  95. sdkrouter-0.1.1.dist-info/RECORD +96 -0
  96. sdkrouter-0.1.1.dist-info/WHEEL +4 -0
sdkrouter/_client.py ADDED
@@ -0,0 +1,432 @@
1
+ """Main SDK client classes."""
2
+
3
+ import json
4
+ import os
5
+ from typing import Any, Optional, Type
6
+
7
+ from openai import AsyncOpenAI, OpenAI
8
+
9
+ from ._config import SDKConfig
10
+ from ._constants import (
11
+ DEFAULT_BASE_URL,
12
+ DEFAULT_MAX_RETRIES,
13
+ DEFAULT_OPENROUTER_URL,
14
+ DEFAULT_TIMEOUT,
15
+ ENV_API_KEY,
16
+ ENV_OPENROUTER_KEY,
17
+ )
18
+ from ._types.parsed import ParsedChatCompletion, ParsedChoice
19
+ from .tools.vision import AsyncVisionResource, VisionResource
20
+ from .tools.cdn import AsyncCDNResource, CDNResource
21
+ from .tools.shortlinks import AsyncShortlinksResource, ShortlinksResource
22
+ from .tools.keys import AsyncKeysResource, KeysResource
23
+ from .tools.cleaner import AsyncCleanerResource, CleanerResource
24
+ from .tools.models import AsyncModelsResource, ModelsResource
25
+ from .utils.parsing import ResponseFormatT, type_to_response_format
26
+
27
+
28
+ def _parse_completion(
29
+ raw_response: Any,
30
+ response_format: Type[ResponseFormatT],
31
+ ) -> ParsedChatCompletion[ResponseFormatT]:
32
+ """
33
+ Parse raw ChatCompletion into ParsedChatCompletion with typed content.
34
+
35
+ Args:
36
+ raw_response: Raw ChatCompletion from OpenAI API
37
+ response_format: Pydantic model class for parsing
38
+
39
+ Returns:
40
+ ParsedChatCompletion with parsed content in each choice
41
+ """
42
+ parsed_choices: list[ParsedChoice[ResponseFormatT]] = []
43
+
44
+ for choice in raw_response.choices:
45
+ parsed_content = None
46
+ content = getattr(choice.message, "content", None)
47
+
48
+ if content:
49
+ try:
50
+ data = json.loads(content)
51
+ parsed_content = response_format.model_validate(data)
52
+ except (json.JSONDecodeError, Exception):
53
+ # If parsing fails, leave parsed as None
54
+ pass
55
+
56
+ parsed_choices.append(
57
+ ParsedChoice.from_choice(choice, parsed_content=parsed_content)
58
+ )
59
+
60
+ return ParsedChatCompletion.from_completion(raw_response, parsed_choices)
61
+
62
+
63
+ class SDKRouter(OpenAI):
64
+ """
65
+ Main SDK client - OpenAI-compatible with additional tools.
66
+
67
+ Extends OpenAI client with vision, OCR, CDN, and shortlinks tools.
68
+
69
+ Example:
70
+ ```python
71
+ from sdkrouter import SDKRouter
72
+
73
+ client = SDKRouter(api_key="your-api-key")
74
+
75
+ # OpenAI-compatible chat
76
+ response = client.chat.completions.create(
77
+ model="openai/gpt-4o",
78
+ messages=[{"role": "user", "content": "Hello!"}],
79
+ )
80
+
81
+ # Vision analysis
82
+ result = client.vision.analyze(image_url="https://example.com/image.jpg")
83
+
84
+ # CDN upload
85
+ file = client.cdn.upload(Path("image.png"))
86
+ ```
87
+ """
88
+
89
+ def __init__(
90
+ self,
91
+ *,
92
+ api_key: Optional[str] = None,
93
+ base_url: Optional[str] = None,
94
+ use_self_hosted: bool = True,
95
+ openrouter_api_key: Optional[str] = None,
96
+ timeout: float = DEFAULT_TIMEOUT,
97
+ max_retries: int = DEFAULT_MAX_RETRIES,
98
+ **kwargs,
99
+ ):
100
+ """
101
+ Initialize SDKRouter client.
102
+
103
+ Args:
104
+ api_key: API key (or set SDKROUTER_API_KEY env var)
105
+ base_url: Base URL for API (default: ai.sdkrouter.com)
106
+ use_self_hosted: If True, use self-hosted proxy. If False, direct to OpenRouter
107
+ openrouter_api_key: OpenRouter API key (for direct mode)
108
+ timeout: Request timeout in seconds
109
+ max_retries: Number of retries for failed requests
110
+ **kwargs: Additional arguments passed to OpenAI client
111
+ """
112
+ # Resolve API key
113
+ resolved_key = (
114
+ api_key
115
+ or os.getenv(ENV_API_KEY)
116
+ or openrouter_api_key
117
+ or os.getenv(ENV_OPENROUTER_KEY)
118
+ )
119
+ if not resolved_key:
120
+ raise ValueError(
121
+ "API key required. Set SDKROUTER_API_KEY environment variable "
122
+ "or pass api_key= parameter."
123
+ )
124
+
125
+ # Resolve base URL
126
+ if use_self_hosted:
127
+ resolved_url = base_url or os.getenv("SDKROUTER_BASE_URL") or DEFAULT_BASE_URL
128
+ else:
129
+ resolved_url = DEFAULT_OPENROUTER_URL
130
+
131
+ # Initialize OpenAI client
132
+ super().__init__(
133
+ api_key=resolved_key,
134
+ base_url=resolved_url,
135
+ timeout=timeout,
136
+ max_retries=max_retries,
137
+ **kwargs,
138
+ )
139
+
140
+ # Store configuration
141
+ self._sdk_config = SDKConfig(
142
+ api_key=resolved_key,
143
+ base_url=resolved_url,
144
+ use_self_hosted=use_self_hosted,
145
+ timeout=timeout,
146
+ max_retries=max_retries,
147
+ )
148
+
149
+ # Lazy-loaded tool resources
150
+ self._vision: Optional[VisionResource] = None
151
+ self._cdn: Optional[CDNResource] = None
152
+ self._shortlinks: Optional[ShortlinksResource] = None
153
+ self._keys: Optional[KeysResource] = None
154
+ self._cleaner: Optional[CleanerResource] = None
155
+ self._models: Optional[ModelsResource] = None
156
+
157
+ @property
158
+ def vision(self) -> VisionResource:
159
+ """Vision analysis and OCR tool."""
160
+ if self._vision is None:
161
+ self._vision = VisionResource(self._sdk_config)
162
+ return self._vision
163
+
164
+ @property
165
+ def cdn(self) -> CDNResource:
166
+ """CDN file storage tool."""
167
+ if self._cdn is None:
168
+ self._cdn = CDNResource(self._sdk_config)
169
+ return self._cdn
170
+
171
+ @property
172
+ def shortlinks(self) -> ShortlinksResource:
173
+ """URL shortening tool."""
174
+ if self._shortlinks is None:
175
+ self._shortlinks = ShortlinksResource(self._sdk_config)
176
+ return self._shortlinks
177
+
178
+ @property
179
+ def keys(self) -> KeysResource:
180
+ """API keys management tool."""
181
+ if self._keys is None:
182
+ self._keys = KeysResource(self._sdk_config)
183
+ return self._keys
184
+
185
+ @property
186
+ def cleaner(self) -> CleanerResource:
187
+ """HTML cleaner tool."""
188
+ if self._cleaner is None:
189
+ self._cleaner = CleanerResource(self._sdk_config)
190
+ return self._cleaner
191
+
192
+ @property
193
+ def models(self) -> ModelsResource:
194
+ """LLM models listing tool."""
195
+ if self._models is None:
196
+ self._models = ModelsResource(self._sdk_config)
197
+ return self._models
198
+
199
+ @property
200
+ def config(self) -> SDKConfig:
201
+ """SDK configuration."""
202
+ return self._sdk_config
203
+
204
+ def parse(
205
+ self,
206
+ *,
207
+ model: str,
208
+ messages: list[dict[str, Any]],
209
+ response_format: Type[ResponseFormatT],
210
+ **kwargs: Any,
211
+ ) -> ParsedChatCompletion[ResponseFormatT]:
212
+ """
213
+ Create chat completion with structured output.
214
+
215
+ Automatically converts Pydantic model to JSON Schema and parses
216
+ the response back into the model.
217
+
218
+ Args:
219
+ model: Model ID (e.g., "openai/gpt-4o")
220
+ messages: Chat messages
221
+ response_format: Pydantic model class for response parsing
222
+ **kwargs: Additional parameters (temperature, max_tokens, etc.)
223
+
224
+ Returns:
225
+ ParsedChatCompletion with .choices[0].message.parsed
226
+
227
+ Example:
228
+ ```python
229
+ class MathResponse(BaseModel):
230
+ steps: list[str]
231
+ answer: float
232
+
233
+ result = client.parse(
234
+ model="openai/gpt-4o",
235
+ messages=[{"role": "user", "content": "Solve 2+2"}],
236
+ response_format=MathResponse,
237
+ )
238
+ print(result.choices[0].message.parsed.answer)
239
+ ```
240
+ """
241
+ # Convert Pydantic model to response_format
242
+ format_param = type_to_response_format(response_format)
243
+
244
+ # Make API call
245
+ raw_response = self.chat.completions.create(
246
+ model=model,
247
+ messages=messages,
248
+ response_format=format_param,
249
+ **kwargs,
250
+ )
251
+
252
+ # Parse response
253
+ return _parse_completion(raw_response, response_format)
254
+
255
+
256
+ class AsyncSDKRouter(AsyncOpenAI):
257
+ """
258
+ Async SDK client - OpenAI-compatible with additional tools.
259
+
260
+ Async version of SDKRouter for use in async contexts.
261
+
262
+ Example:
263
+ ```python
264
+ from sdkrouter import AsyncSDKRouter
265
+
266
+ client = AsyncSDKRouter(api_key="your-api-key")
267
+
268
+ # Async chat
269
+ response = await client.chat.completions.create(
270
+ model="openai/gpt-4o",
271
+ messages=[{"role": "user", "content": "Hello!"}],
272
+ )
273
+
274
+ # Async vision
275
+ result = await client.vision.analyze(image_url="https://example.com/image.jpg")
276
+ ```
277
+ """
278
+
279
+ def __init__(
280
+ self,
281
+ *,
282
+ api_key: Optional[str] = None,
283
+ base_url: Optional[str] = None,
284
+ use_self_hosted: bool = True,
285
+ openrouter_api_key: Optional[str] = None,
286
+ timeout: float = DEFAULT_TIMEOUT,
287
+ max_retries: int = DEFAULT_MAX_RETRIES,
288
+ **kwargs,
289
+ ):
290
+ """Initialize async SDKRouter client."""
291
+ # Resolve API key
292
+ resolved_key = (
293
+ api_key
294
+ or os.getenv(ENV_API_KEY)
295
+ or openrouter_api_key
296
+ or os.getenv(ENV_OPENROUTER_KEY)
297
+ )
298
+ if not resolved_key:
299
+ raise ValueError(
300
+ "API key required. Set SDKROUTER_API_KEY environment variable "
301
+ "or pass api_key= parameter."
302
+ )
303
+
304
+ # Resolve base URL
305
+ if use_self_hosted:
306
+ resolved_url = base_url or os.getenv("SDKROUTER_BASE_URL") or DEFAULT_BASE_URL
307
+ else:
308
+ resolved_url = DEFAULT_OPENROUTER_URL
309
+
310
+ # Initialize AsyncOpenAI client
311
+ super().__init__(
312
+ api_key=resolved_key,
313
+ base_url=resolved_url,
314
+ timeout=timeout,
315
+ max_retries=max_retries,
316
+ **kwargs,
317
+ )
318
+
319
+ # Store configuration
320
+ self._sdk_config = SDKConfig(
321
+ api_key=resolved_key,
322
+ base_url=resolved_url,
323
+ use_self_hosted=use_self_hosted,
324
+ timeout=timeout,
325
+ max_retries=max_retries,
326
+ )
327
+
328
+ # Lazy-loaded async tool resources
329
+ self._vision: Optional[AsyncVisionResource] = None
330
+ self._cdn: Optional[AsyncCDNResource] = None
331
+ self._shortlinks: Optional[AsyncShortlinksResource] = None
332
+ self._keys: Optional[AsyncKeysResource] = None
333
+ self._cleaner: Optional[AsyncCleanerResource] = None
334
+ self._models: Optional[AsyncModelsResource] = None
335
+
336
+ @property
337
+ def vision(self) -> AsyncVisionResource:
338
+ """Vision analysis and OCR tool (async)."""
339
+ if self._vision is None:
340
+ self._vision = AsyncVisionResource(self._sdk_config)
341
+ return self._vision
342
+
343
+ @property
344
+ def cdn(self) -> AsyncCDNResource:
345
+ """CDN file storage tool (async)."""
346
+ if self._cdn is None:
347
+ self._cdn = AsyncCDNResource(self._sdk_config)
348
+ return self._cdn
349
+
350
+ @property
351
+ def shortlinks(self) -> AsyncShortlinksResource:
352
+ """URL shortening tool (async)."""
353
+ if self._shortlinks is None:
354
+ self._shortlinks = AsyncShortlinksResource(self._sdk_config)
355
+ return self._shortlinks
356
+
357
+ @property
358
+ def keys(self) -> AsyncKeysResource:
359
+ """API keys management tool (async)."""
360
+ if self._keys is None:
361
+ self._keys = AsyncKeysResource(self._sdk_config)
362
+ return self._keys
363
+
364
+ @property
365
+ def cleaner(self) -> AsyncCleanerResource:
366
+ """HTML cleaner tool (async)."""
367
+ if self._cleaner is None:
368
+ self._cleaner = AsyncCleanerResource(self._sdk_config)
369
+ return self._cleaner
370
+
371
+ @property
372
+ def models(self) -> AsyncModelsResource:
373
+ """LLM models listing tool (async)."""
374
+ if self._models is None:
375
+ self._models = AsyncModelsResource(self._sdk_config)
376
+ return self._models
377
+
378
+ @property
379
+ def config(self) -> SDKConfig:
380
+ """SDK configuration."""
381
+ return self._sdk_config
382
+
383
+ async def parse(
384
+ self,
385
+ *,
386
+ model: str,
387
+ messages: list[dict[str, Any]],
388
+ response_format: Type[ResponseFormatT],
389
+ **kwargs: Any,
390
+ ) -> ParsedChatCompletion[ResponseFormatT]:
391
+ """
392
+ Create async chat completion with structured output.
393
+
394
+ Automatically converts Pydantic model to JSON Schema and parses
395
+ the response back into the model.
396
+
397
+ Args:
398
+ model: Model ID (e.g., "openai/gpt-4o")
399
+ messages: Chat messages
400
+ response_format: Pydantic model class for response parsing
401
+ **kwargs: Additional parameters (temperature, max_tokens, etc.)
402
+
403
+ Returns:
404
+ ParsedChatCompletion with .choices[0].message.parsed
405
+
406
+ Example:
407
+ ```python
408
+ class MathResponse(BaseModel):
409
+ steps: list[str]
410
+ answer: float
411
+
412
+ result = await client.parse(
413
+ model="openai/gpt-4o",
414
+ messages=[{"role": "user", "content": "Solve 2+2"}],
415
+ response_format=MathResponse,
416
+ )
417
+ print(result.choices[0].message.parsed.answer)
418
+ ```
419
+ """
420
+ # Convert Pydantic model to response_format
421
+ format_param = type_to_response_format(response_format)
422
+
423
+ # Make API call
424
+ raw_response = await self.chat.completions.create(
425
+ model=model,
426
+ messages=messages,
427
+ response_format=format_param,
428
+ **kwargs,
429
+ )
430
+
431
+ # Parse response
432
+ return _parse_completion(raw_response, response_format)
sdkrouter/_config.py ADDED
@@ -0,0 +1,74 @@
1
+ """SDK configuration."""
2
+
3
+ from typing import Optional
4
+
5
+ from pydantic_settings import BaseSettings, SettingsConfigDict
6
+
7
+ from ._constants import (
8
+ DEFAULT_BASE_URL,
9
+ DEFAULT_CDN_URL,
10
+ DEFAULT_CONNECT_TIMEOUT,
11
+ DEFAULT_DJANGO_URL,
12
+ DEFAULT_MAX_RETRIES,
13
+ DEFAULT_OPENAI_URL,
14
+ DEFAULT_OPENROUTER_URL,
15
+ DEFAULT_TIMEOUT,
16
+ )
17
+
18
+
19
+ class SDKConfig(BaseSettings):
20
+ """SDK configuration with environment variable support."""
21
+
22
+ model_config = SettingsConfigDict(
23
+ env_prefix="SDKROUTER_",
24
+ env_file=".env",
25
+ extra="ignore",
26
+ )
27
+
28
+ # API Keys
29
+ api_key: Optional[str] = None
30
+ openrouter_api_key: Optional[str] = None
31
+ openai_api_key: Optional[str] = None
32
+
33
+ # Base URLs (for self-hosted or direct)
34
+ base_url: str = DEFAULT_BASE_URL
35
+ django_url: str = DEFAULT_DJANGO_URL
36
+ cdn_url: str = DEFAULT_CDN_URL
37
+
38
+ # Direct provider URLs (bypass self-hosted)
39
+ openrouter_url: str = DEFAULT_OPENROUTER_URL
40
+ openai_url: str = DEFAULT_OPENAI_URL
41
+
42
+ # Routing mode
43
+ use_self_hosted: bool = True # False = direct to OpenRouter/OpenAI
44
+
45
+ # Timeouts
46
+ timeout: float = DEFAULT_TIMEOUT
47
+ connect_timeout: float = DEFAULT_CONNECT_TIMEOUT
48
+
49
+ # Retry
50
+ max_retries: int = DEFAULT_MAX_RETRIES
51
+
52
+
53
+ _config: Optional[SDKConfig] = None
54
+
55
+
56
+ def get_config() -> SDKConfig:
57
+ """Get or create SDK configuration."""
58
+ global _config
59
+ if _config is None:
60
+ _config = SDKConfig()
61
+ return _config
62
+
63
+
64
+ def configure(**kwargs) -> SDKConfig:
65
+ """Configure SDK with custom settings."""
66
+ global _config
67
+ _config = SDKConfig(**kwargs)
68
+ return _config
69
+
70
+
71
+ def reset_config() -> None:
72
+ """Reset configuration to defaults."""
73
+ global _config
74
+ _config = None
@@ -0,0 +1,21 @@
1
+ """SDK constants and defaults."""
2
+
3
+ # Environment variable names
4
+ ENV_API_KEY = "SDKROUTER_API_KEY"
5
+ ENV_OPENROUTER_KEY = "OPENROUTER_API_KEY"
6
+ ENV_OPENAI_KEY = "OPENAI_API_KEY"
7
+ ENV_BASE_URL = "SDKROUTER_BASE_URL"
8
+ ENV_DJANGO_URL = "SDKROUTER_DJANGO_URL"
9
+ ENV_CDN_URL = "SDKROUTER_CDN_URL"
10
+
11
+ # Default URLs
12
+ DEFAULT_BASE_URL = "https://ai.sdkrouter.com/v1"
13
+ DEFAULT_DJANGO_URL = "https://api.sdkrouter.com"
14
+ DEFAULT_CDN_URL = "https://cdn.sdkrouter.com"
15
+ DEFAULT_OPENROUTER_URL = "https://openrouter.ai/api/v1"
16
+ DEFAULT_OPENAI_URL = "https://api.openai.com/v1"
17
+
18
+ # Default settings
19
+ DEFAULT_TIMEOUT = 60.0
20
+ DEFAULT_CONNECT_TIMEOUT = 5.0
21
+ DEFAULT_MAX_RETRIES = 2
@@ -0,0 +1 @@
1
+ """Internal helpers (not part of public API)."""
@@ -0,0 +1,30 @@
1
+ """Type definitions for SDK resources."""
2
+
3
+ from .cdn import CDNFile, CDNUploadRequest
4
+ from .models import Model, ModelPricing
5
+ from .ocr import OCRRequest, OCRResponse
6
+ from .parsed import ParsedChatCompletion, ParsedChoice, ParsedMessage
7
+ from .shortlinks import ShortLink, ShortLinkCreateRequest
8
+ from .vision import VisionAnalyzeRequest, VisionAnalyzeResponse
9
+
10
+ __all__ = [
11
+ # CDN
12
+ "CDNFile",
13
+ "CDNUploadRequest",
14
+ # Models
15
+ "Model",
16
+ "ModelPricing",
17
+ # OCR
18
+ "OCRRequest",
19
+ "OCRResponse",
20
+ # Parsed (structured output)
21
+ "ParsedChatCompletion",
22
+ "ParsedChoice",
23
+ "ParsedMessage",
24
+ # Shortlinks
25
+ "ShortLink",
26
+ "ShortLinkCreateRequest",
27
+ # Vision
28
+ "VisionAnalyzeRequest",
29
+ "VisionAnalyzeResponse",
30
+ ]
@@ -0,0 +1,27 @@
1
+ """CDN types."""
2
+
3
+ from datetime import datetime
4
+ from typing import Optional
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class CDNUploadRequest(BaseModel):
10
+ """Request for CDN file upload."""
11
+
12
+ filename: Optional[str] = Field(default=None, description="Original filename")
13
+ content_type: Optional[str] = Field(default=None, description="MIME type")
14
+ ttl: str = Field(default="30d", description="Time to live (e.g., '7d', '30d', '1y')")
15
+
16
+
17
+ class CDNFile(BaseModel):
18
+ """CDN file information."""
19
+
20
+ uuid: str = Field(description="Unique file identifier")
21
+ short_code: str = Field(description="Short code for URL")
22
+ filename: str = Field(description="Original filename")
23
+ content_type: str = Field(description="MIME type")
24
+ size_bytes: int = Field(description="File size in bytes")
25
+ url: str = Field(description="Full URL to access file")
26
+ expires_at: Optional[datetime] = Field(default=None, description="Expiration time")
27
+ created_at: Optional[datetime] = Field(default=None, description="Upload time")
@@ -0,0 +1,26 @@
1
+ """Model types."""
2
+
3
+ from typing import Optional
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class ModelPricing(BaseModel):
9
+ """Model pricing information."""
10
+
11
+ prompt: float = Field(description="Cost per 1M input tokens")
12
+ completion: float = Field(description="Cost per 1M output tokens")
13
+ image: Optional[float] = Field(default=None, description="Cost per image")
14
+
15
+
16
+ class Model(BaseModel):
17
+ """LLM model information."""
18
+
19
+ id: str = Field(description="Model identifier")
20
+ name: str = Field(description="Display name")
21
+ provider: str = Field(description="Provider name")
22
+ context_length: int = Field(description="Max context length")
23
+ pricing: ModelPricing = Field(description="Pricing information")
24
+ supports_vision: bool = Field(default=False, description="Supports image input")
25
+ supports_tools: bool = Field(default=False, description="Supports function calling")
26
+ supports_json: bool = Field(default=False, description="Supports JSON mode")
@@ -0,0 +1,24 @@
1
+ """OCR types."""
2
+
3
+ from typing import Literal, Optional
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class OCRRequest(BaseModel):
9
+ """Request for OCR text extraction."""
10
+
11
+ image: Optional[str] = Field(default=None, description="Base64 encoded image")
12
+ image_url: Optional[str] = Field(default=None, description="URL of image")
13
+ mode: Literal["tiny", "small", "base", "gundam"] = Field(
14
+ default="base", description="OCR model size/quality"
15
+ )
16
+
17
+
18
+ class OCRResponse(BaseModel):
19
+ """Response from OCR extraction."""
20
+
21
+ text: str = Field(description="Extracted text")
22
+ model: str = Field(description="Model used")
23
+ cost_usd: float = Field(description="Cost in USD")
24
+ cached: bool = Field(default=False, description="Whether result was from cache")