arcane-os 0.1.0 → 0.1.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 (31) hide show
  1. package/NOTICE +10 -0
  2. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +80 -4
  3. package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +69 -0
  4. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +1151 -0
  5. package/browser-runtime/ai/browser-wasm.mjs +44 -0
  6. package/browser-runtime/ai/browser-wllama-runtime.mjs +390 -0
  7. package/browser-runtime/ai/internal/sha256.mjs +166 -0
  8. package/browser-runtime/ai/model-controller.mjs +581 -0
  9. package/browser-runtime/ai/wllama/LICENCE +21 -0
  10. package/browser-runtime/ai/wllama/index.mjs +3494 -0
  11. package/browser-runtime/ai/wllama/llama.cpp-LICENSE +21 -0
  12. package/browser-runtime/ai/wllama/wllama.wasm +0 -0
  13. package/docs/publishing.md +23 -18
  14. package/docs/reference/README.md +9 -5
  15. package/docs/reference/ai/browser-wasm.md +335 -0
  16. package/docs/reference/availability-and-normalization.md +17 -0
  17. package/docs/reference/behavioral-testing.md +8 -0
  18. package/docs/reference/cli.md +86 -3
  19. package/docs/reference/event-manager.md +15 -6
  20. package/docs/reference/inventory/package-api.json +84 -4
  21. package/docs/reference/protocols.md +113 -14
  22. package/docs/reference/sdk-api.md +244 -11
  23. package/package.json +8 -5
  24. package/runtime/ARCANE_RUNTIME_RELEASE.json +1 -1
  25. package/schemas/arcane-lock.schema.json +17 -6
  26. package/src/dev-server.mjs +2 -1
  27. package/src/doctor.mjs +1 -1
  28. package/src/import-map.mjs +25 -1
  29. package/src/sdk-browser-runtime.mjs +134 -17
  30. package/src/templates/workspace-template.mjs +6 -0
  31. package/src/workspace.mjs +7 -1
@@ -0,0 +1,581 @@
1
+ export const ARCANE_AI_ADAPTER_PROTOCOL = "arcane-ai-adapter/1";
2
+
3
+ const ERROR_CODES = Object.freeze({
4
+ load: "ARCANE_AI_LOAD_FAILED",
5
+ unload: "ARCANE_AI_UNLOAD_FAILED",
6
+ request: "ARCANE_AI_REQUEST_FAILED",
7
+ dispose: "ARCANE_AI_DISPOSE_FAILED",
8
+ probe: "ARCANE_AI_PROBE_FAILED",
9
+ });
10
+
11
+ function abortLike(error, signal) {
12
+ return signal?.aborted === true
13
+ || error?.name === "AbortError"
14
+ || error?.code === "ABORT_ERR"
15
+ || error?.code === "ARCANE_AI_REQUEST_ABORTED";
16
+ }
17
+
18
+ export class ArcaneAIError extends Error {
19
+ constructor(code, message, { cause, kind = "llm", operation = "request" } = {}) {
20
+ super(message, cause === undefined ? undefined : { cause });
21
+ this.name = "ArcaneAIError";
22
+ this.code = code;
23
+ this.kind = kind;
24
+ this.operation = operation;
25
+ }
26
+ }
27
+
28
+ export function normalizeArcaneAIError(error, {
29
+ kind = "llm",
30
+ operation = "request",
31
+ signal = null,
32
+ } = {}) {
33
+ if (abortLike(error, signal)) {
34
+ return new ArcaneAIError(
35
+ "ARCANE_AI_REQUEST_ABORTED",
36
+ "The Arcane AI request was cancelled.",
37
+ { cause: error ?? signal?.reason, kind, operation },
38
+ );
39
+ }
40
+ if (error instanceof ArcaneAIError) return error;
41
+ const code = typeof error?.code === "string" && error.code.startsWith("ARCANE_AI_")
42
+ ? error.code
43
+ : ERROR_CODES[operation] ?? ERROR_CODES.request;
44
+ const message = typeof error?.message === "string" && error.message.trim()
45
+ ? error.message
46
+ : `The Arcane AI ${operation} operation failed.`;
47
+ return new ArcaneAIError(code, message, { cause: error, kind, operation });
48
+ }
49
+
50
+ function providerMethod(provider, name) {
51
+ return typeof provider?.[name] === "function" ? provider[name].bind(provider) : null;
52
+ }
53
+
54
+ function copyError(error) {
55
+ if (!error) return null;
56
+ return Object.freeze({
57
+ code: String(error.code ?? "ARCANE_AI_REQUEST_FAILED"),
58
+ message: String(error.message ?? "The Arcane AI operation failed."),
59
+ });
60
+ }
61
+
62
+ function localRequirement(options, provider) {
63
+ if (options?.localOnly !== undefined && typeof options.localOnly !== "boolean") {
64
+ throw new TypeError("localOnly must be a boolean when provided.");
65
+ }
66
+ if (options?.localOnly === true && provider.capabilities?.().localOnly !== true) {
67
+ throw new ArcaneAIError(
68
+ "ARCANE_AI_LOCAL_ONLY_UNAVAILABLE",
69
+ "The selected AI provider cannot guarantee browser-local inference.",
70
+ { operation: "request" },
71
+ );
72
+ }
73
+ }
74
+
75
+ function linkedAbortSignal(externalSignal) {
76
+ const controller = new AbortController();
77
+ const forward = () => controller.abort(externalSignal.reason);
78
+ if (externalSignal?.aborted) forward();
79
+ else externalSignal?.addEventListener?.("abort", forward, { once: true });
80
+ return Object.freeze({
81
+ controller,
82
+ release: () => externalSignal?.removeEventListener?.("abort", forward),
83
+ });
84
+ }
85
+
86
+ function fireAndForget(callback, ...args) {
87
+ if (typeof callback !== "function") return;
88
+ try {
89
+ Promise.resolve(callback(...args)).catch(() => undefined);
90
+ } catch {
91
+ // Observational callbacks cannot alter the local-inference decision.
92
+ }
93
+ }
94
+
95
+ class ControllerEvent {
96
+ constructor(type, detail, target) {
97
+ this.type = type;
98
+ this.detail = detail;
99
+ this.target = target;
100
+ this.currentTarget = target;
101
+ }
102
+ }
103
+
104
+ let generatedRequestId = 0;
105
+
106
+ function requestIdentity(value) {
107
+ return value === undefined || value === null
108
+ ? `arcane-local-${++generatedRequestId}`
109
+ : value;
110
+ }
111
+
112
+ function displayRequestId(value) {
113
+ return `M-${String(value)}`;
114
+ }
115
+
116
+ function textFromCompletion(completion) {
117
+ const content = completion?.choices?.[0]?.message?.content;
118
+ return typeof content === "string" ? content : "";
119
+ }
120
+
121
+ function toolRecordFromCompletion(completion) {
122
+ const result = {};
123
+ let count = 0;
124
+ for (const choice of completion?.choices ?? []) {
125
+ for (const call of choice?.message?.tool_calls ?? []) {
126
+ result[call.function.name] = call.function.arguments;
127
+ count += 1;
128
+ }
129
+ }
130
+ return count ? result : null;
131
+ }
132
+
133
+ export class ModelController {
134
+ #provider;
135
+ #loadPolicy;
136
+ #listeners = new Map();
137
+ #loadPromise = null;
138
+ #unloadPromise = null;
139
+ #disposePromise = null;
140
+ #operationGeneration = 0;
141
+ #disposing = false;
142
+ #disposed = false;
143
+ #activeStreams = new Set();
144
+ #fallbackState = "unloaded";
145
+ #progress = null;
146
+ #error = null;
147
+
148
+ constructor({ provider, loadPolicy = "on-demand" } = {}) {
149
+ if (!provider || typeof provider !== "object") {
150
+ throw new TypeError("ModelController requires an LLM provider.");
151
+ }
152
+ if (
153
+ provider.protocol !== undefined
154
+ && provider.protocol !== ARCANE_AI_ADAPTER_PROTOCOL
155
+ ) {
156
+ throw new ArcaneAIError(
157
+ "ARCANE_AI_ADAPTER_PROTOCOL_MISMATCH",
158
+ `The LLM provider must implement ${ARCANE_AI_ADAPTER_PROTOCOL}.`,
159
+ { operation: "initialize" },
160
+ );
161
+ }
162
+ if (loadPolicy !== "on-demand" && loadPolicy !== "manual") {
163
+ throw new TypeError("loadPolicy must be \"on-demand\" or \"manual\".");
164
+ }
165
+ this.#provider = provider;
166
+ this.#loadPolicy = loadPolicy;
167
+ }
168
+
169
+ status() {
170
+ const providerStatus = providerMethod(this.#provider, "status")?.() ?? {};
171
+ return Object.freeze({
172
+ ...providerStatus,
173
+ kind: "llm",
174
+ state: providerStatus.state ?? this.#fallbackState,
175
+ progress: providerStatus.progress ?? this.#progress,
176
+ error: providerStatus.error ?? copyError(this.#error),
177
+ });
178
+ }
179
+
180
+ addEventListener(type, listener) {
181
+ if (typeof listener !== "function" && typeof listener?.handleEvent !== "function") return;
182
+ const listeners = this.#listeners.get(type) ?? new Set();
183
+ listeners.add(listener);
184
+ this.#listeners.set(type, listeners);
185
+ }
186
+
187
+ removeEventListener(type, listener) {
188
+ this.#listeners.get(type)?.delete(listener);
189
+ }
190
+
191
+ on(type, listener) {
192
+ this.addEventListener(type, listener);
193
+ return () => this.removeEventListener(type, listener);
194
+ }
195
+
196
+ #emit(type) {
197
+ const event = new ControllerEvent(type, this.status(), this);
198
+ for (const listener of [...(this.#listeners.get(type) ?? [])]) {
199
+ try {
200
+ if (typeof listener === "function") listener.call(this, event);
201
+ else listener.handleEvent(event);
202
+ } catch {
203
+ // UI observers cannot alter lifecycle state.
204
+ }
205
+ }
206
+ }
207
+
208
+ #assertOperational() {
209
+ if (this.#disposed || this.#disposing) {
210
+ throw new ArcaneAIError("ARCANE_AI_DISPOSED", "The LLM controller is disposed.");
211
+ }
212
+ }
213
+
214
+ async load(options = {}) {
215
+ this.#assertOperational();
216
+ const state = this.status().state;
217
+ if (this.#unloadPromise || state === "unloading") {
218
+ throw new ArcaneAIError(
219
+ "ARCANE_AI_OPERATION_SUPERSEDED",
220
+ "The LLM controller cannot load while unload is in progress.",
221
+ { operation: "load" },
222
+ );
223
+ }
224
+ if (state === "ready") return this.status();
225
+ if (this.#loadPromise) return this.#loadPromise;
226
+ const load = providerMethod(this.#provider, "load");
227
+ if (!load) throw new ArcaneAIError("ARCANE_AI_UNAVAILABLE", "The LLM provider cannot load a model.");
228
+ const signal = options.signal ?? null;
229
+ const operationGeneration = ++this.#operationGeneration;
230
+ this.#fallbackState = "loading";
231
+ this.#progress = null;
232
+ this.#error = null;
233
+ this.#emit("statechange");
234
+ this.#loadPromise = (async () => {
235
+ try {
236
+ await load(options, Object.freeze({
237
+ protocol: ARCANE_AI_ADAPTER_PROTOCOL,
238
+ kind: "llm",
239
+ operation: "load",
240
+ signal,
241
+ reportProgress: (progress) => {
242
+ if (
243
+ operationGeneration !== this.#operationGeneration
244
+ || this.#disposing
245
+ || this.#disposed
246
+ ) return;
247
+ this.#progress = progress;
248
+ this.#emit("progress");
249
+ },
250
+ }));
251
+ if (
252
+ operationGeneration !== this.#operationGeneration
253
+ || this.#disposing
254
+ || this.#disposed
255
+ ) return this.status();
256
+ this.#fallbackState = "ready";
257
+ this.#progress = null;
258
+ this.#emit("statechange");
259
+ return this.status();
260
+ } catch (error) {
261
+ const normalized = normalizeArcaneAIError(error, { operation: "load", signal });
262
+ if (
263
+ operationGeneration === this.#operationGeneration
264
+ && !this.#disposing
265
+ && !this.#disposed
266
+ ) {
267
+ this.#fallbackState = "error";
268
+ this.#error = normalized;
269
+ this.#emit("statechange");
270
+ }
271
+ throw normalized;
272
+ } finally {
273
+ this.#loadPromise = null;
274
+ }
275
+ })();
276
+ return this.#loadPromise;
277
+ }
278
+
279
+ async #ready(loadOptions = {}) {
280
+ this.#assertOperational();
281
+ const state = this.status().state;
282
+ if (this.#unloadPromise || state === "unloading") {
283
+ throw new ArcaneAIError(
284
+ "ARCANE_AI_OPERATION_SUPERSEDED",
285
+ "The LLM controller cannot accept requests while unload is in progress.",
286
+ { operation: "request" },
287
+ );
288
+ }
289
+ if (state === "ready") return;
290
+ if (this.#loadPolicy === "manual") {
291
+ throw new ArcaneAIError(
292
+ "ARCANE_AI_NOT_READY",
293
+ "The browser-WASM model must be loaded before use.",
294
+ );
295
+ }
296
+ await this.load(loadOptions);
297
+ }
298
+
299
+ async #closeStreams(reason) {
300
+ const active = [...this.#activeStreams];
301
+ await Promise.all(active.map((handle) => handle.cancel(reason)));
302
+ }
303
+
304
+ async unload(options = {}) {
305
+ if (this.#unloadPromise) return this.#unloadPromise;
306
+ const unload = providerMethod(this.#provider, "unload");
307
+ if (!unload) throw new ArcaneAIError("ARCANE_AI_UNAVAILABLE", "The LLM provider cannot unload.");
308
+ const signal = options.signal ?? null;
309
+ const inFlightLoad = this.#loadPromise;
310
+ const operationGeneration = ++this.#operationGeneration;
311
+ const context = Object.freeze({
312
+ protocol: ARCANE_AI_ADAPTER_PROTOCOL,
313
+ kind: "llm",
314
+ operation: "unload",
315
+ signal,
316
+ });
317
+ this.#fallbackState = "unloading";
318
+ this.#emit("statechange");
319
+ this.#unloadPromise = (async () => {
320
+ try {
321
+ await this.#closeStreams("The browser-WASM model is unloading.");
322
+ // The first call asks the provider to cancel its in-flight load. A
323
+ // provider owns its public status, so wait for the captured load and
324
+ // reassert unload afterward; a late provider-owned `ready` state can
325
+ // otherwise outlive this controller's generation guard.
326
+ await unload(options, context);
327
+ if (inFlightLoad) {
328
+ await inFlightLoad.catch(() => undefined);
329
+ await unload(options, context);
330
+ }
331
+ if (operationGeneration === this.#operationGeneration) {
332
+ this.#fallbackState = "unloaded";
333
+ this.#progress = null;
334
+ this.#error = null;
335
+ this.#emit("statechange");
336
+ }
337
+ return this.status();
338
+ } catch (error) {
339
+ const normalized = normalizeArcaneAIError(error, { operation: "unload", signal });
340
+ if (operationGeneration === this.#operationGeneration) {
341
+ this.#fallbackState = "error";
342
+ this.#error = normalized;
343
+ this.#emit("statechange");
344
+ }
345
+ throw normalized;
346
+ } finally {
347
+ this.#unloadPromise = null;
348
+ }
349
+ })();
350
+ return this.#unloadPromise;
351
+ }
352
+
353
+ async chat(request = {}) {
354
+ this.#assertOperational();
355
+ const signal = request.signal ?? null;
356
+ localRequirement(request, this.#provider);
357
+ if (abortLike(null, signal)) {
358
+ throw normalizeArcaneAIError(null, { operation: "request", signal });
359
+ }
360
+ await this.#ready({ ...(request.loadOptions ?? {}), signal });
361
+ const chat = providerMethod(this.#provider, "chat") ?? providerMethod(this.#provider, "use");
362
+ if (!chat) throw new ArcaneAIError("ARCANE_AI_UNAVAILABLE", "The LLM provider cannot chat.");
363
+ try {
364
+ return await chat(request, Object.freeze({
365
+ protocol: ARCANE_AI_ADAPTER_PROTOCOL,
366
+ kind: "llm",
367
+ operation: "chat",
368
+ signal,
369
+ }));
370
+ } catch (error) {
371
+ throw normalizeArcaneAIError(error, { operation: "request", signal });
372
+ }
373
+ }
374
+
375
+ stream(request = {}) {
376
+ this.#assertOperational();
377
+ localRequirement(request, this.#provider);
378
+ const controller = this;
379
+ const externalSignal = request.signal ?? null;
380
+ const linked = linkedAbortSignal(externalSignal);
381
+ let opened = null;
382
+ let openError = null;
383
+ let cancelPromise = null;
384
+
385
+ const openPromise = (async () => {
386
+ if (linked.controller.signal.aborted) {
387
+ throw normalizeArcaneAIError(null, {
388
+ operation: "request",
389
+ signal: linked.controller.signal,
390
+ });
391
+ }
392
+ await controller.#ready({ ...(request.loadOptions ?? {}), signal: linked.controller.signal });
393
+ const stream = providerMethod(controller.#provider, "stream")
394
+ ?? providerMethod(controller.#provider, "streamChat");
395
+ if (!stream) throw new ArcaneAIError("ARCANE_AI_UNAVAILABLE", "The LLM provider cannot stream.");
396
+ const value = await stream(
397
+ { ...request, signal: linked.controller.signal },
398
+ Object.freeze({
399
+ protocol: ARCANE_AI_ADAPTER_PROTOCOL,
400
+ kind: "llm",
401
+ operation: "stream",
402
+ signal: linked.controller.signal,
403
+ }),
404
+ );
405
+ if (!value || typeof value[Symbol.asyncIterator] !== "function") {
406
+ throw new ArcaneAIError(
407
+ "ARCANE_AI_INVALID_PROVIDER_RESULT",
408
+ "The LLM provider did not return an async stream handle.",
409
+ );
410
+ }
411
+ opened = value;
412
+ return value;
413
+ })().catch((error) => {
414
+ openError = normalizeArcaneAIError(error, {
415
+ operation: "request",
416
+ signal: linked.controller.signal,
417
+ });
418
+ throw openError;
419
+ });
420
+ openPromise.catch(() => undefined);
421
+
422
+ const result = openPromise.then((value) => value.result).then((value) => value).finally(() => {
423
+ linked.release();
424
+ controller.#activeStreams.delete(handle);
425
+ });
426
+ result.catch(() => undefined);
427
+
428
+ const handle = {
429
+ result,
430
+ async cancel(reason = "The stream was cancelled.") {
431
+ cancelPromise ||= (async () => {
432
+ linked.controller.abort(reason);
433
+ try {
434
+ const value = opened ?? await openPromise;
435
+ await value.cancel?.(reason);
436
+ } catch {
437
+ // result exposes the normalized terminal error.
438
+ }
439
+ try {
440
+ await result;
441
+ } catch {
442
+ // Cancellation is expected to reject result.
443
+ }
444
+ return true;
445
+ })();
446
+ return cancelPromise;
447
+ },
448
+ async next(value) {
449
+ if (openError) throw openError;
450
+ const streamHandle = opened ?? await openPromise;
451
+ return streamHandle.next(value);
452
+ },
453
+ async return(value) {
454
+ await this.cancel("The stream consumer stopped before completion.");
455
+ return { value, done: true };
456
+ },
457
+ async throw(error) {
458
+ await this.cancel(error);
459
+ throw normalizeArcaneAIError(error, { operation: "request" });
460
+ },
461
+ [Symbol.asyncIterator]() {
462
+ return this;
463
+ },
464
+ };
465
+ Object.freeze(handle);
466
+ this.#activeStreams.add(handle);
467
+ return handle;
468
+ }
469
+
470
+ async fetchRequest(options = {}) {
471
+ this.#assertOperational();
472
+ localRequirement(options, this.#provider);
473
+ const id = requestIdentity(options.id);
474
+ const request = { ...options, id };
475
+ // localOnly admission is complete before app callbacks observe a request.
476
+ fireAndForget(options.onRequest, request, id);
477
+ const response = await this.chat(request);
478
+ if (options.signal?.aborted) {
479
+ throw normalizeArcaneAIError(null, { operation: "request", signal: options.signal });
480
+ }
481
+ if (typeof options.onResponse === "function") {
482
+ await options.onResponse(response, id, false);
483
+ }
484
+ return response;
485
+ }
486
+
487
+ async streamRequest(options = {}) {
488
+ this.#assertOperational();
489
+ localRequirement(options, this.#provider);
490
+ const id = requestIdentity(options.id);
491
+ const request = { ...options, id };
492
+ const displayId = displayRequestId(id);
493
+ fireAndForget(options.onRequest, request, id);
494
+ const handle = this.stream(request);
495
+ const announcedTools = new Set();
496
+
497
+ try {
498
+ for await (const chunk of handle) {
499
+ if (options.signal?.aborted) break;
500
+ for (const choice of chunk?.choices ?? []) {
501
+ const delta = choice?.delta ?? {};
502
+ if (typeof delta.reasoning_content === "string" && options.seeThinking === true) {
503
+ options.onChunk?.(delta.reasoning_content, displayId, true);
504
+ }
505
+ if (typeof delta.content === "string") {
506
+ options.onChunk?.(delta.content, displayId, false);
507
+ }
508
+ for (const tool of delta.tool_calls ?? []) {
509
+ const name = tool?.function?.name;
510
+ if (typeof name === "string" && name && !announcedTools.has(name)) {
511
+ announcedTools.add(name);
512
+ fireAndForget(options.onToolCall, name);
513
+ }
514
+ }
515
+ }
516
+ }
517
+ const completion = await handle.result;
518
+ if (options.signal?.aborted) {
519
+ throw normalizeArcaneAIError(null, { operation: "request", signal: options.signal });
520
+ }
521
+ const tools = toolRecordFromCompletion(completion);
522
+ const output = tools ?? textFromCompletion(completion);
523
+ if (typeof options.onComplete === "function") {
524
+ await options.onComplete(output, displayId, false);
525
+ }
526
+ return output;
527
+ } catch (error) {
528
+ await handle.cancel(error).catch(() => undefined);
529
+ throw normalizeArcaneAIError(error, { operation: "request", signal: options.signal });
530
+ }
531
+ }
532
+
533
+ async probe(options = {}) {
534
+ this.#assertOperational();
535
+ const probe = providerMethod(this.#provider, "probe");
536
+ if (!probe) throw new ArcaneAIError("ARCANE_AI_UNAVAILABLE", "The LLM provider has no WASM probe.");
537
+ try {
538
+ return await probe(options);
539
+ } catch (error) {
540
+ throw normalizeArcaneAIError(error, { operation: "probe", signal: options.signal });
541
+ }
542
+ }
543
+
544
+ dispose(options = {}) {
545
+ if (this.#disposePromise) return this.#disposePromise;
546
+ this.#disposing = true;
547
+ this.#operationGeneration += 1;
548
+ let resolveOperation;
549
+ let rejectOperation;
550
+ const operation = new Promise((resolve, reject) => {
551
+ resolveOperation = resolve;
552
+ rejectOperation = reject;
553
+ });
554
+ this.#disposePromise = operation;
555
+ (async () => {
556
+ try {
557
+ await this.unload(options);
558
+ const dispose = providerMethod(this.#provider, "dispose");
559
+ if (dispose) await dispose(options);
560
+ this.#disposed = true;
561
+ this.#listeners.clear();
562
+ return this.status();
563
+ } catch (error) {
564
+ throw normalizeArcaneAIError(error, {
565
+ operation: "dispose",
566
+ signal: options.signal,
567
+ });
568
+ } finally {
569
+ this.#disposing = false;
570
+ }
571
+ })().then(resolveOperation, rejectOperation);
572
+ operation.catch(() => {
573
+ if (this.#disposePromise === operation) this.#disposePromise = null;
574
+ });
575
+ return operation;
576
+ }
577
+ }
578
+
579
+ export function createModelController(options) {
580
+ return new ModelController(options);
581
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Xuan Son NGUYEN
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.