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
51 changes: 19 additions & 32 deletions CodenameOne/src/com/codename1/ai/AnthropicClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,53 +24,40 @@

import com.codename1.util.AsyncResource;

/// Anthropic /v1/messages client. Wire format differs from OpenAI in
/// three important ways: system messages live in a top-level `system`
/// string rather than a role; image parts use `{type:"image", source:
/// {type:"base64", media_type, data}}`; tool calls stream argument
/// JSON via `input_json_delta` events.
/// Anthropic Messages client.
///
/// This is currently a scaffold -- the full request/response mapping
/// is tracked as a follow-up. The class compiles and registers under
/// `LlmClient.anthropic(...)` so app code using the API can be built;
/// runtime calls throw a clear `UnsupportedOperationException`.
class AnthropicClient extends LlmClient {
private final String apiKey;
/// Talks to `https://api.anthropic.com/v1/chat/completions`, the
/// official OpenAI-compatible Messages shim. The wire format on that
/// endpoint is the same `/v1/chat/completions` shape that OpenAI and
/// every OpenAI-compatible provider speaks, so this client inherits the
/// full streaming, tool-call, response-format and image-attachment
/// implementation from `OpenAiClient`. Only the provider name, default
/// model, and the embeddings shape (Anthropic does not publish a
/// first-party embeddings endpoint) are overridden here.
///
/// Authentication uses `Authorization: Bearer sk-ant-...` -- identical
/// header layout to OpenAI -- which is why the inherited request
/// configuration works without modification.
class AnthropicClient extends OpenAiClient {

AnthropicClient(String apiKey, String baseUrl) {
super(baseUrl);
this.apiKey = apiKey;
super(apiKey, baseUrl);
setDefaultModel("claude-sonnet-4-5");
}

@Override
public String getProvider() {
return "anthropic";
}

@Override
public AsyncResource<ChatResponse> chat(ChatRequest req) {
AsyncResource<ChatResponse> r = new AsyncResource<ChatResponse>();
r.error(new UnsupportedOperationException(
"AnthropicClient is not yet implemented in this release. "
+ "Use LlmClient.openai(...) or run the model behind an OpenAI-compatible proxy."));
return r;
}

@Override
public AsyncResource<ChatResponse> chatStream(ChatRequest req, StreamingListener listener) {
return chat(req);
}

@Override
public AsyncResource<EmbeddingResponse> embed(EmbeddingRequest req) {
AsyncResource<EmbeddingResponse> r = new AsyncResource<EmbeddingResponse>();
r.error(new UnsupportedOperationException(
"Anthropic does not publish a first-party embeddings endpoint. "
+ "Use a Voyage AI key via LlmClient.localOpenAiCompatible(\"https://api.voyageai.com/v1\", key, model)."));
+ "Use a Voyage AI key via LlmClient.localOpenAiCompatible("
+ "\"https://api.voyageai.com/v1\", key, \"voyage-3\") or "
+ "LlmClient.openai(...) with text-embedding-3-small."));
return r;
}

String getApiKey() {
return apiKey;
}
}
59 changes: 16 additions & 43 deletions CodenameOne/src/com/codename1/ai/GeminiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,56 +22,29 @@
*/
package com.codename1.ai;

import com.codename1.util.AsyncResource;

/// Google Gemini client. The native wire format diverges from OpenAI's:
/// system messages live in `systemInstruction`, content is split into
/// `parts` with `inline_data` / `text`, tool calls arrive atomically
/// at stream end rather than fragment-by-fragment.
/// Google Gemini client.
///
/// Talks to `https://generativelanguage.googleapis.com/v1beta/openai/`,
/// Google's official OpenAI-compatible shim. The endpoint accepts the
/// standard `/chat/completions` and `/embeddings` shape -- including
/// streaming, tool calls, multi-modal image parts, and structured
/// JSON output -- so this client inherits the full
/// implementation from `OpenAiClient` and only overrides the provider
/// name and default model.
///
/// Google publishes an OpenAI-compatibility endpoint at
/// `https://generativelanguage.googleapis.com/v1beta/openai/` that
/// works with [LlmClient#localOpenAiCompatible] today; this dedicated
/// client (which handles the native shape end-to-end) is a follow-up.
class GeminiClient extends LlmClient {
private final String apiKey;
/// Authentication uses `Authorization: Bearer <gemini-api-key>`,
/// identical to the OpenAI header layout. Models are addressed by
/// their public identifiers (`gemini-2.0-flash`, `gemini-2.5-pro`,
/// `gemini-2.5-flash`, ...).
class GeminiClient extends OpenAiClient {

GeminiClient(String apiKey, String baseUrl) {
super(baseUrl);
this.apiKey = apiKey;
super(apiKey, baseUrl);
setDefaultModel("gemini-2.0-flash");
}

@Override
public String getProvider() {
return "gemini";
}

@Override
public AsyncResource<ChatResponse> chat(ChatRequest req) {
AsyncResource<ChatResponse> r = new AsyncResource<ChatResponse>();
r.error(new UnsupportedOperationException(
"GeminiClient (native) is not yet implemented in this release. "
+ "Use LlmClient.localOpenAiCompatible("
+ "\"https://generativelanguage.googleapis.com/v1beta/openai\", apiKey, model) "
+ "to reach Gemini through Google's OpenAI-compatible shim."));
return r;
}

@Override
public AsyncResource<ChatResponse> chatStream(ChatRequest req, StreamingListener listener) {
return chat(req);
}

@Override
public AsyncResource<EmbeddingResponse> embed(EmbeddingRequest req) {
AsyncResource<EmbeddingResponse> r = new AsyncResource<EmbeddingResponse>();
r.error(new UnsupportedOperationException(
"GeminiClient.embed is not yet implemented. Use the OpenAI-compatible shim "
+ "or LlmClient.openai(...) with text-embedding-3-small."));
return r;
}

String getApiKey() {
return apiKey;
}
}
15 changes: 8 additions & 7 deletions CodenameOne/src/com/codename1/ai/LlmClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,16 @@ public abstract class LlmClient {
// uses.
//
// Versions reflect the providers' production REST shapes as of
// mid-2026:
// - OpenAI Chat Completions -- /v1 (stable)
// - Anthropic Messages -- /v1 (stable)
// - Google Gemini (native) -- /v1beta (only path
// that exposes streaming generateContent and tool calls today)
// - Ollama OpenAI-compat shim -- /v1
// mid-2026. Anthropic and Gemini both publish an OpenAI-compatible
// `/chat/completions` endpoint, which is what this client targets
// -- one shared wire format, three providers.
// - OpenAI Chat Completions -- /v1
// - Anthropic Messages compat -- /v1
// - Google Gemini OpenAI compat -- /v1beta/openai
// - Ollama OpenAI-compat shim -- /v1
public static final String DEFAULT_OPENAI_URL = "https://api.openai.com/v1";
public static final String DEFAULT_ANTHROPIC_URL = "https://api.anthropic.com/v1";
public static final String DEFAULT_GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta";
public static final String DEFAULT_GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/openai";
public static final String DEFAULT_OLLAMA_URL = "http://localhost:11434/v1";

private String baseUrl;
Expand Down
Loading
Loading