@ottocode/sdk 0.1.298 → 0.1.300

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.
@@ -107,7 +107,12 @@ export {
107
107
  export type { AnthropicOAuthConfig } from './anthropic-oauth-client.ts';
108
108
  export { createGoogleModel } from './google-client.ts';
109
109
  export type { GoogleProviderConfig } from './google-client.ts';
110
- export { createXaiModel } from './xai-client.ts';
110
+ export {
111
+ createXaiModel,
112
+ getGrokCliHeaders,
113
+ isXaiGrokCliModel,
114
+ XAI_GROK_CLI_MODEL_IDS,
115
+ } from './xai-client.ts';
111
116
  export type { XaiProviderConfig } from './xai-client.ts';
112
117
  export { createZaiModel, createZaiCodingModel } from './zai-client.ts';
113
118
  export type { ZaiProviderConfig } from './zai-client.ts';
@@ -1,5 +1,6 @@
1
1
  import { getGlobalConfigDir, joinPath } from '../../config/src/paths.ts';
2
2
  import type { ModelInfo, ProviderId } from '../../types/src/index.ts';
3
+ import { appendXaiGrokCliModels } from './catalog-manual.ts';
3
4
 
4
5
  export type CachedProviderCatalogEntry = {
5
6
  id: ProviderId;
@@ -69,6 +70,10 @@ export function normalizeModelCatalogPayload(
69
70
  const entry = normalizeProviderEntry(id, value);
70
71
  if (entry) providers[id] = entry;
71
72
  }
73
+ const xaiEntry = appendXaiGrokCliModels(providers.xai);
74
+ if (xaiEntry) {
75
+ providers.xai = xaiEntry;
76
+ }
72
77
  return providers;
73
78
  }
74
79
 
@@ -26,7 +26,17 @@ const OAUTH_MODEL_IDS: Partial<Record<ProviderId, string[]>> = {
26
26
  ],
27
27
  };
28
28
 
29
+ const OAUTH_ONLY_MODEL_IDS: Partial<Record<ProviderId, string[]>> = {
30
+ xai: ['grok-build', 'grok-composer-2.5-fast'],
31
+ };
32
+
33
+ function isOAuthOnlyModel(provider: ProviderId, modelId: string): boolean {
34
+ return OAUTH_ONLY_MODEL_IDS[provider]?.includes(modelId) === true;
35
+ }
36
+
29
37
  function matchesOAuthModel(provider: ProviderId, modelId: string): boolean {
38
+ if (isOAuthOnlyModel(provider, modelId)) return true;
39
+
30
40
  const exactIds = OAUTH_MODEL_IDS[provider];
31
41
  if (exactIds?.includes(modelId)) return true;
32
42
 
@@ -48,7 +58,9 @@ export function filterModelsForAuthType(
48
58
  models: ModelInfo[],
49
59
  authType: 'api' | 'oauth' | 'wallet' | undefined,
50
60
  ): ModelInfo[] {
51
- if (authType !== 'oauth') return models;
61
+ if (authType !== 'oauth') {
62
+ return models.filter((model) => !isOAuthOnlyModel(provider, model.id));
63
+ }
52
64
  const exactIds = OAUTH_MODEL_IDS[provider];
53
65
  const prefixes = OAUTH_MODEL_PREFIXES[provider];
54
66
  if (!exactIds && !prefixes) return models;
@@ -139,6 +139,7 @@ const OWNER_TO_FAMILY: Record<ModelOwner, UnderlyingProviderKey> = {
139
139
  openrouter: 'openai-compatible',
140
140
  xai: 'openai',
141
141
  moonshot: 'moonshot',
142
+ qwen: 'openai-compatible',
142
143
  zai: 'glm',
143
144
  minimax: 'minimax',
144
145
  };
@@ -180,6 +181,8 @@ function inferFromModelId(model: string): UnderlyingProviderKey {
180
181
  return 'openai';
181
182
  if (lower.includes('gemini') || lower.startsWith('google/')) return 'google';
182
183
  if (lower.includes('grok') || lower.startsWith('xai/')) return 'openai';
184
+ if (lower.includes('qwen') || lower.startsWith('qwen/'))
185
+ return 'openai-compatible';
183
186
  if (lower.includes('kimi') || lower.startsWith('moonshotai/'))
184
187
  return 'moonshot';
185
188
  if (
@@ -11,6 +11,7 @@ import {
11
11
  export type CapabilityRequest = {
12
12
  wantsToolCalls?: boolean;
13
13
  wantsVision?: boolean; // input image
14
+ allowUnknownModel?: boolean;
14
15
  };
15
16
 
16
17
  export function validateProviderModel(
@@ -41,7 +42,10 @@ export function validateProviderModel(
41
42
  });
42
43
  return;
43
44
  }
44
- if (!providerAllowsAnyModel(cfg, providerId)) {
45
+ if (
46
+ !effectiveCap?.allowUnknownModel &&
47
+ !providerAllowsAnyModel(cfg, providerId)
48
+ ) {
45
49
  const knownModels = definition.models.length
46
50
  ? definition.models
47
51
  : cachedModels;
@@ -1,15 +1,53 @@
1
1
  import { createXai } from '@ai-sdk/xai';
2
2
  import { catalog } from './catalog-merged.ts';
3
3
 
4
+ const XAI_GROK_CLI_BASE_URL = 'https://cli-chat-proxy.grok.com/v1';
5
+ const XAI_GROK_CLI_CLIENT_VERSION = '0.2.22';
6
+
7
+ export const XAI_GROK_CLI_MODEL_IDS = [
8
+ 'grok-build',
9
+ 'grok-composer-2.5-fast',
10
+ ] as const;
11
+
4
12
  export type XaiProviderConfig = {
5
13
  apiKey?: string;
6
14
  baseURL?: string;
7
15
  useResponses?: boolean;
16
+ useGrokCliProxy?: boolean;
8
17
  };
9
18
 
19
+ export function isXaiGrokCliModel(model: string): boolean {
20
+ const normalized = model.toLowerCase().split('/').pop() || model;
21
+ return XAI_GROK_CLI_MODEL_IDS.some((id) => id === normalized);
22
+ }
23
+
24
+ function getGrokCliPlatform(): string {
25
+ const platform = globalThis.process?.platform;
26
+ if (platform === 'darwin') return 'macos';
27
+ return platform || 'unknown';
28
+ }
29
+
30
+ function getGrokCliArch(): string {
31
+ const arch = globalThis.process?.arch;
32
+ if (arch === 'arm64') return 'aarch64';
33
+ return arch || 'unknown';
34
+ }
35
+
36
+ export function getGrokCliHeaders(model: string): Record<string, string> {
37
+ return {
38
+ 'x-xai-token-auth': 'xai-grok-cli',
39
+ 'x-authenticateresponse': 'authenticate-response',
40
+ 'x-grok-client-version': XAI_GROK_CLI_CLIENT_VERSION,
41
+ 'x-grok-client-identifier': 'grok-shell',
42
+ 'x-grok-model-override': model,
43
+ 'user-agent': `grok-shell/${XAI_GROK_CLI_CLIENT_VERSION} (${getGrokCliPlatform()}; ${getGrokCliArch()})`,
44
+ };
45
+ }
46
+
10
47
  function shouldUseXaiResponsesApi(model: string): boolean {
11
48
  const normalized = model.toLowerCase().split('/').pop() || model;
12
49
  return (
50
+ isXaiGrokCliModel(normalized) ||
13
51
  normalized === 'grok-4.3' ||
14
52
  normalized === 'grok-build-0.1' ||
15
53
  normalized.startsWith('grok-4.20-')
@@ -19,9 +57,18 @@ function shouldUseXaiResponsesApi(model: string): boolean {
19
57
  export function createXaiModel(model: string, config?: XaiProviderConfig) {
20
58
  const entry = catalog.xai;
21
59
  const apiKey = config?.apiKey || process.env.XAI_API_KEY || '';
22
- const baseURL = config?.baseURL || entry?.api;
23
- const instance = createXai({ apiKey, baseURL });
24
- if (config?.useResponses ?? shouldUseXaiResponsesApi(model)) {
60
+ const baseURL =
61
+ config?.baseURL ||
62
+ (config?.useGrokCliProxy ? XAI_GROK_CLI_BASE_URL : entry?.api);
63
+ const instance = createXai({
64
+ apiKey,
65
+ baseURL,
66
+ headers: config?.useGrokCliProxy ? getGrokCliHeaders(model) : undefined,
67
+ });
68
+ if (
69
+ config?.useGrokCliProxy ||
70
+ (config?.useResponses ?? shouldUseXaiResponsesApi(model))
71
+ ) {
25
72
  return instance.responses(model);
26
73
  }
27
74
  return instance(model);
@@ -67,6 +67,7 @@ export type ModelOwner =
67
67
  | 'openrouter'
68
68
  | 'xai'
69
69
  | 'moonshot'
70
+ | 'qwen'
70
71
  | 'zai'
71
72
  | 'minimax';
72
73
 
@@ -93,6 +94,12 @@ export type ModelInfo = {
93
94
  toolCall?: boolean;
94
95
  reasoningText?: boolean;
95
96
  attachment?: boolean;
97
+ /**
98
+ * Editing tool policy override for this model.
99
+ * Use structured for lower-end models that handle simple edit schemas better
100
+ * than free-form patch languages.
101
+ */
102
+ editToolCapability?: 'structured' | 'patch';
96
103
  temperature?: boolean | number;
97
104
  knowledge?: string;
98
105
  releaseDate?: string;