diff --git a/.speakeasy/gen.lock b/.speakeasy/gen.lock index cf305df8..44691f1f 100644 --- a/.speakeasy/gen.lock +++ b/.speakeasy/gen.lock @@ -1,19 +1,19 @@ lockVersion: 2.0.0 id: 8b6cd71c-ea04-44da-af45-e43968b5928d management: - docChecksum: 73e9a83b95df662091c1325b5ce88d01 + docChecksum: b93ae18147eb3aee91b01d8c9d1e0520 docVersion: 1.0.0 speakeasyVersion: 1.680.0 generationVersion: 2.788.4 - releaseVersion: 0.5.1 - configChecksum: cc01f824304d9ee6508e348557713ecb + releaseVersion: 0.5.2 + configChecksum: 86e3290c0928b4f04f54fcab65eb19e5 repoURL: https://github.com/OpenRouterTeam/typescript-sdk.git installationURL: https://github.com/OpenRouterTeam/typescript-sdk published: true persistentEdits: - generation_id: 17290e1f-a7b8-4a42-8730-8e335b3b4185 - pristine_commit_hash: 6cb777df87266ed92b16464de80b5097796805a3 - pristine_tree_hash: c21b5a7b386b90ca65fb1c97f21ac93f27a49d86 + generation_id: ad9139e2-62d7-4b50-b638-48e01a70ae98 + pristine_commit_hash: b6e0f4b7ba8ff35159cb1c170845599e184900d7 + pristine_tree_hash: dd111875a4a5832ae2375b591a38f2ddc9b082cc features: typescript: acceptHeaders: 2.81.2 @@ -1252,16 +1252,16 @@ trackedFiles: pristine_git_object: 26598ac54c0cb052834ba22240442161765fcd35 docs/models/operations/getgenerationdata.md: id: c47059f62af7 - last_write_checksum: sha1:0fd2a05115edc6f810bfde1e821f9a56b5ff29ee - pristine_git_object: 8028472702fc86e1d5736cc60fcaf78c6991fed5 + last_write_checksum: sha1:1e1cec3402d0cae674de954eb5c335e8e1f99b65 + pristine_git_object: 8443a05a902744483eaa6240b73bd09350efab52 docs/models/operations/getgenerationrequest.md: id: fe98f4f7a718 last_write_checksum: sha1:44d8a0ff0bcd4a70a5a95eda4084859787b5fccf pristine_git_object: 4bdaeaf73753b58ffb89151e55fdc352bd18dc1c docs/models/operations/getgenerationresponse.md: id: edafe5ee4596 - last_write_checksum: sha1:90c4e9b37d13e90fa824cbb0de78ec05c0c0d3f0 - pristine_git_object: 39a8787a05d52771433495dded2a09167014385e + last_write_checksum: sha1:c5f328be534ea1c9dab86d08420e2d02ae1ef90e + pristine_git_object: ad8d36ec5e2ca43e7a93e2815165758fee94ab67 docs/models/operations/getguardraildata.md: id: 698e237b3e04 last_write_checksum: sha1:c14d56c6919231412f80e05e5c882090de3e8c42 @@ -1426,6 +1426,14 @@ trackedFiles: id: 98bd2cd64286 last_write_checksum: sha1:f9ff52c02c2cff4b24d7b4dad7101418ce372018 pristine_git_object: 6319d7beaa2d9709e126f07a3207533fcf24c1b8 + docs/models/operations/providername.md: + id: 616da1534f02 + last_write_checksum: sha1:a15073b598f04bccc5c5cb416255ff3823c1e9d4 + pristine_git_object: 8d1a2e2ce89198f09b2d8bbad3756c2c05a2e8dd + docs/models/operations/providerresponse.md: + id: 8af09d586b03 + last_write_checksum: sha1:3969076345480f6e5738cc0299685a56331137f0 + pristine_git_object: f4c34c0401f326239d620b1974214dd189306e53 docs/models/operations/ratelimit.md: id: 94a6ae30f279 last_write_checksum: sha1:55a11ba047d92a3ebaa49307db470a78dc12ff00 @@ -1612,8 +1620,8 @@ trackedFiles: pristine_git_object: 6b96206e454e8dc021b1d853c77a817094f8854c docs/models/providername.md: id: 661ee6ccfa8a - last_write_checksum: sha1:93ffab582f8f3d77c78c13153ef18739044bcde5 - pristine_git_object: a7c2ff6f05d39b0ca9f36bdde124a653f56cea9f + last_write_checksum: sha1:872344502103259c8a9f7e0e2b594415a56d805d + pristine_git_object: a8b50263a71b290c027c938d76aeacfb489d19ba docs/models/provideroverloadedresponseerrordata.md: id: 23d66996874e last_write_checksum: sha1:93e27b1eabea198409d833c54370b889a138f6b0 @@ -1960,8 +1968,8 @@ trackedFiles: pristine_git_object: c6058f73691b9b051a4f6c65f6193df0b7fbbeeb docs/models/schema0enum.md: id: a6b9414adf87 - last_write_checksum: sha1:cb56c840833d6731dbc51fb7693dd4a7e114ba7f - pristine_git_object: 387442526b3b98a40ec0c55d9fe005e34bdace67 + last_write_checksum: sha1:636e706f307b4fa5e3b6e1cefee9ecf64b35ec43 + pristine_git_object: e60a89b9d3b8d5b0ce143dd6524e426bab1bb0ac docs/models/schema2.md: id: a12cbe99ab08 last_write_checksum: sha1:640fe4f2da5fedbd541af33afb50d175c05f3334 @@ -2172,12 +2180,12 @@ trackedFiles: pristine_git_object: 410efafd6a7f50d91ccb87131fedbe0c3d47e15a jsr.json: id: 7f6ab7767282 - last_write_checksum: sha1:9968774b7b8c66dc585f852bfcf6f5d3911206fc - pristine_git_object: 8f77d11a532ea048a80c81db3b3f982a2c8e7374 + last_write_checksum: sha1:cc1f056e68c8aff69094cf47cdbef47236432420 + pristine_git_object: e45f8bb30d51ec9d07d77fd6688d6844bc7ea264 package.json: id: 7030d0b2f71b - last_write_checksum: sha1:9e2d85ad86eb1769b9492b6589e55c57e9e327de - pristine_git_object: 7587aa9f3c653ad96e1d117cf30361a76e728486 + last_write_checksum: sha1:c4300996673b2a00e5fa65f48670decdaf38e848 + pristine_git_object: 7d562ce96128821d1746872183f9c006b2e685bb src/core.ts: id: f431fdbcd144 last_write_checksum: sha1:5aa66b0b6a5964f3eea7f3098c2eb3c0ee9c0131 @@ -2344,8 +2352,8 @@ trackedFiles: pristine_git_object: a187e58707bdb726ca2aff74941efe7493422d4e src/lib/config.ts: id: 320761608fb3 - last_write_checksum: sha1:3fef41a7e2402f2b9c0609d94ecd42e4bffb754c - pristine_git_object: 004af19750619bf38869027939712180f7bb6837 + last_write_checksum: sha1:990ce41cbe8cff1400aeb8ff5687ed42df9840cf + pristine_git_object: 738b3a973e71dd50cc5f980093291bc4fbfd5fbd src/lib/dlv.ts: id: b1988214835a last_write_checksum: sha1:eaac763b22717206a6199104e0403ed17a4e2711 @@ -2900,8 +2908,8 @@ trackedFiles: pristine_git_object: 2c7c2dc7982e4f70c88a08e1f0841b8eb93625e7 src/models/operations/getgeneration.ts: id: 5cdb2959d2a5 - last_write_checksum: sha1:fd246e3ce1857f743c5d8cad3a30c5b35b51ea58 - pristine_git_object: 7ed9f3de4040d8159dc8cb0992e5d2a4e622232d + last_write_checksum: sha1:6e56529dd48556492e6bd4606c3a82afec978eb3 + pristine_git_object: 9be390cccdda24accee51fef46fa8c6e823a6bf3 src/models/operations/getguardrail.ts: id: 11c366ebdade last_write_checksum: sha1:6d0718ddef1890cb60d1a26bdf210709d2e2938c @@ -3032,8 +3040,8 @@ trackedFiles: pristine_git_object: d2180d165594e1e5a5cd373ad9b0e03b13acce61 src/models/providername.ts: id: 89e536fb023a - last_write_checksum: sha1:3a7c044aafdd7c31905fad0d834d0d2c0d9e0b61 - pristine_git_object: 3e7ccef868f314481835d1ec9a2f5e123b2dcbe0 + last_write_checksum: sha1:d847205c0a9dface92527a6c32053d5abc6c7794 + pristine_git_object: 9a8937b430e4a82fc5aa11bcf8d3e8ed007ed25d src/models/provideroverloadedresponseerrordata.ts: id: 379f1256314f last_write_checksum: sha1:0458b6a8454adfa7c415e3bd7f49f2879cc6093e @@ -3180,8 +3188,8 @@ trackedFiles: pristine_git_object: ad0047e3ee3baf0dea29abdfda2f3b7681c03519 src/models/schema0.ts: id: 14bff3bd8497 - last_write_checksum: sha1:83aca8ee163e14d3f04eb710e25a1c6ed3741fb5 - pristine_git_object: 2fc01894cf0abfa06423b13dd78b4a01fa701032 + last_write_checksum: sha1:52c1918d334339beba1b4b80213feaef9134b01a + pristine_git_object: cad90d323d98ff3744dafce37565216d397fb68a src/models/schema2.ts: id: 39bd32ae905d last_write_checksum: sha1:378c8f1e3cbb6ce2e17e383b08d20292d2e936c3 @@ -4064,7 +4072,7 @@ examples: id: "" responses: "200": - application/json: {"data": {"id": "gen-3bhGkxlo4XFrqiabUM7NDtwDzWwG", "upstream_id": "chatcmpl-791bcf62-080e-4568-87d0-94c72e3b4946", "total_cost": 0.0015, "cache_discount": 0.0002, "upstream_inference_cost": 0.0012, "created_at": "2024-07-15T23:33:19.433273+00:00", "model": "sao10k/l3-stheno-8b", "app_id": 12345, "streamed": true, "cancelled": false, "provider_name": "Infermatic", "latency": 1250, "moderation_latency": 50, "generation_time": 1200, "finish_reason": "stop", "tokens_prompt": 10, "tokens_completion": 25, "native_tokens_prompt": 10, "native_tokens_completion": 25, "native_tokens_completion_images": 0, "native_tokens_reasoning": 5, "native_tokens_cached": 3, "num_media_prompt": 1, "num_input_audio_prompt": 0, "num_media_completion": 0, "num_search_results": 5, "origin": "https://openrouter.ai/", "usage": 0.0015, "is_byok": false, "native_finish_reason": "stop", "external_user": "user-123", "api_type": "completions", "router": "openrouter/auto"}} + application/json: {"data": {"id": "gen-3bhGkxlo4XFrqiabUM7NDtwDzWwG", "upstream_id": "chatcmpl-791bcf62-080e-4568-87d0-94c72e3b4946", "total_cost": 0.0015, "cache_discount": 0.0002, "upstream_inference_cost": 0.0012, "created_at": "2024-07-15T23:33:19.433273+00:00", "model": "sao10k/l3-stheno-8b", "app_id": 12345, "streamed": true, "cancelled": false, "provider_name": "Infermatic", "latency": 1250, "moderation_latency": 50, "generation_time": 1200, "finish_reason": "stop", "tokens_prompt": 10, "tokens_completion": 25, "native_tokens_prompt": 10, "native_tokens_completion": 25, "native_tokens_completion_images": 0, "native_tokens_reasoning": 5, "native_tokens_cached": 3, "num_media_prompt": 1, "num_input_audio_prompt": 0, "num_media_completion": 0, "num_search_results": 5, "origin": "https://openrouter.ai/", "usage": 0.0015, "is_byok": false, "native_finish_reason": "stop", "external_user": "user-123", "api_type": "completions", "router": "openrouter/auto", "provider_responses": []}} default: application/json: {"error": {"code": 400, "message": "Invalid request parameters", "metadata": {"field": "temperature", "reason": "Must be between 0 and 2"}}, "user_id": "user-abc123"} 4XX: diff --git a/.speakeasy/gen.yaml b/.speakeasy/gen.yaml index 1b6a60a0..bd8d9095 100644 --- a/.speakeasy/gen.yaml +++ b/.speakeasy/gen.yaml @@ -33,7 +33,7 @@ generation: skipResponseBodyAssertions: false preApplyUnionDiscriminators: true typescript: - version: 0.5.1 + version: 0.5.2 acceptHeaderEnum: false additionalDependencies: dependencies: diff --git a/.speakeasy/in.openapi.yaml b/.speakeasy/in.openapi.yaml index 7ab2a07f..bce49dbc 100644 --- a/.speakeasy/in.openapi.yaml +++ b/.speakeasy/in.openapi.yaml @@ -3542,6 +3542,7 @@ components: - AI21 - AionLabs - Alibaba + - Ambient - Amazon Bedrock - Amazon Nova - Anthropic @@ -8110,6 +8111,7 @@ components: - AI21 - AionLabs - Alibaba + - Ambient - Amazon Bedrock - Amazon Nova - Anthropic @@ -10355,6 +10357,130 @@ paths: nullable: true description: Router used for the request (e.g., openrouter/auto) example: openrouter/auto + provider_responses: + type: array + nullable: true + items: + type: object + properties: + id: + type: string + endpoint_id: + type: string + model_permaslug: + type: string + provider_name: + type: string + enum: + - AnyScale + - Atoma + - Cent-ML + - CrofAI + - Enfer + - GoPomelo + - HuggingFace + - Hyperbolic 2 + - InoCloud + - Kluster + - Lambda + - Lepton + - Lynn 2 + - Lynn + - Mancer + - Meta + - Modal + - Nineteen + - OctoAI + - Recursal + - Reflection + - Replicate + - SambaNova 2 + - SF Compute + - Targon + - Together 2 + - Ubicloud + - 01.AI + - AI21 + - AionLabs + - Alibaba + - Ambient + - Amazon Bedrock + - Amazon Nova + - Anthropic + - Arcee AI + - AtlasCloud + - Avian + - Azure + - BaseTen + - BytePlus + - Black Forest Labs + - Cerebras + - Chutes + - Cirrascale + - Clarifai + - Cloudflare + - Cohere + - Crusoe + - DeepInfra + - DeepSeek + - Featherless + - Fireworks + - Friendli + - GMICloud + - Google + - Google AI Studio + - Groq + - Hyperbolic + - Inception + - Inceptron + - InferenceNet + - Infermatic + - Inflection + - Liquid + - Mara + - Mancer 2 + - Minimax + - ModelRun + - Mistral + - Modular + - Moonshot AI + - Morph + - NCompass + - Nebius + - NextBit + - Novita + - Nvidia + - OpenAI + - OpenInference + - Parasail + - Perplexity + - Phala + - Relace + - SambaNova + - Seed + - SiliconFlow + - Sourceful + - Stealth + - StreamLake + - Switchpoint + - Together + - Upstage + - Venice + - WandB + - Xiaomi + - xAI + - Z.AI + - FakeProvider + status: + type: number + nullable: true + latency: + type: number + is_byok: + type: boolean + required: + - status + description: List of provider responses for this generation, including fallback attempts required: - id - upstream_id @@ -10389,6 +10515,7 @@ paths: - external_user - api_type - router + - provider_responses description: Generation data required: - data diff --git a/.speakeasy/out.openapi.yaml b/.speakeasy/out.openapi.yaml index 8b562f1a..b0a7fece 100644 --- a/.speakeasy/out.openapi.yaml +++ b/.speakeasy/out.openapi.yaml @@ -3561,6 +3561,7 @@ components: - AI21 - AionLabs - Alibaba + - Ambient - Amazon Bedrock - Amazon Nova - Anthropic @@ -8124,6 +8125,7 @@ components: - AI21 - AionLabs - Alibaba + - Ambient - Amazon Bedrock - Amazon Nova - Anthropic @@ -10378,6 +10380,131 @@ paths: nullable: true description: Router used for the request (e.g., openrouter/auto) example: openrouter/auto + provider_responses: + type: array + nullable: true + items: + type: object + properties: + id: + type: string + endpoint_id: + type: string + model_permaslug: + type: string + provider_name: + type: string + enum: + - AnyScale + - Atoma + - Cent-ML + - CrofAI + - Enfer + - GoPomelo + - HuggingFace + - Hyperbolic 2 + - InoCloud + - Kluster + - Lambda + - Lepton + - Lynn 2 + - Lynn + - Mancer + - Meta + - Modal + - Nineteen + - OctoAI + - Recursal + - Reflection + - Replicate + - SambaNova 2 + - SF Compute + - Targon + - Together 2 + - Ubicloud + - 01.AI + - AI21 + - AionLabs + - Alibaba + - Ambient + - Amazon Bedrock + - Amazon Nova + - Anthropic + - Arcee AI + - AtlasCloud + - Avian + - Azure + - BaseTen + - BytePlus + - Black Forest Labs + - Cerebras + - Chutes + - Cirrascale + - Clarifai + - Cloudflare + - Cohere + - Crusoe + - DeepInfra + - DeepSeek + - Featherless + - Fireworks + - Friendli + - GMICloud + - Google + - Google AI Studio + - Groq + - Hyperbolic + - Inception + - Inceptron + - InferenceNet + - Infermatic + - Inflection + - Liquid + - Mara + - Mancer 2 + - Minimax + - ModelRun + - Mistral + - Modular + - Moonshot AI + - Morph + - NCompass + - Nebius + - NextBit + - Novita + - Nvidia + - OpenAI + - OpenInference + - Parasail + - Perplexity + - Phala + - Relace + - SambaNova + - Seed + - SiliconFlow + - Sourceful + - Stealth + - StreamLake + - Switchpoint + - Together + - Upstage + - Venice + - WandB + - Xiaomi + - xAI + - Z.AI + - FakeProvider + x-speakeasy-unknown-values: allow + status: + type: number + nullable: true + latency: + type: number + is_byok: + type: boolean + required: + - status + description: List of provider responses for this generation, including fallback attempts required: - id - upstream_id @@ -10412,6 +10539,7 @@ paths: - external_user - api_type - router + - provider_responses description: Generation data required: - data diff --git a/.speakeasy/workflow.lock b/.speakeasy/workflow.lock index 16615c02..a815c15f 100644 --- a/.speakeasy/workflow.lock +++ b/.speakeasy/workflow.lock @@ -2,8 +2,8 @@ speakeasyVersion: 1.680.0 sources: OpenRouter API: sourceNamespace: open-router-chat-completions-api - sourceRevisionDigest: sha256:d0f1998ad92ab7b3f66f9d40fbde6c4b922137e768efb4adb91907d12c3bb235 - sourceBlobDigest: sha256:f5e39b02fd76a6dbd02df72a4d5b281038b40ecac762921a6157221e98485574 + sourceRevisionDigest: sha256:615066c503f7418f1ff68e6c199acece691743630603d755216098fc1f0bd48f + sourceBlobDigest: sha256:acc5a225365b32a7466c8f297a24a74a6a6a5b6a594dbea5973408f3c3d862a3 tags: - latest - main @@ -12,8 +12,8 @@ targets: openrouter: source: OpenRouter API sourceNamespace: open-router-chat-completions-api - sourceRevisionDigest: sha256:d0f1998ad92ab7b3f66f9d40fbde6c4b922137e768efb4adb91907d12c3bb235 - sourceBlobDigest: sha256:f5e39b02fd76a6dbd02df72a4d5b281038b40ecac762921a6157221e98485574 + sourceRevisionDigest: sha256:615066c503f7418f1ff68e6c199acece691743630603d755216098fc1f0bd48f + sourceBlobDigest: sha256:acc5a225365b32a7466c8f297a24a74a6a6a5b6a594dbea5973408f3c3d862a3 workflow: workflowVersion: 1.0.0 speakeasyVersion: 1.680.0 diff --git a/docs/models/operations/getgenerationdata.md b/docs/models/operations/getgenerationdata.md index 80284727..8443a05a 100644 --- a/docs/models/operations/getgenerationdata.md +++ b/docs/models/operations/getgenerationdata.md @@ -41,43 +41,49 @@ let value: GetGenerationData = { externalUser: "user-123", apiType: "completions", router: "openrouter/auto", + providerResponses: [ + { + status: 9669.26, + }, + ], }; ``` ## Fields -| Field | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `id` | *string* | :heavy_check_mark: | Unique identifier for the generation | gen-3bhGkxlo4XFrqiabUM7NDtwDzWwG | -| `upstreamId` | *string* | :heavy_check_mark: | Upstream provider's identifier for this generation | chatcmpl-791bcf62-080e-4568-87d0-94c72e3b4946 | -| `totalCost` | *number* | :heavy_check_mark: | Total cost of the generation in USD | 0.0015 | -| `cacheDiscount` | *number* | :heavy_check_mark: | Discount applied due to caching | 0.0002 | -| `upstreamInferenceCost` | *number* | :heavy_check_mark: | Cost charged by the upstream provider | 0.0012 | -| `createdAt` | *string* | :heavy_check_mark: | ISO 8601 timestamp of when the generation was created | 2024-07-15T23:33:19.433273+00:00 | -| `model` | *string* | :heavy_check_mark: | Model used for the generation | sao10k/l3-stheno-8b | -| `appId` | *number* | :heavy_check_mark: | ID of the app that made the request | 12345 | -| `streamed` | *boolean* | :heavy_check_mark: | Whether the response was streamed | true | -| `cancelled` | *boolean* | :heavy_check_mark: | Whether the generation was cancelled | false | -| `providerName` | *string* | :heavy_check_mark: | Name of the provider that served the request | Infermatic | -| `latency` | *number* | :heavy_check_mark: | Total latency in milliseconds | 1250 | -| `moderationLatency` | *number* | :heavy_check_mark: | Moderation latency in milliseconds | 50 | -| `generationTime` | *number* | :heavy_check_mark: | Time taken for generation in milliseconds | 1200 | -| `finishReason` | *string* | :heavy_check_mark: | Reason the generation finished | stop | -| `tokensPrompt` | *number* | :heavy_check_mark: | Number of tokens in the prompt | 10 | -| `tokensCompletion` | *number* | :heavy_check_mark: | Number of tokens in the completion | 25 | -| `nativeTokensPrompt` | *number* | :heavy_check_mark: | Native prompt tokens as reported by provider | 10 | -| `nativeTokensCompletion` | *number* | :heavy_check_mark: | Native completion tokens as reported by provider | 25 | -| `nativeTokensCompletionImages` | *number* | :heavy_check_mark: | Native completion image tokens as reported by provider | 0 | -| `nativeTokensReasoning` | *number* | :heavy_check_mark: | Native reasoning tokens as reported by provider | 5 | -| `nativeTokensCached` | *number* | :heavy_check_mark: | Native cached tokens as reported by provider | 3 | -| `numMediaPrompt` | *number* | :heavy_check_mark: | Number of media items in the prompt | 1 | -| `numInputAudioPrompt` | *number* | :heavy_check_mark: | Number of audio inputs in the prompt | 0 | -| `numMediaCompletion` | *number* | :heavy_check_mark: | Number of media items in the completion | 0 | -| `numSearchResults` | *number* | :heavy_check_mark: | Number of search results included | 5 | -| `origin` | *string* | :heavy_check_mark: | Origin URL of the request | https://openrouter.ai/ | -| `usage` | *number* | :heavy_check_mark: | Usage amount in USD | 0.0015 | -| `isByok` | *boolean* | :heavy_check_mark: | Whether this used bring-your-own-key | false | -| `nativeFinishReason` | *string* | :heavy_check_mark: | Native finish reason as reported by provider | stop | -| `externalUser` | *string* | :heavy_check_mark: | External user identifier | user-123 | -| `apiType` | [operations.ApiType](../../models/operations/apitype.md) | :heavy_check_mark: | Type of API used for the generation | | -| `router` | *string* | :heavy_check_mark: | Router used for the request (e.g., openrouter/auto) | openrouter/auto | \ No newline at end of file +| Field | Type | Required | Description | Example | +| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| `id` | *string* | :heavy_check_mark: | Unique identifier for the generation | gen-3bhGkxlo4XFrqiabUM7NDtwDzWwG | +| `upstreamId` | *string* | :heavy_check_mark: | Upstream provider's identifier for this generation | chatcmpl-791bcf62-080e-4568-87d0-94c72e3b4946 | +| `totalCost` | *number* | :heavy_check_mark: | Total cost of the generation in USD | 0.0015 | +| `cacheDiscount` | *number* | :heavy_check_mark: | Discount applied due to caching | 0.0002 | +| `upstreamInferenceCost` | *number* | :heavy_check_mark: | Cost charged by the upstream provider | 0.0012 | +| `createdAt` | *string* | :heavy_check_mark: | ISO 8601 timestamp of when the generation was created | 2024-07-15T23:33:19.433273+00:00 | +| `model` | *string* | :heavy_check_mark: | Model used for the generation | sao10k/l3-stheno-8b | +| `appId` | *number* | :heavy_check_mark: | ID of the app that made the request | 12345 | +| `streamed` | *boolean* | :heavy_check_mark: | Whether the response was streamed | true | +| `cancelled` | *boolean* | :heavy_check_mark: | Whether the generation was cancelled | false | +| `providerName` | *string* | :heavy_check_mark: | Name of the provider that served the request | Infermatic | +| `latency` | *number* | :heavy_check_mark: | Total latency in milliseconds | 1250 | +| `moderationLatency` | *number* | :heavy_check_mark: | Moderation latency in milliseconds | 50 | +| `generationTime` | *number* | :heavy_check_mark: | Time taken for generation in milliseconds | 1200 | +| `finishReason` | *string* | :heavy_check_mark: | Reason the generation finished | stop | +| `tokensPrompt` | *number* | :heavy_check_mark: | Number of tokens in the prompt | 10 | +| `tokensCompletion` | *number* | :heavy_check_mark: | Number of tokens in the completion | 25 | +| `nativeTokensPrompt` | *number* | :heavy_check_mark: | Native prompt tokens as reported by provider | 10 | +| `nativeTokensCompletion` | *number* | :heavy_check_mark: | Native completion tokens as reported by provider | 25 | +| `nativeTokensCompletionImages` | *number* | :heavy_check_mark: | Native completion image tokens as reported by provider | 0 | +| `nativeTokensReasoning` | *number* | :heavy_check_mark: | Native reasoning tokens as reported by provider | 5 | +| `nativeTokensCached` | *number* | :heavy_check_mark: | Native cached tokens as reported by provider | 3 | +| `numMediaPrompt` | *number* | :heavy_check_mark: | Number of media items in the prompt | 1 | +| `numInputAudioPrompt` | *number* | :heavy_check_mark: | Number of audio inputs in the prompt | 0 | +| `numMediaCompletion` | *number* | :heavy_check_mark: | Number of media items in the completion | 0 | +| `numSearchResults` | *number* | :heavy_check_mark: | Number of search results included | 5 | +| `origin` | *string* | :heavy_check_mark: | Origin URL of the request | https://openrouter.ai/ | +| `usage` | *number* | :heavy_check_mark: | Usage amount in USD | 0.0015 | +| `isByok` | *boolean* | :heavy_check_mark: | Whether this used bring-your-own-key | false | +| `nativeFinishReason` | *string* | :heavy_check_mark: | Native finish reason as reported by provider | stop | +| `externalUser` | *string* | :heavy_check_mark: | External user identifier | user-123 | +| `apiType` | [operations.ApiType](../../models/operations/apitype.md) | :heavy_check_mark: | Type of API used for the generation | | +| `router` | *string* | :heavy_check_mark: | Router used for the request (e.g., openrouter/auto) | openrouter/auto | +| `providerResponses` | [operations.ProviderResponse](../../models/operations/providerresponse.md)[] | :heavy_check_mark: | List of provider responses for this generation, including fallback attempts | | \ No newline at end of file diff --git a/docs/models/operations/getgenerationresponse.md b/docs/models/operations/getgenerationresponse.md index 39a8787a..ad8d36ec 100644 --- a/docs/models/operations/getgenerationresponse.md +++ b/docs/models/operations/getgenerationresponse.md @@ -42,6 +42,7 @@ let value: GetGenerationResponse = { externalUser: "user-123", apiType: null, router: "openrouter/auto", + providerResponses: null, }, }; ``` diff --git a/docs/models/operations/providername.md b/docs/models/operations/providername.md new file mode 100644 index 00000000..8d1a2e2c --- /dev/null +++ b/docs/models/operations/providername.md @@ -0,0 +1,17 @@ +# ProviderName + +## Example Usage + +```typescript +import { ProviderName } from "@openrouter/sdk/models/operations"; + +let value: ProviderName = "Lambda"; +``` + +## Values + +This is an open enum. Unrecognized values will be captured as the `Unrecognized` branded type. + +```typescript +"AnyScale" | "Atoma" | "Cent-ML" | "CrofAI" | "Enfer" | "GoPomelo" | "HuggingFace" | "Hyperbolic 2" | "InoCloud" | "Kluster" | "Lambda" | "Lepton" | "Lynn 2" | "Lynn" | "Mancer" | "Meta" | "Modal" | "Nineteen" | "OctoAI" | "Recursal" | "Reflection" | "Replicate" | "SambaNova 2" | "SF Compute" | "Targon" | "Together 2" | "Ubicloud" | "01.AI" | "AI21" | "AionLabs" | "Alibaba" | "Ambient" | "Amazon Bedrock" | "Amazon Nova" | "Anthropic" | "Arcee AI" | "AtlasCloud" | "Avian" | "Azure" | "BaseTen" | "BytePlus" | "Black Forest Labs" | "Cerebras" | "Chutes" | "Cirrascale" | "Clarifai" | "Cloudflare" | "Cohere" | "Crusoe" | "DeepInfra" | "DeepSeek" | "Featherless" | "Fireworks" | "Friendli" | "GMICloud" | "Google" | "Google AI Studio" | "Groq" | "Hyperbolic" | "Inception" | "Inceptron" | "InferenceNet" | "Infermatic" | "Inflection" | "Liquid" | "Mara" | "Mancer 2" | "Minimax" | "ModelRun" | "Mistral" | "Modular" | "Moonshot AI" | "Morph" | "NCompass" | "Nebius" | "NextBit" | "Novita" | "Nvidia" | "OpenAI" | "OpenInference" | "Parasail" | "Perplexity" | "Phala" | "Relace" | "SambaNova" | "Seed" | "SiliconFlow" | "Sourceful" | "Stealth" | "StreamLake" | "Switchpoint" | "Together" | "Upstage" | "Venice" | "WandB" | "Xiaomi" | "xAI" | "Z.AI" | "FakeProvider" | Unrecognized +``` \ No newline at end of file diff --git a/docs/models/operations/providerresponse.md b/docs/models/operations/providerresponse.md new file mode 100644 index 00000000..f4c34c04 --- /dev/null +++ b/docs/models/operations/providerresponse.md @@ -0,0 +1,23 @@ +# ProviderResponse + +## Example Usage + +```typescript +import { ProviderResponse } from "@openrouter/sdk/models/operations"; + +let value: ProviderResponse = { + status: 9155.58, +}; +``` + +## Fields + +| Field | Type | Required | Description | +| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | +| `id` | *string* | :heavy_minus_sign: | N/A | +| `endpointId` | *string* | :heavy_minus_sign: | N/A | +| `modelPermaslug` | *string* | :heavy_minus_sign: | N/A | +| `providerName` | [operations.ProviderName](../../models/operations/providername.md) | :heavy_minus_sign: | N/A | +| `status` | *number* | :heavy_check_mark: | N/A | +| `latency` | *number* | :heavy_minus_sign: | N/A | +| `isByok` | *boolean* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/docs/models/providername.md b/docs/models/providername.md index a7c2ff6f..a8b50263 100644 --- a/docs/models/providername.md +++ b/docs/models/providername.md @@ -13,5 +13,5 @@ let value: ProviderName = "OpenAI"; This is an open enum. Unrecognized values will be captured as the `Unrecognized` branded type. ```typescript -"AI21" | "AionLabs" | "Alibaba" | "Amazon Bedrock" | "Amazon Nova" | "Anthropic" | "Arcee AI" | "AtlasCloud" | "Avian" | "Azure" | "BaseTen" | "BytePlus" | "Black Forest Labs" | "Cerebras" | "Chutes" | "Cirrascale" | "Clarifai" | "Cloudflare" | "Cohere" | "Crusoe" | "DeepInfra" | "DeepSeek" | "Featherless" | "Fireworks" | "Friendli" | "GMICloud" | "Google" | "Google AI Studio" | "Groq" | "Hyperbolic" | "Inception" | "Inceptron" | "InferenceNet" | "Infermatic" | "Inflection" | "Liquid" | "Mara" | "Mancer 2" | "Minimax" | "ModelRun" | "Mistral" | "Modular" | "Moonshot AI" | "Morph" | "NCompass" | "Nebius" | "NextBit" | "Novita" | "Nvidia" | "OpenAI" | "OpenInference" | "Parasail" | "Perplexity" | "Phala" | "Relace" | "SambaNova" | "Seed" | "SiliconFlow" | "Sourceful" | "Stealth" | "StreamLake" | "Switchpoint" | "Together" | "Upstage" | "Venice" | "WandB" | "Xiaomi" | "xAI" | "Z.AI" | "FakeProvider" | Unrecognized +"AI21" | "AionLabs" | "Alibaba" | "Ambient" | "Amazon Bedrock" | "Amazon Nova" | "Anthropic" | "Arcee AI" | "AtlasCloud" | "Avian" | "Azure" | "BaseTen" | "BytePlus" | "Black Forest Labs" | "Cerebras" | "Chutes" | "Cirrascale" | "Clarifai" | "Cloudflare" | "Cohere" | "Crusoe" | "DeepInfra" | "DeepSeek" | "Featherless" | "Fireworks" | "Friendli" | "GMICloud" | "Google" | "Google AI Studio" | "Groq" | "Hyperbolic" | "Inception" | "Inceptron" | "InferenceNet" | "Infermatic" | "Inflection" | "Liquid" | "Mara" | "Mancer 2" | "Minimax" | "ModelRun" | "Mistral" | "Modular" | "Moonshot AI" | "Morph" | "NCompass" | "Nebius" | "NextBit" | "Novita" | "Nvidia" | "OpenAI" | "OpenInference" | "Parasail" | "Perplexity" | "Phala" | "Relace" | "SambaNova" | "Seed" | "SiliconFlow" | "Sourceful" | "Stealth" | "StreamLake" | "Switchpoint" | "Together" | "Upstage" | "Venice" | "WandB" | "Xiaomi" | "xAI" | "Z.AI" | "FakeProvider" | Unrecognized ``` \ No newline at end of file diff --git a/docs/models/schema0enum.md b/docs/models/schema0enum.md index 38744252..e60a89b9 100644 --- a/docs/models/schema0enum.md +++ b/docs/models/schema0enum.md @@ -13,5 +13,5 @@ let value: Schema0Enum = "WandB"; This is an open enum. Unrecognized values will be captured as the `Unrecognized` branded type. ```typescript -"AI21" | "AionLabs" | "Alibaba" | "Amazon Bedrock" | "Amazon Nova" | "Anthropic" | "Arcee AI" | "AtlasCloud" | "Avian" | "Azure" | "BaseTen" | "BytePlus" | "Black Forest Labs" | "Cerebras" | "Chutes" | "Cirrascale" | "Clarifai" | "Cloudflare" | "Cohere" | "Crusoe" | "DeepInfra" | "DeepSeek" | "Featherless" | "Fireworks" | "Friendli" | "GMICloud" | "Google" | "Google AI Studio" | "Groq" | "Hyperbolic" | "Inception" | "Inceptron" | "InferenceNet" | "Infermatic" | "Inflection" | "Liquid" | "Mara" | "Mancer 2" | "Minimax" | "ModelRun" | "Mistral" | "Modular" | "Moonshot AI" | "Morph" | "NCompass" | "Nebius" | "NextBit" | "Novita" | "Nvidia" | "OpenAI" | "OpenInference" | "Parasail" | "Perplexity" | "Phala" | "Relace" | "SambaNova" | "Seed" | "SiliconFlow" | "Sourceful" | "Stealth" | "StreamLake" | "Switchpoint" | "Together" | "Upstage" | "Venice" | "WandB" | "Xiaomi" | "xAI" | "Z.AI" | "FakeProvider" | Unrecognized +"AI21" | "AionLabs" | "Alibaba" | "Ambient" | "Amazon Bedrock" | "Amazon Nova" | "Anthropic" | "Arcee AI" | "AtlasCloud" | "Avian" | "Azure" | "BaseTen" | "BytePlus" | "Black Forest Labs" | "Cerebras" | "Chutes" | "Cirrascale" | "Clarifai" | "Cloudflare" | "Cohere" | "Crusoe" | "DeepInfra" | "DeepSeek" | "Featherless" | "Fireworks" | "Friendli" | "GMICloud" | "Google" | "Google AI Studio" | "Groq" | "Hyperbolic" | "Inception" | "Inceptron" | "InferenceNet" | "Infermatic" | "Inflection" | "Liquid" | "Mara" | "Mancer 2" | "Minimax" | "ModelRun" | "Mistral" | "Modular" | "Moonshot AI" | "Morph" | "NCompass" | "Nebius" | "NextBit" | "Novita" | "Nvidia" | "OpenAI" | "OpenInference" | "Parasail" | "Perplexity" | "Phala" | "Relace" | "SambaNova" | "Seed" | "SiliconFlow" | "Sourceful" | "Stealth" | "StreamLake" | "Switchpoint" | "Together" | "Upstage" | "Venice" | "WandB" | "Xiaomi" | "xAI" | "Z.AI" | "FakeProvider" | Unrecognized ``` \ No newline at end of file diff --git a/jsr.json b/jsr.json index 8f77d11a..e45f8bb3 100644 --- a/jsr.json +++ b/jsr.json @@ -2,7 +2,7 @@ { "name": "@openrouter/sdk", - "version": "0.5.1", + "version": "0.5.2", "exports": { ".": "./src/index.ts", "./models/errors": "./src/models/errors/index.ts", diff --git a/package.json b/package.json index ed877204..eda9617d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openrouter/sdk", - "version": "0.5.1", + "version": "0.5.2", "author": "OpenRouter", "description": "The OpenRouter TypeScript SDK is a type-safe toolkit for building AI applications with access to 300+ language models through a unified API.", "keywords": [ diff --git a/src/lib/config.ts b/src/lib/config.ts index bb132dc7..c53eae86 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -76,7 +76,7 @@ export function serverURLFromOptions(options: SDKOptions): URL | null { export const SDK_METADATA = { language: "typescript", openapiDocVersion: "1.0.0", - sdkVersion: "0.5.1", + sdkVersion: "0.5.2", genVersion: "2.788.4", - userAgent: "speakeasy-sdk/typescript 0.5.1 2.788.4 1.0.0 @openrouter/sdk", + userAgent: "speakeasy-sdk/typescript 0.5.2 2.788.4 1.0.0 @openrouter/sdk", } as const; diff --git a/src/lib/model-result.ts b/src/lib/model-result.ts index c3a4a8b9..02bcff89 100644 --- a/src/lib/model-result.ts +++ b/src/lib/model-result.ts @@ -376,8 +376,7 @@ export class ModelResult { } /** - * Execute tools that can auto-execute (don't require approval). - * Processes tool calls that are approved for automatic execution. + * Execute tools that can auto-execute (don't require approval) in parallel. * * @param toolCalls - The tool calls to execute * @param turnContext - The current turn context @@ -387,18 +386,38 @@ export class ModelResult { toolCalls: ParsedToolCall[], turnContext: TurnContext ): Promise[]> { - const results: UnsentToolResult[] = []; - - for (const tc of toolCalls) { + const toolCallPromises = toolCalls.map(async (tc) => { const tool = this.options.tools?.find(t => t.function.name === tc.name); - if (!tool || !hasExecuteFunction(tool)) continue; + if (!tool || !hasExecuteFunction(tool)) { + return null; + } const result = await executeTool(tool, tc as ParsedToolCall, turnContext); if (result.error) { - results.push(createRejectedResult(tc.id, String(tc.name), result.error.message)); - } else { - results.push(createUnsentResult(tc.id, String(tc.name), result.result)); + return createRejectedResult(tc.id, String(tc.name), result.error.message); + } + return createUnsentResult(tc.id, String(tc.name), result.result); + }); + + const settledResults = await Promise.allSettled(toolCallPromises); + + const results: UnsentToolResult[] = []; + for (let i = 0; i < settledResults.length; i++) { + const settled = settledResults[i]; + const tc = toolCalls[i]; + if (!settled || !tc) continue; + + if (settled.status === 'rejected') { + const errorMessage = settled.reason instanceof Error + ? settled.reason.message + : String(settled.reason); + results.push(createRejectedResult(tc.id, String(tc.name), errorMessage) as UnsentToolResult); + continue; + } + + if (settled.value) { + results.push(settled.value as UnsentToolResult); } } @@ -460,9 +479,8 @@ export class ModelResult { } /** - * Execute all tools in a single round. - * Runs each tool call sequentially and collects results for API submission. - * Emits tool.result events after each tool execution completes. + * Execute all tools in a single round in parallel. + * Emits tool.result events after tool execution completes. * * @param toolCalls - The tool calls to execute * @param turnContext - The current turn context @@ -472,16 +490,13 @@ export class ModelResult { toolCalls: ParsedToolCall[], turnContext: TurnContext ): Promise { - const toolResults: models.OpenResponsesFunctionCallOutput[] = []; - - for (const toolCall of toolCalls) { + const toolCallPromises = toolCalls.map(async (toolCall) => { const tool = this.options.tools?.find((t) => t.function.name === toolCall.name); - if (!tool || !hasExecuteFunction(tool)) continue; + if (!tool || !hasExecuteFunction(tool)) { + return null; + } // Check if arguments failed to parse (remained as string instead of object) - // This happens when the model returns invalid JSON for tool call arguments - // We use 'unknown' cast because the type system doesn't know arguments can be a string - // when JSON parsing fails in stream-transformers.ts const args: unknown = toolCall.arguments; if (typeof args === 'string') { const rawArgs = args; @@ -489,7 +504,6 @@ export class ModelResult { `Raw arguments received: "${rawArgs}". ` + `Please provide valid JSON arguments for this tool call.`; - // Emit error event if broadcaster exists if (this.toolEventBroadcaster) { this.toolEventBroadcaster.push({ type: 'tool_result' as const, @@ -498,25 +512,24 @@ export class ModelResult { }); } - toolResults.push({ - type: 'function_call_output' as const, - id: `output_${toolCall.id}`, - callId: toolCall.id, - output: JSON.stringify({ error: errorMessage }), - }); - continue; + return { + type: 'parse_error' as const, + toolCall, + output: { + type: 'function_call_output' as const, + id: `output_${toolCall.id}`, + callId: toolCall.id, + output: JSON.stringify({ error: errorMessage }), + }, + }; } - // Track preliminary results for this specific tool call const preliminaryResultsForCall: InferToolEventsUnion[] = []; - // Create callback for real-time preliminary results const onPreliminaryResult = this.toolEventBroadcaster ? (callId: string, resultValue: unknown) => { - // Track preliminary results for the tool.result event const typedResult = resultValue as InferToolEventsUnion; preliminaryResultsForCall.push(typedResult); - // Emit preliminary result event this.toolEventBroadcaster?.push({ type: 'preliminary_result' as const, toolCallId: callId, @@ -527,24 +540,73 @@ export class ModelResult { const result = await executeTool(tool, toolCall, turnContext, onPreliminaryResult); - // Emit tool.result event with final result and any preliminary results + return { + type: 'execution' as const, + toolCall, + tool, + result, + preliminaryResultsForCall, + }; + }); + + const settledResults = await Promise.allSettled(toolCallPromises); + const toolResults: models.OpenResponsesFunctionCallOutput[] = []; + + for (let i = 0; i < settledResults.length; i++) { + const settled = settledResults[i]; + const originalToolCall = toolCalls[i]; + if (!settled || !originalToolCall) continue; + + if (settled.status === 'rejected') { + const errorMessage = settled.reason instanceof Error + ? settled.reason.message + : String(settled.reason); + + if (this.toolEventBroadcaster) { + this.toolEventBroadcaster.push({ + type: 'tool_result' as const, + toolCallId: originalToolCall.id, + result: { error: errorMessage } as InferToolOutputsUnion, + }); + } + + toolResults.push({ + type: 'function_call_output' as const, + id: `output_${originalToolCall.id}`, + callId: originalToolCall.id, + output: JSON.stringify({ error: errorMessage }), + }); + continue; + } + + const value = settled.value; + if (!value) continue; + + if (value.type === 'parse_error') { + toolResults.push(value.output); + continue; + } + if (this.toolEventBroadcaster) { - const toolResultEvent = { + this.toolEventBroadcaster.push({ type: 'tool_result' as const, - toolCallId: toolCall.id, - result: (result.error ? { error: result.error.message } : result.result) as InferToolOutputsUnion, - ...(preliminaryResultsForCall.length > 0 && { preliminaryResults: preliminaryResultsForCall }), - }; - this.toolEventBroadcaster.push(toolResultEvent); + toolCallId: value.toolCall.id, + result: (value.result.error + ? { error: value.result.error.message } + : value.result.result) as InferToolOutputsUnion, + ...(value.preliminaryResultsForCall.length > 0 && { + preliminaryResults: value.preliminaryResultsForCall, + }), + }); } toolResults.push({ type: 'function_call_output' as const, - id: `output_${toolCall.id}`, - callId: toolCall.id, - output: result.error - ? JSON.stringify({ error: result.error.message }) - : JSON.stringify(result.result), + id: `output_${value.toolCall.id}`, + callId: value.toolCall.id, + output: value.result.error + ? JSON.stringify({ error: value.result.error.message }) + : JSON.stringify(value.result.result), }); } diff --git a/src/models/operations/getgeneration.ts b/src/models/operations/getgeneration.ts index 7ed9f3de..9be390cc 100644 --- a/src/models/operations/getgeneration.ts +++ b/src/models/operations/getgeneration.ts @@ -27,6 +27,119 @@ export const ApiType = { */ export type ApiType = OpenEnum; +export const ProviderName = { + AnyScale: "AnyScale", + Atoma: "Atoma", + CentML: "Cent-ML", + CrofAI: "CrofAI", + Enfer: "Enfer", + GoPomelo: "GoPomelo", + HuggingFace: "HuggingFace", + Hyperbolic2: "Hyperbolic 2", + InoCloud: "InoCloud", + Kluster: "Kluster", + Lambda: "Lambda", + Lepton: "Lepton", + Lynn2: "Lynn 2", + Lynn: "Lynn", + Mancer: "Mancer", + Meta: "Meta", + Modal: "Modal", + Nineteen: "Nineteen", + OctoAI: "OctoAI", + Recursal: "Recursal", + Reflection: "Reflection", + Replicate: "Replicate", + SambaNova2: "SambaNova 2", + SFCompute: "SF Compute", + Targon: "Targon", + Together2: "Together 2", + Ubicloud: "Ubicloud", + OneDotAI: "01.AI", + Ai21: "AI21", + AionLabs: "AionLabs", + Alibaba: "Alibaba", + Ambient: "Ambient", + AmazonBedrock: "Amazon Bedrock", + AmazonNova: "Amazon Nova", + Anthropic: "Anthropic", + ArceeAI: "Arcee AI", + AtlasCloud: "AtlasCloud", + Avian: "Avian", + Azure: "Azure", + BaseTen: "BaseTen", + BytePlus: "BytePlus", + BlackForestLabs: "Black Forest Labs", + Cerebras: "Cerebras", + Chutes: "Chutes", + Cirrascale: "Cirrascale", + Clarifai: "Clarifai", + Cloudflare: "Cloudflare", + Cohere: "Cohere", + Crusoe: "Crusoe", + DeepInfra: "DeepInfra", + DeepSeek: "DeepSeek", + Featherless: "Featherless", + Fireworks: "Fireworks", + Friendli: "Friendli", + GMICloud: "GMICloud", + Google: "Google", + GoogleAIStudio: "Google AI Studio", + Groq: "Groq", + Hyperbolic: "Hyperbolic", + Inception: "Inception", + Inceptron: "Inceptron", + InferenceNet: "InferenceNet", + Infermatic: "Infermatic", + Inflection: "Inflection", + Liquid: "Liquid", + Mara: "Mara", + Mancer2: "Mancer 2", + Minimax: "Minimax", + ModelRun: "ModelRun", + Mistral: "Mistral", + Modular: "Modular", + MoonshotAI: "Moonshot AI", + Morph: "Morph", + NCompass: "NCompass", + Nebius: "Nebius", + NextBit: "NextBit", + Novita: "Novita", + Nvidia: "Nvidia", + OpenAI: "OpenAI", + OpenInference: "OpenInference", + Parasail: "Parasail", + Perplexity: "Perplexity", + Phala: "Phala", + Relace: "Relace", + SambaNova: "SambaNova", + Seed: "Seed", + SiliconFlow: "SiliconFlow", + Sourceful: "Sourceful", + Stealth: "Stealth", + StreamLake: "StreamLake", + Switchpoint: "Switchpoint", + Together: "Together", + Upstage: "Upstage", + Venice: "Venice", + WandB: "WandB", + Xiaomi: "Xiaomi", + XAI: "xAI", + ZAi: "Z.AI", + FakeProvider: "FakeProvider", +} as const; +export type ProviderName = OpenEnum; + +export type ProviderResponse = { + id?: string | undefined; + endpointId?: string | undefined; + modelPermaslug?: string | undefined; + providerName?: ProviderName | undefined; + status: number | null; + latency?: number | undefined; + isByok?: boolean | undefined; +}; + /** * Generation data */ @@ -163,6 +276,10 @@ export type GetGenerationData = { * Router used for the request (e.g., openrouter/auto) */ router: string | null; + /** + * List of provider responses for this generation, including fallback attempts + */ + providerResponses: Array | null; }; /** @@ -200,6 +317,41 @@ export function getGenerationRequestToJSON( export const ApiType$inboundSchema: z.ZodType = openEnums .inboundSchema(ApiType); +/** @internal */ +export const ProviderName$inboundSchema: z.ZodType = + openEnums.inboundSchema(ProviderName); + +/** @internal */ +export const ProviderResponse$inboundSchema: z.ZodType< + ProviderResponse, + unknown +> = z.object({ + id: z.string().optional(), + endpoint_id: z.string().optional(), + model_permaslug: z.string().optional(), + provider_name: ProviderName$inboundSchema.optional(), + status: z.nullable(z.number()), + latency: z.number().optional(), + is_byok: z.boolean().optional(), +}).transform((v) => { + return remap$(v, { + "endpoint_id": "endpointId", + "model_permaslug": "modelPermaslug", + "provider_name": "providerName", + "is_byok": "isByok", + }); +}); + +export function providerResponseFromJSON( + jsonString: string, +): SafeParseResult { + return safeParse( + jsonString, + (x) => ProviderResponse$inboundSchema.parse(JSON.parse(x)), + `Failed to parse 'ProviderResponse' from JSON`, + ); +} + /** @internal */ export const GetGenerationData$inboundSchema: z.ZodType< GetGenerationData, @@ -238,6 +390,9 @@ export const GetGenerationData$inboundSchema: z.ZodType< external_user: z.nullable(z.string()), api_type: z.nullable(ApiType$inboundSchema), router: z.nullable(z.string()), + provider_responses: z.nullable( + z.array(z.lazy(() => ProviderResponse$inboundSchema)), + ), }).transform((v) => { return remap$(v, { "upstream_id": "upstreamId", @@ -265,6 +420,7 @@ export const GetGenerationData$inboundSchema: z.ZodType< "native_finish_reason": "nativeFinishReason", "external_user": "externalUser", "api_type": "apiType", + "provider_responses": "providerResponses", }); }); diff --git a/src/models/providername.ts b/src/models/providername.ts index 3e7ccef8..9a8937b4 100644 --- a/src/models/providername.ts +++ b/src/models/providername.ts @@ -11,6 +11,7 @@ export const ProviderName = { Ai21: "AI21", AionLabs: "AionLabs", Alibaba: "Alibaba", + Ambient: "Ambient", AmazonBedrock: "Amazon Bedrock", AmazonNova: "Amazon Nova", Anthropic: "Anthropic", diff --git a/src/models/schema0.ts b/src/models/schema0.ts index 2fc01894..cad90d32 100644 --- a/src/models/schema0.ts +++ b/src/models/schema0.ts @@ -11,6 +11,7 @@ export const Schema0Enum = { Ai21: "AI21", AionLabs: "AionLabs", Alibaba: "Alibaba", + Ambient: "Ambient", AmazonBedrock: "Amazon Bedrock", AmazonNova: "Amazon Nova", Anthropic: "Anthropic", diff --git a/tests/e2e/call-model-tools.test.ts b/tests/e2e/call-model-tools.test.ts index 9a61191c..67f48338 100644 --- a/tests/e2e/call-model-tools.test.ts +++ b/tests/e2e/call-model-tools.test.ts @@ -773,4 +773,206 @@ describe('Enhanced Tool Support for callModel', () => { expect(output.unit).toBe('F'); }); }); + + describe('Parallel Tool Execution', () => { + it('should execute multiple tools in parallel', async () => { + const tool100ms = { + type: ToolType.Function, + function: { + name: 'tool_100ms', + description: 'A tool that takes 100ms to execute', + inputSchema: z.object({}), + outputSchema: z.object({ duration: z.number(), order: z.number() }), + execute: async (_params: Record, _context) => { + await new Promise((resolve) => setTimeout(resolve, 100)); + return { duration: 100, order: 1 }; + }, + }, + }; + + const tool200ms = { + type: ToolType.Function, + function: { + name: 'tool_200ms', + description: 'A tool that takes 200ms to execute', + inputSchema: z.object({}), + outputSchema: z.object({ duration: z.number(), order: z.number() }), + execute: async (_params: Record, _context) => { + await new Promise((resolve) => setTimeout(resolve, 200)); + return { duration: 200, order: 2 }; + }, + }, + }; + + const tool300ms = { + type: ToolType.Function, + function: { + name: 'tool_300ms', + description: 'A tool that takes 300ms to execute', + inputSchema: z.object({}), + outputSchema: z.object({ duration: z.number(), order: z.number() }), + execute: async (_params: Record, _context) => { + await new Promise((resolve) => setTimeout(resolve, 300)); + return { duration: 300, order: 3 }; + }, + }, + }; + + const response = await client.callModel({ + model: 'openai/gpt-4o-mini', + input: [ + { + role: 'user', + content: + 'Call all three tools: tool_100ms, tool_200ms, and tool_300ms. Do NOT call any other tools. Just call these three.', + }, + ], + tools: [tool100ms, tool200ms, tool300ms], + stopWhen: stepCountIs(1), + }); + + // Collect all tool outputs to verify all 3 tools were executed + const toolResults: Array<{ output: string }> = []; + for await (const item of response.getItemsStream()) { + if (item.type === 'function_call_output') { + toolResults.push({ output: item.output }); + } + } + + // Verify all 3 tools were executed by checking their outputs + const durations = toolResults.map( + (r) => (JSON.parse(r.output) as { duration: number }).duration + ); + expect(durations).toContain(100); + expect(durations).toContain(200); + expect(durations).toContain(300); + }, 30000); + + it('should collect all errors when multiple tools fail in parallel', async () => { + const successTool = { + type: ToolType.Function, + function: { + name: 'success_tool', + description: 'A tool that succeeds', + inputSchema: z.object({}), + outputSchema: z.object({ status: z.string() }), + execute: async (_params: Record, _context) => { + return { status: 'success' }; + }, + }, + }; + + const failTool = { + type: ToolType.Function, + function: { + name: 'fail_tool', + description: 'A tool that always fails', + inputSchema: z.object({}), + outputSchema: z.object({ status: z.string() }), + execute: async (_params: Record, _context) => { + throw new Error('Intentional failure'); + }, + }, + }; + + const response = await client.callModel({ + model: 'openai/gpt-4o-mini', + input: [ + { + role: 'user', + content: + 'Call both tools: success_tool and fail_tool. Do NOT call any other tools.', + }, + ], + tools: [successTool, failTool], + stopWhen: stepCountIs(2), + }); + + // Collect all tool outputs to verify error handling + const toolResults: Array<{ output: string }> = []; + for await (const item of response.getItemsStream()) { + if (item.type === 'function_call_output') { + toolResults.push({ output: item.output }); + } + } + + // Parse outputs and check for success/error results + const outputs = toolResults.map( + (r) => JSON.parse(r.output) as { status?: string; error?: string } + ); + const hasSuccess = outputs.some((o) => o.status === 'success'); + const hasError = outputs.some((o) => o.error?.includes('Intentional failure')); + + // At least one tool was called + expect(hasSuccess || hasError).toBe(true); + + // If both tools were called, verify we have both success and error + if (toolResults.length >= 2) { + expect(hasSuccess).toBe(true); + expect(hasError).toBe(true); + } + }, 30000); + + it('should return results in original tool call order regardless of completion order', async () => { + const slowTool = { + type: ToolType.Function, + function: { + name: 'slow_tool', + description: 'A slow tool (call this first)', + inputSchema: z.object({}), + outputSchema: z.object({ name: z.string(), completedAt: z.number() }), + execute: async (_params: Record, _context) => { + await new Promise((resolve) => setTimeout(resolve, 200)); + return { name: 'slow_tool', completedAt: Date.now() }; + }, + }, + }; + + const fastTool = { + type: ToolType.Function, + function: { + name: 'fast_tool', + description: 'A fast tool (call this second)', + inputSchema: z.object({}), + outputSchema: z.object({ name: z.string(), completedAt: z.number() }), + execute: async (_params: Record, _context) => { + await new Promise((resolve) => setTimeout(resolve, 50)); + return { name: 'fast_tool', completedAt: Date.now() }; + }, + }, + }; + + const response = await client.callModel({ + model: 'openai/gpt-4o-mini', + input: [ + { + role: 'user', + content: + 'Call slow_tool first, then call fast_tool second. You MUST call them in this exact order.', + }, + ], + tools: [slowTool, fastTool], + stopWhen: stepCountIs(1), + }); + + // Collect both function calls and their outputs + const functionCalls: Array<{ name: string; id: string }> = []; + const toolResults: Array<{ callId: string; output: string }> = []; + + for await (const item of response.getItemsStream()) { + if (item.type === 'function_call') { + functionCalls.push({ name: item.name, id: item.callId }); + } + if (item.type === 'function_call_output') { + toolResults.push({ callId: item.callId, output: item.output }); + } + } + + // Verify results are returned in the same order as calls (by callId matching) + if (functionCalls.length === 2 && toolResults.length === 2) { + expect(toolResults[0]?.callId).toBe(functionCalls[0]?.id); + expect(toolResults[1]?.callId).toBe(functionCalls[1]?.id); + } + }, 30000); + }); });