@madewithremy/admin 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1435 -29
- package/dist/index.js +786 -138
- package/dist/index.js.map +1 -1
- package/dist/prod.js +2252 -296
- 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) {
|
|
@@ -3079,6 +3144,48 @@ function create2(ctx, params) {
|
|
|
3079
3144
|
...placement !== void 0 ? { placement } : {}
|
|
3080
3145
|
});
|
|
3081
3146
|
}
|
|
3147
|
+
function move(ctx, params) {
|
|
3148
|
+
return call(ctx, "POST", `${base(ctx.appId)}/move`, {
|
|
3149
|
+
slug: params.slug,
|
|
3150
|
+
placement: params.placement
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
async function waitForMove(ctx, params) {
|
|
3154
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
3155
|
+
const outcome = await pollUntil(
|
|
3156
|
+
async () => {
|
|
3157
|
+
const { dataSources } = await list8(ctx);
|
|
3158
|
+
const source2 = dataSources.find((s) => s.slug === params.slug);
|
|
3159
|
+
if (!source2) {
|
|
3160
|
+
throw new Error(`Data source "${params.slug}" not found.`);
|
|
3161
|
+
}
|
|
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
|
|
3170
|
+
}
|
|
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
|
+
}
|
|
3082
3189
|
function revectorize(ctx, params) {
|
|
3083
3190
|
const { slug, ingest } = params;
|
|
3084
3191
|
return call(
|
|
@@ -3091,51 +3198,1712 @@ function revectorize(ctx, params) {
|
|
|
3091
3198
|
}
|
|
3092
3199
|
);
|
|
3093
3200
|
}
|
|
3094
|
-
function promote(ctx, params) {
|
|
3095
|
-
return call(
|
|
3096
|
-
ctx,
|
|
3097
|
-
"POST",
|
|
3098
|
-
`${base(ctx.appId)}/promote`,
|
|
3099
|
-
{
|
|
3100
|
-
slug: params.slug,
|
|
3101
|
-
...params.force ? { force: true } : {}
|
|
3102
|
-
}
|
|
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 } : {}
|
|
3209
|
+
}
|
|
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 } : {}
|
|
3220
|
+
}
|
|
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 };
|
|
3249
|
+
}
|
|
3250
|
+
params.onProgress?.(
|
|
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
|
|
3415
|
+
);
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
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) {
|
|
4582
|
+
return call(
|
|
4583
|
+
ctx,
|
|
4584
|
+
"POST",
|
|
4585
|
+
`${base5(ctx.appId)}/inspect`,
|
|
4586
|
+
{
|
|
4587
|
+
...params.slug ? { slug: params.slug } : {},
|
|
4588
|
+
...selectionBody(params.selection),
|
|
4589
|
+
...params.keys ? { keys: params.keys } : {},
|
|
4590
|
+
...params.limit !== void 0 ? { limit: params.limit } : {}
|
|
4591
|
+
}
|
|
4592
|
+
);
|
|
4593
|
+
}
|
|
4594
|
+
function mapTest(ctx, params) {
|
|
4595
|
+
return call(
|
|
4596
|
+
ctx,
|
|
4597
|
+
"POST",
|
|
4598
|
+
`${base5(ctx.appId)}/map-test`,
|
|
4599
|
+
{
|
|
4600
|
+
slug: params.slug,
|
|
4601
|
+
...selectionBody(params.selection),
|
|
4602
|
+
...params.keys ? { keys: params.keys } : {},
|
|
4603
|
+
...params.limit !== void 0 ? { limit: params.limit } : {},
|
|
4604
|
+
...params.dev ? { dev: true } : {}
|
|
4605
|
+
}
|
|
4606
|
+
);
|
|
4607
|
+
}
|
|
4608
|
+
function remap(ctx, params) {
|
|
4609
|
+
return call(ctx, "POST", `${base5(ctx.appId)}/remap`, {
|
|
4610
|
+
slug: params.slug,
|
|
4611
|
+
...params.budgetDollars !== void 0 ? { budgetDollars: params.budgetDollars } : {},
|
|
4612
|
+
...params.limit !== void 0 ? { limit: params.limit } : {}
|
|
4613
|
+
});
|
|
4614
|
+
}
|
|
4615
|
+
function quarantine(ctx, params) {
|
|
4616
|
+
return call(
|
|
4617
|
+
ctx,
|
|
4618
|
+
"GET",
|
|
4619
|
+
`${base5(ctx.appId)}/jobs/${params.id}/quarantine${qs({
|
|
4620
|
+
...params.kind ? { kind: params.kind } : {},
|
|
4621
|
+
...params.reason ? { reason: params.reason } : {},
|
|
4622
|
+
...params.cursor ? { cursor: params.cursor } : {},
|
|
4623
|
+
...params.limit !== void 0 ? { limit: params.limit } : {}
|
|
4624
|
+
})}`
|
|
4625
|
+
);
|
|
4626
|
+
}
|
|
4627
|
+
function replay(ctx, params) {
|
|
4628
|
+
return call(
|
|
4629
|
+
ctx,
|
|
4630
|
+
"POST",
|
|
4631
|
+
`${base5(ctx.appId)}/jobs/${params.id}/replay`,
|
|
4632
|
+
params.kind ? { kind: params.kind } : {}
|
|
4633
|
+
);
|
|
4634
|
+
}
|
|
4635
|
+
|
|
4636
|
+
// src/commands/dataSourceMappers.ts
|
|
4637
|
+
var SELECTION_FLAGS = {
|
|
4638
|
+
store: { type: "string" },
|
|
4639
|
+
access: { type: "string" },
|
|
4640
|
+
prefix: { type: "string" },
|
|
4641
|
+
connector: { type: "boolean" },
|
|
4642
|
+
keys: { type: "string" }
|
|
4643
|
+
};
|
|
4644
|
+
var dataSourceMappersSpecs = {
|
|
4645
|
+
"datasources inspect": {
|
|
4646
|
+
usage: "Usage: remy-admin datasources inspect [--source <slug>] (--store <name> [--access <private|public>] [--prefix <p>] | --connector) [--keys k1,k2,...] [--limit <n>]",
|
|
4647
|
+
flags: {
|
|
4648
|
+
source: { type: "string" },
|
|
4649
|
+
...SELECTION_FLAGS,
|
|
4650
|
+
limit: { type: "number", min: 1 }
|
|
4651
|
+
},
|
|
4652
|
+
requireAnyOf: {
|
|
4653
|
+
flags: ["store", "connector"],
|
|
4654
|
+
message: "Name the objects: --store <name> [--prefix <p>], or --connector for the source's bucket."
|
|
4655
|
+
}
|
|
4656
|
+
},
|
|
4657
|
+
"datasources map test": {
|
|
4658
|
+
usage: "Usage: remy-admin datasources map test [--source <slug>] (--store <name> [--access <private|public>] [--prefix <p>] | --connector) [--keys k1,k2,...] [--limit <n>] [--dev] [--full]",
|
|
4659
|
+
flags: {
|
|
4660
|
+
source: { type: "string" },
|
|
4661
|
+
...SELECTION_FLAGS,
|
|
4662
|
+
limit: { type: "number", min: 1 },
|
|
4663
|
+
dev: { type: "boolean" },
|
|
4664
|
+
full: { type: "boolean" }
|
|
4665
|
+
},
|
|
4666
|
+
requireAnyOf: {
|
|
4667
|
+
flags: ["store", "connector"],
|
|
4668
|
+
message: "Name the objects: --store <name> [--prefix <p>], or --connector for the source's bucket."
|
|
4669
|
+
}
|
|
4670
|
+
},
|
|
4671
|
+
"datasources remap": {
|
|
4672
|
+
usage: "Usage: remy-admin datasources remap [--source <slug>] [--budget <dollars>] [--limit <n>] [--wait] [--timeout <sec>]",
|
|
4673
|
+
flags: {
|
|
4674
|
+
source: { type: "string" },
|
|
4675
|
+
budget: { type: "number", min: 0.01 },
|
|
4676
|
+
limit: { type: "number", min: 1 },
|
|
4677
|
+
wait: { type: "boolean" },
|
|
4678
|
+
timeout: { type: "string" }
|
|
4679
|
+
}
|
|
4680
|
+
},
|
|
4681
|
+
"datasources jobs quarantine": {
|
|
4682
|
+
usage: "Usage: remy-admin datasources jobs quarantine <id> [--kind skip|error] [--reason <text>] [--cursor <id>] [--limit <n>]",
|
|
4683
|
+
positionals: [{ name: "id", required: true }],
|
|
4684
|
+
flags: {
|
|
4685
|
+
kind: { type: "string" },
|
|
4686
|
+
reason: { type: "string" },
|
|
4687
|
+
cursor: { type: "string" },
|
|
4688
|
+
limit: { type: "number", min: 1 }
|
|
4689
|
+
}
|
|
4690
|
+
},
|
|
4691
|
+
"datasources jobs replay": {
|
|
4692
|
+
usage: "Usage: remy-admin datasources jobs replay <id> [--kind skip|error] [--wait] [--timeout <sec>]",
|
|
4693
|
+
positionals: [{ name: "id", required: true }],
|
|
4694
|
+
flags: {
|
|
4695
|
+
kind: { type: "string" },
|
|
4696
|
+
wait: { type: "boolean" },
|
|
4697
|
+
timeout: { type: "string" }
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
};
|
|
4701
|
+
function selectionOf(a) {
|
|
4702
|
+
const store = a.str("store");
|
|
4703
|
+
if (a.bool("connector") && store) {
|
|
4704
|
+
fatal("Give --store or --connector, not both.");
|
|
4705
|
+
}
|
|
4706
|
+
if (a.bool("connector")) {
|
|
4707
|
+
return { connector: true };
|
|
4708
|
+
}
|
|
4709
|
+
const access = a.str("access") ?? "private";
|
|
4710
|
+
if (access !== "private" && access !== "public") {
|
|
4711
|
+
fatal(`--access must be "private" or "public" (got "${access}").`);
|
|
4712
|
+
}
|
|
4713
|
+
return {
|
|
4714
|
+
store,
|
|
4715
|
+
access,
|
|
4716
|
+
...a.str("prefix") !== void 0 ? { prefix: a.str("prefix") } : {}
|
|
4717
|
+
};
|
|
4718
|
+
}
|
|
4719
|
+
function keysOf(a) {
|
|
4720
|
+
const raw = a.str("keys");
|
|
4721
|
+
if (!raw) {
|
|
4722
|
+
return void 0;
|
|
4723
|
+
}
|
|
4724
|
+
const keys = raw.split(",").map((k) => k.trim()).filter(Boolean);
|
|
4725
|
+
if (keys.length === 0) {
|
|
4726
|
+
fatal("--keys needs at least one key.");
|
|
4727
|
+
}
|
|
4728
|
+
return keys;
|
|
4729
|
+
}
|
|
4730
|
+
function kindOf(a) {
|
|
4731
|
+
const kind = a.str("kind");
|
|
4732
|
+
if (kind === void 0) {
|
|
4733
|
+
return void 0;
|
|
4734
|
+
}
|
|
4735
|
+
if (kind !== "skip" && kind !== "error") {
|
|
4736
|
+
fatal(`--kind must be "skip" or "error" (got "${kind}").`);
|
|
4737
|
+
}
|
|
4738
|
+
return kind;
|
|
4739
|
+
}
|
|
4740
|
+
async function inspect2(ctx, a) {
|
|
4741
|
+
const slug = a.str("source");
|
|
4742
|
+
const selection = selectionOf(a);
|
|
4743
|
+
if ("connector" in selection && !slug) {
|
|
4744
|
+
fatal("--connector needs --source: the bucket is the source's.");
|
|
4745
|
+
}
|
|
4746
|
+
const limit = a.num("limit");
|
|
4747
|
+
const result = await inspect(ctx, {
|
|
4748
|
+
...slug ? { slug } : {},
|
|
4749
|
+
selection,
|
|
4750
|
+
...keysOf(a) ? { keys: keysOf(a) } : {},
|
|
4751
|
+
...limit !== void 0 ? { limit } : {}
|
|
4752
|
+
});
|
|
4753
|
+
out({
|
|
4754
|
+
...slug ? { dataSource: slug } : {},
|
|
4755
|
+
...result,
|
|
4756
|
+
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()}).`
|
|
4757
|
+
});
|
|
4758
|
+
}
|
|
4759
|
+
async function mapTest2(ctx, a) {
|
|
4760
|
+
const slug = sourceOf(a);
|
|
4761
|
+
const limit = a.num("limit");
|
|
4762
|
+
const result = await mapTest(ctx, {
|
|
4763
|
+
slug,
|
|
4764
|
+
selection: selectionOf(a),
|
|
4765
|
+
...keysOf(a) ? { keys: keysOf(a) } : {},
|
|
4766
|
+
...limit !== void 0 ? { limit } : {},
|
|
4767
|
+
...a.bool("dev") ? { dev: true } : {}
|
|
4768
|
+
});
|
|
4769
|
+
const results = a.bool("full") ? result.results : result.results.map(
|
|
4770
|
+
(r) => r.outcome.kind === "documents" ? {
|
|
4771
|
+
...r,
|
|
4772
|
+
outcome: {
|
|
4773
|
+
...r.outcome,
|
|
4774
|
+
documents: r.outcome.documents.map((d) => ({
|
|
4775
|
+
...d,
|
|
4776
|
+
preview: d.preview.slice(0, 200)
|
|
4777
|
+
}))
|
|
4778
|
+
}
|
|
4779
|
+
} : r
|
|
3103
4780
|
);
|
|
4781
|
+
out({
|
|
4782
|
+
dataSource: slug,
|
|
4783
|
+
...result,
|
|
4784
|
+
results,
|
|
4785
|
+
note: result.dev ? "Ran the LOCAL mapper through the dev session. Nothing was ingested; deploy to make it live." : "Ran the live release's mapper. Nothing was ingested."
|
|
4786
|
+
});
|
|
4787
|
+
if (result.counts.error > 0) {
|
|
4788
|
+
throw new CliError(
|
|
4789
|
+
`${result.counts.error} of ${result.objects} object(s) errored in the mapper.`,
|
|
4790
|
+
EXIT.buildFailed
|
|
4791
|
+
);
|
|
4792
|
+
}
|
|
3104
4793
|
}
|
|
3105
|
-
function
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
)
|
|
4794
|
+
async function remap2(ctx, a) {
|
|
4795
|
+
const slug = sourceOf(a);
|
|
4796
|
+
const budget = a.num("budget");
|
|
4797
|
+
const limit = a.num("limit");
|
|
4798
|
+
const { job, mapper } = await remap(ctx, {
|
|
4799
|
+
slug,
|
|
4800
|
+
...budget !== void 0 ? { budgetDollars: budget } : {},
|
|
4801
|
+
...limit !== void 0 ? { limit } : {}
|
|
4802
|
+
});
|
|
4803
|
+
if (!a.bool("wait")) {
|
|
4804
|
+
out({
|
|
4805
|
+
dataSource: slug,
|
|
4806
|
+
job,
|
|
4807
|
+
mapper,
|
|
4808
|
+
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."
|
|
4809
|
+
});
|
|
4810
|
+
return;
|
|
4811
|
+
}
|
|
4812
|
+
await waitAndReport(ctx, a, job.id, "job", {
|
|
4813
|
+
dataSource: slug,
|
|
4814
|
+
action: "remap",
|
|
4815
|
+
mapper
|
|
4816
|
+
});
|
|
3115
4817
|
}
|
|
3116
|
-
function
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
}
|
|
3125
|
-
|
|
4818
|
+
async function quarantine2(ctx, a) {
|
|
4819
|
+
const id = a.req("id");
|
|
4820
|
+
const limit = a.num("limit");
|
|
4821
|
+
const kind = kindOf(a);
|
|
4822
|
+
const reason = a.str("reason");
|
|
4823
|
+
const cursor = a.str("cursor");
|
|
4824
|
+
const result = await quarantine(ctx, {
|
|
4825
|
+
id,
|
|
4826
|
+
...kind ? { kind } : {},
|
|
4827
|
+
...reason ? { reason } : {},
|
|
4828
|
+
...cursor ? { cursor } : {},
|
|
4829
|
+
...limit !== void 0 ? { limit } : {}
|
|
4830
|
+
});
|
|
4831
|
+
out({
|
|
4832
|
+
...result,
|
|
4833
|
+
note: result.counts.skipped + result.counts.errors === 0 ? "Nothing quarantined: the mapper produced documents (or deletions) for every object." : `Fix the mapper, deploy, then \`datasources jobs replay ${id}\` to run these through it again.`
|
|
4834
|
+
});
|
|
3126
4835
|
}
|
|
3127
|
-
function
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
{
|
|
3133
|
-
);
|
|
4836
|
+
async function replay2(ctx, a) {
|
|
4837
|
+
const id = a.req("id");
|
|
4838
|
+
const kind = kindOf(a);
|
|
4839
|
+
const { job, mapper, dataSource } = await replay(ctx, {
|
|
4840
|
+
id,
|
|
4841
|
+
...kind ? { kind } : {}
|
|
4842
|
+
});
|
|
4843
|
+
if (!a.bool("wait")) {
|
|
4844
|
+
out({
|
|
4845
|
+
dataSource: dataSource.slug,
|
|
4846
|
+
fromJob: id,
|
|
4847
|
+
job,
|
|
4848
|
+
mapper,
|
|
4849
|
+
note: "Replaying the quarantined objects through the current mapper. Poll `datasources jobs status <id>`, or re-run with --wait."
|
|
4850
|
+
});
|
|
4851
|
+
return;
|
|
4852
|
+
}
|
|
4853
|
+
await waitAndReport(ctx, a, job.id, "job", {
|
|
4854
|
+
dataSource: dataSource.slug,
|
|
4855
|
+
fromJob: id,
|
|
4856
|
+
action: "replay",
|
|
4857
|
+
mapper
|
|
4858
|
+
});
|
|
3134
4859
|
}
|
|
4860
|
+
var dataSourceMappersHandlers = {
|
|
4861
|
+
"datasources inspect": inspect2,
|
|
4862
|
+
"datasources map test": mapTest2,
|
|
4863
|
+
"datasources remap": remap2,
|
|
4864
|
+
"datasources jobs quarantine": quarantine2,
|
|
4865
|
+
"datasources jobs replay": replay2
|
|
4866
|
+
};
|
|
4867
|
+
var dataSourceMappersHelp = `
|
|
4868
|
+
Mapping raw objects into documents (mappers):
|
|
4869
|
+
remy-admin datasources inspect [--source <slug>] (--store <name> [--prefix <p>] | --connector) [--keys k1,k2] [--limit <n>]
|
|
4870
|
+
remy-admin datasources map test [--source <slug>] (--store <name> [--prefix <p>] | --connector) [--keys k1,k2] [--limit <n>] [--dev] [--full]
|
|
4871
|
+
remy-admin datasources remap [--source <slug>] [--budget <dollars>] [--limit <n>] [--wait] [--timeout <sec>]
|
|
4872
|
+
remy-admin datasources jobs quarantine <id> [--kind skip|error] [--reason <text>] [--cursor <id>] [--limit <n>]
|
|
4873
|
+
remy-admin datasources jobs replay <id> [--kind skip|error] [--wait] [--timeout <sec>]
|
|
4874
|
+
|
|
4875
|
+
A source takes every object it is given as one document. When the file is
|
|
4876
|
+
not the document \u2014 JSON records that should become markdown plus metadata,
|
|
4877
|
+
a JSONL object holding many articles, a kill notice that means "remove this
|
|
4878
|
+
story" \u2014 the app declares a MAPPER for the source in mindstudio.json:
|
|
4879
|
+
"dataSources": [{ "slug": "archive", "mapper": { "path": "dist/datasources/archive.mapper.ts" } }]
|
|
4880
|
+
The file exports defineMapper(Archive, { map }) from @mindstudio-ai/agent.
|
|
4881
|
+
map(object) reads the raw object (object.key, .bytes(), .text(), .json())
|
|
4882
|
+
and answers documents([{ externalId, title, markdown, metadata?, replaces? }]),
|
|
4883
|
+
passthrough() (ingest the raw object as-is), skip(reason) or
|
|
4884
|
+
deletes([externalId, ...]); throwing is the object's error. It may call
|
|
4885
|
+
models or fetch \u2014 every call is spend per object. Everything entering a
|
|
4886
|
+
mapped source is mapped: jobs, syncs and Source.add() alike.
|
|
4887
|
+
|
|
4888
|
+
The loop: \`inspect\` profiles the objects (key shapes, extensions, sizes,
|
|
4889
|
+
JSON key signatures of ten sampled heads) before a line is written;
|
|
4890
|
+
\`map test --dev\` runs the LOCAL mapper through the running dev session
|
|
4891
|
+
over real objects and prints every outcome without ingesting anything;
|
|
4892
|
+
deploy; a job or sync runs the compiled mapper; \`jobs quarantine <id>\`
|
|
4893
|
+
lists what it skipped or failed on, by reason; fix, deploy, \`jobs replay
|
|
4894
|
+
<id>\` runs just those objects again; \`remap\` re-applies a changed mapper
|
|
4895
|
+
to every raw copy the source holds \u2014 no origin traffic, unchanged markdown
|
|
4896
|
+
skipped by hash, changed documents superseding their predecessors.
|
|
4897
|
+
|
|
4898
|
+
externalId is the identity the platform replaces by: a later object
|
|
4899
|
+
producing the same id supersedes the earlier document; \`replaces\` names
|
|
4900
|
+
ids this document supersedes (a writethru's original); deletes([...])
|
|
4901
|
+
removes by id. A plan on a mapped source records the mapper's outcome mix
|
|
4902
|
+
on its sample; a run whose skip share climbs past twice that (floor 5%)
|
|
4903
|
+
pauses with pauseReason 'skips' for a look at the quarantine.
|
|
4904
|
+
`;
|
|
3135
4905
|
|
|
3136
4906
|
// src/commands/dataSources.ts
|
|
3137
|
-
var DEFAULT_SOURCE = "default";
|
|
3138
|
-
var POLL_MS3 = 3e3;
|
|
3139
4907
|
var INGEST_FLAGS = {
|
|
3140
4908
|
chunking: { type: "string" },
|
|
3141
4909
|
"max-chars": { type: "number", min: 200 },
|
|
@@ -3182,8 +4950,16 @@ var dataSourcesSpecs = {
|
|
|
3182
4950
|
flags: { source: { type: "string" } }
|
|
3183
4951
|
},
|
|
3184
4952
|
"datasources rm": {
|
|
3185
|
-
usage: "Usage: remy-admin datasources rm [--source <slug>] --document <id>",
|
|
3186
|
-
flags: {
|
|
4953
|
+
usage: "Usage: remy-admin datasources rm [--source <slug>] (--document <id> | --filter <k=v,...|json>)",
|
|
4954
|
+
flags: {
|
|
4955
|
+
source: { type: "string" },
|
|
4956
|
+
document: { type: "string" },
|
|
4957
|
+
filter: { type: "string" }
|
|
4958
|
+
},
|
|
4959
|
+
requireAnyOf: {
|
|
4960
|
+
flags: ["document", "filter"],
|
|
4961
|
+
message: "Say what to remove: --document <id> for one document, or --filter <k=v,...|json> for every document that matches."
|
|
4962
|
+
}
|
|
3187
4963
|
},
|
|
3188
4964
|
"datasources search": {
|
|
3189
4965
|
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>",
|
|
@@ -3216,11 +4992,24 @@ var dataSourcesSpecs = {
|
|
|
3216
4992
|
flags: {
|
|
3217
4993
|
source: { type: "string" },
|
|
3218
4994
|
// Where the corpus lives: a dedicated resource id from `infra list`, or
|
|
3219
|
-
// `shared`.
|
|
4995
|
+
// `shared`. A populated source starts a background move (see `move`).
|
|
3220
4996
|
placement: { type: "string" },
|
|
3221
4997
|
...CONFIG_FLAGS
|
|
3222
4998
|
}
|
|
3223
4999
|
},
|
|
5000
|
+
"datasources move": {
|
|
5001
|
+
usage: "Usage: remy-admin datasources move [--source <slug>] --to <resource-id|shared> [--wait] [--timeout <sec>]",
|
|
5002
|
+
flags: {
|
|
5003
|
+
source: { type: "string" },
|
|
5004
|
+
to: { type: "string" },
|
|
5005
|
+
wait: { type: "boolean" },
|
|
5006
|
+
timeout: { type: "string" }
|
|
5007
|
+
},
|
|
5008
|
+
requireAnyOf: {
|
|
5009
|
+
flags: ["to"],
|
|
5010
|
+
message: "--to is required: a resource id from `infra list`, or `shared`."
|
|
5011
|
+
}
|
|
5012
|
+
},
|
|
3224
5013
|
"datasources revectorize": {
|
|
3225
5014
|
usage: "Usage: remy-admin datasources revectorize [--source <slug>] [settings...] [--wait]",
|
|
3226
5015
|
flags: {
|
|
@@ -3243,21 +5032,20 @@ var dataSourcesSpecs = {
|
|
|
3243
5032
|
flags: { source: { type: "string" } }
|
|
3244
5033
|
}
|
|
3245
5034
|
};
|
|
3246
|
-
var sourceOf = (a) => a.str("source") || DEFAULT_SOURCE;
|
|
3247
5035
|
async function dataSourcesAdd(ctx, a) {
|
|
3248
5036
|
const files = a.rest("file");
|
|
3249
5037
|
const slug = sourceOf(a);
|
|
3250
5038
|
const metadata = parseKeyValuePairs(a.str("metadata"), "metadata");
|
|
3251
5039
|
const results = [];
|
|
3252
5040
|
for (const file of files) {
|
|
3253
|
-
const abs =
|
|
5041
|
+
const abs = path5.isAbsolute(file) ? file : path5.join(WORKSPACE_DIR, file);
|
|
3254
5042
|
let bytes;
|
|
3255
5043
|
try {
|
|
3256
|
-
bytes =
|
|
5044
|
+
bytes = fs5.readFileSync(abs);
|
|
3257
5045
|
} catch (err) {
|
|
3258
5046
|
fatal(`Could not read file "${file}": ${err.message}`);
|
|
3259
5047
|
}
|
|
3260
|
-
const filename =
|
|
5048
|
+
const filename = path5.basename(file);
|
|
3261
5049
|
results.push(
|
|
3262
5050
|
await addDocument(ctx, {
|
|
3263
5051
|
slug,
|
|
@@ -3273,32 +5061,24 @@ async function dataSourcesAdd(ctx, a) {
|
|
|
3273
5061
|
const timeoutMs = timeoutSec ? timeoutSec * 1e3 : DEFAULT_WAIT_TIMEOUT_MS;
|
|
3274
5062
|
const result = await waitForIngest(ctx, {
|
|
3275
5063
|
slug,
|
|
3276
|
-
|
|
5064
|
+
// Every document the files became: a mapped source can make several
|
|
5065
|
+
// from one file, or none (a deletes outcome).
|
|
5066
|
+
documentIds: results.filter((r) => !r.skipped).flatMap((r) => r.documents.map((d) => d.id)),
|
|
3277
5067
|
timeoutMs,
|
|
3278
5068
|
onProgress: progress
|
|
3279
5069
|
});
|
|
3280
|
-
if (result.status === "timeout") {
|
|
3281
|
-
out({
|
|
3282
|
-
dataSource: slug,
|
|
3283
|
-
status: result.status,
|
|
3284
|
-
error: result.error,
|
|
3285
|
-
documents: result.documents
|
|
3286
|
-
});
|
|
3287
|
-
process.exit(EXIT.timeout);
|
|
3288
|
-
}
|
|
3289
|
-
if (result.status === "error") {
|
|
3290
|
-
out({
|
|
3291
|
-
dataSource: slug,
|
|
3292
|
-
status: result.status,
|
|
3293
|
-
documents: result.documents
|
|
3294
|
-
});
|
|
3295
|
-
process.exit(EXIT.buildFailed);
|
|
3296
|
-
}
|
|
3297
5070
|
out({
|
|
3298
5071
|
dataSource: slug,
|
|
3299
5072
|
status: result.status,
|
|
5073
|
+
...result.error ? { error: result.error } : {},
|
|
3300
5074
|
documents: result.documents
|
|
3301
5075
|
});
|
|
5076
|
+
if (result.status === "timeout") {
|
|
5077
|
+
throw new CliError(result.error ?? "Timed out.", EXIT.timeout);
|
|
5078
|
+
}
|
|
5079
|
+
if (result.status === "error") {
|
|
5080
|
+
throw new CliError("At least one document failed.", EXIT.buildFailed);
|
|
5081
|
+
}
|
|
3302
5082
|
return;
|
|
3303
5083
|
}
|
|
3304
5084
|
out({ dataSource: slug, documents: results });
|
|
@@ -3316,11 +5096,32 @@ async function dataSourcesStatus(ctx, a) {
|
|
|
3316
5096
|
}
|
|
3317
5097
|
async function dataSourcesRm(ctx, a) {
|
|
3318
5098
|
const documentId = a.str("document");
|
|
3319
|
-
|
|
3320
|
-
|
|
5099
|
+
const rawFilter = a.str("filter");
|
|
5100
|
+
if (documentId && rawFilter) {
|
|
5101
|
+
fatal("Give --document or --filter, not both.");
|
|
5102
|
+
}
|
|
5103
|
+
if (documentId) {
|
|
5104
|
+
await rm(ctx, { slug: sourceOf(a), documentId });
|
|
5105
|
+
out({ deleted: documentId });
|
|
5106
|
+
return;
|
|
5107
|
+
}
|
|
5108
|
+
let filter;
|
|
5109
|
+
if (rawFilter.trimStart().startsWith("{")) {
|
|
5110
|
+
try {
|
|
5111
|
+
filter = JSON.parse(rawFilter);
|
|
5112
|
+
} catch (err) {
|
|
5113
|
+
fatal(`--filter is not valid JSON: ${err.message}`);
|
|
5114
|
+
}
|
|
5115
|
+
} else {
|
|
5116
|
+
filter = { metadata: parseKeyValuePairs(rawFilter, "filter") };
|
|
3321
5117
|
}
|
|
3322
|
-
|
|
3323
|
-
|
|
5118
|
+
const slug = sourceOf(a);
|
|
5119
|
+
const result = await rmWhereAll(ctx, {
|
|
5120
|
+
slug,
|
|
5121
|
+
filter,
|
|
5122
|
+
onProgress: progress
|
|
5123
|
+
});
|
|
5124
|
+
out({ dataSource: slug, filter, ...result });
|
|
3324
5125
|
}
|
|
3325
5126
|
async function dataSourcesDelete(ctx, a) {
|
|
3326
5127
|
const slug = a.str("source");
|
|
@@ -3335,8 +5136,8 @@ async function dataSourcesDelete(ctx, a) {
|
|
|
3335
5136
|
async function dataSourcesSearch(ctx, a) {
|
|
3336
5137
|
const topK = a.num("top-k");
|
|
3337
5138
|
const retrieval = {
|
|
3338
|
-
...
|
|
3339
|
-
...
|
|
5139
|
+
...triState2(a, "rerank", "rerank"),
|
|
5140
|
+
...triState2(a, "hybrid", "hybrid")
|
|
3340
5141
|
};
|
|
3341
5142
|
const mode = a.str("mode");
|
|
3342
5143
|
if (mode && !["hybrid", "semantic", "lexical"].includes(mode)) {
|
|
@@ -3408,7 +5209,7 @@ function parseKeyValuePairs(raw, flag) {
|
|
|
3408
5209
|
}
|
|
3409
5210
|
return pairs2;
|
|
3410
5211
|
}
|
|
3411
|
-
function
|
|
5212
|
+
function triState2(a, flag, key) {
|
|
3412
5213
|
const raw = a.str(flag);
|
|
3413
5214
|
if (raw === void 0) {
|
|
3414
5215
|
return {};
|
|
@@ -3440,11 +5241,11 @@ function configFromFlags(a) {
|
|
|
3440
5241
|
chunking.dropBlockTypes = dropBlocks.split(",").map((s) => s.trim()).filter(Boolean);
|
|
3441
5242
|
}
|
|
3442
5243
|
const contextual = {
|
|
3443
|
-
...
|
|
5244
|
+
...triState2(a, "contextual", "enabled"),
|
|
3444
5245
|
...a.str("contextual-model") ? { modelId: a.str("contextual-model") } : {}
|
|
3445
5246
|
};
|
|
3446
5247
|
const images = {
|
|
3447
|
-
...
|
|
5248
|
+
...triState2(a, "describe-images", "describe"),
|
|
3448
5249
|
...a.str("image-model") ? { modelId: a.str("image-model") } : {}
|
|
3449
5250
|
};
|
|
3450
5251
|
const embeddingModel = a.str("embedding-model");
|
|
@@ -3470,10 +5271,10 @@ function configFromFlags(a) {
|
|
|
3470
5271
|
ingest.extraction = { modelId: extractionModel };
|
|
3471
5272
|
}
|
|
3472
5273
|
const retrieval = {
|
|
3473
|
-
...
|
|
5274
|
+
...triState2(a, "hybrid", "hybrid")
|
|
3474
5275
|
};
|
|
3475
5276
|
const rerank = {
|
|
3476
|
-
...
|
|
5277
|
+
...triState2(a, "rerank", "enabled"),
|
|
3477
5278
|
...a.str("rerank-model") ? { modelId: a.str("rerank-model") } : {}
|
|
3478
5279
|
};
|
|
3479
5280
|
if (Object.keys(rerank).length) {
|
|
@@ -3491,6 +5292,7 @@ var placementOf = (a) => {
|
|
|
3491
5292
|
const flag = a.str("placement");
|
|
3492
5293
|
return flag === void 0 ? void 0 : flag === "shared" ? "shared" : { resourceId: flag };
|
|
3493
5294
|
};
|
|
5295
|
+
var MOVE_NOTE = "Moving in the background; `datasources list` shows progress, or re-run with `datasources move --wait`.";
|
|
3494
5296
|
async function dataSourcesCreate(ctx, a) {
|
|
3495
5297
|
const slug = a.str("source");
|
|
3496
5298
|
if (!slug) {
|
|
@@ -3499,13 +5301,14 @@ async function dataSourcesCreate(ctx, a) {
|
|
|
3499
5301
|
const { ingest } = configFromFlags(a);
|
|
3500
5302
|
const name = a.str("name");
|
|
3501
5303
|
const placement = placementOf(a);
|
|
5304
|
+
const result = await create2(ctx, {
|
|
5305
|
+
slug,
|
|
5306
|
+
...name ? { name } : {},
|
|
5307
|
+
...ingest ? { ingest } : {},
|
|
5308
|
+
...placement !== void 0 ? { placement } : {}
|
|
5309
|
+
});
|
|
3502
5310
|
out(
|
|
3503
|
-
|
|
3504
|
-
slug,
|
|
3505
|
-
...name ? { name } : {},
|
|
3506
|
-
...ingest ? { ingest } : {},
|
|
3507
|
-
...placement !== void 0 ? { placement } : {}
|
|
3508
|
-
})
|
|
5311
|
+
result.migration && !result.migration.error ? { ...result, note: MOVE_NOTE } : result
|
|
3509
5312
|
);
|
|
3510
5313
|
}
|
|
3511
5314
|
async function dataSourcesConfig(ctx, a) {
|
|
@@ -3516,15 +5319,49 @@ async function dataSourcesConfig(ctx, a) {
|
|
|
3516
5319
|
out(await configGet(ctx, slug));
|
|
3517
5320
|
return;
|
|
3518
5321
|
}
|
|
5322
|
+
const result = await configSet(ctx, {
|
|
5323
|
+
slug,
|
|
5324
|
+
...ingest ? { ingest } : {},
|
|
5325
|
+
...retrieval ? { retrieval } : {},
|
|
5326
|
+
...placement !== void 0 ? { placement } : {}
|
|
5327
|
+
});
|
|
3519
5328
|
out(
|
|
3520
|
-
|
|
3521
|
-
slug,
|
|
3522
|
-
...ingest ? { ingest } : {},
|
|
3523
|
-
...retrieval ? { retrieval } : {},
|
|
3524
|
-
...placement !== void 0 ? { placement } : {}
|
|
3525
|
-
})
|
|
5329
|
+
result.placementChanged && result.migration && !result.migration.error ? { ...result, note: MOVE_NOTE } : result
|
|
3526
5330
|
);
|
|
3527
5331
|
}
|
|
5332
|
+
async function dataSourcesMove(ctx, a) {
|
|
5333
|
+
const slug = sourceOf(a);
|
|
5334
|
+
const to = a.str("to");
|
|
5335
|
+
const placement = to === "shared" ? "shared" : { resourceId: to };
|
|
5336
|
+
const started = await move(ctx, { slug, placement });
|
|
5337
|
+
if (!started.migration || !a.bool("wait")) {
|
|
5338
|
+
out({
|
|
5339
|
+
dataSource: slug,
|
|
5340
|
+
...started,
|
|
5341
|
+
...started.migration ? { note: MOVE_NOTE } : {}
|
|
5342
|
+
});
|
|
5343
|
+
return;
|
|
5344
|
+
}
|
|
5345
|
+
const timeoutSec = a.num("timeout");
|
|
5346
|
+
const result = await waitForMove(ctx, {
|
|
5347
|
+
slug,
|
|
5348
|
+
...timeoutSec ? { timeoutMs: timeoutSec * 1e3 } : {},
|
|
5349
|
+
onProgress: progress
|
|
5350
|
+
});
|
|
5351
|
+
out({
|
|
5352
|
+
dataSource: slug,
|
|
5353
|
+
status: result.status,
|
|
5354
|
+
placement: result.source.placement,
|
|
5355
|
+
migration: result.source.migration,
|
|
5356
|
+
..."error" in result ? { error: result.error } : {}
|
|
5357
|
+
});
|
|
5358
|
+
if (result.status === "failed") {
|
|
5359
|
+
throw new CliError(result.error, EXIT.buildFailed);
|
|
5360
|
+
}
|
|
5361
|
+
if (result.status === "timeout") {
|
|
5362
|
+
throw new CliError(result.error, EXIT.timeout);
|
|
5363
|
+
}
|
|
5364
|
+
}
|
|
3528
5365
|
async function dataSourcesRevectorize(ctx, a) {
|
|
3529
5366
|
const slug = sourceOf(a);
|
|
3530
5367
|
const { ingest } = configFromFlags(a);
|
|
@@ -3537,42 +5374,24 @@ async function dataSourcesRevectorize(ctx, a) {
|
|
|
3537
5374
|
return;
|
|
3538
5375
|
}
|
|
3539
5376
|
const timeoutSec = a.num("timeout");
|
|
3540
|
-
const
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
});
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
process.exit(EXIT.buildFailed);
|
|
3559
|
-
}
|
|
3560
|
-
return;
|
|
3561
|
-
}
|
|
3562
|
-
if (Date.now() - start > timeoutMs) {
|
|
3563
|
-
out({
|
|
3564
|
-
dataSource: slug,
|
|
3565
|
-
status: "timeout",
|
|
3566
|
-
error: `Timed out after ${Math.round(timeoutMs / 1e3)}s with ${pending.length} document(s) still building`
|
|
3567
|
-
});
|
|
3568
|
-
process.exit(EXIT.timeout);
|
|
3569
|
-
}
|
|
3570
|
-
progress(
|
|
3571
|
-
`rebuilding\u2026 ${documents2.length - pending.length}/${documents2.length} done (${Math.round(
|
|
3572
|
-
(Date.now() - start) / 1e3
|
|
3573
|
-
)}s)`
|
|
3574
|
-
);
|
|
3575
|
-
await sleep(POLL_MS3);
|
|
5377
|
+
const result = await waitForCandidate(ctx, {
|
|
5378
|
+
slug,
|
|
5379
|
+
...timeoutSec ? { timeoutMs: timeoutSec * 1e3 } : {},
|
|
5380
|
+
onProgress: progress
|
|
5381
|
+
});
|
|
5382
|
+
if (result.status === "timeout") {
|
|
5383
|
+
out({ dataSource: slug, status: "timeout", error: result.error });
|
|
5384
|
+
throw new CliError(result.error ?? "Timed out.", EXIT.timeout);
|
|
5385
|
+
}
|
|
5386
|
+
out({
|
|
5387
|
+
dataSource: slug,
|
|
5388
|
+
candidateVersion: started.candidateVersion,
|
|
5389
|
+
status: result.status,
|
|
5390
|
+
documents: result.documents,
|
|
5391
|
+
note: result.status === "error" ? "Some documents failed. Promote with --force to accept, or fix and re-run." : "Run `promote` to make this version live."
|
|
5392
|
+
});
|
|
5393
|
+
if (result.status === "error") {
|
|
5394
|
+
throw new CliError("At least one document failed.", EXIT.buildFailed);
|
|
3576
5395
|
}
|
|
3577
5396
|
}
|
|
3578
5397
|
async function dataSourcesPromote(ctx, a) {
|
|
@@ -3600,6 +5419,7 @@ var dataSourcesHandlers = {
|
|
|
3600
5419
|
"datasources search": dataSourcesSearch,
|
|
3601
5420
|
"datasources create": dataSourcesCreate,
|
|
3602
5421
|
"datasources config": dataSourcesConfig,
|
|
5422
|
+
"datasources move": dataSourcesMove,
|
|
3603
5423
|
"datasources revectorize": dataSourcesRevectorize,
|
|
3604
5424
|
"datasources promote": dataSourcesPromote,
|
|
3605
5425
|
"datasources drop": dataSourcesDrop,
|
|
@@ -3614,10 +5434,22 @@ Subcommands:
|
|
|
3614
5434
|
add Add one or more documents (skips unchanged files)
|
|
3615
5435
|
list List data sources with document counts
|
|
3616
5436
|
status Show per-document ingest state
|
|
3617
|
-
rm Remove a document
|
|
5437
|
+
rm Remove a document, or every document matching a filter
|
|
3618
5438
|
search Query a corpus \u2014 useful to sanity-check one you just built
|
|
3619
5439
|
create Create an empty source, optionally on dedicated capacity
|
|
3620
5440
|
config Show or change how a corpus is processed and searched
|
|
5441
|
+
move Move a corpus between shared and dedicated capacity, data intact
|
|
5442
|
+
jobs Bulk-load a corpus from a file store or a manifest, with a plan first
|
|
5443
|
+
connect Follow an S3 bucket the customer owns (credentials = app secret names)
|
|
5444
|
+
sync Bring a connected source up to date with its bucket
|
|
5445
|
+
connector Show a source's connector and what it tracks
|
|
5446
|
+
disconnect Stop following the bucket; documents stay
|
|
5447
|
+
inspect Profile raw objects (key shapes, types, sizes, JSON keys) before writing a mapper
|
|
5448
|
+
map test Run a source's mapper over real objects and show the outcomes; nothing ingested
|
|
5449
|
+
remap Re-apply the live mapper to every raw copy the source holds
|
|
5450
|
+
hydrate Reload an evicted index ahead of the first search
|
|
5451
|
+
sample Draw a sample of a source into a new one with the same config
|
|
5452
|
+
eval Query sets and runs: measure recall, MRR and latency, compare versions
|
|
3621
5453
|
revectorize Rebuild a corpus under new settings, alongside the live one
|
|
3622
5454
|
promote Make a rebuilt version live
|
|
3623
5455
|
drop Discard a candidate or a superseded version
|
|
@@ -3627,10 +5459,15 @@ Usage:
|
|
|
3627
5459
|
remy-admin datasources add [--source <slug>] [--metadata <k=v,...>] [--wait] [--timeout <sec>] <file...>
|
|
3628
5460
|
remy-admin datasources list
|
|
3629
5461
|
remy-admin datasources status [--source <slug>]
|
|
3630
|
-
remy-admin datasources rm [--source <slug>] --document <id>
|
|
5462
|
+
remy-admin datasources rm [--source <slug>] (--document <id> | --filter <k=v,...|json>)
|
|
3631
5463
|
remy-admin datasources search [--source <slug>] [search options] <query>
|
|
3632
5464
|
remy-admin datasources create --source <slug> [--name <name>] [--placement <resource-id|shared>] [rebuild settings...]
|
|
3633
5465
|
remy-admin datasources config [--source <slug>] [--placement <resource-id|shared>] [settings...]
|
|
5466
|
+
remy-admin datasources move [--source <slug>] --to <resource-id|shared> [--wait] [--timeout <sec>]
|
|
5467
|
+
remy-admin datasources jobs start|list|status|approve|pause|resume|cancel \u2026 (see below)
|
|
5468
|
+
remy-admin datasources connect|sync|connector|disconnect \u2026 (see below)
|
|
5469
|
+
remy-admin datasources hydrate|sample \u2026 (see below)
|
|
5470
|
+
remy-admin datasources eval create|list|get|add|import|queries|rm|delete|run|runs|result|compare \u2026 (see below)
|
|
3634
5471
|
remy-admin datasources revectorize [--source <slug>] [settings...] [--wait]
|
|
3635
5472
|
remy-admin datasources promote [--source <slug>] [--force]
|
|
3636
5473
|
remy-admin datasources drop [--source <slug>] [--version <n>]
|
|
@@ -3701,16 +5538,33 @@ Tuning a corpus:
|
|
|
3701
5538
|
capacity needs, roughly in proportion.
|
|
3702
5539
|
--extraction-model <id>
|
|
3703
5540
|
|
|
3704
|
-
|
|
5541
|
+
Placement (shared pool vs dedicated capacity, see \`infra --help\`):
|
|
3705
5542
|
--source defaults to "${DEFAULT_SOURCE}" and is created on first use, on the
|
|
3706
|
-
shared pool.
|
|
3707
|
-
|
|
3708
|
-
|
|
5543
|
+
shared pool. Start a corpus on a resource with \`create --placement\`, or
|
|
5544
|
+
move one at any time with \`move --to <resource-id|shared>\`.
|
|
5545
|
+
A move copies the corpus's stored vectors onto the new capacity in the
|
|
5546
|
+
background: no re-extraction, no re-embedding, no charge beyond the
|
|
5547
|
+
resource itself. Search keeps working from the old placement until the
|
|
5548
|
+
copy lands; add, rm, config, revectorize and delete are refused with
|
|
5549
|
+
data_source_migrating until then. \`datasources list\` shows progress;
|
|
5550
|
+
\`move --wait\` blocks on it (exits ${EXIT.buildFailed} if the move failed,
|
|
5551
|
+
${EXIT.timeout} on timeout). Moving to \`shared\` is how a source leaves a
|
|
5552
|
+
resource you mean to destroy.
|
|
5553
|
+
A move is refused while documents are still building or a candidate version
|
|
5554
|
+
exists (data_source_busy), when the target is not active
|
|
5555
|
+
(capacity_<phase>), or when it would not fit (capacity_exceeded).
|
|
5556
|
+
|
|
5557
|
+
Notes:
|
|
3709
5558
|
--wait blocks until processing finishes, so you can search immediately after.
|
|
3710
5559
|
Exits ${EXIT.buildFailed} if a document failed, ${EXIT.timeout} on timeout.
|
|
3711
5560
|
Re-adding an unchanged file is free: no upload, no re-embedding. Re-adding
|
|
3712
5561
|
with a different --metadata updates the tags in place, also free.
|
|
3713
5562
|
--metadata tags documents for search-time filtering (scalars, \u226416 keys).
|
|
5563
|
+
rm --filter removes every matching document in pages of a thousand: k=v
|
|
5564
|
+
pairs match metadata; a JSON object takes the full selector \u2014 metadata,
|
|
5565
|
+
filename, documentIds, externalIdPrefix (the key a job or connector
|
|
5566
|
+
recorded, e.g. '{"externalIdPrefix":"archive/2019/"}'). An empty filter
|
|
5567
|
+
is refused; to remove everything, delete the source.
|
|
3714
5568
|
delete requires an explicit --source (no default) and refuses while documents
|
|
3715
5569
|
are still ingesting. Extraction caches survive deletion, so re-ingesting
|
|
3716
5570
|
the same files elsewhere costs no re-extraction.
|
|
@@ -3728,7 +5582,24 @@ Examples:
|
|
|
3728
5582
|
remy-admin datasources revectorize --source policies --max-chars 900 --wait
|
|
3729
5583
|
remy-admin datasources search --source policies --candidate "payment terms"
|
|
3730
5584
|
remy-admin datasources promote --source policies
|
|
3731
|
-
|
|
5585
|
+
|
|
5586
|
+
# Load a whole corpus: plan first, approve, watch it run
|
|
5587
|
+
remy-admin datasources jobs start --source archive --store raw --prefix 2024/ --wait
|
|
5588
|
+
remy-admin datasources jobs approve <id> --wait
|
|
5589
|
+
|
|
5590
|
+
# Follow a customer's bucket: secrets by name, first sync stops for approval
|
|
5591
|
+
remy-admin secrets set ARCHIVE_S3_KEY --prod <value> # never on a command line you share
|
|
5592
|
+
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
|
|
5593
|
+
remy-admin datasources sync --source archive --wait
|
|
5594
|
+
|
|
5595
|
+
# Measure before you promote: sample, build a query set, run live vs candidate
|
|
5596
|
+
remy-admin datasources sample --source archive --size 300 --wait
|
|
5597
|
+
remy-admin datasources eval create --source archive-sample --name base --size 150 --wait
|
|
5598
|
+
remy-admin datasources eval run --set <setId> --label live --wait
|
|
5599
|
+
remy-admin datasources revectorize --source archive-sample --max-chars 900 --wait
|
|
5600
|
+
remy-admin datasources eval run --set <setId> --candidate --label small-chunks --wait
|
|
5601
|
+
remy-admin datasources eval compare <runA> <runB>
|
|
5602
|
+
${dataSourceJobsHelp}${dataSourceConnectorsHelp}${dataSourceMappersHelp}${dataSourceEvalsHelp}`;
|
|
3732
5603
|
|
|
3733
5604
|
// src/ops/voice.ts
|
|
3734
5605
|
function normalizeE164(input) {
|
|
@@ -4303,7 +6174,7 @@ function runs(ctx, params = {}) {
|
|
|
4303
6174
|
`/_internal/v2/apps/${ctx.appId}/jewels/training-runs${qs(params)}`
|
|
4304
6175
|
);
|
|
4305
6176
|
}
|
|
4306
|
-
function
|
|
6177
|
+
function getRun2(ctx, runId) {
|
|
4307
6178
|
return call(
|
|
4308
6179
|
ctx,
|
|
4309
6180
|
"GET",
|
|
@@ -4463,9 +6334,9 @@ async function jewelsTimeseries(ctx, a) {
|
|
|
4463
6334
|
);
|
|
4464
6335
|
}
|
|
4465
6336
|
async function jewelsResolve(ctx, a) {
|
|
4466
|
-
const
|
|
6337
|
+
const approve2 = a.bool("approve");
|
|
4467
6338
|
const dismiss = a.bool("dismiss");
|
|
4468
|
-
if (
|
|
6339
|
+
if (approve2 === dismiss) {
|
|
4469
6340
|
fatal("Pass exactly one of --approve or --dismiss.");
|
|
4470
6341
|
}
|
|
4471
6342
|
if (dismiss && a.str("input") !== void 0) {
|
|
@@ -4473,7 +6344,7 @@ async function jewelsResolve(ctx, a) {
|
|
|
4473
6344
|
}
|
|
4474
6345
|
const params = {
|
|
4475
6346
|
itemId: a.req("itemId"),
|
|
4476
|
-
action:
|
|
6347
|
+
action: approve2 ? "approve" : "dismiss"
|
|
4477
6348
|
};
|
|
4478
6349
|
if (a.str("input") !== void 0) {
|
|
4479
6350
|
params.input = parseJsonFlag(a, "input");
|
|
@@ -4521,28 +6392,28 @@ async function jewelsTrain(ctx, a) {
|
|
|
4521
6392
|
let lastLogLen = 0;
|
|
4522
6393
|
while (Date.now() < deadline) {
|
|
4523
6394
|
await sleep(TRAIN_POLL_MS);
|
|
4524
|
-
const { run:
|
|
4525
|
-
const log = Array.isArray(
|
|
6395
|
+
const { run: run3 } = await getRun2(ctx, runId);
|
|
6396
|
+
const log = Array.isArray(run3.log) ? run3.log : [];
|
|
4526
6397
|
const fresh = log.slice(lastLogLen);
|
|
4527
6398
|
lastLogLen = log.length;
|
|
4528
6399
|
const isStatus = (x) => x?.kind === "status";
|
|
4529
6400
|
const isLoss = (x) => x?.kind === "loss";
|
|
4530
6401
|
for (const e of fresh.filter(isStatus)) {
|
|
4531
|
-
out({ status:
|
|
6402
|
+
out({ status: run3.status, at: e.ts, message: e.message });
|
|
4532
6403
|
}
|
|
4533
6404
|
const losses = fresh.filter(isLoss);
|
|
4534
6405
|
if (losses.length) {
|
|
4535
6406
|
const last = losses[losses.length - 1];
|
|
4536
|
-
const p =
|
|
6407
|
+
const p = run3.progress;
|
|
4537
6408
|
out({
|
|
4538
|
-
status:
|
|
6409
|
+
status: run3.status,
|
|
4539
6410
|
step: `${last.step}/${p?.totalSteps ?? "?"}`,
|
|
4540
6411
|
loss: last.loss
|
|
4541
6412
|
});
|
|
4542
6413
|
}
|
|
4543
|
-
if (
|
|
4544
|
-
out(compactRunLog(
|
|
4545
|
-
if (
|
|
6414
|
+
if (run3.status === "complete" || run3.status === "failed") {
|
|
6415
|
+
out(compactRunLog(run3));
|
|
6416
|
+
if (run3.status === "failed") {
|
|
4546
6417
|
process.exitCode = 1;
|
|
4547
6418
|
}
|
|
4548
6419
|
return;
|
|
@@ -4552,19 +6423,19 @@ async function jewelsTrain(ctx, a) {
|
|
|
4552
6423
|
`Timed out waiting for run ${runId} (still in flight; check 'jewels run ${runId}').`
|
|
4553
6424
|
);
|
|
4554
6425
|
}
|
|
4555
|
-
function compactRunLog(
|
|
4556
|
-
let out2 = { ...
|
|
4557
|
-
if (Array.isArray(
|
|
4558
|
-
const statusEntries =
|
|
4559
|
-
const lossPoints =
|
|
6426
|
+
function compactRunLog(run3) {
|
|
6427
|
+
let out2 = { ...run3 };
|
|
6428
|
+
if (Array.isArray(run3.log)) {
|
|
6429
|
+
const statusEntries = run3.log.filter((e) => e?.kind === "status");
|
|
6430
|
+
const lossPoints = run3.log.length - statusEntries.length;
|
|
4560
6431
|
out2 = { ...out2, log: { statusEntries, lossPoints } };
|
|
4561
6432
|
}
|
|
4562
|
-
if (Array.isArray(
|
|
6433
|
+
if (Array.isArray(run3.report?.candidates)) {
|
|
4563
6434
|
out2 = {
|
|
4564
6435
|
...out2,
|
|
4565
6436
|
report: {
|
|
4566
|
-
...
|
|
4567
|
-
candidates:
|
|
6437
|
+
...run3.report,
|
|
6438
|
+
candidates: run3.report.candidates.map(
|
|
4568
6439
|
(c) => Array.isArray(c?.predictions) ? { ...c, predictions: c.predictions.length } : c
|
|
4569
6440
|
)
|
|
4570
6441
|
}
|
|
@@ -4581,8 +6452,8 @@ async function jewelsRuns(ctx, a) {
|
|
|
4581
6452
|
);
|
|
4582
6453
|
}
|
|
4583
6454
|
async function jewelsRunGet(ctx, a) {
|
|
4584
|
-
const { run:
|
|
4585
|
-
out({ run: compactRunLog(
|
|
6455
|
+
const { run: run3 } = await getRun2(ctx, a.req("runId"));
|
|
6456
|
+
out({ run: compactRunLog(run3) });
|
|
4586
6457
|
}
|
|
4587
6458
|
async function jewelsGrade(ctx, a) {
|
|
4588
6459
|
out(await grade(ctx, a.req("runId")));
|
|
@@ -5029,14 +6900,14 @@ async function resolveDomainId(ctx, direction, domain) {
|
|
|
5029
6900
|
}
|
|
5030
6901
|
return match.id;
|
|
5031
6902
|
}
|
|
5032
|
-
function
|
|
6903
|
+
function list10(ctx, params = {}) {
|
|
5033
6904
|
return call(
|
|
5034
6905
|
ctx,
|
|
5035
6906
|
"GET",
|
|
5036
6907
|
`/_internal/v2/apps/${ctx.appId}/outbound-email/messages${qs(params)}`
|
|
5037
6908
|
);
|
|
5038
6909
|
}
|
|
5039
|
-
function
|
|
6910
|
+
function get9(ctx, messageId) {
|
|
5040
6911
|
return call(
|
|
5041
6912
|
ctx,
|
|
5042
6913
|
"GET",
|
|
@@ -5228,7 +7099,7 @@ var emailSpecs = {
|
|
|
5228
7099
|
};
|
|
5229
7100
|
async function emailList(ctx, a) {
|
|
5230
7101
|
out(
|
|
5231
|
-
await
|
|
7102
|
+
await list10(ctx, {
|
|
5232
7103
|
status: a.str("status"),
|
|
5233
7104
|
kind: a.str("kind"),
|
|
5234
7105
|
recipient: a.str("recipient"),
|
|
@@ -5242,7 +7113,7 @@ async function emailList(ctx, a) {
|
|
|
5242
7113
|
);
|
|
5243
7114
|
}
|
|
5244
7115
|
async function emailGet(ctx, a) {
|
|
5245
|
-
out(await
|
|
7116
|
+
out(await get9(ctx, a.req("messageId")));
|
|
5246
7117
|
}
|
|
5247
7118
|
async function emailStats(ctx, a) {
|
|
5248
7119
|
out(await stats2(ctx, { start: a.str("start"), end: a.str("end") }));
|
|
@@ -5418,14 +7289,14 @@ Examples:
|
|
|
5418
7289
|
`;
|
|
5419
7290
|
|
|
5420
7291
|
// src/ops/cron.ts
|
|
5421
|
-
function
|
|
7292
|
+
function list11(ctx, params = {}) {
|
|
5422
7293
|
return call(
|
|
5423
7294
|
ctx,
|
|
5424
7295
|
"GET",
|
|
5425
7296
|
`/_internal/v2/apps/${ctx.appId}/cron/overview${qs(params)}`
|
|
5426
7297
|
);
|
|
5427
7298
|
}
|
|
5428
|
-
function
|
|
7299
|
+
function run2(ctx, route) {
|
|
5429
7300
|
return call(
|
|
5430
7301
|
ctx,
|
|
5431
7302
|
"POST",
|
|
@@ -5447,10 +7318,10 @@ var cronSpecs = {
|
|
|
5447
7318
|
}
|
|
5448
7319
|
};
|
|
5449
7320
|
async function cronList(ctx, a) {
|
|
5450
|
-
out(await
|
|
7321
|
+
out(await list11(ctx, { runs: a.num("runs") }));
|
|
5451
7322
|
}
|
|
5452
7323
|
async function cronRun(ctx, a) {
|
|
5453
|
-
out(await
|
|
7324
|
+
out(await run2(ctx, a.req("route")));
|
|
5454
7325
|
}
|
|
5455
7326
|
var cronHandlers = {
|
|
5456
7327
|
"cron list": cronList,
|
|
@@ -5481,27 +7352,27 @@ Examples:
|
|
|
5481
7352
|
remy-admin cron run dailyDigest`;
|
|
5482
7353
|
|
|
5483
7354
|
// src/ops/infra.ts
|
|
5484
|
-
var
|
|
5485
|
-
var
|
|
5486
|
-
function
|
|
7355
|
+
var DEFAULT_WAIT_TIMEOUT_MS4 = 10 * 60 * 1e3;
|
|
7356
|
+
var POLL_MS5 = 3e3;
|
|
7357
|
+
function base6(appId) {
|
|
5487
7358
|
return `/_internal/v2/apps/${appId}/infra`;
|
|
5488
7359
|
}
|
|
5489
|
-
function
|
|
7360
|
+
function list12(ctx, params) {
|
|
5490
7361
|
return call(
|
|
5491
7362
|
ctx,
|
|
5492
7363
|
"GET",
|
|
5493
|
-
`${
|
|
7364
|
+
`${base6(ctx.appId)}${params?.includeDestroyed ? "?includeDestroyed=true" : ""}`
|
|
5494
7365
|
);
|
|
5495
7366
|
}
|
|
5496
|
-
function
|
|
7367
|
+
function get10(ctx, params) {
|
|
5497
7368
|
return call(
|
|
5498
7369
|
ctx,
|
|
5499
7370
|
"GET",
|
|
5500
|
-
`${
|
|
7371
|
+
`${base6(ctx.appId)}/${encodeURIComponent(params.id)}`
|
|
5501
7372
|
);
|
|
5502
7373
|
}
|
|
5503
7374
|
function provision(ctx, params) {
|
|
5504
|
-
return call(ctx, "POST",
|
|
7375
|
+
return call(ctx, "POST", base6(ctx.appId), {
|
|
5505
7376
|
offeringId: params.offeringId,
|
|
5506
7377
|
...params.name ? { name: params.name } : {}
|
|
5507
7378
|
});
|
|
@@ -5510,77 +7381,69 @@ function hibernate(ctx, params) {
|
|
|
5510
7381
|
return call(
|
|
5511
7382
|
ctx,
|
|
5512
7383
|
"POST",
|
|
5513
|
-
`${
|
|
7384
|
+
`${base6(ctx.appId)}/${encodeURIComponent(params.id)}/hibernate`
|
|
5514
7385
|
);
|
|
5515
7386
|
}
|
|
5516
|
-
function
|
|
7387
|
+
function resume2(ctx, params) {
|
|
5517
7388
|
return call(
|
|
5518
7389
|
ctx,
|
|
5519
7390
|
"POST",
|
|
5520
|
-
`${
|
|
7391
|
+
`${base6(ctx.appId)}/${encodeURIComponent(params.id)}/resume`
|
|
5521
7392
|
);
|
|
5522
7393
|
}
|
|
5523
7394
|
function destroy(ctx, params) {
|
|
5524
7395
|
return call(
|
|
5525
7396
|
ctx,
|
|
5526
7397
|
"POST",
|
|
5527
|
-
`${
|
|
7398
|
+
`${base6(ctx.appId)}/${encodeURIComponent(params.id)}/destroy`
|
|
7399
|
+
);
|
|
7400
|
+
}
|
|
7401
|
+
function resize(ctx, params) {
|
|
7402
|
+
return call(
|
|
7403
|
+
ctx,
|
|
7404
|
+
"POST",
|
|
7405
|
+
`${base6(ctx.appId)}/${encodeURIComponent(params.id)}/resize`,
|
|
7406
|
+
{ offeringId: params.offeringId }
|
|
5528
7407
|
);
|
|
5529
7408
|
}
|
|
5530
7409
|
function rename(ctx, params) {
|
|
5531
7410
|
return call(
|
|
5532
7411
|
ctx,
|
|
5533
7412
|
"POST",
|
|
5534
|
-
`${
|
|
7413
|
+
`${base6(ctx.appId)}/${encodeURIComponent(params.id)}/rename`,
|
|
5535
7414
|
{ name: params.name }
|
|
5536
7415
|
);
|
|
5537
7416
|
}
|
|
5538
7417
|
async function waitForPhase(ctx, params) {
|
|
5539
|
-
const timeoutMs = params.timeoutMs ??
|
|
7418
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS4;
|
|
5540
7419
|
const wanted = new Set(params.phases);
|
|
5541
|
-
const
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
throw err;
|
|
5551
|
-
}
|
|
5552
|
-
transientSince ??= Date.now();
|
|
5553
|
-
if (Date.now() - transientSince > TRANSIENT_GRACE_MS) {
|
|
5554
|
-
throw err;
|
|
5555
|
-
}
|
|
5556
|
-
params.onProgress?.(
|
|
5557
|
-
`retrying after a gateway error\u2026 (${Math.round((Date.now() - start) / 1e3)}s)`
|
|
5558
|
-
);
|
|
5559
|
-
await sleep(POLL_MS4);
|
|
5560
|
-
continue;
|
|
5561
|
-
}
|
|
5562
|
-
if (wanted.has(resource.phase)) {
|
|
5563
|
-
return { status: "done", resource };
|
|
5564
|
-
}
|
|
5565
|
-
if (resource.phase === "failed") {
|
|
5566
|
-
return {
|
|
5567
|
-
status: "failed",
|
|
5568
|
-
resource,
|
|
5569
|
-
error: resource.lastError ?? "The platform could not complete the change."
|
|
5570
|
-
};
|
|
5571
|
-
}
|
|
5572
|
-
if (Date.now() - start > timeoutMs) {
|
|
5573
|
-
return {
|
|
5574
|
-
status: "timeout",
|
|
5575
|
-
resource,
|
|
5576
|
-
error: `Timed out after ${Math.round(timeoutMs / 1e3)}s; resource is still ${resource.phase}`
|
|
5577
|
-
};
|
|
7420
|
+
const arrived = (resource2) => wanted.has(resource2.phase) && (params.until?.(resource2) ?? true);
|
|
7421
|
+
const outcome = await pollUntil(
|
|
7422
|
+
async () => (await get10(ctx, { id: params.id })).resource,
|
|
7423
|
+
(resource2) => arrived(resource2) || resource2.phase === "failed",
|
|
7424
|
+
{
|
|
7425
|
+
timeoutMs,
|
|
7426
|
+
pollMs: POLL_MS5,
|
|
7427
|
+
describe: (resource2, start2) => `${resource2.phase}${resource2.detail ? ` \xB7 ${resource2.detail}` : ""}${resource2.resizingTo ? ` \xB7 resizing to ${resource2.resizingTo.label}` : ""}\u2026 (${elapsedSeconds(start2)}s)`,
|
|
7428
|
+
onProgress: params.onProgress
|
|
5578
7429
|
}
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
7430
|
+
);
|
|
7431
|
+
const resource = outcome.value;
|
|
7432
|
+
if (!outcome.settled) {
|
|
7433
|
+
return {
|
|
7434
|
+
status: "timeout",
|
|
7435
|
+
resource,
|
|
7436
|
+
error: timedOut(timeoutMs, `; resource is still ${resource.phase}`)
|
|
7437
|
+
};
|
|
5583
7438
|
}
|
|
7439
|
+
if (arrived(resource)) {
|
|
7440
|
+
return { status: "done", resource };
|
|
7441
|
+
}
|
|
7442
|
+
return {
|
|
7443
|
+
status: "failed",
|
|
7444
|
+
resource,
|
|
7445
|
+
error: resource.lastError ?? "The platform could not complete the change."
|
|
7446
|
+
};
|
|
5584
7447
|
}
|
|
5585
7448
|
|
|
5586
7449
|
// src/commands/infra.ts
|
|
@@ -5626,13 +7489,27 @@ var infraSpecs = {
|
|
|
5626
7489
|
positionals: [{ name: "id", required: true }],
|
|
5627
7490
|
flags: { name: { type: "string" } },
|
|
5628
7491
|
requireAnyOf: { flags: ["name"], message: "--name is required." }
|
|
7492
|
+
},
|
|
7493
|
+
"infra resize": {
|
|
7494
|
+
usage: "Usage: remy-admin infra resize <id> --offering <id> [--wait] [--timeout <sec>]",
|
|
7495
|
+
positionals: [{ name: "id", required: true }],
|
|
7496
|
+
flags: {
|
|
7497
|
+
offering: { type: "string" },
|
|
7498
|
+
wait: { type: "boolean" },
|
|
7499
|
+
timeout: { type: "string" }
|
|
7500
|
+
},
|
|
7501
|
+
requireAnyOf: {
|
|
7502
|
+
flags: ["offering"],
|
|
7503
|
+
message: "--offering is required (see `infra list` for the sizes)."
|
|
7504
|
+
}
|
|
5629
7505
|
}
|
|
5630
7506
|
};
|
|
5631
|
-
async function
|
|
7507
|
+
async function waitAndReport2(ctx, a, id, phases, summary2, until) {
|
|
5632
7508
|
const timeoutSec = a.str("timeout");
|
|
5633
7509
|
const result = await waitForPhase(ctx, {
|
|
5634
7510
|
id,
|
|
5635
7511
|
phases,
|
|
7512
|
+
...until ? { until } : {},
|
|
5636
7513
|
...timeoutSec ? { timeoutMs: Number(timeoutSec) * 1e3 } : {},
|
|
5637
7514
|
onProgress: progress
|
|
5638
7515
|
});
|
|
@@ -5645,10 +7522,22 @@ async function waitAndReport(ctx, a, id, phases, summary2) {
|
|
|
5645
7522
|
}
|
|
5646
7523
|
}
|
|
5647
7524
|
async function infraList(ctx, a) {
|
|
5648
|
-
out(await
|
|
7525
|
+
out(await list12(ctx, { includeDestroyed: a.bool("include-destroyed") }));
|
|
5649
7526
|
}
|
|
5650
7527
|
async function infraGet(ctx, a) {
|
|
5651
|
-
out(await
|
|
7528
|
+
out(await get10(ctx, { id: a.req("id") }));
|
|
7529
|
+
}
|
|
7530
|
+
function pricingOf(resource) {
|
|
7531
|
+
const offering = resource.offering;
|
|
7532
|
+
if (!offering) {
|
|
7533
|
+
return null;
|
|
7534
|
+
}
|
|
7535
|
+
return {
|
|
7536
|
+
offering: offering.id,
|
|
7537
|
+
activeMonthlyDollars: offering.monthlyPriceDollars,
|
|
7538
|
+
hibernatedMonthlyDollars: offering.hibernatedMonthlyPriceDollars,
|
|
7539
|
+
note: "Bills hourly from activation. A month's credits were verified before the lease was accepted."
|
|
7540
|
+
};
|
|
5652
7541
|
}
|
|
5653
7542
|
async function infraProvision(ctx, a) {
|
|
5654
7543
|
const offeringId = a.str("offering");
|
|
@@ -5657,14 +7546,19 @@ async function infraProvision(ctx, a) {
|
|
|
5657
7546
|
offeringId,
|
|
5658
7547
|
...name ? { name } : {}
|
|
5659
7548
|
});
|
|
7549
|
+
const pricing = pricingOf(resource);
|
|
5660
7550
|
if (!a.bool("wait")) {
|
|
5661
7551
|
out({
|
|
5662
7552
|
resource,
|
|
7553
|
+
pricing,
|
|
5663
7554
|
note: "Provisioning; poll `infra get <id>` or re-run with --wait."
|
|
5664
7555
|
});
|
|
5665
7556
|
return;
|
|
5666
7557
|
}
|
|
5667
|
-
await
|
|
7558
|
+
await waitAndReport2(ctx, a, resource.id, ["active"], {
|
|
7559
|
+
action: "provision",
|
|
7560
|
+
pricing
|
|
7561
|
+
});
|
|
5668
7562
|
}
|
|
5669
7563
|
async function infraHibernate(ctx, a) {
|
|
5670
7564
|
const id = a.req("id");
|
|
@@ -5673,16 +7567,16 @@ async function infraHibernate(ctx, a) {
|
|
|
5673
7567
|
out({ resource });
|
|
5674
7568
|
return;
|
|
5675
7569
|
}
|
|
5676
|
-
await
|
|
7570
|
+
await waitAndReport2(ctx, a, id, ["hibernated"], { action: "hibernate" });
|
|
5677
7571
|
}
|
|
5678
7572
|
async function infraResume(ctx, a) {
|
|
5679
7573
|
const id = a.req("id");
|
|
5680
|
-
const { resource } = await
|
|
7574
|
+
const { resource } = await resume2(ctx, { id });
|
|
5681
7575
|
if (!a.bool("wait")) {
|
|
5682
7576
|
out({ resource });
|
|
5683
7577
|
return;
|
|
5684
7578
|
}
|
|
5685
|
-
await
|
|
7579
|
+
await waitAndReport2(ctx, a, id, ["active"], { action: "resume" });
|
|
5686
7580
|
}
|
|
5687
7581
|
async function infraDestroy(ctx, a) {
|
|
5688
7582
|
const id = a.req("id");
|
|
@@ -5691,7 +7585,7 @@ async function infraDestroy(ctx, a) {
|
|
|
5691
7585
|
out({ resource });
|
|
5692
7586
|
return;
|
|
5693
7587
|
}
|
|
5694
|
-
await
|
|
7588
|
+
await waitAndReport2(ctx, a, id, ["destroyed"], { action: "destroy" });
|
|
5695
7589
|
}
|
|
5696
7590
|
async function infraRename(ctx, a) {
|
|
5697
7591
|
out(
|
|
@@ -5701,6 +7595,33 @@ async function infraRename(ctx, a) {
|
|
|
5701
7595
|
})
|
|
5702
7596
|
);
|
|
5703
7597
|
}
|
|
7598
|
+
async function infraResize(ctx, a) {
|
|
7599
|
+
const id = a.req("id");
|
|
7600
|
+
const { resource } = await resize(ctx, {
|
|
7601
|
+
id,
|
|
7602
|
+
offeringId: a.str("offering")
|
|
7603
|
+
});
|
|
7604
|
+
const pricing = pricingOf({
|
|
7605
|
+
...resource,
|
|
7606
|
+
offering: resource.resizingTo ?? resource.offering
|
|
7607
|
+
});
|
|
7608
|
+
if (!a.bool("wait")) {
|
|
7609
|
+
out({
|
|
7610
|
+
resource,
|
|
7611
|
+
pricing,
|
|
7612
|
+
note: "Resizing; searches on its sources pause while it is parked and restored. Poll `infra get <id>` or re-run with --wait."
|
|
7613
|
+
});
|
|
7614
|
+
return;
|
|
7615
|
+
}
|
|
7616
|
+
await waitAndReport2(
|
|
7617
|
+
ctx,
|
|
7618
|
+
a,
|
|
7619
|
+
id,
|
|
7620
|
+
[resource.phase],
|
|
7621
|
+
{ action: "resize", pricing },
|
|
7622
|
+
(r) => r.resizingTo === null
|
|
7623
|
+
);
|
|
7624
|
+
}
|
|
5704
7625
|
var infraHandlers = {
|
|
5705
7626
|
"infra list": infraList,
|
|
5706
7627
|
"infra get": infraGet,
|
|
@@ -5708,7 +7629,8 @@ var infraHandlers = {
|
|
|
5708
7629
|
"infra hibernate": infraHibernate,
|
|
5709
7630
|
"infra resume": infraResume,
|
|
5710
7631
|
"infra destroy": infraDestroy,
|
|
5711
|
-
"infra rename": infraRename
|
|
7632
|
+
"infra rename": infraRename,
|
|
7633
|
+
"infra resize": infraResize
|
|
5712
7634
|
};
|
|
5713
7635
|
var infraHelp = `remy-admin infra \u2014 Dedicated infrastructure your app leases from the platform.
|
|
5714
7636
|
|
|
@@ -5717,16 +7639,18 @@ for data sources, isolated from the shared pool. Sizes and prices come from the
|
|
|
5717
7639
|
platform catalog (\`infra list\` prints them); nothing is priced client-side.
|
|
5718
7640
|
|
|
5719
7641
|
A resource bills by the hour from activation, at the retained-storage rate while
|
|
5720
|
-
hibernated, and not at all once destroyed. Provisioning needs a
|
|
5721
|
-
credits available in the workspace
|
|
5722
|
-
|
|
7642
|
+
hibernated, and not at all once destroyed. Provisioning (and growing) needs a
|
|
7643
|
+
month's worth of credits available in the workspace, checked before the request
|
|
7644
|
+
is accepted; nothing is charged until activation. The provision output prints
|
|
7645
|
+
the price it committed to. Every action below is audited.
|
|
5723
7646
|
|
|
5724
7647
|
Subcommands:
|
|
5725
7648
|
list Resources on this app, plus the offerings you can provision
|
|
5726
7649
|
get One resource with its state timeline and attached sources
|
|
5727
|
-
provision Lease a new resource (
|
|
7650
|
+
provision Lease a new resource (needs a month's credits; prints the price)
|
|
5728
7651
|
hibernate Park it: data kept, searches paused, compute charge stops
|
|
5729
7652
|
resume Bring a hibernated resource back
|
|
7653
|
+
resize Change its size, data intact (passes through hibernated)
|
|
5730
7654
|
destroy Delete it (refused while data sources are placed on it)
|
|
5731
7655
|
rename Change the display name
|
|
5732
7656
|
|
|
@@ -5736,26 +7660,44 @@ Usage:
|
|
|
5736
7660
|
remy-admin infra provision --offering <id> [--name <name>] [--wait] [--timeout <sec>]
|
|
5737
7661
|
remy-admin infra hibernate <id> [--wait] [--timeout <sec>]
|
|
5738
7662
|
remy-admin infra resume <id> [--wait] [--timeout <sec>]
|
|
7663
|
+
remy-admin infra resize <id> --offering <id> [--wait] [--timeout <sec>]
|
|
5739
7664
|
remy-admin infra destroy <id> [--wait] [--timeout <sec>]
|
|
5740
7665
|
remy-admin infra rename <id> --name <name>
|
|
5741
7666
|
|
|
5742
7667
|
Placing a data source on a resource:
|
|
5743
7668
|
remy-admin datasources create --source <slug> --placement <resource-id>
|
|
5744
|
-
remy-admin datasources
|
|
5745
|
-
remy-admin datasources config --source <slug> --placement shared
|
|
7669
|
+
remy-admin datasources move --source <slug> --to <resource-id|shared> [--wait]
|
|
5746
7670
|
|
|
5747
7671
|
Start a new corpus on a resource with \`create\`; \`add\` creates a source on
|
|
5748
|
-
the shared pool.
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
7672
|
+
the shared pool. A populated source moves with \`datasources move\`: its
|
|
7673
|
+
stored vectors are copied onto the new capacity in the background (no
|
|
7674
|
+
re-embedding), search keeps working from the old placement until the copy
|
|
7675
|
+
lands, and adding or removing documents is refused with
|
|
7676
|
+
data_source_migrating until it finishes. Moving to \`shared\` is how a source
|
|
7677
|
+
leaves a resource you mean to destroy. See \`datasources --help\`.
|
|
7678
|
+
|
|
7679
|
+
Searches on a source whose resource is not active fail with
|
|
7680
|
+
capacity_<phase>: capacity_hibernated once parked, capacity_hibernating /
|
|
7681
|
+
capacity_resuming while it moves. \`active\` means searchable: a resumed
|
|
7682
|
+
resource reports active only once its data is restored. capacity_restoring
|
|
7683
|
+
is the rare case of an active instance that was replaced (node loss) and is
|
|
7684
|
+
refilling from its snapshot. The messages are written for the app's end
|
|
7685
|
+
user; the stable code is what to act on.
|
|
7686
|
+
|
|
7687
|
+
Resizing:
|
|
7688
|
+
A resize keeps the data. The resource is parked (a fresh snapshot), its spec
|
|
7689
|
+
is swapped, and it comes back up restored from that snapshot, so searches on
|
|
7690
|
+
its sources pause for the few minutes it takes. A hibernated resource swaps
|
|
7691
|
+
in place and stays parked. Growing needs a month's credits at the new rate;
|
|
7692
|
+
shrinking is refused below what the resource holds (resize_too_small).
|
|
7693
|
+
\`infra get\` shows resizingTo until the swap has happened.
|
|
7694
|
+
|
|
7695
|
+
Phases and timing:
|
|
5756
7696
|
requested \u2192 provisioning \u2192 active \u21C4 hibernated \u2192 destroyed, with hibernating /
|
|
5757
7697
|
resuming / decommissioning in between and failed when a change cannot complete
|
|
5758
|
-
(retried automatically; resume or hibernate to retry by hand).
|
|
7698
|
+
(retried automatically; resume or hibernate to retry by hand). Each transition
|
|
7699
|
+
takes about two minutes, more with more data: snapshots and restores scale
|
|
7700
|
+
with the collection. --wait defaults to 600s; pass --timeout for a large one.
|
|
5759
7701
|
|
|
5760
7702
|
--wait exit codes: 0 reached the target phase \xB7 1 the platform reported failure \xB7
|
|
5761
7703
|
2 still transitioning when --timeout elapsed (default 600s).`;
|
|
@@ -5777,6 +7719,10 @@ var SPECS = {
|
|
|
5777
7719
|
...prerenderSpecs,
|
|
5778
7720
|
...filesSpecs,
|
|
5779
7721
|
...dataSourcesSpecs,
|
|
7722
|
+
...dataSourceJobsSpecs,
|
|
7723
|
+
...dataSourceConnectorsSpecs,
|
|
7724
|
+
...dataSourceEvalsSpecs,
|
|
7725
|
+
...dataSourceMappersSpecs,
|
|
5780
7726
|
...voiceSpecs,
|
|
5781
7727
|
...agentSpecs,
|
|
5782
7728
|
...eventsSpecs,
|
|
@@ -5802,6 +7748,10 @@ var HANDLERS = {
|
|
|
5802
7748
|
...prerenderHandlers,
|
|
5803
7749
|
...filesHandlers,
|
|
5804
7750
|
...dataSourcesHandlers,
|
|
7751
|
+
...dataSourceJobsHandlers,
|
|
7752
|
+
...dataSourceConnectorsHandlers,
|
|
7753
|
+
...dataSourceEvalsHandlers,
|
|
7754
|
+
...dataSourceMappersHandlers,
|
|
5805
7755
|
...voiceHandlers,
|
|
5806
7756
|
...agentHandlers,
|
|
5807
7757
|
...eventsHandlers,
|
|
@@ -5838,8 +7788,8 @@ var GROUP_HELP = {
|
|
|
5838
7788
|
};
|
|
5839
7789
|
|
|
5840
7790
|
// src/ctx.ts
|
|
5841
|
-
import
|
|
5842
|
-
import
|
|
7791
|
+
import fs6 from "fs";
|
|
7792
|
+
import path6 from "path";
|
|
5843
7793
|
|
|
5844
7794
|
// src/parseJsonConfig.ts
|
|
5845
7795
|
import JSON5 from "json5";
|
|
@@ -5868,10 +7818,10 @@ function parseJsonConfig(raw) {
|
|
|
5868
7818
|
// src/ctx.ts
|
|
5869
7819
|
var DEFAULT_BASE_URL = "https://api.mindstudio.ai";
|
|
5870
7820
|
function loadWorkspaceAppId(workspaceDir = WORKSPACE_DIR) {
|
|
5871
|
-
const manifestPath =
|
|
7821
|
+
const manifestPath = path6.join(workspaceDir, "mindstudio.json");
|
|
5872
7822
|
let raw;
|
|
5873
7823
|
try {
|
|
5874
|
-
raw =
|
|
7824
|
+
raw = fs6.readFileSync(manifestPath, "utf-8");
|
|
5875
7825
|
} catch (err) {
|
|
5876
7826
|
if (err.code === "ENOENT") {
|
|
5877
7827
|
throw new Error(`mindstudio.json not found at ${manifestPath}`);
|
|
@@ -5916,6 +7866,7 @@ Commands:
|
|
|
5916
7866
|
settings App settings \u2014 signup allowlist, test accounts, embed origins, toggles
|
|
5917
7867
|
email Email \u2014 delivery log, blast stats, unsubscribes, inbox, custom domains
|
|
5918
7868
|
cron Scheduled jobs \u2014 schedules, run history, manual trigger
|
|
7869
|
+
infra Dedicated infrastructure \u2014 provision, hibernate, resize + destroy retrieval capacity
|
|
5919
7870
|
|
|
5920
7871
|
Run 'remy-admin <command> --help' for details on each command.
|
|
5921
7872
|
|
|
@@ -5974,6 +7925,11 @@ function resolveRoute(argv) {
|
|
|
5974
7925
|
error: `Unknown subcommand: settings ${sub} ${action ?? ""}. Run 'remy-admin settings --help'`
|
|
5975
7926
|
};
|
|
5976
7927
|
}
|
|
7928
|
+
if (group === "datasources" && (sub === "jobs" || sub === "eval" || sub === "map")) {
|
|
7929
|
+
return {
|
|
7930
|
+
error: `Unknown subcommand: datasources ${sub} ${action ?? ""}. Run 'remy-admin datasources --help'`
|
|
7931
|
+
};
|
|
7932
|
+
}
|
|
5977
7933
|
if (group === "email" && ["domains", "inbound-domains"].includes(sub ?? "")) {
|
|
5978
7934
|
return {
|
|
5979
7935
|
error: `Unknown subcommand: email ${sub} ${action ?? ""}. Run 'remy-admin email --help'`
|