@monotykamary/pi-better-openai 0.1.35 → 0.1.36
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/README.md +8 -0
- package/index.ts +3 -0
- package/package.json +3 -1
- package/src/codex-models.ts +203 -0
- package/src/config.ts +1 -0
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ Usage display, image generation, and live voice require pi's `openai-codex` OAut
|
|
|
29
29
|
|
|
30
30
|
## Features
|
|
31
31
|
|
|
32
|
+
- GPT-6 Astra and Daybreak Blue/Red model fallbacks for the built-in `openai-codex` provider.
|
|
32
33
|
- Fast mode for supported OpenAI models, toggled with `/fast` or in `/openai-settings`.
|
|
33
34
|
- OpenAI subscription usage display via `/openai-usage` and the footer.
|
|
34
35
|
- Interactive settings picker via `/openai-settings`.
|
|
@@ -61,6 +62,7 @@ Default supported models:
|
|
|
61
62
|
[
|
|
62
63
|
"openai/gpt-5.4",
|
|
63
64
|
"openai/gpt-5.5",
|
|
65
|
+
"openai-codex/gpt-6-astra",
|
|
64
66
|
"openai-codex/gpt-5.6-sol",
|
|
65
67
|
"openai-codex/gpt-5.6-terra",
|
|
66
68
|
"openai-codex/gpt-5.6-luna",
|
|
@@ -111,6 +113,12 @@ Example config:
|
|
|
111
113
|
}
|
|
112
114
|
```
|
|
113
115
|
|
|
116
|
+
## Codex model fallbacks
|
|
117
|
+
|
|
118
|
+
The extension adds `gpt-6-astra`, `gpt-daybreak-blue-latest`, and `gpt-daybreak-red-latest` to the built-in `openai-codex` provider without requiring local `models.json` entries. Existing built-in models remain available, and metadata from pi's live catalog takes precedence when pi publishes an official entry with the same ID.
|
|
119
|
+
|
|
120
|
+
Daybreak models require separate OpenAI approval and provisioning. pi currently exposes reasoning levels through `max`; Codex's `ultra` automatic-delegation mode is not a pi thinking level.
|
|
121
|
+
|
|
114
122
|
## Live voice
|
|
115
123
|
|
|
116
124
|
Run `/live` or press `Ctrl+Shift+L` to open the realtime voice panel. `Ctrl+L` remains pi's model selector, so the extension deliberately uses the shifted chord. While live mode has focus:
|
package/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
SettingsList,
|
|
13
13
|
type SettingsListTheme,
|
|
14
14
|
} from "@earendil-works/pi-tui";
|
|
15
|
+
import { registerOpenAICodexModels } from "./src/codex-models.ts";
|
|
15
16
|
import { CONFIG_BASENAME, STATUS_KEY } from "./src/identity.ts";
|
|
16
17
|
import {
|
|
17
18
|
formatTokens,
|
|
@@ -209,6 +210,8 @@ function textPanel(title: string, lines: string[], done: () => void) {
|
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
export default function betterOpenAI(pi: ExtensionAPI): void {
|
|
213
|
+
registerOpenAICodexModels(pi);
|
|
214
|
+
|
|
212
215
|
const fastController = new FastController(SERVICE_TIER);
|
|
213
216
|
let cachedConfig: ResolvedConfig | undefined;
|
|
214
217
|
let footerTotals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monotykamary/pi-better-openai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"description": "Improve OpenAI in pi with fast mode, usage stats, realtime voice, image generation, and footer polish.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fast",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"ws": "8.21.2"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
+
"@earendil-works/pi-ai": "0.85.0",
|
|
65
66
|
"@earendil-works/pi-coding-agent": "0.85.0",
|
|
66
67
|
"@earendil-works/pi-tui": "0.85.0",
|
|
67
68
|
"@types/node": "~22.19.0",
|
|
@@ -73,6 +74,7 @@
|
|
|
73
74
|
"vitest": "^4.1.5"
|
|
74
75
|
},
|
|
75
76
|
"peerDependencies": {
|
|
77
|
+
"@earendil-works/pi-ai": "*",
|
|
76
78
|
"@earendil-works/pi-coding-agent": "*",
|
|
77
79
|
"@earendil-works/pi-tui": "*"
|
|
78
80
|
},
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createProvider,
|
|
3
|
+
type Model,
|
|
4
|
+
type Provider,
|
|
5
|
+
type RefreshModelsContext,
|
|
6
|
+
} from "@earendil-works/pi-ai";
|
|
7
|
+
import { openAICodexResponsesApi } from "@earendil-works/pi-ai/api/openai-codex-responses.lazy";
|
|
8
|
+
import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
|
|
9
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
|
|
11
|
+
const CODEX_API = "openai-codex-responses" as const;
|
|
12
|
+
const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
|
13
|
+
const CODEX_CATALOG_URL = "https://pi.dev/api/models/providers/openai-codex";
|
|
14
|
+
const CODEX_PROVIDER_ID = "openai-codex";
|
|
15
|
+
const CATALOG_REFRESH_INTERVAL_MS = 4 * 60 * 60 * 1000;
|
|
16
|
+
|
|
17
|
+
type CodexModel = Model<typeof CODEX_API>;
|
|
18
|
+
|
|
19
|
+
type CreateCodexProviderOptions = {
|
|
20
|
+
fetch?: typeof globalThis.fetch;
|
|
21
|
+
now?: () => number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const CODEX_COMPAT = {
|
|
25
|
+
supportsOpenAIGrammarTools: true,
|
|
26
|
+
supportsAdditionalTools: true,
|
|
27
|
+
supportsToolSearch: true,
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
export const CODEX_MODEL_FALLBACKS: CodexModel[] = [
|
|
31
|
+
{
|
|
32
|
+
id: "gpt-6-astra",
|
|
33
|
+
name: "GPT-6 Astra",
|
|
34
|
+
api: CODEX_API,
|
|
35
|
+
provider: CODEX_PROVIDER_ID,
|
|
36
|
+
baseUrl: CODEX_BASE_URL,
|
|
37
|
+
reasoning: true,
|
|
38
|
+
thinkingLevelMap: {
|
|
39
|
+
minimal: "low",
|
|
40
|
+
xhigh: "xhigh",
|
|
41
|
+
max: "max",
|
|
42
|
+
},
|
|
43
|
+
input: ["text", "image"],
|
|
44
|
+
contextWindow: 272000,
|
|
45
|
+
maxTokens: 128000,
|
|
46
|
+
cost: {
|
|
47
|
+
input: 10,
|
|
48
|
+
output: 50,
|
|
49
|
+
cacheRead: 1,
|
|
50
|
+
cacheWrite: 12.5,
|
|
51
|
+
tiers: [
|
|
52
|
+
{
|
|
53
|
+
inputTokensAbove: 272000,
|
|
54
|
+
input: 20,
|
|
55
|
+
output: 75,
|
|
56
|
+
cacheRead: 2,
|
|
57
|
+
cacheWrite: 25,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
compat: CODEX_COMPAT,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "gpt-daybreak-blue-latest",
|
|
65
|
+
name: "Daybreak Blue",
|
|
66
|
+
api: CODEX_API,
|
|
67
|
+
provider: CODEX_PROVIDER_ID,
|
|
68
|
+
baseUrl: CODEX_BASE_URL,
|
|
69
|
+
reasoning: true,
|
|
70
|
+
thinkingLevelMap: {
|
|
71
|
+
minimal: "low",
|
|
72
|
+
xhigh: "xhigh",
|
|
73
|
+
max: "max",
|
|
74
|
+
},
|
|
75
|
+
input: ["text", "image"],
|
|
76
|
+
contextWindow: 272000,
|
|
77
|
+
maxTokens: 128000,
|
|
78
|
+
cost: {
|
|
79
|
+
input: 4,
|
|
80
|
+
output: 20,
|
|
81
|
+
cacheRead: 0.4,
|
|
82
|
+
cacheWrite: 5,
|
|
83
|
+
tiers: [
|
|
84
|
+
{
|
|
85
|
+
inputTokensAbove: 272000,
|
|
86
|
+
input: 8,
|
|
87
|
+
output: 30,
|
|
88
|
+
cacheRead: 0.8,
|
|
89
|
+
cacheWrite: 10,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
compat: CODEX_COMPAT,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "gpt-daybreak-red-latest",
|
|
97
|
+
name: "Daybreak Red",
|
|
98
|
+
api: CODEX_API,
|
|
99
|
+
provider: CODEX_PROVIDER_ID,
|
|
100
|
+
baseUrl: CODEX_BASE_URL,
|
|
101
|
+
reasoning: true,
|
|
102
|
+
thinkingLevelMap: {
|
|
103
|
+
minimal: "low",
|
|
104
|
+
xhigh: "xhigh",
|
|
105
|
+
max: "max",
|
|
106
|
+
},
|
|
107
|
+
input: ["text", "image"],
|
|
108
|
+
contextWindow: 372000,
|
|
109
|
+
maxTokens: 128000,
|
|
110
|
+
cost: {
|
|
111
|
+
input: 12.5,
|
|
112
|
+
output: 75,
|
|
113
|
+
cacheRead: 1.25,
|
|
114
|
+
cacheWrite: 15.625,
|
|
115
|
+
},
|
|
116
|
+
compat: CODEX_COMPAT,
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
121
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function catalogEntries(value: unknown): unknown[] | undefined {
|
|
125
|
+
if (Array.isArray(value)) return value;
|
|
126
|
+
if (!isRecord(value)) return undefined;
|
|
127
|
+
if (Array.isArray(value.models)) return value.models;
|
|
128
|
+
return Object.values(value);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function parseCodexCatalog(value: unknown): CodexModel[] {
|
|
132
|
+
const entries = catalogEntries(value);
|
|
133
|
+
if (!entries) throw new Error("Invalid openai-codex model catalog");
|
|
134
|
+
|
|
135
|
+
return entries
|
|
136
|
+
.filter((entry): entry is Record<string, unknown> => {
|
|
137
|
+
return isRecord(entry) && typeof entry.id === "string" && entry.id.length > 0;
|
|
138
|
+
})
|
|
139
|
+
.map((entry) => ({ ...entry, provider: CODEX_PROVIDER_ID }) as CodexModel);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function appendMissingCodexModels(
|
|
143
|
+
models: readonly CodexModel[],
|
|
144
|
+
fallbacks: readonly CodexModel[] = CODEX_MODEL_FALLBACKS,
|
|
145
|
+
): CodexModel[] {
|
|
146
|
+
const merged = [...models];
|
|
147
|
+
const modelIds = new Set(merged.map((model) => model.id));
|
|
148
|
+
for (const model of fallbacks) {
|
|
149
|
+
if (modelIds.has(model.id)) continue;
|
|
150
|
+
merged.push(model);
|
|
151
|
+
modelIds.add(model.id);
|
|
152
|
+
}
|
|
153
|
+
return merged;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function storedCodexModels(context: RefreshModelsContext): CodexModel[] {
|
|
157
|
+
return (context.stored?.models ?? []).filter(
|
|
158
|
+
(model): model is CodexModel => model.provider === CODEX_PROVIDER_ID && model.api === CODEX_API,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function fetchCodexModels(
|
|
163
|
+
context: RefreshModelsContext,
|
|
164
|
+
options: CreateCodexProviderOptions,
|
|
165
|
+
): Promise<readonly CodexModel[]> {
|
|
166
|
+
const cached = storedCodexModels(context);
|
|
167
|
+
const checkedAt = context.stored?.checkedAt;
|
|
168
|
+
const now = options.now?.() ?? Date.now();
|
|
169
|
+
if (!context.force && checkedAt !== undefined && now - checkedAt < CATALOG_REFRESH_INTERVAL_MS) {
|
|
170
|
+
return cached;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const response = await (options.fetch ?? globalThis.fetch)(CODEX_CATALOG_URL, {
|
|
174
|
+
headers: { accept: "application/json" },
|
|
175
|
+
signal: context.signal,
|
|
176
|
+
});
|
|
177
|
+
if (response.status === 404 || response.status === 501) return cached;
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
throw new Error(`OpenAI Codex model catalog request failed: ${response.status}`);
|
|
180
|
+
}
|
|
181
|
+
return parseCodexCatalog(await response.json());
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function createOpenAICodexProvider(
|
|
185
|
+
options: CreateCodexProviderOptions = {},
|
|
186
|
+
): Provider<typeof CODEX_API> {
|
|
187
|
+
const builtIn = openaiCodexProvider();
|
|
188
|
+
return createProvider({
|
|
189
|
+
id: builtIn.id,
|
|
190
|
+
name: builtIn.name,
|
|
191
|
+
baseUrl: builtIn.baseUrl,
|
|
192
|
+
headers: builtIn.headers,
|
|
193
|
+
auth: builtIn.auth,
|
|
194
|
+
models: appendMissingCodexModels(builtIn.getModels()),
|
|
195
|
+
fetchModels: (context) => fetchCodexModels(context, options),
|
|
196
|
+
filterModels: builtIn.filterModels,
|
|
197
|
+
api: openAICodexResponsesApi(),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function registerOpenAICodexModels(pi: ExtensionAPI): void {
|
|
202
|
+
pi.registerProvider(createOpenAICodexProvider());
|
|
203
|
+
}
|
package/src/config.ts
CHANGED