offrouter-core 0.2.1 → 0.2.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 (139) hide show
  1. package/dist/audit.d.ts +137 -0
  2. package/dist/audit.d.ts.map +1 -0
  3. package/dist/audit.js +320 -0
  4. package/dist/audit.js.map +1 -0
  5. package/dist/auth/credential-chain.d.ts +84 -0
  6. package/dist/auth/credential-chain.d.ts.map +1 -0
  7. package/dist/auth/credential-chain.js +150 -0
  8. package/dist/auth/credential-chain.js.map +1 -0
  9. package/dist/auth/index.d.ts +10 -0
  10. package/dist/auth/index.d.ts.map +1 -0
  11. package/dist/auth/index.js +7 -0
  12. package/dist/auth/index.js.map +1 -0
  13. package/dist/auth/keychain.d.ts +60 -0
  14. package/dist/auth/keychain.d.ts.map +1 -0
  15. package/dist/auth/keychain.js +104 -0
  16. package/dist/auth/keychain.js.map +1 -0
  17. package/dist/auth/oauth-pkce.d.ts +138 -0
  18. package/dist/auth/oauth-pkce.d.ts.map +1 -0
  19. package/dist/auth/oauth-pkce.js +375 -0
  20. package/dist/auth/oauth-pkce.js.map +1 -0
  21. package/dist/auth/oauth-server.d.ts +36 -0
  22. package/dist/auth/oauth-server.d.ts.map +1 -0
  23. package/dist/auth/oauth-server.js +210 -0
  24. package/dist/auth/oauth-server.js.map +1 -0
  25. package/dist/config.d.ts +76 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +355 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/delegation.d.ts +123 -0
  30. package/dist/delegation.d.ts.map +1 -0
  31. package/dist/delegation.js +455 -0
  32. package/dist/delegation.js.map +1 -0
  33. package/dist/index.d.ts +41 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +26 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/policy.d.ts +45 -0
  38. package/dist/policy.d.ts.map +1 -0
  39. package/dist/policy.js +318 -0
  40. package/dist/policy.js.map +1 -0
  41. package/{src/protocol.ts → dist/protocol.d.ts} +2 -2
  42. package/dist/protocol.d.ts.map +1 -0
  43. package/dist/protocol.js +6 -0
  44. package/dist/protocol.js.map +1 -0
  45. package/dist/providers/adapter.d.ts +137 -0
  46. package/dist/providers/adapter.d.ts.map +1 -0
  47. package/dist/providers/adapter.js +163 -0
  48. package/dist/providers/adapter.js.map +1 -0
  49. package/dist/providers/catalog-data.d.ts +128 -0
  50. package/dist/providers/catalog-data.d.ts.map +1 -0
  51. package/dist/providers/catalog-data.js +397 -0
  52. package/dist/providers/catalog-data.js.map +1 -0
  53. package/dist/providers/catalog.d.ts +63 -0
  54. package/dist/providers/catalog.d.ts.map +1 -0
  55. package/dist/providers/catalog.js +394 -0
  56. package/dist/providers/catalog.js.map +1 -0
  57. package/dist/providers/fake.d.ts +46 -0
  58. package/dist/providers/fake.d.ts.map +1 -0
  59. package/dist/providers/fake.js +234 -0
  60. package/dist/providers/fake.js.map +1 -0
  61. package/dist/providers/openai-compatible.d.ts +65 -0
  62. package/dist/providers/openai-compatible.d.ts.map +1 -0
  63. package/dist/providers/openai-compatible.js +434 -0
  64. package/dist/providers/openai-compatible.js.map +1 -0
  65. package/dist/proxy/responses-mapper.d.ts +366 -0
  66. package/dist/proxy/responses-mapper.d.ts.map +1 -0
  67. package/dist/proxy/responses-mapper.js +517 -0
  68. package/dist/proxy/responses-mapper.js.map +1 -0
  69. package/dist/proxy/responses-server.d.ts +29 -0
  70. package/dist/proxy/responses-server.d.ts.map +1 -0
  71. package/dist/proxy/responses-server.js +360 -0
  72. package/dist/proxy/responses-server.js.map +1 -0
  73. package/dist/router.d.ts +18 -0
  74. package/dist/router.d.ts.map +1 -0
  75. package/dist/router.js +296 -0
  76. package/dist/router.js.map +1 -0
  77. package/dist/schemas.d.ts +560 -0
  78. package/dist/schemas.d.ts.map +1 -0
  79. package/{src/schemas.ts → dist/schemas.js} +83 -103
  80. package/dist/schemas.js.map +1 -0
  81. package/dist/secrets.d.ts +112 -0
  82. package/dist/secrets.d.ts.map +1 -0
  83. package/dist/secrets.js +326 -0
  84. package/dist/secrets.js.map +1 -0
  85. package/dist/types.d.ts +117 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/types.js +6 -0
  88. package/dist/types.js.map +1 -0
  89. package/dist/usage-file.d.ts +59 -0
  90. package/dist/usage-file.d.ts.map +1 -0
  91. package/dist/usage-file.js +316 -0
  92. package/dist/usage-file.js.map +1 -0
  93. package/dist/usage.d.ts +235 -0
  94. package/dist/usage.d.ts.map +1 -0
  95. package/dist/usage.js +517 -0
  96. package/dist/usage.js.map +1 -0
  97. package/package.json +7 -2
  98. package/src/audit.test.ts +0 -302
  99. package/src/audit.ts +0 -553
  100. package/src/auth/credential-chain.test.ts +0 -326
  101. package/src/auth/credential-chain.ts +0 -235
  102. package/src/auth/index.ts +0 -45
  103. package/src/auth/keychain.test.ts +0 -265
  104. package/src/auth/keychain.ts +0 -168
  105. package/src/auth/oauth-pkce.test.ts +0 -329
  106. package/src/auth/oauth-pkce.ts +0 -571
  107. package/src/auth/oauth-server.test.ts +0 -83
  108. package/src/auth/oauth-server.ts +0 -296
  109. package/src/config.test.ts +0 -479
  110. package/src/config.ts +0 -505
  111. package/src/delegation.test.ts +0 -368
  112. package/src/delegation.ts +0 -605
  113. package/src/index.ts +0 -280
  114. package/src/policy.test.ts +0 -634
  115. package/src/policy.ts +0 -420
  116. package/src/providers/adapter.test.ts +0 -293
  117. package/src/providers/adapter.ts +0 -328
  118. package/src/providers/catalog-data.test.ts +0 -258
  119. package/src/providers/catalog-data.ts +0 -498
  120. package/src/providers/catalog.test.ts +0 -84
  121. package/src/providers/catalog.ts +0 -483
  122. package/src/providers/fake.ts +0 -312
  123. package/src/providers/openai-compatible.test.ts +0 -366
  124. package/src/providers/openai-compatible.ts +0 -554
  125. package/src/proxy/responses-mapper.test.ts +0 -290
  126. package/src/proxy/responses-mapper.ts +0 -736
  127. package/src/proxy/responses-server.test.ts +0 -322
  128. package/src/proxy/responses-server.ts +0 -469
  129. package/src/router.test.ts +0 -699
  130. package/src/router.ts +0 -352
  131. package/src/schemas.test.ts +0 -291
  132. package/src/secrets.test.ts +0 -271
  133. package/src/secrets.ts +0 -461
  134. package/src/types.ts +0 -173
  135. package/src/usage-file.test.ts +0 -243
  136. package/src/usage-file.ts +0 -435
  137. package/src/usage.test.ts +0 -335
  138. package/src/usage.ts +0 -859
  139. package/tsconfig.json +0 -9
@@ -1,483 +0,0 @@
1
- /**
2
- * Static provider/model catalog for offline subscription-first routing tests.
3
- * No credential scraping; entries describe discovery shapes only.
4
- */
5
-
6
- import type {
7
- AuthScope,
8
- AuthTier,
9
- ProviderCandidate,
10
- ProviderHealth,
11
- SubscriptionStatus,
12
- } from "../types.js";
13
- import {
14
- selectBuiltinProviders,
15
- type BuiltinCatalogConfig,
16
- type BuiltinProviderEntry,
17
- } from "./catalog-data.js";
18
-
19
- /** Logical model family shared across auth tiers for preference testing. */
20
- export type LogicalModelFamily =
21
- "frontier-coder" | "fast-coder" | "local-coder" | "vision-helper";
22
-
23
- export interface CatalogEntry {
24
- providerId: string;
25
- modelId: string;
26
- displayName: string;
27
- family: LogicalModelFamily;
28
- authTier: AuthTier;
29
- authScope: AuthScope;
30
- subscriptionStatus?: SubscriptionStatus;
31
- health: ProviderHealth;
32
- supportsTools: boolean;
33
- supportsStreaming: boolean;
34
- supportsJson: boolean;
35
- supportsImages: boolean;
36
- contextWindowTokens?: number;
37
- maxOutputTokens?: number;
38
- estimatedCostUsd?: number;
39
- notes?: string;
40
- }
41
-
42
- /**
43
- * Built-in catalog: for several families, ship subscription + local + api-key
44
- * candidates so routing prefers subscription without live credentials.
45
- */
46
- export const STATIC_CATALOG: readonly CatalogEntry[] = [
47
- // frontier-coder across all three tiers
48
- {
49
- providerId: "claude-subscription",
50
- modelId: "claude-sonnet-4",
51
- displayName: "Claude Sonnet (subscription)",
52
- family: "frontier-coder",
53
- authTier: "subscription",
54
- authScope: "first-party",
55
- subscriptionStatus: "active",
56
- health: "healthy",
57
- supportsTools: true,
58
- supportsStreaming: true,
59
- supportsJson: true,
60
- supportsImages: true,
61
- contextWindowTokens: 200_000,
62
- maxOutputTokens: 32_000,
63
- estimatedCostUsd: 0,
64
- notes: "Official harness subscription capacity",
65
- },
66
- {
67
- providerId: "ollama",
68
- modelId: "qwen2.5-coder:32b",
69
- displayName: "Qwen2.5 Coder 32B (local)",
70
- family: "frontier-coder",
71
- authTier: "local",
72
- authScope: "first-party",
73
- health: "healthy",
74
- supportsTools: true,
75
- supportsStreaming: true,
76
- supportsJson: true,
77
- supportsImages: false,
78
- contextWindowTokens: 32_768,
79
- maxOutputTokens: 8_192,
80
- estimatedCostUsd: 0,
81
- },
82
- {
83
- providerId: "openrouter",
84
- modelId: "anthropic/claude-sonnet-4",
85
- displayName: "Claude Sonnet via OpenRouter",
86
- family: "frontier-coder",
87
- authTier: "api-key",
88
- authScope: "third-party",
89
- health: "healthy",
90
- supportsTools: true,
91
- supportsStreaming: true,
92
- supportsJson: true,
93
- supportsImages: true,
94
- contextWindowTokens: 200_000,
95
- maxOutputTokens: 32_000,
96
- estimatedCostUsd: 0.03,
97
- },
98
- {
99
- providerId: "xai",
100
- modelId: "grok-code-fast-1",
101
- displayName: "Grok Code Fast (api-key)",
102
- family: "frontier-coder",
103
- authTier: "api-key",
104
- authScope: "first-party",
105
- health: "healthy",
106
- supportsTools: true,
107
- supportsStreaming: true,
108
- supportsJson: true,
109
- supportsImages: false,
110
- contextWindowTokens: 131_072,
111
- maxOutputTokens: 16_384,
112
- estimatedCostUsd: 0.02,
113
- },
114
-
115
- // fast-coder across tiers
116
- {
117
- providerId: "codex-subscription",
118
- modelId: "gpt-5-codex",
119
- displayName: "GPT Codex (subscription)",
120
- family: "fast-coder",
121
- authTier: "subscription",
122
- authScope: "first-party",
123
- subscriptionStatus: "active",
124
- health: "healthy",
125
- supportsTools: true,
126
- supportsStreaming: true,
127
- supportsJson: true,
128
- supportsImages: false,
129
- contextWindowTokens: 128_000,
130
- maxOutputTokens: 16_384,
131
- estimatedCostUsd: 0,
132
- },
133
- {
134
- providerId: "lmstudio",
135
- modelId: "local-fast-coder",
136
- displayName: "Local Fast Coder",
137
- family: "fast-coder",
138
- authTier: "local",
139
- authScope: "first-party",
140
- health: "healthy",
141
- supportsTools: true,
142
- supportsStreaming: true,
143
- supportsJson: true,
144
- supportsImages: false,
145
- contextWindowTokens: 16_384,
146
- maxOutputTokens: 4_096,
147
- estimatedCostUsd: 0,
148
- },
149
- {
150
- providerId: "openrouter",
151
- modelId: "openai/gpt-4.1-mini",
152
- displayName: "GPT-4.1 Mini (OpenRouter)",
153
- family: "fast-coder",
154
- authTier: "api-key",
155
- authScope: "third-party",
156
- health: "healthy",
157
- supportsTools: true,
158
- supportsStreaming: true,
159
- supportsJson: true,
160
- supportsImages: false,
161
- contextWindowTokens: 128_000,
162
- maxOutputTokens: 16_384,
163
- estimatedCostUsd: 0.005,
164
- },
165
-
166
- // local-coder family (local preferred; still include subscription / api-key siblings)
167
- {
168
- providerId: "ollama",
169
- modelId: "deepseek-coder-v2:lite",
170
- displayName: "DeepSeek Coder Lite (local)",
171
- family: "local-coder",
172
- authTier: "local",
173
- authScope: "first-party",
174
- health: "healthy",
175
- supportsTools: true,
176
- supportsStreaming: true,
177
- supportsJson: true,
178
- supportsImages: false,
179
- contextWindowTokens: 16_384,
180
- maxOutputTokens: 4_096,
181
- estimatedCostUsd: 0,
182
- },
183
- {
184
- providerId: "gemini-subscription",
185
- modelId: "gemini-2.5-flash",
186
- displayName: "Gemini Flash (subscription)",
187
- family: "local-coder",
188
- authTier: "subscription",
189
- authScope: "first-party",
190
- subscriptionStatus: "active",
191
- health: "healthy",
192
- supportsTools: true,
193
- supportsStreaming: true,
194
- supportsJson: true,
195
- supportsImages: false,
196
- contextWindowTokens: 1_000_000,
197
- maxOutputTokens: 8_192,
198
- estimatedCostUsd: 0,
199
- },
200
- {
201
- providerId: "openrouter",
202
- modelId: "deepseek/deepseek-coder",
203
- displayName: "DeepSeek Coder (OpenRouter)",
204
- family: "local-coder",
205
- authTier: "api-key",
206
- authScope: "third-party",
207
- health: "healthy",
208
- supportsTools: true,
209
- supportsStreaming: true,
210
- supportsJson: true,
211
- supportsImages: false,
212
- contextWindowTokens: 64_000,
213
- maxOutputTokens: 8_192,
214
- estimatedCostUsd: 0.002,
215
- },
216
-
217
- // vision-helper
218
- {
219
- providerId: "claude-subscription",
220
- modelId: "claude-sonnet-4-vision",
221
- displayName: "Claude Vision (subscription)",
222
- family: "vision-helper",
223
- authTier: "subscription",
224
- authScope: "first-party",
225
- subscriptionStatus: "active",
226
- health: "healthy",
227
- supportsTools: true,
228
- supportsStreaming: true,
229
- supportsJson: true,
230
- supportsImages: true,
231
- contextWindowTokens: 200_000,
232
- maxOutputTokens: 16_384,
233
- estimatedCostUsd: 0,
234
- },
235
- {
236
- providerId: "openrouter",
237
- modelId: "openai/gpt-4o",
238
- displayName: "GPT-4o Vision (api-key)",
239
- family: "vision-helper",
240
- authTier: "api-key",
241
- authScope: "third-party",
242
- health: "healthy",
243
- supportsTools: true,
244
- supportsStreaming: true,
245
- supportsJson: true,
246
- supportsImages: true,
247
- contextWindowTokens: 128_000,
248
- maxOutputTokens: 16_384,
249
- estimatedCostUsd: 0.04,
250
- },
251
- {
252
- providerId: "ollama",
253
- modelId: "llava:latest",
254
- displayName: "LLaVA (local vision)",
255
- family: "vision-helper",
256
- authTier: "local",
257
- authScope: "first-party",
258
- health: "healthy",
259
- supportsTools: false,
260
- supportsStreaming: true,
261
- supportsJson: false,
262
- supportsImages: true,
263
- contextWindowTokens: 4_096,
264
- maxOutputTokens: 2_048,
265
- estimatedCostUsd: 0,
266
- },
267
-
268
- // Degraded / unconfigured samples for status tests
269
- {
270
- providerId: "claude-subscription",
271
- modelId: "claude-opus-4",
272
- displayName: "Claude Opus (subscription exhausted)",
273
- family: "frontier-coder",
274
- authTier: "subscription",
275
- authScope: "first-party",
276
- subscriptionStatus: "exhausted",
277
- health: "degraded",
278
- supportsTools: true,
279
- supportsStreaming: true,
280
- supportsJson: true,
281
- supportsImages: true,
282
- contextWindowTokens: 200_000,
283
- maxOutputTokens: 32_000,
284
- estimatedCostUsd: 0,
285
- },
286
- {
287
- providerId: "codex-subscription",
288
- modelId: "o3",
289
- displayName: "o3 (subscription unconfigured)",
290
- family: "fast-coder",
291
- authTier: "subscription",
292
- authScope: "first-party",
293
- subscriptionStatus: "unconfigured",
294
- health: "dead",
295
- supportsTools: true,
296
- supportsStreaming: true,
297
- supportsJson: true,
298
- supportsImages: false,
299
- contextWindowTokens: 200_000,
300
- maxOutputTokens: 32_000,
301
- estimatedCostUsd: 0,
302
- },
303
- ] as const;
304
-
305
- export interface LoadCatalogOptions {
306
- /** Optional filter by provider id. */
307
- providerIds?: string[];
308
- /** Optional filter by logical family. */
309
- families?: LogicalModelFamily[];
310
- /** Optional filter by auth tier. */
311
- authTiers?: AuthTier[];
312
- /** Include degraded/dead by default true. */
313
- includeUnhealthy?: boolean;
314
- }
315
-
316
- export function loadStaticCatalog(
317
- options: LoadCatalogOptions = {},
318
- ): CatalogEntry[] {
319
- const includeUnhealthy = options.includeUnhealthy ?? true;
320
- return STATIC_CATALOG.filter((entry) => {
321
- if (
322
- options.providerIds &&
323
- !options.providerIds.includes(entry.providerId)
324
- ) {
325
- return false;
326
- }
327
- if (options.families && !options.families.includes(entry.family)) {
328
- return false;
329
- }
330
- if (options.authTiers && !options.authTiers.includes(entry.authTier)) {
331
- return false;
332
- }
333
- if (!includeUnhealthy && entry.health !== "healthy") {
334
- return false;
335
- }
336
- return true;
337
- }).map((e) => ({ ...e }));
338
- }
339
-
340
- export function catalogToCandidates(
341
- entries: readonly CatalogEntry[] = STATIC_CATALOG,
342
- ): ProviderCandidate[] {
343
- return entries.map((e) => ({
344
- providerId: e.providerId,
345
- modelId: e.modelId,
346
- displayName: e.displayName,
347
- authTier: e.authTier,
348
- authScope: e.authScope,
349
- subscriptionStatus: e.subscriptionStatus,
350
- health: e.health,
351
- supportsTools: e.supportsTools,
352
- supportsStreaming: e.supportsStreaming,
353
- supportsJson: e.supportsJson,
354
- supportsImages: e.supportsImages,
355
- contextWindowTokens: e.contextWindowTokens,
356
- maxOutputTokens: e.maxOutputTokens,
357
- estimatedCostUsd: e.estimatedCostUsd,
358
- }));
359
- }
360
-
361
- /** Families that must expose all three auth tiers for offline preference tests. */
362
- export const MULTI_TIER_FAMILIES: readonly LogicalModelFamily[] = [
363
- "frontier-coder",
364
- "fast-coder",
365
- "local-coder",
366
- "vision-helper",
367
- ];
368
-
369
- export function familyAuthTiers(family: LogicalModelFamily): Set<AuthTier> {
370
- const tiers = new Set<AuthTier>();
371
- for (const e of STATIC_CATALOG) {
372
- if (e.family === family) tiers.add(e.authTier);
373
- }
374
- return tiers;
375
- }
376
-
377
- // ---------------------------------------------------------------------------
378
- // Built-in catalog: merge static entries with built-in provider metadata.
379
- // Keeps existing static loading behavior intact; subscription-first invariant
380
- // is preserved because builtin entries only add api-key/local candidates and
381
- // never replace the subscription rows already present in STATIC_CATALOG.
382
- // ---------------------------------------------------------------------------
383
-
384
- export interface LoadBuiltinCatalogOptions {
385
- /** Per-provider overrides keyed by providerId (mirrors config providers map). */
386
- providers?: BuiltinCatalogConfig;
387
- /** Optional filter by logical family. */
388
- families?: LogicalModelFamily[];
389
- /** Optional filter by auth tier. */
390
- authTiers?: AuthTier[];
391
- /** Include degraded/dead by default true. */
392
- includeUnhealthy?: boolean;
393
- }
394
-
395
- /** Flatten built-in provider metadata into catalog rows (copies). */
396
- function builtinProvidersToEntries(
397
- providers: readonly BuiltinProviderEntry[],
398
- ): CatalogEntry[] {
399
- const entries: CatalogEntry[] = [];
400
- for (const provider of providers) {
401
- for (const model of provider.knownModels) {
402
- entries.push({
403
- providerId: provider.providerId,
404
- modelId: model.modelId,
405
- displayName:
406
- model.displayName ??
407
- `${provider.displayName} ${model.modelId}`,
408
- family: model.family,
409
- authTier: provider.defaultAuthTier,
410
- authScope: provider.defaultAuthScope,
411
- health: "healthy",
412
- supportsTools: model.supportsTools ?? true,
413
- supportsStreaming: model.supportsStreaming ?? true,
414
- supportsJson: model.supportsJson ?? true,
415
- supportsImages: model.supportsImages ?? false,
416
- contextWindowTokens: model.contextWindowTokens,
417
- maxOutputTokens: model.maxOutputTokens,
418
- notes: provider.subscriptionSurfaceNote,
419
- });
420
- }
421
- }
422
- return entries;
423
- }
424
-
425
- /**
426
- * Structured dedup key for catalog rows. Serializes the discriminating fields
427
- * as a JSON tuple so there is no ad hoc delimiter parsing or collision risk.
428
- */
429
- function catalogEntryDedupKey(entry: CatalogEntry): string {
430
- return JSON.stringify([
431
- entry.providerId,
432
- entry.modelId,
433
- entry.authTier,
434
- entry.family,
435
- ]);
436
- }
437
-
438
- /**
439
- * Load the merged built-in catalog: static rows plus built-in provider
440
- * metadata, with provider enable/disable and base URL overrides applied.
441
- * Existing static loading behavior (loadStaticCatalog) is unchanged.
442
- */
443
- export function loadBuiltinCatalog(
444
- options: LoadBuiltinCatalogOptions = {},
445
- ): CatalogEntry[] {
446
- const includeUnhealthy = options.includeUnhealthy ?? true;
447
- const config = options.providers ?? {};
448
- const providerEnabled = (providerId: string): boolean =>
449
- config[providerId]?.enabled ?? true;
450
-
451
- const builtinEntries = builtinProvidersToEntries(
452
- selectBuiltinProviders(config),
453
- );
454
-
455
- // Merge static catalog with built-in entries, applying the provider
456
- // enable/disable filter to both sources and deduping identical rows.
457
- const merged: CatalogEntry[] = [];
458
- const seen = new Set<string>();
459
- const sources: readonly CatalogEntry[] = [
460
- ...STATIC_CATALOG.map((entry) => ({ ...entry })),
461
- ...builtinEntries,
462
- ];
463
- for (const entry of sources) {
464
- if (!providerEnabled(entry.providerId)) continue;
465
- const key = catalogEntryDedupKey(entry);
466
- if (seen.has(key)) continue;
467
- seen.add(key);
468
- merged.push(entry);
469
- }
470
-
471
- return merged.filter((entry) => {
472
- if (options.families && !options.families.includes(entry.family)) {
473
- return false;
474
- }
475
- if (options.authTiers && !options.authTiers.includes(entry.authTier)) {
476
- return false;
477
- }
478
- if (!includeUnhealthy && entry.health !== "healthy") {
479
- return false;
480
- }
481
- return true;
482
- });
483
- }