Skip to content

Release 0.17.0a1 - #84

Open
github-actions[bot] wants to merge 88 commits into
masterfrom
release-0.17.0a1
Open

Release 0.17.0a1#84
github-actions[bot] wants to merge 88 commits into
masterfrom
release-0.17.0a1

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 July 26, 2026 12:53
Persona.chat(messages: List[AgentMessage], sess) and Persona.stream(...)
declare AgentMessage in their type contract and forward straight to
QuestionSolversService.chat_completion()/stream_completion(), which reads
messages[-1].content for QuestionSolver-based plugins.

run_chat()/run_stream()'s stateless path (CHAT_MEMORY=off, the default)
passed the raw OpenAI-style message dicts from the request straight
through without converting them first. Any QuestionSolver-based plugin
(e.g. ovos-solver-plugin-ddg) then crashed with:

  AttributeError: 'dict' object has no attribute 'content'

which the outer HTTP layer's token-counting code turned into a second,
more confusing error further down the stack:

  AttributeError: 'NoneType' object has no attribute 'split'

chat.py already has a _messages_to_agent() converter for the ChatEngine
path (continue_chat), it just wasn't reused here. This adds an
equivalent local helper in persona.py and applies it at both call sites.

Confirmed by constructing AgentMessage/dict objects directly against a
real QuestionSolversService instance and reproducing the exact
AttributeError with unconverted dicts.

Found while packaging this server as a Home Assistant OS Supervisor
add-on: https://github.com/andlo/haos-ovos-addons
…through tests (#68)

* refactor: unify OpenAI-dict to AgentMessage conversion in persona.py

Move _flatten_text/_role/_messages_to_agent from chat.py into persona.py
and drop the duplicate _dicts_to_agent_messages, so the stateless run_chat
and run_stream paths get the same tool_calls/tool_call_id/name-preserving,
function->tool role mapping that chat.py's tool-calling path already had.
chat.py now imports the shared helpers instead of redefining them.

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

* test: assert AgentMessage contract on the stateless run_chat/run_stream path

The old tests asserted client dicts passed through unconverted; that
contract no longer holds now that run_chat/run_stream always convert to
AgentMessage. Update them to check the converted objects, and add a
regression test covering legacy role mapping, unknown-role fallback, and
content-parts flattening for both run_chat and run_stream.

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

* test: update stale passthrough assertions to the AgentMessage contract

PR #67 fixed run_chat/run_stream to convert client message dicts to
AgentMessage before calling persona.chat/stream, but these tests still
asserted the old dict-passthrough contract (subscript access, string
role comparison). Assert on the converted AgentMessage objects instead,
preserving each test's original intent (role mapping, system-message
prepending, content-parts flattening).

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

* test: assert AgentMessage contract in a2a executor forwarding test

The a2a async tests only run where pytest-asyncio is active, so this
stale dict-passthrough assertion was missed by the local sweep and only
surfaced in CI.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* 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.
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