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.
Files changed (119) hide show
  1. jambonz_python_sdk-0.2.0.dist-info/METADATA +179 -0
  2. jambonz_python_sdk-0.2.0.dist-info/RECORD +119 -0
  3. jambonz_python_sdk-0.2.0.dist-info/WHEEL +4 -0
  4. jambonz_sdk/__init__.py +52 -0
  5. jambonz_sdk/_signature.py +73 -0
  6. jambonz_sdk/client/__init__.py +15 -0
  7. jambonz_sdk/client/api.py +241 -0
  8. jambonz_sdk/schema/callbacks/amd.schema.json +50 -0
  9. jambonz_sdk/schema/callbacks/base.schema.json +29 -0
  10. jambonz_sdk/schema/callbacks/call-status.schema.json +22 -0
  11. jambonz_sdk/schema/callbacks/conference-status.schema.json +24 -0
  12. jambonz_sdk/schema/callbacks/conference-wait.schema.json +11 -0
  13. jambonz_sdk/schema/callbacks/conference.schema.json +11 -0
  14. jambonz_sdk/schema/callbacks/dequeue.schema.json +19 -0
  15. jambonz_sdk/schema/callbacks/dial-dtmf.schema.json +18 -0
  16. jambonz_sdk/schema/callbacks/dial-hold.schema.json +22 -0
  17. jambonz_sdk/schema/callbacks/dial-refer.schema.json +28 -0
  18. jambonz_sdk/schema/callbacks/dial.schema.json +31 -0
  19. jambonz_sdk/schema/callbacks/enqueue-wait.schema.json +17 -0
  20. jambonz_sdk/schema/callbacks/enqueue.schema.json +27 -0
  21. jambonz_sdk/schema/callbacks/gather-partial.schema.json +54 -0
  22. jambonz_sdk/schema/callbacks/gather.schema.json +60 -0
  23. jambonz_sdk/schema/callbacks/listen.schema.json +21 -0
  24. jambonz_sdk/schema/callbacks/llm.schema.json +30 -0
  25. jambonz_sdk/schema/callbacks/message.schema.json +35 -0
  26. jambonz_sdk/schema/callbacks/pipeline-turn.schema.json +109 -0
  27. jambonz_sdk/schema/callbacks/play.schema.json +36 -0
  28. jambonz_sdk/schema/callbacks/session-new.schema.json +143 -0
  29. jambonz_sdk/schema/callbacks/session-reconnect.schema.json +9 -0
  30. jambonz_sdk/schema/callbacks/session-redirect.schema.json +38 -0
  31. jambonz_sdk/schema/callbacks/sip-refer-event.schema.json +20 -0
  32. jambonz_sdk/schema/callbacks/sip-refer.schema.json +22 -0
  33. jambonz_sdk/schema/callbacks/sip-request.schema.json +27 -0
  34. jambonz_sdk/schema/callbacks/transcribe-translation.schema.json +24 -0
  35. jambonz_sdk/schema/callbacks/transcribe.schema.json +46 -0
  36. jambonz_sdk/schema/callbacks/tts-streaming-event.schema.json +77 -0
  37. jambonz_sdk/schema/callbacks/verb-status.schema.json +57 -0
  38. jambonz_sdk/schema/components/actionHook.schema.json +36 -0
  39. jambonz_sdk/schema/components/actionHookDelayAction.schema.json +37 -0
  40. jambonz_sdk/schema/components/amd.schema.json +68 -0
  41. jambonz_sdk/schema/components/auth.schema.json +18 -0
  42. jambonz_sdk/schema/components/bidirectionalAudio.schema.json +22 -0
  43. jambonz_sdk/schema/components/fillerNoise.schema.json +25 -0
  44. jambonz_sdk/schema/components/llm-base.schema.json +94 -0
  45. jambonz_sdk/schema/components/recognizer-assemblyAiOptions.schema.json +66 -0
  46. jambonz_sdk/schema/components/recognizer-awsOptions.schema.json +52 -0
  47. jambonz_sdk/schema/components/recognizer-azureOptions.schema.json +32 -0
  48. jambonz_sdk/schema/components/recognizer-cobaltOptions.schema.json +34 -0
  49. jambonz_sdk/schema/components/recognizer-customOptions.schema.json +27 -0
  50. jambonz_sdk/schema/components/recognizer-deepgramOptions.schema.json +147 -0
  51. jambonz_sdk/schema/components/recognizer-elevenlabsOptions.schema.json +39 -0
  52. jambonz_sdk/schema/components/recognizer-gladiaOptions.schema.json +8 -0
  53. jambonz_sdk/schema/components/recognizer-googleOptions.schema.json +35 -0
  54. jambonz_sdk/schema/components/recognizer-houndifyOptions.schema.json +53 -0
  55. jambonz_sdk/schema/components/recognizer-ibmOptions.schema.json +54 -0
  56. jambonz_sdk/schema/components/recognizer-nuanceOptions.schema.json +150 -0
  57. jambonz_sdk/schema/components/recognizer-nvidiaOptions.schema.json +39 -0
  58. jambonz_sdk/schema/components/recognizer-openaiOptions.schema.json +59 -0
  59. jambonz_sdk/schema/components/recognizer-sonioxOptions.schema.json +46 -0
  60. jambonz_sdk/schema/components/recognizer-speechmaticsOptions.schema.json +100 -0
  61. jambonz_sdk/schema/components/recognizer-verbioOptions.schema.json +46 -0
  62. jambonz_sdk/schema/components/recognizer.schema.json +216 -0
  63. jambonz_sdk/schema/components/synthesizer.schema.json +82 -0
  64. jambonz_sdk/schema/components/target.schema.json +105 -0
  65. jambonz_sdk/schema/components/vad.schema.json +48 -0
  66. jambonz_sdk/schema/jambonz-app.schema.json +113 -0
  67. jambonz_sdk/schema/verbs/alert.schema.json +34 -0
  68. jambonz_sdk/schema/verbs/answer.schema.json +22 -0
  69. jambonz_sdk/schema/verbs/conference.schema.json +107 -0
  70. jambonz_sdk/schema/verbs/config.schema.json +221 -0
  71. jambonz_sdk/schema/verbs/deepgram_s2s.schema.json +81 -0
  72. jambonz_sdk/schema/verbs/dequeue.schema.json +51 -0
  73. jambonz_sdk/schema/verbs/dial.schema.json +200 -0
  74. jambonz_sdk/schema/verbs/dialogflow.schema.json +148 -0
  75. jambonz_sdk/schema/verbs/dtmf.schema.json +49 -0
  76. jambonz_sdk/schema/verbs/dub.schema.json +103 -0
  77. jambonz_sdk/schema/verbs/elevenlabs_s2s.schema.json +81 -0
  78. jambonz_sdk/schema/verbs/enqueue.schema.json +53 -0
  79. jambonz_sdk/schema/verbs/gather.schema.json +190 -0
  80. jambonz_sdk/schema/verbs/google_s2s.schema.json +42 -0
  81. jambonz_sdk/schema/verbs/hangup.schema.json +36 -0
  82. jambonz_sdk/schema/verbs/leave.schema.json +22 -0
  83. jambonz_sdk/schema/verbs/listen.schema.json +127 -0
  84. jambonz_sdk/schema/verbs/llm.schema.json +44 -0
  85. jambonz_sdk/schema/verbs/message.schema.json +82 -0
  86. jambonz_sdk/schema/verbs/openai_s2s.schema.json +42 -0
  87. jambonz_sdk/schema/verbs/pause.schema.json +36 -0
  88. jambonz_sdk/schema/verbs/pipeline.schema.json +240 -0
  89. jambonz_sdk/schema/verbs/play.schema.json +96 -0
  90. jambonz_sdk/schema/verbs/redirect.schema.json +34 -0
  91. jambonz_sdk/schema/verbs/rest_dial.schema.json +113 -0
  92. jambonz_sdk/schema/verbs/s2s.schema.json +39 -0
  93. jambonz_sdk/schema/verbs/say.schema.json +107 -0
  94. jambonz_sdk/schema/verbs/sip-decline.schema.json +58 -0
  95. jambonz_sdk/schema/verbs/sip-refer.schema.json +58 -0
  96. jambonz_sdk/schema/verbs/sip-request.schema.json +54 -0
  97. jambonz_sdk/schema/verbs/stream.schema.json +103 -0
  98. jambonz_sdk/schema/verbs/tag.schema.json +41 -0
  99. jambonz_sdk/schema/verbs/transcribe.schema.json +57 -0
  100. jambonz_sdk/schema/verbs/ultravox_s2s.schema.json +41 -0
  101. jambonz_sdk/types/__init__.py +139 -0
  102. jambonz_sdk/types/components.py +250 -0
  103. jambonz_sdk/types/rest.py +59 -0
  104. jambonz_sdk/types/session.py +55 -0
  105. jambonz_sdk/types/verbs.py +572 -0
  106. jambonz_sdk/validator.py +107 -0
  107. jambonz_sdk/verb_builder.py +316 -0
  108. jambonz_sdk/verb_builder.pyi +1133 -0
  109. jambonz_sdk/verb_registry.py +102 -0
  110. jambonz_sdk/webhook/__init__.py +10 -0
  111. jambonz_sdk/webhook/middleware.py +63 -0
  112. jambonz_sdk/webhook/response.py +43 -0
  113. jambonz_sdk/websocket/__init__.py +15 -0
  114. jambonz_sdk/websocket/audio_client.py +11 -0
  115. jambonz_sdk/websocket/audio_stream.py +151 -0
  116. jambonz_sdk/websocket/client.py +165 -0
  117. jambonz_sdk/websocket/endpoint.py +193 -0
  118. jambonz_sdk/websocket/router.py +87 -0
  119. 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
+ ]
@@ -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