[PromptAPI] Blink layer changes for 'Tool use' - open-loop pattern [chromium/src : main]

0 views
Skip to first unread message

Frank Li (Gerrit)

unread,
Jan 19, 2026, 3:56:35 PMJan 19
to Mike Wasserman, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
Attention needed from Daniel Cheng, Jingyun Liu and Mike Wasserman

Frank Li added 1 comment

Patchset-level comments
File-level comment, Patchset 7 (Latest):
Frank Li . resolved

Hi All,
This CL is ready for the review.
Thanks!

Open in Gerrit

Related details

Attention is currently required from:
  • Daniel Cheng
  • Jingyun Liu
  • Mike Wasserman
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement is not satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: I8b2e969c55f44dc59313d83ae5728e621636e708
Gerrit-Change-Number: 7490714
Gerrit-PatchSet: 7
Gerrit-Owner: Frank Li <fra...@microsoft.com>
Gerrit-Reviewer: Daniel Cheng <dch...@chromium.org>
Gerrit-Reviewer: Frank Li <fra...@microsoft.com>
Gerrit-Reviewer: Jingyun Liu <jin...@google.com>
Gerrit-Reviewer: Mike Wasserman <m...@chromium.org>
Gerrit-CC: Kentaro Hara <har...@chromium.org>
Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
Gerrit-CC: Sushanth Rajasankar <Sush...@microsoft.com>
Gerrit-Attention: Mike Wasserman <m...@chromium.org>
Gerrit-Attention: Jingyun Liu <jin...@google.com>
Gerrit-Attention: Daniel Cheng <dch...@chromium.org>
Gerrit-Comment-Date: Mon, 19 Jan 2026 20:56:25 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
satisfied_requirement
unsatisfied_requirement
open
diffy

Daniel Cheng (Gerrit)

unread,
Jan 20, 2026, 1:39:36 PMJan 20
to Frank Li, Dominic Farolino, Mike Wasserman, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
Attention needed from Dominic Farolino, Frank Li, Jingyun Liu and Mike Wasserman

Daniel Cheng added 1 comment

Patchset-level comments
Daniel Cheng . resolved

Load-balancing this to dom@ for the Blink / Blink public API bits in the mojom

Open in Gerrit

Related details

Attention is currently required from:
  • Dominic Farolino
  • Frank Li
  • Jingyun Liu
  • Mike Wasserman
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement is not satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: I8b2e969c55f44dc59313d83ae5728e621636e708
Gerrit-Change-Number: 7490714
Gerrit-PatchSet: 7
Gerrit-Owner: Frank Li <fra...@microsoft.com>
Gerrit-Reviewer: Daniel Cheng <dch...@chromium.org>
Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
Gerrit-Reviewer: Frank Li <fra...@microsoft.com>
Gerrit-Reviewer: Jingyun Liu <jin...@google.com>
Gerrit-Reviewer: Mike Wasserman <m...@chromium.org>
Gerrit-CC: Kentaro Hara <har...@chromium.org>
Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
Gerrit-CC: Sushanth Rajasankar <Sush...@microsoft.com>
Gerrit-Attention: Mike Wasserman <m...@chromium.org>
Gerrit-Attention: Dominic Farolino <d...@chromium.org>
Gerrit-Attention: Frank Li <fra...@microsoft.com>
Gerrit-Attention: Jingyun Liu <jin...@google.com>
Gerrit-Comment-Date: Tue, 20 Jan 2026 18:39:27 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
satisfied_requirement
unsatisfied_requirement
open
diffy

Dominic Farolino (Gerrit)

unread,
Jan 20, 2026, 9:31:29 PMJan 20
to Frank Li, Mike Wasserman, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
Attention needed from Frank Li, Jingyun Liu and Mike Wasserman

Dominic Farolino added 4 comments

File chrome/browser/ai/ai_language_model.cc
Line 97, Patchset 8 (Latest): // Tool use is not yet supported in browser-side model execution.
Dominic Farolino . unresolved

So there is no production usage of the `tool_call` and `tool_response` members that this CL adds to `AILanguageModelPromptContent` yet, right?

File third_party/blink/public/mojom/ai/ai_language_model.mojom
Line 125, Patchset 8 (Latest): array<AILanguageModelToolDeclaration>? tools;
Dominic Farolino . unresolved

I don't see any browser-side usage of this yet. Am I missing it?

File third_party/blink/renderer/modules/ai/language_model_create_client.cc
Line 131, Patchset 8 (Latest): // but handle it safely. Skip this tool rather than sending invalid data.
Dominic Farolino . unresolved

Shouldn't we just CHECK() instead, if we're so sure it shouldn't happen? Or expose the error to JavaScript?

Line 141, Patchset 8 (Latest): base::JSONReader::Read(json_str, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
Dominic Farolino . unresolved

The behavior of `JSON_PARSE_CHROMIUM_EXTENSIONS` is web-exposed right? Is there a concern about exporting this "CHROMIUM_EXTENSION" behavior to the platform? Is it spec'd?

Open in Gerrit

Related details

Attention is currently required from:
  • Frank Li
  • Jingyun Liu
  • Mike Wasserman
Submit Requirements:
    • requirement satisfiedCode-Coverage
    • requirement is not satisfiedCode-Owners
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: chromium/src
    Gerrit-Branch: main
    Gerrit-Change-Id: I8b2e969c55f44dc59313d83ae5728e621636e708
    Gerrit-Change-Number: 7490714
    Gerrit-PatchSet: 8
    Gerrit-Owner: Frank Li <fra...@microsoft.com>
    Gerrit-Reviewer: Daniel Cheng <dch...@chromium.org>
    Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
    Gerrit-Reviewer: Frank Li <fra...@microsoft.com>
    Gerrit-Reviewer: Jingyun Liu <jin...@google.com>
    Gerrit-Reviewer: Mike Wasserman <m...@chromium.org>
    Gerrit-CC: Kentaro Hara <har...@chromium.org>
    Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
    Gerrit-CC: Sushanth Rajasankar <Sush...@microsoft.com>
    Gerrit-Attention: Mike Wasserman <m...@chromium.org>
    Gerrit-Attention: Frank Li <fra...@microsoft.com>
    Gerrit-Attention: Jingyun Liu <jin...@google.com>
    Gerrit-Comment-Date: Wed, 21 Jan 2026 02:31:23 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    satisfied_requirement
    unsatisfied_requirement
    open
    diffy

    Frank Li (Gerrit)

    unread,
    Jan 21, 2026, 12:31:45 AMJan 21
    to Dominic Farolino, Mike Wasserman, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
    Attention needed from Dominic Farolino, Jingyun Liu and Mike Wasserman

    Frank Li added 5 comments

    Patchset-level comments
    File-level comment, Patchset 10 (Latest):
    Frank Li . resolved

    @Dominic - thank you for your review. PTAL with the updates.

    File chrome/browser/ai/ai_language_model.cc
    Line 97, Patchset 8: // Tool use is not yet supported in browser-side model execution.
    Dominic Farolino . resolved

    So there is no production usage of the `tool_call` and `tool_response` members that this CL adds to `AILanguageModelPromptContent` yet, right?

    Frank Li

    yes. no production usage yet.
    content/browser/ai/echo_ai_language_model.{h,cc} use them for blink web tests.

    File third_party/blink/public/mojom/ai/ai_language_model.mojom
    Line 125, Patchset 8: array<AILanguageModelToolDeclaration>? tools;
    Dominic Farolino . resolved

    I don't see any browser-side usage of this yet. Am I missing it?

    Frank Li

    content/browser/ai/echo_ai_language_model.{h,cc} use them for tests.

    File third_party/blink/renderer/modules/ai/language_model_create_client.cc
    Line 131, Patchset 8: // but handle it safely. Skip this tool rather than sending invalid data.
    Dominic Farolino . resolved

    Shouldn't we just CHECK() instead, if we're so sure it shouldn't happen? Or expose the error to JavaScript?

    Frank Li

    Exposed the error to JS. Updated test expectations.

    Line 141, Patchset 8: base::JSONReader::Read(json_str, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
    Dominic Farolino . resolved

    The behavior of `JSON_PARSE_CHROMIUM_EXTENSIONS` is web-exposed right? Is there a concern about exporting this "CHROMIUM_EXTENSION" behavior to the platform? Is it spec'd?

    Frank Li

    Good suggestion. changed it to use JSON_PARSE_RFC.

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Dominic Farolino
    • Jingyun Liu
    • Mike Wasserman
    Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement is not satisfiedCode-Owners
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: I8b2e969c55f44dc59313d83ae5728e621636e708
      Gerrit-Change-Number: 7490714
      Gerrit-PatchSet: 10
      Gerrit-Owner: Frank Li <fra...@microsoft.com>
      Gerrit-Reviewer: Daniel Cheng <dch...@chromium.org>
      Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
      Gerrit-Reviewer: Frank Li <fra...@microsoft.com>
      Gerrit-Reviewer: Jingyun Liu <jin...@google.com>
      Gerrit-Reviewer: Mike Wasserman <m...@chromium.org>
      Gerrit-CC: Kentaro Hara <har...@chromium.org>
      Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
      Gerrit-CC: Sushanth Rajasankar <Sush...@microsoft.com>
      Gerrit-Attention: Mike Wasserman <m...@chromium.org>
      Gerrit-Attention: Dominic Farolino <d...@chromium.org>
      Gerrit-Attention: Jingyun Liu <jin...@google.com>
      Gerrit-Comment-Date: Wed, 21 Jan 2026 05:31:37 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      Comment-In-Reply-To: Dominic Farolino <d...@chromium.org>
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Dominic Farolino (Gerrit)

      unread,
      Jan 21, 2026, 4:27:12 PMJan 21
      to Frank Li, Mike Wasserman, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
      Attention needed from Frank Li, Jingyun Liu and Mike Wasserman

      Dominic Farolino added 3 comments

      File third_party/blink/renderer/modules/ai/language_model_create_client.cc
      Line 131, Patchset 8: // but handle it safely. Skip this tool rather than sending invalid data.
      Dominic Farolino . unresolved

      Shouldn't we just CHECK() instead, if we're so sure it shouldn't happen? Or expose the error to JavaScript?

      Frank Li

      Exposed the error to JS. Updated test expectations.

      Dominic Farolino

      You did this by calling ToLocalChecked(), right? Doesn't this just crash if an exception was thrown in the stringifier? Does `Stringifier()` call an author's `toString()` implementation? If so, that can throw an error, and I think we want to re-throw the error to JS, which I don't think this CL does.

      Line 141, Patchset 8: base::JSONReader::Read(json_str, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
      Dominic Farolino . unresolved

      The behavior of `JSON_PARSE_CHROMIUM_EXTENSIONS` is web-exposed right? Is there a concern about exporting this "CHROMIUM_EXTENSION" behavior to the platform? Is it spec'd?

      Frank Li

      Good suggestion. changed it to use JSON_PARSE_RFC.

      Dominic Farolino

      The result of an object's `toString()` implementation can still provide a String that is invalid JSON. We want semantics that match `JSON.parse()` I think.

      Can you add a WPT ensuring that when an inputScheme's object's toString() is something like the following invalid strings that throw in `JSON.parse()` they also throw appropriately here?

      ```
      JSON.parse(`{"a": 10,}`);
      JSON.parse(`{
      "a": 10,
      // Comment.
      "b": 11
      }
      `);
      ```
      Line 155, Patchset 10 (Latest): v8::JSON::Stringify(context, schema_v8);
      Dominic Farolino . unresolved

      Does this call an author's `toString()` implementation.

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Frank Li
      • Jingyun Liu
      • Mike Wasserman
      Submit Requirements:
        • requirement satisfiedCode-Coverage
        • requirement is not satisfiedCode-Owners
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: chromium/src
        Gerrit-Branch: main
        Gerrit-Change-Id: I8b2e969c55f44dc59313d83ae5728e621636e708
        Gerrit-Change-Number: 7490714
        Gerrit-PatchSet: 10
        Gerrit-Owner: Frank Li <fra...@microsoft.com>
        Gerrit-Reviewer: Daniel Cheng <dch...@chromium.org>
        Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
        Gerrit-Reviewer: Frank Li <fra...@microsoft.com>
        Gerrit-Reviewer: Jingyun Liu <jin...@google.com>
        Gerrit-Reviewer: Mike Wasserman <m...@chromium.org>
        Gerrit-CC: Kentaro Hara <har...@chromium.org>
        Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
        Gerrit-CC: Sushanth Rajasankar <Sush...@microsoft.com>
        Gerrit-Attention: Mike Wasserman <m...@chromium.org>
        Gerrit-Attention: Frank Li <fra...@microsoft.com>
        Gerrit-Attention: Jingyun Liu <jin...@google.com>
        Gerrit-Comment-Date: Wed, 21 Jan 2026 21:27:06 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        Comment-In-Reply-To: Dominic Farolino <d...@chromium.org>
        Comment-In-Reply-To: Frank Li <fra...@microsoft.com>
        satisfied_requirement
        unsatisfied_requirement
        open
        diffy

        Dominic Farolino (Gerrit)

        unread,
        Jan 21, 2026, 5:07:07 PMJan 21
        to Frank Li, Mike Wasserman, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
        Attention needed from Frank Li, Jingyun Liu and Mike Wasserman

        Dominic Farolino added 2 comments

        File third_party/blink/renderer/modules/ai/language_model_create_client.cc
        Line 155, Patchset 10 (Latest): v8::JSON::Stringify(context, schema_v8);
        Dominic Farolino . unresolved

        Does this call an author's `toString()` implementation.

        Dominic Farolino

        Oops... forgot to go back and edit this incomplete sentence. Yeah, I just wanted to re-check that if this calls the developer-supplied stringifier, we handle the error appropriately. The use of `ToLocalChecked()` below seems risky.

        Line 163, Patchset 10 (Latest): // that shouldn't be web-exposed.
        Dominic Farolino . unresolved

        Suggestion: "CHROMIUM_EXTENSIONS, for example, would relax the parser beyond what the platform currently allows with `JSON.parse()`."

        Gerrit-Comment-Date: Wed, 21 Jan 2026 22:07:03 +0000
        satisfied_requirement
        unsatisfied_requirement
        open
        diffy

        Dominic Farolino (Gerrit)

        unread,
        Jan 21, 2026, 5:18:46 PMJan 21
        to Frank Li, Mike Wasserman, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
        Attention needed from Frank Li, Jingyun Liu and Mike Wasserman

        Dominic Farolino added 1 comment

        File third_party/blink/renderer/modules/ai/language_model_create_client.cc
        Line 165, Patchset 10 (Latest): base::JSONReader::Read(json_str, base::JSON_PARSE_RFC);
        Dominic Farolino . unresolved

        Btw, did you consider using `ParseJSON()` in renderer/platform here at all? Would it be any more ergonomic?

        Gerrit-Comment-Date: Wed, 21 Jan 2026 22:18:41 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        satisfied_requirement
        unsatisfied_requirement
        open
        diffy

        Jingyun Liu (Gerrit)

        unread,
        Jan 21, 2026, 5:39:13 PMJan 21
        to Frank Li, Dominic Farolino, Mike Wasserman, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
        Attention needed from Frank Li and Mike Wasserman

        Jingyun Liu added 4 comments

        File third_party/blink/renderer/modules/ai/language_model.cc
        Line 940, Patchset 10 (Latest): } else {
        Jingyun Liu . unresolved

        in the non-streaming API, the model might also output text + tool call altogether. So I think we need to package the response + tool_call together and call resolver.Resolve()?

        File third_party/blink/renderer/modules/ai/language_model_create_client.cc
        Line 65, Patchset 10 (Latest): if (schema_v8.IsEmpty() || schema_v8->IsNull()) {
        return "Tool inputSchema cannot be null.";
        }
        Jingyun Liu . unresolved

        To double check, does this check allows tools without parameter? basically the JS argument would be `{inputSchema: {}}`?

        Line 258, Patchset 10 (Latest): // Reject kToolCall in expectedInputs - tool calls are model outputs, not
        // inputs. Tool responses should be used to send results back.
        Jingyun Liu . unresolved

        I think we still need to allow people to prefill/append ToolCall and ToolResponse for few shot examples? (if the feature is enabled)

        Line 482, Patchset 10 (Latest): // If all tools failed to convert (e.g., JSON.stringify failures), we
        Jingyun Liu . unresolved

        should we throw error if any tool failed to convert because the prompt won't match client's intention?

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Frank Li
        • Mike Wasserman
        Gerrit-Comment-Date: Wed, 21 Jan 2026 22:39:00 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        satisfied_requirement
        unsatisfied_requirement
        open
        diffy

        Frank Li (Gerrit)

        unread,
        Jan 23, 2026, 4:59:45 PMJan 23
        to Dominic Farolino, Mike Wasserman, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
        Attention needed from Dominic Farolino, Jingyun Liu and Mike Wasserman

        Frank Li added 10 comments

        Patchset-level comments
        File-level comment, Patchset 12 (Latest):
        Frank Li . resolved

        Thanks @Jingyun and @Dominic for the CR feedback!
        PTAL with the updates.

        File third_party/blink/renderer/modules/ai/language_model.cc
        Line 940, Patchset 10: } else {
        Jingyun Liu . resolved

        in the non-streaming API, the model might also output text + tool call altogether. So I think we need to package the response + tool_call together and call resolver.Resolve()?

        Frank Li

        Good catch! Added code/test to cover this.

        File third_party/blink/renderer/modules/ai/language_model_create_client.cc
        Line 65, Patchset 10: if (schema_v8.IsEmpty() || schema_v8->IsNull()) {

        return "Tool inputSchema cannot be null.";
        }
        Jingyun Liu . resolved

        To double check, does this check allows tools without parameter? basically the JS argument would be `{inputSchema: {}}`?

        Frank Li

        It fails because it is an object without type key. I have added the
        "Test tool with empty object inputSchema." test.

        Line 131, Patchset 8: // but handle it safely. Skip this tool rather than sending invalid data.
        Dominic Farolino . resolved

        Shouldn't we just CHECK() instead, if we're so sure it shouldn't happen? Or expose the error to JavaScript?

        Frank Li

        Exposed the error to JS. Updated test expectations.

        Dominic Farolino

        You did this by calling ToLocalChecked(), right? Doesn't this just crash if an exception was thrown in the stringifier? Does `Stringifier()` call an author's `toString()` implementation? If so, that can throw an error, and I think we want to re-throw the error to JS, which I don't think this CL does.

        Frank Li

        I had changed the code to use `v8::TryCatch` to capture exceptions prior to use `ToLocalChecked()`. It is safe to use it now.

        >...Does Stringifier() call an author's toString() implementation?...

        JSON.stringify() calls toJSON() (not toString()), and also invokes getters when accessing properties.


        >...can throw an error, and I think we want to re-throw the error to JS...

        Added code/test for this.

        Line 141, Patchset 8: base::JSONReader::Read(json_str, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
        Dominic Farolino . resolved

        The behavior of `JSON_PARSE_CHROMIUM_EXTENSIONS` is web-exposed right? Is there a concern about exporting this "CHROMIUM_EXTENSION" behavior to the platform? Is it spec'd?

        Frank Li

        Good suggestion. changed it to use JSON_PARSE_RFC.

        Dominic Farolino

        The result of an object's `toString()` implementation can still provide a String that is invalid JSON. We want semantics that match `JSON.parse()` I think.

        Can you add a WPT ensuring that when an inputScheme's object's toString() is something like the following invalid strings that throw in `JSON.parse()` they also throw appropriately here?

        ```
        JSON.parse(`{"a": 10,}`);
        JSON.parse(`{
        "a": 10,
        // Comment.
        "b": 11
        }
        `);
        ```
        Frank Li

        Added the test with the your examples: `createLanguageModel with schema containing invalid-JSON-like text (trailing commas, comments) as string content succeeds because JSON.stringify escapes properly`

        JSON.stringify escapes them, making the RFC parser validation unreachable through normal web API usage. The DLOG tracings of the code:
        ```

        [26716:24284:0122/121231.318:INFO:third_party\blink\renderer\modules\ai\language_model_create_client.cc:151] [TEMP] JSON.stringify output for tool '"exampleTool"':
        {"type":"object","properties":{"trailingCommaExample":{"type":"string","description":"{\"a\": 10,}"},"commentExample":{"type":"string","description":"{\n \"a\": 10,\n //
        Comment.\n \"b\": 11\n}"}}}
        [26716:24284:0122/121231.319:INFO:third_party\blink\renderer\modules\ai\language_model_create_client.cc:161] [TEMP] RFC parsing result for tool '"exampleTool"': SUCCESS
        ```

        Line 155, Patchset 10: v8::JSON::Stringify(context, schema_v8);
        Dominic Farolino . resolved

        Does this call an author's `toString()` implementation.

        Dominic Farolino

        Oops... forgot to go back and edit this incomplete sentence. Yeah, I just wanted to re-check that if this calls the developer-supplied stringifier, we handle the error appropriately. The use of `ToLocalChecked()` below seems risky.

        Line 163, Patchset 10: // that shouldn't be web-exposed.
        Dominic Farolino . resolved

        Suggestion: "CHROMIUM_EXTENSIONS, for example, would relax the parser beyond what the platform currently allows with `JSON.parse()`."

        Frank Li

        Done

        Line 165, Patchset 10: base::JSONReader::Read(json_str, base::JSON_PARSE_RFC);
        Dominic Farolino . unresolved

        Btw, did you consider using `ParseJSON()` in renderer/platform here at all? Would it be any more ergonomic?

        Frank Li

        Investigated `ParseJSON()` - it returns `JSONValue*` while we need `base::Value` for Mojo. Current approach avoids conversion. Is this acceptable or would you prefer adding the conversion step?

        Line 258, Patchset 10: // Reject kToolCall in expectedInputs - tool calls are model outputs, not

        // inputs. Tool responses should be used to send results back.
        Jingyun Liu . resolved

        I think we still need to allow people to prefill/append ToolCall and ToolResponse for few shot examples? (if the feature is enabled)

        Frank Li

        I have added TODO for it.

        Line 482, Patchset 10: // If all tools failed to convert (e.g., JSON.stringify failures), we
        Jingyun Liu . resolved

        should we throw error if any tool failed to convert because the prompt won't match client's intention?

        Frank Li

        Done

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Dominic Farolino
        • Jingyun Liu
        • Mike Wasserman
        Submit Requirements:
        • requirement satisfiedCode-Coverage
        • requirement is not satisfiedCode-Owners
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: chromium/src
        Gerrit-Branch: main
        Gerrit-Change-Id: I8b2e969c55f44dc59313d83ae5728e621636e708
        Gerrit-Change-Number: 7490714
        Gerrit-PatchSet: 12
        Gerrit-Owner: Frank Li <fra...@microsoft.com>
        Gerrit-Reviewer: Daniel Cheng <dch...@chromium.org>
        Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
        Gerrit-Reviewer: Frank Li <fra...@microsoft.com>
        Gerrit-Reviewer: Jingyun Liu <jin...@google.com>
        Gerrit-Reviewer: Mike Wasserman <m...@chromium.org>
        Gerrit-CC: Kentaro Hara <har...@chromium.org>
        Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
        Gerrit-CC: Sushanth Rajasankar <Sush...@microsoft.com>
        Gerrit-Attention: Mike Wasserman <m...@chromium.org>
        Gerrit-Attention: Dominic Farolino <d...@chromium.org>
        Gerrit-Attention: Jingyun Liu <jin...@google.com>
        Gerrit-Comment-Date: Fri, 23 Jan 2026 21:59:31 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        Comment-In-Reply-To: Dominic Farolino <d...@chromium.org>
        Comment-In-Reply-To: Frank Li <fra...@microsoft.com>
        Comment-In-Reply-To: Jingyun Liu <jin...@google.com>
        satisfied_requirement
        unsatisfied_requirement
        open
        diffy

        Jingyun Liu (Gerrit)

        unread,
        Jan 23, 2026, 6:14:47 PMJan 23
        to Frank Li, Dominic Farolino, Mike Wasserman, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
        Attention needed from Dominic Farolino, Frank Li and Mike Wasserman

        Jingyun Liu added 2 comments

        File third_party/blink/renderer/modules/ai/language_model_create_client.cc
        Line 65, Patchset 10: if (schema_v8.IsEmpty() || schema_v8->IsNull()) {
        return "Tool inputSchema cannot be null.";
        }
        Jingyun Liu . unresolved

        To double check, does this check allows tools without parameter? basically the JS argument would be `{inputSchema: {}}`?

        Frank Li

        It fails because it is an object without type key. I have added the
        "Test tool with empty object inputSchema." test.

        Jingyun Liu

        I think we want to support tools without inputs too, especially when the tool is informative or stateful.

        Line 258, Patchset 10: // Reject kToolCall in expectedInputs - tool calls are model outputs, not
        // inputs. Tool responses should be used to send results back.
        Jingyun Liu . unresolved

        I think we still need to allow people to prefill/append ToolCall and ToolResponse for few shot examples? (if the feature is enabled)

        Frank Li

        I have added TODO for it.

        Jingyun Liu

        thanks! can you also add TODO in LanguageModelPromptBuilder::ProcessEntry?

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Dominic Farolino
        • Frank Li
        • Mike Wasserman
        Gerrit-Attention: Frank Li <fra...@microsoft.com>
        Gerrit-Comment-Date: Fri, 23 Jan 2026 23:14:38 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        satisfied_requirement
        unsatisfied_requirement
        open
        diffy

        Frank Li (Gerrit)

        unread,
        Jan 24, 2026, 4:21:52 PMJan 24
        to Dominic Farolino, Mike Wasserman, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
        Attention needed from Dominic Farolino, Jingyun Liu and Mike Wasserman

        Frank Li added 3 comments

        Patchset-level comments
        File-level comment, Patchset 13 (Latest):
        Frank Li . resolved

        Hi @Jingyun,
        PTAL with the updates. Thanks!

        File third_party/blink/renderer/modules/ai/language_model_create_client.cc
        Line 65, Patchset 10: if (schema_v8.IsEmpty() || schema_v8->IsNull()) {
        return "Tool inputSchema cannot be null.";
        }
        Jingyun Liu . resolved

        To double check, does this check allows tools without parameter? basically the JS argument would be `{inputSchema: {}}`?

        Frank Li

        It fails because it is an object without type key. I have added the
        "Test tool with empty object inputSchema." test.

        Jingyun Liu

        I think we want to support tools without inputs too, especially when the tool is informative or stateful.

        Frank Li

        It is already supported for tool without input parameters.

        Example: {name: "getCurrentTime", description: "...", inputSchema: {type: "object"}}

        I have also added above format into the existing no argument test.

        `inputSchema` is required from idl. `type` of `object` is required by JSON Schema.

        Line 258, Patchset 10: // Reject kToolCall in expectedInputs - tool calls are model outputs, not
        // inputs. Tool responses should be used to send results back.
        Jingyun Liu . resolved

        I think we still need to allow people to prefill/append ToolCall and ToolResponse for few shot examples? (if the feature is enabled)

        Frank Li

        I have added TODO for it.

        Jingyun Liu

        thanks! can you also add TODO in LanguageModelPromptBuilder::ProcessEntry?

        Frank Li

        Ah. Thanks for the reminder! I have added TODO comment there too.

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Dominic Farolino
        • Jingyun Liu
        • Mike Wasserman
        Submit Requirements:
        • requirement satisfiedCode-Coverage
        • requirement is not satisfiedCode-Owners
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: chromium/src
        Gerrit-Branch: main
        Gerrit-Change-Id: I8b2e969c55f44dc59313d83ae5728e621636e708
        Gerrit-Change-Number: 7490714
        Gerrit-PatchSet: 13
        Gerrit-Owner: Frank Li <fra...@microsoft.com>
        Gerrit-Reviewer: Daniel Cheng <dch...@chromium.org>
        Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
        Gerrit-Reviewer: Frank Li <fra...@microsoft.com>
        Gerrit-Reviewer: Jingyun Liu <jin...@google.com>
        Gerrit-Reviewer: Mike Wasserman <m...@chromium.org>
        Gerrit-CC: Kentaro Hara <har...@chromium.org>
        Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
        Gerrit-CC: Sushanth Rajasankar <Sush...@microsoft.com>
        Gerrit-Attention: Mike Wasserman <m...@chromium.org>
        Gerrit-Attention: Dominic Farolino <d...@chromium.org>
        Gerrit-Attention: Jingyun Liu <jin...@google.com>
        Gerrit-Comment-Date: Sat, 24 Jan 2026 21:21:40 +0000
        satisfied_requirement
        unsatisfied_requirement
        open
        diffy

        Dominic Farolino (Gerrit)

        unread,
        Jan 26, 2026, 9:04:40 AMJan 26
        to Frank Li, Mike Wasserman, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
        Attention needed from Frank Li, Jingyun Liu and Mike Wasserman

        Dominic Farolino voted and added 4 comments

        Votes added by Dominic Farolino

        Code-Review+1

        4 comments

        File chrome/browser/ai/ai_test_utils.cc
        Line 57, Patchset 13 (Latest): // It is not used in the browser-side unit_tests yet.
        Dominic Farolino . unresolved

        `NOTREACHED();`?

        File third_party/blink/renderer/modules/ai/language_model_create_client.cc
        Line 84, Patchset 13 (Latest): v8::String::NewFromUtf8Literal(script_state->GetIsolate(), "type");
        Line 133, Patchset 13 (Latest): result.exception = try_catch.Exception();
        Dominic Farolino . unresolved

        Do we need to call `try_catch.Reset()` here? When rejecting a Promise with an exception scooped up from a TryCatch, I'm used to [seeing `Reset()`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/bindings/core/v8/script_iterator.cc;l=336-338;drc=5f8643cf150e97b694f2214ee092c34cc0ccf5fe), although I admit I'm not positive if it's necessary.

        Line 165, Patchset 10: base::JSONReader::Read(json_str, base::JSON_PARSE_RFC);
        Dominic Farolino . resolved

        Btw, did you consider using `ParseJSON()` in renderer/platform here at all? Would it be any more ergonomic?

        Frank Li

        Investigated `ParseJSON()` - it returns `JSONValue*` while we need `base::Value` for Mojo. Current approach avoids conversion. Is this acceptable or would you prefer adding the conversion step?

        Dominic Farolino

        Done

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Frank Li
        • Jingyun Liu
        • Mike Wasserman
        Submit Requirements:
          • requirement satisfiedCode-Coverage
          • requirement is not satisfiedCode-Owners
          • requirement satisfiedCode-Review
          • requirement is not satisfiedNo-Unresolved-Comments
          • requirement is not satisfiedReview-Enforcement
          Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
          Gerrit-MessageType: comment
          Gerrit-Project: chromium/src
          Gerrit-Branch: main
          Gerrit-Change-Id: I8b2e969c55f44dc59313d83ae5728e621636e708
          Gerrit-Change-Number: 7490714
          Gerrit-PatchSet: 13
          Gerrit-Owner: Frank Li <fra...@microsoft.com>
          Gerrit-Reviewer: Daniel Cheng <dch...@chromium.org>
          Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
          Gerrit-Reviewer: Frank Li <fra...@microsoft.com>
          Gerrit-Reviewer: Jingyun Liu <jin...@google.com>
          Gerrit-Reviewer: Mike Wasserman <m...@chromium.org>
          Gerrit-CC: Kentaro Hara <har...@chromium.org>
          Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
          Gerrit-CC: Sushanth Rajasankar <Sush...@microsoft.com>
          Gerrit-Attention: Mike Wasserman <m...@chromium.org>
          Gerrit-Attention: Frank Li <fra...@microsoft.com>
          Gerrit-Attention: Jingyun Liu <jin...@google.com>
          Gerrit-Comment-Date: Mon, 26 Jan 2026 14:04:31 +0000
          Gerrit-HasComments: Yes
          Gerrit-Has-Labels: Yes
          satisfied_requirement
          unsatisfied_requirement
          open
          diffy

          Frank Li (Gerrit)

          unread,
          Jan 26, 2026, 3:15:46 PMJan 26
          to Dominic Farolino, Mike Wasserman, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
          Attention needed from Jingyun Liu and Mike Wasserman

          Frank Li added 4 comments

          Patchset-level comments
          File-level comment, Patchset 14 (Latest):
          Frank Li . resolved

          Hi @Domininic,
          PS 14 has the updates for your review. Thanks!

          File chrome/browser/ai/ai_test_utils.cc
          Line 57, Patchset 13: // It is not used in the browser-side unit_tests yet.
          Dominic Farolino . resolved

          `NOTREACHED();`?

          Frank Li

          Done

          File third_party/blink/renderer/modules/ai/language_model_create_client.cc
          Line 84, Patchset 13: v8::String::NewFromUtf8Literal(script_state->GetIsolate(), "type");
          Dominic Farolino . resolved
          Frank Li

          Done

          Line 133, Patchset 13: result.exception = try_catch.Exception();
          Dominic Farolino . resolved

          Do we need to call `try_catch.Reset()` here? When rejecting a Promise with an exception scooped up from a TryCatch, I'm used to [seeing `Reset()`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/bindings/core/v8/script_iterator.cc;l=336-338;drc=5f8643cf150e97b694f2214ee092c34cc0ccf5fe), although I admit I'm not positive if it's necessary.

          Frank Li

          Although the destructor also cleans it up, I have followed standard existing pattern that you provided to be explicit.

          Open in Gerrit

          Related details

          Attention is currently required from:
          • Jingyun Liu
          • Mike Wasserman
          Submit Requirements:
            • requirement satisfiedCode-Coverage
            • requirement is not satisfiedCode-Owners
            • requirement satisfiedCode-Review
            • requirement is not satisfiedReview-Enforcement
            Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
            Gerrit-MessageType: comment
            Gerrit-Project: chromium/src
            Gerrit-Branch: main
            Gerrit-Change-Id: I8b2e969c55f44dc59313d83ae5728e621636e708
            Gerrit-Change-Number: 7490714
            Gerrit-PatchSet: 14
            Gerrit-Owner: Frank Li <fra...@microsoft.com>
            Gerrit-Reviewer: Daniel Cheng <dch...@chromium.org>
            Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
            Gerrit-Reviewer: Frank Li <fra...@microsoft.com>
            Gerrit-Reviewer: Jingyun Liu <jin...@google.com>
            Gerrit-Reviewer: Mike Wasserman <m...@chromium.org>
            Gerrit-CC: Kentaro Hara <har...@chromium.org>
            Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
            Gerrit-CC: Sushanth Rajasankar <Sush...@microsoft.com>
            Gerrit-Attention: Mike Wasserman <m...@chromium.org>
            Gerrit-Attention: Jingyun Liu <jin...@google.com>
            Gerrit-Comment-Date: Mon, 26 Jan 2026 20:15:35 +0000
            Gerrit-HasComments: Yes
            Gerrit-Has-Labels: No
            Comment-In-Reply-To: Dominic Farolino <d...@chromium.org>
            satisfied_requirement
            unsatisfied_requirement
            open
            diffy

            Mike Wasserman (Gerrit)

            unread,
            Jan 27, 2026, 8:16:18 PMJan 27
            to Frank Li, Dominic Farolino, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
            Attention needed from Frank Li and Jingyun Liu

            Mike Wasserman added 53 comments

            Patchset-level comments
            Mike Wasserman . resolved

            Thanks for working on this, and being patient with our reviews!

            File chrome/browser/ai/ai_language_model.cc
            Line 97, Patchset 14 (Latest): // Tool use is not yet supported in browser-side model execution.
            Mike Wasserman . unresolved
            optional nit:
            ```suggestion
            // TODO(crbug.com/422803232): Support on_device_model tool use
            ```
            File content/browser/ai/echo_ai_language_model.cc
            Line 14, Patchset 14 (Latest):#include "base/notreached.h"
            Mike Wasserman . unresolved

            remove if unused

            Line 72, Patchset 14 (Latest): (input.find("[TOOL_SUCCESS:") != std::string::npos) ||
            (input.find("[Tool Error:") != std::string::npos);
            Mike Wasserman . unresolved

            nit: align tool response formats (maybe `<tool-success name='get_weather' id='123' result=['rainy', '52F']>` and `<tool-error message='Location Not Found'>` or similar. Maybe define string constants in this file for parsing and building those strings? Maybe leverage base::Value ValueView/JSON/DebugString?

            Line 75, Patchset 14 (Latest): // Generate tool calls if tools are available and input doesn't contain tool
            Mike Wasserman . unresolved

            It might be aligned with EchoAILanguageModel behavior to just 'echo' the tool declarations in it's initialization, like `did_echo_initial_prompts_`, but we'd like to trend away from Echo impl behaviors that aren't to spec, so the pattern you're using here is better.

            Perhaps this class should just emit tool calls when callers explicitly request mock tool call generation with a well-known input string prefix, like "<GenerateSimpleToolCalls>". or explicitly track a `did_emit_tool_calls_` instead of inferring from tool response presence is `input`.

            Line 82, Patchset 14 (Latest): // Check if any tool has a ToolCallResponsePrefix for mixed text + tool
            Mike Wasserman . unresolved

            It seems like any input (e.g. "<GenerateSimpleToolCalls>Here is your tool call:") could be echoed before tool calls are emitted (optionally stripping the pseudo-token), to simplify logic and usage quite a bit.

            Per-tool prefixes might be useful if they were ordered immediately preceding the respective tool call, but currently the first tool decl with a prefix is yielded before all calls, which seems like a suboptimal first impl.

            Line 93, Patchset 14 (Latest): responder->OnToolCalls(std::move(tool_calls));
            // Open-loop pattern: Must call OnCompletion() after OnToolCalls() to
            // signal that tool call generation is complete. The renderer will convert
            // tool calls to structured messages and resolve the prompt() promise.
            responder->OnCompletion(
            blink::mojom::ModelExecutionContextInfo::New(current_tokens_));
            return;
            Mike Wasserman . unresolved

            Since GenerateSimpleToolCalls actually just ignores the `input` param, that input is never echo'ed. That should probably be fixed along with suggestions above.

            Line 230, Patchset 14 (Latest): for (size_t prompt_idx = 0; prompt_idx < prompts.size(); ++prompt_idx) {
            const auto& prompt = prompts[prompt_idx];

            for (size_t content_idx = 0; content_idx < prompt->content.size();
            ++content_idx) {
            auto& content = prompt->content[content_idx];
            Mike Wasserman . unresolved

            nit: revert if indicies aren't used?

            Line 254, Patchset 14 (Latest): // Tool calls should not be echoed back to the model.
            Mike Wasserman . unresolved

            Eventually, we will support this (e.g. clients append assistant-role tool calls to restore past session history or serve as multi-shot examples)

            Line 297, Patchset 14 (Latest): if (text_value) {
            result += *text_value;
            }
            Mike Wasserman . unresolved
            ```suggestion
            result = base::StrCat({result, text_value ? *text_value : "<text>"});
            ```
            Line 302, Patchset 14 (Latest): result += "<" + (type_str ? *type_str : "unknown") + ">";
            Mike Wasserman . unresolved

            nit: StrCat here and elsewhere

            Line 364, Patchset 14 (Latest):
            if (!args_json.has_value()) {
            // No valid Args JSON found.
            return base::Value(base::Value::Dict());
            }

            return std::move(*args_json);
            Mike Wasserman . unresolved
            nit: 
            ```suggestion
            return args_json.value_or(base::Value(base::Value::Dict()));
            ```
            Line 374, Patchset 14 (Latest):EchoAILanguageModel::ExtractToolCallResponsePrefix() {
            Mike Wasserman . unresolved

            nit: Use 'ToolCallPrefix' here and elsewhere instead of 'ToolCallResponsePrefix'
            ```suggestion
            EchoAILanguageModel::ExtractToolCallPrefix() {
            ```

            Line 401, Patchset 14 (Latest): std::vector<blink::mojom::ToolCallPtr>& tool_calls) {
            Mike Wasserman . unresolved

            optional nit: make this a return type?

            Line 407, Patchset 14 (Latest): // Create tool call.
            Mike Wasserman . unresolved

            nit: remove comments that just repeat the code

            Line 411, Patchset 14 (Latest):
            // Extract argument hints from tool description.
            base::Value args_value = ExtractArgumentHints(tool->description);
            tool_call->arguments = std::move(args_value);
            Mike Wasserman . unresolved
            ```suggestion
            tool_call->arguments = ExtractArgumentHints(tool->description);
            ```
            File content/browser/ai/echo_ai_manager_impl.cc
            Line 84, Patchset 14 (Latest): // Reject kToolCall in expectedInputs - tool calls are model outputs,
            Mike Wasserman . unresolved

            ditto, we'll eventually support this, but okay to punt for now.

            File third_party/blink/public/mojom/ai/ai_language_model.mojom
            Line 124, Patchset 14 (Latest): // Tool declarations for `Tool Use`.
            Mike Wasserman . unresolved

            nit: remove

            File third_party/blink/public/mojom/ai/model_streaming_responder.mojom
            Line 103, Patchset 14 (Latest): // Tool calls should be converted to structured messages and
            // returned to JavaScript. The caller will NOT automatically execute tools -
            // JavaScript must handle execution manually and send results back.
            Mike Wasserman . unresolved
            Let's generalize the recommended pattern for handling calls in this context, maybe:
            ```suggestion
            // Clients should validate and then execute or surface calls to JS API client.
            // Model sessions typically expect a tool result or error input thereafter.
            ```
            File third_party/blink/renderer/modules/ai/DEPS
            Line 2, Patchset 14 (Latest): "+base/json/json_reader.h",
            Mike Wasserman . unresolved

            Per other comment, can we use v8::JSON::Parse|Stringify to avoid atypical base/json use in Blink?

            File third_party/blink/renderer/modules/ai/ai_utils.h
            Line 82, Patchset 14 (Latest):// LanguageModelMessageContent with type "tool-call". This is used in `Tool Use`
            // to return tool calls as structured messages instead of executing them
            // automatically.
            Mike Wasserman . unresolved

            optional nit: remove second sentence

            File third_party/blink/renderer/modules/ai/ai_utils.cc
            Line 350, Patchset 14 (Latest): v8::Local<v8::Context> context = script_state->GetContext();
            Mike Wasserman . unresolved

            Maybe check ContextIsValid(), ditto in helper below

            Line 352, Patchset 14 (Latest): // Convert base::Value to JSON string.
            Mike Wasserman . unresolved

            Would V8ValueConverterImpl::ToV8Value help here?
            https://source.chromium.org/chromium/chromium/src/+/main:content/renderer/v8_value_converter_impl.h;drc=bf712ec1a13783224debb691ba88ad5c15b93194;l=29

            It seems like base::JSONWriter is not generally used in Blink

            Line 356, Patchset 14 (Latest): return ScriptValue(isolate, v8::Object::New(isolate));
            Mike Wasserman . unresolved

            Maybe ScriptValue::CreateNull(isolate) here and in other failures below?

            Line 386, Patchset 14 (Latest): auto* content = LanguageModelMessageContent::Create();
            content->setType(V8LanguageModelMessageType(
            V8LanguageModelMessageType::Enum::kToolCall));
            Mike Wasserman . unresolved

            Maybe move this down to where the value is set?

            Line 392, Patchset 14 (Latest): ConvertMojoValueToV8(script_state, tc->arguments);
            Mike Wasserman . unresolved

            Should this disambiguate empty args from a failure to parse args?

            Line 405, Patchset 14 (Latest): DummyExceptionStateForTesting exception_state;
            Mike Wasserman . unresolved

            This doesn't seem appropriate can we construct an ExceptionState from the isolate and actually yield them in the context of the of the API client?

            File third_party/blink/renderer/modules/ai/language_model.cc
            Line 581, Patchset 14 (Latest): tools; // Empty for CanCreateLanguageModel.
            Mike Wasserman . unresolved

            nit: remove comment or move above decl

            File third_party/blink/renderer/modules/ai/language_model_create_client.cc
            Line 59, Patchset 14 (Latest): result.error_message = "Tool description cannot be empty.";
            Mike Wasserman . unresolved

            Maybe include the tool name in all errors here and below

            Line 65, Patchset 14 (Latest): result.error_message = "Duplicate tool name: " + tool->name();
            Mike Wasserman . unresolved

            nit: StrCat here and elsewhere

            Line 74, Patchset 14 (Latest): // Check if the object is actually null (Web IDL might pass null as object).
            Mike Wasserman . unresolved

            Can't we expect automatic enforcement of this check (and the fact that schema is a non-null object?), by the fact that LanguageModelToolDeclaration idl uses the `required` keyword and specifies the member type, etc?

            Line 84, Patchset 14 (Latest): V8AtomicString(script_state->GetIsolate(), "type");
            Mike Wasserman . unresolved

            optional nit: cache isolate for repeated use

            Line 126, Patchset 14 (Latest): v8::TryCatch try_catch(script_state->GetIsolate());
            Mike Wasserman . unresolved

            If we didn't explicitly catch, would that just throw to the caller, and is that sufficient?

            Line 130, Patchset 14 (Latest): // Check if a V8 exception was thrown (from custom toJSON/getter).
            if (try_catch.HasCaught()) {
            // Capture the exception value before resetting.
            result.exception = try_catch.Exception();
            try_catch.Reset();
            return result;
            }
            Mike Wasserman . unresolved

            Maybe do this before checking whether maybe_json is empty?

            Line 307, Patchset 14 (Latest): // TODO(crbug.com/422803232): Investigate allowing kToolCall in
            // expectedInputs for few-shot prompting examples. This would require
            // careful consideration of:
            // 1. How to distinguish few-shot examples from actual assistant outputs.
            // 2. Whether models can effectively learn from few-shot tool call
            // examples.
            // 3. Whether system prompts can adequately substitute for this use case.
            Mike Wasserman . unresolved

            Let's omit listing thorough considerations here, the TODO can just be:

            ```suggestion
            // TODO(crbug.com/422803232): Maybe allow kToolCall expectedInputs.
            ```
            Line 333, Patchset 14 (Latest): if (expected->type != mojom::blink::AILanguageModelPromptType::kText &&
            expected->type !=
            mojom::blink::AILanguageModelPromptType::kToolResponse &&
            Mike Wasserman . unresolved

            nit: invert to check for kImage or kAudio

            Line 402, Patchset 14 (Latest): bool has_tool_call_in_expected_outputs = false;
            Mike Wasserman . unresolved

            Determine this from the expectedOutputs loop above

            Line 373, Patchset 14 (Latest): // Validate tools if provided, before processing initialPrompts.
            if (options_->hasTools() && !options_->tools().empty()) {
            // Check if the AIPromptAPIToolUse feature is enabled.
            if (!RuntimeEnabledFeatures::AIPromptAPIToolUseEnabled(
            GetExecutionContext())) {
            GetResolver()->Reject(DOMException::Create(
            "Tool use feature is not enabled",
            DOMException::GetErrorName(DOMExceptionCode::kNotSupportedError)));
            return;
            }

            // Validate tool declarations.
            ValidationResult validation_result =
            ValidateToolDeclarations(GetScriptState(), options_->tools());

            // If an exception was thrown, reject the promise with it.
            if (!validation_result.exception.IsEmpty()) {
            GetResolver()->Reject(validation_result.exception);
            return;
            }

            // No exception, so if there's a validation error, reject with it.
            if (!validation_result.error_message.empty()) {
            GetResolver()->RejectWithTypeError(validation_result.error_message);
            return;
            }

            // Validate that expectedOutputs includes "tool-call" when tools are
            // provided.
            bool has_tool_call_in_expected_outputs = false;
            if (options_->hasExpectedOutputs()) {
            for (const auto& expected : options_->expectedOutputs()) {
            if (expected->type() == V8LanguageModelMessageType::Enum::kToolCall) {
            has_tool_call_in_expected_outputs = true;
            break;
            }
            }
            }

            if (!has_tool_call_in_expected_outputs) {
            GetResolver()->RejectWithTypeError(
            "When tools are provided, expectedOutputs must include {type: "
            "\"tool-call\"}.");
            return;
            }
            Mike Wasserman . unresolved

            Perhaps some of this tool decl validation code could be moved into ValidateToolDeclarations? It's a lot of added logic in LanguageModelCreateClient::Create

            Line 543, Patchset 14 (Latest): maybe_tools = ConvertToolsToMojo(GetScriptState(), options_->tools());
            Mike Wasserman . unresolved

            It seems worth considering combining validation and conversion.

            File third_party/blink/renderer/modules/ai/language_model_prompt_builder.cc
            Line 124, Patchset 14 (Latest): for (size_t i = 0; i < result_items.size(); ++i) {
            Mike Wasserman . unresolved

            avoid index if not needed

            Line 146, Patchset 14 (Latest): std::optional<base::Value> parsed_value =

            base::JSONReader::Read(json_str, base::JSON_PARSE_RFC);
            if (!parsed_value) {
            // JSONReader failed to parse the stringified value.
            // This can happen if the value is undefined, or in other edge cases.
            return std::nullopt;
            }
            Mike Wasserman . unresolved

            stringifying and then immediately parsing seems strange, can we maybe use V8ValueConverterImpl::FromV8Value() or similar? Maybe fail later base::JSON handling fails in lower layers?

            Line 522, Patchset 14 (Latest): // Tool calls should not be provided as input to the model.
            // Tool calls are generated BY the model, not sent TO the model.
            // JavaScript should only send back tool RESPONSES after executing tools.
            // TODO(crbug.com/422803232): Consider supporting tool calls as input for
            // few-shot examples (prefilling conversations with example tool
            // interactions).
            Mike Wasserman . unresolved

            Ditto, please soften and simplify these comments and error strings

            Line 534, Patchset 14 (Latest): // LanguageModelToolResponse is a union of ToolSuccess and ToolError
            // interfaces. The bindings generator properly discriminates between the
            // two types.
            Mike Wasserman . unresolved

            nit: remove

            Line 552, Patchset 14 (Latest):
            if (!allowed_types_.Contains(
            mojom::blink::AILanguageModelPromptType::kToolResponse)) {
            Reject(DOMException::Create(
            "Tool responses not supported. Session is not initialized with "
            "tool support.",
            DOMException::GetErrorName(DOMExceptionCode::kNotSupportedError)));
            return;
            }
            Mike Wasserman . unresolved

            Check this before validating the response content as success/error

            Line 568, Patchset 14 (Latest): // Failed to serialize tool result (circular reference, function,
            // etc).
            Mike Wasserman . unresolved

            remove comments that echo code

            File third_party/blink/renderer/modules/ai/model_execution_responder.h
            Line 77, Patchset 14 (Latest):// completion. For `Tool Use` in LanguageModel API: resolves with tool call
            // messages if present, otherwise with text response. For other APIs
            // (Proofreader, etc.): always resolves with text response, ignoring tool_calls
            // parameter.
            Mike Wasserman . unresolved

            This generic resolve-on-completing helper could more simply note that tool calls are ignored here. (and optionally check that the vector is empty?)

            Line 51, Patchset 14 (Latest):// For `Tool Use`: `complete_callback` receives BOTH text response AND mojo
            // tool calls. Text response is always provided (may be empty). Tool calls
            // vector may be empty if no tools were called. The caller must convert
            // non-empty tool calls to Blink dictionaries in V8 context when resolving.
            Mike Wasserman . unresolved

            This rather unnecessarily and inflexibly enforces model-specific details in a messaging tool.

            I'd prefer to see a separate tool_call_callback handler added, leaving usage of this class to handle the ordering of streaming text, then calls, then a completion signal, and avoid changes to the complete callback and generic helpers like ResolvePromiseOnCompletion.

            File third_party/blink/renderer/modules/ai/model_execution_responder.cc
            Line 136, Patchset 14 (Latest):
            // `Tool Use` Open-loop: Store mojo tool calls. We'll convert them to
            // LanguageModelToolCall interface instances in OnCompletion when we have a
            // proper V8 context. This avoids V8 handle lifetime issues across async
            // mojo callbacks.
            Mike Wasserman . unresolved

            I'm not sure this detail is pertinent here. What makes the v8 context lifetime situation different here from the streamingresponder impl?

            File third_party/blink/renderer/modules/ai/model_execution_responder_test.cc
            Line 79, Patchset 14 (Latest): // Test callback: Verify context_info and resolve with text
            // response. Tool calls are not exercised in this test.
            Mike Wasserman . unresolved

            nit: remove comment

            File third_party/blink/renderer/modules/ai/proofreader.cc
            Line 461, Patchset 14 (Latest): // Adapter lambda: Proofreader doesn't use tool calls. Extract text
            Mike Wasserman . unresolved

            It would be nice to avoid this

            File third_party/blink/web_tests/VirtualTestSuites
            Line 33, Patchset 14 (Latest): "platforms": ["Linux", "Mac", "Win"],
            Mike Wasserman . unresolved

            we might be able to enabled Chrome OS here too

            File third_party/blink/web_tests/external/wpt/ai/language-model/language-model-tool-use.tentative.https.window.js
            Line 1, Patchset 14 (Latest):// META: title=Language Model Tool Use (Open Loop)
            Mike Wasserman . unresolved

            I'll have to review this file later

            File third_party/blink/web_tests/virtual/ai_tool_use/README.md
            Line 7, Patchset 14 (Latest):This suite runs Web Platform Tests (WPT) for the Language Model API with Tool Use
            Mike Wasserman . unresolved

            nit: wrap file at 80 chars

            Open in Gerrit

            Related details

            Attention is currently required from:
            • Frank Li
            • Jingyun Liu
            Submit Requirements:
              • requirement satisfiedCode-Coverage
              • requirement is not satisfiedCode-Owners
              • requirement satisfiedCode-Review
              • requirement is not satisfiedNo-Unresolved-Comments
              • requirement is not satisfiedReview-Enforcement
              Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
              Gerrit-MessageType: comment
              Gerrit-Project: chromium/src
              Gerrit-Branch: main
              Gerrit-Change-Id: I8b2e969c55f44dc59313d83ae5728e621636e708
              Gerrit-Change-Number: 7490714
              Gerrit-PatchSet: 14
              Gerrit-Owner: Frank Li <fra...@microsoft.com>
              Gerrit-Reviewer: Daniel Cheng <dch...@chromium.org>
              Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
              Gerrit-Reviewer: Frank Li <fra...@microsoft.com>
              Gerrit-Reviewer: Jingyun Liu <jin...@google.com>
              Gerrit-Reviewer: Mike Wasserman <m...@chromium.org>
              Gerrit-CC: Kentaro Hara <har...@chromium.org>
              Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
              Gerrit-CC: Sushanth Rajasankar <Sush...@microsoft.com>
              Gerrit-Attention: Frank Li <fra...@microsoft.com>
              Gerrit-Attention: Jingyun Liu <jin...@google.com>
              Gerrit-Comment-Date: Wed, 28 Jan 2026 01:16:09 +0000
              Gerrit-HasComments: Yes
              Gerrit-Has-Labels: No
              satisfied_requirement
              unsatisfied_requirement
              open
              diffy

              Mike Wasserman (Gerrit)

              unread,
              Jan 27, 2026, 8:18:13 PMJan 27
              to Frank Li, Dominic Farolino, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
              Attention needed from Frank Li and Jingyun Liu

              Mike Wasserman added 1 comment

              Patchset-level comments
              Mike Wasserman . resolved

              In the future, when it's feasible, smaller changes would likely be easier to review.

              Gerrit-Comment-Date: Wed, 28 Jan 2026 01:18:03 +0000
              Gerrit-HasComments: Yes
              Gerrit-Has-Labels: No
              satisfied_requirement
              unsatisfied_requirement
              open
              diffy

              Frank Li (Gerrit)

              unread,
              Jan 31, 2026, 3:11:15 AM (11 days ago) Jan 31
              to Dominic Farolino, Mike Wasserman, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
              Attention needed from Dominic Farolino, Jingyun Liu and Mike Wasserman

              Frank Li added 53 comments

              Patchset-level comments
              File-level comment, Patchset 16 (Latest):
              Frank Li . resolved

              Hi Mike,
              Thank you for your thorough review! PTAL with the updates (PS 15 and 16).

              File chrome/browser/ai/ai_language_model.cc
              Line 97, Patchset 14: // Tool use is not yet supported in browser-side model execution.
              Mike Wasserman . resolved
              optional nit:
              ```suggestion
              // TODO(crbug.com/422803232): Support on_device_model tool use
              ```
              Frank Li

              Done

              File content/browser/ai/echo_ai_language_model.cc
              Line 14, Patchset 14:#include "base/notreached.h"
              Mike Wasserman . resolved

              remove if unused

              Frank Li

              removed: "base/notreached.h"

              Line 72, Patchset 14: (input.find("[TOOL_SUCCESS:") != std::string::npos) ||

              (input.find("[Tool Error:") != std::string::npos);
              Mike Wasserman . resolved

              nit: align tool response formats (maybe `<tool-success name='get_weather' id='123' result=['rainy', '52F']>` and `<tool-error message='Location Not Found'>` or similar. Maybe define string constants in this file for parsing and building those strings? Maybe leverage base::Value ValueView/JSON/DebugString?

              Frank Li

              updated code in: `EchoAILanguageModel::ExtractToolResponseText()`

              Changed from [TOOL_SUCCESS:] / [Tool Error:] to
              <tool-success id='X' name='Y' result=[...]> / <tool-error id='X' name='Y' message="...">

              Line 75, Patchset 14: // Generate tool calls if tools are available and input doesn't contain tool
              Mike Wasserman . resolved

              It might be aligned with EchoAILanguageModel behavior to just 'echo' the tool declarations in it's initialization, like `did_echo_initial_prompts_`, but we'd like to trend away from Echo impl behaviors that aren't to spec, so the pattern you're using here is better.

              Perhaps this class should just emit tool calls when callers explicitly request mock tool call generation with a well-known input string prefix, like "<GenerateSimpleToolCalls>". or explicitly track a `did_emit_tool_calls_` instead of inferring from tool response presence is `input`.

              Frank Li
              • Added explicit <GenerateSimpleToolCalls> input string prefix to trigger tool call.
              • Removed inference from tool response presence
              Line 82, Patchset 14: // Check if any tool has a ToolCallResponsePrefix for mixed text + tool
              Mike Wasserman . resolved

              It seems like any input (e.g. "<GenerateSimpleToolCalls>Here is your tool call:") could be echoed before tool calls are emitted (optionally stripping the pseudo-token), to simplify logic and usage quite a bit.

              Per-tool prefixes might be useful if they were ordered immediately preceding the respective tool call, but currently the first tool decl with a prefix is yielded before all calls, which seems like a suboptimal first impl.

              Frank Li

              Done

              Line 93, Patchset 14: responder->OnToolCalls(std::move(tool_calls));

              // Open-loop pattern: Must call OnCompletion() after OnToolCalls() to
              // signal that tool call generation is complete. The renderer will convert
              // tool calls to structured messages and resolve the prompt() promise.
              responder->OnCompletion(
              blink::mojom::ModelExecutionContextInfo::New(current_tokens_));
              return;
              Mike Wasserman . resolved

              Since GenerateSimpleToolCalls actually just ignores the `input` param, that input is never echo'ed. That should probably be fixed along with suggestions above.

              Frank Li

              Done

              Line 230, Patchset 14: for (size_t prompt_idx = 0; prompt_idx < prompts.size(); ++prompt_idx) {

              const auto& prompt = prompts[prompt_idx];

              for (size_t content_idx = 0; content_idx < prompt->content.size();
              ++content_idx) {
              auto& content = prompt->content[content_idx];
              Mike Wasserman . resolved

              nit: revert if indicies aren't used?

              Frank Li

              Done

              Line 254, Patchset 14: // Tool calls should not be echoed back to the model.
              Mike Wasserman . resolved

              Eventually, we will support this (e.g. clients append assistant-role tool calls to restore past session history or serve as multi-shot examples)

              Frank Li

              Added TODO.

              Line 297, Patchset 14: if (text_value) {
              result += *text_value;
              }
              Mike Wasserman . resolved
              ```suggestion
              result = base::StrCat({result, text_value ? *text_value : "<text>"});
              ```
              Frank Li

              Done

              Line 302, Patchset 14: result += "<" + (type_str ? *type_str : "unknown") + ">";
              Mike Wasserman . resolved

              nit: StrCat here and elsewhere


              if (!args_json.has_value()) {
              // No valid Args JSON found.
              return base::Value(base::Value::Dict());
              }

              return std::move(*args_json);
              Mike Wasserman . resolved
              nit: 
              ```suggestion
              return args_json.value_or(base::Value(base::Value::Dict()));
              ```
              Frank Li
              build error:
              ~~~
              ../../third_party/libc++/src/include\optional(1118,19): error: static assertion failed due to requirement 'is_copy_constructible_v<base::Value>': optional<T>::value_or: T must be copy constructible
              1118 | static_assert(is_copy_constructible_v<_Tp>, "optional<T>::value_or: T must be copy constructible");
              | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
              ../../content/browser/ai/echo_ai_language_model.cc(387,20): note: in instantiation of function template specialization 'std::optional<base::Value>::value_or<base::Value>' requested here
              387 | return args_json.value_or(base::Value(base::Value::Dict()));
              | ^
              In file included from ../../content/browser/ai/echo_ai_language_model.cc:5:
              In file included from ../..\content/browser/ai/echo_ai_language_model.h:8:
              ../../third_party/libc++/src/include\optional(1120,32): error: call to deleted constructor of 'const value_type' (aka 'const base::Value')
              1120 | return this->has_value() ? this->__get() : static_cast<_Tp>(std::forward<_Up>(__v));
              | ^~~~~~~~~~~~~
              ../..\base/values.h(718,3): note: 'Value' has been explicitly marked deleted here
              718 | Value(const Value&) = delete;
              | ^
              ~~~

              The issue is that base::Value is move-only and .value_or() requires copy-constructibility.

              Line 374, Patchset 14:EchoAILanguageModel::ExtractToolCallResponsePrefix() {
              Mike Wasserman . resolved

              nit: Use 'ToolCallPrefix' here and elsewhere instead of 'ToolCallResponsePrefix'
              ```suggestion
              EchoAILanguageModel::ExtractToolCallPrefix() {
              ```

              Frank Li

              Removed `ExtractToolCallResponsePrefix()` since it is no longer needed.

              Line 401, Patchset 14: std::vector<blink::mojom::ToolCallPtr>& tool_calls) {
              Mike Wasserman . resolved

              optional nit: make this a return type?

              Frank Li

              Done

              Line 407, Patchset 14: // Create tool call.
              Mike Wasserman . resolved

              nit: remove comments that just repeat the code


              // Extract argument hints from tool description.
              base::Value args_value = ExtractArgumentHints(tool->description);
              tool_call->arguments = std::move(args_value);
              Mike Wasserman . resolved
              ```suggestion
              tool_call->arguments = ExtractArgumentHints(tool->description);
              ```
              Frank Li

              Done

              File content/browser/ai/echo_ai_manager_impl.cc
              Line 84, Patchset 14: // Reject kToolCall in expectedInputs - tool calls are model outputs,
              Mike Wasserman . resolved

              ditto, we'll eventually support this, but okay to punt for now.

              Frank Li

              Added TODO.

              File third_party/blink/public/mojom/ai/ai_language_model.mojom
              Line 124, Patchset 14: // Tool declarations for `Tool Use`.
              Mike Wasserman . resolved

              nit: remove

              Frank Li

              Done

              File third_party/blink/public/mojom/ai/model_streaming_responder.mojom
              Line 103, Patchset 14: // Tool calls should be converted to structured messages and

              // returned to JavaScript. The caller will NOT automatically execute tools -
              // JavaScript must handle execution manually and send results back.
              Mike Wasserman . resolved
              Let's generalize the recommended pattern for handling calls in this context, maybe:
              ```suggestion
              // Clients should validate and then execute or surface calls to JS API client.
              // Model sessions typically expect a tool result or error input thereafter.
              ```
              Frank Li

              Done

              File third_party/blink/renderer/modules/ai/DEPS
              Line 2, Patchset 14: "+base/json/json_reader.h",
              Mike Wasserman . resolved

              Per other comment, can we use v8::JSON::Parse|Stringify to avoid atypical base/json use in Blink?

              Frank Li

              Updated to use Stringify

              File third_party/blink/renderer/modules/ai/ai_utils.h
              Line 82, Patchset 14:// LanguageModelMessageContent with type "tool-call". This is used in `Tool Use`

              // to return tool calls as structured messages instead of executing them
              // automatically.
              Mike Wasserman . resolved

              optional nit: remove second sentence

              Frank Li

              Done

              File third_party/blink/renderer/modules/ai/ai_utils.cc
              Line 350, Patchset 14: v8::Local<v8::Context> context = script_state->GetContext();
              Mike Wasserman . resolved

              Maybe check ContextIsValid(), ditto in helper below

              Frank Li

              Done

              Line 352, Patchset 14: // Convert base::Value to JSON string.
              Mike Wasserman . resolved

              Would V8ValueConverterImpl::ToV8Value help here?
              https://source.chromium.org/chromium/chromium/src/+/main:content/renderer/v8_value_converter_impl.h;drc=bf712ec1a13783224debb691ba88ad5c15b93194;l=29

              It seems like base::JSONWriter is not generally used in Blink

              Frank Li

              yes. Converted the code to use `V8ValueConverterImpl::ToV8Value`

              Line 356, Patchset 14: return ScriptValue(isolate, v8::Object::New(isolate));
              Mike Wasserman . resolved

              Maybe ScriptValue::CreateNull(isolate) here and in other failures below?

              Frank Li

              Done

              Line 386, Patchset 14: auto* content = LanguageModelMessageContent::Create();

              content->setType(V8LanguageModelMessageType(
              V8LanguageModelMessageType::Enum::kToolCall));
              Mike Wasserman . resolved

              Maybe move this down to where the value is set?

              Frank Li

              Done

              Line 392, Patchset 14: ConvertMojoValueToV8(script_state, tc->arguments);
              Mike Wasserman . resolved

              Should this disambiguate empty args from a failure to parse args?

              Frank Li

              Done

              Line 405, Patchset 14: DummyExceptionStateForTesting exception_state;
              Mike Wasserman . resolved

              This doesn't seem appropriate can we construct an ExceptionState from the isolate and actually yield them in the context of the of the API client?

              Frank Li

              Done

              File third_party/blink/renderer/modules/ai/language_model.cc
              Line 581, Patchset 14: tools; // Empty for CanCreateLanguageModel.
              Mike Wasserman . resolved

              nit: remove comment or move above decl

              Frank Li

              Done

              File third_party/blink/renderer/modules/ai/language_model_create_client.cc
              Line 59, Patchset 14: result.error_message = "Tool description cannot be empty.";
              Mike Wasserman . resolved

              Maybe include the tool name in all errors here and below

              Frank Li

              Done

              Line 65, Patchset 14: result.error_message = "Duplicate tool name: " + tool->name();
              Mike Wasserman . resolved

              nit: StrCat here and elsewhere

              Frank Li

              Done

              Line 74, Patchset 14: // Check if the object is actually null (Web IDL might pass null as object).
              Mike Wasserman . resolved

              Can't we expect automatic enforcement of this check (and the fact that schema is a non-null object?), by the fact that LanguageModelToolDeclaration idl uses the `required` keyword and specifies the member type, etc?

              Frank Li

              removed the check.

              Line 84, Patchset 14: V8AtomicString(script_state->GetIsolate(), "type");
              Mike Wasserman . resolved

              optional nit: cache isolate for repeated use

              Frank Li

              Done

              Line 126, Patchset 14: v8::TryCatch try_catch(script_state->GetIsolate());
              Mike Wasserman . resolved

              If we didn't explicitly catch, would that just throw to the caller, and is that sufficient?

              Frank Li

              No, the exception does NOT automatically propagate to JavaScript. Without the explicit TryCatch, the exception is set in the V8 isolate's pending exception state but simply returning from the method won't cause the Promise to reject with that exception. We need the TryCatch to capture the exception and pass it to GetResolver()->Reject().

              Line 130, Patchset 14: // Check if a V8 exception was thrown (from custom toJSON/getter).

              if (try_catch.HasCaught()) {
              // Capture the exception value before resetting.
              result.exception = try_catch.Exception();
              try_catch.Reset();
              return result;
              }
              Mike Wasserman . resolved

              Maybe do this before checking whether maybe_json is empty?

              Frank Li

              Done

              Line 307, Patchset 14: // TODO(crbug.com/422803232): Investigate allowing kToolCall in

              // expectedInputs for few-shot prompting examples. This would require
              // careful consideration of:
              // 1. How to distinguish few-shot examples from actual assistant outputs.
              // 2. Whether models can effectively learn from few-shot tool call
              // examples.
              // 3. Whether system prompts can adequately substitute for this use case.
              Mike Wasserman . resolved

              Let's omit listing thorough considerations here, the TODO can just be:

              ```suggestion
              // TODO(crbug.com/422803232): Maybe allow kToolCall expectedInputs.
              ```
              Frank Li

              Done

              Line 333, Patchset 14: if (expected->type != mojom::blink::AILanguageModelPromptType::kText &&

              expected->type !=
              mojom::blink::AILanguageModelPromptType::kToolResponse &&
              Mike Wasserman . resolved

              nit: invert to check for kImage or kAudio

              Frank Li

              Done

              Line 402, Patchset 14: bool has_tool_call_in_expected_outputs = false;
              Mike Wasserman . resolved

              Determine this from the expectedOutputs loop above

              Frank Li

              Done

              Line 373, Patchset 14: // Validate tools if provided, before processing initialPrompts.
              Mike Wasserman . resolved

              Perhaps some of this tool decl validation code could be moved into ValidateToolDeclarations? It's a lot of added logic in LanguageModelCreateClient::Create

              Frank Li

              combined them into `ValidateAndConvertToolDeclarations()`

              Line 543, Patchset 14: maybe_tools = ConvertToolsToMojo(GetScriptState(), options_->tools());
              Mike Wasserman . resolved

              It seems worth considering combining validation and conversion.

              Frank Li

              Done

              File third_party/blink/renderer/modules/ai/language_model_prompt_builder.cc
              Line 124, Patchset 14: for (size_t i = 0; i < result_items.size(); ++i) {
              Mike Wasserman . resolved

              avoid index if not needed

              Frank Li

              Done

              Line 146, Patchset 14: std::optional<base::Value> parsed_value =

              base::JSONReader::Read(json_str, base::JSON_PARSE_RFC);
              if (!parsed_value) {
              // JSONReader failed to parse the stringified value.
              // This can happen if the value is undefined, or in other edge cases.
              return std::nullopt;
              }
              Mike Wasserman . resolved

              stringifying and then immediately parsing seems strange, can we maybe use V8ValueConverterImpl::FromV8Value() or similar? Maybe fail later base::JSON handling fails in lower layers?

              Frank Li

              I have converted the code to use `WebV8ValueConverter`.

              Line 522, Patchset 14: // Tool calls should not be provided as input to the model.

              // Tool calls are generated BY the model, not sent TO the model.
              // JavaScript should only send back tool RESPONSES after executing tools.
              // TODO(crbug.com/422803232): Consider supporting tool calls as input for
              // few-shot examples (prefilling conversations with example tool
              // interactions).
              Mike Wasserman . resolved

              Ditto, please soften and simplify these comments and error strings

              Frank Li

              Done

              Line 534, Patchset 14: // LanguageModelToolResponse is a union of ToolSuccess and ToolError

              // interfaces. The bindings generator properly discriminates between the
              // two types.
              Mike Wasserman . resolved

              nit: remove

              Frank Li

              Done


              if (!allowed_types_.Contains(
              mojom::blink::AILanguageModelPromptType::kToolResponse)) {
              Reject(DOMException::Create(
              "Tool responses not supported. Session is not initialized with "
              "tool support.",
              DOMException::GetErrorName(DOMExceptionCode::kNotSupportedError)));
              return;
              }
              Mike Wasserman . resolved

              Check this before validating the response content as success/error

              Frank Li

              Done

              Line 568, Patchset 14: // Failed to serialize tool result (circular reference, function,
              // etc).
              Mike Wasserman . resolved

              remove comments that echo code

              Frank Li

              Done

              File third_party/blink/renderer/modules/ai/model_execution_responder.h
              Line 77, Patchset 14:// completion. For `Tool Use` in LanguageModel API: resolves with tool call

              // messages if present, otherwise with text response. For other APIs
              // (Proofreader, etc.): always resolves with text response, ignoring tool_calls
              // parameter.
              Mike Wasserman . resolved

              This generic resolve-on-completing helper could more simply note that tool calls are ignored here. (and optionally check that the vector is empty?)

              Frank Li

              Removed the comment since we have changed to use separate tool_call_callback handler without the extra tool_calls param here.

              Line 51, Patchset 14:// For `Tool Use`: `complete_callback` receives BOTH text response AND mojo

              // tool calls. Text response is always provided (may be empty). Tool calls
              // vector may be empty if no tools were called. The caller must convert
              // non-empty tool calls to Blink dictionaries in V8 context when resolving.
              Mike Wasserman . resolved

              This rather unnecessarily and inflexibly enforces model-specific details in a messaging tool.

              I'd prefer to see a separate tool_call_callback handler added, leaving usage of this class to handle the ordering of streaming text, then calls, then a completion signal, and avoid changes to the complete callback and generic helpers like ResolvePromiseOnCompletion.

              Frank Li

              Refactor to add separate tool_call_callback handler.

              File third_party/blink/renderer/modules/ai/model_execution_responder.cc

              // `Tool Use` Open-loop: Store mojo tool calls. We'll convert them to
              // LanguageModelToolCall interface instances in OnCompletion when we have a
              // proper V8 context. This avoids V8 handle lifetime issues across async
              // mojo callbacks.
              Mike Wasserman . resolved

              I'm not sure this detail is pertinent here. What makes the v8 context lifetime situation different here from the streamingresponder impl?

              Frank Li

              Done. Removed comment about V8 handle lifetime issues as it's not pertinent here.

              File third_party/blink/renderer/modules/ai/model_execution_responder_test.cc
              Line 79, Patchset 14: // Test callback: Verify context_info and resolve with text

              // response. Tool calls are not exercised in this test.
              Mike Wasserman . resolved

              nit: remove comment

              Frank Li

              Done

              File third_party/blink/renderer/modules/ai/proofreader.cc
              Line 461, Patchset 14: // Adapter lambda: Proofreader doesn't use tool calls. Extract text
              Mike Wasserman . resolved

              It would be nice to avoid this

              Frank Li

              done. removed the adapter lambda

              File third_party/blink/web_tests/VirtualTestSuites
              Line 33, Patchset 14: "platforms": ["Linux", "Mac", "Win"],
              Mike Wasserman . resolved

              we might be able to enabled Chrome OS here too

              Frank Li

              Done

              File third_party/blink/web_tests/external/wpt/ai/language-model/language-model-tool-use.tentative.https.window.js
              Line 1, Patchset 14:// META: title=Language Model Tool Use (Open Loop)
              Mike Wasserman . resolved

              I'll have to review this file later

              Frank Li

              Acknowledged

              File third_party/blink/web_tests/virtual/ai_tool_use/README.md
              Line 7, Patchset 14:This suite runs Web Platform Tests (WPT) for the Language Model API with Tool Use
              Mike Wasserman . resolved

              nit: wrap file at 80 chars

              Frank Li

              Done

              Open in Gerrit

              Related details

              Attention is currently required from:
              • Dominic Farolino
              • Jingyun Liu
              • Mike Wasserman
              Submit Requirements:
                • requirement satisfiedCode-Coverage
                • requirement is not satisfiedCode-Owners
                • requirement is not satisfiedCode-Review
                • requirement is not satisfiedReview-Enforcement
                Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
                Gerrit-MessageType: comment
                Gerrit-Project: chromium/src
                Gerrit-Branch: main
                Gerrit-Change-Id: I8b2e969c55f44dc59313d83ae5728e621636e708
                Gerrit-Change-Number: 7490714
                Gerrit-PatchSet: 16
                Gerrit-Owner: Frank Li <fra...@microsoft.com>
                Gerrit-Reviewer: Daniel Cheng <dch...@chromium.org>
                Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
                Gerrit-Reviewer: Frank Li <fra...@microsoft.com>
                Gerrit-Reviewer: Jingyun Liu <jin...@google.com>
                Gerrit-Reviewer: Mike Wasserman <m...@chromium.org>
                Gerrit-CC: Kentaro Hara <har...@chromium.org>
                Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
                Gerrit-CC: Sushanth Rajasankar <Sush...@microsoft.com>
                Gerrit-Attention: Mike Wasserman <m...@chromium.org>
                Gerrit-Attention: Dominic Farolino <d...@chromium.org>
                Gerrit-Attention: Jingyun Liu <jin...@google.com>
                Gerrit-Comment-Date: Sat, 31 Jan 2026 08:11:01 +0000
                Gerrit-HasComments: Yes
                Gerrit-Has-Labels: No
                Comment-In-Reply-To: Mike Wasserman <m...@chromium.org>
                satisfied_requirement
                unsatisfied_requirement
                open
                diffy

                Jingyun Liu (Gerrit)

                unread,
                Feb 10, 2026, 2:02:37 PM (20 hours ago) Feb 10
                to Frank Li, Dominic Farolino, Mike Wasserman, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
                Attention needed from Dominic Farolino, Frank Li and Mike Wasserman

                Jingyun Liu added 1 comment

                Patchset-level comments
                File-level comment, Patchset 18 (Latest):
                Jingyun Liu . resolved

                lgtm, thank you!

                Open in Gerrit

                Related details

                Attention is currently required from:
                • Dominic Farolino
                • Frank Li
                • Mike Wasserman
                Submit Requirements:
                • requirement satisfiedCode-Coverage
                • requirement is not satisfiedCode-Owners
                • requirement is not satisfiedCode-Review
                • requirement is not satisfiedReview-Enforcement
                Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
                Gerrit-MessageType: comment
                Gerrit-Project: chromium/src
                Gerrit-Branch: main
                Gerrit-Change-Id: I8b2e969c55f44dc59313d83ae5728e621636e708
                Gerrit-Change-Number: 7490714
                Gerrit-PatchSet: 18
                Gerrit-Owner: Frank Li <fra...@microsoft.com>
                Gerrit-Reviewer: Daniel Cheng <dch...@chromium.org>
                Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
                Gerrit-Reviewer: Frank Li <fra...@microsoft.com>
                Gerrit-Reviewer: Jingyun Liu <jin...@google.com>
                Gerrit-Reviewer: Mike Wasserman <m...@chromium.org>
                Gerrit-CC: Kentaro Hara <har...@chromium.org>
                Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
                Gerrit-CC: Sushanth Rajasankar <Sush...@microsoft.com>
                Gerrit-Attention: Mike Wasserman <m...@chromium.org>
                Gerrit-Attention: Dominic Farolino <d...@chromium.org>
                Gerrit-Attention: Frank Li <fra...@microsoft.com>
                Gerrit-Comment-Date: Tue, 10 Feb 2026 19:02:28 +0000
                Gerrit-HasComments: Yes
                Gerrit-Has-Labels: No
                satisfied_requirement
                unsatisfied_requirement
                open
                diffy

                Frank Li (Gerrit)

                unread,
                Feb 10, 2026, 2:38:22 PM (20 hours ago) Feb 10
                to Nathan Memmott, Dominic Farolino, Mike Wasserman, Jingyun Liu, Daniel Cheng, Sushanth Rajasankar, Chromium LUCI CQ, chromium...@chromium.org, Kentaro Hara, Raphael Kubo da Costa, blink-re...@chromium.org, blink-revie...@chromium.org, blink-revie...@chromium.org, blink-...@chromium.org, chrome-intell...@chromium.org, chrome-intelligence-te...@google.com, ipc-securi...@chromium.org, kinuko...@chromium.org
                Attention needed from Daniel Cheng, Dominic Farolino, Mike Wasserman and Nathan Memmott

                Frank Li added 1 comment

                Patchset-level comments
                Frank Li . resolved

                Hi Nathan,
                Added you here as the owner of files touched under chrome/browser/ai/*.
                Thanks!

                Open in Gerrit

                Related details

                Attention is currently required from:
                • Daniel Cheng
                • Dominic Farolino
                • Mike Wasserman
                • Nathan Memmott
                Submit Requirements:
                • requirement satisfiedCode-Coverage
                • requirement is not satisfiedCode-Owners
                • requirement is not satisfiedCode-Review
                • requirement is not satisfiedReview-Enforcement
                Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
                Gerrit-MessageType: comment
                Gerrit-Project: chromium/src
                Gerrit-Branch: main
                Gerrit-Change-Id: I8b2e969c55f44dc59313d83ae5728e621636e708
                Gerrit-Change-Number: 7490714
                Gerrit-PatchSet: 18
                Gerrit-Owner: Frank Li <fra...@microsoft.com>
                Gerrit-Reviewer: Daniel Cheng <dch...@chromium.org>
                Gerrit-Reviewer: Dominic Farolino <d...@chromium.org>
                Gerrit-Reviewer: Frank Li <fra...@microsoft.com>
                Gerrit-Reviewer: Jingyun Liu <jin...@google.com>
                Gerrit-Reviewer: Mike Wasserman <m...@chromium.org>
                Gerrit-Reviewer: Nathan Memmott <mem...@chromium.org>
                Gerrit-CC: Kentaro Hara <har...@chromium.org>
                Gerrit-CC: Raphael Kubo da Costa <ku...@igalia.com>
                Gerrit-CC: Sushanth Rajasankar <Sush...@microsoft.com>
                Gerrit-Attention: Mike Wasserman <m...@chromium.org>
                Gerrit-Attention: Nathan Memmott <mem...@chromium.org>
                Gerrit-Attention: Dominic Farolino <d...@chromium.org>
                Gerrit-Attention: Daniel Cheng <dch...@chromium.org>
                Gerrit-Comment-Date: Tue, 10 Feb 2026 19:38:12 +0000
                Gerrit-HasComments: Yes
                Gerrit-Has-Labels: No
                satisfied_requirement
                unsatisfied_requirement
                open
                diffy
                Reply all
                Reply to author
                Forward
                0 new messages