@push.rocks/smartai 2.3.0 → 4.0.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,63 +1,63 @@
1
1
  import type {
2
- IOpenAiMaxAuthCredentials,
3
- IOpenAiMaxAuthOptions,
4
- IOpenAiMaxCompleteDeviceCodeOptions,
5
- IOpenAiMaxDeviceCode,
6
- IOpenAiMaxDeviceCodePollOptions,
7
- IOpenAiMaxIdTokenInfo,
8
- IOpenAiMaxTokenData,
2
+ IOpenAiChatGptAuthCredentials,
3
+ IOpenAiChatGptAuthOptions,
4
+ IOpenAiChatGptCompleteDeviceCodeOptions,
5
+ IOpenAiChatGptDeviceCode,
6
+ IOpenAiChatGptDeviceCodePollOptions,
7
+ IOpenAiChatGptTokenData,
8
+ IOpenAiChatGptTokenInfo,
9
9
  } from './smartai.interfaces.js';
10
10
 
11
- export const OPENAI_MAX_AUTH_ISSUER = 'https://auth.openai.com';
12
- export const OPENAI_MAX_CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
13
- export const OPENAI_MAX_CODEX_BASE_URL = 'https://chatgpt.com/backend-api/codex';
14
- export const OPENAI_MAX_DEFAULT_ORIGINATOR = 'smartai';
11
+ export const OPENAI_CHATGPT_AUTH_ISSUER = 'https://auth.openai.com';
12
+ export const OPENAI_CHATGPT_CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
13
+ export const OPENAI_CHATGPT_CODEX_BASE_URL = 'https://chatgpt.com/backend-api/codex';
14
+ export const OPENAI_CHATGPT_DEFAULT_ORIGINATOR = 'smartai';
15
15
 
16
16
  const DEVICE_CODE_TIMEOUT_MS = 15 * 60 * 1000;
17
17
 
18
- export class OpenAiMaxAuthError extends Error {
18
+ export class OpenAiChatGptAuthError extends Error {
19
19
  public status?: number;
20
20
  public body?: string;
21
21
 
22
22
  constructor(message: string, options: { status?: number; body?: string } = {}) {
23
23
  super(message);
24
- this.name = 'OpenAiMaxAuthError';
24
+ this.name = 'OpenAiChatGptAuthError';
25
25
  this.status = options.status;
26
26
  this.body = options.body;
27
27
  }
28
28
  }
29
29
 
30
- export interface IOpenAiMaxAuthorizationCode {
30
+ export interface IOpenAiChatGptAuthorizationCode {
31
31
  authorizationCode: string;
32
32
  codeChallenge: string;
33
33
  codeVerifier: string;
34
34
  }
35
35
 
36
- interface IOpenAiMaxTokenResponse {
36
+ interface IOpenAiChatGptTokenResponse {
37
37
  id_token?: unknown;
38
38
  access_token?: unknown;
39
39
  refresh_token?: unknown;
40
40
  }
41
41
 
42
- function getFetch(options: IOpenAiMaxAuthOptions): typeof fetch {
42
+ function getFetch(options: IOpenAiChatGptAuthOptions): typeof fetch {
43
43
  const fetchFunction = options.fetch ?? globalThis.fetch;
44
44
  if (!fetchFunction) {
45
- throw new OpenAiMaxAuthError('fetch is not available for OpenAI Max authentication.');
45
+ throw new OpenAiChatGptAuthError('fetch is not available for OpenAI ChatGPT authentication.');
46
46
  }
47
47
  return fetchFunction;
48
48
  }
49
49
 
50
- function getIssuer(options: IOpenAiMaxAuthOptions): string {
51
- return (options.issuer ?? OPENAI_MAX_AUTH_ISSUER).replace(/\/+$/, '');
50
+ function getIssuer(options: IOpenAiChatGptAuthOptions): string {
51
+ return (options.issuer ?? OPENAI_CHATGPT_AUTH_ISSUER).replace(/\/+$/, '');
52
52
  }
53
53
 
54
- function getClientId(options: IOpenAiMaxAuthOptions): string {
55
- return options.clientId ?? OPENAI_MAX_CLIENT_ID;
54
+ function getClientId(options: IOpenAiChatGptAuthOptions): string {
55
+ return options.clientId ?? OPENAI_CHATGPT_CLIENT_ID;
56
56
  }
57
57
 
58
58
  function asString(value: unknown, name: string): string {
59
59
  if (typeof value !== 'string' || value.length === 0) {
60
- throw new OpenAiMaxAuthError(`OpenAI Max auth response is missing ${name}.`);
60
+ throw new OpenAiChatGptAuthError(`OpenAI ChatGPT auth response is missing ${name}.`);
61
61
  }
62
62
  return value;
63
63
  }
@@ -69,7 +69,7 @@ function asOptionalString(value: unknown): string | undefined {
69
69
  function asIntervalSeconds(value: unknown): number {
70
70
  const interval = typeof value === 'number' ? value : Number.parseInt(String(value ?? ''), 10);
71
71
  if (!Number.isFinite(interval) || interval <= 0) {
72
- throw new OpenAiMaxAuthError('OpenAI Max device-code response has an invalid interval.');
72
+ throw new OpenAiChatGptAuthError('OpenAI ChatGPT device-code response has an invalid interval.');
73
73
  }
74
74
  return interval;
75
75
  }
@@ -77,7 +77,7 @@ function asIntervalSeconds(value: unknown): number {
77
77
  async function readJson(response: Response, context: string): Promise<unknown> {
78
78
  const body = await response.text();
79
79
  if (!response.ok) {
80
- throw new OpenAiMaxAuthError(`${context} failed with status ${response.status}.`, {
80
+ throw new OpenAiChatGptAuthError(`${context} failed with status ${response.status}.`, {
81
81
  status: response.status,
82
82
  body,
83
83
  });
@@ -86,14 +86,14 @@ async function readJson(response: Response, context: string): Promise<unknown> {
86
86
  try {
87
87
  return body ? JSON.parse(body) : {};
88
88
  } catch (error) {
89
- throw new OpenAiMaxAuthError(`${context} returned invalid JSON: ${(error as Error).message}`, {
89
+ throw new OpenAiChatGptAuthError(`${context} returned invalid JSON: ${(error as Error).message}`, {
90
90
  status: response.status,
91
91
  body,
92
92
  });
93
93
  }
94
94
  }
95
95
 
96
- async function postJson(url: string, body: unknown, options: IOpenAiMaxAuthOptions): Promise<unknown> {
96
+ async function postJson(url: string, body: unknown, options: IOpenAiChatGptAuthOptions): Promise<unknown> {
97
97
  const response = await getFetch(options)(url, {
98
98
  method: 'POST',
99
99
  headers: { 'Content-Type': 'application/json' },
@@ -102,7 +102,7 @@ async function postJson(url: string, body: unknown, options: IOpenAiMaxAuthOptio
102
102
  return readJson(response, `POST ${url}`);
103
103
  }
104
104
 
105
- async function postForm(url: string, body: URLSearchParams, options: IOpenAiMaxAuthOptions): Promise<unknown> {
105
+ async function postForm(url: string, body: URLSearchParams, options: IOpenAiChatGptAuthOptions): Promise<unknown> {
106
106
  const response = await getFetch(options)(url, {
107
107
  method: 'POST',
108
108
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
@@ -118,18 +118,18 @@ function sleep(ms: number): Promise<void> {
118
118
  function parseJwtPayload(jwt: string): Record<string, unknown> {
119
119
  const parts = jwt.split('.');
120
120
  if (parts.length !== 3 || !parts[1]) {
121
- throw new OpenAiMaxAuthError('OpenAI Max auth returned an invalid ID token.');
121
+ throw new OpenAiChatGptAuthError('OpenAI ChatGPT auth returned an invalid token.');
122
122
  }
123
123
 
124
124
  try {
125
125
  return JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8')) as Record<string, unknown>;
126
126
  } catch (error) {
127
- throw new OpenAiMaxAuthError(`OpenAI Max ID token could not be parsed: ${(error as Error).message}`);
127
+ throw new OpenAiChatGptAuthError(`OpenAI ChatGPT token could not be parsed: ${(error as Error).message}`);
128
128
  }
129
129
  }
130
130
 
131
- export function parseOpenAiMaxIdToken(idToken: string): IOpenAiMaxIdTokenInfo {
132
- const claims = parseJwtPayload(idToken);
131
+ export function parseOpenAiChatGptTokenInfo(token: string): IOpenAiChatGptTokenInfo {
132
+ const claims = parseJwtPayload(token);
133
133
  const profile = claims['https://api.openai.com/profile'] as Record<string, unknown> | undefined;
134
134
  const auth = claims['https://api.openai.com/auth'] as Record<string, unknown> | undefined;
135
135
  const expiresAtSeconds = typeof claims.exp === 'number' ? claims.exp : undefined;
@@ -141,28 +141,37 @@ export function parseOpenAiMaxIdToken(idToken: string): IOpenAiMaxIdTokenInfo {
141
141
  chatgptAccountId: asOptionalString(auth?.chatgpt_account_id),
142
142
  chatgptAccountIsFedramp: auth?.chatgpt_account_is_fedramp === true,
143
143
  expiresAt: expiresAtSeconds ? new Date(expiresAtSeconds * 1000).toISOString() : undefined,
144
- rawJwt: idToken,
144
+ rawJwt: token,
145
145
  };
146
146
  }
147
147
 
148
- function createTokenData(response: IOpenAiMaxTokenResponse): IOpenAiMaxTokenData {
149
- const idToken = asString(response.id_token, 'id_token');
150
- const accessToken = asString(response.access_token, 'access_token');
151
- const refreshToken = asString(response.refresh_token, 'refresh_token');
152
- const idTokenInfo = parseOpenAiMaxIdToken(idToken);
148
+ function createTokenData(
149
+ response: IOpenAiChatGptTokenResponse,
150
+ existingTokenData?: IOpenAiChatGptTokenData,
151
+ ): IOpenAiChatGptTokenData {
152
+ const accessToken = asOptionalString(response.access_token) ?? existingTokenData?.accessToken;
153
+ const refreshToken = asOptionalString(response.refresh_token) ?? existingTokenData?.refreshToken;
154
+ const idToken = asOptionalString(response.id_token) ?? existingTokenData?.idToken;
155
+ if (!accessToken) {
156
+ throw new OpenAiChatGptAuthError('OpenAI ChatGPT auth response is missing access_token.');
157
+ }
158
+ if (!refreshToken) {
159
+ throw new OpenAiChatGptAuthError('OpenAI ChatGPT auth response is missing refresh_token.');
160
+ }
161
+ const tokenInfo = parseOpenAiChatGptTokenInfo(idToken ?? accessToken);
153
162
 
154
163
  return {
155
- idToken,
156
164
  accessToken,
157
165
  refreshToken,
158
- accountId: idTokenInfo.chatgptAccountId,
159
- idTokenInfo,
166
+ idToken,
167
+ accountId: tokenInfo.chatgptAccountId,
168
+ tokenInfo,
160
169
  };
161
170
  }
162
171
 
163
- export async function requestOpenAiMaxDeviceCode(
164
- options: IOpenAiMaxAuthOptions = {},
165
- ): Promise<IOpenAiMaxDeviceCode> {
172
+ export async function requestOpenAiChatGptDeviceCode(
173
+ options: IOpenAiChatGptAuthOptions = {},
174
+ ): Promise<IOpenAiChatGptDeviceCode> {
166
175
  const issuer = getIssuer(options);
167
176
  const response = await postJson(`${issuer}/api/accounts/deviceauth/usercode`, {
168
177
  client_id: getClientId(options),
@@ -176,10 +185,10 @@ export async function requestOpenAiMaxDeviceCode(
176
185
  };
177
186
  }
178
187
 
179
- export async function pollOpenAiMaxDeviceCode(
180
- deviceCode: IOpenAiMaxDeviceCode,
181
- options: IOpenAiMaxDeviceCodePollOptions = {},
182
- ): Promise<IOpenAiMaxAuthorizationCode> {
188
+ export async function pollOpenAiChatGptDeviceCode(
189
+ deviceCode: IOpenAiChatGptDeviceCode,
190
+ options: IOpenAiChatGptDeviceCodePollOptions = {},
191
+ ): Promise<IOpenAiChatGptAuthorizationCode> {
183
192
  const issuer = getIssuer(options);
184
193
  const pollUrl = `${issuer}/api/accounts/deviceauth/token`;
185
194
  const timeoutMs = options.timeoutMs ?? DEVICE_CODE_TIMEOUT_MS;
@@ -207,7 +216,7 @@ export async function pollOpenAiMaxDeviceCode(
207
216
 
208
217
  if (response.status !== 403 && response.status !== 404) {
209
218
  const body = await response.text();
210
- throw new OpenAiMaxAuthError(`OpenAI Max device-code polling failed with status ${response.status}.`, {
219
+ throw new OpenAiChatGptAuthError(`OpenAI ChatGPT device-code polling failed with status ${response.status}.`, {
211
220
  status: response.status,
212
221
  body,
213
222
  });
@@ -218,13 +227,13 @@ export async function pollOpenAiMaxDeviceCode(
218
227
  await sleepFunction(Math.min(deviceCode.intervalSeconds * 1000, Math.max(remaining, 0)));
219
228
  }
220
229
 
221
- throw new OpenAiMaxAuthError('OpenAI Max device-code login timed out.');
230
+ throw new OpenAiChatGptAuthError('OpenAI ChatGPT device-code login timed out.');
222
231
  }
223
232
 
224
- export async function exchangeOpenAiMaxAuthorizationCode(
225
- authorizationCode: IOpenAiMaxAuthorizationCode,
226
- options: IOpenAiMaxAuthOptions = {},
227
- ): Promise<IOpenAiMaxTokenData> {
233
+ export async function exchangeOpenAiChatGptAuthorizationCode(
234
+ authorizationCode: IOpenAiChatGptAuthorizationCode,
235
+ options: IOpenAiChatGptAuthOptions = {},
236
+ ): Promise<IOpenAiChatGptTokenData> {
228
237
  const issuer = getIssuer(options);
229
238
  const response = await postForm(`${issuer}/oauth/token`, new URLSearchParams({
230
239
  grant_type: 'authorization_code',
@@ -232,60 +241,60 @@ export async function exchangeOpenAiMaxAuthorizationCode(
232
241
  redirect_uri: `${issuer}/deviceauth/callback`,
233
242
  client_id: getClientId(options),
234
243
  code_verifier: authorizationCode.codeVerifier,
235
- }), options) as IOpenAiMaxTokenResponse;
244
+ }), options) as IOpenAiChatGptTokenResponse;
236
245
 
237
246
  return createTokenData(response);
238
247
  }
239
248
 
240
- export function ensureOpenAiMaxWorkspaceAllowed(
241
- tokenData: IOpenAiMaxTokenData,
249
+ export function ensureOpenAiChatGptWorkspaceAllowed(
250
+ tokenData: IOpenAiChatGptTokenData,
242
251
  forcedChatGptWorkspaceId?: string,
243
252
  ): void {
244
253
  if (!forcedChatGptWorkspaceId) {
245
254
  return;
246
255
  }
247
- if (tokenData.idTokenInfo.chatgptAccountId !== forcedChatGptWorkspaceId) {
248
- throw new OpenAiMaxAuthError(`OpenAI Max login is restricted to workspace ${forcedChatGptWorkspaceId}.`);
256
+ if (tokenData.tokenInfo.chatgptAccountId !== forcedChatGptWorkspaceId) {
257
+ throw new OpenAiChatGptAuthError(`OpenAI ChatGPT login is restricted to workspace ${forcedChatGptWorkspaceId}.`);
249
258
  }
250
259
  }
251
260
 
252
- export async function completeOpenAiMaxDeviceCodeLogin(
253
- deviceCode: IOpenAiMaxDeviceCode,
254
- options: IOpenAiMaxCompleteDeviceCodeOptions = {},
255
- ): Promise<IOpenAiMaxTokenData> {
256
- const authorizationCode = await pollOpenAiMaxDeviceCode(deviceCode, options);
257
- const tokenData = await exchangeOpenAiMaxAuthorizationCode(authorizationCode, options);
258
- ensureOpenAiMaxWorkspaceAllowed(tokenData, options.forcedChatGptWorkspaceId);
261
+ export async function completeOpenAiChatGptDeviceCodeLogin(
262
+ deviceCode: IOpenAiChatGptDeviceCode,
263
+ options: IOpenAiChatGptCompleteDeviceCodeOptions = {},
264
+ ): Promise<IOpenAiChatGptTokenData> {
265
+ const authorizationCode = await pollOpenAiChatGptDeviceCode(deviceCode, options);
266
+ const tokenData = await exchangeOpenAiChatGptAuthorizationCode(authorizationCode, options);
267
+ ensureOpenAiChatGptWorkspaceAllowed(tokenData, options.forcedChatGptWorkspaceId);
259
268
  return tokenData;
260
269
  }
261
270
 
262
- export async function refreshOpenAiMaxTokenData(
263
- tokenData: IOpenAiMaxTokenData,
264
- options: IOpenAiMaxAuthOptions = {},
265
- ): Promise<IOpenAiMaxTokenData> {
271
+ export async function refreshOpenAiChatGptTokenData(
272
+ tokenData: IOpenAiChatGptTokenData,
273
+ options: IOpenAiChatGptAuthOptions = {},
274
+ ): Promise<IOpenAiChatGptTokenData> {
266
275
  const issuer = getIssuer(options);
267
276
  const response = await postJson(`${issuer}/oauth/token`, {
268
277
  client_id: getClientId(options),
269
278
  grant_type: 'refresh_token',
270
279
  refresh_token: tokenData.refreshToken,
271
- }, options) as IOpenAiMaxTokenResponse;
280
+ }, options) as IOpenAiChatGptTokenResponse;
272
281
 
273
282
  return createTokenData({
274
283
  id_token: response.id_token ?? tokenData.idToken,
275
284
  access_token: response.access_token ?? tokenData.accessToken,
276
285
  refresh_token: response.refresh_token ?? tokenData.refreshToken,
277
- });
286
+ }, tokenData);
278
287
  }
279
288
 
280
- export function createOpenAiMaxProviderSettings(credentials: IOpenAiMaxAuthCredentials): {
289
+ export function createOpenAiChatGptProviderSettings(credentials: IOpenAiChatGptAuthCredentials): {
281
290
  apiKey: string;
282
291
  baseURL: string;
283
292
  headers: Record<string, string>;
284
293
  } {
285
- const accountId = credentials.accountId ?? credentials.idTokenInfo?.chatgptAccountId;
286
- const isFedrampAccount = credentials.idTokenInfo?.chatgptAccountIsFedramp === true;
294
+ const accountId = credentials.accountId ?? credentials.tokenInfo?.chatgptAccountId;
295
+ const isFedrampAccount = credentials.tokenInfo?.chatgptAccountIsFedramp === true;
287
296
  const headers: Record<string, string> = {
288
- originator: credentials.originator ?? OPENAI_MAX_DEFAULT_ORIGINATOR,
297
+ originator: credentials.originator ?? OPENAI_CHATGPT_DEFAULT_ORIGINATOR,
289
298
  };
290
299
 
291
300
  if (accountId) {
@@ -297,7 +306,7 @@ export function createOpenAiMaxProviderSettings(credentials: IOpenAiMaxAuthCrede
297
306
 
298
307
  return {
299
308
  apiKey: credentials.accessToken,
300
- baseURL: credentials.baseUrl ?? OPENAI_MAX_CODEX_BASE_URL,
309
+ baseURL: credentials.baseUrl ?? OPENAI_CHATGPT_CODEX_BASE_URL,
301
310
  headers,
302
311
  };
303
312
  }
@@ -2,7 +2,8 @@ import * as plugins from './plugins.js';
2
2
  import type { ISmartAiModelSetup, ISmartAiOptions, LanguageModelV3 } from './smartai.interfaces.js';
3
3
  import { createOllamaModel } from './smartai.provider.ollama.js';
4
4
  import { createAnthropicCachingMiddleware } from './smartai.middleware.anthropic.js';
5
- import { createOpenAiMaxProviderSettings } from './smartai.auth.openai.js';
5
+ import { createOpenAiChatGptInstructionsMiddleware } from './smartai.middleware.openai.js';
6
+ import { createOpenAiChatGptProviderSettings } from './smartai.auth.openai.js';
6
7
 
7
8
  /**
8
9
  * Returns a LanguageModelV3 for the given provider and model.
@@ -24,11 +25,17 @@ export function getModel(options: ISmartAiOptions): LanguageModelV3 {
24
25
  }
25
26
  case 'openai': {
26
27
  const p = plugins.createOpenAI(
27
- options.openAiMaxAuth
28
- ? createOpenAiMaxProviderSettings(options.openAiMaxAuth)
28
+ options.openAiChatGptAuth
29
+ ? createOpenAiChatGptProviderSettings(options.openAiChatGptAuth)
29
30
  : { apiKey: options.apiKey },
30
31
  );
31
- return p(options.model) as LanguageModelV3;
32
+ const base = p(options.model) as LanguageModelV3;
33
+ return options.openAiChatGptAuth
34
+ ? plugins.wrapLanguageModel({
35
+ model: base,
36
+ middleware: createOpenAiChatGptInstructionsMiddleware(),
37
+ }) as unknown as LanguageModelV3
38
+ : base;
32
39
  }
33
40
  case 'google': {
34
41
  const p = plugins.createGoogleGenerativeAI({ apiKey: options.apiKey });
@@ -15,7 +15,7 @@ export type TOpenAiReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'hi
15
15
 
16
16
  export type TOpenAiTextVerbosity = 'low' | 'medium' | 'high';
17
17
 
18
- export interface IOpenAiMaxIdTokenInfo {
18
+ export interface IOpenAiChatGptTokenInfo {
19
19
  email?: string;
20
20
  chatgptPlanType?: string;
21
21
  chatgptUserId?: string;
@@ -25,41 +25,40 @@ export interface IOpenAiMaxIdTokenInfo {
25
25
  rawJwt: string;
26
26
  }
27
27
 
28
- export interface IOpenAiMaxAuthCredentials {
28
+ export interface IOpenAiChatGptAuthCredentials {
29
29
  accessToken: string;
30
30
  refreshToken?: string;
31
31
  idToken?: string;
32
32
  accountId?: string;
33
- idTokenInfo?: IOpenAiMaxIdTokenInfo;
33
+ tokenInfo?: IOpenAiChatGptTokenInfo;
34
34
  baseUrl?: string;
35
35
  originator?: string;
36
36
  }
37
37
 
38
- export interface IOpenAiMaxTokenData extends IOpenAiMaxAuthCredentials {
38
+ export interface IOpenAiChatGptTokenData extends IOpenAiChatGptAuthCredentials {
39
39
  refreshToken: string;
40
- idToken: string;
41
- idTokenInfo: IOpenAiMaxIdTokenInfo;
40
+ tokenInfo: IOpenAiChatGptTokenInfo;
42
41
  }
43
42
 
44
- export interface IOpenAiMaxDeviceCode {
43
+ export interface IOpenAiChatGptDeviceCode {
45
44
  verificationUrl: string;
46
45
  userCode: string;
47
46
  deviceAuthId: string;
48
47
  intervalSeconds: number;
49
48
  }
50
49
 
51
- export interface IOpenAiMaxAuthOptions {
50
+ export interface IOpenAiChatGptAuthOptions {
52
51
  issuer?: string;
53
52
  clientId?: string;
54
53
  fetch?: typeof fetch;
55
54
  }
56
55
 
57
- export interface IOpenAiMaxDeviceCodePollOptions extends IOpenAiMaxAuthOptions {
56
+ export interface IOpenAiChatGptDeviceCodePollOptions extends IOpenAiChatGptAuthOptions {
58
57
  timeoutMs?: number;
59
58
  sleep?: (ms: number) => Promise<void>;
60
59
  }
61
60
 
62
- export interface IOpenAiMaxCompleteDeviceCodeOptions extends IOpenAiMaxDeviceCodePollOptions {
61
+ export interface IOpenAiChatGptCompleteDeviceCodeOptions extends IOpenAiChatGptDeviceCodePollOptions {
63
62
  forcedChatGptWorkspaceId?: string;
64
63
  }
65
64
 
@@ -108,7 +107,7 @@ export interface ISmartAiOptions {
108
107
  * OpenAI ChatGPT/Codex subscription credentials from the device-code auth flow.
109
108
  * Only used when provider === 'openai'.
110
109
  */
111
- openAiMaxAuth?: IOpenAiMaxAuthCredentials;
110
+ openAiChatGptAuth?: IOpenAiChatGptAuthCredentials;
112
111
  /**
113
112
  * Provider-specific AI SDK generation options.
114
113
  * Pass this to generateText()/streamText() alongside the model.
@@ -0,0 +1,46 @@
1
+ import type { JSONObject, LanguageModelV3CallOptions, LanguageModelV3Middleware } from '@ai-sdk/provider';
2
+
3
+ const isNonEmptyString = (value: unknown): value is string => typeof value === 'string' && value.trim().length > 0;
4
+
5
+ const getSystemInstructions = (prompt: LanguageModelV3CallOptions['prompt']): string | undefined => {
6
+ const instructions = prompt
7
+ .filter((message) => message.role === 'system')
8
+ .map((message) => message.content)
9
+ .filter(isNonEmptyString);
10
+
11
+ return instructions.length > 0 ? instructions.join('\n') : undefined;
12
+ };
13
+
14
+ /**
15
+ * ChatGPT's Codex backend requires top-level Responses API instructions.
16
+ * The standard OpenAI provider otherwise serializes system prompts as input items.
17
+ */
18
+ export function createOpenAiChatGptInstructionsMiddleware(): LanguageModelV3Middleware {
19
+ return {
20
+ specificationVersion: 'v3',
21
+ transformParams: async ({ params }) => {
22
+ const instructions = getSystemInstructions(params.prompt);
23
+ if (!instructions) {
24
+ return params;
25
+ }
26
+
27
+ const providerOptions = params.providerOptions ?? {};
28
+ const openAiProviderOptions = providerOptions.openai ?? {};
29
+ if (isNonEmptyString(openAiProviderOptions.instructions)) {
30
+ return params;
31
+ }
32
+
33
+ return {
34
+ ...params,
35
+ prompt: params.prompt.filter((message) => message.role !== 'system'),
36
+ providerOptions: {
37
+ ...providerOptions,
38
+ openai: {
39
+ ...openAiProviderOptions,
40
+ instructions,
41
+ } as JSONObject,
42
+ },
43
+ } satisfies LanguageModelV3CallOptions;
44
+ },
45
+ };
46
+ }