


























@@ -3,7 +3,11 @@ import os from "node:os";
33import path from "node:path";
44import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
55const authProfilesStoreMock = vi.hoisted(() => ({
6-profiles: {} as Record<string, { type: "api_key"; provider: string; key: string }>,
6+profiles: {} as Record<
7+string,
8+| { type: "api_key"; provider: string; key: string }
9+| { type: "oauth"; provider: string; access: string; refresh: string; expires: number }
10+>,
711}));
812913vi.mock("../../agents/auth-profiles.js", () => ({
@@ -25,7 +29,7 @@ vi.mock("../../agents/auth-profiles.js", () => ({
2529.map(([profileId, profile]) => ({ profileId, profile })),
2630replaceRuntimeAuthProfileStoreSnapshots: (
2731snapshots: Array<{
28-store?: { profiles?: Record<string, { type: "api_key"; provider: string; key: string }> };
32+store?: { profiles?: Record<string, AuthProfileForTest> };
2933}>,
3034) => {
3135authProfilesStoreMock.profiles = snapshots[0]?.store?.profiles ?? {};
@@ -55,7 +59,7 @@ vi.mock("../../agents/auth-profiles/store.js", () => {
5559loadAuthProfileStoreWithoutExternalProfiles: store,
5660replaceRuntimeAuthProfileStoreSnapshots: (
5761snapshots: Array<{
58-store?: { profiles?: Record<string, { type: "api_key"; provider: string; key: string }> };
62+store?: { profiles?: Record<string, AuthProfileForTest> };
5963}>,
6064) => {
6165authProfilesStoreMock.profiles = snapshots[0]?.store?.profiles ?? {};
@@ -132,11 +136,13 @@ const queueMocks = vi.hoisted(() => ({
132136133137// Mock dependencies for directive handling persistence.
134138vi.mock("../../agents/agent-scope.js", () => ({
139+listAgentEntries: () => [],
135140resolveAgentConfig: vi.fn(() => ({})),
136141resolveAgentDir: vi.fn(() => "/tmp/agent"),
137142resolveAgentEffectiveModelPrimary: vi.fn(() => undefined),
138143resolveAgentModelFallbacksOverride: vi.fn(() => undefined),
139144resolveAgentWorkspaceDir: vi.fn(() => "/tmp/workspace"),
145+resolveSessionAgentIds: () => ({ sessionAgentId: "main" }),
140146resolveSessionAgentId: vi.fn(() => "main"),
141147}));
142148@@ -173,6 +179,14 @@ const TEST_AGENT_DIR = "/tmp/agent";
173179const OPENAI_DATE_PROFILE_ID = "20251001";
174180175181type ApiKeyProfile = { type: "api_key"; provider: string; key: string };
182+type OAuthProfileForTest = {
183+type: "oauth";
184+provider: string;
185+access: string;
186+refresh: string;
187+expires: number;
188+};
189+type AuthProfileForTest = ApiKeyProfile | OAuthProfileForTest;
176190177191function baseAliasIndex(): ModelAliasIndex {
178192return { byAlias: new Map(), byKey: new Map() };
@@ -224,7 +238,7 @@ afterEach(() => {
224238clearRuntimeAuthProfileStoreSnapshots();
225239});
226240227-function setAuthProfiles(profiles: Record<string, ApiKeyProfile>) {
241+function setAuthProfiles(profiles: Record<string, AuthProfileForTest>) {
228242replaceRuntimeAuthProfileStoreSnapshots([
229243{
230244agentDir: TEST_AGENT_DIR,
@@ -503,6 +517,124 @@ describe("/model chat UX", () => {
503517expect(reply?.text).not.toContain("missing (missing)");
504518});
505519520+it("reports Codex runtime auth for OpenAI status rows", async () => {
521+setAuthProfiles({
522+"openai-codex:patrick@example.test": {
523+type: "oauth",
524+provider: "openai-codex",
525+access: "access-token",
526+refresh: "refresh-token",
527+expires: Date.now() + 60_000,
528+},
529+});
530+531+const reply = await resolveModelInfoReply({
532+directives: parseInlineDirectives("/model status"),
533+provider: "openai",
534+model: "gpt-5.5",
535+defaultProvider: "openai",
536+defaultModel: "gpt-5.5",
537+cfg: {
538+commands: { text: true },
539+agents: {
540+defaults: {
541+agentRuntime: { id: "codex" },
542+model: { primary: "openai/gpt-5.5" },
543+models: {
544+"codex/gpt-5.5": {},
545+"openai/gpt-5.5": {},
546+},
547+},
548+},
549+} as OpenClawConfig,
550+allowedModelCatalog: [{ provider: "openai", id: "gpt-5.5", name: "GPT-5.5" }],
551+});
552+553+expect(reply?.text).toContain("[openai] endpoint: default auth:");
554+expect(reply?.text).not.toContain("[openai] endpoint: default auth: missing");
555+expect(reply?.text).toContain("via codex runtime / openai-codex");
556+expect(reply?.text).toContain("openai-codex:patrick@example.test=OAuth");
557+});
558+559+it("keeps direct provider auth labels when OpenAI API key auth exists", async () => {
560+setAuthProfiles({
561+"openai:api-key": {
562+type: "api_key",
563+provider: "openai",
564+key: "sk-openai-direct",
565+},
566+"openai-codex:patrick@example.test": {
567+type: "oauth",
568+provider: "openai-codex",
569+access: "access-token",
570+refresh: "refresh-token",
571+expires: Date.now() + 60_000,
572+},
573+});
574+575+const reply = await resolveModelInfoReply({
576+directives: parseInlineDirectives("/model status"),
577+provider: "openai",
578+model: "gpt-5.5",
579+defaultProvider: "openai",
580+defaultModel: "gpt-5.5",
581+cfg: {
582+commands: { text: true },
583+agents: {
584+defaults: {
585+agentRuntime: { id: "codex" },
586+model: { primary: "openai/gpt-5.5" },
587+models: {
588+"openai/gpt-5.5": {},
589+},
590+},
591+},
592+} as OpenClawConfig,
593+allowedModelCatalog: [{ provider: "openai", id: "gpt-5.5", name: "GPT-5.5" }],
594+});
595+596+expect(reply?.text).toContain("[openai] endpoint: default auth:");
597+expect(reply?.text).toContain("openai:api-key=");
598+expect(reply?.text).not.toContain("via codex runtime");
599+});
600+601+it("does not borrow Codex auth when OpenAI is pinned to PI runtime", async () => {
602+setAuthProfiles({
603+"openai-codex:patrick@example.test": {
604+type: "oauth",
605+provider: "openai-codex",
606+access: "access-token",
607+refresh: "refresh-token",
608+expires: Date.now() + 60_000,
609+},
610+});
611+612+const reply = await resolveModelInfoReply({
613+directives: parseInlineDirectives("/model status"),
614+provider: "openai",
615+model: "gpt-5.5",
616+defaultProvider: "openai",
617+defaultModel: "gpt-5.5",
618+cfg: {
619+commands: { text: true },
620+agents: {
621+defaults: {
622+agentRuntime: { id: "pi" },
623+model: { primary: "openai/gpt-5.5" },
624+models: {
625+"openai/gpt-5.5": {},
626+},
627+},
628+},
629+} as OpenClawConfig,
630+allowedModelCatalog: [{ provider: "openai", id: "gpt-5.5", name: "GPT-5.5" }],
631+});
632+633+expect(reply?.text).toContain("[openai] endpoint: default auth: missing");
634+expect(reply?.text).not.toContain("via codex runtime");
635+expect(reply?.text).not.toContain("openai-codex:patrick@example.test=OAuth");
636+});
637+506638it("uses workspace-scoped auth evidence in /model status labels", async () => {
507639const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-model-status-auth-label-"));
508640const workspaceDir = path.join(tempRoot, "workspace");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。