PHPackages                             jonaspauleta/laravel-ai-moonshot - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. jonaspauleta/laravel-ai-moonshot

ActiveLibrary[API Development](/categories/api)

jonaspauleta/laravel-ai-moonshot
================================

Moonshot AI (Kimi K2) provider for the official Laravel AI SDK.

v1.4.0(3w ago)6576[1 PRs](https://github.com/jonaspauleta/laravel-ai-moonshot/pulls)MITPHPPHP ^8.4CI passing

Since Apr 25Pushed 2w agoCompare

[ Source](https://github.com/jonaspauleta/laravel-ai-moonshot)[ Packagist](https://packagist.org/packages/jonaspauleta/laravel-ai-moonshot)[ Docs](https://github.com/jonaspauleta/laravel-ai-moonshot)[ RSS](/packages/jonaspauleta-laravel-ai-moonshot/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (4)Dependencies (13)Versions (13)Used By (0)

laravel-ai-moonshot
===================

[](#laravel-ai-moonshot)

[![Latest Version on Packagist](https://camo.githubusercontent.com/29d7ed686cbaa3e1638d1f1657253a10fc4a543cf7b9d83fee2b855d49fb5a36/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6a6f6e61737061756c6574612f6c61726176656c2d61692d6d6f6f6e73686f742e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/jonaspauleta/laravel-ai-moonshot)[![Total Downloads](https://camo.githubusercontent.com/68f12d7e43f2cab94ec36279182216e671d6a47db97a46356040c8c763a22e4e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6a6f6e61737061756c6574612f6c61726176656c2d61692d6d6f6f6e73686f742e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/jonaspauleta/laravel-ai-moonshot)[![Tests](https://camo.githubusercontent.com/cdd1b6bd7ef91a12b7ff07bcb12d5c4be6cd9a1c6bac7680eb3b40495c10fceb/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6a6f6e61737061756c6574612f6c61726176656c2d61692d6d6f6f6e73686f742f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/jonaspauleta/laravel-ai-moonshot/actions/workflows/run-tests.yml)[![License](https://camo.githubusercontent.com/a7e0a01a1e27d203d2f74b5db21df0302fd4169441da64c9aae06d112dd158dc/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6a6f6e61737061756c6574612f6c61726176656c2d61692d6d6f6f6e73686f742e7376673f7374796c653d666c61742d737175617265)](LICENSE.md)

[Moonshot AI](https://platform.moonshot.ai/) (Kimi K2) provider for the official [Laravel AI SDK](https://github.com/laravel/ai).

Moonshot's API is OpenAI-compatible (`POST https://api.moonshot.ai/v1/chat/completions`), so this driver plugs directly into the SDK's `TextProvider` / `TextGateway` contracts and behaves like any first-party provider — `agent()`, `Ai::textProvider()`, agent classes with `#[Provider]` attributes, streaming, tool calling, broadcasting, and queued prompts all work out of the box.

Features
--------

[](#features)

- ✅ Text generation (`prompt()`)
- ✅ Streaming responses (`stream()`, `broadcast()`, `broadcastOnQueue()`)
- ✅ Tool calling (function calling)
- ✅ Structured outputs (schema-constrained JSON via `response_format: json_schema`, strict mode)
- ✅ Image attachments (base64, remote URL, local file, stored disk, `UploadedFile`)
- ✅ Document Q&amp;A via Moonshot Files API (PDF, DOC, XLSX, PPTX, …) — server-side OCR + extraction
- ✅ Kimi **thinking mode** with `reasoning_content` deltas surfaced as `ReasoningStart` / `ReasoningDelta` / `ReasoningEnd` stream events
- ✅ Multi-turn reasoning persistence (`thinking.keep = all`)
- ✅ Per-tier model overrides (`default`, `cheapest`, `smartest`)
- ✅ Custom base URL (proxy / self-hosted compatible)
- ✅ PHPStan level max, Pest 3 / 4, Pint, Rector — full quality pipeline

Capability matrix
-----------------

[](#capability-matrix)

CapabilityStatusText generationSupportedStreamingSupportedTool callingSupported — function tools + Moonshot's `$web_search` builtin (via `WebSearch` ProviderTool)Image inputSupportedDocument Q&amp;ASupported via Moonshot Files APIThinking mode (Kimi reasoning)SupportedStructured outputSupported — `response_format: json_schema`, strict mode (MFJS)Provider tools (file search, web fetch, …)Not supported (web search supported separately)EmbeddingsNot supportedImage generation / audio / transcription / rerankingNot supported> **Limitations:** no embeddings, no image generation, no audio/transcription/reranking, no provider-hosted tools other than Moonshot's `$web_search` builtin (see [Web search](#web-search)), and documents must use the Moonshot Files API (`withMoonshotFile()` / `MoonshotFiles`) instead of Laravel AI generic `Document` attachments. See [Not supported](#not-supported) for details.

> **Package maturity:** this package tracks the evolving `laravel/ai` SDK, which is still on `0.x`. New `laravel/ai` minor versions are adopted only after a compatibility review and ship as a minor release here. See [Versioning](#versioning).

Requirements
------------

[](#requirements)

RequirementVersionPHP`^8.4` (8.4, 8.5)Laravel`12.x | 13.x``laravel/ai``~0.6.3``laravel/ai 0.6.x` requires `illuminate/* ^12.0|^13.0`, so Laravel 11 is not supported. CI exercises every PHP × Laravel combination above on each push and PR.

Installation
------------

[](#installation)

```
composer require jonaspauleta/laravel-ai-moonshot
```

The service provider is auto-discovered. There are no migrations or config files to publish — configuration lives in your application's existing `config/ai.php`.

60-second quick start
---------------------

[](#60-second-quick-start)

```
composer require jonaspauleta/laravel-ai-moonshot
```

```
# .env
MOONSHOT_API_KEY=sk-...
```

```
// config/ai.php
'providers' => [
    'moonshot' => [
        'driver' => 'moonshot',
        'name' => 'moonshot',
        'key' => env('MOONSHOT_API_KEY'),
    ],
],
```

```
use function Laravel\Ai\agent;

$response = agent('You are a helpful assistant.')
    ->prompt('Explain Moonshot Kimi K2 in one sentence.', provider: 'moonshot');

echo $response->text;
```

That's the minimum. See [Configuration](#configuration) for per-tier model overrides, custom base URL, and making Moonshot the default provider.

Configuration
-------------

[](#configuration)

Add your API key to `.env`:

```
MOONSHOT_API_KEY=sk-...
```

Register the provider in `config/ai.php`:

```
'providers' => [
    // ...

    'moonshot' => [
        'driver' => 'moonshot',
        'name' => 'moonshot',
        'key' => env('MOONSHOT_API_KEY'),

        // Optional. Defaults shown.
        'url' => env('MOONSHOT_URL', 'https://api.moonshot.ai/v1'),

        // Optional per-tier model overrides.
        'models' => [
            'text' => [
                'default'  => 'kimi-k2.6',
                'cheapest' => 'kimi-k2.5',
                'smartest' => 'kimi-k2.6',
            ],
        ],
    ],
],
```

To make Moonshot the default provider for the whole application:

```
'default' => env('AI_PROVIDER', 'moonshot'),
```

### Configuration reference

[](#configuration-reference)

KeyTypeRequiredDefaultDescription`driver``string`yes—Must be `'moonshot'`. Resolves the provider in `AiManager`.`name``string`yes—Display name returned by `Provider::name()`.`key``string`yes—Your Moonshot API key.`url``string`no`https://api.moonshot.ai/v1`API base URL. Override for proxies or regional endpoints.`models.text.default``string`no`kimi-k2.6`Used by `Provider::defaultTextModel()`.`models.text.cheapest``string`no`kimi-k2.5`Used by `Provider::cheapestTextModel()` and the `#[UseCheapestModel]` attribute.`models.text.smartest``string`no`kimi-k2.6`Used by `Provider::smartestTextModel()` and the `#[UseSmartestModel]` attribute. There is no separate thinking SKU — enable thinking per-call via `providerOptions`.How it works
------------

[](#how-it-works)

This package ships a single Laravel AI SDK provider — `MoonshotProvider` — backed by `MoonshotGateway`, a `TextGateway` that calls Moonshot's OpenAI-compatible `POST /v1/chat/completions` endpoint. Registration happens in `MoonshotServiceProvider::boot()` via `AiManager::extend('moonshot', …)`, so the driver string `'moonshot'` resolves to a real provider anywhere the SDK looks one up (`agent(provider: 'moonshot')`, `Ai::textProvider('moonshot')`, the `#[Provider('moonshot')]` attribute, etc.).

Streaming reads the SSE body chunk-by-chunk and maps Moonshot's payload to the SDK's stream events:

- `delta.content` → `TextStart` / `TextDelta` / `TextEnd`
- `delta.reasoning_content` → `ReasoningStart` / `ReasoningDelta` / `ReasoningEnd` (Kimi thinking mode)
- `delta.tool_calls` → buffered, then `ToolCall` + `ToolResult` after `finish_reason: tool_calls`
- `usage` chunk → `StreamEnd` payload

`ReasoningEnd` is guaranteed to fire before the first `TextStart` when the model transitions out of thinking. Multi-step tool loops are continued internally up to `TextGenerationOptions::$maxSteps` (default `ceil(count(tools) * 1.5)`).

Usage
-----

[](#usage)

### Quick start (ad-hoc agent)

[](#quick-start-ad-hoc-agent)

```
use function Laravel\Ai\agent;

$response = agent('You are a helpful assistant.')
    ->prompt('Explain Moonshot Kimi K2 in one sentence.', provider: 'moonshot');

echo $response->text;
```

### Agent class

[](#agent-class)

Generate one with `php artisan make:agent RaceEngineer` (the generator ships with `laravel/ai`), or write it by hand:

```
namespace App\Ai\Agents;

use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;
use Stringable;

#[Provider('moonshot')]
class RaceEngineer implements Agent, Conversational, HasTools
{
    use Promptable;

    public function instructions(): Stringable|string
    {
        return 'You are a Formula 1 race engineer. Be concise and technical.';
    }

    public function messages(): iterable
    {
        return [];
    }

    public function tools(): iterable
    {
        return [];
    }
}
```

```
$response = RaceEngineer::make()
    ->prompt('Best dry setup for the Nordschleife in a GT3?');

echo $response->text;
```

### Streaming

[](#streaming)

```
$stream = RaceEngineer::make()
    ->stream('Walk me through a flying lap at Spa.', provider: 'moonshot');

foreach ($stream as $event) {
    // TextStart, TextDelta, TextEnd, ReasoningStart, ReasoningDelta, ReasoningEnd, ...
}
```

### Broadcasting (Echo / Reverb)

[](#broadcasting-echo--reverb)

```
use Illuminate\Broadcasting\PrivateChannel;

RaceEngineer::make()
    ->broadcastOnQueue(
        'What is the best setup for the Nordschleife?',
        new PrivateChannel("agent.{$user->id}.{$requestId}"),
        provider: 'moonshot',
        model: 'kimi-k2.6',
    );
```

### Tool calling

[](#tool-calling)

```
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;

class GetWeather implements Tool
{
    public function description(): string
    {
        return 'Get the current weather for a city.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'city' => $schema->string()->description('City name')->required(),
        ];
    }

    public function handle(Request $request): string
    {
        $city = $request->string('city');

        return "Sunny, 24°C in {$city}.";
    }
}
```

```
use function Laravel\Ai\agent;

$response = agent(
    instructions: 'Use tools when needed.',
    tools: [new GetWeather],
)->prompt('What is the weather in Lisbon?', provider: 'moonshot');
```

### Structured output

[](#structured-output)

Moonshot supports OpenAI-compatible Structured Outputs on `kimi-k2.5` and `kimi-k2.6`. When a schema is provided, the gateway sends `response_format: { type: 'json_schema', json_schema: { name, schema, strict: true } }` and the response is decoded into a `StructuredTextResponse` whose `->structured` is an array matching the schema.

Implement `HasStructuredOutput` on an agent:

```
namespace App\Ai\Agents;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;

#[Provider('moonshot')]
final class ExtractPerson implements Agent, HasStructuredOutput
{
    use Promptable;

    public function instructions(): string
    {
        return 'Extract the person mentioned in the text.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'name' => $schema->string()->description('Full name')->required(),
            'age' => $schema->integer()->required(),
        ];
    }
}
```

```
$response = ExtractPerson::make()
    ->prompt('Ada Lovelace was 36 when she died.');

$response->structured; // ['name' => 'Ada Lovelace', 'age' => 36]
```

Or for one-offs, use `StructuredAnonymousAgent` directly (see the [Laravel AI SDK docs](https://laravel.com/docs/13.x/ai-sdk#structured-output)).

> **MFJS limitations.** Moonshot's strict mode requires [MFJS](https://github.com/MoonshotAI/walle/blob/main/docs/mfjs-spec.zh.md) — a JSON-Schema subset. Unsupported keywords include `format`, `pattern`, `oneOf`, `allOf`, `minLength`/`maxLength`, `minimum`/`maximum`, `title`, `$comment`, and `prefixItems`. Schemas are passed through verbatim — Moonshot returns HTTP 400 on incompatible keywords.

> **Streaming.** Streaming + schema also sends `response_format: json_schema`. The SDK does not emit `ObjectDelta` events — accumulate `TextDelta`s and `json_decode` at `StreamEnd`. Non-streaming returns a decoded `StructuredTextResponse` automatically.

### Web search

[](#web-search)

Moonshot ships a server-side web search builtin (`$web_search`) that you can drop into any Moonshot agent by passing the SDK's generic `Laravel\Ai\Providers\Tools\WebSearch` provider tool. The gateway translates it to Moonshot's `builtin_function` payload and auto-replies to the model's `$web_search` tool\_call with the model's own arguments — Kimi runs the actual search server-side.

```
use function Laravel\Ai\agent;
use Laravel\Ai\Providers\Tools\WebSearch;

$response = agent(
    instructions: 'Cite the sources you find on the web.',
    tools: [new WebSearch],
)->prompt('Latest F1 driver standings.', provider: 'moonshot');
```

Streaming works the same way — the `$web_search` round-trip emits a `ToolCall` event followed by a `ToolResult` event whose `result` is the echoed argument JSON.

> **Caveats**
>
> - The SDK's `WebSearch` knobs — `maxSearches`, `allowedDomains`, `location()` — are silently dropped. Kimi exposes no client-side configuration for the builtin.
> - Other `ProviderTool` subclasses (`WebFetch`, `FileSearch`, …) still throw `UnsupportedProviderToolException`. Moonshot has no equivalent builtins.
> - See the [Kimi web search guide](https://platform.kimi.ai/docs/guide/use-web-search) for billing and rate-limit details — `$web_search` invocations consume search-count tokens on top of normal completion usage.

### Image attachments

[](#image-attachments)

```
use Laravel\Ai\Files\RemoteImage;
use function Laravel\Ai\agent;

$response = agent('Describe the image.')
    ->prompt(
        prompt: 'What do you see?',
        attachments: [new RemoteImage('https://example.com/photo.jpg')],
        provider: 'moonshot',
    );
```

Supported attachment types: `Base64Image`, `RemoteImage`, `LocalImage`, `StoredImage`, and `Illuminate\Http\UploadedFile` (when the MIME type is `image/jpeg|png|gif|webp`). Document attachments are **not** supported through the SDK's generic `Document` contract — use the Files API below instead.

### Document Q&amp;A (PDF, DOC, XLSX, …)

[](#document-qa-pdf-doc-xlsx-)

Moonshot's `chat/completions` endpoint does **not** accept document attachments. Document Q&amp;A goes through Moonshot's separate Files API at `POST /v1/files`, which performs server-side text extraction (including OCR for scanned PDFs) and returns the extracted text. That text is then injected as a leading prompt block on subsequent chat completions.

Supported formats include `.pdf`, `.txt`, `.csv`, `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, `.md`, `.json`, `.html`, `.epub`, plus most code/config formats. See Moonshot's [official format list](https://platform.kimi.ai/docs/api/files) for the full catalog.

#### Ergonomic API: `withMoonshotFile()`

[](#ergonomic-api-withmoonshotfile)

Add the `InjectsMoonshotFiles` trait to any agent class that already uses `Promptable`. Implement `HasMiddleware` so the trait's middleware can prepend the extracted document text to the user prompt:

```
namespace App\Ai\Agents;

use Jonaspauleta\LaravelAiMoonshot\Concerns\InjectsMoonshotFiles;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasMiddleware;
use Laravel\Ai\Promptable;

#[Provider('moonshot')]
final class ContractAnalyst implements Agent, HasMiddleware
{
    use Promptable;
    use InjectsMoonshotFiles;

    public function instructions(): string
    {
        return 'You analyze contracts and surface risky clauses.';
    }
}

$response = ContractAnalyst::make()
    ->withMoonshotFile('storage/app/contracts/2026-renewal.pdf')
    ->withMoonshotFile($uploadedFile, label: 'Refund Policy')
    ->prompt('What are the riskiest clauses?');
```

Each `withMoonshotFile()` call uploads the file, fetches its extracted text, and appends a labelled block (`Document: \n`) that the trait's middleware prepends to your next prompt. Call it as many times as you need; ordering is preserved.

If your agent already implements `HasMiddleware` and overrides `middleware()`, alias the trait method and merge:

```
use InjectsMoonshotFiles {
    middleware as moonshotFilesMiddlewareDefault;
}

public function middleware(): array
{
    return [...$this->moonshotFilesMiddlewareDefault(), new MyOtherMiddleware()];
}
```

#### Lower-level service: `MoonshotFiles`

[](#lower-level-service-moonshotfiles)

When you need control over the file lifecycle (listing, deleting, retrieving extracted content directly), inject the `MoonshotFiles` service:

```
use Jonaspauleta\LaravelAiMoonshot\Files\MoonshotFiles;

$files = app(MoonshotFiles::class);

$file = $files->upload('contract.pdf');
$text = $files->content($file->id);

// Later: list / delete
foreach ($files->list() as $entry) {
    $files->delete($entry->id);
}
```

Run `php artisan ai:moonshot:files` to inspect uploaded files; `php artisan ai:moonshot:files --delete=` to remove them.

#### Limits

[](#limits)

- **1,000 files** per account
- **100 MB** maximum per file (the service rejects oversize uploads before the network call)
- **10 GB** total storage across all uploads
- Extraction is currently free, but rate-limited at peak usage

#### Security: treat document content as untrusted input

[](#security-treat-document-content-as-untrusted-input)

Extracted document text is **untrusted user input** — a malicious PDF can attempt prompt injection by mimicking system instructions. The `withMoonshotFile()` ergonomic API mitigates this by prefixing each block with `Document: ` so the model can distinguish your trusted instructions from document text.

The official Moonshot documentation injects extracted content as a `system` message. The Laravel AI SDK's `MessageRole` enum has no `system` case (the `instructions()` slot is reserved), so this package prepends the document block to the user prompt instead. The `Document: ` framing is what makes the prompt-injection mitigation work — keep the labels meaningful and avoid putting model-controlled strings in them.

### Thinking mode

[](#thinking-mode)

There is no separate thinking model. `kimi-k2.6` and `kimi-k2.5` both expose reasoning via the `thinking` request parameter. The `smartest` tier defaults to `kimi-k2.6` with thinking enabled at call-site via `HasProviderOptions`.

When thinking is enabled, Kimi streams chain-of-thought through `reasoning_content` deltas. This package surfaces them as standard Laravel AI SDK stream events — `ReasoningStart` → `ReasoningDelta` → `ReasoningEnd` — and guarantees `ReasoningEnd` fires before the first `TextStart`.

Pass Moonshot's native `thinking` payload by implementing `HasProviderOptions` on your agent. The gateway merges the array into the request body verbatim:

```
namespace App\Ai\Agents;

use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasProviderOptions;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;
use Stringable;

#[Provider('moonshot')]
class ThinkingAgent implements Agent, Conversational, HasProviderOptions, HasTools
{
    use Promptable;

    public function instructions(): Stringable|string
    {
        return 'Think step by step before answering.';
    }

    public function messages(): iterable
    {
        return [];
    }

    public function tools(): iterable
    {
        return [];
    }

    public function providerOptions(Lab|string $provider): array
    {
        return $provider === 'moonshot'
            // type: 'enabled' | 'disabled'
            // keep: 'all' — kimi-k2.6 only, preserves reasoning across multi-turn conversations
            ? ['thinking' => ['type' => 'enabled', 'keep' => 'all']]
            : [];
    }
}
```

> **Note:** the `keep: 'all'` flag is `kimi-k2.6`-specific. For one-off thinking on `kimi-k2.5`, send `['thinking' => ['type' => 'enabled']]` without `keep`.

Models
------

[](#models)

The defaults track Moonshot's [public model catalog](https://platform.moonshot.ai/docs/api/chat). Override per-tier in `config/ai.php` if Moonshot renames models or you want to pin a specific snapshot.

TierDefault IDUsed byDefault`kimi-k2.6``Provider::defaultTextModel()`Cheapest`kimi-k2.5``#[UseCheapestModel]`, `cheapestTextModel()`Smartest`kimi-k2.6``#[UseSmartestModel]`, `smartestTextModel()`. Same SKU as `default` — Moonshot has no separate thinking model. Enable thinking per-call via `providerOptions(['thinking' => ['type' => 'enabled']])`.Run `php artisan ai:moonshot:models` against a configured environment to print the live catalog (model IDs, context length, image/video/reasoning support). Pass `--json` for the raw response.

You can also pass an explicit model per call: `->prompt('...', model: 'kimi-k2.6')`.

Not supported
-------------

[](#not-supported)

The Moonshot API does not expose endpoints for the following capabilities at the time of release; this package will throw or fail-fast rather than fake them:

- **Embeddings** — Moonshot has no embeddings endpoint.
- **Image generation, audio, transcription, reranking** — text only.
- **Provider tools** other than `WebSearch` (e.g. `WebFetch`, `FileSearch`) — throws `UnsupportedProviderToolException` if passed. `WebSearch` itself is supported via Moonshot's `$web_search` builtin (see [Web search](#web-search)).
- **Document attachments via the SDK's generic `Document` contract** — use `withMoonshotFile()` or the `MoonshotFiles` service instead, which goes through Moonshot's `/v1/files` extraction endpoint (see [Document Q&amp;A](#document-qa-pdf-doc-xlsx-)).

Troubleshooting
---------------

[](#troubleshooting)

SymptomCause / fix`Driver [moonshot] is not supported.``MoonshotServiceProvider` did not boot. Ensure auto-discovery is on, or register manually in `config/app.php` `providers`.HTTP 401 `Invalid API key``MOONSHOT_API_KEY` missing or wrong. Verify with `dd(config('ai.providers.moonshot.key'))` after a `php artisan config:clear`.HTTP 400 `model not found` for `kimi-k2.6` (or any default tier)Moonshot renamed or retired the default. Pin a working model under `config/ai.php` `providers.moonshot.models.text.{default,cheapest,smartest}`.`UnsupportedProviderToolException: Moonshot does not support [WebFetch] provider tools.`You passed an unsupported `ProviderTool` subclass (e.g. `WebFetch`, `FileSearch`). For web search use `Laravel\Ai\Providers\Tools\WebSearch` (mapped to Moonshot's `$web_search` builtin). For everything else, use plain function tools.Document attachment via SDK's generic `Document` is silently ignored or rejectedGeneric `Document` attachments are intentionally unsupported. Use `withMoonshotFile()` (or the `MoonshotFiles` service) — this routes through Moonshot's Files API, which performs server-side extraction. See [Document Q&amp;A](#document-qa-pdf-doc-xlsx-).HTTP 400 with structured outputs (e.g. `unsupported keyword`)Moonshot's strict mode requires [MFJS](https://github.com/MoonshotAI/walle/blob/main/docs/mfjs-spec.zh.md) — a JSON-Schema subset. Drop unsupported keywords (`format`, `pattern`, `oneOf`, `allOf`, `minLength`/`maxLength`, etc.) from your schema. See [Structured output](#structured-output).Streaming hangs on long thinking-mode responsesDefault per-request timeout is 60s. Pass `timeout:` to `streamText()` or raise it in the gateway's HTTP client invocation.`Http::fake()` in tests does not intercept the requestFake key must include the full base URL: `'api.moonshot.ai/v1/chat/completions' => Http::response(...)`. The Laravel HTTP client applies the base URL.`MoonshotFilesException: ... extraction failed for [file-...]`Moonshot could not extract text from the uploaded file. Check the file is one of the [supported formats](https://platform.kimi.ai/docs/api/files) and not corrupted. Inspect status via `php artisan ai:moonshot:files`.`MoonshotFilesException: Moonshot Files API quota exceeded`You hit the 1,000-files / 10 GB account limit, or per-minute rate limit at peak. Delete unused files via `php artisan ai:moonshot:files --delete=`.`MoonshotFilesException: File of N bytes exceeds Moonshot Files API limit of 104857600`The file is larger than 100 MB. Split it or extract relevant pages client-side before uploading.Testing
-------

[](#testing)

```
composer test          # Pest
composer analyse       # PHPStan level max
composer format        # Pint
composer quality       # rector + pint + phpstan + pest
```

CI runs Pint, Rector (dry-run), PHPStan, and Pest on every push and PR — see [`.github/workflows/run-tests.yml`](.github/workflows/run-tests.yml).

Versioning
----------

[](#versioning)

This package follows [Semantic Versioning](https://semver.org/), but the upstream `laravel/ai` SDK is still on `0.x` — its minor bumps may include breaking changes. To keep our SemVer promise honest:

- We pin to a specific `laravel/ai` minor: `composer.json` requires `~0.6.3` (allows `0.6.x` patches but **not** `0.7.0`).
- Each new `laravel/ai` minor (`0.7`, `0.8`, …) lands in this package as one of our minor releases after a compatibility audit.
- Truly breaking changes here (renaming public classes, removing a public method, dropping a PHP version) bump our **major**.

If `laravel/ai` reaches `1.0.0` we will widen the constraint to `^1.0` and follow standard SemVer ranges.

Changelog
---------

[](#changelog)

See [CHANGELOG.md](CHANGELOG.md) for release notes. The project follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Versioning](https://semver.org/).

Contributing
------------

[](#contributing)

Contributions are welcome. Please:

1. Open an issue first for larger changes so we can discuss the approach.
2. Run `composer quality` before pushing — CI will fail otherwise.
3. Use [Conventional Commits](https://www.conventionalcommits.org/) for commit messages.
4. Add Pest tests for new behavior. `tests/Feature/MoonshotStreamTest.php` is a good template for HTTP-faked gateway tests.

Security
--------

[](#security)

If you discover a security vulnerability, please email **** instead of opening a public issue. You'll get a response within 48 hours. See [SECURITY.md](SECURITY.md) for the full policy.

Credits
-------

[](#credits)

- [João Paulo Santos](https://github.com/jonaspauleta)
- The [Laravel AI SDK](https://github.com/laravel/ai) team — `MoonshotGateway` is modeled directly on the SDK's first-party `DeepSeekGateway`.

License
-------

[](#license)

The MIT License (MIT). See [LICENSE.md](LICENSE.md).

###  Health Score

50

—

FairBetter than 95% of packages

Maintenance96

Actively maintained with recent releases

Popularity23

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity58

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 97.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

11

Last Release

21d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/90ea1ae13f99b523c480be342a9c89e9ca9d5c324ca4fe81f7c954d9fa7de997?d=identicon)[jonaspauleta](/maintainers/jonaspauleta)

---

Top Contributors

[![jonaspauleta](https://avatars.githubusercontent.com/u/18901856?v=4)](https://github.com/jonaspauleta "jonaspauleta (40 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

agentaichat-completiongenerative-aikimikimi-k2laravellaravel-ailaravel-ai-sdkllmmoonshotopenai-compatiblephpreasoningstreamingthinking-modetool-callinglaravelaistreamingAgentllmlaravel-ai-sdkgenerative-ailaravel-aikimimoonshottool-callingopenai-compatiblekimi-k2chat-completionreasoningthinking-mode

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/jonaspauleta-laravel-ai-moonshot/health.svg)

```
[![Health](https://phpackages.com/badges/jonaspauleta-laravel-ai-moonshot/health.svg)](https://phpackages.com/packages/jonaspauleta-laravel-ai-moonshot)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.4k](/packages/larastan-larastan)[defstudio/telegraph

A laravel facade to interact with Telegram Bots

815320.5k3](/packages/defstudio-telegraph)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

76318.2M110](/packages/laravel-mcp)[laravel/ai

The official AI SDK for Laravel.

9782.1M153](/packages/laravel-ai)[simplestats-io/laravel-client

Analytics for Laravel. Track visitors, registrations, and payments. Discover which channels actually drive revenue, not just traffic. Server-side, GDPR compliant, ad-blocker proof.

5019.3k](/packages/simplestats-io-laravel-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
