Skip to content

feat: add emulator mode for mocked tool responses#3062

Open
Deeven-Seru wants to merge 1 commit intogoogleapis:mainfrom
Deeven-Seru:feat-1243-emulator
Open

feat: add emulator mode for mocked tool responses#3062
Deeven-Seru wants to merge 1 commit intogoogleapis:mainfrom
Deeven-Seru:feat-1243-emulator

Conversation

@Deeven-Seru
Copy link
Copy Markdown
Contributor

Summary

Implements #1243 by adding an emulator mode that intercepts tool execution and returns predefined mock responses from a local JSON file.

What’s included

  • New server config fields:
    • EmulatorMode
    • EmulatorMocksFile
  • New CLI flags:
    • --emulator-mode
    • --emulator-mocks-file
  • Validation requiring --emulator-mocks-file when emulator mode is enabled.
  • New emulator wrapper in internal/server/emulator.go that:
    • loads and validates mock definitions
    • matches incoming tool params against mock parameters
    • returns mock response instead of hitting live backends
    • returns a clear agent error when no mock matches
  • Dynamic reload now preserves emulator settings on config reload.
  • Unit tests for mock loading and matching behavior.

Example mock file

{
  "mocks": [
    {
      "tool_name": "search_users_bq",
      "parameters": { "id": 123 },
      "response": [{ "id": 123, "name": "Alice" }]
    }
  ]
}

Test

  • go test ./internal/server -run TestEmulatorToolInvoke -count=1

@Deeven-Seru Deeven-Seru requested a review from a team as a code owner April 15, 2026 04:28
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an emulator mode that allows the server to return mock tool responses from a JSON file instead of calling live backends. It adds new CLI flags, updates server configuration, and implements a tool wrapper that matches input parameters against defined mocks. Feedback focuses on optimizing the emulator's performance by pre-normalizing mock data during initialization and refactoring the invocation logic to minimize redundant JSON serialization.

Comment thread internal/server/emulator.go Outdated
Comment thread internal/server/emulator.go Outdated
@Deeven-Seru
Copy link
Copy Markdown
Contributor Author

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an emulator mode that allows the toolbox server to return mock tool responses from a JSON file instead of calling live backends. It adds new CLI flags, validation logic for the emulator configuration, and a new emulatorTool wrapper that intercepts tool invocations to match them against provided mocks. Feedback focuses on improving the emulator's flexibility by allowing partial emulation (only wrapping tools that have mocks), making parameter embedding a no-op to simplify mock matching, centralizing validation logic to avoid duplication, and ensuring the server watches the mocks file for dynamic reloads.

Comment on lines +82 to +92
func wrapToolsForEmulator(toolMap map[string]tools.Tool, mocksByTool map[string][]emulatorMock) map[string]tools.Tool {
wrapped := make(map[string]tools.Tool, len(toolMap))
for toolName, t := range toolMap {
wrapped[toolName] = emulatorTool{
name: toolName,
base: t,
mocks: mocksByTool[toolName],
}
}
return wrapped
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation wraps every single tool in the server when emulator mode is enabled. This means that any tool without a defined mock will return an error upon invocation, effectively breaking all non-mocked tools.

Consider only wrapping tools that actually have mocks defined in the mocksByTool map. This allows for "partial emulation" where some tools are mocked while others continue to use their live backends.

func wrapToolsForEmulator(toolMap map[string]tools.Tool, mocksByTool map[string][]emulatorMock) map[string]tools.Tool {
	wrapped := make(map[string]tools.Tool, len(toolMap))
	for toolName, t := range toolMap {
		if mocks, ok := mocksByTool[toolName]; ok && len(mocks) > 0 {
			wrapped[toolName] = emulatorTool{
				name:  toolName,
				base:  t,
				mocks: mocks,
			}
		} else {
			wrapped[toolName] = t
		}
	}
	return wrapped
}

Comment on lines +130 to +132
func (e emulatorTool) EmbedParams(ctx context.Context, params parameters.ParamValues, embeddingModels map[string]embeddingmodels.EmbeddingModel) (parameters.ParamValues, error) {
return e.base.EmbedParams(ctx, params, embeddingModels)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

By delegating EmbedParams to the base tool, the emulator will attempt to call live embedding models if the tool configuration requires it. This makes mocking difficult because the user would need to provide exact embedding vectors in the mock JSON file to match the parameters in Invoke.

It is recommended to make EmbedParams a no-op for the emulatorTool. This ensures that Invoke receives the original, human-readable parameters, making them much easier to match against the mock file.

Suggested change
func (e emulatorTool) EmbedParams(ctx context.Context, params parameters.ParamValues, embeddingModels map[string]embeddingmodels.EmbeddingModel) (parameters.ParamValues, error) {
return e.base.EmbedParams(ctx, params, embeddingModels)
}
func (e emulatorTool) EmbedParams(ctx context.Context, params parameters.ParamValues, embeddingModels map[string]embeddingmodels.EmbeddingModel) (parameters.ParamValues, error) {
return params, nil
}

Comment thread cmd/internal/serve/command.go
Comment thread cmd/root.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants