Skip to content

Commit 41b6d6f

Browse files
committed
feat: add client for easier configuration
1 parent cc56263 commit 41b6d6f

File tree

3 files changed

+176
-168
lines changed

3 files changed

+176
-168
lines changed

rollup.config.js

+15-15
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@ import commonjs from 'rollup-plugin-commonjs';
33
import typescript from '@rollup/plugin-typescript';
44

55
export default [{
6-
input: 'dist/gptscript.js',
7-
output: {
8-
name: "GPTScript",
9-
file: "dist/gptscript.browser.js",
10-
format: 'iife',
11-
sourcemap: true,
12-
},
13-
external: [
14-
'net','http','path','child_process','sse.js',
15-
],
16-
plugins: [
17-
typescript(),
18-
commonjs(),
19-
resolve(),
20-
],
6+
input: 'dist/gptscript.js',
7+
output: {
8+
name: "GPTScript",
9+
file: "dist/gptscript.browser.js",
10+
format: 'iife',
11+
sourcemap: true,
12+
},
13+
external: [
14+
'net', 'http', 'path', 'child_process', 'sse.js',
15+
],
16+
plugins: [
17+
typescript(),
18+
commonjs(),
19+
resolve({preferBuiltins: true}),
20+
],
2121
}];

src/gptscript.ts

+137-119
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import type {SSE} from "sse.js"
33

44
export interface RunOpts {
5-
gptscriptURL?: string
65
input?: string
76
cacheDir?: string
87
disableCache?: boolean
@@ -40,15 +39,132 @@ export enum RunEventType {
4039
CallFinish = "callFinish",
4140
}
4241

42+
export class Client {
43+
public readonly gptscriptURL?: string
44+
public gptscriptBin?: string
45+
46+
constructor(gptscriptURL?: string, gptscriptBin?: string) {
47+
this.gptscriptURL = gptscriptURL
48+
this.gptscriptBin = gptscriptBin
49+
}
50+
51+
listTools(): Promise<string> {
52+
return this.runBasicCommand("list-tools")
53+
}
54+
55+
listModels(): Promise<string> {
56+
return this.runBasicCommand("list-models")
57+
}
58+
59+
version(): Promise<string> {
60+
return this.runBasicCommand("version")
61+
}
62+
63+
async runBasicCommand(cmd: string): Promise<string> {
64+
const r = new RunSubcommand(cmd, "", "", {}, this.gptscriptBin, this.gptscriptURL)
65+
if (this.gptscriptURL) {
66+
r.request(null)
67+
} else {
68+
await r.exec(["--" + cmd])
69+
}
70+
return r.text()
71+
}
72+
73+
/**
74+
* Runs a tool with the specified name and options.
75+
*
76+
* @param {string} toolName - The name of the tool to run. Can be a file path, URL, or GitHub URL.
77+
* @param {RunOpts} [opts={}] - The options for running the tool.
78+
* @return {Run} The Run object representing the running tool.
79+
*/
80+
run(toolName: string, opts: RunOpts = {}): Run {
81+
return (new Run("run-file-stream-with-events", toolName, "", opts)).nextChat(opts.input)
82+
}
83+
84+
/**
85+
* Evaluates the given tool and returns a Run object.
86+
*
87+
* @param {ToolDef | ToolDef[] | string} tool - The tool to be evaluated. Can be a single ToolDef object, an array of ToolDef objects, or a string representing the tool contents.
88+
* @param {RunOpts} [opts={}] - Optional options for the evaluation.
89+
* @return {Run} The Run object representing the evaluation.
90+
*/
91+
evaluate(tool: ToolDef | ToolDef[] | string, opts: RunOpts = {}): Run {
92+
let toolString: string = ""
93+
94+
if (Array.isArray(tool)) {
95+
toolString = toolArrayToContents(tool)
96+
} else if (typeof tool === "string") {
97+
toolString = tool
98+
} else {
99+
toolString = toolDefToString(tool)
100+
}
101+
102+
return (new Run("run-tool-stream-with-event", "", toolString, opts)).nextChat(opts.input)
103+
}
104+
105+
async parse(fileName: string): Promise<Block[]> {
106+
const r: Run = new RunSubcommand("parse", fileName, "", {}, this.gptscriptBin, this.gptscriptURL)
107+
if (this.gptscriptURL) {
108+
r.request({file: fileName})
109+
} else {
110+
await r.exec(["parse"])
111+
}
112+
return parseBlocksFromNodes((await r.json()).nodes)
113+
}
114+
115+
async parseTool(toolContent: string): Promise<Block[]> {
116+
const r: Run = new RunSubcommand("parse", "", toolContent, {}, this.gptscriptBin, this.gptscriptURL)
117+
if (this.gptscriptURL) {
118+
r.request({input: toolContent})
119+
} else {
120+
await r.exec(["parse"])
121+
}
122+
return parseBlocksFromNodes((await r.json()).nodes)
123+
}
124+
125+
async stringify(blocks: Block[]): Promise<string> {
126+
const nodes: any[] = []
127+
128+
for (const block of blocks) {
129+
if (block.type === "tool") {
130+
nodes.push({
131+
toolNode: {
132+
tool: block
133+
}
134+
})
135+
} else if (block.type === "text") {
136+
nodes.push({
137+
textNode: {
138+
text: "!" + (block.format || "text") + "\n" + block.content
139+
}
140+
})
141+
}
142+
}
143+
144+
const r: Run = new RunSubcommand("fmt", "", JSON.stringify({nodes: nodes}), {}, this.gptscriptBin, this.gptscriptURL)
145+
if (this.gptscriptURL) {
146+
r.request({nodes: nodes})
147+
} else {
148+
await r.exec(["fmt"])
149+
}
150+
151+
return r.text()
152+
}
153+
}
154+
43155
export class Run {
44156
public readonly id: string
45157
public readonly opts: RunOpts
46-
public state: RunState = RunState.Creating
47-
public calls: Call[] = []
48-
public err = ""
49158
public readonly filePath: string
50159
public readonly content: string
160+
public state: RunState = RunState.Creating
161+
public calls: Call[] = []
162+
public err: string = ""
163+
51164
protected stdout?: string
165+
166+
private readonly bin?: string
167+
private readonly gptscriptURL?: string
52168
private readonly requestPath: string = ""
53169
private promise?: Promise<string>
54170
private process?: any
@@ -58,12 +174,17 @@ export class Run {
58174
private callbacks: Record<string, ((f: Frame) => void)[]> = {}
59175
private chatState: string | undefined
60176

61-
constructor(subCommand: string, path: string, content: string, opts: RunOpts) {
177+
constructor(subCommand: string, path: string, content: string, opts: RunOpts, bin?: string, gptscriptURL?: string) {
62178
this.id = randomId("run-")
63179
this.requestPath = subCommand
64180
this.opts = opts
65181
this.filePath = path
66182
this.content = content
183+
184+
if (bin) {
185+
this.bin = bin
186+
}
187+
this.gptscriptURL = gptscriptURL
67188
}
68189

69190
nextChat(input: string = ""): Run {
@@ -78,16 +199,16 @@ export class Run {
78199

79200
run.chatState = this.chatState
80201
run.opts.input = input
81-
if (run.opts.gptscriptURL) {
202+
if (run.gptscriptURL) {
82203
if (run.content !== "") {
83204
run.request({content: this.content})
84205
} else {
85206
run.request({file: this.filePath})
86207
}
87208
} else {
88209
run.exec().catch((e) => {
89-
run.state = RunState.Error
90210
run.err = e.toString()
211+
run.state = RunState.Error
91212
}
92213
)
93214
}
@@ -146,7 +267,7 @@ export class Run {
146267

147268
const child_process = await import("child_process")
148269

149-
this.process = child_process.spawn(await getCmdPath(), extraArgs, spawnOptions as any)
270+
this.process = child_process.spawn(this.bin || await getCmdPath(), extraArgs, spawnOptions as any)
150271
if (process.platform !== "win32") {
151272
// We don't need the named pipe for streaming events.
152273
server.close()
@@ -165,8 +286,8 @@ export class Run {
165286
}
166287

167288
if (!this.process) {
168-
this.state = RunState.Error
169289
this.err = "Run failed to start"
290+
this.state = RunState.Error
170291
server.close()
171292
this.promise = Promise.reject(this.err)
172293
return
@@ -199,11 +320,11 @@ export class Run {
199320
server.close()
200321

201322
if (signal) {
202-
this.state = RunState.Error
203323
this.err = "Run has been aborted"
204-
} else if (code !== 0) {
205324
this.state = RunState.Error
325+
} else if (code !== 0) {
206326
this.err = this.stderr || ""
327+
this.state = RunState.Error
207328
} else if (this.state !== RunState.Continue) {
208329
this.state = RunState.Finished
209330
}
@@ -243,18 +364,18 @@ export class Run {
243364
}
244365

245366
request(tool: any) {
246-
if (!this.opts.gptscriptURL) {
367+
if (!this.gptscriptURL) {
247368
throw new Error("request() requires gptscriptURL to be set")
248369
}
249370
const postData = JSON.stringify({...tool, ...this.opts})
250-
const options = this.requestOptions(this.opts.gptscriptURL, this.requestPath, postData, tool)
371+
const options = this.requestOptions(this.gptscriptURL, this.requestPath, postData, tool)
251372

252373
this.promise = new Promise<string>(async (resolve, reject) => {
253374
// This checks that the code is running in a browser. If it is, then we use SSE.
254375
if (typeof window !== "undefined" && typeof window.document !== "undefined") {
255376
// @ts-ignore
256377
const {SSE} = await import("sse.js")
257-
this.sse = new SSE(this.opts.gptscriptURL + "/" + this.filePath, {
378+
this.sse = new SSE(this.gptscriptURL + "/" + this.filePath, {
258379
headers: {"Content-Type": "application/json"},
259380
payload: postData
260381
} as any)
@@ -530,8 +651,8 @@ export class Run {
530651
}
531652

532653
class RunSubcommand extends Run {
533-
constructor(subCommand: string, path: string, content: string, opts: RunOpts) {
534-
super(subCommand, path, content, opts)
654+
constructor(subCommand: string, path: string, content: string, opts: RunOpts, bin?: string, gptscriptURL?: string) {
655+
super(subCommand, path, content, opts, bin, gptscriptURL)
535656
}
536657

537658
processStdout(data: string | object) {
@@ -777,109 +898,6 @@ async function getCmdPath(): Promise<string> {
777898
return path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "bin", "gptscript")
778899
}
779900

780-
export function listTools(gptscriptURL?: string): Promise<string> {
781-
return runBasicCommand("list-tools", gptscriptURL)
782-
}
783-
784-
export function listModels(gptscriptURL?: string): Promise<string> {
785-
return runBasicCommand("list-models", gptscriptURL)
786-
}
787-
788-
export function version(gptscriptURL?: string): Promise<string> {
789-
return runBasicCommand("version", gptscriptURL)
790-
}
791-
792-
async function runBasicCommand(cmd: string, gptscriptURL?: string): Promise<string> {
793-
const r = new RunSubcommand(cmd, "", "", {gptscriptURL: gptscriptURL})
794-
if (gptscriptURL) {
795-
r.request(null)
796-
} else {
797-
await r.exec(["--" + cmd])
798-
}
799-
return r.text()
800-
}
801-
802-
/**
803-
* Runs a tool with the specified name and options.
804-
*
805-
* @param {string} toolName - The name of the tool to run. Can be a file path, URL, or GitHub URL.
806-
* @param {RunOpts} [opts={}] - The options for running the tool.
807-
* @return {Run} The Run object representing the running tool.
808-
*/
809-
export function run(toolName: string, opts: RunOpts = {}): Run {
810-
return (new Run("run-file-stream-with-events", toolName, "", opts)).nextChat(opts.input)
811-
}
812-
813-
/**
814-
* Evaluates the given tool and returns a Run object.
815-
*
816-
* @param {ToolDef | ToolDef[] | string} tool - The tool to be evaluated. Can be a single ToolDef object, an array of ToolDef objects, or a string representing the tool contents.
817-
* @param {RunOpts} [opts={}] - Optional options for the evaluation.
818-
* @return {Run} The Run object representing the evaluation.
819-
*/
820-
export function evaluate(tool: ToolDef | ToolDef[] | string, opts: RunOpts = {}): Run {
821-
let toolString: string = ""
822-
823-
if (Array.isArray(tool)) {
824-
toolString = toolArrayToContents(tool)
825-
} else if (typeof tool === "string") {
826-
toolString = tool
827-
} else {
828-
toolString = toolDefToString(tool)
829-
}
830-
831-
return (new Run("run-tool-stream-with-event", "", toolString, opts)).nextChat(opts.input)
832-
}
833-
834-
export async function parse(fileName: string, gptscriptURL?: string): Promise<Block[]> {
835-
const r: Run = new RunSubcommand("parse", fileName, "", {gptscriptURL: gptscriptURL})
836-
if (gptscriptURL) {
837-
r.request({file: fileName})
838-
} else {
839-
await r.exec(["parse"])
840-
}
841-
return parseBlocksFromNodes((await r.json()).nodes)
842-
}
843-
844-
export async function parseTool(toolContent: string, gptscriptURL?: string): Promise<Block[]> {
845-
const r: Run = new RunSubcommand("parse", "", toolContent, {gptscriptURL: gptscriptURL})
846-
if (gptscriptURL) {
847-
r.request({input: toolContent})
848-
} else {
849-
await r.exec(["parse"])
850-
}
851-
return parseBlocksFromNodes((await r.json()).nodes)
852-
}
853-
854-
export async function stringify(blocks: Block[], gptscriptURL?: string): Promise<string> {
855-
const nodes: any[] = []
856-
857-
for (const block of blocks) {
858-
if (block.type === "tool") {
859-
nodes.push({
860-
toolNode: {
861-
tool: block
862-
}
863-
})
864-
} else if (block.type === "text") {
865-
nodes.push({
866-
textNode: {
867-
text: "!" + (block.format || "text") + "\n" + block.content
868-
}
869-
})
870-
}
871-
}
872-
873-
const r: Run = new RunSubcommand("fmt", "", JSON.stringify({nodes: nodes}), {gptscriptURL: gptscriptURL})
874-
if (gptscriptURL) {
875-
r.request({nodes: nodes})
876-
} else {
877-
await r.exec(["fmt"])
878-
}
879-
880-
return r.text()
881-
}
882-
883901
function parseBlocksFromNodes(nodes: any[]): Block[] {
884902
const blocks: Block[] = []
885903
for (const node of nodes) {

0 commit comments

Comments
 (0)