@zackbart/connecta 0.7.7 → 0.7.8
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 +42 -0
- package/dist/catalog.d.ts +5 -3
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +13 -4
- package/dist/catalog.js.map +1 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +27 -15
- package/dist/execute.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/meta-tools.d.ts +4 -4
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +85 -25
- package/dist/meta-tools.js.map +1 -1
- package/dist/registry.d.ts +12 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +17 -2
- package/dist/registry.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/catalog.ts +24 -4
- package/src/execute.ts +29 -13
- package/src/index.ts +9 -1
- package/src/meta-tools.ts +89 -25
- package/src/registry.ts +36 -1
- package/src/version.ts +1 -1
package/src/index.ts
CHANGED
|
@@ -94,7 +94,7 @@ export interface ConnectaDiscoveryConfig {
|
|
|
94
94
|
probeTimeoutMs?: number;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
/** Deployment-wide call deadlines and inline-result paging
|
|
97
|
+
/** Deployment-wide call deadlines and inline-result paging thresholds. */
|
|
98
98
|
export interface ConnectaCallsConfig {
|
|
99
99
|
/**
|
|
100
100
|
* Deadline (ms) for `call_tool`/`batch_call` calls that pass no `timeoutMs`.
|
|
@@ -111,6 +111,13 @@ export interface ConnectaCallsConfig {
|
|
|
111
111
|
* 50_000. Connectors may override it individually.
|
|
112
112
|
*/
|
|
113
113
|
maxResultBytes?: number;
|
|
114
|
+
/**
|
|
115
|
+
* Max serialized `batch_call` envelope size (bytes) before the full batch is
|
|
116
|
+
* stashed for `get_result` and only an ordered outcome summary is returned
|
|
117
|
+
* inline. Must be a finite whole number >= 1; invalid values warn and fall
|
|
118
|
+
* back to 100_000. This cap is independent of per-connector child caps.
|
|
119
|
+
*/
|
|
120
|
+
maxBatchResultBytes?: number;
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
export interface AdmissionPoolConfig {
|
|
@@ -544,6 +551,7 @@ export function createConnecta(config: ConnectaConfig): Connecta {
|
|
|
544
551
|
persistToolCatalog: config.discovery?.persistCatalog,
|
|
545
552
|
toolCatalogStaleSeconds: config.discovery?.staleCatalogSeconds,
|
|
546
553
|
maxResultBytes: config.calls?.maxResultBytes,
|
|
554
|
+
maxBatchResultBytes: config.calls?.maxBatchResultBytes,
|
|
547
555
|
credentialHealth: config.credentials?.health,
|
|
548
556
|
});
|
|
549
557
|
// Throws on every structural mistake it can see (see resolveToolkits): a
|
package/src/meta-tools.ts
CHANGED
|
@@ -406,6 +406,15 @@ async function stashResult(
|
|
|
406
406
|
};
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
+
/** Keep an oversized batch's inline outcome summary at fixed string overhead. */
|
|
410
|
+
function batchSummaryString(value: string): string {
|
|
411
|
+
const bytes = enc.encode(value);
|
|
412
|
+
const maxBytes = 512;
|
|
413
|
+
if (bytes.length <= maxBytes) return value;
|
|
414
|
+
const end = alignEndToCharBoundary(bytes, 0, maxBytes, bytes.length);
|
|
415
|
+
return `${dec.decode(bytes.slice(0, end))}…`;
|
|
416
|
+
}
|
|
417
|
+
|
|
409
418
|
/**
|
|
410
419
|
* Return `text` as a single content block; if it exceeds `cap` bytes, stash the
|
|
411
420
|
* full text and return the first `cap` bytes followed by a JSON truncation
|
|
@@ -575,10 +584,10 @@ export interface SkillArgs {
|
|
|
575
584
|
* supplies a deadline for calls that don't carry one. (execute_code, the
|
|
576
585
|
* optional tenth tool, is registered separately by registerExecuteTool.)
|
|
577
586
|
*
|
|
578
|
-
*
|
|
579
|
-
* passed in: `ConnectaConfig.calls.maxResultBytes
|
|
580
|
-
*
|
|
581
|
-
*
|
|
587
|
+
* Deployment-wide result-size caps are read off the registry view rather than
|
|
588
|
+
* passed in: `ConnectaConfig.calls.maxResultBytes`, its per-connector override,
|
|
589
|
+
* and the independent `calls.maxBatchResultBytes` final-envelope boundary each
|
|
590
|
+
* have one runtime source of truth.
|
|
582
591
|
*/
|
|
583
592
|
export function createMetaTools(
|
|
584
593
|
registry: RegistryView,
|
|
@@ -595,6 +604,7 @@ export function createMetaTools(
|
|
|
595
604
|
) {
|
|
596
605
|
// Already normalized and warned about at registry construction.
|
|
597
606
|
const globalCap = registry.maxResultBytes;
|
|
607
|
+
const batchCap = registry.maxBatchResultBytes;
|
|
598
608
|
const defaultToolTimeoutMs = normalizeTimeoutMs(opts.defaultToolTimeoutMs);
|
|
599
609
|
const probeTimeoutMs =
|
|
600
610
|
normalizeTimeoutMs(opts.probeTimeoutMs) ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
@@ -1141,26 +1151,36 @@ export function createMetaTools(
|
|
|
1141
1151
|
),
|
|
1142
1152
|
),
|
|
1143
1153
|
);
|
|
1144
|
-
let
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
:
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1154
|
+
let matchMode: "all" | "partial" = "all";
|
|
1155
|
+
const collectMatches = (mode: "all" | "partial") => {
|
|
1156
|
+
matches.length = 0;
|
|
1157
|
+
let orderBase = 0;
|
|
1158
|
+
catalogs.forEach((catalog, connectorIndex) => {
|
|
1159
|
+
const c = conns[connectorIndex];
|
|
1160
|
+
if (catalog.status === "fulfilled") {
|
|
1161
|
+
for (const ranked of rankTools(catalog.value, q, mode)) {
|
|
1162
|
+
matches.push({
|
|
1163
|
+
connectorId: c.id,
|
|
1164
|
+
connectorTitle: c.title,
|
|
1165
|
+
connectorDescription: c.description,
|
|
1166
|
+
...(connectorGuide(c)
|
|
1167
|
+
? { connectorGuideSkill: connectorSkillName(c.id) }
|
|
1168
|
+
: {}),
|
|
1169
|
+
tool: ranked.tool,
|
|
1170
|
+
score: ranked.score,
|
|
1171
|
+
order: orderBase + ranked.order,
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1160
1174
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1175
|
+
orderBase +=
|
|
1176
|
+
catalog.status === "fulfilled" ? catalog.value.length : 1;
|
|
1177
|
+
});
|
|
1178
|
+
};
|
|
1179
|
+
collectMatches("all");
|
|
1180
|
+
if (q.trim() && matches.length === 0) {
|
|
1181
|
+
matchMode = "partial";
|
|
1182
|
+
collectMatches(matchMode);
|
|
1183
|
+
}
|
|
1164
1184
|
matches.sort((a, b) => b.score - a.score || a.order - b.order);
|
|
1165
1185
|
const page = matches.slice(offset, offset + limit);
|
|
1166
1186
|
const groups: {
|
|
@@ -1235,6 +1255,9 @@ export function createMetaTools(
|
|
|
1235
1255
|
limit,
|
|
1236
1256
|
hasMore: nextOffset !== undefined,
|
|
1237
1257
|
...(nextOffset !== undefined ? { nextOffset } : {}),
|
|
1258
|
+
...(matchMode === "partial" && matches.length > 0
|
|
1259
|
+
? { matchMode }
|
|
1260
|
+
: {}),
|
|
1238
1261
|
},
|
|
1239
1262
|
"Request a smaller limit, omit fullDescriptions, or use compact schemas.",
|
|
1240
1263
|
);
|
|
@@ -1457,9 +1480,50 @@ export function createMetaTools(
|
|
|
1457
1480
|
: {}),
|
|
1458
1481
|
};
|
|
1459
1482
|
});
|
|
1460
|
-
|
|
1483
|
+
const envelope = {
|
|
1461
1484
|
results,
|
|
1462
1485
|
durationMs: Date.now() - batchStarted,
|
|
1486
|
+
};
|
|
1487
|
+
const text = serializeResultText(envelope);
|
|
1488
|
+
const bytes = enc.encode(text);
|
|
1489
|
+
if (bytes.length <= batchCap) return jsonResult(envelope);
|
|
1490
|
+
|
|
1491
|
+
const notice = await stashResult(
|
|
1492
|
+
text,
|
|
1493
|
+
registry.resultsStorage(),
|
|
1494
|
+
bytes.length,
|
|
1495
|
+
);
|
|
1496
|
+
return jsonResult({
|
|
1497
|
+
results: results.map((result) => {
|
|
1498
|
+
const common = {
|
|
1499
|
+
address: batchSummaryString(result.address),
|
|
1500
|
+
ok: !("error" in result),
|
|
1501
|
+
...("durationMs" in result
|
|
1502
|
+
? { durationMs: result.durationMs }
|
|
1503
|
+
: {}),
|
|
1504
|
+
...("attempts" in result ? { attempts: result.attempts } : {}),
|
|
1505
|
+
...("timing" in result ? { timing: result.timing } : {}),
|
|
1506
|
+
};
|
|
1507
|
+
if (!("error" in result)) return common;
|
|
1508
|
+
const error = result.error ?? "Batch call failed";
|
|
1509
|
+
const details =
|
|
1510
|
+
result.errorDetails ??
|
|
1511
|
+
errorDetails("batch_call_failed", error);
|
|
1512
|
+
return {
|
|
1513
|
+
...common,
|
|
1514
|
+
error: batchSummaryString(error),
|
|
1515
|
+
errorDetails: {
|
|
1516
|
+
code: batchSummaryString(details.code),
|
|
1517
|
+
message: batchSummaryString(details.message),
|
|
1518
|
+
retryable: details.retryable,
|
|
1519
|
+
...(details.retryAfterMs !== undefined
|
|
1520
|
+
? { retryAfterMs: details.retryAfterMs }
|
|
1521
|
+
: {}),
|
|
1522
|
+
},
|
|
1523
|
+
};
|
|
1524
|
+
}),
|
|
1525
|
+
durationMs: envelope.durationMs,
|
|
1526
|
+
...notice,
|
|
1463
1527
|
});
|
|
1464
1528
|
},
|
|
1465
1529
|
|
|
@@ -1527,7 +1591,7 @@ const CALL_DESTRUCTIVE_DESC =
|
|
|
1527
1591
|
const GET_RESULT_DESC =
|
|
1528
1592
|
"Page a truncated result stashed by call_tool/batch_call. Input { id, offset?, maxBytes? } → { text, offset, nextOffset?, totalBytes } sliced by byte offset. maxBytes is a whole number of bytes >= 1 (omit for the deployment default) and offset a whole number of bytes >= 0; an offset inside a multi-byte character is moved back to that character's first byte and the offset served is returned. Unknown/expired id is an error.";
|
|
1529
1593
|
const BATCH_DESC =
|
|
1530
|
-
"Use for 2–10 independent tools explicitly annotated readOnlyHint: true. Calls run in parallel with shared request-scoped clients; use execute_code when available instead for dependencies or in-sandbox reduction. Unannotated, write-capable, and destructive tools are refused. Batch timeout, safe retry, result mode, and diagnostics defaults may be overridden per call.";
|
|
1594
|
+
"Use for 2–10 independent tools explicitly annotated readOnlyHint: true. Calls run in parallel with shared request-scoped clients; use execute_code when available instead for dependencies or in-sandbox reduction. Unannotated, write-capable, and destructive tools are refused. Batch timeout, safe retry, result mode, and diagnostics defaults may be overridden per call. An oversized final envelope returns ordered outcome summaries plus a get_result page handle.";
|
|
1531
1595
|
const AUTHORIZE_DESC =
|
|
1532
1596
|
"Use after a connector reports auth_required. Starts downstream OAuth and returns an authorizationUrl for the operator to open. force=true wipes stored credentials first and restarts consent.";
|
|
1533
1597
|
const SKILLS_DESC =
|
package/src/registry.ts
CHANGED
|
@@ -25,6 +25,8 @@ const ID_RE = /^[a-z0-9_-]+$/;
|
|
|
25
25
|
const DEFAULT_TTL_SECONDS = 300;
|
|
26
26
|
const DEFAULT_STALE_SECONDS = 3600;
|
|
27
27
|
export const DEFAULT_MAX_RESULT_BYTES = 50_000;
|
|
28
|
+
/** Independent final-envelope boundary for `batch_call`. */
|
|
29
|
+
export const DEFAULT_MAX_BATCH_RESULT_BYTES = 100_000;
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* Smallest accepted inline-result cap. One byte is pathological but harmless:
|
|
@@ -143,6 +145,11 @@ export interface RegistryOptions {
|
|
|
143
145
|
* to the default 50_000.
|
|
144
146
|
*/
|
|
145
147
|
maxResultBytes?: number;
|
|
148
|
+
/**
|
|
149
|
+
* Cap on the complete serialized batch_call envelope. Must be a whole number
|
|
150
|
+
* of bytes >= 1; anything else warns and falls back to 100_000.
|
|
151
|
+
*/
|
|
152
|
+
maxBatchResultBytes?: number;
|
|
146
153
|
/** Tuning for the credential liveness checks (issue #24). */
|
|
147
154
|
credentialHealth?: CredentialHealthConfig;
|
|
148
155
|
}
|
|
@@ -179,6 +186,8 @@ type ConnectorOperationOptions = Pick<
|
|
|
179
186
|
export interface RegistryView {
|
|
180
187
|
/** Deployment-wide result-size cap threaded to the meta-tools. */
|
|
181
188
|
readonly maxResultBytes: number;
|
|
189
|
+
/** Independent cap for the complete serialized batch_call envelope. */
|
|
190
|
+
readonly maxBatchResultBytes: number;
|
|
182
191
|
listConnectors(): Connector[];
|
|
183
192
|
getConnector(id: string): Connector | undefined;
|
|
184
193
|
resolveAddress(
|
|
@@ -246,6 +255,8 @@ export class Registry implements RegistryView {
|
|
|
246
255
|
private readonly persistToolCatalog: boolean;
|
|
247
256
|
/** Result-size guard cap threaded to the meta-tools. */
|
|
248
257
|
readonly maxResultBytes: number;
|
|
258
|
+
/** Final batch envelope cap threaded to the meta-tools. */
|
|
259
|
+
readonly maxBatchResultBytes: number;
|
|
249
260
|
/** Proactive liveness checks over stored downstream credentials (issue #24). */
|
|
250
261
|
private readonly credentialHealth: CredentialHealthChecker;
|
|
251
262
|
|
|
@@ -262,6 +273,10 @@ export class Registry implements RegistryView {
|
|
|
262
273
|
opts.maxResultBytes,
|
|
263
274
|
DEFAULT_MAX_RESULT_BYTES,
|
|
264
275
|
);
|
|
276
|
+
this.maxBatchResultBytes = resolveMaxResultBytes(
|
|
277
|
+
opts.maxBatchResultBytes,
|
|
278
|
+
DEFAULT_MAX_BATCH_RESULT_BYTES,
|
|
279
|
+
);
|
|
265
280
|
for (const c of connectors) {
|
|
266
281
|
if (!ID_RE.test(c.id)) {
|
|
267
282
|
throw new Error(
|
|
@@ -274,7 +289,11 @@ export class Registry implements RegistryView {
|
|
|
274
289
|
this.connectors.set(c.id, c);
|
|
275
290
|
}
|
|
276
291
|
this.checkConventions(opts.logger);
|
|
277
|
-
this.checkResultCaps(
|
|
292
|
+
this.checkResultCaps(
|
|
293
|
+
opts.logger,
|
|
294
|
+
opts.maxResultBytes,
|
|
295
|
+
opts.maxBatchResultBytes,
|
|
296
|
+
);
|
|
278
297
|
this.credentialHealth = new CredentialHealthChecker(
|
|
279
298
|
{
|
|
280
299
|
listConnectors: () => this.listConnectors(),
|
|
@@ -300,6 +319,7 @@ export class Registry implements RegistryView {
|
|
|
300
319
|
private checkResultCaps(
|
|
301
320
|
logger: Logger,
|
|
302
321
|
configured: number | undefined,
|
|
322
|
+
configuredBatch: number | undefined,
|
|
303
323
|
): void {
|
|
304
324
|
if (configured !== undefined && !isValidMaxResultBytes(configured)) {
|
|
305
325
|
logger.warn(
|
|
@@ -309,6 +329,17 @@ export class Registry implements RegistryView {
|
|
|
309
329
|
`default ${DEFAULT_MAX_RESULT_BYTES} instead.`,
|
|
310
330
|
);
|
|
311
331
|
}
|
|
332
|
+
if (
|
|
333
|
+
configuredBatch !== undefined &&
|
|
334
|
+
!isValidMaxResultBytes(configuredBatch)
|
|
335
|
+
) {
|
|
336
|
+
logger.warn(
|
|
337
|
+
`[connecta] calls.maxBatchResultBytes ${configuredBatch} is not a whole ` +
|
|
338
|
+
`number of bytes >= ${MIN_MAX_RESULT_BYTES}: it would leave the final ` +
|
|
339
|
+
"batch envelope unbounded or serve an unusable page. Using the " +
|
|
340
|
+
`default ${DEFAULT_MAX_BATCH_RESULT_BYTES} instead.`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
312
343
|
for (const c of this.connectors.values()) {
|
|
313
344
|
if (
|
|
314
345
|
c.maxResultBytes !== undefined &&
|
|
@@ -831,6 +862,10 @@ export class ScopedRegistry implements RegistryView {
|
|
|
831
862
|
return this.base.maxResultBytes;
|
|
832
863
|
}
|
|
833
864
|
|
|
865
|
+
get maxBatchResultBytes(): number {
|
|
866
|
+
return this.base.maxBatchResultBytes;
|
|
867
|
+
}
|
|
868
|
+
|
|
834
869
|
/** In scope AND actually registered. */
|
|
835
870
|
private visible(id: string): boolean {
|
|
836
871
|
return (
|
package/src/version.ts
CHANGED