Skip to content

Commit f8389c3

Browse files
committed
fix: update confirmation wrapper to propagate ProcessRequest calls to underlying tools and intercept packing registration
1 parent a6c6290 commit f8389c3

2 files changed

Lines changed: 138 additions & 0 deletions

File tree

tool/tool.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,27 @@ type runnableTool interface {
185185
Run(ctx agent.Context, args any) (result map[string]any, err error)
186186
}
187187

188+
type requestProcessor interface {
189+
ProcessRequest(ctx agent.Context, req *model.LLMRequest) error
190+
}
191+
188192
func (t *confirmationTool) Declaration() *genai.FunctionDeclaration {
189193
return t.runnableTool.Declaration()
190194
}
191195

192196
func (t *confirmationTool) ProcessRequest(ctx agent.Context, req *model.LLMRequest) error {
197+
if rp, ok := t.runnableTool.(requestProcessor); ok {
198+
_, existedBefore := req.Tools[t.Name()]
199+
if err := rp.ProcessRequest(ctx, req); err != nil {
200+
return err
201+
}
202+
// If the inner tool packed itself into req.Tools during ProcessRequest,
203+
// replace it with the confirmation wrapper so confirmationTool.Run is invoked.
204+
if !existedBefore && req.Tools != nil && req.Tools[t.Name()] != nil {
205+
req.Tools[t.Name()] = t
206+
return nil
207+
}
208+
}
193209
return toolutils.PackTool(req, t)
194210
}
195211

tool/tool_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@ import (
2020
"testing"
2121
"time"
2222

23+
"google.golang.org/genai"
24+
2325
"google.golang.org/adk/v2/agent"
2426
"google.golang.org/adk/v2/internal/toolinternal"
27+
"google.golang.org/adk/v2/internal/toolinternal/toolutils"
2528
"google.golang.org/adk/v2/memory"
29+
"google.golang.org/adk/v2/model"
2630
"google.golang.org/adk/v2/session"
2731
"google.golang.org/adk/v2/tool"
2832
"google.golang.org/adk/v2/tool/agenttool"
@@ -296,3 +300,121 @@ func TestWithConfirmation(t *testing.T) {
296300
})
297301
}
298302
}
303+
304+
type mockCustomRequestTool struct {
305+
tool.Tool
306+
decl *genai.FunctionDeclaration
307+
processRequestCalled *bool
308+
}
309+
310+
func (m *mockCustomRequestTool) Declaration() *genai.FunctionDeclaration { return m.decl }
311+
func (m *mockCustomRequestTool) Run(ctx agent.Context, args any) (map[string]any, error) {
312+
return map[string]any{}, nil
313+
}
314+
315+
func (m *mockCustomRequestTool) ProcessRequest(ctx agent.Context, req *model.LLMRequest) error {
316+
*m.processRequestCalled = true
317+
return nil
318+
}
319+
320+
func TestWithConfirmation_ProcessRequest(t *testing.T) {
321+
processRequestCalled := false
322+
baseTool, err := functiontool.New(functiontool.Config{Name: "customReqTool"}, func(ctx agent.Context, input struct{}) (struct{}, error) {
323+
return struct{}{}, nil
324+
})
325+
if err != nil {
326+
t.Fatalf("functiontool.New() failed: %v", err)
327+
}
328+
329+
customTool := &mockCustomRequestTool{
330+
Tool: baseTool,
331+
decl: &genai.FunctionDeclaration{Name: "customReqTool"},
332+
processRequestCalled: &processRequestCalled,
333+
}
334+
335+
ts := &testToolset{tools: []tool.Tool{customTool}}
336+
cts := tool.WithConfirmation(ts, true, nil)
337+
338+
tools, err := cts.Tools(nil)
339+
if err != nil {
340+
t.Fatalf("cts.Tools() failed: %v", err)
341+
}
342+
if len(tools) != 1 {
343+
t.Fatalf("cts.Tools() returned %d tools, want 1", len(tools))
344+
}
345+
346+
processor, ok := tools[0].(toolinternal.RequestProcessor)
347+
if !ok {
348+
t.Fatalf("wrapped tool does not implement RequestProcessor")
349+
}
350+
351+
req := &model.LLMRequest{}
352+
if err := processor.ProcessRequest(nil, req); err != nil {
353+
t.Fatalf("ProcessRequest() failed: %v", err)
354+
}
355+
356+
if !processRequestCalled {
357+
t.Errorf("expected wrapped tool's ProcessRequest to be called, but it was not")
358+
}
359+
}
360+
361+
type mockCustomRequestPackingTool struct {
362+
tool.Tool
363+
decl *genai.FunctionDeclaration
364+
processRequestCalled *bool
365+
}
366+
367+
func (m *mockCustomRequestPackingTool) Declaration() *genai.FunctionDeclaration { return m.decl }
368+
func (m *mockCustomRequestPackingTool) Run(ctx agent.Context, args any) (map[string]any, error) {
369+
return map[string]any{}, nil
370+
}
371+
372+
func (m *mockCustomRequestPackingTool) ProcessRequest(ctx agent.Context, req *model.LLMRequest) error {
373+
*m.processRequestCalled = true
374+
return toolutils.PackTool(req, m)
375+
}
376+
377+
func TestWithConfirmation_ProcessRequest_Packing(t *testing.T) {
378+
processRequestCalled := false
379+
baseTool, err := functiontool.New(functiontool.Config{Name: "customPackingTool"}, func(ctx agent.Context, input struct{}) (struct{}, error) {
380+
return struct{}{}, nil
381+
})
382+
if err != nil {
383+
t.Fatalf("functiontool.New() failed: %v", err)
384+
}
385+
386+
customTool := &mockCustomRequestPackingTool{
387+
Tool: baseTool,
388+
decl: &genai.FunctionDeclaration{Name: "customPackingTool"},
389+
processRequestCalled: &processRequestCalled,
390+
}
391+
392+
ts := &testToolset{tools: []tool.Tool{customTool}}
393+
cts := tool.WithConfirmation(ts, true, nil)
394+
395+
tools, err := cts.Tools(nil)
396+
if err != nil {
397+
t.Fatalf("cts.Tools() failed: %v", err)
398+
}
399+
if len(tools) != 1 {
400+
t.Fatalf("cts.Tools() returned %d tools, want 1", len(tools))
401+
}
402+
403+
processor, ok := tools[0].(toolinternal.RequestProcessor)
404+
if !ok {
405+
t.Fatalf("wrapped tool does not implement RequestProcessor")
406+
}
407+
408+
req := &model.LLMRequest{}
409+
if err := processor.ProcessRequest(nil, req); err != nil {
410+
t.Fatalf("ProcessRequest() failed: %v", err)
411+
}
412+
413+
if !processRequestCalled {
414+
t.Errorf("expected wrapped tool's ProcessRequest to be called, but it was not")
415+
}
416+
417+
if req.Tools["customPackingTool"] != tools[0] {
418+
t.Errorf("expected req.Tools[%q] to be the confirmation wrapper %T, got %T", "customPackingTool", tools[0], req.Tools["customPackingTool"])
419+
}
420+
}

0 commit comments

Comments
 (0)