apps 1.0.0-beta.0 → 1.0.0-beta.1
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/dist/authoring/index.js +11 -11
- package/dist/{index-mcjwntr2.js → chunk-B7NEUIJL.js} +7 -6
- package/dist/chunk-J5AVXTHA.js +154 -0
- package/dist/{index-8wmxmfhc.js → chunk-LWKQDDLZ.js} +30 -22
- package/dist/{index-dh4z81es.js → chunk-NFLNA2SC.js} +5 -4
- package/dist/chunk-RZA7AXBK.js +107 -0
- package/dist/index.js +11 -11
- package/dist/manifest/contract.js +3 -3
- package/dist/manifest/index.js +7 -6
- package/dist/runner/index.js +41 -21
- package/dist/runner/protocol.js +1 -1
- package/dist/workerd/index.js +3 -0
- package/package.json +3 -2
- package/dist/index-wfngzvv2.js +0 -228
package/dist/authoring/index.js
CHANGED
|
@@ -10,17 +10,17 @@ import {
|
|
|
10
10
|
service,
|
|
11
11
|
setActiveRegistry,
|
|
12
12
|
toolSpecOf
|
|
13
|
-
} from "../
|
|
13
|
+
} from "../chunk-LWKQDDLZ.js";
|
|
14
14
|
export {
|
|
15
|
-
|
|
16
|
-
setActiveRegistry,
|
|
17
|
-
service,
|
|
18
|
-
secrets,
|
|
19
|
-
requirementSpecOf,
|
|
20
|
-
oauth,
|
|
21
|
-
newRegistry,
|
|
22
|
-
defineTool,
|
|
23
|
-
defineApp,
|
|
15
|
+
__enterFile,
|
|
24
16
|
__leaveFile,
|
|
25
|
-
|
|
17
|
+
defineApp,
|
|
18
|
+
defineTool,
|
|
19
|
+
newRegistry,
|
|
20
|
+
oauth,
|
|
21
|
+
requirementSpecOf,
|
|
22
|
+
secrets,
|
|
23
|
+
service,
|
|
24
|
+
setActiveRegistry,
|
|
25
|
+
toolSpecOf
|
|
26
26
|
};
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// src/manifest/contract.ts
|
|
2
2
|
import { Data, Schema } from "effect";
|
|
3
3
|
var JsonSchemaObject = Schema.Record(Schema.String, Schema.Json);
|
|
4
|
-
|
|
5
|
-
class PublishError extends Data.TaggedError("PublishError") {
|
|
4
|
+
var PublishError = class extends Data.TaggedError("PublishError") {
|
|
6
5
|
get message() {
|
|
7
6
|
return `publish failed:
|
|
8
|
-
${this.problems.map((problem) => ` - ${problem}`).join(
|
|
9
|
-
`)}`;
|
|
7
|
+
${this.problems.map((problem) => ` - ${problem}`).join("\n")}`;
|
|
10
8
|
}
|
|
11
|
-
}
|
|
9
|
+
};
|
|
12
10
|
|
|
13
|
-
export {
|
|
11
|
+
export {
|
|
12
|
+
JsonSchemaObject,
|
|
13
|
+
PublishError
|
|
14
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
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
|
+
};
|
|
@@ -4,12 +4,12 @@ var oauth = (opts) => ({ kind: "oauth", ...opts });
|
|
|
4
4
|
var RequirementKey = Symbol.for("executor/apps/requirement");
|
|
5
5
|
var DynamicKey = Symbol.for("executor/apps/dynamic");
|
|
6
6
|
var newRegistry = () => ({
|
|
7
|
-
app:
|
|
8
|
-
services: new Map,
|
|
9
|
-
tools: new Map,
|
|
10
|
-
dynamic: new Map,
|
|
7
|
+
app: void 0,
|
|
8
|
+
services: /* @__PURE__ */ new Map(),
|
|
9
|
+
tools: /* @__PURE__ */ new Map(),
|
|
10
|
+
dynamic: /* @__PURE__ */ new Map(),
|
|
11
11
|
problems: [],
|
|
12
|
-
toolFiles: new Map
|
|
12
|
+
toolFiles: /* @__PURE__ */ new Map()
|
|
13
13
|
});
|
|
14
14
|
var activeRegistry = newRegistry();
|
|
15
15
|
var currentFile;
|
|
@@ -22,11 +22,10 @@ var __enterFile = (path) => {
|
|
|
22
22
|
currentFile = path;
|
|
23
23
|
};
|
|
24
24
|
var __leaveFile = () => {
|
|
25
|
-
currentFile =
|
|
25
|
+
currentFile = void 0;
|
|
26
26
|
};
|
|
27
27
|
var sourceFileFor = (claim) => {
|
|
28
|
-
if (currentFile !==
|
|
29
|
-
return currentFile;
|
|
28
|
+
if (currentFile !== void 0) return currentFile;
|
|
30
29
|
activeRegistry.problems.push({
|
|
31
30
|
message: `${claim} called outside an authored module import`
|
|
32
31
|
});
|
|
@@ -51,16 +50,14 @@ var service = (decl) => {
|
|
|
51
50
|
activeRegistry.services.set(decl.origin, { decl, file });
|
|
52
51
|
return value;
|
|
53
52
|
};
|
|
54
|
-
var isRequirement = (value) => typeof value === "object" && value !== null &&
|
|
55
|
-
var isService = (value) => isRequirement(value) &&
|
|
56
|
-
var requirementSpecOf = (value) => isService(value) || isRequirement(value) ? value[RequirementKey] :
|
|
53
|
+
var isRequirement = (value) => typeof value === "object" && value !== null && RequirementKey in value;
|
|
54
|
+
var isService = (value) => isRequirement(value) && "decl" in value && "optional" in value && "array" in value && "grant" in value;
|
|
55
|
+
var requirementSpecOf = (value) => isService(value) || isRequirement(value) ? value[RequirementKey] : void 0;
|
|
57
56
|
var defineApp = (manifest) => {
|
|
58
57
|
const file = sourceFileFor("defineApp(...)");
|
|
59
58
|
const value = { kind: "executor-app", ...manifest };
|
|
60
|
-
if (activeRegistry.app ===
|
|
61
|
-
|
|
62
|
-
else
|
|
63
|
-
activeRegistry.problems.push({ message: `${file}: duplicate defineApp(...)` });
|
|
59
|
+
if (activeRegistry.app === void 0) activeRegistry.app = value;
|
|
60
|
+
else activeRegistry.problems.push({ message: `${file}: duplicate defineApp(...)` });
|
|
64
61
|
return value;
|
|
65
62
|
};
|
|
66
63
|
var ToolKey = Symbol.for("executor/apps/tool");
|
|
@@ -73,18 +70,29 @@ var defineTool = (spec) => {
|
|
|
73
70
|
const fileSummary = activeRegistry.toolFiles.get(file);
|
|
74
71
|
activeRegistry.toolFiles.set(file, {
|
|
75
72
|
definitions: (fileSummary?.definitions ?? 0) + 1,
|
|
76
|
-
hasUnnamedTool: fileSummary?.hasUnnamedTool === true || spec.name ===
|
|
73
|
+
hasUnnamedTool: fileSummary?.hasUnnamedTool === true || spec.name === void 0
|
|
77
74
|
});
|
|
78
75
|
const existing = activeRegistry.tools.get(name);
|
|
79
|
-
if (existing !==
|
|
76
|
+
if (existing !== void 0) {
|
|
80
77
|
activeRegistry.problems.push({
|
|
81
78
|
message: `${file}: tool name '${name}' is already defined in ${existing.file}`
|
|
82
79
|
});
|
|
83
|
-
} else
|
|
84
|
-
activeRegistry.tools.set(name, { spec, file });
|
|
80
|
+
} else activeRegistry.tools.set(name, { spec, file });
|
|
85
81
|
return value;
|
|
86
82
|
};
|
|
87
|
-
var isTool = (value) => typeof value === "object" && value !== null &&
|
|
88
|
-
var toolSpecOf = (value) => isTool(value) ? value[ToolKey] :
|
|
83
|
+
var isTool = (value) => typeof value === "object" && value !== null && ToolKey in value;
|
|
84
|
+
var toolSpecOf = (value) => isTool(value) ? value[ToolKey] : void 0;
|
|
89
85
|
|
|
90
|
-
export {
|
|
86
|
+
export {
|
|
87
|
+
secrets,
|
|
88
|
+
oauth,
|
|
89
|
+
newRegistry,
|
|
90
|
+
setActiveRegistry,
|
|
91
|
+
__enterFile,
|
|
92
|
+
__leaveFile,
|
|
93
|
+
service,
|
|
94
|
+
requirementSpecOf,
|
|
95
|
+
defineApp,
|
|
96
|
+
defineTool,
|
|
97
|
+
toolSpecOf
|
|
98
|
+
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// src/runner/protocol.ts
|
|
2
2
|
import { Data } from "effect";
|
|
3
|
-
|
|
4
|
-
class CallError extends Data.TaggedError("CallError") {
|
|
3
|
+
var CallError = class extends Data.TaggedError("CallError") {
|
|
5
4
|
get message() {
|
|
6
5
|
return `${this.target}: ${this.reason}`;
|
|
7
6
|
}
|
|
8
|
-
}
|
|
7
|
+
};
|
|
9
8
|
|
|
10
|
-
export {
|
|
9
|
+
export {
|
|
10
|
+
CallError
|
|
11
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -10,17 +10,17 @@ import {
|
|
|
10
10
|
service,
|
|
11
11
|
setActiveRegistry,
|
|
12
12
|
toolSpecOf
|
|
13
|
-
} from "./
|
|
13
|
+
} from "./chunk-LWKQDDLZ.js";
|
|
14
14
|
export {
|
|
15
|
-
|
|
16
|
-
setActiveRegistry,
|
|
17
|
-
service,
|
|
18
|
-
secrets,
|
|
19
|
-
requirementSpecOf,
|
|
20
|
-
oauth,
|
|
21
|
-
newRegistry,
|
|
22
|
-
defineTool,
|
|
23
|
-
defineApp,
|
|
15
|
+
__enterFile,
|
|
24
16
|
__leaveFile,
|
|
25
|
-
|
|
17
|
+
defineApp,
|
|
18
|
+
defineTool,
|
|
19
|
+
newRegistry,
|
|
20
|
+
oauth,
|
|
21
|
+
requirementSpecOf,
|
|
22
|
+
secrets,
|
|
23
|
+
service,
|
|
24
|
+
setActiveRegistry,
|
|
25
|
+
toolSpecOf
|
|
26
26
|
};
|
package/dist/manifest/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadApp,
|
|
3
3
|
manifest
|
|
4
|
-
} from "../
|
|
5
|
-
import"../
|
|
4
|
+
} from "../chunk-J5AVXTHA.js";
|
|
5
|
+
import "../chunk-LWKQDDLZ.js";
|
|
6
6
|
import {
|
|
7
7
|
JsonSchemaObject,
|
|
8
8
|
PublishError
|
|
9
|
-
} from "../
|
|
9
|
+
} from "../chunk-B7NEUIJL.js";
|
|
10
|
+
import "../chunk-RZA7AXBK.js";
|
|
10
11
|
export {
|
|
11
|
-
|
|
12
|
-
loadApp,
|
|
12
|
+
JsonSchemaObject,
|
|
13
13
|
PublishError,
|
|
14
|
-
|
|
14
|
+
loadApp,
|
|
15
|
+
manifest
|
|
15
16
|
};
|
package/dist/runner/index.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadApp
|
|
3
|
-
} from "../
|
|
4
|
-
import"../
|
|
5
|
-
import"../
|
|
3
|
+
} from "../chunk-J5AVXTHA.js";
|
|
4
|
+
import "../chunk-LWKQDDLZ.js";
|
|
5
|
+
import "../chunk-B7NEUIJL.js";
|
|
6
6
|
import {
|
|
7
7
|
CallError
|
|
8
|
-
} from "../
|
|
8
|
+
} from "../chunk-NFLNA2SC.js";
|
|
9
|
+
import "../chunk-RZA7AXBK.js";
|
|
9
10
|
|
|
10
11
|
// src/runner/index.ts
|
|
11
12
|
import { Effect } from "effect";
|
|
12
|
-
var hasStandardValidate = (input) =>
|
|
13
|
+
var hasStandardValidate = (input) => "~standard" in input && typeof input["~standard"].validate === "function";
|
|
13
14
|
var parseInput = (name, spec, input) => {
|
|
14
15
|
if (!hasStandardValidate(spec.input))
|
|
15
|
-
return Effect.fail(
|
|
16
|
+
return Effect.fail(
|
|
17
|
+
new CallError({ target: name, reason: "input schema does not implement Standard Schema validate" })
|
|
18
|
+
);
|
|
16
19
|
const standard = spec.input["~standard"];
|
|
17
20
|
return Effect.tryPromise({
|
|
18
21
|
try: () => Promise.resolve(standard.validate(input)),
|
|
@@ -20,32 +23,49 @@ var parseInput = (name, spec, input) => {
|
|
|
20
23
|
target: name,
|
|
21
24
|
reason: `input validation threw (${error instanceof Error ? error.message : String(error)})`
|
|
22
25
|
})
|
|
23
|
-
}).pipe(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
}).pipe(
|
|
27
|
+
Effect.flatMap(
|
|
28
|
+
(result) => result.issues === void 0 ? Effect.succeed(result.value) : Effect.fail(
|
|
29
|
+
new CallError({
|
|
30
|
+
target: name,
|
|
31
|
+
reason: `invalid input (${result.issues.map((issue) => issue.message).join("; ")})`
|
|
32
|
+
})
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
);
|
|
27
36
|
};
|
|
28
37
|
var localRunner = (appDir) => {
|
|
29
38
|
let cachedLoad;
|
|
30
39
|
const loaded = () => Effect.suspend(() => {
|
|
31
|
-
if (cachedLoad !==
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
if (cachedLoad !== void 0) return cachedLoad;
|
|
41
|
+
return Effect.flatMap(
|
|
42
|
+
Effect.cached(loadApp(appDir)),
|
|
43
|
+
(effect) => Effect.sync(() => {
|
|
44
|
+
cachedLoad = effect;
|
|
45
|
+
return effect;
|
|
46
|
+
}).pipe(Effect.flatten)
|
|
47
|
+
);
|
|
37
48
|
});
|
|
38
|
-
const loadedFor = (target) => loaded().pipe(
|
|
49
|
+
const loadedFor = (target) => loaded().pipe(
|
|
50
|
+
Effect.mapError(
|
|
51
|
+
(error) => new CallError({ target, reason: `app failed to load (${error.message})` })
|
|
52
|
+
)
|
|
53
|
+
);
|
|
39
54
|
return {
|
|
40
55
|
manifest: () => Effect.map(loaded(), ({ result }) => result),
|
|
41
56
|
call: (name, input, ctx) => Effect.gen(function* () {
|
|
42
57
|
const { registry } = yield* loadedFor(name);
|
|
43
58
|
const registered = registry.tools.get(name);
|
|
44
|
-
if (registered ===
|
|
59
|
+
if (registered === void 0)
|
|
45
60
|
return yield* new CallError({ target: name, reason: "unknown tool" });
|
|
46
61
|
const parsed = yield* parseInput(name, registered.spec, input);
|
|
47
62
|
return yield* Effect.tryPromise({
|
|
48
|
-
|
|
63
|
+
// SAFETY: ToolSpec intentionally erases authored handler inputs to keep
|
|
64
|
+
// authoring generic-free; validation and ToolCtx establish this call boundary.
|
|
65
|
+
try: () => registered.spec.handler(
|
|
66
|
+
parsed,
|
|
67
|
+
ctx
|
|
68
|
+
),
|
|
49
69
|
catch: (error) => new CallError({
|
|
50
70
|
target: name,
|
|
51
71
|
reason: `handler failed (${error instanceof Error ? error.message : String(error)})`
|
|
@@ -56,6 +76,6 @@ var localRunner = (appDir) => {
|
|
|
56
76
|
};
|
|
57
77
|
};
|
|
58
78
|
export {
|
|
59
|
-
|
|
60
|
-
|
|
79
|
+
CallError,
|
|
80
|
+
localRunner
|
|
61
81
|
};
|
package/dist/runner/protocol.js
CHANGED
package/dist/workerd/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apps",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
4
|
"description": "Author executor apps: declare services, auth, and tools in plain TypeScript; the executor platform runs, validates, and serves them.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/node": "22",
|
|
58
|
+
"esbuild": "0.25",
|
|
58
59
|
"typescript": "5.9.3"
|
|
59
60
|
}
|
|
60
|
-
}
|
|
61
|
+
}
|
package/dist/index-wfngzvv2.js
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
__enterFile,
|
|
3
|
-
__leaveFile,
|
|
4
|
-
newRegistry,
|
|
5
|
-
setActiveRegistry
|
|
6
|
-
} from "./index-8wmxmfhc.js";
|
|
7
|
-
import {
|
|
8
|
-
JsonSchemaObject,
|
|
9
|
-
PublishError
|
|
10
|
-
} from "./index-mcjwntr2.js";
|
|
11
|
-
|
|
12
|
-
// src/manifest/index.ts
|
|
13
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
14
|
-
import { Crypto, Effect, FileSystem, Schema, Semaphore } from "effect";
|
|
15
|
-
|
|
16
|
-
// src/manifest/collect.ts
|
|
17
|
-
var collectRegistry = (registry, compileInputSchema) => {
|
|
18
|
-
const requirementKey = Symbol.for("executor/apps/requirement");
|
|
19
|
-
const problems = registry.problems.map((problem) => problem.message);
|
|
20
|
-
const app = registry.app;
|
|
21
|
-
const requirementSpecOf = (value) => typeof value === "object" && value !== null && (requirementKey in value) ? value[requirementKey] : undefined;
|
|
22
|
-
const canonicalJson = (value) => {
|
|
23
|
-
const sortKeys = (node) => {
|
|
24
|
-
if (Array.isArray(node))
|
|
25
|
-
return node.map(sortKeys);
|
|
26
|
-
if (typeof node === "object" && node !== null)
|
|
27
|
-
return Object.fromEntries(Object.entries(node).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, sortKeys(child)]));
|
|
28
|
-
return node;
|
|
29
|
-
};
|
|
30
|
-
return JSON.stringify(sortKeys(value));
|
|
31
|
-
};
|
|
32
|
-
const claims = new Map;
|
|
33
|
-
for (const [origin, registered] of registry.services) {
|
|
34
|
-
if (registered.file !== "app.ts")
|
|
35
|
-
continue;
|
|
36
|
-
const { decl } = registered;
|
|
37
|
-
claims.set(origin, {
|
|
38
|
-
origin,
|
|
39
|
-
reach: decl.reach ?? [],
|
|
40
|
-
params: decl.params ?? {},
|
|
41
|
-
auth: decl.auth ?? {}
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
const collectRequirement = (role, requirement, where) => {
|
|
45
|
-
const spec = requirementSpecOf(requirement);
|
|
46
|
-
if (spec === undefined) {
|
|
47
|
-
problems.push(`${where}: requirement '${role}' is not a service/requirement value`);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const origin = spec.service.origin;
|
|
51
|
-
if (!claims.has(origin))
|
|
52
|
-
problems.push(`${where}: requirement '${role}' names ${origin}, which app.ts does not declare`);
|
|
53
|
-
if (spec.grant !== undefined && !(spec.grant in (spec.service.auth ?? {})))
|
|
54
|
-
problems.push(`${where}: requirement '${role}' pins grant '${spec.grant}', not a auth method on ${origin}`);
|
|
55
|
-
return {
|
|
56
|
-
role,
|
|
57
|
-
service: origin,
|
|
58
|
-
arity: spec.arity,
|
|
59
|
-
...spec.grant === undefined ? {} : { grant: spec.grant },
|
|
60
|
-
...spec.description === undefined ? {} : { description: spec.description }
|
|
61
|
-
};
|
|
62
|
-
};
|
|
63
|
-
if (app === undefined)
|
|
64
|
-
problems.push("app.ts must call defineApp({...})");
|
|
65
|
-
else if (typeof app.description !== "string" || app.description.trim().length === 0)
|
|
66
|
-
problems.push("app.ts: app description must not be empty");
|
|
67
|
-
for (const [file, summary] of registry.toolFiles) {
|
|
68
|
-
if (summary.definitions > 1 && summary.hasUnnamedTool)
|
|
69
|
-
problems.push(`${file}: multi-tool files must name every tool`);
|
|
70
|
-
}
|
|
71
|
-
const tools = [];
|
|
72
|
-
if (app !== undefined) {
|
|
73
|
-
for (const [name, registered] of registry.tools) {
|
|
74
|
-
const { spec, file } = registered;
|
|
75
|
-
const address = `${app.key}/${name}`;
|
|
76
|
-
if (typeof spec.description !== "string" || spec.description.trim().length === 0)
|
|
77
|
-
problems.push(`${file}: tool '${name}' description must not be empty`);
|
|
78
|
-
const requires = Object.entries(spec.requirements ?? {}).flatMap(([role, requirement]) => {
|
|
79
|
-
const collected = collectRequirement(role, requirement, address);
|
|
80
|
-
return collected === undefined ? [] : [collected];
|
|
81
|
-
}).sort((left, right) => left.role.localeCompare(right.role));
|
|
82
|
-
let compiled;
|
|
83
|
-
try {
|
|
84
|
-
compiled = compileInputSchema(spec.input, address);
|
|
85
|
-
} catch (error) {
|
|
86
|
-
compiled = {
|
|
87
|
-
problem: `${address}: input schema could not compile to JSON schema (${error instanceof Error ? error.message : String(error)})`
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
if ("problem" in compiled) {
|
|
91
|
-
problems.push(compiled.problem);
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
tools.push({
|
|
95
|
-
address,
|
|
96
|
-
name,
|
|
97
|
-
description: spec.description,
|
|
98
|
-
inputSchema: compiled.value,
|
|
99
|
-
annotations: spec.annotations ?? {},
|
|
100
|
-
requires,
|
|
101
|
-
file
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (problems.length > 0 || app === undefined)
|
|
106
|
-
return { problems };
|
|
107
|
-
const services = [...claims.values()].sort((left, right) => left.origin.localeCompare(right.origin));
|
|
108
|
-
const sortedTools = [...tools].sort((left, right) => left.address.localeCompare(right.address));
|
|
109
|
-
const appResult = { key: app.key, label: app.label, description: app.description };
|
|
110
|
-
const manifest = { app: appResult, services, tools: sortedTools };
|
|
111
|
-
return {
|
|
112
|
-
problems: [],
|
|
113
|
-
manifest,
|
|
114
|
-
canonical: canonicalJson(manifest)
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// src/manifest/index.ts
|
|
119
|
-
var hasStandardJsonSchema = (input) => ("~standard" in input) && typeof input["~standard"].jsonSchema?.input === "function";
|
|
120
|
-
var compileInputSchema = (input, where) => {
|
|
121
|
-
if ("~standard" in input) {
|
|
122
|
-
if (!hasStandardJsonSchema(input))
|
|
123
|
-
return {
|
|
124
|
-
problem: `${where}: input is a Standard Schema without the jsonSchema extension — use a library that implements it (zod 4+, effect Schema) or pass raw JSON schema`
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
try {
|
|
128
|
-
const compiled = strictify(hasStandardJsonSchema(input) ? input["~standard"].jsonSchema.input({ target: "draft-2020-12" }) : structuredClone(input));
|
|
129
|
-
return { value: Schema.decodeUnknownSync(JsonSchemaObject)(compiled) };
|
|
130
|
-
} catch (error) {
|
|
131
|
-
return { problem: schemaPublishProblem(where, error) };
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
var schemaPublishProblem = (where, error) => `${where}: input schema could not compile to JSON schema (${error instanceof Error ? error.message : String(error)})`;
|
|
135
|
-
var strictify = (node) => {
|
|
136
|
-
if (typeof node !== "object" || node === null)
|
|
137
|
-
return node;
|
|
138
|
-
if (Array.isArray(node))
|
|
139
|
-
return node.map(strictify);
|
|
140
|
-
const object = { ...node };
|
|
141
|
-
if (object.type === "object" && object.additionalProperties === undefined)
|
|
142
|
-
object.additionalProperties = false;
|
|
143
|
-
for (const key of ["items", "anyOf", "oneOf", "allOf"])
|
|
144
|
-
if (key in object)
|
|
145
|
-
object[key] = strictify(object[key]);
|
|
146
|
-
for (const key of ["properties", "$defs"])
|
|
147
|
-
if (object[key] !== undefined && typeof object[key] === "object" && object[key] !== null)
|
|
148
|
-
object[key] = Object.fromEntries(Object.entries(object[key]).map(([name, value]) => [
|
|
149
|
-
name,
|
|
150
|
-
strictify(value)
|
|
151
|
-
]));
|
|
152
|
-
return object;
|
|
153
|
-
};
|
|
154
|
-
var digestOf = (content) => Effect.gen(function* () {
|
|
155
|
-
const crypto = yield* Crypto.Crypto;
|
|
156
|
-
const bytes = yield* Effect.orDie(crypto.digest("SHA-256", new TextEncoder().encode(content)));
|
|
157
|
-
return Array.from(bytes.slice(0, 8), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
158
|
-
});
|
|
159
|
-
var loadPermit = Semaphore.makeUnsafe(1);
|
|
160
|
-
var runNonce = 0;
|
|
161
|
-
var baseAppImports = new Set;
|
|
162
|
-
var moduleFailure = (where, error) => new PublishError({
|
|
163
|
-
problems: [
|
|
164
|
-
`${where}: failed to execute (${error instanceof Error ? error.message : String(error)})`
|
|
165
|
-
]
|
|
166
|
-
});
|
|
167
|
-
var canonicalModuleUrl = (path, where) => Effect.gen(function* () {
|
|
168
|
-
const fs = yield* FileSystem.FileSystem;
|
|
169
|
-
const realPath = yield* fs.realPath(path).pipe(Effect.mapError((error) => moduleFailure(where, error)));
|
|
170
|
-
return pathToFileURL(realPath);
|
|
171
|
-
});
|
|
172
|
-
var importModule = (specifier, where) => Effect.tryPromise({
|
|
173
|
-
try: () => import(specifier).then(() => {
|
|
174
|
-
return;
|
|
175
|
-
}),
|
|
176
|
-
catch: (error) => moduleFailure(where, error)
|
|
177
|
-
});
|
|
178
|
-
var recordModuleProblems = (registry, effect) => effect.pipe(Effect.catchTag("PublishError", (error) => Effect.sync(() => {
|
|
179
|
-
registry.problems.push(...error.problems.map((message) => ({ message })));
|
|
180
|
-
})));
|
|
181
|
-
var executeModules = (appDir, files) => loadPermit.withPermit(Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
182
|
-
const registry = newRegistry();
|
|
183
|
-
const previous = yield* Effect.sync(() => setActiveRegistry(registry));
|
|
184
|
-
const nonce = yield* Effect.sync(() => ++runNonce);
|
|
185
|
-
const execute = Effect.gen(function* () {
|
|
186
|
-
__enterFile("app.ts");
|
|
187
|
-
yield* recordModuleProblems(registry, Effect.gen(function* () {
|
|
188
|
-
const appUrl = yield* canonicalModuleUrl(`${appDir}/app.ts`, "app.ts");
|
|
189
|
-
const baseUrl = appUrl.href;
|
|
190
|
-
const specifier = baseAppImports.has(baseUrl) ? `${fileURLToPath(appUrl)}?m=${nonce}` : baseUrl;
|
|
191
|
-
yield* importModule(specifier, "app.ts");
|
|
192
|
-
yield* Effect.sync(() => baseAppImports.add(baseUrl));
|
|
193
|
-
}));
|
|
194
|
-
for (const file of files) {
|
|
195
|
-
const where = `tools/${file}`;
|
|
196
|
-
__enterFile(where);
|
|
197
|
-
yield* recordModuleProblems(registry, Effect.gen(function* () {
|
|
198
|
-
const url = yield* canonicalModuleUrl(`${appDir}/tools/${file}`, where);
|
|
199
|
-
yield* importModule(`${fileURLToPath(url)}?m=${nonce}`, where);
|
|
200
|
-
}));
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
yield* restore(execute).pipe(Effect.ensuring(Effect.sync(() => {
|
|
204
|
-
__leaveFile();
|
|
205
|
-
setActiveRegistry(previous);
|
|
206
|
-
})));
|
|
207
|
-
return registry;
|
|
208
|
-
})));
|
|
209
|
-
var loadApp = (appDir) => Effect.gen(function* () {
|
|
210
|
-
const fs = yield* FileSystem.FileSystem;
|
|
211
|
-
const files = (yield* fs.readDirectory(`${appDir}/tools`).pipe(Effect.mapError((error) => new PublishError({ problems: [`tools/: ${error.message}`] })))).filter((file) => file.endsWith(".ts")).sort();
|
|
212
|
-
const registry = yield* executeModules(appDir, files);
|
|
213
|
-
const collected = collectRegistry(registry, compileInputSchema);
|
|
214
|
-
if (collected.problems.length > 0)
|
|
215
|
-
return yield* new PublishError({ problems: collected.problems });
|
|
216
|
-
if (!("manifest" in collected))
|
|
217
|
-
return yield* Effect.die("validated registry projection unexpectedly missing");
|
|
218
|
-
return {
|
|
219
|
-
registry,
|
|
220
|
-
result: {
|
|
221
|
-
digest: `snap:${yield* digestOf(collected.canonical)}`,
|
|
222
|
-
...collected.manifest
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
});
|
|
226
|
-
var manifest = (appDir) => Effect.map(loadApp(appDir), ({ result }) => result);
|
|
227
|
-
|
|
228
|
-
export { loadApp, manifest };
|