@softr/sdk 0.0.79-alpha.0 → 0.0.79-alpha.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/dist/mcp.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DatasourceAction } from "./action.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as _tanstack_react_start0 from "@tanstack/react-start";
|
|
3
3
|
|
|
4
4
|
//#region src/mcp.d.ts
|
|
5
5
|
|
|
@@ -31,6 +31,6 @@ type SoftrMcpOptions = {
|
|
|
31
31
|
* ],
|
|
32
32
|
* }))
|
|
33
33
|
*/
|
|
34
|
-
declare function softrMcpMiddleware(options?: SoftrMcpOptions):
|
|
34
|
+
declare function softrMcpMiddleware(options?: SoftrMcpOptions): _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>;
|
|
35
35
|
//#endregion
|
|
36
36
|
export { softrMcpMiddleware };
|
package/dist/package.js
CHANGED
package/package.json
CHANGED
|
@@ -84,7 +84,7 @@ Asking an AI model for anything: a summary, fields out of a document, a classifi
|
|
|
84
84
|
Calling a connected third-party API (Slack, Stripe, Gmail, Google Calendar, …)?
|
|
85
85
|
load softr-sdk/integrations
|
|
86
86
|
|
|
87
|
-
Running a model on the user's OWN provider account, because they
|
|
87
|
+
Running a model on the user's OWN provider account, because they chose that for the feature?
|
|
88
88
|
load softr-sdk/integrations: its "Calling an AI model" section
|
|
89
89
|
|
|
90
90
|
Opening the app to an AI assistant over MCP?
|
|
@@ -8,7 +8,8 @@ description: >-
|
|
|
8
8
|
optional JSON schema for an answer that is data, and optional files;
|
|
9
9
|
ai.stream runs it for an answer the user watches being written. Load this
|
|
10
10
|
for a summary, an extraction, a classification, a draft, or a chat the app
|
|
11
|
-
asks a model for
|
|
11
|
+
asks a model for, and before you decide whether a feature runs on Softr AI
|
|
12
|
+
or on the user's own account.
|
|
12
13
|
metadata:
|
|
13
14
|
type: sub-skill
|
|
14
15
|
library: softr-sdk
|
|
@@ -19,23 +20,34 @@ metadata:
|
|
|
19
20
|
|
|
20
21
|
# AI
|
|
21
22
|
|
|
22
|
-
An app asks a model through an AI action. Softr runs the call on its own provider keys and bills the workspace's AI credits, so there is no key to ask the user for and nothing to connect. This is the
|
|
23
|
+
An app asks a model through an AI action. Softr runs the call on its own provider keys and bills the workspace's AI credits, so there is no key to ask the user for and nothing to connect. This is the default for every AI feature of the app's own — a summary of a record, fields extracted from a document, a classification, a draft, a chat. The other path is the user's own provider account, through a connected integration: the `integrations` sub-skill. An app can hold both: each feature carries its own choice in its code.
|
|
23
24
|
|
|
24
|
-
##
|
|
25
|
+
## Decide who runs it first
|
|
26
|
+
|
|
27
|
+
Never choose silently between Softr AI and the user's own account. Decide per feature, before you write it, in this order:
|
|
28
|
+
|
|
29
|
+
1. The user named Softr AI or their own account in the prompt: follow it.
|
|
30
|
+
2. The app already has AI features: follow the choice they made, and say so in one line.
|
|
31
|
+
3. `integration_list` shows no `OPEN_AI`, `ANTHROPIC_AI` or `GEMINI_AI` integration: use Softr AI, and say so in one line. Call the tool; do not assume the answer.
|
|
32
|
+
4. Otherwise ask with one `requestinput` of type `aiProvider`, naming the feature. The answer is Softr AI, or the integration with its id and type; a declined request means Softr AI.
|
|
33
|
+
|
|
34
|
+
An own-account answer leaves this skill: the call goes through `defineIntegration`, as the `integrations` sub-skill describes.
|
|
35
|
+
|
|
36
|
+
## Choose the model
|
|
25
37
|
|
|
26
38
|
The model is chosen when the app is built and frozen in its code. Before you declare an action, read Softr's catalog with the `ai_list_models` tool. Each entry carries the `id` the action names, the `provider`, and `bestFor`, what to pick it for:
|
|
27
39
|
|
|
28
|
-
- a summary, a briefing, a draft, reasoning over many facts: the strongest model listed;
|
|
40
|
+
- a chat, an assistant, a summary, a briefing, a draft, reasoning over many facts: the strongest model listed;
|
|
29
41
|
- an extraction or a classification over one document: the cheapest model that reads that document's kind — a PDF needs a model whose `bestFor` says so.
|
|
30
42
|
|
|
31
|
-
Copy the `id` exactly. An id outside the catalog is refused when the action is saved, and a model spelled from memory never is in it.
|
|
43
|
+
Copy the `id` exactly from the tool's answer. An id outside the catalog is refused when the action is saved, and a model spelled from memory, or taken from an example, never is in it.
|
|
32
44
|
|
|
33
45
|
## Declare the action
|
|
34
46
|
|
|
35
47
|
```ts
|
|
36
48
|
const summarizeDeal = defineAiAction({
|
|
37
49
|
id: 'summarizeDeal',
|
|
38
|
-
model: '
|
|
50
|
+
model: '<an id from ai_list_models>',
|
|
39
51
|
permissions: { userGroupTypes: ['LOGGED_IN_USERS'] },
|
|
40
52
|
})
|
|
41
53
|
```
|
|
@@ -105,29 +117,9 @@ const extractInvoice = createServerFn({ method: 'POST' })
|
|
|
105
117
|
|
|
106
118
|
The file's URL comes from the app's own upload: load the Softr SDK `file-uploads` guidance for that half. `readInvoice` names a model whose `bestFor` reads PDF documents.
|
|
107
119
|
|
|
108
|
-
## A conversation
|
|
109
|
-
|
|
110
|
-
A chat sends its earlier turns in `messages` and the new one as `prompt`. The app keeps the turns; the model holds nothing between calls.
|
|
111
|
-
|
|
112
|
-
```ts
|
|
113
|
-
const reply = createServerFn({ method: 'POST' })
|
|
114
|
-
.validator(z.object({ turns: z.array(z.object({ role: z.enum(['user', 'assistant']), content: z.string() })), prompt: z.string() }))
|
|
115
|
-
.handler(async ({ data }) => {
|
|
116
|
-
const answer = await ai.generate(supportChat, {
|
|
117
|
-
system: 'You answer questions about the order the user is looking at.',
|
|
118
|
-
messages: data.turns,
|
|
119
|
-
prompt: data.prompt,
|
|
120
|
-
temperature: 0.5,
|
|
121
|
-
})
|
|
122
|
-
return answer.text
|
|
123
|
-
})
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
A chat the user watches being written streams instead, with the same `messages`: see the next section.
|
|
127
|
-
|
|
128
120
|
## Streaming: an answer the user watches being written
|
|
129
121
|
|
|
130
|
-
A chat, an assistant, a long draft shown as it is typed — never buffer these: the user would watch a blank panel until the last token. `ai.stream(action, init)` takes the same `init` and answers with a `Response` whose body is a stream of Server-Sent Events. Hand it to the browser from the server function, unchanged:
|
|
122
|
+
A chat, an assistant, a long draft shown as it is typed — never buffer these: the user would watch a blank panel until the last token. A chat box the user types into is always this case, never `ai.generate`. `ai.stream(action, init)` takes the same `init` and answers with a `Response` whose body is a stream of Server-Sent Events. Hand it to the browser from the server function, unchanged:
|
|
131
123
|
|
|
132
124
|
```ts
|
|
133
125
|
const draftReply = createServerFn({ method: 'POST' })
|
|
@@ -144,7 +136,7 @@ Each event is one JSON object on a `data:` line:
|
|
|
144
136
|
- `{ "done": true, "usage": { "inputTokens", "outputTokens" }, "model": "..." }` — the answer is complete.
|
|
145
137
|
- `{ "error": "..." }` — in place of `done`, when the call ended before its answer: show the message.
|
|
146
138
|
|
|
147
|
-
On the client, call the server function's URL with `fetch`, read `response.body` with a stream reader, split on blank lines, and parse the JSON after `data:`.
|
|
139
|
+
On the client, call the server function's URL with `fetch`, read `response.body` with a stream reader, split on blank lines, and parse the JSON after `data:`. A conversation sends its earlier turns in `messages`; the app keeps them, the model holds nothing between calls.
|
|
148
140
|
|
|
149
141
|
## Failure modes
|
|
150
142
|
|
|
@@ -156,6 +148,8 @@ On the client, call the server function's URL with `fetch`, read `response.body`
|
|
|
156
148
|
|
|
157
149
|
**Trusting `json` without checking it.** A model may stray from the schema; `json` is then `null`, or a shape close to the one asked. Validate it with `zod` before writing it to a record, and keep `text` as the fallback the user can read.
|
|
158
150
|
|
|
151
|
+
**Buffering a chat.** A chat built on `ai.generate` shows nothing until the whole reply is done, and the user reads "replies appear all at once" in your closing message. A chat streams.
|
|
152
|
+
|
|
159
153
|
**Reading a streamed body with `res.json()`.** The body of `ai.stream` is an event stream, not one JSON document. Return `res.body` in a `Response` or read it with a stream reader.
|
|
160
154
|
|
|
161
|
-
**Asking for the user's API key.** An AI feature of the app's own needs none. Only when the user
|
|
155
|
+
**Asking for the user's API key.** An AI feature of the app's own needs none. Only when the user chose their own provider account for the feature does the call go through a connected integration — load the `integrations` sub-skill then.
|
|
@@ -126,7 +126,7 @@ Do not add an `Authorization` key to `headers` — Softr injects the integration
|
|
|
126
126
|
|
|
127
127
|
## Calling an AI model
|
|
128
128
|
|
|
129
|
-
**This is the path for the user's own provider account only.** An AI feature of the app's own — a summary, an extraction, a chat — runs on Softr's keys through an AI action, with nothing to connect and no key to ask for: load the `ai` sub-skill for it. Call a model through an integration only when the user
|
|
129
|
+
**This is the path for the user's own provider account only.** An AI feature of the app's own — a summary, an extraction, a chat — runs on Softr's keys through an AI action, with nothing to connect and no key to ask for: load the `ai` sub-skill for it. Call a model through an integration only when the user chose their own OpenAI, Anthropic or Gemini account for the feature, in the prompt or when asked, and had it connected.
|
|
130
130
|
|
|
131
131
|
An AI integration is called like any other. For `ANTHROPIC_AI`, the action is `POST` on
|
|
132
132
|
`https://api.anthropic.com/v1/messages`, the request carries the `anthropic-version` header and a body of
|