@zackbart/connecta 0.16.0 → 0.16.1
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 +12 -5
- package/CHANGELOG.md +187 -0
- package/README.md +6 -1
- package/dist/catalog-service.js +41 -4
- package/dist/catalog.d.ts +11 -0
- package/dist/catalog.js +20 -0
- package/dist/errors.d.ts +30 -2
- package/dist/errors.js +1 -0
- package/dist/providers/cloudflare.js +13 -25
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/cloudflare.md +40 -8
- package/documentation/code-first-exploration.md +2 -2
- package/documentation/code-mode.md +2 -2
- package/documentation/meta-tools.md +7 -1
- package/documentation/notion.md +7 -2
- package/documentation/operations.md +53 -11
- package/documentation/provider-audit.md +1 -1
- package/documentation/provider-conventions.md +42 -6
- package/documentation/upgrading.md +454 -0
- package/ethos.md +1 -1
- package/examples/worker/README.md +9 -3
- package/package.json +7 -2
- package/templates/node/AGENTS.md +5 -0
- package/templates/node/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -83,11 +83,18 @@ Two boundaries CI enforces that are not obvious from reading a file:
|
|
|
83
83
|
and fails otherwise. Need a Node API? It goes behind an explicit Node-only
|
|
84
84
|
subpath (`/node` or `/quickjs`), never the root.
|
|
85
85
|
- **The published surface.** Platform-specific storage adapters live in
|
|
86
|
-
`examples/`,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
`
|
|
90
|
-
|
|
86
|
+
`examples/worker/`, never in `src/` and never in the `exports` map. That
|
|
87
|
+
example does ship in the tarball, Cloudflare KV and D1 adapters included —
|
|
88
|
+
it is the Workers starting template a consumer copies, and copying it is the
|
|
89
|
+
point — but every `exports` target resolves into `dist/`, so those adapters
|
|
90
|
+
are readable reference source and not an importable subpath. What is
|
|
91
|
+
forbidden is a platform-bound adapter becoming importable from the package,
|
|
92
|
+
not a file appearing in the artifact. `@clerk/backend` and
|
|
93
|
+
`quickjs-emscripten` are optional peers behind the `./auth/clerk` and
|
|
94
|
+
`./quickjs` subpaths and must never become dependencies or install with
|
|
95
|
+
core. Enforced by `test/package-surface.test.ts` and
|
|
96
|
+
`scripts/check-package.mjs`. Anything heavyweight or platform-bound gets a
|
|
97
|
+
subpath and an optional peer.
|
|
91
98
|
|
|
92
99
|
## Where new tests go
|
|
93
100
|
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,193 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.16.1 — 2026-08-13
|
|
6
|
+
|
|
7
|
+
This is the cleanup that follows 0.16.0 out the door: the packaging housekeeping
|
|
8
|
+
the pre-release smoke gauntlet turned up, one provider tool Cloudflare
|
|
9
|
+
deprecated out from under us, a discovery answer that told a plain lie, and the
|
|
10
|
+
upgrade runbook an existing deployment never had. Two things break, both on
|
|
11
|
+
Cloudflare and both named here rather than left to the section below:
|
|
12
|
+
`list_zone_settings` is gone from the `cloudflare()` named surface, and
|
|
13
|
+
Cloudflare's 404 arrives as `not_found` instead of `connector_call_failed`.
|
|
14
|
+
Nothing else does — no wire shape changes, no construction contract moves, no
|
|
15
|
+
other code reclassified, and the per-setting operations `list_zone_settings`
|
|
16
|
+
sat beside are the supported ones and are untouched. A deployment that writes
|
|
17
|
+
no `api()` connectors, branches on no error code, and never asked an agent for
|
|
18
|
+
a whole zone's settings in one call upgrades without reading further.
|
|
19
|
+
|
|
20
|
+
Three things are additions rather than repairs, and they are the reason this
|
|
21
|
+
release is worth reading rather than just installing: a new
|
|
22
|
+
`ConnectorCallErrorCode` member, `not_found`, with a rule for when a connector
|
|
23
|
+
may mint it; a `"./package.json"` entry in the `exports` map, so the installed
|
|
24
|
+
manifest resolves; and `@cloudflare/codemode` declared as an optional peer at
|
|
25
|
+
`^0.4.4 || ^0.5.0`. Strict semver would read those three as a minor, and would
|
|
26
|
+
read the two Cloudflare changes above as more than that. This ships as a patch
|
|
27
|
+
deliberately: every addition is opt-in at the point a deployment chooses to
|
|
28
|
+
read it, and the tool removal and the 404 reclassification ride along in the
|
|
29
|
+
same patch on purpose — both are scoped to one provider, both have a stated
|
|
30
|
+
replacement, and both carry a version boundary in
|
|
31
|
+
[`documentation/upgrading.md`](./documentation/upgrading.md). Holding them for
|
|
32
|
+
a minor would mean shipping a release that keeps calling an endpoint its
|
|
33
|
+
provider deprecated. The one install-time consequence is spelled out next.
|
|
34
|
+
|
|
35
|
+
One thing to check before upgrading a Worker: `@cloudflare/codemode` is now a
|
|
36
|
+
declared peer, so if your `package.json` holds it at a version outside
|
|
37
|
+
`^0.4.4 || ^0.5.0` — a `0.3.x`, or a `0.4` below `0.4.4` — npm stops the
|
|
38
|
+
upgrade with an `ERESOLVE` conflict rather than installing. Move it into the
|
|
39
|
+
range this release is tested against, or pass `--legacy-peer-deps` if you have
|
|
40
|
+
a reason to run outside it. A version already inside the range, and a range
|
|
41
|
+
loose enough for npm to pick one that is, both resolve exactly as before.
|
|
42
|
+
|
|
43
|
+
Alongside it, the upgrade path an existing deployment takes gets written down.
|
|
44
|
+
`connecta init` was the golden path for a new deployment and the whole story
|
|
45
|
+
for an old one, which is a gap with a shape: `init` refuses to merge into an
|
|
46
|
+
existing path — the guard that keeps an initializer from eating a connector
|
|
47
|
+
set — so an agent pointed at a deployment two releases behind had to
|
|
48
|
+
reconstruct the procedure from release prose written for the maintainer. Both
|
|
49
|
+
interesting failures there were silent too. It overwrites the configuration the
|
|
50
|
+
deployment exists for, or it "fixes" a construction throw by weakening a
|
|
51
|
+
fail-closed default and ships something quieter and wrong.
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- **`not_found`, for a downstream that answered and had nothing to give.** A
|
|
56
|
+
hand-written connector meeting a 404 had exactly one honest code,
|
|
57
|
+
`connector_call_failed`, which also means "the call blew up" — so a program
|
|
58
|
+
inside `execute_code` could not tell a clean absence from a broken connector,
|
|
59
|
+
and a loop over ids had to abort where it should have skipped one. The new
|
|
60
|
+
code earns its place the way every code has to: it changes what the caller
|
|
61
|
+
does next. You do not wait, you do not go to `authorize_connector`, you do
|
|
62
|
+
not repair the arguments — you re-address. It is non-retryable, carries no
|
|
63
|
+
recovery envelope, derives no activity friction class, and is exported from
|
|
64
|
+
the root entry as part of `ConnectorCallErrorCode`.
|
|
65
|
+
|
|
66
|
+
The qualifier is the interesting half, and it is now written down in
|
|
67
|
+
[H11](./documentation/provider-conventions.md#h11--errors-are-mapped-to-what-the-caller-does-next):
|
|
68
|
+
map a status to `not_found` only where the provider tells absence apart from
|
|
69
|
+
a permission gap. Cloudflare does — a token that may not touch a resource is
|
|
70
|
+
refused with 401 or 403 — so its 404 is now `not_found` instead of
|
|
71
|
+
`connector_call_failed`. Notion does not: `object_not_found` means both "it
|
|
72
|
+
is gone" and "it was never shared with this integration", so it deliberately
|
|
73
|
+
stays generic with a message that says so. The hosted-MCP proxy path mints
|
|
74
|
+
the code never, because `P1` forbids re-shaping downstream framing and
|
|
75
|
+
provider prose is never parsed to invent a classification (#373).
|
|
76
|
+
|
|
77
|
+
- **An upgrade runbook for existing deployments.**
|
|
78
|
+
[`documentation/upgrading.md`](./documentation/upgrading.md) is written for
|
|
79
|
+
the agent sitting inside a generated deployment it did not create: read the
|
|
80
|
+
exact pin and the template generation it implies, regenerate that generation
|
|
81
|
+
with `npx @zackbart/connecta@<pin> init` to get a real merge base, three-way
|
|
82
|
+
reconcile the scaffolding against the current template while `src/index.ts`
|
|
83
|
+
stays the deployment's own, cross the version boundaries that break
|
|
84
|
+
construction, and finish where `init` finishes — typecheck, start,
|
|
85
|
+
`connecta doctor`, then a program that exercises the deployment's *own*
|
|
86
|
+
connectors, which doctor deliberately knows nothing about. The migration
|
|
87
|
+
notes are per boundary and derived from this file: the 0.16.0 `api()`
|
|
88
|
+
construction contract (with the one safe answer for an unannotated tool
|
|
89
|
+
written down — `readOnlyHint: false`, which is the routing it already had),
|
|
90
|
+
the `linear()`, `mixpanel()`, and Cloudflare provider changes, redirect
|
|
91
|
+
refusal and the response ceilings, and the fail-closed shipped defaults; then
|
|
92
|
+
0.14's annotation-precedence change, 0.13's rewritten guide summaries, the
|
|
93
|
+
0.11.0 executor requirement, 0.7.0's `verifyState` requirement and
|
|
94
|
+
core-owned routes for the pre-template deployments that still have to cross
|
|
95
|
+
them, and every removed option that throws with its migration. It closes with five refusals, because each is somebody's plausible
|
|
96
|
+
shortcut: no re-init over the top, no weakening a fail-closed default to get
|
|
97
|
+
green, no pinning back, no vendored internals, no second project shape.
|
|
98
|
+
Reachable from the README, from `operations.md`, and — absolutely, because
|
|
99
|
+
that reader has no copy of this repository — from the template's `AGENTS.md`
|
|
100
|
+
(#380).
|
|
101
|
+
- **A suite that keeps the guide honest.** `test/upgrade-guide.test.ts` pins
|
|
102
|
+
every claim its reader cannot check: the generated file inventory against
|
|
103
|
+
`templates/node/`, the seven tool names against the CLI's own list, each
|
|
104
|
+
named version boundary against a release that shipped, each removed option
|
|
105
|
+
against the release section that names its issue, the bump target against
|
|
106
|
+
this package's version, and the three places the guide is linked from. A template that gains a file now fails `npm run check` rather than
|
|
107
|
+
leaving an agent to guess which of the two is wrong (#380).
|
|
108
|
+
- **`@cloudflare/codemode` is a declared optional peer.** Every Workers
|
|
109
|
+
deployment installs the executor behind `execute_code` by hand, and until now
|
|
110
|
+
the only version range anywhere was a devDependency no consumer can read — a
|
|
111
|
+
fresh install resolved a minor ahead of what this repository tests, silently.
|
|
112
|
+
The manifest now publishes `^0.4.4 || ^0.5.0` for it, optional like
|
|
113
|
+
`@clerk/backend` and `quickjs-emscripten`, so a supported version installs in
|
|
114
|
+
silence and an unsupported one stops the install with something to act on
|
|
115
|
+
instead of becoming skew a Worker discovers in production. It still installs
|
|
116
|
+
with nothing: a default `npm install @zackbart/connecta` pulls no executor,
|
|
117
|
+
and the package smoke proves that, both halves of the range behavior, and
|
|
118
|
+
that the version this repository develops against stays inside the range it
|
|
119
|
+
publishes (#376).
|
|
120
|
+
|
|
121
|
+
### Changed
|
|
122
|
+
|
|
123
|
+
- **Cloudflare's 404 is `not_found`.** A deployment branching on
|
|
124
|
+
`connector_call_failed` to detect an unknown zone or account id should read
|
|
125
|
+
`not_found` instead; retryability, the message, and its pointer to
|
|
126
|
+
`list_zones` / `list_accounts` are unchanged (#373).
|
|
127
|
+
- `cloudflare()` no longer names a bulk zone-settings read.
|
|
128
|
+
`GET /zones/{zoneId}/settings` and its `PATCH` sibling are published as
|
|
129
|
+
`deprecated: true`, Cloudflare offers no bulk replacement, and the tool that
|
|
130
|
+
wrapped the read projected nothing — it took a zone id and grew the payload
|
|
131
|
+
by wrapping an unpaginated array in a page object. Read one setting with
|
|
132
|
+
`get_zone_setting` and write one with `update_zone_setting`, both on the
|
|
133
|
+
supported `/zones/{zoneId}/settings/{settingId}` operations. An operator who
|
|
134
|
+
still wants the whole set can name the deprecated path explicitly through
|
|
135
|
+
`cloudflare_api_get`. The named surface is 47 tools plus the three escape
|
|
136
|
+
hatches ([#361](https://github.com/zackbart/connecta/issues/361)).
|
|
137
|
+
- The Cloudflare touched-endpoint manifest drops the deprecated row with the
|
|
138
|
+
tool, so `npm run drift:check -- --specs` is quiet about zone settings
|
|
139
|
+
because nothing calls the endpoint, not because a maintainer signed off on
|
|
140
|
+
calling it anyway.
|
|
141
|
+
|
|
142
|
+
### Fixed
|
|
143
|
+
|
|
144
|
+
- **A search for a connector's own name stops claiming the deployment has no
|
|
145
|
+
such capability.** A connector's `id` — the address prefix an agent already
|
|
146
|
+
holds — and its `title` are displayed, never indexed, so `search_tools({
|
|
147
|
+
query: "inventory" })` against a connector called `inventory` matched no tool
|
|
148
|
+
and was answered with "No matching capability is configured in this
|
|
149
|
+
deployment", which was plainly false. Connector identity stays out of the
|
|
150
|
+
lexical index, because putting it in would move ranking for every query that
|
|
151
|
+
already matches tools; instead an unscoped miss whose terms name configured
|
|
152
|
+
connectors says so, names up to three of them by ID, and sends the caller to
|
|
153
|
+
a scoped browse. A term that matches nothing in the deployment still gets the
|
|
154
|
+
original sentence, unchanged. One `queryAnalysis.guidance` string differs; no
|
|
155
|
+
ranking, result, or field changed (#372).
|
|
156
|
+
- **Every relative link in the shipped Markdown resolves for the reader who
|
|
157
|
+
installed the package.** Ten of them pointed at `eval/`, `test/`, `scripts/`,
|
|
158
|
+
and the README hero — repository paths the tarball has never carried and, per
|
|
159
|
+
#346, should not start carrying. The link gate could not see any of them: it
|
|
160
|
+
read only `documentation/` targets, so the whole class was invisible and grew
|
|
161
|
+
with every trim. The policy is now stated once in the operations guide and
|
|
162
|
+
enforced over *every* relative link in packed Markdown: it either resolves
|
|
163
|
+
inside the tarball or it is cited as an absolute
|
|
164
|
+
`https://github.com/zackbart/connecta/blob/main/...` URL, which an outside
|
|
165
|
+
reader can follow and which `check:docs` resolves back to the checkout, so a
|
|
166
|
+
citation still fails when the file it names moves. The ten links were
|
|
167
|
+
rewritten that way, the README hero now loads from
|
|
168
|
+
`raw.githubusercontent.com` and still renders on npmjs.com, and the
|
|
169
|
+
repository reader loses no citation (#378).
|
|
170
|
+
- **`@zackbart/connecta/package.json` resolves.** The `exports` map listed
|
|
171
|
+
every code subpath and nothing else, so a bundler plugin, framework build
|
|
172
|
+
step, or version probe reaching for the installed manifest — a thing the
|
|
173
|
+
ecosystem broadly expects to work — got `ERR_PACKAGE_PATH_NOT_EXPORTED`
|
|
174
|
+
instead of the file. The manifest is now exported. It is a data file, so this
|
|
175
|
+
widens the published surface by exactly zero code paths: the root entry's
|
|
176
|
+
Workers purity boundary and the optional-peer subpaths are untouched. The
|
|
177
|
+
package-surface gate now asserts the whole subpath set, manifest included, so
|
|
178
|
+
neither this entry nor an unwanted one can arrive unnoticed (#374).
|
|
179
|
+
- **The published-surface rule says what it actually forbids.** `AGENTS.md`
|
|
180
|
+
claimed platform-specific storage adapters live in `examples/`, "not the
|
|
181
|
+
package", while the tarball has always carried `examples/worker` — Cloudflare
|
|
182
|
+
KV and D1 adapters included — because that example is the Workers starting
|
|
183
|
+
template a consumer copies. The invariant was never in danger: nothing under
|
|
184
|
+
`examples/` appears in the `exports` map, so those adapters are reference
|
|
185
|
+
source and not an importable subpath. The wording now draws the line where
|
|
186
|
+
the gates draw it — a platform-bound adapter must not reach `src/` or the
|
|
187
|
+
`exports` map — and says why the example ships, in `AGENTS.md`, the
|
|
188
|
+
operations guide, and the `scripts/check-package.mjs` comment. A new
|
|
189
|
+
assertion in `test/package-surface.test.ts` holds the instruction file and
|
|
190
|
+
the exports map to the same story (#377).
|
|
191
|
+
|
|
5
192
|
## 0.16.0 — 2026-08-12
|
|
6
193
|
|
|
7
194
|
This is the agent-efficiency refocus. One release, sixteen merges, and a single
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# connecta
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
One place for AI agents to connect to the tools you choose.
|
|
6
6
|
|
|
@@ -98,6 +98,11 @@ activity ship as commented configuration, each one a variable and an
|
|
|
98
98
|
uncommented block away. The generated `README.md` walks through all four, and
|
|
99
99
|
the [Worker example](./examples/worker/) does the same for KV and D1.
|
|
100
100
|
|
|
101
|
+
Already have a deployment on an older version? `init` deliberately refuses to
|
|
102
|
+
merge into it, so bringing one current is its own procedure:
|
|
103
|
+
[Upgrading an existing deployment](./documentation/upgrading.md) is the
|
|
104
|
+
runbook, written for the agent working inside that project.
|
|
105
|
+
|
|
101
106
|
The template refuses to merge into an existing directory, so initialization
|
|
102
107
|
cannot overwrite another project. Its generated programs have no filesystem,
|
|
103
108
|
environment, arbitrary network, imports, or timers; only explicitly read-only
|
package/dist/catalog-service.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { compactDiscoverySchema, compactSchema, lexicalCorpusStatistics, lexicalQueryTerms, lexicalSearchQuery, rankTools, schemaObjectKeys, summarizeDiscoveryDescription, summarizeDescription, } from "./catalog.js";
|
|
1
|
+
import { compactDiscoverySchema, compactSchema, lexicalCorpusStatistics, lexicalQueryTerms, lexicalSearchQuery, matchesLexicalTerm, rankTools, schemaObjectKeys, summarizeDiscoveryDescription, summarizeDescription, } from "./catalog.js";
|
|
2
2
|
import { mapSettledWithConcurrency, resolveDiscoveryConcurrency, } from "./concurrency.js";
|
|
3
3
|
import { boundedEchoText, classifyCallError, framingError, } from "./errors.js";
|
|
4
4
|
import { connectorGuide, connectorGuideRequired, connectorGuideSummary, connectorSkillName, } from "./skills.js";
|
|
@@ -10,6 +10,12 @@ export const MAX_DESCRIBE_ADDRESSES = 100;
|
|
|
10
10
|
export const MAX_DISCOVERY_RESULT_BYTES = 256_000;
|
|
11
11
|
const MAX_QUERY_TERMS = 8;
|
|
12
12
|
const MAX_QUERY_TERM_LENGTH = 64;
|
|
13
|
+
/**
|
|
14
|
+
* Connector IDs a no-match search will name back. Three is enough to point at
|
|
15
|
+
* the connector the query already named without turning a miss into a listing
|
|
16
|
+
* of the deployment.
|
|
17
|
+
*/
|
|
18
|
+
const MAX_IDENTITY_CONNECTORS = 3;
|
|
13
19
|
const encoder = new TextEncoder();
|
|
14
20
|
/** Clip one echoed query term without splitting a non-BMP code point. */
|
|
15
21
|
function boundedQueryTerm(term) {
|
|
@@ -561,6 +567,36 @@ export class CatalogService {
|
|
|
561
567
|
required: connectorGuideRequired(scopedConnector),
|
|
562
568
|
}
|
|
563
569
|
: undefined;
|
|
570
|
+
// A connector's own id — the address prefix the caller already has — and
|
|
571
|
+
// the title it is displayed under are the most natural first query terms,
|
|
572
|
+
// and neither is a document in the lexical index. Indexing them would move
|
|
573
|
+
// ranking for every query that already matches tools, so instead a search
|
|
574
|
+
// that matched nothing asks the same lexical question of connector
|
|
575
|
+
// identity and corrects its own sentence: the deployment plainly has this
|
|
576
|
+
// capability, and one scoped browse away are its tools. Unscoped only —
|
|
577
|
+
// guidance for an explicit scope already names that connector rather than
|
|
578
|
+
// claiming the deployment has nothing.
|
|
579
|
+
const identityConnectorIds = matches.length === 0 && !isBrowse && !unsearchableQuery && !scopedConnector
|
|
580
|
+
? connectors
|
|
581
|
+
// The full query, not `analyzedTerms`: that cap exists to bound
|
|
582
|
+
// the serialized term fields, and ranking already reads every
|
|
583
|
+
// term. A ninth term is a real search term, and a search that
|
|
584
|
+
// ranked against it must not deny the connector it names.
|
|
585
|
+
.filter((connector) => queryTerms.some((term) => matchesLexicalTerm(connector.id, term) ||
|
|
586
|
+
(connector.title !== undefined &&
|
|
587
|
+
matchesLexicalTerm(connector.title, term))))
|
|
588
|
+
.map((connector) => connector.id)
|
|
589
|
+
: [];
|
|
590
|
+
const namedIdentityConnectors = identityConnectorIds
|
|
591
|
+
.slice(0, MAX_IDENTITY_CONNECTORS)
|
|
592
|
+
.map((id) => `"${id}"`)
|
|
593
|
+
.join(", ");
|
|
594
|
+
const unnamedIdentityConnectors = identityConnectorIds.length - MAX_IDENTITY_CONNECTORS;
|
|
595
|
+
const identityGuidance = identityConnectorIds.length === 0
|
|
596
|
+
? undefined
|
|
597
|
+
: `No matching ${safetyLabel}capability was found${unavailableCatalogs === 0 ? "" : " in the catalogs that answered"}, but the query names configured connector${identityConnectorIds.length === 1 ? "" : "s"} ${namedIdentityConnectors}${unnamedIdentityConnectors > 0
|
|
598
|
+
? ` and ${unnamedIdentityConnectors} more`
|
|
599
|
+
: ""}. Scope by connector and browse with an empty query to list the tools there.${filterRecovery}`;
|
|
564
600
|
// A scope that resolved to nothing is the same silence one step earlier in
|
|
565
601
|
// the lookup: no connector resolved, so no catalog was even attempted, so
|
|
566
602
|
// no catalog failed and the unavailable path below never fires. Echo only
|
|
@@ -604,9 +640,10 @@ export class CatalogService {
|
|
|
604
640
|
: scopedGuide?.required
|
|
605
641
|
? `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Fetch queryAnalysis.guide before calling, then refine terms or browse with an empty query.${filterRecovery}`
|
|
606
642
|
: `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Refine terms or browse it with an empty query.${filterRecovery}`
|
|
607
|
-
:
|
|
608
|
-
|
|
609
|
-
|
|
643
|
+
: (identityGuidance ??
|
|
644
|
+
(unavailableCatalogs === 0
|
|
645
|
+
? `No matching ${safetyLabel}capability is configured in this deployment. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`
|
|
646
|
+
: `No matching ${safetyLabel}capability was found in the catalogs that answered; ${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`))))
|
|
610
647
|
: matchMode === "partial"
|
|
611
648
|
? scopedConnector
|
|
612
649
|
? `No single tool on connector "${scopedConnector.id}" matched every term. Split distinct intents into separate searches.`
|
package/dist/catalog.d.ts
CHANGED
|
@@ -18,6 +18,17 @@ export interface RankedTool {
|
|
|
18
18
|
exactName: boolean;
|
|
19
19
|
matchedTermCount: number;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Whether one query term matches a whole token of arbitrary text, under the
|
|
23
|
+
* same inflection rules the tool index uses.
|
|
24
|
+
*
|
|
25
|
+
* Connector identity — an `id` or a `title` — is deliberately not a document in
|
|
26
|
+
* that index: making it one would move ranking for every query that already
|
|
27
|
+
* matches tools. This lets a caller ask the index's question of a string that
|
|
28
|
+
* never became a document, which is what the no-match analysis needs to tell
|
|
29
|
+
* "nothing like this exists here" from "that word is a connector".
|
|
30
|
+
*/
|
|
31
|
+
export declare function matchesLexicalTerm(text: string, term: string): boolean;
|
|
21
32
|
export interface LexicalCorpusStatistics {
|
|
22
33
|
documentCount: number;
|
|
23
34
|
documentFrequency: ReadonlyMap<string, number>;
|
package/dist/catalog.js
CHANGED
|
@@ -166,6 +166,26 @@ function matchingTokenCandidates(term) {
|
|
|
166
166
|
}
|
|
167
167
|
return candidates;
|
|
168
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Whether one query term matches a whole token of arbitrary text, under the
|
|
171
|
+
* same inflection rules the tool index uses.
|
|
172
|
+
*
|
|
173
|
+
* Connector identity — an `id` or a `title` — is deliberately not a document in
|
|
174
|
+
* that index: making it one would move ranking for every query that already
|
|
175
|
+
* matches tools. This lets a caller ask the index's question of a string that
|
|
176
|
+
* never became a document, which is what the no-match analysis needs to tell
|
|
177
|
+
* "nothing like this exists here" from "that word is a connector".
|
|
178
|
+
*/
|
|
179
|
+
export function matchesLexicalTerm(text, term) {
|
|
180
|
+
const tokens = new Set(lexicalTokens(text));
|
|
181
|
+
if (tokens.size === 0)
|
|
182
|
+
return false;
|
|
183
|
+
for (const candidate of matchingTokenCandidates(term)) {
|
|
184
|
+
if (tokens.has(candidate))
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
169
189
|
/**
|
|
170
190
|
* Compute query-specific document frequencies across every available catalog.
|
|
171
191
|
* The caller does this once per search and shares the result with each
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Machine-readable classification of a failed connector tool call.
|
|
3
|
+
*
|
|
4
|
+
* A code earns its place by changing what the caller does next, never by
|
|
5
|
+
* naming a cause — the rule provider conventions call H11.
|
|
6
|
+
*/
|
|
7
|
+
export type ConnectorCallErrorCode = "timeout" | "auth_required" | "rate_limited" | "unavailable" | "invalid_args"
|
|
8
|
+
/**
|
|
9
|
+
* The downstream answered, and the thing addressed is not there.
|
|
10
|
+
*
|
|
11
|
+
* Its own code because the next move is none of the others': not a retry,
|
|
12
|
+
* not `authorize_connector`, not a reshaped argument object, but
|
|
13
|
+
* re-addressing — look the identifier up again, or accept the absence and
|
|
14
|
+
* carry on. Inside `execute_code` a program reads that difference off a
|
|
15
|
+
* `connecta.batch` entry's `errorDetails.code` — continue past this one,
|
|
16
|
+
* abort on `connector_call_failed` — or lets the failure escape uncaught so
|
|
17
|
+
* the model sees the typed envelope. Never off a caught error: the guest
|
|
18
|
+
* bridge reduces a rejected host call to `new Error(message)` and drops
|
|
19
|
+
* every own property, and message prose cannot be classified.
|
|
20
|
+
*
|
|
21
|
+
* Use it only where the provider distinguishes absence from a permission
|
|
22
|
+
* gap. A status that means both "it is not there" and "you cannot see it" —
|
|
23
|
+
* Notion's `object_not_found` is the worked example — stays
|
|
24
|
+
* `connector_call_failed` with a message that states the ambiguity, because
|
|
25
|
+
* inventing certainty here is how an agent concludes a page was deleted when
|
|
26
|
+
* it was simply never shared. Addresses connecta itself cannot resolve are
|
|
27
|
+
* already framed as `unknown_address` or `unknown_tool` and never reach a
|
|
28
|
+
* connector, so this code is always about a resource the downstream owns.
|
|
29
|
+
*/
|
|
30
|
+
| "not_found" | "input_required_unsupported" | "connector_call_failed";
|
|
3
31
|
/** One bounded, payload-free explanation of an input-schema mismatch. */
|
|
4
32
|
export interface ArgumentValidationIssue {
|
|
5
33
|
/** JSON Pointer into the submitted arguments; "/" means the root value. */
|
package/dist/errors.js
CHANGED
|
@@ -235,8 +235,12 @@ function failureFor(status, headers, errors) {
|
|
|
235
235
|
if (status === 400 || status === 409 || status === 422) {
|
|
236
236
|
return new ConnectorCallError("invalid_args", `Cloudflare rejected the request (HTTP ${status}). ${detail}`);
|
|
237
237
|
}
|
|
238
|
+
// Cloudflare's 404 is an honest absence: a token that may not touch a
|
|
239
|
+
// resource is refused with 401 or 403 above, not hidden behind a miss, so
|
|
240
|
+
// this is the unambiguous case `not_found` exists for — re-address, do not
|
|
241
|
+
// retry and do not re-authorize.
|
|
238
242
|
if (status === 404) {
|
|
239
|
-
return new ConnectorCallError("
|
|
243
|
+
return new ConnectorCallError("not_found", `Cloudflare found no such resource (HTTP 404). ${detail} Confirm the zone or account id with list_zones or list_accounts.`);
|
|
240
244
|
}
|
|
241
245
|
if (status >= 500) {
|
|
242
246
|
return new ConnectorCallError("unavailable", `Cloudflare is unavailable (HTTP ${status}). ${detail}`);
|
|
@@ -1401,27 +1405,11 @@ function buildTools(scope, authentication) {
|
|
|
1401
1405
|
return args["raw"] === true ? result : projectZone(result);
|
|
1402
1406
|
},
|
|
1403
1407
|
},
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
type: "object",
|
|
1410
|
-
properties: {
|
|
1411
|
-
zoneId: scopeProperty("zoneId", scope.zoneId),
|
|
1412
|
-
},
|
|
1413
|
-
required: scopeRequired("zoneId", scope.zoneId),
|
|
1414
|
-
additionalProperties: false,
|
|
1415
|
-
},
|
|
1416
|
-
outputSchema: listOutputSchema("settings", OPEN_OBJECT_OUTPUT_SCHEMA),
|
|
1417
|
-
handler: async (args, ctx) => {
|
|
1418
|
-
const { result, resultInfo } = await callCloudflare(send, {
|
|
1419
|
-
method: "GET",
|
|
1420
|
-
path: `/zones/${encodePathSegment(zoneArg(args))}/settings`,
|
|
1421
|
-
}, ctx);
|
|
1422
|
-
return { settings: asArray(result), page: pageInfo(resultInfo) };
|
|
1423
|
-
},
|
|
1424
|
-
},
|
|
1408
|
+
// There is deliberately no bulk zone-settings read here. Cloudflare
|
|
1409
|
+
// publishes `GET /zones/{zone_id}/settings` as deprecated (#361), the
|
|
1410
|
+
// per-setting operations below are not, and the tool that wrapped the bulk
|
|
1411
|
+
// read projected nothing. See the Cloudflare guide's "What the named
|
|
1412
|
+
// surface deliberately leaves out".
|
|
1425
1413
|
{
|
|
1426
1414
|
name: "get_zone_setting",
|
|
1427
1415
|
description: "Get one zone setting by its Cloudflare setting id, such as ssl, always_use_https, min_tls_version, brotli, or development_mode.",
|
|
@@ -1433,7 +1421,7 @@ function buildTools(scope, authentication) {
|
|
|
1433
1421
|
settingId: {
|
|
1434
1422
|
type: "string",
|
|
1435
1423
|
minLength: 1,
|
|
1436
|
-
description: "Cloudflare zone setting id
|
|
1424
|
+
description: "Cloudflare zone setting id, such as ssl, brotli, http3, or min_tls_version.",
|
|
1437
1425
|
},
|
|
1438
1426
|
},
|
|
1439
1427
|
required: [...scopeRequired("zoneId", scope.zoneId), "settingId"],
|
|
@@ -1459,7 +1447,7 @@ function buildTools(scope, authentication) {
|
|
|
1459
1447
|
settingId: {
|
|
1460
1448
|
type: "string",
|
|
1461
1449
|
minLength: 1,
|
|
1462
|
-
description: "Cloudflare zone setting id
|
|
1450
|
+
description: "Cloudflare zone setting id, such as ssl, brotli, http3, or min_tls_version.",
|
|
1463
1451
|
},
|
|
1464
1452
|
value: {
|
|
1465
1453
|
type: ["string", "number", "boolean", "array"],
|
|
@@ -3215,7 +3203,7 @@ Account purpose: ${purpose}
|
|
|
3215
3203
|
- Prefer a named tool: its schema is complete, projected, and enough to call it without provider documentation. For an operation without a named tool, use \`cloudflare_api_get\` for GET, \`cloudflare_api_mutate\` for JSON POST/PUT/PATCH/DELETE, or \`cloudflare_api_upload\` for raw and multipart content. Raw tools take a path below \`/client/v4\`; their argument schemas are complete, but endpoint-specific query, header, and body fields come from Cloudflare's API reference. Use \`headers\` for endpoint-specific controls such as \`cf-r2-jurisdiction\`, \`Range\`, \`If-None-Match\`, and Cloudflare product metadata. \`Authorization\`, \`Cookie\`, \`Host\`, \`Content-Length\`, \`Content-Type\`, and \`Transfer-Encoding\` are connector-owned and refused: authentication, host, content type, and request framing are not the caller's to set.
|
|
3216
3204
|
- The raw tools cover the wider control plane without weakening routing: GET is explicitly read-only; every mutation and upload is destructive and must cross the host's approval boundary. The configured Cloudflare credential remains the hard provider-side permission boundary. Absolute URLs, traversal, and query strings embedded in \`path\` are refused locally.
|
|
3217
3205
|
- Useful raw paths include \`/accounts/{accountId}/images/v1\` (Images), \`/accounts/{accountId}/stream\` (Stream), \`/zones/{zoneId}/email/routing/rules\` (Email Routing), \`/accounts/{accountId}/d1/database\` (D1), and \`/accounts/{accountId}/queues\` (Queues). On GET, use \`responseType: "text"\` or \`"base64"\` for non-JSON content. Direct-upload endpoints can issue upload URLs; \`cloudflare_api_upload\` can also send explicit text, base64 bytes, or multipart fields/files.
|
|
3218
|
-
-
|
|
3206
|
+
- Three areas are deliberately unnamed. Read a bucket's CORS policy with \`get_r2_cors\`, then change it with \`cloudflare_api_mutate\` — \`PUT\` or \`DELETE /accounts/{accountId}/r2/buckets/{bucketName}/cors\`, rule fields per Cloudflare's reference — and read account storage totals with \`cloudflare_api_get\` at \`/accounts/{accountId}/r2/metrics\`. Zone settings are read one at a time with \`get_zone_setting\`: Cloudflare deprecated the bulk \`/zones/{zoneId}/settings\` read and published no replacement for it, so reach for the whole set through \`cloudflare_api_get\` only when one setting genuinely will not do.
|
|
3219
3207
|
- Lists paginate with \`page\` and \`perPage\` and return a \`page\` object; request the next page only when \`page.hasMore\` is true. \`list_zone_rulesets\`, \`list_r2_buckets\`, \`list_r2_objects\`, and \`list_kv_keys\` page by cursor instead: pass \`cursor\`, continue while \`nextCursor\` is present, and expect no \`page\` object. Their schemas say so too. \`list_worker_scripts\` is unpaginated.
|
|
3220
3208
|
- Results are projected to the fields that identify and describe a resource. Pass \`raw: true\` on a read when you genuinely need a field the projection drops.
|
|
3221
3209
|
- ${authenticationLine}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Import `cloudflare()` independently from
|
|
4
4
|
`@zackbart/connecta/providers/cloudflare`. It is a deliberate, hand-written
|
|
5
|
-
surface over Cloudflare's v4 REST API. Fifty-
|
|
5
|
+
surface over Cloudflare's v4 REST API. Fifty-one tools combine ergonomic,
|
|
6
6
|
fully described operations for common work with three guarded escape hatches
|
|
7
7
|
for the rest of Cloudflare's fast-moving control plane. Reads, JSON mutations,
|
|
8
8
|
and raw/multipart uploads remain separate so safety routing does not depend on
|
|
@@ -140,7 +140,7 @@ projections:
|
|
|
140
140
|
|
|
141
141
|
| Area | Reads | Writes |
|
|
142
142
|
| --- | --- | --- |
|
|
143
|
-
| Zones | discovery, details,
|
|
143
|
+
| Zones | discovery, details, one setting at a time, rulesets | update a setting |
|
|
144
144
|
| DNS/cache | list and get records | create, update, delete, targeted/full purge |
|
|
145
145
|
| Workers | scripts, settings, deployments | delete a script |
|
|
146
146
|
| KV | namespaces, keys, bulk values | create/rename/delete namespace, bulk write/delete |
|
|
@@ -158,9 +158,10 @@ asserts those properties rather than leaving them as a claim.
|
|
|
158
158
|
A named tool is a permanent line item in every deployment's catalog, so the
|
|
159
159
|
surface was measured against the escape hatches rather than assumed to beat
|
|
160
160
|
them ([#350](https://github.com/zackbart/connecta/issues/350), evidence in
|
|
161
|
-
[`eval/current-version/results/issue-350-evidence.md`](
|
|
162
|
-
Two named tools lost that comparison,
|
|
163
|
-
surface to keep a policy pair on one route
|
|
161
|
+
[`eval/current-version/results/issue-350-evidence.md`](https://github.com/zackbart/connecta/blob/main/eval/current-version/results/issue-350-evidence.md)).
|
|
162
|
+
Two named tools lost that comparison, a third followed one of them off the
|
|
163
|
+
surface to keep a policy pair on one route, and a fourth left because
|
|
164
|
+
Cloudflare deprecated the only endpoint it could call:
|
|
164
165
|
|
|
165
166
|
- **R2 CORS writes.** `set_r2_cors` declared its rule list as free-form objects
|
|
166
167
|
— the untyped body this connection refuses everywhere else — so its schema
|
|
@@ -181,8 +182,31 @@ surface to keep a policy pair on one route:
|
|
|
181
182
|
path, and returned the response untouched — `cloudflare_api_get` at
|
|
182
183
|
`/accounts/{accountId}/r2/metrics` does the same thing without a permanent
|
|
183
184
|
catalog line.
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
- **The bulk zone-settings read.** `list_zone_settings` called
|
|
186
|
+
`GET /zones/{zoneId}/settings`, which Cloudflare's published document now
|
|
187
|
+
marks `deprecated: true` along with its `PATCH` sibling
|
|
188
|
+
([#361](https://github.com/zackbart/connecta/issues/361)). There is no bulk
|
|
189
|
+
replacement to repoint it at: the supported operations are the per-setting
|
|
190
|
+
`GET` and `PATCH /zones/{zoneId}/settings/{settingId}` that `get_zone_setting`
|
|
191
|
+
and `update_zone_setting` already call. Keeping the tool would have meant
|
|
192
|
+
maintaining a name over a contract Cloudflare has announced it intends to
|
|
193
|
+
stop honouring — and #350 had already measured this one as the sharpest
|
|
194
|
+
passthrough on the surface: it took a zone id, projected nothing, and *grew*
|
|
195
|
+
the payload 22.7% by wrapping Cloudflare's settings array in a page object an
|
|
196
|
+
unpaginated endpoint never filled. Read one setting with `get_zone_setting`.
|
|
197
|
+
Where the whole set is genuinely wanted — an audit, a config diff — an
|
|
198
|
+
operator can still ask for it explicitly through `cloudflare_api_get` at
|
|
199
|
+
`/zones/{zoneId}/settings`, which is the honest place for a call whose
|
|
200
|
+
endpoint is on its way out: named by the caller, at the caller's risk, rather
|
|
201
|
+
than promised by connecta's catalog.
|
|
202
|
+
|
|
203
|
+
Because `scripts/drift/cloudflare-endpoints.json` records the endpoints this
|
|
204
|
+
connection *calls*, dropping the tool drops the row — so
|
|
205
|
+
`npm run drift:check -- --specs` is quiet about zone settings by construction
|
|
206
|
+
rather than by a recorded exception. A path reached only through a hatch is
|
|
207
|
+
named by the caller, so it was never a touched endpoint.
|
|
208
|
+
|
|
209
|
+
The surviving 47 named tools all refuse malformed arguments locally, which is
|
|
186
210
|
the one thing no escape hatch can do: a hatch's path is an opaque string, so it
|
|
187
211
|
can only check that a path is a path.
|
|
188
212
|
|
|
@@ -324,9 +348,17 @@ the failure message so the provider's own code number survives to the agent.
|
|
|
324
348
|
| 401 or 403 | `auth_required`, not retryable | Stops and reports which permission is missing |
|
|
325
349
|
| 400 with a credential-shaped code (1001, 6003, 6111, 9103, 9106, 9107) | `auth_required`, not retryable | Stops; the header or key is malformed, not the arguments |
|
|
326
350
|
| 400, 409, 422 | `invalid_args`, not retryable | Repairs the arguments |
|
|
327
|
-
| 404 | `
|
|
351
|
+
| 404 | `not_found`, not retryable | Re-runs discovery for the id |
|
|
328
352
|
| 5xx or a transport error | `unavailable`, retryable | Retries |
|
|
329
353
|
|
|
354
|
+
The 404 row is the unambiguous half of the `not_found` rule
|
|
355
|
+
([H11](./provider-conventions.md#h11--errors-are-mapped-to-what-the-caller-does-next)).
|
|
356
|
+
Cloudflare refuses a token that may not touch a resource with 401 or 403, so a
|
|
357
|
+
404 is an absence rather than a permission gap wearing a miss, and an agent can
|
|
358
|
+
act on it: confirm the zone or account id with `list_zones` or `list_accounts`
|
|
359
|
+
and address the right one. Notion's 404, which proves neither, stays
|
|
360
|
+
`connector_call_failed`.
|
|
361
|
+
|
|
330
362
|
The six credential-shaped codes deserve a caveat: Cloudflare publishes no
|
|
331
363
|
official table mapping error codes to causes, so that set is assembled from
|
|
332
364
|
community reports and probing, not from documentation. The same goes for the
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
> constraint below was dropped.** The owner decided the default directly on
|
|
14
14
|
> 2026-07-30 and [#224](https://github.com/zackbart/connecta/issues/224)
|
|
15
15
|
> shipped it; the ethos records the eval-as-gate as `removed` and
|
|
16
|
-
> [`eval/code-first-gate`](
|
|
16
|
+
> [`eval/code-first-gate`](https://github.com/zackbart/connecta/blob/main/eval/code-first-gate/README.md) continues as
|
|
17
17
|
> measurement that nothing waits on.
|
|
18
18
|
|
|
19
19
|
## Executive summary
|
|
@@ -227,7 +227,7 @@ changing the default surface. Run at least 20 independent samples per task and
|
|
|
227
227
|
model, with prompt variation.
|
|
228
228
|
|
|
229
229
|
That suite was built under
|
|
230
|
-
[`eval/code-first-gate`](
|
|
230
|
+
[`eval/code-first-gate`](https://github.com/zackbart/connecta/blob/main/eval/code-first-gate/README.md). Its recorded results
|
|
231
231
|
remain as measurement history; the runnable comparison was retired when #273
|
|
232
232
|
removed the alternate deployment shapes.
|
|
233
233
|
|
|
@@ -329,8 +329,7 @@ not in the always-loaded usage skill, which `test/meta-tools.test.ts` caps at
|
|
|
329
329
|
2,500 bytes — a budget the guide already spends nearly all of, so new text there
|
|
330
330
|
displaces old rather than adding to what every request pays for.
|
|
331
331
|
|
|
332
|
-
**E2.** The taxonomy
|
|
333
|
-
program may do about it.
|
|
332
|
+
**E2.** The taxonomy: `retryable` is what connecta reports, `Y3` what a program may do.
|
|
334
333
|
|
|
335
334
|
| Code | Raised when | `retryable` |
|
|
336
335
|
| --- | --- | --- |
|
|
@@ -340,6 +339,7 @@ program may do about it.
|
|
|
340
339
|
| `destructive_tool_requires_approval` | the tool is not explicitly read-only | false |
|
|
341
340
|
| `auth_required` | the credential is missing, expired, or rejected | false |
|
|
342
341
|
| `invalid_args` | arguments or discovery bounds were rejected | false |
|
|
342
|
+
| `not_found` | the downstream answered and the resource is not there — the one code that says skip this id rather than stop, classified off `errorDetails` per `E1` and never off a caught error, raised only where the provider tells absence from a permission gap ([H11](./provider-conventions.md#h11--errors-are-mapped-to-what-the-caller-does-next)) | false |
|
|
343
343
|
| `input_required_unsupported` | a downstream asked for mid-call input | false |
|
|
344
344
|
| `rate_limited` | the downstream reported a rate limit | true |
|
|
345
345
|
| `unavailable` | the downstream is down or unreachable | true |
|