Change(crd): rename 'Name' to 'name' in sandboxclaim_types CRD#440
Change(crd): rename 'Name' to 'name' in sandboxclaim_types CRD#440k8s-ci-robot merged 3 commits intokubernetes-sigs:mainfrom
Conversation
Signed-off-by: dongjiang1989 <dongjiang1989@126.com>
✅ Deploy Preview for agent-sandbox canceled.
|
|
/test presubmit-agent-sandbox-e2e-test |
Signed-off-by: dongjiang1989 <dongjiang1989@126.com>
|
/cc @janetkuo PTAL, thanks |
codebot-robot
left a comment
There was a problem hiding this comment.
The change correctly renames the field to lowercase name, addressing the JSON tag and aligning the CRD with Kubernetes schema conventions for sandboxclaim_types. However, this structural change has backward compatibility implications during rollout, as it will immediately break existing clients and potentially cause data loss during resource updates in existing clusters.
A few areas of concern should be addressed before merging:
- Breaking Change Management: A deprecation strategy or backward-compatible parsing is highly recommended. Providing a deprecation window for
Namein the v1alpha1 API schema could provide a seamless migration. Furthermore, the Python client update should include a fallback to parsingNameifnameis missing to bridge the transition for olderSandboxClaimresources. - Go Comment Style: Go documentation comments on exported fields should always begin with the exact name of the identifier (
Name), regardless of the JSON representation, to comply with Go documentation standards. - Unrelated Changes: The updates to tools and indirect dependencies (such as
golangci-lint,dev/tools/.custom-gcl.yaml, anddev/tools/go.mod) mix dependency updates with API changes. These can cause unrelated CI issues and merge conflicts, and should ideally be isolated to a separate PR.
(This review was generated by Overseer)
There was a problem hiding this comment.
Although it's a breaking change, it makes sure we follow k8s api convention before moving the API to beta. Also, it's a status field, so the impact is smaller than changing a field in spec.
@igooch given that it affects Python client, please take a look as well
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: dongjiang1989, igooch, janetkuo The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Six PR-review findings folded into one commit because they all touch
the same K8sClient / K8sAgentSandbox pair and share test infrastructure.
Changes:
1. listSandboxClaims: validate label keys and values against the
Kubernetes label regex before serializing into a labelSelector.
Previously `${k}=${v}` concatenation meant a value like
"alice,env=prod" would silently expand into a two-predicate
selector -- which in deleteAll() would widen the delete set
beyond what the caller intended. Keys and values that don't match
the RFC-1123 forms now throw K8sAgentSandboxError("K8S_API_ERROR")
at serialization time. Helpers extracted as serializeLabelSelector,
validateLabelKey, validateLabelValue.
2. deleteAll: refuse empty labels without an explicit
`{ confirmDeleteAll: true }` opt-in. The docstring used to warn
that empty labels means "delete every claim in the namespace",
but the signature didn't enforce it -- and since this method is
the exact footgun that commit 88fbaf6 was fixing (label filter
silently ignored), the type-level enforcement is warranted.
3. create: on initialFiles upload failure, call sandbox.close() in
the outer catch so the `kubectl port-forward` subprocess is torn
down, not leaked. The old failure path only deleted the claim and
left the tunnel running. sandbox.close() handles the claim delete
when deleteOnClose is set, so the outer cleanup becomes a
best-effort safety net -- a duplicate delete against an already-
gone claim 404s and is silently swallowed.
4. SandboxClaimStatus: correct the backcompat comment to cite the
actual upstream PR kubernetes-sigs#440 (Change(crd): rename 'Name' to 'name') and
the accurate root cause. The historical Go struct tag was
explicitly `json:"Name,omitempty"` -- it wasn't omitted -- and
PR kubernetes-sigs#440 renamed only the tag, not the Go field name.
5. Constructor branch coverage: three new tests verify the
KUBERNETES_SERVICE_HOST environment detection logic added in
3e2af35 actually selects the right loader. The previous k8s-client
mock threw unconditionally from loadFromCluster(), which made the
branch invisible to tests. loadFromCluster / loadFromDefault are
now module-scope vi.fn() spies so the branch is asserted.
6. resolveSandboxName capital-N backcompat coverage: two new tests
assert that the Name-casing dual-field read works against a mock
watch event carrying `status.sandbox.Name` and that lowercase
`name` wins when both are present.
7. initialFiles happy and failure paths: two new tests in sandbox.ts
for create() -- one verifies string + Uint8Array encoding reaches
the uploadFiles execute() calls with the right shell-level base64
shapes, the other verifies that a mid-batch upload failure throws
SANDBOX_CREATION_FAILED, calls sandbox.close() to tear down the
tunnel, and then deletes the claim. Also adds coverage for the
deleteAll empty-labels refusal path.
Test totals: 102 unit tests pass (was 86), 97 int tests still pass
against a real kind cluster.
Ports the K8sAgentSandbox provider from
langchain-ai/deepagentsjs (feat/k8s-agent-sandbox branch) into this
repo as clients/typescript/langchain-agent-sandbox, mirroring the
Python clients/python/langchain-agent-sandbox package. Upgraded to
deepagents >=1.9 (the new V2 BackendProtocol) and integrated into
the repo's kind e2e suite. npm package name: langchain-agent-sandbox
(unscoped, matches the Python sibling).
## Package contents
clients/typescript/langchain-agent-sandbox/:
- src/sandbox.ts K8sAgentSandbox extends BaseSandbox with
execute / uploadFiles / downloadFiles / id +
static factories fromUrl / create / fromExisting /
deleteAll. File operations (ls / read / readRaw /
grep / glob / write / edit) are inherited from
the V2 BaseSandbox defaults that delegate to
execute() with POSIX shell.
- src/http-client.ts SandboxRouterClient — HTTP transport layer that
talks to the sandbox-router-svc with
X-Sandbox-ID / X-Sandbox-Namespace / X-Sandbox-Port
headers. Same wire protocol as the Python and
Go SDKs.
- src/k8s-client.ts K8sClient — thin wrapper around
@kubernetes/client-node for SandboxClaim CRUD,
resolveSandboxName, waitForSandboxReady, Gateway
IP discovery, and label-selector-filtered listing.
- src/connection.ts Three connection strategies: DirectConnectionStrategy,
GatewayConnectionStrategy, TunnelConnectionStrategy
(kubectl port-forward subprocess).
- src/types.ts Connection configs, K8sAgentSandboxOptions,
K8sAgentSandboxCreateOptions (including
initialFiles), K8sAgentSandboxError class with
typed code union including COMMAND_TIMEOUT.
- src/index.ts Public exports.
- src/*.test.ts 4 unit test files, 102 tests, fully mocked.
- src/sandbox.int.test.ts Integration suite wired to
@langchain/sandbox-standard-tests (97
behavior tests covering sandbox lifecycle,
command execution, file operations, write,
read, edit, ls, grep, glob, initialFiles,
and integration workflows).
- package.json, tsconfig.json, tsdown.config.ts, vitest.config.ts,
.gitignore, .npmignore, README.md, pnpm-lock.yaml.
examples/langchain-deepagentsjs/: minimal end-to-end example with
main.ts (Anthropic + deepagents + K8sAgentSandbox), package.json,
README.md, sandbox-template.yaml, .gitignore.
## Repo integration
- Makefile: new `test-langchainjs` target runs
`pnpm install --frozen-lockfile && pnpm typecheck && pnpm test`.
- .github/workflows/test-langchainjs.yml: PR-time unit test CI on
Node 20 + 22 matrix when clients/typescript/langchain-agent-sandbox
changes. Matches the existing minimal-permissions pattern.
- dev/tools/test-e2e: setup_typescript_sdk installs the package if
pnpm is present (hard fails on `pnpm install --frozen-lockfile`
failure to prevent stale-node_modules silent passes);
run_typescript_e2e_tests runs vitest --mode int with
KUBECONFIG=bin/KUBECONFIG and outputs bin/e2e-typescript-junit.xml.
Both functions short-circuit cleanly when pnpm, the package dir, or
LANGCHAIN_SANDBOX_TEMPLATE is missing, so they never block the Go
and Python suites from running.
- dev/ci/presubmits/test-e2e: copies the new junit artifact into
$ARTIFACTS when present.
- test/e2e/README.md: documents the shared sandbox-router dependency
for all three SDKs (Python, Go, TS) as a first-class deployment
prerequisite, with the full build + IMAGE_PLACEHOLDER substitution
+ kubectl apply workflow.
- Root README.md: TypeScript SDK section after the Python SDK
section.
## deepagents-js V2 protocol
The existing upstream code extended BaseSandbox (V1) and implemented
only the four abstract members (execute, uploadFiles, downloadFiles,
id). Against deepagents@^1.9.0 those four members satisfy the V2
SandboxBackendProtocolV2 contract unchanged, and the file operations
inherit from the V2 BaseSandbox defaults. No method rewrites were
required — only targeted hardening (see below).
## Post-port hardening
Eight bugs / gaps surfaced during the migration were fixed in this
same commit:
1. execute() COMMAND_TIMEOUT detection: the original port wrapped
every fetch() rejection in http-client.ts as CONNECTION_FAILED,
including the DOMException raised by AbortSignal.timeout. The new
http-client.ts lets errors named TimeoutError / AbortError
propagate without wrapping so execute()'s catch block can
distinguish them and emit a typed COMMAND_TIMEOUT error.
2. K8sClient constructor: @kubernetes/client-node's loadFromCluster()
does not throw outside a pod — it silently creates a config with
server URL https://undefined:undefined, which crashes at request
time. Fixed by checking process.env.KUBERNETES_SERVICE_HOST to
select loadFromCluster() vs loadFromDefault() explicitly. The
in-pod path still works unchanged.
3. resolveSandboxName: older controllers (pre upstream PR kubernetes-sigs#440)
serialize the field as status.sandbox.Name (capital N) because
the Go JSON tag was explicitly `json:"Name,omitempty"`. PR kubernetes-sigs#440
renamed it to `json:"name,omitempty"`. The extract callback now
reads both field names with lowercase precedence, mirroring the
Python SDK's backcompat shim in upstream PR kubernetes-sigs#515.
4. create() initialFiles: the deepagents-js shared standard test
suite passes `initialFiles: Record<string, string | Uint8Array>`
in createSandbox options to pre-populate files. Added the field
to K8sAgentSandboxCreateOptions and upload-after-initialize
handling with full cleanup on failure — on any upload error,
sandbox.close() is called first to tear down the tunnel subprocess
(preventing a `kubectl port-forward` leak), then the claim is
deleted.
5. listSandboxClaims + deleteAll label filtering: the original port
accepted a labels argument but silently ignored it, deleting
every claim in the namespace. listSandboxClaims now accepts an
optional labels record that is validated (RFC-1123 Kubernetes
label regex) and serialized into a labelSelector. Values
containing "," or "=" are rejected at serialization time so a
label like {owner: "alice,env=prod"} can't silently widen into a
two-predicate selector. deleteAll also refuses an empty labels
object unless the caller passes { confirmDeleteAll: true },
eliminating the original "delete everything" footgun by default.
6. execute() timeout distinction: new COMMAND_TIMEOUT error code
lets callers distinguish a per-command timeout from a generic
CONNECTION_FAILED / COMMAND_FAILED. Verified end-to-end against
a real kind cluster — `sleep 30` with defaultTimeout=3 terminates
at exactly 3005ms with the right typed error.
7. int test env-var gating: the LANGCHAIN_SANDBOX_TEMPLATE /
LANGCHAIN_NAMESPACE env var names match the Python langchain-
agent-sandbox e2e test for symmetry, with the legacy
SANDBOX_TEMPLATE / SANDBOX_NAMESPACE names kept as fallbacks.
sandboxStandardTests({ skip: !template, ... }) uses the shared
suite's built-in skip config so the run stays clean when the
template is unset.
8. docs: the sandbox-router Deployment manifest at
clients/python/agentic-sandbox-client/sandbox-router/sandbox_router.yaml
has a literal `image: IMAGE_PLACEHOLDER` line that requires
build + sed substitution before applying — applying it raw
produces a CrashLoopBackOff. Both READMEs now document the full
build + substitute + apply workflow and link to the router's own
README for configuration details.
## Verification
- 102 unit tests pass (`pnpm test`)
- 97 integration tests pass against a real kind cluster
(`LANGCHAIN_SANDBOX_TEMPLATE=df-standard
LANGCHAIN_NAMESPACE=darkfactory pnpm test:int`) — covers the
full deepagents-js shared standard suite (sandbox lifecycle,
command execution, file operations, write, read, edit, ls, grep,
glob, initialFiles, integration workflows) plus provider-specific
lifecycle tests.
- pnpm typecheck clean
- pnpm build produces dual ESM + CJS output
- make test-langchainjs clean
- All 14 .ts files carry the Apache-2.0 / Kubernetes Authors header
- No `any` introduced; #private fields used throughout
## Source attribution
Ported from langchain-ai/deepagentsjs feat/k8s-agent-sandbox branch
under Apache-2.0; the upstream source was MIT-licensed. All files
carry the kubernetes-sigs Apache-2.0 header.
Ports the K8sAgentSandbox provider from
langchain-ai/deepagentsjs (feat/k8s-agent-sandbox branch) into this
repo as clients/typescript/langchain-agent-sandbox, mirroring the
Python clients/python/langchain-agent-sandbox package. Upgraded to
deepagents >=1.9 (the new V2 BackendProtocol) and integrated into
the repo's kind e2e suite. npm package name: langchain-agent-sandbox
(unscoped, matches the Python sibling).
## Package contents
clients/typescript/langchain-agent-sandbox/:
- src/sandbox.ts K8sAgentSandbox extends BaseSandbox with
execute / uploadFiles / downloadFiles / id +
static factories fromUrl / create / fromExisting /
deleteAll. File operations (ls / read / readRaw /
grep / glob / write / edit) are inherited from
the V2 BaseSandbox defaults that delegate to
execute() with POSIX shell.
- src/http-client.ts SandboxRouterClient — HTTP transport layer that
talks to the sandbox-router-svc with
X-Sandbox-ID / X-Sandbox-Namespace / X-Sandbox-Port
headers. Same wire protocol as the Python and
Go SDKs.
- src/k8s-client.ts K8sClient — thin wrapper around
@kubernetes/client-node for SandboxClaim CRUD,
resolveSandboxName, waitForSandboxReady, Gateway
IP discovery, and label-selector-filtered listing.
- src/connection.ts Three connection strategies: DirectConnectionStrategy,
GatewayConnectionStrategy, TunnelConnectionStrategy
(kubectl port-forward subprocess).
- src/types.ts Connection configs, K8sAgentSandboxOptions,
K8sAgentSandboxCreateOptions (including
initialFiles), K8sAgentSandboxError class with
typed code union including COMMAND_TIMEOUT.
- src/index.ts Public exports.
- src/*.test.ts 4 unit test files, 102 tests, fully mocked.
- src/sandbox.int.test.ts Integration suite wired to
@langchain/sandbox-standard-tests (97
behavior tests covering sandbox lifecycle,
command execution, file operations, write,
read, edit, ls, grep, glob, initialFiles,
and integration workflows).
- package.json, tsconfig.json, tsdown.config.ts, vitest.config.ts,
.gitignore, .npmignore, README.md, pnpm-lock.yaml.
examples/langchain-deepagentsjs/: minimal end-to-end example with
main.ts (Anthropic + deepagents + K8sAgentSandbox), package.json,
README.md, sandbox-template.yaml, .gitignore.
## Repo integration
- Makefile: new `test-langchainjs` target runs
`pnpm install --frozen-lockfile && pnpm typecheck && pnpm test`.
- .github/workflows/test-langchainjs.yml: PR-time unit test CI on
Node 20 + 22 matrix when clients/typescript/langchain-agent-sandbox
changes. Matches the existing minimal-permissions pattern.
- dev/tools/test-e2e: setup_typescript_sdk installs the package if
pnpm is present (hard fails on `pnpm install --frozen-lockfile`
failure to prevent stale-node_modules silent passes);
run_typescript_e2e_tests runs vitest --mode int with
KUBECONFIG=bin/KUBECONFIG and outputs bin/e2e-typescript-junit.xml.
Both functions short-circuit cleanly when pnpm, the package dir, or
LANGCHAIN_SANDBOX_TEMPLATE is missing, so they never block the Go
and Python suites from running.
- dev/ci/presubmits/test-e2e: copies the new junit artifact into
$ARTIFACTS when present.
- test/e2e/README.md: documents the shared sandbox-router dependency
for all three SDKs (Python, Go, TS) as a first-class deployment
prerequisite, with the full build + IMAGE_PLACEHOLDER substitution
+ kubectl apply workflow.
- Root README.md: TypeScript SDK section after the Python SDK
section.
## deepagents-js V2 protocol
The existing upstream code extended BaseSandbox (V1) and implemented
only the four abstract members (execute, uploadFiles, downloadFiles,
id). Against deepagents@^1.9.0 those four members satisfy the V2
SandboxBackendProtocolV2 contract unchanged, and the file operations
inherit from the V2 BaseSandbox defaults. No method rewrites were
required — only targeted hardening (see below).
## Post-port hardening
Eight bugs / gaps surfaced during the migration were fixed in this
same commit:
1. execute() COMMAND_TIMEOUT detection: the original port wrapped
every fetch() rejection in http-client.ts as CONNECTION_FAILED,
including the DOMException raised by AbortSignal.timeout. The new
http-client.ts lets errors named TimeoutError / AbortError
propagate without wrapping so execute()'s catch block can
distinguish them and emit a typed COMMAND_TIMEOUT error.
2. K8sClient constructor: @kubernetes/client-node's loadFromCluster()
does not throw outside a pod — it silently creates a config with
server URL https://undefined:undefined, which crashes at request
time. Fixed by checking process.env.KUBERNETES_SERVICE_HOST to
select loadFromCluster() vs loadFromDefault() explicitly. The
in-pod path still works unchanged.
3. resolveSandboxName: older controllers (pre upstream PR kubernetes-sigs#440)
serialize the field as status.sandbox.Name (capital N) because
the Go JSON tag was explicitly `json:"Name,omitempty"`. PR kubernetes-sigs#440
renamed it to `json:"name,omitempty"`. The extract callback now
reads both field names with lowercase precedence, mirroring the
Python SDK's backcompat shim in upstream PR kubernetes-sigs#515.
4. create() initialFiles: the deepagents-js shared standard test
suite passes `initialFiles: Record<string, string | Uint8Array>`
in createSandbox options to pre-populate files. Added the field
to K8sAgentSandboxCreateOptions and upload-after-initialize
handling with full cleanup on failure — on any upload error,
sandbox.close() is called first to tear down the tunnel subprocess
(preventing a `kubectl port-forward` leak), then the claim is
deleted.
5. listSandboxClaims + deleteAll label filtering: the original port
accepted a labels argument but silently ignored it, deleting
every claim in the namespace. listSandboxClaims now accepts an
optional labels record that is validated (RFC-1123 Kubernetes
label regex) and serialized into a labelSelector. Values
containing "," or "=" are rejected at serialization time so a
label like {owner: "alice,env=prod"} can't silently widen into a
two-predicate selector. deleteAll also refuses an empty labels
object unless the caller passes { confirmDeleteAll: true },
eliminating the original "delete everything" footgun by default.
6. execute() timeout distinction: new COMMAND_TIMEOUT error code
lets callers distinguish a per-command timeout from a generic
CONNECTION_FAILED / COMMAND_FAILED. Verified end-to-end against
a real kind cluster — `sleep 30` with defaultTimeout=3 terminates
at exactly 3005ms with the right typed error.
7. int test env-var gating: the LANGCHAIN_SANDBOX_TEMPLATE /
LANGCHAIN_NAMESPACE env var names match the Python langchain-
agent-sandbox e2e test for symmetry, with the legacy
SANDBOX_TEMPLATE / SANDBOX_NAMESPACE names kept as fallbacks.
sandboxStandardTests({ skip: !template, ... }) uses the shared
suite's built-in skip config so the run stays clean when the
template is unset.
8. docs: the sandbox-router Deployment manifest at
clients/python/agentic-sandbox-client/sandbox-router/sandbox_router.yaml
has a literal `image: IMAGE_PLACEHOLDER` line that requires
build + sed substitution before applying — applying it raw
produces a CrashLoopBackOff. Both READMEs now document the full
build + substitute + apply workflow and link to the router's own
README for configuration details.
## Verification
- 102 unit tests pass (`pnpm test`)
- 97 integration tests pass against a real kind cluster
(`LANGCHAIN_SANDBOX_TEMPLATE=df-standard
LANGCHAIN_NAMESPACE=darkfactory pnpm test:int`) — covers the
full deepagents-js shared standard suite (sandbox lifecycle,
command execution, file operations, write, read, edit, ls, grep,
glob, initialFiles, integration workflows) plus provider-specific
lifecycle tests.
- pnpm typecheck clean
- pnpm build produces dual ESM + CJS output
- make test-langchainjs clean
- All 14 .ts files carry the Apache-2.0 / Kubernetes Authors header
- No `any` introduced; #private fields used throughout
## Source attribution
Ported from langchain-ai/deepagentsjs feat/k8s-agent-sandbox branch
under Apache-2.0; the upstream source was MIT-licensed. All files
carry the kubernetes-sigs Apache-2.0 header.
Ports the K8sAgentSandbox provider from
langchain-ai/deepagentsjs (feat/k8s-agent-sandbox branch) into this
repo as clients/typescript/langchain-agent-sandbox, mirroring the
Python clients/python/langchain-agent-sandbox package. Upgraded to
deepagents >=1.9 (the new V2 BackendProtocol) and integrated into
the repo's kind e2e suite. npm package name: langchain-agent-sandbox
(unscoped, matches the Python sibling).
## Package contents
clients/typescript/langchain-agent-sandbox/:
- src/sandbox.ts K8sAgentSandbox extends BaseSandbox with
execute / uploadFiles / downloadFiles / id +
static factories fromUrl / create / fromExisting /
deleteAll. File operations (ls / read / readRaw /
grep / glob / write / edit) are inherited from
the V2 BaseSandbox defaults that delegate to
execute() with POSIX shell.
- src/http-client.ts SandboxRouterClient — HTTP transport layer that
talks to the sandbox-router-svc with
X-Sandbox-ID / X-Sandbox-Namespace / X-Sandbox-Port
headers. Same wire protocol as the Python and
Go SDKs.
- src/k8s-client.ts K8sClient — thin wrapper around
@kubernetes/client-node for SandboxClaim CRUD,
resolveSandboxName, waitForSandboxReady, Gateway
IP discovery, and label-selector-filtered listing.
- src/connection.ts Three connection strategies: DirectConnectionStrategy,
GatewayConnectionStrategy, TunnelConnectionStrategy
(kubectl port-forward subprocess).
- src/types.ts Connection configs, K8sAgentSandboxOptions,
K8sAgentSandboxCreateOptions (including
initialFiles), K8sAgentSandboxError class with
typed code union including COMMAND_TIMEOUT.
- src/index.ts Public exports.
- src/*.test.ts 4 unit test files, 102 tests, fully mocked.
- src/sandbox.int.test.ts Integration suite wired to
@langchain/sandbox-standard-tests (97
behavior tests covering sandbox lifecycle,
command execution, file operations, write,
read, edit, ls, grep, glob, initialFiles,
and integration workflows).
- package.json, tsconfig.json, tsdown.config.ts, vitest.config.ts,
.gitignore, .npmignore, README.md, pnpm-lock.yaml.
examples/langchain-deepagentsjs/: minimal end-to-end example with
main.ts (Anthropic + deepagents + K8sAgentSandbox), package.json,
README.md, sandbox-template.yaml, .gitignore.
## Repo integration
- Makefile: new `test-langchainjs` target runs
`pnpm install --frozen-lockfile && pnpm typecheck && pnpm test`.
- .github/workflows/test-langchainjs.yml: PR-time unit test CI on
Node 20 + 22 matrix when clients/typescript/langchain-agent-sandbox
changes. Matches the existing minimal-permissions pattern.
- dev/tools/test-e2e: setup_typescript_sdk installs the package if
pnpm is present (hard fails on `pnpm install --frozen-lockfile`
failure to prevent stale-node_modules silent passes);
run_typescript_e2e_tests runs vitest --mode int with
KUBECONFIG=bin/KUBECONFIG and outputs bin/e2e-typescript-junit.xml.
Both functions short-circuit cleanly when pnpm, the package dir, or
LANGCHAIN_SANDBOX_TEMPLATE is missing, so they never block the Go
and Python suites from running.
- dev/ci/presubmits/test-e2e: copies the new junit artifact into
$ARTIFACTS when present.
- test/e2e/README.md: documents the shared sandbox-router dependency
for all three SDKs (Python, Go, TS) as a first-class deployment
prerequisite, with the full build + IMAGE_PLACEHOLDER substitution
+ kubectl apply workflow.
- Root README.md: TypeScript SDK section after the Python SDK
section.
## deepagents-js V2 protocol
The existing upstream code extended BaseSandbox (V1) and implemented
only the four abstract members (execute, uploadFiles, downloadFiles,
id). Against deepagents@^1.9.0 those four members satisfy the V2
SandboxBackendProtocolV2 contract unchanged, and the file operations
inherit from the V2 BaseSandbox defaults. No method rewrites were
required — only targeted hardening (see below).
## Post-port hardening
Eight bugs / gaps surfaced during the migration were fixed in this
same commit:
1. execute() COMMAND_TIMEOUT detection: the original port wrapped
every fetch() rejection in http-client.ts as CONNECTION_FAILED,
including the DOMException raised by AbortSignal.timeout. The new
http-client.ts lets errors named TimeoutError / AbortError
propagate without wrapping so execute()'s catch block can
distinguish them and emit a typed COMMAND_TIMEOUT error.
2. K8sClient constructor: @kubernetes/client-node's loadFromCluster()
does not throw outside a pod — it silently creates a config with
server URL https://undefined:undefined, which crashes at request
time. Fixed by checking process.env.KUBERNETES_SERVICE_HOST to
select loadFromCluster() vs loadFromDefault() explicitly. The
in-pod path still works unchanged.
3. resolveSandboxName: older controllers (pre upstream PR kubernetes-sigs#440)
serialize the field as status.sandbox.Name (capital N) because
the Go JSON tag was explicitly `json:"Name,omitempty"`. PR kubernetes-sigs#440
renamed it to `json:"name,omitempty"`. The extract callback now
reads both field names with lowercase precedence, mirroring the
Python SDK's backcompat shim in upstream PR kubernetes-sigs#515.
4. create() initialFiles: the deepagents-js shared standard test
suite passes `initialFiles: Record<string, string | Uint8Array>`
in createSandbox options to pre-populate files. Added the field
to K8sAgentSandboxCreateOptions and upload-after-initialize
handling with full cleanup on failure — on any upload error,
sandbox.close() is called first to tear down the tunnel subprocess
(preventing a `kubectl port-forward` leak), then the claim is
deleted.
5. listSandboxClaims + deleteAll label filtering: the original port
accepted a labels argument but silently ignored it, deleting
every claim in the namespace. listSandboxClaims now accepts an
optional labels record that is validated (RFC-1123 Kubernetes
label regex) and serialized into a labelSelector. Values
containing "," or "=" are rejected at serialization time so a
label like {owner: "alice,env=prod"} can't silently widen into a
two-predicate selector. deleteAll also refuses an empty labels
object unless the caller passes { confirmDeleteAll: true },
eliminating the original "delete everything" footgun by default.
6. execute() timeout distinction: new COMMAND_TIMEOUT error code
lets callers distinguish a per-command timeout from a generic
CONNECTION_FAILED / COMMAND_FAILED. Verified end-to-end against
a real kind cluster — `sleep 30` with defaultTimeout=3 terminates
at exactly 3005ms with the right typed error.
7. int test env-var gating: the LANGCHAIN_SANDBOX_TEMPLATE /
LANGCHAIN_NAMESPACE env var names match the Python langchain-
agent-sandbox e2e test for symmetry, with the legacy
SANDBOX_TEMPLATE / SANDBOX_NAMESPACE names kept as fallbacks.
sandboxStandardTests({ skip: !template, ... }) uses the shared
suite's built-in skip config so the run stays clean when the
template is unset.
8. docs: the sandbox-router Deployment manifest at
clients/python/agentic-sandbox-client/sandbox-router/sandbox_router.yaml
has a literal `image: IMAGE_PLACEHOLDER` line that requires
build + sed substitution before applying — applying it raw
produces a CrashLoopBackOff. Both READMEs now document the full
build + substitute + apply workflow and link to the router's own
README for configuration details.
## Verification
- 102 unit tests pass (`pnpm test`)
- 97 integration tests pass against a real kind cluster
(`LANGCHAIN_SANDBOX_TEMPLATE=df-standard
LANGCHAIN_NAMESPACE=darkfactory pnpm test:int`) — covers the
full deepagents-js shared standard suite (sandbox lifecycle,
command execution, file operations, write, read, edit, ls, grep,
glob, initialFiles, integration workflows) plus provider-specific
lifecycle tests.
- pnpm typecheck clean
- pnpm build produces dual ESM + CJS output
- make test-langchainjs clean
- All 14 .ts files carry the Apache-2.0 / Kubernetes Authors header
- No `any` introduced; #private fields used throughout
## Source attribution
Ported from langchain-ai/deepagentsjs feat/k8s-agent-sandbox branch
under Apache-2.0; the upstream source was MIT-licensed. All files
carry the kubernetes-sigs Apache-2.0 header.
Ports the K8sAgentSandbox provider from
langchain-ai/deepagentsjs (feat/k8s-agent-sandbox branch) into this
repo as clients/typescript/langchain-agent-sandbox, mirroring the
Python clients/python/langchain-agent-sandbox package. Upgraded to
deepagents >=1.9 (the new V2 BackendProtocol) and integrated into
the repo's kind e2e suite. npm package name: langchain-agent-sandbox
(unscoped, matches the Python sibling).
## Package contents
clients/typescript/langchain-agent-sandbox/:
- src/sandbox.ts K8sAgentSandbox extends BaseSandbox with
execute / uploadFiles / downloadFiles / id +
static factories fromUrl / create / fromExisting /
deleteAll. File operations (ls / read / readRaw /
grep / glob / write / edit) are inherited from
the V2 BaseSandbox defaults that delegate to
execute() with POSIX shell.
- src/http-client.ts SandboxRouterClient — HTTP transport layer that
talks to the sandbox-router-svc with
X-Sandbox-ID / X-Sandbox-Namespace / X-Sandbox-Port
headers. Same wire protocol as the Python and
Go SDKs.
- src/k8s-client.ts K8sClient — thin wrapper around
@kubernetes/client-node for SandboxClaim CRUD,
resolveSandboxName, waitForSandboxReady, Gateway
IP discovery, and label-selector-filtered listing.
- src/connection.ts Three connection strategies: DirectConnectionStrategy,
GatewayConnectionStrategy, TunnelConnectionStrategy
(kubectl port-forward subprocess).
- src/types.ts Connection configs, K8sAgentSandboxOptions,
K8sAgentSandboxCreateOptions (including
initialFiles), K8sAgentSandboxError class with
typed code union including COMMAND_TIMEOUT.
- src/index.ts Public exports.
- src/*.test.ts 4 unit test files, 102 tests, fully mocked.
- src/sandbox.int.test.ts Integration suite wired to
@langchain/sandbox-standard-tests (97
behavior tests covering sandbox lifecycle,
command execution, file operations, write,
read, edit, ls, grep, glob, initialFiles,
and integration workflows).
- package.json, tsconfig.json, tsdown.config.ts, vitest.config.ts,
.gitignore, .npmignore, README.md, pnpm-lock.yaml.
examples/langchain-deepagentsjs/: minimal end-to-end example with
main.ts (Anthropic + deepagents + K8sAgentSandbox), package.json,
README.md, sandbox-template.yaml, .gitignore.
## Repo integration
- Makefile: new `test-langchainjs` target runs
`pnpm install --frozen-lockfile && pnpm typecheck && pnpm test`.
- .github/workflows/test-langchainjs.yml: PR-time unit test CI on
Node 20 + 22 matrix when clients/typescript/langchain-agent-sandbox
changes. Matches the existing minimal-permissions pattern.
- dev/tools/test-e2e: setup_typescript_sdk installs the package if
pnpm is present (hard fails on `pnpm install --frozen-lockfile`
failure to prevent stale-node_modules silent passes);
run_typescript_e2e_tests runs vitest --mode int with
KUBECONFIG=bin/KUBECONFIG and outputs bin/e2e-typescript-junit.xml.
Both functions short-circuit cleanly when pnpm, the package dir, or
LANGCHAIN_SANDBOX_TEMPLATE is missing, so they never block the Go
and Python suites from running.
- dev/ci/presubmits/test-e2e: copies the new junit artifact into
$ARTIFACTS when present.
- test/e2e/README.md: documents the shared sandbox-router dependency
for all three SDKs (Python, Go, TS) as a first-class deployment
prerequisite, with the full build + IMAGE_PLACEHOLDER substitution
+ kubectl apply workflow.
- Root README.md: TypeScript SDK section after the Python SDK
section.
## deepagents-js V2 protocol
The existing upstream code extended BaseSandbox (V1) and implemented
only the four abstract members (execute, uploadFiles, downloadFiles,
id). Against deepagents@^1.9.0 those four members satisfy the V2
SandboxBackendProtocolV2 contract unchanged, and the file operations
inherit from the V2 BaseSandbox defaults. No method rewrites were
required — only targeted hardening (see below).
## Post-port hardening
Eight bugs / gaps surfaced during the migration were fixed in this
same commit:
1. execute() COMMAND_TIMEOUT detection: the original port wrapped
every fetch() rejection in http-client.ts as CONNECTION_FAILED,
including the DOMException raised by AbortSignal.timeout. The new
http-client.ts lets errors named TimeoutError / AbortError
propagate without wrapping so execute()'s catch block can
distinguish them and emit a typed COMMAND_TIMEOUT error.
2. K8sClient constructor: @kubernetes/client-node's loadFromCluster()
does not throw outside a pod — it silently creates a config with
server URL https://undefined:undefined, which crashes at request
time. Fixed by checking process.env.KUBERNETES_SERVICE_HOST to
select loadFromCluster() vs loadFromDefault() explicitly. The
in-pod path still works unchanged.
3. resolveSandboxName: older controllers (pre upstream PR kubernetes-sigs#440)
serialize the field as status.sandbox.Name (capital N) because
the Go JSON tag was explicitly `json:"Name,omitempty"`. PR kubernetes-sigs#440
renamed it to `json:"name,omitempty"`. The extract callback now
reads both field names with lowercase precedence, mirroring the
Python SDK's backcompat shim in upstream PR kubernetes-sigs#515.
4. create() initialFiles: the deepagents-js shared standard test
suite passes `initialFiles: Record<string, string | Uint8Array>`
in createSandbox options to pre-populate files. Added the field
to K8sAgentSandboxCreateOptions and upload-after-initialize
handling with full cleanup on failure — on any upload error,
sandbox.close() is called first to tear down the tunnel subprocess
(preventing a `kubectl port-forward` leak), then the claim is
deleted.
5. listSandboxClaims + deleteAll label filtering: the original port
accepted a labels argument but silently ignored it, deleting
every claim in the namespace. listSandboxClaims now accepts an
optional labels record that is validated (RFC-1123 Kubernetes
label regex) and serialized into a labelSelector. Values
containing "," or "=" are rejected at serialization time so a
label like {owner: "alice,env=prod"} can't silently widen into a
two-predicate selector. deleteAll also refuses an empty labels
object unless the caller passes { confirmDeleteAll: true },
eliminating the original "delete everything" footgun by default.
6. execute() timeout distinction: new COMMAND_TIMEOUT error code
lets callers distinguish a per-command timeout from a generic
CONNECTION_FAILED / COMMAND_FAILED. Verified end-to-end against
a real kind cluster — `sleep 30` with defaultTimeout=3 terminates
at exactly 3005ms with the right typed error.
7. int test env-var gating: the LANGCHAIN_SANDBOX_TEMPLATE /
LANGCHAIN_NAMESPACE env var names match the Python langchain-
agent-sandbox e2e test for symmetry, with the legacy
SANDBOX_TEMPLATE / SANDBOX_NAMESPACE names kept as fallbacks.
sandboxStandardTests({ skip: !template, ... }) uses the shared
suite's built-in skip config so the run stays clean when the
template is unset.
8. docs: the sandbox-router Deployment manifest at
clients/python/agentic-sandbox-client/sandbox-router/sandbox_router.yaml
has a literal `image: IMAGE_PLACEHOLDER` line that requires
build + sed substitution before applying — applying it raw
produces a CrashLoopBackOff. Both READMEs now document the full
build + substitute + apply workflow and link to the router's own
README for configuration details.
## Verification
- 102 unit tests pass (`pnpm test`)
- 97 integration tests pass against a real kind cluster
(`LANGCHAIN_SANDBOX_TEMPLATE=df-standard
LANGCHAIN_NAMESPACE=darkfactory pnpm test:int`) — covers the
full deepagents-js shared standard suite (sandbox lifecycle,
command execution, file operations, write, read, edit, ls, grep,
glob, initialFiles, integration workflows) plus provider-specific
lifecycle tests.
- pnpm typecheck clean
- pnpm build produces dual ESM + CJS output
- make test-langchainjs clean
- All 14 .ts files carry the Apache-2.0 / Kubernetes Authors header
- No `any` introduced; #private fields used throughout
## Source attribution
Ported from langchain-ai/deepagentsjs feat/k8s-agent-sandbox branch
under Apache-2.0; the upstream source was MIT-licensed. All files
carry the kubernetes-sigs Apache-2.0 header.
This change renames the field
Nametonamein the sandboxclaim_types CRD to follow Kubernetes naming conventions.refer: #421 (comment)
Warning: This is a breaking change.
Existing resources and clients that reference
status.Namewill no longer work correctly and must be updated to usestatus.nameinstead.