@adaptiqai/deepseek-harness 0.2.12 → 0.2.14

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/host.js CHANGED
@@ -54,7 +54,7 @@ import { attributionHeaders as attributionHeaders2 } from "@deepseek-ai/dsh-llm"
54
54
  import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
55
55
 
56
56
  // src/host/adapter.ts
57
- import { attributionHeaders, LlmAdapter, LlmError, ProviderRequestId } from "@deepseek-ai/dsh-llm";
57
+ import { attributionHeaders, IMAGE_OFFLOAD_REQUIRED_CODE, LlmAdapter, LlmError, ProviderRequestId } from "@deepseek-ai/dsh-llm";
58
58
 
59
59
  // ../core/lib/errors.js
60
60
  var CODES = {
@@ -122,27 +122,32 @@ function classifyFailure(status, raw, headers, model2, options = {}) {
122
122
  const library = /\blibrary is full/iu.test(detail.message ?? "");
123
123
  return { ...make(CODES.memoryFull, library ? MEMORY_FULL_LIBRARY : MEMORY_FULL, "memory-full"), memoryFull: library ? "library" : "no-room" };
124
124
  }
125
+ if (detail.code === "plan_usage_exhausted") {
126
+ const renewsAt = envelope.detail?.renews_at;
127
+ const failure = make(CODES.noLanes, detail.message ?? "Your plan's usage is used up for now. Pay per token from your balance in the meantime.", "no-lanes");
128
+ return typeof renewsAt === "number" ? { ...failure, planRenewsAt: renewsAt } : failure;
129
+ }
125
130
  if (effective === 402 || detail.code === "rental_paused") {
126
131
  const releaseAt = envelope.detail?.release_at;
127
132
  const minutes = typeof releaseAt === "number" ? Math.max(1, Math.ceil((releaseAt * 1e3 - Date.now()) / 6e4)) : null;
128
- const keep = minutes === null ? "to continue" : `within ${minutes} minute${minutes === 1 ? "" : "s"} to keep them`;
129
- const failure = make(CODES.outOfCredit, `Your balance ran out, so your lanes on ${name2} are paused. Add funds ${keep}.`, "out-of-credit");
133
+ const keep = minutes === null ? "to continue" : `within ${minutes} minute${minutes === 1 ? "" : "s"} to keep your models running`;
134
+ const failure = make(CODES.outOfCredit, `Your balance ran out. Add funds ${keep}.`, "out-of-credit");
130
135
  return minutes === null ? failure : { ...failure, minutesLeft: minutes };
131
136
  }
132
137
  if (effective === 401) {
133
138
  return make(CODES.signIn, "Your AdaptiqAI sign-in has expired. Open Settings \u2192 AdaptiqAI and sign in again.", "sign-in");
134
139
  }
135
140
  if (detail.code === "rental_stopped_unused") {
136
- return make(CODES.noLanes, `Your lane on ${name2} stopped because it went unused, so nothing more was charged. Get access to start it again.`, "no-lanes");
141
+ return make(CODES.noLanes, "The model stopped after going unused. Get access to start it again.", "no-lanes");
137
142
  }
138
143
  if (has(detail, "AdaptiqLaneAccessRequired", "inference_entitlement_required", "instance_access_required", "lane_access") || effective === 403 && has(detail, "lane", "entitle", "model access")) {
139
- return make(CODES.noLanes, `Your workspace has no lanes on ${name2} yet. Get access to start.`, "no-lanes");
144
+ return make(CODES.noLanes, "This model isn't in your workspace yet. Get access to continue; you pay per token from your balance.", "no-lanes");
140
145
  }
141
146
  if (effective === 403) {
142
147
  return make(CODES.signIn, "This AdaptiqAI connection isn't allowed to do that. Sign in again from Settings \u2192 AdaptiqAI.", "sign-in");
143
148
  }
144
149
  if (effective === 429 || has(detail, "AdaptiqLaneQueueTimeout", "InflightLimit", "rate_limit")) {
145
- return make(CODES.busy, `All of your lanes on ${name2} are busy. Your request will retry automatically in a moment.`, "busy");
150
+ return make(CODES.busy, `${name2} is busy. Your request will retry automatically in a moment.`, "busy");
146
151
  }
147
152
  if (effective === 503 && detail.code !== null && MODEL_OFFLINE_CODES.includes(detail.code)) {
148
153
  return make(CODES.server, `${model2 ?? "This model"} is offline right now. It will retry automatically.`, "retrying");
@@ -154,7 +159,7 @@ function classifyFailure(status, raw, headers, model2, options = {}) {
154
159
  return make(CODES.conversationUnavailable, "This conversation couldn't be opened on AdaptiqAI right now. Try again shortly; your saved memory is unchanged.", null);
155
160
  }
156
161
  if (effective === 404 && (has(detail, "model", "not_found", "does not exist") || detail.message === null)) {
157
- return make(CODES.modelNotFound, `${name2} isn't available to your workspace right now. Pick another AdaptiqAI model, or check your lanes in the console.`, "model-unavailable");
162
+ return make(CODES.modelNotFound, `${name2} isn't available to your workspace right now. Pick another AdaptiqAI model, or check your models in the console.`, "model-unavailable");
158
163
  }
159
164
  if (effective === 400 || effective === 413 || effective === 422) {
160
165
  const said = detail.message !== null ? ` (${detail.message.slice(0, 200)})` : "";
@@ -321,10 +326,10 @@ var REFRESH_MARGIN_MS = 2 * 6e4;
321
326
  var DEVICE_TIMEOUT_MS = 10 * 6e4;
322
327
  var UNREACHABLE = "AdaptiqAI can't be reached. Check your internet connection and try again.";
323
328
  function deviceLabel(value) {
324
- let label = value.replace(/\.local$/iu, "").replace(/[\p{Cc}\p{Cf}]/gu, " ").replace(/\s+/gu, " ").trim() || "this computer";
325
- while (Buffer.byteLength(label) > 80)
326
- label = label.slice(0, -1);
327
- return label;
329
+ let label2 = value.replace(/\.local$/iu, "").replace(/[\p{Cc}\p{Cf}]/gu, " ").replace(/\s+/gu, " ").trim() || "this computer";
330
+ while (Buffer.byteLength(label2) > 80)
331
+ label2 = label2.slice(0, -1);
332
+ return label2;
328
333
  }
329
334
  var defaultSleep = (ms, signal) => new Promise((resolve2, reject) => {
330
335
  if (signal.aborted) {
@@ -1061,13 +1066,11 @@ var en = {
1061
1066
  expiredTitle: "Your sign-in expired",
1062
1067
  signInAgain: "Sign in again",
1063
1068
  tryAgain: "Try again",
1064
- models: "Models & lanes",
1069
+ models: "Models",
1065
1070
  status_running: "Running",
1066
1071
  status_offline: "Offline",
1067
1072
  status_disabled: "Paused",
1068
1073
  status_unknown: "Available",
1069
- lanesOne: "{count} lane",
1070
- lanesMany: "{count} lanes",
1071
1074
  thinkingLevels: "Thinking {levels}",
1072
1075
  noModels: "Pick your first model",
1073
1076
  getAccess: "Get access",
@@ -1093,10 +1096,10 @@ var en = {
1093
1096
  routeConflictTitle: "AdaptiqAI models are hidden",
1094
1097
  pillMemory: "Memory",
1095
1098
  pillPreparing: "Preparing memory",
1096
- pillQueued: "Waiting for a free lane",
1099
+ pillQueued: "Waiting in line",
1097
1100
  pillStarting: "Starting\u2026",
1098
1101
  pillSignIn: "Sign in to AdaptiqAI",
1099
- pillFromMemory: "{tokens} from memory",
1102
+ pillFromMemory: "{tokens} from memory \xB7 free",
1100
1103
  pillTooltip: "AdaptiqAI memory for this conversation",
1101
1104
  popTitle: "Memory",
1102
1105
  popSaving: "Saving",
@@ -1172,22 +1175,22 @@ var en = {
1172
1175
  accessTitle: "Get a model",
1173
1176
  offerSpeed: "{speed} tokens/s",
1174
1177
  offerMemory: "{tokens} memory",
1175
- offerPrice: "{price} per lane per {period}",
1178
+ offerPrice: "{price} per {period}",
1176
1179
  offerOwned: "In your workspace",
1177
1180
  offerFull: "Full right now",
1178
1181
  period_hour: "hour",
1179
1182
  period_month: "month",
1180
1183
  period_once: "purchase",
1181
- lanesLabel: "Lanes",
1182
- lanesHint: "Each lane runs one conversation at a time.",
1184
+ lanesLabel: "At once",
1185
+ lanesHint: "How many conversations can run at the same time.",
1183
1186
  totalPrice: "{price} per {period}",
1184
1187
  noOffers: "No models are on offer right now. Check back soon.",
1185
- manualNote: "Our team activates new lanes after you confirm.",
1186
- balanceNote: "Runs from your balance and stops by itself after 15 minutes without requests, or when you stop it.",
1188
+ manualNote: "Our team turns it on after you confirm.",
1189
+ balanceNote: "You pay per token from your balance. Reads from memory are free.",
1187
1190
  cardNote: "You pay by card in your browser.",
1188
- addLanes: "Add lanes",
1191
+ addLanes: "Run more at once",
1189
1192
  purchase_approving: "Confirm in your browser",
1190
- purchase_approving_body: "We opened a page to confirm {lanes} on {model}. This updates by itself.",
1193
+ purchase_approving_body: "We opened a page to confirm {model}. This updates by itself.",
1191
1194
  purchase_console_body: "Finish on the page we opened. {model} appears here as soon as it's active.",
1192
1195
  purchase_requested: "Requested. We'll activate it shortly",
1193
1196
  purchase_requested_body: "You'll get an email too. {model} appears here by itself.",
@@ -1197,10 +1200,8 @@ var en = {
1197
1200
  purchase_ready_body: "Pick it in the model picker and start.",
1198
1201
  purchase_denied: "The purchase was declined in the browser.",
1199
1202
  purchase_expired: "The confirmation page expired.",
1200
- purchase_stopped: "The lanes have stopped.",
1203
+ purchase_stopped: "The model has stopped.",
1201
1204
  purchase_failed: "The purchase didn't go through.",
1202
- lanesCount: "{count} lanes",
1203
- laneCount: "1 lane",
1204
1205
  openPage: "Open the page again",
1205
1206
  stopWaiting: "Stop waiting",
1206
1207
  startChatting: "Start a conversation",
@@ -1274,17 +1275,21 @@ var en = {
1274
1275
  n_connected_body: "Your AdaptiqAI models are in the model picker.",
1275
1276
  n_compaction_title: "Nothing to compact",
1276
1277
  n_compaction_body: "AdaptiqAI keeps the whole conversation in memory, so nothing needs shortening.",
1278
+ n_imagesTextOnly_title: "Images were not sent",
1279
+ n_imagesTextOnly_body: "{model} reads text only, so the model got a short note in place of each image. Choose a model that reads images to ask about them.",
1277
1280
  n_signIn_title: "Sign in to keep going",
1278
1281
  n_signIn_body: "Your AdaptiqAI connection needs a fresh sign-in.",
1279
1282
  n_connect_body: "Connect your AdaptiqAI account to use this model.",
1280
- n_noLanes_title: "No lanes on {model}",
1281
- n_noLanes_body: "Your workspace has no lanes on {model} yet.",
1282
- n_laneStopped_body: "Your lane on {model} stopped because it went unused, so nothing more was charged.",
1283
+ n_noLanes_title: "Get access to continue",
1284
+ n_noLanes_body: "This model isn't in your workspace yet. You pay per token from your balance; reads from memory are free.",
1285
+ n_laneStopped_body: "The model stopped after going unused. Start it again to continue.",
1286
+ n_planUsedUp_body: "Your plan's usage is used up until {until}. Pay per token from your balance in the meantime.",
1287
+ n_planUsedUpNoDate_body: "Your plan's usage is used up for now. Pay per token from your balance in the meantime.",
1283
1288
  n_credit_title: "Add funds to continue",
1284
- n_credit_body: "Your balance ran out, so your lanes on {model} are paused.",
1285
- n_creditMinutes_body: "Your balance ran out, so your lanes on {model} are paused. Add funds within {minutes} min to keep them.",
1286
- n_busy_title: "All lanes are busy",
1287
- n_busy_body: "Your request on {model} is waiting for a free lane and will retry by itself.",
1289
+ n_credit_body: "Your balance ran out. Add funds to keep going.",
1290
+ n_creditMinutes_body: "Your balance ran out. Add funds within {minutes} min to keep your models running.",
1291
+ n_busy_title: "{model} is busy",
1292
+ n_busy_body: "Your request is waiting in line and retries by itself.",
1288
1293
  n_preparing_title: "Getting your conversation ready",
1289
1294
  n_preparing_body: "AdaptiqAI is loading this conversation's memory. It retries by itself.",
1290
1295
  n_unavailable_title: "{model} isn't available",
@@ -1323,122 +1328,6 @@ function fill(template, params = {}) {
1323
1328
  return template.replace(/\{(\w+)\}/gu, (whole, name2) => name2 in params ? String(params[name2]) : whole);
1324
1329
  }
1325
1330
 
1326
- // ../core/lib/update.js
1327
- var RELEASE_AGE_MS = 24 * 36e5;
1328
- var RELEASE_VERSION = /^\d+\.\d+\.\d+$/u;
1329
- function semverGreater(a, b) {
1330
- const parse = (value) => value.replace(/^v/u, "").split(/[.-]/u).slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
1331
- const [x, y] = [parse(a), parse(b)];
1332
- for (let i = 0; i < 3; i++)
1333
- if ((x[i] ?? 0) !== (y[i] ?? 0))
1334
- return (x[i] ?? 0) > (y[i] ?? 0);
1335
- return false;
1336
- }
1337
- var UpdateFailure = class extends Error {
1338
- code;
1339
- constructor(code, message) {
1340
- super(message);
1341
- this.code = code;
1342
- this.name = "UpdateFailure";
1343
- }
1344
- };
1345
-
1346
- // ../core/lib/usage.js
1347
- async function* parseSse(body, activity) {
1348
- const decoder = new TextDecoder();
1349
- const reader = body.getReader();
1350
- let buffer = "";
1351
- try {
1352
- while (true) {
1353
- const { value, done } = await reader.read();
1354
- if (done)
1355
- break;
1356
- activity();
1357
- buffer += decoder.decode(value, { stream: true });
1358
- let boundary = nextBoundary(buffer);
1359
- while (boundary !== null) {
1360
- const block = buffer.slice(0, boundary.index);
1361
- buffer = buffer.slice(boundary.index + boundary.length);
1362
- const event = parseBlock(block);
1363
- if (event !== null)
1364
- yield event;
1365
- boundary = nextBoundary(buffer);
1366
- }
1367
- }
1368
- buffer += decoder.decode();
1369
- const tail = parseBlock(buffer);
1370
- if (tail !== null)
1371
- yield tail;
1372
- } finally {
1373
- reader.releaseLock();
1374
- }
1375
- }
1376
- function nextBoundary(buffer) {
1377
- const match = /\r?\n\r?\n/u.exec(buffer);
1378
- return match === null ? null : { index: match.index, length: match[0].length };
1379
- }
1380
- function parseBlock(block) {
1381
- let event = null;
1382
- const data = [];
1383
- for (const line of block.split(/\r?\n/u)) {
1384
- const notice = /^: ?adaptiq-memory (.*)$/u.exec(line);
1385
- if (notice !== null) {
1386
- event = "adaptiq.memory";
1387
- data.push(notice[1]);
1388
- continue;
1389
- }
1390
- if (line === "" || line.startsWith(":"))
1391
- continue;
1392
- const colon = line.indexOf(":");
1393
- const field = colon === -1 ? line : line.slice(0, colon);
1394
- const value = colon === -1 ? "" : line.slice(colon + 1).replace(/^ /u, "");
1395
- if (field === "event")
1396
- event = value;
1397
- else if (field === "data")
1398
- data.push(value);
1399
- }
1400
- return data.length === 0 ? null : { event, data: data.join("\n") };
1401
- }
1402
- function mapUsage(raw) {
1403
- const n = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.round(value) : 0;
1404
- const details = typeof raw.prompt_tokens_details === "object" && raw.prompt_tokens_details !== null ? raw.prompt_tokens_details : {};
1405
- const completion = typeof raw.completion_tokens_details === "object" && raw.completion_tokens_details !== null ? raw.completion_tokens_details : {};
1406
- const prompt = n(raw.prompt_tokens);
1407
- const output = n(raw.completion_tokens);
1408
- const cached = Math.min(prompt, n(details.cached_tokens) || n(raw.prompt_cache_hit_tokens));
1409
- const written = Math.min(prompt - cached, n(details.cache_write_tokens) || n(raw.cache_write_tokens) || n(raw.cache_creation_input_tokens));
1410
- const reasoning2 = n(completion.reasoning_tokens);
1411
- const total = n(raw.total_tokens);
1412
- return {
1413
- inputTokens: Math.max(0, prompt - cached - written),
1414
- outputTokens: output,
1415
- ...cached > 0 ? { cacheReadTokens: cached } : {},
1416
- ...written > 0 ? { cacheWriteTokens: written } : {},
1417
- ...reasoning2 > 0 ? { reasoningTokens: reasoning2 } : {},
1418
- ...total > 0 ? { totalTokens: total } : prompt + output > 0 ? { totalTokens: prompt + output } : {}
1419
- };
1420
- }
1421
- function memoryNotice(raw) {
1422
- let value = raw;
1423
- if (typeof raw === "string") {
1424
- try {
1425
- value = JSON.parse(raw);
1426
- } catch {
1427
- return null;
1428
- }
1429
- }
1430
- if (typeof value !== "object" || value === null)
1431
- return null;
1432
- const notice = value;
1433
- if (notice.kind !== "hibernated" && notice.kind !== "suggestion")
1434
- return null;
1435
- const sessions = Array.isArray(notice.sessions) ? notice.sessions.length : 0;
1436
- if (sessions === 0)
1437
- return null;
1438
- const number = (field) => typeof field === "number" && Number.isFinite(field) && field >= 0 ? field : null;
1439
- return { kind: notice.kind, sessions, tokens: number(notice.tokens), usageRatio: number(notice.usage_ratio) };
1440
- }
1441
-
1442
1331
  // ../core/lib/workspace.js
1443
1332
  import { randomUUID as randomUUID3 } from "node:crypto";
1444
1333
 
@@ -1545,9 +1434,9 @@ var PurchaseFlow = class {
1545
1434
  }
1546
1435
  const limit = laneLimit(offer);
1547
1436
  if (limit === 0)
1548
- throw new Error(`${offer.name} has no free lanes right now. Try again later, or pick another model.`);
1437
+ throw new Error(`${offer.name} is full right now. Try again later, or pick another model.`);
1549
1438
  if (!Number.isInteger(lanes) || lanes < offer.minLanes || lanes > limit) {
1550
- throw new Error(`Choose between ${offer.minLanes} and ${limit} lanes.`);
1439
+ throw new Error(`Choose between ${offer.minLanes} and ${limit} conversations at once.`);
1551
1440
  }
1552
1441
  const base = {
1553
1442
  offerId: offer.id,
@@ -1668,14 +1557,34 @@ var PurchaseFlow = class {
1668
1557
  }
1669
1558
  };
1670
1559
 
1560
+ // ../core/lib/update.js
1561
+ var RELEASE_AGE_MS = 24 * 36e5;
1562
+ var RELEASE_VERSION = /^\d+\.\d+\.\d+$/u;
1563
+ function semverGreater(a, b) {
1564
+ const parse = (value) => value.replace(/^v/u, "").split(/[.-]/u).slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
1565
+ const [x, y] = [parse(a), parse(b)];
1566
+ for (let i = 0; i < 3; i++)
1567
+ if ((x[i] ?? 0) !== (y[i] ?? 0))
1568
+ return (x[i] ?? 0) > (y[i] ?? 0);
1569
+ return false;
1570
+ }
1571
+ var UpdateFailure = class extends Error {
1572
+ code;
1573
+ constructor(code, message) {
1574
+ super(message);
1575
+ this.code = code;
1576
+ this.name = "UpdateFailure";
1577
+ }
1578
+ };
1579
+
1671
1580
  // ../core/lib/workspace.js
1672
1581
  var obj4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1673
1582
  var str4 = (value) => typeof value === "string" && value.trim() !== "" ? value.trim() : null;
1674
1583
  function say(title, body, params = {}) {
1675
1584
  return { title: fill(en[title], params), body: body === null ? "" : fill(en[body], params), text: { title, body, params } };
1676
1585
  }
1677
- function button(id, label, extra = {}) {
1678
- return { id, label: en[label], labelKey: label, ...extra };
1586
+ function button(id, label2, extra = {}) {
1587
+ return { id, label: en[label2], labelKey: label2, ...extra };
1679
1588
  }
1680
1589
  var count2 = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
1681
1590
  var PRIVATE_HELD = "A private conversation is not kept on AdaptiqAI, so it can't use this before it exists there. Turn Remember on for this conversation to add it.";
@@ -1842,7 +1751,7 @@ function copyFailure(error, model2) {
1842
1751
  case "model_mismatch":
1843
1752
  return str4(detail.message) ?? `This collection can't be made available for ${model2}.`;
1844
1753
  case "inference_entitlement_required":
1845
- return `Your workspace has no lanes on ${model2}.`;
1754
+ return "This model isn't in your workspace yet. Get access to continue; you pay per token from your balance.";
1846
1755
  case "collection_not_found":
1847
1756
  return "This collection no longer exists.";
1848
1757
  case "sources_not_exportable":
@@ -2733,13 +2642,13 @@ var Workspace = class {
2733
2642
  this.publish();
2734
2643
  }
2735
2644
  /** A browser approval completed. */
2736
- signedIn(label) {
2645
+ signedIn(label2) {
2737
2646
  if (this.state.defaultModel === void 0) {
2738
2647
  this.state = { ...this.state, defaultModel: "pending" };
2739
2648
  void this.persist();
2740
2649
  }
2741
2650
  this.clearNotices("sign-in");
2742
- this.raise({ kind: "connected", tone: "success", ...say("connectedTo", "n_connected_body", { workspace: label }), actions: [] });
2651
+ this.raise({ kind: "connected", tone: "success", ...say("connectedTo", "n_connected_body", { workspace: label2 }), actions: [] });
2743
2652
  this.summaryMode = "unknown";
2744
2653
  this.summaryMissingAt = null;
2745
2654
  void this.refresh();
@@ -2821,7 +2730,9 @@ var Workspace = class {
2821
2730
  /**
2822
2731
  * Whether this conversation is known to be stored on AdaptiqAI. A private
2823
2732
  * turn of a conversation that is not stored yet runs ephemeral: there is
2824
- * nothing to read back and nothing may be saved.
2733
+ * nothing to read back and nothing may be saved. While the last answer was
2734
+ * "not stored", the server is asked again: a remembered turn since may have
2735
+ * stored it, and a plugin that reports no turns would not hear of it.
2825
2736
  * @param sessionId - harness session id.
2826
2737
  * @returns true when the server has the conversation.
2827
2738
  */
@@ -2829,7 +2740,7 @@ var Workspace = class {
2829
2740
  if (sessionId2 === void 0)
2830
2741
  return false;
2831
2742
  const session = this.session(sessionId2);
2832
- if (session.remoteAt === 0 && this.deps.auth.phase() === "connected") {
2743
+ if ((session.remoteAt === 0 || !session.exists) && this.deps.auth.phase() === "connected") {
2833
2744
  await Promise.race([this.loadSession(session).catch(() => {
2834
2745
  }), new Promise((resolve2) => setTimeout(resolve2, 2500))]);
2835
2746
  }
@@ -3325,6 +3236,14 @@ var Workspace = class {
3325
3236
  actions: [button("open-console", "n_openConsole", { url: this.links.console, primary: true })]
3326
3237
  });
3327
3238
  }
3239
+ /**
3240
+ * Say that a message's images were not sent because the model reads text only.
3241
+ * @param sessionId - the conversation, when known.
3242
+ * @param modelName - the model's display name.
3243
+ */
3244
+ imagesNotSent(sessionId2, modelName) {
3245
+ this.raise({ kind: "images", tone: "warning", ...say("n_imagesTextOnly_title", "n_imagesTextOnly_body", { model: modelName }), actions: [], ...sessionId2 === void 0 ? {} : { sessionId: sessionId2 } });
3246
+ }
3328
3247
  failed(sessionId2, model2, failure) {
3329
3248
  if (failure.code === CODES.compaction) {
3330
3249
  this.raise({ kind: "compaction", tone: "info", ...say("n_compaction_title", "n_compaction_body"), actions: [], ...sessionId2 === void 0 ? {} : { sessionId: sessionId2 } });
@@ -3366,11 +3285,13 @@ var Workspace = class {
3366
3285
  break;
3367
3286
  case "no-lanes": {
3368
3287
  const offer = this.offers.find((item) => item.model === model2) ?? this.offers.find((item) => item.name.toLowerCase() === name2.toLowerCase());
3288
+ const body = failure.serverCode === "rental_stopped_unused" ? "n_laneStopped_body" : failure.planRenewsAt !== void 0 ? "n_planUsedUp_body" : failure.serverCode === "plan_usage_exhausted" ? "n_planUsedUpNoDate_body" : "n_noLanes_body";
3289
+ const until = failure.planRenewsAt === void 0 ? "" : `${new Date(failure.planRenewsAt * 1e3).toISOString().slice(0, 16).replace("T", " ")} UTC`;
3369
3290
  this.raise({
3370
3291
  kind: "no-lanes",
3371
3292
  tone: "warning",
3372
3293
  modal: true,
3373
- ...say("n_noLanes_title", failure.serverCode === "rental_stopped_unused" ? "n_laneStopped_body" : "n_noLanes_body", { model: name2 }),
3294
+ ...say("n_noLanes_title", body, { until }),
3374
3295
  actions: [button("get-access", "getAccess", { primary: true, ...offer === void 0 ? {} : { offerId: offer.id } }), button("dismiss", "notNow")]
3375
3296
  });
3376
3297
  break;
@@ -3380,7 +3301,7 @@ var Workspace = class {
3380
3301
  kind: "out-of-credit",
3381
3302
  tone: "error",
3382
3303
  modal: true,
3383
- ...failure.minutesLeft === void 0 ? say("n_credit_title", "n_credit_body", { model: name2 }) : say("n_credit_title", "n_creditMinutes_body", { model: name2, minutes: String(failure.minutesLeft) }),
3304
+ ...failure.minutesLeft === void 0 ? say("n_credit_title", "n_credit_body") : say("n_credit_title", "n_creditMinutes_body", { minutes: String(failure.minutesLeft) }),
3384
3305
  actions: [button("add-funds", "n_addFunds", { url: this.links.models, primary: true }), button("dismiss", "notNow")]
3385
3306
  });
3386
3307
  break;
@@ -3560,7 +3481,7 @@ var Workspace = class {
3560
3481
  if (target === void 0)
3561
3482
  throw new Error("Choose an AdaptiqAI model first.");
3562
3483
  const modelName = this.modelName(target);
3563
- const label = modelName ?? target;
3484
+ const label2 = modelName ?? target;
3564
3485
  let body;
3565
3486
  try {
3566
3487
  body = await this.deps.api.json(`/v1/memories/${encodeURIComponent(id)}/copies`, {
@@ -3576,7 +3497,7 @@ var Workspace = class {
3576
3497
  this.copies.set(copyKey(id, target), { source: id, from, target, copy: null, status: "building" });
3577
3498
  return { status: "building", model: target, modelName };
3578
3499
  }
3579
- throw new Error(copyFailure(error, label));
3500
+ throw new Error(copyFailure(error, label2));
3580
3501
  }
3581
3502
  const copy = obj4(body?.copy);
3582
3503
  const status = copyStatus(str4(copy?.status));
@@ -4543,6 +4464,105 @@ var Workspace = class {
4543
4464
  }
4544
4465
  };
4545
4466
 
4467
+ // ../core/lib/tools.js
4468
+ var SOURCE_LIMIT = 6 * 1024 * 1024;
4469
+
4470
+ // ../core/lib/usage.js
4471
+ async function* parseSse(body, activity) {
4472
+ const decoder = new TextDecoder();
4473
+ const reader = body.getReader();
4474
+ let buffer = "";
4475
+ try {
4476
+ while (true) {
4477
+ const { value, done } = await reader.read();
4478
+ if (done)
4479
+ break;
4480
+ activity();
4481
+ buffer += decoder.decode(value, { stream: true });
4482
+ let boundary = nextBoundary(buffer);
4483
+ while (boundary !== null) {
4484
+ const block = buffer.slice(0, boundary.index);
4485
+ buffer = buffer.slice(boundary.index + boundary.length);
4486
+ const event = parseBlock(block);
4487
+ if (event !== null)
4488
+ yield event;
4489
+ boundary = nextBoundary(buffer);
4490
+ }
4491
+ }
4492
+ buffer += decoder.decode();
4493
+ const tail = parseBlock(buffer);
4494
+ if (tail !== null)
4495
+ yield tail;
4496
+ } finally {
4497
+ reader.releaseLock();
4498
+ }
4499
+ }
4500
+ function nextBoundary(buffer) {
4501
+ const match = /\r?\n\r?\n/u.exec(buffer);
4502
+ return match === null ? null : { index: match.index, length: match[0].length };
4503
+ }
4504
+ function parseBlock(block) {
4505
+ let event = null;
4506
+ const data = [];
4507
+ for (const line of block.split(/\r?\n/u)) {
4508
+ const notice = /^: ?adaptiq-memory (.*)$/u.exec(line);
4509
+ if (notice !== null) {
4510
+ event = "adaptiq.memory";
4511
+ data.push(notice[1]);
4512
+ continue;
4513
+ }
4514
+ if (line === "" || line.startsWith(":"))
4515
+ continue;
4516
+ const colon = line.indexOf(":");
4517
+ const field = colon === -1 ? line : line.slice(0, colon);
4518
+ const value = colon === -1 ? "" : line.slice(colon + 1).replace(/^ /u, "");
4519
+ if (field === "event")
4520
+ event = value;
4521
+ else if (field === "data")
4522
+ data.push(value);
4523
+ }
4524
+ return data.length === 0 ? null : { event, data: data.join("\n") };
4525
+ }
4526
+ function mapUsage(raw) {
4527
+ const n = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.round(value) : 0;
4528
+ const details = typeof raw.prompt_tokens_details === "object" && raw.prompt_tokens_details !== null ? raw.prompt_tokens_details : {};
4529
+ const completion = typeof raw.completion_tokens_details === "object" && raw.completion_tokens_details !== null ? raw.completion_tokens_details : {};
4530
+ const prompt = n(raw.prompt_tokens);
4531
+ const output = n(raw.completion_tokens);
4532
+ const cached = Math.min(prompt, n(details.cached_tokens) || n(raw.prompt_cache_hit_tokens));
4533
+ const written = Math.min(prompt - cached, n(details.cache_write_tokens) || n(raw.cache_write_tokens) || n(raw.cache_creation_input_tokens));
4534
+ const reasoning2 = n(completion.reasoning_tokens);
4535
+ const total = n(raw.total_tokens);
4536
+ return {
4537
+ inputTokens: Math.max(0, prompt - cached - written),
4538
+ outputTokens: output,
4539
+ ...cached > 0 ? { cacheReadTokens: cached } : {},
4540
+ ...written > 0 ? { cacheWriteTokens: written } : {},
4541
+ ...reasoning2 > 0 ? { reasoningTokens: reasoning2 } : {},
4542
+ ...total > 0 ? { totalTokens: total } : prompt + output > 0 ? { totalTokens: prompt + output } : {}
4543
+ };
4544
+ }
4545
+ function memoryNotice(raw) {
4546
+ let value = raw;
4547
+ if (typeof raw === "string") {
4548
+ try {
4549
+ value = JSON.parse(raw);
4550
+ } catch {
4551
+ return null;
4552
+ }
4553
+ }
4554
+ if (typeof value !== "object" || value === null)
4555
+ return null;
4556
+ const notice = value;
4557
+ if (notice.kind !== "hibernated" && notice.kind !== "suggestion")
4558
+ return null;
4559
+ const sessions = Array.isArray(notice.sessions) ? notice.sessions.length : 0;
4560
+ if (sessions === 0)
4561
+ return null;
4562
+ const number = (field) => typeof field === "number" && Number.isFinite(field) && field >= 0 ? field : null;
4563
+ return { kind: notice.kind, sessions, tokens: number(notice.tokens), usageRatio: number(notice.usage_ratio) };
4564
+ }
4565
+
4546
4566
  // src/host/stream.ts
4547
4567
  var REASONING_FIELDS = ["reasoning_content", "reasoning", "reasoning_text"];
4548
4568
  async function* translate(events, hooks) {
@@ -4671,7 +4691,70 @@ async function* translate(events, hooks) {
4671
4691
  };
4672
4692
  }
4673
4693
 
4694
+ // src/host/images.ts
4695
+ var ENGINE_IMAGE_LIMITS = Object.freeze({
4696
+ maxImages: 8,
4697
+ maxImageBytes: 20 * 1024 * 1024,
4698
+ maxPixels: 16e6
4699
+ });
4700
+ var ImageProblem = class extends Error {
4701
+ name = "ImageProblem";
4702
+ };
4703
+ var startsWith = (data, bytes, at = 0) => data.length >= at + bytes.length && bytes.every((byte, index) => data[at + index] === byte);
4704
+ var ascii = (text2) => [...text2].map((char) => char.charCodeAt(0));
4705
+ function sniffImageType(data) {
4706
+ if (startsWith(data, [137, 80, 78, 71, 13, 10, 26, 10])) return "image/png";
4707
+ if (startsWith(data, [255, 216, 255])) return "image/jpeg";
4708
+ if (startsWith(data, ascii("GIF87a")) || startsWith(data, ascii("GIF89a"))) return "image/gif";
4709
+ if (startsWith(data, ascii("RIFF")) && startsWith(data, ascii("WEBP"), 8)) return "image/webp";
4710
+ return null;
4711
+ }
4712
+ function fitPixels(width, height, maxPixels) {
4713
+ if (width * height <= maxPixels) return { width, height };
4714
+ const scale = Math.sqrt(maxPixels / (width * height));
4715
+ let w = Math.max(1, Math.floor(width * scale));
4716
+ let h = Math.max(1, Math.floor(height * scale));
4717
+ while (w * h > maxPixels) {
4718
+ if (w >= h) w--;
4719
+ else h--;
4720
+ }
4721
+ return { width: w, height: h };
4722
+ }
4723
+ var carriesImages = (message) => message.role === "user" || message.role === "tool";
4724
+ function sentImages(messages) {
4725
+ const out = [];
4726
+ for (const message of messages) {
4727
+ if (!carriesImages(message)) continue;
4728
+ for (const block of message.content) if (block.type === "image" && block.offloaded !== true) out.push(block);
4729
+ }
4730
+ return out;
4731
+ }
4732
+ function hasImages(messages) {
4733
+ return messages.some((message) => message.content.some((block) => block.type === "image"));
4734
+ }
4735
+ var mib = (bytes) => `${Math.round(bytes / (1024 * 1024) * 10) / 10} MB`;
4736
+ var label = (ref) => ref.name === void 0 ? "An image" : `The image "${ref.name}"`;
4737
+ async function prepareImages(messages, source, limits, signal) {
4738
+ const refs = /* @__PURE__ */ new Map();
4739
+ for (const block of sentImages(messages)) refs.set(String(block.attachment.attachmentId), block.attachment);
4740
+ const entries = await Promise.all([...refs].map(async ([id, ref]) => {
4741
+ const target = { ...fitPixels(ref.width, ref.height, limits.maxPixels), maxBytes: limits.maxImageBytes };
4742
+ const version = await source.readImageRequest(ref, target, signal);
4743
+ const mediaType = sniffImageType(version.data);
4744
+ if (mediaType === null) throw new ImageProblem(`${label(ref)} is not a PNG, JPEG, WebP or GIF file, so AdaptiqAI can't read it. Attach it in one of those formats.`);
4745
+ if (version.data.byteLength > limits.maxImageBytes) {
4746
+ throw new ImageProblem(`${label(ref)} is ${mib(version.data.byteLength)}; AdaptiqAI takes images up to ${mib(limits.maxImageBytes)}. Attach a smaller copy.`);
4747
+ }
4748
+ if (version.width * version.height > limits.maxPixels) {
4749
+ throw new ImageProblem(`${label(ref)} is ${version.width}\xD7${version.height} pixels, more than AdaptiqAI takes. Attach a smaller copy.`);
4750
+ }
4751
+ return [id, { url: `data:${mediaType};base64,${Buffer.from(version.data).toString("base64")}`, width: version.width, height: version.height }];
4752
+ }));
4753
+ return new Map(entries);
4754
+ }
4755
+
4674
4756
  // src/host/wire.ts
4757
+ import { offloadedImageText, requestImageHandleText, textOnlyImageText } from "@deepseek-ai/dsh-llm";
4675
4758
  function sessionReferences(message) {
4676
4759
  if (message.role !== "user") return null;
4677
4760
  const source = message.source;
@@ -4763,16 +4846,62 @@ function userTurns(messages) {
4763
4846
  }
4764
4847
  var text = (blocks) => blocks.filter((block) => block.type === "text").map((block) => block.text).join("");
4765
4848
  var reasoning = (blocks) => blocks.filter((block) => block.type === "reasoning").map((block) => block.text).join("");
4849
+ var Parts = class {
4850
+ parts = [];
4851
+ glue = false;
4852
+ text(value, own = false) {
4853
+ if (value === "") return;
4854
+ const last = this.parts.at(-1);
4855
+ if (last?.type === "text") last.text += (own || this.glue ? "\n" : "") + value;
4856
+ else this.parts.push({ type: "text", text: value });
4857
+ this.glue = own;
4858
+ }
4859
+ image(url) {
4860
+ this.parts.push({ type: "image_url", image_url: { url } });
4861
+ this.glue = false;
4862
+ }
4863
+ get hasImage() {
4864
+ return this.parts.some((part) => part.type === "image_url");
4865
+ }
4866
+ get joined() {
4867
+ return this.parts.map((part) => part.type === "text" ? part.text : "").join("");
4868
+ }
4869
+ };
4870
+ function putImage(block, images, parts, inline) {
4871
+ const access2 = images?.access?.(block.attachment);
4872
+ if (block.offloaded === true) {
4873
+ parts.text(offloadedImageText(block.attachment, access2), true);
4874
+ return null;
4875
+ }
4876
+ const sent = images?.sent?.get(String(block.attachment.attachmentId));
4877
+ if (sent === void 0) {
4878
+ parts.text(textOnlyImageText(block.attachment), true);
4879
+ return null;
4880
+ }
4881
+ parts.text(requestImageHandleText(block.attachment, sent, access2), true);
4882
+ if (!inline) return sent;
4883
+ parts.image(sent.url);
4884
+ return null;
4885
+ }
4766
4886
  function replayField(message) {
4767
4887
  const source = message.source;
4768
4888
  const state2 = source?.replayState;
4769
4889
  const field = state2?.response?.reasoningField;
4770
4890
  return typeof field === "string" && /^[a-z_]{1,32}$/u.test(field) ? field : "reasoning_content";
4771
4891
  }
4772
- function wireMessages(options, recalled = /* @__PURE__ */ new Map()) {
4892
+ function wireMessages(options, recalled = /* @__PURE__ */ new Map(), images) {
4773
4893
  const out = [];
4774
4894
  if (options.system !== void 0 && options.system !== "") out.push({ role: "system", content: options.system });
4895
+ let toolImages = [];
4896
+ const flushToolImages = () => {
4897
+ if (toolImages.length === 0) return;
4898
+ const parts = [{ type: "text", text: toolImages.length === 1 ? "The image from the tool result above:" : "The images from the tool results above, in order:" }];
4899
+ for (const image of toolImages) parts.push({ type: "image_url", image_url: { url: image.url } });
4900
+ out.push({ role: "user", content: parts });
4901
+ toolImages = [];
4902
+ };
4775
4903
  options.messages.forEach((message, index) => {
4904
+ if (message.role !== "tool") flushToolImages();
4776
4905
  switch (message.role) {
4777
4906
  case "system": {
4778
4907
  const content = text(message.content);
@@ -4784,6 +4913,15 @@ function wireMessages(options, recalled = /* @__PURE__ */ new Map()) {
4784
4913
  return;
4785
4914
  }
4786
4915
  case "user": {
4916
+ if (message.content.some((block) => block.type === "image")) {
4917
+ const parts = new Parts();
4918
+ for (const block of message.content) {
4919
+ if (block.type === "text") parts.text(block.text);
4920
+ else if (block.type === "image") putImage(block, images, parts, true);
4921
+ }
4922
+ out.push({ role: "user", content: parts.hasImage ? parts.parts : parts.joined });
4923
+ return;
4924
+ }
4787
4925
  const references = recalled.size === 0 ? null : sessionReferences(message);
4788
4926
  const content = text(message.content);
4789
4927
  out.push({ role: "user", content: references === null ? content : recallSnapshot(content, references, recalled) });
@@ -4805,7 +4943,15 @@ function wireMessages(options, recalled = /* @__PURE__ */ new Map()) {
4805
4943
  return;
4806
4944
  }
4807
4945
  case "tool": {
4808
- const content = text(message.content);
4946
+ const parts = new Parts();
4947
+ for (const block of message.content) {
4948
+ if (block.type === "text") parts.text(block.text);
4949
+ else if (block.type === "image") {
4950
+ const deferred = putImage(block, images, parts, false);
4951
+ if (deferred !== null) toolImages.push(deferred);
4952
+ }
4953
+ }
4954
+ const content = parts.joined;
4809
4955
  out.push({ role: "tool", tool_call_id: message.toolCallId, content: content === "" ? "(no output)" : content });
4810
4956
  return;
4811
4957
  }
@@ -4813,12 +4959,13 @@ function wireMessages(options, recalled = /* @__PURE__ */ new Map()) {
4813
4959
  return;
4814
4960
  }
4815
4961
  });
4962
+ flushToolImages();
4816
4963
  return out;
4817
4964
  }
4818
4965
  function buildChatBody(options, model2, extras) {
4819
4966
  const body = {
4820
4967
  model: options.model,
4821
- messages: wireMessages(options, extras.recalled),
4968
+ messages: wireMessages(options, extras.recalled, extras.images),
4822
4969
  stream: true,
4823
4970
  stream_options: { include_usage: extras.includeUsage ?? true }
4824
4971
  };
@@ -4910,7 +5057,6 @@ function resolvedInfo(route, model2) {
4910
5057
  function describe(model2) {
4911
5058
  const parts = [];
4912
5059
  if (model2.nativeMemory || (model2.contextWindow ?? 0) >= 1e6) parts.push("Native memory");
4913
- if (model2.lanes !== null) parts.push(`${model2.lanes} ${model2.lanes === 1 ? "lane" : "lanes"}`);
4914
5060
  return parts.join(" \xB7 ");
4915
5061
  }
4916
5062
  function toLlmError(failure, cause) {
@@ -4970,6 +5116,41 @@ var AdaptiqAdapter = class extends LlmAdapter {
4970
5116
  stream(options) {
4971
5117
  return this.generate(options);
4972
5118
  }
5119
+ /**
5120
+ * The request's images, read from the harness's stored copies, when the
5121
+ * model's card says it reads images. Otherwise each image goes as a short
5122
+ * note and the person is told.
5123
+ */
5124
+ async images(options, model2, sessionId2, fail2, signal) {
5125
+ if (!hasImages(options.messages)) return void 0;
5126
+ const access2 = this.deps.imageAccess === void 0 ? void 0 : (ref) => {
5127
+ try {
5128
+ return this.deps.imageAccess?.(ref);
5129
+ } catch {
5130
+ return void 0;
5131
+ }
5132
+ };
5133
+ const withAccess = access2 === void 0 ? {} : { access: access2 };
5134
+ const problem = (message) => fail2({ code: CODES.invalidRequest, message, status: null, requestId: null, retryAfterMs: null, serverType: null, serverCode: null, notice: null });
5135
+ if (model2?.input.includes("image") !== true) {
5136
+ if (sentImages(options.messages).length > 0) this.deps.events.imagesNotSent?.(sessionId2, model2?.name ?? options.model);
5137
+ return { sent: null, ...withAccess };
5138
+ }
5139
+ const limits = this.deps.imageLimits ?? ENGINE_IMAGE_LIMITS;
5140
+ const excess = sentImages(options.messages).length - limits.maxImages;
5141
+ if (excess > 0) {
5142
+ throw new LlmError(`AdaptiqAI takes up to ${limits.maxImages} images per request; the ${excess} oldest ${excess === 1 ? "one goes" : "go"} as a note naming ${excess === 1 ? "it" : "them"}.`, IMAGE_OFFLOAD_REQUIRED_CODE, { offloadImages: excess });
5143
+ }
5144
+ const source = this.deps.attachments?.();
5145
+ if (source === void 0) throw problem("This DeepSeek Harness has no attachment service, so images can't be sent. Update DeepSeek Harness and try again.");
5146
+ try {
5147
+ return { sent: await prepareImages(options.messages, source, limits, signal), ...withAccess };
5148
+ } catch (error) {
5149
+ if (error instanceof ImageProblem) throw problem(error.message);
5150
+ if (signal.aborted) throw new LlmError("Request stopped.", CODES.aborted, { cause: error });
5151
+ throw problem(`An attached image could not be read: ${error instanceof Error ? error.message : String(error)}. Attach it again.`);
5152
+ }
5153
+ }
4973
5154
  async *generate(options) {
4974
5155
  if (options.purpose === "compaction") {
4975
5156
  const message = "AdaptiqAI keeps the whole conversation in native memory, so it never needs compacting. Nothing was removed.";
@@ -5044,7 +5225,8 @@ var AdaptiqAdapter = class extends LlmAdapter {
5044
5225
  if (references.length > 0) recalled = await this.deps.recall(sessionId2, options.model, references, signal).catch(() => NOTHING_RECALLED);
5045
5226
  }
5046
5227
  const parent = options.sessionId === void 0 ? void 0 : this.deps.parentSession?.(String(options.sessionId));
5047
- let body = JSON.stringify(buildChatBody(options, model2, { remember, stored, recalled }));
5228
+ const images = await this.images(options, model2, sessionId2, fail2, signal);
5229
+ let body = JSON.stringify(buildChatBody(options, model2, { remember, stored, recalled, images }));
5048
5230
  this.deps.debug?.(`adaptiq: request ${sessionId2 ?? "-"}: ${options.messages.length} messages, ${body.length} bytes, ${recalled.size} read from memory, transcripts sent: ${body.includes("<referenced-sessions>") ? "yes" : "no"}`);
5049
5231
  let response;
5050
5232
  let refreshed = false;
@@ -5085,7 +5267,7 @@ var AdaptiqAdapter = class extends LlmAdapter {
5085
5267
  await response.body?.cancel().catch(() => {
5086
5268
  });
5087
5269
  stored = false;
5088
- body = JSON.stringify(buildChatBody(options, model2, { remember, stored, recalled }));
5270
+ body = JSON.stringify(buildChatBody(options, model2, { remember, stored, recalled, images }));
5089
5271
  continue;
5090
5272
  }
5091
5273
  break;
@@ -5145,7 +5327,7 @@ var AdaptiqAdapter = class extends LlmAdapter {
5145
5327
 
5146
5328
  // src/host/commands.ts
5147
5329
  var HELP = [
5148
- "/adaptiq \u2014 workspace, models, lanes and memory",
5330
+ "/adaptiq \u2014 workspace, models and memory",
5149
5331
  "/adaptiq signin \u2014 connect your AdaptiqAI account in the browser",
5150
5332
  "/adaptiq private \u2014 stop saving new messages of this conversation",
5151
5333
  "/adaptiq remember \u2014 save this conversation to memory again",
@@ -5251,10 +5433,9 @@ ${HELP}`;
5251
5433
  lines.push("No models yet. Pick one in Settings \u2192 AdaptiqAI.");
5252
5434
  } else {
5253
5435
  for (const model2 of snapshot.models) {
5254
- const lanes = model2.lanes === null ? "" : ` \xB7 ${model2.lanes} ${model2.lanes === 1 ? "lane" : "lanes"}`;
5255
5436
  const live = model2.runningNow === null ? "" : ` (${model2.runningNow} running, ${model2.waitingNow ?? 0} waiting)`;
5256
5437
  const window = model2.contextWindow === null ? "" : ` \xB7 ${formatTokens(model2.contextWindow)} context`;
5257
- lines.push(`\u2022 ${model2.name}${window}${lanes}${live}${model2.status === "offline" ? " \xB7 offline" : ""}`);
5438
+ lines.push(`\u2022 ${model2.name}${window}${live}${model2.status === "offline" ? " \xB7 offline" : ""}`);
5258
5439
  }
5259
5440
  }
5260
5441
  if (memory?.available === true && memory.conversationsTokens !== null) {
@@ -5588,7 +5769,7 @@ var render = (_args, value) => {
5588
5769
  return [{ type: "text", text: typeof text2 === "string" ? text2 : JSON.stringify(value) }];
5589
5770
  };
5590
5771
  var say2 = (result) => ({ result });
5591
- var SOURCE_LIMIT = 6 * 1024 * 1024;
5772
+ var SOURCE_LIMIT2 = 6 * 1024 * 1024;
5592
5773
  var MEDIA = {
5593
5774
  ".pdf": "application/pdf",
5594
5775
  ".txt": "text/plain",
@@ -5845,7 +6026,7 @@ ${content}`);
5845
6026
  const info = await stat(path);
5846
6027
  if (!info.isFile()) fail(`${raw} is not a file.`);
5847
6028
  bytes += info.size;
5848
- if (bytes > SOURCE_LIMIT) fail("These files are too large for one request. Use fewer or smaller files, or a repository link.");
6029
+ if (bytes > SOURCE_LIMIT2) fail("These files are too large for one request. Use fewer or smaller files, or a repository link.");
5849
6030
  const content = await readFile2(path);
5850
6031
  files.push({ name: source.name ?? basename(path), mediaType: mediaTypeOf(path), contentBase64: content.toString("base64") });
5851
6032
  } else {
@@ -6091,7 +6272,7 @@ Open **Settings \u2192 AdaptiqAI** to connect, or run \`/adaptiq signin\`.
6091
6272
  var ENTRY_PROTOCOL = 1;
6092
6273
  var RELOAD_SIGNATURE = true ? "ada3dfffa1b3d7b1" : "source";
6093
6274
  function pluginVersion() {
6094
- if (true) return "0.2.12";
6275
+ if (true) return "0.2.14";
6095
6276
  try {
6096
6277
  return createRequire(import.meta.url)("../package.json").version;
6097
6278
  } catch {
@@ -6114,14 +6295,25 @@ async function harnessSessions(ctx) {
6114
6295
  return new Map(records.map((record) => {
6115
6296
  let title = null;
6116
6297
  try {
6117
- const label = typeof labels?.projectedLabels === "function" ? labels.projectedLabels(record).label : void 0;
6118
- if (typeof label === "string" && label.trim() !== "" && label !== record.header.id) title = label.trim();
6298
+ const label2 = typeof labels?.projectedLabels === "function" ? labels.projectedLabels(record).label : void 0;
6299
+ if (typeof label2 === "string" && label2.trim() !== "" && label2 !== record.header.id) title = label2.trim();
6119
6300
  } catch {
6120
6301
  }
6121
6302
  const subagent = record.header.origin === "subagent" || (record.header.delegationDepth ?? 0) > 0;
6122
6303
  return [record.header.id, { title, subagent }];
6123
6304
  }));
6124
6305
  }
6306
+ function attachmentService(ctx) {
6307
+ const service = ctx.get("attachments");
6308
+ return typeof service?.readImageRequest === "function" ? service : void 0;
6309
+ }
6310
+ function imageAccess(ctx, ref) {
6311
+ const host = attachmentService(ctx)?.imageHostPath?.(ref);
6312
+ if (host === void 0) return void 0;
6313
+ const fs = ctx.get("fs");
6314
+ const readonlyPath = fs?.processPathFromHostPath?.(host);
6315
+ return readonlyPath === void 0 ? void 0 : { readonlyPath };
6316
+ }
6125
6317
  var VERSION = pluginVersion();
6126
6318
  async function apply(ctx, config) {
6127
6319
  await applyWith(ctx, config, void 0);
@@ -6146,8 +6338,8 @@ async function applyWith(ctx, raw, runtime) {
6146
6338
  onChange: () => {
6147
6339
  workspace?.authChanged();
6148
6340
  },
6149
- onSignedIn: (label) => {
6150
- workspace?.signedIn(label);
6341
+ onSignedIn: (label2) => {
6342
+ workspace?.signedIn(label2);
6151
6343
  }
6152
6344
  });
6153
6345
  const api = new AdaptiqApi(apiBase, auth, () => ({
@@ -6229,6 +6421,8 @@ async function applyWith(ctx, raw, runtime) {
6229
6421
  attachPending: (sessionId2) => owned.attachPending(sessionId2),
6230
6422
  events: owned,
6231
6423
  autoSleep: (model2) => owned.autoSleep(model2),
6424
+ attachments: () => attachmentService(ctx),
6425
+ imageAccess: (ref) => imageAccess(ctx, ref),
6232
6426
  ...process.env.DSH_ADAPTIQ_DEBUG_LOG === void 0 || process.env.DSH_ADAPTIQ_DEBUG_LOG === "" ? {} : { debug: (message) => {
6233
6427
  log("info", message);
6234
6428
  } }