eve 0.16.0 → 0.16.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/CHANGELOG.md +11 -0
- package/dist/src/channel/compiled-channel.d.ts +2 -0
- package/dist/src/channel/cors.d.ts +43 -0
- package/dist/src/channel/cors.js +1 -0
- package/dist/src/cli/dev/tui/agent-info-probe.d.ts +16 -0
- package/dist/src/cli/dev/tui/agent-info-probe.js +1 -0
- package/dist/src/cli/dev/tui/mcp-connection-status.d.ts +19 -0
- package/dist/src/cli/dev/tui/mcp-connection-status.js +1 -0
- package/dist/src/cli/dev/tui/prompt-command-handler.js +1 -1
- package/dist/src/cli/dev/tui/remote-connection-probe.js +1 -1
- package/dist/src/cli/dev/tui/runner.d.ts +4 -0
- package/dist/src/cli/dev/tui/runner.js +1 -1
- package/dist/src/cli/dev/tui/setup-commands.d.ts +1 -0
- package/dist/src/cli/dev/tui/setup-commands.js +1 -1
- package/dist/src/cli/dev/tui/setup-panel.js +1 -1
- package/dist/src/cli/dev/tui/tui.js +1 -1
- package/dist/src/compiler/manifest.d.ts +8 -2
- package/dist/src/compiler/manifest.js +1 -1
- package/dist/src/compiler/normalize-channel.js +1 -1
- package/dist/src/context/providers/connection-key.d.ts +9 -0
- package/dist/src/context/providers/connection-key.js +1 -0
- package/dist/src/context/providers/connection.d.ts +1 -9
- package/dist/src/context/providers/connection.js +1 -1
- package/dist/src/harness/input-requests.d.ts +1 -0
- package/dist/src/harness/input-requests.js +1 -1
- package/dist/src/harness/tool-loop.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/nitro/host/channel-routes.d.ts +2 -0
- package/dist/src/internal/nitro/host/channel-routes.js +5 -3
- package/dist/src/internal/nitro/host/start-development-server.js +2 -2
- package/dist/src/packages/eve-catalog/src/index.js +1 -1
- package/dist/src/public/channels/eve.d.ts +31 -0
- package/dist/src/public/channels/eve.js +1 -1
- package/dist/src/public/channels/github/githubChannel.js +1 -1
- package/dist/src/public/channels/github/inbound-types.d.ts +189 -0
- package/dist/src/public/channels/github/inbound-types.js +1 -0
- package/dist/src/public/channels/github/inbound.d.ts +2 -190
- package/dist/src/public/channels/github/inbound.js +2 -2
- package/dist/src/public/channels/index.d.ts +1 -1
- package/dist/src/public/definitions/defineChannel.d.ts +10 -0
- package/dist/src/public/definitions/defineChannel.js +1 -1
- package/dist/src/runtime/connections/scoped-authorization.d.ts +9 -0
- package/dist/src/runtime/connections/scoped-authorization.js +1 -1
- package/dist/src/runtime/connections/types.d.ts +4 -4
- package/dist/src/runtime/framework-channels/index.js +1 -1
- package/dist/src/runtime/framework-tools/connection-search-dynamic.js +1 -1
- package/dist/src/runtime/framework-tools/index.d.ts +1 -1
- package/dist/src/runtime/framework-tools/index.js +1 -1
- package/dist/src/runtime/framework-tools/skill.js +1 -1
- package/dist/src/runtime/resolve-channel.js +1 -1
- package/dist/src/runtime/types.d.ts +2 -0
- package/dist/src/setup/boxes/select-connections.js +1 -1
- package/dist/src/setup/cli/option-row.d.ts +2 -0
- package/dist/src/setup/cli/option-row.js +1 -1
- package/dist/src/setup/flows/connections.d.ts +1 -0
- package/dist/src/setup/flows/connections.js +2 -2
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/docs/channels/custom.mdx +19 -0
- package/docs/channels/eve.mdx +23 -0
- package/docs/concepts/sessions-runs-and-streaming.md +2 -0
- package/docs/connections/mcp.mdx +161 -19
- package/docs/connections/openapi.mdx +133 -36
- package/docs/connections/overview.mdx +4 -4
- package/docs/patterns/multi-tenant-approvals.md +4 -0
- package/docs/patterns/multi-tenant-auth.md +156 -37
- package/docs/tools/human-in-the-loop.md +3 -1
- package/package.json +1 -1
|
@@ -35,6 +35,100 @@ export function requireTenantCaller(ctx: SessionContext): {
|
|
|
35
35
|
|
|
36
36
|
The tenant comes from verified route auth, never a prompt, tool argument, or remote API response. See [Auth & route protection](../guides/auth-and-route-protection) for custom session and OIDC examples.
|
|
37
37
|
|
|
38
|
+
## Authenticate with your own API key or JWT
|
|
39
|
+
|
|
40
|
+
For production apps with many customer orgs, the inbound credential is often
|
|
41
|
+
your own API key, session cookie, or JWT. Use that credential to authenticate
|
|
42
|
+
the caller before eve starts a run, then stamp the tenant onto the session:
|
|
43
|
+
|
|
44
|
+
```ts title="agent/channels/eve.ts"
|
|
45
|
+
import { eveChannel } from "eve/channels/eve";
|
|
46
|
+
import { localDev, type AuthFn } from "eve/channels/auth";
|
|
47
|
+
import { verifyAgentCaller } from "../../lib/app-auth.js";
|
|
48
|
+
|
|
49
|
+
function tenantAppAuth(): AuthFn<Request> {
|
|
50
|
+
return async (request) => {
|
|
51
|
+
const caller = await verifyAgentCaller(request);
|
|
52
|
+
if (caller === null) return null;
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
authenticator: "app",
|
|
56
|
+
issuer: "https://app.example.com",
|
|
57
|
+
principalId: caller.userId,
|
|
58
|
+
principalType: "user",
|
|
59
|
+
subject: caller.userId,
|
|
60
|
+
attributes: {
|
|
61
|
+
tenantId: caller.tenantId,
|
|
62
|
+
roles: caller.roles,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default eveChannel({
|
|
69
|
+
auth: [tenantAppAuth(), localDev()],
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`verifyAgentCaller` is application code. It can validate an API key, verify a
|
|
74
|
+
JWT, or read an app session, but it should return only after the user belongs
|
|
75
|
+
to the tenant they are claiming. Keep `principalId` stable for the same user,
|
|
76
|
+
include an `issuer` when ids can come from more than one identity system, and
|
|
77
|
+
put routing facts such as `tenantId` in `attributes`.
|
|
78
|
+
|
|
79
|
+
If one user can switch between orgs, authenticate the selected org on every
|
|
80
|
+
session create or continue request and stamp that selected `tenantId` onto the
|
|
81
|
+
current turn.
|
|
82
|
+
|
|
83
|
+
This is not connection OAuth. The user is already authenticated to your app;
|
|
84
|
+
eve uses that verified principal to pick the correct outbound credential.
|
|
85
|
+
|
|
86
|
+
## Build tenant connection auth
|
|
87
|
+
|
|
88
|
+
For Bearer tokens or tenant-scoped JWTs, write one non-interactive auth helper
|
|
89
|
+
and reuse it across OpenAPI and MCP connections. `principalType: "user"` tells
|
|
90
|
+
eve to require the authenticated user from route auth, key the step-local token
|
|
91
|
+
cache by that user, and pass the projected principal into `getToken`:
|
|
92
|
+
|
|
93
|
+
```ts title="agent/lib/tenant-connection-auth.ts"
|
|
94
|
+
import type { ConnectionPrincipal, NonInteractiveAuthorizationDefinition } from "eve/connections";
|
|
95
|
+
import { tenantCredentials, type TenantService } from "./tenant-credentials.js";
|
|
96
|
+
|
|
97
|
+
function requireTenantPrincipal(principal: ConnectionPrincipal): {
|
|
98
|
+
tenantId: string;
|
|
99
|
+
userId: string;
|
|
100
|
+
} {
|
|
101
|
+
const tenantId = principal.type === "user" ? principal.attributes?.tenantId : undefined;
|
|
102
|
+
|
|
103
|
+
if (principal.type !== "user" || typeof tenantId !== "string") {
|
|
104
|
+
throw new Error("An authenticated tenant user is required.");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { tenantId, userId: principal.id };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function tenantBearerAuth(service: TenantService): NonInteractiveAuthorizationDefinition {
|
|
111
|
+
return {
|
|
112
|
+
principalType: "user",
|
|
113
|
+
async getToken({ principal }) {
|
|
114
|
+
const { tenantId, userId } = requireTenantPrincipal(principal);
|
|
115
|
+
const credential = await tenantCredentials.getBearer(tenantId, service, { userId });
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
token: credential.bearerToken,
|
|
119
|
+
...(credential.expiresAt ? { expiresAt: credential.expiresAt } : {}),
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The model never supplies `tenantId` or sees the returned token. If the remote
|
|
127
|
+
service uses tenant-level credentials shared by multiple users, keep the
|
|
128
|
+
credential lookup keyed by `tenantId` in your provider; user-scoped connection
|
|
129
|
+
auth is still useful because it rejects unauthenticated sessions and keeps
|
|
130
|
+
eve's token cache from crossing caller identities.
|
|
131
|
+
|
|
38
132
|
## Authenticate an authored tool call
|
|
39
133
|
|
|
40
134
|
Derive the tenant inside `execute`, fetch its credential from your application provider, and construct the outbound request:
|
|
@@ -50,11 +144,11 @@ export default defineTool({
|
|
|
50
144
|
inputSchema: z.object({ limit: z.number().int().min(1).max(100).default(20) }),
|
|
51
145
|
async execute({ limit }, ctx) {
|
|
52
146
|
const { tenantId } = requireTenantCaller(ctx);
|
|
53
|
-
const credential = await tenantCredentials.
|
|
147
|
+
const credential = await tenantCredentials.getBearer(tenantId, "billing");
|
|
54
148
|
|
|
55
149
|
const response = await fetch(`https://billing.example.com/v1/invoices?limit=${limit}`, {
|
|
56
150
|
headers: {
|
|
57
|
-
authorization: `Bearer ${credential.
|
|
151
|
+
authorization: `Bearer ${credential.bearerToken}`,
|
|
58
152
|
"x-account-id": credential.externalTenantId,
|
|
59
153
|
},
|
|
60
154
|
});
|
|
@@ -68,40 +162,29 @@ The model controls only `limit`. Even if a prompt asks for another tenant, the e
|
|
|
68
162
|
|
|
69
163
|
## Authenticate an OpenAPI connection
|
|
70
164
|
|
|
71
|
-
|
|
165
|
+
Attach the reusable auth helper to the connection. Generated operation tools
|
|
166
|
+
receive the token at call time without exposing it to the model:
|
|
72
167
|
|
|
73
168
|
```ts title="agent/connections/billing.ts"
|
|
74
169
|
import { defineOpenAPIConnection } from "eve/connections";
|
|
75
170
|
import { tenantCredentials } from "../lib/tenant-credentials.js";
|
|
171
|
+
import { tenantBearerAuth } from "../lib/tenant-connection-auth.js";
|
|
76
172
|
import { requireTenantCaller } from "../lib/tenant.js";
|
|
77
173
|
|
|
78
174
|
export default defineOpenAPIConnection({
|
|
79
175
|
spec: "https://billing.example.com/openapi.json",
|
|
80
176
|
description: "Invoices and subscriptions for the current tenant.",
|
|
81
177
|
operations: { allow: ["listInvoices", "getInvoice", "updateSubscription"] },
|
|
82
|
-
|
|
83
|
-
auth: async (ctx) => {
|
|
84
|
-
const { tenantId } = requireTenantCaller(ctx);
|
|
85
|
-
const credential = await tenantCredentials.get(tenantId, "billing");
|
|
86
|
-
return {
|
|
87
|
-
principalType: "user",
|
|
88
|
-
getToken: async () => ({
|
|
89
|
-
token: credential.token,
|
|
90
|
-
...(credential.expiresAt ? { expiresAt: credential.expiresAt } : {}),
|
|
91
|
-
}),
|
|
92
|
-
};
|
|
93
|
-
},
|
|
178
|
+
auth: tenantBearerAuth("billing"),
|
|
94
179
|
|
|
95
180
|
headers: async (ctx) => {
|
|
96
181
|
const { tenantId } = requireTenantCaller(ctx);
|
|
97
|
-
const credential = await tenantCredentials.
|
|
182
|
+
const credential = await tenantCredentials.getBearer(tenantId, "billing");
|
|
98
183
|
return { "X-Account-Id": credential.externalTenantId };
|
|
99
184
|
},
|
|
100
185
|
});
|
|
101
186
|
```
|
|
102
187
|
|
|
103
|
-
`principalType: "user"` requires an authenticated user and keeps eve's step-local token handling scoped to that principal. If credentials are per user rather than shared by a tenant, include `userId` in your provider lookup.
|
|
104
|
-
|
|
105
188
|
Do not return `Authorization` from `headers` when `auth` is present. eve constructs that header from `getToken` and rejects conflicting definitions.
|
|
106
189
|
|
|
107
190
|
## Authenticate an MCP connection
|
|
@@ -111,63 +194,99 @@ MCP connections accept the same callbacks:
|
|
|
111
194
|
```ts title="agent/connections/support.ts"
|
|
112
195
|
import { defineMcpClientConnection } from "eve/connections";
|
|
113
196
|
import { tenantCredentials } from "../lib/tenant-credentials.js";
|
|
197
|
+
import { tenantBearerAuth } from "../lib/tenant-connection-auth.js";
|
|
114
198
|
import { requireTenantCaller } from "../lib/tenant.js";
|
|
115
199
|
|
|
116
200
|
export default defineMcpClientConnection({
|
|
117
201
|
url: "https://support.example.com/mcp",
|
|
118
202
|
description: "Support tickets and customers for the current tenant.",
|
|
119
203
|
tools: { allow: ["search_tickets", "get_ticket", "add_internal_note"] },
|
|
120
|
-
|
|
121
|
-
auth: async (ctx) => {
|
|
122
|
-
const { tenantId } = requireTenantCaller(ctx);
|
|
123
|
-
const credential = await tenantCredentials.get(tenantId, "support");
|
|
124
|
-
return {
|
|
125
|
-
principalType: "user",
|
|
126
|
-
getToken: async () => ({
|
|
127
|
-
token: credential.token,
|
|
128
|
-
...(credential.expiresAt ? { expiresAt: credential.expiresAt } : {}),
|
|
129
|
-
}),
|
|
130
|
-
};
|
|
131
|
-
},
|
|
204
|
+
auth: tenantBearerAuth("support"),
|
|
132
205
|
|
|
133
206
|
headers: {
|
|
134
207
|
"X-Workspace-Id": async (ctx) => {
|
|
135
208
|
const { tenantId } = requireTenantCaller(ctx);
|
|
136
|
-
const credential = await tenantCredentials.
|
|
209
|
+
const credential = await tenantCredentials.getBearer(tenantId, "support");
|
|
137
210
|
return credential.externalTenantId;
|
|
138
211
|
},
|
|
139
212
|
},
|
|
140
213
|
});
|
|
141
214
|
```
|
|
142
215
|
|
|
143
|
-
|
|
216
|
+
## Authenticate an API-key-only connection
|
|
217
|
+
|
|
218
|
+
If the remote server does not accept Bearer auth, omit `auth` and return the
|
|
219
|
+
tenant API key from `headers` instead:
|
|
220
|
+
|
|
221
|
+
```ts title="agent/connections/support.ts"
|
|
222
|
+
import { defineMcpClientConnection } from "eve/connections";
|
|
223
|
+
import { tenantCredentials } from "../lib/tenant-credentials.js";
|
|
224
|
+
import { requireTenantCaller } from "../lib/tenant.js";
|
|
225
|
+
|
|
226
|
+
export default defineMcpClientConnection({
|
|
227
|
+
url: "https://support.example.com/mcp",
|
|
228
|
+
description: "Support tickets and customers for the current tenant.",
|
|
229
|
+
tools: { allow: ["search_tickets", "get_ticket", "add_internal_note"] },
|
|
230
|
+
|
|
231
|
+
headers: async (ctx) => {
|
|
232
|
+
const { tenantId, userId } = requireTenantCaller(ctx);
|
|
233
|
+
const credential = await tenantCredentials.getApiKey(tenantId, "support", { userId });
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
"X-Api-Key": credential.apiKey,
|
|
237
|
+
"X-Workspace-Id": credential.externalTenantId,
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Use the same shape for OpenAPI connections. API keys resolved in `headers` are
|
|
244
|
+
sent only on outbound requests; they are not model inputs or tool results.
|
|
144
245
|
|
|
145
246
|
## Supply the credential provider
|
|
146
247
|
|
|
147
248
|
The eve-facing files need only this application contract:
|
|
148
249
|
|
|
149
250
|
```ts title="agent/lib/tenant-credentials.ts"
|
|
150
|
-
export
|
|
151
|
-
|
|
251
|
+
export type TenantService = "billing" | "support";
|
|
252
|
+
|
|
253
|
+
export interface TenantBearerCredential {
|
|
254
|
+
bearerToken: string;
|
|
152
255
|
externalTenantId: string;
|
|
153
256
|
expiresAt?: number;
|
|
154
257
|
}
|
|
155
258
|
|
|
259
|
+
export interface TenantApiKeyCredential {
|
|
260
|
+
apiKey: string;
|
|
261
|
+
externalTenantId: string;
|
|
262
|
+
}
|
|
263
|
+
|
|
156
264
|
export interface TenantCredentialProvider {
|
|
157
|
-
|
|
265
|
+
getBearer(
|
|
266
|
+
tenantId: string,
|
|
267
|
+
service: TenantService,
|
|
268
|
+
options?: { userId?: string },
|
|
269
|
+
): Promise<TenantBearerCredential>;
|
|
270
|
+
getApiKey(
|
|
271
|
+
tenantId: string,
|
|
272
|
+
service: TenantService,
|
|
273
|
+
options?: { userId?: string },
|
|
274
|
+
): Promise<TenantApiKeyCredential>;
|
|
158
275
|
}
|
|
159
276
|
|
|
160
277
|
export { tenantCredentials } from "../../lib/tenant-credentials.js";
|
|
161
278
|
```
|
|
162
279
|
|
|
163
|
-
Implement the provider with the secret system your application already trusts:
|
|
280
|
+
Implement the provider with the secret system your application already trusts:
|
|
281
|
+
a cloud secret manager, an encrypted database table, a token broker, or an
|
|
282
|
+
out-of-band OAuth flow you own. eve does not prescribe that choice.
|
|
164
283
|
|
|
165
284
|
The provider must fail closed for unknown tenants, avoid returning secrets in logs or errors, and rotate or refresh credentials before `expiresAt`. Prefer credentials that are themselves restricted to one remote tenant; treat workspace headers as routing, not authorization.
|
|
166
285
|
|
|
167
286
|
## What the model can and cannot see
|
|
168
287
|
|
|
169
288
|
1. Route auth stamps the verified tenant onto the session.
|
|
170
|
-
2.
|
|
289
|
+
2. Tool code reads `ctx.session.auth.current`, and connection auth receives the projected `principal`.
|
|
171
290
|
3. The application provider resolves the corresponding credential.
|
|
172
291
|
4. eve sends the resulting token and headers directly to the remote service.
|
|
173
292
|
5. Neither becomes a model message or tool result.
|
|
@@ -100,10 +100,12 @@ Approvals and questions share one protocol:
|
|
|
100
100
|
1. The model requests input (an approval, or an `ask_question`).
|
|
101
101
|
2. eve emits an `input.requested` stream event carrying the pending requests.
|
|
102
102
|
3. The turn parks at `session.waiting`, durably, for as long as it takes.
|
|
103
|
-
4. The client answers with `inputResponses` (structured, keyed by `requestId`) or a normal follow-up `message`. A follow-up whose text matches an option label
|
|
103
|
+
4. The client answers with `inputResponses` (structured, keyed by `requestId`) or a normal follow-up `message`. A follow-up whose text matches an option ID, option label, or numeric option index resolves automatically, including approval options such as `approve` and `deny`.
|
|
104
104
|
|
|
105
105
|
The run picks back up exactly where it parked. Because the pause is durable, nothing is held in memory while it waits — the process can restart and the parked turn survives.
|
|
106
106
|
|
|
107
|
+
For approval requests, unrelated follow-up text does not deny the tool call. eve keeps the approval pending and holds that text until the approval is answered, then replays it as the next message in the session.
|
|
108
|
+
|
|
107
109
|
See [Sessions, runs & streaming](/docs/concepts/sessions-runs-and-streaming) for the full event and resume contract that this builds on.
|
|
108
110
|
|
|
109
111
|
## Answering from a client or channel
|