Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions workspaces/mi/mi-core/src/rpc-types/mi-diagram/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ import {
DriverDownloadResponse,
DriverMavenCoordinatesRequest,
DriverMavenCoordinatesResponse,
OpenTryItRequest,
GetConnectorDependenciesRequest,
GetConnectorDependenciesResponse,
UpdateConnectorDependencyOverrideRequest,
Expand Down Expand Up @@ -411,6 +412,7 @@ export interface MiDiagramAPI {
checkOldProject: () => Promise<boolean>;
refreshAccessToken: () => void;
getOpenAPISpec: (params: SwaggerTypeRequest) => Promise<SwaggerFromAPIResponse>;
openTryIt: (params: OpenTryItRequest) => void;
editOpenAPISpec: (params: SwaggerTypeRequest) => void;
compareSwaggerAndAPI: (params: SwaggerTypeRequest) => Promise<CompareSwaggerAndAPIResponse>;
updateSwaggerFromAPI: (params: SwaggerTypeRequest) => void;
Expand Down
2 changes: 2 additions & 0 deletions workspaces/mi/mi-core/src/rpc-types/mi-diagram/rpc-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ import {
UpdateRegistryPropertyRequest,
GenerateMappingsParamsRequest,
ProjectCreationStatusResponse,
OpenTryItRequest,
GetConnectorDependenciesRequest,
GetConnectorDependenciesResponse,
UpdateConnectorDependencyOverrideRequest,
Expand Down Expand Up @@ -418,6 +419,7 @@ export const exportProject: NotificationType<ExportProjectRequest> = { method: `
export const checkOldProject: RequestType<void, boolean> = { method: `${_preFix}/checkOldProject` };
export const refreshAccessToken: NotificationType<void> = { method: `${_preFix}/refreshAccessToken` };
export const getOpenAPISpec: RequestType<SwaggerTypeRequest, SwaggerFromAPIResponse> = { method: `${_preFix}/getOpenAPISpec` };
export const openTryIt: NotificationType<OpenTryItRequest> = { method: `${_preFix}/openTryIt` };
export const editOpenAPISpec: NotificationType<SwaggerTypeRequest> = { method: `${_preFix}/editOpenAPISpec` };
export const compareSwaggerAndAPI: RequestType<SwaggerTypeRequest, CompareSwaggerAndAPIResponse> = { method: `${_preFix}/compareSwaggerAndAPI` };
export const updateSwaggerFromAPI: NotificationType<SwaggerTypeRequest> = { method: `${_preFix}/updateSwaggerFromAPI` };
Expand Down
4 changes: 4 additions & 0 deletions workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1849,6 +1849,10 @@ export interface SwaggerTypeRequest {
isRuntimeService?: boolean;
}

export interface OpenTryItRequest extends SwaggerTypeRequest {
apiUrl: string;
}

export interface SwaggerFromAPIResponse {
generatedSwagger: any;
}
Expand Down
3 changes: 2 additions & 1 deletion workspaces/mi/mi-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"onUri"
],
"extensionDependencies": [
"wso2.wso2-integrator"
"wso2.wso2-integrator",
"wso2.hurl-client"
],
"main": "./dist/extension.js",
"contributes": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ import { ChatHistoryManager, SessionContextBlocksState, TOOL_USE_INTERRUPTION_CO
import { getToolAction } from '../../tool-action-mapper';
import { AgentUndoCheckpointManager } from '../../undo/checkpoint-manager';
import { getCopilotSessionDir } from '../../storage-paths';
import { ShellApprovalRuleStore } from '../../tools/types';
import { HURL_TOOL_NAME, ShellApprovalRuleStore } from '../../tools/types';
import { WebToolsProvider } from '../../tools/web_tools';
import {
awaitWithTimeout,
Expand Down Expand Up @@ -1236,6 +1236,8 @@ export async function executeAgent(
repoName: toolInput?.repoName,
question: toolInput?.question,
};
} else if (part.toolName === HURL_TOOL_NAME) {
displayInput = toolInput
}

// Skip tool call UI for todo_write (handled by inline todo list)
Expand Down Expand Up @@ -1293,6 +1295,10 @@ export async function executeAgent(
toolResultEvent.bashExitCode = result.exitCode;
toolResultEvent.bashRunning = !!result.taskId;
}
// Add full tool output for HURL tool (handled by custom UI component)
else if (part.toolName === HURL_TOOL_NAME) {
toolResultEvent.toolOutput = result;
}

// Send to visualizer with result action for display
emitEvent(toolResultEvent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
DEEPWIKI_ASK_QUESTION_TOOL_NAME,
READ_SERVER_LOGS_TOOL_NAME,
TOOL_LOAD_TOOL_NAME,
HURL_TOOL_NAME,
} from '../../tools/types';
import { SYNAPSE_GUIDE } from '../../context/synapse_guide';
import { SYNAPSE_GUIDE as SYNAPSE_GUIDE_OLD } from '../../context/synapse_guide_old';
Expand Down Expand Up @@ -214,7 +215,7 @@ The user's IDE selection (if any) is included in the conversation context and ma
- Use ${SERVER_MANAGEMENT_TOOL_NAME} for status checks, run/stop control. Use action='query' to inspect deployed artifacts, action='control' to activate/deactivate, enable tracing, or set log levels.
- If testing requires API keys/credentials or can't be done locally, explain this and ask the user to test manually. Do not attempt credential-dependent tests.
- **Selective deployment**: To test specific artifacts when full build is slow/broken, rename unneeded XMLs with \`.disabled\` suffix, build and deploy, then always restore originals before ending — including on abort/error. Log renamed files if cleanup fails.
- Test with ${BASH_TOOL_NAME} if possible. If server errors persist that you cannot fix, end the task and ask user to fix manually.
- Use ${HURL_TOOL_NAME} to explore and interact with HTTP APIs by sending requests and observing responses. It is not intended for writing API test suites, assertions. If the tool cannot fulfill the request or lacks the needed capability, fall back to ${BASH_TOOL_NAME} with curl. If server errors persist that you cannot fix, end the task and ask user to fix manually.

## Clean up
- Shutdown the server using ${SERVER_MANAGEMENT_TOOL_NAME} before ending the task.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ import {
createReadServerLogsTool,
createReadServerLogsExecute,
} from '../../tools/log_tools';
import { createHurlTool } from '../../tools/hurl_tool';
import {
createDeepWikiTool,
createDeepWikiExecute,
Expand Down Expand Up @@ -133,6 +134,7 @@ import {
WEB_FETCH_TOOL_NAME,
DEEPWIKI_ASK_QUESTION_TOOL_NAME,
READ_SERVER_LOGS_TOOL_NAME,
HURL_TOOL_NAME,
TOOL_LOAD_TOOL_NAME,
ShellApprovalRuleStore,
DEFERRED_TOOLS,
Expand Down Expand Up @@ -170,6 +172,7 @@ export {
WEB_FETCH_TOOL_NAME,
DEEPWIKI_ASK_QUESTION_TOOL_NAME,
READ_SERVER_LOGS_TOOL_NAME,
HURL_TOOL_NAME,
TOOL_LOAD_TOOL_NAME,
};
import { AgentEventHandler } from './agent';
Expand Down Expand Up @@ -719,6 +722,9 @@ export function createAgentTools(params: CreateToolsParams) {
getWrappedExecute(READ_SERVER_LOGS_TOOL_NAME, createReadServerLogsExecute(projectPath))
),

// HTTP Tool (1 tool)
[HURL_TOOL_NAME]: createHurlTool(),

// Shell Tools (3 tools)
[BASH_TOOL_NAME]: createBashTool(
getWrappedExecute(BASH_TOOL_NAME, createBashExecute(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* under the License.
*/

import { MANAGE_CONNECTOR_TOOL_NAME, ASK_USER_TOOL_NAME, BUILD_AND_DEPLOY_TOOL_NAME, CONNECTOR_TOOL_NAME, CONTEXT_TOOL_NAME, CREATE_DATA_MAPPER_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, FILE_EDIT_TOOL_NAME, FILE_GLOB_TOOL_NAME, FILE_GREP_TOOL_NAME, FILE_READ_TOOL_NAME, FILE_WRITE_TOOL_NAME, GENERATE_DATA_MAPPING_TOOL_NAME, SERVER_MANAGEMENT_TOOL_NAME, SUBAGENT_TOOL_NAME, TODO_WRITE_TOOL_NAME, VALIDATE_CODE_TOOL_NAME, BASH_TOOL_NAME, KILL_TASK_TOOL_NAME, TASK_OUTPUT_TOOL_NAME, WEB_SEARCH_TOOL_NAME, WEB_FETCH_TOOL_NAME, DEEPWIKI_ASK_QUESTION_TOOL_NAME, READ_SERVER_LOGS_TOOL_NAME, TOOL_LOAD_TOOL_NAME } from './tools/types';
import { MANAGE_CONNECTOR_TOOL_NAME, ASK_USER_TOOL_NAME, BUILD_AND_DEPLOY_TOOL_NAME, CONNECTOR_TOOL_NAME, CONTEXT_TOOL_NAME, CREATE_DATA_MAPPER_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, FILE_EDIT_TOOL_NAME, FILE_GLOB_TOOL_NAME, FILE_GREP_TOOL_NAME, FILE_READ_TOOL_NAME, FILE_WRITE_TOOL_NAME, GENERATE_DATA_MAPPING_TOOL_NAME, SERVER_MANAGEMENT_TOOL_NAME, SUBAGENT_TOOL_NAME, TODO_WRITE_TOOL_NAME, VALIDATE_CODE_TOOL_NAME, BASH_TOOL_NAME, KILL_TASK_TOOL_NAME, TASK_OUTPUT_TOOL_NAME, WEB_SEARCH_TOOL_NAME, WEB_FETCH_TOOL_NAME, DEEPWIKI_ASK_QUESTION_TOOL_NAME, READ_SERVER_LOGS_TOOL_NAME, HURL_TOOL_NAME, TOOL_LOAD_TOOL_NAME } from './tools/types';
/**
* Tool action states for UI display
*/
Expand Down Expand Up @@ -269,6 +269,13 @@ export function getToolAction(toolName: string, toolResult?: any, toolInput?: an
failed: 'web fetch failed'
};

case HURL_TOOL_NAME:
return {
loading: 'sending HTTP requests',
completed: 'received HTTP responses',
failed: 'HTTP request execution failed'
};

// Tool Loading (local — deferred tool loading)
case TOOL_LOAD_TOOL_NAME:
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com/) All Rights Reserved.
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { tool } from 'ai';
import { z } from 'zod';
import { HURL_TOOL_NAME, HurlToolOutput } from './types';
import * as vscode from 'vscode';

export { HURL_TOOL_NAME };

const HURL_LM_TOOL_NAME = "run-hurl-test";
type RawHurlToolOutput = HurlToolOutput & {
output: HurlToolOutput["output"] & {
summary?: {
totalEntries: number;
passedEntries: number;
failedEntries: number;
};
};
};

const HURL_SCRIPT_DESCRIPTION = `The hurl script to execute. Hurl is a command-line tool for running HTTP requests written in a simple text format. A script can contain one or more requests.
Example Script:
GET http://example.com/api/resource
Accept: application/json

POST http://example.com/api
Content-Type: application/json

{
"name": "try-it"
}

When defining a request body in Hurl, you must follow strict syntax rules. For simple bodies (e.g., JSON), you can write them directly after a blank line. However, for complex or multi-line raw bodies (such as multipart/form-data, raw HTTP payloads, or content containing special characters), you MUST wrap the entire body inside triple backticks (\`\`\`).
Failing to do this will result in parsing errors (e.g., "invalid HTTP method").

Example (raw multipart body using triple backticks):
POST http://example.com/upload
Content-Type: multipart/form-data; boundary=----Boundary123

\`\`\`
------Boundary123
Content-Disposition: form-data; name="file"; filename="sample.txt"
Content-Type: text/plain

hello world
------Boundary123--
\`\`\`

Use triple backticks whenever the body spans multiple structured lines or includes boundary markers, binary-like content, or custom formatting.

Avoid using unnecessary newlines in the hurl script, as they can lead to parsing issues.
`;

// ============================================================================
// Tool factory
// ============================================================================

/**
* Creates the hurl tool using VSCode LM tool from hurl client.
*
* - Model calls hurl with the hurl script and tryItScenario
* - The tool executes the hurl script and returns the result back to the model
*/
export function createHurlTool() {
return (tool as any)({
description:
'A tool to execute Hurl scripts. The input is a Hurl script as a string. The output includes the execution results, including response details. Use this tool to try out HTTP endpoints. Prefer requests without assertions for simple try-it scenarios ( without including status code assertions such as HTTP 200 or other types of assertions)',
inputSchema: z.object({
hurlScript: z.string().describe(HURL_SCRIPT_DESCRIPTION),
tryItScenario: z.string().max(30).describe("A short title for the try-it scenario being executed. This is used for logging and reporting purposes. Keep it under 30 characters."),
}),
execute: async ({hurlScript, tryItScenario}: { hurlScript: string; tryItScenario: string }): Promise<HurlToolOutput> => {
try {
const lmToolResult = await vscode.lm.invokeTool(HURL_LM_TOOL_NAME, { input: { hurlScript }, toolInvocationToken: undefined });
const resultTextPart = (lmToolResult.content[0] as vscode.LanguageModelTextPart);
const response: RawHurlToolOutput = JSON.parse(resultTextPart.value);
// Remove `summary` to avoid implying test/assertion semantics.
// The tool is used only for trying requests, not API validation.
const { summary, ...outputWithoutSummary } = response.output;
return { ...response, output: outputWithoutSummary };
} catch (error) {
const genericErrorOutput: HurlToolOutput = {
input:{
requests: []
},
output: {
status: "error",
durationMs: 0,
entries: [],
warnings: [`Failed to execute Hurl script. Error: ${error instanceof Error ? error.message : String(error)}`]
}
};
return genericErrorOutput;
}
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const DEFERRED_TOOL_DESCRIPTIONS: Record<string, string> = {
create_data_mapper: 'Create a new data mapper with input/output schemas',
generate_data_mapping: 'Generate TypeScript field mappings for an existing data mapper',
server_management: 'Query/control MI server artifacts using WSO2 MI management API (status, query, activate/deactivate, log levels)',
hurl: 'Execute Hurl scripts to send HTTP requests and inspect responses',
enter_plan_mode: 'Enter planning phase for complex implementation tasks',
exit_plan_mode: 'Request plan approval from user',
ask_user_question: 'Ask user a clarification question with options',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export const CREATE_DATA_MAPPER_TOOL_NAME = 'create_data_mapper';
export const GENERATE_DATA_MAPPING_TOOL_NAME = 'generate_data_mapping';
export const BUILD_AND_DEPLOY_TOOL_NAME = 'build_and_deploy';
export const SERVER_MANAGEMENT_TOOL_NAME = 'server_management';
export const HURL_TOOL_NAME = 'hurl';

// Plan Mode Tool Names
export const SUBAGENT_TOOL_NAME = 'create_subagent';
Expand Down Expand Up @@ -155,6 +156,7 @@ export const DEFERRED_TOOLS = new Set<string>([
CREATE_DATA_MAPPER_TOOL_NAME,
GENERATE_DATA_MAPPING_TOOL_NAME,
SERVER_MANAGEMENT_TOOL_NAME,
HURL_TOOL_NAME,
ENTER_PLAN_MODE_TOOL_NAME,
EXIT_PLAN_MODE_TOOL_NAME,
ASK_USER_TOOL_NAME,
Expand Down Expand Up @@ -439,3 +441,43 @@ export type TaskOutputExecuteFn = (args: {
block?: boolean;
timeout?: number;
}) => Promise<TaskOutputResult>;

// ============================================================================
// Hurl Tool Types
// ============================================================================
export type HurlToolOutput = {
input: {
requests: Array<{
name: string;
method: string;
url: string;
headers: Array<{ key: string; value: string }>;
queryParameters: Array<{ key: string; value: string }>;
body?: string;
assertions?: string[];
}>;
};
output: {
status: string;
durationMs: number;
entries: Array<{
name: string;
method?: string;
url?: string;
statusCode?: number;
responseHeaders?: Array<{ name: string; value: string }>;
responseBody?: string;
status: string;
durationMs?: number;
assertions: Array<{
expression: string;
status: string;
expected?: string;
actual?: string;
message?: string;
}>;
errorMessage?: string;
}>;
warnings: string[];
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ import {
getMessageStore,
getMetadataOfRegistryResource,
getOpenAPISpec,
openTryIt,
getProjectRoot,
getProjectUuid,
getRecipientEndpoint,
Expand Down Expand Up @@ -338,6 +339,7 @@ import {
LoadDriverAndTestConnectionRequest,
canCreateConsolidatedProject,
createConsolidatedProjectFromWorkspace,
OpenTryItRequest,
getConnectorDependencies,
GetConnectorDependenciesRequest,
updateConnectorDependencyOverride,
Expand Down Expand Up @@ -485,6 +487,7 @@ export function registerMiDiagramRpcHandlers(messenger: Messenger, projectUri: s
messenger.onRequest(checkOldProject, () => rpcManger.checkOldProject());
messenger.onNotification(refreshAccessToken, () => rpcManger.refreshAccessToken());
messenger.onRequest(getOpenAPISpec, (args: SwaggerTypeRequest) => rpcManger.getOpenAPISpec(args));
messenger.onNotification(openTryIt, (args: OpenTryItRequest) => rpcManger.openTryIt(args));
messenger.onNotification(editOpenAPISpec, (args: SwaggerTypeRequest) => rpcManger.editOpenAPISpec(args));
messenger.onRequest(compareSwaggerAndAPI, (args: SwaggerTypeRequest) => rpcManger.compareSwaggerAndAPI(args));
messenger.onNotification(updateSwaggerFromAPI, (args: SwaggerTypeRequest) => rpcManger.updateSwaggerFromAPI(args));
Expand Down
Loading
Loading