apps 1.0.0-beta.3 → 1.0.0-beta.4
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.d.ts +52 -23
- package/dist/authoring/index.js +1 -1
- package/dist/{chunk-MUFMJRXR.js → chunk-2DVKSPQH.js} +7 -3
- package/dist/{chunk-A3H32P2J.js → chunk-535V5K64.js} +15 -3
- package/dist/{chunk-YUZ4TXOY.js → chunk-DP6UJCMX.js} +3 -3
- package/dist/{chunk-F7GZYEAC.js → chunk-QA72XJZU.js} +3 -3
- package/dist/{chunk-Y6LLTN7W.js → chunk-XYT6GZ7M.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/manifest/index.js +3 -3
- package/dist/mcp/index.js +2 -2
- package/dist/openapi/index.js +2 -2
- package/dist/runner/index.js +3 -3
- package/dist/runtime/index.js +1 -1
- package/dist/workerd/index.js +1 -1
- package/package.json +1 -1
- package/src/authoring/index.ts +72 -44
- package/src/mcp/index.ts +2 -2
- package/src/openapi/index.ts +2 -2
- package/src/runtime/index.ts +19 -15
|
@@ -102,6 +102,7 @@ export interface RequirementSpec {
|
|
|
102
102
|
readonly description?: string;
|
|
103
103
|
}
|
|
104
104
|
declare const RequirementKey: unique symbol;
|
|
105
|
+
declare const ServiceDeclKey: unique symbol;
|
|
105
106
|
declare const RequirementPhantom: unique symbol;
|
|
106
107
|
/** Requirement arity, tracked at the type level so handler contexts
|
|
107
108
|
* know whether a role arrives as one entry, maybe-one, or a list. */
|
|
@@ -111,8 +112,8 @@ export type RequirementArity = "one" | "optional" | "many";
|
|
|
111
112
|
* a requirement (bare value = arity "one"); .optional()/.array()
|
|
112
113
|
* return terminal Requirements so nonsense chains do not typecheck.
|
|
113
114
|
* The phantom property is never assigned — it exists so auth-compose
|
|
114
|
-
* callbacks and handler contexts can recover the service's grants,
|
|
115
|
-
*
|
|
115
|
+
* callbacks and handler contexts can recover the service's grants, an
|
|
116
|
+
* auth-door pin, and the arity at the type level.
|
|
116
117
|
*/
|
|
117
118
|
export interface Requirement<D extends ServiceDecl = ServiceDecl, G extends string | undefined = string | undefined, A extends RequirementArity = RequirementArity> {
|
|
118
119
|
readonly [RequirementKey]: RequirementSpec;
|
|
@@ -123,13 +124,33 @@ export interface Requirement<D extends ServiceDecl = ServiceDecl, G extends stri
|
|
|
123
124
|
};
|
|
124
125
|
describe(text: string): Requirement<D, G, A>;
|
|
125
126
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
/**
|
|
128
|
+
* A grant-pinned requirement: what `service.auth.<name>` returns. A
|
|
129
|
+
* deliberately narrower type than Service — it can shape arity and
|
|
130
|
+
* describe itself, but carries no `auth` (pinning twice is nonsense)
|
|
131
|
+
* and no declaration access. Pinning is property access because the
|
|
132
|
+
* grants are static facts of the declaration: `github.auth.pat`
|
|
133
|
+
* autocompletes and a wrong name fails compilation.
|
|
134
|
+
*/
|
|
135
|
+
export interface PinnedRequirement<D extends ServiceDecl = ServiceDecl, N extends string = string> extends Requirement<D, N, "one"> {
|
|
136
|
+
optional(): Requirement<D, N, "optional">;
|
|
137
|
+
array(): Requirement<D, N, "many">;
|
|
138
|
+
describe(text: string): PinnedRequirement<D, N>;
|
|
139
|
+
}
|
|
140
|
+
/** The grant-pinned doors of a service, keyed by declared auth method. */
|
|
141
|
+
export type ServiceAuth<D extends ServiceDecl> = {
|
|
142
|
+
readonly [N in keyof NonNullable<D["auth"]> & string]: PinnedRequirement<D, N>;
|
|
143
|
+
};
|
|
144
|
+
export interface Service<D extends ServiceDecl = ServiceDecl> extends Requirement<D, undefined, "one"> {
|
|
145
|
+
/** Symbol-keyed so author IntelliSense shows only authoring verbs;
|
|
146
|
+
* the declaration is plumbing the manifest reads at collection. */
|
|
147
|
+
readonly [ServiceDeclKey]: D;
|
|
148
|
+
/** Pin one auth method: `github.auth.pat` is this tool supporting
|
|
149
|
+
* only that grant. */
|
|
150
|
+
readonly auth: ServiceAuth<D>;
|
|
151
|
+
optional(): Requirement<D, undefined, "optional">;
|
|
152
|
+
array(): Requirement<D, undefined, "many">;
|
|
153
|
+
describe(text: string): Service<D>;
|
|
133
154
|
}
|
|
134
155
|
/** A publish-time authoring problem accumulated without interrupting defines. */
|
|
135
156
|
export interface Problem {
|
|
@@ -223,11 +244,17 @@ export interface DiscoveredToolSpec {
|
|
|
223
244
|
export interface DynamicToolsDecl<Reqs extends Readonly<Record<string, Requirement>> | undefined = Readonly<Record<string, Requirement>> | undefined> {
|
|
224
245
|
readonly description: string;
|
|
225
246
|
readonly requirements?: Reqs;
|
|
226
|
-
discover(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
247
|
+
discover(args: {
|
|
248
|
+
readonly ctx: HandlerCtx<Reqs>;
|
|
249
|
+
}): Promise<readonly DiscoveredToolSpec[]>;
|
|
250
|
+
call(args: {
|
|
251
|
+
readonly tool: {
|
|
252
|
+
readonly name: string;
|
|
253
|
+
readonly detail: unknown;
|
|
254
|
+
};
|
|
255
|
+
readonly input: unknown;
|
|
256
|
+
readonly ctx: HandlerCtx<Reqs>;
|
|
257
|
+
}): Promise<unknown>;
|
|
231
258
|
}
|
|
232
259
|
/**
|
|
233
260
|
* The erased registry row of a dynamic pair — same storage boundary
|
|
@@ -240,11 +267,8 @@ export interface DynamicToolsDecl<Reqs extends Readonly<Record<string, Requireme
|
|
|
240
267
|
export interface DynamicToolsSpec {
|
|
241
268
|
readonly description: string;
|
|
242
269
|
readonly requirements?: Readonly<Record<string, Requirement>>;
|
|
243
|
-
discover(
|
|
244
|
-
call(
|
|
245
|
-
readonly name: string;
|
|
246
|
-
readonly detail: unknown;
|
|
247
|
-
}, input: never, ctx: never): Promise<unknown>;
|
|
270
|
+
discover(args: never): Promise<readonly DiscoveredToolSpec[]>;
|
|
271
|
+
call(args: never): Promise<unknown>;
|
|
248
272
|
}
|
|
249
273
|
declare const DynamicKey: unique symbol;
|
|
250
274
|
/** The value defineDynamicTools returns: an opaque handle a tool file
|
|
@@ -330,7 +354,7 @@ export type InferSchema<S> = S extends {
|
|
|
330
354
|
/**
|
|
331
355
|
* The capability a handler receives for one connected requirement
|
|
332
356
|
* role: the grant's kind-discriminated material (narrowed by any
|
|
333
|
-
*
|
|
357
|
+
* auth-door pin) plus the spending fetch. Arity shapes the value —
|
|
334
358
|
* `.optional()` may be absent, `.array()` is every connected account.
|
|
335
359
|
*/
|
|
336
360
|
export type HandlerRole<R> = R extends Requirement<infer D, infer G, infer A> ? [A] extends ["many"] ? ReadonlyArray<HandlerRoleEntry<D, G>> : [A] extends ["optional"] ? HandlerRoleEntry<D, G> | undefined : HandlerRoleEntry<D, G> : never;
|
|
@@ -354,7 +378,9 @@ export interface ToolAnnotations {
|
|
|
354
378
|
* The generic authored shape: input/ctx/output are all inferred — the
|
|
355
379
|
* handler's input from the Standard Schema, ctx from the requirements
|
|
356
380
|
* record (grants, pins, arity), and the return type checked against
|
|
357
|
-
* the output schema when one is stated.
|
|
381
|
+
* the output schema when one is stated. The handler takes ONE
|
|
382
|
+
* destructurable argument so unused halves never demand an underscore:
|
|
383
|
+
* `handler: async ({ input }) => ...` / `async ({ ctx }) => ...`.
|
|
358
384
|
*/
|
|
359
385
|
export interface ToolDecl<Input extends SchemaLike = SchemaLike, Reqs extends Readonly<Record<string, Requirement>> | undefined = Readonly<Record<string, Requirement>> | undefined, Output extends SchemaLike | undefined = SchemaLike | undefined> {
|
|
360
386
|
/** Address segment. Single-tool files may omit it (filename). */
|
|
@@ -364,7 +390,10 @@ export interface ToolDecl<Input extends SchemaLike = SchemaLike, Reqs extends Re
|
|
|
364
390
|
readonly input: Input;
|
|
365
391
|
readonly output?: Output;
|
|
366
392
|
readonly annotations?: ToolAnnotations;
|
|
367
|
-
handler(
|
|
393
|
+
handler(args: {
|
|
394
|
+
readonly input: InferSchema<Input>;
|
|
395
|
+
readonly ctx: HandlerCtx<Reqs>;
|
|
396
|
+
}): Promise<Output extends SchemaLike ? InferSchema<Output> : unknown>;
|
|
368
397
|
}
|
|
369
398
|
/**
|
|
370
399
|
* The erased registry row. Registration drops the generics — storage
|
|
@@ -380,7 +409,7 @@ export interface ToolSpec {
|
|
|
380
409
|
readonly input: SchemaLike;
|
|
381
410
|
readonly output?: SchemaLike;
|
|
382
411
|
readonly annotations?: ToolAnnotations;
|
|
383
|
-
handler(
|
|
412
|
+
handler(args: never): Promise<unknown>;
|
|
384
413
|
}
|
|
385
414
|
declare const ToolKey: unique symbol;
|
|
386
415
|
export interface Tool {
|
package/dist/authoring/index.js
CHANGED
|
@@ -232,7 +232,7 @@ var callTool = async (registry, address, input, ctx, detail) => {
|
|
|
232
232
|
if (!parsed2.ok) return failure("input", parsed2.reason);
|
|
233
233
|
try {
|
|
234
234
|
const handler = registered.spec.handler;
|
|
235
|
-
return { ok: true, value: await handler(parsed2.value, ctx) };
|
|
235
|
+
return { ok: true, value: await handler({ input: parsed2.value, ctx }) };
|
|
236
236
|
} catch (error) {
|
|
237
237
|
return failure("handler", `handler failed (${errorMessage(error)})`);
|
|
238
238
|
}
|
|
@@ -254,7 +254,11 @@ var callTool = async (registry, address, input, ctx, detail) => {
|
|
|
254
254
|
const call = pair.call;
|
|
255
255
|
return {
|
|
256
256
|
ok: true,
|
|
257
|
-
value: await call({
|
|
257
|
+
value: await call({
|
|
258
|
+
tool: { name: dynamic.name, detail },
|
|
259
|
+
input: parsed.value,
|
|
260
|
+
ctx
|
|
261
|
+
})
|
|
258
262
|
};
|
|
259
263
|
} catch (error) {
|
|
260
264
|
return failure("handler", `dynamic call failed (${errorMessage(error)})`);
|
|
@@ -268,7 +272,7 @@ var discoverTool = async (registry, segment, ctx) => {
|
|
|
268
272
|
let emitted;
|
|
269
273
|
try {
|
|
270
274
|
const discover = pair.discover;
|
|
271
|
-
emitted = await discover(ctx);
|
|
275
|
+
emitted = await discover({ ctx });
|
|
272
276
|
} catch (error) {
|
|
273
277
|
return { ok: false, reason: `discover failed (${errorMessage(error)})` };
|
|
274
278
|
}
|
|
@@ -4,6 +4,7 @@ var basic = (opts) => ({ kind: "basic", ...opts });
|
|
|
4
4
|
var secrets = (opts) => ({ kind: "secrets", ...opts });
|
|
5
5
|
var oauth = (opts) => ({ kind: "oauth", ...opts });
|
|
6
6
|
var RequirementKey = Symbol.for("executor/apps/requirement");
|
|
7
|
+
var ServiceDeclKey = Symbol.for("executor/apps/service-decl");
|
|
7
8
|
var recordAuthoringProblem = (message) => {
|
|
8
9
|
const file = sourceFileFor("wrapper");
|
|
9
10
|
activeRegistry.problems.push({ message: `${file}: ${message}` });
|
|
@@ -73,12 +74,23 @@ var makeTerminal = (spec) => ({
|
|
|
73
74
|
});
|
|
74
75
|
var service = (decl) => {
|
|
75
76
|
const file = sourceFileFor("service(...)");
|
|
77
|
+
const makePinned = (spec) => ({
|
|
78
|
+
[RequirementKey]: spec,
|
|
79
|
+
optional: () => makeTerminal({ ...spec, arity: "optional" }),
|
|
80
|
+
array: () => makeTerminal({ ...spec, arity: "many" }),
|
|
81
|
+
describe: (text) => makePinned({ ...spec, description: text })
|
|
82
|
+
});
|
|
76
83
|
const make = (spec) => ({
|
|
77
84
|
[RequirementKey]: spec,
|
|
78
|
-
decl,
|
|
85
|
+
[ServiceDeclKey]: decl,
|
|
86
|
+
auth: Object.fromEntries(
|
|
87
|
+
Object.keys(decl.auth ?? {}).map((name) => [
|
|
88
|
+
name,
|
|
89
|
+
makePinned({ ...spec, grant: name })
|
|
90
|
+
])
|
|
91
|
+
),
|
|
79
92
|
optional: () => makeTerminal({ ...spec, arity: "optional" }),
|
|
80
93
|
array: () => makeTerminal({ ...spec, arity: "many" }),
|
|
81
|
-
grant: (name) => make({ ...spec, grant: name }),
|
|
82
94
|
describe: (text) => make({ ...spec, description: text })
|
|
83
95
|
});
|
|
84
96
|
const value = make({ service: decl, arity: "one" });
|
|
@@ -87,7 +99,7 @@ var service = (decl) => {
|
|
|
87
99
|
return value;
|
|
88
100
|
};
|
|
89
101
|
var isRequirement = (value) => typeof value === "object" && value !== null && RequirementKey in value;
|
|
90
|
-
var isService = (value) => isRequirement(value) &&
|
|
102
|
+
var isService = (value) => isRequirement(value) && ServiceDeclKey in value;
|
|
91
103
|
var requirementSpecOf = (value) => isService(value) || isRequirement(value) ? value[RequirementKey] : void 0;
|
|
92
104
|
var defineApp = (manifest) => {
|
|
93
105
|
const file = sourceFileFor("defineApp(...)");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineDynamicTools
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-535V5K64.js";
|
|
4
4
|
|
|
5
5
|
// src/mcp/client.ts
|
|
6
6
|
var mcpDiscover = async (_endpoint, _ctx, _auth, _operations) => {
|
|
@@ -17,8 +17,8 @@ function fromMCP(endpoint, options) {
|
|
|
17
17
|
return defineDynamicTools({
|
|
18
18
|
description: `Tools served by the MCP server at ${endpoint}.`,
|
|
19
19
|
...options?.requirements === void 0 ? {} : { requirements: options.requirements },
|
|
20
|
-
discover: (ctx) => mcpDiscover(endpoint, ctx, auth, operations),
|
|
21
|
-
call: (tool, input, ctx) => mcpCall(endpoint, tool.name, input, ctx, auth)
|
|
20
|
+
discover: ({ ctx }) => mcpDiscover(endpoint, ctx, auth, operations),
|
|
21
|
+
call: ({ tool, input, ctx }) => mcpCall(endpoint, tool.name, input, ctx, auth)
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
defineDynamicTools,
|
|
3
3
|
recordAuthoringProblem,
|
|
4
4
|
requirementSpecOf
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-535V5K64.js";
|
|
6
6
|
|
|
7
7
|
// src/openapi/mapper.ts
|
|
8
8
|
var openapiDiscover = async (_spec, _ctx, _auth, _operations) => {
|
|
@@ -31,8 +31,8 @@ function fromOpenAPI(spec, options) {
|
|
|
31
31
|
return defineDynamicTools({
|
|
32
32
|
description: typeof spec === "string" && spec.startsWith("https://") ? `Operations from the OpenAPI document at ${spec}.` : "Operations from an OpenAPI document shipped with the app.",
|
|
33
33
|
...options?.requirements === void 0 ? {} : { requirements: options.requirements },
|
|
34
|
-
discover: (ctx) => openapiDiscover(spec, ctx, auth, operations),
|
|
35
|
-
call: (tool, input, ctx) => openapiCall(tool, input, ctx, auth)
|
|
34
|
+
discover: ({ ctx }) => openapiDiscover(spec, ctx, auth, operations),
|
|
35
|
+
call: ({ tool, input, ctx }) => openapiCall(tool, input, ctx, auth)
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
manifestOf
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-2DVKSPQH.js";
|
|
4
4
|
import {
|
|
5
5
|
PublishError
|
|
6
6
|
} from "./chunk-B7NEUIJL.js";
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
__leaveFile,
|
|
10
10
|
newRegistry,
|
|
11
11
|
setActiveRegistry
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-535V5K64.js";
|
|
13
13
|
|
|
14
14
|
// src/manifest/index.ts
|
|
15
15
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
fromOpenAPI
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QA72XJZU.js";
|
|
4
4
|
import {
|
|
5
5
|
authHeaders
|
|
6
6
|
} from "./chunk-S5KFP4JQ.js";
|
|
7
7
|
import {
|
|
8
8
|
fromMCP
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-DP6UJCMX.js";
|
|
10
10
|
import {
|
|
11
11
|
__enterFile,
|
|
12
12
|
__leaveFile,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
service,
|
|
25
25
|
setActiveRegistry,
|
|
26
26
|
toolSpecOf
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-535V5K64.js";
|
|
28
28
|
export {
|
|
29
29
|
__enterFile,
|
|
30
30
|
__leaveFile,
|
package/dist/manifest/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadApp,
|
|
3
3
|
manifest
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-XYT6GZ7M.js";
|
|
5
|
+
import "../chunk-2DVKSPQH.js";
|
|
6
6
|
import {
|
|
7
7
|
JsonSchemaObject,
|
|
8
8
|
PublishError
|
|
9
9
|
} from "../chunk-B7NEUIJL.js";
|
|
10
|
-
import "../chunk-
|
|
10
|
+
import "../chunk-535V5K64.js";
|
|
11
11
|
export {
|
|
12
12
|
JsonSchemaObject,
|
|
13
13
|
PublishError,
|
package/dist/mcp/index.js
CHANGED
package/dist/openapi/index.js
CHANGED
package/dist/runner/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadApp
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-XYT6GZ7M.js";
|
|
4
4
|
import {
|
|
5
5
|
callTool,
|
|
6
6
|
discoverTool
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-2DVKSPQH.js";
|
|
8
8
|
import "../chunk-B7NEUIJL.js";
|
|
9
9
|
import {
|
|
10
10
|
CallError,
|
|
11
11
|
DiscoverError
|
|
12
12
|
} from "../chunk-4OZFMCTT.js";
|
|
13
|
-
import "../chunk-
|
|
13
|
+
import "../chunk-535V5K64.js";
|
|
14
14
|
|
|
15
15
|
// src/runner/index.ts
|
|
16
16
|
import { Effect } from "effect";
|
package/dist/runtime/index.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.4",
|
|
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",
|
package/src/authoring/index.ts
CHANGED
|
@@ -126,6 +126,7 @@ export interface RequirementSpec {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
const RequirementKey: unique symbol = Symbol.for("executor/apps/requirement");
|
|
129
|
+
const ServiceDeclKey: unique symbol = Symbol.for("executor/apps/service-decl");
|
|
129
130
|
|
|
130
131
|
declare const RequirementPhantom: unique symbol;
|
|
131
132
|
|
|
@@ -138,8 +139,8 @@ export type RequirementArity = "one" | "optional" | "many";
|
|
|
138
139
|
* a requirement (bare value = arity "one"); .optional()/.array()
|
|
139
140
|
* return terminal Requirements so nonsense chains do not typecheck.
|
|
140
141
|
* The phantom property is never assigned — it exists so auth-compose
|
|
141
|
-
* callbacks and handler contexts can recover the service's grants,
|
|
142
|
-
*
|
|
142
|
+
* callbacks and handler contexts can recover the service's grants, an
|
|
143
|
+
* auth-door pin, and the arity at the type level.
|
|
143
144
|
*/
|
|
144
145
|
export interface Requirement<
|
|
145
146
|
D extends ServiceDecl = ServiceDecl,
|
|
@@ -155,16 +156,39 @@ export interface Requirement<
|
|
|
155
156
|
describe(text: string): Requirement<D, G, A>;
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
|
|
159
|
+
/**
|
|
160
|
+
* A grant-pinned requirement: what `service.auth.<name>` returns. A
|
|
161
|
+
* deliberately narrower type than Service — it can shape arity and
|
|
162
|
+
* describe itself, but carries no `auth` (pinning twice is nonsense)
|
|
163
|
+
* and no declaration access. Pinning is property access because the
|
|
164
|
+
* grants are static facts of the declaration: `github.auth.pat`
|
|
165
|
+
* autocompletes and a wrong name fails compilation.
|
|
166
|
+
*/
|
|
167
|
+
export interface PinnedRequirement<
|
|
159
168
|
D extends ServiceDecl = ServiceDecl,
|
|
160
|
-
|
|
161
|
-
> extends Requirement<D,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
169
|
+
N extends string = string,
|
|
170
|
+
> extends Requirement<D, N, "one"> {
|
|
171
|
+
optional(): Requirement<D, N, "optional">;
|
|
172
|
+
array(): Requirement<D, N, "many">;
|
|
173
|
+
describe(text: string): PinnedRequirement<D, N>;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The grant-pinned doors of a service, keyed by declared auth method. */
|
|
177
|
+
export type ServiceAuth<D extends ServiceDecl> = {
|
|
178
|
+
readonly [N in keyof NonNullable<D["auth"]> & string]: PinnedRequirement<D, N>;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export interface Service<D extends ServiceDecl = ServiceDecl>
|
|
182
|
+
extends Requirement<D, undefined, "one"> {
|
|
183
|
+
/** Symbol-keyed so author IntelliSense shows only authoring verbs;
|
|
184
|
+
* the declaration is plumbing the manifest reads at collection. */
|
|
185
|
+
readonly [ServiceDeclKey]: D;
|
|
186
|
+
/** Pin one auth method: `github.auth.pat` is this tool supporting
|
|
187
|
+
* only that grant. */
|
|
188
|
+
readonly auth: ServiceAuth<D>;
|
|
189
|
+
optional(): Requirement<D, undefined, "optional">;
|
|
190
|
+
array(): Requirement<D, undefined, "many">;
|
|
191
|
+
describe(text: string): Service<D>;
|
|
168
192
|
}
|
|
169
193
|
|
|
170
194
|
/** A publish-time authoring problem accumulated without interrupting defines. */
|
|
@@ -294,12 +318,12 @@ export interface DynamicToolsDecl<
|
|
|
294
318
|
> {
|
|
295
319
|
readonly description: string;
|
|
296
320
|
readonly requirements?: Reqs;
|
|
297
|
-
discover(ctx: HandlerCtx<Reqs>): Promise<readonly DiscoveredToolSpec[]>;
|
|
298
|
-
call(
|
|
299
|
-
tool: { readonly name: string; readonly detail: unknown }
|
|
300
|
-
input: unknown
|
|
301
|
-
ctx: HandlerCtx<Reqs
|
|
302
|
-
): Promise<unknown>;
|
|
321
|
+
discover(args: { readonly ctx: HandlerCtx<Reqs> }): Promise<readonly DiscoveredToolSpec[]>;
|
|
322
|
+
call(args: {
|
|
323
|
+
readonly tool: { readonly name: string; readonly detail: unknown };
|
|
324
|
+
readonly input: unknown;
|
|
325
|
+
readonly ctx: HandlerCtx<Reqs>;
|
|
326
|
+
}): Promise<unknown>;
|
|
303
327
|
}
|
|
304
328
|
|
|
305
329
|
/**
|
|
@@ -313,12 +337,8 @@ export interface DynamicToolsDecl<
|
|
|
313
337
|
export interface DynamicToolsSpec {
|
|
314
338
|
readonly description: string;
|
|
315
339
|
readonly requirements?: Readonly<Record<string, Requirement>>;
|
|
316
|
-
discover(
|
|
317
|
-
call(
|
|
318
|
-
tool: { readonly name: string; readonly detail: unknown },
|
|
319
|
-
input: never,
|
|
320
|
-
ctx: never,
|
|
321
|
-
): Promise<unknown>;
|
|
340
|
+
discover(args: never): Promise<readonly DiscoveredToolSpec[]>;
|
|
341
|
+
call(args: never): Promise<unknown>;
|
|
322
342
|
}
|
|
323
343
|
|
|
324
344
|
const DynamicKey: unique symbol = Symbol.for("executor/apps/dynamic");
|
|
@@ -462,18 +482,28 @@ const makeTerminal = <
|
|
|
462
482
|
|
|
463
483
|
export const service = <const D extends ServiceDecl>(decl: D): Service<D> => {
|
|
464
484
|
const file = sourceFileFor("service(...)");
|
|
465
|
-
const
|
|
485
|
+
const makePinned = <N extends keyof NonNullable<D["auth"]> & string>(
|
|
466
486
|
spec: RequirementSpec,
|
|
467
|
-
):
|
|
487
|
+
): PinnedRequirement<D, N> => ({
|
|
488
|
+
[RequirementKey]: spec,
|
|
489
|
+
optional: () => makeTerminal<D, N, "optional">({ ...spec, arity: "optional" }),
|
|
490
|
+
array: () => makeTerminal<D, N, "many">({ ...spec, arity: "many" }),
|
|
491
|
+
describe: (text) => makePinned<N>({ ...spec, description: text }),
|
|
492
|
+
});
|
|
493
|
+
const make = (spec: RequirementSpec): Service<D> => ({
|
|
468
494
|
[RequirementKey]: spec,
|
|
469
|
-
decl,
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
495
|
+
[ServiceDeclKey]: decl,
|
|
496
|
+
auth: Object.fromEntries(
|
|
497
|
+
Object.keys(decl.auth ?? {}).map((name) => [
|
|
498
|
+
name,
|
|
499
|
+
makePinned({ ...spec, grant: name }),
|
|
500
|
+
]),
|
|
501
|
+
) as ServiceAuth<D>,
|
|
502
|
+
optional: () => makeTerminal<D, undefined, "optional">({ ...spec, arity: "optional" }),
|
|
503
|
+
array: () => makeTerminal<D, undefined, "many">({ ...spec, arity: "many" }),
|
|
504
|
+
describe: (text) => make({ ...spec, description: text }),
|
|
475
505
|
});
|
|
476
|
-
const value = make
|
|
506
|
+
const value = make({ service: decl, arity: "one" });
|
|
477
507
|
if (!activeRegistry.services.has(decl.origin))
|
|
478
508
|
activeRegistry.services.set(decl.origin, { decl, file });
|
|
479
509
|
return value;
|
|
@@ -483,11 +513,7 @@ const isRequirement = (value: unknown): value is Requirement =>
|
|
|
483
513
|
typeof value === "object" && value !== null && RequirementKey in value;
|
|
484
514
|
|
|
485
515
|
const isService = (value: unknown): value is Service =>
|
|
486
|
-
isRequirement(value) &&
|
|
487
|
-
"decl" in value &&
|
|
488
|
-
"optional" in value &&
|
|
489
|
-
"array" in value &&
|
|
490
|
-
"grant" in value;
|
|
516
|
+
isRequirement(value) && ServiceDeclKey in value;
|
|
491
517
|
|
|
492
518
|
export const requirementSpecOf = (value: unknown): RequirementSpec | undefined =>
|
|
493
519
|
isService(value) || isRequirement(value) ? value[RequirementKey] : undefined;
|
|
@@ -535,7 +561,7 @@ export type InferSchema<S> = S extends {
|
|
|
535
561
|
/**
|
|
536
562
|
* The capability a handler receives for one connected requirement
|
|
537
563
|
* role: the grant's kind-discriminated material (narrowed by any
|
|
538
|
-
*
|
|
564
|
+
* auth-door pin) plus the spending fetch. Arity shapes the value —
|
|
539
565
|
* `.optional()` may be absent, `.array()` is every connected account.
|
|
540
566
|
*/
|
|
541
567
|
export type HandlerRole<R> = R extends Requirement<infer D, infer G, infer A>
|
|
@@ -570,7 +596,9 @@ export interface ToolAnnotations {
|
|
|
570
596
|
* The generic authored shape: input/ctx/output are all inferred — the
|
|
571
597
|
* handler's input from the Standard Schema, ctx from the requirements
|
|
572
598
|
* record (grants, pins, arity), and the return type checked against
|
|
573
|
-
* the output schema when one is stated.
|
|
599
|
+
* the output schema when one is stated. The handler takes ONE
|
|
600
|
+
* destructurable argument so unused halves never demand an underscore:
|
|
601
|
+
* `handler: async ({ input }) => ...` / `async ({ ctx }) => ...`.
|
|
574
602
|
*/
|
|
575
603
|
export interface ToolDecl<
|
|
576
604
|
Input extends SchemaLike = SchemaLike,
|
|
@@ -586,10 +614,10 @@ export interface ToolDecl<
|
|
|
586
614
|
readonly input: Input;
|
|
587
615
|
readonly output?: Output;
|
|
588
616
|
readonly annotations?: ToolAnnotations;
|
|
589
|
-
handler(
|
|
590
|
-
input: InferSchema<Input
|
|
591
|
-
ctx: HandlerCtx<Reqs
|
|
592
|
-
): Promise<Output extends SchemaLike ? InferSchema<Output> : unknown>;
|
|
617
|
+
handler(args: {
|
|
618
|
+
readonly input: InferSchema<Input>;
|
|
619
|
+
readonly ctx: HandlerCtx<Reqs>;
|
|
620
|
+
}): Promise<Output extends SchemaLike ? InferSchema<Output> : unknown>;
|
|
593
621
|
}
|
|
594
622
|
|
|
595
623
|
/**
|
|
@@ -606,7 +634,7 @@ export interface ToolSpec {
|
|
|
606
634
|
readonly input: SchemaLike;
|
|
607
635
|
readonly output?: SchemaLike;
|
|
608
636
|
readonly annotations?: ToolAnnotations;
|
|
609
|
-
handler(
|
|
637
|
+
handler(args: never): Promise<unknown>;
|
|
610
638
|
}
|
|
611
639
|
|
|
612
640
|
const ToolKey: unique symbol = Symbol.for("executor/apps/tool");
|
package/src/mcp/index.ts
CHANGED
|
@@ -36,7 +36,7 @@ export function fromMCP(
|
|
|
36
36
|
return defineDynamicTools({
|
|
37
37
|
description: `Tools served by the MCP server at ${endpoint}.`,
|
|
38
38
|
...(options?.requirements === undefined ? {} : { requirements: options.requirements }),
|
|
39
|
-
discover: (ctx) => mcpDiscover(endpoint, ctx, auth, operations),
|
|
40
|
-
call: (tool, input, ctx) => mcpCall(endpoint, tool.name, input, ctx, auth),
|
|
39
|
+
discover: ({ ctx }) => mcpDiscover(endpoint, ctx, auth, operations),
|
|
40
|
+
call: ({ tool, input, ctx }) => mcpCall(endpoint, tool.name, input, ctx, auth),
|
|
41
41
|
});
|
|
42
42
|
}
|
package/src/openapi/index.ts
CHANGED
|
@@ -64,7 +64,7 @@ export function fromOpenAPI(
|
|
|
64
64
|
? `Operations from the OpenAPI document at ${spec}.`
|
|
65
65
|
: "Operations from an OpenAPI document shipped with the app.",
|
|
66
66
|
...(options?.requirements === undefined ? {} : { requirements: options.requirements }),
|
|
67
|
-
discover: (ctx) => openapiDiscover(spec, ctx, auth, operations),
|
|
68
|
-
call: (tool, input, ctx) => openapiCall(tool, input, ctx, auth),
|
|
67
|
+
discover: ({ ctx }) => openapiDiscover(spec, ctx, auth, operations),
|
|
68
|
+
call: ({ tool, input, ctx }) => openapiCall(tool, input, ctx, auth),
|
|
69
69
|
});
|
|
70
70
|
}
|
package/src/runtime/index.ts
CHANGED
|
@@ -457,11 +457,11 @@ export const callTool = async (
|
|
|
457
457
|
try {
|
|
458
458
|
// SAFETY: ToolSpec intentionally erases authored handler inputs to keep
|
|
459
459
|
// authoring generic-free; validation and ToolCtx establish this call boundary.
|
|
460
|
-
const handler = registered.spec.handler as (
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
) => Promise<unknown>;
|
|
464
|
-
return { ok: true, value: await handler(parsed.value, ctx) };
|
|
460
|
+
const handler = registered.spec.handler as (args: {
|
|
461
|
+
readonly input: unknown;
|
|
462
|
+
readonly ctx: ToolCtx;
|
|
463
|
+
}) => Promise<unknown>;
|
|
464
|
+
return { ok: true, value: await handler({ input: parsed.value, ctx }) };
|
|
465
465
|
} catch (error) {
|
|
466
466
|
return failure("handler", `handler failed (${errorMessage(error)})`);
|
|
467
467
|
}
|
|
@@ -481,14 +481,18 @@ export const callTool = async (
|
|
|
481
481
|
const parsed = await parseInput(discovered.input, input);
|
|
482
482
|
if (!parsed.ok) return failure("input", parsed.reason);
|
|
483
483
|
try {
|
|
484
|
-
const call = pair.call as (
|
|
485
|
-
tool: { readonly name: string; readonly detail: unknown }
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
) => Promise<unknown>;
|
|
484
|
+
const call = pair.call as (args: {
|
|
485
|
+
readonly tool: { readonly name: string; readonly detail: unknown };
|
|
486
|
+
readonly input: unknown;
|
|
487
|
+
readonly ctx: ToolCtx;
|
|
488
|
+
}) => Promise<unknown>;
|
|
489
489
|
return {
|
|
490
490
|
ok: true,
|
|
491
|
-
value: await call({
|
|
491
|
+
value: await call({
|
|
492
|
+
tool: { name: dynamic.name, detail },
|
|
493
|
+
input: parsed.value,
|
|
494
|
+
ctx,
|
|
495
|
+
}),
|
|
492
496
|
};
|
|
493
497
|
} catch (error) {
|
|
494
498
|
return failure("handler", `dynamic call failed (${errorMessage(error)})`);
|
|
@@ -508,10 +512,10 @@ export const discoverTool = async (
|
|
|
508
512
|
|
|
509
513
|
let emitted: readonly DiscoveredToolSpec[];
|
|
510
514
|
try {
|
|
511
|
-
const discover = pair.discover as (
|
|
512
|
-
|
|
513
|
-
) => Promise<readonly DiscoveredToolSpec[]>;
|
|
514
|
-
emitted = await discover(ctx);
|
|
515
|
+
const discover = pair.discover as (args: {
|
|
516
|
+
readonly ctx: ToolCtx;
|
|
517
|
+
}) => Promise<readonly DiscoveredToolSpec[]>;
|
|
518
|
+
emitted = await discover({ ctx });
|
|
515
519
|
} catch (error) {
|
|
516
520
|
return { ok: false, reason: `discover failed (${errorMessage(error)})` };
|
|
517
521
|
}
|