@zackbart/connecta-eve 0.36.0 → 0.36.2
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/README.md +53 -5
- package/dist/index.d.ts +21 -6
- package/dist/index.js +56 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,40 @@ again for each request, so rotation requires no process restart. A `getToken`
|
|
|
55
55
|
callback can return a string or a promise of one; it overrides the environment.
|
|
56
56
|
No user's identity is forwarded.
|
|
57
57
|
|
|
58
|
+
## Choosing the pool at runtime
|
|
59
|
+
|
|
60
|
+
Pass the same selector to both factories when the pool depends on the chat.
|
|
61
|
+
For example, an app that sets trusted `direct` channel metadata can keep its
|
|
62
|
+
choice fixed for a turn:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import type { DynamicContext } from "eve/connections";
|
|
66
|
+
|
|
67
|
+
export const selectPool = (ctx: DynamicContext) =>
|
|
68
|
+
ctx.memo(["connecta-pool", ctx.turn?.id ?? null], () =>
|
|
69
|
+
ctx.channel.metadata.direct === true && ctx.session.auth.current?.principalId === "alice"
|
|
70
|
+
? "notes_alice"
|
|
71
|
+
: "assistant",
|
|
72
|
+
);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Use `pool: selectPool` in both `connectaConnection` and `connectaInstructions`.
|
|
76
|
+
Eve evaluates the dynamic connection before every model call; the selected
|
|
77
|
+
pool is its `instanceKey`, so a parked call cannot replay against a changed
|
|
78
|
+
pool. A string still creates the same static connection.
|
|
79
|
+
|
|
80
|
+
Both factories call the selector independently. The helper never memoizes its
|
|
81
|
+
result. Memoize selector I/O with `ctx.memo` for your policy's lifetime so the
|
|
82
|
+
connection and instructions agree and repeated evaluations avoid repeated
|
|
83
|
+
lookups. Eve scopes memos per definition, so the two factories each perform
|
|
84
|
+
their first lookup. A turn key fixes each choice for that turn; omit the memo
|
|
85
|
+
to choose again at every evaluation. Ensure both lookups use consistent
|
|
86
|
+
app-owned policy data.
|
|
87
|
+
|
|
88
|
+
Each selected name must match `[a-z0-9_-]+`. A throwing selector or an invalid
|
|
89
|
+
name fails the turn, even when guidance is cached. Handle lookup failures in
|
|
90
|
+
your selector if it should fall back to a least-privileged pool.
|
|
91
|
+
|
|
58
92
|
## Eve 0.84.0 dependency override
|
|
59
93
|
|
|
60
94
|
Eve 0.84.0 pins undici 8.10.2. Connecta's development/test override does not ship
|
|
@@ -93,14 +127,19 @@ Instructions use `server/discover` (MCP 2026-07-28), falling back to legacy
|
|
|
93
127
|
non-OK status or an unsupported-method response. Network errors and timeouts
|
|
94
128
|
never trigger initialize. Both JSON and SSE replies are accepted. Each request
|
|
95
129
|
has a five-second timeout. Successful guidance is cached for ten minutes per
|
|
96
|
-
resolver; `ttlMs` overrides that duration. Eve evaluates
|
|
97
|
-
every model call; the helper keeps one result per
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
130
|
+
endpoint in each resolver; `ttlMs` overrides that duration. Eve evaluates
|
|
131
|
+
instructions before every model call; the helper keeps one result per endpoint
|
|
132
|
+
and turn in Eve's memo, with at most one refresh for that endpoint while its
|
|
133
|
+
memo is retained. Switching pools selects that endpoint's guidance, never the
|
|
134
|
+
previous pool's. A failed refresh keeps that endpoint's last copy, or adds
|
|
135
|
+
nothing if none has succeeded, and waits 30 seconds before retrying it. Other
|
|
136
|
+
endpoints keep their own TTL and backoff. Eve retains only memo keys used by the
|
|
137
|
+
latest evaluation; returning to a pool can refresh again if its cache expired.
|
|
101
138
|
When Eve cancels an evaluation, the helper rethrows the cancellation instead of
|
|
102
139
|
counting it as a failure: nothing is kept for that turn and no wait starts.
|
|
103
140
|
Tools remain usable. Instructions do not grant access.
|
|
141
|
+
Set `clientName` on `connectaInstructions` to identify its requests in server
|
|
142
|
+
logs; the default is `connecta-eve-instructions`.
|
|
104
143
|
|
|
105
144
|
## Vercel OIDC token source
|
|
106
145
|
|
|
@@ -130,6 +169,8 @@ export default connectaInstructions({ pool: "assistant", getToken: getVercelOidc
|
|
|
130
169
|
|
|
131
170
|
Install `@vercel/oidc` in the Eve app when using this variant. The helper has no
|
|
132
171
|
dependency on it and never switches credential types automatically.
|
|
172
|
+
Use the default workload audience: Connecta refuses tokens exchanged for a
|
|
173
|
+
custom audience, even when Vercel signed them.
|
|
133
174
|
|
|
134
175
|
## Contributor validation
|
|
135
176
|
|
|
@@ -149,3 +190,10 @@ isolate each matrix cell; Eve startup is separate from turn latency. Reports
|
|
|
149
190
|
retain actual Eve events and transport inputs. Token usage and model task
|
|
150
191
|
quality remain unmeasured; these results establish local runtime contracts,
|
|
151
192
|
not live Vercel workload admission or production model performance.
|
|
193
|
+
|
|
194
|
+
For a disposable workload check, use the
|
|
195
|
+
[contributor fixture recipe](https://github.com/zackbart/connecta/blob/main/packages/connecta-eve/scripts/workload-fixture/README.md) from a source
|
|
196
|
+
checkout. Its preparation command packs and installs the current core/helper,
|
|
197
|
+
checks local signed controls and cancellation, and creates no live resources.
|
|
198
|
+
Live execution requires separate owner authorization for the concrete account,
|
|
199
|
+
project, bounds, cost ceiling and cleanup. Keep its evidence outside the checkout.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type McpClientConnectionDefinition } from "eve/connections";
|
|
1
|
+
import { defineDynamic as defineDynamicConnection, type DynamicContext, type McpClientConnectionDefinition } from "eve/connections";
|
|
2
2
|
import { type Approval, type ApprovalPolicy } from "eve/tools/approval";
|
|
3
3
|
/** Where an Eve agent reaches Connecta and how it proves who it is. */
|
|
4
4
|
export interface ConnectaEndpointOptions {
|
|
@@ -15,7 +15,11 @@ export interface ConnectaEndpointOptions {
|
|
|
15
15
|
*/
|
|
16
16
|
getToken?: () => string | Promise<string>;
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
/** App-owned pool policy; use `ctx.memo` when its result should stay fixed. */
|
|
19
|
+
export type ConnectaPoolSelector = (ctx: DynamicContext) => string | Promise<string>;
|
|
20
|
+
export interface ConnectaConnectionOptions extends Omit<ConnectaEndpointOptions, "pool"> {
|
|
21
|
+
/** A fixed pool, or a policy Eve evaluates before every model call. */
|
|
22
|
+
pool?: string | ConnectaPoolSelector;
|
|
19
23
|
/** What the connection offers, for the model choosing which connection to search. */
|
|
20
24
|
description: string;
|
|
21
25
|
/** Eve's approval for every Connecta tool: a policy or configuration. Defaults to `never()`. */
|
|
@@ -27,7 +31,11 @@ export interface ConnectaConnectionOptions extends ConnectaEndpointOptions {
|
|
|
27
31
|
*/
|
|
28
32
|
gateWrites?: boolean;
|
|
29
33
|
}
|
|
30
|
-
export interface ConnectaInstructionsOptions extends ConnectaEndpointOptions {
|
|
34
|
+
export interface ConnectaInstructionsOptions extends Omit<ConnectaEndpointOptions, "pool"> {
|
|
35
|
+
/** A fixed pool, or the same policy used by the connection. */
|
|
36
|
+
pool?: string | ConnectaPoolSelector;
|
|
37
|
+
/** MCP client name for instruction requests. Default `connecta-eve-instructions`. */
|
|
38
|
+
clientName?: string;
|
|
31
39
|
/** How long fetched instructions are reused. Default ten minutes. */
|
|
32
40
|
ttlMs?: number;
|
|
33
41
|
/** Fetch implementation, for tests. */
|
|
@@ -44,12 +52,19 @@ export declare const gateConnectaWrites: ApprovalPolicy;
|
|
|
44
52
|
* The Eve connection for a Connecta agent endpoint, with app-owned auth:
|
|
45
53
|
* one agent token, sent as a bearer and re-read on every request.
|
|
46
54
|
*/
|
|
47
|
-
export declare function connectaConnection(options: ConnectaConnectionOptions
|
|
55
|
+
export declare function connectaConnection(options: ConnectaConnectionOptions & {
|
|
56
|
+
pool: ConnectaPoolSelector;
|
|
57
|
+
}): ReturnType<typeof defineDynamicConnection>;
|
|
58
|
+
export declare function connectaConnection(options: ConnectaConnectionOptions & {
|
|
59
|
+
pool?: string;
|
|
60
|
+
}): McpClientConnectionDefinition;
|
|
61
|
+
export declare function connectaConnection(options: ConnectaConnectionOptions): McpClientConnectionDefinition | ReturnType<typeof defineDynamicConnection>;
|
|
48
62
|
/**
|
|
49
63
|
* Connecta's instructions as Eve system instructions, refreshed once per turn.
|
|
50
64
|
* Eve does not pass an MCP server's `instructions` to the model, so this
|
|
51
65
|
* fetches them and caches each success; a failure keeps the last copy, or adds
|
|
52
66
|
* nothing, and the tools still work. Eve evaluates the resolver before every
|
|
53
|
-
* model call; a memo keyed on the turn keeps one result for
|
|
67
|
+
* model call; a memo keyed on the endpoint and turn keeps one result for each.
|
|
68
|
+
* Pool policy runs independently of the connection and owns its memo lifetime.
|
|
54
69
|
*/
|
|
55
|
-
export declare function connectaInstructions(options?: ConnectaInstructionsOptions): import("eve/connections").DynamicSentinel<import("eve/instructions").DynamicInstructionsResult,
|
|
70
|
+
export declare function connectaInstructions(options?: ConnectaInstructionsOptions): import("eve/connections").DynamicSentinel<import("eve/instructions").DynamicInstructionsResult, DynamicContext>;
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { defineMcpClientConnection } from "eve/connections";
|
|
1
|
+
import { defineDynamic as defineDynamicConnection, defineMcpClientConnection, } from "eve/connections";
|
|
2
2
|
import { defineDynamic, defineInstructions } from "eve/instructions";
|
|
3
3
|
import { never } from "eve/tools/approval";
|
|
4
4
|
function environment(name) {
|
|
5
5
|
const value = globalThis.process?.env?.[name];
|
|
6
6
|
return value?.trim() || undefined;
|
|
7
7
|
}
|
|
8
|
+
function validatePool(pool) {
|
|
9
|
+
if (typeof pool !== "string" || !/^[a-z0-9_-]+$/.test(pool)) {
|
|
10
|
+
throw new Error(`Connecta pool "${pool}" must match [a-z0-9_-]+.`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
8
13
|
/** The endpoint URL these options name. */
|
|
9
14
|
export function connectaEndpoint(options = {}) {
|
|
10
15
|
const url = options.url ?? environment("CONNECTA_URL");
|
|
@@ -12,8 +17,7 @@ export function connectaEndpoint(options = {}) {
|
|
|
12
17
|
throw new Error("Connecta needs a url, or CONNECTA_URL in the environment.");
|
|
13
18
|
if (options.pool === undefined)
|
|
14
19
|
return url;
|
|
15
|
-
|
|
16
|
-
throw new Error(`Connecta pool "${options.pool}" must match [a-z0-9_-]+.`);
|
|
20
|
+
validatePool(options.pool);
|
|
17
21
|
return `${url.replace(/\/+$/, "")}/agent/${options.pool}`;
|
|
18
22
|
}
|
|
19
23
|
async function tokenFor(options) {
|
|
@@ -27,13 +31,21 @@ async function tokenFor(options) {
|
|
|
27
31
|
* annotations Connecta derives from its own classification.
|
|
28
32
|
*/
|
|
29
33
|
export const gateConnectaWrites = ({ toolAnnotations }) => toolAnnotations?.readOnlyHint === true ? "not-applicable" : "user-approval";
|
|
30
|
-
/**
|
|
31
|
-
* The Eve connection for a Connecta agent endpoint, with app-owned auth:
|
|
32
|
-
* one agent token, sent as a bearer and re-read on every request.
|
|
33
|
-
*/
|
|
34
34
|
export function connectaConnection(options) {
|
|
35
|
+
const { pool, ...rest } = options;
|
|
36
|
+
if (typeof pool === "function") {
|
|
37
|
+
return defineDynamicConnection(async (ctx) => {
|
|
38
|
+
const selected = await pool(ctx);
|
|
39
|
+
validatePool(selected);
|
|
40
|
+
return connectionDefinition(options, connectaEndpoint({ ...rest, pool: selected }), selected);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return connectionDefinition(options, connectaEndpoint(pool === undefined ? rest : { ...rest, pool }));
|
|
44
|
+
}
|
|
45
|
+
function connectionDefinition(options, endpoint, instanceKey) {
|
|
35
46
|
return defineMcpClientConnection({
|
|
36
|
-
url:
|
|
47
|
+
url: endpoint,
|
|
48
|
+
...(instanceKey === undefined ? {} : { instanceKey }),
|
|
37
49
|
description: options.description,
|
|
38
50
|
auth: {
|
|
39
51
|
principalType: "app",
|
|
@@ -43,9 +55,10 @@ export function connectaConnection(options) {
|
|
|
43
55
|
});
|
|
44
56
|
}
|
|
45
57
|
/** Connecta's MCP `instructions` for one endpoint, or null when unavailable. */
|
|
46
|
-
async function instructionRequest(options, method, signal) {
|
|
58
|
+
async function instructionRequest(options, endpoint, method, signal) {
|
|
47
59
|
const modern = method === "server/discover";
|
|
48
|
-
const
|
|
60
|
+
const clientInfo = { name: options.clientName ?? "connecta-eve-instructions", version: "1" };
|
|
61
|
+
const response = await (options.fetch ?? fetch)(endpoint, {
|
|
49
62
|
method: "POST",
|
|
50
63
|
headers: {
|
|
51
64
|
Authorization: `Bearer ${await tokenFor(options)}`,
|
|
@@ -62,13 +75,13 @@ async function instructionRequest(options, method, signal) {
|
|
|
62
75
|
_meta: {
|
|
63
76
|
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
|
|
64
77
|
"io.modelcontextprotocol/clientCapabilities": {},
|
|
65
|
-
"io.modelcontextprotocol/clientInfo":
|
|
78
|
+
"io.modelcontextprotocol/clientInfo": clientInfo,
|
|
66
79
|
},
|
|
67
80
|
}
|
|
68
81
|
: {
|
|
69
82
|
protocolVersion: "2025-06-18",
|
|
70
83
|
capabilities: {},
|
|
71
|
-
clientInfo
|
|
84
|
+
clientInfo,
|
|
72
85
|
},
|
|
73
86
|
}),
|
|
74
87
|
signal: AbortSignal.any([signal, AbortSignal.timeout(5_000)]),
|
|
@@ -88,32 +101,36 @@ async function instructionRequest(options, method, signal) {
|
|
|
88
101
|
const instructions = JSON.parse(json).result?.instructions;
|
|
89
102
|
return typeof instructions === "string" && instructions.trim() ? instructions.trim() : null;
|
|
90
103
|
}
|
|
91
|
-
async function fetchConnectaInstructions(options, signal) {
|
|
104
|
+
async function fetchConnectaInstructions(options, endpoint, signal) {
|
|
92
105
|
// Only a completed discovery response can justify legacy negotiation.
|
|
93
106
|
// Network failures and timeouts propagate to the resolver's fail-open path.
|
|
94
|
-
const discovered = await instructionRequest(options, "server/discover", signal);
|
|
95
|
-
return discovered ?? instructionRequest(options, "initialize", signal);
|
|
107
|
+
const discovered = await instructionRequest(options, endpoint, "server/discover", signal);
|
|
108
|
+
return discovered ?? instructionRequest(options, endpoint, "initialize", signal);
|
|
96
109
|
}
|
|
97
110
|
/**
|
|
98
111
|
* Connecta's instructions as Eve system instructions, refreshed once per turn.
|
|
99
112
|
* Eve does not pass an MCP server's `instructions` to the model, so this
|
|
100
113
|
* fetches them and caches each success; a failure keeps the last copy, or adds
|
|
101
114
|
* nothing, and the tools still work. Eve evaluates the resolver before every
|
|
102
|
-
* model call; a memo keyed on the turn keeps one result for
|
|
115
|
+
* model call; a memo keyed on the endpoint and turn keeps one result for each.
|
|
116
|
+
* Pool policy runs independently of the connection and owns its memo lifetime.
|
|
103
117
|
*/
|
|
104
118
|
export function connectaInstructions(options = {}) {
|
|
105
119
|
const ttlMs = options.ttlMs ?? 600_000;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const current = async (signal) => {
|
|
120
|
+
const endpoints = new Map();
|
|
121
|
+
const current = async (endpoint, signal) => {
|
|
109
122
|
signal.throwIfAborted();
|
|
123
|
+
let state = endpoints.get(endpoint);
|
|
124
|
+
if (!state)
|
|
125
|
+
endpoints.set(endpoint, (state = { retryAfter: 0 }));
|
|
126
|
+
const { cached, retryAfter } = state;
|
|
110
127
|
if (cached && Date.now() - cached.at <= ttlMs)
|
|
111
128
|
return cached.content;
|
|
112
129
|
if (Date.now() < retryAfter)
|
|
113
130
|
return cached?.content ?? null;
|
|
114
131
|
let fresh;
|
|
115
132
|
try {
|
|
116
|
-
fresh = await fetchConnectaInstructions(options, signal);
|
|
133
|
+
fresh = await fetchConnectaInstructions(options, endpoint, signal);
|
|
117
134
|
}
|
|
118
135
|
catch {
|
|
119
136
|
// Eve's cancellation is not an endpoint failure: rethrowing keeps it out
|
|
@@ -122,12 +139,28 @@ export function connectaInstructions(options = {}) {
|
|
|
122
139
|
fresh = null;
|
|
123
140
|
}
|
|
124
141
|
if (fresh)
|
|
125
|
-
cached = { at: Date.now(), content: fresh };
|
|
126
|
-
retryAfter = fresh ? 0 : Date.now() + 30_000;
|
|
142
|
+
state.cached = { at: Date.now(), content: fresh };
|
|
143
|
+
state.retryAfter = fresh ? 0 : Date.now() + 30_000;
|
|
127
144
|
return fresh ?? cached?.content ?? null;
|
|
128
145
|
};
|
|
129
146
|
return defineDynamic(async (ctx) => {
|
|
130
|
-
const
|
|
147
|
+
const { pool, ...rest } = options;
|
|
148
|
+
let endpoint;
|
|
149
|
+
if (typeof pool === "function") {
|
|
150
|
+
const selected = await pool(ctx);
|
|
151
|
+
validatePool(selected);
|
|
152
|
+
endpoint = connectaEndpoint({ ...rest, pool: selected });
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// Static configuration errors have always omitted optional guidance.
|
|
156
|
+
try {
|
|
157
|
+
endpoint = connectaEndpoint(pool === undefined ? rest : { ...rest, pool });
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const content = await ctx.memo(["connecta-instructions", endpoint, ctx.turn?.id ?? null], ({ abortSignal }) => current(endpoint, abortSignal));
|
|
131
164
|
return content ? defineInstructions({ content: `Connecta guidance:\n${content}` }) : null;
|
|
132
165
|
});
|
|
133
166
|
}
|