@agent-link/server 0.1.118 → 0.1.120

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-link/server",
3
- "version": "0.1.118",
3
+ "version": "0.1.120",
4
4
  "description": "AgentLink relay server",
5
5
  "license": "MIT",
6
6
  "repository": {
package/web/app.js CHANGED
@@ -50,6 +50,7 @@ const App = {
50
50
  const isCompacting = ref(false);
51
51
  const latency = ref(null);
52
52
  const queuedMessages = ref([]);
53
+ const usageStats = ref(null);
53
54
  const inputRef = ref(null);
54
55
 
55
56
  // Sidebar state
@@ -130,6 +131,7 @@ const App = {
130
131
  toolMsgMap: _getToolMsgMap(),
131
132
  messageIdCounter: streaming.getMessageIdCounter(),
132
133
  queuedMessages: queuedMessages.value,
134
+ usageStats: usageStats.value,
133
135
  };
134
136
  }
135
137
 
@@ -149,6 +151,7 @@ const App = {
149
151
  streaming.setMessageIdCounter(cached.messageIdCounter || 0);
150
152
  _restoreToolMsgMap(cached.toolMsgMap || new Map());
151
153
  queuedMessages.value = cached.queuedMessages || [];
154
+ usageStats.value = cached.usageStats || null;
152
155
  } else {
153
156
  // New blank conversation
154
157
  messages.value = [];
@@ -163,6 +166,7 @@ const App = {
163
166
  streaming.reset();
164
167
  _clearToolMsgMap();
165
168
  queuedMessages.value = [];
169
+ usageStats.value = null;
166
170
  }
167
171
 
168
172
  currentConversationId.value = newConvId;
@@ -247,7 +251,7 @@ const App = {
247
251
  const { connect, wsSend, closeWs, submitPassword, setDequeueNext, setFileBrowser, getToolMsgMap, restoreToolMsgMap, clearToolMsgMap } = createConnection({
248
252
  status, agentName, hostname, workDir, sessionId, error,
249
253
  serverVersion, agentVersion, latency,
250
- messages, isProcessing, isCompacting, visibleLimit, queuedMessages,
254
+ messages, isProcessing, isCompacting, visibleLimit, queuedMessages, usageStats,
251
255
  historySessions, currentClaudeSessionId, needsResume, loadingSessions, loadingHistory,
252
256
  folderPickerLoading, folderPickerEntries, folderPickerPath,
253
257
  authRequired, authPassword, authError, authAttempts, authLocked,
@@ -412,6 +416,21 @@ const App = {
412
416
  document.title = name ? `${name} — AgentLink` : 'AgentLink';
413
417
  });
414
418
 
419
+ // ── Usage formatting ──
420
+ function formatTokens(n) {
421
+ if (n >= 1000) return (n / 1000).toFixed(1) + 'k';
422
+ return String(n);
423
+ }
424
+ function formatUsage(u) {
425
+ if (!u) return '';
426
+ const pct = u.contextWindow ? Math.round(u.inputTokens / u.contextWindow * 100) : 0;
427
+ const ctx = formatTokens(u.inputTokens) + ' / ' + formatTokens(u.contextWindow) + ' (' + pct + '%)';
428
+ const cost = '$' + u.totalCost.toFixed(2);
429
+ const model = u.model.replace(/^claude-/, '').replace(/-\d{8}$/, '').replace(/-1m$/, '');
430
+ const dur = (u.durationMs / 1000).toFixed(1) + 's';
431
+ return 'Context ' + ctx + ' \u00b7 Cost ' + cost + ' \u00b7 ' + model + ' \u00b7 ' + dur;
432
+ }
433
+
415
434
  // ── Lifecycle ──
416
435
  onMounted(() => { connect(scheduleHighlight); });
417
436
  onUnmounted(() => { closeWs(); streaming.cleanup(); window.removeEventListener('resize', _resizeHandler); document.removeEventListener('click', _workdirMenuClickHandler); document.removeEventListener('keydown', _workdirMenuKeyHandler); });
@@ -420,11 +439,11 @@ const App = {
420
439
  status, agentName, hostname, workDir, sessionId, error,
421
440
  serverVersion, agentVersion, latency,
422
441
  messages, visibleMessages, hasMoreMessages, loadMoreMessages,
423
- inputText, isProcessing, isCompacting, canSend, hasInput, inputRef, queuedMessages,
442
+ inputText, isProcessing, isCompacting, canSend, hasInput, inputRef, queuedMessages, usageStats,
424
443
  sendMessage, handleKeydown, cancelExecution, removeQueuedMessage, onMessageListScroll,
425
444
  getRenderedContent, copyMessage, toggleTool,
426
445
  isPrevAssistant: _isPrevAssistant,
427
- toggleContextSummary, formatTimestamp,
446
+ toggleContextSummary, formatTimestamp, formatUsage,
428
447
  getToolIcon, getToolSummary, isEditTool, getEditDiffHtml, getFormattedToolInput, autoResize,
429
448
  // AskUserQuestion
430
449
  selectQuestionOption,
@@ -907,6 +926,7 @@ const App = {
907
926
  <button class="queue-item-remove" @click="removeQueuedMessage(qm.id)" title="Remove from queue">&times;</button>
908
927
  </div>
909
928
  </div>
929
+ <div v-if="usageStats" class="usage-bar">{{ formatUsage(usageStats) }}</div>
910
930
  <div
911
931
  :class="['input-card', { 'drag-over': dragOver }]"
912
932
  @dragover="handleDragOver"
@@ -14,7 +14,7 @@ export function createConnection(deps) {
14
14
  const {
15
15
  status, agentName, hostname, workDir, sessionId, error,
16
16
  serverVersion, agentVersion, latency,
17
- messages, isProcessing, isCompacting, visibleLimit, queuedMessages,
17
+ messages, isProcessing, isCompacting, visibleLimit, queuedMessages, usageStats,
18
18
  historySessions, currentClaudeSessionId, needsResume, loadingSessions, loadingHistory,
19
19
  folderPickerLoading, folderPickerEntries, folderPickerPath,
20
20
  authRequired, authPassword, authError, authAttempts, authLocked,
@@ -202,6 +202,7 @@ export function createConnection(deps) {
202
202
  }
203
203
  cache.isProcessing = false;
204
204
  cache.isCompacting = false;
205
+ if (msg.usage) cache.usageStats = msg.usage;
205
206
  if (cache.toolMsgMap) cache.toolMsgMap.clear();
206
207
  processingConversations.value[convId] = false;
207
208
  if (msg.type === 'execution_cancelled') {
@@ -587,6 +588,7 @@ export function createConnection(deps) {
587
588
  isProcessing.value = false;
588
589
  isCompacting.value = false;
589
590
  toolMsgMap.clear();
591
+ if (msg.usage) usageStats.value = msg.usage;
590
592
  if (currentConversationId && currentConversationId.value) {
591
593
  processingConversations.value[currentConversationId.value] = false;
592
594
  }
package/web/style.css CHANGED
@@ -258,7 +258,6 @@ body {
258
258
  }
259
259
 
260
260
  .sidebar-workdir {
261
- overflow: hidden;
262
261
  position: relative;
263
262
  }
264
263
 
@@ -955,6 +954,16 @@ body {
955
954
  color: var(--error);
956
955
  }
957
956
 
957
+ .usage-bar {
958
+ max-width: 768px;
959
+ margin: 0 auto 6px;
960
+ padding: 4px 10px;
961
+ font-size: 0.75rem;
962
+ color: var(--text-secondary);
963
+ text-align: center;
964
+ opacity: 0.7;
965
+ }
966
+
958
967
  .assistant-bubble {
959
968
  background: transparent;
960
969
  padding: 0.2rem 0;