@acruxcoreai/mcp-server 0.1.0 → 0.2.0
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/CHANGELOG.md +32 -0
- package/README.md +3 -2
- package/dist/bin.js +303 -11
- package/dist/bin.js.map +1 -1
- package/dist/bin.mjs +303 -11
- package/dist/bin.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +303 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +303 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -162,7 +162,7 @@ declare function resolveConfig(sources: ConfigSources): McpServerConfig;
|
|
|
162
162
|
declare function parseToolsets(raw: string): Toolset[];
|
|
163
163
|
|
|
164
164
|
/** Version reported to clients in `initialize`. Kept in step with package.json. */
|
|
165
|
-
declare const SERVER_VERSION = "0.
|
|
165
|
+
declare const SERVER_VERSION = "0.2.0";
|
|
166
166
|
/** What the caller gets back, so a host can inspect what was registered. */
|
|
167
167
|
interface BuiltServer {
|
|
168
168
|
/** The configured MCP server, not yet connected to a transport. */
|
package/dist/index.d.ts
CHANGED
|
@@ -162,7 +162,7 @@ declare function resolveConfig(sources: ConfigSources): McpServerConfig;
|
|
|
162
162
|
declare function parseToolsets(raw: string): Toolset[];
|
|
163
163
|
|
|
164
164
|
/** Version reported to clients in `initialize`. Kept in step with package.json. */
|
|
165
|
-
declare const SERVER_VERSION = "0.
|
|
165
|
+
declare const SERVER_VERSION = "0.2.0";
|
|
166
166
|
/** What the caller gets back, so a host can inspect what was registered. */
|
|
167
167
|
interface BuiltServer {
|
|
168
168
|
/** The configured MCP server, not yet connected to a transport. */
|
package/dist/index.js
CHANGED
|
@@ -242,10 +242,15 @@ function toApiError(label, status, body) {
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
// src/format.ts
|
|
245
|
+
var TRUNCATION_MARKER = "\n\n[truncated: response exceeded the size limit \u2014 narrow the query, use a smaller `limit`, or fetch one item at a time]";
|
|
246
|
+
function isTruncated(result) {
|
|
247
|
+
const first = result.content[0];
|
|
248
|
+
return first?.type === "text" && first.text.endsWith(TRUNCATION_MARKER);
|
|
249
|
+
}
|
|
245
250
|
function truncateText(text, maxBytes) {
|
|
246
251
|
const encoder = new TextEncoder();
|
|
247
252
|
if (encoder.encode(text).length <= maxBytes) return text;
|
|
248
|
-
const marker =
|
|
253
|
+
const marker = TRUNCATION_MARKER;
|
|
249
254
|
const budget = Math.max(0, maxBytes - encoder.encode(marker).length);
|
|
250
255
|
let end = Math.min(text.length, budget);
|
|
251
256
|
while (end > 0 && encoder.encode(text.slice(0, end)).length > budget) {
|
|
@@ -628,10 +633,87 @@ var catalogTools = [
|
|
|
628
633
|
|
|
629
634
|
// src/toolsets/evaluations.ts
|
|
630
635
|
var import_zod2 = require("zod");
|
|
636
|
+
|
|
637
|
+
// src/optimize-report.ts
|
|
638
|
+
function summarizeCandidates(grid, cells) {
|
|
639
|
+
const cellByKey = new Map(cells.map((cell) => [cell.cellKey, cell]));
|
|
640
|
+
const baselineByModel = new Map(
|
|
641
|
+
cells.filter((cell) => cell.isProductionBaseline).map((cell) => [cell.model, cell])
|
|
642
|
+
);
|
|
643
|
+
const byCandidate = /* @__PURE__ */ new Map();
|
|
644
|
+
for (const gridCell of grid) {
|
|
645
|
+
if (gridCell.variantKind !== "candidate" || !gridCell.promptCandidateId) continue;
|
|
646
|
+
const reportCell = cellByKey.get(gridCell.cellKey);
|
|
647
|
+
const baseline = baselineByModel.get(gridCell.model);
|
|
648
|
+
let summary = byCandidate.get(gridCell.promptCandidateId);
|
|
649
|
+
if (!summary) {
|
|
650
|
+
summary = {
|
|
651
|
+
candidateId: gridCell.promptCandidateId,
|
|
652
|
+
label: gridCell.variantLabel,
|
|
653
|
+
scores: [],
|
|
654
|
+
beatsBaseline: false
|
|
655
|
+
};
|
|
656
|
+
byCandidate.set(gridCell.promptCandidateId, summary);
|
|
657
|
+
}
|
|
658
|
+
summary.scores.push({
|
|
659
|
+
model: gridCell.model,
|
|
660
|
+
avgScore: reportCell?.avgScore ?? null,
|
|
661
|
+
passRate: reportCell?.passRate ?? null,
|
|
662
|
+
baselineAvgScore: baseline?.avgScore ?? null,
|
|
663
|
+
scoreDelta: reportCell?.deltaVsBaseline?.score ?? null,
|
|
664
|
+
verdict: reportCell?.deltaVsBaseline?.label ?? "unknown"
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
const summaries = [...byCandidate.values()];
|
|
668
|
+
for (const summary of summaries) {
|
|
669
|
+
summary.beatsBaseline = summary.scores.some((score) => score.verdict === "improved") && summary.scores.every((score) => score.verdict === "improved" || score.verdict === "flat");
|
|
670
|
+
}
|
|
671
|
+
return summaries.sort((a, b) => a.label.localeCompare(b.label));
|
|
672
|
+
}
|
|
673
|
+
function bestCandidate(candidates) {
|
|
674
|
+
let best = null;
|
|
675
|
+
let bestDelta = -Infinity;
|
|
676
|
+
for (const candidate of candidates) {
|
|
677
|
+
if (!candidate.beatsBaseline) continue;
|
|
678
|
+
const deltas = candidate.scores.map((score) => score.scoreDelta).filter((delta) => delta !== null);
|
|
679
|
+
const mean = deltas.reduce((sum, delta) => sum + delta, 0) / Math.max(1, deltas.length);
|
|
680
|
+
if (mean > bestDelta) {
|
|
681
|
+
best = candidate;
|
|
682
|
+
bestDelta = mean;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return best;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// src/toolsets/evaluations.ts
|
|
631
689
|
var historyMessageSchema = import_zod2.z.object({
|
|
632
690
|
role: import_zod2.z.enum(["system", "user", "assistant", "tool"]),
|
|
633
691
|
content: import_zod2.z.string()
|
|
634
692
|
});
|
|
693
|
+
async function loadOptimizeRun(client, runId) {
|
|
694
|
+
const run = await client.get(`/runs/${runId}`);
|
|
695
|
+
const notDraftedYet = run.grid.length === 0;
|
|
696
|
+
if (run.kind !== "optimize" && !notDraftedYet) {
|
|
697
|
+
throw new Error(
|
|
698
|
+
`Run ${runId} is an evaluation run, not an optimize run. Read it with get_experiment_run.`
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
if (run.status !== "succeeded") return { run, report: null, candidates: [] };
|
|
702
|
+
const report = await client.get(`/runs/${runId}/report`);
|
|
703
|
+
return { run, report, candidates: summarizeCandidates(run.grid, report.cells) };
|
|
704
|
+
}
|
|
705
|
+
function baselineOf(run) {
|
|
706
|
+
const cell = run.grid.find((row) => row.isProductionBaseline && row.promptVersionId);
|
|
707
|
+
if (!cell?.promptVersionId) {
|
|
708
|
+
throw new Error(`Run ${run.id} has no baseline version, so a promote cannot be checked.`);
|
|
709
|
+
}
|
|
710
|
+
const alias = /^v\d+$/.test(cell.variantLabel) ? null : cell.variantLabel;
|
|
711
|
+
return { versionId: cell.promptVersionId, alias };
|
|
712
|
+
}
|
|
713
|
+
function formatDelta(delta) {
|
|
714
|
+
if (delta === null) return "n/a";
|
|
715
|
+
return `${delta >= 0 ? "+" : ""}${delta.toFixed(1)}`;
|
|
716
|
+
}
|
|
635
717
|
var evaluationTools = [
|
|
636
718
|
defineTool({
|
|
637
719
|
name: "list_datasets",
|
|
@@ -766,23 +848,203 @@ var evaluationTools = [
|
|
|
766
848
|
title: "Get a run, optionally with its comparison report",
|
|
767
849
|
toolset: "evaluations",
|
|
768
850
|
readOnlyHint: true,
|
|
769
|
-
description: "Read one run's status and results. With include_report=true it also fetches the comparison report: the full (variant x model) matrix with per-cell averages, each cell's delta against the same-model production baseline, a leaderboard and an advisory winner. Ask for the report only once the run has succeeded \u2014 an in-flight run has nothing to compare.",
|
|
851
|
+
description: "Read one run's status and results. With include_report=true it also fetches the comparison report: the full (variant x model) matrix with per-cell averages, each cell's delta against the same-model production baseline, a leaderboard and an advisory winner. Ask for the report only once the run has succeeded \u2014 an in-flight run has nothing to compare. For an optimize run, get_optimize_report gives the same scores per candidate, with the ids promote_optimize_candidate needs.",
|
|
770
852
|
inputSchema: {
|
|
771
853
|
run_id: import_zod2.z.string().uuid().describe("Run UUID."),
|
|
772
854
|
include_report: import_zod2.z.boolean().optional().describe("Also fetch the comparison report.")
|
|
773
855
|
},
|
|
774
856
|
async handler({ run_id, include_report }, { client, config }) {
|
|
775
|
-
const run = await client.get(
|
|
857
|
+
const run = await client.get(
|
|
858
|
+
`/runs/${run_id}`
|
|
859
|
+
);
|
|
776
860
|
let report = void 0;
|
|
777
861
|
if (include_report) {
|
|
778
862
|
report = await client.get(`/runs/${run_id}/report`);
|
|
779
863
|
}
|
|
780
864
|
return okResult(
|
|
781
|
-
`Run ${run_id} is ${run.status} with ${run.results?.
|
|
865
|
+
`Run ${run_id} is ${run.status} with ${run.results?.total ?? 0} result(s).`,
|
|
782
866
|
{ run, report },
|
|
783
867
|
config.maxTextBytes
|
|
784
868
|
);
|
|
785
869
|
}
|
|
870
|
+
}),
|
|
871
|
+
defineTool({
|
|
872
|
+
name: "optimize_prompt",
|
|
873
|
+
title: "Start an optimizer run for a prompt",
|
|
874
|
+
toolset: "evaluations",
|
|
875
|
+
readOnlyHint: false,
|
|
876
|
+
description: 'Ask the optimizer to improve a prompt against a dataset. An optimizer model drafts up to `draft_count` rewrites of the version behind `alias` (default "production"), then every rewrite and that baseline run over the dataset on each of `models`, and `judge_model` scores every answer. Returns the run id as soon as the run is queued \u2014 drafting and scoring take minutes, so poll get_optimize_report. Nothing is promoted automatically: a rewrite only ships when promote_optimize_candidate is called. The dataset\'s examples need `criteria`, or the judge has nothing to grade against. Every model named here must be registered in the gateway (see list_models).',
|
|
877
|
+
inputSchema: {
|
|
878
|
+
prompt: import_zod2.z.string().min(1).describe("Prompt name or UUID to improve."),
|
|
879
|
+
dataset_id: import_zod2.z.string().uuid().describe("Dataset whose examples the rewrites are scored on."),
|
|
880
|
+
models: import_zod2.z.array(import_zod2.z.string().min(1)).min(1).describe("Gateway model public names to test each rewrite and the baseline on."),
|
|
881
|
+
judge_model: import_zod2.z.string().min(1).describe("Gateway model that scores every answer. Required."),
|
|
882
|
+
optimizer_model: import_zod2.z.string().min(1).optional().describe("Gateway model that writes the rewrites. Default: the first entry of `models`."),
|
|
883
|
+
draft_count: import_zod2.z.number().int().min(1).max(6).optional().describe("How many rewrites to draft, 1 to 6. Default 3."),
|
|
884
|
+
alias: import_zod2.z.string().min(1).optional().describe('Alias whose version is the baseline to beat. Default "production".'),
|
|
885
|
+
optimizer_prompt: import_zod2.z.string().optional().describe("Name or UUID of a team prompt to use as the optimizer's instructions.")
|
|
886
|
+
},
|
|
887
|
+
async handler(args, { client, config }) {
|
|
888
|
+
const promptId = await resolvePromptRef(client, args.prompt);
|
|
889
|
+
const body = {
|
|
890
|
+
dataset_id: args.dataset_id,
|
|
891
|
+
models: args.models,
|
|
892
|
+
judge_model: args.judge_model
|
|
893
|
+
};
|
|
894
|
+
if (args.optimizer_model !== void 0) body.optimizer_model = args.optimizer_model;
|
|
895
|
+
if (args.draft_count !== void 0) body.draft_count = args.draft_count;
|
|
896
|
+
if (args.alias !== void 0) body.alias = args.alias;
|
|
897
|
+
if (args.optimizer_prompt !== void 0) {
|
|
898
|
+
body.optimizer_prompt_id = await resolvePromptRef(client, args.optimizer_prompt);
|
|
899
|
+
}
|
|
900
|
+
const result = await client.post(`/prompts/${promptId}/optimize`, body);
|
|
901
|
+
const warning = result.prompt_mismatch_warning ? " Warning: some dataset examples were built from a different prompt \u2014 see prompt_mismatch_warning." : "";
|
|
902
|
+
return okResult(
|
|
903
|
+
`Optimize run ${result.run_id} is ${result.status}; ${result.optimizer_model} drafts the rewrites, then they are scored against the ${args.alias ?? "production"} baseline. Poll get_optimize_report with this run id.${warning}`,
|
|
904
|
+
result,
|
|
905
|
+
config.maxTextBytes
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
}),
|
|
909
|
+
defineTool({
|
|
910
|
+
name: "get_optimize_report",
|
|
911
|
+
title: "Read an optimizer run's candidates and scores",
|
|
912
|
+
toolset: "evaluations",
|
|
913
|
+
readOnlyHint: true,
|
|
914
|
+
description: "Read an optimize run started by optimize_prompt. While it is queued or running this returns only its status \u2014 call again later. Once it has succeeded it lists every drafted rewrite with its candidate id, the optimizer's rationale, the rewritten messages, and its average score next to the baseline's on each model. `beatsBaseline` is true only for a rewrite that improved on at least one model and was improved or flat on every other; `recommended` is the one among those with the largest gain, or null when no rewrite beat the baseline. When the rewrites are too long to fit in one result, their messages are left out (`messagesOmitted`); pass `candidate_id` to read one rewrite in full.",
|
|
915
|
+
inputSchema: {
|
|
916
|
+
run_id: import_zod2.z.string().uuid().describe("Optimize run UUID, from optimize_prompt."),
|
|
917
|
+
candidate_id: import_zod2.z.string().uuid().optional().describe("Return only this candidate, with its full messages.")
|
|
918
|
+
},
|
|
919
|
+
async handler({ run_id, candidate_id }, { client, config }) {
|
|
920
|
+
const { run, report, candidates } = await loadOptimizeRun(client, run_id);
|
|
921
|
+
if (run.status === "queued" || run.status === "running") {
|
|
922
|
+
return okResult(
|
|
923
|
+
`Optimize run ${run_id} is ${run.status}. No scores yet \u2014 call get_optimize_report again shortly.`,
|
|
924
|
+
{ runId: run_id, status: run.status },
|
|
925
|
+
config.maxTextBytes
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
if (run.status === "failed") {
|
|
929
|
+
return okResult(
|
|
930
|
+
`Optimize run ${run_id} failed: ${run.error ?? "no reason recorded"}. Nothing to promote.`,
|
|
931
|
+
{ runId: run_id, status: run.status, error: run.error },
|
|
932
|
+
config.maxTextBytes
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
if (candidate_id !== void 0) {
|
|
936
|
+
const candidate = candidates.find((row) => row.candidateId === candidate_id);
|
|
937
|
+
if (!candidate) {
|
|
938
|
+
throw new Error(`Candidate ${candidate_id} is not a rewrite from run ${run_id}.`);
|
|
939
|
+
}
|
|
940
|
+
const detail = await client.get(
|
|
941
|
+
`/runs/${run_id}/candidates/${candidate_id}`
|
|
942
|
+
);
|
|
943
|
+
return okResult(
|
|
944
|
+
`${candidate.label} (candidate_id ${candidate_id}) from optimize run ${run_id}; beatsBaseline: ${candidate.beatsBaseline}.`,
|
|
945
|
+
{
|
|
946
|
+
runId: run_id,
|
|
947
|
+
candidate: { ...candidate, rationale: detail.rationale, messages: detail.messages }
|
|
948
|
+
},
|
|
949
|
+
config.maxTextBytes
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
const reads = await Promise.allSettled(
|
|
953
|
+
candidates.map(
|
|
954
|
+
(candidate) => client.get(`/runs/${run_id}/candidates/${candidate.candidateId}`)
|
|
955
|
+
)
|
|
956
|
+
);
|
|
957
|
+
const withContent = candidates.map((candidate, index) => {
|
|
958
|
+
const read = reads[index];
|
|
959
|
+
return read.status === "fulfilled" ? { ...candidate, rationale: read.value.rationale, messages: read.value.messages } : {
|
|
960
|
+
...candidate,
|
|
961
|
+
rationale: null,
|
|
962
|
+
messages: null,
|
|
963
|
+
readError: read.reason instanceof Error ? read.reason.message : String(read.reason)
|
|
964
|
+
};
|
|
965
|
+
});
|
|
966
|
+
const best = bestCandidate(candidates);
|
|
967
|
+
const winners = candidates.filter((candidate) => candidate.beatsBaseline).length;
|
|
968
|
+
const summary = best ? `Optimize run ${run_id} succeeded. ${winners} of ${candidates.length} rewrite(s) beat the baseline. Recommended: ${best.label} (candidate_id ${best.candidateId}), ` + best.scores.map((score) => `${formatDelta(score.scoreDelta)} on ${score.model}`).join(", ") + ". Promote it with promote_optimize_candidate only if the user agrees." : `Optimize run ${run_id} succeeded, but none of its ${candidates.length} rewrite(s) beat the baseline. Nothing is worth promoting; keep the current version.`;
|
|
969
|
+
const payload = {
|
|
970
|
+
runId: run_id,
|
|
971
|
+
status: run.status,
|
|
972
|
+
judgeModel: report?.judge?.model ?? null,
|
|
973
|
+
recommended: best?.candidateId ?? null,
|
|
974
|
+
baseline: (report?.cells ?? []).filter((cell) => cell.isProductionBaseline).map((cell) => ({
|
|
975
|
+
label: cell.variantLabel,
|
|
976
|
+
model: cell.model,
|
|
977
|
+
avgScore: cell.avgScore,
|
|
978
|
+
passRate: cell.passRate
|
|
979
|
+
})),
|
|
980
|
+
candidates: withContent
|
|
981
|
+
};
|
|
982
|
+
const full = okResult(summary, payload, config.maxTextBytes);
|
|
983
|
+
if (!isTruncated(full)) return full;
|
|
984
|
+
return okResult(
|
|
985
|
+
`${summary} The rewrites are too long to show together, so their messages are left out; call get_optimize_report again with candidate_id to read one.`,
|
|
986
|
+
{
|
|
987
|
+
...payload,
|
|
988
|
+
messagesOmitted: true,
|
|
989
|
+
candidates: withContent.map(({ messages: _messages, ...rest }) => rest)
|
|
990
|
+
},
|
|
991
|
+
config.maxTextBytes
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
}),
|
|
995
|
+
defineTool({
|
|
996
|
+
name: "promote_optimize_candidate",
|
|
997
|
+
title: "Ship an optimizer rewrite as a new prompt version",
|
|
998
|
+
toolset: "evaluations",
|
|
999
|
+
readOnlyHint: false,
|
|
1000
|
+
destructiveHint: true,
|
|
1001
|
+
description: "Commit one rewrite from a succeeded optimize run as a new prompt version and move `alias` to it, so live traffic on that alias starts using it. `alias` defaults to the alias the run was scored against (production when the run used the latest version). Take `candidate_id` from get_optimize_report. Refuses a rewrite that did not beat the baseline, and refuses when the alias now points at a version other than the one the run scored against; set `promote_without_improvement` only when the user has explicitly asked to ship that rewrite anyway. Undo by moving the alias back with promote_prompt_alias.",
|
|
1002
|
+
inputSchema: {
|
|
1003
|
+
run_id: import_zod2.z.string().uuid().describe("Optimize run UUID."),
|
|
1004
|
+
candidate_id: import_zod2.z.string().uuid().describe("Candidate id from get_optimize_report."),
|
|
1005
|
+
alias: import_zod2.z.string().min(1).optional().describe("Alias to move. Default: the alias the run was scored against."),
|
|
1006
|
+
promote_without_improvement: import_zod2.z.boolean().optional().describe(
|
|
1007
|
+
"Ship a rewrite that did not beat the baseline, or replace a version the run never scored. Only on the user's explicit say-so."
|
|
1008
|
+
)
|
|
1009
|
+
},
|
|
1010
|
+
async handler(args, { client, config }) {
|
|
1011
|
+
const { run, candidates } = await loadOptimizeRun(client, args.run_id);
|
|
1012
|
+
if (run.status !== "succeeded") {
|
|
1013
|
+
throw new Error(
|
|
1014
|
+
`Optimize run ${args.run_id} is ${run.status}, so its rewrites are not fully scored. Promote only from a succeeded run.`
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
const candidate = candidates.find((row) => row.candidateId === args.candidate_id);
|
|
1018
|
+
if (!candidate) {
|
|
1019
|
+
throw new Error(
|
|
1020
|
+
`Candidate ${args.candidate_id} is not a rewrite from run ${args.run_id}. Take the id from get_optimize_report.`
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
if (!candidate.beatsBaseline && !args.promote_without_improvement) {
|
|
1024
|
+
const scores = candidate.scores.map((score) => `${formatDelta(score.scoreDelta)} (${score.verdict}) on ${score.model}`).join(", ");
|
|
1025
|
+
throw new Error(
|
|
1026
|
+
`Refused: ${candidate.label} did not beat the baseline (${scores}). Promoting it would ship a prompt that scored no better than the current one. Set promote_without_improvement to true only if the user explicitly asked for this rewrite.`
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
const baseline = baselineOf(run);
|
|
1030
|
+
const alias = args.alias ?? baseline.alias ?? "production";
|
|
1031
|
+
const detail = await client.get(
|
|
1032
|
+
`/runs/${args.run_id}/candidates/${args.candidate_id}`
|
|
1033
|
+
);
|
|
1034
|
+
const aliases = await client.get(`/prompts/${detail.promptId}/aliases`);
|
|
1035
|
+
const live = aliases.find((row) => row.alias === alias);
|
|
1036
|
+
if (live && live.versionId !== baseline.versionId && !args.promote_without_improvement) {
|
|
1037
|
+
throw new Error(
|
|
1038
|
+
`Refused: alias "${alias}" now points at version ${live.versionNumber}, but run ${args.run_id} scored its rewrites against a different version. Nothing compared ${candidate.label} with version ${live.versionNumber}. Start a new optimize run against this alias, or set promote_without_improvement to true only if the user explicitly asked to replace it anyway.`
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
const result = await client.post(`/runs/${args.run_id}/promote`, { prompt_candidate_id: args.candidate_id, alias });
|
|
1042
|
+
return okResult(
|
|
1043
|
+
`Promoted ${candidate.label} as version ${result.version.versionNumber}; alias "${result.alias.alias}" now points at it.`,
|
|
1044
|
+
result,
|
|
1045
|
+
config.maxTextBytes
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
786
1048
|
})
|
|
787
1049
|
];
|
|
788
1050
|
|
|
@@ -809,6 +1071,9 @@ function gatewayMeta(headers) {
|
|
|
809
1071
|
}
|
|
810
1072
|
return meta;
|
|
811
1073
|
}
|
|
1074
|
+
function traceSuffix(meta) {
|
|
1075
|
+
return meta["trace-id"] ? ` Open it with get_trace, trace_id ${meta["trace-id"]}.` : "";
|
|
1076
|
+
}
|
|
812
1077
|
function firstChoiceText(body) {
|
|
813
1078
|
const choice = body?.choices?.[0];
|
|
814
1079
|
const content = choice?.message?.content;
|
|
@@ -853,9 +1118,7 @@ var gatewayTools = [
|
|
|
853
1118
|
});
|
|
854
1119
|
const meta = gatewayMeta(response.headers);
|
|
855
1120
|
return okResult(
|
|
856
|
-
`Ran "${name}"@${args.alias ?? "production"} on ${meta.model ?? "the resolved model"}${meta["cost-usd"] ? ` for $${meta["cost-usd"]}` : ""}
|
|
857
|
-
response.data
|
|
858
|
-
).slice(0, 400)}`,
|
|
1121
|
+
`Ran "${name}"@${args.alias ?? "production"} on ${meta.model ?? "the resolved model"}${meta["cost-usd"] ? ` for $${meta["cost-usd"]}` : ""}.${traceSuffix(meta)} Reply: ` + firstChoiceText(response.data).slice(0, 400),
|
|
859
1122
|
{ completion: response.data, gateway: meta },
|
|
860
1123
|
config.maxTextBytes
|
|
861
1124
|
);
|
|
@@ -896,9 +1159,7 @@ var gatewayTools = [
|
|
|
896
1159
|
});
|
|
897
1160
|
const meta = gatewayMeta(response.headers);
|
|
898
1161
|
return okResult(
|
|
899
|
-
`Completion from ${meta.model ?? args.model}${meta["cost-usd"] ? ` ($${meta["cost-usd"]})` : ""}:
|
|
900
|
-
response.data
|
|
901
|
-
).slice(0, 400)}`,
|
|
1162
|
+
`Completion from ${meta.model ?? args.model}${meta["cost-usd"] ? ` ($${meta["cost-usd"]})` : ""}.${traceSuffix(meta)} Reply: ` + firstChoiceText(response.data).slice(0, 400),
|
|
902
1163
|
{ completion: response.data, gateway: meta },
|
|
903
1164
|
config.maxTextBytes
|
|
904
1165
|
);
|
|
@@ -938,6 +1199,37 @@ var gatewayTools = [
|
|
|
938
1199
|
config.maxTextBytes
|
|
939
1200
|
);
|
|
940
1201
|
}
|
|
1202
|
+
}),
|
|
1203
|
+
defineTool({
|
|
1204
|
+
name: "get_spending_limits",
|
|
1205
|
+
title: "Get budgets and virtual-key limits",
|
|
1206
|
+
toolset: "gateway",
|
|
1207
|
+
readOnlyHint: true,
|
|
1208
|
+
description: "Every spend budget on the team \u2014 team-wide or on one virtual key \u2014 with its period, limit, live spend, what is left and when it resets, plus each virtual key's rate limits and model/provider allow-lists. Use this after a gateway call is refused with HTTP 402 (budget spent) or 429 (rate limit) to see which limit caused it. Read-only: budgets and keys are changed in the dashboard.",
|
|
1209
|
+
inputSchema: {},
|
|
1210
|
+
async handler(_args, { client, config }) {
|
|
1211
|
+
const [budgets, keys] = await Promise.all([
|
|
1212
|
+
client.get("/gateway/budgets"),
|
|
1213
|
+
client.get("/gateway/keys")
|
|
1214
|
+
]);
|
|
1215
|
+
const keyNames = new Map(keys.map((k) => [k.id, k.name]));
|
|
1216
|
+
const limits = budgets.map((b) => ({
|
|
1217
|
+
id: b.id,
|
|
1218
|
+
scope: b.virtualKeyId === null ? "team" : "virtual_key",
|
|
1219
|
+
virtualKeyId: b.virtualKeyId,
|
|
1220
|
+
virtualKeyName: b.virtualKeyId === null ? null : keyNames.get(b.virtualKeyId) ?? null,
|
|
1221
|
+
period: b.period,
|
|
1222
|
+
limitUsd: b.limitUsd,
|
|
1223
|
+
spendUsd: b.spendUsd,
|
|
1224
|
+
remainingUsd: Math.max(0, b.limitUsd - b.spendUsd),
|
|
1225
|
+
exhausted: b.spendUsd >= b.limitUsd,
|
|
1226
|
+
resetsAt: b.resetsAt
|
|
1227
|
+
}));
|
|
1228
|
+
const summary = limits.length === 0 ? `No budgets on this team, so the gateway never refuses a call for spend. ${keys.length} virtual key(s).` : `${limits.length} budget(s), ${keys.length} virtual key(s). ` + limits.map(
|
|
1229
|
+
(l) => `${l.scope === "team" ? "Team" : `Key "${l.virtualKeyName ?? l.virtualKeyId}"`} ${l.period}: $${l.spendUsd} of $${l.limitUsd} spent` + (l.exhausted ? " (EXHAUSTED)" : "") + (l.resetsAt ? `, resets ${l.resetsAt}` : "")
|
|
1230
|
+
).join("; ") + ".";
|
|
1231
|
+
return okResult(summary, { budgets: limits, virtualKeys: keys }, config.maxTextBytes);
|
|
1232
|
+
}
|
|
941
1233
|
})
|
|
942
1234
|
];
|
|
943
1235
|
|
|
@@ -1303,7 +1595,7 @@ function selectTools(config) {
|
|
|
1303
1595
|
}
|
|
1304
1596
|
|
|
1305
1597
|
// src/server.ts
|
|
1306
|
-
var SERVER_VERSION = "0.
|
|
1598
|
+
var SERVER_VERSION = "0.2.0";
|
|
1307
1599
|
function buildInstructions(toolNames, readOnly) {
|
|
1308
1600
|
const parts = [
|
|
1309
1601
|
`AcruxCore is an LLM-ops platform: a prompt registry, a versioned tool catalog, an AI gateway, tracing, and evaluation.
|