@zackbart/connecta 0.24.3 → 0.25.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/AGENTS.md +58 -30
- package/CHANGELOG.md +317 -1
- package/README.md +5 -6
- package/dist/advertised-schema.js +53 -0
- package/dist/auth/downstream-oauth.js +490 -106
- package/dist/branding.d.ts +31 -2
- package/dist/branding.js +116 -8
- package/dist/call-admission.js +139 -113
- package/dist/catalog-service.js +141 -105
- package/dist/catalog.js +258 -33
- package/dist/concurrency.js +24 -23
- package/dist/connector-scope.js +6 -37
- package/dist/connectors/api.d.ts +3 -3
- package/dist/connectors/api.js +10 -2
- package/dist/connectors/guarded-fetch.js +34 -4
- package/dist/connectors/remote-mcp.d.ts +6 -5
- package/dist/connectors/remote-mcp.js +325 -245
- package/dist/credential-contract.d.ts +9 -0
- package/dist/credential-rules.js +2 -2
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +45 -3
- package/dist/errors.d.ts +11 -3
- package/dist/errors.js +2 -1
- package/dist/execute.js +363 -257
- package/dist/executor-admission.js +67 -50
- package/dist/executors/quickjs-runtime.js +4 -2
- package/dist/executors/quickjs.js +251 -210
- package/dist/fix-prompt.js +37 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +63 -21
- package/dist/invocation.js +324 -285
- package/dist/meta-tools.js +304 -114
- package/dist/oauth-callback-outcome.js +94 -0
- package/dist/oauth-sealing.js +29 -0
- package/dist/operator-ui/fix-prompts.js +87 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +46 -1
- package/dist/operator-ui/setup-commands.js +53 -0
- package/dist/operator-ui/view.js +109 -2
- package/dist/program-source.js +205 -0
- package/dist/providers/cloudflare.d.ts +14 -2
- package/dist/providers/cloudflare.js +107 -16
- package/dist/providers/linear.d.ts +26 -4
- package/dist/providers/linear.js +19 -4
- package/dist/providers/mixpanel.d.ts +16 -3
- package/dist/providers/mixpanel.js +13 -2
- package/dist/providers/notion.d.ts +8 -1
- package/dist/providers/notion.js +85 -12
- package/dist/providers/revenuecat.d.ts +30 -4
- package/dist/providers/revenuecat.js +70 -5
- package/dist/providers/stripe.d.ts +10 -3
- package/dist/providers/stripe.js +30 -4
- package/dist/providers/vercel.js +12 -2
- package/dist/registry.d.ts +13 -5
- package/dist/registry.js +317 -258
- package/dist/routes/activity.js +120 -137
- package/dist/routes/credentials.js +174 -163
- package/dist/routes/mcp.js +129 -70
- package/dist/routes/oauth-management.js +60 -71
- package/dist/routes/oauth.js +44 -16
- package/dist/routes/operator.js +82 -0
- package/dist/routes/shared.d.ts +2 -0
- package/dist/routes/ui-data.js +210 -0
- package/dist/routes/ui.js +64 -32
- package/dist/runtime/admission.js +43 -0
- package/dist/runtime/call-admission.js +67 -0
- package/dist/runtime/connector-scope.js +72 -0
- package/dist/runtime/run.js +174 -0
- package/dist/runtime/services.js +130 -0
- package/dist/runtime/storage.js +66 -0
- package/dist/server.js +111 -89
- package/dist/skills.js +1 -1
- package/dist/storage/file.js +39 -2
- package/dist/storage/memory.js +28 -16
- package/dist/timeout.js +17 -47
- package/dist/types.d.ts +64 -7
- package/dist/ui.d.ts +16 -2
- package/dist/ui.js +68 -176
- package/dist/url-safety.js +157 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +368 -182
- package/documentation/auth.md +277 -176
- package/documentation/code-mode.md +479 -329
- package/documentation/meta-tools.md +494 -417
- package/ethos.md +44 -47
- package/examples/worker/AGENTS.md +2 -1
- package/examples/worker/README.md +62 -14
- package/examples/worker/src/d1-storage.ts +116 -0
- package/examples/worker/src/index.ts +18 -21
- package/examples/worker/wrangler.jsonc +16 -4
- package/package.json +11 -4
- package/templates/node/.env.example +3 -3
- package/templates/node/AGENTS.md +5 -4
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +23 -22
- package/dist/auth/downstream-oauth.d.ts +0 -200
- package/dist/catalog-fingerprint.d.ts +0 -19
- package/dist/catalog-limits.d.ts +0 -9
- package/dist/catalog-service.d.ts +0 -263
- package/dist/catalog.d.ts +0 -87
- package/dist/concurrency.d.ts +0 -3
- package/dist/connectors/guarded-fetch.d.ts +0 -92
- package/dist/credential-rules.d.ts +0 -85
- package/dist/execute.d.ts +0 -154
- package/dist/executor-result.d.ts +0 -10
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -67
- package/dist/favicon.d.ts +0 -1
- package/dist/identity.d.ts +0 -4
- package/dist/invocation.d.ts +0 -74
- package/dist/mcp-result.d.ts +0 -8
- package/dist/meta-tools.d.ts +0 -129
- package/dist/operator-ui/generated.d.ts +0 -2
- package/dist/operator-ui/view.d.ts +0 -151
- package/dist/result-shapes.d.ts +0 -13
- package/dist/retired-toolkits.d.ts +0 -2
- package/dist/routes/activity.d.ts +0 -2
- package/dist/routes/credentials.d.ts +0 -2
- package/dist/routes/mcp.d.ts +0 -10
- package/dist/routes/oauth-management.d.ts +0 -2
- package/dist/routes/oauth.d.ts +0 -2
- package/dist/routes/ui.d.ts +0 -2
- package/dist/server.d.ts +0 -9
- package/dist/skills.d.ts +0 -44
- package/dist/timeout.d.ts +0 -21
- package/dist/tool-safety.d.ts +0 -9
- package/documentation/call-admission.md +0 -158
- package/documentation/cloudflare.md +0 -471
- package/documentation/connector-guides.md +0 -176
- package/documentation/connectors.md +0 -431
- package/documentation/linear.md +0 -193
- package/documentation/mixpanel.md +0 -160
- package/documentation/notion.md +0 -308
- package/documentation/operations.md +0 -359
- package/documentation/operator-ui.md +0 -135
- package/documentation/optional-modules-upgrade.md +0 -243
- package/documentation/provider-conventions.md +0 -729
- package/documentation/request-admission.md +0 -204
- package/documentation/revenuecat.md +0 -305
- package/documentation/storage-and-credentials.md +0 -254
- package/documentation/stripe.md +0 -262
- package/documentation/upgrading.md +0 -768
- package/documentation/vercel.md +0 -241
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import type { CatalogDriftReport } from "../types.js";
|
|
2
|
-
import type { CredentialManagementCapability, UiData } from "./model.js";
|
|
3
|
-
/**
|
|
4
|
-
* Everything the operator app knows, and every rule for changing it, with no
|
|
5
|
-
* DOM in sight. The components render this and nothing else, so the questions
|
|
6
|
-
* that matter — what a page shows while it loads, what an identity change
|
|
7
|
-
* erases, whether a value may become an href — are answered by functions a test
|
|
8
|
-
* can call directly rather than by reading rendered markup back out of a
|
|
9
|
-
* browser.
|
|
10
|
-
*/
|
|
11
|
-
export type OperatorPage = "connections" | "activity";
|
|
12
|
-
export declare const OPERATOR_PAGES: readonly OperatorPage[];
|
|
13
|
-
export declare const PAGE_META: Readonly<Record<OperatorPage, {
|
|
14
|
-
path: string;
|
|
15
|
-
label: string;
|
|
16
|
-
}>>;
|
|
17
|
-
export declare function pageForPath(path: string): OperatorPage;
|
|
18
|
-
export interface UiActivityActor {
|
|
19
|
-
kind?: string;
|
|
20
|
-
id?: string;
|
|
21
|
-
namespace?: string;
|
|
22
|
-
label?: string;
|
|
23
|
-
}
|
|
24
|
-
export interface UiActivityEvent {
|
|
25
|
-
occurredAt: string;
|
|
26
|
-
actor?: UiActivityActor;
|
|
27
|
-
connectorId: string;
|
|
28
|
-
toolName: string;
|
|
29
|
-
address: string;
|
|
30
|
-
source: string;
|
|
31
|
-
outcome: string;
|
|
32
|
-
durationMs: number;
|
|
33
|
-
attempts: number;
|
|
34
|
-
errorCode?: string;
|
|
35
|
-
friction?: string;
|
|
36
|
-
}
|
|
37
|
-
/** A message with the tone that decides its live region: status or alert. */
|
|
38
|
-
export interface Notice {
|
|
39
|
-
message: string;
|
|
40
|
-
tone: "info" | "error";
|
|
41
|
-
}
|
|
42
|
-
export declare function info(message: string): Notice;
|
|
43
|
-
export declare function failure(message: string): Notice;
|
|
44
|
-
/**
|
|
45
|
-
* A remote collection's four states, named once so every page spells them the
|
|
46
|
-
* same way. `idle` is "nobody has asked yet" and is what makes a re-entered
|
|
47
|
-
* page fetch again after an identity change.
|
|
48
|
-
*/
|
|
49
|
-
type LoadPhase = "idle" | "loading" | "ready" | "error";
|
|
50
|
-
export interface OperatorState {
|
|
51
|
-
page: OperatorPage;
|
|
52
|
-
/**
|
|
53
|
-
* Bumped by every identity change. Async work captures it before awaiting and
|
|
54
|
-
* throws its result away if the operator is no longer the one who asked —
|
|
55
|
-
* the fence that keeps one identity's data off another identity's screen.
|
|
56
|
-
*/
|
|
57
|
-
generation: number;
|
|
58
|
-
/** `loading` until the first /ui/data answer decides gated or ready. */
|
|
59
|
-
session: "loading" | "gated" | "ready";
|
|
60
|
-
gate: Notice | null;
|
|
61
|
-
/** True while a signed-in operator's /ui/data is in flight. */
|
|
62
|
-
refreshing: boolean;
|
|
63
|
-
/**
|
|
64
|
-
* Element id the next render should focus. A rebuilt page has no stable node
|
|
65
|
-
* to hand focus to from an event handler, so the request travels through
|
|
66
|
-
* state and the shell spends it once the new markup exists.
|
|
67
|
-
*/
|
|
68
|
-
pendingFocus: string | null;
|
|
69
|
-
data: UiData | null;
|
|
70
|
-
connectorFilter: string;
|
|
71
|
-
oauthNotice: Notice | null;
|
|
72
|
-
/** Connector id whose OAuth mutation is in flight. */
|
|
73
|
-
oauthBusy: string | null;
|
|
74
|
-
credentialNotice: Notice | null;
|
|
75
|
-
/** Connector id whose credential form is open. */
|
|
76
|
-
credentialEditing: string | null;
|
|
77
|
-
/** Connector id whose credential mutation is in flight. */
|
|
78
|
-
credentialBusy: string | null;
|
|
79
|
-
activityPhase: LoadPhase;
|
|
80
|
-
activityNotice: Notice | null;
|
|
81
|
-
activityEvents: UiActivityEvent[];
|
|
82
|
-
activityCursor: string | null;
|
|
83
|
-
activitySearch: string;
|
|
84
|
-
}
|
|
85
|
-
export declare function initialState(page: OperatorPage): OperatorState;
|
|
86
|
-
/**
|
|
87
|
-
* Drop to the gate and forget the previous operator. Bumping the generation is
|
|
88
|
-
* what makes the drop stick: work already in flight for the old identity
|
|
89
|
-
* resolves into a state that no longer accepts it.
|
|
90
|
-
*/
|
|
91
|
-
export declare function resetIdentity(state: OperatorState, gate?: Notice | null): OperatorState;
|
|
92
|
-
/**
|
|
93
|
-
* Leaving a page closes what should not survive it: a one-time secret, a half
|
|
94
|
-
* typed credential, and the notices that answered the page just left.
|
|
95
|
-
*/
|
|
96
|
-
export declare function withPage(state: OperatorState, page: OperatorPage): OperatorState;
|
|
97
|
-
export declare function credentialUnavailableCopy(capability?: CredentialManagementCapability): string;
|
|
98
|
-
export declare function connectorStatusLabel(status: string): string;
|
|
99
|
-
export declare function toolCountLabel(count: number): string;
|
|
100
|
-
/**
|
|
101
|
-
* Hosted-provider catalog drift, as an operator reads it
|
|
102
|
-
* ([#343](https://github.com/zackbart/connecta/issues/343)).
|
|
103
|
-
*
|
|
104
|
-
* - `unavailable` — no refresh has been observed in this runtime, so there is
|
|
105
|
-
* nothing to report. Deliberately not `clean`: "we have not looked" and "we
|
|
106
|
-
* looked and it matches" are different answers, and only one of them is a
|
|
107
|
-
* reason to stop worrying.
|
|
108
|
-
* - `clean` — a refresh happened and every category counted zero.
|
|
109
|
-
* - `warning` — a refresh happened and at least one category did not.
|
|
110
|
-
*/
|
|
111
|
-
export type DriftState = "clean" | "warning" | "unavailable";
|
|
112
|
-
export declare function driftTotal(drift?: CatalogDriftReport): number;
|
|
113
|
-
export declare function driftState(drift?: CatalogDriftReport): DriftState;
|
|
114
|
-
/** Every category with its count, so a clean report still shows its zeros. */
|
|
115
|
-
export declare function driftCounts(drift?: CatalogDriftReport): Array<{
|
|
116
|
-
key: string;
|
|
117
|
-
label: string;
|
|
118
|
-
count: number;
|
|
119
|
-
}>;
|
|
120
|
-
/** One line naming the state and when it was observed. Never what drifted. */
|
|
121
|
-
export declare function driftSummary(drift?: CatalogDriftReport): string;
|
|
122
|
-
/**
|
|
123
|
-
* Only http/https may become a clickable href — the browser half of the gate
|
|
124
|
-
* `src/ui.ts` applies before an authorizationUrl is ever serialized. A hostile
|
|
125
|
-
* downstream that gets a `javascript:` URL past one of them still meets the
|
|
126
|
-
* other, and the caller renders inert text instead of a link.
|
|
127
|
-
*/
|
|
128
|
-
export declare function safeHttpHref(url: string | undefined): string | null;
|
|
129
|
-
/** Locale timestamp, or "" for anything that is not a readable date. */
|
|
130
|
-
export declare function formatDate(value?: string): string;
|
|
131
|
-
export declare function actorLabel(actor?: UiActivityActor): string;
|
|
132
|
-
/**
|
|
133
|
-
* The stable id behind a friendly label, shown only when a label or namespace
|
|
134
|
-
* could otherwise make two different people look like one.
|
|
135
|
-
*/
|
|
136
|
-
export declare function actorStableId(actor?: UiActivityActor): string | null;
|
|
137
|
-
export declare function filterActivity(events: UiActivityEvent[], query: string): UiActivityEvent[];
|
|
138
|
-
/** Counts only. What the deployment ran, never what it sent or received. */
|
|
139
|
-
export declare function activitySummary(events: UiActivityEvent[]): string;
|
|
140
|
-
export declare function activityOutcomeClass(outcome: string): string;
|
|
141
|
-
/** The one-line detail under an address: source, retries, and friction. */
|
|
142
|
-
export declare function activityDetail(event: UiActivityEvent): string;
|
|
143
|
-
export declare function credentialStateLabel(credential: {
|
|
144
|
-
configured: boolean;
|
|
145
|
-
fields?: unknown[];
|
|
146
|
-
lastFour?: string;
|
|
147
|
-
updatedAt?: string;
|
|
148
|
-
}): string;
|
|
149
|
-
/** Gate copy for each browser-auth shape, so the sign-in state is never a blank page. */
|
|
150
|
-
export declare function gateCopy(kind: string, signedIn: boolean): string;
|
|
151
|
-
export {};
|
package/dist/result-shapes.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { JsonSchema, ToolDef } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Passive, process-local output-shape learning. Provider declarations always
|
|
4
|
-
* win; observations are an open, optional-field routing aid and never a
|
|
5
|
-
* replacement contract.
|
|
6
|
-
*/
|
|
7
|
-
export declare class ObservedOutputSchemas {
|
|
8
|
-
private readonly entries;
|
|
9
|
-
private cacheKey;
|
|
10
|
-
private set;
|
|
11
|
-
get(connectorId: string, definition: ToolDef): JsonSchema | undefined;
|
|
12
|
-
observe(connectorId: string, definition: ToolDef, value: unknown): void;
|
|
13
|
-
}
|
package/dist/routes/mcp.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type RouteContext, type ServerOptions } from "./shared.js";
|
|
2
|
-
export declare const MCP_CORS_HEADERS: {
|
|
3
|
-
"Access-Control-Allow-Origin": string;
|
|
4
|
-
"Access-Control-Allow-Methods": string;
|
|
5
|
-
"Access-Control-Allow-Headers": string;
|
|
6
|
-
};
|
|
7
|
-
export declare function createMcpRoute(opts: ServerOptions): {
|
|
8
|
-
handle(context: RouteContext): Promise<Response | null>;
|
|
9
|
-
rejectOrigin(request: Request): Response | null;
|
|
10
|
-
};
|
package/dist/routes/oauth.d.ts
DELETED
package/dist/routes/ui.d.ts
DELETED
package/dist/server.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type RuntimeExecutionContext, type ServerOptions } from "./routes/shared.js";
|
|
2
|
-
export type { ServerOptions } from "./routes/shared.js";
|
|
3
|
-
/**
|
|
4
|
-
* Build the Web-standard fetch handler.
|
|
5
|
-
*
|
|
6
|
-
* Route ordering is the contract: private mutation routes precede wildcard
|
|
7
|
-
* OPTIONS, and the security wrapper is applied to every response, including 404s.
|
|
8
|
-
*/
|
|
9
|
-
export declare function createFetchHandler(opts: ServerOptions): (request: Request, runtimeContext?: RuntimeExecutionContext) => Promise<Response>;
|
package/dist/skills.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { Connector } from "./types.js";
|
|
2
|
-
export declare const CONNECTA_INSTRUCTIONS = "Choose a route before discovery. A known-address read needs only call_tool. Unknown-address read-only work starts with execute_code to discover, call, and return the answer; use the same route for reduction, multiple or dependent calls, loops, joins, or branches. Keep discovery and calls together when schemas suffice; do not return catalog matches alone. Inspect unfamiliar result shapes with a small sample before proceeding. Only readOnlyHint: true tools run there. Keep catalog inspection and unannotated, write-capable, or destructive work top level: search_tools then call_destructive_tool when a call is needed. After auth_required use authorize_connector. After a truncated direct result use get_result. Guidance is on demand: fetch skills({ name: \"usage\" }) only when these instructions and the tool description are insufficient or a run needs repair.";
|
|
3
|
-
/** Shared Connecta routing guidance, byte-identical across deployments. */
|
|
4
|
-
export declare const USAGE_SKILL: string;
|
|
5
|
-
/** The always-loaded MCP `instructions` string. */
|
|
6
|
-
export declare function instructionsFor(): string;
|
|
7
|
-
/** True when at least one of `connectors` carries a usage guide. */
|
|
8
|
-
export declare function hasConnectorGuides(connectors: readonly Connector[]): boolean;
|
|
9
|
-
/** The skill name that fetches `connector`'s guide. */
|
|
10
|
-
export declare function connectorSkillName(connectorId: string): string;
|
|
11
|
-
/** The connector's guide, or undefined when it declares none (or a blank one). */
|
|
12
|
-
export declare function connectorGuide(connector: Connector): string | undefined;
|
|
13
|
-
/** Discovery budget for one connector-guide summary, including an ellipsis. */
|
|
14
|
-
export declare const GUIDE_SUMMARY_LENGTH = 120;
|
|
15
|
-
/** Normalize authored and derived summaries under one construction contract. */
|
|
16
|
-
export declare function normalizeGuideSummary(summary: string): string | undefined;
|
|
17
|
-
/** Bounded, decision-useful discovery summary for a connector guide. */
|
|
18
|
-
export declare function connectorGuideSummary(connector: Connector): string | undefined;
|
|
19
|
-
/** Whether correct use always depends on conventions outside the tool schema. */
|
|
20
|
-
export declare function connectorGuideRequired(connector: Connector): boolean;
|
|
21
|
-
export interface SkillListing {
|
|
22
|
-
name: string;
|
|
23
|
-
description: string;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Every fetchable skill: the built-in guides plus one entry per connector that
|
|
27
|
-
* carries a usage guide. Derived from the connector list passed in — the single
|
|
28
|
-
* place guide visibility is decided.
|
|
29
|
-
*/
|
|
30
|
-
export declare function listSkills(connectors: readonly Connector[]): SkillListing[];
|
|
31
|
-
export type SkillLookup = {
|
|
32
|
-
found: true;
|
|
33
|
-
content: string;
|
|
34
|
-
} | {
|
|
35
|
-
found: false;
|
|
36
|
-
message: string;
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* Resolve one skill name. Built-in names match exactly; connector guides are
|
|
40
|
-
* reachable only through the `connector:` prefix. Every miss — unknown name,
|
|
41
|
-
* unknown connector, connector without a guide — is an explicit error, never a
|
|
42
|
-
* silent fallback to the generic guide.
|
|
43
|
-
*/
|
|
44
|
-
export declare function resolveSkill(name: string, connectors: readonly Connector[]): SkillLookup;
|
package/dist/timeout.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generous default bound for a single downstream probe/catalog call. High enough
|
|
3
|
-
* to trip only on a pathological hang, not a realistically slow probe.
|
|
4
|
-
*/
|
|
5
|
-
export declare const DEFAULT_PROBE_TIMEOUT_MS = 30000;
|
|
6
|
-
/** A finite, positive integer number of milliseconds, or undefined. */
|
|
7
|
-
export declare function normalizeTimeoutMs(value: number | undefined): number | undefined;
|
|
8
|
-
/** Resolve after `ms`, or false when the caller aborts first. */
|
|
9
|
-
export declare function sleep(ms: number, signal?: AbortSignal): Promise<boolean>;
|
|
10
|
-
export interface DeadlineOptions {
|
|
11
|
-
timeoutMs?: number;
|
|
12
|
-
signal?: AbortSignal;
|
|
13
|
-
timeoutError: Error;
|
|
14
|
-
}
|
|
15
|
-
export declare function withDeadline<T>(operation: (signal: AbortSignal) => Promise<T>, options: DeadlineOptions): Promise<T>;
|
|
16
|
-
/**
|
|
17
|
-
* Give one operation a caller-facing deadline and the matching cancellation
|
|
18
|
-
* signal. The timeout rejects with the stable, labelled error while aborting
|
|
19
|
-
* any in-flight work that honors the signal.
|
|
20
|
-
*/
|
|
21
|
-
export declare function withAbortableTimeout<T>(operation: (signal: AbortSignal) => Promise<T>, ms: number, label: string): Promise<T>;
|
package/dist/tool-safety.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { ToolDef } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* The one fail-closed classification shared by discovery and invocation.
|
|
4
|
-
*
|
|
5
|
-
* A tool is read-only only when it says so without also saying it is
|
|
6
|
-
* destructive. Missing, false, and contradictory annotations all require the
|
|
7
|
-
* approval-visible call path.
|
|
8
|
-
*/
|
|
9
|
-
export declare function isExplicitlyReadOnly(definition: ToolDef): boolean;
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
# Downstream call admission
|
|
2
|
-
|
|
3
|
-
One admitted MCP request can fan out into many downstream calls. A program
|
|
4
|
-
calls tools from loops, from parallel `connecta.call` calls, and from branches the caller
|
|
5
|
-
never saw. The deployment-wide request pool
|
|
6
|
-
([request admission](./request-admission.md)) bounds the envelope; it cannot
|
|
7
|
-
see anything inside it. A provider that publishes "60 requests per minute"
|
|
8
|
-
needs a bound that counts calls, not requests.
|
|
9
|
-
|
|
10
|
-
So a connector may declare `callAdmission`: an optional per-runtime policy
|
|
11
|
-
around its own `Connector.callTool` attempts.
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
const projects = api("projects", {
|
|
15
|
-
description: "Projects — issues and reports",
|
|
16
|
-
callAdmission: {
|
|
17
|
-
// Plural-ready public shape; this release requires exactly one rule.
|
|
18
|
-
rules: [{
|
|
19
|
-
maxConcurrency: 5,
|
|
20
|
-
maxQueueSize: 10, // default 32
|
|
21
|
-
queueTimeoutMs: 2_000, // default 5_000
|
|
22
|
-
retryAfterMs: 1_000, // concurrency-overload hint
|
|
23
|
-
budget: { kind: "rolling-window", maxCalls: 60, windowMs: 60 * 60_000 },
|
|
24
|
-
partitionKey({ toolName, args }) {
|
|
25
|
-
return projectIdFor(toolName, args);
|
|
26
|
-
},
|
|
27
|
-
}],
|
|
28
|
-
maxPartitions: 1_024, // default
|
|
29
|
-
},
|
|
30
|
-
tools: [/* … */],
|
|
31
|
-
});
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
`api()`, `remoteMcp()`, and a hand-written `Connector` all accept it. Maintained
|
|
35
|
-
prebuilt connections set it themselves — but only when the provider documents a
|
|
36
|
-
number, which is convention
|
|
37
|
-
[P12](./provider-conventions.md#p12--declare-an-admission-budget-only-when-the-provider-documents-a-number).
|
|
38
|
-
An invented budget is a throttle with no source.
|
|
39
|
-
|
|
40
|
-
## Policy contract
|
|
41
|
-
|
|
42
|
-
`maxConcurrency` and `budget` are independently optional, but a rule must
|
|
43
|
-
declare at least one — a rule that bounds nothing is a typo, not a policy.
|
|
44
|
-
Queue settings require `maxConcurrency`, because there is nothing to queue
|
|
45
|
-
behind without it, and `maxQueueSize: 0` is the fail-fast shape. Numeric bounds
|
|
46
|
-
are finite whole numbers: concurrency, timeouts, budget values, and
|
|
47
|
-
`maxPartitions` are positive; queue size and `retryAfterMs` may be zero.
|
|
48
|
-
`budget.kind` must be `"rolling-window"`. Everything invalid throws during
|
|
49
|
-
registry construction.
|
|
50
|
-
|
|
51
|
-
The container is an array so a later release can add atomic rules over
|
|
52
|
-
different partition dimensions, but **exactly one rule** is accepted today.
|
|
53
|
-
Zero or several throw rather than imply semantics the runtime does not have.
|
|
54
|
-
Multiple rules cannot be faked as sequential leases: consuming one rolling
|
|
55
|
-
token before a later rule refuses would charge a call that never reached the
|
|
56
|
-
provider — the exact accounting error a budget exists to prevent.
|
|
57
|
-
|
|
58
|
-
Omitting `partitionKey` gives one connector-wide partition. A callback runs
|
|
59
|
-
synchronously on model-supplied arguments and must return a non-secret string
|
|
60
|
-
of at most 128 UTF-8 bytes. A throw or an invalid return is a typed local
|
|
61
|
-
`connector_call_failed`; neither the callback text nor the arguments are
|
|
62
|
-
surfaced. The callback is operator code and may abort the caller synchronously,
|
|
63
|
-
so cancellation is rechecked after it returns — otherwise a cancelled call
|
|
64
|
-
could still consume a budget entry.
|
|
65
|
-
|
|
66
|
-
State retains the returned key, bounded timestamps, counters, signals, and
|
|
67
|
-
promise continuations. Nothing copies arguments into the limiter; the queued
|
|
68
|
-
waiter closure deliberately captures the `AbortSignal` rather than the input
|
|
69
|
-
object, because closing over the input would retain its `args`. At most
|
|
70
|
-
`maxPartitions` states are live, and an idle state is evicted only once its
|
|
71
|
-
active calls, queue, and rolling-window history are all empty — eviction must
|
|
72
|
-
not be a way to reset a live budget. Exhausted partition capacity is
|
|
73
|
-
`rate_limited` with the configured retry hint, not a silent unbounded map.
|
|
74
|
-
|
|
75
|
-
## Attempt semantics
|
|
76
|
-
|
|
77
|
-
The registry owns the limiter, and `InvocationService.invoke` acquires a permit
|
|
78
|
-
immediately before `Connector.callTool` and releases it in `finally`. Both call
|
|
79
|
-
paths, top-level `call_tool` and program calls through `connecta.call`,
|
|
80
|
-
reach that same seam, so a program cannot buy itself a
|
|
81
|
-
second limit by taking the other route.
|
|
82
|
-
|
|
83
|
-
- **A caller retry is another call.** It reacquires and can consume another
|
|
84
|
-
budget entry. Connecta returns retry hints without waiting or retrying.
|
|
85
|
-
- **A proactive short-window `rate_limited` refusal** returns its retry hint
|
|
86
|
-
without waiting or dispatching. Activity records one failed admission attempt;
|
|
87
|
-
a caller may reissue after the window.
|
|
88
|
-
- **A queued cancellation consumes nothing.** It is removed from the queue with
|
|
89
|
-
no rolling-window entry charged.
|
|
90
|
-
- **Caller cancellation is terminal.** It is non-retryable, releases its
|
|
91
|
-
permit, records a `cancelled` activity outcome, and is not connector-health
|
|
92
|
-
evidence.
|
|
93
|
-
- **Budget exhaustion fails immediately** as `rate_limited`, `retryable: true`,
|
|
94
|
-
with the exact `retryAfterMs` to the next free slot. Concurrency overflow
|
|
95
|
-
uses the configured hint instead, because there is no exact answer.
|
|
96
|
-
- **Local refusals are not connector health failures.** They are activity
|
|
97
|
-
errors, but no provider call happened, so poisoning the connector's health
|
|
98
|
-
with them would report the limiter's success as the downstream's failure.
|
|
99
|
-
`isCallAdmissionError` is what keeps `recordFailure` out of that path.
|
|
100
|
-
|
|
101
|
-
Only tool execution is covered. Catalog `listTools`, status probes, credential
|
|
102
|
-
checks, and authorization operations stay outside the budget: they are not the
|
|
103
|
-
calls a provider is rate-limiting, and charging discovery for them would make
|
|
104
|
-
a program's first search cost it capacity to act.
|
|
105
|
-
|
|
106
|
-
`queueTimeoutMs` bounds only the wait for a permit. The per-call `timeoutMs`
|
|
107
|
-
is one deadline over catalog resolution, the permit wait, and the connector
|
|
108
|
-
call together, so a saturated call can never take longer than `timeoutMs`;
|
|
109
|
-
the queue timeout may only end it sooner. With
|
|
110
|
-
`diagnostics: true`, `admissionMs` reports the permit wait and `connectorMs`
|
|
111
|
-
the admitted attempt — which is the only way to tell "the provider is slow"
|
|
112
|
-
from "we are throttling ourselves".
|
|
113
|
-
|
|
114
|
-
## Enforcement scope
|
|
115
|
-
|
|
116
|
-
Shared connectors use the root registry's controller. Personal connectors use
|
|
117
|
-
the owning principal's registry, so two accounts each receive their own budget;
|
|
118
|
-
all requests and call paths for one principal still share it. `close()` rejects
|
|
119
|
-
queued and future admissions on both kinds of controller, including a personal
|
|
120
|
-
registry constructed after shutdown.
|
|
121
|
-
|
|
122
|
-
The principal cache is bounded at 1,024 registries. Eviction closes an idle
|
|
123
|
-
registry so an older request view cannot use an orphaned controller. A registry
|
|
124
|
-
with active calls, queued calls, or unexpired rolling entries is never evicted.
|
|
125
|
-
If all 1,024 are occupied, a new principal view fails closed with HTTP 403 until
|
|
126
|
-
one drains. Cache churn cannot reset a live account's quota.
|
|
127
|
-
|
|
128
|
-
This is deliberately **per-runtime**. It completely contains fan-out inside one
|
|
129
|
-
request, including parallel `connecta.call` calls in one Worker isolate. A rolling
|
|
130
|
-
budget is exact inside one Node process or Worker isolate, and best-effort
|
|
131
|
-
across isolates, replicas, and restarts.
|
|
132
|
-
|
|
133
|
-
KV cannot coordinate the invariant without atomic operations, and this release
|
|
134
|
-
adds no Durable Object or other distributed coordinator. The async typed permit
|
|
135
|
-
is the seam a coordinator would slot into later; until then the guide says what
|
|
136
|
-
the bound actually is rather than implying a global one.
|
|
137
|
-
|
|
138
|
-
## Observations
|
|
139
|
-
|
|
140
|
-
`/health` exposes one payload-free aggregate at
|
|
141
|
-
`admission.downstreamCalls.aggregate`, summed across root and retained personal
|
|
142
|
-
controllers: rule and retained partition counts, current
|
|
143
|
-
active and queued gauges, cumulative admitted/queued/rejected/rate-limited/
|
|
144
|
-
cancelled counts, and queue-wait count, total, and maximum. The open endpoint
|
|
145
|
-
never exposes connector ids, principal ids, partition keys, tool arguments, or results — a partition key can
|
|
146
|
-
be a customer identifier, which is precisely why it stays out of an unauthenticated
|
|
147
|
-
payload. Queue-wait maxima take the maximum, other counters sum, and `closed`
|
|
148
|
-
means every included controller is closed. Evicted idle controllers no longer
|
|
149
|
-
contribute to these runtime snapshots. Ordinary payload-free activity records the final call outcome and its
|
|
150
|
-
typed error code.
|
|
151
|
-
|
|
152
|
-
## Tests that enforce this
|
|
153
|
-
|
|
154
|
-
| Invariant | Suite |
|
|
155
|
-
| --- | --- |
|
|
156
|
-
| Independent partitions, exact rolling-window reset and retry, queued cancellation charging no budget, synchronous cancel during partition derivation, validated values snapshotted rather than read from mutable config, bounded partition state and contained `partitionKey` failures, empty and multi-rule policies refused | `test/call-admission.test.ts` (controller) |
|
|
157
|
-
| Shared and personal limiters shared by direct and program calls, independent principal budgets, shutdown covering both, eviction preserving live budgets, promise concurrency with input order preserved, cancellation threading, no dispatch or retry or health poisoning after cancellation, retry hints returned without waiting or poisoning health, payload-free `/health` aggregates | `test/call-admission.test.ts` (integration, Node + Workers) |
|
|
158
|
-
| Where provider budgets are allowed to come from at all | [provider conventions P12](./provider-conventions.md#p12--declare-an-admission-budget-only-when-the-provider-documents-a-number), [provider audit](https://github.com/zackbart/connecta/blob/main/records/provider-audit.md) |
|