Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import io.agentscope.core.formatter.anthropic.AnthropicChatFormatter;
import io.agentscope.core.formatter.anthropic.AnthropicResponseParser;
import io.agentscope.core.message.Msg;
import io.agentscope.core.model.transport.ProxyConfig;
import java.net.Proxy;
import java.time.Instant;
import java.util.List;
import org.slf4j.Logger;
Expand Down Expand Up @@ -74,14 +76,16 @@ public class AnthropicChatModel extends ChatModelBase {
* @param defaultOptions default generation options
* @param formatter the message formatter to use (null for default
* Anthropic formatter)
* @param proxyConfig the proxy configuration (null for no proxy)
*/
public AnthropicChatModel(
String baseUrl,
String apiKey,
String modelName,
boolean streamEnabled,
GenerateOptions defaultOptions,
AnthropicBaseFormatter formatter) {
AnthropicBaseFormatter formatter,
ProxyConfig proxyConfig) {
this.baseUrl = baseUrl;
this.apiKey = apiKey;
this.modelName = modelName;
Expand All @@ -101,6 +105,12 @@ public AnthropicChatModel(
clientBuilder.baseUrl(baseUrl);
}

// Configure proxy if provided
if (proxyConfig != null) {
Proxy proxy = proxyConfig.toJavaProxy();
clientBuilder.proxy(proxy);
}

this.client = clientBuilder.build();
}

Expand Down Expand Up @@ -237,6 +247,7 @@ public static class Builder {
private boolean streamEnabled = true;
private GenerateOptions defaultOptions;
private AnthropicBaseFormatter formatter;
private ProxyConfig proxyConfig;

/**
* Sets the base URL for the Anthropic API.
Expand Down Expand Up @@ -304,14 +315,41 @@ public Builder formatter(AnthropicBaseFormatter formatter) {
return this;
}

/**
* Sets the proxy configuration for HTTP traffic.
*
* <p><b>Interaction with other configuration:</b>
* AnthropicChatModel constructs the HTTP client internally, so {@code proxy()} is
* the <i>only</i> way to configure proxy for this model. Simply call this method
* alongside {@link #apiKey(String)}, {@link #baseUrl(String)}, etc., and the proxy
* will be applied when the client is built.
*
* <p><b>Note:</b> The Anthropic SDK is built on OkHttp, which fully supports
* {@link ProxyConfig#toJavaProxy()} conversion. Proxy authentication credentials
* are applied via the SDK's built-in proxy handling.
*
* @param proxyConfig the proxy configuration (see {@link ProxyConfig})
* @return this builder
*/
public Builder proxy(ProxyConfig proxyConfig) {
this.proxyConfig = proxyConfig;
return this;
}

/**
* Builds the AnthropicChatModel instance.
*
* @return a new AnthropicChatModel
*/
public AnthropicChatModel build() {
return new AnthropicChatModel(
baseUrl, apiKey, modelName, streamEnabled, defaultOptions, formatter);
baseUrl,
apiKey,
modelName,
streamEnabled,
defaultOptions,
formatter,
proxyConfig);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
import io.agentscope.core.formatter.dashscope.dto.DashScopeResponse;
import io.agentscope.core.message.Msg;
import io.agentscope.core.model.transport.HttpTransport;
import io.agentscope.core.model.transport.HttpTransportConfig;
import io.agentscope.core.model.transport.HttpTransportFactory;
import io.agentscope.core.model.transport.OkHttpTransport;
import io.agentscope.core.model.transport.ProxyConfig;
import java.time.Instant;
import java.util.List;
import org.slf4j.Logger;
Expand Down Expand Up @@ -366,6 +369,7 @@ public static class Builder {
private Formatter<DashScopeMessage, DashScopeResponse, DashScopeRequest> formatter;
private HttpTransport httpTransport;
private boolean enableEncrypt = false;
private ProxyConfig proxyConfig;

/**
* Sets the API key for DashScope authentication.
Expand Down Expand Up @@ -503,6 +507,21 @@ public Builder formatter(
* .build();
* }</pre>
*
* <p><b>Note on proxy configuration:</b> Because {@code httpTransport} is a
* fully-constructed HTTP client instance, its configuration (including proxy)
* cannot be modified after creation. If you need to configure a proxy, either:
*
* <ul>
* <li>Use {@link #proxy(ProxyConfig)} without calling {@code httpTransport()},
* for simple proxy-only customization.
* <li>Configure the proxy directly within the transport's
* {@link HttpTransportConfig} when building the transport instance.
* </ul>
*
* <p>If both {@code httpTransport()} and {@code proxy()} are called,
* {@code httpTransport()} takes full precedence and {@code proxy()} is ignored
* (a warning is logged at build time).
*
* @param httpTransport the HTTP transport (null for default from factory)
* @return this builder instance
*/
Expand All @@ -511,6 +530,36 @@ public Builder httpTransport(HttpTransport httpTransport) {
return this;
}

/**
* Sets the proxy configuration for HTTP traffic.
*
* <p><b>Interaction with {@link #httpTransport(HttpTransport)}:</b>
* Because {@code httpTransport} is a fully-constructed HTTP client instance,
* its configuration cannot be modified after creation. The final transport is
* determined as follows:
*
* <ul>
* <li>If only {@code proxy()} is called: the default {@link OkHttpTransport}
* is used with the specified proxy configuration applied.
* <li>If only {@code httpTransport()} is called: the provided transport is
* used as-is (proxy must be configured within the transport's own
* {@link HttpTransportConfig}).
* <li>If <i>both</i> are called: {@code httpTransport()} takes full precedence.
* The {@code proxy()} setting is <b>ignored</b> and a warning is logged.
* To configure proxy with a custom transport, set it within the transport's
* {@link HttpTransportConfig} directly.
* <li>If neither is called: the default transport from {@link HttpTransportFactory}
* is used (no proxy).
* </ul>
*
* @param proxyConfig the proxy configuration (see {@link ProxyConfig})
* @return this builder instance
*/
public Builder proxy(ProxyConfig proxyConfig) {
this.proxyConfig = proxyConfig;
return this;
}

/**
* Sets whether encryption should be enabled.
*
Expand Down Expand Up @@ -550,6 +599,11 @@ public Builder enableEncrypt(boolean enableEncrypt) {
* <p>If encryption is enabled, this method will automatically fetch the public key
* from DashScope API. If the fetch fails, an exception will be thrown.
*
* <p><b>Proxy resolution:</b> If both {@link #proxy(ProxyConfig)} and
* {@link #httpTransport(HttpTransport)} are set, {@code httpTransport()} takes
* precedence and a warning is logged. Otherwise, the proxy is applied to a default
* transport, or the factory default is used.
*
* @return configured DashScopeChatModel instance
* @throws DashScopeHttpClient.DashScopeHttpException if encryption is enabled and
* public key fetching fails
Expand All @@ -561,9 +615,9 @@ public DashScopeChatModel build() {
String finalPublicKeyId = null;
String finalPublicKey = null;

HttpTransport transport = resolveTransport();

if (enableEncrypt) {
HttpTransport transport =
httpTransport != null ? httpTransport : HttpTransportFactory.getDefault();
DashScopeHttpClient.PublicKeyResult publicKeyResult =
DashScopeHttpClient.fetchPublicKey(apiKey, baseUrl, transport);
finalPublicKeyId = publicKeyResult.publicKeyId();
Expand All @@ -580,9 +634,44 @@ public DashScopeChatModel build() {
effectiveOptions,
baseUrl,
formatter,
httpTransport,
transport,
finalPublicKeyId,
finalPublicKey);
}

/**
* Resolves the final HttpTransport to use.
*
* <p>If {@code httpTransport()} is set, it takes full precedence and
* {@code proxyConfig} is ignored (with a warning logged). Otherwise,
* if {@code proxy()} is set, a default transport with the proxy applied
* is created. If neither is set, the factory default is used.
*
* @return the resolved HttpTransport
*/
private HttpTransport resolveTransport() {
if (httpTransport != null) {
if (proxyConfig != null) {
log.warn(
"DashScopeChatModel: both proxy() and httpTransport() are set. "
+ "httpTransport() takes precedence, proxy() is ignored. "
+ "To configure proxy, use one of the following:\n"
+ " 1. Simple: only call proxy() without httpTransport()\n"
+ " 2. Advanced: set proxy in HttpTransportConfig when "
+ "building a custom HttpTransport");
}
return httpTransport;
}

if (proxyConfig != null) {
// Only proxy() called → use default transport with proxy
return OkHttpTransport.builder()
.config(HttpTransportConfig.builder().proxy(proxyConfig).build())
.build();
}

// Neither called → use factory default
return HttpTransportFactory.getDefault();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
import com.google.genai.types.GenerateContentConfig;
import com.google.genai.types.GenerateContentResponse;
import com.google.genai.types.HttpOptions;
import com.google.genai.types.ProxyOptions;
import io.agentscope.core.formatter.Formatter;
import io.agentscope.core.formatter.gemini.GeminiChatFormatter;
import io.agentscope.core.message.Msg;
import io.agentscope.core.model.transport.ProxyConfig;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -292,6 +294,7 @@ public static class Builder {
private GenerateOptions defaultOptions;
private Formatter<Content, GenerateContentResponse, GenerateContentConfig.Builder>
formatter;
private ProxyConfig proxyConfig;

/**
* Sets the API key (for Gemini API).
Expand Down Expand Up @@ -416,12 +419,57 @@ public Builder formatter(
return this;
}

/**
* Sets the proxy configuration for HTTP traffic.
*
* <p><b>Interaction with {@link #clientOptions(ClientOptions)}:</b>
* This method performs a <i>smart merge</i> rather than simple overwriting.
* The final {@code ClientOptions} passed to the Google SDK client is determined as follows:
*
* <ul>
* <li>If only {@code proxy()} is called: a new {@code ClientOptions} is created
* containing only the proxy settings.
* <li>If only {@code clientOptions()} is called: the provided options are used as-is.
* <li>If <i>both</i> are called:
* <ul>
* <li>If the provided {@code ClientOptions} does <b>not</b> already contain
* proxy settings ({@code clientOptions.getProxyOptions().isEmpty()}), the
* proxy from {@code proxy()} is <b>merged into</b> the existing options
* (preserving connection pool, etc.).
* <li>If the provided {@code ClientOptions} <b>already contains</b> proxy settings,
* those take precedence. A warning is logged, and the {@code proxy()} setting
* is silently ignored to avoid conflicting configurations.
* </ul>
* <li>If neither is called: no proxy configuration is applied.
* </ul>
*
* <p><b>Note:</b> The {@code nonProxyHosts} field from {@link ProxyConfig} is <b>not
* supported</b> by the Google GenAI SDK. When converting, this field will be lost.
* If host exclusion is required, consider setting {@code http.nonProxyHosts} as a
* JVM system property.
*
* @param proxyConfig the proxy configuration (see {@link ProxyConfig})
* @return this builder
*/
public Builder proxy(ProxyConfig proxyConfig) {
this.proxyConfig = proxyConfig;
return this;
}

/**
* Builds the GeminiChatModel instance.
*
* <p><b>Proxy smart merge:</b> If both {@link #proxy(ProxyConfig)} and
* {@link #clientOptions(ClientOptions)} are set, this method performs a smart merge:
* if the provided {@code ClientOptions} does not already contain proxy settings,
* the proxy from {@code proxy()} is merged into it (preserving connection pool, etc.).
* If {@code ClientOptions} already has proxy configured, it takes precedence and
* a warning is logged.
*
* @return a new GeminiChatModel
*/
public GeminiChatModel build() {
ClientOptions resolvedClientOptions = resolveClientOptions();
return new GeminiChatModel(
apiKey,
modelName,
Expand All @@ -431,9 +479,70 @@ public GeminiChatModel build() {
vertexAI,
httpOptions,
credentials,
clientOptions,
resolvedClientOptions,
defaultOptions,
formatter);
}

/**
* Resolves the final ClientOptions by merging proxy() and clientOptions().
*
* @return the resolved ClientOptions
*/
private ClientOptions resolveClientOptions() {
if (proxyConfig == null) {
// No proxy set, return clientOptions as-is
return clientOptions;
}

ProxyOptions googleProxy = toGoogleProxy(proxyConfig);

if (clientOptions == null) {
// Only proxy() called, create new ClientOptions with proxy
return ClientOptions.builder().proxyOptions(googleProxy).build();
}

if (clientOptions.proxyOptions().isEmpty()) {
// clientOptions set but no proxy configured → merge proxy into it
return clientOptions.toBuilder().proxyOptions(googleProxy).build();
}

// Both have proxy settings → clientOptions takes precedence, warn user
log.warn(
"GeminiChatModel: both proxy() and clientOptions.proxyOptions() are set. "
+ "clientOptions.proxyOptions() takes precedence, proxy() is ignored.");
return clientOptions;
}

/**
* Converts agentscope ProxyConfig to Google SDK ProxyOptions.
*
* <p><b>Note:</b> The {@code nonProxyHosts} field from ProxyConfig is not supported
* by the Google GenAI SDK and will be lost during conversion.
*
* @param config the agentscope proxy configuration
* @return the Google SDK ProxyOptions
*/
private static ProxyOptions toGoogleProxy(ProxyConfig config) {
ProxyOptions.Builder builder =
ProxyOptions.builder().host(config.getHost()).port(config.getPort());

// Map proxy type using ProxyType.Known enum
switch (config.getType()) {
case SOCKS4:
case SOCKS5:
builder.type(com.google.genai.types.ProxyType.Known.SOCKS);
break;
default:
builder.type(com.google.genai.types.ProxyType.Known.HTTP);
}

// Add authentication if present
if (config.hasAuthentication()) {
builder.username(config.getUsername()).password(config.getPassword());
}

return builder.build();
}
}
}
Loading
Loading