pinecall-protocol 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,344 @@
1
+ """Generated from schema/rest.json: the envelopes the read doors answer in."""
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import Field
6
+
7
+ from pinecall_protocol._base import WireModel
8
+ from pinecall_protocol.defs import (
9
+ Channel,
10
+ Contact,
11
+ Cost,
12
+ Direction,
13
+ EndReason,
14
+ Env,
15
+ KnowledgeFile,
16
+ PlatformTool,
17
+ )
18
+ from pinecall_protocol.envelope import Entry
19
+ from pinecall_protocol.state import CallStatus, State
20
+
21
+
22
+ class CallState(WireModel):
23
+ """GET /v1/calls/{call}/state: the whole log folded, and the seq a stream resumes from."""
24
+
25
+ state: State
26
+ last_seq: int
27
+ live: bool
28
+
29
+
30
+ class LogPage(WireModel):
31
+ """One page of a log: what this reader may see, whether the log is open, and where to resume."""
32
+
33
+ entries: list[Entry]
34
+ live: bool
35
+ next: int | None
36
+
37
+
38
+ class SessionLine(WireModel):
39
+ """One call as a list draws it: which call, how far the log got, and the state's own fields."""
40
+
41
+ call: str
42
+ agent: str
43
+ live: bool
44
+ last_seq: int
45
+ status: CallStatus
46
+ channel: Channel | None
47
+ direction: Direction | None
48
+ from_: str | None = Field(alias="from")
49
+ to: str | None
50
+ caller: Contact | None
51
+ started_at: float | None
52
+ ended_at: float | None
53
+ end_reason: EndReason | None
54
+ outcome: str | None
55
+ cost: Cost | None
56
+
57
+
58
+ class SessionList(WireModel):
59
+ """GET /v1/agents/{slug}/sessions: which calls that agent handled, newest first."""
60
+
61
+ calls: list[SessionLine]
62
+
63
+
64
+ # One corner of a world holding an agent: the member whose it is, named so a person can read it.
65
+ class LineHolder(WireModel):
66
+ """One corner of a world holding an agent."""
67
+
68
+ holder: str | None
69
+ name: str | None
70
+
71
+
72
+ class HeldAgent(WireModel):
73
+ """One agent, as somebody choosing which to open needs to see it: its name and its channels."""
74
+
75
+ slug: str
76
+ channels: list[Channel]
77
+ holder: LineHolder | None = None
78
+
79
+
80
+ class AgentList(WireModel):
81
+ """GET /v1/agents: every agent this fleet is holding right now, as the front page lists them."""
82
+
83
+ agents: list[HeldAgent]
84
+
85
+
86
+ # GET /v1/agents/{slug}/line: whose terminal a call that RINGS at this agent's doors lands in. An
87
+ # org shares one sandbox number, so it rings in one place and which one is claimed.
88
+ class TheLine(WireModel):
89
+ """GET /v1/agents/{slug}/line."""
90
+
91
+ agent: str
92
+ env: Env
93
+ held: bool
94
+ holding: LineHolder | None = None
95
+ yours: bool
96
+ waiting: list[LineHolder]
97
+ calling: list[str]
98
+
99
+
100
+ # The base is replaced, never merged.
101
+ class KnowledgePush(WireModel):
102
+ """PUT /v1/knowledge/{base}, the body: the tenant's folder as of now, sent whole."""
103
+
104
+ files: list[KnowledgeFile]
105
+
106
+
107
+ # A chunk answers when its file and heading path start with `expects`, so naming a file alone
108
+ # accepts any chunk of it and naming a heading accepts that section.
109
+ class GoldenQuestion(WireModel):
110
+ """One question of a base's golden: what somebody asks, and the chunk that should answer it."""
111
+
112
+ asks: str
113
+ expects: str
114
+
115
+
116
+ # A golden is fixed and the index is the variable; a question is never softened so a change can
117
+ # pass.
118
+ class KnowledgeGolden(WireModel):
119
+ """POST /v1/knowledge/{base}/eval, the body: the questions a base is held to."""
120
+
121
+ questions: list[GoldenQuestion]
122
+ k: int | None = None
123
+
124
+
125
+ # One question whose expected chunk was not among the k returned, and what came back instead.
126
+ class GoldenMiss(WireModel):
127
+ """GoldenMiss, as protocol/schema declares it."""
128
+
129
+ asks: str
130
+ expects: str
131
+ found: list[str]
132
+
133
+
134
+ # Both figures are computed by code, with no model, so two runs of the same golden over the same
135
+ # base answer the same numbers.
136
+ class KnowledgeScore(WireModel):
137
+ """POST /v1/knowledge/{base}/eval, the answer: how the index did on its own golden."""
138
+
139
+ base: str
140
+ model: str
141
+ questions: int
142
+ k: int
143
+ recall_at_k: float
144
+ ndcg_at_10: float
145
+ took_ms: float
146
+ misses: list[GoldenMiss]
147
+
148
+
149
+ # PUT /v1/knowledge/{base}, the answer: which base, how many chunks it became, and how long that
150
+ # took.
151
+ class KnowledgePushed(WireModel):
152
+ """PUT /v1/knowledge/{base}, the answer."""
153
+
154
+ base: str
155
+ chunks: int
156
+ took_ms: float
157
+
158
+
159
+ class KnowledgeBase(WireModel):
160
+ """One knowledge base as the list draws it: its name, its size, and when it was last pushed."""
161
+
162
+ base: str
163
+ chunks: int
164
+ model: str
165
+ pushed_at: float
166
+
167
+
168
+ class KnowledgeList(WireModel):
169
+ """GET /v1/knowledge: every base this org has pushed."""
170
+
171
+ bases: list[KnowledgeBase]
172
+
173
+
174
+ # A fact is never deleted, only superseded, so the history keeps every version.
175
+ class ContactFact(WireModel):
176
+ """One fact of a contact's history: a MemoryFact with the two dates that bound it."""
177
+
178
+ id: str | None = None
179
+ text: str
180
+ category: str | None = None
181
+ source: str | None = None
182
+ valid_from: float
183
+ invalidated_at: float | None
184
+
185
+
186
+ # GET /v1/contacts/{contact}/memory: everything memory ever kept about one contact, current facts
187
+ # first.
188
+ class ContactMemory(WireModel):
189
+ """GET /v1/contacts/{contact}/memory."""
190
+
191
+ facts: list[ContactFact]
192
+
193
+
194
+ # DELETE /v1/contacts/{contact}/memory, the answer: how many facts the right to be forgotten erased.
195
+ class Forgotten(WireModel):
196
+ """DELETE /v1/contacts/{contact}/memory, the answer."""
197
+
198
+ forgotten: int
199
+
200
+
201
+ # One question of a memory golden: what memory holds about the contact who asks it, the words they
202
+ # just said, and the fact or facts that should come back. The facts are the question's own, so a
203
+ # golden needs no contact in any table.
204
+ class MemoryQuestion(WireModel):
205
+ """One question of a memory golden."""
206
+
207
+ holds: list[str]
208
+ asks: str
209
+ expects: list[str]
210
+
211
+
212
+ # Each question brings its own facts, which are written to a scratch contact of this org, asked, and
213
+ # deleted. A golden is fixed and the ranking is the variable; a question is never softened so a
214
+ # change can pass.
215
+ class MemoryGolden(WireModel):
216
+ """POST /v1/contacts/memory/eval, the body: the questions memory is held to."""
217
+
218
+ questions: list[MemoryQuestion]
219
+ k: int | None = None
220
+
221
+
222
+ # One question memory did not answer whole: what it wanted and did not get, and what came back
223
+ # instead. GoldenMiss is the knowledge base's and names the one chunk that should have won; a memory
224
+ # question may expect several facts and miss some of them.
225
+ class MemoryMiss(WireModel):
226
+ """One question memory did not answer whole."""
227
+
228
+ asks: str
229
+ missing: list[str]
230
+ found: list[str]
231
+
232
+
233
+ # POST /v1/contacts/memory/eval, the answer: how memory ranked the facts its own golden asked for.
234
+ # Both figures are computed by code, with no model in the loop, so two runs of one golden answer the
235
+ # same numbers.
236
+ class MemoryScore(WireModel):
237
+ """POST /v1/contacts/memory/eval, the answer."""
238
+
239
+ model: str
240
+ questions: int
241
+ k: int
242
+ recall_at_k: float
243
+ ndcg_at_10: float
244
+ took_ms: float
245
+ misses: list[MemoryMiss]
246
+
247
+
248
+ # What must come of one call's hang-up: each field is one of the four ways a hang-up costs a
249
+ # business. Nothing here compares one sentence to another — a category is the class's own word, a
250
+ # value is a literal the caller said out loud, and a supersession is an id — because two ways of
251
+ # writing one fact are one fact.
252
+ class ExtractionExpected(WireModel):
253
+ """What must come of one call's hang-up."""
254
+
255
+ writes: list[str] = Field(default_factory=list[str])
256
+ never: list[str] = Field(default_factory=list[str])
257
+ never_says: list[str] = Field(default_factory=list[str])
258
+ invalidates: list[str] = Field(default_factory=list[str])
259
+
260
+
261
+ # One call written down and what memory must make of it: the write side of the table, judged by
262
+ # code. The read side is MemoryGolden and neither answers for the other — a call may extract the
263
+ # perfect fact and never see it again, because six is what a turn is handed and the seventh is cut.
264
+ class ExtractionGolden(WireModel):
265
+ """One call written down and what memory must make of it."""
266
+
267
+ name: str
268
+ said: list[tuple[str, str]]
269
+ holds: list[str] = Field(default_factory=list[str])
270
+ plants: list[str] = Field(default_factory=list[str])
271
+ channel: Channel = "phone"
272
+ expect: ExtractionExpected = Field(default_factory=ExtractionExpected)
273
+
274
+
275
+ # POST /v1/agents/{slug}/memory/extraction, the body: the goldens whole, as the tenant wrote them
276
+ # down. Each costs ONE model call — the very one a hang-up makes — run on the org's own model and
277
+ # keys against the class the caller is holding.
278
+ class ExtractionCases(WireModel):
279
+ """POST /v1/agents/{slug}/memory/extraction, the body."""
280
+
281
+ cases: list[ExtractionGolden]
282
+
283
+
284
+ # One thing that did not hold about a case: which of the questions, and the evidence in a sentence a
285
+ # person can act on.
286
+ class ExtractionBroke(WireModel):
287
+ """One thing that did not hold about a case."""
288
+
289
+ check: str
290
+ detail: str
291
+
292
+
293
+ class ExtractionJudged(WireModel):
294
+ """One case, run: what memory would have kept, what admission refused, and what did not hold."""
295
+
296
+ name: str
297
+ held: bool
298
+ wrote: list[str] = Field(default_factory=list[str])
299
+ refused: list[str] = Field(default_factory=list[str])
300
+ broke: list[ExtractionBroke] = Field(default_factory=list[ExtractionBroke])
301
+
302
+
303
+ # POST /v1/agents/{slug}/memory/extraction, the answer: which model answered, how many cases held,
304
+ # and every one of them. A verb prints this and a pipeline exits on it.
305
+ class ExtractionRun(WireModel):
306
+ """POST /v1/agents/{slug}/memory/extraction, the answer."""
307
+
308
+ agent: str
309
+ model: str
310
+ cases: int
311
+ held: int
312
+ took_ms: float
313
+ results: list[ExtractionJudged]
314
+
315
+
316
+ # POST /v1/calls/{call}/lookup, the body: which platform tool to run for this turn, and what to run
317
+ # it with. Worker-only; the gateway runs it against its own stores and writes memory.ops or
318
+ # docs.sources on the call's log itself.
319
+ class LookupRequest(WireModel):
320
+ """POST /v1/calls/{call}/lookup, the body."""
321
+
322
+ tool: PlatformTool
323
+ input: dict[str, Any]
324
+ speech_id: str | None = None
325
+
326
+
327
+ # POST /v1/calls/{call}/lookup, the answer: what the tool found, and how long finding it took. The
328
+ # output is JSON-encoded into a tool_result block, which is where everything from outside the
329
+ # conversation goes and the only place it goes.
330
+ class LookupResult(WireModel):
331
+ """POST /v1/calls/{call}/lookup, the answer."""
332
+
333
+ output: dict[str, Any]
334
+ took_ms: float
335
+
336
+
337
+ # POST /v1/calls/{call}/remember, the answer: what the call taught about the contact, counted.
338
+ # Worker-only; the body is empty, the gateway reads the turns off its own log and writes memory.ops
339
+ # itself.
340
+ class Remembered(WireModel):
341
+ """POST /v1/calls/{call}/remember, the answer."""
342
+
343
+ ops: int
344
+ took_ms: float
@@ -0,0 +1,83 @@
1
+ """Generated from schema/room.json: the room's facts and the outside world's."""
2
+
3
+ from typing import Any
4
+
5
+ from pinecall_protocol._base import WireModel
6
+ from pinecall_protocol.defs import Channel, EventSource, ParticipantKind, TrackKind, TrackSource
7
+
8
+
9
+ # Phone and web calls have one; a text session has no room and never logs this.
10
+ class RoomOpened(WireModel):
11
+ """The LiveKit room exists and the call lives in it."""
12
+
13
+ name: str
14
+ sid: str
15
+ channel: Channel
16
+
17
+
18
+ # Somebody joined the room: the caller over SIP or the widget, the agent, a supervisor, a listener,
19
+ # or a second SIP leg. Their attributes travel verbatim: the caller's number is a fact of the room,
20
+ # not a field we invent.
21
+ class ParticipantJoined(WireModel):
22
+ """Somebody joined the room."""
23
+
24
+ identity: str
25
+ kind: ParticipantKind
26
+ name: str | None = None
27
+ attributes: dict[str, Any]
28
+
29
+
30
+ # When it is the caller, call.ended follows.
31
+ class ParticipantLeft(WireModel):
32
+ """Somebody left the room."""
33
+
34
+ identity: str
35
+ reason: str
36
+
37
+
38
+ # Ephemeral: it is a light for the console, and the turns say who spoke.
39
+ class ParticipantSpeaking(WireModel):
40
+ """The room's own voice activity for one participant flipped."""
41
+
42
+ identity: str
43
+ speaking: bool
44
+
45
+
46
+ class TrackPublished(WireModel):
47
+ """A participant put a track on the room: their microphone, their camera, a screen."""
48
+
49
+ identity: str
50
+ kind: TrackKind
51
+ source: TrackSource
52
+
53
+
54
+ # A participant's track left the room: they stopped sharing, or a participant.mute took their audio
55
+ # away.
56
+ class TrackUnpublished(WireModel):
57
+ """A participant's track left the room."""
58
+
59
+ identity: str
60
+ kind: TrackKind
61
+ source: TrackSource
62
+
63
+
64
+ # A fact arrived from outside the conversation: the tenant's backend sent call.event, or a
65
+ # participant's browser sent pinecall.event. One event for both, told apart by source. It reached
66
+ # the log only because the agent declared the name in its events, from that source; anything else
67
+ # was refused before this.
68
+ class EventReceived(WireModel):
69
+ """A fact arrived from outside the conversation."""
70
+
71
+ name: str
72
+ data: dict[str, Any]
73
+ source: EventSource
74
+ identity: str | None = None
75
+
76
+
77
+ # Ephemeral, and the payload stays out of the log: the tenant chose what to send and to whom.
78
+ class RoomSent(WireModel):
79
+ """The agent pushed a payload to a browser in the room, on the tenant's room.send."""
80
+
81
+ topic: str
82
+ to: str | None = None
83
+ bytes: int
@@ -0,0 +1,241 @@
1
+ """Generated from schema/state.json: what a log reduces to."""
2
+
3
+ from typing import Annotated, Any, Literal
4
+
5
+ from pydantic import Field
6
+
7
+ from pinecall_protocol._base import WireModel
8
+ from pinecall_protocol.defs import (
9
+ AgentState,
10
+ Channel,
11
+ Contact,
12
+ Cost,
13
+ Direction,
14
+ DocSource,
15
+ EndReason,
16
+ EventSource,
17
+ MemoryOp,
18
+ ParticipantKind,
19
+ Route,
20
+ Supervisor,
21
+ TransferMode,
22
+ UserState,
23
+ )
24
+ from pinecall_protocol.metrics import (
25
+ AgentTurnMetrics,
26
+ AvatarMetrics,
27
+ EOTInferenceMetrics,
28
+ EOUMetrics,
29
+ InterruptionMetrics,
30
+ LLMMetrics,
31
+ ModelUsage,
32
+ RealtimeModelMetrics,
33
+ STTMetrics,
34
+ TTSMetrics,
35
+ UserTurnMetrics,
36
+ VADMetrics,
37
+ )
38
+
39
+ # Where the call is in its life. idle before any call.* entry, which is what an agent's own log
40
+ # looks like.
41
+ type CallStatus = Literal["idle", "ringing", "dialing", "active", "ended"]
42
+
43
+
44
+ class UserTurn(WireModel):
45
+ """One finished turn of the caller, with what the session measured about it."""
46
+
47
+ role: Literal["user"] = "user"
48
+ speech_id: str
49
+ item_id: str | None = None
50
+ text: str
51
+ language: str | None = None
52
+ transcript_confidence: float | None = None
53
+ metrics: UserTurnMetrics
54
+
55
+
56
+ class AgentTurn(WireModel):
57
+ """One finished reply of the agent, with what the session measured about it."""
58
+
59
+ role: Literal["agent"] = "agent"
60
+ speech_id: str
61
+ item_id: str | None = None
62
+ text: str
63
+ interrupted: bool
64
+ metrics: AgentTurnMetrics
65
+
66
+
67
+ # One turn of either side, told apart by role.
68
+ type Turn = Annotated[UserTurn | AgentTurn, Field(discriminator="role")]
69
+
70
+
71
+ # The turns hold the join; this holds the measurements.
72
+ class CollectedMetrics(WireModel):
73
+ """Every raw metric block of the call, by kind, in the order it arrived."""
74
+
75
+ llm: list[LLMMetrics]
76
+ stt: list[STTMetrics]
77
+ tts: list[TTSMetrics]
78
+ vad: list[VADMetrics]
79
+ eou: list[EOUMetrics]
80
+ eot: list[EOTInferenceMetrics]
81
+ interruption: list[InterruptionMetrics]
82
+ realtime: list[RealtimeModelMetrics]
83
+ avatar: list[AvatarMetrics]
84
+
85
+
86
+ class ToolRun(WireModel):
87
+ """One tool call and, once the app answered, its result."""
88
+
89
+ call_id: str
90
+ name: str
91
+ arguments: dict[str, Any]
92
+ speech_id: str | None = None
93
+ status: Literal["running", "done", "failed"]
94
+ output: Any = None
95
+ error: str | None = None
96
+ summary: str | None = None
97
+ duration_s: float | None = None
98
+ seq: int
99
+
100
+
101
+ class PromptBlockState(WireModel):
102
+ """What is known about one block of the prompt without storing its text."""
103
+
104
+ hash: str
105
+ chars: int
106
+ seq: int
107
+
108
+
109
+ # Every block the app has written, by name, without its text. The history between the static and the
110
+ # dynamic blocks is the turns.
111
+ type PromptState = dict[str, PromptBlockState]
112
+
113
+
114
+ class Confirm(WireModel):
115
+ """One confirmation the platform asked for, and how it went."""
116
+
117
+ tool: str
118
+ call_id: str
119
+ audience: str
120
+ phrase: str
121
+ status: Literal["pending", "granted", "declined"]
122
+ said: str | None = None
123
+ reason: str | None = None
124
+
125
+
126
+ class Handoff(WireModel):
127
+ """Whether a supervisor holds the line right now."""
128
+
129
+ active: bool
130
+ by: Supervisor | None
131
+
132
+
133
+ class TransferState(WireModel):
134
+ """The transfer in flight or the one that happened."""
135
+
136
+ to: str
137
+ mode: TransferMode
138
+ status: Literal["requested", "done", "failed"]
139
+ by: Literal["agent", "supervisor"]
140
+
141
+
142
+ class LiveTranscript(WireModel):
143
+ """The words on screen right now: interim transcripts that a finished turn clears."""
144
+
145
+ user: str | None
146
+ agent: str | None
147
+
148
+
149
+ class Gap(WireModel):
150
+ """A stretch of seqs this reader never saw."""
151
+
152
+ from_seq: int
153
+ to_seq: int
154
+
155
+
156
+ class LoggedError(WireModel):
157
+ """An error entry, kept so the console can show what went wrong and when."""
158
+
159
+ seq: int
160
+ code: str
161
+ message: str
162
+
163
+
164
+ class Participant(WireModel):
165
+ """One participant in the room right now, as the room reported them when they joined."""
166
+
167
+ identity: str
168
+ kind: ParticipantKind
169
+ name: str | None = None
170
+ joined_at: float
171
+ speaking: bool
172
+ attributes: dict[str, Any]
173
+
174
+
175
+ class Room(WireModel):
176
+ """The LiveKit room the call lives in, and who is in it right now."""
177
+
178
+ name: str
179
+ sid: str
180
+ participants: list[Participant]
181
+ caller: str | None
182
+
183
+
184
+ # Its data is in the log at that seq.
185
+ class ReceivedEvent(WireModel):
186
+ """One fact that reached the agent from outside the conversation, kept by name and origin."""
187
+
188
+ seq: int
189
+ name: str
190
+ source: EventSource
191
+ identity: str | None = None
192
+
193
+
194
+ class CustomNote(WireModel):
195
+ """A line the app wrote into the log with call.log."""
196
+
197
+ seq: int
198
+ name: str
199
+ data: dict[str, Any]
200
+
201
+
202
+ class State(WireModel):
203
+ """The whole of what a log says, at the seq it was read to."""
204
+
205
+ seq: int
206
+ agent: str
207
+ call: str | None
208
+ status: CallStatus
209
+ channel: Channel | None
210
+ direction: Direction | None
211
+ from_: str | None = Field(alias="from")
212
+ to: str | None
213
+ caller: Contact | None
214
+ room: Room | None
215
+ started_at: float | None
216
+ ended_at: float | None
217
+ end_reason: EndReason | None
218
+ outcome: str | None
219
+ user_state: UserState | None
220
+ agent_state: AgentState | None
221
+ live: LiveTranscript
222
+ turns: list[Turn]
223
+ metrics: CollectedMetrics
224
+ tools: list[ToolRun]
225
+ app_state: dict[str, Any]
226
+ events: list[ReceivedEvent]
227
+ prompt: PromptState
228
+ tools_visible: list[str]
229
+ confirms: list[Confirm]
230
+ memory: list[MemoryOp]
231
+ sources: list[DocSource]
232
+ handoff: Handoff
233
+ held: bool
234
+ muted: bool
235
+ transfer: TransferState | None
236
+ usage: list[ModelUsage]
237
+ cost: Cost | None
238
+ routes: list[Route]
239
+ gaps: list[Gap]
240
+ errors: list[LoggedError]
241
+ custom: list[CustomNote]