-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.ts
More file actions
157 lines (140 loc) · 4.16 KB
/
index.ts
File metadata and controls
157 lines (140 loc) · 4.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import { Event, MelonyPlugin } from "melony";
import {
Codex,
type ApprovalMode,
type CodexOptions,
type SandboxMode,
type Thread,
type ThreadOptions,
type WebSearchMode,
} from "@openai/codex-sdk";
export interface CodexPluginOptions {
// this first options are passed from AGENT.md as its used as a Runtime plugin for the Agent.
name?: string;
instructions?: string;
/**
* Mode of the plugin.
*/
mode?: "coding" | "asking" | "planning"; // we can implement it later
/**
* Optional API key override.
*/
apiKey?: string;
/**
* Optional base URL override for Codex API.
*/
baseURL?: string;
/**
* Codex model to use for threads.
*/
model: string;
/**
* Working directory for Codex execution.
*/
workingDirectory?: string;
/**
* Skip Codex git repository check. Defaults to false.
*/
skipGitRepoCheck?: boolean;
sandboxMode?: SandboxMode;
approvalPolicy?: ApprovalMode;
networkAccessEnabled?: boolean;
webSearchMode?: WebSearchMode;
}
export const codexPlugin =
(
options: CodexPluginOptions = { model: "gpt-5-codex" },
): MelonyPlugin<any, any> =>
(builder) => {
const env = (globalThis as any)?.process?.env as
| Record<string, string | undefined>
| undefined;
const apiKey = options.apiKey ?? env?.CODEX_API_KEY ?? env?.OPENAI_API_KEY;
let model =
typeof options.model === "string" && options.model.trim()
? options.model
: "gpt-5-codex";
// if model is formatted as "openai/gpt-5-codex", then extract the model name
if (model.includes("/")) {
model = model.split("/")[1] ?? "gpt-5-codex";
}
const codexOptions: CodexOptions = {
apiKey,
...(options.baseURL ? { baseUrl: options.baseURL } : {}),
};
const client = new Codex(codexOptions);
let thread: Thread | null = null;
const getThread = (state?: any) => {
if (thread) return thread;
const threadOptions: ThreadOptions = {
model,
...(options.workingDirectory
? { workingDirectory: options.workingDirectory }
: {
workingDirectory: (globalThis as any)?.process?.cwd() || "/tmp",
}),
skipGitRepoCheck: options.skipGitRepoCheck ?? false,
...(options.sandboxMode
? { sandboxMode: options.sandboxMode }
: {
sandboxMode: "workspace-write",
}),
...(options.approvalPolicy
? { approvalPolicy: options.approvalPolicy }
: {}),
...(typeof options.networkAccessEnabled === "boolean"
? { networkAccessEnabled: options.networkAccessEnabled }
: {}),
...(options.webSearchMode
? { webSearchMode: options.webSearchMode }
: {}),
};
const threadId = state?.threadId ?? undefined;
thread = threadId
? client.resumeThread(threadId, threadOptions)
: client.startThread(threadOptions);
if (!threadId) {
if (state) (state as any).threadId = thread.id;
}
return thread;
};
builder.on("agent:invoke" as any, async function* (event, ctx) {
const { content } = event.data;
const state = ctx?.state || {};
// if content is empty, then return an error
if (!content) {
yield {
type: "agent:output",
data: {
content: "No content provided.",
},
} as Event;
return;
}
try {
const turn = await getThread(state).run(content);
const text = turn.finalResponse?.trim();
const result = text && text.length > 0 ? text : "No textual response.";
yield {
type: "agent:output",
data: {
content: result,
},
} as Event;
} catch (error: any) {
const message =
error?.message || "Codex request failed with an unknown error.";
yield {
type: "agent:output",
data: {
content: `Error: ${message}`,
},
} as Event;
}
});
};
export const plugin = {
name: "Codex",
description: "Codex integration tools for OpenBot",
factory: (options: CodexPluginOptions) => codexPlugin(options),
};