Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- Fixed "OpenAI-compatible API" connection type being reset to "Claude-compatible API" on the Default profile (https://github.com/nbplugins/NetbeansPluginClaudeCodeGui/issues/191)
- Improved the IDE log (messages.log): per-request and per-click diagnostics (tab label polling, PreToolUse hooks with file contents, diff tabs, choice menus, permission prompts) are now written only when debug logging is enabled, so the log is no longer flooded and no longer contains edited code

# 1.3.28 (2026-10-07)
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.nbplugins</groupId>
<artifactId>netbeans-plugin-claude-code-gui</artifactId>
<version>1.3.53-SNAPSHOT</version>
<version>1.3.56-SNAPSHOT</version>
<packaging>nbm</packaging>
<name>Netbeans Plugin Claude Code GUI</name>
<description>NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public PtyProcess start(String workingDir, ClaudeProfile profile, String extraCl

// OpenAI proxy: register session and inject ANTHROPIC_BASE_URL pointing to internal proxy
if (profile != null
&& profile.computeConnectionType() == ClaudeProfile.ConnectionType.OPENAI_PROXY) {
&& profile.getConnectionType() == ClaudeProfile.ConnectionType.OPENAI_PROXY) {
ClaudeCodeStatusService mcpSvc = Lookup.getDefault().lookup(ClaudeCodeStatusService.class);
if (mcpSvc != null && mcpSvc.isServerRunning()) {
String uuid = UUID.randomUUID().toString();
Expand Down Expand Up @@ -191,7 +191,7 @@ public PtyProcess start(String workingDir, ClaudeProfile profile, String extraCl
// ChatGPT subscription: pre-emptively refresh the OAuth token, register the
// session, and inject ANTHROPIC_BASE_URL pointing to the internal Codex proxy.
if (profile != null
&& profile.computeConnectionType() == ClaudeProfile.ConnectionType.OPENAI_SUBSCRIPTION) {
&& profile.getConnectionType() == ClaudeProfile.ConnectionType.OPENAI_SUBSCRIPTION) {
ClaudeCodeStatusService mcpSvc = Lookup.getDefault().lookup(ClaudeCodeStatusService.class);
if (mcpSvc != null && mcpSvc.isServerRunning()) {
try {
Expand Down Expand Up @@ -219,9 +219,9 @@ public PtyProcess start(String workingDir, ClaudeProfile profile, String extraCl
}

boolean apiKeyHelper = profile != null && !profile.getApiKey().isBlank() && profile.getBaseUrl().isBlank()
&& profile.computeConnectionType() != ClaudeProfile.ConnectionType.OPENAI_PROXY
&& profile.computeConnectionType() != ClaudeProfile.ConnectionType.OPENAI_SUBSCRIPTION;
LOG.info("Starting Claude: profile=" + (profile != null ? profile.getName() + " (" + profile.computeConnectionType() + ")" : "Default")
&& profile.getConnectionType() != ClaudeProfile.ConnectionType.OPENAI_PROXY
&& profile.getConnectionType() != ClaudeProfile.ConnectionType.OPENAI_SUBSCRIPTION;
LOG.info("Starting Claude: profile=" + (profile != null ? profile.getName() + " (" + profile.getConnectionType() + ")" : "Default")
+ ", apiKeyHelper=" + (apiKeyHelper ? "SET" : "NOT SET")
+ ", ANTHROPIC_AUTH_TOKEN=" + (!env.getOrDefault("ANTHROPIC_AUTH_TOKEN", "").isBlank() ? "SET" : "NOT SET")
+ ", ANTHROPIC_BASE_URL=" + env.getOrDefault("ANTHROPIC_BASE_URL", "(not set)")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.nbplugins.claudecodegui.settings;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSetter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -17,9 +18,13 @@
* programmatically.
*
* <h2>Connection type</h2>
* {@link ConnectionType} is <em>not</em> stored — it is derived from the
* credential fields by {@link #computeConnectionType()}:
* The {@link ConnectionType} is stored explicitly (see {@link #getConnectionType()}).
* Settings written by older plugin versions have no stored type (only the legacy
* {@code openaiProxy} / {@code openaiSubscription} flags and the credential fields);
* for them the type is derived on read:
* <ul>
* <li>{@code openaiSubscription} flag → {@link ConnectionType#OPENAI_SUBSCRIPTION}</li>
* <li>{@code openaiProxy} flag → {@link ConnectionType#OPENAI_PROXY}</li>
* <li>non-blank {@code apiKey} + non-blank {@code baseUrl} → {@link ConnectionType#OTHER_API}</li>
* <li>non-blank {@code apiKey} → {@link ConnectionType#CLAUDE_API}</li>
* <li>non-blank {@code token} → {@link ConnectionType#SUBSCRIPTION}</li>
Expand All @@ -40,7 +45,8 @@ public final class ClaudeProfile {

/**
* Describes how a profile authenticates with Claude / the AI backend.
* Derived at runtime from credential fields; never persisted.
* Persisted by name; for settings written by older versions derived from
* the legacy flags and credential fields.
*/
public enum ConnectionType {
/** Claude manages its own authentication (OAuth flow). No env vars injected. */
Expand Down Expand Up @@ -189,20 +195,23 @@ public enum ProxyMode {
private String storageDir;

/**
* When {@code true}, this profile uses the internal OpenAI-compatible proxy.
* {@link #computeConnectionType()} returns {@link ConnectionType#OPENAI_PROXY}.
* {@link #baseUrl} and {@link #apiKey} hold the OpenAI endpoint URL and API key.
*/
private boolean openaiProxy;

/**
* When {@code true}, this profile uses a ChatGPT Plus/Pro/Team subscription
* (OAuth login) routed through the internal proxy to OpenAI's Codex backend.
* {@link #computeConnectionType()} returns {@link ConnectionType#OPENAI_SUBSCRIPTION}.
* The {@code chatgpt*} fields hold the OAuth token set obtained via
* The connection type chosen for this profile. {@code null} only for settings written
* by older plugin versions, which did not store it; {@link #getConnectionType()} then
* derives it from {@link #legacyOpenaiProxy}, {@link #legacyOpenaiSubscription} and the
* credential fields.
*
* <p>{@link ConnectionType#OPENAI_PROXY}: {@link #baseUrl} and {@link #apiKey} hold the
* OpenAI endpoint URL and API key. {@link ConnectionType#OPENAI_SUBSCRIPTION}: the
* {@code chatgpt*} fields hold the OAuth token set obtained via
* {@code io.github.nbplugins.claudecodegui.chatgptauth.ChatGptOAuthClient}.
*/
private boolean openaiSubscription;
private ConnectionType connectionType;
Comment thread
oleg68 marked this conversation as resolved.

/** Legacy flag from older settings ({@code openaiProxy}); read-only, never written. */
private boolean legacyOpenaiProxy;

/** Legacy flag from older settings ({@code openaiSubscription}); read-only, never written. */
private boolean legacyOpenaiSubscription;

/**
* ChatGPT OAuth access token. Short-lived (hours); refreshed automatically
Expand Down Expand Up @@ -324,16 +333,38 @@ public boolean isDefault() {
}

/**
* Computes the {@link ConnectionType} from the stored credential fields.
* This value is intentionally not persisted.
* Returns the connection type of this profile: the stored one, or, for settings written
* by older plugin versions that did not store it, the type derived from the legacy flags
* and credential fields.
*
* @return the connection type inferred from current field values
* @return the connection type; never {@code null}
*/
public ConnectionType computeConnectionType() {
if (openaiSubscription) {
public ConnectionType getConnectionType() {
return connectionType != null ? connectionType : deriveLegacyConnectionType();
}

/**
* Sets the connection type. {@code null} means "derive from legacy fields".
*
* @param connectionType the connection type
*/
public void setConnectionType(ConnectionType connectionType) {
this.connectionType = connectionType;
}

/** Legacy flag accepted when reading settings written by older plugin versions. */
@JsonSetter("openaiProxy")
private void readLegacyOpenaiProxy(boolean value) { this.legacyOpenaiProxy = value; }

/** Legacy flag accepted when reading settings written by older plugin versions. */
@JsonSetter("openaiSubscription")
private void readLegacyOpenaiSubscription(boolean value) { this.legacyOpenaiSubscription = value; }

private ConnectionType deriveLegacyConnectionType() {
if (legacyOpenaiSubscription) {
return ConnectionType.OPENAI_SUBSCRIPTION;
}
if (openaiProxy) {
if (legacyOpenaiProxy) {
return ConnectionType.OPENAI_PROXY;
}
if (!isBlank(apiKey)) {
Expand All @@ -351,7 +382,7 @@ public ConnectionType computeConnectionType() {
*
* <p>Variables are added in this order (later entries win):
* <ol>
* <li>Auth variables (based on {@link #computeConnectionType()})</li>
* <li>Auth variables (based on {@link #getConnectionType()})</li>
* <li>Proxy variables (based on {@link #proxyMode})</li>
* <li>{@link #extraEnvVars} (in list order)</li>
* </ol>
Expand All @@ -366,7 +397,7 @@ public Map<String, String> toEnvVars() {
Map<String, String> env = new HashMap<>();

// Auth
switch (computeConnectionType()) {
switch (getConnectionType()) {
case SUBSCRIPTION -> env.put("CLAUDE_CODE_OAUTH_TOKEN", blankToEmpty(token));
case CLAUDE_API -> { /* API key is written to settings.local.json as apiKeyHelper */ }
case OTHER_API -> {
Expand Down Expand Up @@ -695,7 +726,7 @@ public void setModelOrder(List<String> ids) {
* potentially harmful) for Anthropic's own connections.
*/
private boolean modelAliasesApply() {
ConnectionType ct = computeConnectionType();
ConnectionType ct = getConnectionType();
return ct == ConnectionType.OTHER_API || ct == ConnectionType.OPENAI_PROXY
|| ct == ConnectionType.OPENAI_SUBSCRIPTION;
}
Expand Down Expand Up @@ -788,18 +819,6 @@ public ClaudeProfile withStorageDir(String storageDir) {
return this;
}

/** Returns {@code true} if this profile uses the internal OpenAI-compatible proxy. */
public boolean isOpenaiProxy() { return openaiProxy; }

/** Sets whether this profile uses the internal OpenAI-compatible proxy. */
public void setOpenaiProxy(boolean openaiProxy) { this.openaiProxy = openaiProxy; }

/** Returns {@code true} if this profile uses a ChatGPT subscription (OAuth) via the internal proxy. */
public boolean isOpenaiSubscription() { return openaiSubscription; }

/** Sets whether this profile uses a ChatGPT subscription (OAuth) via the internal proxy. */
public void setOpenaiSubscription(boolean openaiSubscription) { this.openaiSubscription = openaiSubscription; }

/** Returns the ChatGPT OAuth access token; blank when not signed in. */
public String getChatgptAccessToken() { return blankToEmpty(chatgptAccessToken); }

Expand Down Expand Up @@ -843,7 +862,7 @@ public boolean isSignedIntoChatgpt() {

/**
* Clears all ChatGPT OAuth token fields (access token, refresh token,
* account id, expiry, email), leaving {@link #openaiSubscription} untouched.
* account id, expiry, email), leaving the {@link #getConnectionType() connection type} untouched.
* Used by the "Sign out" action and when switching away from the
* {@link ConnectionType#OPENAI_SUBSCRIPTION} connection type.
*/
Expand All @@ -862,7 +881,7 @@ public void clearChatgptAuth() {
@Override
public String toString() {
return "ClaudeProfile{id='" + getId() + "', name='" + getName()
+ "', connectionType=" + computeConnectionType()
+ "', connectionType=" + getConnectionType()
+ ", proxyMode=" + getProxyMode() + "}";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public final class ClaudeProfileStore {
private static final String KEY_DEFAULT_PROFILE = "defaultProfile";

private static final ObjectMapper MAPPER = new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
private static final TypeReference<List<ClaudeProfile>> LIST_TYPE =
new TypeReference<>() {};

Expand All @@ -64,27 +65,7 @@ public static List<ClaudeProfile> getProfiles() {
.get(KEY_DEFAULT_PROFILE, null);
if (defaultJson != null) {
try {
ClaudeProfile stored = MAPPER.readValue(defaultJson, ClaudeProfile.class);
def.setToken(stored.getToken());
def.setApiKey(stored.getApiKey());
def.setBaseUrl(stored.getBaseUrl());
def.setProxyMode(stored.getProxyMode());
def.setHttpProxy(stored.getHttpProxy());
def.setHttpsProxy(stored.getHttpsProxy());
def.setNoProxy(stored.getNoProxy());
def.setExtraEnvVars(stored.getExtraEnvVars());
def.setExtraCliArgs(stored.getExtraCliArgs());
def.setModelAliases(stored.getModelAliases());
def.setCustomModels(stored.getCustomModels());
def.setModelOrder(stored.getModelOrder());
def.setExplicitPromptCachingModels(stored.getExplicitPromptCachingModels());
def.setStorageDir(stored.getStorageDir());
def.setOpenaiSubscription(stored.isOpenaiSubscription());
def.setChatgptAccessToken(stored.getChatgptAccessToken());
def.setChatgptRefreshToken(stored.getChatgptRefreshToken());
def.setChatgptAccountId(stored.getChatgptAccountId());
def.setChatgptTokenExpiresAt(stored.getChatgptTokenExpiresAt());
def.setChatgptEmail(stored.getChatgptEmail());
def = parseDefaultProfile(defaultJson);
} catch (Exception e) {
LOG.warning("Could not parse stored Default profile JSON, using defaults. Cause: " + e);
def.setExtraCliArgs(ClaudeCodePreferences.getDefaultExtraCliArgs());
Expand All @@ -108,6 +89,21 @@ public static List<ClaudeProfile> getProfiles() {
return result;
}

/**
* Parses a stored Default profile. The whole profile is deserialised (so no field can be
* forgotten), then the identity is forced to the built-in Default.
*
* @param json the stored Default profile JSON, in the current or an older plugin format
* @return the Default profile
* @throws java.io.IOException if the JSON cannot be parsed
*/
static ClaudeProfile parseDefaultProfile(String json) throws java.io.IOException {
ClaudeProfile def = MAPPER.readValue(json, ClaudeProfile.class);
def.setId(ClaudeProfile.DEFAULT_ID);
def.setName(ClaudeProfile.DEFAULT_NAME);
return def;
}

/**
* Returns the preference-backed Default profile.
*
Expand Down
Loading
Loading