From 6f340cb16799f86922c833ced48553e6b48ce7d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 10:29:42 +0000 Subject: [PATCH 1/2] Initial plan From c187164762481ca166efa432125730262ba64fee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 10:36:26 +0000 Subject: [PATCH 2/2] chore: port pydantic-ai v1.103.0 prepare warning update --- .github/pydantic-ai-version.txt | 2 +- packages/sdk/docs/reference/features.mdx | 1 + packages/sdk/lib/toolsets/prepared_toolset.ts | 9 +++++- packages/sdk/tests/prepared_toolset_test.ts | 30 +++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/.github/pydantic-ai-version.txt b/.github/pydantic-ai-version.txt index f634271..e402df2 100644 --- a/.github/pydantic-ai-version.txt +++ b/.github/pydantic-ai-version.txt @@ -1 +1 @@ -1.87.0 +1.103.0 diff --git a/packages/sdk/docs/reference/features.mdx b/packages/sdk/docs/reference/features.mdx index c329f47..3575df4 100644 --- a/packages/sdk/docs/reference/features.mdx +++ b/packages/sdk/docs/reference/features.mdx @@ -84,6 +84,7 @@ Vibes is designed to stay current with Pydantic AI - an AI agent automatically | Toolset reuse | Share toolsets across agents | ✅ | [Toolsets](/concepts/toolsets) | `Toolset` is a plain interface - pass the same instance to multiple agents | | Runtime swap | Replace toolsets during testing | ✅ | [Testing](/concepts/testing) | `agent.override({ toolsets: [...] }).run(prompt)` | | `PreparedToolset` | Modify entire tool list before each step | ✅ | [Toolsets](/concepts/toolsets) | `new PreparedToolset(inner, (ctx, tools) => tools)` - dynamic per-turn | +| Prepare callback warning | Warn when `prepare` callback returns `None` (v1.103.0) | ✅ | [Toolsets](/concepts/toolsets) | `PreparedToolset` logs a warning and falls back to inner tools on `null`/`undefined` | | `ApprovalRequiredToolset` | Enforce human approval on a toolset | ✅ | [Human-in-the-Loop](/concepts/human-in-the-loop) | `new ApprovalRequiredToolset(inner)` - all tools get `requiresApproval` | | `WrapperToolset` | Custom execution behaviour around a toolset | ✅ | [Toolsets](/concepts/toolsets) | `class MyWrapper extends WrapperToolset { callTool(...) { ... } }` | | `ExternalToolset` | Deferred execution outside agent process | ✅ | [Human-in-the-Loop](/concepts/human-in-the-loop) | `new ExternalToolset([{ name, description, jsonSchema }])` - schema-only | diff --git a/packages/sdk/lib/toolsets/prepared_toolset.ts b/packages/sdk/lib/toolsets/prepared_toolset.ts index 2ebc8f0..e96ea2f 100644 --- a/packages/sdk/lib/toolsets/prepared_toolset.ts +++ b/packages/sdk/lib/toolsets/prepared_toolset.ts @@ -42,6 +42,13 @@ export class PreparedToolset implements Toolset { async tools(ctx: RunContext): Promise[]> { const innerTools = await this._inner.tools(ctx); - return this._prepare(ctx, innerTools); + const preparedTools = await this._prepare(ctx, innerTools); + if (preparedTools === null || preparedTools === undefined) { + console.warn( + "PreparedToolset.prepare returned null/undefined; falling back to inner tools.", + ); + return innerTools; + } + return preparedTools; } } diff --git a/packages/sdk/tests/prepared_toolset_test.ts b/packages/sdk/tests/prepared_toolset_test.ts index 2406c79..ba8b183 100644 --- a/packages/sdk/tests/prepared_toolset_test.ts +++ b/packages/sdk/tests/prepared_toolset_test.ts @@ -179,3 +179,33 @@ Deno.test("PreparedToolset - prepare called on every turn", async () => { // called at least twice. assertEquals(prepareCallCount >= 2, true); }); + +Deno.test("PreparedToolset - warns and falls back when prepare returns nullish", async () => { + let capturedNames: string[] = []; + let warning: string | undefined; + const originalWarn = console.warn; + console.warn = (...args: unknown[]) => { + warning = args.map(String).join(" "); + }; + try { + const model = new MockLanguageModelV3({ + doGenerate: (opts) => { + capturedNames = toolNames(opts); + return Promise.resolve(textResponse("done")); + }, + }); + + const inner = new FunctionToolset([makeTool("fallback_tool")]); + const prepared = new PreparedToolset( + inner, + () => undefined as unknown as import("../mod.ts").ToolDefinition[], + ); + const agent = new Agent({ model, toolsets: [prepared] }); + await agent.run("go"); + + assertEquals(capturedNames.includes("fallback_tool"), true); + assertEquals(warning?.includes("PreparedToolset.prepare returned null/undefined"), true); + } finally { + console.warn = originalWarn; + } +});