feat(openapi): implement rest api tool (part 3)#386
Open
AmaadMartin wants to merge 5 commits into
Open
Conversation
added 2 commits
June 8, 2026 18:31
This implements RestApiTool for executing operations defined in OpenAPI specs. TAG=agy CONV=5d70d8ac-425c-4f8c-a969-97b17ecc66ce
…t 3) - Imported OperationEndpoint from openapi_spec_parser instead of redefining it. - Exported RestApiTool in common.ts. - Changed relative imports in rest_api_tool_test.ts to import from @google/adk. TAG=agy CONV=5d70d8ac-425c-4f8c-a969-97b17ecc66ce
b45cb71 to
e67c93f
Compare
kalenkevich
reviewed
Jun 16, 2026
Comment on lines
+122
to
+181
| const method = this.endpoint.method.toUpperCase(); | ||
| let url = `${this.endpoint.baseUrl}${this.endpoint.path}`; | ||
|
|
||
| const headers: Record<string, string> = {}; | ||
|
|
||
| const queryParams = new URLSearchParams(); | ||
| let body: unknown = undefined; | ||
|
|
||
| const parameters = this.operationParser.getParameters(); | ||
| const paramsMap = new Map(parameters.map((p) => [p.name, p])); | ||
|
|
||
| const pathParams: Record<string, string> = {}; | ||
| const bodyData: Record<string, unknown> = {}; | ||
|
|
||
| for (const [argName, argValue] of Object.entries(args)) { | ||
| const param = paramsMap.get(argName); | ||
| if (!param) continue; | ||
|
|
||
| const originalName = param.originalName; | ||
| const location = param.paramLocation; | ||
|
|
||
| if (location === 'path') { | ||
| pathParams[originalName] = String(argValue); | ||
| } else if (location === 'query') { | ||
| queryParams.append(originalName, String(argValue)); | ||
| } else if (location === 'header') { | ||
| headers[originalName] = String(argValue); | ||
| } else if (location === 'body') { | ||
| if ( | ||
| originalName === 'body' || | ||
| originalName === 'array' || | ||
| originalName === '' | ||
| ) { | ||
| body = argValue; | ||
| } else { | ||
| bodyData[originalName] = argValue; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Replace path parameters | ||
| for (const [key, value] of Object.entries(pathParams)) { | ||
| url = url.replace(`{${key}}`, value); | ||
| } | ||
|
|
||
| // Extract query parameters from path if any | ||
| const urlParts = url.split('?'); | ||
| if (urlParts.length > 1) { | ||
| const pathQueryParams = new URLSearchParams(urlParts[1]); | ||
| for (const [key, value] of pathQueryParams.entries()) { | ||
| queryParams.append(key, value); | ||
| } | ||
| url = urlParts[0]; | ||
| } | ||
|
|
||
| // Append query parameters | ||
| const queryString = queryParams.toString(); | ||
| if (queryString) { | ||
| url += `?${queryString}`; | ||
| } |
Collaborator
There was a problem hiding this comment.
move this to separate util function
Collaborator
Author
There was a problem hiding this comment.
Done. Extracted this logic into a standalone helper function 'prepareRequestParams'.
Comment on lines
+184
to
+235
| const requestBody = this.operation.requestBody; | ||
| const finalData = | ||
| body !== undefined | ||
| ? body | ||
| : Object.keys(bodyData).length > 0 | ||
| ? bodyData | ||
| : undefined; | ||
|
|
||
| if (requestBody && 'content' in requestBody) { | ||
| const content = requestBody.content; | ||
| for (const [mimeType, _mediaTypeObject] of Object.entries(content)) { | ||
| if (finalData !== undefined) { | ||
| if (mimeType === 'application/json' || mimeType.endsWith('+json')) { | ||
| body = | ||
| typeof finalData === 'string' | ||
| ? finalData | ||
| : JSON.stringify(finalData); | ||
| headers['Content-Type'] = mimeType; | ||
| } else if (mimeType === 'application/x-www-form-urlencoded') { | ||
| body = new URLSearchParams(finalData as Record<string, string>); | ||
| // Fetch sets content-type automatically for URLSearchParams | ||
| } else if (mimeType === 'multipart/form-data') { | ||
| const formData = new FormData(); | ||
| if (typeof finalData === 'object' && finalData !== null) { | ||
| for (const [key, value] of Object.entries(finalData)) { | ||
| formData.append(key, String(value)); | ||
| } | ||
| } | ||
| body = formData; | ||
| // Fetch sets content-type with boundary automatically. DO NOT set it. | ||
| } else if (mimeType === 'text/plain') { | ||
| body = String(finalData); | ||
| headers['Content-Type'] = mimeType; | ||
| } | ||
| } | ||
| break; // Process only the first mime type | ||
| } | ||
| } else if (finalData !== undefined) { | ||
| // Fallback to JSON if no requestBody content specified but data exists | ||
| body = | ||
| typeof finalData === 'string' ? finalData : JSON.stringify(finalData); | ||
| headers['Content-Type'] = 'application/json'; | ||
| } | ||
|
|
||
| // Handle Auth | ||
| url = applyCredential(url, headers, credential, this.authScheme); | ||
|
|
||
| // Apply dynamic headers from provider | ||
| if (this.headerProvider) { | ||
| const providerHeaders = this.headerProvider(context); | ||
| Object.assign(headers, providerHeaders); | ||
| } |
Collaborator
There was a problem hiding this comment.
same for body, move this body creating to separate function
Collaborator
Author
There was a problem hiding this comment.
Done. Extracted body formatting logic into a standalone helper function 'prepareRequestBody'.
kalenkevich
reviewed
Jun 16, 2026
| } | ||
|
|
||
| @experimental | ||
| public static fromParsedOperation( |
Collaborator
There was a problem hiding this comment.
avoid using static class methods, let it be just a util function that creates the RestApiTool
Collaborator
Author
There was a problem hiding this comment.
Done. Removed the static class method and replaced it with a standalone exported utility function 'createRestApiTool'.
…4) (google#387) * feat(openapi): implement openapi toolset and integration tests (part 4) This implements OpenAPIToolset and adds integration tests and fixtures. TAG=agy CONV=5d70d8ac-425c-4f8c-a969-97b17ecc66ce * refactor(openapi): resolve relative imports in OpenAPIToolset tests (part 4) - Exported OpenAPIToolset in common.ts. - Refactored relative imports inside openapi_toolset_test.ts and openapi_toolset_integration_test.ts to import from @google/adk. TAG=agy CONV=5d70d8ac-425c-4f8c-a969-97b17ecc66ce --------- Co-authored-by: Amaad Martin <amaadmartin@google.com>
…dy parsing helpers, replace static class method with standalone createRestApiTool utility
1fa412e to
1f19d2e
Compare
kalenkevich
approved these changes
Jun 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Link to Issue or Description of Change
Problem:
Developers lacked a dynamic
BaseToolclass capable of mapping LLM function call arguments to REST API paths, headers, query params, and payloads.Solution:
Implemented
RestApiTool(Part 3 of 4):rest_api_tool.ts: A robust tool implementation that leverages operation definitions to format HTTP requests, resolve credentials usingToolAuthHandler, execute API calls, and format responses.Testing Plan
Unit Tests:
Summary of passed results:
rest_api_tool.ts: 95.78%Checklist