Skip to content

feat(sdk): websocket-based sandbox event subscription across all SDKs#43

Open
MDzaja wants to merge 5 commits into
mainfrom
refactor/sdk-ws-sbx-events
Open

feat(sdk): websocket-based sandbox event subscription across all SDKs#43
MDzaja wants to merge 5 commits into
mainfrom
refactor/sdk-ws-sbx-events

Conversation

@MDzaja

@MDzaja MDzaja commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Ports daytonaio/daytona#3987 into the clients repo across all five SDKs (TypeScript, Python, Go, Ruby, Java).

Summary

Sandbox lifecycle waits (start / stop / delete / resize / snapshot / pause) now resolve via real-time Socket.IO events instead of pure polling, with polling retained as a safety net. Each SDK gains a shared EventDispatcher (one Socket.IO connection per client) and an EventSubscriptionManager (per-sandbox fan-out); every Sandbox subscribes lazily and routes state changes through a single applyState path that wakes any waiters.

The result is faster, lower-latency lifecycle operations (state transitions land in hundreds of ms instead of on the poll interval) while remaining fully backward compatible and degrading gracefully when WebSockets are unavailable.

What changed

New (per SDK): EventDispatcher + EventSubscriptionManager (plus a raw socketio_client for Go/Ruby). Copied from upstream and adapted to each SDK's conventions.

Modified (per SDK): Sandbox now tracks state via events (applyState / waitForState), Daytona/client constructs and owns the dispatcher, and list()/get()/create()/fork() wire the subscription manager into each Sandbox.

Because the clients repo had diverged from upstream (cursor-based list(), domain_allow_list, a clients-only pause(), secrets pagination, git ops), the port was done via careful 3‑way merges rather than a raw patch, preserving all existing clients-only features.

Key design

  • One connection, many subscriptions. A single Socket.IO connection per Daytona client; sandboxes multiplex through the subscription manager with reference-counted lifetimes.
  • Events with a polling safety net. waitForState resolves on a pushed sandbox.state.updated event, and a periodic poll guarantees completion even if an event is missed or WebSockets are down.
  • Single state-mutation path. All DTO population routes state through applyState; construction sets state directly (no waiters yet). No path mutates state without notifying waiters.

Robustness & correctness

  • No breaking changes for existing users. No public method signature or return type changed. The only signature changes are on internal Sandbox constructors (they take internal API-client types and are documented "obtain sandboxes via create/get/list").
  • Graceful WebSocket fallback. Connection and subscription are backgrounded and non-throwing — if Socket.IO can't connect (network blocked, WAF, server down), create/list/stop/start/waitFor* all still work via polling and never raise.
  • pause() uses events too. The clients-only pause() was migrated from its polling loop to the shared event-driven waiter.
  • stop() polling fallback hardened. waitUntilStopped now uses a 404‑safe refresh, so an ephemeral auto-delete sandbox that becomes destroyed after stop resolves cleanly under polling instead of throwing.
  • Custom base paths respected. The Socket.IO endpoint is derived from the configured API URL's path (rather than a hardcoded /api), so reverse-proxy deployments at https://host/prefix/api work. Standard …/api deployments are byte-identical to before.

Observability

The handshake now carries source + sdkVersion query params (mirroring the REST X-Daytona-Source / X-Daytona-SDK-Version headers). This lets Daytona measure fleet-wide WebSocket adoption/health server-side — counting Socket.IO connections keyed by SDK and version vs REST activity — with zero client-side telemetry.

New dependencies

  • Python: python-socketio (added to pyproject.toml; both lockfiles regenerated)
  • Java: io.socket:socket.io-client:2.1.2
  • Go: google/uuid promoted from indirect to direct
  • TypeScript (socket.io-client) and Ruby (websocket-client-simple) were already present

Summary by cubic

Adds opt-in real-time sandbox lifecycle updates across all SDKs via Socket.IO (polling remains the default and fallback). Also adds auto-pause interval support in API clients, makes deletion non-blocking by default with an optional wait, and fixes Go SDK secret state updates.

  • New Features

    • Event streaming is opt-in; defaults to polling. Enable via:
      • TypeScript: eventStreaming or DAYTONA_EVENT_STREAMING=true
      • Python (sync/async): event_streaming or DAYTONA_EVENT_STREAMING=true
      • Go: EventStreaming in config or DAYTONA_EVENT_STREAMING=true
      • Ruby: event_streaming or DAYTONA_EVENT_STREAMING=true
      • Java: DaytonaConfig.Builder.eventStreaming(true) or DAYTONA_EVENT_STREAMING=true
    • Event-driven lifecycle across TypeScript, Python (sync/async), Go, Ruby, and Java via a shared EventDispatcher (one connection per client) and EventSubscriptionManager (lazy per-sandbox fan-out with TTL; auto-cleanup on client close). Polling remains a safety net.
    • Deletion is fire-and-forget by default; pass a wait option/overload to block until destroyed (with optional timeout). pause() uses events; stop fallback is 404-safe.
    • API clients: new setAutoPauseInterval endpoint and autoPauseInterval on create and sandbox models.
    • Socket endpoint derives from the configured API base path; handshake includes source and sdkVersion. Exposes the sandbox toolbox proxy URL across SDKs.
    • Clients construct and own the dispatcher/subscription manager; TypeScript feature-detects engine unref so the connection doesn’t block process exit.
  • Bug Fixes

    • Go SDK: update secrets state atomically via updateFromAPIResponse.
    • Restored prior polling behavior in shared waitForState across SDKs.
    • Fixed a Go SDK unit test.

Written for commit 6d3e54d. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 52 files

Not reviewed (too large): openapi-specs/api.json (~3,502 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread sdk-ruby/lib/daytona/common/socketio_client.rb
Comment thread sdk-ruby/lib/daytona/common/event_dispatcher.rb
Comment thread sdk-typescript/src/utils/EventDispatcher.ts
Comment thread sdk-typescript/src/utils/EventDispatcher.ts
Comment thread sdk-python/src/daytona/internal/event_subscription_manager.py
Comment thread sdk-python/src/daytona/_sync/daytona.py Outdated
Comment thread sdk-python/src/daytona/_async/daytona.py Outdated
Comment thread sdk-java/src/main/java/io/daytona/sdk/internal/EventDispatcher.java Outdated
Comment thread sdk-typescript/src/utils/EventSubscriptionManager.ts
Comment thread sdk-go/pkg/common/event_dispatcher.go Outdated
@MDzaja MDzaja force-pushed the refactor/sdk-ws-sbx-events branch from 3be69a9 to 19e8b99 Compare July 7, 2026 15:44

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

14 issues found across 51 files

Not reviewed (too large): openapi-specs/api.json (~3,502 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread sdk-ruby/lib/daytona/common/socketio_client.rb Outdated
Comment thread sdk-ruby/lib/daytona/sandbox.rb
Comment thread sdk-java/src/main/java/io/daytona/sdk/Sandbox.java Outdated
Comment thread sdk-python/src/daytona/internal/event_subscription_manager.py
Comment thread sdk-python/src/daytona/internal/event_dispatcher.py
Comment thread sdk-go/pkg/common/socketio_client.go
Comment thread sdk-go/pkg/common/socketio_client.go
Comment thread sdk-typescript/src/Sandbox.ts
Comment thread artifacts/sdk-docs/python-sdk/async/async-sandbox.mdx
Comment thread sdk-python/src/daytona/internal/event_dispatcher.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 7 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread sdk-java/src/main/java/io/daytona/sdk/Sandbox.java Outdated
Comment thread sdk-go/pkg/common/event_dispatcher.go Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="sdk-typescript/src/utils/EventDispatcher.ts">

<violation number="1" location="sdk-typescript/src/utils/EventDispatcher.ts:352">
P2: A Node process can still hang after an idle disconnect/reconnect cycle: supportsBackgroundUnref stays true for the dispatcher, but the new Socket.IO Manager/engine handles are created with autoUnref off and this early return skips unref setup. Reapply unref setup for each new Manager open, or reset the capability/applied state when replacing the socket.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread sdk-typescript/src/utils/EventDispatcher.ts Outdated
@MDzaja MDzaja force-pushed the refactor/sdk-ws-sbx-events branch from c29c53c to 5f03799 Compare July 8, 2026 15:49
@MDzaja MDzaja requested review from Tpuljak and idagelic July 8, 2026 16:06
@MDzaja MDzaja force-pushed the refactor/sdk-ws-sbx-events branch from 5f03799 to 8dd754c Compare July 9, 2026 09:06
MDzaja added 3 commits July 13, 2026 12:51
Signed-off-by: MDzaja <mirkodzaja0@gmail.com>
…ault to polling

Signed-off-by: MDzaja <mirkodzaja0@gmail.com>
… across all SDKs

Signed-off-by: MDzaja <mirkodzaja0@gmail.com>
@MDzaja MDzaja force-pushed the refactor/sdk-ws-sbx-events branch from 8dd754c to 6d80f4a Compare July 13, 2026 15:17
Signed-off-by: MDzaja <mirkodzaja0@gmail.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 1 file (changes from recent commits).

You’re at about 93% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread sdk-go/pkg/daytona/sandbox.go Outdated
Signed-off-by: MDzaja <mirkodzaja0@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant