pinecall-runtime 0.1.6__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 (328) hide show
  1. pinecall/__init__.py +0 -0
  2. pinecall/channels/__init__.py +0 -0
  3. pinecall/channels/_chooser.py +45 -0
  4. pinecall/channels/offers.py +219 -0
  5. pinecall/channels/rooms.py +176 -0
  6. pinecall/channels/routes.py +275 -0
  7. pinecall/channels/telephony/__init__.py +0 -0
  8. pinecall/channels/telephony/_twilio.py +349 -0
  9. pinecall/channels/telephony/carrier.py +229 -0
  10. pinecall/channels/telephony/carrier_catalog.py +92 -0
  11. pinecall/channels/telephony/carriers.csv +41 -0
  12. pinecall/channels/telephony/dialing.py +419 -0
  13. pinecall/channels/telephony/firewall.py +58 -0
  14. pinecall/channels/telephony/hand_over.py +78 -0
  15. pinecall/channels/telephony/number_path.py +176 -0
  16. pinecall/channels/telephony/numbers.py +530 -0
  17. pinecall/channels/telephony/origination.py +77 -0
  18. pinecall/channels/telephony/sip.py +448 -0
  19. pinecall/channels/whatsapp.py +413 -0
  20. pinecall/cli/__init__.py +0 -0
  21. pinecall/cli/_archive.py +85 -0
  22. pinecall/cli/_drift.py +38 -0
  23. pinecall/cli/_facts.py +67 -0
  24. pinecall/cli/_load.py +433 -0
  25. pinecall/cli/_operator.py +626 -0
  26. pinecall/cli/_sessions.py +164 -0
  27. pinecall/cli/_traceback.py +95 -0
  28. pinecall/cli/_usage.py +34 -0
  29. pinecall/cli/main.py +521 -0
  30. pinecall/domain/__init__.py +0 -0
  31. pinecall/domain/agent.py +446 -0
  32. pinecall/domain/call.py +152 -0
  33. pinecall/domain/country_codes.py +32 -0
  34. pinecall/domain/errors.py +151 -0
  35. pinecall/domain/names.py +115 -0
  36. pinecall/domain/org.py +120 -0
  37. pinecall/domain/person.py +208 -0
  38. pinecall/domain/scope.py +21 -0
  39. pinecall/evals/__init__.py +0 -0
  40. pinecall/evals/_evidence.py +158 -0
  41. pinecall/evals/calibration.py +112 -0
  42. pinecall/evals/callers.py +160 -0
  43. pinecall/evals/case.py +327 -0
  44. pinecall/evals/checks.py +437 -0
  45. pinecall/evals/compliance.py +197 -0
  46. pinecall/evals/dataset.py +451 -0
  47. pinecall/evals/goldens.py +331 -0
  48. pinecall/evals/judges.py +525 -0
  49. pinecall/evals/runs.py +252 -0
  50. pinecall/evals/spoken.py +356 -0
  51. pinecall/fleet/__init__.py +0 -0
  52. pinecall/fleet/client.py +569 -0
  53. pinecall/fleet/demand.py +51 -0
  54. pinecall/fleet/heartbeat.py +194 -0
  55. pinecall/fleet/measures.py +157 -0
  56. pinecall/fleet/roster.py +232 -0
  57. pinecall/fleet/worlds.py +44 -0
  58. pinecall/gateway/__init__.py +0 -0
  59. pinecall/gateway/_call_setup.py +53 -0
  60. pinecall/gateway/_deps.py +556 -0
  61. pinecall/gateway/_gateway.py +75 -0
  62. pinecall/gateway/_served.py +396 -0
  63. pinecall/gateway/_sockets.py +444 -0
  64. pinecall/gateway/_streams.py +83 -0
  65. pinecall/gateway/_text_calls.py +182 -0
  66. pinecall/gateway/api/__init__.py +0 -0
  67. pinecall/gateway/api/accounts.py +324 -0
  68. pinecall/gateway/api/agents.py +150 -0
  69. pinecall/gateway/api/apps.py +478 -0
  70. pinecall/gateway/api/box.py +658 -0
  71. pinecall/gateway/api/calibration.py +58 -0
  72. pinecall/gateway/api/callbacks.py +105 -0
  73. pinecall/gateway/api/calls.py +655 -0
  74. pinecall/gateway/api/chat.py +223 -0
  75. pinecall/gateway/api/dataset.py +163 -0
  76. pinecall/gateway/api/desk.py +113 -0
  77. pinecall/gateway/api/entries.py +77 -0
  78. pinecall/gateway/api/evals.py +647 -0
  79. pinecall/gateway/api/fleet.py +73 -0
  80. pinecall/gateway/api/hosting.py +241 -0
  81. pinecall/gateway/api/judges.py +97 -0
  82. pinecall/gateway/api/keys.py +110 -0
  83. pinecall/gateway/api/line.py +142 -0
  84. pinecall/gateway/api/members.py +197 -0
  85. pinecall/gateway/api/metrics.py +226 -0
  86. pinecall/gateway/api/numbers.py +476 -0
  87. pinecall/gateway/api/ops.py +428 -0
  88. pinecall/gateway/api/org.py +283 -0
  89. pinecall/gateway/api/personas.py +157 -0
  90. pinecall/gateway/api/pipeline.py +206 -0
  91. pinecall/gateway/api/prompts.py +50 -0
  92. pinecall/gateway/api/providers.py +240 -0
  93. pinecall/gateway/api/recordings.py +89 -0
  94. pinecall/gateway/api/relay.py +169 -0
  95. pinecall/gateway/api/retrieval.py +403 -0
  96. pinecall/gateway/api/runner.py +109 -0
  97. pinecall/gateway/api/sessions.py +102 -0
  98. pinecall/gateway/api/settings.py +463 -0
  99. pinecall/gateway/api/signup.py +164 -0
  100. pinecall/gateway/api/sso_login.py +168 -0
  101. pinecall/gateway/api/threads.py +187 -0
  102. pinecall/gateway/api/usage.py +303 -0
  103. pinecall/gateway/api/visitors.py +222 -0
  104. pinecall/gateway/api/whatsapp.py +48 -0
  105. pinecall/gateway/api/widget.py +49 -0
  106. pinecall/gateway/app.py +504 -0
  107. pinecall/gateway/calls/__init__.py +0 -0
  108. pinecall/gateway/calls/binding.py +63 -0
  109. pinecall/gateway/calls/commands.py +18 -0
  110. pinecall/gateway/calls/inbox.py +100 -0
  111. pinecall/gateway/calls/known.py +23 -0
  112. pinecall/gateway/calls/owners.py +87 -0
  113. pinecall/gateway/calls/pump.py +33 -0
  114. pinecall/gateway/calls/serving.py +118 -0
  115. pinecall/gateway/calls/threads.py +367 -0
  116. pinecall/gateway/dispatching/__init__.py +0 -0
  117. pinecall/gateway/dispatching/arrivals.py +58 -0
  118. pinecall/gateway/dispatching/sweep.py +43 -0
  119. pinecall/gateway/ending/__init__.py +0 -0
  120. pinecall/gateway/ending/reaper.py +160 -0
  121. pinecall/gateway/ending/seal.py +312 -0
  122. pinecall/gateway/ending/stranded.py +117 -0
  123. pinecall/log/__init__.py +0 -0
  124. pinecall/log/_histogram.py +74 -0
  125. pinecall/log/_relay.py +259 -0
  126. pinecall/log/_writer.py +454 -0
  127. pinecall/log/days.py +154 -0
  128. pinecall/log/drift.py +550 -0
  129. pinecall/log/facts.py +358 -0
  130. pinecall/log/inbox.py +169 -0
  131. pinecall/log/lists.py +155 -0
  132. pinecall/log/logs.py +581 -0
  133. pinecall/log/openings.py +71 -0
  134. pinecall/log/private.py +135 -0
  135. pinecall/log/queries.py +229 -0
  136. pinecall/log/readers.py +207 -0
  137. pinecall/log/reduce.py +659 -0
  138. pinecall/log/refold.py +217 -0
  139. pinecall/log/store.py +370 -0
  140. pinecall/log/usage.py +242 -0
  141. pinecall/postgres/__init__.py +0 -0
  142. pinecall/postgres/migrate.py +202 -0
  143. pinecall/postgres/migrations/0001_schema.sql +591 -0
  144. pinecall/postgres/migrations/0002_worlds.sql +15 -0
  145. pinecall/postgres/migrations/0003_lent.sql +4 -0
  146. pinecall/postgres/migrations/0004_carriers.sql +23 -0
  147. pinecall/postgres/migrations/0005_retrieved_only.sql +11 -0
  148. pinecall/postgres/migrations/0006_persona_agents.sql +9 -0
  149. pinecall/postgres/migrations/0007_money_in_dollars.sql +27 -0
  150. pinecall/postgres/migrations/0008_lexicon_per_agent.sql +27 -0
  151. pinecall/postgres/migrations/0009_personas_per_agent.sql +31 -0
  152. pinecall/postgres/migrations/0010_agent_judges.sql +12 -0
  153. pinecall/postgres/migrations/0011_erasure.sql +32 -0
  154. pinecall/postgres/migrations/0012_org_policy.sql +10 -0
  155. pinecall/postgres/migrations/0013_calling_rules.sql +12 -0
  156. pinecall/postgres/migrations/0014_consents.sql +24 -0
  157. pinecall/postgres/migrations/0015_disclosure.sql +7 -0
  158. pinecall/postgres/migrations/0016_call_records.sql +21 -0
  159. pinecall/postgres/migrations/0017_reads.sql +17 -0
  160. pinecall/postgres/migrations/0018_hosting.sql +41 -0
  161. pinecall/postgres/migrations/0019_hosted_state.sql +10 -0
  162. pinecall/postgres/migrations/0020_written_once.sql +7 -0
  163. pinecall/postgres/migrations/0021_hosted_running.sql +21 -0
  164. pinecall/postgres/migrations/0022_sealed.sql +39 -0
  165. pinecall/postgres/migrations/0023_usage_totals.sql +158 -0
  166. pinecall/postgres/migrations/0024_log_key_by_day.sql +8 -0
  167. pinecall/postgres/migrations/0025_log_before_the_days.sql +15 -0
  168. pinecall/postgres/migrations/0026_log_before_the_days_validated.sql +4 -0
  169. pinecall/postgres/migrations/0027_log_by_day.sql +96 -0
  170. pinecall/postgres/migrations/0030_whatsapp_seen.sql +17 -0
  171. pinecall/postgres/migrations/0040_reads_widened.sql +15 -0
  172. pinecall/postgres/migrations/0060_drift.sql +62 -0
  173. pinecall/postgres/migrations/0061_canaries.sql +24 -0
  174. pinecall/postgres/migrations/0062_eval_cases.sql +23 -0
  175. pinecall/postgres/migrations/0063_prompts.sql +16 -0
  176. pinecall/postgres/migrations/0075_call_private.sql +14 -0
  177. pinecall/postgres/migrations/0076_recording_keys.sql +13 -0
  178. pinecall/postgres/migrations/0077_tokens_world.sql +7 -0
  179. pinecall/postgres/migrations/0078_facts_cost_by_stage.sql +12 -0
  180. pinecall/postgres/migrations/0079_judge_labels.sql +20 -0
  181. pinecall/postgres/migrations/0080_call_openings.sql +20 -0
  182. pinecall/postgres/migrations/0081_seal_lease.sql +7 -0
  183. pinecall/postgres/migrations/0082_caller_codes.sql +14 -0
  184. pinecall/postgres/migrations/0083_one_use_words.sql +15 -0
  185. pinecall/postgres/migrations/0084_knocks_and_run_leases.sql +23 -0
  186. pinecall/postgres/migrations/0085_seal_lease_holder.sql +8 -0
  187. pinecall/postgres/migrations/0086_route_origin.sql +17 -0
  188. pinecall/postgres/migrations/0087_carrier_networks.sql +20 -0
  189. pinecall/postgres/migrations/0088_route_last_call.sql +3 -0
  190. pinecall/postgres/migrations/0089_offers.sql +21 -0
  191. pinecall/postgres/migrations/0090_invitation_purpose.sql +5 -0
  192. pinecall/postgres/migrations/0091_head_opened_by.sql +6 -0
  193. pinecall/postgres/migrations/0092_key_parent.sql +6 -0
  194. pinecall/postgres/migrations/0093_code_tries.sql +10 -0
  195. pinecall/postgres/migrations/0094_route_approval.sql +12 -0
  196. pinecall/postgres/migrations/0095_sso_domain_proofs.sql +17 -0
  197. pinecall/postgres/migrations/0096_row_level_security.sql +38 -0
  198. pinecall/postgres/migrations/0097_case_status.sql +19 -0
  199. pinecall/postgres/pool.py +190 -0
  200. pinecall/process/__init__.py +0 -0
  201. pinecall/process/_objects.py +97 -0
  202. pinecall/process/box_settings.py +23 -0
  203. pinecall/process/connections.py +124 -0
  204. pinecall/process/metrics.py +127 -0
  205. pinecall/process/recordings.py +323 -0
  206. pinecall/process/resolver.py +41 -0
  207. pinecall/process/sealed_audio.py +173 -0
  208. pinecall/process/settings.py +534 -0
  209. pinecall/process/shared.py +158 -0
  210. pinecall/process/signal.py +416 -0
  211. pinecall/providers/__init__.py +0 -0
  212. pinecall/providers/build.py +570 -0
  213. pinecall/providers/catalog.py +266 -0
  214. pinecall/providers/credentials.py +230 -0
  215. pinecall/providers/declared.py +69 -0
  216. pinecall/providers/prices.py +240 -0
  217. pinecall/providers/tried.py +104 -0
  218. pinecall/providers/voices.py +174 -0
  219. pinecall/public/console/assets/index-BIfKPSgn.js +164 -0
  220. pinecall/public/console/assets/index-ClOTU-xX.css +1 -0
  221. pinecall/public/console/favicon.png +0 -0
  222. pinecall/public/console/index.html +20 -0
  223. pinecall/public/console/logo-mark.png +0 -0
  224. pinecall/public/console/pinecall-logo-dark.png +0 -0
  225. pinecall/public/console/pinecall-logo.png +0 -0
  226. pinecall/public/console/pinecall-mark.png +0 -0
  227. pinecall/public/console/sw.js +37 -0
  228. pinecall/public/widget/pinecall-widget.js +853 -0
  229. pinecall/py.typed +0 -0
  230. pinecall/retrieval/__init__.py +0 -0
  231. pinecall/retrieval/_search.py +186 -0
  232. pinecall/retrieval/_sections.py +70 -0
  233. pinecall/retrieval/embed.py +340 -0
  234. pinecall/retrieval/extraction.py +403 -0
  235. pinecall/retrieval/knowledge.py +659 -0
  236. pinecall/retrieval/lookups.py +192 -0
  237. pinecall/retrieval/memory.py +603 -0
  238. pinecall/runner/__init__.py +0 -0
  239. pinecall/runner/_kube.py +459 -0
  240. pinecall/runner/_plan.py +157 -0
  241. pinecall/runner/main.py +306 -0
  242. pinecall/session/__init__.py +0 -0
  243. pinecall/session/_agent.py +141 -0
  244. pinecall/session/_hearing.py +101 -0
  245. pinecall/session/_livekit.py +195 -0
  246. pinecall/session/_prompt.py +147 -0
  247. pinecall/session/_recording.py +237 -0
  248. pinecall/session/call.py +367 -0
  249. pinecall/session/clock.py +40 -0
  250. pinecall/session/hold.py +239 -0
  251. pinecall/session/room.py +626 -0
  252. pinecall/session/session.py +690 -0
  253. pinecall/session/text.py +144 -0
  254. pinecall/session/tools.py +483 -0
  255. pinecall/session/voice.py +118 -0
  256. pinecall/session/widget.py +201 -0
  257. pinecall/tenancy/__init__.py +0 -0
  258. pinecall/tenancy/admission.py +204 -0
  259. pinecall/tenancy/agents.py +164 -0
  260. pinecall/tenancy/canary.py +126 -0
  261. pinecall/tenancy/carrier_networks.py +156 -0
  262. pinecall/tenancy/carriers.py +265 -0
  263. pinecall/tenancy/codes.py +196 -0
  264. pinecall/tenancy/consents.py +167 -0
  265. pinecall/tenancy/dial_policy.py +405 -0
  266. pinecall/tenancy/disclosure.py +41 -0
  267. pinecall/tenancy/erasure.py +204 -0
  268. pinecall/tenancy/export.py +167 -0
  269. pinecall/tenancy/hosted_running.py +166 -0
  270. pinecall/tenancy/hosting.py +475 -0
  271. pinecall/tenancy/judges.py +126 -0
  272. pinecall/tenancy/keys.py +467 -0
  273. pinecall/tenancy/knocks.py +40 -0
  274. pinecall/tenancy/letters.py +378 -0
  275. pinecall/tenancy/mail.py +458 -0
  276. pinecall/tenancy/org_secrets.py +123 -0
  277. pinecall/tenancy/orgs.py +78 -0
  278. pinecall/tenancy/people.py +543 -0
  279. pinecall/tenancy/personas.py +145 -0
  280. pinecall/tenancy/policy.py +65 -0
  281. pinecall/tenancy/prompts.py +48 -0
  282. pinecall/tenancy/reads.py +73 -0
  283. pinecall/tenancy/recording_keys.py +41 -0
  284. pinecall/tenancy/remembered.py +148 -0
  285. pinecall/tenancy/retention.py +86 -0
  286. pinecall/tenancy/scopes.py +406 -0
  287. pinecall/tenancy/signin.py +622 -0
  288. pinecall/tenancy/spend.py +69 -0
  289. pinecall/tenancy/sso.py +431 -0
  290. pinecall/tenancy/sso_domains.py +132 -0
  291. pinecall/tenancy/throttle.py +88 -0
  292. pinecall/tenancy/tokens.py +400 -0
  293. pinecall/tenancy/traceback.py +70 -0
  294. pinecall/tenancy/usage.py +136 -0
  295. pinecall/tenancy/vault.py +204 -0
  296. pinecall/tenancy/words.py +144 -0
  297. pinecall/wire/__init__.py +0 -0
  298. pinecall/wire/commands.py +350 -0
  299. pinecall/wire/events.py +655 -0
  300. pinecall/wire/frames.py +65 -0
  301. pinecall/wire/metrics.py +287 -0
  302. pinecall/wire/parts.py +320 -0
  303. pinecall/wire/rest/__init__.py +0 -0
  304. pinecall/wire/rest/accounts.py +512 -0
  305. pinecall/wire/rest/agents.py +172 -0
  306. pinecall/wire/rest/calls.py +397 -0
  307. pinecall/wire/rest/evals.py +453 -0
  308. pinecall/wire/rest/fleet.py +69 -0
  309. pinecall/wire/rest/hosting.py +174 -0
  310. pinecall/wire/rest/numbers.py +280 -0
  311. pinecall/wire/rest/ops.py +342 -0
  312. pinecall/wire/rest/providers.py +83 -0
  313. pinecall/wire/rest/retrieval.py +301 -0
  314. pinecall/wire/rest/settings.py +184 -0
  315. pinecall/wire/rest/usage.py +245 -0
  316. pinecall/wire/room.py +61 -0
  317. pinecall/wire/scores.py +49 -0
  318. pinecall/wire/state.py +243 -0
  319. pinecall/worker/__init__.py +0 -0
  320. pinecall/worker/_job.py +655 -0
  321. pinecall/worker/_recorder.py +82 -0
  322. pinecall/worker/_traces.py +48 -0
  323. pinecall/worker/main.py +307 -0
  324. pinecall_runtime-0.1.6.dist-info/METADATA +212 -0
  325. pinecall_runtime-0.1.6.dist-info/RECORD +328 -0
  326. pinecall_runtime-0.1.6.dist-info/WHEEL +4 -0
  327. pinecall_runtime-0.1.6.dist-info/entry_points.txt +2 -0
  328. pinecall_runtime-0.1.6.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,655 @@
1
+ """Every event a log holds, and the registry of them, the room's (`room.py`) included."""
2
+
3
+ from typing import Annotated, Literal
4
+
5
+ from pydantic import Field
6
+
7
+ from pinecall.domain.agent import EventSource
8
+ from pinecall.domain.errors import DeclarationRefused
9
+ from pinecall.domain.names import Channel, Direction, Env, JsonObject, Medium
10
+ from pinecall.domain.org import QuotaName
11
+ from pinecall.wire.frames import Entry, WireModel
12
+ from pinecall.wire.metrics import (
13
+ AgentTurnMetrics,
14
+ AvatarMetrics,
15
+ EOTInferenceMetrics,
16
+ EOUMetrics,
17
+ InterruptionMetrics,
18
+ LLMMetrics,
19
+ ModelUsage,
20
+ RealtimeModelMetrics,
21
+ STTMetrics,
22
+ TTSMetrics,
23
+ UserTurnMetrics,
24
+ VADMetrics,
25
+ )
26
+ from pinecall.wire.parts import (
27
+ AgentState,
28
+ Contact,
29
+ Cost,
30
+ DevVerb,
31
+ DocSource,
32
+ EndedBy,
33
+ EndReason,
34
+ MemoryOp,
35
+ Projection,
36
+ Route,
37
+ Supervisor,
38
+ ToolResult,
39
+ TransferMode,
40
+ UserState,
41
+ )
42
+ from pinecall.wire.room import (
43
+ ParticipantJoined,
44
+ ParticipantLeft,
45
+ ParticipantSpeaking,
46
+ RoomOpened,
47
+ RoomSent,
48
+ TrackPublished,
49
+ TrackUnpublished,
50
+ )
51
+ from pinecall.wire.scores import CallScore
52
+ from pinecall.wire.state import State
53
+
54
+ # Events a store may drop and a slow reader may miss: the entry's ephemeral flag defaults to this.
55
+ EPHEMERAL_EVENTS: frozenset[str] = frozenset(
56
+ {
57
+ "agent.transcript",
58
+ "dev.request",
59
+ "log.caught_up",
60
+ "log.gap",
61
+ "metrics.vad",
62
+ "participant.speaking",
63
+ "pong",
64
+ "room.sent",
65
+ "user.transcript",
66
+ }
67
+ )
68
+
69
+
70
+ # The one event that ends a call: after it nothing more is true and the log is sealed.
71
+ TERMINAL_EVENT = "call.score"
72
+
73
+
74
+ # What the gateway writes on a call's log itself, and no worker ever does: the arrival, a code
75
+ # claimed, a socket attached, the summary and the score, memory and sources, and the markers a
76
+ # reader is sent and no store keeps. The append doors refuse these from anybody.
77
+ GATEWAY_WRITES: frozenset[str] = frozenset(
78
+ {
79
+ "call.ringing",
80
+ "call.dialing",
81
+ "call.attached",
82
+ "call.claimed",
83
+ "call.summary",
84
+ "call.score",
85
+ "memory.ops",
86
+ "docs.sources",
87
+ "log.gap",
88
+ "log.caught_up",
89
+ }
90
+ )
91
+
92
+
93
+ class EventReceived(WireModel):
94
+ """A fact arrived from outside the conversation, from the app or a participant's browser."""
95
+
96
+ name: str
97
+ data: JsonObject
98
+ source: EventSource
99
+ identity: str | None = None
100
+
101
+
102
+ class AgentConfigured(WireModel):
103
+ """The gateway applied an agent.configure; the next call starts with the new config."""
104
+
105
+ changed: list[str]
106
+
107
+
108
+ class AgentDetached(WireModel):
109
+ """One socket stopped holding the agent."""
110
+
111
+ app: str
112
+ env: Env
113
+ left: bool
114
+
115
+
116
+ class AgentDraining(WireModel):
117
+ """One socket let go of its calls without cutting them."""
118
+
119
+ app: str
120
+ env: Env
121
+ handed: int
122
+ parked: int
123
+
124
+
125
+ class AgentRegistered(WireModel):
126
+ """The gateway accepted an agent.register: this socket now speaks for the agent."""
127
+
128
+ routes: list[Route]
129
+ app: str
130
+ sdk: str | None = None
131
+ env: Env | None = None
132
+
133
+
134
+ class AgentStateChanged(WireModel):
135
+ """The agent's state changed, in the session's own words."""
136
+
137
+ state: AgentState
138
+
139
+
140
+ class AgentTranscript(WireModel):
141
+ """One delta of the reply the agent is giving, never the reply so far."""
142
+
143
+ speech_id: str
144
+ text: str
145
+ final: bool
146
+ start: float | None = None
147
+ end: float | None = None
148
+
149
+
150
+ class AttentionAnswered(WireModel):
151
+ """An ask for a person settled: a supervisor took the line, or the wait ran out."""
152
+
153
+ ok: bool
154
+ by: Supervisor | None
155
+ error: str | None = None
156
+
157
+
158
+ class AttentionRequested(WireModel):
159
+ """The agent asked for a person: the caller is on hold until a supervisor takes the line."""
160
+
161
+ reason: str
162
+ wait_s: float
163
+
164
+
165
+ class CallStarted(WireModel):
166
+ """Media is up: the caller and the agent can hear each other, or the text session is open."""
167
+
168
+ channel: Channel
169
+ direction: Direction
170
+ from_: str = Field(alias="from")
171
+ to: str
172
+ run: str | None = None
173
+ persona: str | None = None
174
+ accepts_when: str | None = None
175
+ declines_when: str | None = None
176
+ caller: Contact | None
177
+ started_at: float
178
+ env: Env | None = None
179
+ worker: str | None = None
180
+ medium: Medium | None = None
181
+ state: JsonObject | None = None
182
+
183
+
184
+ class CallAttached(WireModel):
185
+ """A call changed hands without ending."""
186
+
187
+ app: str
188
+ started: CallStarted
189
+ state: JsonObject
190
+ seq: int
191
+ claimed: str | None = None
192
+
193
+
194
+ class CallClaimed(WireModel):
195
+ """The call was bound to a page's code."""
196
+
197
+ code: str
198
+ via: Literal["keypad", "agent"]
199
+
200
+
201
+ class CallDialing(WireModel):
202
+ """The platform is placing an outbound call and the far end has not answered yet."""
203
+
204
+ channel: Channel
205
+ from_: str = Field(alias="from")
206
+ to: str
207
+ run: str | None = None
208
+ caller: Contact | None
209
+ external_id: str | None = None
210
+ asked_by: str | None = None
211
+
212
+
213
+ class CallEnded(WireModel):
214
+ """The call is over; call.summary still follows."""
215
+
216
+ reason: EndReason
217
+ ended_by: EndedBy
218
+ ended_at: float
219
+ duration_s: float
220
+
221
+
222
+ class CallLine(WireModel):
223
+ """The line's hold and mute flags after one of them changed."""
224
+
225
+ held: bool
226
+ muted: bool
227
+
228
+
229
+ class CallRinging(WireModel):
230
+ """An inbound call is offered to this agent and has not been answered yet."""
231
+
232
+ channel: Channel
233
+ from_: str = Field(alias="from")
234
+ to: str
235
+ route: Route
236
+ run: str | None = None
237
+ caller: Contact | None
238
+ external_id: str | None = None
239
+
240
+
241
+ class CallSummary(WireModel):
242
+ """What the call was about, how it went, what it consumed and what that cost."""
243
+
244
+ reason: EndReason
245
+ outcome: str
246
+ duration_s: float
247
+ turns: int
248
+ usage: list[ModelUsage]
249
+ cost: Cost
250
+ recording: str | None = None
251
+
252
+
253
+ class CallTransferred(WireModel):
254
+ """A transfer asked for by the agent or a supervisor finished, one way or the other."""
255
+
256
+ to: str
257
+ mode: TransferMode | None = None
258
+ ok: bool
259
+ error: str | None = None
260
+
261
+
262
+ class CallbackRequested(WireModel):
263
+ """Somebody asked to be called back; written into the agent's own log."""
264
+
265
+ channel: Channel
266
+ number: str
267
+ via: Literal["overflow", "widget", "agent"]
268
+ call: str | None
269
+ when: str | None = None
270
+ note: str | None = None
271
+ contact: Contact | None
272
+
273
+
274
+ class CodeClaimed(WireModel):
275
+ """One code, taken or expired."""
276
+
277
+ code: str
278
+ call: str | None
279
+
280
+
281
+ class CodeIssued(WireModel):
282
+ """One code, waiting for the call that keys it."""
283
+
284
+ code: str
285
+ env: Env
286
+ expires_at: float
287
+ log: Projection
288
+
289
+
290
+ class ConfirmDeclined(WireModel):
291
+ """The caller did not say yes, or the request lapsed; the tool does not run."""
292
+
293
+ tool: str
294
+ call_id: str
295
+ audience: str
296
+ said: str | None = None
297
+ reason: Literal["no", "timeout", "changed", "cancelled"]
298
+
299
+
300
+ class ConfirmGranted(WireModel):
301
+ """The caller said yes; the token minted for it never enters the log."""
302
+
303
+ tool: str
304
+ call_id: str
305
+ audience: str
306
+ said: str
307
+ ttl_s: int
308
+
309
+
310
+ class ConfirmRequest(WireModel):
311
+ """A tool with confirm set is about to run and the platform is asking the caller."""
312
+
313
+ tool: str
314
+ call_id: str
315
+ arguments: JsonObject
316
+ audience: str
317
+ phrase: str
318
+ ttl_s: int
319
+
320
+
321
+ class CreditsExhausted(WireModel):
322
+ """The gateway refused a call, a turn or a register because one of the org's quotas ran out."""
323
+
324
+ org: str
325
+ quota: QuotaName
326
+ used: float
327
+ limit: int
328
+
329
+
330
+ class SpendUnusual(WireModel):
331
+ """The org's calls cost more today than its own days usually do: said once a day."""
332
+
333
+ org: str
334
+ day: str
335
+ today_usd: float
336
+ usual_usd: float
337
+ multiple: float
338
+
339
+
340
+ class Custom(WireModel):
341
+ """A line the app wrote into the log with call.log; the platform never reads it."""
342
+
343
+ name: str
344
+ data: JsonObject
345
+
346
+
347
+ class DevRequest(WireModel):
348
+ """One ask of the process in the agent's directory, on a console's behalf."""
349
+
350
+ id: str
351
+ verb: DevVerb
352
+ data: JsonObject
353
+
354
+
355
+ class DocsSources(WireModel):
356
+ """What retrieval put in front of the model for this turn."""
357
+
358
+ query: str
359
+ sources: list[DocSource]
360
+ took_ms: float
361
+ speech_id: str | None = None
362
+
363
+
364
+ class DtmfReceived(WireModel):
365
+ """One tone the caller keyed."""
366
+
367
+ digit: Literal["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "#"]
368
+ code: int
369
+
370
+
371
+ class ErrorEvent(WireModel):
372
+ """Something went wrong: what failed in a call, or which command the gateway refused."""
373
+
374
+ code: str
375
+ message: str
376
+ command: str | None = None
377
+ id: str | None = None
378
+ recoverable: bool
379
+
380
+
381
+ class FleetFull(WireModel):
382
+ """The gateway refused to open a call because every worker of the fleet was full."""
383
+
384
+ channel: Channel
385
+ workers: int
386
+ active: int
387
+
388
+
389
+ class LogCaughtUp(WireModel):
390
+ """The replay is done: everything up to seq has been sent and what follows is live."""
391
+
392
+ seq: int
393
+
394
+
395
+ class LogGap(WireModel):
396
+ """This reader missed a stretch; the snapshot, when there is one, catches it up in one step."""
397
+
398
+ from_seq: int
399
+ to_seq: int
400
+ snapshot: State | None
401
+
402
+
403
+ class MemoryOps(WireModel):
404
+ """What memory did for this turn or at hangup."""
405
+
406
+ ops: list[MemoryOp]
407
+ speech_id: str | None = None
408
+
409
+
410
+ class MessageTaken(WireModel):
411
+ """One waiting message, off the queue."""
412
+
413
+ message_id: str
414
+ call: str | None
415
+
416
+
417
+ class MessageWaiting(WireModel):
418
+ """One message, kept until somebody can answer it."""
419
+
420
+ channel: Channel
421
+ env: Env
422
+ number: str
423
+ phone_number_id: str
424
+ from_: str = Field(alias="from")
425
+ name: str | None
426
+ message_id: str
427
+ text: str
428
+ received_at: float
429
+
430
+
431
+ class Pong(WireModel):
432
+ """The answer to ping."""
433
+
434
+ ts: float
435
+
436
+
437
+ class PromptChanged(WireModel):
438
+ """A block of the prompt was rewritten; its hash and length, never its text."""
439
+
440
+ name: str
441
+ hash: str
442
+ chars: int
443
+
444
+
445
+ class StateCauseTool(WireModel):
446
+ """A tool call's result changed the state."""
447
+
448
+ kind: Literal["tool"] = "tool"
449
+ tool: str
450
+ call_id: str
451
+
452
+
453
+ class StateCauseEvent(WireModel):
454
+ """A fact from outside changed the state."""
455
+
456
+ kind: Literal["event"] = "event"
457
+ name: str
458
+ seq: int
459
+
460
+
461
+ type StateCause = Annotated[StateCauseTool | StateCauseEvent, Field(discriminator="kind")]
462
+
463
+
464
+ class SupervisorEnded(WireModel):
465
+ """A supervisor hung up the call."""
466
+
467
+ by: Supervisor
468
+ reason: str | None = None
469
+
470
+
471
+ class StateChanged(WireModel):
472
+ """The app's declared state changed; the whole state travels."""
473
+
474
+ state: JsonObject
475
+ changed: list[str]
476
+ cause: StateCause | None = None
477
+
478
+
479
+ class SupervisorReleased(WireModel):
480
+ """The supervisor gave the line back; the agent resumes with the history intact."""
481
+
482
+ by: Supervisor
483
+
484
+
485
+ class SupervisorSaid(WireModel):
486
+ """A supervisor made the agent say this to the caller."""
487
+
488
+ by: Supervisor
489
+ text: str
490
+
491
+
492
+ class SupervisorTookOver(WireModel):
493
+ """A supervisor took the line; the agent is quiet until supervisor.released."""
494
+
495
+ by: Supervisor
496
+
497
+
498
+ class SupervisorTransferred(WireModel):
499
+ """A supervisor asked for a transfer."""
500
+
501
+ by: Supervisor
502
+ to: str
503
+ mode: TransferMode | None = None
504
+
505
+
506
+ class SupervisorWhispered(WireModel):
507
+ """A supervisor told the agent something the caller never heard."""
508
+
509
+ by: Supervisor
510
+ text: str
511
+
512
+
513
+ class ToolCall(WireModel):
514
+ """The model called a tool; tool.result closes it."""
515
+
516
+ call_id: str
517
+ name: str
518
+ arguments: JsonObject
519
+ speech_id: str | None = None
520
+
521
+
522
+ class ToolsChanged(WireModel):
523
+ """The tools the model can see changed."""
524
+
525
+ visible: list[str]
526
+
527
+
528
+ class AgentTurnEnded(WireModel):
529
+ """The agent's reply is over and this is what was said."""
530
+
531
+ speech_id: str
532
+ item_id: str | None = None
533
+ text: str
534
+ interrupted: bool
535
+ metrics: AgentTurnMetrics
536
+
537
+
538
+ class UserTurnEnded(WireModel):
539
+ """The caller's turn is over and this is what they said."""
540
+
541
+ speech_id: str
542
+ item_id: str | None = None
543
+ text: str
544
+ language: str | None = None
545
+ transcript_confidence: float | None = None
546
+ metrics: UserTurnMetrics
547
+
548
+
549
+ class UserStateChanged(WireModel):
550
+ """The caller's state changed, in the session's own words."""
551
+
552
+ state: UserState
553
+
554
+
555
+ class UserTranscript(WireModel):
556
+ """Words from the caller as the recognizer hears them; the final one becomes turn.user."""
557
+
558
+ text: str
559
+ final: bool
560
+ language: str | None = None
561
+ confidence: float | None = None
562
+
563
+
564
+ class VendorSwitched(WireModel):
565
+ """A stage's vendor failed or came back; `serving` is the vendor the stage runs on now."""
566
+
567
+ stage: Literal["llm", "stt", "tts"]
568
+ vendor: str
569
+ model: str
570
+ available: bool
571
+ serving: str
572
+ serving_model: str
573
+
574
+
575
+ EVENTS: dict[str, type[WireModel]] = {
576
+ "agent.configured": AgentConfigured,
577
+ "agent.detached": AgentDetached,
578
+ "agent.draining": AgentDraining,
579
+ "agent.registered": AgentRegistered,
580
+ "agent.state": AgentStateChanged,
581
+ "agent.transcript": AgentTranscript,
582
+ "attention.answered": AttentionAnswered,
583
+ "attention.requested": AttentionRequested,
584
+ "call.attached": CallAttached,
585
+ "call.claimed": CallClaimed,
586
+ "call.dialing": CallDialing,
587
+ "call.ended": CallEnded,
588
+ "call.line": CallLine,
589
+ "call.ringing": CallRinging,
590
+ "call.score": CallScore,
591
+ "call.started": CallStarted,
592
+ "call.summary": CallSummary,
593
+ "call.transferred": CallTransferred,
594
+ "callback.requested": CallbackRequested,
595
+ "code.claimed": CodeClaimed,
596
+ "code.issued": CodeIssued,
597
+ "confirm.declined": ConfirmDeclined,
598
+ "confirm.granted": ConfirmGranted,
599
+ "confirm.request": ConfirmRequest,
600
+ "credits.exhausted": CreditsExhausted,
601
+ "custom": Custom,
602
+ "dev.request": DevRequest,
603
+ "docs.sources": DocsSources,
604
+ "dtmf.received": DtmfReceived,
605
+ "error": ErrorEvent,
606
+ "event.received": EventReceived,
607
+ "fleet.full": FleetFull,
608
+ "log.caught_up": LogCaughtUp,
609
+ "log.gap": LogGap,
610
+ "memory.ops": MemoryOps,
611
+ "message.taken": MessageTaken,
612
+ "message.waiting": MessageWaiting,
613
+ "metrics.avatar": AvatarMetrics,
614
+ "metrics.eot": EOTInferenceMetrics,
615
+ "metrics.eou": EOUMetrics,
616
+ "metrics.interruption": InterruptionMetrics,
617
+ "metrics.llm": LLMMetrics,
618
+ "metrics.realtime": RealtimeModelMetrics,
619
+ "metrics.stt": STTMetrics,
620
+ "metrics.tts": TTSMetrics,
621
+ "metrics.vad": VADMetrics,
622
+ "participant.joined": ParticipantJoined,
623
+ "participant.left": ParticipantLeft,
624
+ "participant.speaking": ParticipantSpeaking,
625
+ "pong": Pong,
626
+ "prompt.changed": PromptChanged,
627
+ "room.opened": RoomOpened,
628
+ "room.sent": RoomSent,
629
+ "state.changed": StateChanged,
630
+ "spend.unusual": SpendUnusual,
631
+ "supervisor.ended": SupervisorEnded,
632
+ "supervisor.released": SupervisorReleased,
633
+ "supervisor.said": SupervisorSaid,
634
+ "supervisor.took_over": SupervisorTookOver,
635
+ "supervisor.transferred": SupervisorTransferred,
636
+ "supervisor.whispered": SupervisorWhispered,
637
+ "tool.call": ToolCall,
638
+ "tool.result": ToolResult,
639
+ "tools.changed": ToolsChanged,
640
+ "track.published": TrackPublished,
641
+ "track.unpublished": TrackUnpublished,
642
+ "turn.agent": AgentTurnEnded,
643
+ "turn.user": UserTurnEnded,
644
+ "user.state": UserStateChanged,
645
+ "user.transcript": UserTranscript,
646
+ "vendor.switched": VendorSwitched,
647
+ }
648
+
649
+
650
+ def event_of(entry: Entry) -> WireModel:
651
+ """Return the entry's data as the model its type names; an unknown type is refused."""
652
+ model = EVENTS.get(entry.type)
653
+ if model is None:
654
+ raise DeclarationRefused(f"unknown event type: {entry.type}")
655
+ return model.read(entry.data, entry.type)
@@ -0,0 +1,65 @@
1
+ """The frames: the base every wire model shares, the log entry, the command, a whole log."""
2
+
3
+ from typing import Self
4
+
5
+ from pydantic import BaseModel, ConfigDict, ValidationError
6
+
7
+ from pinecall.domain.errors import DeclarationRefused
8
+ from pinecall.domain.names import JsonObject
9
+
10
+
11
+ class WireModel(BaseModel):
12
+ """A shape on the wire: unknown keys are refused; a field builds by name or alias."""
13
+
14
+ model_config = ConfigDict(extra="forbid", validate_by_name=True, validate_by_alias=True)
15
+
16
+ @classmethod
17
+ def read(cls, raw: JsonObject, what: str) -> Self:
18
+ """Build the model from decoded JSON, raising DeclarationRefused with what did not fit."""
19
+ try:
20
+ return cls.model_validate(raw)
21
+ except ValidationError as error:
22
+ raise DeclarationRefused(f"{what}: {error}") from error
23
+
24
+ # The two below stay in pydantic's own parser and writer (Rust): on the paths a gateway runs
25
+ # per entry, decoding to a dict and reading it, or dumping and encoding, is what shows in its
26
+ # profile (21 % of a core at 100 calls, 2026-10-01).
27
+ @classmethod
28
+ def read_json(cls, raw: str | bytes, what: str) -> Self:
29
+ """Build the model from JSON text, raising DeclarationRefused with what did not fit."""
30
+ try:
31
+ return cls.model_validate_json(raw)
32
+ except ValidationError as error:
33
+ raise DeclarationRefused(f"{what}: {error}") from error
34
+
35
+ def written(self) -> JsonObject:
36
+ """Return the model as it goes on the wire: wire keys, absent fields absent, JSON values."""
37
+ return self.model_dump(mode="json", by_alias=True, exclude_unset=True)
38
+
39
+ def written_json(self) -> str:
40
+ """Return the model as `written()` is, as JSON text in one step."""
41
+ return self.model_dump_json(by_alias=True, exclude_unset=True)
42
+
43
+
44
+ # seq is written before control returns, so two readers never disagree about order.
45
+ class Entry(WireModel):
46
+ """One line of a call's log, or of an agent's log when call is null."""
47
+
48
+ seq: int
49
+ ts: float
50
+ call: str | None
51
+ agent: str
52
+ type: str
53
+ ephemeral: bool
54
+ data: JsonObject
55
+
56
+
57
+ # The gateway answers with the events the command produces, or with an error naming the id.
58
+ class Command(WireModel):
59
+ """One instruction from an app to the gateway over its WebSocket."""
60
+
61
+ type: str
62
+ agent: str
63
+ call: str | None
64
+ id: str | None = None
65
+ data: JsonObject