mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-24 21:39:50 +08:00
* feat(bundles): add lfx-bundles metapackage skeleton
Creates the manifest-less lfx-bundles distribution (the langchain-community
model) that the long-tail providers will move into. Empty skeleton for now;
the bulk move (scripts/migrate/consolidate_bundles.py) populates the provider
folders + per-provider extras later.
- src/bundles/lfx-bundles: a single pyproject declaring the lfx.bundles
entry-point (lfx_bundles = "lfx_bundles"), an lfx>=1.10.0,<2.0.0 pin, and a
generated (currently empty) `all` extra; plus a bare lfx_bundles namespace
package and a README documenting the model + install stories
- wired into the root workspace via the existing bundle marker blocks:
dep lfx-bundles[all]>=1.0,<2.0, uv source, and member
- hyphen dir name so release.yml's src/bundles/*/pyproject.toml glob builds
it with zero workflow change
Verified: the wheel builds (entry_points.txt carries the lfx.bundles group);
load_lfx_bundles_extensions (PR-1) discovers the entry point and the empty
skeleton registers zero providers with no error.
* feat(bundles): consolidate first long-tail tranche into lfx-bundles
Adds scripts/migrate/consolidate_bundles.py (the inverse of port_bundle.py --
moves in-tree providers into the manifest-less lfx-bundles metapackage) and
runs it on a verified 5-provider tranche: tavily, exa, wikipedia, yahoosearch,
wolframalpha.
Per provider the script:
- moves src/lfx/src/lfx/components/<p>/ -> lfx_bundles/<p>/ (lowercase names);
- leaves a fail-soft import shim (first line `# lfx-bundles-shim`) so
`from lfx.components.<p> import X` keeps working when lfx-bundles is
installed, and raises an actionable ImportError otherwise;
- merges the provider's third-party deps into a PEP 685-normalized lfx-bundles
extra and regenerates the `all` aggregate. Dep parity holds: `uv sync` is a
no-op because those deps were already pulled via langflow-base[complete];
- appends the 4-entry migration block per Component class (28 entries) so saved
flows referencing lfx.components.<p>.<Class> migrate to
ext:<p>:<Class>@official.
To avoid double registration, the in-tree component walk
(_load_components_dynamically) now skips shimmed provider dirs, and
component_index.json is regenerated (355->348 components, 95->90 modules); the
moved providers load only at @official via lfx.bundles discovery.
Verified: discovery finds all 5 at @official with no errors; shims resolve;
`import lfx.components` still works; the index drops the 7 moved component
entries (residual `tools`-category name refs resolve via the shim); ruff clean.
First tranche proves the engine; the remaining long-tail scales by extending
PROVIDER_DEPS (each provider's deps verified individually -- the careful part).
* feat(bundles): consolidate 30-provider tranche 2 into lfx-bundles
Extends PROVIDER_DEPS with 30 individually-verified providers and runs the
consolidation: vector stores (chroma, clickhouse, couchbase, milvus, mongodb,
pgvector, pinecone, qdrant, supabase, upstash, weaviate), model providers
(groq, mistral, ollama, perplexity, sambanova), and tools/memory/data (apify,
assemblyai, confluence, firecrawl, git, glean, icosacomputing, mem0, needle,
scrapegraph, serpapi, unstructured, youtube, zep).
Dep verification (the careful part): every spec comes from langflow-base's
per-provider extras or its direct dependencies; langchain-community providers
carry the wrapper plus the SDK the wrapper lazy-imports (e.g. pgvector,
atlassian-python-api); requests is declared explicitly where imported (it is
only transitive in today's env); pinecone keeps its python_version<'3.14'
marker verbatim. Tranche excludes: providers with langflow imports (vlmrun),
provider-specific lfx.base dirs (composio/huggingface/langwatch), case-
sensitive names (FAISS/Notion), the openai-SDK family (azure/aiml/deepseek/
litellm/lmstudio/novita/openrouter/vllm/xai/cometapi -- cleaner after PR-8),
and the partner set.
Dep parity verified at the resolution level: the uv.lock diff is +220 lines of
lfx-bundles extras metadata with ZERO packages added or removed (`name =` diff
empty), so pip install langflow resolves the identical set.
Also: 192 append-only migration entries (48 classes x 4, zero bare-name
ambiguities); component_index.json regenerated 348->300 components / 90->60
modules (exactly the moved set, no stale standalone entries); mongodb_atlas.py
SLF001 per-file-ignore and the mem0/mongodb detect-secrets baseline entries
migrated to the new paths (lint/secrets exceptions travel with moved files);
35 bundles now discover at @official with 55 components; shims verified across
categories; 449 extension tests pass.
* feat(bundles): consolidate openai-SDK family tranche 3 into lfx-bundles
10 providers that ride the langchain-openai wrapper, deferred from tranche 2
until the partner graduation settled the openai-SDK dep story: aiml, azure,
cometapi, deepseek, litellm, lmstudio, novita, openrouter, vllm, xai.
Dep verification: every provider declares langchain-openai>=1.1.6; the openai
SDK is declared only where a component imports it directly (aiml, deepseek,
litellm, lmstudio, vllm, xai -- wrapper-transitive elsewhere); requests
declared where imported (cometapi, deepseek, novita, xai); lmstudio's lazy
NVIDIAEmbeddings path gets langchain-nvidia-ai-endpoints~=1.0.0. The litellm
component drives LiteLLM-served endpoints through the OpenAI client and does
NOT import the litellm package -- langflow-base's litellm extra stays put.
These providers use the lazy _dynamic_imports __init__ shape; it survives the
move unchanged (import_mod resolves via __spec__.parent and
lfx.components._importing remains a core helper).
Dep parity: uv.lock diff has zero package additions/removals (all specs
already resolved via langflow-base[complete]).
Also: 56 append-only migration entries (14 classes x 4, zero ambiguities);
component_index.json regenerated 300->286 components / 60->50 modules (exactly
the moved set); detect-secrets baseline re-keyed; 45 bundles now discover at
@official with 69 components; shims verified (azure/xai/litellm/deepseek);
ruff clean.
* fix(ci): nightly bundle rename follows [extras] refs and self-refs
Two gaps in update_bundle_versions.py around extras suffixes, both fatal
or silently wrong for the first nightly carrying the lfx-bundles
metapackage:
- update_root_pyproject_for_bundle's dep regex required the version
specifier immediately after the name, so "lfx-bundles[all]>=1.0,<2.0"
(a MAIN dep) was left unrewritten while the workspace member was
renamed to lfx-bundles-nightly; uv lock then tries to resolve stable
lfx-bundles from PyPI, where it does not exist. The same gap silently
left "lfx-docling[local]>=0.1.0" optional-dep refs pointing at the
stable distribution. The regex now tolerates an [extras] group and
carries it into the replacement.
- rename_bundle_pyproject skipped self-referencing extras, so the
metapackage's generated `all` extra kept 45 "lfx-bundles[<provider>]"
members after the rename, pulling the stable distribution (same
lfx_bundles import package, install collision) once published. Self
refs now follow the rename, idempotently.
Tests drive the real script module, mirroring test_bundle_lfx_pin.py.
This closes the PR-2 audit item flagged when the metapackage was
introduced. The canonical-pre-release cutover would retire the nightly
rename entirely; until it lands, the rename must be correct.
* feat(bundles): graduate partner set to standalone lfx-<provider> packages (#13573)
* feat(bundles): graduate partner set to standalone lfx-<provider> packages
Extracts the five partner/flagship providers into manifest-shipping
distributions: lfx-openai, lfx-anthropic, lfx-amazon, lfx-datastax,
lfx-cohere. Each ships extension.json (lfx.compat ["1"]), a
langflow.extensions entry-point, an lfx>=1.10.0,<2.0.0 pin, and is wired into
the root workspace marker blocks. Zero flow impact by the bundle-name
invariant: ext:<provider>:<Class>@official ids are unchanged.
Mechanics:
- adopts the five-phase scripts/migrate/port_bundle.py from
feat/bundle-mass-extraction (handles shared-base moves, consumer rewrites,
surgical index updates, migration-table appends) and runs it per partner
with --migration-release 1.11.0
- datastax's shared lfx.base.datastax moves into lfx_datastax.base; its
backend unit tests move to src/bundles/datastax/tests (74 pass); its ruff
per-file-ignore and its check_component_env_writes ALLOWLIST entry travel;
10 repo consumers rewritten (incl. the vector_store_rag starter project)
- amazon_bedrock_converse.py legacy langflow.* imports rewritten to the lfx.*
equivalents (thin re-export aliases; behavior-identical) pre-extraction
- runtime deps pinned from langflow-base's extras / direct deps (wrapper-
guaranteed SDKs stay transitive, parity-exact); --remove-base-extra dropped
the openai/anthropic/cohere/aws extras from langflow-base[complete] in favor
of the bundle pins; the astradb extra stays (also used outside datastax)
- in-tree dirs replaced with marker shims pointing at lfx_<p>.components.<p>
(skipped by the in-tree walk; legacy from lfx.components.<p> imports keep
working); lfx/components/__init__.py entries kept, consistent with the
consolidated providers
- validate.py: accept classes whose base is a *derived* Component base
(LCVectorStoreComponent / LCToolComponent / ...) -- they inherit the
class-level outputs declaration and only override the output method, which
the AST-only check could not see; bare Component subclasses keep the strict
build/outputs requirement (+ regression test)
- BUNDLE_API.md changelog entries added for the branch's surface changes: the
lfx.bundles manifest-less discovery group + precedence tier +
bundle-discovery-malformed code (from the foundation PR) and the validator
acceptance above
- 100 append-only migration entries (20 classes x 5 partners x 4 shapes);
component_index.json surgically updated 300->280 components / 60->55
modules, sha256 recomputed and verified
- detect-secrets baseline re-keyed for moved partner files
Dep parity: lock diff adds only the five lfx-* package names; no third-party
package added or removed. One benign transitive re-resolution: anthropic SDK
0.105.2 -> 0.109.0 (allowed by langchain-anthropic's range on both sides).
Verified: all five register at @official via the installed-manifest tier
(extension_id/distribution = lfx-<p>, 20 components); manifest-less
lfx_bundles unchanged (35 bundles, disjoint); all 5 shims import; engine-safe;
`lfx extension validate` passes for all five; 450 extension unit tests, 60
pilot-upgrade migration tests, 74 moved datastax tests pass; ruff clean.
* fix(bundles): shim lfx.base.datastax so stored-flow imports keep resolving
The datastax graduation moved lfx.base.datastax into lfx_datastax.base,
but saved flows and starter templates (Hybrid Search RAG,
TemplateAssistant) embed `from lfx.base.datastax.astradb_base import
AstraDBBaseComponent` inside their stored component code fields, which
is re-executed verbatim at flow build time. Without a shim that import
raises ModuleNotFoundError and the flows fail to build
(test-starter-projects red).
Mirror the lfx.components shim contract: module-aliasing to
lfx_datastax.base, narrow except that only translates a missing bundle
(transitive dep failures re-raise untouched), marker-tagged single-file
dir, removed at M4 together with the components shims.
* chore(bundles): bounded version ranges for curated lfx-* packages (#13576)
langflow's pyproject is the release-coordination point now that lfx and the
lfx-* bundles release independently. Every curated lfx-* dependency declares a
BOUNDED range (>=A,<B), never an exact pin -- exact pins in reusable library
metadata create resolver conflicts for downstreams that depend on a different
version. Exact pins for reproducibility live in uv.lock and the release-build
manifests.
- the 4 pilots + 5 graduated partners: >=0.1.0 -> >=0.1.0,<1.0.0 (bundles
follow their own 0.1.x cadence)
- lfx-bundles[all]>=1.0,<2.0 unchanged (versions with the metapackage
contract)
- the three lfx-docling[...] optional-dependency refs bounded the same way
- policy documented inline in the bundle-deps marker block; the cross-bundle
CI matrix verifies the ranges resolve together across the supported lfx axis
Verified: uv lock resolves with zero package/version changes (bounds are
metadata-only today); the nightly rename's dep regex already matches the
bounded form.
* test(bundles): lock the in-tree shim contract + breakage audit (#13577)
* chore(bundles): bounded version ranges for curated lfx-* packages
langflow's pyproject is the release-coordination point now that lfx and the
lfx-* bundles release independently. Every curated lfx-* dependency declares a
BOUNDED range (>=A,<B), never an exact pin -- exact pins in reusable library
metadata create resolver conflicts for downstreams that depend on a different
version. Exact pins for reproducibility live in uv.lock and the release-build
manifests.
- the 4 pilots + 5 graduated partners: >=0.1.0 -> >=0.1.0,<1.0.0 (bundles
follow their own 0.1.x cadence)
- lfx-bundles[all]>=1.0,<2.0 unchanged (versions with the metapackage
contract)
- the three lfx-docling[...] optional-dependency refs bounded the same way
- policy documented inline in the bundle-deps marker block; the cross-bundle
CI matrix verifies the ranges resolve together across the supported lfx axis
Verified: uv lock resolves with zero package/version changes (bounds are
metadata-only today); the nightly rename's dep regex already matches the
bounded form.
* test(bundles): lock the in-tree shim contract + breakage audit
Locks the five-point contract for the 50 lfx-bundles import shims under
lfx/components/ (45 metapackage + 5 graduated partners):
1. first line is the `# lfx-bundles-shim` marker (keys the in-tree walk's
double-registration skip);
2. a shim dir is exactly one __init__.py -- no impls, no deps;
3. sys.modules module-aliasing (submodule trees resolve);
4. bundle missing -> actionable ModuleNotFoundError whose wording is part
of the contract ("pip install lfx-bundles" / "pip install lfx-<p>");
5. a missing *transitive* dep re-raises untouched.
Test shape (env-adaptive by design -- the lfx test env prunes the venv, so
nothing here assumes bundles are installed):
- source-level sweep of every real shim, parameterized (no imports);
- hermetic mechanism tests against a synthetic shim + synthetic target
(alias-resolves / locked-message / transitive-reraise);
- the walk-skip detector and the marker sweep must agree exactly;
- one adaptive live test on lfx.components.tavily exercising whichever
branch the running env is in.
106 tests pass in the pruned lfx env (where the live test exercises the
bare-engine locked-message branch).
Breakage audit (gh code search, recorded in the PR): ~20-25 public repos
reference lfx.components.*; the majority are langflow forks (vendored tree,
unaffected by packaging). Genuine library consumers exist and the moved
providers have real surface (openai: 11 external repos, datastax: 17) --
covered by the shims wherever bundles are co-installed (every
`pip install langflow`). Decision: deprecation warnings NOT warranted now;
revisit at M4 (shim removal), where one loud minor release before removal
is the right shape.
* test(bundles): extend the shim contract sweep to lfx.base shims
The datastax graduation moved lfx.base.datastax into lfx_datastax.base
and left a module-aliasing shim behind (stored flow code fields embed
the legacy import and are re-executed verbatim at build time). Sweep
lfx/base for marker shims with the same source-level contract as the
components sweep: one-file stub, module-aliasing to the bundle's base
subpackage, narrow name-checked except, locked install message.
* docs(bundles): install shapes, override rule, bundle_api_version (#13578)
Documents the deliberately small user-facing rule surface of the metapackage
split (1.11):
- extensions-overview.mdx: the two install stories (pip install langflow =
everything, same as today; pip install lfx = engine only, bring your own
bundles); the bundle-name-is-identity invariant; the current package table
(lfx-bundles metapackage + the 5 partner packages + the 4 pilots); the
legacy-import shim behavior with the locked ModuleNotFoundError wording and
the migration recipe for direct lfx users (switch to lfx[bundles] or pin
lfx-<provider> packages); the override rule ("ship a manifest -- a manifest
always wins", with the bundle-shadowed warning); bundle_api_version as the
single compat number (lfx.compat ["1"]; manifest-less packages ride their
PEP 508 lfx pin) and the no-version-arithmetic rule.
- deployment-lfx-compatibility.mdx: lfx[bundles] as the headless/serverless
deployment footnote (engine + lfx-bundles[all]; slimmer per-provider
alternative; intentionally no lfx[all]).
Both files verified MDX-safe (no unbackticked angle brackets).
* fix(bundles): stack-review fixes — symlink containment, idempotency, docs accuracy (#13579)
* chore(bundles): bounded version ranges for curated lfx-* packages
langflow's pyproject is the release-coordination point now that lfx and the
lfx-* bundles release independently. Every curated lfx-* dependency declares a
BOUNDED range (>=A,<B), never an exact pin -- exact pins in reusable library
metadata create resolver conflicts for downstreams that depend on a different
version. Exact pins for reproducibility live in uv.lock and the release-build
manifests.
- the 4 pilots + 5 graduated partners: >=0.1.0 -> >=0.1.0,<1.0.0 (bundles
follow their own 0.1.x cadence)
- lfx-bundles[all]>=1.0,<2.0 unchanged (versions with the metapackage
contract)
- the three lfx-docling[...] optional-dependency refs bounded the same way
- policy documented inline in the bundle-deps marker block; the cross-bundle
CI matrix verifies the ranges resolve together across the supported lfx axis
Verified: uv lock resolves with zero package/version changes (bounds are
metadata-only today); the nightly rename's dep regex already matches the
bounded form.
* test(bundles): lock the in-tree shim contract + breakage audit
Locks the five-point contract for the 50 lfx-bundles import shims under
lfx/components/ (45 metapackage + 5 graduated partners):
1. first line is the `# lfx-bundles-shim` marker (keys the in-tree walk's
double-registration skip);
2. a shim dir is exactly one __init__.py -- no impls, no deps;
3. sys.modules module-aliasing (submodule trees resolve);
4. bundle missing -> actionable ModuleNotFoundError whose wording is part
of the contract ("pip install lfx-bundles" / "pip install lfx-<p>");
5. a missing *transitive* dep re-raises untouched.
Test shape (env-adaptive by design -- the lfx test env prunes the venv, so
nothing here assumes bundles are installed):
- source-level sweep of every real shim, parameterized (no imports);
- hermetic mechanism tests against a synthetic shim + synthetic target
(alias-resolves / locked-message / transitive-reraise);
- the walk-skip detector and the marker sweep must agree exactly;
- one adaptive live test on lfx.components.tavily exercising whichever
branch the running env is in.
106 tests pass in the pruned lfx env (where the live test exercises the
bare-engine locked-message branch).
Breakage audit (gh code search, recorded in the PR): ~20-25 public repos
reference lfx.components.*; the majority are langflow forks (vendored tree,
unaffected by packaging). Genuine library consumers exist and the moved
providers have real surface (openai: 11 external repos, datastax: 17) --
covered by the shims wherever bundles are co-installed (every
`pip install langflow`). Decision: deprecation warnings NOT warranted now;
revisit at M4 (shim removal), where one loud minor release before removal
is the right shape.
* docs(bundles): install shapes, override rule, bundle_api_version
Documents the deliberately small user-facing rule surface of the metapackage
split (1.11):
- extensions-overview.mdx: the two install stories (pip install langflow =
everything, same as today; pip install lfx = engine only, bring your own
bundles); the bundle-name-is-identity invariant; the current package table
(lfx-bundles metapackage + the 5 partner packages + the 4 pilots); the
legacy-import shim behavior with the locked ModuleNotFoundError wording and
the migration recipe for direct lfx users (switch to lfx[bundles] or pin
lfx-<provider> packages); the override rule ("ship a manifest -- a manifest
always wins", with the bundle-shadowed warning); bundle_api_version as the
single compat number (lfx.compat ["1"]; manifest-less packages ride their
PEP 508 lfx pin) and the no-version-arithmetic rule.
- deployment-lfx-compatibility.mdx: lfx[bundles] as the headless/serverless
deployment footnote (engine + lfx-bundles[all]; slimmer per-provider
alternative; intentionally no lfx[all]).
Both files verified MDX-safe (no unbackticked angle brackets).
* fix(bundles): review fixes — symlink containment, idempotency, docs accuracy
Fixes from the multi-agent stack review (kept as a separate PR so the
reviewed PRs' diffs stay frozen for QA's independent pass):
- _bundles_root.py: directory-level symlink containment — a provider dir
that resolves outside the bundle root is skipped with a typed
bundle-discovery-malformed warning, mirroring the seed-directory walk's
is_within rule (+ regression test). Anchors the trust boundary to the
installed package tree.
- consolidate_bundles.py: migration-append idempotency guard — entries are
deduped on full content so a partially-failed earlier run cannot duplicate
rows on re-run (table stays append-only).
- langflow-base pyproject: documented WHY the aws extra is deliberately
retained after the lfx-amazon graduation (boto3 also backs the server's
own S3 storage backend and lfx's S3 ingestion — review suggested removing
it; that would regress S3 storage support).
- extensions-overview.mdx: note that `lfx extension list` shows
manifest-shipping extensions only; manifest-less packages load at startup
but are not listed.
93 loader+migration tests pass (incl. the new symlink test); ruff clean.
* refactor(bundles): stabilize import_mod as a public BUNDLE_API utility
The lazy-import helper that bundle packages call from their
__getattr__-based __init__.py files lived at lfx.components._importing -- an
internal path with no stability contract, imported by 35 separately-installed
bundle __init__ files (30 lfx-bundles providers + the 5 graduated partners).
- canonical home is now lfx.utils.lazy_import.import_mod (code unchanged);
lfx.components._importing re-exports it so in-tree callers and any external
code on the old path keep working
- all 35 bundle-side imports rewritten to the stable path
- BUNDLE_API.md: surface table entry + changelog (additive)
- contract tests: re-export identity, both call forms, the AttributeError
conversion
Verified: identity holds across both paths; lazy __init__ loads work through
the new path for both bundle families; 110 tests pass; ruff clean.
* fix(bundles): tombstone the broken legacy ZepChatMemory build method (#13580)
build_message_history targeted the zep-python v1 SDK (ZepClient +
zep_python.langchain.ZepChatMessageHistory); both were removed in
zep-python 2.x and the zep extra pins 2.0.2, so the method has been
unable to run for as long as the pin has existed -- its ImportError
guard misleadingly told users to 'pip install zep-python' (already
installed). The component is legacy=True with helpers.Memory as its
designated replacement, so rather than hand-write a new integration
against the 2.x SDK, the method now raises a clear RuntimeError
pointing at the Message History component.
Flow identity is preserved: class/component name, display_name,
description, inputs and the memory output are byte-identical, so saved
flows keep loading, i18n locale keys are unchanged, and
migration_table.json needs no edits. New bundle tests pin the stub
contract (identity, actionable error, no zep_python import).
* test(bundles): extend the shim contract sweep to lfx.base shims
The datastax graduation moved lfx.base.datastax into lfx_datastax.base
and left a module-aliasing shim behind (stored flow code fields embed
the legacy import and are re-executed verbatim at build time). Sweep
lfx/base for marker shims with the same source-level contract as the
components sweep: one-file stub, module-aliasing to the bundle's base
subpackage, narrow name-checked except, locked install message.
* test(bundles): extras-drift guard for the lfx-bundles metapackage
Risk-2 of the metapackage split: the generated `all` extra and the
per-provider extras must never drift by hand-edit, or `pip install
langflow` silently loses a provider's deps. Guard the four invariants:
extras <-> provider dirs (PEP 685-normalized), `all` == the exact
self-ref set, normalized keys collision-free, and the metapackage
provider set disjoint from the graduated partner distributions.
* fix(extension): symlink-escaped providers reject with path-escape, matching the documented contract
The lfx.bundles provider containment check (stack-review symlink fix)
emitted bundle-discovery-malformed, whose template and hint describe a
broken entry-point declaration. The changelog's path-safety entry
already documents symlink escapes as path-escape on every other
discovery path; use the same code here. Also resolves the semantic
merge conflict with the per-mode code split (the removed
_malformed_error location kwarg).
* fix(tests): repoint lfx tests off moved providers; complete provider fallback map
CI fail-fast had been masking these: the LFX test job runs in an
engine-only env where openai/anthropic/chroma are now bundle-package
shims, so every test that used them as the example category failed on
all Python versions (3.14 just reported first), and the backend Group 2
leg failed collecting test_lfx_bundles_extras.py on Python 3.10.
- flow_requirements: complete _PROVIDER_PACKAGE_FALLBACKS for the nine
moved model providers (OpenAI, Anthropic, Amazon Bedrock, Groq,
Google Generative AI, SambaNova, IBM watsonx.ai, Ollama + existing
Azure OpenAI). In engine-only installs MODEL_PROVIDERS_DICT registers
only in-tree providers, so the dynamic source-inspection path cannot
resolve moved providers; the static fallbacks keep lfx run/serve
requirements inference working. A dict hit still wins.
- test_dynamic_imports / test_import_utils: use composio (still-in-tree
lazy category with its SDK absent in the bare env) as the example
category instead of openai/anthropic/chroma; patch import_module at
its canonical home lfx.utils.lazy_import.
- flow-builder tests (build_flow_from_spec, flow_builder_tools,
propose_field_edit): specs use LanguageModelComponent (in-tree)
instead of OpenAIModel.
- test_lfx_bundles_extras: tomli fallback for Python 3.10 (tomllib is
stdlib 3.11+; pytest guarantees tomli on <3.11).
Full lfx unit suite: 4550 passed. Backend extras+pin tests: 24 passed.
* fix(tests): repoint MCP client-server tests off graduated OpenAIModel key
Same fail-fast-masked class as 14dfa7f268, backend side: since the
partner graduation, extension components register in /api/v1/all under
their namespaced ids (ext:openai:OpenAIModelComponent@official), so the
bare 'OpenAIModel' key these tests hardcoded no longer exists on any
Python version (the 'py3.14-only' Group 2 failure was the only leg that
ran; 3.10 was cancelled by fail-fast). The MCP feature itself is fine —
search returns the namespaced ids and add_component accepts them.
- Use LanguageModelComponent (in-tree, stable bare key; SecretStr
api_key, real_time_refresh fields, advanced 'stream', LanguageModel
output) for the redaction/configure/search/describe/spec tests.
- configure_dynamic_field: model_name -> api_key (the refresh field on
LanguageModelComponent).
- prompt-template-variables spec: drop the model node — server-side
validation builds the graph and a model without an API key fails its
build; the test's subject is the dynamic {var} fields.
Full file: 69 passed.
* Update test_mongodb_atlas.py
* Frontend tests
467 lines
24 KiB
Markdown
467 lines
24 KiB
Markdown
# Bundle API
|
|
|
|
Stable surface that Langflow Extension Bundles consume. Every public symbol
|
|
listed below is part of the contract: changes to its name, signature, semantics,
|
|
or visibility require a coordinated version bump and a `## Changelog` entry.
|
|
|
|
This document is paired with the integer **`BUNDLE_API_VERSION`** declared in
|
|
[`lfx.extension.manifest`](src/lfx/src/lfx/extension/manifest.py). Manifests
|
|
declare the contract versions they support via `lfx.compat: ["1"]`; a bundle
|
|
that does not list `str(BUNDLE_API_VERSION)` is rejected at install time with
|
|
`version-constraint-unsatisfied`.
|
|
|
|
> **CI gate:** any PR that modifies a file containing an in-scope surface MUST
|
|
> add a `## Changelog` entry describing the change. The CI guard
|
|
> [`scripts/migrate/check_bundle_api_changelog.py`](scripts/migrate/check_bundle_api_changelog.py)
|
|
> enforces this. Pure-internal refactors that preserve every public symbol's
|
|
> name and signature do not require a changelog entry, but reviewers should be
|
|
> skeptical.
|
|
|
|
---
|
|
|
|
## Surface (v0)
|
|
|
|
### Component base class
|
|
|
|
| Symbol | Source |
|
|
| --- | --- |
|
|
| `Component` | `lfx.custom.custom_component.component.Component` |
|
|
| `Component.build()` (declared on subclasses) | call site of every loaded bundle module |
|
|
| `Component.inputs` | declarative input list |
|
|
| `Component.outputs` | declarative output list |
|
|
| `Component.display_name` / `Component.description` / `Component.icon` / `Component.documentation` | metadata read by the palette |
|
|
| `Component.name` | optional override of the registry class name |
|
|
|
|
### Inputs
|
|
|
|
| Symbol | Source |
|
|
| --- | --- |
|
|
| `Input` (base) | `lfx.io` |
|
|
| `MessageTextInput` / `MultilineInput` / `SecretStrInput` | `lfx.io` |
|
|
| `IntInput` / `FloatInput` / `BoolInput` | `lfx.io` |
|
|
| `DropdownInput` / `TabInput` | `lfx.io` |
|
|
| `DictInput` / `NestedDictInput` | `lfx.io` |
|
|
| `FileInput` / `LinkInput` | `lfx.io` |
|
|
| `HandleInput` | `lfx.io` |
|
|
|
|
### Outputs
|
|
|
|
| Symbol | Source |
|
|
| --- | --- |
|
|
| `Output` | `lfx.io` |
|
|
|
|
### Schema types
|
|
|
|
| Symbol | Source |
|
|
| --- | --- |
|
|
| `Data` | `lfx.schema.data` |
|
|
| `DataFrame` | `lfx.schema.dataframe` |
|
|
| `Message` | `lfx.schema.message` |
|
|
|
|
### Lazy-import helper (consumed by bundle ``__init__.py`` files)
|
|
|
|
| Symbol | Source |
|
|
| --- | --- |
|
|
| `import_mod(attr_name, module_name, package)` | `lfx.utils.lazy_import` (canonical; `lfx.components._importing` re-exports it for backward compatibility) |
|
|
|
|
### Manifest contract (consumed by the loader)
|
|
|
|
| Symbol | Source |
|
|
| --- | --- |
|
|
| Manifest schema (`extension.json` / `[tool.langflow.extension]`) | `lfx.extension.manifest.ExtensionManifest` |
|
|
| `BundleRef` (one entry in `bundles[]`) | `lfx.extension.manifest.BundleRef` |
|
|
| `LfxCompat` (declared as `manifest.lfx`) | `lfx.extension.manifest.LfxCompat` |
|
|
| `BUNDLE_API_VERSION` (the integer this lfx ships) | `lfx.extension.manifest` |
|
|
| `EXTENSION_SCHEMA_URL` / `SCHEMA_VERSION` | `lfx.extension.manifest` |
|
|
|
|
Slot vocabulary: `official` (installed pip distributions and seed
|
|
directories) and `extra` (paths declared in `LANGFLOW_COMPONENTS_PATH`).
|
|
Component IDs at runtime are `ext:<bundle>:<Class>@<slot>`.
|
|
|
|
### Discovery + loading entry points
|
|
|
|
| Symbol | Source |
|
|
| --- | --- |
|
|
| `load_extension(root)` | `lfx.extension.loader` |
|
|
| `load_installed_extensions()` | `lfx.extension.loader` |
|
|
| `discover_inline_bundles()` | `lfx.extension.loader` |
|
|
| `discover_installed_extensions()` / `discover_seed_extensions()` / `discover_all_extensions()` | `lfx.extension.discovery` |
|
|
| `LoadedComponent` | `lfx.extension.loader` (frozen dataclass; what the registry stores) |
|
|
| `LoadResult` | `lfx.extension.loader` |
|
|
| `SLOT_OFFICIAL` / `SLOT_EXTRA` | `lfx.extension.loader` |
|
|
|
|
### Reload pipeline
|
|
|
|
| Symbol | Source |
|
|
| --- | --- |
|
|
| `reload_bundle(registry, bundle_name)` | `lfx.extension.reload` |
|
|
| `BundleRegistry` | `lfx.extension.bundle_registry` |
|
|
| `BundleRecord` | `lfx.extension.bundle_registry` |
|
|
| `ReloadInProgressError` | `lfx.extension.bundle_registry` |
|
|
| `POST /api/v1/extensions/{id}/bundles/{name}/reload` | `langflow.api.v1.extensions` |
|
|
|
|
### Errors
|
|
|
|
| Symbol | Source |
|
|
| --- | --- |
|
|
| `ExtensionError` | `lfx.extension.errors` |
|
|
| `ExtensionErrorCollection` | `lfx.extension.errors` |
|
|
| `format_extension_error(error)` | `lfx.extension.errors` |
|
|
| `ERROR_CODES` (frozenset of every typed code) | `lfx.extension.errors` |
|
|
|
|
The full kebab-case discriminant set is the contract — adding a code is
|
|
backward-compatible; removing or renaming a code is a breaking change and
|
|
requires a `BUNDLE_API_VERSION` bump.
|
|
|
|
### Validate / authoring CLI
|
|
|
|
| Symbol | Source |
|
|
| --- | --- |
|
|
| `validate_extension(root, *, execute_imports=False)` | `lfx.extension.validate` |
|
|
| `ValidateReport` | `lfx.extension.validate` |
|
|
| `lfx extension validate` (CLI) | `lfx.cli._extension_commands` |
|
|
| `lfx extension schema` (CLI) | `lfx.cli._extension_commands` |
|
|
| `lfx extension init` (CLI) | `lfx.cli._extension_commands` |
|
|
| `lfx extension dev` (CLI -- registers a local path and execs `langflow run`) | `lfx.cli._extension_commands` |
|
|
| `lfx extension list` (CLI) | `lfx.cli._extension_commands` |
|
|
| `lfx extension reload` (CLI) | `lfx.cli._extension_commands` |
|
|
| `register_dev_extension` / `unregister_dev_extension` (Python API) | `lfx.extension.dev_registry` |
|
|
|
|
### Migration
|
|
|
|
| Symbol | Source |
|
|
| --- | --- |
|
|
| Migration-table file | `src/lfx/src/lfx/extension/migration/migration_table.json` |
|
|
| `MigrationEntry` | `lfx.extension.migration.schema` |
|
|
| `MigrationTable` | `lfx.extension.migration.schema` |
|
|
| `migrate_flow_payload(payload, table)` | `lfx.extension.migration.rewrite` |
|
|
| `MIGRATION_SCHEMA_VERSION` | `lfx.extension.migration.schema` |
|
|
|
|
---
|
|
|
|
## Out of scope (v0)
|
|
|
|
These are reserved in the manifest schema and produce a typed
|
|
`field-deferred-in-this-milestone` error if set; they are NOT part of the
|
|
v0 contract:
|
|
|
|
- `services` — bundle-declared service factories
|
|
- `routes` — bundle-mounted HTTP routes
|
|
- `hooks` — bundle-declared lifecycle hooks
|
|
- `starter_projects` — bundle-shipped starter flows
|
|
- `userConfig` — bundle-declared user-config schema
|
|
- Multi-bundle manifests (`bundles` list with length > 1)
|
|
|
|
---
|
|
|
|
## Pilot bundle: `lfx-duckduckgo`
|
|
|
|
The shipped LE-1023 pilot is **`duckduckgo`**, extracted into the
|
|
standalone distribution
|
|
[`lfx-duckduckgo`](src/bundles/duckduckgo/) under `src/bundles/duckduckgo/`
|
|
with its own `pyproject.toml`. `langflow`'s own `pyproject.toml`
|
|
declares `lfx-duckduckgo>=0.1.0` as a regular dependency so a flat
|
|
`pip install langflow` continues to ship the bundle as before.
|
|
|
|
Why this bundle:
|
|
|
|
- Single component (`DuckDuckGoSearchComponent`) in a single file
|
|
(`duck_duck_go_search_run.py`).
|
|
- Zero git churn over the last six months.
|
|
- Modern `Component` base class (no `LCToolComponent` legacy).
|
|
- No authentication required — failure mode is a single failed request, not a
|
|
paid-API outage.
|
|
- Class name is globally unique across `src/lfx/src/lfx/components/**`, so the
|
|
bare-name migration entry is allowed by `check_bare_names.py`.
|
|
|
|
The runtime half of the M1 proof-of-delivery gate (save a flow on
|
|
pre-migration Langflow, upgrade, confirm it loads AND runs identically)
|
|
lives in the dogfood checklist at
|
|
[`src/bundles/duckduckgo/M1_DOGFOOD_CHECKLIST.md`](src/bundles/duckduckgo/M1_DOGFOOD_CHECKLIST.md);
|
|
the deserialize half is covered by
|
|
`src/lfx/tests/integration/extension/test_pilot_duckduckgo_upgrade.py`.
|
|
|
|
---
|
|
|
|
## Changelog
|
|
|
|
### v0 (this release)
|
|
|
|
- Initial surface enumerated above. Frozen as `BUNDLE_API_VERSION = 1`.
|
|
- `BundleRegistry.write_locked()` exposed as a public context manager so the
|
|
reload pipeline can hold the registry write lock across both the
|
|
`sys.modules` swap and the `BundleRecord` install. Concurrent readers
|
|
can no longer observe new modules paired with the old record. No change
|
|
to the addressable component contract.
|
|
- HTTP reload endpoint (`POST /api/v1/extensions/{id}/bundles/{name}/reload`)
|
|
returns `422 Unprocessable Entity` for structural failures (broken
|
|
bundle, missing source path, name mismatch) instead of `200 OK` with
|
|
`ok=false`. Body is `{...primaryError, result: ReloadResult}` so the
|
|
full typed result is preserved under the FastAPI `detail` envelope.
|
|
`409 Conflict` for `reload-in-progress` is unchanged.
|
|
- CLI table updated to remove the obsolete `dev register` / `dev unregister`
|
|
/ `dev list` subcommands; the actual surface is `extension dev <path>`
|
|
plus the Python helpers `register_dev_extension` / `unregister_dev_extension`.
|
|
- `MigrationTable.ambiguous_bare_names` added. Each entry is
|
|
`{name, candidates: [list of canonical IDs]}` and registers a bare
|
|
class name that exists in 2+ bundles. The deserializer now surfaces
|
|
`component-name-ambiguous` (with the candidate targets) for any bare
|
|
name listed here, instead of falling through to the generic
|
|
`component-not-found-with-hint`. Seeded with the canonical regression
|
|
cases (`MergeDataComponent`, `SplitTextComponent`, `SubFlowComponent`).
|
|
`check_bare_names.py` now verifies every Component class found in
|
|
2+ bundle folders has a matching marker, so a future bundle move that
|
|
introduces a new ambiguity is caught at PR time.
|
|
- Router-trust CI guard broadened to scan every `.py` under
|
|
`src/backend/base/langflow/api/**` and `src/lfx/src/lfx/**`; a new file
|
|
that mounts an `APIRouter(prefix=".../extensions...")` is auto-detected
|
|
and checked for forbidden install/uninstall/registry-mutation handlers.
|
|
Authors of files with non-literal prefixes can opt in via a
|
|
`# router-trust: in-scope` marker.
|
|
- Router-trust guard rewritten to use AST-based cross-file resolution.
|
|
A forbidden handler in module A is now caught when module B mounts A's
|
|
router via `parent.include_router(child, prefix=".../extensions...")`,
|
|
and the same applies transitively across multi-hop include_router
|
|
chains. An imported router that cannot be statically resolved is
|
|
ignored (the guard never flags routes it cannot prove reachable from
|
|
`/extensions`); routes co-located with an in-scope router ARE flagged.
|
|
- `check_migration_append_only.py` now compares
|
|
`ambiguous_bare_names` alongside `entries`. A marker may not be
|
|
removed once published, and its `candidates` list may only grow --
|
|
shrinking it would silently regress flows from
|
|
`component-name-ambiguous` to `component-not-found-with-hint`.
|
|
- Router-trust guard now resolves dotted attribute references in
|
|
`include_router` and decorators. ``include_router(child.api.router,
|
|
prefix="/extensions")`` after ``import child.api`` (and the
|
|
``import child.api as alias; alias.router`` shape) are caught -- not
|
|
just ``from child.api import router as child_router``. The parser
|
|
flattens any ``Name``/``Attribute`` chain, and the resolver walks
|
|
imports of either kind (``from M import N`` and ``import M``,
|
|
with or without an asname) back to the source file.
|
|
- Router-trust guard's relative-import resolver is now
|
|
``__init__.py``-aware. Inside a package, ``from .child import Y``
|
|
anchors at the package itself (level=1 -> ``pkg``); inside a regular
|
|
module ``pkg.foo`` it anchors at the parent package (level=1 ->
|
|
``pkg``). The arithmetic differs because ``__init__.py``'s file
|
|
module IS the package, while ``pkg/foo.py``'s file module is
|
|
``pkg.foo``. The resolver tracks ``is_package`` and decrements
|
|
``level`` by one for ``__init__.py`` files so both shapes resolve
|
|
correctly.
|
|
- Code-review hardening pass across the extension subsystem. No public
|
|
symbol's name or signature changed; this entry covers behavioural
|
|
tightening that bundle authors and operators should be aware of:
|
|
- **Path-safety contract honored on every discovery path.**
|
|
``DiscoveredExtension`` records emitted from
|
|
``discover_installed_extensions`` / ``discover_seed_extensions``
|
|
now run the same resolve-and-``relative_to`` containment check that
|
|
``validate_extension`` performs. A symlinked ``bundles[0].path``
|
|
or a symlinked seed subdirectory that escapes the extension root
|
|
is now rejected with ``path-escape`` *before* reaching the loader,
|
|
instead of slipping through to ``exec_module()``. The shared
|
|
primitive lives at ``lfx.extension._paths.is_within``; every
|
|
walker (loader, validator, seed discovery, inline-bundle discovery)
|
|
uses the same function and the same ``SKIP_DIR_NAMES``.
|
|
- **``--execute-imports`` env allowlist.** The validator's
|
|
``--execute-imports`` subprocess now inherits an explicit allowlist
|
|
(``PATH``, ``LANG``, ``LC_*``, ``SYSTEMROOT``, ``TMPDIR``, ``TZ``,
|
|
Python locale + encoding vars) instead of denylisting only
|
|
``LANGFLOW_*``/``LFX_*``. Cloud / CI credentials
|
|
(``AWS_*``, ``OPENAI_API_KEY``, ``GITHUB_TOKEN``, ...) no longer
|
|
propagate into untrusted bundle import. The CLI / module docs
|
|
re-frame this pass as best-effort hygiene lint, not a sandbox.
|
|
- **AST hygiene lint widened.** ``_find_top_level_io`` now flags
|
|
``exec``, ``eval``, ``__import__``, ``compile`` as top-level
|
|
primitives and ``importlib.import_module`` /
|
|
``importlib.__import__`` as dotted-name primitives. Still
|
|
best-effort literal-name matching; trivially bypassable by
|
|
obfuscation, and documented as such.
|
|
- **Reload swap is non-destructive.** ``_swap_sys_modules`` now
|
|
builds the staging->prod rename map **before** any ``sys.modules``
|
|
mutation, snapshots popped old modules into a recovery map, and
|
|
restores them on any mid-swap exception. The length-mismatch
|
|
tripwire on ``zip(strict=True)`` no longer leaves the prod
|
|
namespace shredded. A new typed code,
|
|
``reload-class-retag-failed``, is appended to
|
|
``ReloadResult.warnings`` when ``cls.__module__`` cannot be
|
|
retagged so the empty-palette-after-reload regression leaves a
|
|
trail instead of silently failing.
|
|
- **Cross-source bundle-name collision.**
|
|
``load_installed_extensions`` now detects two distributions with
|
|
different canonical names but identical ``bundle.name`` (which
|
|
would silently clobber each other at
|
|
``_lfx_ext.official.<name>.*``) and emits a typed
|
|
``duplicate-bundle-name`` error on the loser, dropping its
|
|
components. ``BundleRegistry.install_bundle`` additionally logs a
|
|
WARNING when an existing record is replaced by a record from a
|
|
different ``source_path`` (catches collisions the upstream
|
|
precedence resolver missed).
|
|
- **Reload endpoint off event loop.**
|
|
``POST /api/v1/extensions/{id}/bundles/{name}/reload`` now invokes
|
|
``reload_bundle`` via ``asyncio.to_thread`` so slow or large
|
|
bundle imports do not freeze the worker for other in-flight
|
|
requests. The wire contract (status codes, body shape) is
|
|
unchanged.
|
|
- **Stable typed-error code rename.**
|
|
``multi-bundle-deferred-in-this-milestone`` is renamed to the
|
|
stable ``multi-bundle-unsupported``. The old code is retained in
|
|
``ERROR_CODES`` as a deprecated alias for one milestone for log
|
|
scrapers. Three new codes are added to
|
|
``ERROR_CODES``: ``duplicate-bundle-name`` (see above),
|
|
``reload-class-retag-failed`` (see above), and
|
|
``reload-transport-error`` (CLI-side connectivity failure,
|
|
previously misreported as ``reload-source-missing``).
|
|
- **Discovery preserves "unreadable" vs "absent" distinction.**
|
|
``_pyproject_declares_extension`` now propagates ``OSError`` so a
|
|
permission failure on a pyproject that *might* declare an
|
|
extension surfaces as ``manifest-unreadable`` instead of being
|
|
silently dropped as "no extension here".
|
|
- **Dev registry corruption is logged.** ``_read_state`` now
|
|
distinguishes file absent (silent, legitimate empty registry),
|
|
file present but unreadable (WARNING), and file present but
|
|
corrupt JSON / wrong shape (WARNING with detail). The state
|
|
file is written with mode 0600 so a hostile third-party process
|
|
cannot inject an extension path into the developer's next
|
|
``langflow run``.
|
|
- **Entry-point predicate avoids module-level side effects.**
|
|
``_entry_point_loads_to_component`` now consults
|
|
``importlib.util.find_spec`` first and only falls through to
|
|
``ep.load()`` when the spec lookup is insufficient. The
|
|
``except BaseException`` was narrowed to ``except Exception`` so
|
|
``SystemExit`` / ``KeyboardInterrupt`` are no longer swallowed at
|
|
filter time.
|
|
- **Frontend reload-success warnings surfaced.** The reload route's
|
|
``ReloadResult.warnings`` (non-empty on success) now reach the
|
|
user via a notice toast in addition to the green success toast.
|
|
Wire shape unchanged; this is a UI fix that consumes existing
|
|
payload fields.
|
|
- **Internal-only file split.** ``sys.modules`` surgery primitives
|
|
moved to ``lfx.extension.reload_swap``; ``load_installed_extensions``
|
|
/ ``load_seed_extensions`` moved to
|
|
``lfx.extension.loader._startup``. Both are re-exported from
|
|
their previous import paths so external imports are unchanged.
|
|
- **Editable installs are discovered via the entry-point fallback.**
|
|
``_distribution_manifest_path`` now falls back to the
|
|
``langflow.extensions`` entry-point group when ``dist.files`` only
|
|
surfaces ``dist-info/`` entries (the ``pip install -e`` /
|
|
``uv pip install -e`` case). The entry-point value is resolved
|
|
via ``importlib.util.find_spec`` -- which runs import-system
|
|
finders but never executes the module body -- and the resulting
|
|
package directory is scanned for ``extension.json`` or a
|
|
``[tool.langflow.extension]`` pyproject. Wheel installs are
|
|
unaffected: the fallback only fires when the primary ``dist.files``
|
|
scan finds no manifest. Previously, editable-installed bundles
|
|
were silently dropped by ``lfx extension list`` and the registry,
|
|
even though the bundle pyproject already declared the
|
|
entry-point.
|
|
- **Reload CLI: ``--bundle`` is optional; ``--all`` is implemented.**
|
|
``lfx extension reload <ext_id>`` now resolves the bundle name
|
|
from local ``discover_all_extensions`` when ``--bundle`` is
|
|
omitted; explicit ``--bundle`` still wins for cases where the
|
|
local install is not visible to the running server.
|
|
``lfx extension reload --all`` iterates every locally-discovered
|
|
bundle, POSTs reload to each, and exits non-zero if any reload
|
|
fails (previously hard-errored as "not yet wired"). ``--all`` is
|
|
mutually exclusive with a positional id / ``--bundle`` (exit 2).
|
|
The HTTP wire contract (``POST /api/v1/extensions/{id}/bundles/
|
|
{name}/reload`` per-bundle) is unchanged; this is a CLI-only
|
|
surface change.
|
|
- **User-scoped extension events.** Bundle lifecycle events
|
|
(``bundle_reloaded``, ``bundle_reload_failed``, ``flow_migrated``,
|
|
``extension_error``) now publish to a per-user keyspace
|
|
(``user:<user_id>``) instead of the shared ``"global"`` bucket so
|
|
flow-migration and reload payloads cannot leak across users via the
|
|
poll endpoint.
|
|
- ``reload_bundle`` gains an optional keyword-only ``user_id: str |
|
|
None = None`` argument. When supplied, ``bundle_reloaded`` /
|
|
``bundle_reload_failed`` events are emitted to keyspace
|
|
``user:<user_id>``; ``None`` (CLI / authless dev) keeps the legacy
|
|
``"global"`` emission. Existing positional callers are unaffected.
|
|
- ``POST /api/v1/extensions/{id}/bundles/{name}/reload`` now resolves
|
|
the authenticated user and threads its id into ``reload_bundle``, so
|
|
every reload triggered via HTTP is published to that user's
|
|
keyspace. Wire contract (status codes, body shape) unchanged.
|
|
- ``GET /api/v1/extensions/events`` drops its client-supplied
|
|
``keyspace`` query parameter. The endpoint derives the keyspace
|
|
from the authenticated user server-side, so an authenticated client
|
|
can no longer poll another user's keyspace. Frontends that polled
|
|
without ``keyspace`` (the in-tree consumer) are unaffected;
|
|
third-party callers that explicitly passed ``keyspace=...`` will
|
|
now receive ``422`` from FastAPI's strict parameter validation.
|
|
- **Reload event payload aligned with ``ReloadResult``.** Both
|
|
``bundle_reloaded`` and ``bundle_reload_failed`` events now carry the
|
|
full ``ReloadResult.to_dict()`` envelope (``ok``, ``bundle``,
|
|
``reload_id``, ``components_added``, ``components_removed``,
|
|
``components_changed``, ``warnings``, ``errors``) instead of a
|
|
hand-rolled subset. Polling clients can now (a) detect body-only
|
|
edits via ``components_changed`` instead of mis-reporting them as
|
|
"no source changes detected", and (b) surface a failed reload's
|
|
``errors[0].message`` instead of degrading to a generic
|
|
"check server logs" fallback. HTTP response shape unchanged.
|
|
- **``GET /api/v1/extensions/events`` rejects ``keyspace`` explicitly.**
|
|
Previously the endpoint accepted but silently ignored any
|
|
client-supplied ``keyspace`` query parameter (server-derived from the
|
|
authenticated user since the prior entry). Silent drop masked client
|
|
bugs that assumed the value had effect. The route now returns ``422
|
|
Unprocessable Entity`` with a typed
|
|
``extension-events-keyspace-forbidden`` error envelope when the
|
|
parameter is present. ``extension-events-keyspace-forbidden`` is
|
|
added to ``ERROR_CODES`` (additive; codes-as-contract semantics
|
|
preserved). In-tree polling clients that never sent the parameter
|
|
are unaffected.
|
|
- **Manifest-less `lfx.bundles` discovery (metapackage split, 1.11).** A
|
|
distribution may declare `[project.entry-points."lfx.bundles"]` whose
|
|
value is an importable package; each immediate subdirectory is loaded as
|
|
one bundle at the `@official` slot with **no `extension.json`** (the
|
|
langchain-community model; these directories are not inputs to
|
|
`lfx extension validate` -- the validator requires an `extension.json` or
|
|
`[tool.langflow.extension]` manifest and reports `manifest-not-found`
|
|
without one).
|
|
`load_lfx_bundles_extensions` is exported from `lfx.extension` (additive).
|
|
Discovery precedence for cross-source bundle-name collisions becomes
|
|
`installed > seed > lfx_bundles > dev > inline` -- **manifest always
|
|
wins**, so a manifest-shipping `lfx-<provider>` shadows the same-named
|
|
provider in a metapackage with the existing `bundle-shadowed` warning
|
|
(graduation requires no lockstep release). A metapackage provider whose
|
|
name is already claimed by an installed or seed source is **never
|
|
imported** -- all `@official` sources share the
|
|
`_lfx_ext.official.<bundle>.*` sys.modules namespace, so importing the
|
|
losing copy would overwrite the winner's live modules; the skipped copy
|
|
carries the same typed `bundle-shadowed` diagnostic (on `errors`, matching
|
|
the resolver) so filtering by code never mixes severities. Namespace
|
|
packages are walked across **all portions**, and resolved roots are
|
|
deduplicated by path so duplicate declarations never self-shadow. New
|
|
warning-only codes added to `ERROR_CODES` (additive), one per discovery
|
|
failure mode and none of which abort startup: `bundle-discovery-malformed`
|
|
(declaration does not resolve to an importable package directory --
|
|
including a parent package whose `__init__` raises during `find_spec`),
|
|
`bundles-provider-name-invalid` (provider folder is not a valid bundle
|
|
name), `bundles-root-unreadable` (root cannot be enumerated), and
|
|
`duplicate-lfx-bundles-provider` (same provider name in more than one
|
|
root; first wins). Manifest-less bundles bypass the
|
|
`version-constraint-unsatisfied` API-version gate by construction (no
|
|
manifest to carry `lfx.compat`); install-time compatibility rides on the
|
|
metapackage's PEP 508 `lfx>=X,<Y` pin instead. Manifest-less records
|
|
register with `manifestless=True` (additive field on `LoadResult` /
|
|
`BundleRecord`) and are **not hot-reloadable**: the reload pipeline
|
|
refuses them with the new typed code `reload-manifestless-unsupported`
|
|
(additive in `ERROR_CODES`) instead of failing `manifest-not-found`;
|
|
pick up metapackage changes by upgrading the distribution and
|
|
restarting the process.
|
|
- **`import_mod` promoted to a stable public home.** The lazy-import helper
|
|
that bundle packages call from their `__getattr__`-based `__init__.py`
|
|
files moved from the internal `lfx.components._importing` to
|
|
`lfx.utils.lazy_import` and is now part of the BUNDLE_API surface (name,
|
|
signature, and semantics contract-stable). `lfx.components._importing`
|
|
re-exports it unchanged, so existing in-tree and third-party callers are
|
|
unaffected (additive).
|
|
- **Validator accepts inherited entry-points.** `validate_extension` no
|
|
longer emits `build-method-missing` for a Component subclass whose base
|
|
is a *derived* Component base (name ends with `Component`, e.g.
|
|
`LCVectorStoreComponent` / `LCToolComponent`): such classes inherit the
|
|
class-level `outputs` declaration and only override the output method,
|
|
which the AST-only check cannot resolve across modules. Direct
|
|
subclasses of the root `Component` keep the strict inline
|
|
`build`-or-`outputs` requirement. Surfaced by the partner graduations
|
|
(`lfx-datastax` et al.); previously-passing bundles are unaffected
|
|
(strictly fewer false positives).
|