sidekick-shared 0.27.6 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +58 -18
  3. package/dist/browser.d.ts +13 -2
  4. package/dist/browser.js +30 -2
  5. package/dist/claudePaths.d.ts +11 -0
  6. package/dist/claudePaths.js +61 -0
  7. package/dist/claudeProfiles.d.ts +2 -9
  8. package/dist/claudeProfiles.js +10 -25
  9. package/dist/codexPaths.d.ts +22 -0
  10. package/dist/codexPaths.js +67 -0
  11. package/dist/codexProfiles.d.ts +1 -4
  12. package/dist/codexProfiles.js +5 -43
  13. package/dist/doctor.js +2 -2
  14. package/dist/esm/accountChangeSignal.js +15 -0
  15. package/dist/esm/accountChanges.js +152 -0
  16. package/dist/esm/accountHealth.js +288 -0
  17. package/dist/esm/accountLaunch.js +145 -0
  18. package/dist/esm/accountLease.js +60 -0
  19. package/dist/esm/accountManager.js +606 -0
  20. package/dist/esm/accountRegistry.js +214 -0
  21. package/dist/esm/accountStatus.js +51 -0
  22. package/dist/esm/accountSwitch.js +74 -0
  23. package/dist/esm/accountSync.js +156 -0
  24. package/dist/esm/accountSyncTypes.js +4 -0
  25. package/dist/esm/accounts.js +1291 -0
  26. package/dist/esm/aggregation/EventAggregator.js +2034 -0
  27. package/dist/esm/aggregation/FrequencyTracker.js +69 -0
  28. package/dist/esm/aggregation/HeatmapTracker.js +89 -0
  29. package/dist/esm/aggregation/PatternExtractor.js +168 -0
  30. package/dist/esm/aggregation/costProvenance.js +31 -0
  31. package/dist/esm/aggregation/snapshot.js +161 -0
  32. package/dist/esm/aggregation/types.js +5 -0
  33. package/dist/esm/analytics/codeImpact.js +18 -0
  34. package/dist/esm/analytics/compactionLedger.js +37 -0
  35. package/dist/esm/analytics/qualityScore.js +112 -0
  36. package/dist/esm/autoSwitch.js +185 -0
  37. package/dist/esm/browser.js +34 -0
  38. package/dist/esm/claudeCredentialDoctor.js +239 -0
  39. package/dist/esm/claudeCredentialLedger.js +91 -0
  40. package/dist/esm/claudeCredentials.js +111 -0
  41. package/dist/esm/claudeLiveGuard.js +43 -0
  42. package/dist/esm/claudePaths.js +22 -0
  43. package/dist/esm/claudeProfiles.js +142 -0
  44. package/dist/esm/claudeRefreshLock.js +106 -0
  45. package/dist/esm/codexAuth.js +101 -0
  46. package/dist/esm/codexCredentialDoctor.js +265 -0
  47. package/dist/esm/codexCredentialStore.js +164 -0
  48. package/dist/esm/codexKeepAlive.js +112 -0
  49. package/dist/esm/codexPaths.js +172 -0
  50. package/dist/esm/codexProfiles.js +1251 -0
  51. package/dist/esm/codexQuota.js +564 -0
  52. package/dist/esm/codexQuotaWatcher.js +363 -0
  53. package/dist/esm/context/composer.js +71 -0
  54. package/dist/esm/context/sessionContext.js +546 -0
  55. package/dist/esm/credentialIO.js +283 -0
  56. package/dist/esm/credentials.js +49 -0
  57. package/dist/esm/doctor.js +305 -0
  58. package/dist/esm/ensureDefaultAccounts.js +62 -0
  59. package/dist/esm/errorHistory.js +56 -0
  60. package/dist/esm/extractors/errorTaxonomy.js +43 -0
  61. package/dist/esm/extractors/gatherAssets.js +32 -0
  62. package/dist/esm/extractors/sessionAssets.js +176 -0
  63. package/dist/esm/extractors/sources/claudeAssets.js +158 -0
  64. package/dist/esm/extractors/sources/codexAssets.js +234 -0
  65. package/dist/esm/extractors/tokenUsage.js +41 -0
  66. package/dist/esm/extractors/toolCall.js +142 -0
  67. package/dist/esm/failingTools.js +65 -0
  68. package/dist/esm/fileHead.js +27 -0
  69. package/dist/esm/formatters/eventHighlighter.js +332 -0
  70. package/dist/esm/formatters/noiseClassifier.js +209 -0
  71. package/dist/esm/formatters/sessionDump.js +328 -0
  72. package/dist/esm/formatters/toolSummary.js +226 -0
  73. package/dist/esm/formatting.js +82 -0
  74. package/dist/esm/handoffUrl.js +23 -0
  75. package/dist/esm/historicalStore.js +305 -0
  76. package/dist/esm/humanPrompt.js +88 -0
  77. package/dist/esm/index.js +213 -0
  78. package/dist/esm/keepAlive.js +189 -0
  79. package/dist/esm/liveCredentialGuard.js +33 -0
  80. package/dist/esm/modelAliases.js +51 -0
  81. package/dist/esm/modelCatalog.js +139 -0
  82. package/dist/esm/modelContext.js +280 -0
  83. package/dist/esm/modelInfo.js +832 -0
  84. package/dist/esm/multiProviderQuotaService.js +450 -0
  85. package/dist/esm/node.js +46 -0
  86. package/dist/esm/observedContextWindows.js +165 -0
  87. package/dist/esm/observedSessionCollector.js +939 -0
  88. package/dist/esm/parsers/changelogParser.js +71 -0
  89. package/dist/esm/parsers/codexHistory.js +89 -0
  90. package/dist/esm/parsers/codexParser.js +906 -0
  91. package/dist/esm/parsers/debugLogParser.js +126 -0
  92. package/dist/esm/parsers/jsonl.js +66 -0
  93. package/dist/esm/parsers/mcpToolName.js +28 -0
  94. package/dist/esm/parsers/openCodeParser.js +625 -0
  95. package/dist/esm/parsers/planExtractor.js +335 -0
  96. package/dist/esm/parsers/sessionActivityDetector.js +246 -0
  97. package/dist/esm/parsers/sessionPathResolver.js +713 -0
  98. package/dist/esm/parsers/subagentScanner.js +360 -0
  99. package/dist/esm/parsers/subagentStatsCache.js +45 -0
  100. package/dist/esm/parsers/subagentTraceParser.js +325 -0
  101. package/dist/esm/paths.js +134 -0
  102. package/dist/esm/peakHours.js +194 -0
  103. package/dist/esm/phrases.js +1330 -0
  104. package/dist/esm/pricingCatalog.js +247 -0
  105. package/dist/esm/processDetection.js +200 -0
  106. package/dist/esm/projectMigration.js +59 -0
  107. package/dist/esm/promptHistory.js +1221 -0
  108. package/dist/esm/providerFailure.js +362 -0
  109. package/dist/esm/providerIds.js +6 -0
  110. package/dist/esm/providerQuota.js +1 -0
  111. package/dist/esm/providerServiceStatus.js +145 -0
  112. package/dist/esm/providerServiceStatusTypes.js +1 -0
  113. package/dist/esm/providerStatus.js +117 -0
  114. package/dist/esm/providers/claudeCode.js +575 -0
  115. package/dist/esm/providers/claudeListing.js +121 -0
  116. package/dist/esm/providers/codex.js +1034 -0
  117. package/dist/esm/providers/codexDatabase.js +297 -0
  118. package/dist/esm/providers/codexRollouts.js +215 -0
  119. package/dist/esm/providers/detect.js +185 -0
  120. package/dist/esm/providers/diagnostics.js +83 -0
  121. package/dist/esm/providers/directoryListingCache.js +206 -0
  122. package/dist/esm/providers/factory.js +54 -0
  123. package/dist/esm/providers/openCode.js +2008 -0
  124. package/dist/esm/providers/openCodeDatabase.js +419 -0
  125. package/dist/esm/providers/rolloutWalker.js +252 -0
  126. package/dist/esm/providers/types.js +12 -0
  127. package/dist/esm/quota.js +182 -0
  128. package/dist/esm/quotaHistory.js +364 -0
  129. package/dist/esm/quotaPoller.js +230 -0
  130. package/dist/esm/quotaPresentation.js +97 -0
  131. package/dist/esm/quotaResolver.js +231 -0
  132. package/dist/esm/quotaSnapshots.js +130 -0
  133. package/dist/esm/quotaThresholds.js +85 -0
  134. package/dist/esm/readers/decisions.js +24 -0
  135. package/dist/esm/readers/handoff.js +20 -0
  136. package/dist/esm/readers/helpers.js +27 -0
  137. package/dist/esm/readers/history.js +9 -0
  138. package/dist/esm/readers/notes.js +48 -0
  139. package/dist/esm/readers/plans.js +225 -0
  140. package/dist/esm/readers/tasks.js +19 -0
  141. package/dist/esm/report/htmlHelpers.js +280 -0
  142. package/dist/esm/report/htmlReportGenerator.js +704 -0
  143. package/dist/esm/report/index.js +7 -0
  144. package/dist/esm/report/logo.js +2 -0
  145. package/dist/esm/report/openBrowser.js +19 -0
  146. package/dist/esm/report/sessionReportInputs.js +33 -0
  147. package/dist/esm/report/transcriptParser.js +83 -0
  148. package/dist/esm/report/types.js +4 -0
  149. package/dist/esm/schemas/accountManager.js +204 -0
  150. package/dist/esm/schemas/accountStatus.js +25 -0
  151. package/dist/esm/schemas/assistantTurn.js +74 -0
  152. package/dist/esm/schemas/index.js +25 -0
  153. package/dist/esm/schemas/observedSessionV1.js +77 -0
  154. package/dist/esm/schemas/providerFailure.js +37 -0
  155. package/dist/esm/schemas/quota.js +132 -0
  156. package/dist/esm/schemas/quotaHistory.js +40 -0
  157. package/dist/esm/schemas/sessionEvent.js +216 -0
  158. package/dist/esm/schemas/stateFile.js +75 -0
  159. package/dist/esm/schemas/tokenEstimation.js +7 -0
  160. package/dist/esm/schemas/transcript.js +86 -0
  161. package/dist/esm/schemas/usageNormalization.js +38 -0
  162. package/dist/esm/search/advancedFilter.js +197 -0
  163. package/dist/esm/search/sessionSearch.js +66 -0
  164. package/dist/esm/sessionFingerprint.js +40 -0
  165. package/dist/esm/sessionMonitor.js +198 -0
  166. package/dist/esm/sessionPreviews.js +522 -0
  167. package/dist/esm/sessionPromptHistory.js +254 -0
  168. package/dist/esm/sessionPromptSignals.js +364 -0
  169. package/dist/esm/sessionStats.js +160 -0
  170. package/dist/esm/sessionTokenTotals.js +98 -0
  171. package/dist/esm/sessionTranscripts.js +49 -0
  172. package/dist/esm/stateFile.js +119 -0
  173. package/dist/esm/statusline/BurnRateCalculator.js +40 -0
  174. package/dist/esm/statusline/claudeStatuslinePayload.js +148 -0
  175. package/dist/esm/statusline/formatter.js +122 -0
  176. package/dist/esm/statusline/index.js +8 -0
  177. package/dist/esm/terminalSync.js +95 -0
  178. package/dist/esm/testing/credentialGuard.js +197 -0
  179. package/dist/esm/testing/index.js +12 -0
  180. package/dist/esm/testing/sessionLogFixtures.js +128 -0
  181. package/dist/esm/tokenEstimation.js +67 -0
  182. package/dist/esm/tokenSummary.js +113 -0
  183. package/dist/esm/toolCategory.js +82 -0
  184. package/dist/esm/transcript.js +257 -0
  185. package/dist/esm/turns/assistantTurn.js +373 -0
  186. package/dist/esm/types/codex.js +23 -0
  187. package/dist/esm/types/decisionLog.js +5 -0
  188. package/dist/esm/types/historicalData.js +47 -0
  189. package/dist/esm/types/knowledgeNote.js +15 -0
  190. package/dist/esm/types/observedSessionV1.js +257 -0
  191. package/dist/esm/types/opencode.js +12 -0
  192. package/dist/esm/types/plan.js +7 -0
  193. package/dist/esm/types/sessionEvent.js +10 -0
  194. package/dist/esm/types/taskPersistence.js +12 -0
  195. package/dist/esm/usage/billingBlocks.js +148 -0
  196. package/dist/esm/usage/claudeUsageDedupe.js +189 -0
  197. package/dist/esm/usage/importSessionHistory.js +84 -0
  198. package/dist/esm/usage/subagentUsage.js +18 -0
  199. package/dist/esm/usage/usageEvents.js +402 -0
  200. package/dist/esm/usage/usageReports.js +202 -0
  201. package/dist/esm/usageNormalization.js +95 -0
  202. package/dist/esm/watchers/eventBridge.js +219 -0
  203. package/dist/esm/watchers/factory.js +63 -0
  204. package/dist/esm/watchers/jsonlTail.js +289 -0
  205. package/dist/esm/watchers/jsonlWatcher.js +435 -0
  206. package/dist/esm/watchers/providerReaderWatcher.js +118 -0
  207. package/dist/esm/watchers/sqliteWatcher.js +285 -0
  208. package/dist/esm/watchers/types.js +4 -0
  209. package/dist/esm/workspaceRoots.js +56 -0
  210. package/dist/esm/writers/atomic.js +375 -0
  211. package/dist/esm/writers/decisions.js +51 -0
  212. package/dist/esm/writers/notes.js +46 -0
  213. package/dist/esm/writers/tasks.js +63 -0
  214. package/dist/esm/zaiQuota.js +323 -0
  215. package/dist/esm/zaiQuotaApi.js +271 -0
  216. package/dist/esm/zaiQuotaWatcher.js +265 -0
  217. package/dist/fileHead.d.ts +22 -0
  218. package/dist/fileHead.js +33 -0
  219. package/dist/index.d.ts +16 -7
  220. package/dist/index.js +49 -15
  221. package/dist/node.d.ts +13 -1
  222. package/dist/node.js +43 -2
  223. package/dist/parsers/codexHistory.js +2 -2
  224. package/dist/parsers/codexParser.d.ts +12 -0
  225. package/dist/parsers/codexParser.js +34 -12
  226. package/dist/parsers/debugLogParser.js +2 -2
  227. package/dist/parsers/sessionPathResolver.js +5 -4
  228. package/dist/promptHistory.d.ts +32 -0
  229. package/dist/promptHistory.js +115 -59
  230. package/dist/providerFailure.d.ts +12 -6
  231. package/dist/providerFailure.js +45 -0
  232. package/dist/providers/claudeCode.js +7 -99
  233. package/dist/providers/claudeListing.d.ts +22 -0
  234. package/dist/providers/claudeListing.js +158 -0
  235. package/dist/providers/codex.d.ts +3 -1
  236. package/dist/providers/codex.js +26 -227
  237. package/dist/providers/codexRollouts.d.ts +44 -0
  238. package/dist/providers/codexRollouts.js +261 -0
  239. package/dist/providers/detect.js +4 -4
  240. package/dist/readers/plans.js +3 -3
  241. package/dist/schemas/index.d.ts +2 -0
  242. package/dist/schemas/index.js +7 -2
  243. package/dist/schemas/providerFailure.d.ts +154 -0
  244. package/dist/schemas/providerFailure.js +40 -0
  245. package/dist/sessionPromptHistory.d.ts +12 -7
  246. package/dist/sessionPromptHistory.js +15 -3
  247. package/dist/sessionPromptSignals.d.ts +9 -0
  248. package/dist/sessionPromptSignals.js +13 -0
  249. package/dist/testing/credentialGuard.d.ts +23 -0
  250. package/dist/testing/credentialGuard.js +233 -0
  251. package/dist/testing/index.d.ts +14 -0
  252. package/dist/testing/index.js +22 -0
  253. package/dist/testing/sessionLogFixtures.d.ts +28 -0
  254. package/dist/testing/sessionLogFixtures.js +169 -0
  255. package/dist/tokenEstimation.d.ts +2 -0
  256. package/dist/tokenEstimation.js +21 -11
  257. package/dist/toolCategory.d.ts +18 -0
  258. package/dist/toolCategory.js +86 -0
  259. package/dist/types/codex.d.ts +4 -0
  260. package/dist/usage/usageEvents.d.ts +16 -0
  261. package/dist/usage/usageEvents.js +53 -9
  262. package/dist/watchers/jsonlTail.d.ts +26 -1
  263. package/dist/watchers/jsonlTail.js +136 -28
  264. package/dist/watchers/jsonlWatcher.js +7 -0
  265. package/dist/watchers/providerReaderWatcher.js +1 -0
  266. package/dist/watchers/types.d.ts +6 -0
  267. package/dist/workspaceRoots.d.ts +23 -0
  268. package/dist/workspaceRoots.js +93 -0
  269. package/package.json +22 -2
@@ -0,0 +1,362 @@
1
+ export const PROVIDER_CREDENTIAL_KINDS = ['oauth', 'api-key', 'unknown'];
2
+ export const PROVIDER_AUTHENTICATION_STATES = [
3
+ 'authenticated',
4
+ 'missing',
5
+ 'signed-out',
6
+ 'expired',
7
+ 'rejected',
8
+ 'unknown',
9
+ ];
10
+ export const PROVIDER_AUTHENTICATION_SOURCES = ['local-check', 'provider-response'];
11
+ export const PROVIDER_FAILURE_CODES = [
12
+ 'missing_credentials',
13
+ 'oauth_reauthentication_required',
14
+ 'api_credentials_rejected',
15
+ 'authentication_rejected',
16
+ 'invalid_provider_session',
17
+ 'service_unavailable',
18
+ 'connection_failed',
19
+ 'timeout',
20
+ 'rate_limited',
21
+ 'overloaded',
22
+ 'execution_policy_denied',
23
+ 'runtime_unavailable',
24
+ 'context_overflow',
25
+ 'unknown',
26
+ ];
27
+ export const PROVIDER_RECOVERY_ACTIONS = [
28
+ 'sign_in',
29
+ 'update_credentials',
30
+ 'check_authentication',
31
+ 'start_new_session',
32
+ 'retry_later',
33
+ 'check_connection',
34
+ 'review_execution_policy',
35
+ 'repair_runtime',
36
+ 'reduce_context',
37
+ 'inspect_error',
38
+ ];
39
+ export const PROVIDER_FAILURE_EVIDENCE_SOURCES = [
40
+ 'structured-error',
41
+ 'http-status',
42
+ 'message',
43
+ 'authentication-check',
44
+ ];
45
+ function read(value, key) {
46
+ try {
47
+ return value && typeof value === 'object' ? value[key] : undefined;
48
+ }
49
+ catch {
50
+ return undefined;
51
+ }
52
+ }
53
+ function text(value) {
54
+ return typeof value === 'string' ? value.slice(0, 16_384) : '';
55
+ }
56
+ function statusNumber(value) {
57
+ const number = typeof value === 'string' && /^\d{3}$/.test(value) ? Number(value) : value;
58
+ return typeof number === 'number' && Number.isInteger(number) && number >= 100 && number <= 599
59
+ ? number
60
+ : undefined;
61
+ }
62
+ function timestamp(value) {
63
+ return typeof value === 'string' &&
64
+ value.length <= 64 &&
65
+ /^(?:\d{4}-\d{2}-\d{2}T|(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), )/.test(value) &&
66
+ Number.isFinite(Date.parse(value))
67
+ ? new Date(value).toISOString()
68
+ : undefined;
69
+ }
70
+ function retryAfter(value) {
71
+ if (typeof value === 'number' || (typeof value === 'string' && /^\d+(\.\d+)?$/.test(value))) {
72
+ const delayMs = Number(value) * 1000;
73
+ return Number.isFinite(delayMs) && delayMs >= 0 && delayMs <= Number.MAX_SAFE_INTEGER
74
+ ? { kind: 'delay', delayMs }
75
+ : undefined;
76
+ }
77
+ const at = timestamp(value);
78
+ return at ? { kind: 'date', at } : undefined;
79
+ }
80
+ function rejected(kind) {
81
+ return kind === 'oauth'
82
+ ? 'oauth_reauthentication_required'
83
+ : kind === 'api-key'
84
+ ? 'api_credentials_rejected'
85
+ : 'authentication_rejected';
86
+ }
87
+ function classifyCode(code, kind) {
88
+ switch (code.toLowerCase().replace(/[^a-z0-9]/g, '')) {
89
+ case 'missingcredentials':
90
+ case 'notloggedin':
91
+ case 'signedout':
92
+ return 'missing_credentials';
93
+ case 'invalidapikey':
94
+ case 'apikeyrejected':
95
+ return 'api_credentials_rejected';
96
+ case 'oauthtokenexpired':
97
+ case 'refreshtokenexpired':
98
+ case 'refreshtokenreused':
99
+ case 'invalidgrant':
100
+ return 'oauth_reauthentication_required';
101
+ case 'authenticationerror':
102
+ case 'autherror':
103
+ case 'invalidtoken':
104
+ case 'tokenexpired':
105
+ case 'unauthorized':
106
+ return rejected(kind);
107
+ case 'threadnotfound':
108
+ case 'invalidthreadid':
109
+ case 'conversationnotfound':
110
+ case 'invalidconversation':
111
+ case 'conversationexpired':
112
+ case 'invalidsessionid':
113
+ return 'invalid_provider_session';
114
+ case 'executionpolicydenied':
115
+ case 'sandboxdenied':
116
+ case 'approvaldenied':
117
+ return 'execution_policy_denied';
118
+ case 'ratelimiterror':
119
+ case 'ratelimitexceeded':
120
+ case 'toomanyrequests':
121
+ return 'rate_limited';
122
+ case 'overloadederror':
123
+ case 'serveroverloaded':
124
+ return 'overloaded';
125
+ case 'serviceunavailable':
126
+ case 'internalservererror':
127
+ case 'servererror':
128
+ return 'service_unavailable';
129
+ case 'timeouterror':
130
+ case 'apiconnectiontimeouterror':
131
+ case 'etimedout':
132
+ case 'underrconnecttimeout':
133
+ case 'underrheaderstimeout':
134
+ case 'deadlineexceeded':
135
+ return 'timeout';
136
+ case 'econnreset':
137
+ case 'econnrefused':
138
+ case 'enotfound':
139
+ case 'eaiagain':
140
+ case 'enetunreach':
141
+ case 'ehostunreach':
142
+ case 'apiconnectionerror':
143
+ return 'connection_failed';
144
+ case 'runtimenotfound':
145
+ case 'runtimeunavailable':
146
+ case 'modulenotfound':
147
+ case 'errmodulenotfound':
148
+ return 'runtime_unavailable';
149
+ case 'contextlengthexceeded':
150
+ case 'contextwindowexceeded':
151
+ case 'prompttoolong':
152
+ return 'context_overflow';
153
+ default:
154
+ return undefined;
155
+ }
156
+ }
157
+ function classifyStatus(status, kind) {
158
+ if (status === 401)
159
+ return rejected(kind);
160
+ if (status === 408 || status === 504)
161
+ return 'timeout';
162
+ if (status === 429)
163
+ return 'rate_limited';
164
+ if (status === 529)
165
+ return 'overloaded';
166
+ if (status >= 500)
167
+ return 'service_unavailable';
168
+ return undefined;
169
+ }
170
+ function classifyMessage(message, kind) {
171
+ if (/\b(execution policy|sandbox policy|approval)\b.{0,60}\b(denied|blocked|rejected|not allowed)\b|\bdenied by policy\b/i.test(message))
172
+ return 'execution_policy_denied';
173
+ // Only explicit credential rejection outranks an overlapping conversation error.
174
+ if (/\b(oauth (?:(?:access |refresh )?token|credentials?)|refresh token) (?:is |was |has (?:been )?)?(expired|invalid|rejected|revoked)\b/i.test(message))
175
+ return 'oauth_reauthentication_required';
176
+ if (/\b(thread|conversation|session id)\b.{0,60}\b(not found|invalid|expired|does not exist)\b|\b(invalid|expired) (thread|conversation|session id)\b/i.test(message))
177
+ return 'invalid_provider_session';
178
+ if (/\b(oauth|refresh token)\b.{0,60}\b(expired|invalid|rejected|revoked)\b/i.test(message))
179
+ return 'oauth_reauthentication_required';
180
+ if (/\b(api[ _-]?key)\b.{0,60}\b(invalid|incorrect|rejected|revoked|expired)\b|\b(invalid|incorrect|rejected) api[ _-]?key\b/i.test(message))
181
+ return 'api_credentials_rejected';
182
+ if (/\b(no|missing) (oauth |api[ _-]?key |authentication )?(credentials|token|api[ _-]?key)\b|\bnot (logged|signed) in\b|\bsigned out\b|\bapi[ _-]?key (not configured|not set)\b/i.test(message))
183
+ return 'missing_credentials';
184
+ if (/\b(authentication failed|invalid authentication|credentials (rejected|expired)|token (expired|rejected|invalid)|login (has |is )?expired|please re-?authenticate)\b/i.test(message))
185
+ return rejected(kind);
186
+ if (/\b(context (length|window).{0,40}(exceed|overflow)|prompt (is )?too long|maximum context length)\b/i.test(message))
187
+ return 'context_overflow';
188
+ if (/\b(overloaded|over capacity|server busy)\b/i.test(message))
189
+ return 'overloaded';
190
+ if (/\b(rate[ _-]?limit|too many requests)\b/i.test(message))
191
+ return 'rate_limited';
192
+ if (/\b(timed? ?out|deadline exceeded|etimedout)\b/i.test(message))
193
+ return 'timeout';
194
+ if (/\b(service unavailable|bad gateway|internal server error)\b/i.test(message))
195
+ return 'service_unavailable';
196
+ if (/\b(enotfound|eai_again|econnreset|econnrefused|dns (failure|lookup failed)|fetch failed|network (error|unreachable)|connection (reset|refused|terminated|termination)|upstream connect error|socket hang up)\b/i.test(message))
197
+ return 'connection_failed';
198
+ if (/\b(claude|codex|cli|runtime|executable)\b.{0,60}\b(not found|not installed|unavailable|not executable)\b|\bspawn\s+\S+\s+(ENOENT|EACCES)\b/i.test(message))
199
+ return 'runtime_unavailable';
200
+ return undefined;
201
+ }
202
+ function recovery(code, kind) {
203
+ switch (code) {
204
+ case 'missing_credentials':
205
+ return kind === 'oauth'
206
+ ? 'sign_in'
207
+ : kind === 'api-key'
208
+ ? 'update_credentials'
209
+ : 'check_authentication';
210
+ case 'oauth_reauthentication_required':
211
+ return 'sign_in';
212
+ case 'api_credentials_rejected':
213
+ return 'update_credentials';
214
+ case 'authentication_rejected':
215
+ return 'check_authentication';
216
+ case 'invalid_provider_session':
217
+ return 'start_new_session';
218
+ case 'connection_failed':
219
+ return 'check_connection';
220
+ case 'execution_policy_denied':
221
+ return 'review_execution_policy';
222
+ case 'runtime_unavailable':
223
+ return 'repair_runtime';
224
+ case 'context_overflow':
225
+ return 'reduce_context';
226
+ case 'unknown':
227
+ return 'inspect_error';
228
+ default:
229
+ return 'retry_later';
230
+ }
231
+ }
232
+ /**
233
+ * Classify a caller-designated terminal failure without I/O or recovery actions.
234
+ * Public status and runtime readiness do not establish request authentication.
235
+ */
236
+ export function diagnoseProviderFailure(input) {
237
+ const codes = [];
238
+ const messages = [];
239
+ const visited = new Set();
240
+ const queue = [{ value: input.error, depth: 0 }];
241
+ let httpStatus;
242
+ let retry;
243
+ for (let index = 0; index < queue.length && index < 32; index++) {
244
+ const { value, depth } = queue[index];
245
+ if (typeof value === 'string') {
246
+ messages.push(text(value));
247
+ continue;
248
+ }
249
+ if (!value || typeof value !== 'object' || visited.has(value))
250
+ continue;
251
+ visited.add(value);
252
+ for (const key of ['code', 'type', 'name'])
253
+ codes.push(text(read(value, key)));
254
+ messages.push(text(read(value, 'message')));
255
+ for (const key of ['status', 'statusCode', 'httpStatus', 'status_code'])
256
+ httpStatus ??= statusNumber(read(value, key));
257
+ const headers = read(value, 'headers');
258
+ const header = typeof Headers !== 'undefined' && headers instanceof Headers
259
+ ? headers.get('retry-after')
260
+ : (read(headers, 'retry-after') ?? read(headers, 'Retry-After'));
261
+ retry ??= retryAfter(header);
262
+ const delay = read(value, 'retryAfterMs');
263
+ if (!retry &&
264
+ typeof delay === 'number' &&
265
+ Number.isFinite(delay) &&
266
+ delay >= 0 &&
267
+ delay <= Number.MAX_SAFE_INTEGER)
268
+ retry = { kind: 'delay', delayMs: delay };
269
+ // ENOENT by itself can be a missing document. Require a spawn operation.
270
+ if (read(value, 'code') === 'ENOENT' && /^spawn\b/.test(text(read(value, 'syscall'))))
271
+ codes.push('runtime_unavailable');
272
+ if (depth < 5) {
273
+ for (const key of ['error', 'cause', 'response', 'data', 'body']) {
274
+ let nested = read(value, key);
275
+ if (typeof nested === 'string' &&
276
+ nested.length <= 16_384 &&
277
+ nested.trim().startsWith('{')) {
278
+ try {
279
+ nested = JSON.parse(nested);
280
+ }
281
+ catch {
282
+ /* Keep message evidence. */
283
+ }
284
+ }
285
+ if (nested != null)
286
+ queue.push({ value: nested, depth: depth + 1 });
287
+ }
288
+ const errors = read(value, 'errors');
289
+ if (Array.isArray(errors))
290
+ for (const error of errors.slice(0, 8))
291
+ queue.push({ value: error, depth: depth + 1 });
292
+ }
293
+ }
294
+ let authentication;
295
+ const auth = input.authentication;
296
+ const checkedAt = timestamp(auth?.checkedAt);
297
+ if (auth &&
298
+ checkedAt &&
299
+ ['oauth', 'api-key', 'unknown'].includes(auth.credentialKind) &&
300
+ ['authenticated', 'missing', 'signed-out', 'expired', 'rejected', 'unknown'].includes(auth.state) &&
301
+ ['local-check', 'provider-response'].includes(auth.source)) {
302
+ authentication = {
303
+ credentialKind: auth.credentialKind,
304
+ state: auth.state,
305
+ checkedAt,
306
+ source: auth.source,
307
+ };
308
+ }
309
+ let diagnosis = codes
310
+ .map((code) => classifyCode(code, input.credentialKind))
311
+ .find((code) => code !== undefined);
312
+ let source = 'structured-error';
313
+ if (!diagnosis && httpStatus !== undefined) {
314
+ diagnosis = classifyStatus(httpStatus, input.credentialKind);
315
+ source = 'http-status';
316
+ }
317
+ if (!diagnosis) {
318
+ source = 'message';
319
+ for (const message of messages) {
320
+ // Message HTTP codes must have an HTTP/status context, not just a stray number.
321
+ const match = message.match(/\b(?:HTTP(?:\/\d(?:\.\d)?)?(?: error)?|(?:unexpected )?status(?: code)?|API error)\s*[:=]?\s*(\d{3})\b|\b(401|403|429|50[02349]|529)\s+(?:Unauthorized|Forbidden|Too Many Requests|Bad Gateway|Service Unavailable|Internal Server Error|Gateway Timeout)\b/i);
322
+ const status = match ? statusNumber(match[1] ?? match[2]) : undefined;
323
+ // A structured status cannot be replaced by a contradictory status in prose.
324
+ if (httpStatus === undefined && status !== undefined) {
325
+ httpStatus = status;
326
+ diagnosis = classifyStatus(status, input.credentialKind);
327
+ }
328
+ diagnosis ??= classifyMessage(message, input.credentialKind);
329
+ if (diagnosis)
330
+ break;
331
+ }
332
+ }
333
+ if (!diagnosis && authentication && authentication.credentialKind === input.credentialKind) {
334
+ if (authentication.state === 'missing' || authentication.state === 'signed-out')
335
+ diagnosis = 'missing_credentials';
336
+ if (authentication.state === 'expired' || authentication.state === 'rejected')
337
+ diagnosis = rejected(input.credentialKind);
338
+ if (diagnosis)
339
+ source = 'authentication-check';
340
+ }
341
+ diagnosis ??= 'unknown';
342
+ return {
343
+ provider: input.provider,
344
+ credentialKind: input.credentialKind,
345
+ diagnosis,
346
+ recovery: recovery(diagnosis, input.credentialKind),
347
+ evidence: diagnosis === 'unknown'
348
+ ? []
349
+ : [
350
+ {
351
+ source,
352
+ rule: diagnosis,
353
+ ...(source === 'authentication-check'
354
+ ? { checkedAt: authentication.checkedAt }
355
+ : {}),
356
+ },
357
+ ],
358
+ ...(authentication ? { authentication } : {}),
359
+ ...(httpStatus !== undefined ? { httpStatus } : {}),
360
+ ...(retry ? { retryAfter: retry } : {}),
361
+ };
362
+ }
@@ -0,0 +1,6 @@
1
+ /** Browser-safe provider ID catalogs. Types are derived from these values. */
2
+ export const SESSION_PROVIDER_IDS = ['claude-code', 'opencode', 'codex'];
3
+ export const ACCOUNT_PROVIDER_IDS = ['claude-code', 'codex'];
4
+ export const QUOTA_PROVIDER_IDS = ['claude-code', 'codex', 'zai'];
5
+ export const RUNTIME_QUOTA_PROVIDER_IDS = ['claude', 'codex', 'zai'];
6
+ export const MODEL_PROVIDER_IDS = ['anthropic', 'openai', 'google', 'unknown'];
@@ -0,0 +1 @@
1
+ export { RUNTIME_QUOTA_PROVIDER_IDS } from './providerIds';
@@ -0,0 +1,145 @@
1
+ const SOURCES = {
2
+ 'claude-code': 'https://status.claude.com/api/v2/summary.json',
3
+ codex: 'https://status.openai.com/api/v2/summary.json',
4
+ };
5
+ function object(value) {
6
+ if (!value || typeof value !== 'object' || Array.isArray(value))
7
+ throw new Error('Invalid object');
8
+ return value;
9
+ }
10
+ function string(value) {
11
+ if (typeof value !== 'string' || !value.trim())
12
+ throw new Error('Invalid string');
13
+ return value;
14
+ }
15
+ function date(value) {
16
+ if (value === undefined || value === null)
17
+ return null;
18
+ const raw = string(value);
19
+ if (!/^\d{4}-\d{2}-\d{2}T/.test(raw) || !Number.isFinite(Date.parse(raw)))
20
+ throw new Error('Invalid date');
21
+ return raw;
22
+ }
23
+ function url(value) {
24
+ if (value === undefined || value === null)
25
+ return null;
26
+ const raw = string(value);
27
+ const parsed = new URL(raw);
28
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
29
+ throw new Error('Invalid link');
30
+ return raw;
31
+ }
32
+ function array(value) {
33
+ if (!Array.isArray(value))
34
+ throw new Error('Invalid array');
35
+ return value;
36
+ }
37
+ function parseSummary(raw) {
38
+ const data = object(raw);
39
+ const status = object(data.status);
40
+ const severity = string(status.indicator);
41
+ if (!['none', 'minor', 'major', 'critical', 'maintenance'].includes(severity))
42
+ throw new Error('Invalid severity');
43
+ const components = array(data.components).map((rawComponent) => {
44
+ const component = object(rawComponent);
45
+ return {
46
+ id: string(component.id),
47
+ name: string(component.name),
48
+ status: string(component.status),
49
+ };
50
+ });
51
+ const incidents = data.incidents === undefined
52
+ ? null
53
+ : array(data.incidents)
54
+ .map((rawIncident) => {
55
+ const incident = object(rawIncident);
56
+ // Statuspage uses objects; its compatible feeds may supply component IDs directly.
57
+ const associations = incident.components ?? incident.component_ids;
58
+ return {
59
+ id: string(incident.id),
60
+ title: string(incident.name),
61
+ status: string(incident.status),
62
+ impact: string(incident.impact),
63
+ url: url(incident.shortlink ?? incident.url),
64
+ updatedAt: date(incident.updated_at),
65
+ componentIds: associations === undefined
66
+ ? null
67
+ : array(associations).map((component) => typeof component === 'string'
68
+ ? string(component)
69
+ : string(object(component).id)),
70
+ };
71
+ })
72
+ .filter((incident) => !['resolved', 'postmortem'].includes(incident.status.toLowerCase()));
73
+ return {
74
+ availability: 'observed',
75
+ severity: severity,
76
+ description: string(status.description),
77
+ providerUpdatedAt: date(object(data.page).updated_at),
78
+ components,
79
+ incidents,
80
+ };
81
+ }
82
+ /**
83
+ * Fetch public vendor status without credentials, caching, polling, or retries.
84
+ * Incidents supplement a request diagnosis; they do not prove its cause. Consumers
85
+ * own freshness and component/endpoint correlation. Omitted incidents are null.
86
+ */
87
+ export async function fetchProviderServiceStatus(provider, { signal } = {}) {
88
+ const sourceUrl = SOURCES[provider];
89
+ const observation = () => ({ provider, sourceUrl, checkedAt: new Date().toISOString() });
90
+ const unavailable = (reason, httpStatus) => ({
91
+ ...observation(),
92
+ availability: 'unavailable',
93
+ reason,
94
+ ...(httpStatus !== undefined ? { httpStatus } : {}),
95
+ });
96
+ if (signal?.aborted)
97
+ return unavailable('cancelled');
98
+ const controller = new AbortController();
99
+ let aborted;
100
+ let rejectAbort = () => { };
101
+ const abortPromise = new Promise((_resolve, reject) => {
102
+ rejectAbort = reject;
103
+ });
104
+ const abort = (reason) => {
105
+ if (aborted)
106
+ return;
107
+ aborted = reason;
108
+ controller.abort();
109
+ rejectAbort(new Error(reason));
110
+ };
111
+ const onCancel = () => abort('cancelled');
112
+ signal?.addEventListener('abort', onCancel, { once: true });
113
+ const timer = setTimeout(() => abort('timeout'), 10_000);
114
+ timer.unref?.();
115
+ try {
116
+ return await Promise.race([
117
+ abortPromise,
118
+ (async () => {
119
+ const response = await fetch(sourceUrl, { signal: controller.signal, credentials: 'omit' });
120
+ if (!response.ok)
121
+ return unavailable('http_error', response.status);
122
+ let body;
123
+ try {
124
+ body = await response.json();
125
+ }
126
+ catch (error) {
127
+ return unavailable(aborted ?? (error instanceof SyntaxError ? 'invalid_response' : 'network_error'));
128
+ }
129
+ try {
130
+ return { ...observation(), ...parseSummary(body) };
131
+ }
132
+ catch {
133
+ return unavailable('invalid_response');
134
+ }
135
+ })(),
136
+ ]);
137
+ }
138
+ catch {
139
+ return unavailable(aborted ?? 'network_error');
140
+ }
141
+ finally {
142
+ clearTimeout(timer);
143
+ signal?.removeEventListener('abort', onCancel);
144
+ }
145
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Stateless provider status fetchers for Atlassian Statuspage APIs.
3
+ *
4
+ * Supports both status.claude.com and status.openai.com (same API format).
5
+ * Polling / eventing is the caller's responsibility.
6
+ */
7
+ /** Compatibility projection only; it cannot express evidence availability. */
8
+ export function toLegacyProviderStatus(status) {
9
+ if (status.availability === 'unavailable') {
10
+ return {
11
+ indicator: 'none',
12
+ description: 'Status unavailable',
13
+ affectedComponents: [],
14
+ activeIncident: null,
15
+ updatedAt: status.checkedAt,
16
+ };
17
+ }
18
+ const incident = status.incidents?.[0];
19
+ return {
20
+ indicator: status.severity === 'maintenance' ? 'minor' : status.severity,
21
+ description: status.description,
22
+ affectedComponents: status.components
23
+ .filter((component) => component.status !== 'operational')
24
+ .map(({ name, status }) => ({ name, status })),
25
+ activeIncident: incident
26
+ ? {
27
+ name: incident.title,
28
+ impact: incident.impact,
29
+ shortlink: incident.url ?? '',
30
+ updatedAt: incident.updatedAt ?? '',
31
+ }
32
+ : null,
33
+ updatedAt: status.providerUpdatedAt ?? '',
34
+ };
35
+ }
36
+ const CLAUDE_BASE = 'https://status.claude.com';
37
+ const OPENAI_BASE = 'https://status.openai.com';
38
+ function fallbackState() {
39
+ return {
40
+ indicator: 'none',
41
+ description: 'Status unavailable',
42
+ affectedComponents: [],
43
+ activeIncident: null,
44
+ updatedAt: new Date().toISOString(),
45
+ };
46
+ }
47
+ /**
48
+ * Generic fetcher for any Atlassian Statuspage-compatible endpoint.
49
+ */
50
+ async function fetchStatusPage(baseUrl) {
51
+ try {
52
+ const statusRes = await fetchWithTimeout(`${baseUrl}/api/v2/status.json`);
53
+ if (!statusRes.ok)
54
+ return fallbackState();
55
+ const statusData = await statusRes.json();
56
+ const indicator = (statusData.status?.indicator ?? 'none');
57
+ const description = statusData.status?.description ?? '';
58
+ const updatedAt = statusData.page?.updated_at ?? new Date().toISOString();
59
+ // All operational — no need to fetch summary
60
+ if (indicator === 'none') {
61
+ return { indicator, description, affectedComponents: [], activeIncident: null, updatedAt };
62
+ }
63
+ // Degraded — fetch summary for components + incidents
64
+ const summaryRes = await fetchWithTimeout(`${baseUrl}/api/v2/summary.json`);
65
+ if (!summaryRes.ok) {
66
+ return { indicator, description, affectedComponents: [], activeIncident: null, updatedAt };
67
+ }
68
+ const summaryData = await summaryRes.json();
69
+ // Filter to non-operational components only
70
+ const affectedComponents = (summaryData.components ?? [])
71
+ .filter((c) => c.status && c.status !== 'operational')
72
+ .map((c) => ({ name: c.name ?? 'Unknown', status: c.status ?? 'unknown' }));
73
+ // Pick first unresolved incident
74
+ const unresolvedIncident = (summaryData.incidents ?? []).find((i) => i.status !== 'resolved' && i.status !== 'postmortem');
75
+ const activeIncident = unresolvedIncident
76
+ ? {
77
+ name: unresolvedIncident.name ?? 'Unknown incident',
78
+ impact: unresolvedIncident.impact ?? 'unknown',
79
+ shortlink: unresolvedIncident.shortlink ?? '',
80
+ updatedAt: unresolvedIncident.updated_at ?? updatedAt,
81
+ }
82
+ : null;
83
+ return { indicator, description, affectedComponents, activeIncident, updatedAt };
84
+ }
85
+ catch {
86
+ return fallbackState();
87
+ }
88
+ }
89
+ async function fetchWithTimeout(url) {
90
+ const controller = new AbortController();
91
+ const timeout = setTimeout(() => controller.abort(), 10_000);
92
+ timeout.unref();
93
+ try {
94
+ return await fetch(url, { signal: controller.signal });
95
+ }
96
+ finally {
97
+ clearTimeout(timeout);
98
+ }
99
+ }
100
+ /**
101
+ * Fetch current Claude API status from status.claude.com.
102
+ *
103
+ * Single-shot — caller wraps in polling loop, EventEmitter, or interval.
104
+ * Consumers requiring reliable availability semantics should use fetchProviderServiceStatus.
105
+ */
106
+ export async function fetchProviderStatus() {
107
+ return fetchStatusPage(CLAUDE_BASE);
108
+ }
109
+ /**
110
+ * Fetch current OpenAI API status from status.openai.com.
111
+ *
112
+ * Single-shot — caller wraps in polling loop, EventEmitter, or interval.
113
+ * Consumers requiring reliable availability semantics should use fetchProviderServiceStatus.
114
+ */
115
+ export async function fetchOpenAIStatus() {
116
+ return fetchStatusPage(OPENAI_BASE);
117
+ }