vibe-coding-master 0.7.11 → 0.7.13

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 CHANGED
@@ -228,6 +228,34 @@ such as a Dev Container or VM.
228
228
  Model and effort can be selected before start/resume/restart. Changes affect the
229
229
  next launched process, not a currently running Claude Code process.
230
230
 
231
+ ### GPT Through Claude Code Router
232
+
233
+ VCM can launch its normal Claude Code sessions with `GPT-5.6 Sol (CCR)` through
234
+ a host-running [Claude Code Router](https://github.com/musistudio/claude-code-router).
235
+ CCR must already be installed, authenticated, configured, and running on the
236
+ host. VCM does not manage the CCR process.
237
+
238
+ VCM uses this fixed Dev Container endpoint:
239
+
240
+ ```text
241
+ http://host.docker.internal:3456
242
+ ```
243
+
244
+ Configure CCR to listen on port `3456` with an API key and make it reachable
245
+ from the container. In the VCM `Settings` section:
246
+
247
+ 1. enter and save the CCR API key;
248
+ 2. enable `CCR GPT models`;
249
+ 3. confirm the status is `available`;
250
+ 4. select `GPT-5.6 Sol (CCR)` in any Session model control.
251
+
252
+ The key is stored in global VCM state (`~/.vcm/settings.json`) with owner-only
253
+ permissions and is never returned by the settings API. CCR settings are applied
254
+ only to the child Claude Code process being launched. Native Claude selections
255
+ remain unchanged. If CCR is disabled, unreachable, rejects the key, or does not
256
+ expose `Codex API/gpt-5.6-sol`, VCM blocks the new Start, Resume, or Restart and
257
+ does not fall back to another model.
258
+
231
259
  ## Launch Template
232
260
 
233
261
  The launch template stores per-role defaults:
@@ -0,0 +1,149 @@
1
+ import { CCR_GATEWAY_BASE_URL, CCR_GPT_MODEL_ID } from "../../shared/types/session.js";
2
+ const DEFAULT_TIMEOUT_MS = 3_000;
3
+ const CCR_ENCODED_MODEL_PREFIX = "anthropic/claude-ccr-h";
4
+ export function createCcrGatewayAdapter(deps = {}) {
5
+ const baseUrl = (deps.baseUrl ?? CCR_GATEWAY_BASE_URL).replace(/\/+$/, "");
6
+ const fetchImpl = deps.fetch ?? globalThis.fetch;
7
+ const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
8
+ return {
9
+ async probe(apiKey) {
10
+ try {
11
+ const root = await requestJson(fetchImpl, `${baseUrl}/`, timeoutMs);
12
+ if (!root.response.ok) {
13
+ return invalidResponse(`CCR gateway root returned HTTP ${root.response.status}.`);
14
+ }
15
+ if (!isCcrGateway(root.payload)) {
16
+ return {
17
+ connectionState: "not-ccr",
18
+ modelAvailable: false,
19
+ error: `The fixed CCR endpoint ${baseUrl} did not identify a Claude Code Router gateway.`
20
+ };
21
+ }
22
+ const models = await requestJson(fetchImpl, `${baseUrl}/v1/models`, timeoutMs, {
23
+ authorization: `Bearer ${apiKey}`,
24
+ "user-agent": "Claude Code"
25
+ });
26
+ if (models.response.status === 401 || models.response.status === 403) {
27
+ return {
28
+ connectionState: "unauthorized",
29
+ modelAvailable: false,
30
+ error: "CCR rejected the configured API key."
31
+ };
32
+ }
33
+ if (!models.response.ok) {
34
+ return invalidResponse(`CCR model discovery returned HTTP ${models.response.status}.`);
35
+ }
36
+ const modelDescriptors = readModelDescriptors(models.payload);
37
+ if (!modelDescriptors) {
38
+ return invalidResponse("CCR returned an invalid /v1/models response.");
39
+ }
40
+ const modelAvailable = modelDescriptors.some(isRequiredModel);
41
+ return {
42
+ connectionState: "available",
43
+ modelAvailable,
44
+ ...(modelAvailable
45
+ ? {}
46
+ : { error: `CCR does not expose the required model ${CCR_GPT_MODEL_ID}.` })
47
+ };
48
+ }
49
+ catch (error) {
50
+ const timedOut = isAbortError(error);
51
+ return {
52
+ connectionState: "unreachable",
53
+ modelAvailable: false,
54
+ error: timedOut
55
+ ? `CCR connection timed out after ${timeoutMs} ms at ${baseUrl}.`
56
+ : `CCR could not be reached from the VCM container backend at ${baseUrl}. Reason: ${errorMessage(error)}`
57
+ };
58
+ }
59
+ }
60
+ };
61
+ }
62
+ async function requestJson(fetchImpl, url, timeoutMs, headers = {}) {
63
+ const controller = new AbortController();
64
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
65
+ try {
66
+ const response = await fetchImpl(url, {
67
+ method: "GET",
68
+ headers: {
69
+ accept: "application/json",
70
+ ...headers
71
+ },
72
+ signal: controller.signal
73
+ });
74
+ let payload;
75
+ try {
76
+ payload = await response.json();
77
+ }
78
+ catch {
79
+ payload = undefined;
80
+ }
81
+ return { response, payload };
82
+ }
83
+ finally {
84
+ clearTimeout(timeout);
85
+ }
86
+ }
87
+ function isCcrGateway(value) {
88
+ if (!isObject(value)) {
89
+ return false;
90
+ }
91
+ return value.name === "claude-code-router"
92
+ || value.plugin === "claude-code-router"
93
+ || value.core === "next-ai-gateway";
94
+ }
95
+ function readModelDescriptors(value) {
96
+ if (!isObject(value) || !Array.isArray(value.data)) {
97
+ return undefined;
98
+ }
99
+ const models = [];
100
+ for (const item of value.data) {
101
+ if (!isObject(item) || typeof item.id !== "string" || !item.id.trim()) {
102
+ return undefined;
103
+ }
104
+ models.push({
105
+ id: item.id.trim(),
106
+ ...(typeof item.display_name === "string" && item.display_name.trim()
107
+ ? { displayName: item.display_name.trim() }
108
+ : {})
109
+ });
110
+ }
111
+ return models;
112
+ }
113
+ function isRequiredModel(model) {
114
+ if (model.id === CCR_GPT_MODEL_ID) {
115
+ return true;
116
+ }
117
+ const target = normalizeModelName(CCR_GPT_MODEL_ID);
118
+ return normalizeModelName(model.displayName) === target
119
+ || normalizeModelName(decodeCcrModelId(model.id)) === target;
120
+ }
121
+ function decodeCcrModelId(modelId) {
122
+ if (!modelId.startsWith(CCR_ENCODED_MODEL_PREFIX)) {
123
+ return undefined;
124
+ }
125
+ const encoded = modelId.slice(CCR_ENCODED_MODEL_PREFIX.length);
126
+ if (!encoded || encoded.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(encoded)) {
127
+ return undefined;
128
+ }
129
+ return Buffer.from(encoded, "hex").toString("utf8");
130
+ }
131
+ function normalizeModelName(value) {
132
+ return (value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "");
133
+ }
134
+ function invalidResponse(error) {
135
+ return {
136
+ connectionState: "invalid-response",
137
+ modelAvailable: false,
138
+ error
139
+ };
140
+ }
141
+ function isAbortError(error) {
142
+ return error instanceof Error && error.name === "AbortError";
143
+ }
144
+ function errorMessage(error) {
145
+ return error instanceof Error ? error.message : String(error);
146
+ }
147
+ function isObject(value) {
148
+ return typeof value === "object" && value !== null && !Array.isArray(value);
149
+ }
@@ -1,3 +1,4 @@
1
+ import { isCcrSessionModel } from "../../shared/types/session.js";
1
2
  import { VcmError } from "../errors.js";
2
3
  export function createClaudeAdapter(runner) {
3
4
  return {
@@ -22,7 +23,9 @@ export function createClaudeAdapter(runner) {
22
23
  if (claudeSessionId) {
23
24
  args.push(resume ? "--resume" : "--session-id", claudeSessionId);
24
25
  }
25
- args.push("--model", model);
26
+ if (!isCcrSessionModel(model)) {
27
+ args.push("--model", model);
28
+ }
26
29
  if (effort === "ultracode") {
27
30
  args.push("--settings", JSON.stringify({ ultracode: true }));
28
31
  }
@@ -89,6 +89,9 @@ export function createNodeFileSystemAdapter() {
89
89
  await fs.rename(tempPath, targetPath);
90
90
  });
91
91
  },
92
+ async chmod(targetPath, mode) {
93
+ await runFileOperation(() => fs.chmod(targetPath, mode));
94
+ },
92
95
  async ensureFile(targetPath, content, options = {}) {
93
96
  if (!options.overwrite && await this.pathExists(targetPath)) {
94
97
  return false;
@@ -5,4 +5,13 @@ export function registerAppSettingsRoutes(app, deps) {
5
5
  app.put("/api/settings/preferences", async (request) => {
6
6
  return deps.appSettings.updatePreferences(request.body ?? {});
7
7
  });
8
+ app.get("/api/settings/ccr", async () => {
9
+ return deps.ccrIntegration.getStatus();
10
+ });
11
+ app.put("/api/settings/ccr", async (request) => {
12
+ return deps.ccrIntegration.updateSettings(request.body ?? {});
13
+ });
14
+ app.post("/api/settings/ccr/check", async () => {
15
+ return deps.ccrIntegration.checkConnection();
16
+ });
8
17
  }
@@ -22,6 +22,7 @@ export function registerSessionRoutes(app, deps) {
22
22
  const project = await requireCurrentProject(deps.projectService);
23
23
  const role = parseRole(request.params.role);
24
24
  const existing = await deps.sessionService.getRoleSession(project.repoRoot, request.params.taskSlug, role);
25
+ await deps.sessionService.assertModelLaunchReady(request.body?.model ?? existing?.model);
25
26
  if (existing) {
26
27
  await deps.translationService.stopSession(existing.id, { clearCache: true });
27
28
  deps.roundService.stopSession(existing.id);
@@ -40,6 +40,7 @@ export function registerTranslationWorkerRoutes(app, deps) {
40
40
  const project = await requireCurrentProject(deps.projectService);
41
41
  const taskSlug = requireTaskSlug(request.body?.taskSlug, "Translator");
42
42
  const existing = await deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "translator");
43
+ await deps.sessionService.assertModelLaunchReady(request.body?.model ?? existing?.model);
43
44
  if (existing) {
44
45
  await deps.translationService.stopSession(existing.id, { clearCache: true });
45
46
  }
@@ -4,11 +4,13 @@ import Fastify from "fastify";
4
4
  import fastifyStatic from "@fastify/static";
5
5
  import { createArtifactService } from "./services/artifact-service.js";
6
6
  import { createClaudeAdapter } from "./adapters/claude-adapter.js";
7
+ import { createCcrGatewayAdapter } from "./adapters/ccr-gateway-adapter.js";
7
8
  import { createCommandRunner } from "./adapters/command-runner.js";
8
9
  import { createCommandDispatcher } from "./services/command-dispatcher.js";
9
10
  import { createClaudeHookService } from "./services/claude-hook-service.js";
10
11
  import { createGitAdapter } from "./adapters/git-adapter.js";
11
12
  import { createAppSettingsService } from "./services/app-settings-service.js";
13
+ import { createCcrIntegrationService } from "./services/ccr-integration-service.js";
12
14
  import { createAutoMemoryService } from "./services/auto-memory-service.js";
13
15
  import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
14
16
  import { createGateReviewService } from "./services/gate-review-service.js";
@@ -78,7 +80,10 @@ export async function createServer(deps, options = {}) {
78
80
  });
79
81
  });
80
82
  registerDiagnosticsRoutes(app, { diagnosticsService: deps.diagnosticsService });
81
- registerAppSettingsRoutes(app, { appSettings: deps.appSettings });
83
+ registerAppSettingsRoutes(app, {
84
+ appSettings: deps.appSettings,
85
+ ccrIntegration: deps.ccrIntegration
86
+ });
82
87
  registerClaudeHookRoutes(app, { claudeHookService: deps.claudeHookService });
83
88
  registerGateReviewRoutes(app, {
84
89
  projectService: deps.projectService,
@@ -156,6 +161,7 @@ export async function createServer(deps, options = {}) {
156
161
  onManualInterrupt: (sessionId) => deps.terminalInterruptService.handleManualInterrupt(sessionId)
157
162
  });
158
163
  app.addHook("onReady", async () => {
164
+ await deps.ccrIntegration.initialize();
159
165
  await cleanupRecentTranslationRuntime(deps);
160
166
  deps.runtimeCoordinator.start();
161
167
  await deps.gatewayService.start();
@@ -202,6 +208,10 @@ export function createDefaultServerDeps(options = {}) {
202
208
  const git = createGitAdapter(runner);
203
209
  const claude = createClaudeAdapter(runner);
204
210
  const appSettings = createAppSettingsService({ fs });
211
+ const ccrIntegration = createCcrIntegrationService({
212
+ settings: appSettings,
213
+ gateway: createCcrGatewayAdapter()
214
+ });
205
215
  const runtime = createNodePtyTerminalRuntime({ fs });
206
216
  const registry = createSessionRegistry();
207
217
  const artifactService = createArtifactService(fs);
@@ -217,6 +227,7 @@ export function createDefaultServerDeps(options = {}) {
217
227
  projectService,
218
228
  taskService,
219
229
  taskWorkflowService,
230
+ ccrIntegration,
220
231
  apiUrl: options.apiUrl
221
232
  });
222
233
  const harnessService = createHarnessService({
@@ -396,6 +407,7 @@ export function createDefaultServerDeps(options = {}) {
396
407
  });
397
408
  return {
398
409
  appSettings,
410
+ ccrIntegration,
399
411
  projectService,
400
412
  taskService,
401
413
  taskCloseService,
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
3
3
  import { VCM_ROLE_NAMES } from "../../shared/constants.js";
4
4
  import { GATE_REVIEW_GATES } from "../../shared/types/gate-review.js";
5
5
  import { createDefaultLaunchTemplate, DEFAULT_TRANSLATION_OUTPUT_MODE, DEFAULT_TRANSLATION_TARGET_LANGUAGE, TRANSLATION_OUTPUT_MODE_OPTIONS, TRANSLATION_TARGET_LANGUAGE_OPTIONS } from "../../shared/types/app-settings.js";
6
- import { CLAUDE_MODEL_OPTIONS, SESSION_EFFORT_OPTIONS } from "../../shared/types/session.js";
6
+ import { CLAUDE_MODEL_OPTIONS, CCR_GPT_SESSION_MODEL, SESSION_EFFORT_OPTIONS } from "../../shared/types/session.js";
7
7
  import { resolveVcmDataDir } from "../vcm-data-dir.js";
8
8
  const MAX_RECENT_REPOSITORIES = 5;
9
9
  export function createAppSettingsService(deps) {
@@ -20,6 +20,7 @@ export function createAppSettingsService(deps) {
20
20
  let shouldSave = false;
21
21
  if (await deps.fs.pathExists(settingsPath)) {
22
22
  raw = await deps.fs.readJson(settingsPath);
23
+ await deps.fs.chmod?.(settingsPath, 0o600);
23
24
  }
24
25
  else {
25
26
  shouldSave = true;
@@ -33,6 +34,7 @@ export function createAppSettingsService(deps) {
33
34
  async function saveSettings(settings) {
34
35
  cachedSettings = settings;
35
36
  await deps.fs.writeJsonAtomic(settingsPath, settings);
37
+ await deps.fs.chmod?.(settingsPath, 0o600);
36
38
  }
37
39
  async function loadProjectIndex() {
38
40
  if (cachedProjectIndex) {
@@ -144,6 +146,21 @@ export function createAppSettingsService(deps) {
144
146
  requiredGates: normalizedRequiredGates
145
147
  };
146
148
  },
149
+ async getCcrIntegrationSettings() {
150
+ return normalizeCcrIntegrationSettings((await loadSettings()).ccr);
151
+ },
152
+ async updateCcrIntegrationSettings(input) {
153
+ const current = await loadSettings();
154
+ const ccr = normalizeCcrIntegrationSettings({
155
+ ...current.ccr,
156
+ ...input
157
+ });
158
+ await saveSettings({
159
+ ...current,
160
+ ccr
161
+ });
162
+ return ccr;
163
+ },
147
164
  getSettingsPath() {
148
165
  return settingsPath;
149
166
  },
@@ -229,8 +246,18 @@ function normalizeSettingsFile(input) {
229
246
  if (gateReview) {
230
247
  settings.gateReview = gateReview;
231
248
  }
249
+ settings.ccr = normalizeCcrIntegrationSettings(input.ccr);
232
250
  return settings;
233
251
  }
252
+ function normalizeCcrIntegrationSettings(input) {
253
+ const candidate = isObject(input) ? input : {};
254
+ const apiKey = typeof candidate.apiKey === "string" ? candidate.apiKey.trim() : "";
255
+ return {
256
+ version: 1,
257
+ enabled: candidate.enabled === true && apiKey.length > 0,
258
+ apiKey
259
+ };
260
+ }
234
261
  function normalizePreferences(input) {
235
262
  const candidate = isObject(input) ? input : {};
236
263
  const rawFlowPauseAlerts = "flowPauseAlerts" in candidate
@@ -304,6 +331,9 @@ function normalizeClaudeModel(input, fallback) {
304
331
  if (typeof input !== "string") {
305
332
  return fallback;
306
333
  }
334
+ if (input === CCR_GPT_SESSION_MODEL) {
335
+ return input;
336
+ }
307
337
  const model = CLAUDE_MODEL_OPTIONS.find((option) => option.value === input);
308
338
  return model?.value ?? fallback;
309
339
  }
@@ -0,0 +1,194 @@
1
+ import { CCR_GATEWAY_BASE_URL, CCR_GPT_MODEL_ID, createSessionModelOptions, isCcrSessionModel } from "../../shared/types/session.js";
2
+ import { VcmError } from "../errors.js";
3
+ const DEFAULT_CACHE_TTL_MS = 10_000;
4
+ export function createCcrIntegrationService(deps) {
5
+ const now = deps.now ?? (() => new Date());
6
+ const cacheTtlMs = deps.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
7
+ let cachedProbe;
8
+ let inFlight;
9
+ async function probe(force = false) {
10
+ const settings = await deps.settings.getCcrIntegrationSettings();
11
+ if (!settings.apiKey) {
12
+ throw missingApiKeyError();
13
+ }
14
+ const currentTime = now();
15
+ if (!force && cachedProbe && currentTime.getTime() - cachedProbe.checkedAtMs <= cacheTtlMs) {
16
+ return cachedProbe;
17
+ }
18
+ if (inFlight) {
19
+ return inFlight;
20
+ }
21
+ inFlight = deps.gateway.probe(settings.apiKey).then((result) => {
22
+ const checkedAtDate = now();
23
+ cachedProbe = {
24
+ result,
25
+ checkedAt: checkedAtDate.toISOString(),
26
+ checkedAtMs: checkedAtDate.getTime()
27
+ };
28
+ return cachedProbe;
29
+ }).finally(() => {
30
+ inFlight = undefined;
31
+ });
32
+ return inFlight;
33
+ }
34
+ async function buildStatus(options = {}) {
35
+ const settings = await deps.settings.getCcrIntegrationSettings();
36
+ if (!settings.enabled) {
37
+ return createStatus({
38
+ enabled: false,
39
+ apiKeyConfigured: Boolean(settings.apiKey),
40
+ connectionState: "disabled",
41
+ modelAvailable: false,
42
+ error: settings.apiKey ? undefined : "Save a CCR API key before enabling CCR GPT models."
43
+ });
44
+ }
45
+ if (!cachedProbe && options.refreshIfMissing) {
46
+ await probe();
47
+ }
48
+ if (!cachedProbe) {
49
+ return createStatus({
50
+ enabled: true,
51
+ apiKeyConfigured: true,
52
+ connectionState: "checking",
53
+ modelAvailable: false
54
+ });
55
+ }
56
+ return createStatus({
57
+ enabled: true,
58
+ apiKeyConfigured: true,
59
+ connectionState: cachedProbe.result.connectionState,
60
+ modelAvailable: cachedProbe.result.modelAvailable,
61
+ checkedAt: cachedProbe.checkedAt,
62
+ error: cachedProbe.result.error
63
+ });
64
+ }
65
+ return {
66
+ async initialize() {
67
+ const settings = await deps.settings.getCcrIntegrationSettings();
68
+ if (settings.enabled) {
69
+ await probe(true);
70
+ }
71
+ },
72
+ async getStatus() {
73
+ return buildStatus({ refreshIfMissing: true });
74
+ },
75
+ async updateSettings(input) {
76
+ if (input.apiKey !== undefined && typeof input.apiKey !== "string") {
77
+ throw invalidSettingsError("CCR API key must be a string.");
78
+ }
79
+ if (input.enabled !== undefined && typeof input.enabled !== "boolean") {
80
+ throw invalidSettingsError("CCR enabled state must be a boolean.");
81
+ }
82
+ if (input.clearApiKey !== undefined && typeof input.clearApiKey !== "boolean") {
83
+ throw invalidSettingsError("CCR clear-key state must be a boolean.");
84
+ }
85
+ const current = await deps.settings.getCcrIntegrationSettings();
86
+ const clearApiKey = input.clearApiKey === true;
87
+ const nextApiKey = clearApiKey
88
+ ? ""
89
+ : input.apiKey === undefined
90
+ ? current.apiKey
91
+ : input.apiKey.trim();
92
+ const nextEnabled = clearApiKey ? false : input.enabled ?? current.enabled;
93
+ if (nextEnabled && !nextApiKey) {
94
+ throw missingApiKeyError();
95
+ }
96
+ await deps.settings.updateCcrIntegrationSettings({
97
+ enabled: nextEnabled,
98
+ apiKey: nextApiKey
99
+ });
100
+ if (clearApiKey || !nextEnabled) {
101
+ cachedProbe = undefined;
102
+ return buildStatus();
103
+ }
104
+ if (input.apiKey !== undefined || input.enabled === true) {
105
+ cachedProbe = undefined;
106
+ await probe(true);
107
+ }
108
+ return buildStatus();
109
+ },
110
+ async checkConnection() {
111
+ const settings = await deps.settings.getCcrIntegrationSettings();
112
+ if (!settings.enabled) {
113
+ throw new VcmError({
114
+ code: "CCR_DISABLED",
115
+ message: "CCR GPT models are disabled.",
116
+ statusCode: 409,
117
+ hint: "Save the CCR API key and enable CCR GPT models before checking the connection."
118
+ });
119
+ }
120
+ await probe(true);
121
+ return buildStatus();
122
+ },
123
+ async getLaunchEnvironment(model) {
124
+ if (!isCcrSessionModel(model)) {
125
+ return {};
126
+ }
127
+ const settings = await deps.settings.getCcrIntegrationSettings();
128
+ if (!settings.enabled) {
129
+ throw new VcmError({
130
+ code: "CCR_DISABLED",
131
+ message: "CCR GPT models are disabled.",
132
+ statusCode: 409,
133
+ hint: "Enable CCR GPT models in VCM Settings before starting this session."
134
+ });
135
+ }
136
+ const checked = await probe();
137
+ if (checked.result.connectionState !== "available" || !checked.result.modelAvailable) {
138
+ throw new VcmError({
139
+ code: "CCR_MODEL_UNAVAILABLE",
140
+ message: checked.result.error ?? `CCR model ${CCR_GPT_MODEL_ID} is unavailable.`,
141
+ statusCode: 409,
142
+ hint: "Check that host CCR is running on port 3456 and exposes GPT-5.6 Sol."
143
+ });
144
+ }
145
+ const noProxy = mergeNoProxy(deps.baseEnv?.NO_PROXY ?? deps.baseEnv?.no_proxy ?? process.env.NO_PROXY ?? process.env.no_proxy, "host.docker.internal");
146
+ return {
147
+ ANTHROPIC_BASE_URL: CCR_GATEWAY_BASE_URL,
148
+ ANTHROPIC_API_BASE_URL: CCR_GATEWAY_BASE_URL,
149
+ CLAUDE_AGENT_API_BASE_URL: CCR_GATEWAY_BASE_URL,
150
+ ANTHROPIC_AUTH_TOKEN: settings.apiKey,
151
+ ANTHROPIC_MODEL: CCR_GPT_MODEL_ID,
152
+ CCR_CLAUDE_CODE_MODEL: CCR_GPT_MODEL_ID,
153
+ CODEXL_CLAUDE_CODE_MODEL: CCR_GPT_MODEL_ID,
154
+ ANTHROPIC_SMALL_FAST_MODEL: CCR_GPT_MODEL_ID,
155
+ CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1",
156
+ NO_PROXY: noProxy,
157
+ no_proxy: noProxy
158
+ };
159
+ }
160
+ };
161
+ }
162
+ function createStatus(input) {
163
+ const reason = input.error
164
+ ?? (input.enabled ? "CCR GPT-5.6 Sol is unavailable." : "Enable CCR GPT models in Settings.");
165
+ return {
166
+ ...input,
167
+ modelOptions: createSessionModelOptions(input.enabled && input.modelAvailable, reason)
168
+ };
169
+ }
170
+ function missingApiKeyError() {
171
+ return new VcmError({
172
+ code: "CCR_API_KEY_MISSING",
173
+ message: "CCR API key is not configured.",
174
+ statusCode: 400,
175
+ hint: "Save the CCR API key before enabling CCR GPT models."
176
+ });
177
+ }
178
+ function invalidSettingsError(message) {
179
+ return new VcmError({
180
+ code: "CCR_SETTINGS_INVALID",
181
+ message,
182
+ statusCode: 400
183
+ });
184
+ }
185
+ export function mergeNoProxy(current, host) {
186
+ const values = (current ?? "")
187
+ .split(",")
188
+ .map((value) => value.trim())
189
+ .filter(Boolean);
190
+ if (!values.includes(host)) {
191
+ values.push(host);
192
+ }
193
+ return values.join(",");
194
+ }