@zackbart/connecta 0.29.0 → 0.31.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 +99 -0
- package/README.md +126 -134
- package/bin/connecta.mjs +13 -2
- package/bin/version.mjs +1 -1
- package/dist/argument-redaction.js +230 -0
- package/dist/auth-elicitation.js +14 -9
- package/dist/authorization-handoff.js +111 -0
- package/dist/call-recovery.js +25 -0
- package/dist/catalog-cache.js +2 -2
- package/dist/catalog-service.js +103 -63
- package/dist/config-schema.d.ts +14 -7
- package/dist/config-schema.js +29 -10
- package/dist/config.d.ts +2 -0
- package/dist/config.js +12 -7
- package/dist/connectors/guarded-fetch.d.ts +9 -1
- package/dist/connectors/guarded-fetch.js +17 -5
- package/dist/connectors/negotiation-cache.js +1 -1
- package/dist/downstream-input-context.js +9 -0
- package/dist/downstream-input.js +13 -11
- package/dist/errors.d.ts +38 -12
- package/dist/errors.js +19 -9
- package/dist/execute.js +375 -43
- package/dist/executor-result.js +11 -9
- package/dist/executors/quickjs-child.js +12 -5
- package/dist/executors/quickjs-protocol.js +5 -6
- package/dist/executors/quickjs-runtime.js +6 -1
- package/dist/executors/quickjs.js +20 -4
- package/dist/guest-runtime.js +52 -2
- package/dist/guest-types.d.ts +47 -5
- package/dist/index.d.ts +1 -1
- package/dist/invocation.js +72 -14
- package/dist/meta-output.js +0 -15
- package/dist/meta-tools.js +166 -95
- package/dist/oauth-handoff.js +88 -11
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/view.d.ts +9 -0
- package/dist/operator-ui/view.js +11 -0
- package/dist/program-result.js +5 -0
- package/dist/program-writes.js +7 -48
- package/dist/provider.d.ts +8 -6
- package/dist/provider.js +3 -4
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/request-token.js +83 -0
- package/dist/providers/_shared/rest/dispatch.js +41 -0
- package/dist/providers/_shared/rest/operation-index.d.ts +133 -0
- package/dist/providers/_shared/rest/operation-index.js +527 -0
- package/dist/providers/_shared/rest/tools.d.ts +141 -0
- package/dist/providers/_shared/rest/tools.js +569 -0
- package/dist/providers/_shared/rest/value-safety.d.ts +151 -0
- package/dist/providers/_shared/rest/value-safety.js +580 -0
- package/dist/providers/cloudflare/graphql.js +385 -0
- package/dist/providers/cloudflare/index.d.ts +47 -51
- package/dist/providers/cloudflare/index.js +188 -1997
- package/dist/providers/cloudflare/openapi.generated.js +3347 -0
- package/dist/providers/cloudflare/rest.d.ts +37 -0
- package/dist/providers/cloudflare/rest.js +1056 -0
- package/dist/providers/cloudflare/skill.generated.js +7 -4
- package/dist/providers/cloudflare/value-safety.js +1247 -0
- package/dist/providers/infisical/index.d.ts +7 -0
- package/dist/providers/infisical/index.js +713 -0
- package/dist/providers/infisical/skill.generated.js +8 -0
- package/dist/providers/notion/index.d.ts +35 -31
- package/dist/providers/notion/index.js +60 -1499
- package/dist/providers/notion/openapi.generated.js +75 -0
- package/dist/providers/notion/rest.d.ts +24 -0
- package/dist/providers/notion/rest.js +1067 -0
- package/dist/providers/notion/skill.generated.js +5 -4
- package/dist/providers/notion/value-safety.js +31 -0
- package/dist/providers/stripe/index.d.ts +26 -20
- package/dist/providers/stripe/index.js +103 -130
- package/dist/providers/stripe/openapi.generated.js +649 -0
- package/dist/providers/stripe/rest.d.ts +14 -0
- package/dist/providers/stripe/rest.js +395 -0
- package/dist/providers/stripe/skill.generated.js +7 -5
- package/dist/providers/stripe/value-safety.js +705 -0
- package/dist/providers/vercel/index.d.ts +31 -22
- package/dist/providers/vercel/index.js +96 -853
- package/dist/providers/vercel/openapi.generated.js +456 -0
- package/dist/providers/vercel/rest.d.ts +20 -0
- package/dist/providers/vercel/rest.js +1002 -0
- package/dist/providers/vercel/skill.generated.js +4 -5
- package/dist/providers/vercel/value-safety.js +328 -0
- package/dist/registry.d.ts +8 -6
- package/dist/registry.js +134 -61
- package/dist/resource-read.js +4 -1
- package/dist/routes/mcp.js +11 -2
- package/dist/sent-secrets.d.ts +54 -13
- package/dist/sent-secrets.js +770 -231
- package/dist/skills.js +26 -16
- package/dist/sqlite.js +21 -6
- package/dist/storage/keys.js +20 -5
- package/dist/storage/memory.js +9 -4
- package/dist/storage/sql.js +18 -9
- package/dist/types.d.ts +17 -3
- package/dist/usage-guide.js +49 -40
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +2 -1
- package/dist/write-outcome.js +47 -0
- package/documentation/README.md +64 -0
- package/documentation/architecture.md +241 -97
- package/documentation/auth.md +138 -5
- package/documentation/code-mode.md +224 -107
- package/documentation/deploying.md +146 -0
- package/documentation/integrating.md +170 -0
- package/documentation/meta-tools.md +142 -30
- package/documentation/operating.md +135 -0
- package/documentation/operator-tests.md +4 -0
- package/documentation/operator-ui.md +132 -0
- package/documentation/provider-auth-migration.md +307 -0
- package/documentation/provider-migration-0.29.md +22 -5
- package/examples/worker/README.md +9 -2
- package/package.json +9 -2
- package/templates/node/README.md +9 -2
- package/templates/node/package.json +1 -1
- package/dist/providers/vercel/mcp-ownership.js +0 -212
package/dist/executor-result.js
CHANGED
|
@@ -18,12 +18,12 @@ const TRUNCATION_HINT = "filter/map/slice data inside execute_code and return on
|
|
|
18
18
|
* of what the program returned, and truncation happens exactly once no matter
|
|
19
19
|
* how many hops the value takes.
|
|
20
20
|
*/
|
|
21
|
-
function truncationEnvelope(text, maxChars, totalChars = text.length) {
|
|
21
|
+
function truncationEnvelope(text, maxChars, totalChars = text.length, hint = TRUNCATION_HINT) {
|
|
22
22
|
const base = {
|
|
23
23
|
truncated: true,
|
|
24
24
|
preview: "",
|
|
25
25
|
totalChars,
|
|
26
|
-
hint
|
|
26
|
+
hint,
|
|
27
27
|
};
|
|
28
28
|
let budget = Math.max(0, maxChars - JSON.stringify(base).length);
|
|
29
29
|
for (let attempt = 0; attempt < 8 && budget > 0; attempt += 1) {
|
|
@@ -37,11 +37,12 @@ function truncationEnvelope(text, maxChars, totalChars = text.length) {
|
|
|
37
37
|
}
|
|
38
38
|
return { ...base, preview: text.slice(0, budget) };
|
|
39
39
|
}
|
|
40
|
-
export function guardExecuteResultValue(value, maxChars = MAX_EXECUTE_RESULT_CHARS) {
|
|
40
|
+
export function guardExecuteResultValue(value, maxChars = MAX_EXECUTE_RESULT_CHARS, hintPrefix) {
|
|
41
|
+
const hint = hintPrefix ? `${hintPrefix} ${TRUNCATION_HINT}` : TRUNCATION_HINT;
|
|
41
42
|
const text = serializeResultText(value);
|
|
42
43
|
if (text.length <= maxChars)
|
|
43
44
|
return value;
|
|
44
|
-
// A
|
|
45
|
+
// A custom executor may already have guarded this value at a larger cap.
|
|
45
46
|
// Shrink its preview once more without nesting notices or losing original size.
|
|
46
47
|
if (value !== null && typeof value === "object") {
|
|
47
48
|
const prior = value;
|
|
@@ -52,10 +53,10 @@ export function guardExecuteResultValue(value, maxChars = MAX_EXECUTE_RESULT_CHA
|
|
|
52
53
|
Number.isSafeInteger(prior.totalChars) &&
|
|
53
54
|
prior.totalChars >= prior.preview.length &&
|
|
54
55
|
Object.keys(value).length === 4) {
|
|
55
|
-
return truncationEnvelope(prior.preview, maxChars, prior.totalChars);
|
|
56
|
+
return truncationEnvelope(prior.preview, maxChars, prior.totalChars, hint);
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
|
-
return truncationEnvelope(text, maxChars);
|
|
59
|
+
return truncationEnvelope(text, maxChars, text.length, hint);
|
|
59
60
|
}
|
|
60
61
|
export function truncateExecuteText(text, max) {
|
|
61
62
|
if (text.length <= max)
|
|
@@ -63,8 +64,8 @@ export function truncateExecuteText(text, max) {
|
|
|
63
64
|
return `${text.slice(0, max)}\n--- TRUNCATED (${text.length} chars total) — filter/map/slice data inside your code and return only what you need ---`;
|
|
64
65
|
}
|
|
65
66
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
67
|
+
* Validate serialization before IPC without discarding the program return.
|
|
68
|
+
* The host applies the presentation cap after it can stash the full value.
|
|
68
69
|
*/
|
|
69
70
|
export function prepareExecuteResultForTransport(outcome) {
|
|
70
71
|
// QuickJS already bounds captured logs at source (entries + cumulative
|
|
@@ -82,8 +83,9 @@ export function prepareExecuteResultForTransport(outcome) {
|
|
|
82
83
|
};
|
|
83
84
|
}
|
|
84
85
|
try {
|
|
86
|
+
serializeResultText(outcome.result);
|
|
85
87
|
return {
|
|
86
|
-
result:
|
|
88
|
+
result: outcome.result,
|
|
87
89
|
...(logs ? { logs } : {}),
|
|
88
90
|
};
|
|
89
91
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { prepareExecuteResultForTransport } from "../executor-result.js";
|
|
2
2
|
import { InvocationFailure } from "../invocation.js";
|
|
3
3
|
import { msg } from "../errors.js";
|
|
4
|
-
import { MAX_QUICKJS_IPC_BYTES, MAX_QUICKJS_HOST_RPC_BYTES, serializedBytes, stringifyBounded, } from "./quickjs-protocol.js";
|
|
4
|
+
import { MAX_QUICKJS_IPC_BYTES, MAX_QUICKJS_RESULT_BYTES, QUICKJS_RESULT_CHUNK_CHARS, MAX_QUICKJS_HOST_RPC_BYTES, serializedBytes, stringifyBounded, } from "./quickjs-protocol.js";
|
|
5
5
|
import { executeQuickJs, prepareQuickJs } from "./quickjs-runtime.js";
|
|
6
6
|
let activeJobId;
|
|
7
7
|
let nextCallId = 1;
|
|
@@ -51,6 +51,13 @@ function fixedTransportFailure(message) {
|
|
|
51
51
|
},
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
function sendResult(jobId, payloadJson) {
|
|
55
|
+
for (let offset = 0; offset + QUICKJS_RESULT_CHUNK_CHARS < payloadJson.length; offset += QUICKJS_RESULT_CHUNK_CHARS) {
|
|
56
|
+
send({ type: "result-chunk", jobId, payloadJson: payloadJson.slice(offset, offset + QUICKJS_RESULT_CHUNK_CHARS) });
|
|
57
|
+
}
|
|
58
|
+
const tail = Math.floor((payloadJson.length - 1) / QUICKJS_RESULT_CHUNK_CHARS) * QUICKJS_RESULT_CHUNK_CHARS;
|
|
59
|
+
send({ type: "result", jobId, payloadJson: payloadJson.slice(tail) });
|
|
60
|
+
}
|
|
54
61
|
async function run(payload) {
|
|
55
62
|
if (activeJobId !== undefined) {
|
|
56
63
|
throw new Error("QuickJS child received overlapping executions.");
|
|
@@ -69,12 +76,12 @@ async function run(payload) {
|
|
|
69
76
|
};
|
|
70
77
|
let payloadJson;
|
|
71
78
|
try {
|
|
72
|
-
payloadJson = stringifyBounded(prepared, "QuickJS execution result");
|
|
79
|
+
payloadJson = stringifyBounded(prepared, "QuickJS execution result", MAX_QUICKJS_RESULT_BYTES);
|
|
73
80
|
}
|
|
74
81
|
catch (err) {
|
|
75
|
-
payloadJson = JSON.stringify(fixedTransportFailure(`QuickJS execution result exceeded the ${
|
|
82
|
+
payloadJson = JSON.stringify(fixedTransportFailure(`QuickJS execution result exceeded the ${MAX_QUICKJS_RESULT_BYTES}-byte aggregate transport limit: ${msg(err)}`));
|
|
76
83
|
}
|
|
77
|
-
|
|
84
|
+
sendResult(payload.id, payloadJson);
|
|
78
85
|
}
|
|
79
86
|
finally {
|
|
80
87
|
activeJobId = undefined;
|
|
@@ -118,7 +125,7 @@ process.on("message", (message) => {
|
|
|
118
125
|
const payload = JSON.parse(message.payloadJson);
|
|
119
126
|
void run(payload).catch((err) => {
|
|
120
127
|
const payloadJson = JSON.stringify(fixedTransportFailure(`QuickJS child failed: ${msg(err)}`));
|
|
121
|
-
|
|
128
|
+
sendResult(payload.id, payloadJson);
|
|
122
129
|
activeJobId = undefined;
|
|
123
130
|
});
|
|
124
131
|
});
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// separate 512 KiB budget measured after both JSON encodings. The final result
|
|
4
|
-
// is shaped to 24k characters; even if every character takes its worst-case
|
|
5
|
-
// seven transport bytes, that leaves ample structural overhead below this hard
|
|
6
|
-
// process.send ceiling.
|
|
1
|
+
// Result messages remain bounded even when a full return travels in chunks.
|
|
2
|
+
// The host's presentation guard stashes the completed value after assembly.
|
|
7
3
|
export const MAX_QUICKJS_IPC_BYTES = 1024 * 1024;
|
|
4
|
+
/** Aggregate result transport bound, independent of per-message IPC and log caps. */
|
|
5
|
+
export const MAX_QUICKJS_RESULT_BYTES = 64 * 1024 * 1024;
|
|
6
|
+
export const QUICKJS_RESULT_CHUNK_CHARS = 65_536;
|
|
8
7
|
export const MAX_QUICKJS_LOG_TRANSPORT_BYTES = 512 * 1024;
|
|
9
8
|
/** Preserve #84's stopgap before a host value enters the child/WASM process. */
|
|
10
9
|
export const MAX_QUICKJS_HOST_RPC_BYTES = 256 * 1024;
|
|
@@ -253,7 +253,12 @@ function installBridge(ctx, providers, logs, onLog) {
|
|
|
253
253
|
// The error identity and typed facts stay in host state. A guest
|
|
254
254
|
// may edit public fields without changing the retained failure.
|
|
255
255
|
bridge.failures.push({ error: error.dup(), call });
|
|
256
|
-
for (const [key, value] of Object.entries({
|
|
256
|
+
for (const [key, value] of Object.entries({
|
|
257
|
+
code: call.code,
|
|
258
|
+
retryable: call.retryable,
|
|
259
|
+
details: call,
|
|
260
|
+
data: call,
|
|
261
|
+
})) {
|
|
257
262
|
const property = ctx.unwrapResult(ctx.evalCode(`(${JSON.stringify(value)})`));
|
|
258
263
|
ctx.setProp(error, key, property);
|
|
259
264
|
property.dispose();
|
|
@@ -21,7 +21,7 @@ import { InvocationFailure } from "../invocation.js";
|
|
|
21
21
|
import { brandExecutor } from "../executor-contract.js";
|
|
22
22
|
import { MAX_EXECUTE_LOG_CHARS } from "../executor-result.js";
|
|
23
23
|
import { detach, fromSignal, runEdge } from "../runtime/run.js";
|
|
24
|
-
import { hostCallLabel, MAX_QUICKJS_IPC_BYTES, MAX_QUICKJS_HOST_RPC_BYTES, serializedBytes, stringifyBounded, } from "./quickjs-protocol.js";
|
|
24
|
+
import { hostCallLabel, MAX_QUICKJS_IPC_BYTES, MAX_QUICKJS_RESULT_BYTES, QUICKJS_RESULT_CHUNK_CHARS, MAX_QUICKJS_HOST_RPC_BYTES, serializedBytes, stringifyBounded, } from "./quickjs-protocol.js";
|
|
25
25
|
export { normalizeCode } from "./quickjs-runtime.js";
|
|
26
26
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
27
27
|
const DEFAULT_CPU_TIME_MS = 250;
|
|
@@ -244,6 +244,8 @@ class QuickJsChildPool {
|
|
|
244
244
|
id,
|
|
245
245
|
logs: [],
|
|
246
246
|
logChars: 0,
|
|
247
|
+
resultChunks: [],
|
|
248
|
+
resultBytes: 0,
|
|
247
249
|
providers: providerMap,
|
|
248
250
|
outcome: Deferred.makeUnsafe(),
|
|
249
251
|
};
|
|
@@ -456,15 +458,29 @@ class QuickJsChildPool {
|
|
|
456
458
|
await this.handleHostCall(slot, child.process, active, message);
|
|
457
459
|
return;
|
|
458
460
|
}
|
|
459
|
-
if (message.type !== "result" || message.jobId !== active.id)
|
|
461
|
+
if ((message.type !== "result" && message.type !== "result-chunk") || message.jobId !== active.id)
|
|
460
462
|
return;
|
|
461
|
-
if (
|
|
463
|
+
if (message.payloadJson.length > QUICKJS_RESULT_CHUNK_CHARS ||
|
|
464
|
+
(message.type === "result-chunk" && message.payloadJson.length !== QUICKJS_RESULT_CHUNK_CHARS) ||
|
|
465
|
+
message.payloadJson.length === 0) {
|
|
466
|
+
this.rejectActive(slot, new Error("QuickJS execution result exceeded the IPC limit."));
|
|
467
|
+
this.recycle(slot);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const bytes = serializedBytes(message.payloadJson);
|
|
471
|
+
if (active.resultBytes + bytes > MAX_QUICKJS_RESULT_BYTES) {
|
|
462
472
|
this.rejectActive(slot, new Error("QuickJS execution result exceeded the IPC limit."));
|
|
463
473
|
this.recycle(slot);
|
|
464
474
|
return;
|
|
465
475
|
}
|
|
466
476
|
try {
|
|
467
|
-
|
|
477
|
+
stringifyBounded(message, "QuickJS result IPC envelope");
|
|
478
|
+
active.resultBytes += bytes;
|
|
479
|
+
active.resultChunks.push(message.payloadJson);
|
|
480
|
+
if (message.type === "result-chunk")
|
|
481
|
+
return;
|
|
482
|
+
const payload = JSON.parse(active.resultChunks.join(""));
|
|
483
|
+
active.resultChunks.length = 0;
|
|
468
484
|
// A result ends any crash streak: the next crash backs off from 100 ms.
|
|
469
485
|
slot.crashes.length = 0;
|
|
470
486
|
slot.backoff = undefined;
|
package/dist/guest-runtime.js
CHANGED
|
@@ -80,10 +80,57 @@ export function guestInitializer() {
|
|
|
80
80
|
const promiseCatch = Function.prototype.call.bind(NativePromise.prototype.catch);
|
|
81
81
|
const promiseFinally = Function.prototype.call.bind(NativePromise.prototype.finally);
|
|
82
82
|
const push = Function.prototype.call.bind(Array.prototype.push);
|
|
83
|
+
const apply = Function.prototype.call.bind(Function.prototype.apply);
|
|
83
84
|
const NativeError = Error;
|
|
85
|
+
const NativeWeakMap = WeakMap;
|
|
86
|
+
const isArray = Array.isArray;
|
|
87
|
+
const weakGet = Function.prototype.call.bind(WeakMap.prototype.get);
|
|
88
|
+
const weakSet = Function.prototype.call.bind(WeakMap.prototype.set);
|
|
84
89
|
return freeze((provider) => {
|
|
85
90
|
const pending = [];
|
|
86
91
|
const emit = provider.emit;
|
|
92
|
+
const hostCall = provider.call;
|
|
93
|
+
const hostResult = provider.result;
|
|
94
|
+
const nativeRefs = new NativeWeakMap();
|
|
95
|
+
async function inline(value) {
|
|
96
|
+
if (!value || typeof value.transfer !== "string" || !value.handle) return value;
|
|
97
|
+
// A completed downstream call stays successful if transfer storage fails.
|
|
98
|
+
// Preserve its handle so the caller can page later, without repeating a write.
|
|
99
|
+
try {
|
|
100
|
+
let text = "", page;
|
|
101
|
+
do {
|
|
102
|
+
page = await hostResult(value.transfer);
|
|
103
|
+
text += page.text;
|
|
104
|
+
} while (page.hasMore);
|
|
105
|
+
return { data: value.handle.valueFormat === "json" ? JSON.parse(text) : text,
|
|
106
|
+
format: value.handle.valueFormat };
|
|
107
|
+
} catch { return value.handle; }
|
|
108
|
+
}
|
|
109
|
+
async function call() {
|
|
110
|
+
const reply = await apply(hostCall, undefined, arguments);
|
|
111
|
+
if (!reply || !reply.native) return inline(reply);
|
|
112
|
+
const native = await inline(reply.native.value);
|
|
113
|
+
if (native.format === "paged" && !reply.value) return native;
|
|
114
|
+
const value = reply.value ? await inline(reply.value) : {
|
|
115
|
+
data: reply.native.dataKey ? native.data[reply.native.dataKey] : native.data, format: "json"
|
|
116
|
+
};
|
|
117
|
+
if (value.format === "paged") return value;
|
|
118
|
+
if (native.format === "paged") {
|
|
119
|
+
value.contentResult = native;
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
if (native.format === "json" && native.data && isArray(native.data.content)) {
|
|
123
|
+
const content = native.data.content;
|
|
124
|
+
// Only host-issued refs cross back. Binary data never needs a second
|
|
125
|
+
// guest-to-host transfer, and guest edits cannot mint native pointers.
|
|
126
|
+
for (let i = 0; i < content.length; i++) {
|
|
127
|
+
weakSet(nativeRefs, content[i], { ref: reply.native.ref, index: i });
|
|
128
|
+
}
|
|
129
|
+
value.content = content;
|
|
130
|
+
weakSet(nativeRefs, value, { ref: reply.native.ref });
|
|
131
|
+
}
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
87
134
|
function trackEmission(task) {
|
|
88
135
|
const entry = { handled: false, settled: promiseThen(task,
|
|
89
136
|
() => ({ ok: true }), error => ({ ok: false, error })) };
|
|
@@ -109,11 +156,14 @@ export function guestInitializer() {
|
|
|
109
156
|
__proto__: null,
|
|
110
157
|
search: provider.search,
|
|
111
158
|
describe: provider.describe,
|
|
112
|
-
call
|
|
159
|
+
call,
|
|
113
160
|
read: provider.read,
|
|
114
161
|
result: provider.result,
|
|
115
162
|
skill: provider.skill,
|
|
116
|
-
emit(block) {
|
|
163
|
+
emit(block) {
|
|
164
|
+
const ref = block && typeof block === "object" ? weakGet(nativeRefs, block) : undefined;
|
|
165
|
+
return trackEmission(emit(ref || block));
|
|
166
|
+
}
|
|
117
167
|
});
|
|
118
168
|
globalThis.connecta = namespace;
|
|
119
169
|
defineProperties(globalThis, {
|
package/dist/guest-types.d.ts
CHANGED
|
@@ -15,7 +15,8 @@ export interface GuestApi {
|
|
|
15
15
|
timeoutMs?: number;
|
|
16
16
|
}): Promise<GuestResult>;
|
|
17
17
|
read(uri: string): Promise<GuestResourceResult>;
|
|
18
|
-
result(id: string, options?: {
|
|
18
|
+
result(id: string | GuestResultHandle, options?: {
|
|
19
|
+
page?: number;
|
|
19
20
|
offset?: number;
|
|
20
21
|
maxBytes?: number;
|
|
21
22
|
}): Promise<GuestResultPage>;
|
|
@@ -24,7 +25,7 @@ export interface GuestApi {
|
|
|
24
25
|
text: string;
|
|
25
26
|
format: "text";
|
|
26
27
|
}>;
|
|
27
|
-
emit(block:
|
|
28
|
+
emit(block: GuestNativeBlock | GuestResult): PromiseLike<void>;
|
|
28
29
|
}
|
|
29
30
|
export interface GuestResourceResult {
|
|
30
31
|
contents: Array<{
|
|
@@ -36,12 +37,52 @@ export interface GuestResourceResult {
|
|
|
36
37
|
blob: string;
|
|
37
38
|
})>;
|
|
38
39
|
}
|
|
39
|
-
export
|
|
40
|
+
export interface GuestResultHandle {
|
|
41
|
+
format: "paged";
|
|
42
|
+
valueFormat: "json" | "text";
|
|
43
|
+
resultId?: string;
|
|
44
|
+
totalBytes: number;
|
|
45
|
+
truncated: true;
|
|
46
|
+
hint: string;
|
|
47
|
+
}
|
|
48
|
+
/** Inline MCP rich results expose original blocks; emit(result) forwards all of them. */
|
|
49
|
+
export type GuestResult = ({
|
|
40
50
|
data: unknown;
|
|
41
51
|
format: "json";
|
|
42
52
|
} | {
|
|
43
53
|
data: string;
|
|
44
54
|
format: "text";
|
|
55
|
+
} | GuestResultHandle) & {
|
|
56
|
+
content?: GuestNativeBlock[];
|
|
57
|
+
/** Paging handle when native content exceeds the inline cap or transfer fails. */
|
|
58
|
+
contentResult?: GuestResultHandle;
|
|
59
|
+
};
|
|
60
|
+
/** Native call blocks can be forwarded unchanged, including annotations and metadata. */
|
|
61
|
+
export type GuestNativeBlock = (GuestBlock | {
|
|
62
|
+
type: "resource";
|
|
63
|
+
resource: {
|
|
64
|
+
uri: string;
|
|
65
|
+
mimeType?: string;
|
|
66
|
+
} & ({
|
|
67
|
+
text: string;
|
|
68
|
+
} | {
|
|
69
|
+
blob: string;
|
|
70
|
+
});
|
|
71
|
+
} | {
|
|
72
|
+
type: "resource_link";
|
|
73
|
+
uri: string;
|
|
74
|
+
name: string;
|
|
75
|
+
title?: string;
|
|
76
|
+
description?: string;
|
|
77
|
+
mimeType?: string;
|
|
78
|
+
size?: number;
|
|
79
|
+
}) & {
|
|
80
|
+
annotations?: {
|
|
81
|
+
audience?: Array<"user" | "assistant">;
|
|
82
|
+
priority?: number;
|
|
83
|
+
lastModified?: string;
|
|
84
|
+
};
|
|
85
|
+
_meta?: Record<string, unknown>;
|
|
45
86
|
};
|
|
46
87
|
export interface GuestResultPage {
|
|
47
88
|
resultId: string;
|
|
@@ -79,7 +120,9 @@ export interface CatalogDescribeArgs {
|
|
|
79
120
|
fullDescriptions?: boolean;
|
|
80
121
|
}
|
|
81
122
|
type GuideRequiredReason = "connector_required" | "approval_required" | "schema_truncated";
|
|
82
|
-
interface CatalogFailureDetail {
|
|
123
|
+
interface CatalogFailureDetail extends Partial<import("./errors.js").AuthorizationHandoff> {
|
|
124
|
+
nextAction?: CallErrorDetails["nextAction"];
|
|
125
|
+
retry?: string;
|
|
83
126
|
code: string;
|
|
84
127
|
message: string;
|
|
85
128
|
retryable: boolean;
|
|
@@ -92,7 +135,6 @@ interface CatalogDescriptionFailureDetail extends CatalogFailureDetail {
|
|
|
92
135
|
}
|
|
93
136
|
interface CatalogSearchFailure extends CatalogFailureDetail {
|
|
94
137
|
connector: string;
|
|
95
|
-
recovery?: CallErrorDetails["recovery"];
|
|
96
138
|
nextAction?: Extract<NonNullable<CallErrorDetails["nextAction"]>, {
|
|
97
139
|
tool: "authorize_connector";
|
|
98
140
|
}>;
|
package/dist/index.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export { validateToolInput } from "./validate.js";
|
|
|
35
35
|
export type { ValidateToolInputOptions } from "./validate.js";
|
|
36
36
|
export { memoryStorage } from "./storage/memory.js";
|
|
37
37
|
export { CONNECTA_VERSION } from "./version.js";
|
|
38
|
-
export type { GuestApi, GuestResourceResult, GuestResult, GuestResultPage, GuestBlock } from "./guest-types.js";
|
|
38
|
+
export type { GuestApi, GuestResourceResult, GuestResult, GuestResultPage, GuestBlock, GuestNativeBlock, } from "./guest-types.js";
|
|
39
39
|
export type { Registry } from "./registry.js";
|
|
40
40
|
export type { RemoteMcpOptions, RemoteMcpAuth, RemoteMcpRedirectPolicy } from "./connectors/remote-mcp.js";
|
|
41
41
|
export type { ApiHandlerContext, ApiOAuthAccess, ApiOAuthClientAuthentication, ApiOAuthConfig, ApiOptions, ApiTool, } from "./connectors/api.js";
|
package/dist/invocation.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withAuthorizationHandoff } from "./authorization-handoff.js";
|
|
1
2
|
import { hasControlCharacters } from "./tool-name.js";
|
|
2
3
|
import { surfaceAllowsTool } from "./tool-safety.js";
|
|
3
4
|
import { Cause, Effect, Exit } from "effect";
|
|
@@ -9,8 +10,10 @@ import { splitAddress } from "./registry.js";
|
|
|
9
10
|
import { runEdge, withDeadlineEffect } from "./runtime/run.js";
|
|
10
11
|
import { validateCatalogToolInput } from "./validate.js";
|
|
11
12
|
import { sentSecretsFor, sentSecretsForRequest, trackCredentialReads } from "./sent-secrets.js";
|
|
13
|
+
import { recoveryFor } from "./call-recovery.js";
|
|
14
|
+
import { classifyWriteOutcome } from "./write-outcome.js";
|
|
12
15
|
import { classificationDigest, recordAuthFailure, recordCallEntry, replayClassificationDigest, resolveInvocationAuth, } from "./invocation-auth.js";
|
|
13
|
-
import { downstreamContinuation, isDownstreamInputResult, assertDownstreamOutputSafe, } from "./downstream-input-context.js";
|
|
16
|
+
import { downstreamContinuation, recordDownstreamArgumentEcho, isDownstreamInputResult, assertDownstreamOutputSafe, } from "./downstream-input-context.js";
|
|
14
17
|
function defined(values) {
|
|
15
18
|
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
|
|
16
19
|
}
|
|
@@ -111,6 +114,7 @@ export class InvocationFailure extends Error {
|
|
|
111
114
|
recovery;
|
|
112
115
|
nextAction;
|
|
113
116
|
retry;
|
|
117
|
+
data;
|
|
114
118
|
constructor(details) {
|
|
115
119
|
super(details.message);
|
|
116
120
|
this.details = details;
|
|
@@ -123,6 +127,7 @@ export class InvocationFailure extends Error {
|
|
|
123
127
|
this.recovery = details.recovery;
|
|
124
128
|
this.nextAction = details.nextAction;
|
|
125
129
|
this.retry = details.retry;
|
|
130
|
+
this.data = details;
|
|
126
131
|
}
|
|
127
132
|
}
|
|
128
133
|
/**
|
|
@@ -155,7 +160,7 @@ export class InvocationService {
|
|
|
155
160
|
pipeline(address, args, context) {
|
|
156
161
|
return Effect.gen({ self: this }, function* () {
|
|
157
162
|
const started = Date.now();
|
|
158
|
-
|
|
163
|
+
let argumentEcho = {};
|
|
159
164
|
let catalogMs = 0;
|
|
160
165
|
let admissionMs = 0;
|
|
161
166
|
let connectorMs = 0;
|
|
@@ -163,6 +168,9 @@ export class InvocationService {
|
|
|
163
168
|
let attempts = 0;
|
|
164
169
|
let resultBytes;
|
|
165
170
|
let dispatchedToConnector = false;
|
|
171
|
+
// The context a dispatched call ran with, read for recovery facts the
|
|
172
|
+
// connector recorded before an interrupting deadline (call-recovery.ts).
|
|
173
|
+
let dispatchContext;
|
|
166
174
|
let preInvocationAuthFailure = false;
|
|
167
175
|
let answered = false;
|
|
168
176
|
const sentSecrets = sentSecretsForRequest(this.catalog.requestScope);
|
|
@@ -214,6 +222,10 @@ export class InvocationService {
|
|
|
214
222
|
const enrich = (error, target) => {
|
|
215
223
|
if (!target)
|
|
216
224
|
return error;
|
|
225
|
+
const recovery = recoveryFor(dispatchContext);
|
|
226
|
+
const recoveryRetry = recovery?.["idempotencyKey"]
|
|
227
|
+
? " A deliberate retry with uncertainCall.recovery.idempotencyKey and the exact original arguments cannot repeat the write while the vendor retains that key (Stripe keeps v1 keys at least 24 hours); after that, look the object up before retrying."
|
|
228
|
+
: "";
|
|
217
229
|
if (isTimeoutFailure(error) && dispatchedToConnector && resolved?.definition.classification === "write") {
|
|
218
230
|
const echoed = argumentEcho;
|
|
219
231
|
return {
|
|
@@ -227,9 +239,35 @@ export class InvocationService {
|
|
|
227
239
|
address: `${target.connector.id}.${target.toolName}`,
|
|
228
240
|
...echoed,
|
|
229
241
|
...("args" in echoed ? {} : { argsOmitted: true }),
|
|
242
|
+
...(recovery ? { recovery } : {}),
|
|
230
243
|
},
|
|
231
244
|
retry: "Do not retry automatically. Check whether the write took effect first." +
|
|
232
|
-
|
|
245
|
+
recoveryRetry +
|
|
246
|
+
(echoed.argsRedacted
|
|
247
|
+
? " Sensitive fields are omitted; use the original arguments if reconciliation requires another call."
|
|
248
|
+
: "args" in echoed
|
|
249
|
+
? ""
|
|
250
|
+
: " The arguments could not be echoed safely; use the exact arguments you sent."),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
// Any other dispatched write whose outcome is unknown (a lost response
|
|
254
|
+
// body, a 5xx, an unreadable reply) keeps the facts its connector
|
|
255
|
+
// recorded before dispatch, here and through program write accounting.
|
|
256
|
+
if (recovery &&
|
|
257
|
+
!error.uncertainCall &&
|
|
258
|
+
dispatchedToConnector &&
|
|
259
|
+
resolved?.definition.classification === "write" &&
|
|
260
|
+
classifyWriteOutcome({ ok: false, dispatched: true, answered, error }) === "unknown") {
|
|
261
|
+
const echoed = argumentEcho;
|
|
262
|
+
error = {
|
|
263
|
+
...error,
|
|
264
|
+
uncertainCall: {
|
|
265
|
+
address: `${target.connector.id}.${target.toolName}`,
|
|
266
|
+
...echoed,
|
|
267
|
+
...("args" in echoed ? {} : { argsOmitted: true }),
|
|
268
|
+
recovery,
|
|
269
|
+
},
|
|
270
|
+
retry: error.retry ?? `Do not retry automatically. Check whether the write took effect first.${recoveryRetry}`,
|
|
233
271
|
};
|
|
234
272
|
}
|
|
235
273
|
if (error.code === "auth_required" && target.connector.startAuth) {
|
|
@@ -244,12 +282,17 @@ export class InvocationService {
|
|
|
244
282
|
...error,
|
|
245
283
|
nextAction: {
|
|
246
284
|
tool: resolved?.definition.classification === "read" ? "call_tool" : "call_destructive_tool",
|
|
247
|
-
arguments: { address: `${target.connector.id}.${target.toolName}`, ...
|
|
248
|
-
purpose: "Use this direct call so the host can fulfill the downstream input request."
|
|
285
|
+
arguments: { address: `${target.connector.id}.${target.toolName}`, ...argumentEcho },
|
|
286
|
+
purpose: "Use this direct call so the host can fulfill the downstream input request." +
|
|
287
|
+
(argumentEcho.argsRedacted
|
|
288
|
+
? " Re-send the original arguments; sensitive fields are omitted."
|
|
289
|
+
: "args" in argumentEcho
|
|
290
|
+
? ""
|
|
291
|
+
: " Re-send the original arguments; they could not be echoed safely."),
|
|
249
292
|
},
|
|
250
293
|
};
|
|
251
294
|
case "destructive_tool_requires_approval": {
|
|
252
|
-
const echoed =
|
|
295
|
+
const echoed = argumentEcho;
|
|
253
296
|
return {
|
|
254
297
|
...error,
|
|
255
298
|
nextAction: {
|
|
@@ -259,9 +302,11 @@ export class InvocationService {
|
|
|
259
302
|
...echoed,
|
|
260
303
|
},
|
|
261
304
|
purpose: "Ask the MCP host to approve this consequential call. " +
|
|
262
|
-
(
|
|
263
|
-
? "Re-send
|
|
264
|
-
: "
|
|
305
|
+
(echoed.argsRedacted
|
|
306
|
+
? "Re-send the original arguments; sensitive fields are omitted from this hint. Add a short reason for the human reviewer."
|
|
307
|
+
: "args" in echoed
|
|
308
|
+
? "Re-send these arguments and add a short reason for the human reviewer."
|
|
309
|
+
: "Re-send the arguments you just sent; they could not be echoed safely. Add a short reason for the human reviewer."),
|
|
265
310
|
},
|
|
266
311
|
};
|
|
267
312
|
}
|
|
@@ -325,7 +370,8 @@ export class InvocationService {
|
|
|
325
370
|
details: { ...error.details, operation, stage, elapsedMs, ...defined({ deadlineMs: context.timeoutMs }) },
|
|
326
371
|
};
|
|
327
372
|
}
|
|
328
|
-
|
|
373
|
+
// Downstream diagnostics were redacted under payload rules where they entered.
|
|
374
|
+
const details = sentSecrets.redact(enrich(error, target), "envelope");
|
|
329
375
|
const outcome = () => ({
|
|
330
376
|
ok: false,
|
|
331
377
|
durationMs: Date.now() - started,
|
|
@@ -365,6 +411,7 @@ export class InvocationService {
|
|
|
365
411
|
return failed(callerCancelledDetails());
|
|
366
412
|
}
|
|
367
413
|
let result;
|
|
414
|
+
let rawResult;
|
|
368
415
|
let inputRequiredValue;
|
|
369
416
|
let observedResult;
|
|
370
417
|
let valueFormat = "json";
|
|
@@ -398,11 +445,15 @@ export class InvocationService {
|
|
|
398
445
|
const target = resolution.resolved;
|
|
399
446
|
resolved = target;
|
|
400
447
|
activityTarget = target;
|
|
448
|
+
argumentEcho = echoedCallArgs(args ?? {}, target.definition.inputSchema);
|
|
449
|
+
sentSecrets.arguments(args ?? {}, target.definition.inputSchema);
|
|
401
450
|
const write = target.definition.classification !== "read";
|
|
402
451
|
const canonicalAddress = `${target.connector.id}.${target.toolName}`;
|
|
403
452
|
const input = context.source !== "execute_code"
|
|
404
453
|
? downstreamContinuation(this.catalog.requestScope, target.connector.id, canonicalAddress)
|
|
405
454
|
: undefined;
|
|
455
|
+
if (input)
|
|
456
|
+
recordDownstreamArgumentEcho(this.catalog.requestScope, argumentEcho);
|
|
406
457
|
const expectedDigest = replayClassificationDigest(this.catalog.requestScope, canonicalAddress);
|
|
407
458
|
if (expectedDigest !== undefined &&
|
|
408
459
|
(target.classificationFresh !== true ||
|
|
@@ -444,6 +495,7 @@ export class InvocationService {
|
|
|
444
495
|
const connectorContext = this.registry.contextFor(target.connector.id, this.catalog.baseUrl, this.catalog.requestScope, defined({ signal: callSignal, timeoutMs: context.timeoutMs, defer: this.catalog.defer }));
|
|
445
496
|
trackCredentialReads(connectorContext);
|
|
446
497
|
sentSecrets.include(sentSecretsFor(connectorContext));
|
|
498
|
+
dispatchContext = connectorContext;
|
|
447
499
|
try {
|
|
448
500
|
if (target.connector.credential) {
|
|
449
501
|
if (!connectorContext.credential) {
|
|
@@ -536,7 +588,8 @@ export class InvocationService {
|
|
|
536
588
|
/* Unserializable values have no measurable byte count. */
|
|
537
589
|
}
|
|
538
590
|
valueFormat = attempt.value.observed.format;
|
|
539
|
-
|
|
591
|
+
rawResult = attempt.value.raw;
|
|
592
|
+
result = context.unwrapResult ? observedResult : rawResult;
|
|
540
593
|
return undefined;
|
|
541
594
|
});
|
|
542
595
|
// One deadline owns discovery, queue admission, and provider dispatch.
|
|
@@ -571,7 +624,7 @@ export class InvocationService {
|
|
|
571
624
|
? Effect.succeed(inputRequiredValue.value)
|
|
572
625
|
: processResult
|
|
573
626
|
? Effect.tryPromise({
|
|
574
|
-
try: () => Promise.resolve(processResult(sentSecrets.redact(result), completed, sentSecrets, valueFormat)),
|
|
627
|
+
try: () => Promise.resolve(processResult(sentSecrets.redact(result), completed, sentSecrets, valueFormat, rawResult)),
|
|
575
628
|
catch: (error) => error,
|
|
576
629
|
})
|
|
577
630
|
: Effect.succeed(result);
|
|
@@ -592,7 +645,7 @@ export class InvocationService {
|
|
|
592
645
|
if (Exit.isFailure(processed))
|
|
593
646
|
return unprocessable();
|
|
594
647
|
try {
|
|
595
|
-
const value = sentSecrets.redact(processed.value);
|
|
648
|
+
const value = sentSecrets.redact(processed.value, "envelope");
|
|
596
649
|
const diagnostics = timing();
|
|
597
650
|
const friction = context.activityFriction?.(value);
|
|
598
651
|
record("success", friction ? { friction } : {});
|
|
@@ -610,6 +663,11 @@ export class InvocationService {
|
|
|
610
663
|
catch {
|
|
611
664
|
return unprocessable();
|
|
612
665
|
}
|
|
613
|
-
})
|
|
666
|
+
}).pipe(Effect.flatMap((outcome) => outcome.ok
|
|
667
|
+
? Effect.succeed(outcome)
|
|
668
|
+
: Effect.promise(async () => ({
|
|
669
|
+
...outcome,
|
|
670
|
+
error: await withAuthorizationHandoff(this.catalog.requestScope, outcome.error),
|
|
671
|
+
}))));
|
|
614
672
|
}
|
|
615
673
|
}
|
package/dist/meta-output.js
CHANGED
|
@@ -51,21 +51,6 @@ export const SEARCH_OUTPUT = {
|
|
|
51
51
|
},
|
|
52
52
|
},
|
|
53
53
|
};
|
|
54
|
-
export const CALL_OUTPUT = advertisedSchema(z
|
|
55
|
-
.object({
|
|
56
|
-
ok: z.boolean().optional(),
|
|
57
|
-
data: z.unknown().optional(),
|
|
58
|
-
format: format.optional(),
|
|
59
|
-
error: error.optional(),
|
|
60
|
-
durationMs: z.number().nonnegative().optional(),
|
|
61
|
-
attempts: z.number().int().nonnegative().optional(),
|
|
62
|
-
truncated: z.boolean().optional(),
|
|
63
|
-
resultId: z.string().optional(),
|
|
64
|
-
totalBytes: z.number().int().nonnegative().optional(),
|
|
65
|
-
hint: z.string().optional(),
|
|
66
|
-
nextAction: z.record(z.string(), z.unknown()).optional(),
|
|
67
|
-
})
|
|
68
|
-
.passthrough());
|
|
69
54
|
export const AUTHORIZE_OUTPUT = advertisedSchema(z
|
|
70
55
|
.object({
|
|
71
56
|
connector: z.string().optional(),
|