@adaptiqai/deepseek-harness 0.2.4 → 0.2.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.
Files changed (2) hide show
  1. package/lib/index.js +115 -18
  2. package/package.json +2 -2
package/lib/index.js CHANGED
@@ -1275,6 +1275,22 @@ var str4 = (value) => typeof value === "string" && value.trim() !== "" ? value.t
1275
1275
  var count2 = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
1276
1276
  var DEFAULT_PREFERENCES = { rememberByDefault: true, welcomed: false };
1277
1277
  var PAGE = 50;
1278
+ var ATTACH_POLL_MS = 15e3;
1279
+ var ATTACH_BACKOFF_MAX_MS = 10 * 6e4;
1280
+ var ATTACH_DEADLINE_MS = 6 * 36e5;
1281
+ var MODEL_ROUTE_CODES = /* @__PURE__ */ new Set(["memory_route_required", "model_required", "model_mismatch"]);
1282
+ function attachBackoffMs(failures) {
1283
+ return Math.min(ATTACH_BACKOFF_MAX_MS, ATTACH_POLL_MS * 2 ** Math.min(Math.max(failures - 1, 0), 10));
1284
+ }
1285
+ function routeChoices(detail) {
1286
+ if (!Array.isArray(detail.models))
1287
+ return null;
1288
+ return detail.models.flatMap((raw) => {
1289
+ const entry = obj4(raw);
1290
+ const id = typeof raw === "string" ? raw : str4(entry?.id) ?? str4(entry?.model);
1291
+ return id === null || id === "" ? [] : [id];
1292
+ });
1293
+ }
1278
1294
  var TROUBLE_QUIET_MS = 6e4;
1279
1295
  var SESSION_BACKOFF_MAX_MS = 5 * 6e4;
1280
1296
  function sessionBackoffMs(failures) {
@@ -2699,7 +2715,10 @@ var Workspace = class {
2699
2715
  if (session !== void 0 && this.now() - session.remoteAt > 1e4)
2700
2716
  await this.loadSession(session).catch(() => {
2701
2717
  });
2702
- const target = model2 !== void 0 && this.catalog.some((entry) => entry.id === model2) ? model2 : this.targetModel(session) ?? null;
2718
+ const named = model2 !== void 0 && model2 !== "" && (this.catalog.length === 0 || this.catalog.some((entry) => entry.id === model2));
2719
+ const target = named ? model2 : this.targetModel(session) ?? null;
2720
+ if (target === null)
2721
+ return { model: null, modelName: null, items: [], nextCursor: null, hasMore: false, summary: null };
2703
2722
  const attached = new Set(session?.knowledge.map((item) => item.id) ?? []);
2704
2723
  const name2 = (id) => this.modelName(id);
2705
2724
  const read = async (store, limit, after) => {
@@ -2708,7 +2727,7 @@ var Workspace = class {
2708
2727
  params.set("query", query.trim().slice(0, 200));
2709
2728
  if (after !== void 0 && after !== "")
2710
2729
  params.set("cursor", after);
2711
- const body = await this.deps.api.json(`/v1/memories?${params}`, { headers: store === null ? {} : { "x-adaptiq-model": store } });
2730
+ const body = await this.deps.api.json(`/v1/memories?${params}`, { headers: { "x-adaptiq-model": store } });
2712
2731
  const items = (Array.isArray(body?.data) ? body.data : []).flatMap((raw) => {
2713
2732
  const memory = obj4(raw);
2714
2733
  return memory === null ? [] : [knowledgeItem(memory, store === target && attached.has(String(memory.memory_id)), { store, target, name: name2 })];
@@ -2718,13 +2737,13 @@ var Workspace = class {
2718
2737
  const here = await read(target, PAGE, cursor);
2719
2738
  await this.followCopies(target);
2720
2739
  let elsewhere = [];
2721
- if ((cursor === void 0 || cursor === "") && target !== null) {
2740
+ if (cursor === void 0 || cursor === "") {
2722
2741
  const others = this.catalog.filter((entry) => entry.id !== target && entry.status !== "disabled").slice(0, 3);
2723
2742
  const pages = await Promise.all(others.map((entry) => read(entry.id, 20).catch(() => ({ items: [], pagination: null }))));
2724
2743
  const seen = new Set(here.items.map((item) => item.id));
2725
2744
  const copied = new Set([...this.copies.values()].filter((copy) => copy.target === target && copy.status === "ready").map((copy) => copy.source));
2726
2745
  const names = new Set(here.items.filter((item) => item.fits).map((item) => item.name));
2727
- const copyable = target !== null && this.catalog.some((entry) => entry.id === target);
2746
+ const copyable = this.catalog.some((entry) => entry.id === target);
2728
2747
  elsewhere = pages.flatMap((page) => page.items).filter((item) => !seen.has(item.id) && !copied.has(item.id) && !names.has(item.name)).map((item) => {
2729
2748
  const copy = this.copies.get(copyKey(item.id, target));
2730
2749
  return { ...item, fits: false, attached: false, convertible: copyable && item.kind === "collection", copying: copy === void 0 || copy.status === "ready" ? null : copy.status };
@@ -2735,7 +2754,7 @@ var Workspace = class {
2735
2754
  const misfits = here.items.filter((item) => !item.fits);
2736
2755
  return {
2737
2756
  model: target,
2738
- modelName: target === null ? null : this.modelName(target),
2757
+ modelName: this.modelName(target),
2739
2758
  items: [...fitting, ...misfits, ...elsewhere],
2740
2759
  nextCursor: str4(here.pagination?.next_cursor),
2741
2760
  hasMore: here.pagination?.has_more === true,
@@ -2934,7 +2953,8 @@ var Workspace = class {
2934
2953
  await this.setKnowledge(input.attachToSession, id, true).catch(() => {
2935
2954
  });
2936
2955
  else {
2937
- this.pendingAttach.set(id, { sessionId: input.attachToSession, at: this.now() });
2956
+ const model2 = str4(memory.model) ?? this.memoryModel(session) ?? null;
2957
+ this.pendingAttach.set(id, { sessionId: input.attachToSession, at: this.now(), model: model2, failures: 0, next: this.now() });
2938
2958
  this.scheduleAttach(1e4);
2939
2959
  }
2940
2960
  }
@@ -2949,25 +2969,102 @@ var Workspace = class {
2949
2969
  }, ms);
2950
2970
  this.attachTimer.unref?.();
2951
2971
  }
2972
+ /**
2973
+ * Read the collections waiting to be attached, each in its own model's store,
2974
+ * and attach the ones that are ready.
2975
+ */
2952
2976
  async attachReady() {
2977
+ const groups = /* @__PURE__ */ new Map();
2978
+ for (const [id, pending] of [...this.pendingAttach]) {
2979
+ if (this.now() - pending.at > ATTACH_DEADLINE_MS) {
2980
+ this.pendingAttach.delete(id);
2981
+ continue;
2982
+ }
2983
+ if (pending.next > this.now())
2984
+ continue;
2985
+ const model2 = pending.model ?? this.targetModel(this.session(pending.sessionId));
2986
+ if (model2 === void 0)
2987
+ continue;
2988
+ groups.set(model2, [...groups.get(model2) ?? [], [id, pending]]);
2989
+ }
2990
+ for (const [model2, entries] of groups) {
2991
+ try {
2992
+ await this.attachCheck(model2, entries);
2993
+ } catch {
2994
+ }
2995
+ }
2996
+ if (this.pendingAttach.size === 0)
2997
+ return;
2998
+ const now = this.now();
2999
+ this.scheduleAttach(Math.min(...[...this.pendingAttach.values()].map((pending) => pending.next > now ? pending.next - now : ATTACH_POLL_MS)));
3000
+ }
3001
+ async attachCheck(model2, entries) {
3002
+ let items = null;
2953
3003
  try {
2954
- const page = await this.listKnowledge();
2955
- for (const [id, pending] of [...this.pendingAttach]) {
2956
- const item = page.items.find((candidate) => candidate.id === id);
2957
- if (this.now() - pending.at > 6 * 36e5 || item?.status === "failed") {
2958
- this.pendingAttach.delete(id);
2959
- continue;
2960
- }
2961
- if (item?.status === "ready") {
3004
+ items = await this.waitingKnowledge(model2);
3005
+ } catch (error) {
3006
+ const detail = error instanceof AdaptiqHttpError && error.status === 409 ? obj4(obj4(error.body)?.detail) : null;
3007
+ const code = str4(detail?.code);
3008
+ if (detail === null || code === null || !MODEL_ROUTE_CODES.has(code)) {
3009
+ for (const [, pending] of entries)
3010
+ pending.next = this.now() + ATTACH_POLL_MS;
3011
+ return;
3012
+ }
3013
+ const choices = routeChoices(detail);
3014
+ if (choices !== null && choices.length > 0 && !choices.includes(model2)) {
3015
+ const name2 = this.modelName(model2) ?? model2;
3016
+ for (const [id, pending] of entries) {
2962
3017
  this.pendingAttach.delete(id);
2963
- await this.setKnowledge(pending.sessionId, id, true).catch(() => {
3018
+ this.raise({
3019
+ kind: "model-unavailable",
3020
+ tone: "info",
3021
+ title: `${name2} isn't available`,
3022
+ body: `Your new collection is saved. Add it from Knowledge once ${name2} is available again.`,
3023
+ actions: [{ id: "open-models", label: "See your models", url: this.links.models }],
3024
+ sessionId: pending.sessionId
2964
3025
  });
2965
- this.raise({ kind: "connected", tone: "success", title: `${item.name} is ready`, body: "It is now used in your conversation.", actions: [], sessionId: pending.sessionId });
2966
3026
  }
3027
+ return;
3028
+ }
3029
+ const pick = choices?.find((choice) => choice === model2);
3030
+ if (pick !== void 0)
3031
+ items = await this.waitingKnowledge(pick).catch(() => null);
3032
+ if (items === null) {
3033
+ for (const [, pending] of entries) {
3034
+ pending.failures += 1;
3035
+ pending.next = this.now() + attachBackoffMs(pending.failures);
3036
+ }
3037
+ return;
2967
3038
  }
2968
- } catch {
2969
3039
  }
2970
- this.scheduleAttach(15e3);
3040
+ for (const [id, pending] of entries) {
3041
+ const item = items.find((candidate) => candidate.id === id);
3042
+ pending.failures = 0;
3043
+ pending.next = this.now() + ATTACH_POLL_MS;
3044
+ if (item?.status === "failed") {
3045
+ this.pendingAttach.delete(id);
3046
+ continue;
3047
+ }
3048
+ if (item?.status === "ready") {
3049
+ this.pendingAttach.delete(id);
3050
+ await this.setKnowledge(pending.sessionId, id, true).catch(() => {
3051
+ });
3052
+ this.raise({ kind: "connected", tone: "success", title: `${item.name} is ready`, body: "It is now used in your conversation.", actions: [], sessionId: pending.sessionId });
3053
+ }
3054
+ }
3055
+ }
3056
+ /**
3057
+ * One model's collections, for the ones waiting to be attached.
3058
+ * @param model - the store to read.
3059
+ * @returns the collections.
3060
+ */
3061
+ async waitingKnowledge(model2) {
3062
+ const params = new URLSearchParams({ limit: String(PAGE), status: "all" });
3063
+ const body = await this.deps.api.json(`/v1/memories?${params}`, { headers: { "x-adaptiq-model": model2 } });
3064
+ return (Array.isArray(body?.data) ? body.data : []).flatMap((raw) => {
3065
+ const memory = obj4(raw);
3066
+ return memory === null ? [] : [knowledgeItem(memory, false)];
3067
+ });
2971
3068
  }
2972
3069
  // ---------------------------------------------------------------- conversations as knowledge
2973
3070
  /** False once the server answered that it has no conversation routes. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaptiqai/deepseek-harness",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "AdaptiqAI for DeepSeek Harness: models with 5M-token native memory and Knowledge. Sign up, get a model and work without leaving the harness",
5
5
  "keywords": [
6
6
  "dsh-plugin",
@@ -89,7 +89,7 @@
89
89
  "react-dom": "~18.3.1",
90
90
  "typescript": "~5.9.3",
91
91
  "vitest": "~3.2.7",
92
- "@adaptiqai/core": "0.2.4"
92
+ "@adaptiqai/core": "0.2.5"
93
93
  },
94
94
  "repository": {
95
95
  "type": "git",