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,512 @@
1
+ """The account doors' bodies: sign-in, keys, people, sign-up, SSO, mail, who a key is, policy."""
2
+
3
+ from datetime import datetime
4
+ from typing import Annotated, Literal, Self
5
+
6
+ from pydantic import AwareDatetime, Field, model_validator
7
+
8
+ from pinecall.domain.names import Env
9
+ from pinecall.domain.person import Key, Member, MemberStatus, Role
10
+ from pinecall.wire.frames import WireModel
11
+
12
+ # ── people ──
13
+
14
+
15
+ class MemberRow(WireModel):
16
+ """A member as every members door answers one: never a password nor a link."""
17
+
18
+ id: str
19
+ email: str
20
+ name: str
21
+ role: Role
22
+ agents: list[str]
23
+ status: MemberStatus
24
+ scopes: list[str]
25
+ operator: bool
26
+ # The production switch, or being an admin.
27
+ production: bool
28
+ verified: bool
29
+
30
+ @classmethod
31
+ def of(cls, member: Member) -> Self:
32
+ """The member as a row."""
33
+ return cls(
34
+ id=member.id,
35
+ email=member.email,
36
+ name=member.name,
37
+ role=member.role,
38
+ agents=sorted(member.agents),
39
+ status=member.status,
40
+ scopes=sorted(member.scopes),
41
+ operator=member.operator,
42
+ production=member.opens_production,
43
+ verified=member.verified,
44
+ )
45
+
46
+
47
+ class MemberList(WireModel):
48
+ """GET /v1/members, the answer: every member of the org, oldest first."""
49
+
50
+ members: list[MemberRow]
51
+
52
+
53
+ class InviteMemberRequest(WireModel):
54
+ """POST /v1/members: who, the role, the agents they see (none is every one), production."""
55
+
56
+ email: str
57
+ name: str
58
+ role: str
59
+ agents: list[str] = Field(default_factory=list[str])
60
+ production: bool = False
61
+
62
+
63
+ class InvitationResponse(WireModel):
64
+ """An invitation or a reset: the member, the link's token once (null when only mailed)."""
65
+
66
+ member: MemberRow
67
+ token: str | None
68
+ expires_at: datetime | None
69
+ mailed: bool
70
+ # The card the token opens, on the box's public name: what a terminal prints, a letter carries.
71
+ link: str | None = None
72
+
73
+
74
+ class ChangeMemberRequest(WireModel):
75
+ """PATCH /v1/members/{id}: what is named changes, what is left out stays."""
76
+
77
+ role: str | None = None
78
+ agents: list[str] | None = None
79
+ status: str | None = None
80
+ production: bool | None = None
81
+
82
+
83
+ class AcceptInvitationRequest(WireModel):
84
+ """POST /v1/invitations/{token}: the password chosen, and what the first key is called."""
85
+
86
+ password: str
87
+ device: str | None = None
88
+
89
+
90
+ # ── keys ──
91
+
92
+
93
+ class KeyIssuedResponse(WireModel):
94
+ """Every door that mints a key: the key this once, and what it was written under."""
95
+
96
+ key: str
97
+ key_id: str
98
+ org: str
99
+ label: str | None
100
+ env: Env
101
+ scopes: list[str]
102
+ subject: str | None
103
+ name: str | None
104
+
105
+ @classmethod
106
+ def of(cls, key: Key, secret: str) -> Self:
107
+ """The key just minted, and its secret this once."""
108
+ return cls(
109
+ key=secret,
110
+ key_id=key.key_id,
111
+ org=key.org,
112
+ label=key.label,
113
+ env=key.env,
114
+ scopes=sorted(key.scopes),
115
+ subject=key.subject,
116
+ name=key.name,
117
+ )
118
+
119
+
120
+ class FirstKeyResponse(WireModel):
121
+ """POST /v1/invitations/{token}: the member's first key, and the member now active."""
122
+
123
+ key: str
124
+ key_id: str
125
+ org: str
126
+ label: str | None
127
+ env: Env
128
+ scopes: list[str]
129
+ subject: str | None
130
+ name: str | None
131
+ member: MemberRow
132
+
133
+
134
+ class OrgMadeResponse(WireModel):
135
+ """POST /v1/signup/verify: the admin's key, the org's slug, and the code for the console."""
136
+
137
+ key: str
138
+ key_id: str
139
+ org: str
140
+ label: str | None
141
+ env: Env
142
+ scopes: list[str]
143
+ subject: str | None
144
+ name: str | None
145
+ slug: str
146
+ member: MemberRow
147
+ code: str
148
+ code_expires_at: float
149
+
150
+
151
+ class CreateKeyRequest(WireModel):
152
+ """POST /v1/keys: a server's token, named, for one world; fewer scopes, and an end, if asked."""
153
+
154
+ label: str
155
+ env: str
156
+ # None is every scope a server's token opens; a list is some of them.
157
+ scopes: list[str] | None = None
158
+ # None never expires.
159
+ expires_at: AwareDatetime | None = None
160
+
161
+
162
+ class KeyRow(WireModel):
163
+ """GET /v1/keys, one row: a key by its fingerprint, never the key."""
164
+
165
+ fingerprint: str
166
+ label: str | None
167
+ kind: Literal["person", "server"]
168
+ # Only a server's token is bound to one world.
169
+ env: Env | None
170
+ name: str | None
171
+ created_by: str | None
172
+ created_at: datetime
173
+ last_used_at: datetime | None
174
+ revoked_at: datetime | None
175
+ scopes: list[str]
176
+ expires_at: datetime | None = None
177
+
178
+
179
+ class RevokeKeyResponse(WireModel):
180
+ """POST /v1/keys/{fingerprint}/revoke, the answer."""
181
+
182
+ fingerprint: str
183
+ revoked: bool
184
+
185
+
186
+ # ── who a key is ──
187
+
188
+
189
+ class WhoamiResponse(WireModel):
190
+ """GET /v1/whoami: whose key this is and what it opens; never the key nor its hash."""
191
+
192
+ org: str
193
+ # None only when the org's row is gone.
194
+ slug: str | None
195
+ key_id: str
196
+ label: str | None
197
+ # The world this request acts in.
198
+ env: Env
199
+ scopes: list[str]
200
+ subject: str | None
201
+ name: str | None
202
+ # The one name a person carries into every org; None for a server's token.
203
+ email: str | None
204
+ operator: bool
205
+ # A person of the box inside an org they are no member of.
206
+ visiting: bool
207
+ production: bool
208
+
209
+
210
+ class BoxIdentityResponse(WireModel):
211
+ """GET /v1/ops/whoami: the box an operator's key opens, and who holds the key."""
212
+
213
+ operator: bool
214
+ version: str
215
+ domain: str | None
216
+ # None for the box's own key.
217
+ name: str | None
218
+ org: str | None
219
+
220
+
221
+ class BrandRow(WireModel):
222
+ """What the box's pages and letters are called and painted with."""
223
+
224
+ name: str
225
+ logo_url: str | None
226
+ accent: str
227
+
228
+
229
+ class GatewayInfoResponse(WireModel):
230
+ """GET /.well-known/pinecall: what a sign-in page reads before anybody holds a key."""
231
+
232
+ version: str
233
+ signup: bool
234
+ # 0 is no rule.
235
+ min_password: int
236
+ # Whether the box itself can post a letter; an org's own mailbox is not counted.
237
+ mail: bool
238
+ brand: BrandRow
239
+ google: bool
240
+
241
+
242
+ # ── signing in ──
243
+
244
+
245
+ class SignInRequest(WireModel):
246
+ """POST /v1/login: an address and a password, the org when there are several, or a code."""
247
+
248
+ org: str | None = None
249
+ email: str | None = None
250
+ password: str | None = None
251
+ code: str | None = None
252
+ # What the minted key is called in the org's list, so it is revoked on its own.
253
+ device: str | None = None
254
+
255
+
256
+ class SignInOrgsRequest(WireModel):
257
+ """POST /v1/login/orgs: the address and the password, to list what they open."""
258
+
259
+ email: str
260
+ password: str
261
+
262
+
263
+ class SignInOrgRow(WireModel):
264
+ """One org a password opens, and the person's role there."""
265
+
266
+ org: str
267
+ slug: str
268
+ name: str
269
+ role: Role
270
+
271
+
272
+ class SignInOrgsResponse(WireModel):
273
+ """POST /v1/login/orgs, the answer: the orgs, oldest first."""
274
+
275
+ orgs: list[SignInOrgRow]
276
+
277
+
278
+ class OrgOfPersonRow(WireModel):
279
+ """GET /v1/login/orgs, one org: the role there, and whether this key is the one opening it."""
280
+
281
+ org: str
282
+ slug: str | None
283
+ name: str | None
284
+ # A role, or "operator" for an org of the box a person runs and is no member of.
285
+ role: str
286
+ status: MemberStatus
287
+ here: bool
288
+ member: bool
289
+
290
+
291
+ class OrgsOfPersonResponse(WireModel):
292
+ """GET /v1/login/orgs, the answer: every org the key's person may open, oldest first."""
293
+
294
+ orgs: list[OrgOfPersonRow]
295
+
296
+
297
+ class SwitchOrgRequest(WireModel):
298
+ """POST /v1/login/org: the org to be signed into, by id or slug."""
299
+
300
+ org: str
301
+
302
+
303
+ class MintCodeResponse(WireModel):
304
+ """A one-use word and when it dies, in epoch seconds: a login code, a terminal's pairing."""
305
+
306
+ code: str
307
+ expires_at: float
308
+
309
+
310
+ class ForgotPasswordRequest(WireModel):
311
+ """POST /v1/login/reset: the address whose password was forgotten."""
312
+
313
+ email: str
314
+
315
+
316
+ class EmptyResponse(WireModel):
317
+ """An answer whose status says everything: `{}`."""
318
+
319
+
320
+ class OpenPairingRequest(WireModel):
321
+ """POST /v1/login/pairings: what the terminal calls itself."""
322
+
323
+ device: str | None = None
324
+
325
+
326
+ class PairingStatusResponse(WireModel):
327
+ """GET /v1/login/pairings/{code}: which terminal a browser is about to sign in."""
328
+
329
+ device: str | None
330
+ expires_at: float
331
+ answered: bool
332
+
333
+
334
+ class PairingApprovedResponse(WireModel):
335
+ """POST /v1/login/pairings/{code}: the terminal signed in, and into which org."""
336
+
337
+ device: str | None
338
+ org: str
339
+ # What the page says it signed in to: the org's name, as a person knows it.
340
+ org_name: str
341
+
342
+
343
+ class KeyCollectedResponse(WireModel):
344
+ """GET /v1/login/pairings/{code}/key: the terminal's key, once."""
345
+
346
+ key: str
347
+
348
+
349
+ # ── sign-up ──
350
+
351
+
352
+ class SignupRequest(WireModel):
353
+ """POST /v1/signup: the org's slug and name, the person, and their password."""
354
+
355
+ org: str
356
+ name: str | None = None
357
+ email: str
358
+ person: str
359
+ password: str
360
+ device: str | None = None
361
+
362
+
363
+ class CodeMailedResponse(WireModel):
364
+ """POST /v1/signup, the answer: where the code went and when it dies; never the code."""
365
+
366
+ email: str
367
+ code_expires_at: float
368
+
369
+
370
+ class VerifySignupRequest(WireModel):
371
+ """POST /v1/signup/verify: the address and the six digits mailed to it."""
372
+
373
+ email: str
374
+ code: str
375
+ device: str | None = None
376
+
377
+
378
+ class ResendCodeRequest(WireModel):
379
+ """POST /v1/signup/resend: the address a sign-up is waiting on."""
380
+
381
+ email: str
382
+
383
+
384
+ # ── the org's identity provider ──
385
+
386
+
387
+ class OrgSsoRequest(WireModel):
388
+ """PUT /v1/org/sso: the provider whole, its client secret included every time."""
389
+
390
+ issuer: str
391
+ client_id: str
392
+ client_secret: str
393
+ domains: list[str]
394
+ # Who the provider seats unasked; None seats nobody it was not told to.
395
+ role: str | None = None
396
+ required: bool = False
397
+
398
+
399
+ class SsoDomainProof(WireModel):
400
+ """One domain of the org's SSO: the TXT record that proves it the org's, and whether seen."""
401
+
402
+ domain: str
403
+ txt: str
404
+ verified: bool
405
+
406
+
407
+ class OrgSsoResponse(WireModel):
408
+ """GET /v1/org/sso: the org's provider, never its secret, and the URI to register there."""
409
+
410
+ configured: bool
411
+ issuer: str | None
412
+ client_id: str | None
413
+ domains: list[str]
414
+ role: Role | None
415
+ required: bool
416
+ redirect_uri: str
417
+ # Each domain's proof: discovery offers the provider for a verified domain alone.
418
+ proofs: list[SsoDomainProof] = Field(default_factory=list[SsoDomainProof])
419
+
420
+
421
+ class DiscoverSsoRequest(WireModel):
422
+ """POST /v1/login/sso/discover: the address whose domain is asked about."""
423
+
424
+ email: str
425
+
426
+
427
+ class SsoOrgRow(WireModel):
428
+ """An org that signs in an address's domain with its provider."""
429
+
430
+ org: str
431
+ slug: str
432
+ name: str
433
+
434
+
435
+ class DiscoverSsoResponse(WireModel):
436
+ """POST /v1/login/sso/discover, the answer: the orgs, oldest first."""
437
+
438
+ orgs: list[SsoOrgRow]
439
+
440
+
441
+ # ── the org's mailbox ──
442
+
443
+
444
+ class OrgMailRequest(WireModel):
445
+ """PUT /v1/org/mail: the org's SMTP account, its password included every time."""
446
+
447
+ host: str
448
+ port: int
449
+ security: str = "starttls"
450
+ # Empty for a relay that asks for nothing.
451
+ username: str = ""
452
+ password: str = ""
453
+ sender: str = Field(alias="from")
454
+
455
+
456
+ class OrgMailResponse(WireModel):
457
+ """GET /v1/org/mail: the org's mailbox, never its password, and how its last letter went."""
458
+
459
+ configured: bool
460
+ host: str | None
461
+ port: int | None
462
+ security: str | None
463
+ username: str | None
464
+ sender: str | None = Field(alias="from")
465
+ verified_at: datetime | None
466
+ last_error: str | None
467
+
468
+
469
+ class SendTestLetterRequest(WireModel):
470
+ """POST /v1/org/mail/test: who the test letter goes to."""
471
+
472
+ to: str
473
+
474
+
475
+ class SendTestLetterResponse(WireModel):
476
+ """POST /v1/org/mail/test, the answer: whether it went, and what the server said if not."""
477
+
478
+ sent: bool
479
+ error: str | None
480
+
481
+
482
+ class CallingHours(WireModel):
483
+ """The hours of the called number's own day a call may ring: from, until (exclusive)."""
484
+
485
+ from_: Annotated[int, Field(ge=0, le=23)] = Field(alias="from")
486
+ until: Annotated[int, Field(ge=1, le=24)]
487
+
488
+ @model_validator(mode="after")
489
+ def _a_window(self) -> Self:
490
+ if self.from_ >= self.until:
491
+ raise ValueError(f"calling hours from {self.from_} until {self.until} leave no hour")
492
+ return self
493
+
494
+
495
+ class OrgPolicy(WireModel):
496
+ """GET and PUT /v1/org/policy: the org's compliance settings, replaced whole."""
497
+
498
+ retention_days: Annotated[int, Field(gt=0)] | None = None
499
+ calling_hours: CallingHours | None = None
500
+ per_number_day: Annotated[int, Field(gt=0)] | None = None
501
+ consent_everywhere: bool = False
502
+ # An outbound call's opening sentence: null is the platform's, "" is none.
503
+ disclosure: Annotated[str, Field(max_length=500)] | None = None
504
+ recording_notice: bool = True
505
+
506
+
507
+ class OrgPolicyRow(WireModel):
508
+ """GET /v1/org/policy: the settings, who set them last, and when."""
509
+
510
+ policy: OrgPolicy
511
+ set_by: str | None
512
+ set_at: float | None
@@ -0,0 +1,172 @@
1
+ """The bodies of the agent doors: agents held, sockets, the line, judging, pipeline, widget."""
2
+
3
+ from typing import Literal
4
+
5
+ from pinecall.domain.names import Channel, Env
6
+ from pinecall.wire.frames import WireModel
7
+ from pinecall.wire.parts import GreetingConfig, WidgetTheme
8
+ from pinecall.wire.rest.numbers import LegTrunk
9
+ from pinecall.wire.rest.providers import ProviderRow
10
+
11
+ # ── the agents an org holds, and whose terminal a ring lands in ──
12
+
13
+
14
+ class ScopeHolder(WireModel):
15
+ """A scope by its member id and email; both null for the org's own."""
16
+
17
+ holder: str | None
18
+ name: str | None
19
+
20
+
21
+ class AgentRow(WireModel):
22
+ """An agent some socket of the org holds, the doors it answers, and whose scope."""
23
+
24
+ slug: str
25
+ channels: list[Channel]
26
+ holder: ScopeHolder | None = None
27
+
28
+
29
+ class AgentList(WireModel):
30
+ """The org's held agents in the request's world."""
31
+
32
+ agents: list[AgentRow]
33
+
34
+
35
+ class AppRow(WireModel):
36
+ """One connected app socket and what it holds."""
37
+
38
+ app: str
39
+ agents: list[str]
40
+ env: Env
41
+ host: str | None
42
+ address: str | None
43
+ sdk: str | None
44
+ holder: ScopeHolder | None
45
+ connected_at: float
46
+
47
+
48
+ class AppList(WireModel):
49
+ """The app sockets of the org this key may see."""
50
+
51
+ apps: list[AppRow]
52
+
53
+
54
+ class StopAppResponse(WireModel):
55
+ """A stop answered."""
56
+
57
+ app: str
58
+ stopped: bool
59
+
60
+
61
+ class RingTarget(WireModel):
62
+ """Whose terminal a ring at the agent lands in, and who else could take it."""
63
+
64
+ agent: str
65
+ env: Env
66
+ held: bool
67
+ holding: ScopeHolder | None = None
68
+ yours: bool
69
+ waiting: list[ScopeHolder]
70
+ calling: list[str]
71
+
72
+
73
+ class DeveloperPhones(WireModel):
74
+ """The phone a developer calls from."""
75
+
76
+ number: str
77
+
78
+
79
+ class TestNumber(WireModel):
80
+ """A production number and the agent it reaches."""
81
+
82
+ number: str
83
+ agent: str
84
+
85
+
86
+ class TestNumbers(WireModel):
87
+ """The person's own phones, and the production numbers they can dial to test."""
88
+
89
+ calling: list[str]
90
+ numbers: list[TestNumber]
91
+
92
+
93
+ class RingHandoff(WireModel):
94
+ """Where a production ring goes: a developer's scope and its fleet, or nowhere (null)."""
95
+
96
+ holder: str | None = None
97
+ fleet: str | None = None
98
+ # Where the sandbox has a LiveKit of its own: the ring is dialled to its SIP through this.
99
+ trunk: LegTrunk | None = None
100
+
101
+
102
+ class JudgingSettings(WireModel):
103
+ """Whether an org's calls are judged at hang-up, and the ceiling per call."""
104
+
105
+ on: bool
106
+ ceiling_usd: float | None
107
+
108
+
109
+ class JudgingRequest(WireModel):
110
+ """JudgingSettings on or off."""
111
+
112
+ on: bool
113
+
114
+
115
+ class HoldAudio(WireModel):
116
+ """What a caller hears while a tool runs: the box's melody, silence, or the org's own clip."""
117
+
118
+ played: Literal["default", "off", "custom"]
119
+ sha256: str | None = None
120
+ seconds: float | None = None
121
+ name: str | None = None
122
+
123
+
124
+ class PlayedRequest(WireModel):
125
+ """PUT /v1/agents/{slug}/pipeline/hold-audio/played: the box's melody back, or silence."""
126
+
127
+ played: Literal["default", "off"]
128
+
129
+
130
+ class PipelineStage(WireModel):
131
+ """One stage as the next call would run it: the vendor, its model, the voice or language."""
132
+
133
+ vendor: str
134
+ model: str | None = None
135
+ voice_id: str | None = None
136
+ language: str | None = None
137
+
138
+
139
+ class Measured(WireModel):
140
+ """One latency over the agent's recent calls: livekit's name, the median, the turns."""
141
+
142
+ name: str
143
+ seconds: float
144
+ turns: int
145
+
146
+
147
+ class PipelineReport(WireModel):
148
+ """GET /v1/agents/{slug}/pipeline: what it hears, decides and speaks with, and how fast."""
149
+
150
+ agent: str
151
+ hears: PipelineStage
152
+ decides: PipelineStage
153
+ speaks: PipelineStage
154
+ greeting: GreetingConfig | None
155
+ voices: list[str]
156
+ providers: list[ProviderRow]
157
+ defaults: dict[str, str]
158
+ models: dict[str, list[str]]
159
+ calls: int
160
+ medians: list[Measured]
161
+ unavailable_reasons: dict[str, str]
162
+
163
+
164
+ class WidgetSettings(WireModel):
165
+ """GET and PUT /v1/agents/{slug}/widget: how the widget presents the agent, per world."""
166
+
167
+ title: str | None = None
168
+ tagline: str | None = None
169
+ greeting: str | None = None
170
+ accent: str | None = None
171
+ autostart: bool = False
172
+ theme: WidgetTheme | None = None