mpfaffenberger / mpfaffenberger/code_puppy

gemini_code_assist.py: model_name is a method (not property), local StreamedResponse shadows pydantic-ai's and drops tool calls; per-request clients and stale OAuth token

Open
#401 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
814
Forks
278
Avg merge
2d 5h
Merged PRs (30d)
76

Description

Problem

code_puppy/gemini_code_assist.py defines GeminiCodeAssistModel.model_name as a plain method, not a property (lines 59-61):

    def model_name(self) -> str:
        \"\"\"Return the model name.\"\"\"
        return self._model_name

Every other Model implementation in the codebase (GeminiModel, RoundRobinModel) and pydantic-ai's own Model base class define model_name as a @property. Anywhere pydantic-ai (or code_puppy telemetry/messaging) reads model.model_name expecting a string, this class returns a bound method object — it stringifies as <bound method GeminiCodeAssistModel.model_name of ...> in logs/usage tracking and breaks comparisons like model.model_name == "gemini-...".

The local StreamedResponse class in the same file repeats the mistake for model_name(), usage() and timestamp() while pydantic-ai's real StreamedResponse exposes model_name/timestamp as properties.

Additional problems in the same file

  1. Name shadowing: the module defines its own class StreamedResponse that is not pydantic_ai.models.StreamedResponse but the type annotation AsyncIterator[StreamedResponse] on request_stream makes it look like the pydantic-ai contract is satisfied. It implements neither _get_event_iterator nor the parts-manager protocol, so streaming via pydantic-ai will fail. gemini_model.py solved the same problem correctly with GeminiStreamingResponse(StreamedResponse) — reuse that approach or rename the local class to something honest like _CodeAssistSSEReader.
  2. Dead code: StreamedResponse.get_response_parts() builds tool_calls = [] and never populates it — function calls are silently dropped from streamed responses.
  3. Per-request client construction: request() and request_stream() create a fresh httpx.AsyncClient per call (lines 81, 108), so no connection pooling across turns; every other model in the repo holds a long-lived client. Accept an optional http_client like GeminiModel does.
  4. Stale access token: the OAuth access_token is captured once at construction in model_factory.get_model() and never refreshed, unlike the Claude OAuth path which refreshes proactively. Long sessions will start failing with 401s until the model is rebuilt.

Suggested fix

    @property
    def model_name(self) -> str:
        return self._model_name

…and align the streaming implementation with gemini_model.GeminiStreamingResponse, inject a shared http_client, and resolve the token via a callable (e.g. get_valid_access_token) at request time.

Filed by Zen Reviewer A (code-puppy-60635a)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in code_puppy/gemini_code_assist.py, then compare its streaming implementation with GeminiStreamingResponse in gemini_model.py and inspect model_factory.get_model(). Verify the model and response interfaces match pydantic-ai, streamed tool calls are preserved, clients are reusable, and access tokens are resolved at request time.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
ai, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.