File size: 5,377 Bytes
ce45eb0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/* Matrix Context — live API adapter (Phase 0, Compatible Mode).
 *
 * Drop-in replacement for the design's mock `window.MC`: same method names,
 * real `fetch` against the same-origin `/v1` surface. This is the ONE place that
 * maps the backend response shapes to what the UI consumes:
 *   - remember  -> read res.item            (backend returns {item:{…}})
 *   - inspect   -> res.routing.* + res.pack.* (selected/unselected/scores/…)
 *   - version   -> contract_version / implementation_version
 *   - scopes    -> plain strings
 * No contract changes; works against MoC Contract v1 as-is.
 */
(function () {
  const BASE = location.origin + "/v1";
  // Optional bearer token (Phase 1 servers may require it; harmless if unset).
  let TOKEN = (window.localStorage && localStorage.getItem("mc_token")) || "";

  function setToken(t) {
    TOKEN = t || "";
    if (window.localStorage) localStorage.setItem("mc_token", TOKEN);
  }

  async function call(method, path, body) {
    const headers = { "Content-Type": "application/json" };
    if (TOKEN) headers["Authorization"] = "Bearer " + TOKEN;
    const opt = { method, headers };
    if (body !== undefined) opt.body = JSON.stringify(body);
    let resp;
    try {
      resp = await fetch(BASE + path, opt);
    } catch (e) {
      throw new ApiError(0, "network error — is the backend running?");
    }
    let data = {};
    try { data = await resp.json(); } catch (e) { /* empty body */ }
    if (!resp.ok) throw new ApiError(resp.status, (data && data.error) || ("HTTP " + resp.status));
    return data;
  }

  function ApiError(status, message) { this.status = status; this.message = message; }
  ApiError.prototype = Object.create(Error.prototype);

  const MC = {
    base: BASE,
    setToken,
    hasToken: () => !!TOKEN,
    ApiError,

    // ---- discovery ----
    async health() {
      const h = await call("GET", "/health");
      return { status: h.status, name: h.name, version: h.version, items: h.items };
    },
    async version() {
      const v = await call("GET", "/version");
      return {
        contract: v.contract_version,
        implementation: v.implementation,
        build: v.implementation_version,
        name: v.name,
      };
    },
    async experts() {
      const r = await call("GET", "/experts");
      return (r.experts || []).map((e) => ({ id: e.name, name: e.name, desc: e.description || "" }));
    },
    async scopes() {
      const r = await call("GET", "/scopes");
      return (r.scopes || []).map((s) => ({ id: s, label: s }));   // plain strings -> objects
    },

    // ---- items ----
    async items(filter) {
      filter = filter || {};
      const q = [];
      if (filter.scope) q.push("scope=" + encodeURIComponent(filter.scope));
      if (filter.expert) q.push("expert=" + encodeURIComponent(filter.expert));
      const r = await call("GET", "/items" + (q.length ? "?" + q.join("&") : ""));
      return { items: r.items || [], count: r.count || 0 };
    },
    async getItem(id) {
      const r = await call("GET", "/items/" + encodeURIComponent(id));
      return r.item;
    },

    // ---- write ----
    async remember(item) {
      const r = await call("POST", "/remember", {
        content: item.content,
        expert: item.expert,
        scope: item.scope,
        importance: item.importance,
        tags: item.tags || [],
        ttl: item.ttl != null ? item.ttl : null,
      });
      return r.item;                          // {id, content, expert, scope, importance, tags, …}
    },
    async forget(id) {
      const r = await call("POST", "/forget", { id });
      return !!r.deleted;
    },

    // ---- recall / inspect ----
    async inspect(query, opts) {
      opts = opts || {};
      const r = await call("POST", "/inspect", {
        query,
        scope: opts.scope || "/",
        max_tokens: opts.max_tokens || 256,
        top_experts: opts.top_experts || 3,
        pin_experts: opts.pin_experts || [],
      });
      // Normalize routing.scores (object map) -> sorted array for the UI.
      const scores = Object.entries((r.routing && r.routing.scores) || {})
        .map(([expert, score]) => ({ expert, score }))
        .sort((a, b) => b.score - a.score);
      return {
        query: r.query,
        routing: {
          selected: (r.routing && r.routing.selected_experts) || [],
          unselected: (r.routing && r.routing.unselected_experts) || [],
          scores,
          widened: !!(r.routing && r.routing.widened),
          reason: (r.routing && r.routing.reason) || "",
        },
        pack: {
          tokens: r.pack && r.pack.tokens,
          maxTokens: r.pack && r.pack.max_tokens,
          items: (r.pack && r.pack.items) || [],
          dropped: (r.pack && r.pack.dropped) || [],
          citations: (r.pack && r.pack.citations) || [],
          prompt: (r.pack && r.pack.prompt) || "",
        },
      };
    },
    async routerExplain(query, opts) {
      opts = opts || {};
      const r = await call("POST", "/router/explain", {
        query, scope: opts.scope || "/", top_experts: opts.top_experts || 3,
      });
      return {
        selected: r.selected_experts || [],
        unselected: r.unselected_experts || [],
        scores: r.scores || [],
        reason: r.reason || "",
        widened: !!r.widened,
      };
    },
  };

  window.MC = MC;
})();