4 Commits
Author SHA1 Message Date
james.maes 86ff3accae feat(07-05): OllamaProvider OpenAI-compat image_url multimodal override (Task 3 GREEN)
- OllamaRequestBody.encodeNativeMultimodal + encodeOpenAICompatMultimodal
  produce the OpenAI-vision data-URL block:
  {type:image_url, image_url:{url:'data:<mediaType>;base64,<b64>'}}
- Both T1 (Ollama gemma4:31b) and T2 (vllm-mlx Qwen3.5 via useOpenAICompat=true)
  consume the same encoder
- OllamaProvider.stream(messages:images:...) routes through the new encoders;
  empty images still produce byte-identical bodies
- All 5 OllamaImageBlockEncodingTests + 147 existing tests pass
2026-04-29 17:45:01 -05:00
james.maes cbc5efcc02 feat(04-02): OllamaProvider actor + OpenAI-compat SSE decoder behind feature flag
- OllamaProvider actor conforms to LLMProvider; branches between
  /api/chat NDJSON (default) and /v1/chat/completions SSE on
  useOpenAICompat flag.
- OpenAICompatDecoder handles atomic tool_calls and accepts arguments
  as either a JSON-encoded string (OpenAI convention) or object.
  Distinct state machine from NDJSONDecoder per the plan's
  "zero shared decoder state" directive.
- [DONE] sentinel emits stopReason+usage+messageStop trio; mid-stream
  EOF emits .stopReason(.streamTruncated).
- 8 tests for OpenAICompatDecoder + 1 conformance compile check.
2026-04-24 17:41:03 -05:00
james.maes 2323a6b929 feat(04-02): NDJSONDecoder with tool_calls-on-sight invariant + OllamaRequestBody drop-tools-array on .none (AGENT-04, AGENT-07)
- NDJSONDecoder emits .toolUseRequested whenever tool_calls is present on
  any chunk; never gates on done:true (AGENT-04 transport gotcha).
- Each tool_use gets a fresh UUID id (Ollama emits no ids on /api/chat).
- done_reason mapping: stop→endTurn (or toolUse if calls seen), length→
  maxTokens, tool_calls→toolUse, missing→toolUse-if-calls-else-endTurn.
- Mid-stream EOF emits .stopReason(.streamTruncated) — no
  partialToolUseAtDisconnect (NDJSON is atomic per line).
- OllamaRequestBody.encodeNative drops the tools array entirely when
  ToolChoice == .none (AGENT-07 cap-recovery invariant).
- ToolChoice.tool(name:) appends a best-effort system hint message
  (Ollama has no first-class tool-choice.force).
- OpenAI-compat path: tool_choice serializes as "auto"/"none"/"required"
  string or {type:function,function:{name}} object.
- Replaces Plan 04-01 placeholder files; tests: 19 passing.
2026-04-24 17:38:35 -05:00
james.maes 0020c35fb6 feat(04-01): scaffold AgentCore package with LLMProvider protocol + LLMEvent + BoundedAsyncChannel (AGENT-01, AGENT-07, AGENT-10)
- packages/AgentCore SPM package with three library products: AgentCore, AnthropicProvider, OllamaProvider (latter two ship placeholders this wave)
- LLMProvider protocol — Sendable, AsyncThrowingStream<LLMEvent, Error>, mandatory toolChoice param
- LLMEvent enum: 10 cases (messageStart, textDelta, thinkingDelta, toolUseRequested, toolUseBuffering, partialToolUseAtDisconnect, stopReason, usage, providerError, messageStop)
- StopReason: 5 cases (endTurn, toolUse, maxTokens, refusal, streamTruncated)
- ToolChoice: 4 cases (auto, none, any, tool(name:))
- ModelID literals per RESEARCH-DELTAS D1 — opus47 = "claude-opus-4-7", qwen25coder32b = "qwen2.5-coder:32b"
- CacheHints / TurnID / TurnUsage / ToolSchema / LLMMessage / LLMProviderError supporting types
- BoundedAsyncChannel<T> actor with .suspend / .dropOldest / .dropNewest policies, AsyncSequence conformance, single-consumer iterator
- 21 AgentCoreTests passing (channel policies, exhaustive enum cases, equality, RawValue literals)
2026-04-24 17:13:40 -05:00