mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-24 19:04:04 +08:00
cloud-dev-toggle
17889 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 7c6947a04c |
fix(cloud): hide entire Knowledge component family in cloud mode
Set cloud_compatible = False on the base KnowledgeComponent so the active Knowledge component and the legacy Knowledge Ingestion shim are also hidden from the palette in Cloud Mode, not just Knowledge Base. All three depend on the same local on-disk database and inherit the flag via getattr, so the base is the single source of truth; removed the now-redundant declaration on KnowledgeBaseComponent. |
|||
| 1f9c74b187 |
fix(cloud): address PR review comments on cloud mode implementation
- Remove unsupported binary formats (parquet, xlsx, zip, jpg, mp3) from AWS/GDrive format choices; _extract_content_for_upload only produces text, so these would upload corrupt bytes under the wrong extension - Fix cloudFilteredOptionCount to use raw `options` prop instead of `flatOptions`, which is already cloud-filtered and would always yield zero — preventing the "no cloud-compatible models" empty state - Guard against null/primitive entries in cloud_default_overrides before accessing .value/.placeholder (shallow type guard doesn't validate nested structure) - Replace hardcoded user-facing strings with t() calls in ModelTrigger, featureTogglesComponent, and sidebarHeader; add missing i18n keys |
|||
| 8d1e60486f | fix(node): clip cloud banner overflow | |||
| db4f6ce367 | chore: remove cloud-compatibility-analysis.md from tracking | |||
| a45f9c1c37 |
feat(cloud): add env-var lock to cloudModeStore and fix cloud-related TS errors
- cloudModeStore: force cloudOnly=true and disable setCloudOnly when VITE_CLOUD_ONLY="true" or window.__CLOUD_ONLY__===true; expose isLocked - featureTogglesComponent: disable Cloud toggle UI when locked, add prop types - Fix TS error in outputModal: guard Array.isArray before accessing .message - Fix TS error in restore-dismissed-node test: add required InputFieldType fields - Wire cloudOnlyLocked through SidebarHeaderComponent to FeatureToggles |
|||
| 8eb1776a0e | [autofix.ci] apply automated fixes | |||
| 282a25f9a6 | Merge remote-tracking branch 'origin/release-1.10.0' into cloud-dev-toggle | |||
| f1ecb51319 |
fix: UI polish for Memories and Traces sidebar sections (#13205)
* feat(memory): improve memory UI with empty states, toasts, and model filtering * [autofix.ci] apply automated fixes * fix: capture memory id/name before setTimeout to prevent stale closure null access * error improvement and testcase addition * fix: surface refresh errors, restore API error messages, flatten sidebar ternary, and add keyboard access to batch-size tooltip * [autofix.ci] apply automated fixes * biome fix * fix types * [autofix.ci] apply automated fixes * fix: UI polish for Memories and Traces sidebar sections * [autofix.ci] apply automated fixes * fix: replace magic index, template literals, and inline noops in Memories/Traces sidebar * [autofix.ci] apply automated fixes * ruff fix * fix testcases --------- Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local> |
|||
| 4f9ef2af37 |
refactor: Move File System component to Files & Knowledge category (#13223)
* change fs category * component index * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| cb10f1d10f |
docs: file system component (#13183)
* docs-file-system-component * delete-internal-feature-doc * docs-filesystem-component * fix-links * Apply suggestions from code review Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com> * clarify-metadata * fix-merge --------- Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com> |
|||
| bd54c1b952 |
feat(memory): improve memory UI with empty states, toasts, and model (#13116)
* feat(memory): improve memory UI with empty states, toasts, and model filtering * [autofix.ci] apply automated fixes * fix: capture memory id/name before setTimeout to prevent stale closure null access * error improvement and testcase addition * fix: surface refresh errors, restore API error messages, flatten sidebar ternary, and add keyboard access to batch-size tooltip * [autofix.ci] apply automated fixes * biome fix * fix types * [autofix.ci] apply automated fixes --------- Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local> |
|||
| bc656b44e8 |
fix(openrouter): validate against /api/v1/auth/key and disconnect all variables (#13214)
Two follow-ups to PR #13185 surfaced during release-1.10.0 QA. BUG-1 (high) — OpenRouter API key validation never rejected invalid keys. ``validate_model_provider_key`` probed OpenRouter's ``/api/v1/models`` endpoint, which is *public* and returns 200 for any bearer token (including missing/invalid). So ``POST /api/v1/variables/`` accepted any string as an OPENROUTER_API_KEY with 201, the catalog swapped to the live 356-model list, and runs failed at request time. Route the probe through ``/api/v1/auth/key`` instead — auth-required, returns 401 on invalid, the existing ValueError → HTTP 400 mapping in ``api/v1/variable.py`` does the rest. Same fix lifts BUG-3 since the generalized validation path is shared. BUG-2 (medium) — clicking Disconnect on OpenRouter in Settings → Model Providers did nothing. ``handleDisconnect`` resolved the variable name via the static ``PROVIDER_VARIABLE_MAPPING`` constant, which doesn't include "OpenRouter" (and the constant is deprecated in favour of the dynamic ``GET /api/v1/models/provider-variable-mapping`` API). Source the variable keys from ``providerVariables`` instead and delete every configured one in parallel so multi-variable providers (OpenRouter's API key + attribution headers, IBM WatsonX's apikey + project_id + url) are fully removed. The static mapping is kept as a fallback for the brief window before the provider-variable API resolves. Tests - ``test_openrouter_provider.py``: update happy/regression cases to pin the new ``/api/v1/auth/key`` URL and add an explicit assertion that the public ``/api/v1/models`` endpoint is never used for validation. - ``test_variable.py``: add ``POST /api/v1/variables/`` integration tests covering both a 401 (returns 400 with "Invalid OpenRouter API key") and a 200 (returns 201), and assert the auth-required URL is used. - ``useProviderConfigurationDisconnect.test.tsx``: new suite — disconnect for OpenRouter deletes all three variables in one batch, fall-back to the static map for Anthropic still works, no-ops when nothing is configured, surfaces an error toast when a delete fails. Fixes the issues reported against release-1.10.0 OSS QA of PR #13185. |
|||
| 09bd68d349 |
docs: text operations component (#13152)
docs-add-text-operations-component |
|||
| 5a8c17b22d |
ci: backport bundle/nightly CI fixes from main (#13206, #13207, #13208) (#13210)
* ci: stop publishing -nightly bundle variants; release bundles on demand (#13206) Bundles (lfx-arxiv, lfx-duckduckgo) change infrequently enough that a nightly cadence is overkill. Drop the rename-to-`-nightly` and bundle publish lanes from the nightly pipeline and add a dedicated workflow_dispatch-only release_bundles.yml for purposeful releases. - nightly_build.yml: drop update_bundle_versions.py invocation and the src/bundles/*/pyproject.toml git-add guard. - release_nightly.yml: remove build-nightly-bundles and publish-nightly-bundles jobs; untether test-cross-platform and publish-nightly-main from them. - release_bundles.yml (new): build all src/bundles/* wheels, run a cross-OS install smoke test, then publish to PyPI under their stable names. Tolerates already-published versions so re-runs without a version bump are no-ops. release.yml still owns bundle publishing as part of main releases. Manual follow-up (PyPI admin): delete the lfx-arxiv-nightly and lfx-duckduckgo-nightly projects from PyPI. * ci(release_bundles): build lfx wheel locally for smoke test (#13207) The release_bundles.yml smoke test installs bundle wheels into a fresh venv, which makes uv resolve their `lfx>=X.Y,<Z` pin against PyPI. When bundles are released ahead of a matching lfx (typical case — bundles ship more often than lfx bumps land on PyPI), the resolve fails: Because only lfx<=0.4.3 is available and lfx-arxiv==0.1.0 depends on lfx>=0.5.0,<0.6.0, we can conclude that lfx-arxiv==0.1.0 cannot be used. Build the lfx wheel from the current ref in the build-bundles job and install it alongside the bundle wheels in the smoke test. The lfx wheel ships as a separate `dist-lfx-smoketest` artifact and is NOT included in the publish-bundles step — only the `dist-bundles` artifact gets pushed to PyPI. * ci(nightly): re-pin bundle lfx deps to lfx-nightly during nightly build (#13208) The nightly pipeline renames the workspace `lfx` package to `lfx-nightly` in `src/lfx/pyproject.toml` and the root workspace dep, but leaves each `src/bundles/*/pyproject.toml`'s `"lfx>=X.Y,<Z"` pin unchanged. With the workspace `lfx` gone and PyPI still on lfx 0.4.3, the create-nightly-tag job's `uv lock` fails: × No solution found when resolving dependencies for split [...] ╰─▶ Because only lfx<=0.4.3 is available and lfx-arxiv depends on lfx>=0.5.0,<0.6.0, we can conclude that lfx-arxiv's requirements are unsatisfiable. `update_lfx_version.py` now also rewrites the `lfx` (or already-rewritten `lfx-nightly`) specifier in every `src/bundles/*/pyproject.toml` to `lfx-nightly==<dev version>`, mirroring how `update_lf_base_dependency` re-pins the lfx dep inside `langflow-base`. The lookahead in the regex prevents matching sibling packages like `lfx-arxiv` / `lfx-duckduckgo`. No-op when no bundle pyprojects exist (e.g. on main today), so this is safe to merge ahead of the bundle extraction landing on main. Restored the guarded `git add src/bundles/*/pyproject.toml` step so the modified bundle pyprojects ride the same commit/tag as the rest of the nightly version bumps. Cherry-pick to release-1.10.0 to unblock nightlies cut from that branch. |
|||
| feec57e16f |
docs: merge knowledge base components (#13150)
* docs-merge-kb-components * Apply suggestions from code review Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com> * use-tabs-for-params-table --------- Co-authored-by: April I. Murphy <36110273+aimurphy@users.noreply.github.com> |
|||
| b9e6fff677 |
feat: Redesign deployment environment cards (#13109)
* feat(deployments): redesign env cards * refactor(deployments): split env ui * test(e2e): fix provider delete selectors |
|||
| aefd8acb37 |
docs: directory component is legacy (#13186)
* remove-and-redirect-component-page * update-tutorials-that-used-directory-component * peer-review * update-tutorial-screenshot |
|||
| 487a2402ba |
fix: Model handling for tool calling in agents and update IBM models (#13201)
fix: Model handling for tool calling in agents and update IBM models (#13191) * Model handling for tool calling in agents and update IBM models * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| ae45d3ad8e |
fix: File upload toast showing only after successful upload (#12984)
* File upload toast showing only ater successful upload * [autofix.ci] apply automated fixes --------- Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| 2be85b167e |
feat: promote OpenRouter to a first-class unified model provider (#13185)
* feat: promote OpenRouter to a first-class unified model provider Adds OpenRouter to the unified Model Providers system so users can configure it once in Settings rather than per-flow. Reuses ChatOpenAI with base_url=https://openrouter.ai/api/v1, supports a live model catalog (fetched from /api/v1/models with the user's bearer key), and forwards the optional HTTP-Referer / X-Title attribution headers when the user provides a site URL or app name. Validation is a cheap GET against /api/v1/models that raises ValueError("Invalid OpenRouter API key") on a 401. The existing flow-level OpenRouterComponent is left untouched for backward compatibility. Implements the opinionated direction proposed in #12839 (single curated provider instead of a generic OpenAI-compatible field). * Update Structured Data Analysis Agent.json * [autofix.ci] apply automated fixes * fix(openrouter): address review feedback - Per-model tool_calling derived from OpenRouter's `supported_parameters`, not blanket-True. The Agent component calls `get_language_model_options(tool_calling=True)` so this fixes a real bug where non-tool-calling models (e.g. perceptron/perceptron-mk1) would have appeared in agent flows and failed at runtime. - Default-model picks intersect with the curated seed list instead of taking the first 5 alphabetical ids (which were ai21/jamba-large-1.7, aion-labs/aion-1.0, etc.). Falls back to first MIN_DEFAULT_MODELS when the seed list shares nothing with the live catalog (stale-seed safety). - Broadened the live-fetch except to include ValueError/TypeError (covers malformed JSON, non-list `data`, non-dict entries) and added defensive isinstance checks so a 200 with a bad payload degrades to `[]` instead of crashing on AttributeError. - Bumped the live-fetch failure log from `debug` to `warning` with URL + status code, so an empty live catalog leaves a server-side breadcrumb. - Wrapped the credentials probe in try/except → ValueError so a 5xx / timeout during `POST /validate-provider` surfaces as a user-facing 400 via api/v1/variable.py (which only catches ValueError), not an unhandled 500. - Tightened `os.environ.get(var["variable_key"])` (was `var.get("variable_key", "")` → `os.environ.get("")` masking misconfig). - Documented `is_header` / `header_name` and the settings-only (no-`component_metadata`) pattern in the MODEL_PROVIDER_METADATA header comment. - Updated seed slugs against the current OpenRouter catalog (anthropic/claude-3.7-sonnet was missing); curated list is now Claude Opus/Sonnet/Haiku 4.x + GPT-4o pair + Gemini 2.5 Pro + Llama 3.3. - Added OPENROUTER_API_KEY / OPENROUTER_SITE_URL / OPENROUTER_APP_NAME to VARIABLES_TO_GET_FROM_ENVIRONMENT so users with env vars set see the provider as configured in Settings → Model Providers (parity with the other providers, and makes the "Falls back to env" copy honest). - Tests: pinned `MIN_DEFAULT_MODELS` instead of magic 3; added cases for per-model tool_calling derivation, seed-intersection defaults, stale-seed fallback, httpx.HTTPStatusError, malformed payload, transient network → ValueError validation, env-var attribution headers, and env auto-import list. Replaced `patch.dict("sys.modules", ...)` with `patch.object(requests, "get", ...)` so the test stays correct if the lazy import is hoisted. * Update Structured Data Analysis Agent.json * feat(model-providers): search bar, models.dev metadata override, capability tags Three follow-ups to the OpenRouter PR, requested by @ogabrielluiz in Slack: 1. **Search bars in the model providers modal.** ProviderList accepts a `query` prop and filters case-insensitively on the provider name; an `<Input icon="Search">` in ModelProvidersContent drives it. Inside the per-provider panel, ModelSelection grows its own search input that filters both LLM and embedding lists and resets when the selected provider changes (so OpenRouter → Anthropic doesn't carry stale text). Empty-state copy in both lanes; new i18n keys. 2. **models.dev metadata override (source of truth for covered providers).** New `lfx.base.models.models_dev_catalog` module: * `fetch_models_dev_snapshot()` — httpx GET against https://models.dev/api.json, returns None on transport / status / payload errors with a warning log. * `load_models_dev_snapshot()` / `save_models_dev_snapshot()` — disk cache at `<user_cache_dir>/langflow/models_dev/snapshot.json` with atomic temp-file write. * `apply_models_dev_overrides()` — translates each covered provider's models.dev entries into `ModelMetadata` (tool_call → tool_calling, reasoning passthrough, modalities.input has "image" → vision, limit.context → context_window, cost.input/output → cost_per_million_in/out). Providers not covered (IBM WatsonX, Azure OpenAI, Groq) pass through unchanged. Live-fetched providers (Ollama, IBM WatsonX, OpenRouter) keep their behavior because `replace_with_live_models` runs downstream at read time. `provider_queries.get_models_detailed()` now consults the in-memory snapshot via `get_active_snapshot()` and is `cache_clear()`-friendly so snapshot installs are picked up on the next call. The non-blocking refresh task in `get_lifespan` hydrates from disk first, then attempts a live fetch on a 24h interval; startup never blocks on models.dev. 3. **Capability tags next to the model name.** ModelRow renders small Badge pills (tool / reasoning / vision / embedding / search / preview) driven by metadata flags, in a stable left-to-right order, with the existing Deprecated badge kept at the end. The embedding tag only surfaces in the `all` view because the dedicated embeddings section header already conveys the same info. Tests: - 14 new backend cases in `test_models_dev_catalog.py` (fetch / load / save / override / cache invalidation paths). - Search + capability-tag cases added to ProviderList and ModelSelection Jest suites (44 total, all green). - lfx tests/unit/base/models + tests/unit/inputs: 114 pass, no regressions. - ruff check clean on all touched files. Out of scope: manual UI refresh button, surfacing pricing in the UI, auto-adding providers from models.dev without a LangChain client. * fix(openrouter): derive reasoning from supported_parameters OpenRouter exposes "reasoning" in each model's supported_parameters array exactly like it does "tools" (verified against /api/v1/models — Claude Opus 4.x, o1/o3, DeepSeek R1 all carry it). The live fetcher was only deriving tool_calling; reasoning was left at the create_model_metadata default of False, so the new reasoning badge never lit up for OpenRouter models even though the rest of the wiring was in place. Now derives both flags from the same array. Test renamed to …_derives_tool_calling_and_reasoning_per_model and pins all four combinations (tools+reasoning, tools only, reasoning only, neither, plus the missing-supported_parameters case). * feat(model-providers): sort by recency, hide deprecated by default OpenAI's list looked jumbled because the catalog rendered in raw constants-file order — reasoning models interleaved with chat models, deprecated and preview rows scattered throughout. Three changes: 1. **Thread `created` (Unix epoch) through ModelMetadata.** New optional field; populated by: - OpenRouter live fetch (existing `created` field on every model; defensive int-coercion handles bad payload values). - models.dev override (parses `release_date` YYYY-MM-DD → UTC midnight epoch; invalid / missing → 0). Static `*_constants.py` lists leave it at 0 → "unknown" tier. 2. **Sort each provider's group in `get_unified_models_detailed`.** Stable sort by `(is_deprecated, -created_epoch)`: - Non-deprecated dated rows: newest first. - Untimed rows (current static fallback): stable sort keeps their hand-curated constants-file order intact within the same tier. - Deprecated rows: bottom. The "first 5 = default" stamping now runs AFTER the sort so the highlighted defaults reflect the new ordering. For OpenAI's static list this changes nothing today (constants are already newest-first); once models.dev hydrates it picks up genuinely-recent dates from `release_date`. 3. **Collapse deprecated into a `<details>` disclosure.** Each section renders active rows normally, then a `Show N deprecated model(s)` summary that's closed by default. Resets on provider change. The rows stay in the DOM so existing flows referencing deprecated models still resolve; they're just out of the way. `not_supported` is already filtered at the API layer (the modal doesn't pass `includeUnsupported`), so those rows remain hidden as before. Tests: - Backend: `created` propagation from `release_date` (+ malformed/empty cases), OpenRouter `created` passthrough (+ invalid-value degradation), and a full sort-order assertion covering [newest, mid, undated-a, undated-b, deprecated] ordering. - Frontend: deprecated disclosure default-closed, singular pluralization, no-disclosure-when-nothing-deprecated, reset on provider change via real user click → onToggle (the right pattern; the prior draft of this test mutated `details.open` directly and missed React's prop reconciliation). - 117 lfx tests + 48 frontend tests pass; ruff clean. * [autofix.ci] apply automated fixes * fix(models.dev override): preserve static deprecated flag + auto-deprecate dated snapshots Two issues surfaced in the OpenAI/Anthropic screenshots: 1. **gpt-3.5-turbo wasn't in the deprecated disclosure** even though openai_constants.py has it flagged ``deprecated=True``. models.dev exposes no deprecated/lifecycle field (verified: only ``release_date`` and ``last_updated``), so the override threw away our static curation. ``apply_models_dev_overrides`` now builds a provider→{deprecated model names} lookup from the static lists and forwards the flag through ``_translate_model_entry`` for any name that matched a static deprecated row. ``gpt-4.5-preview`` and ``gpt-4-turbo-preview`` ride the same path. 2. **Anthropic dated-snapshot ids** (``claude-opus-4-5-20251101``, ``claude-haiku-4-5-20251001``, etc.) crowded the picker alongside their moving aliases. These are pinned-date snapshots of the alias — useful for production pins, noisy as defaults. Same pattern exists for OpenAI (``gpt-4o-2024-05-13``). ``_translate_model_entry`` now matches ``-YYYYMMDD$`` (Anthropic) and ``-YYYY-MM-DD$`` (OpenAI) and auto-flags those rows deprecated so they collapse into the disclosure tier rather than dominating the list. The 4-digit-only ``-0314``-style suffixes (``gpt-4-0314``, ``gpt-4-0613``) are deliberately *not* matched — those are pre-dated-naming snapshots that need static curation if we want to mark them. Tests: - test_apply_overrides_marks_dated_snapshot_ids_deprecated pins: * alias rows stay active (claude-opus-4-5, gpt-4o) * Anthropic 8-digit and OpenAI hyphenated suffixes flip to deprecated * 4-digit-only suffixes don't false-positive - test_apply_overrides_preserves_static_deprecated_flag pins: * gpt-3.5-turbo / gpt-4.5-preview keep their static deprecation * a new model not in the static list (gpt-6) stays active * fix(models.dev override): classify embeddings + age-based auto-deprecation Two follow-ups from screenshot review on the OpenAI provider list. **Bug 1 — embedding models showed in the Language Models section.** models.dev mixes embeddings into a provider's ``models`` dict (e.g. ``text-embedding-3-large`` lives in the OpenAI block). The translator was defaulting every entry to ``model_type="llm"``, so embeddings polluted the LLM lane in the modal. The clean discriminator is the ``family`` field (``"text-embedding"`` for OpenAI's current embeddings, verified for ada-002 / 3-small / 3-large); a name-substring check ("embedding") covers providers that fill ``family`` inconsistently. **Bug 2 — old OpenAI models (gpt-4, gpt-4-turbo, text-embedding-ada-002) stayed active even though they're functionally legacy.** models.dev has no deprecation field, and OpenAI doesn't formally deprecate gpt-4 / gpt-4-turbo in their catalog, but at ~924 days from release they're clearly out of rotation. New ``_is_aged_out`` helper auto-deprecates models older than ~30 months (900 days), preferring ``release_date`` over ``last_updated`` (the latter tracks catalog-curator edits like typo fixes, not new model versions — gpt-4 has last_updated=2024-04 which would falsely keep it active). Threshold tuned conservatively against today's catalog: - 1250d ada-002 → deprecated - 924d gpt-4, gpt-4-turbo → deprecated - 844d text-embedding-3-large/small → ACTIVE (still OpenAI's current embeddings; threshold deliberately above this point) - 735d gpt-4o → active - 261d Claude 4.x → active ``now`` is injected through ``_translate_model_entry`` and ``apply_models_dev_overrides`` for testability so the unit tests don't depend on wall-clock time. Tests: - test_apply_overrides_marks_embedding_family_as_embeddings_model_type pins ``family="text-embedding"`` → embeddings, generic LLMs stay llm, and a name-substring case (``voyage-3-embedding``) still classifies correctly when family is missing. - test_apply_overrides_auto_deprecates_stale_models pins both sides of the 900-day line and the no-date case (insufficient signal → stays active). * fix(models.dev override): drop duplicate static groups + Google dated-preview + gemini-1.5 Three issues from screenshot review. **1. Duplicate embedding rows in the OpenAI section.** The override iterates ``static_lists`` and replaces the first OpenAI group (``OPENAI_MODELS_DETAILED``, LLMs) with the combined override (which holds both LLMs and embeddings from the single models.dev block). The second OpenAI group (``OPENAI_EMBEDDING_MODELS_DETAILED``) then fell through to the else branch and got appended unchanged, so every embedding row rendered twice. Fix: when a provider's override has already been applied, drop any subsequent static groups for the same provider — they would only re-introduce duplicates. **2. Google's dated-preview suffix wasn't auto-deprecated.** Each vendor uses a different shape for snapshot ids: | Vendor | Shape | Example | | --------- | -------------------- | ----------------------------------------- | | Anthropic | ``-YYYYMMDD`` | ``claude-opus-4-5-20251101`` | | OpenAI | ``-YYYY-MM-DD`` | ``gpt-4o-2024-05-13`` | | Google | ``-preview-MM-DD`` | ``gemini-2.5-pro-preview-05-06`` | | Google | ``-preview-MM-YYYY`` | ``gemini-2.5-flash-preview-09-2025`` | ``_DATED_SNAPSHOT_RE`` extended to match the Google variants. The non-dated ``-preview-tts``-style suffix stays active (tts is a real variant, not a snapshot). **3. gemini-1.5-* stayed active despite being long out of rotation.** At ~600-820 days these names sit under the 900-day age threshold, so the heuristic alone wouldn't catch them. Added explicit ``deprecated=True`` entries for ``gemini-1.5-pro`` / ``gemini-1.5-flash`` / ``gemini-1.5-flash-8b`` to ``google_generative_ai_constants``; the override's static-deprecation preservation flows them through. Tests: - test_apply_overrides_drops_subsequent_static_groups_for_overridden_provider pins the no-duplicates invariant. - test_apply_overrides_marks_dated_snapshot_ids_deprecated grows to cover the Google preview-MM-DD / preview-MM-YYYY patterns and the non-dated -preview-tts negative case. - test_apply_overrides_preserves_gemini_1_5_static_deprecation pins that the static curation actually reaches the override output (and includes a sanity assert on the static list itself, so we don't drift the test off of the constants file by accident). 57 lfx model tests pass; ruff clean. * [autofix.ci] apply automated fixes * fix(ollama): stop 10s frontend poll + parallelize /api/show + cache Closes #13137. Two layered fixes for the runaway Ollama model fetch: **Frontend — drop the 10-second refetchInterval.** The previous code polled ``/api/v1/models`` every 10s as long as the Ollama provider card was selected. Each backend call fanned out to ``GET /api/tags`` + ``POST /api/show`` per model serially, so with many local models the request overran the 10s interval and requests accumulated. The catalog already refreshes on credential save / disconnect via ``invalidateProviderQueries`` and the backend cache (below) keeps a on-demand window-focus refresh cheap; the timer was unnecessary. **Backend — parallelize ``/api/show`` and cache.** * ``get_ollama_models`` now fans out per-model capability probes via ``asyncio.gather``, so total latency is bounded by the slowest single request rather than ``N * avg``. Per-model failures are absorbed at debug-level — one broken model no longer poisons the whole catalog response. * In-process TTL cache keyed by ``(base_url, capability)`` with a 30s window in front of the fan-out. Overlapping ``/api/v1/models`` requests (which happen on every modal interaction, agent picker, embed picker, etc.) now share one upstream round-trip. Test-only ``_ollama_cache_clear`` helper for isolation. Tests: - test_get_ollama_models_returns_only_matching_capability — capability filter still works. - test_get_ollama_models_runs_show_requests_in_parallel — pins parallelism via an asyncio.Event that would deadlock under serial. - test_get_ollama_models_tolerates_single_show_failure — one bad model doesn't drop the whole list. - test_get_ollama_models_caches_result_for_ttl_window — second call within the TTL hits the cache (zero upstream calls). - test_get_ollama_models_cache_keyed_by_base_url_and_capability — different base URLs and capability filters stay isolated. - test_get_ollama_models_refetches_after_ttl_expires — TTL expiry triggers a fresh fetch. * Update Structured Data Analysis Agent.json * [autofix.ci] apply automated fixes * feat(model-input): declarative filters propagate to picker + sticky-default Agent's Language Model dropdown was still showing tool-incompatible models like ``gemini-3.1-flash-image-preview`` (``tool_call=false`` per models.dev). Root cause: the tool-calling constraint lived as a hardcoded lambda at the Agent component's call site, but ``update_model_options_in_build_config``'s sticky-default re-injection path didn't know about it — so a previously-saved selection that no longer passed the filter got re-injected with ``not_enabled_locally=True`` and kept appearing in the picker. Make the filter declarative so the helper, the cache key, and the sticky-default path all read it from the same source. * ``ModelInputMixin`` gains a ``filters: dict[str, Any] | None`` field. Any keys/values forward to ``get_unified_models_detailed`` and are matched against each model's metadata. Example: the Agent declares ``ModelInput(name="model", filters={"tool_calling": True})`` instead of passing a lambda at the call site. * ``get_language_model_options`` accepts ``filters: dict | None``; the legacy ``tool_calling`` kwarg is preserved and merged into the filter dict for backward compatibility. * ``handle_model_input_update`` reads ``filters`` from the build_config and, when no explicit ``get_options_func`` is supplied, builds one that applies them. The options-cache key prefix is namespaced by sorted filter pairs so different constraint sets don't share cached options. * The sticky-default block in ``update_model_options_in_build_config`` validates the saved selection against the same filters dict via ``_saved_model_passes_filters``. When the saved row doesn't satisfy every constraint, the row is dropped and the value cleared so the downstream auto-default picks a compatible model. Conservative fallback: an unknown model (not in the catalog) still passes the filter so today's "inject and let the user configure" UX is unchanged for genuinely-unknown selections. * Agent component: drop the inline lambda in ``update_build_config``; the filter now lives on the ModelInput declaration. * Replaces the ``cache_key_prefix`` sniff for tool-calling intent — prefix is now derived from filters, not pattern-matched. Tests cover the helpers, both sticky-default branches (drop vs inject), and the conservative fallback for unknown saved models. * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * fix(google): tool_calling=False on image-output Gemini models Even after the declarative filters={"tool_calling": True} shipped on the Agent component's ModelInput, the picker still showed gemini-3.1-flash-image-preview and friends. Root cause: the static google_generative_ai_constants.py catalog defaulted every Gemini model to tool_calling=True. Image-generation models can't actually run with tools (verified in models.dev — these rows have tool_call: false and modalities.output includes image), so the static-only fallback (before the models.dev refresh hydrates) was wrong upstream of the filter and leaked them through. Four image-output models flipped to tool_calling=False in the static catalog: * gemini-2.5-flash-image * gemini-2.0-flash-preview-image-generation * gemini-3-pro-image-preview * gemini-3.1-flash-image-preview Models.dev's correct flag still wins via the override path once the snapshot hydrates; this fix matters most for cold starts and air-gapped deployments where only the bundled static lists are in play. Tests (test_static_catalog_tool_calling.py): - Parametrized over the four known image models; each must declare tool_calling=False in the static catalog (and remain listed — we need the static curation so the override preserves the flag by name match). - Heuristic regression guard: any Google model with "image" in its name that defaults to tool_calling=True will fail this test. Future image variants are caught before they reach the picker; legitimate exceptions opt in via an allowlist literal in the test. * [autofix.ci] apply automated fixes * fix(model-input): source filters from class declaration, not round-tripped build_config The Agent picker was STILL showing gemini-3.1-flash-image-preview even after the static catalog flag flip and the declarative filters work. Reproduced and traced to a saved-flow round-trip: flows persisted before the filters field shipped don't carry it in their stored template. The frontend POSTs that stale template back, my helper read build_config["model"]["filters"] (None), and the filter path short-circuited to the unfiltered branch. Fix: source filters from the ModelInput's class-level declaration (canonical, always current with the server build), falling back to build_config only when the component happens to expose no inputs attribute. The build_config is also patched in place when the declaration overrides it, so the next round-trip carries the active filter back to the frontend. * New helper ``_filters_from_component_inputs`` walks the component's inputs list and returns the matching ModelInput's filters dict (with None values dropped, same hygiene as the build_config path). * New ``_resolve_filters`` orchestrates: class declaration > build_config, with an in-place write back to build_config when the class wins. * Both call sites in ``handle_model_input_update`` and ``update_model_options_in_build_config`` now call ``_resolve_filters``; the old direct call to ``_filters_from_build_config`` is gone from the hot paths (kept exported because it's still useful as a defensive fallback when no component is available). Tests (4 new in test_build_config_sticky_default.py): - _filters_from_component_inputs reads class declaration / returns empty when no matching input. - _resolve_filters prefers class over build_config and patches build_config in place. - _resolve_filters falls back to build_config when component has no inputs attribute (defensive fallback for non-standard callers). * fix(frontend): honor ModelInput.filters in dropdown augment loop The Agent picker kept showing tool-incompatible models like gemini-3.1-flash-image-preview even after every backend filter and static-catalog fix landed. Root cause was on the frontend, in the ModelInputComponent's groupedOptions memo: - The first loop iterates over the backend-supplied options (which are filtered). - The augment loop then iterates over every enabled model in useGetModelProviders × useGetEnabledModels — both of which are filter-unaware — and pushes the union into the dropdown. So even though the backend correctly excluded gemini-3.1-flash-image-preview from options, the augment loop happily re-added it because providersData listed it and enabledModelsData flagged it as enabled. Fix: read filters off nodeClass.template.model.filters (the same declaration that drives the backend filter) and apply the same metadata match to every option that flows through groupedOptions. Two checkpoints: 1. Defensive pass in the existing-options loop. The backend should have filtered already, but stale saved flows could surface a build_config that hasn't been corrected yet — same self-healing story as the backend _resolve_filters helper. 2. New pass in the augment loop, after the model_type check. Every enabled-model candidate must satisfy the filter or it's skipped. Conservative behavior matches the backend: when a filter key (e.g. tool_calling) is declared but the candidate's metadata doesn't carry that key at all, the candidate fails the check — better drop an undeclared row than surface one that crashes at run time. Tests (4 new): hides tool-incompatible augmented models, drops saved options that fail the filter, conservative drop on missing metadata key (with a known-good sibling so the assertion isn't paper-thin), no-op when no filters are declared. Suite-local beforeEach restores the default hook mocks since jest.clearAllMocks only resets call history, not implementations. * Fix tests * Update .secrets.baseline * Fix starter projects * Update .secrets.baseline * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * Update component_index.json * Update Structured Data Analysis Agent.json * [autofix.ci] apply automated fixes * fix frontend tests * fix remaining CI errors --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| c901602bc2 |
fix(ci): rename bundles to -nightly during nightly build (#13193)
fix: rename bundles for nightly build |
|||
| 23f2edf327 |
chore: Clean up the startup warnings for Python 3.14 (#13156)
* fix: backport policies ToolGuard lazy imports (#13144) fix: backport policies toolguard lazy imports * chore: Clean up the startup warnings for Python 3.14 * Update base.py * Update component_index.json * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * Update component_index.json * [autofix.ci] apply automated fixes * Update Structured Data Analysis Agent.json * [autofix.ci] apply automated fixes * Update component_index.json * [autofix.ci] apply automated fixes * Update component_index.json * Update starter projects * Update .secrets.baseline * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| e08080507b |
feat: Add langflow-stepflow package (#12015)
* feat: Add langflow-stepflow package Introduces a new workspace package `src/langflow-stepflow/` that ports the Stepflow integration from the Stepflow repository into the Langflow codebase. The package has two submodules: - `translation/`: translates Langflow flow JSON to Stepflow flow definitions (ported from integrations/langflow/converter/) - `worker/`: a Stepflow worker that executes individual Langflow components via lfx (ported from integrations/langflow/executor/) Entry point: `python -m langflow_stepflow.worker` starts the HTTP worker server for use by a Stepflow orchestrator. No changes to existing Langflow code in this commit. * fix: Address review feedback on langflow-stepflow package - Widen Python version to >=3.10,<3.14 to match langflow-base - Fix import sorting (ruff I001) and unused variable (ruff F841) - Replace sk- prefixed test strings to avoid Gitleaks false positives - Remove placeholder step reference resolution in component_tool (orchestrator resolves these before reaching the worker) - Let exceptions propagate from component_tool_executor instead of returning error dicts that look like successful results - Skip secret/password field defaults in tool input schemas - Use LRU-style bounded cache (128) for compiled components - Use asyncio.to_thread for sync component methods to avoid blocking - Fix mutable NamedTuple default (list→tuple) in PlaceholderGraph - Warn and skip deps with missing field mappings instead of silently falling back to "input" (which overwrites on multiple deps) - Tighten _is_data_list to check __class_name__ == "Data" specifically - Add comment explaining intentional teardown-before-init sequence - Add integration test for example flows * chore: Upgrade to Stepflow SDK 0.12.0 and adapt to lfx 0.3+ renames - Bump stepflow-py and stepflow-orchestrator from >=0.10.0 to >=0.12.0 - Remove Python 3.11 environment markers (SDK now supports 3.10+) - Replace server.run() with gRPC pull transport (run_grpc_worker) matching the upstream stepflow-langflow integration pattern - Update Flow serialization from Pydantic model_dump to msgspec.to_builtins - Remove Pydantic ValueExpr/actual_instance unwrapping (now plain dicts) - Add _langflow_type_name() to map lfx 0.3+ class renames (JSON→Data, Table→DataFrame) back to canonical Langflow type names - Fix DataFrame isinstance checks for lfx Table subclass - Add missing README.md, tests/__init__.py, helpers package - Add skip conditions for missing fixture files * change to lfx schema * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes * chore: Align langflow-stepflow ruff config with repo and format - Set line-length = 120 to match root pyproject.toml (was 88, causing conflicts between pre-commit format hook and check hook) - Run ruff format across all source and test files - Add pragma: allowlist secret on test fixtures with fake API keys * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * chore: Rebuild component index * [autofix.ci] apply automated fixes * fix(langflow-stepflow): allow Python 3.14 in requires-python Lift requires-python from <3.14 to <3.15 to match the rest of the workspace and the lockfile. Without this, uv sync on Python 3.14 fails the Unit/LFX/Integration test jobs and the Docker build. * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: ogabrielluiz <gabriel@langflow.org> Co-authored-by: Jordan Frazier <jordan.frazier@datastax.com> |
|||
| d1da768192 |
feat(knowledge): merge ingestion + retrieval into mode-driven Knowledge component (#13120)
* feat(knowledge): merge ingestion + retrieval into mode-driven Knowledge component
Merge KnowledgeIngestionComponent and KnowledgeBaseComponent into a single
KnowledgeComponent driven by a TabInput mode selector ("Ingest" / "Retrieve"),
mirroring the PoliciesComponent pattern.
- New: src/lfx/src/lfx/components/files_and_knowledge/knowledge.py holds the
full merged component. update_build_config and update_outputs hide the
inputs and the output that do not apply to the current mode.
- Legacy KnowledgeIngestionComponent / KnowledgeBaseComponent become thin
subclasses pinned to their respective modes with class-level inputs and
outputs aligned to the legacy shape, so saved flows continue to load.
- Output names ("dataframe_output", "retrieve_data") preserved for back-compat.
- New tests cover mode-switch input/output visibility and subclass pinning;
existing test_ingestion / test_retrieval suites rerouted to patch the new
knowledge module path and continue to pass.
* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]
* chore(knowledge): drop emoji from mode labels, mark legacy subclasses
- Mode tab labels: "📥 Ingest" / "🔍 Retrieve" → "Ingest" / "Retrieve" so the
selector blends with the rest of Langflow's TabInput palette (see MemoryComponent
for the same convention).
- Set `legacy = True` on KnowledgeIngestionComponent and KnowledgeBaseComponent
so the back-compat shims stay loadable by saved flows but no longer clutter
the new-component palette — users should reach for the merged Knowledge
component instead.
* [autofix.ci] apply automated fixes
* fix(knowledge): declare both outputs at class level so retrieve mode dispatches correctly
Saved flows in retrieve mode were hitting
``'NoneType' object has no attribute 'to_dataframe'`` because the merged
``KnowledgeComponent`` only declared ``dataframe_output`` (build_kb_info)
at the class level. The runtime resolves outputs from the class-level
``outputs`` list at __init__ time, so ``retrieve_data`` was never in
``_outputs_map``; the runtime fell back to ``build_kb_info``, which ran
``convert_to_dataframe(self.input_df)`` against a hidden, None-valued
input.
Fix: declare both outputs at the class level (TypeConverterComponent +
MemoryComponent already use this pattern). ``update_outputs`` keeps
filtering the canvas-visible output by mode, but the runtime can now
dispatch to either method based on which one the saved flow has wired up.
Also wire ``update_frontend_node`` to re-run the mode-driven output
filter on canvas load so saved flows render the correct output even
before the user clicks the tab.
* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]
* fix(knowledge): dispatch by mode in output methods to survive stale edges and legacy labels
Two failure modes were still triggering the
``'NoneType' object has no attribute 'to_dataframe'`` crash even with both
outputs declared at the class level:
1. User drops the Knowledge component (default INGEST), wires the
``dataframe_output`` edge, then switches to Retrieve. The saved edge
still points at ``build_kb_info``; the runtime calls it against a
None ``input_df`` and crashes inside ``convert_to_dataframe``.
2. Flows saved with the original emoji mode labels ("📥 Ingest" /
"🔍 Retrieve") carry those strings on disk. The post-emoji-removal
equality checks (``mode == MODE_RETRIEVE``) failed, so even though
the user was conceptually in retrieve mode, the component fell
through the ingest path.
Both methods now check the mode at entry and delegate to the other one
when the wired output doesn't match. Mode matching is lenient
(substring-based) so the legacy emoji labels still resolve.
``update_build_config`` and ``update_outputs`` also normalize legacy
labels onto the current canonical values so visibility toggles correctly
for flows pulled from old saves.
* [autofix.ci] apply automated fixes
* feat(knowledge): point legacy shims at the merged component and migrate starters
- Add ``replacement = ["files_and_knowledge.Knowledge"]`` to both
``KnowledgeIngestionComponent`` and ``KnowledgeBaseComponent`` so the
canvas legacy-component banner offers the merged component as the
one-click upgrade target.
- Rewrite the ``KnowledgeBase`` nodes in the two starter projects that
shipped them (``Knowledge Retrieval`` and ``Vector Store RAG``) to use
the new ``Knowledge`` component with mode pre-set to ``Retrieve``. All
edges are re-pointed at the new node id, the embedded module path is
updated to ``lfx.components.files_and_knowledge.knowledge.KnowledgeComponent``,
and the mode value is normalised away from the old emoji label.
* [autofix.ci] apply automated fixes
* Update knowledge.py
* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* Update component_index.json
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* Update src/lfx/src/lfx/components/files_and_knowledge/knowledge.py
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* fix: Restore knowledge creation functionality
* Template update
* Update component_index.json
* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* fix: Linting errors
* Update knowledge.py
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* [autofix.ci] apply automated fixes
* fix: Address @rafaelgiln comments
* Update test_knowledge.py
* Update component_index.json
* Template updates
* Update .secrets.baseline
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* Update Structured Data Analysis Agent.json
* Update component_index.json
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes
* Fix edge connection with parser
* Update Structured Data Analysis Agent.json
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
|
|||
| 7993ed3b70 |
fix: avoid pickling vertex component instances (#13172)
* Avoid pickling vertex component runtime state * Add docstrings for vertex serialization regression helper --------- Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@logspace.ai> |
|||
| c78e10e85a |
docs: add directory depth limitation warning to custom components (#13104)
* docs: add directory depth limitation warning to custom components Add a warning admonition to the custom components documentation explaining the MAX_DEPTH=2 limitation for component discovery. The warning clarifies: - Components must be at most 2 levels deep (category/component.py) - Each first-level directory becomes an independent category in the UI - How to modify MAX_DEPTH for advanced use cases This addresses confusion around subdirectory support mentioned in issue #13102. * docs: simplify directory depth warning per review feedback Co-Authored-By: Antonio <antonio@oriontech.me> --------- Co-authored-by: Antonio <antonio@oriontech.me> |
|||
| cc009c9133 |
feat(lfx): Bundle Separation and LFX Extension Framework (#13043)
* feat(lfx): installed-package + seed-directory discovery for production install (LE-1022) Adds the read-only production install path for Modes A, B, and C of the Bundle Separation iteration. Manifest-shipping pip-installed distributions and seed-directory subdirectories are discovered at server startup and registered as Extensions at @official. * discovery.py: walks importlib.metadata.distributions() + the $LANGFLOW_SEED_DIR / /opt/langflow/bundles seed root; produces DiscoveredExtension records and typed errors for malformed manifests / configured-but-missing seed dirs. * registry.py: ExtensionRegistry service with the immutability invariant for installed and seed entries. Mutation verbs (uninstall, disable, enable, install, update_entry) all raise ExtensionImmutableError carrying the typed installed-extension-immutable / seed-directory-immutable code so the invariant is testable today; the CLI uninstall surface ships in B4. * lfx extension list: read-only inspector with text and JSON output for operators inspecting Mode B/C images. * Errors: four new typed codes (installed-extension-immutable, seed-directory-immutable, seed-directory-not-found, duplicate-extension-id) plus snapshot coverage in tests/unit/extension/test_errors.py. * Tests: 165 extension tests pass, including the LE-1022 acceptance cases -- three pip-installed wheels visible at @official, three seed bundles visible at @official, and the parametrized service-layer immutability check across every mutation verb. * Docs: docs/Deployment/deployment-extensions-production.mdx covers the Dockerfile template, k8s deployment notes, the bundle packaging convention (extension.json shipped via package-data), and troubleshooting for the typed error codes. * feat(lfx): add single-Bundle loader and LANGFLOW_COMPONENTS_PATH discovery (#12967) * feat(lfx): add extension manifest schema, validate CLI, and error formatter (LE-1014) Foundation for the Bundle Separation iteration. Defines what a valid extension.json looks like (Pydantic models + Draft 2020-12 JSON Schema), ships the offline `lfx extension validate` command, and ships the single `format_extension_error` function that every other extension-system module will use to render structured errors. What's in lfx.extension: - `ExtensionManifest` / `BundleRef` / `LangflowCompat` Pydantic models with `extra="forbid"` so unknown fields fail loudly. Deferred fields (`services`, `routes`, `hooks`, `starter_projects`, `userConfig`) are reserved as None-only so non-null values produce a dedicated `field-deferred-in-this-milestone` error instead of a generic schema wall. `bundles` accepts a list but rejects length > 1 with `multi-bundle-deferred-in-this-milestone` (validator-enforced; the loader re-checks at install time in LE-1015). - `schema.build_schema()` + `build_schema_json()` produce the publishable artifact at schemas.langflow.org/extension/v1.json. - `ExtensionError` typed envelope and `format_extension_error` -- one branch per discriminant. Codes are registered in `ERROR_CODES`; an `ExtensionError` constructed with an unknown code raises at construction time, preventing producers from shipping without a matching renderer. - `validate_extension` runs four passes: manifest discovery + schema, path-safety (no `..`, no absolute paths, no symlink escape), AST inspection of every `.py` (syntax, Component subclass present, build() declared, top-level `import *`, top-level I/O primitives), and an opt-in `--execute-imports` that runs each module in a subprocess with a temporary HOME / TMPDIR / LANGFLOW_CONFIG_DIR and LANGFLOW_*/LFX_* env vars stripped. - `lfx extension validate` and `lfx extension schema` typer subcommands. Acceptance-criteria coverage in tests/unit/extension/: - Round-trips every v0 manifest field; deferred fields rejected; multi-bundle rejected with the dedicated discriminant. - JSON Schema validates the v0 example and rejects 12 malformed manifests with distinct error paths (>= 10 required by the ticket). - Median default-validate runtime < 100ms on the basic template. - Crafted side-effect bundle: default validate does NOT execute it (canary file is never written); `--execute-imports` DOES execute it and the canary appears, while LANGFLOW_* env vars are NOT inherited by the subprocess. - Snapshot tests for every code in ERROR_CODES; a guard test verifies ERROR_CODES and the snapshot table are in lockstep so future additions cannot ship without a format branch and a snapshot. Wiring: `lfx extension` is a sub-app under the Authoring help panel so future tickets (LE-1016 init/dev, LE-1018 reload) can attach without colliding with the existing `lfx validate` (which validates flow JSON, not extensions). * fix(lfx): fall back to tomli on Python 3.10 in extension manifest loader tomllib is stdlib only on 3.11+, but lfx supports 3.10-3.13. Use the existing tomli runtime dependency as the 3.10 fallback (same API, so the import alias keeps the rest of the module unchanged). Fixes ModuleNotFoundError seen in CI on the 3.10 job. * Update src/lfx/tests/unit/extension/test_schema.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update src/lfx/src/lfx/extension/schema.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update src/lfx/src/lfx/cli/_extension_commands.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * feat(lfx): add single-Bundle loader and LANGFLOW_COMPONENTS_PATH discovery (LE-1015) Introduces lfx.extension.loader: the runtime that turns an Extension on disk into LoadedComponent records keyed by ext:<bundle>:<Class>@<slot>. Two paths in: - load_extension(root): one manifest, one Bundle, registered at @official. Re-checks multi-bundle at runtime (defense-in-depth vs. the schema). - discover_inline_bundles(paths): each subfolder of LANGFLOW_COMPONENTS_PATH is a Bundle at @extra. Walk order is platform-independent (sorted dirs, user-declared path order). First-wins on duplicate names; second emits duplicate-inline-bundle warning that names both paths. Manifest-first precedence helpers (installed_extension_roots, manifest_owning_distributions, filter_plugin_entry_points) let callers of the legacy langflow.plugins entry-point loader skip distributions that ship a manifest, so component entry-points are not double-registered. New typed error codes: module-import-failed, duplicate-component-name, duplicate-distribution, duplicate-inline-bundle, inline-bundle-name-invalid. Each ships with a format branch and a snapshot test. Tests cover the AC: single-bundle happy path, multi-bundle rejection, missing/empty/no-Component bundle, duplicate class names, deterministic walk order, recursive discovery, inline-bundle first-wins + dot-dir skip + bundle.json metadata, manifest-first precedence partition, PEP-503 distribution-name canonicalization. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(lfx): address LE-1015 review feedback - Drop the unproduced duplicate-distribution error code; LE-1022 will add it once startup-time discovery has a place to surface it. Restores the invariant that every code in ERROR_CODES has a producer. - Clarify that intra-bundle relative imports are NOT supported in v0; only absolute references between bundle modules work in this milestone. - Use strict=False when re-resolving bundle_root in the walker; the path was already existence-checked, and a concurrent removal in the narrow window should not raise across the loader's public boundary. - Hoist the json import out of _read_inline_bundle_json's body. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(lfx): split extension loader into a small subpackage (LE-1015) Addresses the file-size feedback from the LE-1015 review. The single 870-line loader.py becomes a flat package keyed off the four section banners that already existed inline: loader/ __init__.py # re-exports the public surface _types.py # SLOT constants, LoadedComponent, LoadResult _discovery.py # filesystem walk + importlib.util orchestration _detection.py # Component subclass identification (MRO heuristic) _orchestrator.py # load_extension, discover_inline_bundles _plugins.py # manifest-first precedence over langflow.plugins Largest file is now _orchestrator.py at 440 LOC (was 870); every file is well under the 800-LOC project guideline. No behavior change: the public import paths from lfx.extension are unchanged, all 38 loader tests still pass. ``_canonicalize_distribution`` is now exported as ``canonicalize_distribution`` from ``loader._plugins`` (it's a stable PEP-503 helper that downstream modules will reach for, so it loses the private underscore). The test suite is split to mirror the package: tests/unit/extension/loader/ conftest.py # shared fixtures, FakeDist, autouse scrub test_load_extension.py # @official slot, identity, failure modes test_inline_bundles.py # LANGFLOW_COMPONENTS_PATH, @extra slot test_plugins.py # manifest-first precedence helpers test_types.py # LoadedComponent, LoadResult, code parity Each test file imports its own slice of the public API and pulls fixtures from conftest, so a reader looking at one banner can read it in isolation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: LangflowCompat -> LfxCompat * fix: Remove references to ticket * fix: encode maxItems constraint in schema * fix: deferred fields and schema version * Fix ruff errors * fix: align loader tests with renamed manifest API and strip ticket refs The merge of feat/extension-validate brought in the LfxCompat / compat rename, but the loader test fixtures still used LangflowCompat / bundle_api and failed at the manifest layer. Update conftest.py + test_load_extension.py to the post-rename API. Strip 17 LE-XXXX ticket references from loader source files, errors.py loader-specific comments, and the four loader test docstrings, matching the convention applied to LE-1014. Descriptive prose preserved. Promote _BUNDLE_NAME_RE to BUNDLE_NAME_RE on the manifest module so the loader's orchestrator no longer reaches across modules into a private name. * refactor(lfx): clarify broad except, log malformed bundle.json, expand test docstring - Document why _discovery.import_bundle_module catches BaseException (startup-time loader, must surface bad bundles as typed errors rather than abort). - Add debug-level logging when bundle.json is malformed or non-object so a stale-cache footgun is at least observable. - Expand test_skips_re_imported_class docstring so future maintainers don't accidentally weaken the __module__-equality guard if package-style relative imports get added later. Also drops the stale duplicate-distribution claim from the PR description; that code is correctly deferred to LE-1022 along with /all integration. * feat(lfx): wire Extension System into /all, pathsep-split LANGFLOW_COMPONENTS_PATH, emit duplicate-distribution Closes the four AC gaps the previous reviewer flagged on PR #12967: 1. /all integration: get_and_cache_all_types_dict now also calls a new import_extension_components() that loads installed Extensions via load_installed_extensions, loads inline bundles via discover_inline_bundles, and builds frontend-node templates with extension/bundle/extension_version fields stamped on. Failures are logged and skipped per bundle. 2. LANGFLOW_COMPONENTS_PATH is now split on os.pathsep so multi-entry env vars (e.g. /a:/b on POSIX) produce multiple components-path entries instead of one literal non-existent path. Empty segments and missing paths are skipped. 3. duplicate-distribution is a real producer: load_installed_extensions surfaces a typed warning on the winner LoadResult when two distributions share a canonical name, naming every involved manifest path. 4. Manifest-first precedence runtime wiring: new filter_component_entry_points loads each entry-point and only skips ones that resolve to a Component subclass on a manifest-shipping distribution. plugin_routes.load_plugin_routes now applies it so non-component entry-points (route registrars) keep loading per the AC's 'unaffected' promise. Added the previously-missing AC test for same-distribution component+non-component partition. Also makes _distribution_canonical_name defensive against MagicMock test seams. * fix(lfx): register extension components under namespaced ID; promote duplicate-distribution to error Addresses the latest review of PR #12967: P1: /all integration now keys the cache inner dict by LoadedComponent.namespaced_id (ext:<bundle>:<Class>@<slot>) rather than the bare class name. Templates also carry the namespaced_id as an explicit field so consumers that look at the value (not the key) still see the canonical address. This is the form the LE-1020 migration table will rewrite legacy class-name references to. P2: load_installed_extensions now appends duplicate-distribution to result.errors instead of result.warnings, so LoadResult.ok=False when two distributions share a canonical name. The winner's components still appear in result.components so flows already pinned to them keep working; only the conflict status changes. Updated test to assert errors + ok=False; added explicit assertion that the winner's components are still present. * fix(lfx): installed-distribution discovery accepts pyproject.toml manifest form Closes the latest review finding on PR #12967: the installed-distribution scan only looked for extension.json, ignoring distributions whose manifest lives in [tool.langflow.extension] inside pyproject.toml. The AC explicitly treats both as valid manifest forms. _distribution_manifest_path now: - Returns extension.json immediately when present (preserves precedence matching load_manifest's discovery order). - Falls back to pyproject.toml only when extension.json is absent AND the pyproject's [tool.langflow.extension] section is parseable. Validation reuses load_manifest itself so the rule lives in exactly one place; a stray pyproject.toml without the section is correctly ignored. Tests cover: pyproject-only discovery, pyproject-without-section ignored, extension.json wins on collision, end-to-end pyproject load at @official, and pyproject-form manifest-first entry-point suppression. * refactor(lfx): tighten loader invariants, surface silent skips, harden tests Addresses the latest review feedback on PR #12967: Type-level invariants (_types.py): - LoadedComponent.__post_init__ enforces that @extra components must NOT carry a distribution. The reverse (@official without distribution) is permitted because load_extension is also used for dev-mode loads against a working tree before pip install. - LoadResult docstring documents the partial-success contract: components may be non-empty when errors is non-empty (some files imported, others failed). Callers branching on ok get strict success. Silent-failure fixes (_orchestrator.py + settings/base.py): - inline-path-missing: a non-existent / non-dir LANGFLOW_COMPONENTS_PATH entry now produces a typed warning per skipped path so a typo no longer yields zero diagnostics. Settings-layer skip bumped from debug to warning for the same reason. - bundle-json-invalid: a malformed or non-object bundle.json now surfaces a typed warning instead of silently rewriting the user-declared id/version to derived values under the same bundle name. - no-component-subclass gating uses a call-local counter instead of result.errors so the diagnostic stays accurate when a future caller reuses a LoadResult (multi-bundle / batch wrapper scenarios). Test hardening: - test_re_imported_class_is_skipped_via_module_filter rewritten to actually exercise the __module__-equality check via sys.modules injection; previously passed via module-import-failed (relative-import failure), which would silently weaken if package registration changes. - test_user_declared_path_order_is_preserved: AC #8's multi-path order case (distinct bundles in [path_b, path_a]) was unasserted; added. - test_inline_module_import_failure_attributes_identity: AC #10's identity-on-partial-failure was covered for @official but not @extra; added. - test_uses_real_distributions_by_default tightened to assert ep placement (in kept, not in skipped) instead of exact-list equality, so a future Langflow-shipped manifest doesn't silently flip the assertion. bumped 64 -> 175 passing extension tests; 20 backend integration tests still pass. * fix(lfx): malformed pyproject manifests surface manifest-invalid instead of disappearing Closes the latest review finding on PR #12967: a pyproject.toml with a [tool.langflow.extension] section that has missing/invalid required fields was silently dropped because _pyproject_has_extension_section ran full schema validation via load_manifest and returned False on ValueError/TypeError. That conflated 'no section' with 'section malformed'. Fix: detect section presence only. _pyproject_has_extension_section now calls _read_pyproject_extension (TOML parse + key lookup, no schema check). Behavior: - Section absent or pyproject TOML unparseable -> False (treat as regular non-manifest package). - Section present and is a table (valid OR schema-invalid) -> True. - Section present but is not a table -> True; the author intended to declare an extension and load_extension will surface the typed error. This way a typo'd pyproject Extension produces a typed manifest-invalid LoadResult with extension_id attribution, and manifest-first precedence still suppresses its legacy component entry-points -- matching the 'typed load results on success/failure' contract for the supported pyproject manifest form. Tests: two new cases pin the behavior. test_malformed_pyproject_section_ surfaces_manifest_invalid asserts a typed load-failure result with distribution attribution; the second test pins manifest-first suppression for malformed pyproject distributions. * refactor(lfx): emit inline-path-unreadable, document reload contract, trim rot Closes the remaining nits on PR #12967: - inline-path-unreadable (new typed error code): a configured LANGFLOW_COMPONENTS_PATH entry that raises OSError on iterdir (typically permission-denied) now produces a typed LoadResult error carrying str(exc) instead of silently swallowing the message. - duplicate-inline-bundle hint trimmed: removed forward promise about hard-error-in-a-later-release; same actionability, no expiration. - discover_inline_bundles docstring trimmed from three paragraphs to two sentences (per CLAUDE.md anti-multi-paragraph rule). - _distribution_manifest_path docstring trimmed to one-liner. - installed_extension_roots dropped Used-by caller-narration list; manifest_owning_distributions docstring rewritten to call out the shadow-load risk for direct callers and point to load_installed_ extensions for the typed warning surface. - _discovery.import_bundle_module: replaced 'until that lands in a later milestone' rot with a single line ('Absolute imports only between bundle modules; relative imports unsupported.') AND added the single-load-per-process contract note documenting why LE-1018 reload must scrub registry/sys.modules before re-invoking the loader. - load_extension docstring grew a 'Single-load-per-process contract' block telling direct callers not to rely on this function for refresh. Tests: new test_unreadable_path_emits_inline_path_unreadable pins the OSError -> typed-error path. --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(lfx): atomic-swap Bundle reload pipeline + endpoint + CLI (LE-1018) (#12979) * feat(lfx): add single-Bundle loader and LANGFLOW_COMPONENTS_PATH discovery (LE-1015) Introduces lfx.extension.loader: the runtime that turns an Extension on disk into LoadedComponent records keyed by ext:<bundle>:<Class>@<slot>. Two paths in: - load_extension(root): one manifest, one Bundle, registered at @official. Re-checks multi-bundle at runtime (defense-in-depth vs. the schema). - discover_inline_bundles(paths): each subfolder of LANGFLOW_COMPONENTS_PATH is a Bundle at @extra. Walk order is platform-independent (sorted dirs, user-declared path order). First-wins on duplicate names; second emits duplicate-inline-bundle warning that names both paths. Manifest-first precedence helpers (installed_extension_roots, manifest_owning_distributions, filter_plugin_entry_points) let callers of the legacy langflow.plugins entry-point loader skip distributions that ship a manifest, so component entry-points are not double-registered. New typed error codes: module-import-failed, duplicate-component-name, duplicate-distribution, duplicate-inline-bundle, inline-bundle-name-invalid. Each ships with a format branch and a snapshot test. Tests cover the AC: single-bundle happy path, multi-bundle rejection, missing/empty/no-Component bundle, duplicate class names, deterministic walk order, recursive discovery, inline-bundle first-wins + dot-dir skip + bundle.json metadata, manifest-first precedence partition, PEP-503 distribution-name canonicalization. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(lfx): address LE-1015 review feedback - Drop the unproduced duplicate-distribution error code; LE-1022 will add it once startup-time discovery has a place to surface it. Restores the invariant that every code in ERROR_CODES has a producer. - Clarify that intra-bundle relative imports are NOT supported in v0; only absolute references between bundle modules work in this milestone. - Use strict=False when re-resolving bundle_root in the walker; the path was already existence-checked, and a concurrent removal in the narrow window should not raise across the loader's public boundary. - Hoist the json import out of _read_inline_bundle_json's body. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(lfx): split extension loader into a small subpackage (LE-1015) Addresses the file-size feedback from the LE-1015 review. The single 870-line loader.py becomes a flat package keyed off the four section banners that already existed inline: loader/ __init__.py # re-exports the public surface _types.py # SLOT constants, LoadedComponent, LoadResult _discovery.py # filesystem walk + importlib.util orchestration _detection.py # Component subclass identification (MRO heuristic) _orchestrator.py # load_extension, discover_inline_bundles _plugins.py # manifest-first precedence over langflow.plugins Largest file is now _orchestrator.py at 440 LOC (was 870); every file is well under the 800-LOC project guideline. No behavior change: the public import paths from lfx.extension are unchanged, all 38 loader tests still pass. ``_canonicalize_distribution`` is now exported as ``canonicalize_distribution`` from ``loader._plugins`` (it's a stable PEP-503 helper that downstream modules will reach for, so it loses the private underscore). The test suite is split to mirror the package: tests/unit/extension/loader/ conftest.py # shared fixtures, FakeDist, autouse scrub test_load_extension.py # @official slot, identity, failure modes test_inline_bundles.py # LANGFLOW_COMPONENTS_PATH, @extra slot test_plugins.py # manifest-first precedence helpers test_types.py # LoadedComponent, LoadResult, code parity Each test file imports its own slice of the public API and pulls fixtures from conftest, so a reader looking at one banner can read it in isolation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(lfx): atomic-swap Bundle reload pipeline + endpoint + CLI (LE-1018) Five-stage reload (parallel staging load -> validate -> swap under write lock -> cleanup -> emit) for installed Bundles in Mode A. In-flight flows keep the pre-swap class via existing references; new flows pick up the post-swap class atomically; concurrent reloads on the same Bundle are rejected with reload-in-progress. Adds: * lfx/extension/registry.py -- BundleRegistry with per-bundle reload-in-progress guard and components_index.json writer * lfx/extension/reload.py -- the five-stage pipeline; events emission is stubbed (TODO LE-1017) so the swap mechanics can ship before the events service lands * loader: optional module_namespace param so Stage 1 lands in __reload_staging__.<id> instead of the live _lfx_ext.* namespace * errors: four new typed reload codes (reload-in-progress, reload-bundle-not-installed, reload-bundle-name-mismatch, reload-source-missing) with branch templates and snapshot tests * HTTP: POST /api/v1/extensions/{id}/bundles/{name}/reload, gated by the existing get_current_active_user dependency, returns 409 with a typed body for the in-progress collision case * CLI: lfx extension reload <id> [--bundle <name>] -- HTTP client against the dev server with text/json output and proper exit codes (--all is gated until LE-1019 lands the list endpoint) * tests: 16 reload-pipeline tests covering the AC matrix (rename round-trip, broken-bundle isolation, concurrent readers, in-flight flow, double-reload guard, bundle-name mismatch) plus 9 CLI client tests Mode A only. In Mode B/C bundle changes require a Docker image rebuild and the reload path is not exercised. The events emission in Stage 5 is intentionally stubbed -- the LE-1017 ticket will swap the body of _emit_bundle_reload_event in one place without touching the pipeline core. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * [autofix.ci] apply automated fixes * feat(lfx): add `extension init` and `extension dev` CLIs (LE-1016) (#12968) * feat(lfx): add extension manifest schema, validate CLI, and error formatter (LE-1014) Foundation for the Bundle Separation iteration. Defines what a valid extension.json looks like (Pydantic models + Draft 2020-12 JSON Schema), ships the offline `lfx extension validate` command, and ships the single `format_extension_error` function that every other extension-system module will use to render structured errors. What's in lfx.extension: - `ExtensionManifest` / `BundleRef` / `LangflowCompat` Pydantic models with `extra="forbid"` so unknown fields fail loudly. Deferred fields (`services`, `routes`, `hooks`, `starter_projects`, `userConfig`) are reserved as None-only so non-null values produce a dedicated `field-deferred-in-this-milestone` error instead of a generic schema wall. `bundles` accepts a list but rejects length > 1 with `multi-bundle-deferred-in-this-milestone` (validator-enforced; the loader re-checks at install time in LE-1015). - `schema.build_schema()` + `build_schema_json()` produce the publishable artifact at schemas.langflow.org/extension/v1.json. - `ExtensionError` typed envelope and `format_extension_error` -- one branch per discriminant. Codes are registered in `ERROR_CODES`; an `ExtensionError` constructed with an unknown code raises at construction time, preventing producers from shipping without a matching renderer. - `validate_extension` runs four passes: manifest discovery + schema, path-safety (no `..`, no absolute paths, no symlink escape), AST inspection of every `.py` (syntax, Component subclass present, build() declared, top-level `import *`, top-level I/O primitives), and an opt-in `--execute-imports` that runs each module in a subprocess with a temporary HOME / TMPDIR / LANGFLOW_CONFIG_DIR and LANGFLOW_*/LFX_* env vars stripped. - `lfx extension validate` and `lfx extension schema` typer subcommands. Acceptance-criteria coverage in tests/unit/extension/: - Round-trips every v0 manifest field; deferred fields rejected; multi-bundle rejected with the dedicated discriminant. - JSON Schema validates the v0 example and rejects 12 malformed manifests with distinct error paths (>= 10 required by the ticket). - Median default-validate runtime < 100ms on the basic template. - Crafted side-effect bundle: default validate does NOT execute it (canary file is never written); `--execute-imports` DOES execute it and the canary appears, while LANGFLOW_* env vars are NOT inherited by the subprocess. - Snapshot tests for every code in ERROR_CODES; a guard test verifies ERROR_CODES and the snapshot table are in lockstep so future additions cannot ship without a format branch and a snapshot. Wiring: `lfx extension` is a sub-app under the Authoring help panel so future tickets (LE-1016 init/dev, LE-1018 reload) can attach without colliding with the existing `lfx validate` (which validates flow JSON, not extensions). * fix(lfx): fall back to tomli on Python 3.10 in extension manifest loader tomllib is stdlib only on 3.11+, but lfx supports 3.10-3.13. Use the existing tomli runtime dependency as the 3.10 fallback (same API, so the import alias keeps the rest of the module unchanged). Fixes ModuleNotFoundError seen in CI on the 3.10 job. * Update src/lfx/tests/unit/extension/test_schema.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update src/lfx/src/lfx/extension/schema.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update src/lfx/src/lfx/cli/_extension_commands.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * feat(lfx): add single-Bundle loader and LANGFLOW_COMPONENTS_PATH discovery (LE-1015) Introduces lfx.extension.loader: the runtime that turns an Extension on disk into LoadedComponent records keyed by ext:<bundle>:<Class>@<slot>. Two paths in: - load_extension(root): one manifest, one Bundle, registered at @official. Re-checks multi-bundle at runtime (defense-in-depth vs. the schema). - discover_inline_bundles(paths): each subfolder of LANGFLOW_COMPONENTS_PATH is a Bundle at @extra. Walk order is platform-independent (sorted dirs, user-declared path order). First-wins on duplicate names; second emits duplicate-inline-bundle warning that names both paths. Manifest-first precedence helpers (installed_extension_roots, manifest_owning_distributions, filter_plugin_entry_points) let callers of the legacy langflow.plugins entry-point loader skip distributions that ship a manifest, so component entry-points are not double-registered. New typed error codes: module-import-failed, duplicate-component-name, duplicate-distribution, duplicate-inline-bundle, inline-bundle-name-invalid. Each ships with a format branch and a snapshot test. Tests cover the AC: single-bundle happy path, multi-bundle rejection, missing/empty/no-Component bundle, duplicate class names, deterministic walk order, recursive discovery, inline-bundle first-wins + dot-dir skip + bundle.json metadata, manifest-first precedence partition, PEP-503 distribution-name canonicalization. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(lfx): address LE-1015 review feedback - Drop the unproduced duplicate-distribution error code; LE-1022 will add it once startup-time discovery has a place to surface it. Restores the invariant that every code in ERROR_CODES has a producer. - Clarify that intra-bundle relative imports are NOT supported in v0; only absolute references between bundle modules work in this milestone. - Use strict=False when re-resolving bundle_root in the walker; the path was already existence-checked, and a concurrent removal in the narrow window should not raise across the loader's public boundary. - Hoist the json import out of _read_inline_bundle_json's body. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(lfx): split extension loader into a small subpackage (LE-1015) Addresses the file-size feedback from the LE-1015 review. The single 870-line loader.py becomes a flat package keyed off the four section banners that already existed inline: loader/ __init__.py # re-exports the public surface _types.py # SLOT constants, LoadedComponent, LoadResult _discovery.py # filesystem walk + importlib.util orchestration _detection.py # Component subclass identification (MRO heuristic) _orchestrator.py # load_extension, discover_inline_bundles _plugins.py # manifest-first precedence over langflow.plugins Largest file is now _orchestrator.py at 440 LOC (was 870); every file is well under the 800-LOC project guideline. No behavior change: the public import paths from lfx.extension are unchanged, all 38 loader tests still pass. ``_canonicalize_distribution`` is now exported as ``canonicalize_distribution`` from ``loader._plugins`` (it's a stable PEP-503 helper that downstream modules will reach for, so it loses the private underscore). The test suite is split to mirror the package: tests/unit/extension/loader/ conftest.py # shared fixtures, FakeDist, autouse scrub test_load_extension.py # @official slot, identity, failure modes test_inline_bundles.py # LANGFLOW_COMPONENTS_PATH, @extra slot test_plugins.py # manifest-first precedence helpers test_types.py # LoadedComponent, LoadResult, code parity Each test file imports its own slice of the public API and pulls fixtures from conftest, so a reader looking at one banner can read it in isolation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(lfx): add `extension init` and `extension dev` CLIs (LE-1016) The two scaffolding CLIs an Extension author types: - `lfx extension init <target>` writes the basic single-Bundle template (manifest with $schema, README, .gitignore, one Component subclass + a pytest smoke test). AC #1: the generated extension validates clean against LE-1014. AC #2: the generated test file is a valid pytest module that exercises the component's build() method. AC #3: any --template other than 'basic' fails with a typed template-deferred-in-this-milestone error and a non-zero exit. Refuses to scaffold over a non-empty target dir. - `lfx extension dev <target>` validates the local extension, records its absolute path in <config_dir>/extensions/dev_extensions.json, prints reload instructions, and execs `langflow run` (or `python -m langflow` when langflow isn't on PATH). --skip-launch registers without launching (used by tests + external dev-server scripts); --skip-validate lets authors register a known-broken manifest to debug it under the loader. Stack: - Wave 0: error codes (extension-target-exists, extension-target-invalid, local-extension-missing) with format branches and snapshot tests. - Wave 1: lfx.extension.init_template -- pure-data scaffolder, no Typer dependency so the CLI is a thin shell over it. - Wave 1: lfx.extension.dev_registry -- atomic JSON state file under the langflow user-cache dir; helpers for register / list / unregister / load_dev_extensions / dev_extension_component_paths. - Wave 2: lfx.cli._extension_commands gains init/dev subcommands. - Wave 2: langflow.main lifespan hook reads the dev registry after bundle loading and extends components_path with each registered bundle dir, so the existing palette discovery picks up dev extensions. Missing paths surface as local-extension-missing warnings (AC #5) without aborting startup. Tests (84 new, 197 total in tests/unit/extension/): - test_init_template.py: AC scenarios + identifier derivation + deterministic file shape. - test_dev_registry.py: register/list/unregister round-trip, idempotent re-register refreshes timestamp, malformed state file treated as empty, missing-path warning, recovery when path reappears, env-var override precedence. - test_cli.py: AC #1 init->validate, AC #3 deferred templates, --skip-launch registers without launching, --skip-validate short-circuits the pre-flight pass. - test_errors.py: snapshot rows for all three new codes; the every-known-code-has-a-snapshot test enforces parity. LE-1018 (reload) reuses load_dev_extensions; LE-1022 (installed-pkg discovery) shares the components_path extension pattern. AC #4 ("boots Langflow with the extension visible in the palette within 5s") is delivered jointly by `extension dev` (registers + execs) and the lifespan hook (loads on startup). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(lfx): address LE-1016 review feedback Fixes the four HIGH issues + the elevated LOW from the review pass: 1. dev_extension_component_paths now forwards EVERY warning, not just local-extension-missing. Previously a duplicate-component-name (or any future warning code) was silently dropped, hiding real signal from the lifespan hook's logs. 2. Defensive emit when a LoadResult has components but source_path is None. The current loader always sets source_path, but a future hand-built LoadResult could violate that contract; we now surface a typed local-extension-missing error rather than dropping the extension silently. 3. Replaced the fragile ``min(len(parts))`` bundle-root selection with a relative-to-source-path measurement. Handles deep-vs-shallow sibling extensions correctly without depending on absolute path depth. 4. Generated README now documents the langflow/lfx prerequisite under the Develop section so authors know `pytest` requires the lfx environment, not just Python. 5. Forced LANGFLOW_LAZY_LOAD_COMPONENTS=false unconditionally in the `extension dev` exec env (was setdefault, which let a developer's global lazy-loading export silently hide their dev components from the palette and miss AC #4's 5s budget). Plus three MEDIUMs: - sys.modules cleanup in test_generated_test_file_runs_against_generated_component so a later test importing the same dotted path doesn't pick up a stale module from a deleted tmp_path. Component instantiation moved inside the try block because Component.__init__ uses inspect.getsourcefile against self.__class__'s still-live module. - Added regex-drift test that pins the init_template patterns to match manifest.py's so a schema regex change can't quietly produce invalid scaffolded manifests. - Added two new dev_registry tests covering the forward-all-warnings contract and the source_path=None defense. Tests: 201 passing (4 new); ruff check + format clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: LangflowCompat -> LfxCompat * fix: Remove references to ticket * fix: encode maxItems constraint in schema * fix: deferred fields and schema version * Fix ruff errors * fix: align loader tests with renamed manifest API and strip ticket refs The merge of feat/extension-validate brought in the LfxCompat / compat rename, but the loader test fixtures still used LangflowCompat / bundle_api and failed at the manifest layer. Update conftest.py + test_load_extension.py to the post-rename API. Strip 17 LE-XXXX ticket references from loader source files, errors.py loader-specific comments, and the four loader test docstrings, matching the convention applied to LE-1014. Descriptive prose preserved. Promote _BUNDLE_NAME_RE to BUNDLE_NAME_RE on the manifest module so the loader's orchestrator no longer reaches across modules into a private name. * fix(lfx): align init template with renamed manifest API After merging feat/extension-loader into this branch, two surfaces fell out of sync with the renamed manifest schema: - init_template generates extension.json with `lfx: {bundle_api: [1]}`, but the validator now requires `lfx: {compat: ["1"]}` per LfxCompat. This made `test_basic_template_validates_clean` fail with manifest-invalid (`lfx.compat: Field required; lfx.bundle_api: Extra inputs are not permitted`). - test_init_template_regexes_match_manifest_schema reads `manifest_mod._BUNDLE_NAME_RE`, but that symbol was promoted to public `BUNDLE_NAME_RE` in |
|||
| f3ad468c13 |
ci: stop transient Windows npm/cache flakes from failing frontend tests (1.10.0) (#13175)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 50625dce9c |
fix: update embedded chat CDN URL to langflow-ai org and v1.0.8 (#13118)
* fix: update embedded chat CDN URL to langflow-ai org and v1.0.8 * test: update widget code tests for langflow-ai CDN URL and v1.0.8 - Update CDN URL expectations from logspace-ai to langflow-ai - Bump version assertions from v1.0.7 to v1.0.8 - Fix stale multi-line URL assertion removed by the implementation fix * fix(types): add isAuth to GetCodeType to fix Biome no-explicit-any lint errors isAuth was already used in getWidgetCode and passed from EmbedModal but was missing from the type definition, forcing test assertions to use `as any`. |
|||
| d017e156cd |
fix: add stream toggle back to agent (#13155)
* add stream toggle back to agent * chore: auto-bake note keys and regenerate backend locales/en.json [skip ci] * ci: stop transient Windows npm/cache flakes from failing frontend tests The nightly frontend job fails almost every day on a single Windows Playwright shard, never on actual test logic. With fail-fast:false over ~70 Windows shards each making several cache/artifact-service calls, a transient GitHub Actions cache/artifact hiccup on any one shard flips needs.setup-and-test.result to failure and reds the whole nightly (observed steps: Setup Node.js Environment, Cache Playwright Browsers, Upload Playwright Coverage Artifact). - Decouple npm caching from actions/setup-node (its internal cache step can't be made non-fatal) and replace it with a standalone actions/cache@v5 step marked continue-on-error. - Mark Cache Playwright Browsers and the artifact-upload steps continue-on-error so an infra blip can no longer fail a test shard. Only a real test/browser failure can fail a shard now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * [autofix.ci] apply automated fixes --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| 851a48401b |
fix: Auto-create write parents and reject unsafe glob patterns (#13075)
* Auto-create write parents and reject unsafe glob patterns * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| ec924b1af4 |
fix: Remove duplicate Lock flow control from bottom-center menubar (#13088)
* remove flow lock button form canvas controls * [autofix.ci] apply automated fixes * fix testcaes * fix testcaes --------- Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| 682c97846f |
fix(schema): resolve nested $ref objects to named models instead of dict (#13128)
Two-level nested $defs/$ref schemas (e.g. payload → Outer.inner → Inner) were collapsing the inner model to a bare `dict` because of three issues in `create_input_schema_from_json_schema`: 1. `parse_type` resolved `$ref` before calling `_build_model`, so named refs lost their identity and always went through the anonymous path. 2. Anonymous models were named using `len(model_cache)`, but the cache is only populated after a build completes, so concurrent in-progress models collided on `AnonModel0` and falsely triggered the self-reference guard. 3. The `$ref` branch in `_build_model` pre-added the refname to the `building` set before recursing, so the recursive call immediately saw itself as self-referential and fell back to `dict`. The Pydantic model still required `inner` (Outer.required = ["name", "inner"]), but the LLM only saw a generic dict schema for `inner` and omitted the field, producing `payload.inner: Field required` 500s before the MCP call ever reached the server. Fix: - Preserve the original $ref-bearing subschema in `parse_type` and route it through `_build_model`'s $ref branch. - Replace `len(model_cache)` with a monotonic anonymous-name counter. - Drop the redundant `building.add/discard` and model_cache assignment from the `$ref` branch; the recursive `_build_model` call already handles both correctly. Adds four regression tests covering the reporter's repro, missing-field validation, no-spurious-warning, and sibling inline-object distinctness. |
|||
| 4dc9e28eb0 |
fix(initial_setup): upsert by (user_id, name) when loading flows from disk (#13132)
* fix(initial_setup): upsert by (user_id, name) when loading flows from disk Langflow fails to start with IntegrityError on the unique_flow_name constraint when LANGFLOW_LOAD_FLOWS_PATH is used in CI/CD pipelines and the flow file's id differs from the DB record's id (e.g. nightly upgrade, regenerated UUIDs, fresh DB). Manual DB intervention was the only workaround. Root cause: find_existing_flow only looked up by endpoint_name and id, never by (user_id, name). The unique constraint is on (user_id, name), so when those lookups missed, the loader fell through to INSERT and PostgreSQL rejected it. Fix: - find_existing_flow now takes optional keyword args (user_id, name) and falls back to a (user_id, name) lookup when endpoint_name/id don't match. The endpoint_name branch is also scoped by user_id when supplied so it can't accidentally return another user's flow. - upsert_flow_from_file passes name and user_id to find_existing_flow. When the match is by name (existing.id != file's flow_id), the DB id is preserved during the setattr loop -- rewriting it from under FK referrers was unsafe and was never required by the original logic. Tests: 8 regression tests cover lookup precedence (id wins, endpoint scoped by user, name fallback fires only when id misses), the reporter's scenario (no IntegrityError on repeated import with differing file ids), DB-id preservation on name-match, and a no-regression case where matched-by-id behavior is unchanged. Fixes the CI/CD upgrade failure reported in IBM Cloud Slack (C06SUKEN3LJ thread 1777992833.170099). * fix(initial_setup): fix matched_by_id on SQLite, improve update log message Normalize existing.id to UUID before the matched_by_id comparison. On SQLite, SQLAlchemy can return ids as strings; str == UUID is always False, so matched_by_id would silently be wrong and the file id would be dropped even on a genuine id-match. Improve the update log to include the DB id and flow name alongside the file's UUID so the CI/CD upgrade scenario (name-matched upsert with differing UUIDs) is debuggable from logs. * [autofix.ci] apply automated fixes * fix: Preserve db id * [autofix.ci] apply automated fixes * feat: Add flag to control name matching overwrite * [autofix.ci] apply automated fixes * Flip the default value for the flag * Update test_upsert_flow_from_file.py * fix: Sqlalchemy-based crash on startup --------- Co-authored-by: Jordan Frazier <jordan.frazier@datastax.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| c493c817d9 |
fix(api): parse multipart/form-data on /run so session_id is preserved (#13092)
* fix(api): parse multipart/form-data on /run so session_id is preserved
parse_input_request_from_body always parsed the request body as JSON,
so multipart/form-data requests to /api/v1/run/{flow_id} (e.g. uploading
a file inline alongside text and a session_id) silently failed to parse
and returned a default SimplifiedAPIRequest. The flow then executed with
no input_value, no files, and routed the conversation into the default
session keyed on the flow_id rather than the caller-supplied session_id.
Detect multipart bodies and parse them via request.form() instead, mapping
string-valued fields (input_value, input_type, output_type,
output_component, session_id) and JSON-decoded tweaks onto
SimplifiedAPIRequest. Non-string entries (e.g. UploadFile values) are
ignored so inline file uploads do not crash the parser. JSON requests
continue to go through the existing fast path.
Fixes #9859.
* [autofix.ci] apply automated fixes
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
|
|||
| a0a08cc398 |
fix(api): resolve global-variable default_fields on API runs (#11781) (#13129)
When a flow is uploaded via API and run via API, global variables with
"Apply to Fields" (default_fields) were silently ignored. The component
received an empty value and failed with errors like
"OpenAI API key is required". Opening the flow once in the Playground
worked around it because the frontend writes load_from_db: true back to
the stored template.
Root cause: variable resolution at vertex build time
(update_params_with_load_from_db_fields) only fires for fields whose
stored template already has load_from_db: true. That flag is set
exclusively by the frontend's inputGlobalComponent hook, which
API-only workflows never trigger.
Fix: mirror the frontend's behaviour in-memory inside simple_run_flow,
just before Graph.from_payload. A new helper module
langflow.api.v1.global_variable_defaults builds the same
{display_name: variable_name} map as getUnavailableFields and applies
it to empty, eligible (str-type, visible, not already load_from_db)
template fields. The mutation is local to the request; the stored
flow is unchanged. Variable-service errors are logged and swallowed so
a lookup failure cannot block flow execution.
Tests: 27 unit tests cover map construction, the pure graph
transformer (matching rules, skip rules, immutability, malformed
input tolerance), and the async wrapper (success and failure paths).
Fixes #11781
|
|||
| f1e9181887 |
fix: reuse single Langfuse client across flow runs to stop thread leak (#13107)
* fix: reuse single Langfuse client across flow runs to stop thread leak The Langfuse v3 SDK spawns background threads per client instantiation (task_manager, prompt_cache, OTel exporters) and never joins them. LangFuseTracer was constructing a new Langfuse() per flow run, so under load the process accumulated threads indefinitely. Cache the client at module level keyed by (secret_key, public_key, host), route both LangFuseTracer._setup_langfuse and the feedback-helper _get_langfuse_client through it, and call client.flush() in end() so buffered events are delivered before the trace completes. Fixes #9066 * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| 545d8598b7 |
fix: added per-user endpoint for flows (#13072)
* added per-user endpoint * fixed webhook test * added test * added cross user security * fixed nit * added depends wrapper * fix: update webhook_run_flow docstring to match new auth dependency signature * [autofix.ci] apply automated fixes * fixed flow 404 when running from second user * regenerated component index --------- Co-authored-by: ogabrielluiz <gabriel@langflow.org> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| ea559d7971 | fix: validate review tool names inline (#13040) | |||
| bd35587957 |
chore: Add non-blocking test coverage advisor for PRs (#13148)
add checker to verify tests non blocker |
|||
| a003bd6ca6 |
fix: Memory Base Table output causes JSON serialization error when saving Agent message (#13136)
* test * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * fix: Coercion of Numpy scalar values * Update src/backend/tests/unit/test_messages.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> |
|||
| 66912d4a53 |
Merge main source updates into release-1.10.0
# Conflicts: # docker/build_and_push.Dockerfile # docker/build_and_push_backend.Dockerfile # docker/build_and_push_base.Dockerfile # docker/build_and_push_ep.Dockerfile # docker/build_and_push_with_extras.Dockerfile |
|||
| 1e6f0f95aa |
fix: Remove redundant Components entry from the left sidebar (#13090)
remove search Icon Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local> |
|||
| 53c62bceb0 |
chore: Block hardcoded Tailwind palette via Biome lint (#13125)
* add biome checker for hardcoded pallete * add biome checker on ci * [autofix.ci] apply automated fixes * lint-js fix * improve CI job * biome fixes * unblock CI on biome job --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> |
|||
| ac308a09f6 |
fix(frontend): stop empty-state splash from flashing after login (#12966)
* fix(frontend): stop empty-state splash from flashing after login Two bugs caused EmptyPageCommunity and EmptyFolder to render for one frame between login success and the flows query resolving. 1. flowsManagerStore.resetStore() set flows: [] on logout. After logout/login the truthy gate in main-page.tsx (flows && examples && folders) passed immediately with stale empty values, and shouldShowMainContent returned false, so EmptyPageCommunity rendered until the new flows query resolved. Reset to undefined instead so the gate correctly waits for the next fetch. 2. homePage initialized isEmptyFolder = true and only flipped it inside a useEffect, so the first render of HomePage always rendered EmptyFolder regardless of state. Use null as the resolving sentinel, skip the effect until both the global flows store and the folder query are populated, and render the existing list skeleton while resolving. Verified with a MutationObserver in the post-login transition: prior to this change three empty-state components were inserted into the DOM in the same paint window; after the change none are. * fix(frontend): resolve isEmptyFolder when folder query errors The post-login flash guard waited for folderData to be defined, but folderData stays undefined when the folder query errors out (e.g. when myCollectionId is undefined after the user deletes all folders). isEmptyFolder then stuck at null and the page rendered ListSkeleton forever instead of EmptyFolder. Gate on isLoading from useGetFolderQuery instead. isLoading is true only on the first fetch with no data and flips to false on success or error, so the effect still waits past the post-login stale-store window but resolves correctly when the query settles either way. |
|||
| 5e9f0c37bc |
Merge branch 'release-1.9.3' into release-1.10.0
# Conflicts: # .secrets.baseline # docs/package.json # pyproject.toml # src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json # src/backend/base/pyproject.toml # src/backend/tests/unit/components/files_and_knowledge/test_retrieval.py # src/frontend/package-lock.json # src/frontend/package.json # src/frontend/src/components/core/parameterRenderComponent/components/modelInputComponent/__tests__/ModelInputComponent.test.tsx # src/frontend/src/components/core/parameterRenderComponent/components/modelInputComponent/index.tsx # src/frontend/src/modals/modelProviderModal/hooks/useProviderConfiguration.ts # src/lfx/pyproject.toml # src/lfx/src/lfx/_assets/component_index.json # src/sdk/pyproject.toml # uv.lock |
|||
| b695f97cd3 | fix: More python 3.14 compatibility cleanup | |||
| cf488e545c | fix: Gate more tests on package presence | |||
| 5fec0a1a42 | fix: Skip Python 3.14 incompatible watsonX imports | |||
| ecadc106d0 | fix: Python 3.14 fallback for baidu |