Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules/
dist/
.corbits/
.DS_Store
.env
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
bun.lock
dist
30 changes: 19 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ than reimplementing OAuth or the Responses wire protocol.

## Rules

- Consume `@corbits/oauth-core` and `@corbits/openai-responses` as packages
(`github:` specifiers) only — never vendor or fork them. `@intx/inference` and
`@intx/types` are peer dependencies: an adapter must plug into the host's
own copy of the harness, not a second bundled one.
- `@corbits/oauth-core`, `@corbits/openai-responses`, `@intx/inference`,
and `@intx/types` are peer dependencies (npm semver ranges) — never vendor
or fork them. An adapter must plug into the host's own copies of the
harness and its sibling packages, not second bundled ones.
- Parse every trust boundary with arktype (`CodexQuirks`, id_token claims);
never `as T` untrusted input.
- `exactOptionalPropertyTypes` is on: omit optional keys, never assign
Expand All @@ -31,6 +31,9 @@ than reimplementing OAuth or the Responses wire protocol.
`bun link` symlinks — it would not resolve for anyone else.
- No product strings baked in; `CodexQuirks` is the only injection point for
a host's identity, and an absent bag is a validation error, not a default.
- Relative imports inside `src/` carry explicit `.js` suffixes so the
compiled `dist/` output resolves under native Node ESM. Never add a
build-time rewrite script to paper over an extensionless import.
- Tests exist only for load-bearing risk: the exact Codex request shape,
`accountIdFromIdToken` on hostile input, the bridge-message tag structure,
and the content-type repair's conditions.
Expand All @@ -39,13 +42,14 @@ than reimplementing OAuth or the Responses wire protocol.

```sh
bun install
bun run build # tsc -p tsconfig.build.json -> dist/
bun run check # typecheck + lint + format:check + test
```

`@corbits/oauth-core` and `@corbits/openai-responses` resolve from their
GitHub repos, so `bun install` needs those repos pushed. To work against an
unpushed local checkout of either, `bun link` it here; a later `bun install`
re-resolves from git and drops the link.
`@corbits/oauth-core` and `@corbits/openai-responses` resolve from npm, so
`bun install` needs no git access. To work against an unpushed local checkout
of either, `bun link` it here; a later `bun install` re-resolves from the
registry and drops the link.

`CodexQuirks` in `src/quirks.ts` is explicitly typed as `Type<CodexQuirksShape>`
rather than left to inference: a consumer can end up with two resolved
Expand All @@ -54,6 +58,10 @@ across that boundary.

## Distribution

The package ships TypeScript source: `exports` points at `src/index.ts`,
there is no build step and no `dist/`. Consumers install it from npm with
`bun add @corbits/codex-provider` and Bun runs the source as-is.
The package ships compiled `dist/` on npm as `@corbits/codex-provider`:
`exports` points at `dist/index.js` (types at `dist/index.d.ts`), built with
`bun run build` (`tsc -p tsconfig.build.json`) via the `prepack` hook.
Consumers install the published package (`bun add @corbits/codex-provider`
or `npm install @corbits/codex-provider`) on Bun >= 1.2 or Node.js >= 24,
both of which load the compiled output. Only `dist` ships (`files` is
dist-only).
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ OpenAI Codex ("Login with ChatGPT") as an Interchange inference provider: OAuth

## Runtime support

Bun >= 1.2 runs the published TypeScript source. Node >= 24 is an engines floor for tooling; native Node does not load this extensionless TypeScript source as-is. `@intx/inference` and `@intx/types` are peer dependencies and must resolve to the host's own copy.
Bun >= 1.2 and Node >= 24 consume the published compiled `dist/` output.
`@corbits/oauth-core`, `@corbits/openai-responses`, `@intx/inference`, and
`@intx/types` are peer dependencies and must resolve to the host's own copies.

## Quickstart

Expand Down
84 changes: 20 additions & 64 deletions bun.lock

Large diffs are not rendered by default.

20 changes: 14 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,26 @@
"license": "LGPL-2.1-only",
"author": "Sawyer Cutler <sawyer@dirtroad.dev>",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"sideEffects": false,
"files": [
"src",
"!src/**/*.test.ts",
"dist",
"README.md",
"LICENSE"
],
"publishConfig": {
"access": "public"
},
"exports": {
".": "./src/index.ts"
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"scripts": {
"build": "tsc -p tsconfig.build.json",
"prepack": "bun run build",
"typecheck": "tsc --noEmit",
"lint": "eslint .",
"format": "prettier --write .",
Expand All @@ -27,15 +33,17 @@
"check": "bun run typecheck && bun run lint && bun run format:check && bun run test"
},
"dependencies": {
"@corbits/oauth-core": "github:corbitsdev/corbits-oauth-core#3028f8604155eee63f5a1711f0d4022d7984e3f7",
"@corbits/openai-responses": "github:corbitsdev/corbits-openai-responses",
"arktype": "2.2.3"
"arktype": "^2.2.3"
},
"peerDependencies": {
"@corbits/oauth-core": "^0.1.0",
"@corbits/openai-responses": "^0.1.0",
"@intx/inference": ">=0.4.0",
"@intx/types": ">=0.4.0"
},
"devDependencies": {
"@corbits/oauth-core": "^0.1.0",
"@corbits/openai-responses": "^0.1.0",
"@eslint/js": "9.34.0",
"@intx/inference": "0.4.0",
"@intx/types": "0.4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/content-type-repair.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
withCodexContentTypeRepair,
CODEX_RESPONSES_PATH,
type FetchLike,
} from "./index";
} from "./index.js";

const url = `https://chatgpt.com/backend-api${CODEX_RESPONSES_PATH}`;

Expand Down
2 changes: 1 addition & 1 deletion src/content-type-repair.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CODEX_RESPONSES_PATH } from "./constants";
import { CODEX_RESPONSES_PATH } from "./constants.js";

/** The `fetch` shape {@link withCodexContentTypeRepair} wraps. */
export type FetchLike = (
Expand Down
12 changes: 6 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* and product identity via `CodexQuirks`.
*/

export * from "./constants";
export * from "./constants.js";

export {
accountIdFromIdToken,
Expand All @@ -16,15 +16,15 @@ export {
exchangeCodexCode,
refreshCodexTokens,
type CodexTokens,
} from "./oauth";
} from "./oauth.js";

export { wrapCodexBridgeMessage } from "./instructions";
export { wrapCodexBridgeMessage } from "./instructions.js";

export { CodexQuirks, CodexQuirksError, parseCodexQuirks } from "./quirks";
export { CodexQuirks, CodexQuirksError, parseCodexQuirks } from "./quirks.js";

export { createCodexResponsesAdapter } from "./responses-adapter";
export { createCodexResponsesAdapter } from "./responses-adapter.js";

export {
withCodexContentTypeRepair,
type FetchLike,
} from "./content-type-repair";
} from "./content-type-repair.js";
2 changes: 1 addition & 1 deletion src/instructions.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { wrapCodexBridgeMessage } from "./index";
import { wrapCodexBridgeMessage } from "./index.js";

// The Codex backend addresses this text as a leading developer message and
// the host's harness relies on the exact tag structure to identify its own
Expand Down
2 changes: 1 addition & 1 deletion src/oauth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
accountIdFromIdToken,
refreshCodexTokens,
type CodexTokens,
} from "./index";
} from "./index.js";

function jwtWithPayload(payload: unknown): string {
const header = Buffer.from(JSON.stringify({ alg: "none" })).toString(
Expand Down
2 changes: 1 addition & 1 deletion src/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
CODEX_SCOPES,
CODEX_TOKEN_TIMEOUT_MS,
CODEX_TOKEN_URL,
} from "./constants";
} from "./constants.js";

/** Tokens issued by Codex's OAuth server: the shared base shape plus the ChatGPT account id. */
export type CodexTokens = BaseTokens & { accountId?: string };
Expand Down
2 changes: 1 addition & 1 deletion src/quirks.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { parseCodexQuirks } from "./index";
import { parseCodexQuirks } from "./index.js";

// `CodexQuirks` is annotated `Type<CodexQuirksShape>` (see quirks.ts for
// why), which hides the schema's `"+": "reject"` at the type level — only a
Expand Down
2 changes: 1 addition & 1 deletion src/responses-adapter.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ConversationTurn, LastCycleSource } from "@intx/types/runtime";
import { type } from "arktype";
import { describe, expect, test } from "bun:test";
import { createCodexResponsesAdapter } from "./index";
import { createCodexResponsesAdapter } from "./index.js";

const source: LastCycleSource = {
sourceId: "test/codex",
Expand Down
6 changes: 3 additions & 3 deletions src/responses-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {
CODEX_REASONING_EFFORT_OPTION,
CODEX_RESPONSES_PATH,
CODEX_SESSION_ID_OPTION,
} from "./constants";
import { wrapCodexBridgeMessage } from "./instructions";
import { parseCodexQuirks, type CodexQuirks } from "./quirks";
} from "./constants.js";
import { wrapCodexBridgeMessage } from "./instructions.js";
import { parseCodexQuirks, type CodexQuirks } from "./quirks.js";

// `store` and `parallelToolCalls` need an explicit `false` on the wire, and
// `maxOutputTokens: false` because the backend 400s on `max_output_tokens`.
Expand Down
17 changes: 17 additions & 0 deletions tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"allowImportingTsExtensions": false,
"composite": false,
"noEmit": false,
"declaration": true,
"declarationMap": false,
"sourceMap": false,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["src/**/*.test.ts"]
}
Loading