Skip to content

Commit d2642bd

Browse files
committed
docs(changelog): v0.41.0
1 parent 3b74f88 commit d2642bd

1 file changed

Lines changed: 16 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
All notable changes to GopherAgent are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/); versions follow [Semantic Versioning](https://semver.org/) — pre-1.0, breaking API changes only require a minor bump.
44

5+
## [v0.41.0] — 2026-08-10
6+
7+
### Added
8+
9+
- **`pkg/audio` — a provider-neutral speech-to-text seam, so an audio-fed agent is not limited to the one vendor that accepts audio in a message.** Of the three providers here, only Gemini takes audio in a chat message: Anthropic has no audio content block at all, and the OpenAI Chat Completions client cannot express one. Transcribing *before* the message rather than inside it makes the capability portable — every provider can drive an audio-fed agent, because what reaches the model is ordinary text. It is also the cheaper shape for anything long. History is re-sent on every LLM call in a session, so audio carried as a message part is re-uploaded on every subsequent turn: a one-hour recording transcribed once costs one upload, while the same recording as message parts is billed again on each turn of the conversation about it. The package is a stdlib-only leaf holding the `Transcriber` interface plus `Clip`, `Transcript`, `Segment`, and `Options`. Three sentinels — `ErrNoAudio`, `ErrUnsupportedFormat`, `ErrTooLarge` — are separate rather than one error because a live-capture pipeline responds to each differently: an oversized clip must be re-cut into shorter chunks, an unsupported one re-encoded, and an empty one is a caller bug; routing on `errors.Is` beats matching message text. `Transcript.Segments` is nil when the backend emits no timing, which is a normal result rather than a failure, so callers needing timestamps must check rather than assume. `Ext` strips MIME parameters and matches case-insensitively, because browsers' `MediaRecorder` reports `audio/webm;codecs=opus` and some emit the `video/webm` spelling for an audio-only recording — rejecting either would fail a clip the backend decodes fine. (`pkg/audio`)
10+
- **`openai.NewTranscriber` and `gemini.NewTranscriber`.** The OpenAI implementation drives the audio transcription endpoint and populates `Segments`, `Language`, and `Duration`. It selects the `verbose_json` response format only for whisper models: the `gpt-4o-transcribe` family rejects that format outright rather than degrading, so asking for it everywhere would fail every request instead of merely losing timings. The match is a substring rather than a prefix, since compatible endpoints name the same weights differently. Oversized clips are rejected against the endpoint's 25 MB limit before the upload rather than after transferring the payload. `WithBaseURL` works here as on every other client in the package, so a self-hosted transcription server is a supported target. The Gemini implementation has no dedicated transcription endpoint to call, so it constrains the generation API with a system instruction — without one the model opens with a preamble or summarizes instead of transcribing, and both corrupt a transcript appended verbatim. Its `Segments` is always nil, stated on the type rather than discovered at run time, and its language and vocabulary hints travel as instruction text because the API has no parameter for either. (`pkg/llm/openai/transcriber.go`, `pkg/llm/gemini/transcriber.go`)
11+
12+
### Changed (breaking)
13+
14+
- **A message carrying a media part the adapter cannot render now fails with `agent.ErrUnrenderablePart` instead of being silently dropped.** All three adapters converted `history.MediaPart` with a `switch` that fell through for anything unexpected, and one of them documented the omission as deliberate on the grounds that erroring would force callers to pre-validate. That trade was wrong in the direction that matters. Dropping the part does not degrade the call, it silently changes what the question was: the model receives the caption alone and answers it fluently, and nothing distinguishes that from success — not the response, not the logs, not a schema check, because a well-formed answer is exactly what success looks like. A judge that cannot see the image it is judging is not a degraded judge, it is a random one. Four shapes now fail: an unknown part type, an image with neither `URL` nor `Data`, a parts slice that yields no content at all, and media parts on a non-`user` role, which every adapter previously ignored wholesale — OpenAI behind an explicit role guard, Anthropic and Gemini by rendering media only under their `user` branch. Empty text parts are still skipped, as they carry nothing to lose. `blocksFromMediaParts` and `partsFromMediaParts` grew an `error` return; both are unexported and every call site is inside `GenerateStream`, which already returned one. `isRetryable` treats the sentinel as terminal — the same bytes fail identically on every attempt, so retrying only burns the budget. Callers that relied on a malformed part being ignored will now see the call fail; that is the point. (`pkg/agent/errors.go`, `pkg/agent/retry.go`, `pkg/llm/anthropic`, `pkg/llm/openai`, `pkg/llm/gemini`)
15+
16+
### Fixed
17+
18+
- **The Gemini transcriber reads `FinishReason` before testing for nil content.** A candidate stopped by a content filter arrives with a non-`STOP` reason *and* nil content, so checking content first reported a blocked recording as an empty transcript — a filtered meeting became indistinguishable from a silent one, with no error to act on. The same ordering guards truncation, where returning the accumulated prefix would present half a transcript as the whole. A response with no candidate at all is now an error rather than an empty transcript, since it signals a prompt-level block rather than audio without speech. (`pkg/llm/gemini/transcriber.go`)
19+
520
## [v0.40.0] — 2026-08-09
621

722
### Added
@@ -620,6 +635,7 @@ Multi-user, long-running, audit-friendly chat surface — the foundation for sid
620635
- README section on the permission flow — documents `RequiresConfirmation` × `ConfirmHITL` × `Permissions` interaction.
621636
- Enum struct tag support in `tools.SchemaFor[T]()` — emit values into JSON-Schema's `enum` array so providers reject invalid values upstream.
622637

638+
[v0.41.0]: https://github.com/hung12ct/gopheragent/releases/tag/v0.41.0
623639
[v0.40.0]: https://github.com/hung12ct/gopheragent/releases/tag/v0.40.0
624640
[v0.39.0]: https://github.com/hung12ct/gopheragent/releases/tag/v0.39.0
625641
[v0.38.0]: https://github.com/hung12ct/gopheragent/releases/tag/v0.38.0

0 commit comments

Comments
 (0)