feat(devtools): add Reasoning Engine server compatibility routes and headers parser (Agent Engine Deployment Artifact registry fix part 1)#440
Conversation
…oning Engine routes
| let session = await this.sessionService.getSession({ | ||
| appName, | ||
| userId, | ||
| sessionId, | ||
| }); | ||
| if (!session) { | ||
| this.logger.info( | ||
| `Session ${sessionId} not found. Creating it automatically.`, | ||
| ); | ||
| session = await this.sessionService.createSession({ | ||
| appName, | ||
| userId, | ||
| sessionId, | ||
| state: {}, | ||
| }); | ||
| } | ||
| await using agentFile = await this.agentLoader.getAgentFile(appName); | ||
| const agent = await agentFile.load(); | ||
| const runner = await this.getRunner(agent, appName); | ||
| const events: Event[] = []; | ||
| const abortController = new AbortController(); |
There was a problem hiding this comment.
we have getOrCreateSession
There was a problem hiding this comment.
Good point. Refactored to use the existing getOrCreateSession helper.
| await using agentFile = await this.agentLoader.getAgentFile(appName); | ||
| const agent = await agentFile.load(); | ||
| const runner = await this.getRunner(agent, appName); | ||
| const events: Event[] = []; | ||
| const abortController = new AbortController(); | ||
| req.on('close', () => { | ||
| abortController.abort(); | ||
| }); | ||
| for await (const e of runner.runAsync({ | ||
| userId, | ||
| sessionId, | ||
| newMessage, | ||
| stateDelta, | ||
| abortSignal: abortController.signal, | ||
| })) { | ||
| events.push(e); | ||
| } | ||
| res.json({output: events}); | ||
| } catch (e: unknown) { | ||
| const error = `Failed to run agent via Reasoning Engine API: ${e}`; | ||
| res.status(500).json({error}); | ||
| this.logger.error(error); |
There was a problem hiding this comment.
very similar to run_sse can we move the common logic to the same place?
There was a problem hiding this comment.
Refactored the core execution block (agent load, runner fetch, run loop, and event streaming) into a shared executeAgentRun helper. This helper is now reused in /run, /run_sse, and the Reasoning Engine query endpoints to keep them clean and consistent.
…xecution helper in api server
| return this.runnerCache[appName]; | ||
| } | ||
|
|
||
| private async executeAgentRun(options: { |
There was a problem hiding this comment.
make it as generator and then no need to introduce a onEvent callback.
There was a problem hiding this comment.
Implemented! Refactored executeAgentRun to be an async generator and delegate events using yield*. This cleaned up the callsites as well.
| newMessage: any; // eslint-disable-line @typescript-eslint/no-explicit-any | ||
| stateDelta?: any; // eslint-disable-line @typescript-eslint/no-explicit-any | ||
| runConfig?: any; // eslint-disable-line @typescript-eslint/no-explicit-any | ||
| abortSignal: AbortSignal; |
There was a problem hiding this comment.
do not use any type, please use real types instead
There was a problem hiding this comment.
Done. Resolved by using TypeScript indexed access types to extract the exact types from the Runner['runAsync'] parameter object (e.g., Parameters<Runner['runAsync']>[0]['newMessage'] for the newMessage field). This keeps the helper signature 100% type-safe and aligned with the runner's API without introducing duplicate type declarations or direct imports of internal SDK types.
There was a problem hiding this comment.
Update: Replaced the indexed parameter types with the cleaner, direct types: Content (imported from @google/genai), Record<string, unknown>, and RunConfig (imported from @google/adk). This makes the helper signature much more readable.
…dexed access types in helper
Please ensure you have read the contribution guide https://google.github.io/adk-docs/contributing-guide/ before creating a pull request.
Link to Issue or Description of Change
• Closes: #none
• Related: #none
Problem:
Vertex AI's Reasoning Engine execution gateway and liveness checks impose two key constraints:
/and/healthwithout redirects (Vertex AI kills containers that return 3xx codes, which broke ADK because it redirected/to/dev-ui).Content-Type: application/json,application/json), which causes Express's standard body-parser to skip parsing and results in empty request bodies{}.Solution:
Implemented native routing in
adk_api_server.tsfor/healthand/endpoints to return200 OKwhen the Debug UI is disabled. Added a custom raw-body fallback stream listener on thePOST /api/reasoning_enginequery endpoint to parse incoming payloads manually if standard body-parser was skipped.Testing Plan
Unit Tests:
Summary of passed test results:
✓ |unit:dev| dev/test/server/adk_api_server_test.ts (45 tests) 3797ms
✓ AdkWebServer > Reasoning Engine > should return 200 OK on health endpoints when debug UI is disabled 99ms
✓ AdkWebServer > Reasoning Engine > should query the agent using reasoning_engine route with valid JSON 42ms
✓ AdkWebServer > Reasoning Engine > should auto-create session if not exists on reasoning_engine query 56ms
✓ AdkWebServer > Reasoning Engine > should support raw body query and parse headers workaround 83ms
✓ AdkWebServer > Reasoning Engine > should return 400 if appName is missing 36ms
✓ AdkWebServer > Reasoning Engine > should return 500 if execution fails 34ms
Files tested:
• dev/test/server/adk_api_server_test.ts
Manual End-to-End (E2E) Tests:
Manually deployed and verified querying the weather tool agent on Vertex AI Reasoning Engines with unmocked traffic.
Checklist
Additional context
None.