Unit coverage for NewTelemetryClient: an unset OTEL_HOST yields ErrNotConfigured,
and an unreachable collector returns within the dial timeout instead of hanging
(a TCP-accepting, silent listener drives the WithBlock path).
Also close the grpc.ClientConn on the exporter-creation error paths — a
successful dial followed by a failed exporter New() previously leaked it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The two-context design in ddd3916 was correct but fragile: the field named
`ctx` was the liveness signal goroutines must NOT bail on, while the real
cancellation signal was `stopCtx`. That inversion is the exact trap that
produced the earlier happy-path regression (bailing on `ctx`, which a normal
input-close cancels, dropped in-flight results). It also left a latent
restart-without-Stop hazard: a second Start() after input-close orphaned the old
stopCtx and shared wg, deadlocking a later Stop() (with a data race).
Collapse `ctx` into a plain `running` bool (it was only ever read via .Err(),
never awaited), keep `stopCtx` as the single cancellation signal, and guard
Start() on stopCtx so a restart requires a prior Stop() — removing both the
naming trap and the restart hazard.
Comprehensive tests (queue_scenarios_test.go): boundary-N full delivery,
contiguous-prefix-on-abandon, a randomized prefix-invariant fuzz, goroutine-leak,
restart-reject, process-error, double-stop, Running() transitions. The happy-path
tests hang on the pre-rework code and pass here (side-by-side under -race). Live:
a flow's non-empty container dirs of 1/40/100/500 files each deliver every entry;
the error path returns 500 without hanging.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous fix (97e5730) had workers and the reader bail on q.ctx, but the
reader also cancels q.ctx on a normal input-close — so the ListContainerDir
happy path regressed: after input closed (buffer names, close, read all N),
workers dropped still-undelivered results and the consumer hung waiting for the
last one. Any non-empty container directory that didn't error early hung the
file-manager request.
Split the signals: q.ctx still tracks "running" (cancelled by input-close or
Stop), and a new q.stopCtx is cancelled only by Stop(). Workers and the reader
bail on q.stopCtx, so a normal input-close drains and delivers every result
while a hard Stop() still unblocks a consumer that abandoned output. Stop()'s
already-stopped guard now checks q.stopCtx (input-close alone must not
short-circuit it, or blocked workers leak).
Tests: DeliversEveryResultAfterInputClose (hangs on the old fix, verified
side-by-side under -race) + StopHardStopsAfterInputClose; deadlock + ordering
still green under -race.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A set-but-unreachable OTEL collector hung startup: NewTelemetryClient dialed
with grpc.WithBlock() and no timeout on the deadline-free signal context, so
the process blocked forever before it ever served. And a non-ErrNotConfigured
init error from either observability client was log.Fatalf, killing the
process. Both let an OPTIONAL integration take the whole app down.
Bound the dial with a 10s timeout, and on init failure degrade to a no-op
observer with a logged warning instead of exiting. Verified live: a bad
OTEL_HOST that hung startup indefinitely now boots in ~14s with a warning and
runs a full flow; a reachable collector still boots in ~4s with no warning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ListContainerDir aborts on the first stat error and stops reading the queue's
output channel, leaving workers blocked on the unbuffered `q.output <- result`
send and the reader blocked on a full `q.queue`; Stop() -> wg.Wait() then hung
forever. Select every pipeline send/wait on q.ctx so a stopped queue unwinds.
On stop a worker returns without msg.cancel(), so later workers also bail via
q.ctx and output ends at a contiguous prefix instead of developing gaps.
Regression test drives the exact hang (unread output -> Stop must return),
verified with -race.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The openai-compat consolidation dropped the comment warning that DeepSeek needs
the legacy top-level "reasoning_effort" string form; without it a maintainer
could add openai.WithModernReasoningFormat() to the shared opts and silently
break DeepSeek thinking mode. Restore it at the shared opts choke point.
Also fix two stale pointers: glm/qwen config.yml cited
WithPreserveReasoningContent() "in glm.go/qwen.go", but it moved to
openaicompat.go during that same consolidation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- prompts_test.go: PatchPrompt rejects a syntax error, an undeclared variable,
and a whitespace-only template over REST (the path a raw client hits when the
UI is bypassed) and does not persist them; valid templates still create/update.
- validation_test.go: validateKnowledgeFieldLengths accepts each field at its
max length and rejects one character over.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The GraphQL createPrompt/updatePrompt mutations run validator.ValidatePrompt
(Go text/template parse + declared-variable check + trial render), but the
REST PUT /prompts/:type handler only checked the field was present, so a
prompt with a syntax error or an undeclared variable could be stored over
REST and later break rendering. Mirror the GraphQL check in PatchPrompt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The REST handlers reject empty or oversized knowledge fields and over-long
API-token names through their request-model validate tags, but the GraphQL
mutations — the path the web UI uses — accepted them unchecked, so the same
entity could be stored past its documented limits depending on the caller.
Mirror the REST caps at the resolver boundary:
- createKnowledgeDocument / updateKnowledgeDocument: require content (and
question on create), and cap content/question/description/codeLang lengths.
- createAPIToken / updateAPIToken: cap the token-name length.
Limits are kept in sync with server/models via mirror comments.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The renameFlow / putUserInput / callAssistant GraphQL resolvers passed the
title/input straight to the controller with no non-empty check, while the
equivalent REST handlers reject them with 400. An empty flow title in
particular then fails the Flow model's `required` invariant and breaks the
REST GET /flows listing for that user. Mirror the REST guard at the resolver
boundary, matching the existing createFlow "... is required" checks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The streaming ZIP refactor made `ZipResources` open each blob inline while
`streamZipArchive`'s writer commits HTTP 200 on the first byte. A blob missing
on disk (a DB record whose blob file is gone) failed mid-stream, so the client
received a 200 with a central-directory-valid but incomplete archive — the
missing files silently dropped (regression vs main, which buffered then sent).
Stat every blob up front; a missing one now returns before any byte is written,
so `streamZipArchive` emits a clean structured error instead. Keeps the
streaming memory benefit.
Tests (both proven fail-on-unfixed / pass-on-fixed): a unit test asserts the
writer stays empty when a blob is missing, and a download-handler test asserts
a missing blob in a multi-file ZIP returns a clean 500, not a truncated 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- providers: prove a stale user-provider row does not knock out a valid USER
sibling (TestGetProviders_StaleUserRowSpansValidSibling — ollama builds keyless
so it survives beside a skipped minimax; side-by-side verified).
- settings-provider: cover the create-form ?type=/?id= guards (disabled/unknown
type and clone-of-disabled bounce to the list; an enabled type renders). These
had zero coverage — a swap/drop-return regression would have shipped green.
- knowledge-form: cover performSave's server-document reset branch (untouched
fields reflect the returned document under keepDirtyValues), and the useBlocker
"Save and leave" path via a real data router (proceeds the blocked nav, does
NOT honor a CREATE redirect). Swap the negative no-navigate assertion's
setTimeout(0) flush for a deterministic Save-disabled anchor.
Each test mutation-verified to fail on the reverted production code; 652 frontend
tests + go test green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
From a strict re-review of the recent commits (no blocker/high/security found):
- providers.go GetProviders: the skip covers ANY unbuildable saved provider, not
only a disabled type — drop the misleading "of unavailable type" wording and
lower the line to Debug (it re-fires on every providers fetch; WithError keeps
the reason).
- settings-prompt countVariableUses: drop the redundant seed-in-map side effect;
the component already falls back to `?? 0` for unused variables, so the loop's
own `?? 0` is the only seed needed.
- settings-providers create menu: render a disabled "No available provider types"
placeholder instead of a silently-empty menu (loading / no-keys / failed-query
states), with a test covering it.
Counts live-verified unchanged (AgentType 1->3 on the stand); go test + 646
frontend tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A saved user provider whose type is no longer enabled (e.g. its API key was
removed) made GetProviders return an error for the ENTIRE `providers` query —
one stale row blocked all flow creation in the UI ("No available providers").
Skip and log such rows, mirroring how startup already tolerates disabled
default providers. Pre-existing robustness gap, not introduced by this branch.
Verified live on the docker stand: the `providers` query went from a hard
error to returning all 10 enabled providers (the stale minimax row skipped),
and flow creation works again. Adds a side-by-side regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
anthropic, bedrock, and gemini also carry per-agent prices in config.yml but
were not covered. Adding them confirms no current drift and guards them going
forward. bedrock's loaders take a *config.Config, so they're wrapped with an
empty config to read the embedded catalog.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Audited all 133 catalog models across 9 providers against official pricing
pages, each delta independently re-verified. openai/anthropic/gemini/glm/kimi
were already correct (0 changes). Corrections:
- deepseek-v4-pro: 1.74/3.48/0.0145 -> 0.435/0.87/0.003625 (catalog was 4x the
official DeepSeek rate).
- bedrock mistral-large-3-675b: 4.0/12.0 -> 0.5/1.5 (8x overstatement; old
Mistral Large 24.07 rate).
- minimax M3 0.6/2.4 -> 0.3/1.2, M2.7 0.4/1.1 -> 0.3/1.2, M2.7-highspeed
0.4/1.1 -> 0.6/2.4, all +0.06 cache (permanent-50%-off effective rates).
- qwen3.6-35b-a3b: 0.248/1.485 -> 0.375/2.25 (catalog had the China-mainland
price, not the International endpoint the rest of the catalog uses).
- qwen: add cache_read = 0.20 x input (implicit-cache rule) to 16 open-source
models that were missing it.
config.yml agent prices synced for deepseek-v4-pro and MiniMax-M3 (guard test
keeps them aligned).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verified per the official OpenAI model pages:
- gpt-5.2-codex: [low, medium, high, xhigh]
- gpt-5-pro: [high] (the page states it "only supports reasoning.effort: high")
Other openai reasoning models (gpt-5, gpt-5.1 = no xhigh; gpt-5-mini/nano,
codex variants, o-series) keep the default — their official pages do not
enumerate an accepted effort set.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Alibaba Model Studio's context-cache doc bills cached input tokens at 20% of the
input price (implicit hits, International endpoint). Most of the qwen catalog
already used 20%; align the outliers and guard against future drift.
- qwen3.7-max 1.25 -> 0.5, qwen3.7-plus 0.2 -> 0.08: corrects an earlier change
that read a "50% discount" off the model-pricing page; the authoritative rate
is 20%, so the original 0.5 for qwen3.7-max was already correct.
- qwen3.6-{max-preview,plus,flash}, qwen3.5-{plus,flash}: 10% -> 20% stale
outliers, with matching config.yml agent-price updates.
Add TestAgentConfigPricesMatchCatalog: GetPriceInfoForType returns the agent
price with no catalog fallback, so config.yml drift silently mis-prices cost.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verified per the OpenAI model pages (reasoning.effort support):
- gpt-5.4-mini, gpt-5.4-nano, gpt-5.2: [low, medium, high, xhigh]
- gpt-5.2-pro: [medium, high, xhigh] (no low)
gpt-5 / gpt-5.1 keep the default [low, medium, high] (no xhigh; gpt-5 uses
"minimal", not representable in the UI enum). Codex variants are unverified
and left for a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The settings effort dropdown is driven by a model's reasoning.efforts and
falls back to [low, medium, high] when absent, so xhigh/max were never
selectable for OpenAI-compatible models even though the backend accepts them.
Declare the verified accepted levels:
- glm-5.2: [high, max] (Z.AI: GLM-5.2 reasoning_effort accepts only high/max,
so the default low/medium were also wrong for it)
- gpt-5.4: [low, medium, high, xhigh] (OpenAI GPT-5.4/5.5 effort enum)
Other openai/gpt-5.x entries still need per-model effort verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cache_read was 0.5; the Alibaba Model Studio official price (International)
is 1.25 per 1M tokens (a 50% discount on the 2.5 input rate). Pre-existing
inaccuracy, unrelated to the qwen3.7-plus addition.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the placeholder estimates with verified list prices (USD per 1M tokens):
- kimi-k2.7-code: 0.95 / 4.00 / 0.19 cache (platform.kimi.ai official) —
identical input/output to k2.6, only the cache rate differs.
- qwen3.7-plus: 0.4 / 1.6 / 0.2 cache (Alibaba Model Studio official,
International base <=256K tier) — the earlier estimate conflated it with
the qwen3.7-max tier and was ~2.5x too high.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"the only GLM model that honors reasoning_effort" is an unenforceable
prose claim; state the capability the entry actually carries instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The tail reader's no-trailing-newline branch (a torn/truncated final
append, the case M3's fsync defends) and the over-window error branch
had no coverage. Add cases for a newline-free last line, a torn final
append that must be rejected, and a single line exceeding the 64KiB
window.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The M7 pin only checked models actually assigned to an agent type, so an
adaptive reasoning mode declared on an unassigned catalog model would pass
silently. Add a second loop over every catalog model so any openaicompat
provider that declares adaptive/adaptive-only fails loudly — matching the
guard's stated intent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The comment narrated the replaced sync.Map design and defended the
choice; the fixed-stripe array plus FNV indexing is self-evident and
the rationale already lives in commit d12b984.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- glm/models.yml: add glm-5.2 (only GLM model supporting reasoning_effort=high/max)
- kimi/models.yml: add kimi-k2.7-code (code-specialist above k2.6)
- qwen/models.yml: add qwen3.7-plus (cost-efficient tier below qwen3.7-max)
Prices for kimi-k2.7-code and qwen3.7-plus are estimated from adjacent
catalog entries and should be verified against official pricing pages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The per-agent options builder only emitted WithReasoning for low/medium/high,
and the langchaingo fork clamped xhigh/max to high in GetEffort (treating them
as adaptive-only). OpenAI GPT-5.5 and GLM-5.2 now expose xhigh/max as real
reasoning_effort levels, so that clamp encodes a stale invariant.
Extend the effort switch to pass xhigh/max through; the fork's GetEffort clamp
is removed in the local langchaingo (propagated via vendor) so the OpenAI
transport emits the real level. Adaptive (anthropic/bedrock) and budget paths
are unaffected — GetEffort is called only by the OpenAI transport. Validity is
gated per model by ModelReasoningInfo.Efforts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adaptive thinking (thinking.type=adaptive + output_config.effort) is an
Anthropic-protocol mode the langchaingo OpenAI transport cannot emit, and
the openaicompat Call path never threads PrepareAdaptiveCallOptions. So an
adaptive reasoning mode declared in an openaicompat provider's models.yml
would silently no-op.
Assert that none of the openaicompat-backed providers (qwen, glm, deepseek,
kimi, minimax) resolve to adaptive thinking for any agent type. The day
someone adds an adaptive model/mode to one of their configs, this fails
loudly instead of dropping the thinking budget in silence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
record() called readLastEvidenceReceiptHash on every append, which re-read
and re-hashed the entire receipts.jsonl just to get the last hash and verify
the whole chain — O(N^2) over a flow's receipts, on the tool-call hot path
under the per-path lock (M2).
Read only the last line instead (windowed ReadAt from the end), failing
closed if that line is missing, wrong-schema, or hash-mismatched. This is
O(1) per append and stays correct across the multiple recorder instances
that write one path (L6): the file stays the single source of truth, so
there is no per-instance cache to go stale. Full-chain verification belongs
in a separate read-time verify tool, not on every write.
Off by default (EVIDENCE_RECEIPTS_ENABLED=false). Measured: 1024 concurrent
appends under -race dropped from ~49s to ~7.5s.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The append wrote the receipt line and returned without flushing, so a crash
between the write and the OS flushing its page cache could lose or truncate
the last receipt, after which the fail-closed chain check halts all further
writes until the file is repaired (M3). Sync the file before returning.
Off by default (EVIDENCE_RECEIPTS_ENABLED=false), so standard deployments are
unaffected; the cost lands only when the feature is explicitly enabled.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three guards for the L6 sharded mutex pool (d12b984):
- bounded: 100k distinct flows resolve to <= 256 mutexes, so reverting to a
per-path map (the original unbounded leak) fails the test.
- high concurrency: 16 cross-executor writers on one path keep the hash chain
intact under -race.
- stripe collision: two flows that hash to the same stripe keep their own
chains intact, confirming shared stripes add contention but not corruption.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A sync.Map holding one *sync.Mutex per flow path grew unbounded for the
whole server uptime. A fixed 256-stripe array keyed by FNV-32a hash of
the path caps memory at a constant cost while preserving per-path append
serialization required by the hash-chain invariant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added langfuse observability support to the graphiti search tool, allowing for detailed tracking of search operations.
- Introduced a mapping of search types to human-readable titles for better traceability in logs.
- Implemented a new method to build the input payload for langfuse retrievers, encapsulating relevant search parameters.
- Enhanced error handling to include retriever status updates on success and failure, improving observability and debugging capabilities.
- Introduced a process-level singleton for the anonymizer replacer to enhance performance and ensure thread safety.
- The replacer is built once using patterns loaded at startup, allowing it to be shared across all flow executor instances.
- Updated the flow tools executor to utilize the shared replacer, simplifying the creation process and improving efficiency.
- Replaced expirable LRU cache with non-expirable lru.Cache in multiple files for improved performance and reduced resource usage.
- Introduced a new function, newSummarizerCache, to create a fixed-size LRU cache for summarizer results, eliminating the need for background goroutines.
- Updated related code to ensure compatibility with the new cache implementation.
The OAuth callback links/creates an account by the email from ResolveEmail, and
Google/GitHub only ever return a verified address — but that invariant lived
inside each provider and was invisible at the callback. A future OAuth provider
that omitted the verified check would let an attacker register a victim's email
there (unverified), "sign in", and be linked straight into the victim's account:
instant takeover.
Make verification part of the contract: ResolveEmail now returns
(email, verified, err), and the callback refuses to proceed when !verified.
Because Go's bool zero value is false and the compiler forces the new return, a
provider that forgets to report verification fails closed (its own login breaks)
rather than opening a takeover. Google reports claims.EmailVerified; GitHub only
selects verified addresses, so reports true. Added a callback test: a provider
reporting an unverified email is rejected (no link, no session) — red before the
gate, green after.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EmailChange.Mail validated with `vmail`, which accepts not just a real address
but also the literal "admin" and any UUID — escape hatches that exist so the
seeded admin row (mail "admin") passes User.Mail's Valid(). The account's
change-email form (added this branch) reaches it, so a user could save "admin"
or a UUID as their own email — a non-deliverable value (no privilege gain: roles
come from role_id, and UNIQUE(mail) blocks colliding with the real admin row).
Add a strict `realemail` validator (same address regex as vmail, minus the
hatches) and use it for EmailChange.Mail; User.Mail keeps `vmail` so the seeded
admin still validates. Tests cover the validator (admin/UUID rejected, real
address accepted) and the handler (changing to a UUID or "admin" now 400s).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ChangeEmailCurrentUser was the only path that lowercased the email
(strings.ToLower), while local login, the OAuth callback and CreateUser all
store/compare it raw against a case-sensitive UNIQUE(mail). That lone
normalization let a changed address (now lowercased) miss a later raw-case
login, and let the uniqueness pre-check (run on the lowercased value) skip an
existing mixed-case row. Drop the ToLower so every path is consistently
case-sensitive again — the pre-branch invariant. The email validator already
rejects surrounding whitespace, so the paired TrimSpace was dead.
Update the test that codified the old lowercasing to assert case preservation.
A fully case-insensitive scheme (normalize everywhere, or citext /
UNIQUE(lower(mail))) is tracked separately as a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NewProviderController loaded every provider's default config (NewConfig) in an
ungated loop that treated any error as fatal, while actual provider construction
(New) is correctly gated on Enabled. A disabled provider pointed at an unreadable
BEDROCK_CONFIG_PATH (or the equivalent custom/ollama external config) therefore
aborted startup, even though that provider was never going to be used.
Extract the config-loading loop into buildDefaultConfigs and gate the failure on
Enabled: a disabled provider whose external config can't be read is logged and
skipped; an enabled provider still fails loudly. Tests cover both paths
(disabled + bad path non-fatal, enabled + bad path fatal).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A type present in only one of the two hand-maintained lists fails silently:
accepted by the API then erroring "unknown provider type" at construction, or
rejected 422 despite working. Assert set-equality and canonical names so drift
fails in CI instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UsesAdaptiveThinking/PrepareAdaptiveCallOptions had no test coverage after the
native-langchaingo migration removed the old transport-layer tests. Add table
tests: an adaptive-only model forces adaptive over an agent's budget choice or an
absent reasoning block, while budget-only models stay untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The four Zip* builders deferred zw.Close() with an unnamed return, dropping the
error from the central-directory flush. After the resources download moved to
direct socket streaming, a dropped connection during that final flush could
return a truncated archive under HTTP 200. Capture Close() via a named return so
streamZipArchive logs and aborts the partial response. Regression tests in both
packages drive the Close() error path with a failing writer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adaptive thinking (Claude Opus 4.7+) is now implemented natively in the
vxcontrol/langchaingo fork (v0.1.14-update.6): the anthropic and bedrock clients
emit thinking.type=adaptive + output_config.effort and omit the sampling params
adaptive models reject. PentAGI no longer patches the serialized request body.
- pconfig.PrepareAdaptiveCallOptions appends llms.WithAdaptiveReasoning; the
ctx-effort plumbing (WithAdaptiveEffort/AdaptiveEffortFromContext) is removed.
- Delete the bedrock smithy Build middleware and anthropic http RoundTripper
(adaptive_thinking.go + tests in both packages) that rewrote the request body.
- Bump the langchaingo require to v0.1.14-update.6; refresh llms_how_to.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The provider controller repeated every provider type across five hand-written
blocks (default-config wiring, key-gated instantiation, GetProvider fallback,
NewProvider, buildProviderFromConfig) plus the API-layer Valid() whitelist.
Introduce pkg/providers/registry.go: a providerRegistry table whose entries hold
the per-type constructors and credential gating, with small adapter helpers
(ignoreConfig/fromData) absorbing the signature variance (bedrock/ollama/custom
take *config.Config; the rest don't). The controller now wires and looks up
providers in loops over the table. Valid() validates against the new canonical
provider.AllProviderTypes list (no heavyweight import, no cycle).
Adding a provider's backend wiring drops from ~6 edits across these functions to
one registry entry. providers.go shrinks ~300 lines; behavior is unchanged
(same exported funcs, same gating) and all provider/server/graph tests stay green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
deepseek/glm/kimi/qwen/minimax were near-verbatim clones — identical struct +
13 interface methods, differing only in constants, env accessors, and two flags.
Extract pkg/providers/openaicompat with a Spec + a shared Provider implementing
the interface; each provider now keeps only its constants, embedded config/models,
the Default*/Build* loaders, and a thin New() that delegates to openaicompat.New.
Each provider file drops from ~190 to ~78 lines with zero behavior change (same
exported funcs; per-provider tests stay green). The native openai provider is
intentionally left separate (no model prefix / reasoning-preservation).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review nits:
- schema.resolvers.go: log (not swallow) bedrock.DefaultModels errors so an
invalid BEDROCK_MODELS_PATH no longer yields a silently-empty model list.
- settings-providers.tsx: derive the create-provider type list from an
exhaustive Record<ProviderType,string> so a future ProviderType is a compile
error here instead of silently missing from the menu.
- trim two doc comments (convertModelReasoningMode, ReasoningFields) to lead
with the load-bearing contract instead of restating the name.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The bedrock and anthropic adaptive_thinking.go files carried ~96 byte-identical
lines (prepareCallOptions, usesAdaptiveThinking, modelReasoningMode,
reasoningConfigForType, agentConfigForType, the effort context key), and
agentConfigForType was a third copy of the agent-type switch pconfig already
owns in GetPriceInfoForType.
Move the model-agnostic logic onto pconfig.ProviderConfig:
AgentConfigForType (now also backing GetPriceInfoForType), UsesAdaptiveThinking,
PrepareAdaptiveCallOptions, and the WithAdaptiveEffort/AdaptiveEffortFromContext
context helpers. Each provider now keeps only its transport-specific pieces:
the smithy Build middleware (bedrock) / http RoundTripper (anthropic) and
rewriteAdaptiveThinkingBody. Also drop the dead delete(payload,"top_k") in the
anthropic rewrite — langchaingo's Messages payload only serializes
temperature/top_p. Backstop tests rewired to the pconfig API; all provider tests
+ go vet pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>