unitysvc-core 0.2.4__tar.gz → 0.2.5__tar.gz
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.
- {unitysvc_core-0.2.4/src/unitysvc_core.egg-info → unitysvc_core-0.2.5}/PKG-INFO +1 -1
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/pyproject.toml +1 -1
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/__init__.py +2 -2
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/base.py +2 -2
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/offering_data.py +2 -2
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/offering_v1.py +1 -1
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/pricing.py +54 -54
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/service.py +12 -1
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/schema/listing_v1.json +74 -74
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/schema/offering_v1.json +76 -76
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5/src/unitysvc_core.egg-info}/PKG-INFO +1 -1
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/tests/test_pricing.py +38 -38
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/LICENSE +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/README.md +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/setup.cfg +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/__init__.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/documents.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/listing_data.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/listing_v1.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/promotion_data.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/provider_data.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/provider_v1.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/service_group_data.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/models/validators.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/py.typed +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/schema/base.json +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/schema/promotion_v1.json +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/schema/provider_v1.json +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/schema/service_group_v1.json +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/utils.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core/validator.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core.egg-info/SOURCES.txt +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core.egg-info/dependency_links.txt +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core.egg-info/requires.txt +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/src/unitysvc_core.egg-info/top_level.txt +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/tests/test_service_group.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/tests/test_service_status.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/tests/test_utils.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/tests/test_validator.py +0 -0
- {unitysvc_core-0.2.4 → unitysvc_core-0.2.5}/tests/test_validators.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "unitysvc-core"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.5"
|
|
8
8
|
description = "Shared data models and validation helpers for UnitySVC packages"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Bo Peng", email = "bo.peng@unitysvc.com" }]
|
|
@@ -33,6 +33,7 @@ from .offering_v1 import OfferingV1
|
|
|
33
33
|
from .pricing import (
|
|
34
34
|
AddPriceData,
|
|
35
35
|
BasePriceData,
|
|
36
|
+
ChannelPriceData,
|
|
36
37
|
ConstantPriceData,
|
|
37
38
|
CountPriceData,
|
|
38
39
|
DataPriceData,
|
|
@@ -43,7 +44,6 @@ from .pricing import (
|
|
|
43
44
|
ImagePriceData,
|
|
44
45
|
MaxPriceData,
|
|
45
46
|
MinPriceData,
|
|
46
|
-
ModePriceData,
|
|
47
47
|
MultiplyPriceData,
|
|
48
48
|
PercentageStr,
|
|
49
49
|
PriceStr,
|
|
@@ -159,7 +159,7 @@ __all__ = [
|
|
|
159
159
|
"MultiplyPriceData",
|
|
160
160
|
"MaxPriceData",
|
|
161
161
|
"MinPriceData",
|
|
162
|
-
"
|
|
162
|
+
"ChannelPriceData",
|
|
163
163
|
"FirstPriceData",
|
|
164
164
|
"TieredPriceData",
|
|
165
165
|
"GraduatedPriceData",
|
|
@@ -216,8 +216,8 @@ class PricingTypeEnum(StrEnum):
|
|
|
216
216
|
graduated = "graduated" # Graduated tiers (each tier's units at that rate)
|
|
217
217
|
# Expression-based pricing (payout_price only)
|
|
218
218
|
expr = "expr" # Arbitrary expression using usage metrics
|
|
219
|
-
#
|
|
220
|
-
|
|
219
|
+
# Channel-based pricing: select a sub-price by the request's resolved channel
|
|
220
|
+
channel = "channel" # Per-channel pricing keyed by channel name (e.g. managed, byok)
|
|
221
221
|
|
|
222
222
|
|
|
223
223
|
class QuotaResetCycleEnum(StrEnum):
|
|
@@ -91,10 +91,10 @@ class ServiceOfferingData(BaseModel):
|
|
|
91
91
|
description="Payout pricing: How to calculate seller payout",
|
|
92
92
|
)
|
|
93
93
|
|
|
94
|
-
# Access
|
|
94
|
+
# Access channels (keyed by channel name)
|
|
95
95
|
upstream_access_config: dict[str, dict[str, Any]] | None = Field(
|
|
96
96
|
default=None,
|
|
97
|
-
description="Upstream access
|
|
97
|
+
description="Upstream access channels, keyed by channel name",
|
|
98
98
|
)
|
|
99
99
|
|
|
100
100
|
# Documents (keyed by title)
|
|
@@ -46,7 +46,7 @@ class OfferingV1(ServiceOfferingData):
|
|
|
46
46
|
|
|
47
47
|
# Override with typed models for file validation
|
|
48
48
|
upstream_access_config: dict[str, UpstreamAccessConfigData] = Field( # type: ignore[assignment]
|
|
49
|
-
description="Upstream access
|
|
49
|
+
description="Upstream access channels, keyed by channel name",
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
documents: dict[str, DocumentData] | None = Field( # type: ignore[assignment]
|
|
@@ -280,7 +280,7 @@ class TokenPriceData(BasePriceData):
|
|
|
280
280
|
usage: UsageData,
|
|
281
281
|
customer_charge: Decimal | None = None,
|
|
282
282
|
request_count: int | None = None,
|
|
283
|
-
|
|
283
|
+
channel: str | None = None,
|
|
284
284
|
) -> Decimal:
|
|
285
285
|
"""Calculate cost for token-based pricing.
|
|
286
286
|
|
|
@@ -347,7 +347,7 @@ class TimePriceData(BasePriceData):
|
|
|
347
347
|
usage: UsageData,
|
|
348
348
|
customer_charge: Decimal | None = None,
|
|
349
349
|
request_count: int | None = None,
|
|
350
|
-
|
|
350
|
+
channel: str | None = None,
|
|
351
351
|
) -> Decimal:
|
|
352
352
|
"""Calculate cost for time-based pricing.
|
|
353
353
|
|
|
@@ -388,7 +388,7 @@ class ImagePriceData(BasePriceData):
|
|
|
388
388
|
usage: UsageData,
|
|
389
389
|
customer_charge: Decimal | None = None,
|
|
390
390
|
request_count: int | None = None,
|
|
391
|
-
|
|
391
|
+
channel: str | None = None,
|
|
392
392
|
) -> Decimal:
|
|
393
393
|
"""Calculate cost for image-based pricing.
|
|
394
394
|
|
|
@@ -425,7 +425,7 @@ class StepPriceData(BasePriceData):
|
|
|
425
425
|
usage: UsageData,
|
|
426
426
|
customer_charge: Decimal | None = None,
|
|
427
427
|
request_count: int | None = None,
|
|
428
|
-
|
|
428
|
+
channel: str | None = None,
|
|
429
429
|
) -> Decimal:
|
|
430
430
|
"""Calculate cost for step-based pricing.
|
|
431
431
|
|
|
@@ -465,7 +465,7 @@ class DataPriceData(BasePriceData):
|
|
|
465
465
|
usage: UsageData,
|
|
466
466
|
customer_charge: Decimal | None = None,
|
|
467
467
|
request_count: int | None = None,
|
|
468
|
-
|
|
468
|
+
channel: str | None = None,
|
|
469
469
|
) -> Decimal:
|
|
470
470
|
"""Calculate cost for data-volume pricing.
|
|
471
471
|
|
|
@@ -509,7 +509,7 @@ class CountPriceData(BasePriceData):
|
|
|
509
509
|
usage: UsageData,
|
|
510
510
|
customer_charge: Decimal | None = None,
|
|
511
511
|
request_count: int | None = None,
|
|
512
|
-
|
|
512
|
+
channel: str | None = None,
|
|
513
513
|
) -> Decimal:
|
|
514
514
|
"""Calculate cost for count-scaled pricing.
|
|
515
515
|
|
|
@@ -572,7 +572,7 @@ class RevenueSharePriceData(BasePriceData):
|
|
|
572
572
|
usage: UsageData,
|
|
573
573
|
customer_charge: Decimal | None = None,
|
|
574
574
|
request_count: int | None = None,
|
|
575
|
-
|
|
575
|
+
channel: str | None = None,
|
|
576
576
|
) -> Decimal:
|
|
577
577
|
"""Calculate cost for revenue share pricing.
|
|
578
578
|
|
|
@@ -612,7 +612,7 @@ class ConstantPriceData(BasePriceData):
|
|
|
612
612
|
usage: UsageData,
|
|
613
613
|
customer_charge: Decimal | None = None,
|
|
614
614
|
request_count: int | None = None,
|
|
615
|
-
|
|
615
|
+
channel: str | None = None,
|
|
616
616
|
) -> Decimal:
|
|
617
617
|
"""Return the constant price regardless of usage.
|
|
618
618
|
|
|
@@ -685,7 +685,7 @@ class AddPriceData(BasePriceData):
|
|
|
685
685
|
usage: UsageData,
|
|
686
686
|
customer_charge: Decimal | None = None,
|
|
687
687
|
request_count: int | None = None,
|
|
688
|
-
|
|
688
|
+
channel: str | None = None,
|
|
689
689
|
) -> Decimal:
|
|
690
690
|
"""Calculate total cost by summing all price components.
|
|
691
691
|
|
|
@@ -700,7 +700,7 @@ class AddPriceData(BasePriceData):
|
|
|
700
700
|
total = Decimal("0")
|
|
701
701
|
for price_data in self.prices:
|
|
702
702
|
component = validate_pricing(price_data)
|
|
703
|
-
total += component.calculate_cost(usage, customer_charge, request_count,
|
|
703
|
+
total += component.calculate_cost(usage, customer_charge, request_count, channel)
|
|
704
704
|
return total
|
|
705
705
|
|
|
706
706
|
|
|
@@ -745,7 +745,7 @@ class MultiplyPriceData(BasePriceData):
|
|
|
745
745
|
usage: UsageData,
|
|
746
746
|
customer_charge: Decimal | None = None,
|
|
747
747
|
request_count: int | None = None,
|
|
748
|
-
|
|
748
|
+
channel: str | None = None,
|
|
749
749
|
) -> Decimal:
|
|
750
750
|
"""Calculate cost by multiplying base price by factor.
|
|
751
751
|
|
|
@@ -758,7 +758,7 @@ class MultiplyPriceData(BasePriceData):
|
|
|
758
758
|
Base cost multiplied by factor
|
|
759
759
|
"""
|
|
760
760
|
base_pricing = validate_pricing(self.base)
|
|
761
|
-
base_cost = base_pricing.calculate_cost(usage, customer_charge, request_count,
|
|
761
|
+
base_cost = base_pricing.calculate_cost(usage, customer_charge, request_count, channel)
|
|
762
762
|
return base_cost * Decimal(self.factor)
|
|
763
763
|
|
|
764
764
|
|
|
@@ -799,7 +799,7 @@ class MaxPriceData(BasePriceData):
|
|
|
799
799
|
usage: UsageData,
|
|
800
800
|
customer_charge: Decimal | None = None,
|
|
801
801
|
request_count: int | None = None,
|
|
802
|
-
|
|
802
|
+
channel: str | None = None,
|
|
803
803
|
) -> Decimal:
|
|
804
804
|
"""Return the highest cost among applicable children.
|
|
805
805
|
|
|
@@ -810,7 +810,7 @@ class MaxPriceData(BasePriceData):
|
|
|
810
810
|
for price_data in self.prices:
|
|
811
811
|
try:
|
|
812
812
|
component = validate_pricing(price_data)
|
|
813
|
-
costs.append(component.calculate_cost(usage, customer_charge, request_count,
|
|
813
|
+
costs.append(component.calculate_cost(usage, customer_charge, request_count, channel))
|
|
814
814
|
except ValueError:
|
|
815
815
|
continue
|
|
816
816
|
if not costs:
|
|
@@ -857,7 +857,7 @@ class MinPriceData(BasePriceData):
|
|
|
857
857
|
usage: UsageData,
|
|
858
858
|
customer_charge: Decimal | None = None,
|
|
859
859
|
request_count: int | None = None,
|
|
860
|
-
|
|
860
|
+
channel: str | None = None,
|
|
861
861
|
) -> Decimal:
|
|
862
862
|
"""Return the lowest cost among applicable children.
|
|
863
863
|
|
|
@@ -868,7 +868,7 @@ class MinPriceData(BasePriceData):
|
|
|
868
868
|
for price_data in self.prices:
|
|
869
869
|
try:
|
|
870
870
|
component = validate_pricing(price_data)
|
|
871
|
-
costs.append(component.calculate_cost(usage, customer_charge, request_count,
|
|
871
|
+
costs.append(component.calculate_cost(usage, customer_charge, request_count, channel))
|
|
872
872
|
except ValueError:
|
|
873
873
|
continue
|
|
874
874
|
if not costs:
|
|
@@ -914,7 +914,7 @@ class FirstPriceData(BasePriceData):
|
|
|
914
914
|
usage: UsageData,
|
|
915
915
|
customer_charge: Decimal | None = None,
|
|
916
916
|
request_count: int | None = None,
|
|
917
|
-
|
|
917
|
+
channel: str | None = None,
|
|
918
918
|
) -> Decimal:
|
|
919
919
|
"""Return the cost from the first child that can handle the usage.
|
|
920
920
|
|
|
@@ -925,7 +925,7 @@ class FirstPriceData(BasePriceData):
|
|
|
925
925
|
for price_data in self.prices:
|
|
926
926
|
try:
|
|
927
927
|
component = validate_pricing(price_data)
|
|
928
|
-
return component.calculate_cost(usage, customer_charge, request_count,
|
|
928
|
+
return component.calculate_cost(usage, customer_charge, request_count, channel)
|
|
929
929
|
except ValueError:
|
|
930
930
|
continue
|
|
931
931
|
raise ValueError("No child pricing could handle the provided usage data")
|
|
@@ -1140,7 +1140,7 @@ class ExprPriceData(BasePriceData):
|
|
|
1140
1140
|
usage: UsageData,
|
|
1141
1141
|
customer_charge: Decimal | None = None,
|
|
1142
1142
|
request_count: int | None = None,
|
|
1143
|
-
|
|
1143
|
+
channel: str | None = None,
|
|
1144
1144
|
) -> Decimal:
|
|
1145
1145
|
"""Calculate cost by evaluating the expression with usage data.
|
|
1146
1146
|
|
|
@@ -1214,7 +1214,7 @@ class TieredPriceData(BasePriceData):
|
|
|
1214
1214
|
usage: UsageData,
|
|
1215
1215
|
customer_charge: Decimal | None = None,
|
|
1216
1216
|
request_count: int | None = None,
|
|
1217
|
-
|
|
1217
|
+
channel: str | None = None,
|
|
1218
1218
|
) -> Decimal:
|
|
1219
1219
|
"""Calculate cost based on which tier the usage falls into.
|
|
1220
1220
|
|
|
@@ -1232,7 +1232,7 @@ class TieredPriceData(BasePriceData):
|
|
|
1232
1232
|
for tier in self.tiers:
|
|
1233
1233
|
if tier.up_to is None or metric_value <= tier.up_to:
|
|
1234
1234
|
tier_pricing = validate_pricing(tier.price)
|
|
1235
|
-
return tier_pricing.calculate_cost(usage, customer_charge, request_count,
|
|
1235
|
+
return tier_pricing.calculate_cost(usage, customer_charge, request_count, channel)
|
|
1236
1236
|
|
|
1237
1237
|
# Should not reach here if tiers are properly configured
|
|
1238
1238
|
raise ValueError("No matching tier found")
|
|
@@ -1297,7 +1297,7 @@ class GraduatedPriceData(BasePriceData):
|
|
|
1297
1297
|
usage: UsageData,
|
|
1298
1298
|
customer_charge: Decimal | None = None,
|
|
1299
1299
|
request_count: int | None = None,
|
|
1300
|
-
|
|
1300
|
+
channel: str | None = None,
|
|
1301
1301
|
) -> Decimal:
|
|
1302
1302
|
"""Calculate cost with graduated pricing across tiers.
|
|
1303
1303
|
|
|
@@ -1333,56 +1333,56 @@ class GraduatedPriceData(BasePriceData):
|
|
|
1333
1333
|
return total_cost
|
|
1334
1334
|
|
|
1335
1335
|
|
|
1336
|
-
class
|
|
1336
|
+
class ChannelPriceData(BasePriceData):
|
|
1337
1337
|
"""
|
|
1338
|
-
|
|
1338
|
+
Channel-based pricing that selects a sub-price by the request's resolved channel.
|
|
1339
1339
|
|
|
1340
|
-
The active
|
|
1341
|
-
``upstream_access_config`` a request routes through
|
|
1342
|
-
``"byok"``, ``"byoe"``, …) — and threads it into
|
|
1343
|
-
``calculate_cost(...,
|
|
1344
|
-
evaluated; when the
|
|
1340
|
+
The active channel is decided elsewhere — e.g. the gateway picks which keyed
|
|
1341
|
+
``upstream_access_config`` (upstream access channel) a request routes through
|
|
1342
|
+
(``"managed"``, ``"byok"``, ``"byoe"``, …) — and threads it into
|
|
1343
|
+
``calculate_cost(..., channel=...)``. The matching entry in ``channels`` is
|
|
1344
|
+
evaluated; when the channel is unknown or unset, the ``default`` channel is used.
|
|
1345
1345
|
|
|
1346
1346
|
There is no special-casing on the consumer side: every pricing type accepts
|
|
1347
|
-
an optional ``
|
|
1348
|
-
plain price behaves identically for all
|
|
1347
|
+
an optional ``channel`` and a channel-independent price simply ignores it, so a
|
|
1348
|
+
plain price behaves identically for all channels. Channel-dependent pricing is
|
|
1349
1349
|
just this one extra optional pricing type.
|
|
1350
1350
|
|
|
1351
|
-
Each ``
|
|
1352
|
-
or even nested
|
|
1351
|
+
Each ``channels`` value is itself any pricing object (flat, composite, tiered,
|
|
1352
|
+
or even nested channel-based), validated lazily — mirroring how tiered pricing
|
|
1353
1353
|
holds per-tier prices.
|
|
1354
1354
|
|
|
1355
1355
|
Example:
|
|
1356
1356
|
{
|
|
1357
|
-
"type": "
|
|
1357
|
+
"type": "channel",
|
|
1358
1358
|
"default": "managed",
|
|
1359
|
-
"
|
|
1359
|
+
"channels": {
|
|
1360
1360
|
"managed": {"type": "one_million_tokens", "input": "0.50", "output": "1.50"},
|
|
1361
1361
|
"byok": {"type": "constant", "price": "0"}
|
|
1362
1362
|
}
|
|
1363
1363
|
}
|
|
1364
1364
|
"""
|
|
1365
1365
|
|
|
1366
|
-
type: Literal["
|
|
1366
|
+
type: Literal["channel"] = "channel"
|
|
1367
1367
|
|
|
1368
1368
|
default: str = Field(
|
|
1369
|
-
description="
|
|
1370
|
-
"Must be one of the keys in `
|
|
1369
|
+
description="Channel name evaluated when the request's channel is unknown or unset. "
|
|
1370
|
+
"Must be one of the keys in `channels`.",
|
|
1371
1371
|
)
|
|
1372
1372
|
|
|
1373
|
-
|
|
1374
|
-
description="Per-
|
|
1373
|
+
channels: dict[str, dict[str, Any]] = Field(
|
|
1374
|
+
description="Per-channel pricing keyed by channel name (e.g. 'managed', 'byok'). "
|
|
1375
1375
|
"Each value is a nested pricing object of any type.",
|
|
1376
1376
|
min_length=1,
|
|
1377
1377
|
)
|
|
1378
1378
|
|
|
1379
1379
|
@model_validator(mode="after")
|
|
1380
|
-
def _validate_default_and_price(self) ->
|
|
1380
|
+
def _validate_default_and_price(self) -> ChannelPriceData:
|
|
1381
1381
|
"""Require ``default`` to exist and auto-compute the nominal price from it."""
|
|
1382
|
-
if self.default not in self.
|
|
1383
|
-
raise ValueError(f"default
|
|
1382
|
+
if self.default not in self.channels:
|
|
1383
|
+
raise ValueError(f"default channel {self.default!r} is not defined in channels ({sorted(self.channels)})")
|
|
1384
1384
|
if self.price is None:
|
|
1385
|
-
self.price = _extract_nominal_price(self.
|
|
1385
|
+
self.price = _extract_nominal_price(self.channels[self.default])
|
|
1386
1386
|
return self
|
|
1387
1387
|
|
|
1388
1388
|
def calculate_cost(
|
|
@@ -1390,24 +1390,24 @@ class ModePriceData(BasePriceData):
|
|
|
1390
1390
|
usage: UsageData,
|
|
1391
1391
|
customer_charge: Decimal | None = None,
|
|
1392
1392
|
request_count: int | None = None,
|
|
1393
|
-
|
|
1393
|
+
channel: str | None = None,
|
|
1394
1394
|
) -> Decimal:
|
|
1395
|
-
"""Evaluate the price for the resolved ``
|
|
1395
|
+
"""Evaluate the price for the resolved ``channel`` (falling back to ``default``).
|
|
1396
1396
|
|
|
1397
1397
|
Args:
|
|
1398
|
-
usage: Usage data passed to the selected
|
|
1398
|
+
usage: Usage data passed to the selected channel's price.
|
|
1399
1399
|
customer_charge: Customer charge passed through.
|
|
1400
1400
|
request_count: Request count passed through.
|
|
1401
|
-
|
|
1401
|
+
channel: The resolved channel; unknown/``None`` falls back to ``default``.
|
|
1402
1402
|
|
|
1403
1403
|
Returns:
|
|
1404
|
-
Cost from the selected
|
|
1404
|
+
Cost from the selected channel's pricing.
|
|
1405
1405
|
"""
|
|
1406
|
-
selected = self.
|
|
1406
|
+
selected = self.channels.get(channel) if channel is not None else None
|
|
1407
1407
|
if selected is None:
|
|
1408
|
-
selected = self.
|
|
1408
|
+
selected = self.channels[self.default]
|
|
1409
1409
|
pricing = validate_pricing(selected)
|
|
1410
|
-
return pricing.calculate_cost(usage, customer_charge, request_count,
|
|
1410
|
+
return pricing.calculate_cost(usage, customer_charge, request_count, channel)
|
|
1411
1411
|
|
|
1412
1412
|
|
|
1413
1413
|
# Discriminated union of all pricing types
|
|
@@ -1430,7 +1430,7 @@ Pricing = Annotated[
|
|
|
1430
1430
|
| TieredPriceData
|
|
1431
1431
|
| GraduatedPriceData
|
|
1432
1432
|
| ExprPriceData
|
|
1433
|
-
|
|
|
1433
|
+
| ChannelPriceData,
|
|
1434
1434
|
Field(discriminator="type"),
|
|
1435
1435
|
]
|
|
1436
1436
|
|
|
@@ -1454,7 +1454,7 @@ def validate_pricing(
|
|
|
1454
1454
|
| TieredPriceData
|
|
1455
1455
|
| GraduatedPriceData
|
|
1456
1456
|
| ExprPriceData
|
|
1457
|
-
|
|
|
1457
|
+
| ChannelPriceData
|
|
1458
1458
|
):
|
|
1459
1459
|
"""
|
|
1460
1460
|
Validate pricing dict and return the appropriate typed model.
|
|
@@ -116,7 +116,18 @@ class AccessInterfaceData(BaseModel):
|
|
|
116
116
|
|
|
117
117
|
|
|
118
118
|
class UpstreamAccessConfigData(AccessInterfaceData):
|
|
119
|
-
"""
|
|
119
|
+
"""One upstream access channel — a named entry in ``upstream_access_config``.
|
|
120
|
+
|
|
121
|
+
Each channel is a complete way for the gateway to reach the upstream: a wire
|
|
122
|
+
protocol (``access_method``), endpoint (``base_url``), credential (``api_key``),
|
|
123
|
+
routing key, and rate-limit/quality restrictions. A service may declare several
|
|
124
|
+
channels (keyed by free-form channel name, e.g. ``"managed"``, ``"byok"``,
|
|
125
|
+
``"managed-eu"``); the gateway selects one per request.
|
|
126
|
+
|
|
127
|
+
The channel's *type* is derived from its credential/endpoint provenance:
|
|
128
|
+
``managed`` (seller's key, ``${ secrets.* }``), ``byok`` (customer's key,
|
|
129
|
+
``${ customer_secrets.* }``), or ``byoe`` (customer's key + customer-templated
|
|
130
|
+
``base_url``).
|
|
120
131
|
|
|
121
132
|
Extends AccessInterfaceData with extra="allow" to support protocol-specific
|
|
122
133
|
configuration fields (e.g., S3 bucket/region, SMTP host/port) that the
|
|
@@ -207,6 +207,78 @@
|
|
|
207
207
|
"title": "AuthMethodEnum",
|
|
208
208
|
"type": "string"
|
|
209
209
|
},
|
|
210
|
+
"ChannelPriceData": {
|
|
211
|
+
"additionalProperties": false,
|
|
212
|
+
"description": "Channel-based pricing that selects a sub-price by the request's resolved channel.\n\nThe active channel is decided elsewhere — e.g. the gateway picks which keyed\n``upstream_access_config`` (upstream access channel) a request routes through\n(``\"managed\"``, ``\"byok\"``, ``\"byoe\"``, …) — and threads it into\n``calculate_cost(..., channel=...)``. The matching entry in ``channels`` is\nevaluated; when the channel is unknown or unset, the ``default`` channel is used.\n\nThere is no special-casing on the consumer side: every pricing type accepts\nan optional ``channel`` and a channel-independent price simply ignores it, so a\nplain price behaves identically for all channels. Channel-dependent pricing is\njust this one extra optional pricing type.\n\nEach ``channels`` value is itself any pricing object (flat, composite, tiered,\nor even nested channel-based), validated lazily — mirroring how tiered pricing\nholds per-tier prices.\n\nExample:\n {\n \"type\": \"channel\",\n \"default\": \"managed\",\n \"channels\": {\n \"managed\": {\"type\": \"one_million_tokens\", \"input\": \"0.50\", \"output\": \"1.50\"},\n \"byok\": {\"type\": \"constant\", \"price\": \"0\"}\n }\n }",
|
|
213
|
+
"properties": {
|
|
214
|
+
"channels": {
|
|
215
|
+
"additionalProperties": {
|
|
216
|
+
"additionalProperties": true,
|
|
217
|
+
"type": "object"
|
|
218
|
+
},
|
|
219
|
+
"description": "Per-channel pricing keyed by channel name (e.g. 'managed', 'byok'). Each value is a nested pricing object of any type.",
|
|
220
|
+
"minProperties": 1,
|
|
221
|
+
"title": "Channels",
|
|
222
|
+
"type": "object"
|
|
223
|
+
},
|
|
224
|
+
"default": {
|
|
225
|
+
"description": "Channel name evaluated when the request's channel is unknown or unset. Must be one of the keys in `channels`.",
|
|
226
|
+
"title": "Default",
|
|
227
|
+
"type": "string"
|
|
228
|
+
},
|
|
229
|
+
"description": {
|
|
230
|
+
"anyOf": [
|
|
231
|
+
{
|
|
232
|
+
"type": "string"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"type": "null"
|
|
236
|
+
}
|
|
237
|
+
],
|
|
238
|
+
"default": null,
|
|
239
|
+
"description": "Human-readable description of the pricing model",
|
|
240
|
+
"title": "Description"
|
|
241
|
+
},
|
|
242
|
+
"price": {
|
|
243
|
+
"anyOf": [
|
|
244
|
+
{
|
|
245
|
+
"type": "string"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"type": "null"
|
|
249
|
+
}
|
|
250
|
+
],
|
|
251
|
+
"default": null,
|
|
252
|
+
"description": "Summary price for marketplace comparison and sorting. For simple pricing types this is the billing rate. For composite types (add, multiply, tiered, graduated) this is a seller-provided nominal value for marketplace display.",
|
|
253
|
+
"title": "Price"
|
|
254
|
+
},
|
|
255
|
+
"reference": {
|
|
256
|
+
"anyOf": [
|
|
257
|
+
{
|
|
258
|
+
"type": "string"
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"type": "null"
|
|
262
|
+
}
|
|
263
|
+
],
|
|
264
|
+
"default": null,
|
|
265
|
+
"description": "URL to upstream provider's pricing page",
|
|
266
|
+
"title": "Reference"
|
|
267
|
+
},
|
|
268
|
+
"type": {
|
|
269
|
+
"const": "channel",
|
|
270
|
+
"default": "channel",
|
|
271
|
+
"title": "Type",
|
|
272
|
+
"type": "string"
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
"required": [
|
|
276
|
+
"default",
|
|
277
|
+
"channels"
|
|
278
|
+
],
|
|
279
|
+
"title": "ChannelPriceData",
|
|
280
|
+
"type": "object"
|
|
281
|
+
},
|
|
210
282
|
"ConstantPriceData": {
|
|
211
283
|
"additionalProperties": false,
|
|
212
284
|
"description": "Price data for a constant/fixed price.\n\nUsed for fixed fees, discounts, or adjustments that don't depend on usage.\nPrice can be positive (charge) or negative (discount/credit).",
|
|
@@ -964,78 +1036,6 @@
|
|
|
964
1036
|
"title": "MinPriceData",
|
|
965
1037
|
"type": "object"
|
|
966
1038
|
},
|
|
967
|
-
"ModePriceData": {
|
|
968
|
-
"additionalProperties": false,
|
|
969
|
-
"description": "Mode-based pricing that selects a sub-price by the request's resolved mode.\n\nThe active mode is decided elsewhere — e.g. the gateway picks which keyed\n``upstream_access_config`` a request routes through (``\"managed\"``,\n``\"byok\"``, ``\"byoe\"``, …) — and threads it into\n``calculate_cost(..., mode=...)``. The matching entry in ``modes`` is\nevaluated; when the mode is unknown or unset, the ``default`` mode is used.\n\nThere is no special-casing on the consumer side: every pricing type accepts\nan optional ``mode`` and a mode-independent price simply ignores it, so a\nplain price behaves identically for all modes. Mode-dependent pricing is\njust this one extra optional pricing type.\n\nEach ``modes`` value is itself any pricing object (flat, composite, tiered,\nor even nested mode-based), validated lazily — mirroring how tiered pricing\nholds per-tier prices.\n\nExample:\n {\n \"type\": \"mode\",\n \"default\": \"managed\",\n \"modes\": {\n \"managed\": {\"type\": \"one_million_tokens\", \"input\": \"0.50\", \"output\": \"1.50\"},\n \"byok\": {\"type\": \"constant\", \"price\": \"0\"}\n }\n }",
|
|
970
|
-
"properties": {
|
|
971
|
-
"default": {
|
|
972
|
-
"description": "Mode key evaluated when the request's mode is unknown or unset. Must be one of the keys in `modes`.",
|
|
973
|
-
"title": "Default",
|
|
974
|
-
"type": "string"
|
|
975
|
-
},
|
|
976
|
-
"description": {
|
|
977
|
-
"anyOf": [
|
|
978
|
-
{
|
|
979
|
-
"type": "string"
|
|
980
|
-
},
|
|
981
|
-
{
|
|
982
|
-
"type": "null"
|
|
983
|
-
}
|
|
984
|
-
],
|
|
985
|
-
"default": null,
|
|
986
|
-
"description": "Human-readable description of the pricing model",
|
|
987
|
-
"title": "Description"
|
|
988
|
-
},
|
|
989
|
-
"modes": {
|
|
990
|
-
"additionalProperties": {
|
|
991
|
-
"additionalProperties": true,
|
|
992
|
-
"type": "object"
|
|
993
|
-
},
|
|
994
|
-
"description": "Per-mode pricing keyed by mode name (e.g. 'managed', 'byok'). Each value is a nested pricing object of any type.",
|
|
995
|
-
"minProperties": 1,
|
|
996
|
-
"title": "Modes",
|
|
997
|
-
"type": "object"
|
|
998
|
-
},
|
|
999
|
-
"price": {
|
|
1000
|
-
"anyOf": [
|
|
1001
|
-
{
|
|
1002
|
-
"type": "string"
|
|
1003
|
-
},
|
|
1004
|
-
{
|
|
1005
|
-
"type": "null"
|
|
1006
|
-
}
|
|
1007
|
-
],
|
|
1008
|
-
"default": null,
|
|
1009
|
-
"description": "Summary price for marketplace comparison and sorting. For simple pricing types this is the billing rate. For composite types (add, multiply, tiered, graduated) this is a seller-provided nominal value for marketplace display.",
|
|
1010
|
-
"title": "Price"
|
|
1011
|
-
},
|
|
1012
|
-
"reference": {
|
|
1013
|
-
"anyOf": [
|
|
1014
|
-
{
|
|
1015
|
-
"type": "string"
|
|
1016
|
-
},
|
|
1017
|
-
{
|
|
1018
|
-
"type": "null"
|
|
1019
|
-
}
|
|
1020
|
-
],
|
|
1021
|
-
"default": null,
|
|
1022
|
-
"description": "URL to upstream provider's pricing page",
|
|
1023
|
-
"title": "Reference"
|
|
1024
|
-
},
|
|
1025
|
-
"type": {
|
|
1026
|
-
"const": "mode",
|
|
1027
|
-
"default": "mode",
|
|
1028
|
-
"title": "Type",
|
|
1029
|
-
"type": "string"
|
|
1030
|
-
}
|
|
1031
|
-
},
|
|
1032
|
-
"required": [
|
|
1033
|
-
"default",
|
|
1034
|
-
"modes"
|
|
1035
|
-
],
|
|
1036
|
-
"title": "ModePriceData",
|
|
1037
|
-
"type": "object"
|
|
1038
|
-
},
|
|
1039
1039
|
"MultiplyPriceData": {
|
|
1040
1040
|
"additionalProperties": false,
|
|
1041
1041
|
"description": "Composite pricing that multiplies a base price by a factor.\n\nUseful for applying percentage-based adjustments to a base price.\n\nExample:\n {\n \"type\": \"multiply\",\n \"factor\": \"0.70\",\n \"base\": {\"type\": \"one_million_tokens\", \"input\": \"0.50\", \"output\": \"1.50\"}\n }",
|
|
@@ -1932,6 +1932,7 @@
|
|
|
1932
1932
|
"discriminator": {
|
|
1933
1933
|
"mapping": {
|
|
1934
1934
|
"add": "#/$defs/AddPriceData",
|
|
1935
|
+
"channel": "#/$defs/ChannelPriceData",
|
|
1935
1936
|
"constant": "#/$defs/ConstantPriceData",
|
|
1936
1937
|
"expr": "#/$defs/ExprPriceData",
|
|
1937
1938
|
"first": "#/$defs/FirstPriceData",
|
|
@@ -1939,7 +1940,6 @@
|
|
|
1939
1940
|
"image": "#/$defs/ImagePriceData",
|
|
1940
1941
|
"max": "#/$defs/MaxPriceData",
|
|
1941
1942
|
"min": "#/$defs/MinPriceData",
|
|
1942
|
-
"mode": "#/$defs/ModePriceData",
|
|
1943
1943
|
"multiply": "#/$defs/MultiplyPriceData",
|
|
1944
1944
|
"one_byte": "#/$defs/DataPriceData",
|
|
1945
1945
|
"one_day": "#/$defs/TimePriceData",
|
|
@@ -2011,7 +2011,7 @@
|
|
|
2011
2011
|
"$ref": "#/$defs/ExprPriceData"
|
|
2012
2012
|
},
|
|
2013
2013
|
{
|
|
2014
|
-
"$ref": "#/$defs/
|
|
2014
|
+
"$ref": "#/$defs/ChannelPriceData"
|
|
2015
2015
|
}
|
|
2016
2016
|
]
|
|
2017
2017
|
},
|
|
@@ -87,6 +87,78 @@
|
|
|
87
87
|
"title": "AuthMethodEnum",
|
|
88
88
|
"type": "string"
|
|
89
89
|
},
|
|
90
|
+
"ChannelPriceData": {
|
|
91
|
+
"additionalProperties": false,
|
|
92
|
+
"description": "Channel-based pricing that selects a sub-price by the request's resolved channel.\n\nThe active channel is decided elsewhere — e.g. the gateway picks which keyed\n``upstream_access_config`` (upstream access channel) a request routes through\n(``\"managed\"``, ``\"byok\"``, ``\"byoe\"``, …) — and threads it into\n``calculate_cost(..., channel=...)``. The matching entry in ``channels`` is\nevaluated; when the channel is unknown or unset, the ``default`` channel is used.\n\nThere is no special-casing on the consumer side: every pricing type accepts\nan optional ``channel`` and a channel-independent price simply ignores it, so a\nplain price behaves identically for all channels. Channel-dependent pricing is\njust this one extra optional pricing type.\n\nEach ``channels`` value is itself any pricing object (flat, composite, tiered,\nor even nested channel-based), validated lazily — mirroring how tiered pricing\nholds per-tier prices.\n\nExample:\n {\n \"type\": \"channel\",\n \"default\": \"managed\",\n \"channels\": {\n \"managed\": {\"type\": \"one_million_tokens\", \"input\": \"0.50\", \"output\": \"1.50\"},\n \"byok\": {\"type\": \"constant\", \"price\": \"0\"}\n }\n }",
|
|
93
|
+
"properties": {
|
|
94
|
+
"channels": {
|
|
95
|
+
"additionalProperties": {
|
|
96
|
+
"additionalProperties": true,
|
|
97
|
+
"type": "object"
|
|
98
|
+
},
|
|
99
|
+
"description": "Per-channel pricing keyed by channel name (e.g. 'managed', 'byok'). Each value is a nested pricing object of any type.",
|
|
100
|
+
"minProperties": 1,
|
|
101
|
+
"title": "Channels",
|
|
102
|
+
"type": "object"
|
|
103
|
+
},
|
|
104
|
+
"default": {
|
|
105
|
+
"description": "Channel name evaluated when the request's channel is unknown or unset. Must be one of the keys in `channels`.",
|
|
106
|
+
"title": "Default",
|
|
107
|
+
"type": "string"
|
|
108
|
+
},
|
|
109
|
+
"description": {
|
|
110
|
+
"anyOf": [
|
|
111
|
+
{
|
|
112
|
+
"type": "string"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"type": "null"
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"default": null,
|
|
119
|
+
"description": "Human-readable description of the pricing model",
|
|
120
|
+
"title": "Description"
|
|
121
|
+
},
|
|
122
|
+
"price": {
|
|
123
|
+
"anyOf": [
|
|
124
|
+
{
|
|
125
|
+
"type": "string"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"type": "null"
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
"default": null,
|
|
132
|
+
"description": "Summary price for marketplace comparison and sorting. For simple pricing types this is the billing rate. For composite types (add, multiply, tiered, graduated) this is a seller-provided nominal value for marketplace display.",
|
|
133
|
+
"title": "Price"
|
|
134
|
+
},
|
|
135
|
+
"reference": {
|
|
136
|
+
"anyOf": [
|
|
137
|
+
{
|
|
138
|
+
"type": "string"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"type": "null"
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
"default": null,
|
|
145
|
+
"description": "URL to upstream provider's pricing page",
|
|
146
|
+
"title": "Reference"
|
|
147
|
+
},
|
|
148
|
+
"type": {
|
|
149
|
+
"const": "channel",
|
|
150
|
+
"default": "channel",
|
|
151
|
+
"title": "Type",
|
|
152
|
+
"type": "string"
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"required": [
|
|
156
|
+
"default",
|
|
157
|
+
"channels"
|
|
158
|
+
],
|
|
159
|
+
"title": "ChannelPriceData",
|
|
160
|
+
"type": "object"
|
|
161
|
+
},
|
|
90
162
|
"ConstantPriceData": {
|
|
91
163
|
"additionalProperties": false,
|
|
92
164
|
"description": "Price data for a constant/fixed price.\n\nUsed for fixed fees, discounts, or adjustments that don't depend on usage.\nPrice can be positive (charge) or negative (discount/credit).",
|
|
@@ -834,78 +906,6 @@
|
|
|
834
906
|
"title": "MinPriceData",
|
|
835
907
|
"type": "object"
|
|
836
908
|
},
|
|
837
|
-
"ModePriceData": {
|
|
838
|
-
"additionalProperties": false,
|
|
839
|
-
"description": "Mode-based pricing that selects a sub-price by the request's resolved mode.\n\nThe active mode is decided elsewhere — e.g. the gateway picks which keyed\n``upstream_access_config`` a request routes through (``\"managed\"``,\n``\"byok\"``, ``\"byoe\"``, …) — and threads it into\n``calculate_cost(..., mode=...)``. The matching entry in ``modes`` is\nevaluated; when the mode is unknown or unset, the ``default`` mode is used.\n\nThere is no special-casing on the consumer side: every pricing type accepts\nan optional ``mode`` and a mode-independent price simply ignores it, so a\nplain price behaves identically for all modes. Mode-dependent pricing is\njust this one extra optional pricing type.\n\nEach ``modes`` value is itself any pricing object (flat, composite, tiered,\nor even nested mode-based), validated lazily — mirroring how tiered pricing\nholds per-tier prices.\n\nExample:\n {\n \"type\": \"mode\",\n \"default\": \"managed\",\n \"modes\": {\n \"managed\": {\"type\": \"one_million_tokens\", \"input\": \"0.50\", \"output\": \"1.50\"},\n \"byok\": {\"type\": \"constant\", \"price\": \"0\"}\n }\n }",
|
|
840
|
-
"properties": {
|
|
841
|
-
"default": {
|
|
842
|
-
"description": "Mode key evaluated when the request's mode is unknown or unset. Must be one of the keys in `modes`.",
|
|
843
|
-
"title": "Default",
|
|
844
|
-
"type": "string"
|
|
845
|
-
},
|
|
846
|
-
"description": {
|
|
847
|
-
"anyOf": [
|
|
848
|
-
{
|
|
849
|
-
"type": "string"
|
|
850
|
-
},
|
|
851
|
-
{
|
|
852
|
-
"type": "null"
|
|
853
|
-
}
|
|
854
|
-
],
|
|
855
|
-
"default": null,
|
|
856
|
-
"description": "Human-readable description of the pricing model",
|
|
857
|
-
"title": "Description"
|
|
858
|
-
},
|
|
859
|
-
"modes": {
|
|
860
|
-
"additionalProperties": {
|
|
861
|
-
"additionalProperties": true,
|
|
862
|
-
"type": "object"
|
|
863
|
-
},
|
|
864
|
-
"description": "Per-mode pricing keyed by mode name (e.g. 'managed', 'byok'). Each value is a nested pricing object of any type.",
|
|
865
|
-
"minProperties": 1,
|
|
866
|
-
"title": "Modes",
|
|
867
|
-
"type": "object"
|
|
868
|
-
},
|
|
869
|
-
"price": {
|
|
870
|
-
"anyOf": [
|
|
871
|
-
{
|
|
872
|
-
"type": "string"
|
|
873
|
-
},
|
|
874
|
-
{
|
|
875
|
-
"type": "null"
|
|
876
|
-
}
|
|
877
|
-
],
|
|
878
|
-
"default": null,
|
|
879
|
-
"description": "Summary price for marketplace comparison and sorting. For simple pricing types this is the billing rate. For composite types (add, multiply, tiered, graduated) this is a seller-provided nominal value for marketplace display.",
|
|
880
|
-
"title": "Price"
|
|
881
|
-
},
|
|
882
|
-
"reference": {
|
|
883
|
-
"anyOf": [
|
|
884
|
-
{
|
|
885
|
-
"type": "string"
|
|
886
|
-
},
|
|
887
|
-
{
|
|
888
|
-
"type": "null"
|
|
889
|
-
}
|
|
890
|
-
],
|
|
891
|
-
"default": null,
|
|
892
|
-
"description": "URL to upstream provider's pricing page",
|
|
893
|
-
"title": "Reference"
|
|
894
|
-
},
|
|
895
|
-
"type": {
|
|
896
|
-
"const": "mode",
|
|
897
|
-
"default": "mode",
|
|
898
|
-
"title": "Type",
|
|
899
|
-
"type": "string"
|
|
900
|
-
}
|
|
901
|
-
},
|
|
902
|
-
"required": [
|
|
903
|
-
"default",
|
|
904
|
-
"modes"
|
|
905
|
-
],
|
|
906
|
-
"title": "ModePriceData",
|
|
907
|
-
"type": "object"
|
|
908
|
-
},
|
|
909
909
|
"MultiplyPriceData": {
|
|
910
910
|
"additionalProperties": false,
|
|
911
911
|
"description": "Composite pricing that multiplies a base price by a factor.\n\nUseful for applying percentage-based adjustments to a base price.\n\nExample:\n {\n \"type\": \"multiply\",\n \"factor\": \"0.70\",\n \"base\": {\"type\": \"one_million_tokens\", \"input\": \"0.50\", \"output\": \"1.50\"}\n }",
|
|
@@ -1789,7 +1789,7 @@
|
|
|
1789
1789
|
},
|
|
1790
1790
|
"UpstreamAccessConfigData": {
|
|
1791
1791
|
"additionalProperties": true,
|
|
1792
|
-
"description": "
|
|
1792
|
+
"description": "One upstream access channel — a named entry in ``upstream_access_config``.\n\nEach channel is a complete way for the gateway to reach the upstream: a wire\nprotocol (``access_method``), endpoint (``base_url``), credential (``api_key``),\nrouting key, and rate-limit/quality restrictions. A service may declare several\nchannels (keyed by free-form channel name, e.g. ``\"managed\"``, ``\"byok\"``,\n``\"managed-eu\"``); the gateway selects one per request.\n\nThe channel's *type* is derived from its credential/endpoint provenance:\n``managed`` (seller's key, ``${ secrets.* }``), ``byok`` (customer's key,\n``${ customer_secrets.* }``), or ``byoe`` (customer's key + customer-templated\n``base_url``).\n\nExtends AccessInterfaceData with extra=\"allow\" to support protocol-specific\nconfiguration fields (e.g., S3 bucket/region, SMTP host/port) that the\ngateway needs to reach the upstream service.",
|
|
1793
1793
|
"properties": {
|
|
1794
1794
|
"access_method": {
|
|
1795
1795
|
"$ref": "#/$defs/AccessMethodEnum",
|
|
@@ -2014,6 +2014,7 @@
|
|
|
2014
2014
|
"discriminator": {
|
|
2015
2015
|
"mapping": {
|
|
2016
2016
|
"add": "#/$defs/AddPriceData",
|
|
2017
|
+
"channel": "#/$defs/ChannelPriceData",
|
|
2017
2018
|
"constant": "#/$defs/ConstantPriceData",
|
|
2018
2019
|
"expr": "#/$defs/ExprPriceData",
|
|
2019
2020
|
"first": "#/$defs/FirstPriceData",
|
|
@@ -2021,7 +2022,6 @@
|
|
|
2021
2022
|
"image": "#/$defs/ImagePriceData",
|
|
2022
2023
|
"max": "#/$defs/MaxPriceData",
|
|
2023
2024
|
"min": "#/$defs/MinPriceData",
|
|
2024
|
-
"mode": "#/$defs/ModePriceData",
|
|
2025
2025
|
"multiply": "#/$defs/MultiplyPriceData",
|
|
2026
2026
|
"one_byte": "#/$defs/DataPriceData",
|
|
2027
2027
|
"one_day": "#/$defs/TimePriceData",
|
|
@@ -2093,7 +2093,7 @@
|
|
|
2093
2093
|
"$ref": "#/$defs/ExprPriceData"
|
|
2094
2094
|
},
|
|
2095
2095
|
{
|
|
2096
|
-
"$ref": "#/$defs/
|
|
2096
|
+
"$ref": "#/$defs/ChannelPriceData"
|
|
2097
2097
|
}
|
|
2098
2098
|
]
|
|
2099
2099
|
},
|
|
@@ -2153,7 +2153,7 @@
|
|
|
2153
2153
|
"additionalProperties": {
|
|
2154
2154
|
"$ref": "#/$defs/UpstreamAccessConfigData"
|
|
2155
2155
|
},
|
|
2156
|
-
"description": "Upstream access
|
|
2156
|
+
"description": "Upstream access channels, keyed by channel name",
|
|
2157
2157
|
"title": "Upstream Access Config",
|
|
2158
2158
|
"type": "object"
|
|
2159
2159
|
}
|
|
@@ -1808,67 +1808,67 @@ class TestNominalPriceAutoCompute:
|
|
|
1808
1808
|
assert pricing.price == "9.00"
|
|
1809
1809
|
|
|
1810
1810
|
|
|
1811
|
-
class
|
|
1812
|
-
"""Tests for
|
|
1811
|
+
class TestChannelPriceData:
|
|
1812
|
+
"""Tests for channel-based pricing (select a sub-price by resolved channel)."""
|
|
1813
1813
|
|
|
1814
1814
|
def _managed_byok(self) -> dict:
|
|
1815
1815
|
"""Canonical managed+byok: managed is paid, byok is free.
|
|
1816
1816
|
|
|
1817
|
-
Uses ``constant`` sub-prices so the tests exercise
|
|
1817
|
+
Uses ``constant`` sub-prices so the tests exercise channel *selection*
|
|
1818
1818
|
independently of any usage-metric math.
|
|
1819
1819
|
"""
|
|
1820
1820
|
return {
|
|
1821
|
-
"type": "
|
|
1821
|
+
"type": "channel",
|
|
1822
1822
|
"default": "managed",
|
|
1823
|
-
"
|
|
1823
|
+
"channels": {
|
|
1824
1824
|
"managed": {"type": "constant", "price": "6.00"},
|
|
1825
1825
|
"byok": {"type": "constant", "price": "0"},
|
|
1826
1826
|
},
|
|
1827
1827
|
}
|
|
1828
1828
|
|
|
1829
|
-
def
|
|
1830
|
-
"""The resolved
|
|
1829
|
+
def test_selects_matching_channel(self) -> None:
|
|
1830
|
+
"""The resolved channel picks that channel's price."""
|
|
1831
1831
|
pricing = validate_pricing(self._managed_byok())
|
|
1832
1832
|
usage = UsageData()
|
|
1833
1833
|
|
|
1834
|
-
assert pricing.calculate_cost(usage,
|
|
1835
|
-
assert pricing.calculate_cost(usage,
|
|
1834
|
+
assert pricing.calculate_cost(usage, channel="managed") == Decimal("6.00")
|
|
1835
|
+
assert pricing.calculate_cost(usage, channel="byok") == Decimal("0")
|
|
1836
1836
|
|
|
1837
|
-
def
|
|
1838
|
-
"""An unrecognised
|
|
1837
|
+
def test_unknown_channel_falls_back_to_default(self) -> None:
|
|
1838
|
+
"""An unrecognised channel evaluates the default channel."""
|
|
1839
1839
|
pricing = validate_pricing(self._managed_byok())
|
|
1840
1840
|
|
|
1841
|
-
assert pricing.calculate_cost(UsageData(),
|
|
1841
|
+
assert pricing.calculate_cost(UsageData(), channel="nonexistent") == Decimal("6.00")
|
|
1842
1842
|
|
|
1843
|
-
def
|
|
1844
|
-
"""No
|
|
1843
|
+
def test_none_channel_falls_back_to_default(self) -> None:
|
|
1844
|
+
"""No channel (the common case for non-channel-aware callers) uses the default."""
|
|
1845
1845
|
pricing = validate_pricing(self._managed_byok())
|
|
1846
1846
|
|
|
1847
1847
|
assert pricing.calculate_cost(UsageData()) == Decimal("6.00")
|
|
1848
1848
|
|
|
1849
|
-
def
|
|
1850
|
-
"""A plain (
|
|
1849
|
+
def test_non_channel_price_ignores_channel(self) -> None:
|
|
1850
|
+
"""A plain (channel-independent) price yields the same cost for every channel."""
|
|
1851
1851
|
pricing = validate_pricing({"type": "constant", "price": "6.00"})
|
|
1852
1852
|
usage = UsageData()
|
|
1853
1853
|
|
|
1854
1854
|
base = pricing.calculate_cost(usage)
|
|
1855
|
-
assert pricing.calculate_cost(usage,
|
|
1856
|
-
assert pricing.calculate_cost(usage,
|
|
1855
|
+
assert pricing.calculate_cost(usage, channel="byok") == base
|
|
1856
|
+
assert pricing.calculate_cost(usage, channel="managed") == base
|
|
1857
1857
|
assert base == Decimal("6.00")
|
|
1858
1858
|
|
|
1859
1859
|
def test_default_must_exist(self) -> None:
|
|
1860
|
-
"""`default` must name one of the
|
|
1861
|
-
with pytest.raises(ValueError, match="default
|
|
1860
|
+
"""`default` must name one of the channels."""
|
|
1861
|
+
with pytest.raises(ValueError, match="default channel"):
|
|
1862
1862
|
validate_pricing(
|
|
1863
1863
|
{
|
|
1864
|
-
"type": "
|
|
1864
|
+
"type": "channel",
|
|
1865
1865
|
"default": "missing",
|
|
1866
|
-
"
|
|
1866
|
+
"channels": {"managed": {"type": "constant", "price": "1"}},
|
|
1867
1867
|
}
|
|
1868
1868
|
)
|
|
1869
1869
|
|
|
1870
|
-
def
|
|
1871
|
-
"""The summary price auto-computes from the default
|
|
1870
|
+
def test_nominal_price_from_default_channel(self) -> None:
|
|
1871
|
+
"""The summary price auto-computes from the default channel when unset."""
|
|
1872
1872
|
pricing = validate_pricing(self._managed_byok())
|
|
1873
1873
|
assert pricing.price == "6.00" # managed (default) summary price
|
|
1874
1874
|
|
|
@@ -1879,16 +1879,16 @@ class TestModePriceData:
|
|
|
1879
1879
|
pricing = validate_pricing(data)
|
|
1880
1880
|
assert pricing.price == "1.50"
|
|
1881
1881
|
|
|
1882
|
-
def
|
|
1883
|
-
"""`
|
|
1882
|
+
def test_channel_threads_through_composite(self) -> None:
|
|
1883
|
+
"""`channel` propagates into nested children (e.g. a channel price inside `add`)."""
|
|
1884
1884
|
pricing = validate_pricing(
|
|
1885
1885
|
{
|
|
1886
1886
|
"type": "add",
|
|
1887
1887
|
"prices": [
|
|
1888
1888
|
{
|
|
1889
|
-
"type": "
|
|
1889
|
+
"type": "channel",
|
|
1890
1890
|
"default": "managed",
|
|
1891
|
-
"
|
|
1891
|
+
"channels": {
|
|
1892
1892
|
"managed": {"type": "constant", "price": "10"},
|
|
1893
1893
|
"byok": {"type": "constant", "price": "0"},
|
|
1894
1894
|
},
|
|
@@ -1899,17 +1899,17 @@ class TestModePriceData:
|
|
|
1899
1899
|
)
|
|
1900
1900
|
usage = UsageData()
|
|
1901
1901
|
|
|
1902
|
-
# byok: 0 (
|
|
1903
|
-
assert pricing.calculate_cost(usage,
|
|
1904
|
-
assert pricing.calculate_cost(usage,
|
|
1902
|
+
# byok: 0 (channel price) + 1.00 (fee); managed: 10 + 1.00
|
|
1903
|
+
assert pricing.calculate_cost(usage, channel="byok") == Decimal("1.00")
|
|
1904
|
+
assert pricing.calculate_cost(usage, channel="managed") == Decimal("11.00")
|
|
1905
1905
|
|
|
1906
|
-
def
|
|
1907
|
-
"""A
|
|
1906
|
+
def test_channel_price_can_nest_tiered(self) -> None:
|
|
1907
|
+
"""A channel's pricing may itself be any pricing type (here: tiered)."""
|
|
1908
1908
|
pricing = validate_pricing(
|
|
1909
1909
|
{
|
|
1910
|
-
"type": "
|
|
1910
|
+
"type": "channel",
|
|
1911
1911
|
"default": "managed",
|
|
1912
|
-
"
|
|
1912
|
+
"channels": {
|
|
1913
1913
|
"managed": {
|
|
1914
1914
|
"type": "tiered",
|
|
1915
1915
|
"based_on": "request_count",
|
|
@@ -1924,6 +1924,6 @@ class TestModePriceData:
|
|
|
1924
1924
|
)
|
|
1925
1925
|
usage = UsageData()
|
|
1926
1926
|
|
|
1927
|
-
assert pricing.calculate_cost(usage, request_count=10,
|
|
1928
|
-
assert pricing.calculate_cost(usage, request_count=10_000,
|
|
1929
|
-
assert pricing.calculate_cost(usage, request_count=10_000,
|
|
1927
|
+
assert pricing.calculate_cost(usage, request_count=10, channel="managed") == Decimal("5")
|
|
1928
|
+
assert pricing.calculate_cost(usage, request_count=10_000, channel="managed") == Decimal("50")
|
|
1929
|
+
assert pricing.calculate_cost(usage, request_count=10_000, channel="byok") == Decimal("0")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|