@bitkyc08/opencodex 2.7.40 โ 2.7.41
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/README.md +63 -12
- package/assets/claude-code-models.gif +0 -0
- package/gui/dist/assets/index-B2J4t3te.css +1 -0
- package/gui/dist/assets/index-BmvM6wRb.js +65 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -2
- package/src/adapters/google.ts +48 -9
- package/src/adapters/kiro-events.ts +15 -3
- package/src/adapters/kiro.ts +292 -28
- package/src/adapters/openai-chat.ts +63 -15
- package/src/adapters/openai-responses.ts +18 -0
- package/src/bridge.ts +76 -21
- package/src/chat/outbound.ts +66 -34
- package/src/claude/auth-detect.ts +229 -0
- package/src/claude/auth-mode-migration.ts +32 -0
- package/src/claude/auth-mode.ts +62 -0
- package/src/claude/desktop-3p-guard.ts +35 -0
- package/src/claude/desktop-3p.ts +121 -21
- package/src/claude/desktop-health.ts +26 -0
- package/src/claude/desktop-profile.ts +263 -0
- package/src/claude/inbound-debug.ts +4 -0
- package/src/claude/model-info.ts +9 -3
- package/src/cli/account-extended.ts +34 -0
- package/src/cli/account.ts +3 -1
- package/src/cli/claude-desktop.ts +152 -0
- package/src/cli/claude.ts +37 -3
- package/src/cli/doctor.ts +187 -6
- package/src/cli/help.ts +13 -1
- package/src/cli/index.ts +151 -57
- package/src/cli/status-oauth.ts +68 -0
- package/src/cli/status.ts +4 -0
- package/src/codex/account-lifecycle.ts +31 -0
- package/src/codex/auth-api.ts +133 -26
- package/src/codex/auth-collision.ts +55 -11
- package/src/codex/auth-context.ts +50 -6
- package/src/codex/catalog/provider-fetch.ts +6 -2
- package/src/codex/home.ts +61 -1
- package/src/codex/inject.ts +28 -66
- package/src/codex/injected-marker.ts +72 -0
- package/src/codex/journal.ts +39 -3
- package/src/codex/main-account-cache.ts +25 -0
- package/src/codex/model-cache.ts +20 -1
- package/src/codex/paths.ts +5 -0
- package/src/codex/routing.ts +138 -11
- package/src/codex/subagent-model-fallback.ts +455 -0
- package/src/codex/sync.ts +17 -0
- package/src/combos/failover.ts +10 -2
- package/src/combos/index.ts +1 -0
- package/src/combos/types.ts +9 -0
- package/src/config.ts +117 -0
- package/src/grok/inject.ts +339 -0
- package/src/grok/status.ts +88 -0
- package/src/grok/sync.ts +66 -0
- package/src/lib/destination-policy.ts +13 -0
- package/src/lib/errors.ts +59 -0
- package/src/lib/privacy.ts +9 -0
- package/src/lib/process-control.ts +57 -4
- package/src/oauth/health.ts +375 -0
- package/src/oauth/index.ts +69 -22
- package/src/oauth/kiro.ts +5 -3
- package/src/oauth/log.ts +48 -0
- package/src/oauth/store.ts +55 -6
- package/src/providers/alibaba-region-backup.ts +75 -0
- package/src/providers/alibaba-region-migration.ts +143 -0
- package/src/providers/alibaba-region-startup.ts +36 -0
- package/src/providers/api-keys.ts +5 -5
- package/src/providers/derive.ts +22 -1
- package/src/providers/free-directory.ts +181 -0
- package/src/providers/key-failover.ts +2 -2
- package/src/providers/kiro-models.ts +3 -2
- package/src/providers/openai-tiers.ts +1 -1
- package/src/providers/provider-id-rewrite.ts +150 -0
- package/src/providers/registry.ts +49 -2
- package/src/responses/parser.ts +49 -20
- package/src/responses/state.ts +156 -2
- package/src/router.ts +73 -5
- package/src/server/chat-completions.ts +78 -22
- package/src/server/claude-messages.ts +8 -0
- package/src/server/images.ts +2 -1
- package/src/server/index.ts +50 -30
- package/src/server/live.ts +2 -1
- package/src/server/management/agent-settings-routes.ts +337 -18
- package/src/server/management/api-access.ts +141 -0
- package/src/server/management/combo-routes.ts +3 -3
- package/src/server/management/config-routes.ts +4 -4
- package/src/server/management/logs-usage-routes.ts +64 -4
- package/src/server/management/model-routes.ts +114 -8
- package/src/server/management/oauth-account-routes.ts +31 -5
- package/src/server/management/provider-routes.ts +42 -11
- package/src/server/management/shared.ts +81 -2
- package/src/server/management/system-routes.ts +6 -1
- package/src/server/management-api.ts +24 -7
- package/src/server/port-reclaim.ts +261 -0
- package/src/server/request-log.ts +11 -5
- package/src/server/responses/collaboration.ts +11 -6
- package/src/server/responses/compact.ts +2 -1
- package/src/server/responses/core.ts +358 -155
- package/src/server/responses/passthrough-error.ts +53 -0
- package/src/server/search.ts +2 -1
- package/src/server/system-env.ts +23 -6
- package/src/server/windows-tcp-drop.ts +174 -0
- package/src/service.ts +43 -3
- package/src/types.ts +74 -6
- package/src/update/index.ts +30 -19
- package/src/update/job.ts +30 -15
- package/src/usage/log.ts +158 -2
- package/src/usage/summary.ts +8 -4
- package/src/web-search/loop.ts +4 -1
- package/gui/dist/assets/index-CMip1DzF.css +0 -1
- package/gui/dist/assets/index-cydcmbzC.js +0 -52
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type {
|
|
3
|
+
OcxClaudeDesktopAssignment,
|
|
4
|
+
OcxClaudeDesktopFamily,
|
|
5
|
+
OcxClaudeDesktopProfile,
|
|
6
|
+
} from "../types";
|
|
7
|
+
|
|
8
|
+
export const DESKTOP_FAMILIES = ["opus", "fable", "sonnet", "haiku"] as const;
|
|
9
|
+
export type DesktopFamily = OcxClaudeDesktopFamily;
|
|
10
|
+
export type DesktopProfile = OcxClaudeDesktopProfile;
|
|
11
|
+
|
|
12
|
+
export interface DesktopProfileModel {
|
|
13
|
+
route: string;
|
|
14
|
+
label: string;
|
|
15
|
+
contextWindow?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface RenderedDesktopModel extends DesktopProfileModel {
|
|
19
|
+
name: string;
|
|
20
|
+
family: DesktopFamily;
|
|
21
|
+
isFamilyDefault: boolean;
|
|
22
|
+
supports1m: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const DATE_ALIAS = /^claude-opus-4-8-(2026\d{4})$/;
|
|
26
|
+
const DAY_COUNT_2026 = 365;
|
|
27
|
+
|
|
28
|
+
export class DesktopProfileError extends Error {
|
|
29
|
+
constructor(message: string, readonly path = "profile") {
|
|
30
|
+
super(`${path}: ${message}`);
|
|
31
|
+
this.name = "DesktopProfileError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function emptyDesktopProfile(): DesktopProfile {
|
|
36
|
+
return {
|
|
37
|
+
version: 1,
|
|
38
|
+
assignments: {},
|
|
39
|
+
defaults: { opus: null, fable: null, sonnet: null, haiku: null },
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
44
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function assertExactKeys(value: Record<string, unknown>, keys: readonly string[], path: string): void {
|
|
48
|
+
const allowed = new Set(keys);
|
|
49
|
+
for (const key of Object.keys(value)) {
|
|
50
|
+
if (!allowed.has(key)) throw new DesktopProfileError(`unknown field "${key}"`, path);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Applied-state markers survive every profile rebuild.
|
|
56
|
+
*
|
|
57
|
+
* `parseDesktopProfile`, `reconcileDesktopProfile` and `moveDesktopRoute` each construct a
|
|
58
|
+
* fresh `{ version, assignments, defaults }`, and the management routes persist whatever they
|
|
59
|
+
* return. Without this carry-through, saving an assignment โ or merely dragging a model to
|
|
60
|
+
* another family โ would erase the fingerprint the apply route wrote, and the GUI would report
|
|
61
|
+
* "not applied" for a config that is applied on disk.
|
|
62
|
+
*/
|
|
63
|
+
function appliedMarkers(source: { appliedFingerprint?: unknown; appliedAt?: unknown }): {
|
|
64
|
+
appliedFingerprint?: string;
|
|
65
|
+
appliedAt?: string;
|
|
66
|
+
} {
|
|
67
|
+
return {
|
|
68
|
+
...(typeof source.appliedFingerprint === "string" ? { appliedFingerprint: source.appliedFingerprint } : {}),
|
|
69
|
+
...(typeof source.appliedAt === "string" ? { appliedAt: source.appliedAt } : {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isFamily(value: unknown): value is DesktopFamily {
|
|
74
|
+
return typeof value === "string" && (DESKTOP_FAMILIES as readonly string[]).includes(value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function routeModelId(route: string): string {
|
|
78
|
+
const slash = route.indexOf("/");
|
|
79
|
+
return slash >= 0 ? route.slice(slash + 1) : route;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isRealAnthropicRoute(route: string): boolean {
|
|
83
|
+
return route.startsWith("anthropic/claude-");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function validDateAlias(alias: string): boolean {
|
|
87
|
+
const match = DATE_ALIAS.exec(alias);
|
|
88
|
+
if (!match) return false;
|
|
89
|
+
const year = Number(match[1]!.slice(0, 4));
|
|
90
|
+
const month = Number(match[1]!.slice(4, 6));
|
|
91
|
+
const day = Number(match[1]!.slice(6, 8));
|
|
92
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
93
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function parseDesktopProfile(value: unknown): DesktopProfile {
|
|
97
|
+
if (!isPlainObject(value)) throw new DesktopProfileError("must be an object");
|
|
98
|
+
// `appliedFingerprint`/`appliedAt` are written back by the apply route once a profile
|
|
99
|
+
// reaches Claude Desktop (see server/management/agent-settings-routes.ts). Rejecting them
|
|
100
|
+
// here made every reload after the first apply fail with `unknown field`.
|
|
101
|
+
assertExactKeys(value, ["version", "assignments", "defaults", "appliedFingerprint", "appliedAt"], "profile");
|
|
102
|
+
if (value.version !== 1) throw new DesktopProfileError("version must be 1", "profile.version");
|
|
103
|
+
if (!isPlainObject(value.assignments)) throw new DesktopProfileError("must be an object", "profile.assignments");
|
|
104
|
+
if (!isPlainObject(value.defaults)) throw new DesktopProfileError("must be an object", "profile.defaults");
|
|
105
|
+
assertExactKeys(value.defaults, DESKTOP_FAMILIES, "profile.defaults");
|
|
106
|
+
if (value.appliedFingerprint !== undefined && typeof value.appliedFingerprint !== "string") {
|
|
107
|
+
throw new DesktopProfileError("must be a string", "profile.appliedFingerprint");
|
|
108
|
+
}
|
|
109
|
+
if (value.appliedAt !== undefined && typeof value.appliedAt !== "string") {
|
|
110
|
+
throw new DesktopProfileError("must be a string", "profile.appliedAt");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const assignments: Record<string, OcxClaudeDesktopAssignment> = {};
|
|
114
|
+
const aliases = new Set<string>();
|
|
115
|
+
for (const [route, raw] of Object.entries(value.assignments)) {
|
|
116
|
+
if (!route.trim() || !route.includes("/")) throw new DesktopProfileError("route must be provider/model", `profile.assignments.${route || "<empty>"}`);
|
|
117
|
+
if (!isPlainObject(raw)) throw new DesktopProfileError("must be an object", `profile.assignments.${route}`);
|
|
118
|
+
assertExactKeys(raw, ["family", "alias"], `profile.assignments.${route}`);
|
|
119
|
+
if (!isFamily(raw.family)) throw new DesktopProfileError("unknown family", `profile.assignments.${route}.family`);
|
|
120
|
+
if (typeof raw.alias !== "string" || !raw.alias) throw new DesktopProfileError("must be a non-empty string", `profile.assignments.${route}.alias`);
|
|
121
|
+
if (isRealAnthropicRoute(route)) {
|
|
122
|
+
if (raw.alias !== routeModelId(route)) throw new DesktopProfileError("real Anthropic routes must keep their exact model id", `profile.assignments.${route}.alias`);
|
|
123
|
+
} else if (!validDateAlias(raw.alias)) {
|
|
124
|
+
throw new DesktopProfileError("must be a valid claude-opus-4-8-2026MMDD alias", `profile.assignments.${route}.alias`);
|
|
125
|
+
}
|
|
126
|
+
if (aliases.has(raw.alias)) throw new DesktopProfileError(`duplicate alias "${raw.alias}"`, `profile.assignments.${route}.alias`);
|
|
127
|
+
aliases.add(raw.alias);
|
|
128
|
+
assignments[route] = { family: raw.family, alias: raw.alias };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const defaults = {} as DesktopProfile["defaults"];
|
|
132
|
+
for (const family of DESKTOP_FAMILIES) {
|
|
133
|
+
const route = value.defaults[family];
|
|
134
|
+
if (route !== null && typeof route !== "string") throw new DesktopProfileError("must be a route or null", `profile.defaults.${family}`);
|
|
135
|
+
const members = Object.keys(assignments).filter(key => assignments[key]!.family === family).sort();
|
|
136
|
+
if (members.length === 0) {
|
|
137
|
+
if (route !== null) throw new DesktopProfileError("must be null for an empty family", `profile.defaults.${family}`);
|
|
138
|
+
defaults[family] = null;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (typeof route !== "string" || !assignments[route] || assignments[route]!.family !== family) {
|
|
142
|
+
throw new DesktopProfileError("must reference a member of this family", `profile.defaults.${family}`);
|
|
143
|
+
}
|
|
144
|
+
defaults[family] = route;
|
|
145
|
+
}
|
|
146
|
+
return { version: 1, assignments, defaults, ...appliedMarkers(value) };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function dayOfYearAlias(dayIndex: number): string {
|
|
150
|
+
const date = new Date(Date.UTC(2026, 0, dayIndex + 1));
|
|
151
|
+
const y = date.getUTCFullYear();
|
|
152
|
+
const m = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
153
|
+
const d = String(date.getUTCDate()).padStart(2, "0");
|
|
154
|
+
return `claude-opus-4-8-${y}${m}${d}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function routeStartDay(route: string): number {
|
|
158
|
+
return createHash("sha256").update(route).digest().readUInt32BE(0) % DAY_COUNT_2026;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function allocateAlias(route: string, used: Set<string>): string {
|
|
162
|
+
if (isRealAnthropicRoute(route)) return routeModelId(route);
|
|
163
|
+
const start = routeStartDay(route);
|
|
164
|
+
for (let offset = 0; offset < DAY_COUNT_2026; offset += 1) {
|
|
165
|
+
const alias = dayOfYearAlias((start + offset) % DAY_COUNT_2026);
|
|
166
|
+
if (!used.has(alias)) return alias;
|
|
167
|
+
}
|
|
168
|
+
throw new DesktopProfileError("all 365 encoded date slots are occupied", `profile.assignments.${route}.alias`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function reconcileDesktopProfile(
|
|
172
|
+
stored: unknown,
|
|
173
|
+
models: readonly DesktopProfileModel[],
|
|
174
|
+
): DesktopProfile {
|
|
175
|
+
const profile = stored === undefined || stored === null ? emptyDesktopProfile() : parseDesktopProfile(stored);
|
|
176
|
+
const assignments: DesktopProfile["assignments"] = Object.fromEntries(
|
|
177
|
+
Object.entries(profile.assignments).map(([route, assignment]) => [route, { ...assignment }]),
|
|
178
|
+
);
|
|
179
|
+
const used = new Set(Object.values(assignments).map(value => value.alias));
|
|
180
|
+
for (const model of [...models].sort((a, b) => a.route.localeCompare(b.route))) {
|
|
181
|
+
if (assignments[model.route]) continue;
|
|
182
|
+
const alias = allocateAlias(model.route, used);
|
|
183
|
+
used.add(alias);
|
|
184
|
+
assignments[model.route] = { family: "opus", alias };
|
|
185
|
+
}
|
|
186
|
+
const defaults = { ...profile.defaults };
|
|
187
|
+
for (const family of DESKTOP_FAMILIES) {
|
|
188
|
+
const members = Object.keys(assignments).filter(route => assignments[route]!.family === family).sort();
|
|
189
|
+
const current = defaults[family];
|
|
190
|
+
defaults[family] = current && assignments[current]?.family === family ? current : (members[0] ?? null);
|
|
191
|
+
}
|
|
192
|
+
return parseDesktopProfile({ version: 1, assignments, defaults, ...appliedMarkers(profile) });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function moveDesktopRoute(
|
|
196
|
+
profile: DesktopProfile,
|
|
197
|
+
route: string,
|
|
198
|
+
family: DesktopFamily,
|
|
199
|
+
makeDefault = false,
|
|
200
|
+
): DesktopProfile {
|
|
201
|
+
const parsed = parseDesktopProfile(profile);
|
|
202
|
+
const current = parsed.assignments[route];
|
|
203
|
+
if (!current) throw new DesktopProfileError("route is not assigned", `profile.assignments.${route}`);
|
|
204
|
+
const oldFamily = current.family;
|
|
205
|
+
if (oldFamily === family) {
|
|
206
|
+
return makeDefault ? setDesktopFamilyDefault(parsed, family, route) : parsed;
|
|
207
|
+
}
|
|
208
|
+
const assignments = { ...parsed.assignments, [route]: { ...current, family } };
|
|
209
|
+
const defaults = { ...parsed.defaults };
|
|
210
|
+
if (defaults[oldFamily] === route) {
|
|
211
|
+
defaults[oldFamily] = Object.keys(assignments).filter(key => key !== route && assignments[key]!.family === oldFamily).sort()[0] ?? null;
|
|
212
|
+
}
|
|
213
|
+
const destinationMembers = Object.keys(assignments).filter(key => assignments[key]!.family === family).sort();
|
|
214
|
+
if (makeDefault || !defaults[family] || assignments[defaults[family]!]?.family !== family) defaults[family] = route;
|
|
215
|
+
if (!defaults[family] && destinationMembers.length > 0) defaults[family] = destinationMembers[0]!;
|
|
216
|
+
return parseDesktopProfile({ version: 1, assignments, defaults, ...appliedMarkers(parsed) });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function setDesktopFamilyDefault(
|
|
220
|
+
profile: DesktopProfile,
|
|
221
|
+
family: DesktopFamily,
|
|
222
|
+
route: string | null,
|
|
223
|
+
): DesktopProfile {
|
|
224
|
+
const parsed = parseDesktopProfile(profile);
|
|
225
|
+
const members = Object.keys(parsed.assignments).filter(key => parsed.assignments[key]!.family === family);
|
|
226
|
+
if (route === null && members.length > 0) throw new DesktopProfileError("cannot clear a non-empty family default", `profile.defaults.${family}`);
|
|
227
|
+
if (route !== null && parsed.assignments[route]?.family !== family) throw new DesktopProfileError("route is not a member of this family", `profile.defaults.${family}`);
|
|
228
|
+
return parseDesktopProfile({ ...parsed, defaults: { ...parsed.defaults, [family]: route } });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function renderDesktopProfile(
|
|
232
|
+
profile: DesktopProfile,
|
|
233
|
+
models: readonly DesktopProfileModel[],
|
|
234
|
+
): RenderedDesktopModel[] {
|
|
235
|
+
const parsed = parseDesktopProfile(profile);
|
|
236
|
+
const modelByRoute = new Map(models.map(model => [model.route, model]));
|
|
237
|
+
const activeByFamily = new Map<DesktopFamily, string[]>();
|
|
238
|
+
for (const family of DESKTOP_FAMILIES) activeByFamily.set(family, []);
|
|
239
|
+
for (const [route, assignment] of Object.entries(parsed.assignments)) {
|
|
240
|
+
if (modelByRoute.has(route)) activeByFamily.get(assignment.family)!.push(route);
|
|
241
|
+
}
|
|
242
|
+
for (const routes of activeByFamily.values()) routes.sort();
|
|
243
|
+
const effectiveDefaults = {} as Record<DesktopFamily, string | null>;
|
|
244
|
+
for (const family of DESKTOP_FAMILIES) {
|
|
245
|
+
const active = activeByFamily.get(family)!;
|
|
246
|
+
const stored = parsed.defaults[family];
|
|
247
|
+
effectiveDefaults[family] = stored && active.includes(stored) ? stored : (active[0] ?? null);
|
|
248
|
+
}
|
|
249
|
+
const defaultOrder = DESKTOP_FAMILIES.map(family => effectiveDefaults[family]).filter((route): route is string => !!route);
|
|
250
|
+
const defaultSet = new Set(defaultOrder);
|
|
251
|
+
const rest = Object.keys(parsed.assignments).filter(route => modelByRoute.has(route) && !defaultSet.has(route)).sort();
|
|
252
|
+
return [...defaultOrder, ...rest].map(route => {
|
|
253
|
+
const model = modelByRoute.get(route)!;
|
|
254
|
+
const assignment = parsed.assignments[route]!;
|
|
255
|
+
return {
|
|
256
|
+
...model,
|
|
257
|
+
name: assignment.alias,
|
|
258
|
+
family: assignment.family,
|
|
259
|
+
isFamilyDefault: effectiveDefaults[assignment.family] === route,
|
|
260
|
+
supports1m: typeof model.contextWindow === "number" && model.contextWindow >= 1_000_000,
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
}
|
|
@@ -16,6 +16,8 @@ import { createHmac, randomBytes } from "node:crypto";
|
|
|
16
16
|
import { isClaudeDebugEnabled } from "../lib/debug-settings";
|
|
17
17
|
|
|
18
18
|
export interface ClaudeInboundDebugEntry {
|
|
19
|
+
/** Monotonic capture id โ unique even when several entries share Date.now(). */
|
|
20
|
+
id: number;
|
|
19
21
|
at: number;
|
|
20
22
|
endpoint: "messages" | "count_tokens";
|
|
21
23
|
model: string;
|
|
@@ -39,6 +41,7 @@ const RING_LIMIT = 20;
|
|
|
39
41
|
const ring: ClaudeInboundDebugEntry[] = [];
|
|
40
42
|
const salt = randomBytes(16).toString("hex");
|
|
41
43
|
let lastEnabled = false;
|
|
44
|
+
let nextCaptureId = 0;
|
|
42
45
|
|
|
43
46
|
function tag(value: string): string {
|
|
44
47
|
return createHmac("sha256", salt).update(value).digest("hex").slice(0, 8);
|
|
@@ -82,6 +85,7 @@ export function captureClaudeInbound(
|
|
|
82
85
|
const userId = metadata && typeof metadata.user_id === "string" ? metadata.user_id : undefined;
|
|
83
86
|
const system = systemText(body.system);
|
|
84
87
|
const entry: ClaudeInboundDebugEntry = {
|
|
88
|
+
id: ++nextCaptureId,
|
|
85
89
|
at: Date.now(),
|
|
86
90
|
endpoint,
|
|
87
91
|
model: typeof body.model === "string" ? body.model : "unknown",
|
package/src/claude/model-info.ts
CHANGED
|
@@ -102,7 +102,13 @@ function modelInfo(id: string, displayName: string, ladder: readonly string[], i
|
|
|
102
102
|
export type AnthropicIdStyle = "desktop3p" | "readable";
|
|
103
103
|
|
|
104
104
|
/** Build the full anthropic-flavor discovery list (ids are Desktop 3P aliases). */
|
|
105
|
-
export function buildAnthropicModelInfos(
|
|
105
|
+
export function buildAnthropicModelInfos(
|
|
106
|
+
nativeSlugs: readonly string[],
|
|
107
|
+
routedModels: readonly CatalogModel[],
|
|
108
|
+
auto: AutoContextMode = AUTO_CONTEXT_OFF,
|
|
109
|
+
idStyle: AnthropicIdStyle = "desktop3p",
|
|
110
|
+
aliasForRoute: (provider: string, modelId: string) => string = desktop3pAlias,
|
|
111
|
+
): AnthropicModelInfo[] {
|
|
106
112
|
const out: AnthropicModelInfo[] = [];
|
|
107
113
|
const seen = new Set<string>();
|
|
108
114
|
// [1m] picker variant (devlog 260712 B1): Claude Code accounts exactly 1M for ids
|
|
@@ -122,7 +128,7 @@ export function buildAnthropicModelInfos(nativeSlugs: readonly string[], routedM
|
|
|
122
128
|
out.push({ ...base, id, display_name: `${base.display_name} ยท ${label}`, max_input_tokens: Math.min(window, ONE_MILLION) });
|
|
123
129
|
};
|
|
124
130
|
for (const slug of nativeSlugs) {
|
|
125
|
-
const id = idStyle === "readable" ? claudeCodeNativeAlias(slug) :
|
|
131
|
+
const id = idStyle === "readable" ? claudeCodeNativeAlias(slug) : aliasForRoute("native", slug);
|
|
126
132
|
if (seen.has(id)) continue;
|
|
127
133
|
seen.add(id);
|
|
128
134
|
const info = modelInfo(id, `${slug} (native)`, nativeEffectiveLadder(slug), true);
|
|
@@ -130,7 +136,7 @@ export function buildAnthropicModelInfos(nativeSlugs: readonly string[], routedM
|
|
|
130
136
|
push1mVariant(info, nativeOpenAiContextWindow(slug));
|
|
131
137
|
}
|
|
132
138
|
for (const m of routedModels) {
|
|
133
|
-
const id = idStyle === "readable" ? claudeCodeAlias(m.provider, m.id) :
|
|
139
|
+
const id = idStyle === "readable" ? claudeCodeAlias(m.provider, m.id) : aliasForRoute(m.provider, m.id);
|
|
134
140
|
if (seen.has(id)) continue;
|
|
135
141
|
seen.add(id);
|
|
136
142
|
const ladder = Array.isArray(m.reasoningEfforts) ? m.reasoningEfforts : [];
|
|
@@ -19,6 +19,7 @@ const EXTENDED_USAGE = `Usage:
|
|
|
19
19
|
ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
|
|
20
20
|
ocx account alias <provider> <id|main> <display-name|-> [--json]
|
|
21
21
|
ocx account remove <provider> <id|main> --yes [--json]
|
|
22
|
+
ocx account clear-cooldown <provider> <id|main> [--json]
|
|
22
23
|
ocx account add-key <provider> [--label <label>] [--json]`;
|
|
23
24
|
const PIPE_GUIDANCE = `Pipe the API key on stdin, for example:
|
|
24
25
|
ocx account add-key <provider> <<< "$MY_KEY"
|
|
@@ -282,6 +283,39 @@ export async function cmdAddKey(args: string[], deps: AccountDeps): Promise<numb
|
|
|
282
283
|
return 0;
|
|
283
284
|
}
|
|
284
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Lift a quota cooldown on a Codex account.
|
|
288
|
+
*
|
|
289
|
+
* This is the user-facing escape from the lockout described in
|
|
290
|
+
* `devlog/_plan/260726_cooldown_lockout_hardening`: injected routing makes the proxy the
|
|
291
|
+
* only model path for Codex Desktop, so a stuck cooldown reads as "the whole app is dead".
|
|
292
|
+
*
|
|
293
|
+
* Codex accounts only. API-key pools already reset their own 429 cooldowns through key
|
|
294
|
+
* management (`clearKeyCooldowns`), and OAuth providers have no equivalent state here.
|
|
295
|
+
*/
|
|
296
|
+
export async function cmdClearCooldown(args: string[], deps: AccountDeps): Promise<number> {
|
|
297
|
+
const wantsJson = flag(args, "--json");
|
|
298
|
+
const name = args.shift();
|
|
299
|
+
const requestedId = args.shift();
|
|
300
|
+
if (!name || !requestedId || args.length) return usage();
|
|
301
|
+
const classified = configAndType(deps, name);
|
|
302
|
+
if ("error" in classified) return usage(`Error: ${classified.error}`);
|
|
303
|
+
if (classified.type !== "codex") {
|
|
304
|
+
return usage(`Error: ${name} is not a Codex account pool; cooldown clearing applies to Codex accounts only`);
|
|
305
|
+
}
|
|
306
|
+
const id = requestedId === "main" ? MAIN_ID : requestedId;
|
|
307
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
308
|
+
if (!baseUrl) return proxyUnreachable();
|
|
309
|
+
const response = await apiJson(deps, baseUrl, "POST", "/api/codex-auth/accounts/clear-cooldown", { id });
|
|
310
|
+
if (response.status === 0) return proxyUnreachable();
|
|
311
|
+
if (response.status !== 200) return apiError(response.json, `failed to clear cooldown for ${requestedId}`);
|
|
312
|
+
const cleared = response.json?.cleared === true;
|
|
313
|
+
if (wantsJson) console.log(JSON.stringify({ ok: true, provider: name, id, cleared }, null, 2));
|
|
314
|
+
else if (cleared) console.log(`${name}: cooldown lifted for ${requestedId}`);
|
|
315
|
+
else console.log(`${name}: no active cooldown for ${requestedId}`);
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
285
319
|
export async function cmdAlias(args: string[], deps: AccountDeps): Promise<number> {
|
|
286
320
|
const wantsJson = flag(args, "--json");
|
|
287
321
|
const name = args.shift();
|
package/src/cli/account.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { loadConfig } from "../config";
|
|
3
3
|
import { providerCodexAccountMode } from "../providers/registry";
|
|
4
4
|
import type { OcxConfig } from "../types";
|
|
5
|
-
import { cmdAddKey, cmdAlias, cmdAutoSwitch, cmdRefresh, cmdRemove } from "./account-extended";
|
|
5
|
+
import { cmdAddKey, cmdAlias, cmdAutoSwitch, cmdClearCooldown, cmdRefresh, cmdRemove } from "./account-extended";
|
|
6
6
|
import { apiError, apiJson, classifyAccount, fetchRows, proxyUnreachable, resolveBaseUrl, type AccountDeps, type AccountRow, type AccountType, type ApiResult }
|
|
7
7
|
from "./account-api";
|
|
8
8
|
|
|
@@ -23,6 +23,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
23
23
|
ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
|
|
24
24
|
ocx account alias <provider> <account-or-key-id> <display-name|-> [--json]
|
|
25
25
|
ocx account remove <provider> <account-or-key-id|main> --yes [--json]
|
|
26
|
+
ocx account clear-cooldown <provider> <account-id|main> [--json]
|
|
26
27
|
ocx account add-key <provider> [--label <label>] [--json]
|
|
27
28
|
|
|
28
29
|
List and switch provider accounts and API-key pools (masked output only).
|
|
@@ -257,6 +258,7 @@ export async function cmdAccount(args: string[], deps: AccountDeps = {}): Promis
|
|
|
257
258
|
if (sub === "auto-switch") return await cmdAutoSwitch(rest, deps);
|
|
258
259
|
if (sub === "alias" || sub === "rename") return await cmdAlias(rest, deps);
|
|
259
260
|
if (sub === "remove") return await cmdRemove(rest, deps);
|
|
261
|
+
if (sub === "clear-cooldown") return await cmdClearCooldown(rest, deps);
|
|
260
262
|
if (sub === "add-key") return await cmdAddKey(rest, deps);
|
|
261
263
|
console.error(ACCOUNT_USAGE);
|
|
262
264
|
return 1;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { loadConfig, saveConfigPreservingClaudeCode } from "../config";
|
|
4
|
+
import {
|
|
5
|
+
DESKTOP_FAMILIES,
|
|
6
|
+
moveDesktopRoute,
|
|
7
|
+
parseDesktopProfile,
|
|
8
|
+
setDesktopFamilyDefault,
|
|
9
|
+
type DesktopFamily,
|
|
10
|
+
type DesktopProfile,
|
|
11
|
+
} from "../claude/desktop-profile";
|
|
12
|
+
import { writeDesktop3pConfig, type Desktop3pConfigMode, parseDesktop3pModeArgs } from "../claude/desktop-3p";
|
|
13
|
+
import { filterCatalogVisibleModels, visibleNativeSlugs } from "../codex/catalog";
|
|
14
|
+
import { buildClaudeDesktopState, fetchAllModels } from "../server/management-api";
|
|
15
|
+
import { findLiveProxy } from "../server/proxy-liveness";
|
|
16
|
+
|
|
17
|
+
function isFamily(value: string | undefined): value is DesktopFamily {
|
|
18
|
+
return !!value && (DESKTOP_FAMILIES as readonly string[]).includes(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function printDesktopHelp(): void {
|
|
22
|
+
console.log(`Usage:
|
|
23
|
+
ocx claude desktop [apply] [--static|--hybrid|--discovery-only]
|
|
24
|
+
ocx claude desktop show [--json]
|
|
25
|
+
ocx claude desktop move <provider/model> <opus|fable|sonnet|haiku> [--default]
|
|
26
|
+
ocx claude desktop default <family> <provider/model|none>
|
|
27
|
+
ocx claude desktop export <path|->
|
|
28
|
+
ocx claude desktop import <path> [--apply]`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function applyProfile(profile: DesktopProfile, mode: Desktop3pConfigMode): Promise<{ ok: boolean; path: string; reason?: string }> {
|
|
32
|
+
const config = loadConfig();
|
|
33
|
+
const state = await buildClaudeDesktopState(config, profile);
|
|
34
|
+
config.claudeCode = { ...(config.claudeCode ?? {}), desktopProfile: state.profile };
|
|
35
|
+
saveConfigPreservingClaudeCode(config);
|
|
36
|
+
const live = await findLiveProxy();
|
|
37
|
+
const allModels = await fetchAllModels(config);
|
|
38
|
+
const routed = filterCatalogVisibleModels(allModels, config).map(model => ({
|
|
39
|
+
provider: model.provider,
|
|
40
|
+
id: model.id,
|
|
41
|
+
contextWindow: model.contextWindow,
|
|
42
|
+
}));
|
|
43
|
+
const result = writeDesktop3pConfig(
|
|
44
|
+
live?.port ?? config.port ?? 10100,
|
|
45
|
+
[...visibleNativeSlugs(config)],
|
|
46
|
+
routed,
|
|
47
|
+
config.apiKeys?.[0]?.key,
|
|
48
|
+
mode,
|
|
49
|
+
state.profile,
|
|
50
|
+
);
|
|
51
|
+
return { ok: result.written, path: result.path, reason: result.reason };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function handleClaudeDesktopCommand(argv: string[]): Promise<number> {
|
|
55
|
+
const command = argv[0];
|
|
56
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
57
|
+
printDesktopHelp();
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Legacy mode flags remain apply aliases and are parsed before subcommands.
|
|
62
|
+
const legacyFlags = argv.filter(arg => ["--static", "--hybrid", "--discovery-only"].includes(arg));
|
|
63
|
+
const applyInvocation = argv.length === 0 || command === "apply" || legacyFlags.length > 0;
|
|
64
|
+
if (applyInvocation) {
|
|
65
|
+
const nonMode = argv.filter(arg => !["apply", "--static", "--hybrid", "--discovery-only"].includes(arg));
|
|
66
|
+
if (nonMode.length > 0) {
|
|
67
|
+
console.error(`์ ์ ์๋ ์ธ์: ${nonMode.join(" ")}`);
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
const parsedMode = parseDesktop3pModeArgs(legacyFlags);
|
|
71
|
+
if ("error" in parsedMode) { console.error(parsedMode.error); return 1; }
|
|
72
|
+
try {
|
|
73
|
+
const config = loadConfig();
|
|
74
|
+
const state = await buildClaudeDesktopState(config);
|
|
75
|
+
const result = await applyProfile(state.profile, parsedMode.mode);
|
|
76
|
+
if (!result.ok) { console.error(`์ค์ ์ ์ฉ ์คํจ: ${result.reason ?? "unknown error"}`); return 1; }
|
|
77
|
+
console.log(`Claude Desktop ์ค์ ์ ์ ์ฉํ์ต๋๋ค: ${result.path}`);
|
|
78
|
+
console.log("Claude Desktop์ ์์ ํ ์ข
๋ฃํ ๋ค ๋ค์ ์ด์ด ์ฃผ์ธ์.");
|
|
79
|
+
return 0;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const config = loadConfig();
|
|
88
|
+
const state = await buildClaudeDesktopState(config);
|
|
89
|
+
if (command === "show") {
|
|
90
|
+
if (argv.length > 2 || (argv[1] && argv[1] !== "--json")) throw new Error("Usage: ocx claude desktop show [--json]");
|
|
91
|
+
if (argv[1] === "--json") console.log(JSON.stringify(state));
|
|
92
|
+
else {
|
|
93
|
+
for (const family of DESKTOP_FAMILIES) {
|
|
94
|
+
console.log(`${family.toUpperCase()}${state.profile.defaults[family] ? ` (default: ${state.profile.defaults[family]})` : ""}`);
|
|
95
|
+
for (const model of state.models.filter(item => item.assignment.family === family)) {
|
|
96
|
+
console.log(` ${model.available ? "โข" : "โ"} ${model.route} -> ${model.assignment.alias}${model.available ? "" : " (unavailable)"}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
if (command === "move") {
|
|
103
|
+
const [, route, familyRaw, ...flags] = argv;
|
|
104
|
+
if (!route || !isFamily(familyRaw) || flags.some(flag => flag !== "--default")) throw new Error("Usage: ocx claude desktop move <route> <family> [--default]");
|
|
105
|
+
if (!state.models.some(model => model.route === route && model.available)) throw new Error(`ํ์ฌ ์ฌ์ฉํ ์ ์๋ ๋ชจ๋ธ์
๋๋ค: ${route}`);
|
|
106
|
+
const profile = moveDesktopRoute(state.profile, route, familyRaw, flags.includes("--default"));
|
|
107
|
+
config.claudeCode = { ...(config.claudeCode ?? {}), desktopProfile: profile };
|
|
108
|
+
saveConfigPreservingClaudeCode(config);
|
|
109
|
+
console.log(`${route} ๋ชจ๋ธ์ ${familyRaw} ๊ทธ๋ฃน์ผ๋ก ์ฎ๊ฒผ์ต๋๋ค.`);
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
if (command === "default") {
|
|
113
|
+
const [, familyRaw, routeRaw] = argv;
|
|
114
|
+
if (!isFamily(familyRaw) || !routeRaw || argv.length !== 3) throw new Error("Usage: ocx claude desktop default <family> <route|none>");
|
|
115
|
+
const route = routeRaw === "none" ? null : routeRaw;
|
|
116
|
+
if (route && !state.models.some(model => model.route === route && model.available)) throw new Error(`ํ์ฌ ์ฌ์ฉํ ์ ์๋ ๋ชจ๋ธ์
๋๋ค: ${route}`);
|
|
117
|
+
const profile = setDesktopFamilyDefault(state.profile, familyRaw, route);
|
|
118
|
+
config.claudeCode = { ...(config.claudeCode ?? {}), desktopProfile: profile };
|
|
119
|
+
saveConfigPreservingClaudeCode(config);
|
|
120
|
+
console.log(`${familyRaw} ๊ธฐ๋ณธ ๋ชจ๋ธ์ ${route ?? "์์"}์ผ๋ก ์ง์ ํ์ต๋๋ค.`);
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
if (command === "export") {
|
|
124
|
+
const target = argv[1];
|
|
125
|
+
if (!target || argv.length !== 2) throw new Error("Usage: ocx claude desktop export <path|->");
|
|
126
|
+
const json = JSON.stringify(state.profile, null, 2) + "\n";
|
|
127
|
+
if (target === "-") process.stdout.write(json);
|
|
128
|
+
else writeFileSync(resolve(target), json, { encoding: "utf8", mode: 0o600 });
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
if (command === "import") {
|
|
132
|
+
const source = argv[1];
|
|
133
|
+
const flags = argv.slice(2);
|
|
134
|
+
if (!source || flags.some(flag => flag !== "--apply")) throw new Error("Usage: ocx claude desktop import <path> [--apply]");
|
|
135
|
+
const profile = parseDesktopProfile(JSON.parse(readFileSync(resolve(source), "utf8")));
|
|
136
|
+
const reconciled = (await buildClaudeDesktopState(config, profile)).profile;
|
|
137
|
+
config.claudeCode = { ...(config.claudeCode ?? {}), desktopProfile: reconciled };
|
|
138
|
+
saveConfigPreservingClaudeCode(config);
|
|
139
|
+
if (flags.includes("--apply")) {
|
|
140
|
+
const result = await applyProfile(reconciled, "static");
|
|
141
|
+
if (!result.ok) { console.error(`ํ๋กํ์ ์ ์ฅํ์ง๋ง Desktop ์ ์ฉ์ ์คํจํ์ต๋๋ค: ${result.reason ?? "unknown error"}`); return 1; }
|
|
142
|
+
}
|
|
143
|
+
console.log("Claude Desktop ํ๋กํ์ ๊ฐ์ ธ์์ต๋๋ค.");
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
printDesktopHelp();
|
|
147
|
+
return 1;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
150
|
+
return 1;
|
|
151
|
+
}
|
|
152
|
+
}
|
package/src/cli/claude.ts
CHANGED
|
@@ -14,19 +14,39 @@ import { refreshGatewayModelCacheFromProxy } from "../claude/gateway-cache";
|
|
|
14
14
|
import { commandInvocation } from "../lib/win-exec";
|
|
15
15
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
16
16
|
import type { OcxConfig } from "../types";
|
|
17
|
+
import { PROXY_MARKER, ownAdmissionTokens, defaultAuthDetectDeps, detectClaudeAuth, type AuthDetectDeps } from "../claude/auth-detect";
|
|
18
|
+
import { resolveClaudeAuthMode } from "../claude/auth-mode";
|
|
17
19
|
|
|
18
20
|
export interface ClaudeLaunchEnv {
|
|
19
21
|
[key: string]: string | undefined;
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Injectable IO for tests. `env` is deliberately NOT injectable: it is bound to the
|
|
26
|
+
* launch base so detection and the spawned process can never disagree (audit R3-3).
|
|
27
|
+
*/
|
|
28
|
+
export type ClaudeEnvDeps = { authDetect?: Omit<Partial<AuthDetectDeps>, "env" | "ownTokens"> };
|
|
29
|
+
|
|
22
30
|
/**
|
|
23
31
|
* Pure env assembly (unit-tested): never sets ANTHROPIC_API_KEY (setting both
|
|
24
32
|
* token vars triggers Claude Code's auth-conflict warning, 003 E1), and never
|
|
25
33
|
* overrides variables the user already exported, apart from stale loopback
|
|
26
34
|
* ANTHROPIC_BASE_URL values owned by a previous opencodex launch.
|
|
27
35
|
*/
|
|
28
|
-
export function buildClaudeEnv(
|
|
36
|
+
export function buildClaudeEnv(
|
|
37
|
+
config: OcxConfig,
|
|
38
|
+
port: number,
|
|
39
|
+
base: ClaudeLaunchEnv,
|
|
40
|
+
contextWindows: Record<string, number> = {},
|
|
41
|
+
deps: ClaudeEnvDeps = {},
|
|
42
|
+
): ClaudeLaunchEnv {
|
|
29
43
|
const env: ClaudeLaunchEnv = { ...base };
|
|
44
|
+
// Step 1 โ strip OUR OWN dummy from the inherited environment before anything reads
|
|
45
|
+
// or writes the token slot. setDefault below preserves any non-empty value, so a
|
|
46
|
+
// stale marker left in place would suppress the admission key and then be removed,
|
|
47
|
+
// leaving the child with no token at all (audit R2-1). It is opencodex state, never
|
|
48
|
+
// user auth, so dropping it unconditionally is safe.
|
|
49
|
+
if (env.ANTHROPIC_AUTH_TOKEN === PROXY_MARKER) delete env.ANTHROPIC_AUTH_TOKEN;
|
|
30
50
|
const setDefault = (name: string, value: string | undefined) => {
|
|
31
51
|
if (value === undefined || value.length === 0) return;
|
|
32
52
|
if (env[name] !== undefined && env[name] !== "") return; // user wins
|
|
@@ -55,8 +75,22 @@ export function buildClaudeEnv(config: OcxConfig, port: number, base: ClaudeLaun
|
|
|
55
75
|
if ((config.apiKeys?.length ?? 0) > 0) {
|
|
56
76
|
setDefault("ANTHROPIC_AUTH_TOKEN", config.apiKeys![0].key);
|
|
57
77
|
}
|
|
58
|
-
|
|
59
|
-
|
|
78
|
+
// Detection reads the SAME base env this launch will use, so the resolver and the
|
|
79
|
+
// spawned process cannot disagree. Injected deps are spread FIRST and `env` bound
|
|
80
|
+
// LAST, and the injection type excludes `env`, so a test fake cannot break that.
|
|
81
|
+
// `ownTokens` is bound last for the same reason: it is config-derived, and a fake
|
|
82
|
+
// that replaced it could make our own admission key look like user auth.
|
|
83
|
+
const resolved = resolveClaudeAuthMode(config, detectClaudeAuth({
|
|
84
|
+
...defaultAuthDetectDeps(base as NodeJS.ProcessEnv),
|
|
85
|
+
...(deps.authDetect ?? {}),
|
|
86
|
+
env: () => base as NodeJS.ProcessEnv,
|
|
87
|
+
ownTokens: ownAdmissionTokens(config),
|
|
88
|
+
}));
|
|
89
|
+
if (!env.ANTHROPIC_AUTH_TOKEN && resolved.markerMode === "proxy") {
|
|
90
|
+
env.ANTHROPIC_AUTH_TOKEN = PROXY_MARKER;
|
|
91
|
+
}
|
|
92
|
+
if (resolved.origin === "auto-unknown") {
|
|
93
|
+
console.error("โ Claude ์ธ์ฆ์ ํ์ธํ์ง ๋ชปํ์ต๋๋ค โ ๊ตฌ๋
๋ฐฉ์์ผ๋ก ์งํํฉ๋๋ค. GUI์์ ์ธ์ฆ ๋ชจ๋๋ฅผ ์ง์ ์ง์ ํ๋ฉด ์ด ํ๋จ์ ๋ฎ์ด์ธ ์ ์์ต๋๋ค.");
|
|
60
94
|
}
|
|
61
95
|
// NOTE: do NOT set _CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL here. While it enables
|
|
62
96
|
// Design/Remote Control, it DISABLES gateway model discovery (Claude Code's eligibility
|