@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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +3 -3
- package/dist_ts/index.js +2 -2
- package/dist_ts/smartai.auth.openai.d.ts +15 -15
- package/dist_ts/smartai.auth.openai.js +52 -46
- package/dist_ts/smartai.classes.smartai.js +12 -5
- package/dist_ts/smartai.interfaces.d.ts +10 -11
- package/dist_ts/smartai.middleware.openai.d.ts +6 -0
- package/dist_ts/smartai.middleware.openai.js +40 -0
- package/dist_ts_openai_chatgpt_auth/index.d.ts +43 -0
- package/dist_ts_openai_chatgpt_auth/index.js +249 -0
- package/package.json +6 -1
- package/readme.hints.md +2 -1
- package/readme.md +39 -8
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +21 -21
- package/ts/smartai.auth.openai.ts +85 -76
- package/ts/smartai.classes.smartai.ts +11 -4
- package/ts/smartai.interfaces.ts +10 -11
- package/ts/smartai.middleware.openai.ts +46 -0
- package/ts_openai_chatgpt_auth/index.ts +351 -0
|
@@ -1,63 +1,63 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
12
|
-
export const
|
|
13
|
-
export const
|
|
14
|
-
export const
|
|
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
|
|
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 = '
|
|
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
|
|
30
|
+
export interface IOpenAiChatGptAuthorizationCode {
|
|
31
31
|
authorizationCode: string;
|
|
32
32
|
codeChallenge: string;
|
|
33
33
|
codeVerifier: string;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
interface
|
|
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:
|
|
42
|
+
function getFetch(options: IOpenAiChatGptAuthOptions): typeof fetch {
|
|
43
43
|
const fetchFunction = options.fetch ?? globalThis.fetch;
|
|
44
44
|
if (!fetchFunction) {
|
|
45
|
-
throw new
|
|
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:
|
|
51
|
-
return (options.issuer ??
|
|
50
|
+
function getIssuer(options: IOpenAiChatGptAuthOptions): string {
|
|
51
|
+
return (options.issuer ?? OPENAI_CHATGPT_AUTH_ISSUER).replace(/\/+$/, '');
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function getClientId(options:
|
|
55
|
-
return options.clientId ??
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
127
|
+
throw new OpenAiChatGptAuthError(`OpenAI ChatGPT token could not be parsed: ${(error as Error).message}`);
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
export function
|
|
132
|
-
const claims = parseJwtPayload(
|
|
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:
|
|
144
|
+
rawJwt: token,
|
|
145
145
|
};
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
function createTokenData(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
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
|
-
|
|
159
|
-
|
|
166
|
+
idToken,
|
|
167
|
+
accountId: tokenInfo.chatgptAccountId,
|
|
168
|
+
tokenInfo,
|
|
160
169
|
};
|
|
161
170
|
}
|
|
162
171
|
|
|
163
|
-
export async function
|
|
164
|
-
options:
|
|
165
|
-
): Promise<
|
|
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
|
|
180
|
-
deviceCode:
|
|
181
|
-
options:
|
|
182
|
-
): Promise<
|
|
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
|
|
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
|
|
230
|
+
throw new OpenAiChatGptAuthError('OpenAI ChatGPT device-code login timed out.');
|
|
222
231
|
}
|
|
223
232
|
|
|
224
|
-
export async function
|
|
225
|
-
authorizationCode:
|
|
226
|
-
options:
|
|
227
|
-
): Promise<
|
|
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
|
|
244
|
+
}), options) as IOpenAiChatGptTokenResponse;
|
|
236
245
|
|
|
237
246
|
return createTokenData(response);
|
|
238
247
|
}
|
|
239
248
|
|
|
240
|
-
export function
|
|
241
|
-
tokenData:
|
|
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.
|
|
248
|
-
throw new
|
|
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
|
|
253
|
-
deviceCode:
|
|
254
|
-
options:
|
|
255
|
-
): Promise<
|
|
256
|
-
const authorizationCode = await
|
|
257
|
-
const tokenData = await
|
|
258
|
-
|
|
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
|
|
263
|
-
tokenData:
|
|
264
|
-
options:
|
|
265
|
-
): Promise<
|
|
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
|
|
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
|
|
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.
|
|
286
|
-
const isFedrampAccount = credentials.
|
|
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 ??
|
|
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 ??
|
|
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 {
|
|
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.
|
|
28
|
-
?
|
|
28
|
+
options.openAiChatGptAuth
|
|
29
|
+
? createOpenAiChatGptProviderSettings(options.openAiChatGptAuth)
|
|
29
30
|
: { apiKey: options.apiKey },
|
|
30
31
|
);
|
|
31
|
-
|
|
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 });
|
package/ts/smartai.interfaces.ts
CHANGED
|
@@ -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
|
|
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
|
|
28
|
+
export interface IOpenAiChatGptAuthCredentials {
|
|
29
29
|
accessToken: string;
|
|
30
30
|
refreshToken?: string;
|
|
31
31
|
idToken?: string;
|
|
32
32
|
accountId?: string;
|
|
33
|
-
|
|
33
|
+
tokenInfo?: IOpenAiChatGptTokenInfo;
|
|
34
34
|
baseUrl?: string;
|
|
35
35
|
originator?: string;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export interface
|
|
38
|
+
export interface IOpenAiChatGptTokenData extends IOpenAiChatGptAuthCredentials {
|
|
39
39
|
refreshToken: string;
|
|
40
|
-
|
|
41
|
-
idTokenInfo: IOpenAiMaxIdTokenInfo;
|
|
40
|
+
tokenInfo: IOpenAiChatGptTokenInfo;
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
export interface
|
|
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
|
|
50
|
+
export interface IOpenAiChatGptAuthOptions {
|
|
52
51
|
issuer?: string;
|
|
53
52
|
clientId?: string;
|
|
54
53
|
fetch?: typeof fetch;
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
export interface
|
|
56
|
+
export interface IOpenAiChatGptDeviceCodePollOptions extends IOpenAiChatGptAuthOptions {
|
|
58
57
|
timeoutMs?: number;
|
|
59
58
|
sleep?: (ms: number) => Promise<void>;
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
export interface
|
|
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
|
-
|
|
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
|
+
}
|