ampcode-connector 0.1.3 → 0.1.4
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 +2 -2
- package/src/constants.ts +22 -0
- package/src/providers/base.ts +4 -3
- package/src/providers/codex.ts +32 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ampcode-connector",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Proxy AmpCode through local OAuth subscriptions (Claude Code, Codex, Gemini CLI, Antigravity)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@biomejs/biome": "^2.4.0",
|
|
43
|
-
"@types/bun": "
|
|
43
|
+
"@types/bun": "^1.3.9"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"typescript": "^5"
|
package/src/constants.ts
CHANGED
|
@@ -7,6 +7,28 @@ export const DEFAULT_ANTIGRAVITY_PROJECT = "rising-fact-p41fc";
|
|
|
7
7
|
|
|
8
8
|
export const ANTHROPIC_API_URL = "https://api.anthropic.com";
|
|
9
9
|
export const OPENAI_API_URL = "https://api.openai.com";
|
|
10
|
+
export const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
|
11
|
+
|
|
12
|
+
/** Codex-specific headers required by the ChatGPT backend. */
|
|
13
|
+
export const codexHeaders = {
|
|
14
|
+
BETA: "OpenAI-Beta",
|
|
15
|
+
ACCOUNT_ID: "chatgpt-account-id",
|
|
16
|
+
ORIGINATOR: "originator",
|
|
17
|
+
SESSION_ID: "session_id",
|
|
18
|
+
CONVERSATION_ID: "conversation_id",
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export const codexHeaderValues = {
|
|
22
|
+
BETA_RESPONSES: "responses=experimental",
|
|
23
|
+
ORIGINATOR: "codex_cli_rs",
|
|
24
|
+
VERSION: "0.101.0",
|
|
25
|
+
USER_AGENT: `codex_cli_rs/0.101.0 (${process.platform} ${process.arch})`,
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
/** Map /v1/responses → /codex/responses for the ChatGPT backend. */
|
|
29
|
+
export const codexPathMap: Record<string, string> = {
|
|
30
|
+
"/v1/responses": "/codex/responses",
|
|
31
|
+
} as const;
|
|
10
32
|
export const DEFAULT_AMP_UPSTREAM_URL = "https://ampcode.com";
|
|
11
33
|
|
|
12
34
|
export const ANTHROPIC_TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
package/src/providers/base.ts
CHANGED
|
@@ -36,15 +36,16 @@ export async function forward(opts: ForwardOptions): Promise<Response> {
|
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
const contentType = response.headers.get("Content-Type") ?? "application/json";
|
|
39
|
-
const isSSE = contentType.includes("text/event-stream") || opts.streaming;
|
|
40
|
-
if (isSSE) return sse.proxy(response, opts.rewrite);
|
|
41
39
|
|
|
42
40
|
if (!response.ok) {
|
|
43
41
|
const text = await response.text();
|
|
44
|
-
logger.error(`${opts.providerName} API error`, { error: text.slice(0, 200) });
|
|
42
|
+
logger.error(`${opts.providerName} API error (${response.status})`, { error: text.slice(0, 200) });
|
|
45
43
|
return new Response(text, { status: response.status, headers: { "Content-Type": contentType } });
|
|
46
44
|
}
|
|
47
45
|
|
|
46
|
+
const isSSE = contentType.includes("text/event-stream") || opts.streaming;
|
|
47
|
+
if (isSSE) return sse.proxy(response, opts.rewrite);
|
|
48
|
+
|
|
48
49
|
if (opts.rewrite) {
|
|
49
50
|
const text = await response.text();
|
|
50
51
|
return new Response(opts.rewrite(text), { status: response.status, headers: { "Content-Type": contentType } });
|
package/src/providers/codex.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
/** Forwards requests to
|
|
1
|
+
/** Forwards requests to chatgpt.com/backend-api/codex with Codex CLI OAuth token.
|
|
2
|
+
*
|
|
3
|
+
* The ChatGPT backend requires specific headers (account-id from JWT, originator,
|
|
4
|
+
* OpenAI-Beta) and a different URL path (/codex/responses) than api.openai.com. */
|
|
2
5
|
|
|
3
6
|
import { codex as config } from "../auth/configs.ts";
|
|
4
7
|
import * as oauth from "../auth/oauth.ts";
|
|
5
8
|
import * as store from "../auth/store.ts";
|
|
6
|
-
import {
|
|
9
|
+
import { CODEX_BASE_URL, codexHeaders, codexHeaderValues, codexPathMap } from "../constants.ts";
|
|
10
|
+
import { fromBase64url } from "../utils/encoding.ts";
|
|
7
11
|
import type { Provider } from "./base.ts";
|
|
8
12
|
import { denied, forward } from "./base.ts";
|
|
9
13
|
|
|
@@ -20,8 +24,11 @@ export const provider: Provider = {
|
|
|
20
24
|
const accessToken = await oauth.token(config, account);
|
|
21
25
|
if (!accessToken) return denied("OpenAI Codex");
|
|
22
26
|
|
|
27
|
+
const accountId = getAccountId(accessToken, account);
|
|
28
|
+
const codexPath = codexPathMap[sub] ?? sub;
|
|
29
|
+
|
|
23
30
|
return forward({
|
|
24
|
-
url: `${
|
|
31
|
+
url: `${CODEX_BASE_URL}${codexPath}`,
|
|
25
32
|
body: body.forwardBody,
|
|
26
33
|
streaming: body.stream,
|
|
27
34
|
providerName: "OpenAI Codex",
|
|
@@ -30,7 +37,29 @@ export const provider: Provider = {
|
|
|
30
37
|
"Content-Type": "application/json",
|
|
31
38
|
Authorization: `Bearer ${accessToken}`,
|
|
32
39
|
Accept: body.stream ? "text/event-stream" : "application/json",
|
|
40
|
+
Connection: "Keep-Alive",
|
|
41
|
+
[codexHeaders.BETA]: codexHeaderValues.BETA_RESPONSES,
|
|
42
|
+
[codexHeaders.ORIGINATOR]: codexHeaderValues.ORIGINATOR,
|
|
43
|
+
"User-Agent": codexHeaderValues.USER_AGENT,
|
|
44
|
+
Version: codexHeaderValues.VERSION,
|
|
45
|
+
...(accountId ? { [codexHeaders.ACCOUNT_ID]: accountId } : {}),
|
|
33
46
|
},
|
|
34
47
|
});
|
|
35
48
|
},
|
|
36
49
|
};
|
|
50
|
+
|
|
51
|
+
/** Extract chatgpt_account_id from JWT, falling back to stored credentials. */
|
|
52
|
+
function getAccountId(accessToken: string, account: number): string | undefined {
|
|
53
|
+
const creds = store.get("codex", account);
|
|
54
|
+
if (creds?.accountId) return creds.accountId;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const parts = accessToken.split(".");
|
|
58
|
+
if (parts.length < 2 || !parts[1]) return undefined;
|
|
59
|
+
const payload = JSON.parse(new TextDecoder().decode(fromBase64url(parts[1]))) as Record<string, unknown>;
|
|
60
|
+
const auth = payload["https://api.openai.com/auth"] as Record<string, unknown> | undefined;
|
|
61
|
+
return (auth?.["chatgpt_account_id"] as string) ?? undefined;
|
|
62
|
+
} catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|