@moxxy/plugin-provider-xai 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +26 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +35 -0
- package/dist/models.js.map +1 -0
- package/package.json +64 -0
- package/src/index.test.ts +120 -0
- package/src/index.ts +33 -0
- package/src/models.ts +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Moxxy (moxxy.ai)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { grokModels } from './models.js';
|
|
2
|
+
export { grokModels };
|
|
3
|
+
/**
|
|
4
|
+
* xAI (Grok). The xAI API speaks the OpenAI Chat Completions protocol, so this
|
|
5
|
+
* reuses the shared {@link defineOpenAICompatProvider} with the `xai` slug +
|
|
6
|
+
* base URL + Grok catalog forced on (so usage stats, provider events and error
|
|
7
|
+
* context attribute to `xai`, not `openai`).
|
|
8
|
+
*/
|
|
9
|
+
export declare const xaiProviderDef: import("@moxxy/sdk").ProviderDef;
|
|
10
|
+
export declare const xaiPlugin: import("@moxxy/sdk").Plugin;
|
|
11
|
+
export default xaiPlugin;
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,UAAU,EAAE,CAAC;AAKtB;;;;;GAKG;AACH,eAAO,MAAM,cAAc,kCASzB,CAAC;AAEH,eAAO,MAAM,SAAS,6BAIpB,CAAC;AAEH,eAAe,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { definePlugin } from '@moxxy/sdk';
|
|
2
|
+
import { defineOpenAICompatProvider } from '@moxxy/plugin-provider-openai';
|
|
3
|
+
import { grokModels } from './models.js';
|
|
4
|
+
export { grokModels };
|
|
5
|
+
const XAI_BASE_URL = 'https://api.x.ai/v1';
|
|
6
|
+
const XAI_DEFAULT_MODEL = 'grok-4';
|
|
7
|
+
/**
|
|
8
|
+
* xAI (Grok). The xAI API speaks the OpenAI Chat Completions protocol, so this
|
|
9
|
+
* reuses the shared {@link defineOpenAICompatProvider} with the `xai` slug +
|
|
10
|
+
* base URL + Grok catalog forced on (so usage stats, provider events and error
|
|
11
|
+
* context attribute to `xai`, not `openai`).
|
|
12
|
+
*/
|
|
13
|
+
export const xaiProviderDef = defineOpenAICompatProvider({
|
|
14
|
+
name: 'xai',
|
|
15
|
+
baseURL: XAI_BASE_URL,
|
|
16
|
+
defaultModel: XAI_DEFAULT_MODEL,
|
|
17
|
+
models: grokModels,
|
|
18
|
+
auth: {
|
|
19
|
+
kind: 'apiKey',
|
|
20
|
+
hint: 'xAI API key (starts with `xai-`) from https://console.x.ai',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
export const xaiPlugin = definePlugin({
|
|
24
|
+
name: '@moxxy/plugin-provider-xai',
|
|
25
|
+
version: '0.0.0',
|
|
26
|
+
providers: [xaiProviderDef],
|
|
27
|
+
});
|
|
28
|
+
export default xaiPlugin;
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,MAAM,YAAY,GAAG,qBAAqB,CAAC;AAC3C,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AAEnC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,0BAA0B,CAAC;IACvD,IAAI,EAAE,KAAK;IACX,OAAO,EAAE,YAAY;IACrB,YAAY,EAAE,iBAAiB;IAC/B,MAAM,EAAE,UAAU;IAClB,IAAI,EAAE;QACJ,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,4DAA4D;KACnE;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC;IACpC,IAAI,EAAE,4BAA4B;IAClC,OAAO,EAAE,OAAO;IAChB,SAAS,EAAE,CAAC,cAAc,CAAC;CAC5B,CAAC,CAAC;AAEH,eAAe,SAAS,CAAC"}
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ModelDescriptor } from '@moxxy/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* xAI Grok model catalog, as of 2026-06 (verify against
|
|
4
|
+
* https://docs.x.ai/docs/models). The xAI API is OpenAI-compatible, so these
|
|
5
|
+
* stream through the shared {@link import('@moxxy/plugin-provider-openai').OpenAIProvider}.
|
|
6
|
+
* grok-4.3 is the current flagship with a 1M context; the grok-4 tier is 256k;
|
|
7
|
+
* grok-3 is 131k. Vision is available on the grok-4 family. An unlisted model
|
|
8
|
+
* id still works — it's passed straight through to api.x.ai; the catalog only
|
|
9
|
+
* drives context-window budgets and capability gating.
|
|
10
|
+
*
|
|
11
|
+
* The grok-4 family + grok-3-mini are reasoning models — xAI streams their
|
|
12
|
+
* `reasoning_content` deltas, which the shared OpenAIProvider already surfaces;
|
|
13
|
+
* supportsReasoning gates reasoning_effort + reasoning-stream surfacing on
|
|
14
|
+
* (without it the capability is dead upstream even when the user enables it).
|
|
15
|
+
*
|
|
16
|
+
* supportsDocuments is intentionally NOT set: the xAI API is reached through the
|
|
17
|
+
* OpenAI-compatibility surface, and (like the Gemini compat endpoint) it does
|
|
18
|
+
* not honor the OpenAI `file`/`file_data` content part the shared translate
|
|
19
|
+
* layer emits for `document` blocks. Asserting it would make the desktop ship
|
|
20
|
+
* raw PDF bytes the endpoint rejects/ignores — losing the document with no
|
|
21
|
+
* fallback. Leaving it unset keeps the safe extracted-text path. Images ride
|
|
22
|
+
* `image_url` data URLs (accepted), so supportsImages stays true on the vision
|
|
23
|
+
* tier.
|
|
24
|
+
*/
|
|
25
|
+
export declare const grokModels: ReadonlyArray<ModelDescriptor>;
|
|
26
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,UAAU,EAAE,aAAa,CAAC,eAAe,CAYrD,CAAC"}
|
package/dist/models.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xAI Grok model catalog, as of 2026-06 (verify against
|
|
3
|
+
* https://docs.x.ai/docs/models). The xAI API is OpenAI-compatible, so these
|
|
4
|
+
* stream through the shared {@link import('@moxxy/plugin-provider-openai').OpenAIProvider}.
|
|
5
|
+
* grok-4.3 is the current flagship with a 1M context; the grok-4 tier is 256k;
|
|
6
|
+
* grok-3 is 131k. Vision is available on the grok-4 family. An unlisted model
|
|
7
|
+
* id still works — it's passed straight through to api.x.ai; the catalog only
|
|
8
|
+
* drives context-window budgets and capability gating.
|
|
9
|
+
*
|
|
10
|
+
* The grok-4 family + grok-3-mini are reasoning models — xAI streams their
|
|
11
|
+
* `reasoning_content` deltas, which the shared OpenAIProvider already surfaces;
|
|
12
|
+
* supportsReasoning gates reasoning_effort + reasoning-stream surfacing on
|
|
13
|
+
* (without it the capability is dead upstream even when the user enables it).
|
|
14
|
+
*
|
|
15
|
+
* supportsDocuments is intentionally NOT set: the xAI API is reached through the
|
|
16
|
+
* OpenAI-compatibility surface, and (like the Gemini compat endpoint) it does
|
|
17
|
+
* not honor the OpenAI `file`/`file_data` content part the shared translate
|
|
18
|
+
* layer emits for `document` blocks. Asserting it would make the desktop ship
|
|
19
|
+
* raw PDF bytes the endpoint rejects/ignores — losing the document with no
|
|
20
|
+
* fallback. Leaving it unset keeps the safe extracted-text path. Images ride
|
|
21
|
+
* `image_url` data URLs (accepted), so supportsImages stays true on the vision
|
|
22
|
+
* tier.
|
|
23
|
+
*/
|
|
24
|
+
export const grokModels = [
|
|
25
|
+
// grok-4 family: current frontier. 4.3 is the flagship (1M context). Reasoning + vision.
|
|
26
|
+
{ id: 'grok-4.3', contextWindow: 1_000_000, maxOutputTokens: 64_000, supportsTools: true, supportsStreaming: true, supportsImages: true, supportsReasoning: true },
|
|
27
|
+
{ id: 'grok-4', contextWindow: 256_000, maxOutputTokens: 64_000, supportsTools: true, supportsStreaming: true, supportsImages: true, supportsReasoning: true },
|
|
28
|
+
{ id: 'grok-4-fast', contextWindow: 256_000, maxOutputTokens: 64_000, supportsTools: true, supportsStreaming: true, supportsImages: true, supportsReasoning: true },
|
|
29
|
+
// grok-code-fast-1: agentic-coding specialist (text-only). Reasoning model.
|
|
30
|
+
{ id: 'grok-code-fast-1', contextWindow: 256_000, maxOutputTokens: 64_000, supportsTools: true, supportsStreaming: true, supportsReasoning: true },
|
|
31
|
+
// grok-3 tier: prior generation, still served. Only the -mini variant reasons.
|
|
32
|
+
{ id: 'grok-3', contextWindow: 131_072, maxOutputTokens: 32_768, supportsTools: true, supportsStreaming: true },
|
|
33
|
+
{ id: 'grok-3-mini', contextWindow: 131_072, maxOutputTokens: 32_768, supportsTools: true, supportsStreaming: true, supportsReasoning: true },
|
|
34
|
+
];
|
|
35
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,UAAU,GAAmC;IACxD,yFAAyF;IACzF,EAAE,EAAE,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAClK,EAAE,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAC9J,EAAE,EAAE,EAAE,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAEnK,4EAA4E;IAC5E,EAAE,EAAE,EAAE,kBAAkB,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAElJ,+EAA+E;IAC/E,EAAE,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAC/G,EAAE,EAAE,EAAE,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;CAC9I,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@moxxy/plugin-provider-xai",
|
|
3
|
+
"version": "0.21.1",
|
|
4
|
+
"description": "xAI (Grok) LLMProvider plugin for moxxy. Streams the OpenAI-compatible xAI API (https://api.x.ai/v1).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"moxxy",
|
|
7
|
+
"agent",
|
|
8
|
+
"provider",
|
|
9
|
+
"xai",
|
|
10
|
+
"grok",
|
|
11
|
+
"llm"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://moxxy.ai",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/moxxy-ai/moxxy/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/moxxy-ai/moxxy.git",
|
|
20
|
+
"directory": "packages/plugin-provider-xai"
|
|
21
|
+
},
|
|
22
|
+
"author": "Michal Makowski <michal.makowski97@gmail.com>",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"src"
|
|
39
|
+
],
|
|
40
|
+
"moxxy": {
|
|
41
|
+
"plugin": {
|
|
42
|
+
"entry": "./dist/index.js",
|
|
43
|
+
"kind": "provider"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@moxxy/sdk": "0.21.1",
|
|
48
|
+
"@moxxy/plugin-provider-openai": "0.21.1"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^22.10.0",
|
|
52
|
+
"typescript": "^5.7.3",
|
|
53
|
+
"vitest": "^2.1.8",
|
|
54
|
+
"zod": "^3.24.0",
|
|
55
|
+
"@moxxy/tsconfig": "0.0.0",
|
|
56
|
+
"@moxxy/vitest-preset": "0.0.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsc -p tsconfig.json",
|
|
60
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"clean": "rm -rf dist .turbo"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { xaiPlugin, xaiProviderDef, grokModels } from './index.js';
|
|
3
|
+
|
|
4
|
+
describe('@moxxy/plugin-provider-xai', () => {
|
|
5
|
+
it('registers the xai provider', () => {
|
|
6
|
+
expect(xaiPlugin.providers?.map((p) => p.name)).toEqual(['xai']);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('advertises the Grok catalog (including the 1M-context grok-4.3 flagship)', () => {
|
|
10
|
+
expect(xaiProviderDef.models).toEqual(grokModels);
|
|
11
|
+
expect(grokModels.find((m) => m.id === 'grok-4.3')).toMatchObject({ contextWindow: 1_000_000 });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('createClient stamps the xai slug so usage/errors attribute to xAI, not openai', () => {
|
|
15
|
+
const client = xaiProviderDef.createClient({ apiKey: 'xai-test-key' });
|
|
16
|
+
expect(client.name).toBe('xai');
|
|
17
|
+
expect(client.models).toEqual(grokModels);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('exposes an apiKey auth descriptor', () => {
|
|
21
|
+
expect(xaiProviderDef.auth).toMatchObject({ kind: 'apiKey' });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('the reasoning-tier Grok models advertise supportsReasoning', () => {
|
|
25
|
+
// Reasoning gating (reasoning_effort + reasoning-stream surfacing) keys off
|
|
26
|
+
// this flag; absent it, the loop silently drops reasoning config even when
|
|
27
|
+
// the user enables it. The grok-4 family + grok-3-mini are reasoning models.
|
|
28
|
+
const reasoningIds = ['grok-4.3', 'grok-4', 'grok-4-fast', 'grok-code-fast-1', 'grok-3-mini'];
|
|
29
|
+
for (const id of reasoningIds) {
|
|
30
|
+
expect(grokModels.find((m) => m.id === id)?.supportsReasoning, id).toBe(true);
|
|
31
|
+
}
|
|
32
|
+
// The non-mini grok-3 is NOT a reasoning model — keep the flag off so the
|
|
33
|
+
// loop doesn't request reasoning the model won't honor.
|
|
34
|
+
expect(grokModels.find((m) => m.id === 'grok-3')?.supportsReasoning).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('every Grok descriptor carries a positive, in-window maxOutputTokens budget', () => {
|
|
38
|
+
// Without maxOutputTokens the context budgeter can't reserve completion
|
|
39
|
+
// space, so a near-full window + large generation gets server-truncated
|
|
40
|
+
// instead of pre-emptively elided.
|
|
41
|
+
for (const m of grokModels) {
|
|
42
|
+
expect(typeof m.maxOutputTokens, `${m.id} maxOutputTokens`).toBe('number');
|
|
43
|
+
expect(m.maxOutputTokens!, `${m.id} maxOutputTokens > 0`).toBeGreaterThan(0);
|
|
44
|
+
expect(m.maxOutputTokens!, `${m.id} maxOutputTokens <= contextWindow`).toBeLessThanOrEqual(m.contextWindow);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('every Grok descriptor is structurally well-formed (positive window, required flags, unique id)', () => {
|
|
49
|
+
// Lock the catalog invariants so a careless future edit can't silently ship
|
|
50
|
+
// a descriptor the SDK budgeter/capability-gating would mis-read.
|
|
51
|
+
const seen = new Set<string>();
|
|
52
|
+
for (const m of grokModels) {
|
|
53
|
+
expect(typeof m.id, 'id is a string').toBe('string');
|
|
54
|
+
expect(m.id.length, `${m.id} id non-empty`).toBeGreaterThan(0);
|
|
55
|
+
expect(seen.has(m.id), `${m.id} is not duplicated`).toBe(false);
|
|
56
|
+
seen.add(m.id);
|
|
57
|
+
expect(typeof m.contextWindow, `${m.id} contextWindow`).toBe('number');
|
|
58
|
+
expect(m.contextWindow, `${m.id} contextWindow > 0`).toBeGreaterThan(0);
|
|
59
|
+
expect(m.supportsTools, `${m.id} supportsTools`).toBe(true);
|
|
60
|
+
expect(m.supportsStreaming, `${m.id} supportsStreaming`).toBe(true);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('no Grok descriptor asserts supportsDocuments (OpenAI-compat endpoint rejects native file parts)', () => {
|
|
65
|
+
// Intentional: the xAI compat surface does not honor the OpenAI `file`/
|
|
66
|
+
// `file_data` content part the shared translate layer emits for `document`
|
|
67
|
+
// blocks. Asserting supportsDocuments would make the desktop ship raw PDF
|
|
68
|
+
// bytes the endpoint drops — losing the doc with no fallback. The safe path
|
|
69
|
+
// is the extracted-text route (supportsDocuments unset → text). Guard the
|
|
70
|
+
// decision so it can't be re-introduced without an explicit, tested choice.
|
|
71
|
+
for (const m of grokModels) {
|
|
72
|
+
expect(m.supportsDocuments, `${m.id} must not assert supportsDocuments`).toBeUndefined();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('createClient degrades to xAI defaults on hostile/wrong-typed config (no smuggled non-string fields)', () => {
|
|
77
|
+
// The registry hands createClient an untyped Record<string, unknown>. A
|
|
78
|
+
// wrong-typed baseURL/defaultModel (object/number) must NOT reach the OpenAI
|
|
79
|
+
// SDK (where a non-string baseURL would throw or silently mis-route); the
|
|
80
|
+
// narrowing drops them so the vendor defaults stand. A string apiKey is
|
|
81
|
+
// present (the SDK constructor requires a non-empty key) — everything else
|
|
82
|
+
// is hostile. The worst case here is degradation to defaults, never a crash.
|
|
83
|
+
const hostile = {
|
|
84
|
+
apiKey: 'xai-test-key',
|
|
85
|
+
baseURL: { evil: true },
|
|
86
|
+
defaultModel: 12345,
|
|
87
|
+
extraJunk: ['should', 'be', 'ignored'],
|
|
88
|
+
} as unknown as Record<string, unknown>;
|
|
89
|
+
const client = xaiProviderDef.createClient(hostile);
|
|
90
|
+
expect(client.name).toBe('xai');
|
|
91
|
+
expect(client.models).toEqual(grokModels);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('refuses an empty config rather than smuggling OPENAI_API_KEY to xai', () => {
|
|
95
|
+
// Worst case: the registry hands us {} (no key) while OPENAI_API_KEY is set in
|
|
96
|
+
// env. The OpenAI SDK ctor would silently fall back to it and ship the user's
|
|
97
|
+
// real OpenAI credential to api.x.ai — createClient MUST refuse, not smuggle.
|
|
98
|
+
const prev = process.env.OPENAI_API_KEY;
|
|
99
|
+
process.env.OPENAI_API_KEY = 'sk-env-fallback-for-test';
|
|
100
|
+
try {
|
|
101
|
+
expect(() => xaiProviderDef.createClient({})).toThrow(/requires an API key/);
|
|
102
|
+
} finally {
|
|
103
|
+
if (prev === undefined) delete process.env.OPENAI_API_KEY;
|
|
104
|
+
else process.env.OPENAI_API_KEY = prev;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('builds a working client when a real apiKey is supplied (no junk smuggled)', () => {
|
|
109
|
+
const client = xaiProviderDef.createClient({ apiKey: 'xai-test-key' });
|
|
110
|
+
expect(client.name).toBe('xai');
|
|
111
|
+
expect(client.models).toEqual(grokModels);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('exposes a validateKey probe (so a bad xAI key is caught before first stream)', () => {
|
|
115
|
+
// defineOpenAICompatProvider wires validateOpenAICompatKey against the xAI
|
|
116
|
+
// base URL unless validate:false. xai relies on the default, so the setup
|
|
117
|
+
// wizard / `moxxy login` can verify the key against api.x.ai/v1/models.
|
|
118
|
+
expect(typeof xaiProviderDef.validateKey).toBe('function');
|
|
119
|
+
});
|
|
120
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { definePlugin } from '@moxxy/sdk';
|
|
2
|
+
import { defineOpenAICompatProvider } from '@moxxy/plugin-provider-openai';
|
|
3
|
+
import { grokModels } from './models.js';
|
|
4
|
+
|
|
5
|
+
export { grokModels };
|
|
6
|
+
|
|
7
|
+
const XAI_BASE_URL = 'https://api.x.ai/v1';
|
|
8
|
+
const XAI_DEFAULT_MODEL = 'grok-4';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* xAI (Grok). The xAI API speaks the OpenAI Chat Completions protocol, so this
|
|
12
|
+
* reuses the shared {@link defineOpenAICompatProvider} with the `xai` slug +
|
|
13
|
+
* base URL + Grok catalog forced on (so usage stats, provider events and error
|
|
14
|
+
* context attribute to `xai`, not `openai`).
|
|
15
|
+
*/
|
|
16
|
+
export const xaiProviderDef = defineOpenAICompatProvider({
|
|
17
|
+
name: 'xai',
|
|
18
|
+
baseURL: XAI_BASE_URL,
|
|
19
|
+
defaultModel: XAI_DEFAULT_MODEL,
|
|
20
|
+
models: grokModels,
|
|
21
|
+
auth: {
|
|
22
|
+
kind: 'apiKey',
|
|
23
|
+
hint: 'xAI API key (starts with `xai-`) from https://console.x.ai',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const xaiPlugin = definePlugin({
|
|
28
|
+
name: '@moxxy/plugin-provider-xai',
|
|
29
|
+
version: '0.0.0',
|
|
30
|
+
providers: [xaiProviderDef],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export default xaiPlugin;
|
package/src/models.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ModelDescriptor } from '@moxxy/sdk';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* xAI Grok model catalog, as of 2026-06 (verify against
|
|
5
|
+
* https://docs.x.ai/docs/models). The xAI API is OpenAI-compatible, so these
|
|
6
|
+
* stream through the shared {@link import('@moxxy/plugin-provider-openai').OpenAIProvider}.
|
|
7
|
+
* grok-4.3 is the current flagship with a 1M context; the grok-4 tier is 256k;
|
|
8
|
+
* grok-3 is 131k. Vision is available on the grok-4 family. An unlisted model
|
|
9
|
+
* id still works — it's passed straight through to api.x.ai; the catalog only
|
|
10
|
+
* drives context-window budgets and capability gating.
|
|
11
|
+
*
|
|
12
|
+
* The grok-4 family + grok-3-mini are reasoning models — xAI streams their
|
|
13
|
+
* `reasoning_content` deltas, which the shared OpenAIProvider already surfaces;
|
|
14
|
+
* supportsReasoning gates reasoning_effort + reasoning-stream surfacing on
|
|
15
|
+
* (without it the capability is dead upstream even when the user enables it).
|
|
16
|
+
*
|
|
17
|
+
* supportsDocuments is intentionally NOT set: the xAI API is reached through the
|
|
18
|
+
* OpenAI-compatibility surface, and (like the Gemini compat endpoint) it does
|
|
19
|
+
* not honor the OpenAI `file`/`file_data` content part the shared translate
|
|
20
|
+
* layer emits for `document` blocks. Asserting it would make the desktop ship
|
|
21
|
+
* raw PDF bytes the endpoint rejects/ignores — losing the document with no
|
|
22
|
+
* fallback. Leaving it unset keeps the safe extracted-text path. Images ride
|
|
23
|
+
* `image_url` data URLs (accepted), so supportsImages stays true on the vision
|
|
24
|
+
* tier.
|
|
25
|
+
*/
|
|
26
|
+
export const grokModels: ReadonlyArray<ModelDescriptor> = [
|
|
27
|
+
// grok-4 family: current frontier. 4.3 is the flagship (1M context). Reasoning + vision.
|
|
28
|
+
{ id: 'grok-4.3', contextWindow: 1_000_000, maxOutputTokens: 64_000, supportsTools: true, supportsStreaming: true, supportsImages: true, supportsReasoning: true },
|
|
29
|
+
{ id: 'grok-4', contextWindow: 256_000, maxOutputTokens: 64_000, supportsTools: true, supportsStreaming: true, supportsImages: true, supportsReasoning: true },
|
|
30
|
+
{ id: 'grok-4-fast', contextWindow: 256_000, maxOutputTokens: 64_000, supportsTools: true, supportsStreaming: true, supportsImages: true, supportsReasoning: true },
|
|
31
|
+
|
|
32
|
+
// grok-code-fast-1: agentic-coding specialist (text-only). Reasoning model.
|
|
33
|
+
{ id: 'grok-code-fast-1', contextWindow: 256_000, maxOutputTokens: 64_000, supportsTools: true, supportsStreaming: true, supportsReasoning: true },
|
|
34
|
+
|
|
35
|
+
// grok-3 tier: prior generation, still served. Only the -mini variant reasons.
|
|
36
|
+
{ id: 'grok-3', contextWindow: 131_072, maxOutputTokens: 32_768, supportsTools: true, supportsStreaming: true },
|
|
37
|
+
{ id: 'grok-3-mini', contextWindow: 131_072, maxOutputTokens: 32_768, supportsTools: true, supportsStreaming: true, supportsReasoning: true },
|
|
38
|
+
];
|