@astrosheep/keiyaku 4.0.0 → 4.0.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.
Files changed (241) hide show
  1. package/README.md +2 -9
  2. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +2 -2
  3. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +4 -4
  4. package/build/integrations/marketplace/plugins/keiyaku/opencode.js +2 -2
  5. package/build/integrations/marketplace/plugins/keiyaku/package.json +2 -2
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +11 -3
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +9 -6
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +94 -0
  9. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/agents/openai.yaml +4 -0
  10. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +41 -6
  11. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +162 -45
  12. package/build/src/akuma/akuma.d.ts +33 -25
  13. package/build/src/akuma/akuma.js +93 -61
  14. package/build/src/akuma/archetype.d.ts +8 -0
  15. package/build/src/akuma/archetype.js +28 -9
  16. package/build/src/akuma/body.js +196 -84
  17. package/build/src/akuma/coordinate.d.ts +9 -0
  18. package/build/src/akuma/coordinate.js +22 -0
  19. package/build/src/akuma/heart/facts.d.ts +67 -15
  20. package/build/src/akuma/heart/facts.js +3 -3
  21. package/build/src/akuma/heart/index.d.ts +41 -29
  22. package/build/src/akuma/heart/index.js +72 -193
  23. package/build/src/akuma/heart/rows.d.ts +30 -53
  24. package/build/src/akuma/heart/rows.js +91 -111
  25. package/build/src/akuma/heart/schema.d.ts +1 -1
  26. package/build/src/akuma/heart/schema.js +55 -8
  27. package/build/src/akuma/heart/storage.d.ts +33 -0
  28. package/build/src/akuma/heart/storage.js +164 -0
  29. package/build/src/akuma/heart/tells.d.ts +12 -0
  30. package/build/src/akuma/heart/tells.js +79 -0
  31. package/build/src/akuma/heart/timeline.d.ts +15 -0
  32. package/build/src/akuma/heart/timeline.js +86 -0
  33. package/build/src/akuma/index.d.ts +4 -2
  34. package/build/src/akuma/index.js +1 -1
  35. package/build/src/akuma/projection.d.ts +97 -0
  36. package/build/src/akuma/projection.js +145 -0
  37. package/build/src/akuma/provider.d.ts +57 -11
  38. package/build/src/akuma/provider.js +57 -37
  39. package/build/src/akuma/providers/{claude.d.ts → claude/events.d.ts} +9 -41
  40. package/build/src/akuma/providers/claude/events.js +185 -0
  41. package/build/src/akuma/providers/claude/index.d.ts +46 -0
  42. package/build/src/akuma/providers/claude/index.js +265 -0
  43. package/build/src/akuma/providers/claude/input.d.ts +13 -0
  44. package/build/src/akuma/providers/claude/input.js +109 -0
  45. package/build/src/akuma/providers/codex-app-server/events.d.ts +1 -1
  46. package/build/src/akuma/providers/codex-app-server/events.js +12 -12
  47. package/build/src/akuma/providers/codex-app-server/index.d.ts +13 -11
  48. package/build/src/akuma/providers/codex-app-server/index.js +73 -22
  49. package/build/src/akuma/providers/index.js +22 -8
  50. package/build/src/akuma/providers/opencode-sdk/events.d.ts +48 -0
  51. package/build/src/akuma/providers/opencode-sdk/events.js +116 -0
  52. package/build/src/akuma/providers/opencode-sdk/index.d.ts +32 -0
  53. package/build/src/akuma/providers/opencode-sdk/index.js +203 -0
  54. package/build/src/akuma/providers/opencode-sdk/session.d.ts +41 -0
  55. package/build/src/akuma/providers/opencode-sdk/session.js +62 -0
  56. package/build/src/akuma/providers/pi/events.d.ts +33 -0
  57. package/build/src/akuma/providers/pi/events.js +128 -0
  58. package/build/src/akuma/providers/pi/index.d.ts +11 -0
  59. package/build/src/akuma/providers/pi/index.js +162 -0
  60. package/build/src/akuma/requests.d.ts +1 -0
  61. package/build/src/akuma/requests.js +5 -2
  62. package/build/src/alias/index.d.ts +4 -3
  63. package/build/src/alias/index.js +2 -3
  64. package/build/src/body/render.js +2 -1
  65. package/build/src/body/shape.js +7 -1
  66. package/build/src/body/verification.js +19 -4
  67. package/build/src/cli/accepted.d.ts +2 -0
  68. package/build/src/cli/accepted.js +2 -1
  69. package/build/src/cli/commands/akuma-invoke.d.ts +42 -18
  70. package/build/src/cli/commands/akuma-invoke.js +94 -50
  71. package/build/src/cli/commands/akuma.d.ts +15 -7
  72. package/build/src/cli/commands/akuma.js +94 -56
  73. package/build/src/cli/commands/contract.d.ts +142 -0
  74. package/build/src/cli/commands/contract.js +14 -0
  75. package/build/src/cli/commands/install.js +2 -0
  76. package/build/src/cli/commands/task-invoke.d.ts +3 -1
  77. package/build/src/cli/commands/task-invoke.js +27 -2
  78. package/build/src/cli/commands/task.js +2 -2
  79. package/build/src/cli/invoke.d.ts +6 -1
  80. package/build/src/cli/invoke.js +241 -43
  81. package/build/src/cli/main.js +20 -1
  82. package/build/src/cli/parse.d.ts +13 -116
  83. package/build/src/cli/parse.js +51 -78
  84. package/build/src/cli/render/akuma-tool.d.ts +2 -0
  85. package/build/src/cli/render/akuma-tool.js +6 -2
  86. package/build/src/cli/render/akuma.d.ts +2 -1
  87. package/build/src/cli/render/akuma.js +157 -217
  88. package/build/src/cli/render/catalog.d.ts +2 -0
  89. package/build/src/cli/render/catalog.js +16 -0
  90. package/build/src/cli/render/contract.d.ts +1 -1
  91. package/build/src/cli/render/contract.js +38 -5
  92. package/build/src/cli/render/kanshi.js +53 -5
  93. package/build/src/cli/render/refusal.js +17 -0
  94. package/build/src/cli/render/terminal.d.ts +13 -0
  95. package/build/src/cli/render/terminal.js +75 -0
  96. package/build/src/cli/render/text.js +5 -0
  97. package/build/src/cli/result.d.ts +15 -3
  98. package/build/src/contract-worktree.d.ts +43 -0
  99. package/build/src/contract-worktree.js +213 -0
  100. package/build/src/coordination/durable-file.d.ts +3 -0
  101. package/build/src/coordination/durable-file.js +44 -1
  102. package/build/src/core/facts/codec.js +23 -4
  103. package/build/src/core/facts/eligibility.d.ts +1 -10
  104. package/build/src/core/facts/eligibility.js +0 -49
  105. package/build/src/core/facts/observation.d.ts +3 -0
  106. package/build/src/core/facts/observation.js +12 -0
  107. package/build/src/core/facts/types.d.ts +10 -3
  108. package/build/src/core/subject.js +2 -2
  109. package/build/src/core/verbs/amend.js +12 -16
  110. package/build/src/core/verbs/bind.js +2 -16
  111. package/build/src/core/verbs/deliver.js +20 -4
  112. package/build/src/core/verbs/placement.js +3 -8
  113. package/build/src/dispatch/index.d.ts +3 -1
  114. package/build/src/dispatch/index.js +14 -5
  115. package/build/src/duration.d.ts +10 -0
  116. package/build/src/duration.js +22 -0
  117. package/build/src/git/admission.d.ts +2 -2
  118. package/build/src/git/admission.js +26 -16
  119. package/build/src/git/hooks.d.ts +15 -0
  120. package/build/src/git/hooks.js +57 -5
  121. package/build/src/git/identity.d.ts +5 -1
  122. package/build/src/git/identity.js +23 -4
  123. package/build/src/git/integration.d.ts +49 -0
  124. package/build/src/git/integration.js +214 -0
  125. package/build/src/git/observe.d.ts +25 -13
  126. package/build/src/git/observe.js +175 -72
  127. package/build/src/git/read-observation.d.ts +41 -0
  128. package/build/src/git/read-observation.js +361 -0
  129. package/build/src/git/reconcile.d.ts +15 -5
  130. package/build/src/git/reconcile.js +238 -85
  131. package/build/src/git/repository.d.ts +19 -4
  132. package/build/src/git/repository.js +77 -43
  133. package/build/src/git/scratch.d.ts +13 -0
  134. package/build/src/git/scratch.js +66 -0
  135. package/build/src/git/target-placement.d.ts +56 -0
  136. package/build/src/git/target-placement.js +255 -0
  137. package/build/src/git/tender.d.ts +48 -0
  138. package/build/src/git/tender.js +82 -0
  139. package/build/src/git/terminal-seal.d.ts +24 -0
  140. package/build/src/git/terminal-seal.js +87 -0
  141. package/build/src/git/tree.d.ts +2 -2
  142. package/build/src/git/tree.js +4 -0
  143. package/build/src/git/workspace.d.ts +21 -0
  144. package/build/src/git/workspace.js +104 -0
  145. package/build/src/identity/selector.d.ts +6 -0
  146. package/build/src/identity/selector.js +19 -0
  147. package/build/src/index.d.ts +4 -2
  148. package/build/src/index.js +2 -1
  149. package/build/src/kanshi/index.d.ts +1 -1
  150. package/build/src/kanshi/read.d.ts +5 -2
  151. package/build/src/kanshi/read.js +154 -45
  152. package/build/src/kanshi/report.d.ts +31 -6
  153. package/build/src/kanshi/select.js +1 -1
  154. package/build/src/library/address.d.ts +48 -0
  155. package/build/src/library/address.js +134 -0
  156. package/build/src/library/akuma-creation.d.ts +10 -6
  157. package/build/src/library/akuma-creation.js +12 -11
  158. package/build/src/library/bind.d.ts +21 -0
  159. package/build/src/library/bind.js +56 -0
  160. package/build/src/library/catalog.d.ts +58 -0
  161. package/build/src/library/catalog.js +72 -0
  162. package/build/src/library/configuration.d.ts +6 -15
  163. package/build/src/library/configuration.js +14 -17
  164. package/build/src/library/contract.d.ts +21 -46
  165. package/build/src/library/contract.js +193 -183
  166. package/build/src/library/delivery.d.ts +23 -0
  167. package/build/src/library/delivery.js +21 -0
  168. package/build/src/library/fleet.d.ts +65 -0
  169. package/build/src/library/fleet.js +189 -0
  170. package/build/src/library/input.d.ts +3 -0
  171. package/build/src/library/input.js +19 -0
  172. package/build/src/library/keiyaku.d.ts +16 -2
  173. package/build/src/library/keiyaku.js +11 -1
  174. package/build/src/library/mutation.d.ts +38 -0
  175. package/build/src/library/mutation.js +39 -0
  176. package/build/src/library/refusal.d.ts +21 -0
  177. package/build/src/library/refusal.js +29 -0
  178. package/build/src/library/region.d.ts +2 -1
  179. package/build/src/library/region.js +3 -3
  180. package/build/src/library/repo.d.ts +4 -1
  181. package/build/src/library/repo.js +31 -16
  182. package/build/src/protocol/attempt.d.ts +16 -3
  183. package/build/src/protocol/attempt.js +39 -8
  184. package/build/src/protocol/bind.d.ts +9 -2
  185. package/build/src/protocol/bind.js +47 -39
  186. package/build/src/protocol/intent.d.ts +42 -18
  187. package/build/src/protocol/intent.js +36 -75
  188. package/build/src/protocol/operations.d.ts +93 -35
  189. package/build/src/protocol/operations.js +235 -123
  190. package/build/src/protocol/outcome.d.ts +7 -2
  191. package/build/src/protocol/outcome.js +9 -3
  192. package/build/src/protocol/placement.d.ts +30 -0
  193. package/build/src/protocol/placement.js +114 -0
  194. package/build/src/protocol/read/audit.d.ts +9 -3
  195. package/build/src/protocol/read/audit.js +25 -9
  196. package/build/src/protocol/read/documents.d.ts +2 -2
  197. package/build/src/protocol/read/documents.js +4 -4
  198. package/build/src/protocol/read/status.d.ts +11 -5
  199. package/build/src/protocol/read/status.js +28 -15
  200. package/build/src/protocol/run.d.ts +49 -7
  201. package/build/src/protocol/run.js +53 -25
  202. package/build/src/runtime/proc/line-rpc.d.ts +1 -0
  203. package/build/src/runtime/proc/line-rpc.js +21 -0
  204. package/build/src/runtime/proc/run.d.ts +18 -2
  205. package/build/src/runtime/proc/run.js +34 -8
  206. package/build/src/settings.d.ts +6 -0
  207. package/build/src/settings.js +29 -14
  208. package/build/src/settlement/fence.d.ts +9 -0
  209. package/build/src/settlement/fence.js +23 -0
  210. package/build/src/settlement/holder.d.ts +25 -4
  211. package/build/src/settlement/holder.js +102 -28
  212. package/build/src/settlement/settle.d.ts +8 -0
  213. package/build/src/settlement/settle.js +102 -28
  214. package/build/src/task/board.d.ts +20 -2
  215. package/build/src/task/board.js +11 -1
  216. package/build/src/task/compose.d.ts +2 -2
  217. package/build/src/task/compose.js +2 -2
  218. package/build/src/task/context.js +2 -7
  219. package/build/src/task/index.d.ts +9 -9
  220. package/build/src/task/index.js +15 -12
  221. package/build/src/task/operations.d.ts +31 -12
  222. package/build/src/task/operations.js +8 -4
  223. package/build/src/task/store.d.ts +4 -6
  224. package/build/src/task/store.js +3 -3
  225. package/build/src/verification/declaration.d.ts +1 -0
  226. package/build/src/verification/execution.d.ts +49 -0
  227. package/build/src/verification/execution.js +104 -0
  228. package/build/src/world.d.ts +13 -0
  229. package/build/src/world.js +103 -0
  230. package/package.json +7 -4
  231. package/build/src/akuma/activity.d.ts +0 -66
  232. package/build/src/akuma/activity.js +0 -139
  233. package/build/src/akuma/providers/claude.js +0 -305
  234. package/build/src/context-root.d.ts +0 -4
  235. package/build/src/context-root.js +0 -15
  236. package/build/src/git/delivery.d.ts +0 -20
  237. package/build/src/git/delivery.js +0 -109
  238. package/build/src/git/verification.d.ts +0 -14
  239. package/build/src/git/verification.js +0 -56
  240. package/build/src/verification/producer.d.ts +0 -23
  241. package/build/src/verification/producer.js +0 -54
@@ -2,6 +2,14 @@ import { LineRpcProcess } from "../../../runtime/proc/line-rpc.js";
2
2
  import { AgentEventChannel, AKUMA_REQUESTS_ENV, } from "../../provider.js";
3
3
  import { codexNotificationResult, codexObject, codexText, } from "./events.js";
4
4
  export { CODEX_ITEM_DISPOSITIONS, CODEX_NOTIFICATION_DISPOSITIONS } from "./events.js";
5
+ const INTERRUPT_TIMEOUT_MS = 1_000;
6
+ function interrupt(server, threadId, turnId) {
7
+ const request = server.request("turn/interrupt", { threadId, turnId });
8
+ return new Promise((resolve, reject) => {
9
+ const timer = setTimeout(() => reject(new Error("codex app-server interrupt timed out")), INTERRUPT_TIMEOUT_MS);
10
+ void request.then((value) => { clearTimeout(timer); resolve(value); }, (error) => { clearTimeout(timer); reject(error); });
11
+ });
12
+ }
5
13
  function diagnostic(error) {
6
14
  return error instanceof Error ? error.message : String(error);
7
15
  }
@@ -28,6 +36,25 @@ function turnId(result) {
28
36
  throw new Error("codex app-server did not return a turn id");
29
37
  return id;
30
38
  }
39
+ async function steerTurn(server, state, tell) {
40
+ if (state.settled)
41
+ return { kind: "turn-ended" };
42
+ if (state.threadId === undefined || state.turnId === undefined)
43
+ throw new Error("codex app-server turn is not admitted");
44
+ const request = server.request("turn/steer", {
45
+ threadId: state.threadId,
46
+ expectedTurnId: state.turnId,
47
+ input: [{ type: "text", text: tell.text }],
48
+ clientUserMessageId: tell.id,
49
+ });
50
+ const response = codexObject(await request);
51
+ const accepted = codexText(response?.turnId);
52
+ if (accepted === undefined)
53
+ throw new Error("codex app-server steer did not return a turn id");
54
+ if (accepted !== state.turnId)
55
+ throw new Error("codex app-server steer acknowledged a different turn");
56
+ return { kind: "accepted", fence: `${accepted}:${tell.id}` };
57
+ }
31
58
  function sandbox(cwd, options, requests) {
32
59
  return {
33
60
  type: "workspaceWrite",
@@ -46,16 +73,20 @@ async function admitTurn(server, input, state, events, config) {
46
73
  ...(input.options.systemPrompt === undefined || input.options.systemPrompt.length === 0
47
74
  ? {} : { developerInstructions: input.options.systemPrompt }),
48
75
  };
49
- state.threadId = input.session === undefined
50
- ? threadId(await server.request("thread/start", threadParams))
51
- : threadId(await server.request("thread/resume", {
52
- threadId: input.session.sessionId,
76
+ if (input.session.kind === "fresh") {
77
+ state.threadId = threadId(await server.request("thread/start", threadParams));
78
+ }
79
+ else {
80
+ state.threadId = threadId(await server.request("thread/resume", {
81
+ threadId: input.session.coordinate.sessionId,
53
82
  ...threadParams,
54
- }), input.session.sessionId);
83
+ }), input.session.coordinate.sessionId);
84
+ }
55
85
  events.emit({ type: "session", coordinate: { sessionId: state.threadId } });
56
86
  state.turnId = turnId(await server.request("turn/start", {
57
87
  threadId: state.threadId,
58
- input: [{ type: "text", text: input.prompt }],
88
+ input: [{ type: "text", text: [input.body, ...input.launchTells.map((tell) => tell.text)]
89
+ .filter((part) => part.length > 0).join("\n\n") }],
59
90
  ...(input.options.model === undefined ? {} : { model: input.options.model }),
60
91
  ...(input.options.effort === undefined ? {} : { effort: input.options.effort }),
61
92
  approvalPolicy: "never",
@@ -63,6 +94,8 @@ async function admitTurn(server, input, state, events, config) {
63
94
  }));
64
95
  }
65
96
  async function forkCodex(execution, input) {
97
+ if (!("sessionId" in input.session))
98
+ throw new Error("Codex app-server fork requires sessionId");
66
99
  const server = new LineRpcProcess({
67
100
  argv: [execution.executable ?? "codex", "app-server", "--listen", "stdio://"],
68
101
  cwd: input.cwd,
@@ -82,22 +115,28 @@ async function forkCodex(execution, input) {
82
115
  await server.close();
83
116
  }
84
117
  }
85
- async function abortTurn(server, state, completion, finish) {
86
- if (!state.settled && state.threadId !== undefined && state.turnId !== undefined) {
118
+ async function abortTurn(server, state, completion, settle) {
119
+ if (state.settled) {
120
+ await completion;
121
+ return;
122
+ }
123
+ if (state.threadId !== undefined && state.turnId !== undefined) {
87
124
  try {
88
- await server.request("turn/interrupt", { threadId: state.threadId, turnId: state.turnId });
125
+ await interrupt(server, state.threadId, state.turnId);
89
126
  await completion;
90
127
  }
91
128
  catch (error) {
92
- finish({ kind: "failed", diagnostic: diagnostic(error) });
129
+ settle({ kind: "failed", diagnostic: diagnostic(error) });
93
130
  }
94
131
  }
95
- else if (!state.settled) {
96
- finish({ kind: "failed", diagnostic: "codex app-server aborted before turn admission" });
132
+ else {
133
+ settle({ kind: "failed", diagnostic: "codex app-server aborted before turn admission" });
97
134
  }
98
- await server.close(true);
135
+ await completion;
99
136
  }
100
137
  async function startCodex(execution, input) {
138
+ if (input.session.kind === "resume" && !("sessionId" in input.session.coordinate))
139
+ throw new Error("Codex app-server resume requires sessionId");
101
140
  const events = new AgentEventChannel();
102
141
  const server = new LineRpcProcess({
103
142
  argv: [execution.executable ?? "codex", "app-server", "--listen", "stdio://"],
@@ -108,16 +147,28 @@ async function startCodex(execution, input) {
108
147
  ...(input.requests === undefined ? {} : { [AKUMA_REQUESTS_ENV]: input.requests.dir }),
109
148
  } }),
110
149
  });
111
- const state = { answers: [], settled: false, tools: new Map() };
150
+ const state = { settled: false, tools: new Map() };
112
151
  let settle;
113
152
  const completion = new Promise((resolve) => { settle = resolve; });
153
+ let terminal;
114
154
  const finish = (result) => {
115
- if (state.settled)
155
+ if (terminal !== undefined)
116
156
  return;
157
+ terminal = result;
117
158
  state.settled = true;
118
- events.end();
119
- void server.close().then(() => settle(result), (error) => settle({ kind: "failed", diagnostic: `codex app-server cleanup failed: ${diagnostic(error)}` }));
159
+ void server.endInputAndDrain().then(() => {
160
+ events.end();
161
+ settle(result);
162
+ }, () => {
163
+ events.end();
164
+ settle(result);
165
+ });
120
166
  };
167
+ server.onNotification((notification) => {
168
+ const result = codexNotificationResult(notification, state, events);
169
+ if (result !== undefined)
170
+ finish(result);
171
+ });
121
172
  server.onExit(({ code, signal, stderr }) => finish({
122
173
  kind: "failed",
123
174
  diagnostic: stderr || `codex app-server exited before completion (${code ?? signal ?? "unknown"})`,
@@ -126,21 +177,20 @@ async function startCodex(execution, input) {
126
177
  kind: "failed",
127
178
  diagnostic: `codex app-server made forbidden interactive request ${request.method}`,
128
179
  }));
129
- server.onNotification((notification) => {
130
- const result = codexNotificationResult(notification, state, events);
131
- if (result !== undefined)
132
- finish(result);
133
- });
134
180
  try {
135
181
  await admitTurn(server, input, state, events, execution.config);
136
182
  }
137
183
  catch (error) {
138
184
  finish({ kind: "failed", diagnostic: diagnostic(error) });
139
185
  }
186
+ if (state.turnId === undefined)
187
+ throw new Error("codex app-server did not admit a turn");
140
188
  return {
189
+ admission: { fence: state.turnId },
141
190
  events,
142
191
  completion,
143
192
  abort: () => abortTurn(server, state, completion, finish),
193
+ tell: (tell) => steerTurn(server, state, tell),
144
194
  };
145
195
  }
146
196
  export function createCodexAppServerProvider(input = "codex") {
@@ -157,6 +207,7 @@ export function createCodexAppServerProvider(input = "codex") {
157
207
  },
158
208
  fork: (input) => forkCodex(execution, input),
159
209
  start: (input) => startCodex(execution, input),
210
+ resume: (input) => startCodex(execution, input),
160
211
  };
161
212
  }
162
213
  export const codexAppServerProvider = createCodexAppServerProvider();
@@ -1,5 +1,7 @@
1
- import { claudeProvider, createClaudeProvider } from "./claude.js";
1
+ import { claudeProvider, createClaudeProvider } from "./claude/index.js";
2
2
  import { createCodexAppServerProvider } from "./codex-app-server/index.js";
3
+ import { createOpencodeProvider } from "./opencode-sdk/index.js";
4
+ import { createPiProvider } from "./pi/index.js";
3
5
  const PROVIDERS = {
4
6
  claude: claudeProvider,
5
7
  };
@@ -24,6 +26,19 @@ function optionalText(value, field) {
24
26
  }
25
27
  return selected;
26
28
  }
29
+ function providerKind(value) {
30
+ return value === "claude-agent-sdk" || value === "codex-app-server" || value === "opencode-sdk" || value === "pi";
31
+ }
32
+ function providerConfig(value, kind) {
33
+ const config = value.config === undefined ? undefined : record(value.config);
34
+ if (config === null)
35
+ throw new TypeError("provider execution config must be an object");
36
+ if (kind === "claude-agent-sdk" && config !== undefined)
37
+ throw new TypeError("provider execution config is unsupported by claude-agent-sdk");
38
+ if (kind === "opencode-sdk" && config !== undefined)
39
+ throw new TypeError("provider execution config is unsupported by opencode-sdk");
40
+ return config;
41
+ }
27
42
  export function decodeProviderExecution(input) {
28
43
  const value = record(input);
29
44
  if (value === null)
@@ -35,16 +50,11 @@ export function decodeProviderExecution(input) {
35
50
  if (typeof value.name !== "string" || value.name.trim().length === 0) {
36
51
  throw new TypeError("provider execution name must be a nonblank string");
37
52
  }
38
- if (value.kind !== "claude-agent-sdk" && value.kind !== "codex-app-server") {
53
+ if (!providerKind(value.kind)) {
39
54
  throw new TypeError("provider execution has unknown kind");
40
55
  }
41
56
  const executable = optionalText(value, "executable");
42
- const config = value.config === undefined ? undefined : record(value.config);
43
- if (config === null)
44
- throw new TypeError("provider execution config must be an object");
45
- if (value.kind === "claude-agent-sdk" && config !== undefined) {
46
- throw new TypeError("provider execution config is unsupported by claude-agent-sdk");
47
- }
57
+ const config = providerConfig(value, value.kind);
48
58
  const env = value.env === undefined ? undefined : record(value.env);
49
59
  if (env === null)
50
60
  throw new TypeError("provider execution env must be an object");
@@ -67,5 +77,9 @@ export function providerNamed(execution) {
67
77
  }
68
78
  if (execution.kind === "codex-app-server")
69
79
  return createCodexAppServerProvider(execution);
80
+ if (execution.kind === "opencode-sdk")
81
+ return createOpencodeProvider(execution);
82
+ if (execution.kind === "pi")
83
+ return createPiProvider(execution);
70
84
  throw new TypeError(`unknown Akuma provider kind ${execution.kind}`);
71
85
  }
@@ -0,0 +1,48 @@
1
+ import type { AgentEvent, ToolCall } from "../../provider.js";
2
+ type Emitter = {
3
+ emit(event: AgentEvent): void;
4
+ };
5
+ type ToolState = Readonly<{
6
+ name: string;
7
+ call: ToolCall;
8
+ }>;
9
+ type State = {
10
+ tools: Map<string, ToolState>;
11
+ answer: string[];
12
+ seen: Set<string>;
13
+ failure?: string;
14
+ };
15
+ export declare const OPENCODE_EVENT_DISPOSITIONS: {
16
+ readonly "session.next.agent.switched": "dropped";
17
+ readonly "session.next.model.switched": "dropped";
18
+ readonly "session.next.moved": "dropped";
19
+ readonly "session.next.prompted": "dropped";
20
+ readonly "session.next.prompt.admitted": "dropped";
21
+ readonly "session.next.context.updated": "dropped";
22
+ readonly "session.next.synthetic": "dropped";
23
+ readonly "session.next.shell.started": "dropped";
24
+ readonly "session.next.shell.ended": "dropped";
25
+ readonly "session.next.step.started": "dropped";
26
+ readonly "session.next.step.ended": "dropped";
27
+ readonly "session.next.step.failed": "mapped";
28
+ readonly "session.next.text.started": "dropped";
29
+ readonly "session.next.text.ended": "mapped";
30
+ readonly "session.next.tool.input.started": "dropped";
31
+ readonly "session.next.tool.input.ended": "dropped";
32
+ readonly "session.next.tool.called": "mapped";
33
+ readonly "session.next.tool.progress": "dropped";
34
+ readonly "session.next.tool.success": "mapped";
35
+ readonly "session.next.tool.failed": "mapped";
36
+ readonly "session.next.reasoning.started": "dropped";
37
+ readonly "session.next.reasoning.ended": "mapped";
38
+ readonly "session.next.retried": "mapped";
39
+ readonly "session.next.compaction.started": "dropped";
40
+ readonly "session.next.compaction.ended": "dropped";
41
+ readonly "session.next.revert.staged": "dropped";
42
+ readonly "session.next.revert.cleared": "dropped";
43
+ readonly "session.next.revert.committed": "dropped";
44
+ };
45
+ export declare function mapEvent(value: unknown, events: Emitter, state: State): void;
46
+ export type EventState = State;
47
+ export declare function createEventState(): State;
48
+ export {};
@@ -0,0 +1,116 @@
1
+ export const OPENCODE_EVENT_DISPOSITIONS = {
2
+ "session.next.agent.switched": "dropped",
3
+ "session.next.model.switched": "dropped",
4
+ "session.next.moved": "dropped",
5
+ "session.next.prompted": "dropped",
6
+ "session.next.prompt.admitted": "dropped",
7
+ "session.next.context.updated": "dropped",
8
+ "session.next.synthetic": "dropped",
9
+ "session.next.shell.started": "dropped",
10
+ "session.next.shell.ended": "dropped",
11
+ "session.next.step.started": "dropped",
12
+ "session.next.step.ended": "dropped",
13
+ "session.next.step.failed": "mapped",
14
+ "session.next.text.started": "dropped",
15
+ "session.next.text.ended": "mapped",
16
+ "session.next.tool.input.started": "dropped",
17
+ "session.next.tool.input.ended": "dropped",
18
+ "session.next.tool.called": "mapped",
19
+ "session.next.tool.progress": "dropped",
20
+ "session.next.tool.success": "mapped",
21
+ "session.next.tool.failed": "mapped",
22
+ "session.next.reasoning.started": "dropped",
23
+ "session.next.reasoning.ended": "mapped",
24
+ "session.next.retried": "mapped",
25
+ "session.next.compaction.started": "dropped",
26
+ "session.next.compaction.ended": "dropped",
27
+ "session.next.revert.staged": "dropped",
28
+ "session.next.revert.cleared": "dropped",
29
+ "session.next.revert.committed": "dropped",
30
+ };
31
+ function object(value) {
32
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
33
+ }
34
+ function text(value) { return typeof value === "string" && value.length > 0 ? value : undefined; }
35
+ function diagnostic(value) { const v = object(value); return text(v?.message) ?? text(v?.error) ?? "OpenCode session failed"; }
36
+ function callFor(name, input) {
37
+ const value = object(input);
38
+ if (name === "shell")
39
+ return { kind: "run", command: text(value?.command) ?? "shell" };
40
+ if (name === "read")
41
+ return { kind: "read", path: text(value?.path) ?? "file" };
42
+ if (name === "grep" || name === "search")
43
+ return { kind: "search", query: text(value?.query) ?? "search" };
44
+ return { kind: "other", display: name };
45
+ }
46
+ function resultFor(value, failed) {
47
+ return { status: failed ? "error" : "ok", ...(failed ? { message: diagnostic(value) } : {}) };
48
+ }
49
+ export function mapEvent(value, events, state) {
50
+ const event = object(value);
51
+ const eventId = text(event?.id);
52
+ if (eventId !== undefined && state.seen.has(eventId))
53
+ return;
54
+ if (eventId !== undefined)
55
+ state.seen.add(eventId);
56
+ const kind = text(event?.type) ?? "unknown";
57
+ const data = object(event?.data) ?? event ?? {};
58
+ switch (kind) {
59
+ case "session.next.text.ended": {
60
+ const value = text(data.text);
61
+ if (value !== undefined) {
62
+ state.answer.push(value);
63
+ events.emit({ type: "assistant", text: value });
64
+ }
65
+ return;
66
+ }
67
+ case "session.next.reasoning.ended": {
68
+ const value = text(data.text);
69
+ if (value !== undefined)
70
+ events.emit({ type: "thought", text: value });
71
+ return;
72
+ }
73
+ case "session.next.tool.called": {
74
+ const id = text(data.callID);
75
+ if (id === undefined) {
76
+ events.emit({ type: "unknown", kind: `${kind}/missing-id` });
77
+ return;
78
+ }
79
+ const observed = { name: text(data.tool) ?? "tool", call: callFor(text(data.tool) ?? "tool", data.input) };
80
+ state.tools.set(id, observed);
81
+ events.emit({ type: "tool", phase: "started", id, ...observed });
82
+ return;
83
+ }
84
+ case "session.next.tool.success":
85
+ case "session.next.tool.failed": {
86
+ const id = text(data.callID);
87
+ if (id === undefined) {
88
+ events.emit({ type: "unknown", kind: `${kind}/missing-id` });
89
+ return;
90
+ }
91
+ const started = state.tools.get(id);
92
+ const failed = kind.endsWith("failed");
93
+ if (started === undefined) {
94
+ events.emit({ type: "unknown", kind: `${kind}/orphan` });
95
+ return;
96
+ }
97
+ state.tools.delete(id);
98
+ events.emit({ type: "tool", phase: "completed", id, ...started, result: resultFor(data.error, failed) });
99
+ return;
100
+ }
101
+ case "session.next.step.failed":
102
+ state.failure = diagnostic(data.error);
103
+ events.emit({ type: "note", text: state.failure });
104
+ return;
105
+ case "session.next.retried":
106
+ events.emit({ type: "note", text: "Retrying" });
107
+ return;
108
+ default: {
109
+ const disposition = OPENCODE_EVENT_DISPOSITIONS[kind];
110
+ if (disposition === "dropped")
111
+ return;
112
+ events.emit({ type: "unknown", kind });
113
+ }
114
+ }
115
+ }
116
+ export function createEventState() { return { tools: new Map(), answer: [], seen: new Set() }; }
@@ -0,0 +1,32 @@
1
+ import { type ProviderAdapter, type ProviderOptions, type Session } from "../../provider.js";
2
+ import type { ProviderExecution, ResumeCoordinate } from "../../heart/index.js";
3
+ import { type OpencodeSdkLoader } from "./session.js";
4
+ export type OpencodeProviderTestOptions = Readonly<{
5
+ loader?: OpencodeSdkLoader;
6
+ }>;
7
+ export declare function createOpencodeProvider(input?: ProviderExecution | OpencodeProviderTestOptions): ProviderAdapter;
8
+ export declare const opencodeProvider: Readonly<{
9
+ confinement(input: Readonly<{
10
+ cwd: string;
11
+ options: ProviderOptions;
12
+ }>): import("../../heart/facts.js").Confinement;
13
+ admitOptions(options: ProviderOptions): import("../../provider.js").ProviderOptionAdmission;
14
+ fork?(input: Readonly<{
15
+ session: ResumeCoordinate;
16
+ at: string;
17
+ cwd: string;
18
+ }>): Promise<Readonly<{
19
+ session: ResumeCoordinate;
20
+ }>>;
21
+ start(input: import("../../provider.js").DriveInput & Readonly<{
22
+ session: Readonly<{
23
+ kind: "fresh";
24
+ }>;
25
+ }>): Promise<Session>;
26
+ resume?(input: import("../../provider.js").DriveInput & Readonly<{
27
+ session: Readonly<{
28
+ kind: "resume";
29
+ coordinate: ResumeCoordinate;
30
+ }>;
31
+ }>): Promise<Session>;
32
+ }>;
@@ -0,0 +1,203 @@
1
+ import { AgentEventChannel } from "../../provider.js";
2
+ import { createEventState, mapEvent } from "./events.js";
3
+ import { coordinate, loadOpencode, OPENCODE_SDK_PROVIDER, parseModel } from "./session.js";
4
+ function diagnostic(error) { return error instanceof Error ? error.message : String(error); }
5
+ function opencodeSessionId(coordinate) {
6
+ if (!("sessionId" in coordinate) || coordinate.sessionId === undefined) {
7
+ throw new Error("OpenCode resume requires sessionId");
8
+ }
9
+ return coordinate.sessionId;
10
+ }
11
+ function admit(options) {
12
+ if (options.access !== undefined)
13
+ throw new Error("OpenCode does not support explicit access");
14
+ if (options.network !== undefined)
15
+ throw new Error("OpenCode does not support explicit network");
16
+ if (options.systemPrompt !== undefined && options.systemPrompt.length > 0) {
17
+ throw new Error("OpenCode V2 does not expose an enforceable system prompt option");
18
+ }
19
+ if (options.effort !== undefined && options.model === undefined) {
20
+ throw new Error("OpenCode effort requires an explicit model");
21
+ }
22
+ if (options.model !== undefined)
23
+ parseModel(options.model, options.effort);
24
+ }
25
+ function eventValue(value) {
26
+ const envelope = objectData(value);
27
+ if (envelope !== undefined && typeof envelope.data === "string")
28
+ return eventValue(envelope.data);
29
+ if (typeof value !== "string")
30
+ return value;
31
+ try {
32
+ return JSON.parse(value);
33
+ }
34
+ catch {
35
+ return { type: value };
36
+ }
37
+ }
38
+ async function drive(execution, input, loader) {
39
+ admit(input.options);
40
+ const resumeSessionId = input.session.kind === "resume"
41
+ ? opencodeSessionId(input.session.coordinate)
42
+ : undefined;
43
+ const events = new AgentEventChannel();
44
+ const state = createEventState();
45
+ const abortController = new AbortController();
46
+ const runtime = await loadOpencode(execution, input.cwd, abortController.signal, loader);
47
+ let closed = false;
48
+ const closeOnce = async () => {
49
+ if (closed)
50
+ return;
51
+ closed = true;
52
+ await runtime.close();
53
+ };
54
+ try {
55
+ const session = runtime.client.v2.session;
56
+ const model = input.options.model === undefined ? undefined : parseModel(input.options.model, input.options.effort);
57
+ const response = input.session.kind === "fresh"
58
+ ? await session.create({ location: { directory: input.cwd }, ...(model === undefined ? {} : { model }) }, { throwOnError: true })
59
+ : await session.get({ sessionID: resumeSessionId }, { throwOnError: true });
60
+ const responseData = objectData(objectData(response)?.data);
61
+ const info = objectData(responseData?.data) ?? responseData;
62
+ const sessionId = textData(info?.id) ?? resumeSessionId;
63
+ if (sessionId === undefined)
64
+ throw new Error("OpenCode did not return a session id");
65
+ if (input.session.kind === "resume" && model !== undefined) {
66
+ await session.switchModel({ sessionID: sessionId, model }, { throwOnError: true });
67
+ }
68
+ events.emit({ type: "session", coordinate: coordinate(sessionId) });
69
+ const streamResult = await session.events({ sessionID: sessionId });
70
+ const iterator = streamResult.stream[Symbol.asyncIterator]();
71
+ const promptText = [input.body, ...input.launchTells.map((tell) => tell.text)].filter((part) => part.length > 0).join("\n\n");
72
+ const promptResponse = await session.prompt({ sessionID: sessionId, prompt: { text: promptText } }, { throwOnError: true });
73
+ const fence = admittedFence(sessionId, promptResponse);
74
+ let settled = false;
75
+ let settle;
76
+ const completion = new Promise((resolve) => { settle = resolve; });
77
+ const finish = async (result) => {
78
+ if (settled)
79
+ return;
80
+ settled = true;
81
+ events.end();
82
+ try {
83
+ await closeOnce();
84
+ settle(result);
85
+ }
86
+ catch (error) {
87
+ settle({ kind: "failed", diagnostic: `OpenCode cleanup failed: ${diagnostic(error)}` });
88
+ }
89
+ };
90
+ void (async () => {
91
+ try {
92
+ await consumeSession(session, sessionId, iterator, state, events);
93
+ const messages = await session.messages({ sessionID: sessionId, order: "desc", limit: 32 }, { throwOnError: true });
94
+ const final = messages.data.data.find((message) => message.type === "assistant");
95
+ const answer = final?.type === "assistant" ? final.content.filter((part) => part.type === "text").map((part) => part.text).join("\n\n") : state.answer.join("\n\n");
96
+ const historyId = final?.type === "assistant" ? textData(final.id) : undefined;
97
+ if (state.failure !== undefined || final?.type === "assistant" && final.error !== undefined)
98
+ await finish({ kind: "failed", diagnostic: state.failure ?? diagnostic(final?.type === "assistant" ? final.error : undefined) });
99
+ else if (historyId === undefined) {
100
+ await finish({ kind: "failed", diagnostic: "OpenCode completed without a native assistant answer/history point" });
101
+ }
102
+ else
103
+ await finish({ kind: "answered", answer, historyId });
104
+ }
105
+ catch (error) {
106
+ await finish({ kind: "failed", diagnostic: diagnostic(error) });
107
+ }
108
+ })();
109
+ return {
110
+ admission: { fence },
111
+ events,
112
+ completion,
113
+ abort: async () => {
114
+ abortController.abort();
115
+ void session.interrupt({ sessionID: sessionId }, { throwOnError: true }).catch(() => undefined);
116
+ await finish({ kind: "failed", diagnostic: "OpenCode session interrupted" });
117
+ },
118
+ };
119
+ }
120
+ catch (error) {
121
+ try {
122
+ await closeOnce();
123
+ }
124
+ catch (cleanupError) {
125
+ throw new Error(`OpenCode setup failed: ${diagnostic(error)}; cleanup failed: ${diagnostic(cleanupError)}`);
126
+ }
127
+ throw error;
128
+ }
129
+ }
130
+ function objectData(value) { return value !== null && typeof value === "object" ? value : undefined; }
131
+ function textData(value) { return typeof value === "string" && value.trim().length > 0 ? value : undefined; }
132
+ function admittedFence(sessionId, response) {
133
+ const outer = objectData(response);
134
+ const envelope = objectData(outer?.data);
135
+ const admission = objectData(envelope?.data) ?? envelope;
136
+ const id = textData(admission?.id);
137
+ const sequence = typeof admission?.admittedSeq === "number" && Number.isSafeInteger(admission.admittedSeq)
138
+ ? admission.admittedSeq : undefined;
139
+ if (id === undefined && sequence === undefined)
140
+ throw new Error("OpenCode did not return prompt admission evidence");
141
+ return `${sessionId}:${sequence ?? id}`;
142
+ }
143
+ async function consumeSession(session, sessionId, iterator, state, events) {
144
+ const stream = (async () => {
145
+ for (;;) {
146
+ const next = await iterator.next();
147
+ if (next.done)
148
+ return;
149
+ mapEvent(eventValue(next.value), events, state);
150
+ }
151
+ })();
152
+ stream.catch(() => undefined);
153
+ await session.wait({ sessionID: sessionId }, { throwOnError: true });
154
+ await iterator.return?.();
155
+ await stream;
156
+ let after;
157
+ for (;;) {
158
+ const history = await session.history({ sessionID: sessionId, limit: 200, ...(after === undefined ? {} : { after }) }, { throwOnError: true });
159
+ for (const event of history.data.data)
160
+ mapEvent(event, events, state);
161
+ const latest = history.data.data.at(-1)?.durable?.seq;
162
+ if (!history.data.hasMore || latest === undefined || latest === after)
163
+ return;
164
+ after = latest;
165
+ }
166
+ }
167
+ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, kind: "opencode-sdk" }) {
168
+ const execution = "kind" in input ? input : { name: OPENCODE_SDK_PROVIDER, kind: "opencode-sdk" };
169
+ const loader = "loader" in input ? input.loader : undefined;
170
+ return {
171
+ confinement: ({ cwd }) => ({ kind: "declared", writableRoots: [cwd] }),
172
+ admitOptions(options) {
173
+ try {
174
+ admit(options);
175
+ }
176
+ catch (error) {
177
+ return { kind: "refused", diagnostic: diagnostic(error) };
178
+ }
179
+ return { kind: "admitted", options: Object.freeze({ ...options }) };
180
+ },
181
+ start: (input) => drive(execution, input, loader),
182
+ resume: (input) => drive(execution, input, loader),
183
+ fork: async (input) => {
184
+ const sessionId = opencodeSessionId(input.session);
185
+ const runtime = await loadOpencode(execution, input.cwd, new AbortController().signal, loader);
186
+ try {
187
+ const fork = runtime.client.session?.fork;
188
+ if (fork === undefined)
189
+ throw new Error("OpenCode fork is unavailable");
190
+ const result = objectData(await fork({ sessionID: sessionId, messageID: input.at }));
191
+ const data = objectData(result?.data);
192
+ const id = textData(data?.id);
193
+ if (id === undefined || id === sessionId)
194
+ throw new Error("OpenCode fork returned an invalid session id");
195
+ return { session: coordinate(id) };
196
+ }
197
+ finally {
198
+ await runtime.close();
199
+ }
200
+ },
201
+ };
202
+ }
203
+ export const opencodeProvider = createOpencodeProvider();