remote-codex 0.11.29 → 0.11.31

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 (30) hide show
  1. package/apps/relay-server/dist/index.js +713 -39
  2. package/apps/supervisor-api/dist/index.js +64 -10
  3. package/apps/supervisor-web/dist/apple-touch-icon.png +0 -0
  4. package/apps/supervisor-web/dist/assets/{core-oEok_Crl.js → core-DODVy7wn.js} +1 -1
  5. package/apps/supervisor-web/dist/assets/{graph-vendor-DVPtkh3h.js → graph-vendor-DVQUpZ8C.js} +1 -1
  6. package/apps/supervisor-web/dist/assets/index-BkwJP0KB.js +6 -0
  7. package/apps/supervisor-web/dist/assets/index-CJFMmjP5.css +1 -0
  8. package/apps/supervisor-web/dist/assets/{markdown-vendor-BQJfKm05.js → markdown-vendor-RZk8L7-L.js} +1 -1
  9. package/apps/supervisor-web/dist/assets/{react-vendor-CgLzZcV4.js → react-vendor-Dfg_6BLf.js} +1 -1
  10. package/apps/supervisor-web/dist/assets/{terminal-vendor-B365Go3Z.js → terminal-vendor-C5bTa-Ka.js} +1 -1
  11. package/apps/supervisor-web/dist/assets/{thread-ui-DXUs5Aqt.js → thread-ui-BOqbjoiB.js} +27 -27
  12. package/apps/supervisor-web/dist/assets/{ui-vendor-CeKGesq3.js → ui-vendor-CuR8GHb0.js} +1 -1
  13. package/apps/supervisor-web/dist/favicon-16x16.png +0 -0
  14. package/apps/supervisor-web/dist/favicon-32x32.png +0 -0
  15. package/apps/supervisor-web/dist/favicon-48x48.png +0 -0
  16. package/apps/supervisor-web/dist/icon-192.png +0 -0
  17. package/apps/supervisor-web/dist/icon-512.png +0 -0
  18. package/apps/supervisor-web/dist/index.html +15 -9
  19. package/apps/supervisor-web/dist/remote-codex-icon.png +0 -0
  20. package/apps/supervisor-web/dist/site.webmanifest +19 -0
  21. package/config/codex-model-pricing.json +44 -0
  22. package/package.json +1 -1
  23. package/packages/agent-runtime/src/model-pricing.ts +66 -6
  24. package/packages/claude/src/runtimeAdapter.ts +4 -0
  25. package/packages/codex/src/modelPricing.test.ts +84 -0
  26. package/packages/codex/src/types.ts +3 -1
  27. package/packages/opencode/src/runtimeAdapter.ts +15 -0
  28. package/packages/shared/src/index.ts +86 -1
  29. package/apps/supervisor-web/dist/assets/index-FOSXdyuj.css +0 -1
  30. package/apps/supervisor-web/dist/assets/index-fi_pAm4J.js +0 -6
@@ -1,4 +1,4 @@
1
- import{r as s,R as Tv,a as Tw,j as v,b as Fv}from"./react-vendor-CgLzZcV4.js";function mN(t,a){if(typeof t=="function")return t(a);t!=null&&(t.current=a)}function Fw(...t){return a=>{let c=!1;const n=t.map(i=>{const d=mN(i,a);return!c&&typeof d=="function"&&(c=!0),d});if(c)return()=>{for(let i=0;i<n.length;i++){const d=n[i];typeof d=="function"?d():mN(t[i],null)}}}}function B(...t){return s.useCallback(Fw(...t),t)}function Da(t){const a=s.forwardRef((c,n)=>{let{children:i,...d}=c,r=null,h=!1;const o=[];gN(i)&&typeof Tg=="function"&&(i=Tg(i._payload)),s.Children.forEach(i,k=>{var _;if(Gw(k)){h=!0;const f=k;let u="child"in f.props?f.props.child:f.props.children;gN(u)&&typeof Tg=="function"&&(u=Tg(u._payload)),r=Ew(f,u),o.push((_=r==null?void 0:r.props)==null?void 0:_.children)}else o.push(k)}),r?r=s.cloneElement(r,void 0,o):!h&&s.Children.count(i)===1&&s.isValidElement(i)&&(r=i);const y=r?Ww(r):void 0,l=B(n,y);if(!r){if(i||i===0)throw new Error(h?Qw(t):Kw(t));return i}const p=Uw(d,r.props??{});return r.type!==s.Fragment&&(p.ref=n?l:y),s.cloneElement(r,p)});return a.displayName=`${t}.Slot`,a}var Rae=Da("Slot"),GN=Symbol.for("radix.slottable");function Ow(t){const a=c=>"child"in c?c.children(c.child):c.children;return a.displayName=`${t}.Slottable`,a.__radixId=GN,a}var Ew=(t,a)=>{if("child"in t.props){const c=t.props.child;return s.isValidElement(c)?s.cloneElement(c,void 0,t.props.children(c.props.children)):null}return s.isValidElement(a)?a:null};function Uw(t,a){const c={...a};for(const n in a){const i=t[n],d=a[n];/^on[A-Z]/.test(n)?i&&d?c[n]=(...h)=>{const o=d(...h);return i(...h),o}:i&&(c[n]=i):n==="style"?c[n]={...i,...d}:n==="className"&&(c[n]=[i,d].filter(Boolean).join(" "))}return{...t,...c}}function Ww(t){var n,i;let a=(n=Object.getOwnPropertyDescriptor(t.props,"ref"))==null?void 0:n.get,c=a&&"isReactWarning"in a&&a.isReactWarning;return c?t.ref:(a=(i=Object.getOwnPropertyDescriptor(t,"ref"))==null?void 0:i.get,c=a&&"isReactWarning"in a&&a.isReactWarning,c?t.props.ref:t.props.ref||t.ref)}function Gw(t){return s.isValidElement(t)&&typeof t.type=="function"&&"__radixId"in t.type&&t.type.__radixId===GN}var Zw=Symbol.for("react.lazy");function gN(t){return t!=null&&typeof t=="object"&&"$$typeof"in t&&t.$$typeof===Zw&&"_payload"in t&&Xw(t._payload)}function Xw(t){return typeof t=="object"&&t!==null&&"then"in t}var Kw=t=>`${t} failed to slot onto its children. Expected a single React element child or \`Slottable\`.`,Qw=t=>`${t} failed to slot onto its \`Slottable\`. Expected \`Slottable\` to receive a single React element child.`,Tg=Tv[" use ".trim().toString()],e={};/**
1
+ import{r as s,R as Tv,a as Tw,j as v,b as Fv}from"./react-vendor-Dfg_6BLf.js";function mN(t,a){if(typeof t=="function")return t(a);t!=null&&(t.current=a)}function Fw(...t){return a=>{let c=!1;const n=t.map(i=>{const d=mN(i,a);return!c&&typeof d=="function"&&(c=!0),d});if(c)return()=>{for(let i=0;i<n.length;i++){const d=n[i];typeof d=="function"?d():mN(t[i],null)}}}}function B(...t){return s.useCallback(Fw(...t),t)}function Da(t){const a=s.forwardRef((c,n)=>{let{children:i,...d}=c,r=null,h=!1;const o=[];gN(i)&&typeof Tg=="function"&&(i=Tg(i._payload)),s.Children.forEach(i,k=>{var _;if(Gw(k)){h=!0;const f=k;let u="child"in f.props?f.props.child:f.props.children;gN(u)&&typeof Tg=="function"&&(u=Tg(u._payload)),r=Ew(f,u),o.push((_=r==null?void 0:r.props)==null?void 0:_.children)}else o.push(k)}),r?r=s.cloneElement(r,void 0,o):!h&&s.Children.count(i)===1&&s.isValidElement(i)&&(r=i);const y=r?Ww(r):void 0,l=B(n,y);if(!r){if(i||i===0)throw new Error(h?Qw(t):Kw(t));return i}const p=Uw(d,r.props??{});return r.type!==s.Fragment&&(p.ref=n?l:y),s.cloneElement(r,p)});return a.displayName=`${t}.Slot`,a}var Rae=Da("Slot"),GN=Symbol.for("radix.slottable");function Ow(t){const a=c=>"child"in c?c.children(c.child):c.children;return a.displayName=`${t}.Slottable`,a.__radixId=GN,a}var Ew=(t,a)=>{if("child"in t.props){const c=t.props.child;return s.isValidElement(c)?s.cloneElement(c,void 0,t.props.children(c.props.children)):null}return s.isValidElement(a)?a:null};function Uw(t,a){const c={...a};for(const n in a){const i=t[n],d=a[n];/^on[A-Z]/.test(n)?i&&d?c[n]=(...h)=>{const o=d(...h);return i(...h),o}:i&&(c[n]=i):n==="style"?c[n]={...i,...d}:n==="className"&&(c[n]=[i,d].filter(Boolean).join(" "))}return{...t,...c}}function Ww(t){var n,i;let a=(n=Object.getOwnPropertyDescriptor(t.props,"ref"))==null?void 0:n.get,c=a&&"isReactWarning"in a&&a.isReactWarning;return c?t.ref:(a=(i=Object.getOwnPropertyDescriptor(t,"ref"))==null?void 0:i.get,c=a&&"isReactWarning"in a&&a.isReactWarning,c?t.props.ref:t.props.ref||t.ref)}function Gw(t){return s.isValidElement(t)&&typeof t.type=="function"&&"__radixId"in t.type&&t.type.__radixId===GN}var Zw=Symbol.for("react.lazy");function gN(t){return t!=null&&typeof t=="object"&&"$$typeof"in t&&t.$$typeof===Zw&&"_payload"in t&&Xw(t._payload)}function Xw(t){return typeof t=="object"&&t!==null&&"then"in t}var Kw=t=>`${t} failed to slot onto its children. Expected a single React element child or \`Slottable\`.`,Qw=t=>`${t} failed to slot onto its \`Slottable\`. Expected \`Slottable\` to receive a single React element child.`,Tg=Tv[" use ".trim().toString()],e={};/**
2
2
  * @license lucide-react v0.544.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -3,17 +3,23 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Remote Codex Supervisor</title>
7
- <script type="module" crossorigin src="/assets/index-fi_pAm4J.js"></script>
8
- <link rel="modulepreload" crossorigin href="/assets/react-vendor-CgLzZcV4.js">
9
- <link rel="modulepreload" crossorigin href="/assets/ui-vendor-CeKGesq3.js">
10
- <link rel="modulepreload" crossorigin href="/assets/graph-vendor-DVPtkh3h.js">
11
- <link rel="modulepreload" crossorigin href="/assets/terminal-vendor-B365Go3Z.js">
12
- <link rel="modulepreload" crossorigin href="/assets/markdown-vendor-BQJfKm05.js">
13
- <link rel="modulepreload" crossorigin href="/assets/thread-ui-DXUs5Aqt.js">
6
+ <meta name="theme-color" content="#101722" />
7
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
8
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
9
+ <link rel="icon" type="image/png" sizes="48x48" href="/favicon-48x48.png" />
10
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
11
+ <link rel="manifest" href="/site.webmanifest" />
12
+ <title>Remote Codex</title>
13
+ <script type="module" crossorigin src="/assets/index-BkwJP0KB.js"></script>
14
+ <link rel="modulepreload" crossorigin href="/assets/react-vendor-Dfg_6BLf.js">
15
+ <link rel="modulepreload" crossorigin href="/assets/ui-vendor-CuR8GHb0.js">
16
+ <link rel="modulepreload" crossorigin href="/assets/graph-vendor-DVQUpZ8C.js">
17
+ <link rel="modulepreload" crossorigin href="/assets/terminal-vendor-C5bTa-Ka.js">
18
+ <link rel="modulepreload" crossorigin href="/assets/markdown-vendor-RZk8L7-L.js">
19
+ <link rel="modulepreload" crossorigin href="/assets/thread-ui-BOqbjoiB.js">
14
20
  <link rel="stylesheet" crossorigin href="/assets/graph-vendor-C5ap-Sga.css">
15
21
  <link rel="stylesheet" crossorigin href="/assets/terminal-vendor-Beg8tuEN.css">
16
- <link rel="stylesheet" crossorigin href="/assets/index-FOSXdyuj.css">
22
+ <link rel="stylesheet" crossorigin href="/assets/index-CJFMmjP5.css">
17
23
  </head>
18
24
  <body class="bg-stone-950">
19
25
  <div id="root"></div>
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "Remote Codex",
3
+ "short_name": "Remote Codex",
4
+ "icons": [
5
+ {
6
+ "src": "/icon-192.png",
7
+ "sizes": "192x192",
8
+ "type": "image/png"
9
+ },
10
+ {
11
+ "src": "/icon-512.png",
12
+ "sizes": "512x512",
13
+ "type": "image/png"
14
+ }
15
+ ],
16
+ "theme_color": "#101722",
17
+ "background_color": "#101722",
18
+ "display": "standalone"
19
+ }
@@ -9,6 +9,50 @@
9
9
  }
10
10
  },
11
11
  "models": {
12
+ "gpt-5.6": {
13
+ "inputUsdPerMillion": 5,
14
+ "cachedInputUsdPerMillion": 0.5,
15
+ "cacheWriteInputUsdPerMillion": 6.25,
16
+ "outputUsdPerMillion": 30,
17
+ "supportsFastMode": true,
18
+ "contextWindowTokens": 353400,
19
+ "longContextThresholdTokens": 272000,
20
+ "longContextInputMultiplier": 2,
21
+ "longContextOutputMultiplier": 1.5
22
+ },
23
+ "gpt-5.6-sol": {
24
+ "inputUsdPerMillion": 5,
25
+ "cachedInputUsdPerMillion": 0.5,
26
+ "cacheWriteInputUsdPerMillion": 6.25,
27
+ "outputUsdPerMillion": 30,
28
+ "supportsFastMode": true,
29
+ "contextWindowTokens": 353400,
30
+ "longContextThresholdTokens": 272000,
31
+ "longContextInputMultiplier": 2,
32
+ "longContextOutputMultiplier": 1.5
33
+ },
34
+ "gpt-5.6-terra": {
35
+ "inputUsdPerMillion": 2.5,
36
+ "cachedInputUsdPerMillion": 0.25,
37
+ "cacheWriteInputUsdPerMillion": 3.125,
38
+ "outputUsdPerMillion": 15,
39
+ "supportsFastMode": true,
40
+ "contextWindowTokens": 353400,
41
+ "longContextThresholdTokens": 272000,
42
+ "longContextInputMultiplier": 2,
43
+ "longContextOutputMultiplier": 1.5
44
+ },
45
+ "gpt-5.6-luna": {
46
+ "inputUsdPerMillion": 1,
47
+ "cachedInputUsdPerMillion": 0.1,
48
+ "cacheWriteInputUsdPerMillion": 1.25,
49
+ "outputUsdPerMillion": 6,
50
+ "supportsFastMode": true,
51
+ "contextWindowTokens": 353400,
52
+ "longContextThresholdTokens": 272000,
53
+ "longContextInputMultiplier": 2,
54
+ "longContextOutputMultiplier": 1.5
55
+ },
12
56
  "gpt-5.5": {
13
57
  "inputUsdPerMillion": 5,
14
58
  "cachedInputUsdPerMillion": 0.5,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remote-codex",
3
- "version": "0.11.29",
3
+ "version": "0.11.31",
4
4
  "description": "Local web supervisor for Codex workspaces and threads.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -11,10 +11,14 @@ export type PricingTierKey = 'standard' | 'fast';
11
11
  interface ModelPricingEntry {
12
12
  inputUsdPerMillion: number;
13
13
  cachedInputUsdPerMillion: number;
14
+ cacheWriteInputUsdPerMillion?: number;
14
15
  outputUsdPerMillion: number;
15
16
  supportsFastMode: boolean;
16
17
  fastMultiplier?: number;
17
18
  contextWindowTokens?: number;
19
+ longContextThresholdTokens?: number;
20
+ longContextInputMultiplier?: number;
21
+ longContextOutputMultiplier?: number;
18
22
  }
19
23
 
20
24
  interface PricingTierConfig {
@@ -119,10 +123,14 @@ function parsePricingConfig(raw: unknown): PricingConfig {
119
123
  const entry = value as {
120
124
  inputUsdPerMillion?: unknown;
121
125
  cachedInputUsdPerMillion?: unknown;
126
+ cacheWriteInputUsdPerMillion?: unknown;
122
127
  outputUsdPerMillion?: unknown;
123
128
  supportsFastMode?: unknown;
124
129
  fastMultiplier?: unknown;
125
130
  contextWindowTokens?: unknown;
131
+ longContextThresholdTokens?: unknown;
132
+ longContextInputMultiplier?: unknown;
133
+ longContextOutputMultiplier?: unknown;
126
134
  };
127
135
 
128
136
  if (
@@ -133,6 +141,12 @@ function parsePricingConfig(raw: unknown): PricingConfig {
133
141
  ) {
134
142
  throw new Error(`Pricing config model "${modelKey}" has invalid fields.`);
135
143
  }
144
+ if (
145
+ entry.cacheWriteInputUsdPerMillion !== undefined &&
146
+ !isPositiveNumber(entry.cacheWriteInputUsdPerMillion)
147
+ ) {
148
+ throw new Error(`Pricing config model "${modelKey}" cacheWriteInputUsdPerMillion must be a non-negative number.`);
149
+ }
136
150
  if (
137
151
  entry.fastMultiplier !== undefined &&
138
152
  !isPositiveNumber(entry.fastMultiplier)
@@ -145,10 +159,22 @@ function parsePricingConfig(raw: unknown): PricingConfig {
145
159
  ) {
146
160
  throw new Error(`Pricing config model "${modelKey}" contextWindowTokens must be a non-negative number.`);
147
161
  }
162
+ for (const field of [
163
+ 'longContextThresholdTokens',
164
+ 'longContextInputMultiplier',
165
+ 'longContextOutputMultiplier',
166
+ ] as const) {
167
+ if (entry[field] !== undefined && !isPositiveNumber(entry[field])) {
168
+ throw new Error(`Pricing config model "${modelKey}" ${field} must be a non-negative number.`);
169
+ }
170
+ }
148
171
 
149
172
  models[modelKey] = {
150
173
  inputUsdPerMillion: entry.inputUsdPerMillion as number,
151
174
  cachedInputUsdPerMillion: entry.cachedInputUsdPerMillion as number,
175
+ ...(entry.cacheWriteInputUsdPerMillion !== undefined
176
+ ? { cacheWriteInputUsdPerMillion: entry.cacheWriteInputUsdPerMillion as number }
177
+ : {}),
152
178
  outputUsdPerMillion: entry.outputUsdPerMillion as number,
153
179
  supportsFastMode: entry.supportsFastMode,
154
180
  ...(entry.fastMultiplier !== undefined
@@ -157,6 +183,15 @@ function parsePricingConfig(raw: unknown): PricingConfig {
157
183
  ...(entry.contextWindowTokens !== undefined
158
184
  ? { contextWindowTokens: entry.contextWindowTokens as number }
159
185
  : {}),
186
+ ...(entry.longContextThresholdTokens !== undefined
187
+ ? { longContextThresholdTokens: entry.longContextThresholdTokens as number }
188
+ : {}),
189
+ ...(entry.longContextInputMultiplier !== undefined
190
+ ? { longContextInputMultiplier: entry.longContextInputMultiplier as number }
191
+ : {}),
192
+ ...(entry.longContextOutputMultiplier !== undefined
193
+ ? { longContextOutputMultiplier: entry.longContextOutputMultiplier as number }
194
+ : {}),
160
195
  };
161
196
  }
162
197
 
@@ -293,24 +328,48 @@ export function estimateTurnPrice(
293
328
  }
294
329
 
295
330
  const nonCachedInputTokens = Math.max(
296
- usage.total.inputTokens - usage.total.cachedInputTokens,
331
+ usage.total.inputTokens -
332
+ usage.total.cachedInputTokens -
333
+ (usage.total.cacheWriteInputTokens ?? 0),
297
334
  0,
298
335
  );
299
336
  const cachedInputTokens = Math.max(usage.total.cachedInputTokens, 0);
337
+ const cacheWriteInputTokens = Math.max(
338
+ usage.total.cacheWriteInputTokens ?? 0,
339
+ 0,
340
+ );
300
341
  const outputTokens = Math.max(usage.total.outputTokens, 0);
301
- const multiplier =
342
+ const tierMultiplier =
302
343
  tierKey === 'fast' && modelPricing.fastMultiplier !== undefined
303
344
  ? modelPricing.fastMultiplier
304
345
  : tier.multiplier;
346
+ const usesLongContextPricing =
347
+ typeof modelPricing.longContextThresholdTokens === 'number' &&
348
+ usage.total.inputTokens > modelPricing.longContextThresholdTokens;
349
+ const inputMultiplier =
350
+ tierMultiplier *
351
+ (usesLongContextPricing
352
+ ? modelPricing.longContextInputMultiplier ?? 1
353
+ : 1);
354
+ const outputMultiplier =
355
+ tierMultiplier *
356
+ (usesLongContextPricing
357
+ ? modelPricing.longContextOutputMultiplier ?? 1
358
+ : 1);
305
359
 
306
360
  const inputUsd =
307
- (nonCachedInputTokens * modelPricing.inputUsdPerMillion * multiplier) /
361
+ (nonCachedInputTokens * modelPricing.inputUsdPerMillion * inputMultiplier) /
308
362
  TOKEN_PRICE_DENOMINATOR;
309
363
  const cachedInputUsd =
310
- (cachedInputTokens * modelPricing.cachedInputUsdPerMillion * multiplier) /
364
+ (cachedInputTokens * modelPricing.cachedInputUsdPerMillion * inputMultiplier) /
365
+ TOKEN_PRICE_DENOMINATOR;
366
+ const cacheWriteInputUsd =
367
+ (cacheWriteInputTokens *
368
+ (modelPricing.cacheWriteInputUsdPerMillion ?? modelPricing.inputUsdPerMillion) *
369
+ inputMultiplier) /
311
370
  TOKEN_PRICE_DENOMINATOR;
312
371
  const outputUsd =
313
- (outputTokens * modelPricing.outputUsdPerMillion * multiplier) /
372
+ (outputTokens * modelPricing.outputUsdPerMillion * outputMultiplier) /
314
373
  TOKEN_PRICE_DENOMINATOR;
315
374
 
316
375
  return {
@@ -319,7 +378,8 @@ export function estimateTurnPrice(
319
378
  currency: pricingConfig.currency,
320
379
  inputUsd,
321
380
  cachedInputUsd,
381
+ cacheWriteInputUsd,
322
382
  outputUsd,
323
- totalUsd: inputUsd + cachedInputUsd + outputUsd,
383
+ totalUsd: inputUsd + cachedInputUsd + cacheWriteInputUsd + outputUsd,
324
384
  };
325
385
  }
@@ -364,6 +364,7 @@ interface ClaudeTokenUsageBreakdown {
364
364
  totalTokens: number;
365
365
  inputTokens: number;
366
366
  cachedInputTokens: number;
367
+ cacheWriteInputTokens: number;
367
368
  outputTokens: number;
368
369
  reasoningOutputTokens: number;
369
370
  }
@@ -792,6 +793,8 @@ function addClaudeUsage(
792
793
  totalTokens: left.totalTokens + right.totalTokens,
793
794
  inputTokens: left.inputTokens + right.inputTokens,
794
795
  cachedInputTokens: left.cachedInputTokens + right.cachedInputTokens,
796
+ cacheWriteInputTokens:
797
+ left.cacheWriteInputTokens + right.cacheWriteInputTokens,
795
798
  outputTokens: left.outputTokens + right.outputTokens,
796
799
  reasoningOutputTokens: left.reasoningOutputTokens + right.reasoningOutputTokens,
797
800
  };
@@ -821,6 +824,7 @@ function normalizeClaudeUsage(value: unknown): ClaudeTokenUsageBreakdown | null
821
824
  totalTokens,
822
825
  inputTokens,
823
826
  cachedInputTokens: cacheReadInputTokens,
827
+ cacheWriteInputTokens: cacheCreationInputTokens,
824
828
  outputTokens,
825
829
  reasoningOutputTokens: 0,
826
830
  };
@@ -52,6 +52,90 @@ describe('modelPricing', () => {
52
52
  expect(estimate?.totalUsd).toBeCloseTo(0.05025, 10);
53
53
  });
54
54
 
55
+ it('prices GPT-5.6 aliases and cache writes with the published rates', () => {
56
+ const usage = {
57
+ ...sampleUsage,
58
+ total: {
59
+ ...sampleUsage.total,
60
+ cacheWriteInputTokens: 200,
61
+ },
62
+ last: {
63
+ ...sampleUsage.last,
64
+ cacheWriteInputTokens: 200,
65
+ },
66
+ modelContextWindow: 353_400,
67
+ };
68
+
69
+ expect(contextWindowForModel('gpt-5.6')).toBe(353_400);
70
+ expect(contextWindowForModel('openai/gpt-5.6-sol')).toBe(353_400);
71
+ expect(supportsFastMode('gpt-5.6-terra')).toBe(true);
72
+ expect(buildTurnPricingSnapshot('gpt-5.6-luna', false)).toEqual({
73
+ pricingModelKey: 'gpt-5.6-luna',
74
+ pricingTierKey: 'standard',
75
+ });
76
+
77
+ const solEstimate = estimateTurnPrice(usage, {
78
+ pricingModelKey: 'gpt-5.6-sol',
79
+ pricingTierKey: 'standard',
80
+ });
81
+ expect(solEstimate).toMatchObject({
82
+ pricingModelKey: 'gpt-5.6-sol',
83
+ inputUsd: 0.004,
84
+ cachedInputUsd: 0.00025,
85
+ cacheWriteInputUsd: 0.00125,
86
+ outputUsd: 0.045,
87
+ });
88
+ expect(solEstimate?.totalUsd).toBeCloseTo(0.0505, 10);
89
+
90
+ const terraEstimate = estimateTurnPrice(usage, {
91
+ pricingModelKey: 'gpt-5.6-terra',
92
+ pricingTierKey: 'standard',
93
+ });
94
+ expect(terraEstimate?.cacheWriteInputUsd).toBeCloseTo(0.000625, 10);
95
+
96
+ const lunaEstimate = estimateTurnPrice(usage, {
97
+ pricingModelKey: 'gpt-5.6-luna',
98
+ pricingTierKey: 'standard',
99
+ });
100
+ expect(lunaEstimate?.cacheWriteInputUsd).toBeCloseTo(0.00025, 10);
101
+ });
102
+
103
+ it('applies GPT-5.6 long-context rates above 272K input tokens', () => {
104
+ const estimate = estimateTurnPrice(
105
+ {
106
+ total: {
107
+ totalTokens: 301_000,
108
+ inputTokens: 300_000,
109
+ cachedInputTokens: 0,
110
+ cacheWriteInputTokens: 0,
111
+ outputTokens: 1_000,
112
+ reasoningOutputTokens: 0,
113
+ },
114
+ last: {
115
+ totalTokens: 301_000,
116
+ inputTokens: 300_000,
117
+ cachedInputTokens: 0,
118
+ cacheWriteInputTokens: 0,
119
+ outputTokens: 1_000,
120
+ reasoningOutputTokens: 0,
121
+ },
122
+ modelContextWindow: 353_400,
123
+ },
124
+ {
125
+ pricingModelKey: 'gpt-5.6-sol',
126
+ pricingTierKey: 'standard',
127
+ },
128
+ );
129
+
130
+ expect(estimate).toMatchObject({
131
+ inputUsd: 3,
132
+ cachedInputUsd: 0,
133
+ cacheWriteInputUsd: 0,
134
+ outputUsd: 0.045,
135
+ });
136
+ expect(estimate?.totalUsd).toBeCloseTo(3.045, 10);
137
+ });
138
+
55
139
  it('uses the gpt-5.5-specific fast multiplier and marks it fast-capable', () => {
56
140
  expect(supportsFastMode('gpt-5.5')).toBe(true);
57
141
  expect(contextWindowForModel('gpt-5.5')).toBe(272000);
@@ -63,7 +63,9 @@ export type ReasoningEffort =
63
63
  | 'low'
64
64
  | 'medium'
65
65
  | 'high'
66
- | 'xhigh';
66
+ | 'xhigh'
67
+ | 'max'
68
+ | 'ultra';
67
69
 
68
70
  export type CollaborationModeKind = 'default' | 'plan';
69
71
  export type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access';
@@ -439,6 +439,13 @@ function openCodeUsageFromTokens(tokens: unknown) {
439
439
  const cachedInputTokens = nonNegativeNumberValue(
440
440
  tokens.cachedInputTokens ?? tokens.cached_input_tokens ?? cache?.read,
441
441
  ) ?? 0;
442
+ const cacheWriteInputTokens = nonNegativeNumberValue(
443
+ tokens.cacheWriteInputTokens ??
444
+ tokens.cache_write_input_tokens ??
445
+ tokens.cacheWriteTokens ??
446
+ tokens.cache_write_tokens ??
447
+ cache?.write,
448
+ ) ?? 0;
442
449
  const totalTokens = nonNegativeNumberValue(tokens.total ?? tokens.totalTokens ?? tokens.total_tokens)
443
450
  ?? inputTokens + outputTokens;
444
451
 
@@ -451,6 +458,7 @@ function openCodeUsageFromTokens(tokens: unknown) {
451
458
  totalTokens,
452
459
  inputTokens,
453
460
  cachedInputTokens,
461
+ ...(cacheWriteInputTokens > 0 ? { cacheWriteInputTokens } : {}),
454
462
  outputTokens,
455
463
  reasoningOutputTokens,
456
464
  },
@@ -524,6 +532,13 @@ function turnTokenUsage(messages: unknown[], model: ReturnType<typeof parseModel
524
532
  totalTokens: sum.totalTokens + usage.totalTokens,
525
533
  inputTokens: sum.inputTokens + usage.inputTokens,
526
534
  cachedInputTokens: sum.cachedInputTokens + usage.cachedInputTokens,
535
+ ...((sum.cacheWriteInputTokens ?? 0) + (usage.cacheWriteInputTokens ?? 0) > 0
536
+ ? {
537
+ cacheWriteInputTokens:
538
+ (sum.cacheWriteInputTokens ?? 0) +
539
+ (usage.cacheWriteInputTokens ?? 0),
540
+ }
541
+ : {}),
527
542
  outputTokens: sum.outputTokens + usage.outputTokens,
528
543
  reasoningOutputTokens: sum.reasoningOutputTokens + usage.reasoningOutputTokens,
529
544
  }), firstRecord);
@@ -156,6 +156,8 @@ export interface RelayPendingRegistrationDto {
156
156
 
157
157
  export type RelayThreadAccessDto = 'read' | 'control';
158
158
  export type RelayWorkspaceAccessDto = 'none' | 'read' | 'write';
159
+ export type RelayShareScopeDto = 'thread' | 'workspace' | 'device';
160
+ export type RelayWorkspaceScopeDto = 'all' | 'selected';
159
161
 
160
162
  export interface CreateRelaySessionShareInput {
161
163
  targetIdentifier: string;
@@ -204,15 +206,91 @@ export interface RelaySessionShareAccessDto {
204
206
  shareId: string;
205
207
  userId: string;
206
208
  username: string;
209
+ kind: RelayAccessEventKindDto;
207
210
  accessedAt: string;
208
211
  }
209
212
 
213
+ export type RelayAccessEventKindDto =
214
+ | 'access'
215
+ | 'open_device'
216
+ | 'open_thread'
217
+ | 'create_thread'
218
+ | 'send_prompt'
219
+ | 'read_workspace_file'
220
+ | 'write_workspace_file';
221
+
222
+ export interface RelayAccessGrantEventDto {
223
+ id: string;
224
+ grantId: string;
225
+ userId: string;
226
+ username: string;
227
+ kind: RelayAccessEventKindDto;
228
+ accessedAt: string;
229
+ }
230
+
231
+ export interface CreateRelayAccessGrantInput {
232
+ targetIdentifier: string;
233
+ deviceId: string;
234
+ scope: RelayShareScopeDto;
235
+ threadId?: string | null;
236
+ workspaceId?: string | null;
237
+ workspaceScope?: RelayWorkspaceScopeDto;
238
+ workspaceIds?: string[];
239
+ label?: string | null;
240
+ threadAccess: RelayThreadAccessDto;
241
+ workspaceAccess: RelayWorkspaceAccessDto;
242
+ canCreateThreads?: boolean;
243
+ expiresAt?: string | null;
244
+ }
245
+
246
+ export interface UpdateRelayAccessGrantInput {
247
+ workspaceId?: string | null | undefined;
248
+ workspaceScope?: RelayWorkspaceScopeDto | undefined;
249
+ workspaceIds?: string[] | undefined;
250
+ label?: string | null | undefined;
251
+ threadAccess?: RelayThreadAccessDto | undefined;
252
+ workspaceAccess?: RelayWorkspaceAccessDto | undefined;
253
+ canCreateThreads?: boolean | undefined;
254
+ expiresAt?: string | null | undefined;
255
+ }
256
+
257
+ export interface RelayAccessGrantDto {
258
+ id: string;
259
+ ownerUserId: string;
260
+ ownerUsername: string;
261
+ targetUserId: string;
262
+ targetUsername: string;
263
+ deviceId: string;
264
+ deviceName: string;
265
+ scope: RelayShareScopeDto;
266
+ threadId: string | null;
267
+ threadTitle: string | null;
268
+ workspaceId: string | null;
269
+ workspaceLabel: string | null;
270
+ workspaceScope: RelayWorkspaceScopeDto;
271
+ workspaceIds: string[];
272
+ label: string | null;
273
+ threadAccess: RelayThreadAccessDto;
274
+ workspaceAccess: RelayWorkspaceAccessDto;
275
+ canCreateThreads: boolean;
276
+ createdAt: string;
277
+ revokedAt: string | null;
278
+ expiresAt: string | null;
279
+ lastAccessedAt: string | null;
280
+ lastAccessedByUsername: string | null;
281
+ accessEvents: RelayAccessGrantEventDto[];
282
+ }
283
+
210
284
  export interface RelayEffectiveAccessDto {
211
285
  kind: 'owner' | 'shared';
286
+ grantId?: string | null;
212
287
  shareId: string | null;
288
+ scope?: 'owner' | RelayShareScopeDto;
213
289
  threadAccess: RelayThreadAccessDto;
214
290
  workspaceAccess: RelayWorkspaceAccessDto;
215
291
  workspaceId: string | null;
292
+ workspaceScope?: RelayWorkspaceScopeDto | null;
293
+ canCreateThreads?: boolean;
216
294
  }
217
295
 
218
296
  export interface RelaySessionDto {
@@ -243,12 +321,16 @@ export interface RelayPortalSummaryDto {
243
321
  devices: RelayDeviceDto[];
244
322
  sharedWithMe: RelaySessionShareDto[];
245
323
  sharedByMe: RelaySessionShareDto[];
324
+ sharedDevicesWithMe?: RelayAccessGrantDto[];
325
+ sharedThreadsWithMe?: RelayAccessGrantDto[];
326
+ grantsByMe?: RelayAccessGrantDto[];
246
327
  }
247
328
 
248
329
  export interface RelayAdminSummaryDto {
249
330
  users: RelayAdminUserDto[];
250
331
  devices: RelayAdminDeviceDto[];
251
332
  shares: RelaySessionShareDto[];
333
+ grants?: RelayAccessGrantDto[];
252
334
  pendingRegistrations: RelayPendingRegistrationDto[];
253
335
  settings: RelayRegistrationSettingsDto;
254
336
  conversationWindowDays: number;
@@ -620,7 +702,8 @@ export type ReasoningEffortDto =
620
702
  | 'medium'
621
703
  | 'high'
622
704
  | 'xhigh'
623
- | 'max';
705
+ | 'max'
706
+ | 'ultra';
624
707
  export type CollaborationModeDto = 'default' | 'plan';
625
708
  export type SandboxModeDto = 'read-only' | 'workspace-write' | 'danger-full-access';
626
709
 
@@ -812,6 +895,7 @@ export interface ThreadTurnTokenBreakdownDto {
812
895
  totalTokens: number;
813
896
  inputTokens: number;
814
897
  cachedInputTokens: number;
898
+ cacheWriteInputTokens?: number;
815
899
  outputTokens: number;
816
900
  reasoningOutputTokens: number;
817
901
  }
@@ -830,6 +914,7 @@ export interface ThreadTurnPriceEstimateDto {
830
914
  currency: 'USD';
831
915
  inputUsd: number;
832
916
  cachedInputUsd: number;
917
+ cacheWriteInputUsd?: number;
833
918
  outputUsd: number;
834
919
  totalUsd: number;
835
920
  }