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,41 @@
1
+ """Generated from schema/ by `python generate`. Never edited by hand."""
2
+
3
+ from pinecall_protocol._base import ProtocolError, WireModel
4
+ from pinecall_protocol.codec import (
5
+ command_of,
6
+ decode_entries,
7
+ decode_entry,
8
+ encode,
9
+ event_of,
10
+ )
11
+ from pinecall_protocol.envelope import Command, Entry
12
+ from pinecall_protocol.registry import (
13
+ COMMANDS,
14
+ EPHEMERAL_EVENTS,
15
+ EVENTS,
16
+ PRODUCES,
17
+ TERMINAL_EVENT,
18
+ CommandType,
19
+ EventType,
20
+ )
21
+ from pinecall_protocol.state import State
22
+
23
+ __all__ = [
24
+ "COMMANDS",
25
+ "EPHEMERAL_EVENTS",
26
+ "EVENTS",
27
+ "PRODUCES",
28
+ "TERMINAL_EVENT",
29
+ "Command",
30
+ "CommandType",
31
+ "Entry",
32
+ "EventType",
33
+ "ProtocolError",
34
+ "State",
35
+ "WireModel",
36
+ "command_of",
37
+ "decode_entries",
38
+ "decode_entry",
39
+ "encode",
40
+ "event_of",
41
+ ]
@@ -0,0 +1,13 @@
1
+ """The base every wire model shares: unknown keys are refused; a field builds by name or alias."""
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+
5
+
6
+ class WireModel(BaseModel):
7
+ """A shape on the wire. extra=forbid is the schema's additionalProperties: false."""
8
+
9
+ model_config = ConfigDict(extra="forbid", validate_by_name=True, validate_by_alias=True)
10
+
11
+
12
+ class ProtocolError(ValueError):
13
+ """A message did not match the protocol: unknown type, bad shape, a seq out of order."""
@@ -0,0 +1,4 @@
1
+ """The one place the version lives; hatch reads it from here at build time."""
2
+
3
+ # 0.0.0 means "unreleased". The number is the maintainer's call and is never bumped from code.
4
+ __version__ = "0.1.0"
@@ -0,0 +1,52 @@
1
+ """JSON in, models out, and back. The only file that touches a key name: aliases, by_alias."""
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import TypeAdapter, ValidationError
6
+
7
+ from pinecall_protocol._base import ProtocolError, WireModel
8
+ from pinecall_protocol.envelope import Command, Entry
9
+ from pinecall_protocol.registry import COMMANDS, EVENTS
10
+
11
+ _LOG: TypeAdapter[list[Entry]] = TypeAdapter(list[Entry])
12
+
13
+
14
+ def decode_entry(raw: dict[str, Any]) -> Entry:
15
+ """One log line from decoded JSON. A bad shape is a ProtocolError."""
16
+ return _validate(Entry, raw, "entry")
17
+
18
+
19
+ def decode_entries(text: str) -> list[Entry]:
20
+ """A whole log from a JSON array text, in the order it came."""
21
+ try:
22
+ return _LOG.validate_json(text)
23
+ except ValidationError as error:
24
+ raise ProtocolError(f"log: {error}") from error
25
+
26
+
27
+ def event_of(entry: Entry) -> WireModel:
28
+ """The entry's data as the model its type names. Unknown type or bad shape: ProtocolError."""
29
+ model = EVENTS.get(entry.type)
30
+ if model is None:
31
+ raise ProtocolError(f"unknown event type: {entry.type}")
32
+ return _validate(model, entry.data, entry.type)
33
+
34
+
35
+ def command_of(command: Command) -> WireModel:
36
+ """The command's data as the model its type names. Unknown type or bad shape: ProtocolError."""
37
+ model = COMMANDS.get(command.type)
38
+ if model is None:
39
+ raise ProtocolError(f"unknown command type: {command.type}")
40
+ return _validate(model, command.data, command.type)
41
+
42
+
43
+ def encode(model: WireModel) -> dict[str, Any]:
44
+ """A model as it goes on the wire: wire key names, absent fields absent, JSON-ready values."""
45
+ return model.model_dump(mode="json", by_alias=True, exclude_unset=True)
46
+
47
+
48
+ def _validate[T: WireModel](model: type[T], raw: dict[str, Any], what: str) -> T:
49
+ try:
50
+ return model.model_validate(raw)
51
+ except ValidationError as error:
52
+ raise ProtocolError(f"{what}: {error}") from error
@@ -0,0 +1,208 @@
1
+ """Generated from schema/commands/: one model per command."""
2
+
3
+ from typing import Any, Literal
4
+
5
+ from pydantic import Field
6
+
7
+ from pinecall_protocol._base import WireModel
8
+ from pinecall_protocol.defs import AgentConfig, Contact, Route, Supervisor, ToolSpec, TransferMode
9
+ from pinecall_protocol.verbs import Verb
10
+
11
+
12
+ # Declare or change what the agent is: voice, models, language, greeting, the full tool list. Only
13
+ # the fields sent change.
14
+ class AgentConfigure(WireModel):
15
+ """Declare or change what the agent is."""
16
+
17
+ config: AgentConfig
18
+
19
+
20
+ # The gateway answers agent.registered, or error.
21
+ class AgentRegister(WireModel):
22
+ """The app's first message: this socket speaks for this agent and answers these doors."""
23
+
24
+ routes: list[Route]
25
+ sdk: str | None = None
26
+ takes_unclaimed: bool = True
27
+
28
+
29
+ # Make the model speak now, guided by an instruction it reads and the caller never hears: 'tell them
30
+ # a slot at 10:15 just opened'. On livekit's session.generate_reply; the sibling of agent.say, which
31
+ # speaks verbatim. The reply lands as turn.agent.
32
+ class AgentReply(WireModel):
33
+ """Make the model speak now, guided by an instruction it reads and the caller never hears."""
34
+
35
+ instructions: str
36
+ allow_interruptions: bool | None = None
37
+
38
+
39
+ # Make the agent say this text now, verbatim, outside the model's turn: a greeting, a read-back, a
40
+ # system notice. The reply lands as turn.agent.
41
+ class AgentSay(WireModel):
42
+ """Make the agent say this text now, verbatim, outside the model's turn."""
43
+
44
+ text: str
45
+ allow_interruptions: bool | None = None
46
+
47
+
48
+ # The new call's log opens with call.dialing; call.started follows when the far end answers.
49
+ class CallDial(WireModel):
50
+ """Place an outbound call as this agent."""
51
+
52
+ to: str
53
+ from_: str | None = Field(None, alias="from")
54
+ caller: Contact | None = None
55
+ metadata: dict[str, Any] | None = None
56
+
57
+
58
+ class CallDtmf(WireModel):
59
+ """Send touch tones down the line, for an IVR on the far end."""
60
+
61
+ digits: str
62
+
63
+
64
+ # Hand the agent a fact from the tenant's backend: a slot freed, an order shipped, a payment
65
+ # confirmed. Lands as event.received with source app. The agent must have declared the name in its
66
+ # events with app among the senders, or the gateway answers error and nothing touches the log.
67
+ class CallEvent(WireModel):
68
+ """Hand the agent a fact from the tenant's backend."""
69
+
70
+ name: str
71
+ data: dict[str, Any]
72
+
73
+
74
+ # call.ended follows with reason agent_hung_up.
75
+ class CallHangup(WireModel):
76
+ """End the call from the app's side."""
77
+
78
+ reason: str | None = None
79
+
80
+
81
+ class CallHold(WireModel):
82
+ """Put the caller on hold: they hear hold audio, the agent hears nothing."""
83
+
84
+
85
+ # It gets a seq like everything else and lands as custom.
86
+ class CallLog(WireModel):
87
+ """Write a line of the app's own into the call's log."""
88
+
89
+ name: str
90
+ data: dict[str, Any]
91
+
92
+
93
+ class CallMute(WireModel):
94
+ """Mute the agent: it keeps listening and thinking, produces no audio."""
95
+
96
+
97
+ # call.transferred says whether it worked.
98
+ class CallTransfer(WireModel):
99
+ """Send the caller to another number."""
100
+
101
+ to: str
102
+ mode: TransferMode
103
+
104
+
105
+ class CallUnhold(WireModel):
106
+ """Take the caller off hold."""
107
+
108
+
109
+ class CallUnmute(WireModel):
110
+ """Unmute the agent."""
111
+
112
+
113
+ # Why the verb did not run, in the words the console shows: the status it travels under, and the
114
+ # sentence.
115
+ class DevRefusal(WireModel):
116
+ """Why the verb did not run, in the words the console shows."""
117
+
118
+ status: int
119
+ detail: str
120
+
121
+
122
+ class DevAnswer(WireModel):
123
+ """What came of one dev.request, named by its id."""
124
+
125
+ id: str
126
+ result: dict[str, Any] | None = None
127
+ refused: DevRefusal | None = None
128
+
129
+
130
+ # Silence a participant for the rest of the call: their audio leaves the room, for everyone in it.
131
+ # Lands as track.unpublished for their microphone. There is no unmute; a leg that must speak again
132
+ # is invited again.
133
+ class ParticipantMute(WireModel):
134
+ """Silence a participant for the rest of the call."""
135
+
136
+ identity: str
137
+
138
+
139
+ # Lands as participant.left with reason participant_removed. Removing the caller ends the call.
140
+ class ParticipantRemove(WireModel):
141
+ """Put a participant out of the room."""
142
+
143
+ identity: str
144
+
145
+
146
+ class Ping(WireModel):
147
+ """Is the socket alive? The gateway answers pong."""
148
+
149
+
150
+ # The name must be one of the agent's declared blocks, or one of the default four; anything else is
151
+ # refused with the name.
152
+ class PromptSet(WireModel):
153
+ """Rewrite one block of the prompt, whole, by name."""
154
+
155
+ name: str
156
+ text: str
157
+
158
+
159
+ # A second SIP leg dialed to a number is the warm path: the agent stays on with the caller while the
160
+ # other side answers. Lands as participant.joined when they arrive, or error when they do not.
161
+ class RoomInvite(WireModel):
162
+ """Bring somebody else into the call's room."""
163
+
164
+ to: str
165
+ kind: Literal["sip", "participant"]
166
+
167
+
168
+ # Push a payload to a browser in the room over the DataChannel: a card to render, a form to open.
169
+ # Lands as room.sent with the size, never the payload. The widget listens on pinecall.ui; a topic of
170
+ # the tenant's own reaches the tenant's own page code.
171
+ class RoomSend(WireModel):
172
+ """Push a payload to a browser in the room over the DataChannel."""
173
+
174
+ topic: str
175
+ data: dict[str, Any]
176
+ to: str | None = None
177
+
178
+
179
+ # Set up this one call before the first turn: the app's initial state, and any config that differs
180
+ # from the agent's defaults for this caller.
181
+ class SessionConfigure(WireModel):
182
+ """Set up this one call before the first turn."""
183
+
184
+ state: dict[str, Any] | None = None
185
+ config: AgentConfig | None = None
186
+
187
+
188
+ # The platform logs state.changed and re-renders.
189
+ class StateSet(WireModel):
190
+ """The app's state changed and this is all of it."""
191
+
192
+ state: dict[str, Any]
193
+ changed: list[str] | None = None
194
+
195
+
196
+ class SupervisorVerb(WireModel):
197
+ """One supervise verb, from the human the door named."""
198
+
199
+ by: Supervisor
200
+ verb: Verb
201
+
202
+
203
+ # The full list was declared in agent.configure; this is the subset whose when allows them in this
204
+ # state.
205
+ class ToolsSet(WireModel):
206
+ """The tools the model may see now."""
207
+
208
+ tools: list[ToolSpec]