apps 0.8.3 → 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/README.md +51 -166
- package/dist/authoring/index.d.ts +205 -0
- package/dist/authoring/index.js +26 -0
- package/dist/chunk-B7NEUIJL.js +14 -0
- package/dist/chunk-J5AVXTHA.js +154 -0
- package/dist/chunk-LWKQDDLZ.js +98 -0
- package/dist/chunk-NFLNA2SC.js +11 -0
- package/dist/chunk-RZA7AXBK.js +107 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +26 -0
- package/dist/manifest/collect.d.ts +64 -0
- package/dist/manifest/contract.d.ts +55 -0
- package/dist/manifest/contract.js +8 -0
- package/dist/manifest/index.d.ts +18 -0
- package/dist/manifest/index.js +16 -0
- package/dist/runner/index.d.ts +4 -0
- package/dist/runner/index.js +81 -0
- package/dist/runner/protocol.d.ts +28 -0
- package/dist/runner/protocol.js +6 -0
- package/dist/workerd/index.d.ts +2 -0
- package/dist/workerd/index.js +6 -0
- package/package.json +51 -37
- package/src/authoring/index.ts +340 -0
- package/src/index.ts +9 -0
- package/src/manifest/collect.ts +198 -0
- package/src/manifest/contract.ts +55 -0
- package/src/manifest/index.ts +224 -0
- package/src/runner/index.ts +117 -0
- package/src/runner/protocol.ts +41 -0
- package/src/workerd/index.ts +2 -0
- package/.npmignore +0 -1
- package/bin/apps +0 -36
- package/bin/build +0 -14
- package/bin/doc +0 -16
- package/dist/app.js +0 -9773
- package/dist/app.min.js +0 -5
- package/dist/schema.html +0 -119
- package/dist/schema.md +0 -156
- package/index.html +0 -6
- package/index.js +0 -115
- package/lib/addons.js +0 -67
- package/schema.js +0 -100
- package/templates/app.mustache.html +0 -36
- package/templates/build.mustache.html +0 -13
- package/templates/schema.mustache.html +0 -20
- package/test/addons.js +0 -172
- package/test/cli.js +0 -53
- package/test/fixtures/app.json +0 -32
- package/test/index.js +0 -277
- package/test/mocha.opts +0 -2
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// src/authoring/index.ts
|
|
2
|
+
var secrets = (opts) => ({ kind: "secrets", ...opts });
|
|
3
|
+
var oauth = (opts) => ({ kind: "oauth", ...opts });
|
|
4
|
+
var RequirementKey = Symbol.for("executor/apps/requirement");
|
|
5
|
+
var DynamicKey = Symbol.for("executor/apps/dynamic");
|
|
6
|
+
var newRegistry = () => ({
|
|
7
|
+
app: void 0,
|
|
8
|
+
services: /* @__PURE__ */ new Map(),
|
|
9
|
+
tools: /* @__PURE__ */ new Map(),
|
|
10
|
+
dynamic: /* @__PURE__ */ new Map(),
|
|
11
|
+
problems: [],
|
|
12
|
+
toolFiles: /* @__PURE__ */ new Map()
|
|
13
|
+
});
|
|
14
|
+
var activeRegistry = newRegistry();
|
|
15
|
+
var currentFile;
|
|
16
|
+
var setActiveRegistry = (registry) => {
|
|
17
|
+
const previous = activeRegistry;
|
|
18
|
+
activeRegistry = registry;
|
|
19
|
+
return previous;
|
|
20
|
+
};
|
|
21
|
+
var __enterFile = (path) => {
|
|
22
|
+
currentFile = path;
|
|
23
|
+
};
|
|
24
|
+
var __leaveFile = () => {
|
|
25
|
+
currentFile = void 0;
|
|
26
|
+
};
|
|
27
|
+
var sourceFileFor = (claim) => {
|
|
28
|
+
if (currentFile !== void 0) return currentFile;
|
|
29
|
+
activeRegistry.problems.push({
|
|
30
|
+
message: `${claim} called outside an authored module import`
|
|
31
|
+
});
|
|
32
|
+
return "<unattributed>";
|
|
33
|
+
};
|
|
34
|
+
var makeTerminal = (spec) => ({
|
|
35
|
+
[RequirementKey]: spec,
|
|
36
|
+
describe: (text) => makeTerminal({ ...spec, description: text })
|
|
37
|
+
});
|
|
38
|
+
var service = (decl) => {
|
|
39
|
+
const file = sourceFileFor("service(...)");
|
|
40
|
+
const make = (spec) => ({
|
|
41
|
+
[RequirementKey]: spec,
|
|
42
|
+
decl,
|
|
43
|
+
optional: () => makeTerminal({ ...spec, arity: "optional" }),
|
|
44
|
+
array: () => makeTerminal({ ...spec, arity: "many" }),
|
|
45
|
+
grant: (name) => make({ ...spec, grant: name }),
|
|
46
|
+
describe: (text) => make({ ...spec, description: text })
|
|
47
|
+
});
|
|
48
|
+
const value = make({ service: decl, arity: "one" });
|
|
49
|
+
if (!activeRegistry.services.has(decl.origin))
|
|
50
|
+
activeRegistry.services.set(decl.origin, { decl, file });
|
|
51
|
+
return value;
|
|
52
|
+
};
|
|
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;
|
|
56
|
+
var defineApp = (manifest) => {
|
|
57
|
+
const file = sourceFileFor("defineApp(...)");
|
|
58
|
+
const value = { kind: "executor-app", ...manifest };
|
|
59
|
+
if (activeRegistry.app === void 0) activeRegistry.app = value;
|
|
60
|
+
else activeRegistry.problems.push({ message: `${file}: duplicate defineApp(...)` });
|
|
61
|
+
return value;
|
|
62
|
+
};
|
|
63
|
+
var ToolKey = Symbol.for("executor/apps/tool");
|
|
64
|
+
var defineTool = (spec) => {
|
|
65
|
+
const file = sourceFileFor("defineTool(...)");
|
|
66
|
+
const value = { [ToolKey]: spec };
|
|
67
|
+
const fileName = file.slice(file.lastIndexOf("/") + 1);
|
|
68
|
+
const stem = fileName.endsWith(".ts") ? fileName.slice(0, -3) : fileName;
|
|
69
|
+
const name = spec.name ?? stem;
|
|
70
|
+
const fileSummary = activeRegistry.toolFiles.get(file);
|
|
71
|
+
activeRegistry.toolFiles.set(file, {
|
|
72
|
+
definitions: (fileSummary?.definitions ?? 0) + 1,
|
|
73
|
+
hasUnnamedTool: fileSummary?.hasUnnamedTool === true || spec.name === void 0
|
|
74
|
+
});
|
|
75
|
+
const existing = activeRegistry.tools.get(name);
|
|
76
|
+
if (existing !== void 0) {
|
|
77
|
+
activeRegistry.problems.push({
|
|
78
|
+
message: `${file}: tool name '${name}' is already defined in ${existing.file}`
|
|
79
|
+
});
|
|
80
|
+
} else activeRegistry.tools.set(name, { spec, file });
|
|
81
|
+
return value;
|
|
82
|
+
};
|
|
83
|
+
var isTool = (value) => typeof value === "object" && value !== null && ToolKey in value;
|
|
84
|
+
var toolSpecOf = (value) => isTool(value) ? value[ToolKey] : void 0;
|
|
85
|
+
|
|
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
|
+
};
|
|
@@ -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.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
export * from "./authoring/index";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__enterFile,
|
|
3
|
+
__leaveFile,
|
|
4
|
+
defineApp,
|
|
5
|
+
defineTool,
|
|
6
|
+
newRegistry,
|
|
7
|
+
oauth,
|
|
8
|
+
requirementSpecOf,
|
|
9
|
+
secrets,
|
|
10
|
+
service,
|
|
11
|
+
setActiveRegistry,
|
|
12
|
+
toolSpecOf
|
|
13
|
+
} from "./chunk-LWKQDDLZ.js";
|
|
14
|
+
export {
|
|
15
|
+
__enterFile,
|
|
16
|
+
__leaveFile,
|
|
17
|
+
defineApp,
|
|
18
|
+
defineTool,
|
|
19
|
+
newRegistry,
|
|
20
|
+
oauth,
|
|
21
|
+
requirementSpecOf,
|
|
22
|
+
secrets,
|
|
23
|
+
service,
|
|
24
|
+
setActiveRegistry,
|
|
25
|
+
toolSpecOf
|
|
26
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
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 {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import type { AuthMethod, ParamSpec } from "../authoring/index";
|
|
3
|
+
/** A service claim projected from a registered declaration. */
|
|
4
|
+
export interface CollectedClaim {
|
|
5
|
+
readonly origin: string;
|
|
6
|
+
readonly reach: readonly string[];
|
|
7
|
+
readonly params: Readonly<Record<string, ParamSpec>>;
|
|
8
|
+
readonly auth: Readonly<Record<string, AuthMethod>>;
|
|
9
|
+
}
|
|
10
|
+
/** A tool requirement projected from an authoring value. */
|
|
11
|
+
export interface CollectedRequirement {
|
|
12
|
+
readonly role: string;
|
|
13
|
+
readonly service: string;
|
|
14
|
+
readonly arity: "one" | "optional" | "many";
|
|
15
|
+
readonly grant?: string;
|
|
16
|
+
readonly description?: string;
|
|
17
|
+
}
|
|
18
|
+
/** JSON object accepted for a persisted tool input schema. */
|
|
19
|
+
export declare const JsonSchemaObject: Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
20
|
+
export type JsonSchemaObject = typeof JsonSchemaObject.Type;
|
|
21
|
+
/** A validated, publishable tool row. */
|
|
22
|
+
export interface ManifestTool {
|
|
23
|
+
readonly address: string;
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly description: string;
|
|
26
|
+
readonly inputSchema: JsonSchemaObject;
|
|
27
|
+
readonly annotations: {
|
|
28
|
+
readonly readOnly?: boolean;
|
|
29
|
+
readonly destructive?: boolean;
|
|
30
|
+
readonly scopes?: readonly string[];
|
|
31
|
+
};
|
|
32
|
+
readonly requires: readonly CollectedRequirement[];
|
|
33
|
+
readonly file: string;
|
|
34
|
+
}
|
|
35
|
+
/** The deterministic public description of an executor app. */
|
|
36
|
+
export interface AppManifestResult {
|
|
37
|
+
readonly digest: string;
|
|
38
|
+
readonly app: {
|
|
39
|
+
readonly key: string;
|
|
40
|
+
readonly label: string;
|
|
41
|
+
readonly description: string;
|
|
42
|
+
};
|
|
43
|
+
readonly services: readonly CollectedClaim[];
|
|
44
|
+
readonly tools: readonly ManifestTool[];
|
|
45
|
+
}
|
|
46
|
+
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
|
+
readonly _tag: "PublishError";
|
|
48
|
+
} & Readonly<A>;
|
|
49
|
+
/** All publish-time authoring problems from one registry load. */
|
|
50
|
+
export declare class PublishError extends PublishError_base<{
|
|
51
|
+
readonly problems: readonly string[];
|
|
52
|
+
}> {
|
|
53
|
+
get message(): string;
|
|
54
|
+
}
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest: execute an authored app folder, read its registry, validate
|
|
3
|
+
* every claim, and emit the deterministic publish manifest. Defines run
|
|
4
|
+
* without user context; platform services begin at this boundary.
|
|
5
|
+
*/
|
|
6
|
+
import { Crypto, Effect, FileSystem } from "effect";
|
|
7
|
+
import { type Registry } from "../authoring/index";
|
|
8
|
+
import { PublishError, type AppManifestResult } from "./contract";
|
|
9
|
+
export * from "./contract";
|
|
10
|
+
/** A registry and its validated public projection. @internal */
|
|
11
|
+
export interface LoadedApp {
|
|
12
|
+
readonly registry: Registry;
|
|
13
|
+
readonly result: AppManifestResult;
|
|
14
|
+
}
|
|
15
|
+
/** Loads an app once and returns both its live registry and manifest. @internal */
|
|
16
|
+
export declare const loadApp: (appDir: string) => Effect.Effect<LoadedApp, PublishError, Crypto.Crypto | FileSystem.FileSystem>;
|
|
17
|
+
/** Executes an app and returns its deterministic publish manifest. */
|
|
18
|
+
export declare const manifest: (appDir: string) => Effect.Effect<AppManifestResult, PublishError, Crypto.Crypto | FileSystem.FileSystem>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadApp,
|
|
3
|
+
manifest
|
|
4
|
+
} from "../chunk-J5AVXTHA.js";
|
|
5
|
+
import "../chunk-LWKQDDLZ.js";
|
|
6
|
+
import {
|
|
7
|
+
JsonSchemaObject,
|
|
8
|
+
PublishError
|
|
9
|
+
} from "../chunk-B7NEUIJL.js";
|
|
10
|
+
import "../chunk-RZA7AXBK.js";
|
|
11
|
+
export {
|
|
12
|
+
JsonSchemaObject,
|
|
13
|
+
PublishError,
|
|
14
|
+
loadApp,
|
|
15
|
+
manifest
|
|
16
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadApp
|
|
3
|
+
} from "../chunk-J5AVXTHA.js";
|
|
4
|
+
import "../chunk-LWKQDDLZ.js";
|
|
5
|
+
import "../chunk-B7NEUIJL.js";
|
|
6
|
+
import {
|
|
7
|
+
CallError
|
|
8
|
+
} from "../chunk-NFLNA2SC.js";
|
|
9
|
+
import "../chunk-RZA7AXBK.js";
|
|
10
|
+
|
|
11
|
+
// src/runner/index.ts
|
|
12
|
+
import { Effect } from "effect";
|
|
13
|
+
var hasStandardValidate = (input) => "~standard" in input && typeof input["~standard"].validate === "function";
|
|
14
|
+
var parseInput = (name, spec, input) => {
|
|
15
|
+
if (!hasStandardValidate(spec.input))
|
|
16
|
+
return Effect.fail(
|
|
17
|
+
new CallError({ target: name, reason: "input schema does not implement Standard Schema validate" })
|
|
18
|
+
);
|
|
19
|
+
const standard = spec.input["~standard"];
|
|
20
|
+
return Effect.tryPromise({
|
|
21
|
+
try: () => Promise.resolve(standard.validate(input)),
|
|
22
|
+
catch: (error) => new CallError({
|
|
23
|
+
target: name,
|
|
24
|
+
reason: `input validation threw (${error instanceof Error ? error.message : String(error)})`
|
|
25
|
+
})
|
|
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
|
+
);
|
|
36
|
+
};
|
|
37
|
+
var localRunner = (appDir) => {
|
|
38
|
+
let cachedLoad;
|
|
39
|
+
const loaded = () => Effect.suspend(() => {
|
|
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
|
+
);
|
|
48
|
+
});
|
|
49
|
+
const loadedFor = (target) => loaded().pipe(
|
|
50
|
+
Effect.mapError(
|
|
51
|
+
(error) => new CallError({ target, reason: `app failed to load (${error.message})` })
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
return {
|
|
55
|
+
manifest: () => Effect.map(loaded(), ({ result }) => result),
|
|
56
|
+
call: (name, input, ctx) => Effect.gen(function* () {
|
|
57
|
+
const { registry } = yield* loadedFor(name);
|
|
58
|
+
const registered = registry.tools.get(name);
|
|
59
|
+
if (registered === void 0)
|
|
60
|
+
return yield* new CallError({ target: name, reason: "unknown tool" });
|
|
61
|
+
const parsed = yield* parseInput(name, registered.spec, input);
|
|
62
|
+
return yield* Effect.tryPromise({
|
|
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
|
+
),
|
|
69
|
+
catch: (error) => new CallError({
|
|
70
|
+
target: name,
|
|
71
|
+
reason: `handler failed (${error instanceof Error ? error.message : String(error)})`
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
}),
|
|
75
|
+
discover: (name, _binding) => Effect.fail(new CallError({ target: name, reason: "no dynamic tools yet" }))
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
export {
|
|
79
|
+
CallError,
|
|
80
|
+
localRunner
|
|
81
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Crypto, type Effect, FileSystem } from "effect";
|
|
2
|
+
import type { AppManifestResult, PublishError } from "../manifest/contract";
|
|
3
|
+
/** The capability exposed to one tool requirement role. */
|
|
4
|
+
export interface ToolCtxEntry {
|
|
5
|
+
readonly grant: string;
|
|
6
|
+
readonly token?: string;
|
|
7
|
+
readonly secrets?: Readonly<Record<string, string>>;
|
|
8
|
+
fetch(path: string, init?: RequestInit): Promise<Response>;
|
|
9
|
+
}
|
|
10
|
+
/** Tool capabilities keyed by requirement role. */
|
|
11
|
+
export type ToolCtx = Readonly<Record<string, ToolCtxEntry>>;
|
|
12
|
+
declare const CallError_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 & {
|
|
13
|
+
readonly _tag: "CallError";
|
|
14
|
+
} & Readonly<A>;
|
|
15
|
+
/** A runner operation failure associated with its target. */
|
|
16
|
+
export declare class CallError extends CallError_base<{
|
|
17
|
+
readonly target: string;
|
|
18
|
+
readonly reason: string;
|
|
19
|
+
}> {
|
|
20
|
+
get message(): string;
|
|
21
|
+
}
|
|
22
|
+
/** The platform protocol implemented by an app execution environment. */
|
|
23
|
+
export interface AppRunner {
|
|
24
|
+
manifest(): Effect.Effect<AppManifestResult, PublishError, Crypto.Crypto | FileSystem.FileSystem>;
|
|
25
|
+
call(name: string, input: unknown, ctx: ToolCtx): Effect.Effect<unknown, CallError, Crypto.Crypto | FileSystem.FileSystem>;
|
|
26
|
+
discover(name: string, binding: unknown): Effect.Effect<unknown, CallError, Crypto.Crypto | FileSystem.FileSystem>;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,47 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apps",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"app": "./bin/apps",
|
|
8
|
-
"apps": "./bin/apps"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"test": "mocha",
|
|
12
|
-
"build": "bin/build",
|
|
13
|
-
"doc": "bin/doc"
|
|
14
|
-
},
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "Author executor apps: declare services, auth, and tools in plain TypeScript; the executor platform runs, validates, and serves them.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
15
7
|
"repository": {
|
|
16
8
|
"type": "git",
|
|
17
|
-
"url": "https://github.com/
|
|
9
|
+
"url": "git+https://github.com/UsefulSoftwareCo/executor-rework.git",
|
|
10
|
+
"directory": "sdk/apps"
|
|
18
11
|
},
|
|
19
|
-
"
|
|
20
|
-
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src",
|
|
16
|
+
"README.md"
|
|
21
17
|
],
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./authoring": {
|
|
24
|
+
"types": "./dist/authoring/index.d.ts",
|
|
25
|
+
"default": "./dist/authoring/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./manifest": {
|
|
28
|
+
"types": "./dist/manifest/index.d.ts",
|
|
29
|
+
"default": "./dist/manifest/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./manifest/contract": {
|
|
32
|
+
"types": "./dist/manifest/contract.d.ts",
|
|
33
|
+
"default": "./dist/manifest/contract.js"
|
|
34
|
+
},
|
|
35
|
+
"./runner": {
|
|
36
|
+
"types": "./dist/runner/index.d.ts",
|
|
37
|
+
"default": "./dist/runner/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./runner/protocol": {
|
|
40
|
+
"types": "./dist/runner/protocol.d.ts",
|
|
41
|
+
"default": "./dist/runner/protocol.js"
|
|
42
|
+
},
|
|
43
|
+
"./workerd": {
|
|
44
|
+
"types": "./dist/workerd/index.d.ts",
|
|
45
|
+
"default": "./dist/workerd/index.js"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"effect": "4.0.0-beta.93"
|
|
26
50
|
},
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"superagent": "^0.17.0",
|
|
32
|
-
"browserify-hogan": "^0.1.2",
|
|
33
|
-
"hogan.js": "^2.0.0",
|
|
34
|
-
"brfs": "^1.0.2",
|
|
35
|
-
"async": "^0.7.0",
|
|
36
|
-
"heroku-client": "^1.2.0",
|
|
37
|
-
"commander": "^2.2.0",
|
|
38
|
-
"marked": "^0.3.2"
|
|
51
|
+
"peerDependenciesMeta": {
|
|
52
|
+
"effect": {
|
|
53
|
+
"optional": false
|
|
54
|
+
}
|
|
39
55
|
},
|
|
40
56
|
"devDependencies": {
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"cheerio": "^0.15.0",
|
|
45
|
-
"nixt": "^0.3.0"
|
|
57
|
+
"@types/node": "22",
|
|
58
|
+
"esbuild": "0.25",
|
|
59
|
+
"typescript": "5.9.3"
|
|
46
60
|
}
|
|
47
|
-
}
|
|
61
|
+
}
|