ai icon indicating copy to clipboard operation
ai copied to clipboard

Bug in type definition for `TextStreamPart`

Open nvie opened this issue 8 months ago • 5 comments

Description

The TextStreamPart is a union type of many possible things, including "tool-result". However, when asking TypeScript for the list of possible types of the type field, the "tool-result" does not seem to be included if you do not provide a specific/custom ToolSet type param:

Image

The reason for this is this part of the definition:

export type TextStreamPart<TOOLS extends ToolSet> =
  | ...
  | ({
      type: 'tool-result';
    } & ToolResultUnion<TOOLS>)
  | ...

This expression: ToolResultUnion<ToolSet> (i.e. when not not providing a specific/typed tool set), evaluates to never, and therefore the intersection with { type: "tool-result" } will also be never.

Possible fix?

A possible fix might be to catch this never case, and return a "generic" shape that is slightly wider (less useful), but at the very least still informative about what fields are available on every tool-result part, i.e.

// 1️⃣ Renamed ToolResultUnion → ToolResultUnionOrNever
type ToolResultUnionOrNever<TOOLS extends ToolSet> = ToToolResultObject<ToToolsWithDefinedExecute<ToToolsWithExecute<TOOLS>>>;

// 2️⃣ Redefine ToolResultUnion to check if ToolResultUnionOrNever is
//   never (ie if a specific ToolSet param was not provided), and if so,
//   still return a wider type that's still accurate
type ToolResultUnion<TOOLS extends ToolSet> =
    [ToolResultUnionOrNever<TOOLS>] extends [never]
      ? {
        type: 'tool-result';
        toolCallId: string;
        toolName: string;
        args: unknown;
        result: unknown;
      }
    : ToolResultUnionOrNever<TOOLS>;

This would at least make the "tool-result" type visible to TypeScript again when handling all possible cases.

nvie avatar Apr 17 '25 10:04 nvie

Is this on main?

lgrammel avatar Apr 17 '25 12:04 lgrammel

Yes, this seems to be an issue on main.

nvie avatar Apr 17 '25 13:04 nvie

I ran into this problem a few days ago too. I solved it by given it the ToolSet definition.

wong2 avatar Apr 18 '25 03:04 wong2

Do you mean like how I did it in the screenshot above? That's what I'm doing as well, but it isn't working unfortunately. It works for every case, except "tool-result".

The reason it doesn't work is because of this definition:

type ToolResultUnion<TOOLS extends ToolSet> =
  ToToolResultObject<ToToolsWithDefinedExecute<ToToolsWithExecute<TOOLS>>>

Let's put in ToolSet for the TOOLS param:

  • ToToolsWithExecute<ToolSet> (evaluates to) → {}
  • ToToolsWithDefinedExecute<{}>{}
  • ToToolResultObject<{}>never (*)

And therefore:

  • { type: "tool-result" } & nevernever

And that's why "tool-result" is not part of the union of string literals above.

The issue is here (*). Ideally ToToolResultObject<{}> would return some generic "tool-result" structure, instead of never.

nvie avatar Apr 18 '25 06:04 nvie

We have changed the inference in the v5 branch a week or so ago. Can you see if this happens on v5 canary releases as well?

lgrammel avatar Apr 21 '25 08:04 lgrammel

I can confirm that we no longer see this issue on 5.0.29.

marcbouchenoire avatar Sep 03 '25 11:09 marcbouchenoire