apps 1.0.0-beta.1 → 1.0.0-beta.2
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/chunk-646MS7DD.js +99 -0
- package/dist/chunk-OGCPVWAZ.js +191 -0
- package/dist/index.d.ts +0 -8
- package/dist/manifest/index.d.ts +8 -6
- package/dist/manifest/index.js +2 -2
- package/dist/runner/index.js +18 -47
- package/dist/runner/protocol.d.ts +6 -14
- package/dist/runtime/index.d.ts +46 -0
- package/dist/runtime/index.js +10 -0
- package/dist/workerd/index.d.ts +7 -2
- package/dist/workerd/index.js +7 -3
- package/package.json +6 -2
- package/src/index.ts +0 -8
- package/src/manifest/index.ts +89 -157
- package/src/runner/index.ts +25 -85
- package/src/runner/protocol.ts +6 -18
- package/src/runtime/index.ts +363 -0
- package/src/workerd/index.ts +15 -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/src/manifest/index.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
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
8
|
|
|
7
9
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
-
import {
|
|
10
|
+
import { Effect, FileSystem, MutableRef, Semaphore } from "effect";
|
|
9
11
|
import {
|
|
10
12
|
__enterFile,
|
|
11
13
|
__leaveFile,
|
|
@@ -13,118 +15,51 @@ import {
|
|
|
13
15
|
setActiveRegistry,
|
|
14
16
|
type Registry,
|
|
15
17
|
} from "../authoring/index";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
JsonSchemaObject,
|
|
19
|
-
PublishError,
|
|
20
|
-
type AppManifestResult,
|
|
21
|
-
type JsonSchemaObject as JsonSchemaObjectType,
|
|
22
|
-
} from "./contract";
|
|
18
|
+
import { manifestOf } from "../runtime/index";
|
|
19
|
+
import { PublishError, type AppManifestResult } from "./contract";
|
|
23
20
|
|
|
24
21
|
export * from "./contract";
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
readonly jsonSchema?: {
|
|
29
|
-
readonly input: (options: { readonly target: string }) => unknown;
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const hasStandardJsonSchema = (input: object): input is StandardJsonSchemaCapable =>
|
|
35
|
-
"~standard" in input &&
|
|
36
|
-
typeof (input as StandardJsonSchemaCapable)["~standard"].jsonSchema?.input === "function";
|
|
37
|
-
|
|
38
|
-
const compileInputSchema = (
|
|
39
|
-
input: object,
|
|
40
|
-
where: string,
|
|
41
|
-
): InputSchemaCompilation => {
|
|
42
|
-
if ("~standard" in input) {
|
|
43
|
-
if (!hasStandardJsonSchema(input))
|
|
44
|
-
return {
|
|
45
|
-
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`,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const compiled = strictify(
|
|
51
|
-
hasStandardJsonSchema(input)
|
|
52
|
-
? input["~standard"].jsonSchema!.input({ target: "draft-2020-12" })
|
|
53
|
-
: structuredClone(input),
|
|
54
|
-
);
|
|
55
|
-
return { value: Schema.decodeUnknownSync(JsonSchemaObject)(compiled) as JsonSchemaObjectType };
|
|
56
|
-
} catch (error) {
|
|
57
|
-
return { problem: schemaPublishProblem(where, error) };
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const schemaPublishProblem = (where: string, error: unknown): string =>
|
|
62
|
-
`${where}: input schema could not compile to JSON schema (${
|
|
63
|
-
error instanceof Error ? error.message : String(error)
|
|
64
|
-
})`;
|
|
65
|
-
|
|
66
|
-
const strictify = (node: unknown): unknown => {
|
|
67
|
-
if (typeof node !== "object" || node === null) return node;
|
|
68
|
-
if (Array.isArray(node)) return node.map(strictify);
|
|
69
|
-
const object = { ...(node as Record<string, unknown>) };
|
|
70
|
-
if (object.type === "object" && object.additionalProperties === undefined)
|
|
71
|
-
object.additionalProperties = false;
|
|
72
|
-
for (const key of ["items", "anyOf", "oneOf", "allOf"])
|
|
73
|
-
if (key in object) object[key] = strictify(object[key]);
|
|
74
|
-
for (const key of ["properties", "$defs"])
|
|
75
|
-
if (object[key] !== undefined && typeof object[key] === "object" && object[key] !== null)
|
|
76
|
-
object[key] = Object.fromEntries(
|
|
77
|
-
Object.entries(object[key] as Record<string, unknown>).map(([name, value]) => [
|
|
78
|
-
name,
|
|
79
|
-
strictify(value),
|
|
80
|
-
]),
|
|
81
|
-
);
|
|
82
|
-
return object;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const digestOf = (content: string): Effect.Effect<string, never, Crypto.Crypto> =>
|
|
86
|
-
Effect.gen(function* () {
|
|
87
|
-
const crypto = yield* Crypto.Crypto;
|
|
88
|
-
const bytes = yield* Effect.orDie(
|
|
89
|
-
crypto.digest("SHA-256", new TextEncoder().encode(content)),
|
|
90
|
-
);
|
|
91
|
-
return Array.from(bytes.slice(0, 8), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
92
|
-
});
|
|
23
|
+
const moduleProblem = (where: string, error: unknown): string =>
|
|
24
|
+
`${where}: failed to execute (${error instanceof Error ? error.message : String(error)})`;
|
|
93
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Module execution is process-global twice over: the authoring
|
|
28
|
+
* registry is a singleton the imported modules write into, and the
|
|
29
|
+
* ESM module cache is shared. One permit serializes loads; the nonce
|
|
30
|
+
* and first-import memo make re-imports observable across runs.
|
|
31
|
+
* Module-scoped by necessity — this state IS process state.
|
|
32
|
+
*/
|
|
94
33
|
const loadPermit = Semaphore.makeUnsafe(1);
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
const moduleFailure = (where: string, error: unknown): PublishError =>
|
|
99
|
-
new PublishError({
|
|
100
|
-
problems: [
|
|
101
|
-
`${where}: failed to execute (${error instanceof Error ? error.message : String(error)})`,
|
|
102
|
-
],
|
|
103
|
-
});
|
|
34
|
+
const runNonce = MutableRef.make(0);
|
|
35
|
+
const importedAppUrls = MutableRef.make<ReadonlySet<string>>(new Set());
|
|
104
36
|
|
|
105
37
|
const canonicalModuleUrl = (
|
|
106
38
|
path: string,
|
|
107
39
|
where: string,
|
|
108
40
|
): Effect.Effect<URL, PublishError, FileSystem.FileSystem> =>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return pathToFileURL(realPath);
|
|
115
|
-
});
|
|
41
|
+
FileSystem.FileSystem.pipe(
|
|
42
|
+
Effect.flatMap((fs) => fs.realPath(path)),
|
|
43
|
+
Effect.map(pathToFileURL),
|
|
44
|
+
Effect.mapError((error) => new PublishError({ problems: [moduleProblem(where, error)] })),
|
|
45
|
+
);
|
|
116
46
|
|
|
117
47
|
const importModule = (specifier: string, where: string): Effect.Effect<void, PublishError> =>
|
|
118
48
|
Effect.tryPromise({
|
|
119
|
-
try: () => import(specifier)
|
|
120
|
-
catch: (error) =>
|
|
121
|
-
});
|
|
49
|
+
try: () => import(specifier),
|
|
50
|
+
catch: (error) => new PublishError({ problems: [moduleProblem(where, error)] }),
|
|
51
|
+
}).pipe(Effect.asVoid);
|
|
122
52
|
|
|
123
|
-
|
|
53
|
+
/** Import one authored module, converting failure into registry problems. */
|
|
54
|
+
const executeModule = (
|
|
124
55
|
registry: Registry,
|
|
125
|
-
|
|
56
|
+
where: string,
|
|
57
|
+
load: Effect.Effect<void, PublishError, FileSystem.FileSystem>,
|
|
126
58
|
): Effect.Effect<void, never, FileSystem.FileSystem> =>
|
|
127
|
-
|
|
59
|
+
Effect.suspend(() => {
|
|
60
|
+
__enterFile(where);
|
|
61
|
+
return load;
|
|
62
|
+
}).pipe(
|
|
128
63
|
Effect.catchTag("PublishError", (error) =>
|
|
129
64
|
Effect.sync(() => {
|
|
130
65
|
registry.problems.push(...error.problems.map((message) => ({ message })));
|
|
@@ -132,58 +67,70 @@ const recordModuleProblems = (
|
|
|
132
67
|
),
|
|
133
68
|
);
|
|
134
69
|
|
|
70
|
+
/**
|
|
71
|
+
* The app entry loads through its plain canonical URL the first time
|
|
72
|
+
* so relative imports resolve normally, and through a nonce query on
|
|
73
|
+
* every later run so edits are observed instead of served stale from
|
|
74
|
+
* the module cache.
|
|
75
|
+
*/
|
|
76
|
+
const appEntrySpecifier = (appUrl: URL, nonce: number): string => {
|
|
77
|
+
const seen = MutableRef.get(importedAppUrls);
|
|
78
|
+
if (!seen.has(appUrl.href)) {
|
|
79
|
+
MutableRef.set(importedAppUrls, new Set([...seen, appUrl.href]));
|
|
80
|
+
return appUrl.href;
|
|
81
|
+
}
|
|
82
|
+
return `${fileURLToPath(appUrl)}?m=${nonce}`;
|
|
83
|
+
};
|
|
84
|
+
|
|
135
85
|
const executeModules = (
|
|
136
86
|
appDir: string,
|
|
137
87
|
files: readonly string[],
|
|
138
88
|
): Effect.Effect<Registry, never, FileSystem.FileSystem> =>
|
|
139
89
|
loadPermit.withPermit(
|
|
140
|
-
Effect.
|
|
141
|
-
Effect.
|
|
90
|
+
Effect.acquireUseRelease(
|
|
91
|
+
Effect.sync(() => {
|
|
142
92
|
const registry = newRegistry();
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
yield*
|
|
93
|
+
return { registry, previous: setActiveRegistry(registry) };
|
|
94
|
+
}),
|
|
95
|
+
({ registry }) =>
|
|
96
|
+
Effect.gen(function* () {
|
|
97
|
+
const nonce = MutableRef.incrementAndGet(runNonce);
|
|
98
|
+
yield* executeModule(
|
|
149
99
|
registry,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
? `${fileURLToPath(appUrl)}?m=${nonce}`
|
|
155
|
-
: baseUrl;
|
|
156
|
-
yield* importModule(specifier, "app.ts");
|
|
157
|
-
yield* Effect.sync(() => baseAppImports.add(baseUrl));
|
|
158
|
-
}),
|
|
100
|
+
"app.ts",
|
|
101
|
+
canonicalModuleUrl(`${appDir}/app.ts`, "app.ts").pipe(
|
|
102
|
+
Effect.flatMap((appUrl) => importModule(appEntrySpecifier(appUrl, nonce), "app.ts")),
|
|
103
|
+
),
|
|
159
104
|
);
|
|
160
|
-
|
|
161
105
|
for (const file of files) {
|
|
162
106
|
const where = `tools/${file}`;
|
|
163
|
-
|
|
164
|
-
yield* recordModuleProblems(
|
|
107
|
+
yield* executeModule(
|
|
165
108
|
registry,
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
109
|
+
where,
|
|
110
|
+
canonicalModuleUrl(`${appDir}/tools/${file}`, where).pipe(
|
|
111
|
+
Effect.flatMap((url) => importModule(`${fileURLToPath(url)}?m=${nonce}`, where)),
|
|
112
|
+
),
|
|
170
113
|
);
|
|
171
114
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}),
|
|
180
|
-
),
|
|
181
|
-
);
|
|
182
|
-
return registry;
|
|
183
|
-
}),
|
|
115
|
+
return registry;
|
|
116
|
+
}),
|
|
117
|
+
({ previous }) =>
|
|
118
|
+
Effect.sync(() => {
|
|
119
|
+
__leaveFile();
|
|
120
|
+
setActiveRegistry(previous);
|
|
121
|
+
}),
|
|
184
122
|
),
|
|
185
123
|
);
|
|
186
124
|
|
|
125
|
+
const toolFiles = (
|
|
126
|
+
appDir: string,
|
|
127
|
+
): Effect.Effect<readonly string[], PublishError, FileSystem.FileSystem> =>
|
|
128
|
+
FileSystem.FileSystem.pipe(
|
|
129
|
+
Effect.flatMap((fs) => fs.readDirectory(`${appDir}/tools`)),
|
|
130
|
+
Effect.mapError((error) => new PublishError({ problems: [`tools/: ${error.message}`] })),
|
|
131
|
+
Effect.map((entries) => entries.filter((file) => file.endsWith(".ts")).sort()),
|
|
132
|
+
);
|
|
133
|
+
|
|
187
134
|
/** A registry and its validated public projection. @internal */
|
|
188
135
|
export interface LoadedApp {
|
|
189
136
|
readonly registry: Registry;
|
|
@@ -193,32 +140,17 @@ export interface LoadedApp {
|
|
|
193
140
|
/** Loads an app once and returns both its live registry and manifest. @internal */
|
|
194
141
|
export const loadApp = (
|
|
195
142
|
appDir: string,
|
|
196
|
-
): Effect.Effect<LoadedApp, PublishError,
|
|
143
|
+
): Effect.Effect<LoadedApp, PublishError, FileSystem.FileSystem> =>
|
|
197
144
|
Effect.gen(function* () {
|
|
198
|
-
const
|
|
199
|
-
const files = (yield* fs.readDirectory(`${appDir}/tools`).pipe(
|
|
200
|
-
Effect.mapError((error) => new PublishError({ problems: [`tools/: ${error.message}`] })),
|
|
201
|
-
))
|
|
202
|
-
.filter((file) => file.endsWith(".ts"))
|
|
203
|
-
.sort();
|
|
145
|
+
const files = yield* toolFiles(appDir);
|
|
204
146
|
const registry = yield* executeModules(appDir, files);
|
|
205
|
-
const
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
if (!("manifest" in collected))
|
|
209
|
-
return yield* Effect.die("validated registry projection unexpectedly missing");
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
registry,
|
|
213
|
-
result: {
|
|
214
|
-
digest: `snap:${yield* digestOf(collected.canonical)}`,
|
|
215
|
-
...collected.manifest,
|
|
216
|
-
},
|
|
217
|
-
};
|
|
147
|
+
const outcome = yield* Effect.promise(() => manifestOf(registry));
|
|
148
|
+
if (!outcome.ok) return yield* new PublishError({ problems: outcome.problems });
|
|
149
|
+
return { registry, result: outcome.result };
|
|
218
150
|
});
|
|
219
151
|
|
|
220
152
|
/** Executes an app and returns its deterministic publish manifest. */
|
|
221
153
|
export const manifest = (
|
|
222
154
|
appDir: string,
|
|
223
|
-
): Effect.Effect<AppManifestResult, PublishError,
|
|
155
|
+
): Effect.Effect<AppManifestResult, PublishError, FileSystem.FileSystem> =>
|
|
224
156
|
Effect.map(loadApp(appDir), ({ result }) => result);
|
package/src/runner/index.ts
CHANGED
|
@@ -1,73 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { ToolSpec } from "../authoring/index";
|
|
1
|
+
import { Effect, FileSystem } from "effect";
|
|
3
2
|
import {
|
|
4
3
|
loadApp,
|
|
5
4
|
type LoadedApp,
|
|
6
5
|
type PublishError,
|
|
7
6
|
} from "../manifest/index";
|
|
8
|
-
import {
|
|
7
|
+
import { callTool, discoverTool, type CallOutcome } from "../runtime/index";
|
|
8
|
+
import { CallError, type AppRunner } from "./protocol";
|
|
9
9
|
|
|
10
10
|
export * from "./protocol";
|
|
11
11
|
|
|
12
|
-
interface StandardSchemaCapable {
|
|
13
|
-
readonly "~standard": {
|
|
14
|
-
readonly validate: (
|
|
15
|
-
value: unknown,
|
|
16
|
-
) =>
|
|
17
|
-
| { readonly value: unknown; readonly issues?: undefined }
|
|
18
|
-
| { readonly issues: ReadonlyArray<{ readonly message: string }> }
|
|
19
|
-
| Promise<
|
|
20
|
-
| { readonly value: unknown; readonly issues?: undefined }
|
|
21
|
-
| { readonly issues: ReadonlyArray<{ readonly message: string }> }
|
|
22
|
-
>;
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const hasStandardValidate = (input: object): input is StandardSchemaCapable =>
|
|
27
|
-
"~standard" in input &&
|
|
28
|
-
typeof (input as StandardSchemaCapable)["~standard"].validate === "function";
|
|
29
|
-
|
|
30
|
-
const parseInput = (name: string, spec: ToolSpec, input: unknown): Effect.Effect<unknown, CallError> => {
|
|
31
|
-
if (!hasStandardValidate(spec.input))
|
|
32
|
-
return Effect.fail(
|
|
33
|
-
new CallError({ target: name, reason: "input schema does not implement Standard Schema validate" }),
|
|
34
|
-
);
|
|
35
|
-
const standard = spec.input["~standard"];
|
|
36
|
-
|
|
37
|
-
return Effect.tryPromise({
|
|
38
|
-
try: () => Promise.resolve(standard.validate(input)),
|
|
39
|
-
catch: (error) =>
|
|
40
|
-
new CallError({
|
|
41
|
-
target: name,
|
|
42
|
-
reason: `input validation threw (${error instanceof Error ? error.message : String(error)})`,
|
|
43
|
-
}),
|
|
44
|
-
}).pipe(
|
|
45
|
-
Effect.flatMap((result) =>
|
|
46
|
-
result.issues === undefined
|
|
47
|
-
? Effect.succeed(result.value)
|
|
48
|
-
: Effect.fail(
|
|
49
|
-
new CallError({
|
|
50
|
-
target: name,
|
|
51
|
-
reason: `invalid input (${result.issues.map((issue) => issue.message).join("; ")})`,
|
|
52
|
-
}),
|
|
53
|
-
),
|
|
54
|
-
),
|
|
55
|
-
);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
12
|
/** Creates a permissive in-process runner for local development and tests. */
|
|
59
13
|
export const localRunner = (appDir: string): AppRunner => {
|
|
60
|
-
let cachedLoad:
|
|
61
|
-
LoadedApp,
|
|
62
|
-
|
|
63
|
-
Crypto.Crypto | FileSystem.FileSystem
|
|
64
|
-
> | undefined;
|
|
14
|
+
let cachedLoad:
|
|
15
|
+
| Effect.Effect<LoadedApp, PublishError, FileSystem.FileSystem>
|
|
16
|
+
| undefined;
|
|
65
17
|
|
|
66
|
-
const loaded = (): Effect.Effect<
|
|
67
|
-
LoadedApp,
|
|
68
|
-
PublishError,
|
|
69
|
-
Crypto.Crypto | FileSystem.FileSystem
|
|
70
|
-
> =>
|
|
18
|
+
const loaded = (): Effect.Effect<LoadedApp, PublishError, FileSystem.FileSystem> =>
|
|
71
19
|
Effect.suspend(() => {
|
|
72
20
|
if (cachedLoad !== undefined) return cachedLoad;
|
|
73
21
|
return Effect.flatMap(Effect.cached(loadApp(appDir)), (effect) =>
|
|
@@ -78,40 +26,32 @@ export const localRunner = (appDir: string): AppRunner => {
|
|
|
78
26
|
);
|
|
79
27
|
});
|
|
80
28
|
|
|
81
|
-
const
|
|
82
|
-
target: string,
|
|
83
|
-
): Effect.Effect<LoadedApp, CallError, Crypto.Crypto | FileSystem.FileSystem> =>
|
|
29
|
+
const registryFor = (target: string) =>
|
|
84
30
|
loaded().pipe(
|
|
31
|
+
Effect.map(({ registry }) => registry),
|
|
85
32
|
Effect.mapError(
|
|
86
33
|
(error) => new CallError({ target, reason: `app failed to load (${error.message})` }),
|
|
87
34
|
),
|
|
88
35
|
);
|
|
89
36
|
|
|
37
|
+
const settle = (target: string, outcome: CallOutcome): Effect.Effect<unknown, CallError> =>
|
|
38
|
+
outcome.ok
|
|
39
|
+
? Effect.succeed(outcome.value)
|
|
40
|
+
: Effect.fail(new CallError({ target, reason: outcome.reason }));
|
|
41
|
+
|
|
90
42
|
return {
|
|
91
43
|
manifest: () => Effect.map(loaded(), ({ result }) => result),
|
|
92
44
|
call: (name, input, ctx) =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
parsed,
|
|
105
|
-
ctx,
|
|
106
|
-
),
|
|
107
|
-
catch: (error) =>
|
|
108
|
-
new CallError({
|
|
109
|
-
target: name,
|
|
110
|
-
reason: `handler failed (${error instanceof Error ? error.message : String(error)})`,
|
|
111
|
-
}),
|
|
112
|
-
});
|
|
113
|
-
}),
|
|
114
|
-
discover: (name, _binding) =>
|
|
115
|
-
Effect.fail(new CallError({ target: name, reason: "no dynamic tools yet" })),
|
|
45
|
+
registryFor(name).pipe(
|
|
46
|
+
Effect.flatMap((registry) => Effect.promise(() => callTool(registry, name, input, ctx))),
|
|
47
|
+
Effect.flatMap((outcome) => settle(name, outcome)),
|
|
48
|
+
),
|
|
49
|
+
discover: (name, binding) =>
|
|
50
|
+
registryFor(name).pipe(
|
|
51
|
+
Effect.flatMap((registry) =>
|
|
52
|
+
Effect.promise(() => discoverTool(registry, name, binding)),
|
|
53
|
+
),
|
|
54
|
+
Effect.flatMap((outcome) => settle(name, outcome)),
|
|
55
|
+
),
|
|
116
56
|
};
|
|
117
57
|
};
|
package/src/runner/protocol.ts
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Data, type Effect, FileSystem } from "effect";
|
|
2
2
|
import type { AppManifestResult, PublishError } from "../manifest/contract";
|
|
3
|
+
import type { ToolCtx, ToolCtxEntry } from "../runtime/index";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
export interface ToolCtxEntry {
|
|
6
|
-
readonly grant: string;
|
|
7
|
-
readonly token?: string;
|
|
8
|
-
readonly secrets?: Readonly<Record<string, string>>;
|
|
9
|
-
fetch(path: string, init?: RequestInit): Promise<Response>;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/** Tool capabilities keyed by requirement role. */
|
|
13
|
-
export type ToolCtx = Readonly<Record<string, ToolCtxEntry>>;
|
|
5
|
+
export type { ToolCtx, ToolCtxEntry } from "../runtime/index";
|
|
14
6
|
|
|
15
7
|
/** A runner operation failure associated with its target. */
|
|
16
8
|
export class CallError extends Data.TaggedError("CallError")<{
|
|
@@ -24,18 +16,14 @@ export class CallError extends Data.TaggedError("CallError")<{
|
|
|
24
16
|
|
|
25
17
|
/** The platform protocol implemented by an app execution environment. */
|
|
26
18
|
export interface AppRunner {
|
|
27
|
-
manifest(): Effect.Effect<
|
|
28
|
-
AppManifestResult,
|
|
29
|
-
PublishError,
|
|
30
|
-
Crypto.Crypto | FileSystem.FileSystem
|
|
31
|
-
>;
|
|
19
|
+
manifest(): Effect.Effect<AppManifestResult, PublishError, FileSystem.FileSystem>;
|
|
32
20
|
call(
|
|
33
21
|
name: string,
|
|
34
22
|
input: unknown,
|
|
35
23
|
ctx: ToolCtx,
|
|
36
|
-
): Effect.Effect<unknown, CallError,
|
|
24
|
+
): Effect.Effect<unknown, CallError, FileSystem.FileSystem>;
|
|
37
25
|
discover(
|
|
38
26
|
name: string,
|
|
39
27
|
binding: unknown,
|
|
40
|
-
): Effect.Effect<unknown, CallError,
|
|
28
|
+
): Effect.Effect<unknown, CallError, FileSystem.FileSystem>;
|
|
41
29
|
}
|