anygate 0.5.6 → 0.5.8

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.
@@ -66,7 +66,7 @@ import {
66
66
  summarizeServerProviders,
67
67
  validateCustomEndpointUrl,
68
68
  writeSecureLogLine
69
- } from "./chunk-ZEO4BR64.js";
69
+ } from "./chunk-APLGXZWQ.js";
70
70
  import {
71
71
  getTemplateById,
72
72
  listAddableTemplates,
@@ -316,7 +316,9 @@ function getGatewayLaunchCommand(appId, options = {}) {
316
316
  if (options.trace) {
317
317
  args.push("--trace");
318
318
  }
319
- if (options.providerId && options.modelId) {
319
+ if (options.favoritesCatalog) {
320
+ args.push("--favorites");
321
+ } else if (options.providerId && options.modelId) {
320
322
  args.push("--provider", options.providerId, "--model", options.modelId);
321
323
  } else if (options.providerId || options.modelId) {
322
324
  throw new Error("Both providerId and modelId are required for an explicit anygate launch.");
@@ -579,6 +581,8 @@ function handleUiApiRequest(req, res, opts = {}) {
579
581
  handlePostConfig(req, res);
580
582
  } else if (url === "/api/models" && req.method === "GET") {
581
583
  handleGetModels(res);
584
+ } else if (url === "/api/models/test" && req.method === "POST") {
585
+ handleTestModel(req, res);
582
586
  } else if (url === "/api/keys" && req.method === "POST") {
583
587
  handlePostKeys(req, res);
584
588
  } else if (url === "/api/providers/refresh" && req.method === "POST") {
@@ -703,6 +707,294 @@ async function handleGetModels(res) {
703
707
  sendCatalogFetchError(res, err, "Model fetch");
704
708
  }
705
709
  }
710
+ var TEST_TIMEOUT_MS = 3e4;
711
+ var TEST_DEFAULT_PROMPT = "Reply with a single word: pong";
712
+ var TEST_MAX_SAMPLE = 400;
713
+ async function handleTestModel(req, res) {
714
+ let body;
715
+ try {
716
+ body = JSON.parse(await readBody(req));
717
+ } catch {
718
+ sendJson(res, 400, { error: "Invalid JSON body" });
719
+ return;
720
+ }
721
+ const { providerId, modelId, prompt } = body;
722
+ if (!providerId || typeof providerId !== "string") {
723
+ sendJson(res, 400, { error: "providerId required" });
724
+ return;
725
+ }
726
+ if (!modelId || typeof modelId !== "string") {
727
+ sendJson(res, 400, { error: "modelId required" });
728
+ return;
729
+ }
730
+ try {
731
+ const catalog = await fetchModelsWithTimeout();
732
+ const provider = catalog.find((p2) => p2.id === providerId);
733
+ if (!provider) {
734
+ sendJson(res, 200, {
735
+ ok: false,
736
+ providerId,
737
+ modelId,
738
+ format: "unknown",
739
+ connectMs: null,
740
+ ttftMs: null,
741
+ totalMs: null,
742
+ tokens: 0,
743
+ tokensPerSec: null,
744
+ streamStability: "n/a",
745
+ sample: "",
746
+ error: `Provider "${providerId}" not found in catalog`,
747
+ errorHint: "Connect or enable the provider in Providers & Keys first."
748
+ });
749
+ return;
750
+ }
751
+ const model = provider.models.find((m) => m.id === modelId);
752
+ if (!model) {
753
+ sendJson(res, 200, {
754
+ ok: false,
755
+ providerId,
756
+ modelId,
757
+ format: "unknown",
758
+ connectMs: null,
759
+ ttftMs: null,
760
+ totalMs: null,
761
+ tokens: 0,
762
+ tokensPerSec: null,
763
+ streamStability: "n/a",
764
+ sample: "",
765
+ error: `Model "${modelId}" not found on provider "${providerId}"`,
766
+ errorHint: "Pick a model that belongs to the selected provider."
767
+ });
768
+ return;
769
+ }
770
+ const registry = loadRegistry();
771
+ const registryProvider = registry.providers.find((p2) => p2.id === providerId);
772
+ let apiKey = provider.apiKey ?? null;
773
+ if (registryProvider?.authRef) {
774
+ apiKey = await resolveProviderCredential(providerId, registryProvider.authRef);
775
+ }
776
+ const format = model.modelFormat === "anthropic" || model.modelFormat === "openai" ? model.modelFormat : "unsupported";
777
+ const upstreamModelId = model.upstreamModelId || model.id;
778
+ const userPrompt = typeof prompt === "string" && prompt.trim() ? prompt : TEST_DEFAULT_PROMPT;
779
+ let upstreamUrl;
780
+ const headers = { "Content-Type": "application/json" };
781
+ let requestBody;
782
+ if (model.modelFormat === "anthropic") {
783
+ const base = (model.baseUrl || "").replace(/\/v1\/?$/, "");
784
+ if (!base) {
785
+ sendJson(res, 200, failNoEndpoint(providerId, modelId, format));
786
+ return;
787
+ }
788
+ upstreamUrl = `${base.replace(/\/$/, "")}/v1/messages`;
789
+ headers["x-api-key"] = apiKey ?? "";
790
+ headers["anthropic-version"] = "2023-06-01";
791
+ requestBody = {
792
+ model: upstreamModelId,
793
+ max_tokens: 256,
794
+ stream: true,
795
+ messages: [{ role: "user", content: userPrompt }]
796
+ };
797
+ } else if (model.modelFormat === "openai") {
798
+ const completionsUrl = model.completionsUrl || (model.apiBaseUrl ? `${model.apiBaseUrl.replace(/\/$/, "")}/chat/completions` : "");
799
+ if (!completionsUrl) {
800
+ sendJson(res, 200, failNoEndpoint(providerId, modelId, format));
801
+ return;
802
+ }
803
+ upstreamUrl = completionsUrl;
804
+ headers["Authorization"] = `Bearer ${apiKey ?? ""}`;
805
+ requestBody = {
806
+ model: upstreamModelId,
807
+ stream: true,
808
+ messages: [{ role: "user", content: userPrompt }]
809
+ };
810
+ } else {
811
+ sendJson(res, 200, {
812
+ ok: false,
813
+ providerId,
814
+ modelId,
815
+ format: "unsupported",
816
+ connectMs: null,
817
+ ttftMs: null,
818
+ totalMs: null,
819
+ tokens: 0,
820
+ tokensPerSec: null,
821
+ streamStability: "n/a",
822
+ sample: "",
823
+ error: `Model format "${model.modelFormat}" is not directly testable from the UI`,
824
+ errorHint: "This provider routes through anygate's SDK adapter. Use the CLI or Server gateway to exercise it."
825
+ });
826
+ return;
827
+ }
828
+ for (const [k, v] of Object.entries(provider.headers ?? {})) {
829
+ if (!(k.toLowerCase() in headers)) headers[k] = v;
830
+ }
831
+ const controller = new AbortController();
832
+ const timeout = setTimeout(() => controller.abort(), TEST_TIMEOUT_MS);
833
+ const t0 = performance.now();
834
+ let connectMs = null;
835
+ let ttftMs = null;
836
+ let totalMs = null;
837
+ let tokens = 0;
838
+ const gaps = [];
839
+ let lastChunkT = null;
840
+ let sample = "";
841
+ try {
842
+ const upstream = await fetch(upstreamUrl, {
843
+ method: "POST",
844
+ headers,
845
+ body: JSON.stringify(requestBody),
846
+ signal: controller.signal
847
+ });
848
+ connectMs = Math.round(performance.now() - t0);
849
+ if (!upstream.ok || !upstream.body) {
850
+ const snippet = upstream.body ? (await upstream.text()).slice(0, 240) : "";
851
+ clearTimeout(timeout);
852
+ sendJson(res, 200, {
853
+ ok: false,
854
+ providerId,
855
+ modelId,
856
+ format,
857
+ connectMs,
858
+ ttftMs: null,
859
+ totalMs: Math.round(performance.now() - t0),
860
+ tokens: 0,
861
+ tokensPerSec: null,
862
+ streamStability: "n/a",
863
+ sample: snippet,
864
+ error: `Upstream responded ${upstream.status} ${upstream.statusText}`,
865
+ errorHint: upstream.status === 401 ? "API key missing or rejected \u2014 add/refresh the key in Providers & Keys." : upstream.status === 404 ? "Endpoint not found \u2014 the provider base URL may be wrong." : "Check the provider key and base URL."
866
+ });
867
+ return;
868
+ }
869
+ const reader = upstream.body.getReader();
870
+ const decoder = new TextDecoder();
871
+ let buffer = "";
872
+ while (true) {
873
+ const { done, value } = await reader.read();
874
+ if (done) break;
875
+ const chunk = decoder.decode(value, { stream: true });
876
+ if (!chunk) continue;
877
+ const now = performance.now();
878
+ const delta = now - t0;
879
+ if (ttftMs === null) ttftMs = Math.round(delta);
880
+ if (lastChunkT !== null) gaps.push(now - lastChunkT);
881
+ lastChunkT = now;
882
+ buffer += chunk;
883
+ if (model.modelFormat === "anthropic") {
884
+ const lines = buffer.split("\n");
885
+ buffer = lines.pop() ?? "";
886
+ for (const line of lines) {
887
+ const trimmed = line.trim();
888
+ if (!trimmed.startsWith("data:")) continue;
889
+ const data = trimmed.slice(5).trim();
890
+ if (data === "[DONE]") continue;
891
+ try {
892
+ const evt = JSON.parse(data);
893
+ if (evt.type === "content_block_delta" && evt.delta?.type === "text_delta") {
894
+ tokens++;
895
+ if (sample.length < TEST_MAX_SAMPLE) sample += evt.delta.text;
896
+ }
897
+ } catch {
898
+ }
899
+ }
900
+ } else {
901
+ const lines = buffer.split("\n");
902
+ buffer = lines.pop() ?? "";
903
+ for (const line of lines) {
904
+ const trimmed = line.trim();
905
+ if (!trimmed.startsWith("data:")) continue;
906
+ const data = trimmed.slice(5).trim();
907
+ if (data === "[DONE]") continue;
908
+ try {
909
+ const evt = JSON.parse(data);
910
+ const piece = evt.choices?.[0]?.delta?.content;
911
+ if (typeof piece === "string" && piece) {
912
+ tokens++;
913
+ if (sample.length < TEST_MAX_SAMPLE) sample += piece;
914
+ }
915
+ } catch {
916
+ }
917
+ }
918
+ }
919
+ }
920
+ totalMs = Math.round(performance.now() - t0);
921
+ clearTimeout(timeout);
922
+ const tokensPerSec = tokens > 0 && ttftMs !== null ? Math.round(tokens / (totalMs - ttftMs + 1) * 1e3 * 10) / 10 : null;
923
+ const streamStability = computeStability(gaps);
924
+ sendJson(res, 200, {
925
+ ok: true,
926
+ providerId,
927
+ modelId,
928
+ format,
929
+ connectMs,
930
+ ttftMs,
931
+ totalMs,
932
+ tokens,
933
+ tokensPerSec,
934
+ streamStability,
935
+ sample: sample.trim()
936
+ });
937
+ } catch (fetchErr) {
938
+ clearTimeout(timeout);
939
+ const aborted = fetchErr instanceof Error && fetchErr.name === "AbortError";
940
+ sendJson(res, 200, {
941
+ ok: false,
942
+ providerId,
943
+ modelId,
944
+ format,
945
+ connectMs,
946
+ ttftMs,
947
+ totalMs: Math.round(performance.now() - t0),
948
+ tokens,
949
+ tokensPerSec: null,
950
+ streamStability: gaps.length ? computeStability(gaps) : "n/a",
951
+ sample,
952
+ error: aborted ? `Request timed out after ${TEST_TIMEOUT_MS / 1e3}s` : String(fetchErr),
953
+ errorHint: aborted ? "The endpoint did not respond in time \u2014 it may be slow, down, or the URL is unreachable." : "Network-level failure reaching the provider endpoint."
954
+ });
955
+ }
956
+ } catch (err) {
957
+ sendJson(res, 500, {
958
+ ok: false,
959
+ providerId,
960
+ modelId,
961
+ format: "unknown",
962
+ connectMs: null,
963
+ ttftMs: null,
964
+ totalMs: null,
965
+ tokens: 0,
966
+ tokensPerSec: null,
967
+ streamStability: "n/a",
968
+ sample: "",
969
+ error: String(err)
970
+ });
971
+ }
972
+ }
973
+ function failNoEndpoint(providerId, modelId, format) {
974
+ return {
975
+ ok: false,
976
+ providerId,
977
+ modelId,
978
+ format,
979
+ connectMs: null,
980
+ ttftMs: null,
981
+ totalMs: null,
982
+ tokens: 0,
983
+ tokensPerSec: null,
984
+ streamStability: "n/a",
985
+ sample: "",
986
+ error: "No usable upstream endpoint for this model",
987
+ errorHint: "The provider is missing a base URL / completions URL. Re-add or refresh it in Providers & Keys."
988
+ };
989
+ }
990
+ function computeStability(gaps) {
991
+ if (gaps.length < 3) return "steady";
992
+ const mean = gaps.reduce((a, b) => a + b, 0) / gaps.length;
993
+ if (mean === 0) return "steady";
994
+ const variance = gaps.reduce((a, b) => a + (b - mean) ** 2, 0) / gaps.length;
995
+ const cv = Math.sqrt(variance) / mean;
996
+ return cv > 1.2 ? "intermittent" : "steady";
997
+ }
706
998
  async function handlePostKeys(req, res) {
707
999
  try {
708
1000
  const body = JSON.parse(await readBody(req));
@@ -1086,7 +1378,7 @@ var AGY_APP_IDS = /* @__PURE__ */ new Set(["antigravity", "agy", "antigravity-id
1086
1378
  async function handleLaunchApp(req, res, opts) {
1087
1379
  try {
1088
1380
  const body = JSON.parse(await readBody(req));
1089
- const { appId, favorites, cwd } = body;
1381
+ const { appId, favorites, favoritesCatalog, cwd } = body;
1090
1382
  let { providerId, modelId } = body;
1091
1383
  if (!appId) {
1092
1384
  sendJson(res, 400, { error: "Missing appId" });
@@ -1105,7 +1397,11 @@ async function handleLaunchApp(req, res, opts) {
1105
1397
  sendJson(res, 400, { error: "Both providerId and modelId are required to launch a specific anygate model." });
1106
1398
  return;
1107
1399
  }
1108
- if (favorites && !providerId && !modelId) {
1400
+ const fullCatalog = Boolean(favoritesCatalog);
1401
+ if (fullCatalog) {
1402
+ providerId = void 0;
1403
+ modelId = void 0;
1404
+ } else if (favorites && !providerId && !modelId) {
1109
1405
  const prefs = loadPreferences();
1110
1406
  const favList = AGY_APP_IDS.has(appId) ? prefs.antigravityCliFavoriteModels ?? [] : prefs.favoriteModels ?? [];
1111
1407
  if (favList.length > 0) {
@@ -1129,12 +1425,13 @@ async function handleLaunchApp(req, res, opts) {
1129
1425
  const launchCmd = getGatewayLaunchCommand(appId, {
1130
1426
  providerId,
1131
1427
  modelId,
1428
+ favoritesCatalog: fullCatalog,
1132
1429
  cwd: launchFolder,
1133
1430
  trace: opts.trace
1134
1431
  });
1135
1432
  traceUi(
1136
1433
  opts,
1137
- `launch app=${appId} provider=${providerId ?? ""} model=${modelId ?? ""} favorites=${Boolean(favorites)} resolved-from-favorites=${Boolean(favorites && providerId)} cwd=${launchFolder ?? ""} command=${launchCmd}`
1434
+ `launch app=${appId} provider=${providerId ?? ""} model=${modelId ?? ""} favorites=${Boolean(favorites)} catalog=${fullCatalog} cwd=${launchFolder ?? ""} command=${launchCmd}`
1138
1435
  );
1139
1436
  exec(launchCmd, (err) => {
1140
1437
  if (err) {
@@ -1500,4 +1797,4 @@ export {
1500
1797
  resolveUiShutdownDecision,
1501
1798
  runUiCommand
1502
1799
  };
1503
- //# sourceMappingURL=command-PW7KW3PP.js.map
1800
+ //# sourceMappingURL=command-O6JD4567.js.map