@evident-ai/cli 3.6.1-dev.22e9703 → 3.6.1-dev.2a0b3d1

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.js CHANGED
@@ -1445,7 +1445,7 @@ import ora4 from "ora";
1445
1445
  import { select as select4 } from "@inquirer/prompts";
1446
1446
 
1447
1447
  // src/lib/telemetry.ts
1448
- var CLI_VERSION = (true ? "3.6.1-dev.22e9703" : void 0) ?? process.env.npm_package_version ?? "unknown";
1448
+ var CLI_VERSION = (true ? "3.6.1-dev.2a0b3d1" : void 0) ?? process.env.npm_package_version ?? "unknown";
1449
1449
  function getCliVersion() {
1450
1450
  return CLI_VERSION;
1451
1451
  }
@@ -3880,6 +3880,13 @@ function withRequestTimeout(fetchImpl, timeoutMs) {
3880
3880
  }
3881
3881
 
3882
3882
  // src/lib/opencode/client.ts
3883
+ var OpenCodeRequestError = class extends Error {
3884
+ constructor(statusCode, message) {
3885
+ super(message);
3886
+ this.statusCode = statusCode;
3887
+ this.name = "OpenCodeRequestError";
3888
+ }
3889
+ };
3883
3890
  function redactPassword(message, password) {
3884
3891
  return message.replaceAll(password, "[redacted]");
3885
3892
  }
@@ -3901,7 +3908,8 @@ function createOpenCodeClient(options) {
3901
3908
  const response = await fetchImpl(`${baseUrl}${path}`, requestInit);
3902
3909
  if (!response.ok && !requestOptions?.allowStatuses?.includes(response.status)) {
3903
3910
  const body = await response.text();
3904
- throw new Error(
3911
+ throw new OpenCodeRequestError(
3912
+ response.status,
3905
3913
  `OpenCode request failed: HTTP ${response.status}${body ? `: ${body}` : ""}`
3906
3914
  );
3907
3915
  }
@@ -3909,7 +3917,11 @@ function createOpenCodeClient(options) {
3909
3917
  } catch (error2) {
3910
3918
  if (options.version === "v2" && password !== null) {
3911
3919
  const message = error2 instanceof Error ? error2.message : String(error2);
3912
- throw new Error(redactPassword(message, password));
3920
+ const redactedMessage = redactPassword(message, password);
3921
+ if (error2 instanceof OpenCodeRequestError) {
3922
+ throw new OpenCodeRequestError(error2.statusCode, redactedMessage);
3923
+ }
3924
+ throw new Error(redactedMessage);
3913
3925
  }
3914
3926
  throw error2;
3915
3927
  }
@@ -4585,21 +4597,24 @@ function findLastAssistantReplyFor(messages, userMessageId) {
4585
4597
  }
4586
4598
  return last;
4587
4599
  }
4600
+ function assistantMessagesInTurnBlock(messages, userMessageId) {
4601
+ const userIndex = messages.findIndex((message) => idOf2(message) === userMessageId);
4602
+ if (userIndex === -1) return [];
4603
+ const assistants = [];
4604
+ for (let i = userIndex + 1; i < messages.length; i++) {
4605
+ const message = messages[i];
4606
+ const role = roleOf2(message);
4607
+ if (role === "user") break;
4608
+ if (role === "assistant") assistants.push(message);
4609
+ }
4610
+ return assistants;
4611
+ }
4588
4612
  function collectSubagentSessions(messages, userMessageId) {
4589
4613
  if (!messages || messages.length === 0) return [];
4590
4614
  const byParent = messages.filter(
4591
4615
  (message) => roleOf2(message) === "assistant" && parentIdOf2(message) === userMessageId
4592
4616
  );
4593
- const assistants = byParent.length > 0 ? byParent : [];
4594
- if (assistants.length === 0) {
4595
- const userIndex = messages.findIndex((message) => idOf2(message) === userMessageId);
4596
- if (userIndex === -1) return [];
4597
- for (let i = userIndex + 1; i < messages.length; i++) {
4598
- const message = messages[i];
4599
- if (roleOf2(message) === "user") break;
4600
- if (roleOf2(message) === "assistant") assistants.push(message);
4601
- }
4602
- }
4617
+ const assistants = byParent.length > 0 ? byParent : assistantMessagesInTurnBlock(messages, userMessageId);
4603
4618
  const refs = [];
4604
4619
  const seen = /* @__PURE__ */ new Set();
4605
4620
  for (const message of assistants) {
@@ -4754,8 +4769,7 @@ function messageUsage(messages, userMessageId) {
4754
4769
  if (byParentAll.length > 0) {
4755
4770
  correlated = byParentAll;
4756
4771
  } else {
4757
- const reply = findAssistantReplyAfter(messages, userMessageId);
4758
- correlated = reply ? [reply] : [];
4772
+ correlated = assistantMessagesInTurnBlock(messages, userMessageId);
4759
4773
  }
4760
4774
  if (correlated.length === 0) return null;
4761
4775
  return sumAssistantUsage(correlated);
@@ -4977,39 +4991,85 @@ async function hasAnyConfiguredProvider(port, client) {
4977
4991
  );
4978
4992
  return null;
4979
4993
  }
4980
- const path = `/api/integration?location%5Bdirectory%5D=${encodeURIComponent(directory)}`;
4994
+ const query = `?location%5Bdirectory%5D=${encodeURIComponent(directory)}`;
4995
+ const integrationPath = `/api/integration${query}`;
4996
+ let integrationReady = null;
4981
4997
  try {
4982
- const res = await client.request(path);
4998
+ const res = await client.request(integrationPath);
4983
4999
  if (!res.ok) {
4984
5000
  console.error(
4985
- `[hasAnyConfiguredProvider] GET ${path} returned HTTP ${res.status} (port ${port})`
5001
+ `[hasAnyConfiguredProvider] GET ${integrationPath} returned HTTP ${res.status} (port ${port})`
4986
5002
  );
4987
- return null;
5003
+ } else {
5004
+ const body = await res.json();
5005
+ if (!body || typeof body !== "object" || Array.isArray(body) || !Array.isArray(body.data)) {
5006
+ console.error(
5007
+ `[hasAnyConfiguredProvider] GET ${integrationPath} body had no integration data array (port ${port})`
5008
+ );
5009
+ } else {
5010
+ let valid = true;
5011
+ for (const integration of body.data) {
5012
+ if (!integration || typeof integration !== "object" || Array.isArray(integration) || typeof integration.id !== "string" || !Array.isArray(integration.connections)) {
5013
+ console.error(
5014
+ `[hasAnyConfiguredProvider] GET ${integrationPath} body contained an invalid integration (port ${port})`
5015
+ );
5016
+ valid = false;
5017
+ break;
5018
+ }
5019
+ }
5020
+ if (valid) {
5021
+ integrationReady = body.data.some(
5022
+ (integration) => integration.connections.length > 0
5023
+ );
5024
+ }
5025
+ }
4988
5026
  }
4989
- const body = await res.json();
4990
- if (!body || typeof body !== "object" || Array.isArray(body) || !Array.isArray(body.data)) {
5027
+ } catch (error2) {
5028
+ console.error(
5029
+ `[hasAnyConfiguredProvider] GET ${integrationPath} failed (port ${port}): ${error2 instanceof Error ? error2.message : String(error2)}`
5030
+ );
5031
+ }
5032
+ if (integrationReady === true) return true;
5033
+ const providerPath = `/api/provider${query}`;
5034
+ let providerReady = null;
5035
+ try {
5036
+ const res = await client.request(providerPath);
5037
+ if (!res.ok) {
4991
5038
  console.error(
4992
- `[hasAnyConfiguredProvider] GET ${path} body had no integration data array (port ${port})`
5039
+ `[hasAnyConfiguredProvider] GET ${providerPath} returned HTTP ${res.status} (port ${port})`
4993
5040
  );
4994
- return null;
4995
- }
4996
- for (const integration of body.data) {
4997
- if (!integration || typeof integration !== "object" || Array.isArray(integration) || typeof integration.id !== "string" || !Array.isArray(integration.connections)) {
5041
+ } else {
5042
+ const body = await res.json();
5043
+ if (!body || typeof body !== "object" || Array.isArray(body) || !Array.isArray(body.data)) {
4998
5044
  console.error(
4999
- `[hasAnyConfiguredProvider] GET ${path} body contained an invalid integration (port ${port})`
5045
+ `[hasAnyConfiguredProvider] GET ${providerPath} body had no provider data array (port ${port})`
5000
5046
  );
5001
- return null;
5047
+ } else {
5048
+ let valid = true;
5049
+ for (const provider of body.data) {
5050
+ if (!provider || typeof provider !== "object" || Array.isArray(provider) || typeof provider.id !== "string") {
5051
+ console.error(
5052
+ `[hasAnyConfiguredProvider] GET ${providerPath} body contained an invalid provider (port ${port})`
5053
+ );
5054
+ valid = false;
5055
+ break;
5056
+ }
5057
+ }
5058
+ if (valid) {
5059
+ providerReady = body.data.some(
5060
+ (provider) => provider.activation === "enabled"
5061
+ );
5062
+ }
5002
5063
  }
5003
5064
  }
5004
- return body.data.some(
5005
- (integration) => Array.isArray(integration.connections) && integration.connections.length > 0
5006
- );
5007
5065
  } catch (error2) {
5008
5066
  console.error(
5009
- `[hasAnyConfiguredProvider] GET ${path} failed (port ${port}): ${error2 instanceof Error ? error2.message : String(error2)}`
5067
+ `[hasAnyConfiguredProvider] GET ${providerPath} failed (port ${port}): ${error2 instanceof Error ? error2.message : String(error2)}`
5010
5068
  );
5011
- return null;
5012
5069
  }
5070
+ if (providerReady === true) return true;
5071
+ if (integrationReady === null || providerReady === null) return null;
5072
+ return false;
5013
5073
  }
5014
5074
  try {
5015
5075
  const res = await requestWithClient(port, client, "/config/providers");
@@ -5390,6 +5450,14 @@ var OpenCodeV2PromptAckError = class extends Error {
5390
5450
  this.name = "OpenCodeV2PromptAckError";
5391
5451
  }
5392
5452
  };
5453
+ var OpenCodeV2ModelSwitchError = class extends Error {
5454
+ statusCode;
5455
+ constructor(error2) {
5456
+ super(error2 instanceof Error ? error2.message : String(error2));
5457
+ this.name = "OpenCodeV2ModelSwitchError";
5458
+ this.statusCode = error2 instanceof OpenCodeRequestError ? error2.statusCode : null;
5459
+ }
5460
+ };
5393
5461
  async function getOpenCodeDirectoryV2(client) {
5394
5462
  try {
5395
5463
  return adaptV2Location(await readJson(await client.request("/api/location")));
@@ -5509,7 +5577,34 @@ async function v2SessionExists(client, sessionId) {
5509
5577
  return null;
5510
5578
  }
5511
5579
  }
5512
- async function sendV2Prompt(client, sessionId, text) {
5580
+ async function sendV2Prompt(client, sessionId, text, options, onInvalidModelSelection) {
5581
+ const modelSelection = options?.model?.trim();
5582
+ const { model, variant } = splitModelVariant(modelSelection);
5583
+ const separator = model?.indexOf("/") ?? -1;
5584
+ const modelReference = model && separator > 0 && separator < model.length - 1 ? {
5585
+ id: model.slice(separator + 1),
5586
+ providerID: model.slice(0, separator),
5587
+ ...variant ? { variant } : {}
5588
+ } : void 0;
5589
+ if (modelSelection && !modelReference) onInvalidModelSelection?.();
5590
+ if (options?.agent) {
5591
+ await client.request(`/api/session/${encodeURIComponent(sessionId)}/agent`, {
5592
+ method: "POST",
5593
+ headers: { "Content-Type": "application/json" },
5594
+ body: JSON.stringify({ agent: options.agent })
5595
+ });
5596
+ }
5597
+ if (modelReference) {
5598
+ try {
5599
+ await client.request(`/api/session/${encodeURIComponent(sessionId)}/model`, {
5600
+ method: "POST",
5601
+ headers: { "Content-Type": "application/json" },
5602
+ body: JSON.stringify({ model: modelReference })
5603
+ });
5604
+ } catch (error2) {
5605
+ throw new OpenCodeV2ModelSwitchError(error2);
5606
+ }
5607
+ }
5513
5608
  const path = `/api/session/${encodeURIComponent(sessionId)}/prompt`;
5514
5609
  const response = await client.request(path, {
5515
5610
  method: "POST",
@@ -10095,7 +10190,13 @@ var ChannelDriver = class _ChannelDriver {
10095
10190
  false
10096
10191
  );
10097
10192
  }
10098
- opencodeMessageId = this.isV2 ? await sendV2Prompt(this.openCodeClient, sessionId, message.content) : await this.dispatchLocked(
10193
+ opencodeMessageId = this.isV2 ? await sendV2Prompt(
10194
+ this.openCodeClient,
10195
+ sessionId,
10196
+ message.content,
10197
+ options,
10198
+ () => this.logInvalidV2ModelSelection(conv.id, message.id)
10199
+ ) : await this.dispatchLocked(
10099
10200
  sessionId,
10100
10201
  () => sendPromptAsync(
10101
10202
  this.port,
@@ -10170,6 +10271,10 @@ var ChannelDriver = class _ChannelDriver {
10170
10271
  this.signalDispatchNotStarted(conv, message, "session_existence_unknown");
10171
10272
  break;
10172
10273
  }
10274
+ if (this.isV2 && err instanceof OpenCodeV2ModelSwitchError) {
10275
+ await this.failV2ModelSwitch(conv, message, sessionId, err);
10276
+ break;
10277
+ }
10173
10278
  const errorMessage4 = err instanceof Error ? err.message : String(err);
10174
10279
  this.sessions.delete(conv.id);
10175
10280
  this.supersede(conv.id, sessionId);
@@ -10763,6 +10868,14 @@ var ChannelDriver = class _ChannelDriver {
10763
10868
  this.dispatchNotStartedSignalled.set(message.id, branch);
10764
10869
  void this.postSignal(conv.id, message.id, "dispatch_not_started", { branch });
10765
10870
  }
10871
+ logInvalidV2ModelSelection(conversationId, messageId) {
10872
+ this.log({
10873
+ level: "warn",
10874
+ message: `Skipping an invalid OpenCode V2 model selection for message ${messageId.slice(0, 8)}; expected provider/model format`,
10875
+ conversation_id: conversationId,
10876
+ message_id: messageId
10877
+ });
10878
+ }
10766
10879
  /**
10767
10880
  * Class B (#1340): the runner DECIDED an outcome (reattach/settle/fail_permanent)
10768
10881
  * but its own PATCH to record it failed. Fires at most once per (message,
@@ -13403,7 +13516,13 @@ var ChannelDriver = class _ChannelDriver {
13403
13516
  let ocId;
13404
13517
  let preDispatchUserIds = null;
13405
13518
  try {
13406
- ocId = this.isV2 ? await sendV2Prompt(this.openCodeClient, sessionId, row.content) : await this.dispatchLocked(
13519
+ ocId = this.isV2 ? await sendV2Prompt(
13520
+ this.openCodeClient,
13521
+ sessionId,
13522
+ row.content,
13523
+ options,
13524
+ () => this.logInvalidV2ModelSelection(row.conversation_id, row.id)
13525
+ ) : await this.dispatchLocked(
13407
13526
  sessionId,
13408
13527
  () => sendPromptAsync(
13409
13528
  this.port,
@@ -13459,6 +13578,10 @@ var ChannelDriver = class _ChannelDriver {
13459
13578
  this.untrackableAck.set(row.id, errorMessage4);
13460
13579
  this.untrackableAckSessionIds.set(row.id, sessionId);
13461
13580
  }
13581
+ if (err instanceof OpenCodeV2ModelSwitchError) {
13582
+ await this.failV2ModelSwitch(readoptConv, readoptMessage, sessionId, err);
13583
+ return;
13584
+ }
13462
13585
  this.log({
13463
13586
  level: "error",
13464
13587
  message: `V2 re-adopt dispatch for message ${row.id.slice(0, 8)} failed: ${errorMessage4}`,
@@ -14536,6 +14659,38 @@ var ChannelDriver = class _ChannelDriver {
14536
14659
  }
14537
14660
  this.clearSubagentInvocationCaches(messageId);
14538
14661
  }
14662
+ async failV2ModelSwitch(conv, message, sessionId, error2) {
14663
+ const { model } = splitModelVariant(message.opencode_model ?? void 0);
14664
+ const separator = model?.indexOf("/") ?? -1;
14665
+ const classification = error2.statusCode === 404 && model && separator > 0 && separator < model.length - 1 ? {
14666
+ kind: "model_auth",
14667
+ providerId: model.slice(0, separator),
14668
+ modelId: model.slice(separator + 1),
14669
+ reason: "missing"
14670
+ } : null;
14671
+ this.log({
14672
+ level: "error",
14673
+ message: `OpenCode could not apply the selected model for message ${message.id.slice(0, 8)}: ${error2.message}`,
14674
+ conversation_id: conv.id,
14675
+ message_id: message.id
14676
+ });
14677
+ try {
14678
+ await this.markFailed(conv.id, message.id, sessionId, error2.message, void 0, {
14679
+ classification,
14680
+ errorShape: { name: null, statusCode: error2.statusCode }
14681
+ });
14682
+ } catch (markErr) {
14683
+ this.log({
14684
+ level: "warn",
14685
+ message: `markFailed PATCH for V2 model switch failure on message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed: ${markErr instanceof Error ? markErr.message : String(markErr)}`,
14686
+ conversation_id: conv.id,
14687
+ message_id: message.id
14688
+ });
14689
+ this.signalDispatchNotStarted(conv, message, "failure_unreported");
14690
+ return;
14691
+ }
14692
+ if (classification) await this.repairProviderRegistry(conv.id, message.id);
14693
+ }
14539
14694
  /**
14540
14695
  * Classify an errored turn and extract its bounded error shape for `markFailed`.
14541
14696
  *
@@ -15841,14 +15996,21 @@ function request2(options) {
15841
15996
  function safeFailure(operation, status2) {
15842
15997
  return status2 === void 0 ? `${operation} could not reach the managed credential service` : `${operation} returned HTTP ${status2}`;
15843
15998
  }
15844
- function logTransportFailure(options, operation, error2, authorization) {
15845
- let detail = error2 instanceof Error ? error2.message : String(error2);
15999
+ function redactAuthorizedText(message, authorization) {
16000
+ let redacted = message;
15846
16001
  const credential = authorization.split(/\s+/, 2)[1];
15847
16002
  for (const secret of [authorization, credential]) {
15848
- if (secret) detail = detail.replaceAll(secret, "[redacted]");
16003
+ if (secret) redacted = redacted.replaceAll(secret, "[redacted]");
15849
16004
  }
16005
+ return redactDiagnosticText(redacted);
16006
+ }
16007
+ function logTransportFailure(options, operation, error2, authorization) {
16008
+ const detail = redactAuthorizedText(
16009
+ error2 instanceof Error ? error2.message : String(error2),
16010
+ authorization
16011
+ );
15850
16012
  options.log(
15851
- `MANAGED-INFERENCE-TRANSPORT-FAILED: operation=${operation} detail=${redactDiagnosticText(detail)}`,
16013
+ `MANAGED-INFERENCE-TRANSPORT-FAILED: operation=${operation} detail=${detail}`,
15852
16014
  "debug"
15853
16015
  );
15854
16016
  }
@@ -15911,7 +16073,14 @@ async function discoverManagedInferenceSetup(options) {
15911
16073
  return { kind: "none" };
15912
16074
  }
15913
16075
  if (!response.ok) {
15914
- return { kind: "failed", reason: safeFailure("Managed setup discovery", response.status) };
16076
+ const body2 = await responseJson(response);
16077
+ const serverMessage = typeof body2?.error === "string" && body2.error.trim() ? redactAuthorizedText(body2.error.trim(), headers.Authorization).slice(0, 500) : void 0;
16078
+ const failure = serverMessage ? `Managed setup discovery returned HTTP ${response.status}: ${serverMessage}` : safeFailure("Managed setup discovery", response.status);
16079
+ const runnerKeyGuidance = response.status === 401 || response.status === 403 ? "Authenticate as this runner with its runner key (EVIDENT_RUNNER_KEY)." : void 0;
16080
+ return {
16081
+ kind: "failed",
16082
+ reason: runnerKeyGuidance ? `${failure}. ${runnerKeyGuidance}` : failure
16083
+ };
15915
16084
  }
15916
16085
  const body = await responseJson(response);
15917
16086
  const setup = readSetup(body?.setup);