egaki 0.3.0 → 0.4.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.
@@ -1,6 +1,9 @@
1
1
  // Credential storage for egaki.
2
2
  // Reads and writes API keys from ~/.config/egaki/credentials.json.
3
3
  // Keys are stored per-provider and injected as env vars at runtime.
4
+ //
5
+ // Most providers store a plain API key string. The 'chatgpt' provider stores
6
+ // a structured ChatGptAuth object with OAuth tokens for the Codex backend flow.
4
7
  import fs from 'node:fs';
5
8
  import path from 'node:path';
6
9
  import os from 'node:os';
@@ -13,6 +16,12 @@ export const PROVIDERS = {
13
16
  label: 'Egaki (all models, one subscription)',
14
17
  hint: 'Subscribe at https://egaki.org/buy or run: egaki subscribe',
15
18
  },
19
+ chatgpt: {
20
+ envVar: 'OPENAI_API_KEY',
21
+ label: 'ChatGPT (use your subscription)',
22
+ hint: 'Sign in with your ChatGPT account via browser',
23
+ oauth: true,
24
+ },
16
25
  google: {
17
26
  envVar: 'GOOGLE_GENERATIVE_AI_API_KEY',
18
27
  label: 'Google AI (Gemini, Imagen)',
@@ -25,7 +34,7 @@ export const PROVIDERS = {
25
34
  },
26
35
  openai: {
27
36
  envVar: 'OPENAI_API_KEY',
28
- label: 'OpenAI (DALL-E)',
37
+ label: 'OpenAI (API key)',
29
38
  hint: 'Get your key at https://platform.openai.com/api-keys',
30
39
  },
31
40
  replicate: {
@@ -70,6 +79,19 @@ export function saveProviderKey(provider, key) {
70
79
  creds[provider] = key;
71
80
  writeCredentials(creds);
72
81
  }
82
+ export function saveChatGptAuth(auth) {
83
+ const creds = readCredentials();
84
+ creds['chatgpt'] = auth;
85
+ writeCredentials(creds);
86
+ }
87
+ export function getChatGptAuth() {
88
+ const creds = readCredentials();
89
+ const val = creds['chatgpt'];
90
+ if (val && typeof val === 'object') {
91
+ return val;
92
+ }
93
+ return undefined;
94
+ }
73
95
  export function removeProviderKey(provider) {
74
96
  const creds = readCredentials();
75
97
  delete creds[provider];
@@ -77,23 +99,33 @@ export function removeProviderKey(provider) {
77
99
  }
78
100
  export function getProviderKey(provider) {
79
101
  const creds = readCredentials();
80
- return creds[provider];
102
+ const val = creds[provider];
103
+ return typeof val === 'string' ? val : undefined;
81
104
  }
82
- // Inject all stored credentials as env vars so the AI SDK
83
- // providers pick them up automatically. Env vars already set
84
- // by the user take precedence (we don't overwrite them).
105
+ // Inject all stored non-ChatGPT credentials as env vars so the AI SDK
106
+ // providers pick them up automatically. Env vars already set by the user take
107
+ // precedence (we don't overwrite them).
85
108
  export function injectCredentialsToEnv() {
86
109
  const creds = readCredentials();
87
- for (const [provider, key] of Object.entries(creds)) {
110
+ for (const [provider, value] of Object.entries(creds)) {
88
111
  const info = PROVIDERS[provider];
89
- if (!info) {
112
+ if (!info)
90
113
  continue;
91
- }
92
- if (!process.env[info.envVar]) {
93
- process.env[info.envVar] = key;
114
+ // Skip ChatGPT OAuth entries — handled separately
115
+ if (provider === 'chatgpt')
116
+ continue;
117
+ if (typeof value === 'string' && !process.env[info.envVar]) {
118
+ process.env[info.envVar] = value;
94
119
  }
95
120
  }
96
121
  }
122
+ /**
123
+ * Returns true when the current run should use the ChatGPT/Codex backend flow
124
+ * for OpenAI image models instead of a direct OpenAI API key.
125
+ */
126
+ export function shouldUseChatGptBackend() {
127
+ return !process.env['OPENAI_API_KEY'] && Boolean(getChatGptAuth());
128
+ }
97
129
  // Check if a provider has a key available (from env or stored credentials).
98
130
  // Returns the source of the key for display purposes.
99
131
  export function getKeyStatus(provider) {
@@ -101,6 +133,14 @@ export function getKeyStatus(provider) {
101
133
  if (!info) {
102
134
  return { available: false, source: 'none' };
103
135
  }
136
+ // ChatGPT OAuth is a special case
137
+ if (provider === 'chatgpt') {
138
+ const auth = getChatGptAuth();
139
+ if (auth) {
140
+ return { available: true, source: 'oauth' };
141
+ }
142
+ return { available: false, source: 'none' };
143
+ }
104
144
  if (process.env[info.envVar]) {
105
145
  return { available: true, source: 'env' };
106
146
  }
package/dist/login.d.ts CHANGED
@@ -2,7 +2,7 @@ export declare function loginInteractive(): Promise<void>;
2
2
  export declare function loginNonInteractive({ provider, key, }: {
3
3
  provider: string;
4
4
  key: string;
5
- }): void;
5
+ }): Promise<void>;
6
6
  export declare function showLoginStatus(): void;
7
7
  export declare function removeLogin(provider: string): void;
8
8
  export declare function readKeyFromStdin(): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"AAsBA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAqEtD;AAED,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,GAAG,GACJ,EAAE;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;CACZ,GAAG,IAAI,CAiBP;AAED,wBAAgB,eAAe,IAAI,IAAI,CAqBtC;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAYlD;AAGD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAMxD"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"AAyBA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAkFtD;AAED,wBAAsB,mBAAmB,CAAC,EACxC,QAAQ,EACR,GAAG,GACJ,EAAE;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;CACZ,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBhB;AAED,wBAAgB,eAAe,IAAI,IAAI,CAyCtC;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAalD;AAGD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAMxD"}
package/dist/login.js CHANGED
@@ -1,9 +1,11 @@
1
1
  // Login command — interactive and non-interactive API key management.
2
2
  // Interactive mode uses clack prompts for provider selection and key input.
3
3
  // Non-interactive mode supports --provider + --key flags and stdin piping.
4
+ // ChatGPT OAuth uses a browser flow instead of pasting a key.
4
5
  import { intro, outro, select, password, isCancel, cancel, log, note, } from '@clack/prompts';
5
6
  import pc from 'picocolors';
6
- import { PROVIDERS, saveProviderKey, removeProviderKey, getKeyStatus, readCredentials, } from './credentials.js';
7
+ import { PROVIDERS, saveProviderKey, removeProviderKey, getKeyStatus, getChatGptAuth, saveChatGptAuth, } from './credentials.js';
8
+ import { chatGptOAuthLogin, extractPlanType } from './chatgpt-auth.js';
7
9
  export async function loginInteractive() {
8
10
  intro(pc.bold('egaki login'));
9
11
  const providerOptions = Object.entries(PROVIDERS).map(([key, info]) => {
@@ -15,6 +17,11 @@ export async function loginInteractive() {
15
17
  if (status.source === 'stored') {
16
18
  return pc.green('(saved)');
17
19
  }
20
+ if (status.source === 'oauth') {
21
+ const auth = getChatGptAuth();
22
+ const email = auth?.email;
23
+ return pc.green(`(signed in${email ? ` as ${email}` : ''})`);
24
+ }
18
25
  return pc.dim('(not configured)');
19
26
  })();
20
27
  // Highlight the egaki option as recommended
@@ -40,6 +47,13 @@ export async function loginInteractive() {
40
47
  log.error(`Unknown provider: ${provider}`);
41
48
  process.exit(1);
42
49
  }
50
+ // ChatGPT uses browser OAuth instead of key paste
51
+ if (provider === 'chatgpt') {
52
+ const auth = await chatGptOAuthLogin();
53
+ saveChatGptAuth(auth);
54
+ outro('Done — ChatGPT OAuth saved');
55
+ return;
56
+ }
43
57
  log.info(`${pc.dim('Env var:')} ${info.envVar} ${pc.dim('(also used by the CLI when set)')}`);
44
58
  log.info(`${pc.dim('Get key:')} ${info.hint}`);
45
59
  const key = await password({
@@ -59,13 +73,20 @@ export async function loginInteractive() {
59
73
  note(`You can also set ${pc.bold(info.envVar)} as an env var.\nThe CLI reads it automatically, so you can pass it inline too:\n ${pc.dim(`${info.envVar}=... egaki generate "prompt"`)}`, 'Tip');
60
74
  outro('Done');
61
75
  }
62
- export function loginNonInteractive({ provider, key, }) {
76
+ export async function loginNonInteractive({ provider, key, }) {
63
77
  const info = PROVIDERS[provider];
64
78
  if (!info) {
65
79
  const available = Object.keys(PROVIDERS).join(', ');
66
80
  console.error(pc.red(`Unknown provider: ${provider}. Available: ${available}`));
67
81
  process.exit(1);
68
82
  }
83
+ // ChatGPT uses browser OAuth — key flag is not needed
84
+ if (provider === 'chatgpt') {
85
+ const auth = await chatGptOAuthLogin();
86
+ saveChatGptAuth(auth);
87
+ console.log(pc.green('ChatGPT OAuth saved'));
88
+ return;
89
+ }
69
90
  if (!key || key.trim().length === 0) {
70
91
  console.error(pc.red('API key cannot be empty'));
71
92
  process.exit(1);
@@ -77,6 +98,25 @@ export function showLoginStatus() {
77
98
  console.log(pc.bold('Configured providers:\n'));
78
99
  for (const [key, info] of Object.entries(PROVIDERS)) {
79
100
  const status = getKeyStatus(key);
101
+ if (key === 'chatgpt') {
102
+ const auth = getChatGptAuth();
103
+ if (auth) {
104
+ const plan = extractPlanType(auth);
105
+ const email = auth.email ?? auth.accountId ?? 'unknown';
106
+ const expired = auth.expires < Date.now();
107
+ const expiryLabel = expired
108
+ ? pc.yellow('(token expired, will auto-refresh)')
109
+ : pc.dim(`(token valid)`);
110
+ console.log(`${pc.green('*')} ${info.label} ${pc.green('(signed in)')}`);
111
+ console.log(pc.dim(` account: ${email}${plan ? `, plan: ${plan}` : ''}`));
112
+ console.log(` ${expiryLabel}`);
113
+ }
114
+ else {
115
+ console.log(`${pc.dim('-')} ${info.label} ${pc.dim('(not signed in)')}`);
116
+ console.log(pc.dim(` run: egaki login → select ChatGPT`));
117
+ }
118
+ continue;
119
+ }
80
120
  const icon = status.available ? pc.green('*') : pc.dim('-');
81
121
  const source = (() => {
82
122
  if (status.source === 'env') {
@@ -100,7 +140,8 @@ export function removeLogin(provider) {
100
140
  process.exit(1);
101
141
  }
102
142
  removeProviderKey(provider);
103
- console.log(pc.green(`${info.label} key removed`));
143
+ const label = provider === 'chatgpt' ? 'ChatGPT OAuth' : info.label;
144
+ console.log(pc.green(`${label} key removed`));
104
145
  }
105
146
  // Read API key from stdin (for piping: echo "sk-xxx" | egaki login --provider google)
106
147
  export async function readKeyFromStdin() {
@@ -1 +1 @@
1
- {"version":3,"file":"model-catalog.d.ts","sourceRoot":"","sources":["../src/model-catalog.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,WAAW,CAAA;IACjB,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,WAAW,CAAA;IACjB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAA;IACjB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,YAAY,CAAA;AAEnD,MAAM,MAAM,aAAa,GAAG;IAC1B,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAA;IAChB,mCAAmC;IACnC,UAAU,EAAE,OAAO,CAAA;IACnB,8BAA8B;IAC9B,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,kCAAkC;IAClC,IAAI,EAAE,OAAO,CAAA;IACb,uCAAuC;IACvC,cAAc,EAAE,OAAO,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAA;IAC1B,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,SAAS,CAAA;IACf,QAAQ,EAAE,aAAa,CAAA;CACxB,CAAA;AA+ED,eAAO,MAAM,OAAO,EAAE,UAAU,EAs4B/B,CAAA;AAMD,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAE5D"}
1
+ {"version":3,"file":"model-catalog.d.ts","sourceRoot":"","sources":["../src/model-catalog.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,WAAW,CAAA;IACjB,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,WAAW,CAAA;IACjB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAA;IACjB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,YAAY,CAAA;AAEnD,MAAM,MAAM,aAAa,GAAG;IAC1B,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAA;IAChB,mCAAmC;IACnC,UAAU,EAAE,OAAO,CAAA;IACnB,8BAA8B;IAC9B,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,kCAAkC;IAClC,IAAI,EAAE,OAAO,CAAA;IACb,uCAAuC;IACvC,cAAc,EAAE,OAAO,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAA;IAC1B,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,SAAS,CAAA;IACf,QAAQ,EAAE,aAAa,CAAA;CACxB,CAAA;AA+ED,eAAO,MAAM,OAAO,EAAE,UAAU,EAy6B/B,CAAA;AAMD,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAE5D"}
@@ -437,9 +437,42 @@ export const CATALOG = [
437
437
  multipleImages: true,
438
438
  },
439
439
  },
440
- // NOTE: chatgpt-image-latest exists in OpenAI's .d.ts types but it's a moving
441
- // alias used by ChatGPT/Responses API, not a stable image endpoint model.
442
- // Omitted until OpenAI stabilizes it as a first-class image generation target.
440
+ {
441
+ id: 'gpt-image-2',
442
+ name: 'GPT Image 2',
443
+ description: 'State-of-the-art image generation model with token-based pricing. ' +
444
+ 'Cost varies by quality and size; $0.053 is the medium-quality 1024×1024 estimate.',
445
+ released: '2026-04-21',
446
+ ...openaiImage,
447
+ // Token-based pricing: $30/1M output image tokens. Medium quality 1024×1024 ≈ $0.053.
448
+ cost: { type: 'per-image', perImage: 0.053 },
449
+ features: {
450
+ editing: true,
451
+ inpainting: true,
452
+ aspectRatios: [],
453
+ sizes: ['1024x1024', '1536x1024', '1024x1536'],
454
+ seed: false,
455
+ multipleImages: true,
456
+ },
457
+ },
458
+ {
459
+ id: 'chatgpt-image-latest',
460
+ name: 'ChatGPT Image',
461
+ description: 'Rolling-latest alias that tracks the newest ChatGPT image model. Currently points ' +
462
+ 'to gpt-image-2. Pricing may shift when the alias retargets to a newer model.',
463
+ released: '2026-01',
464
+ ...openaiImage,
465
+ // Matches gpt-image-2 medium quality 1024×1024 estimate.
466
+ cost: { type: 'per-image', perImage: 0.053 },
467
+ features: {
468
+ editing: true,
469
+ inpainting: true,
470
+ aspectRatios: [],
471
+ sizes: ['1024x1024', '1536x1024', '1024x1536'],
472
+ seed: false,
473
+ multipleImages: true,
474
+ },
475
+ },
443
476
  // ── Replicate ──────────────────────────────────────────────────────────
444
477
  {
445
478
  id: 'black-forest-labs/flux-1.1-pro',
package/dist/models.d.ts CHANGED
@@ -9,6 +9,11 @@ export declare const DEFAULT_MODEL = "nano-banana-pro-preview";
9
9
  export declare const DEFAULT_VIDEO_MODEL = "veo-3.1-fast-generate-001";
10
10
  export declare function getModelConfig(modelId: string): AnyModelEntry;
11
11
  export declare function ensureProviderKey(providerName: string): void;
12
+ /**
13
+ * ChatGPT OAuth image generation goes through the Codex backend instead of the
14
+ * OpenAI Image API, so OpenAI image models need the custom responses path.
15
+ */
16
+ export declare function shouldUseResponsesApi(modelId: string): boolean;
12
17
  export declare function createImageModel(modelId: string): Promise<ImageModel>;
13
18
  export declare function createTextModel(modelId: string): Promise<LanguageModel>;
14
19
  export declare function createVideoModel(modelId: string): Promise<any>;
@@ -1 +1 @@
1
- {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,IAAI,CAAA;AAInD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAE/D,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,eAAe,CAAA;AACxD,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,CAAA;AAE3C,eAAO,MAAM,YAAY,EAA+B,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;AAC7E,eAAO,MAAM,YAAY,EAAqC,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;AAYnF,eAAO,MAAM,aAAa,4BAA4B,CAAA;AACtD,eAAO,MAAM,mBAAmB,8BAA8B,CAAA;AAE9D,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAO7D;AAuBD,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAiD5D;AAqDD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAsC3E;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC,CAqCxB;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAyCpE"}
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,IAAI,CAAA;AAInD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAE/D,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,eAAe,CAAA;AACxD,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,CAAA;AAE3C,eAAO,MAAM,YAAY,EAA+B,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;AAC7E,eAAO,MAAM,YAAY,EAAqC,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;AAYnF,eAAO,MAAM,aAAa,4BAA4B,CAAA;AACtD,eAAO,MAAM,mBAAmB,8BAA8B,CAAA;AAE9D,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAO7D;AAuBD,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAiD5D;AAgDD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAI9D;AAOD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAsC3E;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC,CAqCxB;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAyCpE"}
package/dist/models.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import pc from 'picocolors';
2
- import { PROVIDERS, EGAKI_GATEWAY_URL } from './credentials.js';
2
+ import { PROVIDERS, EGAKI_GATEWAY_URL, shouldUseChatGptBackend } from './credentials.js';
3
3
  import { CATALOG, findModel } from './model-catalog.js';
4
4
  import { VIDEO_CATALOG, findVideoModel } from './video-model-catalog.js';
5
5
  export const IMAGE_MODELS = CATALOG.map((m) => m.id);
@@ -128,6 +128,16 @@ async function createGatewayVideoModel(modelId, provider) {
128
128
  const gatewayModelId = `${provider}/${bareId}`;
129
129
  return gateway.video(gatewayModelId);
130
130
  }
131
+ /**
132
+ * ChatGPT OAuth image generation goes through the Codex backend instead of the
133
+ * OpenAI Image API, so OpenAI image models need the custom responses path.
134
+ */
135
+ export function shouldUseResponsesApi(modelId) {
136
+ const config = findModel(modelId);
137
+ if (!config)
138
+ return false;
139
+ return config.provider === 'openai' && config.strategy === 'image' && shouldUseChatGptBackend();
140
+ }
131
141
  // Lazily import the provider and create the right model instance.
132
142
  // This avoids loading all provider SDKs upfront — only the one needed
133
143
  // for the selected model gets imported.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "egaki",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "main": "./dist/cli.js",
6
6
  "types": "./dist/cli.d.ts",
@@ -54,7 +54,8 @@
54
54
  "@ai-sdk/replicate": "2.0.20",
55
55
  "@clack/prompts": "^1.0.1",
56
56
  "ai": "6.0.103",
57
- "goke": "^6.1.3",
57
+ "eventsource-parser": "^3.0.6",
58
+ "goke": "^6.6.1",
58
59
  "js-yaml": "^4.1.1",
59
60
  "picocolors": "^1.1.1",
60
61
  "string-dedent": "^3.0.2",