@mewbleh/purrx 1.0.10 → 1.0.11
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/package.json +1 -1
- package/src/api/client.js +13 -23
- package/src/auth/tokens.js +13 -1
package/package.json
CHANGED
package/src/api/client.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { API_BASE, CHATGPT_BASE } from "../config.js";
|
|
2
2
|
|
|
3
|
-
// Models that use the Responses API reasoning fields. gpt-5 family, o-series,
|
|
4
|
-
// and any *-codex variant are reasoning models.
|
|
5
|
-
function isReasoningModel(model) {
|
|
6
|
-
return /^(gpt-5|o\d|codex)/i.test(model) || /-codex\b/i.test(model);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
3
|
// Builds the request URL and headers for the resolved auth mode.
|
|
10
4
|
/**
|
|
11
5
|
* @param {import("../types.js").AuthInfo} authInfo
|
|
@@ -13,13 +7,16 @@ function isReasoningModel(model) {
|
|
|
13
7
|
*/
|
|
14
8
|
function buildRequest(authInfo) {
|
|
15
9
|
if (authInfo.mode === "chatgpt") {
|
|
10
|
+
// These headers match the proven-working ChatGPT-codex request shape.
|
|
11
|
+
// The OpenAI-Beta header is required; the backend rejects requests without
|
|
12
|
+
// it (with a misleading "model not supported" error). Do NOT add an
|
|
13
|
+
// `originator` header here: the codex backend does not expect it on this
|
|
14
|
+
// path and its presence can cause the request to be rejected.
|
|
16
15
|
/** @type {Record<string, string>} */
|
|
17
16
|
const headers = {
|
|
18
17
|
"Content-Type": "application/json",
|
|
19
18
|
Authorization: `Bearer ${authInfo.accessToken}`,
|
|
20
|
-
|
|
21
|
-
originator: "codex_cli_rs",
|
|
22
|
-
"User-Agent": "purrx",
|
|
19
|
+
"OpenAI-Beta": "responses=experimental",
|
|
23
20
|
};
|
|
24
21
|
if (authInfo.accountId) {
|
|
25
22
|
headers["chatgpt-account-id"] = authInfo.accountId;
|
|
@@ -57,31 +54,24 @@ export async function streamResponse(
|
|
|
57
54
|
) {
|
|
58
55
|
const { url, headers } = buildRequest(authInfo);
|
|
59
56
|
|
|
57
|
+
// Minimal request shape that the ChatGPT-codex backend accepts. The backend
|
|
58
|
+
// requires `instructions` to be present (a string, even if empty) and
|
|
59
|
+
// `store: false`. We deliberately avoid adding reasoning/include/cache fields
|
|
60
|
+
// here: the backend rejects requests it doesn't recognize as a valid Codex
|
|
61
|
+
// session with a misleading "model not supported" error, and the known-good
|
|
62
|
+
// client sends only these fields.
|
|
60
63
|
/** @type {Record<string, any>} */
|
|
61
64
|
const payload = {
|
|
62
65
|
model,
|
|
66
|
+
instructions: instructions || "",
|
|
63
67
|
input,
|
|
64
68
|
stream: true,
|
|
65
|
-
// The Codex backend requires store:false; responses are not persisted.
|
|
66
69
|
store: false,
|
|
67
|
-
// Stable cache key improves latency/cost across turns (matches Codex).
|
|
68
|
-
prompt_cache_key: sessionId || "purrx",
|
|
69
70
|
};
|
|
70
|
-
if (instructions) payload.instructions = instructions;
|
|
71
|
-
|
|
72
|
-
// Reasoning models (gpt-5*, o-series, *-codex) require a reasoning block and
|
|
73
|
-
// must request the encrypted reasoning content back since we don't persist
|
|
74
|
-
// responses (store:false). This mirrors the official Codex client and is the
|
|
75
|
-
// shape the ChatGPT/Codex backend expects.
|
|
76
|
-
if (isReasoningModel(model)) {
|
|
77
|
-
payload.reasoning = { effort: "medium", summary: "auto" };
|
|
78
|
-
payload.include = ["reasoning.encrypted_content"];
|
|
79
|
-
}
|
|
80
71
|
|
|
81
72
|
if (tools && tools.length) {
|
|
82
73
|
payload.tools = tools;
|
|
83
74
|
payload.tool_choice = "auto";
|
|
84
|
-
payload.parallel_tool_calls = true;
|
|
85
75
|
}
|
|
86
76
|
|
|
87
77
|
const resp = await fetch(url, {
|
package/src/auth/tokens.js
CHANGED
|
@@ -173,10 +173,22 @@ export function resolveAuthMode(auth) {
|
|
|
173
173
|
}
|
|
174
174
|
if (!auth) return null;
|
|
175
175
|
if (auth.tokens?.access_token) {
|
|
176
|
+
// The chatgpt-account-id header is required by the codex backend. Prefer
|
|
177
|
+
// the stored account_id, but fall back to deriving it from the access or
|
|
178
|
+
// id token claims if it is missing (older logins may not have saved it).
|
|
179
|
+
let accountId = auth.tokens.account_id || null;
|
|
180
|
+
if (!accountId) {
|
|
181
|
+
accountId =
|
|
182
|
+
jwtAuthClaims(auth.tokens.access_token)["chatgpt_account_id"] ||
|
|
183
|
+
(auth.tokens.id_token
|
|
184
|
+
? jwtAuthClaims(auth.tokens.id_token)["chatgpt_account_id"]
|
|
185
|
+
: null) ||
|
|
186
|
+
null;
|
|
187
|
+
}
|
|
176
188
|
return {
|
|
177
189
|
mode: "chatgpt",
|
|
178
190
|
accessToken: auth.tokens.access_token,
|
|
179
|
-
accountId
|
|
191
|
+
accountId,
|
|
180
192
|
};
|
|
181
193
|
}
|
|
182
194
|
if (auth.OPENAI_API_KEY) {
|