Migrate Go AI SDK v0.3.x to v0.4.0
Go AI SDK v0.4.0 changes when streaming tools execute, switches xAI's default API, removes two retired xAI model IDs, and rejects MCP server redirects by default. None of these rename or remove a Go type or function signature, so v0.3.x code keeps compiling — what changes is runtime behavior. This guide is for anyone still on v0.3.x.
Recommended migration process
- Update:
go get github.com/digitallysavvy/go-ai@v0.4.0 - If a
StreamTextconsumer expects tool-result chunks interleaved with text, move that handling to run after the stream ends. - Replace
grok-2andgrok-2-vision-1212model IDs with agrok-3or later model. - If you rely on xAI's Chat Completions API, call
ChatCompletionsLanguageModel()explicitly. - If an MCP server needs redirects, opt in with
Redirect: mcp.MCPRedirectFollow. - Build and test:
go build ./...andgo test ./....
Behavior changes
Streaming tool execution moves to after the stream
Impact: runtime only — no compile change.
StreamText forwards every provider chunk (text, reasoning, custom content) to OnChunk before running any tool's Execute callback; ChunkTypeToolResult chunks now arrive only after the stream ends. In v0.3.x they could interleave with text chunks. This applies to tools with a Go Execute callback — provider-executed results (ProviderExecuted: true, e.g. Google code execution) still arrive inline as the provider produces them.
result, err := ai.StreamText(ctx, ai.StreamTextOptions{
Model: model,
Prompt: "What's the weather?",
Tools: []types.Tool{weatherTool},
// StreamText defaults to a single step; allow more so the model gets
// a turn to respond in text after the tool result.
StopWhen: []ai.StopCondition{ai.IsStepCount(5)},
OnChunk: func(chunk provider.StreamChunk) {
fmt.Print(chunk.Text)
},
})
if err != nil {
log.Fatal(err)
}
defer result.Close()
for range result.Chunks() {
}
This code is unchanged between v0.3.x and v0.4.0. What changes is the order OnChunk sees: in v0.3.x a ChunkTypeToolResult chunk could arrive between text chunks; in v0.4.0 it always arrives after the stream's text and reasoning chunks have finished.
XAI's default language model uses the Responses API
Impact: runtime only — no compile change.
xai.Provider.LanguageModel() now returns a model backed by xAI's Responses API instead of Chat Completions. The Responses API adds image input and finer-grained reasoning effort levels. Call ChatCompletionsLanguageModel() for the v0.3.x behavior.
v0.5.0:
ChatCompletionsLanguageModel()has since been removed (the TS SDK dropped xAI's Chat Completions API). On v0.5.0,LanguageModel()is the only xAI language model. See Migrating from v0.4 to v0.5.
provider := xai.New(xai.Config{APIKey: os.Getenv("XAI_API_KEY")})
model, _ := provider.LanguageModel("grok-3") // Responses API (new default)
chatModel, _ := provider.ChatCompletionsLanguageModel("grok-3") // Chat Completions API (v0.3.x behavior)
XAI removed grok-2 model IDs
Impact: runtime only. LanguageModel() never validated model ID strings at compile time or call time, so this doesn't produce a Go error either — the request reaches xAI, and xAI rejects the retired model.
model, _ := provider.LanguageModel("grok-2") // xAI: model not found
model, _ := provider.LanguageModel("grok-2-vision-1212") // xAI: model not found
// Use instead:
model, _ := provider.LanguageModel("grok-3")
model, _ := provider.LanguageModel("grok-3-mini")
MCP HTTP transport rejects redirects by default
Impact: runtime only — no compile change; may break an MCP integration that relied on a redirect.
MCPRedirectMode's zero value is MCPRedirectError, so mcp.NewHTTPTransport now rejects a redirect instead of following it. This closes an SSRF path where a malicious MCP server redirects requests to an internal service.
transport := mcp.NewHTTPTransport(mcp.HTTPTransportConfig{
URL: "https://mcp.example.com",
})
// v0.4.0: a redirect from this server now returns an error.
// To allow redirects (only for a server you trust):
transport := mcp.NewHTTPTransport(mcp.HTTPTransportConfig{
URL: "https://mcp.example.com",
Config: mcp.TransportConfig{
Redirect: mcp.MCPRedirectFollow,
},
})
Telemetry: register an integration instead of passing a Tracer per call
Impact: low. telemetry.Options.Tracer still works; RegisterTelemetryIntegration is the supported path for new code.
telemetry.RegisterTelemetryIntegration(telemetry.OTelTelemetryIntegration{})
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Prompt: "Hello",
Telemetry: &telemetry.Options{
IsEnabled: telemetry.Bool(true),
RecordInputs: true,
FunctionID: "my-chat",
},
})
If you relied on the default global tracer rather than a custom Tracer, register telemetry.OTelTelemetryIntegration{} — it uses otel.Tracer("ai-sdk") when no custom tracer is set. AddTelemetryIntegration() fans out to multiple backends, ClearTelemetryIntegrations() resets the registry, and telemetry.NoopTelemetryIntegration{} disables telemetry globally.
New in v0.4.0
These are additive; existing code keeps compiling without them.
- Top-level
ReasoningonGenerateTextOptions/StreamTextOptions— see Generating text - Deferred provider tool results via
Tool.SupportsDeferredResults— see Tools and tool calling CustomContentandReasoningFileContentcontent types- Tool-level timeouts via
TimeoutConfig.ToolMs/TimeoutConfig.Tools - Embed/rerank
OnStart/OnFinishcallbacks — see Embeddings - KlingAI v3.0 motion control — new video generation capability
- Prodia language and video models — Prodia's existing image generation provider gains a language model and a video model