remote-codex 0.11.24 → 0.11.26

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.
@@ -863,6 +863,10 @@ function streamMessageId(event: unknown) {
863
863
  return messageIdFromPayload(message);
864
864
  }
865
865
 
866
+ function sessionMessageTimestamp(message: SessionMessage) {
867
+ return typeof message.uuid === 'string' ? isoFromUuidV7(message.uuid) : null;
868
+ }
869
+
866
870
  function addOrUpdateItem(state: ActiveClaudeTurn, item: AgentHistoryItem) {
867
871
  if (!state.items.has(item.id)) {
868
872
  state.itemOrder.push(item.id);
@@ -876,6 +880,68 @@ function orderedItems(state: ActiveClaudeTurn) {
876
880
  .filter((item): item is AgentHistoryItem => Boolean(item));
877
881
  }
878
882
 
883
+ function withHistoryItemCreatedAt<T extends AgentHistoryItem>(
884
+ item: T,
885
+ createdAt: string | null | undefined,
886
+ ): T {
887
+ if (item.createdAt || !createdAt) {
888
+ return item;
889
+ }
890
+ return { ...item, createdAt };
891
+ }
892
+
893
+ function latestReasoningItem(state: ActiveClaudeTurn) {
894
+ const lastItemId = state.itemOrder.at(-1);
895
+ const lastItem = lastItemId ? state.items.get(lastItemId) : null;
896
+ return lastItem?.kind === 'reasoning' ? lastItem : null;
897
+ }
898
+
899
+ function joinReasoningText(left: string, right: string) {
900
+ if (!left) {
901
+ return right;
902
+ }
903
+ if (!right) {
904
+ return left;
905
+ }
906
+ if (right.startsWith(left)) {
907
+ return right;
908
+ }
909
+ if (left.endsWith(right)) {
910
+ return left;
911
+ }
912
+ return `${left}\n\n${right}`;
913
+ }
914
+
915
+ function isRunningHistoryItemStatus(status: string | null | undefined) {
916
+ const normalized = status?.trim().toLowerCase();
917
+ return (
918
+ normalized === 'running' ||
919
+ normalized === 'pending' ||
920
+ normalized === 'in_progress' ||
921
+ normalized === 'in progress'
922
+ );
923
+ }
924
+
925
+ function finalizeTurnItems(
926
+ state: ActiveClaudeTurn,
927
+ status: AgentTurn['status'],
928
+ completedAt: string,
929
+ ) {
930
+ return orderedItems(state).map((item) => {
931
+ if (item.kind === 'userMessage') {
932
+ return item;
933
+ }
934
+ if (status === 'completed' && isRunningHistoryItemStatus(item.status)) {
935
+ return {
936
+ ...item,
937
+ status: 'completed',
938
+ createdAt: item.createdAt ?? completedAt,
939
+ };
940
+ }
941
+ return withHistoryItemCreatedAt(item, completedAt);
942
+ });
943
+ }
944
+
879
945
  function stringFromRecord(value: Record<string, unknown>, key: string) {
880
946
  const raw = value[key];
881
947
  return typeof raw === 'string' && raw.trim() ? raw : null;
@@ -1235,7 +1301,15 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1235
1301
  const sessions = await this.withClaudeConfigEnv(() => this.listSessionsFn({} as ListSessionsOptions));
1236
1302
  return sessions.map((session) => {
1237
1303
  this.knownSessionIds.add(session.sessionId);
1238
- return sessionSummaryFromInfo(session);
1304
+ const summary = sessionSummaryFromInfo(session);
1305
+ const activeTurn = this.activeTurnForSession(summary.providerSessionId);
1306
+ return activeTurn
1307
+ ? {
1308
+ ...summary,
1309
+ status: 'running' as const,
1310
+ updatedAt: summary.updatedAt ?? activeTurn.startedAt,
1311
+ }
1312
+ : summary;
1239
1313
  });
1240
1314
  }
1241
1315
 
@@ -1418,9 +1492,12 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1418
1492
  tools: { type: 'preset', preset: 'claude_code' },
1419
1493
  }),
1420
1494
  });
1421
- const userItem = userMessageToHistoryItem(`${providerTurnId}:user`, {
1422
- content: input.prompt,
1423
- });
1495
+ const userItem = withHistoryItemCreatedAt(
1496
+ userMessageToHistoryItem(`${providerTurnId}:user`, {
1497
+ content: input.prompt,
1498
+ }),
1499
+ startedAt,
1500
+ );
1424
1501
  const initialItems = input.hidden ? [] : [userItem];
1425
1502
  const state: ActiveClaudeTurn = {
1426
1503
  providerSessionId: input.providerSessionId,
@@ -1511,6 +1588,15 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1511
1588
  }
1512
1589
  }
1513
1590
 
1591
+ private activeTurnForSession(providerSessionId: string) {
1592
+ for (const state of this.activeTurns.values()) {
1593
+ if (state.providerSessionId === providerSessionId && !state.completed) {
1594
+ return state;
1595
+ }
1596
+ }
1597
+ return null;
1598
+ }
1599
+
1514
1600
  private reconcileActiveTranscriptTurn(
1515
1601
  providerSessionId: string,
1516
1602
  turns: AgentTurn[],
@@ -1594,31 +1680,16 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1594
1680
 
1595
1681
  private async consumeQuery(state: ActiveClaudeTurn) {
1596
1682
  const rawMessages: SDKMessage[] = [];
1683
+ let terminalStatus: AgentTurn['status'] | null = null;
1684
+ let terminalError: string | null = null;
1597
1685
  try {
1598
1686
  for await (const message of state.query) {
1599
1687
  rawMessages.push(message);
1600
- if (state.completed) {
1601
- continue;
1602
- }
1603
1688
  this.consumeMessage(state, message);
1604
1689
  const status = queryResultStatus(message);
1605
1690
  if (status) {
1606
- state.completed = true;
1607
- this.deleteActiveTurn(state);
1608
- this.emitUsage(state);
1609
- this.emitRuntimeEvent({
1610
- type: 'turn.completed',
1611
- provider: 'claude',
1612
- providerSessionId: state.providerSessionId,
1613
- turn: buildAgentTurn({
1614
- providerTurnId: state.providerTurnId,
1615
- startedAt: state.startedAt,
1616
- status: state.interrupted ? 'interrupted' : status,
1617
- error: queryResultError(message),
1618
- items: orderedItems(state),
1619
- rawTurn: rawMessages,
1620
- }),
1621
- });
1691
+ terminalStatus = status;
1692
+ terminalError = queryResultError(message);
1622
1693
  }
1623
1694
  }
1624
1695
 
@@ -1626,6 +1697,10 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1626
1697
  state.completed = true;
1627
1698
  this.deleteActiveTurn(state);
1628
1699
  this.emitUsage(state);
1700
+ const completedAt = new Date().toISOString();
1701
+ const status = state.interrupted
1702
+ ? 'interrupted'
1703
+ : terminalStatus ?? 'completed';
1629
1704
  this.emitRuntimeEvent({
1630
1705
  type: 'turn.completed',
1631
1706
  provider: 'claude',
@@ -1633,8 +1708,9 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1633
1708
  turn: buildAgentTurn({
1634
1709
  providerTurnId: state.providerTurnId,
1635
1710
  startedAt: state.startedAt,
1636
- status: state.interrupted ? 'interrupted' : 'completed',
1637
- items: orderedItems(state),
1711
+ status,
1712
+ error: terminalError,
1713
+ items: finalizeTurnItems(state, status, completedAt),
1638
1714
  rawTurn: rawMessages,
1639
1715
  }),
1640
1716
  });
@@ -1656,6 +1732,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1656
1732
 
1657
1733
  private consumeMessage(state: ActiveClaudeTurn, message: SDKMessage) {
1658
1734
  this.captureUsage(state, message);
1735
+ const messageCreatedAt = new Date().toISOString();
1659
1736
 
1660
1737
  if (message.type === 'system' && message.subtype === 'init') {
1661
1738
  this.updateToolboxItemsFromSystemInit(message);
@@ -1677,8 +1754,9 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1677
1754
  event: message.event,
1678
1755
  });
1679
1756
  if (toolItem) {
1680
- addOrUpdateItem(state, toolItem);
1681
- this.emitItem(state, toolItem, 'item.started');
1757
+ const nextItem = withHistoryItemCreatedAt(toolItem, messageCreatedAt);
1758
+ addOrUpdateItem(state, nextItem);
1759
+ this.emitItem(state, nextItem, 'item.started');
1682
1760
  return;
1683
1761
  }
1684
1762
 
@@ -1688,16 +1766,22 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1688
1766
  });
1689
1767
  if (reasoningItem) {
1690
1768
  const existing = state.items.get(reasoningItem.id);
1691
- const nextItem: AgentHistoryItem = existing?.kind === 'reasoning'
1769
+ const previousReasoning = existing?.kind === 'reasoning'
1770
+ ? existing
1771
+ : latestReasoningItem(state);
1772
+ const nextItem: AgentHistoryItem = previousReasoning
1692
1773
  ? {
1693
- ...existing,
1694
- text: `${existing.text}${reasoningItem.text}`,
1695
- status: reasoningItem.status ?? existing.status ?? null,
1774
+ ...previousReasoning,
1775
+ text:
1776
+ previousReasoning.id === reasoningItem.id
1777
+ ? `${previousReasoning.text}${reasoningItem.text}`
1778
+ : joinReasoningText(previousReasoning.text, reasoningItem.text),
1779
+ status: reasoningItem.status ?? previousReasoning.status ?? null,
1696
1780
  }
1697
- : reasoningItem;
1781
+ : withHistoryItemCreatedAt(reasoningItem, messageCreatedAt);
1698
1782
  addOrUpdateItem(state, nextItem);
1699
- this.emitItem(state, nextItem, existing ? 'item.completed' : 'item.started', {
1700
- force: Boolean(existing),
1783
+ this.emitItem(state, nextItem, previousReasoning ? 'item.completed' : 'item.started', {
1784
+ force: Boolean(previousReasoning),
1701
1785
  });
1702
1786
  return;
1703
1787
  }
@@ -1715,6 +1799,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1715
1799
  })
1716
1800
  : markTransientAgentHistoryItem({
1717
1801
  id: delta.itemId,
1802
+ createdAt: messageCreatedAt,
1718
1803
  kind: 'agentMessage',
1719
1804
  text: delta.delta,
1720
1805
  });
@@ -1743,8 +1828,9 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1743
1828
  result: message.tool_use_result ?? toolResult.result,
1744
1829
  previous: state.items.get(toolResult.toolUseId) ?? null,
1745
1830
  });
1746
- addOrUpdateItem(state, item);
1747
- this.emitItem(state, item, 'item.completed');
1831
+ const nextItem = withHistoryItemCreatedAt(item, messageCreatedAt);
1832
+ addOrUpdateItem(state, nextItem);
1833
+ this.emitItem(state, nextItem, 'item.completed');
1748
1834
  }
1749
1835
  return;
1750
1836
  }
@@ -1776,12 +1862,13 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1776
1862
  messageId: assistantMessageId,
1777
1863
  message: payload,
1778
1864
  })) {
1779
- const existing = state.items.get(item.id);
1780
- addOrUpdateItem(state, item);
1781
- if (item.kind !== 'agentMessage' && !existing) {
1782
- this.emitItem(state, item, 'item.started');
1783
- } else if (item.kind !== 'agentMessage' && existing) {
1784
- this.emitItem(state, item, 'item.started', { force: true });
1865
+ const nextItem = withHistoryItemCreatedAt(item, messageCreatedAt);
1866
+ const existing = state.items.get(nextItem.id);
1867
+ addOrUpdateItem(state, nextItem);
1868
+ if (nextItem.kind !== 'agentMessage' && !existing) {
1869
+ this.emitItem(state, nextItem, 'item.started');
1870
+ } else if (nextItem.kind !== 'agentMessage' && existing) {
1871
+ this.emitItem(state, nextItem, 'item.started', { force: true });
1785
1872
  }
1786
1873
  }
1787
1874
  return;
@@ -1796,8 +1883,9 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1796
1883
  result: message.tool_use_result ?? message.message,
1797
1884
  previous: state.items.get(message.parent_tool_use_id) ?? null,
1798
1885
  });
1799
- addOrUpdateItem(state, item);
1800
- this.emitItem(state, item, 'item.completed');
1886
+ const nextItem = withHistoryItemCreatedAt(item, messageCreatedAt);
1887
+ addOrUpdateItem(state, nextItem);
1888
+ this.emitItem(state, nextItem, 'item.completed');
1801
1889
  return;
1802
1890
  }
1803
1891
 
@@ -1817,8 +1905,9 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1817
1905
  status: 'running',
1818
1906
  });
1819
1907
  if (item) {
1820
- addOrUpdateItem(state, item);
1821
- this.emitItem(state, item, 'item.started');
1908
+ const nextItem = withHistoryItemCreatedAt(item, messageCreatedAt);
1909
+ addOrUpdateItem(state, nextItem);
1910
+ this.emitItem(state, nextItem, 'item.started');
1822
1911
  } else {
1823
1912
  state.suppressedToolUseIds.add(toolUseId);
1824
1913
  }
@@ -1840,13 +1929,15 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1840
1929
  }
1841
1930
  : {
1842
1931
  id: toolUseId,
1932
+ createdAt: messageCreatedAt,
1843
1933
  kind: 'toolCall',
1844
1934
  text: `${message.tool_name} denied`,
1845
1935
  detailText: typeof message.message === 'string' ? message.message : null,
1846
1936
  status: 'denied',
1847
1937
  };
1848
- addOrUpdateItem(state, item);
1849
- this.emitItem(state, item, 'item.completed');
1938
+ const nextItem = withHistoryItemCreatedAt(item, messageCreatedAt);
1939
+ addOrUpdateItem(state, nextItem);
1940
+ this.emitItem(state, nextItem, 'item.completed');
1850
1941
  }
1851
1942
  }
1852
1943
 
@@ -2076,6 +2167,18 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
2076
2167
  itemsById: new Map(),
2077
2168
  };
2078
2169
  }
2170
+ const previous = current.items.at(-1);
2171
+ if (item.kind === 'reasoning' && previous?.kind === 'reasoning') {
2172
+ const merged = {
2173
+ ...previous,
2174
+ text: joinReasoningText(previous.text, item.text),
2175
+ status: item.status ?? previous.status ?? null,
2176
+ };
2177
+ current.items[current.items.length - 1] = merged;
2178
+ current.itemsById.set(previous.id, merged);
2179
+ current.itemsById.set(item.id, merged);
2180
+ return;
2181
+ }
2079
2182
  const existingIndex = current.items.findIndex((entry) => entry.id === item.id);
2080
2183
  if (existingIndex >= 0) {
2081
2184
  current.items[existingIndex] = item;
@@ -2094,11 +2197,16 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
2094
2197
  if (toolResults.length > 0) {
2095
2198
  for (const toolResult of toolResults) {
2096
2199
  const previous = current?.itemsById.get(toolResult.toolUseId) ?? null;
2097
- upsertCurrentItem(resultForToolUse({
2098
- toolUseId: toolResult.toolUseId,
2099
- result: toolResult.result,
2100
- previous,
2101
- }));
2200
+ upsertCurrentItem(
2201
+ withHistoryItemCreatedAt(
2202
+ resultForToolUse({
2203
+ toolUseId: toolResult.toolUseId,
2204
+ result: toolResult.result,
2205
+ previous,
2206
+ }),
2207
+ sessionMessageTimestamp(message) ?? current?.startedAt,
2208
+ ),
2209
+ );
2102
2210
  }
2103
2211
  continue;
2104
2212
  }
@@ -2126,16 +2234,18 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
2126
2234
  }));
2127
2235
  }
2128
2236
  const messageUuid = message.uuid ?? randomUUID();
2237
+ const userStartedAt = isoFromUuidV7(messageUuid);
2129
2238
  const userItem = await this.userMessageToHistoryItem(
2130
2239
  messageUuid,
2131
2240
  message.message,
2132
2241
  context,
2133
2242
  );
2243
+ const stampedUserItem = withHistoryItemCreatedAt(userItem, userStartedAt);
2134
2244
  current = {
2135
2245
  providerTurnId: `claude-turn-${messageUuid}`,
2136
- startedAt: isoFromUuidV7(messageUuid),
2137
- items: [userItem],
2138
- itemsById: new Map([[messageUuid, userItem]]),
2246
+ startedAt: userStartedAt,
2247
+ items: [stampedUserItem],
2248
+ itemsById: new Map([[messageUuid, stampedUserItem]]),
2139
2249
  };
2140
2250
  continue;
2141
2251
  }
@@ -2145,6 +2255,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
2145
2255
  }
2146
2256
 
2147
2257
  if (message.type === 'assistant') {
2258
+ const assistantCreatedAt = sessionMessageTimestamp(message) ?? current?.startedAt;
2148
2259
  for (const toolUseId of suppressedClaudeToolUseIds(message.message)) {
2149
2260
  suppressedToolUseIds.add(toolUseId);
2150
2261
  current?.itemsById.delete(toolUseId);
@@ -2153,7 +2264,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
2153
2264
  messageId: message.uuid ?? randomUUID(),
2154
2265
  message: message.message,
2155
2266
  })) {
2156
- upsertCurrentItem(item);
2267
+ upsertCurrentItem(withHistoryItemCreatedAt(item, assistantCreatedAt));
2157
2268
  }
2158
2269
  continue;
2159
2270
  }
@@ -2163,13 +2274,18 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
2163
2274
  continue;
2164
2275
  }
2165
2276
  const previous = current?.itemsById.get(message.parent_tool_use_id) ?? null;
2166
- upsertCurrentItem(resultForToolUse({
2167
- toolUseId: message.parent_tool_use_id,
2168
- result: isRecord(message.message) && 'content' in message.message
2169
- ? message.message.content
2170
- : message.message,
2171
- previous,
2172
- }));
2277
+ upsertCurrentItem(
2278
+ withHistoryItemCreatedAt(
2279
+ resultForToolUse({
2280
+ toolUseId: message.parent_tool_use_id,
2281
+ result: isRecord(message.message) && 'content' in message.message
2282
+ ? message.message.content
2283
+ : message.message,
2284
+ previous,
2285
+ }),
2286
+ sessionMessageTimestamp(message) ?? current?.startedAt,
2287
+ ),
2288
+ );
2173
2289
  }
2174
2290
  }
2175
2291
 
@@ -509,6 +509,7 @@ export function shouldPersistLiveHistoryItem(item: ThreadHistoryItemDto) {
509
509
  item.kind === 'agentToolCall' ||
510
510
  item.kind === 'skillToolCall' ||
511
511
  item.kind === 'toolCall' ||
512
+ item.kind === 'reasoning' ||
512
513
  item.kind === 'webSearch'
513
514
  );
514
515
  }
@@ -71,9 +71,9 @@ describe('modelPricing', () => {
71
71
  expect(estimate?.totalUsd).toBeCloseTo(0.125625, 10);
72
72
  });
73
73
 
74
- it('prices Claude Sonnet and its 1M context option from the local pricing config', () => {
74
+ it('prices current Claude Sonnet aliases from the local pricing config', () => {
75
75
  expect(supportsFastMode('sonnet')).toBe(false);
76
- expect(contextWindowForModel('sonnet')).toBe(200000);
76
+ expect(contextWindowForModel('sonnet')).toBe(1000000);
77
77
  expect(contextWindowForModel('sonnet[1m]')).toBe(1000000);
78
78
 
79
79
  const standardEstimate = estimateTurnPrice(sampleUsage, {
@@ -83,9 +83,9 @@ describe('modelPricing', () => {
83
83
  expect(standardEstimate).toMatchObject({
84
84
  pricingModelKey: 'sonnet',
85
85
  pricingTierKey: 'standard',
86
- inputUsd: 0.003,
87
- cachedInputUsd: 0.00015,
88
- outputUsd: 0.0225,
86
+ inputUsd: 0.002,
87
+ cachedInputUsd: 0.0001,
88
+ outputUsd: 0.015,
89
89
  });
90
90
 
91
91
  const oneMillionEstimate = estimateTurnPrice(sampleUsage, {
@@ -95,22 +95,35 @@ describe('modelPricing', () => {
95
95
  expect(oneMillionEstimate).toMatchObject({
96
96
  pricingModelKey: 'sonnet[1m]',
97
97
  pricingTierKey: 'standard',
98
- inputUsd: 0.006,
99
- cachedInputUsd: 0.0003,
100
- outputUsd: 0.03375,
98
+ inputUsd: 0.002,
99
+ cachedInputUsd: 0.0001,
100
+ outputUsd: 0.015,
101
101
  });
102
102
  });
103
103
 
104
- it('prices current Claude Opus and Haiku aliases from the local pricing config', () => {
105
- expect(contextWindowForModel('claude-opus-4-7')).toBe(200000);
104
+ it('prices current Claude Fable, Opus, and Haiku aliases from the local pricing config', () => {
105
+ expect(contextWindowForModel('fable')).toBe(1000000);
106
+ expect(contextWindowForModel('opus')).toBe(1000000);
106
107
  expect(contextWindowForModel('claude-haiku-4-5')).toBe(200000);
107
108
 
109
+ const fableEstimate = estimateTurnPrice(sampleUsage, {
110
+ pricingModelKey: 'fable',
111
+ pricingTierKey: 'standard',
112
+ });
113
+ expect(fableEstimate).toMatchObject({
114
+ pricingModelKey: 'fable',
115
+ pricingTierKey: 'standard',
116
+ inputUsd: 0.01,
117
+ cachedInputUsd: 0.0005,
118
+ outputUsd: 0.075,
119
+ });
120
+
108
121
  const opusEstimate = estimateTurnPrice(sampleUsage, {
109
- pricingModelKey: 'claude-opus-4-7',
122
+ pricingModelKey: 'opus',
110
123
  pricingTierKey: 'standard',
111
124
  });
112
125
  expect(opusEstimate).toMatchObject({
113
- pricingModelKey: 'claude-opus-4-7',
126
+ pricingModelKey: 'opus',
114
127
  pricingTierKey: 'standard',
115
128
  inputUsd: 0.005,
116
129
  cachedInputUsd: 0.00025,
@@ -130,6 +143,51 @@ describe('modelPricing', () => {
130
143
  });
131
144
  });
132
145
 
146
+ it('prices explicit Claude API model ids, including fast Opus and legacy Opus', () => {
147
+ expect(contextWindowForModel('claude-sonnet-5')).toBe(1000000);
148
+ expect(contextWindowForModel('claude-sonnet-4-6')).toBe(1000000);
149
+ expect(contextWindowForModel('claude-opus-4-8')).toBe(1000000);
150
+ expect(supportsFastMode('claude-opus-4-8')).toBe(true);
151
+ expect(supportsFastMode('claude-opus-4-7')).toBe(true);
152
+ expect(contextWindowForModel('claude-opus-4-1')).toBe(200000);
153
+
154
+ const sonnet5Estimate = estimateTurnPrice(sampleUsage, {
155
+ pricingModelKey: 'claude-sonnet-5',
156
+ pricingTierKey: 'standard',
157
+ });
158
+ expect(sonnet5Estimate).toMatchObject({
159
+ pricingModelKey: 'claude-sonnet-5',
160
+ pricingTierKey: 'standard',
161
+ inputUsd: 0.002,
162
+ cachedInputUsd: 0.0001,
163
+ outputUsd: 0.015,
164
+ });
165
+
166
+ const opusFastEstimate = estimateTurnPrice(sampleUsage, {
167
+ pricingModelKey: 'claude-opus-4-8',
168
+ pricingTierKey: 'fast',
169
+ });
170
+ expect(opusFastEstimate).toMatchObject({
171
+ pricingModelKey: 'claude-opus-4-8',
172
+ pricingTierKey: 'fast',
173
+ inputUsd: 0.01,
174
+ cachedInputUsd: 0.0005,
175
+ outputUsd: 0.075,
176
+ });
177
+
178
+ const legacyOpusEstimate = estimateTurnPrice(sampleUsage, {
179
+ pricingModelKey: 'claude-opus-4-1',
180
+ pricingTierKey: 'standard',
181
+ });
182
+ expect(legacyOpusEstimate).toMatchObject({
183
+ pricingModelKey: 'claude-opus-4-1',
184
+ pricingTierKey: 'standard',
185
+ inputUsd: 0.015,
186
+ cachedInputUsd: 0.00075,
187
+ outputUsd: 0.1125,
188
+ });
189
+ });
190
+
133
191
  it('normalizes Claude date-stamped runtime model names to local pricing keys', () => {
134
192
  expect(contextWindowForModel('claude-sonnet-4-5-20250929')).toBe(200000);
135
193
  expect(supportsFastMode('claude-sonnet-4-5-20250929')).toBe(false);
@@ -112,6 +112,48 @@ export interface RelayDeviceDto {
112
112
  createdAt: string;
113
113
  }
114
114
 
115
+ export interface RelayAdminUserDto extends RelayUserDto {
116
+ lastSeenAt: string | null;
117
+ deviceCount: number;
118
+ conversationCount: number;
119
+ }
120
+
121
+ export interface RelayAdminWorkspaceDto {
122
+ id: string;
123
+ label: string;
124
+ absPath?: string | null;
125
+ }
126
+
127
+ export interface RelayAdminThreadDto {
128
+ id: string;
129
+ title: string;
130
+ workspaceId: string | null;
131
+ workspaceLabel: string | null;
132
+ status: string | null;
133
+ updatedAt: string | null;
134
+ }
135
+
136
+ export interface RelayAdminDeviceDto extends RelayDeviceDto {
137
+ ownerUsername: string;
138
+ ownerEmail: string;
139
+ ipAddress: string | null;
140
+ workspaces: RelayAdminWorkspaceDto[];
141
+ threads: RelayAdminThreadDto[];
142
+ }
143
+
144
+ export interface RelayRegistrationSettingsDto {
145
+ enabled: boolean;
146
+ registrationPassword: string | null;
147
+ approvalRequired: boolean;
148
+ }
149
+
150
+ export interface RelayPendingRegistrationDto {
151
+ id: string;
152
+ email: string;
153
+ username: string;
154
+ createdAt: string;
155
+ }
156
+
115
157
  export type RelayThreadAccessDto = 'read' | 'control';
116
158
  export type RelayWorkspaceAccessDto = 'none' | 'read' | 'write';
117
159
 
@@ -143,7 +185,9 @@ export interface RelaySessionShareDto {
143
185
  deviceId: string;
144
186
  deviceName: string;
145
187
  threadId: string;
188
+ threadTitle: string | null;
146
189
  workspaceId: string | null;
190
+ workspaceLabel: string | null;
147
191
  label: string | null;
148
192
  threadAccess: RelayThreadAccessDto;
149
193
  workspaceAccess: RelayWorkspaceAccessDto;
@@ -183,8 +227,10 @@ export interface RelayLoginResultDto {
183
227
  }
184
228
 
185
229
  export interface RelayRegisterResultDto {
186
- token: string;
187
- session: RelaySessionDto;
230
+ token?: string;
231
+ session?: RelaySessionDto;
232
+ pendingApproval?: boolean;
233
+ request?: RelayPendingRegistrationDto;
188
234
  }
189
235
 
190
236
  export interface RelayCreateDeviceResultDto {
@@ -200,8 +246,12 @@ export interface RelayPortalSummaryDto {
200
246
  }
201
247
 
202
248
  export interface RelayAdminSummaryDto {
203
- users: RelayUserDto[];
204
- devices: RelayDeviceDto[];
249
+ users: RelayAdminUserDto[];
250
+ devices: RelayAdminDeviceDto[];
251
+ shares: RelaySessionShareDto[];
252
+ pendingRegistrations: RelayPendingRegistrationDto[];
253
+ settings: RelayRegistrationSettingsDto;
254
+ conversationWindowDays: number;
205
255
  registrationEnabled: boolean;
206
256
  }
207
257