@zackbart/connecta-eve 0.40.0 → 0.42.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/README.md +146 -18
- package/dist/index.d.ts +45 -3
- package/dist/index.js +224 -11
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Connect an Eve agent to a Connecta agent pool and load its endpoint instructions
|
|
|
4
4
|
once per turn. The helper runs in the Eve app; the Connecta server
|
|
5
5
|
runs separately. No `@zackbart/connecta` dependency is installed by this package.
|
|
6
6
|
|
|
7
|
-
Requires **Node 24+** and **Eve 1.0.0-rc.
|
|
7
|
+
Requires **Node 24+** and **Eve 1.0.0-rc.4**. Development and consumer
|
|
8
8
|
checks use that exact release candidate. The peer stays pinned during the RC
|
|
9
9
|
period; later candidates and stable 1.0 need a compatibility check. Helper
|
|
10
10
|
releases share Connecta's version and tag; choose the helper version matching
|
|
@@ -13,7 +13,7 @@ your server release.
|
|
|
13
13
|
## Install and add two files
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
npm install @zackbart/connecta-eve eve@1.0.0-rc.
|
|
16
|
+
npm install @zackbart/connecta-eve eve@1.0.0-rc.4
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
The server must declare your agent, admit it to the named pool and issue its
|
|
@@ -31,10 +31,7 @@ Create `agent/connections/connecta.ts`:
|
|
|
31
31
|
```ts
|
|
32
32
|
import { connectaConnection } from "@zackbart/connecta-eve";
|
|
33
33
|
|
|
34
|
-
export default connectaConnection({
|
|
35
|
-
pool: "assistant",
|
|
36
|
-
description: "Shared CRM and tickets for the assistant.",
|
|
37
|
-
});
|
|
34
|
+
export default connectaConnection({ pool: "assistant" });
|
|
38
35
|
```
|
|
39
36
|
|
|
40
37
|
Create `agent/instructions/connecta.ts`:
|
|
@@ -45,6 +42,25 @@ import { connectaInstructions } from "@zackbart/connecta-eve";
|
|
|
45
42
|
export default connectaInstructions({ pool: "assistant" });
|
|
46
43
|
```
|
|
47
44
|
|
|
45
|
+
`description` is optional. Eve shows it in its catalog listing and matches its
|
|
46
|
+
words against every search query for every tool of the connection, so the default
|
|
47
|
+
is a fixed neutral line. If you set one, describe the domain in prose, such as
|
|
48
|
+
`"Member records, giving and event registration."`. Do not list connectors or
|
|
49
|
+
operations: on a three-connector meta pool, `"CRM, invoices and tickets"` made the
|
|
50
|
+
query `"invoices"` return all six routing tools, 9.4 KB measured, and tells the
|
|
51
|
+
model nothing about the operations behind them. The instructions carry the roster.
|
|
52
|
+
A string such as
|
|
53
|
+
`"Company tools through Connecta"` tells the model nothing, so the helper warns
|
|
54
|
+
at startup.
|
|
55
|
+
|
|
56
|
+
Eve names a connection after its file. If yours is not `connecta.ts`, say so in
|
|
57
|
+
the instructions, or the model is told tool names that do not exist:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// agent/connections/workspace.ts gives the model workspace__* tools.
|
|
61
|
+
export default connectaInstructions({ pool: "assistant", connection: "workspace" });
|
|
62
|
+
```
|
|
63
|
+
|
|
48
64
|
Both factories accept `url` to override `CONNECTA_URL`. With `pool`, the URL is
|
|
49
65
|
`<url>/agent/<pool>`; without `pool`, supply the complete endpoint URL. Pool
|
|
50
66
|
names must match `[a-z0-9_-]+`. `connectaEndpoint({ url, pool })` returns the same
|
|
@@ -56,6 +72,16 @@ again for each request, so rotation requires no process restart. A `getToken`
|
|
|
56
72
|
callback can return a string or a promise of one; it overrides the environment.
|
|
57
73
|
No user's identity is forwarded.
|
|
58
74
|
|
|
75
|
+
`headers` adds request headers beside the bearer, such as a service token for an
|
|
76
|
+
edge in front of Connecta. Pass the same value to every factory. A function runs
|
|
77
|
+
for each request, so a rotated secret needs no restart. On guidance and skill
|
|
78
|
+
requests, the helper's own `Authorization` and MCP protocol headers always win.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const headers = () => ({ "X-Edge-Token": process.env.EDGE_TOKEN ?? "" });
|
|
82
|
+
export default connectaConnection({ pool: "assistant", headers });
|
|
83
|
+
```
|
|
84
|
+
|
|
59
85
|
## Choosing the pool at runtime
|
|
60
86
|
|
|
61
87
|
Pass the same selector to both factories when the pool depends on the chat.
|
|
@@ -90,16 +116,62 @@ Each selected name must match `[a-z0-9_-]+`. A throwing selector or an invalid
|
|
|
90
116
|
name fails the turn, even when guidance is cached. Handle lookup failures in
|
|
91
117
|
your selector if it should fall back to a least-privileged pool.
|
|
92
118
|
|
|
93
|
-
##
|
|
119
|
+
## Gating guidance and connections
|
|
120
|
+
|
|
121
|
+
Pass `when(ctx)` to load guidance only for eligible callers or turns. The helper
|
|
122
|
+
runs this predicate inside Eve's dynamic definition before selecting a pool,
|
|
123
|
+
reading a token, or using cached guidance. False or a thrown/rejected predicate
|
|
124
|
+
returns no guidance and performs no guidance fetch. It is evaluated before
|
|
125
|
+
every model call, including repeated evaluations within a turn.
|
|
94
126
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
127
|
+
```ts
|
|
128
|
+
export default connectaInstructions({
|
|
129
|
+
pool: "assistant",
|
|
130
|
+
when: (ctx) => ctx.session.auth.current?.principalId === "alice",
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`connectaConnection` accepts the same predicate and returns a dynamic connection
|
|
135
|
+
when it is provided. False or a policy failure removes that connection for the
|
|
136
|
+
current evaluation. The two factories evaluate their predicates independently;
|
|
137
|
+
use consistent trusted policy data as with a dynamic pool selector. This policy
|
|
138
|
+
controls what the app offers; Connecta still enforces endpoint authorization.
|
|
139
|
+
No Eve dynamic-value unwrapping is required.
|
|
140
|
+
|
|
141
|
+
## Upgrading to Eve 1.0.0-rc.4
|
|
142
|
+
|
|
143
|
+
Update the app to `eve@1.0.0-rc.4`, reinstall to refresh its lockfile, then
|
|
144
|
+
rebuild and test it before deploying. The helper's connection, dynamic pool,
|
|
145
|
+
instructions and approval APIs use Eve's stable entry points and require no
|
|
146
|
+
authoring changes for this RC. Eve's
|
|
98
147
|
[1.0 stability policy](https://eve.dev/docs/reference/typescript-api#stability-policy)
|
|
99
148
|
does not cover `experimental` fields or internal modules.
|
|
100
149
|
|
|
101
|
-
From
|
|
102
|
-
[
|
|
150
|
+
From rc.1, review Eve's
|
|
151
|
+
[rc.2](https://github.com/vercel/eve/releases/tag/eve%401.0.0-rc.2),
|
|
152
|
+
[rc.3](https://github.com/vercel/eve/releases/tag/eve%401.0.0-rc.3) and
|
|
153
|
+
[rc.4](https://github.com/vercel/eve/releases/tag/eve%401.0.0-rc.4) release notes.
|
|
154
|
+
These change app-owned code and deployments the helper does not manage:
|
|
155
|
+
|
|
156
|
+
- **Rollback.** Session checkpoints move to version 19 (rc.3). A deployment from
|
|
157
|
+
before rc.3 refuses to take over a session rc.3 or later wrote, so that session
|
|
158
|
+
stays on the deployment that wrote it. New runs also use Workflow spec 9; set
|
|
159
|
+
`WORKFLOW_SPEC_VERSION=8` to keep creating spec 8 runs.
|
|
160
|
+
- **File tools.** Every agent gets a default `edit_file` tool (rc.4) that changes
|
|
161
|
+
sandbox files without `write_file`. If you disable, wrap or gate `write_file`,
|
|
162
|
+
do the same for `edit_file`. Connecta's own tools are unaffected.
|
|
163
|
+
- **Builds.** Eve moves to a Nitro 3 release candidate and declares Rolldown
|
|
164
|
+
itself (rc.3). Importing `nitro/storage`, `nitro/kv` or `nitro/database` from
|
|
165
|
+
agent code now fails the build, as does an asset import that leaves the authored
|
|
166
|
+
package.
|
|
167
|
+
- **Code extension.** `code__grep`, `code__worker` and `code__apply_patch` are gone
|
|
168
|
+
(rc.3, rc.4). Follow Eve's notes if your app mounts `eve/extensions/code`.
|
|
169
|
+
- **Dynamic tools.** A dynamic tool no longer replaces a static tool of the same
|
|
170
|
+
name (rc.3). Dynamic connections, including this helper's, still override a
|
|
171
|
+
static connection of the same name.
|
|
172
|
+
|
|
173
|
+
From 0.90, also review the
|
|
174
|
+
[rc.1 release notes](https://github.com/vercel/eve/blob/eve%401.0.0-rc.1/packages/eve/CHANGELOG.md):
|
|
103
175
|
`session.clear()` now cancels running turns and tasks, and object-form connection
|
|
104
176
|
approvals are now preserved at runtime. RC.1 also requires `@vercel/connect`
|
|
105
177
|
`^2.7.0` when that optional integration is installed; older Connect releases
|
|
@@ -143,12 +215,38 @@ any tool whose `readOnlyHint` is not `true`, including writable `execute_code`.
|
|
|
143
215
|
`approval`, either a policy or an approval configuration, takes precedence over
|
|
144
216
|
`gateWrites`.
|
|
145
217
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
218
|
+
An agent-wide `approval` in Eve's `agent.ts` runs beside the connection's policy
|
|
219
|
+
and the stricter result wins. It can add a gate to Connecta tools, including under
|
|
220
|
+
the default `never()`, and cannot remove one that `gateWrites` set.
|
|
221
|
+
|
|
222
|
+
The gate uses the host's cached annotations. If a native tool becomes a write, its cached read name is refused before dispatch. Refresh `tools/list` and use the write's name; approval follows the host's policy. Read-only pools refuse writes.
|
|
149
223
|
|
|
150
224
|
## Endpoint instructions
|
|
151
225
|
|
|
226
|
+
Eve hides MCP tools behind `eve__tool` under a `<connection>__` prefix, and
|
|
227
|
+
`eve__search` covers listed tools only. Connecta's text is host-neutral, so the
|
|
228
|
+
helper places an Eve guide before it, built from the endpoint's
|
|
229
|
+
[facts](https://github.com/zackbart/connecta/blob/main/documentation/meta-tools.md#endpoint-facts):
|
|
230
|
+
|
|
231
|
+
- **Meta pools:** call `eve__tool({ name: "connecta__execute_code", input })`
|
|
232
|
+
directly, with a short example program. `eve__search` sees the routing tools and
|
|
233
|
+
never a downstream operation, so its empty result does not mean a capability is
|
|
234
|
+
missing. Reachable connectors are listed by id.
|
|
235
|
+
- **Native pools:** a read is `connecta__<connector>__<tool>` and a write
|
|
236
|
+
`connecta__<connector>__WRITE__<tool>`. Scope a search with
|
|
237
|
+
`"connecta__<connector>__"`, use `execute_code` for several, dependent or reduced
|
|
238
|
+
calls (omitted when the pool has none), and when tools are missing get a link from
|
|
239
|
+
`connecta__authorize_connector` for the user or operator. Eve lists tools again on
|
|
240
|
+
each step, so the repair needs no reconnect, unlike a host that keeps its first
|
|
241
|
+
list.
|
|
242
|
+
- **Older servers without facts:** only the naming rule.
|
|
243
|
+
|
|
244
|
+
The guide adds about 600 bytes with three connectors. Its roster stays under 512
|
|
245
|
+
bytes: titles go first, then whole entries give way to a `+N more` count. Names
|
|
246
|
+
in Connecta's text and in `nextAction` stay unprefixed, and the guide says so.
|
|
247
|
+
The helper warns once per endpoint when a server named `connecta` is on a
|
|
248
|
+
different major or minor release from the helper.
|
|
249
|
+
|
|
152
250
|
Instructions use `server/discover` (MCP 2026-07-28), falling back to legacy
|
|
153
251
|
`initialize` when discovery returns a response without guidance, including a
|
|
154
252
|
non-OK status or an unsupported-method response. Network errors and timeouts
|
|
@@ -203,7 +301,7 @@ custom audience, even when Vercel signed them.
|
|
|
203
301
|
|
|
204
302
|
In a source checkout with dependencies installed and both packages built, run
|
|
205
303
|
`pnpm --filter @zackbart/connecta-eve run probe:catalog --output /absolute/path/outside/checkout`
|
|
206
|
-
on Node 24. This boots real Eve 1.0.0-rc.
|
|
304
|
+
on Node 24. This boots real Eve 1.0.0-rc.4 with a packed, installed helper against
|
|
207
305
|
seeded local catalogs of 40, 100, 250, 500, and 1,000 downstream tools on the
|
|
208
306
|
meta, native, and native-plus-code surfaces. Native listings include three
|
|
209
307
|
Connecta tools beyond the downstream count. A 1,001-tool native catalog must
|
|
@@ -213,8 +311,11 @@ The probe uses Eve's public language-model interface with a deterministic local
|
|
|
213
311
|
transport, without model credentials or paid calls. It measures construction,
|
|
214
312
|
cold and warm discovery, schema and result bytes, call counts, concurrent-call
|
|
215
313
|
isolation, bounded-result paging, and process memory. Fresh child processes
|
|
216
|
-
isolate each matrix cell; Eve startup is separate from turn latency.
|
|
217
|
-
|
|
314
|
+
isolate each matrix cell; Eve startup is separate from turn latency. Each turn
|
|
315
|
+
also checks what the helper's guide claims about Eve: guidance arrives as its own
|
|
316
|
+
system message on every model call, `eve__search` on a meta pool reaches only
|
|
317
|
+
routing tools, native writes are named `connecta__<connector>__WRITE__<tool>`, and
|
|
318
|
+
every step lists tools again. Reports retain actual Eve events and transport inputs. Token usage and model task
|
|
218
319
|
quality remain unmeasured; these results establish local runtime contracts,
|
|
219
320
|
not live Vercel workload admission or production model performance.
|
|
220
321
|
|
|
@@ -224,3 +325,30 @@ checkout. Its preparation command packs and installs the current core/helper,
|
|
|
224
325
|
checks local signed controls and cancellation, and creates no live resources.
|
|
225
326
|
Live execution requires separate owner authorization for the concrete account,
|
|
226
327
|
project, bounds, cost ceiling and cleanup. Keep its evidence outside the checkout.
|
|
328
|
+
|
|
329
|
+
### Deferred connector guide (opt-in)
|
|
330
|
+
|
|
331
|
+
In `agent/skills/crm.ts`:
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
import { connectaSkill } from "@zackbart/connecta-eve";
|
|
335
|
+
|
|
336
|
+
export default connectaSkill({
|
|
337
|
+
pool: "crm",
|
|
338
|
+
name: "connector:crm",
|
|
339
|
+
description: "Use when working with CRM person identifiers and conventions.",
|
|
340
|
+
when: (ctx) => ctx.session.auth !== null,
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
This uses the pinned Eve 1.0.0-rc.4 public `defineSkill` and `defineDynamic`
|
|
345
|
+
APIs. Eve's deferred discovery searches the description; loading adds only
|
|
346
|
+
instructions. It never installs tools. Use the same caller predicate and pool
|
|
347
|
+
selector as the connection. A false or throwing predicate skips selection,
|
|
348
|
+
token resolution and fetching. The public API accepts content rather than a
|
|
349
|
+
load callback, so fetching happens once per endpoint/guide/turn through Eve's
|
|
350
|
+
session memo; inclusion in model context is lazy. Failed responses omit the
|
|
351
|
+
skill, including network, timeout and malformed-response failures; turn
|
|
352
|
+
cancellation still propagates. A successful guide can be reused while the same
|
|
353
|
+
endpoint/guide/turn memo is retained. A new turn fetches again; failure removes
|
|
354
|
+
the skill rather than serving the prior turn's guide. The guide must already be visible through that endpoint's `skills` tool.
|
package/dist/index.d.ts
CHANGED
|
@@ -14,14 +14,31 @@ export interface ConnectaEndpointOptions {
|
|
|
14
14
|
* no restart. Defaults to the `CONNECTA_ACCESS_TOKEN` environment variable.
|
|
15
15
|
*/
|
|
16
16
|
getToken?: () => string | Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Extra request headers, such as a service token for an edge in front of
|
|
19
|
+
* Connecta. A function is resolved on every request. These never replace
|
|
20
|
+
* the helper's bearer or MCP protocol headers on guidance requests.
|
|
21
|
+
*/
|
|
22
|
+
headers?: ConnectaHeaders;
|
|
17
23
|
}
|
|
24
|
+
/** Fixed headers, or a resolver called on every request. */
|
|
25
|
+
export type ConnectaHeaders = Readonly<Record<string, string>> | (() => Readonly<Record<string, string>> | Promise<Readonly<Record<string, string>>>);
|
|
18
26
|
/** App-owned pool policy; use `ctx.memo` when its result should stay fixed. */
|
|
19
27
|
export type ConnectaPoolSelector = (ctx: DynamicContext) => string | Promise<string>;
|
|
28
|
+
/** App-owned caller/turn policy, evaluated before selecting a pool or fetching guidance. */
|
|
29
|
+
export type ConnectaPredicate = (ctx: DynamicContext) => boolean | Promise<boolean>;
|
|
20
30
|
export interface ConnectaConnectionOptions extends Omit<ConnectaEndpointOptions, "pool"> {
|
|
21
31
|
/** A fixed pool, or a policy Eve evaluates before every model call. */
|
|
22
32
|
pool?: string | ConnectaPoolSelector;
|
|
23
|
-
/**
|
|
24
|
-
|
|
33
|
+
/** Omit this value when false or when the policy throws. Evaluated every model call. */
|
|
34
|
+
when?: ConnectaPredicate;
|
|
35
|
+
/**
|
|
36
|
+
* What the connection offers, in Eve's catalog listing and in the words every
|
|
37
|
+
* tool of this connection matches in `eve__search`. Defaults to a fixed
|
|
38
|
+
* neutral line. Describe the domain in prose; do not list connectors or
|
|
39
|
+
* operations here, because a query naming one then matches every tool.
|
|
40
|
+
*/
|
|
41
|
+
description?: string;
|
|
25
42
|
/** Eve's approval for every Connecta tool: a policy or configuration. Defaults to `never()`. */
|
|
26
43
|
approval?: Approval;
|
|
27
44
|
/**
|
|
@@ -34,6 +51,13 @@ export interface ConnectaConnectionOptions extends Omit<ConnectaEndpointOptions,
|
|
|
34
51
|
export interface ConnectaInstructionsOptions extends Omit<ConnectaEndpointOptions, "pool"> {
|
|
35
52
|
/** A fixed pool, or the same policy used by the connection. */
|
|
36
53
|
pool?: string | ConnectaPoolSelector;
|
|
54
|
+
/** Omit this value when false or when the policy throws. Evaluated every model call. */
|
|
55
|
+
when?: ConnectaPredicate;
|
|
56
|
+
/**
|
|
57
|
+
* The Eve connection's name, which is its file under `agent/connections/`.
|
|
58
|
+
* The model calls `<connection>__<tool>`. Default `connecta`.
|
|
59
|
+
*/
|
|
60
|
+
connection?: string;
|
|
37
61
|
/** MCP client name for instruction requests. Default `connecta-eve-instructions`. */
|
|
38
62
|
clientName?: string;
|
|
39
63
|
/** How long fetched instructions are reused. Default ten minutes. */
|
|
@@ -52,11 +76,15 @@ export declare const gateConnectaWrites: ApprovalPolicy;
|
|
|
52
76
|
* The Eve connection for a Connecta agent endpoint, with app-owned auth:
|
|
53
77
|
* one agent token, sent as a bearer and re-read on every request.
|
|
54
78
|
*/
|
|
79
|
+
export declare function connectaConnection(options: ConnectaConnectionOptions & {
|
|
80
|
+
when: ConnectaPredicate;
|
|
81
|
+
}): ReturnType<typeof defineDynamicConnection>;
|
|
55
82
|
export declare function connectaConnection(options: ConnectaConnectionOptions & {
|
|
56
83
|
pool: ConnectaPoolSelector;
|
|
57
84
|
}): ReturnType<typeof defineDynamicConnection>;
|
|
58
85
|
export declare function connectaConnection(options: ConnectaConnectionOptions & {
|
|
59
86
|
pool?: string;
|
|
87
|
+
when?: undefined;
|
|
60
88
|
}): McpClientConnectionDefinition;
|
|
61
89
|
export declare function connectaConnection(options: ConnectaConnectionOptions): McpClientConnectionDefinition | ReturnType<typeof defineDynamicConnection>;
|
|
62
90
|
/**
|
|
@@ -67,4 +95,18 @@ export declare function connectaConnection(options: ConnectaConnectionOptions):
|
|
|
67
95
|
* model call; a memo keyed on the endpoint and turn keeps one result for each.
|
|
68
96
|
* Pool policy runs independently of the connection and owns its memo lifetime.
|
|
69
97
|
*/
|
|
70
|
-
export declare function connectaInstructions(options?: ConnectaInstructionsOptions): import("eve/
|
|
98
|
+
export declare function connectaInstructions(options?: ConnectaInstructionsOptions): import("eve/skills").DynamicSentinel<import("eve/instructions").DynamicInstructionsResult, DynamicContext>;
|
|
99
|
+
/** One explicitly selected Connecta guide projected as a deferred Eve skill. */
|
|
100
|
+
export interface ConnectaSkillOptions extends Omit<ConnectaInstructionsOptions, "connection"> {
|
|
101
|
+
/** Exact skills-tool name, for example `connector:crm`. */
|
|
102
|
+
name: string;
|
|
103
|
+
/** Short task selector Eve can search without loading the guide. */
|
|
104
|
+
description: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Opt-in deferred instructions, installed from an agent/skills module.
|
|
108
|
+
* Resolves only after caller policy, with no tools added by loading the skill.
|
|
109
|
+
* Eve's public API takes content, not a load callback: fetching is per turn;
|
|
110
|
+
* only model-context inclusion is lazy.
|
|
111
|
+
*/
|
|
112
|
+
export declare function connectaSkill(options: ConnectaSkillOptions): import("eve/skills").DynamicSentinel<import("eve/skills").DynamicSkillResult, DynamicContext>;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { defineDynamic as defineDynamicSkill, defineSkill } from "eve/skills";
|
|
1
2
|
import { defineDynamic as defineDynamicConnection, defineMcpClientConnection, } from "eve/connections";
|
|
2
3
|
import { defineDynamic, defineInstructions } from "eve/instructions";
|
|
3
4
|
import { never } from "eve/tools/approval";
|
|
5
|
+
import { CONNECTA_VERSION } from "./version.js";
|
|
4
6
|
function environment(name) {
|
|
5
7
|
const value = globalThis.process?.env?.[name];
|
|
6
8
|
return value?.trim() || undefined;
|
|
@@ -32,21 +34,43 @@ async function tokenFor(options) {
|
|
|
32
34
|
*/
|
|
33
35
|
export const gateConnectaWrites = ({ toolAnnotations }) => toolAnnotations?.readOnlyHint === true ? "not-applicable" : "user-approval";
|
|
34
36
|
export function connectaConnection(options) {
|
|
37
|
+
warnOnGenericDescription(options.description);
|
|
35
38
|
const { pool, ...rest } = options;
|
|
36
|
-
if (typeof pool === "function") {
|
|
39
|
+
if (typeof pool === "function" || options.when) {
|
|
37
40
|
return defineDynamicConnection(async (ctx) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
if (!(await enabled(options.when, ctx)))
|
|
42
|
+
return null;
|
|
43
|
+
// Only a static `pool` may be omitted; connectaEndpoint validates it. A
|
|
44
|
+
// selector must name a valid pool, or the turn fails instead of using the base URL.
|
|
45
|
+
let selected = pool;
|
|
46
|
+
if (typeof pool === "function") {
|
|
47
|
+
selected = await pool(ctx);
|
|
48
|
+
validatePool(selected);
|
|
49
|
+
}
|
|
50
|
+
return connectionDefinition(options, connectaEndpoint({ ...rest, ...(selected === undefined ? {} : { pool: selected }) }), selected);
|
|
41
51
|
});
|
|
42
52
|
}
|
|
43
53
|
return connectionDefinition(options, connectaEndpoint(pool === undefined ? rest : { ...rest, pool }));
|
|
44
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Eve matches these words against every search query, and a short word also
|
|
57
|
+
* matches longer query words ("use" hits "users"), so this avoids common stems.
|
|
58
|
+
*/
|
|
59
|
+
const DEFAULT_DESCRIPTION = "Connecta integration gateway.";
|
|
60
|
+
/** "Company tools through Connecta" and other strings any Connecta connection could claim. */
|
|
61
|
+
const GENERIC_DESCRIPTION = /\b(?:tools?|services?|integrations?|apps?)\s+(?:through|via|from|using|on|in|with)\s+connecta\b|^\W*(?:(?:the|our|all|my|company|connecta|mcp|tools?|services?|integrations?|connections?|gateway|server|access|through|via|using)\W*)*$/i;
|
|
62
|
+
function warnOnGenericDescription(description) {
|
|
63
|
+
if (description === undefined || !GENERIC_DESCRIPTION.test(description))
|
|
64
|
+
return;
|
|
65
|
+
console.warn(`[connecta-eve] Connection description ${JSON.stringify(description)} gives the model no domain information. Omit it, or say what this agent reaches in prose without listing connectors.`);
|
|
66
|
+
}
|
|
45
67
|
function connectionDefinition(options, endpoint, instanceKey) {
|
|
68
|
+
const { headers } = options;
|
|
46
69
|
return defineMcpClientConnection({
|
|
47
70
|
url: endpoint,
|
|
48
71
|
...(instanceKey === undefined ? {} : { instanceKey }),
|
|
49
|
-
description: options.description,
|
|
72
|
+
description: options.description ?? DEFAULT_DESCRIPTION,
|
|
73
|
+
...(headers === undefined ? {} : { headers: typeof headers === "function" ? () => headers() : headers }),
|
|
50
74
|
auth: {
|
|
51
75
|
principalType: "app",
|
|
52
76
|
getToken: async () => ({ token: await tokenFor(options) }),
|
|
@@ -54,18 +78,116 @@ function connectionDefinition(options, endpoint, instanceKey) {
|
|
|
54
78
|
approval: options.approval ?? (options.gateWrites ? gateConnectaWrites : never()),
|
|
55
79
|
});
|
|
56
80
|
}
|
|
57
|
-
|
|
81
|
+
async function enabled(when, ctx) {
|
|
82
|
+
try {
|
|
83
|
+
return when ? (await when(ctx)) === true : true;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Older servers omit the extension; anything malformed reads the same way. */
|
|
90
|
+
function endpointFacts(value) {
|
|
91
|
+
const facts = value;
|
|
92
|
+
if (facts?.surface !== "meta" && facts?.surface !== "native")
|
|
93
|
+
return undefined;
|
|
94
|
+
if (typeof facts.executeCode !== "boolean" || !Array.isArray(facts.connectors))
|
|
95
|
+
return undefined;
|
|
96
|
+
const text = (field) => (typeof field === "string" ? field.replace(/\s+/g, " ").trim() : "");
|
|
97
|
+
return {
|
|
98
|
+
surface: facts.surface,
|
|
99
|
+
executeCode: facts.executeCode,
|
|
100
|
+
readOnly: facts.readOnly === true,
|
|
101
|
+
connectors: facts.connectors.flatMap((connector) => {
|
|
102
|
+
const id = text(connector?.id);
|
|
103
|
+
if (!id)
|
|
104
|
+
return [];
|
|
105
|
+
const title = text(connector.title);
|
|
106
|
+
const readFirst = text(connector.readFirst);
|
|
107
|
+
return [{ id, ...(title ? { title } : {}), ...(readFirst ? { readFirst } : {}) }];
|
|
108
|
+
}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const ROSTER_MAX_BYTES = 512;
|
|
112
|
+
/** Detail degrades before names do, as the server's own inventory does. */
|
|
113
|
+
function connectorRoster(facts, prefix) {
|
|
114
|
+
const fits = (line) => new TextEncoder().encode(line).length <= ROSTER_MAX_BYTES;
|
|
115
|
+
const entry = ({ id, title, readFirst }, withTitle) => {
|
|
116
|
+
const notes = [
|
|
117
|
+
...(withTitle && title ? [title] : []),
|
|
118
|
+
// A native tool's description already carries its guide pointer.
|
|
119
|
+
...(readFirst && facts.surface === "meta" ? [`read first: ${prefix}skills { name: "${readFirst}" }`] : []),
|
|
120
|
+
];
|
|
121
|
+
return notes.length ? `${id} (${notes.join("; ")})` : id;
|
|
122
|
+
};
|
|
123
|
+
for (const withTitle of [true, false]) {
|
|
124
|
+
const line = facts.connectors.map((connector) => entry(connector, withTitle)).join(", ");
|
|
125
|
+
if (fits(line))
|
|
126
|
+
return line;
|
|
127
|
+
}
|
|
128
|
+
const entries = facts.connectors.map((connector) => entry(connector, false));
|
|
129
|
+
let shown = 0;
|
|
130
|
+
while (fits(`${entries.slice(0, shown + 1).join(", ")}; +${entries.length - shown - 1} more`))
|
|
131
|
+
shown++;
|
|
132
|
+
return `${entries.slice(0, shown).join(", ")}${shown ? "; " : ""}+${entries.length - shown} more`;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* How Eve's deferred catalog reaches this endpoint's tools. Connecta's own text
|
|
136
|
+
* names tools as MCP lists them; Eve adds the connection prefix and hides them
|
|
137
|
+
* behind `eve__tool`, and its search covers listed tools only.
|
|
138
|
+
*/
|
|
139
|
+
function eveGuide(connection, facts) {
|
|
140
|
+
const prefix = `${connection}__`;
|
|
141
|
+
if (!facts) {
|
|
142
|
+
return `Connecta tools are deferred: call eve__tool({ name: "${prefix}<tool>", input }). Names in this guidance and in nextAction omit the "${prefix}" prefix.`;
|
|
143
|
+
}
|
|
144
|
+
const roster = facts.connectors.length ? connectorRoster(facts, prefix) : undefined;
|
|
145
|
+
if (facts.surface === "meta") {
|
|
146
|
+
return `Connecta tools are deferred: call eve__tool({ name: "${prefix}<tool>", input }). Names in this guidance and in nextAction omit the "${prefix}" prefix. Go straight to ${prefix}execute_code without eve__search, input { code: "async () => { const { tools } = await connecta.search({ query: 'list invoices', safety: 'readOnly' }); const r = await connecta.call(tools[0].address, {}); return r.data; }" }. eve__search sees only Connecta's routing tools, never downstream operations, so empty results do not mean a capability is missing; search inside the program.${roster ? ` Reachable: ${roster}.` : ""}`;
|
|
147
|
+
}
|
|
148
|
+
return `Connecta tools are deferred: call eve__tool({ name: "${prefix}<connector>__<tool>", input })${facts.readOnly ? "" : `, with writes named "${prefix}<connector>__WRITE__<tool>"`}. Find them with eve__search; scope one connector with "${prefix}<connector>__", not "<connector>__". Names in this guidance and in nextAction omit "${prefix}".${facts.executeCode ? ` For several, dependent or reduced calls, go straight to ${prefix}execute_code with { code: "async () => { ... }" }, using each tool's Program address.` : ""} Tools missing: ${prefix}authorize_connector returns a link; give it to the user or operator once and stop. Eve lists tools again every step, so a repair needs no reconnect.${roster ? ` Connectors: ${roster}.` : ""}`;
|
|
149
|
+
}
|
|
150
|
+
/** Release lines a helper and server must share; a patch difference is compatible. */
|
|
151
|
+
const releaseLine = (version) => typeof version === "string" ? /^(\d+\.\d+)\./.exec(version)?.[1] : undefined;
|
|
152
|
+
/** An endpoint may embed credentials or a token in its URL, path included; logs get only its origin. */
|
|
153
|
+
function loggableEndpoint(endpoint) {
|
|
154
|
+
try {
|
|
155
|
+
return new URL(endpoint).origin;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return "the configured endpoint";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** A deployment that renamed its server may version it independently, so only `connecta` is compared. */
|
|
162
|
+
function warnOnVersionSkew(endpoint, server) {
|
|
163
|
+
if (server?.name !== "connecta")
|
|
164
|
+
return;
|
|
165
|
+
const line = releaseLine(server.version);
|
|
166
|
+
if (line === undefined || line === releaseLine(CONNECTA_VERSION))
|
|
167
|
+
return;
|
|
168
|
+
// The version is remote text, so only its validated numeric line reaches logs.
|
|
169
|
+
console.warn(`[connecta-eve] Helper ${CONNECTA_VERSION} and Connecta ${line}.x at ${loggableEndpoint(endpoint)} are different releases. Install the helper matching your server.`);
|
|
170
|
+
}
|
|
171
|
+
/** Headers for a guidance request. The caller's extras can never replace what the request itself needs. */
|
|
172
|
+
async function guidanceHeaders(options, own) {
|
|
173
|
+
const extra = typeof options.headers === "function" ? await options.headers() : options.headers;
|
|
174
|
+
const headers = new Headers(extra);
|
|
175
|
+
for (const [name, value] of Object.entries(own))
|
|
176
|
+
headers.set(name, value);
|
|
177
|
+
return Object.fromEntries(headers);
|
|
178
|
+
}
|
|
179
|
+
/** Connecta's MCP `instructions` and endpoint facts for one endpoint, or null when unavailable. */
|
|
58
180
|
async function instructionRequest(options, endpoint, method, signal) {
|
|
59
181
|
const modern = method === "server/discover";
|
|
60
182
|
const clientInfo = { name: options.clientName ?? "connecta-eve-instructions", version: "1" };
|
|
61
183
|
const response = await (options.fetch ?? fetch)(endpoint, {
|
|
62
184
|
method: "POST",
|
|
63
|
-
headers: {
|
|
185
|
+
headers: await guidanceHeaders(options, {
|
|
64
186
|
Authorization: `Bearer ${await tokenFor(options)}`,
|
|
65
187
|
"Content-Type": "application/json",
|
|
66
188
|
Accept: "application/json, text/event-stream",
|
|
67
189
|
...(modern ? { "MCP-Protocol-Version": "2026-07-28", "Mcp-Method": method } : {}),
|
|
68
|
-
},
|
|
190
|
+
}),
|
|
69
191
|
body: JSON.stringify({
|
|
70
192
|
jsonrpc: "2.0",
|
|
71
193
|
id: 1,
|
|
@@ -98,8 +220,15 @@ async function instructionRequest(options, endpoint, method, signal) {
|
|
|
98
220
|
?.slice("data:".length);
|
|
99
221
|
if (!json)
|
|
100
222
|
return null;
|
|
101
|
-
const
|
|
102
|
-
|
|
223
|
+
const { result } = JSON.parse(json);
|
|
224
|
+
const instructions = result?.instructions;
|
|
225
|
+
if (typeof instructions !== "string" || !instructions.trim())
|
|
226
|
+
return null;
|
|
227
|
+
return {
|
|
228
|
+
instructions: instructions.trim(),
|
|
229
|
+
facts: endpointFacts(result?.capabilities?.extensions?.["dev.connecta/endpoint"]),
|
|
230
|
+
server: result?._meta?.["io.modelcontextprotocol/serverInfo"] ?? result?.serverInfo,
|
|
231
|
+
};
|
|
103
232
|
}
|
|
104
233
|
async function fetchConnectaInstructions(options, endpoint, signal) {
|
|
105
234
|
// Only a completed discovery response can justify legacy negotiation.
|
|
@@ -117,6 +246,10 @@ async function fetchConnectaInstructions(options, endpoint, signal) {
|
|
|
117
246
|
*/
|
|
118
247
|
export function connectaInstructions(options = {}) {
|
|
119
248
|
const ttlMs = options.ttlMs ?? 600_000;
|
|
249
|
+
const connection = options.connection ?? "connecta";
|
|
250
|
+
if (!/^[A-Za-z0-9._-]+$/.test(connection)) {
|
|
251
|
+
throw new Error(`Connecta connection "${connection}" must be the file name under agent/connections.`);
|
|
252
|
+
}
|
|
120
253
|
const endpoints = new Map();
|
|
121
254
|
const current = async (endpoint, signal) => {
|
|
122
255
|
signal.throwIfAborted();
|
|
@@ -130,7 +263,13 @@ export function connectaInstructions(options = {}) {
|
|
|
130
263
|
return cached?.content ?? null;
|
|
131
264
|
let fresh;
|
|
132
265
|
try {
|
|
133
|
-
|
|
266
|
+
const guidance = await fetchConnectaInstructions(options, endpoint, signal);
|
|
267
|
+
if (guidance && !state.versionChecked) {
|
|
268
|
+
state.versionChecked = true;
|
|
269
|
+
warnOnVersionSkew(endpoint, guidance.server);
|
|
270
|
+
}
|
|
271
|
+
// Eve's half comes first: it says how to read every name in Connecta's own text.
|
|
272
|
+
fresh = guidance && `${eveGuide(connection, guidance.facts)}\n${guidance.instructions}`;
|
|
134
273
|
}
|
|
135
274
|
catch {
|
|
136
275
|
// Eve's cancellation is not an endpoint failure: rethrowing keeps it out
|
|
@@ -144,6 +283,8 @@ export function connectaInstructions(options = {}) {
|
|
|
144
283
|
return fresh ?? cached?.content ?? null;
|
|
145
284
|
};
|
|
146
285
|
return defineDynamic(async (ctx) => {
|
|
286
|
+
if (!(await enabled(options.when, ctx)))
|
|
287
|
+
return null;
|
|
147
288
|
const { pool, ...rest } = options;
|
|
148
289
|
let endpoint;
|
|
149
290
|
if (typeof pool === "function") {
|
|
@@ -164,3 +305,75 @@ export function connectaInstructions(options = {}) {
|
|
|
164
305
|
return content ? defineInstructions({ content: `Connecta guidance:\n${content}` }) : null;
|
|
165
306
|
});
|
|
166
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Opt-in deferred instructions, installed from an agent/skills module.
|
|
310
|
+
* Resolves only after caller policy, with no tools added by loading the skill.
|
|
311
|
+
* Eve's public API takes content, not a load callback: fetching is per turn;
|
|
312
|
+
* only model-context inclusion is lazy.
|
|
313
|
+
*/
|
|
314
|
+
export function connectaSkill(options) {
|
|
315
|
+
if (!options.name.trim() || !options.description.trim())
|
|
316
|
+
throw new Error("A skill needs a name and description.");
|
|
317
|
+
return defineDynamicSkill(async (ctx) => {
|
|
318
|
+
if (!(await enabled(options.when, ctx)))
|
|
319
|
+
return null;
|
|
320
|
+
const { pool, ...endpointOptions } = options;
|
|
321
|
+
const selected = typeof pool === "function" ? await pool(ctx) : pool;
|
|
322
|
+
if (typeof options.pool === "function")
|
|
323
|
+
validatePool(selected);
|
|
324
|
+
const endpoint = connectaEndpoint({ ...endpointOptions, ...(selected === undefined ? {} : { pool: selected }) });
|
|
325
|
+
const content = await ctx.memo(["connecta-skill", endpoint, options.name, ctx.turn?.id ?? null], async ({ abortSignal }) => {
|
|
326
|
+
try {
|
|
327
|
+
const response = await (options.fetch ?? fetch)(endpoint, {
|
|
328
|
+
method: "POST",
|
|
329
|
+
headers: await guidanceHeaders(options, {
|
|
330
|
+
Authorization: `Bearer ${await tokenFor(options)}`,
|
|
331
|
+
"Content-Type": "application/json",
|
|
332
|
+
Accept: "application/json, text/event-stream",
|
|
333
|
+
"MCP-Protocol-Version": "2026-07-28",
|
|
334
|
+
"Mcp-Method": "tools/call",
|
|
335
|
+
"Mcp-Name": "skills",
|
|
336
|
+
}),
|
|
337
|
+
body: JSON.stringify({
|
|
338
|
+
jsonrpc: "2.0",
|
|
339
|
+
id: 1,
|
|
340
|
+
method: "tools/call",
|
|
341
|
+
params: {
|
|
342
|
+
name: "skills",
|
|
343
|
+
arguments: { name: options.name },
|
|
344
|
+
_meta: {
|
|
345
|
+
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
|
|
346
|
+
"io.modelcontextprotocol/clientCapabilities": {},
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
}),
|
|
350
|
+
signal: AbortSignal.any([abortSignal, AbortSignal.timeout(5_000)]),
|
|
351
|
+
});
|
|
352
|
+
if (!response.ok)
|
|
353
|
+
return null;
|
|
354
|
+
const text = await response.text();
|
|
355
|
+
const json = text.trimStart().startsWith("{")
|
|
356
|
+
? text
|
|
357
|
+
: text
|
|
358
|
+
.split("\n")
|
|
359
|
+
.find((line) => line.startsWith("data:"))
|
|
360
|
+
?.slice(5);
|
|
361
|
+
if (!json)
|
|
362
|
+
return null;
|
|
363
|
+
const reply = JSON.parse(json);
|
|
364
|
+
if (!reply.result || reply.result.isError)
|
|
365
|
+
return null;
|
|
366
|
+
return (reply.result.content
|
|
367
|
+
?.filter((block) => block.type === "text")
|
|
368
|
+
.map((block) => block.text ?? "")
|
|
369
|
+
.join("\n") || null);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
// Endpoint failures omit optional guidance; turn cancellation still propagates.
|
|
373
|
+
abortSignal.throwIfAborted();
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
return content ? defineSkill({ description: options.description, content, deferred: true }) : null;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CONNECTA_VERSION = "0.42.0";
|
package/dist/version.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zackbart/connecta-eve",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Eve connection and instructions helpers for Connecta agent endpoints.",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"eve": "1.0.0-rc.
|
|
31
|
+
"eve": "1.0.0-rc.4"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^24.19.2",
|
|
35
|
-
"eve": "1.0.0-rc.
|
|
35
|
+
"eve": "1.0.0-rc.4",
|
|
36
36
|
"typescript": "^6.0.3",
|
|
37
37
|
"vite": "^8.3.4",
|
|
38
38
|
"vitest": "^5.0.4"
|