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.
- package/dist/{chunk-ZEO4BR64.js → chunk-APLGXZWQ.js} +263 -40
- package/dist/chunk-APLGXZWQ.js.map +1 -0
- package/dist/cli.js +108 -40
- package/dist/cli.js.map +1 -1
- package/dist/{command-PW7KW3PP.js → command-O6JD4567.js} +303 -6
- package/dist/command-O6JD4567.js.map +1 -0
- package/dist/ui/dist/assets/index-3Jrdu_6p.js +6 -0
- package/dist/ui/dist/assets/index-DtRr0Wrk.css +1 -0
- package/dist/ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/chunk-ZEO4BR64.js.map +0 -1
- package/dist/command-PW7KW3PP.js.map +0 -1
- package/dist/ui/dist/assets/index-CfL9bOYD.css +0 -1
- package/dist/ui/dist/assets/index-Ck-e8hv_.js +0 -5
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
summarizeServerProviders,
|
|
67
67
|
validateCustomEndpointUrl,
|
|
68
68
|
writeSecureLogLine
|
|
69
|
-
} from "./chunk-
|
|
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.
|
|
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
|
-
|
|
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)}
|
|
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-
|
|
1800
|
+
//# sourceMappingURL=command-O6JD4567.js.map
|