@vekil/app-sdk 0.4.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/LICENSE +201 -0
- package/README.md +232 -0
- package/dist/cli.cjs +5343 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +5341 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +6288 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5712 -0
- package/dist/index.d.ts +5712 -0
- package/dist/index.js +6054 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime.cjs +5299 -0
- package/dist/runtime.cjs.map +1 -0
- package/dist/runtime.d.cts +188 -0
- package/dist/runtime.d.ts +188 -0
- package/dist/runtime.js +5284 -0
- package/dist/runtime.js.map +1 -0
- package/dist/signature-envelope-DzT4yjFP.d.cts +5877 -0
- package/dist/signature-envelope-DzT4yjFP.d.ts +5877 -0
- package/docs/app-definition.md +223 -0
- package/docs/cli.md +163 -0
- package/docs/runtime.md +256 -0
- package/docs/security-and-versioning.md +147 -0
- package/package.json +86 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# App Definition
|
|
2
|
+
|
|
3
|
+
An App Definition describes what a Remote App offers, when it should be used,
|
|
4
|
+
which information it needs, and what results it can return.
|
|
5
|
+
|
|
6
|
+
Use the exported TypeScript types while authoring. Use `vekil-app schema` when
|
|
7
|
+
an editor or another tool needs JSON Schema. Both represent the same strict
|
|
8
|
+
contract.
|
|
9
|
+
|
|
10
|
+
## Choose a starting point
|
|
11
|
+
|
|
12
|
+
`createRemoteAppStarterDefinition` creates a minimal read-only App with one
|
|
13
|
+
intent, capability, action, and outcome. It is useful for validating a new
|
|
14
|
+
Runtime and learning the release workflow.
|
|
15
|
+
|
|
16
|
+
`defineRemoteApp` accepts the complete `AppDefinitionInput`:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { defineRemoteApp, type AppDefinitionInput } from "@vekil/app-sdk";
|
|
20
|
+
|
|
21
|
+
const input = {
|
|
22
|
+
// Complete strict Definition.
|
|
23
|
+
} satisfies AppDefinitionInput;
|
|
24
|
+
|
|
25
|
+
export default defineRemoteApp(input);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Unknown fields are rejected. Defaults are normalized during parsing, so build
|
|
29
|
+
and import the value returned by `defineRemoteApp`.
|
|
30
|
+
|
|
31
|
+
## Top-level structure
|
|
32
|
+
|
|
33
|
+
| Field | Purpose |
|
|
34
|
+
| ------------------- | ----------------------------------------------------------------------- |
|
|
35
|
+
| `definitionVersion` | Selects the supported Definition contract. |
|
|
36
|
+
| `defaultLocale` | Fallback locale for App-owned copy. |
|
|
37
|
+
| `app` | Listing name, descriptions, category, optional icon, and provider name. |
|
|
38
|
+
| `publication` | Optional website, support email, privacy policy, and terms. |
|
|
39
|
+
| `connection` | Explains account connection and requested provider permissions. |
|
|
40
|
+
| `intents` | Requests the App understands and the information each request needs. |
|
|
41
|
+
| `capabilities` | User-visible abilities backed by one or more actions. |
|
|
42
|
+
| `actions` | Typed operations the Runtime can plan and execute. |
|
|
43
|
+
| `outcomes` | Typed results and allowed continuations. |
|
|
44
|
+
| `artifactContracts` | Versioned data passed between planned actions. |
|
|
45
|
+
| `settings` | Values configured separately by each installer. |
|
|
46
|
+
| `policies` | Installer-controlled approval behavior. |
|
|
47
|
+
| `knowledge` | Installed information available for answering and planning. |
|
|
48
|
+
| `context` | Requested App Context scopes and their disclosure copy. |
|
|
49
|
+
| `publicActions` | Safe quick starts shown on a public Vekil page. |
|
|
50
|
+
| `customRules` | App-specific rule families and examples. |
|
|
51
|
+
| `runtime` | Runtime base URL, JWKS URL, and connection requirement. |
|
|
52
|
+
|
|
53
|
+
The generated schema contains every field constraint:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
vekil-app schema ./app-definition.schema.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Local keys
|
|
60
|
+
|
|
61
|
+
Use stable local keys such as `find-options`, `create-record`, and
|
|
62
|
+
`record-created` to connect Definition entries.
|
|
63
|
+
|
|
64
|
+
Good keys are:
|
|
65
|
+
|
|
66
|
+
- lowercase kebab-case;
|
|
67
|
+
- descriptive of behavior rather than UI copy;
|
|
68
|
+
- stable when labels or descriptions change;
|
|
69
|
+
- unique within their collection.
|
|
70
|
+
|
|
71
|
+
The Runtime resolves these keys with `createAppRuntimeBindings`. Business code
|
|
72
|
+
should not construct runtime references by concatenating strings.
|
|
73
|
+
|
|
74
|
+
## Localized copy
|
|
75
|
+
|
|
76
|
+
App-specific interface copy belongs in the Definition:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const title = {
|
|
80
|
+
en: "Find available options",
|
|
81
|
+
fr: "Trouver des options disponibles"
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Use valid locale codes as keys. Every required localized field must include the
|
|
86
|
+
`defaultLocale`. Translate meaning, while keeping keys and schemas stable.
|
|
87
|
+
|
|
88
|
+
## Intents, capabilities, actions, and outcomes
|
|
89
|
+
|
|
90
|
+
These layers answer different questions:
|
|
91
|
+
|
|
92
|
+
| Layer | Question |
|
|
93
|
+
| ---------- | --------------------------------------------- |
|
|
94
|
+
| Intent | What is the requester trying to accomplish? |
|
|
95
|
+
| Capability | What useful ability does the App provide? |
|
|
96
|
+
| Action | What typed operation can the Runtime perform? |
|
|
97
|
+
| Outcome | What happened, and may the request continue? |
|
|
98
|
+
|
|
99
|
+
### Intents
|
|
100
|
+
|
|
101
|
+
An intent declares:
|
|
102
|
+
|
|
103
|
+
- positive and negative examples;
|
|
104
|
+
- required and optional fields;
|
|
105
|
+
- questions for missing information;
|
|
106
|
+
- supported conversation origins;
|
|
107
|
+
- related capabilities and actions;
|
|
108
|
+
- risk and requester-review behavior;
|
|
109
|
+
- response guidance.
|
|
110
|
+
|
|
111
|
+
Examples teach recognition. They are not scripted replies and requesters do not
|
|
112
|
+
need to repeat them exactly.
|
|
113
|
+
|
|
114
|
+
Choose the intent mode by outcome:
|
|
115
|
+
|
|
116
|
+
- `ANSWER` returns information without creating a submission;
|
|
117
|
+
- `COLLECT` gathers a complete structured request;
|
|
118
|
+
- `EXECUTE` plans and performs a Runtime action.
|
|
119
|
+
|
|
120
|
+
### Capabilities
|
|
121
|
+
|
|
122
|
+
A capability is the installer-visible promise made by the App. Group actions
|
|
123
|
+
only when they form one coherent ability, and describe the result in ordinary
|
|
124
|
+
language.
|
|
125
|
+
|
|
126
|
+
### Actions
|
|
127
|
+
|
|
128
|
+
An action owns:
|
|
129
|
+
|
|
130
|
+
- input and output JSON Schema;
|
|
131
|
+
- side-effect and risk level;
|
|
132
|
+
- approval behavior;
|
|
133
|
+
- required settings, policies, and context;
|
|
134
|
+
- timeout, retry, and idempotency rules;
|
|
135
|
+
- possible outcomes and artifacts.
|
|
136
|
+
|
|
137
|
+
Use the narrowest input and context needed. A read-only planning operation must
|
|
138
|
+
not perform a write.
|
|
139
|
+
|
|
140
|
+
### Outcomes
|
|
141
|
+
|
|
142
|
+
An outcome represents a typed result. Add a continuation only when the same
|
|
143
|
+
request genuinely remains open, such as after presenting a bounded choice of
|
|
144
|
+
available options.
|
|
145
|
+
|
|
146
|
+
## Settings and policies
|
|
147
|
+
|
|
148
|
+
Settings are configured separately for every installation. The same App can
|
|
149
|
+
therefore serve different people or organizations without separate releases.
|
|
150
|
+
|
|
151
|
+
Common settings include:
|
|
152
|
+
|
|
153
|
+
- a selected workspace or calendar;
|
|
154
|
+
- working days and hours;
|
|
155
|
+
- a destination queue;
|
|
156
|
+
- public information supplied by the installer.
|
|
157
|
+
|
|
158
|
+
Policies control approval and supported behavior. They should be explicit
|
|
159
|
+
installer choices rather than hidden prompt text.
|
|
160
|
+
|
|
161
|
+
Do not put provider credentials in settings. OAuth clients, access tokens, API
|
|
162
|
+
keys, and webhook secrets belong in the Remote Runtime's credential store.
|
|
163
|
+
|
|
164
|
+
## App Context
|
|
165
|
+
|
|
166
|
+
Each action requests the minimum context it needs. Context is scoped to one
|
|
167
|
+
authorized operation and can be absent or expire.
|
|
168
|
+
|
|
169
|
+
Treat optional context as unavailable when it is not supplied. Never infer
|
|
170
|
+
permission from an undeclared field or ask for broad context as a convenience.
|
|
171
|
+
|
|
172
|
+
## Artifacts
|
|
173
|
+
|
|
174
|
+
Artifacts are typed values produced by one action and consumed by a later
|
|
175
|
+
planned action. Declare:
|
|
176
|
+
|
|
177
|
+
- a stable local key;
|
|
178
|
+
- schema version and JSON Schema;
|
|
179
|
+
- sensitivity level;
|
|
180
|
+
- allowed audiences;
|
|
181
|
+
- producer and consumer actions;
|
|
182
|
+
- expected cardinality.
|
|
183
|
+
|
|
184
|
+
Read action input and reserved artifacts separately:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { readAppActionInput, readAppContextArtifactInputs } from "@vekil/app-sdk";
|
|
188
|
+
|
|
189
|
+
const input = inputSchema.parse(readAppActionInput(request.input));
|
|
190
|
+
const artifacts = readAppContextArtifactInputs(request.input);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Return requester-visible data only when it is safe independently of the chat
|
|
194
|
+
copy around it.
|
|
195
|
+
|
|
196
|
+
## Public actions
|
|
197
|
+
|
|
198
|
+
Public actions become concise quick starts on a Vekil page. Each one must map
|
|
199
|
+
to a declared intent and capability and accurately describe work the installed
|
|
200
|
+
App can handle.
|
|
201
|
+
|
|
202
|
+
Keep the list short and concrete. A quick start begins a conversation; normal
|
|
203
|
+
clarification, verification, approval, and execution rules still apply.
|
|
204
|
+
|
|
205
|
+
## Publication metadata
|
|
206
|
+
|
|
207
|
+
Website, privacy policy, terms, and support email are optional. Add only links
|
|
208
|
+
that are live and maintained. Developer name, handle, and social links come
|
|
209
|
+
from the Builder profile and do not belong in the Definition.
|
|
210
|
+
|
|
211
|
+
## Validate before import
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
vekil-app validate ./vekil.app.mjs
|
|
215
|
+
vekil-app conformance ./vekil.app.mjs
|
|
216
|
+
vekil-app build ./vekil.app.mjs --out ./dist
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`validate` checks schema and semantic references. `conformance` adds Remote
|
|
220
|
+
Runtime requirements. `build` writes canonical JSON and the matching schema.
|
|
221
|
+
|
|
222
|
+
Import `dist/definition.json` into Builder. Builder performs the final test with
|
|
223
|
+
the deployed Runtime before an App can be released.
|
package/docs/cli.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# CLI reference
|
|
2
|
+
|
|
3
|
+
The `vekil-app` CLI creates and validates Remote App projects with the same
|
|
4
|
+
contracts exported by the SDK.
|
|
5
|
+
|
|
6
|
+
Run it through a project script or directly:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm vekil-app help
|
|
10
|
+
pnpm dlx @vekil/app-sdk help
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Commands write normal results to standard output, validation issues to standard
|
|
14
|
+
error, and return a non-zero exit code on failure.
|
|
15
|
+
|
|
16
|
+
## `init`
|
|
17
|
+
|
|
18
|
+
Create a fail-closed Remote App starter:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
vekil-app init <directory> [--name <name>] [--runtime-url <url>]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- The target directory must be empty.
|
|
25
|
+
- The default Runtime URL is `http://localhost:4100`.
|
|
26
|
+
- Deployed URLs must use HTTPS; HTTP is accepted only on localhost.
|
|
27
|
+
- The generated package is private and pins the current SDK minor line.
|
|
28
|
+
- Planning and execution remain disabled until you implement the handlers.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm dlx @vekil/app-sdk init calendar-runtime \
|
|
34
|
+
--name "Calendar" \
|
|
35
|
+
--runtime-url http://localhost:4100
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## `validate`
|
|
39
|
+
|
|
40
|
+
Parse and validate a Definition:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
vekil-app validate <definition.json|module.mjs>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
JavaScript modules may export the Definition as `default` or as a named
|
|
47
|
+
`definition` value. Validation rejects unknown fields, invalid schemas,
|
|
48
|
+
duplicate local keys, broken references, unsafe context, and inconsistent
|
|
49
|
+
approval or artifact contracts.
|
|
50
|
+
|
|
51
|
+
## `conformance`
|
|
52
|
+
|
|
53
|
+
Run Definition checks and require a Remote Runtime declaration:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
vekil-app conformance <definition.json|module.mjs>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Current local checks cover:
|
|
60
|
+
|
|
61
|
+
- strict Definition validity;
|
|
62
|
+
- deterministic canonical JSON;
|
|
63
|
+
- Remote Runtime requirements.
|
|
64
|
+
|
|
65
|
+
Builder performs the deployed Runtime test separately. Local conformance is not
|
|
66
|
+
a replacement for that test or for a security review.
|
|
67
|
+
|
|
68
|
+
## `build`
|
|
69
|
+
|
|
70
|
+
Write the files needed for Builder import:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
vekil-app build <definition.json|module.mjs> [--out <directory>]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The default output directory is `dist`:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
dist/
|
|
80
|
+
├── app-definition.schema.json
|
|
81
|
+
└── definition.json
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Import `definition.json` into a Remote App project. Do not hand-edit the built
|
|
85
|
+
JSON; change the source Definition and rebuild it.
|
|
86
|
+
|
|
87
|
+
## `inspect`
|
|
88
|
+
|
|
89
|
+
Print a concise JSON summary:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
vekil-app inspect <definition.json|module.mjs>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The output includes the public name, locale, category, connection type,
|
|
96
|
+
capability keys, publication links, and Runtime endpoints. It is useful in CI
|
|
97
|
+
and release reviews.
|
|
98
|
+
|
|
99
|
+
## `schema`
|
|
100
|
+
|
|
101
|
+
Print or write the App Definition JSON Schema:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
vekil-app schema
|
|
105
|
+
vekil-app schema ./app-definition.schema.json
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Use the generated file for editors, CI validation, and tooling. Regenerate it
|
|
109
|
+
after changing the SDK version.
|
|
110
|
+
|
|
111
|
+
## `keys`
|
|
112
|
+
|
|
113
|
+
Generate Runtime signing keys from the manifest downloaded from Builder:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
vekil-app keys [manifest.json] [--key-id <key-id>]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The default path is `./vekil.manifest.json`.
|
|
120
|
+
|
|
121
|
+
The command prints:
|
|
122
|
+
|
|
123
|
+
- `VEKIL_APP_SIGNING_KEY_ID`;
|
|
124
|
+
- `VEKIL_APP_SIGNING_PRIVATE_JWK_BASE64URL`;
|
|
125
|
+
- the public JWKS document.
|
|
126
|
+
|
|
127
|
+
Store the private values in a secret manager and publish only the JWKS.
|
|
128
|
+
Redirecting command output to a committed file can leak the private key.
|
|
129
|
+
|
|
130
|
+
## Generated project scripts
|
|
131
|
+
|
|
132
|
+
The starter includes:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"scripts": {
|
|
137
|
+
"build": "vekil-app build ./vekil.app.mjs --out ./dist",
|
|
138
|
+
"conformance": "vekil-app conformance ./vekil.app.mjs",
|
|
139
|
+
"dev": "node --env-file=.env runtime.mjs",
|
|
140
|
+
"keys": "vekil-app keys ./vekil.manifest.json",
|
|
141
|
+
"validate": "vekil-app validate ./vekil.app.mjs"
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Recommended CI sequence:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
pnpm install --frozen-lockfile
|
|
150
|
+
pnpm validate
|
|
151
|
+
pnpm conformance
|
|
152
|
+
pnpm build
|
|
153
|
+
git diff --exit-code -- dist/definition.json dist/app-definition.schema.json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Commit generated artifacts only when the App repository's release policy
|
|
157
|
+
requires it. Always generate them with the SDK version locked by the project.
|
|
158
|
+
|
|
159
|
+
## Operations handled in Builder
|
|
160
|
+
|
|
161
|
+
The CLI does not sign in to Vekil, create or publish App releases, deploy Runtime
|
|
162
|
+
code, or manage provider credentials. Use Builder for App testing and release;
|
|
163
|
+
use your own deployment and secret-management systems for the Runtime.
|
package/docs/runtime.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Remote Runtime
|
|
2
|
+
|
|
3
|
+
A Remote Runtime is the HTTPS service that implements an App's typed actions.
|
|
4
|
+
The SDK verifies incoming Vekil requests before your handlers run and signs the
|
|
5
|
+
responses returned by those handlers.
|
|
6
|
+
|
|
7
|
+
Your Runtime remains responsible for provider APIs, credentials, business
|
|
8
|
+
rules, data storage, and operational reliability.
|
|
9
|
+
|
|
10
|
+
## Recommended project structure
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
src/
|
|
14
|
+
├── actions/
|
|
15
|
+
│ ├── execute.ts
|
|
16
|
+
│ └── plan.ts
|
|
17
|
+
├── connection/
|
|
18
|
+
│ ├── complete.ts
|
|
19
|
+
│ ├── disconnect.ts
|
|
20
|
+
│ └── prepare.ts
|
|
21
|
+
├── provider/
|
|
22
|
+
│ ├── client.ts
|
|
23
|
+
│ ├── mapper.ts
|
|
24
|
+
│ └── token-vault.ts
|
|
25
|
+
├── runtime/
|
|
26
|
+
│ ├── bindings.ts
|
|
27
|
+
│ ├── handlers.ts
|
|
28
|
+
│ ├── replay-store.ts
|
|
29
|
+
│ └── server.ts
|
|
30
|
+
└── index.ts
|
|
31
|
+
vekil.app.mjs
|
|
32
|
+
vekil.manifest.json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Keep provider code separate from protocol adapters. Handlers should translate
|
|
36
|
+
between typed App operations and provider-specific calls.
|
|
37
|
+
|
|
38
|
+
## Runtime manifest
|
|
39
|
+
|
|
40
|
+
Builder provides `vekil.manifest.json` when a test release is prepared. Keep
|
|
41
|
+
that file with the matching Runtime deployment and load it at startup:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import manifest from "../vekil.manifest.json" with { type: "json" };
|
|
45
|
+
import { createAppRuntimeBindings } from "@vekil/app-sdk/runtime";
|
|
46
|
+
|
|
47
|
+
export const bindings = createAppRuntimeBindings(manifest);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The SDK validates the manifest and rejects local keys that are not part of the
|
|
51
|
+
prepared release. Download a fresh manifest whenever the Definition changes.
|
|
52
|
+
|
|
53
|
+
## Implement the handlers
|
|
54
|
+
|
|
55
|
+
Provide all methods in `AppRuntimeV1Handlers`:
|
|
56
|
+
|
|
57
|
+
| Handler | Responsibility |
|
|
58
|
+
| ------------------------ | ----------------------------------------------------------------- |
|
|
59
|
+
| `prepareInstallation` | Start a connection flow or report that no connection is required. |
|
|
60
|
+
| `completeInstallation` | Verify the callback and return normalized connection status. |
|
|
61
|
+
| `disconnectInstallation` | Revoke provider access and App-owned connection state. |
|
|
62
|
+
| `planAction` | Check feasibility and return bounded typed actions or questions. |
|
|
63
|
+
| `executeAction` | Perform one approved action idempotently and return its result. |
|
|
64
|
+
| `health` | Report whether the current deployment can safely accept work. |
|
|
65
|
+
|
|
66
|
+
Protocol envelopes are validated only after signature, timestamp, audience,
|
|
67
|
+
nonce, and schema checks pass. Your handlers must still validate App-owned
|
|
68
|
+
fields before using them.
|
|
69
|
+
|
|
70
|
+
Use enums exported by `@vekil/app-sdk` instead of free-form status strings.
|
|
71
|
+
|
|
72
|
+
## Start the server
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { createRemoteAppRuntime } from "@vekil/app-sdk/runtime";
|
|
76
|
+
|
|
77
|
+
const runtime = createRemoteAppRuntime({
|
|
78
|
+
handlers,
|
|
79
|
+
manifest,
|
|
80
|
+
platformJwksUrl: process.env.VEKIL_PLATFORM_JWKS_URL!,
|
|
81
|
+
replayStore,
|
|
82
|
+
signingIdentity
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For Fetch-compatible hosts:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
export default {
|
|
90
|
+
fetch(request: Request): Promise<Response> {
|
|
91
|
+
return runtime.handle(request);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For Node HTTP:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { createServer } from "node:http";
|
|
100
|
+
|
|
101
|
+
createServer((request, response) => {
|
|
102
|
+
void runtime.handleNodeRequest(request, response);
|
|
103
|
+
}).listen(Number(process.env.PORT ?? 4100));
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Both adapters preserve the signed raw body, reject bodies over 2 MiB, and serve
|
|
107
|
+
the public JWKS endpoint declared by the App.
|
|
108
|
+
|
|
109
|
+
## Generate signing keys
|
|
110
|
+
|
|
111
|
+
Generate an Ed25519 key pair from the manifest downloaded from Builder:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pnpm vekil-app keys ./vekil.manifest.json
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The command prints two private environment values and a public JWKS document.
|
|
118
|
+
Store the private values in a secret manager. Publish only the JWKS.
|
|
119
|
+
|
|
120
|
+
Load the private values without handling protocol issuers yourself:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { readAppRuntimeSigningIdentity } from "@vekil/app-sdk/runtime";
|
|
124
|
+
|
|
125
|
+
const signingIdentity = readAppRuntimeSigningIdentity({
|
|
126
|
+
manifest,
|
|
127
|
+
keyId: process.env.VEKIL_APP_SIGNING_KEY_ID,
|
|
128
|
+
privateJwkBase64url: process.env.VEKIL_APP_SIGNING_PRIVATE_JWK_BASE64URL
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if (!signingIdentity) {
|
|
132
|
+
throw new Error("Runtime signing configuration is incomplete.");
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
For rotation, publish both old and new public keys, deploy the new private key,
|
|
137
|
+
wait through the accepted signature window, and then remove the old public key.
|
|
138
|
+
Never expose a private JWK through the JWKS route.
|
|
139
|
+
|
|
140
|
+
## Add replay protection
|
|
141
|
+
|
|
142
|
+
Every production Runtime needs an atomic durable replay store:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import type { AppSignatureReplayClaim, AppSignatureReplayStore } from "@vekil/app-sdk/runtime";
|
|
146
|
+
|
|
147
|
+
export class DurableReplayStore implements AppSignatureReplayStore {
|
|
148
|
+
async claim(input: AppSignatureReplayClaim): Promise<boolean> {
|
|
149
|
+
// Atomically claim this signed request until input.expiresAt.
|
|
150
|
+
// Return false if it was already claimed.
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Use a database uniqueness constraint or an atomic cache operation with expiry.
|
|
157
|
+
A read followed by a separate write is unsafe under concurrency.
|
|
158
|
+
|
|
159
|
+
`createDevelopmentReplayStore()` is process-local and exists only for local
|
|
160
|
+
development and tests.
|
|
161
|
+
|
|
162
|
+
## Connect provider accounts
|
|
163
|
+
|
|
164
|
+
When an App needs OAuth or another provider login:
|
|
165
|
+
|
|
166
|
+
1. `prepareInstallation` creates an authorization attempt bound to the signed
|
|
167
|
+
installation request and return URL.
|
|
168
|
+
2. The user authorizes directly with the provider.
|
|
169
|
+
3. The provider redirects to your Runtime callback.
|
|
170
|
+
4. The Runtime verifies state, exchanges the code, and stores provider tokens
|
|
171
|
+
in its own encrypted credential store.
|
|
172
|
+
5. `completeInstallation` consumes a one-time completion reference and returns
|
|
173
|
+
normalized connection status to Vekil.
|
|
174
|
+
|
|
175
|
+
Provider client secrets, access tokens, refresh tokens, webhook secrets, and
|
|
176
|
+
raw provider account payloads must never be returned to Vekil.
|
|
177
|
+
|
|
178
|
+
Bind authorization attempts and stored credentials to the exact installation
|
|
179
|
+
and current authorization grant. Reject stale callbacks, reused completion
|
|
180
|
+
references, account switches, and permission downgrades.
|
|
181
|
+
|
|
182
|
+
## Plan before executing
|
|
183
|
+
|
|
184
|
+
`planAction` is read-only. It may:
|
|
185
|
+
|
|
186
|
+
- validate provider reachability;
|
|
187
|
+
- identify missing information;
|
|
188
|
+
- return bounded choices;
|
|
189
|
+
- propose declared actions;
|
|
190
|
+
- provide typed grounding.
|
|
191
|
+
|
|
192
|
+
It must not perform the requested side effect.
|
|
193
|
+
|
|
194
|
+
`executeAction` receives one authorized action, App Context, idempotency key,
|
|
195
|
+
and attempt number. It must:
|
|
196
|
+
|
|
197
|
+
- validate App-owned input;
|
|
198
|
+
- recheck provider preconditions immediately before a write;
|
|
199
|
+
- use the supplied idempotency key for every side effect;
|
|
200
|
+
- honor cancellation, timeout, and retry semantics;
|
|
201
|
+
- return only declared outcomes, artifacts, and Bubbles;
|
|
202
|
+
- classify retryable and non-retryable errors accurately.
|
|
203
|
+
|
|
204
|
+
Return structured facts and results rather than an unbounded chat transcript.
|
|
205
|
+
Vekil turns those results into a response and can continue the conversation.
|
|
206
|
+
|
|
207
|
+
## Read App Context and artifacts
|
|
208
|
+
|
|
209
|
+
App Context is scoped to one action and one authorization grant. Use only the
|
|
210
|
+
scopes declared by that action. Treat a missing optional scope as unavailable.
|
|
211
|
+
|
|
212
|
+
Reserved artifacts are available under `$artifacts`:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { readAppActionInput, readAppContextArtifactInputs } from "@vekil/app-sdk";
|
|
216
|
+
|
|
217
|
+
const input = inputSchema.parse(readAppActionInput(request.input));
|
|
218
|
+
const artifacts = readAppContextArtifactInputs(request.input);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Consume only artifact contracts declared by the action. Preserve provenance and
|
|
222
|
+
sensitivity, and never widen visibility in Runtime code.
|
|
223
|
+
|
|
224
|
+
## Report health
|
|
225
|
+
|
|
226
|
+
Use the SDK helper so the response always matches the loaded manifest:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { AppRuntimeHealthStatus } from "@vekil/app-sdk";
|
|
230
|
+
import { createAppRuntimeHealthResponse } from "@vekil/app-sdk/runtime";
|
|
231
|
+
|
|
232
|
+
const health = async () =>
|
|
233
|
+
createAppRuntimeHealthResponse({
|
|
234
|
+
manifest,
|
|
235
|
+
status: AppRuntimeHealthStatus.HEALTHY
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Return `HEALTHY` only when the deployment can safely accept all declared work.
|
|
240
|
+
Use `DEGRADED` when the process is reachable but a required dependency or
|
|
241
|
+
configuration is unavailable.
|
|
242
|
+
|
|
243
|
+
## Production checklist
|
|
244
|
+
|
|
245
|
+
- Deploy the Runtime and JWKS endpoints over HTTPS.
|
|
246
|
+
- Deploy the exact manifest tested in Builder.
|
|
247
|
+
- Store signing keys and provider credentials outside source control.
|
|
248
|
+
- Use a durable atomic replay store with expiry.
|
|
249
|
+
- Persist idempotency records and provider write results.
|
|
250
|
+
- Keep outbound timeouts below the declared action timeout.
|
|
251
|
+
- Redact credentials, signed bodies, personal data, and App Context from logs.
|
|
252
|
+
- Emit request, execution, installation, and trace references safely.
|
|
253
|
+
- Test duplicate delivery, retry, timeout, cancellation, and stale grants.
|
|
254
|
+
- Test key rotation, token refresh, revocation, account switch, and scope change.
|
|
255
|
+
- Keep database migrations and rollback behavior release-specific.
|
|
256
|
+
- Run Builder's Runtime test before private installation or submission.
|