@fungi.computer/veil 0.3.0 → 0.5.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.
Files changed (43) hide show
  1. package/dist/connections/cleanup.d.ts +21 -0
  2. package/dist/connections/cleanup.js +66 -0
  3. package/dist/connections/contract.d.ts +287 -0
  4. package/dist/connections/contract.js +95 -0
  5. package/dist/connections/core.d.ts +98 -0
  6. package/dist/connections/core.js +631 -0
  7. package/dist/connections/discover.d.ts +3 -0
  8. package/dist/connections/discover.js +23 -0
  9. package/dist/connections/egress.d.ts +8 -0
  10. package/dist/connections/egress.js +78 -0
  11. package/dist/connections/index.d.ts +4 -0
  12. package/dist/connections/index.js +4 -0
  13. package/dist/connections/internal.d.ts +78 -0
  14. package/dist/connections/internal.js +66 -0
  15. package/dist/connections/mcp-agent.d.ts +37 -0
  16. package/dist/connections/mcp-agent.js +75 -0
  17. package/dist/connections/nango.d.ts +30 -0
  18. package/dist/connections/nango.js +136 -0
  19. package/dist/connections/oauth-flow.d.ts +81 -0
  20. package/dist/connections/oauth-flow.js +322 -0
  21. package/dist/connections/oauth-provider.d.ts +31 -0
  22. package/dist/connections/oauth-provider.js +96 -0
  23. package/dist/connections/oauth.d.ts +93 -0
  24. package/dist/connections/oauth.js +239 -0
  25. package/dist/connections/openapi.d.ts +36 -0
  26. package/dist/connections/openapi.js +120 -0
  27. package/dist/connections/sealed.d.ts +21 -0
  28. package/dist/connections/sealed.js +73 -0
  29. package/dist/connections/source.d.ts +17 -0
  30. package/dist/connections/source.js +111 -0
  31. package/dist/connections/update.d.ts +41 -0
  32. package/dist/connections/update.js +81 -0
  33. package/dist/credential-store.d.ts +64 -0
  34. package/dist/credential-store.js +151 -0
  35. package/dist/gatekeeper.d.ts +21 -1
  36. package/dist/gatekeeper.js +74 -12
  37. package/dist/index.d.ts +1 -1
  38. package/dist/model-keys.d.ts +24 -0
  39. package/dist/nango.d.ts +41 -0
  40. package/dist/nango.js +6 -6
  41. package/dist/openapi-secrets.d.ts +1 -1
  42. package/dist/openapi-secrets.js +19 -58
  43. package/package.json +22 -1
@@ -0,0 +1,21 @@
1
+ import * as Effect from "effect/Effect";
2
+ import type * as Schema from "effect/Schema";
3
+ import type { ConnectionsOptions } from "./contract.js";
4
+ import { type AttemptRow, type ConnectionRow } from "./internal.js";
5
+ /** The cleanup outbox, attempt retirement and reconnect marking over the owner's SQLite. */
6
+ export declare function createCleanup(deps: {
7
+ ownerId: string;
8
+ custody: ConnectionsOptions["custody"];
9
+ clock: () => number;
10
+ sql: ConnectionsOptions["store"]["sql"];
11
+ transactionSync: <A>(body: () => A) => A;
12
+ rows: <S extends Schema.Decoder<unknown>>(schema: S, statement: string, ...bindings: (string | number | null)[]) => S["Type"][];
13
+ bump: () => void;
14
+ }): {
15
+ cleanupPending: () => number;
16
+ sweep: () => Effect.Effect<void, import("./contract.js").ConnectionsError, never>;
17
+ enqueueLocator: (connectionId: string, locator: string, credential: ConnectionRow["credential"]) => void;
18
+ enqueueCleanup: (row: ConnectionRow) => void;
19
+ markReconnect: (id: string) => void;
20
+ retireAttempt: (attempt: AttemptRow, status: string) => void;
21
+ };
@@ -0,0 +1,66 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { attemptRow, cleanupRow, connectionRow, count, sync, } from "./internal.js";
3
+ const cleanupLimit = 5;
4
+ /** The cleanup outbox, attempt retirement and reconnect marking over the owner's SQLite. */
5
+ export function createCleanup(deps) {
6
+ const { sql, rows, bump, transactionSync } = deps;
7
+ /** Sealed credentials still waiting to be deleted; hosts wake their alarm while this is positive. */
8
+ const cleanupPending = () => rows(count, "SELECT COUNT(*) AS n FROM veil_connection_cleanup WHERE attempts < ?", cleanupLimit)[0]?.n ?? 0;
9
+ const sweep = () => Effect.gen(function* () {
10
+ yield* sync(expireAttempts);
11
+ const due = yield* sync(() => rows(cleanupRow, "SELECT * FROM veil_connection_cleanup WHERE attempts < ?", cleanupLimit));
12
+ for (const job of due) {
13
+ const outcome = yield* deps.custody
14
+ .remove({
15
+ ownerId: deps.ownerId,
16
+ connectionId: job.connectionId,
17
+ credential: job.credential,
18
+ }, job.locator)
19
+ .pipe(Effect.result);
20
+ yield* sync(() => {
21
+ if (outcome._tag === "Success")
22
+ sql.exec("DELETE FROM veil_connection_cleanup WHERE id = ?", job.id);
23
+ else
24
+ sql.exec("UPDATE veil_connection_cleanup SET attempts = attempts + 1 WHERE id = ?", job.id);
25
+ });
26
+ }
27
+ });
28
+ const enqueueLocator = (connectionId, locator, credential) => {
29
+ sql.exec("INSERT INTO veil_connection_cleanup (connectionId, locator, credential, attempts) VALUES (?, ?, ?, 0)", connectionId, locator, JSON.stringify(credential));
30
+ };
31
+ const enqueueCleanup = (row) => {
32
+ if (row.locator !== null)
33
+ enqueueLocator(row.id, row.locator, row.credential);
34
+ };
35
+ const markReconnect = (id) => {
36
+ sql.exec("UPDATE veil_connections SET state = 'reconnect_required', authorityVersion = authorityVersion + 1 WHERE id = ? AND state = 'active'", id);
37
+ bump();
38
+ };
39
+ /** An attempt ends: its sealed verifier goes to cleanup and a still-pending connection is withdrawn. */
40
+ const retireAttempt = (attempt, status) => {
41
+ sql.exec("UPDATE veil_connection_attempts SET status = ?, pkceVerifierSealed = NULL WHERE state = ?", status, attempt.state);
42
+ const row = rows(connectionRow, "SELECT * FROM veil_connections WHERE id = ?", attempt.connectionId)[0];
43
+ if (row === undefined)
44
+ return;
45
+ if (attempt.pkceVerifierSealed !== null)
46
+ enqueueLocator(row.id, attempt.pkceVerifierSealed, row.credential);
47
+ if (row.state === "pending") {
48
+ sql.exec("UPDATE veil_connections SET state = 'revoked', authorityVersion = authorityVersion + 1 WHERE id = ?", row.id);
49
+ bump();
50
+ }
51
+ };
52
+ const expireAttempts = () => {
53
+ transactionSync(() => {
54
+ for (const attempt of rows(attemptRow, "SELECT * FROM veil_connection_attempts WHERE status IN ('pending', 'consumed') AND expiresAt <= ?", deps.clock()))
55
+ retireAttempt(attempt, "expired");
56
+ });
57
+ };
58
+ return {
59
+ cleanupPending,
60
+ sweep,
61
+ enqueueLocator,
62
+ enqueueCleanup,
63
+ markReconnect,
64
+ retireAttempt,
65
+ };
66
+ }
@@ -0,0 +1,287 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ import type { PublicEgressPolicy } from "../public-egress-policy.js";
4
+ export type ConnectionsRefusal = "invalid_input" | "denied" | "revoked" | "conflict" | "unsupported" | "too_large" | "reconnect_required" | "pending" | "invalid_response" | "unavailable";
5
+ /** Fixed codes only: upstream bodies, URLs and credential bytes never enter errors. */
6
+ export declare class ConnectionsError extends Error {
7
+ readonly code: ConnectionsRefusal;
8
+ readonly name = "ConnectionsError";
9
+ constructor(code: ConnectionsRefusal, options?: ErrorOptions);
10
+ }
11
+ export declare const identifier: Schema.String;
12
+ declare const placementSchema: Schema.Union<readonly [Schema.Literal<"bearer">, Schema.Struct<{
13
+ readonly header: Schema.String;
14
+ }>, Schema.Struct<{
15
+ readonly query: Schema.String;
16
+ }>]>;
17
+ export type Placement = typeof placementSchema.Type;
18
+ /**
19
+ * How the final request is authorized. For `oauth` the owner may bring its own
20
+ * client (`clientId`, with the client secret as the draft's `secret`); without
21
+ * one the server's dynamic registration is used.
22
+ */
23
+ export declare const credentialSchema: Schema.Union<readonly [Schema.Struct<{
24
+ readonly kind: Schema.Literal<"none">;
25
+ }>, Schema.Struct<{
26
+ readonly kind: Schema.Literal<"static">;
27
+ readonly placement: Schema.Union<readonly [Schema.Literal<"bearer">, Schema.Struct<{
28
+ readonly header: Schema.String;
29
+ }>, Schema.Struct<{
30
+ readonly query: Schema.String;
31
+ }>]>;
32
+ }>, Schema.Struct<{
33
+ readonly kind: Schema.Literal<"oauth">;
34
+ readonly scope: Schema.optional<Schema.String>;
35
+ readonly clientId: Schema.optional<Schema.String>;
36
+ /** An operator-registered REST provider; absent for an MCP server's own sign-in. */
37
+ readonly provider: Schema.optional<Schema.String>;
38
+ }>]>;
39
+ export type Credential = typeof credentialSchema.Type;
40
+ export type SurfaceView = Readonly<{
41
+ kind: "mcp";
42
+ url: string;
43
+ }> | Readonly<{
44
+ kind: "openapi";
45
+ baseUrl: string;
46
+ table: readonly unknown[];
47
+ /** Digest of the decoded operation table. */
48
+ digest: string;
49
+ }>;
50
+ export declare const toolsSchema: Schema.Union<readonly [Schema.Literal<"all">, Schema.Struct<{
51
+ readonly effect: Schema.Literal<"read">;
52
+ }>, Schema.$Array<Schema.String>]>;
53
+ export type Tools = typeof toolsSchema.Type;
54
+ export declare const draftSchema: Schema.Struct<{
55
+ /** Idempotency key: a replay with the same request returns the same connection. */
56
+ readonly requestId: Schema.String;
57
+ readonly name: Schema.String;
58
+ /** Display text for owners; defaults to the name. */
59
+ readonly label: Schema.optional<Schema.String>;
60
+ readonly surface: Schema.Union<readonly [Schema.Struct<{
61
+ readonly kind: Schema.Literal<"mcp">;
62
+ readonly url: Schema.String;
63
+ }>, Schema.Struct<{
64
+ readonly kind: Schema.Literal<"openapi">;
65
+ readonly baseUrl: Schema.String;
66
+ readonly table: Schema.$Array<Schema.Unknown>;
67
+ }>]>;
68
+ readonly credential: Schema.Union<readonly [Schema.Struct<{
69
+ readonly kind: Schema.Literal<"none">;
70
+ }>, Schema.Struct<{
71
+ readonly kind: Schema.Literal<"static">;
72
+ readonly placement: Schema.Union<readonly [Schema.Literal<"bearer">, Schema.Struct<{
73
+ readonly header: Schema.String;
74
+ }>, Schema.Struct<{
75
+ readonly query: Schema.String;
76
+ }>]>;
77
+ }>, Schema.Struct<{
78
+ readonly kind: Schema.Literal<"oauth">;
79
+ readonly scope: Schema.optional<Schema.String>;
80
+ readonly clientId: Schema.optional<Schema.String>;
81
+ /** An operator-registered REST provider; absent for an MCP server's own sign-in. */
82
+ readonly provider: Schema.optional<Schema.String>;
83
+ }>]>;
84
+ readonly secret: Schema.optional<Schema.String>;
85
+ }>;
86
+ export type ConnectionDraft = typeof draftSchema.Type;
87
+ export declare const grantInputSchema: Schema.Struct<{
88
+ readonly connectionId: Schema.String;
89
+ readonly agentId: Schema.String;
90
+ readonly tools: Schema.Union<readonly [Schema.Literal<"all">, Schema.Struct<{
91
+ readonly effect: Schema.Literal<"read">;
92
+ }>, Schema.$Array<Schema.String>]>;
93
+ }>;
94
+ export type GrantInput = typeof grantInputSchema.Type;
95
+ /** The authorization server's redirect back to the owner. */
96
+ export declare const completeInputSchema: Schema.Struct<{
97
+ readonly state: Schema.String;
98
+ readonly code: Schema.String;
99
+ readonly iss: Schema.optional<Schema.String>;
100
+ }>;
101
+ export type CompleteInput = typeof completeInputSchema.Type;
102
+ /** Owner edits to a live connection, guarded by the version the owner last saw. */
103
+ export declare const updateInputSchema: Schema.Struct<{
104
+ readonly requestId: Schema.String;
105
+ readonly connectionId: Schema.String;
106
+ readonly expectedVersion: Schema.Number;
107
+ readonly label: Schema.optional<Schema.String>;
108
+ readonly name: Schema.optional<Schema.String>;
109
+ /** Replacement OpenAPI operation table, decoded by Forage. */
110
+ readonly table: Schema.optional<Schema.$Array<Schema.Unknown>>;
111
+ readonly baseUrl: Schema.optional<Schema.String>;
112
+ /** Credential swap; `none` and `static` only. */
113
+ readonly credential: Schema.optional<Schema.Union<readonly [Schema.Struct<{
114
+ readonly kind: Schema.Literal<"none">;
115
+ }>, Schema.Struct<{
116
+ readonly kind: Schema.Literal<"static">;
117
+ readonly placement: Schema.Union<readonly [Schema.Literal<"bearer">, Schema.Struct<{
118
+ readonly header: Schema.String;
119
+ }>, Schema.Struct<{
120
+ readonly query: Schema.String;
121
+ }>]>;
122
+ }>, Schema.Struct<{
123
+ readonly kind: Schema.Literal<"oauth">;
124
+ readonly scope: Schema.optional<Schema.String>;
125
+ readonly clientId: Schema.optional<Schema.String>;
126
+ /** An operator-registered REST provider; absent for an MCP server's own sign-in. */
127
+ readonly provider: Schema.optional<Schema.String>;
128
+ }>]>>;
129
+ readonly secret: Schema.optional<Schema.String>;
130
+ }>;
131
+ export type UpdateInput = typeof updateInputSchema.Type;
132
+ /** One typed OpenAPI operation call. */
133
+ export declare const callInputSchema: Schema.Struct<{
134
+ readonly operation: Schema.String;
135
+ readonly input: Schema.Unknown;
136
+ }>;
137
+ export type CallInput = typeof callInputSchema.Type;
138
+ export declare const egressRefSchema: Schema.Struct<{
139
+ readonly connectionId: Schema.String;
140
+ readonly grantVersion: Schema.Number;
141
+ }>;
142
+ export type EgressRef = typeof egressRefSchema.Type;
143
+ /** Where custody finds one connection; carries no secret. */
144
+ export type CustodyAddress = Readonly<{
145
+ ownerId: string;
146
+ connectionId: string;
147
+ credential: Credential;
148
+ }>;
149
+ /**
150
+ * Owner-side credential custody. `sealed` stores ciphertext, `nango` a locator.
151
+ * Only `inject` ever opens a credential, and it sends the request itself.
152
+ */
153
+ export type CustodyAdapter = Readonly<{
154
+ put: (address: CustodyAddress, secret: string) => Effect.Effect<string, ConnectionsError>;
155
+ inject: (address: CustodyAddress, locator: string, request: Request) => Effect.Effect<Response, ConnectionsError>;
156
+ remove: (address: CustodyAddress, locator: string) => Effect.Effect<"deleted" | "absent", ConnectionsError>;
157
+ /**
158
+ * Release a sealed OAuth bundle to the owner's connections core, which runs
159
+ * the authorization flow and refreshes tokens. Never reaches an Agent.
160
+ */
161
+ open?: (address: CustodyAddress, locator: string) => Effect.Effect<string, ConnectionsError>;
162
+ /**
163
+ * A custodian that runs the authorization itself (Nango's Connect) returns
164
+ * the page to send the owner to; it settles through a signed completion
165
+ * (`complete`) carrying the `state` given here, not through our callback.
166
+ */
167
+ connect?: (address: CustodyAddress, attempt: Readonly<{
168
+ state: string;
169
+ actor: string;
170
+ integrationId: string;
171
+ }>) => Effect.Effect<Readonly<{
172
+ connectUrl: string;
173
+ expiresAt: number;
174
+ }>, ConnectionsError>;
175
+ }>;
176
+ /**
177
+ * What a custodian proves after it ran an authorization: the attempt's state
178
+ * and where it keeps the credential. The host verifies the custodian's
179
+ * signature before it builds one.
180
+ */
181
+ export type CustodyCompletion = Readonly<{
182
+ state: string;
183
+ locator: string;
184
+ /** The integration the custodian ran it through; it must be the one the attempt began with. */
185
+ integrationId: string;
186
+ }>;
187
+ export type SqlValue = string | number | null;
188
+ /** The owner's SQLite in Cloudflare `ctx.storage` shape. */
189
+ export type SqlStore = Readonly<{
190
+ sql: Readonly<{
191
+ exec: (statement: string, ...bindings: SqlValue[]) => Readonly<{
192
+ toArray: () => readonly unknown[];
193
+ }>;
194
+ }>;
195
+ transactionSync: <A>(operation: () => A) => A;
196
+ }>;
197
+ /**
198
+ * A REST provider the operator registered an app with. Such providers publish
199
+ * no metadata the SDK can discover, and many require a pre-registered client,
200
+ * so the host names the endpoints and holds the client. Only the operator's
201
+ * configuration reaches this type; neither an Agent nor an owner supplies one.
202
+ */
203
+ export type StaticOAuthProvider = Readonly<{
204
+ issuer: string;
205
+ authorizationEndpoint: string;
206
+ tokenEndpoint: string;
207
+ /** Scopes requested when the owner names none. */
208
+ scope?: string;
209
+ client: {
210
+ client_id: string;
211
+ client_secret?: string | undefined;
212
+ };
213
+ }>;
214
+ export type ConnectionsOptions = Readonly<{
215
+ ownerId: string;
216
+ store: SqlStore;
217
+ custody: CustodyAdapter;
218
+ /** Host authority read: may this actor manage connections, or still stand as a grantor? */
219
+ authorize: (actorId: string, purpose: "manage" | "grantor") => boolean;
220
+ /**
221
+ * Asked right before every send, with the grantor who stands behind the
222
+ * grant. The host keeps its own live recheck here (membership, write
223
+ * rights); the core never learns what the host's policy is.
224
+ */
225
+ check?: (grantor: string, grant: Readonly<{
226
+ agentId: string;
227
+ connectionId: string;
228
+ tools: Tools;
229
+ }>, operation: Readonly<{
230
+ name: string;
231
+ effect: "read" | "write" | null;
232
+ }> | null) => Promise<boolean>;
233
+ egressPolicy?: PublicEgressPolicy;
234
+ /**
235
+ * Needed only for `oauth` connections: where the authorization server sends
236
+ * the owner back, and the checked public transport for discovery, token and
237
+ * refresh requests. Custody must also implement `open`.
238
+ */
239
+ oauth?: Readonly<{
240
+ callbackUrl: string;
241
+ fetch: (request: Request) => Promise<Response>;
242
+ /** REST providers the operator registered an app with, by id. */
243
+ providers?: Readonly<Record<string, StaticOAuthProvider>>;
244
+ }> | undefined;
245
+ clock: () => number;
246
+ ids: () => string;
247
+ }>;
248
+ /** Secret-free, versioned, and safe to hand to an Agent. */
249
+ export type ConnectionProjection = Readonly<{
250
+ revision: number;
251
+ connections: readonly Readonly<{
252
+ connectionId: string;
253
+ name: string;
254
+ surface: SurfaceView;
255
+ tools: Tools;
256
+ /** Bumped by every `update`. */
257
+ version: number;
258
+ authorityVersion: number;
259
+ grantVersion: number;
260
+ }>[];
261
+ }>;
262
+ export type ConnectionView = Readonly<{
263
+ connectionId: string;
264
+ name: string;
265
+ label: string;
266
+ surface: Readonly<{
267
+ kind: "mcp" | "openapi";
268
+ origin: string;
269
+ url: string;
270
+ digest: string | null;
271
+ }>;
272
+ credentialKind: Credential["kind"];
273
+ /** How the secret is placed; the secret itself never appears. */
274
+ credential: Credential;
275
+ state: "pending" | "active" | "reconnect_required" | "revoked";
276
+ version: number;
277
+ authorityVersion: number;
278
+ grants: readonly Readonly<{
279
+ agentId: string;
280
+ tools: Tools;
281
+ version: number;
282
+ grantor: string;
283
+ }>[];
284
+ }>;
285
+ /** The Agent's narrowed capability: only the owner opens the credential. */
286
+ export type NarrowFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
287
+ export {};
@@ -0,0 +1,95 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ /** Fixed codes only: upstream bodies, URLs and credential bytes never enter errors. */
4
+ export class ConnectionsError extends Error {
5
+ code;
6
+ name = "ConnectionsError";
7
+ constructor(code, options) {
8
+ super(code, options);
9
+ this.code = code;
10
+ }
11
+ }
12
+ export const identifier = Schema.String.pipe(Schema.check(Schema.isPattern(/^[A-Za-z0-9_.:-]{1,128}$/u)));
13
+ const headerName = Schema.String.pipe(Schema.check(Schema.isPattern(/^[A-Za-z0-9-]{1,64}$/u)));
14
+ const queryName = Schema.String.pipe(Schema.check(Schema.isPattern(/^[A-Za-z0-9_.-]{1,64}$/u)));
15
+ const version = Schema.Number.pipe(Schema.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(1)));
16
+ const placementSchema = Schema.Union([
17
+ Schema.Literal("bearer"),
18
+ Schema.Struct({ header: headerName }),
19
+ Schema.Struct({ query: queryName }),
20
+ ]);
21
+ /**
22
+ * How the final request is authorized. For `oauth` the owner may bring its own
23
+ * client (`clientId`, with the client secret as the draft's `secret`); without
24
+ * one the server's dynamic registration is used.
25
+ */
26
+ export const credentialSchema = Schema.Union([
27
+ Schema.Struct({ kind: Schema.Literal("none") }),
28
+ Schema.Struct({ kind: Schema.Literal("static"), placement: placementSchema }),
29
+ Schema.Struct({
30
+ kind: Schema.Literal("oauth"),
31
+ scope: Schema.optional(Schema.String),
32
+ clientId: Schema.optional(Schema.String),
33
+ /** An operator-registered REST provider; absent for an MCP server's own sign-in. */
34
+ provider: Schema.optional(Schema.String),
35
+ }),
36
+ ]);
37
+ /** What the Agent can call. The OpenAPI table is decoded by Forage before it is stored. */
38
+ const surfaceDraftSchema = Schema.Union([
39
+ Schema.Struct({ kind: Schema.Literal("mcp"), url: Schema.String }),
40
+ Schema.Struct({
41
+ kind: Schema.Literal("openapi"),
42
+ baseUrl: Schema.String,
43
+ table: Schema.Array(Schema.Unknown),
44
+ }),
45
+ ]);
46
+ export const toolsSchema = Schema.Union([
47
+ Schema.Literal("all"),
48
+ /** OpenAPI only: every operation whose stored effect is `read`. */
49
+ Schema.Struct({ effect: Schema.Literal("read") }),
50
+ Schema.Array(Schema.String.pipe(Schema.check(Schema.isMinLength(1), Schema.isMaxLength(256)))).pipe(Schema.check(Schema.isMaxLength(256))),
51
+ ]);
52
+ export const draftSchema = Schema.Struct({
53
+ /** Idempotency key: a replay with the same request returns the same connection. */
54
+ requestId: identifier,
55
+ name: Schema.String.pipe(Schema.check(Schema.isMinLength(1), Schema.isMaxLength(128))),
56
+ /** Display text for owners; defaults to the name. */
57
+ label: Schema.optional(Schema.String.pipe(Schema.check(Schema.isMinLength(1), Schema.isMaxLength(128)))),
58
+ surface: surfaceDraftSchema,
59
+ credential: credentialSchema,
60
+ secret: Schema.optional(Schema.String.pipe(Schema.check(Schema.isPattern(/^[\x21-\x7e]{1,16384}$/u)))),
61
+ });
62
+ export const grantInputSchema = Schema.Struct({
63
+ connectionId: identifier,
64
+ agentId: identifier,
65
+ tools: toolsSchema,
66
+ });
67
+ /** The authorization server's redirect back to the owner. */
68
+ export const completeInputSchema = Schema.Struct({
69
+ state: Schema.String.pipe(Schema.check(Schema.isMaxLength(512))),
70
+ code: Schema.String.pipe(Schema.check(Schema.isMaxLength(4096))),
71
+ iss: Schema.optional(Schema.String.pipe(Schema.check(Schema.isMaxLength(2048)))),
72
+ });
73
+ /** Owner edits to a live connection, guarded by the version the owner last saw. */
74
+ export const updateInputSchema = Schema.Struct({
75
+ requestId: identifier,
76
+ connectionId: identifier,
77
+ expectedVersion: version,
78
+ label: Schema.optional(Schema.String.pipe(Schema.check(Schema.isMinLength(1), Schema.isMaxLength(128)))),
79
+ name: Schema.optional(Schema.String.pipe(Schema.check(Schema.isMinLength(1), Schema.isMaxLength(128)))),
80
+ /** Replacement OpenAPI operation table, decoded by Forage. */
81
+ table: Schema.optional(Schema.Array(Schema.Unknown)),
82
+ baseUrl: Schema.optional(Schema.String),
83
+ /** Credential swap; `none` and `static` only. */
84
+ credential: Schema.optional(credentialSchema),
85
+ secret: Schema.optional(Schema.String.pipe(Schema.check(Schema.isPattern(/^[\x21-\x7e]{1,16384}$/u)))),
86
+ });
87
+ /** One typed OpenAPI operation call. */
88
+ export const callInputSchema = Schema.Struct({
89
+ operation: Schema.String.pipe(Schema.check(Schema.isMaxLength(256))),
90
+ input: Schema.Unknown,
91
+ });
92
+ export const egressRefSchema = Schema.Struct({
93
+ connectionId: identifier,
94
+ grantVersion: version,
95
+ });
@@ -0,0 +1,98 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { ConnectionsError, type CallInput, type ConnectionDraft, type Tools, type GrantInput, type ConnectionsOptions, type EgressRef, type NarrowFetch, type SurfaceView } from "./contract.js";
3
+ /** One owner's connections: attempts, connections, grants, receipts and cleanup in its SQLite. */
4
+ export declare function createConnections(options: ConnectionsOptions): {
5
+ begin: (actor: string, input: ConnectionDraft) => Effect.Effect<{
6
+ connectionId: string;
7
+ authorizeUrl: string | null;
8
+ }, ConnectionsError, never>;
9
+ complete: (actor: string, input: import("./contract.js").CompleteInput) => Effect.Effect<{
10
+ connectionId: string;
11
+ }, ConnectionsError, never>;
12
+ completeCustody: (input: import("./contract.js").CustodyCompletion) => Effect.Effect<{
13
+ connectionId: string;
14
+ }, ConnectionsError, never>;
15
+ grant: (actor: string, input: GrantInput) => Effect.Effect<{
16
+ grantVersion: number;
17
+ }, ConnectionsError, never>;
18
+ ungrant: (actor: string, input: {
19
+ connectionId: string;
20
+ agentId: string;
21
+ }) => Effect.Effect<void, ConnectionsError, never>;
22
+ revoke: (actor: string, input: {
23
+ connectionId: string;
24
+ }) => Effect.Effect<void, ConnectionsError, never>;
25
+ projection: (agentId: string) => Effect.Effect<{
26
+ revision: number;
27
+ connections: {
28
+ connectionId: string;
29
+ name: string;
30
+ surface: SurfaceView;
31
+ tools: readonly string[] | "all" | {
32
+ readonly effect: "read";
33
+ };
34
+ version: number;
35
+ authorityVersion: number;
36
+ grantVersion: number;
37
+ }[];
38
+ }, ConnectionsError, never>;
39
+ list: (actor: string) => Effect.Effect<Readonly<{
40
+ connectionId: string;
41
+ name: string;
42
+ label: string;
43
+ surface: Readonly<{
44
+ kind: "mcp" | "openapi";
45
+ origin: string;
46
+ url: string;
47
+ digest: string | null;
48
+ }>;
49
+ credentialKind: import("./contract.js").Credential["kind"];
50
+ credential: import("./contract.js").Credential;
51
+ state: "pending" | "active" | "reconnect_required" | "revoked";
52
+ version: number;
53
+ authorityVersion: number;
54
+ grants: readonly Readonly<{
55
+ agentId: string;
56
+ tools: Tools;
57
+ version: number;
58
+ grantor: string;
59
+ }>[];
60
+ }>[], ConnectionsError, never>;
61
+ egress: (agentId: string, input: EgressRef) => NarrowFetch;
62
+ call: (agentId: string, input: EgressRef, raw: CallInput) => Effect.Effect<import("zod").JSONType, ConnectionsError, never>;
63
+ ownerCall: (actor: string, at: Readonly<{
64
+ connectionId: string;
65
+ expectedVersion: number;
66
+ }>, raw: CallInput) => Effect.Effect<import("zod").JSONType, ConnectionsError, never>;
67
+ tools: (actor: string, connectionId: string) => Effect.Effect<readonly string[], ConnectionsError, never>;
68
+ operations: (actor: string, id: string) => Effect.Effect<{
69
+ operations: {
70
+ status: "supported";
71
+ name: string;
72
+ method: "GET" | "HEAD" | "OPTIONS" | "POST" | "PUT" | "PATCH" | "DELETE";
73
+ path: string;
74
+ effect: "write" | "read";
75
+ description: string & import("zod").$brand<"ThirdPartyText">;
76
+ textSource: "third_party";
77
+ tags: (string & import("zod").$brand<"ThirdPartyText">)[];
78
+ parameters: {
79
+ name: string;
80
+ in: "header" | "path" | "query";
81
+ required: boolean;
82
+ schema: boolean | Record<string, import("zod").JSONType>;
83
+ style?: string | undefined;
84
+ explode?: boolean | undefined;
85
+ allowReserved?: boolean | undefined;
86
+ }[];
87
+ input: boolean | Record<string, import("zod").JSONType>;
88
+ output: boolean | Record<string, import("zod").JSONType>;
89
+ }[];
90
+ digest: string;
91
+ }, ConnectionsError, never>;
92
+ update: (actor: string, raw: import("./contract.js").UpdateInput) => Effect.Effect<{
93
+ connectionId: string;
94
+ version: number;
95
+ }, ConnectionsError, never>;
96
+ sweep: () => Effect.Effect<void, ConnectionsError, never>;
97
+ cleanupPending: () => number;
98
+ };