Skip to content

About

Managed Python extension child speaking newline-delimited JSON-RPC over stdio - DeepSeek Harness python bridge plugin

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

26 Commits

Folders and files

Repository files navigation

@nelsonlongxiang/dsh-python-bridge

English | 中文

Managed Python extension child speaking newline-delimited JSON-RPC 2.0 over stdio.

PythonBridge spawns one dsh-ext serve child through the dsh-subprocess seam and exchanges one JSON object per line: requests carry {"jsonrpc":"2.0","id",...,"method",...,"params":{}} and responses echo the id with result or error. The child is spawned lazily on the first call; any settled child (clean exit included) is dropped so the next call respawns; disposal quiesces through stdin EOF before escalating to tree termination.

This package is both a plain library and a DSH Cordis plugin: when loaded as a plugin it registers the pythonBridge service so other plugins can create bridges without manually threading the subprocess seam.

Library usage

Install from the private registry:

npm install @nelsonlongxiang/dsh-python-bridge
import { PythonBridge, PythonBridgeError, DEFAULT_EOF_GRACE_MS, resolveEofGrace } from '@nelsonlongxiang/dsh-python-bridge'
import type { SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'

// Inside a Cordis plugin: `ctx` is the injected Context carrying the subprocess seam.
const ctx = {
  subprocess: {
    spawn: (_spec: SubprocessSpawnSpec): SubprocessHandle => {
      throw new Error('not invoked in this snippet')
    },
  },
} as unknown as { subprocess: { spawn: (spec: SubprocessSpawnSpec) => SubprocessHandle } }

const bridge = new PythonBridge({
  argv: ['dsh-ext', 'serve'],
  cwd: process.cwd(),
  eofGraceMs: DEFAULT_EOF_GRACE_MS,
  spawn: (spec) => ctx.subprocess.spawn(spec),
})
const compositions = await bridge.call('graph/list', {})

A JSON-RPC error rejects with PythonBridgeError carrying the wire code, message, and data; transport or spawn failures reject with their underlying error and fail every in-flight call. The public helpers DEFAULT_EOF_GRACE_MS and resolveEofGrace(value, caller?) are the single source of the EOF-grace default and its validation: resolveEofGrace throws TypeError for non-finite, non-positive, or above-timer-ceiling values, and the pythonBridge service runs every eofGraceMs through it on create.

Plugin usage

Library or bundle: the boundary rule

  • A library ships a runtime API that other plugin packages import. It declares no dsh.bundle: dsh plugin add installs it as a plain dependency and prints a one-time orientation warning, activating no layer. A library never inserts plugin rows.
  • A bundle (plugin) ships a configuration layer — a patch file that inserts or overrides plugin rows, declared through dsh.bundle.patch. Only this format can be enabled by users directly; installing a plugin-shaped package without the declaration is the classic "installed but never activated" anomaly.

This package is both by design — the PythonBridge runtime API plus the pythonBridge host service row — so since 0.3.4 it declares dsh.bundle and dsh plugin add activates its layer automatically (verified from-scratch: the package joins dsh.profile.bundles and the row composes with no warning).

One row id, exactly one source. The loader hard-rejects a duplicate entry id at boot (verified: duplicate loader entry id: python-bridge crashes startup). If a composition already inserts the python-bridge row from another patch (precedent: @nelsonlongxiang/dsh-native-teams), that profile must NOT also install this package as a direct dependency — pick one source and remove the other; compositions should migrate to depending on this bundle and dropping their own python-bridge insert rows. History: 0.3.1 declared dsh.bundle while dsh-native-teams also inserted the row, and the collision crashed startup (rolled back in 0.3.2; 0.3.3 carried the revert plus fixes).

The bundle's own patch inserts exactly this row:

- insert:
    - id: python-bridge
      name: '@nelsonlongxiang/dsh-python-bridge'

Other plugins can then inject pythonBridge:

import { PythonBridge, PythonBridgeError } from '@nelsonlongxiang/dsh-python-bridge'

export const name = 'my-python-consumer'
export const inject = ['pythonBridge']

export function apply(ctx: any): void {
  const bridge = ctx.pythonBridge.create({
    argv: ['python', '-m', 'my_backend', 'serve'],
    cwd: process.cwd(),
    eofGraceMs: DEFAULT_EOF_GRACE_MS,
  })

  ctx.effect(() => () => { void bridge.dispose() }, 'my-consumer: bridge teardown')

  // Use `bridge.call(...)` to talk to the Python child.
}

The caller owns the returned bridge and is responsible for disposing it.

Model Experience

Bridge calls

What the model sees

Nothing from this transport: it carries host-side tool and service calls between the harness and the dsh-ext serve child. Consumers that route results into prompts own what the model sees.

Token effect

None — the bridge contributes no prompt content.

KV Cache effect

None — the bridge contributes no request content.

Known Limitations and Deferred Work

  • The child is single-threaded and process-local: a crash fails in-flight calls and the bridge respawns on the next call, with no auto-retry.
  • One bridge owns one child; sharing one dsh-ext serve child across domains is deferred until a third domain appears.

About

Managed Python extension child speaking newline-delimited JSON-RPC over stdio - DeepSeek Harness python bridge plugin

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages