@adaptiqai/deepseek-harness 0.2.3 → 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.
- package/lib/client.js +1 -1
- package/lib/index.js +305 -64
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -140,7 +140,10 @@ function classifyFailure(status, raw, headers, model2) {
|
|
|
140
140
|
return make(CODES.invalidRequest, `AdaptiqAI couldn't accept this request${said}.`, null);
|
|
141
141
|
}
|
|
142
142
|
if (effective !== null && effective >= 500) {
|
|
143
|
-
|
|
143
|
+
const who = model2 ?? "AdaptiqAI";
|
|
144
|
+
if (effective === 503)
|
|
145
|
+
return make(CODES.server, `${model2 ?? "This model"} is offline right now. It will retry automatically.`, "retrying");
|
|
146
|
+
return make(CODES.server, `${who} didn't answer just now. It will retry automatically.`, "retrying");
|
|
144
147
|
}
|
|
145
148
|
if (effective === null) {
|
|
146
149
|
return make(CODES.server, detail.message !== null ? `AdaptiqAI stopped the reply: ${detail.message.slice(0, 200)}` : "AdaptiqAI stopped the reply.", null);
|
|
@@ -149,18 +152,29 @@ function classifyFailure(status, raw, headers, model2) {
|
|
|
149
152
|
}
|
|
150
153
|
function transportFailure(error) {
|
|
151
154
|
const name2 = error instanceof Error ? error.name : "";
|
|
152
|
-
const
|
|
155
|
+
const cause = error instanceof Error ? error.cause?.code : void 0;
|
|
156
|
+
const timeout = name2 === "TimeoutError" || cause === "UND_ERR_HEADERS_TIMEOUT" || cause === "UND_ERR_BODY_TIMEOUT";
|
|
153
157
|
return {
|
|
154
158
|
code: timeout ? CODES.timeout : CODES.transport,
|
|
155
|
-
message: timeout ? "AdaptiqAI took too long to answer. It will retry automatically." : "AdaptiqAI
|
|
159
|
+
message: timeout ? "AdaptiqAI took too long to answer. It will retry automatically." : "AdaptiqAI can't be reached right now. It will retry automatically.",
|
|
156
160
|
status: null,
|
|
157
161
|
requestId: null,
|
|
158
162
|
retryAfterMs: null,
|
|
159
163
|
serverType: null,
|
|
160
164
|
serverCode: null,
|
|
161
|
-
notice: "offline"
|
|
165
|
+
notice: timeout ? "retrying" : "offline"
|
|
162
166
|
};
|
|
163
167
|
}
|
|
168
|
+
function failureReach(failure) {
|
|
169
|
+
if (failure.code === CODES.transport)
|
|
170
|
+
return "unreachable";
|
|
171
|
+
if (failure.code === CODES.timeout)
|
|
172
|
+
return "transient";
|
|
173
|
+
return failure.status !== null && failure.status >= 500 && failure.status !== 507 ? "transient" : null;
|
|
174
|
+
}
|
|
175
|
+
function errorReach(error) {
|
|
176
|
+
return error instanceof AdaptiqHttpError ? failureReach(error.failure) : null;
|
|
177
|
+
}
|
|
164
178
|
var AdaptiqHttpError = class extends Error {
|
|
165
179
|
failure;
|
|
166
180
|
body;
|
|
@@ -1261,6 +1275,27 @@ var str4 = (value) => typeof value === "string" && value.trim() !== "" ? value.t
|
|
|
1261
1275
|
var count2 = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
1262
1276
|
var DEFAULT_PREFERENCES = { rememberByDefault: true, welcomed: false };
|
|
1263
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
|
+
}
|
|
1294
|
+
var TROUBLE_QUIET_MS = 6e4;
|
|
1295
|
+
var SESSION_BACKOFF_MAX_MS = 5 * 6e4;
|
|
1296
|
+
function sessionBackoffMs(failures) {
|
|
1297
|
+
return Math.min(SESSION_BACKOFF_MAX_MS, 3e4 * 2 ** Math.max(1, failures));
|
|
1298
|
+
}
|
|
1264
1299
|
function copyKey(source, target) {
|
|
1265
1300
|
return `${source}\0${target ?? ""}`;
|
|
1266
1301
|
}
|
|
@@ -1436,6 +1471,10 @@ var Workspace = class {
|
|
|
1436
1471
|
catalog = [];
|
|
1437
1472
|
catalogKey = "";
|
|
1438
1473
|
catalogAt = 0;
|
|
1474
|
+
/** The last `/v1/models` rows read: one stalled engine can fail the list for a while. */
|
|
1475
|
+
modelRows = [];
|
|
1476
|
+
/** Background reads have failed for a passing reason since `since`. */
|
|
1477
|
+
trouble = null;
|
|
1439
1478
|
offers = [];
|
|
1440
1479
|
checkoutMode = null;
|
|
1441
1480
|
/** Offer id → what the account summary says about it (model id, console link). */
|
|
@@ -1655,7 +1694,8 @@ var Workspace = class {
|
|
|
1655
1694
|
return;
|
|
1656
1695
|
this.timer = setTimeout(() => {
|
|
1657
1696
|
void this.refresh().finally(() => {
|
|
1658
|
-
this.
|
|
1697
|
+
const normal = this.listeners.size > 0 ? this.deps.watchedRefreshMs : this.deps.refreshMs;
|
|
1698
|
+
this.schedule(this.trouble === null ? normal : Math.min(normal, 15e3 * 2 ** Math.min(this.trouble.failures - 1, 6)));
|
|
1659
1699
|
});
|
|
1660
1700
|
}, ms);
|
|
1661
1701
|
this.timer.unref?.();
|
|
@@ -1690,8 +1730,17 @@ var Workspace = class {
|
|
|
1690
1730
|
this.setCatalog([]);
|
|
1691
1731
|
return;
|
|
1692
1732
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1733
|
+
let data;
|
|
1734
|
+
try {
|
|
1735
|
+
const rows = await this.deps.api.json("/v1/models");
|
|
1736
|
+
data = Array.isArray(rows?.data) ? rows.data : [];
|
|
1737
|
+
this.modelRows = data;
|
|
1738
|
+
} catch (error) {
|
|
1739
|
+
if (errorReach(error) === null || this.modelRows.length === 0)
|
|
1740
|
+
throw error;
|
|
1741
|
+
this.deps.log?.("warn", `adaptiq: model list unavailable, keeping the last one: ${error instanceof Error ? error.message : String(error)}`);
|
|
1742
|
+
data = this.modelRows;
|
|
1743
|
+
}
|
|
1695
1744
|
const summary2 = await this.loadSummary();
|
|
1696
1745
|
let availability;
|
|
1697
1746
|
if (summary2 !== null) {
|
|
@@ -1708,19 +1757,33 @@ var Workspace = class {
|
|
|
1708
1757
|
if (this.memory === null || !this.memory.available)
|
|
1709
1758
|
await this.loadFallbackMemory();
|
|
1710
1759
|
this.lastError = null;
|
|
1760
|
+
this.trouble = null;
|
|
1711
1761
|
this.refreshedAt = this.now();
|
|
1712
1762
|
this.evaluateNotices();
|
|
1713
1763
|
} catch (error) {
|
|
1714
1764
|
const failure = error instanceof AdaptiqHttpError ? error.failure : null;
|
|
1715
1765
|
this.lastError = { code: failure?.code ?? "UNKNOWN", message: failure?.message ?? (error instanceof Error ? error.message : String(error)), at: this.now() };
|
|
1716
|
-
|
|
1717
|
-
|
|
1766
|
+
const reach = failure === null ? null : failureReach(failure);
|
|
1767
|
+
if (reach !== null) {
|
|
1768
|
+
const since = this.trouble?.since ?? this.now();
|
|
1769
|
+
this.trouble = { since, reach, failures: (this.trouble?.failures ?? 0) + 1 };
|
|
1770
|
+
if (this.now() - since >= TROUBLE_QUIET_MS)
|
|
1771
|
+
this.raiseTrouble(reach);
|
|
1772
|
+
}
|
|
1718
1773
|
this.deps.log?.("warn", `adaptiq: refresh failed: ${this.lastError.message}`);
|
|
1719
1774
|
} finally {
|
|
1720
1775
|
this.loading = false;
|
|
1721
1776
|
this.publish();
|
|
1722
1777
|
}
|
|
1723
1778
|
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Say, calmly, that background reads keep failing: "busy" while AdaptiqAI
|
|
1781
|
+
* answers with errors, "can't be reached" only when there is no connection.
|
|
1782
|
+
* @param reach - the kind of trouble.
|
|
1783
|
+
*/
|
|
1784
|
+
raiseTrouble(reach) {
|
|
1785
|
+
this.raise(reach === "unreachable" ? { kind: "offline", tone: "warning", title: "AdaptiqAI can't be reached", body: "Retrying automatically. Your models and conversations stay as they are.", actions: [{ id: "refresh", label: "Try again" }] } : { kind: "offline", tone: "info", title: "AdaptiqAI is busy \u2014 retrying", body: "Your models and conversations stay as they are. This usually clears by itself.", actions: [] });
|
|
1786
|
+
}
|
|
1724
1787
|
setCatalog(models) {
|
|
1725
1788
|
const key = JSON.stringify(models);
|
|
1726
1789
|
this.catalogAt = this.now();
|
|
@@ -1733,8 +1796,13 @@ var Workspace = class {
|
|
|
1733
1796
|
if (session.listeners.size === 0)
|
|
1734
1797
|
continue;
|
|
1735
1798
|
this.emitSession(session);
|
|
1736
|
-
|
|
1799
|
+
const awaited = session.backoff?.model ?? null;
|
|
1800
|
+
if (awaited !== null && !this.modelDown(awaited)) {
|
|
1801
|
+
session.backoff = null;
|
|
1737
1802
|
this.pollSession(session, 0);
|
|
1803
|
+
} else if (!session.exists && session.remoteAt === 0) {
|
|
1804
|
+
this.pollSession(session, 0);
|
|
1805
|
+
}
|
|
1738
1806
|
}
|
|
1739
1807
|
}
|
|
1740
1808
|
async loadSummary() {
|
|
@@ -2085,6 +2153,9 @@ var Workspace = class {
|
|
|
2085
2153
|
knowledge: [],
|
|
2086
2154
|
conversations: [],
|
|
2087
2155
|
remoteAt: 0,
|
|
2156
|
+
home: null,
|
|
2157
|
+
notOn: /* @__PURE__ */ new Set(),
|
|
2158
|
+
backoff: null,
|
|
2088
2159
|
listeners: /* @__PURE__ */ new Set()
|
|
2089
2160
|
};
|
|
2090
2161
|
this.sessions.set(id, session);
|
|
@@ -2162,13 +2233,29 @@ var Workspace = class {
|
|
|
2162
2233
|
clearTimeout(session.timer);
|
|
2163
2234
|
if (this.disposed || session.listeners.size === 0)
|
|
2164
2235
|
return;
|
|
2236
|
+
const wait = session.backoff === null ? 0 : session.backoff.until - this.now();
|
|
2165
2237
|
session.timer = setTimeout(() => {
|
|
2166
2238
|
void this.loadSession(session).finally(() => {
|
|
2167
2239
|
this.pollSession(session, sessionPollDelay(session));
|
|
2168
2240
|
});
|
|
2169
|
-
}, ms);
|
|
2241
|
+
}, Math.max(ms, wait));
|
|
2170
2242
|
session.timer.unref?.();
|
|
2171
2243
|
}
|
|
2244
|
+
/** @returns whether a model's instance is offline or disabled (not asked about conversations). */
|
|
2245
|
+
modelDown(id) {
|
|
2246
|
+
const status = this.catalog.find((entry) => entry.id === id)?.status;
|
|
2247
|
+
return status === "offline" || status === "disabled";
|
|
2248
|
+
}
|
|
2249
|
+
/**
|
|
2250
|
+
* Read a conversation less often after a failed read or while its model is
|
|
2251
|
+
* offline: 1, 2, 4, then every 5 minutes.
|
|
2252
|
+
* @param session - the conversation.
|
|
2253
|
+
* @param model - the offline model it waits for, or null after a failed read.
|
|
2254
|
+
*/
|
|
2255
|
+
backOff(session, model2) {
|
|
2256
|
+
const failures = (session.backoff?.failures ?? 0) + 1;
|
|
2257
|
+
session.backoff = { failures, until: this.now() + sessionBackoffMs(failures), model: model2 };
|
|
2258
|
+
}
|
|
2172
2259
|
/**
|
|
2173
2260
|
* The memory store a conversation's knowledge and memory live in: where it is
|
|
2174
2261
|
* stored once it exists, otherwise the model selected for it.
|
|
@@ -2255,42 +2342,23 @@ var Workspace = class {
|
|
|
2255
2342
|
return;
|
|
2256
2343
|
session.model = route.model;
|
|
2257
2344
|
}
|
|
2258
|
-
const
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
try {
|
|
2263
|
-
body = await this.deps.api.json(path, { allow: [404] });
|
|
2264
|
-
routed = true;
|
|
2265
|
-
} catch (error) {
|
|
2266
|
-
if (!(error instanceof AdaptiqHttpError) || error.status !== 409 && error.status !== 400)
|
|
2267
|
-
throw error;
|
|
2268
|
-
const models = obj4(obj4(error.body)?.detail)?.models;
|
|
2269
|
-
if (Array.isArray(models))
|
|
2270
|
-
suggested = models.filter((model2) => typeof model2 === "string" && model2 !== "");
|
|
2271
|
-
}
|
|
2272
|
-
const candidates = routed ? [] : session.model !== null ? [session.model] : suggested.length > 0 ? suggested.slice(0, 6) : [...this.catalog].sort((a, b) => Number(b.status === "running") - Number(a.status === "running")).map((entry) => entry.id).slice(0, 6);
|
|
2273
|
-
for (const model2 of candidates) {
|
|
2274
|
-
body = await this.deps.api.json(path, { headers: { "x-adaptiq-model": model2 }, allow: [404] });
|
|
2275
|
-
if (body !== null) {
|
|
2276
|
-
if (session.model === null) {
|
|
2277
|
-
session.model = model2;
|
|
2278
|
-
session.onAdaptiq ??= true;
|
|
2279
|
-
}
|
|
2280
|
-
break;
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
if (routed && body !== null) {
|
|
2284
|
-
const served = str4(obj4(body.session)?.model) ?? str4(body.model);
|
|
2285
|
-
if (served !== null)
|
|
2286
|
-
session.model = served;
|
|
2287
|
-
else if (session.model === null && session.lastUsed !== null)
|
|
2288
|
-
session.model = session.lastUsed;
|
|
2289
|
-
session.onAdaptiq ??= true;
|
|
2345
|
+
const found = await this.findSession(session);
|
|
2346
|
+
if (found === void 0) {
|
|
2347
|
+
session.remoteAt = this.now();
|
|
2348
|
+
return;
|
|
2290
2349
|
}
|
|
2291
|
-
|
|
2350
|
+
session.backoff = null;
|
|
2351
|
+
if (found.asked)
|
|
2292
2352
|
session.remoteAt = this.now();
|
|
2353
|
+
const body = found.body;
|
|
2293
2354
|
if (body === null) {
|
|
2355
|
+
if (session.exists) {
|
|
2356
|
+
session.memoryTokens = null;
|
|
2357
|
+
session.saved = null;
|
|
2358
|
+
session.optimizing = false;
|
|
2359
|
+
session.revision = null;
|
|
2360
|
+
session.conversations = [];
|
|
2361
|
+
}
|
|
2294
2362
|
session.exists = false;
|
|
2295
2363
|
session.knowledge = [];
|
|
2296
2364
|
return;
|
|
@@ -2314,6 +2382,7 @@ var Workspace = class {
|
|
|
2314
2382
|
session.conversations = await this.attachedConversations(session).catch(() => session.conversations);
|
|
2315
2383
|
} catch (error) {
|
|
2316
2384
|
session.remoteAt = this.now();
|
|
2385
|
+
this.backOff(session, null);
|
|
2317
2386
|
this.deps.log?.("warn", `adaptiq: could not read conversation ${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2318
2387
|
if (!(error instanceof AdaptiqHttpError))
|
|
2319
2388
|
throw error;
|
|
@@ -2329,6 +2398,83 @@ var Workspace = class {
|
|
|
2329
2398
|
session.loading = void 0;
|
|
2330
2399
|
}
|
|
2331
2400
|
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Find a conversation on the server. Once a model has answered for it, only
|
|
2403
|
+
* that model is asked. Before that, the session-aware route (which needs no
|
|
2404
|
+
* model), then, on gateways that want one, the conversation's model or the
|
|
2405
|
+
* models the gateway names: never a model that is offline or that already
|
|
2406
|
+
* said the conversation is not there.
|
|
2407
|
+
* @param session - the conversation.
|
|
2408
|
+
* @returns its body (null when the server does not have it) and whether
|
|
2409
|
+
* anything could be asked, or undefined when its model is offline (backed off).
|
|
2410
|
+
*/
|
|
2411
|
+
async findSession(session) {
|
|
2412
|
+
const path = `/v1/sessions/${encodeURIComponent(session.id)}`;
|
|
2413
|
+
const ask = async (model2) => {
|
|
2414
|
+
try {
|
|
2415
|
+
return await this.deps.api.json(path, { headers: { "x-adaptiq-model": model2 }, allow: [404] });
|
|
2416
|
+
} catch (error) {
|
|
2417
|
+
if (error instanceof AdaptiqHttpError && error.status === 409)
|
|
2418
|
+
return null;
|
|
2419
|
+
throw error;
|
|
2420
|
+
}
|
|
2421
|
+
};
|
|
2422
|
+
if (session.home !== null) {
|
|
2423
|
+
const home = session.home;
|
|
2424
|
+
if (this.modelDown(home)) {
|
|
2425
|
+
this.backOff(session, home);
|
|
2426
|
+
return void 0;
|
|
2427
|
+
}
|
|
2428
|
+
const body = await ask(home);
|
|
2429
|
+
if (body === null) {
|
|
2430
|
+
session.home = null;
|
|
2431
|
+
session.notOn.add(home);
|
|
2432
|
+
} else
|
|
2433
|
+
session.model = home;
|
|
2434
|
+
return { body, asked: true };
|
|
2435
|
+
}
|
|
2436
|
+
let suggested = [];
|
|
2437
|
+
try {
|
|
2438
|
+
const body = await this.deps.api.json(path, { allow: [404] });
|
|
2439
|
+
if (body !== null) {
|
|
2440
|
+
const served = str4(obj4(body.session)?.model) ?? str4(body.model);
|
|
2441
|
+
if (served !== null) {
|
|
2442
|
+
session.model = served;
|
|
2443
|
+
session.home = served;
|
|
2444
|
+
} else if (session.model === null && session.lastUsed !== null)
|
|
2445
|
+
session.model = session.lastUsed;
|
|
2446
|
+
session.onAdaptiq ??= true;
|
|
2447
|
+
}
|
|
2448
|
+
return { body, asked: true };
|
|
2449
|
+
} catch (error) {
|
|
2450
|
+
if (!(error instanceof AdaptiqHttpError) || error.status !== 409 && error.status !== 400)
|
|
2451
|
+
throw error;
|
|
2452
|
+
const models = obj4(obj4(error.body)?.detail)?.models;
|
|
2453
|
+
if (Array.isArray(models))
|
|
2454
|
+
suggested = models.filter((model2) => typeof model2 === "string" && model2 !== "");
|
|
2455
|
+
}
|
|
2456
|
+
const listed = session.model !== null ? [session.model] : suggested.length > 0 ? suggested : [...this.catalog].sort((a, b) => Number(b.status === "running") - Number(a.status === "running")).map((entry) => entry.id);
|
|
2457
|
+
const open = listed.filter((model2) => !session.notOn.has(model2)).slice(0, 6);
|
|
2458
|
+
const candidates = open.filter((model2) => !this.modelDown(model2));
|
|
2459
|
+
for (const model2 of candidates) {
|
|
2460
|
+
const body = await ask(model2);
|
|
2461
|
+
if (body !== null) {
|
|
2462
|
+
session.home = model2;
|
|
2463
|
+
if (session.model === null) {
|
|
2464
|
+
session.model = model2;
|
|
2465
|
+
session.onAdaptiq ??= true;
|
|
2466
|
+
}
|
|
2467
|
+
return { body, asked: true };
|
|
2468
|
+
}
|
|
2469
|
+
session.notOn.add(model2);
|
|
2470
|
+
}
|
|
2471
|
+
const offline = open.find((model2) => this.modelDown(model2));
|
|
2472
|
+
if (offline !== void 0) {
|
|
2473
|
+
this.backOff(session, offline);
|
|
2474
|
+
return void 0;
|
|
2475
|
+
}
|
|
2476
|
+
return { body: null, asked: candidates.length > 0 };
|
|
2477
|
+
}
|
|
2332
2478
|
/**
|
|
2333
2479
|
* Choose whether new messages of a conversation are saved.
|
|
2334
2480
|
* @param id - harness session id.
|
|
@@ -2387,6 +2533,8 @@ var Workspace = class {
|
|
|
2387
2533
|
const session = this.session(sessionId2);
|
|
2388
2534
|
session.onAdaptiq = true;
|
|
2389
2535
|
session.model = model2;
|
|
2536
|
+
session.notOn.delete(model2);
|
|
2537
|
+
session.backoff = null;
|
|
2390
2538
|
session.inflight++;
|
|
2391
2539
|
session.laneHeld = false;
|
|
2392
2540
|
session.failedNow = false;
|
|
@@ -2420,7 +2568,7 @@ var Workspace = class {
|
|
|
2420
2568
|
session.laneHeld = true;
|
|
2421
2569
|
session.retry = null;
|
|
2422
2570
|
this.setPhase(session, "responding");
|
|
2423
|
-
this.clearNotices("busy", "preparing", "offline");
|
|
2571
|
+
this.clearNotices("busy", "preparing", "offline", "retrying");
|
|
2424
2572
|
}
|
|
2425
2573
|
usage(sessionId2, model2, usage2) {
|
|
2426
2574
|
if (sessionId2 === void 0)
|
|
@@ -2503,8 +2651,20 @@ var Workspace = class {
|
|
|
2503
2651
|
case "model-unavailable":
|
|
2504
2652
|
this.raise({ kind: "model-unavailable", tone: "warning", title: `${name2} isn't available`, body: failure.message, actions: [{ id: "open-models", label: "See your models", url: this.links.models }] });
|
|
2505
2653
|
break;
|
|
2654
|
+
case "retrying": {
|
|
2655
|
+
const offline = entry?.status === "offline";
|
|
2656
|
+
this.raise({
|
|
2657
|
+
kind: "retrying",
|
|
2658
|
+
tone: "info",
|
|
2659
|
+
title: offline ? `${name2} is offline` : `${name2} didn't answer just now`,
|
|
2660
|
+
body: offline ? "Your request retries by itself. Your other AdaptiqAI models still work; pick one to continue now." : "Your request retries by itself.",
|
|
2661
|
+
actions: [],
|
|
2662
|
+
...sessionId2 === void 0 ? {} : { sessionId: sessionId2 }
|
|
2663
|
+
});
|
|
2664
|
+
break;
|
|
2665
|
+
}
|
|
2506
2666
|
case "offline":
|
|
2507
|
-
this.raise({ kind: "offline", tone: "warning", title: "AdaptiqAI can't be reached", body:
|
|
2667
|
+
this.raise({ kind: "offline", tone: "warning", title: "AdaptiqAI can't be reached", body: "Your request retries by itself once the connection is back.", actions: [], ...sessionId2 === void 0 ? {} : { sessionId: sessionId2 } });
|
|
2508
2668
|
break;
|
|
2509
2669
|
default:
|
|
2510
2670
|
break;
|
|
@@ -2555,7 +2715,10 @@ var Workspace = class {
|
|
|
2555
2715
|
if (session !== void 0 && this.now() - session.remoteAt > 1e4)
|
|
2556
2716
|
await this.loadSession(session).catch(() => {
|
|
2557
2717
|
});
|
|
2558
|
-
const
|
|
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 };
|
|
2559
2722
|
const attached = new Set(session?.knowledge.map((item) => item.id) ?? []);
|
|
2560
2723
|
const name2 = (id) => this.modelName(id);
|
|
2561
2724
|
const read = async (store, limit, after) => {
|
|
@@ -2564,7 +2727,7 @@ var Workspace = class {
|
|
|
2564
2727
|
params.set("query", query.trim().slice(0, 200));
|
|
2565
2728
|
if (after !== void 0 && after !== "")
|
|
2566
2729
|
params.set("cursor", after);
|
|
2567
|
-
const body = await this.deps.api.json(`/v1/memories?${params}`, { headers:
|
|
2730
|
+
const body = await this.deps.api.json(`/v1/memories?${params}`, { headers: { "x-adaptiq-model": store } });
|
|
2568
2731
|
const items = (Array.isArray(body?.data) ? body.data : []).flatMap((raw) => {
|
|
2569
2732
|
const memory = obj4(raw);
|
|
2570
2733
|
return memory === null ? [] : [knowledgeItem(memory, store === target && attached.has(String(memory.memory_id)), { store, target, name: name2 })];
|
|
@@ -2574,13 +2737,13 @@ var Workspace = class {
|
|
|
2574
2737
|
const here = await read(target, PAGE, cursor);
|
|
2575
2738
|
await this.followCopies(target);
|
|
2576
2739
|
let elsewhere = [];
|
|
2577
|
-
if (
|
|
2740
|
+
if (cursor === void 0 || cursor === "") {
|
|
2578
2741
|
const others = this.catalog.filter((entry) => entry.id !== target && entry.status !== "disabled").slice(0, 3);
|
|
2579
2742
|
const pages = await Promise.all(others.map((entry) => read(entry.id, 20).catch(() => ({ items: [], pagination: null }))));
|
|
2580
2743
|
const seen = new Set(here.items.map((item) => item.id));
|
|
2581
2744
|
const copied = new Set([...this.copies.values()].filter((copy) => copy.target === target && copy.status === "ready").map((copy) => copy.source));
|
|
2582
2745
|
const names = new Set(here.items.filter((item) => item.fits).map((item) => item.name));
|
|
2583
|
-
const copyable =
|
|
2746
|
+
const copyable = this.catalog.some((entry) => entry.id === target);
|
|
2584
2747
|
elsewhere = pages.flatMap((page) => page.items).filter((item) => !seen.has(item.id) && !copied.has(item.id) && !names.has(item.name)).map((item) => {
|
|
2585
2748
|
const copy = this.copies.get(copyKey(item.id, target));
|
|
2586
2749
|
return { ...item, fits: false, attached: false, convertible: copyable && item.kind === "collection", copying: copy === void 0 || copy.status === "ready" ? null : copy.status };
|
|
@@ -2591,7 +2754,7 @@ var Workspace = class {
|
|
|
2591
2754
|
const misfits = here.items.filter((item) => !item.fits);
|
|
2592
2755
|
return {
|
|
2593
2756
|
model: target,
|
|
2594
|
-
modelName:
|
|
2757
|
+
modelName: this.modelName(target),
|
|
2595
2758
|
items: [...fitting, ...misfits, ...elsewhere],
|
|
2596
2759
|
nextCursor: str4(here.pagination?.next_cursor),
|
|
2597
2760
|
hasMore: here.pagination?.has_more === true,
|
|
@@ -2790,7 +2953,8 @@ var Workspace = class {
|
|
|
2790
2953
|
await this.setKnowledge(input.attachToSession, id, true).catch(() => {
|
|
2791
2954
|
});
|
|
2792
2955
|
else {
|
|
2793
|
-
|
|
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() });
|
|
2794
2958
|
this.scheduleAttach(1e4);
|
|
2795
2959
|
}
|
|
2796
2960
|
}
|
|
@@ -2805,25 +2969,102 @@ var Workspace = class {
|
|
|
2805
2969
|
}, ms);
|
|
2806
2970
|
this.attachTimer.unref?.();
|
|
2807
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
|
+
*/
|
|
2808
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;
|
|
2809
3003
|
try {
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
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) {
|
|
2818
3017
|
this.pendingAttach.delete(id);
|
|
2819
|
-
|
|
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
|
|
2820
3025
|
});
|
|
2821
|
-
this.raise({ kind: "connected", tone: "success", title: `${item.name} is ready`, body: "It is now used in your conversation.", actions: [], sessionId: pending.sessionId });
|
|
2822
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;
|
|
2823
3038
|
}
|
|
2824
|
-
} catch {
|
|
2825
3039
|
}
|
|
2826
|
-
|
|
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
|
+
});
|
|
2827
3068
|
}
|
|
2828
3069
|
// ---------------------------------------------------------------- conversations as knowledge
|
|
2829
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.
|
|
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.
|
|
92
|
+
"@adaptiqai/core": "0.2.5"
|
|
93
93
|
},
|
|
94
94
|
"repository": {
|
|
95
95
|
"type": "git",
|