11// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22
3+ import path from 'node:path' ;
34import util from 'node:util' ;
45
56import Fuse from 'fuse.js' ;
@@ -8,30 +9,41 @@ import ts from 'typescript';
89import { WorkerInput , WorkerSuccess , WorkerError } from './code-tool-types' ;
910import { Isaacus } from 'isaacus' ;
1011
11- function getRunFunctionNode (
12- code : string ,
13- ) : ts . FunctionDeclaration | ts . FunctionExpression | ts . ArrowFunction | null {
12+ function getRunFunctionSource ( code : string ) : {
13+ type : 'declaration' | 'expression' ;
14+ client : string | undefined ;
15+ code : string ;
16+ } | null {
1417 const sourceFile = ts . createSourceFile ( 'code.ts' , code , ts . ScriptTarget . Latest , true ) ;
18+ const printer = ts . createPrinter ( ) ;
1519
1620 for ( const statement of sourceFile . statements ) {
1721 // Check for top-level function declarations
1822 if ( ts . isFunctionDeclaration ( statement ) ) {
1923 if ( statement . name ?. text === 'run' ) {
20- return statement ;
24+ return {
25+ type : 'declaration' ,
26+ client : statement . parameters [ 0 ] ?. name . getText ( ) ,
27+ code : printer . printNode ( ts . EmitHint . Unspecified , statement . body ! , sourceFile ) ,
28+ } ;
2129 }
2230 }
2331
2432 // Check for variable declarations: const run = () => {} or const run = function() {}
2533 if ( ts . isVariableStatement ( statement ) ) {
2634 for ( const declaration of statement . declarationList . declarations ) {
27- if ( ts . isIdentifier ( declaration . name ) && declaration . name . text === 'run' ) {
35+ if (
36+ ts . isIdentifier ( declaration . name ) &&
37+ declaration . name . text === 'run' &&
2838 // Check if it's initialized with a function
29- if (
30- declaration . initializer &&
31- ( ts . isFunctionExpression ( declaration . initializer ) || ts . isArrowFunction ( declaration . initializer ) )
32- ) {
33- return declaration . initializer ;
34- }
39+ declaration . initializer &&
40+ ( ts . isFunctionExpression ( declaration . initializer ) || ts . isArrowFunction ( declaration . initializer ) )
41+ ) {
42+ return {
43+ type : 'expression' ,
44+ client : declaration . initializer . parameters [ 0 ] ?. name . getText ( ) ,
45+ code : printer . printNode ( ts . EmitHint . Unspecified , declaration . initializer , sourceFile ) ,
46+ } ;
3547 }
3648 }
3749 }
@@ -40,6 +52,61 @@ function getRunFunctionNode(
4052 return null ;
4153}
4254
55+ function getTSDiagnostics ( code : string ) : string [ ] {
56+ const functionSource = getRunFunctionSource ( code ) ! ;
57+ const codeWithImport = [
58+ 'import { Isaacus } from "isaacus";' ,
59+ functionSource . type === 'declaration' ?
60+ `async function run(${ functionSource . client } : Isaacus)`
61+ : `const run: (${ functionSource . client } : Isaacus) => Promise<unknown> =` ,
62+ functionSource . code ,
63+ ] . join ( '\n' ) ;
64+ const sourcePath = path . resolve ( 'code.ts' ) ;
65+ const ast = ts . createSourceFile ( sourcePath , codeWithImport , ts . ScriptTarget . Latest , true ) ;
66+ const options = ts . getDefaultCompilerOptions ( ) ;
67+ options . target = ts . ScriptTarget . Latest ;
68+ options . module = ts . ModuleKind . NodeNext ;
69+ options . moduleResolution = ts . ModuleResolutionKind . NodeNext ;
70+ const host = ts . createCompilerHost ( options , true ) ;
71+ const newHost : typeof host = {
72+ ...host ,
73+ getSourceFile : ( ...args ) => {
74+ if ( path . resolve ( args [ 0 ] ) === sourcePath ) {
75+ return ast ;
76+ }
77+ return host . getSourceFile ( ...args ) ;
78+ } ,
79+ readFile : ( ...args ) => {
80+ if ( path . resolve ( args [ 0 ] ) === sourcePath ) {
81+ return codeWithImport ;
82+ }
83+ return host . readFile ( ...args ) ;
84+ } ,
85+ fileExists : ( ...args ) => {
86+ if ( path . resolve ( args [ 0 ] ) === sourcePath ) {
87+ return true ;
88+ }
89+ return host . fileExists ( ...args ) ;
90+ } ,
91+ } ;
92+ const program = ts . createProgram ( {
93+ options,
94+ rootNames : [ sourcePath ] ,
95+ host : newHost ,
96+ } ) ;
97+ const diagnostics = ts . getPreEmitDiagnostics ( program , ast ) ;
98+ return diagnostics . map ( ( d ) => {
99+ const message = ts . flattenDiagnosticMessageText ( d . messageText , '\n' ) ;
100+ if ( ! d . file || ! d . start ) return `- ${ message } ` ;
101+ const { line : tsLine } = ts . getLineAndCharacterOfPosition ( d . file , d . start ) ;
102+ // We add two lines in the beginning, for the client import and the function declaration.
103+ // So the actual (zero-based) line number is tsLine - 2.
104+ const lineNumber = tsLine - 2 ;
105+ const line = code . split ( '\n' ) . at ( lineNumber ) ?. trim ( ) ;
106+ return line ? `- ${ message } \n at line ${ lineNumber + 1 } \n ${ line } ` : `- ${ message } ` ;
107+ } ) ;
108+ }
109+
43110const fuse = new Fuse (
44111 [
45112 'client.embeddings.create' ,
@@ -141,24 +208,28 @@ function parseError(code: string, error: unknown): string | undefined {
141208
142209const fetch = async ( req : Request ) : Promise < Response > => {
143210 const { opts, code } = ( await req . json ( ) ) as WorkerInput ;
144- if ( code == null ) {
211+
212+ const runFunctionSource = code ? getRunFunctionSource ( code ) : null ;
213+ if ( ! runFunctionSource ) {
214+ const message =
215+ code ?
216+ 'The code is missing a top-level `run` function.'
217+ : 'The code argument is missing. Provide one containing a top-level `run` function.' ;
145218 return Response . json (
146219 {
147- message :
148- 'The code param is missing. Provide one containing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```' ,
220+ message : `${ message } Write code within this template:\n\n\`\`\`\nasync function run(client) {\n // Fill this out\n}\n\`\`\`` ,
149221 logLines : [ ] ,
150222 errLines : [ ] ,
151223 } satisfies WorkerError ,
152224 { status : 400 , statusText : 'Code execution error' } ,
153225 ) ;
154226 }
155227
156- const runFunctionNode = getRunFunctionNode ( code ) ;
157- if ( ! runFunctionNode ) {
228+ const diagnostics = getTSDiagnostics ( code ) ;
229+ if ( diagnostics . length > 0 ) {
158230 return Response . json (
159231 {
160- message :
161- 'The code is missing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```' ,
232+ message : `The code contains TypeScript diagnostics:\n${ diagnostics . join ( '\n' ) } ` ,
162233 logLines : [ ] ,
163234 errLines : [ ] ,
164235 } satisfies WorkerError ,
0 commit comments