diff --git a/packages/image-generation/README.md b/packages/image-generation/README.md
index bc87b368..44c67b6e 100644
--- a/packages/image-generation/README.md
+++ b/packages/image-generation/README.md
@@ -41,7 +41,7 @@ uv add "celeste-ai[image-generation]"
-
+
**Missing a provider?** [Request it](https://github.com/withceleste/celeste-python/issues/new) – ⚡ **we ship fast**.
diff --git a/packages/text-generation/pyproject.toml b/packages/text-generation/pyproject.toml
index f084a438..446950c0 100644
--- a/packages/text-generation/pyproject.toml
+++ b/packages/text-generation/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "celeste-text-generation"
-version = "0.2.9"
+version = "0.2.10"
description = "Text generation package for Celeste AI. Unified interface for all providers"
authors = [{name = "Kamilbenkirane", email = "kamil@withceleste.ai"}]
readme = "README.md"
diff --git a/packages/text-generation/src/celeste_text_generation/models.py b/packages/text-generation/src/celeste_text_generation/models.py
index 899a4260..63081f98 100644
--- a/packages/text-generation/src/celeste_text_generation/models.py
+++ b/packages/text-generation/src/celeste_text_generation/models.py
@@ -8,6 +8,7 @@
from celeste_text_generation.providers.google.models import MODELS as GOOGLE_MODELS
from celeste_text_generation.providers.mistral.models import MODELS as MISTRAL_MODELS
from celeste_text_generation.providers.openai.models import MODELS as OPENAI_MODELS
+from celeste_text_generation.providers.xai.models import MODELS as XAI_MODELS
MODELS: list[Model] = [
*ANTHROPIC_MODELS,
@@ -15,4 +16,5 @@
*GOOGLE_MODELS,
*MISTRAL_MODELS,
*OPENAI_MODELS,
+ *XAI_MODELS,
]
diff --git a/packages/text-generation/src/celeste_text_generation/providers/__init__.py b/packages/text-generation/src/celeste_text_generation/providers/__init__.py
index 96600e12..36b5d55f 100644
--- a/packages/text-generation/src/celeste_text_generation/providers/__init__.py
+++ b/packages/text-generation/src/celeste_text_generation/providers/__init__.py
@@ -23,6 +23,9 @@ def _get_providers() -> list[tuple[Provider, type[Client]]]:
from celeste_text_generation.providers.openai.client import (
OpenAITextGenerationClient,
)
+ from celeste_text_generation.providers.xai.client import (
+ XAITextGenerationClient,
+ )
return [
(Provider.ANTHROPIC, AnthropicTextGenerationClient),
@@ -30,6 +33,7 @@ def _get_providers() -> list[tuple[Provider, type[Client]]]:
(Provider.GOOGLE, GoogleTextGenerationClient),
(Provider.MISTRAL, MistralTextGenerationClient),
(Provider.OPENAI, OpenAITextGenerationClient),
+ (Provider.XAI, XAITextGenerationClient),
]
diff --git a/packages/text-generation/src/celeste_text_generation/providers/mistral/config.py b/packages/text-generation/src/celeste_text_generation/providers/mistral/config.py
index e889c537..0d9030a8 100644
--- a/packages/text-generation/src/celeste_text_generation/providers/mistral/config.py
+++ b/packages/text-generation/src/celeste_text_generation/providers/mistral/config.py
@@ -3,7 +3,7 @@
# HTTP Configuration
BASE_URL = "https://api.mistral.ai"
ENDPOINT = "/v1/chat/completions"
-STREAM_ENDPOINT = ENDPOINT # Same endpoint
+STREAM_ENDPOINT = ENDPOINT
# Authentication
AUTH_HEADER_NAME = "Authorization"
diff --git a/packages/text-generation/src/celeste_text_generation/providers/xai/__init__.py b/packages/text-generation/src/celeste_text_generation/providers/xai/__init__.py
new file mode 100644
index 00000000..61c3ce1e
--- /dev/null
+++ b/packages/text-generation/src/celeste_text_generation/providers/xai/__init__.py
@@ -0,0 +1,7 @@
+"""XAI provider for text generation."""
+
+from .client import XAITextGenerationClient
+from .models import MODELS
+from .streaming import XAITextGenerationStream
+
+__all__ = ["MODELS", "XAITextGenerationClient", "XAITextGenerationStream"]
diff --git a/packages/text-generation/src/celeste_text_generation/providers/xai/client.py b/packages/text-generation/src/celeste_text_generation/providers/xai/client.py
new file mode 100644
index 00000000..0e5a3b3a
--- /dev/null
+++ b/packages/text-generation/src/celeste_text_generation/providers/xai/client.py
@@ -0,0 +1,142 @@
+"""XAI client implementation for text generation."""
+
+from collections.abc import AsyncIterator
+from typing import Any, Unpack
+
+import httpx
+from pydantic import BaseModel
+
+from celeste.mime_types import ApplicationMimeType
+from celeste.parameters import ParameterMapper
+from celeste_text_generation.client import TextGenerationClient
+from celeste_text_generation.io import (
+ TextGenerationFinishReason,
+ TextGenerationInput,
+ TextGenerationUsage,
+)
+from celeste_text_generation.parameters import TextGenerationParameters
+
+from . import config
+from .parameters import XAI_PARAMETER_MAPPERS
+from .streaming import XAITextGenerationStream
+
+
+class XAITextGenerationClient(TextGenerationClient):
+ """XAI client for text generation."""
+
+ @classmethod
+ def parameter_mappers(cls) -> list[ParameterMapper]:
+ return XAI_PARAMETER_MAPPERS
+
+ def _init_request(self, inputs: TextGenerationInput) -> dict[str, Any]:
+ """Initialize request from XAI messages array format."""
+ messages = [
+ {
+ "role": "user",
+ "content": inputs.prompt,
+ }
+ ]
+
+ return {"messages": messages}
+
+ def _parse_usage(self, response_data: dict[str, Any]) -> TextGenerationUsage:
+ """Parse usage from response."""
+ usage_data = response_data.get("usage", {})
+ prompt_tokens_details = usage_data.get("prompt_tokens_details", {})
+ completion_tokens_details = usage_data.get("completion_tokens_details", {})
+
+ return TextGenerationUsage(
+ input_tokens=usage_data.get("prompt_tokens"),
+ output_tokens=usage_data.get("completion_tokens"),
+ total_tokens=usage_data.get("total_tokens"),
+ cached_tokens=prompt_tokens_details.get("cached_tokens"),
+ reasoning_tokens=completion_tokens_details.get("reasoning_tokens"),
+ billed_tokens=None,
+ )
+
+ def _parse_content(
+ self,
+ response_data: dict[str, Any],
+ **parameters: Unpack[TextGenerationParameters],
+ ) -> str | BaseModel:
+ """Parse content from response."""
+ choices = response_data.get("choices", [])
+ if not choices:
+ msg = "No choices in response"
+ raise ValueError(msg)
+
+ message = choices[0].get("message", {})
+ content = message.get("content") or ""
+
+ return self._transform_output(content, **parameters)
+
+ def _parse_finish_reason(
+ self, response_data: dict[str, Any]
+ ) -> TextGenerationFinishReason | None:
+ """Parse finish reason from response."""
+ choices = response_data.get("choices", [])
+ if not choices:
+ return None
+
+ choice = choices[0]
+ finish_reason = choice.get("finish_reason")
+
+ if not finish_reason:
+ return None
+
+ return TextGenerationFinishReason(reason=finish_reason)
+
+ def _build_metadata(self, response_data: dict[str, Any]) -> dict[str, Any]:
+ """Build metadata dictionary from response data."""
+ # Filter content field before calling super
+ content_fields = {"choices"}
+ filtered_data = {
+ k: v for k, v in response_data.items() if k not in content_fields
+ }
+ return super()._build_metadata(filtered_data)
+
+ async def _make_request(
+ self,
+ request_body: dict[str, Any],
+ **parameters: Unpack[TextGenerationParameters],
+ ) -> httpx.Response:
+ """Make HTTP request(s) and return response object."""
+ request_body["model"] = self.model.id
+
+ headers = {
+ config.AUTH_HEADER_NAME: f"{config.AUTH_HEADER_PREFIX}{self.api_key.get_secret_value()}",
+ "Content-Type": ApplicationMimeType.JSON,
+ }
+
+ return await self.http_client.post(
+ f"{config.BASE_URL}{config.ENDPOINT}",
+ headers=headers,
+ json_body=request_body,
+ )
+
+ def _stream_class(self) -> type[XAITextGenerationStream]:
+ """Return the Stream class for this client."""
+ return XAITextGenerationStream
+
+ def _make_stream_request(
+ self,
+ request_body: dict[str, Any],
+ **parameters: Unpack[TextGenerationParameters],
+ ) -> AsyncIterator[dict[str, Any]]:
+ """Make HTTP streaming request and return async iterator of events."""
+ request_body["model"] = self.model.id
+ request_body["stream"] = True
+
+ headers = {
+ config.AUTH_HEADER_NAME: f"{config.AUTH_HEADER_PREFIX}{self.api_key.get_secret_value()}",
+ "Content-Type": ApplicationMimeType.JSON,
+ }
+
+ return self.http_client.stream_post(
+ f"{config.BASE_URL}{config.STREAM_ENDPOINT}",
+ headers=headers,
+ json_body=request_body,
+ )
+
+
+__all__ = ["XAITextGenerationClient"]
diff --git a/packages/text-generation/src/celeste_text_generation/providers/xai/config.py b/packages/text-generation/src/celeste_text_generation/providers/xai/config.py
new file mode 100644
index 00000000..04472e3b
--- /dev/null
+++ b/packages/text-generation/src/celeste_text_generation/providers/xai/config.py
@@ -0,0 +1,10 @@
+"""XAI provider configuration for text generation."""
+
+# HTTP Configuration
+BASE_URL = "https://api.x.ai/v1"
+ENDPOINT = "/chat/completions"
+STREAM_ENDPOINT = ENDPOINT
+
+# Authentication
+AUTH_HEADER_NAME = "Authorization"
+AUTH_HEADER_PREFIX = "Bearer "
diff --git a/packages/text-generation/src/celeste_text_generation/providers/xai/models.py b/packages/text-generation/src/celeste_text_generation/providers/xai/models.py
new file mode 100644
index 00000000..449e66e6
--- /dev/null
+++ b/packages/text-generation/src/celeste_text_generation/providers/xai/models.py
@@ -0,0 +1,76 @@
+"""XAI models for text generation."""
+
+from celeste import Model, Provider
+from celeste.constraints import Choice, Range, Schema
+from celeste.core import Parameter
+from celeste_text_generation.parameters import TextGenerationParameter
+
+MODELS: list[Model] = [
+ Model(
+ id="grok-4-1-fast-reasoning",
+ provider=Provider.XAI,
+ display_name="Grok 4.1 Fast Reasoning",
+ streaming=True,
+ parameter_constraints={
+ Parameter.TEMPERATURE: Range(min=0.0, max=2.0),
+ Parameter.MAX_TOKENS: Range(min=1, max=30000),
+ TextGenerationParameter.OUTPUT_SCHEMA: Schema(),
+ },
+ ),
+ Model(
+ id="grok-4-1-fast-non-reasoning",
+ provider=Provider.XAI,
+ display_name="Grok 4.1 Fast Non-Reasoning",
+ streaming=True,
+ parameter_constraints={
+ Parameter.TEMPERATURE: Range(min=0.0, max=2.0),
+ Parameter.MAX_TOKENS: Range(min=1, max=30000),
+ TextGenerationParameter.OUTPUT_SCHEMA: Schema(),
+ },
+ ),
+ Model(
+ id="grok-4-fast-reasoning",
+ provider=Provider.XAI,
+ display_name="Grok 4 Fast Reasoning",
+ streaming=True,
+ parameter_constraints={
+ Parameter.TEMPERATURE: Range(min=0.0, max=2.0),
+ Parameter.MAX_TOKENS: Range(min=1, max=30000),
+ TextGenerationParameter.OUTPUT_SCHEMA: Schema(),
+ },
+ ),
+ Model(
+ id="grok-4-fast-non-reasoning",
+ provider=Provider.XAI,
+ display_name="Grok 4 Fast Non-Reasoning",
+ streaming=True,
+ parameter_constraints={
+ Parameter.TEMPERATURE: Range(min=0.0, max=2.0),
+ Parameter.MAX_TOKENS: Range(min=1, max=30000),
+ TextGenerationParameter.OUTPUT_SCHEMA: Schema(),
+ },
+ ),
+ Model(
+ id="grok-4-0709",
+ provider=Provider.XAI,
+ display_name="Grok 4",
+ streaming=True,
+ parameter_constraints={
+ Parameter.TEMPERATURE: Range(min=0.0, max=2.0),
+ Parameter.MAX_TOKENS: Range(min=1, max=64000),
+ TextGenerationParameter.OUTPUT_SCHEMA: Schema(),
+ },
+ ),
+ Model(
+ id="grok-3-mini",
+ provider=Provider.XAI,
+ display_name="Grok 3 Mini",
+ streaming=True,
+ parameter_constraints={
+ Parameter.TEMPERATURE: Range(min=0.0, max=2.0),
+ Parameter.MAX_TOKENS: Range(min=1, max=16000),
+ TextGenerationParameter.THINKING_LEVEL: Choice(options=["low", "high"]),
+ TextGenerationParameter.OUTPUT_SCHEMA: Schema(),
+ },
+ ),
+]
diff --git a/packages/text-generation/src/celeste_text_generation/providers/xai/parameters.py b/packages/text-generation/src/celeste_text_generation/providers/xai/parameters.py
new file mode 100644
index 00000000..903f6bfb
--- /dev/null
+++ b/packages/text-generation/src/celeste_text_generation/providers/xai/parameters.py
@@ -0,0 +1,214 @@
+"""XAI parameter mappers for text generation."""
+
+import json
+from typing import Any, get_args, get_origin
+
+from pydantic import BaseModel, TypeAdapter
+
+from celeste.core import Parameter
+from celeste.models import Model
+from celeste.parameters import ParameterMapper
+from celeste_text_generation.parameters import TextGenerationParameter
+
+
+class OutputSchemaMapper(ParameterMapper):
+ """Map output_schema parameter to XAI response_format."""
+
+ name = TextGenerationParameter.OUTPUT_SCHEMA
+
+ def map(
+ self,
+ request: dict[str, Any],
+ value: object,
+ model: Model,
+ ) -> dict[str, Any]:
+ """Transform output_schema into provider request."""
+ validated_value = self._validate_value(value, model)
+ if validated_value is None:
+ return request
+
+ schema = self._convert_to_json_schema(validated_value)
+ schema_name = self._get_schema_name(validated_value)
+
+ request["response_format"] = {
+ "type": "json_schema",
+ "json_schema": {
+ "name": schema_name,
+ "strict": True,
+ "schema": schema,
+ },
+ }
+
+ return request
+
+ def parse_output(self, content: str, value: object | None) -> str | BaseModel:
+ """Parse JSON string to BaseModel instance if output_schema provided."""
+ if value is None:
+ return content
+
+ parsed_json = json.loads(content)
+ origin = get_origin(value)
+ if origin is list and isinstance(parsed_json, dict) and "items" in parsed_json:
+ parsed_json = parsed_json["items"]
+
+ return TypeAdapter(value).validate_json(json.dumps(parsed_json))
+
+ def _convert_to_json_schema(self, output_schema: Any) -> dict[str, Any]: # noqa: ANN401
+ """Convert Pydantic BaseModel or list[BaseModel] to JSON Schema format."""
+ origin = get_origin(output_schema)
+ if origin is list:
+ inner_type = get_args(output_schema)[0]
+ items_schema = inner_type.model_json_schema()
+ json_schema = {
+ "type": "object",
+ "properties": {
+ "items": {
+ "type": "array",
+ "items": items_schema,
+ }
+ },
+ "required": ["items"],
+ }
+ else:
+ json_schema = output_schema.model_json_schema()
+
+ json_schema = self._transform_schema(json_schema)
+ return json_schema
+
+ def _transform_schema(
+ self, schema: dict[str, Any], defs: dict[str, Any] | None = None
+ ) -> dict[str, Any]:
+ """Recursively transform schema for API compatibility."""
+ if not isinstance(schema, dict):
+ return schema
+
+ if defs is None:
+ defs = self._collect_all_defs(schema)
+
+ if "$ref" in schema:
+ ref_path = schema["$ref"]
+ if ref_path.startswith("#/$defs/"):
+ def_name = ref_path.split("/")[-1]
+ if def_name in defs:
+ expanded = defs[def_name].copy()
+ expanded.pop("description", None)
+ return self._transform_schema(expanded, defs)
+ return schema
+
+ result: dict[str, Any] = {}
+ for key, value in schema.items():
+ if key == "$defs":
+ continue
+ elif isinstance(value, dict):
+ result[key] = self._transform_schema(value, defs)
+ elif isinstance(value, list):
+ result[key] = [
+ self._transform_schema(item, defs)
+ if isinstance(item, dict)
+ else item
+ for item in value
+ ]
+ else:
+ result[key] = value
+
+ if result.get("type") == "object":
+ result["additionalProperties"] = False
+
+ return result
+
+ def _collect_all_defs(self, schema: Any) -> dict[str, Any]: # noqa: ANN401
+ """Recursively collect all $defs dictionaries from schema tree."""
+ defs: dict[str, Any] = {}
+
+ def collect(value: Any) -> None: # noqa: ANN401
+ if isinstance(value, dict):
+ if "$defs" in value:
+ defs.update(value["$defs"])
+ for v in value.values():
+ collect(v)
+ elif isinstance(value, list):
+ for item in value:
+ collect(item)
+
+ collect(schema)
+ return defs
+
+ def _get_schema_name(self, output_schema: Any) -> str: # noqa: ANN401
+ """Derive schema name from model class name."""
+ origin = get_origin(output_schema)
+ if origin is list:
+ inner_type = get_args(output_schema)[0]
+ class_name = inner_type.__name__
+ return f"{class_name.lower()}_list"
+ else:
+ return output_schema.__name__.lower()
+
+
+class TemperatureMapper(ParameterMapper):
+ """Map temperature parameter to XAI temperature field."""
+
+ name = Parameter.TEMPERATURE
+
+ def map(
+ self,
+ request: dict[str, Any],
+ value: object,
+ model: Model,
+ ) -> dict[str, Any]:
+ """Transform temperature into provider request."""
+ validated_value = self._validate_value(value, model)
+ if validated_value is None:
+ return request
+
+ request["temperature"] = validated_value
+ return request
+
+
+class MaxTokensMapper(ParameterMapper):
+ """Map max_tokens parameter to XAI max_tokens field."""
+
+ name = Parameter.MAX_TOKENS
+
+ def map(
+ self,
+ request: dict[str, Any],
+ value: object,
+ model: Model,
+ ) -> dict[str, Any]:
+ """Transform max_tokens into provider request."""
+ validated_value = self._validate_value(value, model)
+ if validated_value is None:
+ return request
+
+ request["max_tokens"] = validated_value
+ return request
+
+
+class ThinkingLevelMapper(ParameterMapper):
+ """Map thinking_level parameter to XAI reasoning_effort field."""
+
+ name = TextGenerationParameter.THINKING_LEVEL
+
+ def map(
+ self,
+ request: dict[str, Any],
+ value: object,
+ model: Model,
+ ) -> dict[str, Any]:
+ """Transform thinking_level into provider request."""
+ validated_value = self._validate_value(value, model)
+ if validated_value is None:
+ return request
+
+ request["reasoning_effort"] = validated_value
+ return request
+
+
+XAI_PARAMETER_MAPPERS: list[ParameterMapper] = [
+ TemperatureMapper(),
+ MaxTokensMapper(),
+ ThinkingLevelMapper(),
+ OutputSchemaMapper(),
+]
+
+__all__ = ["XAI_PARAMETER_MAPPERS"]
diff --git a/packages/text-generation/src/celeste_text_generation/providers/xai/streaming.py b/packages/text-generation/src/celeste_text_generation/providers/xai/streaming.py
new file mode 100644
index 00000000..1d1a74c7
--- /dev/null
+++ b/packages/text-generation/src/celeste_text_generation/providers/xai/streaming.py
@@ -0,0 +1,134 @@
+"""XAI streaming for text generation."""
+
+from collections.abc import Callable
+from typing import Any, Unpack
+
+from celeste_text_generation.io import (
+ TextGenerationChunk,
+ TextGenerationFinishReason,
+ TextGenerationOutput,
+ TextGenerationUsage,
+)
+from celeste_text_generation.parameters import TextGenerationParameters
+from celeste_text_generation.streaming import TextGenerationStream
+
+
+class XAITextGenerationStream(TextGenerationStream):
+ """XAI streaming for text generation."""
+
+ def __init__(
+ self,
+ sse_iterator: Any, # noqa: ANN401
+ transform_output: Callable[..., object],
+ **parameters: Unpack[TextGenerationParameters],
+ ) -> None:
+ """Initialize stream with output transformation support.
+
+ Args:
+ sse_iterator: Server-Sent Events iterator.
+ transform_output: Function to transform accumulated content (e.g., JSON → BaseModel).
+ **parameters: Parameters passed to stream() for output transformation.
+ """
+ super().__init__(sse_iterator, **parameters)
+ self._transform_output = transform_output
+
+ def _parse_chunk(self, event: dict[str, Any]) -> TextGenerationChunk | None:
+ """Parse chunk from SSE event.
+
+ Extract from choices[0].delta.content (content delta events).
+ Extract finish_reason and usage from final event when finish_reason is not null.
+ Return None if no text delta (filter lifecycle events).
+ """
+ choices = event.get("choices", [])
+ if not choices:
+ return None
+
+ first_choice = choices[0]
+ if not isinstance(first_choice, dict):
+ return None
+
+ delta = first_choice.get("delta", {})
+ if not isinstance(delta, dict):
+ return None
+
+ # Extract content delta
+ content_delta = delta.get("content")
+ finish_reason_str = first_choice.get("finish_reason")
+
+ # Extract usage from event if present (in final event)
+ usage = None
+ usage_dict = event.get("usage")
+ if isinstance(usage_dict, dict):
+ prompt_tokens_details = usage_dict.get("prompt_tokens_details", {})
+ completion_tokens_details = usage_dict.get("completion_tokens_details", {})
+
+ usage = TextGenerationUsage(
+ input_tokens=usage_dict.get("prompt_tokens"),
+ output_tokens=usage_dict.get("completion_tokens"),
+ total_tokens=usage_dict.get("total_tokens"),
+ cached_tokens=prompt_tokens_details.get("cached_tokens"),
+ reasoning_tokens=completion_tokens_details.get("reasoning_tokens"),
+ billed_tokens=None,
+ )
+
+ # Create finish reason if present
+ finish_reason = (
+ TextGenerationFinishReason(reason=finish_reason_str)
+ if finish_reason_str
+ else None
+ )
+
+ # If no content delta and no finish reason, filter this event
+ if not content_delta and not finish_reason:
+ return None
+
+ return TextGenerationChunk(
+ content=content_delta or "", # Empty string if no content (final event)
+ finish_reason=finish_reason,
+ usage=usage,
+ )
+
+ def _parse_usage(self, chunks: list[TextGenerationChunk]) -> TextGenerationUsage:
+ """Parse usage from chunks.
+
+ XAI provides usage metadata in the final event (when finish_reason is not null).
+ Use the last chunk that has usage metadata.
+ """
+ if not chunks:
+ return TextGenerationUsage()
+
+ # Usage metadata is typically in the final chunk (when finish_reason is set)
+ for chunk in reversed(chunks):
+ if chunk.usage:
+ return chunk.usage
+
+ return TextGenerationUsage()
+
+ def _parse_output(
+ self,
+ chunks: list[TextGenerationChunk],
+ **parameters: Unpack[TextGenerationParameters],
+ ) -> TextGenerationOutput:
+ """Assemble chunks into final output with structured output support.
+
+ Concatenates text chunks, then applies parameter transformations
+ (e.g., JSON → BaseModel if output_schema provided).
+ """
+ # Filter out empty chunks (from final events)
+ content_chunks = [chunk for chunk in chunks if chunk.content]
+
+ # Concatenate text chunks
+ content = "".join(chunk.content for chunk in content_chunks)
+
+ # Apply parameter transformations (e.g., JSON → BaseModel if output_schema provided)
+ content = self._transform_output(content, **parameters)
+
+ usage = self._parse_usage(chunks)
+ finish_reason = chunks[-1].finish_reason if chunks else None
+
+ return TextGenerationOutput(
+ content=content,
+ usage=usage,
+ finish_reason=finish_reason,
+ metadata={},
+ )
diff --git a/packages/text-generation/tests/integration_tests/test_text_generation/test_generate.py b/packages/text-generation/tests/integration_tests/test_text_generation/test_generate.py
index 7dcde54f..d3f57e15 100644
--- a/packages/text-generation/tests/integration_tests/test_text_generation/test_generate.py
+++ b/packages/text-generation/tests/integration_tests/test_text_generation/test_generate.py
@@ -13,6 +13,7 @@
(Provider.GOOGLE, "gemini-2.5-flash-lite", {"thinking_budget": 0}),
(Provider.MISTRAL, "mistral-tiny", {}),
(Provider.COHERE, "command-a-03-2025", {}),
+ (Provider.XAI, "grok-3-mini", {}),
],
)
@pytest.mark.integration
diff --git a/packages/text-generation/tests/integration_tests/test_text_generation/test_stream.py b/packages/text-generation/tests/integration_tests/test_text_generation/test_stream.py
index 9fcbc8d1..b67b1ad6 100644
--- a/packages/text-generation/tests/integration_tests/test_text_generation/test_stream.py
+++ b/packages/text-generation/tests/integration_tests/test_text_generation/test_stream.py
@@ -13,6 +13,7 @@
(Provider.GOOGLE, "gemini-2.5-flash-lite", {"thinking_budget": 0}),
(Provider.MISTRAL, "mistral-tiny", {}),
(Provider.COHERE, "command-a-03-2025", {}),
+ (Provider.XAI, "grok-3-mini", {}),
],
)
@pytest.mark.integration
diff --git a/packages/video-generation/README.md b/packages/video-generation/README.md
index 7e56c9e4..4df20632 100644
--- a/packages/video-generation/README.md
+++ b/packages/video-generation/README.md
@@ -39,7 +39,7 @@ uv add "celeste-ai[video-generation]"