Skip to content

bug(ai-grok): grokSummarize is not assignable to summarize()'s adapter param for any current model #821

Description

@tombeckenham

Summary

grokSummarize(...) (and createGrokSummarize(...)) is not assignable to summarize()'s adapter param for any current Grok model (grok-4.3, grok-build-0.1). This is a real type regression introduced by main's provider-options rewrite; it was never caught because the only call sites live in testing/**, which CI excludes from test:types (see #820).

Repro

import { summarize } from '@tanstack/ai'
import { grokSummarize } from '@tanstack/ai-grok/adapters'

summarize({ adapter: grokSummarize('grok-4.3'), text: 'x' })       // ❌ type error
summarize({ adapter: grokSummarize('grok-build-0.1'), text: 'x' }) // ❌ type error
Type 'ChatStreamSummarizeAdapter<"grok-4.3", GrokTextProviderOptions>' is not assignable to
  'SummarizeAdapter<string, object>'.
  Types of property 'summarize' are incompatible.
    Type '(options: SummarizationOptions<GrokTextProviderOptions>) => Promise<SummarizationResult>'
      is not assignable to '(options: SummarizationOptions<object>) => Promise<SummarizationResult>'.
        ... Type 'SummarizationOptions<object>' is not assignable to 'SummarizationOptions<GrokTextProviderOptions>'.
          Type 'object' is not assignable to type 'GrokTextProviderOptions'.

OpenAI (openaiSummarize('gpt-5.2')) passes — confirming this is Grok-specific.

Root cause

SummarizeAdapter.summarize is declared as a property (arrow-function type), not a method shorthand (packages/ai/src/activities/summarize/adapter.ts:49). Under strictFunctionTypes, property-position function params are checked contravariantly. So assignability to the constraint SummarizeAdapter<string, object> (the bound on summarize<TAdapter extends SummarizeAdapter<string, object>>) reduces to:

is object assignable to TProviderOptions?

For Grok, TProviderOptions is GrokTextProviderOptions, which extends Record<string, unknown> (packages/ai-grok/src/text/text-provider-options.ts:35-36):

export interface GrokTextProviderOptions
  extends GrokBaseOptions, Record<string, unknown> { ... }

object is not assignable to Record<string, unknown> (an index-signature type), so the check fails. grok-build-0.1 resolves to GrokBuildProviderOptions = Omit<GrokTextProviderOptions, 'reasoning'> & { reasoning?: never }, which retains the index signature and fails the same way.

OpenAI's options (ExternalTextProviderOptions) are a pure intersection of interfaces with all-optional named props and no index signature, and object is assignable to an all-optional object type — which is why OpenAI passes. So the index signature on the Grok options is exactly what breaks it; it is not present on the other providers.

Minimal isolation

const r1: Record<string, unknown> = {} as object  // ❌ object not assignable to index-sig type
const r2: { a?: number }          = {} as object  // ✅ object assignable to all-optional type

Fix direction

Make Grok's options match the all-optional, no-index-signature shape the other providers use:

  • Drop extends ... Record<string, unknown> from GrokTextProviderOptions; keep only the named optional props.
  • Re-check GrokBuildProviderOptions's Omit<..., 'reasoning'> — with the index signature gone, Omit will preserve the named keys correctly instead of collapsing them into string.

Confirm afterward that per-model provider-options narrowing (the reasoning?: never distinction for grok-build-0.1) still holds.

Test coverage

  • Add a call-site type assertion in packages/ai-grok/tests/ (an included package) so this can never regress silently again:
    summarize({ adapter: grokSummarize('grok-4.3'), text: '' })
    summarize({ adapter: grokSummarize('grok-build-0.1'), text: '' })
    Constructing the adapter (current coverage) is not enough — the constraint only instantiates at the summarize() call site.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions