@adaptiqai/pi 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/index.js +239 -92
  2. package/package.json +2 -2
package/lib/index.js CHANGED
@@ -100,7 +100,7 @@ function classifyFailure(status, raw, headers, model2, options = {}) {
100
100
  if (effective === 503 && detail.code !== null && MODEL_OFFLINE_CODES.includes(detail.code)) {
101
101
  return make(CODES.server, `${model2 ?? "This model"} is offline right now. It will retry automatically.`, "retrying");
102
102
  }
103
- if (has(detail, "AdaptiqNativePreflightBusy", "session_route_unavailable", "conversation_preparing", "preparing", "upload_capacity_exhausted") || effective === 503 && !has(detail, "offline")) {
103
+ if (has(detail, "AdaptiqNativePreflightBusy", "session_route_unavailable", "conversation_preparing", "preparing", "upload_capacity_exhausted", "AdaptiqMemoryNotReady") || effective === 425 || effective === 503 && !has(detail, "offline")) {
104
104
  return make(CODES.preparing, "AdaptiqAI is getting your conversation ready. It will retry automatically.", "preparing");
105
105
  }
106
106
  if (effective === 409 && has(detail, "AdaptiqNativePreflight", "conversation")) {
@@ -317,6 +317,91 @@ function detailMessage(raw) {
317
317
  }
318
318
  return null;
319
319
  }
320
+ var EXPIRED = "The sign-in link expired. Start again.";
321
+ async function startDeviceCode(client, request) {
322
+ let response;
323
+ try {
324
+ response = await client.fetch(`${client.apiBase}/v1/cli-auth/device`, {
325
+ method: "POST",
326
+ headers: { "content-type": "application/json", accept: "application/json" },
327
+ body: JSON.stringify({
328
+ client_app: request.app,
329
+ client_name: request.app,
330
+ device_name: request.device,
331
+ client_binding: request.binding,
332
+ ...request.tokenLifetime === void 0 ? {} : { token_lifetime: request.tokenLifetime }
333
+ }),
334
+ signal: client.signal === void 0 ? AbortSignal.timeout(2e4) : AbortSignal.any([client.signal, AbortSignal.timeout(2e4)]),
335
+ redirect: "error"
336
+ });
337
+ } catch (error) {
338
+ if (client.signal?.aborted === true)
339
+ throw error;
340
+ throw new Error(UNREACHABLE, { cause: error });
341
+ }
342
+ if (!response.ok) {
343
+ const message = detailMessage(await readJson(response));
344
+ if (response.status >= 500)
345
+ throw new Error("AdaptiqAI isn't answering right now. Try again in a minute.");
346
+ throw new Error(message ?? `AdaptiqAI sign-in could not start (HTTP ${response.status}).`);
347
+ }
348
+ const body = await readJson(response);
349
+ const deviceCode = String(body?.device_code ?? "");
350
+ const userCode = String(body?.user_code ?? "");
351
+ const url = String(body?.verification_uri_complete ?? body?.verification_uri ?? "");
352
+ if (deviceCode === "" || userCode === "" || !/^https?:\/\//u.test(url))
353
+ throw new Error("AdaptiqAI returned an incomplete sign-in.");
354
+ return {
355
+ deviceCode,
356
+ userCode,
357
+ url,
358
+ expiresAt: client.now() + Math.min(DEVICE_TIMEOUT_MS, Math.max(60, Number(body?.expires_in ?? 600)) * 1e3),
359
+ interval: Math.max(1, Number(body?.interval ?? 2)) * 1e3
360
+ };
361
+ }
362
+ async function pollDeviceCode(client, code, extra = {}) {
363
+ let response;
364
+ try {
365
+ response = await client.fetch(`${client.apiBase}/v1/cli-auth/device/token`, {
366
+ method: "POST",
367
+ headers: { "content-type": "application/json", accept: "application/json" },
368
+ body: JSON.stringify({ ...extra, device_code: code.deviceCode }),
369
+ signal: client.signal === void 0 ? AbortSignal.timeout(2e4) : AbortSignal.any([client.signal, AbortSignal.timeout(2e4)]),
370
+ redirect: "error"
371
+ });
372
+ } catch {
373
+ return { state: "waiting", delayMs: code.interval * 2 };
374
+ }
375
+ if (response.status === 202)
376
+ return { state: "waiting", delayMs: code.interval };
377
+ if (response.status === 429) {
378
+ const retry = Number(response.headers.get("retry-after") ?? 0) * 1e3;
379
+ return { state: "waiting", delayMs: Math.max(code.interval, retry) + 250 };
380
+ }
381
+ if (response.status === 403) {
382
+ const raw = await readJson(response);
383
+ return { state: "denied", message: detailCode(raw) === "access_denied" ? "The connection was declined in the browser." : detailMessage(raw) ?? "The connection was declined." };
384
+ }
385
+ if (response.status === 410 || response.status === 404)
386
+ return { state: "expired", message: EXPIRED };
387
+ if (!response.ok) {
388
+ if (response.status >= 500)
389
+ return { state: "waiting", delayMs: code.interval * 2 };
390
+ return { state: "failed", message: detailMessage(await readJson(response)) ?? `Sign-in failed (HTTP ${response.status}).` };
391
+ }
392
+ const body = await readJson(response);
393
+ const access = String(body?.access_token ?? "");
394
+ const refresh = String(body?.refresh_token ?? "");
395
+ if (!access.startsWith("ak_") || refresh !== "" && !refresh.startsWith("akr_"))
396
+ return { state: "failed", message: "AdaptiqAI returned an invalid credential." };
397
+ return {
398
+ state: "approved",
399
+ access,
400
+ refresh: refresh === "" ? null : refresh,
401
+ expiresIn: Math.max(60, Number(body?.expires_in ?? 3600)),
402
+ workspaceLabel: typeof body?.workspace_label === "string" && body.workspace_label !== "" ? body.workspace_label : "Your workspace"
403
+ };
404
+ }
320
405
  var AuthManager = class {
321
406
  options;
322
407
  fetch;
@@ -359,6 +444,9 @@ var AuthManager = class {
359
444
  this.state = phase;
360
445
  this.options.onChange?.();
361
446
  }
447
+ client(signal) {
448
+ return { apiBase: this.options.apiBase, fetch: this.fetch, now: this.now, signal };
449
+ }
362
450
  /** Read the stored state once at startup. */
363
451
  async init() {
364
452
  const stored = await this.options.store.read();
@@ -492,39 +580,11 @@ var AuthManager = class {
492
580
  const clientProof = `${randomUUID()}${randomUUID()}`;
493
581
  const binding = createHash("sha256").update(clientProof).digest("hex");
494
582
  const app = this.options.appName ?? "DeepSeek Harness";
495
- let response;
496
- try {
497
- response = await this.fetch(`${this.options.apiBase}/v1/cli-auth/device`, {
498
- method: "POST",
499
- headers: { "content-type": "application/json", accept: "application/json" },
500
- body: JSON.stringify({
501
- client_app: app,
502
- client_name: app,
503
- device_name: deviceLabel(this.options.deviceName ?? hostname()),
504
- client_binding: binding
505
- }),
506
- signal: AbortSignal.any([flow.signal, AbortSignal.timeout(2e4)]),
507
- redirect: "error"
508
- });
509
- } catch (error) {
510
- if (flow.signal.aborted)
511
- throw error;
512
- throw new Error(UNREACHABLE, { cause: error });
513
- }
514
- if (!response.ok) {
515
- const message = detailMessage(await readJson(response));
516
- if (response.status >= 500)
517
- throw new Error("AdaptiqAI isn't answering right now. Try again in a minute.");
518
- throw new Error(message ?? `AdaptiqAI sign-in could not start (HTTP ${response.status}).`);
519
- }
520
- const body = await readJson(response);
521
- const deviceCode = String(body?.device_code ?? "");
522
- const userCode = String(body?.user_code ?? "");
523
- const url = String(body?.verification_uri_complete ?? body?.verification_uri ?? "");
524
- if (deviceCode === "" || userCode === "" || !/^https?:\/\//u.test(url))
525
- throw new Error("AdaptiqAI returned an incomplete sign-in.");
526
- const expiresAt = this.now() + Math.min(DEVICE_TIMEOUT_MS, Math.max(60, Number(body?.expires_in ?? 600)) * 1e3);
527
- const interval = Math.max(1, Number(body?.interval ?? 2)) * 1e3;
583
+ const { deviceCode, userCode, url, expiresAt, interval } = await startDeviceCode(this.client(flow.signal), {
584
+ app,
585
+ device: deviceLabel(this.options.deviceName ?? hostname()),
586
+ binding
587
+ });
528
588
  this.pendingSignIn = { url, userCode, expiresAt };
529
589
  this.set("signing-in");
530
590
  void this.poll(flow, deviceCode, clientProof, interval, expiresAt);
@@ -536,61 +596,27 @@ var AuthManager = class {
536
596
  while (this.now() < expiresAt) {
537
597
  await this.sleep(delay, flow.signal);
538
598
  delay = interval;
539
- let response;
540
- try {
541
- response = await this.fetch(`${this.options.apiBase}/v1/cli-auth/device/token`, {
542
- method: "POST",
543
- headers: { "content-type": "application/json", accept: "application/json" },
544
- body: JSON.stringify({ device_code: deviceCode }),
545
- signal: AbortSignal.any([flow.signal, AbortSignal.timeout(2e4)]),
546
- redirect: "error"
547
- });
548
- } catch {
549
- if (flow.signal.aborted)
550
- return;
551
- delay = interval * 2;
552
- continue;
553
- }
554
- if (response.status === 202)
555
- continue;
556
- if (response.status === 429) {
557
- const retry = Number(response.headers.get("retry-after") ?? 0) * 1e3;
558
- delay = Math.max(interval, retry) + 250;
599
+ const answer = await pollDeviceCode(this.client(flow.signal), { deviceCode, interval });
600
+ if (answer.state === "waiting") {
601
+ delay = answer.delayMs;
559
602
  continue;
560
603
  }
561
- if (response.status === 403) {
562
- const raw = await readJson(response);
563
- this.fail(flow, { code: "denied", message: detailCode(raw) === "access_denied" ? "The connection was declined in the browser." : detailMessage(raw) ?? "The connection was declined." });
604
+ if (answer.state !== "approved") {
605
+ this.fail(flow, { code: answer.state, message: answer.message });
564
606
  return;
565
607
  }
566
- if (response.status === 410 || response.status === 404) {
567
- this.fail(flow, { code: "expired", message: "The sign-in link expired. Start again." });
568
- return;
569
- }
570
- if (!response.ok) {
571
- if (response.status >= 500) {
572
- delay = interval * 2;
573
- continue;
574
- }
575
- this.fail(flow, { code: "failed", message: detailMessage(await readJson(response)) ?? `Sign-in failed (HTTP ${response.status}).` });
576
- return;
577
- }
578
- const body = await readJson(response);
579
- const access = String(body?.access_token ?? "");
580
- const refresh = String(body?.refresh_token ?? "");
581
- if (!access.startsWith("ak_") || !refresh.startsWith("akr_")) {
608
+ if (answer.refresh === null) {
582
609
  this.fail(flow, { code: "failed", message: "AdaptiqAI returned an invalid credential." });
583
610
  return;
584
611
  }
585
- const workspaceLabel = typeof body?.workspace_label === "string" && body.workspace_label !== "" ? body.workspace_label : "Your workspace";
586
612
  const grant = {
587
613
  schema: "adaptiq-dsh-grant-v1",
588
614
  apiBase: this.options.apiBase,
589
- access,
590
- refresh,
615
+ access: answer.access,
616
+ refresh: answer.refresh,
591
617
  clientProof,
592
- expiresAt: this.now() + Math.max(60, Number(body?.expires_in ?? 3600)) * 1e3,
593
- workspaceLabel,
618
+ expiresAt: this.now() + answer.expiresIn * 1e3,
619
+ workspaceLabel: answer.workspaceLabel,
594
620
  obtainedAt: this.now()
595
621
  };
596
622
  await this.options.store.modify(async () => grant);
@@ -599,12 +625,12 @@ var AuthManager = class {
599
625
  this.flow = null;
600
626
  this.pendingSignIn = null;
601
627
  this.source = "device";
602
- this.label = workspaceLabel;
628
+ this.label = answer.workspaceLabel;
603
629
  this.set("connected");
604
- this.options.onSignedIn?.(workspaceLabel);
630
+ this.options.onSignedIn?.(answer.workspaceLabel);
605
631
  return;
606
632
  }
607
- this.fail(flow, { code: "expired", message: "The sign-in link expired. Start again." });
633
+ this.fail(flow, { code: "expired", message: EXPIRED });
608
634
  } catch (error) {
609
635
  if (flow.signal.aborted)
610
636
  return;
@@ -729,6 +755,20 @@ var obj = (value) => typeof value === "object" && value !== null && !Array.isArr
729
755
  var str = (value) => typeof value === "string" && value.trim() !== "" ? value.trim() : null;
730
756
  var num = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
731
757
  var count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
758
+ function pricesFromRow(raw) {
759
+ const pricing = obj(raw);
760
+ const price = (value) => {
761
+ const parsed = typeof value === "string" && value.trim() !== "" ? Number(value) : typeof value === "number" ? value : Number.NaN;
762
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
763
+ };
764
+ const input = price(pricing?.prompt);
765
+ const memoryWrite = price(pricing?.input_cache_write);
766
+ const memoryRead = price(pricing?.input_cache_read);
767
+ const output = price(pricing?.completion);
768
+ if (input === null || memoryWrite === null || memoryRead === null || output === null)
769
+ return null;
770
+ return { input, memoryWrite, memoryRead, output };
771
+ }
732
772
  var LEVEL_NAMES = {
733
773
  off: "Off",
734
774
  minimal: "Minimal",
@@ -817,17 +857,17 @@ function placementsFromRoutes(routes) {
817
857
  const model2 = str(route?.model);
818
858
  if (route === null || model2 === null || route.enabled !== true)
819
859
  continue;
820
- const add = (a, b) => a === null ? b : b === null ? a : a + b;
860
+ const add2 = (a, b) => a === null ? b : b === null ? a : a + b;
821
861
  const current = out.get(model2);
822
862
  const running = count(route.running);
823
863
  const waiting = count(route.waiting);
824
864
  out.set(model2, {
825
865
  name: current?.name ?? (str(route.name) !== null ? routeModelName(str(route.name)) : null),
826
866
  status: "running",
827
- lanes: add(current?.lanes ?? null, count(route.lanes)),
828
- maxParallel: add(current?.maxParallel ?? null, count(route.max_parallel_agents)),
829
- runningNow: add(current?.runningNow ?? null, running),
830
- waitingNow: add(current?.waitingNow ?? null, waiting),
867
+ lanes: add2(current?.lanes ?? null, count(route.lanes)),
868
+ maxParallel: add2(current?.maxParallel ?? null, count(route.max_parallel_agents)),
869
+ runningNow: add2(current?.runningNow ?? null, running),
870
+ waitingNow: add2(current?.waitingNow ?? null, waiting),
831
871
  region: current?.region ?? str(route.region),
832
872
  card: null,
833
873
  contextWindow: null,
@@ -888,7 +928,8 @@ function model(id, row, placement) {
888
928
  maxParallel: placement?.maxParallel ?? count(gateway?.max_parallel_agents),
889
929
  runningNow: placement?.runningNow ?? null,
890
930
  waitingNow: placement?.waitingNow ?? null,
891
- region: placement?.region ?? str(gateway?.region)
931
+ region: placement?.region ?? str(gateway?.region),
932
+ prices: pricesFromRow(row?.pricing)
892
933
  };
893
934
  }
894
935
  function selectableLevels(reasoning) {
@@ -957,6 +998,60 @@ function laneLimit(offer) {
957
998
  return free < offer.minLanes ? 0 : Math.min(offer.maxLanes, free);
958
999
  }
959
1000
 
1001
+ // ../core/lib/processed.js
1002
+ var NOTHING_PROCESSED = { requests: 0, newInput: 0, fromMemory: 0, written: 0, output: 0, reasoning: 0, costUsd: 0 };
1003
+ function stepCost(usage, prices) {
1004
+ if (prices === null)
1005
+ return null;
1006
+ return usage.inputTokens * prices.input + (usage.cacheWriteTokens ?? 0) * prices.memoryWrite + (usage.cacheReadTokens ?? 0) * prices.memoryRead + usage.outputTokens * prices.output;
1007
+ }
1008
+ function add(view, step) {
1009
+ return {
1010
+ requests: view.requests + 1,
1011
+ newInput: view.newInput + step.usage.inputTokens,
1012
+ fromMemory: view.fromMemory + (step.usage.cacheReadTokens ?? 0),
1013
+ written: view.written + (step.usage.cacheWriteTokens ?? 0),
1014
+ output: view.output + step.usage.outputTokens,
1015
+ reasoning: view.reasoning + (step.usage.reasoningTokens ?? 0),
1016
+ costUsd: view.costUsd === null || step.costUsd === null ? null : view.costUsd + step.costUsd
1017
+ };
1018
+ }
1019
+ function addStep(record, step, now) {
1020
+ const turnKey = step.turn ?? record?.turnKey ?? null;
1021
+ const fresh = record == null || step.turn !== void 0 && step.turn !== record.turnKey;
1022
+ return {
1023
+ since: record?.since ?? now,
1024
+ at: now,
1025
+ turnKey,
1026
+ turn: add(fresh ? NOTHING_PROCESSED : record.turn, step),
1027
+ session: add(record?.session ?? NOTHING_PROCESSED, step)
1028
+ };
1029
+ }
1030
+ function readProcessed(raw) {
1031
+ const record = typeof raw === "object" && raw !== null ? raw : null;
1032
+ const view = (value) => {
1033
+ const fields = typeof value === "object" && value !== null ? value : null;
1034
+ if (fields === null)
1035
+ return null;
1036
+ const count3 = (name) => {
1037
+ const field = fields[name];
1038
+ return typeof field === "number" && Number.isFinite(field) && field >= 0 ? field : null;
1039
+ };
1040
+ const numbers = { requests: count3("requests"), newInput: count3("newInput"), fromMemory: count3("fromMemory"), written: count3("written"), output: count3("output"), reasoning: count3("reasoning") };
1041
+ if (Object.values(numbers).some((value2) => value2 === null))
1042
+ return null;
1043
+ return { ...numbers, costUsd: count3("costUsd") };
1044
+ };
1045
+ const turn = view(record?.turn);
1046
+ const session = view(record?.session);
1047
+ const since = record?.since;
1048
+ const at = record?.at;
1049
+ if (turn === null || session === null || typeof since !== "number" || typeof at !== "number")
1050
+ return null;
1051
+ const turnKey = typeof record?.turnKey === "number" ? record.turnKey : null;
1052
+ return { since, at, turnKey, turn, session };
1053
+ }
1054
+
960
1055
  // ../core/lib/relations.js
961
1056
  var PARENT_SESSION_HEADER = "X-Adaptiq-Parent-Session-Id";
962
1057
  var SESSION_ROLE_HEADER = "X-Adaptiq-Session-Role";
@@ -1045,6 +1140,22 @@ var en = {
1045
1140
  popKnowledge: "Knowledge",
1046
1141
  addKnowledge: "Add knowledge",
1047
1142
  popWorkspace: "All memory on {model}",
1143
+ popSeesNow: "What the model sees now",
1144
+ procTitle: "Processed",
1145
+ procTurn: "Last turn",
1146
+ procSession: "This conversation",
1147
+ procSteps: "{count} steps",
1148
+ procStepsOne: "1 step",
1149
+ procNewInput: "New input",
1150
+ procFromMemory: "Read from memory",
1151
+ procWritten: "Saved to memory",
1152
+ procOutput: "Output",
1153
+ procThinking: "Thinking, part of output",
1154
+ procCost: "Cost",
1155
+ procExplain: "Each step reads the whole memory again. {percent} came from memory.",
1156
+ procExplainFree: "Each step reads the whole memory again. {percent} came from memory, which is free.",
1157
+ procExplainCheaper: "Each step reads the whole memory again. {percent} came from memory, at a fraction of the input price.",
1158
+ procSince: "Counted on this computer since {date}",
1048
1159
  openInKnowledge: "Open in Knowledge",
1049
1160
  kTitle: "Knowledge",
1050
1161
  kSearch: "Search your knowledge",
@@ -1529,6 +1640,21 @@ function button(id, label, extra = {}) {
1529
1640
  return { id, label: en[label], labelKey: label, ...extra };
1530
1641
  }
1531
1642
  var count2 = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
1643
+ var PROCESSED_SAVE_MS = 2e3;
1644
+ function savedProcessed(raw) {
1645
+ const out = {};
1646
+ if (typeof raw !== "object" || raw === null)
1647
+ return out;
1648
+ for (const [id, value] of Object.entries(raw)) {
1649
+ const record = readProcessed(value);
1650
+ if (record !== null)
1651
+ out[id] = record;
1652
+ }
1653
+ return out;
1654
+ }
1655
+ function processedSummary(record) {
1656
+ return record === void 0 ? null : { turn: record.turn, session: record.session, since: record.since };
1657
+ }
1532
1658
  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.";
1533
1659
  var ATTACH_TRIES = 3;
1534
1660
  function transient(error) {
@@ -1894,6 +2020,7 @@ var Workspace = class {
1894
2020
  listeners = /* @__PURE__ */ new Set();
1895
2021
  state = { preferences: DEFAULT_PREFERENCES, sessions: {} };
1896
2022
  saving = Promise.resolve();
2023
+ persistTimer;
1897
2024
  refreshing = null;
1898
2025
  timer;
1899
2026
  pendingAttach = /* @__PURE__ */ new Map();
@@ -1953,7 +2080,8 @@ var Workspace = class {
1953
2080
  preferences: { ...DEFAULT_PREFERENCES, ...saved.preferences },
1954
2081
  sessions: typeof saved.sessions === "object" && saved.sessions !== null ? saved.sessions : {},
1955
2082
  purchase: saved.purchase ?? null,
1956
- ...saved.defaultModel === "pending" || saved.defaultModel === "done" ? { defaultModel: saved.defaultModel } : {}
2083
+ ...saved.defaultModel === "pending" || saved.defaultModel === "done" ? { defaultModel: saved.defaultModel } : {},
2084
+ processed: savedProcessed(saved.processed)
1957
2085
  };
1958
2086
  this.purchases.restore(saved.purchase);
1959
2087
  }
@@ -1969,6 +2097,10 @@ var Workspace = class {
1969
2097
  this.purchases.dispose();
1970
2098
  clearTimeout(this.timer);
1971
2099
  clearTimeout(this.attachTimer);
2100
+ if (this.persistTimer !== void 0) {
2101
+ clearTimeout(this.persistTimer);
2102
+ void this.persist();
2103
+ }
1972
2104
  for (const session of this.sessions.values()) {
1973
2105
  clearTimeout(session.timer);
1974
2106
  clearTimeout(session.waitTimer);
@@ -2036,7 +2168,8 @@ var Workspace = class {
2036
2168
  region: model2.region,
2037
2169
  nativeMemory: model2.nativeMemory,
2038
2170
  tools: model2.tools,
2039
- memory: this.modelMemory.get(model2.id) ?? null
2171
+ memory: this.modelMemory.get(model2.id) ?? null,
2172
+ prices: model2.prices
2040
2173
  };
2041
2174
  }
2042
2175
  /**
@@ -2646,12 +2779,16 @@ var Workspace = class {
2646
2779
  return next;
2647
2780
  }
2648
2781
  async persist() {
2782
+ clearTimeout(this.persistTimer);
2783
+ this.persistTimer = void 0;
2649
2784
  const entries = Object.entries(this.state.sessions).sort((a, b) => b[1].at - a[1].at).slice(0, 500);
2785
+ const processed = Object.entries(this.state.processed ?? {}).sort((a, b) => b[1].at - a[1].at).slice(0, 500);
2650
2786
  const snapshot = {
2651
2787
  preferences: this.state.preferences,
2652
2788
  sessions: Object.fromEntries(entries),
2653
2789
  purchase: this.state.purchase ?? null,
2654
- ...this.state.defaultModel === void 0 ? {} : { defaultModel: this.state.defaultModel }
2790
+ ...this.state.defaultModel === void 0 ? {} : { defaultModel: this.state.defaultModel },
2791
+ ...processed.length === 0 ? {} : { processed: Object.fromEntries(processed) }
2655
2792
  };
2656
2793
  this.saving = this.saving.then(() => this.deps.store.save(snapshot)).catch((error) => {
2657
2794
  this.deps.log?.("warn", `adaptiq: could not save preferences: ${String(error)}`);
@@ -2762,6 +2899,7 @@ var Workspace = class {
2762
2899
  saved: session.saved,
2763
2900
  optimizing: session.optimizing,
2764
2901
  lastTurn: session.lastTurn,
2902
+ processed: processedSummary(this.state.processed?.[id]),
2765
2903
  knowledge: [...session.knowledge, ...[...session.pendingKnowledge.values()].filter((item) => !session.knowledge.some((entry) => entry.id === item.id))],
2766
2904
  conversations: [...session.conversations, ...[...session.pendingConversations.values()].filter((item) => !session.conversations.some((entry) => entry.id === item.id))],
2767
2905
  known: session.exists,
@@ -3141,10 +3279,19 @@ var Workspace = class {
3141
3279
  this.setPhase(session, "responding");
3142
3280
  this.clearNotices("busy", "preparing", "offline", "retrying");
3143
3281
  }
3144
- usage(sessionId, model2, usage) {
3282
+ usage(sessionId, model2, usage, step = {}) {
3145
3283
  if (sessionId === void 0)
3146
3284
  return;
3147
3285
  const session = this.session(sessionId);
3286
+ const costUsd = step.costUsd ?? stepCost(usage, this.catalog.find((entry) => entry.id === model2)?.prices ?? null);
3287
+ const counted = addStep(this.state.processed?.[sessionId], { usage, costUsd, ...step.turn === void 0 ? {} : { turn: step.turn } }, this.now());
3288
+ this.state = { ...this.state, processed: { ...this.state.processed, [sessionId]: counted } };
3289
+ if (this.persistTimer === void 0) {
3290
+ this.persistTimer = setTimeout(() => {
3291
+ void this.persist();
3292
+ }, PROCESSED_SAVE_MS);
3293
+ this.persistTimer.unref?.();
3294
+ }
3148
3295
  session.lastTurn = {
3149
3296
  fromMemory: usage.cacheReadTokens ?? 0,
3150
3297
  newInput: usage.inputTokens,
@@ -4949,7 +5096,7 @@ function userTexts(messages) {
4949
5096
  }
4950
5097
 
4951
5098
  // src/index.ts
4952
- var VERSION = true ? "0.1.0" : "0.0.0";
5099
+ var VERSION = true ? "0.1.1" : "0.0.0";
4953
5100
  var ROUTE = "adaptiqai";
4954
5101
  var PARENT_ENV = "ADAPTIQ_PARENT_SESSION_ID";
4955
5102
  var SESSION_ID2 = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/u;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaptiqai/pi",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "AdaptiqAI for Pi: your workspace's models, conversations AdaptiqAI remembers, and your saved knowledge, in one Pi package.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -37,7 +37,7 @@
37
37
  "esbuild": "~0.25.12",
38
38
  "typescript": "~5.9.3",
39
39
  "vitest": "~3.2.7",
40
- "@adaptiqai/core": "0.2.10"
40
+ "@adaptiqai/core": "0.2.11"
41
41
  },
42
42
  "scripts": {
43
43
  "build": "node scripts/build.mjs",