Commit Graph
91 Commits
Author SHA1 Message Date
James VeitchandClaude Sonnet 5 ea66ceee9d docs(beacon): flip llamacpp-integration epic Planning->Active
Spec 008-llamacpp-integration is complete; epic-finish bookkeeping
(archive) follows in a separate chore PR once this merges, matching
the llm-provider-abstraction epic's pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 15:48:03 +01:00
James VeitchandClaude Sonnet 5 cf3f7fc174 fix(llamacpp): surface a clear error for non-router-mode servers (FR-006)
LlamaCppProvider.list_models() caught every exception and returned [],
indistinguishable from "no models installed". Fixed _get_json to raise
on HTTP error status (matching _post_json's existing behavior — its
docstring already claimed this), and list_models() to distinguish
OSError (genuinely unreachable, degrades to []) from RuntimeError
(server responded with an error — surfaced with a router-mode hint).

Found by beacon-reviewer ahead of PR open.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 15:43:12 +01:00
James VeitchandClaude Sonnet 5 5af502d07e feat(llamacpp): add LlamaCppProvider + LlamaCppClient node (spec 008)
Second LLMProvider (ADR-007), backed by llama-server's router mode. Only
one new ComfyUI node — LlamaCppClient, emitting the same LLM_CLIENT socket
Ollama Client does. ChatCompletion, LLMModelSelector, LLMLoadModel,
LLMUnloadModel work unmodified once wired to it — the actual proof the
provider abstraction generalizes, not just Ollama-shaped in practice.

API shape verified live against ggml-org/llama.cpp's tools/server/README.md
(research.md) rather than assumed: model identifier field is "id" (not
"name"), status is a nested {"value": "..."} object. list_models() needs
no normalization — llama.cpp's status vocabulary is exactly ModelStatus's
full set, unlike Ollama's narrower one.

38 new tests (test_llamacpp_provider.py, test_llamacpp.py), including a
dedicated US4 test proving no generic node branches on provider type —
the same call sequence succeeds against an Ollama-shaped or llama.cpp-
shaped fake provider.

README/docs updated for the new node this time, not left stale.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 15:27:29 +01:00
James VeitchandClaude Sonnet 5 d3ee35bfda docs(beacon): DESIGN artifacts for llama.cpp Model Integration (spec 008)
Full spec/plan/tasks/BDD scaffold for the llamacpp-integration epic, now
that its dependency (llm-provider-abstraction, PR #17) is satisfied.
Researched llama-server's router-mode API shape live (postdates training
data) rather than assuming it — two details that would have been wrong by
assumption: the model identifier field is "id" not "name" (differs from
Ollama's /api/tags), and "status" is a nested object ({"value": "..."}) not
a flat string.

Unlike the prerequisite epic, this decomposition genuinely holds as 4
independent user stories — LlamaCppClient is a new node, not a changed one,
so there's no shared "output type" migration forcing an atomic cutover.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 15:17:54 +01:00
James Veitch 9ca65bc29a Merge pull request #17 from darth-veitcher/007-llm-provider-abstraction
LLM Provider Abstraction: generic adapter pattern for Ollama (ADR-007)
2026-07-11 15:08:14 +01:00
James VeitchandClaude Sonnet 5 c32b6a7e42 docs(beacon): note PR #17 open on the llm-provider-abstraction epic
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 14:42:59 +01:00
James VeitchandClaude Sonnet 5 3df38485d2 docs: update README/docs for the generic node rename, add migration table (FR-009)
README.md and docs/index.md still named OllamaChatCompletion/OllamaModelSelector/
OllamaLoadModel/OllamaUnloadModel and the OLLAMA_CLIENT socket — stale after
the atomic cutover. Updated both (they're content-duplicates) and added the
old->new migration table FR-009 requires, which previously only existed as
a code comment (comfydv.ollama.MIGRATION_MAP), not surfaced to users.

Found via a dedicated docs-freshness pass, not caught by the beacon-reviewer
correctness review (which is scoped to code, not prose).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 14:42:19 +01:00
James VeitchandClaude Sonnet 5 8e227790a9 fix(llm): forward options through to structured output (beacon-reviewer finding)
chat_structured() silently dropped the options dict (temperature, seed,
num_predict, repeat_penalty, etc. — set via the OllamaOption* nodes) in
structured_output mode: OllamaProvider.chat_structured() only used it for
the cache key, and the shared comfydv._llm.chat.chat_structured() helper
had no options parameter at all. On main, the hand-rolled implementation
sent options verbatim in the request body; this migration silently lost it,
contradicting FR-008 (structured-output behavior must be unchanged).

Fixed by forwarding options as pydantic-ai's model_settings.extra_body,
matching the original payload shape exactly rather than lossily remapping
onto ModelSettings' own standardized field names (which don't cover
Ollama-native params like num_predict/repeat_penalty/top_k anyway).

Verified against the live local Ollama server: structured output with
temperature=0.0/seed=42 succeeds on first attempt.

Caught by an independent beacon-reviewer pass on the diff before opening a
PR — exactly what that review step is for.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 14:35:51 +01:00
James VeitchandClaude Sonnet 5 6c2c906c4a docs(beacon): mark tasks.md Phase 7 subsumed by T-CUT-10/12
Same lint/typecheck/doctor/live-validation work, done together with the
cutover rather than as a separate pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 14:29:20 +01:00
James VeitchandClaude Sonnet 5 1879ca2265 test(llm): complete atomic node cutover — test_ollama.py rewrite (T-CUT-08/10/12)
Rewrote tests/test_ollama.py against a _FakeProvider double per
atomic-cutover-plan.md's D4/D5 test-layer split: node-contract/delegation
tests only, no aiohttp/_post_json mocking (that lives in
test_ollama_provider.py now). 98 unit tests, collapsed from the original
~125 largely mechanical monkeypatch-per-scenario tests where the retry/
validation coverage they duplicated already lives in
test_llm_chat_structured.py.

Fixed a real gap the rename surfaced along the way: comfy-manager-entry.json
(ComfyUI Manager registry listing) still had the old Ollama-specific
display names — updated it and its matching test expectation.

Full validation (T-CUT-10): 218 tests green (only the pre-existing,
unrelated Dockerfile-python-version test fails); ruff/ty clean (confirmed
via git stash comparison that the remaining ty diagnostics pre-date this
work); beacon doctor --strict shows only already-disclosed items.

Manual smoke test (T-CUT-12): Ollama was reachable in this environment, so
ran the real integration suite rather than a walkthrough. 6/8 passed
including the critical paths (real load/unload, real structured-output
retry-then-raise, real error handling). 2 failures traced via direct curl
to Ollama's /api/chat (bypassing this codebase) to a pre-existing,
ADR-006-documented unreliability in the specific test model, not a
regression.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 14:28:34 +01:00
James VeitchandClaude Sonnet 5 d90ae98fed feat(llm): atomic node cutover — generic ChatCompletion/LLM* nodes (T-CUT-01/04-07/11)
OllamaClient now emits an OllamaProvider via the generic LLM_CLIENT socket
(was OllamaClientType/OLLAMA_CLIENT). OllamaChatCompletion, OllamaModelSelector,
OllamaLoadModel, OllamaUnloadModel renamed to ChatCompletion, LLMModelSelector,
LLMLoadModel, LLMUnloadModel — all delegate to client.* (LLMProvider protocol)
instead of building Ollama URLs/payloads inline. Structured output now goes
through client.chat_structured() (pydantic-ai), not hand-rolled tool-calling.

ollama.py's own duplicate HTTP/cache infra (_post_json, _TTLLRUCache,
_MODEL_LIST_CACHE, _CHAT_RESPONSE_CACHE) is gone — single source of truth in
comfydv._llm.ollama_provider (plan D1); the combo-widget path
(_load_default_models, /dv/ollama/models) imports _fetch_models/_run_async
from there instead of duplicating them. _client_headers deleted (dead code
once nodes delegate to client.*).

Added MIGRATION_MAP (old->new node/socket names, FR-009). NODE_CLASS_MAPPINGS
keys changed to match (breaking rename, confirmed acceptable per ADR-007).

conftest.py's autouse cache-clearing fixture repointed to the single cache
source and ChatCompletion (plan D8).

tests/test_ollama.py itself is NOT yet updated (T-CUT-08, next commit) — it
will fail to collect until then; this is the expected atomic-cutover
intermediate state, not a regression to fix independently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 14:19:07 +01:00
James VeitchandClaude Sonnet 5 fb111c0142 feat(llm): implement OllamaProvider method bodies (T-CUT-02/03)
list_models()/load_model()/unload_model()/chat()/chat_structured() —
behavior-preserving ports of the existing inline ollama.py logic, plus one
genuinely new capability: list_models() also queries /api/ps to report
real loaded/unloaded status (the old OllamaModelSelector only ever showed
names via /api/tags, never live status).

15 new tests in tests/test_ollama_provider.py, mocking at the module's own
_post_json/_get_json seam per atomic-cutover-plan.md's D4 test-layer split.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 14:14:51 +01:00
James VeitchandClaude Sonnet 5 22dbddc8eb docs(beacon): fix tasks.md checkbox hygiene, add trackable T-CUT phase
Struck-through historical tasks (- [ ] ~~...~~) still read as open
checkboxes to beacon's tooling — bullet status was picking one as "active"
instead of the real next step. Marked all superseded Phase 3/5/T014/T019-21
entries [-] (deferred, per the project's own convention), and added a real
Phase 8 with T-CUT-01..12 as trackable checkboxes matching
atomic-cutover-plan.md, so tooling and humans see the same source of truth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 14:12:02 +01:00
James VeitchandClaude Sonnet 5 ef2464aebb docs(beacon): properly spec the atomic node cutover (issue #16)
Full line-by-line inventory of every reference in src/comfydv/ollama.py
and tests/test_ollama.py (1820 lines, read in full — not estimated)
affected by the OllamaClient/OllamaChatCompletion/OllamaModelSelector/
OllamaLoadModel/OllamaUnloadModel rename, plus the design decisions it
surfaced that "just rename it" was hiding:

- Single source of truth for HTTP/cache infra (comfydv._llm.ollama_provider)
- client == "<host string>" equality is removed, not preserved (OllamaProvider
  isn't a str subclass)
- Bare-string client backward compatibility is removed (undocumented side
  effect of the old str-subclass trick, never an intended feature)
- Test-layer split: node-contract/delegation tests stay in test_ollama.py
  using a _FakeProvider double; Ollama-wire-protocol tests move to a new
  test_ollama_provider.py — this is what actually resolves the 35 relocated
  _post_json monkeypatches, rather than patching them 1:1 at a seam that no
  longer exists
- Structured-output retry tests are not ported 1:1 — already covered by
  tests/test_llm_chat_structured.py's existing 6 tests

Produces a 12-step sequenced task list (T-CUT-01..12) in
atomic-cutover-plan.md that supersedes tasks.md's original US1/US3
decomposition (struck through, kept for history). This is now the
authoritative plan for the next BUILD session to execute.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 13:55:39 +01:00
James VeitchandClaude Sonnet 5 736ffd1e77 feat(llm): shared chat_structured() via pydantic-ai (T011-T013)
Provider-layer only — src/comfydv/_llm/chat.py doesn't touch ollama.py, so
it's safe to land ahead of the deferred atomic node cutover (issue #16).
Ports ADR-006's exact retry/validation contract: bounded retries (0-5,
clamped), RuntimeError naming model/attempt-count/truncated-last-response
on exhaustion. Agent's own internal retries disabled so the error contract
is comfydv's, not pydantic-ai's.

Required an API-discovery spike (Agent/OpenAIProvider/OpenAIChatModel
constructors and exception surface aren't reliably knowable from training
data for a fast-moving library) before tests could be written meaningfully
— tests and implementation were validated together rather than strictly
red-first; noted in tasks.md rather than presented as pure TDD.

Also fixes a pre-existing gap in test_packaging.py's requirements.txt/
pyproject.toml consistency check: it stripped `[extras]` on one side of the
comparison but not the other, which this change's pydantic-ai-slim[openai]
entry exposed as the first dependency with an extras marker.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 13:37:16 +01:00
James VeitchandClaude Sonnet 5 1b1c7830d5 docs(beacon): correct tasks.md — US1/US3 cutover is atomic, not incremental
Discovered mid-implementation: OllamaClient is a single shared producer for
every downstream Ollama node, so changing its output type per ADR-007
breaks all consumers simultaneously, and the class rename breaks
tests/test_ollama.py's 125 references atomically, not per-story. Confirmed
by independent product + engineering review (agent-trio deliberation,
aligned). Re-scoped the node-layer cutover as its own follow-up session,
tracked in issue #16. ADR-007's decision itself is unaffected — only
delivery sequencing changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 13:31:21 +01:00
James VeitchandClaude Sonnet 5 9d90f39854 feat(llm): scaffold LLMProvider protocol and OllamaProvider (T001-T006)
Adds pydantic-ai-slim[openai] per ADR-007, the Message/ModelStatus/
ModelInfo/LLMProvider protocol, and an OllamaProvider skeleton carrying
host/headers as instance state — ported _post_json/_fetch_models/_run_async/
_TTLLRUCache infra from comfydv.ollama unchanged. Method bodies land per
user story in specs/007-llm-provider-abstraction/tasks.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 13:25:47 +01:00
James VeitchandClaude Sonnet 5 fb0556fff3 docs(beacon): DESIGN artifacts for LLM provider abstraction (ADR-007)
Adopt a shared LLMProvider protocol (list/load/unload/chat/structured-chat)
as the adapter pattern answer to GitHub issue #15 — Ollama and llama.cpp
share the chat/structured-output mechanism via pydantic-ai while model
lifecycle stays backend-specific per provider. Supersedes ADR-006, narrows
ADR-004's scope to non-chat REST calls.

Adds epic 007 (llm-provider-abstraction, prerequisite) and the
llamacpp-integration epic it unblocks, plus the full spec/plan/tasks/BDD
scaffold for the prerequisite epic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132ojafeazQ3ephcBejEWFj
2026-07-11 13:25:40 +01:00
James Veitch b3374c88c4 add loop 2026-07-11 13:07:58 +01:00
James Veitch 50076d7c6b add manager to local harness 2026-07-10 16:40:51 +01:00
James Veitch aa7ff8dae3 Merge pull request #14 from darth-veitcher/feature/ollama-structured-output
feat(ollama): opt-in structured output via tool-calling + pydantic validation
2026-07-09 17:51:08 +01:00
James VeitchandClaude Sonnet 5 253569d483 feat(ollama): live dynamic output sockets for structured_output
Adds a POST /dv/ollama/update_structured_outputs route and a matching JS
extension so editing structured_output/output_schema on
OllamaChatCompletion updates the node's output sockets immediately in the
UI — one per schema property — mirroring FormatString's existing live
dynamic-output pattern. No need to run the graph first to see the extracted
fields appear. Invalid/incomplete JSON while typing falls back to the base
3-output shape rather than erroring the request.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 17:37:41 +01:00
James VeitchandClaude Sonnet 5 40e851588d feat(ollama): opt-in structured output via tool-calling + pydantic validation
Adds structured_output/output_schema/max_retries inputs to
OllamaChatCompletion, fixing three real reliability problems: models
prepending commentary, wrapping responses in code fences, and occasionally
returning blank output. When enabled, requests go through Ollama's
OpenAI-compatible tool-calling endpoint (a single forced tool call matching
output_schema) instead of native /api/chat, and each response is validated
against a dynamically-built pydantic model — invalid/blank responses retry
with fresh network calls, exhausting into a clear RuntimeError rather than
silently passing bad data downstream. One additional ComfyUI output socket
is exposed per schema property, mirroring FormatString's existing
dynamic-output pattern. Fully backward compatible: structured_output
defaults off and non-structured behavior is untouched.

Ollama's native /api/chat "format" field (JSON-Schema-constrained decoding)
was tried first but proved unreliable against a real local model during
implementation — see ADR-006 for the full investigation and why
tool-calling was chosen over both native format and pydantic-ai (the latter
would reintroduce httpx/openai-sdk, reversing ADR-004).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 12:50:54 +01:00
James Veitch 23f8f55493 Merge pull request #13 from darth-veitcher/chore/finish-ollama-integration-epic
chore(beacon): finish ollama-integration epic
2026-07-04 14:09:52 +01:00
James VeitchandClaude Sonnet 5 d38cd5e794 chore(beacon): finish ollama-integration epic
All owned specs shipped, so beacon epic finish moves it to archive/ and
flips status to Done. Clears the epic-spec-lifecycle and epic-gates
warnings beacon doctor --strict was raising.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 14:09:15 +01:00
James Veitch e42913d587 Merge pull request #12 from darth-veitcher/chore/dynamic-versioning-from-vcs
chore: derive package version from git tags via hatch-vcs
2026-07-04 14:03:15 +01:00
James VeitchandClaude Sonnet 5 335ff5e31a chore: derive package version from git tags via hatch-vcs
Replaces the static project.version string with dynamic = ["version"],
sourced from the nearest git tag (hatch-vcs). Fixes docs.yml, which read
pyproject.toml's version field directly and would have broken once that
field stopped being a plain string; it now reads the resolved version via
importlib.metadata after `uv sync` installs the package.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 13:51:55 +01:00
James Veitch 4f69405af6 Merge pull request #11 from darth-veitcher/feature/ollama-auth-headers-and-format-string-inline-display
feat: Ollama auth headers + response cache, FormatString inline display
v0.1.0
2026-07-04 13:45:07 +01:00
James VeitchandClaude Sonnet 5 eef0f1ff59 feat(format-string): inline read-only display of formatted_string
FormatString is now OUTPUT_NODE=True and returns {"ui": {"text": [...]},
"result": (...)} instead of a bare tuple, so the rendered output shows
directly on the node — no separate Show Text node needed to inspect it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 13:43:37 +01:00
James VeitchandClaude Sonnet 5 fcb95fadfa feat(ollama): per-client auth headers and local response caching
Adds an OLLAMA_HEADERS socket type (Basic Auth, Bearer Token, Custom) that
composes onto OllamaClient, so requests to a proxied/authenticated Ollama
server carry the right Authorization header through model discovery,
load/unload, and chat.

Also adds a bounded local cache: a short-TTL LRU for model discovery
(avoids duplicate /api/tags round-trips from repeated node creation/refresh)
and an unbounded-TTL LRU for chat responses, keyed on the full request
signature — needed because OllamaChatCompletion is OUTPUT_NODE=True and so
is re-executed by ComfyUI on every queue run even when nothing changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 13:43:28 +01:00
James VeitchandClaude Sonnet 4.6 fbf73a7d74 fix(ci): use --group docs instead of --extra docs for uv sync
dependency-groups in pyproject.toml are not optional-dependencies extras;
uv sync uses --group <name> to include them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-29 14:53:18 +01:00
James Veitch bcb2e70e08 Merge pull request #10 from darth-veitcher/feat/chat-inline-display
feat(chat): inline display + model→STRING, OLLAMA_HOST docker support
2026-06-29 14:50:05 +01:00
James VeitchandClaude Sonnet 4.6 09ea6ab716 docs: update lifecycle wiring, add inline display note, add CI mike deploy
- README.md + docs/index.md: fix stale wiring instructions that referenced
  model_name optional input (removed); now correctly shows wiring
  OllamaLoadModel.model_name → OllamaChatCompletion.model (STRING)
- ChatCompletion description updated to mention inline response display
- Minimal chat workflow prose updated to reflect STRING model input + inline
  display (no separate Text Display node needed)
- .github/workflows/docs.yml: auto-deploy docs via mike on push to main;
  reads version from pyproject.toml, deploys as <version> + stable alias

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-29 14:49:26 +01:00
James VeitchandClaude Sonnet 4.6 4c64e2b6da feat(chat): I-commit — model→STRING, OLLAMA_HOST docker support, dynamic test fixtures
- OllamaChatCompletion.model changed from COMBO to plain STRING so it can
  receive a wired value from OllamaLoadModel without a separate model_name
  socket (removes dual-input UX confusion and COMBO validation errors)
- Removed model_name optional input — model STRING is the single entry point
- _load_default_models() tries OLLAMA_HOST env var → host.docker.internal →
  localhost so COMBO nodes pre-populate on server start inside Docker
- docker-compose.dev.yml: OLLAMA_HOST=http://host.docker.internal:11434 so
  model dropdowns are populated at startup without a manual Refresh click
- JS: refreshModelWidget handles both COMBO and STRING widget types; keeps
  user-typed/wired value when it is already a known model
- Tests: first_generative_model fixture dynamically selects the first
  installed model instead of hardcoding embeddinggemma:latest (which is an
  embedding-only model that rejects /api/generate with HTTP 400)
- Tests: test_multi_turn_receives_context passes think=False to prevent
  Qwen3 thinking-mode models from returning empty content fields
- Screenshots retaken: ollama_chat.png, ollama_lifecycle.png, ollama_workflow.png

All 80 unit + 9 integration tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-29 12:49:53 +01:00
James VeitchandClaude Sonnet 4.6 da88e4cdaf test(chat): T-commit — model must be STRING, model_name input removed
2 failing tests:
- test_chat_completion_model_is_plain_string: model must be STRING not COMBO
- test_chat_has_no_model_name_input: redundant optional input removed

Removed test_wired_model_name_overrides_combo (behaviour going away).
Added test_chat_model_receives_wired_string (STRING wiring works directly).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-29 12:32:01 +01:00
James VeitchandClaude Sonnet 4.6 1377c9fbe6 feat(chat): I-commit — inline response display via OUTPUT_NODE
OllamaChatCompletion now sets OUTPUT_NODE = True and returns
{"ui": {"text": [...]}, "result": (...)} so ComfyUI renders the
response text directly in the node body after each run.

On first turn, the full response is displayed.
On subsequent turns, the response is shown with a compact history
summary (last 6 messages, 100-char snippets) so multi-turn context
is visible at a glance without a separate display node.

_history_preview() helper added for the summary rendering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-29 09:50:09 +01:00
James VeitchandClaude Sonnet 4.6 ca4c322b9d test(chat): T-commit — inline display (OUTPUT_NODE + dict return)
5 failing unit tests:
- test_chat_is_output_node
- test_chat_returns_ui_result_dict
- test_chat_ui_contains_response_text
- test_chat_result_is_3_tuple
- test_wired_model_name_overrides_combo (updated to unpack from ["result"])

Integration tests updated to unpack from ["result"] for API consistency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-29 09:49:29 +01:00
James Veitch d89a3bb216 fix(ollama): correctness — event-loop safety, HTTP errors, endpoint, types, timeout
fix(ollama): correctness — event-loop safety, HTTP errors, endpoint, types, timeout
2026-06-29 01:39:45 +01:00
James VeitchandClaude Sonnet 4.6 48b84e23c7 fix(screenshots): lifecycle — wider node spacing, scale 0.72
OllamaChatCompletion grew taller with the new timeout_secs widget.
Nodes repositioned with generous horizontal gaps (60→380→720→1200)
and scale reduced to 0.72 so the full Load→Chat→Unload chain is
visible without overlap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-29 01:39:38 +01:00
James VeitchandClaude Sonnet 4.6 5ccd569f63 chore: ruff format pass — no logic changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-29 01:34:54 +01:00
James VeitchandClaude Sonnet 4.6 b44410c0a8 fix(ollama): I-commit — all 6 correctness issues resolved
- _run_async: ThreadPoolExecutor fallback when called inside a running event loop
- _post_json: raises RuntimeError on HTTP 4xx/5xx; configurable timeout kwarg
- _DEFAULT_MODELS: static placeholder; no import-time network call
- OllamaLoadModel/UnloadModel: /api/generate endpoint; keep_alive as int (-1/0)
- OllamaChatCompletion: timeout_secs INT widget (30–3600s, default 300)
- getHostFromNode (JS): traverses graph links to find connected OllamaClient node

All 67 unit tests green. docs/README corrected to /api/generate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-29 01:34:31 +01:00
James VeitchandClaude Sonnet 4.6 c80e488b49 test(ollama): T-commit — failing tests for all 6 correctness issues
9 new tests cover:
- _run_async: event-loop safety when called from a running loop
- _post_json: raises RuntimeError on 4xx/5xx; timeout kwarg forwarded
- LoadModel/UnloadModel: must use /api/generate, keep_alive must be int
- ChatCompletion: must expose timeout_secs widget and forward it

58 pre-existing unit tests remain green. Integration tests excluded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-29 01:32:49 +01:00
James VeitchandClaude Sonnet 4.6 eca1517b65 fix(ollama): OllamaUnloadModel pass-through + OllamaChatCompletion model_name output
OllamaUnloadModel now works as a pass-through node: wire
OllamaChatCompletion.response into the optional `passthrough` input and
OllamaChatCompletion.model_name into `model` — ComfyUI's data-flow graph
then guarantees Load → Chat → Unload execution order.

OllamaChatCompletion gains an optional `model_name` forceInput so
OllamaLoadModel.model_name can wire in (creating Load → Chat ordering) and
overrides the COMBO dropdown. It also returns `model_name` as a 3rd output
so the model can flow downstream to UnloadModel without duplication.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-29 01:16:30 +01:00
James VeitchandClaude Sonnet 4.6 24a212516e docs: add Ollama model lifecycle section with load/unload screenshot
Shows OllamaLoadModel + OllamaUnloadModel wired to the same client, with
prose explaining why explicit VRAM management matters on single-GPU setups.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-29 01:06:06 +01:00
James VeitchandClaude Sonnet 4.6 653be8622d fix(docs): correct Ollama screenshots — seed localStorage to bypass template browser
Fresh Playwright sessions landed on ComfyUI's template gallery (no `workflow`
key in localStorage) instead of the node canvas. Pre-seeding an empty workflow
via Playwright storage_state fixes the capture. Also shows localhost:11434 in
the OllamaClient screenshot (user-facing default) rather than the Docker-internal
routing URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-29 01:02:08 +01:00
James VeitchandClaude Sonnet 4.6 f3574c0202 feat(006): Ollama model integration — 14 nodes for LLM inference in ComfyUI workflows (#8)
* fix: eliminate all identified broken windows before Ollama implementation

- pyproject.toml: add requires-python>=3.11, real description, remove dead
  [project.scripts] entry (no main() exists), add system/integration markers
- __init__.py: replace pytest-in-sys.modules guard with comfy-in-sys.modules
  (checks the actual condition; cleaner semantics)
- tests/conftest.py: remove duplicate module-level class definitions and the
  aiohttp mock (real aiohttp is installed; mock blocked integration tests)
- circuit_breaker.py: remove copy-pasted INPUT_TYPES boilerplate docstring;
  logger.debug → logger.warning for ComfyUI-absent branch (degraded state)
- random_choice.py: remove copy-pasted INPUT_TYPES boilerplate and dead
  triple-quoted string literal; fix bare re-raise to log before propagating
- format_string.py: print() → logger.warning(); remove # type: ignore on
  two dict assignments; remove cargo-culted # noqa: F401 (ruff doesn't flag)
- scripts/take_screenshots.py: remove unused h_pad and v_pad parameters

All 76 tests pass; ruff clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb

* design(006): full DESIGN phase artefacts for Ollama model integration

Adds spec, plan, tasks, 6 BDD feature files, ADRs, and epic file for
spec 006-ollama-model-integration (14 nodes ported from
darth-veitcher/comfyui-ollama-model-manager, Issue #1 fix, logging
harmonisation, live-service integration tests).

- specs/006-ollama-model-integration/{spec,plan,tasks}.md
- specs/006-ollama-model-integration/features/us{1-6}_*.feature
- specs/006-ollama-model-integration/{.beacon.toml,checklists/}
- project-management/ADRs/ADR-004-aiohttp-over-httpx-for-ollama.md
- project-management/ADRs/ADR-005-ollama-host-config-via-client-node.md
- project-management/Roadmap/epics/ollama-integration.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb

* chore(006): Phase 1 setup — Ollama fixtures, Justfile recipes, test skeleton

- conftest.py: add ollama_host (session), ollama_available (session),
  skip_if_no_ollama fixtures for @pytest.mark.integration tests
- Justfile: add test, test-unit, test-integration, test-system recipes
- tests/test_ollama.py: empty skeleton with module docstring and lazy-import
  block (uncommented phase-by-phase as ollama.py is built)

Closes T001 (system marker — done in broken-windows commit), T002, T003, T004.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb

* feat(006): Phase 3 foundational — ollama.py, ollama.js, __init__ wiring (T011-T014)

- src/comfydv/ollama.py: OllamaClientType, _run_async, _fetch_models,
  _post_json, _DEFAULT_MODELS (populated at import), /dv/ollama/models route,
  all 14 node classes (US1-US6) with correct ComfyUI contract
- src/js/ollama.js: app.registerExtension for OllamaModelSelector/LoadModel/
  ChatCompletion — refreshModelDropdown + ⟳ refresh button
- src/comfydv/__init__.py: register all 14 Ollama nodes in
  NODE_CLASS_MAPPINGS and NODE_DISPLAY_NAME_MAPPINGS

Closes T011, T012, T013, T014.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb

* test(006): full Ollama test suite — T015-T031 all US TDD-T commits (T-T032)

Add tests/test_ollama.py with 6 test classes covering all 14 nodes:
  - TestUS1OllamaConnection: client type, custom host, unreachable error
  - TestUS2ModelSelection: fetch list, selector output, COMBO type, empty fallback
  - TestUS3ModelLifecycle: COMBO type, empty-model guard, load/unload integration
  - TestUS4ChatCompletion: COMBO type, single-turn, multi-turn, history growth
  - TestUS5ComposableOptions: all 7 option nodes + chaining + deterministic
  - TestUS6HistoryInspection: debug output, length counts
  - TestNodeContracts: parametrised over all 14 nodes for ComfyUI contract

Also remove tests/__init__.py — its presence caused pytest to walk up past the
repo root and add the parent directory to sys.path, making the root-level
__init__.py win the 'comfydv' namespace over src/comfydv/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb

* style: ruff format test_ollama.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb

* feat(006): T032/T034 — extend packaging tests and manager entry for 14 Ollama nodes

Update EXPECTED_NODENAMES in test_packaging.py to include all 14 Ollama node
display names. Update comfy-manager-entry.json nodename array and description
to register all 14 nodes with ComfyUI Manager.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb

* docs(006): T033 — add Ollama section to README and docs/index.md

Add all 14 Ollama nodes to the node reference table and add a dedicated
Ollama section covering minimal workflow, option nodes, and multi-turn
conversation patterns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb

* test(006): T035-T/I — TestLoggingConsistency: no print(), correct log levels

Add TestLoggingConsistency class to tests/test_logging.py:
- AST scan: assert zero print() calls across all src/comfydv/*.py
- circuit_breaker: ComfyUI-absent branch uses logger.warning() not debug
- random_choice: exception path calls logger.error() and uses plain raise
- ollama: absent-ComfyUI paths emit at least 2 logger.warning() calls

All checks pass (fixes were already in Phase 2 broken windows commit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb

* docs: add Quickstart section to README — satisfies beacon doctor readme-completeness

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb

* chore(006): mark all 55 tasks complete in tasks.md — spec fully implemented

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb

* docs(006): T036/T037 — add Ollama screenshots and docker extra_hosts for host Ollama routing

Captures 4 Ollama node screenshots (client, chat, workflow, options) via
Playwright against the live dev ComfyUI instance. Adds extra_hosts to
docker-compose.dev.yml so the container can reach host-machine Ollama.
Updates README and docs/index.md to embed all 8 screenshots.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 00:55:15 +01:00
James VeitchandClaude Sonnet 4.6 9b64203bee docs: update docs/index.md to match README — add Circuit Breaker, new screenshots
Mirrors the README.md rewrite into the MkDocs site landing page:
- Add Circuit Breaker section with screenshot (assets/circuit_breaker.png)
- Update Format String and Random Choice prose to match README
- Replace stale dependency list (rich/colorama/termcolor removed in spec 001)
- Add ComfyUI Manager install path
- Image paths use assets/ (MkDocs-relative) vs docs/assets/ in README

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-28 22:25:08 +01:00
James Veitch 32d0bbfa34 Merge pull request #7 from darth-veitcher/docs/playwright-screenshots
docs: automate README screenshots with Playwright
2026-06-28 21:53:08 +01:00
James VeitchandClaude Sonnet 4.6 ca1c35a3cf docs: automate README screenshots with Playwright from live ComfyUI harness
Replace hand-crafted PowerPoint exports with Playwright-generated screenshots
taken from the actual running Docker test harness:

- scripts/take_screenshots.py: headless Chromium via Playwright; creates nodes
  programmatically via LiteGraph/window.app, triggers updateNodeConfig() for
  FormatString's dynamic socket update, then crops tightly around each node
- docs/assets/: four screenshots (fstring, jinja2, random, circuit_breaker)
  replacing the old 1920×1080 PowerPoint exports (90%+ smaller, more accurate)
- Justfile: add `screenshots` recipe (starts harness if needed, waits healthy,
  runs the script)
- README.md: rewrite to cover all three nodes including Circuit Breaker;
  fix stale dependency list (was listing rich/colorama/termcolor which we
  removed in spec 001); add ComfyUI Manager install path
- pyproject.toml + uv.lock: add playwright to [dev] dependencies

Run: just screenshots

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RWysE6jYn4YjNorLBQ1cXb
2026-06-28 21:52:45 +01:00
James Veitch f90b8db722 fix(002): manager-compatible install — requirements.txt, comfy-manager-entry.json, Docker harness
fix(002): manager-compatible install — requirements.txt, aiohttp dep, metadata, Docker harness
2026-06-28 21:22:03 +01:00