dsh-workbuddy-connect 0.7.0 → 0.7.2

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/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as extractDisplayErrorMessage, B as CN_APP_VERSION_FILENAME, C as WORKBUDDY_VARIANTS, D as WORKBUDDY_UPDATE_PATH, F as prepareInternationalChatBody, G as resolveChatIdentity, H as chatUserAgent, I as regionOf, J as appUserAgent, K as validCliVersion, L as PROBE_EFFORT_CANDIDATES, M as normalizeCredits, N as parseModelCatalog, O as WorkBuddyUpstreamClient, P as prepareChatBody, Q as validAppVersion, R as probeModel, S as CN_VARIANT, U as fallbackChatIdentity, V as FALLBACK_CN_APP_VERSION, W as readCliVersion, X as readBundleVersion, Y as installedAppVersion, Z as resolveAppVersion, _ as desktopAuthCandidatesFor, a as readHostHeartbeat, b as atRestKeyProviderFor, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as WORKBUDDY_AUTH_FILENAME, g as defaultDesktopAuthPath, h as defaultDesktopAuthCandidates, i as processStartTimeMs, j as modelWithCurrentPromotion, k as classifyUpstreamError, l as FALLBACK_WORKBUDDY_AI_MODELS, m as WorkBuddyCredentialStore, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as WORKBUDDY_AUTH_FILE_ENV, q as WORKBUDDY_APP_VERSION_FILENAME, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS, v as parseWorkBuddyAuth, w as variantFor, x as AI_VARIANT, y as workbuddyOwnAuthPath, z as randomSentinel } from "./host-heartbeat-d_lBAk8t.js";
1
+ import { $ as validAppVersion, A as extractDisplayErrorMessage, B as randomSentinel, C as WORKBUDDY_VARIANTS, D as WORKBUDDY_UPDATE_PATH, F as prepareCnChatBody, G as readCliVersion, H as FALLBACK_CN_APP_VERSION, I as prepareInternationalChatBody, J as WORKBUDDY_APP_VERSION_FILENAME, K as resolveChatIdentity, L as regionOf, M as normalizeCredits, N as parseModelCatalog, O as WorkBuddyUpstreamClient, P as prepareChatBody, Q as resolveAppVersion, R as PROBE_EFFORT_CANDIDATES, S as CN_VARIANT, U as chatUserAgent, V as CN_APP_VERSION_FILENAME, W as fallbackChatIdentity, X as installedAppVersion, Y as appUserAgent, Z as readBundleVersion, _ as desktopAuthCandidatesFor, a as readHostHeartbeat, b as atRestKeyProviderFor, c as WORKBUDDY_CONNECT_VERSION, d as WorkBuddyCatalog, f as WORKBUDDY_AUTH_FILENAME, g as defaultDesktopAuthPath, h as defaultDesktopAuthCandidates, i as processStartTimeMs, j as modelWithCurrentPromotion, k as classifyUpstreamError, l as FALLBACK_WORKBUDDY_AI_MODELS, m as WorkBuddyCredentialStore, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as WORKBUDDY_AUTH_FILE_ENV, q as validCliVersion, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as FALLBACK_WORKBUDDY_MODELS, v as parseWorkBuddyAuth, w as variantFor, x as AI_VARIANT, y as workbuddyOwnAuthPath, z as probeModel } from "./host-heartbeat-DJb8l_4O.js";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
4
4
  import { dirname, join, resolve } from "node:path";
@@ -10,6 +10,7 @@ import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completio
10
10
  import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
11
11
  import { createServer } from "node:http";
12
12
  import { Readable } from "node:stream";
13
+ import { fileURLToPath } from "node:url";
13
14
  //#region src/catalog-store.ts
14
15
  /**
15
16
  * The last catalog that actually loaded, kept per variant and per account.
@@ -407,11 +408,21 @@ function withCatalogDisplay(name, info) {
407
408
  * It is never probed — disabling thinking is a separate capability, and the
408
409
  * per-model acceptance of `off` cannot be inferred from the row's shape.
409
410
  *
411
+ * The CN endpoint vetoes `off` further, on models declaring `onlyReasoning`
412
+ * (#87) — see {@link region} below. That rule is deliberately region-scoped:
413
+ * the contradiction it resolves was measured on the CN catalog, and applying
414
+ * it everywhere would have changed which levels the international picker
415
+ * offers, which is outside this issue.
416
+ *
410
417
  * The offered set is described internally as "verified accepted", never as
411
418
  * "verified effective": acceptance proves the upstream did not reject the
412
419
  * spelling, not that it changes what the model does.
420
+ *
421
+ * @param region - the endpoint this descriptor is for. Decides only whether
422
+ * the `onlyReasoning` veto applies; every other rule is shared. Passed from
423
+ * the variant descriptor, never inferred from a model id or a name.
413
424
  */
414
- function reasoningFields(info, observed) {
425
+ function reasoningFields(info, observed, region = "cn") {
415
426
  const reasoning = info.reasoning;
416
427
  if (reasoning === void 0 || reasoning.supports !== true) return { reasoning: false };
417
428
  const declared = reasoning.supportedEfforts;
@@ -420,7 +431,7 @@ function reasoningFields(info, observed) {
420
431
  return {
421
432
  reasoning: true,
422
433
  thinkingLevelMap: {
423
- off: reasoning.canDisableThinking === true && declared !== void 0 && declared.length > 0 ? "off" : null,
434
+ off: reasoning.canDisableThinking === true && (region !== "cn" || reasoning.onlyReasoning !== true) && declared !== void 0 && declared.length > 0 ? "off" : null,
424
435
  minimal: null,
425
436
  low: efforts.includes("low") ? "low" : null,
426
437
  medium: efforts.includes("medium") ? "medium" : null,
@@ -431,7 +442,7 @@ function reasoningFields(info, observed) {
431
442
  };
432
443
  }
433
444
  /** Build one pi-ai model descriptor pointing at the loopback shim. */
434
- function toPiModel(info, baseUrl, observed, providerId = WORKBUDDY_PROVIDER) {
445
+ function toPiModel(info, baseUrl, observed, providerId, region) {
435
446
  return {
436
447
  id: info.id,
437
448
  name: info.name,
@@ -439,7 +450,7 @@ function toPiModel(info, baseUrl, observed, providerId = WORKBUDDY_PROVIDER) {
439
450
  provider: providerId,
440
451
  baseUrl,
441
452
  input: info.supportsImages === true ? ["text", "image"] : ["text"],
442
- ...reasoningFields(info, observed),
453
+ ...reasoningFields(info, observed, region),
443
454
  cost: NO_COST,
444
455
  contextWindow: info.contextWindow,
445
456
  maxTokens: info.maxTokens,
@@ -462,9 +473,10 @@ function createWorkBuddyAdapter(options) {
462
473
  const { shim, store, catalog, resolveAttachments, resolveImageAccess, observe, hidden } = options;
463
474
  const providerId = options.providerId ?? "workbuddy";
464
475
  const displayName = options.displayName ?? "WorkBuddy";
476
+ const region = options.region ?? "cn";
465
477
  const buildModels = () => {
466
478
  const baseUrl = `${shim.baseUrl()}/v1`;
467
- return catalog.current().map((info) => toPiModel(info, baseUrl, observe?.(info.id), providerId));
479
+ return catalog.current().map((info) => toPiModel(info, baseUrl, observe?.(info.id), providerId, region));
468
480
  };
469
481
  const provider = {
470
482
  ...createProvider({
@@ -1111,6 +1123,7 @@ var WorkBuddyProbeService = class {
1111
1123
  try {
1112
1124
  const outcome = await probeModel({
1113
1125
  send,
1126
+ region: this.options.region,
1114
1127
  ...this.options.sentinel === void 0 ? {} : { sentinel: this.options.sentinel }
1115
1128
  });
1116
1129
  if (this.options.account() !== account) return {
@@ -1143,6 +1156,79 @@ var WorkBuddyProbeService = class {
1143
1156
  }
1144
1157
  };
1145
1158
  //#endregion
1159
+ //#region src/pi-ai-guard.ts
1160
+ /**
1161
+ * Confirm the pi-ai copy this plugin actually loaded is the generation the DSH
1162
+ * 0.2.0-rc.2 host was built against.
1163
+ *
1164
+ * #92's failure mode: a pnpm profile with `nodeLinker: hoisted` and
1165
+ * `autoInstallPeers: false` installs no peer for us, so a co-installed
1166
+ * plugin's older pi-ai sits at the profile root and wins bare-specifier
1167
+ * resolution. The host then adds a system message (pi-ai 0.87's
1168
+ * `collapseSystemMessages`) and the older copy answers `estimateMessageTokens`
1169
+ * by reading `.length` off a block it does not model — every request dies at
1170
+ * `PI_AI_ERROR` before any upstream HTTP. Declaring pi-ai as a regular
1171
+ * dependency (package.json) is the fix; this module exists so a future
1172
+ * regression reports *what* it loaded instead of that opaque crash.
1173
+ *
1174
+ * It is deliberately a warning, not a throw. The plugin is mid-load at this
1175
+ * point, and a hard failure here would take down a host that may still be able
1176
+ * to serve other providers. The message is the product; the operator decides.
1177
+ *
1178
+ * @module dsh-workbuddy-connect/pi-ai-guard
1179
+ */
1180
+ /** The pi-ai generation this plugin is built and tested against. */
1181
+ const REQUIRED_PI_AI_VERSION = "0.87.1";
1182
+ /**
1183
+ * Read the version of the pi-ai copy Node resolves for *this* module.
1184
+ *
1185
+ * The package's exports map does not open `./package.json`, so the manifest
1186
+ * cannot be imported. Instead the module's own specifier is resolved and the
1187
+ * resulting file is walked up to the directory whose `package.json` names
1188
+ * `@earendil-works/pi-ai`. That reads the copy actually loaded rather than a
1189
+ * guess from the install tree.
1190
+ *
1191
+ * @returns The resolved version, or undefined when it cannot be determined.
1192
+ */
1193
+ function resolvedPiAiVersion() {
1194
+ let entry;
1195
+ try {
1196
+ entry = import.meta.resolve("@earendil-works/pi-ai");
1197
+ } catch {
1198
+ return;
1199
+ }
1200
+ let dir = dirname(fileURLToPath(entry));
1201
+ for (let step = 0; step < 5; step += 1) {
1202
+ try {
1203
+ const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
1204
+ if (manifest.name === "@earendil-works/pi-ai" && typeof manifest.version === "string") return manifest.version;
1205
+ } catch {}
1206
+ const parent = dirname(dir);
1207
+ if (parent === dir) break;
1208
+ dir = parent;
1209
+ }
1210
+ }
1211
+ /**
1212
+ * One line describing a pi-ai copy that is not the required generation, or
1213
+ * undefined when the resolved version matches or cannot be read.
1214
+ *
1215
+ * The cause is left open on purpose. A hoisted profile root is the case #92
1216
+ * measured, but an updater or installer can just as easily leave the wrong copy
1217
+ * in place, and this diagnostic cannot tell them apart from a version string.
1218
+ * Naming both versions and the remedy is what the reader can act on; naming only
1219
+ * versions and the fix also keeps any filesystem path out of logs and reports.
1220
+ *
1221
+ * Carries no plugin prefix: callers log it under their own.
1222
+ *
1223
+ * @param resolved - Version to report on. Read from the live module graph when
1224
+ * omitted; pass it explicitly to describe a hypothetical copy.
1225
+ */
1226
+ function piAiVersionWarning(resolved) {
1227
+ const version = arguments.length === 0 ? resolvedPiAiVersion() : resolved;
1228
+ if (version === void 0 || version === "0.87.1") return void 0;
1229
+ return `loaded @earendil-works/pi-ai ${version}, but this plugin is built for ${REQUIRED_PI_AI_VERSION}. The wrong copy is being resolved — check the profile's pi-ai install (a pnpm profile with nodeLinker "hoisted" and autoInstallPeers false can expose another package's older copy). Reinstalling the plugin so it installs its own ${REQUIRED_PI_AI_VERSION} copy resolves it`;
1230
+ }
1231
+ //#endregion
1146
1232
  //#region src/web-status.ts
1147
1233
  /** Redact token-like content before it crosses to the browser. */
1148
1234
  function safeMessage(error) {
@@ -1940,6 +2026,7 @@ function createVariantRuntime(config, variant, current, identityOf, accountOf, k
1940
2026
  catalog,
1941
2027
  credentials: store,
1942
2028
  client,
2029
+ region: variant.id === CN_VARIANT.id ? "cn" : "global",
1943
2030
  consent: () => current().probeConsent === true,
1944
2031
  account: () => identityOf(variant.id)
1945
2032
  }),
@@ -2030,6 +2117,7 @@ async function startVariant(ctx, runtime) {
2030
2117
  const workbuddy = createWorkBuddyAdapter({
2031
2118
  providerId: variant.id,
2032
2119
  displayName: variant.displayName,
2120
+ region: variant.region,
2033
2121
  shim,
2034
2122
  store,
2035
2123
  catalog,
@@ -2074,6 +2162,8 @@ async function startVariant(ctx, runtime) {
2074
2162
  * working without re-registering the provider.
2075
2163
  */
2076
2164
  function apply(ctx, config) {
2165
+ const piAiWarning = piAiVersionWarning();
2166
+ if (piAiWarning !== void 0) ctx.logger.warn(`dsh-workbuddy-connect: ${piAiWarning}`);
2077
2167
  let current = () => config;
2078
2168
  /** Timers and in-flight work belonging to this plugin instance. */
2079
2169
  let stopped = false;
@@ -2435,4 +2525,4 @@ function apply(ctx, config) {
2435
2525
  });
2436
2526
  }
2437
2527
  //#endregion
2438
- export { AI_VARIANT, CN_APP_VERSION_FILENAME, CN_VARIANT, Config, FALLBACK_CN_APP_VERSION, FALLBACK_WORKBUDDY_AI_MODELS, FALLBACK_WORKBUDDY_MODELS, PROBE_EFFORT_CANDIDATES, WORKBUDDY_AI_SETTINGS_NS, WORKBUDDY_APP_VERSION_FILENAME, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CATALOG_FILENAME, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROBE_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_VARIANTS, WORKBUDDY_VISIBILITY_FILENAME, WorkBuddyCatalog, WorkBuddyCatalogStore, WorkBuddyCredentialStore, WorkBuddyProbeService, WorkBuddyProbeStore, WorkBuddyUpstreamClient, WorkBuddyVisibilityStore, appUserAgent, apply, chatUserAgent, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthPath, desktopAuthCandidatesFor, fallbackChatIdentity, fingerprintModel, inject, installedAppVersion, isHeartbeatProcessAlive, modelWithCurrentPromotion, name, normalizeCredits, parseModelCatalog, parseWorkBuddyAuth, prepareChatBody, prepareInternationalChatBody, probeModel, processStartTimeMs, randomSentinel, readBundleVersion, readCliVersion, readHostHeartbeat, regionOf, resolveAppVersion, resolveChatIdentity, validAppVersion, validCliVersion, variantFor, visibilityAccountOf, workbuddyCatalogPath, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, workbuddyProbePath, workbuddyVisibilityPath };
2528
+ export { AI_VARIANT, CN_APP_VERSION_FILENAME, CN_VARIANT, Config, FALLBACK_CN_APP_VERSION, FALLBACK_WORKBUDDY_AI_MODELS, FALLBACK_WORKBUDDY_MODELS, PROBE_EFFORT_CANDIDATES, WORKBUDDY_AI_SETTINGS_NS, WORKBUDDY_APP_VERSION_FILENAME, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_CATALOG_FILENAME, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROBE_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WORKBUDDY_VARIANTS, WORKBUDDY_VISIBILITY_FILENAME, WorkBuddyCatalog, WorkBuddyCatalogStore, WorkBuddyCredentialStore, WorkBuddyProbeService, WorkBuddyProbeStore, WorkBuddyUpstreamClient, WorkBuddyVisibilityStore, appUserAgent, apply, chatUserAgent, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthPath, desktopAuthCandidatesFor, fallbackChatIdentity, fingerprintModel, inject, installedAppVersion, isHeartbeatProcessAlive, modelWithCurrentPromotion, name, normalizeCredits, parseModelCatalog, parseWorkBuddyAuth, prepareChatBody, prepareCnChatBody, prepareInternationalChatBody, probeModel, processStartTimeMs, randomSentinel, readBundleVersion, readCliVersion, readHostHeartbeat, regionOf, resolveAppVersion, resolveChatIdentity, validAppVersion, validCliVersion, variantFor, visibilityAccountOf, workbuddyCatalogPath, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath, workbuddyProbePath, workbuddyVisibilityPath };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-workbuddy-connect",
3
3
  "displayName": "DSH WorkBuddy Connect",
4
4
  "description": "将 WorkBuddy 桌面 App 包含的模型自动接入 DeepSeek Harness — bring WorkBuddy desktop app models into DeepSeek Harness with zero configuration.",
5
- "version": "0.7.0",
5
+ "version": "0.7.2",
6
6
  "author": "corrinehu",
7
7
  "keywords": [
8
8
  "dsh-plugin",
@@ -66,17 +66,19 @@
66
66
  "platform": "web"
67
67
  }
68
68
  },
69
+ "dependencies": {
70
+ "@earendil-works/pi-ai": "0.87.1"
71
+ },
69
72
  "peerDependencies": {
70
73
  "@deepseek-ai/cordis": "^4.0.2",
71
- "@deepseek-ai/dsh-atomic-write": "0.2.0-rc.1 || 0.2.0-rc.2",
72
- "@deepseek-ai/dsh-attachment": "0.2.0-rc.1 || 0.2.0-rc.2",
73
- "@deepseek-ai/dsh-home-paths": "0.2.0-rc.1 || 0.2.0-rc.2",
74
- "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.1 || 0.2.0-rc.2",
75
- "@deepseek-ai/dsh-llm": "0.2.0-rc.1 || 0.2.0-rc.2",
76
- "@deepseek-ai/dsh-llm-pi-ai": "0.2.0-rc.1 || 0.2.0-rc.2",
77
- "@deepseek-ai/dsh-settings": "0.2.0-rc.1 || 0.2.0-rc.2",
74
+ "@deepseek-ai/dsh-atomic-write": "0.2.0-rc.2",
75
+ "@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
76
+ "@deepseek-ai/dsh-home-paths": "0.2.0-rc.2",
77
+ "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
78
+ "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
79
+ "@deepseek-ai/dsh-llm-pi-ai": "0.2.0-rc.2",
80
+ "@deepseek-ai/dsh-settings": "0.2.0-rc.2",
78
81
  "@deepseek-ai/schemastery": "^3.18.2",
79
- "@earendil-works/pi-ai": "^0.85.1 || ^0.87.1",
80
82
  "react": "^18.2.0"
81
83
  },
82
84
  "devDependencies": {
@@ -100,7 +102,7 @@
100
102
  "@deepseek-ai/dsh-llm-pi-ai": "0.2.0-rc.2",
101
103
  "@deepseek-ai/dsh-settings": "0.2.0-rc.2",
102
104
  "@deepseek-ai/schemastery": "^3.18.2",
103
- "@earendil-works/pi-ai": "^0.87.1",
105
+ "@earendil-works/pi-ai": "0.87.1",
104
106
  "@types/node": "^22.20.0",
105
107
  "@types/react": "~18.3.1",
106
108
  "@types/react-test-renderer": "^18.3.1",