Skip to content

Commit b218790

Browse files
committed
wip, feat: typed tool
1 parent e1e464f commit b218790

File tree

2 files changed

+105
-37
lines changed

2 files changed

+105
-37
lines changed

src/core/athena.ts

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,64 @@ import logger from "../utils/logger.js";
55

66
export type Dict<T> = { [key: string]: T };
77

8-
export interface IAthenaArgument {
9-
type: "string" | "number" | "boolean" | "object" | "array";
8+
type IAthenaArgumentPrimitive = {
9+
type: "string" | "number" | "boolean";
1010
desc: string;
1111
required: boolean;
12-
of?: Dict<IAthenaArgument> | IAthenaArgument;
13-
}
14-
15-
export interface IAthenaTool {
12+
};
13+
14+
export type IAthenaArgument =
15+
| IAthenaArgumentPrimitive
16+
| {
17+
type: "object" | "array";
18+
desc: string;
19+
required: boolean;
20+
of: Dict<IAthenaArgument> | IAthenaArgument;
21+
};
22+
type IAthenaArgumentInstance<T extends IAthenaArgument> =
23+
T extends IAthenaArgumentPrimitive
24+
? T["type"] extends "string"
25+
? T["required"] extends true
26+
? string
27+
: string | undefined
28+
: T["type"] extends "number"
29+
? T["required"] extends true
30+
? number
31+
: number | undefined
32+
: T["type"] extends "boolean"
33+
? T["required"] extends true
34+
? boolean
35+
: boolean | undefined
36+
: never
37+
: T extends { of: Dict<IAthenaArgument> }
38+
? T["required"] extends true
39+
? { [K in keyof T["of"]]: IAthenaArgumentInstance<T["of"][K]> }
40+
:
41+
| { [K in keyof T["of"]]: IAthenaArgumentInstance<T["of"][K]> }
42+
| undefined
43+
: T extends { of: IAthenaArgument }
44+
? T["required"] extends true
45+
? IAthenaArgumentInstance<T["of"]>[]
46+
: IAthenaArgumentInstance<T["of"]>[] | undefined
47+
: never;
48+
49+
export interface IAthenaTool<
50+
Args extends Dict<IAthenaArgument> = Dict<IAthenaArgument>,
51+
RetArgs extends Dict<IAthenaArgument> = Dict<IAthenaArgument>,
52+
> {
1653
name: string;
1754
desc: string;
18-
args: Dict<IAthenaArgument>;
19-
retvals: Dict<IAthenaArgument>;
20-
fn: (args: Dict<any>) => Promise<Dict<any>>;
55+
args: Args;
56+
retvals: RetArgs;
57+
fn: (args: {
58+
[K in keyof Args]: Args[K] extends IAthenaArgument
59+
? IAthenaArgumentInstance<Args[K]>
60+
: never;
61+
}) => Promise<{
62+
[K in keyof RetArgs]: RetArgs[K] extends IAthenaArgument
63+
? IAthenaArgumentInstance<RetArgs[K]>
64+
: never;
65+
}>;
2166
explain_args?: (args: Dict<any>) => IAthenaExplanation;
2267
explain_retvals?: (args: Dict<any>, retvals: Dict<any>) => IAthenaExplanation;
2368
}
@@ -38,7 +83,7 @@ export class Athena extends EventEmitter {
3883
config: Dict<any>;
3984
states: Dict<Dict<any>>;
4085
plugins: Dict<PluginBase>;
41-
tools: Dict<IAthenaTool>;
86+
tools: Dict<IAthenaTool<any, any>>;
4287
events: Dict<IAthenaEvent>;
4388

4489
constructor(config: Dict<any>, states: Dict<Dict<any>>) {
@@ -103,7 +148,26 @@ export class Athena extends EventEmitter {
103148
logger.warn(`Plugin ${name} is unloaded`);
104149
}
105150

106-
registerTool(tool: IAthenaTool) {
151+
registerTool<
152+
Args extends Dict<IAthenaArgument>,
153+
RetArgs extends Dict<IAthenaArgument>,
154+
Tool extends IAthenaTool<Args, RetArgs>,
155+
>(
156+
config: {
157+
name: string;
158+
desc: string;
159+
args: Args;
160+
retvals: RetArgs;
161+
},
162+
toolImpl: {
163+
fn: Tool["fn"];
164+
explain_args?: Tool["explain_args"];
165+
},
166+
) {
167+
const tool = {
168+
...config,
169+
...toolImpl,
170+
};
107171
if (tool.name in this.tools) {
108172
throw new Error(`Tool ${tool.name} already registered`);
109173
}

src/plugins/cerebrum/init.ts

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -47,36 +47,40 @@ export default class Cerebrum extends PluginBase {
4747
this.boundAthenaPrivateEventHandler =
4848
this.athenaPrivateEventHandler.bind(this);
4949
if (this.config.image_supported) {
50-
athena.registerTool({
51-
name: "image/check-out",
52-
desc: "Check out an image. Whenever you want to see an image, or the user asks you to see an image, use this tool.",
53-
args: {
54-
image: {
55-
type: "string",
56-
desc: "The URL or local path of the image to check out.",
57-
required: true,
50+
athena.registerTool(
51+
{
52+
name: "image/check-out",
53+
desc: "Check out an image. Whenever you want to see an image, or the user asks you to see an image, use this tool.",
54+
args: {
55+
image: {
56+
type: "string",
57+
desc: "The URL or local path of the image to check out.",
58+
required: true,
59+
},
5860
},
59-
},
60-
retvals: {
61-
result: {
62-
type: "string",
63-
desc: "The result of checking out the image.",
64-
required: true,
61+
retvals: {
62+
result: {
63+
type: "string",
64+
desc: "The result of checking out the image.",
65+
required: true,
66+
},
6567
},
6668
},
67-
fn: async (args: Dict<any>) => {
68-
let image = args.image;
69-
if (!image.startsWith("http")) {
70-
image = await image2uri(image);
71-
}
72-
this.imageUrls.push(image);
73-
return { result: "success" };
69+
{
70+
fn: async (args) => {
71+
let image = args.image;
72+
if (!image.startsWith("http")) {
73+
image = await image2uri(image);
74+
}
75+
this.imageUrls.push(image);
76+
return { result: "success" };
77+
},
78+
explain_args: (args: Dict<any>) => ({
79+
summary: "Checking out the image...",
80+
details: args.image,
81+
}),
7482
},
75-
explain_args: (args: Dict<any>) => ({
76-
summary: "Checking out the image...",
77-
details: args.image,
78-
}),
79-
});
83+
);
8084
}
8185
athena.on("event", this.boundAthenaEventHandler);
8286
athena.on("private-event", this.boundAthenaPrivateEventHandler);

0 commit comments

Comments
 (0)