@siliconflow-official/dsh-llm-siliconflow 0.2.0-rc.1 → 0.2.0-rc.3

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/README.md CHANGED
@@ -34,6 +34,24 @@ dsh-siliconflow-setup
34
34
  export SILICONFLOW_API_KEY=sk-... # 或写入 $DSH_HOME/.credentials.yaml
35
35
  ```
36
36
 
37
+ 写入 `.credentials.yaml` 采用**跨代兼容规则**(单一构建同时服务新旧两代 dsh):
38
+
39
+ - **新文件 / 扁平(无 `version`)文件 → 保持扁平写入**:旧代 dsh(credentials-local 0.1.0)原生可读;新代 dsh 会在下次启动时自动迁移为 `version: 1`。
40
+ - **已是 `version: 1` 的文件 → 保持 version-1 写入**:新代用户永不降级;新旧两代读路径都识别这两种布局。
41
+ - 其他无法识别的文档(未知键、非法结构)会明确报错而不是被改写;早期版本向导误写的顶层键会在写入时折回 `refs:`。
42
+
43
+ ```yaml
44
+ # 向导写出的新文件(扁平,两代公共分母):
45
+ OTHER_KEY: sk-...
46
+ SILICONFLOW_API_KEY: sk-...
47
+
48
+ # 新代 dsh 首次启动后自动迁移为:
49
+ version: 1
50
+ refs:
51
+ OTHER_KEY: sk-...
52
+ SILICONFLOW_API_KEY: sk-...
53
+ ```
54
+
37
55
  在包发布到 npm 之前,先用 `pnpm install && pnpm build` 构建出 `lib/`,再从本地路径安装:
38
56
 
39
57
  ```sh
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as PUBLIC_BASE_URL, g as discoverChatModels, i as PROVIDER, n as DEFAULT_API_KEY_ENV, r as DEFAULT_MODELS } from "./types-BWDupDnP.js";
2
+ import { _ as discoverChatModels, a as PUBLIC_BASE_URL, i as PROVIDER, n as DEFAULT_API_KEY_ENV, r as DEFAULT_MODELS } from "./types-IqSaZRz4.js";
3
3
  import { createInterface } from "node:readline/promises";
4
4
  import { stdin, stdout } from "node:process";
5
5
  import { mkdir, readFile, writeFile } from "node:fs/promises";
@@ -45,11 +45,66 @@ function settingsPath(home) {
45
45
  function isEnoent(error) {
46
46
  return error.code === "ENOENT";
47
47
  }
48
+ /** The credentials document layout version this wizard reads and writes. */
49
+ const CREDENTIALS_LAYOUT_VERSION = 1;
50
+ /** POSIX identifier rule the credentials seam addresses references by. */
51
+ const REF_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
48
52
  /**
49
- * Read one credential reference from a comment-preserving document.
53
+ * Recognize one parsed credentials document.
54
+ * @param document - the parsed document; parse errors make it unrecognizable.
55
+ * @returns the admitted layout with its references — plus, under `extraKeys`,
56
+ * any top-level key a pre-fix release of this wizard left beside `version`,
57
+ * held out of `entries` so reads treat them as absent while the write path
58
+ * folds them back under `refs` — or `undefined` when the document does not
59
+ * parse, holds an unknown `version`, carries a `refs` section that is not a
60
+ * mapping of reference names to non-empty strings, or holds any other
61
+ * top-level entry this build cannot attribute to its own predecessor.
62
+ */
63
+ function admitCredentialsDocument(document) {
64
+ if (document.errors.length > 0) return void 0;
65
+ const root = document.toJS() ?? {};
66
+ if (typeof root !== "object" || root === null) return void 0;
67
+ const fields = root;
68
+ const entries = /* @__PURE__ */ new Map();
69
+ if (!("version" in fields)) {
70
+ for (const [key, value] of Object.entries(fields)) {
71
+ if (!REF_NAME_PATTERN.test(key)) return void 0;
72
+ if (typeof value !== "string" || value.length === 0) return void 0;
73
+ entries.set(key, value);
74
+ }
75
+ return {
76
+ kind: "flat",
77
+ entries
78
+ };
79
+ }
80
+ if (fields.version !== CREDENTIALS_LAYOUT_VERSION) return void 0;
81
+ const refs = fields["refs"];
82
+ if (refs !== void 0) {
83
+ if (typeof refs !== "object" || refs === null) return void 0;
84
+ for (const [key, value] of Object.entries(refs)) {
85
+ if (!REF_NAME_PATTERN.test(key)) return void 0;
86
+ if (typeof value !== "string" || value.length === 0) return void 0;
87
+ entries.set(key, value);
88
+ }
89
+ }
90
+ const extraKeys = /* @__PURE__ */ new Map();
91
+ for (const [key, value] of Object.entries(fields)) {
92
+ if (key === "version" || key === "refs" || key === "records") continue;
93
+ if (!REF_NAME_PATTERN.test(key) || typeof value !== "string" || value.length === 0) return void 0;
94
+ extraKeys.set(key, value);
95
+ }
96
+ return {
97
+ kind: "versioned",
98
+ entries,
99
+ ...extraKeys.size > 0 ? { extraKeys } : {}
100
+ };
101
+ }
102
+ /**
103
+ * Read one credential reference from a versioned or pre-release flat document.
50
104
  * @param path - the credentials document path.
51
- * @param keyEnv - the top-level reference name (e.g. `SILICONFLOW_API_KEY`).
52
- * @returns the stored value, or `undefined` when absent or the file is missing.
105
+ * @param keyEnv - the reference name to read (e.g. `SILICONFLOW_API_KEY`).
106
+ * @returns the stored value, or `undefined` when the reference is absent, the
107
+ * file is missing, or the document is not a recognizable layout.
53
108
  */
54
109
  async function readCredential(path, keyEnv) {
55
110
  let text;
@@ -59,18 +114,47 @@ async function readCredential(path, keyEnv) {
59
114
  if (isEnoent(error)) return void 0;
60
115
  throw error;
61
116
  }
62
- const value = parseDocument(text).toJS()?.[keyEnv];
63
- return typeof value === "string" && value.length > 0 ? value : void 0;
117
+ const admitted = admitCredentialsDocument(parseDocument(text));
118
+ if (admitted === void 0) return void 0;
119
+ return admitted.entries.get(keyEnv);
64
120
  }
65
121
  /**
66
- * Set one credential reference, preserving every other entry and comment.
122
+ * Write one credential reference, preserving the document's own layout family
123
+ * and every other entry and comment.
124
+ *
125
+ * Cross-era rule (0.2.0-rc.3): which layout a FRESH document gets is decided
126
+ * by what both dsh eras can read, not by this build's version clock. The OLD
127
+ * credentials-local (0.1.0-rc.8) reads only the flat layout; the NEW one
128
+ * (0.1.1+) reads only version-1 but migrates a version-less flat document on
129
+ * its next boot. Flat is therefore the common denominator: a fresh or flat
130
+ * document stays flat under this write, and the running dsh upgrades it when
131
+ * that dsh is new-era. An existing versioned document keeps version-1 so a
132
+ * new-era user is never downgraded; an old-era user with a versioned document
133
+ * still has a file old dsh rejects — that file predates this rule and is
134
+ * repaired by re-running setup on a flat base or by hand.
135
+ *
136
+ * A top-level key a pre-fix release of this wizard left beside `version` is
137
+ * folded back under `refs`, so one run repairs the file its predecessor
138
+ * corrupted. An unrecognized document fails loud instead of being rewritten —
139
+ * a silent rewrite would hide why the running harness rejects it.
67
140
  * @param path - the credentials document path; created when absent.
68
- * @param keyEnv - the top-level reference name to write.
141
+ * @param keyEnv - the reference name to write.
69
142
  * @param key - the value.
143
+ * @throws when the document exists but is not a recognizable credentials layout.
70
144
  */
71
145
  async function writeCredential(path, keyEnv, key) {
72
146
  const doc = await loadDocument(path);
73
- doc.set(keyEnv, key);
147
+ const admitted = admitCredentialsDocument(doc);
148
+ if (admitted === void 0) throw new Error(`setup: ${path} is not a recognizable credentials document (expected version 1 with a refs section, or the pre-release flat layout); fix it before running setup`);
149
+ if (admitted.kind === "flat") {
150
+ doc.setIn([keyEnv], key);
151
+ await persistDocument(path, doc);
152
+ return;
153
+ }
154
+ for (const extra of admitted.extraKeys?.keys() ?? []) doc.deleteIn([extra]);
155
+ for (const [extra, value] of admitted.extraKeys ?? /* @__PURE__ */ new Map()) doc.setIn(["refs", extra], value);
156
+ doc.setIn(["version"], CREDENTIALS_LAYOUT_VERSION);
157
+ doc.setIn(["refs", keyEnv], key);
74
158
  await persistDocument(path, doc);
75
159
  }
76
160
  /**
package/lib/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as listingUrl, a as PUBLIC_BASE_URL, c as name, d as DEFAULT_MAX_TOKENS, f as DEFAULT_STREAM_IDLE_TIMEOUT_MS, g as discoverChatModels, h as inferInputModalities, i as PROVIDER, l as resolveAdapterOptions, m as SiliconFlowAdapter, n as DEFAULT_API_KEY_ENV, o as apply, p as DISCOVERY_TTL_MS, r as DEFAULT_MODELS, s as inject, t as Config, u as DEFAULT_CONTEXT_WINDOW, v as readListing } from "./types-BWDupDnP.js";
2
- export { Config, DEFAULT_API_KEY_ENV, DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_TOKENS, DEFAULT_MODELS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DISCOVERY_TTL_MS, PROVIDER, PUBLIC_BASE_URL, SiliconFlowAdapter, apply, discoverChatModels, inferInputModalities, inject, listingUrl, name, readListing, resolveAdapterOptions };
1
+ import { _ as discoverChatModels, a as PUBLIC_BASE_URL, c as inject, d as DEFAULT_CONTEXT_WINDOW, f as DEFAULT_MAX_TOKENS, g as inferInputModalities, h as SiliconFlowAdapter, i as PROVIDER, l as name, m as DISCOVERY_TTL_MS, n as DEFAULT_API_KEY_ENV, o as apply, p as DEFAULT_STREAM_IDLE_TIMEOUT_MS, r as DEFAULT_MODELS, s as assembleSettingsSection, t as Config, u as resolveAdapterOptions, v as listingUrl, y as readListing } from "./types-IqSaZRz4.js";
2
+ export { Config, DEFAULT_API_KEY_ENV, DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_TOKENS, DEFAULT_MODELS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DISCOVERY_TTL_MS, PROVIDER, PUBLIC_BASE_URL, SiliconFlowAdapter, apply, assembleSettingsSection, discoverChatModels, inferInputModalities, inject, listingUrl, name, readListing, resolveAdapterOptions };
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Single-build cross-era compatibility for the DeepSeek Harness dsh family.
3
+ *
4
+ * One published plugin build must run inside TWO dsh runtime closures:
5
+ *
6
+ * - the NEW era (dsh 0.1.2+ LLM family, first shipped 2026-08-30):
7
+ * `ToolCallId` brand, `version: 1` credentials layout (`refs:` nesting,
8
+ * unknown top-level keys hard-fail), settings installed via the service
9
+ * method `installSection`, model discovery via `LlmModelDiscoveryOperation`
10
+ * (extends `LlmModelDiscoveryRequest` with optional `signal`).
11
+ * - the OLD era (dsh-llm 0.1.1 and earlier; credentials-local 0.1.0):
12
+ * `CallId` brand, flat top-level credentials (no `version` key),
13
+ * settings installed via the module function `installSettingsSection`,
14
+ * model discovery via `LlmModelDiscoveryRequest` carrying `signal`.
15
+ *
16
+ * Compatibility rules this module relies on, established against real
17
+ * published packages (npm tarballs), not assumptions:
18
+ *
19
+ * 1. ESM link-time export validation only applies to NAMED imports
20
+ * (`import { ToolCallId }`). A missing named export throws SyntaxError at
21
+ * link time and the module never evaluates — exactly the reported
22
+ * `SyntaxError: … does not provide an export named 'CallId'`. A NAMESPACE
23
+ * import (`import * as llm`) links unconditionally; missing members are
24
+ * simply `undefined` at runtime. Every era-sensitive value therefore comes
25
+ * from the namespace object, never from a named import.
26
+ * 2. Exports BOTH eras ship (verified in 0.1.1-rc.2 and 0.1.5-rc.2):
27
+ * EMPTY_RESPONSE_CODE, LlmError, LlmAdapter, ProviderRequestId,
28
+ * QUOTA_EXCEEDED_CODE, RetryPolicySchema, assertUsableApiKey,
29
+ * resolveRetryPolicy, attributionHeaders, isQuotaExceededError,
30
+ * isContextWindowExceededError, CONTEXT_WINDOW_EXCEEDED_CODE. Named
31
+ * imports of these stay safe in both eras.
32
+ * 3. `dsh-util-values` postdates the OLD era entirely (404 there); its one
33
+ * consumer in this plugin, `deepEqualJson`, is inlined below.
34
+ *
35
+ * The era probe: `typeof ns.ToolCallId === 'function'`. NEW era exports
36
+ * ToolCallId; OLD era exports CallId instead. One probe classifies the
37
+ * closure and stays stable under future renames.
38
+ *
39
+ * @module dsh-llm-siliconflow/dsh-era
40
+ */
41
+ import type { ToolCallId as ToolCallIdType } from '@deepseek-ai/dsh-llm';
42
+ /**
43
+ * The raw namespace, exported for era-sensitive call sites. Typed loosely:
44
+ * the OLD-era variant of a member is unknown to the NEW-era .d.ts and vice
45
+ * versa, so era-shape access happens through this record, never the typed
46
+ * import.
47
+ */
48
+ export declare const dshLLM: Record<string, unknown>;
49
+ /** True when the running dsh closure is the NEW era (ToolCallId present). */
50
+ export declare const isNewEra: boolean;
51
+ /**
52
+ * Construct a provider tool-call id brand for the running era: `ToolCallId`
53
+ * in the NEW era, `CallId` in the OLD. The constructor is resolved at call
54
+ * time from the live namespace; a closure exposing neither fails loud.
55
+ *
56
+ * @param raw - the raw provider id, e.g. `block.callId ?? ''`.
57
+ * @returns the era-correct branded id.
58
+ */
59
+ export declare function toolCallIdOf(raw: string): ToolCallIdType;
60
+ /**
61
+ * Structural JSON equality over plain values (the shape settings documents
62
+ * and retry policies are made of). Inlined from dsh-util-values so the plugin
63
+ * carries no dependency on a NEW-era-only package: the OLD-era closure cannot
64
+ * even install dsh-util-values, which used to break npm resolution of this
65
+ * plugin under old dsh homes.
66
+ *
67
+ * Owned here over the value domain the plugin actually observes (plain JSON
68
+ * documents): recursion by key set, so object member order never matters —
69
+ * the upstream package compares structurally, and JSON.stringify would make
70
+ * order significant, corrupting that contract.
71
+ *
72
+ * @param a - one plain JSON value.
73
+ * @param b - another plain JSON value.
74
+ * @returns true when both represent the same JSON value.
75
+ */
76
+ export declare function deepEqualJson(a: unknown, b: unknown): boolean;
77
+ /**
78
+ * The era-neutral discovery-request shape one callback can serve both eras
79
+ * from: the fields the SiliconFlow discovery callback reads. In the OLD era
80
+ * the caller passes `LlmModelDiscoveryRequest` (signal present as a field);
81
+ * in the NEW era `LlmModelDiscoveryOperation` (same field, optional).
82
+ */
83
+ export interface ModelDiscoveryShape {
84
+ /** Route being edited, when it edits an existing one. */
85
+ provider?: string;
86
+ /** Endpoint to interrogate. */
87
+ baseURL?: string;
88
+ /** Wire protocol the endpoint speaks, when the draft names one. */
89
+ api?: string;
90
+ /** Credential for this interrogation alone; never stored by the harness. */
91
+ apiKey?: string;
92
+ /** Caller cancellation; present in both eras, optional in the new one. */
93
+ signal?: AbortSignal;
94
+ }
95
+ /**
96
+ * The era-neutral settings-hooks shape. The OLD and NEW installers take
97
+ * identical hook objects; only the mount point moved between eras.
98
+ * Parameterized on the entry type so it flows through without variance
99
+ * friction against exactOptionalPropertyTypes.
100
+ */
101
+ export interface EraSettingsHooks<T> {
102
+ setSource: (source: () => T) => void;
103
+ onChange: () => void;
104
+ }
105
+ /**
106
+ * Structural mirror of the NEW-era settings namespace input type (a lowercase
107
+ * hyphenated identifier). Declared locally so this plugin typechecks under the
108
+ * OLD-era closure, where `@deepseek-ai/dsh-settings` exports a differently
109
+ * shaped `SettingsNamespace`; the namespace value itself is identical either way.
110
+ */
111
+ export type SettingsNamespaceInput = string;
112
+ /**
113
+ * The settings package namespace as a plain record. The OLD era exports
114
+ * installSettingsSection as a MODULE-LEVEL function (it performs its own
115
+ * ctx.inject(['settings']) internally); the NEW era dropped that export and
116
+ * moved the installer onto the service class as installSection. Era probes
117
+ * must consult BOTH surfaces: the module namespace for the old path, the
118
+ * service object for the new one — probing only one proved wrong against
119
+ * real packages (verified against 0.1.1-rc.2 and 0.1.5-rc.2 tarballs).
120
+ */
121
+ export declare const dshSettings: Record<string, unknown>;
122
+ /**
123
+ * The era-union settings service seen by the plugin: the NEW era exposes
124
+ * installSection as a service method; the OLD era exported
125
+ * installSettingsSection as a module function (also re-exported by the
126
+ * service object). Signatures are identical (owner, ns, schema, entry, hooks)
127
+ * — only the mount point moved.
128
+ */
129
+ export interface EraSettingsService {
130
+ installSection?: <T>(owner: unknown, ns: SettingsNamespaceInput, schema: unknown, entry: T, hooks: EraSettingsHooks<T>) => void;
131
+ installSettingsSection?: <T>(owner: unknown, ns: SettingsNamespaceInput, schema: unknown, entry: T, hooks: EraSettingsHooks<T>) => void;
132
+ }
133
+ //# sourceMappingURL=dsh-era.d.ts.map
@@ -18,6 +18,7 @@ import type { Context } from '@deepseek-ai/cordis';
18
18
  import z from '@deepseek-ai/schemastery';
19
19
  import type { RetryPolicyConfig } from '@deepseek-ai/dsh-llm';
20
20
  import { type LaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment';
21
+ import { type EraSettingsHooks, type EraSettingsService } from './dsh-era.ts';
21
22
  import type { SiliconFlowCatalogModel, SiliconFlowConnectionOptions } from './adapter.ts';
22
23
  export { DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_TOKENS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DISCOVERY_TTL_MS, SiliconFlowAdapter, } from './adapter.ts';
23
24
  export { inferInputModalities } from './adapter.ts';
@@ -91,4 +92,29 @@ export type ResolvedSiliconFlowOptions = SiliconFlowConnectionOptions;
91
92
  */
92
93
  export declare function resolveAdapterOptions(config: Config, environment?: LaunchEnvironmentSnapshot): ResolvedSiliconFlowOptions;
93
94
  export declare function apply(ctx: Context, config: Config): void;
95
+ /**
96
+ * Install the settings through whichever surface the running era exposes.
97
+ *
98
+ * NEW era (dsh-settings 0.1.2+): the injected settings SERVICE carries the
99
+ * installer as the method `installSection` — invoked as a method so the
100
+ * service's registration state stays bound.
101
+ *
102
+ * OLD era (dsh-settings 0.1.1): the installer is the MODULE-LEVEL function
103
+ * `installSettingsSection` from '@deepseek-ai/dsh-settings' — it performs
104
+ * its own ctx.inject(['settings']) internally, so it is called directly with
105
+ * (owner, ns, schema, entry, hooks). No service argument is involved.
106
+ *
107
+ * Probing taught against real packages (0.1.1-rc.2 / 0.1.5-rc.2 tarballs):
108
+ * the OLD service object has NO installSettingsSection member, and the NEW
109
+ * module has NO installSettingsSection export — probing a single surface
110
+ * would throw on the other era. Both surfaces are checked in order; when
111
+ * the running dsh exposes neither, this fails loud instead of silently
112
+ * dropping the section.
113
+ */
114
+ export declare function assembleSettingsSection<T>(service: EraSettingsService, args: {
115
+ owner: Context;
116
+ ns: string;
117
+ schema: unknown;
118
+ entry: T;
119
+ } & EraSettingsHooks<T>): void;
94
120
  //# sourceMappingURL=index.d.ts.map
@@ -56,17 +56,36 @@ export declare function credentialsPath(home: string): string;
56
56
  /** The settings document path under a harness home. */
57
57
  export declare function settingsPath(home: string): string;
58
58
  /**
59
- * Read one credential reference from a comment-preserving document.
59
+ * Read one credential reference from a versioned or pre-release flat document.
60
60
  * @param path - the credentials document path.
61
- * @param keyEnv - the top-level reference name (e.g. `SILICONFLOW_API_KEY`).
62
- * @returns the stored value, or `undefined` when absent or the file is missing.
61
+ * @param keyEnv - the reference name to read (e.g. `SILICONFLOW_API_KEY`).
62
+ * @returns the stored value, or `undefined` when the reference is absent, the
63
+ * file is missing, or the document is not a recognizable layout.
63
64
  */
64
65
  export declare function readCredential(path: string, keyEnv: string): Promise<string | undefined>;
65
66
  /**
66
- * Set one credential reference, preserving every other entry and comment.
67
+ * Write one credential reference, preserving the document's own layout family
68
+ * and every other entry and comment.
69
+ *
70
+ * Cross-era rule (0.2.0-rc.3): which layout a FRESH document gets is decided
71
+ * by what both dsh eras can read, not by this build's version clock. The OLD
72
+ * credentials-local (0.1.0-rc.8) reads only the flat layout; the NEW one
73
+ * (0.1.1+) reads only version-1 but migrates a version-less flat document on
74
+ * its next boot. Flat is therefore the common denominator: a fresh or flat
75
+ * document stays flat under this write, and the running dsh upgrades it when
76
+ * that dsh is new-era. An existing versioned document keeps version-1 so a
77
+ * new-era user is never downgraded; an old-era user with a versioned document
78
+ * still has a file old dsh rejects — that file predates this rule and is
79
+ * repaired by re-running setup on a flat base or by hand.
80
+ *
81
+ * A top-level key a pre-fix release of this wizard left beside `version` is
82
+ * folded back under `refs`, so one run repairs the file its predecessor
83
+ * corrupted. An unrecognized document fails loud instead of being rewritten —
84
+ * a silent rewrite would hide why the running harness rejects it.
67
85
  * @param path - the credentials document path; created when absent.
68
- * @param keyEnv - the top-level reference name to write.
86
+ * @param keyEnv - the reference name to write.
69
87
  * @param key - the value.
88
+ * @throws when the document exists but is not a recognizable credentials layout.
70
89
  */
71
90
  export declare function writeCredential(path: string, keyEnv: string, key: string): Promise<void>;
72
91
  /**
@@ -1,11 +1,119 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
- import { CONTEXT_WINDOW_EXCEEDED_CODE, CallId, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, ProviderRequestId, QUOTA_EXCEEDED_CODE, RetryPolicySchema, assertUsableApiKey, attributionHeaders, isContextWindowExceededError, isQuotaExceededError, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
2
+ import * as llm from "@deepseek-ai/dsh-llm";
3
+ import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, ProviderRequestId, QUOTA_EXCEEDED_CODE, RetryPolicySchema, assertUsableApiKey, attributionHeaders, isContextWindowExceededError, isQuotaExceededError, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
3
4
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
4
5
  import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
5
- import { deepEqualJson, installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
6
+ import * as settingsModule from "@deepseek-ai/dsh-settings";
6
7
  import { MAX_TIMER_DELAY_MS, idleWatchdog, timeoutOf } from "@deepseek-ai/dsh-timeout";
7
8
  import { getOrCreateAnonymousUserId } from "@deepseek-ai/dsh-anonymous-user-id";
8
9
  import { EventSourceParserStream } from "eventsource-parser/stream";
10
+ //#region lib/types/dsh-era.js
11
+ /**
12
+ * Single-build cross-era compatibility for the DeepSeek Harness dsh family.
13
+ *
14
+ * One published plugin build must run inside TWO dsh runtime closures:
15
+ *
16
+ * - the NEW era (dsh 0.1.2+ LLM family, first shipped 2026-08-30):
17
+ * `ToolCallId` brand, `version: 1` credentials layout (`refs:` nesting,
18
+ * unknown top-level keys hard-fail), settings installed via the service
19
+ * method `installSection`, model discovery via `LlmModelDiscoveryOperation`
20
+ * (extends `LlmModelDiscoveryRequest` with optional `signal`).
21
+ * - the OLD era (dsh-llm 0.1.1 and earlier; credentials-local 0.1.0):
22
+ * `CallId` brand, flat top-level credentials (no `version` key),
23
+ * settings installed via the module function `installSettingsSection`,
24
+ * model discovery via `LlmModelDiscoveryRequest` carrying `signal`.
25
+ *
26
+ * Compatibility rules this module relies on, established against real
27
+ * published packages (npm tarballs), not assumptions:
28
+ *
29
+ * 1. ESM link-time export validation only applies to NAMED imports
30
+ * (`import { ToolCallId }`). A missing named export throws SyntaxError at
31
+ * link time and the module never evaluates — exactly the reported
32
+ * `SyntaxError: … does not provide an export named 'CallId'`. A NAMESPACE
33
+ * import (`import * as llm`) links unconditionally; missing members are
34
+ * simply `undefined` at runtime. Every era-sensitive value therefore comes
35
+ * from the namespace object, never from a named import.
36
+ * 2. Exports BOTH eras ship (verified in 0.1.1-rc.2 and 0.1.5-rc.2):
37
+ * EMPTY_RESPONSE_CODE, LlmError, LlmAdapter, ProviderRequestId,
38
+ * QUOTA_EXCEEDED_CODE, RetryPolicySchema, assertUsableApiKey,
39
+ * resolveRetryPolicy, attributionHeaders, isQuotaExceededError,
40
+ * isContextWindowExceededError, CONTEXT_WINDOW_EXCEEDED_CODE. Named
41
+ * imports of these stay safe in both eras.
42
+ * 3. `dsh-util-values` postdates the OLD era entirely (404 there); its one
43
+ * consumer in this plugin, `deepEqualJson`, is inlined below.
44
+ *
45
+ * The era probe: `typeof ns.ToolCallId === 'function'`. NEW era exports
46
+ * ToolCallId; OLD era exports CallId instead. One probe classifies the
47
+ * closure and stays stable under future renames.
48
+ *
49
+ * @module dsh-llm-siliconflow/dsh-era
50
+ */
51
+ /**
52
+ * The raw namespace, exported for era-sensitive call sites. Typed loosely:
53
+ * the OLD-era variant of a member is unknown to the NEW-era .d.ts and vice
54
+ * versa, so era-shape access happens through this record, never the typed
55
+ * import.
56
+ */
57
+ const dshLLM = llm;
58
+ dshLLM["ToolCallId"];
59
+ /**
60
+ * Construct a provider tool-call id brand for the running era: `ToolCallId`
61
+ * in the NEW era, `CallId` in the OLD. The constructor is resolved at call
62
+ * time from the live namespace; a closure exposing neither fails loud.
63
+ *
64
+ * @param raw - the raw provider id, e.g. `block.callId ?? ''`.
65
+ * @returns the era-correct branded id.
66
+ */
67
+ function toolCallIdOf(raw) {
68
+ const ctor = dshLLM["ToolCallId"] ?? dshLLM["CallId"];
69
+ if (ctor === void 0) throw new Error("neither ToolCallId nor CallId is exported by @deepseek-ai/dsh-llm");
70
+ return ctor(raw);
71
+ }
72
+ /**
73
+ * Structural JSON equality over plain values (the shape settings documents
74
+ * and retry policies are made of). Inlined from dsh-util-values so the plugin
75
+ * carries no dependency on a NEW-era-only package: the OLD-era closure cannot
76
+ * even install dsh-util-values, which used to break npm resolution of this
77
+ * plugin under old dsh homes.
78
+ *
79
+ * Owned here over the value domain the plugin actually observes (plain JSON
80
+ * documents): recursion by key set, so object member order never matters —
81
+ * the upstream package compares structurally, and JSON.stringify would make
82
+ * order significant, corrupting that contract.
83
+ *
84
+ * @param a - one plain JSON value.
85
+ * @param b - another plain JSON value.
86
+ * @returns true when both represent the same JSON value.
87
+ */
88
+ function deepEqualJson(a, b) {
89
+ if (a === b) return true;
90
+ if (a === null || b === null) return false;
91
+ if (typeof a !== typeof b) return false;
92
+ if (typeof a !== "object") return a === b;
93
+ const ao = a;
94
+ const bo = b;
95
+ if (Array.isArray(ao) !== Array.isArray(bo)) return false;
96
+ if (Array.isArray(ao) && Array.isArray(bo)) {
97
+ if (ao.length !== bo.length) return false;
98
+ return ao.every((item, i) => deepEqualJson(item, bo[i]));
99
+ }
100
+ const ak = Object.keys(ao);
101
+ const bk = Object.keys(bo);
102
+ if (ak.length !== bk.length) return false;
103
+ if (!ak.every((k) => k in bo)) return false;
104
+ return ak.every((k) => deepEqualJson(ao[k], bo[k]));
105
+ }
106
+ /**
107
+ * The settings package namespace as a plain record. The OLD era exports
108
+ * installSettingsSection as a MODULE-LEVEL function (it performs its own
109
+ * ctx.inject(['settings']) internally); the NEW era dropped that export and
110
+ * moved the installer onto the service class as installSection. Era probes
111
+ * must consult BOTH surfaces: the module namespace for the old path, the
112
+ * service object for the new one — probing only one proved wrong against
113
+ * real packages (verified against 0.1.1-rc.2 and 0.1.5-rc.2 tarballs).
114
+ */
115
+ const dshSettings = settingsModule;
116
+ //#endregion
9
117
  //#region lib/types/discovery.js
10
118
  /**
11
119
  * Interrogate the SiliconFlow (OpenAI-compatible) `GET /models` listing for
@@ -408,7 +516,7 @@ function closeBlock(block) {
408
516
  };
409
517
  case "tool-call": return {
410
518
  type: "tool-call",
411
- id: CallId(block.callId ?? ""),
519
+ id: toolCallIdOf(block.callId ?? ""),
412
520
  name: block.name ?? "",
413
521
  arguments: block.text
414
522
  };
@@ -523,7 +631,7 @@ async function* translate(payloads) {
523
631
  yield {
524
632
  type: "tool-call-delta",
525
633
  index: block.index,
526
- id: CallId(block.callId ?? ""),
634
+ id: toolCallIdOf(block.callId ?? ""),
527
635
  ...block.name !== void 0 ? { name: block.name } : {},
528
636
  argumentsDelta: fragment
529
637
  };
@@ -896,7 +1004,7 @@ var SiliconFlowAdapter = class extends LlmAdapter {
896
1004
  */
897
1005
  const name = "llm-siliconflow";
898
1006
  const inject = ["llm"];
899
- const NS = settingsNamespace("llm-siliconflow");
1007
+ const NS = "llm-siliconflow";
900
1008
  /** Credential reference this plugin reads by default, also used by the setup CLI. */
901
1009
  const DEFAULT_API_KEY_ENV = "SILICONFLOW_API_KEY";
902
1010
  /** The single provider route this plugin owns. */
@@ -1125,12 +1233,49 @@ function apply(ctx, config) {
1125
1233
  registration.replace([PROVIDER]);
1126
1234
  registeredPolicy = policy;
1127
1235
  };
1128
- installSettingsSection(ctx, NS, Config, config, {
1129
- setSource: (source) => {
1130
- current = source;
1131
- },
1132
- onChange: ensureRegistrationFacts
1236
+ ctx.inject(["settings"], (settingsCtx) => {
1237
+ assembleSettingsSection(settingsCtx.settings, {
1238
+ owner: ctx,
1239
+ ns: NS,
1240
+ schema: Config,
1241
+ entry: config,
1242
+ setSource: (source) => {
1243
+ current = source;
1244
+ },
1245
+ onChange: ensureRegistrationFacts
1246
+ });
1133
1247
  });
1134
1248
  }
1249
+ /**
1250
+ * Install the settings through whichever surface the running era exposes.
1251
+ *
1252
+ * NEW era (dsh-settings 0.1.2+): the injected settings SERVICE carries the
1253
+ * installer as the method `installSection` — invoked as a method so the
1254
+ * service's registration state stays bound.
1255
+ *
1256
+ * OLD era (dsh-settings 0.1.1): the installer is the MODULE-LEVEL function
1257
+ * `installSettingsSection` from '@deepseek-ai/dsh-settings' — it performs
1258
+ * its own ctx.inject(['settings']) internally, so it is called directly with
1259
+ * (owner, ns, schema, entry, hooks). No service argument is involved.
1260
+ *
1261
+ * Probing taught against real packages (0.1.1-rc.2 / 0.1.5-rc.2 tarballs):
1262
+ * the OLD service object has NO installSettingsSection member, and the NEW
1263
+ * module has NO installSettingsSection export — probing a single surface
1264
+ * would throw on the other era. Both surfaces are checked in order; when
1265
+ * the running dsh exposes neither, this fails loud instead of silently
1266
+ * dropping the section.
1267
+ */
1268
+ function assembleSettingsSection(service, args) {
1269
+ if (typeof service.installSection === "function") {
1270
+ service.installSection(args.owner, args.ns, args.schema, args.entry, args);
1271
+ return;
1272
+ }
1273
+ const oldInstall = dshSettings["installSettingsSection"];
1274
+ if (typeof oldInstall === "function") {
1275
+ oldInstall(args.owner, args.ns, args.schema, args.entry, args);
1276
+ return;
1277
+ }
1278
+ throw new Error("settings install: neither service.installSection (new era) nor module installSettingsSection (old era) is available");
1279
+ }
1135
1280
  //#endregion
1136
- export { listingUrl as _, PUBLIC_BASE_URL as a, name as c, DEFAULT_MAX_TOKENS as d, DEFAULT_STREAM_IDLE_TIMEOUT_MS as f, discoverChatModels as g, inferInputModalities as h, PROVIDER as i, resolveAdapterOptions as l, SiliconFlowAdapter as m, DEFAULT_API_KEY_ENV as n, apply as o, DISCOVERY_TTL_MS as p, DEFAULT_MODELS as r, inject as s, Config as t, DEFAULT_CONTEXT_WINDOW as u, readListing as v };
1281
+ export { discoverChatModels as _, PUBLIC_BASE_URL as a, inject as c, DEFAULT_CONTEXT_WINDOW as d, DEFAULT_MAX_TOKENS as f, inferInputModalities as g, SiliconFlowAdapter as h, PROVIDER as i, name as l, DISCOVERY_TTL_MS as m, DEFAULT_API_KEY_ENV as n, apply as o, DEFAULT_STREAM_IDLE_TIMEOUT_MS as p, DEFAULT_MODELS as r, assembleSettingsSection as s, Config as t, resolveAdapterOptions as u, listingUrl as v, readListing as y };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@siliconflow-official/dsh-llm-siliconflow",
3
3
  "description": "SiliconFlow (OpenAI-compatible) chat-completions adapter plugin for the DeepSeek Harness LLM seam",
4
- "version": "0.2.0-rc.1",
4
+ "version": "0.2.0-rc.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -44,14 +44,14 @@
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@deepseek-ai/cordis": "^4.0.1",
47
- "@deepseek-ai/dsh-anonymous-user-id": ">=0.0.1-rc.0",
48
- "@deepseek-ai/dsh-attachment": ">=0.0.1-rc.0",
49
- "@deepseek-ai/dsh-credentials": ">=0.0.1-rc.0",
50
- "@deepseek-ai/dsh-invariants": ">=0.0.1-rc.0",
51
- "@deepseek-ai/dsh-launch-environment": ">=0.0.1-rc.0",
52
- "@deepseek-ai/dsh-llm": ">=0.0.1-rc.0",
53
- "@deepseek-ai/dsh-settings": ">=0.0.1-rc.0",
54
- "@deepseek-ai/dsh-timeout": ">=0.0.1-rc.0"
47
+ "@deepseek-ai/dsh-anonymous-user-id": "*",
48
+ "@deepseek-ai/dsh-attachment": "*",
49
+ "@deepseek-ai/dsh-credentials": "*",
50
+ "@deepseek-ai/dsh-invariants": "*",
51
+ "@deepseek-ai/dsh-launch-environment": "*",
52
+ "@deepseek-ai/dsh-llm": "*",
53
+ "@deepseek-ai/dsh-settings": "*",
54
+ "@deepseek-ai/dsh-timeout": "*"
55
55
  },
56
56
  "dependencies": {
57
57
  "@deepseek-ai/schemastery": "^3.18.1",
@@ -59,18 +59,46 @@
59
59
  "yaml": "^2.9.0"
60
60
  },
61
61
  "devDependencies": {
62
- "@deepseek-ai/cordis": "^4.0.1",
63
- "@deepseek-ai/dsh-anonymous-user-id": ">=0.0.1-rc.0",
64
- "@deepseek-ai/dsh-attachment": ">=0.0.1-rc.0",
65
- "@deepseek-ai/dsh-credentials": ">=0.0.1-rc.0",
66
- "@deepseek-ai/dsh-invariants": ">=0.0.1-rc.0",
67
- "@deepseek-ai/dsh-launch-environment": ">=0.0.1-rc.0",
68
- "@deepseek-ai/dsh-llm": ">=0.0.1-rc.0",
69
- "@deepseek-ai/dsh-settings": ">=0.0.1-rc.0",
70
- "@deepseek-ai/dsh-timeout": ">=0.0.1-rc.0",
62
+ "@deepseek-ai/cordis": "^4.0.2",
63
+ "@deepseek-ai/dsh-anonymous-user-id": "0.1.5-rc.2",
64
+ "@deepseek-ai/dsh-attachment": "0.1.5-rc.2",
65
+ "@deepseek-ai/dsh-credentials": "0.1.5-rc.2",
66
+ "@deepseek-ai/dsh-invariants": "0.1.5-rc.2",
67
+ "@deepseek-ai/dsh-launch-environment": "0.1.5-rc.2",
68
+ "@deepseek-ai/dsh-llm": "0.1.5-rc.2",
69
+ "@deepseek-ai/dsh-settings": "0.1.5-rc.2",
70
+ "@deepseek-ai/dsh-timeout": "0.1.5-rc.2",
71
71
  "@types/node": "^22.0.0",
72
72
  "tsdown": "^0.22.0",
73
- "typescript": "^6.0.0",
74
- "unrun": "^0.3.1"
73
+ "typescript": "^6.0.0"
74
+ },
75
+ "peerDependenciesMeta": {
76
+ "@deepseek-ai/cordis": {
77
+ "optional": true
78
+ },
79
+ "@deepseek-ai/dsh-anonymous-user-id": {
80
+ "optional": true
81
+ },
82
+ "@deepseek-ai/dsh-attachment": {
83
+ "optional": true
84
+ },
85
+ "@deepseek-ai/dsh-credentials": {
86
+ "optional": true
87
+ },
88
+ "@deepseek-ai/dsh-invariants": {
89
+ "optional": true
90
+ },
91
+ "@deepseek-ai/dsh-launch-environment": {
92
+ "optional": true
93
+ },
94
+ "@deepseek-ai/dsh-llm": {
95
+ "optional": true
96
+ },
97
+ "@deepseek-ai/dsh-settings": {
98
+ "optional": true
99
+ },
100
+ "@deepseek-ai/dsh-timeout": {
101
+ "optional": true
102
+ }
75
103
  }
76
104
  }