@qadanm/opencode-smart-router 0.0.0-stage → 0.6.1

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 (62) hide show
  1. package/CHANGELOG.md +286 -0
  2. package/CONTRIBUTING.md +103 -0
  3. package/LICENSE +21 -0
  4. package/README.md +425 -2
  5. package/SECURITY.md +174 -0
  6. package/config.example.json +107 -0
  7. package/dist/npm/index.js +40 -0
  8. package/docs/ARCHITECTURE.md +104 -0
  9. package/docs/DEBUGGING-GUI.md +70 -0
  10. package/docs/INSTALL.md +151 -0
  11. package/docs/LIMITATIONS.md +230 -0
  12. package/docs/MODELS.md +124 -0
  13. package/docs/OPENDESIGN-INTEGRATION.md +635 -0
  14. package/docs/QA.md +142 -0
  15. package/docs/SHARED-CORE.md +114 -0
  16. package/install +75 -0
  17. package/package.json +94 -4
  18. package/scripts/install.sh +324 -0
  19. package/scripts/uninstall.sh +91 -0
  20. package/src/allowance.ts +135 -0
  21. package/src/core/classify/classifier.ts +305 -0
  22. package/src/core/config-writer.ts +167 -0
  23. package/src/core/config.ts +1047 -0
  24. package/src/core/context/strategy.ts +188 -0
  25. package/src/core/context-health.ts +39 -0
  26. package/src/core/failures.ts +398 -0
  27. package/src/core/history/opencode.ts +173 -0
  28. package/src/core/models/protocol.ts +22 -0
  29. package/src/core/models/registry.ts +204 -0
  30. package/src/core/models/seed.ts +781 -0
  31. package/src/core/offline/mode.ts +321 -0
  32. package/src/core/plan-page.ts +390 -0
  33. package/src/core/plans/catalog.ts +487 -0
  34. package/src/core/policy/calibration.ts +259 -0
  35. package/src/core/policy/eligibility.ts +630 -0
  36. package/src/core/providers/enablement.ts +143 -0
  37. package/src/core/providers/opendesign-credits.ts +206 -0
  38. package/src/core/providers/opendesign-models.ts +654 -0
  39. package/src/core/providers/opendesign.ts +474 -0
  40. package/src/core/quota/calibration.ts +166 -0
  41. package/src/core/quota/commandcode.ts +471 -0
  42. package/src/core/quota/credentials.ts +490 -0
  43. package/src/core/quota/ledger.ts +1304 -0
  44. package/src/core/quota/opencode-go.ts +148 -0
  45. package/src/core/quota/opendesign.ts +1043 -0
  46. package/src/core/quota/reconcile.ts +155 -0
  47. package/src/core/quota/report.ts +261 -0
  48. package/src/core/quota/service.ts +273 -0
  49. package/src/core/reprompt.ts +48 -0
  50. package/src/core/routing/engine.ts +978 -0
  51. package/src/core/types.ts +866 -0
  52. package/src/core/ui.ts +236 -0
  53. package/src/core/util.ts +114 -0
  54. package/src/gui/index.tsx +1675 -0
  55. package/src/gui/viewmodel.ts +456 -0
  56. package/src/index.ts +33 -0
  57. package/src/rpc.ts +204 -0
  58. package/src/server/containment.ts +131 -0
  59. package/src/server/controller.ts +3405 -0
  60. package/src/server/route-tracker.ts +60 -0
  61. package/src/server/shared-core.ts +701 -0
  62. package/src/tui/index.tsx +168 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,286 @@
1
+ # Changelog
2
+
3
+ Notable changes to OpenCode Smart Router. Dates are the day the change landed on
4
+ `main`.
5
+
6
+ ## Unreleased
7
+
8
+ ### Added
9
+
10
+ - **Launch kit** (`docs/LAUNCH-KIT.md`): paste-ready copy for Discord, Hacker
11
+ News, X, and the opencode.cafe submission, plus the demo film assets.
12
+
13
+ ## 0.6.1 — 2026-11-06
14
+
15
+ A defect in the router can no longer stop the editor.
16
+
17
+ Version 0.6.0 could refuse every prompt, in every mode, until the plugin was
18
+ removed by hand. Two defects compounded to cause it. This release fixes both,
19
+ and adds the missing test layer that would have caught them. The full account is
20
+ in [docs/INCIDENT-2026-11-06-prompt-lockout.md](docs/INCIDENT-2026-11-06-prompt-lockout.md).
21
+
22
+ ### Fixed
23
+
24
+ - **An inherited shared core is no longer adopted.** The process-wide core in
25
+ `globalThis` is stamped with `SHARED_CORE_API`; a core from a different
26
+ version is replaced rather than reused. A new bundle could previously inherit
27
+ a core built by an older release, whose ledger had no `planPoolSpent()`, and
28
+ every prompt then threw. `init()` also repairs a ledger that is not a
29
+ `LedgerStore` instance.
30
+ - **`routing.failClosed` defaults to `false`.** Blocking is now opt-in and means
31
+ one thing: what to do when this plugin cannot produce a decision. The old
32
+ default escalated an internal `TypeError` into a refusal to serve the prompt,
33
+ including in manual mode. An internal defect now degrades to "do not route",
34
+ which is what would have happened with the plugin uninstalled.
35
+ - **Quota safety is a separate, unconditional gate.** A provider configured for
36
+ probing but with no reading is still refused, regardless of `failClosed`,
37
+ because that is a spending decision rather than a response to plugin failure.
38
+ - **A failed setup installs no prompt hook.** The entry point used to leave a
39
+ "minimal fail-closed hook" behind when `initialize()` threw, turning a failed
40
+ setup into an editor that refused every message. A location whose setup failed
41
+ now has no router.
42
+ - **Hooks cannot throw or hang at the host boundary.** OpenCode does not catch a
43
+ plugin hook that throws and does not time out one that never settles; both
44
+ were verified against a real OpenCode 2.2.5 process. Every hook is wrapped in
45
+ `containHook()`, and routing runs against a 10 second deadline
46
+ (`withDeadline()`), after which the prompt proceeds unrouted.
47
+ - **Routing only considers models the host can serve.** The router offered the
48
+ whole seed catalog as candidates. On a fresh install it switched a session to
49
+ `opencode-go/claude-haiku-5-5`, which that machine did not have, and the next
50
+ step failed with "Model unavailable". Candidates are now filtered to the
51
+ host's own model list, and an empty result means no route rather than an
52
+ unfiltered fallback.
53
+ - **Model discovery is awaited on the first routing decision.** Discovery starts
54
+ in the background, so the first prompt after startup could route against an
55
+ empty list, which is the window in which an unservable model was reachable.
56
+
57
+ ### Added
58
+
59
+ - `scripts/isolated-e2e.sh`: runs a real OpenCode in a temporary HOME with its
60
+ own config, data, and cache, installs a deliberately broken plugin, and
61
+ asserts only that OpenCode still answers a prompt. It refuses to run if any
62
+ path escapes the sandbox. Seven scenarios pass; two are recorded as host
63
+ limitations because they demonstrate that OpenCode has no defense at that
64
+ boundary. It runs against a loopback model stub that it starts itself, so it
65
+ needs no credentials and cannot be blocked by provider quota. A CI job runs
66
+ it on every push.
67
+ - Regression suites: `test/shared-core-skew.test.ts` and
68
+ `test/shared-core-adoption.test.ts` (a stale core must not be adopted or break
69
+ routing), `test/fail-open-contract.test.ts` (an internal defect must not block
70
+ a prompt), `test/containment.test.ts` (hooks cannot throw or hang),
71
+ `test/setup-failure-never-blocks.test.ts` (a failed setup leaves no router),
72
+ `test/routing-servable-models.test.ts` (only servable models are candidates).
73
+ - Six new entries in the mutation harness, one per corrected property.
74
+
75
+ ### Changed
76
+
77
+ - `config.example.json` ships `failClosed: false`. Copying the example used to
78
+ inherit the old blocking default.
79
+ - `scripts/cleanroom-verify.sh` now overlays the working tree onto `HEAD` and
80
+ reports how many uncommitted paths it covered. Before this, a run with work in
81
+ progress verified the previous commit and reported a pass that said nothing
82
+ about the changes being made.
83
+
84
+ ### Fixed in CI
85
+
86
+ - The `host-survival` job now installs OpenCode V2 from its release binaries
87
+ instead of Homebrew, which does not exist on Ubuntu runners, and verifies the
88
+ download against the published `SHA256SUMS`. It also asserts the major version
89
+ is 2, so a future change to the installer cannot quietly point the suite at
90
+ OpenCode 1, whose hook surface and CLI differ.
91
+ - `test/history.test.ts` no longer fails on the declared Node 20.12 floor, where
92
+ `node:sqlite` does not exist. The reader already degraded gracefully there; the
93
+ test now asserts that degradation and skips only the two cases that genuinely
94
+ require a real database. The suite passes on 20.12 with two honest skips rather
95
+ than two failures.
96
+ - The eight calibration properties the mutation harness listed as unguarded now
97
+ have `test/calibration-policy.test.ts`. They were not missing guards, they were
98
+ pointed at a file that covers a different module, so the properties were never
99
+ exercised. All 69 mutations are now caught.
100
+
101
+ ## 0.6.0 — 2026-11-06
102
+
103
+ Tier-3 work escalates by itself, paid from the plan-model pool.
104
+
105
+ ### Added
106
+
107
+ - **Automatic flagship escalation** (`premium.mode: "auto"`, now the default):
108
+ a tier-3 task routes to the plan-model pool (Claude Sonnet 5.5, GPT-6.1 Sol)
109
+ without a per-task approval prompt while the pool has budget. When the pool
110
+ is spent, escalation degrades to an approval gate and the router serves the
111
+ task with the strongest ordinary model - a routing refusal never fails a
112
+ serviceable task. Tier-1 and tier-2 work always stays on the ordinary pool.
113
+ - **Plan-model pool ledger**: monthly buckets tracking spend against the
114
+ OpenDesign plan-model allowance (Pro: $120/month), persisted across
115
+ restarts, separate from Design Plan credits and from other providers'
116
+ premium spend. The cap is read from the resolved plan catalog.
117
+ - **Per-kind capability evidence**: `ModelRecord.capabilities` (coding,
118
+ debugging, agentic, architecture) from named benchmarks with provenance,
119
+ moving expected success by at most 4 points - enough to break a close
120
+ ranking, never enough to overrule a gate. Unscored models stay neutral:
121
+ absent evidence is unknown, not zero.
122
+ - **Context probes** (`scripts/probe-plan-model-context.py`): end-to-end
123
+ 800K-token verification with memorization sentinels and truncation checks.
124
+
125
+ ### Changed
126
+
127
+ - GPT-6.1 Sol is scored (intelligence 55, Artificial Analysis v5.0.1) and
128
+ auto-approved for tier-3 escalation along with Sonnet 5.5, both by owner
129
+ instruction 2026-11-06. DeepSeek's observed 39.5 is untouched.
130
+ - Catalog context windows raised from measured (328K/400K) to probed-verified
131
+ 800K for both flagships after passed end-to-end probes on 2026-11-06; the
132
+ advertised 1M is deliberately NOT claimed, and Sol's long-context surcharge
133
+ above 272K was confirmed by the billed amounts.
134
+ - MiniMax M3.1 Flash is classified as a Design Plan model. The gateway serves
135
+ it both inside and outside the Design Plan group and OpenDesign has not
136
+ published which applies, so the conservative choice is made: treating an
137
+ ordinary tier-2 workhorse as a plan model keeps routine work from draining
138
+ the escalation pool. Before this, a tier-3 task could land on it as a
139
+ premium model that a tier-2 flag itself made eligible.
140
+
141
+ ### Fixed
142
+
143
+ - A partial settings file no longer produces a warning for every key it leaves
144
+ out. Absent and invalid are now distinct: a key you did not write takes its
145
+ default silently, while a key you did write with the wrong type still reports
146
+ the problem. Before this, a config that only set `providers.opendesign.plan`
147
+ emitted around fifty warnings and hid the ones that mattered.
148
+
149
+ ## 0.5.0 — 2026-11-06
150
+
151
+ The router stops taking things that are not its to take.
152
+
153
+ ### Changed
154
+
155
+ - **The router no longer renames sessions.** The `AUTO · OD · model — name`
156
+ title prefix is gone, for new chats and for every chat going forward. A chat
157
+ title is the owner's name for the work; status belongs in the panel, the
158
+ terminal footer and the decision record. The `ui.titleIndicator` setting is
159
+ removed and ignored if present.
160
+ - `/router-title off` now strips prefixes this plugin wrote in earlier versions
161
+ and reports how many titles it cleaned. Titles that lost their separator are
162
+ left alone rather than rewritten.
163
+ - Existing installs were repaired in place: every prefixed title was restored
164
+ from its separator, or from the session's own first user message when the
165
+ separator was already gone.
166
+
167
+ ### Added
168
+
169
+ - Earlier review pass carried in this release: self-heal floor fallback, real
170
+ safety-margin enforcement, task-scoped rejection memory, strict failure joins,
171
+ request-clock threading, and metric-neutral decision notes.
172
+
173
+ ### Removed
174
+
175
+ - **Sidebar project grouping (never released on npm).** The desktop app ships
176
+ this natively behind Settings -> Experimental -> Organize vertical sidebar ->
177
+ Projects (upstream commit `2bb4988`, after the installed v2.2.5). A plugin
178
+ copy would duplicate app code and fight the native version. The reasoning and
179
+ the prior-art notes are in `docs/SIDEBAR-GROUPING-TABLED.md`.
180
+
181
+ ## 0.4.0 — 2026-11-05
182
+
183
+ Self-healing routing, and npm distribution.
184
+
185
+ ### Added
186
+
187
+ - **Self-healing routing.** A rebuilt session can carry mid-session notices
188
+ into the prompt as system roles, which kills the turn on any
189
+ Anthropic-protocol model with a 400 error. The router folds those notices
190
+ into user messages before the request leaves, refuses the host retry on a
191
+ deterministic rejection, and continues the turn on the next best eligible
192
+ model with one notice. The panel records what was rejected and what took
193
+ over. Found on a live Command Code session: the error was
194
+ "Invalid discriminator value. Expected 'user' | 'assistant' at
195
+ messages.23.role".
196
+ - **npm package** (`@qadanm/opencode-smart-router`). One self-contained file
197
+ with no runtime dependencies, loadable with a single `plugin` entry so the
198
+ router appears in OpenCode's own ecosystem path. The full experience stays
199
+ with the installer.
200
+
201
+ ## 0.3.0 — 2026-11-05
202
+
203
+ The release where the router gained a face, and where several numbers it had been
204
+ asserting turned out to be wrong.
205
+
206
+ ### Added
207
+
208
+ - **Desktop panel** (`./gui-experimental`). A side panel in OpenCode Desktop that
209
+ shows the last routing decision, why the chosen model won, every candidate that
210
+ lost with the reason, per-provider allowances with all their windows, and the
211
+ provider list. Built on the OpenCode GUI plugin API, which the SDK marks
212
+ experimental.
213
+ - **First-run onboarding.** Reports the providers found on this machine, what was
214
+ imported from history, and what the router will do — before asking for anything.
215
+ Evidence rather than a questionnaire, because detection already did the finding.
216
+ - **Terminal parity** (`./tui`). `/router-why` and `/router-quota`, plus a footer
217
+ status slot. Same view-model as the panel, so the two cannot disagree.
218
+ - **Calibration.** The safety margin is now measured from the charges the provider
219
+ reported rather than assumed. Narrows with evidence; stays wide and warns when
220
+ the charges imply a wrong price.
221
+ - **History import from OpenCode's own database.** Per-message, so each event
222
+ carries its real timestamp and the model that actually served it. This is what
223
+ makes a fixed seven-day window accurate instead of smeared onto session
224
+ boundaries.
225
+ - **Per-session automatic-routing switch.** Stops the router changing the model
226
+ without disabling it. `SessionState.paused` existed but nothing read it, so this
227
+ also fixed a switch that would have silently done nothing.
228
+ - **Account reading by paste.** OpenDesign exposes no usage API, so the estimate
229
+ can only see traffic the router routed. Pasting the Plan & balance page seeds
230
+ the real figure. Reconciled against the catalog, which reports disagreement
231
+ rather than absorbing it.
232
+
233
+ ### Fixed
234
+
235
+ - **Thirteen OpenDesign models were priced wrong**, some by 4x in both directions.
236
+ The catalog stored a base price and then applied the Design Plan multiplier on
237
+ top, and several entries already had the multiplier baked in. Verified against
238
+ the published CoRouter catalog and pinned by 36 assertions.
239
+ - **The Plus tier had no weekly cap**, making it look able to spend a whole month
240
+ in one week.
241
+ - **The seven-day window was modelled as rolling** when the vendor documents it as
242
+ fixed from the subscription start, and the estimator ignored the declaration
243
+ entirely even after the catalog was corrected.
244
+ - **The reported cost was discarded.** Every step carries the real charge, and the
245
+ estimator recomputed an approximation of it from a static table instead.
246
+ - **Command Code's DeepSeek V4.1 Flash sent a model id the gateway rejects**, which
247
+ surfaced as "The provider rejected this request" on manual selection.
248
+ - **Session titles were rewritten, and could be destroyed.** `titleIndicator`
249
+ defaulted to true and compact mode returned the prefix alone, losing the name
250
+ irrecoverably. It now defaults to false and never derives a name from its own
251
+ previous output.
252
+ - **Every provider was enabled by default** regardless of whether the machine had
253
+ an account, so a new install produced authentication failures for providers the
254
+ user never claimed. Enablement now follows discovered credentials, and an
255
+ explicit configuration always wins.
256
+ - **A spent allowance was not recognised.** There was no `quota_exhausted` failure
257
+ class, so a 402 was retried and never marked the provider spent. It now stops
258
+ the turn and holds the provider back until its window resets.
259
+ - **`DEPLOYMENT.json` could not be trusted.** It recorded the commit but not that
260
+ the tree was dirty, so three installs were attributed to the wrong revision. It
261
+ now records the diff hash, the changed file count and the GUI bundle hash.
262
+
263
+ ### Changed
264
+
265
+ - **`ui.titleIndicator` now defaults to `false`.** The router no longer edits chat
266
+ names. Anyone who wants the indicator back can set it, and turning it off again
267
+ restores any prefix a previous version added.
268
+ - **Provider enablement is credential-aware.** A provider the user never mentioned
269
+ is enabled only when a credential or connection exists for it.
270
+ - Prices, ceilings and multipliers are now checked against vendor documentation by
271
+ tests, so they cannot drift silently.
272
+
273
+ ### Known limitations
274
+
275
+ - OpenDesign has no usage API for an inference key, and the account-control
276
+ endpoints require a credential the product does not issue. `nexu-io/open-design`
277
+ issue #8674 requests one and remains open. The estimate plus a pasted reading is
278
+ the best available figure.
279
+ - The GUI plugin API is marked experimental by the SDK and may change between
280
+ OpenCode releases.
281
+
282
+ ## 0.2.0 — prior to this changelog
283
+
284
+ Subscription-aware routing across OpenCode Go, Command Code GOAT and OpenDesign,
285
+ with nested usage windows, multi-pool credit accounting and context policy. See
286
+ `docs/ARCHITECTURE.md`.
@@ -0,0 +1,103 @@
1
+ # Contributing
2
+
3
+ This project is small and deliberately narrow in scope, so a precise bug report
4
+ is usually more useful than a large patch.
5
+
6
+ ## Before you start
7
+
8
+ Open an issue describing what you want to change and why. For anything touching
9
+ credential handling, routing eligibility, or quota arithmetic, please discuss it
10
+ first. Those areas carry security and correctness properties that are easy to
11
+ break from the outside, and a short conversation saves a rejected pull request.
12
+
13
+ ## Ground rules
14
+
15
+ **Every change ships with a test.** `npm test` must pass. If behavior is hard to
16
+ test, treat that as a design signal.
17
+
18
+ **Never commit a credential, a token, an account identifier, or a personal file
19
+ path.** Every path in the codebase derives from `homedir()`. Keep it that way.
20
+ `safeError()` and `redact()` exist for this reason.
21
+
22
+ **Do not weaken a safety property to make a test pass.** When a test fails, the
23
+ test may be wrong (it has happened) or the code may be wrong. Work out which
24
+ before editing either.
25
+
26
+ **Provider facts need a source.** Allowances, multipliers, context windows, and
27
+ prices are claims about the world. Cite where a number came from, with a
28
+ retrieval date, in a comment. An unsourced number that looks authoritative is
29
+ worse than an admitted gap.
30
+
31
+ ## Development
32
+
33
+ ```bash
34
+ npm install
35
+ npm run typecheck # tsc --noEmit
36
+ npm test # vitest run
37
+ npm run check # both
38
+ npm run build:server # the plugin bundle
39
+ npm run build:allowance # the standalone allowance core
40
+ ```
41
+
42
+ Node 20.12 or newer.
43
+
44
+ ## Architecture boundaries
45
+
46
+ Two rules keep this maintainable and publishable.
47
+
48
+ **The allowance core stays portable.** `src/allowance.ts` is the public,
49
+ host-independent surface. It may import only `core/plans/catalog`,
50
+ `core/context/strategy`, `core/quota/report`, and `core/types`. It must never
51
+ touch the filesystem, the network, the environment, or the host's plugin API.
52
+ `test/allowance-api.test.ts` enforces this. If that test fails, the change is
53
+ wrong, not the test.
54
+
55
+ **Plans are data, not code.** A subscription tier is a `PlanDefinition` in
56
+ `src/core/plans/catalog.ts`. Do not add `if (plan === "pro")` to a routing or
57
+ quota module. If a new tier needs different behavior instead of different
58
+ numbers, that is an architecture discussion.
59
+
60
+ Provider HTTP handling belongs in `src/core/quota/*-provider.ts`. Routing policy
61
+ belongs in `src/core/routing/` and `src/core/policy/`.
62
+
63
+ ## Adding a provider
64
+
65
+ A provider adapter documents four things before it is accepted:
66
+
67
+ - which credential it needs, and whether that credential can read usage;
68
+ - every host it contacts, and whether each call is read-only;
69
+ - how it reports confidence (`provider`, `estimate`, or `unknown`), and why;
70
+ - what it does when the usage source is unavailable.
71
+
72
+ A provider whose limits cannot be read must report `estimated` or `unknown`. It
73
+ must never report an unknown allowance as unlimited.
74
+
75
+ ## Adding a plan
76
+
77
+ Add a `PlanDefinition` with a source citation and a retrieval date. Add a test
78
+ for the pool shape, the nesting invariants, and the absence of windows the plan
79
+ does not have. A plan with no five-hour window must not report "0% of a five-hour
80
+ limit," because that implies a ceiling nothing can hit.
81
+
82
+ ## Commit messages
83
+
84
+ Explain why, not what. The diff already shows what. A commit that fixes a subtle
85
+ bug should say how the bug showed up, because that part is not recoverable from
86
+ the code.
87
+
88
+ ## Releasing
89
+
90
+ See [README → Releases](README.md#releases). Publishing happens in CI through npm
91
+ trusted publishing, so a maintainer never handles a long-lived token or relays a
92
+ one-time password. Never publish from a working copy: the workflow checks the tag
93
+ against `package.json` and re-runs the full suite first.
94
+
95
+ ## Out of scope
96
+
97
+ Likely to be declined:
98
+
99
+ - spending money on your behalf;
100
+ - acquiring credentials or control keys automatically;
101
+ - disabling provider-side prompt caching;
102
+ - silently truncating or discarding conversation history;
103
+ - telemetry or analytics.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Moe Qadan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.