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
package/dist/chunk-J5AVXTHA.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
__enterFile,
|
|
3
|
-
__leaveFile,
|
|
4
|
-
newRegistry,
|
|
5
|
-
setActiveRegistry
|
|
6
|
-
} from "./chunk-LWKQDDLZ.js";
|
|
7
|
-
import {
|
|
8
|
-
JsonSchemaObject,
|
|
9
|
-
PublishError
|
|
10
|
-
} from "./chunk-B7NEUIJL.js";
|
|
11
|
-
import {
|
|
12
|
-
collectRegistry
|
|
13
|
-
} from "./chunk-RZA7AXBK.js";
|
|
14
|
-
|
|
15
|
-
// src/manifest/index.ts
|
|
16
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
|
-
import { Crypto, Effect, FileSystem, Schema, Semaphore } from "effect";
|
|
18
|
-
var hasStandardJsonSchema = (input) => "~standard" in input && typeof input["~standard"].jsonSchema?.input === "function";
|
|
19
|
-
var compileInputSchema = (input, where) => {
|
|
20
|
-
if ("~standard" in input) {
|
|
21
|
-
if (!hasStandardJsonSchema(input))
|
|
22
|
-
return {
|
|
23
|
-
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`
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
try {
|
|
27
|
-
const compiled = strictify(
|
|
28
|
-
hasStandardJsonSchema(input) ? input["~standard"].jsonSchema.input({ target: "draft-2020-12" }) : structuredClone(input)
|
|
29
|
-
);
|
|
30
|
-
return { value: Schema.decodeUnknownSync(JsonSchemaObject)(compiled) };
|
|
31
|
-
} catch (error) {
|
|
32
|
-
return { problem: schemaPublishProblem(where, error) };
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
var schemaPublishProblem = (where, error) => `${where}: input schema could not compile to JSON schema (${error instanceof Error ? error.message : String(error)})`;
|
|
36
|
-
var strictify = (node) => {
|
|
37
|
-
if (typeof node !== "object" || node === null) return node;
|
|
38
|
-
if (Array.isArray(node)) return node.map(strictify);
|
|
39
|
-
const object = { ...node };
|
|
40
|
-
if (object.type === "object" && object.additionalProperties === void 0)
|
|
41
|
-
object.additionalProperties = false;
|
|
42
|
-
for (const key of ["items", "anyOf", "oneOf", "allOf"])
|
|
43
|
-
if (key in object) object[key] = strictify(object[key]);
|
|
44
|
-
for (const key of ["properties", "$defs"])
|
|
45
|
-
if (object[key] !== void 0 && typeof object[key] === "object" && object[key] !== null)
|
|
46
|
-
object[key] = Object.fromEntries(
|
|
47
|
-
Object.entries(object[key]).map(([name, value]) => [
|
|
48
|
-
name,
|
|
49
|
-
strictify(value)
|
|
50
|
-
])
|
|
51
|
-
);
|
|
52
|
-
return object;
|
|
53
|
-
};
|
|
54
|
-
var digestOf = (content) => Effect.gen(function* () {
|
|
55
|
-
const crypto = yield* Crypto.Crypto;
|
|
56
|
-
const bytes = yield* Effect.orDie(
|
|
57
|
-
crypto.digest("SHA-256", new TextEncoder().encode(content))
|
|
58
|
-
);
|
|
59
|
-
return Array.from(bytes.slice(0, 8), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
60
|
-
});
|
|
61
|
-
var loadPermit = Semaphore.makeUnsafe(1);
|
|
62
|
-
var runNonce = 0;
|
|
63
|
-
var baseAppImports = /* @__PURE__ */ new Set();
|
|
64
|
-
var moduleFailure = (where, error) => new PublishError({
|
|
65
|
-
problems: [
|
|
66
|
-
`${where}: failed to execute (${error instanceof Error ? error.message : String(error)})`
|
|
67
|
-
]
|
|
68
|
-
});
|
|
69
|
-
var canonicalModuleUrl = (path, where) => Effect.gen(function* () {
|
|
70
|
-
const fs = yield* FileSystem.FileSystem;
|
|
71
|
-
const realPath = yield* fs.realPath(path).pipe(
|
|
72
|
-
Effect.mapError((error) => moduleFailure(where, error))
|
|
73
|
-
);
|
|
74
|
-
return pathToFileURL(realPath);
|
|
75
|
-
});
|
|
76
|
-
var importModule = (specifier, where) => Effect.tryPromise({
|
|
77
|
-
try: () => import(specifier).then(() => void 0),
|
|
78
|
-
catch: (error) => moduleFailure(where, error)
|
|
79
|
-
});
|
|
80
|
-
var recordModuleProblems = (registry, effect) => effect.pipe(
|
|
81
|
-
Effect.catchTag(
|
|
82
|
-
"PublishError",
|
|
83
|
-
(error) => Effect.sync(() => {
|
|
84
|
-
registry.problems.push(...error.problems.map((message) => ({ message })));
|
|
85
|
-
})
|
|
86
|
-
)
|
|
87
|
-
);
|
|
88
|
-
var executeModules = (appDir, files) => loadPermit.withPermit(
|
|
89
|
-
Effect.uninterruptibleMask(
|
|
90
|
-
(restore) => Effect.gen(function* () {
|
|
91
|
-
const registry = newRegistry();
|
|
92
|
-
const previous = yield* Effect.sync(() => setActiveRegistry(registry));
|
|
93
|
-
const nonce = yield* Effect.sync(() => ++runNonce);
|
|
94
|
-
const execute = Effect.gen(function* () {
|
|
95
|
-
__enterFile("app.ts");
|
|
96
|
-
yield* recordModuleProblems(
|
|
97
|
-
registry,
|
|
98
|
-
Effect.gen(function* () {
|
|
99
|
-
const appUrl = yield* canonicalModuleUrl(`${appDir}/app.ts`, "app.ts");
|
|
100
|
-
const baseUrl = appUrl.href;
|
|
101
|
-
const specifier = baseAppImports.has(baseUrl) ? `${fileURLToPath(appUrl)}?m=${nonce}` : baseUrl;
|
|
102
|
-
yield* importModule(specifier, "app.ts");
|
|
103
|
-
yield* Effect.sync(() => baseAppImports.add(baseUrl));
|
|
104
|
-
})
|
|
105
|
-
);
|
|
106
|
-
for (const file of files) {
|
|
107
|
-
const where = `tools/${file}`;
|
|
108
|
-
__enterFile(where);
|
|
109
|
-
yield* recordModuleProblems(
|
|
110
|
-
registry,
|
|
111
|
-
Effect.gen(function* () {
|
|
112
|
-
const url = yield* canonicalModuleUrl(`${appDir}/tools/${file}`, where);
|
|
113
|
-
yield* importModule(`${fileURLToPath(url)}?m=${nonce}`, where);
|
|
114
|
-
})
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
yield* restore(execute).pipe(
|
|
119
|
-
Effect.ensuring(
|
|
120
|
-
Effect.sync(() => {
|
|
121
|
-
__leaveFile();
|
|
122
|
-
setActiveRegistry(previous);
|
|
123
|
-
})
|
|
124
|
-
)
|
|
125
|
-
);
|
|
126
|
-
return registry;
|
|
127
|
-
})
|
|
128
|
-
)
|
|
129
|
-
);
|
|
130
|
-
var loadApp = (appDir) => Effect.gen(function* () {
|
|
131
|
-
const fs = yield* FileSystem.FileSystem;
|
|
132
|
-
const files = (yield* fs.readDirectory(`${appDir}/tools`).pipe(
|
|
133
|
-
Effect.mapError((error) => new PublishError({ problems: [`tools/: ${error.message}`] }))
|
|
134
|
-
)).filter((file) => file.endsWith(".ts")).sort();
|
|
135
|
-
const registry = yield* executeModules(appDir, files);
|
|
136
|
-
const collected = collectRegistry(registry, compileInputSchema);
|
|
137
|
-
if (collected.problems.length > 0)
|
|
138
|
-
return yield* new PublishError({ problems: collected.problems });
|
|
139
|
-
if (!("manifest" in collected))
|
|
140
|
-
return yield* Effect.die("validated registry projection unexpectedly missing");
|
|
141
|
-
return {
|
|
142
|
-
registry,
|
|
143
|
-
result: {
|
|
144
|
-
digest: `snap:${yield* digestOf(collected.canonical)}`,
|
|
145
|
-
...collected.manifest
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
});
|
|
149
|
-
var manifest = (appDir) => Effect.map(loadApp(appDir), ({ result }) => result);
|
|
150
|
-
|
|
151
|
-
export {
|
|
152
|
-
loadApp,
|
|
153
|
-
manifest
|
|
154
|
-
};
|
package/dist/chunk-RZA7AXBK.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
// src/manifest/collect.ts
|
|
2
|
-
var collectRegistry = (registry, compileInputSchema) => {
|
|
3
|
-
const requirementKey = Symbol.for("executor/apps/requirement");
|
|
4
|
-
const problems = registry.problems.map((problem) => problem.message);
|
|
5
|
-
const app = registry.app;
|
|
6
|
-
const requirementSpecOf = (value) => typeof value === "object" && value !== null && requirementKey in value ? value[requirementKey] : void 0;
|
|
7
|
-
const canonicalJson = (value) => {
|
|
8
|
-
const sortKeys = (node) => {
|
|
9
|
-
if (Array.isArray(node)) return node.map(sortKeys);
|
|
10
|
-
if (typeof node === "object" && node !== null)
|
|
11
|
-
return Object.fromEntries(
|
|
12
|
-
Object.entries(node).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, sortKeys(child)])
|
|
13
|
-
);
|
|
14
|
-
return node;
|
|
15
|
-
};
|
|
16
|
-
return JSON.stringify(sortKeys(value));
|
|
17
|
-
};
|
|
18
|
-
const claims = /* @__PURE__ */ new Map();
|
|
19
|
-
for (const [origin, registered] of registry.services) {
|
|
20
|
-
if (registered.file !== "app.ts") continue;
|
|
21
|
-
const { decl } = registered;
|
|
22
|
-
claims.set(origin, {
|
|
23
|
-
origin,
|
|
24
|
-
reach: decl.reach ?? [],
|
|
25
|
-
params: decl.params ?? {},
|
|
26
|
-
auth: decl.auth ?? {}
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
const collectRequirement = (role, requirement, where) => {
|
|
30
|
-
const spec = requirementSpecOf(requirement);
|
|
31
|
-
if (spec === void 0) {
|
|
32
|
-
problems.push(`${where}: requirement '${role}' is not a service/requirement value`);
|
|
33
|
-
return void 0;
|
|
34
|
-
}
|
|
35
|
-
const origin = spec.service.origin;
|
|
36
|
-
if (!claims.has(origin))
|
|
37
|
-
problems.push(`${where}: requirement '${role}' names ${origin}, which app.ts does not declare`);
|
|
38
|
-
if (spec.grant !== void 0 && !(spec.grant in (spec.service.auth ?? {})))
|
|
39
|
-
problems.push(
|
|
40
|
-
`${where}: requirement '${role}' pins grant '${spec.grant}', not a auth method on ${origin}`
|
|
41
|
-
);
|
|
42
|
-
return {
|
|
43
|
-
role,
|
|
44
|
-
service: origin,
|
|
45
|
-
arity: spec.arity,
|
|
46
|
-
...spec.grant === void 0 ? {} : { grant: spec.grant },
|
|
47
|
-
...spec.description === void 0 ? {} : { description: spec.description }
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
if (app === void 0) problems.push("app.ts must call defineApp({...})");
|
|
51
|
-
else if (typeof app.description !== "string" || app.description.trim().length === 0)
|
|
52
|
-
problems.push("app.ts: app description must not be empty");
|
|
53
|
-
for (const [file, summary] of registry.toolFiles) {
|
|
54
|
-
if (summary.definitions > 1 && summary.hasUnnamedTool)
|
|
55
|
-
problems.push(`${file}: multi-tool files must name every tool`);
|
|
56
|
-
}
|
|
57
|
-
const tools = [];
|
|
58
|
-
if (app !== void 0) {
|
|
59
|
-
for (const [name, registered] of registry.tools) {
|
|
60
|
-
const { spec, file } = registered;
|
|
61
|
-
const address = `${app.key}/${name}`;
|
|
62
|
-
if (typeof spec.description !== "string" || spec.description.trim().length === 0)
|
|
63
|
-
problems.push(`${file}: tool '${name}' description must not be empty`);
|
|
64
|
-
const requires = Object.entries(spec.requirements ?? {}).flatMap(([role, requirement]) => {
|
|
65
|
-
const collected = collectRequirement(role, requirement, address);
|
|
66
|
-
return collected === void 0 ? [] : [collected];
|
|
67
|
-
}).sort((left, right) => left.role.localeCompare(right.role));
|
|
68
|
-
let compiled;
|
|
69
|
-
try {
|
|
70
|
-
compiled = compileInputSchema(spec.input, address);
|
|
71
|
-
} catch (error) {
|
|
72
|
-
compiled = {
|
|
73
|
-
problem: `${address}: input schema could not compile to JSON schema (${error instanceof Error ? error.message : String(error)})`
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
if ("problem" in compiled) {
|
|
77
|
-
problems.push(compiled.problem);
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
tools.push({
|
|
81
|
-
address,
|
|
82
|
-
name,
|
|
83
|
-
description: spec.description,
|
|
84
|
-
inputSchema: compiled.value,
|
|
85
|
-
annotations: spec.annotations ?? {},
|
|
86
|
-
requires,
|
|
87
|
-
file
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (problems.length > 0 || app === void 0) return { problems };
|
|
92
|
-
const services = [...claims.values()].sort(
|
|
93
|
-
(left, right) => left.origin.localeCompare(right.origin)
|
|
94
|
-
);
|
|
95
|
-
const sortedTools = [...tools].sort((left, right) => left.address.localeCompare(right.address));
|
|
96
|
-
const appResult = { key: app.key, label: app.label, description: app.description };
|
|
97
|
-
const manifest = { app: appResult, services, tools: sortedTools };
|
|
98
|
-
return {
|
|
99
|
-
problems: [],
|
|
100
|
-
manifest,
|
|
101
|
-
canonical: canonicalJson(manifest)
|
|
102
|
-
};
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export {
|
|
106
|
-
collectRegistry
|
|
107
|
-
};
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type { AuthMethod, ParamSpec, Registry } from "../authoring/index";
|
|
2
|
-
type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
|
|
3
|
-
readonly [key: string]: JsonValue;
|
|
4
|
-
};
|
|
5
|
-
type JsonSchemaObject = Readonly<Record<string, JsonValue>>;
|
|
6
|
-
interface CollectedClaim {
|
|
7
|
-
readonly origin: string;
|
|
8
|
-
readonly reach: readonly string[];
|
|
9
|
-
readonly params: Readonly<Record<string, ParamSpec>>;
|
|
10
|
-
readonly auth: Readonly<Record<string, AuthMethod>>;
|
|
11
|
-
}
|
|
12
|
-
interface CollectedRequirement {
|
|
13
|
-
readonly role: string;
|
|
14
|
-
readonly service: string;
|
|
15
|
-
readonly arity: "one" | "optional" | "many";
|
|
16
|
-
readonly grant?: string;
|
|
17
|
-
readonly description?: string;
|
|
18
|
-
}
|
|
19
|
-
interface CollectedTool {
|
|
20
|
-
readonly address: string;
|
|
21
|
-
readonly name: string;
|
|
22
|
-
readonly description: string;
|
|
23
|
-
readonly inputSchema: JsonSchemaObject;
|
|
24
|
-
readonly annotations: {
|
|
25
|
-
readonly readOnly?: boolean;
|
|
26
|
-
readonly destructive?: boolean;
|
|
27
|
-
readonly scopes?: readonly string[];
|
|
28
|
-
};
|
|
29
|
-
readonly requires: readonly CollectedRequirement[];
|
|
30
|
-
readonly file: string;
|
|
31
|
-
}
|
|
32
|
-
interface CollectedManifest {
|
|
33
|
-
readonly app: {
|
|
34
|
-
readonly key: string;
|
|
35
|
-
readonly label: string;
|
|
36
|
-
readonly description: string;
|
|
37
|
-
};
|
|
38
|
-
readonly services: readonly CollectedClaim[];
|
|
39
|
-
readonly tools: readonly CollectedTool[];
|
|
40
|
-
}
|
|
41
|
-
/** Result of compiling one authored input schema during registry projection. */
|
|
42
|
-
export type InputSchemaCompilation = {
|
|
43
|
-
readonly value: JsonSchemaObject;
|
|
44
|
-
} | {
|
|
45
|
-
readonly problem: string;
|
|
46
|
-
};
|
|
47
|
-
/** Registry projection before the deployment-specific digest implementation. */
|
|
48
|
-
export type RegistryCollection = {
|
|
49
|
-
readonly problems: readonly string[];
|
|
50
|
-
} | {
|
|
51
|
-
readonly problems: readonly [];
|
|
52
|
-
readonly manifest: CollectedManifest;
|
|
53
|
-
readonly canonical: string;
|
|
54
|
-
};
|
|
55
|
-
/**
|
|
56
|
-
* Validate and project an executed authoring registry into deterministic data.
|
|
57
|
-
*
|
|
58
|
-
* This function is deliberately closure-free: the workerd runner injects its
|
|
59
|
-
* source into the guest while the local manifest calls the same function
|
|
60
|
-
* directly. Schema compilation is supplied because each runtime owns its
|
|
61
|
-
* boundary decoder.
|
|
62
|
-
*/
|
|
63
|
-
export declare const collectRegistry: (registry: Registry, compileInputSchema: (input: object, where: string) => InputSchemaCompilation) => RegistryCollection;
|
|
64
|
-
export {};
|
package/src/manifest/collect.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import type { AuthMethod, ParamSpec, Registry } from "../authoring/index";
|
|
2
|
-
|
|
3
|
-
type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
|
|
4
|
-
readonly [key: string]: JsonValue;
|
|
5
|
-
};
|
|
6
|
-
type JsonSchemaObject = Readonly<Record<string, JsonValue>>;
|
|
7
|
-
|
|
8
|
-
interface CollectedClaim {
|
|
9
|
-
readonly origin: string;
|
|
10
|
-
readonly reach: readonly string[];
|
|
11
|
-
readonly params: Readonly<Record<string, ParamSpec>>;
|
|
12
|
-
readonly auth: Readonly<Record<string, AuthMethod>>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface CollectedRequirement {
|
|
16
|
-
readonly role: string;
|
|
17
|
-
readonly service: string;
|
|
18
|
-
readonly arity: "one" | "optional" | "many";
|
|
19
|
-
readonly grant?: string;
|
|
20
|
-
readonly description?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface CollectedTool {
|
|
24
|
-
readonly address: string;
|
|
25
|
-
readonly name: string;
|
|
26
|
-
readonly description: string;
|
|
27
|
-
readonly inputSchema: JsonSchemaObject;
|
|
28
|
-
readonly annotations: {
|
|
29
|
-
readonly readOnly?: boolean;
|
|
30
|
-
readonly destructive?: boolean;
|
|
31
|
-
readonly scopes?: readonly string[];
|
|
32
|
-
};
|
|
33
|
-
readonly requires: readonly CollectedRequirement[];
|
|
34
|
-
readonly file: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface CollectedManifest {
|
|
38
|
-
readonly app: { readonly key: string; readonly label: string; readonly description: string };
|
|
39
|
-
readonly services: readonly CollectedClaim[];
|
|
40
|
-
readonly tools: readonly CollectedTool[];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Result of compiling one authored input schema during registry projection. */
|
|
44
|
-
export type InputSchemaCompilation =
|
|
45
|
-
| { readonly value: JsonSchemaObject }
|
|
46
|
-
| { readonly problem: string };
|
|
47
|
-
|
|
48
|
-
/** Registry projection before the deployment-specific digest implementation. */
|
|
49
|
-
export type RegistryCollection =
|
|
50
|
-
| { readonly problems: readonly string[] }
|
|
51
|
-
| {
|
|
52
|
-
readonly problems: readonly [];
|
|
53
|
-
readonly manifest: CollectedManifest;
|
|
54
|
-
readonly canonical: string;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Validate and project an executed authoring registry into deterministic data.
|
|
59
|
-
*
|
|
60
|
-
* This function is deliberately closure-free: the workerd runner injects its
|
|
61
|
-
* source into the guest while the local manifest calls the same function
|
|
62
|
-
* directly. Schema compilation is supplied because each runtime owns its
|
|
63
|
-
* boundary decoder.
|
|
64
|
-
*/
|
|
65
|
-
export const collectRegistry = (
|
|
66
|
-
registry: Registry,
|
|
67
|
-
compileInputSchema: (input: object, where: string) => InputSchemaCompilation,
|
|
68
|
-
): RegistryCollection => {
|
|
69
|
-
const requirementKey = Symbol.for("executor/apps/requirement");
|
|
70
|
-
const problems = registry.problems.map((problem) => problem.message);
|
|
71
|
-
const app = registry.app;
|
|
72
|
-
|
|
73
|
-
const requirementSpecOf = (value: unknown) =>
|
|
74
|
-
typeof value === "object" && value !== null && requirementKey in value
|
|
75
|
-
? (value as Record<PropertyKey, unknown>)[requirementKey] as {
|
|
76
|
-
readonly service: {
|
|
77
|
-
readonly origin: string;
|
|
78
|
-
readonly auth?: Readonly<Record<string, unknown>>;
|
|
79
|
-
};
|
|
80
|
-
readonly arity: "one" | "optional" | "many";
|
|
81
|
-
readonly grant?: string;
|
|
82
|
-
readonly description?: string;
|
|
83
|
-
}
|
|
84
|
-
: undefined;
|
|
85
|
-
|
|
86
|
-
const canonicalJson = (value: unknown): string => {
|
|
87
|
-
const sortKeys = (node: unknown): unknown => {
|
|
88
|
-
if (Array.isArray(node)) return node.map(sortKeys);
|
|
89
|
-
if (typeof node === "object" && node !== null)
|
|
90
|
-
return Object.fromEntries(
|
|
91
|
-
Object.entries(node as Record<string, unknown>)
|
|
92
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
93
|
-
.map(([key, child]) => [key, sortKeys(child)]),
|
|
94
|
-
);
|
|
95
|
-
return node;
|
|
96
|
-
};
|
|
97
|
-
return JSON.stringify(sortKeys(value));
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const claims = new Map<string, CollectedClaim>();
|
|
101
|
-
for (const [origin, registered] of registry.services) {
|
|
102
|
-
if (registered.file !== "app.ts") continue;
|
|
103
|
-
const { decl } = registered;
|
|
104
|
-
claims.set(origin, {
|
|
105
|
-
origin,
|
|
106
|
-
reach: decl.reach ?? [],
|
|
107
|
-
params: decl.params ?? {},
|
|
108
|
-
auth: decl.auth ?? {},
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const collectRequirement = (
|
|
113
|
-
role: string,
|
|
114
|
-
requirement: unknown,
|
|
115
|
-
where: string,
|
|
116
|
-
): CollectedRequirement | undefined => {
|
|
117
|
-
const spec = requirementSpecOf(requirement);
|
|
118
|
-
if (spec === undefined) {
|
|
119
|
-
problems.push(`${where}: requirement '${role}' is not a service/requirement value`);
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
const origin = spec.service.origin;
|
|
123
|
-
if (!claims.has(origin))
|
|
124
|
-
problems.push(`${where}: requirement '${role}' names ${origin}, which app.ts does not declare`);
|
|
125
|
-
if (spec.grant !== undefined && !(spec.grant in (spec.service.auth ?? {})))
|
|
126
|
-
problems.push(
|
|
127
|
-
`${where}: requirement '${role}' pins grant '${spec.grant}', not a auth method on ${origin}`,
|
|
128
|
-
);
|
|
129
|
-
return {
|
|
130
|
-
role,
|
|
131
|
-
service: origin,
|
|
132
|
-
arity: spec.arity,
|
|
133
|
-
...(spec.grant === undefined ? {} : { grant: spec.grant }),
|
|
134
|
-
...(spec.description === undefined ? {} : { description: spec.description }),
|
|
135
|
-
};
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
if (app === undefined) problems.push("app.ts must call defineApp({...})");
|
|
139
|
-
else if (typeof app.description !== "string" || app.description.trim().length === 0)
|
|
140
|
-
problems.push("app.ts: app description must not be empty");
|
|
141
|
-
|
|
142
|
-
for (const [file, summary] of registry.toolFiles) {
|
|
143
|
-
if (summary.definitions > 1 && summary.hasUnnamedTool)
|
|
144
|
-
problems.push(`${file}: multi-tool files must name every tool`);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const tools: CollectedTool[] = [];
|
|
148
|
-
if (app !== undefined) {
|
|
149
|
-
for (const [name, registered] of registry.tools) {
|
|
150
|
-
const { spec, file } = registered;
|
|
151
|
-
const address = `${app.key}/${name}`;
|
|
152
|
-
if (typeof spec.description !== "string" || spec.description.trim().length === 0)
|
|
153
|
-
problems.push(`${file}: tool '${name}' description must not be empty`);
|
|
154
|
-
const requires = Object.entries(spec.requirements ?? {})
|
|
155
|
-
.flatMap(([role, requirement]) => {
|
|
156
|
-
const collected = collectRequirement(role, requirement, address);
|
|
157
|
-
return collected === undefined ? [] : [collected];
|
|
158
|
-
})
|
|
159
|
-
.sort((left, right) => left.role.localeCompare(right.role));
|
|
160
|
-
let compiled: InputSchemaCompilation;
|
|
161
|
-
try {
|
|
162
|
-
compiled = compileInputSchema(spec.input, address);
|
|
163
|
-
} catch (error) {
|
|
164
|
-
compiled = {
|
|
165
|
-
problem: `${address}: input schema could not compile to JSON schema (${
|
|
166
|
-
error instanceof Error ? error.message : String(error)
|
|
167
|
-
})`,
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
if ("problem" in compiled) {
|
|
171
|
-
problems.push(compiled.problem);
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
tools.push({
|
|
175
|
-
address,
|
|
176
|
-
name,
|
|
177
|
-
description: spec.description,
|
|
178
|
-
inputSchema: compiled.value,
|
|
179
|
-
annotations: spec.annotations ?? {},
|
|
180
|
-
requires,
|
|
181
|
-
file,
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (problems.length > 0 || app === undefined) return { problems };
|
|
187
|
-
const services = [...claims.values()].sort((left, right) =>
|
|
188
|
-
left.origin.localeCompare(right.origin),
|
|
189
|
-
);
|
|
190
|
-
const sortedTools = [...tools].sort((left, right) => left.address.localeCompare(right.address));
|
|
191
|
-
const appResult = { key: app.key, label: app.label, description: app.description };
|
|
192
|
-
const manifest = { app: appResult, services, tools: sortedTools } satisfies CollectedManifest;
|
|
193
|
-
return {
|
|
194
|
-
problems: [],
|
|
195
|
-
manifest,
|
|
196
|
-
canonical: canonicalJson(manifest),
|
|
197
|
-
};
|
|
198
|
-
};
|