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
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ notes.

## Customizing

OpenWiki supports OpenAI (with an API key or a ChatGPT login), OpenRouter, Fireworks, Baseten, NVIDIA NIM, an OpenAI-compatible provider, and Anthropic out of the box. The onboarding default is OpenAI with `gpt-5.6-terra`, and each inference provider also includes pre-defined model options plus support for custom model IDs.
OpenWiki supports OpenAI (with an API key or a ChatGPT login), OpenRouter, Fireworks, Baseten, NVIDIA NIM, an OpenAI-compatible provider, and Anthropic out of the box, plus an env-configured `anthropic-aws` provider for [Claude Platform on AWS](#claude-platform-on-aws). The onboarding default is OpenAI with `gpt-5.6-terra`, and each inference provider also includes pre-defined model options plus support for custom model IDs.

### Alternative base URLs

Expand All @@ -206,6 +206,31 @@ ANTHROPIC_API_KEY=your-key
ANTHROPIC_BASE_URL=https://your-gateway.example.com/anthropic
```

### Claude Platform on AWS

[Claude Platform on AWS](https://platform.claude.com/docs/en/build-with-claude/claude-platform-on-aws)
is a distinct, Anthropic-managed offering fronted by AWS (SigV4/API-key auth and
AWS Marketplace billing) with its own environment variables, separate from the
first-party `anthropic` provider above. OpenWiki exposes it as the dedicated
`anthropic-aws` provider. You can pick it in the interactive `openwiki --init`
setup (it prompts for the API key, AWS region, and workspace ID) or configure it
directly through environment variables, whose names mirror the official Anthropic
AWS SDK/CLI:

```bash
OPENWIKI_PROVIDER=anthropic-aws
ANTHROPIC_AWS_API_KEY=your-key # sent as the x-api-key header
ANTHROPIC_AWS_WORKSPACE_ID=wrkspc_XXXXXXXXXXXXXX # required; anthropic-workspace-id header
AWS_REGION=us-west-2 # base URL is derived from the region
```

The base URL is built from the workspace's region as
`https://aws-external-anthropic.{region}.api.aws` (`AWS_DEFAULT_REGION` is used as
a fallback). To point at a proxy or a non-standard endpoint instead, set an
explicit `ANTHROPIC_AWS_BASE_URL`, which overrides the region-derived URL. Setting
`ANTHROPIC_AWS_API_KEY` also auto-selects the `anthropic-aws` provider when
`OPENWIKI_PROVIDER` is unset.

### OpenAI-compatible endpoints

The `openai-compatible` provider targets any OpenAI-compatible chat-completions
Expand Down
48 changes: 46 additions & 2 deletions src/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ import type {
OpenWikiRunResult,
} from "./types.js";
import {
ANTHROPIC_AWS_BASE_URL_ENV_KEY,
ANTHROPIC_AWS_WORKSPACE_ID_ENV_KEY,
ANTHROPIC_BASE_URL_ENV_KEY,
AWS_REGION_ENV_KEY,
getDefaultModelId,
getProviderApiKeyEnvKey,
getProviderBaseUrlEnvKey,
Expand Down Expand Up @@ -115,6 +118,7 @@ export async function runOpenWikiAgent(
ensureProviderKey(provider);
emitDebug(options, `credentials=${provider} key present`);
ensureProviderBaseUrl(provider);
ensureAwsWorkspaceId(provider);

if (provider === "openai-chatgpt") {
// Refresh before the model is built, so `createModel` stays synchronous.
Expand Down Expand Up @@ -418,6 +422,14 @@ function ensureProviderBaseUrl(provider: OpenWikiProvider): void {
}

if (!resolveProviderBaseUrl(provider)) {
// `anthropic-aws` derives its base URL from AWS_REGION, so surface both ways
// to satisfy the requirement instead of just naming the override key.
if (provider === "anthropic-aws") {
throw new Error(
`${AWS_REGION_ENV_KEY} (or an explicit ${ANTHROPIC_AWS_BASE_URL_ENV_KEY}) is required to run OpenWiki with ${getProviderLabel(provider)}.`,
);
}

const baseUrlEnvKey = getProviderBaseUrlEnvKey(provider) ?? "base URL";

throw new Error(
Expand All @@ -426,6 +438,18 @@ function ensureProviderBaseUrl(provider: OpenWikiProvider): void {
}
}

function ensureAwsWorkspaceId(provider: OpenWikiProvider): void {
if (provider !== "anthropic-aws") {
return;
}

if (!process.env[ANTHROPIC_AWS_WORKSPACE_ID_ENV_KEY]?.trim()) {
throw new Error(
`${ANTHROPIC_AWS_WORKSPACE_ID_ENV_KEY} is required to run OpenWiki with ${getProviderLabel(provider)}.`,
);
}
}

function resolveModelId(
options: OpenWikiRunOptions,
provider: OpenWikiProvider,
Expand All @@ -452,12 +476,27 @@ function createModel(
) {
const retryOptions = { maxRetries: providerRetryAttempts };

if (provider === "anthropic") {
if (provider === "anthropic" || provider === "anthropic-aws") {
const baseURL = resolveProviderBaseUrl(provider);
// Claude Platform on AWS scopes every request to a workspace via the
// `anthropic-workspace-id` header (its base URL + API key are handled by the
// shared config path above). The first-party `anthropic` provider has no
// workspace concept, so the header is only sent for `anthropic-aws`.
const workspaceId =
provider === "anthropic-aws"
? process.env[ANTHROPIC_AWS_WORKSPACE_ID_ENV_KEY]?.trim()
: undefined;

return new ChatAnthropic(modelId, {
apiKey: process.env[getProviderApiKeyEnvKey(provider)],
...(baseURL ? { anthropicApiUrl: baseURL } : {}),
...(workspaceId
? {
clientOptions: {
defaultHeaders: { "anthropic-workspace-id": workspaceId },
},
}
: {}),
...retryOptions,
});
}
Expand Down Expand Up @@ -1318,6 +1357,7 @@ function formatDebugValue(key: string, value: string | undefined): string {
if (
key === "LANGCHAIN_ENDPOINT" ||
key === ANTHROPIC_BASE_URL_ENV_KEY ||
key === ANTHROPIC_AWS_BASE_URL_ENV_KEY ||
key === OPENAI_COMPATIBLE_BASE_URL_ENV_KEY
) {
return formatUrlDebugValue(value);
Expand All @@ -1330,7 +1370,11 @@ function formatDebugValue(key: string, value: string | undefined): string {
if (
key === OPENWIKI_MODEL_ID_ENV_KEY ||
key === OPENWIKI_PROVIDER_ENV_KEY ||
key === OPENWIKI_PROVIDER_RETRY_ATTEMPTS_ENV_KEY
key === OPENWIKI_PROVIDER_RETRY_ATTEMPTS_ENV_KEY ||
// Non-secret Claude Platform on AWS identifiers: show them verbatim in
// debug output, matching how the credential diagnostics panel treats them.
key === ANTHROPIC_AWS_WORKSPACE_ID_ENV_KEY ||
key === AWS_REGION_ENV_KEY
) {
return `set(value=${JSON.stringify(value)})`;
}
Expand Down
108 changes: 92 additions & 16 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ export const OPENAI_CHATGPT_EMAIL_ENV_KEY = "OPENAI_CHATGPT_EMAIL";
export const OPENAI_CHATGPT_PLAN_ENV_KEY = "OPENAI_CHATGPT_PLAN";
export const ANTHROPIC_API_KEY_ENV_KEY = "ANTHROPIC_API_KEY";
export const ANTHROPIC_BASE_URL_ENV_KEY = "ANTHROPIC_BASE_URL";
// Claude Platform on AWS (https://platform.claude.com/docs/en/build-with-claude/claude-platform-on-aws)
// is a distinct, Anthropic-managed offering fronted by AWS with its own env
// names, mirroring the official Anthropic SDK/CLI (`ANTHROPIC_AWS_*`, `AWS_REGION`).
// Its base URL is derived from the workspace's AWS region
// (`https://aws-external-anthropic.{region}.api.aws`); ANTHROPIC_AWS_BASE_URL is an
// optional explicit override for proxies/testing. Every request is scoped to a
// workspace via the required `anthropic-workspace-id` header (workspace IDs look
// like `wrkspc_…`).
export const ANTHROPIC_AWS_API_KEY_ENV_KEY = "ANTHROPIC_AWS_API_KEY";
export const ANTHROPIC_AWS_BASE_URL_ENV_KEY = "ANTHROPIC_AWS_BASE_URL";
export const ANTHROPIC_AWS_WORKSPACE_ID_ENV_KEY = "ANTHROPIC_AWS_WORKSPACE_ID";
export const AWS_REGION_ENV_KEY = "AWS_REGION";
export const AWS_DEFAULT_REGION_ENV_KEY = "AWS_DEFAULT_REGION";
export const OPENROUTER_API_KEY_ENV_KEY = "OPENROUTER_API_KEY";
export const OPENWIKI_PROVIDER_ENV_KEY = "OPENWIKI_PROVIDER";
export const OPENWIKI_MODEL_ID_ENV_KEY = "OPENWIKI_MODEL_ID";
Expand Down Expand Up @@ -55,6 +68,7 @@ export const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";

export type OpenWikiProvider =
| "anthropic"
| "anthropic-aws"
| "baseten"
| "fireworks"
| "nvidia"
Expand Down Expand Up @@ -90,6 +104,17 @@ const OPENAI_MODEL_OPTIONS: ProviderModelOption[] = [
{ id: "gpt-5.4-mini", label: "5.4 mini" },
];

/**
* Claude model options. Shared by the first-party `anthropic` provider and the
* `anthropic-aws` (Claude Platform on AWS) provider so both expose an identical
* model list — both target the same Claude API surface.
*/
const CLAUDE_MODEL_OPTIONS: ProviderModelOption[] = [
{ id: "claude-haiku-4-5", label: "Haiku" },
{ id: "claude-sonnet-5", label: "Sonnet" },
{ id: "claude-opus-4-8", label: "Opus" },
];

type ProviderConfig = {
apiKeyEnvKey: string;
/**
Expand All @@ -104,9 +129,17 @@ type ProviderConfig = {
* with an alternative base URL (e.g. a self-hosted or proxied endpoint).
*/
baseUrlEnvKey?: string;
/**
* Derives a base URL from the environment when neither
* {@link ProviderConfig.baseUrlEnvKey} override nor {@link ProviderConfig.baseURL}
* default applies. Used by `anthropic-aws`, whose endpoint is built from the
* workspace's AWS region rather than a fixed URL.
*/
deriveBaseUrl?: (env: NodeJS.ProcessEnv) => string | undefined;
/**
* When true, the provider has no default endpoint and requires a base URL to
* be supplied via {@link ProviderConfig.baseUrlEnvKey}.
* be supplied via {@link ProviderConfig.baseUrlEnvKey} (or, for `anthropic-aws`,
* derived via {@link ProviderConfig.deriveBaseUrl}).
*/
requiresBaseUrl?: boolean;
label: string;
Expand All @@ -117,6 +150,7 @@ export const SELECTABLE_OPENWIKI_PROVIDERS = [
"openai",
"openai-chatgpt",
"anthropic",
"anthropic-aws",
"openrouter",
"openai-compatible",
"fireworks",
Expand Down Expand Up @@ -190,11 +224,15 @@ export const PROVIDER_CONFIGS: Record<OpenWikiProvider, ProviderConfig> = {
apiKeyEnvKey: ANTHROPIC_API_KEY_ENV_KEY,
baseUrlEnvKey: ANTHROPIC_BASE_URL_ENV_KEY,
label: "Anthropic",
modelOptions: [
{ id: "claude-haiku-4-5", label: "Haiku" },
{ id: "claude-sonnet-5", label: "Sonnet" },
{ id: "claude-opus-4-8", label: "Opus" },
],
modelOptions: CLAUDE_MODEL_OPTIONS,
},
"anthropic-aws": {
apiKeyEnvKey: ANTHROPIC_AWS_API_KEY_ENV_KEY,
baseUrlEnvKey: ANTHROPIC_AWS_BASE_URL_ENV_KEY,
deriveBaseUrl: resolveAwsAnthropicBaseUrl,
requiresBaseUrl: true,
label: "Claude Platform on AWS",
modelOptions: CLAUDE_MODEL_OPTIONS,
},
openrouter: {
apiKeyEnvKey: OPENROUTER_API_KEY_ENV_KEY,
Expand Down Expand Up @@ -259,7 +297,43 @@ export function resolveProviderBaseUrl(
return trimmedOverride;
}

return config.baseURL;
return config.baseURL ?? config.deriveBaseUrl?.(env);
}

/**
* The AWS region a Claude Platform on AWS workspace is bound to, read from
* `AWS_REGION` with a fallback to `AWS_DEFAULT_REGION` (matching the standard AWS
* SDK resolution the official Anthropic AWS client uses).
*/
export function resolveAwsRegion(
env: NodeJS.ProcessEnv = process.env,
): string | undefined {
const region = (
env[AWS_REGION_ENV_KEY] ?? env[AWS_DEFAULT_REGION_ENV_KEY]
)?.trim();

return region ? region : undefined;
}

/**
* Builds the Claude Platform on AWS gateway base URL for a region, e.g.
* `https://aws-external-anthropic.us-west-2.api.aws`.
*/
export function buildAwsAnthropicBaseUrl(region: string): string {
return `https://aws-external-anthropic.${region}.api.aws`;
}

/**
* Derives the `anthropic-aws` base URL from the configured AWS region, or
* `undefined` when no region is set (callers then require an explicit
* `ANTHROPIC_AWS_BASE_URL` override).
*/
function resolveAwsAnthropicBaseUrl(
env: NodeJS.ProcessEnv = process.env,
): string | undefined {
const region = resolveAwsRegion(env);

return region ? buildAwsAnthropicBaseUrl(region) : undefined;
}

export function getProviderBaseUrlEnvKey(
Expand Down Expand Up @@ -325,15 +399,17 @@ export function resolveConfiguredProvider(
? "openai-compatible"
: env[OPENROUTER_API_KEY_ENV_KEY]
? "openrouter"
: env[ANTHROPIC_API_KEY_ENV_KEY]
? "anthropic"
: env[BASETEN_API_KEY_ENV_KEY]
? "baseten"
: env[FIREWORKS_API_KEY_ENV_KEY]
? "fireworks"
: env[NVIDIA_API_KEY_ENV_KEY]
? "nvidia"
: DEFAULT_PROVIDER)
: env[ANTHROPIC_AWS_API_KEY_ENV_KEY]
? "anthropic-aws"
: env[ANTHROPIC_API_KEY_ENV_KEY]
? "anthropic"
: env[BASETEN_API_KEY_ENV_KEY]
? "baseten"
: env[FIREWORKS_API_KEY_ENV_KEY]
? "fireworks"
: env[NVIDIA_API_KEY_ENV_KEY]
? "nvidia"
: DEFAULT_PROVIDER)
);
}

Expand Down
Loading