outlaya 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- outlaya/__init__.py +90 -0
- outlaya/_batching.py +96 -0
- outlaya/_client.py +728 -0
- outlaya/_context.py +72 -0
- outlaya/_events.py +402 -0
- outlaya/_metadata.py +138 -0
- outlaya/_providers/__init__.py +31 -0
- outlaya/_providers/_base.py +248 -0
- outlaya/_providers/anthropic.py +532 -0
- outlaya/_providers/cursor.py +517 -0
- outlaya/_providers/gemini.py +315 -0
- outlaya/_providers/openai.py +515 -0
- outlaya/_providers/openrouter.py +11 -0
- outlaya/_types.py +182 -0
- outlaya/_usage.py +86 -0
- outlaya/_util.py +283 -0
- outlaya/_version.py +11 -0
- outlaya/_wrap.py +109 -0
- outlaya/py.typed +0 -0
- outlaya-0.1.0.dist-info/METADATA +381 -0
- outlaya-0.1.0.dist-info/RECORD +25 -0
- outlaya-0.1.0.dist-info/WHEEL +5 -0
- outlaya-0.1.0.dist-info/licenses/LICENSE +21 -0
- outlaya-0.1.0.dist-info/licenses/NOTICE +6 -0
- outlaya-0.1.0.dist-info/top_level.txt +1 -0
outlaya/__init__.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Outlaya Python SDK: AI cost attribution across providers.
|
|
2
|
+
|
|
3
|
+
Wrap a supported provider client, read usage from its response, and send one
|
|
4
|
+
normalized event to Outlaya without putting the ingest call on your request
|
|
5
|
+
path.
|
|
6
|
+
|
|
7
|
+
Supported providers: OpenAI, Anthropic, Gemini, OpenRouter (wrapped clients)
|
|
8
|
+
and Cursor (Admin API pull sync).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from ._client import (
|
|
14
|
+
OutlayaClient,
|
|
15
|
+
OutlayaIngestError,
|
|
16
|
+
Transport,
|
|
17
|
+
TransportResponse,
|
|
18
|
+
flush,
|
|
19
|
+
get_client,
|
|
20
|
+
init,
|
|
21
|
+
)
|
|
22
|
+
from ._context import current_metadata, metadata, with_metadata
|
|
23
|
+
from ._providers.anthropic import wrap_anthropic
|
|
24
|
+
from ._providers.cursor import (
|
|
25
|
+
CursorSyncError,
|
|
26
|
+
CursorSyncResult,
|
|
27
|
+
CursorTransport,
|
|
28
|
+
CursorTransportInput,
|
|
29
|
+
CursorUsagePage,
|
|
30
|
+
CursorUsageRecord,
|
|
31
|
+
sync_cursor_usage,
|
|
32
|
+
)
|
|
33
|
+
from ._providers.gemini import wrap_gemini, wrap_google_genai
|
|
34
|
+
from ._providers.openai import wrap_openai, wrap_openrouter
|
|
35
|
+
from ._types import (
|
|
36
|
+
ClientStats,
|
|
37
|
+
JsonScalar,
|
|
38
|
+
OutlayaClientOptions,
|
|
39
|
+
OutlayaEvent,
|
|
40
|
+
OutlayaEventClient,
|
|
41
|
+
OutlayaMetadata,
|
|
42
|
+
UsageLike,
|
|
43
|
+
UsageReport,
|
|
44
|
+
WrapOptions,
|
|
45
|
+
)
|
|
46
|
+
from ._usage import track_usage
|
|
47
|
+
from ._version import __version__
|
|
48
|
+
from ._wrap import wrap
|
|
49
|
+
|
|
50
|
+
SDK_VERSION = __version__
|
|
51
|
+
OutlayaClientStats = ClientStats
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"ClientStats",
|
|
55
|
+
"CursorSyncError",
|
|
56
|
+
"CursorSyncResult",
|
|
57
|
+
"CursorTransport",
|
|
58
|
+
"CursorTransportInput",
|
|
59
|
+
"CursorUsagePage",
|
|
60
|
+
"CursorUsageRecord",
|
|
61
|
+
"JsonScalar",
|
|
62
|
+
"OutlayaClient",
|
|
63
|
+
"OutlayaClientOptions",
|
|
64
|
+
"OutlayaClientStats",
|
|
65
|
+
"OutlayaEvent",
|
|
66
|
+
"OutlayaEventClient",
|
|
67
|
+
"OutlayaIngestError",
|
|
68
|
+
"OutlayaMetadata",
|
|
69
|
+
"SDK_VERSION",
|
|
70
|
+
"Transport",
|
|
71
|
+
"TransportResponse",
|
|
72
|
+
"UsageLike",
|
|
73
|
+
"UsageReport",
|
|
74
|
+
"WrapOptions",
|
|
75
|
+
"__version__",
|
|
76
|
+
"current_metadata",
|
|
77
|
+
"flush",
|
|
78
|
+
"get_client",
|
|
79
|
+
"init",
|
|
80
|
+
"metadata",
|
|
81
|
+
"sync_cursor_usage",
|
|
82
|
+
"track_usage",
|
|
83
|
+
"with_metadata",
|
|
84
|
+
"wrap",
|
|
85
|
+
"wrap_anthropic",
|
|
86
|
+
"wrap_gemini",
|
|
87
|
+
"wrap_google_genai",
|
|
88
|
+
"wrap_openai",
|
|
89
|
+
"wrap_openrouter",
|
|
90
|
+
]
|
outlaya/_batching.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Packing helpers for the Outlaya ingest request limits."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Callable, Generic, List, Sequence, TypeVar
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
#: The ingest route rejects event batches above 500 entries.
|
|
12
|
+
SDK_INGEST_MAX_BATCH_EVENTS = 500
|
|
13
|
+
|
|
14
|
+
#: 4 MiB stays below the API's 5 MiB request ceiling.
|
|
15
|
+
SDK_INGEST_MAX_CLIENT_BODY_BYTES = 4 * 1024 * 1024 - 1024
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def utf8_byte_length(text: str) -> int:
|
|
19
|
+
return len(text.encode("utf-8"))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def json_byte_length(value: object) -> int:
|
|
23
|
+
return utf8_byte_length(json.dumps(value, separators=(",", ":")))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class RequestBatchPlan(Generic[T]):
|
|
28
|
+
batches: List[List[T]] = field(default_factory=list)
|
|
29
|
+
oversized_items: List[T] = field(default_factory=list)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def plan_request_batches(
|
|
33
|
+
items: Sequence[T],
|
|
34
|
+
max_items: int,
|
|
35
|
+
max_bytes: int,
|
|
36
|
+
serialize: Callable[[List[T]], str],
|
|
37
|
+
) -> RequestBatchPlan[T]:
|
|
38
|
+
"""Pack items into JSON requests within the item and byte limits.
|
|
39
|
+
|
|
40
|
+
Items that cannot fit alone are returned separately.
|
|
41
|
+
"""
|
|
42
|
+
plan: RequestBatchPlan[T] = RequestBatchPlan()
|
|
43
|
+
current: List[T] = []
|
|
44
|
+
|
|
45
|
+
def flush() -> None:
|
|
46
|
+
if current:
|
|
47
|
+
plan.batches.append(current.copy())
|
|
48
|
+
current.clear()
|
|
49
|
+
|
|
50
|
+
for item in items:
|
|
51
|
+
if len(current) >= max_items:
|
|
52
|
+
flush()
|
|
53
|
+
|
|
54
|
+
if not current:
|
|
55
|
+
if utf8_byte_length(serialize([item])) > max_bytes:
|
|
56
|
+
plan.oversized_items.append(item)
|
|
57
|
+
else:
|
|
58
|
+
current.append(item)
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
candidate = [*current, item]
|
|
62
|
+
if utf8_byte_length(serialize(candidate)) <= max_bytes:
|
|
63
|
+
current.append(item)
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
flush()
|
|
67
|
+
if utf8_byte_length(serialize([item])) > max_bytes:
|
|
68
|
+
plan.oversized_items.append(item)
|
|
69
|
+
else:
|
|
70
|
+
current.append(item)
|
|
71
|
+
|
|
72
|
+
flush()
|
|
73
|
+
return plan
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def prepend_and_bound(
|
|
77
|
+
existing: List[T],
|
|
78
|
+
earlier: List[T],
|
|
79
|
+
max_items: float,
|
|
80
|
+
) -> List[T]:
|
|
81
|
+
"""Prepend failed older items while dropping only newer tail items."""
|
|
82
|
+
combined = [*earlier, *existing]
|
|
83
|
+
if max_items == float("inf") or len(combined) <= max_items:
|
|
84
|
+
return combined
|
|
85
|
+
return combined[: max(0, int(max_items))]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
__all__ = [
|
|
89
|
+
"SDK_INGEST_MAX_BATCH_EVENTS",
|
|
90
|
+
"SDK_INGEST_MAX_CLIENT_BODY_BYTES",
|
|
91
|
+
"RequestBatchPlan",
|
|
92
|
+
"json_byte_length",
|
|
93
|
+
"plan_request_batches",
|
|
94
|
+
"prepend_and_bound",
|
|
95
|
+
"utf8_byte_length",
|
|
96
|
+
]
|