Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input
# Conflicts: # docs/architecture.i18n.yaml # docs/cordis-catalog/services.md # docs/core-data-structures/core.i18n.yaml # docs/module-graph.md # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # packages/README.i18n.yaml # packages/llm/llm-pi-ai/README.i18n.yaml # packages/llm/llm-pi-ai/package.json # packages/llm/llm-pi-ai/src/adapter.ts # packages/llm/llm-pi-ai/src/index.ts # packages/llm/llm-pi-ai/tests/adapter.spec.ts # packages/llm/llm-pi-ai/tsconfig.json # packages/llm/llm/README.i18n.yaml # pnpm-lock.yaml # tsconfig.host.json
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -14,7 +14,7 @@
|
||||
{"type":"assistant/chunk","seq":78,"time":1785078729804,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":79,"time":1785078729807,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to use the web_fetch tool exactly once to fetch http://127.0.0.1:43117/menu.html, then reply with exactly \"DONE\". Let me do that."},{"type":"tool-call","id":"call_00_sxjOyfDYN07koiE7jiIa5326","name":"web_fetch","arguments":"{\"url\": \"http://127.0.0.1:43117/menu.html\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-pro"},"id":"58db7df1-5331-49ca-b34f-09c59d8d8c85"},"usage":{"inputTokens":5405,"outputTokens":103,"cacheReadTokens":0,"reasoningTokens":44}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":80,"time":1785078729809,"data":{"turn":1,"step":1,"callId":"call_00_sxjOyfDYN07koiE7jiIa5326","name":"web_fetch","arguments":"{\"url\": \"http://127.0.0.1:43117/menu.html\"}"}}
|
||||
{"type":"tool/result","seq":81,"time":1785078729843,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_00_sxjOyfDYN07koiE7jiIa5326"},"content":[{"type":"tool-result","toolCallId":"call_00_sxjOyfDYN07koiE7jiIa5326","content":[{"type":"text","text":"Fetched http://127.0.0.1:43117/menu.html (HTTP 200)\n\nMenu\n\n# Café menu\n\nPrices include **service & _tax_** — updated daily.\n\n- Espresso\n- Flat white\n\n| Drink | Price |\n| --- | --- |\n| Espresso | €2 |\n| Flat white | €3 |\n\nSee [today’s specials](https://fixture.invalid/specials)."}],"isError":false}],"role":"user","id":"fca910ec-ed8f-45a9-8dda-1e88cfd41126"}},"sourceEventSeqs":[80],"surfaceOp":"append"}
|
||||
{"type":"tool/result","seq":81,"time":1785078729843,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_00_sxjOyfDYN07koiE7jiIa5326"},"content":[{"type":"tool-result","toolCallId":"call_00_sxjOyfDYN07koiE7jiIa5326","content":[{"type":"text","text":"Fetched http://127.0.0.1:43117/menu.html (HTTP 200)\n\nMenu\n\n# Café menu\n\nPrices include **service & _tax_** — updated daily.\n\n- Espresso\n- Flat white\n\n| Drink | Price |\n| --- | --- |\n| Espresso | €2 |\n| Flat white | €3 |\n\nSee [today’s specials](https://fixture.invalid/specials)."}],"isError":false}],"role":"user","id":"fca910ec-ed8f-45a9-8dda-1e88cfd41126"},"meta":{"url":"http://127.0.0.1:43117/menu.html","statusCode":200,"truncated":false}},"sourceEventSeqs":[80],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":82,"time":1785078729847,"data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","seq":83,"time":1785078729848,"data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/chunk","seq":84,"time":1785078730611,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
|
||||
@@ -8,6 +8,10 @@ The headless demo combines the real DeepSeek adapter and coding capabilities wit
|
||||
```mermaid
|
||||
flowchart LR
|
||||
cfg["examples/headless-agent<br/>cordis.yml"]
|
||||
plugin_headless_settings["settings<br/>@deepseek-ai/dsh-settings-local"]
|
||||
cfg --> plugin_headless_settings
|
||||
plugin_headless_credentials["credentials<br/>@deepseek-ai/dsh-credentials-local"]
|
||||
cfg --> plugin_headless_credentials
|
||||
plugin_headless_llm_deepseek["llm-deepseek<br/>@deepseek-ai/dsh-llm-deepseek"]
|
||||
cfg --> plugin_headless_llm_deepseek
|
||||
plugin_headless_subprocess["subprocess<br/>@deepseek-ai/dsh-subprocess-local"]
|
||||
@@ -55,6 +59,8 @@ flowchart LR
|
||||
|
||||
| Plugin id | Package / module |
|
||||
| --- | --- |
|
||||
| `settings` | `@deepseek-ai/dsh-settings-local` |
|
||||
| `credentials` | `@deepseek-ai/dsh-credentials-local` |
|
||||
| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
|
||||
| `subprocess` | `@deepseek-ai/dsh-subprocess-local` |
|
||||
| `bash` | `@deepseek-ai/dsh-bash-local` |
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
# One-shot coding agent with format-pure stdout. The app bin loads the
|
||||
# gitignored root `.env`; this file reads `DEEPSEEK_API_KEY` and optional
|
||||
# `DEEPSEEK_BASE_URL` through `!!js`.
|
||||
# gitignored root `.env` into the process environment; entry configs here are
|
||||
# the composition base, while user-plane values resolve per request through
|
||||
# the two providers below.
|
||||
|
||||
# User-settings document (`$DSH_HOME/settings.yaml`, hot-reloaded): a
|
||||
# `llm-deepseek:` section there overrides the adapter entry below without a
|
||||
# restart.
|
||||
- id: settings
|
||||
name: '@deepseek-ai/dsh-settings-local'
|
||||
|
||||
# Credential store: the live process environment over `$DSH_HOME/.env`
|
||||
# (owner-only file, hot-reloaded). The adapter resolves `DEEPSEEK_API_KEY`
|
||||
# through it at each request, so no key is inlined in this file.
|
||||
- id: credentials
|
||||
name: '@deepseek-ai/dsh-credentials-local'
|
||||
|
||||
# The DeepSeek adapter. Swap to '@deepseek-ai/dsh-llm-pi-ai' for the pi-ai-backed
|
||||
# twin (same config shape; `reasoning: high` replaces thinking/reasoningEffort).
|
||||
# Shipped default: full thinking at max effort on every request (wire-only
|
||||
# defaults; they never enter the request header).
|
||||
# twin (a `providers` dict keyed by route; `reasoning: high` replaces
|
||||
# thinking/reasoningEffort). Shipped default: full thinking at max effort on
|
||||
# every request (wire-only defaults; they never enter the request header).
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
config:
|
||||
apiKey: !!js process.env.DEEPSEEK_API_KEY
|
||||
baseURL: !!js process.env.DEEPSEEK_BASE_URL
|
||||
thinking: enabled
|
||||
reasoningEffort: max
|
||||
models:
|
||||
|
||||
18
examples/headless-agent/credentials.cordis.snapshot.yml
Normal file
18
examples/headless-agent/credentials.cordis.snapshot.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
# Keyless dynamic-configuration composition: the base settings and credentials
|
||||
# providers see only the isolated run home, no API key exists anywhere, and
|
||||
# the deepseek route still registers — so the prompt fails with the actionable
|
||||
# MISSING_CREDENTIAL guidance this snapshot pins as first-run UX.
|
||||
- id: base
|
||||
name: '@cordisjs/plugin-include'
|
||||
config:
|
||||
path: ./cordis.yml
|
||||
patches:
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
disabled: true
|
||||
- insert:
|
||||
# The endpoint is never dialed: credential resolution fails first.
|
||||
- id: llm-deepseek-keyless
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
config:
|
||||
baseURL: 'http://127.0.0.1:9'
|
||||
@@ -27,6 +27,8 @@ const goalScenarioDir = join(snapshotsDir, 'goal-tools')
|
||||
const goalConfigPath = fileURLToPath(new URL('../goal.cordis.snapshot.yml', import.meta.url))
|
||||
const retryScenarioDir = join(snapshotsDir, 'provider-retry')
|
||||
const retryConfigPath = fileURLToPath(new URL('../retry.cordis.snapshot.yml', import.meta.url))
|
||||
const credentialsScenarioDir = join(snapshotsDir, 'missing-credential')
|
||||
const credentialsConfigPath = fileURLToPath(new URL('../credentials.cordis.snapshot.yml', import.meta.url))
|
||||
const ralphScenarioDir = join(snapshotsDir, 'ralph-loop')
|
||||
const ralphConfigPath = fileURLToPath(new URL('../ralph.cordis.snapshot.yml', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('../../../packages/examples/cli-demo/src/bin.ts', import.meta.url))
|
||||
@@ -168,6 +170,40 @@ describe('headless stream-json snapshots', () => {
|
||||
expect(normalized).toBe(await readFile(streamExpected, 'utf8'))
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('surfaces actionable missing-credential guidance through the one-shot app', async () => {
|
||||
const streamExpected = join(credentialsScenarioDir, 'stream-json.expected.jsonl')
|
||||
let runCwd = ''
|
||||
const result = await runLoaderSmoke({
|
||||
label: 'missing-credential headless stream-json snapshot',
|
||||
tempDirPrefix: 'headless-snapshot-missing-credential-',
|
||||
binScript,
|
||||
configPath: credentialsConfigPath,
|
||||
binArgs: ['--config', credentialsConfigPath, '--output-format', 'stream-json', 'say pong'],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
// First-run posture: no key in the environment, none under ./.dsh.
|
||||
DEEPSEEK_API_KEY: '',
|
||||
DEEPSEEK_BASE_URL: '',
|
||||
NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '),
|
||||
},
|
||||
// The designed failure surface: the one-shot app reports the failed turn.
|
||||
expectedExitCode: 1,
|
||||
prepare: (cwd) => { runCwd = cwd },
|
||||
})
|
||||
|
||||
// The guidance leads with the credential store — the path that keeps the
|
||||
// secret out of configuration files — and offers a literal key last.
|
||||
expect(result.stderr).toBe(
|
||||
'dsh-cli-demo: turn 1 failed at step 1: llm-deepseek: no API key for provider route "deepseek";'
|
||||
+ ' store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it),'
|
||||
+ ' export DEEPSEEK_API_KEY in the launching environment, or — as a last resort — set a literal'
|
||||
+ ' "apiKey" in the llm-deepseek settings section\n',
|
||||
)
|
||||
const normalized = normalizeHeadlessStream(result.stdout, runCwd)
|
||||
if (refreshing) await writeFile(streamExpected, normalized)
|
||||
expect(normalized).toBe(await readFile(streamExpected, 'utf8'))
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('logs the model default and a dynamic next-step reasoning effort', async () => {
|
||||
const result = await runLoaderSmoke({
|
||||
label: 'reasoning effort headless stream-json snapshot',
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":0,"time":0,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"say pong"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":2,"time":0,"data":{"title":"say pong","messageSeqs":[1],"source":{"kind":"fallback"}}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash","reasoningEffort":"high"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":5,"time":0,"data":{"turn":1,"step":1}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":6,"time":0,"data":{"turn":1,"reason":{"kind":"error","step":1,"failure":{"message":"llm-deepseek: no API key for provider route \"deepseek\"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), export DEEPSEEK_API_KEY in the launching environment, or — as a last resort — set a literal \"apiKey\" in the llm-deepseek settings section","code":"MISSING_CREDENTIAL"}}}}}
|
||||
{"type":"result","success":false,"sessionId":"{{sessionId}}","turn":1,"result":"","reason":{"kind":"error","step":1,"failure":{"message":"llm-deepseek: no API key for provider route \"deepseek\"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), export DEEPSEEK_API_KEY in the launching environment, or — as a last resort — set a literal \"apiKey\" in the llm-deepseek settings section","code":"MISSING_CREDENTIAL"}}}
|
||||
@@ -22,6 +22,7 @@
|
||||
"@deepseek-ai/dsh-commands": "workspace:*",
|
||||
"@deepseek-ai/dsh-compact-basic": "workspace:*",
|
||||
"@deepseek-ai/dsh-compact-tool-result-prune": "workspace:*",
|
||||
"@deepseek-ai/dsh-credentials-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-fs-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-fs-policy": "workspace:*",
|
||||
"@deepseek-ai/dsh-fs-sandbox": "workspace:^",
|
||||
@@ -54,6 +55,7 @@
|
||||
"@deepseek-ai/dsh-session-telemetry-otel": "workspace:*",
|
||||
"@deepseek-ai/dsh-session-title": "workspace:*",
|
||||
"@deepseek-ai/dsh-session-title-first-message-llm": "workspace:*",
|
||||
"@deepseek-ai/dsh-settings-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-skill": "workspace:*",
|
||||
"@deepseek-ai/dsh-skill-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-spill-local": "workspace:*",
|
||||
|
||||
Reference in New Issue
Block a user