@aphrody/winclean 0.0.0-stage → 0.1.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 +23 -3
- package/package.json +36 -4
- package/src/catalog.ts +3670 -0
- package/src/generated.ts +3726 -0
- package/src/index.ts +80 -0
- package/src/result.ts +73 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/** Compatibility facade for the unified Aphrody OS kernel. */
|
|
3
|
+
import { KernelClient } from "@aphrody/os";
|
|
4
|
+
import { GeneratedTools, type ToolArgsMap, type ToolName } from "./generated";
|
|
5
|
+
import { TOOL_CATALOG } from "./catalog";
|
|
6
|
+
import { ToolResult } from "./result";
|
|
7
|
+
|
|
8
|
+
export * from "./generated";
|
|
9
|
+
export * from "./result";
|
|
10
|
+
export { KernelError } from "@aphrody/os";
|
|
11
|
+
|
|
12
|
+
export interface ToolInfo {
|
|
13
|
+
name: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
inputSchema: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Local kernel-backed WinClean client. */
|
|
19
|
+
export class Winclean extends GeneratedTools implements Disposable {
|
|
20
|
+
readonly path = "kernel://aphrody-os";
|
|
21
|
+
#clientPromise: Promise<KernelClient> | undefined;
|
|
22
|
+
#closed = false;
|
|
23
|
+
|
|
24
|
+
private constructor(readonly endpoint?: string) {
|
|
25
|
+
super();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static open(endpoint?: string): Winclean {
|
|
29
|
+
return new Winclean(endpoint);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get version(): string {
|
|
33
|
+
return "kernel";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
listTools(): ToolInfo[] {
|
|
37
|
+
return TOOL_CATALOG.map(({ name, description, inputSchema }) => ({
|
|
38
|
+
name,
|
|
39
|
+
description,
|
|
40
|
+
inputSchema,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async call<N extends ToolName>(name: N, args?: ToolArgsMap[N]): Promise<ToolResult>;
|
|
45
|
+
async call(name: string, args?: Record<string, unknown>): Promise<ToolResult>;
|
|
46
|
+
async call(name: string, args: object = {}): Promise<ToolResult> {
|
|
47
|
+
if (this.#closed) throw new Error("Winclean client is closed");
|
|
48
|
+
const client = await this.#client();
|
|
49
|
+
try {
|
|
50
|
+
const value = await client.call(name, args as Record<string, any>);
|
|
51
|
+
return new ToolResult(name, {
|
|
52
|
+
structuredContent: value,
|
|
53
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
54
|
+
});
|
|
55
|
+
} catch (error) {
|
|
56
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
57
|
+
return new ToolResult(name, { isError: true, content: [{ type: "text", text }] });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Synchronous calls are routed through the kernel RPC client. */
|
|
62
|
+
callSync(_name: string, _args?: Record<string, unknown>): ToolResult {
|
|
63
|
+
throw new Error(
|
|
64
|
+
"callSync is unavailable: use await Winclean.open().call(...) through the Aphrody OS kernel",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
close(): void {
|
|
69
|
+
this.#closed = true;
|
|
70
|
+
void this.#clientPromise?.then((client) => client.close());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
[Symbol.dispose](): void {
|
|
74
|
+
this.close();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async #client(): Promise<KernelClient> {
|
|
78
|
+
return (this.#clientPromise ??= KernelClient.connect(this.endpoint, "bun/winclean"));
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/result.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/** One MCP content block. */
|
|
3
|
+
export type Content =
|
|
4
|
+
| { type: "text"; text: string }
|
|
5
|
+
| { type: "image"; data: string; mimeType: string }
|
|
6
|
+
| { type: "audio"; data: string; mimeType: string }
|
|
7
|
+
| { type: string; [key: string]: unknown };
|
|
8
|
+
|
|
9
|
+
/** Raw MCP `CallToolResult` as returned by `winclean_call`. */
|
|
10
|
+
export interface RawToolResult {
|
|
11
|
+
content?: Content[];
|
|
12
|
+
structuredContent?: unknown;
|
|
13
|
+
isError?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Thrown by {@link ToolResult.ok} / {@link ToolResult.json} when a tool reports `isError`. */
|
|
17
|
+
export class ToolError extends Error {
|
|
18
|
+
constructor(
|
|
19
|
+
readonly tool: string,
|
|
20
|
+
readonly result: ToolResult,
|
|
21
|
+
) {
|
|
22
|
+
super(`${tool}: ${result.text}`);
|
|
23
|
+
this.name = "ToolError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Result of a WinClean tool invocation. */
|
|
28
|
+
export class ToolResult {
|
|
29
|
+
readonly content: Content[];
|
|
30
|
+
readonly structuredContent: unknown;
|
|
31
|
+
readonly isError: boolean;
|
|
32
|
+
|
|
33
|
+
constructor(
|
|
34
|
+
readonly tool: string,
|
|
35
|
+
raw: RawToolResult,
|
|
36
|
+
) {
|
|
37
|
+
this.content = raw.content ?? [];
|
|
38
|
+
this.structuredContent = raw.structuredContent;
|
|
39
|
+
this.isError = raw.isError === true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Concatenated text blocks. */
|
|
43
|
+
get text(): string {
|
|
44
|
+
return this.content
|
|
45
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
46
|
+
.map((c) => c.text)
|
|
47
|
+
.join("\n");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Decoded image blocks (PNG frames from `computer_observe`, `window_capture`, …). */
|
|
51
|
+
get images(): { mimeType: string; bytes: Uint8Array }[] {
|
|
52
|
+
return this.content
|
|
53
|
+
.filter((c): c is { type: "image"; data: string; mimeType: string } => c.type === "image")
|
|
54
|
+
.map((c) => ({
|
|
55
|
+
mimeType: c.mimeType,
|
|
56
|
+
bytes: Uint8Array.from(Buffer.from(c.data, "base64")),
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Throws {@link ToolError} when the tool failed; returns `this` otherwise. */
|
|
61
|
+
ok(): this {
|
|
62
|
+
if (this.isError) throw new ToolError(this.tool, this);
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** `structuredContent`, or the first text block parsed as JSON. Throws on tool error. */
|
|
67
|
+
json<T = any>(): T {
|
|
68
|
+
this.ok();
|
|
69
|
+
if (this.structuredContent !== undefined) return this.structuredContent as T;
|
|
70
|
+
const first = this.content.find((c): c is { type: "text"; text: string } => c.type === "text");
|
|
71
|
+
return JSON.parse(first?.text ?? "null") as T;
|
|
72
|
+
}
|
|
73
|
+
}
|