Skip to content

Commit 06023a6

Browse files
authored
Merge pull request #973 from RNViththagan/copilot-agent
Add ConnectorGeneratorTool for dynamic OpenAPI connector generation
2 parents eb58d14 + 2c2685a commit 06023a6

File tree

12 files changed

+1723
-65
lines changed

12 files changed

+1723
-65
lines changed

workspaces/ballerina/ballerina-core/src/state-machine-types.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ export type ChatNotify =
204204
| EvalsToolResult
205205
| UsageMetricsEvent
206206
| TaskApprovalRequest
207-
| GeneratedSourcesEvent;
207+
| GeneratedSourcesEvent
208+
| ConnectorGenerationNotification;
208209

209210
export interface ChatStart {
210211
type: "start";
@@ -295,6 +296,31 @@ export interface GeneratedSourcesEvent {
295296
fileArray: SourceFile[];
296297
}
297298

299+
export interface ConnectorGenerationNotification {
300+
type: "connector_generation_notification";
301+
requestId: string;
302+
stage: "requesting_input" | "input_received" | "generating" | "generated" | "skipped" | "error";
303+
serviceName?: string;
304+
serviceDescription?: string;
305+
spec?: {
306+
version: string;
307+
title: string;
308+
description?: string;
309+
baseUrl?: string;
310+
endpointCount: number;
311+
methods: string[];
312+
};
313+
connector?: {
314+
moduleName: string;
315+
importStatement: string;
316+
};
317+
error?: {
318+
message: string;
319+
code: string;
320+
};
321+
message: string;
322+
}
323+
298324
export const stateChanged: NotificationType<MachineStateValue> = { method: 'stateChanged' };
299325
export const onDownloadProgress: NotificationType<DownloadProgress> = { method: 'onDownloadProgress' };
300326
export const onChatNotify: NotificationType<ChatNotify> = { method: 'onChatNotify' };
@@ -374,6 +400,7 @@ export type AIChatMachineStateValue =
374400
| 'TaskReview'
375401
| 'ApprovedTask'
376402
| 'RejectedTask'
403+
| 'WaitingForConnectorSpec'
377404
| 'Completed'
378405
| 'PartiallyCompleted'
379406
| 'Error';
@@ -396,6 +423,9 @@ export enum AIChatMachineEventType {
396423
RESTORE_STATE = 'RESTORE_STATE',
397424
ERROR = 'ERROR',
398425
RETRY = 'RETRY',
426+
CONNECTOR_GENERATION_REQUESTED = 'CONNECTOR_GENERATION_REQUESTED',
427+
PROVIDE_CONNECTOR_SPEC = 'PROVIDE_CONNECTOR_SPEC',
428+
SKIP_CONNECTOR_GENERATION = 'SKIP_CONNECTOR_GENERATION',
399429
}
400430

401431
export interface ChatMessage {
@@ -459,6 +489,14 @@ export interface AIChatMachineContext {
459489
projectId?: string;
460490
currentApproval?: UserApproval;
461491
autoApproveEnabled?: boolean;
492+
currentSpec?: {
493+
requestId: string;
494+
spec?: any;
495+
provided?: boolean;
496+
skipped?: boolean;
497+
comment?: string;
498+
};
499+
previousState?: AIChatMachineStateValue;
462500
}
463501

464502
export type AIChatMachineSendableEvent =
@@ -478,7 +516,10 @@ export type AIChatMachineSendableEvent =
478516
| { type: AIChatMachineEventType.RESET }
479517
| { type: AIChatMachineEventType.RESTORE_STATE; payload: { state: AIChatMachineContext } }
480518
| { type: AIChatMachineEventType.ERROR; payload: { message: string } }
481-
| { type: AIChatMachineEventType.RETRY };
519+
| { type: AIChatMachineEventType.RETRY }
520+
| { type: AIChatMachineEventType.CONNECTOR_GENERATION_REQUESTED; payload: { requestId: string; serviceName?: string; serviceDescription?: string; fromState?: AIChatMachineStateValue } }
521+
| { type: AIChatMachineEventType.PROVIDE_CONNECTOR_SPEC; payload: { requestId: string; spec: any; inputMethod: 'file' | 'paste' | 'url'; sourceIdentifier?: string } }
522+
| { type: AIChatMachineEventType.SKIP_CONNECTOR_GENERATION; payload: { requestId: string; comment?: string } };
482523

483524
export enum LoginMethod {
484525
BI_INTEL = 'biIntel',
@@ -546,3 +587,14 @@ export const aiChatStateChanged: NotificationType<AIChatMachineStateValue> = { m
546587
export const sendAIChatStateEvent: RequestType<AIChatMachineEventType | AIChatMachineSendableEvent, void> = { method: 'sendAIChatStateEvent' };
547588
export const getAIChatContext: RequestType<void, AIChatMachineContext> = { method: 'getAIChatContext' };
548589
export const getAIChatUIHistory: RequestType<void, UIChatHistoryMessage[]> = { method: 'getAIChatUIHistory' };
590+
591+
// Connector Generator RPC methods
592+
export interface ConnectorGeneratorResponsePayload {
593+
requestId: string;
594+
action: 'provide' | 'skip';
595+
spec?: any;
596+
inputMethod?: 'file' | 'paste' | 'url';
597+
sourceIdentifier?: string;
598+
comment?: string;
599+
}
600+
export const sendConnectorGeneratorResponse: RequestType<ConnectorGeneratorResponsePayload, void> = { method: 'sendConnectorGeneratorResponse' };

workspaces/ballerina/ballerina-extension/src/features/ai/service/design/design.ts

Lines changed: 25 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ import { getLibraryProviderTool } from "../libs/libraryProviderTool";
2828
import { GenerationType, getAllLibraries, LIBRARY_PROVIDER_TOOL } from "../libs/libs";
2929
import { Library } from "../libs/libs_types";
3030
import { AIChatStateMachine } from "../../../../views/ai-panel/aiChatMachine";
31-
import { getTempProject, FileModificationInfo } from "../../utils/temp-project-utils";
32-
import { formatCodebaseStructure } from "./utils";
31+
import { getTempProject } from "../../utils/temp-project-utils";
3332
import { getSystemPrompt, getUserPrompt } from "./prompts";
33+
import { createConnectorGeneratorTool, CONNECTOR_GENERATOR_TOOL } from "../libs/connectorGeneratorTool";
3434
import { LangfuseExporter } from 'langfuse-vercel';
3535
import { NodeSDK } from '@opentelemetry/sdk-node';
3636
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
@@ -61,7 +61,7 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
6161

6262
const modifiedFiles: string[] = [];
6363

64-
const userMessageContent = getUserPrompt(params.usecase, hasHistory, tempProjectPath);
64+
const userMessageContent = getUserPrompt(params.usecase, hasHistory, tempProjectPath, project.projectName);
6565
const allMessages: ModelMessage[] = [
6666
{
6767
role: "system",
@@ -84,6 +84,7 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
8484
const tools = {
8585
[TASK_WRITE_TOOL_NAME]: createTaskWriteTool(eventHandler, tempProjectPath, modifiedFiles),
8686
[LIBRARY_PROVIDER_TOOL]: getLibraryProviderTool(libraryDescriptions, GenerationType.CODE_GENERATION),
87+
[CONNECTOR_GENERATOR_TOOL]: createConnectorGeneratorTool(eventHandler, tempProjectPath, project.projectName, modifiedFiles),
8788
[FILE_WRITE_TOOL_NAME]: createWriteTool(createWriteExecute(tempProjectPath, modifiedFiles)),
8889
[FILE_SINGLE_EDIT_TOOL_NAME]: createEditTool(createEditExecute(tempProjectPath, modifiedFiles)),
8990
[FILE_BATCH_EDIT_TOOL_NAME]: createBatchEditTool(createMultiEditExecute(tempProjectPath, modifiedFiles)),
@@ -128,7 +129,14 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
128129

129130
if (toolName === "LibraryProviderTool") {
130131
selectedLibraries = (part.input as any)?.libraryNames || [];
131-
} else if ([FILE_WRITE_TOOL_NAME, FILE_SINGLE_EDIT_TOOL_NAME, FILE_BATCH_EDIT_TOOL_NAME, FILE_READ_TOOL_NAME].includes(toolName)) {
132+
} else if (
133+
[
134+
FILE_WRITE_TOOL_NAME,
135+
FILE_SINGLE_EDIT_TOOL_NAME,
136+
FILE_BATCH_EDIT_TOOL_NAME,
137+
FILE_READ_TOOL_NAME,
138+
].includes(toolName)
139+
) {
132140
const input = part.input as any;
133141
if (input && input.file_path) {
134142
let fileName = input.file_path;
@@ -157,8 +165,14 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
157165
} else if (toolName === "LibraryProviderTool") {
158166
const libraryNames = (part.output as Library[]).map((lib) => lib.name);
159167
const fetchedLibraries = libraryNames.filter((name) => selectedLibraries.includes(name));
160-
}
161-
else if ([FILE_WRITE_TOOL_NAME, FILE_SINGLE_EDIT_TOOL_NAME, FILE_BATCH_EDIT_TOOL_NAME, FILE_READ_TOOL_NAME].includes(toolName)) {
168+
} else if (
169+
[
170+
FILE_WRITE_TOOL_NAME,
171+
FILE_SINGLE_EDIT_TOOL_NAME,
172+
FILE_BATCH_EDIT_TOOL_NAME,
173+
FILE_READ_TOOL_NAME,
174+
].includes(toolName)
175+
) {
162176
} else {
163177
eventHandler({ type: "tool_result", toolName });
164178
}
@@ -170,6 +184,10 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
170184
eventHandler({ type: "error", content: getErrorMessage(error) });
171185
break;
172186
}
187+
case "text-start": {
188+
eventHandler({ type: "content_block", content: " \n" });
189+
break;
190+
}
173191
case "abort": {
174192
console.log("[Design] Aborted by user.");
175193
let messagesToSave: any[] = [];
@@ -221,7 +239,7 @@ Generation stopped by user. The last in-progress task was not saved. Files have
221239
await langfuseExporter.forceFlush();
222240
break;
223241
}
224-
}
242+
}
225243
}
226244
}
227245

@@ -308,37 +326,3 @@ function saveToolResult(
308326
}]
309327
});
310328
}
311-
312-
/**
313-
* Formats file modifications into XML structure for Claude
314-
* TODO: This function is currently not used. Can be removed if workspace modification
315-
* tracking is not needed in the future.
316-
*/
317-
function formatModifications(modifications: FileModificationInfo[]): string {
318-
if (modifications.length === 0) {
319-
return '';
320-
}
321-
322-
const modifiedFiles = modifications.filter(m => m.type === 'modified').map(m => m.filePath);
323-
const newFiles = modifications.filter(m => m.type === 'new').map(m => m.filePath);
324-
const deletedFiles = modifications.filter(m => m.type === 'deleted').map(m => m.filePath);
325-
326-
let text = '<workspace_changes>\n';
327-
text += 'The following changes were detected in the workspace since the last session. ';
328-
text += 'You do not need to acknowledge or repeat these changes in your response. ';
329-
text += 'This information is provided for your awareness only.\n\n';
330-
331-
if (modifiedFiles.length > 0) {
332-
text += '<modified_files>\n' + modifiedFiles.join('\n') + '\n</modified_files>\n\n';
333-
}
334-
if (newFiles.length > 0) {
335-
text += '<new_files>\n' + newFiles.join('\n') + '\n</new_files>\n\n';
336-
}
337-
if (deletedFiles.length > 0) {
338-
text += '<deleted_files>\n' + deletedFiles.join('\n') + '\n</deleted_files>\n\n';
339-
}
340-
341-
text += '</workspace_changes>';
342-
return text;
343-
}
344-

workspaces/ballerina/ballerina-extension/src/features/ai/service/design/prompts.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DIAGNOSTICS_TOOL_NAME } from "../libs/diagnostics_tool";
22
import { LIBRARY_PROVIDER_TOOL } from "../libs/libs";
33
import { TASK_WRITE_TOOL_NAME } from "../libs/task_write_tool";
44
import { FILE_BATCH_EDIT_TOOL_NAME, FILE_SINGLE_EDIT_TOOL_NAME, FILE_WRITE_TOOL_NAME } from "../libs/text_editor_tool";
5+
import { CONNECTOR_GENERATOR_TOOL } from "../libs/connectorGeneratorTool";
56
import { formatCodebaseStructure } from "./utils";
67

78
/**
@@ -64,8 +65,11 @@ This plan will be visible to the user and the execution will be guided on the ta
6465
5. Once plan is APPROVED (success: true in tool response), IMMEDIATELY start the execution cycle:
6566
6667
**For each task:**
67-
- Mark task as in_progress using ${TASK_WRITE_TOOL_NAME} (send ALL tasks)
68+
- Mark task as in_progress using ${TASK_WRITE_TOOL_NAME} and immediately start implementation in parallel (single message with multiple tool calls)
6869
- Implement the task completely (write the Ballerina code)
70+
- When implementing external API integrations:
71+
- First check ${LIBRARY_PROVIDER_TOOL} for known services (Stripe, GitHub, etc.)
72+
- If NOT available, call ${CONNECTOR_GENERATOR_TOOL} to generate connector from OpenAPI spec
6973
- Before marking the task as completed, use the ${DIAGNOSTICS_TOOL_NAME} tool to check for compilation errors and fix them. Introduce a a new subtask if needed to fix errors.
7074
- Mark task as completed using ${TASK_WRITE_TOOL_NAME} (send ALL tasks)
7175
- The tool will wait for TASK COMPLETION APPROVAL from the user
@@ -94,6 +98,8 @@ When generating Ballerina code strictly follow these syntax and structure guidel
9498
- In the library API documentation, if the service type is specified as generic, adhere to the instructions specified there on writing the service.
9599
- For GraphQL service related queries, if the user hasn't specified their own GraphQL Schema, write the proposed GraphQL schema for the user query right after the explanation before generating the Ballerina code. Use the same names as the GraphQL Schema when defining record types.
96100
101+
### Local Connectors
102+
- If the codebase structure shows connector modules in generated/moduleName, import using: import packageName.moduleName
97103
98104
### Code Structure
99105
- Define required configurables for the query. Use only string, int, decimal, boolean types in configurable variables.
@@ -117,7 +123,13 @@ When generating Ballerina code strictly follow these syntax and structure guidel
117123
- To narrow down a union type(or optional type), always declare a separate variable and then use that variable in the if condition.
118124
119125
### File modifications
120-
- You must apply changes to the existing source code using the provided ${[FILE_BATCH_EDIT_TOOL_NAME, FILE_SINGLE_EDIT_TOOL_NAME, FILE_WRITE_TOOL_NAME].join(", ")} tools. The complete existing source code will be provided in the <existing_code> section of the user prompt.
126+
- You must apply changes to the existing source code using the provided ${[
127+
FILE_BATCH_EDIT_TOOL_NAME,
128+
FILE_SINGLE_EDIT_TOOL_NAME,
129+
FILE_WRITE_TOOL_NAME,
130+
].join(
131+
", "
132+
)} tools. The complete existing source code will be provided in the <existing_code> section of the user prompt.
121133
- When making replacements inside an existing file, provide the **exact old string** and the **exact new string** with all newlines, spaces, and indentation, being mindful to replace nearby occurrences together to minimize the number of tool calls.
122134
- Do not modify documentation such as .md files unless explicitly asked to be modified in the query.
123135
- Do not add/modify toml files (Config.toml/Ballerina.toml/Dependencies.toml).
@@ -130,14 +142,15 @@ When generating Ballerina code strictly follow these syntax and structure guidel
130142
* @param usecase User's query/requirement
131143
* @param hasHistory Whether chat history exists
132144
* @param tempProjectPath Path to temp project (used when hasHistory is false)
145+
* @param packageName Name of the Ballerina package
133146
*/
134-
export function getUserPrompt(usecase: string, hasHistory: boolean, tempProjectPath: string) {
147+
export function getUserPrompt(usecase: string, hasHistory: boolean, tempProjectPath: string, packageName: string) {
135148
const content = [];
136149

137150
if (!hasHistory) {
138151
content.push({
139152
type: 'text' as const,
140-
text: formatCodebaseStructure(tempProjectPath)
153+
text: formatCodebaseStructure(tempProjectPath, packageName)
141154
});
142155
}
143156

0 commit comments

Comments
 (0)