@deepseek-ai/dsh-anonymous-user-id 0.0.1-rc.5

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 ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, DeepSeek
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/identity/anonymous-user-id/README.md
5
+ README.md: fb9d8f8046f41aed7c0bc8aa8cede46f9bb8c93b
6
+ README.zh.md: 738289fd6b132e3fd5b6cc5a89dbe8ca1ffb5e42
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @deepseek-ai/dsh-anonymous-user-id
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ Shared anonymous identity for session telemetry, direct feedback acknowledgement, and DeepSeek provider requests. `getOrCreateAnonymousUserId()` returns a random UUID v4 scoped to one harness home, persisted as the bare line `$DSH_HOME/.anonymous-user-id` (`~/.dsh/.anonymous-user-id` when `DSH_HOME` is unset). The OpenTelemetry backend reports it as Resource `user.id`; `/feedback` includes the same value in its acknowledgement; and `dsh-llm-deepseek` sends it as `x-deepseek-harness-user-id`, allowing the receiving systems to correlate records without independently generated identities.
6
+
7
+ The identity is never derived from the hostname, network address, git remote, or another identifying source. Deleting `.anonymous-user-id` resets the identity on the next process launch. Separate harness homes have separate identities.
8
+
9
+ ## Storage contract
10
+
11
+ Reads and writes are synchronous because both boot-time telemetry construction and direct command execution need one API. The result is memoized per resolved file path for the process lifetime. A first writer uses exclusive creation and a concurrent loser adopts the persisted winner; a corrupt file is replaced. Persistence is best-effort, so an unwritable home still receives a process-local UUID rather than blocking telemetry or feedback.
12
+
13
+ ## Composition
14
+
15
+ This package is a shared library, not a Cordis plugin. Consumers import `getOrCreateAnonymousUserId()` directly. Its invariant companion is intentionally empty because the package owns no event stream or public mutable relation that can be checked without creating the identity as a side effect. `DSH_TELEMETRY_DISABLED` stops telemetry export only; it does not suppress direct feedback acknowledgement or the DeepSeek provider header.
16
+
17
+ ## Model Experience
18
+
19
+ None, as the identifier reaches DeepSeek only as model-hidden HTTP transport metadata and never enters the request body, prompt, or model-visible content.
20
+
21
+ #### KV Cache effect
22
+
23
+ None; the transport header changes neither tokens nor the model-visible prefix.
24
+
25
+ ## Known Limitations and Deferred Work
26
+
27
+ - **No recovery after deletion** — loss mints a new anonymous identity by design; recovery would require stable derivation material that weakens anonymity.
28
+ - **Best-effort concurrency** — a reader landing in the narrow interval between a concurrent process's exclusive create and completed write can use a different in-memory UUID for that run; later launches converge on the persisted value.
29
+ - **No cross-home identity** — different `$DSH_HOME` values cannot be correlated.
30
+ - **Configured DeepSeek gateways receive the id** — `dsh-llm-deepseek` sends the stable header to its resolved `baseURL`, including deployment overrides, independently of telemetry sharing mode.
package/README.zh.md ADDED
@@ -0,0 +1,30 @@
1
+ # @deepseek-ai/dsh-anonymous-user-id
2
+
3
+ [English](README.md) | 中文
4
+
5
+ 会话遥测、直接反馈确认与 DeepSeek 提供方请求共用的匿名身份。`getOrCreateAnonymousUserId()` 返回一个限定于单个 harness home 的随机 UUID v4,并以裸行形式持久化到 `$DSH_HOME/.anonymous-user-id`(未设置 `DSH_HOME` 时为 `~/.dsh/.anonymous-user-id`)。OpenTelemetry 后端将其作为 Resource 的 `user.id` 上报;`/feedback` 在确认文本中包含同一个值;`dsh-llm-deepseek` 则通过 `x-deepseek-harness-user-id` 发送该值,使接收系统无需独立生成身份即可关联记录。
6
+
7
+ 该身份绝不从 hostname、网络地址、git remote 或其他可用于识别身份的来源派生。删除 `.anonymous-user-id` 后,下次启动进程时会重置身份。不同 harness home 拥有不同身份。
8
+
9
+ ## 存储约定
10
+
11
+ 读写采用同步方式,因为启动时构造遥测和直接执行命令都需要使用同一个 API。结果在进程生命周期内按解析后的文件路径缓存。首个写入方采用独占创建;并发竞争中失败的一方会采用已持久化的胜出值。损坏的文件会被替换。持久化采用 best-effort,因此即使 home 不可写,系统仍会返回进程本地 UUID,而不会阻塞遥测或反馈。
12
+
13
+ ## 组合
14
+
15
+ 本包是共享库,并非 Cordis 插件。消费方直接导入 `getOrCreateAnonymousUserId()`。其不变式伴生插件刻意留空,因为本包既不拥有事件流,也不拥有任何可以在不触发创建身份这一副作用的情况下检查的公开可变关系。`DSH_TELEMETRY_DISABLED` 只会停止遥测导出,不会禁止直接反馈确认或 DeepSeek 提供方标头。
16
+
17
+ ## 模型体验
18
+
19
+ 无,因为该标识符只会作为模型不可见的 HTTP 传输元数据发送给 DeepSeek,绝不会进入请求正文、提示词或模型可见内容。
20
+
21
+ #### KV Cache 影响
22
+
23
+ 无;该传输标头既不会改变 token,也不会改变模型可见前缀。
24
+
25
+ ## 已知限制与暂缓工作
26
+
27
+ - **删除后无法恢复**:身份丢失后会按设计生成新的匿名身份;若要恢复身份,就需要稳定的派生材料,这会削弱匿名性。
28
+ - **Best-effort 并发**:如果读取方恰好落在并发进程完成独占创建但尚未写完的狭窄时间窗内,本次运行可能使用不同的内存 UUID;后续启动会收敛到已持久化的值。
29
+ - **没有跨 home 身份**:不同 `$DSH_HOME` 值之间无法关联。
30
+ - **已配置的 DeepSeek gateway 会收到该 id**:`dsh-llm-deepseek` 会把稳定标头发送至解析后的 `baseURL`(包括部署覆盖),且不受遥测共享模式影响。
package/lib/index.js ADDED
@@ -0,0 +1,78 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
5
+ //#region lib/types/index.js
6
+ /**
7
+ * Per-harness-home anonymous user id shared by telemetry and feedback.
8
+ *
9
+ * The id is a random UUID persisted as a bare line in `.anonymous-user-id` inside the
10
+ * harness home resolved by {@link resolveDshHome} (`$DSH_HOME` > `~/.dsh`),
11
+ * and never derived from the hostname, network address, git remote, or any
12
+ * other identifying source. It is scoped to the harness home, not the
13
+ * machine: every process sharing one `$DSH_HOME` reports the same id, and
14
+ * deleting the file mints a fresh identity on the next launch.
15
+ *
16
+ * Reads and writes are synchronous so boot-time and command consumers can
17
+ * use one API. The result is memoized per resolved file path: one process
18
+ * touches the disk once, and a file deleted mid-run keeps the process's id
19
+ * until the next launch.
20
+ *
21
+ * @module @deepseek-ai/dsh-anonymous-user-id
22
+ */
23
+ /** File inside the harness home storing the id: a bare UUID line, no wrapper format. */
24
+ const ANONYMOUS_USER_ID_FILE_NAME = ".anonymous-user-id";
25
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
26
+ /** Process-lifetime memo keyed by resolved file path, so distinct test homes never share an id. */
27
+ const memo = /* @__PURE__ */ new Map();
28
+ /** Read a valid persisted id from the file, or `undefined` when absent/corrupt. */
29
+ function readPersistedId(file) {
30
+ let text;
31
+ try {
32
+ text = readFileSync(file, "utf8");
33
+ } catch {
34
+ return;
35
+ }
36
+ const value = text.trim();
37
+ return UUID_PATTERN.test(value) ? value : void 0;
38
+ }
39
+ /**
40
+ * Return the harness home's anonymous user id, creating and persisting one on
41
+ * first use. A concurrent first launch is settled by an exclusive-create
42
+ * write: the loser rereads the winner's id. (A reread landing in the winner's
43
+ * narrow create-to-write window can still yield two per-process ids for that
44
+ * run; the next launch converges on the persisted one.) Persistence is
45
+ * best-effort — a write failure (read-only home) still returns a usable id
46
+ * for the current run so feedback and telemetry are never blocked.
47
+ * @param options - home-location and UUID-generation seams.
48
+ * @returns the stable per-harness-home anonymous user id.
49
+ */
50
+ function getOrCreateAnonymousUserId(options = {}) {
51
+ const file = join(resolveDshHome(void 0, options.env ?? process.env), ANONYMOUS_USER_ID_FILE_NAME);
52
+ const cached = memo.get(file);
53
+ if (cached !== void 0) return cached;
54
+ let id = readPersistedId(file);
55
+ if (id === void 0) {
56
+ const created = (options.randomUUID ?? randomUUID)();
57
+ try {
58
+ mkdirSync(dirname(file), { recursive: true });
59
+ writeFileSync(file, `${created}\n`, {
60
+ encoding: "utf8",
61
+ flag: "wx"
62
+ });
63
+ id = created;
64
+ } catch {
65
+ id = readPersistedId(file);
66
+ if (id === void 0) {
67
+ try {
68
+ writeFileSync(file, `${created}\n`, "utf8");
69
+ } catch {}
70
+ id = created;
71
+ }
72
+ }
73
+ }
74
+ memo.set(file, id);
75
+ return id;
76
+ }
77
+ //#endregion
78
+ export { ANONYMOUS_USER_ID_FILE_NAME, getOrCreateAnonymousUserId };
@@ -0,0 +1,24 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@deepseek-ai/dsh-anonymous-user-id`.
4
+ * @module @deepseek-ai/dsh-anonymous-user-id/invariant
5
+ */
6
+ const PACKAGE_NAME = "@deepseek-ai/dsh-anonymous-user-id";
7
+ /** Cordis companion plugin name. */
8
+ const name = "anonymous-user-id-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: the API owns one private memo and one best-effort
13
+ * file, with no independent event stream or public mutable relation for a
14
+ * companion to compare without creating the identity as a side effect.
15
+ */
16
+ const install = () => {};
17
+ /**
18
+ * Register this package's invariant companion.
19
+ * @param ctx - Cordis context carrying the invariant service.
20
+ * @returns the installed registration's disposer after setup succeeds.
21
+ */
22
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
23
+ //#endregion
24
+ export { apply, inject, name };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Per-harness-home anonymous user id shared by telemetry and feedback.
3
+ *
4
+ * The id is a random UUID persisted as a bare line in `.anonymous-user-id` inside the
5
+ * harness home resolved by {@link resolveDshHome} (`$DSH_HOME` > `~/.dsh`),
6
+ * and never derived from the hostname, network address, git remote, or any
7
+ * other identifying source. It is scoped to the harness home, not the
8
+ * machine: every process sharing one `$DSH_HOME` reports the same id, and
9
+ * deleting the file mints a fresh identity on the next launch.
10
+ *
11
+ * Reads and writes are synchronous so boot-time and command consumers can
12
+ * use one API. The result is memoized per resolved file path: one process
13
+ * touches the disk once, and a file deleted mid-run keeps the process's id
14
+ * until the next launch.
15
+ *
16
+ * @module @deepseek-ai/dsh-anonymous-user-id
17
+ */
18
+ import type { Branded } from '@deepseek-ai/dsh-brand';
19
+ /** A harness-home-scoped anonymous user id (random UUID v4). */
20
+ export type AnonymousUserId = Branded<'AnonymousUserId'>;
21
+ /** File inside the harness home storing the id: a bare UUID line, no wrapper format. */
22
+ export declare const ANONYMOUS_USER_ID_FILE_NAME = ".anonymous-user-id";
23
+ /** Ambient hooks for locating and generating the id; every field has a default. */
24
+ export interface AnonymousUserIdOptions {
25
+ /** Environment consulted for `DSH_HOME`; defaults to `process.env`. */
26
+ env?: NodeJS.ProcessEnv;
27
+ /** UUID generator; defaults to `crypto.randomUUID` (test hook). */
28
+ randomUUID?: () => string;
29
+ }
30
+ /**
31
+ * Return the harness home's anonymous user id, creating and persisting one on
32
+ * first use. A concurrent first launch is settled by an exclusive-create
33
+ * write: the loser rereads the winner's id. (A reread landing in the winner's
34
+ * narrow create-to-write window can still yield two per-process ids for that
35
+ * run; the next launch converges on the persisted one.) Persistence is
36
+ * best-effort — a write failure (read-only home) still returns a usable id
37
+ * for the current run so feedback and telemetry are never blocked.
38
+ * @param options - home-location and UUID-generation seams.
39
+ * @returns the stable per-harness-home anonymous user id.
40
+ */
41
+ export declare function getOrCreateAnonymousUserId(options?: AnonymousUserIdOptions): AnonymousUserId;
42
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@deepseek-ai/dsh-anonymous-user-id`.
3
+ * @module @deepseek-ai/dsh-anonymous-user-id/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "anonymous-user-id-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@deepseek-ai/dsh-anonymous-user-id",
3
+ "description": "Shared anonymous user identity for DeepSeek Harness telemetry and feedback correlation",
4
+ "version": "0.0.1-rc.5",
5
+ "publishConfig": {
6
+ "access": "restricted"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/identity/anonymous-user-id"
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
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./src/*": "./src/*",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "lib/index.js",
30
+ "lib/invariant.js",
31
+ "lib/types/**/*.d.ts"
32
+ ],
33
+ "license": "BSD-3-Clause",
34
+ "peerDependencies": {
35
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.5",
36
+ "@deepseek-ai/dsh-brand": "^0.0.1-rc.5",
37
+ "@deepseek-ai/dsh-home-paths": "^0.0.1-rc.5",
38
+ "@deepseek-ai/cordis": "^4.0.1-rc.4"
39
+ },
40
+ "devDependencies": {
41
+ "@deepseek-ai/dsh-brand": "^0.0.1-rc.5",
42
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.5",
43
+ "@deepseek-ai/dsh-home-paths": "^0.0.1-rc.5",
44
+ "@deepseek-ai/cordis": "^4.0.1-rc.4"
45
+ }
46
+ }