@terreno/mcp 56.0.0-beta.0 → 57.0.0-beta.1
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/LICENSE +21 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +10 -11
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/templates/bootstrap/backend/types/models/userTypes.template.md +5 -4
- package/dist/docs/templates/tools/model.template.md +4 -2
- package/dist/docs/upgrades/0.31.0.md +60 -0
- package/dist/docs/upgrades/56.0.0-beta.2.md +59 -0
- package/dist/docs/upgrades/57.0.0-beta.1.md +41 -0
- package/dist/docs/versioned/next/README.md +1 -0
- package/dist/docs/versioned/next/explanation/README.md +7 -1
- package/dist/docs/versioned/next/explanation/deployment-architecture-gcp.md +65 -0
- package/dist/docs/versioned/next/explanation/deployment-baseline.md +78 -0
- package/dist/docs/versioned/next/explanation/positioning.md +164 -0
- package/dist/docs/versioned/next/explanation/repository-settings.md +44 -0
- package/dist/docs/versioned/next/explanation/roadmap-process.md +330 -0
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +887 -0
- package/dist/docs/versioned/next/how-to/README.md +8 -0
- package/dist/docs/versioned/next/how-to/build-for-web.md +69 -0
- package/dist/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +115 -0
- package/dist/docs/versioned/next/how-to/deploy-to-gcp.md +20 -228
- package/dist/docs/versioned/next/how-to/deploy-web-to-gcs-cdn.md +183 -0
- package/dist/docs/versioned/next/implementationPlans/PLAN_INDEX.md +31 -0
- package/dist/docs/versioned/next/implementationPlans/README.md +56 -30
- package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +2 -1
- package/dist/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +115 -0
- package/dist/docs/versioned/next/implementationPlans/billing-stripe.md +158 -0
- package/dist/docs/versioned/next/implementationPlans/build-terreno-app-validation.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/comms-abstraction.md +323 -0
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-expo-push.md +107 -0
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +157 -0
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-twilio-sms.md +155 -0
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-twilio-verify.md +99 -0
- package/dist/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +180 -0
- package/dist/docs/versioned/next/implementationPlans/deploy-to-gcp.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/deploy-to-vercel.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/deployment-foundation.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/docs-reference-coverage.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/docs-tutorials-ai-first.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/examples-demo-coverage.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/infra-mcp-research.md +188 -0
- package/dist/docs/versioned/next/implementationPlans/infra-mcp.md +269 -0
- package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +126 -0
- package/dist/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +269 -0
- package/dist/docs/versioned/next/implementationPlans/native-module-baseline.md +105 -0
- package/dist/docs/versioned/next/implementationPlans/orgs-and-teams.md +158 -0
- package/dist/docs/versioned/next/implementationPlans/oss-governance-baseline.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/password-reset-and-email-verification.md +147 -0
- package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/public-roadmap-github.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/upgrade-guides-and-skill.md +1 -0
- package/dist/docs/versioned/next/implementationPlans/web-ssr-and-admin-spa.md +1 -0
- package/dist/docs/versioned/next/reference/README.md +5 -0
- package/dist/docs/versioned/next/reference/admin-spa.md +130 -0
- package/dist/docs/versioned/next/reference/ai.md +519 -0
- package/dist/docs/versioned/next/reference/comms.md +132 -0
- package/dist/docs/versioned/next/reference/environment-variables.md +229 -382
- package/dist/docs/versioned/next/reference/test.md +108 -0
- package/dist/docs/versioned/next/roadmap.md +9 -0
- package/dist/docs/versioned/next/tasks/README.md +42 -0
- package/dist/docs/versioned/next/tasks/agentic-sdlc-plugin.md +5 -5
- package/dist/docs/versioned/next/tasks/billing-stripe.md +75 -0
- package/dist/docs/versioned/next/tasks/comms-abstraction.md +90 -0
- package/dist/docs/versioned/next/tasks/comms-adapter-expo-push.md +29 -0
- package/dist/docs/versioned/next/tasks/comms-adapter-sendgrid.md +39 -0
- package/dist/docs/versioned/next/tasks/comms-adapter-twilio-sms.md +39 -0
- package/dist/docs/versioned/next/tasks/comms-adapter-twilio-verify.md +19 -0
- package/dist/docs/versioned/next/tasks/comms-admin-dashboard.md +52 -0
- package/dist/docs/versioned/next/tasks/deploy-to-gcp.md +11 -11
- package/dist/docs/versioned/next/tasks/deployment-foundation.md +12 -12
- package/dist/docs/versioned/next/tasks/docs-reference-coverage.md +4 -4
- package/dist/docs/versioned/next/tasks/infra-mcp.md +43 -0
- package/dist/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +215 -0
- package/dist/docs/versioned/next/tasks/native-module-baseline.md +42 -0
- package/dist/docs/versioned/next/tasks/orgs-and-teams.md +70 -0
- package/dist/docs/versioned/next/tasks/oss-governance-baseline.md +18 -18
- package/dist/docs/versioned/next/tasks/password-reset-and-email-verification.md +60 -0
- package/dist/docs/versioned/next/tasks/positioning-django-rails-universal.md +4 -4
- package/dist/docs/versioned/next/tasks/public-roadmap-github.md +15 -15
- package/package.json +4 -3
- package/src/docs/templates/bootstrap/backend/types/models/userTypes.template.md +5 -4
- package/src/docs/templates/tools/model.template.md +4 -2
- package/src/docs/upgrades/0.31.0.md +60 -0
- package/src/docs/upgrades/56.0.0-beta.2.md +59 -0
- package/src/docs/upgrades/57.0.0-beta.1.md +41 -0
- package/src/docs/versioned/next/README.md +1 -0
- package/src/docs/versioned/next/explanation/README.md +7 -1
- package/src/docs/versioned/next/explanation/deployment-architecture-gcp.md +65 -0
- package/src/docs/versioned/next/explanation/deployment-baseline.md +78 -0
- package/src/docs/versioned/next/explanation/positioning.md +164 -0
- package/src/docs/versioned/next/explanation/repository-settings.md +44 -0
- package/src/docs/versioned/next/explanation/roadmap-process.md +330 -0
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +887 -0
- package/src/docs/versioned/next/how-to/README.md +8 -0
- package/src/docs/versioned/next/how-to/build-for-web.md +69 -0
- package/src/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +115 -0
- package/src/docs/versioned/next/how-to/deploy-to-gcp.md +20 -228
- package/src/docs/versioned/next/how-to/deploy-web-to-gcs-cdn.md +183 -0
- package/src/docs/versioned/next/implementationPlans/PLAN_INDEX.md +31 -0
- package/src/docs/versioned/next/implementationPlans/README.md +56 -30
- package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +2 -1
- package/src/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +1 -0
- package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +115 -0
- package/src/docs/versioned/next/implementationPlans/billing-stripe.md +158 -0
- package/src/docs/versioned/next/implementationPlans/build-terreno-app-validation.md +1 -0
- package/src/docs/versioned/next/implementationPlans/comms-abstraction.md +323 -0
- package/src/docs/versioned/next/implementationPlans/comms-adapter-expo-push.md +107 -0
- package/src/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +157 -0
- package/src/docs/versioned/next/implementationPlans/comms-adapter-twilio-sms.md +155 -0
- package/src/docs/versioned/next/implementationPlans/comms-adapter-twilio-verify.md +99 -0
- package/src/docs/versioned/next/implementationPlans/comms-admin-dashboard.md +180 -0
- package/src/docs/versioned/next/implementationPlans/deploy-to-gcp.md +1 -0
- package/src/docs/versioned/next/implementationPlans/deploy-to-vercel.md +1 -0
- package/src/docs/versioned/next/implementationPlans/deployment-foundation.md +1 -0
- package/src/docs/versioned/next/implementationPlans/docs-reference-coverage.md +1 -0
- package/src/docs/versioned/next/implementationPlans/docs-tutorials-ai-first.md +1 -0
- package/src/docs/versioned/next/implementationPlans/examples-demo-coverage.md +1 -0
- package/src/docs/versioned/next/implementationPlans/infra-mcp-research.md +188 -0
- package/src/docs/versioned/next/implementationPlans/infra-mcp.md +269 -0
- package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci-research.md +126 -0
- package/src/docs/versioned/next/implementationPlans/migrate-cicd-to-circleci.md +269 -0
- package/src/docs/versioned/next/implementationPlans/native-module-baseline.md +105 -0
- package/src/docs/versioned/next/implementationPlans/orgs-and-teams.md +158 -0
- package/src/docs/versioned/next/implementationPlans/oss-governance-baseline.md +1 -0
- package/src/docs/versioned/next/implementationPlans/password-reset-and-email-verification.md +147 -0
- package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +1 -0
- package/src/docs/versioned/next/implementationPlans/public-roadmap-github.md +1 -0
- package/src/docs/versioned/next/implementationPlans/upgrade-guides-and-skill.md +1 -0
- package/src/docs/versioned/next/implementationPlans/web-ssr-and-admin-spa.md +1 -0
- package/src/docs/versioned/next/reference/README.md +5 -0
- package/src/docs/versioned/next/reference/admin-spa.md +130 -0
- package/src/docs/versioned/next/reference/ai.md +519 -0
- package/src/docs/versioned/next/reference/comms.md +132 -0
- package/src/docs/versioned/next/reference/environment-variables.md +229 -382
- package/src/docs/versioned/next/reference/test.md +108 -0
- package/src/docs/versioned/next/roadmap.md +9 -0
- package/src/docs/versioned/next/tasks/README.md +42 -0
- package/src/docs/versioned/next/tasks/agentic-sdlc-plugin.md +5 -5
- package/src/docs/versioned/next/tasks/billing-stripe.md +75 -0
- package/src/docs/versioned/next/tasks/comms-abstraction.md +90 -0
- package/src/docs/versioned/next/tasks/comms-adapter-expo-push.md +29 -0
- package/src/docs/versioned/next/tasks/comms-adapter-sendgrid.md +39 -0
- package/src/docs/versioned/next/tasks/comms-adapter-twilio-sms.md +39 -0
- package/src/docs/versioned/next/tasks/comms-adapter-twilio-verify.md +19 -0
- package/src/docs/versioned/next/tasks/comms-admin-dashboard.md +52 -0
- package/src/docs/versioned/next/tasks/deploy-to-gcp.md +11 -11
- package/src/docs/versioned/next/tasks/deployment-foundation.md +12 -12
- package/src/docs/versioned/next/tasks/docs-reference-coverage.md +4 -4
- package/src/docs/versioned/next/tasks/infra-mcp.md +43 -0
- package/src/docs/versioned/next/tasks/migrate-cicd-to-circleci.md +215 -0
- package/src/docs/versioned/next/tasks/native-module-baseline.md +42 -0
- package/src/docs/versioned/next/tasks/orgs-and-teams.md +70 -0
- package/src/docs/versioned/next/tasks/oss-governance-baseline.md +18 -18
- package/src/docs/versioned/next/tasks/password-reset-and-email-verification.md +60 -0
- package/src/docs/versioned/next/tasks/positioning-django-rails-universal.md +4 -4
- package/src/docs/versioned/next/tasks/public-roadmap-github.md +15 -15
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
# @terreno/ai
|
|
2
|
+
|
|
3
|
+
AI service layer for Terreno backends: provider-agnostic chat via the Vercel AI SDK, request logging, GPT history, projects, file uploads, MCP tools, and optional Langfuse integration.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Install](#install)
|
|
8
|
+
- [Commands](#commands)
|
|
9
|
+
- [Architecture](#architecture)
|
|
10
|
+
- [Key exports](#key-exports)
|
|
11
|
+
- [AIService](#aiservice)
|
|
12
|
+
- [Structured JSON output](#structured-json-output)
|
|
13
|
+
- [TemperaturePresets](#temperaturepresets)
|
|
14
|
+
- [Models](#models)
|
|
15
|
+
- [Route registrars](#route-registrars)
|
|
16
|
+
- [AiApp plugin](#aiapp-plugin)
|
|
17
|
+
- [LangfuseApp plugin](#langfuseapp-plugin)
|
|
18
|
+
- [Langfuse integration](#langfuse-integration)
|
|
19
|
+
- [FileStorageService](#filestorageservice)
|
|
20
|
+
- [MCPService](#mcpservice)
|
|
21
|
+
- [Gemini and Vertex helpers](#gemini-and-vertex-helpers)
|
|
22
|
+
- [Web search types](#web-search-types)
|
|
23
|
+
- [Integration example](#integration-example)
|
|
24
|
+
- [Environment variables](#environment-variables)
|
|
25
|
+
- [Conventions](#conventions)
|
|
26
|
+
- [Testing](#testing)
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bun add @terreno/ai @terreno/api mongoose
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Peer dependencies: `@terreno/api`, `mongoose` (^8.0.0). Consuming apps also install a Vercel AI SDK provider (e.g. `@ai-sdk/google`) for their chosen model.
|
|
35
|
+
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
From the `@terreno/ai` package directory:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
bun run compile # Compile TypeScript
|
|
42
|
+
bun run dev # Watch mode (tsc -w)
|
|
43
|
+
bun run test # Run tests (with bunSetup preload)
|
|
44
|
+
bun run lint # Lint code
|
|
45
|
+
bun run lint:fix # Fix lint issues
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Architecture
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
src/
|
|
52
|
+
index.ts # Public exports
|
|
53
|
+
aiApp.ts # AiApp TerrenoPlugin
|
|
54
|
+
langfuseApp.ts # LangfuseApp TerrenoPlugin
|
|
55
|
+
langfuseClient.ts # Langfuse SDK client lifecycle
|
|
56
|
+
langfuseCache.ts # Prompt/trace caching
|
|
57
|
+
langfusePrompts.ts # Prompt compile/fetch helpers
|
|
58
|
+
langfuseTracing.ts # OpenTelemetry tracing setup
|
|
59
|
+
langfuseVercelAi.ts # Bridge Langfuse prompts to Vercel AI SDK
|
|
60
|
+
models/
|
|
61
|
+
aiRequest.ts # AI request logging model
|
|
62
|
+
gptHistory.ts # Conversation history model
|
|
63
|
+
fileAttachment.ts # Uploaded file metadata
|
|
64
|
+
project.ts # GPT project + memories
|
|
65
|
+
routes/
|
|
66
|
+
gpt.ts # Streaming chat, remix, tools, ratings
|
|
67
|
+
gptHistories.ts # History CRUD
|
|
68
|
+
aiRequestsExplorer.ts # Admin request explorer
|
|
69
|
+
files.ts # File upload/signed URL/delete
|
|
70
|
+
projects.ts # Project CRUD + memories
|
|
71
|
+
mcp.ts # MCP server status and tools
|
|
72
|
+
service/
|
|
73
|
+
aiService.ts # Provider-agnostic AI service
|
|
74
|
+
fileStorage.ts # GCS upload helper
|
|
75
|
+
mcpService.ts # MCP client connections
|
|
76
|
+
parseAiJson.ts # LLM JSON normalization/parsing
|
|
77
|
+
prompts.ts # System prompt constants
|
|
78
|
+
gemini.ts # Gemini Developer API model listing
|
|
79
|
+
vertex.ts # Vertex AI provider helpers
|
|
80
|
+
webSearchTool.ts # WebSearchProvider interface
|
|
81
|
+
types/ # Shared TypeScript types
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Key exports
|
|
85
|
+
|
|
86
|
+
- **Plugins:** `AiApp`, `LangfuseApp`
|
|
87
|
+
- **Service:** `AIService`, `TemperaturePresets`, `FileStorageService`, `MCPService`
|
|
88
|
+
- **Models:** `AIRequest`, `GptHistory`, `FileAttachment`, `Project`
|
|
89
|
+
- **Routes:** `addGptRoutes`, `addGptHistoryRoutes`, `addAiRequestsExplorerRoutes`, `addFileRoutes`, `addProjectRoutes`, `addMcpRoutes`
|
|
90
|
+
- **Structured output:** `parseAiJson`, `normalizeLlmJsonTextForStructuredOutput`, re-exported `Output`, `jsonSchema`, `JSONValue`, `FlexibleSchema` from `ai`
|
|
91
|
+
- **Langfuse:** `initLangfuseClient`, `getLangfuseClient`, `shutdownLangfuseClient`, `compilePrompt`, `createPrompt`, `getPrompt`, `createTelemetryConfig`, `preparePromptForAI`, `initTracing`, `shutdownTracing`, `LangfuseCache`, cache helpers
|
|
92
|
+
- **Gemini / Vertex:** `listGeminiApiModels`, `normalizeGeminiModelId`, `GEMINI_API_BASE_URL`, `createVertexProvider`, `listEnabledVertexModels`, `verifyVertexModelsEnabled`, `assertVertexModelsEnabled`, `isVertexModelAllowed`, `normalizeVertexModelId`, `DEFAULT_VERTEX_LOCATION`
|
|
93
|
+
- **Prompts:** `CONTENT_SUMMARY_PROMPT`, `DEFAULT_GPT_MEMORY`, `JSON_VALUE_SYSTEM_PROMPT`, `REMIX_PROMPT`, `TITLE_GENERATION_PROMPT`, `TRANSLATION_PROMPT`
|
|
94
|
+
- **Web search:** `WebSearchProvider`, `WebSearchResult` types
|
|
95
|
+
|
|
96
|
+
## AIService
|
|
97
|
+
|
|
98
|
+
Provider-agnostic wrapper around a Vercel AI SDK `LanguageModel`. The consuming app supplies the model instance.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import {AIService} from "@terreno/ai";
|
|
102
|
+
import {google} from "@ai-sdk/google";
|
|
103
|
+
|
|
104
|
+
const aiService = new AIService({
|
|
105
|
+
model: google("gemini-2.5-flash"),
|
|
106
|
+
defaultTemperature: 1.0,
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Constructor options
|
|
111
|
+
|
|
112
|
+
| Option | Description |
|
|
113
|
+
|--------|-------------|
|
|
114
|
+
| `model` | Vercel AI SDK `LanguageModel` instance (required) |
|
|
115
|
+
| `defaultTemperature` | Default temperature for text/stream calls (default: `TemperaturePresets.DEFAULT`) |
|
|
116
|
+
|
|
117
|
+
### Properties
|
|
118
|
+
|
|
119
|
+
| Property | Description |
|
|
120
|
+
|----------|-------------|
|
|
121
|
+
| `model` | Configured `LanguageModel` |
|
|
122
|
+
| `defaultTemperature` | Default temperature |
|
|
123
|
+
| `modelId` | Resolved model identifier string |
|
|
124
|
+
|
|
125
|
+
### Methods
|
|
126
|
+
|
|
127
|
+
| Method | Description |
|
|
128
|
+
|--------|-------------|
|
|
129
|
+
| `generateText(options)` | Non-streaming text generation; logs as `requestType: "general"` |
|
|
130
|
+
| `generateJsonValue(options)` | Any JSON value via `Output.json()`; logs as `"json_value"` |
|
|
131
|
+
| `generateJsonObject(options)` | Typed object from schema/Zod via `Output.object()`; logs as `"json_object"` |
|
|
132
|
+
| `generateJsonArray(options)` | Typed array via `Output.array()`; logs as `"json_array"` |
|
|
133
|
+
| `generateTextStream(options)` | Async generator of text chunks; logs full response after stream completes |
|
|
134
|
+
| `generateRemix(options)` | Reword text using `REMIX_PROMPT` at `TemperaturePresets.BALANCED` |
|
|
135
|
+
| `generateSummary(options)` | Summarize text using `CONTENT_SUMMARY_PROMPT` at `TemperaturePresets.LOW` |
|
|
136
|
+
| `translateText(options)` | Translate text using `TRANSLATION_PROMPT` at `TemperaturePresets.LOW` |
|
|
137
|
+
| `buildMessages(prompts)` | Convert `GptHistoryPrompt[]` to Vercel AI SDK `ModelMessage[]` (skips tool-call/result entries) |
|
|
138
|
+
| `generateChatStream(options)` | Stream multi-turn chat with optional tools; logs prompt as joined message text |
|
|
139
|
+
|
|
140
|
+
All generation methods log to `AIRequest` via private `logRequest()`. Logging failures never throw.
|
|
141
|
+
|
|
142
|
+
## Structured JSON output
|
|
143
|
+
|
|
144
|
+
`generateJsonValue`, `generateJsonObject`, and `generateJsonArray`:
|
|
145
|
+
|
|
146
|
+
- Default to `TemperaturePresets.DETERMINISTIC` when `temperature` is omitted.
|
|
147
|
+
- Use `JSON_VALUE_SYSTEM_PROMPT` when `systemPrompt` is omitted.
|
|
148
|
+
- Run model text through `normalizeLlmJsonTextForStructuredOutput` before Vercel `Output.*` parsing (strips fences, preamble, balanced JSON slice, trailing commas, smart-quote repair).
|
|
149
|
+
- On failure: `logger.error` records prompt, system prompt, raw model text, and error details; `AIRequest` stores `response` (raw text or sentinel), `error`, and `metadata` (`system`, `finishReason`, `errorStack`, `rawModelTextCaptured`).
|
|
150
|
+
|
|
151
|
+
Standalone helpers:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import {parseAiJson, normalizeLlmJsonTextForStructuredOutput} from "@terreno/ai";
|
|
155
|
+
|
|
156
|
+
const result = parseAiJson<MyType>(rawLlmText);
|
|
157
|
+
if (result.success) {
|
|
158
|
+
console.info(result.data);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Re-exported from `ai` for schema building: `Output`, `jsonSchema`, types `JSONValue`, `FlexibleSchema`.
|
|
163
|
+
|
|
164
|
+
## TemperaturePresets
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import {TemperaturePresets} from "@terreno/ai";
|
|
168
|
+
|
|
169
|
+
TemperaturePresets.DETERMINISTIC // 0
|
|
170
|
+
TemperaturePresets.LOW // 0.3
|
|
171
|
+
TemperaturePresets.BALANCED // 0.7
|
|
172
|
+
TemperaturePresets.DEFAULT // 1.0
|
|
173
|
+
TemperaturePresets.HIGH // 1.5
|
|
174
|
+
TemperaturePresets.MAXIMUM // 2.0
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Models
|
|
178
|
+
|
|
179
|
+
### AIRequest
|
|
180
|
+
|
|
181
|
+
Logs all AI calls for monitoring and admin explorer.
|
|
182
|
+
|
|
183
|
+
| Field | Type | Description |
|
|
184
|
+
|-------|------|-------------|
|
|
185
|
+
| `aiModel` | string | Model identifier (field name avoids Mongoose `model` conflict) |
|
|
186
|
+
| `prompt` | string | Input prompt |
|
|
187
|
+
| `requestType` | string | e.g. `general`, `remix`, `summarization`, `translation`, `json_value`, `json_object`, `json_array` |
|
|
188
|
+
| `response` | string? | Response text |
|
|
189
|
+
| `responseTime` | number? | Milliseconds |
|
|
190
|
+
| `tokensUsed` | number? | Total tokens |
|
|
191
|
+
| `userId` | ObjectId? | Requesting user |
|
|
192
|
+
| `error` | string? | Error message |
|
|
193
|
+
| `metadata` | Mixed? | Extra data (e.g. structured-output debug) |
|
|
194
|
+
| `parentRequestId` | ObjectId? | Parent in multi-agent workflow |
|
|
195
|
+
| `subRequestIds` | ObjectId[]? | Child request refs |
|
|
196
|
+
| `totalResponseTime` | number? | Combined sub-request time |
|
|
197
|
+
| `totalTokensUsed` | number? | Combined sub-request tokens |
|
|
198
|
+
|
|
199
|
+
**Statics:** `AIRequest.logRequest(params)`, `AIRequest.logMultiAgentRequest(params)`
|
|
200
|
+
|
|
201
|
+
**Plugins:** `createdUpdatedPlugin`, `isDeletedPlugin`, `findOneOrNone`, `findExactlyOne`
|
|
202
|
+
|
|
203
|
+
### GptHistory
|
|
204
|
+
|
|
205
|
+
Conversation history with multi-modal prompts.
|
|
206
|
+
|
|
207
|
+
| Field | Type | Description |
|
|
208
|
+
|-------|------|-------------|
|
|
209
|
+
| `userId` | ObjectId | Owner (required) |
|
|
210
|
+
| `title` | string? | Auto-generated on first `/gpt/prompt` response when empty |
|
|
211
|
+
| `projectId` | ObjectId? | Optional project association |
|
|
212
|
+
| `prompts` | array | Messages: `text`, `type` (`user` \| `assistant` \| `system` \| `tool-call` \| `tool-result`), optional `content` parts, `model`, `rating`, tool fields |
|
|
213
|
+
|
|
214
|
+
**Virtual:** `ownerId` aliases `userId` for `Permissions.IsOwner`.
|
|
215
|
+
|
|
216
|
+
### FileAttachment
|
|
217
|
+
|
|
218
|
+
Metadata for files stored in GCS.
|
|
219
|
+
|
|
220
|
+
| Field | Type | Description |
|
|
221
|
+
|-------|------|-------------|
|
|
222
|
+
| `userId` | ObjectId | Uploader |
|
|
223
|
+
| `filename` | string | Original filename |
|
|
224
|
+
| `gcsKey` | string | Unique GCS object key |
|
|
225
|
+
| `mimeType` | string | MIME type |
|
|
226
|
+
| `size` | number | Bytes |
|
|
227
|
+
| `url` | string | Public GCS URL |
|
|
228
|
+
|
|
229
|
+
**Virtual:** `ownerId` aliases `userId`.
|
|
230
|
+
|
|
231
|
+
### Project
|
|
232
|
+
|
|
233
|
+
GPT project with persistent context and memories.
|
|
234
|
+
|
|
235
|
+
| Field | Type | Description |
|
|
236
|
+
|-------|------|-------------|
|
|
237
|
+
| `userId` | ObjectId | Owner |
|
|
238
|
+
| `name` | string | Project name |
|
|
239
|
+
| `systemContext` | string | Prepended to every chat in this project |
|
|
240
|
+
| `memories` | array | `{text, category?, source: "user" \| "auto"}` entries |
|
|
241
|
+
|
|
242
|
+
**Virtual:** `ownerId` aliases `userId`.
|
|
243
|
+
|
|
244
|
+
## Route registrars
|
|
245
|
+
|
|
246
|
+
### addGptRoutes(router, options)
|
|
247
|
+
|
|
248
|
+
| Endpoint | Method | Auth | Description |
|
|
249
|
+
|----------|--------|------|-------------|
|
|
250
|
+
| `/gpt/prompt` | POST | `IsAuthenticated` | SSE streaming chat; body: `prompt`, optional `historyId`, `systemPrompt`, `attachments`, `model`, `projectId` |
|
|
251
|
+
| `/gpt/remix` | POST | `IsAuthenticated` | Non-streaming text remix; body: `{text}` |
|
|
252
|
+
| `/gpt/histories/:id/rating` | PATCH | `IsAuthenticated` | Rate a prompt; body: `{promptIndex, rating: "up" \| "down" \| null}` |
|
|
253
|
+
| `/gpt/tools` | GET | `IsAuthenticated` | List builtin + MCP tools |
|
|
254
|
+
|
|
255
|
+
AI resolution order: `x-ai-api-key` header + `createModelFn` → `createServerModelFn(modelId)` → configured `aiService` → demo SSE response when `demoMode` and none available.
|
|
256
|
+
|
|
257
|
+
### addGptHistoryRoutes(router, options?)
|
|
258
|
+
|
|
259
|
+
CRUD at `/gpt/histories` via `modelRouter`:
|
|
260
|
+
|
|
261
|
+
| Operation | Permission |
|
|
262
|
+
|-----------|------------|
|
|
263
|
+
| Create, List | `IsAuthenticated` |
|
|
264
|
+
| Read, Update, Delete | `IsOwner` |
|
|
265
|
+
|
|
266
|
+
Query filtered by `userId`; sort `-updated`; query fields `userId`, `projectId`.
|
|
267
|
+
|
|
268
|
+
### addProjectRoutes(router, options?)
|
|
269
|
+
|
|
270
|
+
| Endpoint | Method | Auth | Description |
|
|
271
|
+
|----------|--------|------|-------------|
|
|
272
|
+
| `/gpt/projects/:id/memories` | POST | `IsAuthenticated` (owner) | Add memory; body: `{text, category?}` |
|
|
273
|
+
| `/gpt/projects/:id/memories/:memoryId` | DELETE | `IsAuthenticated` (owner) | Remove memory |
|
|
274
|
+
| `/gpt/projects` | CRUD | Create/List: `IsAuthenticated`; Read/Update/Delete: `IsOwner` | Standard modelRouter |
|
|
275
|
+
|
|
276
|
+
### addFileRoutes(router, options)
|
|
277
|
+
|
|
278
|
+
Requires `fileStorageService` and `gcsBucket` (registered by `AiApp` when both are set).
|
|
279
|
+
|
|
280
|
+
| Endpoint | Method | Auth | Description |
|
|
281
|
+
|----------|--------|------|-------------|
|
|
282
|
+
| `/files/upload` | POST | `IsAuthenticated` | Multipart upload (`file` field); allowed MIME: images, PDF, plain text, CSV, JSON |
|
|
283
|
+
| `/files/*gcsKey` | GET | None | Returns signed read URL (1 hour) |
|
|
284
|
+
| `/files/*gcsKey` | DELETE | `IsAuthenticated` (owner) | Soft-delete attachment and remove from GCS |
|
|
285
|
+
|
|
286
|
+
### addMcpRoutes(router, options)
|
|
287
|
+
|
|
288
|
+
Requires `mcpService` (registered by `AiApp` when set).
|
|
289
|
+
|
|
290
|
+
| Endpoint | Method | Auth | Description |
|
|
291
|
+
|----------|--------|------|-------------|
|
|
292
|
+
| `/mcp/servers` | GET | `IsAuthenticated` + admin | Server connection status |
|
|
293
|
+
| `/mcp/tools` | GET | `IsAuthenticated` | Available MCP tools |
|
|
294
|
+
| `/mcp/servers/:name/reconnect` | POST | `IsAuthenticated` + admin | Reconnect one server |
|
|
295
|
+
|
|
296
|
+
### addAiRequestsExplorerRoutes(router, options?)
|
|
297
|
+
|
|
298
|
+
| Endpoint | Method | Auth | Description |
|
|
299
|
+
|----------|--------|------|-------------|
|
|
300
|
+
| `/aiRequestsExplorer` | GET | `IsAuthenticated` + `user.admin` | Paginated AI request log; filters: `requestType`, `model`, `startDate`, `endDate` |
|
|
301
|
+
|
|
302
|
+
## AiApp plugin
|
|
303
|
+
|
|
304
|
+
`AiApp` registers all AI routes in one `TerrenoPlugin`:
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import {AiApp, AIService, FileStorageService, MCPService} from "@terreno/ai";
|
|
308
|
+
import {google} from "@ai-sdk/google";
|
|
309
|
+
|
|
310
|
+
const aiService = new AIService({model: google("gemini-2.5-flash")});
|
|
311
|
+
|
|
312
|
+
new AiApp({
|
|
313
|
+
aiService,
|
|
314
|
+
fileStorageService: new FileStorageService({bucketName: "my-bucket"}),
|
|
315
|
+
gcsBucket: "my-bucket",
|
|
316
|
+
mcpService: new MCPService([{name: "tools", transport: {type: "sse", url: "..."}}]),
|
|
317
|
+
tools: myToolDefinitions,
|
|
318
|
+
demoMode: false,
|
|
319
|
+
createModelFn: (apiKey, modelId) => google(modelId ?? "gemini-2.5-flash", {apiKey}),
|
|
320
|
+
openApiOptions: options,
|
|
321
|
+
}).register(app);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
| Option | Description |
|
|
325
|
+
|--------|-------------|
|
|
326
|
+
| `aiService` | Pre-configured server-wide AI service |
|
|
327
|
+
| `createModelFn` | Build model from per-request `x-ai-api-key` |
|
|
328
|
+
| `createServerModelFn` | Server-side model factory (e.g. Vertex ADC) without per-request key |
|
|
329
|
+
| `demoMode` | Return canned responses when no AI service resolves |
|
|
330
|
+
| `fileStorageService` + `gcsBucket` | Enable file upload routes |
|
|
331
|
+
| `mcpService` | Enable MCP routes and tool discovery in chat |
|
|
332
|
+
| `tools` | Static Vercel AI SDK tool definitions for chat |
|
|
333
|
+
| `toolChoice` | `"auto"` \| `"none"` \| `"required"` (default `"auto"` when tools present) |
|
|
334
|
+
| `maxSteps` | Max tool-calling steps (default 5) |
|
|
335
|
+
| `titleModelId` | Cheaper model for conversation title generation |
|
|
336
|
+
| `openApiOptions` | Passed to route OpenAPI builders |
|
|
337
|
+
|
|
338
|
+
## LangfuseApp plugin
|
|
339
|
+
|
|
340
|
+
Optional Langfuse admin UI and tracing:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
import {LangfuseApp} from "@terreno/ai";
|
|
344
|
+
|
|
345
|
+
new LangfuseApp({
|
|
346
|
+
publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
|
|
347
|
+
secretKey: process.env.LANGFUSE_SECRET_KEY!,
|
|
348
|
+
baseUrl: process.env.LANGFUSE_BASE_URL,
|
|
349
|
+
adminPath: "/admin/langfuse",
|
|
350
|
+
enableTracing: true,
|
|
351
|
+
enableAdminUI: true,
|
|
352
|
+
evaluation: {enabled: true, scoringFunctions: [...]},
|
|
353
|
+
}).register(app);
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
| Option | Default | Description |
|
|
357
|
+
|--------|---------|-------------|
|
|
358
|
+
| `publicKey`, `secretKey` | — | Langfuse API keys (required) |
|
|
359
|
+
| `baseUrl` | — | Langfuse host |
|
|
360
|
+
| `adminPath` | `"/admin/langfuse"` | Admin route prefix |
|
|
361
|
+
| `organization` | maintainer default | Langfuse organization slug — set your own |
|
|
362
|
+
| `project` | `"terreno"` | Langfuse project slug — set your own |
|
|
363
|
+
| `enableTracing` | `true` | OpenTelemetry via `@langfuse/otel` |
|
|
364
|
+
| `enableAdminUI` | `true` | Prompt, trace, playground, evaluation routes |
|
|
365
|
+
| `evaluation.enabled` | — | Register evaluation scoring routes |
|
|
366
|
+
| `cache` | — | Prompt/trace TTL overrides |
|
|
367
|
+
|
|
368
|
+
Calls `shutdownLangfuseClient()` and `shutdownTracing()` on `SIGTERM`.
|
|
369
|
+
|
|
370
|
+
## Langfuse integration
|
|
371
|
+
|
|
372
|
+
Low-level exports (also used by `addGptRoutes` when `langfuseSystemPromptName` is set):
|
|
373
|
+
|
|
374
|
+
- **Client:** `initLangfuseClient`, `getLangfuseClient`, `isLangfuseInitialized`, `shutdownLangfuseClient`
|
|
375
|
+
- **Prompts:** `getPrompt`, `createPrompt`, `compilePrompt`, `invalidatePromptCache`, `preparePromptForAI`
|
|
376
|
+
- **Tracing:** `initTracing`, `shutdownTracing`, `createTelemetryConfig`
|
|
377
|
+
- **Cache:** `LangfuseCache`, `getCached`, `setCached`, `invalidateCache`
|
|
378
|
+
|
|
379
|
+
Subpath imports for tree-shaking: `@terreno/ai/langfuseClient`, `@terreno/ai/langfuseApp`.
|
|
380
|
+
|
|
381
|
+
## FileStorageService
|
|
382
|
+
|
|
383
|
+
Google Cloud Storage helper for uploads referenced by `addFileRoutes`.
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
const storage = new FileStorageService({
|
|
387
|
+
bucketName: "my-bucket",
|
|
388
|
+
storageOptions: {}, // optional @google-cloud/storage options
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
await storage.upload({buffer, filename, mimeType, userId});
|
|
392
|
+
await storage.getSignedUrl(gcsKey); // 1-hour v4 signed URL
|
|
393
|
+
await storage.delete(gcsKey); // GCS delete + soft-delete FileAttachment
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## MCPService
|
|
397
|
+
|
|
398
|
+
Manages SSE MCP client connections for tool calling.
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
const mcp = new MCPService([
|
|
402
|
+
{name: "my-server", transport: {type: "sse", url: "https://...", headers: {...}}},
|
|
403
|
+
]);
|
|
404
|
+
await mcp.connect();
|
|
405
|
+
const tools = await mcp.getTools();
|
|
406
|
+
const status = mcp.getServerStatus();
|
|
407
|
+
await mcp.reconnectServer("my-server");
|
|
408
|
+
await mcp.disconnect();
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Gemini and Vertex helpers
|
|
412
|
+
|
|
413
|
+
**Gemini Developer API** (API-key based):
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
import {listGeminiApiModels, normalizeGeminiModelId, GEMINI_API_BASE_URL} from "@terreno/ai";
|
|
417
|
+
|
|
418
|
+
const models = await listGeminiApiModels({apiKey: "..."});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Vertex AI / Gemini Enterprise:**
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
import {
|
|
425
|
+
createVertexProvider,
|
|
426
|
+
listEnabledVertexModels,
|
|
427
|
+
assertVertexModelsEnabled,
|
|
428
|
+
DEFAULT_VERTEX_LOCATION,
|
|
429
|
+
} from "@terreno/ai";
|
|
430
|
+
|
|
431
|
+
const vertex = await createVertexProvider({project: "my-gcp-project"});
|
|
432
|
+
const model = vertex.languageModel("gemini-2.5-flash");
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Env fallbacks: `GOOGLE_VERTEX_PROJECT`, `GOOGLE_VERTEX_LOCATION` (default `us-central1`).
|
|
436
|
+
|
|
437
|
+
## Web search types
|
|
438
|
+
|
|
439
|
+
`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`.
|
|
440
|
+
|
|
441
|
+
## Integration example
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
import {TerrenoApp} from "@terreno/api";
|
|
445
|
+
import {AiApp, AIService, LangfuseApp} from "@terreno/ai";
|
|
446
|
+
import {google} from "@ai-sdk/google";
|
|
447
|
+
|
|
448
|
+
const aiService = new AIService({model: google("gemini-2.5-flash")});
|
|
449
|
+
|
|
450
|
+
new TerrenoApp({userModel: User})
|
|
451
|
+
.register(new AiApp({aiService, openApiOptions: {}}))
|
|
452
|
+
.register(
|
|
453
|
+
new LangfuseApp({
|
|
454
|
+
publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
|
|
455
|
+
secretKey: process.env.LANGFUSE_SECRET_KEY!,
|
|
456
|
+
})
|
|
457
|
+
)
|
|
458
|
+
.start();
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Legacy `setupServer` pattern: call `addGptHistoryRoutes`, `addGptRoutes`, etc. inside `addRoutes`.
|
|
462
|
+
|
|
463
|
+
## Environment variables
|
|
464
|
+
|
|
465
|
+
| Variable | Used by | Description |
|
|
466
|
+
|----------|---------|-------------|
|
|
467
|
+
| `GOOGLE_VERTEX_PROJECT` | `createVertexProvider` | GCP project for Vertex models |
|
|
468
|
+
| `GOOGLE_VERTEX_LOCATION` | `createVertexProvider` | Vertex region (default `us-central1`) |
|
|
469
|
+
| `LANGFUSE_PUBLIC_KEY` | `LangfuseApp` | Langfuse public key |
|
|
470
|
+
| `LANGFUSE_SECRET_KEY` | `LangfuseApp` | Langfuse secret key |
|
|
471
|
+
| `LANGFUSE_BASE_URL` | Langfuse client | Langfuse host URL |
|
|
472
|
+
|
|
473
|
+
GCS credentials use standard Google Cloud Application Default Credentials for `FileStorageService`.
|
|
474
|
+
|
|
475
|
+
## Conventions
|
|
476
|
+
|
|
477
|
+
- Use `aiModel` on `AIRequest`, not `model` (Mongoose reserved name).
|
|
478
|
+
- `GptHistory`, `FileAttachment`, and `Project` use `userId` with `ownerId` virtual for `Permissions.IsOwner`.
|
|
479
|
+
- Gpt history list uses `queryFilter: (user) => ({userId: user?.id})`, not `OwnerQueryFilter`.
|
|
480
|
+
- Express user in routes: `(req.user as {_id?: ObjectId})` casting pattern.
|
|
481
|
+
- Throw `APIError` with appropriate status; check conditions early.
|
|
482
|
+
- Uses `Model.findOneOrNone` / `findExactlyOne` — never raw `findOne`.
|
|
483
|
+
|
|
484
|
+
## Testing
|
|
485
|
+
|
|
486
|
+
- Framework: `bun test` with preload `./src/tests/bunSetup.ts`
|
|
487
|
+
- HTTP: supertest against real routes
|
|
488
|
+
- DB: memory Mongo via `@terreno/test` (`TERRENO_TEST_USE_MEMORY_MONGO` or `TERRENO_TEST_MONGODB_URI`)
|
|
489
|
+
- Mock AI model: implement `doGenerate` and `doStream` on a fake `LanguageModel`
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
const createMockModel = () => ({
|
|
493
|
+
doGenerate: mock(async () => ({
|
|
494
|
+
finishReason: "stop" as const,
|
|
495
|
+
rawCall: {rawPrompt: "", rawSettings: {}},
|
|
496
|
+
text: "response text",
|
|
497
|
+
usage: {completionTokens: 10, promptTokens: 5},
|
|
498
|
+
})),
|
|
499
|
+
doStream: mock(async () => ({
|
|
500
|
+
rawCall: {rawPrompt: "", rawSettings: {}},
|
|
501
|
+
stream: new ReadableStream({
|
|
502
|
+
start(controller) {
|
|
503
|
+
controller.enqueue({type: "text-delta" as const, textDelta: "chunk "});
|
|
504
|
+
controller.enqueue({
|
|
505
|
+
type: "finish" as const,
|
|
506
|
+
finishReason: "stop" as const,
|
|
507
|
+
usage: {completionTokens: 10, promptTokens: 5},
|
|
508
|
+
});
|
|
509
|
+
controller.close();
|
|
510
|
+
},
|
|
511
|
+
}),
|
|
512
|
+
})),
|
|
513
|
+
modelId: "mock-model",
|
|
514
|
+
provider: "mock-provider",
|
|
515
|
+
specificationVersion: "v1" as const,
|
|
516
|
+
});
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Never mock `@terreno/api` or Mongoose models — test against real functionality.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# @terreno/comms
|
|
2
|
+
|
|
3
|
+
Provider-agnostic transactional communications for Terreno backends. The core package defines
|
|
4
|
+
mail, SMS, push, and verification contracts without installing SendGrid, Twilio, or Expo SDKs.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
bun add @terreno/comms
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Peer dependency: `mongoose ^8.0.0 || ^9.0.0`.
|
|
13
|
+
|
|
14
|
+
## Register the plugin
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import {
|
|
18
|
+
CommsApp,
|
|
19
|
+
ConsoleMailProvider,
|
|
20
|
+
ConsolePushProvider,
|
|
21
|
+
ConsoleSmsProvider,
|
|
22
|
+
ConsoleVerificationProvider,
|
|
23
|
+
} from "@terreno/comms";
|
|
24
|
+
import {TerrenoApp} from "@terreno/api";
|
|
25
|
+
|
|
26
|
+
new TerrenoApp({userModel: User})
|
|
27
|
+
.register(
|
|
28
|
+
new CommsApp({
|
|
29
|
+
defaultFrom: "notifications@example.com",
|
|
30
|
+
mail: new ConsoleMailProvider(),
|
|
31
|
+
push: new ConsolePushProvider(),
|
|
32
|
+
sms: new ConsoleSmsProvider(),
|
|
33
|
+
verification: new ConsoleVerificationProvider(),
|
|
34
|
+
})
|
|
35
|
+
)
|
|
36
|
+
.start();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Console providers log only non-sensitive metadata such as message lengths and recipient counts.
|
|
40
|
+
They never log message content, recipient identifiers, push tokens, or verification codes.
|
|
41
|
+
|
|
42
|
+
## Send messages
|
|
43
|
+
|
|
44
|
+
Use the registered service from routes, jobs, or other plugins:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import {getCommsService} from "@terreno/comms";
|
|
48
|
+
|
|
49
|
+
await getCommsService().sendMail({
|
|
50
|
+
subject: "Welcome",
|
|
51
|
+
text: "Thanks for joining.",
|
|
52
|
+
to: "person@example.com",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await getCommsService().sendSms({
|
|
56
|
+
body: "Your appointment is tomorrow.",
|
|
57
|
+
to: "+15555550100",
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`sendPushToUser()` resolves active device tokens and deactivates tokens only when a provider marks
|
|
62
|
+
a failure as permanent. `startVerification()` and `checkVerification()` delegate to the configured
|
|
63
|
+
verification provider. `startVerification()` accepts `channel: "sms"` or `channel: "email"`;
|
|
64
|
+
`checkVerification()` verifies the code against the same phone number or email destination and may
|
|
65
|
+
return an `error` reason when `valid` is false. Start attempts store the verification channel in
|
|
66
|
+
delivery-log metadata while recipient values remain redacted.
|
|
67
|
+
|
|
68
|
+
## Provider contracts
|
|
69
|
+
|
|
70
|
+
| Interface | Method |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `MailProvider` | `sendMail(message)` |
|
|
73
|
+
| `SmsProvider` | `sendSms(message)` |
|
|
74
|
+
| `PushProvider` | `sendPush(message)` — one `SendResult` per token |
|
|
75
|
+
| `VerificationProvider` | `startVerification(options)`, `checkVerification(options)` |
|
|
76
|
+
|
|
77
|
+
Concrete SendGrid, Twilio, and Expo providers are separate adapter packages/subpath exports. Core
|
|
78
|
+
`@terreno/comms` has no concrete provider SDK dependencies.
|
|
79
|
+
|
|
80
|
+
## Configuration
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
interface CommsAppOptions {
|
|
84
|
+
basePath?: string; // default: "/comms"
|
|
85
|
+
defaultFrom?: string;
|
|
86
|
+
logMessages?: boolean; // default: true
|
|
87
|
+
mail?: MailProvider;
|
|
88
|
+
onDeliveryEvent?: (event: DeliveryEvent) => Promise<void>;
|
|
89
|
+
push?: PushProvider;
|
|
90
|
+
redactRecipients?: boolean; // default: true
|
|
91
|
+
sms?: SmsProvider;
|
|
92
|
+
verification?: VerificationProvider;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
When a channel is unconfigured:
|
|
97
|
+
|
|
98
|
+
- non-production environments use the matching console provider and emit a warning;
|
|
99
|
+
- production throws a `501` `APIError` titled `Comms channel not configured`.
|
|
100
|
+
|
|
101
|
+
Delivery attempts are stored in `CommsMessage`. Recipient values are stored as `[redacted]` unless
|
|
102
|
+
`redactRecipients` is explicitly `false`.
|
|
103
|
+
|
|
104
|
+
## Routes
|
|
105
|
+
|
|
106
|
+
The default `basePath` is `/comms`.
|
|
107
|
+
|
|
108
|
+
| Method | Path | Access | Purpose |
|
|
109
|
+
|---|---|---|---|
|
|
110
|
+
| `POST` | `/comms/pushTokens` | Authenticated | Register or refresh a device token |
|
|
111
|
+
| `GET` | `/comms/pushTokens` | Authenticated owner | List the current user's tokens |
|
|
112
|
+
| `GET` | `/comms/pushTokens/:id` | Owner | Read one token |
|
|
113
|
+
| `DELETE` | `/comms/pushTokens/:id` | Owner | Deactivate a token |
|
|
114
|
+
| `GET` | `/comms/messages` | Admin | Filtered, paginated delivery log |
|
|
115
|
+
|
|
116
|
+
An active token cannot be claimed by another user. After its owner deactivates it, another
|
|
117
|
+
authenticated user on the same device may register it.
|
|
118
|
+
|
|
119
|
+
## Templates
|
|
120
|
+
|
|
121
|
+
`renderTemplate()` replaces top-level `{{variable}}` placeholders in `subject`, `text`, and `html`.
|
|
122
|
+
Missing or inherited properties render as empty strings.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const message = renderTemplate({
|
|
126
|
+
data: {name: "Ada"},
|
|
127
|
+
template: {
|
|
128
|
+
subject: "Welcome, {{name}}",
|
|
129
|
+
text: "Hello {{name}}",
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
```
|