@adaptiqai/deepseek-harness 0.2.13 → 0.2.15

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
@@ -124,42 +124,42 @@ function classifyFailure(status, raw, headers, model2, options = {}) {
124
124
  }
125
125
  if (detail.code === "plan_usage_exhausted") {
126
126
  const renewsAt = envelope.detail?.renews_at;
127
- const failure = make(CODES.noLanes, detail.message ?? "Your plan's usage is used up for now. Rent extra lanes in the meantime.", "no-lanes");
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
128
  return typeof renewsAt === "number" ? { ...failure, planRenewsAt: renewsAt } : failure;
129
129
  }
130
130
  if (effective === 402 || detail.code === "rental_paused") {
131
131
  const releaseAt = envelope.detail?.release_at;
132
132
  const minutes = typeof releaseAt === "number" ? Math.max(1, Math.ceil((releaseAt * 1e3 - Date.now()) / 6e4)) : null;
133
- const keep = minutes === null ? "to continue" : `within ${minutes} minute${minutes === 1 ? "" : "s"} to keep them`;
134
- const failure = make(CODES.outOfCredit, `Your balance ran out, so your lanes 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");
135
135
  return minutes === null ? failure : { ...failure, minutesLeft: minutes };
136
136
  }
137
137
  if (effective === 401) {
138
138
  return make(CODES.signIn, "Your AdaptiqAI sign-in has expired. Open Settings \u2192 AdaptiqAI and sign in again.", "sign-in");
139
139
  }
140
140
  if (detail.code === "rental_stopped_unused") {
141
- return make(CODES.noLanes, "Your lane 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");
142
142
  }
143
143
  if (has(detail, "AdaptiqLaneAccessRequired", "inference_entitlement_required", "instance_access_required", "lane_access") || effective === 403 && has(detail, "lane", "entitle", "model access")) {
144
- return make(CODES.noLanes, "You need to rent a lane or subscribe to AdaptiqAI to continue.", "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");
145
145
  }
146
146
  if (effective === 403) {
147
147
  return make(CODES.signIn, "This AdaptiqAI connection isn't allowed to do that. Sign in again from Settings \u2192 AdaptiqAI.", "sign-in");
148
148
  }
149
149
  if (effective === 429 || has(detail, "AdaptiqLaneQueueTimeout", "InflightLimit", "rate_limit")) {
150
- 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");
151
151
  }
152
152
  if (effective === 503 && detail.code !== null && MODEL_OFFLINE_CODES.includes(detail.code)) {
153
153
  return make(CODES.server, `${model2 ?? "This model"} is offline right now. It will retry automatically.`, "retrying");
154
154
  }
155
- if (has(detail, "AdaptiqNativePreflightBusy", "session_route_unavailable", "conversation_preparing", "preparing", "upload_capacity_exhausted") || effective === 503 && !has(detail, "offline")) {
155
+ if (has(detail, "AdaptiqNativePreflightBusy", "session_route_unavailable", "conversation_preparing", "preparing", "upload_capacity_exhausted", "AdaptiqMemoryNotReady") || effective === 425 || effective === 503 && !has(detail, "offline")) {
156
156
  return make(CODES.preparing, "AdaptiqAI is getting your conversation ready. It will retry automatically.", "preparing");
157
157
  }
158
158
  if (effective === 409 && has(detail, "AdaptiqNativePreflight", "conversation")) {
159
159
  return make(CODES.conversationUnavailable, "This conversation couldn't be opened on AdaptiqAI right now. Try again shortly; your saved memory is unchanged.", null);
160
160
  }
161
161
  if (effective === 404 && (has(detail, "model", "not_found", "does not exist") || detail.message === null)) {
162
- 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");
163
163
  }
164
164
  if (effective === 400 || effective === 413 || effective === 422) {
165
165
  const said = detail.message !== null ? ` (${detail.message.slice(0, 200)})` : "";
@@ -373,6 +373,91 @@ function detailMessage(raw) {
373
373
  }
374
374
  return null;
375
375
  }
376
+ var EXPIRED = "The sign-in link expired. Start again.";
377
+ async function startDeviceCode(client, request) {
378
+ let response;
379
+ try {
380
+ response = await client.fetch(`${client.apiBase}/v1/cli-auth/device`, {
381
+ method: "POST",
382
+ headers: { "content-type": "application/json", accept: "application/json" },
383
+ body: JSON.stringify({
384
+ client_app: request.app,
385
+ client_name: request.app,
386
+ device_name: request.device,
387
+ client_binding: request.binding,
388
+ ...request.tokenLifetime === void 0 ? {} : { token_lifetime: request.tokenLifetime }
389
+ }),
390
+ signal: client.signal === void 0 ? AbortSignal.timeout(2e4) : AbortSignal.any([client.signal, AbortSignal.timeout(2e4)]),
391
+ redirect: "error"
392
+ });
393
+ } catch (error) {
394
+ if (client.signal?.aborted === true)
395
+ throw error;
396
+ throw new Error(UNREACHABLE, { cause: error });
397
+ }
398
+ if (!response.ok) {
399
+ const message = detailMessage(await readJson(response));
400
+ if (response.status >= 500)
401
+ throw new Error("AdaptiqAI isn't answering right now. Try again in a minute.");
402
+ throw new Error(message ?? `AdaptiqAI sign-in could not start (HTTP ${response.status}).`);
403
+ }
404
+ const body = await readJson(response);
405
+ const deviceCode = String(body?.device_code ?? "");
406
+ const userCode = String(body?.user_code ?? "");
407
+ const url = String(body?.verification_uri_complete ?? body?.verification_uri ?? "");
408
+ if (deviceCode === "" || userCode === "" || !/^https?:\/\//u.test(url))
409
+ throw new Error("AdaptiqAI returned an incomplete sign-in.");
410
+ return {
411
+ deviceCode,
412
+ userCode,
413
+ url,
414
+ expiresAt: client.now() + Math.min(DEVICE_TIMEOUT_MS, Math.max(60, Number(body?.expires_in ?? 600)) * 1e3),
415
+ interval: Math.max(1, Number(body?.interval ?? 2)) * 1e3
416
+ };
417
+ }
418
+ async function pollDeviceCode(client, code, extra = {}) {
419
+ let response;
420
+ try {
421
+ response = await client.fetch(`${client.apiBase}/v1/cli-auth/device/token`, {
422
+ method: "POST",
423
+ headers: { "content-type": "application/json", accept: "application/json" },
424
+ body: JSON.stringify({ ...extra, device_code: code.deviceCode }),
425
+ signal: client.signal === void 0 ? AbortSignal.timeout(2e4) : AbortSignal.any([client.signal, AbortSignal.timeout(2e4)]),
426
+ redirect: "error"
427
+ });
428
+ } catch {
429
+ return { state: "waiting", delayMs: code.interval * 2 };
430
+ }
431
+ if (response.status === 202)
432
+ return { state: "waiting", delayMs: code.interval };
433
+ if (response.status === 429) {
434
+ const retry = Number(response.headers.get("retry-after") ?? 0) * 1e3;
435
+ return { state: "waiting", delayMs: Math.max(code.interval, retry) + 250 };
436
+ }
437
+ if (response.status === 403) {
438
+ const raw = await readJson(response);
439
+ return { state: "denied", message: detailCode(raw) === "access_denied" ? "The connection was declined in the browser." : detailMessage(raw) ?? "The connection was declined." };
440
+ }
441
+ if (response.status === 410 || response.status === 404)
442
+ return { state: "expired", message: EXPIRED };
443
+ if (!response.ok) {
444
+ if (response.status >= 500)
445
+ return { state: "waiting", delayMs: code.interval * 2 };
446
+ return { state: "failed", message: detailMessage(await readJson(response)) ?? `Sign-in failed (HTTP ${response.status}).` };
447
+ }
448
+ const body = await readJson(response);
449
+ const access2 = String(body?.access_token ?? "");
450
+ const refresh = String(body?.refresh_token ?? "");
451
+ if (!access2.startsWith("ak_") || refresh !== "" && !refresh.startsWith("akr_"))
452
+ return { state: "failed", message: "AdaptiqAI returned an invalid credential." };
453
+ return {
454
+ state: "approved",
455
+ access: access2,
456
+ refresh: refresh === "" ? null : refresh,
457
+ expiresIn: Math.max(60, Number(body?.expires_in ?? 3600)),
458
+ workspaceLabel: typeof body?.workspace_label === "string" && body.workspace_label !== "" ? body.workspace_label : "Your workspace"
459
+ };
460
+ }
376
461
  var AuthManager = class {
377
462
  options;
378
463
  fetch;
@@ -415,6 +500,9 @@ var AuthManager = class {
415
500
  this.state = phase;
416
501
  this.options.onChange?.();
417
502
  }
503
+ client(signal) {
504
+ return { apiBase: this.options.apiBase, fetch: this.fetch, now: this.now, signal };
505
+ }
418
506
  /** Read the stored state once at startup. */
419
507
  async init() {
420
508
  const stored = await this.options.store.read();
@@ -548,39 +636,11 @@ var AuthManager = class {
548
636
  const clientProof = `${randomUUID()}${randomUUID()}`;
549
637
  const binding = createHash("sha256").update(clientProof).digest("hex");
550
638
  const app = this.options.appName ?? "DeepSeek Harness";
551
- let response;
552
- try {
553
- response = await this.fetch(`${this.options.apiBase}/v1/cli-auth/device`, {
554
- method: "POST",
555
- headers: { "content-type": "application/json", accept: "application/json" },
556
- body: JSON.stringify({
557
- client_app: app,
558
- client_name: app,
559
- device_name: deviceLabel(this.options.deviceName ?? hostname()),
560
- client_binding: binding
561
- }),
562
- signal: AbortSignal.any([flow.signal, AbortSignal.timeout(2e4)]),
563
- redirect: "error"
564
- });
565
- } catch (error) {
566
- if (flow.signal.aborted)
567
- throw error;
568
- throw new Error(UNREACHABLE, { cause: error });
569
- }
570
- if (!response.ok) {
571
- const message = detailMessage(await readJson(response));
572
- if (response.status >= 500)
573
- throw new Error("AdaptiqAI isn't answering right now. Try again in a minute.");
574
- throw new Error(message ?? `AdaptiqAI sign-in could not start (HTTP ${response.status}).`);
575
- }
576
- const body = await readJson(response);
577
- const deviceCode = String(body?.device_code ?? "");
578
- const userCode = String(body?.user_code ?? "");
579
- const url = String(body?.verification_uri_complete ?? body?.verification_uri ?? "");
580
- if (deviceCode === "" || userCode === "" || !/^https?:\/\//u.test(url))
581
- throw new Error("AdaptiqAI returned an incomplete sign-in.");
582
- const expiresAt = this.now() + Math.min(DEVICE_TIMEOUT_MS, Math.max(60, Number(body?.expires_in ?? 600)) * 1e3);
583
- const interval = Math.max(1, Number(body?.interval ?? 2)) * 1e3;
639
+ const { deviceCode, userCode, url, expiresAt, interval } = await startDeviceCode(this.client(flow.signal), {
640
+ app,
641
+ device: deviceLabel(this.options.deviceName ?? hostname()),
642
+ binding
643
+ });
584
644
  this.pendingSignIn = { url, userCode, expiresAt };
585
645
  this.set("signing-in");
586
646
  void this.poll(flow, deviceCode, clientProof, interval, expiresAt);
@@ -592,61 +652,27 @@ var AuthManager = class {
592
652
  while (this.now() < expiresAt) {
593
653
  await this.sleep(delay, flow.signal);
594
654
  delay = interval;
595
- let response;
596
- try {
597
- response = await this.fetch(`${this.options.apiBase}/v1/cli-auth/device/token`, {
598
- method: "POST",
599
- headers: { "content-type": "application/json", accept: "application/json" },
600
- body: JSON.stringify({ device_code: deviceCode }),
601
- signal: AbortSignal.any([flow.signal, AbortSignal.timeout(2e4)]),
602
- redirect: "error"
603
- });
604
- } catch {
605
- if (flow.signal.aborted)
606
- return;
607
- delay = interval * 2;
655
+ const answer = await pollDeviceCode(this.client(flow.signal), { deviceCode, interval });
656
+ if (answer.state === "waiting") {
657
+ delay = answer.delayMs;
608
658
  continue;
609
659
  }
610
- if (response.status === 202)
611
- continue;
612
- if (response.status === 429) {
613
- const retry = Number(response.headers.get("retry-after") ?? 0) * 1e3;
614
- delay = Math.max(interval, retry) + 250;
615
- continue;
616
- }
617
- if (response.status === 403) {
618
- const raw = await readJson(response);
619
- this.fail(flow, { code: "denied", message: detailCode(raw) === "access_denied" ? "The connection was declined in the browser." : detailMessage(raw) ?? "The connection was declined." });
620
- return;
621
- }
622
- if (response.status === 410 || response.status === 404) {
623
- this.fail(flow, { code: "expired", message: "The sign-in link expired. Start again." });
624
- return;
625
- }
626
- if (!response.ok) {
627
- if (response.status >= 500) {
628
- delay = interval * 2;
629
- continue;
630
- }
631
- this.fail(flow, { code: "failed", message: detailMessage(await readJson(response)) ?? `Sign-in failed (HTTP ${response.status}).` });
660
+ if (answer.state !== "approved") {
661
+ this.fail(flow, { code: answer.state, message: answer.message });
632
662
  return;
633
663
  }
634
- const body = await readJson(response);
635
- const access2 = String(body?.access_token ?? "");
636
- const refresh = String(body?.refresh_token ?? "");
637
- if (!access2.startsWith("ak_") || !refresh.startsWith("akr_")) {
664
+ if (answer.refresh === null) {
638
665
  this.fail(flow, { code: "failed", message: "AdaptiqAI returned an invalid credential." });
639
666
  return;
640
667
  }
641
- const workspaceLabel = typeof body?.workspace_label === "string" && body.workspace_label !== "" ? body.workspace_label : "Your workspace";
642
668
  const grant = {
643
669
  schema: "adaptiq-dsh-grant-v1",
644
670
  apiBase: this.options.apiBase,
645
- access: access2,
646
- refresh,
671
+ access: answer.access,
672
+ refresh: answer.refresh,
647
673
  clientProof,
648
- expiresAt: this.now() + Math.max(60, Number(body?.expires_in ?? 3600)) * 1e3,
649
- workspaceLabel,
674
+ expiresAt: this.now() + answer.expiresIn * 1e3,
675
+ workspaceLabel: answer.workspaceLabel,
650
676
  obtainedAt: this.now()
651
677
  };
652
678
  await this.options.store.modify(async () => grant);
@@ -655,12 +681,12 @@ var AuthManager = class {
655
681
  this.flow = null;
656
682
  this.pendingSignIn = null;
657
683
  this.source = "device";
658
- this.label = workspaceLabel;
684
+ this.label = answer.workspaceLabel;
659
685
  this.set("connected");
660
- this.options.onSignedIn?.(workspaceLabel);
686
+ this.options.onSignedIn?.(answer.workspaceLabel);
661
687
  return;
662
688
  }
663
- this.fail(flow, { code: "expired", message: "The sign-in link expired. Start again." });
689
+ this.fail(flow, { code: "expired", message: EXPIRED });
664
690
  } catch (error) {
665
691
  if (flow.signal.aborted)
666
692
  return;
@@ -785,6 +811,20 @@ var obj = (value) => typeof value === "object" && value !== null && !Array.isArr
785
811
  var str = (value) => typeof value === "string" && value.trim() !== "" ? value.trim() : null;
786
812
  var num = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
787
813
  var count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
814
+ function pricesFromRow(raw) {
815
+ const pricing = obj(raw);
816
+ const price = (value) => {
817
+ const parsed = typeof value === "string" && value.trim() !== "" ? Number(value) : typeof value === "number" ? value : Number.NaN;
818
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
819
+ };
820
+ const input = price(pricing?.prompt);
821
+ const memoryWrite = price(pricing?.input_cache_write);
822
+ const memoryRead = price(pricing?.input_cache_read);
823
+ const output = price(pricing?.completion);
824
+ if (input === null || memoryWrite === null || memoryRead === null || output === null)
825
+ return null;
826
+ return { input, memoryWrite, memoryRead, output };
827
+ }
788
828
  var LEVEL_NAMES = {
789
829
  off: "Off",
790
830
  minimal: "Minimal",
@@ -873,17 +913,17 @@ function placementsFromRoutes(routes) {
873
913
  const model2 = str(route?.model);
874
914
  if (route === null || model2 === null || route.enabled !== true)
875
915
  continue;
876
- const add = (a, b) => a === null ? b : b === null ? a : a + b;
916
+ const add2 = (a, b) => a === null ? b : b === null ? a : a + b;
877
917
  const current = out.get(model2);
878
918
  const running = count(route.running);
879
919
  const waiting = count(route.waiting);
880
920
  out.set(model2, {
881
921
  name: current?.name ?? (str(route.name) !== null ? routeModelName(str(route.name)) : null),
882
922
  status: "running",
883
- lanes: add(current?.lanes ?? null, count(route.lanes)),
884
- maxParallel: add(current?.maxParallel ?? null, count(route.max_parallel_agents)),
885
- runningNow: add(current?.runningNow ?? null, running),
886
- waitingNow: add(current?.waitingNow ?? null, waiting),
923
+ lanes: add2(current?.lanes ?? null, count(route.lanes)),
924
+ maxParallel: add2(current?.maxParallel ?? null, count(route.max_parallel_agents)),
925
+ runningNow: add2(current?.runningNow ?? null, running),
926
+ waitingNow: add2(current?.waitingNow ?? null, waiting),
887
927
  region: current?.region ?? str(route.region),
888
928
  card: null,
889
929
  contextWindow: null,
@@ -944,7 +984,8 @@ function model(id, row, placement) {
944
984
  maxParallel: placement?.maxParallel ?? count(gateway?.max_parallel_agents),
945
985
  runningNow: placement?.runningNow ?? null,
946
986
  waitingNow: placement?.waitingNow ?? null,
947
- region: placement?.region ?? str(gateway?.region)
987
+ region: placement?.region ?? str(gateway?.region),
988
+ prices: pricesFromRow(row?.pricing)
948
989
  };
949
990
  }
950
991
  function selectableLevels(reasoning2) {
@@ -1020,6 +1061,60 @@ function laneLimit(offer) {
1020
1061
  return free < offer.minLanes ? 0 : Math.min(offer.maxLanes, free);
1021
1062
  }
1022
1063
 
1064
+ // ../core/lib/processed.js
1065
+ var NOTHING_PROCESSED = { requests: 0, newInput: 0, fromMemory: 0, written: 0, output: 0, reasoning: 0, costUsd: 0 };
1066
+ function stepCost(usage2, prices) {
1067
+ if (prices === null)
1068
+ return null;
1069
+ return usage2.inputTokens * prices.input + (usage2.cacheWriteTokens ?? 0) * prices.memoryWrite + (usage2.cacheReadTokens ?? 0) * prices.memoryRead + usage2.outputTokens * prices.output;
1070
+ }
1071
+ function add(view, step) {
1072
+ return {
1073
+ requests: view.requests + 1,
1074
+ newInput: view.newInput + step.usage.inputTokens,
1075
+ fromMemory: view.fromMemory + (step.usage.cacheReadTokens ?? 0),
1076
+ written: view.written + (step.usage.cacheWriteTokens ?? 0),
1077
+ output: view.output + step.usage.outputTokens,
1078
+ reasoning: view.reasoning + (step.usage.reasoningTokens ?? 0),
1079
+ costUsd: view.costUsd === null || step.costUsd === null ? null : view.costUsd + step.costUsd
1080
+ };
1081
+ }
1082
+ function addStep(record, step, now) {
1083
+ const turnKey = step.turn ?? record?.turnKey ?? null;
1084
+ const fresh = record == null || step.turn !== void 0 && step.turn !== record.turnKey;
1085
+ return {
1086
+ since: record?.since ?? now,
1087
+ at: now,
1088
+ turnKey,
1089
+ turn: add(fresh ? NOTHING_PROCESSED : record.turn, step),
1090
+ session: add(record?.session ?? NOTHING_PROCESSED, step)
1091
+ };
1092
+ }
1093
+ function readProcessed(raw) {
1094
+ const record = typeof raw === "object" && raw !== null ? raw : null;
1095
+ const view = (value) => {
1096
+ const fields = typeof value === "object" && value !== null ? value : null;
1097
+ if (fields === null)
1098
+ return null;
1099
+ const count3 = (name2) => {
1100
+ const field = fields[name2];
1101
+ return typeof field === "number" && Number.isFinite(field) && field >= 0 ? field : null;
1102
+ };
1103
+ const numbers = { requests: count3("requests"), newInput: count3("newInput"), fromMemory: count3("fromMemory"), written: count3("written"), output: count3("output"), reasoning: count3("reasoning") };
1104
+ if (Object.values(numbers).some((value2) => value2 === null))
1105
+ return null;
1106
+ return { ...numbers, costUsd: count3("costUsd") };
1107
+ };
1108
+ const turn = view(record?.turn);
1109
+ const session = view(record?.session);
1110
+ const since = record?.since;
1111
+ const at = record?.at;
1112
+ if (turn === null || session === null || typeof since !== "number" || typeof at !== "number")
1113
+ return null;
1114
+ const turnKey = typeof record?.turnKey === "number" ? record.turnKey : null;
1115
+ return { since, at, turnKey, turn, session };
1116
+ }
1117
+
1023
1118
  // ../core/lib/relations.js
1024
1119
  var PARENT_SESSION_HEADER = "X-Adaptiq-Parent-Session-Id";
1025
1120
  var SESSION_ROLE_HEADER = "X-Adaptiq-Session-Role";
@@ -1066,13 +1161,11 @@ var en = {
1066
1161
  expiredTitle: "Your sign-in expired",
1067
1162
  signInAgain: "Sign in again",
1068
1163
  tryAgain: "Try again",
1069
- models: "Models & lanes",
1164
+ models: "Models",
1070
1165
  status_running: "Running",
1071
1166
  status_offline: "Offline",
1072
1167
  status_disabled: "Paused",
1073
1168
  status_unknown: "Available",
1074
- lanesOne: "{count} lane",
1075
- lanesMany: "{count} lanes",
1076
1169
  thinkingLevels: "Thinking {levels}",
1077
1170
  noModels: "Pick your first model",
1078
1171
  getAccess: "Get access",
@@ -1098,10 +1191,10 @@ var en = {
1098
1191
  routeConflictTitle: "AdaptiqAI models are hidden",
1099
1192
  pillMemory: "Memory",
1100
1193
  pillPreparing: "Preparing memory",
1101
- pillQueued: "Waiting for a free lane",
1194
+ pillQueued: "Waiting in line",
1102
1195
  pillStarting: "Starting\u2026",
1103
1196
  pillSignIn: "Sign in to AdaptiqAI",
1104
- pillFromMemory: "{tokens} from memory",
1197
+ pillFromMemory: "{tokens} from memory \xB7 free",
1105
1198
  pillTooltip: "AdaptiqAI memory for this conversation",
1106
1199
  popTitle: "Memory",
1107
1200
  popSaving: "Saving",
@@ -1110,6 +1203,22 @@ var en = {
1110
1203
  popKnowledge: "Knowledge",
1111
1204
  addKnowledge: "Add knowledge",
1112
1205
  popWorkspace: "All memory on {model}",
1206
+ popSeesNow: "What the model sees now",
1207
+ procTitle: "Processed",
1208
+ procTurn: "Last turn",
1209
+ procSession: "This conversation",
1210
+ procSteps: "{count} steps",
1211
+ procStepsOne: "1 step",
1212
+ procNewInput: "New input",
1213
+ procFromMemory: "Read from memory",
1214
+ procWritten: "Saved to memory",
1215
+ procOutput: "Output",
1216
+ procThinking: "Thinking, part of output",
1217
+ procCost: "Cost",
1218
+ procExplain: "Each step reads the whole memory again. {percent} came from memory.",
1219
+ procExplainFree: "Each step reads the whole memory again. {percent} came from memory, which is free.",
1220
+ procExplainCheaper: "Each step reads the whole memory again. {percent} came from memory, at a fraction of the input price.",
1221
+ procSince: "Counted on this computer since {date}",
1113
1222
  openInKnowledge: "Open in Knowledge",
1114
1223
  kTitle: "Knowledge",
1115
1224
  kSearch: "Search your knowledge",
@@ -1177,22 +1286,22 @@ var en = {
1177
1286
  accessTitle: "Get a model",
1178
1287
  offerSpeed: "{speed} tokens/s",
1179
1288
  offerMemory: "{tokens} memory",
1180
- offerPrice: "{price} per lane per {period}",
1289
+ offerPrice: "{price} per {period}",
1181
1290
  offerOwned: "In your workspace",
1182
1291
  offerFull: "Full right now",
1183
1292
  period_hour: "hour",
1184
1293
  period_month: "month",
1185
1294
  period_once: "purchase",
1186
- lanesLabel: "Lanes",
1187
- lanesHint: "Each lane runs one conversation at a time.",
1295
+ lanesLabel: "At once",
1296
+ lanesHint: "How many conversations can run at the same time.",
1188
1297
  totalPrice: "{price} per {period}",
1189
1298
  noOffers: "No models are on offer right now. Check back soon.",
1190
- manualNote: "Our team activates new lanes after you confirm.",
1191
- balanceNote: "Runs from your balance and stops by itself after 15 minutes without requests, or when you stop it.",
1299
+ manualNote: "Our team turns it on after you confirm.",
1300
+ balanceNote: "You pay per token from your balance. Reads from memory are free.",
1192
1301
  cardNote: "You pay by card in your browser.",
1193
- addLanes: "Add lanes",
1302
+ addLanes: "Run more at once",
1194
1303
  purchase_approving: "Confirm in your browser",
1195
- purchase_approving_body: "We opened a page to confirm {lanes} on {model}. This updates by itself.",
1304
+ purchase_approving_body: "We opened a page to confirm {model}. This updates by itself.",
1196
1305
  purchase_console_body: "Finish on the page we opened. {model} appears here as soon as it's active.",
1197
1306
  purchase_requested: "Requested. We'll activate it shortly",
1198
1307
  purchase_requested_body: "You'll get an email too. {model} appears here by itself.",
@@ -1202,10 +1311,8 @@ var en = {
1202
1311
  purchase_ready_body: "Pick it in the model picker and start.",
1203
1312
  purchase_denied: "The purchase was declined in the browser.",
1204
1313
  purchase_expired: "The confirmation page expired.",
1205
- purchase_stopped: "The lanes have stopped.",
1314
+ purchase_stopped: "The model has stopped.",
1206
1315
  purchase_failed: "The purchase didn't go through.",
1207
- lanesCount: "{count} lanes",
1208
- laneCount: "1 lane",
1209
1316
  openPage: "Open the page again",
1210
1317
  stopWaiting: "Stop waiting",
1211
1318
  startChatting: "Start a conversation",
@@ -1284,16 +1391,16 @@ var en = {
1284
1391
  n_signIn_title: "Sign in to keep going",
1285
1392
  n_signIn_body: "Your AdaptiqAI connection needs a fresh sign-in.",
1286
1393
  n_connect_body: "Connect your AdaptiqAI account to use this model.",
1287
- n_noLanes_title: "Rent a lane to continue",
1288
- n_noLanes_body: "You need to rent a lane or subscribe to AdaptiqAI to continue.",
1289
- n_laneStopped_body: "Your lane stopped because it went unused, so nothing more was charged.",
1290
- n_planUsedUp_body: "Your plan's usage is used up until {until}. Rent extra lanes in the meantime.",
1291
- n_planUsedUpNoDate_body: "Your plan's usage is used up for now. Rent extra lanes in the meantime.",
1394
+ n_noLanes_title: "Get access to continue",
1395
+ n_noLanes_body: "This model isn't in your workspace yet. You pay per token from your balance; reads from memory are free.",
1396
+ n_laneStopped_body: "The model stopped after going unused. Start it again to continue.",
1397
+ n_planUsedUp_body: "Your plan's usage is used up until {until}. Pay per token from your balance in the meantime.",
1398
+ n_planUsedUpNoDate_body: "Your plan's usage is used up for now. Pay per token from your balance in the meantime.",
1292
1399
  n_credit_title: "Add funds to continue",
1293
- n_credit_body: "Your balance ran out, so your lanes are paused.",
1294
- n_creditMinutes_body: "Your balance ran out, so your lanes are paused. Add funds within {minutes} min to keep them.",
1295
- n_busy_title: "All lanes are busy",
1296
- n_busy_body: "Your request on {model} is waiting for a free lane and will retry by itself.",
1400
+ n_credit_body: "Your balance ran out. Add funds to keep going.",
1401
+ n_creditMinutes_body: "Your balance ran out. Add funds within {minutes} min to keep your models running.",
1402
+ n_busy_title: "{model} is busy",
1403
+ n_busy_body: "Your request is waiting in line and retries by itself.",
1297
1404
  n_preparing_title: "Getting your conversation ready",
1298
1405
  n_preparing_body: "AdaptiqAI is loading this conversation's memory. It retries by itself.",
1299
1406
  n_unavailable_title: "{model} isn't available",
@@ -1332,122 +1439,6 @@ function fill(template, params = {}) {
1332
1439
  return template.replace(/\{(\w+)\}/gu, (whole, name2) => name2 in params ? String(params[name2]) : whole);
1333
1440
  }
1334
1441
 
1335
- // ../core/lib/update.js
1336
- var RELEASE_AGE_MS = 24 * 36e5;
1337
- var RELEASE_VERSION = /^\d+\.\d+\.\d+$/u;
1338
- function semverGreater(a, b) {
1339
- const parse = (value) => value.replace(/^v/u, "").split(/[.-]/u).slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
1340
- const [x, y] = [parse(a), parse(b)];
1341
- for (let i = 0; i < 3; i++)
1342
- if ((x[i] ?? 0) !== (y[i] ?? 0))
1343
- return (x[i] ?? 0) > (y[i] ?? 0);
1344
- return false;
1345
- }
1346
- var UpdateFailure = class extends Error {
1347
- code;
1348
- constructor(code, message) {
1349
- super(message);
1350
- this.code = code;
1351
- this.name = "UpdateFailure";
1352
- }
1353
- };
1354
-
1355
- // ../core/lib/usage.js
1356
- async function* parseSse(body, activity) {
1357
- const decoder = new TextDecoder();
1358
- const reader = body.getReader();
1359
- let buffer = "";
1360
- try {
1361
- while (true) {
1362
- const { value, done } = await reader.read();
1363
- if (done)
1364
- break;
1365
- activity();
1366
- buffer += decoder.decode(value, { stream: true });
1367
- let boundary = nextBoundary(buffer);
1368
- while (boundary !== null) {
1369
- const block = buffer.slice(0, boundary.index);
1370
- buffer = buffer.slice(boundary.index + boundary.length);
1371
- const event = parseBlock(block);
1372
- if (event !== null)
1373
- yield event;
1374
- boundary = nextBoundary(buffer);
1375
- }
1376
- }
1377
- buffer += decoder.decode();
1378
- const tail = parseBlock(buffer);
1379
- if (tail !== null)
1380
- yield tail;
1381
- } finally {
1382
- reader.releaseLock();
1383
- }
1384
- }
1385
- function nextBoundary(buffer) {
1386
- const match = /\r?\n\r?\n/u.exec(buffer);
1387
- return match === null ? null : { index: match.index, length: match[0].length };
1388
- }
1389
- function parseBlock(block) {
1390
- let event = null;
1391
- const data = [];
1392
- for (const line of block.split(/\r?\n/u)) {
1393
- const notice = /^: ?adaptiq-memory (.*)$/u.exec(line);
1394
- if (notice !== null) {
1395
- event = "adaptiq.memory";
1396
- data.push(notice[1]);
1397
- continue;
1398
- }
1399
- if (line === "" || line.startsWith(":"))
1400
- continue;
1401
- const colon = line.indexOf(":");
1402
- const field = colon === -1 ? line : line.slice(0, colon);
1403
- const value = colon === -1 ? "" : line.slice(colon + 1).replace(/^ /u, "");
1404
- if (field === "event")
1405
- event = value;
1406
- else if (field === "data")
1407
- data.push(value);
1408
- }
1409
- return data.length === 0 ? null : { event, data: data.join("\n") };
1410
- }
1411
- function mapUsage(raw) {
1412
- const n = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.round(value) : 0;
1413
- const details = typeof raw.prompt_tokens_details === "object" && raw.prompt_tokens_details !== null ? raw.prompt_tokens_details : {};
1414
- const completion = typeof raw.completion_tokens_details === "object" && raw.completion_tokens_details !== null ? raw.completion_tokens_details : {};
1415
- const prompt = n(raw.prompt_tokens);
1416
- const output = n(raw.completion_tokens);
1417
- const cached = Math.min(prompt, n(details.cached_tokens) || n(raw.prompt_cache_hit_tokens));
1418
- const written = Math.min(prompt - cached, n(details.cache_write_tokens) || n(raw.cache_write_tokens) || n(raw.cache_creation_input_tokens));
1419
- const reasoning2 = n(completion.reasoning_tokens);
1420
- const total = n(raw.total_tokens);
1421
- return {
1422
- inputTokens: Math.max(0, prompt - cached - written),
1423
- outputTokens: output,
1424
- ...cached > 0 ? { cacheReadTokens: cached } : {},
1425
- ...written > 0 ? { cacheWriteTokens: written } : {},
1426
- ...reasoning2 > 0 ? { reasoningTokens: reasoning2 } : {},
1427
- ...total > 0 ? { totalTokens: total } : prompt + output > 0 ? { totalTokens: prompt + output } : {}
1428
- };
1429
- }
1430
- function memoryNotice(raw) {
1431
- let value = raw;
1432
- if (typeof raw === "string") {
1433
- try {
1434
- value = JSON.parse(raw);
1435
- } catch {
1436
- return null;
1437
- }
1438
- }
1439
- if (typeof value !== "object" || value === null)
1440
- return null;
1441
- const notice = value;
1442
- if (notice.kind !== "hibernated" && notice.kind !== "suggestion")
1443
- return null;
1444
- const sessions = Array.isArray(notice.sessions) ? notice.sessions.length : 0;
1445
- if (sessions === 0)
1446
- return null;
1447
- const number = (field) => typeof field === "number" && Number.isFinite(field) && field >= 0 ? field : null;
1448
- return { kind: notice.kind, sessions, tokens: number(notice.tokens), usageRatio: number(notice.usage_ratio) };
1449
- }
1450
-
1451
1442
  // ../core/lib/workspace.js
1452
1443
  import { randomUUID as randomUUID3 } from "node:crypto";
1453
1444
 
@@ -1554,9 +1545,9 @@ var PurchaseFlow = class {
1554
1545
  }
1555
1546
  const limit = laneLimit(offer);
1556
1547
  if (limit === 0)
1557
- throw new Error(`${offer.name} has no free lanes right now. Try again later, or pick another model.`);
1548
+ throw new Error(`${offer.name} is full right now. Try again later, or pick another model.`);
1558
1549
  if (!Number.isInteger(lanes) || lanes < offer.minLanes || lanes > limit) {
1559
- throw new Error(`Choose between ${offer.minLanes} and ${limit} lanes.`);
1550
+ throw new Error(`Choose between ${offer.minLanes} and ${limit} conversations at once.`);
1560
1551
  }
1561
1552
  const base = {
1562
1553
  offerId: offer.id,
@@ -1677,6 +1668,26 @@ var PurchaseFlow = class {
1677
1668
  }
1678
1669
  };
1679
1670
 
1671
+ // ../core/lib/update.js
1672
+ var RELEASE_AGE_MS = 24 * 36e5;
1673
+ var RELEASE_VERSION = /^\d+\.\d+\.\d+$/u;
1674
+ function semverGreater(a, b) {
1675
+ const parse = (value) => value.replace(/^v/u, "").split(/[.-]/u).slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
1676
+ const [x, y] = [parse(a), parse(b)];
1677
+ for (let i = 0; i < 3; i++)
1678
+ if ((x[i] ?? 0) !== (y[i] ?? 0))
1679
+ return (x[i] ?? 0) > (y[i] ?? 0);
1680
+ return false;
1681
+ }
1682
+ var UpdateFailure = class extends Error {
1683
+ code;
1684
+ constructor(code, message) {
1685
+ super(message);
1686
+ this.code = code;
1687
+ this.name = "UpdateFailure";
1688
+ }
1689
+ };
1690
+
1680
1691
  // ../core/lib/workspace.js
1681
1692
  var obj4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1682
1693
  var str4 = (value) => typeof value === "string" && value.trim() !== "" ? value.trim() : null;
@@ -1687,6 +1698,21 @@ function button(id, label2, extra = {}) {
1687
1698
  return { id, label: en[label2], labelKey: label2, ...extra };
1688
1699
  }
1689
1700
  var count2 = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
1701
+ var PROCESSED_SAVE_MS = 2e3;
1702
+ function savedProcessed(raw) {
1703
+ const out = {};
1704
+ if (typeof raw !== "object" || raw === null)
1705
+ return out;
1706
+ for (const [id, value] of Object.entries(raw)) {
1707
+ const record = readProcessed(value);
1708
+ if (record !== null)
1709
+ out[id] = record;
1710
+ }
1711
+ return out;
1712
+ }
1713
+ function processedSummary(record) {
1714
+ return record === void 0 ? null : { turn: record.turn, session: record.session, since: record.since };
1715
+ }
1690
1716
  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.";
1691
1717
  var ATTACH_TRIES = 3;
1692
1718
  function transient(error) {
@@ -1851,7 +1877,7 @@ function copyFailure(error, model2) {
1851
1877
  case "model_mismatch":
1852
1878
  return str4(detail.message) ?? `This collection can't be made available for ${model2}.`;
1853
1879
  case "inference_entitlement_required":
1854
- return "You need to rent a lane or subscribe to AdaptiqAI to continue.";
1880
+ return "This model isn't in your workspace yet. Get access to continue; you pay per token from your balance.";
1855
1881
  case "collection_not_found":
1856
1882
  return "This collection no longer exists.";
1857
1883
  case "sources_not_exportable":
@@ -2052,6 +2078,7 @@ var Workspace = class {
2052
2078
  listeners = /* @__PURE__ */ new Set();
2053
2079
  state = { preferences: DEFAULT_PREFERENCES, sessions: {} };
2054
2080
  saving = Promise.resolve();
2081
+ persistTimer;
2055
2082
  refreshing = null;
2056
2083
  timer;
2057
2084
  pendingAttach = /* @__PURE__ */ new Map();
@@ -2111,7 +2138,8 @@ var Workspace = class {
2111
2138
  preferences: { ...DEFAULT_PREFERENCES, ...saved.preferences },
2112
2139
  sessions: typeof saved.sessions === "object" && saved.sessions !== null ? saved.sessions : {},
2113
2140
  purchase: saved.purchase ?? null,
2114
- ...saved.defaultModel === "pending" || saved.defaultModel === "done" ? { defaultModel: saved.defaultModel } : {}
2141
+ ...saved.defaultModel === "pending" || saved.defaultModel === "done" ? { defaultModel: saved.defaultModel } : {},
2142
+ processed: savedProcessed(saved.processed)
2115
2143
  };
2116
2144
  this.purchases.restore(saved.purchase);
2117
2145
  }
@@ -2127,6 +2155,10 @@ var Workspace = class {
2127
2155
  this.purchases.dispose();
2128
2156
  clearTimeout(this.timer);
2129
2157
  clearTimeout(this.attachTimer);
2158
+ if (this.persistTimer !== void 0) {
2159
+ clearTimeout(this.persistTimer);
2160
+ void this.persist();
2161
+ }
2130
2162
  for (const session of this.sessions.values()) {
2131
2163
  clearTimeout(session.timer);
2132
2164
  clearTimeout(session.waitTimer);
@@ -2194,7 +2226,8 @@ var Workspace = class {
2194
2226
  region: model2.region,
2195
2227
  nativeMemory: model2.nativeMemory,
2196
2228
  tools: model2.tools,
2197
- memory: this.modelMemory.get(model2.id) ?? null
2229
+ memory: this.modelMemory.get(model2.id) ?? null,
2230
+ prices: model2.prices
2198
2231
  };
2199
2232
  }
2200
2233
  /**
@@ -2804,12 +2837,16 @@ var Workspace = class {
2804
2837
  return next;
2805
2838
  }
2806
2839
  async persist() {
2840
+ clearTimeout(this.persistTimer);
2841
+ this.persistTimer = void 0;
2807
2842
  const entries = Object.entries(this.state.sessions).sort((a, b) => b[1].at - a[1].at).slice(0, 500);
2843
+ const processed = Object.entries(this.state.processed ?? {}).sort((a, b) => b[1].at - a[1].at).slice(0, 500);
2808
2844
  const snapshot = {
2809
2845
  preferences: this.state.preferences,
2810
2846
  sessions: Object.fromEntries(entries),
2811
2847
  purchase: this.state.purchase ?? null,
2812
- ...this.state.defaultModel === void 0 ? {} : { defaultModel: this.state.defaultModel }
2848
+ ...this.state.defaultModel === void 0 ? {} : { defaultModel: this.state.defaultModel },
2849
+ ...processed.length === 0 ? {} : { processed: Object.fromEntries(processed) }
2813
2850
  };
2814
2851
  this.saving = this.saving.then(() => this.deps.store.save(snapshot)).catch((error) => {
2815
2852
  this.deps.log?.("warn", `adaptiq: could not save preferences: ${String(error)}`);
@@ -2830,7 +2867,9 @@ var Workspace = class {
2830
2867
  /**
2831
2868
  * Whether this conversation is known to be stored on AdaptiqAI. A private
2832
2869
  * turn of a conversation that is not stored yet runs ephemeral: there is
2833
- * nothing to read back and nothing may be saved.
2870
+ * nothing to read back and nothing may be saved. While the last answer was
2871
+ * "not stored", the server is asked again: a remembered turn since may have
2872
+ * stored it, and a plugin that reports no turns would not hear of it.
2834
2873
  * @param sessionId - harness session id.
2835
2874
  * @returns true when the server has the conversation.
2836
2875
  */
@@ -2838,7 +2877,7 @@ var Workspace = class {
2838
2877
  if (sessionId2 === void 0)
2839
2878
  return false;
2840
2879
  const session = this.session(sessionId2);
2841
- if (session.remoteAt === 0 && this.deps.auth.phase() === "connected") {
2880
+ if ((session.remoteAt === 0 || !session.exists) && this.deps.auth.phase() === "connected") {
2842
2881
  await Promise.race([this.loadSession(session).catch(() => {
2843
2882
  }), new Promise((resolve2) => setTimeout(resolve2, 2500))]);
2844
2883
  }
@@ -2918,6 +2957,7 @@ var Workspace = class {
2918
2957
  saved: session.saved,
2919
2958
  optimizing: session.optimizing,
2920
2959
  lastTurn: session.lastTurn,
2960
+ processed: processedSummary(this.state.processed?.[id]),
2921
2961
  knowledge: [...session.knowledge, ...[...session.pendingKnowledge.values()].filter((item) => !session.knowledge.some((entry) => entry.id === item.id))],
2922
2962
  conversations: [...session.conversations, ...[...session.pendingConversations.values()].filter((item) => !session.conversations.some((entry) => entry.id === item.id))],
2923
2963
  known: session.exists,
@@ -3297,10 +3337,19 @@ var Workspace = class {
3297
3337
  this.setPhase(session, "responding");
3298
3338
  this.clearNotices("busy", "preparing", "offline", "retrying");
3299
3339
  }
3300
- usage(sessionId2, model2, usage2) {
3340
+ usage(sessionId2, model2, usage2, step = {}) {
3301
3341
  if (sessionId2 === void 0)
3302
3342
  return;
3303
3343
  const session = this.session(sessionId2);
3344
+ const costUsd = step.costUsd ?? stepCost(usage2, this.catalog.find((entry) => entry.id === model2)?.prices ?? null);
3345
+ const counted = addStep(this.state.processed?.[sessionId2], { usage: usage2, costUsd, ...step.turn === void 0 ? {} : { turn: step.turn } }, this.now());
3346
+ this.state = { ...this.state, processed: { ...this.state.processed, [sessionId2]: counted } };
3347
+ if (this.persistTimer === void 0) {
3348
+ this.persistTimer = setTimeout(() => {
3349
+ void this.persist();
3350
+ }, PROCESSED_SAVE_MS);
3351
+ this.persistTimer.unref?.();
3352
+ }
3304
3353
  session.lastTurn = {
3305
3354
  fromMemory: usage2.cacheReadTokens ?? 0,
3306
3355
  newInput: usage2.inputTokens,
@@ -4562,6 +4611,111 @@ var Workspace = class {
4562
4611
  }
4563
4612
  };
4564
4613
 
4614
+ // ../core/lib/tools.js
4615
+ var SOURCE_LIMIT = 6 * 1024 * 1024;
4616
+
4617
+ // ../core/lib/usage.js
4618
+ async function* parseSse(body, activity) {
4619
+ const decoder = new TextDecoder();
4620
+ const reader = body.getReader();
4621
+ let buffer = "";
4622
+ try {
4623
+ while (true) {
4624
+ const { value, done } = await reader.read();
4625
+ if (done)
4626
+ break;
4627
+ activity();
4628
+ buffer += decoder.decode(value, { stream: true });
4629
+ let boundary = nextBoundary(buffer);
4630
+ while (boundary !== null) {
4631
+ const block = buffer.slice(0, boundary.index);
4632
+ buffer = buffer.slice(boundary.index + boundary.length);
4633
+ const event = parseBlock(block);
4634
+ if (event !== null)
4635
+ yield event;
4636
+ boundary = nextBoundary(buffer);
4637
+ }
4638
+ }
4639
+ buffer += decoder.decode();
4640
+ const tail = parseBlock(buffer);
4641
+ if (tail !== null)
4642
+ yield tail;
4643
+ } finally {
4644
+ reader.releaseLock();
4645
+ }
4646
+ }
4647
+ function nextBoundary(buffer) {
4648
+ const match = /\r?\n\r?\n/u.exec(buffer);
4649
+ return match === null ? null : { index: match.index, length: match[0].length };
4650
+ }
4651
+ function parseBlock(block) {
4652
+ let event = null;
4653
+ const data = [];
4654
+ for (const line of block.split(/\r?\n/u)) {
4655
+ const notice = /^: ?adaptiq-memory (.*)$/u.exec(line);
4656
+ if (notice !== null) {
4657
+ event = "adaptiq.memory";
4658
+ data.push(notice[1]);
4659
+ continue;
4660
+ }
4661
+ if (line === "" || line.startsWith(":"))
4662
+ continue;
4663
+ const colon = line.indexOf(":");
4664
+ const field = colon === -1 ? line : line.slice(0, colon);
4665
+ const value = colon === -1 ? "" : line.slice(colon + 1).replace(/^ /u, "");
4666
+ if (field === "event")
4667
+ event = value;
4668
+ else if (field === "data")
4669
+ data.push(value);
4670
+ }
4671
+ return data.length === 0 ? null : { event, data: data.join("\n") };
4672
+ }
4673
+ function mapUsage(raw) {
4674
+ const n = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.round(value) : 0;
4675
+ const details = typeof raw.prompt_tokens_details === "object" && raw.prompt_tokens_details !== null ? raw.prompt_tokens_details : {};
4676
+ const completion = typeof raw.completion_tokens_details === "object" && raw.completion_tokens_details !== null ? raw.completion_tokens_details : {};
4677
+ const prompt = n(raw.prompt_tokens);
4678
+ const output = n(raw.completion_tokens);
4679
+ const cached = Math.min(prompt, n(details.cached_tokens) || n(raw.prompt_cache_hit_tokens));
4680
+ const written = Math.min(prompt - cached, n(details.cache_write_tokens) || n(raw.cache_write_tokens) || n(raw.cache_creation_input_tokens));
4681
+ const reasoning2 = n(completion.reasoning_tokens);
4682
+ const total = n(raw.total_tokens);
4683
+ return {
4684
+ inputTokens: Math.max(0, prompt - cached - written),
4685
+ outputTokens: output,
4686
+ ...cached > 0 ? { cacheReadTokens: cached } : {},
4687
+ ...written > 0 ? { cacheWriteTokens: written } : {},
4688
+ ...reasoning2 > 0 ? { reasoningTokens: reasoning2 } : {},
4689
+ ...total > 0 ? { totalTokens: total } : prompt + output > 0 ? { totalTokens: prompt + output } : {}
4690
+ };
4691
+ }
4692
+ function usageCost(raw) {
4693
+ const adaptiq = typeof raw.adaptiq === "object" && raw.adaptiq !== null ? raw.adaptiq : null;
4694
+ const value = adaptiq?.cost_usd;
4695
+ const cost = typeof value === "string" && value.trim() !== "" ? Number(value) : typeof value === "number" ? value : Number.NaN;
4696
+ return Number.isFinite(cost) && cost >= 0 ? cost : null;
4697
+ }
4698
+ function memoryNotice(raw) {
4699
+ let value = raw;
4700
+ if (typeof raw === "string") {
4701
+ try {
4702
+ value = JSON.parse(raw);
4703
+ } catch {
4704
+ return null;
4705
+ }
4706
+ }
4707
+ if (typeof value !== "object" || value === null)
4708
+ return null;
4709
+ const notice = value;
4710
+ if (notice.kind !== "hibernated" && notice.kind !== "suggestion")
4711
+ return null;
4712
+ const sessions = Array.isArray(notice.sessions) ? notice.sessions.length : 0;
4713
+ if (sessions === 0)
4714
+ return null;
4715
+ const number = (field) => typeof field === "number" && Number.isFinite(field) && field >= 0 ? field : null;
4716
+ return { kind: notice.kind, sessions, tokens: number(notice.tokens), usageRatio: number(notice.usage_ratio) };
4717
+ }
4718
+
4565
4719
  // src/host/stream.ts
4566
4720
  var REASONING_FIELDS = ["reasoning_content", "reasoning", "reasoning_text"];
4567
4721
  async function* translate(events, hooks) {
@@ -4572,6 +4726,7 @@ async function* translate(events, hooks) {
4572
4726
  let reasoningField = null;
4573
4727
  let finish = null;
4574
4728
  let usage2 = null;
4729
+ let costUsd = null;
4575
4730
  let started = false;
4576
4731
  let nextIndex = 0;
4577
4732
  const closed = /* @__PURE__ */ new Set();
@@ -4617,6 +4772,7 @@ async function* translate(events, hooks) {
4617
4772
  }
4618
4773
  if (typeof payload.usage === "object" && payload.usage !== null) {
4619
4774
  usage2 = mapUsage(payload.usage);
4775
+ costUsd = usageCost(payload.usage);
4620
4776
  }
4621
4777
  const choices = Array.isArray(payload.choices) ? payload.choices : [];
4622
4778
  const choice = choices[0];
@@ -4678,7 +4834,7 @@ async function* translate(events, hooks) {
4678
4834
  throw hooks.onError({ error: { message: "The model returned no reply.", type: "empty_response", code: "EMPTY_RESPONSE" } });
4679
4835
  }
4680
4836
  if (usage2 !== null) {
4681
- hooks.onUsage?.(usage2);
4837
+ hooks.onUsage?.(usage2, costUsd);
4682
4838
  yield { type: "usage", usage: usage2 };
4683
4839
  }
4684
4840
  const hasTools = blocks.some((block) => block.kind === "tool-call");
@@ -5056,7 +5212,6 @@ function resolvedInfo(route, model2) {
5056
5212
  function describe(model2) {
5057
5213
  const parts = [];
5058
5214
  if (model2.nativeMemory || (model2.contextWindow ?? 0) >= 1e6) parts.push("Native memory");
5059
- if (model2.lanes !== null) parts.push(`${model2.lanes} ${model2.lanes === 1 ? "lane" : "lanes"}`);
5060
5215
  return parts.join(" \xB7 ");
5061
5216
  }
5062
5217
  function toLlmError(failure, cause) {
@@ -5190,7 +5345,8 @@ var AdaptiqAdapter = class extends LlmAdapter {
5190
5345
  return toLlmError(failure, cause);
5191
5346
  };
5192
5347
  if (sessionId2 !== void 0) this.deps.events.started(sessionId2, options.model);
5193
- if (sessionId2 !== void 0 && options.purpose === void 0) this.deps.noteUserTexts?.(sessionId2, userTurns(options.messages));
5348
+ const written = options.purpose === void 0 ? userTurns(options.messages) : null;
5349
+ if (sessionId2 !== void 0 && written !== null) this.deps.noteUserTexts?.(sessionId2, written);
5194
5350
  try {
5195
5351
  let credential = await this.deps.auth.credential();
5196
5352
  if (credential === void 0) {
@@ -5299,8 +5455,8 @@ var AdaptiqAdapter = class extends LlmAdapter {
5299
5455
  answered = true;
5300
5456
  if (sessionId2 !== void 0) this.deps.events.output(sessionId2);
5301
5457
  },
5302
- onUsage: (usage2) => {
5303
- if (sessionId2 !== void 0) this.deps.events.usage(sessionId2, options.model, usage2);
5458
+ onUsage: (usage2, costUsd) => {
5459
+ if (sessionId2 !== void 0) this.deps.events.usage(sessionId2, options.model, usage2, { costUsd, ...written === null ? {} : { turn: written.length } });
5304
5460
  },
5305
5461
  onError: (payload) => fail2(classifyFailure(null, payload, response?.headers ?? null, name2, { autoSleep: this.deps.autoSleep?.(options.model) === true }))
5306
5462
  });
@@ -5327,7 +5483,7 @@ var AdaptiqAdapter = class extends LlmAdapter {
5327
5483
 
5328
5484
  // src/host/commands.ts
5329
5485
  var HELP = [
5330
- "/adaptiq \u2014 workspace, models, lanes and memory",
5486
+ "/adaptiq \u2014 workspace, models and memory",
5331
5487
  "/adaptiq signin \u2014 connect your AdaptiqAI account in the browser",
5332
5488
  "/adaptiq private \u2014 stop saving new messages of this conversation",
5333
5489
  "/adaptiq remember \u2014 save this conversation to memory again",
@@ -5433,10 +5589,9 @@ ${HELP}`;
5433
5589
  lines.push("No models yet. Pick one in Settings \u2192 AdaptiqAI.");
5434
5590
  } else {
5435
5591
  for (const model2 of snapshot.models) {
5436
- const lanes = model2.lanes === null ? "" : ` \xB7 ${model2.lanes} ${model2.lanes === 1 ? "lane" : "lanes"}`;
5437
5592
  const live = model2.runningNow === null ? "" : ` (${model2.runningNow} running, ${model2.waitingNow ?? 0} waiting)`;
5438
5593
  const window = model2.contextWindow === null ? "" : ` \xB7 ${formatTokens(model2.contextWindow)} context`;
5439
- lines.push(`\u2022 ${model2.name}${window}${lanes}${live}${model2.status === "offline" ? " \xB7 offline" : ""}`);
5594
+ lines.push(`\u2022 ${model2.name}${window}${live}${model2.status === "offline" ? " \xB7 offline" : ""}`);
5440
5595
  }
5441
5596
  }
5442
5597
  if (memory?.available === true && memory.conversationsTokens !== null) {
@@ -5770,7 +5925,7 @@ var render = (_args, value) => {
5770
5925
  return [{ type: "text", text: typeof text2 === "string" ? text2 : JSON.stringify(value) }];
5771
5926
  };
5772
5927
  var say2 = (result) => ({ result });
5773
- var SOURCE_LIMIT = 6 * 1024 * 1024;
5928
+ var SOURCE_LIMIT2 = 6 * 1024 * 1024;
5774
5929
  var MEDIA = {
5775
5930
  ".pdf": "application/pdf",
5776
5931
  ".txt": "text/plain",
@@ -6027,7 +6182,7 @@ ${content}`);
6027
6182
  const info = await stat(path);
6028
6183
  if (!info.isFile()) fail(`${raw} is not a file.`);
6029
6184
  bytes += info.size;
6030
- if (bytes > SOURCE_LIMIT) fail("These files are too large for one request. Use fewer or smaller files, or a repository link.");
6185
+ if (bytes > SOURCE_LIMIT2) fail("These files are too large for one request. Use fewer or smaller files, or a repository link.");
6031
6186
  const content = await readFile2(path);
6032
6187
  files.push({ name: source.name ?? basename(path), mediaType: mediaTypeOf(path), contentBase64: content.toString("base64") });
6033
6188
  } else {
@@ -6273,7 +6428,7 @@ Open **Settings \u2192 AdaptiqAI** to connect, or run \`/adaptiq signin\`.
6273
6428
  var ENTRY_PROTOCOL = 1;
6274
6429
  var RELOAD_SIGNATURE = true ? "ada3dfffa1b3d7b1" : "source";
6275
6430
  function pluginVersion() {
6276
- if (true) return "0.2.13";
6431
+ if (true) return "0.2.15";
6277
6432
  try {
6278
6433
  return createRequire(import.meta.url)("../package.json").version;
6279
6434
  } catch {