diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 2b3ef601..4469bd24 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "name": "kbagent", - "version": "0.76.2", + "version": "0.77.0", "source": "./plugins/kbagent", "description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, call MCP tools, manage dev branches, and debug SQL in workspaces", "category": "development" diff --git a/CLAUDE.md b/CLAUDE.md index 1685b29d..5f5d298c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -602,6 +602,15 @@ kbagent kai ask --message "question" [--project NAME] kbagent kai chat --message "msg" [--chat-id ID] [--project NAME] kbagent kai chat-detail --chat-id ID [--project NAME] kbagent kai history [--project NAME] [--limit N] +# kai group DEPRECATED (0.77.0+): targets the LEGACY `kai-assistant` backend, which is frozen -- +# Linear AI-3388 was canceled, and product confirmed only the successor backend gets further work. +# That successor, `kai-agent` (Linear AI-3391), is a different API surface and is NOT wired into +# kbagent; retargeting is a separate future task. Behavior is UNCHANGED -- every subcommand still +# works against a master Storage token + `agent-chat` feature; each now warns on stderr in human +# mode and adds an additive "deprecation" key to the --json success payload (error envelopes carry +# none). The group and the serve `/kai/*` routes will be removed in a later minor. NO replacement +# in the interim: use `docs query` for documentation questions and the native commands +# (`storage tables`, `config detail`, `search`, `lineage show`, ...) for project data. kbagent transformation create --project NAME --name NAME (--sql 'SELECT ...' | --sql-file PATH) [--created-table NAME ...] [--component-id ID] [--description D] [--branch ID] [--dry-run] kbagent transformation show --project NAME --config-id ID [--component-id ID] [--branch ID] diff --git a/README.md b/README.md index 8d96c8bb..9eaea59c 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ kbagent workspace query --project prod --workspace-id WS_ID \ | **Project members & invitations** | `project invite` (single or `--from-csv` bulk with parallel workers), `project member-list / member-remove / member-set-role`, `project invitation-list / invitation-cancel`. Role whitelist enforced at the CLI layer; Manage API "already invited" treated as `noop` not error (since 0.29.0). | | **Lineage** | Column-level dependency analysis across projects. SQL/Python parsing, AI-enhanced detection, interactive web browser, Mermaid/HTML/ER export. | | **Semantic layer** | Define and manage a metastore semantic model per project — datasets, metrics, relationships, constraints, glossary. Validate (incl. `--deep`), export, diff two models/files, import/promote across projects, AI-assisted `build` from tables. `kbagent semantic-layer ...` (alias `sl`). | -| **Kai (AI Assistant)** | Ask Keboola's built-in AI questions about your project. One-shot or chat sessions with full MCP context. | +| **Kai (AI Assistant)** — **DEPRECATED (0.77.0)** | Ask Keboola's built-in AI questions about your project. One-shot or chat sessions with full MCP context. **Deprecated since 0.77.0**: targets the legacy `kai-assistant` backend, which is frozen; its successor `kai-agent` is not wired into kbagent. Still fully functional, but slated for removal in a later minor with no replacement — use `kbagent docs query` for documentation questions and the native commands for project data. | | **Encryption** | Encrypt secrets (`#password`, `#api_token`) via Keboola Encryption API. Works with sync push and MCP. | | **Permissions** | Firewall for AI agents: read-only, deny-writes, deny-destructive (session-only flags or persisted policy). Project pin + `KBAGENT_PROJECT` env override. Code-level enforcement, stable `ErrorCode` enum, not prompt tricks. | | **Auto-update** | Self-updates kbagent + `keboola-mcp-server` on every startup (since 0.30.1). "What's new" after each update. Full changelog via `kbagent changelog`. | @@ -241,7 +241,7 @@ kbagent workspace create | list | detail | delete | password | load | query | kbagent tool list | call kbagent sync init | pull | status | diff | push | branch-link | branch-unlink | branch-status kbagent schedule list | detail | find -kbagent kai ping | preflight | ask | chat | chat-detail | history +kbagent kai ping | preflight | ask | chat | chat-detail | history # DEPRECATED (0.77.0) kbagent encrypt values kbagent permissions list | show | set | reset | check kbagent agent list | show | create | update | delete | run | runs | run-detail | run-events diff --git a/docs/build-your-own-client.md b/docs/build-your-own-client.md index 42ac41b3..38511845 100644 --- a/docs/build-your-own-client.md +++ b/docs/build-your-own-client.md @@ -101,7 +101,7 @@ without first knowing the token. Everything else needs auth. /sharing kbagent sharing (list, share, link, unshare, unlink, edges) /data-apps kbagent data-app (CRUD, deploy, start/stop, secrets-*, validate-repo) /mcp kbagent tool (list, call) -- multi-project MCP fan-out -/kai kbagent kai (ping, ask, chat, history, preflight, chat-detail) +/kai kbagent kai (ping, ask, chat, history, preflight, chat-detail) -- DEPRECATED 0.77.0 /encrypt kbagent encrypt values /search kbagent search QUERY /org kbagent org setup diff --git a/docs/error-codes.md b/docs/error-codes.md index b23180d5..185ac871 100644 --- a/docs/error-codes.md +++ b/docs/error-codes.md @@ -109,6 +109,10 @@ of `ErrorCode` in `src/keboola_agent_cli/errors.py`. ### KAI (AI Service) +Both codes remain valid and unchanged, but the `kbagent kai` command group that +raises them is **deprecated since 0.77.0** (legacy `kai-assistant` backend +frozen; removal planned, no replacement). + | Code | Description | |---|---| | `KAI_ERROR` | AI Service request failed | diff --git a/docs/web-server.md b/docs/web-server.md index e20e3ae2..381f4b0f 100644 --- a/docs/web-server.md +++ b/docs/web-server.md @@ -90,7 +90,7 @@ else lives here, with their own agents that know their projects. | `/sharing` | share/unshare/link/unlink | `kbagent sharing *` | | `/data-apps` | CRUD, deploy, start/stop, **secrets** | `kbagent data-app *` | | `/mcp/tools` | list, schema, **call** (multi-project) | `kbagent tool *` | -| `/kai/*` | ping, ask, chat, history | `kbagent kai *` | +| `/kai/*` **(DEPRECATED 0.77.0)** | ping, ask, chat, history | `kbagent kai *` (deprecated -- legacy `kai-assistant` backend frozen; removal planned, no replacement) | | `/encrypt` | encrypt secret values | `kbagent encrypt values` | | `/search` | textual + config-based cross-project search | `kbagent search` | | `/org` `/members` | bulk org setup, invite, remove, role (manage token) | `kbagent org|project member-* *` | diff --git a/plugins/kbagent/.claude-plugin/plugin.json b/plugins/kbagent/.claude-plugin/plugin.json index 6fdc5f54..55ac266e 100644 --- a/plugins/kbagent/.claude-plugin/plugin.json +++ b/plugins/kbagent/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "kbagent", - "version": "0.76.2", + "version": "0.77.0", "description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, call MCP tools, manage dev branches, and debug SQL in workspaces", "author": { "name": "Keboola", diff --git a/plugins/kbagent/agents/keboola-expert.md b/plugins/kbagent/agents/keboola-expert.md index 6019fd14..e0e6633f 100644 --- a/plugins/kbagent/agents/keboola-expert.md +++ b/plugins/kbagent/agents/keboola-expert.md @@ -93,7 +93,7 @@ a critical failure. | Search items by name across projects | `kbagent search QUERY [--project P] [--type table\|bucket\|config\|flow\|data-app\|transformation] [--search-type textual\|config-based] [--limit N] [--regex]` (0.30.0+); `--regex` (0.67.0+) opts into case-insensitive whole-term regex on entity names — `report` does NOT match `monthly_report`, write `.*report.*`; textual mode marks `table` results matched via a column name with `matched_columns` in `--json` (0.67.0+; always present, `[]` when the name itself matched; always `[]` under `--regex` — regex never matches column names) | `tool call search_tables` / `tool call search_configurations` (one resource-type per call) | chaining multiple `tool call` for different types; `--regex` with `--search-type config-based` (exit 2); `--regex` below 0.67.0 | | Search config JSON bodies | `kbagent search QUERY --search-type config-based [--project P]` (0.30.0+) | `kbagent config search --query Q` (config-body only, no tables/buckets) | repeated `tool call get_config` to grep locally | | Browse configs (exploration) | `kbagent config list` / `kbagent config search --query Q` | `tool call list_configs` | full-project pull via MCP just to grep locally | -| Answer a Keboola-documentation question ("how do I configure incremental loading?") | `kbagent docs query "QUESTION" [--project P]` (0.73.0+; AI-service RAG, returns answer + source URLs) | -- | `kai ask` (project-scoped assistant, not docs Q&A); `tool call docs_query` (deprecated 0.74.0) | +| Answer a Keboola-documentation question ("how do I configure incremental loading?") | `kbagent docs query "QUESTION" [--project P]` (0.73.0+; AI-service RAG, returns answer + source URLs) | -- | `kai ask` (**the whole `kai` group is DEPRECATED 0.77.0** -- legacy `kai-assistant` backend frozen, AI-3388 canceled, successor `kai-agent` not wired into kbagent, removal planned, no replacement); `tool call docs_query` (deprecated 0.74.0) | | Fetch a specific config | `kbagent config detail --project P --component-id C --config-id K --json` | `tool call get_config` | re-using an earlier JSON dump | | Override the auto-derived output bucket on a config | `kbagent config set-default-bucket --bucket in.c-name` (0.26.0+) -- read-modify-write of `storage.output.default_bucket`, preserves siblings; `--clear` removes it | `kbagent config update --set 'storage.output.default_bucket=in.c-name'` (works pre-0.26.0 but not discoverable) | editing the raw JSON in the UI; full-config replace with `--configuration` (wipes other storage keys) | | Cross-project migration | `kbagent sync pull` + edit files locally + `kbagent sync push --dry-run` | -- | repeated `tool call` loops, one per resource | diff --git a/plugins/kbagent/skills/kbagent/SKILL.md b/plugins/kbagent/skills/kbagent/SKILL.md index 84e64dbe..097e63d5 100644 --- a/plugins/kbagent/skills/kbagent/SKILL.md +++ b/plugins/kbagent/skills/kbagent/SKILL.md @@ -188,12 +188,12 @@ When working inside a git repository or project directory, run `kbagent init` (o | Show what's in a cached lineage graph | `kbagent lineage info --load LOAD` | | Query upstream/downstream dependencies from a cached lineage graph | `kbagent lineage show --load LOAD` | | Start a local web server with interactive lineage browser | `kbagent lineage server --load LOAD` | -| Check Kai server health and MCP connection status | `kbagent kai ping` | -| Ask Kai a one-shot question and get the full response | `kbagent kai ask --message MESSAGE` | -| Send a message to Kai in a chat session | `kbagent kai chat --message MESSAGE` | -| Check whether the configured token can use Kai (master token + AI Agent Chat) | `kbagent kai preflight` | -| Fetch the full message history of a single Kai chat | `kbagent kai chat-detail --chat-id CHAT-ID` | -| List recent Kai chat sessions | `kbagent kai history` | +| (DEPRECATED) Check Kai server health and MCP connection status | `kbagent kai ping` | +| (DEPRECATED) Ask Kai a one-shot question and get the full response | `kbagent kai ask --message MESSAGE` | +| (DEPRECATED) Send a message to Kai in a chat session | `kbagent kai chat --message MESSAGE` | +| (DEPRECATED) Check whether the configured token can use Kai | `kbagent kai preflight` | +| (DEPRECATED) Fetch the full message history of a single Kai chat | `kbagent kai chat-detail --chat-id CHAT-ID` | +| (DEPRECATED) List recent Kai chat sessions | `kbagent kai history` | | Ask the Keboola documentation a natural language question | `kbagent docs query ` | | Create a SQL transformation from a SQL script | `kbagent transformation create --name NAME` | | Show a SQL transformation's block/code tree with positional IDs | `kbagent transformation show --config-id CONFIG-ID` | diff --git a/plugins/kbagent/skills/kbagent/references/commands-reference.md b/plugins/kbagent/skills/kbagent/references/commands-reference.md index 3c8fc538..8523fabc 100644 --- a/plugins/kbagent/skills/kbagent/references/commands-reference.md +++ b/plugins/kbagent/skills/kbagent/references/commands-reference.md @@ -220,9 +220,13 @@ Ports the MCP `create_sql_transformation` / `update_sql_transformation` tools (# - `transformation edit --project NAME --config-id ID --change-description TEXT (--op JSON ... | --op-file ops.json) [--storage JSON|@file|-] [--component-id ID] [--branch ID] [--dry-run]` -- 9 ops (`add_block`, `remove_block`, `rename_block`, `add_code`, `remove_code`, `rename_code`, `set_code`, `add_script`, `str_replace`) applied sequentially against batch-start ids. `--storage` REPLACES `configuration.storage` wholesale (include ALL mappings you want to keep). Unknown ids error with the list of valid ids. ## Documentation Q&A (since v0.73.0) -- `docs query "QUESTION" [--project NAME]` -- natural-language answer from the Keboola documentation via the AI Service (server-side RAG, no local corpus). Returns answer text + source URLs; `--json` emits `{query, text, source_urls}`. Unlike `kai ask` it does NOT see project data, works with any token (no master-token / feature-flag requirement), and is the right tool for "how do I ..." questions. Ports the `docs_query` MCP tool. +- `docs query "QUESTION" [--project NAME]` -- natural-language answer from the Keboola documentation via the AI Service (server-side RAG, no local corpus). Returns answer text + source URLs; `--json` emits `{query, text, source_urls}`. Unlike `kai ask` (DEPRECATED since v0.77.0) it does NOT see project data, works with any token (no master-token / feature-flag requirement), and is the right tool for "how do I ..." questions. Ports the `docs_query` MCP tool. **Prefer this over `kai ask` for documentation questions.** + +## Kai (Keboola AI Assistant) -- DEPRECATED since v0.77.0 +**DEPRECATED -- do not recommend this group for new work.** `kbagent kai *` talks to the **legacy `kai-assistant` backend**, which is frozen: Linear AI-3388 was canceled, and product confirmed that only the successor backend will receive further work. That successor, **`kai-agent`** (Linear AI-3391), is a different API surface that is **not wired into kbagent**. The group will be **removed in a later minor** and there is **no replacement in the interim** -- use `docs query` for documentation questions and native commands (`storage tables`, `config detail`, `search`, `lineage show`, ...) for project data. + +Behavior is unchanged in 0.77.0: every subcommand still works exactly as before. Each now prints a deprecation warning on **stderr** in human mode and adds an additive `deprecation` key to the `--json` **success** payload (error envelopes carry no such key; no existing key, exit code, or API call changed). -## Kai (Keboola AI Assistant) Requires the project to be added with its **master ('owner') Storage API token** -- the auto-generated owner token, not a custom one. Custom tokens cannot access Kai. Also requires the `agent-chat` feature flag on the project. Use `kai preflight` to verify both conditions without raising. - `kai ping [--project NAME]` -- check Kai server health and MCP connection status. Fails with KAI_NOT_ENABLED if the agent-chat feature is missing or the token is not a master token - `kai preflight [--project NAME]` -- inspect token readiness WITHOUT raising. Returns `{ok, is_master_token, has_agent_chat_feature, token_description, error}`. Use this in UIs and automation pre-flight checks instead of `ping` diff --git a/plugins/kbagent/skills/kbagent/references/gotchas.md b/plugins/kbagent/skills/kbagent/references/gotchas.md index 354a05fe..c1e79518 100644 --- a/plugins/kbagent/skills/kbagent/references/gotchas.md +++ b/plugins/kbagent/skills/kbagent/references/gotchas.md @@ -11,6 +11,44 @@ Versioning convention: behavior; the inline `(updated vX.Y.Z)` records when the refinement landed. --> +## The whole `kai` group is DEPRECATED -- legacy backend frozen, no replacement (since v0.77.0) + +- **All six `kbagent kai *` subcommands (`ping`, `preflight`, `ask`, `chat`, + `chat-detail`, `history`) are on a removal track.** They talk to the + **legacy `kai-assistant` backend** -- the service registered under that id + in `GET /v2/storage` -- which is frozen. Linear AI-3388 was **canceled**, + and product confirmed that only the successor backend will receive further + work. +- **The successor is `kai-agent`** (`kai-agent.{stack_suffix}`, a Hono + server), tracked in Linear AI-3391. It is a **different API surface** and + is **NOT wired into kbagent** -- retargeting is a separate, future task. + Do not assume `kbagent kai` will migrate to it. +- **There is NO replacement in the interim.** For "how do I ..." + documentation questions use `kbagent docs query` (AI Service RAG, works + with any token, does **not** see project data). For project data use the + native commands (`storage tables`, `storage table-detail`, `config list`, + `config detail`, `search`, `job detail`, `lineage show`) or the MCP + integration. Do not recommend `kai ask` as a project-data oracle. +- **Behavior is UNCHANGED in 0.77.0 -- deprecated is not removed.** Every + subcommand still hits the same endpoints with the same exit codes. The only + additions are the deprecation surfaces, which follow the `tool`-group + pattern exactly: a yellow `Warning:` on **stderr** in human mode (stdout + stays byte-clean for piping) and an additive `deprecation` string key on + the `--json` **success** payload. **Error envelopes carry no `deprecation` + key** -- a failing command exits before the key is injected. No existing + key was renamed or removed, so a script that parses `--json` today keeps + working. +- **The token requirements have not relaxed and never will**: a project must + still be registered with its **master ('owner') Storage API token** (the + auto-generated one, not a custom token) *and* carry the `agent-chat` + feature flag, or every call fails `KAI_NOT_ENABLED`. Use `kai preflight` + to check both without raising. Because the backend is frozen, this gate is + permanent. +- **The `/kai/*` REST routes on `kbagent serve` are deprecated too**, for the + same reason. They still respond this release. See also the + "Web UI `Kai Chat` is gone" entry below -- its "what stays" list now means + "stays until removal". + ## MCP passthrough is DEPRECATED; parity map + canary (since v0.74.0) - **`tool call` / `tool list` / `agent --type mcp_tool` are on a removal @@ -59,7 +97,9 @@ Versioning convention: reports `schema_version`). - **`docs query` vs `kai ask`**: `docs query` is documentation-only RAG (any token, no feature flag, no project data); `kai ask` sees project data but - needs the master token + `agent-chat` feature. + needs the master token + `agent-chat` feature. **`kai ask` is DEPRECATED + since v0.77.0** -- use `docs query` for documentation and native commands + for project data. ## `token` group mints/rotates/revokes SCOPED Storage tokens; secret shown ONCE (since v0.66.0) @@ -630,6 +670,10 @@ a kbagent co-pilot. per-project session-state API. Only the dashboard UI tile + left nav entry was swapped. `kbagent kai ping|preflight|ask|chat` CLI commands are unchanged. +- **Caveat added in v0.77.0:** the whole `kai` CLI group *and* the + `/kai/*` REST routes are now **DEPRECATED** (see the next entry). + They still work, but "what stays" now means "stays until removal in a + later minor" -- do not build anything new on them. **Implication for AI agents:** diff --git a/plugins/kbagent/skills/kbagent/references/kai-workflow.md b/plugins/kbagent/skills/kbagent/references/kai-workflow.md index 3fc53a32..c4471eca 100644 --- a/plugins/kbagent/skills/kbagent/references/kai-workflow.md +++ b/plugins/kbagent/skills/kbagent/references/kai-workflow.md @@ -1,21 +1,49 @@ -# Kai (Keboola AI Assistant) Workflow +# Kai (Keboola AI Assistant) Workflow -- DEPRECATED + +> **DEPRECATED (since v0.77.0). Do not recommend the `kai` group for new work.** +> +> `kbagent kai *` talks to the **legacy `kai-assistant` backend**, which is +> frozen: Linear AI-3388 was **canceled**, and product confirmed that only the +> successor backend will receive further work. That successor is **`kai-agent`** +> (Linear AI-3391) — a different API surface that is **not wired into kbagent**. +> Retargeting is a separate, future task. +> +> **The group will be removed in a later minor, and there is no replacement in +> the interim.** Behavior is unchanged in 0.77.0 — every subcommand still works +> exactly as before against a master Storage token; each one now emits a +> deprecation warning on stderr (human mode) and an additive `deprecation` key +> in the `--json` success payload. +> +> **What to use instead today:** +> - `kbagent docs query "QUESTION"` for "how do I ..." documentation questions +> (AI Service RAG, works with any token, does **not** see project data). +> - Native commands for project data: `storage tables`, `storage table-detail`, +> `config list`, `config detail`, `search`, `job detail`, `lineage show`. +> - The MCP integration for agentic exploration. +> +> This file is kept only so that existing `kai` users can still read the +> mechanics. Everything below describes behavior that is deprecated. Kai is Keboola's cloud AI assistant with MCP access to project data. kbagent bridges Claude Code (local) to Kai (cloud) for Keboola-specific questions. -> **BETA**: Kai commands require a project with the `agent-chat` feature enabled. -> Token authentication requirements are being finalized. +> **Token requirement**: Kai commands require a project added with its MASTER +> Storage API token and the `agent-chat` feature enabled. Custom Storage API +> tokens cannot access Kai. ## When to use Kai vs local tools +`kai` is deprecated, so the honest answer is "prefer the right-hand column". +The historical guidance was: + | Situation | Use | |-----------|-----| -| Need project-specific context (tables, configs, lineage) | `kbagent kai ask` | +| Need project-specific context (tables, configs, lineage) | ~~`kbagent kai ask`~~ (DEPRECATED) -> `storage tables`, `config detail`, `lineage show` | | Simple data listing (buckets, tables, configs) | `kbagent config list`, `kbagent storage tables` | -| Need Keboola domain knowledge (component behavior, best practices) | `kbagent kai ask` | +| Need Keboola domain knowledge (component behavior, best practices) | ~~`kbagent kai ask`~~ (DEPRECATED) -> `kbagent docs query` | | Need to modify data (upload, create, delete) | Direct CLI commands | -## Quick start +## Quick start (deprecated -- still functional) ```bash # Check if Kai is available @@ -42,7 +70,10 @@ Check via: `kbagent --json kai ping --project ALIAS` — exit code 0 means Kai i ## JSON output -All kai commands support `--json` for structured output: +All kai commands support `--json` for structured output. Since v0.77.0 every +success payload also carries an additive `deprecation` string key (existing +keys are untouched; error envelopes carry no such key), so a script can detect +the deprecation programmatically: ```bash # Ping @@ -58,7 +89,10 @@ kbagent --json kai history --project my-project # {"status": "ok", "data": {"chats": [...], "has_more": false}} ``` -## Common patterns for Claude Code +## Common patterns for Claude Code (DEPRECATED -- do not suggest these) + +These are kept for reference only. Use `kbagent docs query` for documentation +questions and native commands for project data. ```bash # Use kai ask as a Keboola knowledge oracle diff --git a/pyproject.toml b/pyproject.toml index c3dc599e..f38557b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "keboola-cli" -version = "0.76.2" +version = "0.77.0" description = "AI-friendly CLI for managing Keboola projects" readme = "README.md" requires-python = ">=3.12" diff --git a/src/keboola_agent_cli/changelog.py b/src/keboola_agent_cli/changelog.py index 2c3b55c7..4a55dcdd 100644 --- a/src/keboola_agent_cli/changelog.py +++ b/src/keboola_agent_cli/changelog.py @@ -24,6 +24,27 @@ # Ordered newest-first. Each value is a list of brief one-line descriptions. CHANGELOG: dict[str, list[str]] = { + "0.77.0": [ + "Change: the whole `kbagent kai` command group is now DEPRECATED -- it still works " + "exactly as before, but it is on a removal track. It talks to the legacy " + "`kai-assistant` backend, which is frozen: Linear AI-3388 was canceled, and " + "product confirmed that only the successor backend will receive further work. That " + "successor, `kai-agent` (Linear AI-3391), is a different API surface and is not " + "wired into kbagent; retargeting it is a separate, future task. All six " + "subcommands (`ping`, " + "`preflight`, `ask`, `chat`, `chat-detail`, `history`) and the `serve` `/kai/*` " + "routes keep working against a master Storage token with the `agent-chat` feature -- " + "nothing was removed and no behavior changed. There is no replacement in the " + "interim: use `kbagent docs query` for documentation questions and the native " + "commands (`storage tables`, `config detail`, `search`, `lineage show`, ...) for " + "project data.", + "UX: each `kai` subcommand now surfaces the deprecation the same way the `tool` " + "group does -- a yellow warning on stderr in human mode (stdout stays byte-clean " + "for piping) and an additive `deprecation` string key on the `--json` success " + "payload. Error envelopes carry no `deprecation` key, and no existing key, exit " + "code, or API call changed, so scripts that parse `kbagent --json kai ...` today " + "keep working unchanged.", + ], "0.76.2": [ "Fix (#528, #530): self-update no longer risks leaving the running Windows uv tool " "environment partially upgraded. kbagent now completes all network checks and command " diff --git a/src/keboola_agent_cli/commands/context.py b/src/keboola_agent_cli/commands/context.py index 57a3bab6..52c29330 100644 --- a/src/keboola_agent_cli/commands/context.py +++ b/src/keboola_agent_cli/commands/context.py @@ -1292,7 +1292,25 @@ --input accepts: inline JSON, @file.json (from file), or - (from stdin). --branch is a CLI flag (NOT a tool input param). Do not pass branch_id in --input. -### Kai -- Keboola AI Assistant (BETA) +### Kai -- Keboola AI Assistant (DEPRECATED since v0.77.0) + + DEPRECATED -- do NOT recommend this group for new work. It talks to the + LEGACY `kai-assistant` backend, which is frozen: Linear AI-3388 was + canceled, and product confirmed that only the successor backend will + receive further work. That successor, `kai-agent` (Linear AI-3391), is a + different API surface and is NOT wired into kbagent. The group will be + removed in a later minor. + + There is NO replacement in the interim. For "how do I ..." documentation + questions use `kbagent docs query` (AI Service RAG, works with any token, + but does not see project data). For project-data questions use the native + commands (`storage tables`, `config detail`, `search`, `lineage show`, ...) + or the MCP integration. + + Behavior is UNCHANGED in this release: every subcommand still works exactly + as before. Each one now prints a deprecation warning on stderr (human mode) + and adds an additive `deprecation` key to the `--json` success payload -- + no existing key, exit code, or API call changed. Requires the project to be added with its MASTER Storage API token (the auto-generated 'owner' token, not a custom one) and the 'AI Agent Chat' @@ -1358,8 +1376,9 @@ Answer a natural-language question from the Keboola documentation via the AI Service (server-side RAG; no local corpus). Returns the answer text plus source URLs. --json emits {{query, text, source_urls}}. Unlike - `kai ask` this does NOT see project data -- it is documentation-only, - works with any token, and is the right tool for "how do I ..." questions. + `kai ask` (DEPRECATED since v0.77.0) this does NOT see project data -- it + is documentation-only, works with any token, and is the right tool for + "how do I ..." questions. Prefer this over `kai ask` for documentation. ### Developer Portal (since v0.49.0) diff --git a/src/keboola_agent_cli/commands/kai.py b/src/keboola_agent_cli/commands/kai.py index 34345b4c..4d5a836c 100644 --- a/src/keboola_agent_cli/commands/kai.py +++ b/src/keboola_agent_cli/commands/kai.py @@ -2,11 +2,33 @@ Bridges Claude Code (local) to Kai (cloud) via kbagent CLI. Kai has MCP access to project data and can answer Keboola-specific questions. + +The whole ``kai`` group is DEPRECATED as of 0.77.0. It targets the *legacy* +``kai-assistant`` backend (the service registered under that id in +``GET /v2/storage``), which is frozen: Linear AI-3388 was canceled, and +product confirmed that only the successor backend will receive further work. +That successor, ``kai-agent`` (Linear AI-3391), is a different API surface +that is not wired into kbagent -- retargeting is a separate, future task. + +Nothing changes behaviorally in this release: every subcommand keeps working +exactly as before on a master Storage token. Removal is planned for a later +minor and there is **no replacement in the interim** -- for documentation +questions use ``kbagent docs query`` (AI Service RAG, no project data). + +All six subcommands surface the deprecation the same way the ``tool`` group +does (see ``commands/tool.py``): human mode warns on stderr, JSON mode adds +an additive ``deprecation`` key to the success payload. No existing key, exit +code, or API call changes. """ +from collections.abc import Callable +from typing import Any + import typer +from rich.console import Console from ..errors import ConfigError, ErrorCode, KeboolaApiError +from ..output import OutputFormatter from ._helpers import ( check_cli_permission, get_formatter, @@ -14,9 +36,22 @@ map_error_to_exit_code, ) +# Group-wide deprecation banner (0.77.0). Surfaced by every subcommand. +KAI_DEPRECATION = ( + "The `kai` group is deprecated: it targets the legacy kai-assistant " + "backend, which is frozen (Linear AI-3388 canceled), and its successor " + "kai-agent (AI-3391) is not wired into kbagent. The group still works " + "against a master Storage token but will be removed in a later minor -- " + "there is no replacement in the interim." +) + kai_app = typer.Typer( help=( - "(BETA) Keboola AI Assistant (Kai) — ask questions about your project.\n\n" + "(DEPRECATED) Keboola AI Assistant (Kai) — ask questions about your project.\n\n" + "DEPRECATED (0.77.0): this group talks to the legacy 'kai-assistant' " + "backend, which is frozen; its successor 'kai-agent' is not wired into " + "kbagent yet. Still fully functional, but slated for removal in a later " + "minor with no replacement in the interim.\n\n" "Requires a master Storage API token (the auto-generated 'owner' token, " "not a custom one) with the 'AI Agent Chat' feature flag enabled on the " "project. Custom Storage API tokens cannot be used with Kai." @@ -24,6 +59,24 @@ ) +def _output_deprecated( + formatter: OutputFormatter, + result: dict[str, Any], + human_formatter: Callable[[Console, Any], object], +) -> None: + """Emit a Kai success payload carrying the group deprecation notice. + + Mirrors ``commands/tool.py``: in JSON mode the banner is an *additive* + ``deprecation`` key on the success payload -- error envelopes never carry + it, because a failing command exits before reaching this function. The + human-mode counterpart is the ``formatter.warning`` call each subcommand + makes up front (stderr only, so stdout stays byte-clean for piping). + """ + if formatter.json_mode: + result["deprecation"] = KAI_DEPRECATION + formatter.output(result, human_formatter) + + @kai_app.callback(invoke_without_command=True) def _kai_permission_check(ctx: typer.Context) -> None: check_cli_permission(ctx, "kai") @@ -38,10 +91,21 @@ def kai_ping( help="Project alias (uses default if omitted).", ), ) -> None: - """Check Kai server health and MCP connection status.""" + """(DEPRECATED) Check Kai server health and MCP connection status. + + DEPRECATED (0.77.0): the legacy kai-assistant backend this targets is + frozen (Linear AI-3388 canceled; successor kai-agent per AI-3391 is not + wired into kbagent). Behavior is unchanged, but the group will be removed + in a later minor and there is no replacement in the interim. + """ formatter = get_formatter(ctx) service = get_service(ctx, "kai_service") + # Deprecation surface (0.77.0): stderr warning in human mode (never + # pollutes stdout); a no-op in JSON mode, where _output_deprecated + # injects the banner into the success envelope instead. + formatter.warning(KAI_DEPRECATION) + try: alias = service.resolve_alias(project) result = service.ping(alias) @@ -59,7 +123,7 @@ def _human(console, data): console.print(f" Server: {data['server_version']}") console.print(f" MCP connection: {data['mcp_status']}") - formatter.output(result, _human) + _output_deprecated(formatter, result, _human) @kai_app.command("ask") @@ -77,7 +141,12 @@ def kai_ask( help="Project alias (uses default if omitted).", ), ) -> None: - """Ask Kai a one-shot question and get the full response. + """(DEPRECATED) Ask Kai a one-shot question and get the full response. + + DEPRECATED (0.77.0): the legacy kai-assistant backend this targets is + frozen (Linear AI-3388 canceled; successor kai-agent per AI-3391 is not + wired into kbagent). Behavior is unchanged, but the group will be removed + in a later minor and there is no replacement in the interim. Kai has access to your project's data, configurations, and lineage via MCP tools. Use this for Keboola-specific questions that require @@ -86,6 +155,11 @@ def kai_ask( formatter = get_formatter(ctx) service = get_service(ctx, "kai_service") + # Deprecation surface (0.77.0): stderr warning in human mode (never + # pollutes stdout); a no-op in JSON mode, where _output_deprecated + # injects the banner into the success envelope instead. + formatter.warning(KAI_DEPRECATION) + try: alias = service.resolve_alias(project) result = service.ask(alias, message) @@ -99,7 +173,7 @@ def kai_ask( def _human(console, data): console.print(data["response"]) - formatter.output(result, _human) + _output_deprecated(formatter, result, _human) @kai_app.command("chat") @@ -122,7 +196,12 @@ def kai_chat( help="Project alias (uses default if omitted).", ), ) -> None: - """Send a message to Kai in a chat session. + """(DEPRECATED) Send a message to Kai in a chat session. + + DEPRECATED (0.77.0): the legacy kai-assistant backend this targets is + frozen (Linear AI-3388 canceled; successor kai-agent per AI-3391 is not + wired into kbagent). Behavior is unchanged, but the group will be removed + in a later minor and there is no replacement in the interim. Use --chat-id to continue a previous conversation. Without --chat-id, starts a new chat. @@ -130,6 +209,11 @@ def kai_chat( formatter = get_formatter(ctx) service = get_service(ctx, "kai_service") + # Deprecation surface (0.77.0): stderr warning in human mode (never + # pollutes stdout); a no-op in JSON mode, where _output_deprecated + # injects the banner into the success envelope instead. + formatter.warning(KAI_DEPRECATION) + try: alias = service.resolve_alias(project) result = service.chat_message(alias, message, chat_id=chat_id) @@ -144,7 +228,7 @@ def _human(console, data): console.print(data["response"]) console.print(f"\n[dim]Chat ID: {data['chat_id']}[/dim]") - formatter.output(result, _human) + _output_deprecated(formatter, result, _human) @kai_app.command("preflight") @@ -156,7 +240,14 @@ def kai_preflight( help="Project alias (uses default if omitted).", ), ) -> None: - """Check whether the configured token can use Kai (master token + AI Agent Chat). + """(DEPRECATED) Check whether the configured token can use Kai. + + DEPRECATED (0.77.0): the legacy kai-assistant backend this targets is + frozen (Linear AI-3388 canceled; successor kai-agent per AI-3391 is not + wired into kbagent). Behavior is unchanged, but the group will be removed + in a later minor and there is no replacement in the interim. + + Checks for a master token with the AI Agent Chat feature flag. Inspects /v2/storage/tokens/verify and returns a structured readiness payload WITHOUT raising on failure — unlike ``ping``/``ask``/``chat`` @@ -167,6 +258,11 @@ def kai_preflight( formatter = get_formatter(ctx) service = get_service(ctx, "kai_service") + # Deprecation surface (0.77.0): stderr warning in human mode (never + # pollutes stdout); a no-op in JSON mode, where _output_deprecated + # injects the banner into the success envelope instead. + formatter.warning(KAI_DEPRECATION) + try: alias = service.resolve_alias(project) result = service.preflight(alias) @@ -192,7 +288,7 @@ def _human(console, data): if data["error"]: console.print(f" [red]Reason:[/red] {data['error']}") - formatter.output(result, _human) + _output_deprecated(formatter, result, _human) @kai_app.command("chat-detail") @@ -209,7 +305,12 @@ def kai_chat_detail( help="Project alias (uses default if omitted).", ), ) -> None: - """Fetch the full message history of a single Kai chat. + """(DEPRECATED) Fetch the full message history of a single Kai chat. + + DEPRECATED (0.77.0): the legacy kai-assistant backend this targets is + frozen (Linear AI-3388 canceled; successor kai-agent per AI-3391 is not + wired into kbagent). Behavior is unchanged, but the group will be removed + in a later minor and there is no replacement in the interim. Use this to restore a previous conversation when continuing it with `kai chat --chat-id ID`, or to export a transcript for offline review. @@ -220,6 +321,11 @@ def kai_chat_detail( formatter = get_formatter(ctx) service = get_service(ctx, "kai_service") + # Deprecation surface (0.77.0): stderr warning in human mode (never + # pollutes stdout); a no-op in JSON mode, where _output_deprecated + # injects the banner into the success envelope instead. + formatter.warning(KAI_DEPRECATION) + try: alias = service.resolve_alias(project) result = service.get_chat_detail(alias, chat_id) @@ -245,7 +351,7 @@ def _human(console, data): console.print(f"\n[bold {style}]{role}:[/bold {style}]") console.print(msg["content"]) - formatter.output(result, _human) + _output_deprecated(formatter, result, _human) @kai_app.command("history") @@ -263,10 +369,21 @@ def kai_history( help="Maximum number of chats to return.", ), ) -> None: - """List recent Kai chat sessions.""" + """(DEPRECATED) List recent Kai chat sessions. + + DEPRECATED (0.77.0): the legacy kai-assistant backend this targets is + frozen (Linear AI-3388 canceled; successor kai-agent per AI-3391 is not + wired into kbagent). Behavior is unchanged, but the group will be removed + in a later minor and there is no replacement in the interim. + """ formatter = get_formatter(ctx) service = get_service(ctx, "kai_service") + # Deprecation surface (0.77.0): stderr warning in human mode (never + # pollutes stdout); a no-op in JSON mode, where _output_deprecated + # injects the banner into the success envelope instead. + formatter.warning(KAI_DEPRECATION) + try: alias = service.resolve_alias(project) result = service.get_history(alias, limit=limit) @@ -298,4 +415,4 @@ def _human(console, data): if data["has_more"]: console.print("[dim]More chats available. Use --limit to see more.[/dim]") - formatter.output(result, _human) + _output_deprecated(formatter, result, _human) diff --git a/tests/test_kai_cli.py b/tests/test_kai_cli.py index a8626f01..adafb72c 100644 --- a/tests/test_kai_cli.py +++ b/tests/test_kai_cli.py @@ -2,6 +2,10 @@ Tests the `kbagent kai` subcommands: ping, ask, chat, history. Each command is tested in both JSON and human output modes, plus error cases. + +The group is DEPRECATED since 0.77.0 but deliberately UNCHANGED behaviorally -- +every test below the deprecation class pins the pre-0.77.0 contract and must +keep passing. ``TestKaiDeprecation`` adds the new surfacing contract on top. """ import json @@ -899,3 +903,296 @@ def test_kai_ping_config_error(self, tmp_config_dir: Path) -> None: output = json.loads(result.output) assert output["status"] == "error" assert "CONFIG_ERROR" in output["error"]["code"] + + +class TestKaiDeprecation: + """The whole `kai` group is deprecated (0.77.0) but fully functional. + + Pins the surfacing contract, which mirrors the ``tool`` group (see + ``tests/test_mcp_deprecation_warnings.py``): + + - human mode: a yellow ``Warning:`` on STDERR only -- stdout stays + byte-clean so ``kai ask | ...`` keeps working, + - JSON mode: an *additive* ``deprecation`` key inside the success + envelope's ``data`` payload; no existing key renamed or removed, + - error paths carry no ``deprecation`` key and keep their exit code. + """ + + @staticmethod + def _flat(text: str) -> str: + """Collapse whitespace so Rich line-wrapping cannot break asserts.""" + return " ".join(_strip_ansi(text).split()) + + def test_ping_human_warns_on_stderr_stdout_clean(self, tmp_config_dir: Path) -> None: + """kai ping human mode: banner on stderr, result on a clean stdout.""" + setup_single_project(tmp_config_dir) + + mock_service = MagicMock(spec=KaiService) + mock_service.resolve_alias.return_value = "prod" + mock_service.ping.return_value = { + "project_alias": "prod", + "timestamp": "2025-01-15T10:30:00+00:00", + "app_name": "kai-api", + "app_version": "1.2.3", + "server_version": "2.0.0", + "mcp_status": "connected", + } + + with pytest.MonkeyPatch.context() as mp: + mp.setattr( + "keboola_agent_cli.commands.kai.get_service", + lambda ctx, name: mock_service, + ) + + result = runner.invoke( + app, + ["--config-dir", str(tmp_config_dir), "kai", "ping", "--project", "prod"], + ) + + assert result.exit_code == 0, result.output + stderr = self._flat(result.stderr) + assert "Warning:" in stderr + assert "`kai` group is deprecated" in stderr + assert "legacy kai-assistant" in stderr + assert "no replacement in the interim" in stderr + # stdout carries only the result -- no deprecation noise + stdout = self._flat(result.stdout) + assert "Kai is alive" in stdout + assert "deprecated" not in stdout + + def test_ping_json_payload_gains_deprecation_key(self, tmp_config_dir: Path) -> None: + """kai ping --json: additive `deprecation` key, existing keys untouched.""" + setup_single_project(tmp_config_dir) + + mock_service = MagicMock(spec=KaiService) + mock_service.resolve_alias.return_value = "prod" + mock_service.ping.return_value = { + "project_alias": "prod", + "timestamp": "2025-01-15T10:30:00+00:00", + "app_name": "kai-api", + "app_version": "1.2.3", + "server_version": "2.0.0", + "mcp_status": "connected", + } + + with pytest.MonkeyPatch.context() as mp: + mp.setattr( + "keboola_agent_cli.commands.kai.get_service", + lambda ctx, name: mock_service, + ) + + result = runner.invoke( + app, + [ + "--json", + "--config-dir", + str(tmp_config_dir), + "kai", + "ping", + "--project", + "prod", + ], + ) + + assert result.exit_code == 0, result.output + payload = json.loads(result.stdout) + assert payload["status"] == "ok" + data = payload["data"] + # pre-existing keys are byte-identical -- purely additive change + assert data["project_alias"] == "prod" + assert data["app_name"] == "kai-api" + assert data["mcp_status"] == "connected" + assert "deprecated" in data["deprecation"] + assert "kai-assistant" in data["deprecation"] + # JSON mode never duplicates the banner on stderr + assert "deprecated" not in result.stderr + + def test_ask_human_warns_on_stderr_response_stays_pipeable(self, tmp_config_dir: Path) -> None: + """kai ask human mode: the answer alone reaches stdout.""" + setup_single_project(tmp_config_dir) + + mock_service = MagicMock(spec=KaiService) + mock_service.resolve_alias.return_value = "prod" + mock_service.ask.return_value = { + "project_alias": "prod", + "chat_id": "chat-uuid-1", + "response": "You have 19 tables.", + } + + with pytest.MonkeyPatch.context() as mp: + mp.setattr( + "keboola_agent_cli.commands.kai.get_service", + lambda ctx, name: mock_service, + ) + + result = runner.invoke( + app, + [ + "--config-dir", + str(tmp_config_dir), + "kai", + "ask", + "--project", + "prod", + "-m", + "How many tables?", + ], + ) + + assert result.exit_code == 0, result.output + assert "`kai` group is deprecated" in self._flat(result.stderr) + stdout = self._flat(result.stdout) + assert "You have 19 tables." in stdout + assert "deprecated" not in stdout + + def test_ask_json_payload_gains_deprecation_key(self, tmp_config_dir: Path) -> None: + """kai ask --json: additive `deprecation` key alongside the answer.""" + setup_single_project(tmp_config_dir) + + mock_service = MagicMock(spec=KaiService) + mock_service.resolve_alias.return_value = "prod" + mock_service.ask.return_value = { + "project_alias": "prod", + "chat_id": "chat-uuid-1", + "response": "You have 19 tables.", + } + + with pytest.MonkeyPatch.context() as mp: + mp.setattr( + "keboola_agent_cli.commands.kai.get_service", + lambda ctx, name: mock_service, + ) + + result = runner.invoke( + app, + [ + "--json", + "--config-dir", + str(tmp_config_dir), + "kai", + "ask", + "--project", + "prod", + "-m", + "How many tables?", + ], + ) + + assert result.exit_code == 0, result.output + data = json.loads(result.stdout)["data"] + assert data["response"] == "You have 19 tables." + assert data["chat_id"] == "chat-uuid-1" + assert "deprecated" in data["deprecation"] + + def test_every_subcommand_surfaces_the_banner(self, tmp_config_dir: Path) -> None: + """All six subcommands carry the JSON `deprecation` key.""" + setup_single_project(tmp_config_dir) + + mock_service = MagicMock(spec=KaiService) + mock_service.resolve_alias.return_value = "prod" + mock_service.ping.return_value = {"project_alias": "prod"} + mock_service.ask.return_value = {"project_alias": "prod", "response": "x"} + mock_service.chat_message.return_value = { + "project_alias": "prod", + "response": "x", + "chat_id": "c1", + } + mock_service.preflight.return_value = { + "project_alias": "prod", + "ok": True, + "is_master_token": True, + "has_agent_chat_feature": True, + "token_description": "owner", + "project_name": "P", + "project_id": 1, + "error": None, + } + mock_service.get_chat_detail.return_value = { + "project_alias": "prod", + "chat_id": "c1", + "title": "T", + "created_at": None, + "messages": [], + } + mock_service.get_history.return_value = { + "project_alias": "prod", + "chats": [], + "has_more": False, + } + + invocations = [ + ["kai", "ping"], + ["kai", "ask", "-m", "q"], + ["kai", "chat", "-m", "q"], + ["kai", "preflight"], + ["kai", "chat-detail", "--chat-id", "c1"], + ["kai", "history"], + ] + + for argv in invocations: + with pytest.MonkeyPatch.context() as mp: + mp.setattr( + "keboola_agent_cli.commands.kai.get_service", + lambda ctx, name: mock_service, + ) + + result = runner.invoke( + app, + ["--json", "--config-dir", str(tmp_config_dir), *argv, "--project", "prod"], + ) + + assert result.exit_code == 0, f"{argv}: {result.output}" + data = json.loads(result.stdout)["data"] + assert "deprecation" in data, f"{argv} has no deprecation key" + assert "deprecated" in data["deprecation"], argv + + def test_error_envelope_carries_no_deprecation_key(self, tmp_config_dir: Path) -> None: + """A failing kai command keeps its exit code and gains no new key.""" + setup_single_project(tmp_config_dir) + + mock_service = MagicMock(spec=KaiService) + mock_service.resolve_alias.return_value = "prod" + mock_service.ping.side_effect = KeboolaApiError( + message="Kai is not enabled on this project.", + error_code="KAI_NOT_ENABLED", + ) + + with pytest.MonkeyPatch.context() as mp: + mp.setattr( + "keboola_agent_cli.commands.kai.get_service", + lambda ctx, name: mock_service, + ) + + result = runner.invoke( + app, + [ + "--json", + "--config-dir", + str(tmp_config_dir), + "kai", + "ping", + "--project", + "prod", + ], + ) + + assert result.exit_code != 0 + payload = json.loads(result.stdout) + assert payload["status"] == "error" + assert "deprecation" not in payload + assert "deprecation" not in payload["error"] + + def test_group_help_is_marked_deprecated(self, tmp_config_dir: Path) -> None: + """`kai --help` and each subcommand help announce the deprecation.""" + result = runner.invoke(app, ["--config-dir", str(tmp_config_dir), "kai", "--help"]) + assert result.exit_code == 0, result.output + group_help = self._flat(result.output) + assert "(DEPRECATED)" in group_help + assert "legacy 'kai-assistant' backend" in group_help + + for sub in ("ping", "ask", "chat", "preflight", "chat-detail", "history"): + sub_help = runner.invoke( + app, ["--config-dir", str(tmp_config_dir), "kai", sub, "--help"] + ) + assert sub_help.exit_code == 0, sub_help.output + assert "(DEPRECATED)" in self._flat(sub_help.output), sub diff --git a/uv.lock b/uv.lock index 0131e87b..4e9766b7 100644 --- a/uv.lock +++ b/uv.lock @@ -590,7 +590,7 @@ wheels = [ [[package]] name = "keboola-cli" -version = "0.76.2" +version = "0.77.0" source = { editable = "." } dependencies = [ { name = "croniter" },