@modelprofile.com/flexharness-providers 7.0.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.
Files changed (130) hide show
  1. package/dist_ts_media_openai/image.d.ts +46 -0
  2. package/dist_ts_media_openai/image.js +110 -0
  3. package/dist_ts_media_openai/index.d.ts +10 -0
  4. package/dist_ts_media_openai/index.js +16 -0
  5. package/dist_ts_media_openai/plugins.d.ts +3 -0
  6. package/dist_ts_media_openai/plugins.js +4 -0
  7. package/dist_ts_openai_account/index.d.ts +6 -0
  8. package/dist_ts_openai_account/index.js +7 -0
  9. package/dist_ts_openai_account/plugins.d.ts +7 -0
  10. package/dist_ts_openai_account/plugins.js +6 -0
  11. package/dist_ts_openai_account/smartai.providers.credentials.d.ts +7 -0
  12. package/dist_ts_openai_account/smartai.providers.credentials.js +126 -0
  13. package/dist_ts_openai_account/smartai.providers.error.d.ts +11 -0
  14. package/dist_ts_openai_account/smartai.providers.error.js +30 -0
  15. package/dist_ts_openai_account/smartai.providers.interfaces.d.ts +161 -0
  16. package/dist_ts_openai_account/smartai.providers.interfaces.js +2 -0
  17. package/dist_ts_openai_account/smartai.providers.openai.d.ts +29 -0
  18. package/dist_ts_openai_account/smartai.providers.openai.js +1075 -0
  19. package/dist_ts_openai_account/smartai.providers.registry.d.ts +11 -0
  20. package/dist_ts_openai_account/smartai.providers.registry.js +53 -0
  21. package/dist_ts_openai_auth/index.d.ts +6 -0
  22. package/dist_ts_openai_auth/index.js +10 -0
  23. package/dist_ts_openai_auth/interfaces.d.ts +71 -0
  24. package/dist_ts_openai_auth/interfaces.js +2 -0
  25. package/dist_ts_openai_auth/smartai.auth.openai.d.ts +34 -0
  26. package/dist_ts_openai_auth/smartai.auth.openai.js +603 -0
  27. package/dist_ts_openai_auth/smartai.middleware.openai.d.ts +6 -0
  28. package/dist_ts_openai_auth/smartai.middleware.openai.js +47 -0
  29. package/dist_ts_openai_auth_files/index.d.ts +43 -0
  30. package/dist_ts_openai_auth_files/index.js +377 -0
  31. package/dist_ts_openai_auth_files/plugins.d.ts +9 -0
  32. package/dist_ts_openai_auth_files/plugins.js +10 -0
  33. package/dist_ts_providers/anthropic/index.d.ts +8 -0
  34. package/dist_ts_providers/anthropic/index.js +13 -0
  35. package/dist_ts_providers/anthropic/plugins.d.ts +4 -0
  36. package/dist_ts_providers/anthropic/plugins.js +4 -0
  37. package/dist_ts_providers/anthropic/smartai.middleware.anthropic.d.ts +8 -0
  38. package/dist_ts_providers/anthropic/smartai.middleware.anthropic.js +10 -0
  39. package/dist_ts_providers/google/index.d.ts +5 -0
  40. package/dist_ts_providers/google/index.js +6 -0
  41. package/dist_ts_providers/google/plugins.d.ts +3 -0
  42. package/dist_ts_providers/google/plugins.js +3 -0
  43. package/dist_ts_providers/groq/index.d.ts +5 -0
  44. package/dist_ts_providers/groq/index.js +6 -0
  45. package/dist_ts_providers/groq/plugins.d.ts +3 -0
  46. package/dist_ts_providers/groq/plugins.js +3 -0
  47. package/dist_ts_providers/index.d.ts +8 -0
  48. package/dist_ts_providers/index.js +9 -0
  49. package/dist_ts_providers/mistral/index.d.ts +5 -0
  50. package/dist_ts_providers/mistral/index.js +6 -0
  51. package/dist_ts_providers/mistral/plugins.d.ts +3 -0
  52. package/dist_ts_providers/mistral/plugins.js +3 -0
  53. package/dist_ts_providers/ollama/index.d.ts +6 -0
  54. package/dist_ts_providers/ollama/index.js +7 -0
  55. package/dist_ts_providers/ollama/interfaces.d.ts +26 -0
  56. package/dist_ts_providers/ollama/interfaces.js +2 -0
  57. package/dist_ts_providers/ollama/smartai.provider.ollama.d.ts +8 -0
  58. package/dist_ts_providers/ollama/smartai.provider.ollama.js +378 -0
  59. package/dist_ts_providers/openai/index.d.ts +15 -0
  60. package/dist_ts_providers/openai/index.js +14 -0
  61. package/dist_ts_providers/openai/plugins.d.ts +6 -0
  62. package/dist_ts_providers/openai/plugins.js +4 -0
  63. package/dist_ts_providers/perplexity/index.d.ts +5 -0
  64. package/dist_ts_providers/perplexity/index.js +6 -0
  65. package/dist_ts_providers/perplexity/plugins.d.ts +3 -0
  66. package/dist_ts_providers/perplexity/plugins.js +3 -0
  67. package/dist_ts_providers/xai/index.d.ts +5 -0
  68. package/dist_ts_providers/xai/index.js +6 -0
  69. package/dist_ts_providers/xai/plugins.d.ts +3 -0
  70. package/dist_ts_providers/xai/plugins.js +3 -0
  71. package/dist_ts_research/index.d.ts +19 -0
  72. package/dist_ts_research/index.js +98 -0
  73. package/dist_ts_research/plugins.d.ts +2 -0
  74. package/dist_ts_research/plugins.js +3 -0
  75. package/license.md +21 -0
  76. package/openai-codex-license.txt +201 -0
  77. package/package.json +105 -0
  78. package/readme.md +62 -0
  79. package/third-party-notices.md +19 -0
  80. package/ts_media_openai/image.ts +147 -0
  81. package/ts_media_openai/index.ts +26 -0
  82. package/ts_media_openai/plugins.ts +3 -0
  83. package/ts_media_openai/readme.md +37 -0
  84. package/ts_openai_account/index.ts +11 -0
  85. package/ts_openai_account/plugins.ts +9 -0
  86. package/ts_openai_account/readme.md +37 -0
  87. package/ts_openai_account/smartai.providers.credentials.ts +163 -0
  88. package/ts_openai_account/smartai.providers.error.ts +49 -0
  89. package/ts_openai_account/smartai.providers.interfaces.ts +222 -0
  90. package/ts_openai_account/smartai.providers.openai.ts +1209 -0
  91. package/ts_openai_account/smartai.providers.registry.ts +57 -0
  92. package/ts_openai_auth/index.ts +12 -0
  93. package/ts_openai_auth/interfaces.ts +89 -0
  94. package/ts_openai_auth/readme.md +37 -0
  95. package/ts_openai_auth/smartai.auth.openai.ts +789 -0
  96. package/ts_openai_auth/smartai.middleware.openai.ts +55 -0
  97. package/ts_openai_auth_files/index.ts +499 -0
  98. package/ts_openai_auth_files/plugins.ts +13 -0
  99. package/ts_openai_auth_files/readme.md +37 -0
  100. package/ts_providers/anthropic/index.ts +20 -0
  101. package/ts_providers/anthropic/plugins.ts +4 -0
  102. package/ts_providers/anthropic/readme.md +37 -0
  103. package/ts_providers/anthropic/smartai.middleware.anthropic.ts +12 -0
  104. package/ts_providers/google/index.ts +8 -0
  105. package/ts_providers/google/plugins.ts +3 -0
  106. package/ts_providers/google/readme.md +37 -0
  107. package/ts_providers/groq/index.ts +8 -0
  108. package/ts_providers/groq/plugins.ts +3 -0
  109. package/ts_providers/groq/readme.md +37 -0
  110. package/ts_providers/index.ts +8 -0
  111. package/ts_providers/mistral/index.ts +8 -0
  112. package/ts_providers/mistral/plugins.ts +3 -0
  113. package/ts_providers/mistral/readme.md +37 -0
  114. package/ts_providers/ollama/index.ts +8 -0
  115. package/ts_providers/ollama/interfaces.ts +28 -0
  116. package/ts_providers/ollama/readme.md +37 -0
  117. package/ts_providers/ollama/smartai.provider.ollama.ts +426 -0
  118. package/ts_providers/openai/index.ts +29 -0
  119. package/ts_providers/openai/plugins.ts +6 -0
  120. package/ts_providers/openai/readme.md +37 -0
  121. package/ts_providers/perplexity/index.ts +8 -0
  122. package/ts_providers/perplexity/plugins.ts +3 -0
  123. package/ts_providers/perplexity/readme.md +37 -0
  124. package/ts_providers/readme.md +62 -0
  125. package/ts_providers/xai/index.ts +8 -0
  126. package/ts_providers/xai/plugins.ts +3 -0
  127. package/ts_providers/xai/readme.md +37 -0
  128. package/ts_research/index.ts +120 -0
  129. package/ts_research/plugins.ts +2 -0
  130. package/ts_research/readme.md +37 -0
@@ -0,0 +1,55 @@
1
+ import type { JSONObject, LanguageModelV4CallOptions, LanguageModelV4Middleware } 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: LanguageModelV4CallOptions['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
+ const hasUnsupportedChatGptCacheRetention = (options: JSONObject): boolean => {
15
+ return options.promptCacheRetention !== undefined || options.prompt_cache_retention !== undefined;
16
+ };
17
+
18
+ /**
19
+ * ChatGPT's Codex backend requires top-level Responses API instructions.
20
+ * The standard OpenAI provider otherwise serializes system prompts as input items.
21
+ */
22
+ export function createOpenAiChatGptInstructionsMiddleware(): LanguageModelV4Middleware {
23
+ return {
24
+ specificationVersion: 'v4',
25
+ transformParams: async ({ params }) => {
26
+ const instructions = getSystemInstructions(params.prompt);
27
+ const providerOptions = params.providerOptions ?? {};
28
+ const openAiProviderOptions = providerOptions.openai ?? {};
29
+ const shouldApplyInstructions = !!instructions && !isNonEmptyString(openAiProviderOptions.instructions);
30
+ const shouldStripCacheRetention = hasUnsupportedChatGptCacheRetention(openAiProviderOptions);
31
+
32
+ if (!shouldApplyInstructions && !shouldStripCacheRetention) {
33
+ return params;
34
+ }
35
+
36
+ const nextOpenAiProviderOptions: JSONObject = { ...openAiProviderOptions };
37
+ delete nextOpenAiProviderOptions.promptCacheRetention;
38
+ delete nextOpenAiProviderOptions.prompt_cache_retention;
39
+ if (shouldApplyInstructions) {
40
+ nextOpenAiProviderOptions.instructions = instructions;
41
+ }
42
+
43
+ return {
44
+ ...params,
45
+ prompt: shouldApplyInstructions
46
+ ? params.prompt.filter((message) => message.role !== 'system')
47
+ : params.prompt,
48
+ providerOptions: {
49
+ ...providerOptions,
50
+ openai: nextOpenAiProviderOptions,
51
+ },
52
+ } satisfies LanguageModelV4CallOptions;
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,499 @@
1
+ import * as plugins from './plugins.js';
2
+ import {
3
+ parseOpenAiChatGptTokenInfo,
4
+ refreshOpenAiChatGptTokenData,
5
+ } from '@modelprofile.com/flexharness-providers/auth';
6
+ import type {
7
+ IOpenAiChatGptAuthOptions,
8
+ IOpenAiChatGptTokenData,
9
+ IOpenAiChatGptTokenInfo,
10
+ } from '@modelprofile.com/flexharness-providers/auth';
11
+
12
+ export type TOpenAiChatGptAuthSource = 'smartai' | 'opencode' | 'codex';
13
+ export type TOpenAiChatGptAuthFileFormat = TOpenAiChatGptAuthSource | 'auto';
14
+
15
+ export interface IOpenAiChatGptAuthSourceConfig {
16
+ source: TOpenAiChatGptAuthSource;
17
+ filePath?: string;
18
+ format?: TOpenAiChatGptAuthFileFormat;
19
+ writeBack?: boolean;
20
+ }
21
+
22
+ export interface IOpenAiChatGptAuthSourceInspection {
23
+ source: TOpenAiChatGptAuthSource;
24
+ filePath: string;
25
+ exists: boolean;
26
+ usable: boolean;
27
+ expired?: boolean;
28
+ accountId?: string;
29
+ email?: string;
30
+ plan?: string;
31
+ expiresAt?: string;
32
+ error?: string;
33
+ }
34
+
35
+ export interface IInspectOpenAiChatGptAuthSourcesOptions {
36
+ sources?: Array<TOpenAiChatGptAuthSource | IOpenAiChatGptAuthSourceConfig>;
37
+ homeDir?: string;
38
+ now?: Date;
39
+ }
40
+
41
+ export interface IResolveOpenAiChatGptAuthOptions extends IInspectOpenAiChatGptAuthSourcesOptions {
42
+ refresh?: 'ifNeeded' | false;
43
+ writeBack?: Partial<Record<TOpenAiChatGptAuthSource, boolean>>;
44
+ authOptions?: IOpenAiChatGptAuthOptions;
45
+ }
46
+
47
+ export interface IResolvedOpenAiChatGptAuth {
48
+ source: TOpenAiChatGptAuthSource;
49
+ filePath: string;
50
+ tokenData: IOpenAiChatGptTokenData;
51
+ refreshed: boolean;
52
+ }
53
+
54
+ interface INormalizedOpenAiChatGptAuthSourceConfig {
55
+ source: TOpenAiChatGptAuthSource;
56
+ filePath: string;
57
+ format: TOpenAiChatGptAuthFileFormat;
58
+ writeBack?: boolean;
59
+ }
60
+
61
+ const defaultSources: TOpenAiChatGptAuthSource[] = ['smartai', 'opencode', 'codex'];
62
+ const refreshWindowMs = 5 * 60 * 1000;
63
+ const lockWaitTimeoutMs = 30_000;
64
+ const lockStaleMs = 60_000;
65
+ const maximumAuthFileBytes = 1024 * 1024;
66
+ const inProcessFileLocks = new Map<string, Promise<void>>();
67
+
68
+ const canonicalizeFilePath = async (filePathArg: string): Promise<string> => {
69
+ const resolvedPath = plugins.path.resolve(filePathArg);
70
+ await plugins.fs.mkdir(plugins.path.dirname(resolvedPath), { recursive: true, mode: 0o700 });
71
+ try {
72
+ return await plugins.fs.realpath(resolvedPath);
73
+ } catch (errorArg) {
74
+ if ((errorArg as NodeJS.ErrnoException).code !== 'ENOENT') throw errorArg;
75
+ const parentPath = await plugins.fs.realpath(plugins.path.dirname(resolvedPath));
76
+ return plugins.path.join(parentPath, plugins.path.basename(resolvedPath));
77
+ }
78
+ };
79
+
80
+ const acquireCrossProcessFileLock = async (
81
+ filePathArg: string,
82
+ ): Promise<() => Promise<void>> => plugins.properLockfile.lock(filePathArg, {
83
+ lockfilePath: `${filePathArg}.smartai.lock`,
84
+ realpath: false,
85
+ stale: lockStaleMs,
86
+ update: 10_000,
87
+ retries: {
88
+ retries: Math.ceil(lockWaitTimeoutMs / 50),
89
+ factor: 1,
90
+ minTimeout: 50,
91
+ maxTimeout: 50,
92
+ randomize: false,
93
+ },
94
+ });
95
+
96
+ const withAuthFileLock = async <TResult>(
97
+ filePathArg: string,
98
+ taskArg: (canonicalFilePathArg: string) => Promise<TResult>,
99
+ ): Promise<TResult> => {
100
+ const canonicalFilePath = await canonicalizeFilePath(filePathArg);
101
+ const previous = inProcessFileLocks.get(canonicalFilePath) ?? Promise.resolve();
102
+ let releaseInProcess!: () => void;
103
+ const current = new Promise<void>((resolve) => {
104
+ releaseInProcess = resolve;
105
+ });
106
+ const tail = previous.then(() => current);
107
+ inProcessFileLocks.set(canonicalFilePath, tail);
108
+ await previous;
109
+ try {
110
+ const releaseCrossProcess = await acquireCrossProcessFileLock(canonicalFilePath);
111
+ try {
112
+ return await taskArg(canonicalFilePath);
113
+ } finally {
114
+ await releaseCrossProcess();
115
+ }
116
+ } finally {
117
+ releaseInProcess();
118
+ if (inProcessFileLocks.get(canonicalFilePath) === tail) {
119
+ inProcessFileLocks.delete(canonicalFilePath);
120
+ }
121
+ }
122
+ };
123
+
124
+ export const getDefaultOpenAiChatGptAuthPath = (
125
+ source: TOpenAiChatGptAuthSource,
126
+ homeDir = plugins.os.homedir(),
127
+ ): string => {
128
+ switch (source) {
129
+ case 'smartai':
130
+ return plugins.path.join(homeDir, '.git.zone', 'ide', 'openai-chatgpt-auth.json');
131
+ case 'opencode':
132
+ return plugins.path.join(homeDir, '.local', 'share', 'opencode', 'auth.json');
133
+ case 'codex':
134
+ return plugins.path.join(homeDir, '.codex', 'auth.json');
135
+ default:
136
+ throw new Error(`Unsupported OpenAI ChatGPT auth source: ${source satisfies never}`);
137
+ }
138
+ };
139
+
140
+ export const normalizeOpenAiChatGptAuth = (
141
+ input: unknown,
142
+ format: TOpenAiChatGptAuthFileFormat = 'auto',
143
+ ): IOpenAiChatGptTokenData | undefined => {
144
+ if (format === 'auto') {
145
+ return normalizeOpenAiChatGptAuth(input, 'smartai')
146
+ ?? normalizeOpenAiChatGptAuth(input, 'opencode')
147
+ ?? normalizeOpenAiChatGptAuth(input, 'codex');
148
+ }
149
+
150
+ if (!isRecord(input)) return undefined;
151
+ if (format === 'opencode') {
152
+ return normalizeOpenCodeAuth(input);
153
+ }
154
+ return normalizeTokenObject(isRecord(input.tokens) ? input.tokens : input);
155
+ };
156
+
157
+ const readBoundedAuthFileText = async (filePathArg: string): Promise<string> => {
158
+ const canonicalFilePath = await plugins.fs.realpath(filePathArg);
159
+ let handle: plugins.FileHandle | undefined;
160
+ try {
161
+ handle = await plugins.fs.open(
162
+ canonicalFilePath,
163
+ plugins.fsConstants.O_RDONLY | plugins.fsConstants.O_NONBLOCK,
164
+ );
165
+ const stat = await handle.stat();
166
+ if (!stat.isFile() || stat.size > maximumAuthFileBytes) {
167
+ throw new Error('Authentication file is not a bounded regular file.');
168
+ }
169
+ const bytes = new Uint8Array(maximumAuthFileBytes + 1);
170
+ let offset = 0;
171
+ while (offset < bytes.byteLength) {
172
+ const result = await handle.read(bytes, offset, bytes.byteLength - offset, offset);
173
+ if (result.bytesRead === 0) break;
174
+ offset += result.bytesRead;
175
+ }
176
+ if (offset > maximumAuthFileBytes) {
177
+ throw new Error('Authentication file exceeds its size limit.');
178
+ }
179
+ return new TextDecoder().decode(bytes.subarray(0, offset));
180
+ } finally {
181
+ await handle?.close();
182
+ }
183
+ };
184
+
185
+ const readBoundedAuthJson = async (filePathArg: string): Promise<unknown> =>
186
+ JSON.parse(await readBoundedAuthFileText(filePathArg)) as unknown;
187
+
188
+ export const readOpenAiChatGptAuthFile = async (
189
+ filePath: string,
190
+ format: TOpenAiChatGptAuthFileFormat = 'auto',
191
+ ): Promise<IOpenAiChatGptTokenData | undefined> => {
192
+ const parsed = await readBoundedAuthJson(filePath);
193
+ return normalizeOpenAiChatGptAuth(parsed, format);
194
+ };
195
+
196
+ export const inspectOpenAiChatGptAuthSources = async (
197
+ options: IInspectOpenAiChatGptAuthSourcesOptions = {},
198
+ ): Promise<IOpenAiChatGptAuthSourceInspection[]> => {
199
+ const now = options.now ?? new Date();
200
+ const sourceConfigs = normalizeSourceConfigs(options.sources, options.homeDir);
201
+ return Promise.all(sourceConfigs.map(async (sourceConfig) => {
202
+ try {
203
+ const tokenData = await readOpenAiChatGptAuthFile(sourceConfig.filePath!, sourceConfig.format ?? sourceConfig.source);
204
+ if (!tokenData) {
205
+ return {
206
+ source: sourceConfig.source,
207
+ filePath: sourceConfig.filePath!,
208
+ exists: true,
209
+ usable: false,
210
+ error: 'No OpenAI ChatGPT auth token found in file.',
211
+ };
212
+ }
213
+ return toInspection(sourceConfig.source, sourceConfig.filePath!, tokenData, now);
214
+ } catch (error) {
215
+ const nodeError = error as NodeJS.ErrnoException;
216
+ return {
217
+ source: sourceConfig.source,
218
+ filePath: sourceConfig.filePath!,
219
+ exists: nodeError.code !== 'ENOENT',
220
+ usable: false,
221
+ error: nodeError.code === 'ENOENT' ? undefined : 'Authentication file could not be read.',
222
+ };
223
+ }
224
+ }));
225
+ };
226
+
227
+ export const resolveOpenAiChatGptAuth = async (
228
+ options: IResolveOpenAiChatGptAuthOptions = {},
229
+ ): Promise<IResolvedOpenAiChatGptAuth | undefined> => {
230
+ const now = options.now ?? new Date();
231
+ const sourceConfigs = normalizeSourceConfigs(options.sources, options.homeDir);
232
+ const refresh = options.refresh ?? 'ifNeeded';
233
+
234
+ for (const sourceConfig of sourceConfigs) {
235
+ let tokenData: IOpenAiChatGptTokenData | undefined;
236
+ try {
237
+ tokenData = await readOpenAiChatGptAuthFile(sourceConfig.filePath!, sourceConfig.format ?? sourceConfig.source);
238
+ } catch (error) {
239
+ const nodeError = error as NodeJS.ErrnoException;
240
+ if (nodeError.code === 'ENOENT') continue;
241
+ continue;
242
+ }
243
+ if (!tokenData) continue;
244
+
245
+ const shouldRefresh = refresh === 'ifNeeded' && shouldRefreshToken(tokenData, now);
246
+ const writeBack = sourceConfig.writeBack ?? options.writeBack?.[sourceConfig.source] ?? sourceConfig.source === 'smartai';
247
+ if (writeBack && sourceConfig.format === 'auto') {
248
+ throw new Error('The auto auth-file format cannot be used for write-back.');
249
+ }
250
+ if (!shouldRefresh) {
251
+ return { source: sourceConfig.source, filePath: sourceConfig.filePath!, tokenData, refreshed: false };
252
+ }
253
+
254
+ if (!writeBack) {
255
+ if (!isExpired(tokenData, now)) {
256
+ return { source: sourceConfig.source, filePath: sourceConfig.filePath!, tokenData, refreshed: false };
257
+ }
258
+ continue;
259
+ }
260
+
261
+ try {
262
+ const resolved = await withAuthFileLock(sourceConfig.filePath!, async (canonicalFilePath) => {
263
+ const latestTokenData = await readOpenAiChatGptAuthFile(
264
+ canonicalFilePath,
265
+ sourceConfig.format ?? sourceConfig.source,
266
+ );
267
+ if (!latestTokenData) return undefined;
268
+ if (!shouldRefreshToken(latestTokenData, now)) {
269
+ return {
270
+ source: sourceConfig.source,
271
+ filePath: sourceConfig.filePath!,
272
+ tokenData: latestTokenData,
273
+ refreshed: false,
274
+ };
275
+ }
276
+ const refreshed = await refreshOpenAiChatGptTokenData(
277
+ latestTokenData,
278
+ options.authOptions ?? {},
279
+ );
280
+ await writeOpenAiChatGptAuthFileUnlocked(
281
+ canonicalFilePath,
282
+ refreshed,
283
+ sourceConfig.format ?? sourceConfig.source,
284
+ );
285
+ return {
286
+ source: sourceConfig.source,
287
+ filePath: sourceConfig.filePath!,
288
+ tokenData: refreshed,
289
+ refreshed: true,
290
+ };
291
+ });
292
+ if (resolved) return resolved;
293
+ } catch {
294
+ continue;
295
+ }
296
+ }
297
+
298
+ return undefined;
299
+ };
300
+
301
+ export const writeOpenAiChatGptAuthFile = async (
302
+ filePath: string,
303
+ tokenData: IOpenAiChatGptTokenData,
304
+ format: TOpenAiChatGptAuthFileFormat = 'smartai',
305
+ ): Promise<void> => {
306
+ if (format === 'auto') throw new Error('The auto auth-file format cannot be written.');
307
+ return withAuthFileLock(filePath, async (canonicalFilePath) => {
308
+ await writeOpenAiChatGptAuthFileUnlocked(canonicalFilePath, tokenData, format);
309
+ });
310
+ };
311
+
312
+ const writeOpenAiChatGptAuthFileUnlocked = async (
313
+ filePath: string,
314
+ tokenData: IOpenAiChatGptTokenData,
315
+ format: TOpenAiChatGptAuthFileFormat,
316
+ ): Promise<void> => {
317
+ const current = await readJsonFileIfExists(filePath);
318
+ const payload = format === 'opencode'
319
+ ? toOpenCodeAuthFile(current, tokenData)
320
+ : format === 'codex'
321
+ ? toCodexAuthFile(current, tokenData)
322
+ : toSmartAiAuthFile(tokenData);
323
+ await writeJsonAtomic(filePath, payload);
324
+ };
325
+
326
+ const normalizeSourceConfigs = (
327
+ sources: Array<TOpenAiChatGptAuthSource | IOpenAiChatGptAuthSourceConfig> | undefined,
328
+ homeDir = plugins.os.homedir(),
329
+ ): INormalizedOpenAiChatGptAuthSourceConfig[] => {
330
+ return (sources ?? defaultSources).map((sourceInput) => {
331
+ const sourceConfig = typeof sourceInput === 'string' ? { source: sourceInput } : sourceInput;
332
+ return {
333
+ source: sourceConfig.source,
334
+ filePath: sourceConfig.filePath ?? getDefaultOpenAiChatGptAuthPath(sourceConfig.source, homeDir),
335
+ format: sourceConfig.format ?? sourceConfig.source,
336
+ writeBack: sourceConfig.writeBack,
337
+ };
338
+ });
339
+ };
340
+
341
+ const toInspection = (
342
+ source: TOpenAiChatGptAuthSource,
343
+ filePath: string,
344
+ tokenData: IOpenAiChatGptTokenData,
345
+ now: Date,
346
+ ): IOpenAiChatGptAuthSourceInspection => {
347
+ const expired = isExpired(tokenData, now);
348
+ return {
349
+ source,
350
+ filePath,
351
+ exists: true,
352
+ usable: !expired,
353
+ expired,
354
+ accountId: tokenData.accountId ?? tokenData.tokenInfo.chatgptAccountId,
355
+ email: tokenData.tokenInfo.email,
356
+ plan: tokenData.tokenInfo.chatgptPlanType,
357
+ expiresAt: tokenData.tokenInfo.expiresAt,
358
+ };
359
+ };
360
+
361
+ const normalizeTokenObject = (input: Record<string, unknown>): IOpenAiChatGptTokenData | undefined => {
362
+ const accessToken = stringValue(input.accessToken) ?? stringValue(input.access_token);
363
+ const refreshToken = stringValue(input.refreshToken) ?? stringValue(input.refresh_token);
364
+ const idToken = stringValue(input.idToken) ?? stringValue(input.id_token);
365
+ const accountId = stringValue(input.accountId) ?? stringValue(input.account_id);
366
+ return createTokenDataFromValues({ accessToken, refreshToken, idToken, accountId });
367
+ };
368
+
369
+ const normalizeOpenCodeAuth = (input: Record<string, unknown>): IOpenAiChatGptTokenData | undefined => {
370
+ if (!isRecord(input.openai)) return undefined;
371
+ const accessToken = stringValue(input.openai.access);
372
+ const refreshToken = stringValue(input.openai.refresh);
373
+ const accountId = stringValue(input.openai.accountId);
374
+ const expiresAt = typeof input.openai.expires === 'number' ? new Date(input.openai.expires).toISOString() : undefined;
375
+ return createTokenDataFromValues({ accessToken, refreshToken, accountId, fallbackExpiresAt: expiresAt });
376
+ };
377
+
378
+ const createTokenDataFromValues = (input: {
379
+ accessToken?: string;
380
+ refreshToken?: string;
381
+ idToken?: string;
382
+ accountId?: string;
383
+ fallbackExpiresAt?: string;
384
+ }): IOpenAiChatGptTokenData | undefined => {
385
+ if (!input.accessToken || !input.refreshToken) return undefined;
386
+ const parseToken = input.idToken ?? input.accessToken;
387
+ const tokenInfo = parseTokenInfo(parseToken, input.accountId, input.fallbackExpiresAt);
388
+ if (
389
+ input.accountId !== undefined
390
+ && tokenInfo.chatgptAccountId !== undefined
391
+ && input.accountId !== tokenInfo.chatgptAccountId
392
+ ) {
393
+ return undefined;
394
+ }
395
+ const accountId = tokenInfo.chatgptAccountId ?? input.accountId;
396
+ return {
397
+ accessToken: input.accessToken,
398
+ refreshToken: input.refreshToken,
399
+ idToken: input.idToken,
400
+ accountId,
401
+ tokenInfo: { ...tokenInfo, chatgptAccountId: accountId },
402
+ };
403
+ };
404
+
405
+ const parseTokenInfo = (token: string, accountId?: string, fallbackExpiresAt?: string): IOpenAiChatGptTokenInfo => {
406
+ try {
407
+ const tokenInfo = parseOpenAiChatGptTokenInfo(token);
408
+ return {
409
+ ...tokenInfo,
410
+ chatgptAccountId: tokenInfo.chatgptAccountId ?? accountId,
411
+ expiresAt: tokenInfo.expiresAt ?? fallbackExpiresAt,
412
+ };
413
+ } catch {
414
+ return {
415
+ chatgptAccountId: accountId,
416
+ chatgptAccountIsFedramp: false,
417
+ expiresAt: fallbackExpiresAt,
418
+ rawJwt: token,
419
+ };
420
+ }
421
+ };
422
+
423
+ const toSmartAiAuthFile = (tokenData: IOpenAiChatGptTokenData): Record<string, unknown> => ({
424
+ accessToken: tokenData.accessToken,
425
+ refreshToken: tokenData.refreshToken,
426
+ idToken: tokenData.idToken,
427
+ accountId: tokenData.accountId,
428
+ tokenInfo: tokenData.tokenInfo,
429
+ });
430
+
431
+ const toCodexAuthFile = (current: Record<string, unknown>, tokenData: IOpenAiChatGptTokenData): Record<string, unknown> => ({
432
+ ...current,
433
+ OPENAI_API_KEY: current.OPENAI_API_KEY ?? null,
434
+ tokens: {
435
+ ...(isRecord(current.tokens) ? current.tokens : {}),
436
+ id_token: tokenData.idToken,
437
+ access_token: tokenData.accessToken,
438
+ refresh_token: tokenData.refreshToken,
439
+ account_id: tokenData.accountId ?? tokenData.tokenInfo.chatgptAccountId,
440
+ },
441
+ last_refresh: new Date().toISOString(),
442
+ });
443
+
444
+ const toOpenCodeAuthFile = (current: Record<string, unknown>, tokenData: IOpenAiChatGptTokenData): Record<string, unknown> => ({
445
+ ...current,
446
+ openai: {
447
+ ...(isRecord(current.openai) ? current.openai : {}),
448
+ type: 'oauth',
449
+ refresh: tokenData.refreshToken,
450
+ access: tokenData.accessToken,
451
+ expires: tokenData.tokenInfo.expiresAt ? Date.parse(tokenData.tokenInfo.expiresAt) : undefined,
452
+ accountId: tokenData.accountId ?? tokenData.tokenInfo.chatgptAccountId,
453
+ },
454
+ });
455
+
456
+ const shouldRefreshToken = (tokenData: IOpenAiChatGptTokenData, now: Date): boolean => {
457
+ if (!tokenData.tokenInfo.expiresAt) return false;
458
+ return Date.parse(tokenData.tokenInfo.expiresAt) - now.getTime() < refreshWindowMs;
459
+ };
460
+
461
+ const isExpired = (tokenData: IOpenAiChatGptTokenData, now: Date): boolean => {
462
+ if (!tokenData.tokenInfo.expiresAt) return false;
463
+ return Date.parse(tokenData.tokenInfo.expiresAt) <= now.getTime();
464
+ };
465
+
466
+ const readJsonFileIfExists = async (filePath: string): Promise<Record<string, unknown>> => {
467
+ try {
468
+ const parsed = await readBoundedAuthJson(filePath);
469
+ return isRecord(parsed) ? parsed : {};
470
+ } catch (error) {
471
+ const nodeError = error as NodeJS.ErrnoException;
472
+ if (nodeError.code === 'ENOENT') return {};
473
+ throw error;
474
+ }
475
+ };
476
+
477
+ const writeJsonAtomic = async (filePath: string, payload: unknown): Promise<void> => {
478
+ const tempPath = `${filePath}.${plugins.crypto.randomUUID()}.tmp`;
479
+ await plugins.fs.mkdir(plugins.path.dirname(filePath), { recursive: true, mode: 0o700 });
480
+ try {
481
+ await plugins.fs.writeFile(tempPath, `${JSON.stringify(payload, undefined, 2)}\n`, {
482
+ flag: 'wx',
483
+ mode: 0o600,
484
+ });
485
+ await plugins.fs.rename(tempPath, filePath);
486
+ } finally {
487
+ await plugins.fs.unlink(tempPath).catch((errorArg: NodeJS.ErrnoException) => {
488
+ if (errorArg.code !== 'ENOENT') throw errorArg;
489
+ });
490
+ }
491
+ };
492
+
493
+ const isRecord = (value: unknown): value is Record<string, unknown> => {
494
+ return !!value && typeof value === 'object' && !Array.isArray(value);
495
+ };
496
+
497
+ const stringValue = (value: unknown): string | undefined => {
498
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
499
+ };
@@ -0,0 +1,13 @@
1
+ // node native scope
2
+ import * as crypto from 'node:crypto';
3
+ import { constants as fsConstants } from 'node:fs';
4
+ import * as fs from 'node:fs/promises';
5
+ import type { FileHandle } from 'node:fs/promises';
6
+ import * as os from 'node:os';
7
+ import * as path from 'node:path';
8
+
9
+ // third-party scope
10
+ import * as properLockfile from 'proper-lockfile';
11
+
12
+ export { crypto, fs, fsConstants, os, path, properLockfile };
13
+ export type { FileHandle };
@@ -0,0 +1,37 @@
1
+ Explicit interoperability with existing Codex, OpenCode and GitZone IDE credential files.
2
+
3
+ Part of the [FlexHarness toolbox](https://code.foss.global/modelprofile.com/flexharness).
4
+ Install the components your project uses. Provider adapters are registered explicitly;
5
+ the agent and harness do not install or register vendor SDKs.
6
+
7
+ ```sh
8
+ pnpm add @modelprofile.com/flexharness-providers
9
+ ```
10
+
11
+ See the [toolbox usage and migration guide](https://code.foss.global/modelprofile.com/flexharness#readme)
12
+ for composition examples and the SmartAI/SmartAgent migration map.
13
+
14
+ ## Issue Reporting and Security
15
+
16
+ Report issues and security concerns through [community.foss.global](https://community.foss.global).
17
+
18
+ ## License and Legal Information
19
+
20
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
21
+
22
+ **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
23
+
24
+ ### Trademarks
25
+
26
+ This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
27
+
28
+ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
29
+
30
+ ### Company Information
31
+
32
+ Task Venture Capital GmbH<br>
33
+ Registered at District Court Bremen HRB 35230 HB, Germany
34
+
35
+ For any legal inquiries or further information, please contact us via email at hello@task.vc.
36
+
37
+ By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
@@ -0,0 +1,20 @@
1
+ import * as plugins from './plugins.js';
2
+ import { createAnthropicCachingMiddleware } from './smartai.middleware.anthropic.js';
3
+
4
+ export interface IAnthropicModelOptions extends plugins.ISmartAiOptions {
5
+ provider: 'anthropic';
6
+ promptCaching?: boolean | plugins.ISmartAiCacheOptions;
7
+ }
8
+
9
+ export const createAnthropicModelProvider = (): plugins.IModelProvider<IAnthropicModelOptions> => ({
10
+ id: 'anthropic',
11
+ getModel: (options) => {
12
+ const model = plugins.createAnthropic({ apiKey: options.apiKey })(options.model);
13
+ if (options.promptCaching === false) return model;
14
+ return plugins.wrapLanguageModel({ model, middleware: createAnthropicCachingMiddleware(
15
+ typeof options.promptCaching === 'object' ? options.promptCaching : undefined,
16
+ ) });
17
+ },
18
+ });
19
+
20
+ export { createAnthropicCachingMiddleware };
@@ -0,0 +1,4 @@
1
+ import { createAnthropic } from '@ai-sdk/anthropic';
2
+ import { wrapLanguageModel } from 'ai';
3
+ export { createAnthropic, wrapLanguageModel };
4
+ export type { IModelProvider, ISmartAiOptions, ISmartAiCacheOptions } from '@modelprofile.com/flexharness-models';
@@ -0,0 +1,37 @@
1
+ Explicit anthropic model provider for FlexHarness models and the AI SDK.
2
+
3
+ Part of the [FlexHarness toolbox](https://code.foss.global/modelprofile.com/flexharness).
4
+ Install the components your project uses. Provider adapters are registered explicitly;
5
+ the agent and harness do not install or register vendor SDKs.
6
+
7
+ ```sh
8
+ pnpm add @modelprofile.com/flexharness-providers
9
+ ```
10
+
11
+ See the [toolbox usage and migration guide](https://code.foss.global/modelprofile.com/flexharness#readme)
12
+ for composition examples and the SmartAI/SmartAgent migration map.
13
+
14
+ ## Issue Reporting and Security
15
+
16
+ Report issues and security concerns through [community.foss.global](https://community.foss.global).
17
+
18
+ ## License and Legal Information
19
+
20
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
21
+
22
+ **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
23
+
24
+ ### Trademarks
25
+
26
+ This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
27
+
28
+ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
29
+
30
+ ### Company Information
31
+
32
+ Task Venture Capital GmbH<br>
33
+ Registered at District Court Bremen HRB 35230 HB, Germany
34
+
35
+ For any legal inquiries or further information, please contact us via email at hello@task.vc.
36
+
37
+ By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
@@ -0,0 +1,12 @@
1
+ import type { LanguageModelV4Middleware } from '@ai-sdk/provider';
2
+ import { createSmartAiCachingMiddleware } from '@modelprofile.com/flexharness-models';
3
+ import type { ISmartAiCacheOptions } from '@modelprofile.com/flexharness-models';
4
+
5
+ /**
6
+ * Creates middleware that adds Anthropic prompt caching directives.
7
+ * Marks the last system message and last user message with ephemeral cache control,
8
+ * reducing input token cost and latency on repeated calls.
9
+ */
10
+ export function createAnthropicCachingMiddleware(options: ISmartAiCacheOptions = {}): LanguageModelV4Middleware {
11
+ return createSmartAiCachingMiddleware({ ...options, provider: 'anthropic' });
12
+ }