@zackbart/connecta 0.29.0 → 0.30.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 +67 -0
- package/README.md +123 -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/catalog-cache.js +2 -2
- package/dist/catalog-service.js +103 -63
- package/dist/config.d.ts +2 -0
- package/dist/config.js +12 -7
- package/dist/connectors/guarded-fetch.js +3 -3
- 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 +36 -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 +40 -14
- package/dist/meta-output.js +0 -15
- package/dist/meta-tools.js +166 -95
- package/dist/oauth-handoff.js +88 -11
- package/dist/program-result.js +5 -0
- package/dist/program-writes.js +5 -1
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/request-token.js +83 -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/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/documentation/README.md +63 -0
- package/documentation/architecture.md +121 -89
- package/documentation/auth.md +138 -5
- package/documentation/code-mode.md +224 -107
- package/documentation/deploying.md +146 -0
- package/documentation/integrating.md +159 -0
- package/documentation/meta-tools.md +135 -30
- package/documentation/operating.md +135 -0
- package/documentation/operator-tests.md +4 -0
- package/documentation/operator-ui.md +132 -0
- package/documentation/provider-migration-0.29.md +5 -0
- package/examples/worker/README.md +7 -0
- package/package.json +7 -1
- package/templates/node/README.md +7 -0
- package/templates/node/package.json +1 -1
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";
|
|
@@ -10,7 +11,7 @@ import { runEdge, withDeadlineEffect } from "./runtime/run.js";
|
|
|
10
11
|
import { validateCatalogToolInput } from "./validate.js";
|
|
11
12
|
import { sentSecretsFor, sentSecretsForRequest, trackCredentialReads } from "./sent-secrets.js";
|
|
12
13
|
import { classificationDigest, recordAuthFailure, recordCallEntry, replayClassificationDigest, resolveInvocationAuth, } from "./invocation-auth.js";
|
|
13
|
-
import { downstreamContinuation, isDownstreamInputResult, assertDownstreamOutputSafe, } from "./downstream-input-context.js";
|
|
14
|
+
import { downstreamContinuation, recordDownstreamArgumentEcho, isDownstreamInputResult, assertDownstreamOutputSafe, } from "./downstream-input-context.js";
|
|
14
15
|
function defined(values) {
|
|
15
16
|
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
|
|
16
17
|
}
|
|
@@ -111,6 +112,7 @@ export class InvocationFailure extends Error {
|
|
|
111
112
|
recovery;
|
|
112
113
|
nextAction;
|
|
113
114
|
retry;
|
|
115
|
+
data;
|
|
114
116
|
constructor(details) {
|
|
115
117
|
super(details.message);
|
|
116
118
|
this.details = details;
|
|
@@ -123,6 +125,7 @@ export class InvocationFailure extends Error {
|
|
|
123
125
|
this.recovery = details.recovery;
|
|
124
126
|
this.nextAction = details.nextAction;
|
|
125
127
|
this.retry = details.retry;
|
|
128
|
+
this.data = details;
|
|
126
129
|
}
|
|
127
130
|
}
|
|
128
131
|
/**
|
|
@@ -155,7 +158,7 @@ export class InvocationService {
|
|
|
155
158
|
pipeline(address, args, context) {
|
|
156
159
|
return Effect.gen({ self: this }, function* () {
|
|
157
160
|
const started = Date.now();
|
|
158
|
-
|
|
161
|
+
let argumentEcho = {};
|
|
159
162
|
let catalogMs = 0;
|
|
160
163
|
let admissionMs = 0;
|
|
161
164
|
let connectorMs = 0;
|
|
@@ -229,7 +232,11 @@ export class InvocationService {
|
|
|
229
232
|
...("args" in echoed ? {} : { argsOmitted: true }),
|
|
230
233
|
},
|
|
231
234
|
retry: "Do not retry automatically. Check whether the write took effect first." +
|
|
232
|
-
(
|
|
235
|
+
(echoed.argsRedacted
|
|
236
|
+
? " Sensitive fields are omitted; use the original arguments if reconciliation requires another call."
|
|
237
|
+
: "args" in echoed
|
|
238
|
+
? ""
|
|
239
|
+
: " The arguments could not be echoed safely; use the exact arguments you sent."),
|
|
233
240
|
};
|
|
234
241
|
}
|
|
235
242
|
if (error.code === "auth_required" && target.connector.startAuth) {
|
|
@@ -244,12 +251,17 @@ export class InvocationService {
|
|
|
244
251
|
...error,
|
|
245
252
|
nextAction: {
|
|
246
253
|
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."
|
|
254
|
+
arguments: { address: `${target.connector.id}.${target.toolName}`, ...argumentEcho },
|
|
255
|
+
purpose: "Use this direct call so the host can fulfill the downstream input request." +
|
|
256
|
+
(argumentEcho.argsRedacted
|
|
257
|
+
? " Re-send the original arguments; sensitive fields are omitted."
|
|
258
|
+
: "args" in argumentEcho
|
|
259
|
+
? ""
|
|
260
|
+
: " Re-send the original arguments; they could not be echoed safely."),
|
|
249
261
|
},
|
|
250
262
|
};
|
|
251
263
|
case "destructive_tool_requires_approval": {
|
|
252
|
-
const echoed =
|
|
264
|
+
const echoed = argumentEcho;
|
|
253
265
|
return {
|
|
254
266
|
...error,
|
|
255
267
|
nextAction: {
|
|
@@ -259,9 +271,11 @@ export class InvocationService {
|
|
|
259
271
|
...echoed,
|
|
260
272
|
},
|
|
261
273
|
purpose: "Ask the MCP host to approve this consequential call. " +
|
|
262
|
-
(
|
|
263
|
-
? "Re-send
|
|
264
|
-
: "
|
|
274
|
+
(echoed.argsRedacted
|
|
275
|
+
? "Re-send the original arguments; sensitive fields are omitted from this hint. Add a short reason for the human reviewer."
|
|
276
|
+
: "args" in echoed
|
|
277
|
+
? "Re-send these arguments and add a short reason for the human reviewer."
|
|
278
|
+
: "Re-send the arguments you just sent; they could not be echoed safely. Add a short reason for the human reviewer."),
|
|
265
279
|
},
|
|
266
280
|
};
|
|
267
281
|
}
|
|
@@ -325,7 +339,8 @@ export class InvocationService {
|
|
|
325
339
|
details: { ...error.details, operation, stage, elapsedMs, ...defined({ deadlineMs: context.timeoutMs }) },
|
|
326
340
|
};
|
|
327
341
|
}
|
|
328
|
-
|
|
342
|
+
// Downstream diagnostics were redacted under payload rules where they entered.
|
|
343
|
+
const details = sentSecrets.redact(enrich(error, target), "envelope");
|
|
329
344
|
const outcome = () => ({
|
|
330
345
|
ok: false,
|
|
331
346
|
durationMs: Date.now() - started,
|
|
@@ -365,6 +380,7 @@ export class InvocationService {
|
|
|
365
380
|
return failed(callerCancelledDetails());
|
|
366
381
|
}
|
|
367
382
|
let result;
|
|
383
|
+
let rawResult;
|
|
368
384
|
let inputRequiredValue;
|
|
369
385
|
let observedResult;
|
|
370
386
|
let valueFormat = "json";
|
|
@@ -398,11 +414,15 @@ export class InvocationService {
|
|
|
398
414
|
const target = resolution.resolved;
|
|
399
415
|
resolved = target;
|
|
400
416
|
activityTarget = target;
|
|
417
|
+
argumentEcho = echoedCallArgs(args ?? {}, target.definition.inputSchema);
|
|
418
|
+
sentSecrets.arguments(args ?? {}, target.definition.inputSchema);
|
|
401
419
|
const write = target.definition.classification !== "read";
|
|
402
420
|
const canonicalAddress = `${target.connector.id}.${target.toolName}`;
|
|
403
421
|
const input = context.source !== "execute_code"
|
|
404
422
|
? downstreamContinuation(this.catalog.requestScope, target.connector.id, canonicalAddress)
|
|
405
423
|
: undefined;
|
|
424
|
+
if (input)
|
|
425
|
+
recordDownstreamArgumentEcho(this.catalog.requestScope, argumentEcho);
|
|
406
426
|
const expectedDigest = replayClassificationDigest(this.catalog.requestScope, canonicalAddress);
|
|
407
427
|
if (expectedDigest !== undefined &&
|
|
408
428
|
(target.classificationFresh !== true ||
|
|
@@ -536,7 +556,8 @@ export class InvocationService {
|
|
|
536
556
|
/* Unserializable values have no measurable byte count. */
|
|
537
557
|
}
|
|
538
558
|
valueFormat = attempt.value.observed.format;
|
|
539
|
-
|
|
559
|
+
rawResult = attempt.value.raw;
|
|
560
|
+
result = context.unwrapResult ? observedResult : rawResult;
|
|
540
561
|
return undefined;
|
|
541
562
|
});
|
|
542
563
|
// One deadline owns discovery, queue admission, and provider dispatch.
|
|
@@ -571,7 +592,7 @@ export class InvocationService {
|
|
|
571
592
|
? Effect.succeed(inputRequiredValue.value)
|
|
572
593
|
: processResult
|
|
573
594
|
? Effect.tryPromise({
|
|
574
|
-
try: () => Promise.resolve(processResult(sentSecrets.redact(result), completed, sentSecrets, valueFormat)),
|
|
595
|
+
try: () => Promise.resolve(processResult(sentSecrets.redact(result), completed, sentSecrets, valueFormat, rawResult)),
|
|
575
596
|
catch: (error) => error,
|
|
576
597
|
})
|
|
577
598
|
: Effect.succeed(result);
|
|
@@ -592,7 +613,7 @@ export class InvocationService {
|
|
|
592
613
|
if (Exit.isFailure(processed))
|
|
593
614
|
return unprocessable();
|
|
594
615
|
try {
|
|
595
|
-
const value = sentSecrets.redact(processed.value);
|
|
616
|
+
const value = sentSecrets.redact(processed.value, "envelope");
|
|
596
617
|
const diagnostics = timing();
|
|
597
618
|
const friction = context.activityFriction?.(value);
|
|
598
619
|
record("success", friction ? { friction } : {});
|
|
@@ -610,6 +631,11 @@ export class InvocationService {
|
|
|
610
631
|
catch {
|
|
611
632
|
return unprocessable();
|
|
612
633
|
}
|
|
613
|
-
})
|
|
634
|
+
}).pipe(Effect.flatMap((outcome) => outcome.ok
|
|
635
|
+
? Effect.succeed(outcome)
|
|
636
|
+
: Effect.promise(async () => ({
|
|
637
|
+
...outcome,
|
|
638
|
+
error: await withAuthorizationHandoff(this.catalog.requestScope, outcome.error),
|
|
639
|
+
}))));
|
|
614
640
|
}
|
|
615
641
|
}
|
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(),
|