feat(sdk): route native commands through run
This commit is contained in:
1 parent
b367e53a6c
commit
e122cfb627
47 files changed
+880
-1369
No files matched your search
@@ -6,17 +6,17 @@
|
||||
en: c97ffc5ce0f7db05
|
||||
zh: bdd91202075498a3
|
||||
/python-sdk-example/run-the-minimal-agent:
|
||||
en: 4b79fd6576e2759d
|
||||
en: ac09bb6bc3ab76a0
|
||||
zh: 6258540acaf4bc37
|
||||
/python-sdk-example/compact-a-full-sdk-session:
|
||||
en: ad4d67225570ed01
|
||||
zh: 0d59f6dac40129d6
|
||||
en: 7749773f97211ac1
|
||||
zh: 594348ae66a2ef6c
|
||||
/python-sdk-example/change-session-permissions:
|
||||
en: 3471642d8444d0e9
|
||||
zh: 7831f28295a6e68a
|
||||
en: 1fe5e06c6842a53b
|
||||
zh: ff0836d8cffaeef7
|
||||
/python-sdk-example/review-a-plan:
|
||||
en: 46758c022608af0f
|
||||
zh: 09b5a59176c11176
|
||||
en: 2596f68bb55082c8
|
||||
zh: 1e40ca75e1a0f9b9
|
||||
/python-sdk-example/run-a-goal:
|
||||
en: ae73e9fd9d402047
|
||||
zh: f9694bc6a6e12929
|
||||
|
||||
@@ -25,14 +25,14 @@ The shipped [`@deepseek-ai/dsh-sdk-minimal` bundle](../../../packages/bundle/sdk
|
||||
- `read_image` for PNG/JPEG/WebP/GIF files, with an image-capable model
|
||||
- `working_directory` to inspect or change the Session directory
|
||||
|
||||
The bundle does not include `dsh-base`, so every additional row is an explicit profile change. Runtime context, text file tools, local instruction discovery, settings, managed credentials, telemetry, Web tools, subagents, and the full default tool roster are absent. The tree retains SDK startup and JSON-RPC serving, one environment-configured DeepSeek adapter, local execution, and JSONL persistence. [Automatic context compaction](../../../packages/bundle/sdk-minimal/README.md#model-experience) summarizes older history through an additional model request while preserving the complete log. SDK prompt text does not execute slash commands.
|
||||
The bundle does not include `dsh-base`, so every additional row is an explicit profile change. Runtime context, text file tools, local instruction discovery, settings, managed credentials, telemetry, Web tools, subagents, and the full default tool roster are absent. The tree retains SDK startup and JSON-RPC serving, one environment-configured DeepSeek adapter, local execution, and JSONL persistence. [Automatic context compaction](../../../packages/bundle/sdk-minimal/README.md#model-experience) summarizes older history through an additional model request while preserving the complete log. This minimal composition leaves unsupported slash input on the ordinary prompt path.
|
||||
|
||||
The persistent PTY can modify any path available to the runtime process, so use a disposable checkout or container.
|
||||
|
||||
<a id="compact-a-full-sdk-session"></a>
|
||||
## Compact a full SDK Session
|
||||
|
||||
The [compaction example](compact.py) uses the full `sdk` profile. It reads `README.md` from the selected workspace into a `Session.query()` prompt, sends `/compact` through the same method, then asks a follow-up using the retained context. Each query is paired with `Session.receive_response()`. It uses the same model credentials and `DSH_MODEL` default as the minimal example, and requires an explicit home:
|
||||
The [compaction example](compact.py) uses the full `sdk` profile. It reads `README.md` from the selected workspace into a `Session.run()` prompt, executes `/compact` through the same method, then asks a follow-up using the retained context. It uses the same model credentials and `DSH_MODEL` default as the minimal example, and requires an explicit home:
|
||||
|
||||
```sh
|
||||
python python/sdk/examples/compact.py \
|
||||
@@ -40,12 +40,12 @@ python python/sdk/examples/compact.py \
|
||||
--workspace /absolute/path/to/disposable-workspace
|
||||
```
|
||||
|
||||
The script consumes notifications and prints the command id and outcome separately from the model's responses. A failed turn or command stops the example. See [Query and response](../README.md#query-and-response) for completion and error behavior; `minimal.py` retains its `run()` entry and does not enable command execution.
|
||||
The script prints command ids and outcomes separately from model responses. A failed turn or command stops the example. See [Run text and commands](../README.md#run-text-and-commands) for completion and error behavior; `minimal.py` uses the same `run()` entry, with capabilities supplied by its selected profile.
|
||||
|
||||
<a id="change-session-permissions"></a>
|
||||
## Change Session permissions
|
||||
|
||||
The [permission example](permission.py) starts the full `sdk` profile with a `workspace-write` default, sends `/permission <preset>`, then submits an ordinary query on the same Session. Supply the preset explicitly:
|
||||
The [permission example](permission.py) starts the full `sdk` profile with a `workspace-write` default, sends `/permission <preset>`, then runs ordinary text on the same Session. Supply the preset explicitly:
|
||||
|
||||
```sh
|
||||
python python/sdk/examples/permission.py \
|
||||
@@ -54,12 +54,12 @@ python python/sdk/examples/permission.py \
|
||||
--preset read-only
|
||||
```
|
||||
|
||||
The command changes the Session's policy while the printed launch setting remains `workspace-write`. The later query uses the Session's current policy. Bare `/permission` is ordinary text, and unknown presets produce a command error. See [Query and response](../README.md#query-and-response) for result and state semantics.
|
||||
The command changes the Session's policy while the printed launch setting remains `workspace-write`. The later input uses the Session's current policy. Bare `/permission` is ordinary text, and unknown presets produce a command error. See [Run text and commands](../README.md#run-text-and-commands) for result and state semantics.
|
||||
|
||||
<a id="review-a-plan"></a>
|
||||
## Review a plan
|
||||
|
||||
The [plan example](plan.py) sends `/plan <task>` through query and keeps the Session notification subscription and question handler alive after command completion. Enter `a` to approve, `k` to give feedback, or `d` to dismiss; EOF dismisses without approving. Approval allows implementation from a later model step, so choose an isolated workspace.
|
||||
The [plan example](plan.py) sends `/plan <task>` through `run()` and keeps the Session notification subscription and question handler alive after command completion. Enter `a` to approve, `k` to give feedback, or `d` to dismiss; EOF dismisses without approving. Approval allows implementation from a later model step, so choose an isolated workspace.
|
||||
|
||||
```sh
|
||||
python python/sdk/examples/plan.py \
|
||||
|
||||
@@ -32,7 +32,7 @@ python python/sdk/examples/minimal.py \
|
||||
<a id="compact-a-full-sdk-session"></a>
|
||||
## 压缩完整 SDK Session
|
||||
|
||||
[压缩示例](compact.py)使用完整 `sdk` profile。它将所选工作区的 `README.md` 读入 `Session.query()` 输入,通过同一方法发送 `/compact`,随后使用保留的上下文继续提问。每次 query 都配对调用 `Session.receive_response()`。模型凭据和 `DSH_MODEL` 默认值与极简示例一致,并且要求显式指定 home:
|
||||
[压缩示例](compact.py)使用完整 `sdk` profile。它将所选工作区的 `README.md` 读入 `Session.run()` 输入,通过同一方法执行 `/compact`,随后使用保留的上下文继续提问。模型凭据和 `DSH_MODEL` 默认值与极简示例一致,并且要求显式指定 home:
|
||||
|
||||
```sh
|
||||
python python/sdk/examples/compact.py \
|
||||
@@ -40,12 +40,12 @@ python python/sdk/examples/compact.py \
|
||||
--workspace /absolute/path/to/disposable-workspace
|
||||
```
|
||||
|
||||
脚本消费通知,并将命令 id 和结果与模型回复分开打印。轮次或命令失败时,示例会停止。完成和错误行为见[查询与接收](../README.zh.md#query-and-response);`minimal.py` 保留其 `run()` 入口,不启用命令执行。
|
||||
脚本将命令 id 和结果与模型回复分开打印。轮次或命令失败时,示例会停止。完成和错误行为见[运行文本与命令](../README.zh.md#run-text-and-commands);`minimal.py` 使用同一个 `run()` 入口,能力由所选 profile 提供。
|
||||
|
||||
<a id="change-session-permissions"></a>
|
||||
## 修改 Session 权限
|
||||
|
||||
[权限示例](permission.py)以 `workspace-write` 为默认值启动完整 `sdk` profile,发送 `/permission <preset>`,随后在同一个 Session 中提交普通 query。请显式指定 preset:
|
||||
[权限示例](permission.py)以 `workspace-write` 为默认值启动完整 `sdk` profile,发送 `/permission <preset>`,随后在同一个 Session 中运行普通文本。请显式指定 preset:
|
||||
|
||||
```sh
|
||||
python python/sdk/examples/permission.py \
|
||||
@@ -54,12 +54,12 @@ python python/sdk/examples/permission.py \
|
||||
--preset read-only
|
||||
```
|
||||
|
||||
命令会修改 Session 的策略,而打印出的启动设置仍为 `workspace-write`。后续 query 使用 Session 的当前策略。裸 `/permission` 是普通文本,未知 preset 返回命令错误。结果和状态语义见[查询与接收](../README.zh.md#query-and-response)。
|
||||
命令会修改 Session 的策略,而打印出的启动设置仍为 `workspace-write`。后续输入使用 Session 的当前策略。裸 `/permission` 是普通文本,未知 preset 返回命令错误。结果和状态语义见[运行文本与命令](../README.zh.md#run-text-and-commands)。
|
||||
|
||||
<a id="review-a-plan"></a>
|
||||
## 审查计划
|
||||
|
||||
[计划示例](plan.py) 通过 query 发送 `/plan <task>`,并在命令完成后保持 Session 通知订阅和问答处理器运行。输入 `a` 批准、`k` 提供反馈、`d` 关闭;EOF 也会关闭而不批准。批准后模型可以从后续步骤开始实施,因此应选择隔离的工作区。
|
||||
[计划示例](plan.py) 通过 `run()` 发送 `/plan <task>`,并在命令完成后保持 Session 通知订阅和问答处理器运行。输入 `a` 批准、`k` 提供反馈、`d` 关闭;EOF 也会关闭而不批准。批准后模型可以从后续步骤开始实施,因此应选择隔离的工作区。
|
||||
|
||||
```sh
|
||||
python python/sdk/examples/plan.py \
|
||||
|
||||
@@ -7,22 +7,21 @@ import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from deepseek_harness import CommandExecution, DeepSeekHarness, RunResult, Session
|
||||
from deepseek_harness import CommandExecution, DeepSeekHarness, RunResult
|
||||
|
||||
|
||||
def receive(session: Session) -> None:
|
||||
"""Consume notifications and print the query's command or activity result."""
|
||||
for result in session.receive_response():
|
||||
if isinstance(result, RunResult):
|
||||
if result.finish_reason != "completed":
|
||||
raise RuntimeError(f"Agent turn ended with {result.finish_reason}: {result.final_response}")
|
||||
print(result.final_response)
|
||||
elif isinstance(result, CommandExecution):
|
||||
print(f"Command {result.command_id}: {result.result.kind}")
|
||||
if result.result.kind == "error":
|
||||
raise RuntimeError(result.result.text)
|
||||
if result.result.text is not None:
|
||||
print(result.result.text)
|
||||
def display(result: RunResult | CommandExecution) -> None:
|
||||
"""Print the input's result and stop on a reported failure."""
|
||||
if isinstance(result, CommandExecution):
|
||||
print(f"Command {result.command_id}: {result.result.kind}")
|
||||
if result.result.kind == "error":
|
||||
raise RuntimeError(result.result.text)
|
||||
if result.result.text is not None:
|
||||
print(result.result.text)
|
||||
else:
|
||||
if result.finish_reason != "completed":
|
||||
raise RuntimeError(f"Agent turn ended with {result.finish_reason}: {result.final_response}")
|
||||
print(result.final_response)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -50,15 +49,12 @@ def main() -> None:
|
||||
max_tokens=args.max_tokens,
|
||||
) as harness:
|
||||
session = harness.start_session(args.session_id)
|
||||
session.query(
|
||||
display(session.run(
|
||||
"Read this project README and summarize its purpose and development commands without using tools.\n\n"
|
||||
+ readme.read_text(encoding="utf-8")
|
||||
)
|
||||
receive(session)
|
||||
session.query("/compact")
|
||||
receive(session)
|
||||
session.query("What are the project's purpose and development commands? Use the earlier README without tools.")
|
||||
receive(session)
|
||||
))
|
||||
display(session.run("/compact"))
|
||||
display(session.run("What are the project's purpose and development commands? Use the earlier README without tools."))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -52,12 +52,12 @@ def main() -> None:
|
||||
session = harness.start_session()
|
||||
with harness.client.subscribe_notifications(lambda notice: notice.payload.get("sessionId") == session.id) as notifications:
|
||||
deadline = time.monotonic() + args.timeout_seconds
|
||||
session.query(f"/goal {args.objective}")
|
||||
for message in session.receive_response():
|
||||
if isinstance(message, CommandExecution):
|
||||
print(message.result.text, flush=True)
|
||||
if message.result.kind == "error":
|
||||
raise RuntimeError(message.result.text)
|
||||
execution = session.run(f"/goal {args.objective}")
|
||||
if not isinstance(execution, CommandExecution):
|
||||
raise RuntimeError("The SDK profile did not execute the requested command")
|
||||
print(execution.result.text, flush=True)
|
||||
if execution.result.kind == "error":
|
||||
raise RuntimeError(execution.result.text)
|
||||
while True:
|
||||
event = display(notifications.next(timeout_seconds=max(0, deadline - time.monotonic())))
|
||||
if event is not None and event["type"] == "goal/change" and event["data"]["operation"] in {
|
||||
|
||||
@@ -7,7 +7,7 @@ import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from deepseek_harness import DeepSeekHarness
|
||||
from deepseek_harness import CommandExecution, DeepSeekHarness
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -41,7 +41,12 @@ def main() -> None:
|
||||
profile=args.profile,
|
||||
) as harness:
|
||||
result = harness.run(args.prompt, session_id=args.session_id)
|
||||
print(result.final_response)
|
||||
if isinstance(result, CommandExecution):
|
||||
if result.result.kind == "error":
|
||||
raise RuntimeError(result.result.text)
|
||||
print(result.result.text or "")
|
||||
else:
|
||||
print(result.final_response)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -5,30 +5,27 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from contextlib import closing
|
||||
from pathlib import Path
|
||||
|
||||
from deepseek_harness import CommandExecution, DeepSeekHarness, RunResult, Session
|
||||
from deepseek_harness import CommandExecution, DeepSeekHarness, RunResult
|
||||
|
||||
|
||||
def receive(session: Session) -> None:
|
||||
"""Print a command or activity result and stop on a reported failure."""
|
||||
with closing(session.receive_response()) as responses:
|
||||
for result in responses:
|
||||
if isinstance(result, CommandExecution):
|
||||
print(f"Command {result.command_id}: {result.result.kind}")
|
||||
if result.result.kind == "error":
|
||||
raise RuntimeError(result.result.text)
|
||||
if result.result.text is not None:
|
||||
print(result.result.text)
|
||||
elif isinstance(result, RunResult):
|
||||
if result.finish_reason != "completed":
|
||||
raise RuntimeError(f"Agent turn ended with {result.finish_reason}")
|
||||
print(result.final_response)
|
||||
def display(result: RunResult | CommandExecution) -> None:
|
||||
"""Print the input's result and stop on a reported failure."""
|
||||
if isinstance(result, CommandExecution):
|
||||
print(f"Command {result.command_id}: {result.result.kind}")
|
||||
if result.result.kind == "error":
|
||||
raise RuntimeError(result.result.text)
|
||||
if result.result.text is not None:
|
||||
print(result.result.text)
|
||||
else:
|
||||
if result.finish_reason != "completed":
|
||||
raise RuntimeError(f"Agent turn ended with {result.finish_reason}: {result.final_response}")
|
||||
print(result.final_response)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Send an explicit permission command, then query the same Session."""
|
||||
"""Send an explicit permission command, then run another prompt in the same Session."""
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--dsh-home", required=True, type=Path)
|
||||
parser.add_argument("--workspace", type=Path, default=Path.cwd())
|
||||
@@ -45,13 +42,10 @@ def main() -> None:
|
||||
env={"DSH_PERMISSION_MODE": "workspace-write"},
|
||||
) as harness:
|
||||
session = harness.start_session()
|
||||
session.query("Summarize the current file and approval policy in one sentence. Do not use tools.")
|
||||
receive(session)
|
||||
session.query(f"/permission {args.preset}")
|
||||
receive(session)
|
||||
display(session.run("Summarize the current file and approval policy in one sentence. Do not use tools."))
|
||||
display(session.run(f"/permission {args.preset}"))
|
||||
print(f"Launch setting: {harness.config.env['DSH_PERMISSION_MODE']}")
|
||||
session.query("Summarize the current file and approval policy again. Do not use tools.")
|
||||
receive(session)
|
||||
display(session.run("Summarize the current file and approval policy again. Do not use tools."))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -72,12 +72,12 @@ def main() -> None:
|
||||
) as harness:
|
||||
session = harness.start_session()
|
||||
with closing(harness.client.subscribe_session_notifications(session.id)) as notifications:
|
||||
session.query(f"/plan {args.task}")
|
||||
for message in session.receive_response():
|
||||
if isinstance(message, CommandExecution):
|
||||
print(message.result.text)
|
||||
if message.result.kind == "error":
|
||||
raise RuntimeError(message.result.text)
|
||||
execution = session.run(f"/plan {args.task}")
|
||||
if not isinstance(execution, CommandExecution):
|
||||
raise RuntimeError("The SDK profile did not execute the requested command")
|
||||
print(execution.result.text, flush=True)
|
||||
if execution.result.kind == "error":
|
||||
raise RuntimeError(execution.result.text)
|
||||
harness.client.request(
|
||||
"session/wait", {"sessionId": session.id}, response_model=WaitResult,
|
||||
on_notification=lambda notice: display(notice, session.id), notification_subscription=notifications,
|
||||
|
||||
Reference in new issue
Block a user