Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,35 @@ Raises:
- Prefer region comments for grouping methods in files that already use them.
- End edited Markdown and YAML files with a trailing newline.

## What this package actually ships (and what it does not)

This client installs **`ondewo/csi` only**. It contains **no** `ondewo/nlu`, `ondewo/s2t` or
`ondewo/t2s` `*_pb2` modules — `pyproject.toml` declares `ondewo-nlu-client==7.0.2`,
`ondewo-s2t-client` and `ondewo-t2s-client`, and the csi protos reference those packages'
descriptors at import time. Verified against the installed `.dist-info/RECORD`: this dist claims
zero files under `ondewo/nlu/`.

That matters when someone reasons about descriptor-pool collisions. The sibling client
`ondewo-vtsi-client-python` **does** vendor foreign protos (55 files under `ondewo/nlu`, plus
`ondewo/{s2t,t2s,sip,qa}`), so *it* has to be regenerated in lockstep with the service clients. This
one does not — bump it only when the `ondewo/csi` surface itself changes. Do not "helpfully" add
vendored nlu protos here; it would create exactly the duplicate-file-in-pool crash the current
layout avoids.

Consumers pin this repo by git rev, not by PyPI version: `ondewo-vtsi` and `ondewo-csi` both pin
rev **`23b568fa`** (the `CALL_ENDED` control-status regen) in their `pyproject.toml`. Never rebase or
force-push a commit that a pin references.

## Jenkins — never trigger a multibranch scan or branch indexing

**NEVER trigger a Jenkins multibranch scan or branch indexing.** Do not call a multibranch/folder job's
`build`, `scan`, or reindex endpoints, click "Scan Repository Now" / "Build Now" on a folder, run
`p4 scan`, or use any API/CLI that reindexes branches or scans the repository. A scan/reindex runs across
**every** branch, consumes CI resources, and can kick off unintended builds and deploys.

If a branch is not building — it was not discovered, or its job is marked `buildable: false` / orphaned —
**report it and stop**. Let the user or a Jenkins admin adjust branch-discovery/config or rename the branch
to the convention. Never force a build by scanning or reindexing.
## Release gotchas (hard-won this session)

These bit us during the 6.14.0 release. Keep them in mind when releasing.
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ create_async_services: ## Create async services for all synchronous services
cp "$$file" "$$dir/async_$$filename"; \
done; \
for file in "$$dir"/async_*.py; do \
perl -i -pe 'unless(/def stub/){ s/^([[:space:]]*)def /$$1async def /g; s/self\.stub/await self.stub/g; s/\(BaseServicesInterface\)/(AsyncBaseServicesInterface)/g; s/base_services_interface/async_base_services_interface/g; s/import BaseServicesInterface/import AsyncBaseServicesInterface/g; }' \
perl -i -pe 'unless(/def stub/){ s/^([[:space:]]*)def /$$1async def /g; s/self\.stub/await self.stub/g; s/\(BaseServicesInterface\)/(AsyncBaseServicesInterface)/g; s/base_services_interface/async_base_services_interface/g; s/import BaseServicesInterface/import AsyncBaseServicesInterface/g; s/core\.services_interface/core.async_services_interface/g; s/import ServicesInterface/import AsyncServicesInterface/g; s/\((?<!Async)ServicesInterface\)/(AsyncServicesInterface)/g; }' \
"$$file"; \
done; \
done
Expand Down
1 change: 0 additions & 1 deletion examples/streamer.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ def create_s2s_request(
if save_to_disk:
f = open(f"record_{session_id}.raw", "wb")

global PLAYING
while True: # not self.stop.done():
if PLAYING:
print("PLAYING")
Expand Down
64 changes: 32 additions & 32 deletions ondewo/csi/conversation_pb2.py

Large diffs are not rendered by default.

48 changes: 46 additions & 2 deletions ondewo/csi/conversation_pb2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ class _ControlStatusEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
"""Status that control stream needs to stop immediately"""
VAD_START_OF_SPEECH: _ControlStatus.ValueType # 2
"""Status that voice activity detection detected start of speech"""
BARGE_IN: _ControlStatus.ValueType # 3
"""Status that the caller started speaking while the bot was speaking (barge-in): playback pauses and the un-played audio remainder is buffered"""
RESUME_PLAYBACK: _ControlStatus.ValueType # 4
"""Status sent by the server to the client to resume playback of the buffered audio remainder after a false interruption (no committed transcription final arrived within the false interruption timeout)"""
DISCARD_REMAINDER: _ControlStatus.ValueType # 5
"""Status sent by the server to the client to discard the buffered audio remainder because the interruption committed as a real turn"""
PLAYBACK_RESUMED: _ControlStatus.ValueType # 6
"""Status reported by the client to the server via <code>SetControlStatus</code> that playback of the buffered audio remainder has resumed"""
PLAYBACK_DONE: _ControlStatus.ValueType # 7
"""Status reported by the client to the server via <code>SetControlStatus</code> that playback of an utterance has completed"""
CALL_ENDED: _ControlStatus.ValueType # 8
"""Status reported by ondewo-sip to the server via <code>SetControlStatus</code> that the phone call has ENDED and the server must reset ALL per-call state in place (persistent-listener model). It is a command, not a state: it is never pushed onto the control stream"""

class ControlStatus(_ControlStatus, metaclass=_ControlStatusEnumTypeWrapper):
"""Control status."""
Expand All @@ -60,6 +72,18 @@ EMERGENCY_STOP: ControlStatus.ValueType # 1
"""Status that control stream needs to stop immediately"""
VAD_START_OF_SPEECH: ControlStatus.ValueType # 2
"""Status that voice activity detection detected start of speech"""
BARGE_IN: ControlStatus.ValueType # 3
"""Status that the caller started speaking while the bot was speaking (barge-in): playback pauses and the un-played audio remainder is buffered"""
RESUME_PLAYBACK: ControlStatus.ValueType # 4
"""Status sent by the server to the client to resume playback of the buffered audio remainder after a false interruption (no committed transcription final arrived within the false interruption timeout)"""
DISCARD_REMAINDER: ControlStatus.ValueType # 5
"""Status sent by the server to the client to discard the buffered audio remainder because the interruption committed as a real turn"""
PLAYBACK_RESUMED: ControlStatus.ValueType # 6
"""Status reported by the client to the server via <code>SetControlStatus</code> that playback of the buffered audio remainder has resumed"""
PLAYBACK_DONE: ControlStatus.ValueType # 7
"""Status reported by the client to the server via <code>SetControlStatus</code> that playback of an utterance has completed"""
CALL_ENDED: ControlStatus.ValueType # 8
"""Status reported by ondewo-sip to the server via <code>SetControlStatus</code> that the phone call has ENDED and the server must reset ALL per-call state in place (persistent-listener model). It is a command, not a state: it is never pushed onto the control stream"""
global___ControlStatus = ControlStatus

class _ControlMessageServiceName:
Expand Down Expand Up @@ -1153,6 +1177,18 @@ class S2sStreamResponse(google.protobuf.message.Message):
DETECT_INTENT_RESPONSE_FIELD_NUMBER: builtins.int
SYNTHESIZE_RESPONSE_FIELD_NUMBER: builtins.int
SIP_TRIGGER_FIELD_NUMBER: builtins.int
UTTERANCE_ID_FIELD_NUMBER: builtins.int
CHUNK_INDEX_FIELD_NUMBER: builtins.int
LAST_CHUNK_FIELD_NUMBER: builtins.int
TURN_EPOCH_FIELD_NUMBER: builtins.int
utterance_id: builtins.str
"""Optional. Identifier of the utterance this response belongs to. All chunks of one synthesized utterance share the same <code>utterance_id</code>"""
chunk_index: builtins.int
"""Optional. Zero-based index of this chunk within the utterance identified by <code>utterance_id</code>"""
last_chunk: builtins.bool
"""Optional. If <code>true</code>, this is the last chunk of the utterance identified by <code>utterance_id</code>"""
turn_epoch: builtins.int
"""Optional. Monotonic turn epoch of the conversation turn that produced this response. Used by consumers to fence stale audio of an interrupted turn"""
@property
def detect_intent_response(self) -> ondewo.nlu.session_pb2.DetectIntentResponse:
"""full NLU detect intent response"""
Expand All @@ -1171,9 +1207,13 @@ class S2sStreamResponse(google.protobuf.message.Message):
detect_intent_response: ondewo.nlu.session_pb2.DetectIntentResponse | None = ...,
synthesize_response: ondewo.t2s.text_to_speech_pb2.SynthesizeResponse | None = ...,
sip_trigger: global___SipTrigger | None = ...,
utterance_id: builtins.str = ...,
chunk_index: builtins.int = ...,
last_chunk: builtins.bool = ...,
turn_epoch: builtins.int = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["detect_intent_response", b"detect_intent_response", "response", b"response", "sip_trigger", b"sip_trigger", "synthesize_response", b"synthesize_response"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["detect_intent_response", b"detect_intent_response", "response", b"response", "sip_trigger", b"sip_trigger", "synthesize_response", b"synthesize_response"]) -> None: ...
def ClearField(self, field_name: typing.Literal["chunk_index", b"chunk_index", "detect_intent_response", b"detect_intent_response", "last_chunk", b"last_chunk", "response", b"response", "sip_trigger", b"sip_trigger", "synthesize_response", b"synthesize_response", "turn_epoch", b"turn_epoch", "utterance_id", b"utterance_id"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["response", b"response"]) -> typing.Literal["detect_intent_response", "synthesize_response", "sip_trigger"] | None: ...

global___S2sStreamResponse = S2sStreamResponse
Expand Down Expand Up @@ -1298,14 +1338,18 @@ class ControlStreamResponse(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor

CONTROL_STATUS_FIELD_NUMBER: builtins.int
EPOCH_FIELD_NUMBER: builtins.int
control_status: global___ControlStatus.ValueType
"""Control status"""
epoch: builtins.int
"""Monotonic barge-in epoch/sequence number so control status transitions are correlatable with the <code>S2sStreamResponse</code> <code>turn_epoch</code> and a second barge-in during a resumed remainder can never be coalesced away"""
def __init__(
self,
*,
control_status: global___ControlStatus.ValueType = ...,
epoch: builtins.int = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["control_status", b"control_status"]) -> None: ...
def ClearField(self, field_name: typing.Literal["control_status", b"control_status", "epoch", b"epoch"]) -> None: ...

global___ControlStreamResponse = ControlStreamResponse

Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ dependencies = [
"loguru",
"numpy",
"ondewo-client-utils>=3.2.0",
"ondewo-nlu-client",
"ondewo-nlu-client==7.0.2",
"ondewo-s2t-client",
"ondewo-t2s-client",
"requests",
Expand All @@ -54,6 +54,7 @@ dev = [
"python-dotenv",
"setuptools",
"typing_extensions",
"types-requests",
"pytest-cov",
"pytest-asyncio",
]
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/client/test_keycloak.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def _token_body(access_token: str, refresh_token: str, expires_in: int) -> Dict[

def _build_provider(
transport: FakeTransport,
token_expiration_in_s: int | None = None,
token_expiration_in_s: Optional[int] = None,
) -> KeycloakTokenProvider:
"""Construct a `KeycloakTokenProvider` wired to the fake transport and shared test fixtures.

Expand Down Expand Up @@ -1055,7 +1055,7 @@ def _build_background_provider(
transport: FakeTransport,
clock: Dict[str, float],
stop_event: ScriptedEvent,
token_expiration_in_s: int | None = None,
token_expiration_in_s: Optional[int] = None,
) -> KeycloakTokenProvider:
"""Construct a provider wired to an injected clock + scripted event, background thread off.

Expand Down
57 changes: 43 additions & 14 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading