@amalgm/chat 0.1.2 → 0.2.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 (286) hide show
  1. package/AGENTS.md +98 -0
  2. package/PURPOSE.md +143 -112
  3. package/README.md +87 -54
  4. package/dist/acp/index.d.ts +7 -0
  5. package/dist/acp/index.d.ts.map +1 -0
  6. package/dist/acp/index.js +7 -0
  7. package/dist/acp/index.js.map +1 -0
  8. package/dist/acp/projection.d.ts +30 -0
  9. package/dist/acp/projection.d.ts.map +1 -0
  10. package/dist/acp/projection.js +147 -0
  11. package/dist/acp/projection.js.map +1 -0
  12. package/dist/api/conversations.d.ts +66 -0
  13. package/dist/api/conversations.d.ts.map +1 -0
  14. package/dist/api/conversations.js +202 -0
  15. package/dist/api/conversations.js.map +1 -0
  16. package/dist/api/index.d.ts +3 -3
  17. package/dist/api/index.d.ts.map +1 -1
  18. package/dist/api/index.js +1 -2
  19. package/dist/api/index.js.map +1 -1
  20. package/dist/conversations/index.d.ts +3 -0
  21. package/dist/conversations/index.d.ts.map +1 -0
  22. package/dist/conversations/index.js +3 -0
  23. package/dist/conversations/index.js.map +1 -0
  24. package/dist/conversations/transcript.d.ts +13 -0
  25. package/dist/conversations/transcript.d.ts.map +1 -0
  26. package/dist/conversations/transcript.js +171 -0
  27. package/dist/conversations/transcript.js.map +1 -0
  28. package/dist/conversations/types.d.ts +147 -0
  29. package/dist/conversations/types.d.ts.map +1 -0
  30. package/dist/conversations/types.js +9 -0
  31. package/dist/conversations/types.js.map +1 -0
  32. package/dist/events/constructors.d.ts +9 -15
  33. package/dist/events/constructors.d.ts.map +1 -1
  34. package/dist/events/constructors.js +6 -12
  35. package/dist/events/constructors.js.map +1 -1
  36. package/dist/events/index.d.ts +2 -3
  37. package/dist/events/index.d.ts.map +1 -1
  38. package/dist/events/index.js +2 -3
  39. package/dist/events/index.js.map +1 -1
  40. package/dist/events/schema.d.ts +2 -5
  41. package/dist/events/schema.d.ts.map +1 -1
  42. package/dist/events/schema.js +2 -5
  43. package/dist/events/schema.js.map +1 -1
  44. package/dist/execution/contract.d.ts +51 -0
  45. package/dist/execution/contract.d.ts.map +1 -0
  46. package/dist/execution/contract.js +127 -0
  47. package/dist/execution/contract.js.map +1 -0
  48. package/dist/execution/index.d.ts +6 -0
  49. package/dist/execution/index.d.ts.map +1 -0
  50. package/dist/execution/index.js +4 -0
  51. package/dist/execution/index.js.map +1 -0
  52. package/dist/execution/revision.d.ts +5 -0
  53. package/dist/execution/revision.d.ts.map +1 -0
  54. package/dist/execution/revision.js +17 -0
  55. package/dist/execution/revision.js.map +1 -0
  56. package/dist/execution/store.d.ts +26 -0
  57. package/dist/execution/store.d.ts.map +1 -0
  58. package/dist/execution/store.js +38 -0
  59. package/dist/execution/store.js.map +1 -0
  60. package/dist/index.d.ts +8 -25
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +7 -25
  63. package/dist/index.js.map +1 -1
  64. package/dist/input/index.d.ts +3 -0
  65. package/dist/input/index.d.ts.map +1 -0
  66. package/dist/input/index.js +2 -0
  67. package/dist/input/index.js.map +1 -0
  68. package/dist/input/prompt.d.ts +17 -0
  69. package/dist/input/prompt.d.ts.map +1 -0
  70. package/dist/input/prompt.js +104 -0
  71. package/dist/input/prompt.js.map +1 -0
  72. package/dist/normalizers/claude.d.ts +6 -9
  73. package/dist/normalizers/claude.d.ts.map +1 -1
  74. package/dist/normalizers/claude.js +5 -8
  75. package/dist/normalizers/claude.js.map +1 -1
  76. package/dist/normalizers/codex.d.ts +5 -8
  77. package/dist/normalizers/codex.d.ts.map +1 -1
  78. package/dist/normalizers/codex.js +4 -7
  79. package/dist/normalizers/codex.js.map +1 -1
  80. package/dist/normalizers/cursor.d.ts +1 -2
  81. package/dist/normalizers/cursor.d.ts.map +1 -1
  82. package/dist/normalizers/cursor.js +2 -3
  83. package/dist/normalizers/cursor.js.map +1 -1
  84. package/dist/normalizers/index.d.ts +3 -5
  85. package/dist/normalizers/index.d.ts.map +1 -1
  86. package/dist/normalizers/index.js +3 -5
  87. package/dist/normalizers/index.js.map +1 -1
  88. package/dist/normalizers/opencode.d.ts +2 -4
  89. package/dist/normalizers/opencode.d.ts.map +1 -1
  90. package/dist/normalizers/opencode.js +2 -4
  91. package/dist/normalizers/opencode.js.map +1 -1
  92. package/dist/normalizers/pi.d.ts +3 -4
  93. package/dist/normalizers/pi.d.ts.map +1 -1
  94. package/dist/normalizers/pi.js +3 -4
  95. package/dist/normalizers/pi.js.map +1 -1
  96. package/dist/sessions/chat.d.ts +176 -0
  97. package/dist/sessions/chat.d.ts.map +1 -0
  98. package/dist/sessions/chat.js +701 -0
  99. package/dist/sessions/chat.js.map +1 -0
  100. package/dist/sessions/index.d.ts +3 -0
  101. package/dist/sessions/index.d.ts.map +1 -0
  102. package/dist/sessions/index.js +2 -0
  103. package/dist/sessions/index.js.map +1 -0
  104. package/dist/transcript/index.d.ts +1 -1
  105. package/dist/transcript/index.js +1 -1
  106. package/dist/transcript/message-reducer.d.ts +1 -1
  107. package/dist/transcript/message-reducer.js +1 -1
  108. package/dist/transcript/part-accumulator.d.ts +1 -1
  109. package/dist/transcript/part-accumulator.js +1 -1
  110. package/dist/transcript/tool-display.d.ts +1 -1
  111. package/dist/transcript/tool-display.js +1 -1
  112. package/dist/transcript/tool-shape.d.ts +1 -1
  113. package/dist/transcript/tool-shape.js +1 -1
  114. package/dist/usage/finalize.d.ts +7 -9
  115. package/dist/usage/finalize.d.ts.map +1 -1
  116. package/dist/usage/finalize.js +11 -13
  117. package/dist/usage/finalize.js.map +1 -1
  118. package/dist/usage/index.d.ts +2 -3
  119. package/dist/usage/index.d.ts.map +1 -1
  120. package/dist/usage/index.js +2 -3
  121. package/dist/usage/index.js.map +1 -1
  122. package/dist/usage/model-catalog.d.ts +2 -3
  123. package/dist/usage/model-catalog.d.ts.map +1 -1
  124. package/dist/usage/model-catalog.js +10 -3
  125. package/dist/usage/model-catalog.js.map +1 -1
  126. package/dist/usage/normalize.d.ts +5 -7
  127. package/dist/usage/normalize.d.ts.map +1 -1
  128. package/dist/usage/normalize.js +3 -5
  129. package/dist/usage/normalize.js.map +1 -1
  130. package/dist/usage/ports.d.ts +14 -24
  131. package/dist/usage/ports.d.ts.map +1 -1
  132. package/dist/usage/ports.js +9 -17
  133. package/dist/usage/ports.js.map +1 -1
  134. package/dist/usage/steps.d.ts +70 -0
  135. package/dist/usage/steps.d.ts.map +1 -0
  136. package/dist/usage/steps.js +152 -0
  137. package/dist/usage/steps.js.map +1 -0
  138. package/dist/usage/tokens.d.ts +4 -7
  139. package/dist/usage/tokens.d.ts.map +1 -1
  140. package/dist/usage/tokens.js +4 -7
  141. package/dist/usage/tokens.js.map +1 -1
  142. package/docs/contracts/acp-and-step-usage.md +51 -0
  143. package/docs/contracts/conversation-persistence.md +47 -0
  144. package/docs/contracts/input-and-execution.md +177 -0
  145. package/docs/contracts/platform-authorization.md +22 -0
  146. package/host/adapters/acp-capabilities.js +30 -0
  147. package/host/adapters/acp-client.js +156 -0
  148. package/host/adapters/acp.js +284 -0
  149. package/host/adapters/claude.js +235 -0
  150. package/host/adapters/codex.js +733 -0
  151. package/host/adapters/cursor.js +347 -0
  152. package/host/adapters/input-capabilities.js +89 -0
  153. package/host/adapters/opencode.js +486 -0
  154. package/host/adapters/pi.js +519 -0
  155. package/host/adapters/prompt.js +212 -0
  156. package/host/auth.js +257 -0
  157. package/host/credentials/store.js +209 -0
  158. package/host/http.d.ts +32 -0
  159. package/host/http.js +238 -0
  160. package/host/index.d.ts +84 -0
  161. package/host/index.js +59 -0
  162. package/host/lib/runtime-paths.js +44 -0
  163. package/host/model-resolution.js +313 -0
  164. package/host/native-contract.js +185 -0
  165. package/host/native-runtime.js +194 -0
  166. package/host/permissions.js +27 -0
  167. package/host/platform-egress.js +146 -0
  168. package/host/recorder.js +61 -0
  169. package/host/sqlite/index.d.ts +69 -0
  170. package/host/sqlite/index.js +704 -0
  171. package/host/tooling/mcp-bundle.js +214 -0
  172. package/host/tooling/mcp-relay.js +83 -0
  173. package/host/tooling/native-binaries.js +667 -0
  174. package/host/tooling/native-config.js +460 -0
  175. package/host/tooling/package-import.js +111 -0
  176. package/host/tooling/runtime-home.js +26 -0
  177. package/host/tooling/system-instructions.js +69 -0
  178. package/host/tooling/system-prompt.js +35 -0
  179. package/package.json +45 -26
  180. package/skills/chat/SKILL.md +97 -0
  181. package/skills/chat/agents/openai.yaml +4 -0
  182. package/skills/chat/references/contracts.md +131 -0
  183. package/dist/api/chat-payloads.d.ts +0 -10
  184. package/dist/api/chat-payloads.d.ts.map +0 -1
  185. package/dist/api/chat-payloads.js +0 -57
  186. package/dist/api/chat-payloads.js.map +0 -1
  187. package/dist/contract/chat-input.d.ts +0 -144
  188. package/dist/contract/chat-input.d.ts.map +0 -1
  189. package/dist/contract/chat-input.js +0 -261
  190. package/dist/contract/chat-input.js.map +0 -1
  191. package/dist/contract/create.d.ts +0 -191
  192. package/dist/contract/create.d.ts.map +0 -1
  193. package/dist/contract/create.js +0 -249
  194. package/dist/contract/create.js.map +0 -1
  195. package/dist/contract/freeze.d.ts +0 -85
  196. package/dist/contract/freeze.d.ts.map +0 -1
  197. package/dist/contract/freeze.js +0 -151
  198. package/dist/contract/freeze.js.map +0 -1
  199. package/dist/contract/harnesses.d.ts +0 -21
  200. package/dist/contract/harnesses.d.ts.map +0 -1
  201. package/dist/contract/harnesses.js +0 -39
  202. package/dist/contract/harnesses.js.map +0 -1
  203. package/dist/contract/index.d.ts +0 -17
  204. package/dist/contract/index.d.ts.map +0 -1
  205. package/dist/contract/index.js +0 -17
  206. package/dist/contract/index.js.map +0 -1
  207. package/dist/contract/input.d.ts +0 -45
  208. package/dist/contract/input.d.ts.map +0 -1
  209. package/dist/contract/input.js +0 -78
  210. package/dist/contract/input.js.map +0 -1
  211. package/dist/contract/models.d.ts +0 -57
  212. package/dist/contract/models.d.ts.map +0 -1
  213. package/dist/contract/models.js +0 -380
  214. package/dist/contract/models.js.map +0 -1
  215. package/dist/contract/payload-merge.d.ts +0 -76
  216. package/dist/contract/payload-merge.d.ts.map +0 -1
  217. package/dist/contract/payload-merge.js +0 -215
  218. package/dist/contract/payload-merge.js.map +0 -1
  219. package/dist/events/tool-display.d.ts +0 -22
  220. package/dist/events/tool-display.d.ts.map +0 -1
  221. package/dist/events/tool-display.js +0 -60
  222. package/dist/events/tool-display.js.map +0 -1
  223. package/dist/events/tool-shape.d.ts +0 -48
  224. package/dist/events/tool-shape.d.ts.map +0 -1
  225. package/dist/events/tool-shape.js +0 -225
  226. package/dist/events/tool-shape.js.map +0 -1
  227. package/dist/relay/index.d.ts +0 -65
  228. package/dist/relay/index.d.ts.map +0 -1
  229. package/dist/relay/index.js +0 -197
  230. package/dist/relay/index.js.map +0 -1
  231. package/dist/turns/index.d.ts +0 -11
  232. package/dist/turns/index.d.ts.map +0 -1
  233. package/dist/turns/index.js +0 -11
  234. package/dist/turns/index.js.map +0 -1
  235. package/dist/turns/queries.d.ts +0 -36
  236. package/dist/turns/queries.d.ts.map +0 -1
  237. package/dist/turns/queries.js +0 -37
  238. package/dist/turns/queries.js.map +0 -1
  239. package/dist/turns/replay.d.ts +0 -105
  240. package/dist/turns/replay.d.ts.map +0 -1
  241. package/dist/turns/replay.js +0 -163
  242. package/dist/turns/replay.js.map +0 -1
  243. package/dist/turns/status.d.ts +0 -49
  244. package/dist/turns/status.d.ts.map +0 -1
  245. package/dist/turns/status.js +0 -56
  246. package/dist/turns/status.js.map +0 -1
  247. package/dist/turns/turn-store.d.ts +0 -109
  248. package/dist/turns/turn-store.d.ts.map +0 -1
  249. package/dist/turns/turn-store.js +0 -169
  250. package/dist/turns/turn-store.js.map +0 -1
  251. package/dist/warm/client-cache.d.ts +0 -47
  252. package/dist/warm/client-cache.d.ts.map +0 -1
  253. package/dist/warm/client-cache.js +0 -48
  254. package/dist/warm/client-cache.js.map +0 -1
  255. package/dist/warm/envelope-cache.d.ts +0 -79
  256. package/dist/warm/envelope-cache.d.ts.map +0 -1
  257. package/dist/warm/envelope-cache.js +0 -85
  258. package/dist/warm/envelope-cache.js.map +0 -1
  259. package/dist/warm/index.d.ts +0 -18
  260. package/dist/warm/index.d.ts.map +0 -1
  261. package/dist/warm/index.js +0 -12
  262. package/dist/warm/index.js.map +0 -1
  263. package/dist/warm/payload.d.ts +0 -137
  264. package/dist/warm/payload.d.ts.map +0 -1
  265. package/dist/warm/payload.js +0 -112
  266. package/dist/warm/payload.js.map +0 -1
  267. package/dist/warm/revision.d.ts +0 -70
  268. package/dist/warm/revision.d.ts.map +0 -1
  269. package/dist/warm/revision.js +0 -63
  270. package/dist/warm/revision.js.map +0 -1
  271. package/dist/warm/store-policy.d.ts +0 -57
  272. package/dist/warm/store-policy.d.ts.map +0 -1
  273. package/dist/warm/store-policy.js +0 -89
  274. package/dist/warm/store-policy.js.map +0 -1
  275. package/dist/warm/store.d.ts +0 -42
  276. package/dist/warm/store.d.ts.map +0 -1
  277. package/dist/warm/store.js +0 -69
  278. package/dist/warm/store.js.map +0 -1
  279. package/dist/wire/frames.d.ts +0 -208
  280. package/dist/wire/frames.d.ts.map +0 -1
  281. package/dist/wire/frames.js +0 -245
  282. package/dist/wire/frames.js.map +0 -1
  283. package/dist/wire/index.d.ts +0 -7
  284. package/dist/wire/index.d.ts.map +0 -1
  285. package/dist/wire/index.js +0 -7
  286. package/dist/wire/index.js.map +0 -1
@@ -0,0 +1,733 @@
1
+ // Codex app-server JSON-RPC-over-stdio adapter.
2
+ //
3
+ // Event construction and normalization come from the SDK; the adapter session
4
+ // object doubles as normalizer state. Effectful tooling and the recorder are
5
+ // sibling host modules.
6
+ //
7
+ // Hook renderers are injected through the constructor option
8
+ // `options.renderers` on `CodexAdapter` and the optional trailing `renderers`
9
+ // parameter on config builders. Hook rendering stays inside this boundary.
10
+ // The uninjected default reports no hooks; the JSON/map renderers throw if
11
+ // reached without injection (unreachable when `hasHooks` is false).
12
+
13
+ import { spawn, spawnSync } from 'node:child_process';
14
+ import fs from 'node:fs';
15
+ import os from 'node:os';
16
+ import path from 'node:path';
17
+ import { createEventConstructors } from '../../dist/events/index.js';
18
+ import { codexErrorMessage, createCodexNormalizer } from '../../dist/normalizers/codex.js';
19
+ import { recordNativeEvent } from '../recorder.js';
20
+ import { relayedMcpServers, toCodexMcpToml } from '../tooling/mcp-bundle.js';
21
+ import { bundledCodexBinary, bundledCodexPathDirs, executableExists, findOnPath } from '../tooling/native-binaries.js';
22
+ import { syncCodexProviderAuth } from '../tooling/native-config.js';
23
+ import { prepareHarnessRuntime } from '../tooling/runtime-home.js';
24
+ import { composeSystemPrompt } from '../tooling/system-prompt.js';
25
+ import { requestPermission } from '../permissions.js';
26
+ import {
27
+ COMMON_REASONING_SETTINGS,
28
+ COMMON_SPEED_SETTINGS,
29
+ nativeInputCapabilities,
30
+ } from './input-capabilities.js';
31
+ import { codexPromptInput } from './prompt.js';
32
+
33
+ const events = createEventConstructors({ clockMs: () => Date.now() });
34
+ const { done, errorEvent } = events;
35
+ const { normalizeCodexNotification } = createCodexNormalizer(events);
36
+
37
+ // Engine default: require('../../amalgm-mcp/agent-config/renderers/hooks').
38
+ const defaultRenderers = {
39
+ hasHooks: () => false,
40
+ toCodexHooksJson() {
41
+ throw new Error('CodexAdapter: renderers.toCodexHooksJson was not injected (engine source: amalgm-mcp/agent-config/renderers/hooks.js)');
42
+ },
43
+ toHookMap() {
44
+ throw new Error('CodexAdapter: renderers.toHookMap was not injected (engine source: amalgm-mcp/agent-config/renderers/hooks.js)');
45
+ },
46
+ };
47
+
48
+ class JsonLineRpc {
49
+ constructor({ binary, cwd, env }) {
50
+ this.id = 1;
51
+ this.pending = new Map();
52
+ this.handlers = new Set();
53
+ this.requestHandler = null;
54
+ this.buffer = '';
55
+ this.closed = false;
56
+ this.closeError = null;
57
+ this.child = spawn(binary, ['app-server'], {
58
+ cwd,
59
+ env,
60
+ stdio: ['pipe', 'pipe', 'pipe'],
61
+ detached: process.platform !== 'win32',
62
+ windowsHide: true,
63
+ });
64
+ this.child.stdout.setEncoding('utf8');
65
+ this.child.stdout.on('data', (chunk) => this.onData(chunk));
66
+ this.child.stderr.setEncoding('utf8');
67
+ this.child.stderr.on('data', (chunk) => {
68
+ const text = String(chunk || '').trim();
69
+ if (text && process.env.AMALGM_CHAT_DEBUG) console.warn('[Codex]', text.slice(0, 1000));
70
+ });
71
+ this.child.on('error', (err) => {
72
+ this.failAll(err);
73
+ });
74
+ this.child.on('exit', (code, signal) => {
75
+ const err = new Error(`codex app-server exited (${code ?? signal ?? 'unknown'})`);
76
+ this.failAll(err);
77
+ });
78
+ this.child.stdin.on('error', (err) => {
79
+ this.failAll(err);
80
+ });
81
+ }
82
+
83
+ failAll(err) {
84
+ this.closed = true;
85
+ this.closeError = err;
86
+ for (const pending of this.pending.values()) pending.reject(err);
87
+ this.pending.clear();
88
+ }
89
+
90
+ rejectPending(id, err) {
91
+ const pending = this.pending.get(id);
92
+ if (!pending) return;
93
+ this.pending.delete(id);
94
+ pending.reject(err);
95
+ }
96
+
97
+ onData(chunk) {
98
+ this.buffer += chunk;
99
+ const lines = this.buffer.split('\n');
100
+ this.buffer = lines.pop() || '';
101
+ for (const line of lines) {
102
+ if (!line.trim()) continue;
103
+ let msg;
104
+ try { msg = JSON.parse(line); } catch { continue; }
105
+ if (msg.id !== undefined && (msg.result !== undefined || msg.error !== undefined)) {
106
+ const pending = this.pending.get(msg.id);
107
+ if (!pending) continue;
108
+ this.pending.delete(msg.id);
109
+ if (msg.error) pending.reject(new Error(msg.error.message || JSON.stringify(msg.error)));
110
+ else pending.resolve(msg.result);
111
+ } else if (msg.id !== undefined && msg.method) {
112
+ this.handleServerRequest(msg);
113
+ } else {
114
+ if (process.env.AMALGM_CHAT_DEBUG_CODEX === '1') {
115
+ console.log('[Codex:event]', JSON.stringify(msg).slice(0, 4000));
116
+ }
117
+ for (const handler of this.handlers) handler(msg);
118
+ }
119
+ }
120
+ }
121
+
122
+ async handleServerRequest(msg) {
123
+ try {
124
+ if (!this.requestHandler) throw new Error(`Method not supported: ${msg.method}`);
125
+ const result = await this.requestHandler(msg.method, msg.params || {});
126
+ this.write({ id: msg.id, result: result ?? {} });
127
+ } catch (error) {
128
+ this.write({ id: msg.id, error: { code: -32603, message: error.message || 'Internal error' } });
129
+ }
130
+ }
131
+
132
+ write(value) {
133
+ if (this.closed || this.child.exitCode !== null || this.child.signalCode !== null) return;
134
+ try {
135
+ this.child.stdin.write(`${JSON.stringify(value)}\n`, (error) => {
136
+ if (error) this.failAll(error);
137
+ });
138
+ } catch (error) {
139
+ this.failAll(error);
140
+ }
141
+ }
142
+
143
+ request(method, params = {}, timeoutMs = 180000) {
144
+ if (this.closeError) return Promise.reject(this.closeError);
145
+ if (this.closed || this.child.exitCode !== null || this.child.signalCode !== null) {
146
+ return Promise.reject(new Error('codex app-server is not running'));
147
+ }
148
+ const id = this.id++;
149
+ const payload = `${JSON.stringify({ id, method, params })}\n`;
150
+ return new Promise((resolve, reject) => {
151
+ const timer = setTimeout(() => {
152
+ this.pending.delete(id);
153
+ reject(new Error(`${method} timed out`));
154
+ }, timeoutMs);
155
+ this.pending.set(id, {
156
+ resolve: (value) => { clearTimeout(timer); resolve(value); },
157
+ reject: (err) => { clearTimeout(timer); reject(err); },
158
+ });
159
+ try {
160
+ this.child.stdin.write(payload, (err) => {
161
+ if (err) this.rejectPending(id, err);
162
+ });
163
+ } catch (err) {
164
+ this.rejectPending(id, err);
165
+ }
166
+ });
167
+ }
168
+
169
+ notify(method, params) {
170
+ if (this.closed || this.child.exitCode !== null || this.child.signalCode !== null) return;
171
+ const payload = `${JSON.stringify(params === undefined ? { method } : { method, params })}\n`;
172
+ try {
173
+ this.child.stdin.write(payload, (err) => {
174
+ if (err) this.failAll(err);
175
+ });
176
+ } catch (err) {
177
+ this.failAll(err);
178
+ }
179
+ }
180
+
181
+ on(handler) {
182
+ this.handlers.add(handler);
183
+ return () => this.handlers.delete(handler);
184
+ }
185
+
186
+ onRequest(handler) {
187
+ this.requestHandler = handler;
188
+ }
189
+
190
+ close() {
191
+ try { this.child.stdin.end(); } catch {}
192
+ try {
193
+ if (process.platform !== 'win32' && this.child.pid) process.kill(-this.child.pid, 'SIGTERM');
194
+ else this.child.kill('SIGTERM');
195
+ } catch {}
196
+ }
197
+ }
198
+
199
+ function resolveBinary() {
200
+ const home = process.env.HOME || os.homedir();
201
+ const candidates = [
202
+ process.env.CODEX_BINARY,
203
+ bundledCodexBinary(),
204
+ path.join(home, '.npm-global/bin/codex'),
205
+ '/opt/homebrew/bin/codex',
206
+ '/usr/local/bin/codex',
207
+ findOnPath('codex'),
208
+ ].filter(Boolean);
209
+ const failures = [];
210
+ for (const candidate of candidates) {
211
+ if (!executableExists(candidate)) {
212
+ failures.push(`${candidate} (missing or not executable)`);
213
+ continue;
214
+ }
215
+ const res = spawnSync(candidate, ['--version'], { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' });
216
+ if (res.status === 0) return candidate;
217
+ failures.push(`${candidate} (--version failed: ${res.error?.message || res.status || res.signal || 'unknown'})`);
218
+ }
219
+ throw new Error(`Codex CLI binary is not available. Checked: ${failures.join(', ') || 'no candidates'}. Reinstall Amalgm or run amalgm update so the bundled Codex native package is present.`);
220
+ }
221
+
222
+ function codexEnv(baseEnv, binary) {
223
+ const env = { ...(baseEnv || {}) };
224
+ if (binary !== bundledCodexBinary()) return env;
225
+ const pathDirs = bundledCodexPathDirs();
226
+ if (pathDirs.length > 0) {
227
+ env.PATH = [...pathDirs, env.PATH].filter(Boolean).join(path.delimiter);
228
+ }
229
+ env.CODEX_MANAGED_BY_NPM = '1';
230
+ return env;
231
+ }
232
+
233
+ function positiveInteger(value) {
234
+ const number = Number(value);
235
+ return Number.isFinite(number) && number > 0 ? Math.floor(number) : null;
236
+ }
237
+
238
+ function modelWindowConfigLines(contract) {
239
+ const contextWindow = positiveInteger(contract.contextWindow);
240
+ if (!contextWindow) return [];
241
+ return [
242
+ `model_context_window = ${contextWindow}`,
243
+ `model_auto_compact_token_limit = ${Math.max(1, Math.floor(contextWindow * 0.9))}`,
244
+ '',
245
+ ];
246
+ }
247
+
248
+ function serviceTierForContract(contract) {
249
+ return contract.fastMode ? 'fast' : null;
250
+ }
251
+
252
+ function isCompactCommand(text) {
253
+ return String(text || '').trim().toLowerCase() === '/compact';
254
+ }
255
+
256
+ function readTextFile(file) {
257
+ try {
258
+ return fs.readFileSync(file, 'utf8');
259
+ } catch {
260
+ return '';
261
+ }
262
+ }
263
+
264
+ function ensureCodexHomeAlias(home) {
265
+ if (!home) return null;
266
+ const alias = path.join(home, '.codex');
267
+ try {
268
+ const stat = fs.lstatSync(alias);
269
+ return stat.isDirectory() || stat.isSymbolicLink() ? alias : null;
270
+ } catch {}
271
+ try {
272
+ fs.symlinkSync('.', alias, 'dir');
273
+ return alias;
274
+ } catch {
275
+ try {
276
+ fs.mkdirSync(alias, { recursive: true });
277
+ return alias;
278
+ } catch {
279
+ return null;
280
+ }
281
+ }
282
+ }
283
+
284
+ function codexConfigDirs(home) {
285
+ fs.mkdirSync(home, { recursive: true });
286
+ const dirs = [home];
287
+ const alias = ensureCodexHomeAlias(home);
288
+ if (alias) dirs.push(alias);
289
+ const seen = new Set();
290
+ return dirs.filter((dir) => {
291
+ let key;
292
+ try {
293
+ key = fs.realpathSync(dir);
294
+ } catch {
295
+ key = path.resolve(dir);
296
+ }
297
+ if (seen.has(key)) return false;
298
+ seen.add(key);
299
+ return true;
300
+ });
301
+ }
302
+
303
+ function unlinkSymlinkIfPresent(target) {
304
+ try {
305
+ if (fs.lstatSync(target).isSymbolicLink()) fs.unlinkSync(target);
306
+ } catch {}
307
+ }
308
+
309
+ function writeCodexHomeFile(home, filename, contents, mode = 0o600, options = {}) {
310
+ for (const dir of codexConfigDirs(home)) {
311
+ const target = path.join(dir, filename);
312
+ if (options.unlinkSymlink) unlinkSymlinkIfPresent(target);
313
+ fs.writeFileSync(target, contents, { mode });
314
+ try { fs.chmodSync(target, mode); } catch {}
315
+ }
316
+ }
317
+
318
+ function tomlSectionName(line) {
319
+ const match = String(line || '').match(/^\s*\[([^\]]+)\]\s*$/);
320
+ return match ? match[1].trim() : null;
321
+ }
322
+
323
+ function removeTopLevelKeys(toml, keys) {
324
+ const remove = new Set(keys);
325
+ let inTopLevel = true;
326
+ return String(toml || '')
327
+ .split(/\r?\n/)
328
+ .filter((line) => {
329
+ if (tomlSectionName(line)) inTopLevel = false;
330
+ if (!inTopLevel) return true;
331
+ const match = line.match(/^\s*([A-Za-z0-9_-]+)\s*=/);
332
+ return !match || !remove.has(match[1]);
333
+ })
334
+ .join('\n');
335
+ }
336
+
337
+ function removeTomlSections(toml, sectionNames) {
338
+ const remove = new Set(sectionNames);
339
+ let skipping = false;
340
+ const out = [];
341
+ for (const line of String(toml || '').split(/\r?\n/)) {
342
+ const section = tomlSectionName(line);
343
+ if (section) {
344
+ skipping = [...remove].some((name) => section === name || section.startsWith(`${name}.`));
345
+ }
346
+ if (!skipping) out.push(line);
347
+ }
348
+ return out.join('\n');
349
+ }
350
+
351
+ function ensureFeatureBoolean(toml, key, value) {
352
+ const lines = String(toml || '').split(/\r?\n/);
353
+ const sectionIndex = lines.findIndex((line) => tomlSectionName(line) === 'features');
354
+ const rendered = `${key} = ${value ? 'true' : 'false'}`;
355
+ if (sectionIndex === -1) {
356
+ return `${String(toml || '').trimEnd()}\n\n[features]\n${rendered}\n`;
357
+ }
358
+ let insertAt = lines.length;
359
+ for (let i = sectionIndex + 1; i < lines.length; i += 1) {
360
+ if (tomlSectionName(lines[i])) {
361
+ insertAt = i;
362
+ break;
363
+ }
364
+ if (new RegExp(`^\\s*${key}\\s*=`).test(lines[i])) {
365
+ lines[i] = rendered;
366
+ return lines.join('\n');
367
+ }
368
+ }
369
+ lines.splice(insertAt, 0, rendered);
370
+ return lines.join('\n');
371
+ }
372
+
373
+ function tomlQuoted(value) {
374
+ return JSON.stringify(String(value || ''));
375
+ }
376
+
377
+ function hookTrustBlocks(toml) {
378
+ const lines = String(toml || '').split(/\r?\n/);
379
+ const blocks = [];
380
+ for (let i = 0; i < lines.length; i += 1) {
381
+ const section = tomlSectionName(lines[i]);
382
+ const match = section?.match(/^hooks\.state\."([^"]+)"$/);
383
+ if (!match) continue;
384
+ let end = lines.length;
385
+ for (let j = i + 1; j < lines.length; j += 1) {
386
+ if (tomlSectionName(lines[j])) {
387
+ end = j;
388
+ break;
389
+ }
390
+ }
391
+ blocks.push({ key: match[1], lines: lines.slice(i, end) });
392
+ }
393
+ return blocks;
394
+ }
395
+
396
+ function hookTrustToml(toml) {
397
+ return hookTrustBlocks(toml).map((block) => block.lines.join('\n')).join('\n\n');
398
+ }
399
+
400
+ function mirrorCodexHookTrust(toml, sourceDir, runtimeHome) {
401
+ if (!sourceDir || !runtimeHome) return toml;
402
+ const sourceHookPrefix = `${path.join(sourceDir, 'hooks.json')}:`;
403
+ const runtimePrefixes = [
404
+ `${path.join(runtimeHome, 'hooks.json')}:`,
405
+ `${path.join(runtimeHome, '.codex', 'hooks.json')}:`,
406
+ ];
407
+ const blocks = hookTrustBlocks(toml);
408
+ if (blocks.length === 0) return toml;
409
+ const existing = new Set(blocks.map((block) => block.key));
410
+ const additions = [];
411
+ for (const block of blocks) {
412
+ if (!block.key.startsWith(sourceHookPrefix)) continue;
413
+ const suffix = block.key.slice(sourceHookPrefix.length);
414
+ for (const prefix of runtimePrefixes) {
415
+ const nextKey = `${prefix}${suffix}`;
416
+ if (existing.has(nextKey)) continue;
417
+ existing.add(nextKey);
418
+ const lines = [...block.lines];
419
+ lines[0] = `[hooks.state.${tomlQuoted(nextKey)}]`;
420
+ additions.push(lines.join('\n'));
421
+ }
422
+ }
423
+ if (additions.length === 0) return toml;
424
+ const parent = /\n\s*\[hooks\.state\]\s*(?:\n|$)/.test(`\n${toml}`) ? '' : '\n[hooks.state]\n';
425
+ return `${String(toml || '').trimEnd()}${parent}\n${additions.join('\n\n')}\n`;
426
+ }
427
+
428
+ function generatedCodexHookTrust(contract, renderers = defaultRenderers) {
429
+ const home = contract.auth.runtimeHome;
430
+ if (!home || !renderers.hasHooks(contract.agentConfig)) return '';
431
+ const hookMap = renderers.toHookMap(contract.agentConfig);
432
+ const hookFiles = [
433
+ path.join(home, 'hooks.json'),
434
+ path.join(home, '.codex', 'hooks.json'),
435
+ ];
436
+ const lines = ['[hooks.state]'];
437
+ for (const hooksPath of hookFiles) {
438
+ for (const [event, entries] of Object.entries(hookMap)) {
439
+ for (let index = 0; index < entries.length; index += 1) {
440
+ lines.push('');
441
+ lines.push(`[hooks.state.${tomlQuoted(`${hooksPath}:${event}:${index}`)}]`);
442
+ lines.push('trusted = true');
443
+ }
444
+ }
445
+ }
446
+ return lines.join('\n');
447
+ }
448
+
449
+ function generatedMcpSectionNames(contract) {
450
+ return relayedMcpServers(contract).map((server) => `mcp_servers.${server.name}`);
451
+ }
452
+
453
+ function buildCodexConfig(contract, existingConfig, syncInfo, renderers = defaultRenderers) {
454
+ const mcpToml = toCodexMcpToml(contract);
455
+ const topLevelKeys = [
456
+ 'model_provider',
457
+ ...(contract.authMethod === 'amalgm' ? ['model_context_window', 'model_auto_compact_token_limit'] : []),
458
+ ];
459
+ let config = removeTopLevelKeys(existingConfig, topLevelKeys);
460
+ config = removeTomlSections(config, [
461
+ ...(contract.authMethod === 'amalgm' ? ['model_providers.amalgm'] : []),
462
+ ...generatedMcpSectionNames(contract),
463
+ ]);
464
+ if (renderers.hasHooks(contract.agentConfig)) {
465
+ config = ensureFeatureBoolean(config, 'codex_hooks', true);
466
+ }
467
+ const generated = contract.authMethod === 'amalgm'
468
+ ? [
469
+ 'model_provider = "amalgm"',
470
+ ...modelWindowConfigLines(contract),
471
+ '',
472
+ '[model_providers.amalgm]',
473
+ 'name = "amalgm"',
474
+ `base_url = "${contract.auth.baseUrl}"`,
475
+ 'env_key = "OPENAI_API_KEY"',
476
+ 'wire_api = "responses"',
477
+ 'requires_openai_auth = false',
478
+ ].join('\n')
479
+ : contract.authMethod === 'byok'
480
+ ? 'model_provider = "openai"'
481
+ : contract.authMethod === 'provider_auth'
482
+ ? 'model_provider = "openai"'
483
+ : '';
484
+ return [
485
+ generated.trimEnd(),
486
+ String(config || '').trim(),
487
+ generatedCodexHookTrust(contract, renderers).trim(),
488
+ String(mcpToml || '').trim(),
489
+ ].filter(Boolean).join('\n\n') + '\n';
490
+ }
491
+
492
+ function baseNativeConfigForContract(contract, syncInfo) {
493
+ return readTextFile(syncInfo?.sourceConfigPath);
494
+ }
495
+
496
+ function writeConfig(contract, syncInfo, renderers = defaultRenderers) {
497
+ const home = contract.auth.runtimeHome;
498
+ if (!home) return;
499
+ fs.mkdirSync(home, { recursive: true });
500
+ ensureCodexHomeAlias(home);
501
+ if (syncInfo === undefined) {
502
+ syncInfo = contract.authMethod === 'provider_auth' ? syncCodexProviderAuth(home) : null;
503
+ }
504
+ const nativeConfig = baseNativeConfigForContract(contract, syncInfo);
505
+ if (renderers.hasHooks(contract.agentConfig)) {
506
+ writeCodexHomeFile(home, 'hooks.json', renderers.toCodexHooksJson(contract.agentConfig), 0o600);
507
+ }
508
+ if (contract.authMethod === 'provider_auth') {
509
+ writeCodexHomeFile(home, 'config.toml', buildCodexConfig(contract, nativeConfig, syncInfo, renderers), 0o600);
510
+ return;
511
+ }
512
+ writeCodexHomeFile(home, 'config.toml', buildCodexConfig(contract, nativeConfig, syncInfo, renderers), 0o600);
513
+ writeCodexHomeFile(home, 'auth.json', JSON.stringify({
514
+ auth_mode: 'apikey',
515
+ OPENAI_API_KEY: contract.auth.tokenRef,
516
+ }, null, 2), 0o600, { unlinkSymlink: true });
517
+ }
518
+
519
+ class CodexAdapter {
520
+ constructor(options = {}) {
521
+ this.renderers = { ...defaultRenderers, ...(options.renderers || {}) };
522
+ this.permissionPort = options.requestPermission || null;
523
+ this.inputCapabilities = nativeInputCapabilities({
524
+ authModes: ['platform', 'subscription', 'byok'],
525
+ modelSettings: [...COMMON_REASONING_SETTINGS, ...COMMON_SPEED_SETTINGS],
526
+ tools: true,
527
+ });
528
+ }
529
+
530
+ async create(contract) {
531
+ const runtime = prepareHarnessRuntime(contract);
532
+ writeConfig(contract, runtime.syncInfo, this.renderers);
533
+ const binary = resolveBinary();
534
+ const client = new JsonLineRpc({ binary, cwd: contract.cwd, env: codexEnv(runtime.env, binary) });
535
+ await client.request('initialize', { clientInfo: { name: 'amalgm-chat', version: '1.0.0' }, capabilities: { experimentalApi: true } });
536
+ client.notify('initialized');
537
+ const fullAccess = contract.permissionMode === 'full_access';
538
+ const threadOptions = {
539
+ cwd: contract.cwd,
540
+ approvalPolicy: fullAccess ? 'never' : 'on-request',
541
+ sandbox: fullAccess ? 'danger-full-access' : 'workspace-write',
542
+ modelProvider: contract.authMethod === 'amalgm' ? 'amalgm' : 'openai',
543
+ serviceTier: serviceTierForContract(contract),
544
+ developerInstructions: composeSystemPrompt(contract) || null,
545
+ persistExtendedHistory: true,
546
+ };
547
+ const session = {
548
+ sessionId: contract.sessionId,
549
+ providerSessionId: contract.providerSessionId || null,
550
+ client,
551
+ permissionMode: contract.permissionMode,
552
+ activeTurnId: null,
553
+ activeTurnHadAssistant: false,
554
+ cancelCurrentTurn: null,
555
+ cancelRequested: false,
556
+ };
557
+ client.onRequest((method, params) => this.handleServerRequest(session, method, params));
558
+ const thread = contract.providerSessionId
559
+ ? await client.request('thread/resume', {
560
+ threadId: contract.providerSessionId,
561
+ ...threadOptions,
562
+ excludeTurns: true,
563
+ })
564
+ : await client.request('thread/start', threadOptions);
565
+ session.providerSessionId = thread.thread.id;
566
+ return session;
567
+ }
568
+
569
+ async handleServerRequest(session, method, params) {
570
+ const permissionMethods = new Set([
571
+ 'item/commandExecution/requestApproval',
572
+ 'item/fileChange/requestApproval',
573
+ 'item/permissions/requestApproval',
574
+ 'execCommandApproval',
575
+ 'applyPatchApproval',
576
+ ]);
577
+ if (!permissionMethods.has(method)) throw new Error(`Method not supported: ${method}`);
578
+ const decision = session.permissionMode === 'full_access'
579
+ ? { outcome: 'allow_always' }
580
+ : await requestPermission(this.permissionPort, {
581
+ adapterId: 'codex',
582
+ providerSessionId: session.providerSessionId,
583
+ kind: method.includes('fileChange') || method === 'applyPatchApproval' ? 'file_change' : 'command',
584
+ title: params.reason || params.command || 'Codex requests permission',
585
+ input: params,
586
+ });
587
+ const allow = decision.outcome === 'allow_once' || decision.outcome === 'allow_always';
588
+ const always = decision.outcome === 'allow_always';
589
+ if (method === 'item/commandExecution/requestApproval') {
590
+ return { decision: allow ? (always ? 'acceptForSession' : 'accept') : decision.outcome === 'cancel' ? 'cancel' : 'decline' };
591
+ }
592
+ if (method === 'item/fileChange/requestApproval') {
593
+ return { decision: allow ? (always ? 'acceptForSession' : 'accept') : decision.outcome === 'cancel' ? 'cancel' : 'decline' };
594
+ }
595
+ if (method === 'item/permissions/requestApproval') {
596
+ return {
597
+ permissions: allow
598
+ ? {
599
+ ...(params.permissions?.network ? { network: params.permissions.network } : {}),
600
+ ...(params.permissions?.fileSystem ? { fileSystem: params.permissions.fileSystem } : {}),
601
+ }
602
+ : {},
603
+ scope: always ? 'session' : 'turn',
604
+ };
605
+ }
606
+ return {
607
+ decision: allow
608
+ ? (always ? 'approved_for_session' : 'approved')
609
+ : decision.outcome === 'cancel'
610
+ ? 'abort'
611
+ : { denied: { rejection: 'Denied by the user' } },
612
+ };
613
+ }
614
+
615
+ async resume(session) { return session; }
616
+
617
+ async stop(session) {
618
+ session.cancelRequested = true;
619
+ try { session.cancelCurrentTurn?.(); } catch {}
620
+ if (!session.activeTurnId) return;
621
+ await session.client.request('turn/interrupt', { threadId: session.providerSessionId, turnId: session.activeTurnId }).catch(() => {});
622
+ }
623
+
624
+ async destroy(session) { session.client.close(); }
625
+
626
+ async *prompt(session, input, contract) {
627
+ const queue = [];
628
+ let complete = false;
629
+ let compactCompletionTimer = null;
630
+ let wake = null;
631
+ const wakeLoop = () => { if (wake) { wake(); wake = null; } };
632
+ const push = (e) => { queue.push(e); wakeLoop(); };
633
+ session.cancelRequested = false;
634
+ session.cancelCurrentTurn = () => {
635
+ if (complete) return;
636
+ complete = true;
637
+ push(done({ providerSessionId: session.providerSessionId, stopReason: 'cancelled' }));
638
+ };
639
+ const off = session.client.on((msg) => {
640
+ const p = msg.params || {};
641
+ if (p.threadId && p.threadId !== session.providerSessionId) return;
642
+ recordNativeEvent('codex.app-server.notification', msg, {
643
+ providerSessionId: session.providerSessionId,
644
+ sessionId: contract.sessionId,
645
+ assistantMessageId: contract.assistantMessageId,
646
+ method: msg.method,
647
+ });
648
+ if (msg.method === 'turn/started' && p.turn?.id) {
649
+ session.activeTurnId = p.turn.id;
650
+ session.activeTurnHadAssistant = false;
651
+ }
652
+ if (p.turnId && !session.activeTurnId && msg.method !== 'turn/started') return;
653
+ if (p.turnId && session.activeTurnId && p.turnId !== session.activeTurnId) return;
654
+ if (msg.method === 'item/started' && ['agentMessage', 'reasoning', 'commandExecution', 'contextCompaction'].includes(p.item?.type)) {
655
+ session.activeTurnHadAssistant = true;
656
+ }
657
+ if (msg.method === 'thread/tokenUsage/updated' && !session.activeTurnHadAssistant) return;
658
+ for (const e of normalizeCodexNotification(msg, session.providerSessionId, session)) {
659
+ if (session.cancelRequested && e.type === 'done') e.stopReason = 'cancelled';
660
+ if (e.type === 'done' || e.type === 'error') complete = true;
661
+ push(e);
662
+ }
663
+ if (compactCommand && msg.method === 'item/completed' && p.item?.type === 'contextCompaction') {
664
+ if (compactCompletionTimer) clearTimeout(compactCompletionTimer);
665
+ compactCompletionTimer = setTimeout(() => {
666
+ push(done({ providerSessionId: session.providerSessionId, stopReason: 'end_turn' }));
667
+ complete = true;
668
+ wakeLoop();
669
+ }, 500);
670
+ }
671
+ });
672
+ session.activeTurnId = null;
673
+ session.activeTurnHadAssistant = false;
674
+ const nativeInput = codexPromptInput(input);
675
+ const compactCommand = nativeInput.length === 1
676
+ && nativeInput[0].type === 'text'
677
+ && isCompactCommand(nativeInput[0].text);
678
+ const startRequest = compactCommand
679
+ ? session.client.request('thread/compact/start', { threadId: session.providerSessionId })
680
+ : session.client.request('turn/start', {
681
+ threadId: session.providerSessionId,
682
+ input: nativeInput,
683
+ cwd: contract.cwd,
684
+ model: contract.cliModel.replace(/\/(?:low|medium|high|xhigh|max|ultra)$/i, ''),
685
+ effort: contract.reasoningEffort || undefined,
686
+ serviceTier: serviceTierForContract(contract),
687
+ approvalPolicy: contract.permissionMode === 'full_access' ? 'never' : 'on-request',
688
+ });
689
+ startRequest.then((result) => {
690
+ if (compactCommand) return;
691
+ session.activeTurnId = result?.turn?.id || session.activeTurnId;
692
+ const resultError = result?.error || result?.turn?.error || result?.turn?.lastError;
693
+ if (!resultError) return;
694
+ push(errorEvent(codexErrorMessage(resultError), { providerSessionId: session.providerSessionId }));
695
+ push(done({ providerSessionId: session.providerSessionId, stopReason: 'error' }));
696
+ complete = true;
697
+ }).catch((err) => {
698
+ if (session.cancelRequested) {
699
+ push(done({ providerSessionId: session.providerSessionId, stopReason: 'cancelled' }));
700
+ complete = true;
701
+ return;
702
+ }
703
+ push(errorEvent(err.message, { providerSessionId: session.providerSessionId }));
704
+ push(done({ providerSessionId: session.providerSessionId, stopReason: 'error' }));
705
+ complete = true;
706
+ });
707
+ try {
708
+ while (!complete || queue.length) {
709
+ if (!queue.length) await new Promise((resolve) => { wake = resolve; });
710
+ else yield queue.shift();
711
+ }
712
+ } finally {
713
+ if (compactCompletionTimer) clearTimeout(compactCompletionTimer);
714
+ off();
715
+ session.cancelCurrentTurn = null;
716
+ session.cancelRequested = false;
717
+ }
718
+ }
719
+ }
720
+
721
+ const __private = {
722
+ buildCodexConfig,
723
+ ensureFeatureBoolean,
724
+ hookTrustToml,
725
+ mirrorCodexHookTrust,
726
+ modelWindowConfigLines,
727
+ removeTomlSections,
728
+ removeTopLevelKeys,
729
+ serviceTierForContract,
730
+ writeConfig,
731
+ };
732
+
733
+ export { CodexAdapter, __private };