@axhub/genie 0.2.10 → 0.2.12
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/api-docs.html +2 -2
- package/dist/assets/App-Clb2COtW.js +274 -0
- package/dist/assets/ImagePlaygroundPage-DqhMSbM8.js +106 -0
- package/dist/assets/ImagePlaygroundPage-MEn3NN80.css +1 -0
- package/dist/assets/ReviewApp-CDcLYe-u.js +1 -0
- package/dist/assets/{_basePickBy-DVVb07UV.js → _basePickBy-jUZsM51q.js} +1 -1
- package/dist/assets/{_baseUniq-BtbziL5G.js → _baseUniq-BXglE6_v.js} +1 -1
- package/dist/assets/{arc-BsCC8yBD.js → arc-D-oFCFBv.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-woFp6eNI.js → architectureDiagram-2XIMDMQ5-DC8bAnQt.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-ya8VAc2k.js → blockDiagram-WCTKOSBZ-C4semIRc.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-CY1dZmIZ.js → c4Diagram-IC4MRINW-FHj1QO3y.js} +1 -1
- package/dist/assets/channel-BF4woPXX.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-CR1lAd74.js → chunk-4BX2VUAB-D-LjsQ_s.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-CP98WcFC.js → chunk-55IACEB6-DI3j_d7A.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-D9c7ijAB.js → chunk-FMBD7UC4-BEVnaLFN.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-DQAGYOn-.js → chunk-JSJVCQXG-CSxpcErk.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-BbTXiDq7.js → chunk-KX2RTZJC-BbuhDN4h.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-BI6AX0dr.js → chunk-NQ4KR5QH-C3x61XQa.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-DB3V2Ifo.js → chunk-QZHKN3VN-DxWOFtPh.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-DhzTthv6.js → chunk-WL4C6EOR-Bt2OauD2.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-D2kHlnQ7.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-D2kHlnQ7.js +1 -0
- package/dist/assets/clone-CqBvwCJW.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-BQ09ZE2j.js → cose-bilkent-S5V4N54A-Dexadrue.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-Dc2ueD_R.js → dagre-KLK3FWXG-F9U4X2xC.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-DP-LsQoL.js → diagram-E7M64L7V-B3V17aH3.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-Cg6r42cB.js → diagram-IFDJBPK2-CdHAmLL1.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-aHsfoUZE.js → diagram-P4PSJMXO-CrTNfk8K.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-qBXJ4aAz.js → erDiagram-INFDFZHY-vDh9SWK9.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-D_13emJM.js → flowDiagram-PKNHOUZH-DpltMg7L.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-BvIcOLwz.js → ganttDiagram-A5KZAMGK-COTk2xur.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-ad0vvNcU.js → gitGraphDiagram-K3NZZRJ6-BNV7bvvj.js} +1 -1
- package/dist/assets/{graph-CeJCMjan.js → graph-Dkeg9oys.js} +1 -1
- package/dist/assets/{highlighted-body-TPN3WLV5-B_novwSz.js → highlighted-body-TPN3WLV5-DaiQEBwR.js} +1 -1
- package/dist/assets/index-DgGmiqsP.css +1 -0
- package/dist/assets/index-DvA901Vs.js +2 -0
- package/dist/assets/{infoDiagram-LFFYTUFH-lOxAqb3m.js → infoDiagram-LFFYTUFH-CZioW3Gt.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-DIr-51gj.js → ishikawaDiagram-PHBUUO56-BbqR3i1B.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-CYcIW0ZU.js → journeyDiagram-4ABVD52K-wfb-WHzl.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-C1ZK616a.js → kanban-definition-K7BYSVSG-B3c4y3VN.js} +1 -1
- package/dist/assets/{layout-CI2RM-v6.js → layout-Xr9Z2VGF.js} +1 -1
- package/dist/assets/{linear-DE7bISck.js → linear-JBmzAJtl.js} +1 -1
- package/dist/assets/{mermaid-O7DHMXV3-XxAJo8EK.js → mermaid-O7DHMXV3-fDuyNLKe.js} +238 -220
- package/dist/assets/{mindmap-definition-YRQLILUH-Dz6EFjmn.js → mindmap-definition-YRQLILUH-B5NTN_jD.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-DPpEzUed.js → pieDiagram-SKSYHLDU-CuO98GVu.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-xdoXNet7.js → quadrantDiagram-337W2JSQ-LL3f4vLf.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-DUq8H3CL.js → requirementDiagram-Z7DCOOCP-Di-2O6LH.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-CmqEUxRu.js → sankeyDiagram-WA2Y5GQK-9lHqrXqR.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-DhtXRNiH.js → sequenceDiagram-2WXFIKYE-BQu-SoGr.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-Dj0HOlbN.js → stateDiagram-RAJIS63D-BUxvd2BC.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-CDVexTiR.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-DUuJzZB5.js → timeline-definition-YZTLITO2-oP47UEU6.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-DpYBQ0qr.js → treemap-KZPCXAKY-BRjDo2aE.js} +1 -1
- package/dist/assets/{vendor-codemirror-CMHSJ_9p.js → vendor-codemirror-BiCeS-y4.js} +1 -1
- package/dist/assets/{vendor-react-xmA_f8ig.js → vendor-react-DVlYPmi3.js} +1 -1
- package/dist/assets/{vennDiagram-LZ73GAT5-DpePUyOd.js → vennDiagram-LZ73GAT5-DrRqcDqo.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-Cfp1I4_U.js → xychartDiagram-JWTSCODW-DUXrymAi.js} +1 -1
- package/dist/index.html +4 -4
- package/package.json +25 -6
- package/scripts/refresh-acp-default-capabilities.mjs +160 -0
- package/server/acp-runtime/client.js +1137 -181
- package/server/acp-runtime/command-overrides.js +48 -0
- package/server/acp-runtime/index.js +576 -16
- package/server/acp-runtime/registry.js +6 -4
- package/server/acp-runtime/session-store.js +235 -92
- package/server/database/db.js +12 -3
- package/server/external-agent/ws.js +212 -11
- package/server/index.js +156 -53
- package/server/projects-watcher-config.js +4 -0
- package/server/projects.js +485 -125
- package/server/routes/cc-connect.js +5 -4
- package/server/routes/codex.js +24 -0
- package/server/routes/commands.js +166 -10
- package/server/routes/runs.js +641 -0
- package/server/routes/session-core.js +357 -109
- package/server/session-core/eventStore.js +0 -121
- package/server/session-core/providerAdapters.js +644 -163
- package/server/session-core/providerDiscovery.js +66 -38
- package/server/session-core/runRegistry.js +244 -0
- package/server/session-core/runtimeState.js +75 -3
- package/server/session-core/runtimeWriter.js +132 -10
- package/server/utils/codexImagePlayground.js +479 -0
- package/server/utils/localTerminal.js +56 -0
- package/server/utils/shellCommand.js +70 -0
- package/shared/acpCapabilities.js +393 -0
- package/shared/acpDefaultCapabilities.generated.json +141 -0
- package/shared/conversationEvents.js +425 -121
- package/dist/assets/App-CYCCsgwf.js +0 -264
- package/dist/assets/ReviewApp-0srHIXwb.js +0 -1
- package/dist/assets/channel-BMhScXFe.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-CMIxlWcT.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-CMIxlWcT.js +0 -1
- package/dist/assets/clone-BPqOt4r3.js +0 -1
- package/dist/assets/index-C514cLyb.js +0 -2
- package/dist/assets/index-h1DBl_g3.css +0 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-C9utf5gv.js +0 -1
|
@@ -2,16 +2,41 @@ import {
|
|
|
2
2
|
isConversationEvent,
|
|
3
3
|
normalizeRealtimePayloadToConversationEvents
|
|
4
4
|
} from '../../shared/conversationEvents.js';
|
|
5
|
-
import { appendMirroredConversationEvents } from './eventStore.js';
|
|
6
5
|
import { publishSessionRuntimeStateChanges } from './runtimeState.js';
|
|
7
6
|
|
|
7
|
+
function shouldSendNormalizedEventsBeforeRawPayload(data) {
|
|
8
|
+
if (!data || data.type === 'conversation-event') {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (data.type === 'claude-response') {
|
|
13
|
+
const dataType = data.data?.type;
|
|
14
|
+
return dataType === 'content_block_delta' || dataType === 'content_block_stop';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (data.type === 'codex-response') {
|
|
18
|
+
const dataType = data.data?.type;
|
|
19
|
+
return dataType === 'item_delta' || dataType === 'item_done';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isRuntimeReplayConversationEvent(event) {
|
|
26
|
+
const sourceType = String(event?.rawRef?.sourceType || '').trim().toLowerCase();
|
|
27
|
+
return Boolean(event?.extensions?.runtimeReplay) || sourceType === 'acp-session-loaded-replay';
|
|
28
|
+
}
|
|
29
|
+
|
|
8
30
|
export class SessionEventMirrorWriter {
|
|
9
|
-
constructor(writer, provider = 'claude') {
|
|
31
|
+
constructor(writer, provider = 'claude', options = {}) {
|
|
10
32
|
this.writer = writer;
|
|
11
33
|
this.provider = provider;
|
|
12
34
|
this.sessionId = null;
|
|
13
35
|
this.isWebSocketWriter = writer?.isWebSocketWriter;
|
|
14
36
|
this.isSSEStreamWriter = writer?.isSSEStreamWriter;
|
|
37
|
+
this.runRegistry = options.runRegistry || null;
|
|
38
|
+
this.runId = options.runId || null;
|
|
39
|
+
this.clientRequestId = options.clientRequestId || null;
|
|
15
40
|
}
|
|
16
41
|
|
|
17
42
|
send(data) {
|
|
@@ -19,8 +44,6 @@ export class SessionEventMirrorWriter {
|
|
|
19
44
|
this.sessionId = data.sessionId;
|
|
20
45
|
}
|
|
21
46
|
|
|
22
|
-
this.writer.send(data);
|
|
23
|
-
|
|
24
47
|
const normalizedEvents = data?.type === 'conversation-event' && isConversationEvent(data?.event)
|
|
25
48
|
? [data.event]
|
|
26
49
|
: normalizeRealtimePayloadToConversationEvents({
|
|
@@ -29,21 +52,28 @@ export class SessionEventMirrorWriter {
|
|
|
29
52
|
sessionId: data?.sessionId || this.sessionId
|
|
30
53
|
}, this.provider);
|
|
31
54
|
|
|
32
|
-
normalizedEvents.forEach((event) => {
|
|
55
|
+
const sendNormalizedEvents = () => normalizedEvents.forEach((event) => {
|
|
33
56
|
if (!(data?.type === 'conversation-event' && data?.event?.eventId === event.eventId)) {
|
|
34
|
-
this.writer.send({
|
|
57
|
+
this.writer.send(this.annotateConversationEventPayload({
|
|
35
58
|
type: 'conversation-event',
|
|
36
59
|
provider: event.provider,
|
|
37
60
|
sessionId: event.sessionId,
|
|
38
61
|
event
|
|
39
|
-
});
|
|
62
|
+
}, event));
|
|
40
63
|
}
|
|
41
64
|
});
|
|
42
65
|
|
|
66
|
+
if (data?.type === 'conversation-event' && isConversationEvent(data?.event)) {
|
|
67
|
+
this.writer.send(this.annotateConversationEventPayload(data, data.event));
|
|
68
|
+
} else if (shouldSendNormalizedEventsBeforeRawPayload(data)) {
|
|
69
|
+
sendNormalizedEvents();
|
|
70
|
+
this.writer.send(data);
|
|
71
|
+
} else {
|
|
72
|
+
this.writer.send(data);
|
|
73
|
+
sendNormalizedEvents();
|
|
74
|
+
}
|
|
75
|
+
|
|
43
76
|
if (normalizedEvents.length > 0) {
|
|
44
|
-
void appendMirroredConversationEvents(normalizedEvents).catch((error) => {
|
|
45
|
-
console.warn('[WARN] Failed to persist mirrored conversation events:', error.message);
|
|
46
|
-
});
|
|
47
77
|
void publishSessionRuntimeStateChanges(normalizedEvents).catch((error) => {
|
|
48
78
|
console.warn('[WARN] Failed to publish runtime state changes:', error.message);
|
|
49
79
|
});
|
|
@@ -63,4 +93,96 @@ export class SessionEventMirrorWriter {
|
|
|
63
93
|
}
|
|
64
94
|
return this.sessionId;
|
|
65
95
|
}
|
|
96
|
+
|
|
97
|
+
setRunContext({ runId, clientRequestId, runRegistry } = {}) {
|
|
98
|
+
if (runId !== undefined) {
|
|
99
|
+
this.runId = runId;
|
|
100
|
+
}
|
|
101
|
+
if (clientRequestId !== undefined) {
|
|
102
|
+
this.clientRequestId = clientRequestId;
|
|
103
|
+
}
|
|
104
|
+
if (runRegistry !== undefined) {
|
|
105
|
+
this.runRegistry = runRegistry;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
annotateConversationEventPayload(payload, event) {
|
|
110
|
+
if (!this.runRegistry || !this.runId || !isConversationEvent(event)) {
|
|
111
|
+
return payload;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const eventSessionId = event.sessionId || payload?.sessionId || this.sessionId || null;
|
|
115
|
+
if (eventSessionId) {
|
|
116
|
+
this.sessionId = eventSessionId;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (isRuntimeReplayConversationEvent(event)) {
|
|
120
|
+
return payload;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const eventClientRequestId = event.extensions?.clientRequestId || payload?.clientRequestId || this.clientRequestId || null;
|
|
124
|
+
const runEvent = this.runRegistry.appendRunEvent(this.runId, {
|
|
125
|
+
type: 'conversation-event',
|
|
126
|
+
provider: event.provider || payload?.provider || this.provider,
|
|
127
|
+
sessionId: eventSessionId,
|
|
128
|
+
clientRequestId: eventClientRequestId,
|
|
129
|
+
event,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
this.updateRunStatusFromConversationEvent(event, {
|
|
133
|
+
sessionId: eventSessionId,
|
|
134
|
+
clientRequestId: eventClientRequestId,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (!runEvent) {
|
|
138
|
+
return payload;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
...payload,
|
|
143
|
+
provider: payload?.provider || event.provider || this.provider,
|
|
144
|
+
sessionId: payload?.sessionId || eventSessionId,
|
|
145
|
+
clientRequestId: payload?.clientRequestId || eventClientRequestId,
|
|
146
|
+
runId: this.runId,
|
|
147
|
+
runEventSeq: runEvent.seq,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
updateRunStatusFromConversationEvent(event, { sessionId = null, clientRequestId = null } = {}) {
|
|
152
|
+
if (!this.runRegistry || !this.runId || !isConversationEvent(event)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const state = event.kind === 'session_state_changed'
|
|
157
|
+
? String(event.payload?.state || '').trim().toLowerCase()
|
|
158
|
+
: '';
|
|
159
|
+
let status = null;
|
|
160
|
+
|
|
161
|
+
if (event.kind === 'approval_request' || state === 'awaiting_approval') {
|
|
162
|
+
status = 'awaiting_approval';
|
|
163
|
+
} else if (event.kind === 'elicitation_request' || state === 'awaiting_input') {
|
|
164
|
+
status = 'awaiting_input';
|
|
165
|
+
} else if (event.kind === 'error' || state === 'errored') {
|
|
166
|
+
status = 'failed';
|
|
167
|
+
} else if (state === 'completed') {
|
|
168
|
+
status = 'succeeded';
|
|
169
|
+
} else if (state === 'aborted') {
|
|
170
|
+
status = 'canceled';
|
|
171
|
+
} else if (state === 'streaming' || event.kind === 'approval_resolved' || event.kind === 'elicitation_resolved') {
|
|
172
|
+
status = 'running';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (status) {
|
|
176
|
+
this.runRegistry.updateRun(this.runId, {
|
|
177
|
+
status,
|
|
178
|
+
sessionId,
|
|
179
|
+
clientRequestId,
|
|
180
|
+
});
|
|
181
|
+
} else if (sessionId || clientRequestId) {
|
|
182
|
+
this.runRegistry.updateRun(this.runId, {
|
|
183
|
+
sessionId,
|
|
184
|
+
clientRequestId,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
66
188
|
}
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import TOML from '@iarna/toml';
|
|
5
|
+
|
|
6
|
+
export const CODEX_IMAGE_MODEL = 'gpt-image-2';
|
|
7
|
+
const DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';
|
|
8
|
+
const PROJECT_IMAGE_CONFIG_FILENAMES = [
|
|
9
|
+
path.join('.codex', 'imagegen.config.local.json'),
|
|
10
|
+
path.join('.codex', 'imagegen.config.json')
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function dedupePaths(paths) {
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
const result = [];
|
|
16
|
+
|
|
17
|
+
for (const item of paths) {
|
|
18
|
+
if (typeof item !== 'string' || !item.trim()) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const key = item.trim();
|
|
23
|
+
if (seen.has(key)) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
seen.add(key);
|
|
28
|
+
result.push(key);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function splitPathList(value, delimiter = path.delimiter) {
|
|
35
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return value
|
|
40
|
+
.split(delimiter)
|
|
41
|
+
.map((item) => item.trim())
|
|
42
|
+
.filter(Boolean);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getUnixConfigDirs(env = {}) {
|
|
46
|
+
const dirs = [];
|
|
47
|
+
const configuredDirs = splitPathList(env.XDG_CONFIG_DIRS || '', ':');
|
|
48
|
+
|
|
49
|
+
if (configuredDirs.length) {
|
|
50
|
+
dirs.push(...configuredDirs);
|
|
51
|
+
} else {
|
|
52
|
+
dirs.push('/etc/xdg');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
dirs.push('/etc');
|
|
56
|
+
return dedupePaths(dirs);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getWindowsConfigDirs(env = {}, homeDir = os.homedir()) {
|
|
60
|
+
return dedupePaths([
|
|
61
|
+
env.APPDATA ? path.win32.join(env.APPDATA, 'Codex') : null,
|
|
62
|
+
env.LOCALAPPDATA ? path.win32.join(env.LOCALAPPDATA, 'Codex') : null,
|
|
63
|
+
env.PROGRAMDATA ? path.win32.join(env.PROGRAMDATA, 'Codex') : 'C:\\ProgramData\\Codex',
|
|
64
|
+
path.win32.join(homeDir, 'AppData', 'Roaming', 'Codex'),
|
|
65
|
+
path.win32.join(homeDir, 'AppData', 'Local', 'Codex')
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function createCodexConfigPaths({
|
|
70
|
+
platform = process.platform,
|
|
71
|
+
homeDir = os.homedir(),
|
|
72
|
+
env = process.env
|
|
73
|
+
} = {}) {
|
|
74
|
+
const useWin32 = platform === 'win32';
|
|
75
|
+
const pathApi = useWin32 ? path.win32 : path.posix;
|
|
76
|
+
const codexHome = pathApi.join(homeDir, '.codex');
|
|
77
|
+
const configuredCodexHome = typeof env.CODEX_HOME === 'string' && env.CODEX_HOME.trim()
|
|
78
|
+
? env.CODEX_HOME.trim()
|
|
79
|
+
: null;
|
|
80
|
+
const configDirs = [];
|
|
81
|
+
|
|
82
|
+
if (useWin32) {
|
|
83
|
+
configDirs.push(...getWindowsConfigDirs(env, homeDir));
|
|
84
|
+
} else {
|
|
85
|
+
const xdgConfigHome = env.XDG_CONFIG_HOME
|
|
86
|
+
? pathApi.join(env.XDG_CONFIG_HOME, 'codex')
|
|
87
|
+
: pathApi.join(homeDir, '.config', 'codex');
|
|
88
|
+
configDirs.push(xdgConfigHome);
|
|
89
|
+
configDirs.push(...getUnixConfigDirs(env).map((dir) => pathApi.join(dir, 'codex')));
|
|
90
|
+
|
|
91
|
+
if (platform === 'darwin') {
|
|
92
|
+
configDirs.push('/Library/Application Support/Codex');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const roots = dedupePaths([configuredCodexHome, codexHome, ...configDirs]);
|
|
97
|
+
return {
|
|
98
|
+
authPaths: roots.map((root) => pathApi.join(root, 'auth.json')),
|
|
99
|
+
configPaths: roots.map((root) => pathApi.join(root, 'config.toml'))
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function createProjectImageConfigPaths(projectPath) {
|
|
104
|
+
if (typeof projectPath !== 'string' || !projectPath.trim()) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const root = path.resolve(projectPath.trim());
|
|
109
|
+
return PROJECT_IMAGE_CONFIG_FILENAMES.map((filename) => path.join(root, filename));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function readExistingTextFiles(paths) {
|
|
113
|
+
const files = [];
|
|
114
|
+
|
|
115
|
+
for (const filePath of paths) {
|
|
116
|
+
try {
|
|
117
|
+
files.push({
|
|
118
|
+
path: filePath,
|
|
119
|
+
content: await fs.readFile(filePath, 'utf8')
|
|
120
|
+
});
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error.code !== 'ENOENT' && error.code !== 'ENOTDIR') {
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return files;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function mergePlainObjects(base, override) {
|
|
132
|
+
const next = { ...(base || {}) };
|
|
133
|
+
for (const [key, value] of Object.entries(override || {})) {
|
|
134
|
+
if (
|
|
135
|
+
value &&
|
|
136
|
+
typeof value === 'object' &&
|
|
137
|
+
!Array.isArray(value) &&
|
|
138
|
+
next[key] &&
|
|
139
|
+
typeof next[key] === 'object' &&
|
|
140
|
+
!Array.isArray(next[key])
|
|
141
|
+
) {
|
|
142
|
+
next[key] = mergePlainObjects(next[key], value);
|
|
143
|
+
} else {
|
|
144
|
+
next[key] = value;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return next;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function parseConfigFiles(files) {
|
|
151
|
+
const warnings = [];
|
|
152
|
+
let config = {};
|
|
153
|
+
|
|
154
|
+
for (const file of files) {
|
|
155
|
+
try {
|
|
156
|
+
config = mergePlainObjects(config, TOML.parse(file.content));
|
|
157
|
+
} catch (error) {
|
|
158
|
+
warnings.push({
|
|
159
|
+
path: file.path,
|
|
160
|
+
message: `Failed to parse Codex config: ${error.message}`
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { config, warnings };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeBaseUrl(value) {
|
|
169
|
+
const trimmed = String(value || '').trim();
|
|
170
|
+
if (!trimmed) {
|
|
171
|
+
return DEFAULT_OPENAI_BASE_URL;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return trimmed.replace(/\/+$/, '');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getActiveProvider(config) {
|
|
178
|
+
const providerId = typeof config?.model_provider === 'string'
|
|
179
|
+
? config.model_provider.trim()
|
|
180
|
+
: '';
|
|
181
|
+
const providers = config?.model_providers && typeof config.model_providers === 'object'
|
|
182
|
+
? config.model_providers
|
|
183
|
+
: {};
|
|
184
|
+
const provider = providerId ? providers[providerId] : null;
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
providerId: providerId || null,
|
|
188
|
+
provider: provider && typeof provider === 'object' ? provider : null
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function getApiMode(provider) {
|
|
193
|
+
const wireApi = typeof provider?.wire_api === 'string'
|
|
194
|
+
? provider.wire_api.trim().toLowerCase()
|
|
195
|
+
: '';
|
|
196
|
+
|
|
197
|
+
return wireApi === 'responses' ? 'images' : 'images';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getProjectApiMode(value) {
|
|
201
|
+
return value === 'responses' ? 'responses' : 'images';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function getBooleanLaunchParam(value, fallback = false) {
|
|
205
|
+
if (typeof value === 'boolean') {
|
|
206
|
+
return value ? 'true' : 'false';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (typeof value === 'string') {
|
|
210
|
+
const normalized = value.trim().toLowerCase();
|
|
211
|
+
if (['true', '1', 'yes', 'on'].includes(normalized)) {
|
|
212
|
+
return 'true';
|
|
213
|
+
}
|
|
214
|
+
if (['false', '0', 'no', 'off'].includes(normalized)) {
|
|
215
|
+
return 'false';
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return fallback ? 'true' : 'false';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function resolveEnvReference(value, env = process.env) {
|
|
223
|
+
if (typeof value !== 'string') {
|
|
224
|
+
return '';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const trimmed = value.trim();
|
|
228
|
+
if (!trimmed) {
|
|
229
|
+
return '';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (trimmed.startsWith('env:')) {
|
|
233
|
+
const envName = trimmed.slice('env:'.length).trim();
|
|
234
|
+
return envName ? String(env[envName] || '').trim() : '';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const envMatch = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(trimmed);
|
|
238
|
+
if (envMatch) {
|
|
239
|
+
return String(env[envMatch[1]] || '').trim();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return trimmed;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function getProjectApiKey(config, env = process.env) {
|
|
246
|
+
if (typeof config?.apiKeyEnv === 'string' && config.apiKeyEnv.trim()) {
|
|
247
|
+
return String(env[config.apiKeyEnv.trim()] || '').trim();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return resolveEnvReference(config?.apiKey || config?.api_key || '', env);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function normalizeProjectConfigObject(value) {
|
|
254
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const record = value;
|
|
259
|
+
if (record.profile && typeof record.profile === 'object' && !Array.isArray(record.profile)) {
|
|
260
|
+
return record.profile;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return record;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function readProjectImageConfig(projectPath, env = process.env) {
|
|
267
|
+
const paths = createProjectImageConfigPaths(projectPath);
|
|
268
|
+
const files = await readExistingTextFiles(paths);
|
|
269
|
+
const warnings = [];
|
|
270
|
+
|
|
271
|
+
for (const file of files) {
|
|
272
|
+
try {
|
|
273
|
+
const parsed = JSON.parse(file.content);
|
|
274
|
+
const config = normalizeProjectConfigObject(parsed);
|
|
275
|
+
if (!config) {
|
|
276
|
+
warnings.push({
|
|
277
|
+
path: file.path,
|
|
278
|
+
message: 'Project image config must be a JSON object.'
|
|
279
|
+
});
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const source = typeof config.source === 'string' ? config.source.trim().toLowerCase() : '';
|
|
284
|
+
if (source === 'codex') {
|
|
285
|
+
return {
|
|
286
|
+
config: null,
|
|
287
|
+
file: file.path,
|
|
288
|
+
paths,
|
|
289
|
+
warnings,
|
|
290
|
+
useCodexConfig: true
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const apiKey = getProjectApiKey(config, env);
|
|
295
|
+
const launchParams = {
|
|
296
|
+
apiUrl: normalizeBaseUrl(config.apiUrl || config.baseUrl || config.base_url || DEFAULT_OPENAI_BASE_URL),
|
|
297
|
+
apiKey,
|
|
298
|
+
apiMode: getProjectApiMode(config.apiMode || config.api_mode),
|
|
299
|
+
model: typeof config.model === 'string' && config.model.trim()
|
|
300
|
+
? config.model.trim()
|
|
301
|
+
: CODEX_IMAGE_MODEL,
|
|
302
|
+
codexCli: getBooleanLaunchParam(config.codexCli ?? config.codex_cli, false)
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
if (config.apiProxy !== undefined || config.api_proxy !== undefined) {
|
|
306
|
+
launchParams.apiProxy = getBooleanLaunchParam(config.apiProxy ?? config.api_proxy, false);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const configWarnings = [];
|
|
310
|
+
if (!apiKey) {
|
|
311
|
+
const keySource = typeof config.apiKey === 'string' ? config.apiKey.trim() : '';
|
|
312
|
+
const keyEnv = typeof config.apiKeyEnv === 'string' ? config.apiKeyEnv.trim() : '';
|
|
313
|
+
configWarnings.push({
|
|
314
|
+
path: file.path,
|
|
315
|
+
message: keySource.startsWith('env:') || keyEnv
|
|
316
|
+
? 'Project image config references an environment variable, but no API key value was found.'
|
|
317
|
+
: 'Project image config does not include an API key.'
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
config,
|
|
323
|
+
file: file.path,
|
|
324
|
+
paths,
|
|
325
|
+
launchParams,
|
|
326
|
+
warnings: [...warnings, ...configWarnings],
|
|
327
|
+
useCodexConfig: false
|
|
328
|
+
};
|
|
329
|
+
} catch (error) {
|
|
330
|
+
warnings.push({
|
|
331
|
+
path: file.path,
|
|
332
|
+
message: `Failed to parse project image config: ${error.message}`
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
config: null,
|
|
339
|
+
file: null,
|
|
340
|
+
paths,
|
|
341
|
+
warnings,
|
|
342
|
+
useCodexConfig: false
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function readApiKeyFromAuth(auth) {
|
|
347
|
+
if (!auth || typeof auth !== 'object') {
|
|
348
|
+
return '';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (typeof auth.OPENAI_API_KEY === 'string' && auth.OPENAI_API_KEY.trim()) {
|
|
352
|
+
return auth.OPENAI_API_KEY.trim();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (typeof auth.openaiApiKey === 'string' && auth.openaiApiKey.trim()) {
|
|
356
|
+
return auth.openaiApiKey.trim();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (typeof auth.api_key === 'string' && auth.api_key.trim()) {
|
|
360
|
+
return auth.api_key.trim();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return '';
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function readFirstAuth(authPaths) {
|
|
367
|
+
const files = await readExistingTextFiles(authPaths);
|
|
368
|
+
for (const file of files) {
|
|
369
|
+
try {
|
|
370
|
+
const parsed = JSON.parse(file.content);
|
|
371
|
+
const apiKey = readApiKeyFromAuth(parsed);
|
|
372
|
+
if (apiKey) {
|
|
373
|
+
return {
|
|
374
|
+
apiKey,
|
|
375
|
+
authFile: file.path
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
} catch {
|
|
379
|
+
// Ignore malformed auth candidates and continue scanning.
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
apiKey: '',
|
|
385
|
+
authFile: files[0]?.path || null
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export async function resolveCodexImagePlaygroundConfig(options = {}) {
|
|
390
|
+
const projectConfig = await readProjectImageConfig(options.projectPath, options.env || process.env);
|
|
391
|
+
if (projectConfig.launchParams && !projectConfig.useCodexConfig) {
|
|
392
|
+
const ready = Boolean(projectConfig.launchParams.apiKey);
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
ready,
|
|
396
|
+
launchParams: projectConfig.launchParams,
|
|
397
|
+
discovery: {
|
|
398
|
+
projectConfigFile: projectConfig.file,
|
|
399
|
+
projectConfigPaths: projectConfig.paths,
|
|
400
|
+
configSource: 'project'
|
|
401
|
+
},
|
|
402
|
+
warnings: projectConfig.warnings
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const paths = options.configPaths && options.authPaths
|
|
407
|
+
? {
|
|
408
|
+
configPaths: options.configPaths,
|
|
409
|
+
authPaths: options.authPaths
|
|
410
|
+
}
|
|
411
|
+
: createCodexConfigPaths(options);
|
|
412
|
+
const configFiles = await readExistingTextFiles(paths.configPaths);
|
|
413
|
+
const { config, warnings } = parseConfigFiles(configFiles);
|
|
414
|
+
const { provider } = getActiveProvider(config);
|
|
415
|
+
const { apiKey, authFile } = await readFirstAuth(paths.authPaths);
|
|
416
|
+
const apiUrl = normalizeBaseUrl(provider?.base_url || config?.base_url || DEFAULT_OPENAI_BASE_URL);
|
|
417
|
+
const ready = Boolean(apiKey);
|
|
418
|
+
|
|
419
|
+
return {
|
|
420
|
+
ready,
|
|
421
|
+
launchParams: {
|
|
422
|
+
apiUrl,
|
|
423
|
+
apiKey,
|
|
424
|
+
apiMode: getApiMode(provider),
|
|
425
|
+
model: CODEX_IMAGE_MODEL,
|
|
426
|
+
codexCli: 'true'
|
|
427
|
+
},
|
|
428
|
+
discovery: {
|
|
429
|
+
configFiles: configFiles.map((file) => file.path),
|
|
430
|
+
authFile,
|
|
431
|
+
scannedConfigPaths: paths.configPaths,
|
|
432
|
+
scannedAuthPaths: paths.authPaths,
|
|
433
|
+
projectConfigFile: projectConfig.file,
|
|
434
|
+
projectConfigPaths: projectConfig.paths,
|
|
435
|
+
configSource: 'codex'
|
|
436
|
+
},
|
|
437
|
+
warnings: ready ? [...projectConfig.warnings, ...warnings] : [
|
|
438
|
+
...projectConfig.warnings,
|
|
439
|
+
...warnings,
|
|
440
|
+
{ message: 'No OpenAI API key found in Codex auth files.' }
|
|
441
|
+
]
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function normalizeRoutePath(routePath) {
|
|
446
|
+
const value = typeof routePath === 'string' ? routePath.trim() : '';
|
|
447
|
+
if (!value.startsWith('/') || value.startsWith('//')) {
|
|
448
|
+
return '/image-playground';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return value;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function buildCodexImagePlaygroundLaunch({
|
|
455
|
+
routePath = '/image-playground',
|
|
456
|
+
config
|
|
457
|
+
} = {}) {
|
|
458
|
+
const launchParams = config?.launchParams || {};
|
|
459
|
+
const urlSearchParams = new URLSearchParams();
|
|
460
|
+
|
|
461
|
+
for (const [key, value] of Object.entries(launchParams)) {
|
|
462
|
+
if (typeof value === 'string' && value) {
|
|
463
|
+
urlSearchParams.set(key, value);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const queryString = urlSearchParams.toString();
|
|
468
|
+
const safeRoutePath = normalizeRoutePath(routePath);
|
|
469
|
+
const url = queryString ? `${safeRoutePath}?${queryString}` : safeRoutePath;
|
|
470
|
+
const { apiKey: _apiKey, ...safeParams } = launchParams;
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
ready: Boolean(config?.ready),
|
|
474
|
+
url,
|
|
475
|
+
params: safeParams,
|
|
476
|
+
discovery: config?.discovery || {},
|
|
477
|
+
warnings: config?.warnings || []
|
|
478
|
+
};
|
|
479
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
|
|
4
|
+
function escapeAppleScriptString(value) {
|
|
5
|
+
return String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function buildLocalTerminalSpawn({ command, platform = os.platform() }) {
|
|
9
|
+
const terminalCommand = String(command || '').trim();
|
|
10
|
+
|
|
11
|
+
if (!terminalCommand) {
|
|
12
|
+
throw new Error('Command is required');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (platform === 'darwin') {
|
|
16
|
+
return {
|
|
17
|
+
command: 'osascript',
|
|
18
|
+
args: [
|
|
19
|
+
'-e',
|
|
20
|
+
'tell application "Terminal" to activate',
|
|
21
|
+
'-e',
|
|
22
|
+
`tell application "Terminal" to do script "${escapeAppleScriptString(terminalCommand)}"`
|
|
23
|
+
]
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (platform === 'win32') {
|
|
28
|
+
return {
|
|
29
|
+
command: 'cmd.exe',
|
|
30
|
+
args: [
|
|
31
|
+
'/c',
|
|
32
|
+
'start',
|
|
33
|
+
'',
|
|
34
|
+
'powershell.exe',
|
|
35
|
+
'-NoExit',
|
|
36
|
+
'-Command',
|
|
37
|
+
terminalCommand
|
|
38
|
+
]
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
command: 'x-terminal-emulator',
|
|
44
|
+
args: ['-e', 'bash', '-lc', terminalCommand]
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function openLocalTerminal({ command, platform = os.platform() }) {
|
|
49
|
+
const spawnConfig = buildLocalTerminalSpawn({ command, platform });
|
|
50
|
+
const child = spawn(spawnConfig.command, spawnConfig.args, {
|
|
51
|
+
detached: true,
|
|
52
|
+
stdio: 'ignore'
|
|
53
|
+
});
|
|
54
|
+
child.unref();
|
|
55
|
+
return spawnConfig;
|
|
56
|
+
}
|