@zackbart/connecta 0.37.1 → 0.39.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/CHANGELOG.md +94 -0
- package/README.md +5 -4
- package/bin/connecta.mjs +1 -39
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +2 -4
- package/dist/agent-identity.js +1 -1
- package/dist/agent-routes.js +231 -0
- package/dist/auth/clerk-sdk/generated.js +2 -1
- package/dist/auth/downstream-oauth/consent.js +519 -0
- package/dist/auth/downstream-oauth/grant-store.js +439 -0
- package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
- package/dist/auth/downstream-oauth/refresh-records.js +447 -0
- package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
- package/dist/auth/downstream-oauth.js +66 -2516
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/native.js +27 -0
- package/dist/auth/oauth-grant.js +24 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/static-oauth.js +153 -131
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +9 -3
- package/dist/authorization-handoff.js +11 -5
- package/dist/branding.d.ts +1 -15
- package/dist/branding.js +2 -2
- package/dist/bytes.js +42 -0
- package/dist/call-admission-context.js +2 -5
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-cache.js +130 -43
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-policy.js +18 -0
- package/dist/catalog-service.js +285 -48
- package/dist/catalog.js +18 -2
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.js +6 -2
- package/dist/config.d.ts +57 -25
- package/dist/config.js +30 -77
- package/dist/connector-caller.d.ts +1 -1
- package/dist/connector-health.js +59 -0
- package/dist/connector-status.js +117 -0
- package/dist/connectors/api-connector.d.ts +7 -2
- package/dist/connectors/api-connector.js +11 -7
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- package/dist/connectors/negotiation-cache.js +22 -4
- package/dist/connectors/remote-mcp/call.js +164 -0
- package/dist/connectors/remote-mcp/catalog.js +278 -0
- package/dist/connectors/remote-mcp/config.js +263 -0
- package/dist/connectors/remote-mcp/connection.js +671 -0
- package/dist/connectors/remote-mcp/errors.js +488 -0
- package/dist/connectors/remote-mcp/oauth.js +469 -0
- package/dist/connectors/remote-mcp/resources.js +473 -0
- package/dist/connectors/remote-mcp/runtime.js +1 -0
- package/dist/connectors/remote-mcp/transport.js +544 -0
- package/dist/connectors/remote-mcp.js +61 -2898
- package/dist/connectors/resource-uri.js +0 -4
- package/dist/credential-contract.d.ts +23 -0
- package/dist/credential-reads.js +17 -0
- package/dist/credential-verification.js +53 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.js +60 -25
- package/dist/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- package/dist/describe-config.d.ts +12 -2
- package/dist/describe-config.js +2 -0
- package/dist/discovery-health.js +199 -0
- package/dist/downstream-input.js +4 -2
- package/dist/errors.d.ts +55 -17
- package/dist/errors.js +95 -41
- package/dist/execute.js +172 -125
- package/dist/executor-result.js +5 -2
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-functions.js +83 -0
- package/dist/guest-runtime.js +63 -20
- package/dist/guest-types.d.ts +24 -10
- package/dist/http/argument-problems.js +35 -0
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +7 -6
- package/dist/http/fields.js +192 -13
- package/dist/http/hatch-response.js +73 -0
- package/dist/http/hatch.js +88 -23
- package/dist/http/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +4 -3
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +33 -5
- package/dist/http/safety.js +9 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +45 -30
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/invocation-auth.js +2 -1
- package/dist/invocation.js +21 -6
- package/dist/membership.d.ts +13 -5
- package/dist/membership.js +34 -2
- package/dist/meta-tools.js +50 -35
- package/dist/native-recovery.js +4 -16
- package/dist/native-tools.js +6 -2
- package/dist/oauth-handoff.js +2 -2
- package/dist/oauth-sealing.js +10 -0
- package/dist/operation-vocabulary.js +136 -14
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -49
- package/dist/operator-ui/contract.d.ts +12 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +22 -1
- package/dist/program-writes.js +3 -8
- package/dist/provider-call.js +0 -7
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/kit.js +1 -6
- package/dist/providers/_shared/google/service-account.js +1 -3
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/guide.js +3 -0
- package/dist/providers/_shared/json.js +12 -0
- package/dist/providers/_shared/rest/compact-index.js +1 -1
- package/dist/providers/_shared/rest/kit.js +1 -3
- package/dist/providers/_shared/rest/operation-index.js +49 -53
- package/dist/providers/_shared/rest/value-safety.js +106 -28
- package/dist/providers/breeze/api.js +9 -8
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/breeze/tools.js +7 -7
- package/dist/providers/ccb/api.js +3 -7
- package/dist/providers/ccb/index.js +7 -1
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +10 -3
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/lib/schemas.js +24 -22
- package/dist/providers/docs/safety.js +2 -0
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/docs/tools.js +4 -14
- package/dist/providers/drive/api.js +1 -0
- package/dist/providers/drive/lib/contracts.js +8 -5
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/drive/tools.js +23 -41
- package/dist/providers/forms/api.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/forms/tools.js +11 -16
- package/dist/providers/github/api.js +1 -0
- package/dist/providers/github/lib/discovery.js +1 -1
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +61 -17
- package/dist/providers/gmail/lib/schemas.js +60 -50
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/gmail/tools.js +12 -26
- package/dist/providers/granola/index.js +2 -0
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/api.js +8 -4
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +5 -2
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +1 -9
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/planning-center/tools.js +2 -1
- package/dist/providers/quickbooks/safety.js +80 -39
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/api.js +1 -0
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/sheets/tools.js +14 -23
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/slides/tools.js +19 -26
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/api.js +5 -8
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +2 -7
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/tools.js +6 -4
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +11 -5
- package/dist/registry.js +46 -20
- package/dist/request-principal.js +1 -1
- package/dist/request-state.js +2 -2
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/activity-disclosure.js +1 -1
- package/dist/routes/connect.js +2 -2
- package/dist/routes/credentials.js +130 -61
- package/dist/routes/dispatch.js +5 -1
- package/dist/routes/mcp.js +34 -17
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +6 -5
- package/dist/routes/shared.d.ts +13 -8
- package/dist/routes/shared.js +15 -9
- package/dist/routes/ui-config.js +3 -3
- package/dist/routes/ui-data.js +126 -20
- package/dist/routes/ui.js +17 -1
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +8 -3
- package/dist/runtime/native-storage.js +9 -0
- package/dist/runtime/request-reads.js +182 -0
- package/dist/runtime/request-scope.js +16 -0
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/runtime/storage.js +12 -0
- package/dist/sent-secrets.d.ts +1 -2
- package/dist/sent-secrets.js +3 -8
- package/dist/server.js +4 -4
- package/dist/skills.js +44 -54
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +69 -128
- package/dist/storage/memory.js +8 -2
- package/dist/storage/sql-native.js +27 -101
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +13 -14
- package/dist/storage-digest.js +32 -0
- package/dist/timeout.js +8 -0
- package/dist/tool-error.js +4 -0
- package/dist/types.d.ts +33 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +18 -37
- package/dist/validate.d.ts +3 -0
- package/dist/validate.js +220 -158
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +1 -1
- package/dist/write-outcome.js +0 -3
- package/documentation/README.md +19 -55
- package/documentation/architecture.md +436 -515
- package/documentation/auth.md +229 -88
- package/documentation/code-mode.md +201 -94
- package/documentation/deploying.md +25 -43
- package/documentation/integrating.md +18 -19
- package/documentation/meta-tools.md +225 -51
- package/documentation/migration-0.33.md +10 -6
- package/documentation/migration-0.34.md +5 -5
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +20 -120
- package/documentation/operator-ui.md +108 -16
- package/documentation/pool-membership-upgrade.md +44 -0
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/documentation/providers.md +105 -0
- package/examples/worker/AGENTS.md +14 -16
- package/examples/worker/README.md +24 -118
- package/examples/worker/src/connecta.config.ts +1 -0
- package/guides/connecting-agents.md +9 -31
- package/guides/how-it-works.md +6 -4
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/AGENTS.md +13 -12
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +1 -0
- package/dist/auth/oauth-v2-migration.js +0 -135
- package/dist/provider-call-facts.d.ts +0 -115
- package/dist/provider-call.d.ts +0 -112
- package/dist/storage/d1-copy.js +0 -255
- package/dist/storage/sql-schema.d.ts +0 -6
- package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
- package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
package/dist/config.d.ts
CHANGED
|
@@ -18,8 +18,10 @@ declare const discovery: {
|
|
|
18
18
|
/**
|
|
19
19
|
* Deadline (ms) for each downstream probe/catalog call fanned out by
|
|
20
20
|
* `search_tools` and by `connecta.search`/`connecta.describe` inside
|
|
21
|
-
* `execute_code`. Defaults to 30_000.
|
|
22
|
-
*
|
|
21
|
+
* `execute_code`. Defaults to 30_000. An unscoped search caps each probe at
|
|
22
|
+
* 5_000 and its whole fan-out at 9_000, and skips a connector whose
|
|
23
|
+
* discovery recently timed out for up to 60 seconds. A timed-out connector
|
|
24
|
+
* degrades independently; this does not apply to tool calls. Catalog walks receive the
|
|
23
25
|
* same cancellation signal, which aborts an in-flight page where supported and
|
|
24
26
|
* prevents another from starting.
|
|
25
27
|
*/
|
|
@@ -28,11 +30,12 @@ declare const discovery: {
|
|
|
28
30
|
declare const calls: {
|
|
29
31
|
/**
|
|
30
32
|
* Deadline (ms) for `call_tool`/`call_destructive_tool` calls that pass no
|
|
31
|
-
* `timeoutMs`. An explicit per-call value wins.
|
|
32
|
-
*
|
|
33
|
+
* `timeoutMs`. An explicit per-call value wins. Unset by default, when those
|
|
34
|
+
* calls fall back to `execute.hostCallTimeoutMs`.
|
|
33
35
|
*
|
|
34
|
-
* Each call makes one attempt. `
|
|
35
|
-
*
|
|
36
|
+
* Each call makes one attempt. `connecta.call` inside `execute_code` uses
|
|
37
|
+
* it too, ahead of `execute.hostCallTimeoutMs`; `connecta.result`,
|
|
38
|
+
* `connecta.skill` and `connecta.read` do not.
|
|
36
39
|
*/
|
|
37
40
|
defaultTimeoutMs: import("./config-schema.js").Field<number, number | undefined, true>;
|
|
38
41
|
/**
|
|
@@ -60,9 +63,12 @@ declare const execute: {
|
|
|
60
63
|
/** Host calls one program may make. Default 20. */
|
|
61
64
|
maxHostCalls: import("./config-schema.js").Field<number, number, true>;
|
|
62
65
|
/**
|
|
63
|
-
* Deadline for
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
+
* Deadline for a program's `connecta.result`, `connecta.skill` and
|
|
67
|
+
* `connecta.read`, in milliseconds. Default 15_000. Discovery uses
|
|
68
|
+
* `discovery.probeTimeoutMs`. `connecta.call` and the direct call tools use it only when
|
|
69
|
+
* `calls.defaultTimeoutMs` is unset, and an explicit per-call `timeoutMs`
|
|
70
|
+
* always wins. Raise it for providers whose legitimate calls run longer,
|
|
71
|
+
* such as analytics queries.
|
|
66
72
|
*/
|
|
67
73
|
hostCallTimeoutMs: import("./config-schema.js").Field<number, number, true>;
|
|
68
74
|
/**
|
|
@@ -78,6 +84,14 @@ declare const execute: {
|
|
|
78
84
|
* budget every call already spends. Default 10.
|
|
79
85
|
*/
|
|
80
86
|
maxWrites: import("./config-schema.js").Field<number, number, true>;
|
|
87
|
+
/**
|
|
88
|
+
* Serialized characters a program may return, shared with emitted text,
|
|
89
|
+
* before Connecta stashes the return and answers with a paging notice.
|
|
90
|
+
* Default 24_000 (~6k tokens). Raise it when agents need large results whole
|
|
91
|
+
* and the model's context can afford them: each paging round trip costs a
|
|
92
|
+
* model step. From 4_000 to 1_000_000.
|
|
93
|
+
*/
|
|
94
|
+
maxResultChars: import("./config-schema.js").Field<number, number, true>;
|
|
81
95
|
};
|
|
82
96
|
declare const codeAdmission: {
|
|
83
97
|
/** Simultaneous work admitted to this pool. */
|
|
@@ -179,8 +193,9 @@ declare const pool: {
|
|
|
179
193
|
/**
|
|
180
194
|
* Who may open `/mcp/<name>`: principal ids in the namespace of the one
|
|
181
195
|
* interactive auth provider, `{ namespace, id }`, or `"agent:<name>"` for a
|
|
182
|
-
* declared agent.
|
|
183
|
-
* `tools` to these members and
|
|
196
|
+
* declared agent. All members must be people or all must be agents. Required
|
|
197
|
+
* and non-empty; the pool serves exactly its `tools` to these members and
|
|
198
|
+
* the same 404 to everyone else.
|
|
184
199
|
*/
|
|
185
200
|
members: import("./config-schema.js").Field<readonly MemberRef[], readonly MemberRef[], false>;
|
|
186
201
|
/**
|
|
@@ -188,8 +203,8 @@ declare const pool: {
|
|
|
188
203
|
* connector whose `subject` is a mapping function. Humans always act as
|
|
189
204
|
* themselves through that mapping, and a fixed `subject` address applies to
|
|
190
205
|
* everyone. Without an owner, a mapping-subject Workspace connector is
|
|
191
|
-
* absent for this pool's agents, as a personal connector is
|
|
192
|
-
* pool
|
|
206
|
+
* absent for this pool's agents, as a personal connector is. People use a
|
|
207
|
+
* separate pool and always act as themselves.
|
|
193
208
|
*/
|
|
194
209
|
owner: import("./config-schema.js").Field<string, string | undefined, true>;
|
|
195
210
|
/**
|
|
@@ -206,7 +221,7 @@ declare const pool: {
|
|
|
206
221
|
* `<connector>__<tool>` and annotated from its classification, for clients
|
|
207
222
|
* that run their own tool search; calls take the same invocation path.
|
|
208
223
|
*/
|
|
209
|
-
surface: import("./config-schema.js").Field<"
|
|
224
|
+
surface: import("./config-schema.js").Field<"meta" | "native", "meta" | "native" | undefined, true>;
|
|
210
225
|
/**
|
|
211
226
|
* `false` drops `execute_code` from a native pool and lists `read_result`
|
|
212
227
|
* for paging instead. Only valid with `surface: "native"`: the meta surface
|
|
@@ -242,7 +257,7 @@ declare const connectaConfig: {
|
|
|
242
257
|
readOnly?: boolean | undefined;
|
|
243
258
|
owner?: string | undefined;
|
|
244
259
|
description?: string | undefined;
|
|
245
|
-
surface?: "
|
|
260
|
+
surface?: "meta" | "native" | undefined;
|
|
246
261
|
executeCode?: boolean | undefined;
|
|
247
262
|
tools: readonly string[];
|
|
248
263
|
members: readonly MemberRef[];
|
|
@@ -252,7 +267,7 @@ declare const connectaConfig: {
|
|
|
252
267
|
readonly members: readonly MemberRef[];
|
|
253
268
|
readonly owner: string | undefined;
|
|
254
269
|
readonly description: string | undefined;
|
|
255
|
-
readonly surface: "
|
|
270
|
+
readonly surface: "meta" | "native" | undefined;
|
|
256
271
|
readonly executeCode: boolean | undefined;
|
|
257
272
|
}>> | undefined, true>;
|
|
258
273
|
/** Exact connector id -> tool name -> verdict. Hidden tools cannot be discovered or called.
|
|
@@ -314,8 +329,10 @@ declare const connectaConfig: {
|
|
|
314
329
|
/**
|
|
315
330
|
* Deadline (ms) for each downstream probe/catalog call fanned out by
|
|
316
331
|
* `search_tools` and by `connecta.search`/`connecta.describe` inside
|
|
317
|
-
* `execute_code`. Defaults to 30_000.
|
|
318
|
-
*
|
|
332
|
+
* `execute_code`. Defaults to 30_000. An unscoped search caps each probe at
|
|
333
|
+
* 5_000 and its whole fan-out at 9_000, and skips a connector whose
|
|
334
|
+
* discovery recently timed out for up to 60 seconds. A timed-out connector
|
|
335
|
+
* degrades independently; this does not apply to tool calls. Catalog walks receive the
|
|
319
336
|
* same cancellation signal, which aborts an in-flight page where supported and
|
|
320
337
|
* prevents another from starting.
|
|
321
338
|
*/
|
|
@@ -333,11 +350,12 @@ declare const connectaConfig: {
|
|
|
333
350
|
readonly shape: {
|
|
334
351
|
/**
|
|
335
352
|
* Deadline (ms) for `call_tool`/`call_destructive_tool` calls that pass no
|
|
336
|
-
* `timeoutMs`. An explicit per-call value wins.
|
|
337
|
-
*
|
|
353
|
+
* `timeoutMs`. An explicit per-call value wins. Unset by default, when those
|
|
354
|
+
* calls fall back to `execute.hostCallTimeoutMs`.
|
|
338
355
|
*
|
|
339
|
-
* Each call makes one attempt. `
|
|
340
|
-
*
|
|
356
|
+
* Each call makes one attempt. `connecta.call` inside `execute_code` uses
|
|
357
|
+
* it too, ahead of `execute.hostCallTimeoutMs`; `connecta.result`,
|
|
358
|
+
* `connecta.skill` and `connecta.read` do not.
|
|
341
359
|
*/
|
|
342
360
|
defaultTimeoutMs: import("./config-schema.js").Field<number, number | undefined, true>;
|
|
343
361
|
/**
|
|
@@ -371,6 +389,7 @@ declare const connectaConfig: {
|
|
|
371
389
|
hostCallTimeoutMs?: number | undefined;
|
|
372
390
|
watchdogMs?: number | undefined;
|
|
373
391
|
maxWrites?: number | undefined;
|
|
392
|
+
maxResultChars?: number | undefined;
|
|
374
393
|
}, {
|
|
375
394
|
readonly maxEmittedBytes: number;
|
|
376
395
|
readonly maxEmittedBlocks: number;
|
|
@@ -378,6 +397,7 @@ declare const connectaConfig: {
|
|
|
378
397
|
readonly hostCallTimeoutMs: number;
|
|
379
398
|
readonly watchdogMs: number;
|
|
380
399
|
readonly maxWrites: number;
|
|
400
|
+
readonly maxResultChars: number;
|
|
381
401
|
}, true> & {
|
|
382
402
|
readonly shape: {
|
|
383
403
|
/**
|
|
@@ -391,9 +411,12 @@ declare const connectaConfig: {
|
|
|
391
411
|
/** Host calls one program may make. Default 20. */
|
|
392
412
|
maxHostCalls: import("./config-schema.js").Field<number, number, true>;
|
|
393
413
|
/**
|
|
394
|
-
* Deadline for
|
|
395
|
-
*
|
|
396
|
-
*
|
|
414
|
+
* Deadline for a program's `connecta.result`, `connecta.skill` and
|
|
415
|
+
* `connecta.read`, in milliseconds. Default 15_000. Discovery uses
|
|
416
|
+
* `discovery.probeTimeoutMs`. `connecta.call` and the direct call tools use it only when
|
|
417
|
+
* `calls.defaultTimeoutMs` is unset, and an explicit per-call `timeoutMs`
|
|
418
|
+
* always wins. Raise it for providers whose legitimate calls run longer,
|
|
419
|
+
* such as analytics queries.
|
|
397
420
|
*/
|
|
398
421
|
hostCallTimeoutMs: import("./config-schema.js").Field<number, number, true>;
|
|
399
422
|
/**
|
|
@@ -409,6 +432,14 @@ declare const connectaConfig: {
|
|
|
409
432
|
* budget every call already spends. Default 10.
|
|
410
433
|
*/
|
|
411
434
|
maxWrites: import("./config-schema.js").Field<number, number, true>;
|
|
435
|
+
/**
|
|
436
|
+
* Serialized characters a program may return, shared with emitted text,
|
|
437
|
+
* before Connecta stashes the return and answers with a paging notice.
|
|
438
|
+
* Default 24_000 (~6k tokens). Raise it when agents need large results whole
|
|
439
|
+
* and the model's context can afford them: each paging round trip costs a
|
|
440
|
+
* model step. From 4_000 to 1_000_000.
|
|
441
|
+
*/
|
|
442
|
+
maxResultChars: import("./config-schema.js").Field<number, number, true>;
|
|
412
443
|
};
|
|
413
444
|
};
|
|
414
445
|
/**
|
|
@@ -654,5 +685,6 @@ export declare function executeLimits(config: ResolvedConfig): {
|
|
|
654
685
|
hostCallTimeoutMs: number;
|
|
655
686
|
watchdogMs: number;
|
|
656
687
|
maxWrites: number;
|
|
688
|
+
maxResultChars: number;
|
|
657
689
|
};
|
|
658
690
|
export {};
|
package/dist/config.js
CHANGED
|
@@ -69,8 +69,10 @@ const discovery = {
|
|
|
69
69
|
/**
|
|
70
70
|
* Deadline (ms) for each downstream probe/catalog call fanned out by
|
|
71
71
|
* `search_tools` and by `connecta.search`/`connecta.describe` inside
|
|
72
|
-
* `execute_code`. Defaults to 30_000.
|
|
73
|
-
*
|
|
72
|
+
* `execute_code`. Defaults to 30_000. An unscoped search caps each probe at
|
|
73
|
+
* 5_000 and its whole fan-out at 9_000, and skips a connector whose
|
|
74
|
+
* discovery recently timed out for up to 60 seconds. A timed-out connector
|
|
75
|
+
* degrades independently; this does not apply to tool calls. Catalog walks receive the
|
|
74
76
|
* same cancellation signal, which aborts an in-flight page where supported and
|
|
75
77
|
* prevents another from starting.
|
|
76
78
|
*/
|
|
@@ -79,11 +81,12 @@ const discovery = {
|
|
|
79
81
|
const calls = {
|
|
80
82
|
/**
|
|
81
83
|
* Deadline (ms) for `call_tool`/`call_destructive_tool` calls that pass no
|
|
82
|
-
* `timeoutMs`. An explicit per-call value wins.
|
|
83
|
-
*
|
|
84
|
+
* `timeoutMs`. An explicit per-call value wins. Unset by default, when those
|
|
85
|
+
* calls fall back to `execute.hostCallTimeoutMs`.
|
|
84
86
|
*
|
|
85
|
-
* Each call makes one attempt. `
|
|
86
|
-
*
|
|
87
|
+
* Each call makes one attempt. `connecta.call` inside `execute_code` uses
|
|
88
|
+
* it too, ahead of `execute.hostCallTimeoutMs`; `connecta.result`,
|
|
89
|
+
* `connecta.skill` and `connecta.read` do not.
|
|
87
90
|
*/
|
|
88
91
|
defaultTimeoutMs: whole({ min: 1 }),
|
|
89
92
|
/**
|
|
@@ -111,9 +114,12 @@ const execute = {
|
|
|
111
114
|
/** Host calls one program may make. Default 20. */
|
|
112
115
|
maxHostCalls: whole({ min: 1, default: D.execute.maxHostCalls }),
|
|
113
116
|
/**
|
|
114
|
-
* Deadline for
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
+
* Deadline for a program's `connecta.result`, `connecta.skill` and
|
|
118
|
+
* `connecta.read`, in milliseconds. Default 15_000. Discovery uses
|
|
119
|
+
* `discovery.probeTimeoutMs`. `connecta.call` and the direct call tools use it only when
|
|
120
|
+
* `calls.defaultTimeoutMs` is unset, and an explicit per-call `timeoutMs`
|
|
121
|
+
* always wins. Raise it for providers whose legitimate calls run longer,
|
|
122
|
+
* such as analytics queries.
|
|
117
123
|
*/
|
|
118
124
|
hostCallTimeoutMs: whole({ min: 1, default: D.execute.hostCallTimeoutMs }),
|
|
119
125
|
/**
|
|
@@ -129,6 +135,14 @@ const execute = {
|
|
|
129
135
|
* budget every call already spends. Default 10.
|
|
130
136
|
*/
|
|
131
137
|
maxWrites: whole({ min: 1, default: D.execute.maxWrites }),
|
|
138
|
+
/**
|
|
139
|
+
* Serialized characters a program may return, shared with emitted text,
|
|
140
|
+
* before Connecta stashes the return and answers with a paging notice.
|
|
141
|
+
* Default 24_000 (~6k tokens). Raise it when agents need large results whole
|
|
142
|
+
* and the model's context can afford them: each paging round trip costs a
|
|
143
|
+
* model step. From 4_000 to 1_000_000.
|
|
144
|
+
*/
|
|
145
|
+
maxResultChars: whole({ min: 4_000, max: 1_000_000, default: D.execute.maxResultChars }),
|
|
132
146
|
};
|
|
133
147
|
function admissionPool(defaults) {
|
|
134
148
|
return {
|
|
@@ -225,8 +239,9 @@ const pool = {
|
|
|
225
239
|
/**
|
|
226
240
|
* Who may open `/mcp/<name>`: principal ids in the namespace of the one
|
|
227
241
|
* interactive auth provider, `{ namespace, id }`, or `"agent:<name>"` for a
|
|
228
|
-
* declared agent.
|
|
229
|
-
* `tools` to these members and
|
|
242
|
+
* declared agent. All members must be people or all must be agents. Required
|
|
243
|
+
* and non-empty; the pool serves exactly its `tools` to these members and
|
|
244
|
+
* the same 404 to everyone else.
|
|
230
245
|
*/
|
|
231
246
|
members: required(opaque({ check: assertMemberList }), (path) => `${path} is required: list who may open this pool, as principal ids, { namespace, id }, or "agent:<name>".`),
|
|
232
247
|
/**
|
|
@@ -234,8 +249,8 @@ const pool = {
|
|
|
234
249
|
* connector whose `subject` is a mapping function. Humans always act as
|
|
235
250
|
* themselves through that mapping, and a fixed `subject` address applies to
|
|
236
251
|
* everyone. Without an owner, a mapping-subject Workspace connector is
|
|
237
|
-
* absent for this pool's agents, as a personal connector is
|
|
238
|
-
* pool
|
|
252
|
+
* absent for this pool's agents, as a personal connector is. People use a
|
|
253
|
+
* separate pool and always act as themselves.
|
|
239
254
|
*/
|
|
240
255
|
owner: opaque({
|
|
241
256
|
check: (value, path) => {
|
|
@@ -492,48 +507,6 @@ const connectaConfig = {
|
|
|
492
507
|
readOnly: required(opaque({ check: assertFlag }), (path) => READ_ONLY_REQUIRED(path)),
|
|
493
508
|
};
|
|
494
509
|
const CONFIG = object(connectaConfig);
|
|
495
|
-
/** Pause-only options #672 removed with `resume_execution`. */
|
|
496
|
-
const RETIRED_PAUSE_OPTIONS = ["ConnectaConfig.execute.resumableWrites", "ConnectaConfig.execute.pausedRunTtlSeconds"];
|
|
497
|
-
/** Access options pools replaced (#837, decisions/0006). */
|
|
498
|
-
const isRetiredPoolGrant = (path) => /^ConnectaConfig\.pools\.[^.]+\.grant$/.test(path);
|
|
499
|
-
/** Management resolvers maintainers and pool membership replaced (#837, decisions/0006). */
|
|
500
|
-
const RETIRED_IDENTITY = [
|
|
501
|
-
["credentialAdministration", "maintainers administer shared credentials and OAuth grants. List them in maintainers."],
|
|
502
|
-
[
|
|
503
|
-
"personalConnection",
|
|
504
|
-
"pool members connect and disconnect their own personal connectors in their pools, and maintainers their own. Add people to a pool's members.",
|
|
505
|
-
],
|
|
506
|
-
[
|
|
507
|
-
"accessTokenManagement",
|
|
508
|
-
"maintainers issue, rotate, and revoke agent tokens from an interactive session. List them in maintainers.",
|
|
509
|
-
],
|
|
510
|
-
["activityAccess", "maintainers read activity; activity.readGate can still narrow it. List them in maintainers."],
|
|
511
|
-
];
|
|
512
|
-
/** `trust`, on the deployment or a pool, was replaced by `readOnly` (#820). */
|
|
513
|
-
const isRetiredTrust = (path) => path === "ConnectaConfig.trust" || (path.startsWith("ConnectaConfig.pools.") && path.endsWith(".trust"));
|
|
514
|
-
/**
|
|
515
|
-
* `identity` was removed whole (#837), so the walk reports only the section.
|
|
516
|
-
* Name each key it still holds, read by descriptor without running a getter
|
|
517
|
-
* or echoing a value, so a retired resolver's message names its replacement.
|
|
518
|
-
*/
|
|
519
|
-
function expandRetiredIdentity(config, paths) {
|
|
520
|
-
if (!paths.includes("ConnectaConfig.identity"))
|
|
521
|
-
return paths;
|
|
522
|
-
let keys = [];
|
|
523
|
-
try {
|
|
524
|
-
const descriptor = Reflect.getOwnPropertyDescriptor(config, "identity");
|
|
525
|
-
const value = descriptor && "value" in descriptor ? descriptor.value : undefined;
|
|
526
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
527
|
-
keys = Reflect.ownKeys(value).filter((key) => typeof key === "string");
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
catch {
|
|
531
|
-
keys = [];
|
|
532
|
-
}
|
|
533
|
-
if (keys.length === 0)
|
|
534
|
-
return paths;
|
|
535
|
-
return paths.flatMap((path) => path === "ConnectaConfig.identity" ? keys.map((key) => `ConnectaConfig.identity.${key}`) : [path]);
|
|
536
|
-
}
|
|
537
510
|
function rejectUnknownOptions(paths) {
|
|
538
511
|
if (paths.length === 0)
|
|
539
512
|
return;
|
|
@@ -547,27 +520,6 @@ function rejectUnknownOptions(paths) {
|
|
|
547
520
|
: "") +
|
|
548
521
|
(paths.includes("ConnectaConfig.execute.approval")
|
|
549
522
|
? "\nexecute.approval was removed. The MCP client owns approval; set readOnly: true on the deployment or a pool to refuse writes."
|
|
550
|
-
: "") +
|
|
551
|
-
(paths.some((path) => RETIRED_PAUSE_OPTIONS.includes(path))
|
|
552
|
-
? "\nPrograms no longer pause at writes, so there is nothing to configure: writable endpoints let programs and call_destructive_tool write; readOnly endpoints refuse every write (issue #672). Delete the option."
|
|
553
|
-
: "") +
|
|
554
|
-
(paths.includes("ConnectaConfig.identity.connectorAccess")
|
|
555
|
-
? "\nidentity.connectorAccess was removed (#837): pools are the access model. List who may use plain /mcp in maintainers, and give each pool its tools and members. A guarded { tool, requireReadOnly: true } grant becomes a pool with readOnly: true."
|
|
556
|
-
: "") +
|
|
557
|
-
RETIRED_IDENTITY.filter(([key]) => paths.includes(`ConnectaConfig.identity.${key}`))
|
|
558
|
-
.map(([key, replacement]) => `\nidentity.${key} was removed (#837): ${replacement}`)
|
|
559
|
-
.join("") +
|
|
560
|
-
(paths.some((path) => path === "ConnectaConfig.identity" || path.startsWith("ConnectaConfig.identity."))
|
|
561
|
-
? "\nThe identity section was removed (#837): maintainers and pool members decide access and management. Delete it."
|
|
562
|
-
: "") +
|
|
563
|
-
(paths.includes("ConnectaConfig.grant")
|
|
564
|
-
? "\nThe top-level grant was removed (#837); list the people who may use plain /mcp in maintainers."
|
|
565
|
-
: "") +
|
|
566
|
-
(paths.some(isRetiredPoolGrant)
|
|
567
|
-
? "\nPool grant was removed (#837); list who may open the pool in its members."
|
|
568
|
-
: "") +
|
|
569
|
-
(paths.some(isRetiredTrust)
|
|
570
|
-
? '\ntrust was removed (#820); set readOnly, which every deployment now chooses explicitly. trust: "read-only" becomes readOnly: true, which now also refuses call_destructive_tool. trust: "trusted" becomes readOnly: false. A pool inherits the deployment readOnly unless it sets its own.'
|
|
571
523
|
: ""));
|
|
572
524
|
}
|
|
573
525
|
/** Checks that span fields; each value is already individually valid. */
|
|
@@ -598,7 +550,7 @@ export function resolveConfig(config) {
|
|
|
598
550
|
*/
|
|
599
551
|
export function readConfig(config) {
|
|
600
552
|
const read = readPlain(config, "ConnectaConfig", CONFIG);
|
|
601
|
-
rejectUnknownOptions(
|
|
553
|
+
rejectUnknownOptions(read.unknown);
|
|
602
554
|
const input = read.value;
|
|
603
555
|
const parsed = CONFIG.parse(input, "ConnectaConfig");
|
|
604
556
|
assertCoherent(parsed);
|
|
@@ -648,5 +600,6 @@ export function executeLimits(config) {
|
|
|
648
600
|
hostCallTimeoutMs: config.execute.hostCallTimeoutMs,
|
|
649
601
|
watchdogMs: config.execute.watchdogMs,
|
|
650
602
|
maxWrites: config.execute.maxWrites,
|
|
603
|
+
maxResultChars: config.execute.maxResultChars,
|
|
651
604
|
};
|
|
652
605
|
}
|
|
@@ -9,7 +9,7 @@ export interface ConnectorCaller {
|
|
|
9
9
|
* with or without a subject, a Connecta-issued access token.
|
|
10
10
|
*/
|
|
11
11
|
authenticated: boolean;
|
|
12
|
-
/** The pool
|
|
12
|
+
/** The admitted pool: routed at `/mcp` or selected at `/mcp/<pool>` or `/agent/<pool>`. */
|
|
13
13
|
pool?: string;
|
|
14
14
|
/**
|
|
15
15
|
* That pool's configured `owner`, the Workspace address an agent acts as
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Where one connector's bounded health facts are stored for a registry view:
|
|
2
|
+
// the catalog cache's store and owner partition (principal, subject, caller
|
|
3
|
+
// identity and pool, origin) and the admitted endpoint, with the connector's
|
|
4
|
+
// described configuration and the package version, through the deployment's
|
|
5
|
+
// keyed digest (a described endpoint keeps its path), beside the catalog
|
|
6
|
+
// invalidation generation those facts must match (INV-4, INV-5). Shared by
|
|
7
|
+
// discovery health and the operator page's status observations.
|
|
8
|
+
import { Effect } from "effect";
|
|
9
|
+
import { catalogOwnerPartition } from "./catalog-policy.js";
|
|
10
|
+
import { canonicalJson } from "./canonical-json.js";
|
|
11
|
+
import { secretDigest } from "./storage-digest.js";
|
|
12
|
+
import { responseCacheKeys } from "./storage/keys.js";
|
|
13
|
+
import { CONNECTA_VERSION } from "./version.js";
|
|
14
|
+
// A connector's described configuration, serialized once per connector object.
|
|
15
|
+
// A change `describe()` cannot see, such as a static header rotated in
|
|
16
|
+
// deployment code, takes effect on redeploy and leaves an earlier record to
|
|
17
|
+
// lapse within its TTL.
|
|
18
|
+
const configs = new WeakMap();
|
|
19
|
+
function describedConfig(connector) {
|
|
20
|
+
let config = configs.get(connector);
|
|
21
|
+
if (config === undefined) {
|
|
22
|
+
let described = null;
|
|
23
|
+
try {
|
|
24
|
+
described = connector.describe?.() ?? null;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
described = null;
|
|
28
|
+
}
|
|
29
|
+
config = canonicalJson([CONNECTA_VERSION, connector.kind ?? null, connector.authScope ?? null, described]);
|
|
30
|
+
configs.set(connector, config);
|
|
31
|
+
}
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
34
|
+
// The endpoint a request was admitted at (plain `/mcp`, a pool, an agent
|
|
35
|
+
// path). The same person and pool can reach one connector through several,
|
|
36
|
+
// and each keeps its own records. The operator page binds none.
|
|
37
|
+
const endpoints = new WeakMap();
|
|
38
|
+
/** Bind the admitted endpoint to a request scope, before any discovery runs in it. */
|
|
39
|
+
export function bindDiscoveryEndpoint(requestScope, endpoint) {
|
|
40
|
+
endpoints.set(requestScope, endpoint);
|
|
41
|
+
}
|
|
42
|
+
/** Where one connector's records sit in this view's owner partition. */
|
|
43
|
+
export function healthAddress(registry, id, baseUrl, requestScope) {
|
|
44
|
+
return Effect.suspend(() => {
|
|
45
|
+
const connector = registry.getConnector(id);
|
|
46
|
+
if (!connector)
|
|
47
|
+
return Effect.succeed(undefined);
|
|
48
|
+
const ctx = registry.contextFor(id, baseUrl, requestScope);
|
|
49
|
+
const { storage, partition } = catalogOwnerPartition(ctx);
|
|
50
|
+
if (!storage)
|
|
51
|
+
return Effect.succeed(undefined);
|
|
52
|
+
return Effect.promise(async () => ({
|
|
53
|
+
storage,
|
|
54
|
+
digest: await secretDigest(ctx, JSON.stringify([partition, endpoints.get(requestScope) ?? null, describedConfig(connector)])),
|
|
55
|
+
generationKey: responseCacheKeys.generation(id),
|
|
56
|
+
logger: ctx.logger,
|
|
57
|
+
}));
|
|
58
|
+
}).pipe(Effect.catchCause(() => Effect.succeed(undefined)));
|
|
59
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// The operator page's stored connector status (#1173 item 2.3).
|
|
2
|
+
//
|
|
3
|
+
// A live detail read records what it saw, one observation per connector and
|
|
4
|
+
// viewer partition, beside discovery health in the connector-health family and
|
|
5
|
+
// under the same owner partition and catalog generation (INV-4, INV-5). The
|
|
6
|
+
// detail route can then answer at once from storage, labelled with its age,
|
|
7
|
+
// and the browser asks for a live read separately. An observation holds only
|
|
8
|
+
// a state, a problem code, a tool count and its time (INV-6); the tools stay
|
|
9
|
+
// in the complete catalog cache (INV-8). It is never deferred work: a live
|
|
10
|
+
// read writes it before its response, and nothing refreshes it afterwards.
|
|
11
|
+
import { Effect } from "effect";
|
|
12
|
+
import { healthAddress } from "./connector-health.js";
|
|
13
|
+
import { failureRecord, logFailure } from "./operator-record.js";
|
|
14
|
+
import { causeError } from "./runtime/run.js";
|
|
15
|
+
import { Storage } from "./runtime/services.js";
|
|
16
|
+
import { requestReads } from "./runtime/request-scope.js";
|
|
17
|
+
import { storageGet, storageGetMany, storageSet } from "./runtime/storage.js";
|
|
18
|
+
import { connectorHealthKeys } from "./storage/keys.js";
|
|
19
|
+
/** How long the operator page shows a stored status before only a live read will do. */
|
|
20
|
+
const STATUS_OBSERVATION_TTL_MS = 120_000;
|
|
21
|
+
const STATUS_STATES = new Set([
|
|
22
|
+
"ok",
|
|
23
|
+
"per_caller",
|
|
24
|
+
"auth_required",
|
|
25
|
+
"credential_required",
|
|
26
|
+
"error",
|
|
27
|
+
]);
|
|
28
|
+
const STATUS_PROBLEMS = new Set([
|
|
29
|
+
"connector_unavailable",
|
|
30
|
+
"connector_timeout",
|
|
31
|
+
"oauth_required",
|
|
32
|
+
"credential_required",
|
|
33
|
+
"auth_required",
|
|
34
|
+
"caller_refused",
|
|
35
|
+
"credential_mismatch",
|
|
36
|
+
"catalog_failed",
|
|
37
|
+
]);
|
|
38
|
+
/**
|
|
39
|
+
* This viewer's current observation of one connector, and the slot a live
|
|
40
|
+
* read writes its own to when there is none. A record from another generation, past its TTL or
|
|
41
|
+
* of an unknown shape is absent. A storage failure is absent too.
|
|
42
|
+
*/
|
|
43
|
+
export function readStatusObservation(registry, id, baseUrl, requestScope) {
|
|
44
|
+
return Effect.gen(function* () {
|
|
45
|
+
const address = yield* healthAddress(registry, id, baseUrl, requestScope);
|
|
46
|
+
if (!address)
|
|
47
|
+
return {};
|
|
48
|
+
const key = connectorHealthKeys.status(id, address.digest);
|
|
49
|
+
const values = yield* storageGetMany([key, address.generationKey]).pipe(Effect.provideService(Storage, address.storage), Effect.catchCause((cause) => Effect.sync(() => {
|
|
50
|
+
logFailure(address.logger, "discovery health read failed", failureRecord({}, causeError(cause)));
|
|
51
|
+
return undefined;
|
|
52
|
+
})));
|
|
53
|
+
if (!values)
|
|
54
|
+
return {};
|
|
55
|
+
const slot = { storage: address.storage, key, logger: address.logger, generation: values[1] ?? null };
|
|
56
|
+
try {
|
|
57
|
+
const value = JSON.parse(values[0] ?? "null");
|
|
58
|
+
if (value?.v === 1 &&
|
|
59
|
+
value.generation === slot.generation &&
|
|
60
|
+
typeof value.state === "string" &&
|
|
61
|
+
STATUS_STATES.has(value.state) &&
|
|
62
|
+
(value.problem === undefined || STATUS_PROBLEMS.has(value.problem)) &&
|
|
63
|
+
Number.isSafeInteger(value.toolCount) &&
|
|
64
|
+
value.toolCount >= 0 &&
|
|
65
|
+
Number.isFinite(value.observedAt) &&
|
|
66
|
+
Date.now() - value.observedAt < STATUS_OBSERVATION_TTL_MS)
|
|
67
|
+
return {
|
|
68
|
+
slot,
|
|
69
|
+
observation: {
|
|
70
|
+
state: value.state,
|
|
71
|
+
...(value.problem ? { problem: value.problem } : {}),
|
|
72
|
+
toolCount: value.toolCount,
|
|
73
|
+
observedAt: value.observedAt,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
/* An unreadable record is absent. */
|
|
79
|
+
}
|
|
80
|
+
return { slot };
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Where a live read will record its observation, without reading the stored
|
|
85
|
+
* one. The generation is read through the request's read memo: a live read's
|
|
86
|
+
* catalog listing locates its cache entry by that same key, so the listing
|
|
87
|
+
* reuses this read instead of paying another round trip. A generation is never
|
|
88
|
+
* reused once replaced, so a memoized value can only lapse an observation
|
|
89
|
+
* early, never keep one past an invalidation that preceded this request.
|
|
90
|
+
*/
|
|
91
|
+
export function prepareStatusSlot(registry, id, baseUrl, requestScope) {
|
|
92
|
+
return Effect.gen(function* () {
|
|
93
|
+
const address = yield* healthAddress(registry, id, baseUrl, requestScope);
|
|
94
|
+
if (!address)
|
|
95
|
+
return undefined;
|
|
96
|
+
const generation = yield* requestReads(requestScope)
|
|
97
|
+
.read(address.storage, address.generationKey, storageGet(address.generationKey))
|
|
98
|
+
.pipe(Effect.provideService(Storage, address.storage), Effect.catchCause((cause) => Effect.sync(() => {
|
|
99
|
+
logFailure(address.logger, "discovery health read failed", failureRecord({}, causeError(cause)));
|
|
100
|
+
return undefined;
|
|
101
|
+
})));
|
|
102
|
+
if (generation === undefined)
|
|
103
|
+
return undefined;
|
|
104
|
+
return {
|
|
105
|
+
storage: address.storage,
|
|
106
|
+
key: connectorHealthKeys.status(id, address.digest),
|
|
107
|
+
logger: address.logger,
|
|
108
|
+
generation,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** Record what a live read saw. A failed write is logged; the page already has its answer. */
|
|
113
|
+
export function storeStatusObservation(slot, observation) {
|
|
114
|
+
return storageSet(slot.key, JSON.stringify({ v: 1, ...observation, generation: slot.generation }), {
|
|
115
|
+
expiresAtMs: Math.ceil(observation.observedAt + STATUS_OBSERVATION_TTL_MS),
|
|
116
|
+
}).pipe(Effect.provideService(Storage, slot.storage), Effect.catchCause((cause) => Effect.sync(() => logFailure(slot.logger, "discovery health write failed", failureRecord({}, causeError(cause))))));
|
|
117
|
+
}
|
|
@@ -204,8 +204,8 @@ export declare const API_OPTIONS: import("../config-schema.js").Field<unknown, u
|
|
|
204
204
|
credential: import("../config-schema.js").Field<unknown, unknown, true> & {
|
|
205
205
|
readonly shape: {
|
|
206
206
|
fields: import("../config-schema.js").Field<unknown[], readonly unknown[] | undefined, true>;
|
|
207
|
-
label: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
208
207
|
description: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
208
|
+
label: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
209
209
|
placeholder: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
210
210
|
};
|
|
211
211
|
};
|
|
@@ -228,9 +228,9 @@ export declare const API_OPTIONS: import("../config-schema.js").Field<unknown, u
|
|
|
228
228
|
};
|
|
229
229
|
tools: import("../config-schema.js").Field<unknown[], readonly unknown[] | undefined, true>;
|
|
230
230
|
title: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
231
|
+
description: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
231
232
|
authScope: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
232
233
|
maxResultBytes: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
233
|
-
description: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
234
234
|
testCredential: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
235
235
|
testCredentials: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
236
236
|
validateArgs: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
@@ -244,6 +244,11 @@ export declare const API_OPTIONS: import("../config-schema.js").Field<unknown, u
|
|
|
244
244
|
*/
|
|
245
245
|
export interface ApiOAuthHooks {
|
|
246
246
|
status(ctx: ConnectorContext): Promise<ConnectorStatus>;
|
|
247
|
+
/**
|
|
248
|
+
* `status` for a call about to enter a handler, which also renews a token
|
|
249
|
+
* within a minute of its stored expiry. A failed renewal rejects.
|
|
250
|
+
*/
|
|
251
|
+
prepareCall(ctx: ConnectorContext): Promise<ConnectorStatus>;
|
|
247
252
|
startAuth(ctx: ConnectorContext, opts?: {
|
|
248
253
|
force?: boolean;
|
|
249
254
|
}): Promise<ConnectorStatus>;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { declaresRouteDescriptions, routePhrases } from "../agent-routes.js";
|
|
2
|
+
import { wrapViewCredentials } from "../runtime/call.js";
|
|
1
3
|
import { storedAuthObservation, withStoredAuth } from "../auth/stored-observation.js";
|
|
2
4
|
import { Effect, Exit } from "effect";
|
|
3
5
|
import { registerConnectorVocabulary } from "../operation-vocabulary.js";
|
|
@@ -13,7 +15,7 @@ import { describedEndpoint, describedOrigin, describedTools } from "../described
|
|
|
13
15
|
import { READ_ONLY, CALL_ADMISSION, CREDENTIAL, USAGE_GUIDE } from "./option-shapes.js";
|
|
14
16
|
import { assertStaticToolNames } from "../tool-name.js";
|
|
15
17
|
import { registerInvocationAuth } from "../invocation-auth.js";
|
|
16
|
-
import { redactCatalog, redactSentSecrets, sentSecretsFetch
|
|
18
|
+
import { redactCatalog, redactSentSecrets, sentSecretsFetch } from "../sent-secrets.js";
|
|
17
19
|
import { callClassification } from "../call-classification.js";
|
|
18
20
|
import { classifyTool } from "../tool-safety.js";
|
|
19
21
|
import { closeProviderCall, enterProviderBoundary, inLogicalCall, knownProviderFailure, nestedProviderRequests, providerBoundary, providerCallContext, providerFetch, providerRequests, tracksRequests, } from "../provider-call.js";
|
|
@@ -24,7 +26,7 @@ export function defined(value) {
|
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
26
28
|
* Three things a hand-written surface is refused for at construction rather
|
|
27
|
-
* than in production
|
|
29
|
+
* than in production: no description (discovery has nothing to route on
|
|
28
30
|
* and a guess costs a call), no explicit `readOnlyHint` (connecta never infers
|
|
29
31
|
* the safety class, so an unclassified tool is a deployment bug), and an
|
|
30
32
|
* `inputSchema` the validator cannot compile (declaring one is optional;
|
|
@@ -173,7 +175,7 @@ export function apiConnector(id, opts, oauth) {
|
|
|
173
175
|
callTool: (name, args, ctx, options) => runEdge(core(name, args, ctx, options)),
|
|
174
176
|
};
|
|
175
177
|
const core = (name, args, ctx) => Effect.gen(function* () {
|
|
176
|
-
|
|
178
|
+
wrapViewCredentials(ctx);
|
|
177
179
|
enterProviderBoundary(ctx, connector);
|
|
178
180
|
const tool = byName.get(name);
|
|
179
181
|
// Refusals before the handler runs are known failures: nothing was sent.
|
|
@@ -295,13 +297,15 @@ export function apiConnector(id, opts, oauth) {
|
|
|
295
297
|
// This boundary's identity in a logical call, fixed now: this object with
|
|
296
298
|
// this function, never whatever the property or a copy holds by then.
|
|
297
299
|
providerBoundary(connector);
|
|
300
|
+
// Its catalog is authored here, so declared route descriptions follow each request's surface.
|
|
301
|
+
declaresRouteDescriptions(connector);
|
|
298
302
|
registerCallEntry(connector, core);
|
|
299
303
|
if (oauth)
|
|
300
304
|
registerInvocationAuth(connector, async (ctx) => {
|
|
301
|
-
// The
|
|
302
|
-
//
|
|
303
|
-
if ((await settleOnClose(ctx, () => oauth.
|
|
304
|
-
throw new ConnectorCallError("downstream_oauth_required", `Connector "${id}" requires authorization.
|
|
305
|
+
// The stored grant, as status reads it. Only a token about to expire is
|
|
306
|
+
// renewed, before any handler runs; nothing is sent to the API.
|
|
307
|
+
if ((await settleOnClose(ctx, () => oauth.prepareCall(ctx))).state === "auth_required") {
|
|
308
|
+
throw new ConnectorCallError("downstream_oauth_required", `Connector "${id}" requires authorization. ${routePhrases(ctx).authorizeCall()}`);
|
|
305
309
|
}
|
|
306
310
|
});
|
|
307
311
|
registerConnectorVocabulary(connector, opts.tools);
|