jambonz-python-sdk 0.2.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.
- jambonz_python_sdk-0.2.0.dist-info/METADATA +179 -0
- jambonz_python_sdk-0.2.0.dist-info/RECORD +119 -0
- jambonz_python_sdk-0.2.0.dist-info/WHEEL +4 -0
- jambonz_sdk/__init__.py +52 -0
- jambonz_sdk/_signature.py +73 -0
- jambonz_sdk/client/__init__.py +15 -0
- jambonz_sdk/client/api.py +241 -0
- jambonz_sdk/schema/callbacks/amd.schema.json +50 -0
- jambonz_sdk/schema/callbacks/base.schema.json +29 -0
- jambonz_sdk/schema/callbacks/call-status.schema.json +22 -0
- jambonz_sdk/schema/callbacks/conference-status.schema.json +24 -0
- jambonz_sdk/schema/callbacks/conference-wait.schema.json +11 -0
- jambonz_sdk/schema/callbacks/conference.schema.json +11 -0
- jambonz_sdk/schema/callbacks/dequeue.schema.json +19 -0
- jambonz_sdk/schema/callbacks/dial-dtmf.schema.json +18 -0
- jambonz_sdk/schema/callbacks/dial-hold.schema.json +22 -0
- jambonz_sdk/schema/callbacks/dial-refer.schema.json +28 -0
- jambonz_sdk/schema/callbacks/dial.schema.json +31 -0
- jambonz_sdk/schema/callbacks/enqueue-wait.schema.json +17 -0
- jambonz_sdk/schema/callbacks/enqueue.schema.json +27 -0
- jambonz_sdk/schema/callbacks/gather-partial.schema.json +54 -0
- jambonz_sdk/schema/callbacks/gather.schema.json +60 -0
- jambonz_sdk/schema/callbacks/listen.schema.json +21 -0
- jambonz_sdk/schema/callbacks/llm.schema.json +30 -0
- jambonz_sdk/schema/callbacks/message.schema.json +35 -0
- jambonz_sdk/schema/callbacks/pipeline-turn.schema.json +109 -0
- jambonz_sdk/schema/callbacks/play.schema.json +36 -0
- jambonz_sdk/schema/callbacks/session-new.schema.json +143 -0
- jambonz_sdk/schema/callbacks/session-reconnect.schema.json +9 -0
- jambonz_sdk/schema/callbacks/session-redirect.schema.json +38 -0
- jambonz_sdk/schema/callbacks/sip-refer-event.schema.json +20 -0
- jambonz_sdk/schema/callbacks/sip-refer.schema.json +22 -0
- jambonz_sdk/schema/callbacks/sip-request.schema.json +27 -0
- jambonz_sdk/schema/callbacks/transcribe-translation.schema.json +24 -0
- jambonz_sdk/schema/callbacks/transcribe.schema.json +46 -0
- jambonz_sdk/schema/callbacks/tts-streaming-event.schema.json +77 -0
- jambonz_sdk/schema/callbacks/verb-status.schema.json +57 -0
- jambonz_sdk/schema/components/actionHook.schema.json +36 -0
- jambonz_sdk/schema/components/actionHookDelayAction.schema.json +37 -0
- jambonz_sdk/schema/components/amd.schema.json +68 -0
- jambonz_sdk/schema/components/auth.schema.json +18 -0
- jambonz_sdk/schema/components/bidirectionalAudio.schema.json +22 -0
- jambonz_sdk/schema/components/fillerNoise.schema.json +25 -0
- jambonz_sdk/schema/components/llm-base.schema.json +94 -0
- jambonz_sdk/schema/components/recognizer-assemblyAiOptions.schema.json +66 -0
- jambonz_sdk/schema/components/recognizer-awsOptions.schema.json +52 -0
- jambonz_sdk/schema/components/recognizer-azureOptions.schema.json +32 -0
- jambonz_sdk/schema/components/recognizer-cobaltOptions.schema.json +34 -0
- jambonz_sdk/schema/components/recognizer-customOptions.schema.json +27 -0
- jambonz_sdk/schema/components/recognizer-deepgramOptions.schema.json +147 -0
- jambonz_sdk/schema/components/recognizer-elevenlabsOptions.schema.json +39 -0
- jambonz_sdk/schema/components/recognizer-gladiaOptions.schema.json +8 -0
- jambonz_sdk/schema/components/recognizer-googleOptions.schema.json +35 -0
- jambonz_sdk/schema/components/recognizer-houndifyOptions.schema.json +53 -0
- jambonz_sdk/schema/components/recognizer-ibmOptions.schema.json +54 -0
- jambonz_sdk/schema/components/recognizer-nuanceOptions.schema.json +150 -0
- jambonz_sdk/schema/components/recognizer-nvidiaOptions.schema.json +39 -0
- jambonz_sdk/schema/components/recognizer-openaiOptions.schema.json +59 -0
- jambonz_sdk/schema/components/recognizer-sonioxOptions.schema.json +46 -0
- jambonz_sdk/schema/components/recognizer-speechmaticsOptions.schema.json +100 -0
- jambonz_sdk/schema/components/recognizer-verbioOptions.schema.json +46 -0
- jambonz_sdk/schema/components/recognizer.schema.json +216 -0
- jambonz_sdk/schema/components/synthesizer.schema.json +82 -0
- jambonz_sdk/schema/components/target.schema.json +105 -0
- jambonz_sdk/schema/components/vad.schema.json +48 -0
- jambonz_sdk/schema/jambonz-app.schema.json +113 -0
- jambonz_sdk/schema/verbs/alert.schema.json +34 -0
- jambonz_sdk/schema/verbs/answer.schema.json +22 -0
- jambonz_sdk/schema/verbs/conference.schema.json +107 -0
- jambonz_sdk/schema/verbs/config.schema.json +221 -0
- jambonz_sdk/schema/verbs/deepgram_s2s.schema.json +81 -0
- jambonz_sdk/schema/verbs/dequeue.schema.json +51 -0
- jambonz_sdk/schema/verbs/dial.schema.json +200 -0
- jambonz_sdk/schema/verbs/dialogflow.schema.json +148 -0
- jambonz_sdk/schema/verbs/dtmf.schema.json +49 -0
- jambonz_sdk/schema/verbs/dub.schema.json +103 -0
- jambonz_sdk/schema/verbs/elevenlabs_s2s.schema.json +81 -0
- jambonz_sdk/schema/verbs/enqueue.schema.json +53 -0
- jambonz_sdk/schema/verbs/gather.schema.json +190 -0
- jambonz_sdk/schema/verbs/google_s2s.schema.json +42 -0
- jambonz_sdk/schema/verbs/hangup.schema.json +36 -0
- jambonz_sdk/schema/verbs/leave.schema.json +22 -0
- jambonz_sdk/schema/verbs/listen.schema.json +127 -0
- jambonz_sdk/schema/verbs/llm.schema.json +44 -0
- jambonz_sdk/schema/verbs/message.schema.json +82 -0
- jambonz_sdk/schema/verbs/openai_s2s.schema.json +42 -0
- jambonz_sdk/schema/verbs/pause.schema.json +36 -0
- jambonz_sdk/schema/verbs/pipeline.schema.json +240 -0
- jambonz_sdk/schema/verbs/play.schema.json +96 -0
- jambonz_sdk/schema/verbs/redirect.schema.json +34 -0
- jambonz_sdk/schema/verbs/rest_dial.schema.json +113 -0
- jambonz_sdk/schema/verbs/s2s.schema.json +39 -0
- jambonz_sdk/schema/verbs/say.schema.json +107 -0
- jambonz_sdk/schema/verbs/sip-decline.schema.json +58 -0
- jambonz_sdk/schema/verbs/sip-refer.schema.json +58 -0
- jambonz_sdk/schema/verbs/sip-request.schema.json +54 -0
- jambonz_sdk/schema/verbs/stream.schema.json +103 -0
- jambonz_sdk/schema/verbs/tag.schema.json +41 -0
- jambonz_sdk/schema/verbs/transcribe.schema.json +57 -0
- jambonz_sdk/schema/verbs/ultravox_s2s.schema.json +41 -0
- jambonz_sdk/types/__init__.py +139 -0
- jambonz_sdk/types/components.py +250 -0
- jambonz_sdk/types/rest.py +59 -0
- jambonz_sdk/types/session.py +55 -0
- jambonz_sdk/types/verbs.py +572 -0
- jambonz_sdk/validator.py +107 -0
- jambonz_sdk/verb_builder.py +316 -0
- jambonz_sdk/verb_builder.pyi +1133 -0
- jambonz_sdk/verb_registry.py +102 -0
- jambonz_sdk/webhook/__init__.py +10 -0
- jambonz_sdk/webhook/middleware.py +63 -0
- jambonz_sdk/webhook/response.py +43 -0
- jambonz_sdk/websocket/__init__.py +15 -0
- jambonz_sdk/websocket/audio_client.py +11 -0
- jambonz_sdk/websocket/audio_stream.py +151 -0
- jambonz_sdk/websocket/client.py +165 -0
- jambonz_sdk/websocket/endpoint.py +193 -0
- jambonz_sdk/websocket/router.py +87 -0
- jambonz_sdk/websocket/session.py +259 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
"""Verb type definitions matching jambonz JSON schemas.
|
|
2
|
+
|
|
3
|
+
Each verb is a TypedDict where keys match the exact JSON schema property names.
|
|
4
|
+
The 'verb' key is always required and set to a literal string.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, TypedDict, Union
|
|
10
|
+
|
|
11
|
+
from jambonz_sdk.types.components import (
|
|
12
|
+
ActionHook,
|
|
13
|
+
ActionHookDelayAction,
|
|
14
|
+
Amd,
|
|
15
|
+
Auth,
|
|
16
|
+
BidirectionalAudio,
|
|
17
|
+
FillerNoise,
|
|
18
|
+
McpServer,
|
|
19
|
+
Recognizer,
|
|
20
|
+
Synthesizer,
|
|
21
|
+
Target,
|
|
22
|
+
Vad,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SayVerb(TypedDict, total=False):
|
|
27
|
+
"""Speak text using TTS."""
|
|
28
|
+
|
|
29
|
+
verb: str # "say"
|
|
30
|
+
id: str
|
|
31
|
+
text: str | list[str]
|
|
32
|
+
instructions: str
|
|
33
|
+
stream: bool
|
|
34
|
+
loop: int | str
|
|
35
|
+
synthesizer: Synthesizer
|
|
36
|
+
earlyMedia: bool
|
|
37
|
+
disableTtsCache: bool
|
|
38
|
+
closeStreamOnEmpty: bool
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PlayVerb(TypedDict, total=False):
|
|
42
|
+
"""Play an audio file."""
|
|
43
|
+
|
|
44
|
+
verb: str # "play"
|
|
45
|
+
id: str
|
|
46
|
+
url: str | list[str]
|
|
47
|
+
loop: int | str
|
|
48
|
+
earlyMedia: bool
|
|
49
|
+
seekOffset: int | str
|
|
50
|
+
timeoutSecs: int | str
|
|
51
|
+
actionHook: ActionHook
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class GatherVerb(TypedDict, total=False):
|
|
55
|
+
"""Collect speech/DTMF input."""
|
|
56
|
+
|
|
57
|
+
verb: str # "gather"
|
|
58
|
+
id: str
|
|
59
|
+
actionHook: ActionHook
|
|
60
|
+
input: list[str] # ["speech", "digits"]
|
|
61
|
+
finishOnKey: str
|
|
62
|
+
numDigits: int
|
|
63
|
+
minDigits: int
|
|
64
|
+
maxDigits: int
|
|
65
|
+
interDigitTimeout: int
|
|
66
|
+
speechTimeout: int
|
|
67
|
+
timeout: int
|
|
68
|
+
partialResultHook: ActionHook
|
|
69
|
+
listenDuringPrompt: bool
|
|
70
|
+
dtmfBargein: bool
|
|
71
|
+
bargein: bool
|
|
72
|
+
minBargeinWordCount: int
|
|
73
|
+
recognizer: Recognizer
|
|
74
|
+
say: SayVerb
|
|
75
|
+
play: PlayVerb
|
|
76
|
+
fillerNoise: FillerNoise
|
|
77
|
+
actionHookDelayAction: ActionHookDelayAction
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class DialVerb(TypedDict, total=False):
|
|
81
|
+
"""Place outbound call and bridge."""
|
|
82
|
+
|
|
83
|
+
verb: str # "dial"
|
|
84
|
+
id: str
|
|
85
|
+
target: list[Target]
|
|
86
|
+
actionHook: ActionHook
|
|
87
|
+
onHoldHook: ActionHook
|
|
88
|
+
answerOnBridge: bool
|
|
89
|
+
callerId: str
|
|
90
|
+
callerName: str
|
|
91
|
+
confirmHook: ActionHook
|
|
92
|
+
referHook: ActionHook
|
|
93
|
+
dialMusic: str
|
|
94
|
+
dtmfCapture: dict[str, Any]
|
|
95
|
+
dtmfHook: ActionHook
|
|
96
|
+
headers: dict[str, str]
|
|
97
|
+
anchorMedia: bool
|
|
98
|
+
exitMediaPath: bool
|
|
99
|
+
boostAudioSignal: int | str
|
|
100
|
+
listen: dict[str, Any]
|
|
101
|
+
stream: dict[str, Any]
|
|
102
|
+
transcribe: dict[str, Any]
|
|
103
|
+
timeLimit: int
|
|
104
|
+
timeout: int
|
|
105
|
+
proxy: str
|
|
106
|
+
amd: Amd
|
|
107
|
+
dub: list[dict[str, Any]]
|
|
108
|
+
tag: dict[str, Any]
|
|
109
|
+
forwardPAI: bool
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ListenVerb(TypedDict, total=False):
|
|
113
|
+
"""Stream audio to external websocket."""
|
|
114
|
+
|
|
115
|
+
verb: str # "listen"
|
|
116
|
+
id: str
|
|
117
|
+
url: str
|
|
118
|
+
actionHook: ActionHook
|
|
119
|
+
wsAuth: Auth
|
|
120
|
+
mixType: str # "mono" | "stereo" | "mixed"
|
|
121
|
+
metadata: dict[str, Any]
|
|
122
|
+
sampleRate: int
|
|
123
|
+
finishOnKey: str
|
|
124
|
+
maxLength: int
|
|
125
|
+
passDtmf: bool
|
|
126
|
+
playBeep: bool
|
|
127
|
+
disableBidirectionalAudio: bool
|
|
128
|
+
bidirectionalAudio: BidirectionalAudio
|
|
129
|
+
timeout: int
|
|
130
|
+
transcribe: dict[str, Any]
|
|
131
|
+
earlyMedia: bool
|
|
132
|
+
channel: int
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class StreamVerb(TypedDict, total=False):
|
|
136
|
+
"""Stream audio to external websocket (alias for listen)."""
|
|
137
|
+
|
|
138
|
+
verb: str # "stream"
|
|
139
|
+
id: str
|
|
140
|
+
url: str
|
|
141
|
+
actionHook: ActionHook
|
|
142
|
+
wsAuth: Auth
|
|
143
|
+
mixType: str
|
|
144
|
+
metadata: dict[str, Any]
|
|
145
|
+
sampleRate: int
|
|
146
|
+
finishOnKey: str
|
|
147
|
+
maxLength: int
|
|
148
|
+
passDtmf: bool
|
|
149
|
+
playBeep: bool
|
|
150
|
+
disableBidirectionalAudio: bool
|
|
151
|
+
bidirectionalAudio: BidirectionalAudio
|
|
152
|
+
timeout: int
|
|
153
|
+
transcribe: dict[str, Any]
|
|
154
|
+
earlyMedia: bool
|
|
155
|
+
channel: int
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TranscribeVerb(TypedDict, total=False):
|
|
159
|
+
"""Real-time call transcription."""
|
|
160
|
+
|
|
161
|
+
verb: str # "transcribe"
|
|
162
|
+
id: str
|
|
163
|
+
enable: bool
|
|
164
|
+
transcriptionHook: str
|
|
165
|
+
translationHook: str
|
|
166
|
+
recognizer: Recognizer
|
|
167
|
+
earlyMedia: bool
|
|
168
|
+
channel: int
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class ConferenceVerb(TypedDict, total=False):
|
|
172
|
+
"""Multi-party conference room."""
|
|
173
|
+
|
|
174
|
+
verb: str # "conference"
|
|
175
|
+
id: str
|
|
176
|
+
name: str
|
|
177
|
+
beep: bool
|
|
178
|
+
memberTag: str
|
|
179
|
+
speakOnlyTo: str
|
|
180
|
+
startConferenceOnEnter: bool
|
|
181
|
+
endConferenceOnExit: bool
|
|
182
|
+
endConferenceDuration: int
|
|
183
|
+
maxParticipants: int
|
|
184
|
+
joinMuted: bool
|
|
185
|
+
actionHook: ActionHook
|
|
186
|
+
waitHook: ActionHook
|
|
187
|
+
statusEvents: list[str]
|
|
188
|
+
statusHook: ActionHook
|
|
189
|
+
enterHook: ActionHook
|
|
190
|
+
record: dict[str, Any]
|
|
191
|
+
listen: dict[str, Any]
|
|
192
|
+
distributeDtmf: bool
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class EnqueueVerb(TypedDict, total=False):
|
|
196
|
+
"""Place caller in a queue."""
|
|
197
|
+
|
|
198
|
+
verb: str # "enqueue"
|
|
199
|
+
id: str
|
|
200
|
+
name: str
|
|
201
|
+
actionHook: ActionHook
|
|
202
|
+
waitHook: ActionHook
|
|
203
|
+
priority: int
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class DequeueVerb(TypedDict, total=False):
|
|
207
|
+
"""Remove caller from a queue."""
|
|
208
|
+
|
|
209
|
+
verb: str # "dequeue"
|
|
210
|
+
id: str
|
|
211
|
+
name: str
|
|
212
|
+
actionHook: ActionHook
|
|
213
|
+
timeout: int
|
|
214
|
+
beep: bool
|
|
215
|
+
callSid: str
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class HangupVerb(TypedDict, total=False):
|
|
219
|
+
"""Terminate the call."""
|
|
220
|
+
|
|
221
|
+
verb: str # "hangup"
|
|
222
|
+
id: str
|
|
223
|
+
headers: dict[str, str]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class PauseVerb(TypedDict, total=False):
|
|
227
|
+
"""Pause execution."""
|
|
228
|
+
|
|
229
|
+
verb: str # "pause"
|
|
230
|
+
id: str
|
|
231
|
+
length: int
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class RedirectVerb(TypedDict, total=False):
|
|
235
|
+
"""Transfer control to different webhook."""
|
|
236
|
+
|
|
237
|
+
verb: str # "redirect"
|
|
238
|
+
id: str
|
|
239
|
+
actionHook: ActionHook
|
|
240
|
+
statusHook: ActionHook
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class ConfigVerb(TypedDict, total=False):
|
|
244
|
+
"""Set session-level defaults."""
|
|
245
|
+
|
|
246
|
+
verb: str # "config"
|
|
247
|
+
id: str
|
|
248
|
+
synthesizer: Synthesizer
|
|
249
|
+
recognizer: Recognizer
|
|
250
|
+
bargeIn: dict[str, Any]
|
|
251
|
+
ttsStream: dict[str, Any]
|
|
252
|
+
record: dict[str, Any]
|
|
253
|
+
listen: dict[str, Any]
|
|
254
|
+
stream: dict[str, Any]
|
|
255
|
+
transcribe: dict[str, Any]
|
|
256
|
+
amd: Amd
|
|
257
|
+
fillerNoise: FillerNoise
|
|
258
|
+
vad: Vad
|
|
259
|
+
notifyEvents: bool
|
|
260
|
+
notifySttLatency: bool
|
|
261
|
+
reset: str | list[str]
|
|
262
|
+
onHoldMusic: str
|
|
263
|
+
actionHookDelayAction: ActionHookDelayAction
|
|
264
|
+
sipRequestWithinDialogHook: ActionHook
|
|
265
|
+
boostAudioSignal: int | str
|
|
266
|
+
referHook: ActionHook
|
|
267
|
+
earlyMedia: bool
|
|
268
|
+
autoStreamTts: bool
|
|
269
|
+
disableTtsCache: bool
|
|
270
|
+
trackTtsPlayout: bool
|
|
271
|
+
noiseIsolation: dict[str, Any]
|
|
272
|
+
turnTaking: dict[str, Any]
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class TagVerb(TypedDict, total=False):
|
|
276
|
+
"""Attach metadata to the call."""
|
|
277
|
+
|
|
278
|
+
verb: str # "tag"
|
|
279
|
+
id: str
|
|
280
|
+
data: dict[str, Any]
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class DtmfVerb(TypedDict, total=False):
|
|
284
|
+
"""Send DTMF tones."""
|
|
285
|
+
|
|
286
|
+
verb: str # "dtmf"
|
|
287
|
+
id: str
|
|
288
|
+
dtmf: str
|
|
289
|
+
duration: int
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class MessageVerb(TypedDict, total=False):
|
|
293
|
+
"""Send SMS/MMS message."""
|
|
294
|
+
|
|
295
|
+
verb: str # "message"
|
|
296
|
+
id: str
|
|
297
|
+
to: str
|
|
298
|
+
from_: str # 'from' is reserved in Python; serialized as 'from'
|
|
299
|
+
text: str
|
|
300
|
+
media: str | list[str]
|
|
301
|
+
carrier: str
|
|
302
|
+
account_sid: str
|
|
303
|
+
message_sid: str
|
|
304
|
+
actionHook: ActionHook
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class DubVerb(TypedDict, total=False):
|
|
308
|
+
"""Manage audio dubbing tracks."""
|
|
309
|
+
|
|
310
|
+
verb: str # "dub"
|
|
311
|
+
id: str
|
|
312
|
+
action: str # "addTrack" | "removeTrack" | "silenceTrack" | "playOnTrack" | "sayOnTrack"
|
|
313
|
+
track: str
|
|
314
|
+
play: str
|
|
315
|
+
say: str | dict[str, Any]
|
|
316
|
+
loop: bool
|
|
317
|
+
gain: int | str
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class AlertVerb(TypedDict, total=False):
|
|
321
|
+
"""Send SIP 180 with Alert-Info."""
|
|
322
|
+
|
|
323
|
+
verb: str # "alert"
|
|
324
|
+
id: str
|
|
325
|
+
message: str
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class AnswerVerb(TypedDict, total=False):
|
|
329
|
+
"""Explicitly answer the call."""
|
|
330
|
+
|
|
331
|
+
verb: str # "answer"
|
|
332
|
+
id: str
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class LeaveVerb(TypedDict, total=False):
|
|
336
|
+
"""Leave conference or queue."""
|
|
337
|
+
|
|
338
|
+
verb: str # "leave"
|
|
339
|
+
id: str
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class SipDeclineVerb(TypedDict, total=False):
|
|
343
|
+
"""Reject incoming call with SIP error."""
|
|
344
|
+
|
|
345
|
+
verb: str # "sip:decline"
|
|
346
|
+
id: str
|
|
347
|
+
status: int
|
|
348
|
+
reason: str
|
|
349
|
+
headers: dict[str, str]
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class SipRequestVerb(TypedDict, total=False):
|
|
353
|
+
"""Send SIP request within dialog."""
|
|
354
|
+
|
|
355
|
+
verb: str # "sip:request"
|
|
356
|
+
id: str
|
|
357
|
+
method: str
|
|
358
|
+
body: str
|
|
359
|
+
headers: dict[str, str]
|
|
360
|
+
actionHook: ActionHook
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class SipReferVerb(TypedDict, total=False):
|
|
364
|
+
"""Send SIP REFER for call transfer."""
|
|
365
|
+
|
|
366
|
+
verb: str # "sip:refer"
|
|
367
|
+
id: str
|
|
368
|
+
referTo: str
|
|
369
|
+
referredBy: str
|
|
370
|
+
referredByDisplayName: str
|
|
371
|
+
headers: dict[str, str]
|
|
372
|
+
actionHook: ActionHook
|
|
373
|
+
eventHook: ActionHook
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# LLM/S2S verbs share the LlmBase structure
|
|
377
|
+
|
|
378
|
+
class LlmVerb(TypedDict, total=False):
|
|
379
|
+
"""Legacy LLM verb (prefer s2s or vendor-specific shortcuts)."""
|
|
380
|
+
|
|
381
|
+
verb: str # "llm"
|
|
382
|
+
id: str
|
|
383
|
+
vendor: str
|
|
384
|
+
model: str
|
|
385
|
+
auth: Auth
|
|
386
|
+
connectOptions: dict[str, Any]
|
|
387
|
+
llmOptions: dict[str, Any]
|
|
388
|
+
mcpServers: list[McpServer]
|
|
389
|
+
actionHook: ActionHook
|
|
390
|
+
eventHook: ActionHook
|
|
391
|
+
toolHook: ActionHook
|
|
392
|
+
events: list[str]
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class S2sVerb(TypedDict, total=False):
|
|
396
|
+
"""Generic S2S verb (use when vendor is dynamic)."""
|
|
397
|
+
|
|
398
|
+
verb: str # "s2s"
|
|
399
|
+
id: str
|
|
400
|
+
vendor: str
|
|
401
|
+
model: str
|
|
402
|
+
auth: Auth
|
|
403
|
+
connectOptions: dict[str, Any]
|
|
404
|
+
llmOptions: dict[str, Any]
|
|
405
|
+
mcpServers: list[McpServer]
|
|
406
|
+
actionHook: ActionHook
|
|
407
|
+
eventHook: ActionHook
|
|
408
|
+
toolHook: ActionHook
|
|
409
|
+
events: list[str]
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
class OpenaiS2sVerb(TypedDict, total=False):
|
|
413
|
+
"""OpenAI speech-to-speech."""
|
|
414
|
+
|
|
415
|
+
verb: str # "openai_s2s"
|
|
416
|
+
id: str
|
|
417
|
+
vendor: str
|
|
418
|
+
model: str
|
|
419
|
+
auth: Auth
|
|
420
|
+
connectOptions: dict[str, Any]
|
|
421
|
+
llmOptions: dict[str, Any]
|
|
422
|
+
mcpServers: list[McpServer]
|
|
423
|
+
actionHook: ActionHook
|
|
424
|
+
eventHook: ActionHook
|
|
425
|
+
toolHook: ActionHook
|
|
426
|
+
events: list[str]
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class GoogleS2sVerb(TypedDict, total=False):
|
|
430
|
+
"""Google speech-to-speech."""
|
|
431
|
+
|
|
432
|
+
verb: str # "google_s2s"
|
|
433
|
+
id: str
|
|
434
|
+
vendor: str
|
|
435
|
+
model: str
|
|
436
|
+
auth: Auth
|
|
437
|
+
connectOptions: dict[str, Any]
|
|
438
|
+
llmOptions: dict[str, Any]
|
|
439
|
+
mcpServers: list[McpServer]
|
|
440
|
+
actionHook: ActionHook
|
|
441
|
+
eventHook: ActionHook
|
|
442
|
+
toolHook: ActionHook
|
|
443
|
+
events: list[str]
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
class DeepgramS2sVerb(TypedDict, total=False):
|
|
447
|
+
"""Deepgram speech-to-speech."""
|
|
448
|
+
|
|
449
|
+
verb: str # "deepgram_s2s"
|
|
450
|
+
id: str
|
|
451
|
+
vendor: str
|
|
452
|
+
model: str
|
|
453
|
+
auth: Auth
|
|
454
|
+
connectOptions: dict[str, Any]
|
|
455
|
+
llmOptions: dict[str, Any]
|
|
456
|
+
mcpServers: list[McpServer]
|
|
457
|
+
actionHook: ActionHook
|
|
458
|
+
eventHook: ActionHook
|
|
459
|
+
toolHook: ActionHook
|
|
460
|
+
events: list[str]
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
class ElevenlabsS2sVerb(TypedDict, total=False):
|
|
464
|
+
"""ElevenLabs speech-to-speech."""
|
|
465
|
+
|
|
466
|
+
verb: str # "elevenlabs_s2s"
|
|
467
|
+
id: str
|
|
468
|
+
vendor: str
|
|
469
|
+
model: str
|
|
470
|
+
auth: Auth
|
|
471
|
+
connectOptions: dict[str, Any]
|
|
472
|
+
llmOptions: dict[str, Any]
|
|
473
|
+
mcpServers: list[McpServer]
|
|
474
|
+
actionHook: ActionHook
|
|
475
|
+
eventHook: ActionHook
|
|
476
|
+
toolHook: ActionHook
|
|
477
|
+
events: list[str]
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
class UltravoxS2sVerb(TypedDict, total=False):
|
|
481
|
+
"""Ultravox speech-to-speech."""
|
|
482
|
+
|
|
483
|
+
verb: str # "ultravox_s2s"
|
|
484
|
+
id: str
|
|
485
|
+
vendor: str
|
|
486
|
+
model: str
|
|
487
|
+
auth: Auth
|
|
488
|
+
connectOptions: dict[str, Any]
|
|
489
|
+
llmOptions: dict[str, Any]
|
|
490
|
+
mcpServers: list[McpServer]
|
|
491
|
+
actionHook: ActionHook
|
|
492
|
+
eventHook: ActionHook
|
|
493
|
+
toolHook: ActionHook
|
|
494
|
+
events: list[str]
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
class DialogflowVerb(TypedDict, total=False):
|
|
498
|
+
"""Google Dialogflow agent."""
|
|
499
|
+
|
|
500
|
+
verb: str # "dialogflow"
|
|
501
|
+
id: str
|
|
502
|
+
project: str
|
|
503
|
+
lang: str
|
|
504
|
+
event: str
|
|
505
|
+
environment: str
|
|
506
|
+
welcomeEvent: str
|
|
507
|
+
welcomeEventParams: dict[str, Any]
|
|
508
|
+
noInputTimeout: int
|
|
509
|
+
noInputEvent: str
|
|
510
|
+
passDtmfAsTextInput: bool
|
|
511
|
+
thresholdWordCount: int
|
|
512
|
+
actionHook: ActionHook
|
|
513
|
+
eventHook: ActionHook
|
|
514
|
+
tts: Synthesizer
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
class PipelineVerb(TypedDict, total=False):
|
|
518
|
+
"""Integrated STT -> LLM -> TTS voice AI pipeline."""
|
|
519
|
+
|
|
520
|
+
verb: str # "pipeline"
|
|
521
|
+
id: str
|
|
522
|
+
stt: Recognizer
|
|
523
|
+
tts: Synthesizer
|
|
524
|
+
turnDetection: str | dict[str, Any]
|
|
525
|
+
bargeIn: dict[str, Any]
|
|
526
|
+
noResponseTimeout: int
|
|
527
|
+
llm: dict[str, Any]
|
|
528
|
+
actionHook: ActionHook
|
|
529
|
+
eventHook: ActionHook
|
|
530
|
+
toolHook: ActionHook
|
|
531
|
+
greeting: bool
|
|
532
|
+
earlyGeneration: bool
|
|
533
|
+
noiseIsolation: str | dict[str, Any]
|
|
534
|
+
mcpServers: list[McpServer]
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
# Union of all verb types
|
|
538
|
+
AnyVerb = Union[
|
|
539
|
+
SayVerb,
|
|
540
|
+
PlayVerb,
|
|
541
|
+
GatherVerb,
|
|
542
|
+
DialVerb,
|
|
543
|
+
ListenVerb,
|
|
544
|
+
StreamVerb,
|
|
545
|
+
TranscribeVerb,
|
|
546
|
+
ConferenceVerb,
|
|
547
|
+
EnqueueVerb,
|
|
548
|
+
DequeueVerb,
|
|
549
|
+
HangupVerb,
|
|
550
|
+
PauseVerb,
|
|
551
|
+
RedirectVerb,
|
|
552
|
+
ConfigVerb,
|
|
553
|
+
TagVerb,
|
|
554
|
+
DtmfVerb,
|
|
555
|
+
MessageVerb,
|
|
556
|
+
DubVerb,
|
|
557
|
+
AlertVerb,
|
|
558
|
+
AnswerVerb,
|
|
559
|
+
LeaveVerb,
|
|
560
|
+
SipDeclineVerb,
|
|
561
|
+
SipRequestVerb,
|
|
562
|
+
SipReferVerb,
|
|
563
|
+
LlmVerb,
|
|
564
|
+
S2sVerb,
|
|
565
|
+
OpenaiS2sVerb,
|
|
566
|
+
GoogleS2sVerb,
|
|
567
|
+
DeepgramS2sVerb,
|
|
568
|
+
ElevenlabsS2sVerb,
|
|
569
|
+
UltravoxS2sVerb,
|
|
570
|
+
DialogflowVerb,
|
|
571
|
+
PipelineVerb,
|
|
572
|
+
]
|
jambonz_sdk/validator.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""JSON Schema validation for jambonz verb applications.
|
|
2
|
+
|
|
3
|
+
Uses the ``jsonschema`` library to validate verb dicts against the
|
|
4
|
+
bundled JSON Schema files from @jambonz/schema.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from jsonschema import Draft202012Validator
|
|
14
|
+
from referencing import Registry, Resource
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_schema(path: Path) -> dict[str, Any]:
|
|
18
|
+
with path.open() as f:
|
|
19
|
+
return json.load(f)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class JambonzValidator:
|
|
23
|
+
"""Validates jambonz verb dicts against JSON Schema (draft 2020-12).
|
|
24
|
+
|
|
25
|
+
Schemas are loaded once at construction time from the bundled
|
|
26
|
+
``schema/`` directory.
|
|
27
|
+
|
|
28
|
+
Example::
|
|
29
|
+
|
|
30
|
+
validator = JambonzValidator()
|
|
31
|
+
errors = validator.validate_verb({"verb": "say", "text": "Hello"})
|
|
32
|
+
assert errors == []
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, schema_dir: str | Path | None = None) -> None:
|
|
36
|
+
self._schema_dir = Path(schema_dir) if schema_dir else (
|
|
37
|
+
Path(__file__).resolve().parent / "schema"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Load the root app schema
|
|
41
|
+
app_schema_path = self._schema_dir / "jambonz-app.schema.json"
|
|
42
|
+
self._app_schema = _load_schema(app_schema_path)
|
|
43
|
+
|
|
44
|
+
# Build a registry of all schemas for $ref resolution
|
|
45
|
+
resources: list[tuple[str, Resource]] = [] # type: ignore[type-arg]
|
|
46
|
+
self._store: dict[str, dict[str, Any]] = {}
|
|
47
|
+
|
|
48
|
+
for subdir in ("components", "callbacks", "verbs"):
|
|
49
|
+
subdir_path = self._schema_dir / subdir
|
|
50
|
+
if not subdir_path.is_dir():
|
|
51
|
+
continue
|
|
52
|
+
for schema_file in subdir_path.glob("*.schema.json"):
|
|
53
|
+
schema = _load_schema(schema_file)
|
|
54
|
+
if "$id" in schema:
|
|
55
|
+
self._store[schema["$id"]] = schema
|
|
56
|
+
resources.append((
|
|
57
|
+
schema["$id"],
|
|
58
|
+
Resource.from_contents(schema), # type: ignore[arg-type]
|
|
59
|
+
))
|
|
60
|
+
|
|
61
|
+
# Add the root schema
|
|
62
|
+
self._store[self._app_schema["$id"]] = self._app_schema
|
|
63
|
+
resources.append((
|
|
64
|
+
self._app_schema["$id"],
|
|
65
|
+
Resource.from_contents(self._app_schema), # type: ignore[arg-type]
|
|
66
|
+
))
|
|
67
|
+
|
|
68
|
+
self._registry: Registry = Registry().with_resources(resources) # type: ignore[assignment]
|
|
69
|
+
|
|
70
|
+
# Pre-compile the app validator
|
|
71
|
+
self._app_validator = Draft202012Validator(
|
|
72
|
+
self._app_schema,
|
|
73
|
+
registry=self._registry,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def validate_app(self, verbs: list[dict[str, Any]]) -> list[str]:
|
|
77
|
+
"""Validate a complete verb array against the root app schema.
|
|
78
|
+
|
|
79
|
+
Returns a list of error messages (empty if valid).
|
|
80
|
+
"""
|
|
81
|
+
errors: list[str] = []
|
|
82
|
+
for error in self._app_validator.iter_errors(verbs):
|
|
83
|
+
path = "/".join(str(p) for p in error.absolute_path) or "/"
|
|
84
|
+
errors.append(f"{path}: {error.message}")
|
|
85
|
+
return errors
|
|
86
|
+
|
|
87
|
+
def validate_verb(self, verb: dict[str, Any]) -> list[str]:
|
|
88
|
+
"""Validate a single verb dict against its schema.
|
|
89
|
+
|
|
90
|
+
Returns a list of error messages (empty if valid).
|
|
91
|
+
"""
|
|
92
|
+
verb_name = verb.get("verb")
|
|
93
|
+
if not verb_name:
|
|
94
|
+
return ["missing 'verb' property"]
|
|
95
|
+
|
|
96
|
+
# Look up the verb schema by $id
|
|
97
|
+
schema_id = f"https://jambonz.org/schema/verbs/{verb_name}"
|
|
98
|
+
schema = self._store.get(schema_id)
|
|
99
|
+
if schema is None:
|
|
100
|
+
return [f"unknown verb: {verb_name}"]
|
|
101
|
+
|
|
102
|
+
validator = Draft202012Validator(schema, registry=self._registry)
|
|
103
|
+
errors: list[str] = []
|
|
104
|
+
for error in validator.iter_errors(verb):
|
|
105
|
+
path = "/".join(str(p) for p in error.absolute_path) or "/"
|
|
106
|
+
errors.append(f"{path}: {error.message}")
|
|
107
|
+
return errors
|