@evident-ai/cli 3.6.1-dev.69de1a0 → 3.6.1-dev.6dfa784
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/README.md +4 -0
- package/dist/index.js +366 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,6 +125,10 @@ Options:
|
|
|
125
125
|
- `--opencode-config-overlay <path>` — Apply a runner-provided OpenCode config
|
|
126
126
|
before starting OpenCode. Relative paths are resolved from the working directory;
|
|
127
127
|
an existing `opencode.jsonc` is replaced before `opencode.json`.
|
|
128
|
+
- `--auto-configure-credentials-proxy` — Install Evident's generated `evident-*`
|
|
129
|
+
OpenCode provider entries for the runner's credential pool. This is off by default;
|
|
130
|
+
without it, the CLI only delivers `EVIDENT_PROXY_TOKEN` and leaves provider
|
|
131
|
+
configuration to you.
|
|
128
132
|
- `--credential-sync-marker <path>` — Own the interval credential sync and write
|
|
129
133
|
this marker once the shutdown flush has finished, so the runner image's lifecycle
|
|
130
134
|
hooks can wait on it.
|
package/dist/index.js
CHANGED
|
@@ -1442,7 +1442,7 @@ import ora4 from "ora";
|
|
|
1442
1442
|
import { select as select4 } from "@inquirer/prompts";
|
|
1443
1443
|
|
|
1444
1444
|
// src/lib/telemetry.ts
|
|
1445
|
-
var CLI_VERSION = (true ? "3.6.1-dev.
|
|
1445
|
+
var CLI_VERSION = (true ? "3.6.1-dev.6dfa784" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
1446
1446
|
function getCliVersion() {
|
|
1447
1447
|
return CLI_VERSION;
|
|
1448
1448
|
}
|
|
@@ -3209,6 +3209,52 @@ async function getModelAttachmentCapability(port, model, client) {
|
|
|
3209
3209
|
return null;
|
|
3210
3210
|
}
|
|
3211
3211
|
}
|
|
3212
|
+
async function isPinnedProviderLoaded(port, model, client) {
|
|
3213
|
+
if (client?.version === "v2") return null;
|
|
3214
|
+
const { model: baseModel } = splitModelVariant(model);
|
|
3215
|
+
if (!baseModel) return null;
|
|
3216
|
+
const slash = baseModel.indexOf("/");
|
|
3217
|
+
if (slash <= 0) return null;
|
|
3218
|
+
const providerId = baseModel.slice(0, slash);
|
|
3219
|
+
try {
|
|
3220
|
+
const res = await requestWithClient(port, client, "/config/providers");
|
|
3221
|
+
if (!res.ok) {
|
|
3222
|
+
console.error(
|
|
3223
|
+
`[isPinnedProviderLoaded] GET /config/providers returned HTTP ${res.status} (port ${port})`
|
|
3224
|
+
);
|
|
3225
|
+
return null;
|
|
3226
|
+
}
|
|
3227
|
+
const body = await res.json();
|
|
3228
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
3229
|
+
console.error(
|
|
3230
|
+
`[isPinnedProviderLoaded] GET /config/providers body was not a plain object (port ${port})`
|
|
3231
|
+
);
|
|
3232
|
+
return null;
|
|
3233
|
+
}
|
|
3234
|
+
const providers = body.providers;
|
|
3235
|
+
if (!Array.isArray(providers)) {
|
|
3236
|
+
console.error(
|
|
3237
|
+
`[isPinnedProviderLoaded] GET /config/providers body had no providers array (port ${port})`
|
|
3238
|
+
);
|
|
3239
|
+
return null;
|
|
3240
|
+
}
|
|
3241
|
+
const providerIds = providers.filter(
|
|
3242
|
+
(provider) => provider !== null && typeof provider === "object" && typeof provider.id === "string"
|
|
3243
|
+
).map((provider) => provider.id);
|
|
3244
|
+
if (providerIds.length === 0) {
|
|
3245
|
+
console.error(
|
|
3246
|
+
`[isPinnedProviderLoaded] GET /config/providers had no provider entries with string ids (port ${port})`
|
|
3247
|
+
);
|
|
3248
|
+
return null;
|
|
3249
|
+
}
|
|
3250
|
+
return providerIds.includes(providerId);
|
|
3251
|
+
} catch (err) {
|
|
3252
|
+
console.error(
|
|
3253
|
+
`[isPinnedProviderLoaded] GET /config/providers failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3254
|
+
);
|
|
3255
|
+
return null;
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3212
3258
|
async function buildFileParts(attachments, capable) {
|
|
3213
3259
|
const outcomes = [];
|
|
3214
3260
|
const parts = [];
|
|
@@ -7742,6 +7788,211 @@ function describe(err) {
|
|
|
7742
7788
|
return err instanceof Error ? err.message : String(err);
|
|
7743
7789
|
}
|
|
7744
7790
|
|
|
7791
|
+
// src/lib/vault-file-sync.ts
|
|
7792
|
+
import { createHash } from "node:crypto";
|
|
7793
|
+
async function syncVaultFiles(options) {
|
|
7794
|
+
const files = await listVaultFiles(options);
|
|
7795
|
+
if (files === null) return { applied: 0 };
|
|
7796
|
+
const listedIds = new Set(files.map((file) => file.id));
|
|
7797
|
+
for (const fileId of options.outcomes.keys()) {
|
|
7798
|
+
if (!listedIds.has(fileId)) options.outcomes.delete(fileId);
|
|
7799
|
+
}
|
|
7800
|
+
let applied = 0;
|
|
7801
|
+
for (const file of files) {
|
|
7802
|
+
try {
|
|
7803
|
+
if (await reconcileVaultFile(options, file)) applied += 1;
|
|
7804
|
+
} catch (err) {
|
|
7805
|
+
options.log({
|
|
7806
|
+
level: "error",
|
|
7807
|
+
message: `Vault file ${file.id} reconciliation failed unexpectedly (message processing is unaffected): ${describe2(err)}`
|
|
7808
|
+
});
|
|
7809
|
+
}
|
|
7810
|
+
}
|
|
7811
|
+
return { applied };
|
|
7812
|
+
}
|
|
7813
|
+
async function listVaultFiles(options) {
|
|
7814
|
+
let response;
|
|
7815
|
+
try {
|
|
7816
|
+
response = await options.fetchImpl(`${options.apiUrl}/runners/${options.agentId}/vault-files`, {
|
|
7817
|
+
headers: { Authorization: options.getAuthHeader() }
|
|
7818
|
+
});
|
|
7819
|
+
} catch (err) {
|
|
7820
|
+
options.log({
|
|
7821
|
+
level: "warn",
|
|
7822
|
+
message: `Could not list vault files \u2014 retrying on the next drain: ${describe2(err)}`
|
|
7823
|
+
});
|
|
7824
|
+
return null;
|
|
7825
|
+
}
|
|
7826
|
+
if (!response.ok) {
|
|
7827
|
+
options.log({
|
|
7828
|
+
level: response.status === 404 ? "debug" : "warn",
|
|
7829
|
+
message: `Listing vault files returned HTTP ${response.status} \u2014 retrying on the next drain`
|
|
7830
|
+
});
|
|
7831
|
+
return null;
|
|
7832
|
+
}
|
|
7833
|
+
let body;
|
|
7834
|
+
try {
|
|
7835
|
+
body = await response.json();
|
|
7836
|
+
} catch (err) {
|
|
7837
|
+
options.log({
|
|
7838
|
+
level: "warn",
|
|
7839
|
+
message: `Vault file list was not readable JSON \u2014 retrying on the next drain: ${describe2(err)}`
|
|
7840
|
+
});
|
|
7841
|
+
return null;
|
|
7842
|
+
}
|
|
7843
|
+
if (body === null || typeof body !== "object" || !Array.isArray(body.files)) {
|
|
7844
|
+
options.log({
|
|
7845
|
+
level: "warn",
|
|
7846
|
+
message: "Vault file list was not an object with a files array \u2014 ignoring it for this drain"
|
|
7847
|
+
});
|
|
7848
|
+
return null;
|
|
7849
|
+
}
|
|
7850
|
+
const files = [];
|
|
7851
|
+
for (const entry of body.files) {
|
|
7852
|
+
const file = asVaultFile(entry);
|
|
7853
|
+
if (file === null) {
|
|
7854
|
+
options.log({
|
|
7855
|
+
level: "warn",
|
|
7856
|
+
message: "Ignoring a malformed vault file entry"
|
|
7857
|
+
});
|
|
7858
|
+
continue;
|
|
7859
|
+
}
|
|
7860
|
+
files.push(file);
|
|
7861
|
+
}
|
|
7862
|
+
return files;
|
|
7863
|
+
}
|
|
7864
|
+
function asVaultFile(entry) {
|
|
7865
|
+
if (entry === null || typeof entry !== "object") return null;
|
|
7866
|
+
const value = entry;
|
|
7867
|
+
if (typeof value.id !== "string" || value.id === "" || typeof value.destination_path !== "string" || value.destination_path === "" || typeof value.size_bytes !== "number" || !Number.isFinite(value.size_bytes) || value.size_bytes < 0 || typeof value.content_sha256 !== "string" || value.content_sha256 === "" || typeof value.created_at !== "string" || typeof value.updated_at !== "string") {
|
|
7868
|
+
return null;
|
|
7869
|
+
}
|
|
7870
|
+
return {
|
|
7871
|
+
id: value.id,
|
|
7872
|
+
destination_path: value.destination_path,
|
|
7873
|
+
size_bytes: value.size_bytes,
|
|
7874
|
+
content_sha256: value.content_sha256,
|
|
7875
|
+
created_at: value.created_at,
|
|
7876
|
+
updated_at: value.updated_at
|
|
7877
|
+
};
|
|
7878
|
+
}
|
|
7879
|
+
async function reconcileVaultFile(options, file) {
|
|
7880
|
+
const existing = options.outcomes.get(file.id);
|
|
7881
|
+
if (existing?.sha256 === file.content_sha256) {
|
|
7882
|
+
if (existing.reported || existing.reportAttempts >= MAX_ACK_ATTEMPTS) return false;
|
|
7883
|
+
await reportOutcome(options, file, existing);
|
|
7884
|
+
return false;
|
|
7885
|
+
}
|
|
7886
|
+
const content = await downloadVaultFile(options, file);
|
|
7887
|
+
if (content === null) return false;
|
|
7888
|
+
const actualSha256 = createHash("sha256").update(content).digest("hex");
|
|
7889
|
+
if (actualSha256 !== file.content_sha256) {
|
|
7890
|
+
options.log({
|
|
7891
|
+
level: "warn",
|
|
7892
|
+
message: `Vault file ${file.id} content hash mismatch: expected ${file.content_sha256}, received ${actualSha256} \u2014 retrying on the next drain`
|
|
7893
|
+
});
|
|
7894
|
+
return false;
|
|
7895
|
+
}
|
|
7896
|
+
const outcome = await applyVaultFile(options, file, content);
|
|
7897
|
+
const entry = {
|
|
7898
|
+
sha256: file.content_sha256,
|
|
7899
|
+
outcome: outcome.ok ? "applied" : "refused",
|
|
7900
|
+
reason: outcome.ok ? null : outcome.code,
|
|
7901
|
+
reported: false,
|
|
7902
|
+
reportAttempts: 0
|
|
7903
|
+
};
|
|
7904
|
+
options.outcomes.set(file.id, entry);
|
|
7905
|
+
await reportOutcome(options, file, entry);
|
|
7906
|
+
return outcome.ok;
|
|
7907
|
+
}
|
|
7908
|
+
async function downloadVaultFile(options, file) {
|
|
7909
|
+
try {
|
|
7910
|
+
const response = await options.fetchImpl(
|
|
7911
|
+
`${options.apiUrl}/runners/${options.agentId}/vault-files/${file.id}/content`,
|
|
7912
|
+
{ headers: { Authorization: options.getAuthHeader() } }
|
|
7913
|
+
);
|
|
7914
|
+
if (!response.ok) {
|
|
7915
|
+
options.log({
|
|
7916
|
+
level: "warn",
|
|
7917
|
+
message: `Downloading vault file ${file.id} returned HTTP ${response.status} \u2014 retrying on the next drain`
|
|
7918
|
+
});
|
|
7919
|
+
return null;
|
|
7920
|
+
}
|
|
7921
|
+
return Buffer.from(await response.arrayBuffer());
|
|
7922
|
+
} catch (err) {
|
|
7923
|
+
options.log({
|
|
7924
|
+
level: "warn",
|
|
7925
|
+
message: `Downloading vault file ${file.id} failed \u2014 retrying on the next drain: ${describe2(err)}`
|
|
7926
|
+
});
|
|
7927
|
+
return null;
|
|
7928
|
+
}
|
|
7929
|
+
}
|
|
7930
|
+
async function applyVaultFile(options, file, content) {
|
|
7931
|
+
try {
|
|
7932
|
+
const outcome = await writePushedFile({
|
|
7933
|
+
requestedPath: file.destination_path,
|
|
7934
|
+
content,
|
|
7935
|
+
allowedDirectories: options.allowedDirectories,
|
|
7936
|
+
homeDir: options.homeDir
|
|
7937
|
+
});
|
|
7938
|
+
if (!outcome.ok) {
|
|
7939
|
+
options.log({
|
|
7940
|
+
level: "warn",
|
|
7941
|
+
message: `Vault file ${file.id} refused (${outcome.code}): ${outcome.message}`
|
|
7942
|
+
});
|
|
7943
|
+
} else {
|
|
7944
|
+
options.log({
|
|
7945
|
+
level: "info",
|
|
7946
|
+
message: `Vault file ${file.id} applied (${content.byteLength} bytes)`
|
|
7947
|
+
});
|
|
7948
|
+
}
|
|
7949
|
+
return outcome;
|
|
7950
|
+
} catch (err) {
|
|
7951
|
+
options.log({
|
|
7952
|
+
level: "error",
|
|
7953
|
+
message: `Vault file ${file.id} could not be written: ${describe2(err)}`
|
|
7954
|
+
});
|
|
7955
|
+
return { ok: false, code: "write_failed", message: "The runner could not write the file." };
|
|
7956
|
+
}
|
|
7957
|
+
}
|
|
7958
|
+
async function reportOutcome(options, file, entry) {
|
|
7959
|
+
const body = {
|
|
7960
|
+
status: entry.outcome === "applied" ? "applied" : "rejected",
|
|
7961
|
+
reported_sha256: entry.sha256
|
|
7962
|
+
};
|
|
7963
|
+
if (entry.outcome === "refused" && entry.reason !== null) body.reason = entry.reason;
|
|
7964
|
+
try {
|
|
7965
|
+
const response = await options.fetchImpl(
|
|
7966
|
+
`${options.apiUrl}/runners/${options.agentId}/vault-files/${file.id}/status`,
|
|
7967
|
+
{
|
|
7968
|
+
method: "POST",
|
|
7969
|
+
headers: {
|
|
7970
|
+
Authorization: options.getAuthHeader(),
|
|
7971
|
+
"Content-Type": "application/json"
|
|
7972
|
+
},
|
|
7973
|
+
body: JSON.stringify(body)
|
|
7974
|
+
}
|
|
7975
|
+
);
|
|
7976
|
+
if (response.ok) {
|
|
7977
|
+
entry.reported = true;
|
|
7978
|
+
return;
|
|
7979
|
+
}
|
|
7980
|
+
recordReportFailure(options, file, entry, `HTTP ${response.status}`);
|
|
7981
|
+
} catch (err) {
|
|
7982
|
+
recordReportFailure(options, file, entry, describe2(err));
|
|
7983
|
+
}
|
|
7984
|
+
}
|
|
7985
|
+
function recordReportFailure(options, file, entry, reason) {
|
|
7986
|
+
entry.reportAttempts += 1;
|
|
7987
|
+
options.log({
|
|
7988
|
+
level: "error",
|
|
7989
|
+
message: entry.reportAttempts >= MAX_ACK_ATTEMPTS ? `Reporting vault file ${file.id} as ${entry.outcome} failed (${reason}) \u2014 giving up after ${entry.reportAttempts} attempts` : `Reporting vault file ${file.id} as ${entry.outcome} failed (${reason}) \u2014 retrying on the next drain (attempt ${entry.reportAttempts} of ${MAX_ACK_ATTEMPTS})`
|
|
7990
|
+
});
|
|
7991
|
+
}
|
|
7992
|
+
function describe2(err) {
|
|
7993
|
+
return err instanceof Error ? err.message : String(err);
|
|
7994
|
+
}
|
|
7995
|
+
|
|
7745
7996
|
// src/lib/channels/driver.ts
|
|
7746
7997
|
function messageIdOf(m) {
|
|
7747
7998
|
if (!m || typeof m !== "object") return void 0;
|
|
@@ -8150,6 +8401,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8150
8401
|
* re-downloaded and re-written every ~2s until the server expires it.
|
|
8151
8402
|
*/
|
|
8152
8403
|
fileAckFailures = /* @__PURE__ */ new Map();
|
|
8404
|
+
vaultFileOutcomes = /* @__PURE__ */ new Map();
|
|
8153
8405
|
/**
|
|
8154
8406
|
* Monotonic count of files this runner has pulled and written (#559). Only
|
|
8155
8407
|
* ever increases, so `run.ts` detects work by comparing it against the value
|
|
@@ -8307,10 +8559,21 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8307
8559
|
ackFailures: this.fileAckFailures,
|
|
8308
8560
|
log: this.log
|
|
8309
8561
|
});
|
|
8310
|
-
|
|
8562
|
+
const vaultResult = await syncVaultFiles({
|
|
8563
|
+
agentId: this.agentId,
|
|
8564
|
+
apiUrl: this.apiUrl,
|
|
8565
|
+
getAuthHeader: this.getAuthHeader,
|
|
8566
|
+
fetchImpl: this.fetchImpl,
|
|
8567
|
+
allowedDirectories: this.fileSyncDirectories,
|
|
8568
|
+
homeDir: this.homeDir,
|
|
8569
|
+
outcomes: this.vaultFileOutcomes,
|
|
8570
|
+
log: this.log
|
|
8571
|
+
});
|
|
8572
|
+
const applied = result.applied + vaultResult.applied;
|
|
8573
|
+
this.appliedFileCount += applied;
|
|
8311
8574
|
if (result.claudeCredentialApplied) this.claudeCredentialApplyCount += 1;
|
|
8312
8575
|
if (result.opencodeAuthApplied) this.opencodeAuthApplyCount += 1;
|
|
8313
|
-
return
|
|
8576
|
+
return applied;
|
|
8314
8577
|
} catch (err) {
|
|
8315
8578
|
this.log({
|
|
8316
8579
|
level: "error",
|
|
@@ -8722,6 +8985,47 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8722
8985
|
agent: message.opencode_agent ?? void 0,
|
|
8723
8986
|
model: message.opencode_model ?? void 0
|
|
8724
8987
|
};
|
|
8988
|
+
const pinnedProviderLoaded = await isPinnedProviderLoaded(
|
|
8989
|
+
this.port,
|
|
8990
|
+
options.model,
|
|
8991
|
+
this.openCodeClient
|
|
8992
|
+
);
|
|
8993
|
+
if (pinnedProviderLoaded === false) {
|
|
8994
|
+
const dispatchedModel = message.opencode_model;
|
|
8995
|
+
const separator = dispatchedModel?.indexOf("/") ?? -1;
|
|
8996
|
+
const hasProviderAndModel = dispatchedModel !== null && separator > 0 && separator < dispatchedModel.length - 1;
|
|
8997
|
+
const providerId = hasProviderAndModel ? dispatchedModel.slice(0, separator) : null;
|
|
8998
|
+
const modelId = hasProviderAndModel ? dispatchedModel.slice(separator + 1) : null;
|
|
8999
|
+
const errorMessage3 = `Provider namespace not loaded on this runner: ${message.opencode_model}. Refused before dispatch; no turn was started.`;
|
|
9000
|
+
this.log({
|
|
9001
|
+
level: "error",
|
|
9002
|
+
message: `Refusing message ${message.id.slice(0, 8)} in conversation ${conv.id.slice(0, 8)}: provider namespace ${providerId ?? "(unknown)"} is not loaded; no turn was started`,
|
|
9003
|
+
conversation_id: conv.id,
|
|
9004
|
+
message_id: message.id
|
|
9005
|
+
});
|
|
9006
|
+
try {
|
|
9007
|
+
await this.markFailed(conv.id, message.id, sessionId, errorMessage3, void 0, {
|
|
9008
|
+
classification: {
|
|
9009
|
+
kind: "model_auth",
|
|
9010
|
+
providerId,
|
|
9011
|
+
modelId,
|
|
9012
|
+
reason: "missing"
|
|
9013
|
+
},
|
|
9014
|
+
errorShape: { name: null, statusCode: null }
|
|
9015
|
+
});
|
|
9016
|
+
this.signalDispatchNotStarted(conv, message, "model_provider_absent");
|
|
9017
|
+
} catch (markErr) {
|
|
9018
|
+
this.log({
|
|
9019
|
+
level: "warn",
|
|
9020
|
+
message: `markFailed PATCH for refused message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed: ${markErr instanceof Error ? markErr.message : String(markErr)}`,
|
|
9021
|
+
conversation_id: conv.id,
|
|
9022
|
+
message_id: message.id
|
|
9023
|
+
});
|
|
9024
|
+
this.signalDispatchNotStarted(conv, message, "failure_unreported");
|
|
9025
|
+
}
|
|
9026
|
+
await this.repairProviderRegistry(conv.id, message.id);
|
|
9027
|
+
break;
|
|
9028
|
+
}
|
|
8725
9029
|
let opencodeMessageId;
|
|
8726
9030
|
let preDispatchUserIds = null;
|
|
8727
9031
|
try {
|
|
@@ -13920,22 +14224,6 @@ function mergeManagedOpenCodeConfigContent(content, managedProviders) {
|
|
|
13920
14224
|
}
|
|
13921
14225
|
return JSON.stringify({ ...config, provider: { ...provider, ...managedProviders } });
|
|
13922
14226
|
}
|
|
13923
|
-
function removeManagedOpenCodeConfigContent(content) {
|
|
13924
|
-
if (!content?.trim()) return content;
|
|
13925
|
-
const config = parseConfigContent(content);
|
|
13926
|
-
if (typeof config.provider !== "object" || config.provider === null || Array.isArray(config.provider)) {
|
|
13927
|
-
return content;
|
|
13928
|
-
}
|
|
13929
|
-
const provider = { ...config.provider };
|
|
13930
|
-
let changed = false;
|
|
13931
|
-
for (const namespace of CREDENTIAL_PROXY_NATIVE_NAMESPACES) {
|
|
13932
|
-
if (namespace in provider) {
|
|
13933
|
-
delete provider[namespace];
|
|
13934
|
-
changed = true;
|
|
13935
|
-
}
|
|
13936
|
-
}
|
|
13937
|
-
return changed ? JSON.stringify({ ...config, provider }) : content;
|
|
13938
|
-
}
|
|
13939
14227
|
function isFile(filePath) {
|
|
13940
14228
|
return existsSync2(filePath) && statSync6(filePath).isFile();
|
|
13941
14229
|
}
|
|
@@ -14397,23 +14685,23 @@ async function responseJson(response) {
|
|
|
14397
14685
|
function readSetup(value) {
|
|
14398
14686
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
14399
14687
|
const setup = value;
|
|
14400
|
-
if (typeof setup.runner_id !== "string" || typeof setup.generation !== "string" || typeof setup.status !== "string" || setup.token_id !== null && typeof setup.token_id !== "string") {
|
|
14688
|
+
if (typeof setup.runner_id !== "string" || typeof setup.generation !== "string" || typeof setup.status !== "string" || setup.token_id !== null && typeof setup.token_id !== "string" || typeof setup.acknowledged_at !== "string" && setup.acknowledged_at !== null) {
|
|
14401
14689
|
return null;
|
|
14402
14690
|
}
|
|
14403
14691
|
return setup;
|
|
14404
14692
|
}
|
|
14405
14693
|
function clearLocalSetup(options) {
|
|
14406
14694
|
const env = options.env ?? process.env;
|
|
14407
|
-
|
|
14408
|
-
|
|
14409
|
-
|
|
14410
|
-
|
|
14411
|
-
|
|
14412
|
-
|
|
14413
|
-
}
|
|
14695
|
+
const store = options.store ?? defaultStore;
|
|
14696
|
+
const stored = store.get(options.agentId);
|
|
14697
|
+
store.clear(options.agentId);
|
|
14698
|
+
const token = env.EVIDENT_PROXY_TOKEN;
|
|
14699
|
+
if (token !== void 0 && token === stored?.token) {
|
|
14700
|
+
delete env.EVIDENT_PROXY_TOKEN;
|
|
14701
|
+
} else if (token !== void 0) {
|
|
14414
14702
|
options.log(
|
|
14415
|
-
|
|
14416
|
-
"
|
|
14703
|
+
`MANAGED-INFERENCE-CLEANUP-PRESERVED-EXTERNAL-TOKEN: runner=${options.agentId}`,
|
|
14704
|
+
"debug"
|
|
14417
14705
|
);
|
|
14418
14706
|
}
|
|
14419
14707
|
}
|
|
@@ -14439,6 +14727,25 @@ async function discoverManagedInferenceSetup(options) {
|
|
|
14439
14727
|
const setup = readSetup(body?.setup);
|
|
14440
14728
|
return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed setup discovery returned an invalid response" };
|
|
14441
14729
|
}
|
|
14730
|
+
async function enrollManagedInferenceSetup(options) {
|
|
14731
|
+
const response = await post(options, "enroll", {});
|
|
14732
|
+
if (!response) {
|
|
14733
|
+
return { kind: "failed", reason: safeFailure("Managed credential enrollment") };
|
|
14734
|
+
}
|
|
14735
|
+
if (response.status === 404) {
|
|
14736
|
+
return { kind: "none" };
|
|
14737
|
+
}
|
|
14738
|
+
const body = await responseJson(response);
|
|
14739
|
+
if (!response.ok) {
|
|
14740
|
+
const serverError = body?.error;
|
|
14741
|
+
return {
|
|
14742
|
+
kind: "failed",
|
|
14743
|
+
reason: typeof serverError === "string" && serverError.trim() ? redactDiagnosticText(serverError).slice(0, 500) : safeFailure("Managed credential enrollment", response.status)
|
|
14744
|
+
};
|
|
14745
|
+
}
|
|
14746
|
+
const setup = readSetup(body?.setup);
|
|
14747
|
+
return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed credential enrollment returned an invalid response" };
|
|
14748
|
+
}
|
|
14442
14749
|
async function post(options, operation, body) {
|
|
14443
14750
|
const headers = requestHeaders(options);
|
|
14444
14751
|
try {
|
|
@@ -14453,31 +14760,37 @@ async function post(options, operation, body) {
|
|
|
14453
14760
|
}
|
|
14454
14761
|
}
|
|
14455
14762
|
function applyCredential(options, credential) {
|
|
14456
|
-
const { config, unsupportedProviders } = buildCredentialProxyNativeConfig({
|
|
14457
|
-
inferenceBaseUrl: credential.inferenceBaseUrl,
|
|
14458
|
-
modelDescriptors: credential.modelDescriptors
|
|
14459
|
-
});
|
|
14460
|
-
if (unsupportedProviders.length > 0 || Object.keys(config.provider).length === 0) {
|
|
14461
|
-
return "Managed setup contains unsupported provider descriptors";
|
|
14462
|
-
}
|
|
14463
14763
|
const env = options.env ?? process.env;
|
|
14464
14764
|
let merged;
|
|
14465
|
-
|
|
14466
|
-
|
|
14467
|
-
|
|
14468
|
-
|
|
14765
|
+
if (options.autoConfigureCredentialsProxy) {
|
|
14766
|
+
const { config, unsupportedProviders } = buildCredentialProxyNativeConfig({
|
|
14767
|
+
inferenceBaseUrl: credential.inferenceBaseUrl,
|
|
14768
|
+
modelDescriptors: credential.modelDescriptors
|
|
14769
|
+
});
|
|
14770
|
+
if (unsupportedProviders.length > 0 || Object.keys(config.provider).length === 0) {
|
|
14771
|
+
return "Managed setup contains unsupported provider descriptors";
|
|
14772
|
+
}
|
|
14773
|
+
try {
|
|
14774
|
+
merged = mergeManagedOpenCodeConfigContent(env.OPENCODE_CONFIG_CONTENT, config.provider);
|
|
14775
|
+
} catch (error2) {
|
|
14776
|
+
return "OPENCODE_CONFIG_CONTENT is malformed; managed providers were not installed";
|
|
14777
|
+
}
|
|
14469
14778
|
}
|
|
14470
14779
|
try {
|
|
14471
14780
|
(options.store ?? defaultStore).set(options.agentId, credential);
|
|
14472
14781
|
} catch (error2) {
|
|
14782
|
+
const detail = (error2 instanceof Error ? error2.message : String(error2)).replaceAll(
|
|
14783
|
+
credential.token,
|
|
14784
|
+
"[redacted]"
|
|
14785
|
+
);
|
|
14473
14786
|
options.log(
|
|
14474
|
-
`MANAGED-INFERENCE-STORE-FAILED: runner=${options.agentId} generation=${credential.generation}`,
|
|
14787
|
+
`MANAGED-INFERENCE-STORE-FAILED: runner=${options.agentId} generation=${credential.generation} detail=${redactDiagnosticText(detail)}`,
|
|
14475
14788
|
"warn"
|
|
14476
14789
|
);
|
|
14477
14790
|
return "Managed credentials could not be written to the protected store";
|
|
14478
14791
|
}
|
|
14479
14792
|
env.EVIDENT_PROXY_TOKEN = credential.token;
|
|
14480
|
-
env.OPENCODE_CONFIG_CONTENT = merged;
|
|
14793
|
+
if (merged !== void 0) env.OPENCODE_CONFIG_CONTENT = merged;
|
|
14481
14794
|
return null;
|
|
14482
14795
|
}
|
|
14483
14796
|
async function reportManagedInferenceApplyFailure(options, failure) {
|
|
@@ -14528,7 +14841,7 @@ async function prepareManagedInferenceSetup(options, discovery, health) {
|
|
|
14528
14841
|
}
|
|
14529
14842
|
return reaffirmStored(options, stored);
|
|
14530
14843
|
}
|
|
14531
|
-
if (discovery.setup.
|
|
14844
|
+
if (discovery.setup.acknowledged_at !== null && stored?.generation === discovery.setup.generation && stored.tokenId === discovery.setup.token_id) {
|
|
14532
14845
|
return reaffirmStored(options, stored);
|
|
14533
14846
|
}
|
|
14534
14847
|
const response = await post(options, "bootstrap", { generation: discovery.setup.generation });
|
|
@@ -14566,7 +14879,7 @@ async function acknowledgeManagedInferenceSetup(options, preparation) {
|
|
|
14566
14879
|
const response = await post(options, "acknowledge", {
|
|
14567
14880
|
generation: preparation.generation,
|
|
14568
14881
|
token_id: preparation.tokenId,
|
|
14569
|
-
config_validated: true
|
|
14882
|
+
config_validated: options.autoConfigureCredentialsProxy === true
|
|
14570
14883
|
});
|
|
14571
14884
|
if (!response?.ok) {
|
|
14572
14885
|
throw new Error(
|
|
@@ -16369,13 +16682,17 @@ async function run(options) {
|
|
|
16369
16682
|
agentId: state.agentId,
|
|
16370
16683
|
apiUrl: getApiUrlConfig(),
|
|
16371
16684
|
getAuthHeader: () => state.authHeader,
|
|
16685
|
+
autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
|
|
16372
16686
|
log: (message, level = "info") => logActivity(state, {
|
|
16373
16687
|
type: level === "error" ? "error" : "info",
|
|
16374
16688
|
level,
|
|
16375
16689
|
...level === "error" ? { error: message } : { message }
|
|
16376
16690
|
})
|
|
16377
16691
|
};
|
|
16378
|
-
|
|
16692
|
+
let managedDiscovery = await discoverManagedInferenceSetup(managedSetupOptions);
|
|
16693
|
+
if (managedDiscovery.kind === "none") {
|
|
16694
|
+
managedDiscovery = await enrollManagedInferenceSetup(managedSetupOptions);
|
|
16695
|
+
}
|
|
16379
16696
|
let managedPreparation = { kind: "none" };
|
|
16380
16697
|
if (managedDiscovery.kind === "failed") {
|
|
16381
16698
|
managedPreparation = managedDiscovery;
|
|
@@ -16996,6 +17313,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
16996
17313
|
).option(
|
|
16997
17314
|
"--opencode-config-overlay <path>",
|
|
16998
17315
|
"Apply this runner-provided OpenCode config before starting OpenCode."
|
|
17316
|
+
).option(
|
|
17317
|
+
"--auto-configure-credentials-proxy",
|
|
17318
|
+
"Install Evident's generated OpenCode providers for the runner's credential pool (off by default)."
|
|
16999
17319
|
).option(
|
|
17000
17320
|
"--credential-sync-marker <path>",
|
|
17001
17321
|
"Own the interval credential sync and write this marker once the shutdown flush has finished, so the runner image's lifecycle hooks can wait on it."
|
|
@@ -17039,6 +17359,7 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
17039
17359
|
restoreSessionDb: options.restoreSessionDb,
|
|
17040
17360
|
restoreRunnerCredentials: options.restoreRunnerCredentials,
|
|
17041
17361
|
opencodeConfigOverlay: options.opencodeConfigOverlay,
|
|
17362
|
+
autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
|
|
17042
17363
|
credentialSyncMarker: options.credentialSyncMarker
|
|
17043
17364
|
});
|
|
17044
17365
|
}
|