{"record":{"id":"72878d4db877bb4f","repo":"wavetermdev/waveterm","slug":"ai-endpoint-is-required","errorCode":null,"errorMessage":"ai:endpoint is required","messagePattern":"ai:endpoint is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/aiusechat/anthropic/anthropic-convertmessage.go","lineNumber":43,"sourceCode":")\n\n// these conversions are based off the anthropic spec\n// and the aiprompts/aisdk-uimessage-type.md doc (v5)\n\n// buildAnthropicHTTPRequest creates a complete HTTP request for the Anthropic API\nfunc buildAnthropicHTTPRequest(ctx context.Context, msgs []anthropicInputMessage, chatOpts uctypes.WaveChatOpts) (*http.Request, error) {\n\topts := chatOpts.Config\n\tif opts.Model == \"\" {\n\t\treturn nil, errors.New(\"ai:model is required\")\n\t}\n\tif chatOpts.ClientId == \"\" {\n\t\treturn nil, errors.New(\"chatOpts.ClientId is required\")\n\t}\n\n\t// Set defaults\n\tendpoint := opts.Endpoint\n\tif endpoint == \"\" {\n\t\treturn nil, errors.New(\"ai:endpoint is required\")\n\t}\n\n\tmaxTokens := opts.MaxTokens\n\tif maxTokens <= 0 {\n\t\tmaxTokens = AnthropicDefaultMaxTokens\n\t}\n\n\t// Convert messages to clear FileName fields from Source blocks\n\tconvertedMsgs := make([]anthropicInputMessage, len(msgs))\n\tfor i, msg := range msgs {\n\t\tconvertedMsgs[i] = convertMessageForAPI(msg)\n\t}\n\n\t// inject chatOpts.TabState as a \"text\" block at the END of the LAST \"user\" message found (append to Content)\n\tif chatOpts.TabState != \"\" {\n\t\t// Find the last \"user\" message\n\t\tfor i := len(convertedMsgs) - 1; i >= 0; i-- {\n\t\t\tif convertedMsgs[i].Role == \"user\" {","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/aiusechat/anthropic/anthropic-convertmessage.go#L25-L61","documentation":"After model and ClientId checks, buildAnthropicHTTPRequest requires an endpoint: opts.Endpoint. Unlike maxTokens, endpoint has no default, so an empty value is a hard error — the library refuses to guess which Anthropic-compatible server to call.","triggerScenarios":"chatOpts.Config.Endpoint left empty when calling RunAnthropicChatStep; custom proxy setups where the endpoint config key exists but is blank.","commonSituations":"Users switching from an OpenAI backend (which may default) to Anthropic without adding 'ai:endpoint'; self-hosted gateway config with an empty URL value; config file parsed but the endpoint key misspelled.","solutions":["Set chatOpts.Config.Endpoint to the Anthropic API base URL (e.g. \"https://api.anthropic.com\").","If using a proxy/gateway, set the endpoint to that gateway URL in the ai config.","Validate endpoint presence when loading config so it fails at startup instead of per-request."],"exampleFix":"// before\nConfig: uctypes.AIConfig{Model: \"claude-sonnet-4-20250514\"}\n\n// after\nConfig: uctypes.AIConfig{Model: \"claude-sonnet-4-20250514\", Endpoint: \"https://api.anthropic.com\"}","handlingStrategy":"validation","validationCode":"if chatOpts.Config.Endpoint == \"\" {\n    chatOpts.Config.Endpoint = \"https://api.anthropic.com\" // or return an error\n}","typeGuard":"func hasEndpoint(cfg uctypes.AIConfig) bool { return cfg.Endpoint != \"\" }","tryCatchPattern":"_, _, _, err := RunAnthropicChatStep(ctx, sse, chatOpts, cont)\nif err != nil && strings.Contains(err.Error(), \"ai:endpoint is required\") {\n    return fmt.Errorf(\"configure 'ai:endpoint' (e.g. https://api.anthropic.com): %w\", err)\n}","preventionTips":["Set ai:endpoint whenever you configure an Anthropic backend, including proxy setups.","Apply a default endpoint in a config-normalization step so it is never empty.","Recheck endpoint config after switching backends between providers."],"tags":["configuration","network","validation"],"backgroundTag":"missing-required-config","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}