Skip to content

Release 0.17.2a1 - #91

Open
github-actions[bot] wants to merge 94 commits into
masterfrom
release-0.17.2a1
Open

Release 0.17.2a1#91
github-actions[bot] wants to merge 94 commits into
masterfrom
release-0.17.2a1

Conversation

@github-actions

Copy link
Copy Markdown

Human review requested!

renovate Bot and others added 30 commits December 18, 2025 03:33
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* chore: migrate setup.py→pyproject.toml, consolidate CI workflows

Replace setup.py with pyproject.toml (dynamic version from version.py).
Add build_tests, license_tests, lint, pip_audit workflows using
OpenVoiceOS/gh-automations@dev reusable workflows. Migrate
publish_stable and release_workflow from TigreGotico/gh-automations@master
to OpenVoiceOS/gh-automations@dev; add workflow_dispatch trigger and
bot-safety guard.

split from #25

* fix: define __version__ attr required by pyproject dynamic version

* test: guard dynamic-version contract (version.py ↔ __version__ fields)

Asserts VERSION_* integers are non-negative, __version__ is correctly
derived from them, and the string matches PEP-440 format.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…t in chat.py (#40)

* fix: declare uvicorn+ovos-workshop deps; restore py3.9 compat in chat.py

Fixes #39 (partial — OPM lang-detector fallback fix in ovos-lang-detector-classics-plugin,
see fix/issue-39-cld2-exception branch there)

(a) Add uvicorn and ovos-workshop to [project].dependencies in pyproject.toml.
    Both are imported at runtime (__main__.py and ollama.py respectively) but
    were not declared, causing ImportError in clean environments.

(b) Replace two multi-line f-string expressions in streaming_completion_response
    (chat.py lines ~285 and ~302) with intermediate dict variables.
    Multi-line dict literals inside f-string {} require Python 3.12 (PEP 701)
    but the package declares requires-python >= 3.9.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Delete tests/test_issue_39.py

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…aths (#29)

* feat: vendor-prefixed OpenAI/Ollama routers + deprecated legacy paths

* test: cover deprecated paths middleware and route registration (split/router-infra)

- Verify Deprecation + Link headers on /v1 and /api legacy paths
- Verify canonical paths receive no deprecation headers
- Cover _build_successor_path mapping and passthrough
- Cover FastAPI 422 responses for malformed/missing bodies
- Smoke-test register_deprecated_routes mounts /v1/ legacy paths

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: normalize Google-style docstrings in chat and ollama routers

Remove ``(Type)`` annotations from Args/Returns sections (redundant with
function signatures). Condense multi-line boilerplate docstrings on
lifespan managers and inner streaming generators to one-liners.
No logic changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: A2A server endpoint (/a2a) with agent card + executor

* test: full A2A server coverage — agent card, executor, cancel, SDK-absent path, invalid base_url

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: improve OVOSPersonaAgentExecutor.__init__ docstring

Add summary sentence to the __init__ docstring; no logic changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
)

* feat(compat): Anthropic Claude-compatible endpoints (/anthropic/v1)

* test: full Anthropic compat coverage — schema, multi-turn, system, streaming, errors

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: real-SDK e2e against live server + enable CI test execution

Add tests/e2e/test_e2e_anthropic.py driving the official anthropic SDK
(Anthropic(base_url=...)) against a live uvicorn-served app, covering
non-streaming, system prompt, multi-turn and streaming. Add anthropic to
the dev extra and wire build_tests.yml to install it and run the suite.

* test: use asyncio.run in A2A executor tests (py3.14 compatibility)

asyncio.get_event_loop() raises on Python 3.14 (and whenever a prior test
closes the main-thread loop); switch the A2A executor tests to asyncio.run
so the suite is loop-state independent.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(compat): Google Gemini-compatible endpoints (/gemini/v1beta)

* test: full Gemini compat coverage — schema, multi-turn, system-instruction, streaming, errors

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: real-SDK e2e against live server + enable CI test execution

Add tests/e2e/test_e2e_gemini.py driving the official google-genai SDK
(genai.Client(http_options=HttpOptions(base_url=...))) against a live
uvicorn-served app, covering non-streaming, system instruction, multi-turn
and streaming. Add google-genai to the dev extra and wire build_tests.yml
to install it and run the suite.

* test: use asyncio.run in A2A executor tests (py3.14 compatibility)

asyncio.get_event_loop() raises on Python 3.14 (and whenever a prior test
closes the main-thread loop); switch the A2A executor tests to asyncio.run
so the suite is loop-state independent.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(compat): AWS Bedrock-compatible endpoints (/bedrock/model)

* test: full AWS Bedrock compat coverage — all model families, invoke, stream, converse, schema errors

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(compat): emit AWS event-stream framing for Bedrock streaming

invoke-with-response-stream now encodes vnd.amazon.eventstream binary frames
(prelude/headers/payload with CRC32 checksums) instead of SSE, so boto3's
invoke_model_with_response_stream parses the stream natively.

Add tests/e2e/test_e2e_bedrock.py driving the official boto3 bedrock-runtime
client (invoke_model for Claude + Titan, converse, streaming). Add boto3 to
the dev extra and wire build_tests.yml to run the suite.

* test(compat): decode Bedrock stream as event-stream frames in unit tests

Update the invoke-with-response-stream unit tests to decode the
vnd.amazon.eventstream binary framing via botocore's EventStreamBuffer,
matching the streaming format boto3 consumes.

* test: use asyncio.run in A2A executor tests (py3.14 compatibility)

asyncio.get_event_loop() raises on Python 3.14 (and whenever a prior test
closes the main-thread loop); switch the A2A executor tests to asyncio.run
so the suite is loop-state independent.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(compat): HuggingFace TGI-compatible endpoints (/tgi)

* test: full TGI compat coverage — schema, generate, streaming, info/health, errors

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(compat): serve TGI generation at endpoint root for InferenceClient

The maintained huggingface_hub InferenceClient posts to the bare endpoint
URL and selects streaming via the body 'stream' flag; add a root dispatch
route delegating to the shared non-streaming/streaming handlers so the
official client works against the server unchanged.

Add tests/e2e/test_e2e_tgi.py driving InferenceClient (non-stream, details,
stream) plus /health, /info and the native /generate route. Add
huggingface_hub to the dev extra and wire build_tests.yml to run the suite.

* test: use asyncio.run in A2A executor tests (py3.14 compatibility)

asyncio.get_event_loop() raises on Python 3.14 (and whenever a prior test
closes the main-thread loop); switch the A2A executor tests to asyncio.run
so the suite is loop-state independent.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
JarbasAl and others added 30 commits August 4, 2026 00:26
* fix: loader never passes toolbox_id; plugins own their id

* fix: loader always calls cls(config=cfg, bus=bus), no fallback

The OPM ToolBox contract puts toolbox_id back where it belongs: a
class attribute the plugin declares itself, validated at construction.
The loader's job is simply to call the constructor the same way every
other OPM plugin factory does — cls(config=cfg, bus=bus) — with no
try/except duck-typing on the signature.

_load_toolboxes() now sources config from settings.persona_config (the
same top-level config blob solver plugins already receive, keyed by
plugin name per the existing self.llm_solver: {...} convention) since
there is no dedicated per-toolbox config section in Settings yet. bus
is always None: this server has no message bus of its own.

Test stub ToolBoxes now declare a class-level toolbox_id and take
config/bus like the real contract; dropped the test asserting the old
no-argument cls() + toolbox_id-kwarg fallback behavior.

Authored with Claude Code.

* fix: test stubs pass toolbox_id via super().__init__, not class attribute

Maintainer rejected the class-attribute toolbox_id design. The loader in
tools.py was already correct (cls(config=cfg, bus=bus), no signature
guessing) and is unchanged. Test stub ToolBoxes (FakeToolBox, BoomBox x3)
now supply toolbox_id through super().__init__(toolbox_id=..., config=,
bus=) like real plugins will. Reworks the "missing class toolbox_id is
skipped" test into "a plugin that fails to construct (e.g. forgets to
forward toolbox_id to the base) is still skipped by the loader" — there
is no more class-attribute requirement to validate.

* fix: require ovos-plugin-manager>=2.11.1a1 for the config kwarg
mcp 2.x removed mcp.server.fastmcp.FastMCP; fastmcp (Apache-2.0) is the maintained continuation. Also fixes remote reachability: mcp 1.x rejected non-localhost Host headers with 421, so the mounted endpoint could not be exposed behind a proxy.
One process loads a directory of personas; clients pick one with the OpenAI `model` field, and the model-listing endpoints enumerate them. Includes a test-isolation fix: the persona registry is process-global, so a module building a real app left it populated for later modules whose standalone routers rely on the empty-registry fallback.
Examples used chat_module, which the loader does not read at all (ovos_persona resolves handlers first, then solvers as the legacy fallback), so copy-pasted personas silently loaded no plugins. Updates the examples to handlers and links the technical manual for the full schema.
Client-supplied OpenAI tools are relayed for the caller to execute; the persona's own ToolBox plugins are executed server-side in a bounded agentic loop. Includes a fix for client tools shadowed by a persona tool of the same name, which were offered to the model twice and executed server-side instead of relayed.
_load_toolboxes passed the entire persona config blob to every ToolBox
plugin. Both shipped implementations read their settings straight off
`config` -- MCPToolBox wants transport/command/url, UTCPToolBox likewise --
so the keys were missing, the constructor raised KeyError('command'),
discover_tools() swallowed it into a warning, and the persona served zero
tools with no visible error.

Plugins now get the section keyed by their plugin name, the same shape solver
plugins already receive. The full blob is still passed when the persona
defines no section, so a toolbox that self-locates keeps working.

Verified against a real MCP server: the registry goes from [] to ['echo'],
and the server-side tool loop then executes it end to end.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ace/append)

A client-supplied system message is handled per the persona's system_prompt_strategy: ignore (default), replace, or append. The strategy is applied before the chat engine sees the messages, so it overrides engine-level allow_system_prompts — documented, since a persona that opted into merging client prompts needs 'append' to keep that behaviour.
MCP now mounts only when the server is started with --mcp, matching the other three OVOS servers, so installing the mcp extra no longer silently exposes a tool endpoint. The ImportError guard now logs a warning naming the install command instead of passing silently. BREAKING: deployments relying on auto-mount lose /mcp until they pass --mcp.
…eady receive (#86)

The legacy completions endpoint accepts the OpenAI `user` field and the A2A
executor receives the protocol's `context_id`, but both discarded it before
calling run_chat/run_stream, so in CHAT_MEMORY=transparent mode every caller on
those two surfaces landed in the process-global "default" memory bucket and read
back each other's history.

`context_id` is the A2A conversation identifier (a collection of tasks and
messages), not a per-task id, so it is stable across the turns of one
conversation.

Anonymous requests are unchanged: they still fall back to the documented single
default session, which transparent mode documents as single-user only.
`pip install ".[a2a]"` resolved `a2a-sdk>=0.3.0` to 1.1.2, whose
`a2a.server.apps` module was removed (moved to a route-factory API in
`a2a.server.routes`). The adapter's try/except therefore always caught an
ImportError and set `_A2A_AVAILABLE = False`: the whole /a2a surface was
dead code, exercised only by tests that mock the SDK's own internals into
sys.modules and so could never notice.

The 1.x line also changed the type shapes the adapter built against:
`AgentCard.url` became `AgentCard.supported_interfaces` (a list of
`AgentInterface`), `Part` collapsed from `Part(root=TextPart(...))` to a
flat `Part(text=...)`, `TaskState`/`AgentSkill` moved from a pydantic model
to protobuf-generated messages, and `TaskStatusUpdateEvent.final` was
dropped in favor of the terminal-state-based `TaskUpdater` helper. The
framework additionally requires an explicit `Task` event before any
status/artifact update referencing it, and validates that an artifact
`append=True` chunk references an artifact_id that was already created.

I moved to a2a-sdk>=1.1.2 rather than trying to straddle both lines on
import: 0.3.x cannot even be installed fresh anymore (it resolves to
1.1.2), and the 1.x shapes are incompatible enough (protobuf messages
instead of pydantic, different Part/AgentCard fields) that a dual-import
adapter would roughly double the surface for a version nobody can `pip
install` cleanly. But the *wire* protocol is a different question from the
install: already-deployed 0.3.x clients still exist and this server does
not control them, so `create_jsonrpc_routes(..., enable_v0_3_compat=True)`
and a second `create_agent_card_routes(..., card_url="/.well-known/agent.json")`
registration keep those callers working (message/send accepted, card
discoverable at the old well-known path) instead of rejecting them outright
with -32009. If a later change drops that compat flag, existing 0.3.x
clients stop working on the wire — that would be a deliberate protocol
break, not just an install-time one.

Rewrote `_agent_card`, `OVOSPersonaAgentExecutor` (now using
`RequestContext.get_user_input()` and `TaskUpdater` instead of manually
extracting `TextPart`s and building status events) and
`create_a2a_application`, which now returns a plain `Starlette` app (the
`A2AStarletteApplication`/`.build()` wrapper no longer exists) built from
`create_agent_card_routes` + `create_jsonrpc_routes`. Updated the two call
sites in `__init__.py` that called `.build()`.

The agent card advertises `base_url.rstrip("/") + "/"`, not `base_url`
verbatim: the JSON-RPC route is registered with `rpc_url="/"` inside a
Starlette app mounted at `base_url`'s path, so the real endpoint has a
trailing slash. A card advertising the bare path gets Starlette's 307 on
a real POST, and the a2a-sdk client transport does not follow redirects —
it just fails. The round-trip test does not pass `follow_redirects=True`
for the same reason: it needs to fail the way a real client would if this
regressed. Also dropped the redundant `httpx>=0.27` pin from the `a2a`
extra — a2a-sdk 1.1.2 already requires `httpx>=0.28.1`.

Rewrote `tests/test_a2a_server.py` and `tests/test_a2a_unit.py` to import
the real installed a2a-sdk instead of injecting fake stub modules, added a
genuine round trip (a real `a2a.client` over an in-process ASGI transport,
through the mounted FastAPI app, `message/send` in and a completed `Task`
with the persona's streamed text out), a 0.3.x-method-name compat check,
a dual-well-known-path discovery check, and assertions that pin
`capabilities`/`default_input_modes`/`default_output_modes` so they can't
silently drift. Both files `importorskip`/`skipif` as a single unit when
the extra is absent, rather than "passing" against a hand-rolled stub.
Added `a2a` to the CI `build_tests.yml` install_extras — it was never
installed there, so this suite always skipped in CI too.

Rebased onto current dev (#86 merged): kept `session_id=context.context_id`
in the `run_stream` call (a2a.py merged automatically) and ported the three
memory-keying regression tests from #86's `test_a2a_unit.py` onto the real
`RequestContext`/`EventQueueLegacy` shapes instead of dropping them in the
conflict resolution — they still assert two conversations don't share a
memory bucket, one conversation reuses its bucket across turns, and a
caller that sends no context_id gets a fresh bucket each time.

Fail-before (trailing-slash fix): temporarily un-normalized `rpc_url` back
to bare `base_url` with a2a-sdk 1.1.2 installed — `test_agent_card_url_has_trailing_slash`
and `test_real_round_trip_send_message` both failed with the real
`A2AClientError: HTTP Error 307`. Restored the fix: both pass.

Fail-before (import path): with a2a-sdk 1.1.2 actually installed, reverting
only `ovos_persona_server/a2a.py` (keeping the new tests) makes all 32 a2a
tests skip (`_A2A_AVAILABLE` false, against the real `ModuleNotFoundError:
No module named 'a2a.server.apps'`). Restoring the fix: 32/32 pass.

Full suite (`tests --ignore=tests/e2e`, same venv, `a2a` extra installed
both times): unmodified `origin/dev` = 300 non-a2a + 36 old fully-mocked
a2a tests (pass regardless of whether a2a-sdk is even installed) = 336
passed. This branch = 300 unchanged non-a2a + 32 real a2a tests (including
the 3 ported memory tests) = 332 passed, 0 failed.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants