@paean-ai/harness-network 0.1.1-rc.16
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 -0
- package/README.i18n.yaml +37 -0
- package/README.md +92 -0
- package/README.zh.md +92 -0
- package/lib/index.js +151 -0
- package/lib/types/index.d.ts +53 -0
- package/lib/types/probe.d.ts +14 -0
- package/lib/types/types.d.ts +23 -0
- package/lib/types/types.js +3 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +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.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/paean/paean-network/README.md
|
|
5
|
+
/:
|
|
6
|
+
en: b6693519d044a57a
|
|
7
|
+
zh: 44ba83c2f410a7dd
|
|
8
|
+
/paean-ai-harness-network:
|
|
9
|
+
en: 6b0b2e4c5c4cf583
|
|
10
|
+
zh: 90e45981adc36810
|
|
11
|
+
/paean-ai-harness-network/summary:
|
|
12
|
+
en: f78b5bf92eff01ee
|
|
13
|
+
zh: 0cef12cdce1f6117
|
|
14
|
+
/paean-ai-harness-network/table-of-contents:
|
|
15
|
+
en: d152484eb41ac6b4
|
|
16
|
+
zh: 184c58005fb159f7
|
|
17
|
+
/paean-ai-harness-network/use-this-package:
|
|
18
|
+
en: adc0bf49f2210f7a
|
|
19
|
+
zh: a3a1707b60f4cbf9
|
|
20
|
+
/paean-ai-harness-network/understand-the-implementation:
|
|
21
|
+
en: 086dc88fc7d6e7e9
|
|
22
|
+
zh: f89902ec93f0a38c
|
|
23
|
+
/paean-ai-harness-network/further-exploration:
|
|
24
|
+
en: b8390df89d3381ec
|
|
25
|
+
zh: 9d10572f26dc769d
|
|
26
|
+
/paean-ai-harness-network/model-experience:
|
|
27
|
+
en: 19a12a60f8a5fa34
|
|
28
|
+
zh: b6133fca43308d46
|
|
29
|
+
/paean-ai-harness-network/model-experience/kv-cache-effect:
|
|
30
|
+
en: 622085eff4b93114
|
|
31
|
+
zh: 229c9791ff2ed15a
|
|
32
|
+
/paean-ai-harness-network/known-limitations-and-deferred-work:
|
|
33
|
+
en: dc938dc8217be3f2
|
|
34
|
+
zh: 9d0b1b030bf141fe
|
|
35
|
+
/paean-ai-harness-network/known-limitations-and-deferred-work/dev-note:
|
|
36
|
+
en: e67f633dc6fe7773
|
|
37
|
+
zh: 8ec3e73528ddb1a6
|
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Select the Paean API ingress and compare credential-free connection latency without changing custom endpoints or system proxy settings."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `@paean-ai/harness-network`
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
Choose the default Paean API or the OpenSOTA DCDN ingress for new requests. Compare response-header latency without sending an account token or calling a model. The selection persists in the profile configuration and takes effect without restarting. Custom endpoint configurations remain unchanged.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
Paean compositions mount this plugin in both GUI and headless profiles. In Settings → API connection, select **Paean · default** (`api.paean.ai`) or **OpenSOTA · DCDN acceleration** (`api.opensota.cn`). The default is Paean. The comparison button measures both nodes but never switches automatically. The page reports DNS, TLS, proxy, timeout and HTTP failures with recovery guidance.
|
|
29
|
+
|
|
30
|
+
Custom compositions mount the service through a `cordis.yml` row:
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
- id: paean-network
|
|
34
|
+
name: '@paean-ai/harness-network'
|
|
35
|
+
config:
|
|
36
|
+
node: paean
|
|
37
|
+
probeTimeoutMs: 8000
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`node` accepts only `paean` and `opensota` and is a live setting. `probeTimeoutMs` is a deployment-owned deadline from 1 to 60000 milliseconds. The service preserves paths such as `/v1`. URLs outside the two HTTPS origins remain unchanged, including development deployments and custom model providers.
|
|
41
|
+
|
|
42
|
+
-----
|
|
43
|
+
|
|
44
|
+
<a id="understand-the-implementation"></a>
|
|
45
|
+
## Understand the implementation
|
|
46
|
+
|
|
47
|
+
<details>
|
|
48
|
+
<summary>Implementation internals — click to expand</summary>
|
|
49
|
+
|
|
50
|
+
The service owns one volatile preference. API consumers resolve it at the start of each request; the pi-ai provider-options waterfall resolves the same preference before creating an immutable adapter snapshot. Both `paean` and `paean-anthropic` model routes participate. Requests already using a snapshot retain their endpoint. The account plugin owns the localized settings section and loopback HTTP routes; settings persistence and concurrency remain owned by DSH ConfigEditor.
|
|
51
|
+
|
|
52
|
+
Comparison performs concurrent unauthenticated `GET /user/profile` requests against the two fixed origins, refuses redirects and discards bodies. HTTP 401 is an expected reachable response, not evidence about the user's session. The Host's fetch policy owns process proxies; this plugin never changes OS network settings. Repeated calls share one pending comparison. Disposal aborts and drains it.
|
|
53
|
+
|
|
54
|
+
**Runtime invariant:** No companion is published. The preference has one authority, and request snapshots are derived values rather than independently mutable records.
|
|
55
|
+
|
|
56
|
+
</details>
|
|
57
|
+
|
|
58
|
+
-----
|
|
59
|
+
|
|
60
|
+
<a id="further-exploration"></a>
|
|
61
|
+
## Further Exploration
|
|
62
|
+
|
|
63
|
+
- [Paean account and Workspace](../paean-app/README.md) — the settings page and API consumers.
|
|
64
|
+
- [pi-ai adapter](../../llm/llm-pi-ai/README.md) — provider snapshots and credential references.
|
|
65
|
+
- [Live configuration forms](../../../docs/cookbook/adding-a-settings-card.md) — persistence and concurrent editing.
|
|
66
|
+
|
|
67
|
+
<a id="model-experience"></a>
|
|
68
|
+
## Model Experience
|
|
69
|
+
|
|
70
|
+
None, as ingress selection and connection diagnostics add no model-visible text.
|
|
71
|
+
|
|
72
|
+
#### KV Cache effect
|
|
73
|
+
|
|
74
|
+
The plugin does not change model input. A different ingress may use different server-side caches.
|
|
75
|
+
|
|
76
|
+
## Known Limitations and Deferred Work
|
|
77
|
+
|
|
78
|
+
Measurements describe this Host's current connection, not model throughput.
|
|
79
|
+
|
|
80
|
+
- A single sample is affected by connection reuse, DNS caching and transient load.
|
|
81
|
+
- The plugin does not route the browser sign-in site, downloads or custom endpoints.
|
|
82
|
+
- Streaming Workspace proxy traffic uses its existing HTTP transport; the fetch probe does not verify every proxy transport path.
|
|
83
|
+
|
|
84
|
+
<a id="dev-note"></a>
|
|
85
|
+
### Dev Note
|
|
86
|
+
|
|
87
|
+
<details>
|
|
88
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
89
|
+
|
|
90
|
+
None.
|
|
91
|
+
|
|
92
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "选择 Paean API 接入节点并对比无凭据连接延迟,不改变自定义端点或系统代理设置。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `@paean-ai/harness-network`
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
为新请求选择默认 Paean API 或 OpenSOTA DCDN 接入点。无需发送账号令牌或调用模型,即可对比收到响应头的延迟。选择保存在 profile 配置中,无需重启即可生效。自定义端点配置保持不变。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用此包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用此包
|
|
27
|
+
|
|
28
|
+
Paean 组合在 GUI 和 headless profile 中都挂载此插件。在“设置 → API 接入节点”中,选择 **Paean · 默认节点**(`api.paean.ai`)或 **OpenSOTA · DCDN 加速节点**(`api.opensota.cn`)。默认为 Paean。测速按钮测量两个节点,但不会自动切换。页面分别显示 DNS、TLS、代理、超时和 HTTP 错误及修复指引。
|
|
29
|
+
|
|
30
|
+
自定义组合通过 `cordis.yml` 条目挂载此服务:
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
- id: paean-network
|
|
34
|
+
name: '@paean-ai/harness-network'
|
|
35
|
+
config:
|
|
36
|
+
node: paean
|
|
37
|
+
probeTimeoutMs: 8000
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`node` 仅接受 `paean` 和 `opensota`,属于动态配置。`probeTimeoutMs` 是部署方指定的超时限制,范围为 1 至 60000 毫秒。服务保留 `/v1` 等路径。两个 HTTPS origin 之外的 URL 保持不变,包括开发环境和自定义模型提供方。
|
|
41
|
+
|
|
42
|
+
-----
|
|
43
|
+
|
|
44
|
+
<a id="understand-the-implementation"></a>
|
|
45
|
+
## 理解实现
|
|
46
|
+
|
|
47
|
+
<details>
|
|
48
|
+
<summary>实现细节 — 点击展开</summary>
|
|
49
|
+
|
|
50
|
+
服务持有一个 volatile 偏好。API 消费方在每次请求开始时解析它;pi-ai 提供方选项 waterfall 在创建不可变适配器快照之前解析同一偏好。`paean` 和 `paean-anthropic` 两个模型路由均参与。已经使用快照的请求保留其端点。账号插件拥有本地化设置页和回环 HTTP 路由;配置持久化和并发编辑仍由 DSH ConfigEditor 负责。
|
|
51
|
+
|
|
52
|
+
对比功能向两个固定 origin 并发发送无凭据的 `GET /user/profile` 请求,拒绝重定向并丢弃响应体。HTTP 401 是节点可达的预期响应,不代表用户会话状态。进程代理由 Host 的 fetch 策略负责;此插件不修改操作系统网络设置。重复调用共享同一次测速,卸载会取消并等待其结束。
|
|
53
|
+
|
|
54
|
+
**运行时不变量:** 不发布配套检查器。偏好只有一个权威来源,请求快照是派生值,不是独立可变记录。
|
|
55
|
+
|
|
56
|
+
</details>
|
|
57
|
+
|
|
58
|
+
-----
|
|
59
|
+
|
|
60
|
+
<a id="further-exploration"></a>
|
|
61
|
+
## 进一步探索
|
|
62
|
+
|
|
63
|
+
- [Paean 账号与 Workspace](../paean-app/README.zh.md) — 设置页与 API 消费方。
|
|
64
|
+
- [pi-ai 适配器](../../llm/llm-pi-ai/README.zh.md) — 提供方快照与凭据引用。
|
|
65
|
+
- [动态配置表单](../../../docs/cookbook/adding-a-settings-card.zh.md) — 持久化与并发编辑。
|
|
66
|
+
|
|
67
|
+
<a id="model-experience"></a>
|
|
68
|
+
## Model Experience
|
|
69
|
+
|
|
70
|
+
无,因为接入选择和连接诊断不添加模型可见文本。
|
|
71
|
+
|
|
72
|
+
#### KV Cache effect
|
|
73
|
+
|
|
74
|
+
此插件不改变模型输入。不同接入点可能使用不同的服务端缓存。
|
|
75
|
+
|
|
76
|
+
## Known Limitations and Deferred Work
|
|
77
|
+
|
|
78
|
+
测量反映此 Host 当前的连接情况,不代表模型吞吐量。
|
|
79
|
+
|
|
80
|
+
- 单次采样会受到连接复用、DNS 缓存和瞬时负载影响。
|
|
81
|
+
- 此插件不调整浏览器登录站点、下载地址或自定义端点。
|
|
82
|
+
- 流式 Workspace 代理流量使用原有 HTTP 传输;fetch 测速不能验证所有代理传输路径。
|
|
83
|
+
|
|
84
|
+
<a id="dev-note"></a>
|
|
85
|
+
### 开发备注
|
|
86
|
+
|
|
87
|
+
<details>
|
|
88
|
+
<summary>维护者工作上下文 — 点击展开</summary>
|
|
89
|
+
|
|
90
|
+
无。
|
|
91
|
+
|
|
92
|
+
</details>
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
//#region lib/types/probe.js
|
|
4
|
+
/** Fixed destinations; callers cannot turn the diagnostic into an arbitrary URL fetch. */
|
|
5
|
+
const PAEAN_NODES = {
|
|
6
|
+
paean: "https://api.paean.ai",
|
|
7
|
+
opensota: "https://api.opensota.cn"
|
|
8
|
+
};
|
|
9
|
+
/** Safe classification from transport error codes, without exposing their messages. */
|
|
10
|
+
function failureOf(error) {
|
|
11
|
+
let cause = error;
|
|
12
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13
|
+
while (typeof cause === "object" && cause !== null && !seen.has(cause)) {
|
|
14
|
+
seen.add(cause);
|
|
15
|
+
const code = "code" in cause && typeof cause.code === "string" ? cause.code : "";
|
|
16
|
+
if (code === "ENOTFOUND" || code === "EAI_AGAIN") return "dns";
|
|
17
|
+
if (code === "UND_ERR_PRX_TLS" || code === "UND_ERR_PROXY") return "proxy";
|
|
18
|
+
if (/CERT|TLS|SSL/.test(code)) return "tls";
|
|
19
|
+
cause = "cause" in cause ? cause.cause : void 0;
|
|
20
|
+
}
|
|
21
|
+
return "connection";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Measure a fixed public endpoint without authentication, redirects, or response-body retention.
|
|
25
|
+
* @param node - allowlisted ingress.
|
|
26
|
+
* @param timeoutMs - maximum time to wait for response headers.
|
|
27
|
+
* @param signal - owning plugin or caller cancellation.
|
|
28
|
+
* @param fetcher - Host fetch implementation, including its process-local proxy policy.
|
|
29
|
+
* @returns a safe result even when connection establishment fails.
|
|
30
|
+
*/
|
|
31
|
+
async function probeNode(node, timeoutMs, signal, fetcher = globalThis.fetch) {
|
|
32
|
+
const started = performance.now();
|
|
33
|
+
const timeout = new AbortController();
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
timeout.abort();
|
|
36
|
+
}, timeoutMs);
|
|
37
|
+
const result = () => ({
|
|
38
|
+
node,
|
|
39
|
+
endpoint: PAEAN_NODES[node],
|
|
40
|
+
elapsedMs: Math.round(performance.now() - started)
|
|
41
|
+
});
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetcher(`${PAEAN_NODES[node]}/user/profile`, {
|
|
44
|
+
method: "GET",
|
|
45
|
+
redirect: "manual",
|
|
46
|
+
credentials: "omit",
|
|
47
|
+
cache: "no-store",
|
|
48
|
+
signal: AbortSignal.any([signal, timeout.signal]),
|
|
49
|
+
headers: { accept: "application/json" }
|
|
50
|
+
});
|
|
51
|
+
const measured = result();
|
|
52
|
+
await response.body?.cancel();
|
|
53
|
+
if (response.status === 200 || response.status === 401) return {
|
|
54
|
+
...measured,
|
|
55
|
+
reachable: true,
|
|
56
|
+
status: response.status
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
...measured,
|
|
60
|
+
reachable: false,
|
|
61
|
+
status: response.status,
|
|
62
|
+
failure: response.status === 407 ? "proxy" : "http"
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return {
|
|
66
|
+
...result(),
|
|
67
|
+
reachable: false,
|
|
68
|
+
failure: signal.aborted ? "aborted" : timeout.signal.aborted ? "timeout" : failureOf(error)
|
|
69
|
+
};
|
|
70
|
+
} finally {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region lib/types/index.js
|
|
76
|
+
/** Shared API ingress selection for Paean plugins and pi-ai provider routing. */
|
|
77
|
+
/** Only the two public ingress nodes are selectable; custom deployments remain explicit. */
|
|
78
|
+
const Config = z.object({
|
|
79
|
+
node: z.union(["paean", "opensota"]).default("paean").volatile(),
|
|
80
|
+
probeTimeoutMs: z.number().step(1).min(1).max(6e4).default(8e3)
|
|
81
|
+
});
|
|
82
|
+
/** One settings-backed preference shared by API consumers; it never owns credentials. */
|
|
83
|
+
var PaeanNetwork = class extends Service {
|
|
84
|
+
config;
|
|
85
|
+
static Config = Config;
|
|
86
|
+
lifetime = new AbortController();
|
|
87
|
+
pending;
|
|
88
|
+
constructor(ctx, config) {
|
|
89
|
+
super(ctx, "paeanNetwork");
|
|
90
|
+
this.config = config;
|
|
91
|
+
ctx.inject(["settings"], (child) => {
|
|
92
|
+
child.effect(() => child.settings.configure({ auto: false }, ctx.fiber));
|
|
93
|
+
});
|
|
94
|
+
let previous;
|
|
95
|
+
let previousNode;
|
|
96
|
+
let routed;
|
|
97
|
+
ctx.on("llm-pi-ai/providers", (_configured, next) => {
|
|
98
|
+
const providers = next();
|
|
99
|
+
const node = config.node.get();
|
|
100
|
+
if (providers === previous && node === previousNode && routed !== void 0) return routed;
|
|
101
|
+
routed = providers;
|
|
102
|
+
for (const key of ["paean", "paean-anthropic"]) {
|
|
103
|
+
const profile = providers[key];
|
|
104
|
+
const base = profile?.baseURL;
|
|
105
|
+
const target = base === void 0 ? void 0 : this.resolve(base);
|
|
106
|
+
if (profile !== void 0 && target !== void 0 && target !== base) routed = {
|
|
107
|
+
...routed,
|
|
108
|
+
[key]: {
|
|
109
|
+
...profile,
|
|
110
|
+
baseURL: target
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
previous = providers;
|
|
115
|
+
previousNode = node;
|
|
116
|
+
return routed;
|
|
117
|
+
});
|
|
118
|
+
ctx.effect(() => async () => {
|
|
119
|
+
this.lifetime.abort();
|
|
120
|
+
await this.pending;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Resolve a branded API URL through the selected ingress, preserving its path.
|
|
125
|
+
* Custom endpoints remain owned by their explicit plugin configuration.
|
|
126
|
+
* @param base - deployment-configured API base or model URL.
|
|
127
|
+
* @returns the selected URL, or the unchanged custom URL.
|
|
128
|
+
*/
|
|
129
|
+
resolve(base) {
|
|
130
|
+
const url = new URL(base);
|
|
131
|
+
if (!Object.values(PAEAN_NODES).includes(url.origin) || url.username || url.password) return base;
|
|
132
|
+
const target = new URL(PAEAN_NODES[this.config.node.get()]);
|
|
133
|
+
target.pathname = url.pathname;
|
|
134
|
+
target.search = url.search;
|
|
135
|
+
target.hash = url.hash;
|
|
136
|
+
return url.pathname === "/" && !url.search && !url.hash ? target.origin : target.href;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Compare the two fixed nodes concurrently, sharing an in-flight comparison.
|
|
140
|
+
* Disposal cancels and drains outstanding fetches. No token is read or sent.
|
|
141
|
+
* @returns response-header latency and safe failure details for both nodes.
|
|
142
|
+
*/
|
|
143
|
+
compare() {
|
|
144
|
+
this.pending ??= Promise.all(["paean", "opensota"].map((node) => probeNode(node, this.config.probeTimeoutMs, this.lifetime.signal))).finally(() => {
|
|
145
|
+
this.pending = void 0;
|
|
146
|
+
});
|
|
147
|
+
return this.pending;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
//#endregion
|
|
151
|
+
export { Config, PaeanNetwork as default };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Shared API ingress selection for Paean plugins and pi-ai provider routing. */
|
|
2
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
3
|
+
import type { Volatile } from '@deepseek-ai/cordis';
|
|
4
|
+
import z from '@deepseek-ai/schemastery';
|
|
5
|
+
import type { NodeProbe, PaeanNode } from './types.ts';
|
|
6
|
+
declare module '@deepseek-ai/cordis' {
|
|
7
|
+
interface Context {
|
|
8
|
+
paeanNetwork: PaeanNetwork;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** Live node preference and deployment-owned diagnostic deadline. */
|
|
12
|
+
export interface Config {
|
|
13
|
+
/** Public ingress used for subsequent requests to branded Paean API URLs. */
|
|
14
|
+
node: Volatile<PaeanNode>;
|
|
15
|
+
/** Maximum response-header wait for each credential-free probe, in milliseconds. */
|
|
16
|
+
probeTimeoutMs: number;
|
|
17
|
+
}
|
|
18
|
+
/** Only the two public ingress nodes are selectable; custom deployments remain explicit. */
|
|
19
|
+
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
20
|
+
node: z<"paean" | "opensota", "paean" | "opensota", "volatile-defined">;
|
|
21
|
+
probeTimeoutMs: z<number, number, "defined">;
|
|
22
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
23
|
+
node: z<"paean" | "opensota", "paean" | "opensota", "volatile-defined">;
|
|
24
|
+
probeTimeoutMs: z<number, number, "defined">;
|
|
25
|
+
}>>, "plain">;
|
|
26
|
+
/** One settings-backed preference shared by API consumers; it never owns credentials. */
|
|
27
|
+
export default class PaeanNetwork extends Service {
|
|
28
|
+
private readonly config;
|
|
29
|
+
static Config: z<Schemastery.ObjectS<NoInfer<{
|
|
30
|
+
node: z<"paean" | "opensota", "paean" | "opensota", "volatile-defined">;
|
|
31
|
+
probeTimeoutMs: z<number, number, "defined">;
|
|
32
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
33
|
+
node: z<"paean" | "opensota", "paean" | "opensota", "volatile-defined">;
|
|
34
|
+
probeTimeoutMs: z<number, number, "defined">;
|
|
35
|
+
}>>, "plain">;
|
|
36
|
+
private readonly lifetime;
|
|
37
|
+
private pending;
|
|
38
|
+
constructor(ctx: Context, config: Config);
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a branded API URL through the selected ingress, preserving its path.
|
|
41
|
+
* Custom endpoints remain owned by their explicit plugin configuration.
|
|
42
|
+
* @param base - deployment-configured API base or model URL.
|
|
43
|
+
* @returns the selected URL, or the unchanged custom URL.
|
|
44
|
+
*/
|
|
45
|
+
resolve(base: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Compare the two fixed nodes concurrently, sharing an in-flight comparison.
|
|
48
|
+
* Disposal cancels and drains outstanding fetches. No token is read or sent.
|
|
49
|
+
* @returns response-header latency and safe failure details for both nodes.
|
|
50
|
+
*/
|
|
51
|
+
compare(): Promise<NodeProbe[]>;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Bounded, credential-free Paean API response-header measurements. */
|
|
2
|
+
import type { NodeProbe, PaeanNode } from './types.ts';
|
|
3
|
+
/** Fixed destinations; callers cannot turn the diagnostic into an arbitrary URL fetch. */
|
|
4
|
+
export declare const PAEAN_NODES: Readonly<Record<PaeanNode, string>>;
|
|
5
|
+
/**
|
|
6
|
+
* Measure a fixed public endpoint without authentication, redirects, or response-body retention.
|
|
7
|
+
* @param node - allowlisted ingress.
|
|
8
|
+
* @param timeoutMs - maximum time to wait for response headers.
|
|
9
|
+
* @param signal - owning plugin or caller cancellation.
|
|
10
|
+
* @param fetcher - Host fetch implementation, including its process-local proxy policy.
|
|
11
|
+
* @returns a safe result even when connection establishment fails.
|
|
12
|
+
*/
|
|
13
|
+
export declare function probeNode(node: PaeanNode, timeoutMs: number, signal: AbortSignal, fetcher?: typeof fetch): Promise<NodeProbe>;
|
|
14
|
+
//# sourceMappingURL=probe.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Client-safe fields for Paean node selection and connection diagnostics. */
|
|
2
|
+
/** Supported public Paean API ingress nodes. */
|
|
3
|
+
export type PaeanNode = 'paean' | 'opensota';
|
|
4
|
+
/** Safe diagnostic category; raw transport messages and proxy credentials are excluded. */
|
|
5
|
+
export type ProbeFailure = 'timeout' | 'dns' | 'tls' | 'proxy' | 'connection' | 'http' | 'aborted';
|
|
6
|
+
/** One credential-free response-header measurement, not model throughput. */
|
|
7
|
+
interface ProbeTiming {
|
|
8
|
+
node: PaeanNode;
|
|
9
|
+
endpoint: string;
|
|
10
|
+
/** Wall duration until headers or failure, in milliseconds. */
|
|
11
|
+
elapsedMs: number;
|
|
12
|
+
}
|
|
13
|
+
/** Response-header timing with either expected reachability or a safe failure category. */
|
|
14
|
+
export type NodeProbe = ProbeTiming & ({
|
|
15
|
+
reachable: true;
|
|
16
|
+
status: 200 | 401;
|
|
17
|
+
} | {
|
|
18
|
+
reachable: false;
|
|
19
|
+
status?: number;
|
|
20
|
+
failure: ProbeFailure;
|
|
21
|
+
});
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=types.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@paean-ai/harness-network",
|
|
3
|
+
"description": "Shared Paean API node selection and credential-free connection diagnostics",
|
|
4
|
+
"version": "0.1.1-rc.16",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/a8e-ai/paean-harness.git",
|
|
11
|
+
"directory": "packages/paean/paean-network"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./types": {
|
|
22
|
+
"types": "./lib/types/types.d.ts",
|
|
23
|
+
"default": "./lib/types/types.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@deepseek-ai/cordis": "~4.0.4"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@deepseek-ai/schemastery": "~3.18.4"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@deepseek-ai/cordis": "~4.0.4",
|
|
37
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.2.0-rc.2",
|
|
38
|
+
"@deepseek-ai/dsh-app-boot": "0.2.0-rc.2",
|
|
39
|
+
"@deepseek-ai/cordis-plugin-timer": "~1.1.6",
|
|
40
|
+
"@deepseek-ai/dsh-settings": "0.2.0-rc.2",
|
|
41
|
+
"@deepseek-ai/dsh-config-editor": "0.2.0-rc.2",
|
|
42
|
+
"@deepseek-ai/dsh-hmr": "0.2.0-rc.2"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"lib/index.js",
|
|
46
|
+
"lib/types/**/*.d.ts",
|
|
47
|
+
"lib/types/types.js"
|
|
48
|
+
]
|
|
49
|
+
}
|