@poncho-ai/harness 0.30.0 → 0.31.1

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/dist/index.js CHANGED
@@ -134,7 +134,7 @@ var parseAgentMarkdown = (content) => {
134
134
  if (typeof parsed.name !== "string" || parsed.name.trim() === "") {
135
135
  throw new Error("Invalid AGENT.md: frontmatter requires a non-empty `name`.");
136
136
  }
137
- const KNOWN_PROVIDERS = /* @__PURE__ */ new Set(["anthropic", "openai"]);
137
+ const KNOWN_PROVIDERS = /* @__PURE__ */ new Set(["anthropic", "openai", "openai-codex"]);
138
138
  const splitProviderPrefix = (raw) => {
139
139
  const slashIdx = raw.indexOf("/");
140
140
  if (slashIdx > 0) {
@@ -1440,6 +1440,11 @@ All credentials in \`poncho.config.js\` use **env var name** fields (\`*Env\` su
1440
1440
  |---|---|---|
1441
1441
  | \`providers.anthropic.apiKeyEnv\` | \`ANTHROPIC_API_KEY\` | Anthropic model API key |
1442
1442
  | \`providers.openai.apiKeyEnv\` | \`OPENAI_API_KEY\` | OpenAI model API key |
1443
+ | \`providers.openaiCodex.refreshTokenEnv\` | \`OPENAI_CODEX_REFRESH_TOKEN\` | OpenAI Codex OAuth refresh token |
1444
+ | \`providers.openaiCodex.accountIdEnv\` | \`OPENAI_CODEX_ACCOUNT_ID\` | OpenAI Codex account/org routing header (optional) |
1445
+ | \`providers.openaiCodex.accessTokenEnv\` | \`OPENAI_CODEX_ACCESS_TOKEN\` | OpenAI Codex OAuth access token seed (optional) |
1446
+ | \`providers.openaiCodex.accessTokenExpiresAtEnv\` | \`OPENAI_CODEX_ACCESS_TOKEN_EXPIRES_AT\` | Access token epoch expiry in ms (optional) |
1447
+ | \`providers.openaiCodex.authFilePathEnv\` | \`OPENAI_CODEX_AUTH_FILE\` | Overrides local auth file path used by \`poncho auth\` |
1443
1448
  | \`auth.tokenEnv\` | \`PONCHO_AUTH_TOKEN\` | Auth passphrase / bearer token |
1444
1449
  | \`storage.urlEnv\` | \`UPSTASH_REDIS_REST_URL\` / \`REDIS_URL\` | Storage connection URL |
1445
1450
  | \`storage.tokenEnv\` | \`UPSTASH_REDIS_REST_TOKEN\` | Upstash REST token |
@@ -1511,6 +1516,10 @@ export default {
1511
1516
  providers: {
1512
1517
  // anthropic: { apiKeyEnv: 'ANTHROPIC_API_KEY' }, // default
1513
1518
  // openai: { apiKeyEnv: 'OPENAI_API_KEY' }, // default
1519
+ // openaiCodex: {
1520
+ // refreshTokenEnv: 'OPENAI_CODEX_REFRESH_TOKEN',
1521
+ // accountIdEnv: 'OPENAI_CODEX_ACCOUNT_ID', // optional
1522
+ // },
1514
1523
  },
1515
1524
 
1516
1525
  // Unified storage (preferred). Replaces separate \`state\` and \`memory\` blocks.
@@ -1609,6 +1618,11 @@ Remote storage keys are namespaced and versioned, for example \`poncho:v1:<agent
1609
1618
  |----------|----------|-------------|
1610
1619
  | \`ANTHROPIC_API_KEY\` | Yes* | Claude API key |
1611
1620
  | \`OPENAI_API_KEY\` | No | OpenAI API key (if using OpenAI) |
1621
+ | \`OPENAI_CODEX_REFRESH_TOKEN\` | No | OpenAI Codex OAuth refresh token (if using \`model.provider: openai-codex\`) |
1622
+ | \`OPENAI_CODEX_ACCOUNT_ID\` | No | OpenAI Codex account/org id for request routing (optional) |
1623
+ | \`OPENAI_CODEX_ACCESS_TOKEN\` | No | Optional pre-seeded short-lived access token |
1624
+ | \`OPENAI_CODEX_ACCESS_TOKEN_EXPIRES_AT\` | No | Epoch millis expiry for \`OPENAI_CODEX_ACCESS_TOKEN\` |
1625
+ | \`OPENAI_CODEX_AUTH_FILE\` | No | Local auth store override for \`poncho auth\` commands |
1612
1626
  | \`PONCHO_AUTH_TOKEN\` | No | Unified auth token (Web UI passphrase + API Bearer token) |
1613
1627
  | \`PONCHO_INTERNAL_SECRET\` | No | Shared secret used by internal serverless callbacks (recommended for Vercel/Lambda) |
1614
1628
  | \`PONCHO_SELF_BASE_URL\` | No | Explicit base URL for internal self-callbacks when auto-detection is unavailable |
@@ -1833,6 +1847,36 @@ Make sure you have a \`.env\` file with your API key:
1833
1847
  echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
1834
1848
  \`\`\`
1835
1849
 
1850
+ ### "OpenAI Codex credentials not found"
1851
+
1852
+ Bootstrap credentials with device auth, then export env values:
1853
+
1854
+ \`\`\`bash
1855
+ poncho auth login --provider openai-codex --device
1856
+ poncho auth export --provider openai-codex --format env
1857
+ \`\`\`
1858
+
1859
+ For production, copy exported values into your deployment secret manager (not committed files).
1860
+
1861
+ ### "OpenAI Codex token refresh failed" / \`invalid_grant\`
1862
+
1863
+ Your refresh token is expired or rotated. Re-run one-time auth and rotate deployment secrets:
1864
+
1865
+ \`\`\`bash
1866
+ poncho auth login --provider openai-codex --device
1867
+ poncho auth export --provider openai-codex --format env
1868
+ \`\`\`
1869
+
1870
+ Then restart your deployment so new secrets are loaded.
1871
+
1872
+ ### "Missing scopes: model.request / api.model.read / api.responses.write"
1873
+
1874
+ Re-authenticate and ensure the OAuth flow requested required scopes. Then verify:
1875
+
1876
+ \`\`\`bash
1877
+ poncho auth status --provider openai-codex
1878
+ \`\`\`
1879
+
1836
1880
  ### "MCP server failed to connect"
1837
1881
 
1838
1882
  Check that:
@@ -2064,8 +2108,8 @@ var ponchoDocsTool = defineTool({
2064
2108
 
2065
2109
  // src/harness.ts
2066
2110
  import { randomUUID as randomUUID3 } from "crypto";
2067
- import { readFile as readFile8 } from "fs/promises";
2068
- import { resolve as resolve10 } from "path";
2111
+ import { readFile as readFile9 } from "fs/promises";
2112
+ import { resolve as resolve11 } from "path";
2069
2113
  import { getTextContent as getTextContent2 } from "@poncho-ai/sdk";
2070
2114
 
2071
2115
  // src/upload-store.ts
@@ -3572,6 +3616,252 @@ var LocalMcpBridge = class {
3572
3616
  // src/model-factory.ts
3573
3617
  import { createOpenAI } from "@ai-sdk/openai";
3574
3618
  import { createAnthropic } from "@ai-sdk/anthropic";
3619
+
3620
+ // src/openai-codex-auth.ts
3621
+ import { homedir as homedir3 } from "os";
3622
+ import { dirname as dirname4, resolve as resolve8 } from "path";
3623
+ import { mkdir as mkdir5, readFile as readFile7, chmod, writeFile as writeFile6, rm as rm3 } from "fs/promises";
3624
+ var OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
3625
+ var OPENAI_AUTH_ISSUER = "https://auth.openai.com";
3626
+ var REFRESH_TOKEN_GRACE_MS = 5 * 60 * 1e3;
3627
+ var DEVICE_POLLING_SAFETY_MARGIN_MS = 3e3;
3628
+ var DEVICE_FLOW_TIMEOUT_MS = 10 * 60 * 1e3;
3629
+ var REQUIRED_SCOPES = [
3630
+ "openid",
3631
+ "profile",
3632
+ "email",
3633
+ "offline_access",
3634
+ "api.responses.write",
3635
+ "model.request",
3636
+ "api.model.read"
3637
+ ];
3638
+ var defaultedConfig = (config) => ({
3639
+ refreshTokenEnv: config?.refreshTokenEnv ?? "OPENAI_CODEX_REFRESH_TOKEN",
3640
+ accessTokenEnv: config?.accessTokenEnv ?? "OPENAI_CODEX_ACCESS_TOKEN",
3641
+ accessTokenExpiresAtEnv: config?.accessTokenExpiresAtEnv ?? "OPENAI_CODEX_ACCESS_TOKEN_EXPIRES_AT",
3642
+ accountIdEnv: config?.accountIdEnv ?? "OPENAI_CODEX_ACCOUNT_ID",
3643
+ authFilePathEnv: config?.authFilePathEnv ?? "OPENAI_CODEX_AUTH_FILE"
3644
+ });
3645
+ var parseEpochMillis = (value) => {
3646
+ if (!value) return void 0;
3647
+ const parsed = Number.parseInt(value, 10);
3648
+ if (Number.isNaN(parsed) || parsed <= 0) return void 0;
3649
+ return parsed;
3650
+ };
3651
+ var getOpenAICodexAuthFilePath = (config) => {
3652
+ const env = defaultedConfig(config);
3653
+ const fromEnv = process.env[env.authFilePathEnv];
3654
+ if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
3655
+ return resolve8(fromEnv);
3656
+ }
3657
+ return resolve8(homedir3(), ".poncho", "auth", "openai-codex.json");
3658
+ };
3659
+ var readOpenAICodexSession = async (config) => {
3660
+ const filePath = getOpenAICodexAuthFilePath(config);
3661
+ try {
3662
+ const content = await readFile7(filePath, "utf8");
3663
+ const parsed = JSON.parse(content);
3664
+ if (typeof parsed.refreshToken !== "string" || parsed.refreshToken.length === 0) {
3665
+ return void 0;
3666
+ }
3667
+ return {
3668
+ refreshToken: parsed.refreshToken,
3669
+ accessToken: typeof parsed.accessToken === "string" && parsed.accessToken.length > 0 ? parsed.accessToken : void 0,
3670
+ accessTokenExpiresAt: typeof parsed.accessTokenExpiresAt === "number" && parsed.accessTokenExpiresAt > 0 ? parsed.accessTokenExpiresAt : void 0,
3671
+ accountId: typeof parsed.accountId === "string" && parsed.accountId.length > 0 ? parsed.accountId : void 0
3672
+ };
3673
+ } catch {
3674
+ return void 0;
3675
+ }
3676
+ };
3677
+ var writeOpenAICodexSession = async (session, config) => {
3678
+ const filePath = getOpenAICodexAuthFilePath(config);
3679
+ await mkdir5(dirname4(filePath), { recursive: true });
3680
+ const payload = {
3681
+ ...session,
3682
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3683
+ };
3684
+ await writeFile6(filePath, `${JSON.stringify(payload, null, 2)}
3685
+ `, "utf8");
3686
+ await chmod(filePath, 384);
3687
+ };
3688
+ var deleteOpenAICodexSession = async (config) => {
3689
+ const filePath = getOpenAICodexAuthFilePath(config);
3690
+ await rm3(filePath, { force: true });
3691
+ };
3692
+ var parseAccountIdFromJwt = (token) => {
3693
+ if (!token) return void 0;
3694
+ const parts = token.split(".");
3695
+ if (parts.length !== 3) return void 0;
3696
+ try {
3697
+ const claims = JSON.parse(Buffer.from(parts[1] ?? "", "base64url").toString("utf8"));
3698
+ return claims.chatgpt_account_id ?? claims["https://api.openai.com/auth"]?.chatgpt_account_id ?? claims.organizations?.[0]?.id;
3699
+ } catch {
3700
+ return void 0;
3701
+ }
3702
+ };
3703
+ var exchangeRefreshToken = async (refreshToken) => {
3704
+ const response = await fetch(`${OPENAI_AUTH_ISSUER}/oauth/token`, {
3705
+ method: "POST",
3706
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
3707
+ body: new URLSearchParams({
3708
+ grant_type: "refresh_token",
3709
+ refresh_token: refreshToken,
3710
+ client_id: OPENAI_CODEX_CLIENT_ID
3711
+ }).toString()
3712
+ });
3713
+ if (!response.ok) {
3714
+ const body = await response.text();
3715
+ throw new Error(
3716
+ `OpenAI Codex token refresh failed (${response.status}). Re-run \`poncho auth login --provider openai-codex --device\`, export the new token, and update deployment secrets. Details: ${body.slice(0, 240)}`
3717
+ );
3718
+ }
3719
+ return await response.json();
3720
+ };
3721
+ var exchangeAuthorizationCode = async (code, codeVerifier) => {
3722
+ const response = await fetch(`${OPENAI_AUTH_ISSUER}/oauth/token`, {
3723
+ method: "POST",
3724
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
3725
+ body: new URLSearchParams({
3726
+ grant_type: "authorization_code",
3727
+ code,
3728
+ redirect_uri: `${OPENAI_AUTH_ISSUER}/deviceauth/callback`,
3729
+ client_id: OPENAI_CODEX_CLIENT_ID,
3730
+ code_verifier: codeVerifier
3731
+ }).toString()
3732
+ });
3733
+ if (!response.ok) {
3734
+ const body = await response.text();
3735
+ throw new Error(
3736
+ `OpenAI Codex device token exchange failed (${response.status}): ${body.slice(0, 240)}`
3737
+ );
3738
+ }
3739
+ return await response.json();
3740
+ };
3741
+ var readSessionFromEnv = (config) => {
3742
+ const env = defaultedConfig(config);
3743
+ const refreshToken = process.env[env.refreshTokenEnv];
3744
+ if (!refreshToken || refreshToken.trim().length === 0) return void 0;
3745
+ return {
3746
+ refreshToken: refreshToken.trim(),
3747
+ accessToken: process.env[env.accessTokenEnv]?.trim() || void 0,
3748
+ accessTokenExpiresAt: parseEpochMillis(process.env[env.accessTokenExpiresAtEnv]),
3749
+ accountId: process.env[env.accountIdEnv]?.trim() || void 0
3750
+ };
3751
+ };
3752
+ var shouldRefresh = (expiresAt) => !expiresAt || Date.now() + REFRESH_TOKEN_GRACE_MS >= expiresAt;
3753
+ var runtimeCachedSession;
3754
+ var readSession = async (config) => {
3755
+ const envSession = readSessionFromEnv(config);
3756
+ if (envSession) return { session: envSession, source: "env" };
3757
+ if (runtimeCachedSession) {
3758
+ return { session: runtimeCachedSession, source: "file" };
3759
+ }
3760
+ const fileSession = await readOpenAICodexSession(config);
3761
+ if (!fileSession) {
3762
+ throw new Error(
3763
+ "OpenAI Codex credentials not found. Run `poncho auth login --provider openai-codex --device` locally, or set OPENAI_CODEX_REFRESH_TOKEN in your environment."
3764
+ );
3765
+ }
3766
+ runtimeCachedSession = fileSession;
3767
+ return { session: fileSession, source: "file" };
3768
+ };
3769
+ var getOpenAICodexAccessToken = async (config) => {
3770
+ const { session, source } = await readSession(config);
3771
+ if (session.accessToken && !shouldRefresh(session.accessTokenExpiresAt)) {
3772
+ return { accessToken: session.accessToken, accountId: session.accountId };
3773
+ }
3774
+ const refreshed = await exchangeRefreshToken(session.refreshToken);
3775
+ const nextSession = {
3776
+ refreshToken: refreshed.refresh_token ?? session.refreshToken,
3777
+ accessToken: refreshed.access_token,
3778
+ accessTokenExpiresAt: Date.now() + (refreshed.expires_in ?? 3600) * 1e3,
3779
+ accountId: session.accountId ?? parseAccountIdFromJwt(refreshed.id_token) ?? parseAccountIdFromJwt(refreshed.access_token)
3780
+ };
3781
+ runtimeCachedSession = nextSession;
3782
+ if (source === "file") {
3783
+ await writeOpenAICodexSession(nextSession, config);
3784
+ }
3785
+ return {
3786
+ accessToken: nextSession.accessToken,
3787
+ accountId: nextSession.accountId
3788
+ };
3789
+ };
3790
+ var getOpenAICodexRequiredScopes = () => [...REQUIRED_SCOPES];
3791
+ var startOpenAICodexDeviceAuth = async () => {
3792
+ const response = await fetch(`${OPENAI_AUTH_ISSUER}/api/accounts/deviceauth/usercode`, {
3793
+ method: "POST",
3794
+ headers: {
3795
+ "Content-Type": "application/json",
3796
+ "User-Agent": "poncho/1.0"
3797
+ },
3798
+ body: JSON.stringify({
3799
+ client_id: OPENAI_CODEX_CLIENT_ID,
3800
+ scope: REQUIRED_SCOPES.join(" ")
3801
+ })
3802
+ });
3803
+ if (!response.ok) {
3804
+ const body = await response.text();
3805
+ throw new Error(
3806
+ `OpenAI Codex device authorization start failed (${response.status}): ${body.slice(0, 240)}`
3807
+ );
3808
+ }
3809
+ const data = await response.json();
3810
+ const intervalRaw = typeof data.interval === "number" ? data.interval : Number.parseInt(String(data.interval ?? "5"), 10);
3811
+ const intervalMs = Math.max(Number.isNaN(intervalRaw) ? 5 : intervalRaw, 1) * 1e3;
3812
+ return {
3813
+ deviceAuthId: data.device_auth_id,
3814
+ userCode: data.user_code,
3815
+ verificationUrl: `${OPENAI_AUTH_ISSUER}/codex/device`,
3816
+ intervalMs
3817
+ };
3818
+ };
3819
+ var completeOpenAICodexDeviceAuth = async (request) => {
3820
+ const startedAt = Date.now();
3821
+ while (Date.now() - startedAt < DEVICE_FLOW_TIMEOUT_MS) {
3822
+ const response = await fetch(`${OPENAI_AUTH_ISSUER}/api/accounts/deviceauth/token`, {
3823
+ method: "POST",
3824
+ headers: {
3825
+ "Content-Type": "application/json",
3826
+ "User-Agent": "poncho/1.0"
3827
+ },
3828
+ body: JSON.stringify({
3829
+ device_auth_id: request.deviceAuthId,
3830
+ user_code: request.userCode
3831
+ })
3832
+ });
3833
+ if (response.ok) {
3834
+ const data = await response.json();
3835
+ const tokens = await exchangeAuthorizationCode(
3836
+ data.authorization_code,
3837
+ data.code_verifier
3838
+ );
3839
+ if (!tokens.refresh_token || tokens.refresh_token.length === 0) {
3840
+ throw new Error("OpenAI Codex device auth succeeded but no refresh token was returned.");
3841
+ }
3842
+ return {
3843
+ refreshToken: tokens.refresh_token,
3844
+ accessToken: tokens.access_token,
3845
+ accessTokenExpiresAt: Date.now() + (tokens.expires_in ?? 3600) * 1e3,
3846
+ accountId: parseAccountIdFromJwt(tokens.id_token) ?? parseAccountIdFromJwt(tokens.access_token)
3847
+ };
3848
+ }
3849
+ if (response.status !== 403 && response.status !== 404) {
3850
+ const body = await response.text();
3851
+ throw new Error(
3852
+ `OpenAI Codex device authorization polling failed (${response.status}): ${body.slice(0, 240)}`
3853
+ );
3854
+ }
3855
+ await new Promise((resolveWait) => {
3856
+ setTimeout(resolveWait, request.intervalMs + DEVICE_POLLING_SAFETY_MARGIN_MS);
3857
+ });
3858
+ }
3859
+ throw new Error(
3860
+ "OpenAI Codex device authorization timed out. Re-run `poncho auth login --provider openai-codex --device`."
3861
+ );
3862
+ };
3863
+
3864
+ // src/model-factory.ts
3575
3865
  var MODEL_CONTEXT_WINDOWS = {
3576
3866
  "claude-opus-4-6": 2e5,
3577
3867
  "claude-sonnet-4-6": 2e5,
@@ -3596,6 +3886,42 @@ var MODEL_CONTEXT_WINDOWS = {
3596
3886
  "o3": 2e5
3597
3887
  };
3598
3888
  var DEFAULT_CONTEXT_WINDOW = 2e5;
3889
+ var OPENAI_CODEX_DEFAULT_INSTRUCTIONS = "You are Codex, based on GPT-5. You are running as a coding agent in Poncho.";
3890
+ var OPENAI_CODEX_RESPONSES_URL = process.env.OPENAI_CODEX_RESPONSES_URL ?? "https://chatgpt.com/backend-api/codex/responses";
3891
+ var extractSystemInstructionFromInput = (input) => {
3892
+ if (!Array.isArray(input)) return void 0;
3893
+ for (const message of input) {
3894
+ if (!message || typeof message !== "object") continue;
3895
+ const candidate = message;
3896
+ if (candidate.role !== "system") continue;
3897
+ if (typeof candidate.content === "string" && candidate.content.trim().length > 0) {
3898
+ return candidate.content;
3899
+ }
3900
+ if (Array.isArray(candidate.content)) {
3901
+ const textParts = candidate.content.map((part) => {
3902
+ if (!part || typeof part !== "object") return "";
3903
+ const p = part;
3904
+ return typeof p.text === "string" ? p.text : "";
3905
+ }).filter((text) => text.trim().length > 0);
3906
+ if (textParts.length > 0) {
3907
+ return textParts.join("\n");
3908
+ }
3909
+ }
3910
+ }
3911
+ return void 0;
3912
+ };
3913
+ var normalizeToolParameterSchemas = (tools) => {
3914
+ if (!Array.isArray(tools)) return;
3915
+ for (const tool of tools) {
3916
+ if (!tool || typeof tool !== "object") continue;
3917
+ const entry = tool;
3918
+ if (!entry.parameters || typeof entry.parameters !== "object") continue;
3919
+ const schema = entry.parameters;
3920
+ if (schema.type === "object" && (typeof schema.properties !== "object" || schema.properties === null)) {
3921
+ schema.properties = {};
3922
+ }
3923
+ }
3924
+ };
3599
3925
  var getModelContextWindow = (modelName) => {
3600
3926
  if (MODEL_CONTEXT_WINDOWS[modelName] !== void 0) {
3601
3927
  return MODEL_CONTEXT_WINDOWS[modelName];
@@ -3610,6 +3936,48 @@ var getModelContextWindow = (modelName) => {
3610
3936
  };
3611
3937
  var createModelProvider = (provider, config) => {
3612
3938
  const normalized = (provider ?? "anthropic").toLowerCase();
3939
+ if (normalized === "openai-codex") {
3940
+ const openai = createOpenAI({
3941
+ apiKey: "oauth-placeholder",
3942
+ fetch: async (input, init) => {
3943
+ const { accessToken, accountId } = await getOpenAICodexAccessToken(config?.openaiCodex);
3944
+ const headers = new Headers(init?.headers);
3945
+ headers.set("Authorization", `Bearer ${accessToken}`);
3946
+ headers.set("originator", "poncho");
3947
+ headers.set("User-Agent", "poncho/1.0");
3948
+ if (accountId) {
3949
+ headers.set("ChatGPT-Account-Id", accountId);
3950
+ }
3951
+ const originalUrl = input instanceof URL ? input.toString() : typeof input === "string" ? input : input.url;
3952
+ const parsed = new URL(originalUrl);
3953
+ const shouldRewrite = parsed.pathname.includes("/v1/responses") || parsed.pathname.includes("/chat/completions");
3954
+ const targetUrl = shouldRewrite ? OPENAI_CODEX_RESPONSES_URL : originalUrl;
3955
+ let body = init?.body;
3956
+ if (shouldRewrite && typeof body === "string" && headers.get("Content-Type")?.includes("application/json")) {
3957
+ try {
3958
+ const payload = JSON.parse(body);
3959
+ if (typeof payload.instructions !== "string" || payload.instructions.trim() === "") {
3960
+ payload.instructions = extractSystemInstructionFromInput(payload.input) ?? OPENAI_CODEX_DEFAULT_INSTRUCTIONS;
3961
+ }
3962
+ normalizeToolParameterSchemas(payload.tools);
3963
+ payload.store = false;
3964
+ body = JSON.stringify(payload);
3965
+ } catch {
3966
+ }
3967
+ }
3968
+ try {
3969
+ return await fetch(targetUrl, { ...init, headers, body });
3970
+ } catch (error) {
3971
+ const message = error instanceof Error ? error.message : String(error);
3972
+ if (shouldRewrite && targetUrl.includes("chatgpt.com") && message.includes("ENOTFOUND chatgpt.com")) {
3973
+ return fetch(originalUrl, { ...init, headers, body });
3974
+ }
3975
+ throw error;
3976
+ }
3977
+ }
3978
+ });
3979
+ return (modelName) => openai(modelName);
3980
+ }
3613
3981
  if (normalized === "openai") {
3614
3982
  const apiKeyEnv2 = config?.openai?.apiKeyEnv ?? "OPENAI_API_KEY";
3615
3983
  const openai = createOpenAI({
@@ -3626,8 +3994,8 @@ var createModelProvider = (provider, config) => {
3626
3994
  };
3627
3995
 
3628
3996
  // src/skill-context.ts
3629
- import { readFile as readFile7, readdir as readdir2, stat } from "fs/promises";
3630
- import { dirname as dirname4, resolve as resolve8, normalize } from "path";
3997
+ import { readFile as readFile8, readdir as readdir2, stat } from "fs/promises";
3998
+ import { dirname as dirname5, resolve as resolve9, normalize } from "path";
3631
3999
  import YAML3 from "yaml";
3632
4000
  var DEFAULT_SKILL_DIRS = ["skills"];
3633
4001
  var resolveSkillDirs = (workingDir, extraPaths) => {
@@ -3639,7 +4007,7 @@ var resolveSkillDirs = (workingDir, extraPaths) => {
3639
4007
  }
3640
4008
  }
3641
4009
  }
3642
- return dirs.map((d) => resolve8(workingDir, d));
4010
+ return dirs.map((d) => resolve9(workingDir, d));
3643
4011
  };
3644
4012
  var FRONTMATTER_PATTERN3 = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
3645
4013
  var asRecord2 = (value) => typeof value === "object" && value !== null ? value : {};
@@ -3708,7 +4076,7 @@ var collectSkillManifests = async (directory) => {
3708
4076
  const entries = await readdir2(directory, { withFileTypes: true });
3709
4077
  const files = [];
3710
4078
  for (const entry of entries) {
3711
- const fullPath = resolve8(directory, entry.name);
4079
+ const fullPath = resolve9(directory, entry.name);
3712
4080
  let isDir = entry.isDirectory();
3713
4081
  let isFile = entry.isFile();
3714
4082
  if (entry.isSymbolicLink()) {
@@ -3743,13 +4111,13 @@ var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
3743
4111
  const seen = /* @__PURE__ */ new Set();
3744
4112
  for (const manifest of allManifests) {
3745
4113
  try {
3746
- const content = await readFile7(manifest, "utf8");
4114
+ const content = await readFile8(manifest, "utf8");
3747
4115
  const parsed = parseSkillFrontmatter(content);
3748
4116
  if (parsed && !seen.has(parsed.name)) {
3749
4117
  seen.add(parsed.name);
3750
4118
  skills.push({
3751
4119
  ...parsed,
3752
- skillDir: dirname4(manifest),
4120
+ skillDir: dirname5(manifest),
3753
4121
  skillPath: manifest
3754
4122
  });
3755
4123
  }
@@ -3788,7 +4156,7 @@ ${xmlSkills}
3788
4156
  };
3789
4157
  var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
3790
4158
  var loadSkillInstructions = async (skill) => {
3791
- const content = await readFile7(skill.skillPath, "utf8");
4159
+ const content = await readFile8(skill.skillPath, "utf8");
3792
4160
  const match = content.match(FRONTMATTER_PATTERN3);
3793
4161
  return match ? match[2].trim() : content.trim();
3794
4162
  };
@@ -3797,11 +4165,11 @@ var readSkillResource = async (skill, relativePath) => {
3797
4165
  if (normalized.startsWith("..") || normalized.startsWith("/")) {
3798
4166
  throw new Error("Path must be relative and within the skill directory");
3799
4167
  }
3800
- const fullPath = resolve8(skill.skillDir, normalized);
4168
+ const fullPath = resolve9(skill.skillDir, normalized);
3801
4169
  if (!fullPath.startsWith(skill.skillDir)) {
3802
4170
  throw new Error("Path escapes the skill directory");
3803
4171
  }
3804
- return await readFile7(fullPath, "utf8");
4172
+ return await readFile8(fullPath, "utf8");
3805
4173
  };
3806
4174
  var MAX_INSTRUCTIONS_PER_SKILL = 1200;
3807
4175
  var loadSkillContext = async (workingDir) => {
@@ -3920,7 +4288,7 @@ function convertSchema(schema) {
3920
4288
  // src/skill-tools.ts
3921
4289
  import { defineTool as defineTool4 } from "@poncho-ai/sdk";
3922
4290
  import { access as access2, readdir as readdir3, stat as stat2 } from "fs/promises";
3923
- import { extname, normalize as normalize2, resolve as resolve9, sep as sep2 } from "path";
4291
+ import { extname, normalize as normalize2, resolve as resolve10, sep as sep2 } from "path";
3924
4292
  import { pathToFileURL } from "url";
3925
4293
  import { createJiti as createJiti2 } from "jiti";
3926
4294
  var createSkillTools = (skills, options) => {
@@ -4178,7 +4546,7 @@ var collectScriptFiles = async (directory) => {
4178
4546
  if (entry.name === "node_modules") {
4179
4547
  continue;
4180
4548
  }
4181
- const fullPath = resolve9(directory, entry.name);
4549
+ const fullPath = resolve10(directory, entry.name);
4182
4550
  let isDir = entry.isDirectory();
4183
4551
  let isFile = entry.isFile();
4184
4552
  if (entry.isSymbolicLink()) {
@@ -4217,8 +4585,8 @@ var normalizeScriptPolicyPath = (relativePath) => {
4217
4585
  };
4218
4586
  var resolveScriptPath = (baseDir, relativePath, containmentDir) => {
4219
4587
  const normalized = normalizeScriptPolicyPath(relativePath);
4220
- const fullPath = resolve9(baseDir, normalized);
4221
- const boundary = resolve9(containmentDir ?? baseDir);
4588
+ const fullPath = resolve10(baseDir, normalized);
4589
+ const boundary = resolve10(containmentDir ?? baseDir);
4222
4590
  if (!fullPath.startsWith(`${boundary}${sep2}`) && fullPath !== boundary) {
4223
4591
  throw new Error("Script path must stay inside the allowed directory");
4224
4592
  }
@@ -4324,8 +4692,8 @@ function applyRateLimitCooldown(retryAfterHeader) {
4324
4692
  async function runWithSearchThrottle(fn) {
4325
4693
  const previous = searchQueue;
4326
4694
  let release;
4327
- searchQueue = new Promise((resolve12) => {
4328
- release = resolve12;
4695
+ searchQueue = new Promise((resolve13) => {
4696
+ release = resolve13;
4329
4697
  });
4330
4698
  await previous.catch(() => {
4331
4699
  });
@@ -5122,6 +5490,8 @@ export default {
5122
5490
  providers: {
5123
5491
  anthropic: { apiKeyEnv: "ANTHROPIC_API_KEY" },
5124
5492
  openai: { apiKeyEnv: "OPENAI_API_KEY" },
5493
+ // openai-codex provider reads OAuth tokens from env vars by default:
5494
+ // openaiCodex: { refreshTokenEnv: "OPENAI_CODEX_REFRESH_TOKEN", accountIdEnv: "OPENAI_CODEX_ACCOUNT_ID" },
5125
5495
  },
5126
5496
  auth: {
5127
5497
  required: true,
@@ -5524,8 +5894,8 @@ var AgentHarness = class _AgentHarness {
5524
5894
  return false;
5525
5895
  }
5526
5896
  try {
5527
- const agentFilePath = resolve10(this.workingDir, "AGENT.md");
5528
- const rawContent = await readFile8(agentFilePath, "utf8");
5897
+ const agentFilePath = resolve11(this.workingDir, "AGENT.md");
5898
+ const rawContent = await readFile9(agentFilePath, "utf8");
5529
5899
  if (rawContent === this.agentFileFingerprint) {
5530
5900
  return false;
5531
5901
  }
@@ -5594,8 +5964,8 @@ var AgentHarness = class _AgentHarness {
5594
5964
  }
5595
5965
  }
5596
5966
  async initialize() {
5597
- const agentFilePath = resolve10(this.workingDir, "AGENT.md");
5598
- const agentRawContent = await readFile8(agentFilePath, "utf8");
5967
+ const agentFilePath = resolve11(this.workingDir, "AGENT.md");
5968
+ const agentRawContent = await readFile9(agentFilePath, "utf8");
5599
5969
  this.parsedAgent = parseAgentMarkdown(agentRawContent);
5600
5970
  this.agentFileFingerprint = agentRawContent;
5601
5971
  const identity = await ensureAgentIdentity(this.workingDir);
@@ -5702,14 +6072,14 @@ var AgentHarness = class _AgentHarness {
5702
6072
  const filePath = pathResolve(stateDir, `${sessionId}.json`);
5703
6073
  return {
5704
6074
  async save(json) {
5705
- const { mkdir: mkdir6, writeFile: writeFile7 } = await import("fs/promises");
5706
- await mkdir6(stateDir, { recursive: true });
5707
- await writeFile7(filePath, json, "utf8");
6075
+ const { mkdir: mkdir7, writeFile: writeFile8 } = await import("fs/promises");
6076
+ await mkdir7(stateDir, { recursive: true });
6077
+ await writeFile8(filePath, json, "utf8");
5708
6078
  },
5709
6079
  async load() {
5710
- const { readFile: readFile10 } = await import("fs/promises");
6080
+ const { readFile: readFile11 } = await import("fs/promises");
5711
6081
  try {
5712
- return await readFile10(filePath, "utf8");
6082
+ return await readFile11(filePath, "utf8");
5713
6083
  } catch {
5714
6084
  return void 0;
5715
6085
  }
@@ -5775,7 +6145,7 @@ var AgentHarness = class _AgentHarness {
5775
6145
  let browserMod;
5776
6146
  try {
5777
6147
  const { existsSync } = await import("fs");
5778
- const { join, dirname: dirname6 } = await import("path");
6148
+ const { join, dirname: dirname7 } = await import("path");
5779
6149
  const { pathToFileURL: pathToFileURL2 } = await import("url");
5780
6150
  let searchDir = this.workingDir;
5781
6151
  let entryPath;
@@ -5785,7 +6155,7 @@ var AgentHarness = class _AgentHarness {
5785
6155
  entryPath = candidate;
5786
6156
  break;
5787
6157
  }
5788
- const parent = dirname6(searchDir);
6158
+ const parent = dirname7(searchDir);
5789
6159
  if (parent === searchDir) break;
5790
6160
  searchDir = parent;
5791
6161
  }
@@ -5890,9 +6260,9 @@ var AgentHarness = class _AgentHarness {
5890
6260
  for await (const event of this.run(input)) {
5891
6261
  eventQueue.push(event);
5892
6262
  if (queueResolve) {
5893
- const resolve12 = queueResolve;
6263
+ const resolve13 = queueResolve;
5894
6264
  queueResolve = null;
5895
- resolve12();
6265
+ resolve13();
5896
6266
  }
5897
6267
  }
5898
6268
  } catch (error) {
@@ -5911,8 +6281,8 @@ var AgentHarness = class _AgentHarness {
5911
6281
  if (eventQueue.length > 0) {
5912
6282
  yield eventQueue.shift();
5913
6283
  } else if (!generatorDone) {
5914
- await new Promise((resolve12) => {
5915
- queueResolve = resolve12;
6284
+ await new Promise((resolve13) => {
6285
+ queueResolve = resolve13;
5916
6286
  });
5917
6287
  }
5918
6288
  }
@@ -6504,8 +6874,8 @@ ${textContent}` };
6504
6874
  let timer;
6505
6875
  nextPart = await Promise.race([
6506
6876
  fullStreamIterator.next(),
6507
- new Promise((resolve12) => {
6508
- timer = setTimeout(() => resolve12(null), effectiveTimeout);
6877
+ new Promise((resolve13) => {
6878
+ timer = setTimeout(() => resolve13(null), effectiveTimeout);
6509
6879
  })
6510
6880
  ]);
6511
6881
  clearTimeout(timer);
@@ -6822,7 +7192,7 @@ ${textContent}` };
6822
7192
  const raced = await Promise.race([
6823
7193
  this.dispatcher.executeBatch(approvedCalls, toolContext),
6824
7194
  new Promise(
6825
- (resolve12) => setTimeout(() => resolve12(TOOL_DEADLINE_SENTINEL), toolDeadlineRemainingMs)
7195
+ (resolve13) => setTimeout(() => resolve13(TOOL_DEADLINE_SENTINEL), toolDeadlineRemainingMs)
6826
7196
  )
6827
7197
  ]);
6828
7198
  if (raced === TOOL_DEADLINE_SENTINEL) {
@@ -7163,8 +7533,8 @@ var LatitudeCapture = class {
7163
7533
 
7164
7534
  // src/state.ts
7165
7535
  import { randomUUID as randomUUID4 } from "crypto";
7166
- import { mkdir as mkdir5, readFile as readFile9, readdir as readdir4, rename as rename3, rm as rm3, writeFile as writeFile6 } from "fs/promises";
7167
- import { dirname as dirname5, resolve as resolve11 } from "path";
7536
+ import { mkdir as mkdir6, readFile as readFile10, readdir as readdir4, rename as rename3, rm as rm4, writeFile as writeFile7 } from "fs/promises";
7537
+ import { dirname as dirname6, resolve as resolve12 } from "path";
7168
7538
  var DEFAULT_OWNER = "local-owner";
7169
7539
  var LOCAL_STATE_FILE = "state.json";
7170
7540
  var CONVERSATIONS_DIRECTORY = "conversations";
@@ -7180,9 +7550,9 @@ var toStoreIdentity = async ({
7180
7550
  return { name: ensured.name, id: agentId };
7181
7551
  };
7182
7552
  var writeJsonAtomic3 = async (filePath, payload) => {
7183
- await mkdir5(dirname5(filePath), { recursive: true });
7553
+ await mkdir6(dirname6(filePath), { recursive: true });
7184
7554
  const tmpPath = `${filePath}.tmp`;
7185
- await writeFile6(tmpPath, JSON.stringify(payload, null, 2), "utf8");
7555
+ await writeFile7(tmpPath, JSON.stringify(payload, null, 2), "utf8");
7186
7556
  await rename3(tmpPath, filePath);
7187
7557
  };
7188
7558
  var formatUtcTimestamp = (value) => new Date(value).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
@@ -7371,8 +7741,8 @@ var FileConversationStore = class {
7371
7741
  agentId: this.agentId
7372
7742
  });
7373
7743
  const agentDir = getAgentStoreDirectory(identity);
7374
- const conversationsDir = resolve11(agentDir, CONVERSATIONS_DIRECTORY);
7375
- const indexPath = resolve11(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
7744
+ const conversationsDir = resolve12(agentDir, CONVERSATIONS_DIRECTORY);
7745
+ const indexPath = resolve12(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
7376
7746
  this.paths = { conversationsDir, indexPath };
7377
7747
  return this.paths;
7378
7748
  }
@@ -7386,9 +7756,9 @@ var FileConversationStore = class {
7386
7756
  }
7387
7757
  async readConversationFile(fileName) {
7388
7758
  const { conversationsDir } = await this.resolvePaths();
7389
- const filePath = resolve11(conversationsDir, fileName);
7759
+ const filePath = resolve12(conversationsDir, fileName);
7390
7760
  try {
7391
- const raw = await readFile9(filePath, "utf8");
7761
+ const raw = await readFile10(filePath, "utf8");
7392
7762
  return JSON.parse(raw);
7393
7763
  } catch {
7394
7764
  return void 0;
@@ -7434,7 +7804,7 @@ var FileConversationStore = class {
7434
7804
  this.loaded = true;
7435
7805
  const { indexPath } = await this.resolvePaths();
7436
7806
  try {
7437
- const raw = await readFile9(indexPath, "utf8");
7807
+ const raw = await readFile10(indexPath, "utf8");
7438
7808
  const parsed = JSON.parse(raw);
7439
7809
  for (const conversation of parsed.conversations ?? []) {
7440
7810
  this.conversations.set(conversation.conversationId, conversation);
@@ -7457,7 +7827,7 @@ var FileConversationStore = class {
7457
7827
  const { conversationsDir } = await this.resolvePaths();
7458
7828
  const existing = this.conversations.get(conversation.conversationId);
7459
7829
  const fileName = existing?.fileName ?? this.resolveConversationFileName(conversation);
7460
- const filePath = resolve11(conversationsDir, fileName);
7830
+ const filePath = resolve12(conversationsDir, fileName);
7461
7831
  this.writing = this.writing.then(async () => {
7462
7832
  await writeJsonAtomic3(filePath, conversation);
7463
7833
  this.conversations.set(conversation.conversationId, {
@@ -7555,7 +7925,7 @@ var FileConversationStore = class {
7555
7925
  if (removed) {
7556
7926
  this.writing = this.writing.then(async () => {
7557
7927
  if (existing) {
7558
- await rm3(resolve11(conversationsDir, existing.fileName), { force: true });
7928
+ await rm4(resolve12(conversationsDir, existing.fileName), { force: true });
7559
7929
  }
7560
7930
  await this.writeIndex();
7561
7931
  });
@@ -7577,7 +7947,7 @@ var FileConversationStore = class {
7577
7947
  const summary = this.conversations.get(conversationId);
7578
7948
  if (!summary) return void 0;
7579
7949
  const { conversationsDir } = await this.resolvePaths();
7580
- const filePath = resolve11(conversationsDir, summary.fileName);
7950
+ const filePath = resolve12(conversationsDir, summary.fileName);
7581
7951
  let result;
7582
7952
  this.writing = this.writing.then(async () => {
7583
7953
  const conv = await this.readConversationFile(summary.fileName);
@@ -7616,7 +7986,7 @@ var FileStateStore = class {
7616
7986
  workingDir: this.workingDir,
7617
7987
  agentId: this.agentId
7618
7988
  });
7619
- this.filePath = resolve11(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
7989
+ this.filePath = resolve12(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
7620
7990
  }
7621
7991
  isExpired(state) {
7622
7992
  return typeof this.ttlMs === "number" && Date.now() - state.updatedAt > this.ttlMs;
@@ -7628,7 +7998,7 @@ var FileStateStore = class {
7628
7998
  }
7629
7999
  this.loaded = true;
7630
8000
  try {
7631
- const raw = await readFile9(this.filePath, "utf8");
8001
+ const raw = await readFile10(this.filePath, "utf8");
7632
8002
  const parsed = JSON.parse(raw);
7633
8003
  for (const state of parsed.states ?? []) {
7634
8004
  this.states.set(state.runId, state);
@@ -8337,6 +8707,7 @@ export {
8337
8707
  LatitudeCapture,
8338
8708
  LocalMcpBridge,
8339
8709
  LocalUploadStore,
8710
+ OPENAI_CODEX_CLIENT_ID,
8340
8711
  PONCHO_UPLOAD_SCHEME,
8341
8712
  S3UploadStore,
8342
8713
  STORAGE_SCHEMA_VERSION,
@@ -8346,6 +8717,7 @@ export {
8346
8717
  buildAgentDirectoryName,
8347
8718
  buildSkillContextWindow,
8348
8719
  compactMessages,
8720
+ completeOpenAICodexDeviceAuth,
8349
8721
  createConversationStore,
8350
8722
  createDefaultTools,
8351
8723
  createDeleteDirectoryTool,
@@ -8361,6 +8733,7 @@ export {
8361
8733
  createUploadStore,
8362
8734
  createWriteTool,
8363
8735
  defineTool7 as defineTool,
8736
+ deleteOpenAICodexSession,
8364
8737
  deriveUploadKey,
8365
8738
  ensureAgentIdentity,
8366
8739
  estimateTokens,
@@ -8369,6 +8742,9 @@ export {
8369
8742
  generateAgentId,
8370
8743
  getAgentStoreDirectory,
8371
8744
  getModelContextWindow,
8745
+ getOpenAICodexAccessToken,
8746
+ getOpenAICodexAuthFilePath,
8747
+ getOpenAICodexRequiredScopes,
8372
8748
  getPonchoStoreRoot,
8373
8749
  jsonSchemaToZod,
8374
8750
  loadPonchoConfig,
@@ -8380,6 +8756,7 @@ export {
8380
8756
  parseAgentFile,
8381
8757
  parseAgentMarkdown,
8382
8758
  ponchoDocsTool,
8759
+ readOpenAICodexSession,
8383
8760
  readSkillResource,
8384
8761
  renderAgentPrompt,
8385
8762
  resolveAgentIdentity,
@@ -8387,5 +8764,7 @@ export {
8387
8764
  resolveMemoryConfig,
8388
8765
  resolveSkillDirs,
8389
8766
  resolveStateConfig,
8390
- slugifyStorageComponent
8767
+ slugifyStorageComponent,
8768
+ startOpenAICodexDeviceAuth,
8769
+ writeOpenAICodexSession
8391
8770
  };