Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,17 @@ STORAGE_API_URL=https://connection.keboola.com
```bash
kai ping # Check if the server is alive
kai info # Show server version, uptime, connected MCP servers
kai verify # Diagnose token, service URL, and monthly message quota
kai --version # Show CLI version
```

`kai verify` is the right starting point when chats work in the Keboola
platform UI but your app errors out. It reports which project/user the token
resolves to, the discovered kai-assistant URL, and your current monthly
message usage (e.g. `19/150 messages used, 131 left`). On a 429
`rate_limit:chat` it prints the code and message cleanly instead of dumping
raw JSON. Pass `--json-output` for a machine-readable report.

#### Chat

```bash
Expand Down
14 changes: 14 additions & 0 deletions examples/js-dataapp/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,16 @@
# Copy this file to .env.local and fill in real values.
# Use the env var names exactly as written — `KAI_TOKEN` is NOT recognised.

# Keboola Storage API token (Master or scoped) — same value the platform UI uses
STORAGE_API_TOKEN=your-keboola-token

# Keboola Storage API URL for your stack
# Examples:
# https://connection.keboola.com (AWS US)
# https://connection.eu-central-1.keboola.com (AWS EU)
# https://connection.us-east4.gcp.keboola.com (GCP US)
# https://connection.europe-west3.gcp.keboola.com (GCP EU)
STORAGE_API_URL=https://connection.keboola.com

# Optional: dev server port (default 3000)
# PORT=3000
51 changes: 51 additions & 0 deletions examples/js-dataapp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Kai Data App — JavaScript example

Minimal Express + vanilla-JS frontend that talks to the Keboola AI Assistant.

## Run locally

```bash
cp .env.example .env.local # fill in STORAGE_API_TOKEN and STORAGE_API_URL
npm install
node server.js # serves http://localhost:3000
```

## Env vars

The required names are exact:

- `STORAGE_API_TOKEN` — Keboola Storage API token (the platform UI uses the same)
- `STORAGE_API_URL` — Storage API URL for your stack (see `.env.example`)

`KAI_TOKEN` and similar names are **not** recognised. The legacy `KBC_TOKEN` /
`KBC_URL` names are honoured as fallbacks only.

## Troubleshooting

### "Works in the Keboola platform UI but my app errors out"

Run `kai verify` from the same shell where you set the env vars:

```bash
pip install kai-client # or: uv tool install kai-client
kai verify
```

It reports which project your token resolves to, whether the kai-assistant
service is reachable, and your current monthly message usage
(e.g. `19/150 messages used, 131 left`).

### Common errors

- **`429 rate_limit:chat — You have exceeded your maximum number of messages
for this month.`** Your project hit its monthly chat-message limit. Note
that the platform UI's "X / 150" counter is **per-token, server-side** —
if the UI says you have messages left but the API says you don't, your
app token and UI session may be hitting different counters. Contact
Keboola support or wait for the reset date shown by `kai verify`.

- **`401 storage.tokenInvalid`** The token doesn't exist or has been
expired/revoked. Generate a fresh one.

- **`kai-assistant service not found`** Your stack doesn't have the AI
Assistant feature enabled, or `STORAGE_API_URL` points at the wrong stack.
20 changes: 18 additions & 2 deletions examples/js-dataapp/public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,24 @@ async function readSSEStream(url, fetchOptions, onEvent) {
const res = await fetch(url, fetchOptions);

if (!res.ok) {
const err = await res.text();
addError(`Error: ${err}`);
// server.js returns {error: {status, code?, message}} for structured upstream
// errors. Fall back to raw text if the body isn't JSON.
let body;
try {
body = await res.json();
} catch {
body = null;
}
const e = body && body.error;
if (e && typeof e === "object") {
const prefix = e.code ? `${e.status || res.status} ${e.code}` : `${e.status || res.status}`;
// Prefer message, then code, then a generic fallback — handles {code} bodies
// with no message field.
const detail = e.message || e.code || "Request failed";
addError(`${prefix} — ${detail}`);
} else {
addError(`Error: ${typeof e === "string" ? e : res.statusText}`);
}
return null;
}

Expand Down
17 changes: 16 additions & 1 deletion examples/js-dataapp/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,22 @@ async function proxySSE(payload, res) {

if (!upstream.ok) {
const text = await upstream.text();
return res.status(upstream.status).json({ error: text });
// Try to parse the upstream body as a structured KAI error
// ({code, message}) so the frontend can render a clean message
// instead of the raw JSON blob. Common shapes hit here:
// 429 → {"code":"rate_limit:chat","message":"You have exceeded..."}
// 401 → {"code":"unauthorized:chat","message":"..."}
let parsed;
try {
parsed = JSON.parse(text);
} catch {
parsed = null;
}
const error =
parsed && typeof parsed === "object" && (parsed.code || parsed.message)
? { status: upstream.status, code: parsed.code, message: parsed.message }
: { status: upstream.status, message: text || upstream.statusText };
return res.status(upstream.status).json({ error });
}

res.setHeader("Content-Type", "text/event-stream");
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "kai-client"
version = "0.12.0"
version = "0.13.0"
description = "Python client library for the Keboola AI Assistant Backend API"
readme = "README.md"
license = { text = "MIT" }
Expand Down
4 changes: 1 addition & 3 deletions src/kai_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
VoteType,
)

__version__ = "0.12.0"
__version__ = "0.13.0"

__all__ = [
# Main client
Expand Down Expand Up @@ -154,5 +154,3 @@
# Version
"__version__",
]


Loading
Loading