@shuind/dsh-codex-harness 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +22 -7
- package/README.zh.md +3 -3
- package/cordis.patch.yml +5 -5
- package/lib/index.js +410 -4
- package/lib/types/index.d.ts +12 -0
- package/lib/types/index.js +120 -1
- package/lib/types/patch.js +15 -15
- package/lib/types/remote.d.ts +21 -0
- package/lib/types/remote.js +324 -0
- package/package.json +57 -13
- package/presets/codex/agent.cordis.yml +36 -0
- package/presets/codex/preset.yml +3 -3
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 DeepSeek
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
# @shuind/dsh-codex-harness
|
|
2
2
|
|
|
3
|
-
给 DSH 接入 Codex 风格 GPT
|
|
3
|
+
给 DSH 接入 Codex 风格 GPT 模型的插件。
|
|
4
4
|
|
|
5
|
-
DSH
|
|
5
|
+
选择 `Codex 模式` 时,本包复用用户在 DSH 中已经配置的模型,只增加 Codex 体验层:
|
|
6
|
+
|
|
7
|
+
- 检测用户配置的 GPT 系列模型;
|
|
8
|
+
- 为未声明能力的 GPT 模型补充可选思考强度(`low`、`medium`、`high`、`xhigh`、`max`);
|
|
9
|
+
- 为未声明能力的 GPT 模型默认补充文本和图片输入;
|
|
10
|
+
- 默认优先把 GPT Responses 请求中的 `web_search` 改为远程 hosted tool;失败时回退到保留的 DSH `web_search` function tool;
|
|
11
|
+
- 默认优先调用远程 `/responses/compact`,并把返回的原生 compaction item 在后续 Responses 请求中恢复;失败时回退到保留的 DSH 本地 compaction;
|
|
12
|
+
- 继续提供 Codex 的 `exec_command`、`write_stdin`、`apply_patch` 和 `update_plan`。
|
|
13
|
+
|
|
14
|
+
DSH 的通用 `@deepseek-ai/dsh-llm-pi-ai` 继续负责用户配置的 provider、Responses 中转地址、API key、实际模型请求和图片附件传输。本插件不会创建新的 `openai-codex` route,也不会覆盖用户的 endpoint 或凭据。
|
|
6
15
|
|
|
7
16
|
## 安装
|
|
8
17
|
|
|
9
18
|
```sh
|
|
10
|
-
dsh plugin --profile web add @shuind/dsh-codex-harness@0.1.
|
|
19
|
+
dsh plugin --profile web add @shuind/dsh-codex-harness@0.1.10
|
|
11
20
|
```
|
|
12
21
|
|
|
13
22
|
重启 Web,创建新会话,在模式菜单中选择 `Codex 模式`。
|
|
14
23
|
|
|
24
|
+
## 中转站配置
|
|
25
|
+
|
|
26
|
+
请继续在 DSH 的 Models/`llm-pi-ai` 配置中填写你的 Responses 中转站地址、API key 和模型。例如 provider 下配置 `api: openai-responses`、`baseURL`、`apiKeyEnv`,并在 `models` 中填写中转站实际支持的 GPT 模型 id。Codex 模式不会替换这些配置。
|
|
27
|
+
|
|
28
|
+
插件只会把缺少 `input` 或 `reasoningEfforts` 的 GPT 模型配置补成图片和思考强度能力;如果用户已经显式配置了这些字段,则保持用户配置不变。不同中转站对 GPT 模型名、图片格式、reasoning 参数和 hosted `web_search`/`responses/compact` 的支持程度不同,远程请求失败时会回退到 DSH 本地实现。
|
|
29
|
+
|
|
15
30
|
如果之前安装过旧包,先移除旧包,并把旧的 Codex preset 目录备份后再重启:
|
|
16
31
|
|
|
17
32
|
```sh
|
|
@@ -19,7 +34,7 @@ dsh plugin --profile web remove @shuind/dsh-codex
|
|
|
19
34
|
mv "$DSH_HOME/.agent-presets/codex" "$DSH_HOME/.agent-presets/codex.old"
|
|
20
35
|
```
|
|
21
36
|
|
|
22
|
-
安装器会把随包提供的 preset 写入 `$DSH_HOME/.agent-presets/codex
|
|
37
|
+
安装器会把随包提供的 preset 写入 `$DSH_HOME/.agent-presets/codex`,不会覆盖已有目录。升级已有安装时,请先备份并移除旧的 `codex` preset 目录,再重启 Web,让新版本安装包含压缩组的 preset。
|
|
23
38
|
|
|
24
39
|
## 提供的工具
|
|
25
40
|
|
|
@@ -28,11 +43,11 @@ mv "$DSH_HOME/.agent-presets/codex" "$DSH_HOME/.agent-presets/codex.old"
|
|
|
28
43
|
- `apply_patch`
|
|
29
44
|
- `update_plan`
|
|
30
45
|
|
|
31
|
-
工具名称、参数和结果格式兼容 Codex
|
|
46
|
+
工具名称、参数和结果格式兼容 Codex。模型、思考强度、图片和搜索能力通过 DSH 的 LLM、附件和 Web seam 提供,Skills、文件系统策略、Shell、Terminal 等能力仍通过 DSH 的可插拔扩展提供。
|
|
32
47
|
|
|
33
48
|
## 兼容性
|
|
34
49
|
|
|
35
|
-
-
|
|
50
|
+
- 兼容包含 `@deepseek-ai/dsh-llm-pi-ai` 和 `@deepseek-ai/dsh-tool-web` 的 DSH `0.1.0-rc.8` 及更新版本。
|
|
36
51
|
- 兼容旧版和新版 `dsh-tool-todo`。
|
|
37
52
|
- 不依赖 `registerTodosProjection`。
|
|
38
53
|
- Codex 工具只在 Codex preset 中挂载,不会污染 standard、code、minimal 或其他 preset。
|
|
@@ -47,6 +62,6 @@ mv "$DSH_HOME/.agent-presets/codex" "$DSH_HOME/.agent-presets/codex.old"
|
|
|
47
62
|
name: '@shuind/dsh-codex-harness'
|
|
48
63
|
```
|
|
49
64
|
|
|
50
|
-
配置项:`defaultYieldTimeMs`、`pollYieldTimeMs`、`writeYieldTimeMs`、`maxOutputBytes
|
|
65
|
+
配置项:`defaultYieldTimeMs`、`pollYieldTimeMs`、`writeYieldTimeMs`、`maxOutputBytes`、`hostedWebSearch`、`remoteCompact`。其中后两个默认开启,设为 `false` 可以直接使用保留的 DSH 本地 fallback。provider、Responses 中转地址、API key、模型和模型级能力继续由 DSH 的 `llm-pi-ai` 配置管理。
|
|
51
66
|
|
|
52
67
|
MIT License
|
package/README.zh.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
# @shuind/dsh-codex-harness
|
|
2
|
-
|
|
3
|
-
中文文档已默认放在 [README.md](README.md)。
|
|
1
|
+
# @shuind/dsh-codex-harness
|
|
2
|
+
|
|
3
|
+
中文文档已默认放在 [README.md](README.md)。
|
package/cordis.patch.yml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
- insert:
|
|
2
|
-
# The bundle is host-plane installation only. The Codex tool/prompt row
|
|
3
|
-
# lives in presets/codex/agent.cordis.yml, so other presets stay unchanged.
|
|
4
|
-
- id: codex-preset-installer
|
|
5
|
-
name: '@shuind/dsh-codex-harness/installer'
|
|
1
|
+
- insert:
|
|
2
|
+
# The bundle is host-plane installation only. The Codex tool/prompt row
|
|
3
|
+
# lives in presets/codex/agent.cordis.yml, so other presets stay unchanged.
|
|
4
|
+
- id: codex-preset-installer
|
|
5
|
+
name: '@shuind/dsh-codex-harness/installer'
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
2
3
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
4
|
import { randomBytes } from "node:crypto";
|
|
4
5
|
import { resolve } from "node:path";
|
|
6
|
+
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
7
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
8
|
String.raw`start: begin_patch hunk+ end_patch
|
|
6
9
|
begin_patch: "*** Begin Patch" LF
|
|
7
10
|
end_patch: "*** End Patch" LF?
|
|
@@ -399,6 +402,309 @@ async function runWriteStdin(ctx, args, exec, config) {
|
|
|
399
402
|
return output;
|
|
400
403
|
}
|
|
401
404
|
//#endregion
|
|
405
|
+
//#region lib/types/remote.js
|
|
406
|
+
const SETTINGS_NAMESPACE = "llm-pi-ai";
|
|
407
|
+
const REMOTE_COMPACTION_OPEN = "<codex-remote-compaction>";
|
|
408
|
+
const REMOTE_COMPACTION_CLOSE = "</codex-remote-compaction>";
|
|
409
|
+
const HOSTED_REQUESTS = new AsyncLocalStorage();
|
|
410
|
+
let hostedPatchUsers = 0;
|
|
411
|
+
let hostedPatchRestore;
|
|
412
|
+
function isGptModel$1(model) {
|
|
413
|
+
return typeof model === "string" && /(?:^|\/)(?:gpt|chatgpt)(?:[-_.]|\d|$)/i.test(model);
|
|
414
|
+
}
|
|
415
|
+
function settingsOf(ctx) {
|
|
416
|
+
return ctx.get("settings")?.get?.(SETTINGS_NAMESPACE);
|
|
417
|
+
}
|
|
418
|
+
function profileOf(ctx, provider) {
|
|
419
|
+
return settingsOf(ctx)?.providers?.[provider];
|
|
420
|
+
}
|
|
421
|
+
function supportsResponses(profile, provider) {
|
|
422
|
+
return profile?.api === "openai-responses" || profile?.api === "openai-codex-responses" || profile?.api === void 0 && provider === "openai";
|
|
423
|
+
}
|
|
424
|
+
function responsesEndpoint(baseURL, suffix) {
|
|
425
|
+
return `${baseURL.replace(/\/+$/, "")}/${suffix}`;
|
|
426
|
+
}
|
|
427
|
+
function hasHeader(headers, name) {
|
|
428
|
+
return Object.keys(headers ?? {}).some((key) => key.toLowerCase() === name.toLowerCase());
|
|
429
|
+
}
|
|
430
|
+
async function apiHeaders(ctx, profile) {
|
|
431
|
+
const headers = {
|
|
432
|
+
...profile.headers,
|
|
433
|
+
"content-type": "application/json"
|
|
434
|
+
};
|
|
435
|
+
if (profile.apiKeyEnv !== void 0 && !hasHeader(headers, "authorization")) {
|
|
436
|
+
const resolved = await ctx.get("credentials")?.resolve(credentialRef(profile.apiKeyEnv));
|
|
437
|
+
if (resolved?.value === void 0) return void 0;
|
|
438
|
+
headers.authorization = `Bearer ${resolved.value}`;
|
|
439
|
+
}
|
|
440
|
+
return headers;
|
|
441
|
+
}
|
|
442
|
+
function isResponsesRequest(url) {
|
|
443
|
+
try {
|
|
444
|
+
return new URL(url).pathname.replace(/\/+$/, "").endsWith("/responses");
|
|
445
|
+
} catch {
|
|
446
|
+
return url.replace(/\/+$/, "").endsWith("/responses");
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function isWebSearchTool(tool) {
|
|
450
|
+
if (typeof tool !== "object" || tool === null) return false;
|
|
451
|
+
const value = tool;
|
|
452
|
+
if (value.name === "web_search") return true;
|
|
453
|
+
const fn = value.function;
|
|
454
|
+
return typeof fn === "object" && fn !== null && fn.name === "web_search";
|
|
455
|
+
}
|
|
456
|
+
function hasWebSearchTool(body) {
|
|
457
|
+
return Array.isArray(body.tools) && body.tools.some(isWebSearchTool);
|
|
458
|
+
}
|
|
459
|
+
/** Convert a generic DSH Responses tool list to the hosted Codex variant. */
|
|
460
|
+
function addHostedWebSearch(body) {
|
|
461
|
+
const tools = Array.isArray(body.tools) ? body.tools.filter((tool) => !isWebSearchTool(tool)) : [];
|
|
462
|
+
tools.push({
|
|
463
|
+
type: "web_search",
|
|
464
|
+
external_web_access: true
|
|
465
|
+
});
|
|
466
|
+
return {
|
|
467
|
+
...replaceRemoteCompactions(body),
|
|
468
|
+
tools
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
/** Replace the text placeholder written by dsh-compaction-basic with the native item. */
|
|
472
|
+
function replaceRemoteCompactions(body) {
|
|
473
|
+
if (!Array.isArray(body.input)) return body;
|
|
474
|
+
const input = body.input.map((item) => {
|
|
475
|
+
if (typeof item !== "object" || item === null) return item;
|
|
476
|
+
const value = item;
|
|
477
|
+
const marker = (Array.isArray(value.content) ? value.content : []).find((part) => typeof part === "object" && part !== null && typeof part.text === "string" && String(part.text).includes(REMOTE_COMPACTION_OPEN));
|
|
478
|
+
if (marker === void 0) return item;
|
|
479
|
+
const text = String(marker.text);
|
|
480
|
+
const start = text.indexOf(REMOTE_COMPACTION_OPEN) + 25;
|
|
481
|
+
const end = text.indexOf(REMOTE_COMPACTION_CLOSE, start);
|
|
482
|
+
if (end < start) return item;
|
|
483
|
+
return {
|
|
484
|
+
type: "compaction",
|
|
485
|
+
encrypted_content: text.slice(start, end)
|
|
486
|
+
};
|
|
487
|
+
});
|
|
488
|
+
return {
|
|
489
|
+
...body,
|
|
490
|
+
input
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
function hasRemoteCompaction(body) {
|
|
494
|
+
if (!Array.isArray(body.input)) return false;
|
|
495
|
+
return body.input.some((item) => {
|
|
496
|
+
if (typeof item !== "object" || item === null) return false;
|
|
497
|
+
const content = item.content;
|
|
498
|
+
return Array.isArray(content) && content.some((part) => typeof part === "object" && part !== null && typeof part.text === "string" && String(part.text).includes(REMOTE_COMPACTION_OPEN));
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
function canPatchBody(body) {
|
|
502
|
+
return isGptModel$1(body.model) && (hasWebSearchTool(body) || hasRemoteCompaction(body));
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Install a scoped fetch shim for pi-ai's already-built Responses request.
|
|
506
|
+
* The generic DSH adapter remains the owner of auth, streaming, replay, and
|
|
507
|
+
* attachments; this shim changes only the hosted-tool portion of the wire body.
|
|
508
|
+
*/
|
|
509
|
+
function installGlobalHostedWebSearchPatch() {
|
|
510
|
+
const original = globalThis.fetch;
|
|
511
|
+
const patched = async (input, init) => {
|
|
512
|
+
if (!HOSTED_REQUESTS.getStore()) return original(input, init);
|
|
513
|
+
const request = new Request(input, init);
|
|
514
|
+
if (!isResponsesRequest(request.url)) return original(input, init);
|
|
515
|
+
let body;
|
|
516
|
+
try {
|
|
517
|
+
body = JSON.parse(await request.clone().text());
|
|
518
|
+
} catch {
|
|
519
|
+
return original(input, init);
|
|
520
|
+
}
|
|
521
|
+
if (!canPatchBody(body)) return original(input, init);
|
|
522
|
+
const fallbackRequest = request.clone();
|
|
523
|
+
const headers = new Headers(request.headers);
|
|
524
|
+
headers.delete("content-length");
|
|
525
|
+
const hostedRequest = new Request(request, {
|
|
526
|
+
body: JSON.stringify(hasWebSearchTool(body) ? addHostedWebSearch(body) : replaceRemoteCompactions(body)),
|
|
527
|
+
headers
|
|
528
|
+
});
|
|
529
|
+
try {
|
|
530
|
+
const hostedResponse = await original(hostedRequest);
|
|
531
|
+
if (!hostedResponse.ok) return original(fallbackRequest);
|
|
532
|
+
return hostedResponse;
|
|
533
|
+
} catch {
|
|
534
|
+
return original(fallbackRequest);
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
globalThis.fetch = patched;
|
|
538
|
+
return () => {
|
|
539
|
+
if (globalThis.fetch === patched) globalThis.fetch = original;
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
/** Enable the transport patch for this Codex plugin scope only. */
|
|
543
|
+
function installHostedWebSearch() {
|
|
544
|
+
hostedPatchUsers += 1;
|
|
545
|
+
hostedPatchRestore ??= installGlobalHostedWebSearchPatch();
|
|
546
|
+
return () => {
|
|
547
|
+
hostedPatchUsers = Math.max(0, hostedPatchUsers - 1);
|
|
548
|
+
if (hostedPatchUsers === 0) {
|
|
549
|
+
hostedPatchRestore?.();
|
|
550
|
+
hostedPatchRestore = void 0;
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
/** Iterate an existing DSH stream with the hosted-request context installed. */
|
|
555
|
+
function hostedWebSearchStream(next) {
|
|
556
|
+
return (async function* () {
|
|
557
|
+
const iterator = next()[Symbol.asyncIterator]();
|
|
558
|
+
let completed = false;
|
|
559
|
+
try {
|
|
560
|
+
while (true) {
|
|
561
|
+
const item = await HOSTED_REQUESTS.run(true, () => iterator.next());
|
|
562
|
+
if (item.done) {
|
|
563
|
+
completed = true;
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
yield item.value;
|
|
567
|
+
}
|
|
568
|
+
} finally {
|
|
569
|
+
if (!completed) await iterator.return?.();
|
|
570
|
+
}
|
|
571
|
+
})();
|
|
572
|
+
}
|
|
573
|
+
function textOf(message) {
|
|
574
|
+
return message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
575
|
+
}
|
|
576
|
+
function responsesInput(messages) {
|
|
577
|
+
const result = [];
|
|
578
|
+
for (const message of messages) {
|
|
579
|
+
const text = textOf(message);
|
|
580
|
+
if (message.role === "assistant") {
|
|
581
|
+
if (text.length > 0) result.push({
|
|
582
|
+
type: "message",
|
|
583
|
+
role: "assistant",
|
|
584
|
+
content: [{
|
|
585
|
+
type: "output_text",
|
|
586
|
+
text
|
|
587
|
+
}]
|
|
588
|
+
});
|
|
589
|
+
for (const block of message.content) if (block.type === "tool-call") result.push({
|
|
590
|
+
type: "function_call",
|
|
591
|
+
call_id: block.id,
|
|
592
|
+
name: block.name,
|
|
593
|
+
arguments: block.arguments
|
|
594
|
+
});
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
const role = message.role === "system" ? "developer" : "user";
|
|
598
|
+
if (text.length > 0) result.push({
|
|
599
|
+
type: "message",
|
|
600
|
+
role,
|
|
601
|
+
content: [{
|
|
602
|
+
type: "input_text",
|
|
603
|
+
text
|
|
604
|
+
}]
|
|
605
|
+
});
|
|
606
|
+
for (const block of message.content) {
|
|
607
|
+
if (block.type !== "tool-result") continue;
|
|
608
|
+
result.push({
|
|
609
|
+
type: "function_call_output",
|
|
610
|
+
call_id: block.toolCallId,
|
|
611
|
+
output: block.content.filter((item) => item.type === "text").map((item) => item.text).join("")
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return result;
|
|
616
|
+
}
|
|
617
|
+
function responsesTools(tools) {
|
|
618
|
+
if (tools === void 0) return void 0;
|
|
619
|
+
return tools.filter((tool) => tool.name !== "web_search").map((tool) => ({
|
|
620
|
+
type: "function",
|
|
621
|
+
name: tool.name,
|
|
622
|
+
description: tool.description,
|
|
623
|
+
parameters: tool.parameters
|
|
624
|
+
}));
|
|
625
|
+
}
|
|
626
|
+
function compactBody(options) {
|
|
627
|
+
const tools = responsesTools(options.tools);
|
|
628
|
+
const compacted = replaceRemoteCompactions({
|
|
629
|
+
model: options.model,
|
|
630
|
+
input: responsesInput(options.messages.slice(0, -1)),
|
|
631
|
+
...options.system === void 0 ? {} : { instructions: options.system },
|
|
632
|
+
...tools === void 0 ? {} : { tools },
|
|
633
|
+
...options.maxTokens === void 0 ? {} : { max_output_tokens: options.maxTokens },
|
|
634
|
+
...options.reasoningEffort === void 0 ? {} : { reasoning: { effort: options.reasoningEffort } }
|
|
635
|
+
});
|
|
636
|
+
return options.tools?.some((tool) => tool.name === "web_search") ? addHostedWebSearch(compacted) : compacted;
|
|
637
|
+
}
|
|
638
|
+
function compactText(body) {
|
|
639
|
+
const output = Array.isArray(body.output) ? body.output : [];
|
|
640
|
+
const text = [];
|
|
641
|
+
for (const item of output) {
|
|
642
|
+
if (typeof item !== "object" || item === null) continue;
|
|
643
|
+
const value = item;
|
|
644
|
+
if (typeof value.encrypted_content === "string") text.push(value.encrypted_content);
|
|
645
|
+
}
|
|
646
|
+
const encrypted = text.join("\n\n").trim();
|
|
647
|
+
return encrypted.length === 0 ? "" : `${REMOTE_COMPACTION_OPEN}${encrypted}${REMOTE_COMPACTION_CLOSE}`;
|
|
648
|
+
}
|
|
649
|
+
async function remoteCompact(ctx, options) {
|
|
650
|
+
const profile = profileOf(ctx, options.provider);
|
|
651
|
+
if (!supportsResponses(profile, options.provider) || profile?.baseURL === void 0) throw new Error("Codex remote compaction requires an OpenAI Responses provider with baseURL");
|
|
652
|
+
const headers = await apiHeaders(ctx, profile);
|
|
653
|
+
if (headers === void 0) throw new Error("Codex remote compaction has no configured API key");
|
|
654
|
+
const response = await fetch(responsesEndpoint(profile.baseURL, "responses/compact"), {
|
|
655
|
+
method: "POST",
|
|
656
|
+
headers,
|
|
657
|
+
body: JSON.stringify(compactBody(options)),
|
|
658
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
659
|
+
});
|
|
660
|
+
if (!response.ok) throw new Error(`remote compaction returned HTTP ${response.status}`);
|
|
661
|
+
const text = compactText(await response.json());
|
|
662
|
+
if (text.length === 0) throw new Error("remote compaction returned no compaction text");
|
|
663
|
+
return text;
|
|
664
|
+
}
|
|
665
|
+
/** Remote-first compaction waterfall with the existing DSH path as fallback. */
|
|
666
|
+
function remoteCompactStream(ctx, options, next) {
|
|
667
|
+
return (async function* () {
|
|
668
|
+
try {
|
|
669
|
+
const text = await remoteCompact(ctx, options);
|
|
670
|
+
yield {
|
|
671
|
+
type: "block-start",
|
|
672
|
+
index: 0,
|
|
673
|
+
blockType: "text"
|
|
674
|
+
};
|
|
675
|
+
yield {
|
|
676
|
+
type: "text-delta",
|
|
677
|
+
index: 0,
|
|
678
|
+
text
|
|
679
|
+
};
|
|
680
|
+
yield {
|
|
681
|
+
type: "block-end",
|
|
682
|
+
index: 0,
|
|
683
|
+
block: {
|
|
684
|
+
type: "text",
|
|
685
|
+
text
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
yield {
|
|
689
|
+
type: "usage",
|
|
690
|
+
usage: {
|
|
691
|
+
inputTokens: 0,
|
|
692
|
+
outputTokens: 0
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
yield {
|
|
696
|
+
type: "finish",
|
|
697
|
+
reason: { kind: "stop" }
|
|
698
|
+
};
|
|
699
|
+
} catch (error) {
|
|
700
|
+
if (options.signal?.aborted) throw error;
|
|
701
|
+
ctx.logger.warn("codex: remote compaction failed; using dsh-compaction-basic fallback");
|
|
702
|
+
ctx.logger.warn(error);
|
|
703
|
+
yield* hostedWebSearchStream(next);
|
|
704
|
+
}
|
|
705
|
+
})();
|
|
706
|
+
}
|
|
707
|
+
//#endregion
|
|
402
708
|
//#region lib/types/index.js
|
|
403
709
|
/** Codex-compatible prompt overlay and core tools for a dsh agent preset. */
|
|
404
710
|
const name = "codex";
|
|
@@ -406,15 +712,103 @@ const inject = [
|
|
|
406
712
|
"tools",
|
|
407
713
|
"systemPrompt",
|
|
408
714
|
"shell",
|
|
409
|
-
"fs"
|
|
715
|
+
"fs",
|
|
716
|
+
"llm",
|
|
717
|
+
"credentials"
|
|
410
718
|
];
|
|
411
719
|
/** Runtime configuration schema for the Codex tool bridge. */
|
|
412
720
|
const Config = z.object({
|
|
413
721
|
defaultYieldTimeMs: z.number().step(1).min(0).default(1e4),
|
|
414
722
|
pollYieldTimeMs: z.number().step(1).min(0).default(5e3),
|
|
415
723
|
writeYieldTimeMs: z.number().step(1).min(0).default(250),
|
|
416
|
-
maxOutputBytes: z.number().step(1).min(1).default(64e3)
|
|
724
|
+
maxOutputBytes: z.number().step(1).min(1).default(64e3),
|
|
725
|
+
hostedWebSearch: z.boolean().default(true),
|
|
726
|
+
remoteCompact: z.boolean().default(true)
|
|
417
727
|
});
|
|
728
|
+
const LLM_PI_AI_SETTINGS = settingsNamespace("llm-pi-ai");
|
|
729
|
+
const GPT_REASONING_EFFORTS = {
|
|
730
|
+
low: "low",
|
|
731
|
+
medium: "medium",
|
|
732
|
+
high: "high",
|
|
733
|
+
xhigh: "xhigh",
|
|
734
|
+
max: "max"
|
|
735
|
+
};
|
|
736
|
+
function codexReasoningEfforts(configured) {
|
|
737
|
+
if (configured === false) return false;
|
|
738
|
+
const filtered = configured === void 0 ? {} : Object.fromEntries(Object.entries(configured).filter(([level]) => level !== "off" && level !== "minimal"));
|
|
739
|
+
return Object.keys(filtered).length === 0 ? GPT_REASONING_EFFORTS : filtered;
|
|
740
|
+
}
|
|
741
|
+
/** GPT model ids are the only models whose relay capabilities Codex fills in. */
|
|
742
|
+
function isGptModel(id) {
|
|
743
|
+
return /(?:^|\/)(?:gpt|chatgpt)(?:[-_.]|\d|$)/i.test(id);
|
|
744
|
+
}
|
|
745
|
+
/** Add Codex defaults without overwriting explicit user capabilities. */
|
|
746
|
+
function enrichCodexModel(model) {
|
|
747
|
+
if (!isGptModel(model.id)) return model;
|
|
748
|
+
const input = model.input === void 0 || model.input.length === 0 ? ["text", "image"] : model.input;
|
|
749
|
+
const efforts = codexReasoningEfforts(model.reasoningEfforts);
|
|
750
|
+
if (input === model.input && efforts === model.reasoningEfforts) return model;
|
|
751
|
+
return {
|
|
752
|
+
...model,
|
|
753
|
+
input,
|
|
754
|
+
reasoningEfforts: efforts
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
/** The settings schema keys modelOverrides by id, so its values must not carry an id field. */
|
|
758
|
+
function enrichCodexOverride(id, model) {
|
|
759
|
+
if (!isGptModel(id)) return model;
|
|
760
|
+
const { id: _id, ...withoutId } = enrichCodexModel({
|
|
761
|
+
...model,
|
|
762
|
+
id
|
|
763
|
+
});
|
|
764
|
+
const inputMissing = model.input === void 0 || model.input.length === 0;
|
|
765
|
+
const nextEfforts = codexReasoningEfforts(model.reasoningEfforts);
|
|
766
|
+
const reasoningChanged = JSON.stringify(nextEfforts) !== JSON.stringify(model.reasoningEfforts);
|
|
767
|
+
if (!inputMissing && !reasoningChanged) return model;
|
|
768
|
+
return {
|
|
769
|
+
...withoutId,
|
|
770
|
+
reasoningEfforts: nextEfforts
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
/** Persist only missing GPT capabilities into the user's existing pi-ai model config. */
|
|
774
|
+
async function enrichConfiguredGptModels(ctx) {
|
|
775
|
+
const settings = ctx.get("settings");
|
|
776
|
+
if (settings === void 0) return;
|
|
777
|
+
const current = settings.get(LLM_PI_AI_SETTINGS);
|
|
778
|
+
if (current?.providers === void 0) return;
|
|
779
|
+
const providers = {};
|
|
780
|
+
let changed = false;
|
|
781
|
+
for (const [provider, profile] of Object.entries(current.providers)) {
|
|
782
|
+
const models = profile.models;
|
|
783
|
+
const overrides = profile.modelOverrides;
|
|
784
|
+
const nextModels = Array.isArray(models) ? models.map(enrichCodexModel) : void 0;
|
|
785
|
+
const nextOverrides = overrides === void 0 ? void 0 : Object.fromEntries(Object.entries(overrides).map(([id, model]) => [id, enrichCodexOverride(id, model)]));
|
|
786
|
+
const modelsChanged = nextModels !== void 0 && nextModels.some((model, index) => model !== models?.[index]);
|
|
787
|
+
const overridesChanged = nextOverrides !== void 0 && overrides !== void 0 && Object.entries(nextOverrides).some(([id, model]) => model !== overrides[id]);
|
|
788
|
+
if (modelsChanged || overridesChanged) {
|
|
789
|
+
changed = true;
|
|
790
|
+
providers[provider] = {
|
|
791
|
+
...modelsChanged ? { models: nextModels } : {},
|
|
792
|
+
...overridesChanged ? { modelOverrides: nextOverrides } : {}
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (changed) await settings.update(LLM_PI_AI_SETTINGS, { providers });
|
|
797
|
+
}
|
|
798
|
+
/** Keep newly edited GPT model entries enriched while Codex mode is mounted. */
|
|
799
|
+
function watchConfiguredGptModels(ctx) {
|
|
800
|
+
let tail = Promise.resolve();
|
|
801
|
+
const schedule = () => {
|
|
802
|
+
tail = tail.then(() => enrichConfiguredGptModels(ctx)).catch((error) => {
|
|
803
|
+
ctx.logger.warn("codex: could not enrich configured GPT model capabilities");
|
|
804
|
+
ctx.logger.warn(error);
|
|
805
|
+
});
|
|
806
|
+
};
|
|
807
|
+
ctx.on("settings/document-updated", (ns) => {
|
|
808
|
+
if (ns === LLM_PI_AI_SETTINGS) schedule();
|
|
809
|
+
});
|
|
810
|
+
schedule();
|
|
811
|
+
}
|
|
418
812
|
const CODEX_BASE_PROMPT = String.raw`You are Codex, based on {{model}}. You are running as a coding agent in dsh Web on a user's computer.
|
|
419
813
|
|
|
420
814
|
## General
|
|
@@ -821,9 +1215,21 @@ function apply(ctx, config = {}) {
|
|
|
821
1215
|
defaultYieldTimeMs: config.defaultYieldTimeMs ?? 1e4,
|
|
822
1216
|
pollYieldTimeMs: config.pollYieldTimeMs ?? 5e3,
|
|
823
1217
|
writeYieldTimeMs: config.writeYieldTimeMs ?? 250,
|
|
824
|
-
maxOutputBytes: config.maxOutputBytes ?? 64e3
|
|
1218
|
+
maxOutputBytes: config.maxOutputBytes ?? 64e3,
|
|
1219
|
+
hostedWebSearch: config.hostedWebSearch ?? true,
|
|
1220
|
+
remoteCompact: config.remoteCompact ?? true
|
|
825
1221
|
};
|
|
826
1222
|
if (ctx.fs.sandboxMode !== void 0 && ctx.get("sandboxPolicy") === void 0) throw new Error("codex: a sandboxing filesystem requires ctx.sandboxPolicy");
|
|
1223
|
+
watchConfiguredGptModels(ctx);
|
|
1224
|
+
if ((resolved.hostedWebSearch || resolved.remoteCompact) && typeof ctx.effect === "function") {
|
|
1225
|
+
const disposeHostedWebSearch = installHostedWebSearch();
|
|
1226
|
+
ctx.effect(() => disposeHostedWebSearch, "codex: Responses transport wrapper");
|
|
1227
|
+
}
|
|
1228
|
+
if (resolved.hostedWebSearch || resolved.remoteCompact) ctx.on("llm/stream", (options, next) => {
|
|
1229
|
+
if (resolved.remoteCompact && options.purpose === "compaction" && isGptModel(options.model)) return remoteCompactStream(ctx, options, next);
|
|
1230
|
+
if ((resolved.hostedWebSearch || resolved.remoteCompact) && options.purpose === void 0 && isGptModel(options.model)) return hostedWebSearchStream(next);
|
|
1231
|
+
return next();
|
|
1232
|
+
});
|
|
827
1233
|
ctx.systemPrompt.section({
|
|
828
1234
|
name: "codex:base",
|
|
829
1235
|
order: 10,
|
|
@@ -840,4 +1246,4 @@ var types_default = {
|
|
|
840
1246
|
apply
|
|
841
1247
|
};
|
|
842
1248
|
//#endregion
|
|
843
|
-
export { Config, apply, types_default as default, inject, name };
|
|
1249
|
+
export { Config, apply, types_default as default, enrichCodexModel, inject, name };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -13,9 +13,21 @@ export interface Config {
|
|
|
13
13
|
writeYieldTimeMs?: number;
|
|
14
14
|
/** Maximum output retained in one canonical result, in UTF-8 bytes. */
|
|
15
15
|
maxOutputBytes?: number;
|
|
16
|
+
/** Send GPT Responses requests with the native hosted web_search tool first. */
|
|
17
|
+
hostedWebSearch?: boolean;
|
|
18
|
+
/** Use the provider's /responses/compact endpoint before local compaction. */
|
|
19
|
+
remoteCompact?: boolean;
|
|
16
20
|
}
|
|
17
21
|
/** Runtime configuration schema for the Codex tool bridge. */
|
|
18
22
|
export declare const Config: z<Config>;
|
|
23
|
+
export interface CodexModelProfile {
|
|
24
|
+
id: string;
|
|
25
|
+
input?: string[];
|
|
26
|
+
reasoningEfforts?: false | Record<string, string | null>;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
/** Add Codex defaults without overwriting explicit user capabilities. */
|
|
30
|
+
export declare function enrichCodexModel(model: CodexModelProfile): CodexModelProfile;
|
|
19
31
|
/** Mount the Codex prompt/tool layer inside one fixed agent preset. */
|
|
20
32
|
export declare function apply(ctx: Context, config?: Config): void;
|
|
21
33
|
declare const _default: {
|
package/lib/types/index.js
CHANGED
|
@@ -1,17 +1,113 @@
|
|
|
1
1
|
/** Codex-compatible prompt overlay and core tools for a dsh agent preset. */
|
|
2
2
|
import z from '@deepseek-ai/schemastery';
|
|
3
|
+
import { settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
3
4
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
4
5
|
import { applyPatchHunks, parsePatch } from "./patch.js";
|
|
5
6
|
import { renderExecResult, runExecCommand, runWriteStdin } from "./exec.js";
|
|
7
|
+
import { hostedWebSearchStream, installHostedWebSearch, remoteCompactStream } from "./remote.js";
|
|
6
8
|
export const name = 'codex';
|
|
7
|
-
export const inject = ['tools', 'systemPrompt', 'shell', 'fs'];
|
|
9
|
+
export const inject = ['tools', 'systemPrompt', 'shell', 'fs', 'llm', 'credentials'];
|
|
8
10
|
/** Runtime configuration schema for the Codex tool bridge. */
|
|
9
11
|
export const Config = z.object({
|
|
10
12
|
defaultYieldTimeMs: z.number().step(1).min(0).default(10_000),
|
|
11
13
|
pollYieldTimeMs: z.number().step(1).min(0).default(5_000),
|
|
12
14
|
writeYieldTimeMs: z.number().step(1).min(0).default(250),
|
|
13
15
|
maxOutputBytes: z.number().step(1).min(1).default(64_000),
|
|
16
|
+
hostedWebSearch: z.boolean().default(true),
|
|
17
|
+
remoteCompact: z.boolean().default(true),
|
|
14
18
|
});
|
|
19
|
+
const LLM_PI_AI_SETTINGS = settingsNamespace('llm-pi-ai');
|
|
20
|
+
const GPT_REASONING_EFFORTS = {
|
|
21
|
+
low: 'low',
|
|
22
|
+
medium: 'medium',
|
|
23
|
+
high: 'high',
|
|
24
|
+
xhigh: 'xhigh',
|
|
25
|
+
max: 'max',
|
|
26
|
+
};
|
|
27
|
+
function codexReasoningEfforts(configured) {
|
|
28
|
+
if (configured === false)
|
|
29
|
+
return false;
|
|
30
|
+
const filtered = configured === undefined
|
|
31
|
+
? {}
|
|
32
|
+
: Object.fromEntries(Object.entries(configured).filter(([level]) => level !== 'off' && level !== 'minimal'));
|
|
33
|
+
return Object.keys(filtered).length === 0 ? GPT_REASONING_EFFORTS : filtered;
|
|
34
|
+
}
|
|
35
|
+
/** GPT model ids are the only models whose relay capabilities Codex fills in. */
|
|
36
|
+
function isGptModel(id) {
|
|
37
|
+
return /(?:^|\/)(?:gpt|chatgpt)(?:[-_.]|\d|$)/i.test(id);
|
|
38
|
+
}
|
|
39
|
+
/** Add Codex defaults without overwriting explicit user capabilities. */
|
|
40
|
+
export function enrichCodexModel(model) {
|
|
41
|
+
if (!isGptModel(model.id))
|
|
42
|
+
return model;
|
|
43
|
+
const input = model.input === undefined || model.input.length === 0
|
|
44
|
+
? ['text', 'image']
|
|
45
|
+
: model.input;
|
|
46
|
+
const efforts = codexReasoningEfforts(model.reasoningEfforts);
|
|
47
|
+
if (input === model.input && efforts === model.reasoningEfforts)
|
|
48
|
+
return model;
|
|
49
|
+
return { ...model, input, reasoningEfforts: efforts };
|
|
50
|
+
}
|
|
51
|
+
/** The settings schema keys modelOverrides by id, so its values must not carry an id field. */
|
|
52
|
+
function enrichCodexOverride(id, model) {
|
|
53
|
+
if (!isGptModel(id))
|
|
54
|
+
return model;
|
|
55
|
+
const enriched = enrichCodexModel({ ...model, id });
|
|
56
|
+
const { id: _id, ...withoutId } = enriched;
|
|
57
|
+
const inputMissing = model.input === undefined || model.input.length === 0;
|
|
58
|
+
const nextEfforts = codexReasoningEfforts(model.reasoningEfforts);
|
|
59
|
+
const reasoningChanged = JSON.stringify(nextEfforts) !== JSON.stringify(model.reasoningEfforts);
|
|
60
|
+
if (!inputMissing && !reasoningChanged)
|
|
61
|
+
return model;
|
|
62
|
+
return { ...withoutId, reasoningEfforts: nextEfforts };
|
|
63
|
+
}
|
|
64
|
+
/** Persist only missing GPT capabilities into the user's existing pi-ai model config. */
|
|
65
|
+
async function enrichConfiguredGptModels(ctx) {
|
|
66
|
+
const settings = ctx.get('settings');
|
|
67
|
+
if (settings === undefined)
|
|
68
|
+
return;
|
|
69
|
+
const current = settings.get(LLM_PI_AI_SETTINGS);
|
|
70
|
+
if (current?.providers === undefined)
|
|
71
|
+
return;
|
|
72
|
+
const providers = {};
|
|
73
|
+
let changed = false;
|
|
74
|
+
for (const [provider, profile] of Object.entries(current.providers)) {
|
|
75
|
+
const models = profile.models;
|
|
76
|
+
const overrides = profile.modelOverrides;
|
|
77
|
+
const nextModels = Array.isArray(models) ? models.map(enrichCodexModel) : undefined;
|
|
78
|
+
const nextOverrides = overrides === undefined
|
|
79
|
+
? undefined
|
|
80
|
+
: Object.fromEntries(Object.entries(overrides).map(([id, model]) => [id, enrichCodexOverride(id, model)]));
|
|
81
|
+
const modelsChanged = nextModels !== undefined && nextModels.some((model, index) => model !== models?.[index]);
|
|
82
|
+
const overridesChanged = nextOverrides !== undefined
|
|
83
|
+
&& overrides !== undefined
|
|
84
|
+
&& Object.entries(nextOverrides).some(([id, model]) => model !== overrides[id]);
|
|
85
|
+
if (modelsChanged || overridesChanged) {
|
|
86
|
+
changed = true;
|
|
87
|
+
providers[provider] = {
|
|
88
|
+
...modelsChanged ? { models: nextModels } : {},
|
|
89
|
+
...overridesChanged ? { modelOverrides: nextOverrides } : {},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (changed)
|
|
94
|
+
await settings.update(LLM_PI_AI_SETTINGS, { providers });
|
|
95
|
+
}
|
|
96
|
+
/** Keep newly edited GPT model entries enriched while Codex mode is mounted. */
|
|
97
|
+
function watchConfiguredGptModels(ctx) {
|
|
98
|
+
let tail = Promise.resolve();
|
|
99
|
+
const schedule = () => {
|
|
100
|
+
tail = tail.then(() => enrichConfiguredGptModels(ctx)).catch(error => {
|
|
101
|
+
ctx.logger.warn('codex: could not enrich configured GPT model capabilities');
|
|
102
|
+
ctx.logger.warn(error);
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
ctx.on('settings/document-updated', (ns) => {
|
|
106
|
+
if (ns === LLM_PI_AI_SETTINGS)
|
|
107
|
+
schedule();
|
|
108
|
+
});
|
|
109
|
+
schedule();
|
|
110
|
+
}
|
|
15
111
|
const CODEX_BASE_PROMPT = String.raw `You are Codex, based on {{model}}. You are running as a coding agent in dsh Web on a user's computer.
|
|
16
112
|
|
|
17
113
|
## General
|
|
@@ -308,10 +404,33 @@ export function apply(ctx, config = {}) {
|
|
|
308
404
|
pollYieldTimeMs: config.pollYieldTimeMs ?? 5_000,
|
|
309
405
|
writeYieldTimeMs: config.writeYieldTimeMs ?? 250,
|
|
310
406
|
maxOutputBytes: config.maxOutputBytes ?? 64_000,
|
|
407
|
+
hostedWebSearch: config.hostedWebSearch ?? true,
|
|
408
|
+
remoteCompact: config.remoteCompact ?? true,
|
|
311
409
|
};
|
|
312
410
|
if (ctx.fs.sandboxMode !== undefined && ctx.get('sandboxPolicy') === undefined) {
|
|
313
411
|
throw new Error('codex: a sandboxing filesystem requires ctx.sandboxPolicy');
|
|
314
412
|
}
|
|
413
|
+
// The generic pi-ai plugin remains the owner of the user's configured
|
|
414
|
+
// provider routes. Codex adds only scoped transport behavior and capability
|
|
415
|
+
// metadata; failed remote operations continue through the generic path.
|
|
416
|
+
watchConfiguredGptModels(ctx);
|
|
417
|
+
if ((resolved.hostedWebSearch || resolved.remoteCompact) && typeof ctx.effect === 'function') {
|
|
418
|
+
const disposeHostedWebSearch = installHostedWebSearch();
|
|
419
|
+
ctx.effect(() => disposeHostedWebSearch, 'codex: Responses transport wrapper');
|
|
420
|
+
}
|
|
421
|
+
if (resolved.hostedWebSearch || resolved.remoteCompact) {
|
|
422
|
+
ctx.on('llm/stream', (options, next) => {
|
|
423
|
+
if (resolved.remoteCompact && options.purpose === 'compaction' && isGptModel(options.model)) {
|
|
424
|
+
return remoteCompactStream(ctx, options, next);
|
|
425
|
+
}
|
|
426
|
+
if ((resolved.hostedWebSearch || resolved.remoteCompact)
|
|
427
|
+
&& options.purpose === undefined
|
|
428
|
+
&& isGptModel(options.model)) {
|
|
429
|
+
return hostedWebSearchStream(next);
|
|
430
|
+
}
|
|
431
|
+
return next();
|
|
432
|
+
});
|
|
433
|
+
}
|
|
315
434
|
ctx.systemPrompt.section({ name: 'codex:base', order: 10, text: CODEX_BASE_PROMPT });
|
|
316
435
|
registerExecTools(ctx, resolved);
|
|
317
436
|
registerPatchTool(ctx);
|
package/lib/types/patch.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/** Parser and line-oriented applicator for Codex's `apply_patch` language. */
|
|
2
2
|
/** The grammar sent to providers that support OpenAI custom grammar tools. */
|
|
3
|
-
export const APPLY_PATCH_GRAMMAR = String.raw `start: begin_patch hunk+ end_patch
|
|
4
|
-
begin_patch: "*** Begin Patch" LF
|
|
5
|
-
end_patch: "*** End Patch" LF?
|
|
6
|
-
hunk: add_hunk | delete_hunk | update_hunk
|
|
7
|
-
add_hunk: "*** Add File: " filename LF add_line+
|
|
8
|
-
delete_hunk: "*** Delete File: " filename LF
|
|
9
|
-
update_hunk: "*** Update File: " filename LF change_move? change?
|
|
10
|
-
filename: /(.+)/
|
|
11
|
-
add_line: "+" /(.*)/ LF -> line
|
|
12
|
-
change_move: "*** Move to: " filename LF
|
|
13
|
-
change: (change_context | change_line)+ eof_line?
|
|
14
|
-
change_context: ("@@" | "@@ " /(.+)/) LF
|
|
15
|
-
change_line: ("+" | "-" | " ") /(.*)/ LF
|
|
16
|
-
eof_line: "*** End of File" LF
|
|
17
|
-
%import common.LF
|
|
3
|
+
export const APPLY_PATCH_GRAMMAR = String.raw `start: begin_patch hunk+ end_patch
|
|
4
|
+
begin_patch: "*** Begin Patch" LF
|
|
5
|
+
end_patch: "*** End Patch" LF?
|
|
6
|
+
hunk: add_hunk | delete_hunk | update_hunk
|
|
7
|
+
add_hunk: "*** Add File: " filename LF add_line+
|
|
8
|
+
delete_hunk: "*** Delete File: " filename LF
|
|
9
|
+
update_hunk: "*** Update File: " filename LF change_move? change?
|
|
10
|
+
filename: /(.+)/
|
|
11
|
+
add_line: "+" /(.*)/ LF -> line
|
|
12
|
+
change_move: "*** Move to: " filename LF
|
|
13
|
+
change: (change_context | change_line)+ eof_line?
|
|
14
|
+
change_context: ("@@" | "@@ " /(.+)/) LF
|
|
15
|
+
change_line: ("+" | "-" | " ") /(.*)/ LF
|
|
16
|
+
eof_line: "*** End of File" LF
|
|
17
|
+
%import common.LF
|
|
18
18
|
`;
|
|
19
19
|
function invalid(message) {
|
|
20
20
|
throw new Error(`apply_patch: ${message}`);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
export interface RemoteCodexConfig {
|
|
4
|
+
hostedWebSearch: boolean;
|
|
5
|
+
remoteCompact: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface JsonObject {
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
/** Convert a generic DSH Responses tool list to the hosted Codex variant. */
|
|
11
|
+
export declare function addHostedWebSearch(body: JsonObject): JsonObject;
|
|
12
|
+
/** Replace the text placeholder written by dsh-compaction-basic with the native item. */
|
|
13
|
+
export declare function replaceRemoteCompactions(body: JsonObject): JsonObject;
|
|
14
|
+
/** Enable the transport patch for this Codex plugin scope only. */
|
|
15
|
+
export declare function installHostedWebSearch(): () => void;
|
|
16
|
+
/** Iterate an existing DSH stream with the hosted-request context installed. */
|
|
17
|
+
export declare function hostedWebSearchStream(next: () => AsyncIterable<StreamChunk>): AsyncIterable<StreamChunk>;
|
|
18
|
+
/** Remote-first compaction waterfall with the existing DSH path as fallback. */
|
|
19
|
+
export declare function remoteCompactStream(ctx: Context, options: GenerateOptions, next: () => AsyncIterable<StreamChunk>): AsyncIterable<StreamChunk>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=remote.d.ts.map
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
+
const SETTINGS_NAMESPACE = 'llm-pi-ai';
|
|
4
|
+
const REMOTE_COMPACTION_OPEN = '<codex-remote-compaction>';
|
|
5
|
+
const REMOTE_COMPACTION_CLOSE = '</codex-remote-compaction>';
|
|
6
|
+
const HOSTED_REQUESTS = new AsyncLocalStorage();
|
|
7
|
+
let hostedPatchUsers = 0;
|
|
8
|
+
let hostedPatchRestore;
|
|
9
|
+
function isGptModel(model) {
|
|
10
|
+
return typeof model === 'string' && /(?:^|\/)(?:gpt|chatgpt)(?:[-_.]|\d|$)/i.test(model);
|
|
11
|
+
}
|
|
12
|
+
function settingsOf(ctx) {
|
|
13
|
+
const provider = ctx.get('settings');
|
|
14
|
+
return provider?.get?.(SETTINGS_NAMESPACE);
|
|
15
|
+
}
|
|
16
|
+
function profileOf(ctx, provider) {
|
|
17
|
+
return settingsOf(ctx)?.providers?.[provider];
|
|
18
|
+
}
|
|
19
|
+
function supportsResponses(profile, provider) {
|
|
20
|
+
return profile?.api === 'openai-responses'
|
|
21
|
+
|| profile?.api === 'openai-codex-responses'
|
|
22
|
+
|| (profile?.api === undefined && provider === 'openai');
|
|
23
|
+
}
|
|
24
|
+
function responsesEndpoint(baseURL, suffix) {
|
|
25
|
+
return `${baseURL.replace(/\/+$/, '')}/${suffix}`;
|
|
26
|
+
}
|
|
27
|
+
function hasHeader(headers, name) {
|
|
28
|
+
return Object.keys(headers ?? {}).some(key => key.toLowerCase() === name.toLowerCase());
|
|
29
|
+
}
|
|
30
|
+
async function apiHeaders(ctx, profile) {
|
|
31
|
+
const headers = { ...profile.headers, 'content-type': 'application/json' };
|
|
32
|
+
if (profile.apiKeyEnv !== undefined && !hasHeader(headers, 'authorization')) {
|
|
33
|
+
const credentials = ctx.get('credentials');
|
|
34
|
+
const resolved = await credentials?.resolve(credentialRef(profile.apiKeyEnv));
|
|
35
|
+
if (resolved?.value === undefined)
|
|
36
|
+
return undefined;
|
|
37
|
+
headers.authorization = `Bearer ${resolved.value}`;
|
|
38
|
+
}
|
|
39
|
+
return headers;
|
|
40
|
+
}
|
|
41
|
+
function isResponsesRequest(url) {
|
|
42
|
+
try {
|
|
43
|
+
const path = new URL(url).pathname.replace(/\/+$/, '');
|
|
44
|
+
return path.endsWith('/responses');
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return url.replace(/\/+$/, '').endsWith('/responses');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function isWebSearchTool(tool) {
|
|
51
|
+
if (typeof tool !== 'object' || tool === null)
|
|
52
|
+
return false;
|
|
53
|
+
const value = tool;
|
|
54
|
+
if (value.name === 'web_search')
|
|
55
|
+
return true;
|
|
56
|
+
const fn = value.function;
|
|
57
|
+
return typeof fn === 'object' && fn !== null && fn.name === 'web_search';
|
|
58
|
+
}
|
|
59
|
+
function hasWebSearchTool(body) {
|
|
60
|
+
return Array.isArray(body.tools) && body.tools.some(isWebSearchTool);
|
|
61
|
+
}
|
|
62
|
+
/** Convert a generic DSH Responses tool list to the hosted Codex variant. */
|
|
63
|
+
export function addHostedWebSearch(body) {
|
|
64
|
+
const tools = Array.isArray(body.tools) ? body.tools.filter(tool => !isWebSearchTool(tool)) : [];
|
|
65
|
+
tools.push({ type: 'web_search', external_web_access: true });
|
|
66
|
+
return { ...replaceRemoteCompactions(body), tools };
|
|
67
|
+
}
|
|
68
|
+
/** Replace the text placeholder written by dsh-compaction-basic with the native item. */
|
|
69
|
+
export function replaceRemoteCompactions(body) {
|
|
70
|
+
if (!Array.isArray(body.input))
|
|
71
|
+
return body;
|
|
72
|
+
const input = body.input.map(item => {
|
|
73
|
+
if (typeof item !== 'object' || item === null)
|
|
74
|
+
return item;
|
|
75
|
+
const value = item;
|
|
76
|
+
const content = Array.isArray(value.content) ? value.content : [];
|
|
77
|
+
const marker = content.find(part => (typeof part === 'object'
|
|
78
|
+
&& part !== null
|
|
79
|
+
&& typeof part.text === 'string'
|
|
80
|
+
&& String(part.text).includes(REMOTE_COMPACTION_OPEN)));
|
|
81
|
+
if (marker === undefined)
|
|
82
|
+
return item;
|
|
83
|
+
const text = String(marker.text);
|
|
84
|
+
const start = text.indexOf(REMOTE_COMPACTION_OPEN) + REMOTE_COMPACTION_OPEN.length;
|
|
85
|
+
const end = text.indexOf(REMOTE_COMPACTION_CLOSE, start);
|
|
86
|
+
if (end < start)
|
|
87
|
+
return item;
|
|
88
|
+
return {
|
|
89
|
+
type: 'compaction',
|
|
90
|
+
encrypted_content: text.slice(start, end),
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
return { ...body, input };
|
|
94
|
+
}
|
|
95
|
+
function hasRemoteCompaction(body) {
|
|
96
|
+
if (!Array.isArray(body.input))
|
|
97
|
+
return false;
|
|
98
|
+
return body.input.some(item => {
|
|
99
|
+
if (typeof item !== 'object' || item === null)
|
|
100
|
+
return false;
|
|
101
|
+
const content = item.content;
|
|
102
|
+
return Array.isArray(content) && content.some(part => (typeof part === 'object'
|
|
103
|
+
&& part !== null
|
|
104
|
+
&& typeof part.text === 'string'
|
|
105
|
+
&& String(part.text).includes(REMOTE_COMPACTION_OPEN)));
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function canPatchBody(body) {
|
|
109
|
+
return isGptModel(body.model)
|
|
110
|
+
&& (hasWebSearchTool(body) || hasRemoteCompaction(body));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Install a scoped fetch shim for pi-ai's already-built Responses request.
|
|
114
|
+
* The generic DSH adapter remains the owner of auth, streaming, replay, and
|
|
115
|
+
* attachments; this shim changes only the hosted-tool portion of the wire body.
|
|
116
|
+
*/
|
|
117
|
+
function installGlobalHostedWebSearchPatch() {
|
|
118
|
+
const original = globalThis.fetch;
|
|
119
|
+
const patched = async (input, init) => {
|
|
120
|
+
if (!HOSTED_REQUESTS.getStore())
|
|
121
|
+
return original(input, init);
|
|
122
|
+
const request = new Request(input, init);
|
|
123
|
+
if (!isResponsesRequest(request.url))
|
|
124
|
+
return original(input, init);
|
|
125
|
+
let body;
|
|
126
|
+
try {
|
|
127
|
+
body = JSON.parse(await request.clone().text());
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return original(input, init);
|
|
131
|
+
}
|
|
132
|
+
if (!canPatchBody(body))
|
|
133
|
+
return original(input, init);
|
|
134
|
+
const fallbackRequest = request.clone();
|
|
135
|
+
const headers = new Headers(request.headers);
|
|
136
|
+
headers.delete('content-length');
|
|
137
|
+
const hostedRequest = new Request(request, {
|
|
138
|
+
body: JSON.stringify(hasWebSearchTool(body) ? addHostedWebSearch(body) : replaceRemoteCompactions(body)),
|
|
139
|
+
headers,
|
|
140
|
+
});
|
|
141
|
+
try {
|
|
142
|
+
const hostedResponse = await original(hostedRequest);
|
|
143
|
+
// A rejected hosted-tool request is retried with the untouched request so
|
|
144
|
+
// dsh-tool-web can still produce the normal local function-tool path.
|
|
145
|
+
if (!hostedResponse.ok)
|
|
146
|
+
return original(fallbackRequest);
|
|
147
|
+
return hostedResponse;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Network and transport failures must have the same fallback behavior as
|
|
151
|
+
// an HTTP rejection; the local function-tool path remains available.
|
|
152
|
+
return original(fallbackRequest);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
globalThis.fetch = patched;
|
|
156
|
+
return () => {
|
|
157
|
+
if (globalThis.fetch === patched)
|
|
158
|
+
globalThis.fetch = original;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** Enable the transport patch for this Codex plugin scope only. */
|
|
162
|
+
export function installHostedWebSearch() {
|
|
163
|
+
hostedPatchUsers += 1;
|
|
164
|
+
hostedPatchRestore ??= installGlobalHostedWebSearchPatch();
|
|
165
|
+
return () => {
|
|
166
|
+
hostedPatchUsers = Math.max(0, hostedPatchUsers - 1);
|
|
167
|
+
if (hostedPatchUsers === 0) {
|
|
168
|
+
hostedPatchRestore?.();
|
|
169
|
+
hostedPatchRestore = undefined;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** Iterate an existing DSH stream with the hosted-request context installed. */
|
|
174
|
+
export function hostedWebSearchStream(next) {
|
|
175
|
+
return (async function* () {
|
|
176
|
+
const iterator = next()[Symbol.asyncIterator]();
|
|
177
|
+
let completed = false;
|
|
178
|
+
try {
|
|
179
|
+
while (true) {
|
|
180
|
+
const item = await HOSTED_REQUESTS.run(true, () => iterator.next());
|
|
181
|
+
if (item.done) {
|
|
182
|
+
completed = true;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
yield item.value;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
if (!completed)
|
|
190
|
+
await iterator.return?.();
|
|
191
|
+
}
|
|
192
|
+
})();
|
|
193
|
+
}
|
|
194
|
+
function textOf(message) {
|
|
195
|
+
return message.content
|
|
196
|
+
.filter(block => block.type === 'text')
|
|
197
|
+
.map(block => block.text)
|
|
198
|
+
.join('');
|
|
199
|
+
}
|
|
200
|
+
function responsesInput(messages) {
|
|
201
|
+
const result = [];
|
|
202
|
+
for (const message of messages) {
|
|
203
|
+
const text = textOf(message);
|
|
204
|
+
if (message.role === 'assistant') {
|
|
205
|
+
if (text.length > 0)
|
|
206
|
+
result.push({
|
|
207
|
+
type: 'message',
|
|
208
|
+
role: 'assistant',
|
|
209
|
+
content: [{ type: 'output_text', text }],
|
|
210
|
+
});
|
|
211
|
+
for (const block of message.content) {
|
|
212
|
+
if (block.type === 'tool-call') {
|
|
213
|
+
result.push({
|
|
214
|
+
type: 'function_call',
|
|
215
|
+
call_id: block.id,
|
|
216
|
+
name: block.name,
|
|
217
|
+
arguments: block.arguments,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const role = message.role === 'system' ? 'developer' : 'user';
|
|
224
|
+
if (text.length > 0)
|
|
225
|
+
result.push({
|
|
226
|
+
type: 'message',
|
|
227
|
+
role,
|
|
228
|
+
content: [{ type: 'input_text', text }],
|
|
229
|
+
});
|
|
230
|
+
for (const block of message.content) {
|
|
231
|
+
if (block.type !== 'tool-result')
|
|
232
|
+
continue;
|
|
233
|
+
result.push({
|
|
234
|
+
type: 'function_call_output',
|
|
235
|
+
call_id: block.toolCallId,
|
|
236
|
+
output: block.content.filter(item => item.type === 'text').map(item => item.text).join(''),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
function responsesTools(tools) {
|
|
243
|
+
if (tools === undefined)
|
|
244
|
+
return undefined;
|
|
245
|
+
return tools.filter(tool => tool.name !== 'web_search').map(tool => ({
|
|
246
|
+
type: 'function',
|
|
247
|
+
name: tool.name,
|
|
248
|
+
description: tool.description,
|
|
249
|
+
parameters: tool.parameters,
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
function compactBody(options) {
|
|
253
|
+
const tools = responsesTools(options.tools);
|
|
254
|
+
const body = {
|
|
255
|
+
model: options.model,
|
|
256
|
+
// dsh-compaction-basic appends an instruction for its local summarizer.
|
|
257
|
+
// The hosted compact endpoint owns summarization and must not receive it.
|
|
258
|
+
input: responsesInput(options.messages.slice(0, -1)),
|
|
259
|
+
...options.system === undefined ? {} : { instructions: options.system },
|
|
260
|
+
...tools === undefined ? {} : { tools },
|
|
261
|
+
...options.maxTokens === undefined ? {} : { max_output_tokens: options.maxTokens },
|
|
262
|
+
...options.reasoningEffort === undefined ? {} : { reasoning: { effort: options.reasoningEffort } },
|
|
263
|
+
};
|
|
264
|
+
const compacted = replaceRemoteCompactions(body);
|
|
265
|
+
return options.tools?.some(tool => tool.name === 'web_search') ? addHostedWebSearch(compacted) : compacted;
|
|
266
|
+
}
|
|
267
|
+
function compactText(body) {
|
|
268
|
+
const output = Array.isArray(body.output) ? body.output : [];
|
|
269
|
+
const text = [];
|
|
270
|
+
for (const item of output) {
|
|
271
|
+
if (typeof item !== 'object' || item === null)
|
|
272
|
+
continue;
|
|
273
|
+
const value = item;
|
|
274
|
+
if (typeof value.encrypted_content === 'string')
|
|
275
|
+
text.push(value.encrypted_content);
|
|
276
|
+
}
|
|
277
|
+
const encrypted = text.join('\n\n').trim();
|
|
278
|
+
return encrypted.length === 0 ? '' : `${REMOTE_COMPACTION_OPEN}${encrypted}${REMOTE_COMPACTION_CLOSE}`;
|
|
279
|
+
}
|
|
280
|
+
async function remoteCompact(ctx, options) {
|
|
281
|
+
const profile = profileOf(ctx, options.provider);
|
|
282
|
+
if (!supportsResponses(profile, options.provider) || profile?.baseURL === undefined) {
|
|
283
|
+
throw new Error('Codex remote compaction requires an OpenAI Responses provider with baseURL');
|
|
284
|
+
}
|
|
285
|
+
const headers = await apiHeaders(ctx, profile);
|
|
286
|
+
if (headers === undefined)
|
|
287
|
+
throw new Error('Codex remote compaction has no configured API key');
|
|
288
|
+
const response = await fetch(responsesEndpoint(profile.baseURL, 'responses/compact'), {
|
|
289
|
+
method: 'POST',
|
|
290
|
+
headers,
|
|
291
|
+
body: JSON.stringify(compactBody(options)),
|
|
292
|
+
...options.signal === undefined ? {} : { signal: options.signal },
|
|
293
|
+
});
|
|
294
|
+
if (!response.ok)
|
|
295
|
+
throw new Error(`remote compaction returned HTTP ${response.status}`);
|
|
296
|
+
const body = await response.json();
|
|
297
|
+
const text = compactText(body);
|
|
298
|
+
if (text.length === 0)
|
|
299
|
+
throw new Error('remote compaction returned no compaction text');
|
|
300
|
+
return text;
|
|
301
|
+
}
|
|
302
|
+
/** Remote-first compaction waterfall with the existing DSH path as fallback. */
|
|
303
|
+
export function remoteCompactStream(ctx, options, next) {
|
|
304
|
+
return (async function* () {
|
|
305
|
+
try {
|
|
306
|
+
const text = await remoteCompact(ctx, options);
|
|
307
|
+
yield { type: 'block-start', index: 0, blockType: 'text' };
|
|
308
|
+
yield { type: 'text-delta', index: 0, text };
|
|
309
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text } };
|
|
310
|
+
yield { type: 'usage', usage: { inputTokens: 0, outputTokens: 0 } };
|
|
311
|
+
yield { type: 'finish', reason: { kind: 'stop' } };
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
if (options.signal?.aborted)
|
|
315
|
+
throw error;
|
|
316
|
+
ctx.logger.warn('codex: remote compaction failed; using dsh-compaction-basic fallback');
|
|
317
|
+
ctx.logger.warn(error);
|
|
318
|
+
// The local fallback still replays the marker through a later GPT
|
|
319
|
+
// Responses request, so it needs the same scoped wire context.
|
|
320
|
+
yield* hostedWebSearchStream(next);
|
|
321
|
+
}
|
|
322
|
+
})();
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=remote.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shuind/dsh-codex-harness",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "A minimal Codex harness for GPT models that do not fit DSH's native interface, while remaining compatible with the DSH plugin ecosystem.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -67,41 +67,85 @@
|
|
|
67
67
|
"peerDependencies": {
|
|
68
68
|
"@deepseek-ai/cordis": ">=4.0.1",
|
|
69
69
|
"@deepseek-ai/dsh-agent": ">=0.1.0-rc.6",
|
|
70
|
+
"@deepseek-ai/dsh-credentials": ">=0.1.0-rc.8",
|
|
70
71
|
"@deepseek-ai/dsh-fs": ">=0.0.1-rc.1",
|
|
71
72
|
"@deepseek-ai/dsh-invariants": ">=0.0.1-rc.1",
|
|
73
|
+
"@deepseek-ai/dsh-llm": ">=0.1.0-rc.8",
|
|
74
|
+
"@deepseek-ai/dsh-settings": ">=0.1.0-rc.8",
|
|
72
75
|
"@deepseek-ai/dsh-sandbox": ">=0.0.1-rc.1",
|
|
73
76
|
"@deepseek-ai/dsh-sandbox-policy": ">=0.0.1-rc.1",
|
|
74
77
|
"@deepseek-ai/dsh-shell": ">=0.0.1-rc.5",
|
|
75
78
|
"@deepseek-ai/dsh-shell-env": ">=0.0.1-rc.3",
|
|
76
79
|
"@deepseek-ai/dsh-system-prompt": ">=0.0.1-rc.1",
|
|
80
|
+
"@deepseek-ai/dsh-tool-web": ">=0.1.0-rc.8",
|
|
77
81
|
"@deepseek-ai/dsh-tool-todo": ">=0.0.1-rc.1",
|
|
78
82
|
"@deepseek-ai/dsh-terminal": ">=0.0.1-rc.3",
|
|
79
83
|
"@deepseek-ai/dsh-tools": ">=0.0.1-rc.1"
|
|
80
84
|
},
|
|
81
85
|
"peerDependenciesMeta": {
|
|
82
|
-
"@deepseek-ai/cordis": {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"@deepseek-ai/dsh-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
"@deepseek-ai/dsh-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"@deepseek-ai/dsh-
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
"@deepseek-ai/cordis": {
|
|
87
|
+
"optional": true
|
|
88
|
+
},
|
|
89
|
+
"@deepseek-ai/dsh-agent": {
|
|
90
|
+
"optional": true
|
|
91
|
+
},
|
|
92
|
+
"@deepseek-ai/dsh-credentials": {
|
|
93
|
+
"optional": true
|
|
94
|
+
},
|
|
95
|
+
"@deepseek-ai/dsh-fs": {
|
|
96
|
+
"optional": true
|
|
97
|
+
},
|
|
98
|
+
"@deepseek-ai/dsh-invariants": {
|
|
99
|
+
"optional": true
|
|
100
|
+
},
|
|
101
|
+
"@deepseek-ai/dsh-llm": {
|
|
102
|
+
"optional": true
|
|
103
|
+
},
|
|
104
|
+
"@deepseek-ai/dsh-settings": {
|
|
105
|
+
"optional": true
|
|
106
|
+
},
|
|
107
|
+
"@deepseek-ai/dsh-sandbox": {
|
|
108
|
+
"optional": true
|
|
109
|
+
},
|
|
110
|
+
"@deepseek-ai/dsh-sandbox-policy": {
|
|
111
|
+
"optional": true
|
|
112
|
+
},
|
|
113
|
+
"@deepseek-ai/dsh-shell": {
|
|
114
|
+
"optional": true
|
|
115
|
+
},
|
|
116
|
+
"@deepseek-ai/dsh-shell-env": {
|
|
117
|
+
"optional": true
|
|
118
|
+
},
|
|
119
|
+
"@deepseek-ai/dsh-system-prompt": {
|
|
120
|
+
"optional": true
|
|
121
|
+
},
|
|
122
|
+
"@deepseek-ai/dsh-tool-web": {
|
|
123
|
+
"optional": true
|
|
124
|
+
},
|
|
125
|
+
"@deepseek-ai/dsh-tool-todo": {
|
|
126
|
+
"optional": true
|
|
127
|
+
},
|
|
128
|
+
"@deepseek-ai/dsh-terminal": {
|
|
129
|
+
"optional": true
|
|
130
|
+
},
|
|
131
|
+
"@deepseek-ai/dsh-tools": {
|
|
132
|
+
"optional": true
|
|
133
|
+
}
|
|
94
134
|
},
|
|
95
135
|
"devDependencies": {
|
|
96
136
|
"@deepseek-ai/cordis": "4.0.1",
|
|
97
137
|
"@deepseek-ai/dsh-agent": "0.1.0-rc.6",
|
|
138
|
+
"@deepseek-ai/dsh-credentials": "0.1.0-rc.8",
|
|
98
139
|
"@deepseek-ai/dsh-fs": "0.1.0-rc.6",
|
|
99
140
|
"@deepseek-ai/dsh-invariants": "0.1.0-rc.6",
|
|
141
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.8",
|
|
142
|
+
"@deepseek-ai/dsh-settings": "0.1.0-rc.8",
|
|
100
143
|
"@deepseek-ai/dsh-sandbox": "0.1.0-rc.6",
|
|
101
144
|
"@deepseek-ai/dsh-sandbox-policy": "0.1.0-rc.6",
|
|
102
145
|
"@deepseek-ai/dsh-shell": "0.1.0-rc.6",
|
|
103
146
|
"@deepseek-ai/dsh-shell-env": "0.1.0-rc.6",
|
|
104
147
|
"@deepseek-ai/dsh-system-prompt": "0.1.0-rc.6",
|
|
148
|
+
"@deepseek-ai/dsh-tool-web": "0.1.0-rc.8",
|
|
105
149
|
"@deepseek-ai/dsh-tool-todo": "0.1.0-rc.6",
|
|
106
150
|
"@deepseek-ai/dsh-terminal": "0.1.0-rc.6",
|
|
107
151
|
"@deepseek-ai/dsh-tools": "0.1.0-rc.6",
|
|
@@ -18,6 +18,19 @@
|
|
|
18
18
|
- id: codex-tools
|
|
19
19
|
name: '@shuind/dsh-codex-harness'
|
|
20
20
|
|
|
21
|
+
# The generic DSH pi-ai adapter still owns the user's configured provider,
|
|
22
|
+
# endpoint, API key, and model. Codex's remote-first transport wrapper is
|
|
23
|
+
# installed only in this Codex scope and falls back to the generic DSH path.
|
|
24
|
+
|
|
25
|
+
# Keep the local tool mounted as the fallback. Codex rewrites the final GPT
|
|
26
|
+
# Responses request to a hosted web_search tool first; a failed remote request
|
|
27
|
+
# is retried through this unchanged local function-tool path.
|
|
28
|
+
- id: codex-web-search
|
|
29
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
30
|
+
config:
|
|
31
|
+
search: true
|
|
32
|
+
fetch: false
|
|
33
|
+
|
|
21
34
|
# PTY execution is optional. Pipe-backed exec_command remains available on all
|
|
22
35
|
# platforms through the shell capability consumed by the Codex tool layer.
|
|
23
36
|
- id: codex-terminal
|
|
@@ -35,6 +48,29 @@
|
|
|
35
48
|
config:
|
|
36
49
|
timeoutMs: 300000
|
|
37
50
|
|
|
51
|
+
# Context compaction is agent-scoped in Web. The token meter stays on the host
|
|
52
|
+
# plane, while this group owns the backend, `/compact`, and optional tool-result
|
|
53
|
+
# pruning for Codex sessions.
|
|
54
|
+
- id: compaction
|
|
55
|
+
name: cordis:group
|
|
56
|
+
group: true
|
|
57
|
+
isolate:
|
|
58
|
+
compaction: true
|
|
59
|
+
toolResultPruner: true
|
|
60
|
+
config:
|
|
61
|
+
- id: compaction-basic
|
|
62
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
63
|
+
|
|
64
|
+
- id: command-compact
|
|
65
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
66
|
+
|
|
67
|
+
- id: tool-result-pruner
|
|
68
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
69
|
+
config:
|
|
70
|
+
thresholdChars: 8192
|
|
71
|
+
headChars: 4096
|
|
72
|
+
tailChars: 1024
|
|
73
|
+
|
|
38
74
|
# Skills are the dsh extensibility seam and do not change the four Codex core
|
|
39
75
|
# tool names.
|
|
40
76
|
- id: skill-filesystem
|
package/presets/codex/preset.yml
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
name: Codex 模式
|
|
2
|
-
description: 使用 Codex 的基础提示词、exec_command、write_stdin、apply_patch 和 update_plan,并保留 dsh 的 Skills 与可插拔运行时。
|
|
3
|
-
order: 5
|
|
1
|
+
name: Codex 模式
|
|
2
|
+
description: 使用 Codex 的基础提示词、exec_command、write_stdin、apply_patch 和 update_plan,并保留 dsh 的 Skills 与可插拔运行时。
|
|
3
|
+
order: 5
|