@alvin0/ai-agent-sdk-auth-node 0.1.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.
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/bin/ai-agent-sdk-codex-login.mjs +2 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +139 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/codex-CBvNhtAh.mjs +269 -0
- package/dist/codex-CBvNhtAh.mjs.map +1 -0
- package/dist/codex.d.mts +45 -0
- package/dist/codex.d.mts.map +1 -0
- package/dist/codex.mjs +3 -0
- package/dist/env-Bsy-tNY9.d.mts +9 -0
- package/dist/env-Bsy-tNY9.d.mts.map +1 -0
- package/dist/env-OQOpcank.mjs +32 -0
- package/dist/env-OQOpcank.mjs.map +1 -0
- package/dist/env.d.mts +2 -0
- package/dist/env.mjs +3 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +3 -0
- package/package.json +94 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 alvin0 (chaulamdinhai) <chaulamdinhai@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @alvin0/ai-agent-sdk-auth-node
|
|
2
|
+
|
|
3
|
+
Runtime: **Node 22.12+**.
|
|
4
|
+
|
|
5
|
+
Node-owned environment credentials and project-local Codex OAuth storage. The
|
|
6
|
+
package root and `/env` entrypoint are environment-only: installing either does
|
|
7
|
+
not require or load a model provider.
|
|
8
|
+
|
|
9
|
+
For an OpenAI agent whose key comes from the environment:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add @alvin0/ai-agent-sdk-core @alvin0/ai-agent-sdk-provider-openai @alvin0/ai-agent-sdk-auth-node
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { envCredential } from '@alvin0/ai-agent-sdk-auth-node'
|
|
17
|
+
import { createAgentRuntime } from '@alvin0/ai-agent-sdk-core'
|
|
18
|
+
import { openAiPlugin } from '@alvin0/ai-agent-sdk-provider-openai'
|
|
19
|
+
|
|
20
|
+
const runtime = await createAgentRuntime({
|
|
21
|
+
providers: [openAiPlugin({ apiKey: envCredential('OPENAI_API_KEY') })],
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`envCredential()` is lazy, receives the model-operation cancellation signal and
|
|
26
|
+
remains callable for compatibility. It is borrowed by the provider and has no
|
|
27
|
+
close lifecycle.
|
|
28
|
+
|
|
29
|
+
Codex support is an explicit optional closure:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
pnpm add @alvin0/ai-agent-sdk-core @alvin0/ai-agent-sdk-provider-codex @alvin0/ai-agent-sdk-auth-node
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { createAgentRuntime } from '@alvin0/ai-agent-sdk-core'
|
|
37
|
+
import { codexNodeProviderPlugin } from '@alvin0/ai-agent-sdk-auth-node/codex'
|
|
38
|
+
|
|
39
|
+
const runtime = await createAgentRuntime({
|
|
40
|
+
providers: [codexNodeProviderPlugin()],
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`codexNodeProviderPlugin()` uses the revisioned `fileCodexCredentialStore()` by
|
|
45
|
+
default. The store is borrowed and remains caller-owned; the runtime closes the
|
|
46
|
+
provider registration, not the store. Codex defaults to
|
|
47
|
+
`.providers/.codex/auth.json` below `process.cwd()` and never uses the Codex
|
|
48
|
+
CLI's global credential file. Writes use compare-and-swap under a cross-process
|
|
49
|
+
writer lock, a private same-directory temporary file, file sync, atomic rename,
|
|
50
|
+
mode `0600`, and directory sync. Credential-file symlinks are rejected.
|
|
51
|
+
|
|
52
|
+
The deprecated `fileCodexAuthStore()` and `codexNodePlugin()` retain the former
|
|
53
|
+
`read/write` compatibility contract. New runtime composition should use the
|
|
54
|
+
revisioned factory above.
|
|
55
|
+
|
|
56
|
+
Run `ai-agent-sdk-codex-login` after installation to authenticate this project.
|
|
57
|
+
|
|
58
|
+
Composition: `provider-factory.credentials`. Lifecycle: `borrowed-caller-owned`;
|
|
59
|
+
`envCredential()` and file-backed stores are resolved lazily by the selected
|
|
60
|
+
provider and are never closed by core.
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { C as shouldRefresh, E as fileCodexAuthStore, O as resolveCodexAuthPath, S as runDeviceCodeLogin, _ as readJwtClaims } from "./codex-CBvNhtAh.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/cli.ts
|
|
4
|
+
/**
|
|
5
|
+
* `npm run provider:codex:login-device`
|
|
6
|
+
*
|
|
7
|
+
* Signs in to Codex with the OAuth device-code flow and stores the tokens in this
|
|
8
|
+
* PROJECT, at `.providers/.codex/auth.json`, rather than touching the Codex CLI's
|
|
9
|
+
* own `~/.codex/auth.json`.
|
|
10
|
+
*
|
|
11
|
+
* The isolation is deliberate. OAuth refresh tokens are single-use and rotate on
|
|
12
|
+
* every refresh, so two programs sharing one credential file will eventually race:
|
|
13
|
+
* the second one to refresh replays a spent token, gets `refresh_token_reused`, and
|
|
14
|
+
* the user is silently logged out of their real Codex CLI. A separate store cannot
|
|
15
|
+
* cause that.
|
|
16
|
+
*
|
|
17
|
+
* Flags:
|
|
18
|
+
* --force sign in again even if valid credentials already exist
|
|
19
|
+
* --status report the current credential state and exit
|
|
20
|
+
* --path <file> write somewhere other than the default
|
|
21
|
+
* --issuer <url> use a non-production auth issuer
|
|
22
|
+
*/
|
|
23
|
+
const BLUE = "\x1B[94m";
|
|
24
|
+
const GRAY = "\x1B[90m";
|
|
25
|
+
const BOLD = "\x1B[1m";
|
|
26
|
+
const RESET = "\x1B[0m";
|
|
27
|
+
function parseFlags(argv) {
|
|
28
|
+
const flags = {
|
|
29
|
+
force: false,
|
|
30
|
+
status: false,
|
|
31
|
+
path: void 0,
|
|
32
|
+
issuer: void 0
|
|
33
|
+
};
|
|
34
|
+
for (let index = 0; index < argv.length; index++) {
|
|
35
|
+
const arg = argv[index];
|
|
36
|
+
switch (arg) {
|
|
37
|
+
case "--force":
|
|
38
|
+
flags.force = true;
|
|
39
|
+
break;
|
|
40
|
+
case "--status":
|
|
41
|
+
flags.status = true;
|
|
42
|
+
break;
|
|
43
|
+
case "--path":
|
|
44
|
+
flags.path = argv[++index];
|
|
45
|
+
break;
|
|
46
|
+
case "--issuer":
|
|
47
|
+
flags.issuer = argv[++index];
|
|
48
|
+
break;
|
|
49
|
+
default: if (arg !== void 0 && arg.startsWith("-")) throw new Error(`unknown flag "${arg}"`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return flags;
|
|
53
|
+
}
|
|
54
|
+
function renderPrompt(code) {
|
|
55
|
+
process.stdout.write(`\n${BOLD}Sign in to Codex${RESET} ${GRAY}(device authorization)${RESET}\n\n 1. Open this URL and sign in:\n ${BLUE}${code.verificationUrl}${RESET}\n\n 2. Enter this one-time code ${GRAY}(expires in 15 minutes)${RESET}:\n ${BOLD}${BLUE}${code.userCode}${RESET}\n\n${GRAY}Only continue if YOU started this login. If someone sent you this code, stop.${RESET}\n\n`);
|
|
56
|
+
}
|
|
57
|
+
/** Best-effort browser launch; failure is fine because the URL is printed anyway. */
|
|
58
|
+
async function openBrowser(url) {
|
|
59
|
+
try {
|
|
60
|
+
const { spawn } = await import("node:child_process");
|
|
61
|
+
const command = process.platform === "win32" ? {
|
|
62
|
+
file: "cmd",
|
|
63
|
+
args: [
|
|
64
|
+
"/c",
|
|
65
|
+
"start",
|
|
66
|
+
"",
|
|
67
|
+
url
|
|
68
|
+
]
|
|
69
|
+
} : process.platform === "darwin" ? {
|
|
70
|
+
file: "open",
|
|
71
|
+
args: [url]
|
|
72
|
+
} : {
|
|
73
|
+
file: "xdg-open",
|
|
74
|
+
args: [url]
|
|
75
|
+
};
|
|
76
|
+
spawn(command.file, command.args, {
|
|
77
|
+
stdio: "ignore",
|
|
78
|
+
detached: true
|
|
79
|
+
}).unref();
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
82
|
+
async function main() {
|
|
83
|
+
const flags = parseFlags(process.argv.slice(2));
|
|
84
|
+
const location = resolveCodexAuthPath(flags.path);
|
|
85
|
+
const store = fileCodexAuthStore(location);
|
|
86
|
+
const existing = await store.read();
|
|
87
|
+
if (flags.status) {
|
|
88
|
+
if (existing?.tokens === void 0 || existing.tokens === null) {
|
|
89
|
+
process.stdout.write(`codex: not signed in ${GRAY}(${location})${RESET}\n`);
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
const claims = readJwtClaims(existing.tokens.id_token);
|
|
93
|
+
const stale = shouldRefresh(existing);
|
|
94
|
+
process.stdout.write(`codex: signed in ${GRAY}(${location})${RESET}\n account : ${claims?.accountId ?? existing.tokens.account_id ?? "<none>"}\n email : ${claims?.email ?? "<undisclosed>"}\n plan : ${claims?.planType ?? "<undisclosed>"}\n token : ${stale ? "needs refresh" : "valid"}\n`);
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
if (!flags.force && existing?.tokens !== void 0 && existing.tokens !== null && !shouldRefresh(existing)) {
|
|
98
|
+
const claims = readJwtClaims(existing.tokens.id_token);
|
|
99
|
+
process.stdout.write(`codex: already signed in as ${claims?.email ?? claims?.accountId ?? "this account"}\n${GRAY} ${location}\n pass --force to sign in again${RESET}\n`);
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
const cancel = new AbortController();
|
|
103
|
+
const onSigint = () => {
|
|
104
|
+
cancel.abort();
|
|
105
|
+
process.stdout.write("\ncodex: login cancelled\n");
|
|
106
|
+
};
|
|
107
|
+
process.once("SIGINT", onSigint);
|
|
108
|
+
let lastReport = 0;
|
|
109
|
+
try {
|
|
110
|
+
const result = await runDeviceCodeLogin(store, {
|
|
111
|
+
signal: cancel.signal,
|
|
112
|
+
...flags.issuer === void 0 ? {} : { issuer: flags.issuer }
|
|
113
|
+
}, {
|
|
114
|
+
onPrompt: (code) => {
|
|
115
|
+
renderPrompt(code);
|
|
116
|
+
openBrowser(code.verificationUrl);
|
|
117
|
+
},
|
|
118
|
+
onPoll: (elapsedMs) => {
|
|
119
|
+
if (elapsedMs - lastReport < 15e3 && elapsedMs !== 0) return;
|
|
120
|
+
lastReport = elapsedMs;
|
|
121
|
+
process.stdout.write(`${GRAY} waiting for approval… ${Math.round(elapsedMs / 1e3)}s${RESET}\n`);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
process.stdout.write(`\n${BOLD}codex: signed in${RESET}\n account : ${result.accountId ?? "<none>"}\n email : ${result.email ?? "<undisclosed>"}\n plan : ${result.planType ?? "<undisclosed>"}\n stored : ${result.location} ${GRAY}(git-ignored)${RESET}\n`);
|
|
125
|
+
return 0;
|
|
126
|
+
} finally {
|
|
127
|
+
process.removeListener("SIGINT", onSigint);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
process.exitCode = await main();
|
|
132
|
+
} catch (error) {
|
|
133
|
+
process.stdout.write(`\ncodex: login failed — ${error instanceof Error ? error.message : String(error)}\n`);
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
export { };
|
|
139
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["/**\n * `npm run provider:codex:login-device`\n *\n * Signs in to Codex with the OAuth device-code flow and stores the tokens in this\n * PROJECT, at `.providers/.codex/auth.json`, rather than touching the Codex CLI's\n * own `~/.codex/auth.json`.\n *\n * The isolation is deliberate. OAuth refresh tokens are single-use and rotate on\n * every refresh, so two programs sharing one credential file will eventually race:\n * the second one to refresh replays a spent token, gets `refresh_token_reused`, and\n * the user is silently logged out of their real Codex CLI. A separate store cannot\n * cause that.\n *\n * Flags:\n * --force sign in again even if valid credentials already exist\n * --status report the current credential state and exit\n * --path <file> write somewhere other than the default\n * --issuer <url> use a non-production auth issuer\n */\n\nimport {\n fileCodexAuthStore,\n readJwtClaims,\n resolveCodexAuthPath,\n runDeviceCodeLogin,\n shouldRefresh,\n type CodexDeviceCode,\n} from './codex.ts'\n\nconst BLUE = '\\u001B[94m'\nconst GRAY = '\\u001B[90m'\nconst BOLD = '\\u001B[1m'\nconst RESET = '\\u001B[0m'\n\ninterface Flags {\n force: boolean\n status: boolean\n path: string | undefined\n issuer: string | undefined\n}\n\nfunction parseFlags(argv: readonly string[]): Flags {\n const flags: Flags = { force: false, status: false, path: undefined, issuer: undefined }\n for (let index = 0; index < argv.length; index++) {\n const arg = argv[index]\n switch (arg) {\n case '--force': flags.force = true; break\n case '--status': flags.status = true; break\n case '--path': flags.path = argv[++index]; break\n case '--issuer': flags.issuer = argv[++index]; break\n default:\n if (arg !== undefined && arg.startsWith('-')) {\n throw new Error(`unknown flag \"${arg}\"`)\n }\n }\n }\n return flags\n}\n\nfunction renderPrompt(code: CodexDeviceCode): void {\n process.stdout.write(\n `\\n${BOLD}Sign in to Codex${RESET} ${GRAY}(device authorization)${RESET}\\n`\n + `\\n 1. Open this URL and sign in:\\n ${BLUE}${code.verificationUrl}${RESET}\\n`\n + `\\n 2. Enter this one-time code ${GRAY}(expires in 15 minutes)${RESET}:\\n ${BOLD}${BLUE}${code.userCode}${RESET}\\n`\n + `\\n${GRAY}Only continue if YOU started this login. If someone sent you this code, stop.${RESET}\\n\\n`,\n )\n}\n\n/** Best-effort browser launch; failure is fine because the URL is printed anyway. */\nasync function openBrowser(url: string): Promise<void> {\n try {\n const { spawn } = await import('node:child_process')\n const command = process.platform === 'win32'\n ? { file: 'cmd', args: ['/c', 'start', '', url] }\n : process.platform === 'darwin'\n ? { file: 'open', args: [url] }\n : { file: 'xdg-open', args: [url] }\n spawn(command.file, command.args, { stdio: 'ignore', detached: true }).unref()\n } catch {\n // The printed URL is the real interface; auto-open is a convenience.\n }\n}\n\nasync function main(): Promise<number> {\n const flags = parseFlags(process.argv.slice(2))\n const location = resolveCodexAuthPath(flags.path)\n const store = fileCodexAuthStore(location)\n const existing = await store.read()\n\n if (flags.status) {\n if (existing?.tokens === undefined || existing.tokens === null) {\n process.stdout.write(`codex: not signed in ${GRAY}(${location})${RESET}\\n`)\n return 1\n }\n const claims = readJwtClaims(existing.tokens.id_token)\n const stale = shouldRefresh(existing)\n process.stdout.write(\n `codex: signed in ${GRAY}(${location})${RESET}\\n`\n + ` account : ${claims?.accountId ?? existing.tokens.account_id ?? '<none>'}\\n`\n + ` email : ${claims?.email ?? '<undisclosed>'}\\n`\n + ` plan : ${claims?.planType ?? '<undisclosed>'}\\n`\n + ` token : ${stale ? 'needs refresh' : 'valid'}\\n`,\n )\n return 0\n }\n\n if (!flags.force && existing?.tokens !== undefined && existing.tokens !== null\n && !shouldRefresh(existing)) {\n const claims = readJwtClaims(existing.tokens.id_token)\n process.stdout.write(\n `codex: already signed in as ${claims?.email ?? claims?.accountId ?? 'this account'}\\n`\n + `${GRAY} ${location}\\n pass --force to sign in again${RESET}\\n`,\n )\n return 0\n }\n\n // Ctrl-C during a 15-minute poll should exit promptly rather than wait.\n const cancel = new AbortController()\n const onSigint = (): void => {\n cancel.abort()\n process.stdout.write('\\ncodex: login cancelled\\n')\n }\n process.once('SIGINT', onSigint)\n\n let lastReport = 0\n try {\n const result = await runDeviceCodeLogin(\n store,\n { signal: cancel.signal, ...flags.issuer === undefined ? {} : { issuer: flags.issuer } },\n {\n onPrompt: (code) => {\n renderPrompt(code)\n void openBrowser(code.verificationUrl)\n },\n onPoll: (elapsedMs) => {\n // Throttle to one line per 15s so a long wait does not spam the log.\n if (elapsedMs - lastReport < 15_000 && elapsedMs !== 0) return\n lastReport = elapsedMs\n process.stdout.write(`${GRAY} waiting for approval… ${Math.round(elapsedMs / 1000)}s${RESET}\\n`)\n },\n },\n )\n process.stdout.write(\n `\\n${BOLD}codex: signed in${RESET}\\n`\n + ` account : ${result.accountId ?? '<none>'}\\n`\n + ` email : ${result.email ?? '<undisclosed>'}\\n`\n + ` plan : ${result.planType ?? '<undisclosed>'}\\n`\n + ` stored : ${result.location} ${GRAY}(git-ignored)${RESET}\\n`,\n )\n return 0\n } finally {\n process.removeListener('SIGINT', onSigint)\n }\n}\n\ntry {\n process.exitCode = await main()\n} catch (error: unknown) {\n process.stdout.write(`\\ncodex: login failed — ${error instanceof Error ? error.message : String(error)}\\n`)\n process.exitCode = 1\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { AgentSdkError } from "@alvin0/ai-agent-sdk-core";
|
|
2
|
+
import { defineCredentialStore } from "@alvin0/ai-agent-sdk-core/provider";
|
|
3
|
+
import { ACCESS_TOKEN_REFRESH_WINDOW_MS, CODEX_BASE_URL, CODEX_CLIENT_ID, CODEX_CLIENT_VERSION, CODEX_ORIGINATOR, CodexRefreshError, DEFAULT_CODEX_ISSUER, LAST_REFRESH_MAX_AGE_MS, codexAdapter, codexPlugin, isFedrampAccount, memoryCodexAuthStore, memoryCodexCredentialStore, readJwtClaims, refreshCodexTokens, requestDeviceCode, requireTokens, resolveAccountId, runDeviceCodeLogin, shouldRefresh } from "@alvin0/ai-agent-sdk-provider-codex";
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
|
+
import { constants } from "node:fs";
|
|
7
|
+
import { chmod, lstat, mkdir, open, rename, unlink } from "node:fs/promises";
|
|
8
|
+
|
|
9
|
+
//#region src/common/credential-file.ts
|
|
10
|
+
const MAX_CREDENTIAL_FILE_BYTES = 1048576;
|
|
11
|
+
const LOCK_RETRY_MS = 10;
|
|
12
|
+
const LOCK_TIMEOUT_MS = 3e4;
|
|
13
|
+
/** Read one stable regular file without following its final symlink. */
|
|
14
|
+
async function readCredentialText(location, signal) {
|
|
15
|
+
signal?.throwIfAborted();
|
|
16
|
+
let handle;
|
|
17
|
+
try {
|
|
18
|
+
handle = await openNoFollow(location, constants.O_RDONLY);
|
|
19
|
+
signal?.throwIfAborted();
|
|
20
|
+
const [opened, linked] = await Promise.all([handle.stat(), lstat(location)]);
|
|
21
|
+
if (!opened.isFile() || linked.isSymbolicLink() || opened.dev !== linked.dev || opened.ino !== linked.ino) throw credentialFileError("Codex credential path must be a stable regular file");
|
|
22
|
+
if (opened.size > 1048576) throw credentialFileError("Codex credential file exceeds the 1 MiB limit");
|
|
23
|
+
const raw = await handle.readFile({
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
...signal === void 0 ? {} : { signal }
|
|
26
|
+
});
|
|
27
|
+
signal?.throwIfAborted();
|
|
28
|
+
return raw;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (errorCode(error) === "ENOENT") return void 0;
|
|
31
|
+
throw error;
|
|
32
|
+
} finally {
|
|
33
|
+
await handle?.close().catch(() => void 0);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Serialize cross-process compare-and-swap writers with an exclusive sidecar lock. */
|
|
37
|
+
async function withCredentialFileLock(location, signal, task) {
|
|
38
|
+
await prepareDirectory(dirname(location), signal);
|
|
39
|
+
const lockPath = `${location}.lock`;
|
|
40
|
+
const startedAt = Date.now();
|
|
41
|
+
let handle;
|
|
42
|
+
while (handle === void 0) {
|
|
43
|
+
signal?.throwIfAborted();
|
|
44
|
+
try {
|
|
45
|
+
handle = await openNoFollow(lockPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (errorCode(error) !== "EEXIST") throw error;
|
|
48
|
+
await rejectSymlink(lockPath);
|
|
49
|
+
if (Date.now() - startedAt >= LOCK_TIMEOUT_MS) throw credentialFileError("Codex credential writer lock did not become available", void 0, "CODEX_CREDENTIAL_LOCK_TIMEOUT");
|
|
50
|
+
await abortableDelay(LOCK_RETRY_MS, signal);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
signal?.throwIfAborted();
|
|
55
|
+
return await task();
|
|
56
|
+
} finally {
|
|
57
|
+
await handle.close().catch(() => void 0);
|
|
58
|
+
await unlink(lockPath).catch(() => void 0);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Atomically replace one credential file while preserving private modes. */
|
|
62
|
+
async function replaceCredentialText(location, payload, signal) {
|
|
63
|
+
signal?.throwIfAborted();
|
|
64
|
+
if (Buffer.byteLength(payload, "utf8") > 1048576) throw credentialFileError("Codex credential file exceeds the 1 MiB limit");
|
|
65
|
+
const directory = dirname(location);
|
|
66
|
+
await prepareDirectory(directory, signal);
|
|
67
|
+
await rejectSymlink(location);
|
|
68
|
+
const temporary = join(directory, `.auth-${process.pid}-${randomUUID()}.tmp`);
|
|
69
|
+
let handle;
|
|
70
|
+
try {
|
|
71
|
+
handle = await openNoFollow(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
|
|
72
|
+
signal?.throwIfAborted();
|
|
73
|
+
await handle.writeFile(payload, {
|
|
74
|
+
encoding: "utf8",
|
|
75
|
+
...signal === void 0 ? {} : { signal }
|
|
76
|
+
});
|
|
77
|
+
await handle.sync();
|
|
78
|
+
await handle.close();
|
|
79
|
+
handle = void 0;
|
|
80
|
+
signal?.throwIfAborted();
|
|
81
|
+
await chmod(temporary, 384);
|
|
82
|
+
await rejectSymlink(location);
|
|
83
|
+
await rename(temporary, location);
|
|
84
|
+
await chmod(location, 384);
|
|
85
|
+
await syncDirectory(directory);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
await handle?.close().catch(() => void 0);
|
|
88
|
+
await unlink(temporary).catch(() => void 0);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function credentialFileError(message, cause, code = "INVALID_CREDENTIAL") {
|
|
93
|
+
return new AgentSdkError(message, code, cause === void 0 ? {} : { cause });
|
|
94
|
+
}
|
|
95
|
+
function openNoFollow(path, flags, mode) {
|
|
96
|
+
const noFollow = "O_NOFOLLOW" in constants ? constants.O_NOFOLLOW : 0;
|
|
97
|
+
return mode === void 0 ? open(path, flags | noFollow) : open(path, flags | noFollow, mode);
|
|
98
|
+
}
|
|
99
|
+
async function prepareDirectory(directory, signal) {
|
|
100
|
+
signal?.throwIfAborted();
|
|
101
|
+
if (await mkdir(directory, {
|
|
102
|
+
recursive: true,
|
|
103
|
+
mode: 448
|
|
104
|
+
}) !== void 0) await chmod(directory, 448);
|
|
105
|
+
signal?.throwIfAborted();
|
|
106
|
+
}
|
|
107
|
+
async function rejectSymlink(path) {
|
|
108
|
+
try {
|
|
109
|
+
if ((await lstat(path)).isSymbolicLink()) throw credentialFileError("Codex credential path must not be a symbolic link");
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (errorCode(error) === "ENOENT") return;
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function syncDirectory(directory) {
|
|
116
|
+
let handle;
|
|
117
|
+
try {
|
|
118
|
+
handle = await open(directory, constants.O_RDONLY);
|
|
119
|
+
await handle.sync();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
const code = errorCode(error);
|
|
122
|
+
if (process.platform === "win32" && (code === "EISDIR" || code === "EPERM" || code === "EINVAL")) return;
|
|
123
|
+
throw error;
|
|
124
|
+
} finally {
|
|
125
|
+
await handle?.close().catch(() => void 0);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function abortableDelay(ms, signal) {
|
|
129
|
+
if (signal?.aborted === true) return Promise.reject(signal.reason);
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
const finish = () => {
|
|
132
|
+
signal?.removeEventListener("abort", abort);
|
|
133
|
+
resolve();
|
|
134
|
+
};
|
|
135
|
+
const abort = () => {
|
|
136
|
+
clearTimeout(timer);
|
|
137
|
+
reject(signal?.reason ?? /* @__PURE__ */ new Error("operation aborted"));
|
|
138
|
+
};
|
|
139
|
+
const timer = setTimeout(finish, ms);
|
|
140
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function errorCode(error) {
|
|
144
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
145
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, "code");
|
|
146
|
+
return descriptor !== void 0 && "value" in descriptor ? descriptor.value : void 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/codex-store.ts
|
|
151
|
+
/** Symlink-safe, atomic Node filesystem store for Codex OAuth credentials. */
|
|
152
|
+
const DEFAULT_CODEX_AUTH_PATH = ".providers/.codex/auth.json";
|
|
153
|
+
const CODEX_AUTH_PATH_ENV = "AI_AGENT_SDK_CODEX_AUTH";
|
|
154
|
+
function resolveCodexAuthPath(explicitPath, options = {}) {
|
|
155
|
+
const env = options.env ?? process.env;
|
|
156
|
+
const selected = nonEmptyPath(explicitPath) ?? nonEmptyPath(env["AI_AGENT_SDK_CODEX_AUTH"]) ?? ".providers/.codex/auth.json";
|
|
157
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
158
|
+
return isAbsolute(selected) ? resolve(selected) : resolve(cwd, selected);
|
|
159
|
+
}
|
|
160
|
+
/** @deprecated Use the revisioned {@link fileCodexCredentialStore}. */
|
|
161
|
+
function fileCodexAuthStore(path, options = {}) {
|
|
162
|
+
const location = resolveCodexAuthPath(path, options);
|
|
163
|
+
return {
|
|
164
|
+
location,
|
|
165
|
+
async read() {
|
|
166
|
+
return (await readAuthFile(location))?.file;
|
|
167
|
+
},
|
|
168
|
+
async write(file) {
|
|
169
|
+
const payload = authPayload(file);
|
|
170
|
+
await withCredentialFileLock(location, void 0, async () => {
|
|
171
|
+
await replaceCredentialText(location, payload);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/** Revisioned compare-and-swap store used by normal Node provider composition. */
|
|
177
|
+
function fileCodexCredentialStore(path, options = {}) {
|
|
178
|
+
const location = resolveCodexAuthPath(path, options);
|
|
179
|
+
return defineCredentialStore({
|
|
180
|
+
id: "codex-file-credentials",
|
|
181
|
+
label: "Codex file credential store",
|
|
182
|
+
async read({ signal }) {
|
|
183
|
+
const snapshot = await readAuthFile(location, signal);
|
|
184
|
+
return snapshot === void 0 ? void 0 : Object.freeze({
|
|
185
|
+
value: snapshot.file,
|
|
186
|
+
revision: revisionOf(snapshot.raw)
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
async commit(input, { signal }) {
|
|
190
|
+
validateExpectedRevision(input.expectedRevision);
|
|
191
|
+
const payload = authPayload(input.value);
|
|
192
|
+
return await withCredentialFileLock(location, signal, async () => {
|
|
193
|
+
const current = await readAuthFile(location, signal);
|
|
194
|
+
if ((current === void 0 ? null : revisionOf(current.raw)) !== input.expectedRevision) throw credentialFileError("Codex credential revision changed before commit", void 0, "CODEX_CREDENTIAL_REVISION_CONFLICT");
|
|
195
|
+
await replaceCredentialText(location, payload, signal);
|
|
196
|
+
return Object.freeze({ revision: revisionOf(payload) });
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function nonEmptyPath(value) {
|
|
202
|
+
if (value === void 0 || value.trim().length === 0) return void 0;
|
|
203
|
+
if (value.includes("\0")) throw new TypeError("Codex credential path must not contain NUL");
|
|
204
|
+
return value;
|
|
205
|
+
}
|
|
206
|
+
async function readAuthFile(location, signal) {
|
|
207
|
+
const raw = await readCredentialText(location, signal);
|
|
208
|
+
if (raw === void 0) return void 0;
|
|
209
|
+
try {
|
|
210
|
+
return Object.freeze({
|
|
211
|
+
file: JSON.parse(raw),
|
|
212
|
+
raw
|
|
213
|
+
});
|
|
214
|
+
} catch (error) {
|
|
215
|
+
throw credentialFileError("Codex credential file is not valid JSON; delete it and log in again", error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function authPayload(file) {
|
|
219
|
+
try {
|
|
220
|
+
const encoded = JSON.stringify(file, null, 2);
|
|
221
|
+
if (encoded === void 0) throw new TypeError("credential value is not JSON");
|
|
222
|
+
return `${encoded}\n`;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
throw credentialFileError("Codex credential value could not be serialized", error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function revisionOf(raw) {
|
|
228
|
+
return createHash("sha256").update(raw, "utf8").digest("hex");
|
|
229
|
+
}
|
|
230
|
+
function validateExpectedRevision(value) {
|
|
231
|
+
if (value !== null && (typeof value !== "string" || value.length === 0 || value.length > 256)) throw credentialFileError("Codex expected credential revision is invalid");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/codex.ts
|
|
236
|
+
/** @deprecated Use {@link codexNodeProviderPlugin} for normal runtime composition. */
|
|
237
|
+
function codexNodeAdapter(options = {}) {
|
|
238
|
+
return codexAdapter({
|
|
239
|
+
...options,
|
|
240
|
+
authStore: options.authStore ?? fileCodexAuthStore()
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/** @deprecated Use {@link codexNodeProviderPlugin}. */
|
|
244
|
+
function codexNodePlugin(options = {}) {
|
|
245
|
+
const routes = Object.freeze([...options.routes ?? ["codex"]]);
|
|
246
|
+
const adapter = codexNodeAdapter(options);
|
|
247
|
+
return Object.freeze({
|
|
248
|
+
id: "codex",
|
|
249
|
+
displayName: "Codex",
|
|
250
|
+
setup: (registrar) => {
|
|
251
|
+
registrar.registerAdapter(routes, adapter);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/** Preferred Node composition plugin backed by revision-safe file credentials by default. */
|
|
256
|
+
function codexNodeProviderPlugin(options = {}) {
|
|
257
|
+
return codexPlugin({
|
|
258
|
+
...options,
|
|
259
|
+
authStore: options.authStore ?? fileCodexCredentialStore()
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
/** Compatibility alias for the former `ai-agent-sdk/codex` entry. */
|
|
263
|
+
const codexAdapter$1 = codexNodeAdapter;
|
|
264
|
+
/** Compatibility alias for the former `ai-agent-sdk/codex` entry. */
|
|
265
|
+
const codexPlugin$1 = codexNodePlugin;
|
|
266
|
+
|
|
267
|
+
//#endregion
|
|
268
|
+
export { shouldRefresh as C, fileCodexCredentialStore as D, fileCodexAuthStore as E, resolveCodexAuthPath as O, runDeviceCodeLogin as S, DEFAULT_CODEX_AUTH_PATH as T, readJwtClaims as _, CODEX_ORIGINATOR as a, requireTokens as b, LAST_REFRESH_MAX_AGE_MS as c, codexNodePlugin as d, codexNodeProviderPlugin as f, memoryCodexCredentialStore as g, memoryCodexAuthStore as h, CODEX_CLIENT_VERSION as i, codexAdapter$1 as l, isFedrampAccount as m, CODEX_BASE_URL as n, CodexRefreshError as o, codexPlugin$1 as p, CODEX_CLIENT_ID as r, DEFAULT_CODEX_ISSUER as s, ACCESS_TOKEN_REFRESH_WINDOW_MS as t, codexNodeAdapter as u, refreshCodexTokens as v, CODEX_AUTH_PATH_ENV as w, resolveAccountId as x, requestDeviceCode as y };
|
|
269
|
+
//# sourceMappingURL=codex-CBvNhtAh.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-CBvNhtAh.mjs","names":["universalCodexAdapter","universalCodexPlugin","codexAdapter","codexPlugin"],"sources":["../src/common/credential-file.ts","../src/codex-store.ts","../src/codex.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport { constants } from 'node:fs'\nimport {\n chmod,\n lstat,\n mkdir,\n open,\n rename,\n unlink,\n type FileHandle,\n} from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { AgentSdkError } from '@alvin0/ai-agent-sdk-core'\n\nexport const MAX_CREDENTIAL_FILE_BYTES = 1024 * 1024\nconst LOCK_RETRY_MS = 10\nconst LOCK_TIMEOUT_MS = 30_000\n\n/** Read one stable regular file without following its final symlink. */\nexport async function readCredentialText(\n location: string,\n signal?: AbortSignal,\n): Promise<string | undefined> {\n signal?.throwIfAborted()\n let handle: FileHandle | undefined\n try {\n handle = await openNoFollow(location, constants.O_RDONLY)\n signal?.throwIfAborted()\n const [opened, linked] = await Promise.all([handle.stat(), lstat(location)])\n if (!opened.isFile() || linked.isSymbolicLink()\n || opened.dev !== linked.dev || opened.ino !== linked.ino) {\n throw credentialFileError('Codex credential path must be a stable regular file')\n }\n if (opened.size > MAX_CREDENTIAL_FILE_BYTES) {\n throw credentialFileError('Codex credential file exceeds the 1 MiB limit')\n }\n const raw = await handle.readFile({ encoding: 'utf8', ...(signal === undefined ? {} : { signal }) })\n signal?.throwIfAborted()\n return raw\n } catch (error: unknown) {\n if (errorCode(error) === 'ENOENT') return undefined\n throw error\n } finally {\n await handle?.close().catch(() => undefined)\n }\n}\n\n/** Serialize cross-process compare-and-swap writers with an exclusive sidecar lock. */\nexport async function withCredentialFileLock<T>(\n location: string,\n signal: AbortSignal | undefined,\n task: () => Promise<T>,\n): Promise<T> {\n const directory = dirname(location)\n await prepareDirectory(directory, signal)\n const lockPath = `${location}.lock`\n const startedAt = Date.now()\n let handle: FileHandle | undefined\n while (handle === undefined) {\n signal?.throwIfAborted()\n try {\n handle = await openNoFollow(\n lockPath,\n constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL,\n 0o600,\n )\n } catch (error: unknown) {\n if (errorCode(error) !== 'EEXIST') throw error\n await rejectSymlink(lockPath)\n if (Date.now() - startedAt >= LOCK_TIMEOUT_MS) {\n throw credentialFileError(\n 'Codex credential writer lock did not become available',\n undefined,\n 'CODEX_CREDENTIAL_LOCK_TIMEOUT',\n )\n }\n await abortableDelay(LOCK_RETRY_MS, signal)\n }\n }\n try {\n signal?.throwIfAborted()\n return await task()\n } finally {\n await handle.close().catch(() => undefined)\n await unlink(lockPath).catch(() => undefined)\n }\n}\n\n/** Atomically replace one credential file while preserving private modes. */\nexport async function replaceCredentialText(\n location: string,\n payload: string,\n signal?: AbortSignal,\n): Promise<void> {\n signal?.throwIfAborted()\n if (Buffer.byteLength(payload, 'utf8') > MAX_CREDENTIAL_FILE_BYTES) {\n throw credentialFileError('Codex credential file exceeds the 1 MiB limit')\n }\n const directory = dirname(location)\n await prepareDirectory(directory, signal)\n await rejectSymlink(location)\n const temporary = join(directory, `.auth-${process.pid}-${randomUUID()}.tmp`)\n let handle: FileHandle | undefined\n try {\n handle = await openNoFollow(\n temporary,\n constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL,\n 0o600,\n )\n signal?.throwIfAborted()\n await handle.writeFile(payload, { encoding: 'utf8', ...(signal === undefined ? {} : { signal }) })\n await handle.sync()\n await handle.close()\n handle = undefined\n signal?.throwIfAborted()\n await chmod(temporary, 0o600)\n await rejectSymlink(location)\n await rename(temporary, location)\n await chmod(location, 0o600)\n await syncDirectory(directory)\n } catch (error: unknown) {\n await handle?.close().catch(() => undefined)\n await unlink(temporary).catch(() => undefined)\n throw error\n }\n}\n\nexport function credentialFileError(\n message: string,\n cause?: unknown,\n code = 'INVALID_CREDENTIAL',\n): AgentSdkError {\n return new AgentSdkError(message, code, cause === undefined ? {} : { cause })\n}\n\nfunction openNoFollow(path: string, flags: number, mode?: number): Promise<FileHandle> {\n const noFollow = 'O_NOFOLLOW' in constants ? constants.O_NOFOLLOW : 0\n return mode === undefined ? open(path, flags | noFollow) : open(path, flags | noFollow, mode)\n}\n\nasync function prepareDirectory(directory: string, signal?: AbortSignal): Promise<void> {\n signal?.throwIfAborted()\n const created = await mkdir(directory, { recursive: true, mode: 0o700 })\n if (created !== undefined) await chmod(directory, 0o700)\n signal?.throwIfAborted()\n}\n\nasync function rejectSymlink(path: string): Promise<void> {\n try {\n if ((await lstat(path)).isSymbolicLink()) {\n throw credentialFileError('Codex credential path must not be a symbolic link')\n }\n } catch (error: unknown) {\n if (errorCode(error) === 'ENOENT') return\n throw error\n }\n}\n\nasync function syncDirectory(directory: string): Promise<void> {\n let handle: FileHandle | undefined\n try {\n handle = await open(directory, constants.O_RDONLY)\n await handle.sync()\n } catch (error: unknown) {\n const code = errorCode(error)\n if (process.platform === 'win32' && (code === 'EISDIR' || code === 'EPERM' || code === 'EINVAL')) return\n throw error\n } finally {\n await handle?.close().catch(() => undefined)\n }\n}\n\nfunction abortableDelay(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted === true) return Promise.reject(signal.reason)\n return new Promise((resolve, reject) => {\n const finish = () => { signal?.removeEventListener('abort', abort); resolve() }\n const abort = () => { clearTimeout(timer); reject(signal?.reason ?? new Error('operation aborted')) }\n const timer = setTimeout(finish, ms)\n signal?.addEventListener('abort', abort, { once: true })\n })\n}\n\nfunction errorCode(error: unknown): unknown {\n if (typeof error !== 'object' || error === null) return undefined\n const descriptor = Object.getOwnPropertyDescriptor(error, 'code')\n return descriptor !== undefined && 'value' in descriptor ? descriptor.value : undefined\n}\n","/** Symlink-safe, atomic Node filesystem store for Codex OAuth credentials. */\n\nimport { createHash } from 'node:crypto'\nimport { isAbsolute, resolve } from 'node:path'\nimport { defineCredentialStore } from '@alvin0/ai-agent-sdk-core/provider'\nimport type {\n CodexAuthFile,\n CodexAuthStore,\n CodexCredentialStore,\n} from '@alvin0/ai-agent-sdk-provider-codex'\nimport {\n credentialFileError,\n readCredentialText,\n replaceCredentialText,\n withCredentialFileLock,\n} from './common/credential-file.ts'\n\nexport const DEFAULT_CODEX_AUTH_PATH = '.providers/.codex/auth.json'\nexport const CODEX_AUTH_PATH_ENV = 'AI_AGENT_SDK_CODEX_AUTH'\n\nexport interface CodexAuthPathOptions {\n /** Base for relative paths. Defaults to `process.cwd()`. */\n readonly cwd?: string\n /** Environment source. Defaults to `process.env`. */\n readonly env?: Readonly<Record<string, string | undefined>>\n}\n\nexport function resolveCodexAuthPath(\n explicitPath?: string,\n options: CodexAuthPathOptions = {},\n): string {\n const env = options.env ?? process.env\n const selected = nonEmptyPath(explicitPath)\n ?? nonEmptyPath(env[CODEX_AUTH_PATH_ENV])\n ?? DEFAULT_CODEX_AUTH_PATH\n const cwd = resolve(options.cwd ?? process.cwd())\n return isAbsolute(selected) ? resolve(selected) : resolve(cwd, selected)\n}\n\n/** @deprecated Use the revisioned {@link fileCodexCredentialStore}. */\nexport function fileCodexAuthStore(\n path?: string,\n options: CodexAuthPathOptions = {},\n): CodexAuthStore {\n const location = resolveCodexAuthPath(path, options)\n return {\n location,\n async read(): Promise<CodexAuthFile | undefined> {\n return (await readAuthFile(location))?.file\n },\n async write(file: CodexAuthFile): Promise<void> {\n const payload = authPayload(file)\n await withCredentialFileLock(location, undefined, async () => {\n await replaceCredentialText(location, payload)\n })\n },\n }\n}\n\n/** Revisioned compare-and-swap store used by normal Node provider composition. */\nexport function fileCodexCredentialStore(\n path?: string,\n options: CodexAuthPathOptions = {},\n): CodexCredentialStore {\n const location = resolveCodexAuthPath(path, options)\n return defineCredentialStore<CodexAuthFile>({\n id: 'codex-file-credentials',\n label: 'Codex file credential store',\n async read({ signal }) {\n const snapshot = await readAuthFile(location, signal)\n return snapshot === undefined ? undefined : Object.freeze({\n value: snapshot.file,\n revision: revisionOf(snapshot.raw),\n })\n },\n async commit(input, { signal }) {\n validateExpectedRevision(input.expectedRevision)\n const payload = authPayload(input.value)\n return await withCredentialFileLock(location, signal, async () => {\n const current = await readAuthFile(location, signal)\n const actual = current === undefined ? null : revisionOf(current.raw)\n if (actual !== input.expectedRevision) {\n throw credentialFileError(\n 'Codex credential revision changed before commit',\n undefined,\n 'CODEX_CREDENTIAL_REVISION_CONFLICT',\n )\n }\n await replaceCredentialText(location, payload, signal)\n return Object.freeze({ revision: revisionOf(payload) })\n })\n },\n })\n}\n\nfunction nonEmptyPath(value: string | undefined): string | undefined {\n if (value === undefined || value.trim().length === 0) return undefined\n if (value.includes('\\0')) throw new TypeError('Codex credential path must not contain NUL')\n return value\n}\n\nasync function readAuthFile(\n location: string,\n signal?: AbortSignal,\n): Promise<{ readonly file: CodexAuthFile; readonly raw: string } | undefined> {\n const raw = await readCredentialText(location, signal)\n if (raw === undefined) return undefined\n try {\n return Object.freeze({ file: JSON.parse(raw) as CodexAuthFile, raw })\n } catch (error: unknown) {\n throw credentialFileError(\n 'Codex credential file is not valid JSON; delete it and log in again',\n error,\n )\n }\n}\n\nfunction authPayload(file: CodexAuthFile): string {\n try {\n const encoded = JSON.stringify(file, null, 2)\n if (encoded === undefined) throw new TypeError('credential value is not JSON')\n return `${encoded}\\n`\n } catch (error: unknown) {\n throw credentialFileError('Codex credential value could not be serialized', error)\n }\n}\n\nfunction revisionOf(raw: string): string {\n return createHash('sha256').update(raw, 'utf8').digest('hex')\n}\n\nfunction validateExpectedRevision(value: string | null): void {\n if (value !== null && (typeof value !== 'string' || value.length === 0 || value.length > 256)) {\n throw credentialFileError('Codex expected credential revision is invalid')\n }\n}\n","/** Node wrapper over the injected-store Universal Codex provider. */\n\nimport type {\n ComposableModelProviderPlugin,\n ModelProviderPlugin,\n ModelProviderRegistrar,\n} from '@alvin0/ai-agent-sdk-core/provider'\nimport {\n CODEX_BASE_URL,\n CODEX_CLIENT_VERSION,\n CODEX_ORIGINATOR,\n codexAdapter as universalCodexAdapter,\n codexPlugin as universalCodexPlugin,\n type CodexAdapterOptions as UniversalCodexAdapterOptions,\n type CodexAuthStore,\n type CodexCredentialStore,\n type CodexProviderOptions,\n} from '@alvin0/ai-agent-sdk-provider-codex'\nimport { fileCodexAuthStore, fileCodexCredentialStore } from './codex-store.ts'\n\nexport { CODEX_BASE_URL, CODEX_CLIENT_VERSION, CODEX_ORIGINATOR }\n\nexport interface CodexNodeAdapterOptions extends Omit<UniversalCodexAdapterOptions, 'authStore'> {\n /** Omit only in the Node compatibility wrapper to use the project-local file store. */\n readonly authStore?: CodexAuthStore\n}\n\nexport interface CodexNodePluginOptions extends CodexNodeAdapterOptions {\n readonly routes?: readonly string[]\n}\n\n/** @deprecated Use {@link codexNodeProviderPlugin} for normal runtime composition. */\nexport function codexNodeAdapter(\n options: CodexNodeAdapterOptions = {},\n): ReturnType<typeof universalCodexAdapter> {\n return universalCodexAdapter({\n ...options,\n authStore: options.authStore ?? fileCodexAuthStore(),\n })\n}\n\n/** @deprecated Use {@link codexNodeProviderPlugin}. */\nexport function codexNodePlugin(options: CodexNodePluginOptions = {}): ModelProviderPlugin {\n const routes = Object.freeze([...(options.routes ?? ['codex'])])\n const adapter = codexNodeAdapter(options)\n return Object.freeze({\n id: 'codex', displayName: 'Codex',\n setup: (registrar: ModelProviderRegistrar) => { registrar.registerAdapter(routes, adapter) },\n })\n}\n\nexport interface CodexNodeProviderOptions extends Omit<CodexProviderOptions, 'authStore'> {\n readonly authStore?: CodexCredentialStore\n}\n\n/** Preferred Node composition plugin backed by revision-safe file credentials by default. */\nexport function codexNodeProviderPlugin(\n options: CodexNodeProviderOptions = {},\n): ComposableModelProviderPlugin & { readonly family: 'codex' } {\n return universalCodexPlugin({\n ...options,\n authStore: options.authStore ?? fileCodexCredentialStore(),\n })\n}\n\n/** Compatibility alias for the former `ai-agent-sdk/codex` entry. */\nexport const codexAdapter = codexNodeAdapter\n/** Compatibility alias for the former `ai-agent-sdk/codex` entry. */\nexport const codexPlugin = codexNodePlugin\nexport type CodexAdapterOptions = CodexNodeAdapterOptions\nexport type CodexPluginOptions = CodexNodePluginOptions\n\nexport {\n ACCESS_TOKEN_REFRESH_WINDOW_MS,\n CODEX_CLIENT_ID,\n CodexRefreshError,\n DEFAULT_CODEX_ISSUER,\n LAST_REFRESH_MAX_AGE_MS,\n isFedrampAccount,\n memoryCodexAuthStore,\n memoryCodexCredentialStore,\n readJwtClaims,\n refreshCodexTokens,\n requestDeviceCode,\n requireTokens,\n resolveAccountId,\n runDeviceCodeLogin,\n shouldRefresh,\n type CodexAuthFile,\n type CodexAuthStore,\n type CodexCredentialStore,\n type CodexDeviceCode,\n type CodexJwtClaims,\n type CodexLoginProgress,\n type CodexLoginResult,\n type CodexOAuthOptions,\n type CodexTokens,\n type RefreshFailureKind,\n} from '@alvin0/ai-agent-sdk-provider-codex'\nexport {\n CODEX_AUTH_PATH_ENV,\n DEFAULT_CODEX_AUTH_PATH,\n fileCodexAuthStore,\n fileCodexCredentialStore,\n resolveCodexAuthPath,\n type CodexAuthPathOptions,\n} from './codex-store.ts'\n"],"mappings":"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"}
|
package/dist/codex.d.mts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ComposableModelProviderPlugin, ModelProviderPlugin } from "@alvin0/ai-agent-sdk-core/provider";
|
|
2
|
+
import { ACCESS_TOKEN_REFRESH_WINDOW_MS, CODEX_BASE_URL, CODEX_CLIENT_ID, CODEX_CLIENT_VERSION, CODEX_ORIGINATOR, CodexAdapterOptions as CodexAdapterOptions$1, CodexAuthFile, CodexAuthStore, CodexAuthStore as CodexAuthStore$1, CodexCredentialStore, CodexCredentialStore as CodexCredentialStore$1, CodexDeviceCode, CodexJwtClaims, CodexLoginProgress, CodexLoginResult, CodexOAuthOptions, CodexProviderOptions, CodexRefreshError, CodexTokens, DEFAULT_CODEX_ISSUER, LAST_REFRESH_MAX_AGE_MS, RefreshFailureKind, codexAdapter as codexAdapter$1, isFedrampAccount, memoryCodexAuthStore, memoryCodexCredentialStore, readJwtClaims, refreshCodexTokens, requestDeviceCode, requireTokens, resolveAccountId, runDeviceCodeLogin, shouldRefresh } from "@alvin0/ai-agent-sdk-provider-codex";
|
|
3
|
+
//#region src/codex-store.d.ts
|
|
4
|
+
declare const DEFAULT_CODEX_AUTH_PATH = ".providers/.codex/auth.json";
|
|
5
|
+
declare const CODEX_AUTH_PATH_ENV = "AI_AGENT_SDK_CODEX_AUTH";
|
|
6
|
+
interface CodexAuthPathOptions {
|
|
7
|
+
/** Base for relative paths. Defaults to `process.cwd()`. */
|
|
8
|
+
readonly cwd?: string;
|
|
9
|
+
/** Environment source. Defaults to `process.env`. */
|
|
10
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
11
|
+
}
|
|
12
|
+
declare function resolveCodexAuthPath(explicitPath?: string, options?: CodexAuthPathOptions): string;
|
|
13
|
+
/** @deprecated Use the revisioned {@link fileCodexCredentialStore}. */
|
|
14
|
+
declare function fileCodexAuthStore(path?: string, options?: CodexAuthPathOptions): CodexAuthStore$1;
|
|
15
|
+
/** Revisioned compare-and-swap store used by normal Node provider composition. */
|
|
16
|
+
declare function fileCodexCredentialStore(path?: string, options?: CodexAuthPathOptions): CodexCredentialStore$1;
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/codex.d.ts
|
|
19
|
+
interface CodexNodeAdapterOptions extends Omit<CodexAdapterOptions$1, 'authStore'> {
|
|
20
|
+
/** Omit only in the Node compatibility wrapper to use the project-local file store. */
|
|
21
|
+
readonly authStore?: CodexAuthStore$1;
|
|
22
|
+
}
|
|
23
|
+
interface CodexNodePluginOptions extends CodexNodeAdapterOptions {
|
|
24
|
+
readonly routes?: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
/** @deprecated Use {@link codexNodeProviderPlugin} for normal runtime composition. */
|
|
27
|
+
declare function codexNodeAdapter(options?: CodexNodeAdapterOptions): ReturnType<typeof codexAdapter$1>;
|
|
28
|
+
/** @deprecated Use {@link codexNodeProviderPlugin}. */
|
|
29
|
+
declare function codexNodePlugin(options?: CodexNodePluginOptions): ModelProviderPlugin;
|
|
30
|
+
interface CodexNodeProviderOptions extends Omit<CodexProviderOptions, 'authStore'> {
|
|
31
|
+
readonly authStore?: CodexCredentialStore$1;
|
|
32
|
+
}
|
|
33
|
+
/** Preferred Node composition plugin backed by revision-safe file credentials by default. */
|
|
34
|
+
declare function codexNodeProviderPlugin(options?: CodexNodeProviderOptions): ComposableModelProviderPlugin & {
|
|
35
|
+
readonly family: 'codex';
|
|
36
|
+
};
|
|
37
|
+
/** Compatibility alias for the former `ai-agent-sdk/codex` entry. */
|
|
38
|
+
declare const codexAdapter: typeof codexNodeAdapter;
|
|
39
|
+
/** Compatibility alias for the former `ai-agent-sdk/codex` entry. */
|
|
40
|
+
declare const codexPlugin: typeof codexNodePlugin;
|
|
41
|
+
type CodexAdapterOptions = CodexNodeAdapterOptions;
|
|
42
|
+
type CodexPluginOptions = CodexNodePluginOptions;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { ACCESS_TOKEN_REFRESH_WINDOW_MS, CODEX_AUTH_PATH_ENV, CODEX_BASE_URL, CODEX_CLIENT_ID, CODEX_CLIENT_VERSION, CODEX_ORIGINATOR, CodexAdapterOptions, type CodexAuthFile, type CodexAuthPathOptions, type CodexAuthStore, type CodexCredentialStore, type CodexDeviceCode, type CodexJwtClaims, type CodexLoginProgress, type CodexLoginResult, CodexNodeAdapterOptions, CodexNodePluginOptions, CodexNodeProviderOptions, type CodexOAuthOptions, CodexPluginOptions, CodexRefreshError, type CodexTokens, DEFAULT_CODEX_AUTH_PATH, DEFAULT_CODEX_ISSUER, LAST_REFRESH_MAX_AGE_MS, type RefreshFailureKind, codexAdapter, codexNodeAdapter, codexNodePlugin, codexNodeProviderPlugin, codexPlugin, fileCodexAuthStore, fileCodexCredentialStore, isFedrampAccount, memoryCodexAuthStore, memoryCodexCredentialStore, readJwtClaims, refreshCodexTokens, requestDeviceCode, requireTokens, resolveAccountId, resolveCodexAuthPath, runDeviceCodeLogin, shouldRefresh };
|
|
45
|
+
//# sourceMappingURL=codex.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.d.mts","names":[],"sources":["../src/codex-store.ts","../src/codex.ts"],"mappings":";;;cAiBa;cACA;UAEI;;WAEN;;WAEA,MAAM,SAAS;;iBAGV,qBACd,uBACA,UAAS;;iBAWK,mBACd,eACA,UAAS,uBACR;;iBAiBa,yBACd,eACA,UAAS,uBACR;;;UCzCc,gCAAgC,KAAK;;WAE3C,YAAY;;UAGN,+BAA+B;WACrC;;;iBAIK,iBACd,UAAS,0BACR,kBAAkB;;iBAQL,gBAAgB,UAAS,yBAA8B;UAStD,iCAAiC,KAAK;WAC5C,YAAY;;;iBAIP,wBACd,UAAS,2BACR;WAA2C;;;cAQjC,qBAAY;;cAEZ,oBAAW;KACZ,sBAAsB;KACtB,qBAAqB"}
|
package/dist/codex.mjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { C as shouldRefresh, D as fileCodexCredentialStore, E as fileCodexAuthStore, O as resolveCodexAuthPath, S as runDeviceCodeLogin, T as DEFAULT_CODEX_AUTH_PATH, _ as readJwtClaims, a as CODEX_ORIGINATOR, b as requireTokens, c as LAST_REFRESH_MAX_AGE_MS, d as codexNodePlugin, f as codexNodeProviderPlugin, g as memoryCodexCredentialStore, h as memoryCodexAuthStore, i as CODEX_CLIENT_VERSION, l as codexAdapter, m as isFedrampAccount, n as CODEX_BASE_URL, o as CodexRefreshError, p as codexPlugin, r as CODEX_CLIENT_ID, s as DEFAULT_CODEX_ISSUER, t as ACCESS_TOKEN_REFRESH_WINDOW_MS, u as codexNodeAdapter, v as refreshCodexTokens, w as CODEX_AUTH_PATH_ENV, x as resolveAccountId, y as requestDeviceCode } from "./codex-CBvNhtAh.mjs";
|
|
2
|
+
|
|
3
|
+
export { ACCESS_TOKEN_REFRESH_WINDOW_MS, CODEX_AUTH_PATH_ENV, CODEX_BASE_URL, CODEX_CLIENT_ID, CODEX_CLIENT_VERSION, CODEX_ORIGINATOR, CodexRefreshError, DEFAULT_CODEX_AUTH_PATH, DEFAULT_CODEX_ISSUER, LAST_REFRESH_MAX_AGE_MS, codexAdapter, codexNodeAdapter, codexNodePlugin, codexNodeProviderPlugin, codexPlugin, fileCodexAuthStore, fileCodexCredentialStore, isFedrampAccount, memoryCodexAuthStore, memoryCodexCredentialStore, readJwtClaims, refreshCodexTokens, requestDeviceCode, requireTokens, resolveAccountId, resolveCodexAuthPath, runDeviceCodeLogin, shouldRefresh };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CredentialSource } from "@alvin0/ai-agent-sdk-core/provider";
|
|
2
|
+
//#region src/env.d.ts
|
|
3
|
+
/** Read a credential lazily while preserving the historical callable view. */
|
|
4
|
+
declare function envCredential(envVar: string): CredentialSource & (() => string);
|
|
5
|
+
/** @deprecated Use {@link envCredential}. */
|
|
6
|
+
declare const apiKeyFromEnv: typeof envCredential;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { envCredential as n, apiKeyFromEnv as t };
|
|
9
|
+
//# sourceMappingURL=env-Bsy-tNY9.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-Bsy-tNY9.d.mts","names":[],"sources":["../src/env.ts"],"mappings":";;;iBAOgB,cAAc,iBAAiB;;cAyBlC,sBAAa"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AgentSdkError, MISSING_CREDENTIAL_CODE } from "@alvin0/ai-agent-sdk-core";
|
|
2
|
+
import { defineCredentialSource } from "@alvin0/ai-agent-sdk-core/provider";
|
|
3
|
+
|
|
4
|
+
//#region src/env.ts
|
|
5
|
+
/** Read a credential lazily while preserving the historical callable view. */
|
|
6
|
+
function envCredential(envVar) {
|
|
7
|
+
if (envVar.trim().length === 0) throw new TypeError("credential environment variable must not be empty");
|
|
8
|
+
const read = () => {
|
|
9
|
+
const value = process.env[envVar];
|
|
10
|
+
if (value === void 0 || value.length === 0) throw new AgentSdkError(`no credential available; set ${envVar}`, MISSING_CREDENTIAL_CODE);
|
|
11
|
+
return value;
|
|
12
|
+
};
|
|
13
|
+
const source = defineCredentialSource({
|
|
14
|
+
id: `env:${envVar}`,
|
|
15
|
+
resolve: ({ signal }) => {
|
|
16
|
+
signal.throwIfAborted();
|
|
17
|
+
return read();
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return Object.freeze(Object.assign(read, {
|
|
21
|
+
kind: source.kind,
|
|
22
|
+
apiVersion: source.apiVersion,
|
|
23
|
+
id: source.id,
|
|
24
|
+
resolve: source.resolve
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
/** @deprecated Use {@link envCredential}. */
|
|
28
|
+
const apiKeyFromEnv = envCredential;
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
export { envCredential as n, apiKeyFromEnv as t };
|
|
32
|
+
//# sourceMappingURL=env-OQOpcank.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-OQOpcank.mjs","names":[],"sources":["../src/env.ts"],"sourcesContent":["import { AgentSdkError, MISSING_CREDENTIAL_CODE } from '@alvin0/ai-agent-sdk-core'\nimport {\n defineCredentialSource,\n type CredentialSource,\n} from '@alvin0/ai-agent-sdk-core/provider'\n\n/** Read a credential lazily while preserving the historical callable view. */\nexport function envCredential(envVar: string): CredentialSource & (() => string) {\n if (envVar.trim().length === 0) throw new TypeError('credential environment variable must not be empty')\n const read = () => {\n const value = process.env[envVar]\n if (value === undefined || value.length === 0) {\n throw new AgentSdkError(`no credential available; set ${envVar}`, MISSING_CREDENTIAL_CODE)\n }\n return value\n }\n const source = defineCredentialSource({\n id: `env:${envVar}`,\n resolve: ({ signal }) => {\n signal.throwIfAborted()\n return read()\n },\n })\n return Object.freeze(Object.assign(read, {\n kind: source.kind,\n apiVersion: source.apiVersion,\n id: source.id,\n resolve: source.resolve,\n }))\n}\n\n/** @deprecated Use {@link envCredential}. */\nexport const apiKeyFromEnv = envCredential\n"],"mappings":";;;;;AAOA,SAAgB,cAAc,QAAmD;CAC/E,IAAI,OAAO,KAAK,CAAC,CAAC,WAAW,GAAG,MAAM,IAAI,UAAU,mDAAmD;CACvG,MAAM,aAAa;EACjB,MAAM,QAAQ,QAAQ,IAAI;EAC1B,IAAI,UAAU,UAAa,MAAM,WAAW,GAC1C,MAAM,IAAI,cAAc,gCAAgC,UAAU,uBAAuB;EAE3F,OAAO;CACT;CACA,MAAM,SAAS,uBAAuB;EACpC,IAAI,OAAO;EACX,UAAU,EAAE,aAAa;GACvB,OAAO,eAAe;GACtB,OAAO,KAAK;EACd;CACF,CAAC;CACD,OAAO,OAAO,OAAO,OAAO,OAAO,MAAM;EACvC,MAAM,OAAO;EACb,YAAY,OAAO;EACnB,IAAI,OAAO;EACX,SAAS,OAAO;CAClB,CAAC,CAAC;AACJ;;AAGA,MAAa,gBAAgB"}
|
package/dist/env.d.mts
ADDED
package/dist/env.mjs
ADDED
package/dist/index.d.mts
ADDED
package/dist/index.mjs
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alvin0/ai-agent-sdk-auth-node",
|
|
3
|
+
"author": {
|
|
4
|
+
"name": "alvin0 - chaulamdinhai",
|
|
5
|
+
"email": "chaulamdinhai@gmail.com"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"description": "Node environment credentials and project-local Codex authentication for ai-agent-sdk",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/alvin0/ai-agent-sdk.git",
|
|
13
|
+
"directory": "packages/auth-node"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/alvin0/ai-agent-sdk/tree/main/packages/auth-node#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/alvin0/ai-agent-sdk/issues"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"bin",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"main": "./dist/index.mjs",
|
|
28
|
+
"types": "./dist/index.d.mts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.mts",
|
|
32
|
+
"import": "./dist/index.mjs",
|
|
33
|
+
"default": "./dist/index.mjs"
|
|
34
|
+
},
|
|
35
|
+
"./env": {
|
|
36
|
+
"types": "./dist/env.d.mts",
|
|
37
|
+
"import": "./dist/env.mjs",
|
|
38
|
+
"default": "./dist/env.mjs"
|
|
39
|
+
},
|
|
40
|
+
"./codex": {
|
|
41
|
+
"types": "./dist/codex.d.mts",
|
|
42
|
+
"import": "./dist/codex.mjs",
|
|
43
|
+
"default": "./dist/codex.mjs"
|
|
44
|
+
},
|
|
45
|
+
"./package.json": "./package.json"
|
|
46
|
+
},
|
|
47
|
+
"bin": {
|
|
48
|
+
"ai-agent-sdk-codex-login": "./bin/ai-agent-sdk-codex-login.mjs"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public",
|
|
52
|
+
"provenance": true
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@alvin0/ai-agent-sdk-core": "^0.1.0",
|
|
56
|
+
"@alvin0/ai-agent-sdk-provider-codex": "^0.1.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependenciesMeta": {
|
|
59
|
+
"@alvin0/ai-agent-sdk-provider-codex": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@alvin0/ai-agent-sdk-core": "^0.1.0",
|
|
65
|
+
"@alvin0/ai-agent-sdk-provider-codex": "^0.1.0",
|
|
66
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
67
|
+
"@types/node": "26.4.0",
|
|
68
|
+
"publint": "0.3.24",
|
|
69
|
+
"tsdown": "0.22.14",
|
|
70
|
+
"typescript": "7.0.2",
|
|
71
|
+
"vitest": "4.1.11"
|
|
72
|
+
},
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": ">=22.12"
|
|
75
|
+
},
|
|
76
|
+
"aiAgentSdk": {
|
|
77
|
+
"runtime": "node",
|
|
78
|
+
"coreApi": 1,
|
|
79
|
+
"roles": [
|
|
80
|
+
"credential-source",
|
|
81
|
+
"credential-store"
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
"scripts": {
|
|
85
|
+
"build": "tsdown",
|
|
86
|
+
"clean": "node -e \"for(const p of ['dist','artifacts'])require('node:fs').rmSync(p,{recursive:true,force:true})\"",
|
|
87
|
+
"typecheck": "tsc --noEmit",
|
|
88
|
+
"test": "vitest run --config vitest.config.ts",
|
|
89
|
+
"pack": "pnpm pack --pack-destination artifacts",
|
|
90
|
+
"test:pack": "node ../../scripts/test-packed-node-capability.mts auth-node",
|
|
91
|
+
"check:publint": "publint",
|
|
92
|
+
"check:types": "attw --profile esm-only --pack ."
|
|
93
|
+
}
|
|
94
|
+
}
|