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,453 @@
1
+ """The bodies of the eval doors: a golden, a case, a suite, a run, a replay, a persona, a judge."""
2
+
3
+ from datetime import date
4
+ from typing import Literal
5
+
6
+ from pydantic import Field
7
+
8
+ from pinecall.domain.agent import RunsOn
9
+ from pinecall.domain.names import Env, Json, JsonObject
10
+ from pinecall.wire.frames import WireModel
11
+ from pinecall.wire.parts import EndReason, ModelConfig, ScoreVerdict
12
+ from pinecall.wire.rest.calls import SessionScore
13
+
14
+ # How a Spanish-speaking agent addresses the caller.
15
+ type Register = Literal["tu", "usted"]
16
+
17
+
18
+ # `failed` is the run itself; a golden that failed is a score, not a status.
19
+ type RunStatus = Literal["running", "done", "failed"]
20
+
21
+
22
+ # A call that broke is kept `pending` at hang-up; a person approves it into the nightly or
23
+ # dismisses it. A case kept by hand is approved from the start.
24
+ type CaseStatus = Literal["pending", "approved", "dismissed"]
25
+
26
+
27
+ # ── a golden ──
28
+
29
+
30
+ class EventStep(WireModel):
31
+ """A fact of the tenant's backend injected into a golden, after the caller turn it names."""
32
+
33
+ # 0 is before the caller's first line.
34
+ after_turn: int = 0
35
+ name: str
36
+ data: JsonObject = Field(default_factory=dict[str, Json])
37
+
38
+
39
+ class Expect(WireModel):
40
+ """What a golden expects; each field set is one judge."""
41
+
42
+ tools: list[str] = Field(default_factory=list[str])
43
+ # Catches an action taken before consent, which a list of phrases cannot.
44
+ not_tools: list[str] = Field(default_factory=list[str])
45
+ not_said: list[str] = Field(default_factory=list[str], alias="not")
46
+ says: list[str] = Field(default_factory=list[str])
47
+ # Any one of these is enough: an expectation with several right answers.
48
+ says_any: list[str] = Field(default_factory=list[str])
49
+ # Every price, hour, date and name the agent stated is in the call's evidence.
50
+ grounded: bool = False
51
+ # A field named `register` would shadow a pydantic attribute.
52
+ addressed_as: Register | None = Field(default=None, alias="register")
53
+ # With `events` only: whether the agent takes the fact up.
54
+ replies: bool | None = None
55
+ # Hang-up judges asked of the golden's call by name: the one field a model may answer.
56
+ judges: list[str] = Field(default_factory=list[str])
57
+
58
+
59
+ class Golden(WireModel):
60
+ """One scripted conversation: the state it opens in, the caller's lines, what is expected."""
61
+
62
+ name: str
63
+ state: JsonObject = Field(default_factory=dict[str, Json])
64
+ # The caller's lines, each one a turn the box pays the agent's model for: forty at most.
65
+ input: list[str] = Field(default_factory=list[str], max_length=40)
66
+ # Answered to `recall` for this call only; the memory table is never written.
67
+ memory: list[str] = Field(default_factory=list[str])
68
+ events: list[EventStep] = Field(default_factory=list[EventStep])
69
+ # The day the model is told it is; the real one when absent.
70
+ today: date | None = None
71
+ expect: Expect = Field(default_factory=Expect)
72
+ # The call `pinecall runs promote` made it from; no judge reads it.
73
+ promoted_from: str | None = None
74
+
75
+
76
+ # ── a suite, its run, and the matrix it is scored into ──
77
+
78
+
79
+ class RunSuiteRequest(WireModel):
80
+ """POST /v1/evals/run, the body: the agent, its goldens and cases, the models, a version."""
81
+
82
+ agent: str
83
+ # Each golden is a call whose simulated caller the box pays for: two hundred at most, and two
84
+ # hundred calls in all once the cases join and every model plays each (the door counts them).
85
+ goldens: list[Golden] = Field(default_factory=list[Golden], max_length=200)
86
+ # Cases of the org's dataset by name; a case held out is played only when named here.
87
+ cases: list[str] = Field(default_factory=list[str], max_length=200)
88
+ # Every case of the agent's that is not held out: the nightly run.
89
+ dataset: bool = False
90
+ # A version of the agent's settings in the app's scope to run instead of the one standing.
91
+ version: int | None = Field(default=None, ge=1)
92
+ # Empty: the model the agent declares. Each one plays every golden again: eight at most.
93
+ models: list[ModelConfig] = Field(default_factory=list[ModelConfig], max_length=8)
94
+ # The app socket to drive, as `WS /v1/chat?app=`; absent, the newest that holds the agent.
95
+ app: str | None = None
96
+ # Each golden said out loud, through the world's fleet.
97
+ voice: bool = False
98
+ interferer_db: float | None = None
99
+ packet_loss: float = Field(default=0.0, ge=0, le=1)
100
+
101
+
102
+ class OpenedCall(WireModel):
103
+ """The call one golden opened under one model."""
104
+
105
+ golden: str
106
+ model: str
107
+ call: str
108
+
109
+
110
+ class JudgeScore(WireModel):
111
+ """One judge's verdict on one golden under one model."""
112
+
113
+ metric: str
114
+ # livekit's scoring: a pass is 1, unsure is 0.5, a fail is 0.
115
+ score: float
116
+ passed: bool
117
+ reason: str
118
+ criteria: str
119
+ judge_calls: int
120
+
121
+
122
+ # `asked` is set on a cell that did not hold, and null when the run kept no requests.
123
+ class ScoreRow(WireModel):
124
+ """Every judge's verdict on one golden under one model, and the call's own summary."""
125
+
126
+ model: str
127
+ golden: str
128
+ scores: list[JudgeScore]
129
+ summary: JsonObject | None
130
+ asked: list[JsonObject] | None = None
131
+
132
+
133
+ class Failure(WireModel):
134
+ """One judge that did not hold, and the cell it broke on."""
135
+
136
+ model: str
137
+ golden: str
138
+ metric: str
139
+
140
+
141
+ class ScoreMatrix(WireModel):
142
+ """Models by goldens: the axes in the order the cells came, one row per cell."""
143
+
144
+ models: list[str]
145
+ goldens: list[str]
146
+ metrics: list[str]
147
+ # A count, not a price: a judge's tokens are billed with the rest of the model usage.
148
+ judge_calls: int
149
+ runs: list[ScoreRow]
150
+ failures: list[Failure]
151
+
152
+
153
+ class EvalRunResponse(WireModel):
154
+ """An eval run: the calls it opened, the matrix judged so far, and why it stopped early."""
155
+
156
+ id: str
157
+ agent: str
158
+ started_at: float
159
+ finished_at: float | None
160
+ status: RunStatus
161
+ calls: list[OpenedCall]
162
+ matrix: ScoreMatrix | None
163
+ error: str | None
164
+
165
+
166
+ class EvalRunList(WireModel):
167
+ """GET /v1/evals/runs: the runs of the key's org and world, newest first."""
168
+
169
+ runs: list[EvalRunResponse]
170
+
171
+
172
+ # ── the org's dataset: real calls kept as goldens ──
173
+
174
+
175
+ class PromoteCaseRequest(WireModel):
176
+ """POST /v1/evals/cases, the body: the finished call, its name, what it expects, held out."""
177
+
178
+ call: str
179
+ name: str = Field(min_length=1)
180
+ expect: Expect = Field(default_factory=Expect)
181
+ # Left out of a nightly run, read only when a run names it.
182
+ held_out: bool = False
183
+
184
+
185
+ class BrokeOn(WireModel):
186
+ """A judge that broke on the call a case was kept from, and why."""
187
+
188
+ judge: str
189
+ reason: str
190
+
191
+
192
+ class EvalCase(WireModel):
193
+ """One case of the org's dataset: the golden a real call made, whose, from where, by whom."""
194
+
195
+ id: str
196
+ agent: str
197
+ name: str
198
+ golden: Golden
199
+ source_call: str
200
+ source_env: Env
201
+ held_out: bool
202
+ author: str
203
+ created_at: float
204
+ status: CaseStatus = "approved"
205
+ # The judges that broke at hang-up; empty for a case a person kept from a call that held.
206
+ broke: list[BrokeOn] = Field(default_factory=list[BrokeOn])
207
+ # The settings version the call ran on, None where its corner had set nothing.
208
+ source_version: int | None = None
209
+ # Written into the repository as a golden: the nightly plays the file, not the case.
210
+ kept_in_repo: bool = False
211
+ decided_by: str | None = None
212
+
213
+
214
+ class EvalCaseList(WireModel):
215
+ """GET /v1/evals/cases: the org's cases, by agent and name; how many wait, of how many."""
216
+
217
+ cases: list[EvalCase]
218
+ pending: int = 0
219
+ # Past it a broken call is judged as ever and not kept: the inbox is for reading.
220
+ pending_at_most: int = 0
221
+
222
+
223
+ class CaseDecision(WireModel):
224
+ """PATCH /v1/evals/cases/{id}, the body: what a person decided; each field set is written."""
225
+
226
+ status: CaseStatus | None = None
227
+ held_out: bool | None = None
228
+ kept_in_repo: bool | None = None
229
+ # With `dismissed`: the judge that should have held, written as a calibration label.
230
+ judge_was_wrong: str | None = Field(default=None, min_length=1)
231
+ note: str | None = None
232
+
233
+
234
+ # ── the judge judged ──
235
+
236
+
237
+ class LabelRequest(WireModel):
238
+ """POST /v1/evals/calibration, the body: what one judge should have answered on a call."""
239
+
240
+ call: str
241
+ judge: str = Field(min_length=1)
242
+ held: bool
243
+ note: str | None = None
244
+
245
+
246
+ class JudgeAgreement(WireModel):
247
+ """One judge against the labels people gave: how many, compared, agreed, and trusted."""
248
+
249
+ judge: str
250
+ labelled: int
251
+ compared: int
252
+ agreed: int
253
+ rate: float | None
254
+ # None until enough labels were compared to say; False is a judge under its line.
255
+ trusted: bool | None
256
+
257
+
258
+ class Calibration(WireModel):
259
+ """GET /v1/evals/calibration: each judge's agreement, and the line it is held to."""
260
+
261
+ judges: list[JudgeAgreement]
262
+ labels_to_judge: int
263
+ agrees_at_least: float
264
+
265
+
266
+ # ── a finished call checked again ──
267
+
268
+
269
+ class ReplayCallRequest(WireModel):
270
+ """POST /v1/evals/replay/{call}, the body: the words banned and the latency budget."""
271
+
272
+ banned: list[str] = Field(default_factory=list[str])
273
+ budget: dict[str, float] = Field(default_factory=dict[str, float])
274
+
275
+
276
+ class CheckVerdict(WireModel):
277
+ """One code check's answer: its name, its status and why."""
278
+
279
+ check: str
280
+ status: ScoreVerdict
281
+ detail: str
282
+
283
+
284
+ class ReplayCallResponse(WireModel):
285
+ """POST /v1/evals/replay/{call}, the answer: the six checks and whether none broke."""
286
+
287
+ call: str
288
+ agent: str
289
+ passed: bool
290
+ verdicts: list[CheckVerdict]
291
+
292
+
293
+ # ── the simulated caller ──
294
+
295
+
296
+ class CallerPersona(WireModel):
297
+ """A persona as the caller doors take it: who to play, and on what."""
298
+
299
+ name: str = ""
300
+ # Bounded: each is a model's prompt, on keys the box may lend.
301
+ goal: str = Field(max_length=4000)
302
+ style: str = Field(max_length=4000)
303
+ facts: JsonObject = Field(default_factory=dict[str, Json])
304
+ llm: str | None = None
305
+ tts: str | None = None
306
+ voice: str | None = None
307
+ # For the persona judge alone: the model playing the caller is never told it.
308
+ accepts_when: str = ""
309
+ declines_when: str = ""
310
+
311
+
312
+ class Spoken(WireModel):
313
+ """One turn of the call as the caller heard it."""
314
+
315
+ who: Literal["agent", "caller"]
316
+ said: str = Field(max_length=4000)
317
+
318
+
319
+ class NextLineRequest(WireModel):
320
+ """POST /v1/evals/caller, the body: the persona, the call so far, the turns left."""
321
+
322
+ persona: CallerPersona
323
+ # Two turns for each of the forty a call may take, and no more.
324
+ heard: list[Spoken] = Field(default_factory=list[Spoken], max_length=80)
325
+ turns_left: int = Field(default=1, ge=0, le=40)
326
+
327
+
328
+ class NextLineResponse(WireModel):
329
+ """The caller's next line, and whether it hangs up after it."""
330
+
331
+ say: str
332
+ hangup: bool = False
333
+
334
+
335
+ class PlaceVoiceCallRequest(WireModel):
336
+ """POST /v1/evals/voice, the body: the room, the agent, the persona, and the line."""
337
+
338
+ # Minted by the client, as the box mints one (`call_` and a word), so it tails the log before
339
+ # the call starts; the door takes it only for a call nobody opened yet.
340
+ call: str = Field(pattern=r"^call_[A-Za-z0-9]{1,64}$")
341
+ agent: str
342
+ persona: CallerPersona
343
+ # The caller's lines, each paid for by the box (its model and its voice): forty at most.
344
+ turns: int = Field(default=6, ge=1, le=40)
345
+ # dB of a background voice under the caller's; absent is a clean line.
346
+ interferer_db: float | None = None
347
+ packet_loss: float = Field(default=0.0, ge=0, le=1)
348
+ # The state the agent opens the call in: the persona's, as `pinecall simulate` reads it.
349
+ state: JsonObject = Field(default_factory=dict[str, Json])
350
+
351
+
352
+ class PlaceVoiceCallResponse(WireModel):
353
+ """POST /v1/evals/voice, the answer: how many lines the caller said, and on what line."""
354
+
355
+ call: str
356
+ turns: int
357
+ line: str
358
+ # What the caller's lines and voice cost, in US dollars, by the box's rates.
359
+ caller_cost_usd: float = 0.0
360
+ # The caller hung up because its call had spent the providers row's `caller.ceiling_usd`.
361
+ stopped_at_ceiling: bool = False
362
+
363
+
364
+ # ── an agent's personas ──
365
+
366
+
367
+ class PersonaRequest(WireModel):
368
+ """PUT /v1/agents/{slug}/personas/{name}, the body: the caller written whole."""
369
+
370
+ about: str = ""
371
+ goal: str
372
+ style: str
373
+ facts: dict[str, str] = Field(default_factory=dict[str, str])
374
+ state: JsonObject = Field(default_factory=dict[str, Json])
375
+ llm: str | None = None
376
+ tts: str | None = None
377
+ voice: str | None = None
378
+ accepts_when: str | None = None
379
+ declines_when: str | None = None
380
+ # The name this caller had, when the write renames it.
381
+ was: str | None = None
382
+
383
+
384
+ class PersonaRow(WireModel):
385
+ """One caller of the agent as the list shows it."""
386
+
387
+ name: str
388
+ about: str
389
+ goal: str
390
+ style: str
391
+ facts: dict[str, str]
392
+ state: JsonObject
393
+ llm: str | None
394
+ tts: str | None
395
+ voice: str | None
396
+ accepts_when: str
397
+ declines_when: str
398
+ author: str
399
+ set_at: float
400
+
401
+
402
+ class PersonaList(WireModel):
403
+ """GET /v1/agents/{slug}/personas: the agent's callers, by name."""
404
+
405
+ personas: list[PersonaRow]
406
+
407
+
408
+ class PersonaRunRow(WireModel):
409
+ """One call a persona made: when, how long, how it ended, what it cost, the judges' score."""
410
+
411
+ call: str
412
+ agent: str
413
+ started_at: float
414
+ ended_at: float | None
415
+ turns: int
416
+ end_reason: EndReason | None
417
+ outcome: str | None
418
+ cost_usd: float | None
419
+ score: SessionScore | None
420
+
421
+
422
+ class PersonaRunList(WireModel):
423
+ """GET /v1/agents/{slug}/personas/{name}/runs: a page of the persona's calls, newest first."""
424
+
425
+ runs: list[PersonaRunRow]
426
+ total: int
427
+ next: str | None
428
+
429
+
430
+ # ── an agent's own judges ──
431
+
432
+
433
+ class JudgeRequest(WireModel):
434
+ """PUT /v1/agents/{slug}/judges/{name}, the body: the question, and which calls it reads."""
435
+
436
+ question: str
437
+ runs_on: RunsOn = "every-call"
438
+
439
+
440
+ class JudgeRow(WireModel):
441
+ """One of the agent's own judges as the list shows it."""
442
+
443
+ name: str
444
+ question: str
445
+ runs_on: RunsOn
446
+ author: str
447
+ set_at: float
448
+
449
+
450
+ class JudgeList(WireModel):
451
+ """GET /v1/agents/{slug}/judges: the agent's own judges, by name."""
452
+
453
+ judges: list[JudgeRow]
@@ -0,0 +1,69 @@
1
+ """The bodies of the fleet doors: a worker's heartbeat and what the gateway answers it."""
2
+
3
+ from pydantic import ConfigDict
4
+
5
+ from pinecall.wire.frames import WireModel
6
+
7
+
8
+ class HeartbeatRequest(WireModel):
9
+ """A worker's report: its fleet, its name, what it holds and how loaded it is."""
10
+
11
+ fleet: str
12
+ worker: str
13
+ # The name the worker registered under with LiveKit: the one a dispatch reaches it by; null
14
+ # until LiveKit registered it, so no call is offered to a worker nobody can give it to.
15
+ agent_name: str | None = None
16
+ active: int
17
+ # Measured slots; null when the worker is gated on its machine's CPU.
18
+ max_jobs: int | None
19
+ load: float
20
+ draining: bool
21
+ # Its last minute, as its calls' jobs told it; absent from a worker of an older release.
22
+ ended: int | None = None
23
+ failed: int | None = None
24
+ errors: int | None = None
25
+ turns: int | None = None
26
+ first_audio_p95_s: float | None = None
27
+
28
+
29
+ class HeartbeatResponse(WireModel):
30
+ """The answer to a heartbeat: whether this worker is cordoned, and its fleet full."""
31
+
32
+ cordoned: bool
33
+ full: bool
34
+
35
+
36
+ class FleetTotals(WireModel):
37
+ """One fleet over the workers heard from lately."""
38
+
39
+ fleet: str
40
+ workers: int
41
+ active: int
42
+ seats: int
43
+ free: int
44
+ accepting: int
45
+ full: bool
46
+ # Rooms with a caller that no worker opened yet, offered by the gateway (gateway/dispatching/).
47
+ waiting: int = 0
48
+
49
+
50
+ class WorkerStatus(WireModel):
51
+ """One worker as its last heartbeat left it, and whether the operator cordoned it."""
52
+
53
+ model_config = ConfigDict(frozen=True, extra="forbid")
54
+
55
+ fleet: str
56
+ worker: str
57
+ # The name the worker registered under with LiveKit: the one a dispatch reaches it by.
58
+ agent_name: str | None = None
59
+ active: int
60
+ max_jobs: int | None
61
+ load: float
62
+ draining: bool
63
+ cordoned: bool
64
+ seen_at: float
65
+ ended: int | None = None
66
+ failed: int | None = None
67
+ errors: int | None = None
68
+ turns: int | None = None
69
+ first_audio_p95_s: float | None = None
@@ -0,0 +1,174 @@
1
+ """The bodies of the hosting doors: hosted apps, releases, secrets, and the runner's report."""
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import ConfigDict, Field
6
+
7
+ from pinecall.domain.names import Env
8
+ from pinecall.wire.frames import WireModel
9
+
10
+ # A runner is deployed apart from its gateway, and must outlive a gateway newer than itself: what
11
+ # the gateway answers it is read leniently, and every field added to it carries a default.
12
+ READ_BY_AN_OLDER_RUNNER = ConfigDict(extra="ignore", validate_by_name=True, validate_by_alias=True)
13
+
14
+
15
+ class HostedAppRow(WireModel):
16
+ """One app the box hosts for the org: its newest release, and how the runner left it."""
17
+
18
+ name: str
19
+ # The newest release, null before the first.
20
+ release: int | None
21
+ # The release serving the app now, null while none is.
22
+ live_release: int | None
23
+ # Why the newest release did not build or start, null when it did or is still on its way.
24
+ failed_why: str | None
25
+ # A person stopped it: nothing runs, and its releases and token stay.
26
+ stopped: bool
27
+ created_by: str
28
+ created_at: float
29
+
30
+
31
+ class HostedAppList(WireModel):
32
+ """GET /v1/hosted: the apps the box hosts for the org in the world, by name."""
33
+
34
+ apps: list[HostedAppRow]
35
+
36
+
37
+ class ReleaseRow(WireModel):
38
+ """One release of a hosted app: its number, its sources' sha256 and size, who sent it."""
39
+
40
+ name: str
41
+ release: int
42
+ sha256: str
43
+ bytes: int
44
+ author: str
45
+ note: str
46
+ created_at: float
47
+
48
+
49
+ class ReleaseList(WireModel):
50
+ """GET /v1/hosted/{name}/releases: the app's releases, newest first."""
51
+
52
+ releases: list[ReleaseRow]
53
+
54
+
55
+ class RollbackRequest(WireModel):
56
+ """POST /v1/hosted/{name}/rollback: which release's sources go again, as the next release."""
57
+
58
+ release: int = Field(ge=1)
59
+
60
+
61
+ class AppLogsResponse(WireModel):
62
+ """GET /v1/hosted/{name}/logs: the last lines the runner read, and which process, and when."""
63
+
64
+ name: str
65
+ host: str | None
66
+ lines: str
67
+ # Null before the runner has sent any: asking is what makes it send them.
68
+ at: float | None
69
+
70
+
71
+ class ServedRow(WireModel):
72
+ """The time one app served on one UTC day."""
73
+
74
+ org: str
75
+ env: Env
76
+ name: str
77
+ day: str
78
+ seconds: float
79
+
80
+
81
+ class ServedPage(WireModel):
82
+ """GET /v1/hosted/usage and GET /v1/ops/hosted-usage: the time apps served, per day."""
83
+
84
+ since: str
85
+ until: str
86
+ rows: list[ServedRow]
87
+
88
+
89
+ class PutSecretRequest(WireModel):
90
+ """PUT /v1/secrets/{name}, the body: the value, which no door ever answers back."""
91
+
92
+ value: str
93
+
94
+
95
+ class SecretRow(WireModel):
96
+ """One of the org's secrets as the list shows it: never its value."""
97
+
98
+ name: str
99
+ set_by: str
100
+ set_at: float
101
+
102
+
103
+ class SecretList(WireModel):
104
+ """GET /v1/secrets: the org's secrets in the world, by name."""
105
+
106
+ secrets: list[SecretRow]
107
+
108
+
109
+ # ── the runner ──
110
+
111
+
112
+ class RunnerReport(WireModel):
113
+ """What the runner says of one app: the host it answers for went live, or failed and why."""
114
+
115
+ org: str
116
+ name: str
117
+ host: str
118
+ state: Literal["live", "failed"]
119
+ why: str = Field(default="", max_length=2000)
120
+
121
+
122
+ class AppLogs(WireModel):
123
+ """The last lines of one app's process, as the runner read them from its container."""
124
+
125
+ org: str
126
+ name: str
127
+ host: str
128
+ lines: str = Field(max_length=256 * 1024)
129
+
130
+
131
+ class RunnerHeartbeatRequest(WireModel):
132
+ """POST /v1/runner/heartbeat: the runner's name, and what changed since its last."""
133
+
134
+ runner: str
135
+ reports: list[RunnerReport] = Field(default_factory=list[RunnerReport])
136
+ logs: list[AppLogs] = Field(default_factory=list[AppLogs])
137
+
138
+
139
+ class WantedApp(WireModel):
140
+ """One app the runner is to have running: a release, under the host name its process takes."""
141
+
142
+ model_config = READ_BY_AN_OLDER_RUNNER
143
+
144
+ org: str
145
+ name: str
146
+ release: int
147
+ sha256: str
148
+ host: str
149
+ # Whether an app socket of the org says it runs on that host: the release is serving.
150
+ registered: bool
151
+ # Whether this host was already reported failed: the runner leaves it alone.
152
+ failed: bool
153
+ # Whether a person asked for its logs lately: the runner sends them with its next beat.
154
+ logs_wanted: bool = False
155
+ # The host that last went live: it serves until the one wanted registers, and is the one
156
+ # started again when its process exits.
157
+ live_host: str | None = None
158
+
159
+
160
+ class RunnerHeartbeatResponse(WireModel):
161
+ """The answer to a heartbeat: the runner's world, and every app of it that has a release."""
162
+
163
+ model_config = READ_BY_AN_OLDER_RUNNER
164
+
165
+ world: Env
166
+ apps: list[WantedApp]
167
+
168
+
169
+ class AppEnvironment(WireModel):
170
+ """GET /v1/runner/apps/{org}/{name}/environment: what the app's process is started with."""
171
+
172
+ model_config = READ_BY_AN_OLDER_RUNNER
173
+
174
+ environment: dict[str, str]