fix: set codex oauth model default
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Onboarding: resolve CLI entrypoint when running via `npx` so gateway daemon install works without a build step.
|
- Onboarding: resolve CLI entrypoint when running via `npx` so gateway daemon install works without a build step.
|
||||||
|
- Onboarding: when OpenAI Codex OAuth is used, default to `openai-codex/gpt-5.2` and warn if the selected model lacks auth.
|
||||||
- CLI: auto-migrate legacy config entries on command start (same behavior as gateway startup).
|
- CLI: auto-migrate legacy config entries on command start (same behavior as gateway startup).
|
||||||
- Auth: prioritize OAuth profiles but fall back to API keys when refresh fails; stored profiles now load without explicit auth order.
|
- Auth: prioritize OAuth profiles but fall back to API keys when refresh fails; stored profiles now load without explicit auth order.
|
||||||
- Docs: add group chat participation guidance to the AGENTS template.
|
- Docs: add group chat participation guidance to the AGENTS template.
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
- Model: `/model list` is an alias for `/model`.
|
- Model: `/model list` is an alias for `/model`.
|
||||||
- Model: `/model` output now includes auth source location (env/auth.json/models.json).
|
- Model: `/model` output now includes auth source location (env/auth.json/models.json).
|
||||||
- Model: avoid duplicate `missing (missing)` auth labels in `/model` list output.
|
- Model: avoid duplicate `missing (missing)` auth labels in `/model` list output.
|
||||||
|
- Auth: when `openai` has no API key but Codex OAuth exists, suggest `openai-codex/gpt-5.2` vs `OPENAI_API_KEY`.
|
||||||
- Docs: clarify auth storage, migration, and OpenAI Codex OAuth onboarding.
|
- Docs: clarify auth storage, migration, and OpenAI Codex OAuth onboarding.
|
||||||
- Sandbox: copy inbound media into sandbox workspaces so agent tools can read attachments.
|
- Sandbox: copy inbound media into sandbox workspaces so agent tools can read attachments.
|
||||||
- Control UI: show a reading indicator bubble while the assistant is responding.
|
- Control UI: show a reading indicator bubble while the assistant is responding.
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ The macOS app should:
|
|||||||
- Auto-capture the callback on `http://127.0.0.1:1455/auth/callback` when possible.
|
- Auto-capture the callback on `http://127.0.0.1:1455/auth/callback` when possible.
|
||||||
- If the callback fails, prompt the user to paste the redirect URL or code.
|
- If the callback fails, prompt the user to paste the redirect URL or code.
|
||||||
- Store credentials in `~/.clawdbot/credentials/oauth.json` (same OAuth store as Anthropic).
|
- Store credentials in `~/.clawdbot/credentials/oauth.json` (same OAuth store as Anthropic).
|
||||||
|
- Set `agent.model` to `openai-codex/gpt-5.2` when the model is unset or `openai/*`.
|
||||||
|
|
||||||
### Alternative: API key (instructions only)
|
### Alternative: API key (instructions only)
|
||||||
|
|
||||||
|
|||||||
@@ -49,9 +49,11 @@ It does **not** install or change anything on the remote host.
|
|||||||
2) **Model/Auth**
|
2) **Model/Auth**
|
||||||
- **Anthropic OAuth (recommended)**: browser flow; paste the `code#state`.
|
- **Anthropic OAuth (recommended)**: browser flow; paste the `code#state`.
|
||||||
- **OpenAI Codex OAuth**: browser flow; paste the `code#state`.
|
- **OpenAI Codex OAuth**: browser flow; paste the `code#state`.
|
||||||
|
- Sets `agent.model` to `openai-codex/gpt-5.2` when model is unset or `openai/*`.
|
||||||
- **API key**: stores the key for you.
|
- **API key**: stores the key for you.
|
||||||
- **Minimax M2.1 (LM Studio)**: config is auto‑written for the LM Studio endpoint.
|
- **Minimax M2.1 (LM Studio)**: config is auto‑written for the LM Studio endpoint.
|
||||||
- **Skip**: no auth configured yet.
|
- **Skip**: no auth configured yet.
|
||||||
|
- Wizard runs a model check and warns if the configured model is unknown or missing auth.
|
||||||
- OAuth credentials live in `~/.clawdbot/credentials/oauth.json`; auth profiles live in `~/.clawdbot/agent/auth-profiles.json` (API keys + OAuth).
|
- OAuth credentials live in `~/.clawdbot/credentials/oauth.json`; auth profiles live in `~/.clawdbot/agent/auth-profiles.json` (API keys + OAuth).
|
||||||
|
|
||||||
3) **Workspace**
|
3) **Workspace**
|
||||||
|
|||||||
@@ -87,4 +87,76 @@ describe("getApiKeyForModel", () => {
|
|||||||
await fs.rm(tempDir, { recursive: true, force: true });
|
await fs.rm(tempDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("suggests openai-codex when only Codex OAuth is configured", async () => {
|
||||||
|
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||||
|
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
|
||||||
|
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||||
|
const previousOpenAiKey = process.env.OPENAI_API_KEY;
|
||||||
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-auth-"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
delete process.env.OPENAI_API_KEY;
|
||||||
|
process.env.CLAWDBOT_STATE_DIR = tempDir;
|
||||||
|
process.env.CLAWDBOT_AGENT_DIR = path.join(tempDir, "agent");
|
||||||
|
process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
|
||||||
|
|
||||||
|
const authProfilesPath = path.join(
|
||||||
|
tempDir,
|
||||||
|
"agent",
|
||||||
|
"auth-profiles.json",
|
||||||
|
);
|
||||||
|
await fs.mkdir(path.dirname(authProfilesPath), {
|
||||||
|
recursive: true,
|
||||||
|
mode: 0o700,
|
||||||
|
});
|
||||||
|
await fs.writeFile(
|
||||||
|
authProfilesPath,
|
||||||
|
`${JSON.stringify(
|
||||||
|
{
|
||||||
|
version: 1,
|
||||||
|
profiles: {
|
||||||
|
"openai-codex:default": {
|
||||||
|
type: "oauth",
|
||||||
|
provider: "openai-codex",
|
||||||
|
...oauthFixture,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)}\n`,
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.resetModules();
|
||||||
|
const { resolveApiKeyForProvider } = await import("./model-auth.js");
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
resolveApiKeyForProvider({ provider: "openai" }),
|
||||||
|
).rejects.toThrow(/openai-codex\/gpt-5\\.2/);
|
||||||
|
} finally {
|
||||||
|
if (previousOpenAiKey === undefined) {
|
||||||
|
delete process.env.OPENAI_API_KEY;
|
||||||
|
} else {
|
||||||
|
process.env.OPENAI_API_KEY = previousOpenAiKey;
|
||||||
|
}
|
||||||
|
if (previousStateDir === undefined) {
|
||||||
|
delete process.env.CLAWDBOT_STATE_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.CLAWDBOT_STATE_DIR = previousStateDir;
|
||||||
|
}
|
||||||
|
if (previousAgentDir === undefined) {
|
||||||
|
delete process.env.CLAWDBOT_AGENT_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.CLAWDBOT_AGENT_DIR = previousAgentDir;
|
||||||
|
}
|
||||||
|
if (previousPiAgentDir === undefined) {
|
||||||
|
delete process.env.PI_CODING_AGENT_DIR;
|
||||||
|
} else {
|
||||||
|
process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
|
||||||
|
}
|
||||||
|
await fs.rm(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { getShellEnvAppliedKeys } from "../infra/shell-env.js";
|
|||||||
import {
|
import {
|
||||||
type AuthProfileStore,
|
type AuthProfileStore,
|
||||||
ensureAuthProfileStore,
|
ensureAuthProfileStore,
|
||||||
|
listProfilesForProvider,
|
||||||
resolveApiKeyForProfile,
|
resolveApiKeyForProfile,
|
||||||
resolveAuthProfileOrder,
|
resolveAuthProfileOrder,
|
||||||
} from "./auth-profiles.js";
|
} from "./auth-profiles.js";
|
||||||
@@ -83,6 +84,15 @@ export async function resolveApiKeyForProvider(params: {
|
|||||||
return { apiKey: customKey, source: "models.json" };
|
return { apiKey: customKey, source: "models.json" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (provider === "openai") {
|
||||||
|
const hasCodex = listProfilesForProvider(store, "openai-codex").length > 0;
|
||||||
|
if (hasCodex) {
|
||||||
|
throw new Error(
|
||||||
|
'No API key found for provider "openai". You are authenticated with OpenAI Codex OAuth. Use openai-codex/gpt-5.2 (ChatGPT OAuth) or set OPENAI_API_KEY for openai/gpt-5.2.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(`No API key found for provider "${provider}".`);
|
throw new Error(`No API key found for provider "${provider}".`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import {
|
|||||||
type OAuthCredentials,
|
type OAuthCredentials,
|
||||||
type OAuthProvider,
|
type OAuthProvider,
|
||||||
} from "@mariozechner/pi-ai";
|
} from "@mariozechner/pi-ai";
|
||||||
|
import { ensureAuthProfileStore, listProfilesForProvider } from "../agents/auth-profiles.js";
|
||||||
|
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
|
||||||
|
import { getCustomProviderApiKey, resolveEnvApiKey } from "../agents/model-auth.js";
|
||||||
|
import { loadModelCatalog } from "../agents/model-catalog.js";
|
||||||
|
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
|
||||||
import {
|
import {
|
||||||
isRemoteEnvironment,
|
isRemoteEnvironment,
|
||||||
loginAntigravityVpsAware,
|
loginAntigravityVpsAware,
|
||||||
@@ -58,6 +63,82 @@ import { defaultRuntime } from "../runtime.js";
|
|||||||
import { resolveUserPath, sleep } from "../utils.js";
|
import { resolveUserPath, sleep } from "../utils.js";
|
||||||
import type { WizardPrompter } from "./prompts.js";
|
import type { WizardPrompter } from "./prompts.js";
|
||||||
|
|
||||||
|
const OPENAI_CODEX_DEFAULT_MODEL = "openai-codex/gpt-5.2";
|
||||||
|
|
||||||
|
function shouldSetOpenAICodexModel(model?: string): boolean {
|
||||||
|
const trimmed = model?.trim();
|
||||||
|
if (!trimmed) return true;
|
||||||
|
const normalized = trimmed.toLowerCase();
|
||||||
|
if (normalized.startsWith("openai-codex/")) return false;
|
||||||
|
if (normalized.startsWith("openai/")) return true;
|
||||||
|
return normalized === "gpt" || normalized === "gpt-mini";
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyOpenAICodexModelDefault(
|
||||||
|
cfg: ClawdbotConfig,
|
||||||
|
): { next: ClawdbotConfig; changed: boolean } {
|
||||||
|
if (!shouldSetOpenAICodexModel(cfg.agent?.model)) {
|
||||||
|
return { next: cfg, changed: false };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
next: {
|
||||||
|
...cfg,
|
||||||
|
agent: {
|
||||||
|
...cfg.agent,
|
||||||
|
model: OPENAI_CODEX_DEFAULT_MODEL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function warnIfModelConfigLooksOff(
|
||||||
|
config: ClawdbotConfig,
|
||||||
|
prompter: WizardPrompter,
|
||||||
|
) {
|
||||||
|
const ref = resolveConfiguredModelRef({
|
||||||
|
cfg: config,
|
||||||
|
defaultProvider: DEFAULT_PROVIDER,
|
||||||
|
defaultModel: DEFAULT_MODEL,
|
||||||
|
});
|
||||||
|
const warnings: string[] = [];
|
||||||
|
const catalog = await loadModelCatalog({ config, useCache: false });
|
||||||
|
if (catalog.length > 0) {
|
||||||
|
const known = catalog.some(
|
||||||
|
(entry) => entry.provider === ref.provider && entry.id === ref.model,
|
||||||
|
);
|
||||||
|
if (!known) {
|
||||||
|
warnings.push(
|
||||||
|
`Model not found: ${ref.provider}/${ref.model}. Update agent.model or run /models list.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = ensureAuthProfileStore();
|
||||||
|
const hasProfile = listProfilesForProvider(store, ref.provider).length > 0;
|
||||||
|
const envKey = resolveEnvApiKey(ref.provider);
|
||||||
|
const customKey = getCustomProviderApiKey(config, ref.provider);
|
||||||
|
if (!hasProfile && !envKey && !customKey) {
|
||||||
|
warnings.push(
|
||||||
|
`No auth configured for provider "${ref.provider}". The agent may fail until credentials are added.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref.provider === "openai") {
|
||||||
|
const hasCodex =
|
||||||
|
listProfilesForProvider(store, "openai-codex").length > 0;
|
||||||
|
if (hasCodex) {
|
||||||
|
warnings.push(
|
||||||
|
`Detected OpenAI Codex OAuth. Consider setting agent.model to ${OPENAI_CODEX_DEFAULT_MODEL}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warnings.length > 0) {
|
||||||
|
await prompter.note(warnings.join("\n"), "Model check");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function runOnboardingWizard(
|
export async function runOnboardingWizard(
|
||||||
opts: OnboardOptions,
|
opts: OnboardOptions,
|
||||||
runtime: RuntimeEnv = defaultRuntime,
|
runtime: RuntimeEnv = defaultRuntime,
|
||||||
@@ -287,6 +368,14 @@ export async function runOnboardingWizard(
|
|||||||
provider: "openai-codex",
|
provider: "openai-codex",
|
||||||
mode: "oauth",
|
mode: "oauth",
|
||||||
});
|
});
|
||||||
|
const applied = applyOpenAICodexModelDefault(nextConfig);
|
||||||
|
nextConfig = applied.next;
|
||||||
|
if (applied.changed) {
|
||||||
|
await prompter.note(
|
||||||
|
`Default model set to ${OPENAI_CODEX_DEFAULT_MODEL}`,
|
||||||
|
"Model configured",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
spin.stop("OpenAI OAuth failed");
|
spin.stop("OpenAI OAuth failed");
|
||||||
@@ -380,6 +469,8 @@ export async function runOnboardingWizard(
|
|||||||
nextConfig = applyMinimaxConfig(nextConfig);
|
nextConfig = applyMinimaxConfig(nextConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await warnIfModelConfigLooksOff(nextConfig, prompter);
|
||||||
|
|
||||||
const portRaw = await prompter.text({
|
const portRaw = await prompter.text({
|
||||||
message: "Gateway port",
|
message: "Gateway port",
|
||||||
initialValue: String(localPort),
|
initialValue: String(localPort),
|
||||||
|
|||||||
Reference in New Issue
Block a user