egaki 0.4.1 → 0.6.0

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/dist/models.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import pc from 'picocolors';
2
- import { PROVIDERS, EGAKI_GATEWAY_URL, shouldUseChatGptBackend } from './credentials.js';
3
- import { CATALOG, findModel } from './model-catalog.js';
4
- import { VIDEO_CATALOG, findVideoModel } from './video-model-catalog.js';
2
+ import { PROVIDERS, EGAKI_GATEWAY_URL, shouldUseChatGptBackend, shouldUseXaiOAuth, getXaiAuth, saveXaiAuth, } from './credentials.js';
3
+ import { getValidXaiAuth } from './xai-auth.js';
4
+ import { CATALOG, VIDEO_CATALOG, findModel, findVideoModel, findAnyModel, } from './model-catalog.js';
5
5
  export const IMAGE_MODELS = CATALOG.map((m) => m.id);
6
6
  export const VIDEO_MODELS = VIDEO_CATALOG.map((m) => m.id);
7
7
  /**
@@ -17,36 +17,111 @@ function stripProviderPrefix(modelId) {
17
17
  export const DEFAULT_MODEL = 'nano-banana-pro-preview';
18
18
  export const DEFAULT_VIDEO_MODEL = 'veo-3.1-fast-generate-001';
19
19
  export function getModelConfig(modelId) {
20
- const entry = findModel(modelId) ?? findVideoModel(modelId);
20
+ const entry = findAnyModel(modelId);
21
21
  if (!entry) {
22
22
  console.error(pc.red(`Unknown model: ${modelId}`));
23
23
  process.exit(1);
24
24
  }
25
25
  return entry;
26
26
  }
27
- /**
28
- * Check if the egaki gateway API key is available (from env or stored credentials).
29
- */
27
+ // Vertex is the only provider that uses a prefix (vertex/model-id) in the catalog
28
+ // but expects a bare model ID in its SDK. Replicate and fal use slashes as part
29
+ // of the actual model name (e.g. fal-ai/flux/schnell, black-forest-labs/flux-1.1-pro).
30
+ // stripProviderPrefix is called inside the Vertex lambdas only.
31
+ const PROVIDER_SDKS = {
32
+ google: {
33
+ image: async (id) => (await import('@ai-sdk/google')).google.image(id),
34
+ text: async (id) => (await import('@ai-sdk/google')).google(id),
35
+ video: async (id) => (await import('@ai-sdk/google')).google.video(id),
36
+ },
37
+ vertex: {
38
+ image: async (id) => (await import('@ai-sdk/google-vertex')).vertex.image(stripProviderPrefix(id)),
39
+ text: async (id) => (await import('@ai-sdk/google-vertex')).vertex(stripProviderPrefix(id)),
40
+ video: async (id) => (await import('@ai-sdk/google-vertex')).vertex.video(stripProviderPrefix(id)),
41
+ },
42
+ openai: {
43
+ image: async (id) => (await import('@ai-sdk/openai')).openai.image(id),
44
+ },
45
+ replicate: {
46
+ image: async (id) => (await import('@ai-sdk/replicate')).replicate.image(id),
47
+ },
48
+ fal: {
49
+ image: async (id) => (await import('@ai-sdk/fal')).fal.image(id),
50
+ video: async (id) => (await import('@ai-sdk/fal')).fal.video(id),
51
+ },
52
+ xai: {
53
+ image: async (id) => {
54
+ const xaiProvider = await getXaiProviderInstance();
55
+ return xaiProvider.image(id);
56
+ },
57
+ video: async (id) => {
58
+ const xaiProvider = await getXaiProviderInstance();
59
+ return xaiProvider.video(id);
60
+ },
61
+ },
62
+ // Providers routed exclusively through the egaki gateway (no direct SDK).
63
+ // They only need catalog entries and gateway routing, no local SDK factory.
64
+ // bfl, recraft, klingai, alibaba — all handled by the gateway fallback.
65
+ };
66
+ // ─── xAI provider instance ───────────────────────────────────────────────────
67
+ // Creates an @ai-sdk/xai provider instance with either a direct API key or
68
+ // OAuth tokens from the Grok Build subscription. OAuth tokens are injected via
69
+ // a custom fetch that replaces the Authorization header on every request.
70
+ async function getXaiProviderInstance() {
71
+ const { createXai } = await import('@ai-sdk/xai');
72
+ // Direct API key path (env or stored)
73
+ if (!shouldUseXaiOAuth()) {
74
+ return createXai({
75
+ apiKey: process.env['XAI_API_KEY'],
76
+ });
77
+ }
78
+ // OAuth path: inject bearer token via custom fetch
79
+ const storedAuth = getXaiAuth();
80
+ if (!storedAuth) {
81
+ console.error(pc.red('Missing xAI OAuth tokens. Please run `egaki login` and select xAI Grok Build.'));
82
+ process.exit(1);
83
+ }
84
+ const auth = await getValidXaiAuth(storedAuth, saveXaiAuth);
85
+ if (auth instanceof Error) {
86
+ console.error(pc.red(auth.message));
87
+ process.exit(1);
88
+ }
89
+ return createXai({
90
+ // Dummy key to satisfy the SDK's apiKey requirement; the custom fetch
91
+ // overrides the Authorization header with the real OAuth token.
92
+ apiKey: 'xai-oauth-placeholder',
93
+ fetch: async (input, init) => {
94
+ const headers = new Headers(input instanceof Request ? input.headers : undefined);
95
+ if (init?.headers) {
96
+ const initHeaders = new Headers(init.headers);
97
+ initHeaders.forEach((value, key) => headers.set(key, value));
98
+ }
99
+ headers.set('authorization', `Bearer ${auth.access}`);
100
+ return fetch(input, { ...init, headers });
101
+ },
102
+ });
103
+ }
104
+ // ─── key checking ────────────────────────────────────────────────────────────
30
105
  function hasEgakiKey() {
31
106
  const info = PROVIDERS['egaki'];
32
107
  if (!info)
33
108
  return false;
34
109
  return Boolean(process.env[info.envVar]);
35
110
  }
36
- /**
37
- * Check if a direct provider key is available for the given provider.
38
- */
39
111
  function hasDirectProviderKey(providerName) {
40
112
  const info = PROVIDERS[providerName];
41
113
  if (!info)
42
114
  return false;
43
- return Boolean(process.env[info.envVar]);
115
+ if (Boolean(process.env[info.envVar]))
116
+ return true;
117
+ // xAI OAuth counts as having a direct provider key since we use @ai-sdk/xai directly
118
+ if (providerName === 'xai' && shouldUseXaiOAuth())
119
+ return true;
120
+ return false;
44
121
  }
45
- // Check that the provider's API key is available before making API calls.
122
+ // Check that the provider's API key or OAuth login is available before making API calls.
46
123
  // Prints a user-friendly error with instructions on how to configure it.
47
- // Prioritizes egaki subscription over individual provider keys.
48
124
  export function ensureProviderKey(providerName) {
49
- // If direct provider key exists, we're good
50
125
  if (hasDirectProviderKey(providerName))
51
126
  return;
52
127
  // Vertex models require a direct key — the upstream Vercel AI Gateway
@@ -62,7 +137,6 @@ export function ensureProviderKey(providerName) {
62
137
  console.error('');
63
138
  process.exit(1);
64
139
  }
65
- // If egaki key exists, we'll route through the gateway
66
140
  if (hasEgakiKey())
67
141
  return;
68
142
  const info = PROVIDERS[providerName];
@@ -85,26 +159,16 @@ export function ensureProviderKey(providerName) {
85
159
  console.error('');
86
160
  process.exit(1);
87
161
  }
88
- /**
89
- * Create a gateway-backed image model using createGateway from the AI SDK.
90
- * The model ID is sent as provider/model format to the gateway.
91
- */
162
+ // ─── gateway model factories ─────────────────────────────────────────────────
92
163
  async function createGatewayImageModel(modelId, provider) {
93
164
  const { createGateway } = await import('ai');
94
165
  const gateway = createGateway({
95
166
  apiKey: process.env['EGAKI_API_KEY'],
96
167
  baseURL: EGAKI_GATEWAY_URL,
97
168
  });
98
- // Gateway expects provider/model format for routing.
99
- // For models that already have a prefix (e.g. vertex/imagen-4.0), strip it
100
- // and use the catalog provider. For bare IDs, prepend the provider.
101
169
  const bareId = stripProviderPrefix(modelId);
102
- const gatewayModelId = `${provider}/${bareId}`;
103
- return gateway.image(gatewayModelId);
170
+ return gateway.image(`${provider}/${bareId}`);
104
171
  }
105
- /**
106
- * Create a gateway-backed text model using createGateway from the AI SDK.
107
- */
108
172
  async function createGatewayTextModel(modelId, provider) {
109
173
  const { createGateway } = await import('ai');
110
174
  const gateway = createGateway({
@@ -112,12 +176,8 @@ async function createGatewayTextModel(modelId, provider) {
112
176
  baseURL: EGAKI_GATEWAY_URL,
113
177
  });
114
178
  const bareId = stripProviderPrefix(modelId);
115
- const gatewayModelId = `${provider}/${bareId}`;
116
- return gateway(gatewayModelId);
179
+ return gateway(`${provider}/${bareId}`);
117
180
  }
118
- /**
119
- * Create a gateway-backed video model using createGateway from the AI SDK.
120
- */
121
181
  async function createGatewayVideoModel(modelId, provider) {
122
182
  const { createGateway } = await import('ai');
123
183
  const gateway = createGateway({
@@ -125,9 +185,9 @@ async function createGatewayVideoModel(modelId, provider) {
125
185
  baseURL: EGAKI_GATEWAY_URL,
126
186
  });
127
187
  const bareId = stripProviderPrefix(modelId);
128
- const gatewayModelId = `${provider}/${bareId}`;
129
- return gateway.video(gatewayModelId);
188
+ return gateway.video(`${provider}/${bareId}`);
130
189
  }
190
+ // ─── ChatGPT OAuth routing ───────────────────────────────────────────────────
131
191
  /**
132
192
  * ChatGPT OAuth image generation goes through the Codex backend instead of the
133
193
  * OpenAI Image API, so OpenAI image models need the custom responses path.
@@ -138,44 +198,69 @@ export function shouldUseResponsesApi(modelId) {
138
198
  return false;
139
199
  return config.provider === 'openai' && config.strategy === 'image' && shouldUseChatGptBackend();
140
200
  }
141
- // Lazily import the provider and create the right model instance.
142
- // This avoids loading all provider SDKs upfront — only the one needed
143
- // for the selected model gets imported.
144
- //
145
- // Priority: direct provider key > egaki gateway > error
201
+ /**
202
+ * Determine which auth source will be used for a given provider.
203
+ * Returns the auth type for logging. Does NOT check if the key is valid.
204
+ */
205
+ function resolveAuthSource(providerName) {
206
+ const info = PROVIDERS[providerName];
207
+ // xAI has special OAuth handling
208
+ if (providerName === 'xai') {
209
+ if (process.env['XAI_API_KEY'] && !shouldUseXaiOAuth()) {
210
+ return { type: 'api-key', label: 'XAI_API_KEY' };
211
+ }
212
+ if (shouldUseXaiOAuth()) {
213
+ return { type: 'oauth', label: 'xAI Grok Build OAuth' };
214
+ }
215
+ }
216
+ // ChatGPT OAuth
217
+ if (providerName === 'openai' && shouldUseChatGptBackend()) {
218
+ return { type: 'oauth', label: 'ChatGPT OAuth' };
219
+ }
220
+ // Direct provider env var
221
+ if (info && process.env[info.envVar]) {
222
+ return { type: 'api-key', label: info.envVar };
223
+ }
224
+ // Egaki gateway
225
+ if (hasEgakiKey()) {
226
+ return { type: 'egaki-gateway' };
227
+ }
228
+ // Should not reach here if ensureProviderKey passed
229
+ return { type: 'api-key', label: 'unknown' };
230
+ }
231
+ function logAuthSource(source) {
232
+ switch (source.type) {
233
+ case 'api-key':
234
+ console.error(pc.dim(`Auth: ${source.label}`));
235
+ break;
236
+ case 'oauth':
237
+ console.error(pc.dim(`Auth: ${source.label}`));
238
+ break;
239
+ case 'egaki-gateway':
240
+ console.error(pc.dim('Auth: Egaki subscription (gateway)'));
241
+ break;
242
+ }
243
+ }
244
+ // ─── public model factories ──────────────────────────────────────────────────
245
+ // Priority for xAI: explicit XAI_API_KEY > xai-oauth tokens > egaki gateway > error.
246
+ // Priority for others: direct provider key > egaki gateway > error.
247
+ // Uses PROVIDER_SDKS map for direct provider creation, eliminating switch blocks.
146
248
  export async function createImageModel(modelId) {
147
249
  const config = getModelConfig(modelId);
148
250
  ensureProviderKey(config.provider);
149
- // If the user has a direct provider key, use the provider SDK directly
251
+ const authSource = resolveAuthSource(config.provider);
252
+ logAuthSource(authSource);
150
253
  if (hasDirectProviderKey(config.provider)) {
151
- switch (config.provider) {
152
- case 'google': {
153
- const { google } = await import('@ai-sdk/google');
154
- return google.image(modelId);
155
- }
156
- case 'vertex': {
157
- const { vertex } = await import('@ai-sdk/google-vertex');
158
- return vertex.image(stripProviderPrefix(modelId));
159
- }
160
- case 'openai': {
161
- const { openai } = await import('@ai-sdk/openai');
162
- return openai.image(modelId);
163
- }
164
- case 'replicate': {
165
- const { replicate } = await import('@ai-sdk/replicate');
166
- return replicate.image(modelId);
167
- }
168
- case 'fal': {
169
- const { fal } = await import('@ai-sdk/fal');
170
- return fal.image(modelId);
171
- }
254
+ const factory = PROVIDER_SDKS[config.provider]?.image;
255
+ if (factory) {
256
+ return factory(modelId);
172
257
  }
258
+ console.error(pc.red(`Direct image generation is not supported for provider: ${config.provider}.`));
259
+ process.exit(1);
173
260
  }
174
- // Fall back to egaki gateway
175
261
  if (hasEgakiKey()) {
176
262
  return createGatewayImageModel(modelId, config.provider);
177
263
  }
178
- // Should not reach here — ensureProviderKey would have exited
179
264
  console.error(pc.red(`No API key available for provider: ${config.provider}`));
180
265
  process.exit(1);
181
266
  }
@@ -186,24 +271,16 @@ export async function createTextModel(modelId) {
186
271
  process.exit(1);
187
272
  }
188
273
  ensureProviderKey(config.provider);
189
- // Direct provider key takes priority
274
+ const authSource = resolveAuthSource(config.provider);
275
+ logAuthSource(authSource);
190
276
  if (hasDirectProviderKey(config.provider)) {
191
- switch (config.provider) {
192
- case 'google': {
193
- const { google } = await import('@ai-sdk/google');
194
- return google(modelId);
195
- }
196
- case 'vertex': {
197
- const { vertex } = await import('@ai-sdk/google-vertex');
198
- return vertex(stripProviderPrefix(modelId));
199
- }
200
- default:
201
- // Only Google/Vertex support generateText with responseModalities for images
202
- console.error(pc.red(`Text+image generation is only supported for Google/Vertex models, got provider: ${config.provider}`));
203
- process.exit(1);
277
+ const factory = PROVIDER_SDKS[config.provider]?.text;
278
+ if (factory) {
279
+ return factory(modelId);
204
280
  }
281
+ console.error(pc.red(`Text+image generation is not supported for provider: ${config.provider}`));
282
+ process.exit(1);
205
283
  }
206
- // Fall back to egaki gateway
207
284
  if (hasEgakiKey()) {
208
285
  return createGatewayTextModel(modelId, config.provider);
209
286
  }
@@ -217,27 +294,16 @@ export async function createVideoModel(modelId) {
217
294
  process.exit(1);
218
295
  }
219
296
  ensureProviderKey(config.provider);
220
- // Direct provider key takes priority
297
+ const authSource = resolveAuthSource(config.provider);
298
+ logAuthSource(authSource);
221
299
  if (hasDirectProviderKey(config.provider)) {
222
- switch (config.provider) {
223
- case 'google': {
224
- const { google } = await import('@ai-sdk/google');
225
- return google.video(modelId);
226
- }
227
- case 'vertex': {
228
- const { vertex } = await import('@ai-sdk/google-vertex');
229
- return vertex.video(stripProviderPrefix(modelId));
230
- }
231
- case 'fal': {
232
- const { fal } = await import('@ai-sdk/fal');
233
- return fal.video(modelId);
234
- }
235
- default:
236
- console.error(pc.red(`Direct video generation is only supported for Google, Vertex, and Fal keys, got provider: ${config.provider}`));
237
- process.exit(1);
300
+ const factory = PROVIDER_SDKS[config.provider]?.video;
301
+ if (factory) {
302
+ return factory(modelId);
238
303
  }
304
+ console.error(pc.red(`Direct video generation is not supported for provider: ${config.provider}. Use an egaki subscription instead.`));
305
+ process.exit(1);
239
306
  }
240
- // Fall back to egaki gateway
241
307
  if (hasEgakiKey()) {
242
308
  return createGatewayVideoModel(modelId, config.provider);
243
309
  }
@@ -1,52 +1,2 @@
1
- export type VideoDurationPricingTier = {
2
- /** USD per second for this variant */
3
- costPerSecond: number;
4
- /** Optional resolution discriminator (e.g. 720p, 1080p, 4k) */
5
- resolution?: string;
6
- /** Optional mode discriminator (e.g. std, pro) */
7
- mode?: string;
8
- /** Optional audio discriminator */
9
- audio?: boolean;
10
- };
11
- export type PerVideoSecondCost = {
12
- type: 'per-video-second';
13
- /** Fallback duration when request does not specify one (seconds) */
14
- defaultDurationSec: number;
15
- tiers: VideoDurationPricingTier[];
16
- };
17
- export type UnknownVideoCost = {
18
- type: 'unknown';
19
- };
20
- export type VideoModelCost = PerVideoSecondCost | UnknownVideoCost;
21
- export type VideoModelFeatures = {
22
- /** Supports text prompt to video generation */
23
- textToVideo: boolean;
24
- /** Supports image-to-video prompt object ({ image, text }) */
25
- imageToVideo: boolean;
26
- /** Optional capabilities exposed by provider */
27
- capabilities: Array<'t2v' | 'i2v' | 'r2v' | 'motion-control' | 'editing'>;
28
- /** Optional supported aspect ratios */
29
- aspectRatios?: string[];
30
- /** Optional supported resolutions */
31
- resolutions?: string[];
32
- /** Optional duration range in seconds */
33
- durationRangeSec?: {
34
- min: number;
35
- max: number;
36
- };
37
- seed: boolean;
38
- multipleVideos: boolean;
39
- };
40
- export type VideoModelEntry = {
41
- id: string;
42
- name: string;
43
- description?: string;
44
- provider: string;
45
- strategy: 'video';
46
- released: string;
47
- cost: VideoModelCost;
48
- features: VideoModelFeatures;
49
- };
50
- export declare const VIDEO_CATALOG: VideoModelEntry[];
51
- export declare function findVideoModel(id: string): VideoModelEntry | undefined;
1
+ export { VIDEO_CATALOG, findVideoModel, type VideoModelEntry, type VideoModelFeatures, type VideoModelCost, type PerVideoSecondCost, type UnknownCost as UnknownVideoCost, type VideoDurationPricingTier, } from './model-catalog.js';
52
2
  //# sourceMappingURL=video-model-catalog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"video-model-catalog.d.ts","sourceRoot":"","sources":["../src/video-model-catalog.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,wBAAwB,GAAG;IACrC,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAA;IACrB,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,mCAAmC;IACnC,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,kBAAkB,CAAA;IACxB,oEAAoE;IACpE,kBAAkB,EAAE,MAAM,CAAA;IAC1B,KAAK,EAAE,wBAAwB,EAAE,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,SAAS,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG,kBAAkB,GAAG,gBAAgB,CAAA;AAElE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,+CAA+C;IAC/C,WAAW,EAAE,OAAO,CAAA;IACpB,8DAA8D;IAC9D,YAAY,EAAE,OAAO,CAAA;IACrB,gDAAgD;IAChD,YAAY,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,gBAAgB,GAAG,SAAS,CAAC,CAAA;IACzE,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,yCAAyC;IACzC,gBAAgB,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IAC/C,IAAI,EAAE,OAAO,CAAA;IACb,cAAc,EAAE,OAAO,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,cAAc,CAAA;IACpB,QAAQ,EAAE,kBAAkB,CAAA;CAC7B,CAAA;AAID,eAAO,MAAM,aAAa,EAAE,eAAe,EA0S1C,CAAA;AAID,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEtE"}
1
+ {"version":3,"file":"video-model-catalog.d.ts","sourceRoot":"","sources":["../src/video-model-catalog.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,aAAa,EACb,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,WAAW,IAAI,gBAAgB,EACpC,KAAK,wBAAwB,GAC9B,MAAM,oBAAoB,CAAA"}