backeer-opencode 0.3.0 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to `backeer-opencode` are documented here.
4
4
 
5
+ ## 0.3.1 - 2026-09-01
6
+
7
+ - Apply remote question replies on OpenCode 1.18.x even when the plugin-provided
8
+ client does not expose the legacy or v2 question helpers, by falling back to
9
+ OpenCode's underlying authenticated HTTP transport.
10
+ - Normalize native API failures so Backeer receives an accurate terminal ACK
11
+ instead of reporting success for an error response.
12
+ - Add regression coverage for legacy, v2, and error question reply paths.
13
+
5
14
  ## 0.3.0 - 2026-09-01
6
15
 
7
16
  - Publish structured `question.asked` and `question.v2.asked` action metadata.
@@ -1807,13 +1807,46 @@ async function acknowledgeCommand(config, commandID, acknowledgement) {
1807
1807
  );
1808
1808
  await postJsonWithRetry(config, url, acknowledgement);
1809
1809
  }
1810
+ function questionResponseError(result) {
1811
+ if (!result?.error) return;
1812
+ const status = Number(result?.response?.status) || 502;
1813
+ const detail = result.error?.data?.message ?? result.error?.message ?? "OpenCode rejected the question response";
1814
+ return new HttpError(String(detail), status);
1815
+ }
1816
+ function normalizeQuestionError(error) {
1817
+ if (error instanceof HttpError) return error;
1818
+ const status = Number(error?.status ?? error?.statusCode ?? error?.cause?.status);
1819
+ if (!Number.isFinite(status)) return error;
1820
+ return new HttpError(error?.message || "OpenCode rejected the question response", status);
1821
+ }
1822
+ function rawQuestionRequest(client, pending, response, isV2) {
1823
+ const transport = client?._client;
1824
+ if (typeof transport?.post !== "function") {
1825
+ throw new HttpError("This OpenCode version cannot apply remote question replies", 501);
1826
+ }
1827
+ const rejecting = response.action === "reject";
1828
+ if (isV2) {
1829
+ return transport.post({
1830
+ url: `/api/session/{sessionID}/question/{requestID}/${rejecting ? "reject" : "reply"}`,
1831
+ path: { sessionID: pending.sessionID, requestID: pending.requestID },
1832
+ ...rejecting ? {} : { body: { answers: response.answers } },
1833
+ ...rejecting ? {} : { headers: { "Content-Type": "application/json" } },
1834
+ throwOnError: true
1835
+ });
1836
+ }
1837
+ return transport.post({
1838
+ url: `/question/{requestID}/${rejecting ? "reject" : "reply"}`,
1839
+ path: { requestID: pending.requestID },
1840
+ query: { directory: pending.directory },
1841
+ ...rejecting ? {} : { body: { answers: response.answers } },
1842
+ ...rejecting ? {} : { headers: { "Content-Type": "application/json" } },
1843
+ throwOnError: true
1844
+ });
1845
+ }
1810
1846
  async function replyToQuestion(client, pending, response) {
1811
1847
  const isV2 = pending.sourceEvent === "question.v2.asked";
1812
1848
  const api = isV2 ? client?.v2?.session?.question : client?.question;
1813
1849
  const method = response.action === "reject" ? api?.reject : api?.reply;
1814
- if (typeof method !== "function") {
1815
- throw new HttpError("This OpenCode version cannot apply remote question replies", 501);
1816
- }
1817
1850
  const input = isV2 ? {
1818
1851
  sessionID: pending.sessionID,
1819
1852
  requestID: pending.requestID,
@@ -1824,7 +1857,14 @@ async function replyToQuestion(client, pending, response) {
1824
1857
  directory: pending.directory,
1825
1858
  ...response.action === "answer" ? { answers: response.answers } : {}
1826
1859
  };
1827
- const result = await method.call(api, input);
1860
+ let result;
1861
+ try {
1862
+ result = typeof method === "function" ? await method.call(api, input, { throwOnError: true }) : await rawQuestionRequest(client, pending, response, isV2);
1863
+ } catch (error) {
1864
+ throw normalizeQuestionError(error);
1865
+ }
1866
+ const responseError = questionResponseError(result);
1867
+ if (responseError) throw responseError;
1828
1868
  if (result?.data === false || result === false) {
1829
1869
  throw new HttpError("OpenCode did not apply the question response", 409);
1830
1870
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backeer-opencode",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Backeer lifecycle notifications and remote permission and question replies for OpenCode",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -33,13 +33,52 @@ async function acknowledgeCommand(config, commandID, acknowledgement) {
33
33
  await postJsonWithRetry(config, url, acknowledgement)
34
34
  }
35
35
 
36
- async function replyToQuestion(client, pending, response) {
36
+ function questionResponseError(result) {
37
+ if (!result?.error) return
38
+ const status = Number(result?.response?.status) || 502
39
+ const detail = result.error?.data?.message
40
+ ?? result.error?.message
41
+ ?? "OpenCode rejected the question response"
42
+ return new HttpError(String(detail), status)
43
+ }
44
+
45
+ function normalizeQuestionError(error) {
46
+ if (error instanceof HttpError) return error
47
+ const status = Number(error?.status ?? error?.statusCode ?? error?.cause?.status)
48
+ if (!Number.isFinite(status)) return error
49
+ return new HttpError(error?.message || "OpenCode rejected the question response", status)
50
+ }
51
+
52
+ function rawQuestionRequest(client, pending, response, isV2) {
53
+ const transport = client?._client
54
+ if (typeof transport?.post !== "function") {
55
+ throw new HttpError("This OpenCode version cannot apply remote question replies", 501)
56
+ }
57
+
58
+ const rejecting = response.action === "reject"
59
+ if (isV2) {
60
+ return transport.post({
61
+ url: `/api/session/{sessionID}/question/{requestID}/${rejecting ? "reject" : "reply"}`,
62
+ path: { sessionID: pending.sessionID, requestID: pending.requestID },
63
+ ...(rejecting ? {} : { body: { answers: response.answers } }),
64
+ ...(rejecting ? {} : { headers: { "Content-Type": "application/json" } }),
65
+ throwOnError: true,
66
+ })
67
+ }
68
+ return transport.post({
69
+ url: `/question/{requestID}/${rejecting ? "reject" : "reply"}`,
70
+ path: { requestID: pending.requestID },
71
+ query: { directory: pending.directory },
72
+ ...(rejecting ? {} : { body: { answers: response.answers } }),
73
+ ...(rejecting ? {} : { headers: { "Content-Type": "application/json" } }),
74
+ throwOnError: true,
75
+ })
76
+ }
77
+
78
+ export async function replyToQuestion(client, pending, response) {
37
79
  const isV2 = pending.sourceEvent === "question.v2.asked"
38
80
  const api = isV2 ? client?.v2?.session?.question : client?.question
39
81
  const method = response.action === "reject" ? api?.reject : api?.reply
40
- if (typeof method !== "function") {
41
- throw new HttpError("This OpenCode version cannot apply remote question replies", 501)
42
- }
43
82
  const input = isV2
44
83
  ? {
45
84
  sessionID: pending.sessionID,
@@ -54,7 +93,16 @@ async function replyToQuestion(client, pending, response) {
54
93
  directory: pending.directory,
55
94
  ...(response.action === "answer" ? { answers: response.answers } : {}),
56
95
  }
57
- const result = await method.call(api, input)
96
+ let result
97
+ try {
98
+ result = typeof method === "function"
99
+ ? await method.call(api, input, { throwOnError: true })
100
+ : await rawQuestionRequest(client, pending, response, isV2)
101
+ } catch (error) {
102
+ throw normalizeQuestionError(error)
103
+ }
104
+ const responseError = questionResponseError(result)
105
+ if (responseError) throw responseError
58
106
  if (result?.data === false || result === false) {
59
107
  throw new HttpError("OpenCode did not apply the question response", 409)
60
108
  }