agl 1.0.17 → 21.0.2

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 (277) hide show
  1. package/.smartconfig.json +55 -0
  2. package/changelog.md +1026 -0
  3. package/cli.js +14 -0
  4. package/dist_serve/bundle.js +44778 -0
  5. package/dist_serve/index.html +13 -0
  6. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  7. package/dist_ts/00_commitinfo_data.js +9 -0
  8. package/dist_ts/classes.authmodels.d.ts +126 -0
  9. package/dist_ts/classes.authmodels.js +1422 -0
  10. package/dist_ts/classes.authstore.d.ts +160 -0
  11. package/dist_ts/classes.authstore.js +2483 -0
  12. package/dist_ts/classes.browserresourcehost.d.ts +85 -0
  13. package/dist_ts/classes.browserresourcehost.js +912 -0
  14. package/dist_ts/classes.cli.d.ts +21 -0
  15. package/dist_ts/classes.cli.js +1082 -0
  16. package/dist_ts/classes.config.d.ts +23 -0
  17. package/dist_ts/classes.config.js +267 -0
  18. package/dist_ts/classes.controller.d.ts +523 -0
  19. package/dist_ts/classes.controller.js +10611 -0
  20. package/dist_ts/classes.controllermanagement.d.ts +32 -0
  21. package/dist_ts/classes.controllermanagement.js +222 -0
  22. package/dist_ts/classes.draftmanager.d.ts +30 -0
  23. package/dist_ts/classes.draftmanager.js +179 -0
  24. package/dist_ts/classes.embeddeddb.d.ts +30 -0
  25. package/dist_ts/classes.embeddeddb.js +159 -0
  26. package/dist_ts/classes.flexframedtransport.d.ts +56 -0
  27. package/dist_ts/classes.flexframedtransport.js +275 -0
  28. package/dist_ts/classes.flexmodels.private.d.ts +231 -0
  29. package/dist_ts/classes.flexmodels.private.js +985 -0
  30. package/dist_ts/classes.flexmodels.public.d.ts +207 -0
  31. package/dist_ts/classes.flexmodels.public.js +885 -0
  32. package/dist_ts/classes.flexprojectionreader.d.ts +89 -0
  33. package/dist_ts/classes.flexprojectionreader.js +580 -0
  34. package/dist_ts/classes.flexservice.d.ts +196 -0
  35. package/dist_ts/classes.flexservice.js +3704 -0
  36. package/dist_ts/classes.flexstore.d.ts +174 -0
  37. package/dist_ts/classes.flexstore.js +3400 -0
  38. package/dist_ts/classes.flexsupervisor.d.ts +105 -0
  39. package/dist_ts/classes.flexsupervisor.js +1191 -0
  40. package/dist_ts/classes.gitreversion.d.ts +431 -0
  41. package/dist_ts/classes.gitreversion.js +10592 -0
  42. package/dist_ts/classes.harnessoperationmanager.d.ts +113 -0
  43. package/dist_ts/classes.harnessoperationmanager.js +602 -0
  44. package/dist_ts/classes.opencodeclient.d.ts +146 -0
  45. package/dist_ts/classes.opencodeclient.js +3003 -0
  46. package/dist_ts/classes.opencodesupervisor.d.ts +60 -0
  47. package/dist_ts/classes.opencodesupervisor.js +1003 -0
  48. package/dist_ts/classes.passkeymanager.d.ts +34 -0
  49. package/dist_ts/classes.passkeymanager.js +456 -0
  50. package/dist_ts/classes.processinspection.d.ts +50 -0
  51. package/dist_ts/classes.processinspection.js +484 -0
  52. package/dist_ts/classes.resourcecoordinator.d.ts +64 -0
  53. package/dist_ts/classes.resourcecoordinator.js +294 -0
  54. package/dist_ts/classes.sessionintelligencereader.d.ts +13 -0
  55. package/dist_ts/classes.sessionintelligencereader.js +40 -0
  56. package/dist_ts/classes.systemmetricssampler.d.ts +53 -0
  57. package/dist_ts/classes.systemmetricssampler.js +168 -0
  58. package/dist_ts/classes.terminalmanager.d.ts +95 -0
  59. package/dist_ts/classes.terminalmanager.js +981 -0
  60. package/dist_ts/classes.terminalresourcehost.d.ts +20 -0
  61. package/dist_ts/classes.terminalresourcehost.js +84 -0
  62. package/dist_ts/classes.upgradecoordinator.d.ts +244 -0
  63. package/dist_ts/classes.upgradecoordinator.js +2155 -0
  64. package/dist_ts/classes.upgradetransaction.d.ts +92 -0
  65. package/dist_ts/classes.upgradetransaction.js +2125 -0
  66. package/dist_ts/classes.uploadmanager.d.ts +24 -0
  67. package/dist_ts/classes.uploadmanager.js +210 -0
  68. package/dist_ts/classes.validation.d.ts +91 -0
  69. package/dist_ts/classes.validation.js +1087 -0
  70. package/dist_ts/flexharness.child.d.ts +1 -0
  71. package/dist_ts/flexharness.child.js +28 -0
  72. package/dist_ts/functions.canonicaldirectory.d.ts +21 -0
  73. package/dist_ts/functions.canonicaldirectory.js +144 -0
  74. package/dist_ts/functions.controllerdataroot.d.ts +15 -0
  75. package/dist_ts/functions.controllerdataroot.js +76 -0
  76. package/dist_ts/functions.embeddeddb.d.ts +2 -0
  77. package/dist_ts/functions.embeddeddb.js +21 -0
  78. package/dist_ts/functions.flexcredentialstore.d.ts +16 -0
  79. package/dist_ts/functions.flexcredentialstore.js +93 -0
  80. package/dist_ts/functions.opencodeauth.d.ts +3 -0
  81. package/dist_ts/functions.opencodeauth.js +31 -0
  82. package/dist_ts/functions.runtimeenvironment.d.ts +2 -0
  83. package/dist_ts/functions.runtimeenvironment.js +79 -0
  84. package/dist_ts/functions.upgradefollower.d.ts +3 -0
  85. package/dist_ts/functions.upgradefollower.js +95 -0
  86. package/dist_ts/index.d.ts +24 -0
  87. package/dist_ts/index.js +25 -0
  88. package/dist_ts/interfaces.auth.d.ts +181 -0
  89. package/dist_ts/interfaces.auth.js +82 -0
  90. package/dist_ts/interfaces.config.d.ts +34 -0
  91. package/dist_ts/interfaces.config.js +2 -0
  92. package/dist_ts/interfaces.flexipc.d.ts +939 -0
  93. package/dist_ts/interfaces.flexipc.js +1492 -0
  94. package/dist_ts/interfaces.gitreversion.d.ts +246 -0
  95. package/dist_ts/interfaces.gitreversion.js +2 -0
  96. package/dist_ts/interfaces.lifecycle.d.ts +37 -0
  97. package/dist_ts/interfaces.lifecycle.js +2 -0
  98. package/dist_ts/interfaces.opencode.d.ts +101 -0
  99. package/dist_ts/interfaces.opencode.js +3 -0
  100. package/dist_ts/interfaces.projects.d.ts +156 -0
  101. package/dist_ts/interfaces.projects.js +5 -0
  102. package/dist_ts/plugins.d.ts +36 -0
  103. package/dist_ts/plugins.js +40 -0
  104. package/dist_ts_interfaces/index.d.ts +2 -0
  105. package/dist_ts_interfaces/index.js +3 -0
  106. package/dist_ts_interfaces/interfaces.d.ts +747 -0
  107. package/dist_ts_interfaces/interfaces.js +47 -0
  108. package/dist_ts_interfaces/plugins.d.ts +4 -0
  109. package/dist_ts_interfaces/plugins.js +3 -0
  110. package/dist_ts_interfaces/requests.d.ts +806 -0
  111. package/dist_ts_interfaces/requests.js +2 -0
  112. package/dist_ts_migration/classes.documentmigrationrunner.d.ts +8 -0
  113. package/dist_ts_migration/classes.documentmigrationrunner.js +24 -0
  114. package/dist_ts_migration/classes.flexmigrationrunner.d.ts +11 -0
  115. package/dist_ts_migration/classes.flexmigrationrunner.js +25 -0
  116. package/dist_ts_migration/classes.flexpostharnessmigrationrunner.d.ts +10 -0
  117. package/dist_ts_migration/classes.flexpostharnessmigrationrunner.js +15 -0
  118. package/dist_ts_migration/classes.migrationrunner.d.ts +8 -0
  119. package/dist_ts_migration/classes.migrationrunner.js +34 -0
  120. package/dist_ts_migration/index.d.ts +12 -0
  121. package/dist_ts_migration/index.js +13 -0
  122. package/dist_ts_migration/v10_flexharnessv3migration.d.ts +11 -0
  123. package/dist_ts_migration/v10_flexharnessv3migration.js +23 -0
  124. package/dist_ts_migration/v13_flexprojectionorder.d.ts +11 -0
  125. package/dist_ts_migration/v13_flexprojectionorder.js +23 -0
  126. package/dist_ts_migration/v16_flexprovidercredentials.d.ts +22 -0
  127. package/dist_ts_migration/v16_flexprovidercredentials.js +149 -0
  128. package/dist_ts_migration/v18_resourcelayout.d.ts +22 -0
  129. package/dist_ts_migration/v18_resourcelayout.js +23 -0
  130. package/dist_ts_migration/v18_resourcelayoutmigration.d.ts +6 -0
  131. package/dist_ts_migration/v18_resourcelayoutmigration.js +61 -0
  132. package/dist_ts_migration/v19_flexprojectmanagement.d.ts +11 -0
  133. package/dist_ts_migration/v19_flexprojectmanagement.js +21 -0
  134. package/dist_ts_migration/v1_embeddeddatabaselocation.d.ts +28 -0
  135. package/dist_ts_migration/v1_embeddeddatabaselocation.js +415 -0
  136. package/dist_ts_migration/v22_projectfilesystemidentitymigration.d.ts +7 -0
  137. package/dist_ts_migration/v22_projectfilesystemidentitymigration.js +105 -0
  138. package/dist_ts_migration/v2_controllerdataroot.d.ts +130 -0
  139. package/dist_ts_migration/v2_controllerdataroot.js +1887 -0
  140. package/dist_ts_migration/v6_protocoldocumentmigration.d.ts +7 -0
  141. package/dist_ts_migration/v6_protocoldocumentmigration.js +100 -0
  142. package/dist_ts_migration/v6_protocoldocuments.d.ts +7 -0
  143. package/dist_ts_migration/v6_protocoldocuments.js +138 -0
  144. package/html/index.html +13 -0
  145. package/{LICENSE → license.md} +2 -2
  146. package/package.json +86 -48
  147. package/readme.md +480 -0
  148. package/readme.plan.md +188 -0
  149. package/ts/00_commitinfo_data.ts +8 -0
  150. package/ts/classes.authmodels.ts +1245 -0
  151. package/ts/classes.authstore.ts +3274 -0
  152. package/ts/classes.browserresourcehost.ts +1205 -0
  153. package/ts/classes.cli.ts +1254 -0
  154. package/ts/classes.config.ts +351 -0
  155. package/ts/classes.controller.ts +14426 -0
  156. package/ts/classes.controllermanagement.ts +330 -0
  157. package/ts/classes.draftmanager.ts +232 -0
  158. package/ts/classes.embeddeddb.ts +166 -0
  159. package/ts/classes.flexframedtransport.ts +283 -0
  160. package/ts/classes.flexmodels.private.ts +823 -0
  161. package/ts/classes.flexmodels.public.ts +732 -0
  162. package/ts/classes.flexprojectionreader.ts +816 -0
  163. package/ts/classes.flexservice.ts +4302 -0
  164. package/ts/classes.flexstore.ts +4565 -0
  165. package/ts/classes.flexsupervisor.ts +1453 -0
  166. package/ts/classes.gitreversion.ts +13353 -0
  167. package/ts/classes.harnessoperationmanager.ts +870 -0
  168. package/ts/classes.opencodeclient.ts +4110 -0
  169. package/ts/classes.opencodesupervisor.ts +1280 -0
  170. package/ts/classes.passkeymanager.ts +570 -0
  171. package/ts/classes.processinspection.ts +623 -0
  172. package/ts/classes.resourcecoordinator.ts +436 -0
  173. package/ts/classes.sessionintelligencereader.ts +52 -0
  174. package/ts/classes.systemmetricssampler.ts +215 -0
  175. package/ts/classes.terminalmanager.ts +1207 -0
  176. package/ts/classes.terminalresourcehost.ts +122 -0
  177. package/ts/classes.upgradecoordinator.ts +2675 -0
  178. package/ts/classes.upgradetransaction.ts +2900 -0
  179. package/ts/classes.uploadmanager.ts +240 -0
  180. package/ts/classes.validation.ts +1409 -0
  181. package/ts/flexharness.child.ts +35 -0
  182. package/ts/functions.canonicaldirectory.ts +213 -0
  183. package/ts/functions.controllerdataroot.ts +107 -0
  184. package/ts/functions.embeddeddb.ts +22 -0
  185. package/ts/functions.flexcredentialstore.ts +128 -0
  186. package/ts/functions.opencodeauth.ts +34 -0
  187. package/ts/functions.runtimeenvironment.ts +90 -0
  188. package/ts/functions.upgradefollower.ts +115 -0
  189. package/ts/index.ts +36 -6
  190. package/ts/interfaces.auth.ts +316 -0
  191. package/ts/interfaces.config.ts +37 -0
  192. package/ts/interfaces.flexipc.ts +2586 -0
  193. package/ts/interfaces.gitreversion.ts +336 -0
  194. package/ts/interfaces.lifecycle.ts +46 -0
  195. package/ts/interfaces.opencode.ts +153 -0
  196. package/ts/interfaces.projects.ts +182 -0
  197. package/ts/plugins.ts +64 -0
  198. package/ts_interfaces/index.ts +2 -0
  199. package/ts_interfaces/interfaces.ts +827 -0
  200. package/ts_interfaces/plugins.ts +15 -0
  201. package/ts_interfaces/requests.ts +1186 -0
  202. package/ts_migration/classes.documentmigrationrunner.ts +32 -0
  203. package/ts_migration/classes.flexmigrationrunner.ts +33 -0
  204. package/ts_migration/classes.flexpostharnessmigrationrunner.ts +22 -0
  205. package/ts_migration/classes.migrationrunner.ts +40 -0
  206. package/ts_migration/index.ts +12 -0
  207. package/ts_migration/v10_flexharnessv3migration.ts +35 -0
  208. package/ts_migration/v13_flexprojectionorder.ts +33 -0
  209. package/ts_migration/v16_flexprovidercredentials.ts +196 -0
  210. package/ts_migration/v18_resourcelayout.ts +55 -0
  211. package/ts_migration/v18_resourcelayoutmigration.ts +68 -0
  212. package/ts_migration/v19_flexprojectmanagement.ts +28 -0
  213. package/ts_migration/v1_embeddeddatabaselocation.ts +560 -0
  214. package/ts_migration/v22_projectfilesystemidentitymigration.ts +121 -0
  215. package/ts_migration/v2_controllerdataroot.ts +2349 -0
  216. package/ts_migration/v6_protocoldocumentmigration.ts +144 -0
  217. package/ts_migration/v6_protocoldocuments.ts +198 -0
  218. package/ts_web/00_commitinfo_data.ts +8 -0
  219. package/ts_web/classes.controllersocketclient.ts +517 -0
  220. package/ts_web/classes.sessiondraftsync.ts +390 -0
  221. package/ts_web/elements.harnesscontrollerapp.ts +11494 -0
  222. package/ts_web/index.ts +2 -0
  223. package/ts_web/plugins.ts +31 -0
  224. package/.gitlab-ci.yml +0 -71
  225. package/.npmignore +0 -5
  226. package/README.md +0 -40
  227. package/dist/cli.js +0 -2
  228. package/dist/index.d.ts +0 -0
  229. package/dist/index.js +0 -9
  230. package/dist/mod_bundle/index.d.ts +0 -1
  231. package/dist/mod_bundle/index.js +0 -18
  232. package/dist/mod_bundle/mod.plugins.d.ts +0 -4
  233. package/dist/mod_bundle/mod.plugins.js +0 -11
  234. package/dist/mod_install/index.d.ts +0 -4
  235. package/dist/mod_install/index.js +0 -21
  236. package/dist/mod_install/mod.plugins.d.ts +0 -1
  237. package/dist/mod_install/mod.plugins.js +0 -7
  238. package/dist/mod_standard/index.d.ts +0 -4
  239. package/dist/mod_standard/index.js +0 -21
  240. package/dist/mod_standard/mod.browser.d.ts +0 -6
  241. package/dist/mod_standard/mod.browser.js +0 -14
  242. package/dist/mod_standard/mod.clean.d.ts +0 -4
  243. package/dist/mod_standard/mod.clean.js +0 -12
  244. package/dist/mod_standard/mod.compile.d.ts +0 -4
  245. package/dist/mod_standard/mod.compile.js +0 -21
  246. package/dist/mod_standard/mod.plugins.d.ts +0 -8
  247. package/dist/mod_standard/mod.plugins.js +0 -19
  248. package/dist/mod_standard/mod.test.d.ts +0 -4
  249. package/dist/mod_standard/mod.test.js +0 -12
  250. package/dist/plr.cli.d.ts +0 -3
  251. package/dist/plr.cli.js +0 -57
  252. package/dist/plr.config.d.ts +0 -11
  253. package/dist/plr.config.js +0 -26
  254. package/dist/plr.mods.d.ts +0 -17
  255. package/dist/plr.mods.js +0 -17
  256. package/dist/plr.paths.d.ts +0 -19
  257. package/dist/plr.paths.js +0 -26
  258. package/dist/plr.plugins.d.ts +0 -8
  259. package/dist/plr.plugins.js +0 -16
  260. package/npmextra.json +0 -10
  261. package/ts/mod_bundle/index.ts +0 -20
  262. package/ts/mod_bundle/mod.plugins.ts +0 -9
  263. package/ts/mod_install/index.ts +0 -20
  264. package/ts/mod_install/mod.plugins.ts +0 -1
  265. package/ts/mod_standard/index.ts +0 -23
  266. package/ts/mod_standard/mod.browser.ts +0 -17
  267. package/ts/mod_standard/mod.clean.ts +0 -13
  268. package/ts/mod_standard/mod.compile.ts +0 -23
  269. package/ts/mod_standard/mod.plugins.ts +0 -17
  270. package/ts/mod_standard/mod.test.ts +0 -13
  271. package/ts/plr.cli.ts +0 -60
  272. package/ts/plr.config.ts +0 -36
  273. package/ts/plr.mods.ts +0 -25
  274. package/ts/plr.paths.ts +0 -29
  275. package/ts/plr.plugins.ts +0 -17
  276. package/tslint.json +0 -3
  277. package/yarn.lock +0 -3464
@@ -0,0 +1,2586 @@
1
+ import * as plugins from './plugins.js';
2
+ import {
3
+ controllerFilesystemIdentityMaximumComponents,
4
+ controllerProjectLimit,
5
+ type IControllerProjectFilesystemIdentity,
6
+ } from './interfaces.projects.js';
7
+
8
+ type TFlexCreateSessionOptions = plugins.flexharness.IFlexCreateSessionOptions;
9
+ type TFlexMessage = plugins.flexharness.IFlexMessage;
10
+ type TFlexMessagePage = plugins.flexharness.IFlexMessagePage;
11
+ type TFlexPermissionRequest = plugins.flexharness.IFlexPermissionRequest;
12
+ export type TFlexPromptQueueEntry = plugins.flexharness.IFlexPromptQueueEntry;
13
+ type TFlexPromptOptions = plugins.flexharness.IFlexPromptOptions;
14
+ type TFlexSession = plugins.flexharness.IFlexSession;
15
+ type TFlexToolMessagePart = plugins.flexharness.IFlexToolMessagePart;
16
+ type TFlexReasoningMessagePart = Extract<plugins.flexharness.TFlexMessagePart, { type: 'reasoning' }>;
17
+ type TFlexTextMessagePart = Extract<plugins.flexharness.TFlexMessagePart, { type: 'text' }>;
18
+ type TFlexUpdateSessionOptions = plugins.flexharness.IFlexUpdateSessionOptions;
19
+ type TFlexPermissionDecision = plugins.flexharness.TFlexPermissionDecision;
20
+ type TFlexPrompt = plugins.flexharness.TFlexPrompt;
21
+ type TFlexProjectManagementRecord = plugins.flexharness.TFlexProjectManagementRecord;
22
+ type TFlexProjectManagementSnapshot = plugins.flexharness.IFlexProjectManagementSnapshot;
23
+ type TFlexProjectManagementTombstone = plugins.flexharness.IFlexProjectManagementTombstone;
24
+ type TFlexProjectManagementWriteContext = plugins.flexharness.IFlexProjectManagementWriteContext;
25
+ type TFlexJsonValue = plugins.flexharness.TJsonValue;
26
+ export type TFlexSlashCommandDescriptor = plugins.flexharness.IFlexSlashCommandDescriptor;
27
+ export type TFlexSessionReversionInfo = plugins.flexharness.IFlexSessionReversionInfo;
28
+ export type TFlexTurnReversionFinalizedOutcome =
29
+ plugins.flexharness.TFlexTurnReversionFinalizedOutcome;
30
+ export type TFlexTurnReversionCaptureInspectionV2 =
31
+ plugins.flexharness.TFlexTurnReversionCaptureInspectionV2;
32
+ export type TFlexTurnReversionApplyInspection =
33
+ plugins.flexharness.IFlexTurnReversionApplyInspection;
34
+
35
+ export const flexIpcProtocolVersion = 14 as const;
36
+ export const flexIpcMaximumFrameBytes = 2 * 1024 * 1024;
37
+ export const flexIpcMaximumPendingRequests = 64;
38
+ export const flexIpcMaximumBrowserChannels = 128;
39
+ export const flexIpcMaximumBrowserFrameBytes = 256 * 1024 + 4;
40
+ export const flexIpcMaximumPendingBrowserFrames = 128;
41
+ export const flexIpcMaximumPendingBrowserFrameBytes = 8 * 1024 * 1024;
42
+ export const flexIpcMaximumChildJobs = 64;
43
+ export const flexIpcMaximumPromptQueueEntries = 128;
44
+ export const flexIpcMaximumQueuedSessionChanges = 256;
45
+ export const flexIpcMaximumQueuedSessionChangeBytes = 8 * 1024 * 1024;
46
+ export const flexIpcControlTimeoutMs = 35_000;
47
+ export const flexIpcStartupTimeoutMs = 180_000;
48
+ export const flexIpcDisposeTimeoutMs = 10_000;
49
+ export const flexIpcSlashTimeoutMs = 30 * 60 * 1000;
50
+ export const flexIpcMaximumHostTimeoutMs = 5 * 60 * 1000;
51
+ export const flexIpcCancellationGraceTimeoutMs = 2_000;
52
+
53
+ export interface IFlexProjectRecord {
54
+ projectId: string;
55
+ directory: string;
56
+ directoryIdentity?: IControllerProjectFilesystemIdentity;
57
+ }
58
+
59
+ export interface IFlexIdentityBoundProjectRecord extends IFlexProjectRecord {
60
+ directoryIdentity: IControllerProjectFilesystemIdentity;
61
+ }
62
+
63
+ export interface IFlexDatabaseDescriptor {
64
+ mongoDbUrl: string;
65
+ mongoDbName: string;
66
+ }
67
+
68
+ export interface IFlexServiceInit {
69
+ controllerId: string;
70
+ database: IFlexDatabaseDescriptor;
71
+ flexCredentialDirectory: string;
72
+ projects: IFlexProjectRecord[];
73
+ }
74
+
75
+ export interface IFlexBrowserResourceDescriptor {
76
+ resourceId: string;
77
+ attachmentRevision: number;
78
+ }
79
+
80
+ export interface IFlexBrowserChannelBinding {
81
+ projectId: string;
82
+ browserResourceId: string;
83
+ attachmentAuthorityId: string;
84
+ attachmentRevision: number;
85
+ actorId: string;
86
+ peerId: string;
87
+ role: 'agent';
88
+ source: 'flex';
89
+ scopeId: string;
90
+ channelId: string;
91
+ runId: string;
92
+ sessionId: { harnessId: 'flex'; nativeId: string };
93
+ }
94
+
95
+ export interface IFlexIntelligenceSessionId {
96
+ harnessId: 'opencode' | 'flex';
97
+ nativeId: string;
98
+ }
99
+
100
+ export interface IFlexIntelligenceChatSummary {
101
+ sessionId: IFlexIntelligenceSessionId;
102
+ title: string;
103
+ updatedAt: string;
104
+ isActive?: boolean;
105
+ }
106
+
107
+ export interface IFlexIntelligenceChatMessage {
108
+ role: string;
109
+ text: string;
110
+ timestamp?: string;
111
+ }
112
+
113
+ export interface IFlexIntelligenceChatTranscript {
114
+ sessionId: IFlexIntelligenceSessionId;
115
+ title: string;
116
+ messages: IFlexIntelligenceChatMessage[];
117
+ totalMessages?: number;
118
+ }
119
+
120
+ export interface IFlexIntelligenceResult {
121
+ answer: string;
122
+ scratchpad: string;
123
+ model: string;
124
+ }
125
+
126
+ export interface IFlexTurnReversionBaseRequest {
127
+ scopeId: string;
128
+ storageKey: string;
129
+ sessionId: string;
130
+ runId: string;
131
+ captureId: string;
132
+ }
133
+
134
+ export interface IFlexTurnReversionApplyRequest extends IFlexTurnReversionBaseRequest {
135
+ reference: TFlexJsonValue;
136
+ operationId: string;
137
+ direction: 'undo' | 'redo';
138
+ }
139
+
140
+ export interface IFlexTurnReversionReleaseRequest extends IFlexTurnReversionBaseRequest {
141
+ reference: TFlexJsonValue;
142
+ }
143
+
144
+ export interface IFlexGitWorktreeDescriptor {
145
+ worktreeId: string;
146
+ projectId: string;
147
+ sessionId: string;
148
+ path: string;
149
+ repositoryId: string;
150
+ createdAt: string;
151
+ }
152
+
153
+ export type TFlexIntelligenceJobStatus = 'running' | 'completed' | 'failed' | 'cancelled';
154
+
155
+ export interface IFlexHostRequestMap {
156
+ 'reversion.prepare': {
157
+ request: IFlexTurnReversionBaseRequest;
158
+ response: { prepared: true };
159
+ };
160
+ 'reversion.inspect-capture': {
161
+ request: IFlexTurnReversionBaseRequest;
162
+ response: TFlexTurnReversionCaptureInspectionV2;
163
+ };
164
+ 'reversion.finalize': {
165
+ request: IFlexTurnReversionBaseRequest;
166
+ response: TFlexTurnReversionFinalizedOutcome;
167
+ };
168
+ 'reversion.inspect-apply': {
169
+ request: IFlexTurnReversionApplyRequest;
170
+ response: TFlexTurnReversionApplyInspection;
171
+ };
172
+ 'reversion.apply': {
173
+ request: IFlexTurnReversionApplyRequest;
174
+ response: { applied: true };
175
+ };
176
+ 'reversion.release': {
177
+ request: IFlexTurnReversionReleaseRequest;
178
+ response: { released: true };
179
+ };
180
+ 'git.worktree.create': {
181
+ request: { scopeId: string; sessionId: string };
182
+ response: { worktree: IFlexGitWorktreeDescriptor };
183
+ };
184
+ 'git.worktree.list': {
185
+ request: { scopeId: string; sessionId: string };
186
+ response: { worktrees: IFlexGitWorktreeDescriptor[] };
187
+ };
188
+ 'git.worktree.remove': {
189
+ request: {
190
+ scopeId: string;
191
+ sessionId: string;
192
+ worktreeId: string;
193
+ compensate?: true;
194
+ };
195
+ response: { removed: true };
196
+ };
197
+ 'crossharness.chats.list': {
198
+ request: { capabilityToken: string; limit: number };
199
+ response: { chats: IFlexIntelligenceChatSummary[] };
200
+ };
201
+ 'crossharness.chat.read': {
202
+ request: {
203
+ capabilityToken: string;
204
+ sessionId: IFlexIntelligenceSessionId;
205
+ limit: number;
206
+ maxChars: number;
207
+ };
208
+ response: { transcript: IFlexIntelligenceChatTranscript };
209
+ };
210
+ 'project-management.load': {
211
+ request: {
212
+ storageKey: string;
213
+ sessionId: string;
214
+ sessionGenerationId: string;
215
+ sessionGenerationSequence: number;
216
+ };
217
+ response: { record: TFlexProjectManagementRecord | null };
218
+ };
219
+ 'project-management.save': {
220
+ request: {
221
+ storageKey: string;
222
+ sessionId: string;
223
+ snapshot: TFlexProjectManagementSnapshot;
224
+ expectedRevision: number;
225
+ writeContext: TFlexProjectManagementWriteContext;
226
+ };
227
+ response: TFlexProjectManagementCommitResult;
228
+ };
229
+ 'project-management.tombstone': {
230
+ request: {
231
+ storageKey: string;
232
+ sessionId: string;
233
+ tombstone: TFlexProjectManagementTombstone;
234
+ expectedRevision: number;
235
+ };
236
+ response: TFlexProjectManagementCommitResult;
237
+ };
238
+ 'project-management.purge': {
239
+ request: { storageKey: string };
240
+ response: { purged: true };
241
+ };
242
+ 'browser.resources.resolve': {
243
+ request: { scopeId: string; sessionId: string; runId: string };
244
+ response: { resources: IFlexBrowserResourceDescriptor[] };
245
+ };
246
+ 'browser.channel.open': {
247
+ request: {
248
+ scopeId: string;
249
+ sessionId: string;
250
+ runId: string;
251
+ resourceId: string;
252
+ attachmentRevision: number;
253
+ channelId: string;
254
+ };
255
+ response: { binding: IFlexBrowserChannelBinding; capabilityToken: string };
256
+ };
257
+ 'browser.channel.close': {
258
+ request: { channelId: string };
259
+ response: { closed: true };
260
+ };
261
+ }
262
+
263
+ export type TFlexProjectManagementCommitResult =
264
+ | { committed: true }
265
+ | { committed: false; actualRevision: number };
266
+
267
+ export type TFlexHostRequestMethod = keyof IFlexHostRequestMap;
268
+ export type TFlexHostRequest<TMethod extends TFlexHostRequestMethod> =
269
+ IFlexHostRequestMap[TMethod]['request'];
270
+ export type TFlexHostResponse<TMethod extends TFlexHostRequestMethod> =
271
+ IFlexHostRequestMap[TMethod]['response'];
272
+
273
+ export type TFlexServiceState =
274
+ | 'starting'
275
+ | 'ready'
276
+ | 'unsupported'
277
+ | 'failed'
278
+ | 'stopping'
279
+ | 'stopped';
280
+
281
+ export type TFlexServiceStatusCode =
282
+ | 'UNSUPPORTED_RUNTIME'
283
+ | 'INITIALIZATION_FAILED'
284
+ | 'SERVICE_STOPPED';
285
+
286
+ export interface IFlexServiceStatus {
287
+ state: TFlexServiceState;
288
+ ready: boolean;
289
+ code?: TFlexServiceStatusCode;
290
+ startedAt?: string;
291
+ }
292
+
293
+ export type TFlexPublicConnectionState =
294
+ | 'pending'
295
+ | 'active'
296
+ | 'deleting'
297
+ | 'reauthRequired';
298
+
299
+ export interface IFlexPublicAccountSummary {
300
+ providerId: string;
301
+ authKind: 'chatgptOAuth';
302
+ accountId?: string;
303
+ email?: string;
304
+ plan?: string;
305
+ }
306
+
307
+ export interface IFlexPublicProviderConnection {
308
+ loginId: string;
309
+ providerId: 'openai';
310
+ status: TFlexPublicConnectionState;
311
+ account?: IFlexPublicAccountSummary;
312
+ }
313
+
314
+ /** Private child-to-controller projection. It is never exposed by controller RPC. */
315
+ export interface IFlexOpenCodeOAuthAuth {
316
+ type: 'oauth';
317
+ access: string;
318
+ refresh: string;
319
+ expires: number;
320
+ accountId?: string;
321
+ }
322
+
323
+ export interface IFlexPublicProviderModel {
324
+ providerId: 'openai';
325
+ modelId: string;
326
+ displayName: string;
327
+ description: string;
328
+ hidden: boolean;
329
+ reasoningEfforts: Array<{ effort: string; description: string }>;
330
+ defaultReasoningEffort: string;
331
+ inputModalities: Array<'text' | 'image' | 'audio'>;
332
+ supportsPersonality: boolean;
333
+ serviceTiers: Array<{ id: string; name: string; description: string }>;
334
+ defaultServiceTier?: string;
335
+ isDefault: boolean;
336
+ }
337
+
338
+ export interface IFlexPublicProviderRateLimitWindow {
339
+ usedPercent: number;
340
+ limitWindowSeconds: number;
341
+ resetAfterSeconds: number;
342
+ resetsAt: number;
343
+ }
344
+
345
+ export interface IFlexPublicProviderRateLimitDetails {
346
+ allowed: boolean;
347
+ limitReached: boolean;
348
+ primaryWindow?: IFlexPublicProviderRateLimitWindow;
349
+ secondaryWindow?: IFlexPublicProviderRateLimitWindow;
350
+ }
351
+
352
+ export interface IFlexPublicProviderAdditionalRateLimit {
353
+ limitName: string;
354
+ meteredFeature: string;
355
+ rateLimit?: IFlexPublicProviderRateLimitDetails;
356
+ }
357
+
358
+ export interface IFlexPublicProviderAccountRateLimits {
359
+ providerId: 'openai';
360
+ plan: string;
361
+ observedAt: string;
362
+ rateLimit?: IFlexPublicProviderRateLimitDetails;
363
+ additionalRateLimits: IFlexPublicProviderAdditionalRateLimit[];
364
+ rateLimitReachedType?: string;
365
+ rateLimitResetCreditsAvailableCount?: number;
366
+ }
367
+
368
+ export interface IFlexModelChoice {
369
+ providerConnectionId: string;
370
+ modelId: string;
371
+ variant?: string;
372
+ }
373
+
374
+ export interface IFlexJobAdmission {
375
+ jobId: string;
376
+ }
377
+
378
+ export interface IFlexSessionListResult {
379
+ sessions: TFlexSession[];
380
+ total: number;
381
+ truncated: boolean;
382
+ }
383
+
384
+ export type TFlexSlashExecutionResult =
385
+ | plugins.flexharness.IFlexSlashCommandNotCommandResult
386
+ | plugins.flexharness.IFlexSlashCommandMalformedResult
387
+ | plugins.flexharness.IFlexSlashCommandUnknownResult
388
+ | plugins.flexharness.IFlexSlashCommandOperationResult
389
+ | plugins.flexharness.IFlexSlashCommandHandlerResult
390
+ | {
391
+ type: 'prompt-admission';
392
+ name: string;
393
+ admission: { queueId: string; runId: string };
394
+ };
395
+
396
+ export interface IFlexRequestMap {
397
+ 'service.status': {
398
+ request: Record<string, never>;
399
+ response: IFlexServiceStatus;
400
+ };
401
+ 'project.replaceAll': {
402
+ request: { projects: IFlexProjectRecord[] };
403
+ response: { replaced: true; retired: number };
404
+ };
405
+ 'project.register': {
406
+ request: { project: IFlexIdentityBoundProjectRecord };
407
+ response: { registered: true };
408
+ };
409
+ 'project.remove': {
410
+ request: { project: IFlexIdentityBoundProjectRecord };
411
+ response: { removed: true };
412
+ };
413
+ 'intelligence.start': {
414
+ request: {
415
+ scopeId: string;
416
+ sourceSessionId: IFlexIntelligenceSessionId;
417
+ question: string;
418
+ scratchpad: string;
419
+ model: IFlexModelChoice;
420
+ capabilityToken: string;
421
+ };
422
+ response: { jobId: string; workerSessionId: string };
423
+ };
424
+ 'intelligence.get': {
425
+ request: { scopeId: string; jobId: string; capabilityToken: string };
426
+ response: {
427
+ status: TFlexIntelligenceJobStatus;
428
+ workerSessionId: string;
429
+ result?: IFlexIntelligenceResult;
430
+ error?: string;
431
+ };
432
+ };
433
+ 'intelligence.cancel': {
434
+ request: { scopeId: string; capabilityToken: string };
435
+ response: { cancelled: boolean };
436
+ };
437
+ 'session.list': {
438
+ request: { scopeId: string; offset?: number; limit?: number };
439
+ response: IFlexSessionListResult;
440
+ };
441
+ 'session.create': {
442
+ request: { scopeId: string; options?: TFlexCreateSessionOptions };
443
+ response: TFlexSession;
444
+ };
445
+ 'session.get': {
446
+ request: { scopeId: string; sessionId: string };
447
+ response: TFlexSession;
448
+ };
449
+ 'session.update': {
450
+ request: { scopeId: string; sessionId: string; options: TFlexUpdateSessionOptions };
451
+ response: TFlexSession;
452
+ };
453
+ 'session.delete': {
454
+ request: { scopeId: string; sessionId: string };
455
+ response: { deleted: true };
456
+ };
457
+ 'session.compact': {
458
+ request: { scopeId: string; sessionId: string; model?: IFlexModelChoice };
459
+ response: { compacted: true };
460
+ };
461
+ 'session.reversion.info': {
462
+ request: { scopeId: string; sessionId: string };
463
+ response: TFlexSessionReversionInfo;
464
+ };
465
+ 'slash.list': {
466
+ request: { scopeId: string; sessionId: string };
467
+ response: { commands: TFlexSlashCommandDescriptor[] };
468
+ };
469
+ 'slash.execute': {
470
+ request: {
471
+ scopeId: string;
472
+ sessionId: string;
473
+ input: string;
474
+ model?: IFlexModelChoice;
475
+ };
476
+ response: TFlexSlashExecutionResult;
477
+ };
478
+ 'message.page': {
479
+ request: { scopeId: string; sessionId: string; limit?: number; before?: string };
480
+ response: TFlexMessagePage;
481
+ };
482
+ 'message.get': {
483
+ request: { scopeId: string; sessionId: string; messageId: string };
484
+ response: TFlexMessage;
485
+ };
486
+ 'prompt.start': {
487
+ request: {
488
+ scopeId: string;
489
+ sessionId: string;
490
+ prompt: TFlexPrompt;
491
+ options?: TFlexPromptOptions;
492
+ model?: IFlexModelChoice;
493
+ upload?: {
494
+ operationId: string;
495
+ directory: string;
496
+ };
497
+ };
498
+ response: { queueId: string; runId: string };
499
+ };
500
+ 'prompt.acknowledge': {
501
+ request: { scopeId: string; sessionId: string; queueId: string; runId: string };
502
+ response: { acknowledged: true };
503
+ };
504
+ 'prompt.get': {
505
+ request: { scopeId: string; sessionId: string; queueId: string };
506
+ response: { entry: TFlexPromptQueueEntry };
507
+ };
508
+ 'prompt.list': {
509
+ request: { scopeId: string; sessionId: string };
510
+ response: { entries: TFlexPromptQueueEntry[] };
511
+ };
512
+ 'prompt.cancel': {
513
+ request: { scopeId: string; sessionId: string; queueId: string };
514
+ response: { accepted: boolean };
515
+ };
516
+ 'permission.list': {
517
+ request: { scopeId: string; sessionId?: string; familyRootSessionId?: string };
518
+ response: { permissions: TFlexPermissionRequest[] };
519
+ };
520
+ 'permission.respond': {
521
+ request: {
522
+ scopeId: string;
523
+ sessionId: string;
524
+ permissionId: string;
525
+ decision: TFlexPermissionDecision;
526
+ };
527
+ response: { accepted: true };
528
+ };
529
+ 'provider.list': {
530
+ request: Record<string, never>;
531
+ response: {
532
+ providers: Array<{ providerId: 'openai'; displayName: string; loginFlow: 'device' }>;
533
+ };
534
+ };
535
+ 'provider.login.begin': {
536
+ request: { providerId: 'openai' };
537
+ response: {
538
+ loginId: string;
539
+ verificationUrl: string;
540
+ userCode: string;
541
+ status: 'pending';
542
+ };
543
+ };
544
+ 'provider.login.status': {
545
+ request: { loginId: string };
546
+ response: IFlexPublicProviderConnection;
547
+ };
548
+ 'provider.login.cancel': {
549
+ request: { loginId: string };
550
+ response: { canceled: boolean };
551
+ };
552
+ 'provider.connection.list': {
553
+ request: Record<string, never>;
554
+ response: { connections: IFlexPublicProviderConnection[] };
555
+ };
556
+ 'provider.connection.opencode-auth.get': {
557
+ request: { providerConnectionId: string };
558
+ response: { auth: IFlexOpenCodeOAuthAuth };
559
+ };
560
+ 'provider.connection.logout.prepare': {
561
+ request: { providerConnectionId: string };
562
+ response: { accepted: true };
563
+ };
564
+ 'provider.connection.logout': {
565
+ request: { providerConnectionId: string };
566
+ response: { accepted: true };
567
+ };
568
+ 'provider.connection.ratelimits.get': {
569
+ request: { providerConnectionId: string };
570
+ response: { rateLimits: IFlexPublicProviderAccountRateLimits };
571
+ };
572
+ 'provider.models.refresh': {
573
+ request: { providerConnectionId: string };
574
+ response: IFlexJobAdmission;
575
+ };
576
+ 'model.choice.get': {
577
+ request: { scopeId: string };
578
+ response: { choice: IFlexModelChoice | null };
579
+ };
580
+ 'model.choice.set': {
581
+ request: { scopeId: string; choice: IFlexModelChoice };
582
+ response: { choice: IFlexModelChoice };
583
+ };
584
+ 'model.choice.validate': {
585
+ request: { choice: IFlexModelChoice };
586
+ response: { valid: true };
587
+ };
588
+ }
589
+
590
+ export type TFlexRequestMethod = keyof IFlexRequestMap;
591
+ export type TFlexRequest<TMethod extends TFlexRequestMethod> = IFlexRequestMap[TMethod]['request'];
592
+ export type TFlexResponse<TMethod extends TFlexRequestMethod> = IFlexRequestMap[TMethod]['response'];
593
+
594
+ export interface IFlexIpcInitMessage {
595
+ version: typeof flexIpcProtocolVersion;
596
+ type: 'init';
597
+ init: IFlexServiceInit;
598
+ }
599
+
600
+ export interface IFlexIpcRequestMessage {
601
+ version: typeof flexIpcProtocolVersion;
602
+ type: 'request';
603
+ requestId: string;
604
+ method: TFlexRequestMethod;
605
+ payload: unknown;
606
+ }
607
+
608
+ export interface IFlexIpcRequestCancelMessage {
609
+ version: typeof flexIpcProtocolVersion;
610
+ type: 'request.cancel';
611
+ requestId: string;
612
+ }
613
+
614
+ export interface IFlexIpcDisposeMessage {
615
+ version: typeof flexIpcProtocolVersion;
616
+ type: 'dispose';
617
+ requestId: string;
618
+ }
619
+
620
+ export type TFlexIpcHostResponseMessage = {
621
+ version: typeof flexIpcProtocolVersion;
622
+ type: 'host.response';
623
+ requestId: string;
624
+ } & (
625
+ | { ok: true; result: unknown }
626
+ | { ok: false; error: IFlexServiceError }
627
+ );
628
+
629
+ export interface IFlexIpcParentBrowserFrameMessage {
630
+ version: typeof flexIpcProtocolVersion;
631
+ type: 'browser.channel.frame';
632
+ channelId: string;
633
+ dataBase64: string;
634
+ }
635
+
636
+ export interface IFlexIpcBrowserChannelClosedMessage {
637
+ version: typeof flexIpcProtocolVersion;
638
+ type: 'browser.channel.closed';
639
+ channelId: string;
640
+ }
641
+
642
+ export type TFlexParentMessage =
643
+ | IFlexIpcInitMessage
644
+ | IFlexIpcRequestMessage
645
+ | IFlexIpcRequestCancelMessage
646
+ | IFlexIpcDisposeMessage
647
+ | TFlexIpcHostResponseMessage
648
+ | IFlexIpcParentBrowserFrameMessage
649
+ | IFlexIpcBrowserChannelClosedMessage;
650
+
651
+ export type TFlexServiceErrorCode =
652
+ | 'INVALID_REQUEST'
653
+ | 'NOT_READY'
654
+ | 'NOT_FOUND'
655
+ | 'CONFLICT'
656
+ | 'BUSY'
657
+ | 'TIMEOUT'
658
+ | 'ABORTED'
659
+ | 'OUTCOME_UNKNOWN'
660
+ | 'SLASH_UNAVAILABLE'
661
+ | 'LIMIT_EXCEEDED'
662
+ | 'STALE_RUN'
663
+ | 'PROVIDER_LOGIN_FAILED'
664
+ | 'PROVIDER_REAUTH_REQUIRED'
665
+ | 'MODEL_CATALOG_REQUIRED'
666
+ | 'UNSUPPORTED_RUNTIME'
667
+ | 'INTERNAL';
668
+
669
+ export const flexServiceErrorMessages: Readonly<Record<TFlexServiceErrorCode, string>> = {
670
+ INVALID_REQUEST: 'The Flex request is invalid.',
671
+ NOT_READY: 'The Flex service is not ready.',
672
+ NOT_FOUND: 'The requested Flex resource was not found.',
673
+ CONFLICT: 'The Flex operation conflicted with a concurrent change.',
674
+ BUSY: 'The Flex resource is busy.',
675
+ TIMEOUT: 'The Flex operation timed out.',
676
+ ABORTED: 'The Flex operation was aborted.',
677
+ OUTCOME_UNKNOWN: 'The Flex operation outcome is unknown.',
678
+ SLASH_UNAVAILABLE: 'The Flex slash command is unavailable.',
679
+ LIMIT_EXCEEDED: 'The Flex service limit was reached.',
680
+ STALE_RUN: 'The requested Flex run is no longer current.',
681
+ PROVIDER_LOGIN_FAILED: 'The provider login did not complete.',
682
+ PROVIDER_REAUTH_REQUIRED: 'The provider connection requires authentication.',
683
+ MODEL_CATALOG_REQUIRED: 'The provider model catalog must be refreshed.',
684
+ UNSUPPORTED_RUNTIME: 'The Flex child runtime is unsupported.',
685
+ INTERNAL: 'The Flex operation failed.',
686
+ };
687
+
688
+ export interface IFlexServiceError {
689
+ name: 'FlexServiceError';
690
+ code: TFlexServiceErrorCode;
691
+ message: string;
692
+ }
693
+
694
+ export interface IFlexIpcStatusMessage {
695
+ version: typeof flexIpcProtocolVersion;
696
+ type: 'status';
697
+ status: IFlexServiceStatus;
698
+ }
699
+
700
+ export type TFlexIpcResponseMessage = {
701
+ version: typeof flexIpcProtocolVersion;
702
+ type: 'response';
703
+ requestId: string;
704
+ } & (
705
+ | { ok: true; result: unknown }
706
+ | { ok: false; error: IFlexServiceError }
707
+ );
708
+
709
+ export type TFlexJobKind = 'provider.models';
710
+
711
+ export type TFlexChildEvent =
712
+ | {
713
+ type: 'session.history.changed';
714
+ scopeId: string;
715
+ sessionId: string;
716
+ direction: 'undo' | 'redo' | 'branch';
717
+ runId?: string;
718
+ }
719
+ | {
720
+ type: 'sessions.changed';
721
+ scopeId?: string;
722
+ sessionId?: string;
723
+ coalesced: boolean;
724
+ }
725
+ | {
726
+ type: 'permissions.changed';
727
+ scopeId: string;
728
+ sessionId: string;
729
+ }
730
+ | {
731
+ type: 'tool.updated';
732
+ scopeId: string;
733
+ sessionId: string;
734
+ runId: string;
735
+ messageId: string;
736
+ messageIndex: number;
737
+ partIndex: number;
738
+ sequence: number;
739
+ timestamp: string;
740
+ part: TFlexToolMessagePart;
741
+ }
742
+ | {
743
+ type: 'reasoning.updated';
744
+ scopeId: string;
745
+ sessionId: string;
746
+ runId: string;
747
+ messageId: string;
748
+ messageIndex: number;
749
+ partIndex: number;
750
+ sequence: number;
751
+ timestamp: string;
752
+ part: TFlexReasoningMessagePart;
753
+ }
754
+ | {
755
+ type: 'text.updated';
756
+ scopeId: string;
757
+ sessionId: string;
758
+ runId: string;
759
+ messageId: string;
760
+ messageIndex: number;
761
+ partIndex: number;
762
+ sequence: number;
763
+ timestamp: string;
764
+ status: 'running' | 'completed';
765
+ part: TFlexTextMessagePart;
766
+ }
767
+ | {
768
+ type: 'prompt.queued';
769
+ scopeId: string;
770
+ sessionId: string;
771
+ queueId: string;
772
+ runId?: never;
773
+ entry: TFlexPromptQueueEntry;
774
+ }
775
+ | {
776
+ type: 'prompt.started' | 'prompt.running' | 'prompt.finished';
777
+ scopeId: string;
778
+ sessionId: string;
779
+ queueId: string;
780
+ runId: string;
781
+ entry: TFlexPromptQueueEntry;
782
+ }
783
+ | {
784
+ type: 'prompt.message-bound';
785
+ scopeId: string;
786
+ sessionId: string;
787
+ queueId: string;
788
+ runId: string;
789
+ messageId: string;
790
+ }
791
+ | {
792
+ type: 'job.finished';
793
+ jobId: string;
794
+ kind: TFlexJobKind;
795
+ status: 'completed' | 'failed' | 'cancelled';
796
+ models?: IFlexPublicProviderModel[];
797
+ }
798
+ | {
799
+ type: 'provider.connection.changed';
800
+ connection: IFlexPublicProviderConnection;
801
+ };
802
+
803
+ export interface IFlexIpcEventMessage {
804
+ version: typeof flexIpcProtocolVersion;
805
+ type: 'event';
806
+ event: TFlexChildEvent;
807
+ }
808
+
809
+ export interface IFlexIpcHostRequestMessage {
810
+ version: typeof flexIpcProtocolVersion;
811
+ type: 'host.request';
812
+ requestId: string;
813
+ method: TFlexHostRequestMethod;
814
+ payload: unknown;
815
+ timeoutMs: number;
816
+ }
817
+
818
+ export interface IFlexIpcHostCancelMessage {
819
+ version: typeof flexIpcProtocolVersion;
820
+ type: 'host.cancel';
821
+ requestId: string;
822
+ }
823
+
824
+ export interface IFlexIpcHostResponseAcknowledgementMessage {
825
+ version: typeof flexIpcProtocolVersion;
826
+ type: 'host.response.acknowledge';
827
+ requestId: string;
828
+ }
829
+
830
+ export interface IFlexIpcChildBrowserFrameMessage {
831
+ version: typeof flexIpcProtocolVersion;
832
+ type: 'browser.channel.frame';
833
+ channelId: string;
834
+ dataBase64: string;
835
+ }
836
+
837
+ export type TFlexChildMessage =
838
+ | IFlexIpcStatusMessage
839
+ | TFlexIpcResponseMessage
840
+ | IFlexIpcEventMessage
841
+ | IFlexIpcHostRequestMessage
842
+ | IFlexIpcHostCancelMessage
843
+ | IFlexIpcHostResponseAcknowledgementMessage
844
+ | IFlexIpcChildBrowserFrameMessage;
845
+
846
+ const requestMethods: ReadonlySet<string> = new Set<TFlexRequestMethod>([
847
+ 'service.status',
848
+ 'project.replaceAll',
849
+ 'project.register',
850
+ 'project.remove',
851
+ 'intelligence.start',
852
+ 'intelligence.get',
853
+ 'intelligence.cancel',
854
+ 'session.list',
855
+ 'session.create',
856
+ 'session.get',
857
+ 'session.update',
858
+ 'session.delete',
859
+ 'session.compact',
860
+ 'session.reversion.info',
861
+ 'slash.list',
862
+ 'slash.execute',
863
+ 'message.page',
864
+ 'message.get',
865
+ 'prompt.start',
866
+ 'prompt.acknowledge',
867
+ 'prompt.get',
868
+ 'prompt.list',
869
+ 'prompt.cancel',
870
+ 'permission.list',
871
+ 'permission.respond',
872
+ 'provider.list',
873
+ 'provider.login.begin',
874
+ 'provider.login.status',
875
+ 'provider.login.cancel',
876
+ 'provider.connection.list',
877
+ 'provider.connection.opencode-auth.get',
878
+ 'provider.connection.logout.prepare',
879
+ 'provider.connection.logout',
880
+ 'provider.connection.ratelimits.get',
881
+ 'provider.models.refresh',
882
+ 'model.choice.get',
883
+ 'model.choice.set',
884
+ 'model.choice.validate',
885
+ ]);
886
+ const hostRequestMethods: ReadonlySet<string> = new Set<TFlexHostRequestMethod>([
887
+ 'reversion.prepare',
888
+ 'reversion.inspect-capture',
889
+ 'reversion.finalize',
890
+ 'reversion.inspect-apply',
891
+ 'reversion.apply',
892
+ 'reversion.release',
893
+ 'git.worktree.create',
894
+ 'git.worktree.list',
895
+ 'git.worktree.remove',
896
+ 'crossharness.chats.list',
897
+ 'crossharness.chat.read',
898
+ 'project-management.load',
899
+ 'project-management.save',
900
+ 'project-management.tombstone',
901
+ 'project-management.purge',
902
+ 'browser.resources.resolve',
903
+ 'browser.channel.open',
904
+ 'browser.channel.close',
905
+ ]);
906
+ const serviceStates: ReadonlySet<string> = new Set<TFlexServiceState>([
907
+ 'starting', 'ready', 'unsupported', 'failed', 'stopping', 'stopped',
908
+ ]);
909
+ const serviceStatusCodes: ReadonlySet<string> = new Set<TFlexServiceStatusCode>([
910
+ 'UNSUPPORTED_RUNTIME', 'INITIALIZATION_FAILED', 'SERVICE_STOPPED',
911
+ ]);
912
+ const serviceErrorCodes: ReadonlySet<string> = new Set<TFlexServiceErrorCode>(
913
+ Object.keys(flexServiceErrorMessages) as TFlexServiceErrorCode[],
914
+ );
915
+ const identifierPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,511}$/u;
916
+ const projectIdPattern = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
917
+ const requestIdPattern = /^[A-Za-z0-9_-]{22}$/u;
918
+ const qualifiedIdPattern = /^flex:(?:job|provider:openai):[A-Za-z0-9_-]{22}$/u;
919
+ const intelligenceCapabilityTokenPattern = /^[A-Za-z0-9_-]{43}$/u;
920
+
921
+ export const isFlexPlainObject = (valueArg: unknown): valueArg is Record<string, unknown> =>
922
+ typeof valueArg === 'object'
923
+ && valueArg !== null
924
+ && !Array.isArray(valueArg)
925
+ && (
926
+ Object.getPrototypeOf(valueArg) === Object.prototype
927
+ || Object.getPrototypeOf(valueArg) === null
928
+ );
929
+
930
+ export const hasFlexExactKeys = (
931
+ valueArg: Record<string, unknown>,
932
+ requiredKeysArg: readonly string[],
933
+ optionalKeysArg: readonly string[] = [],
934
+ ): boolean => {
935
+ const keys = Object.keys(valueArg);
936
+ return requiredKeysArg.every((key) => Object.hasOwn(valueArg, key))
937
+ && keys.every((key) => requiredKeysArg.includes(key) || optionalKeysArg.includes(key));
938
+ };
939
+
940
+ const isBoundedString = (
941
+ valueArg: unknown,
942
+ maximumBytesArg: number,
943
+ allowEmptyArg = false,
944
+ ): valueArg is string => typeof valueArg === 'string'
945
+ && (allowEmptyArg || valueArg.length > 0)
946
+ && Buffer.byteLength(valueArg, 'utf8') <= maximumBytesArg;
947
+
948
+ const isIdentifier = (valueArg: unknown): valueArg is string =>
949
+ isBoundedString(valueArg, 512) && identifierPattern.test(valueArg);
950
+
951
+ const isRequestId = (valueArg: unknown): valueArg is string =>
952
+ typeof valueArg === 'string' && requestIdPattern.test(valueArg);
953
+
954
+ const isChannelId = (valueArg: unknown): valueArg is string =>
955
+ typeof valueArg === 'string' && identifierPattern.test(valueArg);
956
+
957
+ const isCanonicalBrowserFrame = (valueArg: unknown): valueArg is string => {
958
+ if (typeof valueArg !== 'string' || valueArg.length === 0) return false;
959
+ const bytes = Buffer.from(valueArg, 'base64');
960
+ return bytes.byteLength >= 1
961
+ && bytes.byteLength <= flexIpcMaximumBrowserFrameBytes
962
+ && bytes.toString('base64') === valueArg;
963
+ };
964
+
965
+ const isQualifiedId = (valueArg: unknown): valueArg is string =>
966
+ typeof valueArg === 'string' && qualifiedIdPattern.test(valueArg);
967
+
968
+ const isIntelligenceCapabilityToken = (valueArg: unknown): valueArg is string =>
969
+ typeof valueArg === 'string' && intelligenceCapabilityTokenPattern.test(valueArg);
970
+
971
+ const isIsoDate = (valueArg: unknown): valueArg is string =>
972
+ isBoundedString(valueArg, 64)
973
+ && Number.isFinite(Date.parse(valueArg))
974
+ && new Date(valueArg).toISOString() === valueArg;
975
+
976
+ const isNonNegativeInteger = (valueArg: unknown): valueArg is number =>
977
+ Number.isSafeInteger(valueArg) && (valueArg as number) >= 0;
978
+
979
+ const isFlexIntelligenceSessionId = (
980
+ valueArg: unknown,
981
+ ): valueArg is IFlexIntelligenceSessionId => isFlexPlainObject(valueArg)
982
+ && hasFlexExactKeys(valueArg, ['harnessId', 'nativeId'])
983
+ && (valueArg.harnessId === 'opencode' || valueArg.harnessId === 'flex')
984
+ && isIdentifier(valueArg.nativeId);
985
+
986
+ const isFlexIntelligenceChatSummary = (
987
+ valueArg: unknown,
988
+ ): valueArg is IFlexIntelligenceChatSummary => isFlexPlainObject(valueArg)
989
+ && hasFlexExactKeys(valueArg, ['sessionId', 'title', 'updatedAt'], ['isActive'])
990
+ && isFlexIntelligenceSessionId(valueArg.sessionId)
991
+ && isBoundedString(valueArg.title, 4096, true)
992
+ && isIsoDate(valueArg.updatedAt)
993
+ && (valueArg.isActive === undefined || typeof valueArg.isActive === 'boolean');
994
+
995
+ const isFlexIntelligenceChatMessage = (
996
+ valueArg: unknown,
997
+ ): valueArg is IFlexIntelligenceChatMessage => isFlexPlainObject(valueArg)
998
+ && hasFlexExactKeys(valueArg, ['role', 'text'], ['timestamp'])
999
+ && isBoundedString(valueArg.role, 128)
1000
+ && isBoundedString(valueArg.text, 16 * 1024, true)
1001
+ && (valueArg.timestamp === undefined || isIsoDate(valueArg.timestamp));
1002
+
1003
+ const isFlexIntelligenceChatTranscript = (
1004
+ valueArg: unknown,
1005
+ ): valueArg is IFlexIntelligenceChatTranscript => isFlexPlainObject(valueArg)
1006
+ && hasFlexExactKeys(valueArg, ['sessionId', 'title', 'messages'], ['totalMessages'])
1007
+ && isFlexIntelligenceSessionId(valueArg.sessionId)
1008
+ && isBoundedString(valueArg.title, 4096, true)
1009
+ && Array.isArray(valueArg.messages)
1010
+ && valueArg.messages.length <= 50
1011
+ && valueArg.messages.every(isFlexIntelligenceChatMessage)
1012
+ && (valueArg.totalMessages === undefined || isNonNegativeInteger(valueArg.totalMessages))
1013
+ && Buffer.byteLength(JSON.stringify(valueArg), 'utf8') <= 256 * 1024;
1014
+
1015
+ const isFlexIntelligenceResult = (valueArg: unknown): valueArg is IFlexIntelligenceResult => (
1016
+ isFlexPlainObject(valueArg)
1017
+ && hasFlexExactKeys(valueArg, ['answer', 'scratchpad', 'model'])
1018
+ && isBoundedString(valueArg.answer, 128 * 1024, true)
1019
+ && isBoundedString(valueArg.scratchpad, 128 * 1024, true)
1020
+ && valueArg.answer.length <= 32_768
1021
+ && valueArg.scratchpad.length <= 32_768
1022
+ && valueArg.model === 'openai/gpt-5.6-luna'
1023
+ );
1024
+
1025
+ const isJsonValue = (valueArg: unknown, depthArg = 0): boolean => {
1026
+ if (depthArg > 20) return false;
1027
+ if (
1028
+ valueArg === null
1029
+ || typeof valueArg === 'string'
1030
+ || typeof valueArg === 'boolean'
1031
+ || (typeof valueArg === 'number' && Number.isFinite(valueArg))
1032
+ ) return true;
1033
+ if (Array.isArray(valueArg)) {
1034
+ return valueArg.length <= 4096 && valueArg.every((entry) => isJsonValue(entry, depthArg + 1));
1035
+ }
1036
+ if (!isFlexPlainObject(valueArg) || Object.keys(valueArg).length > 4096) return false;
1037
+ return Object.values(valueArg).every((entry) => isJsonValue(entry, depthArg + 1));
1038
+ };
1039
+
1040
+ const isFlexSessionGeneration = (valueArg: Record<string, unknown>): boolean =>
1041
+ isBoundedString(
1042
+ valueArg.sessionGenerationId,
1043
+ plugins.flexharness.FLEX_SESSION_GENERATION_ID_MAX_BYTES,
1044
+ )
1045
+ && isNonNegativeInteger(valueArg.sessionGenerationSequence)
1046
+ && Number(valueArg.sessionGenerationSequence) >= 1;
1047
+
1048
+ const isFlexProjectManagementRecord = (
1049
+ valueArg: unknown,
1050
+ ): valueArg is TFlexProjectManagementRecord => {
1051
+ try {
1052
+ plugins.flexharness.assertFlexProjectManagementRecord(valueArg);
1053
+ return true;
1054
+ } catch {
1055
+ return false;
1056
+ }
1057
+ };
1058
+
1059
+ const isFlexProjectManagementSnapshot = (
1060
+ valueArg: unknown,
1061
+ ): valueArg is TFlexProjectManagementSnapshot => {
1062
+ try {
1063
+ plugins.flexharness.assertFlexProjectManagementSnapshot(valueArg);
1064
+ return true;
1065
+ } catch {
1066
+ return false;
1067
+ }
1068
+ };
1069
+
1070
+ const isFlexProjectManagementTombstone = (
1071
+ valueArg: unknown,
1072
+ ): valueArg is TFlexProjectManagementTombstone => {
1073
+ try {
1074
+ plugins.flexharness.assertFlexProjectManagementTombstone(valueArg);
1075
+ return true;
1076
+ } catch {
1077
+ return false;
1078
+ }
1079
+ };
1080
+
1081
+ const isFlexProjectManagementWriteContext = (
1082
+ valueArg: unknown,
1083
+ ): valueArg is TFlexProjectManagementWriteContext => isFlexPlainObject(valueArg)
1084
+ && hasFlexExactKeys(valueArg, ['actor'], ['runId', 'agent', 'toolCallId'])
1085
+ && (valueArg.actor === 'agent' || valueArg.actor === 'application')
1086
+ && ['runId', 'agent', 'toolCallId'].every((key) => (
1087
+ valueArg[key] === undefined || isBoundedString(valueArg[key], 512)
1088
+ ));
1089
+
1090
+ const isFlexProjectManagementCommitResult = (
1091
+ valueArg: unknown,
1092
+ ): valueArg is TFlexProjectManagementCommitResult => isFlexPlainObject(valueArg)
1093
+ && (
1094
+ (hasFlexExactKeys(valueArg, ['committed']) && valueArg.committed === true)
1095
+ || (
1096
+ hasFlexExactKeys(valueArg, ['committed', 'actualRevision'])
1097
+ && valueArg.committed === false
1098
+ && isNonNegativeInteger(valueArg.actualRevision)
1099
+ )
1100
+ );
1101
+
1102
+ export const isFlexProjectRecord = (valueArg: unknown): valueArg is IFlexProjectRecord => {
1103
+ if (!isFlexPlainObject(valueArg) || !hasFlexExactKeys(
1104
+ valueArg,
1105
+ ['projectId', 'directory'],
1106
+ ['directoryIdentity'],
1107
+ )) {
1108
+ return false;
1109
+ }
1110
+ if (
1111
+ typeof valueArg.projectId !== 'string'
1112
+ || !projectIdPattern.test(valueArg.projectId)
1113
+ || !isBoundedString(valueArg.directory, 4096)
1114
+ || valueArg.directory.includes('\0')
1115
+ || !plugins.path.isAbsolute(valueArg.directory)
1116
+ || plugins.path.normalize(valueArg.directory) !== valueArg.directory
1117
+ ) return false;
1118
+ if (valueArg.directoryIdentity === undefined) return true;
1119
+ const directoryIdentity = valueArg.directoryIdentity;
1120
+ if (!isFlexPlainObject(directoryIdentity) || !Array.isArray(directoryIdentity.ancestry)) {
1121
+ return false;
1122
+ }
1123
+ const ancestry = directoryIdentity.ancestry;
1124
+ return hasFlexExactKeys(directoryIdentity, ['ancestry'])
1125
+ && ancestry.length === 1
1126
+ + valueArg.directory.slice(1).split('/').filter(Boolean).length
1127
+ && ancestry.length <= controllerFilesystemIdentityMaximumComponents
1128
+ && ancestry.every((component) => (
1129
+ isFlexPlainObject(component)
1130
+ && hasFlexExactKeys(component, ['device', 'inode', 'type'])
1131
+ && typeof component.device === 'string'
1132
+ && /^(?:0|[1-9][0-9]{0,31})$/u.test(component.device)
1133
+ && typeof component.inode === 'string'
1134
+ && /^(?:0|[1-9][0-9]{0,31})$/u.test(component.inode)
1135
+ && component.type === 'directory'
1136
+ ));
1137
+ };
1138
+
1139
+ export const isFlexIdentityBoundProjectRecord = (
1140
+ valueArg: unknown,
1141
+ ): valueArg is IFlexIdentityBoundProjectRecord => isFlexProjectRecord(valueArg)
1142
+ && valueArg.directoryIdentity !== undefined;
1143
+
1144
+ export const isFlexDatabaseDescriptor = (
1145
+ valueArg: unknown,
1146
+ ): valueArg is IFlexDatabaseDescriptor => isFlexPlainObject(valueArg)
1147
+ && hasFlexExactKeys(valueArg, ['mongoDbUrl', 'mongoDbName'])
1148
+ && isBoundedString(valueArg.mongoDbUrl, 4096)
1149
+ && /^(?:mongodb|mongodb\+srv):\/\//u.test(valueArg.mongoDbUrl)
1150
+ && typeof valueArg.mongoDbName === 'string'
1151
+ && /^[A-Za-z0-9_-]{1,63}$/u.test(valueArg.mongoDbName);
1152
+
1153
+ const isFlexCredentialDirectory = (valueArg: unknown): valueArg is string => {
1154
+ if (
1155
+ !isBoundedString(valueArg, 4096)
1156
+ || !valueArg.startsWith('/')
1157
+ || valueArg.includes('\0')
1158
+ || valueArg === '/'
1159
+ ) return false;
1160
+ const segments = valueArg.split('/');
1161
+ return !segments.some((segment, index) => (
1162
+ index > 0 && (segment === '' || segment === '.' || segment === '..')
1163
+ ));
1164
+ };
1165
+
1166
+ export const isFlexServiceInit = (valueArg: unknown): valueArg is IFlexServiceInit => {
1167
+ if (
1168
+ !isFlexPlainObject(valueArg)
1169
+ || !hasFlexExactKeys(valueArg, [
1170
+ 'controllerId', 'database', 'flexCredentialDirectory', 'projects',
1171
+ ])
1172
+ || !isIdentifier(valueArg.controllerId)
1173
+ || !isFlexDatabaseDescriptor(valueArg.database)
1174
+ || !isFlexCredentialDirectory(valueArg.flexCredentialDirectory)
1175
+ || !Array.isArray(valueArg.projects)
1176
+ || valueArg.projects.length > controllerProjectLimit
1177
+ || !valueArg.projects.every(isFlexProjectRecord)
1178
+ ) return false;
1179
+ return new Set(valueArg.projects.map((project) => project.projectId)).size
1180
+ === valueArg.projects.length;
1181
+ };
1182
+
1183
+ export const isFlexServiceStatus = (valueArg: unknown): valueArg is IFlexServiceStatus => {
1184
+ if (
1185
+ !isFlexPlainObject(valueArg)
1186
+ || !hasFlexExactKeys(valueArg, ['state', 'ready'], ['code', 'startedAt'])
1187
+ || typeof valueArg.state !== 'string'
1188
+ || !serviceStates.has(valueArg.state)
1189
+ || typeof valueArg.ready !== 'boolean'
1190
+ || (valueArg.code !== undefined
1191
+ && (typeof valueArg.code !== 'string' || !serviceStatusCodes.has(valueArg.code)))
1192
+ || (valueArg.startedAt !== undefined && !isIsoDate(valueArg.startedAt))
1193
+ ) return false;
1194
+ if (valueArg.state === 'ready') return valueArg.ready && valueArg.code === undefined;
1195
+ return !valueArg.ready;
1196
+ };
1197
+
1198
+ const isAccountSummary = (valueArg: unknown): valueArg is IFlexPublicAccountSummary =>
1199
+ isFlexPlainObject(valueArg)
1200
+ && hasFlexExactKeys(valueArg, ['providerId', 'authKind'], ['accountId', 'email', 'plan'])
1201
+ && valueArg.providerId === 'openai'
1202
+ && valueArg.authKind === 'chatgptOAuth'
1203
+ && ['accountId', 'email', 'plan'].every((key) => (
1204
+ valueArg[key] === undefined || isBoundedString(valueArg[key], 512)
1205
+ ));
1206
+
1207
+ export const isFlexPublicProviderConnection = (
1208
+ valueArg: unknown,
1209
+ ): valueArg is IFlexPublicProviderConnection => isFlexPlainObject(valueArg)
1210
+ && hasFlexExactKeys(valueArg, ['loginId', 'providerId', 'status'], ['account'])
1211
+ && isQualifiedId(valueArg.loginId)
1212
+ && valueArg.providerId === 'openai'
1213
+ && ['pending', 'active', 'deleting', 'reauthRequired'].includes(String(valueArg.status))
1214
+ && (valueArg.account === undefined || isAccountSummary(valueArg.account));
1215
+
1216
+ const isOpenCodeOAuthAuth = (valueArg: unknown): valueArg is IFlexOpenCodeOAuthAuth => (
1217
+ isFlexPlainObject(valueArg)
1218
+ && hasFlexExactKeys(valueArg, ['type', 'access', 'refresh', 'expires'], ['accountId'])
1219
+ && valueArg.type === 'oauth'
1220
+ && isBoundedString(valueArg.access, 16 * 1024)
1221
+ && isBoundedString(valueArg.refresh, 16 * 1024)
1222
+ && isNonNegativeInteger(valueArg.expires)
1223
+ && (valueArg.accountId === undefined || isBoundedString(valueArg.accountId, 512))
1224
+ && Buffer.byteLength(JSON.stringify(valueArg), 'utf8') <= 48 * 1024
1225
+ );
1226
+
1227
+ const isModelChoice = (valueArg: unknown): valueArg is IFlexModelChoice =>
1228
+ isFlexPlainObject(valueArg)
1229
+ && hasFlexExactKeys(valueArg, ['providerConnectionId', 'modelId'], ['variant'])
1230
+ && isQualifiedId(valueArg.providerConnectionId)
1231
+ && isBoundedString(valueArg.modelId, 256)
1232
+ && (valueArg.variant === undefined || isBoundedString(valueArg.variant, 128));
1233
+
1234
+ const isModelIdentity = (valueArg: unknown): boolean => isFlexPlainObject(valueArg)
1235
+ && hasFlexExactKeys(valueArg, ['provider', 'model'], ['displayName', 'variant'])
1236
+ && isBoundedString(valueArg.provider, 256)
1237
+ && isBoundedString(valueArg.model, 256)
1238
+ && (valueArg.displayName === undefined || isBoundedString(valueArg.displayName, 512))
1239
+ && (valueArg.variant === undefined || isBoundedString(valueArg.variant, 128));
1240
+
1241
+ const isUsage = (valueArg: unknown): boolean => isFlexPlainObject(valueArg)
1242
+ && hasFlexExactKeys(valueArg, [
1243
+ 'inputTokens', 'outputTokens', 'totalTokens', 'cacheReadTokens', 'cacheWriteTokens',
1244
+ ])
1245
+ && Object.values(valueArg).every(isNonNegativeInteger);
1246
+
1247
+ const isMessagePart = (valueArg: unknown): boolean => {
1248
+ if (!isFlexPlainObject(valueArg) || !isIdentifier(valueArg.partId)) return false;
1249
+ switch (valueArg.type) {
1250
+ case 'text':
1251
+ return hasFlexExactKeys(valueArg, ['partId', 'type', 'text'])
1252
+ && isBoundedString(valueArg.text, 96 * 1024, true);
1253
+ case 'reasoning':
1254
+ return hasFlexExactKeys(valueArg, ['partId', 'type', 'text', 'status'])
1255
+ && isBoundedString(valueArg.text, 96 * 1024, true)
1256
+ && ['running', 'completed', 'cancelled'].includes(String(valueArg.status));
1257
+ case 'tool':
1258
+ return hasFlexExactKeys(
1259
+ valueArg,
1260
+ ['partId', 'type', 'toolCallId', 'toolName', 'status', 'input'],
1261
+ ['output', 'error', 'childSessionId', 'model'],
1262
+ )
1263
+ && isIdentifier(valueArg.toolCallId)
1264
+ && isBoundedString(valueArg.toolName, 512)
1265
+ && ['running', 'completed', 'failed', 'cancelled'].includes(String(valueArg.status))
1266
+ && isJsonValue(valueArg.input)
1267
+ && (valueArg.output === undefined || isJsonValue(valueArg.output))
1268
+ && (valueArg.error === undefined || isBoundedString(valueArg.error, 2048))
1269
+ && (valueArg.childSessionId === undefined || isIdentifier(valueArg.childSessionId))
1270
+ && (valueArg.model === undefined || (
1271
+ valueArg.childSessionId !== undefined && isModelIdentity(valueArg.model)
1272
+ ));
1273
+ case 'attachment':
1274
+ return hasFlexExactKeys(
1275
+ valueArg,
1276
+ ['partId', 'type', 'attachmentType', 'source'],
1277
+ ['sizeBytes', 'mediaType', 'name'],
1278
+ )
1279
+ && ['image', 'file'].includes(String(valueArg.attachmentType))
1280
+ && ['inline-base64', 'data-url', 'remote-url'].includes(String(valueArg.source))
1281
+ && (valueArg.sizeBytes === undefined || isNonNegativeInteger(valueArg.sizeBytes))
1282
+ && (valueArg.mediaType === undefined || isBoundedString(valueArg.mediaType, 512))
1283
+ && (valueArg.name === undefined || isBoundedString(valueArg.name, 1024));
1284
+ default:
1285
+ return false;
1286
+ }
1287
+ };
1288
+
1289
+ export const isFlexPublicMessage = (valueArg: unknown): valueArg is TFlexMessage =>
1290
+ isFlexPlainObject(valueArg)
1291
+ && hasFlexExactKeys(
1292
+ valueArg,
1293
+ ['messageId', 'sessionId', 'runId', 'role', 'status', 'createdAt', 'parts'],
1294
+ ['completedAt', 'model', 'usage', 'error'],
1295
+ )
1296
+ && isIdentifier(valueArg.messageId)
1297
+ && isIdentifier(valueArg.sessionId)
1298
+ && isIdentifier(valueArg.runId)
1299
+ && ['user', 'assistant'].includes(String(valueArg.role))
1300
+ && ['streaming', 'completed', 'failed', 'cancelled'].includes(String(valueArg.status))
1301
+ && isIsoDate(valueArg.createdAt)
1302
+ && (valueArg.completedAt === undefined || isIsoDate(valueArg.completedAt))
1303
+ && Array.isArray(valueArg.parts)
1304
+ && valueArg.parts.length <= 2000
1305
+ && valueArg.parts.every(isMessagePart)
1306
+ && (valueArg.model === undefined || isModelIdentity(valueArg.model))
1307
+ && (valueArg.usage === undefined || isUsage(valueArg.usage))
1308
+ && (valueArg.error === undefined || isBoundedString(valueArg.error, 2048));
1309
+
1310
+ export const isFlexPublicSession = (valueArg: unknown): valueArg is TFlexSession => {
1311
+ if (
1312
+ !isFlexPlainObject(valueArg)
1313
+ || !hasFlexExactKeys(
1314
+ valueArg,
1315
+ ['scopeId', 'sessionId', 'createdAt', 'updatedAt', 'status', 'activity'],
1316
+ [
1317
+ 'sessionGenerationId', 'sessionGenerationSequence',
1318
+ 'title', 'archivedAt', 'parentSessionId', 'parentRunId',
1319
+ 'parentToolCallId', 'agent', 'depth',
1320
+ ],
1321
+ )
1322
+ || !isIdentifier(valueArg.scopeId)
1323
+ || !isIdentifier(valueArg.sessionId)
1324
+ || !isIsoDate(valueArg.createdAt)
1325
+ || !isIsoDate(valueArg.updatedAt)
1326
+ || (valueArg.title !== undefined || Object.hasOwn(valueArg, 'title'))
1327
+ && !isBoundedString(valueArg.title, 4096, true)
1328
+ || (valueArg.archivedAt !== undefined && !isIsoDate(valueArg.archivedAt))
1329
+ || !['idle', 'scheduled', 'running', 'waiting_permission', 'failed', 'cancelled']
1330
+ .includes(String(valueArg.status))
1331
+ || !isFlexPlainObject(valueArg.activity)
1332
+ || !hasFlexExactKeys(valueArg.activity, ['status'], [
1333
+ 'runId', 'startedAt', 'completedAt', 'error',
1334
+ ])
1335
+ || !['idle', 'scheduled', 'running', 'waiting_permission', 'failed', 'cancelled']
1336
+ .includes(String(valueArg.activity.status))
1337
+ ) return false;
1338
+ const hasGenerationId = valueArg.sessionGenerationId !== undefined;
1339
+ const hasGenerationSequence = valueArg.sessionGenerationSequence !== undefined;
1340
+ if (
1341
+ hasGenerationId !== hasGenerationSequence
1342
+ || (hasGenerationId && !isFlexSessionGeneration(valueArg))
1343
+ ) return false;
1344
+ const relationship = [
1345
+ valueArg.parentSessionId,
1346
+ valueArg.parentRunId,
1347
+ valueArg.parentToolCallId,
1348
+ valueArg.agent,
1349
+ ];
1350
+ const relationshipCount = relationship.filter((entry) => entry !== undefined).length;
1351
+ return (valueArg.activity.runId === undefined || isIdentifier(valueArg.activity.runId))
1352
+ && (valueArg.activity.startedAt === undefined || isIsoDate(valueArg.activity.startedAt))
1353
+ && (valueArg.activity.completedAt === undefined || isIsoDate(valueArg.activity.completedAt))
1354
+ && (valueArg.activity.error === undefined || isBoundedString(valueArg.activity.error, 2048))
1355
+ && (relationshipCount === 0 || relationshipCount === relationship.length)
1356
+ && (valueArg.parentSessionId === undefined || isIdentifier(valueArg.parentSessionId))
1357
+ && (valueArg.parentRunId === undefined || isIdentifier(valueArg.parentRunId))
1358
+ && (valueArg.parentToolCallId === undefined || isIdentifier(valueArg.parentToolCallId))
1359
+ && (valueArg.agent === undefined || isBoundedString(valueArg.agent, 128))
1360
+ && (
1361
+ relationshipCount === 0
1362
+ ? valueArg.depth === undefined || valueArg.depth === 0
1363
+ : Number.isSafeInteger(valueArg.depth)
1364
+ && Number(valueArg.depth) >= 1
1365
+ && Number(valueArg.depth) <= 8
1366
+ );
1367
+ };
1368
+
1369
+ const isPromptQueueEntry = (valueArg: unknown): valueArg is TFlexPromptQueueEntry => (
1370
+ isFlexPlainObject(valueArg)
1371
+ && hasFlexExactKeys(
1372
+ valueArg,
1373
+ ['queueId', 'queueSequence', 'scopeId', 'sessionId', 'status', 'queuedAt'],
1374
+ ['runId', 'scheduleKey', 'startedAt', 'finishedAt', 'error'],
1375
+ )
1376
+ && isIdentifier(valueArg.queueId)
1377
+ && isNonNegativeInteger(valueArg.queueSequence)
1378
+ && valueArg.queueSequence >= 1
1379
+ && isIdentifier(valueArg.scopeId)
1380
+ && isIdentifier(valueArg.sessionId)
1381
+ && ['queued', 'starting', 'scheduled', 'running', 'completed', 'failed', 'cancelled']
1382
+ .includes(String(valueArg.status))
1383
+ && isIsoDate(valueArg.queuedAt)
1384
+ && (valueArg.runId === undefined || isIdentifier(valueArg.runId))
1385
+ && (valueArg.scheduleKey === undefined || isIdentifier(valueArg.scheduleKey))
1386
+ && (valueArg.startedAt === undefined || isIsoDate(valueArg.startedAt))
1387
+ && (valueArg.finishedAt === undefined || isIsoDate(valueArg.finishedAt))
1388
+ && (valueArg.error === undefined || (
1389
+ isFlexPlainObject(valueArg.error)
1390
+ && hasFlexExactKeys(valueArg.error, ['name', 'message'], ['code'])
1391
+ && isBoundedString(valueArg.error.name, 128)
1392
+ && isBoundedString(valueArg.error.message, 2048)
1393
+ && (valueArg.error.code === undefined || isBoundedString(valueArg.error.code, 128))
1394
+ ))
1395
+ );
1396
+
1397
+ const isPermissionRequest = (valueArg: unknown): valueArg is TFlexPermissionRequest =>
1398
+ isFlexPlainObject(valueArg)
1399
+ && hasFlexExactKeys(
1400
+ valueArg,
1401
+ ['permissionId', 'scopeId', 'sessionId', 'runId', 'createdAt', 'kind', 'description'],
1402
+ ['toolCallId', 'rememberKey', 'metadata'],
1403
+ )
1404
+ && isIdentifier(valueArg.permissionId)
1405
+ && isIdentifier(valueArg.scopeId)
1406
+ && isIdentifier(valueArg.sessionId)
1407
+ && isIdentifier(valueArg.runId)
1408
+ && isIsoDate(valueArg.createdAt)
1409
+ && isBoundedString(valueArg.kind, 128)
1410
+ && isBoundedString(valueArg.description, 2048)
1411
+ && (valueArg.toolCallId === undefined || isIdentifier(valueArg.toolCallId))
1412
+ && (valueArg.rememberKey === undefined || isBoundedString(valueArg.rememberKey, 512))
1413
+ && (valueArg.metadata === undefined || isJsonValue(valueArg.metadata));
1414
+
1415
+ const isPrompt = (valueArg: unknown): valueArg is TFlexPrompt => {
1416
+ if (isBoundedString(valueArg, 768 * 1024, true)) return true;
1417
+ if (!Array.isArray(valueArg) || valueArg.length < 1 || valueArg.length > 128) return false;
1418
+ return valueArg.every((part) => {
1419
+ if (!isFlexPlainObject(part)) return false;
1420
+ if (part.type === 'text') {
1421
+ return hasFlexExactKeys(part, ['type', 'text'])
1422
+ && isBoundedString(part.text, 768 * 1024, true);
1423
+ }
1424
+ if (part.type === 'image') {
1425
+ return hasFlexExactKeys(part, ['type', 'data'], ['mediaType', 'name'])
1426
+ && isBoundedString(part.data, 768 * 1024)
1427
+ && (part.mediaType === undefined || isBoundedString(part.mediaType, 512))
1428
+ && (part.name === undefined || isBoundedString(part.name, 1024));
1429
+ }
1430
+ if (part.type === 'file') {
1431
+ return hasFlexExactKeys(part, ['type', 'data', 'mediaType'], ['name'])
1432
+ && isBoundedString(part.data, 768 * 1024)
1433
+ && isBoundedString(part.mediaType, 512)
1434
+ && (part.name === undefined || isBoundedString(part.name, 1024));
1435
+ }
1436
+ return false;
1437
+ });
1438
+ };
1439
+
1440
+ const isPromptOptions = (valueArg: unknown): valueArg is TFlexPromptOptions =>
1441
+ isFlexPlainObject(valueArg)
1442
+ && hasFlexExactKeys(valueArg, [], ['modelHint', 'system', 'maxSteps'])
1443
+ && (valueArg.modelHint === undefined || isBoundedString(valueArg.modelHint, 512))
1444
+ && (valueArg.system === undefined || isBoundedString(valueArg.system, 128 * 1024, true))
1445
+ && (valueArg.maxSteps === undefined
1446
+ || (Number.isSafeInteger(valueArg.maxSteps) && Number(valueArg.maxSteps) >= 1
1447
+ && Number(valueArg.maxSteps) <= 1000));
1448
+
1449
+ const isCreateSessionOptions = (valueArg: unknown): valueArg is TFlexCreateSessionOptions =>
1450
+ isFlexPlainObject(valueArg)
1451
+ && hasFlexExactKeys(valueArg, [], ['sessionId', 'title'])
1452
+ && (valueArg.sessionId === undefined || isIdentifier(valueArg.sessionId))
1453
+ && (valueArg.title === undefined || isBoundedString(valueArg.title, 4096, true));
1454
+
1455
+ const isUpdateSessionOptions = (valueArg: unknown): valueArg is TFlexUpdateSessionOptions =>
1456
+ isFlexPlainObject(valueArg)
1457
+ && hasFlexExactKeys(valueArg, [], ['title', 'archived'])
1458
+ && (valueArg.title === undefined || valueArg.title === null
1459
+ || isBoundedString(valueArg.title, 4096, true))
1460
+ && (valueArg.archived === undefined || typeof valueArg.archived === 'boolean');
1461
+
1462
+ const isScopeAndSession = (valueArg: Record<string, unknown>): boolean =>
1463
+ isIdentifier(valueArg.scopeId) && isIdentifier(valueArg.sessionId);
1464
+
1465
+ const isAffectedWorkspace = (valueArg: unknown): boolean => isFlexPlainObject(valueArg)
1466
+ && hasFlexExactKeys(valueArg, ['id', 'label'])
1467
+ && isBoundedString(valueArg.id, 512)
1468
+ && valueArg.id.trim().length > 0
1469
+ && isBoundedString(valueArg.label, 2048)
1470
+ && valueArg.label.trim().length > 0;
1471
+
1472
+ const isReversionOutcome = (
1473
+ valueArg: unknown,
1474
+ ): valueArg is TFlexTurnReversionFinalizedOutcome => {
1475
+ if (!isFlexPlainObject(valueArg) || !isJsonValue(valueArg.reference)) return false;
1476
+ if (valueArg.disposition === 'no-change') {
1477
+ return hasFlexExactKeys(valueArg, ['disposition', 'reference'], ['affectedWorkspaces'])
1478
+ && (valueArg.affectedWorkspaces === undefined
1479
+ || (Array.isArray(valueArg.affectedWorkspaces) && valueArg.affectedWorkspaces.length === 0));
1480
+ }
1481
+ if (valueArg.disposition === 'revertible') {
1482
+ return hasFlexExactKeys(valueArg, ['disposition', 'reference', 'affectedWorkspaces'])
1483
+ && Array.isArray(valueArg.affectedWorkspaces)
1484
+ && valueArg.affectedWorkspaces.length >= 1
1485
+ && valueArg.affectedWorkspaces.length <= 64
1486
+ && valueArg.affectedWorkspaces.every(isAffectedWorkspace)
1487
+ && new Set(valueArg.affectedWorkspaces.map((entry) => (
1488
+ entry as { id: string }
1489
+ ).id)).size === valueArg.affectedWorkspaces.length;
1490
+ }
1491
+ return valueArg.disposition === 'nonrevertible'
1492
+ && hasFlexExactKeys(
1493
+ valueArg,
1494
+ ['disposition', 'reference', 'reasonCode'],
1495
+ ['affectedWorkspaces'],
1496
+ )
1497
+ && isBoundedString(valueArg.reasonCode, 128)
1498
+ && /^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(valueArg.reasonCode)
1499
+ && (valueArg.affectedWorkspaces === undefined || (
1500
+ Array.isArray(valueArg.affectedWorkspaces)
1501
+ && valueArg.affectedWorkspaces.length <= 64
1502
+ && valueArg.affectedWorkspaces.every(isAffectedWorkspace)
1503
+ ));
1504
+ };
1505
+
1506
+ const isSlashDescriptor = (valueArg: unknown): valueArg is TFlexSlashCommandDescriptor => (
1507
+ isFlexPlainObject(valueArg)
1508
+ && hasFlexExactKeys(
1509
+ valueArg,
1510
+ ['name', 'description', 'kind', 'hints', 'available', 'workspaceReversion'],
1511
+ ['unavailableReason'],
1512
+ )
1513
+ && typeof valueArg.name === 'string'
1514
+ && /^[a-z][a-z0-9-]{0,63}$/u.test(valueArg.name)
1515
+ && isBoundedString(valueArg.description, 2048, true)
1516
+ && ['builtin', 'template', 'handler'].includes(String(valueArg.kind))
1517
+ && Array.isArray(valueArg.hints)
1518
+ && valueArg.hints.length <= 64
1519
+ && valueArg.hints.every((hint) => isBoundedString(hint, 128))
1520
+ && typeof valueArg.available === 'boolean'
1521
+ && (valueArg.unavailableReason === undefined
1522
+ || isBoundedString(valueArg.unavailableReason, 2048))
1523
+ && ['supported', 'unsupported', 'not-applicable'].includes(
1524
+ String(valueArg.workspaceReversion),
1525
+ )
1526
+ );
1527
+
1528
+ const isSlashInvocation = (valueArg: Record<string, unknown>): boolean => (
1529
+ isBoundedString(valueArg.input, 768 * 1024)
1530
+ && typeof valueArg.name === 'string'
1531
+ && /^[a-z][a-z0-9-]{0,63}$/u.test(valueArg.name)
1532
+ && isBoundedString(valueArg.rawArguments, 768 * 1024, true)
1533
+ && Array.isArray(valueArg.arguments)
1534
+ && valueArg.arguments.length <= 4096
1535
+ && valueArg.arguments.every((entry) => isBoundedString(entry, 768 * 1024, true))
1536
+ );
1537
+
1538
+ const isSlashExecutionResult = (valueArg: unknown): valueArg is TFlexSlashExecutionResult => {
1539
+ if (!isFlexPlainObject(valueArg) || typeof valueArg.type !== 'string') return false;
1540
+ if (valueArg.type === 'not-command') return hasFlexExactKeys(valueArg, ['type']);
1541
+ if (valueArg.type === 'malformed') {
1542
+ return hasFlexExactKeys(valueArg, ['type', 'reason'])
1543
+ && isBoundedString(valueArg.reason, 2048);
1544
+ }
1545
+ if (valueArg.type === 'unknown') {
1546
+ return hasFlexExactKeys(valueArg, ['type', 'input', 'name', 'rawArguments', 'arguments'])
1547
+ && isSlashInvocation(valueArg);
1548
+ }
1549
+ if (valueArg.type === 'operation') {
1550
+ return hasFlexExactKeys(valueArg, ['type', 'name'])
1551
+ && typeof valueArg.name === 'string'
1552
+ && /^[a-z][a-z0-9-]{0,63}$/u.test(valueArg.name);
1553
+ }
1554
+ if (valueArg.type === 'handler-result') {
1555
+ return hasFlexExactKeys(valueArg, ['type', 'name', 'result'])
1556
+ && typeof valueArg.name === 'string'
1557
+ && /^[a-z][a-z0-9-]{0,63}$/u.test(valueArg.name)
1558
+ && isJsonValue(valueArg.result);
1559
+ }
1560
+ return valueArg.type === 'prompt-admission'
1561
+ && hasFlexExactKeys(valueArg, ['type', 'name', 'admission'])
1562
+ && typeof valueArg.name === 'string'
1563
+ && /^[a-z][a-z0-9-]{0,63}$/u.test(valueArg.name)
1564
+ && isFlexPlainObject(valueArg.admission)
1565
+ && hasFlexExactKeys(valueArg.admission, ['queueId', 'runId'])
1566
+ && isIdentifier(valueArg.admission.queueId)
1567
+ && isIdentifier(valueArg.admission.runId);
1568
+ };
1569
+
1570
+ const isSessionReversionInfo = (valueArg: unknown): valueArg is TFlexSessionReversionInfo => (
1571
+ isFlexPlainObject(valueArg)
1572
+ && hasFlexExactKeys(valueArg, ['undoAvailable', 'redoAvailable', 'groups'])
1573
+ && typeof valueArg.undoAvailable === 'boolean'
1574
+ && typeof valueArg.redoAvailable === 'boolean'
1575
+ && Array.isArray(valueArg.groups)
1576
+ && valueArg.groups.length <= 3000
1577
+ && valueArg.groups.every((entry) => isFlexPlainObject(entry)
1578
+ && hasFlexExactKeys(entry, [
1579
+ 'runId', 'kind', 'visibility', 'affectedWorkspaces', 'affectedWorkspacesTruncated',
1580
+ ])
1581
+ && isIdentifier(entry.runId)
1582
+ && ['candidate', 'barrier', 'no-change'].includes(String(entry.kind))
1583
+ && ['visible', 'hidden'].includes(String(entry.visibility))
1584
+ && Array.isArray(entry.affectedWorkspaces)
1585
+ && entry.affectedWorkspaces.length <= 64
1586
+ && entry.affectedWorkspaces.every(isAffectedWorkspace)
1587
+ && typeof entry.affectedWorkspacesTruncated === 'boolean')
1588
+ );
1589
+
1590
+ export const isFlexRequestPayload = <TMethod extends TFlexRequestMethod>(
1591
+ methodArg: TMethod,
1592
+ valueArg: unknown,
1593
+ ): valueArg is TFlexRequest<TMethod> => {
1594
+ if (!isFlexPlainObject(valueArg)) return false;
1595
+ switch (methodArg) {
1596
+ case 'service.status':
1597
+ case 'provider.list':
1598
+ case 'provider.connection.list':
1599
+ return hasFlexExactKeys(valueArg, []);
1600
+ case 'project.replaceAll':
1601
+ return hasFlexExactKeys(valueArg, ['projects'])
1602
+ && Array.isArray(valueArg.projects)
1603
+ && valueArg.projects.length <= controllerProjectLimit
1604
+ && valueArg.projects.every(isFlexProjectRecord)
1605
+ && new Set(valueArg.projects.map((project) => project.projectId)).size
1606
+ === valueArg.projects.length;
1607
+ case 'project.register':
1608
+ case 'project.remove':
1609
+ return hasFlexExactKeys(valueArg, ['project'])
1610
+ && isFlexIdentityBoundProjectRecord(valueArg.project);
1611
+ case 'intelligence.start':
1612
+ return hasFlexExactKeys(valueArg, [
1613
+ 'scopeId', 'sourceSessionId', 'question', 'scratchpad', 'model', 'capabilityToken',
1614
+ ])
1615
+ && isIdentifier(valueArg.scopeId)
1616
+ && isFlexIntelligenceSessionId(valueArg.sourceSessionId)
1617
+ && isBoundedString(valueArg.question, 32 * 1024)
1618
+ && valueArg.question.length <= 8_192
1619
+ && String(valueArg.question).trim().length > 0
1620
+ && isBoundedString(valueArg.scratchpad, 128 * 1024, true)
1621
+ && valueArg.scratchpad.length <= 32_768
1622
+ && isModelChoice(valueArg.model)
1623
+ && valueArg.model.modelId === 'gpt-5.6-luna'
1624
+ && valueArg.model.variant === undefined
1625
+ && isIntelligenceCapabilityToken(valueArg.capabilityToken);
1626
+ case 'intelligence.get':
1627
+ return hasFlexExactKeys(valueArg, ['scopeId', 'jobId', 'capabilityToken'])
1628
+ && isIdentifier(valueArg.scopeId)
1629
+ && isQualifiedId(valueArg.jobId)
1630
+ && isIntelligenceCapabilityToken(valueArg.capabilityToken);
1631
+ case 'intelligence.cancel':
1632
+ return hasFlexExactKeys(valueArg, ['scopeId', 'capabilityToken'])
1633
+ && isIdentifier(valueArg.scopeId)
1634
+ && isIntelligenceCapabilityToken(valueArg.capabilityToken);
1635
+ case 'session.list':
1636
+ return hasFlexExactKeys(valueArg, ['scopeId'], ['offset', 'limit'])
1637
+ && isIdentifier(valueArg.scopeId)
1638
+ && (valueArg.offset === undefined || isNonNegativeInteger(valueArg.offset))
1639
+ && (valueArg.limit === undefined
1640
+ || (Number.isSafeInteger(valueArg.limit) && Number(valueArg.limit) >= 1
1641
+ && Number(valueArg.limit) <= 50));
1642
+ case 'session.create':
1643
+ return hasFlexExactKeys(valueArg, ['scopeId'], ['options'])
1644
+ && isIdentifier(valueArg.scopeId)
1645
+ && (valueArg.options === undefined || isCreateSessionOptions(valueArg.options));
1646
+ case 'session.get':
1647
+ case 'session.delete':
1648
+ case 'session.reversion.info':
1649
+ case 'slash.list':
1650
+ case 'prompt.list':
1651
+ return hasFlexExactKeys(valueArg, ['scopeId', 'sessionId'])
1652
+ && isScopeAndSession(valueArg);
1653
+ case 'session.compact':
1654
+ return hasFlexExactKeys(valueArg, ['scopeId', 'sessionId'], ['model'])
1655
+ && isScopeAndSession(valueArg)
1656
+ && (valueArg.model === undefined || isModelChoice(valueArg.model));
1657
+ case 'slash.execute':
1658
+ return hasFlexExactKeys(valueArg, ['scopeId', 'sessionId', 'input'], ['model'])
1659
+ && isScopeAndSession(valueArg)
1660
+ && isBoundedString(valueArg.input, 768 * 1024)
1661
+ && (valueArg.model === undefined || isModelChoice(valueArg.model));
1662
+ case 'session.update':
1663
+ return hasFlexExactKeys(valueArg, ['scopeId', 'sessionId', 'options'])
1664
+ && isScopeAndSession(valueArg)
1665
+ && isUpdateSessionOptions(valueArg.options);
1666
+ case 'message.page':
1667
+ return hasFlexExactKeys(valueArg, ['scopeId', 'sessionId'], ['limit', 'before'])
1668
+ && isScopeAndSession(valueArg)
1669
+ && (valueArg.limit === undefined
1670
+ || (Number.isSafeInteger(valueArg.limit) && Number(valueArg.limit) >= 1
1671
+ && Number(valueArg.limit) <= 50))
1672
+ && (valueArg.before === undefined || isBoundedString(valueArg.before, 4096));
1673
+ case 'message.get':
1674
+ return hasFlexExactKeys(valueArg, ['scopeId', 'sessionId', 'messageId'])
1675
+ && isScopeAndSession(valueArg) && isIdentifier(valueArg.messageId);
1676
+ case 'prompt.start':
1677
+ return hasFlexExactKeys(
1678
+ valueArg,
1679
+ ['scopeId', 'sessionId', 'prompt'],
1680
+ ['options', 'model', 'upload'],
1681
+ )
1682
+ && isScopeAndSession(valueArg)
1683
+ && isPrompt(valueArg.prompt)
1684
+ && (valueArg.options === undefined || isPromptOptions(valueArg.options))
1685
+ && (valueArg.model === undefined || isModelChoice(valueArg.model))
1686
+ && (valueArg.upload === undefined || (
1687
+ isFlexPlainObject(valueArg.upload)
1688
+ && hasFlexExactKeys(valueArg.upload, ['operationId', 'directory'])
1689
+ && isIdentifier(valueArg.upload.operationId)
1690
+ && isBoundedString(valueArg.upload.directory, 4096)
1691
+ && plugins.path.isAbsolute(valueArg.upload.directory)
1692
+ ));
1693
+ case 'prompt.get':
1694
+ case 'prompt.cancel':
1695
+ return hasFlexExactKeys(valueArg, ['scopeId', 'sessionId', 'queueId'])
1696
+ && isScopeAndSession(valueArg) && isIdentifier(valueArg.queueId);
1697
+ case 'prompt.acknowledge':
1698
+ return hasFlexExactKeys(valueArg, ['scopeId', 'sessionId', 'queueId', 'runId'])
1699
+ && isScopeAndSession(valueArg)
1700
+ && isIdentifier(valueArg.queueId)
1701
+ && isIdentifier(valueArg.runId);
1702
+ case 'permission.list':
1703
+ return hasFlexExactKeys(valueArg, ['scopeId'], ['sessionId', 'familyRootSessionId'])
1704
+ && isIdentifier(valueArg.scopeId)
1705
+ && (valueArg.sessionId === undefined || isIdentifier(valueArg.sessionId))
1706
+ && (valueArg.familyRootSessionId === undefined
1707
+ || isIdentifier(valueArg.familyRootSessionId))
1708
+ && !(valueArg.sessionId !== undefined && valueArg.familyRootSessionId !== undefined);
1709
+ case 'permission.respond':
1710
+ return hasFlexExactKeys(
1711
+ valueArg,
1712
+ ['scopeId', 'sessionId', 'permissionId', 'decision'],
1713
+ )
1714
+ && isScopeAndSession(valueArg)
1715
+ && isIdentifier(valueArg.permissionId)
1716
+ && ['once', 'always', 'reject'].includes(String(valueArg.decision));
1717
+ case 'provider.login.begin':
1718
+ return hasFlexExactKeys(valueArg, ['providerId']) && valueArg.providerId === 'openai';
1719
+ case 'provider.login.status':
1720
+ case 'provider.login.cancel':
1721
+ return hasFlexExactKeys(valueArg, ['loginId']) && isQualifiedId(valueArg.loginId);
1722
+ case 'provider.connection.logout.prepare':
1723
+ case 'provider.connection.logout':
1724
+ case 'provider.connection.opencode-auth.get':
1725
+ case 'provider.connection.ratelimits.get':
1726
+ case 'provider.models.refresh':
1727
+ return hasFlexExactKeys(valueArg, ['providerConnectionId'])
1728
+ && isQualifiedId(valueArg.providerConnectionId);
1729
+ case 'model.choice.get':
1730
+ return hasFlexExactKeys(valueArg, ['scopeId']) && isIdentifier(valueArg.scopeId);
1731
+ case 'model.choice.set':
1732
+ return hasFlexExactKeys(valueArg, ['scopeId', 'choice'])
1733
+ && isIdentifier(valueArg.scopeId) && isModelChoice(valueArg.choice);
1734
+ case 'model.choice.validate':
1735
+ return hasFlexExactKeys(valueArg, ['choice']) && isModelChoice(valueArg.choice);
1736
+ default:
1737
+ return false;
1738
+ }
1739
+ };
1740
+
1741
+ export const isFlexHostRequestPayload = <TMethod extends TFlexHostRequestMethod>(
1742
+ methodArg: TMethod,
1743
+ valueArg: unknown,
1744
+ ): valueArg is TFlexHostRequest<TMethod> => {
1745
+ if (!isFlexPlainObject(valueArg)) return false;
1746
+ const reversionBase = (optionalKeysArg: readonly string[] = []) => (
1747
+ hasFlexExactKeys(
1748
+ valueArg,
1749
+ ['scopeId', 'storageKey', 'sessionId', 'runId', 'captureId'],
1750
+ optionalKeysArg,
1751
+ )
1752
+ && isIdentifier(valueArg.scopeId)
1753
+ && isIdentifier(valueArg.storageKey)
1754
+ && isIdentifier(valueArg.sessionId)
1755
+ && isIdentifier(valueArg.runId)
1756
+ && isIdentifier(valueArg.captureId)
1757
+ );
1758
+ if (
1759
+ methodArg === 'reversion.prepare'
1760
+ || methodArg === 'reversion.inspect-capture'
1761
+ || methodArg === 'reversion.finalize'
1762
+ ) return reversionBase();
1763
+ if (methodArg === 'reversion.inspect-apply' || methodArg === 'reversion.apply') {
1764
+ return reversionBase(['reference', 'operationId', 'direction'])
1765
+ && Object.hasOwn(valueArg, 'reference')
1766
+ && isJsonValue(valueArg.reference)
1767
+ && Buffer.byteLength(JSON.stringify(valueArg.reference), 'utf8') <= 256 * 1024
1768
+ && isIdentifier(valueArg.operationId)
1769
+ && (valueArg.direction === 'undo' || valueArg.direction === 'redo');
1770
+ }
1771
+ if (methodArg === 'reversion.release') {
1772
+ return reversionBase(['reference'])
1773
+ && Object.hasOwn(valueArg, 'reference')
1774
+ && isJsonValue(valueArg.reference)
1775
+ && Buffer.byteLength(JSON.stringify(valueArg.reference), 'utf8') <= 256 * 1024;
1776
+ }
1777
+ if (methodArg === 'git.worktree.create' || methodArg === 'git.worktree.list') {
1778
+ return hasFlexExactKeys(valueArg, ['scopeId', 'sessionId'])
1779
+ && isIdentifier(valueArg.scopeId)
1780
+ && isIdentifier(valueArg.sessionId);
1781
+ }
1782
+ if (methodArg === 'git.worktree.remove') {
1783
+ const compensating = Object.hasOwn(valueArg, 'compensate');
1784
+ return hasFlexExactKeys(
1785
+ valueArg,
1786
+ ['scopeId', 'sessionId', 'worktreeId', ...(compensating ? ['compensate'] : [])],
1787
+ )
1788
+ && isIdentifier(valueArg.scopeId)
1789
+ && isIdentifier(valueArg.sessionId)
1790
+ && isIdentifier(valueArg.worktreeId)
1791
+ && (!compensating || valueArg.compensate === true);
1792
+ }
1793
+ if (methodArg === 'crossharness.chats.list') {
1794
+ return hasFlexExactKeys(valueArg, ['capabilityToken', 'limit'])
1795
+ && isIntelligenceCapabilityToken(valueArg.capabilityToken)
1796
+ && Number.isSafeInteger(valueArg.limit)
1797
+ && Number(valueArg.limit) >= 1
1798
+ && Number(valueArg.limit) <= 50;
1799
+ }
1800
+ if (methodArg === 'crossharness.chat.read') {
1801
+ return hasFlexExactKeys(
1802
+ valueArg,
1803
+ ['capabilityToken', 'sessionId', 'limit', 'maxChars'],
1804
+ )
1805
+ && isIntelligenceCapabilityToken(valueArg.capabilityToken)
1806
+ && isFlexIntelligenceSessionId(valueArg.sessionId)
1807
+ && Number.isSafeInteger(valueArg.limit)
1808
+ && Number(valueArg.limit) >= 1
1809
+ && Number(valueArg.limit) <= 50
1810
+ && Number.isSafeInteger(valueArg.maxChars)
1811
+ && Number(valueArg.maxChars) >= 128
1812
+ && Number(valueArg.maxChars) <= 4096;
1813
+ }
1814
+ if (methodArg === 'project-management.load') {
1815
+ return hasFlexExactKeys(valueArg, [
1816
+ 'storageKey',
1817
+ 'sessionId',
1818
+ 'sessionGenerationId',
1819
+ 'sessionGenerationSequence',
1820
+ ])
1821
+ && isIdentifier(valueArg.storageKey)
1822
+ && isIdentifier(valueArg.sessionId)
1823
+ && isFlexSessionGeneration(valueArg);
1824
+ }
1825
+ if (methodArg === 'project-management.save') {
1826
+ return hasFlexExactKeys(valueArg, [
1827
+ 'storageKey',
1828
+ 'sessionId',
1829
+ 'snapshot',
1830
+ 'expectedRevision',
1831
+ 'writeContext',
1832
+ ])
1833
+ && isIdentifier(valueArg.storageKey)
1834
+ && isIdentifier(valueArg.sessionId)
1835
+ && isFlexProjectManagementSnapshot(valueArg.snapshot)
1836
+ && isNonNegativeInteger(valueArg.expectedRevision)
1837
+ && valueArg.snapshot.revision === Number(valueArg.expectedRevision) + 1
1838
+ && isFlexProjectManagementWriteContext(valueArg.writeContext);
1839
+ }
1840
+ if (methodArg === 'project-management.tombstone') {
1841
+ return hasFlexExactKeys(valueArg, [
1842
+ 'storageKey',
1843
+ 'sessionId',
1844
+ 'tombstone',
1845
+ 'expectedRevision',
1846
+ ])
1847
+ && isIdentifier(valueArg.storageKey)
1848
+ && isIdentifier(valueArg.sessionId)
1849
+ && isFlexProjectManagementTombstone(valueArg.tombstone)
1850
+ && isNonNegativeInteger(valueArg.expectedRevision)
1851
+ && valueArg.tombstone.revision === Number(valueArg.expectedRevision) + 1;
1852
+ }
1853
+ if (methodArg === 'project-management.purge') {
1854
+ return hasFlexExactKeys(valueArg, ['storageKey']) && isIdentifier(valueArg.storageKey);
1855
+ }
1856
+ if (methodArg === 'browser.channel.close') {
1857
+ return hasFlexExactKeys(valueArg, ['channelId']) && isChannelId(valueArg.channelId);
1858
+ }
1859
+ const common = hasFlexExactKeys(
1860
+ valueArg,
1861
+ methodArg === 'browser.resources.resolve'
1862
+ ? ['scopeId', 'sessionId', 'runId']
1863
+ : ['scopeId', 'sessionId', 'runId', 'resourceId', 'attachmentRevision', 'channelId'],
1864
+ )
1865
+ && isBoundedString(valueArg.scopeId, 128)
1866
+ && identifierPattern.test(String(valueArg.scopeId))
1867
+ && isBoundedString(valueArg.sessionId, 128)
1868
+ && identifierPattern.test(String(valueArg.sessionId))
1869
+ && isBoundedString(valueArg.runId, 512)
1870
+ && identifierPattern.test(String(valueArg.runId));
1871
+ if (!common || methodArg === 'browser.resources.resolve') return common;
1872
+ return isBoundedString(valueArg.resourceId, 256)
1873
+ && Buffer.byteLength(String(valueArg.resourceId), 'utf8') >= 16
1874
+ && identifierPattern.test(String(valueArg.resourceId))
1875
+ && isNonNegativeInteger(valueArg.attachmentRevision)
1876
+ && isChannelId(valueArg.channelId);
1877
+ };
1878
+
1879
+ const isGitWorktreeDescriptor = (
1880
+ valueArg: unknown,
1881
+ ): valueArg is IFlexGitWorktreeDescriptor => isFlexPlainObject(valueArg)
1882
+ && hasFlexExactKeys(valueArg, [
1883
+ 'worktreeId', 'projectId', 'sessionId', 'path', 'repositoryId', 'createdAt',
1884
+ ])
1885
+ && isIdentifier(valueArg.worktreeId)
1886
+ && typeof valueArg.projectId === 'string'
1887
+ && projectIdPattern.test(valueArg.projectId)
1888
+ && isIdentifier(valueArg.sessionId)
1889
+ && isBoundedString(valueArg.path, 4096)
1890
+ && !valueArg.path.includes('\0')
1891
+ && plugins.path.isAbsolute(valueArg.path)
1892
+ && plugins.path.normalize(valueArg.path) === valueArg.path
1893
+ && isIdentifier(valueArg.repositoryId)
1894
+ && isIsoDate(valueArg.createdAt);
1895
+
1896
+ const isFlexBrowserResourceDescriptor = (
1897
+ valueArg: unknown,
1898
+ ): valueArg is IFlexBrowserResourceDescriptor => isFlexPlainObject(valueArg)
1899
+ && hasFlexExactKeys(valueArg, ['resourceId', 'attachmentRevision'])
1900
+ && isBoundedString(valueArg.resourceId, 256)
1901
+ && Buffer.byteLength(valueArg.resourceId, 'utf8') >= 16
1902
+ && identifierPattern.test(valueArg.resourceId)
1903
+ && isNonNegativeInteger(valueArg.attachmentRevision);
1904
+
1905
+ const isFlexBrowserChannelBinding = (
1906
+ valueArg: unknown,
1907
+ ): valueArg is IFlexBrowserChannelBinding => isFlexPlainObject(valueArg)
1908
+ && hasFlexExactKeys(valueArg, [
1909
+ 'projectId',
1910
+ 'browserResourceId',
1911
+ 'attachmentAuthorityId',
1912
+ 'attachmentRevision',
1913
+ 'actorId',
1914
+ 'peerId',
1915
+ 'role',
1916
+ 'source',
1917
+ 'scopeId',
1918
+ 'channelId',
1919
+ 'runId',
1920
+ 'sessionId',
1921
+ ])
1922
+ && isBoundedString(valueArg.projectId, 128)
1923
+ && isBoundedString(valueArg.browserResourceId, 256)
1924
+ && Buffer.byteLength(valueArg.browserResourceId, 'utf8') >= 16
1925
+ && isBoundedString(valueArg.attachmentAuthorityId, 256)
1926
+ && isNonNegativeInteger(valueArg.attachmentRevision)
1927
+ && isBoundedString(valueArg.actorId, 256)
1928
+ && isBoundedString(valueArg.peerId, 256)
1929
+ && valueArg.role === 'agent'
1930
+ && valueArg.source === 'flex'
1931
+ && isBoundedString(valueArg.scopeId, 128)
1932
+ && isChannelId(valueArg.channelId)
1933
+ && isBoundedString(valueArg.runId, 512)
1934
+ && identifierPattern.test(valueArg.runId)
1935
+ && isFlexPlainObject(valueArg.sessionId)
1936
+ && hasFlexExactKeys(valueArg.sessionId, ['harnessId', 'nativeId'])
1937
+ && valueArg.sessionId.harnessId === 'flex'
1938
+ && isBoundedString(valueArg.sessionId.nativeId, 128);
1939
+
1940
+ export const isFlexHostResponseResult = <TMethod extends TFlexHostRequestMethod>(
1941
+ methodArg: TMethod,
1942
+ valueArg: unknown,
1943
+ ): valueArg is TFlexHostResponse<TMethod> => {
1944
+ if (!isFlexPlainObject(valueArg)) return false;
1945
+ if (methodArg === 'reversion.prepare') {
1946
+ return hasFlexExactKeys(valueArg, ['prepared']) && valueArg.prepared === true;
1947
+ }
1948
+ if (methodArg === 'reversion.inspect-capture') {
1949
+ if (valueArg.status === 'finalized') {
1950
+ return hasFlexExactKeys(valueArg, ['status', 'outcome'])
1951
+ && isReversionOutcome(valueArg.outcome);
1952
+ }
1953
+ return hasFlexExactKeys(valueArg, ['status'])
1954
+ && ['missing', 'prepared', 'unknown'].includes(String(valueArg.status));
1955
+ }
1956
+ if (methodArg === 'reversion.finalize') return isReversionOutcome(valueArg);
1957
+ if (methodArg === 'reversion.inspect-apply') {
1958
+ return hasFlexExactKeys(valueArg, ['status'])
1959
+ && ['not-applied', 'applied', 'unknown'].includes(String(valueArg.status));
1960
+ }
1961
+ if (methodArg === 'reversion.apply') {
1962
+ return hasFlexExactKeys(valueArg, ['applied']) && valueArg.applied === true;
1963
+ }
1964
+ if (methodArg === 'reversion.release') {
1965
+ return hasFlexExactKeys(valueArg, ['released']) && valueArg.released === true;
1966
+ }
1967
+ if (methodArg === 'git.worktree.create') {
1968
+ return hasFlexExactKeys(valueArg, ['worktree'])
1969
+ && isGitWorktreeDescriptor(valueArg.worktree);
1970
+ }
1971
+ if (methodArg === 'git.worktree.list') {
1972
+ return hasFlexExactKeys(valueArg, ['worktrees'])
1973
+ && Array.isArray(valueArg.worktrees)
1974
+ && valueArg.worktrees.length <= 128
1975
+ && valueArg.worktrees.every(isGitWorktreeDescriptor);
1976
+ }
1977
+ if (methodArg === 'git.worktree.remove') {
1978
+ return hasFlexExactKeys(valueArg, ['removed']) && valueArg.removed === true;
1979
+ }
1980
+ if (methodArg === 'crossharness.chats.list') {
1981
+ return hasFlexExactKeys(valueArg, ['chats'])
1982
+ && Array.isArray(valueArg.chats)
1983
+ && valueArg.chats.length <= 50
1984
+ && valueArg.chats.every(isFlexIntelligenceChatSummary)
1985
+ && Buffer.byteLength(JSON.stringify(valueArg), 'utf8') <= 256 * 1024;
1986
+ }
1987
+ if (methodArg === 'crossharness.chat.read') {
1988
+ return hasFlexExactKeys(valueArg, ['transcript'])
1989
+ && isFlexIntelligenceChatTranscript(valueArg.transcript);
1990
+ }
1991
+ if (methodArg === 'project-management.load') {
1992
+ return hasFlexExactKeys(valueArg, ['record'])
1993
+ && (valueArg.record === null || isFlexProjectManagementRecord(valueArg.record));
1994
+ }
1995
+ if (
1996
+ methodArg === 'project-management.save'
1997
+ || methodArg === 'project-management.tombstone'
1998
+ ) return isFlexProjectManagementCommitResult(valueArg);
1999
+ if (methodArg === 'project-management.purge') {
2000
+ return hasFlexExactKeys(valueArg, ['purged']) && valueArg.purged === true;
2001
+ }
2002
+ if (methodArg === 'browser.resources.resolve') {
2003
+ return hasFlexExactKeys(valueArg, ['resources'])
2004
+ && Array.isArray(valueArg.resources)
2005
+ && valueArg.resources.length <= flexIpcMaximumBrowserChannels
2006
+ && valueArg.resources.every(isFlexBrowserResourceDescriptor)
2007
+ && new Set(valueArg.resources.map((entry) => entry.resourceId)).size
2008
+ === valueArg.resources.length;
2009
+ }
2010
+ if (methodArg === 'browser.channel.open') {
2011
+ return hasFlexExactKeys(valueArg, ['binding', 'capabilityToken'])
2012
+ && isFlexBrowserChannelBinding(valueArg.binding)
2013
+ && isBoundedString(valueArg.capabilityToken, 512)
2014
+ && Buffer.byteLength(valueArg.capabilityToken, 'utf8') >= 16;
2015
+ }
2016
+ return hasFlexExactKeys(valueArg, ['closed']) && valueArg.closed === true;
2017
+ };
2018
+
2019
+ const isPublicProviderModel = (valueArg: unknown): valueArg is IFlexPublicProviderModel => {
2020
+ if (
2021
+ !isFlexPlainObject(valueArg)
2022
+ || !hasFlexExactKeys(
2023
+ valueArg,
2024
+ [
2025
+ 'providerId', 'modelId', 'displayName', 'description', 'hidden',
2026
+ 'reasoningEfforts', 'defaultReasoningEffort', 'inputModalities',
2027
+ 'supportsPersonality', 'serviceTiers', 'isDefault',
2028
+ ],
2029
+ ['defaultServiceTier'],
2030
+ )
2031
+ || valueArg.providerId !== 'openai'
2032
+ || !isBoundedString(valueArg.modelId, 256)
2033
+ || !isBoundedString(valueArg.displayName, 512)
2034
+ || !isBoundedString(valueArg.description, 2048, true)
2035
+ || typeof valueArg.hidden !== 'boolean'
2036
+ || !Array.isArray(valueArg.reasoningEfforts)
2037
+ || valueArg.reasoningEfforts.length > 16
2038
+ || !valueArg.reasoningEfforts.every((entry) => isFlexPlainObject(entry)
2039
+ && hasFlexExactKeys(entry, ['effort', 'description'])
2040
+ && isBoundedString(entry.effort, 128)
2041
+ && isBoundedString(entry.description, 1024, true))
2042
+ || !isBoundedString(valueArg.defaultReasoningEffort, 128)
2043
+ || !Array.isArray(valueArg.inputModalities)
2044
+ || valueArg.inputModalities.length > 3
2045
+ || !valueArg.inputModalities.every((entry) => ['text', 'image', 'audio'].includes(String(entry)))
2046
+ || typeof valueArg.supportsPersonality !== 'boolean'
2047
+ || !Array.isArray(valueArg.serviceTiers)
2048
+ || valueArg.serviceTiers.length > 16
2049
+ || !valueArg.serviceTiers.every((entry) => isFlexPlainObject(entry)
2050
+ && hasFlexExactKeys(entry, ['id', 'name', 'description'])
2051
+ && isBoundedString(entry.id, 128)
2052
+ && isBoundedString(entry.name, 256)
2053
+ && isBoundedString(entry.description, 1024, true))
2054
+ || (valueArg.defaultServiceTier !== undefined
2055
+ && !isBoundedString(valueArg.defaultServiceTier, 128))
2056
+ || typeof valueArg.isDefault !== 'boolean'
2057
+ ) return false;
2058
+ return true;
2059
+ };
2060
+
2061
+ const isRateLimitWindow = (valueArg: unknown): valueArg is IFlexPublicProviderRateLimitWindow => (
2062
+ isFlexPlainObject(valueArg)
2063
+ && hasFlexExactKeys(valueArg, [
2064
+ 'usedPercent', 'limitWindowSeconds', 'resetAfterSeconds', 'resetsAt',
2065
+ ])
2066
+ && isNonNegativeInteger(valueArg.usedPercent)
2067
+ && valueArg.usedPercent <= 100
2068
+ && isNonNegativeInteger(valueArg.limitWindowSeconds)
2069
+ && valueArg.limitWindowSeconds >= 1
2070
+ && isNonNegativeInteger(valueArg.resetAfterSeconds)
2071
+ && isNonNegativeInteger(valueArg.resetsAt)
2072
+ );
2073
+
2074
+ const isRateLimitDetails = (valueArg: unknown): valueArg is IFlexPublicProviderRateLimitDetails => (
2075
+ isFlexPlainObject(valueArg)
2076
+ && hasFlexExactKeys(valueArg, ['allowed', 'limitReached'], ['primaryWindow', 'secondaryWindow'])
2077
+ && typeof valueArg.allowed === 'boolean'
2078
+ && typeof valueArg.limitReached === 'boolean'
2079
+ && (valueArg.primaryWindow === undefined || isRateLimitWindow(valueArg.primaryWindow))
2080
+ && (valueArg.secondaryWindow === undefined || isRateLimitWindow(valueArg.secondaryWindow))
2081
+ );
2082
+
2083
+ const isAccountRateLimits = (
2084
+ valueArg: unknown,
2085
+ ): valueArg is IFlexPublicProviderAccountRateLimits => isFlexPlainObject(valueArg)
2086
+ && hasFlexExactKeys(
2087
+ valueArg,
2088
+ ['providerId', 'plan', 'observedAt', 'additionalRateLimits'],
2089
+ ['rateLimit', 'rateLimitReachedType', 'rateLimitResetCreditsAvailableCount'],
2090
+ )
2091
+ && valueArg.providerId === 'openai'
2092
+ && isBoundedString(valueArg.plan, 256)
2093
+ && isIsoDate(valueArg.observedAt)
2094
+ && (valueArg.rateLimit === undefined || isRateLimitDetails(valueArg.rateLimit))
2095
+ && Array.isArray(valueArg.additionalRateLimits)
2096
+ && valueArg.additionalRateLimits.length <= 64
2097
+ && valueArg.additionalRateLimits.every((entry) => isFlexPlainObject(entry)
2098
+ && hasFlexExactKeys(entry, ['limitName', 'meteredFeature'], ['rateLimit'])
2099
+ && isBoundedString(entry.limitName, 256)
2100
+ && isBoundedString(entry.meteredFeature, 256)
2101
+ && (entry.rateLimit === undefined || isRateLimitDetails(entry.rateLimit)))
2102
+ && (valueArg.rateLimitReachedType === undefined
2103
+ || isBoundedString(valueArg.rateLimitReachedType, 256))
2104
+ && (valueArg.rateLimitResetCreditsAvailableCount === undefined
2105
+ || isNonNegativeInteger(valueArg.rateLimitResetCreditsAvailableCount));
2106
+
2107
+ export const isFlexResponseResult = <TMethod extends TFlexRequestMethod>(
2108
+ methodArg: TMethod,
2109
+ valueArg: unknown,
2110
+ ): valueArg is TFlexResponse<TMethod> => {
2111
+ switch (methodArg) {
2112
+ case 'service.status':
2113
+ return isFlexServiceStatus(valueArg);
2114
+ case 'project.replaceAll':
2115
+ return isFlexPlainObject(valueArg)
2116
+ && hasFlexExactKeys(valueArg, ['replaced', 'retired'])
2117
+ && valueArg.replaced === true
2118
+ && isNonNegativeInteger(valueArg.retired)
2119
+ && valueArg.retired <= controllerProjectLimit;
2120
+ case 'project.register':
2121
+ return isFlexPlainObject(valueArg)
2122
+ && hasFlexExactKeys(valueArg, ['registered'])
2123
+ && valueArg.registered === true;
2124
+ case 'project.remove':
2125
+ return isFlexPlainObject(valueArg)
2126
+ && hasFlexExactKeys(valueArg, ['removed'])
2127
+ && valueArg.removed === true;
2128
+ case 'intelligence.start':
2129
+ return isFlexPlainObject(valueArg)
2130
+ && hasFlexExactKeys(valueArg, ['jobId', 'workerSessionId'])
2131
+ && isQualifiedId(valueArg.jobId)
2132
+ && isIdentifier(valueArg.workerSessionId);
2133
+ case 'intelligence.get':
2134
+ if (
2135
+ !isFlexPlainObject(valueArg)
2136
+ || !hasFlexExactKeys(
2137
+ valueArg,
2138
+ ['status', 'workerSessionId'],
2139
+ ['result', 'error'],
2140
+ )
2141
+ || !['running', 'completed', 'failed', 'cancelled'].includes(String(valueArg.status))
2142
+ || !isIdentifier(valueArg.workerSessionId)
2143
+ || (valueArg.result !== undefined && !isFlexIntelligenceResult(valueArg.result))
2144
+ || (valueArg.error !== undefined && valueArg.error !== 'Session Intelligence failed.')
2145
+ ) return false;
2146
+ return (valueArg.status === 'completed' && valueArg.result !== undefined
2147
+ && valueArg.error === undefined)
2148
+ || (valueArg.status === 'failed' && valueArg.result === undefined
2149
+ && valueArg.error === 'Session Intelligence failed.')
2150
+ || (valueArg.status === 'cancelled' && valueArg.result === undefined
2151
+ && valueArg.error === undefined)
2152
+ || (valueArg.status === 'running' && valueArg.result === undefined
2153
+ && valueArg.error === undefined);
2154
+ case 'intelligence.cancel':
2155
+ return isFlexPlainObject(valueArg)
2156
+ && hasFlexExactKeys(valueArg, ['cancelled'])
2157
+ && typeof valueArg.cancelled === 'boolean';
2158
+ case 'session.list':
2159
+ return isFlexPlainObject(valueArg)
2160
+ && hasFlexExactKeys(valueArg, ['sessions', 'total', 'truncated'])
2161
+ && Array.isArray(valueArg.sessions) && valueArg.sessions.length <= 50
2162
+ && valueArg.sessions.every(isFlexPublicSession)
2163
+ && isNonNegativeInteger(valueArg.total)
2164
+ && typeof valueArg.truncated === 'boolean';
2165
+ case 'session.create':
2166
+ case 'session.get':
2167
+ case 'session.update':
2168
+ return isFlexPublicSession(valueArg);
2169
+ case 'session.delete':
2170
+ return isFlexPlainObject(valueArg) && hasFlexExactKeys(valueArg, ['deleted'])
2171
+ && valueArg.deleted === true;
2172
+ case 'session.compact':
2173
+ return isFlexPlainObject(valueArg) && hasFlexExactKeys(valueArg, ['compacted'])
2174
+ && valueArg.compacted === true;
2175
+ case 'session.reversion.info':
2176
+ return isSessionReversionInfo(valueArg);
2177
+ case 'slash.list':
2178
+ return isFlexPlainObject(valueArg)
2179
+ && hasFlexExactKeys(valueArg, ['commands'])
2180
+ && Array.isArray(valueArg.commands)
2181
+ && valueArg.commands.length <= 132
2182
+ && valueArg.commands.every(isSlashDescriptor);
2183
+ case 'slash.execute':
2184
+ return isSlashExecutionResult(valueArg);
2185
+ case 'message.page':
2186
+ return isFlexPlainObject(valueArg)
2187
+ && hasFlexExactKeys(valueArg, ['messages'], ['nextCursor'])
2188
+ && Array.isArray(valueArg.messages) && valueArg.messages.length <= 50
2189
+ && valueArg.messages.every(isFlexPublicMessage)
2190
+ && (valueArg.nextCursor === undefined || isBoundedString(valueArg.nextCursor, 4096));
2191
+ case 'message.get':
2192
+ return isFlexPublicMessage(valueArg);
2193
+ case 'prompt.start':
2194
+ return isFlexPlainObject(valueArg)
2195
+ && hasFlexExactKeys(valueArg, ['queueId', 'runId'])
2196
+ && isIdentifier(valueArg.queueId)
2197
+ && isIdentifier(valueArg.runId);
2198
+ case 'prompt.acknowledge':
2199
+ return isFlexPlainObject(valueArg)
2200
+ && hasFlexExactKeys(valueArg, ['acknowledged'])
2201
+ && valueArg.acknowledged === true;
2202
+ case 'prompt.get':
2203
+ return isFlexPlainObject(valueArg)
2204
+ && hasFlexExactKeys(valueArg, ['entry'])
2205
+ && isPromptQueueEntry(valueArg.entry);
2206
+ case 'prompt.list':
2207
+ return isFlexPlainObject(valueArg)
2208
+ && hasFlexExactKeys(valueArg, ['entries'])
2209
+ && Array.isArray(valueArg.entries)
2210
+ && valueArg.entries.length <= flexIpcMaximumPromptQueueEntries
2211
+ && valueArg.entries.every(isPromptQueueEntry);
2212
+ case 'prompt.cancel':
2213
+ return isFlexPlainObject(valueArg) && hasFlexExactKeys(valueArg, ['accepted'])
2214
+ && typeof valueArg.accepted === 'boolean';
2215
+ case 'permission.list':
2216
+ return isFlexPlainObject(valueArg) && hasFlexExactKeys(valueArg, ['permissions'])
2217
+ && Array.isArray(valueArg.permissions) && valueArg.permissions.length <= 64
2218
+ && valueArg.permissions.every(isPermissionRequest);
2219
+ case 'permission.respond':
2220
+ case 'provider.connection.logout.prepare':
2221
+ case 'provider.connection.logout':
2222
+ return isFlexPlainObject(valueArg) && hasFlexExactKeys(valueArg, ['accepted'])
2223
+ && valueArg.accepted === true;
2224
+ case 'provider.connection.ratelimits.get':
2225
+ return isFlexPlainObject(valueArg)
2226
+ && hasFlexExactKeys(valueArg, ['rateLimits'])
2227
+ && isAccountRateLimits(valueArg.rateLimits);
2228
+ case 'provider.list':
2229
+ return isFlexPlainObject(valueArg) && hasFlexExactKeys(valueArg, ['providers'])
2230
+ && Array.isArray(valueArg.providers) && valueArg.providers.length === 1
2231
+ && isFlexPlainObject(valueArg.providers[0])
2232
+ && hasFlexExactKeys(valueArg.providers[0], ['providerId', 'displayName', 'loginFlow'])
2233
+ && valueArg.providers[0].providerId === 'openai'
2234
+ && isBoundedString(valueArg.providers[0].displayName, 256)
2235
+ && valueArg.providers[0].loginFlow === 'device';
2236
+ case 'provider.login.begin':
2237
+ return isFlexPlainObject(valueArg)
2238
+ && hasFlexExactKeys(
2239
+ valueArg,
2240
+ ['loginId', 'verificationUrl', 'userCode', 'status'],
2241
+ )
2242
+ && isQualifiedId(valueArg.loginId)
2243
+ && isBoundedString(valueArg.verificationUrl, 4096)
2244
+ && isBoundedString(valueArg.userCode, 256)
2245
+ && valueArg.status === 'pending';
2246
+ case 'provider.login.status':
2247
+ return isFlexPublicProviderConnection(valueArg);
2248
+ case 'provider.login.cancel':
2249
+ return isFlexPlainObject(valueArg) && hasFlexExactKeys(valueArg, ['canceled'])
2250
+ && typeof valueArg.canceled === 'boolean';
2251
+ case 'provider.connection.list':
2252
+ return isFlexPlainObject(valueArg) && hasFlexExactKeys(valueArg, ['connections'])
2253
+ && Array.isArray(valueArg.connections) && valueArg.connections.length <= 512
2254
+ && valueArg.connections.every(isFlexPublicProviderConnection);
2255
+ case 'provider.connection.opencode-auth.get':
2256
+ return isFlexPlainObject(valueArg) && hasFlexExactKeys(valueArg, ['auth'])
2257
+ && isOpenCodeOAuthAuth(valueArg.auth);
2258
+ case 'provider.models.refresh':
2259
+ return isFlexPlainObject(valueArg) && hasFlexExactKeys(valueArg, ['jobId'])
2260
+ && isQualifiedId(valueArg.jobId);
2261
+ case 'model.choice.get':
2262
+ return isFlexPlainObject(valueArg) && hasFlexExactKeys(valueArg, ['choice'])
2263
+ && (valueArg.choice === null || isModelChoice(valueArg.choice));
2264
+ case 'model.choice.set':
2265
+ return isFlexPlainObject(valueArg) && hasFlexExactKeys(valueArg, ['choice'])
2266
+ && isModelChoice(valueArg.choice);
2267
+ case 'model.choice.validate':
2268
+ return isFlexPlainObject(valueArg)
2269
+ && hasFlexExactKeys(valueArg, ['valid'])
2270
+ && valueArg.valid === true;
2271
+ default:
2272
+ return false;
2273
+ }
2274
+ };
2275
+
2276
+ export const isFlexServiceError = (valueArg: unknown): valueArg is IFlexServiceError =>
2277
+ isFlexPlainObject(valueArg)
2278
+ && hasFlexExactKeys(valueArg, ['name', 'code', 'message'])
2279
+ && valueArg.name === 'FlexServiceError'
2280
+ && typeof valueArg.code === 'string'
2281
+ && serviceErrorCodes.has(valueArg.code)
2282
+ && valueArg.message === flexServiceErrorMessages[valueArg.code as TFlexServiceErrorCode];
2283
+
2284
+ const isFlexChildEvent = (valueArg: unknown): valueArg is TFlexChildEvent => {
2285
+ if (!isFlexPlainObject(valueArg)) return false;
2286
+ switch (valueArg.type) {
2287
+ case 'session.history.changed':
2288
+ return hasFlexExactKeys(
2289
+ valueArg,
2290
+ ['type', 'scopeId', 'sessionId', 'direction'],
2291
+ ['runId'],
2292
+ )
2293
+ && isIdentifier(valueArg.scopeId)
2294
+ && isIdentifier(valueArg.sessionId)
2295
+ && ['undo', 'redo', 'branch'].includes(String(valueArg.direction))
2296
+ && (valueArg.runId === undefined || isIdentifier(valueArg.runId))
2297
+ && (valueArg.direction === 'branch'
2298
+ ? valueArg.runId === undefined
2299
+ : valueArg.runId !== undefined);
2300
+ case 'sessions.changed':
2301
+ return hasFlexExactKeys(valueArg, ['type', 'coalesced'], ['scopeId', 'sessionId'])
2302
+ && typeof valueArg.coalesced === 'boolean'
2303
+ && (valueArg.scopeId === undefined || isIdentifier(valueArg.scopeId))
2304
+ && (valueArg.sessionId === undefined || isIdentifier(valueArg.sessionId))
2305
+ && (valueArg.sessionId === undefined || valueArg.scopeId !== undefined);
2306
+ case 'permissions.changed':
2307
+ return hasFlexExactKeys(valueArg, ['type', 'scopeId', 'sessionId'])
2308
+ && isIdentifier(valueArg.scopeId) && isIdentifier(valueArg.sessionId);
2309
+ case 'tool.updated':
2310
+ return hasFlexExactKeys(valueArg, [
2311
+ 'type', 'scopeId', 'sessionId', 'runId', 'messageId',
2312
+ 'messageIndex', 'partIndex', 'sequence', 'timestamp', 'part',
2313
+ ])
2314
+ && isIdentifier(valueArg.scopeId)
2315
+ && isIdentifier(valueArg.sessionId)
2316
+ && isIdentifier(valueArg.runId)
2317
+ && isIdentifier(valueArg.messageId)
2318
+ && isNonNegativeInteger(valueArg.messageIndex)
2319
+ && isNonNegativeInteger(valueArg.partIndex)
2320
+ && isNonNegativeInteger(valueArg.sequence)
2321
+ && valueArg.sequence >= 1
2322
+ && isIsoDate(valueArg.timestamp)
2323
+ && isFlexPlainObject(valueArg.part)
2324
+ && valueArg.part.type === 'tool'
2325
+ && isMessagePart(valueArg.part);
2326
+ case 'reasoning.updated':
2327
+ return hasFlexExactKeys(valueArg, [
2328
+ 'type', 'scopeId', 'sessionId', 'runId', 'messageId',
2329
+ 'messageIndex', 'partIndex', 'sequence', 'timestamp', 'part',
2330
+ ])
2331
+ && isIdentifier(valueArg.scopeId)
2332
+ && isIdentifier(valueArg.sessionId)
2333
+ && isIdentifier(valueArg.runId)
2334
+ && isIdentifier(valueArg.messageId)
2335
+ && isNonNegativeInteger(valueArg.messageIndex)
2336
+ && isNonNegativeInteger(valueArg.partIndex)
2337
+ && isNonNegativeInteger(valueArg.sequence)
2338
+ && valueArg.sequence >= 1
2339
+ && isIsoDate(valueArg.timestamp)
2340
+ && isFlexPlainObject(valueArg.part)
2341
+ && valueArg.part.type === 'reasoning'
2342
+ && isMessagePart(valueArg.part);
2343
+ case 'text.updated':
2344
+ return hasFlexExactKeys(valueArg, [
2345
+ 'type', 'scopeId', 'sessionId', 'runId', 'messageId',
2346
+ 'messageIndex', 'partIndex', 'sequence', 'timestamp', 'status', 'part',
2347
+ ])
2348
+ && isIdentifier(valueArg.scopeId)
2349
+ && isIdentifier(valueArg.sessionId)
2350
+ && isIdentifier(valueArg.runId)
2351
+ && isIdentifier(valueArg.messageId)
2352
+ && isNonNegativeInteger(valueArg.messageIndex)
2353
+ && isNonNegativeInteger(valueArg.partIndex)
2354
+ && isNonNegativeInteger(valueArg.sequence)
2355
+ && valueArg.sequence >= 1
2356
+ && isIsoDate(valueArg.timestamp)
2357
+ && ['running', 'completed'].includes(String(valueArg.status))
2358
+ && isFlexPlainObject(valueArg.part)
2359
+ && valueArg.part.type === 'text'
2360
+ && isMessagePart(valueArg.part);
2361
+ case 'prompt.queued':
2362
+ case 'prompt.started':
2363
+ case 'prompt.running':
2364
+ case 'prompt.finished':
2365
+ return hasFlexExactKeys(
2366
+ valueArg,
2367
+ ['type', 'scopeId', 'sessionId', 'queueId', 'entry'],
2368
+ ['runId'],
2369
+ )
2370
+ && isIdentifier(valueArg.scopeId)
2371
+ && isIdentifier(valueArg.sessionId)
2372
+ && isIdentifier(valueArg.queueId)
2373
+ && (valueArg.runId === undefined || isIdentifier(valueArg.runId))
2374
+ && isPromptQueueEntry(valueArg.entry)
2375
+ && valueArg.entry.scopeId === valueArg.scopeId
2376
+ && valueArg.entry.sessionId === valueArg.sessionId
2377
+ && valueArg.entry.queueId === valueArg.queueId
2378
+ && valueArg.entry.runId === valueArg.runId
2379
+ && (
2380
+ (valueArg.type === 'prompt.queued'
2381
+ && valueArg.runId === undefined
2382
+ && valueArg.entry.status === 'queued')
2383
+ || (valueArg.type === 'prompt.started'
2384
+ && valueArg.runId !== undefined
2385
+ && ['starting', 'scheduled'].includes(valueArg.entry.status))
2386
+ || (valueArg.type === 'prompt.running'
2387
+ && valueArg.runId !== undefined
2388
+ && valueArg.entry.status === 'running')
2389
+ || (valueArg.type === 'prompt.finished'
2390
+ && valueArg.runId !== undefined
2391
+ && ['completed', 'failed', 'cancelled'].includes(valueArg.entry.status))
2392
+ );
2393
+ case 'prompt.message-bound':
2394
+ return hasFlexExactKeys(valueArg, [
2395
+ 'type', 'scopeId', 'sessionId', 'queueId', 'runId', 'messageId',
2396
+ ])
2397
+ && isIdentifier(valueArg.scopeId)
2398
+ && isIdentifier(valueArg.sessionId)
2399
+ && isIdentifier(valueArg.queueId)
2400
+ && isIdentifier(valueArg.runId)
2401
+ && isIdentifier(valueArg.messageId);
2402
+ case 'job.finished':
2403
+ return hasFlexExactKeys(
2404
+ valueArg,
2405
+ ['type', 'jobId', 'kind', 'status'],
2406
+ ['models'],
2407
+ )
2408
+ && isQualifiedId(valueArg.jobId)
2409
+ && valueArg.kind === 'provider.models'
2410
+ && ['completed', 'failed', 'cancelled'].includes(String(valueArg.status))
2411
+ && (valueArg.models === undefined || (
2412
+ valueArg.status === 'completed'
2413
+ && Array.isArray(valueArg.models)
2414
+ && valueArg.models.length <= 256
2415
+ && valueArg.models.every(isPublicProviderModel)
2416
+ ));
2417
+ case 'provider.connection.changed':
2418
+ return hasFlexExactKeys(valueArg, ['type', 'connection'])
2419
+ && isFlexPublicProviderConnection(valueArg.connection);
2420
+ default:
2421
+ return false;
2422
+ }
2423
+ };
2424
+
2425
+ export const parseFlexParentMessage = (valueArg: unknown): TFlexParentMessage => {
2426
+ if (
2427
+ !isFlexPlainObject(valueArg)
2428
+ || valueArg.version !== flexIpcProtocolVersion
2429
+ || typeof valueArg.type !== 'string'
2430
+ ) throw new Error('Invalid Flex parent message.');
2431
+ if (valueArg.type === 'init') {
2432
+ if (!hasFlexExactKeys(valueArg, ['version', 'type', 'init']) || !isFlexServiceInit(valueArg.init)) {
2433
+ throw new Error('Invalid Flex init message.');
2434
+ }
2435
+ return valueArg as unknown as IFlexIpcInitMessage;
2436
+ }
2437
+ if (valueArg.type === 'request') {
2438
+ if (
2439
+ !hasFlexExactKeys(valueArg, ['version', 'type', 'requestId', 'method', 'payload'])
2440
+ || !isRequestId(valueArg.requestId)
2441
+ || typeof valueArg.method !== 'string'
2442
+ || !requestMethods.has(valueArg.method)
2443
+ || !isFlexRequestPayload(valueArg.method as TFlexRequestMethod, valueArg.payload)
2444
+ ) throw new Error('Invalid Flex request message.');
2445
+ return valueArg as unknown as IFlexIpcRequestMessage;
2446
+ }
2447
+ if (valueArg.type === 'request.cancel') {
2448
+ if (
2449
+ !hasFlexExactKeys(valueArg, ['version', 'type', 'requestId'])
2450
+ || !isRequestId(valueArg.requestId)
2451
+ ) throw new Error('Invalid Flex request cancellation message.');
2452
+ return valueArg as unknown as IFlexIpcRequestCancelMessage;
2453
+ }
2454
+ if (valueArg.type === 'dispose') {
2455
+ if (!hasFlexExactKeys(valueArg, ['version', 'type', 'requestId']) || !isRequestId(valueArg.requestId)) {
2456
+ throw new Error('Invalid Flex dispose message.');
2457
+ }
2458
+ return valueArg as unknown as IFlexIpcDisposeMessage;
2459
+ }
2460
+ if (valueArg.type === 'host.response') {
2461
+ if (!isRequestId(valueArg.requestId) || typeof valueArg.ok !== 'boolean') {
2462
+ throw new Error('Invalid Flex host response message.');
2463
+ }
2464
+ if (valueArg.ok) {
2465
+ if (!hasFlexExactKeys(valueArg, ['version', 'type', 'requestId', 'ok', 'result'])) {
2466
+ throw new Error('Invalid successful Flex host response message.');
2467
+ }
2468
+ } else if (
2469
+ !hasFlexExactKeys(valueArg, ['version', 'type', 'requestId', 'ok', 'error'])
2470
+ || !isFlexServiceError(valueArg.error)
2471
+ ) {
2472
+ throw new Error('Invalid failed Flex host response message.');
2473
+ }
2474
+ return valueArg as unknown as TFlexIpcHostResponseMessage;
2475
+ }
2476
+ if (valueArg.type === 'browser.channel.frame') {
2477
+ if (
2478
+ !hasFlexExactKeys(valueArg, ['version', 'type', 'channelId', 'dataBase64'])
2479
+ || !isChannelId(valueArg.channelId)
2480
+ || !isCanonicalBrowserFrame(valueArg.dataBase64)
2481
+ ) throw new Error('Invalid Flex browser channel frame.');
2482
+ return valueArg as unknown as IFlexIpcParentBrowserFrameMessage;
2483
+ }
2484
+ if (valueArg.type === 'browser.channel.closed') {
2485
+ if (
2486
+ !hasFlexExactKeys(valueArg, ['version', 'type', 'channelId'])
2487
+ || !isChannelId(valueArg.channelId)
2488
+ ) throw new Error('Invalid Flex browser channel close message.');
2489
+ return valueArg as unknown as IFlexIpcBrowserChannelClosedMessage;
2490
+ }
2491
+ throw new Error('Invalid Flex parent message type.');
2492
+ };
2493
+
2494
+ export const parseFlexChildMessage = (valueArg: unknown): TFlexChildMessage => {
2495
+ if (
2496
+ !isFlexPlainObject(valueArg)
2497
+ || valueArg.version !== flexIpcProtocolVersion
2498
+ || typeof valueArg.type !== 'string'
2499
+ ) throw new Error('Invalid Flex child message.');
2500
+ if (valueArg.type === 'status') {
2501
+ if (!hasFlexExactKeys(valueArg, ['version', 'type', 'status']) || !isFlexServiceStatus(valueArg.status)) {
2502
+ throw new Error('Invalid Flex status message.');
2503
+ }
2504
+ return valueArg as unknown as IFlexIpcStatusMessage;
2505
+ }
2506
+ if (valueArg.type === 'response') {
2507
+ if (!isRequestId(valueArg.requestId) || typeof valueArg.ok !== 'boolean') {
2508
+ throw new Error('Invalid Flex response message.');
2509
+ }
2510
+ if (valueArg.ok) {
2511
+ if (!hasFlexExactKeys(valueArg, ['version', 'type', 'requestId', 'ok', 'result'])) {
2512
+ throw new Error('Invalid successful Flex response message.');
2513
+ }
2514
+ } else if (
2515
+ !hasFlexExactKeys(valueArg, ['version', 'type', 'requestId', 'ok', 'error'])
2516
+ || !isFlexServiceError(valueArg.error)
2517
+ ) {
2518
+ throw new Error('Invalid failed Flex response message.');
2519
+ }
2520
+ return valueArg as unknown as TFlexIpcResponseMessage;
2521
+ }
2522
+ if (valueArg.type === 'event') {
2523
+ if (!hasFlexExactKeys(valueArg, ['version', 'type', 'event']) || !isFlexChildEvent(valueArg.event)) {
2524
+ throw new Error('Invalid Flex event message.');
2525
+ }
2526
+ return valueArg as unknown as IFlexIpcEventMessage;
2527
+ }
2528
+ if (valueArg.type === 'host.request') {
2529
+ if (
2530
+ !hasFlexExactKeys(
2531
+ valueArg,
2532
+ ['version', 'type', 'requestId', 'method', 'payload', 'timeoutMs'],
2533
+ )
2534
+ || !isRequestId(valueArg.requestId)
2535
+ || typeof valueArg.method !== 'string'
2536
+ || !hostRequestMethods.has(valueArg.method)
2537
+ || !Number.isSafeInteger(valueArg.timeoutMs)
2538
+ || Number(valueArg.timeoutMs) < 1
2539
+ || Number(valueArg.timeoutMs) > flexIpcMaximumHostTimeoutMs
2540
+ || !isFlexHostRequestPayload(
2541
+ valueArg.method as TFlexHostRequestMethod,
2542
+ valueArg.payload,
2543
+ )
2544
+ ) throw new Error('Invalid Flex host request message.');
2545
+ return valueArg as unknown as IFlexIpcHostRequestMessage;
2546
+ }
2547
+ if (valueArg.type === 'host.cancel') {
2548
+ if (
2549
+ !hasFlexExactKeys(valueArg, ['version', 'type', 'requestId'])
2550
+ || !isRequestId(valueArg.requestId)
2551
+ ) throw new Error('Invalid Flex host cancellation message.');
2552
+ return valueArg as unknown as IFlexIpcHostCancelMessage;
2553
+ }
2554
+ if (valueArg.type === 'host.response.acknowledge') {
2555
+ if (
2556
+ !hasFlexExactKeys(valueArg, ['version', 'type', 'requestId'])
2557
+ || !isRequestId(valueArg.requestId)
2558
+ ) throw new Error('Invalid Flex host response acknowledgement message.');
2559
+ return valueArg as unknown as IFlexIpcHostResponseAcknowledgementMessage;
2560
+ }
2561
+ if (valueArg.type === 'browser.channel.frame') {
2562
+ if (
2563
+ !hasFlexExactKeys(valueArg, ['version', 'type', 'channelId', 'dataBase64'])
2564
+ || !isChannelId(valueArg.channelId)
2565
+ || !isCanonicalBrowserFrame(valueArg.dataBase64)
2566
+ ) throw new Error('Invalid Flex browser channel frame.');
2567
+ return valueArg as unknown as IFlexIpcChildBrowserFrameMessage;
2568
+ }
2569
+ throw new Error('Invalid Flex child message type.');
2570
+ };
2571
+
2572
+ export const createFlexServiceError = (
2573
+ codeArg: TFlexServiceErrorCode,
2574
+ ): IFlexServiceError => ({
2575
+ name: 'FlexServiceError',
2576
+ code: codeArg,
2577
+ message: flexServiceErrorMessages[codeArg],
2578
+ });
2579
+
2580
+ export class FlexServiceError extends Error {
2581
+ public readonly name = 'FlexServiceError';
2582
+
2583
+ constructor(public readonly code: TFlexServiceErrorCode) {
2584
+ super(flexServiceErrorMessages[code]);
2585
+ }
2586
+ }