@toddzheng024/dscode-bundle 0.7.16 → 0.7.18

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.
@@ -25,8 +25,5 @@ Local changes: DSCODE effort, shell control, TUI commands and footer.
25
25
  @deepseek-ai/dsh-terminal-bash@0.1.5-rc.2: MIT; {"type":"git","url":"git+https://github.com/deepseek-ai/deepseek-harness.git","directory":"packages/terminal/terminal-bash"}
26
26
  Local changes: DSCODE effort, shell control, TUI commands and footer.
27
27
 
28
- @deepseek-ai/dsh-compaction-basic@0.1.5-rc.2: MIT; {"type":"git","url":"git+https://github.com/deepseek-ai/deepseek-harness.git","directory":"packages/compaction/compaction-basic"}
29
- Local changes: DSCODE effort, shell control, TUI commands and footer.
30
-
31
28
  dsh-code (DSCODE vendored terminal, forked from dsh-code@1.2.0): MIT; https://github.com/unlinearity/dsh-code
32
29
  Local changes: DSCODE UI (welcome header, activity line, footer telemetry, effort bar), commands, panels and paste handling.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.7.16",
2
+ "version": "0.7.18",
3
3
  "type": "module",
4
4
  "license": "MIT",
5
5
  "author": "Todd Zheng",
@@ -49,7 +49,7 @@
49
49
  "./bash": "./vendor/bash/index.js",
50
50
  "./persistent": "./vendor/persistent/index.js",
51
51
  "./terminal": "./vendor/terminal/index.js",
52
- "./compaction-basic": "./vendor/compaction-basic/index.js",
52
+ "./compaction": "./plugins/compaction/engine.mjs",
53
53
  "./policy": "./plugins/dscode/index.mjs",
54
54
  "./code-review": "./plugins/code-review/index.mjs",
55
55
  "./auto-review": "./plugins/auto-review/index.mjs",
@@ -0,0 +1,304 @@
1
+ import { BasicCompactionEngine } from '@deepseek-ai/dsh-compaction-basic';
2
+ import { ManualCompactionError, toolPairingBalancedBefore } from '@deepseek-ai/dsh-compaction';
3
+ import { SessionSeq } from '@deepseek-ai/dsh-session';
4
+ import { effectiveContextWindow, fitsInWindow, prefetchThresholdTokens, pricedCompactionPolicy } from './threshold.mjs';
5
+
6
+ // DSCODE's compaction policy as a subclass of the upstream engine instead of a
7
+ // build-time patch of its source. It owns exactly the decisions this deployment
8
+ // adds - a threshold priced from the route's cache discount, the adapter's
9
+ // completion reserve, background prefetch, and overflow recovery that lets
10
+ // pruning decide first - and leaves the durable surface transaction to the
11
+ // upstream `compactRegion`, which stays the only writer.
12
+ //
13
+ // `BasicCompactionEngine` registers the automatic hooks itself and dispatches
14
+ // them through `this.compactIfNeeded`, so overriding it here replaces the policy
15
+ // without touching the installed package.
16
+ export class DscodeCompactionEngine extends BasicCompactionEngine {
17
+ constructor(ctx, config) {
18
+ super(ctx, config);
19
+ // The upstream default hides whether the deployment configured a threshold,
20
+ // so the priced policy is enabled only when the raw config left it unset.
21
+ this.dscodePricedThreshold = config?.thresholdRatio === undefined;
22
+ this.dscodePrefetch = new WeakMap();
23
+ this.dscodePendingPrefetch = new WeakMap();
24
+ this.dscodeWarnedTargets = new Set();
25
+ }
26
+
27
+ /**
28
+ * Compact for step-boundary pressure or one provider-confirmed overflow, with
29
+ * the threshold priced from the route's cache discount and the completion
30
+ * budget the adapter reserves inside the window.
31
+ * @param agent - agent whose latest durable routed request is measured.
32
+ * @param trigger - normal step-boundary pressure or context-overflow recovery.
33
+ * @param signal - live turn cancellation signal forwarded to summarization.
34
+ * @returns the latest summary compaction result, or `null` when no summary ran.
35
+ */
36
+ async compactIfNeeded(agent, trigger, signal) {
37
+ const target = this.dscodeRoutedTarget(agent.session);
38
+ if (target === undefined) return null;
39
+ const policy = this.dscodeTargetPolicy(target);
40
+ const meter = this.ctx.tokenMeter;
41
+ const prune = this.ctx.get('toolResultPruner');
42
+ let measurement = meter.measure(agent.session);
43
+
44
+ if (trigger === 'context-overflow') {
45
+ // The pruner runs first and may already return the failed request under the
46
+ // window. The caller retries whenever that prune replaced the surface, so a
47
+ // summary here would only add a model call and its stall to a request that
48
+ // no longer needs one.
49
+ const generation = agent.session.surface.replaceGeneration;
50
+ if (prune !== undefined) {
51
+ prune.pruneSession(agent.session);
52
+ measurement = meter.measure(agent.session);
53
+ }
54
+ if (agent.session.surface.replaceGeneration > generation) {
55
+ const info = await this.ctx.llm.resolveModelInfo(target.provider, target.model, signal);
56
+ if (fitsInWindow(measurement.totalTokens, info)) return null;
57
+ }
58
+ return this.dscodeCompactOldest(agent, measurement, 0, signal);
59
+ }
60
+
61
+ const modelInfo = await this.ctx.llm.resolveModelInfo(target.provider, target.model, signal);
62
+ const context = modelInfo.context;
63
+ if (context === undefined) {
64
+ const key = `${target.provider}/${target.model}`;
65
+ if (!this.dscodeWarnedTargets.has(key)) {
66
+ this.dscodeWarnedTargets.add(key);
67
+ this.ctx.logger.warn(`compaction: no context capacity for ${key}; configure contextWindow on that adapter model`);
68
+ }
69
+ return null;
70
+ }
71
+ this.dscodeAssertInactive(agent.session, 'automatic pressure compaction');
72
+ const spec = this.dscodeCompactSpec(await pricedCompactionPolicy({ ...this.config, dscodePricedThreshold: this.dscodePricedThreshold }, policy), effectiveContextWindow(context, modelInfo));
73
+
74
+ this.dscodePlanPrefetch(agent, measurement, spec, signal);
75
+ if (measurement.totalTokens < spec.thresholdTokens) return null;
76
+ if (prune !== undefined) {
77
+ prune.pruneSession(agent.session);
78
+ measurement = meter.measure(agent.session);
79
+ }
80
+ if (measurement.totalTokens < spec.thresholdTokens) return null;
81
+
82
+ let result = null;
83
+ const prefetched = await this.dscodeCommitPrefetch(agent, signal);
84
+ if (prefetched !== null) {
85
+ result = prefetched;
86
+ measurement = meter.measure(agent.session);
87
+ if (measurement.totalTokens < spec.thresholdTokens) return result;
88
+ }
89
+ for (let attempt = 0; attempt <= spec.compactionRetries; attempt += 1) {
90
+ const range = this.dscodeSelectRange(agent.session, measurement, spec.retainTokens);
91
+ if (range === null) {
92
+ if (result === null) return null;
93
+ break;
94
+ }
95
+ result = await this.compactRegion(range.start, range.end, agent, signal);
96
+ measurement = meter.measure(agent.session);
97
+ if (measurement.totalTokens < spec.thresholdTokens) return result;
98
+ }
99
+ throw new Error(`compaction still above threshold after ${spec.compactionRetries + 1} compaction attempts (${measurement.totalTokens} estimated tokens >= threshold ${spec.thresholdTokens})`);
100
+ }
101
+
102
+ /**
103
+ * Summarize the replayed region through the upstream cache-reusing call, or
104
+ * return the summary a prefetch already produced for this exact commit.
105
+ * @param input - replayed conversation prefix to condense.
106
+ * @param agent - supplies routed-model history, fallback model, and session id.
107
+ * @param signal - optional cancellation forwarded to the adapter.
108
+ * @returns the summary blocks and the call envelope behind them.
109
+ */
110
+ async summarize(input, agent, signal) {
111
+ const pending = this.dscodePendingPrefetch.get(agent.session);
112
+ if (pending === undefined) return super.summarize(input, agent, signal);
113
+ this.dscodePendingPrefetch.delete(agent.session);
114
+ // The upstream transaction already opened its marker, so this wait is the visible
115
+ // compaction. A failed or invalidated prefetch falls back to a fresh call instead
116
+ // of failing a transaction that has already begun.
117
+ await pending.wait;
118
+ if (pending.failure !== null || agent.session.surface.replaceGeneration !== pending.generation) return super.summarize(input, agent, signal);
119
+ return pending.summarized;
120
+ }
121
+
122
+ /** Compact the oldest compactable span for a retention budget, or return null when none is safe. */
123
+ async dscodeCompactOldest(agent, measurement, retainTokens, signal) {
124
+ const range = this.dscodeSelectRange(agent.session, measurement, retainTokens);
125
+ if (range === null) return null;
126
+ return this.compactRegion(range.start, range.end, agent, signal);
127
+ }
128
+
129
+ /** The routed provider/model of the session's latest durable request. */
130
+ dscodeRoutedTarget(session) {
131
+ const config = session.requestHeader()?.config;
132
+ if (config === undefined || config.provider.length === 0 || config.model.length === 0) return undefined;
133
+ return { provider: config.provider, model: config.model };
134
+ }
135
+
136
+ /** Merge the exact-target override over the validated defaults for one routed target. */
137
+ dscodeTargetPolicy(target) {
138
+ const config = this.config;
139
+ const override = config.modelPolicies.find(entry => entry.provider === target.provider && entry.model === target.model);
140
+ const inherited = config.retainTokens === undefined ? { retainRatio: config.retainRatio } : { retainTokens: config.retainTokens };
141
+ const retention = override?.retainTokens !== undefined ? { retainTokens: override.retainTokens }
142
+ : override?.retainRatio !== undefined ? { retainRatio: override.retainRatio }
143
+ : inherited;
144
+ return {
145
+ target: { ...target },
146
+ thresholdRatio: override?.thresholdRatio ?? config.thresholdRatio,
147
+ ...retention,
148
+ summarizationProvider: override?.summarizationProvider ?? config.summarizationProvider,
149
+ summarizationModel: override?.summarizationModel ?? config.summarizationModel,
150
+ maxTokens: override?.maxTokens ?? config.maxTokens,
151
+ compactionRetries: override?.compactionRetries ?? config.compactionRetries,
152
+ maxOverflowRetries: override?.maxOverflowRetries ?? config.maxOverflowRetries,
153
+ };
154
+ }
155
+
156
+ /** Scale one routed policy into concrete token budgets for its effective window. */
157
+ dscodeCompactSpec(policy, contextWindow) {
158
+ const key = `${policy.target.provider}/${policy.target.model}`;
159
+ if (!Number.isInteger(contextWindow) || contextWindow <= 0) throw new Error(`compaction: contextWindow (${contextWindow}) must be a positive integer for ${key}`);
160
+ const thresholdTokens = Math.floor(contextWindow * policy.thresholdRatio);
161
+ const retainTokens = policy.retainTokens === undefined ? Math.floor(contextWindow * policy.retainRatio) : policy.retainTokens;
162
+ if (retainTokens >= thresholdTokens) throw new Error(`compaction: retainTokens (${retainTokens}) must be less than threshold tokens ${thresholdTokens} for ${key}`);
163
+ return { ...policy, contextWindow, thresholdTokens, retainTokens };
164
+ }
165
+
166
+ /**
167
+ * Resolve the next range starting at the first non-system surface node while
168
+ * retaining a priced recent tail and never splitting a tool-call/result pair.
169
+ * @param session - session supplying authoritative current surface positions.
170
+ * @param measurement - unified pressure and surface measurement from the conversation meter.
171
+ * @param retainTokens - minimum recent tail budget retained verbatim.
172
+ * @returns the positional range to compact, or `null`.
173
+ */
174
+ dscodeSelectRange(session, measurement, retainTokens) {
175
+ const pricedNodes = measurement.nodes;
176
+ if (pricedNodes.length === 0) return null;
177
+ const surfaceNodes = session.surface.nodes;
178
+ if (surfaceNodes.length !== pricedNodes.length || surfaceNodes.some((seq, index) => seq !== pricedNodes[index]?.seq)) throw new Error('compaction: token-meter surface does not match the current session surface');
179
+ const firstIdx = this.dscodeSystemHead(session, surfaceNodes[0]) === undefined ? 0 : 1;
180
+ let accumulated = 0;
181
+ let keepFromIdx = pricedNodes.length;
182
+ for (let index = pricedNodes.length - 1; index >= 0; index -= 1) {
183
+ accumulated += pricedNodes[index].tokens;
184
+ keepFromIdx = index;
185
+ if (accumulated >= retainTokens) break;
186
+ }
187
+ if (keepFromIdx <= firstIdx) return null;
188
+ while (keepFromIdx > firstIdx) {
189
+ if (toolPairingBalancedBefore(session, surfaceNodes[keepFromIdx])) break;
190
+ keepFromIdx -= 1;
191
+ }
192
+ if (keepFromIdx <= firstIdx) return null;
193
+ return { start: surfaceNodes[firstIdx], end: surfaceNodes[keepFromIdx - 1], startIdx: firstIdx, endIdx: keepFromIdx - 1 };
194
+ }
195
+
196
+ /** The `system/message` at a surface head, or undefined when that node is something else. */
197
+ dscodeSystemHead(session, headSeq) {
198
+ if (headSeq === undefined) return undefined;
199
+ const head = session.eventAt(headSeq);
200
+ return head.type === 'system/message' ? head : undefined;
201
+ }
202
+
203
+ /** The replayed conversation prefix a summary call condenses: system head, tools, then the shadowed messages. */
204
+ dscodeSummarizationInput(session, shadowedSeqs) {
205
+ const header = session.requestHeader();
206
+ const nodes = session.surface.nodes;
207
+ const system = this.dscodeSystemHead(session, nodes[0]);
208
+ const head = system === undefined ? null : session.deriveEventMessage(system);
209
+ const region = shadowedSeqs.map(seq => session.deriveEventMessage(session.eventAt(seq))).filter(message => message !== null);
210
+ return { ...header?.tools === undefined ? {} : { tools: header.tools }, messages: head === null ? region : [head, ...region] };
211
+ }
212
+
213
+ /** The newest unmatched compaction start and end-seed boundary of one session. */
214
+ dscodeEntryState(session) {
215
+ let unmatchedCompactionStart;
216
+ let entryStateKnown = false;
217
+ let latestEndSeedSeq;
218
+ for (let seq = session.seq - 1; seq >= 0; seq -= 1) {
219
+ const event = session.eventAt(SessionSeq(seq));
220
+ if (latestEndSeedSeq === undefined && event.type === 'session/end-seed') latestEndSeedSeq = event.seq;
221
+ if (!entryStateKnown) {
222
+ if (event.type === 'compaction/start') {
223
+ unmatchedCompactionStart = event;
224
+ entryStateKnown = true;
225
+ } else if (event.type === 'compaction/end') entryStateKnown = true;
226
+ }
227
+ if (entryStateKnown && latestEndSeedSeq !== undefined) break;
228
+ }
229
+ return { unmatchedCompactionStart, latestEndSeedSeq };
230
+ }
231
+
232
+ /** Reject a second compaction while the durable compaction lock is active. */
233
+ dscodeAssertInactive(session, stage) {
234
+ const { unmatchedCompactionStart, latestEndSeedSeq } = this.dscodeEntryState(session);
235
+ if (unmatchedCompactionStart === undefined || (latestEndSeedSeq !== undefined && latestEndSeedSeq > unmatchedCompactionStart.seq)) return;
236
+ throw new ManualCompactionError('busy', `${stage}: compaction already in progress; the session compaction lock is already active`);
237
+ }
238
+
239
+ /**
240
+ * Start one background summarization of the oldest compactable span once the
241
+ * measured pressure crosses the mark one lead below the priced threshold.
242
+ * Nothing is appended and no compaction lock is taken, so an invalidated or
243
+ * cancelled prefetch costs only the summarization call.
244
+ * @param agent - agent whose pressure the caller just priced.
245
+ * @param measurement - the measurement the caller priced.
246
+ * @param spec - the caller's resolved spec for this route.
247
+ * @param signal - live turn signal forwarded to the summarizer.
248
+ */
249
+ dscodePlanPrefetch(agent, measurement, spec, signal) {
250
+ const session = agent.session;
251
+ const existing = this.dscodePrefetch.get(session);
252
+ if (existing !== undefined) {
253
+ if (existing.failure === null) return;
254
+ this.dscodePrefetch.delete(session);
255
+ }
256
+ if (measurement.totalTokens >= spec.thresholdTokens) return;
257
+ if (measurement.totalTokens < prefetchThresholdTokens(spec.thresholdTokens, spec.contextWindow)) return;
258
+ if (this.dscodeEntryState(session).unmatchedCompactionStart !== undefined) return;
259
+ let range;
260
+ try {
261
+ range = this.dscodeSelectRange(session, measurement, spec.retainTokens);
262
+ } catch (error) {
263
+ return;
264
+ }
265
+ if (range === null) return;
266
+ const shadowedSeqs = session.surface.nodes.slice(range.startIdx, range.endIdx + 1);
267
+ const prefetch = { range: { start: range.start, end: range.end }, generation: session.surface.replaceGeneration, summarized: null, failure: null, wait: null };
268
+ this.dscodePrefetch.set(session, prefetch);
269
+ prefetch.wait = (async () => {
270
+ try {
271
+ const input = this.dscodeSummarizationInput(session, shadowedSeqs);
272
+ prefetch.summarized = await this.summarize(input, agent, signal);
273
+ } catch (error) {
274
+ prefetch.failure = error;
275
+ }
276
+ })();
277
+ }
278
+
279
+ /**
280
+ * Commit the pending prefetch when its span is still the same replacement
281
+ * target, or return null so the caller summarizes afresh. The upstream
282
+ * `compactRegion` owns the marker pair, the surface replacement and every
283
+ * stability check; `summarize` then returns the summary already produced.
284
+ * @param agent - agent whose pressure triggered the automatic compaction.
285
+ * @param signal - live turn signal forwarded to the transaction.
286
+ * @returns the committed compaction result, or null when no prefetch was usable.
287
+ */
288
+ async dscodeCommitPrefetch(agent, signal) {
289
+ const session = agent.session;
290
+ const prefetch = this.dscodePrefetch.get(session);
291
+ if (prefetch === undefined) return null;
292
+ this.dscodePrefetch.delete(session);
293
+ if (prefetch.failure !== null) return null;
294
+ if (session.surface.replaceGeneration !== prefetch.generation) return null;
295
+ this.dscodePendingPrefetch.set(session, prefetch);
296
+ try {
297
+ return await this.compactRegion(prefetch.range.start, prefetch.range.end, agent, signal);
298
+ } finally {
299
+ this.dscodePendingPrefetch.delete(session);
300
+ }
301
+ }
302
+ }
303
+
304
+ export default DscodeCompactionEngine;
@@ -198,7 +198,7 @@
198
198
  toolResultPruner: true
199
199
  config:
200
200
  - id: compaction-basic
201
- name: '@toddzheng024/dscode-bundle/compaction-basic'
201
+ name: '@toddzheng024/dscode-bundle/compaction'
202
202
 
203
203
  - id: command-compact
204
204
  name: '@deepseek-ai/dsh-command-compact'
@@ -32,7 +32,7 @@ import { dscodeChatLines } from './dscode/chat.mjs';
32
32
  import { readFileSync } from 'node:fs';
33
33
  import { footerFor as dscodeFooterFor } from '../../../plugins/session-metrics/view.mjs';
34
34
  import { newerVersion as dscodeNewerVersion } from '../../../plugins/tui-tools/update.mjs';
35
- import { t as dscodeMessage, normalizeLanguage as dscodeNormalizeLanguage } from '../../../plugins/i18n/messages.mjs';
35
+ import { languageName as dscodeLanguageName, normalizeLanguage as dscodeNormalizeLanguage, t as dscodeMessage } from '../../../plugins/i18n/messages.mjs';
36
36
  import { dscodeTelemetryNodes } from './dscode/telemetry.mjs';
37
37
  import { dscodeFooterHeader } from './render/status.mjs';
38
38
  import { dscodePadEnd, welcomeArtRows, welcomePath, WELCOME_ART, WELCOME_ART_SMALL } from './dscode/welcome.mjs';
@@ -4604,9 +4604,10 @@ function Input({ effortSurface, ultraPulse, active, frozen, frozenHint, busy, de
4604
4604
  const argument = text.slice('/language'.length).trim();
4605
4605
  if (argument === '')
4606
4606
  openLanguage();
4607
- else if (argument === 'en' || argument === 'zh') {
4608
- saveLanguage(parseLanguageName(argument));
4609
- notify(t('notice.languageSaved', { name: argument }));
4607
+ else if (dscodeNormalizeLanguage(argument) !== null) {
4608
+ const name = parseLanguageName(argument);
4609
+ saveLanguage(name);
4610
+ notify(t('notice.languageSaved', { name: dscodeLanguageName(name) }));
4610
4611
  refresh();
4611
4612
  }
4612
4613
  else
@@ -6,26 +6,32 @@
6
6
  * choice lives in language.json next to theme.json (see the runner's
7
7
  * persistence block).
8
8
  *
9
+ * The selectable list and the alias table come from the DSCODE message
10
+ * tables, so `/language` offers everything those tables translate. The
11
+ * terminal's own catalogues still only cover English and Simplified Chinese:
12
+ * any other choice paints the shell in English and DSCODE's labels in the
13
+ * chosen language, which is exactly what `/language` promises.
14
+ *
9
15
  * @module @deepseek-ai/dsh-tui/i18n
10
16
  */
11
17
  import { en } from './locales/en.mjs';
12
18
  import { zh } from './locales/zh.mjs';
13
- /** Valid language names for argument parsing. */
14
- export const LANGUAGE_NAMES = ['en', 'zh'];
19
+ import { LANGUAGES as DSCODE_LANGUAGES, normalizeLanguage } from '../../../plugins/i18n/messages.mjs';
20
+ /** Valid language names for argument parsing, in picker order. */
21
+ export const LANGUAGE_NAMES = DSCODE_LANGUAGES.map(language => language.code);
15
22
  /** The /language picker rows in canonical order. */
16
- export const LANGUAGES = [
17
- { id: 'en', label: 'English' },
18
- { id: 'zh', label: '中文' },
19
- ];
20
- const CATALOGS = { en, zh };
23
+ export const LANGUAGES = DSCODE_LANGUAGES.map(language => ({ id: language.code, label: language.name }));
24
+ /** Catalogues this module can paint; anything else keeps English. */
25
+ const CATALOGS = { en, 'zh-CN': zh, 'zh-TW': zh };
21
26
  /** The language in force. */
22
27
  let activeName = 'en';
23
28
  /**
24
- * Parse a persisted or typed language name: only 'en' and 'zh' survive;
25
- * anything else falls back to English.
29
+ * Parse a persisted or typed language name through the DSCODE alias table, so
30
+ * `zh`, `jp` or `简体中文` all resolve. An unknown value falls back to English.
26
31
  */
27
32
  export function parseLanguageName(value) {
28
- return value === 'zh' ? 'zh' : 'en';
33
+ const code = normalizeLanguage(value);
34
+ return code !== null && LANGUAGE_NAMES.includes(code) ? code : 'en';
29
35
  }
30
36
  /** The language name in force. */
31
37
  export function getLanguage() {
@@ -37,11 +43,11 @@ export function setLanguage(name) {
37
43
  }
38
44
  /**
39
45
  * One message from the active catalog, with `{n}`-style placeholders filled
40
- * from the params record. Unknown placeholders stay literal; a missing key
41
- * falls back to the English entry so a catalog gap degrades visibly but
42
- * never crashes.
46
+ * from the params record. Unknown placeholders stay literal; a language the
47
+ * terminal cannot paint, or a missing key, falls back to the English entry so
48
+ * a catalog gap degrades visibly but never crashes.
43
49
  */
44
50
  export function t(key, params = {}) {
45
- const template = CATALOGS[activeName][key] ?? en[key];
51
+ const template = CATALOGS[activeName]?.[key] ?? en[key];
46
52
  return template.replace(/\{(\w+)\}/gu, (whole, name) => params[name] === undefined ? whole : String(params[name]));
47
53
  }
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 DeepSeek
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.