@ixo/oracle-runtime-workers 0.2.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 (215) hide show
  1. package/README.md +110 -0
  2. package/package.json +77 -0
  3. package/src/attachments/classify.ts +114 -0
  4. package/src/attachments/content-blocks.ts +51 -0
  5. package/src/attachments/download.ts +235 -0
  6. package/src/attachments/extract.ts +241 -0
  7. package/src/attachments/index.ts +84 -0
  8. package/src/attachments/magic.ts +133 -0
  9. package/src/attachments/pipeline.ts +409 -0
  10. package/src/attachments/retention.ts +205 -0
  11. package/src/attachments/route.ts +41 -0
  12. package/src/attachments/sandbox-archive.ts +90 -0
  13. package/src/attachments/sanitize.ts +35 -0
  14. package/src/attachments/types.ts +86 -0
  15. package/src/attachments/view.ts +107 -0
  16. package/src/core/env.ts +200 -0
  17. package/src/core/index.ts +513 -0
  18. package/src/core/llm.ts +842 -0
  19. package/src/core/main-agent-types.ts +123 -0
  20. package/src/core/main-agent.ts +399 -0
  21. package/src/core/manifest.ts +365 -0
  22. package/src/core/meta-tools.ts +232 -0
  23. package/src/core/middlewares/byo-history-sanitizer.ts +165 -0
  24. package/src/core/middlewares/capability-gate.ts +76 -0
  25. package/src/core/middlewares/index.ts +18 -0
  26. package/src/core/middlewares/page-context.ts +98 -0
  27. package/src/core/middlewares/safety-guardrail.ts +117 -0
  28. package/src/core/middlewares/summarization.ts +99 -0
  29. package/src/core/middlewares/tool-repetition-guard.ts +125 -0
  30. package/src/core/middlewares/tool-validation.ts +83 -0
  31. package/src/core/middlewares/work-status.ts +57 -0
  32. package/src/core/openrouter-pricing.ts +124 -0
  33. package/src/core/plugin-loader.ts +192 -0
  34. package/src/core/plugins/index.ts +6 -0
  35. package/src/core/plugins/skills/index.ts +8 -0
  36. package/src/core/plugins/skills/skills-tools.ts +356 -0
  37. package/src/core/plugins/skills/skills.plugin.ts +113 -0
  38. package/src/core/plugins/weather/index.ts +8 -0
  39. package/src/core/plugins/weather/weather-client.ts +153 -0
  40. package/src/core/plugins/weather/weather-middleware.ts +27 -0
  41. package/src/core/plugins/weather/weather-sub-agent.ts +65 -0
  42. package/src/core/plugins/weather/weather-tools.ts +103 -0
  43. package/src/core/plugins/weather/weather.plugin.ts +227 -0
  44. package/src/core/prompt-composer.ts +469 -0
  45. package/src/core/registries.ts +642 -0
  46. package/src/core/runtime-context.ts +619 -0
  47. package/src/core/state.ts +108 -0
  48. package/src/core/sub-agent-fallback.ts +187 -0
  49. package/src/core/subagent-as-tool.ts +322 -0
  50. package/src/core/template.ts +245 -0
  51. package/src/core/utils.ts +246 -0
  52. package/src/core/wrap-plugin-tool.ts +100 -0
  53. package/src/do/ambient.ts +266 -0
  54. package/src/do/contracts.ts +562 -0
  55. package/src/do/debug-timers.ts +120 -0
  56. package/src/do/fresh-stub.ts +32 -0
  57. package/src/do/gateway-retry.ts +73 -0
  58. package/src/do/secrets-adapter.ts +32 -0
  59. package/src/do/size-alerts.ts +76 -0
  60. package/src/do/sse-stream.ts +472 -0
  61. package/src/do/transcript.ts +192 -0
  62. package/src/do/turn-body.ts +84 -0
  63. package/src/do/turn-metadata.ts +146 -0
  64. package/src/do/ucan-service.ts +339 -0
  65. package/src/do/user-oracle-do.ts +3035 -0
  66. package/src/gateway-worker.ts +82 -0
  67. package/src/index.ts +237 -0
  68. package/src/llm/byo-adapter.ts +59 -0
  69. package/src/llm/byo-catalog.ts +520 -0
  70. package/src/llm/byo-client.ts +280 -0
  71. package/src/llm/byo-routes.ts +255 -0
  72. package/src/llm/byo-service.ts +836 -0
  73. package/src/llm/chatgpt-oauth.ts +430 -0
  74. package/src/llm/provider-error.ts +283 -0
  75. package/src/matrix/gateway-do.ts +918 -0
  76. package/src/matrix/ingest.ts +209 -0
  77. package/src/matrix/replay-format.ts +34 -0
  78. package/src/matrix/reply-chain.ts +109 -0
  79. package/src/matrix/room-state-codec.ts +115 -0
  80. package/src/matrix/user-homeserver.ts +106 -0
  81. package/src/matrix/work-status.ts +294 -0
  82. package/src/memory/session-history-indexer.ts +328 -0
  83. package/src/owner-store/ixo-vfs-store.ts +570 -0
  84. package/src/owner-store/matrix-media-store.ts +136 -0
  85. package/src/owner-store/migrating-store.ts +185 -0
  86. package/src/owner-store/owner-copy-errors.ts +175 -0
  87. package/src/owner-store/retry.ts +45 -0
  88. package/src/owner-store/tus-upload.ts +333 -0
  89. package/src/owner-store/types.ts +0 -0
  90. package/src/plugin-api/define-plugin.ts +45 -0
  91. package/src/plugin-api/index.ts +56 -0
  92. package/src/plugin-api/oracle-plugin.ts +145 -0
  93. package/src/plugin-api/tool-helper.ts +82 -0
  94. package/src/plugin-api/types.ts +816 -0
  95. package/src/plugin-api/ucan-errors.ts +16 -0
  96. package/src/plugins/agui/agui-agent.ts +115 -0
  97. package/src/plugins/agui/agui.plugin.ts +148 -0
  98. package/src/plugins/agui/index.ts +2 -0
  99. package/src/plugins/attachments/attachments.plugin.ts +54 -0
  100. package/src/plugins/attachments/index.ts +6 -0
  101. package/src/plugins/attachments/view-attachment-tool.ts +91 -0
  102. package/src/plugins/base64.ts +26 -0
  103. package/src/plugins/composio/composio-tools.ts +365 -0
  104. package/src/plugins/composio/composio-ucan.ts +43 -0
  105. package/src/plugins/composio/composio.plugin.ts +214 -0
  106. package/src/plugins/composio/index.ts +15 -0
  107. package/src/plugins/domain-indexer/domain-indexer-agent.ts +57 -0
  108. package/src/plugins/domain-indexer/domain-indexer-tools.ts +253 -0
  109. package/src/plugins/domain-indexer/domain-indexer.plugin.ts +100 -0
  110. package/src/plugins/domain-indexer/index.ts +3 -0
  111. package/src/plugins/editor/blocknote-bridge.ts +120 -0
  112. package/src/plugins/editor/content-session.ts +331 -0
  113. package/src/plugins/editor/content-tools.ts +686 -0
  114. package/src/plugins/editor/document-model.ts +0 -0
  115. package/src/plugins/editor/editor-access-denied-tool.ts +58 -0
  116. package/src/plugins/editor/editor-agent.ts +34 -0
  117. package/src/plugins/editor/editor-config.ts +64 -0
  118. package/src/plugins/editor/editor-mx.ts +128 -0
  119. package/src/plugins/editor/editor.plugin.ts +143 -0
  120. package/src/plugins/editor/failures.ts +154 -0
  121. package/src/plugins/editor/index.ts +31 -0
  122. package/src/plugins/editor/jsdom-shim.ts +55 -0
  123. package/src/plugins/editor/prompts.ts +59 -0
  124. package/src/plugins/editor/prop-policy.ts +147 -0
  125. package/src/plugins/editor/provider.ts +386 -0
  126. package/src/plugins/editor/room-membership.ts +95 -0
  127. package/src/plugins/editor/standalone-editor-tool.ts +200 -0
  128. package/src/plugins/firecrawl/firecrawl-agent.ts +93 -0
  129. package/src/plugins/firecrawl/firecrawl-tools.ts +220 -0
  130. package/src/plugins/firecrawl/firecrawl.plugin.ts +105 -0
  131. package/src/plugins/firecrawl/index.ts +15 -0
  132. package/src/plugins/flows/action-metadata.ts +3505 -0
  133. package/src/plugins/flows/actions.ts +190 -0
  134. package/src/plugins/flows/edit.ts +314 -0
  135. package/src/plugins/flows/errors.ts +38 -0
  136. package/src/plugins/flows/explain.ts +43 -0
  137. package/src/plugins/flows/flow-doc.ts +111 -0
  138. package/src/plugins/flows/flows.plugin.ts +129 -0
  139. package/src/plugins/flows/forms.ts +330 -0
  140. package/src/plugins/flows/index.ts +2 -0
  141. package/src/plugins/flows/linkage.ts +147 -0
  142. package/src/plugins/flows/port-types.ts +53 -0
  143. package/src/plugins/flows/prompts.ts +65 -0
  144. package/src/plugins/flows/read.ts +392 -0
  145. package/src/plugins/flows/references.ts +66 -0
  146. package/src/plugins/flows/test-support.ts +165 -0
  147. package/src/plugins/flows/tools/authoring.ts +516 -0
  148. package/src/plugins/flows/tools/discovery.ts +119 -0
  149. package/src/plugins/flows/tools/forms.ts +142 -0
  150. package/src/plugins/flows/tools/inspect.ts +165 -0
  151. package/src/plugins/flows/tools/linkage.ts +117 -0
  152. package/src/plugins/flows/tools/settings.ts +184 -0
  153. package/src/plugins/flows/translator.ts +328 -0
  154. package/src/plugins/flows/types.ts +210 -0
  155. package/src/plugins/flows/ydoc-helpers.ts +543 -0
  156. package/src/plugins/index.ts +70 -0
  157. package/src/plugins/mcp-call-timeout.ts +38 -0
  158. package/src/plugins/mcp-tool-adapter.ts +87 -0
  159. package/src/plugins/memory/index.ts +17 -0
  160. package/src/plugins/memory/memory-tools.ts +463 -0
  161. package/src/plugins/memory/memory-ucan.ts +51 -0
  162. package/src/plugins/memory/memory.plugin.ts +122 -0
  163. package/src/plugins/memory/types.ts +147 -0
  164. package/src/plugins/portal/action-log.ts +34 -0
  165. package/src/plugins/portal/index.ts +2 -0
  166. package/src/plugins/portal/portal.plugin.ts +203 -0
  167. package/src/plugins/sandbox/index.ts +34 -0
  168. package/src/plugins/sandbox/sandbox-bridge.ts +295 -0
  169. package/src/plugins/sandbox/sandbox-mcp.ts +127 -0
  170. package/src/plugins/sandbox/sandbox-write-blob.ts +103 -0
  171. package/src/plugins/sandbox/sandbox.plugin.ts +599 -0
  172. package/src/plugins/tasks/index.ts +17 -0
  173. package/src/plugins/tasks/manifest.ts +81 -0
  174. package/src/plugins/tasks/middleware.ts +219 -0
  175. package/src/plugins/tasks/tasks-tools.ts +414 -0
  176. package/src/plugins/tasks/tasks.plugin.ts +74 -0
  177. package/src/plugins/ucan-failure.ts +133 -0
  178. package/src/plugins/user-preferences/index.ts +22 -0
  179. package/src/plugins/user-preferences/schema.ts +31 -0
  180. package/src/plugins/user-preferences/user-preferences-store.ts +172 -0
  181. package/src/plugins/user-preferences/user-preferences-tool.ts +131 -0
  182. package/src/plugins/user-preferences/user-preferences.plugin.ts +137 -0
  183. package/src/plugins/vfs/index.ts +40 -0
  184. package/src/plugins/vfs/vfs-auth.ts +56 -0
  185. package/src/plugins/vfs/vfs-client.ts +655 -0
  186. package/src/plugins/vfs/vfs-content.ts +181 -0
  187. package/src/plugins/vfs/vfs-errors.ts +163 -0
  188. package/src/plugins/vfs/vfs-sandbox-tools.ts +272 -0
  189. package/src/plugins/vfs/vfs-tools.ts +635 -0
  190. package/src/plugins/vfs/vfs.plugin.ts +201 -0
  191. package/src/realtime/frontend-call-registry.ts +163 -0
  192. package/src/realtime/realtime-endpoint.ts +619 -0
  193. package/src/realtime/session-socket-hub.ts +249 -0
  194. package/src/realtime/socket-io-codec.ts +187 -0
  195. package/src/secrets/jwe.ts +74 -0
  196. package/src/secrets/pin-cipher.ts +43 -0
  197. package/src/secrets/secrets-service.ts +319 -0
  198. package/src/shell/app.ts +564 -0
  199. package/src/shell/auth.ts +290 -0
  200. package/src/sqlite/blob-codec.ts +100 -0
  201. package/src/sqlite/blob-compactor.ts +146 -0
  202. package/src/sqlite/cache-config.ts +45 -0
  203. package/src/sqlite/database.ts +643 -0
  204. package/src/sqlite/do-vfs.ts +1485 -0
  205. package/src/sqlite/index.ts +61 -0
  206. package/src/sqlite/serialization.ts +370 -0
  207. package/src/sqlite/sessions-store.ts +311 -0
  208. package/src/sqlite/sqlite-saver.ts +919 -0
  209. package/src/sqlite/vacuum-policy.ts +77 -0
  210. package/src/sqlite/wa-sqlite-loader.ts +123 -0
  211. package/src/sqlite/wasm.d.ts +10 -0
  212. package/src/tasks/schedule.ts +174 -0
  213. package/src/tasks/scheduler.ts +753 -0
  214. package/src/tasks/spec.ts +125 -0
  215. package/src/tasks/store.ts +351 -0
@@ -0,0 +1,283 @@
1
+ /**
2
+ * BYO degradation notices — the slice of the Node runtime's
3
+ * `llm/provider-error.ts` the Workers runtime consumes: the payload emitted
4
+ * when a turn silently degrades from the user's own credential to the
5
+ * platform model. It rides the SSE `error` event channel (the only channel
6
+ * existing clients surface) but is a warning, not a failure — the turn still
7
+ * streams a reply.
8
+ */
9
+
10
+ import {
11
+ BYO_PROVIDER_INFO,
12
+ isByoProvider,
13
+ type ByoProvider,
14
+ } from './byo-catalog';
15
+
16
+ /** `kind` value for the BYO degradation notice. */
17
+ export const BYO_FALLBACK_KIND = 'byo_fallback';
18
+
19
+ export type ByoFallbackReason =
20
+ | 'not_connected'
21
+ | 'reconnect_required'
22
+ | 'unreachable'
23
+ | 'error';
24
+
25
+ export interface ByoFallbackNoticePayload {
26
+ error: string;
27
+ kind: typeof BYO_FALLBACK_KIND;
28
+ source: 'byo';
29
+ provider?: ByoProvider;
30
+ providerLabel?: string;
31
+ reason: ByoFallbackReason;
32
+ retryable: false;
33
+ timestamp: string;
34
+ }
35
+
36
+ export function buildByoFallbackNotice(
37
+ reason: ByoFallbackReason,
38
+ provider?: ByoProvider,
39
+ ): ByoFallbackNoticePayload {
40
+ const providerLabel = provider
41
+ ? BYO_PROVIDER_INFO[provider].label
42
+ : undefined;
43
+ const account = providerLabel ?? 'your connected AI account';
44
+ const message =
45
+ reason === 'unreachable'
46
+ ? `Your ${account} can't be reached from this oracle right now, so this reply is using the platform model instead.`
47
+ : reason === 'reconnect_required'
48
+ ? `Your ${account} connection has expired, so this reply is using the platform model instead. Reconnect it in your Personal Agent settings.`
49
+ : reason === 'not_connected'
50
+ ? `The model you selected needs a connected ${account}, so this reply is using the platform model instead. Connect it in your Personal Agent settings.`
51
+ : `Your connected AI account could not be used for this reply, so it is using the platform model instead.`;
52
+ return {
53
+ error: message,
54
+ kind: BYO_FALLBACK_KIND,
55
+ source: 'byo',
56
+ ...(provider && { provider }),
57
+ ...(providerLabel && { providerLabel }),
58
+ reason,
59
+ retryable: false,
60
+ timestamp: new Date().toISOString(),
61
+ };
62
+ }
63
+
64
+ // ── LLM error classification (port of the Node runtime's provider-error) ──
65
+ // One classification is what becomes bytes on the SSE `error` channel; the
66
+ // Portal re-maps `kind` to localized copy, Matrix/Slack clients show
67
+ // `message`. `redactOperatorFault` runs at the wire so a platform-side
68
+ // billing/auth failure never leaks operator detail to end users.
69
+
70
+ export type LlmErrorKind =
71
+ | 'rate_limit'
72
+ | 'billing'
73
+ | 'auth'
74
+ | 'timeout'
75
+ | 'server'
76
+ | 'network'
77
+ | 'unknown';
78
+
79
+ export interface ClassifiedLlmError {
80
+ kind: LlmErrorKind;
81
+ /** Where the failing credential lives — the user's own account or ours. */
82
+ source: 'byo' | 'platform';
83
+ /** Set on BYO turns; lets the client name the account that failed. */
84
+ provider?: ByoProvider;
85
+ /** Human label for the provider ("OpenAI API", "ChatGPT (subscription)"). */
86
+ providerLabel?: string;
87
+ /** HTTP status when one could be recovered. */
88
+ status?: number;
89
+ /** Whether an immediate identical retry has any chance of succeeding. */
90
+ retryable: boolean;
91
+ /** Human-readable message (English fallback for clients without their own error UI). */
92
+ message: string;
93
+ /** Raw provider text, for logs and support. */
94
+ detail: string;
95
+ }
96
+
97
+ interface ErrorParts {
98
+ status?: number;
99
+ code?: string;
100
+ text: string;
101
+ }
102
+
103
+ function extractErrorParts(error: unknown): ErrorParts {
104
+ const text = error instanceof Error ? error.message : String(error);
105
+ const parts: ErrorParts = { text };
106
+ if (error && typeof error === 'object') {
107
+ const record = error as Record<string, unknown>;
108
+ if (typeof record['status'] === 'number') parts.status = record['status'];
109
+ if (typeof record['code'] === 'string') parts.code = record['code'];
110
+ if (error instanceof Error && error.name === 'InsufficientQuotaError') {
111
+ parts.code = 'insufficient_quota';
112
+ }
113
+ const nested = record['error'];
114
+ if (nested && typeof nested === 'object') {
115
+ const nestedRecord = nested as Record<string, unknown>;
116
+ if (!parts.code && typeof nestedRecord['code'] === 'string') {
117
+ parts.code = nestedRecord['code'];
118
+ }
119
+ if (!parts.code && typeof nestedRecord['type'] === 'string') {
120
+ parts.code = nestedRecord['type'];
121
+ }
122
+ }
123
+ const response = record['response'];
124
+ if (
125
+ parts.status === undefined &&
126
+ response &&
127
+ typeof response === 'object' &&
128
+ typeof (response as Record<string, unknown>)['status'] === 'number'
129
+ ) {
130
+ parts.status = (response as Record<string, unknown>)['status'] as number;
131
+ }
132
+ }
133
+ if (parts.status === undefined) {
134
+ const prefixed = /^(\d{3})\s/.exec(text);
135
+ if (prefixed) parts.status = Number(prefixed[1]);
136
+ }
137
+ return parts;
138
+ }
139
+
140
+ const BILLING_TEXT =
141
+ /insufficient[_ ]quota|insufficient balance|credit balance is too low|purchase credits|billing hard limit|payment required/i;
142
+ const RATE_LIMIT_TEXT =
143
+ /rate[_ -]?limit|too many requests|usage[_ -]?limit|resource[_ -]?exhausted|tokens per min|requests per min/i;
144
+ const AUTH_TEXT =
145
+ /incorrect api key|invalid api key|invalid x-api-key|authentication[_ ]error|permission[_ ]error|unauthorized|forbidden|token has expired|invalid bearer/i;
146
+ const TIMEOUT_TEXT = /timed?[_ ]?out|deadline exceeded|request timeout/i;
147
+ const NETWORK_TEXT =
148
+ /fetch failed|network|econnrefused|econnreset|etimedout|eai_again|socket hang up|und_err/i;
149
+ const SERVER_TEXT =
150
+ /internal server error|bad gateway|service unavailable|server[_ ]error|overloaded/i;
151
+
152
+ function detectKind(parts: ErrorParts): LlmErrorKind {
153
+ const { status, code, text } = parts;
154
+ if (code === 'insufficient_quota') return 'billing';
155
+ if (code === 'rate_limit_error') return 'rate_limit';
156
+ if (code === 'authentication_error' || code === 'permission_error') {
157
+ return 'auth';
158
+ }
159
+ if (code === 'overloaded_error') return 'server';
160
+ if (BILLING_TEXT.test(text)) return 'billing';
161
+ if (status === 401 || status === 403) return 'auth';
162
+ if (status === 402) return 'billing';
163
+ if (status === 429 || RATE_LIMIT_TEXT.test(text)) return 'rate_limit';
164
+ if (status === 408 || status === 504 || TIMEOUT_TEXT.test(text)) {
165
+ return 'timeout';
166
+ }
167
+ if (status !== undefined && status >= 500) return 'server';
168
+ if (AUTH_TEXT.test(text)) return 'auth';
169
+ if (NETWORK_TEXT.test(text)) return 'network';
170
+ if (SERVER_TEXT.test(text)) return 'server';
171
+ return 'unknown';
172
+ }
173
+
174
+ const RETRYABLE_KINDS: ReadonlySet<LlmErrorKind> = new Set([
175
+ 'rate_limit',
176
+ 'timeout',
177
+ 'server',
178
+ 'network',
179
+ ]);
180
+
181
+ function capitalize(value: string): string {
182
+ return value.charAt(0).toUpperCase() + value.slice(1);
183
+ }
184
+
185
+ function fallbackMessage(
186
+ kind: LlmErrorKind,
187
+ provider: ByoProvider | undefined,
188
+ providerLabel: string | undefined,
189
+ ): string {
190
+ const account = providerLabel
191
+ ? `your ${providerLabel} account`
192
+ : 'the model provider';
193
+ switch (kind) {
194
+ case 'billing':
195
+ return provider === 'chatgpt'
196
+ ? 'Your ChatGPT subscription has no usage left. Check your plan, or switch to another model.'
197
+ : `${capitalize(account)} is out of credit. Top up with the provider, or switch to another model.`;
198
+ case 'rate_limit':
199
+ return provider === 'chatgpt'
200
+ ? "You've hit your ChatGPT plan's usage limit. Wait for it to reset, or switch to another model."
201
+ : `${capitalize(account)} is being rate-limited. Wait a moment and try again.`;
202
+ case 'auth':
203
+ return providerLabel
204
+ ? `Your ${providerLabel} credentials were rejected. Reconnect or update the key in your Personal Agent settings.`
205
+ : 'The model provider rejected the credentials for this request.';
206
+ case 'timeout':
207
+ return 'The model took too long to respond. Please try again.';
208
+ case 'server':
209
+ return providerLabel
210
+ ? `${providerLabel} is having trouble right now. Please try again shortly.`
211
+ : 'The model provider is having trouble right now. Please try again shortly.';
212
+ case 'network':
213
+ return 'Could not reach the model provider. Please try again.';
214
+ case 'unknown':
215
+ return 'Something went wrong while generating the reply. Please try again.';
216
+ }
217
+ }
218
+
219
+ export interface ClassifyLlmErrorContext {
220
+ /** The BYO provider the failing turn ran on, when it was a BYO turn. */
221
+ byoProvider?: ByoProvider | string | null;
222
+ }
223
+
224
+ export function classifyLlmError(
225
+ error: unknown,
226
+ ctx?: ClassifyLlmErrorContext,
227
+ ): ClassifiedLlmError {
228
+ const parts = extractErrorParts(error);
229
+ const kind = detectKind(parts);
230
+ const provider =
231
+ typeof ctx?.byoProvider === 'string' && isByoProvider(ctx.byoProvider)
232
+ ? ctx.byoProvider
233
+ : undefined;
234
+ const providerLabel = provider
235
+ ? BYO_PROVIDER_INFO[provider].label
236
+ : undefined;
237
+ return {
238
+ kind,
239
+ source: provider ? 'byo' : 'platform',
240
+ ...(provider && { provider }),
241
+ ...(providerLabel && { providerLabel }),
242
+ ...(parts.status !== undefined && { status: parts.status }),
243
+ retryable: RETRYABLE_KINDS.has(kind),
244
+ message: fallbackMessage(kind, provider, providerLabel),
245
+ detail: parts.text,
246
+ };
247
+ }
248
+
249
+ const OPERATOR_FAULT_KINDS: ReadonlySet<LlmErrorKind> = new Set([
250
+ 'billing',
251
+ 'auth',
252
+ ]);
253
+
254
+ /**
255
+ * A platform-side billing/auth failure is the operator's problem, not the
256
+ * user's: collapse it to a generic error before it reaches any client.
257
+ */
258
+ export function redactOperatorFault(
259
+ classified: ClassifiedLlmError,
260
+ ): ClassifiedLlmError {
261
+ if (
262
+ classified.source !== 'platform' ||
263
+ !OPERATOR_FAULT_KINDS.has(classified.kind)
264
+ ) {
265
+ return classified;
266
+ }
267
+ const message = fallbackMessage('unknown', undefined, undefined);
268
+ return {
269
+ kind: 'unknown',
270
+ source: 'platform',
271
+ status: 500,
272
+ retryable: false,
273
+ message,
274
+ detail: message,
275
+ };
276
+ }
277
+
278
+ export function isOperatorFault(classified: ClassifiedLlmError): boolean {
279
+ return (
280
+ classified.source === 'platform' &&
281
+ OPERATOR_FAULT_KINDS.has(classified.kind)
282
+ );
283
+ }