@madewithremy/admin 0.1.7 → 0.1.9
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/index.d.ts +1327 -14
- package/dist/index.js +774 -180
- package/dist/index.js.map +1 -1
- package/dist/prod.js +2081 -288
- package/package.json +1 -1
package/dist/prod.js
CHANGED
|
@@ -36,10 +36,10 @@ function fatal(message) {
|
|
|
36
36
|
throw new CliError(message);
|
|
37
37
|
}
|
|
38
38
|
var AdminApiError = class extends Error {
|
|
39
|
-
constructor(method,
|
|
40
|
-
super(`API ${method} ${
|
|
39
|
+
constructor(method, path7, status2, body) {
|
|
40
|
+
super(`API ${method} ${path7} returned ${status2}: ${JSON.stringify(body)}`);
|
|
41
41
|
this.method = method;
|
|
42
|
-
this.path =
|
|
42
|
+
this.path = path7;
|
|
43
43
|
this.status = status2;
|
|
44
44
|
this.body = body;
|
|
45
45
|
this.name = "AdminApiError";
|
|
@@ -935,7 +935,7 @@ async function waitForCommit(ctx, params) {
|
|
|
935
935
|
const { commitSha, onProgress } = params;
|
|
936
936
|
const timeoutMs = params.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
937
937
|
const shortSha = commitSha.slice(0, 8);
|
|
938
|
-
const
|
|
938
|
+
const start2 = Date.now();
|
|
939
939
|
let release;
|
|
940
940
|
while (true) {
|
|
941
941
|
const r = await byCommit(ctx, commitSha);
|
|
@@ -948,7 +948,7 @@ async function waitForCommit(ctx, params) {
|
|
|
948
948
|
`Failed to resolve release for ${shortSha}: HTTP ${r.status} ${JSON.stringify(r.body)}`
|
|
949
949
|
);
|
|
950
950
|
}
|
|
951
|
-
if (Date.now() -
|
|
951
|
+
if (Date.now() - start2 > RESOLVE_GRACE_MS) {
|
|
952
952
|
return {
|
|
953
953
|
outcome: "not_found",
|
|
954
954
|
error: `No release created for commit ${commitSha} within ${RESOLVE_GRACE_MS / 1e3}s \u2014 either the push hasn't registered yet or this SHA never produced a build`
|
|
@@ -976,7 +976,7 @@ async function waitForCommit(ctx, params) {
|
|
|
976
976
|
summary: summarizeRelease(release)
|
|
977
977
|
};
|
|
978
978
|
}
|
|
979
|
-
if (Date.now() -
|
|
979
|
+
if (Date.now() - start2 > timeoutMs) {
|
|
980
980
|
return {
|
|
981
981
|
outcome: "timeout",
|
|
982
982
|
release,
|
|
@@ -985,7 +985,7 @@ async function waitForCommit(ctx, params) {
|
|
|
985
985
|
};
|
|
986
986
|
}
|
|
987
987
|
onProgress?.(
|
|
988
|
-
`${release.status}\u2026 (${Math.round((Date.now() -
|
|
988
|
+
`${release.status}\u2026 (${Math.round((Date.now() - start2) / 1e3)}s)`
|
|
989
989
|
);
|
|
990
990
|
await sleep(POLL_MS);
|
|
991
991
|
const r = await byCommit(ctx, commitSha);
|
|
@@ -1247,14 +1247,14 @@ async function diagnosticsGet(ctx, a) {
|
|
|
1247
1247
|
const releaseId = await resolveReleaseId(ctx, a);
|
|
1248
1248
|
if (a.bool("wait")) {
|
|
1249
1249
|
const timeout = (a.num("timeout") ?? 120) * 1e3;
|
|
1250
|
-
const
|
|
1250
|
+
const start2 = Date.now();
|
|
1251
1251
|
while (true) {
|
|
1252
1252
|
const release2 = await getRelease(ctx, releaseId);
|
|
1253
1253
|
if (release2.diagnostics) {
|
|
1254
1254
|
out(release2.diagnostics);
|
|
1255
1255
|
return;
|
|
1256
1256
|
}
|
|
1257
|
-
if (Date.now() -
|
|
1257
|
+
if (Date.now() - start2 > timeout) {
|
|
1258
1258
|
out(DIAGNOSTICS_PENDING);
|
|
1259
1259
|
return;
|
|
1260
1260
|
}
|
|
@@ -2869,11 +2869,55 @@ Notes:
|
|
|
2869
2869
|
- Public image URLs accept transform params, e.g. ?w=400&fit=cover.`;
|
|
2870
2870
|
|
|
2871
2871
|
// src/commands/dataSources.ts
|
|
2872
|
-
import
|
|
2873
|
-
import
|
|
2872
|
+
import fs5 from "fs";
|
|
2873
|
+
import path5 from "path";
|
|
2874
2874
|
|
|
2875
2875
|
// src/ops/dataSources.ts
|
|
2876
2876
|
import { createHash as createHash2 } from "crypto";
|
|
2877
|
+
|
|
2878
|
+
// src/poll.ts
|
|
2879
|
+
async function pollUntil(read, isSettled, options) {
|
|
2880
|
+
const start2 = Date.now();
|
|
2881
|
+
let transientSince = null;
|
|
2882
|
+
while (true) {
|
|
2883
|
+
let value;
|
|
2884
|
+
try {
|
|
2885
|
+
value = await read();
|
|
2886
|
+
transientSince = null;
|
|
2887
|
+
} catch (err) {
|
|
2888
|
+
if (!isTransientError(err)) {
|
|
2889
|
+
throw err;
|
|
2890
|
+
}
|
|
2891
|
+
transientSince ??= Date.now();
|
|
2892
|
+
if (Date.now() - transientSince > TRANSIENT_GRACE_MS) {
|
|
2893
|
+
throw err;
|
|
2894
|
+
}
|
|
2895
|
+
options.onProgress?.(
|
|
2896
|
+
`retrying after a gateway error\u2026 (${elapsedSeconds(start2)}s)`
|
|
2897
|
+
);
|
|
2898
|
+
await sleep(options.pollMs);
|
|
2899
|
+
continue;
|
|
2900
|
+
}
|
|
2901
|
+
if (isSettled(value)) {
|
|
2902
|
+
return { settled: true, value };
|
|
2903
|
+
}
|
|
2904
|
+
if (Date.now() - start2 > options.timeoutMs) {
|
|
2905
|
+
return { settled: false, value };
|
|
2906
|
+
}
|
|
2907
|
+
if (options.describe) {
|
|
2908
|
+
options.onProgress?.(options.describe(value, start2));
|
|
2909
|
+
}
|
|
2910
|
+
await sleep(options.pollMs);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
function elapsedSeconds(start2) {
|
|
2914
|
+
return Math.round((Date.now() - start2) / 1e3);
|
|
2915
|
+
}
|
|
2916
|
+
function timedOut(timeoutMs, still) {
|
|
2917
|
+
return `Timed out after ${Math.round(timeoutMs / 1e3)}s ${still}`;
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
// src/ops/dataSources.ts
|
|
2877
2921
|
var DEFAULT_WAIT_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
2878
2922
|
var POLL_MS2 = 3e3;
|
|
2879
2923
|
function base(appId) {
|
|
@@ -2903,7 +2947,12 @@ async function addDocument(ctx, params) {
|
|
|
2903
2947
|
);
|
|
2904
2948
|
if (token.alreadyCurrent) {
|
|
2905
2949
|
onProgress?.(`${filename}: unchanged, skipped`);
|
|
2906
|
-
return {
|
|
2950
|
+
return {
|
|
2951
|
+
filename,
|
|
2952
|
+
skipped: true,
|
|
2953
|
+
document: token.document,
|
|
2954
|
+
documents: token.documents ?? [token.document]
|
|
2955
|
+
};
|
|
2907
2956
|
}
|
|
2908
2957
|
onProgress?.(
|
|
2909
2958
|
`${filename}: uploading ${(content.length / 1024 / 1024).toFixed(1)}MB\u2026`
|
|
@@ -2926,7 +2975,9 @@ async function addDocument(ctx, params) {
|
|
|
2926
2975
|
filename,
|
|
2927
2976
|
skipped: false,
|
|
2928
2977
|
queued: confirmed.queued,
|
|
2929
|
-
document: confirmed.document
|
|
2978
|
+
document: confirmed.document,
|
|
2979
|
+
documents: confirmed.documents ?? (confirmed.document ? [confirmed.document] : []),
|
|
2980
|
+
outcome: confirmed.outcome
|
|
2930
2981
|
};
|
|
2931
2982
|
}
|
|
2932
2983
|
async function waitForIngest(ctx, params) {
|
|
@@ -2936,50 +2987,64 @@ async function waitForIngest(ctx, params) {
|
|
|
2936
2987
|
return { status: "up-to-date", documents: [] };
|
|
2937
2988
|
}
|
|
2938
2989
|
const wanted = new Set(documentIds);
|
|
2939
|
-
const
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
}
|
|
2950
|
-
|
|
2951
|
-
if (Date.now() - transientSince > TRANSIENT_GRACE_MS) {
|
|
2952
|
-
throw err;
|
|
2953
|
-
}
|
|
2954
|
-
onProgress?.(
|
|
2955
|
-
`retrying after a gateway error\u2026 (${Math.round((Date.now() - start) / 1e3)}s)`
|
|
2956
|
-
);
|
|
2957
|
-
await sleep(POLL_MS2);
|
|
2958
|
-
continue;
|
|
2959
|
-
}
|
|
2960
|
-
const tracked = (docs ?? []).filter((d) => wanted.has(d.id));
|
|
2961
|
-
const pending = tracked.filter((d) => d.status === "processing");
|
|
2962
|
-
const failed = tracked.filter((d) => d.status === "error");
|
|
2963
|
-
if (pending.length === 0) {
|
|
2964
|
-
return {
|
|
2965
|
-
status: failed.length ? "error" : "done",
|
|
2966
|
-
documents: tracked.map(summarize)
|
|
2967
|
-
};
|
|
2990
|
+
const pendingOf = (docs) => docs.filter((d) => d.status === "processing");
|
|
2991
|
+
const outcome = await pollUntil(
|
|
2992
|
+
async () => {
|
|
2993
|
+
const docs = await allDocuments(ctx, { slug, ids: documentIds });
|
|
2994
|
+
return (docs ?? []).filter((d) => wanted.has(d.id));
|
|
2995
|
+
},
|
|
2996
|
+
(tracked2) => pendingOf(tracked2).length === 0,
|
|
2997
|
+
{
|
|
2998
|
+
timeoutMs,
|
|
2999
|
+
pollMs: POLL_MS2,
|
|
3000
|
+
describe: (tracked2, start2) => `ingesting\u2026 ${tracked2.length - pendingOf(tracked2).length}/${tracked2.length} done (${elapsedSeconds(start2)}s)`,
|
|
3001
|
+
onProgress
|
|
2968
3002
|
}
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
3003
|
+
);
|
|
3004
|
+
const tracked = outcome.value;
|
|
3005
|
+
if (!outcome.settled) {
|
|
3006
|
+
return {
|
|
3007
|
+
status: "timeout",
|
|
3008
|
+
documents: tracked.map(summarize),
|
|
3009
|
+
error: timedOut(
|
|
3010
|
+
timeoutMs,
|
|
3011
|
+
`with ${pendingOf(tracked).length} document(s) still processing`
|
|
3012
|
+
)
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
return {
|
|
3016
|
+
status: tracked.some((d) => d.status === "error") ? "error" : "done",
|
|
3017
|
+
documents: tracked.map(summarize)
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
async function waitForCandidate(ctx, params) {
|
|
3021
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
3022
|
+
const pendingOf = (docs2) => docs2.filter((d) => d.status === "processing");
|
|
3023
|
+
const outcome = await pollUntil(
|
|
3024
|
+
() => allDocuments(ctx, { slug: params.slug, candidate: true }),
|
|
3025
|
+
(docs2) => pendingOf(docs2).length === 0,
|
|
3026
|
+
{
|
|
3027
|
+
timeoutMs,
|
|
3028
|
+
pollMs: POLL_MS2,
|
|
3029
|
+
describe: (docs2, start2) => `rebuilding\u2026 ${docs2.length - pendingOf(docs2).length}/${docs2.length} done (${elapsedSeconds(start2)}s)`,
|
|
3030
|
+
onProgress: params.onProgress
|
|
2975
3031
|
}
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3032
|
+
);
|
|
3033
|
+
const docs = outcome.value;
|
|
3034
|
+
if (!outcome.settled) {
|
|
3035
|
+
return {
|
|
3036
|
+
status: "timeout",
|
|
3037
|
+
documents: docs.map(summarize),
|
|
3038
|
+
error: timedOut(
|
|
3039
|
+
timeoutMs,
|
|
3040
|
+
`with ${pendingOf(docs).length} document(s) still building`
|
|
3041
|
+
)
|
|
3042
|
+
};
|
|
2982
3043
|
}
|
|
3044
|
+
return {
|
|
3045
|
+
status: docs.some((d) => d.status === "error") ? "error" : "ready",
|
|
3046
|
+
documents: docs.map(summarize)
|
|
3047
|
+
};
|
|
2983
3048
|
}
|
|
2984
3049
|
var DOCUMENTS_IDS_MAX = 200;
|
|
2985
3050
|
function documents(ctx, params) {
|
|
@@ -3087,110 +3152,1801 @@ function move(ctx, params) {
|
|
|
3087
3152
|
}
|
|
3088
3153
|
async function waitForMove(ctx, params) {
|
|
3089
3154
|
const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
3090
|
-
const
|
|
3091
|
-
|
|
3092
|
-
for (; ; ) {
|
|
3093
|
-
let source;
|
|
3094
|
-
try {
|
|
3155
|
+
const outcome = await pollUntil(
|
|
3156
|
+
async () => {
|
|
3095
3157
|
const { dataSources } = await list8(ctx);
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
if (!isTransientError(err)) {
|
|
3100
|
-
throw err;
|
|
3101
|
-
}
|
|
3102
|
-
transientSince ??= Date.now();
|
|
3103
|
-
if (Date.now() - transientSince > TRANSIENT_GRACE_MS) {
|
|
3104
|
-
throw err;
|
|
3158
|
+
const source2 = dataSources.find((s) => s.slug === params.slug);
|
|
3159
|
+
if (!source2) {
|
|
3160
|
+
throw new Error(`Data source "${params.slug}" not found.`);
|
|
3105
3161
|
}
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3162
|
+
return source2;
|
|
3163
|
+
},
|
|
3164
|
+
(source2) => !source2.migration || source2.migration.error !== null,
|
|
3165
|
+
{
|
|
3166
|
+
timeoutMs,
|
|
3167
|
+
pollMs: POLL_MS2,
|
|
3168
|
+
describe: (source2, start2) => `moving\u2026 ${source2.migration.copied}/${source2.migration.total} documents (${elapsedSeconds(start2)}s)`,
|
|
3169
|
+
onProgress: params.onProgress
|
|
3111
3170
|
}
|
|
3112
|
-
|
|
3113
|
-
|
|
3171
|
+
);
|
|
3172
|
+
const source = outcome.value;
|
|
3173
|
+
const migration = source.migration;
|
|
3174
|
+
if (!outcome.settled) {
|
|
3175
|
+
return {
|
|
3176
|
+
status: "timeout",
|
|
3177
|
+
source,
|
|
3178
|
+
error: timedOut(
|
|
3179
|
+
timeoutMs,
|
|
3180
|
+
`with ${migration.copied}/${migration.total} documents copied`
|
|
3181
|
+
)
|
|
3182
|
+
};
|
|
3183
|
+
}
|
|
3184
|
+
if (migration?.error) {
|
|
3185
|
+
return { status: "failed", source, error: migration.error };
|
|
3186
|
+
}
|
|
3187
|
+
return { status: "done", source };
|
|
3188
|
+
}
|
|
3189
|
+
function revectorize(ctx, params) {
|
|
3190
|
+
const { slug, ingest } = params;
|
|
3191
|
+
return call(
|
|
3192
|
+
ctx,
|
|
3193
|
+
"POST",
|
|
3194
|
+
`${base(ctx.appId)}/revectorize`,
|
|
3195
|
+
{
|
|
3196
|
+
slug,
|
|
3197
|
+
...ingest ? { ingest } : {}
|
|
3114
3198
|
}
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3199
|
+
);
|
|
3200
|
+
}
|
|
3201
|
+
function promote(ctx, params) {
|
|
3202
|
+
return call(
|
|
3203
|
+
ctx,
|
|
3204
|
+
"POST",
|
|
3205
|
+
`${base(ctx.appId)}/promote`,
|
|
3206
|
+
{
|
|
3207
|
+
slug: params.slug,
|
|
3208
|
+
...params.force ? { force: true } : {}
|
|
3118
3209
|
}
|
|
3119
|
-
|
|
3120
|
-
|
|
3210
|
+
);
|
|
3211
|
+
}
|
|
3212
|
+
function drop(ctx, params) {
|
|
3213
|
+
return call(
|
|
3214
|
+
ctx,
|
|
3215
|
+
"POST",
|
|
3216
|
+
`${base(ctx.appId)}/versions/drop`,
|
|
3217
|
+
{
|
|
3218
|
+
slug: params.slug,
|
|
3219
|
+
...params.version !== void 0 ? { version: params.version } : {}
|
|
3121
3220
|
}
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3221
|
+
);
|
|
3222
|
+
}
|
|
3223
|
+
function rm(ctx, params) {
|
|
3224
|
+
return call(
|
|
3225
|
+
ctx,
|
|
3226
|
+
"POST",
|
|
3227
|
+
`${base(ctx.appId)}/documents/delete`,
|
|
3228
|
+
{
|
|
3229
|
+
slug: params.slug,
|
|
3230
|
+
documentId: params.documentId
|
|
3231
|
+
}
|
|
3232
|
+
);
|
|
3233
|
+
}
|
|
3234
|
+
function rmWhere(ctx, params) {
|
|
3235
|
+
return call(
|
|
3236
|
+
ctx,
|
|
3237
|
+
"POST",
|
|
3238
|
+
`${base(ctx.appId)}/documents/delete-many`,
|
|
3239
|
+
{ slug: params.slug, filter: params.filter }
|
|
3240
|
+
);
|
|
3241
|
+
}
|
|
3242
|
+
async function rmWhereAll(ctx, params) {
|
|
3243
|
+
let deleted = 0;
|
|
3244
|
+
while (true) {
|
|
3245
|
+
const page = await rmWhere(ctx, params);
|
|
3246
|
+
deleted += page.deleted;
|
|
3247
|
+
if (page.remaining === 0 || page.deleted === 0) {
|
|
3248
|
+
return { deleted };
|
|
3128
3249
|
}
|
|
3129
3250
|
params.onProgress?.(
|
|
3130
|
-
`
|
|
3131
|
-
|
|
3132
|
-
|
|
3251
|
+
`removed ${deleted.toLocaleString()} so far, ${page.remaining.toLocaleString()} remaining\u2026`
|
|
3252
|
+
);
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
function deleteSource(ctx, slug) {
|
|
3256
|
+
return call(
|
|
3257
|
+
ctx,
|
|
3258
|
+
"POST",
|
|
3259
|
+
`${base(ctx.appId)}/delete`,
|
|
3260
|
+
{ slug }
|
|
3261
|
+
);
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
// src/ops/dataSourceJobs.ts
|
|
3265
|
+
var DEFAULT_WAIT_TIMEOUT_MS2 = 60 * 60 * 1e3;
|
|
3266
|
+
var POLL_MS3 = 5e3;
|
|
3267
|
+
function base2(appId) {
|
|
3268
|
+
return `/_internal/v2/apps/${appId}/datasources/jobs`;
|
|
3269
|
+
}
|
|
3270
|
+
function start(ctx, params) {
|
|
3271
|
+
const { slug, source, approve: approve2, budgetDollars, concurrency, limit } = params;
|
|
3272
|
+
return call(ctx, "POST", base2(ctx.appId), {
|
|
3273
|
+
slug,
|
|
3274
|
+
source,
|
|
3275
|
+
...approve2 ? { approve: true } : {},
|
|
3276
|
+
...budgetDollars !== void 0 ? { budgetDollars } : {},
|
|
3277
|
+
...concurrency !== void 0 ? { concurrency } : {},
|
|
3278
|
+
...limit !== void 0 ? { limit } : {}
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
3281
|
+
async function uploadManifest(ctx, params) {
|
|
3282
|
+
const token = await call(
|
|
3283
|
+
ctx,
|
|
3284
|
+
"POST",
|
|
3285
|
+
`${base2(ctx.appId)}/manifest-token`,
|
|
3286
|
+
{ slug: params.slug }
|
|
3287
|
+
);
|
|
3288
|
+
await uploadDirect(token.upload, params.content, "manifest.jsonl");
|
|
3289
|
+
return { key: token.key };
|
|
3290
|
+
}
|
|
3291
|
+
function list9(ctx, params) {
|
|
3292
|
+
return call(
|
|
3293
|
+
ctx,
|
|
3294
|
+
"GET",
|
|
3295
|
+
`${base2(ctx.appId)}${qs({ slug: params?.slug })}`
|
|
3296
|
+
);
|
|
3297
|
+
}
|
|
3298
|
+
function get7(ctx, params) {
|
|
3299
|
+
return call(
|
|
3300
|
+
ctx,
|
|
3301
|
+
"GET",
|
|
3302
|
+
`${base2(ctx.appId)}/${encodeURIComponent(params.id)}`
|
|
3303
|
+
);
|
|
3304
|
+
}
|
|
3305
|
+
function approve(ctx, params) {
|
|
3306
|
+
return call(
|
|
3307
|
+
ctx,
|
|
3308
|
+
"POST",
|
|
3309
|
+
`${base2(ctx.appId)}/${encodeURIComponent(params.id)}/approve`
|
|
3310
|
+
);
|
|
3311
|
+
}
|
|
3312
|
+
function pause(ctx, params) {
|
|
3313
|
+
return call(
|
|
3314
|
+
ctx,
|
|
3315
|
+
"POST",
|
|
3316
|
+
`${base2(ctx.appId)}/${encodeURIComponent(params.id)}/pause`
|
|
3317
|
+
);
|
|
3318
|
+
}
|
|
3319
|
+
function resume(ctx, params) {
|
|
3320
|
+
return call(
|
|
3321
|
+
ctx,
|
|
3322
|
+
"POST",
|
|
3323
|
+
`${base2(ctx.appId)}/${encodeURIComponent(params.id)}/resume`
|
|
3324
|
+
);
|
|
3325
|
+
}
|
|
3326
|
+
function cancel(ctx, params) {
|
|
3327
|
+
return call(
|
|
3328
|
+
ctx,
|
|
3329
|
+
"POST",
|
|
3330
|
+
`${base2(ctx.appId)}/${encodeURIComponent(params.id)}/cancel`
|
|
3331
|
+
);
|
|
3332
|
+
}
|
|
3333
|
+
function waitForPlan(ctx, params) {
|
|
3334
|
+
return settle(ctx, params, (job) => job.state !== "planning");
|
|
3335
|
+
}
|
|
3336
|
+
function waitForJob(ctx, params) {
|
|
3337
|
+
return settle(
|
|
3338
|
+
ctx,
|
|
3339
|
+
params,
|
|
3340
|
+
(job) => !["planning", "planned", "running"].includes(job.state) || job.state === "planned" && (job.error !== null || !job.autoApprove)
|
|
3341
|
+
);
|
|
3342
|
+
}
|
|
3343
|
+
async function settle(ctx, params, isSettled) {
|
|
3344
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS2;
|
|
3345
|
+
const outcome = await pollUntil(
|
|
3346
|
+
async () => (await get7(ctx, { id: params.id })).job,
|
|
3347
|
+
isSettled,
|
|
3348
|
+
{
|
|
3349
|
+
timeoutMs,
|
|
3350
|
+
pollMs: POLL_MS3,
|
|
3351
|
+
describe: describeProgress,
|
|
3352
|
+
onProgress: params.onProgress
|
|
3353
|
+
}
|
|
3354
|
+
);
|
|
3355
|
+
const job = outcome.value;
|
|
3356
|
+
if (!outcome.settled) {
|
|
3357
|
+
return {
|
|
3358
|
+
status: "timeout",
|
|
3359
|
+
job,
|
|
3360
|
+
error: timedOut(timeoutMs, `; job is still ${job.state}`)
|
|
3361
|
+
};
|
|
3362
|
+
}
|
|
3363
|
+
if (job.state === "failed" || job.state === "cancelled") {
|
|
3364
|
+
return {
|
|
3365
|
+
status: "failed",
|
|
3366
|
+
job,
|
|
3367
|
+
error: job.error ?? `The job was ${job.state}.`
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
if (job.state === "paused") {
|
|
3371
|
+
return {
|
|
3372
|
+
status: "failed",
|
|
3373
|
+
job,
|
|
3374
|
+
error: job.error ?? `The job paused (${job.pauseReason ?? "user"}). Resume it to continue.`
|
|
3375
|
+
};
|
|
3376
|
+
}
|
|
3377
|
+
if (job.state === "planned" && (job.error !== null || job.autoApprove)) {
|
|
3378
|
+
return {
|
|
3379
|
+
status: "planned",
|
|
3380
|
+
job,
|
|
3381
|
+
error: job.error ?? "The plan is ready but the job did not start. Approve it once the cause is fixed."
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
return { status: "done", job };
|
|
3385
|
+
}
|
|
3386
|
+
function describeProgress(job, start2) {
|
|
3387
|
+
const elapsed = `(${elapsedSeconds(start2)}s)`;
|
|
3388
|
+
if (job.state === "planning") {
|
|
3389
|
+
return job.enumerationDone ? `planning\u2026 reading a sample of ${job.counts.objectsSeen.toLocaleString()} objects ${elapsed}` : `planning\u2026 ${job.counts.objectsSeen.toLocaleString()} objects counted ${elapsed}`;
|
|
3390
|
+
}
|
|
3391
|
+
const total = job.plan?.projected.documents ?? job.counts.objectsSeen;
|
|
3392
|
+
const processed = job.counts.documentsDone + job.counts.documentsSkipped + job.counts.documentsFailed;
|
|
3393
|
+
const spend = `$${job.estimatedDollars.toFixed(2)}${job.budgetDollars !== null ? ` of $${job.budgetDollars.toFixed(2)}` : ""}`;
|
|
3394
|
+
return `${job.state}\u2026 ${processed.toLocaleString()}/${total.toLocaleString()} documents \xB7 ${spend} ${elapsed}`;
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
// src/commands/_shared/dataSources.ts
|
|
3398
|
+
var DEFAULT_SOURCE = "default";
|
|
3399
|
+
var sourceOf = (a) => a.str("source") || DEFAULT_SOURCE;
|
|
3400
|
+
var timeoutOf = (a) => {
|
|
3401
|
+
const seconds = a.str("timeout");
|
|
3402
|
+
return seconds ? { timeoutMs: Number(seconds) * 1e3 } : {};
|
|
3403
|
+
};
|
|
3404
|
+
function failOnWait(result) {
|
|
3405
|
+
if (result.status === "timeout") {
|
|
3406
|
+
throw new CliError(result.error ?? "Timed out.", EXIT.timeout);
|
|
3407
|
+
}
|
|
3408
|
+
if (result.status === "failed" || result.status === "error") {
|
|
3409
|
+
throw new CliError(result.error ?? "Failed.", EXIT.buildFailed);
|
|
3410
|
+
}
|
|
3411
|
+
if (result.status === "planned") {
|
|
3412
|
+
throw new CliError(
|
|
3413
|
+
`${result.error ?? "The plan is ready but the job did not start."} Fix the cause, then \`datasources jobs approve <id>\`.`,
|
|
3414
|
+
EXIT.buildFailed
|
|
3133
3415
|
);
|
|
3134
|
-
await sleep(POLL_MS2);
|
|
3135
3416
|
}
|
|
3136
3417
|
}
|
|
3137
|
-
function
|
|
3138
|
-
const
|
|
3418
|
+
async function waitAndReport(ctx, a, id, mode, summary2) {
|
|
3419
|
+
const wait = mode === "plan" ? waitForPlan : waitForJob;
|
|
3420
|
+
const result = await wait(ctx, { id, ...timeoutOf(a), onProgress: progress });
|
|
3421
|
+
out({ ...summary2, ...result });
|
|
3422
|
+
failOnWait(result);
|
|
3423
|
+
}
|
|
3424
|
+
|
|
3425
|
+
// src/ops/dataSourceConnectors.ts
|
|
3426
|
+
function base3(appId) {
|
|
3427
|
+
return `/_internal/v2/apps/${appId}/datasources`;
|
|
3428
|
+
}
|
|
3429
|
+
function connect(ctx, params) {
|
|
3430
|
+
const {
|
|
3431
|
+
slug,
|
|
3432
|
+
bucket,
|
|
3433
|
+
region,
|
|
3434
|
+
prefix,
|
|
3435
|
+
endpoint,
|
|
3436
|
+
accessKeyIdSecret,
|
|
3437
|
+
secretAccessKeySecret,
|
|
3438
|
+
deletions,
|
|
3439
|
+
budgetDollarsPerSync
|
|
3440
|
+
} = params;
|
|
3441
|
+
return call(
|
|
3442
|
+
ctx,
|
|
3443
|
+
"POST",
|
|
3444
|
+
`${base3(ctx.appId)}/connector`,
|
|
3445
|
+
{
|
|
3446
|
+
slug,
|
|
3447
|
+
bucket,
|
|
3448
|
+
region,
|
|
3449
|
+
...prefix !== void 0 ? { prefix } : {},
|
|
3450
|
+
...endpoint !== void 0 ? { endpoint } : {},
|
|
3451
|
+
accessKeyIdSecret,
|
|
3452
|
+
secretAccessKeySecret,
|
|
3453
|
+
...deletions !== void 0 ? { deletions } : {},
|
|
3454
|
+
...budgetDollarsPerSync !== void 0 ? { budgetDollarsPerSync } : {}
|
|
3455
|
+
}
|
|
3456
|
+
);
|
|
3457
|
+
}
|
|
3458
|
+
function get8(ctx, params) {
|
|
3459
|
+
return call(
|
|
3460
|
+
ctx,
|
|
3461
|
+
"GET",
|
|
3462
|
+
`${base3(ctx.appId)}/connector${qs({ slug: params.slug })}`
|
|
3463
|
+
);
|
|
3464
|
+
}
|
|
3465
|
+
function disconnect(ctx, params) {
|
|
3466
|
+
return call(
|
|
3467
|
+
ctx,
|
|
3468
|
+
"POST",
|
|
3469
|
+
`${base3(ctx.appId)}/connector/remove`,
|
|
3470
|
+
{ slug: params.slug }
|
|
3471
|
+
);
|
|
3472
|
+
}
|
|
3473
|
+
function sync(ctx, params) {
|
|
3474
|
+
return call(ctx, "POST", `${base3(ctx.appId)}/sync`, {
|
|
3475
|
+
slug: params.slug,
|
|
3476
|
+
...params.budgetDollars !== void 0 ? { budgetDollars: params.budgetDollars } : {}
|
|
3477
|
+
});
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
// src/commands/dataSourceConnectors.ts
|
|
3481
|
+
var dataSourceConnectorsSpecs = {
|
|
3482
|
+
"datasources connect": {
|
|
3483
|
+
usage: "Usage: remy-admin datasources connect --source <slug> --bucket <name> --region <aws-region> [--prefix <p>] [--endpoint <url>] --access-key-secret <NAME> --secret-key-secret <NAME> [--deletions mirror|keep] [--budget-per-sync <dollars>]",
|
|
3484
|
+
flags: {
|
|
3485
|
+
source: { type: "string" },
|
|
3486
|
+
bucket: { type: "string" },
|
|
3487
|
+
region: { type: "string" },
|
|
3488
|
+
prefix: { type: "string" },
|
|
3489
|
+
endpoint: { type: "string" },
|
|
3490
|
+
"access-key-secret": { type: "string" },
|
|
3491
|
+
"secret-key-secret": { type: "string" },
|
|
3492
|
+
deletions: { type: "string" },
|
|
3493
|
+
"budget-per-sync": { type: "number", min: 0.01 }
|
|
3494
|
+
}
|
|
3495
|
+
},
|
|
3496
|
+
"datasources connector": {
|
|
3497
|
+
usage: "Usage: remy-admin datasources connector [--source <slug>]",
|
|
3498
|
+
flags: { source: { type: "string" } }
|
|
3499
|
+
},
|
|
3500
|
+
"datasources disconnect": {
|
|
3501
|
+
usage: "Usage: remy-admin datasources disconnect --source <slug>",
|
|
3502
|
+
flags: { source: { type: "string" } }
|
|
3503
|
+
},
|
|
3504
|
+
"datasources sync": {
|
|
3505
|
+
usage: "Usage: remy-admin datasources sync [--source <slug>] [--budget <dollars>] [--wait] [--timeout <sec>]",
|
|
3506
|
+
flags: {
|
|
3507
|
+
source: { type: "string" },
|
|
3508
|
+
budget: { type: "number", min: 0.01 },
|
|
3509
|
+
wait: { type: "boolean" },
|
|
3510
|
+
timeout: { type: "string" }
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
};
|
|
3514
|
+
async function connect2(ctx, a) {
|
|
3515
|
+
const slug = a.str("source");
|
|
3516
|
+
if (!slug) {
|
|
3517
|
+
fatal("--source is required \u2014 a connector is bound to one named source.");
|
|
3518
|
+
}
|
|
3519
|
+
const bucket = a.str("bucket");
|
|
3520
|
+
const region = a.str("region");
|
|
3521
|
+
const accessKeyIdSecret = a.str("access-key-secret");
|
|
3522
|
+
const secretAccessKeySecret = a.str("secret-key-secret");
|
|
3523
|
+
for (const [flag, value] of [
|
|
3524
|
+
["bucket", bucket],
|
|
3525
|
+
["region", region],
|
|
3526
|
+
["access-key-secret", accessKeyIdSecret],
|
|
3527
|
+
["secret-key-secret", secretAccessKeySecret]
|
|
3528
|
+
]) {
|
|
3529
|
+
if (!value) {
|
|
3530
|
+
fatal(`--${flag} is required.`);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
const deletions = a.str("deletions");
|
|
3534
|
+
if (deletions && deletions !== "mirror" && deletions !== "keep") {
|
|
3535
|
+
fatal(`--deletions must be "mirror" or "keep" (got "${deletions}").`);
|
|
3536
|
+
}
|
|
3537
|
+
const prefix = a.str("prefix");
|
|
3538
|
+
const endpoint = a.str("endpoint");
|
|
3539
|
+
const budget = a.num("budget-per-sync");
|
|
3540
|
+
const { connector } = await connect(ctx, {
|
|
3541
|
+
slug,
|
|
3542
|
+
bucket,
|
|
3543
|
+
region,
|
|
3544
|
+
...prefix !== void 0 ? { prefix } : {},
|
|
3545
|
+
...endpoint !== void 0 ? { endpoint } : {},
|
|
3546
|
+
accessKeyIdSecret,
|
|
3547
|
+
secretAccessKeySecret,
|
|
3548
|
+
...deletions ? { deletions } : {},
|
|
3549
|
+
...budget !== void 0 ? { budgetDollarsPerSync: budget } : {}
|
|
3550
|
+
});
|
|
3551
|
+
out({
|
|
3552
|
+
dataSource: slug,
|
|
3553
|
+
connector,
|
|
3554
|
+
note: "Connected. Run `datasources sync --source " + slug + " --wait` to load the bucket; the first sync plans first and stops for `datasources jobs approve <id>` when the plan is over the per-sync budget."
|
|
3555
|
+
});
|
|
3556
|
+
}
|
|
3557
|
+
async function show(ctx, a) {
|
|
3558
|
+
const slug = sourceOf(a);
|
|
3559
|
+
out({ dataSource: slug, ...await get8(ctx, { slug }) });
|
|
3560
|
+
}
|
|
3561
|
+
async function disconnect2(ctx, a) {
|
|
3562
|
+
const slug = a.str("source");
|
|
3563
|
+
if (!slug) {
|
|
3564
|
+
fatal(
|
|
3565
|
+
"--source is required \u2014 disconnecting never falls back to the default source."
|
|
3566
|
+
);
|
|
3567
|
+
}
|
|
3568
|
+
const result = await disconnect(ctx, { slug });
|
|
3569
|
+
out({
|
|
3570
|
+
dataSource: slug,
|
|
3571
|
+
...result,
|
|
3572
|
+
note: "The documents stay. Connect again to follow a bucket; unchanged files are recognised by content and cost nothing to re-sync."
|
|
3573
|
+
});
|
|
3574
|
+
}
|
|
3575
|
+
async function sync2(ctx, a) {
|
|
3576
|
+
const slug = sourceOf(a);
|
|
3577
|
+
const budget = a.num("budget");
|
|
3578
|
+
const { job } = await sync(ctx, {
|
|
3579
|
+
slug,
|
|
3580
|
+
...budget !== void 0 ? { budgetDollars: budget } : {}
|
|
3581
|
+
});
|
|
3582
|
+
if (!a.bool("wait")) {
|
|
3583
|
+
out({
|
|
3584
|
+
dataSource: slug,
|
|
3585
|
+
job,
|
|
3586
|
+
note: "Syncing. Poll `datasources jobs status <id>`, or re-run with --wait. A plan over the per-sync budget waits in `planned` for `datasources jobs approve <id>`."
|
|
3587
|
+
});
|
|
3588
|
+
return;
|
|
3589
|
+
}
|
|
3590
|
+
await waitAndReport(ctx, a, job.id, "job", {
|
|
3591
|
+
dataSource: slug,
|
|
3592
|
+
action: "sync"
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3595
|
+
var dataSourceConnectorsHandlers = {
|
|
3596
|
+
"datasources connect": connect2,
|
|
3597
|
+
"datasources connector": show,
|
|
3598
|
+
"datasources disconnect": disconnect2,
|
|
3599
|
+
"datasources sync": sync2
|
|
3600
|
+
};
|
|
3601
|
+
var dataSourceConnectorsHelp = `
|
|
3602
|
+
Following an S3 bucket (connectors):
|
|
3603
|
+
remy-admin datasources connect --source <slug> --bucket <name> --region <aws-region> [--prefix <p>] --access-key-secret <NAME> --secret-key-secret <NAME> [--deletions mirror|keep] [--budget-per-sync <dollars>]
|
|
3604
|
+
remy-admin datasources connector [--source <slug>]
|
|
3605
|
+
remy-admin datasources sync [--source <slug>] [--budget <dollars>] [--wait] [--timeout <sec>]
|
|
3606
|
+
remy-admin datasources disconnect --source <slug>
|
|
3607
|
+
|
|
3608
|
+
A connector makes a bucket the customer owns the origin of a source. The
|
|
3609
|
+
credentials are the NAMES of two app secrets \u2014 set them first with
|
|
3610
|
+
\`remy-admin secrets set ARCHIVE_S3_KEY --prod <value>\` (or in the dashboard);
|
|
3611
|
+
never put a key on a command line or in code. connect checks that both
|
|
3612
|
+
secrets exist and that the keys can list the prefix before it records
|
|
3613
|
+
anything, and refuses otherwise with the S3 error.
|
|
3614
|
+
|
|
3615
|
+
sync lists the bucket, compares every object's ETag with what was ingested
|
|
3616
|
+
before, and runs a job (see above) over what is new or changed: the same
|
|
3617
|
+
plan, the same gates, auto-approved under the connector's per-sync budget.
|
|
3618
|
+
A plan over the budget \u2014 the first backfill of a big bucket \u2014 waits in
|
|
3619
|
+
\`planned\` for \`jobs approve <id>\`; the nightly deltas run unattended.
|
|
3620
|
+
A sync over an unchanged bucket costs nothing. Keys that disappeared from
|
|
3621
|
+
the bucket are removed from the source at the end of a full sync under the
|
|
3622
|
+
default \`--deletions mirror\`; \`--deletions keep\` leaves them. A changed
|
|
3623
|
+
object replaces its document; the old one is kept as lineage.
|
|
3624
|
+
|
|
3625
|
+
Scheduling is the app's: a nightly sync is an ordinary cron interface job
|
|
3626
|
+
whose method calls \`Source.sync()\` from the SDK. There is no platform
|
|
3627
|
+
schedule to configure.
|
|
3628
|
+
|
|
3629
|
+
On a source without a mapper, objects whose extension no extractor reads
|
|
3630
|
+
are skipped, not failed. Re-running connect with the same bucket, region,
|
|
3631
|
+
prefix and endpoint updates credentials and policy in place; pointing a
|
|
3632
|
+
source at a different bucket needs a disconnect first. disconnect keeps
|
|
3633
|
+
every document \u2014 only the link and its object index go.
|
|
3634
|
+
`;
|
|
3635
|
+
|
|
3636
|
+
// src/commands/dataSourceEvals.ts
|
|
3637
|
+
import fs3 from "fs";
|
|
3638
|
+
import path3 from "path";
|
|
3639
|
+
|
|
3640
|
+
// src/ops/dataSourceEvals.ts
|
|
3641
|
+
var DEFAULT_WAIT_TIMEOUT_MS3 = 30 * 60 * 1e3;
|
|
3642
|
+
var POLL_MS4 = 4e3;
|
|
3643
|
+
function base4(appId) {
|
|
3644
|
+
return `/_internal/v2/apps/${appId}/datasources`;
|
|
3645
|
+
}
|
|
3646
|
+
function hydrate(ctx, params) {
|
|
3647
|
+
return call(
|
|
3648
|
+
ctx,
|
|
3649
|
+
"POST",
|
|
3650
|
+
`${base4(ctx.appId)}/hydrate`,
|
|
3651
|
+
{ slug: params.slug }
|
|
3652
|
+
);
|
|
3653
|
+
}
|
|
3654
|
+
async function waitForHydration(ctx, params) {
|
|
3655
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS3;
|
|
3656
|
+
const outcome = await pollUntil(
|
|
3657
|
+
async () => {
|
|
3658
|
+
const { dataSources } = await list8(ctx);
|
|
3659
|
+
const source2 = dataSources.find((s) => s.slug === params.slug);
|
|
3660
|
+
if (!source2) {
|
|
3661
|
+
throw new Error(`Data source "${params.slug}" not found.`);
|
|
3662
|
+
}
|
|
3663
|
+
return source2;
|
|
3664
|
+
},
|
|
3665
|
+
(source2) => !source2.hydration || source2.hydration.error !== null,
|
|
3666
|
+
{
|
|
3667
|
+
timeoutMs,
|
|
3668
|
+
pollMs: POLL_MS4,
|
|
3669
|
+
describe: (source2, start2) => `loading\u2026 ${source2.hydration.copied}/${source2.hydration.total} documents (${elapsedSeconds(start2)}s)`,
|
|
3670
|
+
onProgress: params.onProgress
|
|
3671
|
+
}
|
|
3672
|
+
);
|
|
3673
|
+
const source = outcome.value;
|
|
3674
|
+
const hydration = source.hydration;
|
|
3675
|
+
if (!outcome.settled) {
|
|
3676
|
+
return {
|
|
3677
|
+
status: "timeout",
|
|
3678
|
+
source,
|
|
3679
|
+
error: timedOut(
|
|
3680
|
+
timeoutMs,
|
|
3681
|
+
`with ${hydration.copied}/${hydration.total} documents loaded`
|
|
3682
|
+
)
|
|
3683
|
+
};
|
|
3684
|
+
}
|
|
3685
|
+
if (hydration?.error) {
|
|
3686
|
+
return { status: "failed", source, error: hydration.error };
|
|
3687
|
+
}
|
|
3688
|
+
return { status: "done", source };
|
|
3689
|
+
}
|
|
3690
|
+
function sample(ctx, params) {
|
|
3691
|
+
const { slug, size, as, filter, stratify, placement } = params;
|
|
3692
|
+
return call(
|
|
3693
|
+
ctx,
|
|
3694
|
+
"POST",
|
|
3695
|
+
`${base4(ctx.appId)}/sample`,
|
|
3696
|
+
{
|
|
3697
|
+
slug,
|
|
3698
|
+
size,
|
|
3699
|
+
...as ? { as } : {},
|
|
3700
|
+
...filter ? { filter } : {},
|
|
3701
|
+
...stratify ? { stratify } : {},
|
|
3702
|
+
...placement !== void 0 ? { placement } : {}
|
|
3703
|
+
}
|
|
3704
|
+
);
|
|
3705
|
+
}
|
|
3706
|
+
function createSet(ctx, params) {
|
|
3707
|
+
const { slug, name, description, size, style, modelId, tags, seed } = params;
|
|
3708
|
+
return call(ctx, "POST", `${base4(ctx.appId)}/eval/sets`, {
|
|
3709
|
+
slug,
|
|
3710
|
+
name,
|
|
3711
|
+
...description ? { description } : {},
|
|
3712
|
+
...size !== void 0 ? { size } : {},
|
|
3713
|
+
...style ? { style } : {},
|
|
3714
|
+
...modelId ? { modelId } : {},
|
|
3715
|
+
...tags?.length ? { tags } : {},
|
|
3716
|
+
...seed !== void 0 ? { seed } : {}
|
|
3717
|
+
});
|
|
3718
|
+
}
|
|
3719
|
+
function listSets(ctx, params) {
|
|
3720
|
+
return call(
|
|
3721
|
+
ctx,
|
|
3722
|
+
"GET",
|
|
3723
|
+
`${base4(ctx.appId)}/eval/sets${qs({ slug: params?.slug })}`
|
|
3724
|
+
);
|
|
3725
|
+
}
|
|
3726
|
+
function getSet(ctx, params) {
|
|
3727
|
+
return call(
|
|
3728
|
+
ctx,
|
|
3729
|
+
"GET",
|
|
3730
|
+
`${base4(ctx.appId)}/eval/sets/${encodeURIComponent(params.id)}${qs({
|
|
3731
|
+
queries: params.queries ? "true" : void 0
|
|
3732
|
+
})}`
|
|
3733
|
+
);
|
|
3734
|
+
}
|
|
3735
|
+
function deleteSet(ctx, params) {
|
|
3736
|
+
return call(
|
|
3737
|
+
ctx,
|
|
3738
|
+
"POST",
|
|
3739
|
+
`${base4(ctx.appId)}/eval/sets/${encodeURIComponent(params.id)}/delete`
|
|
3740
|
+
);
|
|
3741
|
+
}
|
|
3742
|
+
function addQueries(ctx, params) {
|
|
3743
|
+
return call(
|
|
3744
|
+
ctx,
|
|
3745
|
+
"POST",
|
|
3746
|
+
`${base4(ctx.appId)}/eval/sets/${encodeURIComponent(params.id)}/queries`,
|
|
3747
|
+
{ queries: params.queries }
|
|
3748
|
+
);
|
|
3749
|
+
}
|
|
3750
|
+
function listQueries(ctx, params) {
|
|
3751
|
+
return call(
|
|
3752
|
+
ctx,
|
|
3753
|
+
"GET",
|
|
3754
|
+
`${base4(ctx.appId)}/eval/sets/${encodeURIComponent(params.id)}/queries`
|
|
3755
|
+
);
|
|
3756
|
+
}
|
|
3757
|
+
function deleteQuery(ctx, params) {
|
|
3758
|
+
return call(
|
|
3759
|
+
ctx,
|
|
3760
|
+
"POST",
|
|
3761
|
+
`${base4(ctx.appId)}/eval/sets/${encodeURIComponent(params.id)}/queries/delete`,
|
|
3762
|
+
{ queryId: params.queryId }
|
|
3763
|
+
);
|
|
3764
|
+
}
|
|
3765
|
+
function run(ctx, params) {
|
|
3766
|
+
const { setId, slug, candidate, label, retrieval } = params;
|
|
3767
|
+
return call(ctx, "POST", `${base4(ctx.appId)}/eval/runs`, {
|
|
3768
|
+
setId,
|
|
3769
|
+
...slug ? { slug } : {},
|
|
3770
|
+
...candidate ? { candidate: true } : {},
|
|
3771
|
+
...label ? { label } : {},
|
|
3772
|
+
...retrieval && Object.keys(retrieval).length ? { retrieval } : {}
|
|
3773
|
+
});
|
|
3774
|
+
}
|
|
3775
|
+
function listRuns(ctx, params) {
|
|
3776
|
+
return call(
|
|
3777
|
+
ctx,
|
|
3778
|
+
"GET",
|
|
3779
|
+
`${base4(ctx.appId)}/eval/runs${qs({ setId: params?.setId, slug: params?.slug })}`
|
|
3780
|
+
);
|
|
3781
|
+
}
|
|
3782
|
+
function getRun(ctx, params) {
|
|
3783
|
+
return call(
|
|
3784
|
+
ctx,
|
|
3785
|
+
"GET",
|
|
3786
|
+
`${base4(ctx.appId)}/eval/runs/${encodeURIComponent(params.id)}${qs({
|
|
3787
|
+
queries: params.queries ? "true" : void 0,
|
|
3788
|
+
worst: params.worst
|
|
3789
|
+
})}`
|
|
3790
|
+
);
|
|
3791
|
+
}
|
|
3792
|
+
function compare(ctx, params) {
|
|
3793
|
+
return call(
|
|
3794
|
+
ctx,
|
|
3795
|
+
"GET",
|
|
3796
|
+
`${base4(ctx.appId)}/eval/runs/compare${qs({ a: params.a, b: params.b })}`
|
|
3797
|
+
);
|
|
3798
|
+
}
|
|
3799
|
+
async function waitForRun(ctx, params) {
|
|
3800
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS3;
|
|
3801
|
+
const outcome = await pollUntil(
|
|
3802
|
+
async () => (await getRun(ctx, { id: params.id })).run,
|
|
3803
|
+
(run4) => run4.state === "done" || run4.state === "failed",
|
|
3804
|
+
{
|
|
3805
|
+
timeoutMs,
|
|
3806
|
+
pollMs: POLL_MS4,
|
|
3807
|
+
describe: (run4, start2) => `${run4.state}\u2026 ${run4.completed}/${run4.total} queries (${elapsedSeconds(start2)}s)`,
|
|
3808
|
+
onProgress: params.onProgress
|
|
3809
|
+
}
|
|
3810
|
+
);
|
|
3811
|
+
const run3 = outcome.value;
|
|
3812
|
+
if (!outcome.settled) {
|
|
3813
|
+
return {
|
|
3814
|
+
status: "timeout",
|
|
3815
|
+
run: run3,
|
|
3816
|
+
error: timedOut(
|
|
3817
|
+
timeoutMs,
|
|
3818
|
+
`; run is ${run3.state} (${run3.completed}/${run3.total})`
|
|
3819
|
+
)
|
|
3820
|
+
};
|
|
3821
|
+
}
|
|
3822
|
+
if (run3.state === "failed") {
|
|
3823
|
+
return { status: "failed", run: run3, error: run3.error ?? "The run failed." };
|
|
3824
|
+
}
|
|
3825
|
+
return { status: "done", run: run3 };
|
|
3826
|
+
}
|
|
3827
|
+
async function waitForSet(ctx, params) {
|
|
3828
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS3;
|
|
3829
|
+
const outcome = await pollUntil(
|
|
3830
|
+
async () => (await getSet(ctx, { id: params.id })).set,
|
|
3831
|
+
(set4) => set4.state === "ready" || set4.state === "failed",
|
|
3832
|
+
{
|
|
3833
|
+
timeoutMs,
|
|
3834
|
+
pollMs: POLL_MS4,
|
|
3835
|
+
describe: (set4, start2) => `${set4.state}\u2026 (${elapsedSeconds(start2)}s)`,
|
|
3836
|
+
onProgress: params.onProgress
|
|
3837
|
+
}
|
|
3838
|
+
);
|
|
3839
|
+
const set3 = outcome.value;
|
|
3840
|
+
if (!outcome.settled) {
|
|
3841
|
+
return {
|
|
3842
|
+
status: "timeout",
|
|
3843
|
+
set: set3,
|
|
3844
|
+
error: timedOut(timeoutMs, `; set is still ${set3.state}`)
|
|
3845
|
+
};
|
|
3846
|
+
}
|
|
3847
|
+
if (set3.state === "failed") {
|
|
3848
|
+
return { status: "failed", set: set3, error: set3.error ?? "Generation failed." };
|
|
3849
|
+
}
|
|
3850
|
+
return { status: "done", set: set3 };
|
|
3851
|
+
}
|
|
3852
|
+
|
|
3853
|
+
// src/commands/dataSourceEvals.ts
|
|
3854
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3855
|
+
var dataSourceEvalsSpecs = {
|
|
3856
|
+
"datasources hydrate": {
|
|
3857
|
+
usage: "Usage: remy-admin datasources hydrate [--source <slug>] [--wait] [--timeout <sec>]",
|
|
3858
|
+
flags: {
|
|
3859
|
+
source: { type: "string" },
|
|
3860
|
+
wait: { type: "boolean" },
|
|
3861
|
+
timeout: { type: "string" }
|
|
3862
|
+
}
|
|
3863
|
+
},
|
|
3864
|
+
"datasources sample": {
|
|
3865
|
+
usage: "Usage: remy-admin datasources sample --source <slug> --size <n> [--as <slug>] [--filter <k=v,...|json>] [--stratify <metadata-key>] [--placement <resource-id|shared>] [--wait] [--timeout <sec>]",
|
|
3866
|
+
flags: {
|
|
3867
|
+
source: { type: "string" },
|
|
3868
|
+
size: { type: "number", min: 1 },
|
|
3869
|
+
as: { type: "string" },
|
|
3870
|
+
filter: { type: "string" },
|
|
3871
|
+
stratify: { type: "string" },
|
|
3872
|
+
placement: { type: "string" },
|
|
3873
|
+
wait: { type: "boolean" },
|
|
3874
|
+
timeout: { type: "string" }
|
|
3875
|
+
},
|
|
3876
|
+
requireAnyOf: {
|
|
3877
|
+
flags: ["size"],
|
|
3878
|
+
message: "--size <n> is required (documents to draw, up to 2000)."
|
|
3879
|
+
}
|
|
3880
|
+
},
|
|
3881
|
+
"datasources eval create": {
|
|
3882
|
+
usage: "Usage: remy-admin datasources eval create --source <slug> --name <name> [--size <n>] [--style cloze|question] [--model <id>] [--tags <a,b>] [--description <text>] [--seed <n>] [--wait] [--timeout <sec>]",
|
|
3883
|
+
flags: {
|
|
3884
|
+
source: { type: "string" },
|
|
3885
|
+
name: { type: "string" },
|
|
3886
|
+
size: { type: "number", min: 0 },
|
|
3887
|
+
style: { type: "string" },
|
|
3888
|
+
model: { type: "string" },
|
|
3889
|
+
tags: { type: "string" },
|
|
3890
|
+
description: { type: "string" },
|
|
3891
|
+
seed: { type: "number", min: 0 },
|
|
3892
|
+
wait: { type: "boolean" },
|
|
3893
|
+
timeout: { type: "string" }
|
|
3894
|
+
},
|
|
3895
|
+
requireAnyOf: { flags: ["name"], message: "--name is required." }
|
|
3896
|
+
},
|
|
3897
|
+
"datasources eval list": {
|
|
3898
|
+
usage: "Usage: remy-admin datasources eval list [--source <slug>]",
|
|
3899
|
+
flags: { source: { type: "string" } }
|
|
3900
|
+
},
|
|
3901
|
+
"datasources eval get": {
|
|
3902
|
+
usage: "Usage: remy-admin datasources eval get <setId> [--queries]",
|
|
3903
|
+
positionals: [{ name: "id", required: true }],
|
|
3904
|
+
flags: { queries: { type: "boolean" } }
|
|
3905
|
+
},
|
|
3906
|
+
"datasources eval delete": {
|
|
3907
|
+
usage: "Usage: remy-admin datasources eval delete <setId>",
|
|
3908
|
+
positionals: [{ name: "id", required: true }]
|
|
3909
|
+
},
|
|
3910
|
+
"datasources eval add": {
|
|
3911
|
+
usage: "Usage: remy-admin datasources eval add --set <id> --query <text> (--expect <ref,...> | --expect-file <name,...> | --expect-external <id,...>) [--passage <text>] [--tags <a,b>]",
|
|
3912
|
+
flags: {
|
|
3913
|
+
set: { type: "string" },
|
|
3914
|
+
query: { type: "string" },
|
|
3915
|
+
expect: { type: "string" },
|
|
3916
|
+
"expect-file": { type: "string" },
|
|
3917
|
+
"expect-external": { type: "string" },
|
|
3918
|
+
passage: { type: "string" },
|
|
3919
|
+
tags: { type: "string" }
|
|
3920
|
+
},
|
|
3921
|
+
requireAnyOf: {
|
|
3922
|
+
flags: ["expect", "expect-file", "expect-external"],
|
|
3923
|
+
message: "Say which document should come back: --expect <documentId|filename|externalId,...>, --expect-file <filename,...> or --expect-external <externalId,...>."
|
|
3924
|
+
}
|
|
3925
|
+
},
|
|
3926
|
+
"datasources eval import": {
|
|
3927
|
+
usage: "Usage: remy-admin datasources eval import --set <id> <file.jsonl>",
|
|
3928
|
+
positionals: [{ name: "file", required: true }],
|
|
3929
|
+
flags: { set: { type: "string" } }
|
|
3930
|
+
},
|
|
3931
|
+
"datasources eval queries": {
|
|
3932
|
+
usage: "Usage: remy-admin datasources eval queries --set <id>",
|
|
3933
|
+
flags: { set: { type: "string" } }
|
|
3934
|
+
},
|
|
3935
|
+
"datasources eval rm": {
|
|
3936
|
+
usage: "Usage: remy-admin datasources eval rm --set <id> <queryId>",
|
|
3937
|
+
positionals: [{ name: "queryId", required: true }],
|
|
3938
|
+
flags: { set: { type: "string" } }
|
|
3939
|
+
},
|
|
3940
|
+
"datasources eval run": {
|
|
3941
|
+
usage: "Usage: remy-admin datasources eval run --set <id> [--source <slug>] [--candidate] [--label <text>] [--mode hybrid|semantic|lexical] [--rerank true|false] [--rerank-model <id>] [--candidates <n>] [--hybrid true|false] [--top-k <n>] [--wait] [--timeout <sec>]",
|
|
3942
|
+
flags: {
|
|
3943
|
+
set: { type: "string" },
|
|
3944
|
+
source: { type: "string" },
|
|
3945
|
+
candidate: { type: "boolean" },
|
|
3946
|
+
label: { type: "string" },
|
|
3947
|
+
mode: { type: "string" },
|
|
3948
|
+
rerank: { type: "string" },
|
|
3949
|
+
"rerank-model": { type: "string" },
|
|
3950
|
+
candidates: { type: "number", min: 1 },
|
|
3951
|
+
hybrid: { type: "string" },
|
|
3952
|
+
"top-k": { type: "number", min: 1 },
|
|
3953
|
+
wait: { type: "boolean" },
|
|
3954
|
+
timeout: { type: "string" }
|
|
3955
|
+
},
|
|
3956
|
+
requireAnyOf: { flags: ["set"], message: "--set <id> is required." }
|
|
3957
|
+
},
|
|
3958
|
+
"datasources eval runs": {
|
|
3959
|
+
usage: "Usage: remy-admin datasources eval runs [--set <id>] [--source <slug>]",
|
|
3960
|
+
flags: { set: { type: "string" }, source: { type: "string" } }
|
|
3961
|
+
},
|
|
3962
|
+
"datasources eval result": {
|
|
3963
|
+
usage: "Usage: remy-admin datasources eval result <runId> [--queries] [--worst <n>]",
|
|
3964
|
+
positionals: [{ name: "id", required: true }],
|
|
3965
|
+
flags: { queries: { type: "boolean" }, worst: { type: "number", min: 1 } }
|
|
3966
|
+
},
|
|
3967
|
+
"datasources eval compare": {
|
|
3968
|
+
usage: "Usage: remy-admin datasources eval compare <runA> <runB>",
|
|
3969
|
+
positionals: [
|
|
3970
|
+
{ name: "a", required: true },
|
|
3971
|
+
{ name: "b", required: true }
|
|
3972
|
+
]
|
|
3973
|
+
}
|
|
3974
|
+
};
|
|
3975
|
+
var requireSet = (a) => {
|
|
3976
|
+
const id = a.str("set");
|
|
3977
|
+
if (!id) {
|
|
3978
|
+
fatal("--set <id> is required.");
|
|
3979
|
+
}
|
|
3980
|
+
return id;
|
|
3981
|
+
};
|
|
3982
|
+
var splitList = (raw) => (raw ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
3983
|
+
var triState = (a, flag) => {
|
|
3984
|
+
const raw = a.str(flag);
|
|
3985
|
+
if (raw === void 0) {
|
|
3986
|
+
return void 0;
|
|
3987
|
+
}
|
|
3988
|
+
if (raw !== "true" && raw !== "false") {
|
|
3989
|
+
fatal(`--${flag} must be "true" or "false" (got "${raw}").`);
|
|
3990
|
+
}
|
|
3991
|
+
return raw === "true";
|
|
3992
|
+
};
|
|
3993
|
+
function selectorFromFlag(raw) {
|
|
3994
|
+
if (raw === void 0) {
|
|
3995
|
+
return void 0;
|
|
3996
|
+
}
|
|
3997
|
+
if (raw.trimStart().startsWith("{")) {
|
|
3998
|
+
try {
|
|
3999
|
+
return JSON.parse(raw);
|
|
4000
|
+
} catch (err) {
|
|
4001
|
+
fatal(`--filter is not valid JSON: ${err.message}`);
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
const metadata = {};
|
|
4005
|
+
for (const part of raw.split(",")) {
|
|
4006
|
+
const trimmed = part.trim();
|
|
4007
|
+
if (!trimmed) {
|
|
4008
|
+
continue;
|
|
4009
|
+
}
|
|
4010
|
+
const eq = trimmed.indexOf("=");
|
|
4011
|
+
if (eq <= 0) {
|
|
4012
|
+
fatal(`--filter entries must be key=value (got "${trimmed}").`);
|
|
4013
|
+
}
|
|
4014
|
+
const key = trimmed.slice(0, eq).trim();
|
|
4015
|
+
const value = trimmed.slice(eq + 1).trim();
|
|
4016
|
+
metadata[key] = value === "true" ? true : value === "false" ? false : value !== "" && !Number.isNaN(Number(value)) ? Number(value) : value;
|
|
4017
|
+
}
|
|
4018
|
+
return { metadata };
|
|
4019
|
+
}
|
|
4020
|
+
async function hydrate2(ctx, a) {
|
|
4021
|
+
const slug = a.str("source") || DEFAULT_SOURCE;
|
|
4022
|
+
const started = await hydrate(ctx, { slug });
|
|
4023
|
+
if (started.resident || !a.bool("wait")) {
|
|
4024
|
+
out({
|
|
4025
|
+
dataSource: slug,
|
|
4026
|
+
...started,
|
|
4027
|
+
...started.resident ? { note: "The index is already loaded." } : {
|
|
4028
|
+
note: "Loading in the background. `datasources list` shows progress; re-run with --wait to block."
|
|
4029
|
+
}
|
|
4030
|
+
});
|
|
4031
|
+
return;
|
|
4032
|
+
}
|
|
4033
|
+
const result = await waitForHydration(ctx, {
|
|
4034
|
+
slug,
|
|
4035
|
+
...timeoutOf(a),
|
|
4036
|
+
onProgress: progress
|
|
4037
|
+
});
|
|
4038
|
+
out({ dataSource: slug, action: "hydrate", ...result });
|
|
4039
|
+
failOnWait(result);
|
|
4040
|
+
}
|
|
4041
|
+
async function sampleCmd(ctx, a) {
|
|
4042
|
+
const slug = a.str("source");
|
|
4043
|
+
if (!slug) {
|
|
4044
|
+
fatal("--source is required \u2014 a sample is drawn from one named source.");
|
|
4045
|
+
}
|
|
4046
|
+
const placementRaw = a.str("placement");
|
|
4047
|
+
const placement = placementRaw === void 0 ? void 0 : placementRaw === "shared" ? "shared" : { resourceId: placementRaw };
|
|
4048
|
+
const result = await sample(ctx, {
|
|
4049
|
+
slug,
|
|
4050
|
+
size: a.num("size"),
|
|
4051
|
+
...a.str("as") ? { as: a.str("as") } : {},
|
|
4052
|
+
...a.str("filter") ? { filter: selectorFromFlag(a.str("filter")) } : {},
|
|
4053
|
+
...a.str("stratify") ? { stratify: a.str("stratify") } : {},
|
|
4054
|
+
...placement !== void 0 ? { placement } : {}
|
|
4055
|
+
});
|
|
4056
|
+
if (!a.bool("wait")) {
|
|
4057
|
+
out({
|
|
4058
|
+
...result,
|
|
4059
|
+
note: `Sampling into "${result.dataSource.slug}". Poll \`datasources jobs status ${result.job.id}\` or re-run with --wait.`
|
|
4060
|
+
});
|
|
4061
|
+
return;
|
|
4062
|
+
}
|
|
4063
|
+
await waitAndReport(ctx, a, result.job.id, "job", {
|
|
4064
|
+
dataSource: result.dataSource,
|
|
4065
|
+
action: "sample"
|
|
4066
|
+
});
|
|
4067
|
+
}
|
|
4068
|
+
async function evalCreate(ctx, a) {
|
|
4069
|
+
const slug = a.str("source") || DEFAULT_SOURCE;
|
|
4070
|
+
const style = a.str("style");
|
|
4071
|
+
if (style && style !== "cloze" && style !== "question") {
|
|
4072
|
+
fatal(`--style must be "cloze" or "question" (got "${style}").`);
|
|
4073
|
+
}
|
|
4074
|
+
const size = a.num("size");
|
|
4075
|
+
const seed = a.num("seed");
|
|
4076
|
+
const { set: set3 } = await createSet(ctx, {
|
|
4077
|
+
slug,
|
|
4078
|
+
name: a.str("name"),
|
|
4079
|
+
...a.str("description") ? { description: a.str("description") } : {},
|
|
4080
|
+
...size !== void 0 ? { size } : {},
|
|
4081
|
+
...style ? { style } : {},
|
|
4082
|
+
...a.str("model") ? { modelId: a.str("model") } : {},
|
|
4083
|
+
...a.str("tags") ? { tags: splitList(a.str("tags")) } : {},
|
|
4084
|
+
...seed !== void 0 ? { seed } : {}
|
|
4085
|
+
});
|
|
4086
|
+
const generating = set3.state === "queued" || set3.state === "generating";
|
|
4087
|
+
if (!generating || !a.bool("wait")) {
|
|
4088
|
+
out({
|
|
4089
|
+
dataSource: slug,
|
|
4090
|
+
set: set3,
|
|
4091
|
+
note: generating ? "Generating in the background. `datasources eval get <id>` shows the state; re-run with --wait to block." : "Empty set. Add queries with `datasources eval add` or `eval import`."
|
|
4092
|
+
});
|
|
4093
|
+
return;
|
|
4094
|
+
}
|
|
4095
|
+
const result = await waitForSet(ctx, {
|
|
4096
|
+
id: set3.id,
|
|
4097
|
+
...timeoutOf(a),
|
|
4098
|
+
onProgress: progress
|
|
4099
|
+
});
|
|
4100
|
+
out({ dataSource: slug, action: "create", ...result });
|
|
4101
|
+
failOnWait(result);
|
|
4102
|
+
}
|
|
4103
|
+
async function evalList(ctx, a) {
|
|
4104
|
+
const slug = a.str("source");
|
|
4105
|
+
out(await listSets(ctx, slug ? { slug } : void 0));
|
|
4106
|
+
}
|
|
4107
|
+
async function evalGet(ctx, a) {
|
|
4108
|
+
out(
|
|
4109
|
+
await getSet(ctx, {
|
|
4110
|
+
id: a.req("id"),
|
|
4111
|
+
...a.bool("queries") ? { queries: true } : {}
|
|
4112
|
+
})
|
|
4113
|
+
);
|
|
4114
|
+
}
|
|
4115
|
+
async function evalDelete(ctx, a) {
|
|
4116
|
+
out(await deleteSet(ctx, { id: a.req("id") }));
|
|
4117
|
+
}
|
|
4118
|
+
function expectFromFlags(a) {
|
|
4119
|
+
const expect = {};
|
|
4120
|
+
for (const ref of splitList(a.str("expect"))) {
|
|
4121
|
+
if (UUID_RE.test(ref)) {
|
|
4122
|
+
(expect.documentIds ??= []).push(ref);
|
|
4123
|
+
} else if (ref.includes(".")) {
|
|
4124
|
+
(expect.filenames ??= []).push(ref);
|
|
4125
|
+
} else {
|
|
4126
|
+
(expect.externalIds ??= []).push(ref);
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
for (const name of splitList(a.str("expect-file"))) {
|
|
4130
|
+
(expect.filenames ??= []).push(name);
|
|
4131
|
+
}
|
|
4132
|
+
for (const id of splitList(a.str("expect-external"))) {
|
|
4133
|
+
(expect.externalIds ??= []).push(id);
|
|
4134
|
+
}
|
|
4135
|
+
return expect;
|
|
4136
|
+
}
|
|
4137
|
+
async function evalAdd(ctx, a) {
|
|
4138
|
+
const id = requireSet(a);
|
|
4139
|
+
const query3 = a.str("query");
|
|
4140
|
+
if (!query3) {
|
|
4141
|
+
fatal("--query is required.");
|
|
4142
|
+
}
|
|
4143
|
+
const result = await addQueries(ctx, {
|
|
4144
|
+
id,
|
|
4145
|
+
queries: [
|
|
4146
|
+
{
|
|
4147
|
+
query: query3,
|
|
4148
|
+
expect: expectFromFlags(a),
|
|
4149
|
+
...a.str("passage") ? { passage: a.str("passage") } : {},
|
|
4150
|
+
...a.str("tags") ? { tags: splitList(a.str("tags")) } : {}
|
|
4151
|
+
}
|
|
4152
|
+
]
|
|
4153
|
+
});
|
|
4154
|
+
out({ set: id, ...result });
|
|
4155
|
+
}
|
|
4156
|
+
async function evalImport(ctx, a) {
|
|
4157
|
+
const id = requireSet(a);
|
|
4158
|
+
const file = a.req("file");
|
|
4159
|
+
const abs = path3.isAbsolute(file) ? file : path3.join(WORKSPACE_DIR, file);
|
|
4160
|
+
let content;
|
|
4161
|
+
try {
|
|
4162
|
+
content = fs3.readFileSync(abs, "utf8");
|
|
4163
|
+
} catch (err) {
|
|
4164
|
+
fatal(`Could not read "${file}": ${err.message}`);
|
|
4165
|
+
}
|
|
4166
|
+
const rows = [];
|
|
4167
|
+
content.split("\n").forEach((line, i) => {
|
|
4168
|
+
const trimmed = line.trim();
|
|
4169
|
+
if (!trimmed) {
|
|
4170
|
+
return;
|
|
4171
|
+
}
|
|
4172
|
+
let parsed;
|
|
4173
|
+
try {
|
|
4174
|
+
parsed = JSON.parse(trimmed);
|
|
4175
|
+
} catch {
|
|
4176
|
+
fatal(`${file}:${i + 1} is not valid JSON.`);
|
|
4177
|
+
}
|
|
4178
|
+
if (typeof parsed?.query !== "string" || typeof parsed?.expect !== "object") {
|
|
4179
|
+
fatal(
|
|
4180
|
+
`${file}:${i + 1} needs { "query": "...", "expect": { "filenames" | "documentIds" | "externalIds" | "contentHashes": [...] } }.`
|
|
4181
|
+
);
|
|
4182
|
+
}
|
|
4183
|
+
rows.push(parsed);
|
|
4184
|
+
});
|
|
4185
|
+
if (rows.length === 0) {
|
|
4186
|
+
fatal(`"${file}" has no queries.`);
|
|
4187
|
+
}
|
|
4188
|
+
let added = 0;
|
|
4189
|
+
let queryCount = 0;
|
|
4190
|
+
for (let i = 0; i < rows.length; i += 500) {
|
|
4191
|
+
progress(`importing ${Math.min(i + 500, rows.length)}/${rows.length}\u2026`);
|
|
4192
|
+
const result = await addQueries(ctx, {
|
|
4193
|
+
id,
|
|
4194
|
+
queries: rows.slice(i, i + 500)
|
|
4195
|
+
});
|
|
4196
|
+
added += result.added;
|
|
4197
|
+
queryCount = result.queryCount;
|
|
4198
|
+
}
|
|
4199
|
+
out({ set: id, added, queryCount });
|
|
4200
|
+
}
|
|
4201
|
+
async function evalQueries(ctx, a) {
|
|
4202
|
+
out(await listQueries(ctx, { id: requireSet(a) }));
|
|
4203
|
+
}
|
|
4204
|
+
async function evalRm(ctx, a) {
|
|
4205
|
+
out(
|
|
4206
|
+
await deleteQuery(ctx, {
|
|
4207
|
+
id: requireSet(a),
|
|
4208
|
+
queryId: a.req("queryId")
|
|
4209
|
+
})
|
|
4210
|
+
);
|
|
4211
|
+
}
|
|
4212
|
+
async function evalRun(ctx, a) {
|
|
4213
|
+
const mode = a.str("mode");
|
|
4214
|
+
if (mode && !["hybrid", "semantic", "lexical"].includes(mode)) {
|
|
4215
|
+
fatal(`--mode must be "hybrid", "semantic" or "lexical" (got "${mode}").`);
|
|
4216
|
+
}
|
|
4217
|
+
const rerank = triState(a, "rerank");
|
|
4218
|
+
const hybrid = triState(a, "hybrid");
|
|
4219
|
+
const rerankModel = a.str("rerank-model");
|
|
4220
|
+
const candidates = a.num("candidates");
|
|
4221
|
+
const topK = a.num("top-k");
|
|
4222
|
+
const retrieval = {
|
|
4223
|
+
...mode ? { mode } : {},
|
|
4224
|
+
...hybrid !== void 0 ? { hybrid } : {},
|
|
4225
|
+
...rerank !== void 0 || rerankModel || candidates !== void 0 ? {
|
|
4226
|
+
rerank: {
|
|
4227
|
+
...rerank !== void 0 ? { enabled: rerank } : {},
|
|
4228
|
+
...rerankModel ? { modelId: rerankModel } : {},
|
|
4229
|
+
...candidates !== void 0 ? { candidates } : {}
|
|
4230
|
+
}
|
|
4231
|
+
} : {},
|
|
4232
|
+
...topK !== void 0 ? { topK } : {}
|
|
4233
|
+
};
|
|
4234
|
+
const { run: run3 } = await run(ctx, {
|
|
4235
|
+
setId: a.str("set"),
|
|
4236
|
+
...a.str("source") ? { slug: a.str("source") } : {},
|
|
4237
|
+
...a.bool("candidate") ? { candidate: true } : {},
|
|
4238
|
+
...a.str("label") ? { label: a.str("label") } : {},
|
|
4239
|
+
retrieval
|
|
4240
|
+
});
|
|
4241
|
+
if (!a.bool("wait")) {
|
|
4242
|
+
out({
|
|
4243
|
+
run: run3,
|
|
4244
|
+
note: "Running in the background. `datasources eval result <id>` shows the numbers when it is done; re-run with --wait to block."
|
|
4245
|
+
});
|
|
4246
|
+
return;
|
|
4247
|
+
}
|
|
4248
|
+
const result = await waitForRun(ctx, {
|
|
4249
|
+
id: run3.id,
|
|
4250
|
+
...timeoutOf(a),
|
|
4251
|
+
onProgress: progress
|
|
4252
|
+
});
|
|
4253
|
+
out({ action: "run", ...result });
|
|
4254
|
+
failOnWait(result);
|
|
4255
|
+
}
|
|
4256
|
+
async function evalRuns(ctx, a) {
|
|
4257
|
+
const setId = a.str("set");
|
|
4258
|
+
const slug = a.str("source");
|
|
4259
|
+
out(
|
|
4260
|
+
await listRuns(ctx, {
|
|
4261
|
+
...setId ? { setId } : {},
|
|
4262
|
+
...slug ? { slug } : {}
|
|
4263
|
+
})
|
|
4264
|
+
);
|
|
4265
|
+
}
|
|
4266
|
+
async function evalResult(ctx, a) {
|
|
4267
|
+
const worst = a.num("worst");
|
|
4268
|
+
out(
|
|
4269
|
+
await getRun(ctx, {
|
|
4270
|
+
id: a.req("id"),
|
|
4271
|
+
...a.bool("queries") ? { queries: true } : {},
|
|
4272
|
+
...worst !== void 0 ? { worst } : {}
|
|
4273
|
+
})
|
|
4274
|
+
);
|
|
4275
|
+
}
|
|
4276
|
+
async function evalCompare(ctx, a) {
|
|
4277
|
+
out(await compare(ctx, { a: a.req("a"), b: a.req("b") }));
|
|
4278
|
+
}
|
|
4279
|
+
var dataSourceEvalsHandlers = {
|
|
4280
|
+
"datasources hydrate": hydrate2,
|
|
4281
|
+
"datasources sample": sampleCmd,
|
|
4282
|
+
"datasources eval create": evalCreate,
|
|
4283
|
+
"datasources eval list": evalList,
|
|
4284
|
+
"datasources eval get": evalGet,
|
|
4285
|
+
"datasources eval delete": evalDelete,
|
|
4286
|
+
"datasources eval add": evalAdd,
|
|
4287
|
+
"datasources eval import": evalImport,
|
|
4288
|
+
"datasources eval queries": evalQueries,
|
|
4289
|
+
"datasources eval rm": evalRm,
|
|
4290
|
+
"datasources eval run": evalRun,
|
|
4291
|
+
"datasources eval runs": evalRuns,
|
|
4292
|
+
"datasources eval result": evalResult,
|
|
4293
|
+
"datasources eval compare": evalCompare
|
|
4294
|
+
};
|
|
4295
|
+
var dataSourceEvalsHelp = `
|
|
4296
|
+
Warming and sampling:
|
|
4297
|
+
remy-admin datasources hydrate [--source <slug>] [--wait]
|
|
4298
|
+
remy-admin datasources sample --source <slug> --size <n> [--as <slug>] [--filter <k=v,...|json>] [--stratify <key>] [--placement <id|shared>] [--wait]
|
|
4299
|
+
|
|
4300
|
+
A large source whose index was evicted (the shared pool holds a working set,
|
|
4301
|
+
not everything) answers searches with index_warming while it reloads in the
|
|
4302
|
+
background; small sources reload inside the search and never show it.
|
|
4303
|
+
\`hydrate\` starts that reload ahead of time \u2014 before a demo, say \u2014 and
|
|
4304
|
+
\`--wait\` blocks until the index is back. \`datasources list\` shows the
|
|
4305
|
+
reload's progress under \`hydration\`.
|
|
4306
|
+
|
|
4307
|
+
\`sample\` draws documents from a source into a new one that shares its
|
|
4308
|
+
exact pinned config, so what you measure on the sample says something about
|
|
4309
|
+
the parent. Uniform by default; --stratify <metadata key> takes each value
|
|
4310
|
+
in proportion; --filter scopes the draw. It runs as a job (auto-approved;
|
|
4311
|
+
extraction is reused, so the plan is chunk + embed only). Documents keep
|
|
4312
|
+
their content hashes, which is what lets a query set built on the sample
|
|
4313
|
+
score the parent as well. Up to 2,000 documents.
|
|
4314
|
+
|
|
4315
|
+
Retrieval evals:
|
|
4316
|
+
remy-admin datasources eval create --source <slug> --name <name> [--size <n>] [--style cloze|question] [--model <id>] [--tags <a,b>] [--wait]
|
|
4317
|
+
remy-admin datasources eval list [--source <slug>]
|
|
4318
|
+
remy-admin datasources eval get <setId> [--queries]
|
|
4319
|
+
remy-admin datasources eval add --set <id> --query <text> --expect <ref,...> [--passage <text>] [--tags <a,b>]
|
|
4320
|
+
remy-admin datasources eval import --set <id> <file.jsonl>
|
|
4321
|
+
remy-admin datasources eval queries --set <id>
|
|
4322
|
+
remy-admin datasources eval rm --set <id> <queryId>
|
|
4323
|
+
remy-admin datasources eval delete <setId>
|
|
4324
|
+
remy-admin datasources eval run --set <id> [--source <slug>] [--candidate] [--label <text>] [retrieval options] [--wait]
|
|
4325
|
+
remy-admin datasources eval runs [--set <id>] [--source <slug>]
|
|
4326
|
+
remy-admin datasources eval result <runId> [--queries] [--worst <n>]
|
|
4327
|
+
remy-admin datasources eval compare <runA> <runB>
|
|
4328
|
+
|
|
4329
|
+
A query SET is a list of questions with the documents that should come
|
|
4330
|
+
back. \`create --size N\` generates them from the corpus: the default
|
|
4331
|
+
\`cloze\` style holds one sentence out of a chunk and uses it as the query
|
|
4332
|
+
(free and deterministic, but it flatters keyword matching \u2014 a real user
|
|
4333
|
+
does not type sentences from the document); \`--style question\` has a
|
|
4334
|
+
chat model write the question a user would ask (a model call per query,
|
|
4335
|
+
cost recorded on the set). Both record the document AND the chunk that
|
|
4336
|
+
answers, and both skip sentences that restate the title. Add real user
|
|
4337
|
+
questions with \`add\` (--expect takes document ids, filenames or external
|
|
4338
|
+
ids, comma-separated; every reference must exist) or \`import\` a JSONL of
|
|
4339
|
+
{"query","expect":{"filenames":[...]},"passage"?,"tags"?}.
|
|
4340
|
+
|
|
4341
|
+
A RUN scores a set against one target: the set's own source by default,
|
|
4342
|
+
--source another (the parent of a sample, or a sample of the parent \u2014
|
|
4343
|
+
expectations resolve by content hash), --candidate the target's candidate
|
|
4344
|
+
version. Every query is a real search through the same code apps use, ten
|
|
4345
|
+
results deep, with explain on. Retrieval options override the target's
|
|
4346
|
+
live config for the run only: --mode, --rerank true|false, --rerank-model
|
|
4347
|
+
<id>, --candidates <n>, --hybrid true|false, --top-k <n>. Runs are capped
|
|
4348
|
+
at 2,000 queries and cost what 2,000 searches cost.
|
|
4349
|
+
|
|
4350
|
+
\`result\` prints the aggregates \u2014 recall@1/5/10, chunk recall, MRR,
|
|
4351
|
+
nDCG@10, latency p50/p95, cost per query, how often reranking ran, and
|
|
4352
|
+
which branch (dense, lexical, both) found the expected document, per tag
|
|
4353
|
+
too \u2014 plus \`--worst N\` for the queries that missed and what came back
|
|
4354
|
+
instead, or \`--queries\` for every row. \`compare\` puts two runs side by
|
|
4355
|
+
side with deltas and, when they share a set, per-query wins and losses.
|
|
4356
|
+
The output is JSON; the table is yours to write.
|
|
4357
|
+
|
|
4358
|
+
The workflow: sample the source, create a set on the sample, run it against
|
|
4359
|
+
the live version, revectorize with the change you want to test, run again
|
|
4360
|
+
with --candidate, compare, promote or drop.
|
|
4361
|
+
`;
|
|
4362
|
+
|
|
4363
|
+
// src/commands/dataSourceJobs.ts
|
|
4364
|
+
import fs4 from "fs";
|
|
4365
|
+
import path4 from "path";
|
|
4366
|
+
var dataSourceJobsSpecs = {
|
|
4367
|
+
"datasources jobs start": {
|
|
4368
|
+
usage: "Usage: remy-admin datasources jobs start [--source <slug>] (--store <name> [--access <private|public>] [--prefix <p>] | --manifest <file.jsonl>) [--limit <n>] [--budget <dollars>] [--concurrency <n>] [--approve] [--wait] [--timeout <sec>]",
|
|
4369
|
+
flags: {
|
|
4370
|
+
source: { type: "string" },
|
|
4371
|
+
store: { type: "string" },
|
|
4372
|
+
access: { type: "string" },
|
|
4373
|
+
prefix: { type: "string" },
|
|
4374
|
+
manifest: { type: "string" },
|
|
4375
|
+
limit: { type: "number", min: 1 },
|
|
4376
|
+
budget: { type: "number", min: 0.01 },
|
|
4377
|
+
concurrency: { type: "number", min: 1 },
|
|
4378
|
+
approve: { type: "boolean" },
|
|
4379
|
+
wait: { type: "boolean" },
|
|
4380
|
+
timeout: { type: "string" }
|
|
4381
|
+
},
|
|
4382
|
+
requireAnyOf: {
|
|
4383
|
+
flags: ["store", "manifest"],
|
|
4384
|
+
message: "Give the job something to read: --store <name> [--prefix <p>], or --manifest <file.jsonl>."
|
|
4385
|
+
}
|
|
4386
|
+
},
|
|
4387
|
+
"datasources jobs list": {
|
|
4388
|
+
usage: "Usage: remy-admin datasources jobs list [--source <slug>]",
|
|
4389
|
+
flags: { source: { type: "string" } }
|
|
4390
|
+
},
|
|
4391
|
+
"datasources jobs status": {
|
|
4392
|
+
usage: "Usage: remy-admin datasources jobs status <id> [--wait] [--timeout <sec>]",
|
|
4393
|
+
positionals: [{ name: "id", required: true }],
|
|
4394
|
+
flags: { wait: { type: "boolean" }, timeout: { type: "string" } }
|
|
4395
|
+
},
|
|
4396
|
+
"datasources jobs approve": {
|
|
4397
|
+
usage: "Usage: remy-admin datasources jobs approve <id> [--wait] [--timeout <sec>]",
|
|
4398
|
+
positionals: [{ name: "id", required: true }],
|
|
4399
|
+
flags: { wait: { type: "boolean" }, timeout: { type: "string" } }
|
|
4400
|
+
},
|
|
4401
|
+
"datasources jobs pause": {
|
|
4402
|
+
usage: "Usage: remy-admin datasources jobs pause <id>",
|
|
4403
|
+
positionals: [{ name: "id", required: true }]
|
|
4404
|
+
},
|
|
4405
|
+
"datasources jobs resume": {
|
|
4406
|
+
usage: "Usage: remy-admin datasources jobs resume <id> [--wait] [--timeout <sec>]",
|
|
4407
|
+
positionals: [{ name: "id", required: true }],
|
|
4408
|
+
flags: { wait: { type: "boolean" }, timeout: { type: "string" } }
|
|
4409
|
+
},
|
|
4410
|
+
"datasources jobs cancel": {
|
|
4411
|
+
usage: "Usage: remy-admin datasources jobs cancel <id>",
|
|
4412
|
+
positionals: [{ name: "id", required: true }]
|
|
4413
|
+
}
|
|
4414
|
+
};
|
|
4415
|
+
async function jobsStart(ctx, a) {
|
|
4416
|
+
const slug = sourceOf(a);
|
|
4417
|
+
const store = a.str("store");
|
|
4418
|
+
const manifest = a.str("manifest");
|
|
4419
|
+
if (store && manifest) {
|
|
4420
|
+
fatal("Give --store or --manifest, not both.");
|
|
4421
|
+
}
|
|
4422
|
+
let source;
|
|
4423
|
+
if (manifest) {
|
|
4424
|
+
const abs = path4.isAbsolute(manifest) ? manifest : path4.join(WORKSPACE_DIR, manifest);
|
|
4425
|
+
let content;
|
|
4426
|
+
try {
|
|
4427
|
+
content = fs4.readFileSync(abs);
|
|
4428
|
+
} catch (err) {
|
|
4429
|
+
fatal(`Could not read manifest "${manifest}": ${err.message}`);
|
|
4430
|
+
}
|
|
4431
|
+
progress(`uploading manifest (${(content.length / 1024).toFixed(0)} KB)\u2026`);
|
|
4432
|
+
const { key } = await uploadManifest(ctx, { slug, content });
|
|
4433
|
+
source = { type: "manifest", key };
|
|
4434
|
+
} else {
|
|
4435
|
+
const access = a.str("access") ?? "private";
|
|
4436
|
+
if (access !== "private" && access !== "public") {
|
|
4437
|
+
fatal(`--access must be "private" or "public" (got "${access}").`);
|
|
4438
|
+
}
|
|
4439
|
+
source = {
|
|
4440
|
+
type: "store",
|
|
4441
|
+
store,
|
|
4442
|
+
access,
|
|
4443
|
+
prefix: a.str("prefix") ?? ""
|
|
4444
|
+
};
|
|
4445
|
+
}
|
|
4446
|
+
const budget = a.num("budget");
|
|
4447
|
+
const concurrency = a.num("concurrency");
|
|
4448
|
+
const limit = a.num("limit");
|
|
4449
|
+
const { job } = await start(ctx, {
|
|
4450
|
+
slug,
|
|
4451
|
+
source,
|
|
4452
|
+
...a.bool("approve") ? { approve: true } : {},
|
|
4453
|
+
...budget !== void 0 ? { budgetDollars: budget } : {},
|
|
4454
|
+
...concurrency !== void 0 ? { concurrency } : {},
|
|
4455
|
+
...limit !== void 0 ? { limit } : {}
|
|
4456
|
+
});
|
|
4457
|
+
if (!a.bool("wait")) {
|
|
4458
|
+
out({
|
|
4459
|
+
dataSource: slug,
|
|
4460
|
+
job,
|
|
4461
|
+
note: job.autoApprove ? "Planning, then running once the plan passes the gates. Poll `datasources jobs status <id>` or re-run with --wait." : "Planning. When the plan is ready, review it with `datasources jobs status <id>` and run `datasources jobs approve <id>`."
|
|
4462
|
+
});
|
|
4463
|
+
return;
|
|
4464
|
+
}
|
|
4465
|
+
await waitAndReport(ctx, a, job.id, job.autoApprove ? "job" : "plan", {
|
|
4466
|
+
dataSource: slug,
|
|
4467
|
+
action: "start",
|
|
4468
|
+
...job.autoApprove ? {} : { note: "Review the plan, then `datasources jobs approve <id>`." }
|
|
4469
|
+
});
|
|
4470
|
+
}
|
|
4471
|
+
async function jobsList(ctx, a) {
|
|
4472
|
+
const slug = a.str("source");
|
|
4473
|
+
out(await list9(ctx, slug ? { slug } : void 0));
|
|
4474
|
+
}
|
|
4475
|
+
async function jobsStatus(ctx, a) {
|
|
4476
|
+
const id = a.req("id");
|
|
4477
|
+
if (!a.bool("wait")) {
|
|
4478
|
+
out(await get7(ctx, { id }));
|
|
4479
|
+
return;
|
|
4480
|
+
}
|
|
4481
|
+
await waitAndReport(ctx, a, id, "job", { action: "status" });
|
|
4482
|
+
}
|
|
4483
|
+
async function jobsApprove(ctx, a) {
|
|
4484
|
+
const id = a.req("id");
|
|
4485
|
+
const { job } = await approve(ctx, { id });
|
|
4486
|
+
if (!a.bool("wait")) {
|
|
4487
|
+
out({
|
|
4488
|
+
job,
|
|
4489
|
+
note: "Running. Poll `datasources jobs status <id>` or re-run with --wait."
|
|
4490
|
+
});
|
|
4491
|
+
return;
|
|
4492
|
+
}
|
|
4493
|
+
await waitAndReport(ctx, a, id, "job", { action: "approve" });
|
|
4494
|
+
}
|
|
4495
|
+
async function jobsPause(ctx, a) {
|
|
4496
|
+
out(await pause(ctx, { id: a.req("id") }));
|
|
4497
|
+
}
|
|
4498
|
+
async function jobsResume(ctx, a) {
|
|
4499
|
+
const id = a.req("id");
|
|
4500
|
+
const { job } = await resume(ctx, { id });
|
|
4501
|
+
if (!a.bool("wait")) {
|
|
4502
|
+
out({ job });
|
|
4503
|
+
return;
|
|
4504
|
+
}
|
|
4505
|
+
await waitAndReport(ctx, a, id, "job", { action: "resume" });
|
|
4506
|
+
}
|
|
4507
|
+
async function jobsCancel(ctx, a) {
|
|
4508
|
+
out(await cancel(ctx, { id: a.req("id") }));
|
|
4509
|
+
}
|
|
4510
|
+
var dataSourceJobsHandlers = {
|
|
4511
|
+
"datasources jobs start": jobsStart,
|
|
4512
|
+
"datasources jobs list": jobsList,
|
|
4513
|
+
"datasources jobs status": jobsStatus,
|
|
4514
|
+
"datasources jobs approve": jobsApprove,
|
|
4515
|
+
"datasources jobs pause": jobsPause,
|
|
4516
|
+
"datasources jobs resume": jobsResume,
|
|
4517
|
+
"datasources jobs cancel": jobsCancel
|
|
4518
|
+
};
|
|
4519
|
+
var dataSourceJobsHelp = `
|
|
4520
|
+
Bulk ingestion (jobs):
|
|
4521
|
+
remy-admin datasources jobs start [--source <slug>] --store <name> [--access <private|public>] [--prefix <p>] [options]
|
|
4522
|
+
remy-admin datasources jobs start [--source <slug>] --manifest <file.jsonl> [options]
|
|
4523
|
+
remy-admin datasources jobs list [--source <slug>]
|
|
4524
|
+
remy-admin datasources jobs status <id> [--wait] [--timeout <sec>]
|
|
4525
|
+
remy-admin datasources jobs approve <id> [--wait] [--timeout <sec>]
|
|
4526
|
+
remy-admin datasources jobs pause|resume|cancel <id>
|
|
4527
|
+
|
|
4528
|
+
A job loads a whole corpus at once. It reads either every object under a
|
|
4529
|
+
prefix of one of the app's file stores (\`files put\` fills one; an app can
|
|
4530
|
+
write one at runtime) or a manifest you upload: JSONL, one line per document,
|
|
4531
|
+
{"url": "https://\u2026", "filename": "x.pdf", "externalId": "\u2026", "metadata": {\u2026}}.
|
|
4532
|
+
filename, externalId and metadata are optional; the URL must be public.
|
|
4533
|
+
|
|
4534
|
+
Nothing is spent until you say so. The platform counts the source, reads a
|
|
4535
|
+
sample of about a hundred documents, and writes a plan: documents, chunks,
|
|
4536
|
+
cost per stage (embedding, extraction, contextual) at today's rates, storage,
|
|
4537
|
+
whether it fits where the source lives, and a duration. \`jobs status\` shows
|
|
4538
|
+
it; \`jobs approve\` starts the run. Two gates: the corpus has to fit the
|
|
4539
|
+
source's placement (a shared-pool source over the per-source cap answers
|
|
4540
|
+
plan_requires_dedicated: place it on dedicated capacity first), and the
|
|
4541
|
+
workspace has to be able to cover the projection (insufficient_credits).
|
|
4542
|
+
|
|
4543
|
+
Options on start:
|
|
4544
|
+
--approve Run as soon as the plan passes the gates, no separate approve
|
|
4545
|
+
--budget <dollars> Pause when estimated spend reaches this (default 1.5x the plan)
|
|
4546
|
+
--concurrency <n> Batches in flight at once (default 8, max 64)
|
|
4547
|
+
--limit <n> Only the first N objects: a cheap sample of the corpus
|
|
4548
|
+
--wait Block until the plan is ready (or, with --approve, until done)
|
|
4549
|
+
|
|
4550
|
+
While running, documents are read in batches of fifty with one embedding
|
|
4551
|
+
call per batch. Unchanged documents are skipped by content hash, so re-running
|
|
4552
|
+
a job over the same source costs nothing new. Per-document failures are kept
|
|
4553
|
+
on the job (\`jobs status\` lists the last twenty) and the run continues; five
|
|
4554
|
+
failed batches in a row pause it, as does the budget. \`jobs resume\` continues
|
|
4555
|
+
from the checkpoint and retries failed batches; \`jobs cancel\` stops it and
|
|
4556
|
+
keeps what was indexed. Search works on the partial corpus throughout.
|
|
4557
|
+
|
|
4558
|
+
One bulk operation per source: a job refuses to start during a move or while
|
|
4559
|
+
a candidate version exists, and moves, re-vectorizes and deletes refuse while
|
|
4560
|
+
a job is in flight (data_source_busy). Interactive \`add\` keeps working.
|
|
4561
|
+
|
|
4562
|
+
--wait exit codes: 0 done \xB7 1 the job failed, paused, was cancelled, or sits
|
|
4563
|
+
in planned because a gate refused it (approve once the cause is fixed) \xB7
|
|
4564
|
+
2 still running when --timeout elapsed (default 3600s).
|
|
4565
|
+
`;
|
|
4566
|
+
|
|
4567
|
+
// src/ops/dataSourceMappers.ts
|
|
4568
|
+
function base5(appId) {
|
|
4569
|
+
return `/_internal/v2/apps/${appId}/datasources`;
|
|
4570
|
+
}
|
|
4571
|
+
function selectionBody(selection) {
|
|
4572
|
+
if ("connector" in selection) {
|
|
4573
|
+
return { connector: true };
|
|
4574
|
+
}
|
|
4575
|
+
return {
|
|
4576
|
+
store: selection.store,
|
|
4577
|
+
...selection.access ? { access: selection.access } : {},
|
|
4578
|
+
...selection.prefix !== void 0 ? { prefix: selection.prefix } : {}
|
|
4579
|
+
};
|
|
4580
|
+
}
|
|
4581
|
+
function inspect(ctx, params) {
|
|
3139
4582
|
return call(
|
|
3140
4583
|
ctx,
|
|
3141
4584
|
"POST",
|
|
3142
|
-
`${
|
|
4585
|
+
`${base5(ctx.appId)}/inspect`,
|
|
3143
4586
|
{
|
|
3144
|
-
slug,
|
|
3145
|
-
...
|
|
4587
|
+
...params.slug ? { slug: params.slug } : {},
|
|
4588
|
+
...selectionBody(params.selection),
|
|
4589
|
+
...params.keys ? { keys: params.keys } : {},
|
|
4590
|
+
...params.limit !== void 0 ? { limit: params.limit } : {}
|
|
3146
4591
|
}
|
|
3147
4592
|
);
|
|
3148
4593
|
}
|
|
3149
|
-
function
|
|
4594
|
+
function mapTest(ctx, params) {
|
|
3150
4595
|
return call(
|
|
3151
4596
|
ctx,
|
|
3152
4597
|
"POST",
|
|
3153
|
-
`${
|
|
4598
|
+
`${base5(ctx.appId)}/map-test`,
|
|
3154
4599
|
{
|
|
3155
4600
|
slug: params.slug,
|
|
3156
|
-
...params.
|
|
4601
|
+
...selectionBody(params.selection),
|
|
4602
|
+
...params.keys ? { keys: params.keys } : {},
|
|
4603
|
+
...params.limit !== void 0 ? { limit: params.limit } : {},
|
|
4604
|
+
...params.dev ? { dev: true } : {}
|
|
3157
4605
|
}
|
|
3158
4606
|
);
|
|
3159
4607
|
}
|
|
3160
|
-
function
|
|
4608
|
+
function mapDeploy(ctx, params) {
|
|
3161
4609
|
return call(
|
|
3162
4610
|
ctx,
|
|
3163
4611
|
"POST",
|
|
3164
|
-
`${
|
|
4612
|
+
`${base5(ctx.appId)}/mapper/deploy`,
|
|
3165
4613
|
{
|
|
3166
4614
|
slug: params.slug,
|
|
3167
|
-
...params.
|
|
4615
|
+
...params.releaseId ? { releaseId: params.releaseId } : {}
|
|
3168
4616
|
}
|
|
3169
4617
|
);
|
|
3170
4618
|
}
|
|
3171
|
-
function
|
|
4619
|
+
function remap(ctx, params) {
|
|
4620
|
+
return call(ctx, "POST", `${base5(ctx.appId)}/remap`, {
|
|
4621
|
+
slug: params.slug,
|
|
4622
|
+
...params.budgetDollars !== void 0 ? { budgetDollars: params.budgetDollars } : {},
|
|
4623
|
+
...params.limit !== void 0 ? { limit: params.limit } : {}
|
|
4624
|
+
});
|
|
4625
|
+
}
|
|
4626
|
+
function quarantine(ctx, params) {
|
|
3172
4627
|
return call(
|
|
3173
4628
|
ctx,
|
|
3174
|
-
"
|
|
3175
|
-
`${
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
4629
|
+
"GET",
|
|
4630
|
+
`${base5(ctx.appId)}/jobs/${params.id}/quarantine${qs({
|
|
4631
|
+
...params.kind ? { kind: params.kind } : {},
|
|
4632
|
+
...params.reason ? { reason: params.reason } : {},
|
|
4633
|
+
...params.cursor ? { cursor: params.cursor } : {},
|
|
4634
|
+
...params.limit !== void 0 ? { limit: params.limit } : {}
|
|
4635
|
+
})}`
|
|
3180
4636
|
);
|
|
3181
4637
|
}
|
|
3182
|
-
function
|
|
4638
|
+
function replay(ctx, params) {
|
|
3183
4639
|
return call(
|
|
3184
4640
|
ctx,
|
|
3185
4641
|
"POST",
|
|
3186
|
-
`${
|
|
3187
|
-
{
|
|
4642
|
+
`${base5(ctx.appId)}/jobs/${params.id}/replay`,
|
|
4643
|
+
params.kind ? { kind: params.kind } : {}
|
|
4644
|
+
);
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4647
|
+
// src/commands/dataSourceMappers.ts
|
|
4648
|
+
var SELECTION_FLAGS = {
|
|
4649
|
+
store: { type: "string" },
|
|
4650
|
+
access: { type: "string" },
|
|
4651
|
+
prefix: { type: "string" },
|
|
4652
|
+
connector: { type: "boolean" },
|
|
4653
|
+
keys: { type: "string" }
|
|
4654
|
+
};
|
|
4655
|
+
var dataSourceMappersSpecs = {
|
|
4656
|
+
"datasources inspect": {
|
|
4657
|
+
usage: "Usage: remy-admin datasources inspect [--source <slug>] (--store <name> [--access <private|public>] [--prefix <p>] | --connector) [--keys k1,k2,...] [--limit <n>]",
|
|
4658
|
+
flags: {
|
|
4659
|
+
source: { type: "string" },
|
|
4660
|
+
...SELECTION_FLAGS,
|
|
4661
|
+
limit: { type: "number", min: 1 }
|
|
4662
|
+
},
|
|
4663
|
+
requireAnyOf: {
|
|
4664
|
+
flags: ["store", "connector"],
|
|
4665
|
+
message: "Name the objects: --store <name> [--prefix <p>], or --connector for the source's bucket."
|
|
4666
|
+
}
|
|
4667
|
+
},
|
|
4668
|
+
"datasources map test": {
|
|
4669
|
+
usage: "Usage: remy-admin datasources map test [--source <slug>] (--store <name> [--access <private|public>] [--prefix <p>] | --connector) [--keys k1,k2,...] [--limit <n>] [--dev] [--full]",
|
|
4670
|
+
flags: {
|
|
4671
|
+
source: { type: "string" },
|
|
4672
|
+
...SELECTION_FLAGS,
|
|
4673
|
+
limit: { type: "number", min: 1 },
|
|
4674
|
+
dev: { type: "boolean" },
|
|
4675
|
+
full: { type: "boolean" }
|
|
4676
|
+
},
|
|
4677
|
+
requireAnyOf: {
|
|
4678
|
+
flags: ["store", "connector"],
|
|
4679
|
+
message: "Name the objects: --store <name> [--prefix <p>], or --connector for the source's bucket."
|
|
4680
|
+
}
|
|
4681
|
+
},
|
|
4682
|
+
"datasources map deploy": {
|
|
4683
|
+
usage: "Usage: remy-admin datasources map deploy [--source <slug>] [--release <id>]",
|
|
4684
|
+
flags: {
|
|
4685
|
+
source: { type: "string" },
|
|
4686
|
+
release: { type: "string" }
|
|
4687
|
+
}
|
|
4688
|
+
},
|
|
4689
|
+
"datasources remap": {
|
|
4690
|
+
usage: "Usage: remy-admin datasources remap [--source <slug>] [--budget <dollars>] [--limit <n>] [--wait] [--timeout <sec>]",
|
|
4691
|
+
flags: {
|
|
4692
|
+
source: { type: "string" },
|
|
4693
|
+
budget: { type: "number", min: 0.01 },
|
|
4694
|
+
limit: { type: "number", min: 1 },
|
|
4695
|
+
wait: { type: "boolean" },
|
|
4696
|
+
timeout: { type: "string" }
|
|
4697
|
+
}
|
|
4698
|
+
},
|
|
4699
|
+
"datasources jobs quarantine": {
|
|
4700
|
+
usage: "Usage: remy-admin datasources jobs quarantine <id> [--kind skip|error] [--reason <text>] [--cursor <id>] [--limit <n>]",
|
|
4701
|
+
positionals: [{ name: "id", required: true }],
|
|
4702
|
+
flags: {
|
|
4703
|
+
kind: { type: "string" },
|
|
4704
|
+
reason: { type: "string" },
|
|
4705
|
+
cursor: { type: "string" },
|
|
4706
|
+
limit: { type: "number", min: 1 }
|
|
4707
|
+
}
|
|
4708
|
+
},
|
|
4709
|
+
"datasources jobs replay": {
|
|
4710
|
+
usage: "Usage: remy-admin datasources jobs replay <id> [--kind skip|error] [--wait] [--timeout <sec>]",
|
|
4711
|
+
positionals: [{ name: "id", required: true }],
|
|
4712
|
+
flags: {
|
|
4713
|
+
kind: { type: "string" },
|
|
4714
|
+
wait: { type: "boolean" },
|
|
4715
|
+
timeout: { type: "string" }
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4718
|
+
};
|
|
4719
|
+
function selectionOf(a) {
|
|
4720
|
+
const store = a.str("store");
|
|
4721
|
+
if (a.bool("connector") && store) {
|
|
4722
|
+
fatal("Give --store or --connector, not both.");
|
|
4723
|
+
}
|
|
4724
|
+
if (a.bool("connector")) {
|
|
4725
|
+
return { connector: true };
|
|
4726
|
+
}
|
|
4727
|
+
const access = a.str("access") ?? "private";
|
|
4728
|
+
if (access !== "private" && access !== "public") {
|
|
4729
|
+
fatal(`--access must be "private" or "public" (got "${access}").`);
|
|
4730
|
+
}
|
|
4731
|
+
return {
|
|
4732
|
+
store,
|
|
4733
|
+
access,
|
|
4734
|
+
...a.str("prefix") !== void 0 ? { prefix: a.str("prefix") } : {}
|
|
4735
|
+
};
|
|
4736
|
+
}
|
|
4737
|
+
function keysOf(a) {
|
|
4738
|
+
const raw = a.str("keys");
|
|
4739
|
+
if (!raw) {
|
|
4740
|
+
return void 0;
|
|
4741
|
+
}
|
|
4742
|
+
const keys = raw.split(",").map((k) => k.trim()).filter(Boolean);
|
|
4743
|
+
if (keys.length === 0) {
|
|
4744
|
+
fatal("--keys needs at least one key.");
|
|
4745
|
+
}
|
|
4746
|
+
return keys;
|
|
4747
|
+
}
|
|
4748
|
+
function kindOf(a) {
|
|
4749
|
+
const kind = a.str("kind");
|
|
4750
|
+
if (kind === void 0) {
|
|
4751
|
+
return void 0;
|
|
4752
|
+
}
|
|
4753
|
+
if (kind !== "skip" && kind !== "error") {
|
|
4754
|
+
fatal(`--kind must be "skip" or "error" (got "${kind}").`);
|
|
4755
|
+
}
|
|
4756
|
+
return kind;
|
|
4757
|
+
}
|
|
4758
|
+
async function inspect2(ctx, a) {
|
|
4759
|
+
const slug = a.str("source");
|
|
4760
|
+
const selection = selectionOf(a);
|
|
4761
|
+
if ("connector" in selection && !slug) {
|
|
4762
|
+
fatal("--connector needs --source: the bucket is the source's.");
|
|
4763
|
+
}
|
|
4764
|
+
const limit = a.num("limit");
|
|
4765
|
+
const result = await inspect(ctx, {
|
|
4766
|
+
...slug ? { slug } : {},
|
|
4767
|
+
selection,
|
|
4768
|
+
...keysOf(a) ? { keys: keysOf(a) } : {},
|
|
4769
|
+
...limit !== void 0 ? { limit } : {}
|
|
4770
|
+
});
|
|
4771
|
+
out({
|
|
4772
|
+
...slug ? { dataSource: slug } : {},
|
|
4773
|
+
...result,
|
|
4774
|
+
note: result.report.truncated ? `Listed the first ${result.report.objects.toLocaleString()} objects; raise --limit to see more of the shape.` : `Listed every object (${result.report.objects.toLocaleString()}).`
|
|
4775
|
+
});
|
|
4776
|
+
}
|
|
4777
|
+
async function mapTest2(ctx, a) {
|
|
4778
|
+
const slug = sourceOf(a);
|
|
4779
|
+
const limit = a.num("limit");
|
|
4780
|
+
const result = await mapTest(ctx, {
|
|
4781
|
+
slug,
|
|
4782
|
+
selection: selectionOf(a),
|
|
4783
|
+
...keysOf(a) ? { keys: keysOf(a) } : {},
|
|
4784
|
+
...limit !== void 0 ? { limit } : {},
|
|
4785
|
+
...a.bool("dev") ? { dev: true } : {}
|
|
4786
|
+
});
|
|
4787
|
+
const results = a.bool("full") ? result.results : result.results.map(
|
|
4788
|
+
(r) => r.outcome.kind === "documents" ? {
|
|
4789
|
+
...r,
|
|
4790
|
+
outcome: {
|
|
4791
|
+
...r.outcome,
|
|
4792
|
+
documents: r.outcome.documents.map((d) => ({
|
|
4793
|
+
...d,
|
|
4794
|
+
preview: d.preview.slice(0, 200)
|
|
4795
|
+
}))
|
|
4796
|
+
}
|
|
4797
|
+
} : r
|
|
3188
4798
|
);
|
|
4799
|
+
out({
|
|
4800
|
+
dataSource: slug,
|
|
4801
|
+
...result,
|
|
4802
|
+
results,
|
|
4803
|
+
note: result.dev ? "Ran the LOCAL mapper through the dev session. Nothing was ingested; push a branch, wait for its build, then `datasources map deploy` to make it the source's mapper." : "Ran the source's active mapper. Nothing was ingested."
|
|
4804
|
+
});
|
|
4805
|
+
if (result.counts.error > 0) {
|
|
4806
|
+
throw new CliError(
|
|
4807
|
+
`${result.counts.error} of ${result.objects} object(s) errored in the mapper.`,
|
|
4808
|
+
EXIT.buildFailed
|
|
4809
|
+
);
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
async function mapDeploy2(ctx, a) {
|
|
4813
|
+
const slug = sourceOf(a);
|
|
4814
|
+
const releaseId = a.str("release");
|
|
4815
|
+
const result = await mapDeploy(ctx, {
|
|
4816
|
+
slug,
|
|
4817
|
+
...releaseId ? { releaseId } : {}
|
|
4818
|
+
});
|
|
4819
|
+
const { mapper } = result;
|
|
4820
|
+
const from = `${mapper.branch ?? "release"} @ ${(mapper.commitSha ?? "").slice(0, 7)}`;
|
|
4821
|
+
out({
|
|
4822
|
+
dataSource: slug,
|
|
4823
|
+
mapper,
|
|
4824
|
+
changed: result.changed,
|
|
4825
|
+
note: result.changed ? `Active: the mapper compiled by ${from}. Jobs, syncs and add() run it from now on; \`remap\` re-applies it to what is already loaded.` : `Already active: the mapper compiled by ${from}. Nothing changed.`
|
|
4826
|
+
});
|
|
4827
|
+
}
|
|
4828
|
+
async function remap2(ctx, a) {
|
|
4829
|
+
const slug = sourceOf(a);
|
|
4830
|
+
const budget = a.num("budget");
|
|
4831
|
+
const limit = a.num("limit");
|
|
4832
|
+
const { job, mapper } = await remap(ctx, {
|
|
4833
|
+
slug,
|
|
4834
|
+
...budget !== void 0 ? { budgetDollars: budget } : {},
|
|
4835
|
+
...limit !== void 0 ? { limit } : {}
|
|
4836
|
+
});
|
|
4837
|
+
if (!a.bool("wait")) {
|
|
4838
|
+
out({
|
|
4839
|
+
dataSource: slug,
|
|
4840
|
+
job,
|
|
4841
|
+
mapper,
|
|
4842
|
+
note: "Remapping from the raw copies. Poll `datasources jobs status <id>`, or re-run with --wait. Unchanged documents are skipped by hash; changed ones supersede their predecessors."
|
|
4843
|
+
});
|
|
4844
|
+
return;
|
|
4845
|
+
}
|
|
4846
|
+
await waitAndReport(ctx, a, job.id, "job", {
|
|
4847
|
+
dataSource: slug,
|
|
4848
|
+
action: "remap",
|
|
4849
|
+
mapper
|
|
4850
|
+
});
|
|
4851
|
+
}
|
|
4852
|
+
async function quarantine2(ctx, a) {
|
|
4853
|
+
const id = a.req("id");
|
|
4854
|
+
const limit = a.num("limit");
|
|
4855
|
+
const kind = kindOf(a);
|
|
4856
|
+
const reason = a.str("reason");
|
|
4857
|
+
const cursor = a.str("cursor");
|
|
4858
|
+
const result = await quarantine(ctx, {
|
|
4859
|
+
id,
|
|
4860
|
+
...kind ? { kind } : {},
|
|
4861
|
+
...reason ? { reason } : {},
|
|
4862
|
+
...cursor ? { cursor } : {},
|
|
4863
|
+
...limit !== void 0 ? { limit } : {}
|
|
4864
|
+
});
|
|
4865
|
+
out({
|
|
4866
|
+
...result,
|
|
4867
|
+
note: result.counts.skipped + result.counts.errors === 0 ? "Nothing quarantined: the mapper produced documents (or deletions) for every object." : `Fix the mapper, push, \`datasources map deploy\`, then \`datasources jobs replay ${id}\` to run these through it again.`
|
|
4868
|
+
});
|
|
4869
|
+
}
|
|
4870
|
+
async function replay2(ctx, a) {
|
|
4871
|
+
const id = a.req("id");
|
|
4872
|
+
const kind = kindOf(a);
|
|
4873
|
+
const { job, mapper, dataSource } = await replay(ctx, {
|
|
4874
|
+
id,
|
|
4875
|
+
...kind ? { kind } : {}
|
|
4876
|
+
});
|
|
4877
|
+
if (!a.bool("wait")) {
|
|
4878
|
+
out({
|
|
4879
|
+
dataSource: dataSource.slug,
|
|
4880
|
+
fromJob: id,
|
|
4881
|
+
job,
|
|
4882
|
+
mapper,
|
|
4883
|
+
note: "Replaying the quarantined objects through the current mapper. Poll `datasources jobs status <id>`, or re-run with --wait."
|
|
4884
|
+
});
|
|
4885
|
+
return;
|
|
4886
|
+
}
|
|
4887
|
+
await waitAndReport(ctx, a, job.id, "job", {
|
|
4888
|
+
dataSource: dataSource.slug,
|
|
4889
|
+
fromJob: id,
|
|
4890
|
+
action: "replay",
|
|
4891
|
+
mapper
|
|
4892
|
+
});
|
|
3189
4893
|
}
|
|
4894
|
+
var dataSourceMappersHandlers = {
|
|
4895
|
+
"datasources inspect": inspect2,
|
|
4896
|
+
"datasources map test": mapTest2,
|
|
4897
|
+
"datasources map deploy": mapDeploy2,
|
|
4898
|
+
"datasources remap": remap2,
|
|
4899
|
+
"datasources jobs quarantine": quarantine2,
|
|
4900
|
+
"datasources jobs replay": replay2
|
|
4901
|
+
};
|
|
4902
|
+
var dataSourceMappersHelp = `
|
|
4903
|
+
Mapping raw objects into documents (mappers):
|
|
4904
|
+
remy-admin datasources inspect [--source <slug>] (--store <name> [--prefix <p>] | --connector) [--keys k1,k2] [--limit <n>]
|
|
4905
|
+
remy-admin datasources map test [--source <slug>] (--store <name> [--prefix <p>] | --connector) [--keys k1,k2] [--limit <n>] [--dev] [--full]
|
|
4906
|
+
remy-admin datasources map deploy [--source <slug>] [--release <id>]
|
|
4907
|
+
remy-admin datasources remap [--source <slug>] [--budget <dollars>] [--limit <n>] [--wait] [--timeout <sec>]
|
|
4908
|
+
remy-admin datasources jobs quarantine <id> [--kind skip|error] [--reason <text>] [--cursor <id>] [--limit <n>]
|
|
4909
|
+
remy-admin datasources jobs replay <id> [--kind skip|error] [--wait] [--timeout <sec>]
|
|
4910
|
+
|
|
4911
|
+
A source takes every object it is given as one document. When the file is
|
|
4912
|
+
not the document \u2014 JSON records that should become markdown plus metadata,
|
|
4913
|
+
a JSONL object holding many articles, a kill notice that means "remove this
|
|
4914
|
+
story" \u2014 the app declares a MAPPER for the source in mindstudio.json:
|
|
4915
|
+
"dataSources": [{ "slug": "archive", "mapper": { "path": "dist/datasources/archive.mapper.ts" } }]
|
|
4916
|
+
The file exports defineMapper(Archive, { map }) from @mindstudio-ai/agent.
|
|
4917
|
+
map(object) reads the raw object (object.key, .bytes(), .text(), .json())
|
|
4918
|
+
and answers documents([{ externalId, title, markdown, metadata?, replaces? }]),
|
|
4919
|
+
passthrough() (ingest the raw object as-is), skip(reason) or
|
|
4920
|
+
deletes([externalId, ...]); throwing is the object's error. It may call
|
|
4921
|
+
models or fetch \u2014 every call is spend per object. Everything entering a
|
|
4922
|
+
mapped source is mapped: jobs, syncs and Source.add() alike.
|
|
4923
|
+
|
|
4924
|
+
The loop: \`inspect\` profiles the objects (key shapes, extensions, sizes,
|
|
4925
|
+
JSON key signatures of ten sampled heads) before a line is written;
|
|
4926
|
+
\`map test --dev\` runs the LOCAL mapper through the running dev session
|
|
4927
|
+
over real objects and prints every outcome without ingesting anything;
|
|
4928
|
+
push a branch and wait for its build (\`releases wait\`) \u2014 a mapper runs on
|
|
4929
|
+
the platform, so the platform has to compile it, and a private branch
|
|
4930
|
+
build is all it needs; \`map deploy\` makes that build's mapper the
|
|
4931
|
+
source's active one (publishing to main activates the mapper main declares
|
|
4932
|
+
too, so merge the branch before publishing); a job or sync runs it; \`jobs
|
|
4933
|
+
quarantine <id>\` lists what it skipped or failed on, by reason; fix, push,
|
|
4934
|
+
\`map deploy\`, \`jobs replay <id>\` runs just those objects again; \`remap\`
|
|
4935
|
+
re-applies a changed mapper to every raw copy the source holds \u2014 no origin
|
|
4936
|
+
traffic, unchanged markdown skipped by hash, changed documents superseding
|
|
4937
|
+
their predecessors. \`jobs start\` refuses (mapper_not_deployed) while the
|
|
4938
|
+
dev session declares a mapper that is not yet active: the job would have
|
|
4939
|
+
loaded the raw records as documents.
|
|
4940
|
+
|
|
4941
|
+
externalId is the identity the platform replaces by: a later object
|
|
4942
|
+
producing the same id supersedes the earlier document; \`replaces\` names
|
|
4943
|
+
ids this document supersedes (a writethru's original); deletes([...])
|
|
4944
|
+
removes by id. A plan on a mapped source records the mapper's outcome mix
|
|
4945
|
+
on its sample; a run whose skip share climbs past twice that (floor 5%)
|
|
4946
|
+
pauses with pauseReason 'skips' for a look at the quarantine.
|
|
4947
|
+
`;
|
|
3190
4948
|
|
|
3191
4949
|
// src/commands/dataSources.ts
|
|
3192
|
-
var DEFAULT_SOURCE = "default";
|
|
3193
|
-
var POLL_MS3 = 3e3;
|
|
3194
4950
|
var INGEST_FLAGS = {
|
|
3195
4951
|
chunking: { type: "string" },
|
|
3196
4952
|
"max-chars": { type: "number", min: 200 },
|
|
@@ -3237,8 +4993,16 @@ var dataSourcesSpecs = {
|
|
|
3237
4993
|
flags: { source: { type: "string" } }
|
|
3238
4994
|
},
|
|
3239
4995
|
"datasources rm": {
|
|
3240
|
-
usage: "Usage: remy-admin datasources rm [--source <slug>] --document <id>",
|
|
3241
|
-
flags: {
|
|
4996
|
+
usage: "Usage: remy-admin datasources rm [--source <slug>] (--document <id> | --filter <k=v,...|json>)",
|
|
4997
|
+
flags: {
|
|
4998
|
+
source: { type: "string" },
|
|
4999
|
+
document: { type: "string" },
|
|
5000
|
+
filter: { type: "string" }
|
|
5001
|
+
},
|
|
5002
|
+
requireAnyOf: {
|
|
5003
|
+
flags: ["document", "filter"],
|
|
5004
|
+
message: "Say what to remove: --document <id> for one document, or --filter <k=v,...|json> for every document that matches."
|
|
5005
|
+
}
|
|
3242
5006
|
},
|
|
3243
5007
|
"datasources search": {
|
|
3244
5008
|
usage: "Usage: remy-admin datasources search [--source <slug>] [--top-k <n>] [--mode <hybrid|semantic|lexical>] [--filter <k=v,...|json>] [--phrase <text>] [--contains <words>] [--max-per-document <n>] [--rerank <true|false>] [--hybrid <true|false>] [--highlight] [--candidate] <query>",
|
|
@@ -3311,21 +5075,20 @@ var dataSourcesSpecs = {
|
|
|
3311
5075
|
flags: { source: { type: "string" } }
|
|
3312
5076
|
}
|
|
3313
5077
|
};
|
|
3314
|
-
var sourceOf = (a) => a.str("source") || DEFAULT_SOURCE;
|
|
3315
5078
|
async function dataSourcesAdd(ctx, a) {
|
|
3316
5079
|
const files = a.rest("file");
|
|
3317
5080
|
const slug = sourceOf(a);
|
|
3318
5081
|
const metadata = parseKeyValuePairs(a.str("metadata"), "metadata");
|
|
3319
5082
|
const results = [];
|
|
3320
5083
|
for (const file of files) {
|
|
3321
|
-
const abs =
|
|
5084
|
+
const abs = path5.isAbsolute(file) ? file : path5.join(WORKSPACE_DIR, file);
|
|
3322
5085
|
let bytes;
|
|
3323
5086
|
try {
|
|
3324
|
-
bytes =
|
|
5087
|
+
bytes = fs5.readFileSync(abs);
|
|
3325
5088
|
} catch (err) {
|
|
3326
5089
|
fatal(`Could not read file "${file}": ${err.message}`);
|
|
3327
5090
|
}
|
|
3328
|
-
const filename =
|
|
5091
|
+
const filename = path5.basename(file);
|
|
3329
5092
|
results.push(
|
|
3330
5093
|
await addDocument(ctx, {
|
|
3331
5094
|
slug,
|
|
@@ -3341,32 +5104,24 @@ async function dataSourcesAdd(ctx, a) {
|
|
|
3341
5104
|
const timeoutMs = timeoutSec ? timeoutSec * 1e3 : DEFAULT_WAIT_TIMEOUT_MS;
|
|
3342
5105
|
const result = await waitForIngest(ctx, {
|
|
3343
5106
|
slug,
|
|
3344
|
-
|
|
5107
|
+
// Every document the files became: a mapped source can make several
|
|
5108
|
+
// from one file, or none (a deletes outcome).
|
|
5109
|
+
documentIds: results.filter((r) => !r.skipped).flatMap((r) => r.documents.map((d) => d.id)),
|
|
3345
5110
|
timeoutMs,
|
|
3346
5111
|
onProgress: progress
|
|
3347
5112
|
});
|
|
3348
|
-
if (result.status === "timeout") {
|
|
3349
|
-
out({
|
|
3350
|
-
dataSource: slug,
|
|
3351
|
-
status: result.status,
|
|
3352
|
-
error: result.error,
|
|
3353
|
-
documents: result.documents
|
|
3354
|
-
});
|
|
3355
|
-
process.exit(EXIT.timeout);
|
|
3356
|
-
}
|
|
3357
|
-
if (result.status === "error") {
|
|
3358
|
-
out({
|
|
3359
|
-
dataSource: slug,
|
|
3360
|
-
status: result.status,
|
|
3361
|
-
documents: result.documents
|
|
3362
|
-
});
|
|
3363
|
-
process.exit(EXIT.buildFailed);
|
|
3364
|
-
}
|
|
3365
5113
|
out({
|
|
3366
5114
|
dataSource: slug,
|
|
3367
5115
|
status: result.status,
|
|
5116
|
+
...result.error ? { error: result.error } : {},
|
|
3368
5117
|
documents: result.documents
|
|
3369
5118
|
});
|
|
5119
|
+
if (result.status === "timeout") {
|
|
5120
|
+
throw new CliError(result.error ?? "Timed out.", EXIT.timeout);
|
|
5121
|
+
}
|
|
5122
|
+
if (result.status === "error") {
|
|
5123
|
+
throw new CliError("At least one document failed.", EXIT.buildFailed);
|
|
5124
|
+
}
|
|
3370
5125
|
return;
|
|
3371
5126
|
}
|
|
3372
5127
|
out({ dataSource: slug, documents: results });
|
|
@@ -3384,11 +5139,32 @@ async function dataSourcesStatus(ctx, a) {
|
|
|
3384
5139
|
}
|
|
3385
5140
|
async function dataSourcesRm(ctx, a) {
|
|
3386
5141
|
const documentId = a.str("document");
|
|
3387
|
-
|
|
3388
|
-
|
|
5142
|
+
const rawFilter = a.str("filter");
|
|
5143
|
+
if (documentId && rawFilter) {
|
|
5144
|
+
fatal("Give --document or --filter, not both.");
|
|
5145
|
+
}
|
|
5146
|
+
if (documentId) {
|
|
5147
|
+
await rm(ctx, { slug: sourceOf(a), documentId });
|
|
5148
|
+
out({ deleted: documentId });
|
|
5149
|
+
return;
|
|
5150
|
+
}
|
|
5151
|
+
let filter;
|
|
5152
|
+
if (rawFilter.trimStart().startsWith("{")) {
|
|
5153
|
+
try {
|
|
5154
|
+
filter = JSON.parse(rawFilter);
|
|
5155
|
+
} catch (err) {
|
|
5156
|
+
fatal(`--filter is not valid JSON: ${err.message}`);
|
|
5157
|
+
}
|
|
5158
|
+
} else {
|
|
5159
|
+
filter = { metadata: parseKeyValuePairs(rawFilter, "filter") };
|
|
3389
5160
|
}
|
|
3390
|
-
|
|
3391
|
-
|
|
5161
|
+
const slug = sourceOf(a);
|
|
5162
|
+
const result = await rmWhereAll(ctx, {
|
|
5163
|
+
slug,
|
|
5164
|
+
filter,
|
|
5165
|
+
onProgress: progress
|
|
5166
|
+
});
|
|
5167
|
+
out({ dataSource: slug, filter, ...result });
|
|
3392
5168
|
}
|
|
3393
5169
|
async function dataSourcesDelete(ctx, a) {
|
|
3394
5170
|
const slug = a.str("source");
|
|
@@ -3403,8 +5179,8 @@ async function dataSourcesDelete(ctx, a) {
|
|
|
3403
5179
|
async function dataSourcesSearch(ctx, a) {
|
|
3404
5180
|
const topK = a.num("top-k");
|
|
3405
5181
|
const retrieval = {
|
|
3406
|
-
...
|
|
3407
|
-
...
|
|
5182
|
+
...triState2(a, "rerank", "rerank"),
|
|
5183
|
+
...triState2(a, "hybrid", "hybrid")
|
|
3408
5184
|
};
|
|
3409
5185
|
const mode = a.str("mode");
|
|
3410
5186
|
if (mode && !["hybrid", "semantic", "lexical"].includes(mode)) {
|
|
@@ -3476,7 +5252,7 @@ function parseKeyValuePairs(raw, flag) {
|
|
|
3476
5252
|
}
|
|
3477
5253
|
return pairs2;
|
|
3478
5254
|
}
|
|
3479
|
-
function
|
|
5255
|
+
function triState2(a, flag, key) {
|
|
3480
5256
|
const raw = a.str(flag);
|
|
3481
5257
|
if (raw === void 0) {
|
|
3482
5258
|
return {};
|
|
@@ -3508,11 +5284,11 @@ function configFromFlags(a) {
|
|
|
3508
5284
|
chunking.dropBlockTypes = dropBlocks.split(",").map((s) => s.trim()).filter(Boolean);
|
|
3509
5285
|
}
|
|
3510
5286
|
const contextual = {
|
|
3511
|
-
...
|
|
5287
|
+
...triState2(a, "contextual", "enabled"),
|
|
3512
5288
|
...a.str("contextual-model") ? { modelId: a.str("contextual-model") } : {}
|
|
3513
5289
|
};
|
|
3514
5290
|
const images = {
|
|
3515
|
-
...
|
|
5291
|
+
...triState2(a, "describe-images", "describe"),
|
|
3516
5292
|
...a.str("image-model") ? { modelId: a.str("image-model") } : {}
|
|
3517
5293
|
};
|
|
3518
5294
|
const embeddingModel = a.str("embedding-model");
|
|
@@ -3538,10 +5314,10 @@ function configFromFlags(a) {
|
|
|
3538
5314
|
ingest.extraction = { modelId: extractionModel };
|
|
3539
5315
|
}
|
|
3540
5316
|
const retrieval = {
|
|
3541
|
-
...
|
|
5317
|
+
...triState2(a, "hybrid", "hybrid")
|
|
3542
5318
|
};
|
|
3543
5319
|
const rerank = {
|
|
3544
|
-
...
|
|
5320
|
+
...triState2(a, "rerank", "enabled"),
|
|
3545
5321
|
...a.str("rerank-model") ? { modelId: a.str("rerank-model") } : {}
|
|
3546
5322
|
};
|
|
3547
5323
|
if (Object.keys(rerank).length) {
|
|
@@ -3623,10 +5399,10 @@ async function dataSourcesMove(ctx, a) {
|
|
|
3623
5399
|
..."error" in result ? { error: result.error } : {}
|
|
3624
5400
|
});
|
|
3625
5401
|
if (result.status === "failed") {
|
|
3626
|
-
|
|
5402
|
+
throw new CliError(result.error, EXIT.buildFailed);
|
|
3627
5403
|
}
|
|
3628
5404
|
if (result.status === "timeout") {
|
|
3629
|
-
|
|
5405
|
+
throw new CliError(result.error, EXIT.timeout);
|
|
3630
5406
|
}
|
|
3631
5407
|
}
|
|
3632
5408
|
async function dataSourcesRevectorize(ctx, a) {
|
|
@@ -3641,42 +5417,24 @@ async function dataSourcesRevectorize(ctx, a) {
|
|
|
3641
5417
|
return;
|
|
3642
5418
|
}
|
|
3643
5419
|
const timeoutSec = a.num("timeout");
|
|
3644
|
-
const
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
});
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
process.exit(EXIT.buildFailed);
|
|
3663
|
-
}
|
|
3664
|
-
return;
|
|
3665
|
-
}
|
|
3666
|
-
if (Date.now() - start > timeoutMs) {
|
|
3667
|
-
out({
|
|
3668
|
-
dataSource: slug,
|
|
3669
|
-
status: "timeout",
|
|
3670
|
-
error: `Timed out after ${Math.round(timeoutMs / 1e3)}s with ${pending.length} document(s) still building`
|
|
3671
|
-
});
|
|
3672
|
-
process.exit(EXIT.timeout);
|
|
3673
|
-
}
|
|
3674
|
-
progress(
|
|
3675
|
-
`rebuilding\u2026 ${documents2.length - pending.length}/${documents2.length} done (${Math.round(
|
|
3676
|
-
(Date.now() - start) / 1e3
|
|
3677
|
-
)}s)`
|
|
3678
|
-
);
|
|
3679
|
-
await sleep(POLL_MS3);
|
|
5420
|
+
const result = await waitForCandidate(ctx, {
|
|
5421
|
+
slug,
|
|
5422
|
+
...timeoutSec ? { timeoutMs: timeoutSec * 1e3 } : {},
|
|
5423
|
+
onProgress: progress
|
|
5424
|
+
});
|
|
5425
|
+
if (result.status === "timeout") {
|
|
5426
|
+
out({ dataSource: slug, status: "timeout", error: result.error });
|
|
5427
|
+
throw new CliError(result.error ?? "Timed out.", EXIT.timeout);
|
|
5428
|
+
}
|
|
5429
|
+
out({
|
|
5430
|
+
dataSource: slug,
|
|
5431
|
+
candidateVersion: started.candidateVersion,
|
|
5432
|
+
status: result.status,
|
|
5433
|
+
documents: result.documents,
|
|
5434
|
+
note: result.status === "error" ? "Some documents failed. Promote with --force to accept, or fix and re-run." : "Run `promote` to make this version live."
|
|
5435
|
+
});
|
|
5436
|
+
if (result.status === "error") {
|
|
5437
|
+
throw new CliError("At least one document failed.", EXIT.buildFailed);
|
|
3680
5438
|
}
|
|
3681
5439
|
}
|
|
3682
5440
|
async function dataSourcesPromote(ctx, a) {
|
|
@@ -3719,11 +5477,23 @@ Subcommands:
|
|
|
3719
5477
|
add Add one or more documents (skips unchanged files)
|
|
3720
5478
|
list List data sources with document counts
|
|
3721
5479
|
status Show per-document ingest state
|
|
3722
|
-
rm Remove a document
|
|
5480
|
+
rm Remove a document, or every document matching a filter
|
|
3723
5481
|
search Query a corpus \u2014 useful to sanity-check one you just built
|
|
3724
5482
|
create Create an empty source, optionally on dedicated capacity
|
|
3725
5483
|
config Show or change how a corpus is processed and searched
|
|
3726
5484
|
move Move a corpus between shared and dedicated capacity, data intact
|
|
5485
|
+
jobs Bulk-load a corpus from a file store or a manifest, with a plan first
|
|
5486
|
+
connect Follow an S3 bucket the customer owns (credentials = app secret names)
|
|
5487
|
+
sync Bring a connected source up to date with its bucket
|
|
5488
|
+
connector Show a source's connector and what it tracks
|
|
5489
|
+
disconnect Stop following the bucket; documents stay
|
|
5490
|
+
inspect Profile raw objects (key shapes, types, sizes, JSON keys) before writing a mapper
|
|
5491
|
+
map test Run a source's mapper over real objects and show the outcomes; nothing ingested
|
|
5492
|
+
map deploy Make a build's compiled mapper the source's active one (a branch build will do)
|
|
5493
|
+
remap Re-apply the active mapper to every raw copy the source holds
|
|
5494
|
+
hydrate Reload an evicted index ahead of the first search
|
|
5495
|
+
sample Draw a sample of a source into a new one with the same config
|
|
5496
|
+
eval Query sets and runs: measure recall, MRR and latency, compare versions
|
|
3727
5497
|
revectorize Rebuild a corpus under new settings, alongside the live one
|
|
3728
5498
|
promote Make a rebuilt version live
|
|
3729
5499
|
drop Discard a candidate or a superseded version
|
|
@@ -3733,11 +5503,15 @@ Usage:
|
|
|
3733
5503
|
remy-admin datasources add [--source <slug>] [--metadata <k=v,...>] [--wait] [--timeout <sec>] <file...>
|
|
3734
5504
|
remy-admin datasources list
|
|
3735
5505
|
remy-admin datasources status [--source <slug>]
|
|
3736
|
-
remy-admin datasources rm [--source <slug>] --document <id>
|
|
5506
|
+
remy-admin datasources rm [--source <slug>] (--document <id> | --filter <k=v,...|json>)
|
|
3737
5507
|
remy-admin datasources search [--source <slug>] [search options] <query>
|
|
3738
5508
|
remy-admin datasources create --source <slug> [--name <name>] [--placement <resource-id|shared>] [rebuild settings...]
|
|
3739
5509
|
remy-admin datasources config [--source <slug>] [--placement <resource-id|shared>] [settings...]
|
|
3740
5510
|
remy-admin datasources move [--source <slug>] --to <resource-id|shared> [--wait] [--timeout <sec>]
|
|
5511
|
+
remy-admin datasources jobs start|list|status|approve|pause|resume|cancel \u2026 (see below)
|
|
5512
|
+
remy-admin datasources connect|sync|connector|disconnect \u2026 (see below)
|
|
5513
|
+
remy-admin datasources hydrate|sample \u2026 (see below)
|
|
5514
|
+
remy-admin datasources eval create|list|get|add|import|queries|rm|delete|run|runs|result|compare \u2026 (see below)
|
|
3741
5515
|
remy-admin datasources revectorize [--source <slug>] [settings...] [--wait]
|
|
3742
5516
|
remy-admin datasources promote [--source <slug>] [--force]
|
|
3743
5517
|
remy-admin datasources drop [--source <slug>] [--version <n>]
|
|
@@ -3830,6 +5604,11 @@ Notes:
|
|
|
3830
5604
|
Re-adding an unchanged file is free: no upload, no re-embedding. Re-adding
|
|
3831
5605
|
with a different --metadata updates the tags in place, also free.
|
|
3832
5606
|
--metadata tags documents for search-time filtering (scalars, \u226416 keys).
|
|
5607
|
+
rm --filter removes every matching document in pages of a thousand: k=v
|
|
5608
|
+
pairs match metadata; a JSON object takes the full selector \u2014 metadata,
|
|
5609
|
+
filename, documentIds, externalIdPrefix (the key a job or connector
|
|
5610
|
+
recorded, e.g. '{"externalIdPrefix":"archive/2019/"}'). An empty filter
|
|
5611
|
+
is refused; to remove everything, delete the source.
|
|
3833
5612
|
delete requires an explicit --source (no default) and refuses while documents
|
|
3834
5613
|
are still ingesting. Extraction caches survive deletion, so re-ingesting
|
|
3835
5614
|
the same files elsewhere costs no re-extraction.
|
|
@@ -3847,7 +5626,24 @@ Examples:
|
|
|
3847
5626
|
remy-admin datasources revectorize --source policies --max-chars 900 --wait
|
|
3848
5627
|
remy-admin datasources search --source policies --candidate "payment terms"
|
|
3849
5628
|
remy-admin datasources promote --source policies
|
|
3850
|
-
|
|
5629
|
+
|
|
5630
|
+
# Load a whole corpus: plan first, approve, watch it run
|
|
5631
|
+
remy-admin datasources jobs start --source archive --store raw --prefix 2024/ --wait
|
|
5632
|
+
remy-admin datasources jobs approve <id> --wait
|
|
5633
|
+
|
|
5634
|
+
# Follow a customer's bucket: secrets by name, first sync stops for approval
|
|
5635
|
+
remy-admin secrets set ARCHIVE_S3_KEY --prod <value> # never on a command line you share
|
|
5636
|
+
remy-admin datasources connect --source archive --bucket acme-docs --region us-east-1 --prefix contracts/ --access-key-secret ARCHIVE_S3_KEY --secret-key-secret ARCHIVE_S3_SECRET --budget-per-sync 5
|
|
5637
|
+
remy-admin datasources sync --source archive --wait
|
|
5638
|
+
|
|
5639
|
+
# Measure before you promote: sample, build a query set, run live vs candidate
|
|
5640
|
+
remy-admin datasources sample --source archive --size 300 --wait
|
|
5641
|
+
remy-admin datasources eval create --source archive-sample --name base --size 150 --wait
|
|
5642
|
+
remy-admin datasources eval run --set <setId> --label live --wait
|
|
5643
|
+
remy-admin datasources revectorize --source archive-sample --max-chars 900 --wait
|
|
5644
|
+
remy-admin datasources eval run --set <setId> --candidate --label small-chunks --wait
|
|
5645
|
+
remy-admin datasources eval compare <runA> <runB>
|
|
5646
|
+
${dataSourceJobsHelp}${dataSourceConnectorsHelp}${dataSourceMappersHelp}${dataSourceEvalsHelp}`;
|
|
3851
5647
|
|
|
3852
5648
|
// src/ops/voice.ts
|
|
3853
5649
|
function normalizeE164(input) {
|
|
@@ -4422,7 +6218,7 @@ function runs(ctx, params = {}) {
|
|
|
4422
6218
|
`/_internal/v2/apps/${ctx.appId}/jewels/training-runs${qs(params)}`
|
|
4423
6219
|
);
|
|
4424
6220
|
}
|
|
4425
|
-
function
|
|
6221
|
+
function getRun2(ctx, runId) {
|
|
4426
6222
|
return call(
|
|
4427
6223
|
ctx,
|
|
4428
6224
|
"GET",
|
|
@@ -4582,9 +6378,9 @@ async function jewelsTimeseries(ctx, a) {
|
|
|
4582
6378
|
);
|
|
4583
6379
|
}
|
|
4584
6380
|
async function jewelsResolve(ctx, a) {
|
|
4585
|
-
const
|
|
6381
|
+
const approve2 = a.bool("approve");
|
|
4586
6382
|
const dismiss = a.bool("dismiss");
|
|
4587
|
-
if (
|
|
6383
|
+
if (approve2 === dismiss) {
|
|
4588
6384
|
fatal("Pass exactly one of --approve or --dismiss.");
|
|
4589
6385
|
}
|
|
4590
6386
|
if (dismiss && a.str("input") !== void 0) {
|
|
@@ -4592,7 +6388,7 @@ async function jewelsResolve(ctx, a) {
|
|
|
4592
6388
|
}
|
|
4593
6389
|
const params = {
|
|
4594
6390
|
itemId: a.req("itemId"),
|
|
4595
|
-
action:
|
|
6391
|
+
action: approve2 ? "approve" : "dismiss"
|
|
4596
6392
|
};
|
|
4597
6393
|
if (a.str("input") !== void 0) {
|
|
4598
6394
|
params.input = parseJsonFlag(a, "input");
|
|
@@ -4640,28 +6436,28 @@ async function jewelsTrain(ctx, a) {
|
|
|
4640
6436
|
let lastLogLen = 0;
|
|
4641
6437
|
while (Date.now() < deadline) {
|
|
4642
6438
|
await sleep(TRAIN_POLL_MS);
|
|
4643
|
-
const { run:
|
|
4644
|
-
const log = Array.isArray(
|
|
6439
|
+
const { run: run3 } = await getRun2(ctx, runId);
|
|
6440
|
+
const log = Array.isArray(run3.log) ? run3.log : [];
|
|
4645
6441
|
const fresh = log.slice(lastLogLen);
|
|
4646
6442
|
lastLogLen = log.length;
|
|
4647
6443
|
const isStatus = (x) => x?.kind === "status";
|
|
4648
6444
|
const isLoss = (x) => x?.kind === "loss";
|
|
4649
6445
|
for (const e of fresh.filter(isStatus)) {
|
|
4650
|
-
out({ status:
|
|
6446
|
+
out({ status: run3.status, at: e.ts, message: e.message });
|
|
4651
6447
|
}
|
|
4652
6448
|
const losses = fresh.filter(isLoss);
|
|
4653
6449
|
if (losses.length) {
|
|
4654
6450
|
const last = losses[losses.length - 1];
|
|
4655
|
-
const p =
|
|
6451
|
+
const p = run3.progress;
|
|
4656
6452
|
out({
|
|
4657
|
-
status:
|
|
6453
|
+
status: run3.status,
|
|
4658
6454
|
step: `${last.step}/${p?.totalSteps ?? "?"}`,
|
|
4659
6455
|
loss: last.loss
|
|
4660
6456
|
});
|
|
4661
6457
|
}
|
|
4662
|
-
if (
|
|
4663
|
-
out(compactRunLog(
|
|
4664
|
-
if (
|
|
6458
|
+
if (run3.status === "complete" || run3.status === "failed") {
|
|
6459
|
+
out(compactRunLog(run3));
|
|
6460
|
+
if (run3.status === "failed") {
|
|
4665
6461
|
process.exitCode = 1;
|
|
4666
6462
|
}
|
|
4667
6463
|
return;
|
|
@@ -4671,19 +6467,19 @@ async function jewelsTrain(ctx, a) {
|
|
|
4671
6467
|
`Timed out waiting for run ${runId} (still in flight; check 'jewels run ${runId}').`
|
|
4672
6468
|
);
|
|
4673
6469
|
}
|
|
4674
|
-
function compactRunLog(
|
|
4675
|
-
let out2 = { ...
|
|
4676
|
-
if (Array.isArray(
|
|
4677
|
-
const statusEntries =
|
|
4678
|
-
const lossPoints =
|
|
6470
|
+
function compactRunLog(run3) {
|
|
6471
|
+
let out2 = { ...run3 };
|
|
6472
|
+
if (Array.isArray(run3.log)) {
|
|
6473
|
+
const statusEntries = run3.log.filter((e) => e?.kind === "status");
|
|
6474
|
+
const lossPoints = run3.log.length - statusEntries.length;
|
|
4679
6475
|
out2 = { ...out2, log: { statusEntries, lossPoints } };
|
|
4680
6476
|
}
|
|
4681
|
-
if (Array.isArray(
|
|
6477
|
+
if (Array.isArray(run3.report?.candidates)) {
|
|
4682
6478
|
out2 = {
|
|
4683
6479
|
...out2,
|
|
4684
6480
|
report: {
|
|
4685
|
-
...
|
|
4686
|
-
candidates:
|
|
6481
|
+
...run3.report,
|
|
6482
|
+
candidates: run3.report.candidates.map(
|
|
4687
6483
|
(c) => Array.isArray(c?.predictions) ? { ...c, predictions: c.predictions.length } : c
|
|
4688
6484
|
)
|
|
4689
6485
|
}
|
|
@@ -4700,8 +6496,8 @@ async function jewelsRuns(ctx, a) {
|
|
|
4700
6496
|
);
|
|
4701
6497
|
}
|
|
4702
6498
|
async function jewelsRunGet(ctx, a) {
|
|
4703
|
-
const { run:
|
|
4704
|
-
out({ run: compactRunLog(
|
|
6499
|
+
const { run: run3 } = await getRun2(ctx, a.req("runId"));
|
|
6500
|
+
out({ run: compactRunLog(run3) });
|
|
4705
6501
|
}
|
|
4706
6502
|
async function jewelsGrade(ctx, a) {
|
|
4707
6503
|
out(await grade(ctx, a.req("runId")));
|
|
@@ -5148,14 +6944,14 @@ async function resolveDomainId(ctx, direction, domain) {
|
|
|
5148
6944
|
}
|
|
5149
6945
|
return match.id;
|
|
5150
6946
|
}
|
|
5151
|
-
function
|
|
6947
|
+
function list10(ctx, params = {}) {
|
|
5152
6948
|
return call(
|
|
5153
6949
|
ctx,
|
|
5154
6950
|
"GET",
|
|
5155
6951
|
`/_internal/v2/apps/${ctx.appId}/outbound-email/messages${qs(params)}`
|
|
5156
6952
|
);
|
|
5157
6953
|
}
|
|
5158
|
-
function
|
|
6954
|
+
function get9(ctx, messageId) {
|
|
5159
6955
|
return call(
|
|
5160
6956
|
ctx,
|
|
5161
6957
|
"GET",
|
|
@@ -5347,7 +7143,7 @@ var emailSpecs = {
|
|
|
5347
7143
|
};
|
|
5348
7144
|
async function emailList(ctx, a) {
|
|
5349
7145
|
out(
|
|
5350
|
-
await
|
|
7146
|
+
await list10(ctx, {
|
|
5351
7147
|
status: a.str("status"),
|
|
5352
7148
|
kind: a.str("kind"),
|
|
5353
7149
|
recipient: a.str("recipient"),
|
|
@@ -5361,7 +7157,7 @@ async function emailList(ctx, a) {
|
|
|
5361
7157
|
);
|
|
5362
7158
|
}
|
|
5363
7159
|
async function emailGet(ctx, a) {
|
|
5364
|
-
out(await
|
|
7160
|
+
out(await get9(ctx, a.req("messageId")));
|
|
5365
7161
|
}
|
|
5366
7162
|
async function emailStats(ctx, a) {
|
|
5367
7163
|
out(await stats2(ctx, { start: a.str("start"), end: a.str("end") }));
|
|
@@ -5537,14 +7333,14 @@ Examples:
|
|
|
5537
7333
|
`;
|
|
5538
7334
|
|
|
5539
7335
|
// src/ops/cron.ts
|
|
5540
|
-
function
|
|
7336
|
+
function list11(ctx, params = {}) {
|
|
5541
7337
|
return call(
|
|
5542
7338
|
ctx,
|
|
5543
7339
|
"GET",
|
|
5544
7340
|
`/_internal/v2/apps/${ctx.appId}/cron/overview${qs(params)}`
|
|
5545
7341
|
);
|
|
5546
7342
|
}
|
|
5547
|
-
function
|
|
7343
|
+
function run2(ctx, route) {
|
|
5548
7344
|
return call(
|
|
5549
7345
|
ctx,
|
|
5550
7346
|
"POST",
|
|
@@ -5566,10 +7362,10 @@ var cronSpecs = {
|
|
|
5566
7362
|
}
|
|
5567
7363
|
};
|
|
5568
7364
|
async function cronList(ctx, a) {
|
|
5569
|
-
out(await
|
|
7365
|
+
out(await list11(ctx, { runs: a.num("runs") }));
|
|
5570
7366
|
}
|
|
5571
7367
|
async function cronRun(ctx, a) {
|
|
5572
|
-
out(await
|
|
7368
|
+
out(await run2(ctx, a.req("route")));
|
|
5573
7369
|
}
|
|
5574
7370
|
var cronHandlers = {
|
|
5575
7371
|
"cron list": cronList,
|
|
@@ -5600,27 +7396,27 @@ Examples:
|
|
|
5600
7396
|
remy-admin cron run dailyDigest`;
|
|
5601
7397
|
|
|
5602
7398
|
// src/ops/infra.ts
|
|
5603
|
-
var
|
|
5604
|
-
var
|
|
5605
|
-
function
|
|
7399
|
+
var DEFAULT_WAIT_TIMEOUT_MS4 = 10 * 60 * 1e3;
|
|
7400
|
+
var POLL_MS5 = 3e3;
|
|
7401
|
+
function base6(appId) {
|
|
5606
7402
|
return `/_internal/v2/apps/${appId}/infra`;
|
|
5607
7403
|
}
|
|
5608
|
-
function
|
|
7404
|
+
function list12(ctx, params) {
|
|
5609
7405
|
return call(
|
|
5610
7406
|
ctx,
|
|
5611
7407
|
"GET",
|
|
5612
|
-
`${
|
|
7408
|
+
`${base6(ctx.appId)}${params?.includeDestroyed ? "?includeDestroyed=true" : ""}`
|
|
5613
7409
|
);
|
|
5614
7410
|
}
|
|
5615
|
-
function
|
|
7411
|
+
function get10(ctx, params) {
|
|
5616
7412
|
return call(
|
|
5617
7413
|
ctx,
|
|
5618
7414
|
"GET",
|
|
5619
|
-
`${
|
|
7415
|
+
`${base6(ctx.appId)}/${encodeURIComponent(params.id)}`
|
|
5620
7416
|
);
|
|
5621
7417
|
}
|
|
5622
7418
|
function provision(ctx, params) {
|
|
5623
|
-
return call(ctx, "POST",
|
|
7419
|
+
return call(ctx, "POST", base6(ctx.appId), {
|
|
5624
7420
|
offeringId: params.offeringId,
|
|
5625
7421
|
...params.name ? { name: params.name } : {}
|
|
5626
7422
|
});
|
|
@@ -5629,28 +7425,28 @@ function hibernate(ctx, params) {
|
|
|
5629
7425
|
return call(
|
|
5630
7426
|
ctx,
|
|
5631
7427
|
"POST",
|
|
5632
|
-
`${
|
|
7428
|
+
`${base6(ctx.appId)}/${encodeURIComponent(params.id)}/hibernate`
|
|
5633
7429
|
);
|
|
5634
7430
|
}
|
|
5635
|
-
function
|
|
7431
|
+
function resume2(ctx, params) {
|
|
5636
7432
|
return call(
|
|
5637
7433
|
ctx,
|
|
5638
7434
|
"POST",
|
|
5639
|
-
`${
|
|
7435
|
+
`${base6(ctx.appId)}/${encodeURIComponent(params.id)}/resume`
|
|
5640
7436
|
);
|
|
5641
7437
|
}
|
|
5642
7438
|
function destroy(ctx, params) {
|
|
5643
7439
|
return call(
|
|
5644
7440
|
ctx,
|
|
5645
7441
|
"POST",
|
|
5646
|
-
`${
|
|
7442
|
+
`${base6(ctx.appId)}/${encodeURIComponent(params.id)}/destroy`
|
|
5647
7443
|
);
|
|
5648
7444
|
}
|
|
5649
7445
|
function resize(ctx, params) {
|
|
5650
7446
|
return call(
|
|
5651
7447
|
ctx,
|
|
5652
7448
|
"POST",
|
|
5653
|
-
`${
|
|
7449
|
+
`${base6(ctx.appId)}/${encodeURIComponent(params.id)}/resize`,
|
|
5654
7450
|
{ offeringId: params.offeringId }
|
|
5655
7451
|
);
|
|
5656
7452
|
}
|
|
@@ -5658,56 +7454,40 @@ function rename(ctx, params) {
|
|
|
5658
7454
|
return call(
|
|
5659
7455
|
ctx,
|
|
5660
7456
|
"POST",
|
|
5661
|
-
`${
|
|
7457
|
+
`${base6(ctx.appId)}/${encodeURIComponent(params.id)}/rename`,
|
|
5662
7458
|
{ name: params.name }
|
|
5663
7459
|
);
|
|
5664
7460
|
}
|
|
5665
7461
|
async function waitForPhase(ctx, params) {
|
|
5666
|
-
const timeoutMs = params.timeoutMs ??
|
|
7462
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS4;
|
|
5667
7463
|
const wanted = new Set(params.phases);
|
|
5668
|
-
const
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
throw err;
|
|
5678
|
-
}
|
|
5679
|
-
transientSince ??= Date.now();
|
|
5680
|
-
if (Date.now() - transientSince > TRANSIENT_GRACE_MS) {
|
|
5681
|
-
throw err;
|
|
5682
|
-
}
|
|
5683
|
-
params.onProgress?.(
|
|
5684
|
-
`retrying after a gateway error\u2026 (${Math.round((Date.now() - start) / 1e3)}s)`
|
|
5685
|
-
);
|
|
5686
|
-
await sleep(POLL_MS4);
|
|
5687
|
-
continue;
|
|
5688
|
-
}
|
|
5689
|
-
if (wanted.has(resource.phase) && (params.until?.(resource) ?? true)) {
|
|
5690
|
-
return { status: "done", resource };
|
|
5691
|
-
}
|
|
5692
|
-
if (resource.phase === "failed") {
|
|
5693
|
-
return {
|
|
5694
|
-
status: "failed",
|
|
5695
|
-
resource,
|
|
5696
|
-
error: resource.lastError ?? "The platform could not complete the change."
|
|
5697
|
-
};
|
|
5698
|
-
}
|
|
5699
|
-
if (Date.now() - start > timeoutMs) {
|
|
5700
|
-
return {
|
|
5701
|
-
status: "timeout",
|
|
5702
|
-
resource,
|
|
5703
|
-
error: `Timed out after ${Math.round(timeoutMs / 1e3)}s; resource is still ${resource.phase}`
|
|
5704
|
-
};
|
|
7464
|
+
const arrived = (resource2) => wanted.has(resource2.phase) && (params.until?.(resource2) ?? true);
|
|
7465
|
+
const outcome = await pollUntil(
|
|
7466
|
+
async () => (await get10(ctx, { id: params.id })).resource,
|
|
7467
|
+
(resource2) => arrived(resource2) || resource2.phase === "failed",
|
|
7468
|
+
{
|
|
7469
|
+
timeoutMs,
|
|
7470
|
+
pollMs: POLL_MS5,
|
|
7471
|
+
describe: (resource2, start2) => `${resource2.phase}${resource2.detail ? ` \xB7 ${resource2.detail}` : ""}${resource2.resizingTo ? ` \xB7 resizing to ${resource2.resizingTo.label}` : ""}\u2026 (${elapsedSeconds(start2)}s)`,
|
|
7472
|
+
onProgress: params.onProgress
|
|
5705
7473
|
}
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
7474
|
+
);
|
|
7475
|
+
const resource = outcome.value;
|
|
7476
|
+
if (!outcome.settled) {
|
|
7477
|
+
return {
|
|
7478
|
+
status: "timeout",
|
|
7479
|
+
resource,
|
|
7480
|
+
error: timedOut(timeoutMs, `; resource is still ${resource.phase}`)
|
|
7481
|
+
};
|
|
5710
7482
|
}
|
|
7483
|
+
if (arrived(resource)) {
|
|
7484
|
+
return { status: "done", resource };
|
|
7485
|
+
}
|
|
7486
|
+
return {
|
|
7487
|
+
status: "failed",
|
|
7488
|
+
resource,
|
|
7489
|
+
error: resource.lastError ?? "The platform could not complete the change."
|
|
7490
|
+
};
|
|
5711
7491
|
}
|
|
5712
7492
|
|
|
5713
7493
|
// src/commands/infra.ts
|
|
@@ -5768,7 +7548,7 @@ var infraSpecs = {
|
|
|
5768
7548
|
}
|
|
5769
7549
|
}
|
|
5770
7550
|
};
|
|
5771
|
-
async function
|
|
7551
|
+
async function waitAndReport2(ctx, a, id, phases, summary2, until) {
|
|
5772
7552
|
const timeoutSec = a.str("timeout");
|
|
5773
7553
|
const result = await waitForPhase(ctx, {
|
|
5774
7554
|
id,
|
|
@@ -5786,10 +7566,10 @@ async function waitAndReport(ctx, a, id, phases, summary2, until) {
|
|
|
5786
7566
|
}
|
|
5787
7567
|
}
|
|
5788
7568
|
async function infraList(ctx, a) {
|
|
5789
|
-
out(await
|
|
7569
|
+
out(await list12(ctx, { includeDestroyed: a.bool("include-destroyed") }));
|
|
5790
7570
|
}
|
|
5791
7571
|
async function infraGet(ctx, a) {
|
|
5792
|
-
out(await
|
|
7572
|
+
out(await get10(ctx, { id: a.req("id") }));
|
|
5793
7573
|
}
|
|
5794
7574
|
function pricingOf(resource) {
|
|
5795
7575
|
const offering = resource.offering;
|
|
@@ -5819,7 +7599,7 @@ async function infraProvision(ctx, a) {
|
|
|
5819
7599
|
});
|
|
5820
7600
|
return;
|
|
5821
7601
|
}
|
|
5822
|
-
await
|
|
7602
|
+
await waitAndReport2(ctx, a, resource.id, ["active"], {
|
|
5823
7603
|
action: "provision",
|
|
5824
7604
|
pricing
|
|
5825
7605
|
});
|
|
@@ -5831,16 +7611,16 @@ async function infraHibernate(ctx, a) {
|
|
|
5831
7611
|
out({ resource });
|
|
5832
7612
|
return;
|
|
5833
7613
|
}
|
|
5834
|
-
await
|
|
7614
|
+
await waitAndReport2(ctx, a, id, ["hibernated"], { action: "hibernate" });
|
|
5835
7615
|
}
|
|
5836
7616
|
async function infraResume(ctx, a) {
|
|
5837
7617
|
const id = a.req("id");
|
|
5838
|
-
const { resource } = await
|
|
7618
|
+
const { resource } = await resume2(ctx, { id });
|
|
5839
7619
|
if (!a.bool("wait")) {
|
|
5840
7620
|
out({ resource });
|
|
5841
7621
|
return;
|
|
5842
7622
|
}
|
|
5843
|
-
await
|
|
7623
|
+
await waitAndReport2(ctx, a, id, ["active"], { action: "resume" });
|
|
5844
7624
|
}
|
|
5845
7625
|
async function infraDestroy(ctx, a) {
|
|
5846
7626
|
const id = a.req("id");
|
|
@@ -5849,7 +7629,7 @@ async function infraDestroy(ctx, a) {
|
|
|
5849
7629
|
out({ resource });
|
|
5850
7630
|
return;
|
|
5851
7631
|
}
|
|
5852
|
-
await
|
|
7632
|
+
await waitAndReport2(ctx, a, id, ["destroyed"], { action: "destroy" });
|
|
5853
7633
|
}
|
|
5854
7634
|
async function infraRename(ctx, a) {
|
|
5855
7635
|
out(
|
|
@@ -5877,7 +7657,7 @@ async function infraResize(ctx, a) {
|
|
|
5877
7657
|
});
|
|
5878
7658
|
return;
|
|
5879
7659
|
}
|
|
5880
|
-
await
|
|
7660
|
+
await waitAndReport2(
|
|
5881
7661
|
ctx,
|
|
5882
7662
|
a,
|
|
5883
7663
|
id,
|
|
@@ -5983,6 +7763,10 @@ var SPECS = {
|
|
|
5983
7763
|
...prerenderSpecs,
|
|
5984
7764
|
...filesSpecs,
|
|
5985
7765
|
...dataSourcesSpecs,
|
|
7766
|
+
...dataSourceJobsSpecs,
|
|
7767
|
+
...dataSourceConnectorsSpecs,
|
|
7768
|
+
...dataSourceEvalsSpecs,
|
|
7769
|
+
...dataSourceMappersSpecs,
|
|
5986
7770
|
...voiceSpecs,
|
|
5987
7771
|
...agentSpecs,
|
|
5988
7772
|
...eventsSpecs,
|
|
@@ -6008,6 +7792,10 @@ var HANDLERS = {
|
|
|
6008
7792
|
...prerenderHandlers,
|
|
6009
7793
|
...filesHandlers,
|
|
6010
7794
|
...dataSourcesHandlers,
|
|
7795
|
+
...dataSourceJobsHandlers,
|
|
7796
|
+
...dataSourceConnectorsHandlers,
|
|
7797
|
+
...dataSourceEvalsHandlers,
|
|
7798
|
+
...dataSourceMappersHandlers,
|
|
6011
7799
|
...voiceHandlers,
|
|
6012
7800
|
...agentHandlers,
|
|
6013
7801
|
...eventsHandlers,
|
|
@@ -6044,8 +7832,8 @@ var GROUP_HELP = {
|
|
|
6044
7832
|
};
|
|
6045
7833
|
|
|
6046
7834
|
// src/ctx.ts
|
|
6047
|
-
import
|
|
6048
|
-
import
|
|
7835
|
+
import fs6 from "fs";
|
|
7836
|
+
import path6 from "path";
|
|
6049
7837
|
|
|
6050
7838
|
// src/parseJsonConfig.ts
|
|
6051
7839
|
import JSON5 from "json5";
|
|
@@ -6074,10 +7862,10 @@ function parseJsonConfig(raw) {
|
|
|
6074
7862
|
// src/ctx.ts
|
|
6075
7863
|
var DEFAULT_BASE_URL = "https://api.mindstudio.ai";
|
|
6076
7864
|
function loadWorkspaceAppId(workspaceDir = WORKSPACE_DIR) {
|
|
6077
|
-
const manifestPath =
|
|
7865
|
+
const manifestPath = path6.join(workspaceDir, "mindstudio.json");
|
|
6078
7866
|
let raw;
|
|
6079
7867
|
try {
|
|
6080
|
-
raw =
|
|
7868
|
+
raw = fs6.readFileSync(manifestPath, "utf-8");
|
|
6081
7869
|
} catch (err) {
|
|
6082
7870
|
if (err.code === "ENOENT") {
|
|
6083
7871
|
throw new Error(`mindstudio.json not found at ${manifestPath}`);
|
|
@@ -6181,6 +7969,11 @@ function resolveRoute(argv) {
|
|
|
6181
7969
|
error: `Unknown subcommand: settings ${sub} ${action ?? ""}. Run 'remy-admin settings --help'`
|
|
6182
7970
|
};
|
|
6183
7971
|
}
|
|
7972
|
+
if (group === "datasources" && (sub === "jobs" || sub === "eval" || sub === "map")) {
|
|
7973
|
+
return {
|
|
7974
|
+
error: `Unknown subcommand: datasources ${sub} ${action ?? ""}. Run 'remy-admin datasources --help'`
|
|
7975
|
+
};
|
|
7976
|
+
}
|
|
6184
7977
|
if (group === "email" && ["domains", "inbound-domains"].includes(sub ?? "")) {
|
|
6185
7978
|
return {
|
|
6186
7979
|
error: `Unknown subcommand: email ${sub} ${action ?? ""}. Run 'remy-admin email --help'`
|