agent-message 0.1.4 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -8
- package/npm/bin/agent-message.mjs +2 -45
- package/npm/runtime/bin/agent-message-cli-darwin-amd64 +0 -0
- package/npm/runtime/bin/agent-message-cli-darwin-arm64 +0 -0
- package/npm/runtime/bin/agent-message-server-darwin-amd64 +0 -0
- package/npm/runtime/bin/agent-message-server-darwin-arm64 +0 -0
- package/npm/runtime/web-dist/assets/index-BIXs-4JX.js +195 -0
- package/npm/runtime/web-dist/assets/index-BdQqmJ4R.css +1 -0
- package/npm/runtime/web-dist/index.html +2 -2
- package/npm/runtime/web-dist/sw.js +1 -1
- package/package.json +5 -5
- package/codex-message/Cargo.lock +0 -1867
- package/codex-message/Cargo.toml +0 -17
- package/codex-message/README.md +0 -29
- package/codex-message/src/agent_message.rs +0 -205
- package/codex-message/src/app.rs +0 -857
- package/codex-message/src/codex.rs +0 -278
- package/codex-message/src/main.rs +0 -101
- package/codex-message/src/render.rs +0 -125
- package/npm/bin/codex-message.mjs +0 -41
- package/npm/runtime/web-dist/assets/index-AfnEJAni.css +0 -1
- package/npm/runtime/web-dist/assets/index-QUUfdfoN.js +0 -182
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
use std::collections::HashMap;
|
|
2
|
-
use std::path::Path;
|
|
3
|
-
use std::process::Stdio;
|
|
4
|
-
use std::sync::Arc;
|
|
5
|
-
use std::sync::atomic::{AtomicU64, Ordering};
|
|
6
|
-
|
|
7
|
-
use anyhow::{Context, Result, anyhow, bail};
|
|
8
|
-
use serde_json::Value;
|
|
9
|
-
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
|
10
|
-
use tokio::process::{Child, ChildStdin, ChildStdout, Command};
|
|
11
|
-
use tokio::sync::{Mutex, mpsc, oneshot};
|
|
12
|
-
|
|
13
|
-
#[derive(Debug)]
|
|
14
|
-
pub(crate) enum IncomingMessage {
|
|
15
|
-
Request {
|
|
16
|
-
method: String,
|
|
17
|
-
id: Value,
|
|
18
|
-
params: Value,
|
|
19
|
-
},
|
|
20
|
-
Notification {
|
|
21
|
-
method: String,
|
|
22
|
-
params: Value,
|
|
23
|
-
},
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
#[derive(Debug, Clone)]
|
|
27
|
-
pub(crate) struct RpcError {
|
|
28
|
-
pub(crate) code: Option<i64>,
|
|
29
|
-
pub(crate) message: String,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
impl std::fmt::Display for RpcError {
|
|
33
|
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
34
|
-
match self.code {
|
|
35
|
-
Some(code) => write!(f, "rpc error {code}: {}", self.message),
|
|
36
|
-
None => write!(f, "rpc error: {}", self.message),
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
impl std::error::Error for RpcError {}
|
|
42
|
-
|
|
43
|
-
pub(crate) struct CodexAppServer {
|
|
44
|
-
child: Child,
|
|
45
|
-
stdin: Arc<Mutex<ChildStdin>>,
|
|
46
|
-
next_request_id: AtomicU64,
|
|
47
|
-
pending: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Value, RpcError>>>>>,
|
|
48
|
-
events_rx: mpsc::UnboundedReceiver<IncomingMessage>,
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
impl CodexAppServer {
|
|
52
|
-
pub(crate) async fn start(codex_bin: &Path, cwd: &Path) -> Result<Self> {
|
|
53
|
-
let mut child = Command::new(codex_bin)
|
|
54
|
-
.arg("app-server")
|
|
55
|
-
.current_dir(cwd)
|
|
56
|
-
.stdin(Stdio::piped())
|
|
57
|
-
.stdout(Stdio::piped())
|
|
58
|
-
.stderr(Stdio::piped())
|
|
59
|
-
.kill_on_drop(true)
|
|
60
|
-
.spawn()
|
|
61
|
-
.with_context(|| format!("spawn `{}`", codex_bin.display()))?;
|
|
62
|
-
|
|
63
|
-
let stdin = child.stdin.take().context("capture codex stdin")?;
|
|
64
|
-
let stdout = child.stdout.take().context("capture codex stdout")?;
|
|
65
|
-
let stderr = child.stderr.take().context("capture codex stderr")?;
|
|
66
|
-
|
|
67
|
-
let pending = Arc::new(Mutex::new(HashMap::new()));
|
|
68
|
-
let (events_tx, events_rx) = mpsc::unbounded_channel();
|
|
69
|
-
|
|
70
|
-
spawn_stdout_pump(stdout, Arc::clone(&pending), events_tx);
|
|
71
|
-
spawn_stderr_pump(stderr);
|
|
72
|
-
|
|
73
|
-
Ok(Self {
|
|
74
|
-
child,
|
|
75
|
-
stdin: Arc::new(Mutex::new(stdin)),
|
|
76
|
-
next_request_id: AtomicU64::new(1),
|
|
77
|
-
pending,
|
|
78
|
-
events_rx,
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
pub(crate) async fn initialize(&self) -> Result<()> {
|
|
83
|
-
let initialize = serde_json::json!({
|
|
84
|
-
"clientInfo": {
|
|
85
|
-
"name": "codex_message",
|
|
86
|
-
"title": "Codex Message",
|
|
87
|
-
"version": env!("CARGO_PKG_VERSION"),
|
|
88
|
-
},
|
|
89
|
-
"capabilities": {
|
|
90
|
-
"experimentalApi": true,
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
self.request("initialize", initialize).await?;
|
|
94
|
-
self.notify("initialized", serde_json::json!({})).await
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
pub(crate) async fn request(&self, method: &str, params: Value) -> Result<Value> {
|
|
98
|
-
let id = self.next_request_id.fetch_add(1, Ordering::SeqCst);
|
|
99
|
-
let request_id = Value::from(id);
|
|
100
|
-
let key = id_key(&request_id)?;
|
|
101
|
-
let (tx, rx) = oneshot::channel();
|
|
102
|
-
self.pending.lock().await.insert(key, tx);
|
|
103
|
-
self.write_message(serde_json::json!({
|
|
104
|
-
"id": request_id,
|
|
105
|
-
"method": method,
|
|
106
|
-
"params": params,
|
|
107
|
-
}))
|
|
108
|
-
.await?;
|
|
109
|
-
|
|
110
|
-
match rx.await {
|
|
111
|
-
Ok(Ok(result)) => Ok(result),
|
|
112
|
-
Ok(Err(error)) => Err(error.into()),
|
|
113
|
-
Err(_) => bail!("rpc response channel dropped for method `{method}`"),
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
pub(crate) async fn notify(&self, method: &str, params: Value) -> Result<()> {
|
|
118
|
-
self.write_message(serde_json::json!({
|
|
119
|
-
"method": method,
|
|
120
|
-
"params": params,
|
|
121
|
-
}))
|
|
122
|
-
.await
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
pub(crate) async fn respond(&self, id: Value, result: Value) -> Result<()> {
|
|
126
|
-
self.write_message(serde_json::json!({
|
|
127
|
-
"id": id,
|
|
128
|
-
"result": result,
|
|
129
|
-
}))
|
|
130
|
-
.await
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
pub(crate) async fn next_event(&mut self) -> Result<IncomingMessage> {
|
|
134
|
-
self.events_rx
|
|
135
|
-
.recv()
|
|
136
|
-
.await
|
|
137
|
-
.ok_or_else(|| anyhow!("codex app-server event stream ended"))
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
pub(crate) async fn shutdown(&mut self) -> Result<()> {
|
|
141
|
-
if let Err(error) = self.child.start_kill() {
|
|
142
|
-
eprintln!("[codex] failed to signal shutdown: {error}");
|
|
143
|
-
}
|
|
144
|
-
let _ = self.child.wait().await;
|
|
145
|
-
Ok(())
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async fn write_message(&self, message: Value) -> Result<()> {
|
|
149
|
-
let encoded = serde_json::to_vec(&message).context("encode rpc message")?;
|
|
150
|
-
let mut stdin = self.stdin.lock().await;
|
|
151
|
-
stdin
|
|
152
|
-
.write_all(&encoded)
|
|
153
|
-
.await
|
|
154
|
-
.context("write rpc payload to codex stdin")?;
|
|
155
|
-
stdin
|
|
156
|
-
.write_all(b"\n")
|
|
157
|
-
.await
|
|
158
|
-
.context("write rpc newline to codex stdin")?;
|
|
159
|
-
stdin.flush().await.context("flush codex stdin")
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
fn spawn_stdout_pump(
|
|
164
|
-
stdout: ChildStdout,
|
|
165
|
-
pending: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Value, RpcError>>>>>,
|
|
166
|
-
events_tx: mpsc::UnboundedSender<IncomingMessage>,
|
|
167
|
-
) {
|
|
168
|
-
tokio::spawn(async move {
|
|
169
|
-
let mut lines = BufReader::new(stdout).lines();
|
|
170
|
-
loop {
|
|
171
|
-
let next = lines.next_line().await;
|
|
172
|
-
let line = match next {
|
|
173
|
-
Ok(Some(line)) => line,
|
|
174
|
-
Ok(None) => break,
|
|
175
|
-
Err(error) => {
|
|
176
|
-
eprintln!("[codex] failed to read stdout: {error}");
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
let trimmed = line.trim();
|
|
181
|
-
if trimmed.is_empty() {
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
let value: Value = match serde_json::from_str(trimmed) {
|
|
186
|
-
Ok(value) => value,
|
|
187
|
-
Err(error) => {
|
|
188
|
-
eprintln!("[codex] invalid JSON from app-server: {error}: {trimmed}");
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
if let Some(id) = value.get("id") {
|
|
194
|
-
if value.get("method").is_some() {
|
|
195
|
-
let Some(method) = value.get("method").and_then(Value::as_str) else {
|
|
196
|
-
eprintln!("[codex] request missing method string: {trimmed}");
|
|
197
|
-
continue;
|
|
198
|
-
};
|
|
199
|
-
let params = value.get("params").cloned().unwrap_or(Value::Null);
|
|
200
|
-
if events_tx
|
|
201
|
-
.send(IncomingMessage::Request {
|
|
202
|
-
method: method.to_string(),
|
|
203
|
-
id: id.clone(),
|
|
204
|
-
params,
|
|
205
|
-
})
|
|
206
|
-
.is_err()
|
|
207
|
-
{
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
let key = match id_key(id) {
|
|
214
|
-
Ok(key) => key,
|
|
215
|
-
Err(error) => {
|
|
216
|
-
eprintln!("[codex] invalid response id: {error:#}");
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
let sender = pending.lock().await.remove(&key);
|
|
221
|
-
let Some(sender) = sender else {
|
|
222
|
-
eprintln!("[codex] dropped response for unknown request id: {trimmed}");
|
|
223
|
-
continue;
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
if let Some(result) = value.get("result") {
|
|
227
|
-
let _ = sender.send(Ok(result.clone()));
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
let error = value.get("error").cloned().unwrap_or(Value::Null);
|
|
232
|
-
let rpc_error = RpcError {
|
|
233
|
-
code: error.get("code").and_then(Value::as_i64),
|
|
234
|
-
message: error
|
|
235
|
-
.get("message")
|
|
236
|
-
.and_then(Value::as_str)
|
|
237
|
-
.unwrap_or("unknown rpc error")
|
|
238
|
-
.to_string(),
|
|
239
|
-
};
|
|
240
|
-
let _ = sender.send(Err(rpc_error));
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if let Some(method) = value.get("method").and_then(Value::as_str) {
|
|
245
|
-
let params = value.get("params").cloned().unwrap_or(Value::Null);
|
|
246
|
-
if events_tx
|
|
247
|
-
.send(IncomingMessage::Notification {
|
|
248
|
-
method: method.to_string(),
|
|
249
|
-
params,
|
|
250
|
-
})
|
|
251
|
-
.is_err()
|
|
252
|
-
{
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
fn spawn_stderr_pump(stderr: tokio::process::ChildStderr) {
|
|
261
|
-
tokio::spawn(async move {
|
|
262
|
-
let mut lines = BufReader::new(stderr).lines();
|
|
263
|
-
loop {
|
|
264
|
-
match lines.next_line().await {
|
|
265
|
-
Ok(Some(line)) => eprintln!("[codex] {line}"),
|
|
266
|
-
Ok(None) => break,
|
|
267
|
-
Err(error) => {
|
|
268
|
-
eprintln!("[codex] failed to read stderr: {error}");
|
|
269
|
-
break;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
fn id_key(id: &Value) -> Result<String> {
|
|
277
|
-
serde_json::to_string(id).context("serialize request id")
|
|
278
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
mod agent_message;
|
|
2
|
-
mod app;
|
|
3
|
-
mod codex;
|
|
4
|
-
mod render;
|
|
5
|
-
|
|
6
|
-
use std::path::PathBuf;
|
|
7
|
-
|
|
8
|
-
use anyhow::Context;
|
|
9
|
-
use app::App;
|
|
10
|
-
use clap::Parser;
|
|
11
|
-
|
|
12
|
-
#[derive(Debug, Clone, clap::ValueEnum)]
|
|
13
|
-
enum ApprovalPolicyArg {
|
|
14
|
-
Untrusted,
|
|
15
|
-
OnFailure,
|
|
16
|
-
OnRequest,
|
|
17
|
-
Never,
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
impl ApprovalPolicyArg {
|
|
21
|
-
fn as_app_server_value(&self) -> &'static str {
|
|
22
|
-
match self {
|
|
23
|
-
Self::Untrusted => "untrusted",
|
|
24
|
-
Self::OnFailure => "on-failure",
|
|
25
|
-
Self::OnRequest => "on-request",
|
|
26
|
-
Self::Never => "never",
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
#[derive(Debug, Clone, clap::ValueEnum)]
|
|
32
|
-
enum SandboxArg {
|
|
33
|
-
ReadOnly,
|
|
34
|
-
WorkspaceWrite,
|
|
35
|
-
DangerFullAccess,
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
#[derive(Debug, Clone, Parser)]
|
|
39
|
-
#[command(author, version, about)]
|
|
40
|
-
struct Cli {
|
|
41
|
-
#[arg(long = "to", env = "CODEX_MESSAGE_TO", default_value = "jay")]
|
|
42
|
-
to_username: String,
|
|
43
|
-
|
|
44
|
-
#[arg(long, env = "CODEX_MESSAGE_CODEX_BIN", default_value = "codex")]
|
|
45
|
-
codex_bin: PathBuf,
|
|
46
|
-
|
|
47
|
-
#[arg(long, env = "CODEX_MESSAGE_MODEL")]
|
|
48
|
-
model: Option<String>,
|
|
49
|
-
|
|
50
|
-
#[arg(long, env = "CODEX_MESSAGE_CWD")]
|
|
51
|
-
cwd: Option<PathBuf>,
|
|
52
|
-
|
|
53
|
-
#[arg(long, env = "CODEX_MESSAGE_APPROVAL_POLICY")]
|
|
54
|
-
approval_policy: Option<ApprovalPolicyArg>,
|
|
55
|
-
|
|
56
|
-
#[arg(long, env = "CODEX_MESSAGE_SANDBOX", default_value = "workspace-write")]
|
|
57
|
-
sandbox: SandboxArg,
|
|
58
|
-
|
|
59
|
-
#[arg(long, env = "CODEX_MESSAGE_NETWORK_ACCESS", default_value_t = false)]
|
|
60
|
-
network_access: bool,
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
#[derive(Debug, Clone)]
|
|
64
|
-
pub(crate) struct Config {
|
|
65
|
-
pub(crate) to_username: String,
|
|
66
|
-
pub(crate) codex_bin: PathBuf,
|
|
67
|
-
pub(crate) model: Option<String>,
|
|
68
|
-
pub(crate) cwd: PathBuf,
|
|
69
|
-
pub(crate) approval_policy: Option<String>,
|
|
70
|
-
pub(crate) sandbox: SandboxArg,
|
|
71
|
-
pub(crate) network_access: bool,
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
impl TryFrom<Cli> for Config {
|
|
75
|
-
type Error = anyhow::Error;
|
|
76
|
-
|
|
77
|
-
fn try_from(value: Cli) -> Result<Self, Self::Error> {
|
|
78
|
-
let cwd = match value.cwd {
|
|
79
|
-
Some(path) => path,
|
|
80
|
-
None => std::env::current_dir().context("resolve current working directory")?,
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
Ok(Self {
|
|
84
|
-
to_username: value.to_username,
|
|
85
|
-
codex_bin: value.codex_bin,
|
|
86
|
-
model: value.model,
|
|
87
|
-
cwd,
|
|
88
|
-
approval_policy: value
|
|
89
|
-
.approval_policy
|
|
90
|
-
.map(|policy| policy.as_app_server_value().to_string()),
|
|
91
|
-
sandbox: value.sandbox,
|
|
92
|
-
network_access: value.network_access,
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
#[tokio::main]
|
|
98
|
-
async fn main() -> anyhow::Result<()> {
|
|
99
|
-
let config = Config::try_from(Cli::parse())?;
|
|
100
|
-
App::new(config).run().await
|
|
101
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
use serde_json::{Map, Value, json};
|
|
2
|
-
|
|
3
|
-
pub(crate) fn report_spec(badge: &str, title: &str, lines: &[String], body: Option<&str>) -> Value {
|
|
4
|
-
let mut elements = Map::new();
|
|
5
|
-
let mut children = Vec::new();
|
|
6
|
-
|
|
7
|
-
push_badge(&mut elements, &mut children, "badge", badge);
|
|
8
|
-
push_text(&mut elements, &mut children, "title", title);
|
|
9
|
-
push_separator(&mut elements, &mut children, "sep-top");
|
|
10
|
-
|
|
11
|
-
for (index, line) in lines.iter().enumerate() {
|
|
12
|
-
let key = format!("line-{index}");
|
|
13
|
-
push_text(&mut elements, &mut children, &key, line);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if let Some(body) = body.and_then(non_empty) {
|
|
17
|
-
if !lines.is_empty() {
|
|
18
|
-
push_separator(&mut elements, &mut children, "sep-body");
|
|
19
|
-
}
|
|
20
|
-
for (index, paragraph) in paragraphs(body).into_iter().enumerate() {
|
|
21
|
-
let key = format!("body-{index}");
|
|
22
|
-
push_text(&mut elements, &mut children, &key, ¶graph);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
elements.insert(
|
|
27
|
-
"root".to_string(),
|
|
28
|
-
json!({
|
|
29
|
-
"type": "Stack",
|
|
30
|
-
"children": children,
|
|
31
|
-
}),
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
json!({
|
|
35
|
-
"root": "root",
|
|
36
|
-
"elements": elements,
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
pub(crate) fn approval_spec(
|
|
41
|
-
badge: &str,
|
|
42
|
-
title: &str,
|
|
43
|
-
details: &[String],
|
|
44
|
-
reply_hint: &str,
|
|
45
|
-
) -> Value {
|
|
46
|
-
let mut lines = details.to_vec();
|
|
47
|
-
lines.push(format!("Reply: {reply_hint}"));
|
|
48
|
-
report_spec(badge, title, &lines, None)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
fn push_badge(
|
|
52
|
-
elements: &mut Map<String, Value>,
|
|
53
|
-
children: &mut Vec<String>,
|
|
54
|
-
key: &str,
|
|
55
|
-
text: &str,
|
|
56
|
-
) {
|
|
57
|
-
children.push(key.to_string());
|
|
58
|
-
elements.insert(
|
|
59
|
-
key.to_string(),
|
|
60
|
-
json!({
|
|
61
|
-
"type": "Badge",
|
|
62
|
-
"props": { "text": text },
|
|
63
|
-
}),
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
fn push_text(elements: &mut Map<String, Value>, children: &mut Vec<String>, key: &str, text: &str) {
|
|
68
|
-
children.push(key.to_string());
|
|
69
|
-
elements.insert(
|
|
70
|
-
key.to_string(),
|
|
71
|
-
json!({
|
|
72
|
-
"type": "Text",
|
|
73
|
-
"props": { "text": text },
|
|
74
|
-
}),
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
fn push_separator(elements: &mut Map<String, Value>, children: &mut Vec<String>, key: &str) {
|
|
79
|
-
children.push(key.to_string());
|
|
80
|
-
elements.insert(
|
|
81
|
-
key.to_string(),
|
|
82
|
-
json!({
|
|
83
|
-
"type": "Separator",
|
|
84
|
-
}),
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
fn paragraphs(text: &str) -> Vec<String> {
|
|
89
|
-
text.split("\n\n")
|
|
90
|
-
.map(str::trim)
|
|
91
|
-
.filter(|part| !part.is_empty())
|
|
92
|
-
.map(ToOwned::to_owned)
|
|
93
|
-
.collect()
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
fn non_empty(value: &str) -> Option<&str> {
|
|
97
|
-
let trimmed = value.trim();
|
|
98
|
-
if trimmed.is_empty() {
|
|
99
|
-
None
|
|
100
|
-
} else {
|
|
101
|
-
Some(trimmed)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
#[cfg(test)]
|
|
106
|
-
mod tests {
|
|
107
|
-
use super::*;
|
|
108
|
-
|
|
109
|
-
#[test]
|
|
110
|
-
fn report_spec_has_expected_shape() {
|
|
111
|
-
let spec = report_spec(
|
|
112
|
-
"Completed",
|
|
113
|
-
"Request finished",
|
|
114
|
-
&["Status: ok".to_string()],
|
|
115
|
-
Some("First paragraph\n\nSecond paragraph"),
|
|
116
|
-
);
|
|
117
|
-
assert_eq!(spec["root"], "root");
|
|
118
|
-
assert_eq!(spec["elements"]["badge"]["type"], "Badge");
|
|
119
|
-
assert_eq!(spec["elements"]["title"]["type"], "Text");
|
|
120
|
-
assert_eq!(
|
|
121
|
-
spec["elements"]["body-1"]["props"]["text"],
|
|
122
|
-
"Second paragraph"
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { spawnSync } from 'node:child_process'
|
|
4
|
-
import { existsSync } from 'node:fs'
|
|
5
|
-
import { dirname, resolve } from 'node:path'
|
|
6
|
-
import process from 'node:process'
|
|
7
|
-
import { fileURLToPath } from 'node:url'
|
|
8
|
-
|
|
9
|
-
const scriptDir = dirname(fileURLToPath(import.meta.url))
|
|
10
|
-
const packageRoot = resolve(scriptDir, '..', '..')
|
|
11
|
-
const crateDir = resolve(packageRoot, 'codex-message')
|
|
12
|
-
const manifestPath = resolve(crateDir, 'Cargo.toml')
|
|
13
|
-
const binaryPath = resolve(crateDir, 'target', 'debug', process.platform === 'win32' ? 'codex-message.exe' : 'codex-message')
|
|
14
|
-
|
|
15
|
-
if (!existsSync(manifestPath)) {
|
|
16
|
-
console.error(`codex-message sources were not found at ${crateDir}`)
|
|
17
|
-
process.exit(1)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (!existsSync(binaryPath)) {
|
|
21
|
-
const build = spawnSync('cargo', ['build', '--manifest-path', manifestPath], {
|
|
22
|
-
stdio: 'inherit',
|
|
23
|
-
cwd: packageRoot,
|
|
24
|
-
})
|
|
25
|
-
if (build.status !== 0) {
|
|
26
|
-
process.exit(build.status ?? 1)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const child = spawnSync(binaryPath, process.argv.slice(2), {
|
|
31
|
-
stdio: 'inherit',
|
|
32
|
-
cwd: process.cwd(),
|
|
33
|
-
env: process.env,
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
if (child.error) {
|
|
37
|
-
console.error(child.error.message)
|
|
38
|
-
process.exit(1)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
process.exit(child.status ?? 0)
|