Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ecc8331
fix(stream): idle watchdog, stream error chunks, BOM/CR, retry budget…
jkyberneees Oct 18, 2026
d98b7aa
fix(stream): ignore non-object error keys on stream chunks
jkyberneees Oct 18, 2026
c532519
fix(mapping): tool_calls finish on OpenAI chat, index-less parallel t…
jkyberneees Oct 18, 2026
111db37
docs: state finish-reason precedence for tool-call turns
jkyberneees Oct 18, 2026
575223e
fix(request): accept image/jpg, no empty/whitespace text blocks, Gemi…
jkyberneees Oct 18, 2026
b9614ad
feat(anthropic): adaptive thinking with output_config.effort; budget_…
jkyberneees Oct 18, 2026
c886e91
feat(anthropic): native output_config.format JSON mode
jkyberneees Oct 18, 2026
420d07f
feat(anthropic): AnthropicToolJSON quirk keeps forced-tool JSON emula…
jkyberneees Oct 18, 2026
2d94037
fix(models): base64 embeddings, bare-array models, Anthropic paginati…
jkyberneees Oct 18, 2026
d10b181
feat(chat): surface provider refusals as ChatResult.Refusal and Delta…
jkyberneees Oct 18, 2026
689ef90
feat(chat): ResponseMeta with provider id, served model, request id a…
jkyberneees Oct 18, 2026
054459b
feat(chat): per-request headers with reserved-auth rejection
jkyberneees Oct 18, 2026
43d439c
feat(chat): WithAttemptObserver reports every HTTP attempt on every e…
jkyberneees Oct 18, 2026
c216307
feat(content): document and image-URL content parts on every format
jkyberneees Oct 18, 2026
aa41f7a
fix(content): normalize document and image-URL MIME types to lower case
jkyberneees Oct 18, 2026
d3de9b0
feat(tools): RunTools tool-loop helper with signature-safe replay
jkyberneees Oct 18, 2026
977b763
feat(count): CountTokens for Anthropic and Gemini, ErrUnsupported els…
jkyberneees Oct 18, 2026
49867c1
feat(chat): TopK, Gemini generationConfig deep-merge and embedding Ta…
jkyberneees Oct 18, 2026
6b21232
fix(tools): check context before each RunTools handler
jkyberneees Oct 18, 2026
c27a838
docs: v1.1 upgrade notes and install pin
jkyberneees Oct 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,30 @@ go test -tags e2e -run 'TestE2E' -timeout 15m -v . # LIVE provider e2e (see be
|---|---|
| `llm.go` | `SDK` entrypoint: `New`, options (`WithProvider`, `FromEnv`, …), `Chat`, `Provider` (model cache, shared learn-once state) |
| `message.go` | Canonical types: `ChatRequest`, `Message`, `ChatResult`, `Delta`, `Usage`, `ToolDef` |
| `content.go` | Content-part boundary validation (text, image, image URL, document; MIME case-insensitive, canonical lower-case on the wire), aggregate inline cap, attachment helpers |
| `chat.go` | `providerClient`: retry orchestration (buffered + streaming), error classification, learn-once consumption, SSE pump wiring, `httpError` parsing |
| `openai.go` / `gemini.go` / `anthropic.go` | Per-format request builders, response/stream mappers, model listing |
| `responses.go` | OpenAI Responses API (`/v1/responses`) for GPT-5.6+ tools+reasoning |
| `tts.go` | Text-to-speech: `Speak`/`SpeakRequest`/`SpeakResult` — OpenAI-compat `/audio/speech` + Gemini AUDIO modality, no transcoding |
| `controls.go` | Request controls: `ToolChoice`, `ResponseFormat` (incl. Anthropic forced-tool JSON emulation + `foldAnthropicJSON`), `ParallelToolCalls`, per-format mapping and boundary validation |
| `embed.go` | Embeddings: `Embed`/`EmbedRequest`/`EmbedResult` — OpenAI-compat `/embeddings` + Gemini `batchEmbedContents` |
| `controls.go` | Request controls: `ToolChoice`, `ResponseFormat` (Anthropic JSON is native `output_config.format`; the forced-tool emulation + `foldAnthropicJSON` run only under `Quirks.AnthropicToolJSON`, off for the built-in provider), `ParallelToolCalls`, per-format mapping and boundary validation |
| `observe.go` | `AttemptEvent` + `WithAttemptObserver`: per-attempt record filled by `doObserved` in the HTTP helpers and emitted from the retry ladders (`withRetryN`, `callStream`) with the delay about to be waited; key-masked errors; panics recovered |
| `meta.go` | `ResponseMeta`/`RateLimit` and `parseResponseMeta` (request-id + rate-limit headers from the successful attempt); per-request `Headers` validation (reserved auth/framing names rejected, no values in errors) and ctx carriage |
| `tools.go` | `RunTools`: buffered tool-call loop (`ToolHandler`, `ToolLoopOptions`, `ErrMaxToolTurns`); handler errors/panics/unknown tools become `IsError` results, `Parallel` keeps call order |
| `count.go` | `ChatClient.CountTokens`: Anthropic `/v1/messages/count_tokens` + Gemini `:countTokens` through the shared ladder (op `count_tokens`); OpenAI-format/Responses -> `UnsupportedError` (`ErrUnsupported`), no I/O, never an estimate |
| `embed.go` | Embeddings: `Embed`/`EmbedRequest`/`EmbedResult` — OpenAI-compat `/embeddings` (requests `encoding_format: base64`, decodes float32 LE, accepts float arrays) + Gemini `batchEmbedContents` |
| `stt.go` | Speech-to-text: `Transcribe`/`TranscribeRequest`/`TranscribeResult` — OpenAI-compat `/audio/transcriptions` (multipart), 25MB input cap |
| `sse.go` | SSE parser (abort-safe via `done` channel) + idle-watchdog pump |
| `retry.go` | `RetryPolicy` + the single buffered ladder `withRetry` (chat, tts, stt, embed); backoff/jitter/`Retry-After`/`retrySleep` (default 8 attempts, cap 30s) |
| `provider.go` | Built-in registry, quirks flags, config validation |
| `models.go` | `ListModels` orchestration (retries, caps, cache backing) |
| `models.go` | `ListModels` orchestration (shared `withRetryN` ladder: `WithRetryPolicy` attempts, default 3, Retry-After, request-timeout budget; deep-copied cache, empty lists cached) |
| `auth.go`, `transport.go`, `errors.go` | Env resolution, pooled HTTP clients, typed errors |

The canonical type system is OpenAI-shaped; `gemini.go`/`anthropic.go` translate both directions. Provider quirks are **explicit registry flags** — never URL sniffing.

## Invariants (breaking any of these is a defect)

1. **Canonical finish reasons** are `stop | length | tool_calls | content_filter | ""`. Unmapped provider stop reasons map to `""` on every format — provider-specific strings never leak. A turn with tool calls finishes `tool_calls` on every format (Gemini's `STOP` is mapped).
1. **Canonical finish reasons** are `stop | length | tool_calls | content_filter | ""`. Unmapped provider stop reasons map to `""` on every format — provider-specific strings never leak. A tool-call turn that ends normally (provider stop/`STOP`/end, or the format's tool signal) finishes `tool_calls` on every format (Gemini's `STOP` and OpenAI-format `stop` are mapped); a `length`, `content_filter` or unmapped reason keeps that value (or `""`) even with calls present, since the calls may be incomplete.
Refusal text surfaces in `ChatResult.Refusal` / `DeltaRefusal` (never in `Content`); a refusal on `stop`/unmapped finishes `content_filter`.
2. **API keys never appear** in error text, `String()`, or any typed error.
3. **Streaming**: retries only before the first emitted delta; a failure after partial output returns the partial `*ChatResult` + wrapped error and is **never retried**; a premature close (no completion signal — on every format, Gemini included; an unmapped finish reason still counts as one) is an error, never a silent empty or partial success; the parser goroutine is always released (abort-safe `done` protocol).
A successful non-SSE response is parsed directly with the usual body cap and format mapper. It learns buffered mode for future requests, but never discards the current generation or makes a replacement request; parse/read errors remain terminal for that response.
Expand All @@ -53,6 +59,8 @@ The canonical type system is OpenAI-shaped; `gemini.go`/`anthropic.go` translate
8. **`Delta.ToolIndex` is the call's position in `ChatResult.ToolCalls`** on every format — never a provider content-block or output-item index.
9. **`Usage` has one meaning everywhere**: `PromptTokens` uncached-only, cache reads/writes in their fields (a DeepSeek miss is uncached input, not a write), `CompletionTokens` includes reasoning, `CachedTokens` is a diagnostic subset never summed.
10. **The request timeout is a whole-call budget**, retries included, on every entry point (buffered, streaming, speech, transcription, embeddings).
11. **Meta fields are provider-reported or zero, never guessed**; headers come from the successful attempt only. Attempt-observer panics are recovered and discarded; events never contain the API key.
12. **Anthropic JSON mode is native `output_config.format`** unless the explicit registry quirk `Quirks.AnthropicToolJSON` is on (off for the built-in provider, set via `WithQuirks`); the quirk restores the pre-M4 forced-tool emulation byte-for-byte, including its ConfigErrors (also under any thinking mode). Never infer it from URL or model name.

## Testing conventions

Expand Down
Loading
Loading