apps 1.0.0-beta.2 → 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 +254 -37
- package/dist/authoring/index.js +11 -1
- package/dist/{chunk-OGCPVWAZ.js → chunk-2DVKSPQH.js} +134 -13
- package/dist/{chunk-NFLNA2SC.js → chunk-4OZFMCTT.js} +7 -1
- package/dist/{chunk-LWKQDDLZ.js → chunk-535V5K64.js} +61 -4
- package/dist/chunk-DP6UJCMX.js +27 -0
- package/dist/chunk-QA72XJZU.js +41 -0
- package/dist/chunk-S5KFP4JQ.js +20 -0
- package/dist/{chunk-646MS7DD.js → chunk-XYT6GZ7M.js} +7 -7
- package/dist/http/index.d.ts +9 -0
- package/dist/http/index.js +6 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +23 -1
- package/dist/manifest/contract.d.ts +8 -0
- package/dist/manifest/index.js +3 -3
- package/dist/mcp/client.d.ts +9 -0
- package/dist/mcp/index.d.ts +11 -0
- package/dist/mcp/index.js +7 -0
- package/dist/openapi/index.d.ts +14 -0
- package/dist/openapi/index.js +7 -0
- package/dist/openapi/mapper.d.ts +7 -0
- package/dist/runner/index.js +29 -15
- package/dist/runner/protocol.d.ts +18 -5
- package/dist/runner/protocol.js +5 -3
- package/dist/runtime/index.d.ts +42 -5
- package/dist/runtime/index.js +3 -1
- package/dist/workerd/index.d.ts +1 -1
- package/dist/workerd/index.js +1 -1
- package/package.json +13 -6
- package/src/authoring/index.ts +395 -49
- package/src/http/index.ts +26 -0
- package/src/index.ts +3 -0
- package/src/manifest/contract.ts +9 -0
- package/src/mcp/client.ts +59 -0
- package/src/mcp/index.ts +42 -0
- package/src/openapi/index.ts +70 -0
- package/src/openapi/mapper.ts +25 -0
- package/src/runner/index.ts +35 -12
- package/src/runner/protocol.ts +32 -7
- package/src/runtime/index.ts +216 -25
- package/src/workerd/index.ts +2 -0
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
* Authored modules record plain claims without I/O or user context; the
|
|
4
4
|
* manifest loader owns execution, validation, and effects.
|
|
5
5
|
*/
|
|
6
|
+
/** A single opaque credential with RFC 6750 bearer semantics. */
|
|
7
|
+
export interface BearerMethod {
|
|
8
|
+
readonly kind: "bearer";
|
|
9
|
+
readonly label: string;
|
|
10
|
+
readonly help?: string;
|
|
11
|
+
}
|
|
12
|
+
/** An RFC 7617 username/password pair. */
|
|
13
|
+
export interface BasicMethod {
|
|
14
|
+
readonly kind: "basic";
|
|
15
|
+
readonly label: string;
|
|
16
|
+
readonly help?: string;
|
|
17
|
+
}
|
|
6
18
|
export interface SecretsMethod<F extends Readonly<Record<string, SecretField>> = Readonly<Record<string, SecretField>>> {
|
|
7
19
|
readonly kind: "secrets";
|
|
8
20
|
readonly label: string;
|
|
@@ -17,7 +29,7 @@ export interface SecretField {
|
|
|
17
29
|
readonly secret: boolean;
|
|
18
30
|
}
|
|
19
31
|
/**
|
|
20
|
-
* An oauth auth method is a
|
|
32
|
+
* An oauth auth method is a bearer method with acquisition strategies
|
|
21
33
|
* layered on: the material contract is always {token, refresh_token?,
|
|
22
34
|
* expires_at?}, and the host fills it by the best strategy it can offer
|
|
23
35
|
* — authorization-code ceremony, device-code ceremony, or paste. Paste
|
|
@@ -41,7 +53,15 @@ export interface OAuthMethod {
|
|
|
41
53
|
* (audience-bound tokens, DPoP). Stated, never inferred. */
|
|
42
54
|
readonly paste?: false;
|
|
43
55
|
}
|
|
44
|
-
export type AuthMethod = SecretsMethod | OAuthMethod;
|
|
56
|
+
export type AuthMethod = BearerMethod | BasicMethod | SecretsMethod | OAuthMethod;
|
|
57
|
+
export declare const bearer: (opts: {
|
|
58
|
+
readonly label: string;
|
|
59
|
+
readonly help?: string;
|
|
60
|
+
}) => BearerMethod;
|
|
61
|
+
export declare const basic: (opts: {
|
|
62
|
+
readonly label: string;
|
|
63
|
+
readonly help?: string;
|
|
64
|
+
}) => BasicMethod;
|
|
45
65
|
export declare const secrets: <const F extends Readonly<Record<string, SecretField>>>(opts: {
|
|
46
66
|
readonly label: string;
|
|
47
67
|
/** Markdown shown under this auth method on the connect page, for example:
|
|
@@ -82,55 +102,195 @@ export interface RequirementSpec {
|
|
|
82
102
|
readonly description?: string;
|
|
83
103
|
}
|
|
84
104
|
declare const RequirementKey: unique symbol;
|
|
105
|
+
declare const ServiceDeclKey: unique symbol;
|
|
106
|
+
declare const RequirementPhantom: unique symbol;
|
|
107
|
+
/** Requirement arity, tracked at the type level so handler contexts
|
|
108
|
+
* know whether a role arrives as one entry, maybe-one, or a list. */
|
|
109
|
+
export type RequirementArity = "one" | "optional" | "many";
|
|
85
110
|
/**
|
|
86
111
|
* A requirement value in a tool's `requirements` record. A Service IS
|
|
87
112
|
* a requirement (bare value = arity "one"); .optional()/.array()
|
|
88
113
|
* return terminal Requirements so nonsense chains do not typecheck.
|
|
114
|
+
* The phantom property is never assigned — it exists so auth-compose
|
|
115
|
+
* callbacks and handler contexts can recover the service's grants, an
|
|
116
|
+
* auth-door pin, and the arity at the type level.
|
|
89
117
|
*/
|
|
90
|
-
export interface Requirement<D extends ServiceDecl = ServiceDecl> {
|
|
118
|
+
export interface Requirement<D extends ServiceDecl = ServiceDecl, G extends string | undefined = string | undefined, A extends RequirementArity = RequirementArity> {
|
|
91
119
|
readonly [RequirementKey]: RequirementSpec;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
120
|
+
readonly [RequirementPhantom]?: {
|
|
121
|
+
readonly decl: D;
|
|
122
|
+
readonly pinned: G;
|
|
123
|
+
readonly arity: A;
|
|
124
|
+
};
|
|
125
|
+
describe(text: string): Requirement<D, G, A>;
|
|
126
|
+
}
|
|
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">;
|
|
100
153
|
describe(text: string): Service<D>;
|
|
101
154
|
}
|
|
102
155
|
/** A publish-time authoring problem accumulated without interrupting defines. */
|
|
103
156
|
export interface Problem {
|
|
104
157
|
readonly message: string;
|
|
105
158
|
}
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
159
|
+
/** One connected role, discriminated by the declared auth method kind. */
|
|
160
|
+
export type AuthRoleEntry<D extends ServiceDecl> = {
|
|
161
|
+
[N in keyof NonNullable<D["auth"]> & string]: NonNullable<D["auth"]>[N] extends SecretsMethod<infer F> ? {
|
|
162
|
+
readonly grant: N;
|
|
163
|
+
readonly kind: "secrets";
|
|
164
|
+
readonly secrets: {
|
|
165
|
+
readonly [K in keyof F & string]: string;
|
|
166
|
+
};
|
|
167
|
+
} : NonNullable<D["auth"]>[N] extends BasicMethod ? {
|
|
168
|
+
readonly grant: N;
|
|
169
|
+
readonly kind: "basic";
|
|
170
|
+
readonly username: string;
|
|
171
|
+
readonly password: string;
|
|
172
|
+
} : {
|
|
173
|
+
readonly grant: N;
|
|
174
|
+
readonly kind: "bearer" | "oauth";
|
|
175
|
+
readonly token: string;
|
|
176
|
+
};
|
|
177
|
+
}[keyof NonNullable<D["auth"]> & string];
|
|
178
|
+
/** Kind-discriminated credential material accepted by surface helpers. */
|
|
179
|
+
export type RoleMaterial = {
|
|
180
|
+
readonly kind: "bearer" | "oauth";
|
|
181
|
+
readonly token: string;
|
|
182
|
+
} | {
|
|
183
|
+
readonly kind: "basic";
|
|
184
|
+
readonly username: string;
|
|
185
|
+
readonly password: string;
|
|
118
186
|
} | {
|
|
119
|
-
readonly kind: "
|
|
120
|
-
readonly
|
|
121
|
-
|
|
122
|
-
|
|
187
|
+
readonly kind: "secrets";
|
|
188
|
+
readonly secrets: Readonly<Record<string, string>>;
|
|
189
|
+
};
|
|
190
|
+
/** Recover the connected role type produced by a service requirement. */
|
|
191
|
+
export type RoleOf<S, G extends string | undefined = undefined> = S extends Requirement<infer D, infer Pin> ? [G] extends [string] ? Extract<AuthRoleEntry<D>, {
|
|
192
|
+
grant: G;
|
|
193
|
+
}> : Pin extends string ? Extract<AuthRoleEntry<D>, {
|
|
194
|
+
grant: Pin;
|
|
195
|
+
}> : AuthRoleEntry<D> : never;
|
|
196
|
+
/** Role record received by an authored authentication placement callback. */
|
|
197
|
+
export type AuthRoles<Reqs extends Readonly<Record<string, Requirement>>> = {
|
|
198
|
+
readonly [K in keyof Reqs]: NonNullable<Reqs[K][typeof RequirementPhantom]> extends {
|
|
199
|
+
readonly decl: infer D extends ServiceDecl;
|
|
200
|
+
readonly pinned: infer G;
|
|
201
|
+
} ? G extends string ? Extract<AuthRoleEntry<D>, {
|
|
202
|
+
grant: G;
|
|
203
|
+
}> : AuthRoleEntry<D> : never;
|
|
123
204
|
};
|
|
205
|
+
/** Request placement returned by an authored dynamic auth callback. */
|
|
206
|
+
export interface AuthPlacement {
|
|
207
|
+
readonly headers?: HeadersInit;
|
|
208
|
+
readonly searchParams?: URLSearchParams | Readonly<Record<string, string>>;
|
|
209
|
+
}
|
|
210
|
+
export type AuthComposeFn<Reqs extends Readonly<Record<string, Requirement>> = never> = (roles: AuthRoles<Reqs>) => AuthPlacement | undefined | void;
|
|
211
|
+
export type HasSecretsGrant<D extends ServiceDecl> = true extends (NonNullable<D["auth"]>[keyof NonNullable<D["auth"]>] extends infer M ? M extends SecretsMethod ? true : false : never) ? true : false;
|
|
212
|
+
/** Whether an OpenAPI wrapper must state credential placement explicitly. */
|
|
213
|
+
export type RequiresAuthCompose<Reqs extends Readonly<Record<string, Requirement>>> = true extends {
|
|
214
|
+
[K in keyof Reqs]: NonNullable<Reqs[K][typeof RequirementPhantom]> extends {
|
|
215
|
+
readonly decl: infer D extends ServiceDecl;
|
|
216
|
+
readonly pinned: infer G;
|
|
217
|
+
} ? G extends keyof NonNullable<D["auth"]> & string ? NonNullable<D["auth"]>[G] extends SecretsMethod ? true : false : HasSecretsGrant<D> : never;
|
|
218
|
+
}[keyof Reqs] ? true : false;
|
|
219
|
+
/** Record a wrapper-specific publish problem on the active authored file. @internal */
|
|
220
|
+
export declare const recordAuthoringProblem: (message: string) => void;
|
|
221
|
+
/** Operation curation for a dynamic tool set. Exactly one of allow/block. */
|
|
222
|
+
export type OperationSelection = {
|
|
223
|
+
readonly allow: readonly string[];
|
|
224
|
+
readonly block?: undefined;
|
|
225
|
+
} | {
|
|
226
|
+
readonly block: readonly string[];
|
|
227
|
+
readonly allow?: undefined;
|
|
228
|
+
};
|
|
229
|
+
/** One tool emitted by a dynamic discovery pass. */
|
|
230
|
+
export interface DiscoveredToolSpec {
|
|
231
|
+
readonly name: string;
|
|
232
|
+
readonly description: string;
|
|
233
|
+
readonly input: SchemaLike;
|
|
234
|
+
readonly annotations?: ToolAnnotations;
|
|
235
|
+
readonly detail?: unknown;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The generic authored shape of a dynamic pair: ctx is inferred from
|
|
239
|
+
* the requirements record exactly as for static tools. Discovered
|
|
240
|
+
* inputs are runtime data (the schema arrives from the upstream at
|
|
241
|
+
* discover time), so `call`'s input is honestly `unknown` — the
|
|
242
|
+
* runtime validates against the discovered descriptor before calling.
|
|
243
|
+
*/
|
|
244
|
+
export interface DynamicToolsDecl<Reqs extends Readonly<Record<string, Requirement>> | undefined = Readonly<Record<string, Requirement>> | undefined> {
|
|
245
|
+
readonly description: string;
|
|
246
|
+
readonly requirements?: Reqs;
|
|
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>;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* The erased registry row of a dynamic pair — same storage boundary
|
|
261
|
+
* as ToolSpec. App code: runs where handlers run (in-process locally,
|
|
262
|
+
* inside the isolate on workerd) and places credentials itself from
|
|
263
|
+
* ctx, like any handler. `discover` interrogates the tool source;
|
|
264
|
+
* `call` executes one discovered tool from the identity and detail
|
|
265
|
+
* discover emitted.
|
|
266
|
+
*/
|
|
267
|
+
export interface DynamicToolsSpec {
|
|
268
|
+
readonly description: string;
|
|
269
|
+
readonly requirements?: Readonly<Record<string, Requirement>>;
|
|
270
|
+
discover(args: never): Promise<readonly DiscoveredToolSpec[]>;
|
|
271
|
+
call(args: never): Promise<unknown>;
|
|
272
|
+
}
|
|
124
273
|
declare const DynamicKey: unique symbol;
|
|
125
|
-
/** The value
|
|
126
|
-
*
|
|
274
|
+
/** The value defineDynamicTools returns: an opaque handle a tool file
|
|
275
|
+
* default-exports. The spec is undefined when the definition was
|
|
127
276
|
* invalid (a problem was recorded instead). */
|
|
128
277
|
export interface DynamicToolSet {
|
|
129
|
-
readonly [DynamicKey]:
|
|
278
|
+
readonly [DynamicKey]: DynamicToolsSpec | undefined;
|
|
130
279
|
}
|
|
131
|
-
/**
|
|
132
|
-
export
|
|
133
|
-
|
|
280
|
+
/** Reads a dynamic spec from a tool file's default export. @internal */
|
|
281
|
+
export declare const dynamicSpecOf: (value: unknown) => DynamicToolsSpec | undefined;
|
|
282
|
+
/**
|
|
283
|
+
* The dynamic-tools primitive: an author-written discover/call pair
|
|
284
|
+
* whose tool list is interrogated at runtime, per connection, instead
|
|
285
|
+
* of authored one file at a time. The file's name becomes the address
|
|
286
|
+
* segment (appKey/<stem>/<toolName>); tools land in the catalog after
|
|
287
|
+
* discovery and never block a save. fromMCP/fromOpenAPI are shipped
|
|
288
|
+
* pairs over this same primitive.
|
|
289
|
+
*/
|
|
290
|
+
export declare const defineDynamicTools: <const Reqs extends Readonly<Record<string, Requirement>> | undefined = undefined>(decl: DynamicToolsDecl<Reqs>) => DynamicToolSet;
|
|
291
|
+
/** A registered dynamic tool set and its source attribution. @internal */
|
|
292
|
+
export interface DynamicRegistration {
|
|
293
|
+
readonly spec: DynamicToolsSpec;
|
|
134
294
|
readonly file: string;
|
|
135
295
|
}
|
|
136
296
|
/** A registered service declaration and its source attribution. @internal */
|
|
@@ -148,7 +308,7 @@ export interface Registry {
|
|
|
148
308
|
app: AppManifest | undefined;
|
|
149
309
|
readonly services: Map<string, RegisteredService>;
|
|
150
310
|
readonly tools: Map<string, RegisteredTool>;
|
|
151
|
-
readonly dynamic: Map<string,
|
|
311
|
+
readonly dynamic: Map<string, DynamicRegistration>;
|
|
152
312
|
readonly problems: Problem[];
|
|
153
313
|
readonly toolFiles: Map<string, {
|
|
154
314
|
definitions: number;
|
|
@@ -178,27 +338,84 @@ export declare const defineApp: (manifest: {
|
|
|
178
338
|
}) => AppManifest;
|
|
179
339
|
/** Any Standard Schema (zod preferred) or a raw JSON-schema object. */
|
|
180
340
|
export type SchemaLike = object;
|
|
341
|
+
/**
|
|
342
|
+
* The validated value a Standard Schema produces, recovered from its
|
|
343
|
+
* phantom types (zod 4 and effect Schema both publish them). A raw
|
|
344
|
+
* JSON-schema object carries no phantom, so its input is `unknown` —
|
|
345
|
+
* stated weakness, not silent weakness.
|
|
346
|
+
*/
|
|
347
|
+
export type InferSchema<S> = S extends {
|
|
348
|
+
readonly "~standard": {
|
|
349
|
+
readonly types?: infer T;
|
|
350
|
+
};
|
|
351
|
+
} ? NonNullable<T> extends {
|
|
352
|
+
readonly output: infer O;
|
|
353
|
+
} ? O : unknown : unknown;
|
|
354
|
+
/**
|
|
355
|
+
* The capability a handler receives for one connected requirement
|
|
356
|
+
* role: the grant's kind-discriminated material (narrowed by any
|
|
357
|
+
* auth-door pin) plus the spending fetch. Arity shapes the value —
|
|
358
|
+
* `.optional()` may be absent, `.array()` is every connected account.
|
|
359
|
+
*/
|
|
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;
|
|
361
|
+
/** One connected role entry: declared-auth material + capability fetch. */
|
|
362
|
+
export type HandlerRoleEntry<D extends ServiceDecl, G extends string | undefined> = ([G] extends [string] ? Extract<AuthRoleEntry<D>, {
|
|
363
|
+
grant: G;
|
|
364
|
+
}> : AuthRoleEntry<D>) & {
|
|
365
|
+
fetch(path: string, init?: RequestInit): Promise<Response>;
|
|
366
|
+
};
|
|
367
|
+
/** The ctx record a handler receives, keyed by requirement role. */
|
|
368
|
+
export type HandlerCtx<Reqs> = [Reqs] extends [Readonly<Record<string, Requirement>>] ? {
|
|
369
|
+
readonly [K in keyof Reqs]: HandlerRole<Reqs[K]>;
|
|
370
|
+
} : Readonly<Record<never, never>>;
|
|
181
371
|
export interface ToolAnnotations {
|
|
182
372
|
readonly readOnly?: boolean;
|
|
183
373
|
readonly destructive?: boolean;
|
|
184
374
|
/** Facts, not flow: shortfall surfaces as the scope challenge. */
|
|
185
375
|
readonly scopes?: readonly string[];
|
|
186
376
|
}
|
|
187
|
-
|
|
377
|
+
/**
|
|
378
|
+
* The generic authored shape: input/ctx/output are all inferred — the
|
|
379
|
+
* handler's input from the Standard Schema, ctx from the requirements
|
|
380
|
+
* record (grants, pins, arity), and the return type checked against
|
|
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 }) => ...`.
|
|
384
|
+
*/
|
|
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> {
|
|
188
386
|
/** Address segment. Single-tool files may omit it (filename). */
|
|
387
|
+
readonly name?: string;
|
|
388
|
+
readonly description: string;
|
|
389
|
+
readonly requirements?: Reqs;
|
|
390
|
+
readonly input: Input;
|
|
391
|
+
readonly output?: Output;
|
|
392
|
+
readonly annotations?: ToolAnnotations;
|
|
393
|
+
handler(args: {
|
|
394
|
+
readonly input: InferSchema<Input>;
|
|
395
|
+
readonly ctx: HandlerCtx<Reqs>;
|
|
396
|
+
}): Promise<Output extends SchemaLike ? InferSchema<Output> : unknown>;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* The erased registry row. Registration drops the generics — storage
|
|
400
|
+
* is untyped by design, the same boundary every schema library draws
|
|
401
|
+
* between its typed builders and its runtime representation. The
|
|
402
|
+
* runtime re-establishes safety by validating input before the
|
|
403
|
+
* handler runs and shaping ctx from resolved connections.
|
|
404
|
+
*/
|
|
405
|
+
export interface ToolSpec {
|
|
189
406
|
readonly name?: string;
|
|
190
407
|
readonly description: string;
|
|
191
408
|
readonly requirements?: Readonly<Record<string, Requirement>>;
|
|
192
409
|
readonly input: SchemaLike;
|
|
193
410
|
readonly output?: SchemaLike;
|
|
194
411
|
readonly annotations?: ToolAnnotations;
|
|
195
|
-
handler(
|
|
412
|
+
handler(args: never): Promise<unknown>;
|
|
196
413
|
}
|
|
197
414
|
declare const ToolKey: unique symbol;
|
|
198
415
|
export interface Tool {
|
|
199
416
|
readonly [ToolKey]: ToolSpec;
|
|
200
417
|
}
|
|
201
|
-
export declare const defineTool: (
|
|
418
|
+
export declare const defineTool: <const Input extends SchemaLike, const Reqs extends Readonly<Record<string, Requirement>> | undefined = undefined, const Output extends SchemaLike | undefined = undefined>(decl: ToolDecl<Input, Reqs, Output>) => Tool;
|
|
202
419
|
export declare const toolSpecOf: (value: unknown) => ToolSpec | undefined;
|
|
203
420
|
/** A tool file's default export. */
|
|
204
421
|
export type ToolExport = Tool | ReadonlyArray<Tool>;
|
package/dist/authoring/index.js
CHANGED
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__enterFile,
|
|
3
3
|
__leaveFile,
|
|
4
|
+
basic,
|
|
5
|
+
bearer,
|
|
4
6
|
defineApp,
|
|
7
|
+
defineDynamicTools,
|
|
5
8
|
defineTool,
|
|
9
|
+
dynamicSpecOf,
|
|
6
10
|
newRegistry,
|
|
7
11
|
oauth,
|
|
12
|
+
recordAuthoringProblem,
|
|
8
13
|
requirementSpecOf,
|
|
9
14
|
secrets,
|
|
10
15
|
service,
|
|
11
16
|
setActiveRegistry,
|
|
12
17
|
toolSpecOf
|
|
13
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-535V5K64.js";
|
|
14
19
|
export {
|
|
15
20
|
__enterFile,
|
|
16
21
|
__leaveFile,
|
|
22
|
+
basic,
|
|
23
|
+
bearer,
|
|
17
24
|
defineApp,
|
|
25
|
+
defineDynamicTools,
|
|
18
26
|
defineTool,
|
|
27
|
+
dynamicSpecOf,
|
|
19
28
|
newRegistry,
|
|
20
29
|
oauth,
|
|
30
|
+
recordAuthoringProblem,
|
|
21
31
|
requirementSpecOf,
|
|
22
32
|
secrets,
|
|
23
33
|
service,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// src/runtime/index.ts
|
|
2
|
-
var failure = (reason) => ({
|
|
2
|
+
var failure = (kind, reason) => ({
|
|
3
|
+
ok: false,
|
|
4
|
+
kind,
|
|
5
|
+
reason
|
|
6
|
+
});
|
|
3
7
|
var errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
4
8
|
var isJsonValue = (value) => {
|
|
5
9
|
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
@@ -48,6 +52,10 @@ var compileInputSchema = (input, where) => {
|
|
|
48
52
|
};
|
|
49
53
|
var requirementKey = Symbol.for("executor/apps/requirement");
|
|
50
54
|
var requirementSpecOf = (value) => typeof value === "object" && value !== null && requirementKey in value ? value[requirementKey] : void 0;
|
|
55
|
+
var segmentOf = (file) => {
|
|
56
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
57
|
+
return name.endsWith(".ts") ? name.slice(0, -3) : name;
|
|
58
|
+
};
|
|
51
59
|
var canonicalJson = (value) => {
|
|
52
60
|
const sortKeys = (node) => {
|
|
53
61
|
if (Array.isArray(node)) return node.map(sortKeys);
|
|
@@ -100,6 +108,8 @@ var collectRegistry = (registry) => {
|
|
|
100
108
|
for (const [file, summary] of registry.toolFiles) {
|
|
101
109
|
if (summary.definitions > 1 && summary.hasUnnamedTool)
|
|
102
110
|
problems.push(`${file}: multi-tool files must name every tool`);
|
|
111
|
+
if (registry.dynamic.has(file))
|
|
112
|
+
problems.push(`${file}: a file claims a dynamic tool set or defines tools, never both`);
|
|
103
113
|
}
|
|
104
114
|
const tools = [];
|
|
105
115
|
if (app !== void 0) {
|
|
@@ -128,13 +138,43 @@ var collectRegistry = (registry) => {
|
|
|
128
138
|
});
|
|
129
139
|
}
|
|
130
140
|
}
|
|
141
|
+
const dynamic = [];
|
|
142
|
+
const seenSegments = /* @__PURE__ */ new Map();
|
|
143
|
+
for (const [file, registered] of registry.dynamic) {
|
|
144
|
+
const segment = segmentOf(file);
|
|
145
|
+
const where = `${file}: dynamic tool set '${segment}'`;
|
|
146
|
+
const existing = seenSegments.get(segment);
|
|
147
|
+
if (existing !== void 0) {
|
|
148
|
+
problems.push(`${where}: segment already claimed by ${existing}`);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
seenSegments.set(segment, file);
|
|
152
|
+
const requires = Object.entries(registered.spec.requirements ?? {}).flatMap(([role, requirement]) => {
|
|
153
|
+
const collected = collectRequirement(role, requirement, where);
|
|
154
|
+
return collected === void 0 ? [] : [collected];
|
|
155
|
+
}).sort((left, right) => left.role.localeCompare(right.role));
|
|
156
|
+
dynamic.push({
|
|
157
|
+
segment,
|
|
158
|
+
description: registered.spec.description,
|
|
159
|
+
requires,
|
|
160
|
+
file
|
|
161
|
+
});
|
|
162
|
+
}
|
|
131
163
|
if (problems.length > 0 || app === void 0) return { problems };
|
|
132
164
|
const services = [...claims.values()].sort(
|
|
133
165
|
(left, right) => left.origin.localeCompare(right.origin)
|
|
134
166
|
);
|
|
135
167
|
const sortedTools = [...tools].sort((left, right) => left.address.localeCompare(right.address));
|
|
168
|
+
const sortedDynamic = [...dynamic].sort(
|
|
169
|
+
(left, right) => left.segment.localeCompare(right.segment)
|
|
170
|
+
);
|
|
136
171
|
const appResult = { key: app.key, label: app.label, description: app.description };
|
|
137
|
-
const manifest = {
|
|
172
|
+
const manifest = {
|
|
173
|
+
app: appResult,
|
|
174
|
+
services,
|
|
175
|
+
tools: sortedTools,
|
|
176
|
+
dynamic: sortedDynamic
|
|
177
|
+
};
|
|
138
178
|
return { problems: [], manifest, canonical: canonicalJson(manifest) };
|
|
139
179
|
};
|
|
140
180
|
var digestOf = async (canonical) => {
|
|
@@ -154,8 +194,8 @@ var manifestOf = async (registry) => {
|
|
|
154
194
|
};
|
|
155
195
|
};
|
|
156
196
|
var standardValidateOf = (input) => "~standard" in input && typeof input["~standard"].validate === "function" ? input["~standard"].validate : void 0;
|
|
157
|
-
var parseInput = async (
|
|
158
|
-
const validate = standardValidateOf(
|
|
197
|
+
var parseInput = async (schema, input) => {
|
|
198
|
+
const validate = standardValidateOf(schema);
|
|
159
199
|
if (validate === void 0)
|
|
160
200
|
return { ok: false, reason: "input schema does not implement Standard Schema validate" };
|
|
161
201
|
try {
|
|
@@ -170,21 +210,102 @@ var parseInput = async (spec, input) => {
|
|
|
170
210
|
return { ok: false, reason: `input validation threw (${errorMessage(error)})` };
|
|
171
211
|
}
|
|
172
212
|
};
|
|
173
|
-
var
|
|
174
|
-
const
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
213
|
+
var splitDynamicAddress = (address) => {
|
|
214
|
+
const slash = address.indexOf("/");
|
|
215
|
+
if (slash <= 0 || slash === address.length - 1) return void 0;
|
|
216
|
+
return { segment: address.slice(0, slash), name: address.slice(slash + 1) };
|
|
217
|
+
};
|
|
218
|
+
var dynamicFor = (registry, segment) => {
|
|
219
|
+
for (const [file, registered] of registry.dynamic)
|
|
220
|
+
if (segmentOf(file) === segment) return registered.spec;
|
|
221
|
+
return void 0;
|
|
222
|
+
};
|
|
223
|
+
var discoveredSpecs = /* @__PURE__ */ new WeakMap();
|
|
224
|
+
var callTool = async (registry, address, input, ctx, detail) => {
|
|
225
|
+
const dynamic = splitDynamicAddress(address);
|
|
226
|
+
if (dynamic === void 0) {
|
|
227
|
+
if (detail !== void 0)
|
|
228
|
+
return failure("handler", "a static tool takes no discovery detail");
|
|
229
|
+
const registered = registry.tools.get(address);
|
|
230
|
+
if (registered === void 0) return failure("handler", "unknown tool");
|
|
231
|
+
const parsed2 = await parseInput(registered.spec.input, input);
|
|
232
|
+
if (!parsed2.ok) return failure("input", parsed2.reason);
|
|
233
|
+
try {
|
|
234
|
+
const handler = registered.spec.handler;
|
|
235
|
+
return { ok: true, value: await handler({ input: parsed2.value, ctx }) };
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return failure("handler", `handler failed (${errorMessage(error)})`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (detail === void 0)
|
|
241
|
+
return failure("handler", "a dynamic tool requires discovery detail");
|
|
242
|
+
const pair = dynamicFor(registry, dynamic.segment);
|
|
243
|
+
if (pair === void 0)
|
|
244
|
+
return failure("handler", `unknown dynamic tool set '${dynamic.segment}'`);
|
|
245
|
+
const discovered = discoveredSpecs.get(registry)?.get(dynamic.segment)?.get(dynamic.name);
|
|
246
|
+
if (discovered === void 0)
|
|
247
|
+
return failure(
|
|
248
|
+
"input",
|
|
249
|
+
`dynamic tool '${dynamic.name}' has no compiled discovery descriptor`
|
|
250
|
+
);
|
|
251
|
+
const parsed = await parseInput(discovered.input, input);
|
|
252
|
+
if (!parsed.ok) return failure("input", parsed.reason);
|
|
178
253
|
try {
|
|
179
|
-
const
|
|
180
|
-
return {
|
|
254
|
+
const call = pair.call;
|
|
255
|
+
return {
|
|
256
|
+
ok: true,
|
|
257
|
+
value: await call({
|
|
258
|
+
tool: { name: dynamic.name, detail },
|
|
259
|
+
input: parsed.value,
|
|
260
|
+
ctx
|
|
261
|
+
})
|
|
262
|
+
};
|
|
263
|
+
} catch (error) {
|
|
264
|
+
return failure("handler", `dynamic call failed (${errorMessage(error)})`);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
var discoverTool = async (registry, segment, ctx) => {
|
|
268
|
+
const pair = dynamicFor(registry, segment);
|
|
269
|
+
if (pair === void 0)
|
|
270
|
+
return { ok: false, reason: `unknown dynamic tool set '${segment}'` };
|
|
271
|
+
discoveredSpecs.get(registry)?.delete(segment);
|
|
272
|
+
let emitted;
|
|
273
|
+
try {
|
|
274
|
+
const discover = pair.discover;
|
|
275
|
+
emitted = await discover({ ctx });
|
|
181
276
|
} catch (error) {
|
|
182
|
-
return
|
|
277
|
+
return { ok: false, reason: `discover failed (${errorMessage(error)})` };
|
|
278
|
+
}
|
|
279
|
+
const tools = [];
|
|
280
|
+
const live = /* @__PURE__ */ new Map();
|
|
281
|
+
for (const tool of emitted) {
|
|
282
|
+
if (live.has(tool.name))
|
|
283
|
+
return { ok: false, reason: `discover emitted duplicate tool '${tool.name}'` };
|
|
284
|
+
const compiled = compileInputSchema(tool.input, `${segment}/${tool.name}`);
|
|
285
|
+
if ("problem" in compiled) return { ok: false, reason: compiled.problem };
|
|
286
|
+
live.set(tool.name, tool);
|
|
287
|
+
tools.push({
|
|
288
|
+
segment,
|
|
289
|
+
name: tool.name,
|
|
290
|
+
descriptor: {
|
|
291
|
+
description: tool.description,
|
|
292
|
+
inputSchema: compiled.value,
|
|
293
|
+
annotations: {
|
|
294
|
+
...tool.annotations?.readOnly === void 0 ? {} : { readOnly: tool.annotations.readOnly },
|
|
295
|
+
...tool.annotations?.destructive === void 0 ? {} : { destructive: tool.annotations.destructive }
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
detail: tool.detail ?? null
|
|
299
|
+
});
|
|
183
300
|
}
|
|
301
|
+
const registryDiscoveries = discoveredSpecs.get(registry) ?? /* @__PURE__ */ new Map();
|
|
302
|
+
registryDiscoveries.set(segment, live);
|
|
303
|
+
discoveredSpecs.set(registry, registryDiscoveries);
|
|
304
|
+
return { ok: true, value: tools.sort((left, right) => left.name.localeCompare(right.name)) };
|
|
184
305
|
};
|
|
185
|
-
var discoverTool = async (_registry, _name, _binding) => failure("no dynamic tools yet");
|
|
186
306
|
|
|
187
307
|
export {
|
|
308
|
+
compileInputSchema,
|
|
188
309
|
manifestOf,
|
|
189
310
|
callTool,
|
|
190
311
|
discoverTool
|
|
@@ -5,7 +5,13 @@ var CallError = class extends Data.TaggedError("CallError") {
|
|
|
5
5
|
return `${this.target}: ${this.reason}`;
|
|
6
6
|
}
|
|
7
7
|
};
|
|
8
|
+
var DiscoverError = class extends Data.TaggedError("DiscoverError") {
|
|
9
|
+
get message() {
|
|
10
|
+
return `${this.segment}: ${this.reason}`;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
8
13
|
|
|
9
14
|
export {
|
|
10
|
-
CallError
|
|
15
|
+
CallError,
|
|
16
|
+
DiscoverError
|
|
11
17
|
};
|