@zackbart/connecta 0.19.0 → 0.20.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 +58 -0
- package/dist/apps-shell.d.ts +10 -12
- package/dist/apps-shell.js +29 -220
- package/dist/execute.d.ts +0 -7
- package/dist/execute.js +8 -117
- package/dist/index.js +108 -67
- package/dist/invocation.d.ts +1 -1
- package/dist/meta-tools.d.ts +0 -1
- package/dist/meta-tools.js +8 -493
- package/dist/operator-ui/generated.d.ts +2 -2
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +5 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/code-mode.md +19 -19
- package/documentation/meta-tools.md +4 -30
- package/documentation/operations.md +13 -13
- package/documentation/provider-conventions.md +3 -4
- package/documentation/upgrading.md +34 -8
- package/ethos.md +1 -1
- package/package.json +1 -1
- package/templates/node/package.json +1 -1
package/dist/execute.js
CHANGED
|
@@ -7,7 +7,6 @@ import { ExecutorAdmissionError, ExecutorExecutionError, isAdmittingExecutor, }
|
|
|
7
7
|
import { boundedEchoText, classifyCallError, msg } from "./errors.js";
|
|
8
8
|
import { InvocationFailure, InvocationService, } from "./invocation.js";
|
|
9
9
|
import { hasConnectorGuides } from "./skills.js";
|
|
10
|
-
import { isExplicitlyReadOnly } from "./tool-safety.js";
|
|
11
10
|
/** Keep one model-written program from amplifying into an unbounded fan-out. */
|
|
12
11
|
const EXECUTE_MAX_HOST_CALLS = 20;
|
|
13
12
|
export const EXECUTE_MAX_BATCH_CALLS = 10;
|
|
@@ -149,11 +148,7 @@ function requireEmittedBlock(raw) {
|
|
|
149
148
|
}
|
|
150
149
|
return raw;
|
|
151
150
|
}
|
|
152
|
-
const UI_SHAPE_HINT = "connecta.ui accepts exactly one
|
|
153
|
-
const MAX_UI_READ_BINDINGS = 32;
|
|
154
|
-
const MAX_UI_VIEW_ARGS = 32;
|
|
155
|
-
const UI_READ_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
156
|
-
const FORBIDDEN_UI_KEY = new Set(["__proto__", "constructor", "prototype"]);
|
|
151
|
+
const UI_SHAPE_HINT = "connecta.ui accepts exactly one argument: a non-empty string of HTML";
|
|
157
152
|
/** What the argument was, named the way the emit validator names a bad field. */
|
|
158
153
|
function describeUiArgument(raw) {
|
|
159
154
|
if (raw === null)
|
|
@@ -168,9 +163,9 @@ function describeUiArgument(raw) {
|
|
|
168
163
|
return `${/^[aeiou]/.test(kind) ? "an" : "a"} ${kind}`;
|
|
169
164
|
}
|
|
170
165
|
/**
|
|
171
|
-
* Strict U1
|
|
172
|
-
*
|
|
173
|
-
* block
|
|
166
|
+
* Strict U1 validation. There is no options parameter and no sugar form, for
|
|
167
|
+
* M1's reason: sugar is how a one-shape contract grows hair. An options bag or
|
|
168
|
+
* an MCP block object is just a non-string, and fails as one.
|
|
174
169
|
*/
|
|
175
170
|
function requireUiHtml(raw) {
|
|
176
171
|
if (typeof raw !== "string" || raw.length === 0) {
|
|
@@ -178,80 +173,11 @@ function requireUiHtml(raw) {
|
|
|
178
173
|
}
|
|
179
174
|
return raw;
|
|
180
175
|
}
|
|
181
|
-
function requireRecord(raw, label) {
|
|
182
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
183
|
-
throw guestFailure("invalid_args", `${label} must be an object`);
|
|
184
|
-
}
|
|
185
|
-
return raw;
|
|
186
|
-
}
|
|
187
|
-
function requireExactKeys(value, allowed, label) {
|
|
188
|
-
const extras = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
189
|
-
if (extras.length > 0) {
|
|
190
|
-
throw guestFailure("invalid_args", `${label} carries unsupported field(s) ${extras.map((key) => JSON.stringify(key)).join(", ")}`);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
function requireUiReadKey(raw, label) {
|
|
194
|
-
if (typeof raw !== "string" ||
|
|
195
|
-
raw.length === 0 ||
|
|
196
|
-
raw.length > 128 ||
|
|
197
|
-
FORBIDDEN_UI_KEY.has(raw)) {
|
|
198
|
-
throw guestFailure("invalid_args", `${label} must be a non-empty string of at most 128 characters and cannot be __proto__, constructor, or prototype`);
|
|
199
|
-
}
|
|
200
|
-
return raw;
|
|
201
|
-
}
|
|
202
|
-
function requireUiReads(raw) {
|
|
203
|
-
const record = requireRecord(raw, "connecta.ui options.reads");
|
|
204
|
-
const names = Object.keys(record);
|
|
205
|
-
if (names.length === 0 || names.length > MAX_UI_READ_BINDINGS) {
|
|
206
|
-
throw guestFailure("invalid_args", `connecta.ui options.reads must contain from 1 through ${MAX_UI_READ_BINDINGS} named bindings`);
|
|
207
|
-
}
|
|
208
|
-
const reads = Object.create(null);
|
|
209
|
-
for (const name of names) {
|
|
210
|
-
if (!UI_READ_NAME.test(name) || FORBIDDEN_UI_KEY.has(name)) {
|
|
211
|
-
throw guestFailure("invalid_args", `connecta.ui read binding name ${JSON.stringify(name)} must match ${UI_READ_NAME}`);
|
|
212
|
-
}
|
|
213
|
-
const value = requireRecord(record[name], `connecta.ui read binding ${JSON.stringify(name)}`);
|
|
214
|
-
requireExactKeys(value, ["address", "fixedArgs", "viewArgs"], `connecta.ui read binding ${JSON.stringify(name)}`);
|
|
215
|
-
if (typeof value.address !== "string" || value.address.length === 0) {
|
|
216
|
-
throw guestFailure("invalid_args", `connecta.ui read binding ${JSON.stringify(name)} address must be a non-empty string`);
|
|
217
|
-
}
|
|
218
|
-
const fixedArgs = value.fixedArgs === undefined
|
|
219
|
-
? {}
|
|
220
|
-
: requireRecord(value.fixedArgs, `connecta.ui read binding ${JSON.stringify(name)} fixedArgs`);
|
|
221
|
-
const rawViewArgs = value.viewArgs ?? [];
|
|
222
|
-
if (!Array.isArray(rawViewArgs) || rawViewArgs.length > MAX_UI_VIEW_ARGS) {
|
|
223
|
-
throw guestFailure("invalid_args", `connecta.ui read binding ${JSON.stringify(name)} viewArgs must be an array of at most ${MAX_UI_VIEW_ARGS} strings`);
|
|
224
|
-
}
|
|
225
|
-
const viewArgs = rawViewArgs.map((key) => requireUiReadKey(key, `connecta.ui read binding ${JSON.stringify(name)} viewArgs entry`));
|
|
226
|
-
if (new Set(viewArgs).size !== viewArgs.length) {
|
|
227
|
-
throw guestFailure("invalid_args", `connecta.ui read binding ${JSON.stringify(name)} viewArgs must not repeat a key`);
|
|
228
|
-
}
|
|
229
|
-
for (const key of viewArgs) {
|
|
230
|
-
if (Object.prototype.hasOwnProperty.call(fixedArgs, key)) {
|
|
231
|
-
throw guestFailure("invalid_args", `connecta.ui read binding ${JSON.stringify(name)} view argument ${JSON.stringify(key)} cannot override a fixed argument`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
reads[name] = {
|
|
235
|
-
address: value.address,
|
|
236
|
-
fixedArgs,
|
|
237
|
-
viewArgs,
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
return reads;
|
|
241
|
-
}
|
|
242
176
|
function requireUiPayload(values) {
|
|
243
|
-
if (values.length !== 1
|
|
177
|
+
if (values.length !== 1) {
|
|
244
178
|
throw guestFailure("invalid_args", `${UI_SHAPE_HINT}; got ${values.length} arguments`);
|
|
245
179
|
}
|
|
246
|
-
|
|
247
|
-
if (values.length === 1)
|
|
248
|
-
return { html };
|
|
249
|
-
const options = requireRecord(values[1], "connecta.ui options");
|
|
250
|
-
requireExactKeys(options, ["reads"], "connecta.ui options");
|
|
251
|
-
if (!Object.prototype.hasOwnProperty.call(options, "reads")) {
|
|
252
|
-
throw guestFailure("invalid_args", "connecta.ui options must contain reads");
|
|
253
|
-
}
|
|
254
|
-
return { html, reads: requireUiReads(options.reads) };
|
|
180
|
+
return { html: requireUiHtml(values[0]) };
|
|
255
181
|
}
|
|
256
182
|
/**
|
|
257
183
|
* Request-local collection for `connecta.emit` and `connecta.ui`. Budgets fail
|
|
@@ -309,10 +235,6 @@ export class EmitCollector {
|
|
|
309
235
|
this.assertUiVacant();
|
|
310
236
|
this.acceptUiPayload(requireUiPayload(values));
|
|
311
237
|
}
|
|
312
|
-
acceptValidatedUi(payload) {
|
|
313
|
-
this.assertUiVacant();
|
|
314
|
-
this.acceptUiPayload(payload);
|
|
315
|
-
}
|
|
316
238
|
assertUiVacant() {
|
|
317
239
|
if (this.ui) {
|
|
318
240
|
throw guestFailure("invalid_args", "connecta.ui accepts at most one payload per run: a view was already accepted and stands");
|
|
@@ -595,36 +517,6 @@ export async function buildSandboxProviders(registry, baseUrl, logger, activity,
|
|
|
595
517
|
const callNamespace = async (connectorId, toolAlias, args) => {
|
|
596
518
|
return called("call", () => invocation.invokeToolAlias(String(connectorId), String(toolAlias), sanitizeIdentifier, args ?? {}, invocationContext()));
|
|
597
519
|
};
|
|
598
|
-
/**
|
|
599
|
-
* A read binding is admitted while the program still owns the request. The
|
|
600
|
-
* shell later calls the ordinary `call_tool`, which repeats this same
|
|
601
|
-
* fail-closed check against the then-current catalog; validating here keeps
|
|
602
|
-
* a typo or destructive address from producing a view whose controls can
|
|
603
|
-
* never work, while validation at use keeps a stale view from retaining old
|
|
604
|
-
* authority.
|
|
605
|
-
*/
|
|
606
|
-
const validateUiReads = async (payload) => {
|
|
607
|
-
if (!payload.reads)
|
|
608
|
-
return payload;
|
|
609
|
-
const reads = Object.create(null);
|
|
610
|
-
for (const [name, binding] of Object.entries(payload.reads)) {
|
|
611
|
-
const resolution = await catalog.resolveTool(binding.address, limits.signal !== undefined ? { signal: limits.signal } : {});
|
|
612
|
-
if (!resolution.ok) {
|
|
613
|
-
throw new InvocationFailure({
|
|
614
|
-
...resolution.error,
|
|
615
|
-
message: `connecta.ui read binding ${JSON.stringify(name)} could not resolve ${JSON.stringify(binding.address)}: ${resolution.error.message}`,
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
if (!isExplicitlyReadOnly(resolution.resolved.definition)) {
|
|
619
|
-
throw guestFailure("destructive_tool_requires_approval", `connecta.ui read binding ${JSON.stringify(name)} refuses ${JSON.stringify(binding.address)}: the tool is not explicitly read-only`);
|
|
620
|
-
}
|
|
621
|
-
reads[name] = {
|
|
622
|
-
...binding,
|
|
623
|
-
address: `${resolution.resolved.connector.id}.${resolution.resolved.toolName}`,
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
return { html: payload.html, reads };
|
|
627
|
-
};
|
|
628
520
|
const fns = {
|
|
629
521
|
__callNamespace: callNamespace,
|
|
630
522
|
call: (address, args) => callAddress(address, args),
|
|
@@ -646,8 +538,7 @@ export async function buildSandboxProviders(registry, baseUrl, logger, activity,
|
|
|
646
538
|
if (!limits.emitCollector) {
|
|
647
539
|
throw guestFailure("unavailable", "connecta.ui is unavailable: no emission collector was configured for this execution", true);
|
|
648
540
|
}
|
|
649
|
-
|
|
650
|
-
limits.emitCollector.acceptValidatedUi(payload);
|
|
541
|
+
limits.emitCollector.acceptUi(...values);
|
|
651
542
|
},
|
|
652
543
|
batch: async (calls) => {
|
|
653
544
|
const started = Date.now();
|
|
@@ -996,7 +887,7 @@ Write one plain-JavaScript async arrow function. Use only:
|
|
|
996
887
|
- <connectorId>.<toolName>(args) for a sanitized shortcut, or connecta.call(address, args) for a canonical address.
|
|
997
888
|
- connecta.search(args), connecta.describe(args), and connecta.batch(calls) for discovery and independent read-only calls.
|
|
998
889
|
- connecta.emit(block) — { type: "text", text } or { type: "image" | "audio", data (base64), mimeType }. Success-only; ${emitBudgets.maxBlocks} blocks/${emitBudgets.maxBytes} bytes; invalid/over-budget throws.
|
|
999
|
-
- connecta.ui(html
|
|
890
|
+
- connecta.ui(html) for one display-only, success-only view; return the same summary the HTML renders.
|
|
1000
891
|
- console.log(...) — captured.
|
|
1001
892
|
|
|
1002
893
|
Programs have no portable ambient capabilities. Return JSON and reduce large results before they truncate. Fetch skills({ name: "usage" }) once for selection rules, exact result shapes, repair, examples, guide handling${connectorGuides ? ", connector-guide rules" : ""}, and runtime differences.`;
|
package/dist/index.js
CHANGED
|
@@ -42,71 +42,116 @@ function normalizeAuth(auth) {
|
|
|
42
42
|
return rank(a) - rank(b);
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
45
|
+
const admissionPoolSchema = {
|
|
46
|
+
concurrency: null,
|
|
47
|
+
maxQueueSize: null,
|
|
48
|
+
queueTimeoutMs: null,
|
|
49
|
+
retryAfterMs: null,
|
|
50
|
+
};
|
|
51
|
+
const CONFIG_SCHEMA = {
|
|
52
|
+
connectors: null,
|
|
53
|
+
auth: null,
|
|
54
|
+
storage: null,
|
|
55
|
+
publicUrl: null,
|
|
56
|
+
activity: {
|
|
57
|
+
store: null,
|
|
58
|
+
readGate: null,
|
|
59
|
+
deploymentId: null,
|
|
60
|
+
},
|
|
61
|
+
credentials: {
|
|
62
|
+
encryptionKey: null,
|
|
63
|
+
},
|
|
64
|
+
accessTokens: {
|
|
65
|
+
maxActive: null,
|
|
66
|
+
},
|
|
67
|
+
discovery: {
|
|
68
|
+
concurrency: null,
|
|
69
|
+
catalogTtlSeconds: null,
|
|
70
|
+
persistCatalog: null,
|
|
71
|
+
staleCatalogSeconds: null,
|
|
72
|
+
probeTimeoutMs: null,
|
|
73
|
+
},
|
|
74
|
+
calls: {
|
|
75
|
+
defaultTimeoutMs: null,
|
|
76
|
+
maxResultBytes: null,
|
|
77
|
+
},
|
|
78
|
+
execute: {
|
|
79
|
+
maxEmittedBytes: null,
|
|
80
|
+
maxEmittedBlocks: null,
|
|
81
|
+
},
|
|
82
|
+
admission: {
|
|
83
|
+
requests: admissionPoolSchema,
|
|
84
|
+
code: admissionPoolSchema,
|
|
85
|
+
},
|
|
86
|
+
branding: {
|
|
87
|
+
productName: null,
|
|
88
|
+
productUrl: null,
|
|
89
|
+
ownerName: null,
|
|
90
|
+
ownerUrl: null,
|
|
91
|
+
description: null,
|
|
92
|
+
pageTitle: null,
|
|
93
|
+
favicon: {
|
|
94
|
+
svg: null,
|
|
95
|
+
ico: null,
|
|
96
|
+
href: null,
|
|
97
|
+
},
|
|
98
|
+
themeColor: null,
|
|
99
|
+
},
|
|
100
|
+
logger: null,
|
|
101
|
+
serverInfo: {
|
|
102
|
+
name: null,
|
|
103
|
+
version: null,
|
|
104
|
+
title: null,
|
|
105
|
+
websiteUrl: null,
|
|
106
|
+
icons: [
|
|
107
|
+
{
|
|
108
|
+
src: null,
|
|
109
|
+
mimeType: null,
|
|
110
|
+
sizes: null,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
deploymentInfo: null,
|
|
115
|
+
executor: null,
|
|
116
|
+
};
|
|
117
|
+
function unknownOptionPaths(value, path, schema) {
|
|
118
|
+
if (schema === null)
|
|
119
|
+
return [];
|
|
120
|
+
if (Array.isArray(schema)) {
|
|
121
|
+
if (!Array.isArray(value))
|
|
122
|
+
return [];
|
|
123
|
+
return value.flatMap((entry, index) => unknownOptionPaths(entry, `${path}[${index}]`, schema[0]));
|
|
101
124
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
125
|
+
if (typeof value !== "object" || value === null)
|
|
126
|
+
return [];
|
|
127
|
+
const known = new Set(Object.keys(schema));
|
|
128
|
+
const unknown = Reflect.ownKeys(value)
|
|
129
|
+
.filter((key) => typeof key !== "string" || !known.has(key))
|
|
130
|
+
.map((key) => `${path}.${String(key)}`)
|
|
131
|
+
.sort();
|
|
132
|
+
if (unknown.length > 0)
|
|
133
|
+
return unknown;
|
|
134
|
+
for (const [key, childSchema] of Object.entries(schema)) {
|
|
135
|
+
unknown.push(...unknownOptionPaths(value[key], `${path}.${key}`, childSchema));
|
|
105
136
|
}
|
|
106
|
-
|
|
137
|
+
return unknown;
|
|
138
|
+
}
|
|
139
|
+
function rejectUnknownOptions(paths) {
|
|
140
|
+
if (paths.length === 0)
|
|
107
141
|
return;
|
|
108
|
-
throw new Error(
|
|
109
|
-
|
|
142
|
+
throw new Error(`Unknown Connecta configuration option${paths.length === 1 ? "" : "s"}:\n` +
|
|
143
|
+
paths.map((path) => `- ${path}`).join("\n"));
|
|
144
|
+
}
|
|
145
|
+
/** Reject JavaScript typos and removed options before construction does work. */
|
|
146
|
+
function assertKnownConfig(config) {
|
|
147
|
+
rejectUnknownOptions(unknownOptionPaths(config, "ConnectaConfig", CONFIG_SCHEMA));
|
|
148
|
+
const activity = config.activity;
|
|
149
|
+
if (activity !== undefined &&
|
|
150
|
+
(typeof activity !== "object" ||
|
|
151
|
+
activity === null ||
|
|
152
|
+
typeof activity.store?.record !== "function")) {
|
|
153
|
+
throw new Error("ConnectaConfig.activity.store must implement record(event)");
|
|
154
|
+
}
|
|
110
155
|
}
|
|
111
156
|
/**
|
|
112
157
|
* One-time construction warnings for deployment shapes that run fine but are
|
|
@@ -193,11 +238,7 @@ function warnInsecureConfig(config, inboundAuth, logger) {
|
|
|
193
238
|
}
|
|
194
239
|
}
|
|
195
240
|
export function createConnecta(config) {
|
|
196
|
-
|
|
197
|
-
if (hasOwn(config, "surface")) {
|
|
198
|
-
throw new Error("ConnectaConfig.surface was removed in issue #273. Remove it; connecta " +
|
|
199
|
-
"now serves one seven-tool surface.");
|
|
200
|
-
}
|
|
241
|
+
assertKnownConfig(config);
|
|
201
242
|
if (!config.executor) {
|
|
202
243
|
throw new Error("ConnectaConfig.executor is required. Configure quickJsExecutor() from " +
|
|
203
244
|
'"@zackbart/connecta/quickjs" on Node, or ' +
|
package/dist/invocation.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export interface InvocationContext<T> {
|
|
|
57
57
|
requestSignal?: AbortSignal;
|
|
58
58
|
unwrapResult?: boolean;
|
|
59
59
|
/**
|
|
60
|
-
* Caller-owned result policy. MCP
|
|
60
|
+
* Caller-owned result policy. MCP applies result paging here; code mode
|
|
61
61
|
* normally accepts the already-unwrapped value unchanged.
|
|
62
62
|
*/
|
|
63
63
|
processResult?: (value: unknown, resolved: ResolvedCatalogTool) => T | Promise<T>;
|
package/dist/meta-tools.d.ts
CHANGED
|
@@ -46,7 +46,6 @@ type ResultMode = "mcp" | "value";
|
|
|
46
46
|
export interface CallArgs {
|
|
47
47
|
address: string;
|
|
48
48
|
args?: Record<string, unknown>;
|
|
49
|
-
fields?: string[];
|
|
50
49
|
resultMode?: ResultMode;
|
|
51
50
|
timeoutMs?: number;
|
|
52
51
|
/** Retries after the first attempt; honored only for safely annotated tools. */
|