Skip to content

Commit 62ccb93

Browse files
committed
add ai endpoint
1 parent 31483a2 commit 62ccb93

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed

apps/cli/src/apis.ts

+17
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
GetUserDataResponse,
77
VerifyTokenResponse,
88
} from "@codemod-com/api-types";
9+
import type { LLMEngine } from "@codemod-com/utilities";
910
import Axios, { type RawAxiosRequestHeaders } from "axios";
1011

1112
export const getCLIAccessToken = async (
@@ -208,3 +209,19 @@ export const createCodeDiff = async (
208209

209210
return res.data;
210211
};
212+
213+
export const sendAIRequest = async (options: {
214+
accessToken: string;
215+
prompt: string;
216+
engine?: LLMEngine;
217+
}): Promise<string> => {
218+
const { accessToken, prompt, engine = "gpt-4" } = options;
219+
220+
const res = await Axios.post<string>(
221+
`${process.env.AI_BACKEND_URL}/sendChat`,
222+
{ messages: [prompt], engine },
223+
{ headers: { Authorization: `Bearer ${accessToken}` } },
224+
);
225+
226+
return res.data;
227+
};

apps/cli/src/utils.ts

+237
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,240 @@ export const rebuildCodemodFallback = async (options: {
191191

192192
export const oraCheckmark = chalk.green("✔");
193193
export const oraCross = chalk.red("✖");
194+
195+
const generateCodemodContext = `### Context
196+
- You will be provided with BEFORE and AFTER code snippet pairs.
197+
- Write a single jscodeshift codemod that transforms each BEFORE snippet into the AFTER snippet.
198+
- Identify common patterns and create a generic codemod to handle all cases.
199+
- Use only jscodeshift and TypeScript.
200+
- If comments in AFTER snippets describe the transformation, do not preserve them.
201+
- Only include a code block in your response, no extra explanations.
202+
- Comment your code following best practices.
203+
- Do not import 'namedTypes' or 'builders' from jscodeshift.
204+
- Always narrow node types using typeguards before accessing their properties.
205+
`;
206+
207+
const improveCodemodContext = `### Context
208+
- You will be provided with BEFORE and AFTER code snippet pairs and an existing codemod that might or might not satisfy them.
209+
- Use the provided jscodeshift codemod and see whether it would turn each BEFORE snippet into corresponding AFTER snippet.
210+
- Identify common patterns and improve the codemod to handle all cases.
211+
- Use only jscodeshift and TypeScript.
212+
- If comments in AFTER snippets describe the transformation, do not preserve them.
213+
- Only include the generated codemod in your response, no extra explanations.
214+
- Comment your code following best practices.
215+
- Do not import 'namedTypes' or 'builders' from jscodeshift.
216+
- Always narrow node types using typeguards before accessing their properties.
217+
`;
218+
219+
const jscodeshiftUsageExamples = `Here are two examples of using typeguards to check whether the import source is a string literal:
220+
\`\`\`typescript
221+
if (j.Literal.check(node.source)) { // CORRECT
222+
// rest of the code
223+
}
224+
225+
if (j.Literal.check(node.source) && typeof node.source.value === 'string') { // CORRECT
226+
// rest of the code
227+
}
228+
\`\`\`
229+
230+
231+
- Never check the node type without using typeguards. The following example is INCORRECT:
232+
\`\`\`typescript
233+
if (node.source.type === 'Literal') { // INCORRECT
234+
// rest of the code
235+
}
236+
\`\`\`
237+
238+
### Examples
239+
#### Example 1
240+
**BEFORE**:
241+
\`\`\`typescript
242+
import { A } from '@ember/array';
243+
let arr = new A();
244+
\`\`\`
245+
**AFTER**:
246+
\`\`\`typescript
247+
import { A as emberA } from '@ember/array';
248+
let arr = A();
249+
\`\`\`
250+
**CODEMOD**:
251+
\`\`\`typescript
252+
export default function transform(file, api) {
253+
const j = api.jscodeshift;
254+
const root = j(file.source);
255+
root.find(j.NewExpression, { callee: { name: "A" } }).replaceWith(() => {
256+
root.find(j.ImportSpecifier, {
257+
imported: { name: "A" },
258+
local: { name: "A" }
259+
}).replaceWith(() => {
260+
return j.importSpecifier(j.identifier("A"), j.identifier("emberA"));
261+
});
262+
return j.callExpression(j.identifier("A"), []);
263+
});
264+
return root.toSource();
265+
}
266+
\`\`\`
267+
268+
#### Example 2
269+
**BEFORE**:
270+
\`\`\`typescript
271+
import { Route, Router } from 'react-router-dom';
272+
const MyApp = () => (
273+
<Router history={history}>
274+
<Route path='/posts' component={PostList} />
275+
<Route path='/posts/:id' component={PostEdit} />
276+
<Route path='/posts/:id/show' component={PostShow} />
277+
<Route path='/posts/:id/delete' component={PostDelete} />
278+
</Router>
279+
);
280+
\`\`\`
281+
**AFTER**:
282+
\`\`\`typescript
283+
import { Route, Router } from 'react-router-dom';
284+
const MyApp = () => (
285+
<Router history={history}>
286+
<Switch>
287+
<Route path='/posts' component={PostList} />
288+
<Route path='/posts/:id' component={PostEdit} />
289+
<Route path='/posts/:id/show' component={PostShow} />
290+
<Route path='/posts/:id/delete' component={PostDelete} />
291+
</Switch>
292+
</Router>
293+
);
294+
\`\`\`
295+
**CODEMOD**:
296+
\`\`\`typescript
297+
import type { API, FileInfo, Options, Transform } from "jscodeshift";
298+
function transform(file: FileInfo, api: API, options: Options): string | undefined {
299+
const j = api.jscodeshift;
300+
const root = j(file.source);
301+
root.find(j.JSXElement, {
302+
openingElement: { name: { name: "Router" } }
303+
}).forEach((path) => {
304+
const hasSwitch = root.findJSXElements("Switch").length > 0;
305+
if (hasSwitch) {
306+
return;
307+
}
308+
const children = path.value.children;
309+
const newEl = j.jsxElement(
310+
j.jsxOpeningElement(j.jsxIdentifier("Switch"), [], false),
311+
j.jsxClosingElement(j.jsxIdentifier("Switch")),
312+
children
313+
);
314+
path.value.children = [j.jsxText("\\n "), newEl, j.jsxText("\\n")];
315+
});
316+
return root.toSource(options);
317+
}
318+
export default transform;
319+
\`\`\`
320+
321+
#### Example 3
322+
**BEFORE**:
323+
\`\`\`typescript
324+
import { Redirect, Route } from 'react-router';
325+
\`\`\`
326+
**AFTER**:
327+
\`\`\`typescript
328+
import { Redirect, Route } from 'react-router-dom';
329+
\`\`\`
330+
**CODEMOD**:
331+
\`\`\`typescript
332+
import type { API, FileInfo, Options, Transform } from 'jscodeshift';
333+
function transform(file: FileInfo, api: API, options: Options): string | undefined {
334+
const j = api.jscodeshift;
335+
const root = j(file.source);
336+
root.find(j.ImportDeclaration, {
337+
source: { value: 'react-router' }
338+
}).forEach((path) => {
339+
path.value.source.value = 'react-router-dom';
340+
});
341+
return root.toSource(options);
342+
}
343+
export default transform;
344+
\`\`\`
345+
346+
## Additional API about jscodeshift
347+
### closestScope: Finds the closest enclosing scope of a node. Useful for determining the scope context of variables and functions.
348+
\`\`\`typescript
349+
const closestScopes = j.find(j.Identifier).closestScope();
350+
\`\`\`
351+
352+
### some: checks if at least one element in the collection passes the test implemented by the provided function.
353+
\`\`\`typescript
354+
const hasVariableA = root.find(j.VariableDeclarator).some(path => path.node.id.name === 'a');
355+
\`\`\`
356+
357+
### map: Maps each node in the collection to a new value.
358+
\`\`\`typescript
359+
const variableNames = j.find(j.VariableDeclaration).map(path => path.node.declarations.map(decl => decl.id.name));
360+
\`\`\`
361+
362+
### paths: Returns the paths of the found nodes.
363+
\`\`\`typescript
364+
const paths = j.find(j.VariableDeclaration).paths();
365+
\`\`\`
366+
367+
### get: Gets the first node in the collection.
368+
\`\`\`typescript
369+
const firstVariableDeclaration = j.find(j.VariableDeclaration).get();
370+
\`\`\`
371+
372+
### at: Navigates to a specific path in the AST.
373+
\`\`\`typescript
374+
const secondVariableDeclaration = j.find(j.VariableDeclaration).at(1);
375+
\`\`\`
376+
377+
### isOfType: checks if the node in the collection is of a specific type.
378+
\`\`\`typescript
379+
const isVariableDeclarator = root.find(j.VariableDeclarator).at(0).isOfType('VariableDeclarator');
380+
\`\`\`
381+
`;
382+
383+
export function getCodemodPrompt(
384+
type: "generate",
385+
testCases: { before: string; after: string }[],
386+
): string;
387+
export function getCodemodPrompt(
388+
type: "improve",
389+
testCases: { before: string; after: string }[],
390+
existingCodemodSource: string,
391+
): string;
392+
export function getCodemodPrompt(
393+
type: "improve" | "generate",
394+
testCases: { before: string; after: string }[],
395+
existingCodemodSource?: string,
396+
) {
397+
return `${type === "generate" ? generateCodemodContext : improveCodemodContext}
398+
399+
${
400+
type === "improve"
401+
? `\n\n### Existing Codemod
402+
\`\`\`typescript
403+
${existingCodemodSource}
404+
\`\`\`
405+
`
406+
: ""
407+
}
408+
409+
### Input Snippets
410+
411+
412+
${testCases
413+
.map(
414+
({ before, after }, i) => `## Input ${i + 1}
415+
**BEFORE**:
416+
\`\`\`typescript
417+
${before}
418+
\`\`\`
419+
**AFTER**:
420+
\`\`\`typescript
421+
${after}
422+
\`\`\`
423+
`,
424+
)
425+
.join("\n")}
426+
427+
428+
${jscodeshiftUsageExamples}
429+
`;
430+
}

0 commit comments

Comments
 (0)