@intentic/sandbox-contract 1.245.0 → 1.247.0

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 (247) hide show
  1. package/README.md +17 -1
  2. package/dist/batch-runs.d.ts +2 -0
  3. package/dist/batch-runs.d.ts.map +1 -1
  4. package/dist/batch-runs.js +1 -0
  5. package/dist/batch-runs.js.map +1 -1
  6. package/dist/command-classes.d.ts +6 -3
  7. package/dist/command-classes.d.ts.map +1 -1
  8. package/dist/command-classes.js +43 -18
  9. package/dist/command-classes.js.map +1 -1
  10. package/dist/contracts/{cursor.contract.d.ts → accounts.contract.d.ts} +102 -3
  11. package/dist/contracts/accounts.contract.d.ts.map +1 -0
  12. package/dist/contracts/accounts.contract.js +61 -0
  13. package/dist/contracts/accounts.contract.js.map +1 -0
  14. package/dist/contracts/agent.contract.d.ts +19 -0
  15. package/dist/contracts/agent.contract.d.ts.map +1 -1
  16. package/dist/contracts/agents.contract.d.ts +121 -0
  17. package/dist/contracts/agents.contract.d.ts.map +1 -1
  18. package/dist/contracts/agents.contract.js +4 -4
  19. package/dist/contracts/agents.contract.js.map +1 -1
  20. package/dist/contracts/ci.contract.d.ts +2 -0
  21. package/dist/contracts/ci.contract.d.ts.map +1 -1
  22. package/dist/contracts/host.contract.d.ts +35 -0
  23. package/dist/contracts/host.contract.d.ts.map +1 -1
  24. package/dist/contracts/host.contract.js +3 -2
  25. package/dist/contracts/host.contract.js.map +1 -1
  26. package/dist/contracts/personas.contract.d.ts +4 -2
  27. package/dist/contracts/personas.contract.d.ts.map +1 -1
  28. package/dist/contracts/runner.contract.d.ts +2 -2
  29. package/dist/contracts/settings.contract.d.ts +58 -20
  30. package/dist/contracts/settings.contract.d.ts.map +1 -1
  31. package/dist/contracts/system.contract.d.ts +80 -30
  32. package/dist/contracts/system.contract.d.ts.map +1 -1
  33. package/dist/contracts/system.contract.js +26 -17
  34. package/dist/contracts/system.contract.js.map +1 -1
  35. package/dist/contracts/usage.contract.d.ts +22 -0
  36. package/dist/contracts/usage.contract.d.ts.map +1 -1
  37. package/dist/contracts/usage.contract.js +19 -0
  38. package/dist/contracts/usage.contract.js.map +1 -1
  39. package/dist/definition.d.ts +20 -28
  40. package/dist/definition.d.ts.map +1 -1
  41. package/dist/documents.d.ts +0 -1
  42. package/dist/documents.d.ts.map +1 -1
  43. package/dist/documents.js +1 -2
  44. package/dist/documents.js.map +1 -1
  45. package/dist/embed.d.ts +23 -0
  46. package/dist/embed.d.ts.map +1 -0
  47. package/dist/embed.js +84 -0
  48. package/dist/embed.js.map +1 -0
  49. package/dist/events.d.ts +21 -0
  50. package/dist/events.d.ts.map +1 -1
  51. package/dist/events.js +5 -2
  52. package/dist/events.js.map +1 -1
  53. package/dist/fast-tier.js +1 -1
  54. package/dist/fast-tier.js.map +1 -1
  55. package/dist/history-state.d.ts.map +1 -1
  56. package/dist/history-state.js +2 -0
  57. package/dist/history-state.js.map +1 -1
  58. package/dist/index.d.ts +453 -306
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +7 -15
  61. package/dist/index.js.map +1 -1
  62. package/dist/model-pins.d.ts +17 -0
  63. package/dist/model-pins.d.ts.map +1 -0
  64. package/dist/{quick-model.js → model-pins.js} +18 -11
  65. package/dist/model-pins.js.map +1 -0
  66. package/dist/model-roles.d.ts +144 -0
  67. package/dist/model-roles.d.ts.map +1 -0
  68. package/dist/model-roles.js +129 -0
  69. package/dist/model-roles.js.map +1 -0
  70. package/dist/peer-dial.d.ts +33 -0
  71. package/dist/peer-dial.d.ts.map +1 -0
  72. package/dist/peer-dial.js +79 -0
  73. package/dist/peer-dial.js.map +1 -0
  74. package/dist/peer-mcp-server.d.ts +36 -0
  75. package/dist/peer-mcp-server.d.ts.map +1 -0
  76. package/dist/peer-mcp-server.js +71 -0
  77. package/dist/peer-mcp-server.js.map +1 -0
  78. package/dist/provider-specs.d.ts +38 -20
  79. package/dist/provider-specs.d.ts.map +1 -1
  80. package/dist/provider-specs.js +39 -13
  81. package/dist/provider-specs.js.map +1 -1
  82. package/dist/runtime-state.d.ts +1 -1
  83. package/dist/runtime-state.js +1 -1
  84. package/dist/runtime-state.js.map +1 -1
  85. package/dist/safety-policy.d.ts +12 -3
  86. package/dist/safety-policy.d.ts.map +1 -1
  87. package/dist/safety-policy.js +30 -5
  88. package/dist/safety-policy.js.map +1 -1
  89. package/dist/schemas/agent.d.ts +27 -8
  90. package/dist/schemas/agent.d.ts.map +1 -1
  91. package/dist/schemas/agent.js +10 -4
  92. package/dist/schemas/agent.js.map +1 -1
  93. package/dist/schemas/agents.d.ts +42 -0
  94. package/dist/schemas/agents.d.ts.map +1 -1
  95. package/dist/schemas/agents.js +25 -4
  96. package/dist/schemas/agents.js.map +1 -1
  97. package/dist/schemas/automations.d.ts +11 -2
  98. package/dist/schemas/automations.d.ts.map +1 -1
  99. package/dist/schemas/automations.js +1 -1
  100. package/dist/schemas/automations.js.map +1 -1
  101. package/dist/schemas/ci.d.ts +6 -0
  102. package/dist/schemas/ci.d.ts.map +1 -1
  103. package/dist/schemas/ci.js +3 -2
  104. package/dist/schemas/ci.js.map +1 -1
  105. package/dist/schemas/context.d.ts +30 -0
  106. package/dist/schemas/context.d.ts.map +1 -0
  107. package/dist/schemas/context.js +34 -0
  108. package/dist/schemas/context.js.map +1 -0
  109. package/dist/schemas/{computers.d.ts → devices.d.ts} +154 -60
  110. package/dist/schemas/devices.d.ts.map +1 -0
  111. package/dist/schemas/devices.js +157 -0
  112. package/dist/schemas/devices.js.map +1 -0
  113. package/dist/schemas/hosts.d.ts +12 -0
  114. package/dist/schemas/hosts.d.ts.map +1 -1
  115. package/dist/schemas/hosts.js +1 -0
  116. package/dist/schemas/hosts.js.map +1 -1
  117. package/dist/schemas/issues.d.ts +0 -5
  118. package/dist/schemas/issues.d.ts.map +1 -1
  119. package/dist/schemas/issues.js +0 -1
  120. package/dist/schemas/issues.js.map +1 -1
  121. package/dist/schemas/personas.d.ts +5 -3
  122. package/dist/schemas/personas.d.ts.map +1 -1
  123. package/dist/schemas/personas.js +3 -2
  124. package/dist/schemas/personas.js.map +1 -1
  125. package/dist/schemas/plan-limits.d.ts +20 -0
  126. package/dist/schemas/plan-limits.d.ts.map +1 -1
  127. package/dist/schemas/plan-limits.js +21 -0
  128. package/dist/schemas/plan-limits.js.map +1 -1
  129. package/dist/schemas/provider-oauth.d.ts +48 -16
  130. package/dist/schemas/provider-oauth.d.ts.map +1 -1
  131. package/dist/schemas/provider-oauth.js +22 -20
  132. package/dist/schemas/provider-oauth.js.map +1 -1
  133. package/dist/schemas/settings.d.ts +42 -16
  134. package/dist/schemas/settings.d.ts.map +1 -1
  135. package/dist/schemas/settings.js +22 -28
  136. package/dist/schemas/settings.js.map +1 -1
  137. package/dist/schemas/terminal.js +9 -9
  138. package/dist/schemas/terminal.js.map +1 -1
  139. package/dist/schemas/usage.d.ts +5 -2
  140. package/dist/schemas/usage.d.ts.map +1 -1
  141. package/dist/schemas/usage.js +5 -2
  142. package/dist/schemas/usage.js.map +1 -1
  143. package/dist/shell-regions.d.ts +4 -0
  144. package/dist/shell-regions.d.ts.map +1 -0
  145. package/dist/shell-regions.js +156 -0
  146. package/dist/shell-regions.js.map +1 -0
  147. package/dist/workspace-state.d.ts +8 -0
  148. package/dist/workspace-state.d.ts.map +1 -1
  149. package/dist/workspace-state.js +13 -5
  150. package/dist/workspace-state.js.map +1 -1
  151. package/package.json +37 -4
  152. package/src/agent-catalog.ts +2 -2
  153. package/src/arrival.ts +3 -3
  154. package/src/batch-runs.test.ts +10 -5
  155. package/src/batch-runs.ts +10 -3
  156. package/src/command-classes.test.ts +195 -71
  157. package/src/command-classes.ts +148 -46
  158. package/src/contracts/accounts.contract.ts +94 -0
  159. package/src/contracts/agents.contract.ts +4 -3
  160. package/src/contracts/exit.contract.ts +2 -2
  161. package/src/contracts/host.contract.ts +17 -5
  162. package/src/contracts/settings.contract.ts +1 -1
  163. package/src/contracts/system.contract.ts +43 -24
  164. package/src/contracts/usage.contract.ts +31 -0
  165. package/src/contracts/vpn.contract.ts +2 -2
  166. package/src/documents.test.ts +2 -1
  167. package/src/documents.ts +7 -11
  168. package/src/embed.test.ts +68 -0
  169. package/src/embed.ts +164 -0
  170. package/src/events.ts +31 -4
  171. package/src/fast-tier.test.ts +1 -1
  172. package/src/fast-tier.ts +5 -5
  173. package/src/history-state.ts +12 -3
  174. package/src/host-protocol.ts +2 -2
  175. package/src/index.ts +8 -16
  176. package/src/model-order.ts +1 -1
  177. package/src/{quick-model.test.ts → model-pins.test.ts} +73 -29
  178. package/src/model-pins.ts +183 -0
  179. package/src/model-roles.ts +224 -0
  180. package/src/peer-dial.test.ts +203 -0
  181. package/src/peer-dial.ts +163 -0
  182. package/src/peer-mcp-server.test.ts +104 -0
  183. package/src/peer-mcp-server.ts +144 -0
  184. package/src/plan-pools.ts +1 -1
  185. package/src/prompt-complexity.test.ts +1 -1
  186. package/src/prompt-complexity.ts +2 -2
  187. package/src/provider-specs.test.ts +45 -18
  188. package/src/provider-specs.ts +147 -67
  189. package/src/routes.test.ts +6 -3
  190. package/src/runner-protocol.ts +1 -1
  191. package/src/runtime-state.ts +2 -2
  192. package/src/safety-policy.test.ts +88 -0
  193. package/src/safety-policy.ts +84 -14
  194. package/src/schemas/agent.ts +83 -30
  195. package/src/schemas/agents.ts +67 -6
  196. package/src/schemas/automations.ts +6 -4
  197. package/src/schemas/capabilities.ts +4 -4
  198. package/src/schemas/ci.ts +23 -6
  199. package/src/schemas/context.ts +87 -0
  200. package/src/schemas/{computers.ts → devices.ts} +190 -107
  201. package/src/schemas/hosts.ts +5 -1
  202. package/src/schemas/issues.ts +0 -4
  203. package/src/schemas/personas.ts +8 -3
  204. package/src/schemas/plan-limits.ts +50 -0
  205. package/src/schemas/provider-oauth.ts +49 -52
  206. package/src/schemas/settings.ts +105 -140
  207. package/src/schemas/terminal.ts +12 -12
  208. package/src/schemas/usage.ts +62 -27
  209. package/src/schemas/version-seam.test.ts +0 -1
  210. package/src/shell-regions.ts +289 -0
  211. package/src/versions.ts +2 -2
  212. package/src/webext-links.ts +2 -2
  213. package/src/webext-protocol.ts +2 -2
  214. package/src/workspace-state.test.ts +48 -1
  215. package/src/workspace-state.ts +48 -11
  216. package/dist/agent-run-model.d.ts +0 -4
  217. package/dist/agent-run-model.d.ts.map +0 -1
  218. package/dist/agent-run-model.js +0 -13
  219. package/dist/agent-run-model.js.map +0 -1
  220. package/dist/contracts/claude.contract.d.ts +0 -91
  221. package/dist/contracts/claude.contract.d.ts.map +0 -1
  222. package/dist/contracts/claude.contract.js +0 -50
  223. package/dist/contracts/claude.contract.js.map +0 -1
  224. package/dist/contracts/cursor.contract.d.ts.map +0 -1
  225. package/dist/contracts/cursor.contract.js +0 -50
  226. package/dist/contracts/cursor.contract.js.map +0 -1
  227. package/dist/contracts/grok.contract.d.ts +0 -36
  228. package/dist/contracts/grok.contract.d.ts.map +0 -1
  229. package/dist/contracts/grok.contract.js +0 -31
  230. package/dist/contracts/grok.contract.js.map +0 -1
  231. package/dist/contracts/keys.contract.d.ts +0 -81
  232. package/dist/contracts/keys.contract.d.ts.map +0 -1
  233. package/dist/contracts/keys.contract.js +0 -51
  234. package/dist/contracts/keys.contract.js.map +0 -1
  235. package/dist/quick-model.d.ts +0 -15
  236. package/dist/quick-model.d.ts.map +0 -1
  237. package/dist/quick-model.js.map +0 -1
  238. package/dist/schemas/computers.d.ts.map +0 -1
  239. package/dist/schemas/computers.js +0 -135
  240. package/dist/schemas/computers.js.map +0 -1
  241. package/src/agent-run-model.test.ts +0 -76
  242. package/src/agent-run-model.ts +0 -65
  243. package/src/contracts/claude.contract.ts +0 -71
  244. package/src/contracts/cursor.contract.ts +0 -74
  245. package/src/contracts/grok.contract.ts +0 -41
  246. package/src/contracts/keys.contract.ts +0 -79
  247. package/src/quick-model.ts +0 -155
@@ -1,135 +0,0 @@
1
- import { z } from "zod";
2
- import { HostFactsSchema } from "./hosts.js";
3
- import { DEV_VERSION } from "../versions.js";
4
- export const MachineSandboxSchema = z.object({
5
- slug: z.string(),
6
- container: z.string(),
7
- name: z.string().optional(),
8
- running: z.boolean(),
9
- image: z.string(),
10
- tunnelRunning: z.boolean().optional(),
11
- });
12
- export const MachineSandboxOpSchema = z.enum([
13
- "start",
14
- "stop",
15
- "restart",
16
- "prepare",
17
- "update",
18
- "rebuild",
19
- "rollback",
20
- "remove",
21
- "logs",
22
- "runner-up",
23
- "runner-remove",
24
- ]);
25
- export const MachineSandboxFlowSchema = z.object({
26
- op: MachineSandboxOpSchema,
27
- slug: z.string().min(1),
28
- hash: z.string().optional(),
29
- parentUrl: z.string().optional(),
30
- pair: z.string().optional().meta({ secret: true }),
31
- definition: z.string().optional(),
32
- overlay: z.string().optional(),
33
- overlayHash: z.string().optional(),
34
- });
35
- export const MachineSandboxFlowInputSchema = MachineSandboxFlowSchema.extend({ id: z.string().min(1) });
36
- export const MachineFlowLineSchema = z.discriminatedUnion("kind", [
37
- z.object({ kind: z.literal("line"), text: z.string() }),
38
- z.object({ kind: z.literal("result"), message: z.string() }),
39
- z.object({ kind: z.literal("error"), message: z.string() }),
40
- ]);
41
- export const MachineCommandSchema = z.enum(["mirror-off", "mirror-on", "sync-pause", "sync-resume", "sync-unpair"]);
42
- export const MachineSandboxIdSchema = z
43
- .string()
44
- .max(200)
45
- .regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/);
46
- export const MachineCommandInputSchema = z.object({
47
- id: z.string().min(1),
48
- command: MachineCommandSchema,
49
- sandboxId: MachineSandboxIdSchema.optional(),
50
- });
51
- export const MachineCommandResultSchema = z.object({
52
- ok: z.boolean(),
53
- message: z.string(),
54
- output: z.string().optional(),
55
- });
56
- export const MachinePairingSchema = z.object({
57
- sandboxId: z.string(),
58
- mode: z.enum(["sync", "mirror"]),
59
- localDir: z.string().optional(),
60
- mirroring: z.enum(["on", "off"]).optional(),
61
- mutagenStatus: z.string().optional(),
62
- conflicts: z.number().int().nonnegative().optional(),
63
- paused: z.boolean().optional(),
64
- backupStatus: z.string().optional(),
65
- });
66
- export const MachinePortStateSchema = z.enum([
67
- "mirrored",
68
- "held-by-sandbox",
69
- "busy",
70
- ]);
71
- export const MachinePortSchema = z.object({
72
- port: z.number().int().min(1).max(65535),
73
- host: z.enum(["127.0.0.1", "::1"]),
74
- sandboxId: z.string(),
75
- state: MachinePortStateSchema,
76
- heldBy: z.string().optional(),
77
- command: z.string().optional(),
78
- });
79
- export const MachineWatcherSchema = z.object({
80
- running: z.boolean(),
81
- pid: z.number().int().optional(),
82
- build: z.string().optional(),
83
- lastTickAt: z.number().optional(),
84
- });
85
- export const WATCHER_STALL_AFTER_MS = 60_000;
86
- export const watcherStalled = (watcher, now) => watcher.running && watcher.lastTickAt !== undefined && now - watcher.lastTickAt > WATCHER_STALL_AFTER_MS;
87
- export const MachineReportSchema = z.object({
88
- hostname: z.string(),
89
- os: z.string(),
90
- agents: z.object({ sync: z.string().optional(), host: z.string().optional() }),
91
- sandboxes: z.array(MachineSandboxSchema),
92
- pairings: z.array(MachinePairingSchema),
93
- ports: z.array(MachinePortSchema),
94
- watcher: MachineWatcherSchema,
95
- capturedAt: z.number(),
96
- });
97
- export const watcherBuildSkew = (report) => {
98
- const running = report.watcher.build;
99
- const installed = report.agents.sync;
100
- if (!report.watcher.running || installed === undefined || installed === DEV_VERSION || running === installed) {
101
- return undefined;
102
- }
103
- return { running, installed };
104
- };
105
- export const ComputerGapSchema = z.enum([
106
- "offline",
107
- "scope-off",
108
- "no-agent",
109
- "unreported",
110
- ]);
111
- export const ComputerSyncSchema = z.object({
112
- machine: z.string(),
113
- mode: z.enum(["sync", "mirror"]),
114
- seenAt: z.number().optional(),
115
- });
116
- export const ComputerSchema = z.object({
117
- key: z.string(),
118
- label: z.string(),
119
- sync: ComputerSyncSchema.optional(),
120
- hostId: z.string().optional(),
121
- online: z.boolean().optional(),
122
- platform: z.string().optional(),
123
- facts: HostFactsSchema.optional(),
124
- hostAgent: z.string().optional(),
125
- lastSeen: z.number().optional(),
126
- report: MachineReportSchema.optional(),
127
- gap: ComputerGapSchema.optional(),
128
- });
129
- export const ComputersListSchema = z.object({ computers: z.array(ComputerSchema) });
130
- export const SyncStatusSchema = z.object({
131
- enrolled: z.boolean(),
132
- available: z.boolean().optional(),
133
- machines: z.array(MachineReportSchema).optional(),
134
- });
135
- //# sourceMappingURL=computers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"computers.js","sourceRoot":"","sources":["../../src/schemas/computers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAyB7C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IAErB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IAGjB,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC;AA2BH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC;IACzC,OAAO;IACP,MAAM;IACN,SAAS;IACT,SAAS;IACT,QAAQ;IACR,SAAS;IACT,UAAU;IACV,QAAQ;IACR,MAAM;IACN,WAAW;IACX,eAAe;CAClB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,EAAE,EAAE,sBAAsB;IAG1B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAGvB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAK3B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAUlD,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACrC,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,6BAA6B,GAAG,wBAAwB,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAMxG,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IAC9D,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IACvD,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IAC5D,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;CAC9D,CAAC,CAAC;AA8BH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC,CAAC;AASpH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC;KAClC,MAAM,EAAE;KACR,GAAG,CAAC,GAAG,CAAC;KACR,KAAK,CAAC,8BAA8B,CAAC,CAAC;AAC3C,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACrB,OAAO,EAAE,oBAAoB;IAC7B,SAAS,EAAE,sBAAsB,CAAC,QAAQ,EAAE;CAC/C,CAAC,CAAC;AAQH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE;IACf,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEhC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAa/B,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE;IAI3C,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAGpC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IACpD,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAM9B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACtC,CAAC,CAAC;AAMH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC;IAEzC,UAAU;IAGV,iBAAiB;IAGjB,MAAM;CACT,CAAC,CAAC;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;IACxC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAElC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,KAAK,EAAE,sBAAsB;IAE7B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAE7B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACjC,CAAC,CAAC;AAKH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAMhC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAM5B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACpC,CAAC,CAAC;AAQH,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAC7C,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAuB,EAAE,GAAW,EAAW,EAAE,CAC5E,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,GAAG,GAAG,OAAO,CAAC,UAAU,GAAG,sBAAsB,CAAC;AAC7G,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAKxC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IAad,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC;IAG9E,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,oBAAoB,CAAC;IACxC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,oBAAoB,CAAC;IACvC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC;IACjC,OAAO,EAAE,oBAAoB;IAG7B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;CACzB,CAAC,CAAC;AAqBH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAqB,EAAoF,EAAE;IACxI,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;IACrC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IACrC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,WAAW,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC3G,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AAClC,CAAC,CAAC;AAIF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,IAAI,CAAC;IAEpC,SAAS;IAGT,WAAW;IAEX,UAAU;IAGV,YAAY;CACf,CAAC,CAAC;AAuBH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IAInB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAGhC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IAEnC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IAEf,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IAGjB,IAAI,EAAE,kBAAkB,CAAC,QAAQ,EAAE;IAEnC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAE7B,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAW9B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,KAAK,EAAE,eAAe,CAAC,QAAQ,EAAE;IAIjC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,MAAM,EAAE,mBAAmB,CAAC,QAAQ,EAAE;IACtC,GAAG,EAAE,iBAAiB,CAAC,QAAQ,EAAE;CACpC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;AAWpF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IAMrB,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACjC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,QAAQ,EAAE;CACpD,CAAC,CAAC"}
@@ -1,76 +0,0 @@
1
- import { expect, test } from "vitest";
2
- import { resolveAgentRunModels } from "./agent-run-model.js";
3
- import type { QuickModelSource } from "./quick-model.js";
4
- import type { AgentRunPin } from "./schemas/agent.js";
5
-
6
- /* Which model a run somebody's BUTTON started opens on. The rule answers the same two surfaces its quick-model
7
- * sibling does: the daemon walks it, the settings row names it, so what these pin is the pair of properties
8
- * that separate the two: an account this sandbox cannot reach never sits at the head of the chain, and an empty
9
- * answer stays empty rather than being filled in with a tier nobody chose.
10
- *
11
- * And one property neither of those covers, new with the pins being objects: an entry's own knobs are the
12
- * entry's, so what survives the walk is the WHOLE pin. A resolver that handed back the pair inside it would run
13
- * the fallback at the head's effort, which is a tier that appears nowhere on the user's screen. */
14
-
15
- const CLAUDE: QuickModelSource = { provider: `claude`, ready: true, models: [`claude-opus-5`, `claude-sonnet-5`] };
16
- const CODEX: QuickModelSource = { provider: `codex`, ready: true, models: [`gpt-5.6`] };
17
- const GOOGLE: QuickModelSource = { provider: `gemini`, ready: true, models: [`gemini-3-pro`] };
18
-
19
- const offline = (source: QuickModelSource): QuickModelSource => ({ ...source, ready: false });
20
-
21
- const pin = (provider: string, model: string, knobs: Partial<AgentRunPin> = {}): AgentRunPin => ({ provider, model, ...knobs });
22
-
23
- test("keeps the user's own order, this list is read, never ranked", () => {
24
- // The opposite of the quick chain, which sorts by tier and cost. Here the order IS the setting: someone who
25
- // put Opus above GPT wants Opus first, and a resolver that knew better would spend the wrong account.
26
- expect(resolveAgentRunModels([CLAUDE, CODEX], [pin(`codex`, `gpt-5.6`), pin(`claude`, `claude-opus-5`)])).toEqual([
27
- { provider: `codex`, model: `gpt-5.6` },
28
- { provider: `claude`, model: `claude-opus-5` },
29
- ]);
30
- });
31
-
32
- test("steps over a provider this sandbox has no credential for", () => {
33
- // The whole reason the setting is a list. With Claude disconnected the head would otherwise be an account
34
- // that fails every Fix with agent, while a perfectly good Codex sits underneath it.
35
- expect(resolveAgentRunModels([offline(CLAUDE), CODEX], [pin(`claude`, `claude-opus-5`), pin(`codex`, `gpt-5.6`)])).toEqual([
36
- { provider: `codex`, model: `gpt-5.6` },
37
- ]);
38
- });
39
-
40
- test("each surviving entry keeps its own knobs, not the head's", () => {
41
- // The effort used to be one setting beside the list, so a fallback ran at whatever the head was set to. It
42
- // is now a property of the entry that actually answers, which is the only place it was ever true.
43
- expect(
44
- resolveAgentRunModels(
45
- [offline(CODEX), CLAUDE],
46
- [pin(`codex`, `gpt-5.6`, { effort: `low` }), pin(`claude`, `claude-opus-5`, { effort: `max`, thinking: true })],
47
- ),
48
- ).toEqual([{ provider: `claude`, model: `claude-opus-5`, effort: `max`, thinking: true }]);
49
- });
50
-
51
- test("resolves to nothing when no pin is reachable: it does NOT fall back to whatever is connected", () => {
52
- // The deliberate difference from resolveQuickModels, which lands on its Auto ladder here. An agent run is
53
- // billed in whole sessions, so an unreachable list hands the choice back to the caller's floor (the user's
54
- // own composer pick) rather than spending an account they never pointed at.
55
- expect(resolveAgentRunModels([offline(CLAUDE), GOOGLE], [pin(`claude`, `claude-opus-5`)])).toEqual([]);
56
- });
57
-
58
- test("an empty list resolves to nothing even with accounts connected", () => {
59
- expect(resolveAgentRunModels([CLAUDE, CODEX, GOOGLE], [])).toEqual([]);
60
- });
61
-
62
- test("drops a duplicate rather than spending two attempts proving one account is out, and the first one's knobs are the ones kept", () => {
63
- // Two entries can now name one model and disagree about how hard it thinks, which is what reordering a list
64
- // by hand produces. The one the user reads first is the one they meant.
65
- expect(
66
- resolveAgentRunModels([CLAUDE], [pin(`claude`, `claude-opus-5`, { effort: `max` }), pin(`claude`, `claude-opus-5`, { effort: `low` })]),
67
- ).toEqual([{ provider: `claude`, model: `claude-opus-5`, effort: `max` }]);
68
- });
69
-
70
- test("carries a model id the static catalog has never heard of", () => {
71
- // The picker offers a custom-id escape hatch, so a pin can name a model released after this build. Second-
72
- // guessing it here would quietly run something other than what the settings row says.
73
- expect(resolveAgentRunModels([CLAUDE], [pin(`claude`, `claude-opus-9-preview`)])).toEqual([
74
- { provider: `claude`, model: `claude-opus-9-preview` },
75
- ]);
76
- });
@@ -1,65 +0,0 @@
1
- import { quickModelKey, type QuickModelSource } from "./quick-model.js";
2
- import type { AgentRunPin } from "./schemas/agent.js";
3
-
4
- /* WHAT A SURFACE-STARTED AGENT RUN OPENS ON, the resolver for `agentRunModels`, sibling to resolveQuickModels
5
- * and deliberately not the same function.
6
- *
7
- * BOTH ARE ORDERED LISTS, FOR THE SAME REASON. One connected account whose allowance went on the chat this
8
- * morning is enough to take every one of these down: the user presses Fix with agent on a red pipeline, an
9
- * isolated session opens, and it dies on a credential error they cannot see from the row. Written in order, the
10
- * next entry catches it.
11
- *
12
- * THEY DIFFER ON WHAT AN EMPTY LIST MEANS, and that difference is the whole reason this is its own file rather
13
- * than a flag on the other one. A quick helper exists to stay OFF the frontier tier, so "work it out from what
14
- * is connected" is a good answer and quickModel's empty list resolves to a derived Auto ladder. An agent run is
15
- * a full session with a worktree, billed whole: nothing here can judge whether a job is worth the frontier tier,
16
- * so an empty list resolves to NOTHING and the caller falls back to the model the user picked for their own
17
- * chat, a choice they made, rather than one this file guessed for them. For the same reason there is no Auto
18
- * ladder underneath a list that has been emptied by disconnection: it would spend an account the user never
19
- * pointed at, on the most expensive kind of run this app starts.
20
- *
21
- * WHAT "STEPPED OVER" MEANS HERE IS NARROWER than the quick chain's, and worth being exact about. The quick
22
- * chain re-asks the next rung when a call comes back refused, because a one-shot that failed has cost nothing
23
- * and can simply be run again. An agent session cannot be replayed that way, by the time a provider refuses
24
- * mid-turn the agent may have already edited files, so this list is read ONCE, at the moment the turn is
25
- * composed, and steps over exactly one thing: an account that is not connected. A model that accepts the turn
26
- * and fails later is a failed run the user reads on the card, like any other. */
27
-
28
- /* The pins that could actually be started right now, in the user's own order.
29
- *
30
- * `sources` is the same readiness view resolveQuickModels takes, so both settings rows agree about which
31
- * accounts this sandbox can send to, a pin greyed as "Not connected" in one row and silently spent by the
32
- * other would be the worst of both.
33
- *
34
- * A pin whose provider is gone is DROPPED rather than held: it would otherwise sit at the head of the chain
35
- * failing every run, which is exactly what the list exists to prevent. It stays on SCREEN, greyed, the settings
36
- * row renders the stored list, not this one, because a setting that vanished from view would look like the app
37
- * had eaten it.
38
- *
39
- * THE WHOLE PIN SURVIVES, not the pair inside it: the entry's own effort, harness and cost knobs are what the
40
- * turn is composed from (turn-resume.ts), so a resolver that handed back a bare (provider, model) would silently
41
- * run the head of the list at the provider's defaults. Nothing here reads or judges those fields, which is the
42
- * point of carrying them whole.
43
- *
44
- * Empty out means nobody has pinned anything this sandbox can reach, and the caller's floor takes over. */
45
- export const resolveAgentRunModels = (sources: readonly QuickModelSource[], pinned: readonly AgentRunPin[]): readonly AgentRunPin[] => {
46
- const ready = new Set(sources.filter((source) => source.ready).map((source) => source.provider));
47
- // Taken verbatim, unvalidated against the catalog, the same reading resolveQuickModels gives its keys and
48
- // for the same reason: the picker offers a custom-id escape hatch for a model the static catalog has not
49
- // caught up with, and second-guessing the id here would run a different model than the settings row names.
50
- const requested = pinned.filter((pin) => ready.has(pin.provider));
51
- /* The same model twice would spend two attempts proving the same account is out. Hand-edited list, so this
52
- * is a real state rather than a defensive branch.
53
- *
54
- * THE FIRST OF A PAIR WINS, WHOLE. Two entries can now name one model and differ in their knobs (the same
55
- * Sonnet at Max and again at Low, written while reordering the list), and the one the user reads first is
56
- * the one they meant; keeping the earlier position with the later entry's effort would run a tier that
57
- * appears nowhere the pin does. */
58
- const chain: AgentRunPin[] = [];
59
- for (const pin of requested) {
60
- if (!chain.some((held) => quickModelKey(held) === quickModelKey(pin))) {
61
- chain.push(pin);
62
- }
63
- }
64
- return chain;
65
- };
@@ -1,71 +0,0 @@
1
- import { oc } from "@orpc/contract";
2
- import {
3
- AccountIdSchema,
4
- AccountListQuerySchema,
5
- AccountRenameSchema,
6
- AuthorizeChallengeSchema,
7
- OauthAccountListSchema,
8
- OauthAccountSchema,
9
- OauthExchangeSchema,
10
- } from "../schemas/provider-oauth.js";
11
- import { OkSchema } from "../schemas/shared.js";
12
-
13
- // Claude subscription OAuth, the sandbox owns the credential. `start` hands the browser the authorize URL +
14
- // PKCE material; `exchange` stores the resulting tokens as a new account and returns it; `accounts` lists the
15
- // connected accounts; `rename` renames one; `disconnect` clears the one named by id. A sandbox can hold several
16
- // Claude accounts.
17
- //
18
- // The model catalog is NOT here: it is the one thing every provider answers identically, so it lives on the
19
- // shared /providers/{provider}/models route (providers.contract.ts). What is left is what is genuinely Claude's
20
- //, an account handshake no other provider has.
21
- export const claudeContract = {
22
- start: oc
23
- .route({
24
- method: "POST",
25
- path: "/claude/oauth/start",
26
- summary: "Begin connecting a Claude account",
27
- description:
28
- "Hands back the address to send somebody to, and the proof this sandbox will need to finish the exchange. The sandbox holds the credential afterwards, not the browser.",
29
- })
30
- .output(AuthorizeChallengeSchema),
31
- exchange: oc
32
- .route({
33
- method: "POST",
34
- path: "/claude/oauth/exchange",
35
- summary: "Finish connecting a Claude account",
36
- description:
37
- "Trades the code from the sign-in for stored tokens and answers with the account it just connected. A sandbox can hold several Claude accounts side by side.",
38
- })
39
- .input(OauthExchangeSchema)
40
- .output(OauthAccountSchema),
41
- // Each account carries its plan-limit reading. `force` re-measures before answering, see
42
- // AccountListQuerySchema, and USAGE_WAIT_MS in claude.routes.ts for what an ordinary read waits.
43
- accounts: oc
44
- .route({
45
- method: "GET",
46
- path: "/claude/accounts",
47
- summary: "Connected Claude accounts",
48
- description:
49
- "Each connected account with how full its plan limits were when last measured. Ask for a fresh measurement and it takes one before answering, which is slower.",
50
- })
51
- .input(AccountListQuerySchema)
52
- .output(OauthAccountListSchema),
53
- rename: oc
54
- .route({
55
- method: "POST",
56
- path: "/claude/account/rename",
57
- summary: "Rename a Claude account",
58
- description: "Changes the label one account shows under, so several are tellable apart.",
59
- })
60
- .input(AccountRenameSchema)
61
- .output(OauthAccountSchema),
62
- disconnect: oc
63
- .route({
64
- method: "POST",
65
- path: "/claude/account/disconnect",
66
- summary: "Disconnect a Claude account",
67
- description: "Clears the stored tokens for one account. The others stay connected.",
68
- })
69
- .input(AccountIdSchema)
70
- .output(OkSchema),
71
- };
@@ -1,74 +0,0 @@
1
- import { oc } from "@orpc/contract";
2
- import {
3
- AccountIdSchema,
4
- AccountListQuerySchema,
5
- AccountRenameSchema,
6
- CursorLoginCancelSchema,
7
- CursorLoginStartSchema,
8
- OauthAccountListSchema,
9
- OauthAccountSchema,
10
- } from "../schemas/provider-oauth.js";
11
- import { OkSchema } from "../schemas/shared.js";
12
-
13
- /* Cursor subscription sign-in, the sandbox owns the credential and the platform never sees it, the same bargain
14
- * claude.contract.ts strikes. What differs is who finishes the handshake.
15
- *
16
- * Claude's flow is start → the browser gets a code → `exchange` hands it back. Cursor's PKCE verifier is
17
- * redeemable on its own, so it never leaves the daemon: `start` begins the flow, keeps the verifier in memory,
18
- * polls Cursor until the browser completes the sign-in, mints a 90-day user key and writes it as an account.
19
- * The caller opens the URL and then watches `accounts`, exactly as it does for a device login, which is why
20
- * there is no `exchange` route here and a `cancel` one instead.
21
- *
22
- * The model catalog is deliberately absent, as it is for every provider: it is the one question they all answer
23
- * identically, so it lives on the shared /providers/{provider}/models route (providers.contract.ts). */
24
- export const cursorContract = {
25
- start: oc
26
- .route({
27
- method: "POST",
28
- path: "/cursor/login/start",
29
- summary: "Begin connecting a Cursor account",
30
- description:
31
- "Hands back the page to sign in on. The sandbox finishes the handshake itself and stores the credential, so nothing has to be pasted back: watch the account list instead.",
32
- })
33
- .output(CursorLoginStartSchema),
34
- cancel: oc
35
- .route({
36
- method: "POST",
37
- path: "/cursor/login/cancel",
38
- summary: "Abandon a Cursor sign-in",
39
- description: "Stops waiting on a sign-in nobody completed. An abandoned attempt also expires on its own.",
40
- })
41
- .input(CursorLoginCancelSchema)
42
- .output(OkSchema),
43
- // Shaped like Claude's, including `force`, so the two account lists render through one component. Cursor
44
- // publishes no account-wide headroom today, so `usage` stays absent on these rows and they draw a dot rather
45
- // than a ring, which is what an unmeasured account has always meant here.
46
- accounts: oc
47
- .route({
48
- method: "GET",
49
- path: "/cursor/accounts",
50
- summary: "Connected Cursor accounts",
51
- description:
52
- "Each connected account, and whether its stored key is still good. Cursor publishes no plan-wide allowance, so these rows carry no usage reading.",
53
- })
54
- .input(AccountListQuerySchema)
55
- .output(OauthAccountListSchema),
56
- rename: oc
57
- .route({
58
- method: "POST",
59
- path: "/cursor/account/rename",
60
- summary: "Rename a Cursor account",
61
- description: "Changes the label one account shows under, so several are tellable apart.",
62
- })
63
- .input(AccountRenameSchema)
64
- .output(OauthAccountSchema),
65
- disconnect: oc
66
- .route({
67
- method: "POST",
68
- path: "/cursor/account/disconnect",
69
- summary: "Disconnect a Cursor account",
70
- description: "Clears the stored key for one account. The others stay connected.",
71
- })
72
- .input(AccountIdSchema)
73
- .output(OkSchema),
74
- };
@@ -1,41 +0,0 @@
1
- import { oc } from "@orpc/contract";
2
- import { AccountIdSchema, DeviceStartSchema, OauthAccountListSchema } from "../schemas/provider-oauth.js";
3
- import { OkSchema } from "../schemas/shared.js";
4
-
5
- // xAI Grok (via OpenCode) uses subscription OAuth, the sandbox owns the credential (OpenCode persists the
6
- // tokens and refreshes them). `start` authorizes xAI's headless device-code method and returns the verification
7
- // URL + instructions (which carry the one-time code the user enters at x.ai); OpenCode then polls to completion
8
- //, there is no paste-back, and the UI polls `accounts` until connected. `disconnect` clears the tokens.
9
- // ponytail: OpenCode holds one xAI auth per data dir, so `accounts` is 0 or 1, the list shape matches the
10
- // other providers without paying for per-account OpenCode servers yet.
11
- //
12
- // Like Claude's, this contract is now the account handshake alone, the model catalog answers on the shared
13
- // /providers/{provider}/models route (providers.contract.ts).
14
- export const grokContract = {
15
- start: oc
16
- .route({
17
- method: "POST",
18
- path: "/grok/oauth/start",
19
- summary: "Begin connecting a Grok account",
20
- description:
21
- "Hands back the page to open and the one-time code to type there. Nothing is pasted back afterwards: the sandbox waits for the sign-in to complete on its own, so poll the accounts call until it appears.",
22
- })
23
- .output(DeviceStartSchema),
24
- accounts: oc
25
- .route({
26
- method: "GET",
27
- path: "/grok/accounts",
28
- summary: "Connected Grok account",
29
- description: "What is signed in. A list for consistency with the other providers, though there is at most one.",
30
- })
31
- .output(OauthAccountListSchema),
32
- disconnect: oc
33
- .route({
34
- method: "POST",
35
- path: "/grok/account/disconnect",
36
- summary: "Disconnect the Grok account",
37
- description: "Clears the stored tokens.",
38
- })
39
- .input(AccountIdSchema)
40
- .output(OkSchema),
41
- };
@@ -1,79 +0,0 @@
1
- import { oc } from "@orpc/contract";
2
- import { z } from "zod";
3
- import { KeyProviderParamSchema } from "../schemas/agent.js";
4
- import { AccountIdSchema, AccountRenameSchema, OauthAccountListSchema, OauthAccountSchema } from "../schemas/provider-oauth.js";
5
- import { OkSchema } from "../schemas/shared.js";
6
-
7
- /* THE PROVIDERS YOU CONNECT BY PASTING A KEY (Sandbox ▸ Agent), on four routes with the provider as a PARAMETER
8
- * rather than four routes each.
9
- *
10
- * The lesson providers.contract.ts already learned about model catalogs: five identical route files differing
11
- * only in a name is a shape that costs a vertical slice through the contract, the router, the service container
12
- * and every test double every time a provider is added. Here the providers genuinely are identical — a keyed
13
- * provider is a base URL, a catalog URL and a store — so the id is a parameter and adding one is a spec row.
14
- *
15
- * WHY NOT THE OAUTH ROUTES. Claude and Cursor connect through a handshake this daemon runs: a `start` that
16
- * returns something to open and an `exchange`/poll that completes it. There is no handshake here at all — the
17
- * user already has the credential and hands it over — so `connect` takes the key and answers with the account
18
- * it became. Sharing a route family with the OAuth providers would have meant a `start` that returns nothing
19
- * and an `exchange` whose "code" is really a secret, which is a worse lie than two families.
20
- *
21
- * WHY NOT THE TRANSLATOR ROUTES. Those address subscriptions CLIProxyAPI holds; these credentials live in this
22
- * daemon's own auth tree, and a disconnect here removes a file rather than asking a proxy to forget one.
23
- *
24
- * THE PATHS MIRROR THE OAUTH PROVIDERS' (`<base>/accounts`, `<base>/account/rename`,
25
- * `<base>/account/disconnect`) with `/keys/<provider>` as the base, so the browser's account helpers reach
26
- * these rows with the same three URLs they already build for Claude, Grok and Cursor. Only `connect`
27
- * differs, because only `connect` is a different act.
28
- *
29
- * THE KEY TRAVELS IN, NEVER OUT. `connect` takes it and every answer on this contract is an OauthAccount, whose
30
- * shape has no field a credential could ride in — which is what makes "does this leak the key" a question you
31
- * answer by reading the schema instead of the handler. */
32
- export const keysContract = {
33
- accounts: oc
34
- .route({
35
- method: "GET",
36
- path: "/keys/{provider}/accounts",
37
- summary: "Connected accounts for a key-based provider",
38
- description: "Which keys are connected for this provider. The keys themselves never travel: being in this list is what connected means.",
39
- })
40
- .input(KeyProviderParamSchema)
41
- .output(OauthAccountListSchema),
42
- connect: oc
43
- .route({
44
- method: "POST",
45
- path: "/keys/{provider}/connect",
46
- summary: "Connect an API key",
47
- description:
48
- "Stores an API key for this provider and answers with the account it became. A sandbox can hold several keys for one provider side by side.",
49
- })
50
- .input(
51
- KeyProviderParamSchema.extend({
52
- apiKey: z.string().min(1).describe("The key, as the provider issued it. Stored in this sandbox and never sent back out."),
53
- label: z
54
- .string()
55
- .max(80)
56
- .optional()
57
- .describe("What to call it. Blank falls back to the provider's own name, which is all a pasted key can say about itself."),
58
- }),
59
- )
60
- .output(OauthAccountSchema),
61
- rename: oc
62
- .route({
63
- method: "POST",
64
- path: "/keys/{provider}/account/rename",
65
- summary: "Rename one connected key",
66
- description: "Nothing in a pasted key says whose it is, so naming it is the only way to tell two of them apart.",
67
- })
68
- .input(KeyProviderParamSchema.extend(AccountRenameSchema.shape))
69
- .output(OkSchema),
70
- disconnect: oc
71
- .route({
72
- method: "POST",
73
- path: "/keys/{provider}/account/disconnect",
74
- summary: "Disconnect one key",
75
- description: "Removes a single stored key. Any others under the same provider stay connected.",
76
- })
77
- .input(KeyProviderParamSchema.extend(AccountIdSchema.shape))
78
- .output(OkSchema),
79
- };