fix(sdk): keep query internal and return session identity
This commit is contained in:
1 parent
9976a87e97
commit
1950259ce1
25 files changed
+229
-111
No files matched your search
@@ -12,8 +12,8 @@
|
||||
en: ac793c588b13ce87
|
||||
zh: 502015cda46de3ac
|
||||
/deepseek-harness-python-sdk/run-text-and-commands:
|
||||
en: 4a70013e5ba565a9
|
||||
zh: 00bb9b06d38f91ba
|
||||
en: 477bb559cbf13f84
|
||||
zh: c5825be4ae2f1508
|
||||
/deepseek-harness-python-sdk/client-approvals:
|
||||
en: fc69be1beb8abff1
|
||||
zh: 91b56781931a6f68
|
||||
|
||||
@@ -61,21 +61,21 @@ with DeepSeekHarness(dsh_home="/absolute/path/to/saved-home") as harness:
|
||||
<a id="run-text-and-commands"></a>
|
||||
## Run text and commands
|
||||
|
||||
`session.run(input, on_notification=..., delivery="queue")` is the high-level input entry for strings and content-block arrays. Both use the runtime's [supported command forms and Desktop aliases](../../packages/sdk/server/README.md#command-input); unrecognized input remains an ordinary prompt with its original block order. `delivery="steer"` targets the next available step for ordinary input, including images. Command admission and timing remain owned by the native handler.
|
||||
`session.run(input, on_notification=..., delivery="queue")` is the public prompt-or-command input entry for strings and content-block arrays. Both use the runtime's [supported command forms and Desktop aliases](../../packages/sdk/server/README.md#command-input); unrecognized input remains an ordinary prompt with its original block order. `delivery="steer"` targets the next available step for ordinary input, including images. Command admission and timing remain owned by the native handler.
|
||||
|
||||
The call returns `RunResult` for an ordinary prompt's inbox-receipt-to-idle interval, or `CommandExecution` after a command settles. Use `isinstance(result, RunResult)` before reading `final_response`; otherwise inspect the command's `result.kind`. A command retains its `command_id` and optional `source_event_seq`, rather than selecting an earlier assistant reply.
|
||||
The call returns `RunResult` for an ordinary prompt's inbox-receipt-to-idle interval, or `RunCommandResult` after a command settles. Both results include `session_id`, including when `harness.run()` creates a fresh Session. `RunCommandResult` extends the native `CommandExecution` with that Session identity. Use `isinstance(result, RunResult)` before reading `final_response`; otherwise inspect the command's `result.kind`. A command retains its `command_id` and optional `source_event_seq`, rather than selecting an earlier assistant reply.
|
||||
|
||||
Content-block arrays use the same command routing as strings. Both `session.run([{"type": "text", "text": "/compact"}])` and `session.run("/compact")` invoke native compaction and return `CommandExecution`. To bypass native command routing, call the low-level `client.session_prompt(session_id, blocks)` and manage the subsequent subscription and wait yourself; runtime skill recognition still applies.
|
||||
Content-block arrays use the same command routing as strings. Both `session.run([{"type": "text", "text": "/compact"}])` and `session.run("/compact")` invoke native compaction and return `RunCommandResult`. To bypass native command routing, call the low-level `client.session_prompt(session_id, blocks)` and manage the subsequent subscription and wait yourself; runtime skill recognition still applies.
|
||||
|
||||
Select a model that supports image input. Submit a command and image in the existing content-block array, for example `session.run([{"type": "text", "text": "/plan Analyze this image."}, {"type": "image", "data": image_base64, "mimeType": "image/png"}])`; `harness.run()` accepts the same input. Plan input and goal creation or editing accept images; `/plan off`, goal controls, compact, and permission commands reject them through the [native attachment rules](../../packages/sdk/server/README.md#command-input). Native command input supports encoded raster images, not general file uploads or client-supplied attachment references.
|
||||
|
||||
Each call subscribes before sending and releases its subscription before returning or raising. `on_notification` receives the ordinary activity interval in wire order; command callbacks replay Session-tree notifications from subscription through their own `command/done` after the command response, so that interval can include overlapping work. Calls may overlap without consuming each other's results. Handler refusals return `CommandExecution.result.kind="error"`; routing, protocol, transport and observer failures raise. Malformed native-command responses from `run()` or `session_command()` raise `SdkProtocolError`, with the model validation error retained as the cause. A timeout or observer failure does not roll back runtime work or authorize an automatic retry. `request_timeout_seconds` bounds the protocol response wait, not later ordinary prompt activity.
|
||||
Each call subscribes before sending and releases its subscription before returning or raising. `on_notification` receives the ordinary activity interval in wire order; command callbacks replay Session-tree notifications from subscription through their own `command/done` after the command response, so that interval can include overlapping work. Calls may overlap without consuming each other's results. Handler refusals return `RunCommandResult.result.kind="error"`; routing, protocol, transport and observer failures raise. Malformed native-command responses from `run()` or `session_command()` raise `SdkProtocolError`, with the model validation error retained as the cause. A timeout or observer failure does not roll back runtime work or authorize an automatic retry. `request_timeout_seconds` bounds the protocol response wait, not later ordinary prompt activity.
|
||||
|
||||
The default full `sdk` profile provides the supported native commands; `sdk-minimal` keeps unsupported input on its ordinary prompt path. `/compact` can make an auxiliary model request and completes without a normal turn or idle notification; empty history can succeed without a summary. The lower-level `HarnessClient.session_command(session_id, line)` accepts ASCII registered names or canonical English aliases of supported builtin definitions, without prompt fallback or attachments. Use `run()` for Chinese aliases. See the [compaction example](examples/README.md#compact-a-full-sdk-session).
|
||||
The default full `sdk` profile provides the supported native commands; `sdk-minimal` keeps unsupported input on its ordinary prompt path. `/compact` can make an auxiliary model request and completes without a normal turn or idle notification; empty history can succeed without a summary. The lower-level `HarnessClient.session_command(session_id, line)` accepts ASCII registered names or canonical English aliases of supported builtin definitions, without prompt fallback or attachments, and returns the native `CommandExecution`. Use `run()` for Chinese aliases. See the [compaction example](examples/README.md#compact-a-full-sdk-session).
|
||||
|
||||
`/permission <preset>` changes only the receiving Session. An unknown preset returns a command error without changing policy. Later input and restoration retain successful changes; other Sessions and the caller's `DeepSeekHarnessConfig` keep their own values. A running approval-policy change can inject a notice and cause another model step. See the [permission example](examples/README.md#change-session-permissions).
|
||||
|
||||
`/plan` enters plan mode, `/plan off` leaves, and `/plan <message>` enters then submits the trimmed message through native steer. Its `CommandExecution` confirms command completion, not completion of planning or review. Enter with `/plan` then run ordinary text when a `RunResult` is needed. For combined input, subscribe before `run()` and use `session/wait` while keeping the question handler alive, as in the [plan example](examples/README.md#review-a-plan). Plan guidance and `exit_plan_mode` follow their native lifecycle without changing permission settings.
|
||||
`/plan` enters plan mode, `/plan off` leaves, and `/plan <message>` enters then submits the trimmed message through native steer. Its `RunCommandResult` confirms command completion, not completion of planning or review. Enter with `/plan` then run ordinary text when a `RunResult` is needed. For combined input, subscribe before `run()` and use `session/wait` while keeping the question handler alive, as in the [plan example](examples/README.md#review-a-plan). Plan guidance and `exit_plan_mode` follow their native lifecycle without changing permission settings.
|
||||
|
||||
`/goal` shows the current goal; `/goal <objective>`, `edit <objective>`, `pause`, `resume`, and `clear` use the native handler. Creation and resume can start automatic rounds after `run()` returns. Subscribe before sending, observe terminal `goal/change` operations, then call `session/wait` for any closing model reply. Idle between rounds is not goal completion. A restored active goal stays disarmed until explicit resume. See the [goal example](examples/README.md#run-a-goal).
|
||||
|
||||
|
||||
@@ -61,21 +61,21 @@ with DeepSeekHarness(dsh_home="/absolute/path/to/saved-home") as harness:
|
||||
<a id="run-text-and-commands"></a>
|
||||
## 运行文本与命令
|
||||
|
||||
`session.run(input, on_notification=..., delivery="queue")` 是字符串和内容块数组共用的高层输入入口。两者都使用运行时的[命令形式与 Desktop 别名](../../packages/sdk/server/README.zh.md#command-input)识别规则;未识别的输入保持普通提示词及原始内容块顺序。对普通输入,包括图片,`delivery="steer"` 指向下一个可用步骤。命令的接纳规则与执行时序仍由原生处理器负责。
|
||||
`session.run(input, on_notification=..., delivery="queue")` 是字符串和内容块数组共用的公开提示词/命令输入入口。两者都使用运行时的[命令形式与 Desktop 别名](../../packages/sdk/server/README.zh.md#command-input)识别规则;未识别的输入保持普通提示词及原始内容块顺序。对普通输入,包括图片,`delivery="steer"` 指向下一个可用步骤。命令的接纳规则与执行时序仍由原生处理器负责。
|
||||
|
||||
普通提示词返回其入队回执到下一次 idle 区间的 `RunResult`,命令则在执行结束后返回 `CommandExecution`。读取 `final_response` 前使用 `isinstance(result, RunResult)` 检查类型,否则检查命令的 `result.kind`。命令保留自己的 `command_id` 和可选 `source_event_seq`,不会选取更早的助手回复作为结果。
|
||||
普通提示词返回其入队回执到下一次 idle 区间的 `RunResult`,命令则在执行结束后返回 `RunCommandResult`。两种结果都包含 `session_id`,包括 `harness.run()` 创建新 Session 的情况。`RunCommandResult` 在原生 `CommandExecution` 上增加该 Session 标识。读取 `final_response` 前使用 `isinstance(result, RunResult)` 检查类型,否则检查命令的 `result.kind`。命令保留自己的 `command_id` 和可选 `source_event_seq`,不会选取更早的助手回复作为结果。
|
||||
|
||||
内容块数组与字符串使用相同的命令路由。`session.run([{"type": "text", "text": "/compact"}])` 和 `session.run("/compact")` 都调用原生压缩并返回 `CommandExecution`。要绕过原生命令路由,调用低层 `client.session_prompt(session_id, blocks)` 并自行管理后续订阅与等待;运行时 skill(技能)识别仍然适用。
|
||||
内容块数组与字符串使用相同的命令路由。`session.run([{"type": "text", "text": "/compact"}])` 和 `session.run("/compact")` 都调用原生压缩并返回 `RunCommandResult`。要绕过原生命令路由,调用低层 `client.session_prompt(session_id, blocks)` 并自行管理后续订阅与等待;运行时 skill(技能)识别仍然适用。
|
||||
|
||||
先选择支持图片输入的模型。通过现有内容块数组同时提交命令和图片,例如 `session.run([{"type": "text", "text": "/plan Analyze this image."}, {"type": "image", "data": image_base64, "mimeType": "image/png"}])`;`harness.run()` 接受相同输入。计划输入和目标创建、编辑接受图片;`/plan off`、目标控制、压缩和权限命令按[原生附件规则](../../packages/sdk/server/README.zh.md#command-input)拒绝图片。原生命令输入支持编码栅格图片,不支持一般文件上传或客户端提供的附件引用。
|
||||
|
||||
每次调用在发送前订阅,并在返回或抛出异常前释放自己的订阅。`on_notification` 按线上的顺序接收普通活动区间通知;命令回调在命令响应到达后,按顺序交付从订阅时刻到自身 `command/done` 的 Session 树通知,因此该区间可能包含重叠工作的通知。调用可以重叠,不会消耗彼此的结果。处理器拒绝返回 `CommandExecution.result.kind="error"`,路由、协议、传输和观察回调失败则抛出异常。`run()` 或 `session_command()` 收到格式错误的原生命令响应时抛出 `SdkProtocolError`,并保留模型校验错误作为 cause。超时或观察回调失败不回滚运行时工作,也不授权自动重试。`request_timeout_seconds` 限制协议响应等待,不限制之后的普通提示词活动。
|
||||
每次调用在发送前订阅,并在返回或抛出异常前释放自己的订阅。`on_notification` 按线上的顺序接收普通活动区间通知;命令回调在命令响应到达后,按顺序交付从订阅时刻到自身 `command/done` 的 Session 树通知,因此该区间可能包含重叠工作的通知。调用可以重叠,不会消耗彼此的结果。处理器拒绝返回 `RunCommandResult.result.kind="error"`,路由、协议、传输和观察回调失败则抛出异常。`run()` 或 `session_command()` 收到格式错误的原生命令响应时抛出 `SdkProtocolError`,并保留模型校验错误作为 cause。超时或观察回调失败不回滚运行时工作,也不授权自动重试。`request_timeout_seconds` 限制协议响应等待,不限制之后的普通提示词活动。
|
||||
|
||||
默认的完整 `sdk` profile 提供已支持的原生命令;`sdk-minimal` 将未支持的输入保留在普通提示词路径。`/compact` 可以发起辅助模型请求,其完成不需要普通 turn 或 idle 通知;空历史可以在不生成摘要时成功。底层 `HarnessClient.session_command(session_id, line)` 接受受支持内置定义的 ASCII 注册名或英文规范别名,不回退为提示词,也不接受附件。中文别名使用 `run()`。见[压缩示例](examples/README.zh.md#compact-a-full-sdk-session)。
|
||||
默认的完整 `sdk` profile 提供已支持的原生命令;`sdk-minimal` 将未支持的输入保留在普通提示词路径。`/compact` 可以发起辅助模型请求,其完成不需要普通 turn 或 idle 通知;空历史可以在不生成摘要时成功。底层 `HarnessClient.session_command(session_id, line)` 接受受支持内置定义的 ASCII 注册名或英文规范别名,不回退为提示词,也不接受附件,返回原生 `CommandExecution`。中文别名使用 `run()`。见[压缩示例](examples/README.zh.md#compact-a-full-sdk-session)。
|
||||
|
||||
`/permission <preset>` 只修改接收命令的 Session。未知 preset 返回命令错误,不改变策略。后续输入与恢复保留成功的修改,其他 Session 和调用方的 `DeepSeekHarnessConfig` 保留自己的值。运行中的审批策略变化可以注入通知并触发下一模型步骤。见[权限示例](examples/README.zh.md#change-session-permissions)。
|
||||
|
||||
`/plan` 进入计划模式,`/plan off` 退出,`/plan <message>` 进入后通过原生 steer 提交去除首尾空白的正文。其 `CommandExecution` 确认命令执行结束,不代表计划生成或审阅结束。需要 `RunResult` 时,先用 `/plan` 进入,再运行普通文本。组合输入应在 `run()` 前订阅,并保持问答处理器存活,通过 `session/wait` 观察后续工作,见[计划示例](examples/README.zh.md#review-a-plan)。计划指导和 `exit_plan_mode` 遵循原生生命周期,不修改权限设置。
|
||||
`/plan` 进入计划模式,`/plan off` 退出,`/plan <message>` 进入后通过原生 steer 提交去除首尾空白的正文。其 `RunCommandResult` 确认命令执行结束,不代表计划生成或审阅结束。需要 `RunResult` 时,先用 `/plan` 进入,再运行普通文本。组合输入应在 `run()` 前订阅,并保持问答处理器存活,通过 `session/wait` 观察后续工作,见[计划示例](examples/README.zh.md#review-a-plan)。计划指导和 `exit_plan_mode` 遵循原生生命周期,不修改权限设置。
|
||||
|
||||
`/goal` 查看当前目标;`/goal <objective>`、`edit <objective>`、`pause`、`resume` 和 `clear` 使用原生处理器。创建和恢复可以在 `run()` 返回后启动自动轮次。发送前订阅,观察终态 `goal/change` 操作后,再调用 `session/wait` 等待可能的模型收尾。轮次之间的 idle 不代表目标完成。恢复的活动目标在显式 resume 前保持 disarmed。见[目标示例](examples/README.zh.md#run-a-goal)。
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from deepseek_harness import CommandExecution, DeepSeekHarness, RunResult
|
||||
from deepseek_harness import RunCommandResult, DeepSeekHarness, RunResult
|
||||
|
||||
|
||||
def display(result: RunResult | CommandExecution) -> None:
|
||||
def display(result: RunResult | RunCommandResult) -> None:
|
||||
"""Print the input's result and stop on a reported failure."""
|
||||
if isinstance(result, CommandExecution):
|
||||
if isinstance(result, RunCommandResult):
|
||||
print(f"Command {result.command_id}: {result.result.kind}")
|
||||
if result.result.kind == "error":
|
||||
raise RuntimeError(result.result.text)
|
||||
|
||||
@@ -11,7 +11,7 @@ from pathlib import Path
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from deepseek_harness import CommandExecution, DeepSeekHarness, Notification
|
||||
from deepseek_harness import RunCommandResult, DeepSeekHarness, Notification
|
||||
from deepseek_harness.api import final_response
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ def main() -> None:
|
||||
return event
|
||||
|
||||
execution = session.run(f"/goal {args.objective}")
|
||||
if not isinstance(execution, CommandExecution):
|
||||
if not isinstance(execution, RunCommandResult):
|
||||
raise RuntimeError("The SDK profile did not execute the requested command")
|
||||
print(execution.result.text, flush=True)
|
||||
if execution.result.kind == "error":
|
||||
|
||||
@@ -7,7 +7,7 @@ import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from deepseek_harness import CommandExecution, DeepSeekHarness
|
||||
from deepseek_harness import RunCommandResult, DeepSeekHarness
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -41,7 +41,7 @@ def main() -> None:
|
||||
profile=args.profile,
|
||||
) as harness:
|
||||
result = harness.run(args.prompt, session_id=args.session_id)
|
||||
if isinstance(result, CommandExecution):
|
||||
if isinstance(result, RunCommandResult):
|
||||
if result.result.kind == "error":
|
||||
raise RuntimeError(result.result.text)
|
||||
print(result.result.text or "")
|
||||
|
||||
@@ -7,12 +7,12 @@ import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from deepseek_harness import CommandExecution, DeepSeekHarness, RunResult
|
||||
from deepseek_harness import RunCommandResult, DeepSeekHarness, RunResult
|
||||
|
||||
|
||||
def display(result: RunResult | CommandExecution) -> None:
|
||||
def display(result: RunResult | RunCommandResult) -> None:
|
||||
"""Print the input's result and stop on a reported failure."""
|
||||
if isinstance(result, CommandExecution):
|
||||
if isinstance(result, RunCommandResult):
|
||||
print(f"Command {result.command_id}: {result.result.kind}")
|
||||
if result.result.kind == "error":
|
||||
raise RuntimeError(result.result.text)
|
||||
|
||||
@@ -13,7 +13,7 @@ from pathlib import Path
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from deepseek_harness import CommandExecution, DeepSeekHarness, Notification, UserQuestionRequest
|
||||
from deepseek_harness import RunCommandResult, DeepSeekHarness, Notification, UserQuestionRequest
|
||||
from deepseek_harness.api import final_response
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ def main() -> None:
|
||||
session = harness.start_session()
|
||||
with closing(harness.client.subscribe_session_notifications(session.id)) as notifications:
|
||||
execution = session.run(f"/plan {args.task}")
|
||||
if not isinstance(execution, CommandExecution):
|
||||
if not isinstance(execution, RunCommandResult):
|
||||
raise RuntimeError("The SDK profile did not execute the requested command")
|
||||
print(execution.result.text, flush=True)
|
||||
if execution.result.kind == "error":
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from .api import DeepSeekHarness, DeepSeekHarnessConfig, RunResult, Session
|
||||
from .api import RunCommandResult, DeepSeekHarness, DeepSeekHarnessConfig, RunResult, Session
|
||||
from .client import HarnessClient, HarnessConfig, UserQuestionHandler
|
||||
from .errors import SdkProtocolError
|
||||
from .models import (
|
||||
@@ -12,6 +12,7 @@ __all__ = [
|
||||
"DeepSeekHarnessConfig",
|
||||
"Session",
|
||||
"RunResult",
|
||||
"RunCommandResult",
|
||||
"CommandExecution",
|
||||
"CommandResult",
|
||||
"CommandSuccessResult",
|
||||
|
||||
@@ -50,6 +50,12 @@ class RunResult:
|
||||
notifications: list[Notification]
|
||||
|
||||
|
||||
class RunCommandResult(CommandExecution):
|
||||
"""Settled native command result with the Session id for continued interaction."""
|
||||
|
||||
session_id: str
|
||||
|
||||
|
||||
class _PromptActivity:
|
||||
"""Collect an ordinary prompt's notifications from its inbox receipt through idle."""
|
||||
|
||||
@@ -174,8 +180,8 @@ class DeepSeekHarness:
|
||||
session_id: str | None = None,
|
||||
on_notification: Callable[[Notification], None] | None = None,
|
||||
delivery: Literal["queue", "steer"] = "queue",
|
||||
) -> RunResult | CommandExecution:
|
||||
"""Run text or content blocks through native input routing on a fresh or named Session."""
|
||||
) -> RunResult | RunCommandResult:
|
||||
"""Run input on a fresh or named Session, returning its id with either result kind."""
|
||||
return self.start_session(session_id).run(input, on_notification=on_notification, delivery=delivery)
|
||||
|
||||
|
||||
@@ -200,11 +206,12 @@ class Session:
|
||||
*,
|
||||
on_notification: Callable[[Notification], None] | None = None,
|
||||
delivery: Literal["queue", "steer"] = "queue",
|
||||
) -> RunResult | CommandExecution:
|
||||
) -> RunResult | RunCommandResult:
|
||||
"""Run text or content blocks through native input routing.
|
||||
|
||||
Input returns the prompt's receipt-to-idle RunResult or a settled
|
||||
CommandExecution; command completion does not wait for later model work.
|
||||
RunCommandResult. Both include this Session's id. Command completion does
|
||||
not wait for later model work.
|
||||
The first text block selects native commands. Matched commands require
|
||||
one text block with only encoded images beside it; native handlers decide
|
||||
which commands accept those images. Ordinary prompts retain block order
|
||||
@@ -232,7 +239,7 @@ class Session:
|
||||
on_notification(notification)
|
||||
if _is_command_done(notification, self.id, execution.command_id):
|
||||
break
|
||||
return execution
|
||||
return RunCommandResult(session_id=self.id, commandId=execution.command_id, result=execution.result)
|
||||
activity = _PromptActivity(self.id, response)
|
||||
while True:
|
||||
notification = subscription.next()
|
||||
|
||||
@@ -7,7 +7,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from deepseek_harness import CommandExecution, RunResult, Session, UserQuestionRequest
|
||||
from deepseek_harness import CommandExecution, RunCommandResult, RunResult, Session, UserQuestionRequest
|
||||
from test_commands_runtime import (
|
||||
_Completion, _Runtime, _SESSION_ID, _WAIT_SECONDS, _assert_request_permission,
|
||||
_command_pair, _of_type, _permission_facts, _read_events,
|
||||
@@ -214,6 +214,36 @@ def test_plan_state_survives_resume_without_changing_permissions(tmp_path: Path)
|
||||
assert [e["data"] for e in _of_type(events, "plan/mode")] == [{"active": True}]
|
||||
|
||||
|
||||
def test_top_level_plan_result_identifies_the_session_for_wait_continue_and_resume(tmp_path: Path) -> None:
|
||||
first = _Completion("PLAN_READY", blocked=True)
|
||||
with _runtime(tmp_path, [first, _Completion("CONTINUED_PLAN")], initial_permission="workspace-write") as runtime:
|
||||
execution = runtime.call(runtime.harness.run, "/plan Design this change without tools.")
|
||||
assert isinstance(execution, RunCommandResult)
|
||||
assert execution.result.kind == "success"
|
||||
assert first.entered.wait(_WAIT_SECONDS), runtime.model.failures
|
||||
assert not first.release.is_set()
|
||||
session = runtime.harness.start_session(execution.session_id)
|
||||
first.release.set()
|
||||
_wait_activity(runtime, session)
|
||||
continued = runtime.call(runtime.harness.run, "Continue this plan without tools.", session_id=execution.session_id)
|
||||
assert isinstance(continued, RunResult)
|
||||
assert continued.session_id == execution.session_id
|
||||
assert continued.final_response == "CONTINUED_PLAN"
|
||||
assert "Design this change without tools." in _request_texts(runtime.model.captured()[1])
|
||||
with _runtime(tmp_path, [_Completion("RESTORED_PLAN")], initial_permission="workspace-write") as resumed:
|
||||
session = resumed.call(resumed.harness.resume_session, execution.session_id)
|
||||
result = resumed.call(session.run, "Finish reviewing this plan without tools.")
|
||||
assert isinstance(result, RunResult) and result.session_id == execution.session_id
|
||||
assert result.final_response == "RESTORED_PLAN"
|
||||
[request] = resumed.model.captured()
|
||||
assert _GUIDANCE in _effective_system(request)
|
||||
assert "Design this change without tools." in _request_texts(request)
|
||||
assert "Continue this plan without tools." in _request_texts(request)
|
||||
events = _read_events(resumed.logs, session_id=execution.session_id)
|
||||
_command_pair(events, execution, "plan")
|
||||
assert len(_of_type(events, "turn/start")) == 3
|
||||
|
||||
|
||||
def test_plan_state_is_local_and_literal_prompts_do_not_toggle_it(tmp_path: Path) -> None:
|
||||
with _runtime(tmp_path, [_Completion("OTHER"), _Completion("PLANNING"), _Completion("RAW_INPUT")],
|
||||
initial_permission="workspace-write") as runtime:
|
||||
|
||||
@@ -10,7 +10,7 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
import pytest
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
from deepseek_harness import CommandExecution, DeepSeekHarness, Notification, RunResult, SdkProtocolError
|
||||
from deepseek_harness import CommandExecution, RunCommandResult, DeepSeekHarness, Notification, RunResult, SdkProtocolError
|
||||
from deepseek_harness.errors import JsonRpcError
|
||||
|
||||
|
||||
@@ -146,11 +146,56 @@ def test_run_returns_command_without_waiting_for_idle(sdk: DeepSeekHarness, entr
|
||||
result = (sdk.run("/compact", session_id="main", on_notification=notifications.append)
|
||||
if entry == "harness" else sdk.start_session("main").run("/compact", on_notification=notifications.append))
|
||||
assert isinstance(result, CommandExecution)
|
||||
assert isinstance(result, RunCommandResult)
|
||||
assert result.session_id == "main"
|
||||
assert result.result.kind == "success"
|
||||
assert [notice.payload["event"]["type"] for notice in notifications] == ["command/run", "command/done"]
|
||||
assert_released(sdk)
|
||||
|
||||
|
||||
def test_top_level_command_results_identify_distinct_sessions_and_allow_continuing_one(
|
||||
sdk: DeepSeekHarness, tmp_path: Path,
|
||||
) -> None:
|
||||
first = sdk.run("/compact")
|
||||
second = sdk.run("/compact")
|
||||
assert isinstance(first, RunCommandResult) and isinstance(second, RunCommandResult)
|
||||
assert first.session_id and second.session_id and first.session_id != second.session_id
|
||||
continued = sdk.run("continue the first session", session_id=first.session_id)
|
||||
assert isinstance(continued, RunResult)
|
||||
assert continued.session_id == first.session_id
|
||||
sent = [json.loads(line) for line in (tmp_path / "requests.jsonl").read_text(encoding="utf-8").splitlines()]
|
||||
queries = [frame["params"] for frame in sent if frame["method"] == "session/query"]
|
||||
assert [query["sessionId"] for query in queries] == [first.session_id, second.session_id, first.session_id]
|
||||
assert_released(sdk)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("result", [
|
||||
{"kind": "success"},
|
||||
{"kind": "success", "text": "Compacted.", "sourceEventSeq": 0},
|
||||
{"kind": "success", "sourceEventSeq": 2**53 - 1},
|
||||
{"kind": "error", "text": "The agent is busy."},
|
||||
])
|
||||
def test_run_adds_session_identity_without_changing_command_results(peer: Path, tmp_path: Path, result: dict) -> None:
|
||||
execution = {"commandId": "custom-command", "result": result}
|
||||
with DeepSeekHarness(
|
||||
cwd=str(tmp_path), dsh_home=str(tmp_path / "home"), request_timeout_seconds=5,
|
||||
_launch_args=(sys.executable, str(peer)),
|
||||
env={"REQUEST_LOG": str(tmp_path / "requests.jsonl"), "QUERY_RESPONSE": json.dumps({
|
||||
"kind": "command", "execution": execution,
|
||||
})},
|
||||
) as sdk:
|
||||
returned = sdk.run("custom-response")
|
||||
assert isinstance(returned, RunCommandResult)
|
||||
assert returned.session_id
|
||||
assert returned.model_dump(by_alias=True, exclude_none=True) == {
|
||||
**execution, "session_id": returned.session_id,
|
||||
}
|
||||
low_level = sdk.client._query(returned.session_id, "custom-response", "queue")
|
||||
assert isinstance(low_level, CommandExecution)
|
||||
assert low_level.model_dump(by_alias=True, exclude_none=True) == execution
|
||||
assert_released(sdk)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("text", ["ordinary", "fast-prompt", "/model", " /compact extra "])
|
||||
def test_run_preserves_ordinary_text_and_result(sdk: DeepSeekHarness, text: str) -> None:
|
||||
notifications = []
|
||||
|
||||
Reference in new issue
Block a user