@terreno/mcp 57.16.2 → 57.17.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.
- package/dist/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +26 -5
- package/dist/docs/templates/bootstrap/frontend/store/sdk.template.md +2 -0
- package/dist/docs/upgrades/57.17.0.md +126 -0
- package/dist/docs/versioned/next/approval-rubric.md +171 -0
- package/dist/docs/versioned/next/explanation/admin-interface.md +5 -1
- package/dist/docs/versioned/next/explanation/agent-ui-asks.md +1 -0
- package/dist/docs/versioned/next/explanation/authentication.md +60 -10
- package/dist/docs/versioned/next/explanation/example-coverage.md +2 -1
- package/dist/docs/versioned/next/explanation/model-router-actions.md +32 -1
- package/dist/docs/versioned/next/how-to/README.md +1 -0
- package/dist/docs/versioned/next/how-to/add-github-oauth.md +49 -16
- package/dist/docs/versioned/next/how-to/admin-add-model.md +4 -1
- package/dist/docs/versioned/next/how-to/ai-feature-development.md +5 -0
- package/dist/docs/versioned/next/how-to/background-jobs.md +18 -5
- package/dist/docs/versioned/next/how-to/circleci.md +39 -3
- package/dist/docs/versioned/next/how-to/configure-better-auth.md +283 -5
- package/dist/docs/versioned/next/how-to/connect-mcp-service-token.md +3 -1
- package/dist/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +1 -1
- package/dist/docs/versioned/next/how-to/enable-mcp-oauth.md +134 -0
- package/dist/docs/versioned/next/how-to/expose-mcp-tools.md +43 -4
- package/dist/docs/versioned/next/how-to/install-agent-skills.md +8 -1
- package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +7 -4
- package/dist/docs/versioned/next/how-to/password-reset.md +8 -2
- package/dist/docs/versioned/next/how-to/product-announcements.md +5 -3
- package/dist/docs/versioned/next/how-to/rate-limiting.md +2 -2
- package/dist/docs/versioned/next/how-to/run-tests-locally.md +50 -0
- package/dist/docs/versioned/next/how-to/use-syncdb-correctly.md +1 -1
- package/dist/docs/versioned/next/how-to/websocket-integration.md +42 -9
- package/dist/docs/versioned/next/how-to/whats-new-banner.md +55 -0
- package/dist/docs/versioned/next/implementationPlans/agent-ui-asks.md +2 -0
- package/dist/docs/versioned/next/implementationPlans/app-mcp-server.md +7 -4
- package/dist/docs/versioned/next/implementationPlans/fix-1594-cta-realtime-better-auth.md +41 -0
- package/dist/docs/versioned/next/implementationPlans/fix-better-auth-sessions-and-config.md +80 -0
- package/dist/docs/versioned/next/implementationPlans/rbac-permissions.md +5 -2
- package/dist/docs/versioned/next/implementationPlans/rtk-token-source-web-bearer.md +246 -0
- package/dist/docs/versioned/next/reference/admin-backend.md +46 -0
- package/dist/docs/versioned/next/reference/admin-config.md +2 -2
- package/dist/docs/versioned/next/reference/ai.md +48 -4
- package/dist/docs/versioned/next/reference/api.md +410 -12
- package/dist/docs/versioned/next/reference/blocks.md +1 -1
- package/dist/docs/versioned/next/reference/comms.md +19 -1
- package/dist/docs/versioned/next/reference/environment-variables.md +6 -1
- package/dist/docs/versioned/next/reference/feature-flags.md +1 -1
- package/dist/docs/versioned/next/reference/jobs.md +8 -6
- package/dist/docs/versioned/next/reference/legacy/rtk.md +145 -18
- package/dist/docs/versioned/next/reference/lifecycle-plugin.md +5 -0
- package/dist/docs/versioned/next/reference/syncdb.md +64 -3
- package/dist/docs/versioned/next/reference/ui.md +23 -1
- package/package.json +3 -3
- package/src/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +26 -5
- package/src/docs/templates/bootstrap/frontend/store/sdk.template.md +2 -0
- package/src/docs/upgrades/57.17.0.md +126 -0
- package/src/docs/versioned/next/approval-rubric.md +171 -0
- package/src/docs/versioned/next/explanation/admin-interface.md +5 -1
- package/src/docs/versioned/next/explanation/agent-ui-asks.md +1 -0
- package/src/docs/versioned/next/explanation/authentication.md +60 -10
- package/src/docs/versioned/next/explanation/example-coverage.md +2 -1
- package/src/docs/versioned/next/explanation/model-router-actions.md +32 -1
- package/src/docs/versioned/next/how-to/README.md +1 -0
- package/src/docs/versioned/next/how-to/add-github-oauth.md +49 -16
- package/src/docs/versioned/next/how-to/admin-add-model.md +4 -1
- package/src/docs/versioned/next/how-to/ai-feature-development.md +5 -0
- package/src/docs/versioned/next/how-to/background-jobs.md +18 -5
- package/src/docs/versioned/next/how-to/circleci.md +39 -3
- package/src/docs/versioned/next/how-to/configure-better-auth.md +283 -5
- package/src/docs/versioned/next/how-to/connect-mcp-service-token.md +3 -1
- package/src/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +1 -1
- package/src/docs/versioned/next/how-to/enable-mcp-oauth.md +134 -0
- package/src/docs/versioned/next/how-to/expose-mcp-tools.md +43 -4
- package/src/docs/versioned/next/how-to/install-agent-skills.md +8 -1
- package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +7 -4
- package/src/docs/versioned/next/how-to/password-reset.md +8 -2
- package/src/docs/versioned/next/how-to/product-announcements.md +5 -3
- package/src/docs/versioned/next/how-to/rate-limiting.md +2 -2
- package/src/docs/versioned/next/how-to/run-tests-locally.md +50 -0
- package/src/docs/versioned/next/how-to/use-syncdb-correctly.md +1 -1
- package/src/docs/versioned/next/how-to/websocket-integration.md +42 -9
- package/src/docs/versioned/next/how-to/whats-new-banner.md +55 -0
- package/src/docs/versioned/next/implementationPlans/agent-ui-asks.md +2 -0
- package/src/docs/versioned/next/implementationPlans/app-mcp-server.md +7 -4
- package/src/docs/versioned/next/implementationPlans/fix-1594-cta-realtime-better-auth.md +41 -0
- package/src/docs/versioned/next/implementationPlans/fix-better-auth-sessions-and-config.md +80 -0
- package/src/docs/versioned/next/implementationPlans/rbac-permissions.md +5 -2
- package/src/docs/versioned/next/implementationPlans/rtk-token-source-web-bearer.md +246 -0
- package/src/docs/versioned/next/reference/admin-backend.md +46 -0
- package/src/docs/versioned/next/reference/admin-config.md +2 -2
- package/src/docs/versioned/next/reference/ai.md +48 -4
- package/src/docs/versioned/next/reference/api.md +410 -12
- package/src/docs/versioned/next/reference/blocks.md +1 -1
- package/src/docs/versioned/next/reference/comms.md +19 -1
- package/src/docs/versioned/next/reference/environment-variables.md +6 -1
- package/src/docs/versioned/next/reference/feature-flags.md +1 -1
- package/src/docs/versioned/next/reference/jobs.md +8 -6
- package/src/docs/versioned/next/reference/legacy/rtk.md +145 -18
- package/src/docs/versioned/next/reference/lifecycle-plugin.md +5 -0
- package/src/docs/versioned/next/reference/syncdb.md +64 -3
- package/src/docs/versioned/next/reference/ui.md +23 -1
|
@@ -14,6 +14,7 @@ AI service layer for Terreno backends: provider-agnostic chat via the Vercel AI
|
|
|
14
14
|
- [Models](#models)
|
|
15
15
|
- [Route registrars](#route-registrars)
|
|
16
16
|
- [AiApp plugin](#aiapp-plugin)
|
|
17
|
+
- [Cost and abuse limits](#cost-and-abuse-limits)
|
|
17
18
|
- [LangfuseApp plugin](#langfuseapp-plugin)
|
|
18
19
|
- [Observability](#observability)
|
|
19
20
|
- [Durable agent harness](#durable-agent-harness)
|
|
@@ -284,7 +285,7 @@ AI resolution order: `x-ai-api-key` header + `createModelFn` → `createServerMo
|
|
|
284
285
|
|
|
285
286
|
Pass `asks: true` (or `{kinds: ["choice"]}`) to let the model ask the user typed questions in the chat. Asks are off by default; with them off, tools, system prompt, and SSE events are unchanged. See [Agent UI Asks](agent-ui-asks.md).
|
|
286
287
|
|
|
287
|
-
Pass `uiBlocks: true` (or `{hostActions, html, imageHosts, repair, richBlocks, datasetTtlDays, datasetMaxRows}`) to require each assistant reply to be a block document. `html: true` allows `html` blocks and sanitizes them before they are stored. `imageHosts` lists hostnames allowed on `https` image sources. The client receives that document once, after missing action ids are filled, `repair: true` rewrites it, and html is sanitized, and before `{ask}`. `{replace: "text", text}` is sent only when text was already streamed and then changed. `datasetTtlDays` defaults to `0` (keep the dataset). `datasetMaxRows` defaults to 50,000. Off by default; with it off, the system prompt, SSE events, `/gpt/datasets`, and `/gpt/actions` are unchanged. When it is on, the system prompt gains `TERRENO_UI_BLOCKS_SYSTEM_PROMPT` (host callback names included when `hostActions` is set). After the final text, the route validates it and sends `{blocks: {ok, errors, warnings}}` before `{done}`. `hostActions` is the callback allowlist: a name outside it fails with `UNKNOWN_HOST_ACTION`. A stepper or checklist `callback.name` must also name an action whose `handles` is that block; a stepper that names any other registered action fails with `UNKNOWN_HOST_ACTION`. `{repair: true}` runs one repair call when validation fails and stores that reply. A document that is still invalid is stored with a `Block validation errors:` note so the next turn sees it. See [Validate a block document locally](../how-to/agent-ui-blocks.md).
|
|
288
|
+
Pass `uiBlocks: true` (or `{hostActions, html, imageHosts, repair, richBlocks, datasetTtlDays, datasetMaxRows}`) to require each assistant reply to be a block document. `html: true` allows `html` blocks and sanitizes them before they are stored. `imageHosts` lists hostnames allowed on `https` image sources in `image`, `gallery`, and `list` blocks; Markdown images in `text` blocks and plain replies are not checked against it (see [Agent UI asks](../explanation/agent-ui-asks.md)). The client receives that document once, after missing action ids are filled, `repair: true` rewrites it, and html is sanitized, and before `{ask}`. `{replace: "text", text}` is sent only when text was already streamed and then changed. `datasetTtlDays` defaults to `0` (keep the dataset). `datasetMaxRows` defaults to 50,000. Off by default; with it off, the system prompt, SSE events, `/gpt/datasets`, and `/gpt/actions` are unchanged. When it is on, the system prompt gains `TERRENO_UI_BLOCKS_SYSTEM_PROMPT` (host callback names included when `hostActions` is set). After the final text, the route validates it and sends `{blocks: {ok, errors, warnings}}` before `{done}`. `hostActions` is the callback allowlist: a name outside it fails with `UNKNOWN_HOST_ACTION`. A stepper or checklist `callback.name` must also name an action whose `handles` is that block; a stepper that names any other registered action fails with `UNKNOWN_HOST_ACTION`. `{repair: true}` runs one repair call when validation fails and stores that reply. A document that is still invalid is stored with a `Block validation errors:` note so the next turn sees it. See [Validate a block document locally](../how-to/agent-ui-blocks.md).
|
|
288
289
|
|
|
289
290
|
`uiBlocks.richBlocks` defaults to `true`: the prompt offers the rich blocks, and `stepper` when a host action has `handles: "stepper"`. The `checklist` line names the `handles: "checklist"` actions as its callback, or, without one, tells the model to leave `callback` out so ticks stay local. This is a behaviour change on upgrade: every host with `uiBlocks` on gets the new blocks in its prompt. Set `richBlocks: false` while shipped clients (for example older native builds) cannot render them; the prompt is then the same as before rich blocks, even with a stepper action registered. The opt-out changes only what the model is told to write. Validation accepts the rich blocks either way.
|
|
290
291
|
|
|
@@ -379,7 +380,7 @@ Errors raised before the stream starts return JSON `{status, title, detail, fiel
|
|
|
379
380
|
|
|
380
381
|
Each `attachments` item is `{type: "image" | "file", url, mimeType, filename?}`. The `url` must be `http(s):` or `data:`. Client-only URLs (`blob:`, `file:`, `content:`, `ph:`) return `400` before streaming starts, because neither the model provider nor a later page load can read them. Upload the file with `POST /files/upload` first, or send it as a `data:` URL.
|
|
381
382
|
|
|
382
|
-
When `fileStorageService` is set, `data:` attachments are uploaded with `FileStorageService.upload`. The saved user prompt then stores the storage `url` plus `gcsKey`, not the base64 payload. The model still receives the original data for that turn. On later turns,
|
|
383
|
+
When `fileStorageService` is set, `data:` attachments are uploaded with `FileStorageService.upload`. The saved user prompt then stores the storage `url` plus `gcsKey`, not the base64 payload. The model still receives the original data for that turn. On later turns, a part whose `gcsKey` names a live `FileAttachment` owned by the conversation's owner is sent to the model as a 1-hour signed URL; any other part keeps its stored `url` and is not signed. A custom `fileStorageService` whose `upload` does not create `FileAttachment` records therefore gets no signed URLs on later turns. A failed upload returns `502 Attachment upload failed`. Without storage, attachments are saved as sent.
|
|
383
384
|
|
|
384
385
|
`fileUploadsEnabled` turns uploads off without removing storage. Pass `false`, or a function that returns `false`, and any prompt that includes attachments returns `403 File uploads are disabled` before streaming. Omit it, or pass `true`, to leave uploads enabled. The example app wires this to the `file-uploads` feature flag.
|
|
385
386
|
|
|
@@ -409,7 +410,7 @@ CRUD at `/gpt/histories` via `modelRouter`:
|
|
|
409
410
|
| Create, List | `IsAuthenticated` |
|
|
410
411
|
| Read, Update, Delete | `IsOwner` |
|
|
411
412
|
|
|
412
|
-
Query filtered by `userId`; sort `-updated`; query fields `userId`, `projectId`. Create sets `userId` to the caller, so `{}` is a valid create body even when the app validates request bodies. Create and update bodies drop `pendingAsk` (including dotted `pendingAsk.*` paths), so only a chat turn writes it; the OpenAPI spec marks it `readOnly` on create and update.
|
|
413
|
+
Query filtered by `userId`; sort `-updated`; query fields `userId`, `projectId`. Create sets `userId` to the caller, so `{}` is a valid create body even when the app validates request bodies. Create and update bodies drop `pendingAsk` (including dotted `pendingAsk.*` paths), so only a chat turn writes it; the OpenAPI spec marks it `readOnly` on create and update. Create and update bodies also drop any `gcsKey` on `prompts[].content[]` parts (including dotted `prompts.N.content.M.gcsKey` paths) that the history does not already store, so a client can send stored prompts back but cannot add an attachment key.
|
|
413
414
|
|
|
414
415
|
Pass `chat`, the options given to `addGptRoutes`, to add two headless actions for clients that do not read server-sent events, such as a watch app. Both exist only when `chat` turns `asks` on: without `chat`, or with `asks` off, neither action exists, so a host that never turned asks on gets no new endpoints. `AiApp` adds them when its `asks` option is set.
|
|
415
416
|
|
|
@@ -495,6 +496,45 @@ new AiApp({
|
|
|
495
496
|
| `openApiOptions` | Passed to route OpenAPI builders |
|
|
496
497
|
| `projects` | Default `true`. `false` skips the `/gpt/projects` routes, so `AiApp` never registers the `Project` model |
|
|
497
498
|
|
|
499
|
+
## Cost and abuse limits
|
|
500
|
+
|
|
501
|
+
`@terreno/ai` sets no per-user limit on concurrent turns, tokens, or spend for `/gpt/prompt`
|
|
502
|
+
or the headless turn action (`POST /gpt/histories/:id/turn`). Any signed-in user can start as
|
|
503
|
+
many turns as the host lets through, and each one calls the model with the app's server-side
|
|
504
|
+
key unless the request brings its own `x-ai-api-key`.
|
|
505
|
+
|
|
506
|
+
A reply keeps running after the client disconnects. This is by design: `/gpt/prompt` saves the
|
|
507
|
+
reply as a `streaming` prompt so a reload can resume it through `GET /gpt/histories/:id/stream`,
|
|
508
|
+
and the turn action finishes and saves the turn whether or not the client waits. Closing the tab
|
|
509
|
+
does not stop the model call or its cost.
|
|
510
|
+
|
|
511
|
+
Before exposing AI routes to users, do both:
|
|
512
|
+
|
|
513
|
+
1. Enable HTTP rate limiting on `TerrenoApp`. It is off unless you pass `rateLimit`. AI routes
|
|
514
|
+
fall in the `api` bucket, which counts requests, not tokens:
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
new TerrenoApp({
|
|
518
|
+
userModel: User,
|
|
519
|
+
rateLimit: {
|
|
520
|
+
store: "redis", // "memory" (default) | "redis" | "mongo"; use a shared store with more than one instance
|
|
521
|
+
trustProxy: 1, // behind a load balancer such as Cloud Run
|
|
522
|
+
limits: {
|
|
523
|
+
windowMs: 15 * 60 * 1000, // default 15 minutes
|
|
524
|
+
apiMax: 600, // default; lower it to bound chat turns per user or IP
|
|
525
|
+
authMax: 20, // default
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
For a tighter limit on chat alone, add `createRouteRateLimitMiddleware({name, max, windowMs,
|
|
532
|
+
store, keyBy})` from `@terreno/api` in front of the AI routes. See
|
|
533
|
+
[Rate limiting](../how-to/rate-limiting.md).
|
|
534
|
+
2. Set a budget, quota, or spend alert with the model provider (for example a Google Cloud
|
|
535
|
+
budget alert and Vertex AI or Gemini API quotas), so a runaway caller or bug is caught by the
|
|
536
|
+
provider even when the app's own limits are loose.
|
|
537
|
+
|
|
498
538
|
## LangfuseApp plugin
|
|
499
539
|
|
|
500
540
|
Optional Langfuse admin UI and tracing:
|
|
@@ -546,7 +586,7 @@ the `chat-safety-agreement` llm-judge, and the 12-item proofread synthetic datas
|
|
|
546
586
|
labeled for 988 vs care-team routing, toxicity, a privacy leak, and a dismissive reply.
|
|
547
587
|
Admin → Scripts → `seedChatSafetyDataset`, or `bun run script seedChatSafetyDataset --wet` from
|
|
548
588
|
`example-backend`, loads that set into an already-running database. `SEED_DEFAULTS=true` loads
|
|
549
|
-
it on boot,
|
|
589
|
+
it on boot outside deployed (`NODE_ENV=production`) processes, which ignore the flag. Invalid price JSON or negative/non-numeric prices
|
|
550
590
|
fail startup with `AI_OBS_PRICE_MAP_JSON` in the error.
|
|
551
591
|
|
|
552
592
|
`POST /ai/example-summarize` (example backend) runs that seeded prompt with
|
|
@@ -762,6 +802,8 @@ await mcp.reconnectServer("my-server");
|
|
|
762
802
|
await mcp.disconnect();
|
|
763
803
|
```
|
|
764
804
|
|
|
805
|
+
`getTools()` merges every connected server's tools by name. When two servers offer the same name, the first server in the config keeps it and the later one is dropped with a warning. In a chat turn, MCP tools never replace a route tool or a `createRequestTools` tool of the same name: the MCP tool is dropped with a warning, so a host tool keeps its `execute` and its `needsApproval` policy.
|
|
806
|
+
|
|
765
807
|
## Gemini and Vertex helpers
|
|
766
808
|
|
|
767
809
|
**Gemini Developer API** (API-key based):
|
|
@@ -788,6 +830,8 @@ const model = vertex.languageModel("gemini-3.8-flash");
|
|
|
788
830
|
|
|
789
831
|
Env fallbacks: `GOOGLE_VERTEX_PROJECT`, `GOOGLE_VERTEX_LOCATION` (default `global`).
|
|
790
832
|
|
|
833
|
+
`languageModel`, `imageModel`, and `isModelAllowed` accept only a plain model id: letters, digits, `.`, `_`, and `-`, with an optional `publishers/google/models/` or `models/` prefix and an optional `@version`. Any other id (one with another `/`, `:`, `?`, or `#`) is refused with `400 Model not permitted`, with or without `allowedModels`. With `allowedModels` set, the id without its prefix and `@version` must match an entry. The provider is given the checked id without its prefix, so a model id sent in a request body cannot name a different model than the one the allow-list checked.
|
|
834
|
+
|
|
791
835
|
## Web search types
|
|
792
836
|
|
|
793
837
|
`WebSearchProvider` and `WebSearchResult` define a pluggable search interface for custom Vercel AI SDK tools. The package does not ship a default provider — implement `search(query)` and wire it into a `tool()` passed to `AiApp` `tools`.
|