@aexol/spectral 0.9.254 → 0.9.256

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.
Files changed (54) hide show
  1. package/dist/extensions/dataset-uploader/analyzer.d.ts +103 -0
  2. package/dist/extensions/dataset-uploader/analyzer.d.ts.map +1 -0
  3. package/dist/extensions/dataset-uploader/analyzer.js +266 -0
  4. package/dist/extensions/dataset-uploader/cleaner.d.ts +59 -0
  5. package/dist/extensions/dataset-uploader/cleaner.d.ts.map +1 -0
  6. package/dist/extensions/dataset-uploader/cleaner.js +118 -0
  7. package/dist/extensions/dataset-uploader/exporter.d.ts +147 -0
  8. package/dist/extensions/dataset-uploader/exporter.d.ts.map +1 -0
  9. package/dist/extensions/dataset-uploader/exporter.js +313 -0
  10. package/dist/extensions/dataset-uploader/extra-values.d.ts +17 -0
  11. package/dist/extensions/dataset-uploader/extra-values.d.ts.map +1 -0
  12. package/dist/extensions/dataset-uploader/extra-values.js +51 -0
  13. package/dist/extensions/dataset-uploader/index.d.ts +22 -0
  14. package/dist/extensions/dataset-uploader/index.d.ts.map +1 -0
  15. package/dist/extensions/dataset-uploader/index.js +247 -0
  16. package/dist/extensions/dataset-uploader/scrubber.d.ts +34 -0
  17. package/dist/extensions/dataset-uploader/scrubber.d.ts.map +1 -0
  18. package/dist/extensions/dataset-uploader/scrubber.js +91 -0
  19. package/dist/extensions/dataset-uploader/upload.d.ts +105 -0
  20. package/dist/extensions/dataset-uploader/upload.d.ts.map +1 -0
  21. package/dist/extensions/dataset-uploader/upload.js +274 -0
  22. package/dist/generated/zeus/const.d.ts.map +1 -1
  23. package/dist/generated/zeus/const.js +55 -0
  24. package/dist/generated/zeus/index.d.ts +264 -0
  25. package/dist/generated/zeus/index.d.ts.map +1 -1
  26. package/dist/mcp/commands.d.ts.map +1 -1
  27. package/dist/mcp/commands.js +3 -0
  28. package/dist/mcp/direct-tools.d.ts.map +1 -1
  29. package/dist/mcp/direct-tools.js +4 -3
  30. package/dist/mcp/index.d.ts.map +1 -1
  31. package/dist/mcp/index.js +5 -1
  32. package/dist/mcp/mcp-auth-flow.d.ts +20 -2
  33. package/dist/mcp/mcp-auth-flow.d.ts.map +1 -1
  34. package/dist/mcp/mcp-auth-flow.js +87 -7
  35. package/dist/mcp/mcp-auth-start.d.ts +63 -0
  36. package/dist/mcp/mcp-auth-start.d.ts.map +1 -0
  37. package/dist/mcp/mcp-auth-start.js +131 -0
  38. package/dist/mcp/mcp-oauth-provider.d.ts +24 -0
  39. package/dist/mcp/mcp-oauth-provider.d.ts.map +1 -1
  40. package/dist/mcp/mcp-oauth-provider.js +20 -0
  41. package/dist/mcp/proxy-modes.d.ts +7 -0
  42. package/dist/mcp/proxy-modes.d.ts.map +1 -1
  43. package/dist/mcp/proxy-modes.js +71 -11
  44. package/dist/mcp/server-manager.d.ts +2 -1
  45. package/dist/mcp/server-manager.d.ts.map +1 -1
  46. package/dist/mcp/server-manager.js +9 -4
  47. package/dist/mcp/types.d.ts +9 -0
  48. package/dist/mcp/types.d.ts.map +1 -1
  49. package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
  50. package/dist/sdk/coding-agent/core/extensions/native-extensions.js +10 -0
  51. package/dist/server/handlers/mcp-auth.d.ts +7 -4
  52. package/dist/server/handlers/mcp-auth.d.ts.map +1 -1
  53. package/dist/server/handlers/mcp-auth.js +15 -80
  54. package/package.json +1 -1
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Chunked dataset upload to the team dataset vault (Dataset-uploader.md §3.2 —
3
+ * the `dataset_upload` tool's core).
4
+ *
5
+ * Loads a (cleaned) sharegpt JSONL, validates each line, groups it into
6
+ * batches of `batchSize` and calls the backend MCP tool
7
+ * `upload_dataset_samples` (JSON-RPC tools/call, Bearer auth) per batch,
8
+ * then sums the per-batch `DatasetUploadResult` ({accepted, duplicates,
9
+ * rejected}).
10
+ *
11
+ * Auth/URL resolution mirrors `aexol-mcp.ts`: `~/.spectral/config.json`
12
+ * (`readConfig()`), token = `teamApiKey || userJwt`, URL via `getApiUrl()`,
13
+ * and a single `ensureAuthenticated()` OAuth retry on 401/403.
14
+ *
15
+ * projectId: the Studio binding (`.aexol/aexol.jsonc`) supplies a single
16
+ * batch-level projectId for ALL batches — the exporter does not persist
17
+ * per-sample project provenance in the JSONL, so per-project grouping is not
18
+ * possible without exporter rework (documented in the result notes).
19
+ *
20
+ * Testability: the backend call lives behind `callBackendTool`, overridable
21
+ * per-call (`options.callBackendTool`) or via `setCallBackendToolForTesting`
22
+ * (module-level), so tests can stub the network entirely.
23
+ */
24
+ import { existsSync, readFileSync } from "node:fs";
25
+ import { getApiUrl, readConfig } from "../../config.js";
26
+ import { AexolMcpClient, AexolMcpError } from "../../mcp-client.js";
27
+ import { ensureAuthenticated } from "../../auth-helper.js";
28
+ import { readStudioBinding } from "../../studio-binding.js";
29
+ import { renderContentToString } from "../aexol-backend-client.js";
30
+ import { loadJsonl } from "./analyzer.js";
31
+ import { scrub } from "./scrubber.js";
32
+ export const DEFAULT_BATCH_SIZE = 50;
33
+ /** Backend MCP tool that ingests one batch of samples. */
34
+ export const BACKEND_UPLOAD_TOOL = "upload_dataset_samples";
35
+ /** Backend MCP tool returning vault stats (best-effort, post-upload). */
36
+ export const BACKEND_VAULT_STATS_TOOL = "dataset_vault_stats";
37
+ export const DATASET_FORMAT = "sharegpt";
38
+ // ---------------------------------------------------------------------------
39
+ // Default backend call (real network path)
40
+ // ---------------------------------------------------------------------------
41
+ let testOverride = null;
42
+ /**
43
+ * Test hook: override the backend call for the whole module. Pass null to
44
+ * restore the real implementation. Prefer `options.callBackendTool` for
45
+ * per-call injection; this exists so tests that drive code through other
46
+ * entry points (e.g. the extension index) can stub the network too.
47
+ */
48
+ export function setCallBackendToolForTesting(fn) {
49
+ testOverride = fn;
50
+ }
51
+ function resolveCallBackendTool(options) {
52
+ return options.callBackendTool ?? testOverride ?? defaultCallBackendTool;
53
+ }
54
+ function firstJsonContent(content) {
55
+ if (content.length === 0)
56
+ return null;
57
+ const first = content[0];
58
+ if (first.json !== undefined)
59
+ return first.json;
60
+ if (typeof first.text === "string") {
61
+ try {
62
+ return JSON.parse(first.text);
63
+ }
64
+ catch {
65
+ return first.text;
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+ /**
71
+ * Real backend call: config → token → AexolMcpClient.tools/call, with a
72
+ * single OAuth re-auth retry on 401/403 (same flow as aexol-mcp.ts).
73
+ */
74
+ async function defaultCallBackendTool(tool, args) {
75
+ const cfg = await readConfig();
76
+ if (!cfg) {
77
+ throw new Error("No Spectral config found — run `spectral login` before uploading. No data was sent.");
78
+ }
79
+ const apiUrl = getApiUrl(cfg.apiUrl);
80
+ let token = cfg.teamApiKey || cfg.userJwt || "";
81
+ if (!token) {
82
+ throw new Error("No auth token in Spectral config — run `spectral login` before uploading. No data was sent.");
83
+ }
84
+ let client = new AexolMcpClient(apiUrl, token);
85
+ try {
86
+ const res = await client.callTool(tool, args);
87
+ if (res.isError === true) {
88
+ throw new Error(`Backend tool "${tool}" reported an error: ${renderContentToString(res.content)}`);
89
+ }
90
+ return firstJsonContent(res.content);
91
+ }
92
+ catch (err) {
93
+ if (err instanceof AexolMcpError && (err.status === 401 || err.status === 403)) {
94
+ const auth = await ensureAuthenticated();
95
+ if (auth.ok && auth.config) {
96
+ const newToken = auth.config.teamApiKey || auth.config.userJwt || "";
97
+ if (newToken && newToken !== token) {
98
+ client = new AexolMcpClient(apiUrl, newToken);
99
+ const res = await client.callTool(tool, args);
100
+ if (res.isError === true) {
101
+ throw new Error(`Backend tool "${tool}" reported an error after re-auth: ${renderContentToString(res.content)}`);
102
+ }
103
+ return firstJsonContent(res.content);
104
+ }
105
+ }
106
+ throw new Error(`Authentication failed (HTTP ${err.status}) and re-authentication did not produce a working token. ` +
107
+ `Run \`spectral login\`. Original error: ${err.message}`);
108
+ }
109
+ throw err;
110
+ }
111
+ }
112
+ // ---------------------------------------------------------------------------
113
+ // Upload
114
+ // ---------------------------------------------------------------------------
115
+ function toInt(value, fallback) {
116
+ return typeof value === "number" && Number.isFinite(value) ? Math.trunc(value) : fallback;
117
+ }
118
+ /**
119
+ * Upload a dataset JSONL to the team vault in chunks.
120
+ *
121
+ * dryRun: parse + validate only — no backend call, `wouldUpload` carries the
122
+ * count, totals stay zero.
123
+ */
124
+ export async function uploadDataset(options) {
125
+ if (!existsSync(options.inputPath)) {
126
+ throw new Error(`Dataset file not found: ${options.inputPath}`);
127
+ }
128
+ const batchSize = typeof options.batchSize === "number" && options.batchSize > 0
129
+ ? Math.trunc(options.batchSize)
130
+ : DEFAULT_BATCH_SIZE;
131
+ const { samples, malformedLines } = loadJsonl(options.inputPath);
132
+ const notes = [];
133
+ // ---- team / project resolution (single projectId for all batches) ------
134
+ const cwd = options.cwd ?? process.cwd();
135
+ let binding = null;
136
+ try {
137
+ binding = await readStudioBinding(cwd);
138
+ }
139
+ catch {
140
+ binding = null;
141
+ }
142
+ const teamId = options.teamId ?? binding?.teamId ?? null;
143
+ const projectId = binding?.projectId ?? null;
144
+ if (projectId) {
145
+ notes.push(`All batches use Studio binding projectId ${projectId} (batch-level provenance).`);
146
+ }
147
+ else {
148
+ notes.push("No Studio binding found — batches are sent without projectId (sessions without a bound project).");
149
+ }
150
+ notes.push("Per-project batching is not supported by the exporter JSONL (no per-sample project provenance); " +
151
+ "a single projectId from the binding is used for all batches.");
152
+ const validSamples = samples.map((s) => JSON.stringify(s));
153
+ const invalidLines = malformedLines;
154
+ // ---- pre-upload residue warning (pattern-only dry-run scrub) ------------
155
+ // Runs in BOTH dry-run and real uploads, before the first network batch.
156
+ // Pattern rules only (no extraValues) — counts only, never values.
157
+ let residueWarning = null;
158
+ try {
159
+ const rawContent = readFileSync(options.inputPath, "utf8");
160
+ const scrubResult = scrub(rawContent);
161
+ const totalRedactions = scrubResult.redactions.reduce((sum, r) => sum + r.count, 0);
162
+ if (totalRedactions > 0) {
163
+ residueWarning = {
164
+ totalRedactions,
165
+ kinds: scrubResult.redactions.map((r) => r.kind),
166
+ };
167
+ }
168
+ }
169
+ catch {
170
+ // Non-fatal — the warning is advisory.
171
+ }
172
+ const chunkCount = Math.ceil(validSamples.length / batchSize);
173
+ const batches = [];
174
+ const totals = { accepted: 0, duplicates: 0, rejected: 0 };
175
+ let vaultStats;
176
+ if (options.dryRun === true) {
177
+ for (let i = 0; i < chunkCount; i++) {
178
+ const chunk = validSamples.slice(i * batchSize, (i + 1) * batchSize);
179
+ batches.push({ projectId, sent: chunk.length, accepted: 0, duplicates: 0, rejected: 0 });
180
+ }
181
+ }
182
+ else {
183
+ const call = resolveCallBackendTool(options);
184
+ for (let i = 0; i < chunkCount; i++) {
185
+ const chunk = validSamples.slice(i * batchSize, (i + 1) * batchSize);
186
+ const args = {
187
+ format: DATASET_FORMAT,
188
+ samples: chunk,
189
+ };
190
+ if (teamId)
191
+ args.teamId = teamId;
192
+ if (projectId)
193
+ args.projectId = projectId;
194
+ const payload = await call(BACKEND_UPLOAD_TOOL, args);
195
+ const result = (payload && typeof payload === "object" ? payload : {});
196
+ const accepted = toInt(result.accepted, 0);
197
+ const duplicates = toInt(result.duplicates, 0);
198
+ const rejected = toInt(result.rejected, 0);
199
+ batches.push({ projectId, sent: chunk.length, accepted, duplicates, rejected });
200
+ totals.accepted += accepted;
201
+ totals.duplicates += duplicates;
202
+ totals.rejected += rejected;
203
+ }
204
+ // Best-effort vault stats — a failure here never fails the upload.
205
+ try {
206
+ const statsArgs = {};
207
+ if (teamId)
208
+ statsArgs.teamId = teamId;
209
+ vaultStats = await call(BACKEND_VAULT_STATS_TOOL, statsArgs);
210
+ }
211
+ catch {
212
+ vaultStats = undefined;
213
+ notes.push("Vault stats could not be fetched (non-fatal).");
214
+ }
215
+ }
216
+ return {
217
+ inputPath: options.inputPath,
218
+ dryRun: options.dryRun === true,
219
+ totalLines: validSamples.length + invalidLines,
220
+ validSamples: validSamples.length,
221
+ invalidLines,
222
+ batchSize,
223
+ teamId,
224
+ projectId,
225
+ batches,
226
+ totals,
227
+ wouldUpload: validSamples.length,
228
+ ...(vaultStats !== undefined ? { vaultStats } : {}),
229
+ ...(residueWarning !== null ? { residueWarning } : {}),
230
+ notes,
231
+ };
232
+ }
233
+ // ---------------------------------------------------------------------------
234
+ // Markdown rendering
235
+ // ---------------------------------------------------------------------------
236
+ /** Render an upload result as a markdown summary for the agent. */
237
+ export function renderUploadMarkdown(result) {
238
+ const lines = [];
239
+ lines.push(`# Dataset upload ${result.dryRun ? "(dry run)" : ""} — ${result.inputPath}`);
240
+ if (result.residueWarning) {
241
+ lines.push("");
242
+ lines.push(`⚠️ residue detected (${result.residueWarning.totalRedactions} redactions across kinds: ${result.residueWarning.kinds.join(", ")}) — run dataset_clean before uploading`);
243
+ }
244
+ lines.push("");
245
+ lines.push(`- Samples: **${result.validSamples}** valid` +
246
+ (result.invalidLines > 0 ? ` (${result.invalidLines} invalid line(s) skipped)` : "") +
247
+ `, batch size ${result.batchSize}`);
248
+ lines.push(`- Scope: team ${result.teamId ?? "(resolved by backend)"} / project ${result.projectId ?? "none"}`);
249
+ if (result.dryRun) {
250
+ lines.push(`- Dry run: **${result.wouldUpload} sample(s) would be uploaded in ${result.batches.length} batch(es)** — no data was sent.`);
251
+ }
252
+ else {
253
+ lines.push(`- Totals: accepted **${result.totals.accepted}**, duplicates **${result.totals.duplicates}**, rejected **${result.totals.rejected}**`);
254
+ lines.push("");
255
+ lines.push("| Batch | Sent | Accepted | Duplicates | Rejected |");
256
+ lines.push("| ---: | ---: | ---: | ---: | ---: |");
257
+ result.batches.forEach((b, i) => {
258
+ lines.push(`| ${i + 1} | ${b.sent} | ${b.accepted} | ${b.duplicates} | ${b.rejected} |`);
259
+ });
260
+ }
261
+ if (result.vaultStats !== undefined) {
262
+ lines.push("");
263
+ lines.push("## Vault stats");
264
+ lines.push("");
265
+ lines.push("```json");
266
+ lines.push(JSON.stringify(result.vaultStats, null, 2));
267
+ lines.push("```");
268
+ }
269
+ lines.push("");
270
+ lines.push("## Notes");
271
+ for (const note of result.notes)
272
+ lines.push(`- ${note}`);
273
+ return lines.join("\n");
274
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../../../src/generated/zeus/const.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAC,GAAG,CAy6B5C,CAAA;AAED,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAC,GAAG,CA+1C1C,CAAA;AAED,eAAO,MAAM,GAAG;;;;CAIf,CAAA"}
1
+ {"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../../../src/generated/zeus/const.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAC,GAAG,CA47B5C,CAAA;AAED,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAC,GAAG,CA24C1C,CAAA;AAED,eAAO,MAAM,GAAG;;;;CAIf,CAAA"}
@@ -178,6 +178,10 @@ export const AllTypesProps = {
178
178
  adminCreditPackCount: {},
179
179
  adminTeamPayments: {},
180
180
  adminTeamPaymentCount: {},
181
+ adminUsageByModel: {
182
+ from: "DateTime",
183
+ to: "DateTime"
184
+ },
181
185
  adminCreditGrants: {},
182
186
  adminLeads: {
183
187
  status: "LeadStatus",
@@ -207,6 +211,8 @@ export const AllTypesProps = {
207
211
  syntheticDataset: {},
208
212
  fineTuneJobs: {},
209
213
  fineTuneJob: {},
214
+ datasetSamples: {},
215
+ datasetVaultStats: {},
210
216
  dedicatedUsage: {},
211
217
  seoSearchAnalytics: {
212
218
  input: "SeoSearchAnalyticsInput"
@@ -480,6 +486,10 @@ export const AllTypesProps = {
480
486
  cancelFineTuneJob: {},
481
487
  deleteFineTuneJob: {},
482
488
  deleteSyntheticDataset: {},
489
+ uploadDatasetSamples: {
490
+ input: "UploadDatasetSamplesInput"
491
+ },
492
+ deleteDatasetSample: {},
483
493
  deployDedicated: {
484
494
  input: "DeployDedicatedInput"
485
495
  },
@@ -538,6 +548,7 @@ export const AllTypesProps = {
538
548
  GenerateSyntheticDatasetInput: {},
539
549
  ImportSyntheticDatasetInput: {},
540
550
  StartFineTuneJobInput: {},
551
+ UploadDatasetSamplesInput: {},
541
552
  AdminCreateBillingPlanInput: {},
542
553
  AdminUpdateBillingPlanInput: {},
543
554
  AdminCreateCreditPackInput: {},
@@ -1096,6 +1107,7 @@ export const ReturnTypes = {
1096
1107
  adminCreditPackCount: "Int",
1097
1108
  adminTeamPayments: "AdminTeamPayment",
1098
1109
  adminTeamPaymentCount: "Int",
1110
+ adminUsageByModel: "AdminModelUsagePage",
1099
1111
  adminCreditGrants: "CreditGrant",
1100
1112
  adminLeads: "Lead",
1101
1113
  adminLeadCount: "Int",
@@ -1108,6 +1120,8 @@ export const ReturnTypes = {
1108
1120
  syntheticDataset: "SyntheticDataset",
1109
1121
  fineTuneJobs: "FineTuneJob",
1110
1122
  fineTuneJob: "FineTuneJob",
1123
+ datasetSamples: "DatasetSample",
1124
+ datasetVaultStats: "DatasetVaultStats",
1111
1125
  dedicatedHardwareOptions: "DedicatedHardware",
1112
1126
  dedicatedUsage: "DedicatedUsageSummary",
1113
1127
  latestSpectralRelease: "SpectralRelease",
@@ -1271,6 +1285,8 @@ export const ReturnTypes = {
1271
1285
  cancelFineTuneJob: "Boolean",
1272
1286
  deleteFineTuneJob: "Boolean",
1273
1287
  deleteSyntheticDataset: "Boolean",
1288
+ uploadDatasetSamples: "DatasetUploadResult",
1289
+ deleteDatasetSample: "Boolean",
1274
1290
  deployDedicated: "UserModel",
1275
1291
  updateDedicated: "UserModel",
1276
1292
  destroyDedicated: "UserModel",
@@ -1470,6 +1486,27 @@ export const ReturnTypes = {
1470
1486
  message: "String",
1471
1487
  outputModelName: "String"
1472
1488
  },
1489
+ DatasetSample: {
1490
+ id: "ID",
1491
+ teamId: "ID",
1492
+ projectId: "ID",
1493
+ contentHash: "String",
1494
+ format: "String",
1495
+ sampleCount: "Int",
1496
+ sourceSessionId: "String",
1497
+ createdAt: "DateTime"
1498
+ },
1499
+ DatasetUploadResult: {
1500
+ accepted: "Int",
1501
+ duplicates: "Int",
1502
+ rejected: "Int",
1503
+ sampleIds: "ID"
1504
+ },
1505
+ DatasetVaultStats: {
1506
+ count: "Int",
1507
+ totalBytes: "Int",
1508
+ topTools: "String"
1509
+ },
1473
1510
  DedicatedHardware: {
1474
1511
  id: "String",
1475
1512
  name: "String",
@@ -1563,6 +1600,24 @@ export const ReturnTypes = {
1563
1600
  completedAt: "DateTime",
1564
1601
  expiresAt: "DateTime"
1565
1602
  },
1603
+ AdminModelUsageEntry: {
1604
+ date: "String",
1605
+ model: "String",
1606
+ inputTokens: "Float",
1607
+ outputTokens: "Float",
1608
+ cacheReadTokens: "Float",
1609
+ cacheWriteTokens: "Float",
1610
+ totalTokens: "Float",
1611
+ creditsUsed: "Float",
1612
+ costUsd: "Float",
1613
+ eventCount: "Int",
1614
+ teamCount: "Int"
1615
+ },
1616
+ AdminModelUsagePage: {
1617
+ rows: "AdminModelUsageEntry",
1618
+ page: "Int",
1619
+ pageSize: "Int"
1620
+ },
1566
1621
  CreditGrant: {
1567
1622
  id: "ID",
1568
1623
  teamId: "ID",