@anuma/sdk 6.39.0 → 6.39.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.
@@ -4296,15 +4296,17 @@ Build ONLY when the user gives an explicit instruction to build, create, generat
4296
4296
 
4297
4297
  Once the user HAS given an explicit build instruction, you are in App Builder mode: you produce polished, production-quality React apps that feel designed \u2014 not generic.
4298
4298
 
4299
- DESIGN: on the initial build, before writing code, briefly state (2-3 sentences) what you want this app to feel like \u2014 name the specific fonts, the palette in hex, and one signature detail. Be concrete: "Fraunces display + Inter body + JetBrains Mono metadata, terracotta #b75432 accent on bone #f3efe6, paper-grain texture via inline SVG noise" is the kind of specificity that anchors the build. Default to nothing: don't pick "safe modern productivity" unless the brief literally asks for utilitarian. On later turns, state a design brief only when the change is visual (a restyle, new theme, or new UI); for a logic, data, or behavior-only change, skip the brief and just make the change.
4299
+ DESIGN: on the initial build, before writing code, briefly state (2-3 sentences) what you want this app to feel like \u2014 name the specific fonts, the palette in hex, and one signature detail. Be concrete: "Fraunces display + Inter body + JetBrains Mono metadata, terracotta #b75432 accent on bone #f3efe6, paper-grain texture via inline SVG noise" is the kind of specificity that anchors the build. Default to nothing: don't pick "safe modern productivity" unless the brief literally asks for utilitarian. On later turns, state a design brief only when the change is visual (a restyle, new theme, or new UI); for a text, logic, data, or behavior-only change, skip the brief and just make the change.
4300
+
4301
+ EDIT SCOPE: Change only the files and lines required by the current user request. A title rename is a text edit, not a design change. Preserve styles, dependencies, and unrelated logic on text edits. Design feedback does not authorize unrelated edits.
4300
4302
 
4301
4303
  WORKFLOW:
4302
4304
  1. NEVER output code as text or markdown. ALWAYS use tools.
4303
4305
  2. Initial build: state your design brief, then create_file with a "files" array writing ALL files in one call. The preview renders automatically.
4304
4306
  3. patch_file requires read_file for that path earlier in the conversation (or create_file in the same conversation, which counts). read_file output is numbered "42: <text>" \u2014 the prefix is display-only; never copy it into "find". Each "find" must match exactly one location \u2014 include 2-3 lines of surrounding context. Pass multiple patches per call to change distinct locations.
4305
4307
  4. Prefer patch_file for changes to existing files \u2014 surgical, reviewable, preserves surrounding code. Reserve create_file overwrites for substantial restructuring. Inserts: find the line before, replace with that line + the new line. Deletes: empty replace.
4306
- 5. After substantial changes, call critique_design first (taste, hierarchy, distinctiveness, weakest decision) and then audit_design (mechanical: tokens, focus states, aria-labels, alt, heading order). Patch what each surfaces. critique catches taste issues no checklist would; audit catches mechanics you'd miss. Run both; act on both.
4307
- 6. Before declaring done, call verify_app. The host's preview runtime reports whether your app actually mounted and surfaces any errors it captured \u2014 hallucinated imports, syntax the bundler rejected, undefined components, runtime crashes. If \`rendered\` is false or \`errors\` is non-empty, fix every error before the next message. If the response includes a \`note\` that verify_app isn't wired in this host, skip \u2014 audit + critique are your feedback in that case.
4308
+ 5. On the initial build, when adding new UI, or after user-requested design changes, call critique_design first and then audit_design. Critique checks taste, hierarchy, and distinctiveness. Audit checks tokens, focus states, aria-labels, alt text, and heading order. Patch only issues within the current user request. For text, logic, or data edits, skip both design tools.
4309
+ 6. Before declaring done, call verify_app. The host's preview runtime reports whether your app actually mounted and surfaces any errors it captured \u2014 hallucinated imports, syntax the bundler rejected, undefined components, runtime crashes. If \`rendered\` is false or \`errors\` is non-empty, fix every error before the next message. If the response includes a \`note\` that verify_app isn't wired in this host, skip runtime verification. An unavailable runtime verifier does not authorize design changes. Use audit_design and critique_design only for the initial build, adding new UI, or user-requested design changes.
4308
4310
  7. Keep responses to a sentence or two. Exception: when responding to critique_design, write a substantive paragraph per rubric question.
4309
4311
 
4310
4312
  STRUCTURE:
@@ -4470,12 +4472,13 @@ function isCacheExpired(cache, expirationMs = DEFAULT_CACHE_EXPIRATION_MS) {
4470
4472
  if (!cache) return true;
4471
4473
  return Date.now() - cache.timestamp > expirationMs;
4472
4474
  }
4473
- function buildCacheEntry(tools, checksum) {
4475
+ function buildCacheEntry(tools, checksum, etag) {
4474
4476
  return {
4475
4477
  tools,
4476
4478
  timestamp: Date.now(),
4477
4479
  version: CACHE_VERSION,
4478
- ...checksum && { checksum }
4480
+ ...checksum && { checksum },
4481
+ ...etag && { etag }
4479
4482
  };
4480
4483
  }
4481
4484
  function writeLocalStorageCache(entry) {
@@ -4510,19 +4513,36 @@ function shouldRefreshTools(responseChecksum, cache = localStorageToolsCache) {
4510
4513
  const cachedChecksum = getToolsChecksum2(cache);
4511
4514
  return cachedChecksum instanceof Promise ? cachedChecksum.then(decide) : decide(cachedChecksum);
4512
4515
  }
4513
- async function fetchServerToolsFromApi(baseUrl, token) {
4514
- const response = await fetch(`${baseUrl}/api/v1/tools`, {
4516
+ async function fetchServerToolsFromApi(baseUrl, authHeaders, etag) {
4517
+ const request = (validator) => fetch(`${baseUrl}/api/v1/tools`, {
4515
4518
  method: "GET",
4516
4519
  headers: {
4517
- Authorization: `Bearer ${token}`,
4518
- "Content-Type": "application/json"
4520
+ ...authHeaders,
4521
+ "Content-Type": "application/json",
4522
+ ...validator && { "If-None-Match": validator }
4519
4523
  }
4520
4524
  });
4525
+ let response;
4526
+ try {
4527
+ response = await request(etag);
4528
+ } catch (error) {
4529
+ if (!etag || !(error instanceof TypeError)) throw error;
4530
+ etag = void 0;
4531
+ response = await request();
4532
+ }
4533
+ if (etag && response.status === 304) {
4534
+ return { notModified: true };
4535
+ }
4521
4536
  if (!response.ok) {
4522
4537
  throw new Error(`Failed to fetch server tools: ${response.status}`);
4523
4538
  }
4524
4539
  const data = await response.json();
4525
- return convertServerToolsResponse(data);
4540
+ const responseEtag = response.headers?.get("ETag") ?? void 0;
4541
+ return {
4542
+ notModified: false,
4543
+ ...convertServerToolsResponse(data),
4544
+ ...responseEtag && { etag: responseEtag }
4545
+ };
4526
4546
  }
4527
4547
  async function getServerTools(options) {
4528
4548
  const {
@@ -4554,34 +4574,30 @@ async function getServerTools(options) {
4554
4574
  try {
4555
4575
  const { BASE_URL: BASE_URL2 } = await import("./clientConfig-VPOGQRNP.mjs");
4556
4576
  const effectiveBaseUrl = baseUrl ?? BASE_URL2;
4577
+ const validator = cached && !forceRefresh ? cached.etag : void 0;
4578
+ let authHeaders;
4557
4579
  if (apiKey) {
4558
- const response = await fetch(`${effectiveBaseUrl}/api/v1/tools`, {
4559
- method: "GET",
4560
- headers: {
4561
- "X-API-Key": apiKey,
4562
- "Content-Type": "application/json"
4563
- }
4564
- });
4565
- if (!response.ok) {
4566
- throw new Error(`Failed to fetch server tools: ${response.status}`);
4580
+ authHeaders = { "X-API-Key": apiKey };
4581
+ } else {
4582
+ if (!getToken) {
4583
+ getLogger().warn("[serverTools] No auth method available for fetching tools");
4584
+ return cached?.tools ?? [];
4567
4585
  }
4568
- const data = await response.json();
4569
- const { tools: tools2, checksum: checksum2 } = convertServerToolsResponse(data);
4570
- await persistCache(buildCacheEntry(tools2, checksum2));
4571
- return tools2;
4572
- }
4573
- if (!getToken) {
4574
- getLogger().warn("[serverTools] No auth method available for fetching tools");
4575
- return cached?.tools ?? [];
4586
+ const token = await getToken();
4587
+ if (!token) {
4588
+ getLogger().warn("[serverTools] No auth token available for fetching tools");
4589
+ return cached?.tools ?? [];
4590
+ }
4591
+ authHeaders = { Authorization: `Bearer ${token}` };
4576
4592
  }
4577
- const token = await getToken();
4578
- if (!token) {
4579
- getLogger().warn("[serverTools] No auth token available for fetching tools");
4580
- return cached?.tools ?? [];
4593
+ const result = await fetchServerToolsFromApi(effectiveBaseUrl, authHeaders, validator);
4594
+ if (result.notModified) {
4595
+ const confirmed = cached;
4596
+ await persistCache({ ...confirmed, timestamp: Date.now() });
4597
+ return confirmed.tools;
4581
4598
  }
4582
- const { tools, checksum } = await fetchServerToolsFromApi(effectiveBaseUrl, token);
4583
- await persistCache(buildCacheEntry(tools, checksum));
4584
- return tools;
4599
+ await persistCache(buildCacheEntry(result.tools, result.checksum, result.etag));
4600
+ return result.tools;
4585
4601
  } catch (error) {
4586
4602
  getLogger().error("[serverTools] Failed to fetch server tools:", error);
4587
4603
  if (cached?.tools) {
@@ -22032,6 +22048,128 @@ function useCredits(options = {}) {
22032
22048
 
22033
22049
  // src/react/useModels.ts
22034
22050
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
22051
+ var MODELS_CACHE_TTL_MS = 5 * 60 * 1e3;
22052
+ var modelsCache = /* @__PURE__ */ new Map();
22053
+ var modelsInFlight = /* @__PURE__ */ new Map();
22054
+ var cacheGeneration = 0;
22055
+ function clearModelsCache() {
22056
+ cacheGeneration++;
22057
+ modelsCache.clear();
22058
+ modelsInFlight.clear();
22059
+ }
22060
+ function pruneStaleTokenEntries(newKey) {
22061
+ const [newBaseUrl, newProvider] = JSON.parse(newKey);
22062
+ for (const existingKey of modelsCache.keys()) {
22063
+ if (existingKey === newKey) continue;
22064
+ const [existingBaseUrl, existingProvider] = JSON.parse(existingKey);
22065
+ if (existingBaseUrl === newBaseUrl && existingProvider === newProvider) {
22066
+ modelsCache.delete(existingKey);
22067
+ }
22068
+ }
22069
+ }
22070
+ function fingerprintToken(token) {
22071
+ if (!token) return "anon";
22072
+ let h1 = 3735928559;
22073
+ let h2 = 1103547991;
22074
+ for (let i = 0; i < token.length; i++) {
22075
+ const ch = token.charCodeAt(i);
22076
+ h1 = Math.imul(h1 ^ ch, 2654435761);
22077
+ h2 = Math.imul(h2 ^ ch, 1597334677);
22078
+ }
22079
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
22080
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
22081
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);
22082
+ }
22083
+ function modelsCacheKey(baseUrl, provider, token) {
22084
+ return JSON.stringify([baseUrl, provider ?? null, fingerprintToken(token)]);
22085
+ }
22086
+ async function fetchAllModelPages(baseUrl, provider, headers, previous, signal) {
22087
+ const pages = /* @__PURE__ */ new Map();
22088
+ const models = [];
22089
+ let pageToken;
22090
+ do {
22091
+ const pageKey = pageToken ?? "";
22092
+ const stored2 = previous?.pages.get(pageKey);
22093
+ const request = (etag) => getApiV1Models({
22094
+ baseUrl,
22095
+ headers: etag ? { ...headers, "If-None-Match": etag } : headers,
22096
+ query: { provider, page_token: pageToken },
22097
+ signal
22098
+ });
22099
+ let response = await request(stored2?.etag);
22100
+ if (stored2?.etag && !response.response && response.error) {
22101
+ response = await request();
22102
+ }
22103
+ let page;
22104
+ if (response.response?.status === 304) {
22105
+ if (!stored2) throw new Error("Failed to fetch models");
22106
+ page = stored2;
22107
+ } else {
22108
+ if (response.error) {
22109
+ const errorMsg = response.error.error ?? "Failed to fetch models";
22110
+ throw new Error(errorMsg);
22111
+ }
22112
+ page = {
22113
+ body: response.data ?? {},
22114
+ etag: response.response?.headers?.get("ETag") ?? void 0
22115
+ };
22116
+ }
22117
+ pages.set(pageKey, page);
22118
+ models.push(...page.body.data || []);
22119
+ pageToken = page.body.next_page_token;
22120
+ } while (pageToken);
22121
+ return { pages, models, fetchedAt: Date.now() };
22122
+ }
22123
+ function loadModels(key, baseUrl, provider, headers, force, signal) {
22124
+ const cached = modelsCache.get(key);
22125
+ if (cached && !force && Date.now() - cached.fetchedAt < MODELS_CACHE_TTL_MS) {
22126
+ return Promise.resolve([...cached.models]);
22127
+ }
22128
+ const shared = (force ? void 0 : modelsInFlight.get(key)) ?? startSharedRequest(key, baseUrl, provider, headers, cached);
22129
+ return waitForSharedRequest(key, shared, signal).then((models) => [...models]);
22130
+ }
22131
+ function startSharedRequest(key, baseUrl, provider, headers, previous) {
22132
+ const controller = new AbortController();
22133
+ const generation = cacheGeneration;
22134
+ const shared = {
22135
+ controller,
22136
+ waiters: 0,
22137
+ promise: Promise.resolve([])
22138
+ };
22139
+ shared.promise = fetchAllModelPages(baseUrl, provider, headers, previous, controller.signal).then((entry) => {
22140
+ if (modelsInFlight.get(key) === shared && generation === cacheGeneration) {
22141
+ pruneStaleTokenEntries(key);
22142
+ modelsCache.set(key, entry);
22143
+ }
22144
+ return entry.models;
22145
+ }).finally(() => {
22146
+ if (modelsInFlight.get(key) === shared) modelsInFlight.delete(key);
22147
+ });
22148
+ modelsInFlight.set(key, shared);
22149
+ return shared;
22150
+ }
22151
+ function waitForSharedRequest(key, shared, signal) {
22152
+ shared.waiters++;
22153
+ let left = false;
22154
+ const leave = () => {
22155
+ if (left) return;
22156
+ left = true;
22157
+ shared.waiters--;
22158
+ if (shared.waiters === 0) {
22159
+ shared.controller.abort();
22160
+ if (modelsInFlight.get(key) === shared) modelsInFlight.delete(key);
22161
+ }
22162
+ };
22163
+ if (signal.aborted) {
22164
+ leave();
22165
+ } else {
22166
+ signal.addEventListener("abort", leave, { once: true });
22167
+ }
22168
+ return shared.promise.finally(() => {
22169
+ left = true;
22170
+ signal.removeEventListener("abort", leave);
22171
+ });
22172
+ }
22035
22173
  function useModels(options = {}) {
22036
22174
  const { getToken, baseUrl = BASE_URL, provider, autoFetch = true } = options;
22037
22175
  const [models, setModels] = useState2([]);
@@ -22054,7 +22192,7 @@ function useModels(options = {}) {
22054
22192
  }
22055
22193
  };
22056
22194
  }, []);
22057
- const fetchModels = useCallback2(async () => {
22195
+ const fetchModels = useCallback2(async (force = false) => {
22058
22196
  if (abortControllerRef.current) {
22059
22197
  abortControllerRef.current.abort();
22060
22198
  }
@@ -22073,35 +22211,17 @@ function useModels(options = {}) {
22073
22211
  if (token) {
22074
22212
  headers["Authorization"] = `Bearer ${token}`;
22075
22213
  }
22076
- let allModels = [];
22077
- let nextPageToken;
22078
- do {
22079
- if (signal.aborted) return;
22080
- const response = await getApiV1Models({
22081
- baseUrl: baseUrlRef.current,
22082
- headers,
22083
- query: {
22084
- provider: providerRef.current,
22085
- page_token: nextPageToken
22086
- },
22087
- signal
22088
- });
22089
- if (response.error) {
22090
- const errorMsg = response.error.error ?? "Failed to fetch models";
22091
- throw new Error(errorMsg);
22092
- }
22093
- if (response.data) {
22094
- const newModels = response.data.data || [];
22095
- allModels = [...allModels, ...newModels];
22096
- nextPageToken = response.data.next_page_token;
22097
- }
22098
- } while (nextPageToken);
22214
+ const baseUrl2 = baseUrlRef.current;
22215
+ const provider2 = providerRef.current;
22216
+ const key = modelsCacheKey(baseUrl2, provider2, token);
22217
+ const allModels = await loadModels(key, baseUrl2, provider2, headers, force, signal);
22099
22218
  if (signal.aborted) return;
22100
22219
  setModels(allModels);
22101
22220
  } catch (err) {
22102
22221
  if (err instanceof Error && err.name === "AbortError") {
22103
22222
  return;
22104
22223
  }
22224
+ if (signal.aborted) return;
22105
22225
  setError(err instanceof Error ? err : new Error(String(err)));
22106
22226
  } finally {
22107
22227
  if (!signal.aborted) {
@@ -22114,7 +22234,7 @@ function useModels(options = {}) {
22114
22234
  }, []);
22115
22235
  const refetch = useCallback2(async () => {
22116
22236
  setModels([]);
22117
- await fetchModels();
22237
+ await fetchModels(true);
22118
22238
  }, [fetchModels]);
22119
22239
  const hasFetchedRef = useRef2(false);
22120
22240
  useEffect2(() => {
@@ -25899,6 +26019,7 @@ export {
25899
26019
  clearConversationMemoriesOp,
25900
26020
  clearEncryptionKey,
25901
26021
  clearLazyTitleCache,
26022
+ clearModelsCache,
25902
26023
  clearServerToolsCache,
25903
26024
  consoleLogger,
25904
26025
  conversationMemoryToStored,
package/dist/index.cjs CHANGED
@@ -3261,15 +3261,17 @@ Build ONLY when the user gives an explicit instruction to build, create, generat
3261
3261
 
3262
3262
  Once the user HAS given an explicit build instruction, you are in App Builder mode: you produce polished, production-quality React apps that feel designed \u2014 not generic.
3263
3263
 
3264
- DESIGN: on the initial build, before writing code, briefly state (2-3 sentences) what you want this app to feel like \u2014 name the specific fonts, the palette in hex, and one signature detail. Be concrete: "Fraunces display + Inter body + JetBrains Mono metadata, terracotta #b75432 accent on bone #f3efe6, paper-grain texture via inline SVG noise" is the kind of specificity that anchors the build. Default to nothing: don't pick "safe modern productivity" unless the brief literally asks for utilitarian. On later turns, state a design brief only when the change is visual (a restyle, new theme, or new UI); for a logic, data, or behavior-only change, skip the brief and just make the change.
3264
+ DESIGN: on the initial build, before writing code, briefly state (2-3 sentences) what you want this app to feel like \u2014 name the specific fonts, the palette in hex, and one signature detail. Be concrete: "Fraunces display + Inter body + JetBrains Mono metadata, terracotta #b75432 accent on bone #f3efe6, paper-grain texture via inline SVG noise" is the kind of specificity that anchors the build. Default to nothing: don't pick "safe modern productivity" unless the brief literally asks for utilitarian. On later turns, state a design brief only when the change is visual (a restyle, new theme, or new UI); for a text, logic, data, or behavior-only change, skip the brief and just make the change.
3265
+
3266
+ EDIT SCOPE: Change only the files and lines required by the current user request. A title rename is a text edit, not a design change. Preserve styles, dependencies, and unrelated logic on text edits. Design feedback does not authorize unrelated edits.
3265
3267
 
3266
3268
  WORKFLOW:
3267
3269
  1. NEVER output code as text or markdown. ALWAYS use tools.
3268
3270
  2. Initial build: state your design brief, then create_file with a "files" array writing ALL files in one call. The preview renders automatically.
3269
3271
  3. patch_file requires read_file for that path earlier in the conversation (or create_file in the same conversation, which counts). read_file output is numbered "42: <text>" \u2014 the prefix is display-only; never copy it into "find". Each "find" must match exactly one location \u2014 include 2-3 lines of surrounding context. Pass multiple patches per call to change distinct locations.
3270
3272
  4. Prefer patch_file for changes to existing files \u2014 surgical, reviewable, preserves surrounding code. Reserve create_file overwrites for substantial restructuring. Inserts: find the line before, replace with that line + the new line. Deletes: empty replace.
3271
- 5. After substantial changes, call critique_design first (taste, hierarchy, distinctiveness, weakest decision) and then audit_design (mechanical: tokens, focus states, aria-labels, alt, heading order). Patch what each surfaces. critique catches taste issues no checklist would; audit catches mechanics you'd miss. Run both; act on both.
3272
- 6. Before declaring done, call verify_app. The host's preview runtime reports whether your app actually mounted and surfaces any errors it captured \u2014 hallucinated imports, syntax the bundler rejected, undefined components, runtime crashes. If \`rendered\` is false or \`errors\` is non-empty, fix every error before the next message. If the response includes a \`note\` that verify_app isn't wired in this host, skip \u2014 audit + critique are your feedback in that case.
3273
+ 5. On the initial build, when adding new UI, or after user-requested design changes, call critique_design first and then audit_design. Critique checks taste, hierarchy, and distinctiveness. Audit checks tokens, focus states, aria-labels, alt text, and heading order. Patch only issues within the current user request. For text, logic, or data edits, skip both design tools.
3274
+ 6. Before declaring done, call verify_app. The host's preview runtime reports whether your app actually mounted and surfaces any errors it captured \u2014 hallucinated imports, syntax the bundler rejected, undefined components, runtime crashes. If \`rendered\` is false or \`errors\` is non-empty, fix every error before the next message. If the response includes a \`note\` that verify_app isn't wired in this host, skip runtime verification. An unavailable runtime verifier does not authorize design changes. Use audit_design and critique_design only for the initial build, adding new UI, or user-requested design changes.
3273
3275
  7. Keep responses to a sentence or two. Exception: when responding to critique_design, write a substantive paragraph per rubric question.
3274
3276
 
3275
3277
  STRUCTURE:
@@ -3432,12 +3434,13 @@ function isCacheExpired(cache, expirationMs = DEFAULT_CACHE_EXPIRATION_MS) {
3432
3434
  if (!cache) return true;
3433
3435
  return Date.now() - cache.timestamp > expirationMs;
3434
3436
  }
3435
- function buildCacheEntry(tools, checksum) {
3437
+ function buildCacheEntry(tools, checksum, etag) {
3436
3438
  return {
3437
3439
  tools,
3438
3440
  timestamp: Date.now(),
3439
3441
  version: CACHE_VERSION,
3440
- ...checksum && { checksum }
3442
+ ...checksum && { checksum },
3443
+ ...etag && { etag }
3441
3444
  };
3442
3445
  }
3443
3446
  function writeLocalStorageCache(entry) {
@@ -3457,19 +3460,36 @@ var localStorageToolsCache = {
3457
3460
  set: writeLocalStorageCache,
3458
3461
  clear: removeLocalStorageCache
3459
3462
  };
3460
- async function fetchServerToolsFromApi(baseUrl, token) {
3461
- const response = await fetch(`${baseUrl}/api/v1/tools`, {
3463
+ async function fetchServerToolsFromApi(baseUrl, authHeaders, etag) {
3464
+ const request = (validator) => fetch(`${baseUrl}/api/v1/tools`, {
3462
3465
  method: "GET",
3463
3466
  headers: {
3464
- Authorization: `Bearer ${token}`,
3465
- "Content-Type": "application/json"
3467
+ ...authHeaders,
3468
+ "Content-Type": "application/json",
3469
+ ...validator && { "If-None-Match": validator }
3466
3470
  }
3467
3471
  });
3472
+ let response;
3473
+ try {
3474
+ response = await request(etag);
3475
+ } catch (error) {
3476
+ if (!etag || !(error instanceof TypeError)) throw error;
3477
+ etag = void 0;
3478
+ response = await request();
3479
+ }
3480
+ if (etag && response.status === 304) {
3481
+ return { notModified: true };
3482
+ }
3468
3483
  if (!response.ok) {
3469
3484
  throw new Error(`Failed to fetch server tools: ${response.status}`);
3470
3485
  }
3471
3486
  const data = await response.json();
3472
- return convertServerToolsResponse(data);
3487
+ const responseEtag = response.headers?.get("ETag") ?? void 0;
3488
+ return {
3489
+ notModified: false,
3490
+ ...convertServerToolsResponse(data),
3491
+ ...responseEtag && { etag: responseEtag }
3492
+ };
3473
3493
  }
3474
3494
  async function getServerTools(options) {
3475
3495
  const {
@@ -3501,34 +3521,30 @@ async function getServerTools(options) {
3501
3521
  try {
3502
3522
  const { BASE_URL: BASE_URL2 } = await Promise.resolve().then(() => (init_clientConfig(), clientConfig_exports));
3503
3523
  const effectiveBaseUrl = baseUrl ?? BASE_URL2;
3524
+ const validator = cached && !forceRefresh ? cached.etag : void 0;
3525
+ let authHeaders;
3504
3526
  if (apiKey) {
3505
- const response = await fetch(`${effectiveBaseUrl}/api/v1/tools`, {
3506
- method: "GET",
3507
- headers: {
3508
- "X-API-Key": apiKey,
3509
- "Content-Type": "application/json"
3510
- }
3511
- });
3512
- if (!response.ok) {
3513
- throw new Error(`Failed to fetch server tools: ${response.status}`);
3527
+ authHeaders = { "X-API-Key": apiKey };
3528
+ } else {
3529
+ if (!getToken) {
3530
+ getLogger().warn("[serverTools] No auth method available for fetching tools");
3531
+ return cached?.tools ?? [];
3514
3532
  }
3515
- const data = await response.json();
3516
- const { tools: tools2, checksum: checksum2 } = convertServerToolsResponse(data);
3517
- await persistCache(buildCacheEntry(tools2, checksum2));
3518
- return tools2;
3519
- }
3520
- if (!getToken) {
3521
- getLogger().warn("[serverTools] No auth method available for fetching tools");
3522
- return cached?.tools ?? [];
3533
+ const token = await getToken();
3534
+ if (!token) {
3535
+ getLogger().warn("[serverTools] No auth token available for fetching tools");
3536
+ return cached?.tools ?? [];
3537
+ }
3538
+ authHeaders = { Authorization: `Bearer ${token}` };
3523
3539
  }
3524
- const token = await getToken();
3525
- if (!token) {
3526
- getLogger().warn("[serverTools] No auth token available for fetching tools");
3527
- return cached?.tools ?? [];
3540
+ const result = await fetchServerToolsFromApi(effectiveBaseUrl, authHeaders, validator);
3541
+ if (result.notModified) {
3542
+ const confirmed = cached;
3543
+ await persistCache({ ...confirmed, timestamp: Date.now() });
3544
+ return confirmed.tools;
3528
3545
  }
3529
- const { tools, checksum } = await fetchServerToolsFromApi(effectiveBaseUrl, token);
3530
- await persistCache(buildCacheEntry(tools, checksum));
3531
- return tools;
3546
+ await persistCache(buildCacheEntry(result.tools, result.checksum, result.etag));
3547
+ return result.tools;
3532
3548
  } catch (error) {
3533
3549
  getLogger().error("[serverTools] Failed to fetch server tools:", error);
3534
3550
  if (cached?.tools) {
package/dist/index.mjs CHANGED
@@ -3056,15 +3056,17 @@ Build ONLY when the user gives an explicit instruction to build, create, generat
3056
3056
 
3057
3057
  Once the user HAS given an explicit build instruction, you are in App Builder mode: you produce polished, production-quality React apps that feel designed \u2014 not generic.
3058
3058
 
3059
- DESIGN: on the initial build, before writing code, briefly state (2-3 sentences) what you want this app to feel like \u2014 name the specific fonts, the palette in hex, and one signature detail. Be concrete: "Fraunces display + Inter body + JetBrains Mono metadata, terracotta #b75432 accent on bone #f3efe6, paper-grain texture via inline SVG noise" is the kind of specificity that anchors the build. Default to nothing: don't pick "safe modern productivity" unless the brief literally asks for utilitarian. On later turns, state a design brief only when the change is visual (a restyle, new theme, or new UI); for a logic, data, or behavior-only change, skip the brief and just make the change.
3059
+ DESIGN: on the initial build, before writing code, briefly state (2-3 sentences) what you want this app to feel like \u2014 name the specific fonts, the palette in hex, and one signature detail. Be concrete: "Fraunces display + Inter body + JetBrains Mono metadata, terracotta #b75432 accent on bone #f3efe6, paper-grain texture via inline SVG noise" is the kind of specificity that anchors the build. Default to nothing: don't pick "safe modern productivity" unless the brief literally asks for utilitarian. On later turns, state a design brief only when the change is visual (a restyle, new theme, or new UI); for a text, logic, data, or behavior-only change, skip the brief and just make the change.
3060
+
3061
+ EDIT SCOPE: Change only the files and lines required by the current user request. A title rename is a text edit, not a design change. Preserve styles, dependencies, and unrelated logic on text edits. Design feedback does not authorize unrelated edits.
3060
3062
 
3061
3063
  WORKFLOW:
3062
3064
  1. NEVER output code as text or markdown. ALWAYS use tools.
3063
3065
  2. Initial build: state your design brief, then create_file with a "files" array writing ALL files in one call. The preview renders automatically.
3064
3066
  3. patch_file requires read_file for that path earlier in the conversation (or create_file in the same conversation, which counts). read_file output is numbered "42: <text>" \u2014 the prefix is display-only; never copy it into "find". Each "find" must match exactly one location \u2014 include 2-3 lines of surrounding context. Pass multiple patches per call to change distinct locations.
3065
3067
  4. Prefer patch_file for changes to existing files \u2014 surgical, reviewable, preserves surrounding code. Reserve create_file overwrites for substantial restructuring. Inserts: find the line before, replace with that line + the new line. Deletes: empty replace.
3066
- 5. After substantial changes, call critique_design first (taste, hierarchy, distinctiveness, weakest decision) and then audit_design (mechanical: tokens, focus states, aria-labels, alt, heading order). Patch what each surfaces. critique catches taste issues no checklist would; audit catches mechanics you'd miss. Run both; act on both.
3067
- 6. Before declaring done, call verify_app. The host's preview runtime reports whether your app actually mounted and surfaces any errors it captured \u2014 hallucinated imports, syntax the bundler rejected, undefined components, runtime crashes. If \`rendered\` is false or \`errors\` is non-empty, fix every error before the next message. If the response includes a \`note\` that verify_app isn't wired in this host, skip \u2014 audit + critique are your feedback in that case.
3068
+ 5. On the initial build, when adding new UI, or after user-requested design changes, call critique_design first and then audit_design. Critique checks taste, hierarchy, and distinctiveness. Audit checks tokens, focus states, aria-labels, alt text, and heading order. Patch only issues within the current user request. For text, logic, or data edits, skip both design tools.
3069
+ 6. Before declaring done, call verify_app. The host's preview runtime reports whether your app actually mounted and surfaces any errors it captured \u2014 hallucinated imports, syntax the bundler rejected, undefined components, runtime crashes. If \`rendered\` is false or \`errors\` is non-empty, fix every error before the next message. If the response includes a \`note\` that verify_app isn't wired in this host, skip runtime verification. An unavailable runtime verifier does not authorize design changes. Use audit_design and critique_design only for the initial build, adding new UI, or user-requested design changes.
3068
3070
  7. Keep responses to a sentence or two. Exception: when responding to critique_design, write a substantive paragraph per rubric question.
3069
3071
 
3070
3072
  STRUCTURE:
@@ -3227,12 +3229,13 @@ function isCacheExpired(cache, expirationMs = DEFAULT_CACHE_EXPIRATION_MS) {
3227
3229
  if (!cache) return true;
3228
3230
  return Date.now() - cache.timestamp > expirationMs;
3229
3231
  }
3230
- function buildCacheEntry(tools, checksum) {
3232
+ function buildCacheEntry(tools, checksum, etag) {
3231
3233
  return {
3232
3234
  tools,
3233
3235
  timestamp: Date.now(),
3234
3236
  version: CACHE_VERSION,
3235
- ...checksum && { checksum }
3237
+ ...checksum && { checksum },
3238
+ ...etag && { etag }
3236
3239
  };
3237
3240
  }
3238
3241
  function writeLocalStorageCache(entry) {
@@ -3252,19 +3255,36 @@ var localStorageToolsCache = {
3252
3255
  set: writeLocalStorageCache,
3253
3256
  clear: removeLocalStorageCache
3254
3257
  };
3255
- async function fetchServerToolsFromApi(baseUrl, token) {
3256
- const response = await fetch(`${baseUrl}/api/v1/tools`, {
3258
+ async function fetchServerToolsFromApi(baseUrl, authHeaders, etag) {
3259
+ const request = (validator) => fetch(`${baseUrl}/api/v1/tools`, {
3257
3260
  method: "GET",
3258
3261
  headers: {
3259
- Authorization: `Bearer ${token}`,
3260
- "Content-Type": "application/json"
3262
+ ...authHeaders,
3263
+ "Content-Type": "application/json",
3264
+ ...validator && { "If-None-Match": validator }
3261
3265
  }
3262
3266
  });
3267
+ let response;
3268
+ try {
3269
+ response = await request(etag);
3270
+ } catch (error) {
3271
+ if (!etag || !(error instanceof TypeError)) throw error;
3272
+ etag = void 0;
3273
+ response = await request();
3274
+ }
3275
+ if (etag && response.status === 304) {
3276
+ return { notModified: true };
3277
+ }
3263
3278
  if (!response.ok) {
3264
3279
  throw new Error(`Failed to fetch server tools: ${response.status}`);
3265
3280
  }
3266
3281
  const data = await response.json();
3267
- return convertServerToolsResponse(data);
3282
+ const responseEtag = response.headers?.get("ETag") ?? void 0;
3283
+ return {
3284
+ notModified: false,
3285
+ ...convertServerToolsResponse(data),
3286
+ ...responseEtag && { etag: responseEtag }
3287
+ };
3268
3288
  }
3269
3289
  async function getServerTools(options) {
3270
3290
  const {
@@ -3296,34 +3316,30 @@ async function getServerTools(options) {
3296
3316
  try {
3297
3317
  const { BASE_URL: BASE_URL2 } = await import("./clientConfig-WITO4T6L.mjs");
3298
3318
  const effectiveBaseUrl = baseUrl ?? BASE_URL2;
3319
+ const validator = cached && !forceRefresh ? cached.etag : void 0;
3320
+ let authHeaders;
3299
3321
  if (apiKey) {
3300
- const response = await fetch(`${effectiveBaseUrl}/api/v1/tools`, {
3301
- method: "GET",
3302
- headers: {
3303
- "X-API-Key": apiKey,
3304
- "Content-Type": "application/json"
3305
- }
3306
- });
3307
- if (!response.ok) {
3308
- throw new Error(`Failed to fetch server tools: ${response.status}`);
3322
+ authHeaders = { "X-API-Key": apiKey };
3323
+ } else {
3324
+ if (!getToken) {
3325
+ getLogger().warn("[serverTools] No auth method available for fetching tools");
3326
+ return cached?.tools ?? [];
3309
3327
  }
3310
- const data = await response.json();
3311
- const { tools: tools2, checksum: checksum2 } = convertServerToolsResponse(data);
3312
- await persistCache(buildCacheEntry(tools2, checksum2));
3313
- return tools2;
3314
- }
3315
- if (!getToken) {
3316
- getLogger().warn("[serverTools] No auth method available for fetching tools");
3317
- return cached?.tools ?? [];
3328
+ const token = await getToken();
3329
+ if (!token) {
3330
+ getLogger().warn("[serverTools] No auth token available for fetching tools");
3331
+ return cached?.tools ?? [];
3332
+ }
3333
+ authHeaders = { Authorization: `Bearer ${token}` };
3318
3334
  }
3319
- const token = await getToken();
3320
- if (!token) {
3321
- getLogger().warn("[serverTools] No auth token available for fetching tools");
3322
- return cached?.tools ?? [];
3335
+ const result = await fetchServerToolsFromApi(effectiveBaseUrl, authHeaders, validator);
3336
+ if (result.notModified) {
3337
+ const confirmed = cached;
3338
+ await persistCache({ ...confirmed, timestamp: Date.now() });
3339
+ return confirmed.tools;
3323
3340
  }
3324
- const { tools, checksum } = await fetchServerToolsFromApi(effectiveBaseUrl, token);
3325
- await persistCache(buildCacheEntry(tools, checksum));
3326
- return tools;
3341
+ await persistCache(buildCacheEntry(result.tools, result.checksum, result.etag));
3342
+ return result.tools;
3327
3343
  } catch (error) {
3328
3344
  getLogger().error("[serverTools] Failed to fetch server tools:", error);
3329
3345
  if (cached?.tools) {