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
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,34 @@ OPENWIKI_PROVIDER_RETRY_ATTEMPTS=3

The value must be a positive integer. If the value is unset, OpenWiki defaults to 3 retries.

### Model output token limit

To override the maximum number of tokens generated in a model response, set
`OPENWIKI_MAX_OUTPUT_TOKENS`:

```bash
OPENWIKI_MAX_OUTPUT_TOKENS=8192
```

The value must be a positive integer. If unset, OpenWiki does not override the
model client's output token limit. Provider and model limits still apply;
unsupported values may be rejected, while very small values can truncate
responses or tool calls.

### Bedrock stream idle timeout

For the Bedrock provider, set `OPENWIKI_STREAM_IDLE_TIMEOUT` to control how
long the client waits for the first or next streamed response chunk:

```bash
OPENWIKI_STREAM_IDLE_TIMEOUT=300000
```

The value is milliseconds and must be an integer from `0` to `2147483647`.
Set it to `0` to disable the watchdog. If unset, OpenWiki preserves the
`@langchain/aws` provider default. Prefer a sufficiently long finite timeout
over disabling the watchdog so a stalled stream cannot hang forever.

### Diagrams

OpenWiki embeds **Mermaid** diagrams in the generated wiki wherever they make a
Expand Down
53 changes: 52 additions & 1 deletion src/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,21 +91,25 @@ import {
OPENAI_COMPATIBLE_BASE_URL_ENV_KEY,
OPENROUTER_API_KEY_ENV_KEY,
OPENROUTER_BASE_URL,
OPENWIKI_MAX_OUTPUT_TOKENS_ENV_KEY,
OPENWIKI_MODEL_ID_ENV_KEY,
OPENWIKI_PROVIDER_ENV_KEY,
OPENWIKI_PROVIDER_RETRY_ATTEMPTS_ENV_KEY,
OPENWIKI_STREAM_IDLE_TIMEOUT_ENV_KEY,
providerRequiresBaseUrl,
providerRequiresRegion,
providerRequiresSecretKey,
providerUsesAwsSdkCredentials,
providerUsesExternalCliAuth,
providerUsesResponsesApi,
resolveConfiguredProvider,
resolveMaxOutputTokens,
resolveOpenRouterProviderOnly,
resolveProviderBaseUrl,
resolveProviderLocation,
resolveProviderRegion,
resolveProviderRetryAttempts,
resolveStreamIdleTimeoutForProvider,
type OpenWikiProvider,
} from "../constants.js";
import {
Expand Down Expand Up @@ -213,6 +217,16 @@ export async function runOpenWikiAgent(
emitDebug(options, `model=${modelId}`);
const providerRetryAttempts = resolveProviderRetryAttempts();
emitDebug(options, `provider.retryAttempts=${providerRetryAttempts}`);
const maxOutputTokens = resolveMaxOutputTokens();
emitDebug(
options,
`model.maxOutputTokens=${maxOutputTokens ?? "provider-default"}`,
);
const streamIdleTimeout = resolveStreamIdleTimeoutForProvider(provider);
emitDebug(
options,
`model.streamIdleTimeout=${streamIdleTimeout ?? "provider-default"}`,
);

const result = await runOpenWikiAgentCore(
command,
Expand All @@ -221,6 +235,8 @@ export async function runOpenWikiAgent(
provider,
modelId,
providerRetryAttempts,
maxOutputTokens,
streamIdleTimeout,
);

await recordRunSafe(command, options, {
Expand Down Expand Up @@ -251,6 +267,8 @@ async function runOpenWikiAgentCore(
provider: OpenWikiProvider,
modelId: string,
providerRetryAttempts: number,
maxOutputTokens: number | undefined,
streamIdleTimeout: number | undefined,
): Promise<OpenWikiRunResult> {
const outputMode = options.outputMode ?? "local-wiki";
const context = await createRunContext(
Expand All @@ -265,7 +283,13 @@ async function runOpenWikiAgentCore(
? null
: await createOpenWikiContentSnapshot(cwd, outputMode);
emitDebug(options, "openwiki.snapshot=created");
const model = createModel(provider, modelId, providerRetryAttempts);
const model = createModel(
provider,
modelId,
providerRetryAttempts,
maxOutputTokens,
streamIdleTimeout,
);
emitDebug(options, `model.provider=${provider}`);
emitDebug(options, "model=initialized");
const threadId = options.threadId ?? createThreadId(cwd, createRunThreadId());
Expand Down Expand Up @@ -802,8 +826,16 @@ export function createModel(
provider: OpenWikiProvider,
modelId: string,
providerRetryAttempts: number,
maxOutputTokens?: number,
streamIdleTimeout?: number,
) {
const retryOptions = { maxRetries: providerRetryAttempts };
const maxTokensOptions =
maxOutputTokens === undefined ? {} : { maxTokens: maxOutputTokens };
const googleMaxOutputTokensOptions =
maxOutputTokens === undefined ? {} : { maxOutputTokens };
const streamIdleTimeoutOptions =
streamIdleTimeout === undefined ? {} : { streamIdleTimeout };

if (provider === "gemini") {
return new ChatGoogle({
Expand All @@ -812,6 +844,7 @@ export function createModel(
platformType: "gai",
// Gemini 3.x thought-signature round-trip; see the constant's comment.
...GEMINI_THOUGHT_SIGNATURE_OPTIONS,
...googleMaxOutputTokensOptions,
...retryOptions,
});
}
Expand All @@ -835,6 +868,7 @@ export function createModel(
projectId,
location,
retryOptions,
maxOutputTokens,
);
}

Expand All @@ -844,6 +878,7 @@ export function createModel(
return new ChatAnthropic(modelId, {
apiKey: getProviderApiKey(provider),
...(baseURL ? { anthropicApiUrl: baseURL } : {}),
...maxTokensOptions,
...retryOptions,
});
}
Expand Down Expand Up @@ -871,6 +906,7 @@ export function createModel(
// every generation — including the non-streaming `.invoke()` calls
// DeepAgents' agent node issues internally.
streaming: true,
...maxTokensOptions,
...retryOptions,
configuration: {
baseURL: CODEX_RESPONSES_BASE_URL,
Expand All @@ -893,6 +929,7 @@ export function createModel(
model: modelId,
provider: providerOnly ? { only: providerOnly } : undefined,
siteName: "OpenWiki",
...maxTokensOptions,
...retryOptions,
});
}
Expand All @@ -901,6 +938,8 @@ export function createModel(
return new ChatBedrockConverse({
model: modelId,
region: resolveProviderRegion(provider),
...maxTokensOptions,
...streamIdleTimeoutOptions,
...retryOptions,
});
}
Expand All @@ -916,6 +955,7 @@ export function createModel(
: undefined,
model: modelId,
useResponsesApi: providerUsesResponsesApi(provider, modelId),
...maxTokensOptions,
...retryOptions,
});
}
Expand Down Expand Up @@ -982,7 +1022,13 @@ function createGeminiEnterpriseModel(
projectId: string,
location: string,
retryOptions: { maxRetries: number },
maxOutputTokens?: number,
) {
const maxTokensOptions =
maxOutputTokens === undefined ? {} : { maxTokens: maxOutputTokens };
const googleMaxOutputTokensOptions =
maxOutputTokens === undefined ? {} : { maxOutputTokens };

switch (resolveVertexSurface(modelId)) {
case "anthropic":
// No JS-native Claude-on-Vertex chat model exists; bridge via
Expand Down Expand Up @@ -1012,6 +1058,7 @@ function createGeminiEnterpriseModel(
dangerouslyAllowBrowser: true,
}),
),
...maxTokensOptions,
...retryOptions,
});

Expand All @@ -1027,6 +1074,7 @@ function createGeminiEnterpriseModel(
fetch: createVertexAuthFetch(),
},
model: toVertexPublisherModel(modelId),
...maxTokensOptions,
...retryOptions,
});

Expand All @@ -1051,6 +1099,7 @@ function createGeminiEnterpriseModel(
// process.env, using the `/node` entrypoint where googleAuthOptions is
// typed (the default entrypoint types authOptions as `never`).
googleAuthOptions: { projectId },
...googleMaxOutputTokensOptions,
...retryOptions,
});
}
Expand Down Expand Up @@ -1855,6 +1904,8 @@ export function formatEnvironmentDebugValue(
if (
key === OPENWIKI_MODEL_ID_ENV_KEY ||
key === OPENWIKI_PROVIDER_ENV_KEY ||
key === OPENWIKI_MAX_OUTPUT_TOKENS_ENV_KEY ||
key === OPENWIKI_STREAM_IDLE_TIMEOUT_ENV_KEY ||
key === OPENWIKI_PROVIDER_RETRY_ATTEMPTS_ENV_KEY ||
key === BEDROCK_AWS_REGION_ENV_KEY
) {
Expand Down
71 changes: 71 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export const GOOGLE_APPLICATION_CREDENTIALS_ENV_KEY =
export const DEFAULT_VERTEX_LOCATION = "global";
export const OPENWIKI_PROVIDER_ENV_KEY = "OPENWIKI_PROVIDER";
export const OPENWIKI_MODEL_ID_ENV_KEY = "OPENWIKI_MODEL_ID";
export const OPENWIKI_MAX_OUTPUT_TOKENS_ENV_KEY = "OPENWIKI_MAX_OUTPUT_TOKENS";
export const OPENWIKI_STREAM_IDLE_TIMEOUT_ENV_KEY =
"OPENWIKI_STREAM_IDLE_TIMEOUT";
const MAX_STREAM_IDLE_TIMEOUT_MS = 2_147_483_647;
export const NEBIUS_BASE_URL = "https://api.tokenfactory.nebius.com/v1/";
export const OPENWIKI_PROVIDER_RETRY_ATTEMPTS_ENV_KEY =
"OPENWIKI_PROVIDER_RETRY_ATTEMPTS";
Expand Down Expand Up @@ -863,6 +867,73 @@ function inspectCredentialPair(
return { complete: hasAccessKey && hasSecretKey, missingEnvKey: null };
}

export function resolveMaxOutputTokens(
env: NodeJS.ProcessEnv = process.env,
): number | undefined {
const rawMaxOutputTokens = env[OPENWIKI_MAX_OUTPUT_TOKENS_ENV_KEY];

if (rawMaxOutputTokens === undefined) {
return undefined;
}

const maxOutputTokens = rawMaxOutputTokens.trim();

if (!/^[1-9]\d*$/u.test(maxOutputTokens)) {
throw new Error(
`Invalid ${OPENWIKI_MAX_OUTPUT_TOKENS_ENV_KEY}. Expected a positive integer.`,
);
}

const parsedMaxOutputTokens = Number(maxOutputTokens);

if (!Number.isSafeInteger(parsedMaxOutputTokens)) {
throw new Error(
`Invalid ${OPENWIKI_MAX_OUTPUT_TOKENS_ENV_KEY}. Expected a positive integer.`,
);
}

return parsedMaxOutputTokens;
}

/** Milliseconds to wait for the first or next Bedrock stream chunk. 0 disables the watchdog. */
export function resolveStreamIdleTimeout(
env: NodeJS.ProcessEnv = process.env,
): number | undefined {
const rawStreamIdleTimeout = env[OPENWIKI_STREAM_IDLE_TIMEOUT_ENV_KEY];

if (rawStreamIdleTimeout === undefined) {
return undefined;
}

const streamIdleTimeout = rawStreamIdleTimeout.trim();

if (!/^\d+$/u.test(streamIdleTimeout)) {
throw new Error(
`Invalid ${OPENWIKI_STREAM_IDLE_TIMEOUT_ENV_KEY}. Expected an integer from 0 to ${MAX_STREAM_IDLE_TIMEOUT_MS} milliseconds.`,
);
}

const parsedStreamIdleTimeout = Number(streamIdleTimeout);

if (
!Number.isSafeInteger(parsedStreamIdleTimeout) ||
parsedStreamIdleTimeout > MAX_STREAM_IDLE_TIMEOUT_MS
) {
throw new Error(
`Invalid ${OPENWIKI_STREAM_IDLE_TIMEOUT_ENV_KEY}. Expected an integer from 0 to ${MAX_STREAM_IDLE_TIMEOUT_MS} milliseconds.`,
);
}

return parsedStreamIdleTimeout;
}

export function resolveStreamIdleTimeoutForProvider(
provider: OpenWikiProvider,
env: NodeJS.ProcessEnv = process.env,
): number | undefined {
return provider === "bedrock" ? resolveStreamIdleTimeout(env) : undefined;
}

export function resolveProviderRetryAttempts(
env: NodeJS.ProcessEnv = process.env,
): number {
Expand Down
Loading