diff --git a/.speakeasy/gen.lock b/.speakeasy/gen.lock index 7ec3e1d..a38a993 100644 --- a/.speakeasy/gen.lock +++ b/.speakeasy/gen.lock @@ -1,12 +1,12 @@ lockVersion: 2.0.0 id: cfd52247-6a25-4c6d-bbce-fe6fce0cd69d management: - docChecksum: 4cc4a9d9115d0eca9a5eb4786505579d + docChecksum: df8176ff428f8885f4b799a5b5492507 docVersion: 1.0.0 - speakeasyVersion: 1.658.2 - generationVersion: 2.755.9 - releaseVersion: 0.0.19 - configChecksum: 834d5c1cea368e35ab6535ffb2bf9e7a + speakeasyVersion: 1.662.0 + generationVersion: 2.763.3 + releaseVersion: 0.1.0 + configChecksum: 3507566ac94068cc19518236eeba966a repoURL: https://github.com/OpenRouterTeam/python-sdk.git installationURL: https://github.com/OpenRouterTeam/python-sdk.git published: true @@ -15,7 +15,7 @@ features: acceptHeaders: 3.0.0 additionalDependencies: 1.0.0 constsAndDefaults: 1.0.5 - core: 5.23.9 + core: 5.23.11 defaultEnabledRetries: 0.2.0 deprecations: 3.0.2 devContainers: 3.0.0 @@ -27,6 +27,7 @@ features: globalSecurityCallbacks: 1.0.0 globalSecurityFlattening: 1.0.0 globalServerURLs: 3.2.0 + globals: 3.0.0 groups: 3.0.1 methodArguments: 1.0.2 methodSecurity: 3.0.1 @@ -34,7 +35,7 @@ features: nullables: 1.0.1 openEnums: 1.0.1 responseFormat: 1.0.1 - retries: 3.0.2 + retries: 3.0.3 sdkHooks: 1.1.0 serverEvents: 1.0.11 serverEventsSentinels: 0.1.0 @@ -67,12 +68,14 @@ generatedFiles: - docs/components/chatmessagecontentitemaudio.md - docs/components/chatmessagecontentitemaudioformat.md - docs/components/chatmessagecontentitemaudioinputaudio.md + - docs/components/chatmessagecontentitemfile.md - docs/components/chatmessagecontentitemimage.md - docs/components/chatmessagecontentitemimagedetail.md - docs/components/chatmessagecontentitemtext.md - docs/components/chatmessagecontentitemvideo.md - docs/components/chatmessagecontentitemvideoinputvideo.md - docs/components/chatmessagecontentitemvideovideourl.md + - docs/components/chatmessagecontentitemvideovideourlvideourl.md - docs/components/chatmessagetokenlogprob.md - docs/components/chatmessagetokenlogprobs.md - docs/components/chatmessagetoolcall.md @@ -110,6 +113,7 @@ generatedFiles: - docs/components/effort.md - docs/components/endpointstatus.md - docs/components/engine.md + - docs/components/file.md - docs/components/filecitation.md - docs/components/filecitationtype.md - docs/components/filepath.md @@ -416,8 +420,7 @@ generatedFiles: - docs/components/usermessage.md - docs/components/usermessagecontent.md - docs/components/variables.md - - docs/components/videourl1.md - - docs/components/videourl2.md + - docs/components/videourl.md - docs/components/websearchpreviewtooluserlocation.md - docs/components/websearchpreviewtooluserlocationtype.md - docs/components/websearchstatus.md @@ -436,6 +439,7 @@ generatedFiles: - docs/errors/toomanyrequestsresponseerror.md - docs/errors/unauthorizedresponseerror.md - docs/errors/unprocessableentityresponseerror.md + - docs/models/internal/globals.md - docs/models/utils/retryconfig.md - docs/operations/apitype.md - docs/operations/calldata.md @@ -554,6 +558,7 @@ generatedFiles: - src/openrouter/components/chatgenerationtokenusage.py - src/openrouter/components/chatmessagecontentitem.py - src/openrouter/components/chatmessagecontentitemaudio.py + - src/openrouter/components/chatmessagecontentitemfile.py - src/openrouter/components/chatmessagecontentitemimage.py - src/openrouter/components/chatmessagecontentitemtext.py - src/openrouter/components/chatmessagecontentitemvideo.py @@ -684,6 +689,7 @@ generatedFiles: - src/openrouter/components/unprocessableentityresponseerrordata.py - src/openrouter/components/urlcitation.py - src/openrouter/components/usermessage.py + - src/openrouter/components/videourl.py - src/openrouter/components/websearchpreviewtooluserlocation.py - src/openrouter/components/websearchstatus.py - src/openrouter/credits.py @@ -711,6 +717,9 @@ generatedFiles: - src/openrouter/errors/unprocessableentityresponse_error.py - src/openrouter/generations.py - src/openrouter/httpclient.py + - src/openrouter/models/__init__.py + - src/openrouter/models/internal/__init__.py + - src/openrouter/models/internal/globals.py - src/openrouter/models_.py - src/openrouter/oauth.py - src/openrouter/operations/__init__.py @@ -1104,3 +1113,4 @@ examples: "500": application/json: {"error": {"code": "", "message": ""}} examplesVersion: 1.0.2 +releaseNotes: "## Python SDK Changes Detected:\n* `open_router.chat.send()`: \n * `request.messages.[].[user].content.[array].[].[video_url]` **Added**\n * `response.choices.[].message.content.[array].[].[video_url]` **Added**\n" diff --git a/.speakeasy/gen.yaml b/.speakeasy/gen.yaml index c68359b..8effb83 100644 --- a/.speakeasy/gen.yaml +++ b/.speakeasy/gen.yaml @@ -30,7 +30,7 @@ generation: generateNewTests: true skipResponseBodyAssertions: false python: - version: 0.0.19 + version: 0.1.0 additionalDependencies: dev: {} main: {} @@ -62,6 +62,7 @@ python: operations: operations shared: components webhooks: "" + inferUnionDiscriminators: true inputModelSuffix: input legacyPyright: false license: Apache-2.0 diff --git a/.speakeasy/out.openapi.yaml b/.speakeasy/out.openapi.yaml index 2987de9..1dd123b 100644 --- a/.speakeasy/out.openapi.yaml +++ b/.speakeasy/out.openapi.yaml @@ -4669,12 +4669,7 @@ components: type: string const: input_video video_url: - type: object - properties: - url: - type: string - required: - - url + $ref: '#/components/schemas/VideoURL' required: - type - video_url @@ -4694,12 +4689,40 @@ components: - type - video_url type: object + VideoURL: + type: object + properties: + url: + type: string + required: + - url + ChatMessageContentItemFile: + type: object + properties: + type: + type: string + const: file + file: + type: object + properties: + file_data: + type: string + file_id: + type: string + filename: + type: string + required: + - file_data + required: + - type + - file ChatMessageContentItem: oneOf: - $ref: '#/components/schemas/ChatMessageContentItemText' - $ref: '#/components/schemas/ChatMessageContentItemImage' - $ref: '#/components/schemas/ChatMessageContentItemAudio' - $ref: '#/components/schemas/ChatMessageContentItemVideo' + - $ref: '#/components/schemas/ChatMessageContentItemFile' type: object discriminator: propertyName: type @@ -4709,6 +4732,7 @@ components: input_audio: '#/components/schemas/ChatMessageContentItemAudio' input_video: '#/components/schemas/ChatMessageContentItemVideo' video_url: '#/components/schemas/ChatMessageContentItemVideo' + file: '#/components/schemas/ChatMessageContentItemFile' ChatMessageToolCall: type: object properties: @@ -5626,7 +5650,7 @@ components: - model - choices additionalProperties: false - parameters: {} + parameters: {AppIdentifier: {name: HTTP-Referer, in: header, schema: {type: string}, description: "The app identifier should be your app's URL and is used as the primary identifier for rankings.\nThis is used to track API usage per application.\n"}, AppDisplayName: {name: X-Title, in: header, schema: {type: string}, description: "The app display name allows you to customize how your app appears in OpenRouter's dashboard.\n"}} securitySchemes: apiKey: type: http @@ -8385,3 +8409,9 @@ x-retry-strategy: initialDelay: 500 maxDelay: 60000 maxAttempts: 3 +x-speakeasy-globals: + parameters: + - $ref: "#/components/parameters/AppIdentifier" + - $ref: "#/components/parameters/AppDisplayName" + - $ref: "#/components/parameters/AppIdentifier" + - $ref: "#/components/parameters/AppDisplayName" diff --git a/.speakeasy/workflow.lock b/.speakeasy/workflow.lock index e8a1112..7317841 100644 --- a/.speakeasy/workflow.lock +++ b/.speakeasy/workflow.lock @@ -1,17 +1,18 @@ -speakeasyVersion: 1.658.2 +speakeasyVersion: 1.662.0 sources: -OAS: sourceNamespace: open-router-chat-completions-api - sourceRevisionDigest: sha256:fc0c90dc8ebb69ef4a571e4e63dcd7f8f33c3254c17ad2583b628fbbf3d1ac05 - sourceBlobDigest: sha256:635d63fd18db468c1dcc23700382af788de76594ba3b8292fae9b14c0e5c22c4 + sourceRevisionDigest: sha256:01256c8494de6bfc13c36d82ae316a6a13d402194f844618bcd4d59e34f325f3 + sourceBlobDigest: sha256:4c80e48fd5e1cd030e68d664eb93984b4d5946867252ff1755a2bd2a05eccd4e tags: - latest + - speakeasy-sdk-regen-1763684529 targets: open-router: source: -OAS sourceNamespace: open-router-chat-completions-api - sourceRevisionDigest: sha256:fc0c90dc8ebb69ef4a571e4e63dcd7f8f33c3254c17ad2583b628fbbf3d1ac05 - sourceBlobDigest: sha256:635d63fd18db468c1dcc23700382af788de76594ba3b8292fae9b14c0e5c22c4 + sourceRevisionDigest: sha256:01256c8494de6bfc13c36d82ae316a6a13d402194f844618bcd4d59e34f325f3 + sourceBlobDigest: sha256:4c80e48fd5e1cd030e68d664eb93984b4d5946867252ff1755a2bd2a05eccd4e workflow: workflowVersion: 1.0.0 speakeasyVersion: latest diff --git a/RELEASES.md b/RELEASES.md index 0d0d01f..d0af6fa 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -8,4 +8,14 @@ Based on: ### Generated - [python v0.0.16] . ### Releases -- [PyPI v0.0.16] https://pypi.org/project/openrouter/0.0.16 - . \ No newline at end of file +- [PyPI v0.0.16] https://pypi.org/project/openrouter/0.0.16 - . + +## 2025-11-27 00:22:11 +### Changes +Based on: +- OpenAPI Doc +- Speakeasy CLI 1.662.0 (2.763.3) https://github.com/speakeasy-api/speakeasy +### Generated +- [python v0.1.0] . +### Releases +- [PyPI v0.1.0] https://pypi.org/project/openrouter/0.1.0 - . \ No newline at end of file diff --git a/docs/components/chatmessagecontentitem.md b/docs/components/chatmessagecontentitem.md index 7063cdb..2ac5390 100644 --- a/docs/components/chatmessagecontentitem.md +++ b/docs/components/chatmessagecontentitem.md @@ -33,3 +33,9 @@ value: components.ChatMessageContentItemVideo = /* values here */ value: components.ChatMessageContentItemVideo = /* values here */ ``` +### `components.ChatMessageContentItemFile` + +```python +value: components.ChatMessageContentItemFile = /* values here */ +``` + diff --git a/docs/components/chatmessagecontentitemfile.md b/docs/components/chatmessagecontentitemfile.md new file mode 100644 index 0000000..ca73897 --- /dev/null +++ b/docs/components/chatmessagecontentitemfile.md @@ -0,0 +1,9 @@ +# ChatMessageContentItemFile + + +## Fields + +| Field | Type | Required | Description | +| ---------------------------------------- | ---------------------------------------- | ---------------------------------------- | ---------------------------------------- | +| `type` | *Literal["file"]* | :heavy_check_mark: | N/A | +| `file` | [components.File](../components/file.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/components/chatmessagecontentitemvideoinputvideo.md b/docs/components/chatmessagecontentitemvideoinputvideo.md index 4d7dd0e..da28cf1 100644 --- a/docs/components/chatmessagecontentitemvideoinputvideo.md +++ b/docs/components/chatmessagecontentitemvideoinputvideo.md @@ -3,7 +3,7 @@ ## Fields -| Field | Type | Required | Description | -| -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -| `type` | *Literal["input_video"]* | :heavy_check_mark: | N/A | -| `video_url` | [components.VideoURL1](../components/videourl1.md) | :heavy_check_mark: | N/A | \ No newline at end of file +| Field | Type | Required | Description | +| ------------------------------------------------ | ------------------------------------------------ | ------------------------------------------------ | ------------------------------------------------ | +| `type` | *Literal["input_video"]* | :heavy_check_mark: | N/A | +| `video_url` | [components.VideoURL](../components/videourl.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/components/chatmessagecontentitemvideovideourl.md b/docs/components/chatmessagecontentitemvideovideourl.md index 0585b55..023d696 100644 --- a/docs/components/chatmessagecontentitemvideovideourl.md +++ b/docs/components/chatmessagecontentitemvideovideourl.md @@ -3,7 +3,7 @@ ## Fields -| Field | Type | Required | Description | -| -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -| `type` | *Literal["video_url"]* | :heavy_check_mark: | N/A | -| `video_url` | [components.VideoURL2](../components/videourl2.md) | :heavy_check_mark: | N/A | \ No newline at end of file +| Field | Type | Required | Description | +| ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `type` | *Literal["video_url"]* | :heavy_check_mark: | N/A | +| `video_url` | [components.ChatMessageContentItemVideoVideoURLVideoURL](../components/chatmessagecontentitemvideovideourlvideourl.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/docs/components/videourl2.md b/docs/components/chatmessagecontentitemvideovideourlvideourl.md similarity index 85% rename from docs/components/videourl2.md rename to docs/components/chatmessagecontentitemvideovideourlvideourl.md index b7f2463..ee08920 100644 --- a/docs/components/videourl2.md +++ b/docs/components/chatmessagecontentitemvideovideourlvideourl.md @@ -1,4 +1,4 @@ -# VideoURL2 +# ChatMessageContentItemVideoVideoURLVideoURL ## Fields diff --git a/docs/components/file.md b/docs/components/file.md new file mode 100644 index 0000000..d5b3937 --- /dev/null +++ b/docs/components/file.md @@ -0,0 +1,10 @@ +# File + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `file_data` | *str* | :heavy_check_mark: | N/A | +| `file_id` | *Optional[str]* | :heavy_minus_sign: | N/A | +| `filename` | *Optional[str]* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/components/videourl1.md b/docs/components/videourl.md similarity index 95% rename from docs/components/videourl1.md rename to docs/components/videourl.md index 505c186..5bd5dec 100644 --- a/docs/components/videourl1.md +++ b/docs/components/videourl.md @@ -1,4 +1,4 @@ -# VideoURL1 +# VideoURL ## Fields diff --git a/docs/models/internal/globals.md b/docs/models/internal/globals.md new file mode 100644 index 0000000..5399b31 --- /dev/null +++ b/docs/models/internal/globals.md @@ -0,0 +1,9 @@ +# Globals + + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `http_referer` | *Optional[str]* | :heavy_minus_sign: | The app identifier should be your app's URL and is used as the primary identifier for rankings.
This is used to track API usage per application.
| +| `x_title` | *Optional[str]* | :heavy_minus_sign: | The app display name allows you to customize how your app appears in OpenRouter's dashboard.
| \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index e10c064..f34abc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "openrouter" -version = "0.0.19" +version = "0.1.0" description = "Official Python Client SDK for OpenRouter." authors = [{ name = "OpenRouter" },] readme = "README-PYPI.md" diff --git a/src/openrouter/_version.py b/src/openrouter/_version.py index a25d226..a900a2f 100644 --- a/src/openrouter/_version.py +++ b/src/openrouter/_version.py @@ -3,10 +3,10 @@ import importlib.metadata __title__: str = "openrouter" -__version__: str = "0.0.19" +__version__: str = "0.1.0" __openapi_doc_version__: str = "1.0.0" -__gen_version__: str = "2.755.9" -__user_agent__: str = "speakeasy-sdk/python 0.0.19 2.755.9 1.0.0 openrouter" +__gen_version__: str = "2.763.3" +__user_agent__: str = "speakeasy-sdk/python 0.1.0 2.763.3 1.0.0 openrouter" try: if __package__ is not None: diff --git a/src/openrouter/components/__init__.py b/src/openrouter/components/__init__.py index 8a34729..1554012 100644 --- a/src/openrouter/components/__init__.py +++ b/src/openrouter/components/__init__.py @@ -58,6 +58,12 @@ ChatMessageContentItemAudioInputAudioTypedDict, ChatMessageContentItemAudioTypedDict, ) + from .chatmessagecontentitemfile import ( + ChatMessageContentItemFile, + ChatMessageContentItemFileTypedDict, + File, + FileTypedDict, + ) from .chatmessagecontentitemimage import ( ChatMessageContentItemImage, ChatMessageContentItemImageDetail, @@ -76,10 +82,8 @@ ChatMessageContentItemVideoTypedDict, ChatMessageContentItemVideoVideoURL, ChatMessageContentItemVideoVideoURLTypedDict, - VideoURL1, - VideoURL1TypedDict, - VideoURL2, - VideoURL2TypedDict, + ChatMessageContentItemVideoVideoURLVideoURL, + ChatMessageContentItemVideoVideoURLVideoURLTypedDict, ) from .chatmessagetokenlogprob import ( ChatMessageTokenLogprob, @@ -780,6 +784,7 @@ UserMessageContentTypedDict, UserMessageTypedDict, ) + from .videourl import VideoURL, VideoURLTypedDict from .websearchpreviewtooluserlocation import ( WebSearchPreviewToolUserLocation, WebSearchPreviewToolUserLocationType, @@ -824,6 +829,8 @@ "ChatMessageContentItemAudioInputAudio", "ChatMessageContentItemAudioInputAudioTypedDict", "ChatMessageContentItemAudioTypedDict", + "ChatMessageContentItemFile", + "ChatMessageContentItemFileTypedDict", "ChatMessageContentItemImage", "ChatMessageContentItemImageDetail", "ChatMessageContentItemImageTypedDict", @@ -836,6 +843,8 @@ "ChatMessageContentItemVideoTypedDict", "ChatMessageContentItemVideoVideoURL", "ChatMessageContentItemVideoVideoURLTypedDict", + "ChatMessageContentItemVideoVideoURLVideoURL", + "ChatMessageContentItemVideoVideoURLVideoURLTypedDict", "ChatMessageTokenLogprob", "ChatMessageTokenLogprobTypedDict", "ChatMessageTokenLogprobs", @@ -903,12 +912,14 @@ "Effort", "EndpointStatus", "Engine", + "File", "FileCitation", "FileCitationType", "FileCitationTypedDict", "FilePath", "FilePathType", "FilePathTypedDict", + "FileTypedDict", "ForbiddenResponseErrorData", "ForbiddenResponseErrorDataTypedDict", "IDFileParser", @@ -1380,10 +1391,8 @@ "UserMessageTypedDict", "Variables", "VariablesTypedDict", - "VideoURL1", - "VideoURL1TypedDict", - "VideoURL2", - "VideoURL2TypedDict", + "VideoURL", + "VideoURLTypedDict", "WebSearchPreviewToolUserLocation", "WebSearchPreviewToolUserLocationType", "WebSearchPreviewToolUserLocationTypedDict", @@ -1433,6 +1442,10 @@ "ChatMessageContentItemAudioInputAudio": ".chatmessagecontentitemaudio", "ChatMessageContentItemAudioInputAudioTypedDict": ".chatmessagecontentitemaudio", "ChatMessageContentItemAudioTypedDict": ".chatmessagecontentitemaudio", + "ChatMessageContentItemFile": ".chatmessagecontentitemfile", + "ChatMessageContentItemFileTypedDict": ".chatmessagecontentitemfile", + "File": ".chatmessagecontentitemfile", + "FileTypedDict": ".chatmessagecontentitemfile", "ChatMessageContentItemImage": ".chatmessagecontentitemimage", "ChatMessageContentItemImageDetail": ".chatmessagecontentitemimage", "ChatMessageContentItemImageTypedDict": ".chatmessagecontentitemimage", @@ -1446,10 +1459,8 @@ "ChatMessageContentItemVideoTypedDict": ".chatmessagecontentitemvideo", "ChatMessageContentItemVideoVideoURL": ".chatmessagecontentitemvideo", "ChatMessageContentItemVideoVideoURLTypedDict": ".chatmessagecontentitemvideo", - "VideoURL1": ".chatmessagecontentitemvideo", - "VideoURL1TypedDict": ".chatmessagecontentitemvideo", - "VideoURL2": ".chatmessagecontentitemvideo", - "VideoURL2TypedDict": ".chatmessagecontentitemvideo", + "ChatMessageContentItemVideoVideoURLVideoURL": ".chatmessagecontentitemvideo", + "ChatMessageContentItemVideoVideoURLVideoURLTypedDict": ".chatmessagecontentitemvideo", "ChatMessageTokenLogprob": ".chatmessagetokenlogprob", "ChatMessageTokenLogprobTypedDict": ".chatmessagetokenlogprob", "TopLogprob": ".chatmessagetokenlogprob", @@ -1987,6 +1998,8 @@ "UserMessageContent": ".usermessage", "UserMessageContentTypedDict": ".usermessage", "UserMessageTypedDict": ".usermessage", + "VideoURL": ".videourl", + "VideoURLTypedDict": ".videourl", "WebSearchPreviewToolUserLocation": ".websearchpreviewtooluserlocation", "WebSearchPreviewToolUserLocationType": ".websearchpreviewtooluserlocation", "WebSearchPreviewToolUserLocationTypedDict": ".websearchpreviewtooluserlocation", diff --git a/src/openrouter/components/chatgenerationparams.py b/src/openrouter/components/chatgenerationparams.py index 32fd9ca..2b9b05c 100644 --- a/src/openrouter/components/chatgenerationparams.py +++ b/src/openrouter/components/chatgenerationparams.py @@ -21,9 +21,9 @@ UNSET_SENTINEL, UnrecognizedStr, ) -from openrouter.utils import validate_const, validate_open_enum +from openrouter.utils import get_discriminator, validate_const, validate_open_enum import pydantic -from pydantic import model_serializer +from pydantic import Discriminator, Tag, model_serializer from pydantic.functional_validators import AfterValidator, PlainValidator from typing import Any, Dict, List, Literal, Optional, Union from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict @@ -134,16 +134,16 @@ class ChatGenerationParamsResponseFormatText(BaseModel): ) -ChatGenerationParamsResponseFormatUnion = TypeAliasType( - "ChatGenerationParamsResponseFormatUnion", +ChatGenerationParamsResponseFormatUnion = Annotated[ Union[ - ChatGenerationParamsResponseFormatText, - ChatGenerationParamsResponseFormatJSONObject, - ChatGenerationParamsResponseFormatPython, - ResponseFormatJSONSchema, - ResponseFormatTextGrammar, + Annotated[ChatGenerationParamsResponseFormatText, Tag("text")], + Annotated[ChatGenerationParamsResponseFormatJSONObject, Tag("json_object")], + Annotated[ResponseFormatJSONSchema, Tag("json_schema")], + Annotated[ResponseFormatTextGrammar, Tag("grammar")], + Annotated[ChatGenerationParamsResponseFormatPython, Tag("python")], ], -) + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] ChatGenerationParamsStopTypedDict = TypeAliasType( diff --git a/src/openrouter/components/chatmessagecontentitem.py b/src/openrouter/components/chatmessagecontentitem.py index 87547ca..6d949b9 100644 --- a/src/openrouter/components/chatmessagecontentitem.py +++ b/src/openrouter/components/chatmessagecontentitem.py @@ -5,6 +5,10 @@ ChatMessageContentItemAudio, ChatMessageContentItemAudioTypedDict, ) +from .chatmessagecontentitemfile import ( + ChatMessageContentItemFile, + ChatMessageContentItemFileTypedDict, +) from .chatmessagecontentitemimage import ( ChatMessageContentItemImage, ChatMessageContentItemImageTypedDict, @@ -29,6 +33,7 @@ ChatMessageContentItemTextTypedDict, ChatMessageContentItemImageTypedDict, ChatMessageContentItemAudioTypedDict, + ChatMessageContentItemFileTypedDict, ChatMessageContentItemVideoTypedDict, ], ) @@ -41,6 +46,7 @@ Annotated[ChatMessageContentItemAudio, Tag("input_audio")], Annotated[ChatMessageContentItemVideo, Tag("input_video")], Annotated[ChatMessageContentItemVideo, Tag("video_url")], + Annotated[ChatMessageContentItemFile, Tag("file")], ], Discriminator(lambda m: get_discriminator(m, "type", "type")), ] diff --git a/src/openrouter/components/chatmessagecontentitemfile.py b/src/openrouter/components/chatmessagecontentitemfile.py new file mode 100644 index 0000000..f878dc3 --- /dev/null +++ b/src/openrouter/components/chatmessagecontentitemfile.py @@ -0,0 +1,37 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from openrouter.types import BaseModel +from openrouter.utils import validate_const +import pydantic +from pydantic.functional_validators import AfterValidator +from typing import Literal, Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +class FileTypedDict(TypedDict): + file_data: str + file_id: NotRequired[str] + filename: NotRequired[str] + + +class File(BaseModel): + file_data: str + + file_id: Optional[str] = None + + filename: Optional[str] = None + + +class ChatMessageContentItemFileTypedDict(TypedDict): + file: FileTypedDict + type: Literal["file"] + + +class ChatMessageContentItemFile(BaseModel): + file: File + + TYPE: Annotated[ + Annotated[Literal["file"], AfterValidator(validate_const("file"))], + pydantic.Field(alias="type"), + ] = "file" diff --git a/src/openrouter/components/chatmessagecontentitemvideo.py b/src/openrouter/components/chatmessagecontentitemvideo.py index fccda91..553d826 100644 --- a/src/openrouter/components/chatmessagecontentitemvideo.py +++ b/src/openrouter/components/chatmessagecontentitemvideo.py @@ -1,29 +1,31 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" from __future__ import annotations +from .videourl import VideoURL, VideoURLTypedDict from openrouter.types import BaseModel -from openrouter.utils import validate_const +from openrouter.utils import get_discriminator, validate_const import pydantic +from pydantic import Discriminator, Tag from pydantic.functional_validators import AfterValidator from typing import Literal, Union from typing_extensions import Annotated, TypeAliasType, TypedDict -class VideoURL2TypedDict(TypedDict): +class ChatMessageContentItemVideoVideoURLVideoURLTypedDict(TypedDict): url: str -class VideoURL2(BaseModel): +class ChatMessageContentItemVideoVideoURLVideoURL(BaseModel): url: str class ChatMessageContentItemVideoVideoURLTypedDict(TypedDict): - video_url: VideoURL2TypedDict + video_url: ChatMessageContentItemVideoVideoURLVideoURLTypedDict type: Literal["video_url"] class ChatMessageContentItemVideoVideoURL(BaseModel): - video_url: VideoURL2 + video_url: ChatMessageContentItemVideoVideoURLVideoURL TYPE: Annotated[ Annotated[Literal["video_url"], AfterValidator(validate_const("video_url"))], @@ -31,21 +33,13 @@ class ChatMessageContentItemVideoVideoURL(BaseModel): ] = "video_url" -class VideoURL1TypedDict(TypedDict): - url: str - - -class VideoURL1(BaseModel): - url: str - - class ChatMessageContentItemVideoInputVideoTypedDict(TypedDict): - video_url: VideoURL1TypedDict + video_url: VideoURLTypedDict type: Literal["input_video"] class ChatMessageContentItemVideoInputVideo(BaseModel): - video_url: VideoURL1 + video_url: VideoURL TYPE: Annotated[ Annotated[ @@ -64,7 +58,10 @@ class ChatMessageContentItemVideoInputVideo(BaseModel): ) -ChatMessageContentItemVideo = TypeAliasType( - "ChatMessageContentItemVideo", - Union[ChatMessageContentItemVideoInputVideo, ChatMessageContentItemVideoVideoURL], -) +ChatMessageContentItemVideo = Annotated[ + Union[ + Annotated[ChatMessageContentItemVideoInputVideo, Tag("input_video")], + Annotated[ChatMessageContentItemVideoVideoURL, Tag("video_url")], + ], + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] diff --git a/src/openrouter/components/completioncreateparams.py b/src/openrouter/components/completioncreateparams.py index 3a81d2e..712feaa 100644 --- a/src/openrouter/components/completioncreateparams.py +++ b/src/openrouter/components/completioncreateparams.py @@ -16,9 +16,9 @@ UNSET, UNSET_SENTINEL, ) -from openrouter.utils import validate_const +from openrouter.utils import get_discriminator, validate_const import pydantic -from pydantic import model_serializer +from pydantic import Discriminator, Tag, model_serializer from pydantic.functional_validators import AfterValidator from typing import Dict, List, Literal, Optional, Union from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict @@ -127,16 +127,16 @@ class CompletionCreateParamsResponseFormatText(BaseModel): ) -CompletionCreateParamsResponseFormatUnion = TypeAliasType( - "CompletionCreateParamsResponseFormatUnion", +CompletionCreateParamsResponseFormatUnion = Annotated[ Union[ - CompletionCreateParamsResponseFormatText, - CompletionCreateParamsResponseFormatJSONObject, - CompletionCreateParamsResponseFormatPython, - ResponseFormatJSONSchema, - ResponseFormatTextGrammar, + Annotated[CompletionCreateParamsResponseFormatText, Tag("text")], + Annotated[CompletionCreateParamsResponseFormatJSONObject, Tag("json_object")], + Annotated[ResponseFormatJSONSchema, Tag("json_schema")], + Annotated[ResponseFormatTextGrammar, Tag("grammar")], + Annotated[CompletionCreateParamsResponseFormatPython, Tag("python")], ], -) + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] class CompletionCreateParamsTypedDict(TypedDict): diff --git a/src/openrouter/components/message.py b/src/openrouter/components/message.py index 676b497..8d92cba 100644 --- a/src/openrouter/components/message.py +++ b/src/openrouter/components/message.py @@ -10,8 +10,9 @@ from .toolresponsemessage import ToolResponseMessage, ToolResponseMessageTypedDict from .usermessage import UserMessage, UserMessageTypedDict from openrouter.types import BaseModel -from openrouter.utils import validate_const +from openrouter.utils import get_discriminator, validate_const import pydantic +from pydantic import Discriminator, Tag from pydantic.functional_validators import AfterValidator from typing import List, Literal, Optional, Union from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict @@ -56,13 +57,13 @@ class MessageDeveloper(BaseModel): ) -Message = TypeAliasType( - "Message", +Message = Annotated[ Union[ - SystemMessage, - UserMessage, - MessageDeveloper, - ToolResponseMessage, - AssistantMessage, + Annotated[SystemMessage, Tag("system")], + Annotated[UserMessage, Tag("user")], + Annotated[MessageDeveloper, Tag("developer")], + Annotated[AssistantMessage, Tag("assistant")], + Annotated[ToolResponseMessage, Tag("tool")], ], -) + Discriminator(lambda m: get_discriminator(m, "role", "role")), +] diff --git a/src/openrouter/components/openairesponsesannotation.py b/src/openrouter/components/openairesponsesannotation.py index 315df01..1303f98 100644 --- a/src/openrouter/components/openairesponsesannotation.py +++ b/src/openrouter/components/openairesponsesannotation.py @@ -4,8 +4,10 @@ from .filecitation import FileCitation, FileCitationTypedDict from .filepath import FilePath, FilePathTypedDict from .urlcitation import URLCitation, URLCitationTypedDict +from openrouter.utils import get_discriminator +from pydantic import Discriminator, Tag from typing import Union -from typing_extensions import TypeAliasType +from typing_extensions import Annotated, TypeAliasType OpenAIResponsesAnnotationTypedDict = TypeAliasType( @@ -14,6 +16,11 @@ ) -OpenAIResponsesAnnotation = TypeAliasType( - "OpenAIResponsesAnnotation", Union[FilePath, FileCitation, URLCitation] -) +OpenAIResponsesAnnotation = Annotated[ + Union[ + Annotated[FileCitation, Tag("file_citation")], + Annotated[URLCitation, Tag("url_citation")], + Annotated[FilePath, Tag("file_path")], + ], + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] diff --git a/src/openrouter/components/openresponsesnonstreamingresponse.py b/src/openrouter/components/openresponsesnonstreamingresponse.py index 6051f4e..4836d91 100644 --- a/src/openrouter/components/openresponsesnonstreamingresponse.py +++ b/src/openrouter/components/openresponsesnonstreamingresponse.py @@ -48,8 +48,8 @@ UNSET, UNSET_SENTINEL, ) -from openrouter.utils import validate_open_enum -from pydantic import model_serializer +from openrouter.utils import get_discriminator, validate_open_enum +from pydantic import Discriminator, Tag, model_serializer from pydantic.functional_validators import PlainValidator from typing import Any, Dict, List, Literal, Optional, Union from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict @@ -127,16 +127,19 @@ def serialize_model(self, handler): ) -OpenResponsesNonStreamingResponseToolUnion = TypeAliasType( - "OpenResponsesNonStreamingResponseToolUnion", +OpenResponsesNonStreamingResponseToolUnion = Annotated[ Union[ - OpenResponsesWebSearchPreviewTool, - OpenResponsesWebSearchPreview20250311Tool, - OpenResponsesWebSearchTool, - OpenResponsesWebSearch20250826Tool, - OpenResponsesNonStreamingResponseToolFunction, + Annotated[OpenResponsesNonStreamingResponseToolFunction, Tag("function")], + Annotated[OpenResponsesWebSearchPreviewTool, Tag("web_search_preview")], + Annotated[ + OpenResponsesWebSearchPreview20250311Tool, + Tag("web_search_preview_2025_03_11"), + ], + Annotated[OpenResponsesWebSearchTool, Tag("web_search")], + Annotated[OpenResponsesWebSearch20250826Tool, Tag("web_search_2025_08_26")], ], -) + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] class OpenResponsesNonStreamingResponseTypedDict(TypedDict): diff --git a/src/openrouter/components/openresponsesrequest.py b/src/openrouter/components/openresponsesrequest.py index 265f2d5..60a5e54 100644 --- a/src/openrouter/components/openresponsesrequest.py +++ b/src/openrouter/components/openresponsesrequest.py @@ -44,8 +44,8 @@ UNSET_SENTINEL, UnrecognizedStr, ) -from openrouter.utils import validate_open_enum -from pydantic import model_serializer +from openrouter.utils import get_discriminator, validate_open_enum +from pydantic import Discriminator, Tag, model_serializer from pydantic.functional_validators import PlainValidator from typing import Any, Dict, List, Literal, Optional, Union from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict @@ -120,16 +120,19 @@ def serialize_model(self, handler): ) -OpenResponsesRequestToolUnion = TypeAliasType( - "OpenResponsesRequestToolUnion", +OpenResponsesRequestToolUnion = Annotated[ Union[ - OpenResponsesWebSearchPreviewTool, - OpenResponsesWebSearchPreview20250311Tool, - OpenResponsesWebSearchTool, - OpenResponsesWebSearch20250826Tool, - OpenResponsesRequestToolFunction, + Annotated[OpenResponsesRequestToolFunction, Tag("function")], + Annotated[OpenResponsesWebSearchPreviewTool, Tag("web_search_preview")], + Annotated[ + OpenResponsesWebSearchPreview20250311Tool, + Tag("web_search_preview_2025_03_11"), + ], + Annotated[OpenResponsesWebSearchTool, Tag("web_search")], + Annotated[OpenResponsesWebSearch20250826Tool, Tag("web_search_2025_08_26")], ], -) + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] ServiceTier = Union[ @@ -438,7 +441,14 @@ class PluginModeration(BaseModel): ) -Plugin = TypeAliasType("Plugin", Union[PluginModeration, PluginFileParser, PluginWeb]) +Plugin = Annotated[ + Union[ + Annotated[PluginModeration, Tag("moderation")], + Annotated[PluginWeb, Tag("web")], + Annotated[PluginFileParser, Tag("file-parser")], + ], + Discriminator(lambda m: get_discriminator(m, "id", "id")), +] class OpenResponsesRequestTypedDict(TypedDict): diff --git a/src/openrouter/components/openresponsesstreamevent.py b/src/openrouter/components/openresponsesstreamevent.py index 6e423d8..8b21548 100644 --- a/src/openrouter/components/openresponsesstreamevent.py +++ b/src/openrouter/components/openresponsesstreamevent.py @@ -59,8 +59,10 @@ from .responseoutputtext import ResponseOutputText, ResponseOutputTextTypedDict from .responsesoutputitem import ResponsesOutputItem, ResponsesOutputItemTypedDict from openrouter.types import BaseModel +from openrouter.utils import get_discriminator +from pydantic import Discriminator, Tag from typing import List, Literal, Union -from typing_extensions import TypeAliasType, TypedDict +from typing_extensions import Annotated, TypeAliasType, TypedDict TypeResponseReasoningSummaryPartDone = Literal["response.reasoning_summary_part.done",] @@ -328,10 +330,14 @@ class OpenResponsesStreamEventResponseOutputTextDelta(BaseModel): ) -Part2 = TypeAliasType( - "Part2", - Union[ReasoningTextContent, OpenAIResponsesRefusalContent, ResponseOutputText], -) +Part2 = Annotated[ + Union[ + Annotated[ResponseOutputText, Tag("output_text")], + Annotated[ReasoningTextContent, Tag("reasoning_text")], + Annotated[OpenAIResponsesRefusalContent, Tag("refusal")], + ], + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] class OpenResponsesStreamEventResponseContentPartDoneTypedDict(TypedDict): @@ -374,10 +380,14 @@ class OpenResponsesStreamEventResponseContentPartDone(BaseModel): ) -Part1 = TypeAliasType( - "Part1", - Union[ReasoningTextContent, OpenAIResponsesRefusalContent, ResponseOutputText], -) +Part1 = Annotated[ + Union[ + Annotated[ResponseOutputText, Tag("output_text")], + Annotated[ReasoningTextContent, Tag("reasoning_text")], + Annotated[OpenAIResponsesRefusalContent, Tag("refusal")], + ], + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] class OpenResponsesStreamEventResponseContentPartAddedTypedDict(TypedDict): @@ -609,36 +619,97 @@ class OpenResponsesStreamEventResponseCreated(BaseModel): r"""Union of all possible event types emitted during response streaming""" -OpenResponsesStreamEvent = TypeAliasType( - "OpenResponsesStreamEvent", +OpenResponsesStreamEvent = Annotated[ Union[ - OpenResponsesStreamEventResponseCreated, - OpenResponsesStreamEventResponseInProgress, - OpenResponsesStreamEventResponseCompleted, - OpenResponsesStreamEventResponseIncomplete, - OpenResponsesStreamEventResponseFailed, - OpenResponsesStreamEventResponseOutputItemAdded, - OpenResponsesStreamEventResponseOutputItemDone, - OpenResponsesImageGenCallCompleted, - OpenResponsesImageGenCallGenerating, - OpenResponsesImageGenCallInProgress, - OpenResponsesErrorEvent, - OpenResponsesStreamEventResponseFunctionCallArgumentsDelta, - OpenResponsesStreamEventResponseRefusalDelta, - OpenResponsesReasoningSummaryPartAddedEvent, - OpenResponsesStreamEventResponseContentPartAdded, - OpenResponsesImageGenCallPartialImage, - OpenResponsesStreamEventResponseFunctionCallArgumentsDone, - OpenResponsesReasoningDeltaEvent, - OpenResponsesReasoningDoneEvent, - OpenResponsesStreamEventResponseRefusalDone, - OpenResponsesStreamEventResponseReasoningSummaryPartDone, - OpenResponsesReasoningSummaryTextDeltaEvent, - OpenResponsesReasoningSummaryTextDoneEvent, - OpenResponsesStreamEventResponseContentPartDone, - OpenResponsesStreamEventResponseOutputTextDelta, - OpenResponsesStreamEventResponseOutputTextDone, - OpenResponsesStreamEventResponseOutputTextAnnotationAdded, + Annotated[OpenResponsesStreamEventResponseCreated, Tag("response.created")], + Annotated[ + OpenResponsesStreamEventResponseInProgress, Tag("response.in_progress") + ], + Annotated[OpenResponsesStreamEventResponseCompleted, Tag("response.completed")], + Annotated[ + OpenResponsesStreamEventResponseIncomplete, Tag("response.incomplete") + ], + Annotated[OpenResponsesStreamEventResponseFailed, Tag("response.failed")], + Annotated[OpenResponsesErrorEvent, Tag("error")], + Annotated[ + OpenResponsesStreamEventResponseOutputItemAdded, + Tag("response.output_item.added"), + ], + Annotated[ + OpenResponsesStreamEventResponseOutputItemDone, + Tag("response.output_item.done"), + ], + Annotated[ + OpenResponsesStreamEventResponseContentPartAdded, + Tag("response.content_part.added"), + ], + Annotated[ + OpenResponsesStreamEventResponseContentPartDone, + Tag("response.content_part.done"), + ], + Annotated[ + OpenResponsesStreamEventResponseOutputTextDelta, + Tag("response.output_text.delta"), + ], + Annotated[ + OpenResponsesStreamEventResponseOutputTextDone, + Tag("response.output_text.done"), + ], + Annotated[ + OpenResponsesStreamEventResponseRefusalDelta, Tag("response.refusal.delta") + ], + Annotated[ + OpenResponsesStreamEventResponseRefusalDone, Tag("response.refusal.done") + ], + Annotated[ + OpenResponsesStreamEventResponseOutputTextAnnotationAdded, + Tag("response.output_text.annotation.added"), + ], + Annotated[ + OpenResponsesStreamEventResponseFunctionCallArgumentsDelta, + Tag("response.function_call_arguments.delta"), + ], + Annotated[ + OpenResponsesStreamEventResponseFunctionCallArgumentsDone, + Tag("response.function_call_arguments.done"), + ], + Annotated[ + OpenResponsesReasoningDeltaEvent, Tag("response.reasoning_text.delta") + ], + Annotated[OpenResponsesReasoningDoneEvent, Tag("response.reasoning_text.done")], + Annotated[ + OpenResponsesReasoningSummaryPartAddedEvent, + Tag("response.reasoning_summary_part.added"), + ], + Annotated[ + OpenResponsesStreamEventResponseReasoningSummaryPartDone, + Tag("response.reasoning_summary_part.done"), + ], + Annotated[ + OpenResponsesReasoningSummaryTextDeltaEvent, + Tag("response.reasoning_summary_text.delta"), + ], + Annotated[ + OpenResponsesReasoningSummaryTextDoneEvent, + Tag("response.reasoning_summary_text.done"), + ], + Annotated[ + OpenResponsesImageGenCallInProgress, + Tag("response.image_generation_call.in_progress"), + ], + Annotated[ + OpenResponsesImageGenCallGenerating, + Tag("response.image_generation_call.generating"), + ], + Annotated[ + OpenResponsesImageGenCallPartialImage, + Tag("response.image_generation_call.partial_image"), + ], + Annotated[ + OpenResponsesImageGenCallCompleted, + Tag("response.image_generation_call.completed"), + ], ], -) + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] r"""Union of all possible event types emitted during response streaming""" diff --git a/src/openrouter/components/outputmessage.py b/src/openrouter/components/outputmessage.py index a956778..1304077 100644 --- a/src/openrouter/components/outputmessage.py +++ b/src/openrouter/components/outputmessage.py @@ -7,8 +7,10 @@ ) from .responseoutputtext import ResponseOutputText, ResponseOutputTextTypedDict from openrouter.types import BaseModel +from openrouter.utils import get_discriminator +from pydantic import Discriminator, Tag from typing import List, Literal, Optional, Union -from typing_extensions import NotRequired, TypeAliasType, TypedDict +from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict OutputMessageRole = Literal["assistant",] @@ -52,9 +54,13 @@ ) -OutputMessageContent = TypeAliasType( - "OutputMessageContent", Union[OpenAIResponsesRefusalContent, ResponseOutputText] -) +OutputMessageContent = Annotated[ + Union[ + Annotated[ResponseOutputText, Tag("output_text")], + Annotated[OpenAIResponsesRefusalContent, Tag("refusal")], + ], + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] class OutputMessageTypedDict(TypedDict): diff --git a/src/openrouter/components/responseformattextconfig.py b/src/openrouter/components/responseformattextconfig.py index 7478c80..a82e125 100644 --- a/src/openrouter/components/responseformattextconfig.py +++ b/src/openrouter/components/responseformattextconfig.py @@ -10,8 +10,10 @@ ResponsesFormatTextJSONSchemaConfig, ResponsesFormatTextJSONSchemaConfigTypedDict, ) +from openrouter.utils import get_discriminator +from pydantic import Discriminator, Tag from typing import Union -from typing_extensions import TypeAliasType +from typing_extensions import Annotated, TypeAliasType ResponseFormatTextConfigTypedDict = TypeAliasType( @@ -25,12 +27,12 @@ r"""Text response format configuration""" -ResponseFormatTextConfig = TypeAliasType( - "ResponseFormatTextConfig", +ResponseFormatTextConfig = Annotated[ Union[ - ResponsesFormatText, - ResponsesFormatJSONObject, - ResponsesFormatTextJSONSchemaConfig, + Annotated[ResponsesFormatText, Tag("text")], + Annotated[ResponsesFormatJSONObject, Tag("json_object")], + Annotated[ResponsesFormatTextJSONSchemaConfig, Tag("json_schema")], ], -) + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] r"""Text response format configuration""" diff --git a/src/openrouter/components/responsesoutputitem.py b/src/openrouter/components/responsesoutputitem.py index 63c4023..28966bd 100644 --- a/src/openrouter/components/responsesoutputitem.py +++ b/src/openrouter/components/responsesoutputitem.py @@ -25,8 +25,10 @@ ResponsesWebSearchCallOutput, ResponsesWebSearchCallOutputTypedDict, ) +from openrouter.utils import get_discriminator +from pydantic import Discriminator, Tag from typing import Union -from typing_extensions import TypeAliasType +from typing_extensions import Annotated, TypeAliasType ResponsesOutputItemTypedDict = TypeAliasType( @@ -43,15 +45,15 @@ r"""An output item from the response""" -ResponsesOutputItem = TypeAliasType( - "ResponsesOutputItem", +ResponsesOutputItem = Annotated[ Union[ - ResponsesWebSearchCallOutput, - ResponsesOutputItemFileSearchCall, - ResponsesImageGenerationCall, - ResponsesOutputMessage, - ResponsesOutputItemReasoning, - ResponsesOutputItemFunctionCall, + Annotated[ResponsesOutputMessage, Tag("message")], + Annotated[ResponsesOutputItemReasoning, Tag("reasoning")], + Annotated[ResponsesOutputItemFunctionCall, Tag("function_call")], + Annotated[ResponsesWebSearchCallOutput, Tag("web_search_call")], + Annotated[ResponsesOutputItemFileSearchCall, Tag("file_search_call")], + Annotated[ResponsesImageGenerationCall, Tag("image_generation_call")], ], -) + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] r"""An output item from the response""" diff --git a/src/openrouter/components/responsesoutputmessage.py b/src/openrouter/components/responsesoutputmessage.py index 74126d4..6717a26 100644 --- a/src/openrouter/components/responsesoutputmessage.py +++ b/src/openrouter/components/responsesoutputmessage.py @@ -7,8 +7,10 @@ ) from .responseoutputtext import ResponseOutputText, ResponseOutputTextTypedDict from openrouter.types import BaseModel +from openrouter.utils import get_discriminator +from pydantic import Discriminator, Tag from typing import List, Literal, Optional, Union -from typing_extensions import NotRequired, TypeAliasType, TypedDict +from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict ResponsesOutputMessageRole = Literal["assistant",] @@ -52,10 +54,13 @@ ) -ResponsesOutputMessageContent = TypeAliasType( - "ResponsesOutputMessageContent", - Union[OpenAIResponsesRefusalContent, ResponseOutputText], -) +ResponsesOutputMessageContent = Annotated[ + Union[ + Annotated[ResponseOutputText, Tag("output_text")], + Annotated[OpenAIResponsesRefusalContent, Tag("refusal")], + ], + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] class ResponsesOutputMessageTypedDict(TypedDict): diff --git a/src/openrouter/components/videourl.py b/src/openrouter/components/videourl.py new file mode 100644 index 0000000..e1ef80d --- /dev/null +++ b/src/openrouter/components/videourl.py @@ -0,0 +1,13 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from openrouter.types import BaseModel +from typing_extensions import TypedDict + + +class VideoURLTypedDict(TypedDict): + url: str + + +class VideoURL(BaseModel): + url: str diff --git a/src/openrouter/models/__init__.py b/src/openrouter/models/__init__.py new file mode 100644 index 0000000..726fc5e --- /dev/null +++ b/src/openrouter/models/__init__.py @@ -0,0 +1,3 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +# package diff --git a/src/openrouter/models/internal/__init__.py b/src/openrouter/models/internal/__init__.py new file mode 100644 index 0000000..e7070a1 --- /dev/null +++ b/src/openrouter/models/internal/__init__.py @@ -0,0 +1,54 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from typing import TYPE_CHECKING +from importlib import import_module +import builtins +import sys + +if TYPE_CHECKING: + from .globals import Globals, GlobalsTypedDict + +__all__ = ["Globals", "GlobalsTypedDict"] + +_dynamic_imports: dict[str, str] = { + "Globals": ".globals", + "GlobalsTypedDict": ".globals", +} + + +def dynamic_import(modname, retries=3): + for attempt in range(retries): + try: + return import_module(modname, __package__) + except KeyError: + # Clear any half-initialized module and retry + sys.modules.pop(modname, None) + if attempt == retries - 1: + break + raise KeyError(f"Failed to import module '{modname}' after {retries} attempts") + + +def __getattr__(attr_name: str) -> object: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__} " + ) + + try: + module = dynamic_import(module_name) + result = getattr(module, attr_name) + return result + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = builtins.list(_dynamic_imports.keys()) + return builtins.sorted(lazy_attrs) diff --git a/src/openrouter/models/internal/globals.py b/src/openrouter/models/internal/globals.py new file mode 100644 index 0000000..248f13e --- /dev/null +++ b/src/openrouter/models/internal/globals.py @@ -0,0 +1,41 @@ +"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" + +from __future__ import annotations +from openrouter.types import BaseModel +from openrouter.utils import FieldMetadata, HeaderMetadata +import pydantic +from typing import Optional +from typing_extensions import Annotated, NotRequired, TypedDict + + +class GlobalsTypedDict(TypedDict): + http_referer: NotRequired[str] + r"""The app identifier should be your app's URL and is used as the primary identifier for rankings. + This is used to track API usage per application. + + """ + x_title: NotRequired[str] + r"""The app display name allows you to customize how your app appears in OpenRouter's dashboard. + + """ + + +class Globals(BaseModel): + http_referer: Annotated[ + Optional[str], + pydantic.Field(alias="HTTP-Referer"), + FieldMetadata(header=HeaderMetadata(style="simple", explode=False)), + ] = None + r"""The app identifier should be your app's URL and is used as the primary identifier for rankings. + This is used to track API usage per application. + + """ + + x_title: Annotated[ + Optional[str], + pydantic.Field(alias="X-Title"), + FieldMetadata(header=HeaderMetadata(style="simple", explode=False)), + ] = None + r"""The app display name allows you to customize how your app appears in OpenRouter's dashboard. + + """ diff --git a/src/openrouter/operations/createembeddings.py b/src/openrouter/operations/createembeddings.py index ddd90c3..b184d5a 100644 --- a/src/openrouter/operations/createembeddings.py +++ b/src/openrouter/operations/createembeddings.py @@ -15,8 +15,8 @@ UNSET_SENTINEL, UnrecognizedStr, ) -from openrouter.utils import validate_open_enum -from pydantic import model_serializer +from openrouter.utils import get_discriminator, validate_open_enum +from pydantic import Discriminator, Tag, model_serializer from pydantic.functional_validators import PlainValidator from typing import Any, List, Literal, Optional, Union from typing_extensions import Annotated, NotRequired, TypeAliasType, TypedDict @@ -63,7 +63,13 @@ class ContentText(BaseModel): ) -Content = TypeAliasType("Content", Union[ContentText, ContentImageURL]) +Content = Annotated[ + Union[ + Annotated[ContentText, Tag("text")], + Annotated[ContentImageURL, Tag("image_url")], + ], + Discriminator(lambda m: get_discriminator(m, "type", "type")), +] class InputTypedDict(TypedDict): diff --git a/src/openrouter/sdk.py b/src/openrouter/sdk.py index 0ad8870..a71e057 100644 --- a/src/openrouter/sdk.py +++ b/src/openrouter/sdk.py @@ -9,6 +9,7 @@ import importlib from openrouter import components, utils from openrouter._hooks import SDKHooks +from openrouter.models import internal from openrouter.types import OptionalNullable, UNSET import sys from typing import Any, Callable, Dict, Optional, TYPE_CHECKING, Union, cast @@ -77,6 +78,8 @@ class OpenRouter(BaseSDK): def __init__( self, api_key: Optional[Union[Optional[str], Callable[[], Optional[str]]]] = None, + http_referer: Optional[str] = None, + x_title: Optional[str] = None, server: Optional[str] = None, server_url: Optional[str] = None, url_params: Optional[Dict[str, str]] = None, @@ -89,6 +92,8 @@ def __init__( r"""Instantiates the SDK configuring it with the provided parameters. :param api_key: The api_key required for authentication + :param http_referer: Configures the http_referer parameter for all supported operations + :param x_title: Configures the x_title parameter for all supported operations :param server: The server by name to use for all methods :param server_url: The server URL to use for all methods :param url_params: Parameters to optionally template the server URL with @@ -129,6 +134,13 @@ def __init__( if url_params is not None: server_url = utils.template_url(server_url, url_params) + _globals = internal.Globals( + http_referer=utils.get_global_from_env( + http_referer, "OPENROUTER_HTTP_REFERER", str + ), + x_title=utils.get_global_from_env(x_title, "OPENROUTER_X_TITLE", str), + ) + BaseSDK.__init__( self, SDKConfiguration( @@ -136,6 +148,7 @@ def __init__( client_supplied=client_supplied, async_client=async_client, async_client_supplied=async_client_supplied, + globals=_globals, security=security, server_url=server_url, server=server, diff --git a/src/openrouter/sdkconfiguration.py b/src/openrouter/sdkconfiguration.py index 4f12a18..2643316 100644 --- a/src/openrouter/sdkconfiguration.py +++ b/src/openrouter/sdkconfiguration.py @@ -10,6 +10,7 @@ from .utils import Logger, RetryConfig, remove_suffix from dataclasses import dataclass from openrouter import components +from openrouter.models import internal from openrouter.types import OptionalNullable, UNSET from pydantic import Field from typing import Callable, Dict, Optional, Tuple, Union @@ -30,6 +31,7 @@ class SDKConfiguration: async_client: Union[AsyncHttpClient, None] async_client_supplied: bool debug_logger: Logger + globals: internal.Globals security: Optional[ Union[components.Security, Callable[[], components.Security]] ] = None diff --git a/src/openrouter/utils/retries.py b/src/openrouter/utils/retries.py index 4d60867..88a91b1 100644 --- a/src/openrouter/utils/retries.py +++ b/src/openrouter/utils/retries.py @@ -3,7 +3,9 @@ import asyncio import random import time -from typing import List +from datetime import datetime +from email.utils import parsedate_to_datetime +from typing import List, Optional import httpx @@ -51,9 +53,11 @@ def __init__(self, config: RetryConfig, status_codes: List[str]): class TemporaryError(Exception): response: httpx.Response + retry_after: Optional[int] def __init__(self, response: httpx.Response): self.response = response + self.retry_after = _parse_retry_after_header(response) class PermanentError(Exception): @@ -63,6 +67,62 @@ def __init__(self, inner: Exception): self.inner = inner +def _parse_retry_after_header(response: httpx.Response) -> Optional[int]: + """Parse Retry-After header from response. + + Returns: + Retry interval in milliseconds, or None if header is missing or invalid. + """ + retry_after_header = response.headers.get("retry-after") + if not retry_after_header: + return None + + try: + seconds = float(retry_after_header) + return round(seconds * 1000) + except ValueError: + pass + + try: + retry_date = parsedate_to_datetime(retry_after_header) + delta = (retry_date - datetime.now(retry_date.tzinfo)).total_seconds() + return round(max(0, delta) * 1000) + except (ValueError, TypeError): + pass + + return None + + +def _get_sleep_interval( + exception: Exception, + initial_interval: int, + max_interval: int, + exponent: float, + retries: int, +) -> float: + """Get sleep interval for retry with exponential backoff. + + Args: + exception: The exception that triggered the retry. + initial_interval: Initial retry interval in milliseconds. + max_interval: Maximum retry interval in milliseconds. + exponent: Base for exponential backoff calculation. + retries: Current retry attempt count. + + Returns: + Sleep interval in seconds. + """ + if ( + isinstance(exception, TemporaryError) + and exception.retry_after is not None + and exception.retry_after > 0 + ): + return exception.retry_after / 1000 + + sleep = (initial_interval / 1000) * exponent**retries + random.uniform(0, 1) + return min(sleep, max_interval / 1000) + + def retry(func, retries: Retries): if retries.config.strategy == "backoff": @@ -183,8 +243,10 @@ def retry_with_backoff( return exception.response raise - sleep = (initial_interval / 1000) * exponent**retries + random.uniform(0, 1) - sleep = min(sleep, max_interval / 1000) + + sleep = _get_sleep_interval( + exception, initial_interval, max_interval, exponent, retries + ) time.sleep(sleep) retries += 1 @@ -211,7 +273,9 @@ async def retry_with_backoff_async( return exception.response raise - sleep = (initial_interval / 1000) * exponent**retries + random.uniform(0, 1) - sleep = min(sleep, max_interval / 1000) + + sleep = _get_sleep_interval( + exception, initial_interval, max_interval, exponent, retries + ) await asyncio.sleep(sleep) retries += 1 diff --git a/uv.lock b/uv.lock index d506269..7778462 100644 --- a/uv.lock +++ b/uv.lock @@ -211,7 +211,7 @@ wheels = [ [[package]] name = "openrouter" -version = "0.0.16" +version = "0.1.0" source = { editable = "." } dependencies = [ { name = "httpcore" },