@providerprotocol/ai 0.0.4 → 0.0.6
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/README.md +19 -0
- package/dist/anthropic/index.js +1 -24
- package/dist/anthropic/index.js.map +1 -1
- package/dist/google/index.js +3 -46
- package/dist/google/index.js.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/ollama/index.js +13 -44
- package/dist/ollama/index.js.map +1 -1
- package/dist/openai/index.d.ts +46 -27
- package/dist/openai/index.js +2 -116
- package/dist/openai/index.js.map +1 -1
- package/dist/openrouter/index.d.ts +23 -10
- package/dist/openrouter/index.js +2 -85
- package/dist/openrouter/index.js.map +1 -1
- package/dist/xai/index.d.ts +306 -0
- package/dist/xai/index.js +1696 -0
- package/dist/xai/index.js.map +1 -0
- package/package.json +9 -1
- package/src/core/llm.ts +6 -1
- package/src/openai/index.ts +2 -1
- package/src/openrouter/index.ts +2 -1
- package/src/providers/anthropic/transform.ts +7 -29
- package/src/providers/google/transform.ts +9 -49
- package/src/providers/ollama/transform.ts +27 -49
- package/src/providers/openai/index.ts +12 -8
- package/src/providers/openai/llm.completions.ts +9 -9
- package/src/providers/openai/llm.responses.ts +9 -9
- package/src/providers/openai/transform.completions.ts +12 -79
- package/src/providers/openai/transform.responses.ts +12 -54
- package/src/providers/openai/types.ts +54 -31
- package/src/providers/openrouter/index.ts +12 -8
- package/src/providers/openrouter/llm.completions.ts +9 -9
- package/src/providers/openrouter/llm.responses.ts +9 -9
- package/src/providers/openrouter/transform.completions.ts +12 -79
- package/src/providers/openrouter/transform.responses.ts +12 -25
- package/src/providers/openrouter/types.ts +22 -28
- package/src/providers/xai/index.ts +223 -0
- package/src/providers/xai/llm.completions.ts +201 -0
- package/src/providers/xai/llm.messages.ts +195 -0
- package/src/providers/xai/llm.responses.ts +211 -0
- package/src/providers/xai/transform.completions.ts +565 -0
- package/src/providers/xai/transform.messages.ts +448 -0
- package/src/providers/xai/transform.responses.ts +678 -0
- package/src/providers/xai/types.ts +938 -0
- package/src/xai/index.ts +41 -0
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
isToolResultMessage,
|
|
12
12
|
} from '../../types/messages.ts';
|
|
13
13
|
import type {
|
|
14
|
-
|
|
14
|
+
OpenRouterCompletionsParams,
|
|
15
15
|
OpenRouterCompletionsRequest,
|
|
16
16
|
OpenRouterCompletionsMessage,
|
|
17
17
|
OpenRouterUserContent,
|
|
@@ -23,94 +23,30 @@ import type {
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Transform UPP request to OpenRouter Chat Completions format
|
|
26
|
+
*
|
|
27
|
+
* Params are spread directly to allow pass-through of any OpenRouter API fields,
|
|
28
|
+
* even those not explicitly defined in our type. This enables developers to
|
|
29
|
+
* use new API features without waiting for library updates.
|
|
26
30
|
*/
|
|
27
|
-
export function transformRequest
|
|
28
|
-
request: LLMRequest<
|
|
31
|
+
export function transformRequest(
|
|
32
|
+
request: LLMRequest<OpenRouterCompletionsParams>,
|
|
29
33
|
modelId: string
|
|
30
34
|
): OpenRouterCompletionsRequest {
|
|
31
|
-
const params
|
|
35
|
+
const params = request.params ?? ({} as OpenRouterCompletionsParams);
|
|
32
36
|
|
|
37
|
+
// Spread params to pass through all fields, then set required fields
|
|
33
38
|
const openrouterRequest: OpenRouterCompletionsRequest = {
|
|
39
|
+
...params,
|
|
34
40
|
model: modelId,
|
|
35
41
|
messages: transformMessages(request.messages, request.system),
|
|
36
42
|
};
|
|
37
43
|
|
|
38
|
-
//
|
|
39
|
-
if (params.temperature !== undefined) {
|
|
40
|
-
openrouterRequest.temperature = params.temperature;
|
|
41
|
-
}
|
|
42
|
-
if (params.top_p !== undefined) {
|
|
43
|
-
openrouterRequest.top_p = params.top_p;
|
|
44
|
-
}
|
|
45
|
-
if (params.top_k !== undefined) {
|
|
46
|
-
openrouterRequest.top_k = params.top_k;
|
|
47
|
-
}
|
|
48
|
-
if (params.min_p !== undefined) {
|
|
49
|
-
openrouterRequest.min_p = params.min_p;
|
|
50
|
-
}
|
|
51
|
-
if (params.top_a !== undefined) {
|
|
52
|
-
openrouterRequest.top_a = params.top_a;
|
|
53
|
-
}
|
|
54
|
-
if (params.max_tokens !== undefined) {
|
|
55
|
-
openrouterRequest.max_tokens = params.max_tokens;
|
|
56
|
-
}
|
|
57
|
-
if (params.frequency_penalty !== undefined) {
|
|
58
|
-
openrouterRequest.frequency_penalty = params.frequency_penalty;
|
|
59
|
-
}
|
|
60
|
-
if (params.presence_penalty !== undefined) {
|
|
61
|
-
openrouterRequest.presence_penalty = params.presence_penalty;
|
|
62
|
-
}
|
|
63
|
-
if (params.repetition_penalty !== undefined) {
|
|
64
|
-
openrouterRequest.repetition_penalty = params.repetition_penalty;
|
|
65
|
-
}
|
|
66
|
-
if (params.stop !== undefined) {
|
|
67
|
-
openrouterRequest.stop = params.stop;
|
|
68
|
-
}
|
|
69
|
-
if (params.logprobs !== undefined) {
|
|
70
|
-
openrouterRequest.logprobs = params.logprobs;
|
|
71
|
-
}
|
|
72
|
-
if (params.top_logprobs !== undefined) {
|
|
73
|
-
openrouterRequest.top_logprobs = params.top_logprobs;
|
|
74
|
-
}
|
|
75
|
-
if (params.seed !== undefined) {
|
|
76
|
-
openrouterRequest.seed = params.seed;
|
|
77
|
-
}
|
|
78
|
-
if (params.user !== undefined) {
|
|
79
|
-
openrouterRequest.user = params.user;
|
|
80
|
-
}
|
|
81
|
-
if (params.logit_bias !== undefined) {
|
|
82
|
-
openrouterRequest.logit_bias = params.logit_bias;
|
|
83
|
-
}
|
|
84
|
-
if (params.prediction !== undefined) {
|
|
85
|
-
openrouterRequest.prediction = params.prediction;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// OpenRouter-specific parameters
|
|
89
|
-
if (params.transforms !== undefined) {
|
|
90
|
-
openrouterRequest.transforms = params.transforms;
|
|
91
|
-
}
|
|
92
|
-
if (params.models !== undefined) {
|
|
93
|
-
openrouterRequest.models = params.models;
|
|
94
|
-
}
|
|
95
|
-
if (params.route !== undefined) {
|
|
96
|
-
openrouterRequest.route = params.route;
|
|
97
|
-
}
|
|
98
|
-
if (params.provider !== undefined) {
|
|
99
|
-
openrouterRequest.provider = params.provider;
|
|
100
|
-
}
|
|
101
|
-
if (params.debug !== undefined) {
|
|
102
|
-
openrouterRequest.debug = params.debug;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Tools
|
|
44
|
+
// Tools come from request, not params
|
|
106
45
|
if (request.tools && request.tools.length > 0) {
|
|
107
46
|
openrouterRequest.tools = request.tools.map(transformTool);
|
|
108
|
-
if (params.parallel_tool_calls !== undefined) {
|
|
109
|
-
openrouterRequest.parallel_tool_calls = params.parallel_tool_calls;
|
|
110
|
-
}
|
|
111
47
|
}
|
|
112
48
|
|
|
113
|
-
// Structured output via response_format
|
|
49
|
+
// Structured output via response_format (overrides params.response_format if set)
|
|
114
50
|
if (request.structure) {
|
|
115
51
|
const schema: Record<string, unknown> = {
|
|
116
52
|
type: 'object',
|
|
@@ -133,9 +69,6 @@ export function transformRequest<TParams extends OpenRouterLLMParams>(
|
|
|
133
69
|
strict: true,
|
|
134
70
|
},
|
|
135
71
|
};
|
|
136
|
-
} else if (params.response_format !== undefined) {
|
|
137
|
-
// Pass through response_format from params if no structure is defined
|
|
138
|
-
openrouterRequest.response_format = params.response_format;
|
|
139
72
|
}
|
|
140
73
|
|
|
141
74
|
return openrouterRequest;
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
isToolResultMessage,
|
|
12
12
|
} from '../../types/messages.ts';
|
|
13
13
|
import type {
|
|
14
|
-
|
|
14
|
+
OpenRouterResponsesParams,
|
|
15
15
|
OpenRouterResponsesRequest,
|
|
16
16
|
OpenRouterResponsesInputItem,
|
|
17
17
|
OpenRouterResponsesContentPart,
|
|
@@ -25,43 +25,30 @@ import type {
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Transform UPP request to OpenRouter Responses API format
|
|
28
|
+
*
|
|
29
|
+
* Params are spread directly to allow pass-through of any OpenRouter API fields,
|
|
30
|
+
* even those not explicitly defined in our type. This enables developers to
|
|
31
|
+
* use new API features without waiting for library updates.
|
|
28
32
|
*/
|
|
29
|
-
export function transformRequest
|
|
30
|
-
request: LLMRequest<
|
|
33
|
+
export function transformRequest(
|
|
34
|
+
request: LLMRequest<OpenRouterResponsesParams>,
|
|
31
35
|
modelId: string
|
|
32
36
|
): OpenRouterResponsesRequest {
|
|
33
|
-
const params
|
|
37
|
+
const params = request.params ?? ({} as OpenRouterResponsesParams);
|
|
34
38
|
|
|
39
|
+
// Spread params to pass through all fields, then set required fields
|
|
35
40
|
const openrouterRequest: OpenRouterResponsesRequest = {
|
|
41
|
+
...params,
|
|
36
42
|
model: modelId,
|
|
37
43
|
input: transformInputItems(request.messages, request.system),
|
|
38
44
|
};
|
|
39
45
|
|
|
40
|
-
//
|
|
41
|
-
if (params.temperature !== undefined) {
|
|
42
|
-
openrouterRequest.temperature = params.temperature;
|
|
43
|
-
}
|
|
44
|
-
if (params.top_p !== undefined) {
|
|
45
|
-
openrouterRequest.top_p = params.top_p;
|
|
46
|
-
}
|
|
47
|
-
if (params.max_output_tokens !== undefined) {
|
|
48
|
-
openrouterRequest.max_output_tokens = params.max_output_tokens;
|
|
49
|
-
} else if (params.max_tokens !== undefined) {
|
|
50
|
-
openrouterRequest.max_output_tokens = params.max_tokens;
|
|
51
|
-
}
|
|
52
|
-
if (params.reasoning !== undefined) {
|
|
53
|
-
openrouterRequest.reasoning = { ...params.reasoning };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Tools
|
|
46
|
+
// Tools come from request, not params
|
|
57
47
|
if (request.tools && request.tools.length > 0) {
|
|
58
48
|
openrouterRequest.tools = request.tools.map(transformTool);
|
|
59
|
-
if (params.parallel_tool_calls !== undefined) {
|
|
60
|
-
openrouterRequest.parallel_tool_calls = params.parallel_tool_calls;
|
|
61
|
-
}
|
|
62
49
|
}
|
|
63
50
|
|
|
64
|
-
// Structured output via text.format
|
|
51
|
+
// Structured output via text.format (overrides params.text if set)
|
|
65
52
|
if (request.structure) {
|
|
66
53
|
const schema: Record<string, unknown> = {
|
|
67
54
|
type: 'object',
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenRouter
|
|
3
|
-
* These are passed through to the
|
|
2
|
+
* OpenRouter Chat Completions API parameters
|
|
3
|
+
* These are passed through to the /api/v1/chat/completions endpoint
|
|
4
4
|
*/
|
|
5
|
-
export interface
|
|
6
|
-
// ============================================
|
|
7
|
-
// Common Parameters (both APIs)
|
|
8
|
-
// ============================================
|
|
9
|
-
|
|
5
|
+
export interface OpenRouterCompletionsParams {
|
|
10
6
|
/** Maximum number of tokens to generate */
|
|
11
7
|
max_tokens?: number;
|
|
12
8
|
|
|
13
|
-
/** Maximum output tokens (Responses API) */
|
|
14
|
-
max_output_tokens?: number;
|
|
15
|
-
|
|
16
9
|
/** Temperature for randomness (0.0 - 2.0) */
|
|
17
10
|
temperature?: number;
|
|
18
11
|
|
|
@@ -55,24 +48,12 @@ export interface OpenRouterLLMParams {
|
|
|
55
48
|
/** Top-a sampling threshold (0.0 - 1.0) */
|
|
56
49
|
top_a?: number;
|
|
57
50
|
|
|
58
|
-
// ============================================
|
|
59
|
-
// Tool Calling
|
|
60
|
-
// ============================================
|
|
61
|
-
|
|
62
51
|
/** Whether to enable parallel tool calls */
|
|
63
52
|
parallel_tool_calls?: boolean;
|
|
64
53
|
|
|
65
|
-
|
|
66
|
-
// Structured Output
|
|
67
|
-
// ============================================
|
|
68
|
-
|
|
69
|
-
/** Response format for structured output (Chat Completions API only) */
|
|
54
|
+
/** Response format for structured output */
|
|
70
55
|
response_format?: OpenRouterResponseFormat;
|
|
71
56
|
|
|
72
|
-
// ============================================
|
|
73
|
-
// OpenRouter-Specific Parameters
|
|
74
|
-
// ============================================
|
|
75
|
-
|
|
76
57
|
/**
|
|
77
58
|
* Prompt transforms to apply
|
|
78
59
|
* See: https://openrouter.ai/docs/guides/features/message-transforms
|
|
@@ -98,7 +79,6 @@ export interface OpenRouterLLMParams {
|
|
|
98
79
|
|
|
99
80
|
/**
|
|
100
81
|
* Predicted output for latency optimization
|
|
101
|
-
* https://platform.openai.com/docs/guides/latency-optimization#use-predicted-outputs
|
|
102
82
|
*/
|
|
103
83
|
prediction?: {
|
|
104
84
|
type: 'content';
|
|
@@ -112,13 +92,27 @@ export interface OpenRouterLLMParams {
|
|
|
112
92
|
/** If true, returns the transformed request body sent to the provider */
|
|
113
93
|
echo_upstream_body?: boolean;
|
|
114
94
|
};
|
|
95
|
+
}
|
|
115
96
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
97
|
+
/**
|
|
98
|
+
* OpenRouter Responses API parameters (Beta)
|
|
99
|
+
* These are passed through to the /api/v1/responses endpoint
|
|
100
|
+
*/
|
|
101
|
+
export interface OpenRouterResponsesParams {
|
|
102
|
+
/** Maximum output tokens */
|
|
103
|
+
max_output_tokens?: number;
|
|
104
|
+
|
|
105
|
+
/** Temperature for randomness (0.0 - 2.0) */
|
|
106
|
+
temperature?: number;
|
|
107
|
+
|
|
108
|
+
/** Top-p (nucleus) sampling (0.0 - 1.0) */
|
|
109
|
+
top_p?: number;
|
|
110
|
+
|
|
111
|
+
/** Whether to enable parallel tool calls */
|
|
112
|
+
parallel_tool_calls?: boolean;
|
|
119
113
|
|
|
120
114
|
/**
|
|
121
|
-
* Reasoning configuration
|
|
115
|
+
* Reasoning configuration
|
|
122
116
|
*/
|
|
123
117
|
reasoning?: {
|
|
124
118
|
effort?: 'low' | 'medium' | 'high';
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Provider,
|
|
3
|
+
ModelReference,
|
|
4
|
+
LLMHandler,
|
|
5
|
+
LLMProvider,
|
|
6
|
+
} from '../../types/provider.ts';
|
|
7
|
+
import { createCompletionsLLMHandler } from './llm.completions.ts';
|
|
8
|
+
import { createResponsesLLMHandler } from './llm.responses.ts';
|
|
9
|
+
import { createMessagesLLMHandler } from './llm.messages.ts';
|
|
10
|
+
import type { XAICompletionsParams, XAIResponsesParams, XAIMessagesParams, XAIConfig, XAIAPIMode } from './types.ts';
|
|
11
|
+
|
|
12
|
+
/** Union type for modalities interface */
|
|
13
|
+
type XAILLMParamsUnion = XAICompletionsParams | XAIResponsesParams | XAIMessagesParams;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* xAI provider options
|
|
17
|
+
*/
|
|
18
|
+
export interface XAIProviderOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Which API to use:
|
|
21
|
+
* - 'completions': Chat Completions API (OpenAI-compatible, default)
|
|
22
|
+
* - 'responses': Responses API (OpenAI Responses-compatible, stateful)
|
|
23
|
+
* - 'messages': Messages API (Anthropic-compatible)
|
|
24
|
+
*/
|
|
25
|
+
api?: XAIAPIMode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* xAI provider with configurable API mode
|
|
30
|
+
*
|
|
31
|
+
* xAI's APIs are compatible with OpenAI and Anthropic SDKs, supporting three API modes:
|
|
32
|
+
* - Chat Completions API (OpenAI-compatible) - default, recommended
|
|
33
|
+
* - Responses API (OpenAI Responses-compatible) - stateful conversations
|
|
34
|
+
* - Messages API (Anthropic-compatible) - for migration from Anthropic
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // Using the Chat Completions API (default)
|
|
38
|
+
* const model = xai('grok-4');
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // Using the Responses API (stateful)
|
|
42
|
+
* const model = xai('grok-4', { api: 'responses' });
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* // Using the Messages API (Anthropic-compatible)
|
|
46
|
+
* const model = xai('grok-4', { api: 'messages' });
|
|
47
|
+
*/
|
|
48
|
+
export interface XAIProvider extends Provider<XAIProviderOptions> {
|
|
49
|
+
/**
|
|
50
|
+
* Create a model reference
|
|
51
|
+
* @param modelId - The model identifier (e.g., 'grok-4', 'grok-4.1-fast', 'grok-3-mini')
|
|
52
|
+
* @param options - Provider options including API selection
|
|
53
|
+
*/
|
|
54
|
+
(modelId: string, options?: XAIProviderOptions): ModelReference<XAIProviderOptions>;
|
|
55
|
+
|
|
56
|
+
/** Provider name */
|
|
57
|
+
readonly name: 'xai';
|
|
58
|
+
|
|
59
|
+
/** Provider version */
|
|
60
|
+
readonly version: string;
|
|
61
|
+
|
|
62
|
+
/** Supported modalities */
|
|
63
|
+
readonly modalities: {
|
|
64
|
+
llm: LLMHandler<XAILLMParamsUnion>;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create the xAI provider
|
|
70
|
+
*/
|
|
71
|
+
function createXAIProvider(): XAIProvider {
|
|
72
|
+
// Track which API mode is currently active for the modalities
|
|
73
|
+
// Default to 'completions' (recommended for most use cases)
|
|
74
|
+
let currentApiMode: XAIAPIMode = 'completions';
|
|
75
|
+
|
|
76
|
+
// Create handlers eagerly so we can inject provider reference
|
|
77
|
+
const completionsHandler = createCompletionsLLMHandler();
|
|
78
|
+
const responsesHandler = createResponsesLLMHandler();
|
|
79
|
+
const messagesHandler = createMessagesLLMHandler();
|
|
80
|
+
|
|
81
|
+
const fn = function (
|
|
82
|
+
modelId: string,
|
|
83
|
+
options?: XAIProviderOptions
|
|
84
|
+
): ModelReference<XAIProviderOptions> {
|
|
85
|
+
const apiMode = options?.api ?? 'completions';
|
|
86
|
+
currentApiMode = apiMode;
|
|
87
|
+
return { modelId, provider };
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Create a dynamic modalities object that returns the correct handler
|
|
91
|
+
const modalities = {
|
|
92
|
+
get llm(): LLMHandler<XAILLMParamsUnion> {
|
|
93
|
+
switch (currentApiMode) {
|
|
94
|
+
case 'responses':
|
|
95
|
+
return responsesHandler as unknown as LLMHandler<XAILLMParamsUnion>;
|
|
96
|
+
case 'messages':
|
|
97
|
+
return messagesHandler as unknown as LLMHandler<XAILLMParamsUnion>;
|
|
98
|
+
case 'completions':
|
|
99
|
+
default:
|
|
100
|
+
return completionsHandler as unknown as LLMHandler<XAILLMParamsUnion>;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Define properties
|
|
106
|
+
Object.defineProperties(fn, {
|
|
107
|
+
name: {
|
|
108
|
+
value: 'xai',
|
|
109
|
+
writable: false,
|
|
110
|
+
configurable: true,
|
|
111
|
+
},
|
|
112
|
+
version: {
|
|
113
|
+
value: '1.0.0',
|
|
114
|
+
writable: false,
|
|
115
|
+
configurable: true,
|
|
116
|
+
},
|
|
117
|
+
modalities: {
|
|
118
|
+
value: modalities,
|
|
119
|
+
writable: false,
|
|
120
|
+
configurable: true,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const provider = fn as XAIProvider;
|
|
125
|
+
|
|
126
|
+
// Inject provider reference into all handlers (spec compliance)
|
|
127
|
+
completionsHandler._setProvider?.(provider as unknown as LLMProvider<XAICompletionsParams>);
|
|
128
|
+
responsesHandler._setProvider?.(provider as unknown as LLMProvider<XAIResponsesParams>);
|
|
129
|
+
messagesHandler._setProvider?.(provider as unknown as LLMProvider<XAIMessagesParams>);
|
|
130
|
+
|
|
131
|
+
return provider;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* xAI provider
|
|
136
|
+
*
|
|
137
|
+
* Supports three API modes:
|
|
138
|
+
* - Chat Completions API (default, OpenAI-compatible)
|
|
139
|
+
* - Responses API (stateful, OpenAI Responses-compatible)
|
|
140
|
+
* - Messages API (Anthropic-compatible)
|
|
141
|
+
*
|
|
142
|
+
* xAI's Grok models support:
|
|
143
|
+
* - Real-time search via Live Search API (deprecated Dec 2025) or Agent Tools API
|
|
144
|
+
* - Reasoning with `reasoning_effort` parameter (for Grok 3 Mini)
|
|
145
|
+
* - Tool/function calling
|
|
146
|
+
* - Image input
|
|
147
|
+
* - Streaming responses
|
|
148
|
+
* - Structured output (JSON mode)
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* import { xai } from './providers/xai';
|
|
153
|
+
* import { llm } from './core/llm';
|
|
154
|
+
*
|
|
155
|
+
* // Using Chat Completions API (default, recommended)
|
|
156
|
+
* const model = llm({
|
|
157
|
+
* model: xai('grok-4'),
|
|
158
|
+
* params: { max_tokens: 1000 }
|
|
159
|
+
* });
|
|
160
|
+
*
|
|
161
|
+
* // Using Responses API (stateful conversations)
|
|
162
|
+
* const statefulModel = llm({
|
|
163
|
+
* model: xai('grok-4', { api: 'responses' }),
|
|
164
|
+
* params: {
|
|
165
|
+
* max_output_tokens: 1000,
|
|
166
|
+
* store: true, // Enable stateful storage
|
|
167
|
+
* }
|
|
168
|
+
* });
|
|
169
|
+
*
|
|
170
|
+
* // Continue a previous conversation
|
|
171
|
+
* const continuedModel = llm({
|
|
172
|
+
* model: xai('grok-4', { api: 'responses' }),
|
|
173
|
+
* params: {
|
|
174
|
+
* previous_response_id: 'resp_123...',
|
|
175
|
+
* }
|
|
176
|
+
* });
|
|
177
|
+
*
|
|
178
|
+
* // Using Messages API (Anthropic-compatible)
|
|
179
|
+
* const anthropicModel = llm({
|
|
180
|
+
* model: xai('grok-4', { api: 'messages' }),
|
|
181
|
+
* params: { max_tokens: 1000 }
|
|
182
|
+
* });
|
|
183
|
+
*
|
|
184
|
+
* // Using reasoning effort (Grok 3 Mini only)
|
|
185
|
+
* const reasoningModel = llm({
|
|
186
|
+
* model: xai('grok-3-mini'),
|
|
187
|
+
* params: {
|
|
188
|
+
* max_tokens: 1000,
|
|
189
|
+
* reasoning_effort: 'high', // 'low' or 'high'
|
|
190
|
+
* }
|
|
191
|
+
* });
|
|
192
|
+
*
|
|
193
|
+
* // Using Live Search (deprecated Dec 2025)
|
|
194
|
+
* const searchModel = llm({
|
|
195
|
+
* model: xai('grok-4'),
|
|
196
|
+
* params: {
|
|
197
|
+
* max_tokens: 1000,
|
|
198
|
+
* search_parameters: {
|
|
199
|
+
* mode: 'auto',
|
|
200
|
+
* sources: ['web', 'x', 'news'],
|
|
201
|
+
* }
|
|
202
|
+
* }
|
|
203
|
+
* });
|
|
204
|
+
*
|
|
205
|
+
* // Generate
|
|
206
|
+
* const turn = await model.generate('Hello!');
|
|
207
|
+
* console.log(turn.response.text);
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
export const xai = createXAIProvider();
|
|
211
|
+
|
|
212
|
+
// Re-export types
|
|
213
|
+
export type {
|
|
214
|
+
XAICompletionsParams,
|
|
215
|
+
XAIResponsesParams,
|
|
216
|
+
XAIMessagesParams,
|
|
217
|
+
XAIConfig,
|
|
218
|
+
XAIAPIMode,
|
|
219
|
+
XAIModelOptions,
|
|
220
|
+
XAIModelReference,
|
|
221
|
+
XAISearchParameters,
|
|
222
|
+
XAIAgentTool,
|
|
223
|
+
} from './types.ts';
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { LLMHandler, BoundLLMModel, LLMRequest, LLMResponse, LLMStreamResult, LLMCapabilities } from '../../types/llm.ts';
|
|
2
|
+
import type { StreamEvent } from '../../types/stream.ts';
|
|
3
|
+
import type { LLMProvider } from '../../types/provider.ts';
|
|
4
|
+
import { UPPError } from '../../types/errors.ts';
|
|
5
|
+
import { resolveApiKey } from '../../http/keys.ts';
|
|
6
|
+
import { doFetch, doStreamFetch } from '../../http/fetch.ts';
|
|
7
|
+
import { parseSSEStream } from '../../http/sse.ts';
|
|
8
|
+
import { normalizeHttpError } from '../../http/errors.ts';
|
|
9
|
+
import type { XAICompletionsParams, XAICompletionsResponse, XAICompletionsStreamChunk } from './types.ts';
|
|
10
|
+
import {
|
|
11
|
+
transformRequest,
|
|
12
|
+
transformResponse,
|
|
13
|
+
transformStreamEvent,
|
|
14
|
+
createStreamState,
|
|
15
|
+
buildResponseFromState,
|
|
16
|
+
} from './transform.completions.ts';
|
|
17
|
+
|
|
18
|
+
const XAI_COMPLETIONS_API_URL = 'https://api.x.ai/v1/chat/completions';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* xAI Chat Completions API capabilities
|
|
22
|
+
*/
|
|
23
|
+
const XAI_COMPLETIONS_CAPABILITIES: LLMCapabilities = {
|
|
24
|
+
streaming: true,
|
|
25
|
+
tools: true,
|
|
26
|
+
structuredOutput: true,
|
|
27
|
+
imageInput: true,
|
|
28
|
+
videoInput: false,
|
|
29
|
+
audioInput: false,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create xAI Chat Completions LLM handler
|
|
34
|
+
*/
|
|
35
|
+
export function createCompletionsLLMHandler(): LLMHandler<XAICompletionsParams> {
|
|
36
|
+
// Provider reference injected by createProvider() or xAI's custom factory
|
|
37
|
+
let providerRef: LLMProvider<XAICompletionsParams> | null = null;
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
_setProvider(provider: LLMProvider<XAICompletionsParams>) {
|
|
41
|
+
providerRef = provider;
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
bind(modelId: string): BoundLLMModel<XAICompletionsParams> {
|
|
45
|
+
// Use the injected provider reference
|
|
46
|
+
if (!providerRef) {
|
|
47
|
+
throw new UPPError(
|
|
48
|
+
'Provider reference not set. Handler must be used with createProvider() or have _setProvider called.',
|
|
49
|
+
'INVALID_REQUEST',
|
|
50
|
+
'xai',
|
|
51
|
+
'llm'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const model: BoundLLMModel<XAICompletionsParams> = {
|
|
56
|
+
modelId,
|
|
57
|
+
capabilities: XAI_COMPLETIONS_CAPABILITIES,
|
|
58
|
+
|
|
59
|
+
get provider(): LLMProvider<XAICompletionsParams> {
|
|
60
|
+
return providerRef!;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
async complete(request: LLMRequest<XAICompletionsParams>): Promise<LLMResponse> {
|
|
64
|
+
const apiKey = await resolveApiKey(
|
|
65
|
+
request.config,
|
|
66
|
+
'XAI_API_KEY',
|
|
67
|
+
'xai',
|
|
68
|
+
'llm'
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const baseUrl = request.config.baseUrl ?? XAI_COMPLETIONS_API_URL;
|
|
72
|
+
const body = transformRequest(request, modelId);
|
|
73
|
+
|
|
74
|
+
const response = await doFetch(
|
|
75
|
+
baseUrl,
|
|
76
|
+
{
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: {
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
Authorization: `Bearer ${apiKey}`,
|
|
81
|
+
},
|
|
82
|
+
body: JSON.stringify(body),
|
|
83
|
+
signal: request.signal,
|
|
84
|
+
},
|
|
85
|
+
request.config,
|
|
86
|
+
'xai',
|
|
87
|
+
'llm'
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const data = (await response.json()) as XAICompletionsResponse;
|
|
91
|
+
return transformResponse(data);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
stream(request: LLMRequest<XAICompletionsParams>): LLMStreamResult {
|
|
95
|
+
const state = createStreamState();
|
|
96
|
+
let responseResolve: (value: LLMResponse) => void;
|
|
97
|
+
let responseReject: (error: Error) => void;
|
|
98
|
+
|
|
99
|
+
const responsePromise = new Promise<LLMResponse>((resolve, reject) => {
|
|
100
|
+
responseResolve = resolve;
|
|
101
|
+
responseReject = reject;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
async function* generateEvents(): AsyncGenerator<StreamEvent, void, unknown> {
|
|
105
|
+
try {
|
|
106
|
+
const apiKey = await resolveApiKey(
|
|
107
|
+
request.config,
|
|
108
|
+
'XAI_API_KEY',
|
|
109
|
+
'xai',
|
|
110
|
+
'llm'
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const baseUrl = request.config.baseUrl ?? XAI_COMPLETIONS_API_URL;
|
|
114
|
+
const body = transformRequest(request, modelId);
|
|
115
|
+
body.stream = true;
|
|
116
|
+
body.stream_options = { include_usage: true };
|
|
117
|
+
|
|
118
|
+
const response = await doStreamFetch(
|
|
119
|
+
baseUrl,
|
|
120
|
+
{
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: {
|
|
123
|
+
'Content-Type': 'application/json',
|
|
124
|
+
Authorization: `Bearer ${apiKey}`,
|
|
125
|
+
},
|
|
126
|
+
body: JSON.stringify(body),
|
|
127
|
+
signal: request.signal,
|
|
128
|
+
},
|
|
129
|
+
request.config,
|
|
130
|
+
'xai',
|
|
131
|
+
'llm'
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
const error = await normalizeHttpError(response, 'xai', 'llm');
|
|
136
|
+
responseReject(error);
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!response.body) {
|
|
141
|
+
const error = new UPPError(
|
|
142
|
+
'No response body for streaming request',
|
|
143
|
+
'PROVIDER_ERROR',
|
|
144
|
+
'xai',
|
|
145
|
+
'llm'
|
|
146
|
+
);
|
|
147
|
+
responseReject(error);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for await (const data of parseSSEStream(response.body)) {
|
|
152
|
+
// Skip [DONE] marker
|
|
153
|
+
if (data === '[DONE]') {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check for xAI error event
|
|
158
|
+
if (typeof data === 'object' && data !== null) {
|
|
159
|
+
const chunk = data as XAICompletionsStreamChunk;
|
|
160
|
+
|
|
161
|
+
// Check for error in chunk
|
|
162
|
+
if ('error' in chunk && chunk.error) {
|
|
163
|
+
const errorData = chunk.error as { message?: string; type?: string };
|
|
164
|
+
const error = new UPPError(
|
|
165
|
+
errorData.message ?? 'Unknown error',
|
|
166
|
+
'PROVIDER_ERROR',
|
|
167
|
+
'xai',
|
|
168
|
+
'llm'
|
|
169
|
+
);
|
|
170
|
+
responseReject(error);
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const uppEvents = transformStreamEvent(chunk, state);
|
|
175
|
+
for (const event of uppEvents) {
|
|
176
|
+
yield event;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Build final response
|
|
182
|
+
responseResolve(buildResponseFromState(state));
|
|
183
|
+
} catch (error) {
|
|
184
|
+
responseReject(error as Error);
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
[Symbol.asyncIterator]() {
|
|
191
|
+
return generateEvents();
|
|
192
|
+
},
|
|
193
|
+
response: responsePromise,
|
|
194
|
+
};
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return model;
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|