Skip to content

Control-plane methods leak the raw generated ApiException instead of a typed OtariError #20

Description

@njbrake

Note: this issue was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.

Summary

Control-plane methods (keys, users, budgets, pricing, usage) raise the raw generated otari._client.exceptions.ApiException on any HTTP error, while the inference path raises a typed otari.errors.OtariError. SDK consumers get two different exception types depending on which surface they call, and the CLI prints a full traceback for control-plane errors instead of a clean message.

Repro

export GATEWAY_API_BASE=https://<gateway>
GATEWAY_ADMIN_KEY=wrong-key otari keys list      # raw UnauthorizedException traceback
GATEWAY_API_KEY=gw-<well-formed-but-invalid> otari completion -m openai:x "hi"   # clean: Error: Invalid API key

Same underlying server 401, two behaviors.

Root cause

  • Inference methods map the generated exception in src/otari/client.py:
    except ApiException as exc:
        raise self._map_api_exception(exc) from exc
  • Control-plane resources in src/otari/control_plane.py call the generated client directly with no equivalent wrapper:
    def list(self, ...): return self.raw.list_keys_v1_keys_get(...)
  • _map_api_exception lives on the inference client base, so the control plane had no shared way to reuse it.

The CLI's handle_errors() only catches OtariError/ValueError, so the raw ApiException escapes to a traceback (which also dumps response headers).

Proposed fix

Extract the mapping into a module-level map_api_exception helper and route every control-plane ergonomic alias through it (leaving the raw escape hatch unwrapped), so control-plane callers get the same typed OtariError contract as the inference path. PR incoming.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions