Prechádzať zdrojové kódy

refactor(python): launch the minimal example through sdk-minimal

Make minimal.py select the shipped sdk-minimal profile directly and pass its selected model into the profile-owned adapter catalog. The Python SDK still starts only the bundled dsh CLI with an explicit Harness home; it no longer supplies an invocation overlay for this mode.

Drive both the source keyless process test and installed-wheel smoke through the same named profile. The keyless test pins the generated profile manifest and exact two-tool model request, while the packaged smoke keeps the persistent-shell, editor, session-log, and model-visible snapshot evidence.

Delete minimal.patch.yml and the unused complete-config/replay fixtures. Their composition now has one owner in @deepseek-ai/dsh-sdk-minimal, so the example and tests cannot drift into separate launch trees.
Tianyi Cui 1 mesiac pred
rodič
commit
79a8f667f7

+ 0 - 90
examples/python-sdk-agent/minimal.cordis.yml

@@ -1,90 +0,0 @@
-# Complete minimal-agent composition fixture for lower-level snapshot tests.
-# Python users apply `minimal.patch.yml` to `dsh --profile sdk`. This fixture
-# retains the same two-tool behavior without defining a supported launch path.
-
-- id: sdk-jsonrpc-server
-  name: '@deepseek-ai/dsh-sdk-jsonrpc-server'
-  config:
-    maxTokensAsSuccess: false
-
-- id: deepseek-llm-api-extensions
-  name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'
-
-- id: session-log-deepseek
-  name: '@deepseek-ai/dsh-session-log-deepseek'
-
-- id: plugin-package-inventory-deepseek
-  name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
-
-- id: llm-deepseek
-  name: '@deepseek-ai/dsh-llm-deepseek'
-  config:
-    apiKeyEnv: DEEPSEEK_API_KEY
-    streamIdleTimeoutMs: 172800000
-    models:
-      - id: !!js process.env.DSH_MODEL ?? 'deepseek-v4-flash'
-        contextWindow: !!js Number(process.env.DSH_CONTEXT_WINDOW ?? 1000000)
-
-- id: sandbox
-  name: '@deepseek-ai/dsh-sandbox-local'
-
-- id: sandbox-policy
-  name: '@deepseek-ai/dsh-sandbox-policy'
-  config:
-    mode: danger-full-access
-    workspaceRoot: !!js process.env.DSH_CWD ?? process.cwd()
-
-- id: subprocess
-  name: '@deepseek-ai/dsh-subprocess-local'
-
-- id: pty
-  name: '@deepseek-ai/dsh-terminal'
-
-- id: terminal-bash
-  name: '@deepseek-ai/dsh-terminal-bash'
-  config:
-    timeoutMs: 300000
-
-# The editor uses the bare local filesystem; persistent Bash still consumes the
-# shared danger-full-access sandbox policy above.
-- id: fs-local
-  name: '@deepseek-ai/dsh-fs-local'
-  config:
-    cwd: !!js process.env.DSH_CWD ?? process.cwd()
-
-- id: agent-spine
-  name: '@deepseek-ai/dsh-agent-spine-demo'
-  config:
-    includeHarnessIdentity: false
-    includeRuntimeContext: false
-    persona: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a helpful software engineer assistant.'
-    workspaceContext: false
-    skills:
-      enabled: false
-    toolBash: false
-    toolJobs: false
-
-- id: persistent-bash
-  name: '@deepseek-ai/dsh-tool-bash-persistent'
-  config:
-    timeoutMs: 300000
-    description: |-
-      Run commands in a bash shell
-      * When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
-      * You don't have access to the internet via this tool.
-      * You do have access to a mirror of common linux and python packages via apt and pip.
-      * State is persistent across command calls and discussions with the user.
-      * To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
-      * Please avoid commands that may produce a very large amount of output.
-      * Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background.
-
-- id: str-replace-editor
-  name: '@deepseek-ai/dsh-tool-str-replace-editor'
-  config:
-    maxOutputChars: 16000
-
-- id: sessions
-  name: '@deepseek-ai/dsh-session-persistence-jsonl'
-  config:
-    root: !!js process.env.DSH_SESSION_ROOT ?? './.sessions'
-    compression: none

+ 0 - 47
examples/python-sdk-agent/minimal.patch.yml

@@ -1,47 +0,0 @@
-# Minimal Python SDK overlay for `dsh --profile sdk`. The SDK server applies
-# the model-facing allowlist to every root session, so later base-bundle tools
-# cannot appear implicitly. The profile still owns JSON-RPC and persistence.
-
-- id: system-prompt
-  config:
-    includeHarnessIdentity: false
-    includeRuntimeContext: false
-    persona: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a helpful software engineer assistant.'
-    personaComplete: true
-
-- id: sdk-jsonrpc-server
-  config:
-    maxTokensAsSuccess: !!js "process.env.DSH_MAX_TOKENS_AS_SUCCESS === undefined ? true : JSON.parse(process.env.DSH_MAX_TOKENS_AS_SUCCESS)"
-    toolFilter:
-      allow:
-        - bash
-        - str_replace_editor
-
-# Remove non-tool model inputs and turn rewriting, plus the one-shot Bash row
-# that would conflict with the persistent Bash tool inserted below.
-- id: agent-instructions
-  disabled: true
-- id: tool-bash
-  disabled: true
-- id: compaction-basic
-  disabled: true
-- id: command-compact
-  disabled: true
-- id: tool-result-pruner
-  disabled: true
-
-- id: tool-str-replace-editor
-  config:
-    maxOutputChars: 16000
-
-- insert:
-    - id: pty
-      name: '@deepseek-ai/dsh-terminal'
-    - id: terminal-bash
-      name: '@deepseek-ai/dsh-terminal-bash'
-      config:
-        timeoutMs: 300000
-    - id: persistent-bash
-      name: '@deepseek-ai/dsh-tool-bash-persistent'
-      config:
-        timeoutMs: 300000

+ 2 - 5
examples/python-sdk-agent/minimal.py

@@ -10,9 +10,6 @@ from pathlib import Path
 from deepseek_harness import DeepSeekHarness
 
 
-PATCH = Path(__file__).with_name("minimal.patch.yml")
-
-
 def main() -> None:
     """Parse one task and print the agent's final response."""
     parser = argparse.ArgumentParser()
@@ -24,7 +21,7 @@ def main() -> None:
         type=Path,
         default=Path(configured_home) if configured_home.strip() else None,
     )
-    parser.add_argument("--profile", default="sdk")
+    parser.add_argument("--profile", default="sdk-minimal")
     parser.add_argument("--session-id")
     parser.add_argument("--provider", default="deepseek-official")
     parser.add_argument("--model", default=os.environ.get("DSH_MODEL", "deepseek-v4-flash"))
@@ -42,7 +39,7 @@ def main() -> None:
         cwd=str(workspace),
         dsh_home=str(dsh_home),
         profile=args.profile,
-        patches=(str(PATCH.resolve()),),
+        env={"DSH_MODEL": args.model},
     ) as harness:
         result = harness.run(args.prompt, session_id=args.session_id)
     print(result.final_response)

+ 0 - 20
examples/python-sdk-agent/minimal.snapshot.cordis.yml

@@ -1,20 +0,0 @@
-# Keyless replay keeps the complete minimal composition intact and replaces
-# only its live DeepSeek adapter with the fixture-backed provider. The replay
-# catalog claims the same route initialized by the SDK.
-- id: base
-  name: '@deepseek-ai/cordis-plugin-include'
-  config:
-    path: ./minimal.cordis.yml
-    patches:
-      - id: llm-deepseek
-        name: '@deepseek-ai/dsh-llm-deepseek'
-        disabled: true
-      - insert:
-          - id: llm-replay
-            name: '@deepseek-ai/dsh-llm-replay'
-            config:
-              providers:
-                - id: deepseek-official
-                  name: DeepSeek
-                  models:
-                    - id: deepseek-v4-flash

+ 10 - 7
examples/python-sdk-agent/tests/keyless-smoke.e2e.ts

@@ -10,7 +10,6 @@ import { describe, expect, it } from 'vitest'
 
 const binScript = fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url))
 const patchPath = fileURLToPath(new URL('./keyless.patch.yml', import.meta.url))
-const minimalPatchPath = fileURLToPath(new URL('../minimal.patch.yml', import.meta.url))
 const repoRoot = fileURLToPath(new URL('../../..', import.meta.url))
 const decompress = promisify(zstdDecompress)
 
@@ -181,7 +180,7 @@ describe('Python SDK dsh profile keyless smoke', () => {
     }
   }, 40_000)
 
-  it('keeps the minimal overlay on an explicit model-facing allowlist', async () => {
+  it('boots the standalone minimal profile with its exact model-facing roster', async () => {
     const root = await mkdtemp(join(tmpdir(), 'dsh-python-sdk-minimal-'))
     const modelRequests: Record<string, unknown>[] = []
     const modelServer = createServer((request, response) => {
@@ -205,16 +204,13 @@ describe('Python SDK dsh profile keyless smoke', () => {
       'tsx/esm',
       binScript,
       '--profile',
-      'sdk',
-      '--patch',
-      minimalPatchPath,
+      'sdk-minimal',
     ], {
       cwd: repoRoot,
       env: {
         DSH_HOME: join(root, '.dsh'),
-        DSH_PERMISSION_MODE: 'danger-full-access',
+        DSH_MODEL: 'deepseek-v4-pro',
         DSH_SYSTEM_PROMPT: 'Minimal allowlist prompt.',
-        DSH_TELEMETRY_DISABLED: '1',
         DEEPSEEK_API_KEY: 'keyless-smoke-no-call',
         DEEPSEEK_BASE_URL: `http://127.0.0.1:${address.port}`,
       },
@@ -259,6 +255,13 @@ describe('Python SDK dsh profile keyless smoke', () => {
       }
       expect(request.messages?.[0]).toMatchObject({ role: 'system', content: 'Minimal allowlist prompt.' })
       expect(request.tools?.map(tool => tool.function?.name).sort()).toEqual(['bash', 'str_replace_editor'])
+      const profile = JSON.parse(
+        await readFile(join(root, '.dsh', 'profiles', 'sdk-minimal', 'package.json'), 'utf8'),
+      ) as { dsh?: { profile?: { bundles?: string[]; patchReload?: string } } }
+      expect(profile.dsh?.profile).toEqual({
+        bundles: ['@deepseek-ai/dsh-sdk-minimal'],
+        patchReload: 'startup',
+      })
 
       child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 3, method: 'shutdown' })}\n`)
       await waitForLine(lines, value => value.id === 3, () => stderr)

+ 6 - 72
scripts/smoke-python-runtime.py

@@ -46,14 +46,6 @@ MINIMAL_BASH_COMMAND = (
     "printf 'COUNT=%s CWD=%s\\n' \"$counter\" \"$PWD\"; "
     "if [ \"$counter\" -eq 1 ]; then cd /tmp; fi"
 )
-MINIMAL_BASH_DESCRIPTION = """Run commands in a bash shell
-* When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
-* You don't have access to the internet via this tool.
-* You do have access to a mirror of common linux and python packages via apt and pip.
-* State is persistent across command calls and discussions with the user.
-* To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
-* Please avoid commands that may produce a very large amount of output.
-* Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background."""
 LEGACY_CUSTOM_DISABLED_ROWS = (
     "agent-instructions",
     "goal",
@@ -121,10 +113,6 @@ RESTART_SNAPSHOT_DIRECTORY = (
     Path(__file__).resolve().parent / "snapshots" / "python-sdk-single-exe" / "restart"
 )
 RESTART_SNAPSHOT_FILENAMES = ("result.json", "requests.json", "session.1.jsonl", "session.2.jsonl")
-# The agent loop's dynamic runtime-context snapshot is the one model-visible message this
-# expected output cannot carry: the same composition emits it on macOS and not on Linux
-# (deepseek-harness#2488), and the file must replay on both. Everything else is compared.
-RUNTIME_CONTEXT_PREFIX = "Current runtime context"
 MCP_SERVER_SCRIPT = """\
 import json
 import os
@@ -972,7 +960,7 @@ def smoke_sdk_custom(base_url: str, executable: Path) -> None:
 
 
 def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) -> None:
-    """Exercise the checked-in minimal composition through the packaged executable."""
+    """Exercise the shipped standalone minimal profile through the packaged executable."""
     from deepseek_harness import DeepSeekHarness
 
     # One mock model serves every scenario of a run, so the snapshot takes this turn's slice.
@@ -983,59 +971,15 @@ def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) -
         prompt = f"{MINIMAL_PROMPT}\n{MINIMAL_EDITOR_PATH_PREFIX}{editor_path}"
         dsh_home = root / "home"
         sessions = dsh_home / "sessions"
-        disabled = [
-            "agent-instructions",
-            "tool-bash",
-            "compaction-basic",
-            "command-compact",
-            "tool-result-pruner",
-        ]
-        patch = write_profile_patch(root, "minimal.patch.yml", sessions, [
-            {
-                "id": "system-prompt",
-                "config": {
-                    "includeHarnessIdentity": False,
-                    "includeRuntimeContext": False,
-                    "persona": "You are a helpful software engineer assistant.",
-                    "personaComplete": True,
-                },
-            },
-            {
-                "id": "sdk-jsonrpc-server",
-                "config": {
-                    "maxTokensAsSuccess": True,
-                    "toolFilter": {"allow": ["bash", "str_replace_editor"]},
-                },
-            },
-            *({"id": row_id, "disabled": True} for row_id in disabled),
-            {"id": "tool-str-replace-editor", "config": {"maxOutputChars": 16000}},
-            {"insert": [
-                {"id": "pty", "name": "@deepseek-ai/dsh-terminal"},
-                {
-                    "id": "terminal-bash",
-                    "name": "@deepseek-ai/dsh-terminal-bash",
-                    "config": {"timeoutMs": 300000},
-                },
-                {
-                    "id": "persistent-bash",
-                    "name": "@deepseek-ai/dsh-tool-bash-persistent",
-                    "config": {
-                        "timeoutMs": 300000,
-                        "description": MINIMAL_BASH_DESCRIPTION,
-                    },
-                },
-            ]},
-        ])
         with DeepSeekHarness(
             provider="deepseek-official",
             model="smoke-model",
             cwd=str(root),
             dsh_bin=str(executable),
             dsh_home=str(dsh_home),
-            patches=(str(patch),),
+            profile="sdk-minimal",
             env={
-                "DSH_PERMISSION_MODE": "danger-full-access",
-                "DSH_TELEMETRY_DISABLED": "1",
+                "DSH_MODEL": "smoke-model",
             },
             api_key="sk-keyless-smoke",
             base_url=base_url,
@@ -1550,9 +1494,9 @@ def build_minimal_snapshot_files(
     Every assembled system prompt, advertised tool schema, and system or user message is
     kept verbatim: they carry what the deployment actually shows the model, so a plugin
     that contributes an unintended system section or user message cannot pass unnoticed.
-    Assistant and tool payloads keep only their call identity, and the dynamic
-    runtime-context snapshot is dropped, because their text differs across the platforms
-    this expected output must replay on.
+    Assistant and tool payloads keep only their call identity because their text differs
+    across the platforms this expected output must replay on. The shipped profile omits
+    dynamic runtime context, so every message it emits is compared.
     """
     snapshot = []
     for body in requests:
@@ -1564,21 +1508,11 @@ def build_minimal_snapshot_files(
             "messages": [
                 minimal_snapshot_message(message, cwd)
                 for message in messages
-                if not is_runtime_context_message(message)
             ],
         })
     return {"model-visible.json": json.dumps(snapshot, indent=2, ensure_ascii=False) + "\n"}
 
 
-def is_runtime_context_message(message: object) -> bool:
-    """Identify the agent loop's dynamic runtime-context snapshot, current or cleared."""
-    return (
-        isinstance(message, dict)
-        and message.get("role") == "user"
-        and message_text(message.get("content")).startswith(RUNTIME_CONTEXT_PREFIX)
-    )
-
-
 def minimal_snapshot_message(message: object, cwd: Path) -> dict[str, object]:
     """Reduce one model-visible message to its stable, behavior-carrying parts."""
     if not isinstance(message, dict):