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
4 changes: 3 additions & 1 deletion agents/langchain-deepagents-code/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ RUN chmod 444 /opt/nemoclaw-deepagents-code/generate-config.ts /opt/nemoclaw-dee
&& cmp -s /usr/local/lib/nemoclaw/dcode-launcher.sh /usr/local/lib/nemoclaw/dcode-managed-exec \
&& chmod -R a+rX /opt/nemoclaw-blueprint \
&& test "$(find /opt/nemoclaw-deepagents-profile-plugin -type f -print | LC_ALL=C sort)" = "$(printf '%s\n' '/opt/nemoclaw-deepagents-profile-plugin/pyproject.toml' '/opt/nemoclaw-deepagents-profile-plugin/src/nemoclaw_deepagents_profile/__init__.py')" \
&& printf '%s %s\n' '1cee6afafcbe545f5d095c94cb0ad81ff2a1512f84ad9d128a69a9b3d72b3def' '/opt/nemoclaw-deepagents-profile-plugin/src/nemoclaw_deepagents_profile/__init__.py' '7ba7b77bd6f889cc861eddbe3e38fc1f4433a85b7bc2a9b516e19a19a37a7686' '/opt/nemoclaw-deepagents-profile-plugin/pyproject.toml' | sha256sum -c - \
&& printf '%s %s\n' '59f5e458f64964df94a5f95a27b693ffa54d3ded96dc5c865c53d72ba34b64c6' '/opt/nemoclaw-deepagents-profile-plugin/src/nemoclaw_deepagents_profile/__init__.py' '7ba7b77bd6f889cc861eddbe3e38fc1f4433a85b7bc2a9b516e19a19a37a7686' '/opt/nemoclaw-deepagents-profile-plugin/pyproject.toml' | sha256sum -c - \
&& /opt/venv/bin/pip3 install --no-index --no-cache-dir --no-deps --no-build-isolation /opt/nemoclaw-deepagents-profile-plugin \
&& /opt/venv/bin/python3 -I -c 'import nemoclaw_deepagents_profile; print("NEMOCLAW_DCODE_PROFILE_" + "IMPORT_GATE", flush=True); import deepagents; import deepagents_code' \
&& /opt/venv/bin/pip3 check \
Expand All @@ -79,6 +79,7 @@ RUN chmod 444 /opt/nemoclaw-deepagents-code/generate-config.ts /opt/nemoclaw-dee
ARG NEMOCLAW_MODEL=nvidia/nemotron-3-ultra-550b-a55b
ARG NEMOCLAW_PROVIDER_KEY=inference
ARG NEMOCLAW_UPSTREAM_PROVIDER=nvidia
ARG NEMOCLAW_UPSTREAM_ENDPOINT_URL=
Comment thread
coderabbitai[bot] marked this conversation as resolved.
ARG NEMOCLAW_INFERENCE_BASE_URL=https://inference.local/v1
ARG NEMOCLAW_INFERENCE_API=openai-completions
ARG NEMOCLAW_TOOL_DISCLOSURE=progressive
Expand Down Expand Up @@ -125,6 +126,7 @@ ENV HOME=/sandbox \
NEMOCLAW_MODEL=${NEMOCLAW_MODEL} \
NEMOCLAW_PROVIDER_KEY=${NEMOCLAW_PROVIDER_KEY} \
NEMOCLAW_UPSTREAM_PROVIDER=${NEMOCLAW_UPSTREAM_PROVIDER} \
NEMOCLAW_UPSTREAM_ENDPOINT_URL=${NEMOCLAW_UPSTREAM_ENDPOINT_URL} \
NEMOCLAW_INFERENCE_BASE_URL=${NEMOCLAW_INFERENCE_BASE_URL} \
NEMOCLAW_INFERENCE_API=${NEMOCLAW_INFERENCE_API} \
NEMOCLAW_TOOL_DISCLOSURE=${NEMOCLAW_TOOL_DISCLOSURE} \
Expand Down
21 changes: 12 additions & 9 deletions agents/langchain-deepagents-code/dependency-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ This file records the reviewed dependency baseline for the Deep Agents Code sand
Update it whenever `requirements.lock` changes.

- Lockfile: `agents/langchain-deepagents-code/requirements.lock`
- Lockfile SHA-256: `d8b01f36a0f325f38d18b4dc2cfdf452125987571a86ca58d9c93e08b7b06a14`
- Lockfile SHA-256: `7889fd275175ceadde843480587a3ed5b3dc517537222e60fa6fdfe4d5b21332`
- Audit command: `uv tool run --python 3.13 pip-audit -r agents/langchain-deepagents-code/requirements.lock --progress-spinner off --disable-pip`
- Audit date: 2026-07-07
- Audit date: 2026-07-09
- Audit result: `No known vulnerabilities found`

The Dockerfile installs this lockfile with `pip3 install --require-hashes`, so this review covers the exact package versions selected for the managed image install.
Expand Down Expand Up @@ -59,7 +59,7 @@ NemoClaw no longer vendors or overlays that source.
- Native profile SHA-256: `c8e8dd2b0182334b54be4f46ff0c7b45fbb95dc13bd9a92c249eb47a14fa13d7`
- Unmodified built-in bootstrap SHA-256: `005a91e7fc4ca6b21220673dd9d02d6686bf63e1e4f1102d124b01f96886efcf`
- First-party adapter: `nemoclaw-deepagents-profile==0.1.0`
- Adapter module SHA-256: `1cee6afafcbe545f5d095c94cb0ad81ff2a1512f84ad9d128a69a9b3d72b3def`
- Adapter module SHA-256: `59f5e458f64964df94a5f95a27b693ffa54d3ded96dc5c865c53d72ba34b64c6`
- Adapter project metadata SHA-256: `7ba7b77bd6f889cc861eddbe3e38fc1f4433a85b7bc2a9b516e19a19a37a7686`
- Adapter wheel license expression: `Apache-2.0`
- Adapter dependency audit result: `No known vulnerabilities found`. Its only
Expand Down Expand Up @@ -124,12 +124,15 @@ aliases. They are not a new provider profile and do not modify the reviewed
canonical NVIDIA profile.

The two managed model IDs remain language-local constants in the TypeScript
config generator and the isolated Python image/plugin validators. Those
components run on opposite sides of the offline wheel-install boundary, so a
shared runtime data file would enlarge the installed trust surface solely to
deduplicate two immutable strings. The focused profile-plugin suite extracts
the identifiers from every production consumer and requires the exact sets to
match, preventing drift without adding another mutable build artifact.
config generator and the isolated Python image/plugin validators. NemoClaw
registers both IDs under the managed OpenAI adapter and the managed OpenRouter
adapter because Deep Agents Code applies provider-native request shaping before
it reaches the shared `inference.local` route. Those components run on opposite
sides of the offline wheel-install boundary, so a shared runtime data file would
enlarge the installed trust surface solely to deduplicate two immutable strings.
The focused profile-plugin suite extracts the identifiers from every production
consumer and requires the exact sets to match, preventing drift without adding
another mutable build artifact.

For `force_nonempty_content`, the invalid state originates in the NVIDIA Ultra
chat template/serving path: a Chat Completions response that combines reasoning
Expand Down
124 changes: 105 additions & 19 deletions agents/langchain-deepagents-code/generate-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,28 @@ type Settings = {
baseUrl: string;
providerKey: string;
upstreamProvider: string;
upstreamEndpointUrl: string | null;
inferenceApi: string;
};

type ManagedDeepAgentsProvider = "openai" | "openrouter";

type ManagedDeepAgentsConfig = {
text: string;
provider: ManagedDeepAgentsProvider;
model: string;
defaultModel: string;
};

const NEMOTRON_ULTRA_MODEL_IDS = new Set([
"nvidia/nemotron-3-ultra-550b-a55b",
"nvidia/nvidia/nemotron-3-ultra",
]);

const OPENROUTER_UPSTREAM_PROVIDERS = new Set(["openrouter", "openrouter-api"]);
const OPENROUTER_ENDPOINT_HOST = "openrouter.ai";
const OPENROUTER_ENDPOINT_PATH = "/api/v1";

function readSettings(env: NodeJS.ProcessEnv): Settings {
const providerKey = normalizeCommentMetadata(
env.NEMOCLAW_PROVIDER_KEY || "inference",
Expand All @@ -38,6 +52,10 @@ function readSettings(env: NodeJS.ProcessEnv): Settings {
env.NEMOCLAW_UPSTREAM_PROVIDER || env.NEMOCLAW_PROVIDER_KEY || "inference",
"NEMOCLAW_UPSTREAM_PROVIDER",
),
upstreamEndpointUrl: normalizeOptionalEndpointUrl(
env.NEMOCLAW_UPSTREAM_ENDPOINT_URL,
"NEMOCLAW_UPSTREAM_ENDPOINT_URL",
),
inferenceApi: normalizeCommentMetadata(
env.NEMOCLAW_INFERENCE_API || "openai-completions",
"NEMOCLAW_INFERENCE_API",
Expand All @@ -58,6 +76,30 @@ function normalizeCommentMetadata(value: string, name: string): string {
return value.trim();
}

function normalizeOptionalEndpointUrl(value: string | undefined, name: string): string | null {
if (value === undefined || value.trim() === "") return null;
if (/[\p{Cc}\p{Cf}]/u.test(value)) {
throw new Error(`${name} must not contain control characters.`);
}
const text = value.trim();
let url: URL;
try {
url = new URL(text);
} catch {
throw new Error(`${name} must be a valid URL.`);
}
if (url.protocol !== "http:" && url.protocol !== "https:") {
throw new Error(`${name} must use HTTP or HTTPS.`);
}
if (url.username || url.password) {
throw new Error(`${name} must not include credentials.`);
}
if (url.search || url.hash) {
throw new Error(`${name} must not include query strings or fragments.`);
}
return url.href;
}

function normalizeInferenceBaseUrl(value: string): string {
if (/[\r\n]/.test(value)) {
throw new Error("NEMOCLAW_INFERENCE_BASE_URL must not contain line breaks.");
Expand Down Expand Up @@ -91,51 +133,94 @@ function tomlArray(values: readonly string[]): string {
return `[${values.map(tomlString).join(", ")}]`;
}

function modelNameForOpenAiProvider(model: string): string {
function managedDeepAgentsProviderFor(settings: Settings): ManagedDeepAgentsProvider {
if (OPENROUTER_UPSTREAM_PROVIDERS.has(settings.upstreamProvider)) return "openrouter";
if (
settings.upstreamProvider === "compatible-endpoint" &&
isOpenRouterEndpointUrl(settings.upstreamEndpointUrl)
) {
return "openrouter";
}
return "openai";
}

function isOpenRouterEndpointUrl(value: string | null): boolean {
if (!value) return false;
const url = new URL(value);
return (
url.protocol === "https:" &&
url.hostname.toLowerCase() === OPENROUTER_ENDPOINT_HOST &&
url.pathname.replace(/\/+$/, "") === OPENROUTER_ENDPOINT_PATH
);
}

function modelNameForManagedProvider(model: string): string {
const trimmed = model.trim();
return trimmed.startsWith("openai:") ? trimmed.slice("openai:".length) : trimmed;
for (const prefix of ["openai:", "openrouter:"]) {
if (trimmed.startsWith(prefix)) return trimmed.slice(prefix.length);
}
return trimmed;
}

function buildConfig(settings: Settings): string {
const model = modelNameForOpenAiProvider(settings.model);
const defaultModel = `openai:${model}`;
function openAiModelRequestParamLines(model: string): string[] {
// Source boundary: NVIDIA's Ultra serving template owns the empty assistant
// content behavior; this generator owns only the managed per-model request
// parameters. Keep the exact invalid state, regression proof, and separate
// removal conditions for this option and the dispatch guard in
// dependency-review.md under "Managed Ultra compatibility workarounds."
const modelParams = NEMOTRON_ULTRA_MODEL_IDS.has(model)
return NEMOTRON_ULTRA_MODEL_IDS.has(model)
? [
"",
`[models.providers.openai.params.${tomlString(model)}]`,
"# Nemotron Ultra coding-agent requests need nonempty content when tool calls and reasoning are combined.",
"extra_body = { chat_template_kwargs = { force_nonempty_content = true } }",
]
: [];
}

function providerConfigLines(
provider: ManagedDeepAgentsProvider,
model: string,
baseUrl: string,
): string[] {
return [
`[models.providers.${provider}]`,
`models = ${tomlArray([model])}`,
'api_key_env = "DEEPAGENTS_CODE_OPENAI_API_KEY"',
`base_url = ${tomlString(baseUrl)}`,
"enabled = true",
...(provider === "openai"
? [
"",
"[models.providers.openai.params]",
"# NemoClaw-managed inference.local currently exposes Chat Completions.",
"# Remove this override when that route supports OpenAI Responses API.",
"use_responses_api = false",
...openAiModelRequestParamLines(model),
]
: []),
];
}

function buildConfig(settings: Settings): ManagedDeepAgentsConfig {
const provider = managedDeepAgentsProviderFor(settings);
const model = modelNameForManagedProvider(settings.model);
const defaultModel = `${provider}:${model}`;
const text = [
"# Generated by NemoClaw. This file contains no provider secrets.",
`# NemoClaw provider route: ${settings.providerKey}; upstream provider: ${settings.upstreamProvider}; API: ${settings.inferenceApi}.`,
"",
"[models]",
`default = ${tomlString(defaultModel)}`,
"",
"[models.providers.openai]",
`models = ${tomlArray([model])}`,
'api_key_env = "DEEPAGENTS_CODE_OPENAI_API_KEY"',
`base_url = ${tomlString(settings.baseUrl)}`,
"enabled = true",
"",
"[models.providers.openai.params]",
"# NemoClaw-managed inference.local currently exposes Chat Completions.",
"# Remove this override when that route supports OpenAI Responses API.",
"use_responses_api = false",
...modelParams,
...providerConfigLines(provider, model, settings.baseUrl),
"",
"[update]",
"check = false",
"auto_update = false",
"",
].join("\n");
return { text, provider, model, defaultModel };
}

function main(): void {
Expand All @@ -145,11 +230,12 @@ function main(): void {
mkdirSync(join(configDir, "skills"), { recursive: true, mode: 0o770 });

const configPath = join(configDir, "config.toml");
writeFileSync(configPath, buildConfig(settings));
const config = buildConfig(settings);
writeFileSync(configPath, config.text);
chmodSync(configPath, 0o600);

console.log(
`[config] Wrote ${configPath} (model=openai:${modelNameForOpenAiProvider(settings.model)}, base_url=${settings.baseUrl})`,
`[config] Wrote ${configPath} (model=${config.defaultModel}, base_url=${settings.baseUrl})`,
);
}

Expand Down
20 changes: 12 additions & 8 deletions agents/langchain-deepagents-code/managed-dcode-runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,13 @@
_MANAGED_FETCH_CA_BUNDLE_FILE = Path(
"/etc/openshell-tls/ca-bundle.pem"
)
_MANAGED_ADAPTER_PROVIDER = "openai"
# Keep this managed adapter allow-list in sync with generate-config.ts and the
# patch-managed-deepagents-code.py provider guards injected into Deep Agents Code.
_MANAGED_ADAPTER_PROVIDERS = frozenset({"openai", "openrouter"})
_NVIDIA_DISPLAY_PROVIDER_ALIASES = frozenset(
{"nvidia", "nvidia-prod", "nvidia-nim", "nvidia-router"}
)
_OPENROUTER_DISPLAY_PROVIDER_ALIASES = frozenset({"openrouter", "openrouter-api"})
_DISPLAY_PROVIDER_NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,63}")
# Match the launchers' root-owned, image-baked proxy validator. Its deliberate
# RFC 1123 deviation permits underscores only for controlled internal/container
Expand Down Expand Up @@ -1301,22 +1304,23 @@ def managed_auto_approval_enabled() -> bool:
def managed_display_provider(adapter_provider: object) -> str:
"""Return the provider label to show for the managed inference adapter.

Managed inference always routes through the OpenAI-compatible adapter, so
Deep Agents Code reports the wire provider (`openai`) in the status bar and
the model-identity system prompt. Substitute the onboard-selected upstream
provider so those surfaces match the launch page. Only the managed
``openai`` adapter is relabeled; every other adapter is returned unchanged.
NVIDIA route aliases share the canonical ``nvidia`` display family.
Managed inference normally routes through the OpenAI-compatible adapter, and
OpenRouter routes through Deep Agents Code's native OpenRouter adapter while
still targeting the managed ``inference.local`` gateway. Substitute the
onboard-selected upstream provider so status surfaces match the launch page.
NVIDIA and OpenRouter aliases share canonical display families.
"""
adapter = adapter_provider if isinstance(adapter_provider, str) else ""
if adapter != _MANAGED_ADAPTER_PROVIDER:
if adapter not in _MANAGED_ADAPTER_PROVIDERS:
return adapter

upstream = os.environ.get(_UPSTREAM_PROVIDER_ENV, "")
if _DISPLAY_PROVIDER_NAME.fullmatch(upstream) is None:
return adapter
if upstream in _NVIDIA_DISPLAY_PROVIDER_ALIASES:
return "nvidia"
if upstream in _OPENROUTER_DISPLAY_PROVIDER_ALIASES:
return "openrouter"
return upstream


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,24 +498,26 @@ def _parse_interpreter_ptc(raw):


def _get_provider_kwargs(provider: str, *, model_name: str | None = None) -> dict[str, Any]:
"""Return only the NemoClaw-managed OpenAI-compatible constructor contract."""
"""Return only the NemoClaw-managed inference constructor contract."""
del model_name
from deepagents_code.model_config import ModelConfig, ModelConfigError
from deepagents_code._nemoclaw_managed import managed_inference_base_url

if provider != "openai":
if provider not in {"openai", "openrouter"}:
raise ModelConfigError(
"Only the NemoClaw-managed OpenAI-compatible provider is enabled"
"Only NemoClaw-managed inference providers are enabled"
)
# Load once so malformed TOML still fails through the upstream config error
# path, but do not consume mutable provider classes, credentials, params, or
# endpoints from it.
ModelConfig.load()
return {
kwargs = {
"api_key": "nemoclaw-managed-inference",
"base_url": managed_inference_base_url(),
"use_responses_api": False,
}
if provider == "openai":
kwargs["use_responses_api"] = False
return kwargs
'''

# Source-of-truth boundary: upstream Deep Agents Code 0.1.34 resolves and pins
Expand Down Expand Up @@ -1077,9 +1079,9 @@ def _nemoclaw_codex_mount(self) -> None:

def _nemoclaw_select_with_auth_check(self, model_spec: str, provider: str) -> None:
if provider:
if provider != "openai":
if provider not in {"openai", "openrouter"}:
self.app.notify(
"Only the NemoClaw-managed OpenAI-compatible provider is enabled.",
"Only NemoClaw-managed inference providers are enabled.",
severity="warning",
markup=False,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@
MANAGED_PROFILE_KEYS = (
"openai:nvidia/nemotron-3-ultra-550b-a55b",
"openai:nvidia/nvidia/nemotron-3-ultra",
"openrouter:nvidia/nemotron-3-ultra-550b-a55b",
"openrouter:nvidia/nvidia/nemotron-3-ultra",
)
_INVALID_EXECUTE_COMMAND = re.compile(r"\[\s*content\s*\]", re.IGNORECASE)
_REGISTRATION_LOCK = threading.Lock()

# invalidState: Deep Agents resolves pre-built ChatOpenAI models under `openai:`
# keys, while its native Ultra profile is registered under an NVIDIA key.
# sourceBoundary: NemoClaw owns only these two managed inference aliases and one
# sourceBoundary: NemoClaw owns only these managed inference aliases and one
# exact malformed-tool-call guard layered onto them; the prompt, tool overrides,
# bootstrap, canonical profile, and upstream source remain byte-identical Deep
# Agents artifacts.
Expand Down Expand Up @@ -210,13 +212,14 @@ def _register_aliases(
raise _fail("managed aliases are in a partial registration state")

try:
first_key, second_key = MANAGED_PROFILE_KEYS
first_key, *alias_keys = MANAGED_PROFILE_KEYS
register_profile(first_key, native_profile)
register_profile(first_key, overlay)
managed_profile = registry.get(first_key)
if managed_profile is None or managed_profile is native_profile:
raise _fail("managed profile overlay was not applied")
register_profile(second_key, managed_profile)
for alias_key in alias_keys:
register_profile(alias_key, managed_profile)
if registry.get(CANONICAL_PROFILE_KEY) is not native_profile:
raise _fail("canonical profile changed during managed registration")
if not all(
Expand Down
2 changes: 1 addition & 1 deletion agents/langchain-deepagents-code/requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# SPDX-License-Identifier: Apache-2.0
#
uv==0.11.15
deepagents-code[nvidia]==0.1.34
deepagents-code[nvidia,openrouter]==0.1.34
nemo-relay[langgraph]==0.4.0
Loading