capture-runtime-client 0.4.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.
Files changed (48) hide show
  1. capture_runtime_client/__init__.py +41 -0
  2. capture_runtime_client/client.py +852 -0
  3. capture_runtime_client/codec.py +124 -0
  4. capture_runtime_client/contracts.py +272 -0
  5. capture_runtime_client/errors.py +170 -0
  6. capture_runtime_client/private/__init__.py +1 -0
  7. capture_runtime_client/private/assets/__init__.py +1 -0
  8. capture_runtime_client/private/assets/contract-set.json +1 -0
  9. capture_runtime_client/private/assets/contract-set.sha256 +1 -0
  10. capture_runtime_client/private/generated_models.py +627 -0
  11. capture_runtime_client/private/schemas/__init__.py +1 -0
  12. capture_runtime_client/private/schemas/capture-block.schema.json +151 -0
  13. capture_runtime_client/private/schemas/capture-document.schema.json +356 -0
  14. capture_runtime_client/private/schemas/capture-engine.schema.json +40 -0
  15. capture_runtime_client/private/schemas/capture-event-v2.schema.json +301 -0
  16. capture_runtime_client/private/schemas/capture-failure-v2.schema.json +40 -0
  17. capture_runtime_client/private/schemas/capture-operation-v2.schema.json +207 -0
  18. capture_runtime_client/private/schemas/capture-source.schema.json +34 -0
  19. capture_runtime_client/private/schemas/error-body-v2.schema.json +34 -0
  20. capture_runtime_client/private/schemas/error-envelope-v2.schema.json +49 -0
  21. capture_runtime_client/private/schemas/finalize-ingestion-v2.schema.json +27 -0
  22. capture_runtime_client/private/schemas/ingestion-v2.schema.json +127 -0
  23. capture_runtime_client/private/schemas/open-ingestion-v2.schema.json +79 -0
  24. capture_runtime_client/private/schemas/page-locator.schema.json +49 -0
  25. capture_runtime_client/private/schemas/partial-capture-v2.schema.json +267 -0
  26. capture_runtime_client/private/schemas/raw-capture-segment.schema.json +125 -0
  27. capture_runtime_client/private/schemas/raw-capture.schema.json +260 -0
  28. capture_runtime_client/private/schemas/report-structuring-failure-v2.schema.json +28 -0
  29. capture_runtime_client/private/schemas/runtime-artifact-descriptor-v2.schema.json +34 -0
  30. capture_runtime_client/private/schemas/runtime-installation-v2.schema.json +126 -0
  31. capture_runtime_client/private/schemas/runtime-installations-v2.schema.json +143 -0
  32. capture_runtime_client/private/schemas/runtime-model-installation-v2.schema.json +121 -0
  33. capture_runtime_client/private/schemas/runtime-model-installations-v2.schema.json +138 -0
  34. capture_runtime_client/private/schemas/runtime-model-option-v2.schema.json +80 -0
  35. capture_runtime_client/private/schemas/runtime-model-options-v2.schema.json +103 -0
  36. capture_runtime_client/private/schemas/runtime-ready.schema.json +88 -0
  37. capture_runtime_client/private/schemas/runtime-requirement-v2.schema.json +120 -0
  38. capture_runtime_client/private/schemas/runtime-requirements-v2.schema.json +137 -0
  39. capture_runtime_client/private/schemas/runtime-streaming-capabilities-v2.schema.json +64 -0
  40. capture_runtime_client/private/schemas/start-capture-v2.schema.json +59 -0
  41. capture_runtime_client/private/schemas/start-runtime-installation-v2.schema.json +26 -0
  42. capture_runtime_client/private/schemas/start-runtime-model-installation-v2.schema.json +21 -0
  43. capture_runtime_client/private/schemas/time-locator.schema.json +27 -0
  44. capture_runtime_client/py.typed +1 -0
  45. capture_runtime_client/transport.py +158 -0
  46. capture_runtime_client-0.4.0.dist-info/METADATA +18 -0
  47. capture_runtime_client-0.4.0.dist-info/RECORD +48 -0
  48. capture_runtime_client-0.4.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,627 @@
1
+ # Generated by packages/capture-runtime/scripts/generate_contracts.py. Do not edit.
2
+
3
+ """Generated host-side Pydantic models for the Capture Runtime v2 API."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from datetime import datetime
8
+ from enum import StrEnum
9
+ from typing import Annotated, Any, Final, Literal, Self
10
+
11
+ from pydantic import (
12
+ BaseModel,
13
+ ConfigDict,
14
+ Field,
15
+ StringConstraints,
16
+ field_validator,
17
+ model_validator,
18
+ )
19
+ from pydantic.alias_generators import to_camel
20
+
21
+ API_VERSION: Final = "2.0"
22
+ RUNTIME_VERSION: Final = "0.4.0"
23
+ CAPTURE_DOCUMENT_SCHEMA_VERSION: Final = "2"
24
+
25
+ NonEmptyString = Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)]
26
+ CaptureText = Annotated[
27
+ str, StringConstraints(strip_whitespace=True, min_length=1, max_length=2_000_000)
28
+ ]
29
+ ProjectedText = Annotated[
30
+ str, StringConstraints(strip_whitespace=True, min_length=1, max_length=8_000_000)
31
+ ]
32
+ WarningText = Annotated[str, StringConstraints(strip_whitespace=True, max_length=500)]
33
+ Sha256Hex = Annotated[str, StringConstraints(pattern=r"^[0-9a-f]{64}$")]
34
+ EngineDigest = Annotated[str, StringConstraints(pattern=r"^sha256:[0-9a-f]{64}$")]
35
+
36
+
37
+ class StrictModel(BaseModel):
38
+ """Reject unexpected wire fields and expose camelCase JSON aliases."""
39
+
40
+ model_config = ConfigDict(
41
+ alias_generator=to_camel,
42
+ extra="forbid",
43
+ populate_by_name=True,
44
+ str_strip_whitespace=True,
45
+ )
46
+
47
+
48
+ def _require_aware(value: datetime) -> datetime:
49
+ if value.tzinfo is None or value.utcoffset() is None:
50
+ raise ValueError("timestamp must include a timezone")
51
+ return value
52
+
53
+
54
+ class CaptureSourceKind(StrEnum):
55
+ PDF = "pdf"
56
+ IMAGE = "image"
57
+ AUDIO = "audio"
58
+
59
+
60
+ class StructuringMode(StrEnum):
61
+ RUNTIME = "runtime"
62
+ HOST = "host"
63
+
64
+
65
+ class RuntimeInstallationStatus(StrEnum):
66
+ QUEUED = "queued"
67
+ RUNNING = "running"
68
+ COMPLETED = "completed"
69
+ FAILED = "failed"
70
+ CANCELLED = "cancelled"
71
+ MANUAL_ACTION_REQUIRED = "manual_action_required"
72
+
73
+
74
+ class RuntimeModelOptionStatus(StrEnum):
75
+ NOT_INSTALLED = "not-installed"
76
+ INSTALLED = "installed"
77
+ ACTIVE = "active"
78
+
79
+
80
+ class RuntimeRequirementStatus(StrEnum):
81
+ READY = "ready"
82
+ MISSING = "missing"
83
+ INSTALLABLE = "installable"
84
+ MANUAL_ACTION_REQUIRED = "manual_action_required"
85
+ UNAVAILABLE = "unavailable"
86
+
87
+
88
+ CaptureRequirementId = Literal[
89
+ "windowsml-ocr",
90
+ "whisper-primary",
91
+ "ollama-runtime",
92
+ "capture-ollama-model",
93
+ ]
94
+
95
+
96
+ class PageLocator(StrictModel):
97
+ kind: Literal["page"] = "page"
98
+ page: int = Field(ge=1)
99
+ bounding_box: tuple[float, float, float, float] | None = None
100
+
101
+
102
+ class TimeLocator(StrictModel):
103
+ kind: Literal["time"] = "time"
104
+ start_ms: int = Field(ge=0)
105
+ end_ms: int = Field(gt=0)
106
+
107
+ @model_validator(mode="after")
108
+ def validate_interval(self) -> Self:
109
+ if self.end_ms <= self.start_ms:
110
+ raise ValueError("endMs must be greater than startMs")
111
+ return self
112
+
113
+
114
+ CaptureLocator = Annotated[PageLocator | TimeLocator, Field(discriminator="kind")]
115
+
116
+
117
+ class CaptureSource(StrictModel):
118
+ sha256: Sha256Hex
119
+ file_name: Annotated[
120
+ str, StringConstraints(strip_whitespace=True, min_length=1, max_length=255)
121
+ ]
122
+ media_type: NonEmptyString
123
+ bytes: int = Field(ge=1)
124
+
125
+
126
+ class CaptureEngine(StrictModel):
127
+ engine: NonEmptyString
128
+ model: NonEmptyString
129
+ digest: EngineDigest
130
+ device: NonEmptyString | None = None
131
+
132
+
133
+ class RawCaptureSegment(StrictModel):
134
+ segment_id: NonEmptyString
135
+ order: int = Field(ge=0)
136
+ locator: CaptureLocator
137
+ text: CaptureText
138
+
139
+
140
+ def project_source_text(segments: list[RawCaptureSegment]) -> str:
141
+ return "\n".join(segment.text for segment in segments)
142
+
143
+
144
+ class RawCapture(StrictModel):
145
+ schema_version: Literal["2"] = CAPTURE_DOCUMENT_SCHEMA_VERSION
146
+ diagnostic_only: Literal[True] = True
147
+ source: CaptureSource
148
+ segments: list[RawCaptureSegment] = Field(min_length=1, max_length=10_000)
149
+ source_text: ProjectedText
150
+ extraction_engine: CaptureEngine
151
+ warnings: list[WarningText] = Field(default_factory=list, max_length=1_000)
152
+ created_at: datetime
153
+
154
+ _aware_created_at = field_validator("created_at")(_require_aware)
155
+
156
+ @model_validator(mode="after")
157
+ def validate_projection(self) -> Self:
158
+ expected_orders = list(range(len(self.segments)))
159
+ if [segment.order for segment in self.segments] != expected_orders:
160
+ raise ValueError("raw segment order must be contiguous and match list order")
161
+ if self.source_text != project_source_text(self.segments):
162
+ raise ValueError("sourceText must be the exact raw segment projection")
163
+ identifiers = [segment.segment_id for segment in self.segments]
164
+ if len(identifiers) != len(set(identifiers)):
165
+ raise ValueError("raw segmentId values must be unique")
166
+ return self
167
+
168
+
169
+ class CaptureBlock(StrictModel):
170
+ block_id: NonEmptyString
171
+ order: int = Field(ge=0)
172
+ type: Literal["heading", "paragraph", "list-item", "table", "quote", "transcript"]
173
+ source_segment_id: NonEmptyString
174
+ locator: CaptureLocator
175
+ source_text: CaptureText
176
+ target_text: CaptureText
177
+
178
+
179
+ class CaptureDocument(StrictModel):
180
+ schema_version: Literal["2"] = CAPTURE_DOCUMENT_SCHEMA_VERSION
181
+ source: CaptureSource
182
+ raw_segments: list[RawCaptureSegment] = Field(min_length=1, max_length=10_000)
183
+ blocks: list[CaptureBlock] = Field(min_length=1, max_length=10_000)
184
+ source_text: ProjectedText
185
+ target_text: ProjectedText
186
+ extraction_engine: CaptureEngine
187
+ structuring_engine: CaptureEngine
188
+ warnings: list[WarningText] = Field(default_factory=list, max_length=1_000)
189
+ created_at: datetime
190
+ completed_at: datetime
191
+
192
+ _aware_created_at = field_validator("created_at")(_require_aware)
193
+ _aware_completed_at = field_validator("completed_at")(_require_aware)
194
+
195
+ @model_validator(mode="after")
196
+ def validate_document(self) -> Self:
197
+ if self.completed_at < self.created_at:
198
+ raise ValueError("completedAt must not precede createdAt")
199
+ if [segment.order for segment in self.raw_segments] != list(range(len(self.raw_segments))):
200
+ raise ValueError("raw segment order must be contiguous and match list order")
201
+ if [block.order for block in self.blocks] != list(range(len(self.blocks))):
202
+ raise ValueError("block order must be contiguous and match list order")
203
+ segment_ids = [segment.segment_id for segment in self.raw_segments]
204
+ if len(segment_ids) != len(set(segment_ids)):
205
+ raise ValueError("raw segmentId values must be unique")
206
+ block_ids = [block.block_id for block in self.blocks]
207
+ if len(block_ids) != len(set(block_ids)):
208
+ raise ValueError("blockId values must be unique")
209
+ segments_by_id = {segment.segment_id: segment for segment in self.raw_segments}
210
+ if len(self.blocks) != len(self.raw_segments):
211
+ raise ValueError("blocks must cover every raw segment exactly once")
212
+ for index, block in enumerate(self.blocks):
213
+ expected_segment = self.raw_segments[index]
214
+ segment = segments_by_id.get(block.source_segment_id)
215
+ if segment is None:
216
+ raise ValueError("every block must reference a raw source segment")
217
+ if block.source_segment_id != expected_segment.segment_id:
218
+ raise ValueError("block sequence must follow raw segment order")
219
+ if block.locator != segment.locator:
220
+ raise ValueError("block locator must equal its raw source segment locator")
221
+ if block.source_text != segment.text:
222
+ raise ValueError("block sourceText must equal its raw source segment text")
223
+ if self.source_text != project_source_text(self.raw_segments):
224
+ raise ValueError("sourceText must be the exact raw segment projection")
225
+ expected_target = "\n".join(block.target_text for block in self.blocks)
226
+ if self.target_text != expected_target:
227
+ raise ValueError("targetText must be the exact block target projection")
228
+ return self
229
+
230
+
231
+ class RuntimeArtifactDescriptorV2(StrictModel):
232
+ artifact_url: NonEmptyString
233
+ artifact_file_name: NonEmptyString
234
+ bytes: int = Field(ge=1, le=536_870_912)
235
+ sha256: Sha256Hex
236
+
237
+
238
+ class RuntimeRequirementV2(StrictModel):
239
+ requirement_id: CaptureRequirementId
240
+ kind: NonEmptyString
241
+ display_name: NonEmptyString
242
+ status: RuntimeRequirementStatus
243
+ required_for: list[str]
244
+ install_strategy: NonEmptyString
245
+ detail: str | None = None
246
+ artifact: RuntimeArtifactDescriptorV2 | None = None
247
+
248
+
249
+ class RuntimeRequirementsV2(StrictModel):
250
+ items: list[RuntimeRequirementV2]
251
+
252
+
253
+ class StartRuntimeInstallationV2(StrictModel):
254
+ requirement_id: CaptureRequirementId
255
+ consent: Literal[True]
256
+
257
+
258
+ class RuntimeModelOptionV2(StrictModel):
259
+ option_id: NonEmptyString
260
+ display_name: NonEmptyString
261
+ model_reference: NonEmptyString
262
+ expected_digest: Sha256Hex | None = None
263
+ expected_bytes: int | None = Field(default=None, ge=1)
264
+ profile_id: NonEmptyString
265
+ profile_spec_sha256: Sha256Hex
266
+ status: RuntimeModelOptionStatus
267
+
268
+
269
+ class RuntimeModelOptionsV2(StrictModel):
270
+ catalog_sha256: Sha256Hex
271
+ items: list[RuntimeModelOptionV2]
272
+
273
+
274
+ class StartRuntimeModelInstallationV2(StrictModel):
275
+ option_id: NonEmptyString
276
+ consent: Literal[True]
277
+
278
+
279
+ class RuntimeModelInstallationV2(StrictModel):
280
+ installation_id: str
281
+ option_id: NonEmptyString
282
+ status: RuntimeInstallationStatus
283
+ progress: float = Field(ge=0, le=1)
284
+ error: CaptureFailureV2 | None = None
285
+ created_at: datetime
286
+ updated_at: datetime
287
+ completed_at: datetime | None = None
288
+
289
+ _aware_times = field_validator("created_at", "updated_at", "completed_at")(
290
+ lambda value: None if value is None else _require_aware(value)
291
+ )
292
+
293
+
294
+ class RuntimeModelInstallationsV2(StrictModel):
295
+ items: list[RuntimeModelInstallationV2]
296
+
297
+
298
+ class RuntimeInstallationV2(StrictModel):
299
+ installation_id: str
300
+ requirement_id: CaptureRequirementId
301
+ status: RuntimeInstallationStatus
302
+ progress: float = Field(ge=0, le=1)
303
+ error: CaptureFailureV2 | None = None
304
+ created_at: datetime
305
+ updated_at: datetime
306
+ completed_at: datetime | None = None
307
+
308
+ _aware_times = field_validator("created_at", "updated_at", "completed_at")(
309
+ lambda value: None if value is None else _require_aware(value)
310
+ )
311
+
312
+
313
+ class RuntimeInstallationsV2(StrictModel):
314
+ items: list[RuntimeInstallationV2]
315
+
316
+
317
+ class StreamingIngestionMode(StrEnum):
318
+ FILE = "file"
319
+
320
+
321
+ class StreamingIngestionStatus(StrEnum):
322
+ OPEN = "open"
323
+ FINALIZING = "finalizing"
324
+ READY = "ready"
325
+ CANCELLED = "cancelled"
326
+ FAILED = "failed"
327
+ EXPIRED = "expired"
328
+
329
+
330
+ class StreamingCaptureStatus(StrEnum):
331
+ CREATED = "created"
332
+ WAITING_INPUT = "waiting_input"
333
+ EXTRACTING = "extracting"
334
+ AWAITING_STRUCTURING = "awaiting_structuring"
335
+ STRUCTURING = "structuring"
336
+ COMPLETED = "completed"
337
+ FAILED = "failed"
338
+ CANCELLED = "cancelled"
339
+
340
+
341
+ class StreamingEventType(StrEnum):
342
+ ACCEPTED = "accepted"
343
+ INPUT_CHECKPOINT = "input_checkpoint"
344
+ HEARTBEAT = "heartbeat"
345
+ SEGMENT = "segment"
346
+ CHECKPOINT = "checkpoint"
347
+ RESYNC_REQUIRED = "resync_required"
348
+ COMPLETED = "completed"
349
+ FAILED = "failed"
350
+ CANCELLED = "cancelled"
351
+
352
+
353
+ class OpenIngestionV2(StrictModel):
354
+ protocol_version: Literal["2"] = "2"
355
+ kind: CaptureSourceKind = CaptureSourceKind.AUDIO
356
+ mode: StreamingIngestionMode = StreamingIngestionMode.FILE
357
+ client_request_id: NonEmptyString
358
+ file_name: Annotated[
359
+ str, StringConstraints(strip_whitespace=True, min_length=1, max_length=255)
360
+ ]
361
+ media_type: NonEmptyString
362
+ total_bytes: int = Field(gt=0)
363
+ source_sha256: Sha256Hex | None = None
364
+
365
+
366
+ class IngestionV2(StrictModel):
367
+ protocol_version: Literal["2"] = "2"
368
+ kind: CaptureSourceKind = CaptureSourceKind.AUDIO
369
+ ingestion_id: NonEmptyString
370
+ status: StreamingIngestionStatus
371
+ file_name: Annotated[
372
+ str, StringConstraints(strip_whitespace=True, min_length=1, max_length=255)
373
+ ]
374
+ media_type: NonEmptyString
375
+ total_bytes: int = Field(gt=0)
376
+ received_bytes: int = Field(ge=0)
377
+ contiguous_bytes: int = Field(ge=0)
378
+ next_chunk_index: int = Field(ge=0)
379
+ next_offset: int = Field(ge=0)
380
+ source_sha256: Sha256Hex | None = None
381
+ finalized_sha256: Sha256Hex | None = None
382
+ expires_at: datetime
383
+
384
+ _aware_expires_at = field_validator("expires_at")(_require_aware)
385
+
386
+ @model_validator(mode="after")
387
+ def validate_progress(self) -> Self:
388
+ if self.received_bytes > self.total_bytes:
389
+ raise ValueError("receivedBytes must not exceed totalBytes")
390
+ if self.contiguous_bytes > self.received_bytes:
391
+ raise ValueError("contiguousBytes must not exceed receivedBytes")
392
+ if self.next_offset != self.contiguous_bytes:
393
+ raise ValueError("nextOffset must equal contiguousBytes")
394
+ if self.status is StreamingIngestionStatus.READY:
395
+ if self.received_bytes != self.total_bytes or self.finalized_sha256 is None:
396
+ raise ValueError("ready ingestion must be complete and checksummed")
397
+ return self
398
+
399
+
400
+ class StartCaptureV2(StrictModel):
401
+ protocol_version: Literal["2"] = "2"
402
+ client_request_id: NonEmptyString
403
+ ingestion_id: NonEmptyString
404
+ structuring_mode: StructuringMode
405
+ target_language: str | None = None
406
+ start_policy: Literal["eager"] = "eager"
407
+
408
+ @field_validator("target_language")
409
+ @classmethod
410
+ def validate_target_language(cls, value: str | None) -> str | None:
411
+ if value is not None and not 1 <= len(value) <= 64:
412
+ raise ValueError("targetLanguage must be 1 to 64 characters")
413
+ return value
414
+
415
+
416
+ class FinalizeIngestionV2(StrictModel):
417
+ protocol_version: Literal["2"] = "2"
418
+ total_bytes: int = Field(gt=0)
419
+ sha256: Sha256Hex
420
+
421
+
422
+ class CaptureFailureV2(StrictModel):
423
+ code: Annotated[str, StringConstraints(pattern=r"^[a-z][a-z0-9_]{1,63}$")]
424
+ message: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1, max_length=500)]
425
+ stage: NonEmptyString | None = None
426
+ retryable: bool = False
427
+
428
+
429
+ class ReportStructuringFailureV2(StrictModel):
430
+ protocol_version: Literal["2"] = "2"
431
+ code: Annotated[str, StringConstraints(pattern=r"^[a-z][a-z0-9_]{1,63}$")]
432
+ message: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1, max_length=500)]
433
+
434
+
435
+ class CaptureOperationV2(StrictModel):
436
+ protocol_version: Literal["2"] = "2"
437
+ capture_id: NonEmptyString
438
+ ingestion_id: NonEmptyString
439
+ kind: CaptureSourceKind = CaptureSourceKind.AUDIO
440
+ status: StreamingCaptureStatus
441
+ progress: float | None = Field(default=None, ge=0, le=1)
442
+ partial_revision: int = Field(ge=0)
443
+ last_event_sequence: int = Field(ge=0)
444
+ source: CaptureSource | None = None
445
+ error: CaptureFailureV2 | None = None
446
+ created_at: datetime
447
+ updated_at: datetime
448
+ completed_at: datetime | None = None
449
+
450
+ _aware_times = field_validator("created_at", "updated_at", "completed_at")(
451
+ lambda value: None if value is None else _require_aware(value)
452
+ )
453
+
454
+ @model_validator(mode="after")
455
+ def validate_state(self) -> Self:
456
+ terminal = {
457
+ StreamingCaptureStatus.COMPLETED,
458
+ StreamingCaptureStatus.FAILED,
459
+ StreamingCaptureStatus.CANCELLED,
460
+ }
461
+ if (self.status in terminal) != (self.completed_at is not None):
462
+ raise ValueError("terminal streaming captures must have completedAt")
463
+ return self
464
+
465
+
466
+ class CaptureStreamingResult(StrictModel):
467
+ operation: CaptureOperationV2
468
+ raw: RawCapture
469
+ result: CaptureDocument
470
+
471
+ @model_validator(mode="after")
472
+ def validate_source_identity(self) -> Self:
473
+ if self.raw.source != self.result.source:
474
+ raise ValueError("raw and structured result source identity must match")
475
+ if self.operation.source is not None and self.operation.source != self.raw.source:
476
+ raise ValueError("operation and result source identity must match")
477
+ return self
478
+
479
+
480
+ class PartialCaptureV2(StrictModel):
481
+ protocol_version: Literal["2"] = "2"
482
+ capture_id: NonEmptyString
483
+ source: CaptureSource
484
+ revision: int = Field(ge=0)
485
+ covered_until_ms: int = Field(ge=0)
486
+ segments: list[RawCaptureSegment] = Field(default_factory=list, max_length=10_000)
487
+ source_text: Annotated[str, StringConstraints(max_length=8_000_000)] = ""
488
+ extraction_engine: CaptureEngine | None = None
489
+ updated_at: datetime
490
+
491
+ _aware_updated_at = field_validator("updated_at")(_require_aware)
492
+
493
+ @model_validator(mode="after")
494
+ def validate_projection(self) -> Self:
495
+ if [segment.order for segment in self.segments] != list(range(len(self.segments))):
496
+ raise ValueError("partial segment order must be contiguous")
497
+ expected_text = project_source_text(self.segments) if self.segments else ""
498
+ if self.source_text != expected_text:
499
+ raise ValueError("sourceText must be the exact partial segment projection")
500
+ return self
501
+
502
+
503
+ class CaptureEventV2(StrictModel):
504
+ protocol_version: Literal["2"] = "2"
505
+ event_id: NonEmptyString
506
+ sequence: int = Field(ge=0)
507
+ capture_id: NonEmptyString
508
+ kind: CaptureSourceKind = CaptureSourceKind.AUDIO
509
+ event_type: StreamingEventType
510
+ stage: NonEmptyString
511
+ progress: float | None = Field(default=None, ge=0, le=1)
512
+ partial_revision: int | None = Field(default=None, ge=0)
513
+ covered_until_ms: int | None = Field(default=None, ge=0)
514
+ segments: list[RawCaptureSegment] = Field(default_factory=list, max_length=1_000)
515
+ error: CaptureFailureV2 | None = None
516
+ created_at: datetime
517
+
518
+ _aware_created_at = field_validator("created_at")(_require_aware)
519
+
520
+ @model_validator(mode="after")
521
+ def validate_event_payload(self) -> Self:
522
+ if self.event_type is StreamingEventType.SEGMENT and not self.segments:
523
+ raise ValueError("segment events must contain at least one segment")
524
+ if self.event_type is StreamingEventType.FAILED and self.error is None:
525
+ raise ValueError("failed events must contain an error")
526
+ if self.event_type is not StreamingEventType.FAILED and self.error is not None:
527
+ raise ValueError("only failed events may contain an error")
528
+ return self
529
+
530
+
531
+ class RuntimeStreamingCapabilitiesV2(StrictModel):
532
+ protocol_version: Literal["2"] = "2"
533
+ capture_kinds: list[CaptureSourceKind] = Field(
534
+ default_factory=lambda: [
535
+ CaptureSourceKind.PDF,
536
+ CaptureSourceKind.IMAGE,
537
+ CaptureSourceKind.AUDIO,
538
+ ],
539
+ min_length=1,
540
+ )
541
+ supports_progressive_audio: bool = True
542
+ max_chunk_bytes: int = Field(gt=0, le=4 * 1024 * 1024)
543
+ checkpoint_interval_ms: int = Field(gt=0)
544
+ heartbeat_interval_ms: int = Field(gt=0)
545
+ stall_timeout_ms: int = Field(gt=0)
546
+
547
+
548
+ class RuntimeReady(StrictModel):
549
+ """General v2 readiness payload shared by runtime clients."""
550
+
551
+ ready: bool
552
+ service: Literal["capture-runtime"] = "capture-runtime"
553
+ api_version: Literal["2.0"] = API_VERSION
554
+ runtime_version: Literal["0.4.0"] = RUNTIME_VERSION
555
+ capture_document_schema_version: Literal["2"] = CAPTURE_DOCUMENT_SCHEMA_VERSION
556
+ capture_document_schema_sha256: Sha256Hex | None = None
557
+ schema_sha256: Sha256Hex | None = None
558
+ contract_set_version: Literal["2"] = "2"
559
+ capabilities: dict[str, Any] = Field(default_factory=dict)
560
+ message: str | None = None
561
+
562
+
563
+ class ErrorBodyV2(StrictModel):
564
+ code: NonEmptyString
565
+ message: NonEmptyString
566
+ details: dict[str, Any] | None = None
567
+
568
+
569
+ class ErrorEnvelopeV2(StrictModel):
570
+ error: ErrorBodyV2
571
+
572
+
573
+ # Generated public names. Do not edit.
574
+ __all__ = [
575
+ 'StrictModel',
576
+ 'NonEmptyString',
577
+ 'CaptureText',
578
+ 'ProjectedText',
579
+ 'WarningText',
580
+ 'Sha256Hex',
581
+ 'EngineDigest',
582
+ 'CaptureLocator',
583
+ 'CaptureRequirementId',
584
+ 'project_source_text',
585
+ 'PageLocator',
586
+ 'TimeLocator',
587
+ 'CaptureSource',
588
+ 'CaptureEngine',
589
+ 'RawCaptureSegment',
590
+ 'RawCapture',
591
+ 'CaptureBlock',
592
+ 'CaptureDocument',
593
+ 'RuntimeArtifactDescriptorV2',
594
+ 'RuntimeRequirementV2',
595
+ 'RuntimeRequirementsV2',
596
+ 'StartRuntimeInstallationV2',
597
+ 'RuntimeModelOptionV2',
598
+ 'RuntimeModelOptionsV2',
599
+ 'StartRuntimeModelInstallationV2',
600
+ 'RuntimeModelInstallationV2',
601
+ 'RuntimeModelInstallationsV2',
602
+ 'RuntimeInstallationV2',
603
+ 'RuntimeInstallationsV2',
604
+ 'OpenIngestionV2',
605
+ 'IngestionV2',
606
+ 'StartCaptureV2',
607
+ 'FinalizeIngestionV2',
608
+ 'CaptureFailureV2',
609
+ 'ReportStructuringFailureV2',
610
+ 'CaptureOperationV2',
611
+ 'CaptureStreamingResult',
612
+ 'PartialCaptureV2',
613
+ 'CaptureEventV2',
614
+ 'RuntimeStreamingCapabilitiesV2',
615
+ 'RuntimeReady',
616
+ 'ErrorBodyV2',
617
+ 'ErrorEnvelopeV2',
618
+ 'CaptureSourceKind',
619
+ 'StructuringMode',
620
+ 'RuntimeInstallationStatus',
621
+ 'RuntimeModelOptionStatus',
622
+ 'RuntimeRequirementStatus',
623
+ 'StreamingIngestionMode',
624
+ 'StreamingIngestionStatus',
625
+ 'StreamingCaptureStatus',
626
+ 'StreamingEventType',
627
+ ]
@@ -0,0 +1 @@
1
+ """Private generated schemas used by strict SDK validation."""