@elevasis/core 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/README.md +41 -41
- package/src/__tests__/publish.test.ts +18 -18
- package/src/__tests__/{template-foundations-compatibility.test.ts → template-core-compatibility.test.ts} +99 -99
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +1135 -1131
- package/src/_gen/__tests__/scaffold-contracts.test.ts +53 -53
- package/src/_gen/scaffold-contracts.ts +45 -45
- package/src/auth/multi-tenancy/credentials/README.md +38 -38
- package/src/auth/multi-tenancy/credentials/index.ts +6 -6
- package/src/auth/multi-tenancy/credentials/server/encryption.ts +39 -39
- package/src/auth/multi-tenancy/credentials/server/service.ts +60 -60
- package/src/auth/multi-tenancy/index.ts +17 -17
- package/src/auth/multi-tenancy/invitations/api-schemas.ts +107 -107
- package/src/auth/multi-tenancy/invitations/index.ts +37 -37
- package/src/auth/multi-tenancy/invitations/invitation.ts +86 -86
- package/src/auth/multi-tenancy/invitations/server/index.ts +25 -25
- package/src/auth/multi-tenancy/invitations/server/transforms.ts +24 -24
- package/src/auth/multi-tenancy/invitations/server/workos.ts +24 -24
- package/src/auth/multi-tenancy/invitations/supabase.ts +50 -50
- package/src/auth/multi-tenancy/memberships/api-schemas.ts +126 -126
- package/src/auth/multi-tenancy/memberships/index.ts +21 -21
- package/src/auth/multi-tenancy/memberships/membership.ts +138 -138
- package/src/auth/multi-tenancy/memberships/server/index.ts +15 -15
- package/src/auth/multi-tenancy/memberships/server/transforms.ts +32 -32
- package/src/auth/multi-tenancy/memberships/server/workos.ts +21 -21
- package/src/auth/multi-tenancy/memberships/supabase.ts +46 -46
- package/src/auth/multi-tenancy/organizations/api-schemas.ts +128 -128
- package/src/auth/multi-tenancy/organizations/index.ts +23 -23
- package/src/auth/multi-tenancy/organizations/organization.ts +24 -24
- package/src/auth/multi-tenancy/organizations/server/index.ts +10 -10
- package/src/auth/multi-tenancy/organizations/server/transforms.ts +35 -35
- package/src/auth/multi-tenancy/organizations/server/workos.ts +20 -20
- package/src/auth/multi-tenancy/types.ts +83 -83
- package/src/auth/multi-tenancy/users/api-schemas.ts +194 -194
- package/src/auth/multi-tenancy/users/index.ts +27 -27
- package/src/auth/multi-tenancy/users/server/index.ts +19 -19
- package/src/auth/multi-tenancy/users/server/transforms.ts +21 -21
- package/src/auth/multi-tenancy/users/server/workos.ts +16 -16
- package/src/auth/multi-tenancy/users/user.ts +65 -65
- package/src/business/README.md +52 -52
- package/src/business/__tests__/entities-published.test.ts +33 -33
- package/src/business/acquisition/api-schemas.ts +759 -759
- package/src/business/acquisition/index.ts +109 -109
- package/src/business/acquisition/types.ts +402 -402
- package/src/business/base-entities.test.ts +481 -481
- package/src/business/base-entities.ts +241 -241
- package/src/business/entities-published.ts +24 -24
- package/src/business/index.ts +15 -15
- package/src/business/pdf/browser/pdfmake-browser.ts +229 -229
- package/src/business/pdf/index.ts +10 -10
- package/src/business/pdf/server/index.ts +21 -21
- package/src/business/pdf/server/themes/default.ts +8 -8
- package/src/business/pdf/server/themes/index.ts +9 -9
- package/src/business/pdf/server/themes/types.ts +8 -8
- package/src/business/pdf/types.ts +272 -272
- package/src/business/projects/index.ts +2 -1
- package/src/business/projects/sse-events.ts +21 -0
- package/src/business/projects/types.ts +89 -89
- package/src/business/sales/api-schemas.ts +75 -75
- package/src/business/seo/__tests__/linking.test.ts +549 -549
- package/src/business/seo/__tests__/types.test.ts +404 -404
- package/src/business/seo/index.ts +2 -2
- package/src/business/seo/linking.ts +281 -281
- package/src/business/seo/types.ts +199 -199
- package/src/commands/queue/index.ts +3 -3
- package/src/commands/queue/schemas.test.ts +593 -593
- package/src/commands/queue/schemas.ts +125 -125
- package/src/commands/queue/sse-events.ts +61 -61
- package/src/commands/queue/types/action.ts +52 -52
- package/src/commands/queue/types/checkpoint.ts +44 -44
- package/src/commands/queue/types/index.ts +7 -7
- package/src/commands/queue/types/task.ts +116 -116
- package/src/commands/queue/types.ts +14 -14
- package/src/content/distribution-metadata.ts +61 -61
- package/src/content/index.ts +10 -10
- package/src/deployments/index.ts +22 -22
- package/src/execution/core/__tests__/archived-logs.test.ts +72 -72
- package/src/execution/core/index.ts +11 -11
- package/src/execution/core/runner-types.ts +80 -80
- package/src/execution/core/server/environment.ts +31 -31
- package/src/execution/core/sse-executions.ts +119 -119
- package/src/execution/core/types.ts +29 -29
- package/src/execution/engine/__tests__/fixtures/test-agents.ts +4 -4
- package/src/execution/engine/__tests__/timeout.test.ts +565 -565
- package/src/execution/engine/agent/__tests__/errors.test.ts +508 -508
- package/src/execution/engine/agent/actions/__tests__/processor.test.ts +531 -531
- package/src/execution/engine/agent/actions/executor.ts +205 -205
- package/src/execution/engine/agent/actions/navigate-knowledge-executor.ts +230 -230
- package/src/execution/engine/agent/actions/processor.ts +116 -116
- package/src/execution/engine/agent/actions/types.ts +70 -70
- package/src/execution/engine/agent/core/agent.ts +810 -810
- package/src/execution/engine/agent/core/types.ts +155 -155
- package/src/execution/engine/agent/errors.ts +251 -251
- package/src/execution/engine/agent/index.ts +78 -78
- package/src/execution/engine/agent/knowledge-map/types.ts +106 -106
- package/src/execution/engine/agent/knowledge-map/utils.ts +101 -101
- package/src/execution/engine/agent/memory/__tests__/manager.test.ts +754 -754
- package/src/execution/engine/agent/memory/domains.ts +99 -99
- package/src/execution/engine/agent/memory/manager.ts +365 -365
- package/src/execution/engine/agent/memory/processor.ts +66 -66
- package/src/execution/engine/agent/memory/types.ts +90 -90
- package/src/execution/engine/agent/memory/utils.ts +134 -134
- package/src/execution/engine/agent/observability/logging.ts +467 -467
- package/src/execution/engine/agent/observability/types.ts +64 -64
- package/src/execution/engine/agent/reasoning/adapters/agent-adapter-helpers.ts +349 -349
- package/src/execution/engine/agent/reasoning/processor.ts +92 -92
- package/src/execution/engine/agent/reasoning/prompt-sections/base-actions.ts +134 -134
- package/src/execution/engine/agent/reasoning/prompt-sections/completion.ts +49 -49
- package/src/execution/engine/agent/reasoning/prompt-sections/knowledge-map.ts +93 -93
- package/src/execution/engine/agent/reasoning/prompt-sections/memory.ts +65 -65
- package/src/execution/engine/agent/reasoning/prompt-sections/tools.ts +44 -44
- package/src/execution/engine/agent/reasoning/request-builder.ts +169 -169
- package/src/execution/engine/agent/reasoning/types.ts +18 -18
- package/src/execution/engine/base/errors.ts +118 -118
- package/src/execution/engine/base/index.ts +2 -2
- package/src/execution/engine/base/logging.ts +31 -31
- package/src/execution/engine/base/serialization.ts +324 -324
- package/src/execution/engine/base/types.ts +126 -126
- package/src/execution/engine/base/utils.ts +41 -41
- package/src/execution/engine/index.ts +434 -434
- package/src/execution/engine/interface/index.ts +1 -1
- package/src/execution/engine/interface/types.ts +62 -62
- package/src/execution/engine/llm/__tests__/model-info.test.ts +50 -50
- package/src/execution/engine/llm/__tests__/model-validation.test.ts +321 -321
- package/src/execution/engine/llm/__tests__/response-schema-validator.test.ts +115 -115
- package/src/execution/engine/llm/adapters/__tests__/adapter-factory.test.ts +375 -375
- package/src/execution/engine/llm/adapters/__tests__/anthropic-adapter.test.ts +463 -463
- package/src/execution/engine/llm/adapters/__tests__/anthropic.integration.test.ts +177 -177
- package/src/execution/engine/llm/adapters/__tests__/google-adapter.test.ts +722 -722
- package/src/execution/engine/llm/adapters/__tests__/google.integration.test.ts +376 -376
- package/src/execution/engine/llm/adapters/__tests__/openai-adapter.test.ts +551 -551
- package/src/execution/engine/llm/adapters/__tests__/openrouter-adapter.test.ts +563 -563
- package/src/execution/engine/llm/adapters/__tests__/openrouter.integration.test.ts +105 -105
- package/src/execution/engine/llm/adapters/__tests__/universal-adapter.test.ts +537 -537
- package/src/execution/engine/llm/adapters/circuit-breaker.ts +147 -147
- package/src/execution/engine/llm/adapters/index.ts +17 -17
- package/src/execution/engine/llm/adapters/mock-adapter.ts +116 -116
- package/src/execution/engine/llm/adapters/server/adapter-factory.ts +130 -130
- package/src/execution/engine/llm/adapters/server/anthropic.ts +137 -137
- package/src/execution/engine/llm/adapters/server/google.ts +283 -283
- package/src/execution/engine/llm/adapters/server/index.ts +12 -12
- package/src/execution/engine/llm/adapters/server/openai.ts +206 -206
- package/src/execution/engine/llm/adapters/server/openrouter.ts +235 -235
- package/src/execution/engine/llm/adapters/universal-adapter.ts +230 -230
- package/src/execution/engine/llm/errors.ts +186 -186
- package/src/execution/engine/llm/model-info.ts +332 -332
- package/src/execution/engine/llm/response-schema-validator.ts +113 -113
- package/src/execution/engine/llm/types.ts +86 -86
- package/src/execution/engine/test-utils/index.ts +6 -6
- package/src/execution/engine/test-utils/mocks.ts +56 -56
- package/src/execution/engine/tools/integration/base-integration-adapter.ts +50 -50
- package/src/execution/engine/tools/integration/index.ts +53 -53
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/anymailfinder-adapter.ts +73 -73
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/anymailfinder-tools.ts +209 -209
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/find-company-email/index.ts +82 -82
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/find-decision-maker-email/index.ts +122 -122
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/find-person-email/index.ts +89 -89
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/verify-email/index.ts +84 -84
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/index.ts +16 -16
- package/src/execution/engine/tools/integration/server/adapters/apify/__tests__/apify-run-actor.integration.test.ts +293 -293
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +100 -100
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-tools.ts +217 -217
- package/src/execution/engine/tools/integration/server/adapters/apify/fetch/get-dataset-items/index.ts +92 -92
- package/src/execution/engine/tools/integration/server/adapters/apify/fetch/run-actor/index.ts +218 -218
- package/src/execution/engine/tools/integration/server/adapters/apify/fetch/start-actor/index.ts +87 -87
- package/src/execution/engine/tools/integration/server/adapters/apify/index.ts +11 -11
- package/src/execution/engine/tools/integration/server/adapters/attio/__tests__/attio-crud.integration.test.ts +361 -361
- package/src/execution/engine/tools/integration/server/adapters/attio/attio-adapter.ts +162 -162
- package/src/execution/engine/tools/integration/server/adapters/attio/attio-tools.ts +594 -594
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/create-attribute/index.ts +214 -214
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/create-note/index.ts +152 -152
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/create-record/index.ts +141 -141
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/delete-note/index.ts +86 -86
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/delete-record/index.ts +105 -105
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/get-record/index.ts +118 -118
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-attributes/index.ts +165 -165
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-notes/index.ts +96 -96
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-objects/index.ts +104 -104
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-records/index.ts +156 -156
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/update-attribute/index.ts +220 -220
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/update-record/index.ts +140 -140
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/utils/types.ts +146 -146
- package/src/execution/engine/tools/integration/server/adapters/attio/index.ts +31 -31
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-adapter.ts +210 -210
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-tools.ts +104 -104
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/__tests__/google-sheets.integration.test.ts +261 -261
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/google-sheets-adapter.ts +1189 -1189
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/google-sheets-tools.ts +641 -641
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/index.ts +18 -18
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/activate-campaign/index.ts +86 -86
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/add-to-campaign/__tests__/index.test.ts +289 -289
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/add-to-campaign/index.ts +154 -154
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/bulk-add-leads/__tests__/index.test.ts +325 -325
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/bulk-add-leads/index.ts +153 -153
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/bulk-delete-leads/index.ts +84 -84
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/create-campaign/index.ts +125 -125
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/create-inbox-test/index.ts +107 -107
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/delete-campaign/index.ts +85 -85
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-account-health/index.ts +91 -91
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-campaign/index.ts +92 -92
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-campaign-analytics/__tests__/index.test.ts +195 -195
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-campaign-analytics/index.ts +113 -113
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-daily-campaign-analytics/index.ts +104 -104
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-emails/index.ts +155 -155
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-step-analytics/__tests__/index.test.ts +196 -196
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-step-analytics/index.ts +102 -102
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/list-campaigns/__tests__/index.test.ts +189 -189
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/list-campaigns/index.ts +87 -87
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/list-leads/index.ts +112 -112
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/patch-lead/index.ts +76 -76
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/pause-campaign/index.ts +86 -86
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/remove-from-subsequence/index.ts +98 -98
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/send-reply/index.ts +126 -126
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-campaign/__tests__/index.test.ts +193 -193
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-campaign/index.ts +99 -99
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-interest-status/__tests__/index.test.ts +621 -621
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-interest-status/index.ts +125 -125
- package/src/execution/engine/tools/integration/server/adapters/instantly/index.ts +29 -29
- package/src/execution/engine/tools/integration/server/adapters/instantly/instantly-adapter.ts +178 -178
- package/src/execution/engine/tools/integration/server/adapters/instantly/instantly-tools.ts +1473 -1473
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/fetch/check-credits/index.ts +59 -59
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/fetch/verify-email/index.ts +102 -102
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/index.ts +17 -17
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/millionverifier-adapter.ts +80 -80
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/millionverifier-tools.ts +102 -102
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/get-email/index.ts +102 -102
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.ts +134 -134
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/utils/types.ts +75 -75
- package/src/execution/engine/tools/integration/server/adapters/resend/index.ts +27 -27
- package/src/execution/engine/tools/integration/server/adapters/resend/resend-adapter.ts +108 -108
- package/src/execution/engine/tools/integration/server/adapters/resend/resend-tools.ts +132 -132
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/create-envelope/index.ts +274 -274
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/download-document/index.ts +230 -230
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/get-envelope/index.ts +133 -133
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/void-envelope/index.ts +90 -90
- package/src/execution/engine/tools/integration/server/adapters/stripe/fetch/utils/types.ts +210 -210
- package/src/execution/engine/tools/integration/server/adapters/stripe/stripe-adapter.ts +517 -517
- package/src/execution/engine/tools/integration/server/adapters/stripe/stripe-tools.ts +309 -309
- package/src/execution/engine/tools/integration/server/adapters/tomba/fetch/domain-search/index.ts +133 -133
- package/src/execution/engine/tools/integration/server/adapters/tomba/fetch/email-finder/index.ts +122 -122
- package/src/execution/engine/tools/integration/server/adapters/tomba/fetch/email-verifier/index.ts +111 -111
- package/src/execution/engine/tools/integration/server/adapters/tomba/index.ts +11 -11
- package/src/execution/engine/tools/integration/server/adapters/tomba/tomba-adapter.ts +78 -78
- package/src/execution/engine/tools/integration/server/adapters/tomba/tomba-tools.ts +222 -222
- package/src/execution/engine/tools/integration/server/index.ts +61 -61
- package/src/execution/engine/tools/integration/service.ts +161 -161
- package/src/execution/engine/tools/integration/tool.ts +253 -253
- package/src/execution/engine/tools/integration/types/anymailfinder.ts +74 -74
- package/src/execution/engine/tools/integration/types/apify.ts +92 -92
- package/src/execution/engine/tools/integration/types/index.ts +19 -19
- package/src/execution/engine/tools/integration/types/instantly.ts +557 -557
- package/src/execution/engine/tools/integration/types/millionverifier.ts +56 -56
- package/src/execution/engine/tools/integration/types/stripe.ts +162 -162
- package/src/execution/engine/tools/integration/types/tomba.ts +94 -94
- package/src/execution/engine/tools/lead-service-types.ts +884 -884
- package/src/execution/engine/tools/llm/index.ts +11 -11
- package/src/execution/engine/tools/llm/server/index.ts +8 -8
- package/src/execution/engine/tools/llm/server/llm-call-tool.ts +118 -118
- package/src/execution/engine/tools/platform/__tests__/pdf.test.ts +441 -441
- package/src/execution/engine/tools/platform/acquisition/company-tools.ts +248 -248
- package/src/execution/engine/tools/platform/acquisition/contact-tools.ts +319 -319
- package/src/execution/engine/tools/platform/acquisition/index.ts +43 -43
- package/src/execution/engine/tools/platform/acquisition/list-tools.ts +148 -148
- package/src/execution/engine/tools/platform/acquisition/types.ts +260 -260
- package/src/execution/engine/tools/platform/email/index.ts +122 -122
- package/src/execution/engine/tools/platform/email/types.ts +96 -96
- package/src/execution/engine/tools/platform/index.ts +157 -157
- package/src/execution/engine/tools/platform/notification.ts +81 -81
- package/src/execution/engine/tools/platform/pdf/index.ts +110 -110
- package/src/execution/engine/tools/platform/pdf/types.ts +77 -77
- package/src/execution/engine/tools/platform/scheduler.ts +87 -87
- package/src/execution/engine/tools/platform/storage/index.ts +370 -370
- package/src/execution/engine/tools/platform/types.ts +148 -148
- package/src/execution/engine/tools/registry.ts +700 -699
- package/src/execution/engine/tools/tool-maps.ts +786 -786
- package/src/execution/engine/tools/types.ts +233 -233
- package/src/execution/engine/workflow/__tests__/errors.test.ts +139 -139
- package/src/execution/engine/workflow/errors.ts +63 -63
- package/src/execution/engine/workflow/helpers/index.ts +11 -11
- package/src/execution/engine/workflow/helpers/server/index.ts +8 -8
- package/src/execution/engine/workflow/helpers/server/llm-call.ts +93 -93
- package/src/execution/engine/workflow/index.ts +19 -19
- package/src/execution/engine/workflow/log-truncate.ts +26 -26
- package/src/execution/engine/workflow/logging.ts +191 -191
- package/src/execution/engine/workflow/types.ts +182 -182
- package/src/execution/engine/workflow/utils.ts +280 -280
- package/src/execution/engine/workflow/workflow.ts +168 -168
- package/src/execution/index.ts +3 -3
- package/src/execution/scheduler/__tests__/api-schemas.test.ts +733 -733
- package/src/execution/scheduler/__tests__/utils.test.ts +1009 -1009
- package/src/execution/scheduler/api-schemas.ts +296 -296
- package/src/execution/scheduler/index.ts +50 -50
- package/src/execution/scheduler/schemas.ts +264 -264
- package/src/execution/scheduler/types.ts +111 -111
- package/src/execution/scheduler/utils.ts +364 -364
- package/src/forms/index.ts +7 -7
- package/src/forms/schemas.ts +69 -69
- package/src/forms/types.ts +70 -70
- package/src/index.ts +71 -60
- package/src/integrations/credentials/__tests__/schemas.test.ts +82 -82
- package/src/integrations/credentials/__tests__/utils.test.ts +144 -144
- package/src/integrations/credentials/api-schemas.ts +143 -143
- package/src/integrations/credentials/index.ts +32 -32
- package/src/integrations/credentials/schemas.ts +164 -164
- package/src/integrations/credentials/utils.ts +59 -59
- package/src/integrations/oauth/__tests__/provider-registry.test.ts +59 -59
- package/src/integrations/oauth/api-schemas.ts +92 -92
- package/src/integrations/oauth/index.ts +19 -19
- package/src/integrations/oauth/provider-registry.ts +61 -61
- package/src/integrations/oauth/server/__tests__/refresh-concurrent.test.ts +183 -183
- package/src/integrations/oauth/server/__tests__/refresh.test.ts +577 -577
- package/src/integrations/oauth/server/credentials.ts +39 -39
- package/src/integrations/oauth/server/refresh.ts +214 -214
- package/src/integrations/oauth/types.ts +34 -34
- package/src/integrations/webhook-endpoints/__tests__/api-schemas.test.ts +318 -318
- package/src/integrations/webhook-endpoints/api-schemas.ts +102 -102
- package/src/integrations/webhook-endpoints/index.ts +28 -28
- package/src/integrations/webhook-endpoints/types.ts +51 -51
- package/src/operations/activities/api-schemas.ts +79 -79
- package/src/operations/activities/index.ts +9 -9
- package/src/operations/activities/sse-events.ts +30 -30
- package/src/operations/activities/types.ts +63 -63
- package/src/operations/debug-logs/client.ts +60 -60
- package/src/operations/debug-logs/debug-logger.ts +83 -83
- package/src/operations/debug-logs/index.ts +8 -8
- package/src/operations/debug-logs/server.ts +19 -19
- package/src/operations/debug-logs/types.ts +33 -33
- package/src/operations/index.ts +50 -50
- package/src/operations/notifications/api-schemas.ts +91 -91
- package/src/operations/notifications/index.ts +3 -3
- package/src/operations/notifications/sse-events.ts +21 -21
- package/src/operations/notifications/types.ts +47 -47
- package/src/operations/observability/__tests__/openrouter-cost-flow.test.ts +297 -297
- package/src/operations/observability/__tests__/utils.test.ts +54 -54
- package/src/operations/observability/ai-usage-collector.ts +64 -64
- package/src/operations/observability/index.ts +13 -13
- package/src/operations/observability/metrics-collector.ts +49 -49
- package/src/operations/observability/schemas.ts +39 -39
- package/src/operations/observability/types.ts +463 -463
- package/src/operations/observability/utils.ts +77 -77
- package/src/operations/sessions/__tests__/manager.test.ts +821 -821
- package/src/operations/sessions/index.ts +26 -26
- package/src/operations/sessions/server/manager.ts +90 -90
- package/src/operations/sessions/server/session.ts +180 -180
- package/src/operations/sessions/types.ts +98 -98
- package/src/operations/triggers/index.ts +12 -12
- package/src/operations/triggers/webhook/definitions/instantly-account-error.ts +44 -44
- package/src/operations/triggers/webhook/definitions/instantly-auto-reply-received.ts +51 -51
- package/src/operations/triggers/webhook/definitions/instantly-campaign-completed.ts +45 -45
- package/src/operations/triggers/webhook/definitions/instantly-email-bounced.ts +49 -49
- package/src/operations/triggers/webhook/definitions/instantly-lead-unsubscribed.ts +45 -45
- package/src/operations/triggers/webhook/definitions/instantly-reply-received.ts +54 -54
- package/src/operations/triggers/webhook/index.ts +35 -35
- package/src/operations/triggers/webhook/types.ts +74 -74
- package/src/organization-model/README.md +97 -97
- package/src/organization-model/__tests__/defaults.test.ts +175 -175
- package/src/organization-model/__tests__/domains/customers.test.ts +295 -295
- package/src/organization-model/__tests__/domains/goals.test.ts +479 -479
- package/src/organization-model/__tests__/domains/identity.test.ts +279 -279
- package/src/organization-model/__tests__/domains/navigation.test.ts +212 -212
- package/src/organization-model/__tests__/domains/offerings.test.ts +419 -419
- package/src/organization-model/__tests__/domains/operations.test.ts +203 -203
- package/src/organization-model/__tests__/domains/resource-mappings.test.ts +362 -362
- package/src/organization-model/__tests__/domains/roles.test.ts +347 -347
- package/src/organization-model/__tests__/domains/statuses.test.ts +243 -243
- package/src/organization-model/__tests__/foundation.test.ts +105 -105
- package/src/organization-model/__tests__/graph.test.ts +894 -894
- package/src/organization-model/__tests__/resolve.test.ts +690 -690
- package/src/organization-model/__tests__/schema.test.ts +407 -407
- package/src/organization-model/contracts.ts +14 -14
- package/src/organization-model/defaults.ts +148 -148
- package/src/organization-model/domains/branding.ts +22 -22
- package/src/organization-model/domains/customers.ts +75 -75
- package/src/organization-model/domains/features.ts +22 -22
- package/src/organization-model/domains/goals.ts +80 -80
- package/src/organization-model/domains/identity.ts +94 -94
- package/src/organization-model/domains/navigation.ts +391 -391
- package/src/organization-model/domains/offerings.ts +66 -66
- package/src/organization-model/domains/operations.ts +85 -85
- package/src/organization-model/domains/projects.ts +48 -48
- package/src/organization-model/domains/prospecting.ts +33 -33
- package/src/organization-model/domains/roles.ts +55 -55
- package/src/organization-model/domains/sales.ts +94 -94
- package/src/organization-model/domains/shared.ts +62 -62
- package/src/organization-model/domains/statuses.ts +130 -130
- package/src/organization-model/foundation.ts +97 -97
- package/src/organization-model/graph/build.ts +399 -399
- package/src/organization-model/graph/index.ts +4 -4
- package/src/organization-model/graph/schema.ts +48 -48
- package/src/organization-model/graph/types.ts +40 -40
- package/src/organization-model/index.ts +13 -13
- package/src/organization-model/organization-graph.mdx +272 -272
- package/src/organization-model/organization-model.mdx +320 -320
- package/src/organization-model/published.ts +85 -85
- package/src/organization-model/resolve.ts +66 -66
- package/src/organization-model/schema.ts +287 -287
- package/src/organization-model/types.ts +46 -46
- package/src/platform/api/index.ts +1 -1
- package/src/platform/api/types.ts +35 -35
- package/src/platform/constants/http.ts +37 -37
- package/src/platform/constants/index.ts +5 -5
- package/src/platform/constants/limits.ts +32 -32
- package/src/platform/constants/resilience.ts +51 -51
- package/src/platform/constants/timeouts.ts +20 -20
- package/src/platform/constants/versions.ts +3 -3
- package/src/platform/registry/__tests__/resource-registry-static.test.ts +347 -347
- package/src/platform/registry/__tests__/resource-registry.integration.test.ts +1028 -1028
- package/src/platform/registry/__tests__/resource-registry.list-executable.test.ts +393 -393
- package/src/platform/registry/__tests__/resource-registry.test.ts +2005 -2005
- package/src/platform/registry/__tests__/serialization.test.ts +1127 -1127
- package/src/platform/registry/command-view.ts +180 -180
- package/src/platform/registry/domains.ts +165 -165
- package/src/platform/registry/index.ts +93 -93
- package/src/platform/registry/reserved.ts +24 -24
- package/src/platform/registry/resource-metadata.ts +59 -59
- package/src/platform/registry/resource-registry.command-queue-groups.test.ts +129 -129
- package/src/platform/registry/resource-registry.ts +876 -876
- package/src/platform/registry/serialization.ts +273 -273
- package/src/platform/registry/serialized-types.ts +231 -231
- package/src/platform/registry/stats-types.ts +66 -66
- package/src/platform/registry/types.ts +404 -404
- package/src/platform/registry/validation.ts +513 -513
- package/src/platform/resilience/__tests__/rate-limiter.test.ts +471 -471
- package/src/platform/resilience/circuit-breaker.ts +164 -164
- package/src/platform/resilience/errors.ts +68 -68
- package/src/platform/resilience/http-error-mapper.ts +129 -129
- package/src/platform/resilience/index.ts +93 -93
- package/src/platform/resilience/rate-limiter-types.ts +46 -46
- package/src/platform/resilience/rate-limiter.ts +140 -140
- package/src/platform/resilience/retry.ts +89 -89
- package/src/platform/resilience/timeout.ts +63 -63
- package/src/platform/sse/events.ts +37 -34
- package/src/platform/sse/index.ts +7 -7
- package/src/platform/utils/__tests__/validation.test.ts +1083 -1083
- package/src/platform/utils/currency.ts +96 -96
- package/src/platform/utils/debounce.ts +52 -52
- package/src/platform/utils/error.ts +41 -41
- package/src/platform/utils/hmac.test.ts +97 -97
- package/src/platform/utils/index.ts +32 -32
- package/src/platform/utils/server/betterstack-logger.ts +210 -210
- package/src/platform/utils/server/hmac.ts +44 -44
- package/src/platform/utils/server/unsubscribe.ts +111 -111
- package/src/platform/utils/token-counter.ts +96 -96
- package/src/platform/utils/validation.ts +425 -425
- package/src/projects/api-schemas.ts +268 -268
- package/src/published.ts +1 -1
- package/src/reference/_generated/contracts.md +611 -607
- package/src/reference/glossary.md +105 -105
- package/src/requests/__tests__/api-schemas.test.ts +277 -277
- package/src/requests/api-schemas.ts +83 -83
- package/src/requests/index.ts +1 -1
- package/src/scaffold-registry/__tests__/index.test.ts +17 -0
- package/src/scaffold-registry/__tests__/schema.test.ts +329 -230
- package/src/scaffold-registry/index.ts +205 -189
- package/src/scaffold-registry/schema.ts +196 -128
- package/src/server.ts +272 -272
- package/src/supabase/database.types.ts +2719 -2719
- package/src/supabase/helpers.ts +20 -20
- package/src/supabase/index.ts +52 -52
- package/src/supabase/server/client.ts +58 -58
- package/src/test-utils/README.md +38 -38
- package/src/test-utils/browser-mocks.ts +54 -54
- package/src/test-utils/fixtures/api-keys.ts +52 -52
- package/src/test-utils/fixtures/index.ts +4 -4
- package/src/test-utils/fixtures/memberships.ts +80 -80
- package/src/test-utils/fixtures/organizations.ts +69 -69
- package/src/test-utils/fixtures/users.ts +79 -79
- package/src/test-utils/index.ts +11 -11
- package/src/test-utils/mocks/index.ts +2 -2
- package/src/test-utils/mocks/supabase.ts +142 -142
- package/src/test-utils/mocks/workos.ts +108 -108
- package/src/test-utils/rls/RLSTestContext.ts +556 -556
- package/src/test-utils/rls/index.ts +1 -1
|
@@ -1,563 +1,563 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenRouter Adapter Tests
|
|
3
|
-
* Verifies OpenRouter adapter configuration, API translation, and options handling
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
7
|
-
import { OpenRouterAdapter, isOpenRouterModel } from '../server/openrouter'
|
|
8
|
-
import { LLMResponseParseError } from '../../errors'
|
|
9
|
-
import type { LLMGenerateRequest } from '../../types'
|
|
10
|
-
import type { LLMModel } from '../../model-info'
|
|
11
|
-
|
|
12
|
-
// Mock OpenAI SDK (OpenRouter uses OpenAI SDK with custom baseURL)
|
|
13
|
-
vi.mock('openai', () => {
|
|
14
|
-
return {
|
|
15
|
-
default: vi.fn().mockImplementation(() => ({
|
|
16
|
-
chat: {
|
|
17
|
-
completions: {
|
|
18
|
-
create: vi.fn()
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}))
|
|
22
|
-
}
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
describe('OpenRouterAdapter', () => {
|
|
26
|
-
const mockOpenAICreate = vi.fn()
|
|
27
|
-
|
|
28
|
-
beforeEach(async () => {
|
|
29
|
-
vi.clearAllMocks()
|
|
30
|
-
|
|
31
|
-
// Get the mock OpenAI constructor
|
|
32
|
-
const OpenAI = (await import('openai')).default as unknown as { mockImplementation: (fn: () => unknown) => void }
|
|
33
|
-
OpenAI.mockImplementation(() => ({
|
|
34
|
-
chat: {
|
|
35
|
-
completions: {
|
|
36
|
-
create: mockOpenAICreate
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}))
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
const baseRequest: LLMGenerateRequest = {
|
|
43
|
-
messages: [
|
|
44
|
-
{ role: 'system', content: 'You are a helpful assistant' },
|
|
45
|
-
{ role: 'user', content: 'Hello' }
|
|
46
|
-
],
|
|
47
|
-
responseSchema: {
|
|
48
|
-
type: 'object',
|
|
49
|
-
properties: {
|
|
50
|
-
response: { type: 'string' }
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
maxOutputTokens: 1000
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const mockOpenRouterResponse = {
|
|
57
|
-
choices: [
|
|
58
|
-
{
|
|
59
|
-
message: {
|
|
60
|
-
content: JSON.stringify({ response: 'Hello from Claude!' })
|
|
61
|
-
},
|
|
62
|
-
finish_reason: 'stop'
|
|
63
|
-
}
|
|
64
|
-
],
|
|
65
|
-
usage: {
|
|
66
|
-
prompt_tokens: 15,
|
|
67
|
-
completion_tokens: 25,
|
|
68
|
-
total_tokens: 40,
|
|
69
|
-
cost: 0.00125 // Actual cost from OpenRouter when usage accounting is enabled
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
describe('isOpenRouterModel', () => {
|
|
74
|
-
it('returns true for valid OpenRouter models', () => {
|
|
75
|
-
expect(isOpenRouterModel('openrouter/anthropic/claude-sonnet-4.5')).toBe(true)
|
|
76
|
-
expect(isOpenRouterModel('openrouter/deepseek/deepseek-v3.2')).toBe(true)
|
|
77
|
-
expect(isOpenRouterModel('openrouter/x-ai/grok-4.1-fast')).toBe(true)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('returns false for non-OpenRouter models', () => {
|
|
81
|
-
expect(isOpenRouterModel('gpt-5-mini')).toBe(false)
|
|
82
|
-
expect(isOpenRouterModel('gpt-5')).toBe(false)
|
|
83
|
-
expect(isOpenRouterModel('mock')).toBe(false)
|
|
84
|
-
})
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
describe('Basic Configuration', () => {
|
|
88
|
-
it('creates adapter with OpenRouter model', async () => {
|
|
89
|
-
const adapter = new OpenRouterAdapter({
|
|
90
|
-
apiKey: 'sk-or-v1-test-key',
|
|
91
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
95
|
-
|
|
96
|
-
const response = await adapter.generate(baseRequest)
|
|
97
|
-
|
|
98
|
-
expect(response.output).toEqual({ response: 'Hello from Claude!' })
|
|
99
|
-
expect(response.usage).toEqual({
|
|
100
|
-
inputTokens: 15,
|
|
101
|
-
outputTokens: 25,
|
|
102
|
-
totalTokens: 40
|
|
103
|
-
})
|
|
104
|
-
expect(response.cost).toBe(0.00125)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('strips openrouter/ prefix when calling API', async () => {
|
|
108
|
-
const adapter = new OpenRouterAdapter({
|
|
109
|
-
apiKey: 'sk-or-v1-test-key',
|
|
110
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
114
|
-
|
|
115
|
-
await adapter.generate(baseRequest)
|
|
116
|
-
|
|
117
|
-
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
118
|
-
// Model should be stripped of 'openrouter/' prefix
|
|
119
|
-
expect(createParams.model).toBe('anthropic/claude-sonnet-4.5')
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('uses correct OpenRouter baseURL and headers', async () => {
|
|
123
|
-
// Verify OpenAI SDK was instantiated with OpenRouter config
|
|
124
|
-
const OpenAI = (await import('openai')).default as unknown as ReturnType<typeof vi.fn>
|
|
125
|
-
|
|
126
|
-
new OpenRouterAdapter({
|
|
127
|
-
apiKey: 'sk-or-v1-test-key',
|
|
128
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
// SDK is lazily loaded, so we need to trigger a call to check constructor params
|
|
132
|
-
// The mock verifies the constructor was called correctly
|
|
133
|
-
expect(OpenAI).toBeDefined()
|
|
134
|
-
})
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
describe('Structured Output', () => {
|
|
138
|
-
it('sends response_format with json_schema and strict mode', async () => {
|
|
139
|
-
const adapter = new OpenRouterAdapter({
|
|
140
|
-
apiKey: 'sk-or-v1-test-key',
|
|
141
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
145
|
-
|
|
146
|
-
await adapter.generate(baseRequest)
|
|
147
|
-
|
|
148
|
-
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
149
|
-
expect(createParams.response_format).toEqual({
|
|
150
|
-
type: 'json_schema',
|
|
151
|
-
json_schema: {
|
|
152
|
-
name: 'llm_response',
|
|
153
|
-
strict: true,
|
|
154
|
-
schema: baseRequest.responseSchema
|
|
155
|
-
}
|
|
156
|
-
})
|
|
157
|
-
})
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
describe('OpenRouter-Specific Options', () => {
|
|
161
|
-
it('always includes usage accounting in extra_body', async () => {
|
|
162
|
-
const adapter = new OpenRouterAdapter({
|
|
163
|
-
apiKey: 'sk-or-v1-test-key',
|
|
164
|
-
model: 'openrouter/deepseek/deepseek-v3.2'
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
168
|
-
|
|
169
|
-
await adapter.generate(baseRequest)
|
|
170
|
-
|
|
171
|
-
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
172
|
-
expect(createParams.extra_body).toEqual({
|
|
173
|
-
usage: { include: true }
|
|
174
|
-
})
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
it('passes transforms option via extra_body', async () => {
|
|
178
|
-
const adapter = new OpenRouterAdapter({
|
|
179
|
-
apiKey: 'sk-or-v1-test-key',
|
|
180
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5',
|
|
181
|
-
modelOptions: {
|
|
182
|
-
transforms: ['middle-out']
|
|
183
|
-
}
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
187
|
-
|
|
188
|
-
await adapter.generate(baseRequest)
|
|
189
|
-
|
|
190
|
-
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
191
|
-
expect(createParams.extra_body).toEqual({
|
|
192
|
-
usage: { include: true },
|
|
193
|
-
transforms: ['middle-out']
|
|
194
|
-
})
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('passes route option via extra_body', async () => {
|
|
198
|
-
const adapter = new OpenRouterAdapter({
|
|
199
|
-
apiKey: 'sk-or-v1-test-key',
|
|
200
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5',
|
|
201
|
-
modelOptions: {
|
|
202
|
-
route: 'fallback'
|
|
203
|
-
}
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
207
|
-
|
|
208
|
-
await adapter.generate(baseRequest)
|
|
209
|
-
|
|
210
|
-
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
211
|
-
expect(createParams.extra_body).toEqual({
|
|
212
|
-
usage: { include: true },
|
|
213
|
-
route: 'fallback'
|
|
214
|
-
})
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
it('passes both transforms and route options', async () => {
|
|
218
|
-
const adapter = new OpenRouterAdapter({
|
|
219
|
-
apiKey: 'sk-or-v1-test-key',
|
|
220
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5',
|
|
221
|
-
modelOptions: {
|
|
222
|
-
transforms: ['middle-out'],
|
|
223
|
-
route: 'fallback'
|
|
224
|
-
}
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
228
|
-
|
|
229
|
-
await adapter.generate(baseRequest)
|
|
230
|
-
|
|
231
|
-
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
232
|
-
expect(createParams.extra_body).toEqual({
|
|
233
|
-
usage: { include: true },
|
|
234
|
-
transforms: ['middle-out'],
|
|
235
|
-
route: 'fallback'
|
|
236
|
-
})
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
it('handles empty model options object', async () => {
|
|
240
|
-
const adapter = new OpenRouterAdapter({
|
|
241
|
-
apiKey: 'sk-or-v1-test-key',
|
|
242
|
-
model: 'openrouter/deepseek/deepseek-v3.2',
|
|
243
|
-
modelOptions: {}
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
247
|
-
|
|
248
|
-
await adapter.generate(baseRequest)
|
|
249
|
-
|
|
250
|
-
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
251
|
-
// Should still have usage accounting even with empty modelOptions
|
|
252
|
-
expect(createParams.extra_body).toEqual({
|
|
253
|
-
usage: { include: true }
|
|
254
|
-
})
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
it('extracts cost from response when available', async () => {
|
|
258
|
-
const adapter = new OpenRouterAdapter({
|
|
259
|
-
apiKey: 'sk-or-v1-test-key',
|
|
260
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
264
|
-
|
|
265
|
-
const response = await adapter.generate(baseRequest)
|
|
266
|
-
|
|
267
|
-
expect(response.cost).toBe(0.00125)
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
it('returns undefined cost when not present in response', async () => {
|
|
271
|
-
const adapter = new OpenRouterAdapter({
|
|
272
|
-
apiKey: 'sk-or-v1-test-key',
|
|
273
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
// Response without cost field
|
|
277
|
-
mockOpenAICreate.mockResolvedValueOnce({
|
|
278
|
-
choices: [
|
|
279
|
-
{
|
|
280
|
-
message: {
|
|
281
|
-
content: JSON.stringify({ response: 'Hello!' })
|
|
282
|
-
},
|
|
283
|
-
finish_reason: 'stop'
|
|
284
|
-
}
|
|
285
|
-
],
|
|
286
|
-
usage: {
|
|
287
|
-
prompt_tokens: 10,
|
|
288
|
-
completion_tokens: 20,
|
|
289
|
-
total_tokens: 30
|
|
290
|
-
// No cost field
|
|
291
|
-
}
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
const response = await adapter.generate(baseRequest)
|
|
295
|
-
|
|
296
|
-
expect(response.cost).toBeUndefined()
|
|
297
|
-
expect(response.usage).toBeDefined()
|
|
298
|
-
})
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
describe('Standard Parameters', () => {
|
|
302
|
-
it('passes temperature when specified', async () => {
|
|
303
|
-
const adapter = new OpenRouterAdapter({
|
|
304
|
-
apiKey: 'sk-or-v1-test-key',
|
|
305
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
309
|
-
|
|
310
|
-
await adapter.generate({
|
|
311
|
-
...baseRequest,
|
|
312
|
-
temperature: 0.7
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
316
|
-
expect(createParams.temperature).toBe(0.7)
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
it('passes topP when specified', async () => {
|
|
320
|
-
const adapter = new OpenRouterAdapter({
|
|
321
|
-
apiKey: 'sk-or-v1-test-key',
|
|
322
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
326
|
-
|
|
327
|
-
await adapter.generate({
|
|
328
|
-
...baseRequest,
|
|
329
|
-
topP: 0.9
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
333
|
-
expect(createParams.top_p).toBe(0.9)
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
it('passes maxOutputTokens as max_tokens', async () => {
|
|
337
|
-
const adapter = new OpenRouterAdapter({
|
|
338
|
-
apiKey: 'sk-or-v1-test-key',
|
|
339
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
343
|
-
|
|
344
|
-
await adapter.generate({
|
|
345
|
-
...baseRequest,
|
|
346
|
-
maxOutputTokens: 4000
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
350
|
-
expect(createParams.max_tokens).toBe(4000)
|
|
351
|
-
})
|
|
352
|
-
|
|
353
|
-
it('combines all parameters correctly', async () => {
|
|
354
|
-
const adapter = new OpenRouterAdapter({
|
|
355
|
-
apiKey: 'sk-or-v1-test-key',
|
|
356
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5',
|
|
357
|
-
modelOptions: {
|
|
358
|
-
route: 'fallback'
|
|
359
|
-
}
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
363
|
-
|
|
364
|
-
await adapter.generate({
|
|
365
|
-
...baseRequest,
|
|
366
|
-
temperature: 0.8,
|
|
367
|
-
topP: 0.95,
|
|
368
|
-
maxOutputTokens: 8000
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
372
|
-
expect(createParams.model).toBe('anthropic/claude-sonnet-4.5')
|
|
373
|
-
expect(createParams.temperature).toBe(0.8)
|
|
374
|
-
expect(createParams.top_p).toBe(0.95)
|
|
375
|
-
expect(createParams.max_tokens).toBe(8000)
|
|
376
|
-
expect(createParams.extra_body).toEqual({
|
|
377
|
-
usage: { include: true },
|
|
378
|
-
route: 'fallback'
|
|
379
|
-
})
|
|
380
|
-
})
|
|
381
|
-
})
|
|
382
|
-
|
|
383
|
-
describe('Error Handling', () => {
|
|
384
|
-
it('throws error when no response from OpenRouter', async () => {
|
|
385
|
-
const adapter = new OpenRouterAdapter({
|
|
386
|
-
apiKey: 'sk-or-v1-test-key',
|
|
387
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
mockOpenAICreate.mockResolvedValueOnce({
|
|
391
|
-
choices: [],
|
|
392
|
-
usage: null
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
await expect(adapter.generate(baseRequest)).rejects.toThrow('No response from OpenRouter')
|
|
396
|
-
})
|
|
397
|
-
|
|
398
|
-
it('throws TokenLimitExceededError when finish_reason is length', async () => {
|
|
399
|
-
const adapter = new OpenRouterAdapter({
|
|
400
|
-
apiKey: 'sk-or-v1-test-key',
|
|
401
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
402
|
-
})
|
|
403
|
-
|
|
404
|
-
mockOpenAICreate.mockResolvedValueOnce({
|
|
405
|
-
choices: [
|
|
406
|
-
{
|
|
407
|
-
message: { content: '{}' },
|
|
408
|
-
finish_reason: 'length'
|
|
409
|
-
}
|
|
410
|
-
],
|
|
411
|
-
usage: {
|
|
412
|
-
prompt_tokens: 1000,
|
|
413
|
-
completion_tokens: 4000,
|
|
414
|
-
total_tokens: 5000
|
|
415
|
-
}
|
|
416
|
-
})
|
|
417
|
-
|
|
418
|
-
await expect(adapter.generate(baseRequest)).rejects.toThrow('LLM token limit exceeded')
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
it('re-throws API errors', async () => {
|
|
422
|
-
const adapter = new OpenRouterAdapter({
|
|
423
|
-
apiKey: 'sk-or-v1-test-key',
|
|
424
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
425
|
-
})
|
|
426
|
-
|
|
427
|
-
const apiError = new Error('Rate limit exceeded')
|
|
428
|
-
Object.assign(apiError, { status: 429 })
|
|
429
|
-
|
|
430
|
-
mockOpenAICreate.mockRejectedValueOnce(apiError)
|
|
431
|
-
|
|
432
|
-
await expect(adapter.generate(baseRequest)).rejects.toThrow('Rate limit exceeded')
|
|
433
|
-
})
|
|
434
|
-
})
|
|
435
|
-
|
|
436
|
-
describe('JSON Parse Error Handling', () => {
|
|
437
|
-
it('throws LLMResponseParseError when response is invalid JSON', async () => {
|
|
438
|
-
const adapter = new OpenRouterAdapter({
|
|
439
|
-
apiKey: 'sk-or-v1-test-key',
|
|
440
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
441
|
-
})
|
|
442
|
-
|
|
443
|
-
const invalidJsonResponse = {
|
|
444
|
-
choices: [
|
|
445
|
-
{
|
|
446
|
-
message: {
|
|
447
|
-
content: '{"response": "incomplete json' // Invalid JSON - unterminated string
|
|
448
|
-
},
|
|
449
|
-
finish_reason: 'stop'
|
|
450
|
-
}
|
|
451
|
-
],
|
|
452
|
-
usage: {
|
|
453
|
-
prompt_tokens: 10,
|
|
454
|
-
completion_tokens: 20,
|
|
455
|
-
total_tokens: 30
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
mockOpenAICreate.mockResolvedValueOnce(invalidJsonResponse)
|
|
460
|
-
|
|
461
|
-
await expect(adapter.generate(baseRequest)).rejects.toThrow(LLMResponseParseError)
|
|
462
|
-
})
|
|
463
|
-
|
|
464
|
-
it('includes truncated raw content in LLMResponseParseError context', async () => {
|
|
465
|
-
const adapter = new OpenRouterAdapter({
|
|
466
|
-
apiKey: 'sk-or-v1-test-key',
|
|
467
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
468
|
-
})
|
|
469
|
-
|
|
470
|
-
const invalidJsonResponse = {
|
|
471
|
-
choices: [
|
|
472
|
-
{
|
|
473
|
-
message: {
|
|
474
|
-
content: '{"response": "incomplete'
|
|
475
|
-
},
|
|
476
|
-
finish_reason: 'stop'
|
|
477
|
-
}
|
|
478
|
-
],
|
|
479
|
-
usage: {
|
|
480
|
-
prompt_tokens: 10,
|
|
481
|
-
completion_tokens: 20,
|
|
482
|
-
total_tokens: 30
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
mockOpenAICreate.mockResolvedValueOnce(invalidJsonResponse)
|
|
487
|
-
|
|
488
|
-
try {
|
|
489
|
-
await adapter.generate(baseRequest)
|
|
490
|
-
expect.fail('Should have thrown')
|
|
491
|
-
} catch (error) {
|
|
492
|
-
expect(error).toBeInstanceOf(LLMResponseParseError)
|
|
493
|
-
const parseError = error as LLMResponseParseError
|
|
494
|
-
expect(parseError.context).toHaveProperty('rawContent')
|
|
495
|
-
expect(parseError.context).toHaveProperty('parseError')
|
|
496
|
-
expect(parseError.message).toContain('Failed to parse LLM response as JSON')
|
|
497
|
-
}
|
|
498
|
-
})
|
|
499
|
-
|
|
500
|
-
it('parses valid JSON successfully', async () => {
|
|
501
|
-
const adapter = new OpenRouterAdapter({
|
|
502
|
-
apiKey: 'sk-or-v1-test-key',
|
|
503
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
504
|
-
})
|
|
505
|
-
|
|
506
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
507
|
-
|
|
508
|
-
const result = await adapter.generate(baseRequest)
|
|
509
|
-
expect(result.output).toEqual({ response: 'Hello from Claude!' })
|
|
510
|
-
})
|
|
511
|
-
})
|
|
512
|
-
|
|
513
|
-
describe('Multiple Models', () => {
|
|
514
|
-
it('works with different OpenRouter models', async () => {
|
|
515
|
-
const models: LLMModel[] = [
|
|
516
|
-
'openrouter/anthropic/claude-sonnet-4.5',
|
|
517
|
-
'openrouter/deepseek/deepseek-v3.2',
|
|
518
|
-
'openrouter/x-ai/grok-4.1-fast'
|
|
519
|
-
]
|
|
520
|
-
|
|
521
|
-
for (const model of models) {
|
|
522
|
-
const adapter = new OpenRouterAdapter({
|
|
523
|
-
apiKey: 'sk-or-v1-test-key',
|
|
524
|
-
model
|
|
525
|
-
})
|
|
526
|
-
|
|
527
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
528
|
-
|
|
529
|
-
const response = await adapter.generate(baseRequest)
|
|
530
|
-
expect(response.output).toBeDefined()
|
|
531
|
-
|
|
532
|
-
const createParams = mockOpenAICreate.mock.calls[mockOpenAICreate.mock.calls.length - 1][0]
|
|
533
|
-
expect(createParams.model).toBe(model.replace('openrouter/', ''))
|
|
534
|
-
}
|
|
535
|
-
})
|
|
536
|
-
})
|
|
537
|
-
|
|
538
|
-
describe('Backward Compatibility', () => {
|
|
539
|
-
it('works with existing LLM request format', async () => {
|
|
540
|
-
const adapter = new OpenRouterAdapter({
|
|
541
|
-
apiKey: 'sk-or-v1-test-key',
|
|
542
|
-
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
546
|
-
|
|
547
|
-
// Use the standard LLMGenerateRequest format
|
|
548
|
-
const response = await adapter.generate({
|
|
549
|
-
messages: [{ role: 'user', content: 'Test message' }],
|
|
550
|
-
responseSchema: {
|
|
551
|
-
type: 'object',
|
|
552
|
-
properties: {
|
|
553
|
-
result: { type: 'string' }
|
|
554
|
-
}
|
|
555
|
-
},
|
|
556
|
-
maxOutputTokens: 2000
|
|
557
|
-
})
|
|
558
|
-
|
|
559
|
-
expect(response.output).toBeDefined()
|
|
560
|
-
expect(response.usage).toBeDefined()
|
|
561
|
-
})
|
|
562
|
-
})
|
|
563
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter Adapter Tests
|
|
3
|
+
* Verifies OpenRouter adapter configuration, API translation, and options handling
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
7
|
+
import { OpenRouterAdapter, isOpenRouterModel } from '../server/openrouter'
|
|
8
|
+
import { LLMResponseParseError } from '../../errors'
|
|
9
|
+
import type { LLMGenerateRequest } from '../../types'
|
|
10
|
+
import type { LLMModel } from '../../model-info'
|
|
11
|
+
|
|
12
|
+
// Mock OpenAI SDK (OpenRouter uses OpenAI SDK with custom baseURL)
|
|
13
|
+
vi.mock('openai', () => {
|
|
14
|
+
return {
|
|
15
|
+
default: vi.fn().mockImplementation(() => ({
|
|
16
|
+
chat: {
|
|
17
|
+
completions: {
|
|
18
|
+
create: vi.fn()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}))
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('OpenRouterAdapter', () => {
|
|
26
|
+
const mockOpenAICreate = vi.fn()
|
|
27
|
+
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
vi.clearAllMocks()
|
|
30
|
+
|
|
31
|
+
// Get the mock OpenAI constructor
|
|
32
|
+
const OpenAI = (await import('openai')).default as unknown as { mockImplementation: (fn: () => unknown) => void }
|
|
33
|
+
OpenAI.mockImplementation(() => ({
|
|
34
|
+
chat: {
|
|
35
|
+
completions: {
|
|
36
|
+
create: mockOpenAICreate
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}))
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const baseRequest: LLMGenerateRequest = {
|
|
43
|
+
messages: [
|
|
44
|
+
{ role: 'system', content: 'You are a helpful assistant' },
|
|
45
|
+
{ role: 'user', content: 'Hello' }
|
|
46
|
+
],
|
|
47
|
+
responseSchema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
response: { type: 'string' }
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
maxOutputTokens: 1000
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const mockOpenRouterResponse = {
|
|
57
|
+
choices: [
|
|
58
|
+
{
|
|
59
|
+
message: {
|
|
60
|
+
content: JSON.stringify({ response: 'Hello from Claude!' })
|
|
61
|
+
},
|
|
62
|
+
finish_reason: 'stop'
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
usage: {
|
|
66
|
+
prompt_tokens: 15,
|
|
67
|
+
completion_tokens: 25,
|
|
68
|
+
total_tokens: 40,
|
|
69
|
+
cost: 0.00125 // Actual cost from OpenRouter when usage accounting is enabled
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
describe('isOpenRouterModel', () => {
|
|
74
|
+
it('returns true for valid OpenRouter models', () => {
|
|
75
|
+
expect(isOpenRouterModel('openrouter/anthropic/claude-sonnet-4.5')).toBe(true)
|
|
76
|
+
expect(isOpenRouterModel('openrouter/deepseek/deepseek-v3.2')).toBe(true)
|
|
77
|
+
expect(isOpenRouterModel('openrouter/x-ai/grok-4.1-fast')).toBe(true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('returns false for non-OpenRouter models', () => {
|
|
81
|
+
expect(isOpenRouterModel('gpt-5-mini')).toBe(false)
|
|
82
|
+
expect(isOpenRouterModel('gpt-5')).toBe(false)
|
|
83
|
+
expect(isOpenRouterModel('mock')).toBe(false)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe('Basic Configuration', () => {
|
|
88
|
+
it('creates adapter with OpenRouter model', async () => {
|
|
89
|
+
const adapter = new OpenRouterAdapter({
|
|
90
|
+
apiKey: 'sk-or-v1-test-key',
|
|
91
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
95
|
+
|
|
96
|
+
const response = await adapter.generate(baseRequest)
|
|
97
|
+
|
|
98
|
+
expect(response.output).toEqual({ response: 'Hello from Claude!' })
|
|
99
|
+
expect(response.usage).toEqual({
|
|
100
|
+
inputTokens: 15,
|
|
101
|
+
outputTokens: 25,
|
|
102
|
+
totalTokens: 40
|
|
103
|
+
})
|
|
104
|
+
expect(response.cost).toBe(0.00125)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('strips openrouter/ prefix when calling API', async () => {
|
|
108
|
+
const adapter = new OpenRouterAdapter({
|
|
109
|
+
apiKey: 'sk-or-v1-test-key',
|
|
110
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
114
|
+
|
|
115
|
+
await adapter.generate(baseRequest)
|
|
116
|
+
|
|
117
|
+
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
118
|
+
// Model should be stripped of 'openrouter/' prefix
|
|
119
|
+
expect(createParams.model).toBe('anthropic/claude-sonnet-4.5')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('uses correct OpenRouter baseURL and headers', async () => {
|
|
123
|
+
// Verify OpenAI SDK was instantiated with OpenRouter config
|
|
124
|
+
const OpenAI = (await import('openai')).default as unknown as ReturnType<typeof vi.fn>
|
|
125
|
+
|
|
126
|
+
new OpenRouterAdapter({
|
|
127
|
+
apiKey: 'sk-or-v1-test-key',
|
|
128
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// SDK is lazily loaded, so we need to trigger a call to check constructor params
|
|
132
|
+
// The mock verifies the constructor was called correctly
|
|
133
|
+
expect(OpenAI).toBeDefined()
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
describe('Structured Output', () => {
|
|
138
|
+
it('sends response_format with json_schema and strict mode', async () => {
|
|
139
|
+
const adapter = new OpenRouterAdapter({
|
|
140
|
+
apiKey: 'sk-or-v1-test-key',
|
|
141
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
145
|
+
|
|
146
|
+
await adapter.generate(baseRequest)
|
|
147
|
+
|
|
148
|
+
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
149
|
+
expect(createParams.response_format).toEqual({
|
|
150
|
+
type: 'json_schema',
|
|
151
|
+
json_schema: {
|
|
152
|
+
name: 'llm_response',
|
|
153
|
+
strict: true,
|
|
154
|
+
schema: baseRequest.responseSchema
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
describe('OpenRouter-Specific Options', () => {
|
|
161
|
+
it('always includes usage accounting in extra_body', async () => {
|
|
162
|
+
const adapter = new OpenRouterAdapter({
|
|
163
|
+
apiKey: 'sk-or-v1-test-key',
|
|
164
|
+
model: 'openrouter/deepseek/deepseek-v3.2'
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
168
|
+
|
|
169
|
+
await adapter.generate(baseRequest)
|
|
170
|
+
|
|
171
|
+
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
172
|
+
expect(createParams.extra_body).toEqual({
|
|
173
|
+
usage: { include: true }
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('passes transforms option via extra_body', async () => {
|
|
178
|
+
const adapter = new OpenRouterAdapter({
|
|
179
|
+
apiKey: 'sk-or-v1-test-key',
|
|
180
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5',
|
|
181
|
+
modelOptions: {
|
|
182
|
+
transforms: ['middle-out']
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
187
|
+
|
|
188
|
+
await adapter.generate(baseRequest)
|
|
189
|
+
|
|
190
|
+
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
191
|
+
expect(createParams.extra_body).toEqual({
|
|
192
|
+
usage: { include: true },
|
|
193
|
+
transforms: ['middle-out']
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('passes route option via extra_body', async () => {
|
|
198
|
+
const adapter = new OpenRouterAdapter({
|
|
199
|
+
apiKey: 'sk-or-v1-test-key',
|
|
200
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5',
|
|
201
|
+
modelOptions: {
|
|
202
|
+
route: 'fallback'
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
207
|
+
|
|
208
|
+
await adapter.generate(baseRequest)
|
|
209
|
+
|
|
210
|
+
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
211
|
+
expect(createParams.extra_body).toEqual({
|
|
212
|
+
usage: { include: true },
|
|
213
|
+
route: 'fallback'
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('passes both transforms and route options', async () => {
|
|
218
|
+
const adapter = new OpenRouterAdapter({
|
|
219
|
+
apiKey: 'sk-or-v1-test-key',
|
|
220
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5',
|
|
221
|
+
modelOptions: {
|
|
222
|
+
transforms: ['middle-out'],
|
|
223
|
+
route: 'fallback'
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
228
|
+
|
|
229
|
+
await adapter.generate(baseRequest)
|
|
230
|
+
|
|
231
|
+
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
232
|
+
expect(createParams.extra_body).toEqual({
|
|
233
|
+
usage: { include: true },
|
|
234
|
+
transforms: ['middle-out'],
|
|
235
|
+
route: 'fallback'
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('handles empty model options object', async () => {
|
|
240
|
+
const adapter = new OpenRouterAdapter({
|
|
241
|
+
apiKey: 'sk-or-v1-test-key',
|
|
242
|
+
model: 'openrouter/deepseek/deepseek-v3.2',
|
|
243
|
+
modelOptions: {}
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
247
|
+
|
|
248
|
+
await adapter.generate(baseRequest)
|
|
249
|
+
|
|
250
|
+
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
251
|
+
// Should still have usage accounting even with empty modelOptions
|
|
252
|
+
expect(createParams.extra_body).toEqual({
|
|
253
|
+
usage: { include: true }
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('extracts cost from response when available', async () => {
|
|
258
|
+
const adapter = new OpenRouterAdapter({
|
|
259
|
+
apiKey: 'sk-or-v1-test-key',
|
|
260
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
264
|
+
|
|
265
|
+
const response = await adapter.generate(baseRequest)
|
|
266
|
+
|
|
267
|
+
expect(response.cost).toBe(0.00125)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('returns undefined cost when not present in response', async () => {
|
|
271
|
+
const adapter = new OpenRouterAdapter({
|
|
272
|
+
apiKey: 'sk-or-v1-test-key',
|
|
273
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// Response without cost field
|
|
277
|
+
mockOpenAICreate.mockResolvedValueOnce({
|
|
278
|
+
choices: [
|
|
279
|
+
{
|
|
280
|
+
message: {
|
|
281
|
+
content: JSON.stringify({ response: 'Hello!' })
|
|
282
|
+
},
|
|
283
|
+
finish_reason: 'stop'
|
|
284
|
+
}
|
|
285
|
+
],
|
|
286
|
+
usage: {
|
|
287
|
+
prompt_tokens: 10,
|
|
288
|
+
completion_tokens: 20,
|
|
289
|
+
total_tokens: 30
|
|
290
|
+
// No cost field
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
const response = await adapter.generate(baseRequest)
|
|
295
|
+
|
|
296
|
+
expect(response.cost).toBeUndefined()
|
|
297
|
+
expect(response.usage).toBeDefined()
|
|
298
|
+
})
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
describe('Standard Parameters', () => {
|
|
302
|
+
it('passes temperature when specified', async () => {
|
|
303
|
+
const adapter = new OpenRouterAdapter({
|
|
304
|
+
apiKey: 'sk-or-v1-test-key',
|
|
305
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
309
|
+
|
|
310
|
+
await adapter.generate({
|
|
311
|
+
...baseRequest,
|
|
312
|
+
temperature: 0.7
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
316
|
+
expect(createParams.temperature).toBe(0.7)
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it('passes topP when specified', async () => {
|
|
320
|
+
const adapter = new OpenRouterAdapter({
|
|
321
|
+
apiKey: 'sk-or-v1-test-key',
|
|
322
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
326
|
+
|
|
327
|
+
await adapter.generate({
|
|
328
|
+
...baseRequest,
|
|
329
|
+
topP: 0.9
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
333
|
+
expect(createParams.top_p).toBe(0.9)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('passes maxOutputTokens as max_tokens', async () => {
|
|
337
|
+
const adapter = new OpenRouterAdapter({
|
|
338
|
+
apiKey: 'sk-or-v1-test-key',
|
|
339
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
343
|
+
|
|
344
|
+
await adapter.generate({
|
|
345
|
+
...baseRequest,
|
|
346
|
+
maxOutputTokens: 4000
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
350
|
+
expect(createParams.max_tokens).toBe(4000)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('combines all parameters correctly', async () => {
|
|
354
|
+
const adapter = new OpenRouterAdapter({
|
|
355
|
+
apiKey: 'sk-or-v1-test-key',
|
|
356
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5',
|
|
357
|
+
modelOptions: {
|
|
358
|
+
route: 'fallback'
|
|
359
|
+
}
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
363
|
+
|
|
364
|
+
await adapter.generate({
|
|
365
|
+
...baseRequest,
|
|
366
|
+
temperature: 0.8,
|
|
367
|
+
topP: 0.95,
|
|
368
|
+
maxOutputTokens: 8000
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
const createParams = mockOpenAICreate.mock.calls[0][0]
|
|
372
|
+
expect(createParams.model).toBe('anthropic/claude-sonnet-4.5')
|
|
373
|
+
expect(createParams.temperature).toBe(0.8)
|
|
374
|
+
expect(createParams.top_p).toBe(0.95)
|
|
375
|
+
expect(createParams.max_tokens).toBe(8000)
|
|
376
|
+
expect(createParams.extra_body).toEqual({
|
|
377
|
+
usage: { include: true },
|
|
378
|
+
route: 'fallback'
|
|
379
|
+
})
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
describe('Error Handling', () => {
|
|
384
|
+
it('throws error when no response from OpenRouter', async () => {
|
|
385
|
+
const adapter = new OpenRouterAdapter({
|
|
386
|
+
apiKey: 'sk-or-v1-test-key',
|
|
387
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
mockOpenAICreate.mockResolvedValueOnce({
|
|
391
|
+
choices: [],
|
|
392
|
+
usage: null
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
await expect(adapter.generate(baseRequest)).rejects.toThrow('No response from OpenRouter')
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
it('throws TokenLimitExceededError when finish_reason is length', async () => {
|
|
399
|
+
const adapter = new OpenRouterAdapter({
|
|
400
|
+
apiKey: 'sk-or-v1-test-key',
|
|
401
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
mockOpenAICreate.mockResolvedValueOnce({
|
|
405
|
+
choices: [
|
|
406
|
+
{
|
|
407
|
+
message: { content: '{}' },
|
|
408
|
+
finish_reason: 'length'
|
|
409
|
+
}
|
|
410
|
+
],
|
|
411
|
+
usage: {
|
|
412
|
+
prompt_tokens: 1000,
|
|
413
|
+
completion_tokens: 4000,
|
|
414
|
+
total_tokens: 5000
|
|
415
|
+
}
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
await expect(adapter.generate(baseRequest)).rejects.toThrow('LLM token limit exceeded')
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
it('re-throws API errors', async () => {
|
|
422
|
+
const adapter = new OpenRouterAdapter({
|
|
423
|
+
apiKey: 'sk-or-v1-test-key',
|
|
424
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
const apiError = new Error('Rate limit exceeded')
|
|
428
|
+
Object.assign(apiError, { status: 429 })
|
|
429
|
+
|
|
430
|
+
mockOpenAICreate.mockRejectedValueOnce(apiError)
|
|
431
|
+
|
|
432
|
+
await expect(adapter.generate(baseRequest)).rejects.toThrow('Rate limit exceeded')
|
|
433
|
+
})
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
describe('JSON Parse Error Handling', () => {
|
|
437
|
+
it('throws LLMResponseParseError when response is invalid JSON', async () => {
|
|
438
|
+
const adapter = new OpenRouterAdapter({
|
|
439
|
+
apiKey: 'sk-or-v1-test-key',
|
|
440
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
const invalidJsonResponse = {
|
|
444
|
+
choices: [
|
|
445
|
+
{
|
|
446
|
+
message: {
|
|
447
|
+
content: '{"response": "incomplete json' // Invalid JSON - unterminated string
|
|
448
|
+
},
|
|
449
|
+
finish_reason: 'stop'
|
|
450
|
+
}
|
|
451
|
+
],
|
|
452
|
+
usage: {
|
|
453
|
+
prompt_tokens: 10,
|
|
454
|
+
completion_tokens: 20,
|
|
455
|
+
total_tokens: 30
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
mockOpenAICreate.mockResolvedValueOnce(invalidJsonResponse)
|
|
460
|
+
|
|
461
|
+
await expect(adapter.generate(baseRequest)).rejects.toThrow(LLMResponseParseError)
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
it('includes truncated raw content in LLMResponseParseError context', async () => {
|
|
465
|
+
const adapter = new OpenRouterAdapter({
|
|
466
|
+
apiKey: 'sk-or-v1-test-key',
|
|
467
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
const invalidJsonResponse = {
|
|
471
|
+
choices: [
|
|
472
|
+
{
|
|
473
|
+
message: {
|
|
474
|
+
content: '{"response": "incomplete'
|
|
475
|
+
},
|
|
476
|
+
finish_reason: 'stop'
|
|
477
|
+
}
|
|
478
|
+
],
|
|
479
|
+
usage: {
|
|
480
|
+
prompt_tokens: 10,
|
|
481
|
+
completion_tokens: 20,
|
|
482
|
+
total_tokens: 30
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
mockOpenAICreate.mockResolvedValueOnce(invalidJsonResponse)
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
await adapter.generate(baseRequest)
|
|
490
|
+
expect.fail('Should have thrown')
|
|
491
|
+
} catch (error) {
|
|
492
|
+
expect(error).toBeInstanceOf(LLMResponseParseError)
|
|
493
|
+
const parseError = error as LLMResponseParseError
|
|
494
|
+
expect(parseError.context).toHaveProperty('rawContent')
|
|
495
|
+
expect(parseError.context).toHaveProperty('parseError')
|
|
496
|
+
expect(parseError.message).toContain('Failed to parse LLM response as JSON')
|
|
497
|
+
}
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
it('parses valid JSON successfully', async () => {
|
|
501
|
+
const adapter = new OpenRouterAdapter({
|
|
502
|
+
apiKey: 'sk-or-v1-test-key',
|
|
503
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
507
|
+
|
|
508
|
+
const result = await adapter.generate(baseRequest)
|
|
509
|
+
expect(result.output).toEqual({ response: 'Hello from Claude!' })
|
|
510
|
+
})
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
describe('Multiple Models', () => {
|
|
514
|
+
it('works with different OpenRouter models', async () => {
|
|
515
|
+
const models: LLMModel[] = [
|
|
516
|
+
'openrouter/anthropic/claude-sonnet-4.5',
|
|
517
|
+
'openrouter/deepseek/deepseek-v3.2',
|
|
518
|
+
'openrouter/x-ai/grok-4.1-fast'
|
|
519
|
+
]
|
|
520
|
+
|
|
521
|
+
for (const model of models) {
|
|
522
|
+
const adapter = new OpenRouterAdapter({
|
|
523
|
+
apiKey: 'sk-or-v1-test-key',
|
|
524
|
+
model
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
528
|
+
|
|
529
|
+
const response = await adapter.generate(baseRequest)
|
|
530
|
+
expect(response.output).toBeDefined()
|
|
531
|
+
|
|
532
|
+
const createParams = mockOpenAICreate.mock.calls[mockOpenAICreate.mock.calls.length - 1][0]
|
|
533
|
+
expect(createParams.model).toBe(model.replace('openrouter/', ''))
|
|
534
|
+
}
|
|
535
|
+
})
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
describe('Backward Compatibility', () => {
|
|
539
|
+
it('works with existing LLM request format', async () => {
|
|
540
|
+
const adapter = new OpenRouterAdapter({
|
|
541
|
+
apiKey: 'sk-or-v1-test-key',
|
|
542
|
+
model: 'openrouter/anthropic/claude-sonnet-4.5'
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
mockOpenAICreate.mockResolvedValueOnce(mockOpenRouterResponse)
|
|
546
|
+
|
|
547
|
+
// Use the standard LLMGenerateRequest format
|
|
548
|
+
const response = await adapter.generate({
|
|
549
|
+
messages: [{ role: 'user', content: 'Test message' }],
|
|
550
|
+
responseSchema: {
|
|
551
|
+
type: 'object',
|
|
552
|
+
properties: {
|
|
553
|
+
result: { type: 'string' }
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
maxOutputTokens: 2000
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
expect(response.output).toBeDefined()
|
|
560
|
+
expect(response.usage).toBeDefined()
|
|
561
|
+
})
|
|
562
|
+
})
|
|
563
|
+
})
|