Commit Graph

31 Commits

Author SHA1 Message Date
Levi Neely 3867d52689 add generation params: top_p, top_k, min_p, top_a, repetition_penalty, max_completion_tokens, response_format, reasoning_effort, include_reasoning, stop, verbosity 2026-05-16 23:54:04 +02:00
Levi Neely c812f0882e freeloader: fix 404 tool_unsupported detection and FIFO-drain recovery
- backend: add HTTP 404 and "no endpoints found" to isToolUnsupported
  so OpenRouter 404s are correctly classified as tool_unsupported
- core: drain FIFO on error return from executeTurn so freeloader's
  resubmitted prompt (queued via fifo.in while running) is picked up

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 18:47:34 +02:00
Levi Neely 78e5416550 core: freeloader infrastructure — turnError hook, ToolUnsupportedError, prompt.prev, MemoryStore
- backend: ToolUnsupportedError type, isToolUnsupported detector (400/422)
- backend: classifyError maps error types to hook payload strings
- agent: HookTurnError constant; TurnError func on agentConfig set at init
- loop: fire TurnError hook on first backend error; if ran, skip retries
- store: MemoryStore — in-memory KV Store implementation
- store: Session.prevPrompt field; prompt.prev read-only session file
- hooks: inject payload map as OLLIE_* env vars in runHookCmd
- agent: agentSpawn payload includes model field
- agent: limitretry state emitted on RateLimitError backoff

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 17:17:25 +02:00
Levi Neely 9f1d7a5ec8 core: caching risk-reduction tests
loop_prune_test.go — three new prune tests:
- ReadWriteReadCycle: pre-write read pruned, post-write read kept (primary invariant)
- AssistantMessagesUnaffected: only role:"tool" messages are dropped, never assistant messages
- NonExecuteCodeUnaffected: non-execute_code tool calls don't confuse the path tracker

loop_cache_test.go — one new cache test:
- IdenticalParallelReads: two goroutines simultaneously miss the cache, both execute,
  results are consistent and no panic (sync.Map race safety)

cost_test.go — six computeCostUSD unit tests:
- BaseTokens, ClaudeCacheReadDiscount (10%), NonClaudeCacheReadDiscount (50%),
  CacheCreationSurcharge (125%), AllFieldsCombined, UnknownModelZero

openai_encode_test.go — four OpenRouter cache injection tests:
- OpenRouterClaude_SystemCacheControl: system content encoded as array with cache_control
- OpenRouterClaude_LastToolCacheControl: last tool gets cache_control:ephemeral
- OpenRouterNonClaude_NoCacheControl: non-Claude model on openrouter gets no injection
- OpenAI_NoCacheControl: plain openai backend never injects cache_control

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 14:33:29 +02:00
Levi Neely 905bf4e9a9 core: tests and emission fixes for parallel tool execution and caching
- loop_parallel_test.go: four tests covering concurrent execution (barrier
  pattern), serial tool breaking a batch, cancellation filling all results,
  and nil ClassifyTool falling back to serial
- loop.go: emit "tool" events for all cancelled tool calls (early-ctx-err
  path in execOne, top-level ctx check, and post-batch fill) so every
  dispatched tool call has a visible result regardless of cancellation timing
- anthropic_stream_test.go: five tests covering tool schema cache_control
  placement (last tool only, single-tool edge case) and tool result caching
  (large gets cache, small does not, last of multiple large results wins)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 13:13:32 +02:00
Levi Neely efac665ffe core: tool schema caching and large tool result caching
Tool schema caching:
- Mark the last tool with cache_control:ephemeral before each Anthropic
  request; Anthropic caches all preceding tools as a prefix
- Same for OpenRouter+Claude via openAITool.CacheControl extension

Large tool result caching (Anthropic only):
- In buildAnthropicMessages, scan backwards through the encoded output
  and mark the last tool_result block ≥4096 bytes with cache_control
- 4096 bytes ≈ 1024 tokens, Anthropic's minimum cache entry size
- OpenAI wire format has no block structure for tool results; skip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 12:22:45 +02:00
Levi Neely ba14c8f07f core: cache system prompt on OpenRouter+Claude via content block encoding
When OpenRouter routes to a Claude model, encode the system message as
a content-block array with cache_control:ephemeral instead of a plain
string. OpenRouter passes this through to Anthropic, enabling the same
90% cache-read discount as the native Anthropic backend.

Detection: b.name=="openrouter" && model contains "claude-".
Encoding: MarshalJSON on openAIMessage outputs content as array when
ContentBlocks is set, preserving the existing *string path for all
other cases with no test changes required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 12:14:46 +02:00
Levi Neely cf6fee36e2 core: Anthropic prompt caching and cache-aware cost accounting
- Anthropic system prompt sent as a cacheable content block
  (cache_control: ephemeral) so repeated turns reuse the cached prefix
- Parse cache_read_input_tokens and cache_creation_input_tokens from
  Anthropic message_start; parse cached_tokens from OpenAI
  prompt_tokens_details
- Normalise Usage so CachedInputTokens is always excluded from
  InputTokens regardless of provider
- computeCostUSD now takes backend.Usage and applies correct cache
  discounts: 10% read / 125% creation for Anthropic, 50% read for OpenAI
- Session tracks TotalCachedInputTokens / TotalCacheCreationTokens;
  /usage shows cached token count when non-zero

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 12:01:14 +02:00
Levi Neely 370171bf80 agent: context overflow recovery and OLLIE_MAX_SESSION_COST spending cap
On ContextOverflowError (400 with context-length body), restore pre-turn
snapshot, compact, and retry the turn once. Covers runaway context growth
that slips past the proactive 75% threshold.

OLLIE_MAX_SESSION_COST (USD) blocks turn start and interrupts mid-turn
via the usage event handler, which fires between every tool round.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:35:39 +02:00
Levi Neely 59ee25b010 agent: per-turn and session cost tracking in USD
Usage events now carry a 4th field (costUSD) so OpenRouter's reported
cost flows through unchanged. For backends that only report token counts
(Anthropic, direct OpenAI, Ollama), cost is computed from a prefix-keyed
pricing table. The higher-fidelity source always wins.

Per turn:
- turnInputTokens/turnOutputTokens/turnCostUSD reset before each turn
- recordTurnCost() stores LastTurnCostUSD, adds to SessionCostUSD
- "costLast=$X.XXXX" emitted as an info event at turn end

Session file "cost" returns costLast/costSession key=value pairs.
Agent interface gains Cost() string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:14:12 +02:00
Levi Neely 625257dea1 agent: retry on transient backend errors and mid-stream drops
Previously only HTTP 429 triggered a retry. Network failures, 5xx
responses, and mid-stream drops all hard-failed the turn.

- Add TransientError for 5xx and network-level failures; streamRequest
  wraps client.Do errors and >=500 responses in it
- Merge pre-stream and mid-stream retry into one loop (maxTransientRetries=3)
  covering rate limits, transient errors, and pure stream drops
- On stream drop with ctx cancelled: record partial state and return
  (existing pause behaviour preserved)
- On stream drop without cancellation: close any open <think> block,
  emit retry countdown, restart ChatStream from the same history

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 17:54:27 +02:00
Levi Neely eabe17a1b1 backend: stream extended thinking / reasoning tokens
- Add StreamEvent.Reasoning for incremental thinking deltas
- Add GenerationParams.ThinkingBudget to enable Anthropic extended thinking
- Anthropic: parse thinking_delta events; set Anthropic-Beta header and
  thinking request param when ThinkingBudget > 0
- OpenAI: parse delta.reasoning (o-series) and delta.reasoning_content
  (OpenRouter) fields
- Loop: emit "reasoning" events wrapped in <think>/</think> markers
- FormatEvent: append reasoning content directly to the chat log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 19:01:27 +02:00
Levi Neely c7ad2ca68a backend: require OLLIE_BACKEND/OLLIE_MODEL; no silent ollama default
- NewWithName and newFromEnv now error when OLLIE_BACKEND is unset
  instead of falling back to ollama silently
- createSession reads OLLIE_MODEL as fallback when model= is not given
- Update test to assert error on missing OLLIE_BACKEND

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 18:25:39 +02:00
Levi Neely b54c5c08f9 kiro: implement social token refresh via desktop.kiro.dev 2026-04-26 18:11:21 +02:00
Levi Neely c6d6f2f162 backend: recover DeepSeek DSML tool calls leaking into content stream
DeepSeek's native tool-call format uses <|DSML|invoke> tokens. When
OpenRouter fails to convert them to standard tool_calls chunks, they
arrive as raw content text. Detect the marker, buffer the content, and
parse it into ToolCall structs at [DONE] if no structured tool calls
accumulated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:17:29 +02:00
Levi Neely d89f458473 backend: mark codewhisperer as intentionally untested, remove test 2026-04-21 22:09:27 +02:00
Levi Neely 2264f3f11d agent: fix backend race, split commands.go, use OLLIE_TMP_PATH
- Add backend.NewWithName(name) to avoid os.Setenv race on OLLIE_BACKEND
- Extract handleCommand into commands.go (core.go 1264 -> 859 lines)
- Replace hardcoded /tmp/ollie with OLLIE_TMP_PATH (guaranteed by env.Ensure)
2026-04-21 21:44:39 +02:00
Levi Neely d957bda8a0 backend: remove unreachable NewRequestWithContext guards (URL validated in ctor) 2026-04-21 15:47:34 +02:00
Levi Neely 5cfa5a0df9 backend: 100% coverage for buildAnthropicMessages and streamAnthropicSSE
- Test all buildAnthropicMessages branches: system concat, assistant
  tool-calls-only, nil arguments default, tool batching, unknown role,
  full multi-turn conversation
- Test streamAnthropicSSE done re-entry: events after message_stop and
  error are ignored
- Remove unreachable done guard in process closure
2026-04-21 15:45:46 +02:00
Levi Neely 38dd34ea20 backend: cover Ollama Models/ContextLength HTTP paths, constructor URL validation 2026-04-21 15:42:27 +02:00
Levi Neely 13b17cd777 backend: extract parseOllamaContextLength, 100% coverage 2026-04-21 15:39:52 +02:00
Levi Neely 6a60f5014b backend: extract newFromEnv, 100% coverage for new.go 2026-04-21 15:36:26 +02:00
Levi Neely 9d8398bab0 backend: test coverage for new.go (loadEnvFile, openAIName, Backends) 2026-04-21 15:34:01 +02:00
Levi Neely 21827c57eb backend: extract stream parsers, shared HTTP, url.URL, comprehensive tests
- Extract streamOpenAISSE, streamOllamaNDJSON from ChatStream goroutines
  into standalone functions taking io.Reader for direct testability
- Remove ctx parameter from streamAnthropicSSE (caller handles cancellation)
- Extract streamRequest into backend.go: shared HTTP execution, 429/error
  handling, and goroutine lifecycle for all three backends
- Extract encodeOpenAIMessages, encodeOpenAITools from ChatStream
- Replace string baseURL with *url.URL; validate in constructors
- Constructors (NewOpenAI, NewOllama, NewAnthropic, NewCopilot) now return error
- Remove unreachable json.Marshal and http.NewRequestWithContext error branches

ChatStream coverage: 100% (OpenAI, Ollama, Anthropic)
Stream parsers: 100% (OpenAI, Ollama), 96.6% (Anthropic)
streamRequest: 100%

Test files:
- openai_encode_test.go: message/tool encoding, JSON round-trip
- openai_stream_test.go: SSE parsing via io.Reader
- ollama_stream_test.go: NDJSON parsing via io.Reader
- anthropic_stream_test.go: SSE parsing, mapAnthropicStopReason
- openai_test.go: HTTP integration, error paths, models, context length
- backend_test.go: contract checks (all interface methods), stream text/usage/
  tool calls/tool conversation, HTTP errors, rate limits for all backends
2026-04-21 15:32:03 +02:00
Levi Neely 46272f22a6 test: sandbox coverage + backend contract tests
- sandbox: remove dead code (DefaultConfig, Load/Save, LayeredConfig, Merge,
  Version, SystemDefaults, validateName, ConfigPath, OpenSandbox); unexport
  internal symbols; LoadSandbox now takes io.Reader; loadSandboxConfig owns
  file opening; 83% package coverage via new sandbox_test.go
- backend: add BaseURL field to AnthropicBackend for testability; new
  backend_test.go runs checkContract against all five implementations
  (Ollama, OpenAI, Copilot, Anthropic, CodeWhisperer) via httptest servers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 13:00:06 +02:00
Levi Neely 92034b5342 add OLLIE_CFG_PATH and OLLIE_DATA_PATH env vars
Replace all hard-coded ~/.config/ollie and ~/.local/share/ollie paths
with a new paths package that reads OLLIE_CFG_PATH and OLLIE_DATA_PATH,
falling back to the previous defaults when unset.
2026-04-17 15:31:19 +02:00
Levi Neely a035913514 fix: only use json wire field for JSON objects in tool results
Bare JSON scalars (0, true, null, "string") and arrays pass
json.Valid() but the Kiro API rejects them in the json field with
ValidationException. Once such a tool result is in session history,
every subsequent request fails, making the session unrecoverable.

Restrict the json path to content starting with '{' so only actual
JSON objects are sent that way; everything else uses the text field.
2026-04-13 16:21:45 +02:00
Levi Neely aa32d17e81 backend: add Backends() listing supported backend names 2026-04-13 14:45:14 +02:00
Levi Neely 4786866e66 fix: strip inline comments in env file parser, fix backtick syntax errors
loadEnvFile now strips " # ..." suffixes from values so inline comments
in ~/.config/ollie/env don't leak into environment variables.  Also add
kiro to the supported-backends error message and fix two raw-string
backtick escaping errors in tool descriptions.
2026-04-13 09:32:56 +02:00
Levi Neely 45a094f46e backend: add ContextLength, Models, and auto-compaction
- Add ContextLength(ctx) int to Backend interface; each backend fetches
  from its API (OpenAI /v1/models, Ollama /api/show, CodeWhisperer
  ListAvailableModels) with caching. Falls back to 128k default.
- Add Models(ctx) []string to Backend interface for model discovery.
- Add /models command to list available models.
- Auto-compact before each turn when estimated tokens >= 75% of context length.
2026-04-10 19:35:44 +02:00
Levi Neely 97b85e5163 refactor: promote backend/config to pkg/, extract TUI to ollie-tui
Move internal/backend and internal/config to pkg/ so external consumers
can import them. Remove cmd/ollie and internal/tui — they now live in
the separate ollie-tui repo which imports ollie as a library.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 22:05:35 +02:00