bkper 5.4.0 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +14 -0
  2. package/lib/agent/extensions/auth-commands.d.ts.map +1 -1
  3. package/lib/agent/extensions/auth-commands.js +28 -10
  4. package/lib/agent/extensions/auth-commands.js.map +1 -1
  5. package/lib/agent/extensions/bkper-ai-provider.d.ts.map +1 -1
  6. package/lib/agent/extensions/bkper-ai-provider.js +17 -7
  7. package/lib/agent/extensions/bkper-ai-provider.js.map +1 -1
  8. package/lib/agent/system-prompt.d.ts.map +1 -1
  9. package/lib/agent/system-prompt.js +5 -4
  10. package/lib/agent/system-prompt.js.map +1 -1
  11. package/lib/auth/local-auth-service.d.ts.map +1 -1
  12. package/lib/auth/local-auth-service.js +12 -2
  13. package/lib/auth/local-auth-service.js.map +1 -1
  14. package/lib/commands/apps/api.d.ts +36 -0
  15. package/lib/commands/apps/api.d.ts.map +1 -0
  16. package/lib/commands/apps/api.js +144 -0
  17. package/lib/commands/apps/api.js.map +1 -0
  18. package/lib/commands/apps/index.d.ts +2 -0
  19. package/lib/commands/apps/index.d.ts.map +1 -1
  20. package/lib/commands/apps/index.js +2 -0
  21. package/lib/commands/apps/index.js.map +1 -1
  22. package/lib/commands/apps/list.d.ts +5 -3
  23. package/lib/commands/apps/list.d.ts.map +1 -1
  24. package/lib/commands/apps/list.js +13 -5
  25. package/lib/commands/apps/list.js.map +1 -1
  26. package/lib/commands/apps/register.d.ts.map +1 -1
  27. package/lib/commands/apps/register.js +37 -5
  28. package/lib/commands/apps/register.js.map +1 -1
  29. package/lib/docs/ai/decision-models.md +88 -248
  30. package/lib/docs/api/ai-gateway.md +397 -0
  31. package/lib/docs/api/managed-agent.md +385 -0
  32. package/lib/docs/apps/ai.md +39 -6
  33. package/lib/docs/apps/context-menu.md +97 -0
  34. package/lib/docs/cli/app-management.md +14 -1
  35. package/lib/docs/cli/app-usage.md +87 -0
  36. package/lib/docs/core/core-concepts.md +9 -3
  37. package/lib/docs/index.md +4 -1
  38. package/package.json +4 -4
@@ -0,0 +1,385 @@
1
+ # Bkper Managed Agent API
2
+
3
+ > Build apps, chat bots, and scripts on the agent behind Bkper Agent — the guide, with a page for each operation: sessions, instructions, files, and streams.
4
+
5
+ The Managed Agent API lets you build intelligent apps on Bkper's agent: a private cloud workspace with the `bkper` CLI, Bkper context, files, and durable sessions. Your app owns its screens, prompts, and instructions; Bkper runs the agent.
6
+
7
+ Build, for example:
8
+
9
+ - a Bkper platform app with its own chat, in a Book or on its own page;
10
+ - a chat bot for Slack, Teams, or Discord, with one session per thread;
11
+ - a scheduled script that asks for a monthly report and saves the file.
12
+
13
+ Sessions belong to the user, not the app: work started in one app continues in any other, including [Bkper Agent](https://bkper.com/docs/ai/bkper-agent), the agent in Bkper's Books. Bkper Agent is built only on this API and is its reference implementation. Usage counts against the user's Bkper AI allowance; see [Models and Usage](https://bkper.com/docs/ai/models). To call models directly, without an agent or workspace, use the [AI Gateway API](https://bkper.com/docs/api/ai-gateway). Building a Bkper app? Start with [Add Bkper AI to an App](https://bkper.com/docs/platform/apps/ai).
14
+
15
+ ## Requirements
16
+
17
+ - A Bkper account in the Managed Agent private beta. Other accounts get 403 `agent_beta_required`.
18
+ - A Bkper OAuth access token for the user the agent acts as. [Bkper platform apps](https://bkper.com/docs/platform/apps/overview) need none: call the API from the app's server, and the platform adds the user's credentials, as it does for Bkper AI.
19
+ - A server, bot, or script to call the API. Browsers on other websites are refused: the API sends no CORS headers and rejects cross-origin writes with 403 `origin_forbidden`.
20
+
21
+ ## Configure your client
22
+
23
+ | Setting | Value |
24
+ | --- | --- |
25
+ | Base URL | `https://agent.bkper.app` |
26
+ | Authentication | `Authorization: Bearer <Bkper access token>` |
27
+ | Contract | [`https://agent.bkper.app/openapi.json`](https://agent.bkper.app/openapi.json), OpenAPI 3.1 |
28
+
29
+ Treat the token as a secret. Never put it in a URL, a log, or a browser bundle.
30
+
31
+ ## Get a token for local testing
32
+
33
+ ```bash
34
+ bkper auth login
35
+ export BKPER_TOKEN="$(bkper auth token)"
36
+ ```
37
+
38
+ For a bot or a service, get tokens for its Bkper account through your OAuth flow; see [REST API authentication](https://bkper.com/docs/platform/scripts/rest-api).
39
+
40
+ ## Quick start
41
+
42
+ 1. Create a session with instructions and a first message. The response is the new session, with its `id`. The idempotency key is also the input's ID.
43
+
44
+ ```bash
45
+ curl --fail-with-body https://agent.bkper.app/v1/sessions \
46
+ -H "Authorization: Bearer ${BKPER_TOKEN}" \
47
+ -H "Content-Type: application/json" \
48
+ -H "Idempotency-Key: first-session-1" \
49
+ --data '{
50
+ "instructions": "Answer in at most three short paragraphs. Never change a Book.",
51
+ "input": { "content": [{ "type": "text", "text": "List my Books and what each one tracks." }] }
52
+ }'
53
+ ```
54
+
55
+ 2. Wait for the answer. Poll until `status` is `completed`, `failed`, or `withdrawn`. A completed input names the reply in `answerEntryId`.
56
+
57
+ ```bash
58
+ curl --fail-with-body \
59
+ https://agent.bkper.app/v1/sessions/${SESSION_ID}/inputs/first-session-1 \
60
+ -H "Authorization: Bearer ${BKPER_TOKEN}"
61
+ ```
62
+
63
+ 3. Read the reply. Its `content` holds `text` blocks, plus `thinking` and `tool_call` blocks for the agent's reasoning and steps.
64
+
65
+ ```bash
66
+ curl --fail-with-body \
67
+ https://agent.bkper.app/v1/sessions/${SESSION_ID}/entries/${ANSWER_ENTRY_ID} \
68
+ -H "Authorization: Bearer ${BKPER_TOKEN}"
69
+ ```
70
+
71
+ 4. Continue the conversation with `POST /v1/sessions/{sessionId}/inputs` and a new key.
72
+
73
+ Interactive clients watch the session's stream instead of polling; see [Observe progress](#observe-progress).
74
+
75
+ ## Instructions
76
+
77
+ `instructions` add context to ground the agent's reasoning: the task, who it works for, how to answer, and what it may change. Set them when you create a session, change them with `PATCH /v1/sessions/{sessionId}`, and clear them with `null`. A change applies from the agent's next step.
78
+
79
+ The agent receives, in order:
80
+
81
+ 1. **Bkper's base**: the workspace, Bkper's accounting model, and rules no interface can relax.
82
+ 2. **Your `instructions`.**
83
+ 3. **Each message**, with its optional `bookContext` as labeled information, never as instructions or permission.
84
+ 4. **Each message's attached files**, copied into the workspace and listed with their paths as labeled information.
85
+
86
+ There is no default. Without `instructions`, the agent follows only Bkper's base, which does not ask anyone before acting. Bkper Agent sends its own instructions, so it asks questions and confirms changes; your interface decides its own.
87
+
88
+ For a Slack bot:
89
+
90
+ ```text
91
+ You answer in a Slack thread. Use Slack mrkdwn,
92
+ keep replies under 150 words, and end with one
93
+ suggested next step. Never change a Book: explain
94
+ what you would change instead.
95
+ ```
96
+
97
+ For a scheduled close script:
98
+
99
+ ```text
100
+ You prepare the monthly close pack for the Book in
101
+ the message. Post nothing. Publish one PDF with the
102
+ income statement and balance sheet, then reply with
103
+ the totals you checked.
104
+ ```
105
+
106
+ - Instructions can be up to 16,000 characters.
107
+ - A fork keeps its parent's instructions unless the create request sets new ones.
108
+ - Sessions keep the text they were given. Updating your client changes only the sessions it creates or patches.
109
+
110
+ ## Who the agent acts as
111
+
112
+ Every request acts as the user whose token you send. That user's Book permissions, sessions, files, and Bkper AI allowance apply. Their sessions appear in their Bkper Agent too.
113
+
114
+ - **Personal tools**: use each person's own token, so each person sees only their Books.
115
+ - **Shared channel bots**: everyone in the channel acts with the bot's account. Use a dedicated Bkper account, shared only on the Books the channel needs, with the lowest permission that does the job.
116
+ - **Read-only bots**: instructions are guidance, not enforcement. To guarantee read-only behavior, share the Books with the bot's account as View only.
117
+
118
+ Without confirmation in your instructions, Book changes happen at once. Bkper records each change as an event made by the agent, and transactions can still be reviewed, edited, or trashed afterward.
119
+
120
+ ## Sessions and inputs
121
+
122
+ - **Session**: one conversation, with its history, model, title, and `instructions`.
123
+ - **Entry**: one immutable item of history: `user`, `assistant`, `tool_result`, or `compaction`.
124
+ - **Input**: one message you sent, with its status: `queued`, `running`, `completed`, `withdrawn`, or `failed`.
125
+
126
+ `delivery` sets how a new input reaches the agent:
127
+
128
+ | `delivery` | Effect | Default |
129
+ | --- | --- | --- |
130
+ | `prompt` | Starts a run | When idle |
131
+ | `steer` | Delivered after the current step, to change direction | — |
132
+ | `followUp` | Queued until the current run ends | While working |
133
+
134
+ `POST /v1/sessions/{sessionId}/stop` stops the run and withdraws queued inputs. It returns once the stop is recorded, without waiting.
135
+
136
+ ### Idempotency
137
+
138
+ Every create, input, and upload request needs an `Idempotency-Key` of 1–128 letters, digits, underscores, or hyphens.
139
+
140
+ - Repeating a request with the same key returns the original session, input, or file, so retries are safe.
141
+ - The same key with different content returns 409 `idempotency_conflict`.
142
+ - For bots, derive the key from the platform's message ID. A Slack event delivered twice then reaches the agent once.
143
+
144
+ ### Book context
145
+
146
+ Send `bookContext: { "bookId": "…", "query": "…" }` with an input when the message is about a Book. Bkper checks the user's access when the agent opens the Book.
147
+
148
+ ### Forks
149
+
150
+ Create a session with `parent` to continue from a point in another session:
151
+
152
+ - `position: "at"` (default) continues after a finished reply.
153
+ - `position: "before"` starts before one of the user's messages, to send an edited version.
154
+
155
+ A fork copies the conversation only, not workspace files or Book changes.
156
+
157
+ ## Observe progress
158
+
159
+ `GET /v1/sessions/{sessionId}/stream` sends server-sent events over an authenticated `fetch`. Browsers' `EventSource` cannot send the token.
160
+
161
+ - The first event is a `snapshot`: the session, the latest entries, live progress, and queued inputs.
162
+ - Then `update` events carry only what changed, about every 100 ms.
163
+ - Store entries by `id`; replace `session`, `live`, and `queue` with each update.
164
+ - The stream closes after five minutes. Reconnect for a fresh snapshot.
165
+ - Disconnecting never stops the agent.
166
+
167
+ `GET /v1/sessions/stream` streams the user's session list in the same way, for clients that show sessions created elsewhere.
168
+
169
+ ## Files
170
+
171
+ Files go both ways as the same `File` resource. `origin` tells them apart:
172
+
173
+ - `upload` — a file the user gave the agent;
174
+ - `publish` — a file the agent delivered.
175
+
176
+ ### Give the agent files
177
+
178
+ Upload each file, then attach the returned IDs to an input.
179
+
180
+ 1. **Upload the raw bytes** with `POST /v1/files`. Any type is accepted.
181
+
182
+ ```bash
183
+ curl --fail-with-body https://agent.bkper.app/v1/files \
184
+ -H "Authorization: Bearer $BKPER_TOKEN" \
185
+ -H "Idempotency-Key: statement-2026-03" \
186
+ -H "Content-Type: application/pdf" \
187
+ -H "Content-Disposition: attachment; filename*=UTF-8''extrato-mar%C3%A7o.pdf" \
188
+ --data-binary @extrato-março.pdf
189
+ ```
190
+
191
+ | Header | Meaning |
192
+ | --------------------- | ----------------------------------------------------------------------- |
193
+ | `Idempotency-Key` | Required. The same key and bytes return the same file |
194
+ | `Content-Length` | Required; HTTP clients set it. Up to 25 MiB |
195
+ | `Content-Type` | Optional. Recorded as declared, unverified |
196
+ | `Content-Disposition` | Optional. The file name, as `filename*=UTF-8''…` or `filename="…"`. Without it: `upload` |
197
+
198
+ The response is `201` with the `File`:
199
+
200
+ ```json
201
+ {
202
+ "id": "7f3c1a2b-…",
203
+ "origin": "upload",
204
+ "name": "extrato-março.pdf",
205
+ "mediaType": "application/pdf",
206
+ "size": 482113,
207
+ "sha256": "9a1e…",
208
+ "createdAt": "1767225600000",
209
+ "expiresAt": "1769817600000",
210
+ "createdBy": "…"
211
+ }
212
+ ```
213
+
214
+ 2. **Send the IDs as `file` parts** of an input, or of the first input when creating a session. Uploads belong to the user, not to a session.
215
+
216
+ ```json
217
+ {
218
+ "content": [
219
+ { "type": "text", "text": "Record these statement lines in the Book" },
220
+ { "type": "file", "fileId": "7f3c1a2b-…" }
221
+ ]
222
+ }
223
+ ```
224
+
225
+ An input carries up to 10 files, each once, and some text or at least one file. It may name any of the user's files still kept, including files the agent published.
226
+
227
+ Before the agent reads the input, each file is copied into its workspace, at `/workspace/uploads/<fileId>/<name>`. The agent gets their paths, names, declared types, and sizes, labeled as information, never as instructions. It processes every format with code, and reads images to see them when the session's model accepts images (`inputModalities` in `GET /v1/models`). The user's entry in the history shows their own text and the same `file` parts.
228
+
229
+ ### Files the agent delivers
230
+
231
+ When the agent publishes a file, a `tool_result` entry carries a `file` part with `fileId`, `name`, `mediaType`, and `size`. List a session's published files with `GET /v1/sessions/{sessionId}/files`.
232
+
233
+ ### Download, retention, and quotas
234
+
235
+ - `GET /v1/files/{fileId}` returns the `File`; `GET /v1/files/{fileId}/content` its bytes, always as an attachment. The `Content-Type` is the allowlisted type for the name, never an upload's declared type; anything else is `application/octet-stream`.
236
+ - Every stored copy is deleted 30 days after creation, at `expiresAt`. Downloads then return `410 file_gone`, and inputs naming the file return `410 file_gone`. The `File` and the parts in the history remain.
237
+ - Quotas count only files created in the last 30 days, separately for each origin: 1,000 files and 1 GiB of uploads, and 1,000 files and 1 GiB of published files. Uploads never block publishing. A full upload quota returns `409 file_quota_exceeded`; a full publish quota fails the agent's `publish`.
238
+
239
+ ## Example: a chat bot
240
+
241
+ A Slack bot that keeps one session per thread. The same shape works for Teams and Discord.
242
+
243
+ ```ts
244
+ const AGENT = 'https://agent.bkper.app';
245
+ const INSTRUCTIONS =
246
+ 'You answer in a Slack thread. Use Slack mrkdwn. Keep it short.';
247
+
248
+ // Slack thread → session ID. Keep it in your own store.
249
+ const sessions = new Map<string, string>();
250
+
251
+ async function agent(path: string, body?: unknown, key?: string) {
252
+ const response = await fetch(AGENT + path, {
253
+ method: body ? 'POST' : 'GET',
254
+ headers: {
255
+ Authorization: `Bearer ${await botToken()}`,
256
+ 'Content-Type': 'application/json',
257
+ ...(key ? { 'Idempotency-Key': key } : {}),
258
+ },
259
+ body: body ? JSON.stringify(body) : undefined,
260
+ });
261
+ const result = await response.json();
262
+ if (!response.ok) throw new Error(result.error.code);
263
+ return result;
264
+ }
265
+
266
+ type SlackEvent = { event_id: string; thread_ts: string; text: string };
267
+
268
+ export async function reply(event: SlackEvent): Promise<string> {
269
+ const input = { content: [{ type: 'text', text: event.text }] };
270
+ // The event ID is the key: a redelivered event arrives once.
271
+ const key = event.event_id;
272
+ let id = sessions.get(event.thread_ts);
273
+ if (id) {
274
+ await agent(`/v1/sessions/${id}/inputs`, input, key);
275
+ } else {
276
+ const body = { instructions: INSTRUCTIONS, input };
277
+ id = (await agent('/v1/sessions', body, key)).id as string;
278
+ sessions.set(event.thread_ts, id);
279
+ }
280
+ for (;;) {
281
+ const sent = await agent(`/v1/sessions/${id}/inputs/${key}`);
282
+ if (sent.status === 'completed') {
283
+ const answer = await agent(
284
+ `/v1/sessions/${id}/entries/${sent.answerEntryId}`
285
+ );
286
+ type Part = { type: string; text?: string };
287
+ return (answer.content as Part[])
288
+ .filter(part => part.type === 'text')
289
+ .map(part => part.text)
290
+ .join('\n');
291
+ }
292
+ if (sent.status === 'failed' || sent.status === 'withdrawn') {
293
+ throw new Error(sent.error?.code ?? sent.status);
294
+ }
295
+ await new Promise(resolve => setTimeout(resolve, 2000));
296
+ }
297
+ }
298
+ ```
299
+
300
+ `botToken()` returns a current access token for the bot's Bkper account. A message sent while the agent is still working is queued as a follow-up, so a busy thread never loses a message.
301
+
302
+ Acknowledge each Slack event at once and post the reply when `reply` returns: Slack expects an answer within three seconds, and an agent run usually takes longer.
303
+
304
+ ## Limits
305
+
306
+ | Limit | Value |
307
+ | --- | --- |
308
+ | Message text | 16,000 characters in total, up to 16 parts |
309
+ | Files per message | 10 |
310
+ | Request body (JSON) | 64 KiB |
311
+ | Uploaded or published file | 25 MiB |
312
+ | Stored files per user | 1,000 files and 1 GiB each for uploads and published files, over 30 days |
313
+ | File retention | 30 days after creation |
314
+ | `instructions` | 16,000 characters |
315
+ | `bookContext.query` | 4,000 characters |
316
+ | List page | 50 sessions, entries, or files |
317
+
318
+ ## Errors
319
+
320
+ Errors are `{ "error": { "code", "type", "message" } }`. Branch on `code`.
321
+
322
+ | Status | `code` | Meaning |
323
+ | --- | --- | --- |
324
+ | 400 | `invalid_request`, `unknown_model`, `unknown_thinking_level` | A field is invalid or unknown |
325
+ | 401 | `unauthorized`, `authentication_failed` | The token is missing, invalid, or expired |
326
+ | 403 | `agent_beta_required` | The account is not in the private beta |
327
+ | 403 | `origin_forbidden` | A browser on another website sent a write |
328
+ | 404 | `not_found`, `parent_not_found`, `file_not_found` | No such resource for this user |
329
+ | 405 | `method_not_allowed` | The path does not support the method |
330
+ | 409 | `idempotency_conflict` | The key was used with different content |
331
+ | 409 | `invalid_fork_point` | The fork point is not a finished reply or a user message |
332
+ | 409 | `input_running` | The input is already running and cannot be withdrawn |
333
+ | 409 | `file_quota_exceeded` | The user's upload quota is full |
334
+ | 410 | `file_gone` | The file's stored copy was deleted |
335
+ | 411 | `length_required` | An upload has no `Content-Length` |
336
+ | 413 | `request_too_large`, `file_too_large` | The JSON body exceeds 64 KiB, or the upload 25 MiB |
337
+ | 503 | `service_unavailable`, `authentication_failed` | A dependency is unavailable; retry with backoff |
338
+
339
+ Model failures appear on the failed input and reply as `error.code`, such as `usage_limit_exceeded` when the monthly Bkper AI allowance is used up.
340
+
341
+ ## Compatibility
342
+
343
+ The API is versioned under `/v1`. To keep working as it grows:
344
+
345
+ - ignore fields you do not know;
346
+ - show unknown entry, block, and part types generically;
347
+ - treat unknown statuses and error codes as generic.
348
+
349
+ ## Reference
350
+
351
+ Each operation has its own page. The machine-readable contract is [openapi.json](https://agent.bkper.app/openapi.json).
352
+
353
+ ### Sessions
354
+
355
+ - [`GET /v1/sessions` — List sessions](https://bkper.com/docs/api/managed-agent/operations/listsessions.md)
356
+ - [`POST /v1/sessions` — Create or fork a session](https://bkper.com/docs/api/managed-agent/operations/createsession.md)
357
+ - [`GET /v1/sessions/{sessionId}` — Get a session](https://bkper.com/docs/api/managed-agent/operations/getsession.md)
358
+ - [`PATCH /v1/sessions/{sessionId}` — Update a session](https://bkper.com/docs/api/managed-agent/operations/updatesession.md)
359
+ - [`GET /v1/sessions/{sessionId}/entries` — List history entries](https://bkper.com/docs/api/managed-agent/operations/listentries.md)
360
+ - [`GET /v1/sessions/{sessionId}/entries/{entryId}` — Get a full entry](https://bkper.com/docs/api/managed-agent/operations/getentry.md)
361
+ - [`POST /v1/sessions/{sessionId}/compact` — Compact earlier context](https://bkper.com/docs/api/managed-agent/operations/compactsession.md)
362
+
363
+ ### Inputs
364
+
365
+ - [`POST /v1/sessions/{sessionId}/inputs` — Send an input](https://bkper.com/docs/api/managed-agent/operations/sendinput.md)
366
+ - [`GET /v1/sessions/{sessionId}/inputs/{inputId}` — Get an input](https://bkper.com/docs/api/managed-agent/operations/getinput.md)
367
+ - [`POST /v1/sessions/{sessionId}/inputs/{inputId}/withdraw` — Withdraw a queued input](https://bkper.com/docs/api/managed-agent/operations/withdrawinput.md)
368
+ - [`POST /v1/sessions/{sessionId}/stop` — Stop the current run](https://bkper.com/docs/api/managed-agent/operations/stopsession.md)
369
+
370
+ ### Streams
371
+
372
+ - [`GET /v1/sessions/stream` — Observe the session list](https://bkper.com/docs/api/managed-agent/operations/observesessions.md)
373
+ - [`GET /v1/sessions/{sessionId}/stream` — Observe a session](https://bkper.com/docs/api/managed-agent/operations/observesession.md)
374
+
375
+ ### Files
376
+
377
+ - [`GET /v1/sessions/{sessionId}/files` — List published files](https://bkper.com/docs/api/managed-agent/operations/listfiles.md)
378
+ - [`POST /v1/files` — Upload a file](https://bkper.com/docs/api/managed-agent/operations/uploadfile.md)
379
+ - [`GET /v1/files/{fileId}` — Get file metadata](https://bkper.com/docs/api/managed-agent/operations/getfile.md)
380
+ - [`GET /v1/files/{fileId}/content` — Download a file](https://bkper.com/docs/api/managed-agent/operations/downloadfile.md)
381
+
382
+ ### Models
383
+
384
+ - [`GET /v1/models` — List models](https://bkper.com/docs/api/managed-agent/operations/listmodels.md)
385
+
@@ -1,6 +1,6 @@
1
1
  # Add Bkper AI to an App
2
2
 
3
- Bkper Platform apps can call [Bkper AI](https://bkper.com/docs/ai/ai-gateway.md) without storing a model-provider key. The platform authorizes each request as the current user and attributes usage to the app. Scripts and servers outside the platform use the same code with their own Bkper token.
3
+ Bkper Platform apps can call [Bkper AI models](https://bkper.com/docs/ai/models.md) without storing a model-provider key. The platform authorizes each request as the current user and attributes usage to the app. Scripts and servers outside the platform use the same code with their own Bkper token.
4
4
 
5
5
  Model output is a suggestion, not permission to change a Book. Keep decisions about resource movements, and any resulting writes, in application code.
6
6
 
@@ -8,6 +8,7 @@ Model output is a suggestion, not permission to change a Book. Keep decisions ab
8
8
 
9
9
  - **A yes/no, a choice, or a level on a scale?** Ask a decision model, such as `jev`, with TypeSafe's SDK, `@typesafe-ai/sdk`.
10
10
  - **Text or a custom JSON object?** Ask a language model (LLM), such as `gpt-luna`, with AI SDK's Open Responses provider, `@ai-sdk/open-responses`.
11
+ - **A conversation that works on Books?** Reading and comparing Books, computing with scripts, delivering files, remembering earlier messages: [run an agent](#run-an-agent) with the Managed Agent API instead of calling a model.
11
12
 
12
13
  By default, add the SDK to the **Worker** package. Keep the call in a server service used by your authenticated app API route, and define the route's request and response in the app's Zod/OpenAPI contract. For inference used only by the UI, the client can also [call Bkper AI directly](#call-from-the-browser).
13
14
 
@@ -32,7 +33,7 @@ The client can call Bkper AI directly with `auth.authenticatedFetch()`, which se
32
33
  | Reuse by scripts, agents, or events | Yes, through the route | No |
33
34
  | Request path | Client → app Worker → Bkper AI | Client → Bkper AI |
34
35
 
35
- In both cases usage counts against the user's allowance. See [Call from a browser](https://bkper.com/docs/ai/ai-gateway.md#call-from-a-browser) for a request example.
36
+ In both cases usage counts against the user's allowance. See [Call from a browser](https://bkper.com/docs/api/ai-gateway.md#call-from-a-browser) for a request example.
36
37
 
37
38
  ## Ask a decision model
38
39
 
@@ -64,7 +65,7 @@ export async function scoreRecurring(description: string) {
64
65
  - **`baseURL`** has no `/v1`. The SDK calls `POST /v1/systemone`, which behaves exactly like `POST /v1/decisions`.
65
66
  - **The answer is typed** from the question. Bkper AI validates every answer against its question before responding, so your code can use it directly.
66
67
 
67
- The Decision Models guide covers questions, state, answers, and thresholds. The open-source [Merge Duplicates app](https://github.com/bkper/bkper-apps/tree/main/merge-duplicates) uses this setup to suggest duplicate pairs for human review.
68
+ [Decision Models](https://bkper.com/docs/ai/decision-models.md) covers designing the questions, facts, and thresholds; the [AI Gateway API](https://bkper.com/docs/api/ai-gateway.md#decision-models) covers the request, answers, errors, and caching. The open-source [Merge Duplicates app](https://github.com/bkper/bkper-apps/tree/main/merge-duplicates) uses this setup to suggest duplicate pairs for human review.
68
69
 
69
70
  ## Generate a language response
70
71
 
@@ -95,6 +96,38 @@ export async function draftReviewNote(description: string) {
95
96
 
96
97
  Keep schemas simple. Constraints such as a string's minimum or maximum length may not be supported by every model's strict JSON Schema subset; check them in code after generation.
97
98
 
99
+ ## Run an agent
100
+
101
+ For a chat that does real work on Books, start a session on the [Managed Agent API](https://bkper.com/docs/api/managed-agent.md) from the app's server. Bkper runs the agent in the user's private cloud workspace, with the `bkper` CLI, files, and durable sessions. Your app keeps its screens, prompts, and instructions. The Managed Agent is in private beta.
102
+
103
+ ```ts
104
+ // No Authorization header: the platform adds the user's.
105
+ const response = await fetch('https://agent.bkper.app/v1/sessions', {
106
+ method: 'POST',
107
+ headers: {
108
+ 'Content-Type': 'application/json',
109
+ // Reuse the same key if you retry this request.
110
+ 'Idempotency-Key': inputId,
111
+ },
112
+ body: JSON.stringify({
113
+ instructions: 'You help the finance team close each month.',
114
+ input: {
115
+ content: [{ type: 'text', text: question }],
116
+ bookContext: { bookId, query },
117
+ },
118
+ }),
119
+ });
120
+ const session = await response.json();
121
+ ```
122
+
123
+ Then follow the session's stream, or poll the input until it completes, as in the [quick start](https://bkper.com/docs/api/managed-agent.md#quick-start).
124
+
125
+ - **The user's agent.** Sessions belong to the user, so work started in your app continues in Bkper Agent or any other app.
126
+ - **Your services as tools.** The agent can call your app's API as the user. Describe the routes in your instructions, and the agent can run your checks instead of redoing them.
127
+ - **Real changes.** Unlike a model call, the agent can change Books with the user's permissions. Say in your instructions when it may.
128
+
129
+ > **Tip**
130
+ > Replies usually come in Markdown. Render them, sanitized, in your chat, or ask for another format in your instructions.
98
131
  ## Outside a Platform app
99
132
 
100
133
  Scripts, servers, and tools send their own Bkper token. Pass it as `apiKey`: both SDKs send it as a bearer token. Get a client each time you need one, so every call uses a current token:
@@ -163,6 +196,6 @@ Map these errors to your route's typed error response. Keep the code and message
163
196
  ## Next steps
164
197
 
165
198
  - [Decision Models](https://bkper.com/docs/ai/decision-models.md): questions, answers, thresholds, and caching.
166
- - [Bkper AI Gateway](https://bkper.com/docs/ai/ai-gateway.md): tokens, raw HTTP requests, errors, and privacy.
167
- - [Models and Usage](https://bkper.com/docs/ai/models.md): model IDs, capabilities, and usage rates.
168
- - [Bkper AI API reference](https://bkper.com/docs/api/ai.md): every request and response field.
199
+ - [Models and Usage](https://bkper.com/docs/ai/models.md): models, usage rates, the allowance, and privacy.
200
+ - [AI Gateway API](https://bkper.com/docs/api/ai-gateway.md): tokens, raw HTTP requests, capabilities, and errors.
201
+ - [Managed Agent API](https://bkper.com/docs/api/managed-agent.md): sessions, instructions, files, and streams.
@@ -55,6 +55,8 @@ menuOpenMode: SIDEBAR
55
55
  | `EXPANDED` | Opens in a wider panel with more room for complex UIs. |
56
56
  | `NEW_TAB` | Opens the menu URL in a new browser tab instead of an embedded panel. |
57
57
 
58
+ An `EXPANDED` panel returns to sidebar size the first time the App changes the Book after opening, so the person sees the change.
59
+
58
60
  ### Live context updates
59
61
 
60
62
  Bkper keeps embedded Apps informed of context changes without reloading the iframe, allowing them to preserve their current state. For Apps opened in `SIDEBAR` or `EXPANDED`, Bkper communicates those changes by sending the updated App URL to the iframe when its origin remains the same:
@@ -102,6 +104,101 @@ window.addEventListener('message', event => {
102
104
 
103
105
  Apps opened with `NEW_TAB` do not receive this message. Their context is set only by the URL used to open the tab.
104
106
 
107
+ ### Navigating the Book
108
+
109
+ Embedded Apps can take the user to a page in Bkper, such as a chart or a transaction, without reloading Bkper or the App. Bkper navigates around the panel and keeps the App open, so the user can follow the App through the Book.
110
+
111
+ The App asks Bkper to navigate with a message. Bkper decides whether and how to navigate.
112
+
113
+ #### Host support
114
+
115
+ Each time the App iframe finishes loading, Bkper announces the requests it supports:
116
+
117
+ ```js
118
+ {
119
+ type: 'bkper:host-ready',
120
+ supports: ['navigate'],
121
+ }
122
+ ```
123
+
124
+ Send `bkper:navigate` only after receiving this message with `navigate` in `supports`. Until then, keep the normal link behavior. Register the message listener when the App starts, so it does not miss the message.
125
+
126
+ Apps opened with `NEW_TAB` do not receive this message, because they have no Bkper parent.
127
+
128
+ #### Requesting navigation
129
+
130
+ Post the Bkper URL to the parent:
131
+
132
+ ```js
133
+ window.parent.postMessage(
134
+ {
135
+ type: 'bkper:navigate',
136
+ url: 'https://bkper.app/books/abc123/transactions?query=account%3ASales&charts=true',
137
+ },
138
+ BKPER_ORIGIN
139
+ );
140
+ ```
141
+
142
+ Bkper ignores the message when any of these is true:
143
+
144
+ - It does not come from the App iframe currently open in the panel.
145
+ - It does not come from that iframe's own origin.
146
+ - `url` is not an absolute URL on the Bkper origin.
147
+ - `url` includes a username or password.
148
+
149
+ For a valid request, Bkper navigates as follows:
150
+
151
+ - **Book pages** (Transactions and Accounts, under `/books/{bookId}/`): Bkper navigates without reloading and adds a browser history entry, so Back returns to the previous page. The App stays open: Bkper sets the App in the URL, replacing any other App given there. An expanded panel returns to sidebar size, so the destination is visible.
152
+ - **Other Bkper pages**: Bkper loads the page as a normal navigation, which closes the panel.
153
+
154
+ The usual Bkper rules apply at the destination:
155
+
156
+ - The user needs access to the destination Book.
157
+ - When the destination is in the same Book, the App receives `bkper:app-url-changed` if its context changes, as described in [Live context updates](#live-context-updates).
158
+ - When the destination is another Book, Bkper opens the App again for that Book if it is installed there, and closes it otherwise.
159
+
160
+ Bkper does not reply to `bkper:navigate`.
161
+
162
+ Send `bkper:navigate` only in response to a user action, such as a click. Bkper cannot verify this, and navigation the user did not ask for interrupts their work.
163
+
164
+ #### Example
165
+
166
+ Keep links in the App as real links that open in a new tab, so they work wherever navigation is not available, such as in a `NEW_TAB` App or an older Bkper version:
167
+
168
+ ```html
169
+ <a href="https://bkper.app/books/abc123/transactions?query=account%3ASales" target="_blank">Sales</a>
170
+ ```
171
+
172
+ Then turn plain clicks on Bkper links into navigation requests once Bkper supports them:
173
+
174
+ ```js
175
+ const BKPER_ORIGIN = 'https://bkper.app';
176
+ let canNavigate = false;
177
+
178
+ window.addEventListener('message', event => {
179
+ // Verify that the trusted Bkper parent sent the message.
180
+ if (event.source !== window.parent || event.origin !== BKPER_ORIGIN) return;
181
+
182
+ const message = event.data;
183
+ if (message?.type === 'bkper:host-ready' && Array.isArray(message.supports)) {
184
+ canNavigate = message.supports.includes('navigate');
185
+ }
186
+ });
187
+
188
+ document.addEventListener('click', event => {
189
+ // Keep modified and non-primary clicks as normal links, such as opening a new tab.
190
+ if (!canNavigate || event.defaultPrevented || event.button !== 0) return;
191
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
192
+
193
+ // Only links to Bkper itself become navigation requests.
194
+ const link = event.composedPath().find(node => node instanceof HTMLAnchorElement);
195
+ if (!link || new URL(link.href).origin !== BKPER_ORIGIN) return;
196
+
197
+ event.preventDefault();
198
+ window.parent.postMessage({ type: 'bkper:navigate', url: link.href }, BKPER_ORIGIN);
199
+ });
200
+ ```
201
+
105
202
  ### Available expressions
106
203
 
107
204
  The menu URL supports these dynamic expressions:
@@ -271,8 +271,13 @@ bkper app install my-app -b abc123
271
271
 
272
272
  # Uninstall an app from a book
273
273
  bkper app uninstall my-app -b abc123
274
+
275
+ # List the apps installed in a book
276
+ bkper app list -b abc123
274
277
  ```
275
278
 
279
+ To use installed apps, including calling their API with `bkper app api`, see [App Usage](./app-usage.md).
280
+
276
281
  ---
277
282
 
278
283
  ## Secrets
@@ -554,7 +559,8 @@ Inside the interactive agent:
554
559
  - `app init [name]` - Scaffold a new app from the template. With `name`, creates `./<name>` and uses it as the app id. Without `name`, initializes the current directory and derives the app id from the folder name. Validates the marked `AGENTS.md`, initializes Git on `main` when needed without staging or committing, and does not install dependencies. Read the printed `AGENTS.md` path before specialization and preserve its standards, specifics, and marker pairs.
555
560
  - `app clone <appId> [path]` - Clone a Bkper-managed App source repository. Does not install dependencies; run `bun install` explicitly afterward. External-source Apps must be cloned from their provider instead.
556
561
  - `app git-credential <appId> [operation]` - Internal noninteractive Git credential helper for managed Artifacts source. Generated repository config pins the App ID and exact remote URL/path; Git appends `get`, `store`, or `erase`. Never persists tokens.
557
- - `app list` - List all apps you have access to
562
+ - `app list` - List apps you have access to; readmes are omitted (use `app get`)
563
+ - `-b, --book <bookId>` - List the apps installed in this book instead
558
564
  - `app sync` - Verify that source is clean, committed, and stored before syncing [bkper.yaml][bkper.yaml reference]. For an eligible standalone repository with no remote, activate managed source for a new or existing App; existing migrations atomically upload all local branches and tags. For external source, verify that the configured upstream contains the current commit.
559
565
  - `app build` - Build the server Worker bundle for deployment
560
566
  - `app deploy` - Require stored source, then explicitly deploy the existing local build. Managed Apps safely push and Platform-verifies the current commit. External Apps keep direct upload behavior after the CLI verifies that their upstream contains the current commit.
@@ -584,6 +590,13 @@ Inside the interactive agent:
584
590
  - `app install <appId> -b <bookId>` - Install an app on a book
585
591
  - `app uninstall <appId> -b <bookId>` - Uninstall an app from a book
586
592
 
593
+ ### App API
594
+
595
+ - `app api spec <appId>` - Print the app's OpenAPI spec
596
+ - `app api request <appId> <path>` - Send a request to the app's API as the signed-in user
597
+
598
+ See [App Usage](./app-usage.md) for options and usage.
599
+
587
600
  ### Secrets Management
588
601
 
589
602
  - `app secrets put <name>` - Store a secret