viio-sync-api-sdk 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.
@@ -0,0 +1,27 @@
1
+ """Async Python SDK for the Viio Sync API."""
2
+
3
+ from ._auth import BearerTokenProvider, SyncApiAuthentication
4
+ from ._client import SyncApiBatch, SyncApiClient
5
+ from ._errors import (
6
+ SyncApiAuthenticationError,
7
+ SyncApiBatchAlreadyInProgressError,
8
+ SyncApiBatchStateError,
9
+ SyncApiClosedError,
10
+ SyncApiError,
11
+ SyncApiProtocolError,
12
+ )
13
+ from ._options import SyncApiClientOptions
14
+
15
+ __all__ = [
16
+ "BearerTokenProvider",
17
+ "SyncApiAuthentication",
18
+ "SyncApiAuthenticationError",
19
+ "SyncApiBatch",
20
+ "SyncApiBatchAlreadyInProgressError",
21
+ "SyncApiBatchStateError",
22
+ "SyncApiClient",
23
+ "SyncApiClientOptions",
24
+ "SyncApiClosedError",
25
+ "SyncApiError",
26
+ "SyncApiProtocolError",
27
+ ]
viio_sync_api/_auth.py ADDED
@@ -0,0 +1,88 @@
1
+ """Authentication modes shared by high-level and generated client calls."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import inspect
7
+ from abc import ABC, abstractmethod
8
+ from collections.abc import Awaitable, Callable
9
+ from dataclasses import dataclass
10
+ from typing import Protocol
11
+
12
+ from ._errors import SyncApiAuthenticationError
13
+
14
+ BearerTokenProvider = Callable[[], Awaitable[str]]
15
+ AuthorizationMetadata = tuple[str, str]
16
+
17
+
18
+ class _AuthorizationProvider(Protocol):
19
+ async def get_authorization(self) -> AuthorizationMetadata: ...
20
+
21
+ async def close(self) -> None: ...
22
+
23
+
24
+ class SyncApiAuthentication(ABC):
25
+ """Defines exactly one authentication mode for a client."""
26
+
27
+ @staticmethod
28
+ def api_key(api_key: str) -> SyncApiAuthentication:
29
+ """Use a fixed API key sent with the ``ApiKey`` authorization scheme."""
30
+ if not isinstance(api_key, str) or not api_key.strip():
31
+ raise ValueError("api_key cannot be empty")
32
+ return _ProviderAuthentication("ApiKey", lambda: _completed(api_key))
33
+
34
+ @staticmethod
35
+ def bearer_token(token_provider: BearerTokenProvider) -> SyncApiAuthentication:
36
+ """Use an asynchronous bearer provider owned by a Viio-managed caller."""
37
+ if not callable(token_provider):
38
+ raise TypeError("token_provider must be callable")
39
+ return _ProviderAuthentication("Bearer", token_provider)
40
+
41
+ @abstractmethod
42
+ def _create_provider(self) -> _AuthorizationProvider:
43
+ raise NotImplementedError
44
+
45
+
46
+ async def _completed(value: str) -> str:
47
+ return value
48
+
49
+
50
+ @dataclass(frozen=True, slots=True, repr=False)
51
+ class _ProviderAuthentication(SyncApiAuthentication):
52
+ scheme: str
53
+ value_provider: BearerTokenProvider
54
+
55
+ def _create_provider(self) -> _AuthorizationProvider:
56
+ return _DelegateAuthorizationProvider(self.scheme, self.value_provider)
57
+
58
+ def __repr__(self) -> str:
59
+ return f"SyncApiAuthentication({self.scheme})"
60
+
61
+
62
+ @dataclass(slots=True)
63
+ class _DelegateAuthorizationProvider:
64
+ scheme: str
65
+ value_provider: BearerTokenProvider
66
+
67
+ async def get_authorization(self) -> AuthorizationMetadata:
68
+ try:
69
+ value_awaitable = self.value_provider()
70
+ except Exception:
71
+ raise SyncApiAuthenticationError("The authentication provider failed to provide a credential") from None
72
+
73
+ if not inspect.isawaitable(value_awaitable):
74
+ raise SyncApiAuthenticationError("The authentication provider returned no awaitable credential")
75
+
76
+ try:
77
+ value = await value_awaitable
78
+ except asyncio.CancelledError:
79
+ raise
80
+ except Exception:
81
+ raise SyncApiAuthenticationError("The authentication provider failed to provide a credential") from None
82
+
83
+ if not isinstance(value, str) or not value.strip():
84
+ raise SyncApiAuthenticationError("The authentication provider returned an empty credential")
85
+ return "authorization", f"{self.scheme} {value}"
86
+
87
+ async def close(self) -> None:
88
+ return None
@@ -0,0 +1,192 @@
1
+ """High-level AsyncIO client and explicit batch lifecycle."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from enum import Enum
7
+ from types import TracebackType
8
+
9
+ from ._errors import (
10
+ SyncApiBatchAlreadyInProgressError,
11
+ SyncApiBatchStateError,
12
+ SyncApiClosedError,
13
+ SyncApiProtocolError,
14
+ )
15
+ from ._options import SyncApiClientOptions
16
+ from ._transport import _GrpcSyncApiTransport, _SyncApiTransport
17
+ from .v1 import (
18
+ SYNC_ERROR_CODE_SYNC_ALREADY_IN_PROGRESS,
19
+ AbortSyncBatchRequest,
20
+ CompleteSyncBatchRequest,
21
+ StartSyncBatchRequest,
22
+ SyncRecordsRequest,
23
+ SyncServiceStub,
24
+ )
25
+
26
+ _TYPED_WRAPPERS = (
27
+ "accounts",
28
+ "licenses",
29
+ "plans",
30
+ "usage",
31
+ "employees",
32
+ "groups",
33
+ "group_members",
34
+ "externally_discovered_usage",
35
+ "devices",
36
+ "audit_logs",
37
+ "ai_usage",
38
+ "ai_costs",
39
+ )
40
+
41
+
42
+ class SyncApiClient:
43
+ """Supported AsyncIO client for authenticated Sync API batch operations."""
44
+
45
+ def __init__(
46
+ self,
47
+ options: SyncApiClientOptions,
48
+ *,
49
+ _transport: _SyncApiTransport | None = None,
50
+ ) -> None:
51
+ if not isinstance(options, SyncApiClientOptions):
52
+ raise TypeError("options must be a SyncApiClientOptions")
53
+ options.validate()
54
+ self._transport = _transport or _GrpcSyncApiTransport(options)
55
+ self._closed = False
56
+
57
+ @property
58
+ def grpc_client(self) -> SyncServiceStub:
59
+ """The authenticated generated v1 stub for advanced calls."""
60
+ self._ensure_open()
61
+ return self._transport.grpc_client
62
+
63
+ async def start_batch(
64
+ self,
65
+ direct_integration_installation_id: str,
66
+ ) -> SyncApiBatch:
67
+ """Start a server-owned batch and return its lifecycle-aware handle."""
68
+ self._ensure_open()
69
+ if not isinstance(direct_integration_installation_id, str) or not direct_integration_installation_id.strip():
70
+ raise ValueError("direct_integration_installation_id cannot be empty")
71
+
72
+ response = await self._transport.start(
73
+ StartSyncBatchRequest(direct_integration_installation_id=direct_integration_installation_id)
74
+ )
75
+ if not response.success:
76
+ if response.error_code == SYNC_ERROR_CODE_SYNC_ALREADY_IN_PROGRESS:
77
+ raise SyncApiBatchAlreadyInProgressError(response.message)
78
+ raise SyncApiProtocolError("StartBatch", response.message)
79
+ if not response.batch_id.strip():
80
+ raise SyncApiProtocolError(
81
+ "StartBatch",
82
+ "The server returned an empty batch ID.",
83
+ )
84
+ return SyncApiBatch(self, response.batch_id)
85
+
86
+ async def _write(self, batch_id: str, request: SyncRecordsRequest) -> None:
87
+ self._ensure_open()
88
+ if not isinstance(request, SyncRecordsRequest):
89
+ raise TypeError("request must be a SyncRecordsRequest")
90
+ wrapper_count = sum(request.HasField(field) for field in _TYPED_WRAPPERS)
91
+ if wrapper_count != 1:
92
+ raise ValueError("A write request must contain exactly one typed records wrapper")
93
+
94
+ request_with_batch_id = SyncRecordsRequest()
95
+ request_with_batch_id.CopyFrom(request)
96
+ request_with_batch_id.batch_id = batch_id
97
+ response = await self._transport.write(request_with_batch_id)
98
+ if not response.success:
99
+ raise SyncApiProtocolError("Write", response.message)
100
+
101
+ async def _complete(self, batch_id: str) -> None:
102
+ self._ensure_open()
103
+ response = await self._transport.complete(CompleteSyncBatchRequest(batch_id=batch_id))
104
+ if not response.success:
105
+ raise SyncApiProtocolError("CompleteBatch", response.message)
106
+
107
+ async def _abort(self, batch_id: str) -> None:
108
+ self._ensure_open()
109
+ response = await self._transport.abort(AbortSyncBatchRequest(batch_id=batch_id))
110
+ if not response.success:
111
+ raise SyncApiProtocolError("AbortBatch", response.message)
112
+
113
+ def _ensure_open(self) -> None:
114
+ if self._closed:
115
+ raise SyncApiClosedError("The Sync API client is closed")
116
+
117
+ async def close(self) -> None:
118
+ """Release SDK-owned channel and authentication resources."""
119
+ if self._closed:
120
+ return
121
+ self._closed = True
122
+ await self._transport.close()
123
+
124
+ async def __aenter__(self) -> SyncApiClient:
125
+ self._ensure_open()
126
+ return self
127
+
128
+ async def __aexit__(
129
+ self,
130
+ exception_type: type[BaseException] | None,
131
+ exception: BaseException | None,
132
+ traceback: TracebackType | None,
133
+ ) -> None:
134
+ del exception_type, exception, traceback
135
+ await self.close()
136
+
137
+
138
+ class SyncApiBatch:
139
+ """One explicit Sync API batch with serialized local lifecycle checks."""
140
+
141
+ def __init__(self, client: SyncApiClient, batch_id: str) -> None:
142
+ self._client = client
143
+ self._batch_id = batch_id
144
+ self._operation_lock = asyncio.Lock()
145
+ self._state = _BatchState.ACTIVE
146
+ self._closed = False
147
+
148
+ @property
149
+ def batch_id(self) -> str:
150
+ """The server-generated batch identifier."""
151
+ return self._batch_id
152
+
153
+ async def write(self, request: SyncRecordsRequest) -> None:
154
+ """Write one typed wrapper; concurrent batch operations are serialized."""
155
+ self._ensure_open()
156
+ async with self._operation_lock:
157
+ self._ensure_active()
158
+ await self._client._write(self._batch_id, request)
159
+
160
+ async def complete(self) -> None:
161
+ """Complete the batch and reject future operations after success."""
162
+ self._ensure_open()
163
+ async with self._operation_lock:
164
+ self._ensure_active()
165
+ await self._client._complete(self._batch_id)
166
+ self._state = _BatchState.COMPLETED
167
+
168
+ async def abort(self) -> None:
169
+ """Abort the batch and reject future operations after success."""
170
+ self._ensure_open()
171
+ async with self._operation_lock:
172
+ self._ensure_active()
173
+ await self._client._abort(self._batch_id)
174
+ self._state = _BatchState.ABORTED
175
+
176
+ def close(self) -> None:
177
+ """Release local state without completing or aborting the remote batch."""
178
+ self._closed = True
179
+
180
+ def _ensure_open(self) -> None:
181
+ if self._closed:
182
+ raise SyncApiClosedError("The Sync API batch is closed")
183
+
184
+ def _ensure_active(self) -> None:
185
+ if self._state is not _BatchState.ACTIVE:
186
+ raise SyncApiBatchStateError(f"The batch has already been {self._state.value}")
187
+
188
+
189
+ class _BatchState(Enum):
190
+ ACTIVE = "active"
191
+ COMPLETED = "completed"
192
+ ABORTED = "aborted"
@@ -0,0 +1,61 @@
1
+ """Validate a Sync API URL and translate it to a Python gRPC target."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ipaddress
6
+ from dataclasses import dataclass
7
+ from urllib.parse import SplitResult, urlsplit
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class ParsedEndpoint:
12
+ secure: bool
13
+ target: str
14
+
15
+
16
+ def parse_endpoint(value: str) -> ParsedEndpoint:
17
+ if not isinstance(value, str) or not value.strip():
18
+ raise ValueError("endpoint cannot be empty")
19
+
20
+ try:
21
+ parsed = urlsplit(value)
22
+ port = parsed.port
23
+ except ValueError as error:
24
+ raise ValueError("endpoint must be a valid absolute URL") from error
25
+
26
+ if (
27
+ parsed.scheme not in {"https", "http"}
28
+ or not parsed.hostname
29
+ or parsed.username is not None
30
+ or parsed.password is not None
31
+ or parsed.query
32
+ or parsed.fragment
33
+ ):
34
+ raise ValueError(
35
+ "endpoint must be an absolute HTTPS URL, or an HTTP loopback URL, without embedded credentials"
36
+ )
37
+
38
+ if parsed.path not in {"", "/"}:
39
+ raise ValueError("endpoint cannot contain a path")
40
+
41
+ secure = parsed.scheme == "https"
42
+ if not secure and not _is_loopback(parsed):
43
+ raise ValueError("endpoint must use HTTPS unless it targets a loopback host")
44
+
45
+ endpoint_port = port or (443 if secure else 80)
46
+ host = parsed.hostname
47
+ assert host is not None
48
+ target_host = f"[{host}]" if ":" in host else host
49
+ return ParsedEndpoint(secure=secure, target=f"{target_host}:{endpoint_port}")
50
+
51
+
52
+ def _is_loopback(parsed: SplitResult) -> bool:
53
+ hostname = parsed.hostname
54
+ if hostname is None:
55
+ return False
56
+ if hostname.lower() == "localhost":
57
+ return True
58
+ try:
59
+ return ipaddress.ip_address(hostname).is_loopback
60
+ except ValueError:
61
+ return False
@@ -0,0 +1,32 @@
1
+ """Public SDK-specific exceptions."""
2
+
3
+
4
+ class SyncApiError(Exception):
5
+ """Base class for SDK-specific failures."""
6
+
7
+
8
+ class SyncApiAuthenticationError(SyncApiError):
9
+ """Authentication could not produce a usable credential."""
10
+
11
+
12
+ class SyncApiProtocolError(SyncApiError):
13
+ """The Sync API returned a valid response that reports failure."""
14
+
15
+ def __init__(self, operation: str, message: str) -> None:
16
+ super().__init__(f"Sync API operation {operation!r} failed: {message}")
17
+ self.operation = operation
18
+
19
+
20
+ class SyncApiBatchAlreadyInProgressError(SyncApiProtocolError):
21
+ """A batch could not start because another sync is already in progress."""
22
+
23
+ def __init__(self, message: str) -> None:
24
+ super().__init__("StartBatch", message)
25
+
26
+
27
+ class SyncApiBatchStateError(SyncApiError):
28
+ """An operation is invalid for the batch's current local state."""
29
+
30
+
31
+ class SyncApiClosedError(SyncApiError):
32
+ """An operation was attempted on a closed SDK resource."""
@@ -0,0 +1,35 @@
1
+ """Sync API client options."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from ._auth import SyncApiAuthentication
8
+ from ._endpoint import ParsedEndpoint, parse_endpoint
9
+
10
+
11
+ @dataclass(slots=True)
12
+ class SyncApiClientOptions:
13
+ """Configures an authenticated AsyncIO Sync API client."""
14
+
15
+ endpoint: str
16
+ authentication: SyncApiAuthentication
17
+ # Maximum response-message size this client accepts. None uses gRPC's default.
18
+ max_receive_message_size: int | None = 16 * 1024 * 1024
19
+ # Maximum request-message size this client sends. None uses gRPC's default.
20
+ max_send_message_size: int | None = None
21
+ timeout: float = 100.0
22
+
23
+ def validate(self) -> ParsedEndpoint:
24
+ parsed = parse_endpoint(self.endpoint)
25
+ if not isinstance(self.authentication, SyncApiAuthentication):
26
+ raise TypeError("authentication must be a SyncApiAuthentication")
27
+ if self.max_receive_message_size is not None and self.max_receive_message_size <= 0:
28
+ raise ValueError("max_receive_message_size must be positive")
29
+ if self.max_send_message_size is not None and self.max_send_message_size <= 0:
30
+ raise ValueError("max_send_message_size must be positive")
31
+ if self.timeout <= 0:
32
+ raise ValueError("timeout must be positive and finite")
33
+ if self.timeout == float("inf"):
34
+ raise ValueError("timeout must be positive and finite")
35
+ return parsed
@@ -0,0 +1,154 @@
1
+ """Authenticated gRPC AsyncIO transport."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import AsyncIterator, Awaitable, Callable
6
+ from typing import Any, Protocol, cast
7
+
8
+ import grpc
9
+
10
+ from ._auth import _AuthorizationProvider
11
+ from ._options import SyncApiClientOptions
12
+ from .v1 import (
13
+ AbortSyncBatchRequest,
14
+ AbortSyncBatchResponse,
15
+ CompleteSyncBatchRequest,
16
+ CompleteSyncBatchResponse,
17
+ StartSyncBatchRequest,
18
+ StartSyncBatchResponse,
19
+ SyncRecordsRequest,
20
+ SyncRecordsResponse,
21
+ SyncServiceStub,
22
+ )
23
+
24
+
25
+ class _SyncApiTransport(Protocol):
26
+ @property
27
+ def grpc_client(self) -> SyncServiceStub: ...
28
+
29
+ async def start(self, request: StartSyncBatchRequest) -> StartSyncBatchResponse: ...
30
+
31
+ async def write(self, request: SyncRecordsRequest) -> SyncRecordsResponse: ...
32
+
33
+ async def complete(self, request: CompleteSyncBatchRequest) -> CompleteSyncBatchResponse: ...
34
+
35
+ async def abort(self, request: AbortSyncBatchRequest) -> AbortSyncBatchResponse: ...
36
+
37
+ async def close(self) -> None: ...
38
+
39
+
40
+ class _AuthenticationInterceptor:
41
+ def __init__(self, authorization_provider: _AuthorizationProvider) -> None:
42
+ self._authorization_provider = authorization_provider
43
+
44
+ async def _authenticated_details(
45
+ self,
46
+ details: grpc.aio.ClientCallDetails,
47
+ ) -> grpc.aio.ClientCallDetails:
48
+ authorization = await self._authorization_provider.get_authorization()
49
+ metadata = grpc.aio.Metadata(*(item for item in (details.metadata or ()) if item[0].lower() != "authorization"))
50
+ metadata.add(*authorization)
51
+ return grpc.aio.ClientCallDetails(
52
+ method=details.method,
53
+ timeout=details.timeout,
54
+ metadata=metadata,
55
+ credentials=details.credentials,
56
+ wait_for_ready=details.wait_for_ready,
57
+ )
58
+
59
+
60
+ class _UnaryUnaryAuthenticationInterceptor(
61
+ _AuthenticationInterceptor,
62
+ grpc.aio.UnaryUnaryClientInterceptor,
63
+ ):
64
+ async def intercept_unary_unary(
65
+ self,
66
+ continuation: Callable[[grpc.aio.ClientCallDetails, Any], Awaitable[Any]],
67
+ client_call_details: grpc.aio.ClientCallDetails,
68
+ request: Any,
69
+ ) -> Any:
70
+ return await continuation(await self._authenticated_details(client_call_details), request)
71
+
72
+
73
+ class _StreamUnaryAuthenticationInterceptor(
74
+ _AuthenticationInterceptor,
75
+ grpc.aio.StreamUnaryClientInterceptor,
76
+ ):
77
+ async def intercept_stream_unary(
78
+ self,
79
+ continuation: Callable[[grpc.aio.ClientCallDetails, Any], Awaitable[Any]],
80
+ client_call_details: grpc.aio.ClientCallDetails,
81
+ request_iterator: Any,
82
+ ) -> Any:
83
+ return await continuation(await self._authenticated_details(client_call_details), request_iterator)
84
+
85
+
86
+ class _GrpcSyncApiTransport:
87
+ def __init__(self, options: SyncApiClientOptions) -> None:
88
+ endpoint = options.validate()
89
+ self._timeout = options.timeout
90
+ self._authorization_provider = options.authentication._create_provider()
91
+ interceptors: list[grpc.aio.ClientInterceptor] = [
92
+ _UnaryUnaryAuthenticationInterceptor(self._authorization_provider),
93
+ _StreamUnaryAuthenticationInterceptor(self._authorization_provider),
94
+ ]
95
+ channel_options: list[tuple[str, Any]] = []
96
+ if options.max_receive_message_size is not None:
97
+ channel_options.append(("grpc.max_receive_message_length", options.max_receive_message_size))
98
+ if options.max_send_message_size is not None:
99
+ channel_options.append(("grpc.max_send_message_length", options.max_send_message_size))
100
+
101
+ if endpoint.secure:
102
+ credentials = grpc.ssl_channel_credentials()
103
+ self._channel = grpc.aio.secure_channel(
104
+ endpoint.target,
105
+ credentials,
106
+ options=channel_options,
107
+ interceptors=interceptors,
108
+ )
109
+ else:
110
+ self._channel = grpc.aio.insecure_channel(
111
+ endpoint.target,
112
+ options=channel_options,
113
+ interceptors=interceptors,
114
+ )
115
+
116
+ self._grpc_client = SyncServiceStub(self._channel) # type: ignore[no-untyped-call]
117
+ self._closed = False
118
+
119
+ @property
120
+ def grpc_client(self) -> SyncServiceStub:
121
+ return self._grpc_client
122
+
123
+ async def start(self, request: StartSyncBatchRequest) -> StartSyncBatchResponse:
124
+ response = await self._grpc_client.StartSyncBatch(request, timeout=self._timeout)
125
+ return cast(StartSyncBatchResponse, response)
126
+
127
+ async def write(self, request: SyncRecordsRequest) -> SyncRecordsResponse:
128
+ response = await self._grpc_client.SyncRecords(
129
+ _single_request(request),
130
+ timeout=self._timeout,
131
+ )
132
+ return cast(SyncRecordsResponse, response)
133
+
134
+ async def complete(
135
+ self,
136
+ request: CompleteSyncBatchRequest,
137
+ ) -> CompleteSyncBatchResponse:
138
+ response = await self._grpc_client.CompleteSyncBatch(request, timeout=self._timeout)
139
+ return cast(CompleteSyncBatchResponse, response)
140
+
141
+ async def abort(self, request: AbortSyncBatchRequest) -> AbortSyncBatchResponse:
142
+ response = await self._grpc_client.AbortSyncBatch(request, timeout=self._timeout)
143
+ return cast(AbortSyncBatchResponse, response)
144
+
145
+ async def close(self) -> None:
146
+ if self._closed:
147
+ return
148
+ self._closed = True
149
+ await self._channel.close()
150
+ await self._authorization_provider.close()
151
+
152
+
153
+ async def _single_request(request: SyncRecordsRequest) -> AsyncIterator[SyncRecordsRequest]:
154
+ yield request
viio_sync_api/py.typed ADDED
File without changes
@@ -0,0 +1,10 @@
1
+ """Generated ``viio.sync.v1`` protobuf messages and gRPC service types."""
2
+
3
+ from .anthropic_pb2 import * # noqa: F403
4
+ from .bamboo_hr_pb2 import * # noqa: F403
5
+ from .contracts_pb2 import * # noqa: F403
6
+ from .contracts_pb2_grpc import SyncServiceStub as SyncServiceStub
7
+ from .generic_pb2 import * # noqa: F403
8
+ from .google_pb2 import * # noqa: F403
9
+ from .microsoft_pb2 import * # noqa: F403
10
+ from .okta_pb2 import * # noqa: F403
@@ -0,0 +1,62 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: viio/sync/v1/anthropic.proto
5
+ # Protobuf Python Version: 7.35.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 7,
15
+ 35,
16
+ 1,
17
+ '',
18
+ 'viio/sync/v1/anthropic.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
26
+
27
+
28
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cviio/sync/v1/anthropic.proto\x12\x0cviio.sync.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xa4\x05\n\x12\x41nthropicChatUsage\x12/\n\x0bstarting_at\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12-\n\tending_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x17\n\naccount_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x17\n\napi_key_id\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x1f\n\x12service_account_id\x18\x05 \x01(\tH\x02\x88\x01\x01\x12\x19\n\x0cworkspace_id\x18\x06 \x01(\tH\x03\x88\x01\x01\x12\x12\n\x05model\x18\x07 \x01(\tH\x04\x88\x01\x01\x12\x19\n\x0cservice_tier\x18\x08 \x01(\tH\x05\x88\x01\x01\x12\x1b\n\x0e\x63ontext_window\x18\t \x01(\tH\x06\x88\x01\x01\x12\x1a\n\rinference_geo\x18\n \x01(\tH\x07\x88\x01\x01\x12\x1d\n\x15uncached_input_tokens\x18\x0b \x01(\x03\x12\x1f\n\x17\x63\x61\x63he_read_input_tokens\x18\x0c \x01(\x03\x12\x15\n\routput_tokens\x18\r \x01(\x03\x12<\n\x0e\x63\x61\x63he_creation\x18\x0e \x01(\x0b\x32$.viio.sync.v1.AnthropicCacheCreation\x12=\n\x0fserver_tool_use\x18\x0f \x01(\x0b\x32$.viio.sync.v1.AnthropicServerToolUseB\r\n\x0b_account_idB\r\n\x0b_api_key_idB\x15\n\x13_service_account_idB\x0f\n\r_workspace_idB\x08\n\x06_modelB\x0f\n\r_service_tierB\x11\n\x0f_context_windowB\x10\n\x0e_inference_geo\"^\n\x16\x41nthropicCacheCreation\x12!\n\x19\x65phemeral_1h_input_tokens\x18\x01 \x01(\x03\x12!\n\x19\x65phemeral_5m_input_tokens\x18\x02 \x01(\x03\"5\n\x16\x41nthropicServerToolUse\x12\x1b\n\x13web_search_requests\x18\x01 \x01(\x03\"\xdd\x03\n\rAnthropicCost\x12/\n\x0bstarting_at\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12-\n\tending_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0e\n\x06\x61mount\x18\x03 \x01(\t\x12\x10\n\x08\x63urrency\x18\x04 \x01(\t\x12\x19\n\x0cworkspace_id\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x06 \x01(\tH\x01\x88\x01\x01\x12\x16\n\tcost_type\x18\x07 \x01(\tH\x02\x88\x01\x01\x12\x17\n\ntoken_type\x18\x08 \x01(\tH\x03\x88\x01\x01\x12\x12\n\x05model\x18\t \x01(\tH\x04\x88\x01\x01\x12\x19\n\x0cservice_tier\x18\n \x01(\tH\x05\x88\x01\x01\x12\x1b\n\x0e\x63ontext_window\x18\x0b \x01(\tH\x06\x88\x01\x01\x12\x1a\n\rinference_geo\x18\x0c \x01(\tH\x07\x88\x01\x01\x42\x0f\n\r_workspace_idB\x0e\n\x0c_descriptionB\x0c\n\n_cost_typeB\r\n\x0b_token_typeB\x08\n\x06_modelB\x0f\n\r_service_tierB\x11\n\x0f_context_windowB\x10\n\x0e_inference_geo\"\x92\x04\n\x18\x41nthropicClaudeCodeUsage\x12(\n\x04\x64\x61te\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x35\n\x05\x61\x63tor\x18\x02 \x01(\x0b\x32&.viio.sync.v1.AnthropicClaudeCodeActor\x12\x1c\n\x0forganization_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x1a\n\rcustomer_type\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x1e\n\x11subscription_type\x18\x05 \x01(\tH\x02\x88\x01\x01\x12\x1a\n\rterminal_type\x18\x06 \x01(\tH\x03\x88\x01\x01\x12\x42\n\x0c\x63ore_metrics\x18\x07 \x01(\x0b\x32,.viio.sync.v1.AnthropicClaudeCodeCoreMetrics\x12\x42\n\x0ctool_actions\x18\x08 \x01(\x0b\x32,.viio.sync.v1.AnthropicClaudeCodeToolActions\x12I\n\x10model_breakdowns\x18\t \x03(\x0b\x32/.viio.sync.v1.AnthropicClaudeCodeModelBreakdownB\x12\n\x10_organization_idB\x10\n\x0e_customer_typeB\x14\n\x12_subscription_typeB\x10\n\x0e_terminal_type\"\x82\x01\n\x18\x41nthropicClaudeCodeActor\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x1a\n\remail_address\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x19\n\x0c\x61pi_key_name\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x10\n\x0e_email_addressB\x0f\n\r_api_key_name\"\xc1\x01\n\x1e\x41nthropicClaudeCodeCoreMetrics\x12\x14\n\x0cnum_sessions\x18\x01 \x01(\x03\x12\x43\n\rlines_of_code\x18\x02 \x01(\x0b\x32,.viio.sync.v1.AnthropicClaudeCodeLinesOfCode\x12\x1e\n\x16\x63ommits_by_claude_code\x18\x03 \x01(\x03\x12$\n\x1cpull_requests_by_claude_code\x18\x04 \x01(\x03\"@\n\x1e\x41nthropicClaudeCodeLinesOfCode\x12\r\n\x05\x61\x64\x64\x65\x64\x18\x01 \x01(\x03\x12\x0f\n\x07removed\x18\x02 \x01(\x03\"\xb0\x02\n\x1e\x41nthropicClaudeCodeToolActions\x12>\n\tedit_tool\x18\x01 \x01(\x0b\x32+.viio.sync.v1.AnthropicClaudeCodeToolAction\x12\x44\n\x0fmulti_edit_tool\x18\x02 \x01(\x0b\x32+.viio.sync.v1.AnthropicClaudeCodeToolAction\x12?\n\nwrite_tool\x18\x03 \x01(\x0b\x32+.viio.sync.v1.AnthropicClaudeCodeToolAction\x12G\n\x12notebook_edit_tool\x18\x04 \x01(\x0b\x32+.viio.sync.v1.AnthropicClaudeCodeToolAction\"C\n\x1d\x41nthropicClaudeCodeToolAction\x12\x10\n\x08\x61\x63\x63\x65pted\x18\x01 \x01(\x03\x12\x10\n\x08rejected\x18\x02 \x01(\x03\"\xb3\x01\n!AnthropicClaudeCodeModelBreakdown\x12\r\n\x05model\x18\x01 \x01(\t\x12\x37\n\x06tokens\x18\x02 \x01(\x0b\x32\'.viio.sync.v1.AnthropicClaudeCodeTokens\x12\x46\n\x0e\x65stimated_cost\x18\x03 \x01(\x0b\x32..viio.sync.v1.AnthropicClaudeCodeEstimatedCost\"f\n\x19\x41nthropicClaudeCodeTokens\x12\r\n\x05input\x18\x01 \x01(\x03\x12\x0e\n\x06output\x18\x02 \x01(\x03\x12\x12\n\ncache_read\x18\x03 \x01(\x03\x12\x16\n\x0e\x63\x61\x63he_creation\x18\x04 \x01(\x03\"D\n AnthropicClaudeCodeEstimatedCost\x12\x10\n\x08\x63urrency\x18\x01 \x01(\t\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x03\x42\x1c\xaa\x02\x19Viio.SyncApi.V1.Contractsb\x06proto3')
29
+
30
+ _globals = globals()
31
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
32
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'viio.sync.v1.anthropic_pb2', _globals)
33
+ if not _descriptor._USE_C_DESCRIPTORS:
34
+ _globals['DESCRIPTOR']._loaded_options = None
35
+ _globals['DESCRIPTOR']._serialized_options = b'\252\002\031Viio.SyncApi.V1.Contracts'
36
+ _globals['_ANTHROPICCHATUSAGE']._serialized_start=80
37
+ _globals['_ANTHROPICCHATUSAGE']._serialized_end=756
38
+ _globals['_ANTHROPICCACHECREATION']._serialized_start=758
39
+ _globals['_ANTHROPICCACHECREATION']._serialized_end=852
40
+ _globals['_ANTHROPICSERVERTOOLUSE']._serialized_start=854
41
+ _globals['_ANTHROPICSERVERTOOLUSE']._serialized_end=907
42
+ _globals['_ANTHROPICCOST']._serialized_start=910
43
+ _globals['_ANTHROPICCOST']._serialized_end=1387
44
+ _globals['_ANTHROPICCLAUDECODEUSAGE']._serialized_start=1390
45
+ _globals['_ANTHROPICCLAUDECODEUSAGE']._serialized_end=1920
46
+ _globals['_ANTHROPICCLAUDECODEACTOR']._serialized_start=1923
47
+ _globals['_ANTHROPICCLAUDECODEACTOR']._serialized_end=2053
48
+ _globals['_ANTHROPICCLAUDECODECOREMETRICS']._serialized_start=2056
49
+ _globals['_ANTHROPICCLAUDECODECOREMETRICS']._serialized_end=2249
50
+ _globals['_ANTHROPICCLAUDECODELINESOFCODE']._serialized_start=2251
51
+ _globals['_ANTHROPICCLAUDECODELINESOFCODE']._serialized_end=2315
52
+ _globals['_ANTHROPICCLAUDECODETOOLACTIONS']._serialized_start=2318
53
+ _globals['_ANTHROPICCLAUDECODETOOLACTIONS']._serialized_end=2622
54
+ _globals['_ANTHROPICCLAUDECODETOOLACTION']._serialized_start=2624
55
+ _globals['_ANTHROPICCLAUDECODETOOLACTION']._serialized_end=2691
56
+ _globals['_ANTHROPICCLAUDECODEMODELBREAKDOWN']._serialized_start=2694
57
+ _globals['_ANTHROPICCLAUDECODEMODELBREAKDOWN']._serialized_end=2873
58
+ _globals['_ANTHROPICCLAUDECODETOKENS']._serialized_start=2875
59
+ _globals['_ANTHROPICCLAUDECODETOKENS']._serialized_end=2977
60
+ _globals['_ANTHROPICCLAUDECODEESTIMATEDCOST']._serialized_start=2979
61
+ _globals['_ANTHROPICCLAUDECODEESTIMATEDCOST']._serialized_end=3047
62
+ # @@protoc_insertion_point(module_scope)