@acid-cat/core-ts 0.1.0 → 0.2.0
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 +24 -0
- package/dist/app.js +62 -0
- package/dist/cli.js +163 -0
- package/dist/context.d.ts +18 -0
- package/dist/context.js +20 -0
- package/dist/http.d.ts +5 -3
- package/dist/http.js +73 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +8 -610
- package/dist/mcp.d.ts +5 -3
- package/dist/mcp.js +155 -0
- package/dist/session.js +139 -0
- package/dist/webmcp.js +130 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -60,6 +60,30 @@ Standard Schema does not convert to JSON Schema. Give the converter of your sche
|
|
|
60
60
|
| `cliMain(app, options)`, `runCli(…)` | [CLI](../../spec/bindings/cli.md) | The command line of the process, or an exit code |
|
|
61
61
|
| `installWebMcp(session)` | [WebMCP](../../spec/bindings/webmcp.md) | Tools in the page, with a status |
|
|
62
62
|
|
|
63
|
+
### Context for each request
|
|
64
|
+
|
|
65
|
+
`mcpHandler` and `httpHandler` take `context` or `createContext`. With `context`, all requests share one context. With `createContext(request)`, the handler makes a new context for each request that passes the screening. Use it when a request must not use objects that an earlier request made, for example on Cloudflare Workers, or when a stateful service must not be shared between callers. When the context has `Symbol.asyncDispose` or `Symbol.dispose`, the handler calls it after the response:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const handler = httpHandler(aircon, {
|
|
69
|
+
allowedOrigins: [],
|
|
70
|
+
toJsonSchema,
|
|
71
|
+
createContext: () => ({ unit: connectUnit(env), [Symbol.asyncDispose]: () => disconnectUnit() }),
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Context for each request
|
|
76
|
+
|
|
77
|
+
`mcpHandler` and `httpHandler` take `context` or `createContext`. With `context`, all requests share one context. With `createContext(request)`, the handler makes a new context for each request that passes the screening. Use it when a request must not use objects that an earlier request made, for example on Cloudflare Workers, or when a stateful service must not be shared between callers. When the context has `Symbol.asyncDispose` or `Symbol.dispose`, the handler calls it after the response:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const handler = httpHandler(aircon, {
|
|
81
|
+
allowedOrigins: [],
|
|
82
|
+
toJsonSchema,
|
|
83
|
+
createContext: () => ({ unit: connectUnit(env), [Symbol.asyncDispose]: () => disconnectUnit() }),
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
63
87
|
## Examples and tests
|
|
64
88
|
|
|
65
89
|
- [`examples/aircon.ts`](examples/aircon.ts) is the air conditioner of [`examples/aircon.pkl`](../../examples/aircon.pkl), with a simulated unit. Its catalog agrees with the Pkl catalog.
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { canonicalText } from "@acid-cat/catalog";
|
|
2
|
+
//#region src/app.ts
|
|
3
|
+
/**
|
|
4
|
+
* Capabilities and applications in plain TypeScript. An application writes its own catalog.
|
|
5
|
+
* See spec/catalog.md.
|
|
6
|
+
*/
|
|
7
|
+
/** Declare a capability. `Context` is the type of the services that `run` receives. */
|
|
8
|
+
const defineCapability = (capability) => capability;
|
|
9
|
+
/**
|
|
10
|
+
* Declare capabilities for one context type. The other types come from the schemas:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const capability = capabilitiesFor<{ unit: Unit }>();
|
|
14
|
+
* capability({ id: "aircon.power.set", input: z.object({ on: z.boolean() }), run: ({ on }, { context }) => … });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
const capabilitiesFor = () => (capability) => capability;
|
|
18
|
+
/** Declare an application. */
|
|
19
|
+
const defineApp = (app) => app;
|
|
20
|
+
/** The catalog of an application, as a JSON document. */
|
|
21
|
+
function catalogOf(app, options) {
|
|
22
|
+
const types = {};
|
|
23
|
+
const toJson = (schema, where) => {
|
|
24
|
+
const { $schema: _, $defs, ...json } = options.toJsonSchema(schema);
|
|
25
|
+
for (const [name, definition] of Object.entries($defs ?? {})) {
|
|
26
|
+
const existing = types[name];
|
|
27
|
+
if (existing !== void 0 && JSON.stringify(existing) !== JSON.stringify(definition)) throw new Error(`${where}: two different schemas have the name ${name}. Give each one its own name.`);
|
|
28
|
+
types[name] = definition;
|
|
29
|
+
}
|
|
30
|
+
return json;
|
|
31
|
+
};
|
|
32
|
+
const capabilities = {};
|
|
33
|
+
for (const capability of app.capabilities) {
|
|
34
|
+
if (capability.id in capabilities) throw new Error(`The capability ${capability.id} is declared two times.`);
|
|
35
|
+
const failures = Object.fromEntries(Object.entries(capability.failures ?? {}).map(([tag, schema]) => [tag, toJson(schema, capability.id)]));
|
|
36
|
+
capabilities[capability.id] = {
|
|
37
|
+
description: capability.description,
|
|
38
|
+
audience: capability.audience,
|
|
39
|
+
...capability.input === void 0 ? {} : { input: toJson(capability.input, capability.id) },
|
|
40
|
+
...capability.output === void 0 ? {} : { output: toJson(capability.output, capability.id) },
|
|
41
|
+
...Object.keys(failures).length === 0 ? {} : { failures },
|
|
42
|
+
...capability.readOnly === true ? { readOnly: true } : {},
|
|
43
|
+
...capability.state === void 0 ? {} : { state: toJson(capability.state.schema, capability.id) }
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
acidcat: "0.1",
|
|
48
|
+
...app.title === void 0 ? {} : { title: app.title },
|
|
49
|
+
...app.description === void 0 ? {} : { description: app.description },
|
|
50
|
+
targets: Object.fromEntries(Object.entries(app.targets).map(([name, target]) => [name, {
|
|
51
|
+
bindings: target.bindings,
|
|
52
|
+
...target.description === void 0 ? {} : { description: target.description },
|
|
53
|
+
...target.options === void 0 ? {} : { options: target.options }
|
|
54
|
+
}])),
|
|
55
|
+
...Object.keys(types).length === 0 ? {} : { $defs: types },
|
|
56
|
+
capabilities
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** The catalog of an application, as text in canonical form. */
|
|
60
|
+
const renderCatalog = (app, options) => canonicalText(catalogOf(app, options));
|
|
61
|
+
//#endregion
|
|
62
|
+
export { capabilitiesFor, catalogOf, defineApp, defineCapability, renderCatalog };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createSession } from "./session.js";
|
|
2
|
+
import { CLI_EXIT_CODES, REFERENCE, bundle, cliExitCode, commandPath, isCallFailure } from "@acid-cat/spec-ts";
|
|
3
|
+
//#region src/cli.ts
|
|
4
|
+
/**
|
|
5
|
+
* The CLI binding (spec/bindings/cli.md): a command line made from the catalog.
|
|
6
|
+
*/
|
|
7
|
+
var UsageError = class extends Error {
|
|
8
|
+
exitCode;
|
|
9
|
+
constructor(message, exitCode) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.exitCode = exitCode;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
/** Run the command line as the main program of the process. An interrupt (`SIGINT`) cancels the call. */
|
|
15
|
+
function main(app, options) {
|
|
16
|
+
const interrupt = new AbortController();
|
|
17
|
+
process.once("SIGINT", () => interrupt.abort());
|
|
18
|
+
runCli(app, options, process.argv.slice(2), {
|
|
19
|
+
stdout: (text) => process.stdout.write(`${text}\n`),
|
|
20
|
+
stderr: (text) => process.stderr.write(`${text}\n`),
|
|
21
|
+
signal: interrupt.signal
|
|
22
|
+
}).then((code) => {
|
|
23
|
+
process.exitCode = code;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** Run the command line with arguments. The result is the exit code (spec/bindings/cli.md#outcomes). */
|
|
27
|
+
async function runCli(app, options, args, io) {
|
|
28
|
+
const session = createSession(app, {
|
|
29
|
+
target: options.target ?? "cli",
|
|
30
|
+
context: options.context,
|
|
31
|
+
toJsonSchema: options.toJsonSchema
|
|
32
|
+
});
|
|
33
|
+
const types = session.view.$defs ?? {};
|
|
34
|
+
const commands = Object.keys(session.view.capabilities).map((id) => ({
|
|
35
|
+
id,
|
|
36
|
+
path: commandPath(id)
|
|
37
|
+
}));
|
|
38
|
+
for (const { path } of commands) if (path[0] === "call" || path[0] === "list") throw new Error(`The command ${path.join(" ")} has the same name as a command of the command line.`);
|
|
39
|
+
const print = (outcome) => {
|
|
40
|
+
if (isCallFailure(outcome)) io.stderr(JSON.stringify({
|
|
41
|
+
kind: outcome.kind,
|
|
42
|
+
message: outcome.message
|
|
43
|
+
}));
|
|
44
|
+
else io.stdout(JSON.stringify(outcome.ok ? outcome.value : outcome.error, null, 2));
|
|
45
|
+
return cliExitCode(outcome);
|
|
46
|
+
};
|
|
47
|
+
try {
|
|
48
|
+
const [first, ...rest] = args;
|
|
49
|
+
if (first === void 0 || first === "--help" || first === "-h") {
|
|
50
|
+
io.stdout(help(options.name, commands.map(({ path }) => path.join(" "))));
|
|
51
|
+
return CLI_EXIT_CODES.value;
|
|
52
|
+
}
|
|
53
|
+
if (first === "--version" || first === "-v") {
|
|
54
|
+
io.stdout(options.version);
|
|
55
|
+
return CLI_EXIT_CODES.value;
|
|
56
|
+
}
|
|
57
|
+
if (first === "list") {
|
|
58
|
+
io.stdout(JSON.stringify([...session.handles.values()].map((handle) => ({
|
|
59
|
+
id: handle.id,
|
|
60
|
+
description: handle.description,
|
|
61
|
+
...handle.availability.getSnapshot(),
|
|
62
|
+
usage: `${options.name} ${commandPath(handle.id).join(" ")}`
|
|
63
|
+
})), null, 2));
|
|
64
|
+
return CLI_EXIT_CODES.value;
|
|
65
|
+
}
|
|
66
|
+
if (first === "call") {
|
|
67
|
+
const [id, text = "{}"] = rest;
|
|
68
|
+
if (id === void 0) throw new UsageError("Give the id of a capability.", CLI_EXIT_CODES["invalid-input"]);
|
|
69
|
+
let input;
|
|
70
|
+
try {
|
|
71
|
+
input = JSON.parse(text);
|
|
72
|
+
} catch {
|
|
73
|
+
return print({
|
|
74
|
+
kind: "invalid-input",
|
|
75
|
+
message: "The input is not JSON."
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return print(await session.invoke(id, input, { signal: io.signal }));
|
|
79
|
+
}
|
|
80
|
+
const match = commands.filter(({ path }) => path.every((word, index) => args[index] === word)).sort((a, b) => b.path.length - a.path.length)[0];
|
|
81
|
+
if (match === void 0) throw new UsageError(`There is no command ${args.filter((arg) => !arg.startsWith("-")).join(" ")}.`, CLI_EXIT_CODES["unknown-capability"]);
|
|
82
|
+
const capability = session.view.capabilities[match.id];
|
|
83
|
+
const schema = bundle(capability.input ?? { type: "object" }, types);
|
|
84
|
+
const flagArgs = args.slice(match.path.length);
|
|
85
|
+
if (flagArgs.includes("--help") || flagArgs.includes("-h")) {
|
|
86
|
+
io.stdout(usage(options.name, match.path, capability.description, schema));
|
|
87
|
+
return CLI_EXIT_CODES.value;
|
|
88
|
+
}
|
|
89
|
+
return print(await session.invoke(match.id, parseFlags(flagArgs, schema), { signal: io.signal }));
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (!(error instanceof UsageError)) throw error;
|
|
92
|
+
io.stderr(error.message);
|
|
93
|
+
return error.exitCode;
|
|
94
|
+
} finally {
|
|
95
|
+
await session.dispose();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Make the input from the flags (spec/bindings/cli.md#commands). */
|
|
99
|
+
function parseFlags(args, schema) {
|
|
100
|
+
const properties = schema.properties ?? {};
|
|
101
|
+
const definitions = schema.$defs ?? {};
|
|
102
|
+
const input = {};
|
|
103
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
104
|
+
const arg = args[index];
|
|
105
|
+
if (!arg.startsWith("--")) throw new UsageError(`Unexpected argument ${arg}.`, CLI_EXIT_CODES["invalid-input"]);
|
|
106
|
+
const [name, inline] = arg.slice(2).split(/=(.*)/s, 2);
|
|
107
|
+
const property = properties[name];
|
|
108
|
+
if (property === void 0) throw new UsageError(`Unknown flag --${name}.`, CLI_EXIT_CODES["invalid-input"]);
|
|
109
|
+
const type = resolve(property, definitions);
|
|
110
|
+
if (type.type === "boolean") {
|
|
111
|
+
input[name] = inline === void 0 ? true : inline === "true";
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const text = inline ?? args[index += 1];
|
|
115
|
+
if (text === void 0) throw new UsageError(`The flag --${name} needs a value.`, CLI_EXIT_CODES["invalid-input"]);
|
|
116
|
+
input[name] = valueOf(name, text, type);
|
|
117
|
+
}
|
|
118
|
+
for (const name of schema.required ?? []) if (!(name in input)) throw new UsageError(`The flag --${name} is required.`, CLI_EXIT_CODES["invalid-input"]);
|
|
119
|
+
return input;
|
|
120
|
+
}
|
|
121
|
+
function valueOf(name, text, type) {
|
|
122
|
+
if (Array.isArray(type.enum) && type.enum.every((value) => typeof value === "string")) {
|
|
123
|
+
if (!type.enum.includes(text)) throw new UsageError(`The flag --${name} must be one of: ${type.enum.join(", ")}.`, CLI_EXIT_CODES["invalid-input"]);
|
|
124
|
+
return text;
|
|
125
|
+
}
|
|
126
|
+
if (type.type === "integer" || type.type === "number") {
|
|
127
|
+
const number = Number(text);
|
|
128
|
+
if (text.trim() === "" || !Number.isFinite(number) || type.type === "integer" && !Number.isInteger(number)) throw new UsageError(`The flag --${name} must be a ${type.type}.`, CLI_EXIT_CODES["invalid-input"]);
|
|
129
|
+
return number;
|
|
130
|
+
}
|
|
131
|
+
if (type.type === "string") return text;
|
|
132
|
+
try {
|
|
133
|
+
return JSON.parse(text);
|
|
134
|
+
} catch {
|
|
135
|
+
throw new UsageError(`The flag --${name} must be JSON.`, CLI_EXIT_CODES["invalid-input"]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function resolve(property, definitions) {
|
|
139
|
+
const name = typeof property.$ref === "string" ? REFERENCE.exec(property.$ref)?.[1] : void 0;
|
|
140
|
+
return name === void 0 ? property : definitions[name] ?? property;
|
|
141
|
+
}
|
|
142
|
+
function help(name, paths) {
|
|
143
|
+
return [
|
|
144
|
+
`Usage: ${name} <command> [flags]`,
|
|
145
|
+
"",
|
|
146
|
+
"Commands:",
|
|
147
|
+
...paths.map((path) => ` ${path}`),
|
|
148
|
+
" call <id> [input]",
|
|
149
|
+
" list"
|
|
150
|
+
].join("\n");
|
|
151
|
+
}
|
|
152
|
+
function usage(name, path, description, schema) {
|
|
153
|
+
const properties = Object.keys(schema.properties ?? {});
|
|
154
|
+
const required = new Set(schema.required ?? []);
|
|
155
|
+
const flags = properties.map((property) => required.has(property) ? `--${property} <value>` : `[--${property} <value>]`);
|
|
156
|
+
return [
|
|
157
|
+
`Usage: ${name} ${[...path, ...flags].join(" ")}`,
|
|
158
|
+
"",
|
|
159
|
+
description
|
|
160
|
+
].join("\n");
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
export { main, runCli };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The context of the web handlers: one context that all requests share, or a new context for each request.
|
|
3
|
+
*/
|
|
4
|
+
export type ContextOption<Context> = {
|
|
5
|
+
/** The context of all requests. */
|
|
6
|
+
readonly context: Context;
|
|
7
|
+
readonly createContext?: undefined;
|
|
8
|
+
} | {
|
|
9
|
+
/**
|
|
10
|
+
* Make the context of one request. The handler calls it for each request that passes the screening. When the
|
|
11
|
+
* context has `Symbol.asyncDispose` or `Symbol.dispose`, the handler calls it after the response, as a `using`
|
|
12
|
+
* declaration does.
|
|
13
|
+
*/
|
|
14
|
+
readonly createContext: (request: Request) => Context | Promise<Context>;
|
|
15
|
+
readonly context?: undefined;
|
|
16
|
+
};
|
|
17
|
+
/** Give the context of one request to `use`, and release it afterwards if the handler made it. */
|
|
18
|
+
export declare function withContext<Context, A>(option: ContextOption<Context>, request: Request, use: (context: Context) => Promise<A>): Promise<A>;
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/context.ts
|
|
2
|
+
/** Give the context of one request to `use`, and release it afterwards if the handler made it. */
|
|
3
|
+
async function withContext(option, request, use) {
|
|
4
|
+
if (option.createContext === void 0) return use(option.context);
|
|
5
|
+
const context = await option.createContext(request);
|
|
6
|
+
try {
|
|
7
|
+
return await use(context);
|
|
8
|
+
} finally {
|
|
9
|
+
await release(context);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Call `Symbol.asyncDispose` or `Symbol.dispose` of a value, if it has one. */
|
|
13
|
+
async function release(value) {
|
|
14
|
+
if (typeof value !== "object" || value === null) return;
|
|
15
|
+
const disposable = value;
|
|
16
|
+
if (typeof disposable[Symbol.asyncDispose] === "function") await disposable[Symbol.asyncDispose]();
|
|
17
|
+
else if (typeof disposable[Symbol.dispose] === "function") disposable[Symbol.dispose]();
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { withContext };
|
package/dist/http.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { App, ToJsonSchema } from "./app.js";
|
|
2
|
-
|
|
2
|
+
import type { ContextOption } from "./context.js";
|
|
3
|
+
/** The options of `httpHandler`. Give `context` to share one context, or `createContext` to make one for each request. */
|
|
4
|
+
export type HttpOptions<Context> = HttpSettings & ContextOption<Context>;
|
|
5
|
+
export interface HttpSettings {
|
|
3
6
|
/** The origins that can call the API. A request with no `Origin` header is accepted. */
|
|
4
7
|
readonly allowedOrigins: readonly string[];
|
|
5
|
-
readonly context: Context;
|
|
6
8
|
readonly toJsonSchema: ToJsonSchema;
|
|
7
9
|
/** The target that the API serves. The default is `"api"`. */
|
|
8
10
|
readonly target?: string | undefined;
|
|
@@ -13,5 +15,5 @@ export interface HttpOptions<Context> {
|
|
|
13
15
|
/** The maximum size of a request body, in bytes. The default is 1 MiB. */
|
|
14
16
|
readonly maximumBytes?: number | undefined;
|
|
15
17
|
}
|
|
16
|
-
/** A web request handler for the API. Each request has its own session. */
|
|
18
|
+
/** A web request handler for the API. Each request has its own session, and with `createContext` its own context. */
|
|
17
19
|
export declare function httpHandler<Context>(app: App<Context>, options: HttpOptions<Context>): (request: Request) => Promise<Response>;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createSession } from "./session.js";
|
|
2
|
+
import { withContext } from "./context.js";
|
|
3
|
+
import { callFailure, httpStatus, isCallFailure, readBounded, screenRequest } from "@acid-cat/spec-ts";
|
|
4
|
+
//#region src/http.ts
|
|
5
|
+
/**
|
|
6
|
+
* The HTTP binding (spec/bindings/http.md): JSON through HTTP.
|
|
7
|
+
*/
|
|
8
|
+
/** A web request handler for the API. Each request has its own session, and with `createContext` its own context. */
|
|
9
|
+
function httpHandler(app, options) {
|
|
10
|
+
const maximumBytes = options.maximumBytes ?? 1048576;
|
|
11
|
+
return async (request) => {
|
|
12
|
+
const screened = screenRequest(request, {
|
|
13
|
+
methods: ["GET", "POST"],
|
|
14
|
+
allowedOrigins: options.allowedOrigins,
|
|
15
|
+
mediaType: "application/json",
|
|
16
|
+
maximumBytes
|
|
17
|
+
});
|
|
18
|
+
if (screened !== void 0) return rejection(screened);
|
|
19
|
+
const path = new URL(request.url).pathname.slice((options.base ?? "").length);
|
|
20
|
+
return withContext(options, request, async (context) => {
|
|
21
|
+
const session = createSession(app, {
|
|
22
|
+
target: options.target ?? "api",
|
|
23
|
+
context,
|
|
24
|
+
toJsonSchema: options.toJsonSchema
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
if (request.method === "GET" && path === "/capabilities") return json(200, [...session.handles.values()].map((handle) => ({
|
|
28
|
+
id: handle.id,
|
|
29
|
+
...handle.availability.getSnapshot()
|
|
30
|
+
})));
|
|
31
|
+
if (request.method === "GET" && path === "/catalog" && options.serveCatalog !== false) return json(200, session.view);
|
|
32
|
+
const id = request.method === "POST" && path.startsWith("/capabilities/") ? path.slice(14) : void 0;
|
|
33
|
+
if (id === void 0) return json(404, callFailure("unknown-capability", "There is no such route."));
|
|
34
|
+
const body = await readBounded(request, maximumBytes);
|
|
35
|
+
if ("rejection" in body) return rejection(body.rejection);
|
|
36
|
+
let input;
|
|
37
|
+
try {
|
|
38
|
+
input = body.text === "" ? {} : JSON.parse(body.text);
|
|
39
|
+
} catch {
|
|
40
|
+
return json(422, callFailure("invalid-input", "The request body is not JSON."));
|
|
41
|
+
}
|
|
42
|
+
const outcome = await session.invoke(decodeURIComponent(id), input, { signal: request.signal });
|
|
43
|
+
return json(httpStatus(outcome), isCallFailure(outcome) ? {
|
|
44
|
+
kind: outcome.kind,
|
|
45
|
+
message: outcome.message
|
|
46
|
+
} : outcome);
|
|
47
|
+
} finally {
|
|
48
|
+
await session.dispose();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const json = (status, body) => new Response(JSON.stringify(body), {
|
|
54
|
+
status,
|
|
55
|
+
headers: { "Content-Type": "application/json" }
|
|
56
|
+
});
|
|
57
|
+
const statuses = {
|
|
58
|
+
method: 405,
|
|
59
|
+
origin: 403,
|
|
60
|
+
"media-type": 415,
|
|
61
|
+
"too-large": 413,
|
|
62
|
+
"not-utf8": 400
|
|
63
|
+
};
|
|
64
|
+
function rejection(reason) {
|
|
65
|
+
const headers = new Headers({ "Cache-Control": "no-store" });
|
|
66
|
+
if (reason === "method") headers.set("Allow", "GET, POST");
|
|
67
|
+
return new Response(null, {
|
|
68
|
+
status: statuses[reason],
|
|
69
|
+
headers
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { httpHandler };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,9 +3,10 @@ export type { AnyCapability, App, Capability, Observable, Target, ToJsonSchema }
|
|
|
3
3
|
export { createSession } from "./session.js";
|
|
4
4
|
export type { Handle, Session, SessionOptions } from "./session.js";
|
|
5
5
|
export { mcpHandler } from "./mcp.js";
|
|
6
|
-
export type { McpOptions } from "./mcp.js";
|
|
6
|
+
export type { McpOptions, McpSettings } from "./mcp.js";
|
|
7
7
|
export { httpHandler } from "./http.js";
|
|
8
|
-
export type { HttpOptions } from "./http.js";
|
|
8
|
+
export type { HttpOptions, HttpSettings } from "./http.js";
|
|
9
|
+
export type { ContextOption } from "./context.js";
|
|
9
10
|
export { main as cliMain, runCli } from "./cli.js";
|
|
10
11
|
export type { CliIo, CliOptions } from "./cli.js";
|
|
11
12
|
export { findHost, installWebMcp } from "./webmcp.js";
|