Skip to content

Conversation

@timtmok
Copy link
Contributor

@timtmok timtmok commented Dec 3, 2025

Address #10818

This used Vercel's MCP server to assist in the migration to v5.

  • Removed the workaround for the temperature
  • Updated the field names that changed
  • Fixed the responses to match the v5 format

Release Notes

New Features

  • Update ai-sdk to v5

Bug Fixes

  • N/A

QA Notes

I did some light testing with tool calling with various providers. The chat response handling is where things changed the most.

@github-actions
Copy link

github-actions bot commented Dec 3, 2025

E2E Tests 🚀
This PR will run tests tagged with: @:critical

readme  valid tags

@timtmok timtmok marked this pull request as ready for review December 3, 2025 17:03
@timtmok timtmok requested a review from sharon-wang December 3, 2025 17:03
Copy link
Member

@sharon-wang sharon-wang left a comment

Choose a reason for hiding this comment

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

Looking good so far with Amazon Bedrock, OpenAI and OpenRouter via Custom Provider 👍 getPlot tool is working with Amazon Bedrock, so I wonder if our plot image handling is compatible with Anthropic models but not the OpenAI models?

Providers that don't support the responses endpoint aren't working though -- I think we'll need some way to override the chat endpoint for those providers to /chat/completions

@sharon-wang sharon-wang requested a review from wch December 3, 2025 22:42
@timtmok timtmok requested a review from sharon-wang December 4, 2025 19:54
* AI SDK 5 supports images in tool results via the 'content' output type with 'media' parts.
*/
function getPlotToolResultToAiMessage(part: vscode.LanguageModelToolResultPart2): ai.CoreUserMessage {
function getPlotToolResultToAiMessage(part: vscode.LanguageModelToolResultPart2): ai.ToolModelMessage {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just wondering: is this function still needed? I think that convertToolResultToAiMessageExperimentalContent should be able to handle tool results with images.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed it and tested the change. Various models and providers were able to handle the tool output without this function. It also fixed the problem with GPT models on Snowflake where it couldn't use the get plot tool output.

Comment on lines 82 to 85
if (cacheBreakpoint && bedrockCacheBreakpoint) {
cacheBreakpoint = false;
markBedrockCacheBreakpoint(toolMessage);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This probably doesn't have to be addressed just yet, but if/when Anthropic goes through this code path (instead of the separate Anthropic provider code), then we'll want to support their cache breakpoint markers as well.

Comment on lines 131 to 134
// Fix empty role field
if (choice.delta.role === '') {
choice.delta.role = 'assistant';
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this check here because of Snowflake, or other providers that don't quite adhere to the OpenAI completions format?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah this was originally added for Snowflake, but it's possible other providers may omit this as well

}
}

transformedLines.push(`data: ${JSON.stringify(data)}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

Using the types from OpenAI, I suggest actually extracting a lot of the code above into a function which takes as input something that's close to a ChatCompletionChunk, but where the type takes into account the possibly-missing fields. The function would return something that actually is a ChatCompletionChunk.

Then you can really lean into type checking to make sure all the cases are taken care of.

For example, if you define this type, then the role could be "":

type PossiblyBrokenChatCompletionChunk = Omit<OpenAI.ChatCompletionChunk, 'choices'> & {
	choices: Array<Omit<OpenAI.ChatCompletionChunk['choices'][0], 'delta'> & {
		delta: Omit<OpenAI.ChatCompletionChunk['choices'][0]['delta'], 'role'> & {
			role?: OpenAI.ChatCompletionChunk['choices'][0]['delta']['role'] | ''
		}
	}>
};

I know that's kind of awkward but it does work. It could probably be made a bit simpler and easier to read by referencing the types by name instead of by path. (I would just ask an LLM to add in the other possibly-empty fields as well.)

Next, you'd modify the type guard function above to have the signature:

export function isPossiblyBrokenChatCompletionChunk(obj: unknown): obj is PossiblyBrokenChatCompletionChunk {
    // body of this function can remain the same
}

Then you could add a function with this signature:

function fixPossiblyBrokenChatCompletionChunk(chunk: PossiblyBrokenChatCompletionChunk): OpenAI.ChatCompletionChunk {
	// Implementation goes here
}

Then up above you'd use:

if (isPossiblyBrokenChatCompletionChunk(data)) {
  const fixedChunk = fixPossiblyBrokenChatCompletionChunk(data)
  transformedLines.push(`data: ${JSON.stringify(fixedChunk)}`);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've refactored it so there's a function to check if it's a possible chat completion chunk and another function to fill in any missing fields.

Copy link
Member

@sharon-wang sharon-wang left a comment

Choose a reason for hiding this comment

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

I can log in with Snowflake now ❄️ ✅

I'm not sure if this is my local state, but I lost the Snowflake icon in the models popup:

Image

The icon is there in a release build of 2026.01.0+24, so not sure if it's my dev build state or possibly something has changed?

Comment on lines 131 to 134
// Fix empty role field
if (choice.delta.role === '') {
choice.delta.role = 'assistant';
}
Copy link
Member

Choose a reason for hiding this comment

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

Yeah this was originally added for Snowflake, but it's possible other providers may omit this as well

Comment on lines 58 to 68
// Transform tools to be compatible with OpenAI-compatible providers
// Some providers don't support the 'strict' field in tool function definitions
if (requestBody.tools && Array.isArray(requestBody.tools)) {
log.debug(`[${providerName}] Request contains ${requestBody.tools.length} tools: ${requestBody.tools.map((t: any) => t.function?.name || t.name).join(', ')}`);
for (const tool of requestBody.tools) {
if (tool.function && tool.function.strict !== undefined) {
delete tool.function.strict;
log.debug(`[${providerName}] Removed 'strict' field from tool: ${tool.function.name}`);
}
}
log.trace(`[${providerName}] Tools payload: ${JSON.stringify(requestBody.tools)}`);
Copy link
Member

Choose a reason for hiding this comment

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

Thoughts on consolidating the empty input schema handling that is currently in models.ts in AILanguageModel.provideLanguageModelChatResponse() with the handling here, so that all the tool modifications are in one place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe moving it to the utils.ts for generic handling? Although I removed it, there was some model specific handling. I'm good either way as long as models.ts doesn't grow too large as it is already a all-in-one kind of file.

I really hope that providers adhere to a standard API. This special handling feels like a side effect of providers not implementing the API.

@timtmok
Copy link
Contributor Author

timtmok commented Dec 10, 2025

I tried these latest changes with different models and providers and things feel good. The bonus is Snowflake works with GPT models calling the GetPlot tool. Its response is brief but it is able to understand the image.

image

The response is from GPT-5 but was also similar with GPT-4.1.

image

The Snowflake icon seems okay for me as well but these changes shouldn't have changed that.

@timtmok timtmok requested review from sharon-wang and wch December 10, 2025 18:40
* @param obj The object to check
* @returns True if the object appears to be a ChatCompletionChunk (possibly malformed)
*/
export function isPossiblyBrokenChatCompletionChunk(obj: unknown): obj is PossiblyBrokenChatCompletionChunk {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that this type guard isn't correct for the definition of PossiblyBrokenChatCompletionChunk above. With that interface definition, even an empty object, {}, could count as a PossiblyBrokenChatCompletionChunk.

But this function requires it to be an object and have:

  • id: string
  • choices: Array<unknown>
  • object: "chat.completion.chunk"

With this mismatch between the type and the type guard, the type guard could tell you something is not a PossiblyBrokenChatCompletionChunk when in fact it is, and that could be a source of bugs in the future.

The type and the type guard should match (or at least be much closer). I suggest updating the type to include essential things like the id, choices, and object -- whatever things we can reasonably assume that an OpenAI-compatible implementation would include.

}
// Check if it's a possibly broken chunk and fix it
// Otherwise, keep the original line
if (isPossiblyBrokenChatCompletionChunk(data)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a follow on to the note above about the type and type guard mismatch: if isPossiblyBrokenChatCompletionChunk() actually matched the PossiblyBrokenChatCompletionChunk type, then it would always enter this if block and never go into the else. (Assuming that data is a JS object, as opposed to something like a string or null.)

Copy link
Member

@sharon-wang sharon-wang left a comment

Choose a reason for hiding this comment

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

General functionality looking good! I'm seeing some issues with tool calling still:

  • we can address the getPlot issues separately since we've got other issues tracking that
  • the project tree tool and text search tool (and maybe any tools with required input params?) are showing some odd behaviour for Snowflake though. I wonder if it has to do with removing "strict"?

Comment on lines 201 to 211
// Transform tools to be compatible with OpenAI-compatible providers
// Some providers don't support the 'strict' field in tool function definitions
if (requestBody.tools && Array.isArray(requestBody.tools)) {
log.debug(`[${providerName}] Request contains ${requestBody.tools.length} tools: ${requestBody.tools.map((t: any) => t.function?.name || t.name).join(', ')}`);
for (const tool of requestBody.tools) {
if (tool.function && tool.function.strict !== undefined) {
delete tool.function.strict;
log.debug(`[${providerName}] Removed 'strict' field from tool: ${tool.function.name}`);
}
}
log.trace(`[${providerName}] Tools payload: ${JSON.stringify(requestBody.tools)}`);
Copy link
Member

Choose a reason for hiding this comment

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

I'm seeing some weird behaviour with the project tree and text search tools for Snowflake (seen with Claude Sonnet 4/4.5 and GPT 5), which I wonder if it has to do with removing "strict"? I think the issue might be occurring with tools that have required input parameters, that Snowflake may not be passing since "strict" is removed?

Image
chat log
{
  "requesterUsername": "",
  "responderUsername": "Positron Assistant",
  "responderAvatarIconUri": {
    "id": "positron-assistant"
  },
  "initialLocation": "panel",
  "requests": [
    {
      "requestId": "request_ae462a9e-a83d-4d03-a385-d0fa53ade8ff",
      "message": {
        "parts": [
          {
            "range": {
              "start": 0,
              "endExclusive": 98
            },
            "editorRange": {
              "startLineNumber": 1,
              "startColumn": 1,
              "endLineNumber": 1,
              "endColumn": 99
            },
            "text": "find the file with penguins. only look for code or qmd files and dont look for data files like csv",
            "kind": "text"
          }
        ],
        "text": "find the file with penguins. only look for code or qmd files and dont look for data files like csv"
      },
      "variableData": {
        "variables": [
          {
            "kind": "runtimeSession",
            "id": "positron.implicit.runtimeSession",
            "name": "R 4.5.1",
            "value": {
              "activeSession": {
                "identifier": "r-c009ad73",
                "language": "R",
                "languageId": "r",
                "version": "4.5.1",
                "mode": "console",
                "executions": []
              },
              "variables": [
                {
                  "access_key": "fig",
                  "display_name": "fig",
                  "display_type": "plotly",
                  "display_value": "[x = [visdat = [11f05b81270b = function () ], cur_data = \"11f05b81270b\", attrs = [11f05b81270b = [x = ~percollege, color = ~state, alpha_stroke = 1, sizes = 10 100, spans = 1 20, type = \"box\"]",
                  "has_children": true,
                  "has_viewer": false,
                  "is_truncated": true,
                  "kind": "map",
                  "length": 8,
                  "size": 127424,
                  "type_info": "plotly/htmlwidget",
                  "updated_time": 1765464518345
                },
                {
                  "access_key": "gg",
                  "display_name": "gg",
                  "display_type": "gg",
                  "display_value": "[data = [437 rows x 28 columns] <tbl_df>, layers = [{aes_params, computed_geom_params, computed_mapping, computed_stat_params, constructor, data, geom, geom_params [7 more]}",
                  "has_children": true,
                  "has_viewer": false,
                  "is_truncated": true,
                  "kind": "map",
                  "length": 11,
                  "size": 3387048,
                  "type_info": "gg/ggplot",
                  "updated_time": 1765464574522
                },
                {
                  "access_key": "midwest",
                  "display_name": "midwest",
                  "display_type": "tbl_df [437, 28]",
                  "display_value": "[437 rows x 28 columns] <tbl_df>",
                  "has_children": true,
                  "has_viewer": true,
                  "is_truncated": false,
                  "kind": "table",
                  "length": 28,
                  "size": 104888,
                  "type_info": "",
                  "updated_time": 1765464574525
                }
              ]
            }
          }
        ]
      },
      "response": [
        {
          "kind": "mcpServersStarting",
          "didStartServerIds": []
        },
        {
          "value": "I'll search for files containing \"penguins\" in code and qmd files.",
          "supportThemeIcons": false,
          "supportHtml": false
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Searching for text in project",
          "pastTenseMessage": "Searched for text in project",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "toolCallId": "b82615de-dc70-4c7c-bdd7-9599c949c4e4",
          "toolId": "positron_findTextInProject_internal"
        },
        {
          "value": "Let me try a different approach by first checking the project structure:",
          "supportThemeIcons": false,
          "supportHtml": false
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Constructing project tree",
          "pastTenseMessage": "Constructed project tree",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "toolCallId": "1b4d5948-8158-4881-a501-e9cab643babd",
          "toolId": "getProjectTree"
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Constructing project tree",
          "pastTenseMessage": "Constructed project tree",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "toolCallId": "828ebf4d-6157-4e10-8fec-cda2d9115087",
          "toolId": "getProjectTree"
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Constructing project tree",
          "pastTenseMessage": "Constructed project tree",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "toolCallId": "3fc94166-bd1c-46ef-956c-603b32578079",
          "toolId": "getProjectTree"
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Constructing project tree",
          "pastTenseMessage": "Constructed project tree",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "toolCallId": "cbc60ca8-f46a-40b7-b746-f5015b97a865",
          "toolId": "getProjectTree"
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Constructing project tree",
          "pastTenseMessage": "Constructed project tree",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "toolCallId": "b8ed5605-16ad-4c2f-a04e-ee28a9d74df9",
          "toolId": "getProjectTree"
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Constructing project tree",
          "pastTenseMessage": "Constructed project tree",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "toolCallId": "c9301b9a-44d5-4105-b9ef-b3d07fa529c5",
          "toolId": "getProjectTree"
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Constructing project tree",
          "pastTenseMessage": "Constructed project tree",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "toolCallId": "6910e95d-2d09-420e-9b70-db653cbffc40",
          "toolId": "getProjectTree"
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Constructing project tree",
          "pastTenseMessage": "Constructed project tree",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "toolCallId": "83e6644b-8a92-4eb0-a768-4e88f12e24f3",
          "toolId": "getProjectTree"
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Constructing project tree",
          "pastTenseMessage": "Constructed project tree",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "toolCallId": "f0ed30e6-18b0-4a81-a079-ac3755f70aa7",
          "toolId": "getProjectTree"
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Constructing project tree",
          "pastTenseMessage": "Constructed project tree",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "toolCallId": "49a86973-388b-4739-81a3-241408f0c0c1",
          "toolId": "getProjectTree"
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Constructing project tree",
          "pastTenseMessage": "Constructed project tree",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "toolCallId": "cc65d5e0-8ad8-401a-8033-9dd9f48e5d0a",
          "toolId": "getProjectTree"
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Constructing project tree",
          "pastTenseMessage": "Constructed project tree",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "toolCallId": "7b0d118a-7373-4f5d-8db9-a31f6803b469",
          "toolId": "getProjectTree"
        },
        {
          "value": "7.7 7.7 7.\n.77 7\n\n..\n\n9......\n\n.......\n\n9 9......9\n\n9......9\n\n9.9......9\n\n0100",
          "supportThemeIcons": false,
          "supportHtml": false
        }
      ],
      "responseId": "response_82c2f0db-6536-4f9d-bf58-25309722423f",
      "result": {
        "timings": {
          "firstProgress": 3291,
          "totalElapsed": 49577
        },
        "metadata": {
          "modelId": "claude-sonnet-4-5",
          "availableTools": [
            "inspectVariables",
            "getPlot",
            "getTableSummary",
            "getProjectTree",
            "getDirectoryStructure",
            "getChangedFiles",
            "listPackageHelpTopics",
            "listAvailableVignettes",
            "getPackageVignette",
            "getHelpPage",
            "publish-content_troubleshootDeploymentFailure",
            "publish-content_troubleshootConfigurationError",
            "positron_findTextInProject_internal",
            "positron_getFileContents_internal"
          ],
          "positronContext": {
            "prompts": [
              "<sessions>\nThe user has attached information about their interactive interpreter session below. This session is running alongside the conversation with you in the Positron IDE.\n\n<session>\n{\n  \"identifier\": \"r-c009ad73\",\n  \"language\": \"R\",\n  \"languageId\": \"r\",\n  \"version\": \"4.5.1\",\n  \"mode\": \"console\",\n  \"executions\": []\n}\n<variables description=\"Variables defined in the current session, in a pipe-delimited format, where each line is `name|kind|display_type|access_key`.\">\nfig|map|plotly|fig\ngg|map|gg|gg\nmidwest|table|tbl_df [437, 28]|midwest\n</variables>\n</session>\n</sessions>",
              "<context>\n<plots>\nA plot is visible.\n</plots>\n\n<version>\nPositron version: 2026.01.0-0\n</version>\n\n<date>\nToday's date is: Thursday, December 11, 2025 at 10:13:19 AM EST\n</date>\n</context>"
            ],
            "attachedDataTypes": []
          },
          "systemPrompt": "You are Positron Assistant, a coding assistant designed to help with data science tasks created by Posit, PBC.\n\nYou are an expert data scientist and software developer, with expertise in R and Python programming. Your job is to assist a USER by answering questions and helping them with their coding and data science tasks.\n\n<communication>\nYou are terse in your replies, but friendly and helpful.\n\nYou respond to the USER’s question or requirements carefully. You politely ask the USER to rephrase the question if you are not able to understand the question or requirements.\n\nYou use the information given to you, including additional context and conversation history when it is provided, to create your responses.\n\nYou generally don’t try to do too much at once, breaking up the conversation into smaller chunks and checking in with the USER frequently. You provide suggestions where appropriate.\n\nYou avoid sycophancy and never start your responses by saying a question or idea or observation is great, interesting, profound or any other positive adjective. Skip flattery and respond directly to the USER’s question or request.\n\nGenerally, the USER appreciates concise responses. Eliminate emojis, filler, soft asks, conversational transition and call-to-action appendixes.\n\nYou always assume the USER is competent, even if their questions show reduced linguistic expression.\n\nWhen explaining and giving examples to the USER you prefer to use markdown codeblocks, rather than using tools to edit the environment or workspace.\n\nWhen responding with code, you first think step-by-step. You explain the code briefly before including it in your response as a single code block.\n</communication>\n\n<style>\nYou output code that is correct, of high quality, and with a consistent style.\n\nYou follow the coding style and use the packages and frameworks used by the USER in example code and context that they have given you as part of their request.\n\nFor code that generates statistical information, ensure the final line returns a useful object rather than printing/displaying it.\n\nFor Python, specifically avoid these output functions in code unless explicitly requested by the USER:\n- `print()`\n- `display()`\n- `pprint()`\n- `pp.pprint()`\n\nFor R, specifically avoid these output functions in code unless explicitly requested by the USER:\n- `print()`\n- `cat()`\n- `message()`\n- `summary()` as a standalone statement\n</style>\n\n<context>\nYou are running inside Positron, the data science IDE created by Posit, PBC. Positron is a fork of VS Code. Positron is designed to be a great development environment for data scientists.\n\nPositron provides a console where the USER can interact directly with R or Python runtimes. The USER can also edit their code, debug their application, run unit tests, and view any plotting output using Positron.\n\nWe will automatically attach context about the running Positron session to the USER’s query using `<context>` tags.\n\nDepending on the user's question, this context might not be useful. You ignore the extra context if it is not useful.\nYou do not mention the context in your response if it is irrelevant, but do keep it in mind as it might become relevant in a future response.\n\nIf the USER asks you about features or abilities of the Positron editor that you do not recognize in the automatically provided context, direct the USER to the user guides provided online at <https://positron.posit.co/>.\n</context>\n\n<tools>\nWe will provide you with a collection of tools to interact with the current Positron session.\n\nThe USER can see when you invoke a tool, so you do not need to tell the user or mention the name of tools when you use them.\n\nYou much prefer to respond to the USER with code to perform a data analysis, rather than directly trying to calculate summaries or statistics for your response.\n</tools>\n\n<chat-participants>\nWhen the USER asks a question about Shiny, you attempt to respond as normal in the first instance.\n\nIf you find you cannot complete the USER’s Shiny request or don’t know the answer to their Shiny question, suggest that they use the `@shiny` command in the chat panel to provide additional support using Shiny Assistant.\n\nIf the USER asks you to run or start a Shiny app, you direct them to use the Shiny Assistant, which is able to launch a Shiny app correctly.\n</chat-participants>\n\n<quarto>\nWhen the USER asks a question about Quarto, you attempt to respond as normal in the first instance.\n\nWhen you respond with Quarto examples, you use at least four tildes (`~~~~quarto`) to create the surrounding codeblock.\n\nIf you find you cannot complete the USER’s Quarto request, or don’t know the answer to their Quarto question, direct the USER to the user guides provided online at <https://quarto.org/docs/guide/>.\n</quarto>\n\n<communication>\nYou are running in \"Ask\" mode.\n\nYou may include code blocks in your response, but in this mode you are UNABLE to run the code or see the results.\nThe code blocks are shown to the user, but the code is not executed unless the USER requests it.\n\nIf your response requires the results of executing a code block, STOP. Explain to the user what the code will do and end your response.\nDo not offer to run the code for the user.\n\nYou NEVER try to summarise, present statistics or insights, or comment on the result of executing code blocks.\n</communication>\n\n\nWhen responding with code or instructions that are destructive, dangerous, or difficult to reverse, you follow these guidelines:\n\n\n- **Always include warnings** for these specific operations:\n  - Deleting files or directories (`rm`, `os.remove()`, `unlink()`, `fs.unlink()`, etc.)\n  - Modifying system files or directories\n- Enclose the warning text in `<warning>` tags. For example: `<warning>**Warning: This code will permanently delete the current directory and all its contents. Use with caution!**</warning>`\n- The warning text should clearly describe the destructive or dangerous nature of the suggested action or code\n\n- Start with a clear warning at the beginning of the response\n- Include additional warnings alongside the code or instructions where appropriate\n\n\n<example>\n\n<user>delete a directory using Python</user>\n<response>\n\n````md\n<warning>\n**Warning: This code will permanently delete the directory and all its contents. Use with caution!**\n</warning>\n\n```python\nimport shutil\n\nshutil.rmtree('/path/to/directory')\n```\n\n- `shutil.rmtree()`: Recursively deletes a directory and all its contents\n````\n\n</response>\n\n</example>\n\n\n\n\n\n\n<style-r>\nWhen writing R code you generally follow tidyverse coding style and principles.\n\nYou use the modern `|>` pipe.\nYou use the testthat framework for unit testing.\nYou suggest and use the usethis package to perform common workflow tasks.\n\nIf the USER asks you to use base R, data.table, or any other coding style or framework, you switch to using these frameworks without mentioning anything else about it. You remember for the entire conversation to use the requested alternate setup.\n</style-r>\n\nThe following examples provide some rules-of-thumb on analyzing data with R.\n\n<examples-r>\nWhen visualizing data with ggplot2, keep your initial plotting code minimal--just a dataset, aesthetic, geometry, and possibly labels.\n\n```r\nggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g, color = species)) +\n  geom_point() +\n  labs(\n    title = \"Penguin Flipper Length vs. Body Mass\",\n    x = \"Flipper Length (mm)\",\n    y = \"Body Mass (g)\",\n    color = \"Species\"\n  )\n```\n\n* Refrain from applying `geom_smooth()` unless the user requests it.\n\nIn dplyr, iterate across columns using `across()`:\n\n```r\ndiamonds |>\n  summarize(\n    across(where(is.numeric), list(mean = mean, sd = sd), na.rm = TRUE),\n    .groups = \"drop\"\n  )\n```\n\n* Prefer the new `.by` syntax over `group_by()` when applying operations by group.\n* Use dplyr's `join_by(col_name)` helper when joining, as in `flights |> left_join(airlines, join_by(carrier))`.\n</examples-r>\n"
        }
      },
      "followups": [],
      "isCanceled": false,
      "agent": {
        "extensionId": {
          "value": "positron.positron-assistant",
          "_lower": "positron.positron-assistant"
        },
        "extensionVersion": "0.0.1",
        "publisherDisplayName": "positron",
        "extensionPublisherId": "positron",
        "extensionDisplayName": "Positron Assistant",
        "id": "positron.assistant.chat",
        "description": "Ask Assistant",
        "metadata": {
          "isSticky": false,
          "themeIcon": {
            "id": "positron-assistant"
          },
          "hasFollowups": false
        },
        "name": "assistant",
        "fullName": "Positron Assistant",
        "isDefault": true,
        "locations": [
          "panel"
        ],
        "modes": [
          "ask"
        ],
        "slashCommands": [
          {
            "name": "exportQuarto",
            "description": "Export the conversation so far into a new Quarto document."
          },
          {
            "name": "fix",
            "description": "Fix issues in the active editor or Console"
          },
          {
            "name": "explain",
            "description": "Explain the code in the active editor"
          }
        ],
        "disambiguation": []
      },
      "contentReferences": [],
      "codeCitations": [],
      "timestamp": 1765465996552,
      "modelId": "snowflake-cortex/claude-sonnet-4-5"
    }
  ]
}

Here's OpenAI for comparison, which doesn't seem to loop on tool calling:

Image

And Anthropic for comparison as well:

Image
chat log
{
  "requesterUsername": "",
  "responderUsername": "Positron Assistant",
  "responderAvatarIconUri": {
    "id": "positron-assistant"
  },
  "initialLocation": "panel",
  "requests": [
    {
      "requestId": "request_71f0d97b-aa4b-46ee-a7bf-d596a9eb8cd0",
      "message": {
        "parts": [
          {
            "range": {
              "start": 0,
              "endExclusive": 98
            },
            "editorRange": {
              "startLineNumber": 1,
              "startColumn": 1,
              "endLineNumber": 1,
              "endColumn": 99
            },
            "text": "find the file with penguins. only look for code or qmd files and dont look for data files like csv",
            "kind": "text"
          }
        ],
        "text": "find the file with penguins. only look for code or qmd files and dont look for data files like csv"
      },
      "variableData": {
        "variables": [
          {
            "kind": "runtimeSession",
            "id": "positron.implicit.runtimeSession",
            "name": "R 4.5.1",
            "value": {
              "activeSession": {
                "identifier": "r-c009ad73",
                "language": "R",
                "languageId": "r",
                "version": "4.5.1",
                "mode": "console",
                "executions": []
              },
              "variables": [
                {
                  "access_key": "fig",
                  "display_name": "fig",
                  "display_type": "plotly",
                  "display_value": "[x = [visdat = [11f05b81270b = function () ], cur_data = \"11f05b81270b\", attrs = [11f05b81270b = [x = ~percollege, color = ~state, alpha_stroke = 1, sizes = 10 100, spans = 1 20, type = \"box\"]",
                  "has_children": true,
                  "has_viewer": false,
                  "is_truncated": true,
                  "kind": "map",
                  "length": 8,
                  "size": 127424,
                  "type_info": "plotly/htmlwidget",
                  "updated_time": 1765464518345
                },
                {
                  "access_key": "gg",
                  "display_name": "gg",
                  "display_type": "gg",
                  "display_value": "[data = [437 rows x 28 columns] <tbl_df>, layers = [{aes_params, computed_geom_params, computed_mapping, computed_stat_params, constructor, data, geom, geom_params [7 more]}",
                  "has_children": true,
                  "has_viewer": false,
                  "is_truncated": true,
                  "kind": "map",
                  "length": 11,
                  "size": 3387048,
                  "type_info": "gg/ggplot",
                  "updated_time": 1765464574522
                },
                {
                  "access_key": "midwest",
                  "display_name": "midwest",
                  "display_type": "tbl_df [437, 28]",
                  "display_value": "[437 rows x 28 columns] <tbl_df>",
                  "has_children": true,
                  "has_viewer": true,
                  "is_truncated": false,
                  "kind": "table",
                  "length": 28,
                  "size": 104888,
                  "type_info": "",
                  "updated_time": 1765464574525
                }
              ]
            }
          }
        ]
      },
      "response": [
        {
          "kind": "mcpServersStarting",
          "didStartServerIds": []
        },
        {
          "value": "I'll search for files containing \"penguins\" in code and Quarto markdown files.",
          "supportThemeIcons": false,
          "supportHtml": false
        },
        {
          "kind": "toolInvocationSerialized",
          "invocationMessage": "Searching for text in project",
          "pastTenseMessage": "Searched for text in project",
          "isConfirmed": {
            "type": 1
          },
          "isComplete": true,
          "source": {
            "type": "internal",
            "label": "Built-In"
          },
          "resultDetails": {
            "input": "{\n  \"pattern\": \"penguins\",\n  \"maxResults\": 30\n}",
            "output": [
              {
                "type": "embed",
                "isText": true,
                "value": "positron-chat-export-2025-12-10T14-59-39-700Z.json:178: [...] labels.\\n\\n```r\\nggplot(penguins, aes(x = flipper_length_mm, [...]\nDESCRIPTION:23: palmerpenguins,\ndata-files/spotify_data/data.csv:14860: 0.466,1996,0.865,['The Penguins'],0.524,179427,0.177,0,11XR0tRT4g5ov4u8M92wbF,0.0,8,0.21,-12.306,1,Earth [...]\ndata-files/spotify_data/data.csv:47463: [...] Hurley'],0.633,207333,0.016,0,6bGMai7fcmgBAS9AASU15T,0.914,5,0.103,-18.242,1,Penguins,39,1971-09-01,0.0596,70.995\ndata-files/spotify_data/data.csv:104784: 0.5479999999999999,2000,0.0039,['Millencolin'],0.368,173360,0.977,0,3jRsoe4Vkxa4BMYqGHX8L0,0.0,11,0.35,-2.757,0,Penguins & Polarbears,52,2000-02-22,0.127,165.889\ndata-files/spotify_data/data.csv:137905: 0.197,2005,0.00136,['Chiodos'],0.424,273507,0.917,0,7ggui6l9tJaUixcqr7pQIK,0.0,7,0.0501,-5.593,1,There's No Penguins In Alaska,41,2005,0.109,101.012\ndata-files/spotify_data/data.csv:149716: 0.5579999999999999,1990,0.493,['Camping with Penguins'],0.6509999999999999,167893,0.17800000000000002,0,0Jv27yLnuuLxwVfIiTvXmn,1.54e-06,8,0.147,-17.816,0,Spider,29,1990,0.0324,120.052\ndata-files/spotify_data/data.csv:153992: [...] Freegrant'],0.6759999999999999,397500,0.897,0,1XXFYfsPAPYMw3Ukr5KPQN,0.804,7,0.149,-8.078,1,Penguins Dancing Too [...]\ndata-files/spotify_data/data.csv:165889: 0.501,1996,0.8690000000000001,['The Penguins'],0.444,182333,0.278,0,4MRt9ZxfcWnqL1clGJe6WN,0.0,7,0.119,-10.465,1,Devil [...]\nworkspaces/quarto_interactive/quarto_interactive.qmd:2: title: \"Palmer Penguins\"\nworkspaces/quarto_interactive/quarto_interactive.qmd:10: [...] FileAttachment(\"penguins.csv\")\nworkspaces/quarto_interactive/quarto_interactive.qmd:21: ![](images/penguins.png){width=\"80%\"}\npositron-chat-export-2025-12-11T15-16-06-469Z.json:24: [...] penguins. only look for code or [...]\npositron-chat-export-2025-12-11T15-16-06-469Z.json:28: [...] with penguins. only look for code [...]\npositron-chat-export-2025-12-11T15-16-06-469Z.json:99: [...] \\\"penguins\\\" in code and qmd files.\",\npositron-chat-export-2025-12-11T15-16-06-469Z.json:340: [...] labels.\\n\\n```r\\nggplot(penguins, aes(x = flipper_length_mm, [...]"
              }
            ]
          },
          "toolCallId": "c2435b6b-3bb5-463e-bc47-511d279ff612",
          "toolId": "positron_findTextInProject_internal"
        },
        {
          "value": "Based on the search results, I found the main file with penguin-related code:\n\n**workspaces/quarto_interactive/quarto_interactive.qmd**\n\nThis Quarto document appears to contain:\n- A title \"Palmer Penguins\" \n- A reference to \"penguins.csv\" data file\n- An image reference to \"penguins.png\"\n\nThis looks like the main code/document file you're looking for that works with penguin data. The other mentions are in data files (CSV), chat exports, or the DESCRIPTION file listing palmerpenguins as a dependency.",
          "supportThemeIcons": false,
          "supportHtml": false
        }
      ],
      "responseId": "response_4012a779-2ff7-4311-8c15-7ec7ed65e08b",
      "result": {
        "timings": {
          "firstProgress": 2379,
          "totalElapsed": 9077
        },
        "metadata": {
          "modelId": "claude-sonnet-4-20250514",
          "tokenUsage": {
            "provider": "anthropic-api",
            "tokens": {
              "inputTokens": 1594,
              "outputTokens": 138,
              "cachedTokens": 4953,
              "providerMetadata": {
                "anthropic": {
                  "input_tokens": 1594,
                  "cache_creation_input_tokens": 0,
                  "cache_read_input_tokens": 4953,
                  "cache_creation": {
                    "ephemeral_5m_input_tokens": 0,
                    "ephemeral_1h_input_tokens": 0
                  },
                  "output_tokens": 138,
                  "service_tier": "standard"
                }
              }
            }
          },
          "availableTools": [
            "inspectVariables",
            "getPlot",
            "getTableSummary",
            "getProjectTree",
            "getDirectoryStructure",
            "getChangedFiles",
            "listPackageHelpTopics",
            "listAvailableVignettes",
            "getPackageVignette",
            "getHelpPage",
            "publish-content_troubleshootDeploymentFailure",
            "publish-content_troubleshootConfigurationError",
            "positron_findTextInProject_internal",
            "positron_getFileContents_internal"
          ],
          "positronContext": {
            "prompts": [
              "<sessions>\nThe user has attached information about their interactive interpreter session below. This session is running alongside the conversation with you in the Positron IDE.\n\n<session>\n{\n  \"identifier\": \"r-c009ad73\",\n  \"language\": \"R\",\n  \"languageId\": \"r\",\n  \"version\": \"4.5.1\",\n  \"mode\": \"console\",\n  \"executions\": []\n}\n<variables description=\"Variables defined in the current session, in a pipe-delimited format, where each line is `name|kind|display_type|access_key`.\">\nfig|map|plotly|fig\ngg|map|gg|gg\nmidwest|table|tbl_df [437, 28]|midwest\n</variables>\n</session>\n</sessions>",
              "<context>\n<plots>\nA plot is visible.\n</plots>\n\n<version>\nPositron version: 2026.01.0-0\n</version>\n\n<date>\nToday's date is: Thursday, December 11, 2025 at 10:18:21 AM EST\n</date>\n</context>"
            ],
            "attachedDataTypes": []
          },
          "systemPrompt": "You are Positron Assistant, a coding assistant designed to help with data science tasks created by Posit, PBC.\n\nYou are an expert data scientist and software developer, with expertise in R and Python programming. Your job is to assist a USER by answering questions and helping them with their coding and data science tasks.\n\n<communication>\nYou are terse in your replies, but friendly and helpful.\n\nYou respond to the USER’s question or requirements carefully. You politely ask the USER to rephrase the question if you are not able to understand the question or requirements.\n\nYou use the information given to you, including additional context and conversation history when it is provided, to create your responses.\n\nYou generally don’t try to do too much at once, breaking up the conversation into smaller chunks and checking in with the USER frequently. You provide suggestions where appropriate.\n\nYou avoid sycophancy and never start your responses by saying a question or idea or observation is great, interesting, profound or any other positive adjective. Skip flattery and respond directly to the USER’s question or request.\n\nGenerally, the USER appreciates concise responses. Eliminate emojis, filler, soft asks, conversational transition and call-to-action appendixes.\n\nYou always assume the USER is competent, even if their questions show reduced linguistic expression.\n\nWhen explaining and giving examples to the USER you prefer to use markdown codeblocks, rather than using tools to edit the environment or workspace.\n\nWhen responding with code, you first think step-by-step. You explain the code briefly before including it in your response as a single code block.\n</communication>\n\n<style>\nYou output code that is correct, of high quality, and with a consistent style.\n\nYou follow the coding style and use the packages and frameworks used by the USER in example code and context that they have given you as part of their request.\n\nFor code that generates statistical information, ensure the final line returns a useful object rather than printing/displaying it.\n\nFor Python, specifically avoid these output functions in code unless explicitly requested by the USER:\n- `print()`\n- `display()`\n- `pprint()`\n- `pp.pprint()`\n\nFor R, specifically avoid these output functions in code unless explicitly requested by the USER:\n- `print()`\n- `cat()`\n- `message()`\n- `summary()` as a standalone statement\n</style>\n\n<context>\nYou are running inside Positron, the data science IDE created by Posit, PBC. Positron is a fork of VS Code. Positron is designed to be a great development environment for data scientists.\n\nPositron provides a console where the USER can interact directly with R or Python runtimes. The USER can also edit their code, debug their application, run unit tests, and view any plotting output using Positron.\n\nWe will automatically attach context about the running Positron session to the USER’s query using `<context>` tags.\n\nDepending on the user's question, this context might not be useful. You ignore the extra context if it is not useful.\nYou do not mention the context in your response if it is irrelevant, but do keep it in mind as it might become relevant in a future response.\n\nIf the USER asks you about features or abilities of the Positron editor that you do not recognize in the automatically provided context, direct the USER to the user guides provided online at <https://positron.posit.co/>.\n</context>\n\n<tools>\nWe will provide you with a collection of tools to interact with the current Positron session.\n\nThe USER can see when you invoke a tool, so you do not need to tell the user or mention the name of tools when you use them.\n\nYou much prefer to respond to the USER with code to perform a data analysis, rather than directly trying to calculate summaries or statistics for your response.\n</tools>\n\n<chat-participants>\nWhen the USER asks a question about Shiny, you attempt to respond as normal in the first instance.\n\nIf you find you cannot complete the USER’s Shiny request or don’t know the answer to their Shiny question, suggest that they use the `@shiny` command in the chat panel to provide additional support using Shiny Assistant.\n\nIf the USER asks you to run or start a Shiny app, you direct them to use the Shiny Assistant, which is able to launch a Shiny app correctly.\n</chat-participants>\n\n<quarto>\nWhen the USER asks a question about Quarto, you attempt to respond as normal in the first instance.\n\nWhen you respond with Quarto examples, you use at least four tildes (`~~~~quarto`) to create the surrounding codeblock.\n\nIf you find you cannot complete the USER’s Quarto request, or don’t know the answer to their Quarto question, direct the USER to the user guides provided online at <https://quarto.org/docs/guide/>.\n</quarto>\n\n<communication>\nYou are running in \"Ask\" mode.\n\nYou may include code blocks in your response, but in this mode you are UNABLE to run the code or see the results.\nThe code blocks are shown to the user, but the code is not executed unless the USER requests it.\n\nIf your response requires the results of executing a code block, STOP. Explain to the user what the code will do and end your response.\nDo not offer to run the code for the user.\n\nYou NEVER try to summarise, present statistics or insights, or comment on the result of executing code blocks.\n</communication>\n\n\nWhen responding with code or instructions that are destructive, dangerous, or difficult to reverse, you follow these guidelines:\n\n\n- **Always include warnings** for these specific operations:\n  - Deleting files or directories (`rm`, `os.remove()`, `unlink()`, `fs.unlink()`, etc.)\n  - Modifying system files or directories\n- Enclose the warning text in `<warning>` tags. For example: `<warning>**Warning: This code will permanently delete the current directory and all its contents. Use with caution!**</warning>`\n- The warning text should clearly describe the destructive or dangerous nature of the suggested action or code\n\n- Start with a clear warning at the beginning of the response\n- Include additional warnings alongside the code or instructions where appropriate\n\n\n<example>\n\n<user>delete a directory using Python</user>\n<response>\n\n````md\n<warning>\n**Warning: This code will permanently delete the directory and all its contents. Use with caution!**\n</warning>\n\n```python\nimport shutil\n\nshutil.rmtree('/path/to/directory')\n```\n\n- `shutil.rmtree()`: Recursively deletes a directory and all its contents\n````\n\n</response>\n\n</example>\n\n\n\n\n\n\n<style-r>\nWhen writing R code you generally follow tidyverse coding style and principles.\n\nYou use the modern `|>` pipe.\nYou use the testthat framework for unit testing.\nYou suggest and use the usethis package to perform common workflow tasks.\n\nIf the USER asks you to use base R, data.table, or any other coding style or framework, you switch to using these frameworks without mentioning anything else about it. You remember for the entire conversation to use the requested alternate setup.\n</style-r>\n\nThe following examples provide some rules-of-thumb on analyzing data with R.\n\n<examples-r>\nWhen visualizing data with ggplot2, keep your initial plotting code minimal--just a dataset, aesthetic, geometry, and possibly labels.\n\n```r\nggplot(penguins, aes(x = flipper_length_mm, y = body_mass_g, color = species)) +\n  geom_point() +\n  labs(\n    title = \"Penguin Flipper Length vs. Body Mass\",\n    x = \"Flipper Length (mm)\",\n    y = \"Body Mass (g)\",\n    color = \"Species\"\n  )\n```\n\n* Refrain from applying `geom_smooth()` unless the user requests it.\n\nIn dplyr, iterate across columns using `across()`:\n\n```r\ndiamonds |>\n  summarize(\n    across(where(is.numeric), list(mean = mean, sd = sd), na.rm = TRUE),\n    .groups = \"drop\"\n  )\n```\n\n* Prefer the new `.by` syntax over `group_by()` when applying operations by group.\n* Use dplyr's `join_by(col_name)` helper when joining, as in `flights |> left_join(airlines, join_by(carrier))`.\n</examples-r>\n"
        }
      },
      "responseMarkdownInfo": [],
      "followups": [],
      "isCanceled": false,
      "agent": {
        "extensionId": {
          "value": "positron.positron-assistant",
          "_lower": "positron.positron-assistant"
        },
        "extensionVersion": "0.0.1",
        "publisherDisplayName": "positron",
        "extensionPublisherId": "positron",
        "extensionDisplayName": "Positron Assistant",
        "id": "positron.assistant.chat",
        "description": "Ask Assistant",
        "metadata": {
          "isSticky": false,
          "themeIcon": {
            "id": "positron-assistant"
          },
          "hasFollowups": false
        },
        "name": "assistant",
        "fullName": "Positron Assistant",
        "isDefault": true,
        "locations": [
          "panel"
        ],
        "modes": [
          "ask"
        ],
        "slashCommands": [
          {
            "name": "exportQuarto",
            "description": "Export the conversation so far into a new Quarto document."
          },
          {
            "name": "fix",
            "description": "Fix issues in the active editor or Console"
          },
          {
            "name": "explain",
            "description": "Explain the code in the active editor"
          }
        ],
        "disambiguation": []
      },
      "contentReferences": [
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "fsPath": "/Users/sashimi/qa-example-content/positron-chat-export-2025-12-10T14-59-39-700Z.json",
              "external": "file:///Users/sashimi/qa-example-content/positron-chat-export-2025-12-10T14-59-39-700Z.json",
              "path": "/Users/sashimi/qa-example-content/positron-chat-export-2025-12-10T14-59-39-700Z.json",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 178,
              "startColumn": 7429,
              "endLineNumber": 178,
              "endColumn": 7437
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "fsPath": "/Users/sashimi/qa-example-content/DESCRIPTION",
              "external": "file:///Users/sashimi/qa-example-content/DESCRIPTION",
              "path": "/Users/sashimi/qa-example-content/DESCRIPTION",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 23,
              "startColumn": 11,
              "endLineNumber": 23,
              "endColumn": 19
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "fsPath": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "external": "file:///Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "path": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 14860,
              "startColumn": 24,
              "endLineNumber": 14860,
              "endColumn": 32
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "fsPath": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "external": "file:///Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "path": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 47463,
              "startColumn": 105,
              "endLineNumber": 47463,
              "endColumn": 113
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "fsPath": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "external": "file:///Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "path": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 104784,
              "startColumn": 113,
              "endLineNumber": 104784,
              "endColumn": 121
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "fsPath": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "external": "file:///Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "path": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 137905,
              "startColumn": 109,
              "endLineNumber": 137905,
              "endColumn": 117
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "fsPath": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "external": "file:///Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "path": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 149716,
              "startColumn": 46,
              "endLineNumber": 149716,
              "endColumn": 54
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "fsPath": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "external": "file:///Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "path": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 153992,
              "startColumn": 134,
              "endLineNumber": 153992,
              "endColumn": 142
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "fsPath": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "external": "file:///Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "path": "/Users/sashimi/qa-example-content/data-files/spotify_data/data.csv",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 165889,
              "startColumn": 37,
              "endLineNumber": 165889,
              "endColumn": 45
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "external": "file:///Users/sashimi/qa-example-content/workspaces/quarto_interactive/quarto_interactive.qmd",
              "path": "/Users/sashimi/qa-example-content/workspaces/quarto_interactive/quarto_interactive.qmd",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 2,
              "startColumn": 16,
              "endLineNumber": 2,
              "endColumn": 24
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "external": "file:///Users/sashimi/qa-example-content/workspaces/quarto_interactive/quarto_interactive.qmd",
              "path": "/Users/sashimi/qa-example-content/workspaces/quarto_interactive/quarto_interactive.qmd",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 10,
              "startColumn": 24,
              "endLineNumber": 10,
              "endColumn": 32
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "external": "file:///Users/sashimi/qa-example-content/workspaces/quarto_interactive/quarto_interactive.qmd",
              "path": "/Users/sashimi/qa-example-content/workspaces/quarto_interactive/quarto_interactive.qmd",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 21,
              "startColumn": 12,
              "endLineNumber": 21,
              "endColumn": 20
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "external": "file:///Users/sashimi/qa-example-content/positron-chat-export-2025-12-11T15-16-06-469Z.json",
              "path": "/Users/sashimi/qa-example-content/positron-chat-export-2025-12-11T15-16-06-469Z.json",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 24,
              "startColumn": 41,
              "endLineNumber": 24,
              "endColumn": 49
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "external": "file:///Users/sashimi/qa-example-content/positron-chat-export-2025-12-11T15-16-06-469Z.json",
              "path": "/Users/sashimi/qa-example-content/positron-chat-export-2025-12-11T15-16-06-469Z.json",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 28,
              "startColumn": 37,
              "endLineNumber": 28,
              "endColumn": 45
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "external": "file:///Users/sashimi/qa-example-content/positron-chat-export-2025-12-11T15-16-06-469Z.json",
              "path": "/Users/sashimi/qa-example-content/positron-chat-export-2025-12-11T15-16-06-469Z.json",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 99,
              "startColumn": 56,
              "endLineNumber": 99,
              "endColumn": 64
            }
          }
        },
        {
          "kind": "reference",
          "reference": {
            "uri": {
              "$mid": 1,
              "external": "file:///Users/sashimi/qa-example-content/positron-chat-export-2025-12-11T15-16-06-469Z.json",
              "path": "/Users/sashimi/qa-example-content/positron-chat-export-2025-12-11T15-16-06-469Z.json",
              "scheme": "file"
            },
            "range": {
              "startLineNumber": 340,
              "startColumn": 7429,
              "endLineNumber": 340,
              "endColumn": 7437
            }
          }
        }
      ],
      "codeCitations": [],
      "timestamp": 1765466297971,
      "modelId": "anthropic-api/claude-sonnet-4-20250514",
      "tokenUsage": {
        "provider": "anthropic-api",
        "tokens": {
          "inputTokens": 1594,
          "outputTokens": 138,
          "cachedTokens": 4953,
          "providerMetadata": {
            "anthropic": {
              "input_tokens": 1594,
              "cache_creation_input_tokens": 0,
              "cache_read_input_tokens": 4953,
              "cache_creation": {
                "ephemeral_5m_input_tokens": 0,
                "ephemeral_1h_input_tokens": 0
              },
              "output_tokens": 138,
              "service_tier": "standard"
            }
          }
        }
      }
    }
  ]
}

Comment on lines 224 to 229
} else if (content instanceof vscode.LanguageModelDataPart && isChatImagePart(content)) {
return {
type: 'image',
type: 'media',
data: Buffer.from(content.data).toString('base64'),
mimeType: content.mimeType,
mediaType: content.mimeType,
};
Copy link
Member

Choose a reason for hiding this comment

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

Noting what I'm seeing with the plot tool -- I think it's still not quite working via the Vercel SDK, compared to the Anthropic SDK. We can address this separately though, since we've got a few issues open re: getPlot.

Snowflake + Claude Sonnet 4

Plot tool called and seemingly made up what it "saw" in the plot. Seems like it couldn't actually see the plot and made something up instead of saying it couldn't see the plot.

Image

Snowflake + GPT 5

Plot tool called, but cannot seem to see the plot.

Image

Anthropic + Claude Sonnet 4

Plot tool called and response seems accurate ✅

Image

@timtmok
Copy link
Contributor Author

timtmok commented Dec 11, 2025

The tool call error was caused from trying to fix the empty argument by replacing empty strings with {}. There are still problems with Snowflake. Although, it's now Anthropic models that can't use the getPlot tool. GPT works but only when not using Ask mode. Copilot will actually warn that it cannot use the tool in Ask mode so this may be expected.

I'll continue with the other feedback for now.

Tools with no required args need a fix so that the tool will be called properly
@timtmok
Copy link
Contributor Author

timtmok commented Dec 16, 2025

I found the problem with the tool calling. Tools that don't have any required arguments need {} set for the arguments. This can be seen with getPlot and how it is defined with the type generic on the implementation.

I'm not exactly sure why tools that have required arguments are fine with '' but I suspect it's the way the tool calling is done in deltas so all the arguments are formed when all of them have been processed.

I've tested the getProjectTree and getPlot tools with OpenRouter and Snowflake. OpenRouter needed the revert to using the chat API endpoint or else the tool-call part never got sent. GPT models also work now with the getPlot tool.

@timtmok
Copy link
Contributor Author

timtmok commented Dec 16, 2025

I ended up going with a simpler check to see if the data is a chat completion chunk. It was simpler to see if it says it's a chunk then go into all the fields to see if they need any fixing. It got to complicated checking if any fields were broken and that code became just as long as fixing it.

@timtmok
Copy link
Contributor Author

timtmok commented Dec 16, 2025

I also changed the connection test to check if any models are "free". This will make it easier to test OpenRouter when low on credits.

@timtmok timtmok requested review from sharon-wang and wch December 16, 2025 21:58
Copy link
Member

@sharon-wang sharon-wang left a comment

Choose a reason for hiding this comment

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

Looking good and working well for me!

get plot related issues (for Snowflake and OpenRouter + Custom Provider) will be addressed separately in #10759

Looks like the Snowflake test might need some updates:

  1) Snowflake Provider
       OpenAI Compatible Fetch - Snowflake Streaming Fix
         transforms empty role fields in streaming response:

      AssertionError [ERR_ASSERTION]: Empty role should be transformed to "assistant"
      + expected - actual

      -false
      +true

"mocha-multi": "^1.1.7",
"mocha-multi-reporters": "^1.5.1",
"npm-run-all": "^4.1.5",
"openai": "^6.10.0",
Copy link
Member

Choose a reason for hiding this comment

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

let's move this to the assistant extension package.json instead!


/**
* Creates a wrapped OpenAI provider that uses the Chat Completions API
* instead of the Responses API. Snowflake Cortex only supports v1/chat/completions.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* instead of the Responses API. Snowflake Cortex only supports v1/chat/completions.
* instead of the Responses API.
* At this time, providers like Snowflake and OpenRouter need
* to use v1/chat/completions, and we are making the assumption
* that other custom OpenAI-compatible providers also use the
* old endpoint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants