File size: 3,857 Bytes
1944112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
 * Typed failures.
 *
 * Every error the engine raised used to be a bare string, so a caller wanting
 * to tell "this device has no WebGPU" from "that folder was missing a shard"
 * had to match on prose — which then silently broke whenever the prose
 * improved. A code is the part of an error message that is allowed to be
 * depended on.
 *
 * The codes are deliberately few. Each one exists because a caller does
 * something *different* about it, not because it names a different line of
 * code:
 *
 *   NO_WEBGPU             tell the user to check flags/hardware; retrying is futile
 *   NO_MODEL              nothing registered at all — send them to your setup flow
 *   UNKNOWN_MODEL         that id is not resolvable; `listAvailableModels()` says what is
 *   CACHE_INCOMPLETE      a locally-registered model was evicted; re-register the folder
 *   INVALID_MODEL_FOLDER  the folder is not a compiled MLC model; `detail` says what is missing
 *   BAD_REQUEST           the caller's arguments are wrong; a bug in the caller
 *   ABORTED               the caller cancelled it; not a failure, and not to be
 *                         reported to a user as one
 *   GENERATION_FAILED     the model failed mid-generation
 *   PACKAGE_INCOMPLETE    this package is not wired into your build correctly —
 *                         a missing `vendor/` bundle, or a decode worker the
 *                         bundler did not emit. Fixed once, in the build, by
 *                         the developer; never branched on at runtime.
 *
 * `PACKAGE_INCOMPLETE` covers two causes with one code on purpose. They are
 * different sentences and `detail.cause` tells them apart, but no caller writes
 * a different `catch` branch for them: both mean "your build is wrong, the app
 * has not shipped yet", and both are fixed by editing config, not by handling
 * an error path. A second code would have grown the table a caller reads
 * without giving that caller anything new to do.
 *
 * `message` stays human-readable and stays the thing you print. `detail`
 * carries whatever structured context the site had — the missing cache keys,
 * the absent field — so a caller never has to parse the sentence.
 */
export const ERROR = {
  NO_WEBGPU: "NO_WEBGPU",
  NO_MODEL: "NO_MODEL",
  UNKNOWN_MODEL: "UNKNOWN_MODEL",
  CACHE_INCOMPLETE: "CACHE_INCOMPLETE",
  INVALID_MODEL_FOLDER: "INVALID_MODEL_FOLDER",
  BAD_REQUEST: "BAD_REQUEST",
  ABORTED: "ABORTED",
  GENERATION_FAILED: "GENERATION_FAILED",
  PACKAGE_INCOMPLETE: "PACKAGE_INCOMPLETE",
};

export class EngineError extends Error {
  /**
   * @param {string} code one of ERROR
   * @param {string} message human-readable, safe to show a user
   * @param {object} [detail] structured context, so callers need not parse `message`
   */
  constructor(code, message, detail) {
    super(message);
    this.name = "EngineError";
    this.code = code;
    if (detail !== undefined) this.detail = detail;
  }

  /** Wire form. `error` stays a plain string so existing callers keep working. */
  toJSON() {
    return { code: this.code, message: this.message, ...(this.detail ? { detail: this.detail } : {}) };
  }
}

/**
 * Normalise anything thrown into an EngineError.
 *
 * Errors from WebLLM, the GPU stack and the structured-clone boundary arrive as
 * plain Errors, DOMExceptions or strings, and a caller should not have to care
 * which. Anything unrecognised becomes `GENERATION_FAILED` rather than being
 * given a more specific code it has not earned.
 */
export function asEngineError(err, fallback = ERROR.GENERATION_FAILED) {
  if (err instanceof EngineError) return err;
  const message = String(err?.message ?? err);
  return new EngineError(fallback, message);
}

export const isEngineError = (err, code) =>
  err instanceof EngineError && (code === undefined || err.code === code);