diff --git a/python/development.i18n.yaml b/python/development.i18n.yaml index f1ada26c49..7e2f051761 100644 --- a/python/development.i18n.yaml +++ b/python/development.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write python/development.md -development.md: aa0144d7eaa66711d0f08316d4445da060918ca8 -development.zh.md: a35f6fc8de1bdbd282fd8999a1440fde0c400b34 +development.md: 9b8e401a995b42434d3817f80bcabac910af9d6c +development.zh.md: c5f68b4f01f213d6f34d37c664617f7f4232125f diff --git a/python/development.md b/python/development.md index aa0144d7ea..9b8e401a99 100644 --- a/python/development.md +++ b/python/development.md @@ -34,7 +34,7 @@ uv run --project python/sdk python scripts/smoke-python-runtime.py \ --scenario sdk-minimal --exe dist-exe/deepseek-harness-sdk-runtime-macos-arm64 ``` -Three scenarios compare committed expected output under `scripts/snapshots/python-sdk-single-exe/`. `minimal/model-visible.json` pins the Linux/macOS `sdk-minimal` profile's assembled system prompts, advertised tool schemas, and model-visible messages; `minimal/win-x64/model-visible.json` pins its PowerShell counterpart. A plugin that contributes an unintended system section or user message therefore fails the job, and every message the profile emits is compared. `advanced/` pins one complex process's SDK result and parent/child session logs across every target. `restart/` launches two complete SDK runtime processes against one persistence root and snapshots their isolated model histories, high-level results, and separate durable logs across every target. Rerun the owning scenario with `--update-snapshots` and review that diff before committing it. +Four scenarios compare committed expected output under `scripts/snapshots/python-sdk-single-exe/`. `minimal/model-visible.json` pins the Linux/macOS `sdk-minimal` profile's assembled system prompts, advertised tool schemas, and model-visible messages; `minimal/win-x64/model-visible.json` pins its PowerShell counterpart. A plugin that contributes an unintended system section or user message therefore fails the job, and every message the profile emits is compared. `advanced/` pins one complex process's SDK result and parent/child session logs across every target. `restart/` launches two complete SDK runtime processes against one persistence root and snapshots their isolated model histories, high-level results, and separate durable logs across every target. `sdk-minimal-in-history` reuses the persistent-shell and editor scenario with a section that changes after the first successful shell call. `minimal-in-history/prompt-history.json` pins both prompt versions, the unchanged leading prompt in later requests, appended SDK system-message events, and `request/context.systemPromptUpdate`; tool schemas stay fixed and the editor file is checked independently. Rerun the owning scenario with `--update-snapshots` and review that diff before committing it. Trusted pull requests also run `--scenario sdk-live --installed-wheel` on every native target. That scenario performs two tool-using turns against `https://api.deepseek.com`, verifies the created file externally, and fails when the repository secret is absent instead of self-skipping. Fork and Dependabot pull requests run the complete keyless installed-wheel path but receive no key. diff --git a/python/development.zh.md b/python/development.zh.md index a35f6fc8de..c5f68b4f01 100644 --- a/python/development.zh.md +++ b/python/development.zh.md @@ -34,7 +34,7 @@ uv run --project python/sdk python scripts/smoke-python-runtime.py \ --scenario sdk-minimal --exe dist-exe/deepseek-harness-sdk-runtime-macos-arm64 ``` -其中三个场景会比对 `scripts/snapshots/python-sdk-single-exe/` 下已提交的期望输出。`minimal/model-visible.json` 固定 Linux/macOS `sdk-minimal` profile 所组装的系统提示词、对外公布的工具 schema 与模型可见消息;`minimal/win-x64/model-visible.json` 固定对应的 PowerShell 版本。因此,插件一旦贡献出计划外的系统分段或 user 消息,该任务即失败,且该 profile 发出的每条消息都会参与比对。`advanced/` 跨所有目标固定一个复杂进程的 SDK 结果及父/子会话日志。`restart/` 针对同一持久化根目录启动两个完整 SDK 运行时进程,并跨所有目标固定其彼此隔离的模型历史、高层结果与独立持久日志。重新运行对应场景时加上 `--update-snapshots`,并在提交前审阅该差异。 +其中四个场景会比对 `scripts/snapshots/python-sdk-single-exe/` 下已提交的期望输出。`minimal/model-visible.json` 固定 Linux/macOS `sdk-minimal` profile 所组装的系统提示词、对外公布的工具 schema 与模型可见消息;`minimal/win-x64/model-visible.json` 固定对应的 PowerShell 版本。因此,插件一旦贡献出计划外的系统分段或 user 消息,该任务即失败,且该 profile 发出的每条消息都会参与比对。`advanced/` 跨所有目标固定一个复杂进程的 SDK 结果及父/子会话日志。`restart/` 针对同一持久化根目录启动两个完整 SDK 运行时进程,并跨所有目标固定其彼此隔离的模型历史、高层结果与独立持久日志。`sdk-minimal-in-history` 复用持久 shell 与编辑器场景,并在首次 shell 调用成功后更改一个分段。`minimal-in-history/prompt-history.json` 固定两个提示词版本、后续请求中不变的首条提示词、追加的 SDK system-message 事件及 `request/context.systemPromptUpdate`;工具 schema 保持不变,并独立检查编辑器创建的文件。重新运行对应场景时加上 `--update-snapshots`,并在提交前审阅该差异。 可信拉取请求还会在每个原生目标上运行 `--scenario sdk-live --installed-wheel`。该场景面向 `https://api.deepseek.com` 执行两个使用工具的轮次,从外部验证已创建文件,并在仓库密钥缺失时失败而不是自行 skip。Fork 与 Dependabot 拉取请求会运行完整的 keyless 安装后 wheel 路径,但不会获得密钥。 diff --git a/scripts/fixtures/python-sdk-in-history-prompt.mjs b/scripts/fixtures/python-sdk-in-history-prompt.mjs new file mode 100644 index 0000000000..aa7c947834 --- /dev/null +++ b/scripts/fixtures/python-sdk-in-history-prompt.mjs @@ -0,0 +1,18 @@ +/** Change one ordinary section after a successful persistent shell call. */ +export const name = 'python-sdk-in-history-prompt' +export const inject = ['systemPrompt'] + +/** @param {import('@deepseek-ai/cordis').Context} ctx - Composed runtime services. */ +export function apply(ctx) { + let version = 1 + ctx.effect(() => ctx.systemPrompt.section({ + name: 'python-sdk:in-history', + order: 400, + text: () => `Python SDK prompt version ${version}.`, + })) + ctx.on('tools/post-execute', async (exec, result, next) => { + const downstream = await next() + if (!result.isError && (exec.name === 'bash' || exec.name === 'pwsh')) version = 2 + return downstream + }) +} diff --git a/scripts/smoke-python-runtime.py b/scripts/smoke-python-runtime.py index a97b5970d7..3f14738cb2 100644 --- a/scripts/smoke-python-runtime.py +++ b/scripts/smoke-python-runtime.py @@ -134,6 +134,10 @@ MINIMAL_SNAPSHOT_DIRECTORY = ( if IS_WINDOWS: MINIMAL_SNAPSHOT_DIRECTORY /= "win-x64" MINIMAL_SNAPSHOT_FILENAMES = ("model-visible.json",) +IN_HISTORY_SNAPSHOT_DIRECTORY = ( + Path(__file__).resolve().parent / "snapshots" / "python-sdk-single-exe" / "minimal-in-history" +) +IN_HISTORY_SNAPSHOT_FILENAMES = ("prompt-history.json",) RESTART_SNAPSHOT_DIRECTORY = ( Path(__file__).resolve().parent / "snapshots" / "python-sdk-single-exe" / "restart" ) @@ -313,7 +317,8 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]: messages = body.get("messages") if not isinstance(messages, list) or not messages: raise AssertionError(f"model request has no messages: {body}") - latest = messages[-1] + # A system prompt update may follow the tool result without replacing it. + latest = next(message for message in reversed(messages) if message.get("role") != "system") if not isinstance(latest, dict): raise AssertionError(f"model request has an invalid latest message: {body}") @@ -769,7 +774,7 @@ def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--scenario", - choices=("all", "sdk-default", "sdk-custom", "sdk-minimal", "sdk-fs-search", "sdk-spawn-node", "sdk-mcp", "sdk-snapshot", "sdk-restart", "sdk-profile-plugin", "sdk-live", "direct"), + choices=("all", "sdk-default", "sdk-custom", "sdk-minimal", "sdk-minimal-in-history", "sdk-fs-search", "sdk-spawn-node", "sdk-mcp", "sdk-snapshot", "sdk-restart", "sdk-profile-plugin", "sdk-live", "direct"), default="all", ) parser.add_argument("--exe", type=Path) @@ -788,10 +793,10 @@ def main() -> None: parser.error("--scenario sdk-profile-plugin requires --installed-wheel") if args.installed_wheel: args.exe = assert_installed_wheel_environment() - if args.scenario in {"all", "sdk-custom", "sdk-minimal", "sdk-fs-search", "sdk-spawn-node", "sdk-snapshot", "sdk-restart", "direct"} and args.exe is None: + if args.scenario in {"all", "sdk-custom", "sdk-minimal", "sdk-minimal-in-history", "sdk-fs-search", "sdk-spawn-node", "sdk-snapshot", "sdk-restart", "direct"} and args.exe is None: parser.error("--exe is required for custom, minimal, snapshot, and direct scenarios") - if args.update_snapshots and args.scenario not in {"all", "sdk-minimal", "sdk-snapshot", "sdk-restart"}: - parser.error("--update-snapshots requires --scenario sdk-minimal, sdk-snapshot, sdk-restart, or all") + if args.update_snapshots and args.scenario not in {"all", "sdk-minimal", "sdk-minimal-in-history", "sdk-snapshot", "sdk-restart"}: + parser.error("--update-snapshots requires --scenario sdk-minimal, sdk-minimal-in-history, sdk-snapshot, sdk-restart, or all") if args.exe is not None and not args.exe.is_file(): parser.error(f"runtime executable does not exist: {args.exe}") @@ -809,6 +814,9 @@ def main() -> None: if args.scenario in {"all", "sdk-minimal"}: assert args.exe is not None smoke_sdk_minimal(model.url, args.exe.resolve(), args.update_snapshots) + if args.scenario in {"all", "sdk-minimal-in-history"}: + assert args.exe is not None + smoke_sdk_minimal(model.url, args.exe.resolve(), args.update_snapshots, in_history=True) if args.scenario in {"all", "sdk-fs-search"}: assert args.exe is not None smoke_sdk_fs_search(model.url, args.exe.resolve()) @@ -1038,7 +1046,9 @@ def smoke_sdk_custom(base_url: str, executable: Path) -> None: assert_session_log(sessions, root, EXPECTED_TEXT, CODE_WORKER_TEXT, WORKFLOW_WORKER_TEXT) -def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) -> None: +def smoke_sdk_minimal( + base_url: str, executable: Path, update_snapshots: bool, *, in_history: bool = False, +) -> None: """Exercise the shipped standalone minimal profile through the packaged executable.""" from deepseek_harness import DeepSeekHarness @@ -1050,6 +1060,19 @@ 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" + patches = () + if in_history: + patch = root / "in-history.patch.yml" + patch.write_text(json.dumps([ + {"id": "llm-deepseek", "config": {"models": [ + {"id": "smoke-model", "systemPromptUpdate": "in-history"}, + ]}}, + {"insert": [{ + "id": "in-history-prompt", + "name": (Path(__file__).resolve().parent / "fixtures/python-sdk-in-history-prompt.mjs").as_uri(), + }]}, + ])) + patches = (str(patch),) with DeepSeekHarness( provider="deepseek-official", model="smoke-model", @@ -1057,6 +1080,7 @@ def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) - dsh_bin=str(executable), dsh_home=str(dsh_home), profile="sdk-minimal", + patches=patches, api_key="sk-keyless-smoke", base_url=base_url, request_timeout_seconds=60, @@ -1070,10 +1094,18 @@ def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) - raise AssertionError(f"packaged editor wrote unexpected content: {editor_path.read_text()!r}") assert_session_log(sessions, root, MINIMAL_TEXT, "COUNT=1", "COUNT=2") - files = build_minimal_snapshot_files(MockModelHandler.requests[first_request:], root) - compare_snapshot_files( - files, update_snapshots, MINIMAL_SNAPSHOT_DIRECTORY, MINIMAL_SNAPSHOT_FILENAMES, - ) + requests = MockModelHandler.requests[first_request:] + if in_history: + logs = read_session_logs(sessions) + files = build_in_history_snapshot_files(result, requests, logs[result.session_id]) + compare_snapshot_files( + files, update_snapshots, IN_HISTORY_SNAPSHOT_DIRECTORY, IN_HISTORY_SNAPSHOT_FILENAMES, + ) + else: + files = build_minimal_snapshot_files(requests, root) + compare_snapshot_files( + files, update_snapshots, MINIMAL_SNAPSHOT_DIRECTORY, MINIMAL_SNAPSHOT_FILENAMES, + ) def smoke_sdk_fs_search(base_url: str, executable: Path) -> None: @@ -1686,6 +1718,56 @@ def snapshot_child_ids(result: "RunResult") -> list[str]: return child_ids +def build_in_history_snapshot_files( + result: "RunResult", + requests: list[dict[str, object]], + log: list[dict[str, object]], +) -> dict[str, str]: + """Assert live requests, SDK subscriptions, and persistence retain both prompts.""" + systems = [event for event in result.events if event.get("type") == "system/message"] + assert len(systems) == 2, systems + assert [event.get("surfaceOp") for event in systems] == ["append", "append"], systems + prompts = [message_text(event["data"]["message"]["content"]) for event in systems] + assert "Python SDK prompt version 1." in prompts[0], prompts + assert "Python SDK prompt version 2." not in prompts[0], prompts + assert "Python SDK prompt version 2." in prompts[1], prompts + assert "Python SDK prompt version 1." not in prompts[1], prompts + assert prompts[0] != prompts[1], prompts + assert [event for event in log if event.get("type") == "system/message"] == systems + subscribed = [ + notification.payload["event"] + for notification in result.notifications + if notification.method == "session.event" + and notification.payload.get("event", {}).get("type") == "system/message" + ] + assert subscribed == systems, subscribed + contexts = [event["data"] for event in result.events if event.get("type") == "request/context"] + assert contexts and all(context.get("systemPromptUpdate") == "in-history" for context in contexts), contexts + assert len([event for event in result.events if event.get("type") == "request/header"]) == 1 + assert all(event.get("surfaceOp") in (None, "append") for event in result.events) + first_tool = next(index for index, event in enumerate(result.events) if event.get("type") == "tool/result") + assert result.events.index(systems[1]) > first_tool + assert len(requests) == 4, requests + request_prompts = [] + for index, request in enumerate(requests): + messages = request["messages"] + assert messages[0]["role"] == "system" and message_text(messages[0]["content"]) == prompts[0] + assert request["tools"] == requests[0]["tools"], "prompt update changed tool schemas" + positions = [position for position, message in enumerate(messages) if message["role"] == "system"] + texts = [message_text(messages[position]["content"]) for position in positions] + assert texts == (prompts[:1] if index == 0 else prompts), texts + if index > 0: + assert messages[positions[1] - 1]["role"] == "tool", messages + request_prompts.append(texts) + evidence = { + "requestSystemPrompts": request_prompts, + "systemMessageOperations": [event["surfaceOp"] for event in systems], + "subscribedSystemPrompts": prompts, + "requestContexts": contexts, + } + return {"prompt-history.json": json.dumps(evidence, indent=2, ensure_ascii=False) + "\n"} + + def build_minimal_snapshot_files( requests: list[dict[str, object]], cwd: Path, diff --git a/scripts/snapshots/python-sdk-single-exe/minimal-in-history/prompt-history.json b/scripts/snapshots/python-sdk-single-exe/minimal-in-history/prompt-history.json new file mode 100644 index 0000000000..9fe5365ee8 --- /dev/null +++ b/scripts/snapshots/python-sdk-single-exe/minimal-in-history/prompt-history.json @@ -0,0 +1,35 @@ +{ + "requestSystemPrompts": [ + [ + "You are a helpful software engineer assistant.\n\nPython SDK prompt version 1." + ], + [ + "You are a helpful software engineer assistant.\n\nPython SDK prompt version 1.", + "You are a helpful software engineer assistant.\n\nPython SDK prompt version 2." + ], + [ + "You are a helpful software engineer assistant.\n\nPython SDK prompt version 1.", + "You are a helpful software engineer assistant.\n\nPython SDK prompt version 2." + ], + [ + "You are a helpful software engineer assistant.\n\nPython SDK prompt version 1.", + "You are a helpful software engineer assistant.\n\nPython SDK prompt version 2." + ] + ], + "systemMessageOperations": [ + "append", + "append" + ], + "subscribedSystemPrompts": [ + "You are a helpful software engineer assistant.\n\nPython SDK prompt version 1.", + "You are a helpful software engineer assistant.\n\nPython SDK prompt version 2." + ], + "requestContexts": [ + { + "provider": "deepseek-official", + "model": "smoke-model", + "contextWindow": 1000000, + "systemPromptUpdate": "in-history" + } + ] +} diff --git a/snapshots/sdk/sdk.snapshot.ts b/snapshots/sdk/sdk.snapshot.ts index 2bb0b65254..3c821f246d 100644 --- a/snapshots/sdk/sdk.snapshot.ts +++ b/snapshots/sdk/sdk.snapshot.ts @@ -893,6 +893,28 @@ describe('TypeScript SDK snapshots over the jsonrpc runtime', () => { expect(normalizedResult).toBe(await readFile(resultExpectedPath, 'utf8')) } + if (scenario.name === 'system-prompt-in-history') { + const events = notifications.flatMap(notification => { + const event = notificationEvent(notification) + return event === undefined ? [] : [event] + }) + const systems = events.filter(event => event.type === 'system/message') + expect(systems).toHaveLength(2) + expect(systems.map(event => event.surfaceOp)).toEqual(['append', 'append']) + const prompts = systems.map(event => (event.data as { + message: { content: Array<{ text: string }> } + }).message.content[0]!.text) + expect(prompts[0]).not.toContain('Snapshot guidance added after the first read') + expect(prompts[1]).toContain('Snapshot guidance added after the first read') + expect(prompts[1]).not.toBe(prompts[0]) + expect(events.indexOf(systems[1]!)).toBeGreaterThan(events.findIndex(event => event.type === 'tool/result')) + expect(events.filter(event => event.type === 'request/header')).toHaveLength(1) + expect(events.filter(event => event.type === 'request/context')).toMatchObject([ + { data: { systemPromptUpdate: 'in-history' } }, + ]) + expect(events.filter(event => event.surfaceOp !== undefined).every(event => event.surfaceOp === 'append')).toBe(true) + } + // Wire-shape invariants that must hold in every mode. if (scenario.manifest.workspace?.final === true) { const expectedWorkspace = await captureExpectedWorkspaceSnapshot(join(scenario.dir, 'workspace.expected')) diff --git a/snapshots/sdk/system-prompt-in-history/notifications.expected.jsonl b/snapshots/sdk/system-prompt-in-history/notifications.expected.jsonl new file mode 100644 index 0000000000..461049e2ea --- /dev/null +++ b/snapshots/sdk/system-prompt-in-history/notifications.expected.jsonl @@ -0,0 +1,21 @@ +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":3,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Read task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}}} +{"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"running"}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":4,"time":0,"data":{"turn":1}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":5,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Read task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"session/title","seq":10,"time":0,"data":{"title":"Read task.txt with the read","messageSeqs":[8],"source":{"kind":"fallback"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/header","seq":11,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/context","seq":12,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash","systemPromptUpdate":"in-history"}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_task_read","name":"read","args":["{\"file_path\":\"task.txt\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":14,"time":0,"data":{"turn":1,"step":1,"callId":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":15,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_task_read"},"content":[{"type":"tool-result","toolCallId":"call_task_read","content":[{"type":"text","text":"{{cwd}}/task.txt\nfile\n\n1: Reply with the single word DONE after reading this file.\n\n(End of file - total 1 lines)\n"}],"isError":false}],"role":"user","id":"{{sessionId}}"},"meta":{"path":"{{cwd}}/task.txt","offset":1,"lines":[{"number":1,"text":"Reply with the single word DONE after reading this file."}],"totalLines":1}},"sourceEventSeqs":[14],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":16,"time":0,"data":{"turn":1,"step":1}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":17,"time":0,"data":{"turn":1,"step":2}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"system/message","seq":18,"time":0,"data":{"turn":1,"step":2,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":19,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["DONE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":20,"time":0,"data":{"turn":1,"step":2}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":21,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}}} +{"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}} diff --git a/snapshots/sdk/system-prompt-in-history/replay.override.json b/snapshots/sdk/system-prompt-in-history/replay.override.json new file mode 100644 index 0000000000..73179c6ca3 --- /dev/null +++ b/snapshots/sdk/system-prompt-in-history/replay.override.json @@ -0,0 +1,22 @@ +[ + { + "kind": "chunks", + "chunks": [ + { "type": "block-start", "index": 0, "blockType": "tool-call" }, + { "type": "tool-call-delta", "index": 0, "id": "call_task_read", "name": "read", "argumentsDelta": "{\"file_path\":\"task.txt\"}" }, + { "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "call_task_read", "name": "read", "arguments": "{\"file_path\":\"task.txt\"}" } }, + { "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 5 } }, + { "type": "finish", "reason": { "kind": "tool-calls" } } + ] + }, + { + "kind": "chunks", + "chunks": [ + { "type": "block-start", "index": 0, "blockType": "text" }, + { "type": "text-delta", "index": 0, "text": "DONE" }, + { "type": "block-end", "index": 0, "block": { "type": "text", "text": "DONE" } }, + { "type": "usage", "usage": { "inputTokens": 12, "outputTokens": 2, "cacheReadTokens": 10 } }, + { "type": "finish", "reason": { "kind": "stop" } } + ] + } +] diff --git a/snapshots/sdk/system-prompt-in-history/result.expected.json b/snapshots/sdk/system-prompt-in-history/result.expected.json new file mode 100644 index 0000000000..a078961bf8 --- /dev/null +++ b/snapshots/sdk/system-prompt-in-history/result.expected.json @@ -0,0 +1 @@ +{"sessionId":"{{sessionId}}","finalResponse":"DONE"} diff --git a/snapshots/sdk/system-prompt-in-history/session.v2.jsonl b/snapshots/sdk/system-prompt-in-history/session.v2.jsonl new file mode 100644 index 0000000000..4d178df2e6 --- /dev/null +++ b/snapshots/sdk/system-prompt-in-history/session.v2.jsonl @@ -0,0 +1,23 @@ +{"type":"session","version":2,"id":"{{session:1}}","createdAt":0,"cwd":"{{cwd}}","isSeeded":false,"delegationDepth":0} +{"type":"permission/preset","data":{"preset":"danger-full-access"}} +{"type":"sandbox/mode","data":{"mode":"danger-full-access"}} +{"type":"approval/policy","data":{"policy":"never"}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Read task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"}]}} +{"type":"turn/start","data":{"turn":1}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} +{"type":"step/start","data":{"turn":1,"step":1}} +{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Read task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"} +{"type":"session/title","data":{"title":"Read task.txt with the read","messageSeqs":[8],"source":{"kind":"fallback"}}} +{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}} +{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","systemPromptUpdate":"in-history"}} +{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_task_read","name":"read","args":["{\"file_path\":\"task.txt\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_task_read","name":"read","arguments":"{\"file_path\":\"task.txt\"}"}} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_task_read"},"content":[{"type":"tool-result","toolCallId":"call_task_read","content":[{"type":"text","text":"{{cwd}}/task.txt\nfile\n\n1: Reply with the single word DONE after reading this file.\n\n(End of file - total 1 lines)\n"}],"isError":false}],"role":"user","id":"{{message:5}}"},"meta":{"path":"{{cwd}}/task.txt","offset":1,"lines":[{"number":1,"text":"Reply with the single word DONE after reading this file."}],"totalLines":1}},"sourceEventSeqs":[14],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":1}} +{"type":"step/start","data":{"turn":1,"step":2}} +{"type":"system/message","data":{"turn":1,"step":2,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{message:6}}"}},"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["DONE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":2,"cacheReadTokens":10}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":2}} +{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/snapshots/sdk/system-prompt-in-history/snapshot.yml b/snapshots/sdk/system-prompt-in-history/snapshot.yml new file mode 100644 index 0000000000..45c99997a7 --- /dev/null +++ b/snapshots/sdk/system-prompt-in-history/snapshot.yml @@ -0,0 +1,9 @@ +version: 1 +scenario: system-prompt-in-history +profile: sdk +composition: system-prompt-in-history +recording: authored +header: + class: system-prompt-in-history +replay: + override: true diff --git a/snapshots/sdk/system-prompt-in-history/workspace/task.txt b/snapshots/sdk/system-prompt-in-history/workspace/task.txt new file mode 100644 index 0000000000..57548edaf3 --- /dev/null +++ b/snapshots/sdk/system-prompt-in-history/workspace/task.txt @@ -0,0 +1 @@ +Reply with the single word DONE after reading this file.