2
2
import type { SSE } from "sse.js"
3
3
4
4
export interface RunOpts {
5
- gptscriptURL ?: string
6
5
input ?: string
7
6
cacheDir ?: string
8
7
disableCache ?: boolean
@@ -40,15 +39,151 @@ export enum RunEventType {
40
39
CallFinish = "callFinish" ,
41
40
}
42
41
42
+ export class Client {
43
+ public readonly gptscriptURL ?: string
44
+ public gptscriptBin ?: string
45
+ public readonly key ?: string
46
+ public objectFactory : ( obj : any ) => any
47
+
48
+ constructor ( gptscriptURL ?: string , gptscriptBin ?: string , key ?: string , objectFactory ?: ( obj : any ) => any ) {
49
+ this . gptscriptURL = gptscriptURL
50
+ this . gptscriptBin = gptscriptBin
51
+ this . key = key
52
+
53
+ if ( objectFactory ) {
54
+ this . objectFactory = objectFactory
55
+ } else {
56
+ this . objectFactory = ( obj : any ) => obj
57
+ }
58
+
59
+ // if (!this.gptscriptBin) {
60
+ // getCmdPath().then(
61
+ // (path) => (this.gptscriptBin = path),
62
+ // ).catch(err => {
63
+ // console.error(`Failed to get cmd path: ${err}`)
64
+ // })
65
+ // }
66
+ }
67
+
68
+ listTools ( ) : Promise < string > {
69
+ return this . runBasicCommand ( "list-tools" )
70
+ }
71
+
72
+ listModels ( ) : Promise < string > {
73
+ return this . runBasicCommand ( "list-models" )
74
+ }
75
+
76
+ version ( ) : Promise < string > {
77
+ return this . runBasicCommand ( "version" )
78
+ }
79
+
80
+ async runBasicCommand ( cmd : string ) : Promise < string > {
81
+ const r = new RunSubcommand ( cmd , "" , "" , { } , this . gptscriptBin , this . gptscriptURL )
82
+ if ( this . gptscriptURL ) {
83
+ r . request ( null )
84
+ } else {
85
+ await r . exec ( [ "--" + cmd ] )
86
+ }
87
+ return r . text ( )
88
+ }
89
+
90
+ /**
91
+ * Runs a tool with the specified name and options.
92
+ *
93
+ * @param {string } toolName - The name of the tool to run. Can be a file path, URL, or GitHub URL.
94
+ * @param {RunOpts } [opts={}] - The options for running the tool.
95
+ * @return {Run } The Run object representing the running tool.
96
+ */
97
+ run ( toolName : string , opts : RunOpts = { } ) : Run {
98
+ return ( new Run ( "run-file-stream-with-events" , toolName , "" , opts ) ) . nextChat ( opts . input )
99
+ }
100
+
101
+ /**
102
+ * Evaluates the given tool and returns a Run object.
103
+ *
104
+ * @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.
105
+ * @param {RunOpts } [opts={}] - Optional options for the evaluation.
106
+ * @return {Run } The Run object representing the evaluation.
107
+ */
108
+ evaluate ( tool : ToolDef | ToolDef [ ] | string , opts : RunOpts = { } ) : Run {
109
+ let toolString : string = ""
110
+
111
+ if ( Array . isArray ( tool ) ) {
112
+ toolString = toolArrayToContents ( tool )
113
+ } else if ( typeof tool === "string" ) {
114
+ toolString = tool
115
+ } else {
116
+ toolString = toolDefToString ( tool )
117
+ }
118
+
119
+ return ( new Run ( "run-tool-stream-with-event" , "" , toolString , opts ) ) . nextChat ( opts . input )
120
+ }
121
+
122
+ async parse ( fileName : string , gptscriptURL ?: string ) : Promise < Block [ ] > {
123
+ const r : Run = new RunSubcommand ( "parse" , fileName , "" , { } , this . gptscriptBin , this . gptscriptURL )
124
+ if ( gptscriptURL ) {
125
+ r . request ( { file : fileName } )
126
+ } else {
127
+ await r . exec ( [ "parse" ] )
128
+ }
129
+ return parseBlocksFromNodes ( ( await r . json ( ) ) . nodes )
130
+ }
131
+
132
+ async parseTool ( toolContent : string , gptscriptURL ?: string ) : Promise < Block [ ] > {
133
+ const r : Run = new RunSubcommand ( "parse" , "" , toolContent , { } , this . gptscriptBin , this . gptscriptURL )
134
+ if ( gptscriptURL ) {
135
+ r . request ( { input : toolContent } )
136
+ } else {
137
+ await r . exec ( [ "parse" ] )
138
+ }
139
+ return parseBlocksFromNodes ( ( await r . json ( ) ) . nodes )
140
+ }
141
+
142
+ async stringify ( blocks : Block [ ] , gptscriptURL ?: string ) : Promise < string > {
143
+ const nodes : any [ ] = [ ]
144
+
145
+ for ( const block of blocks ) {
146
+ if ( block . type === "tool" ) {
147
+ nodes . push ( {
148
+ toolNode : {
149
+ tool : block
150
+ }
151
+ } )
152
+ } else if ( block . type === "text" ) {
153
+ nodes . push ( {
154
+ textNode : {
155
+ text : "!" + ( block . format || "text" ) + "\n" + block . content
156
+ }
157
+ } )
158
+ }
159
+ }
160
+
161
+ const r : Run = new RunSubcommand ( "fmt" , "" , JSON . stringify ( { nodes : nodes } ) , { } , this . gptscriptBin , this . gptscriptURL )
162
+ if ( gptscriptURL ) {
163
+ r . request ( { nodes : nodes } )
164
+ } else {
165
+ await r . exec ( [ "fmt" ] )
166
+ }
167
+
168
+ return r . text ( )
169
+ }
170
+ }
171
+
43
172
export class Run {
44
173
public readonly id : string
45
174
public readonly opts : RunOpts
46
- public state : RunState = RunState . Creating
47
- public calls : Call [ ] = [ ]
48
- public err = ""
49
175
public readonly filePath : string
50
176
public readonly content : string
177
+ public readonly reactive : { state : RunState , calls : Call [ ] , err : string } = {
178
+ state : RunState . Creating ,
179
+ calls : [ ] ,
180
+ err : ""
181
+ }
182
+
51
183
protected stdout ?: string
184
+
185
+ private readonly bin ?: string
186
+ private readonly gptscriptURL ?: string
52
187
private readonly requestPath : string = ""
53
188
private promise ?: Promise < string >
54
189
private process ?: any
@@ -58,12 +193,33 @@ export class Run {
58
193
private callbacks : Record < string , ( ( f : Frame ) => void ) [ ] > = { }
59
194
private chatState : string | undefined
60
195
61
- constructor ( subCommand : string , path : string , content : string , opts : RunOpts ) {
196
+ constructor ( subCommand : string , path : string , content : string , opts : RunOpts , bin ?: string , gptscriptURL ?: string , stateTracker ?: ( obj : any ) => any ) {
62
197
this . id = randomId ( "run-" )
63
198
this . requestPath = subCommand
64
199
this . opts = opts
65
200
this . filePath = path
66
201
this . content = content
202
+
203
+ if ( bin ) {
204
+ this . bin = bin
205
+ }
206
+ this . gptscriptURL = gptscriptURL
207
+
208
+ if ( stateTracker ) {
209
+ this . reactive = stateTracker ( this . reactive )
210
+ }
211
+ }
212
+
213
+ get state ( ) : RunState {
214
+ return this . reactive . state
215
+ }
216
+
217
+ get err ( ) : string {
218
+ return this . reactive . err
219
+ }
220
+
221
+ get calls ( ) : Call [ ] {
222
+ return this . reactive . calls
67
223
}
68
224
69
225
nextChat ( input : string = "" ) : Run {
@@ -78,16 +234,16 @@ export class Run {
78
234
79
235
run . chatState = this . chatState
80
236
run . opts . input = input
81
- if ( run . opts . gptscriptURL ) {
237
+ if ( run . gptscriptURL ) {
82
238
if ( run . content !== "" ) {
83
239
run . request ( { content : this . content } )
84
240
} else {
85
241
run . request ( { file : this . filePath } )
86
242
}
87
243
} else {
88
244
run . exec ( ) . catch ( ( e ) => {
89
- run . state = RunState . Error
90
- run . err = e . toString ( )
245
+ run . reactive . err = e . toString ( )
246
+ run . reactive . state = RunState . Error
91
247
}
92
248
)
93
249
}
@@ -146,7 +302,7 @@ export class Run {
146
302
147
303
const child_process = await import ( "child_process" )
148
304
149
- this . process = child_process . spawn ( await getCmdPath ( ) , extraArgs , spawnOptions as any )
305
+ this . process = child_process . spawn ( this . bin || await getCmdPath ( ) , extraArgs , spawnOptions as any )
150
306
if ( process . platform !== "win32" ) {
151
307
// We don't need the named pipe for streaming events.
152
308
server . close ( )
@@ -165,8 +321,8 @@ export class Run {
165
321
}
166
322
167
323
if ( ! this . process ) {
168
- this . state = RunState . Error
169
- this . err = "Run failed to start"
324
+ this . reactive . err = "Run failed to start"
325
+ this . reactive . state = RunState . Error
170
326
server . close ( )
171
327
this . promise = Promise . reject ( this . err )
172
328
return
@@ -181,7 +337,7 @@ export class Run {
181
337
this . process . stdin . end ( )
182
338
}
183
339
184
- this . state = RunState . Running
340
+ this . reactive . state = RunState . Running
185
341
186
342
if ( this . process . stdout ) {
187
343
this . process . stdout . on ( "data" , ( data : any ) => {
@@ -199,17 +355,17 @@ export class Run {
199
355
server . close ( )
200
356
201
357
if ( signal ) {
202
- this . state = RunState . Error
203
- this . err = "Run has been aborted"
358
+ this . reactive . err = "Run has been aborted"
359
+ this . reactive . state = RunState . Error
204
360
} else if ( code !== 0 ) {
205
- this . state = RunState . Error
206
- this . err = this . stderr || ""
361
+ this . reactive . err = this . stderr || ""
362
+ this . reactive . state = RunState . Error
207
363
} else if ( this . state !== RunState . Continue ) {
208
- this . state = RunState . Finished
364
+ this . reactive . state = RunState . Finished
209
365
}
210
366
211
367
if ( this . err ) {
212
- this . state = RunState . Error
368
+ this . reactive . state = RunState . Error
213
369
reject ( this . err )
214
370
} else {
215
371
resolve ( this . stdout || "" )
@@ -227,40 +383,40 @@ export class Run {
227
383
try {
228
384
data = JSON . parse ( data )
229
385
} catch ( e ) {
230
- this . err = `Failed to parse stdout: "${ data } "`
386
+ this . reactive . err = `Failed to parse stdout: "${ data } "`
231
387
return
232
388
}
233
389
}
234
390
235
391
const out = data as ChatState
236
392
if ( out . done !== undefined && ! out . done ) {
237
393
this . chatState = out . state
238
- this . state = RunState . Continue
394
+ this . reactive . state = RunState . Continue
239
395
} else {
240
- this . state = RunState . Finished
396
+ this . reactive . state = RunState . Finished
241
397
this . chatState = undefined
242
398
}
243
399
}
244
400
245
401
request ( tool : any ) {
246
- if ( ! this . opts . gptscriptURL ) {
402
+ if ( ! this . gptscriptURL ) {
247
403
throw new Error ( "request() requires gptscriptURL to be set" )
248
404
}
249
405
const postData = JSON . stringify ( { ...tool , ...this . opts } )
250
- const options = this . requestOptions ( this . opts . gptscriptURL , this . requestPath , postData , tool )
406
+ const options = this . requestOptions ( this . gptscriptURL , this . requestPath , postData , tool )
251
407
252
408
this . promise = new Promise < string > ( async ( resolve , reject ) => {
253
409
// This checks that the code is running in a browser. If it is, then we use SSE.
254
410
if ( typeof window !== "undefined" && typeof window . document !== "undefined" ) {
255
411
// @ts -ignore
256
412
const { SSE } = await import ( "sse.js" )
257
- this . sse = new SSE ( this . opts . gptscriptURL + "/" + this . filePath , {
413
+ this . sse = new SSE ( this . gptscriptURL + "/" + this . filePath , {
258
414
headers : { "Content-Type" : "application/json" } ,
259
415
payload : postData
260
416
} as any )
261
417
262
418
this . sse . addEventListener ( "open" , ( ) => {
263
- this . state = RunState . Running
419
+ this . reactive . state = RunState . Running
264
420
} )
265
421
266
422
this . sse . addEventListener ( "message" , ( data : any ) => {
@@ -281,16 +437,16 @@ export class Run {
281
437
282
438
this . sse . addEventListener ( "close" , ( ) => {
283
439
if ( this . state === RunState . Running || this . state === RunState . Finished ) {
284
- this . state = RunState . Finished
440
+ this . reactive . state = RunState . Finished
285
441
resolve ( this . stdout || "" )
286
442
} else if ( this . state === RunState . Error ) {
287
443
reject ( this . err )
288
444
}
289
445
} )
290
446
291
447
this . sse . addEventListener ( "error" , ( err : any ) => {
292
- this . state = RunState . Error
293
- this . err = err
448
+ this . reactive . state = RunState . Error
449
+ this . reactive . err = err
294
450
reject ( err )
295
451
} )
296
452
} else {
@@ -300,7 +456,7 @@ export class Run {
300
456
// Use frag to keep track of partial object writes.
301
457
let frag = ""
302
458
this . req = http . request ( options , ( res : any ) => {
303
- this . state = RunState . Running
459
+ this . reactive . state = RunState . Running
304
460
res . on ( "data" , ( chunk : any ) => {
305
461
for ( let line of ( chunk . toString ( ) + frag ) . split ( "\n" ) ) {
306
462
const c = line . replace ( / ^ ( d a t a : ) / , "" ) . trim ( )
@@ -333,7 +489,7 @@ export class Run {
333
489
334
490
res . on ( "end" , ( ) => {
335
491
if ( this . state === RunState . Running || this . state === RunState . Finished ) {
336
- this . state = RunState . Finished
492
+ this . reactive . state = RunState . Finished
337
493
resolve ( this . stdout || "" )
338
494
} else if ( this . state === RunState . Error ) {
339
495
reject ( this . err )
@@ -342,22 +498,22 @@ export class Run {
342
498
343
499
res . on ( "aborted" , ( ) => {
344
500
if ( this . state !== RunState . Finished ) {
345
- this . state = RunState . Error
346
- this . err = "Run has been aborted"
501
+ this . reactive . state = RunState . Error
502
+ this . reactive . err = "Run has been aborted"
347
503
reject ( this . err )
348
504
}
349
505
} )
350
506
351
507
res . on ( "error" , ( error : Error ) => {
352
- this . state = RunState . Error
353
- this . err = error . message || ""
508
+ this . reactive . state = RunState . Error
509
+ this . reactive . err = error . message || ""
354
510
reject ( this . err )
355
511
} )
356
512
} )
357
513
358
514
this . req . on ( "error" , ( error : Error ) => {
359
- this . state = RunState . Error
360
- this . err = error . message || ""
515
+ this . reactive . state = RunState . Error
516
+ this . reactive . err = error . message || ""
361
517
reject ( this . err )
362
518
} )
363
519
@@ -403,17 +559,17 @@ export class Run {
403
559
}
404
560
405
561
if ( ! this . state ) {
406
- this . state = RunState . Creating
562
+ this . reactive . state = RunState . Creating
407
563
}
408
564
409
565
if ( f . type === RunEventType . RunStart ) {
410
- this . state = RunState . Running
566
+ this . reactive . state = RunState . Running
411
567
} else if ( f . type === RunEventType . RunFinish ) {
412
568
if ( f . err ) {
413
- this . state = RunState . Error
414
- this . err = f . err || ""
569
+ this . reactive . state = RunState . Error
570
+ this . reactive . err = f . err || ""
415
571
} else {
416
- this . state = RunState . Finished
572
+ this . reactive . state = RunState . Finished
417
573
this . stdout = f . output || ""
418
574
}
419
575
} else if ( ( f . type as string ) . startsWith ( "call" ) ) {
@@ -530,8 +686,8 @@ export class Run {
530
686
}
531
687
532
688
class RunSubcommand extends Run {
533
- constructor ( subCommand : string , path : string , content : string , opts : RunOpts ) {
534
- super ( subCommand , path , content , opts )
689
+ constructor ( subCommand : string , path : string , content : string , opts : RunOpts , bin ?: string , gptscriptURL ?: string ) {
690
+ super ( subCommand , path , content , opts , bin , gptscriptURL )
535
691
}
536
692
537
693
processStdout ( data : string | object ) {
@@ -777,109 +933,6 @@ async function getCmdPath(): Promise<string> {
777
933
return path . join ( path . dirname ( url . fileURLToPath ( import . meta. url ) ) , ".." , "bin" , "gptscript" )
778
934
}
779
935
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
-
883
936
function parseBlocksFromNodes ( nodes : any [ ] ) : Block [ ] {
884
937
const blocks : Block [ ] = [ ]
885
938
for ( const node of nodes ) {
0 commit comments