dsh-packycode-api 1.1.8 → 1.1.9
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/CHANGELOG.md +9 -0
- package/README.en.md +15 -4
- package/README.md +12 -5
- package/lib/client.js +1 -0
- package/lib/index.js +156 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.9
|
|
4
|
+
|
|
5
|
+
- Added `gemini-4-argon` (**Gemini 4 Argon**) and `gemini-3.8-flash` (**Gemini 3.8 Flash**), both using `google-generative-ai` at `https://cf.api.fan/v1beta`. PackyCode's [live catalog](https://www.packyapi.com/api/pricing) and its support reply declare only the native `gemini` endpoint for Argon; Flash supports it too, keeping the Gemini family on one native route. Requests use streaming `streamGenerateContent`, with `thinkingLevel: LOW / MEDIUM / HIGH`, text/image input and native tool/signature replay.
|
|
6
|
+
- A compatibility wrapper removes the unsupported `thinkingBudget` emitted for Argon by older pi-ai hosts and defaults both Gemini models to `MEDIUM` when no effort is supplied. The DSH profile continues to select `high` by default. Both models declare a 1,048,576-token context/input window and 65,536-token single-stream output limit, without automatic long-decode continuation. See the [Argon](https://ai.google.dev/gemini-api/docs/models/gemini-4-argon) and [Flash](https://ai.google.dev/gemini-api/docs/models/gemini-3.8-flash) model pages.
|
|
7
|
+
- Both models appear by default in the picker and settings coverage summary. Live checks include them; `--model MODEL [--with-vision]` also allows a bounded check of one model through the built host route. Credentials still come only from the plugin's stored key ring.
|
|
8
|
+
- Flash tool continuations now resend once after one second when the gateway returns HTTP 400 `Corrupted thought signature` / `Invalid thought signature` before any stream event. Fixed-response experiments showed the same signed request being refused and later accepted; three fresh native roundtrips passed with at most one resend. The guard requires a current signed same-model call and matching result, preserves the model/key/signature/request, and does not retry after output, on unrelated failures, or for Argon. Cancellation stops the wait, and a second refusal remains unchanged for the host's normal failure/retry handling.
|
|
9
|
+
- Added `scripts/gemini-tool-check.mjs`, which exercises the built route, DSH's message assembler and JSON-persisted replay state, checking HTTP resend counts and request-body equality without printing signatures or credentials.
|
|
10
|
+
- Validation: 321 offline tests passed, both bundles rebuilt and the tarball generated. The built native Flash route passed three fresh `low` tool roundtrips and one `high` roundtrip; Argon passed its tool roundtrip, and Flash text/red/blue-image checks passed. These final live tool runs required no resend; the exact HTTP-400-then-success path is covered by a real SDK/host regression. A separate Flash Completions comparison passed three tool roundtrips. Both Gemini catalog models remain on the requested native protocol; the guard mitigates an intermittent gateway refusal and does not repair its server-side validator.
|
|
11
|
+
|
|
3
12
|
## 1.1.8
|
|
4
13
|
|
|
5
14
|
- **Diagnosed an installation that mounts nothing, and made it reportable.** A profile that once had this plugin installed from a local path keeps a `node_modules` inside the installed package directory; a later registry install rewrites the package's files and leaves that tree in place. DSH resolves a plugin's imports from inside the profile first, so the plugin kept loading the old copies — `@deepseek-ai/schemastery` 3.18.2 has no `Schema.volatile()`, the module threw while it was imported, and the Loader created no fiber for the row. `@deepseek-ai/dsh-client-modules` skips a fiber-less entry, so the browser bundle was never composed and the only symptom was a settings page with no PackyCode section, no model route, no RPC channel, and a clean host log. The diagnosis was confirmed on the affected desktop profile: the composed entry `{"id":"packycode-api","name":"dsh-packycode-api"}` is present, the 67-row boot graph carries no bundle for it, `POST /packycode-api/status` is unregistered, and importing the installed package from a copy reproduces `TypeError: schema.volatile is not a function`.
|
package/README.en.md
CHANGED
|
@@ -33,6 +33,11 @@ The `PackyCode` model group ships with the plugin. No YAML edits and no separate
|
|
|
33
33
|
| **Web search through PackyCode** | Optional: DSH's `web_search` searches through PackyCode, with a DeepSeek or GPT backend, for every request or for PackyCode models only |
|
|
34
34
|
| **Credentials stay on the machine** | Keys live in the DSH credential file. The browser receives labels, state, and balances only |
|
|
35
35
|
|
|
36
|
+
<img width="774" height="455" alt="截屏2026-10-15 10 34 28" src="https://github.com/user-attachments/assets/c5eaa535-dd1e-4e2f-be9f-c181404d4dd6" />
|
|
37
|
+
<img width="575" height="522" alt="截屏2026-10-15 10 34 49" src="https://github.com/user-attachments/assets/3cf4d090-a4f9-49fe-9ab3-3aab21d6e909" />
|
|
38
|
+
<img width="257" height="309" alt="截屏2026-10-15 10 37 21" src="https://github.com/user-attachments/assets/621cd800-17a2-42f6-8dfe-c1b4be09af61" />
|
|
39
|
+
|
|
40
|
+
|
|
36
41
|
## Install
|
|
37
42
|
|
|
38
43
|
You need **DeepSeek Harness** and a **PackyCode (PackyAPI) API key**. For DSH setup, see the [official instructions](https://github.com/deepseek-ai/deepseek-harness#run).
|
|
@@ -45,7 +50,7 @@ Open the desktop application's **Plugins** page, click **Add plugin**, and enter
|
|
|
45
50
|
dsh-packycode-api
|
|
46
51
|
```
|
|
47
52
|
|
|
48
|
-
Choose the **official** install source, then click **Install**. To pin a version, enter `dsh-packycode-api@1.1.
|
|
53
|
+
Choose the **official** install source, then click **Install**. To pin a version, enter `dsh-packycode-api@1.1.9`.
|
|
49
54
|
|
|
50
55
|
A **first install requires no DSH restart**: open **Settings → PackyCode** and add a key immediately. When updating an already loaded older version, follow the restart guidance in the **Plugin version** card.
|
|
51
56
|
|
|
@@ -57,14 +62,14 @@ Open **Settings → PackyCode**. Paste the API key under **Add a key**. A label
|
|
|
57
62
|
|
|
58
63
|
### 3. Select a model
|
|
59
64
|
|
|
60
|
-
Choose a model from the `PackyCode` group. `deepseek-flash`, `gpt-6.1-sol`, `gpt-6-astra`,
|
|
65
|
+
Choose a model from the `PackyCode` group. `deepseek-flash`, `gpt-6.1-sol`, `gpt-6-astra`, `grok-4.7`, `gemini-4-argon`, and `gemini-3.8-flash` use HTTP. The three Claude models also need the Claude Code CLI on this device; see below. Your key's groups must cover the selected model.
|
|
61
66
|
|
|
62
67
|
<details>
|
|
63
68
|
<summary>Verify the install / other profiles / a local checkout</summary>
|
|
64
69
|
|
|
65
70
|
The **Plugins** page shows the installed state and version, and is also where you enable, disable, or uninstall.
|
|
66
71
|
|
|
67
|
-
**For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.
|
|
72
|
+
**For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.9`. Do not use it for `desktop` — the CLI refuses it (`profile "desktop" is managed exclusively by the Electron application`).
|
|
68
73
|
|
|
69
74
|
For a local checkout, keep the `link:` prefix: `dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`. **Do not pass a bare path or `file:`** — that install copies the whole checkout, `node_modules` included, into the profile, where it then shadows the packages the DSH installation itself provides and the plugin looks uninstalled; see the FAQ. If you already installed that way, uninstall first and reinstall as above.
|
|
70
75
|
|
|
@@ -142,13 +147,19 @@ Claude Code CLI → temporary local MCP web_search → selected PackyCode DeepSe
|
|
|
142
147
|
| `gpt-6.1-sol` | HTTP, Responses, images allowed | Cannot be off; `low` / `medium` / `high` / `xhigh` / `max` | `codex`, `codex-sale` |
|
|
143
148
|
| `gpt-6-astra` | Same | Same | Same |
|
|
144
149
|
| `grok-4.7` (Grok 4.7) | HTTP, Responses, text/image input; 500K context | Cannot be off; `low` / `medium` / `high` / `xhigh` | The Grok coverage read from the gateway in Settings |
|
|
150
|
+
| `gemini-4-argon` (Gemini 4 Argon) | HTTP, native Gemini streaming, text/image input; 1,048,576 context, 65,536 output | Cannot be off; `low` / `medium` / `high` | `gemini-slb` |
|
|
151
|
+
| `gemini-3.8-flash` (Gemini 3.8 Flash) | HTTP, native Gemini streaming, text/image input; 1,048,576 input, 65,536 output | Cannot be off; `low` / `medium` / `high` | `gemini-slb` |
|
|
145
152
|
| `claude-opus-5-5` | Local Claude Code CLI | The picker offers the same levels; the CLI chooses the actual effort | A group the official CLI is allowed to use; see below |
|
|
146
153
|
| `claude-sonnet-5-5` | Same | Same | Same |
|
|
147
154
|
| `claude-haiku-5-5` | Same; session titles always use this model | Same | Same |
|
|
148
155
|
|
|
149
156
|
A model in the list does not mean the current token group allows it. When changing groups, do not drop the group that serves the model you still want.
|
|
150
157
|
|
|
151
|
-
All
|
|
158
|
+
All nine models in the current catalog declare text and image input. DeepSeek Flash follows the [official vision guide](https://api-docs.deepseek.com/guides/vision/); images are read through DSH's attachment service and sent with the Chat Completions request.
|
|
159
|
+
|
|
160
|
+
Gemini capabilities and limits follow Google's model pages: [Gemini 4 Argon](https://ai.google.dev/gemini-api/docs/models/gemini-4-argon), [Gemini 3.8 Flash](https://ai.google.dev/gemini-api/docs/models/gemini-3.8-flash). Both models use the native Gemini protocol through `/v1beta/models/{model}:streamGenerateContent`, streaming text, thinking and tool calls; images are read through DSH's attachment service. Thinking levels are sent with `thinkingLevel`. Both routes use the single-stream output limit without automatic continuation beyond 65,536 tokens. Available endpoints and groups follow the [PackyAPI live model catalog](https://www.packyapi.com/api/pricing).
|
|
161
|
+
|
|
162
|
+
**Flash native tool continuation:** The gateway sometimes refuses an unchanged valid signature with `Corrupted thought signature` / `Invalid thought signature`. For a current signed tool continuation returning HTTP 400 before any stream event, the plugin waits one second and resends once with the same key, signature and request. A second failure is returned unchanged; the protocol and model stay the same. Text, images and tool continuation passed live checks. This mitigates intermittent gateway refusals, rather than repairing persistent upstream signature errors.
|
|
152
163
|
|
|
153
164
|
Grok 4.7's context and reasoning levels follow the [official guide](https://docs.x.ai/developers/grok-4-7). There is no separate text-output limit; the plugin uses the context size as its numeric output ceiling, bounded by the remaining context per request.
|
|
154
165
|
|
package/README.md
CHANGED
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
| **网页搜索可走 PackyCode** | 可选:DSH 的 `web_search` 改用 PackyCode 搜索,后端在 DeepSeek 与 GPT 之间切换;可只对 PackyCode 模型生效 |
|
|
34
34
|
| **凭据不离开本机** | Key 只存在 DSH 凭据文件里。浏览器只能看到备注、状态和余额,拿不到 Key |
|
|
35
35
|
|
|
36
|
-
<img width="
|
|
36
|
+
<img width="774" height="455" alt="截屏2026-10-15 10 34 28" src="https://github.com/user-attachments/assets/060c4192-00ea-40e6-9b85-bbec605b5c31" />
|
|
37
|
+
<img width="575" height="522" alt="截屏2026-10-15 10 34 49" src="https://github.com/user-attachments/assets/89cf89ca-fce3-427d-95cb-94fa2b05d6aa" />
|
|
37
38
|
<img width="272" height="425" alt="截屏2026-10-13 10 07 46" src="https://github.com/user-attachments/assets/040fc57a-2dd2-4195-9bbc-5fa3357d1eb1" />
|
|
38
39
|
|
|
39
40
|
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
dsh-packycode-api
|
|
50
51
|
```
|
|
51
52
|
|
|
52
|
-
在**安装源**中选择**官方源**,然后点击**安装**。需要固定版本时填入 `dsh-packycode-api@1.1.
|
|
53
|
+
在**安装源**中选择**官方源**,然后点击**安装**。需要固定版本时填入 `dsh-packycode-api@1.1.9`。
|
|
53
54
|
|
|
54
55
|
首次安装后**无需重启 DSH**,可直接打开**设置 → PackyCode** 添加 Key。更新已加载的旧版时,按「插件版本」卡片的重启提示操作。
|
|
55
56
|
|
|
@@ -61,14 +62,14 @@ dsh-packycode-api
|
|
|
61
62
|
|
|
62
63
|
### 3. 选择模型
|
|
63
64
|
|
|
64
|
-
回到对话,在模型选择器里选 `PackyCode` 分组下的模型。`deepseek-flash`、`gpt-6.1-sol`、`gpt-6-astra` 和 `
|
|
65
|
+
回到对话,在模型选择器里选 `PackyCode` 分组下的模型。`deepseek-flash`、`gpt-6.1-sol`、`gpt-6-astra`、`grok-4.7`、`gemini-4-argon` 和 `gemini-3.8-flash` 通过 HTTP 调用;三个 Claude 模型还需要本机装有 Claude Code CLI,见下文。Key 的分组必须覆盖所选模型。
|
|
65
66
|
|
|
66
67
|
<details>
|
|
67
68
|
<summary>检查安装 / 其它 profile / 本地目录</summary>
|
|
68
69
|
|
|
69
70
|
桌面应用的**插件**页可以看到已安装状态与版本,也可以在那里启用、停用或卸载。
|
|
70
71
|
|
|
71
|
-
**仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.
|
|
72
|
+
**仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.9`。不要对 `desktop` 使用它 —— CLI 会直接拒绝(`profile "desktop" is managed exclusively by the Electron application`)。
|
|
72
73
|
|
|
73
74
|
本地开发要加 `link:` 前缀:`dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`。**不要直接写路径(或 `file:`)** —— 那样的安装会把整个仓库目录(连同 `node_modules`)复制进 profile,之后它会在 profile 内部挡住 DSH 自带的包,插件看起来像没装,见「常见问题」。已经那样装过的话,先卸载再按上面的方式重装。
|
|
74
75
|
|
|
@@ -146,13 +147,19 @@ Claude Code CLI → 本机临时 MCP web_search → 插件选定的 DeepSeek/GPT
|
|
|
146
147
|
| `gpt-6.1-sol` | HTTP,Responses,可带图 | 不能关;`low` / `medium` / `high` / `xhigh` / `max` | `codex`、`codex-sale` |
|
|
147
148
|
| `gpt-6-astra` | 同上 | 同上 | 同上 |
|
|
148
149
|
| `grok-4.7`(Grok 4.7) | HTTP,Responses,可带图;500k 上下文 | 不能关;`low` / `medium` / `high` / `xhigh` | 以设置页从网关读取的 Grok 分组覆盖为准 |
|
|
150
|
+
| `gemini-4-argon`(Gemini 4 Argon) | HTTP,Gemini 原生流式,可带图;1,048,576 上下文,65,536 输出 | 不能关;`low` / `medium` / `high` | `gemini-slb` |
|
|
151
|
+
| `gemini-3.8-flash`(Gemini 3.8 Flash) | HTTP,Gemini 原生流式,可带图;1,048,576 输入,65,536 输出 | 不能关;`low` / `medium` / `high` | `gemini-slb` |
|
|
149
152
|
| `claude-opus-5-5` | 本机 Claude Code CLI | 选择器档位同上,实际档位由 CLI 决定 | 能给官方 CLI 用的分组,比如cc分组 |
|
|
150
153
|
| `claude-sonnet-5-5` | 同上 | 同上 | 同上 |
|
|
151
154
|
| `claude-haiku-5-5` | 同上;会话标题也固定走它 | 同上 | 同上 |
|
|
152
155
|
|
|
153
156
|
列表里看得到模型,不代表当前令牌分组一定放行。换分组时不要把对应分组一起去掉。
|
|
154
157
|
|
|
155
|
-
当前目录中的
|
|
158
|
+
当前目录中的 9 个模型都声明了文字和图像输入。DeepSeek Flash 的识图能力按 [官方图像理解文档](https://api-docs.deepseek.com/zh-cn/guides/vision/) 配置;图片通过 DSH 附件服务读取,再随 chat/completions 请求发送。
|
|
159
|
+
|
|
160
|
+
Gemini 的能力与限制来自 Google 官方模型页:[Gemini 4 Argon](https://ai.google.dev/gemini-api/docs/models/gemini-4-argon)、[Gemini 3.8 Flash](https://ai.google.dev/gemini-api/docs/models/gemini-3.8-flash)。两款模型统一使用 Gemini 原生协议,通过 `/v1beta/models/{model}:streamGenerateContent` 流式返回文字、思考和工具调用,图片仍由 DSH 附件服务读取。思考档位通过 `thinkingLevel` 传入。两者均使用单次流式输出上限,不执行超过 65,536 token 的自动续写。可用端点与分组以 [PackyAPI 实时模型目录](https://www.packyapi.com/api/pricing) 为准。
|
|
161
|
+
|
|
162
|
+
**Flash 原生工具续聊:** 网关偶尔会拒绝原样回传的有效签名,返回 `Corrupted thought signature` / `Invalid thought signature`。插件仅在当前工具续聊、HTTP 400 且尚未输出任何流事件时,等待 1 秒,用相同 Key、签名及请求原样重发一次;再次失败时仍返回原错误,不更换协议或模型。文字、图片和工具续聊均已实测;该处理缓解网关的间歇性拒绝,不能保证修复持续性的上游签名错误。
|
|
156
163
|
|
|
157
164
|
Grok 4.7 的上下文与推理档位来自 [官方文档](https://docs.x.ai/developers/grok-4-7)。官方没有单独限制文字输出长度;插件将输出上限设为上下文大小,并按每次请求的剩余上下文收紧。
|
|
158
165
|
|
package/lib/client.js
CHANGED
|
@@ -498,6 +498,7 @@ window.__ModuleLoader__.load({
|
|
|
498
498
|
["DeepSeek", ["deepseek-flash"]],
|
|
499
499
|
["GPT", ["gpt-6.1-sol", "gpt-6-astra"]],
|
|
500
500
|
["Grok", ["grok-4.7"]],
|
|
501
|
+
["Gemini", ["gemini-4-argon", "gemini-3.8-flash"]],
|
|
501
502
|
["Claude", [
|
|
502
503
|
"claude-opus-5-5",
|
|
503
504
|
"claude-sonnet-5-5",
|
package/lib/index.js
CHANGED
|
@@ -11,10 +11,13 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
|
11
11
|
import { credentialKey } from "@deepseek-ai/dsh-credentials";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
14
|
-
import {
|
|
14
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
15
|
+
import { lazyStream } from "@earendil-works/pi-ai/api/lazy";
|
|
16
|
+
import { clampThinkingLevel, createProvider } from "@earendil-works/pi-ai";
|
|
15
17
|
import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
|
|
16
18
|
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
17
19
|
import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
|
|
20
|
+
import { googleGenerativeAIApi } from "@earendil-works/pi-ai/api/google-generative-ai.lazy";
|
|
18
21
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
19
22
|
//#region src/request.js
|
|
20
23
|
/**
|
|
@@ -544,8 +547,9 @@ function createBalanceReader({ listKeys, getHosts, fetchImpl = ambientFetch, now
|
|
|
544
547
|
* model does not support is refused before the request is sent: Claude Opus
|
|
545
548
|
* 5.5 (platform.claude.com effort docs), GPT-6.1 Sol and GPT-6 Astra
|
|
546
549
|
* `reasoning.effort` (developers.openai.com model pages), DeepSeek thinking
|
|
547
|
-
* mode (`thinking` toggle plus `low|high|max` effort),
|
|
548
|
-
* (docs.x.ai/developers/grok-4-7)
|
|
550
|
+
* mode (`thinking` toggle plus `low|high|max` effort), Grok 4.7
|
|
551
|
+
* (docs.x.ai/developers/grok-4-7), and Gemini 4 Argon
|
|
552
|
+
* (ai.google.dev/gemini-api/docs/models/gemini-4-argon and docs/openai).
|
|
549
553
|
*
|
|
550
554
|
* @module dsh-packycode-api/catalog
|
|
551
555
|
*/
|
|
@@ -559,6 +563,7 @@ const NO_COST = Object.freeze({
|
|
|
559
563
|
const COMPLETIONS_BASE = `${DEFAULT_API_HOST}/v1`;
|
|
560
564
|
const RESPONSES_BASE = `${DEFAULT_API_HOST}/v1`;
|
|
561
565
|
const MESSAGES_BASE = DEFAULT_API_HOST;
|
|
566
|
+
const GEMINI_BASE = `${DEFAULT_API_HOST}/v1beta`;
|
|
562
567
|
/**
|
|
563
568
|
* pi-ai thinking-level maps. A level pinned to `null` is unsupported; an
|
|
564
569
|
* absent key keeps pi-ai's own default for that level, which is how `off`
|
|
@@ -592,6 +597,12 @@ const GROK_LEVELS = Object.freeze({
|
|
|
592
597
|
...ALWAYS_THINKING_LEVELS,
|
|
593
598
|
max: null
|
|
594
599
|
});
|
|
600
|
+
/** Gemini 4 Argon and 3.8 Flash support low | medium | high, with no off. */
|
|
601
|
+
const GEMINI_LEVELS = Object.freeze({
|
|
602
|
+
...ALWAYS_THINKING_LEVELS,
|
|
603
|
+
xhigh: null,
|
|
604
|
+
max: null
|
|
605
|
+
});
|
|
595
606
|
const PACKYCODE_MODELS = Object.freeze([
|
|
596
607
|
Object.freeze({
|
|
597
608
|
id: "deepseek-flash",
|
|
@@ -652,6 +663,32 @@ const PACKYCODE_MODELS = Object.freeze([
|
|
|
652
663
|
thinkingLevelMap: GROK_LEVELS,
|
|
653
664
|
compat: Object.freeze({ supportsLongCacheRetention: false })
|
|
654
665
|
}),
|
|
666
|
+
Object.freeze({
|
|
667
|
+
id: "gemini-4-argon",
|
|
668
|
+
name: "Gemini 4 Argon",
|
|
669
|
+
api: "google-generative-ai",
|
|
670
|
+
provider: PROVIDER,
|
|
671
|
+
baseUrl: GEMINI_BASE,
|
|
672
|
+
reasoning: true,
|
|
673
|
+
input: Object.freeze(["text", "image"]),
|
|
674
|
+
cost: NO_COST,
|
|
675
|
+
contextWindow: 1048576,
|
|
676
|
+
maxTokens: 65536,
|
|
677
|
+
thinkingLevelMap: GEMINI_LEVELS
|
|
678
|
+
}),
|
|
679
|
+
Object.freeze({
|
|
680
|
+
id: "gemini-3.8-flash",
|
|
681
|
+
name: "Gemini 3.8 Flash",
|
|
682
|
+
api: "google-generative-ai",
|
|
683
|
+
provider: PROVIDER,
|
|
684
|
+
baseUrl: GEMINI_BASE,
|
|
685
|
+
reasoning: true,
|
|
686
|
+
input: Object.freeze(["text", "image"]),
|
|
687
|
+
cost: NO_COST,
|
|
688
|
+
contextWindow: 1048576,
|
|
689
|
+
maxTokens: 65536,
|
|
690
|
+
thinkingLevelMap: GEMINI_LEVELS
|
|
691
|
+
}),
|
|
655
692
|
Object.freeze({
|
|
656
693
|
id: "claude-opus-5-5",
|
|
657
694
|
name: "Claude Opus 5.5",
|
|
@@ -5375,13 +5412,119 @@ function createPackyCodePluginManager({ fetchImpl = ambientFetch, runCommand = s
|
|
|
5375
5412
|
});
|
|
5376
5413
|
}
|
|
5377
5414
|
//#endregion
|
|
5415
|
+
//#region src/gemini-replay.js
|
|
5416
|
+
/** A bounded resend for the gateway's intermittent Flash signature refusal. */
|
|
5417
|
+
const waitForRetry = (ms, signal) => setTimeout$1(ms, void 0, { signal });
|
|
5418
|
+
/** Only a current-turn result for a signed same-model native call qualifies. */
|
|
5419
|
+
function hasSignedResult(model, context) {
|
|
5420
|
+
if (model?.id !== "gemini-3.8-flash" || model.api !== "google-generative-ai") return false;
|
|
5421
|
+
const messages = context?.messages ?? [];
|
|
5422
|
+
const calls = /* @__PURE__ */ new Set();
|
|
5423
|
+
for (const message of messages.slice(messages.findLastIndex((item) => item.role === "user") + 1)) {
|
|
5424
|
+
if (message.role === "assistant" && message.provider === model.provider && message.model === model.id && message.api === model.api) for (const block of Array.isArray(message.content) ? message.content : []) {
|
|
5425
|
+
const signature = block.thoughtSignature;
|
|
5426
|
+
if (block.type === "toolCall" && typeof block.id === "string" && typeof signature === "string" && signature.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/u.test(signature)) calls.add(block.id);
|
|
5427
|
+
}
|
|
5428
|
+
if (message.role === "toolResult" && calls.has(message.toolCallId)) return true;
|
|
5429
|
+
}
|
|
5430
|
+
return false;
|
|
5431
|
+
}
|
|
5432
|
+
function isSignatureRefusal(event) {
|
|
5433
|
+
if (event.type !== "error" || event.reason !== "error" || event.error.content?.length) return false;
|
|
5434
|
+
const text = event.error.errorMessage ?? "";
|
|
5435
|
+
return /\b400\b/u.test(text) && /\b(?:Corrupted|Invalid) thought signature\b/iu.test(text) && !/\b(?:401|403|408|409|429|5\d\d)\b/u.test(text) && classifyGatewayFailure(text) === void 0;
|
|
5436
|
+
}
|
|
5437
|
+
/**
|
|
5438
|
+
* The caller closes over one resolved model/context/key/session. Reopening it
|
|
5439
|
+
* resends that request once; it never chooses a different key or changes the
|
|
5440
|
+
* signature. SDK retries remain disabled by the host. Its ordinary retry
|
|
5441
|
+
* policy still receives any terminal failure after this one compatibility
|
|
5442
|
+
* resend. Never replay after even a start event has reached the consumer.
|
|
5443
|
+
*/
|
|
5444
|
+
function guardGeminiReplay(open, { model, context, signal, wait = waitForRetry } = {}) {
|
|
5445
|
+
const initial = open();
|
|
5446
|
+
if (!hasSignedResult(model, context) || signal?.aborted) return initial;
|
|
5447
|
+
let finalMessage;
|
|
5448
|
+
return lazyStream(model, async () => ({
|
|
5449
|
+
async *[Symbol.asyncIterator]() {
|
|
5450
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
5451
|
+
const source = attempt === 0 ? initial : open();
|
|
5452
|
+
let observed = false;
|
|
5453
|
+
let refused;
|
|
5454
|
+
for await (const event of source) if (attempt === 0 && !observed && isSignatureRefusal(event)) refused = event;
|
|
5455
|
+
else {
|
|
5456
|
+
observed = true;
|
|
5457
|
+
yield event;
|
|
5458
|
+
}
|
|
5459
|
+
finalMessage = await source.result();
|
|
5460
|
+
if (refused === void 0) return;
|
|
5461
|
+
try {
|
|
5462
|
+
await wait(1e3, signal);
|
|
5463
|
+
signal?.throwIfAborted();
|
|
5464
|
+
} catch (error) {
|
|
5465
|
+
if (!signal?.aborted) throw error;
|
|
5466
|
+
finalMessage = {
|
|
5467
|
+
...refused.error,
|
|
5468
|
+
stopReason: "aborted",
|
|
5469
|
+
errorMessage: "Request aborted"
|
|
5470
|
+
};
|
|
5471
|
+
yield {
|
|
5472
|
+
type: "error",
|
|
5473
|
+
reason: "aborted",
|
|
5474
|
+
error: finalMessage
|
|
5475
|
+
};
|
|
5476
|
+
return;
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
},
|
|
5480
|
+
result: async () => finalMessage
|
|
5481
|
+
}));
|
|
5482
|
+
}
|
|
5483
|
+
//#endregion
|
|
5484
|
+
//#region src/gemini-api.js
|
|
5485
|
+
/** Gemini's native API, including level-based thinking on older pi-ai hosts. */
|
|
5486
|
+
/**
|
|
5487
|
+
* pi-ai 0.85 treats Argon as a budget-based model, and defaults Flash to
|
|
5488
|
+
* MINIMAL when reasoning is omitted. Both models support LOW/MEDIUM/HIGH,
|
|
5489
|
+
* default to MEDIUM and cannot disable thinking. Keep the native SDK stream,
|
|
5490
|
+
* credentials, tools and replay signatures; correct only that config.
|
|
5491
|
+
*/
|
|
5492
|
+
function createGeminiApi() {
|
|
5493
|
+
const api = googleGenerativeAIApi();
|
|
5494
|
+
return {
|
|
5495
|
+
...api,
|
|
5496
|
+
streamSimple(model, context, options) {
|
|
5497
|
+
if (!["gemini-4-argon", "gemini-3.8-flash"].includes(model.id)) return api.streamSimple(model, context, options);
|
|
5498
|
+
const level = options?.reasoning === void 0 ? "medium" : clampThinkingLevel(model, options.reasoning);
|
|
5499
|
+
const wireLevel = (model.thinkingLevelMap?.[level] ?? level).toUpperCase();
|
|
5500
|
+
return api.streamSimple(model, context, {
|
|
5501
|
+
...options,
|
|
5502
|
+
onPayload: async (payload, descriptor) => {
|
|
5503
|
+
const { thinkingBudget: _budget, ...thinking } = payload.config?.thinkingConfig ?? {};
|
|
5504
|
+
const normalized = {
|
|
5505
|
+
...payload,
|
|
5506
|
+
config: {
|
|
5507
|
+
...payload.config,
|
|
5508
|
+
thinkingConfig: {
|
|
5509
|
+
...thinking,
|
|
5510
|
+
thinkingLevel: wireLevel
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5513
|
+
};
|
|
5514
|
+
return await options?.onPayload?.(normalized, descriptor) ?? normalized;
|
|
5515
|
+
}
|
|
5516
|
+
});
|
|
5517
|
+
}
|
|
5518
|
+
};
|
|
5519
|
+
}
|
|
5520
|
+
//#endregion
|
|
5378
5521
|
//#region src/provider.js
|
|
5379
5522
|
/**
|
|
5380
5523
|
* The PackyCode pi-ai provider, built in code so the user configures no route.
|
|
5381
5524
|
*
|
|
5382
5525
|
* `createProvider` dispatches on `model.api`, which is what lets one route
|
|
5383
|
-
* carry
|
|
5384
|
-
*
|
|
5526
|
+
* carry Chat Completions, Responses, Anthropic Messages and native Gemini
|
|
5527
|
+
* models — the gateway refuses some of them over other protocols.
|
|
5385
5528
|
*
|
|
5386
5529
|
* @module dsh-packycode-api/provider
|
|
5387
5530
|
*/
|
|
@@ -5413,7 +5556,8 @@ function createPackyCodeProvider() {
|
|
|
5413
5556
|
api: {
|
|
5414
5557
|
"openai-completions": openAICompletionsApi(),
|
|
5415
5558
|
"openai-responses": openAIResponsesApi(),
|
|
5416
|
-
"anthropic-messages": anthropicMessagesApi()
|
|
5559
|
+
"anthropic-messages": anthropicMessagesApi(),
|
|
5560
|
+
"google-generative-ai": createGeminiApi()
|
|
5417
5561
|
}
|
|
5418
5562
|
});
|
|
5419
5563
|
const requestOptions = (model, options) => model.api === "openai-responses" && !model.id.startsWith("gpt-") ? {
|
|
@@ -5505,7 +5649,7 @@ function guardKeyRotation(stream, { onKeyFailure, key } = {}) {
|
|
|
5505
5649
|
//#region src/route.js
|
|
5506
5650
|
/**
|
|
5507
5651
|
* The HTTP model route: the pi-ai provider profile and the adapter that
|
|
5508
|
-
* serves the catalog's HTTP models (DeepSeek, GPT and
|
|
5652
|
+
* serves the catalog's HTTP models (DeepSeek, GPT, Grok and Gemini) with the ring's keys.
|
|
5509
5653
|
*
|
|
5510
5654
|
* @module dsh-packycode-api/route
|
|
5511
5655
|
*/
|
|
@@ -5617,7 +5761,11 @@ function createRouteAdapter({ ring, keys, fallbackSessionId, resolveAttachments,
|
|
|
5617
5761
|
};
|
|
5618
5762
|
const guarded = (method) => (model, context, options) => {
|
|
5619
5763
|
const routed = requestOptions(model, options);
|
|
5620
|
-
return guardKeyRotation(raw[method](model, context, routed), {
|
|
5764
|
+
return guardKeyRotation(guardGeminiReplay(() => raw[method](model, context, routed), {
|
|
5765
|
+
model,
|
|
5766
|
+
context,
|
|
5767
|
+
signal: routed?.signal
|
|
5768
|
+
}), {
|
|
5621
5769
|
key: routed?.apiKey,
|
|
5622
5770
|
onKeyFailure: keys.onKeyFailure
|
|
5623
5771
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-packycode-api",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.9",
|
|
4
4
|
"description": "Use a PackyCode (PackyAPI) API key in DeepSeek Harness through a built-in multi-protocol model route with key rotation and balance display",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|