apps 1.0.0-beta.1 → 1.0.0-beta.3
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/README.md +3 -2
- package/dist/authoring/index.d.ts +222 -34
- package/dist/authoring/index.js +11 -1
- package/dist/{chunk-NFLNA2SC.js → chunk-4OZFMCTT.js} +7 -1
- package/dist/{chunk-LWKQDDLZ.js → chunk-A3H32P2J.js} +46 -1
- package/dist/chunk-F7GZYEAC.js +41 -0
- package/dist/chunk-MUFMJRXR.js +308 -0
- package/dist/chunk-S5KFP4JQ.js +20 -0
- package/dist/chunk-Y6LLTN7W.js +99 -0
- package/dist/chunk-YUZ4TXOY.js +27 -0
- package/dist/http/index.d.ts +9 -0
- package/dist/http/index.js +6 -0
- package/dist/index.d.ts +3 -8
- package/dist/index.js +23 -1
- package/dist/manifest/contract.d.ts +8 -0
- package/dist/manifest/index.d.ts +8 -6
- package/dist/manifest/index.js +3 -3
- package/dist/mcp/client.d.ts +9 -0
- package/dist/mcp/index.d.ts +11 -0
- package/dist/mcp/index.js +7 -0
- package/dist/openapi/index.d.ts +14 -0
- package/dist/openapi/index.js +7 -0
- package/dist/openapi/mapper.d.ts +7 -0
- package/dist/runner/index.js +36 -51
- package/dist/runner/protocol.d.ts +20 -15
- package/dist/runner/protocol.js +5 -3
- package/dist/runtime/index.d.ts +83 -0
- package/dist/runtime/index.js +12 -0
- package/dist/workerd/index.d.ts +7 -2
- package/dist/workerd/index.js +7 -3
- package/package.json +18 -7
- package/src/authoring/index.ts +362 -44
- package/src/http/index.ts +26 -0
- package/src/index.ts +3 -8
- package/src/manifest/contract.ts +9 -0
- package/src/manifest/index.ts +89 -157
- package/src/mcp/client.ts +59 -0
- package/src/mcp/index.ts +42 -0
- package/src/openapi/index.ts +70 -0
- package/src/openapi/mapper.ts +25 -0
- package/src/runner/index.ts +50 -87
- package/src/runner/protocol.ts +34 -21
- package/src/runtime/index.ts +550 -0
- package/src/workerd/index.ts +17 -2
- package/dist/chunk-J5AVXTHA.js +0 -154
- package/dist/chunk-RZA7AXBK.js +0 -107
- package/dist/manifest/collect.d.ts +0 -64
- package/src/manifest/collect.ts +0 -198
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// src/runtime/index.ts
|
|
2
|
+
var failure = (kind, reason) => ({
|
|
3
|
+
ok: false,
|
|
4
|
+
kind,
|
|
5
|
+
reason
|
|
6
|
+
});
|
|
7
|
+
var errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
8
|
+
var isJsonValue = (value) => {
|
|
9
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
10
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
11
|
+
if (Array.isArray(value)) return value.every(isJsonValue);
|
|
12
|
+
if (typeof value !== "object") return false;
|
|
13
|
+
return Object.values(value).every(isJsonValue);
|
|
14
|
+
};
|
|
15
|
+
var standardJsonSchemaOf = (input) => "~standard" in input ? input["~standard"].jsonSchema : void 0;
|
|
16
|
+
var strictify = (node) => {
|
|
17
|
+
if (typeof node !== "object" || node === null) return node;
|
|
18
|
+
if (Array.isArray(node)) return node.map(strictify);
|
|
19
|
+
const object = { ...node };
|
|
20
|
+
if (object.type === "object" && object.additionalProperties === void 0)
|
|
21
|
+
object.additionalProperties = false;
|
|
22
|
+
for (const key of ["items", "anyOf", "oneOf", "allOf"])
|
|
23
|
+
if (key in object) object[key] = strictify(object[key]);
|
|
24
|
+
for (const key of ["properties", "$defs"])
|
|
25
|
+
if (object[key] !== void 0 && typeof object[key] === "object" && object[key] !== null)
|
|
26
|
+
object[key] = Object.fromEntries(
|
|
27
|
+
Object.entries(object[key]).map(([name, value]) => [
|
|
28
|
+
name,
|
|
29
|
+
strictify(value)
|
|
30
|
+
])
|
|
31
|
+
);
|
|
32
|
+
return object;
|
|
33
|
+
};
|
|
34
|
+
var compileInputSchema = (input, where) => {
|
|
35
|
+
const standard = standardJsonSchemaOf(input);
|
|
36
|
+
if ("~standard" in input && typeof standard?.input !== "function")
|
|
37
|
+
return {
|
|
38
|
+
problem: `${where}: input is a Standard Schema without the jsonSchema extension \u2014 use a library that implements it (zod 4+, effect Schema) or pass raw JSON schema`
|
|
39
|
+
};
|
|
40
|
+
try {
|
|
41
|
+
const compiled = strictify(
|
|
42
|
+
typeof standard?.input === "function" ? standard.input({ target: "draft-2020-12" }) : structuredClone(input)
|
|
43
|
+
);
|
|
44
|
+
if (typeof compiled !== "object" || compiled === null || Array.isArray(compiled) || !isJsonValue(compiled))
|
|
45
|
+
throw new Error("expected a JSON object");
|
|
46
|
+
return { value: compiled };
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return {
|
|
49
|
+
problem: `${where}: input schema could not compile to JSON schema (${errorMessage(error)})`
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var requirementKey = Symbol.for("executor/apps/requirement");
|
|
54
|
+
var requirementSpecOf = (value) => typeof value === "object" && value !== null && requirementKey in value ? value[requirementKey] : void 0;
|
|
55
|
+
var segmentOf = (file) => {
|
|
56
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
57
|
+
return name.endsWith(".ts") ? name.slice(0, -3) : name;
|
|
58
|
+
};
|
|
59
|
+
var canonicalJson = (value) => {
|
|
60
|
+
const sortKeys = (node) => {
|
|
61
|
+
if (Array.isArray(node)) return node.map(sortKeys);
|
|
62
|
+
if (typeof node === "object" && node !== null)
|
|
63
|
+
return Object.fromEntries(
|
|
64
|
+
Object.entries(node).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, sortKeys(child)])
|
|
65
|
+
);
|
|
66
|
+
return node;
|
|
67
|
+
};
|
|
68
|
+
return JSON.stringify(sortKeys(value));
|
|
69
|
+
};
|
|
70
|
+
var collectRegistry = (registry) => {
|
|
71
|
+
const problems = registry.problems.map((problem) => problem.message);
|
|
72
|
+
const app = registry.app;
|
|
73
|
+
const claims = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const [origin, registered] of registry.services) {
|
|
75
|
+
if (registered.file !== "app.ts") continue;
|
|
76
|
+
const { decl } = registered;
|
|
77
|
+
claims.set(origin, {
|
|
78
|
+
origin,
|
|
79
|
+
reach: decl.reach ?? [],
|
|
80
|
+
params: decl.params ?? {},
|
|
81
|
+
auth: decl.auth ?? {}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const collectRequirement = (role, requirement, where) => {
|
|
85
|
+
const spec = requirementSpecOf(requirement);
|
|
86
|
+
if (spec === void 0) {
|
|
87
|
+
problems.push(`${where}: requirement '${role}' is not a service/requirement value`);
|
|
88
|
+
return void 0;
|
|
89
|
+
}
|
|
90
|
+
const origin = spec.service.origin;
|
|
91
|
+
if (!claims.has(origin))
|
|
92
|
+
problems.push(`${where}: requirement '${role}' names ${origin}, which app.ts does not declare`);
|
|
93
|
+
if (spec.grant !== void 0 && !(spec.grant in (spec.service.auth ?? {})))
|
|
94
|
+
problems.push(
|
|
95
|
+
`${where}: requirement '${role}' pins grant '${spec.grant}', not a auth method on ${origin}`
|
|
96
|
+
);
|
|
97
|
+
return {
|
|
98
|
+
role,
|
|
99
|
+
service: origin,
|
|
100
|
+
arity: spec.arity,
|
|
101
|
+
...spec.grant === void 0 ? {} : { grant: spec.grant },
|
|
102
|
+
...spec.description === void 0 ? {} : { description: spec.description }
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
if (app === void 0) problems.push("app.ts must call defineApp({...})");
|
|
106
|
+
else if (typeof app.description !== "string" || app.description.trim().length === 0)
|
|
107
|
+
problems.push("app.ts: app description must not be empty");
|
|
108
|
+
for (const [file, summary] of registry.toolFiles) {
|
|
109
|
+
if (summary.definitions > 1 && summary.hasUnnamedTool)
|
|
110
|
+
problems.push(`${file}: multi-tool files must name every tool`);
|
|
111
|
+
if (registry.dynamic.has(file))
|
|
112
|
+
problems.push(`${file}: a file claims a dynamic tool set or defines tools, never both`);
|
|
113
|
+
}
|
|
114
|
+
const tools = [];
|
|
115
|
+
if (app !== void 0) {
|
|
116
|
+
for (const [name, registered] of registry.tools) {
|
|
117
|
+
const { spec, file } = registered;
|
|
118
|
+
const address = `${app.key}/${name}`;
|
|
119
|
+
if (typeof spec.description !== "string" || spec.description.trim().length === 0)
|
|
120
|
+
problems.push(`${file}: tool '${name}' description must not be empty`);
|
|
121
|
+
const requires = Object.entries(spec.requirements ?? {}).flatMap(([role, requirement]) => {
|
|
122
|
+
const collected = collectRequirement(role, requirement, address);
|
|
123
|
+
return collected === void 0 ? [] : [collected];
|
|
124
|
+
}).sort((left, right) => left.role.localeCompare(right.role));
|
|
125
|
+
const compiled = compileInputSchema(spec.input, address);
|
|
126
|
+
if ("problem" in compiled) {
|
|
127
|
+
problems.push(compiled.problem);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
tools.push({
|
|
131
|
+
address,
|
|
132
|
+
name,
|
|
133
|
+
description: spec.description,
|
|
134
|
+
inputSchema: compiled.value,
|
|
135
|
+
annotations: spec.annotations ?? {},
|
|
136
|
+
requires,
|
|
137
|
+
file
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const dynamic = [];
|
|
142
|
+
const seenSegments = /* @__PURE__ */ new Map();
|
|
143
|
+
for (const [file, registered] of registry.dynamic) {
|
|
144
|
+
const segment = segmentOf(file);
|
|
145
|
+
const where = `${file}: dynamic tool set '${segment}'`;
|
|
146
|
+
const existing = seenSegments.get(segment);
|
|
147
|
+
if (existing !== void 0) {
|
|
148
|
+
problems.push(`${where}: segment already claimed by ${existing}`);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
seenSegments.set(segment, file);
|
|
152
|
+
const requires = Object.entries(registered.spec.requirements ?? {}).flatMap(([role, requirement]) => {
|
|
153
|
+
const collected = collectRequirement(role, requirement, where);
|
|
154
|
+
return collected === void 0 ? [] : [collected];
|
|
155
|
+
}).sort((left, right) => left.role.localeCompare(right.role));
|
|
156
|
+
dynamic.push({
|
|
157
|
+
segment,
|
|
158
|
+
description: registered.spec.description,
|
|
159
|
+
requires,
|
|
160
|
+
file
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (problems.length > 0 || app === void 0) return { problems };
|
|
164
|
+
const services = [...claims.values()].sort(
|
|
165
|
+
(left, right) => left.origin.localeCompare(right.origin)
|
|
166
|
+
);
|
|
167
|
+
const sortedTools = [...tools].sort((left, right) => left.address.localeCompare(right.address));
|
|
168
|
+
const sortedDynamic = [...dynamic].sort(
|
|
169
|
+
(left, right) => left.segment.localeCompare(right.segment)
|
|
170
|
+
);
|
|
171
|
+
const appResult = { key: app.key, label: app.label, description: app.description };
|
|
172
|
+
const manifest = {
|
|
173
|
+
app: appResult,
|
|
174
|
+
services,
|
|
175
|
+
tools: sortedTools,
|
|
176
|
+
dynamic: sortedDynamic
|
|
177
|
+
};
|
|
178
|
+
return { problems: [], manifest, canonical: canonicalJson(manifest) };
|
|
179
|
+
};
|
|
180
|
+
var digestOf = async (canonical) => {
|
|
181
|
+
const bytes = new Uint8Array(
|
|
182
|
+
await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonical))
|
|
183
|
+
);
|
|
184
|
+
return Array.from(bytes.slice(0, 8), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
185
|
+
};
|
|
186
|
+
var manifestOf = async (registry) => {
|
|
187
|
+
const collected = collectRegistry(registry);
|
|
188
|
+
if (collected.problems.length > 0) return { ok: false, problems: collected.problems };
|
|
189
|
+
if (!("manifest" in collected))
|
|
190
|
+
return { ok: false, problems: ["validated registry projection unexpectedly missing"] };
|
|
191
|
+
return {
|
|
192
|
+
ok: true,
|
|
193
|
+
result: { digest: `snap:${await digestOf(collected.canonical)}`, ...collected.manifest }
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
var standardValidateOf = (input) => "~standard" in input && typeof input["~standard"].validate === "function" ? input["~standard"].validate : void 0;
|
|
197
|
+
var parseInput = async (schema, input) => {
|
|
198
|
+
const validate = standardValidateOf(schema);
|
|
199
|
+
if (validate === void 0)
|
|
200
|
+
return { ok: false, reason: "input schema does not implement Standard Schema validate" };
|
|
201
|
+
try {
|
|
202
|
+
const result = await validate(input);
|
|
203
|
+
if (result.issues !== void 0)
|
|
204
|
+
return {
|
|
205
|
+
ok: false,
|
|
206
|
+
reason: `invalid input (${result.issues.map((issue) => issue.message).join("; ")})`
|
|
207
|
+
};
|
|
208
|
+
return { ok: true, value: result.value };
|
|
209
|
+
} catch (error) {
|
|
210
|
+
return { ok: false, reason: `input validation threw (${errorMessage(error)})` };
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
var splitDynamicAddress = (address) => {
|
|
214
|
+
const slash = address.indexOf("/");
|
|
215
|
+
if (slash <= 0 || slash === address.length - 1) return void 0;
|
|
216
|
+
return { segment: address.slice(0, slash), name: address.slice(slash + 1) };
|
|
217
|
+
};
|
|
218
|
+
var dynamicFor = (registry, segment) => {
|
|
219
|
+
for (const [file, registered] of registry.dynamic)
|
|
220
|
+
if (segmentOf(file) === segment) return registered.spec;
|
|
221
|
+
return void 0;
|
|
222
|
+
};
|
|
223
|
+
var discoveredSpecs = /* @__PURE__ */ new WeakMap();
|
|
224
|
+
var callTool = async (registry, address, input, ctx, detail) => {
|
|
225
|
+
const dynamic = splitDynamicAddress(address);
|
|
226
|
+
if (dynamic === void 0) {
|
|
227
|
+
if (detail !== void 0)
|
|
228
|
+
return failure("handler", "a static tool takes no discovery detail");
|
|
229
|
+
const registered = registry.tools.get(address);
|
|
230
|
+
if (registered === void 0) return failure("handler", "unknown tool");
|
|
231
|
+
const parsed2 = await parseInput(registered.spec.input, input);
|
|
232
|
+
if (!parsed2.ok) return failure("input", parsed2.reason);
|
|
233
|
+
try {
|
|
234
|
+
const handler = registered.spec.handler;
|
|
235
|
+
return { ok: true, value: await handler(parsed2.value, ctx) };
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return failure("handler", `handler failed (${errorMessage(error)})`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (detail === void 0)
|
|
241
|
+
return failure("handler", "a dynamic tool requires discovery detail");
|
|
242
|
+
const pair = dynamicFor(registry, dynamic.segment);
|
|
243
|
+
if (pair === void 0)
|
|
244
|
+
return failure("handler", `unknown dynamic tool set '${dynamic.segment}'`);
|
|
245
|
+
const discovered = discoveredSpecs.get(registry)?.get(dynamic.segment)?.get(dynamic.name);
|
|
246
|
+
if (discovered === void 0)
|
|
247
|
+
return failure(
|
|
248
|
+
"input",
|
|
249
|
+
`dynamic tool '${dynamic.name}' has no compiled discovery descriptor`
|
|
250
|
+
);
|
|
251
|
+
const parsed = await parseInput(discovered.input, input);
|
|
252
|
+
if (!parsed.ok) return failure("input", parsed.reason);
|
|
253
|
+
try {
|
|
254
|
+
const call = pair.call;
|
|
255
|
+
return {
|
|
256
|
+
ok: true,
|
|
257
|
+
value: await call({ name: dynamic.name, detail }, parsed.value, ctx)
|
|
258
|
+
};
|
|
259
|
+
} catch (error) {
|
|
260
|
+
return failure("handler", `dynamic call failed (${errorMessage(error)})`);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
var discoverTool = async (registry, segment, ctx) => {
|
|
264
|
+
const pair = dynamicFor(registry, segment);
|
|
265
|
+
if (pair === void 0)
|
|
266
|
+
return { ok: false, reason: `unknown dynamic tool set '${segment}'` };
|
|
267
|
+
discoveredSpecs.get(registry)?.delete(segment);
|
|
268
|
+
let emitted;
|
|
269
|
+
try {
|
|
270
|
+
const discover = pair.discover;
|
|
271
|
+
emitted = await discover(ctx);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
return { ok: false, reason: `discover failed (${errorMessage(error)})` };
|
|
274
|
+
}
|
|
275
|
+
const tools = [];
|
|
276
|
+
const live = /* @__PURE__ */ new Map();
|
|
277
|
+
for (const tool of emitted) {
|
|
278
|
+
if (live.has(tool.name))
|
|
279
|
+
return { ok: false, reason: `discover emitted duplicate tool '${tool.name}'` };
|
|
280
|
+
const compiled = compileInputSchema(tool.input, `${segment}/${tool.name}`);
|
|
281
|
+
if ("problem" in compiled) return { ok: false, reason: compiled.problem };
|
|
282
|
+
live.set(tool.name, tool);
|
|
283
|
+
tools.push({
|
|
284
|
+
segment,
|
|
285
|
+
name: tool.name,
|
|
286
|
+
descriptor: {
|
|
287
|
+
description: tool.description,
|
|
288
|
+
inputSchema: compiled.value,
|
|
289
|
+
annotations: {
|
|
290
|
+
...tool.annotations?.readOnly === void 0 ? {} : { readOnly: tool.annotations.readOnly },
|
|
291
|
+
...tool.annotations?.destructive === void 0 ? {} : { destructive: tool.annotations.destructive }
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
detail: tool.detail ?? null
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
const registryDiscoveries = discoveredSpecs.get(registry) ?? /* @__PURE__ */ new Map();
|
|
298
|
+
registryDiscoveries.set(segment, live);
|
|
299
|
+
discoveredSpecs.set(registry, registryDiscoveries);
|
|
300
|
+
return { ok: true, value: tools.sort((left, right) => left.name.localeCompare(right.name)) };
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
export {
|
|
304
|
+
compileInputSchema,
|
|
305
|
+
manifestOf,
|
|
306
|
+
callTool,
|
|
307
|
+
discoverTool
|
|
308
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/http/index.ts
|
|
2
|
+
var authHeaders = (role) => {
|
|
3
|
+
switch (role.kind) {
|
|
4
|
+
case "bearer":
|
|
5
|
+
case "oauth":
|
|
6
|
+
return { authorization: `Bearer ${role.token}` };
|
|
7
|
+
case "basic":
|
|
8
|
+
return { authorization: `Basic ${base64(`${role.username}:${role.password}`)}` };
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var base64 = (value) => {
|
|
12
|
+
const bytes = new TextEncoder().encode(value);
|
|
13
|
+
let binary = "";
|
|
14
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
15
|
+
return btoa(binary);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
authHeaders
|
|
20
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
manifestOf
|
|
3
|
+
} from "./chunk-MUFMJRXR.js";
|
|
4
|
+
import {
|
|
5
|
+
PublishError
|
|
6
|
+
} from "./chunk-B7NEUIJL.js";
|
|
7
|
+
import {
|
|
8
|
+
__enterFile,
|
|
9
|
+
__leaveFile,
|
|
10
|
+
newRegistry,
|
|
11
|
+
setActiveRegistry
|
|
12
|
+
} from "./chunk-A3H32P2J.js";
|
|
13
|
+
|
|
14
|
+
// src/manifest/index.ts
|
|
15
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
16
|
+
import { Effect, FileSystem, MutableRef, Semaphore } from "effect";
|
|
17
|
+
var moduleProblem = (where, error) => `${where}: failed to execute (${error instanceof Error ? error.message : String(error)})`;
|
|
18
|
+
var loadPermit = Semaphore.makeUnsafe(1);
|
|
19
|
+
var runNonce = MutableRef.make(0);
|
|
20
|
+
var importedAppUrls = MutableRef.make(/* @__PURE__ */ new Set());
|
|
21
|
+
var canonicalModuleUrl = (path, where) => FileSystem.FileSystem.pipe(
|
|
22
|
+
Effect.flatMap((fs) => fs.realPath(path)),
|
|
23
|
+
Effect.map(pathToFileURL),
|
|
24
|
+
Effect.mapError((error) => new PublishError({ problems: [moduleProblem(where, error)] }))
|
|
25
|
+
);
|
|
26
|
+
var importModule = (specifier, where) => Effect.tryPromise({
|
|
27
|
+
try: () => import(specifier),
|
|
28
|
+
catch: (error) => new PublishError({ problems: [moduleProblem(where, error)] })
|
|
29
|
+
}).pipe(Effect.asVoid);
|
|
30
|
+
var executeModule = (registry, where, load) => Effect.suspend(() => {
|
|
31
|
+
__enterFile(where);
|
|
32
|
+
return load;
|
|
33
|
+
}).pipe(
|
|
34
|
+
Effect.catchTag(
|
|
35
|
+
"PublishError",
|
|
36
|
+
(error) => Effect.sync(() => {
|
|
37
|
+
registry.problems.push(...error.problems.map((message) => ({ message })));
|
|
38
|
+
})
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
var appEntrySpecifier = (appUrl, nonce) => {
|
|
42
|
+
const seen = MutableRef.get(importedAppUrls);
|
|
43
|
+
if (!seen.has(appUrl.href)) {
|
|
44
|
+
MutableRef.set(importedAppUrls, /* @__PURE__ */ new Set([...seen, appUrl.href]));
|
|
45
|
+
return appUrl.href;
|
|
46
|
+
}
|
|
47
|
+
return `${fileURLToPath(appUrl)}?m=${nonce}`;
|
|
48
|
+
};
|
|
49
|
+
var executeModules = (appDir, files) => loadPermit.withPermit(
|
|
50
|
+
Effect.acquireUseRelease(
|
|
51
|
+
Effect.sync(() => {
|
|
52
|
+
const registry = newRegistry();
|
|
53
|
+
return { registry, previous: setActiveRegistry(registry) };
|
|
54
|
+
}),
|
|
55
|
+
({ registry }) => Effect.gen(function* () {
|
|
56
|
+
const nonce = MutableRef.incrementAndGet(runNonce);
|
|
57
|
+
yield* executeModule(
|
|
58
|
+
registry,
|
|
59
|
+
"app.ts",
|
|
60
|
+
canonicalModuleUrl(`${appDir}/app.ts`, "app.ts").pipe(
|
|
61
|
+
Effect.flatMap((appUrl) => importModule(appEntrySpecifier(appUrl, nonce), "app.ts"))
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
const where = `tools/${file}`;
|
|
66
|
+
yield* executeModule(
|
|
67
|
+
registry,
|
|
68
|
+
where,
|
|
69
|
+
canonicalModuleUrl(`${appDir}/tools/${file}`, where).pipe(
|
|
70
|
+
Effect.flatMap((url) => importModule(`${fileURLToPath(url)}?m=${nonce}`, where))
|
|
71
|
+
)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return registry;
|
|
75
|
+
}),
|
|
76
|
+
({ previous }) => Effect.sync(() => {
|
|
77
|
+
__leaveFile();
|
|
78
|
+
setActiveRegistry(previous);
|
|
79
|
+
})
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
var toolFiles = (appDir) => FileSystem.FileSystem.pipe(
|
|
83
|
+
Effect.flatMap((fs) => fs.readDirectory(`${appDir}/tools`)),
|
|
84
|
+
Effect.mapError((error) => new PublishError({ problems: [`tools/: ${error.message}`] })),
|
|
85
|
+
Effect.map((entries) => entries.filter((file) => file.endsWith(".ts")).sort())
|
|
86
|
+
);
|
|
87
|
+
var loadApp = (appDir) => Effect.gen(function* () {
|
|
88
|
+
const files = yield* toolFiles(appDir);
|
|
89
|
+
const registry = yield* executeModules(appDir, files);
|
|
90
|
+
const outcome = yield* Effect.promise(() => manifestOf(registry));
|
|
91
|
+
if (!outcome.ok) return yield* new PublishError({ problems: outcome.problems });
|
|
92
|
+
return { registry, result: outcome.result };
|
|
93
|
+
});
|
|
94
|
+
var manifest = (appDir) => Effect.map(loadApp(appDir), ({ result }) => result);
|
|
95
|
+
|
|
96
|
+
export {
|
|
97
|
+
loadApp,
|
|
98
|
+
manifest
|
|
99
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineDynamicTools
|
|
3
|
+
} from "./chunk-A3H32P2J.js";
|
|
4
|
+
|
|
5
|
+
// src/mcp/client.ts
|
|
6
|
+
var mcpDiscover = async (_endpoint, _ctx, _auth, _operations) => {
|
|
7
|
+
throw new Error("mcp client not built yet");
|
|
8
|
+
};
|
|
9
|
+
var mcpCall = async (_endpoint, _tool, _input, _ctx, _auth) => {
|
|
10
|
+
throw new Error("mcp client not built yet");
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/mcp/index.ts
|
|
14
|
+
function fromMCP(endpoint, options) {
|
|
15
|
+
const auth = options?.auth;
|
|
16
|
+
const operations = options?.operations;
|
|
17
|
+
return defineDynamicTools({
|
|
18
|
+
description: `Tools served by the MCP server at ${endpoint}.`,
|
|
19
|
+
...options?.requirements === void 0 ? {} : { requirements: options.requirements },
|
|
20
|
+
discover: (ctx) => mcpDiscover(endpoint, ctx, auth, operations),
|
|
21
|
+
call: (tool, input, ctx) => mcpCall(endpoint, tool.name, input, ctx, auth)
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
fromMCP
|
|
27
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** HTTP credential placement helpers for kind-bearing connected roles. */
|
|
2
|
+
import type { RoleMaterial } from "../authoring/index";
|
|
3
|
+
export type HttpSpendableRole = Extract<RoleMaterial, {
|
|
4
|
+
kind: "bearer" | "oauth" | "basic";
|
|
5
|
+
}>;
|
|
6
|
+
/** Build the protocol-defined Authorization header for a connected role. */
|
|
7
|
+
export declare const authHeaders: (role: HttpSpendableRole) => {
|
|
8
|
+
readonly authorization: string;
|
|
9
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The authoring surface: what app code imports. Pure and synchronous —
|
|
3
|
-
* no effect dependency, no node builtins — so authored files run
|
|
4
|
-
* unmodified in any execution environment, including sandboxed workers.
|
|
5
|
-
*
|
|
6
|
-
* Host-side machinery lives behind subpaths: "apps/manifest" loads and
|
|
7
|
-
* validates authored sources, "apps/runner" executes them.
|
|
8
|
-
*/
|
|
9
1
|
export * from "./authoring/index";
|
|
2
|
+
export * from "./http/index";
|
|
3
|
+
export * from "./mcp/index";
|
|
4
|
+
export * from "./openapi/index";
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fromOpenAPI
|
|
3
|
+
} from "./chunk-F7GZYEAC.js";
|
|
4
|
+
import {
|
|
5
|
+
authHeaders
|
|
6
|
+
} from "./chunk-S5KFP4JQ.js";
|
|
7
|
+
import {
|
|
8
|
+
fromMCP
|
|
9
|
+
} from "./chunk-YUZ4TXOY.js";
|
|
1
10
|
import {
|
|
2
11
|
__enterFile,
|
|
3
12
|
__leaveFile,
|
|
13
|
+
basic,
|
|
14
|
+
bearer,
|
|
4
15
|
defineApp,
|
|
16
|
+
defineDynamicTools,
|
|
5
17
|
defineTool,
|
|
18
|
+
dynamicSpecOf,
|
|
6
19
|
newRegistry,
|
|
7
20
|
oauth,
|
|
21
|
+
recordAuthoringProblem,
|
|
8
22
|
requirementSpecOf,
|
|
9
23
|
secrets,
|
|
10
24
|
service,
|
|
11
25
|
setActiveRegistry,
|
|
12
26
|
toolSpecOf
|
|
13
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-A3H32P2J.js";
|
|
14
28
|
export {
|
|
15
29
|
__enterFile,
|
|
16
30
|
__leaveFile,
|
|
31
|
+
authHeaders,
|
|
32
|
+
basic,
|
|
33
|
+
bearer,
|
|
17
34
|
defineApp,
|
|
35
|
+
defineDynamicTools,
|
|
18
36
|
defineTool,
|
|
37
|
+
dynamicSpecOf,
|
|
38
|
+
fromMCP,
|
|
39
|
+
fromOpenAPI,
|
|
19
40
|
newRegistry,
|
|
20
41
|
oauth,
|
|
42
|
+
recordAuthoringProblem,
|
|
21
43
|
requirementSpecOf,
|
|
22
44
|
secrets,
|
|
23
45
|
service,
|
|
@@ -32,6 +32,13 @@ export interface ManifestTool {
|
|
|
32
32
|
readonly requires: readonly CollectedRequirement[];
|
|
33
33
|
readonly file: string;
|
|
34
34
|
}
|
|
35
|
+
/** A dynamic address segment projected without endpoint or specification data. */
|
|
36
|
+
export interface ManifestDynamic {
|
|
37
|
+
readonly segment: string;
|
|
38
|
+
readonly description: string;
|
|
39
|
+
readonly requires: readonly CollectedRequirement[];
|
|
40
|
+
readonly file: string;
|
|
41
|
+
}
|
|
35
42
|
/** The deterministic public description of an executor app. */
|
|
36
43
|
export interface AppManifestResult {
|
|
37
44
|
readonly digest: string;
|
|
@@ -42,6 +49,7 @@ export interface AppManifestResult {
|
|
|
42
49
|
};
|
|
43
50
|
readonly services: readonly CollectedClaim[];
|
|
44
51
|
readonly tools: readonly ManifestTool[];
|
|
52
|
+
readonly dynamic: readonly ManifestDynamic[];
|
|
45
53
|
}
|
|
46
54
|
declare const PublishError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
47
55
|
readonly _tag: "PublishError";
|
package/dist/manifest/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Manifest: execute an authored app folder
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Manifest: execute an authored app folder and project its registry.
|
|
3
|
+
* This module owns exactly what differs on a host with a filesystem —
|
|
4
|
+
* finding and importing authored modules. Everything after the modules
|
|
5
|
+
* load (validation, canonicalization, digesting) is the shared
|
|
6
|
+
* execution core in ../runtime; sandboxed guests run that same core.
|
|
5
7
|
*/
|
|
6
|
-
import {
|
|
8
|
+
import { Effect, FileSystem } from "effect";
|
|
7
9
|
import { type Registry } from "../authoring/index";
|
|
8
10
|
import { PublishError, type AppManifestResult } from "./contract";
|
|
9
11
|
export * from "./contract";
|
|
@@ -13,6 +15,6 @@ export interface LoadedApp {
|
|
|
13
15
|
readonly result: AppManifestResult;
|
|
14
16
|
}
|
|
15
17
|
/** Loads an app once and returns both its live registry and manifest. @internal */
|
|
16
|
-
export declare const loadApp: (appDir: string) => Effect.Effect<LoadedApp, PublishError,
|
|
18
|
+
export declare const loadApp: (appDir: string) => Effect.Effect<LoadedApp, PublishError, FileSystem.FileSystem>;
|
|
17
19
|
/** Executes an app and returns its deterministic publish manifest. */
|
|
18
|
-
export declare const manifest: (appDir: string) => Effect.Effect<AppManifestResult, PublishError,
|
|
20
|
+
export declare const manifest: (appDir: string) => Effect.Effect<AppManifestResult, PublishError, FileSystem.FileSystem>;
|
package/dist/manifest/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadApp,
|
|
3
3
|
manifest
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-Y6LLTN7W.js";
|
|
5
|
+
import "../chunk-MUFMJRXR.js";
|
|
6
6
|
import {
|
|
7
7
|
JsonSchemaObject,
|
|
8
8
|
PublishError
|
|
9
9
|
} from "../chunk-B7NEUIJL.js";
|
|
10
|
-
import "../chunk-
|
|
10
|
+
import "../chunk-A3H32P2J.js";
|
|
11
11
|
export {
|
|
12
12
|
JsonSchemaObject,
|
|
13
13
|
PublishError,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Stub-contract MCP client used by the fromMCP dynamic pair. */
|
|
2
|
+
import type { AuthComposeFn, DiscoveredToolSpec, OperationSelection } from "../authoring/index";
|
|
3
|
+
/** Normalize authored placement, or apply MCP's bearer default. */
|
|
4
|
+
export declare const composePlacement: (auth: AuthComposeFn<never> | undefined, ctx: unknown) => {
|
|
5
|
+
headers: Headers;
|
|
6
|
+
searchParams: URLSearchParams;
|
|
7
|
+
};
|
|
8
|
+
export declare const mcpDiscover: (_endpoint: string, _ctx: unknown, _auth: AuthComposeFn<never> | undefined, _operations: OperationSelection | undefined) => Promise<readonly DiscoveredToolSpec[]>;
|
|
9
|
+
export declare const mcpCall: (_endpoint: string, _tool: string, _input: unknown, _ctx: unknown, _auth: AuthComposeFn<never> | undefined) => Promise<unknown>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** fromMCP sugar over the authored dynamic-tools primitive. */
|
|
2
|
+
import { type AuthComposeFn, type DynamicToolSet, type OperationSelection, type Requirement } from "../authoring/index";
|
|
3
|
+
export type FromMCPOptions<Reqs extends Readonly<Record<string, Requirement>>> = {
|
|
4
|
+
readonly requirements: Reqs;
|
|
5
|
+
readonly auth?: AuthComposeFn<NoInfer<Reqs>>;
|
|
6
|
+
readonly operations?: OperationSelection;
|
|
7
|
+
};
|
|
8
|
+
export declare function fromMCP<const Reqs extends Readonly<Record<string, Requirement>>>(endpoint: string, options: FromMCPOptions<Reqs>): DynamicToolSet;
|
|
9
|
+
export declare function fromMCP(endpoint: string, options?: {
|
|
10
|
+
readonly operations?: OperationSelection;
|
|
11
|
+
}): DynamicToolSet;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** fromOpenAPI sugar over the authored dynamic-tools primitive. */
|
|
2
|
+
import { type AuthComposeFn, type DynamicToolSet, type OperationSelection, type Requirement, type RequiresAuthCompose } from "../authoring/index";
|
|
3
|
+
export type FromOpenAPIOptions<Reqs extends Readonly<Record<string, Requirement>>> = {
|
|
4
|
+
readonly requirements: Reqs;
|
|
5
|
+
readonly auth?: AuthComposeFn<NoInfer<Reqs>>;
|
|
6
|
+
readonly operations?: OperationSelection;
|
|
7
|
+
} & (RequiresAuthCompose<Reqs> extends true ? {
|
|
8
|
+
readonly auth: AuthComposeFn<NoInfer<Reqs>>;
|
|
9
|
+
} : unknown);
|
|
10
|
+
export type OpenAPIDocumentInput = string | Readonly<Record<string, unknown>>;
|
|
11
|
+
export declare function fromOpenAPI<const Reqs extends Readonly<Record<string, Requirement>>>(spec: OpenAPIDocumentInput, options: FromOpenAPIOptions<Reqs>): DynamicToolSet;
|
|
12
|
+
export declare function fromOpenAPI(spec: OpenAPIDocumentInput, options?: {
|
|
13
|
+
readonly operations?: OperationSelection;
|
|
14
|
+
}): DynamicToolSet;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Stub-contract OpenAPI discovery and call mapper. */
|
|
2
|
+
import type { AuthComposeFn, DiscoveredToolSpec, OperationSelection } from "../authoring/index";
|
|
3
|
+
export declare const openapiDiscover: (_spec: string | Readonly<Record<string, unknown>>, _ctx: unknown, _auth: AuthComposeFn<never> | undefined, _operations: OperationSelection | undefined) => Promise<readonly DiscoveredToolSpec[]>;
|
|
4
|
+
export declare const openapiCall: (_tool: {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly detail: unknown;
|
|
7
|
+
}, _input: unknown, _ctx: unknown, _auth: AuthComposeFn<never> | undefined) => Promise<unknown>;
|