@dna113p/machines 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -11
- package/claude/statusline.mjs +277 -0
- package/dist/examples/chatgpt-web.d.ts +1 -0
- package/dist/examples/chatgpt-web.js +36 -0
- package/dist/examples/claude.d.ts +1 -0
- package/dist/examples/claude.js +30 -0
- package/dist/examples/codex.d.ts +1 -0
- package/dist/examples/codex.js +30 -0
- package/dist/examples/decision.d.ts +1 -0
- package/dist/examples/decision.js +66 -0
- package/dist/examples/deepseek.d.ts +1 -0
- package/dist/examples/deepseek.js +27 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +74 -11
- package/dist/pi-extension/index.js +16 -9
- package/dist/src/acp.js +2 -1
- package/dist/src/agent-bindings.d.ts +6 -0
- package/dist/src/agent-bindings.js +35 -5
- package/dist/src/agent-limit.d.ts +25 -0
- package/dist/src/agent-limit.js +34 -0
- package/dist/src/agent-limits.d.ts +37 -0
- package/dist/src/agent-limits.js +212 -0
- package/dist/src/agent-protocol.js +3 -1
- package/dist/src/catalog.d.ts +3 -0
- package/dist/src/catalog.js +84 -12
- package/dist/src/chatgpt-web.d.ts +143 -0
- package/dist/src/chatgpt-web.js +946 -0
- package/dist/src/claude.d.ts +35 -0
- package/dist/src/claude.js +294 -0
- package/dist/src/cli.js +282 -16
- package/dist/src/codex.d.ts +16 -0
- package/dist/src/codex.js +176 -0
- package/dist/src/conversation.d.ts +74 -0
- package/dist/src/conversation.js +46 -0
- package/dist/src/coordinator-guidance.d.ts +3 -0
- package/dist/src/coordinator-guidance.js +17 -0
- package/dist/src/decision.d.ts +29 -0
- package/dist/src/decision.js +67 -0
- package/dist/src/deepseek.d.ts +12 -0
- package/dist/src/deepseek.js +16 -0
- package/dist/src/host.d.ts +11 -0
- package/dist/src/host.js +153 -16
- package/dist/src/index.d.ts +39 -2
- package/dist/src/index.js +62 -5
- package/dist/src/jev.d.ts +15 -0
- package/dist/src/jev.js +90 -0
- package/dist/src/json.d.ts +6 -0
- package/dist/src/json.js +28 -0
- package/dist/src/launcher.d.ts +11 -1
- package/dist/src/launcher.js +72 -3
- package/dist/src/laya.d.ts +15 -0
- package/dist/src/laya.js +13 -0
- package/dist/src/openrouter.d.ts +16 -0
- package/dist/src/openrouter.js +97 -0
- package/dist/src/run-inbox.d.ts +49 -0
- package/dist/src/run-inbox.js +233 -0
- package/dist/src/run-status.d.ts +121 -0
- package/dist/src/run-status.js +336 -0
- package/dist/src/run-watch.d.ts +24 -0
- package/dist/src/run-watch.js +112 -0
- package/dist/src/session.d.ts +15 -2
- package/dist/src/session.js +47 -8
- package/dist/src/system-one-http.d.ts +25 -0
- package/dist/src/system-one-http.js +146 -0
- package/dist/src/terminal-human.d.ts +8 -1
- package/dist/src/terminal-human.js +36 -8
- package/dist/src/von.d.ts +15 -0
- package/dist/src/von.js +13 -0
- package/docs/architecture.md +74 -3
- package/docs/authoring.md +809 -1
- package/docs/integrations.md +322 -3
- package/docs/local-decisions.md +212 -0
- package/package.json +21 -4
- package/services/laya/server.py +230 -0
- package/skills/machine-builder/SKILL.md +56 -6
- package/skills/machine-delegation/SKILL.md +128 -0
- package/skills/machine-delegation/references/delegation-machine.md +90 -0
- package/skills/machine-project-setup/SKILL.md +181 -0
- package/skills/machine-project-setup/references/project-workflows.md +50 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AgentRunner } from "./index.ts";
|
|
2
|
+
export interface ClaudeAgentOptions {
|
|
3
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
4
|
+
readonly harness?: string;
|
|
5
|
+
/** A Claude Code model alias (for example "opus", "sonnet", "haiku") or a full model name. */
|
|
6
|
+
readonly model?: string;
|
|
7
|
+
/** An effort level supported by the installed Claude Code and selected model. */
|
|
8
|
+
readonly effort?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Defaults to "dontAsk": tools run only when Claude Code's own permission
|
|
11
|
+
* rules already allow them, and anything that would prompt is denied.
|
|
12
|
+
*/
|
|
13
|
+
readonly permissionMode?: "acceptEdits" | "auto" | "bypassPermissions" | "dontAsk" | "manual" | "plan";
|
|
14
|
+
/** Restricts the built-in tool set, for example ["Read"]. */
|
|
15
|
+
readonly tools?: readonly string[];
|
|
16
|
+
/** Permission rules to allow in addition to Claude Code's settings, for example ["Bash(npm test)"]. */
|
|
17
|
+
readonly allowedTools?: readonly string[];
|
|
18
|
+
/** Tools or permission rules to deny regardless of Claude Code's settings. */
|
|
19
|
+
readonly disallowedTools?: readonly string[];
|
|
20
|
+
/** Defaults to false, so each invocation does not save a resumable Claude Code session. */
|
|
21
|
+
readonly sessionPersistence?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Defaults to "limit": once the plan limit is used up and Claude Code starts
|
|
24
|
+
* spending paid extra usage, the turn stops with an `AgentLimitError`.
|
|
25
|
+
* "allow" lets the turn continue on extra usage.
|
|
26
|
+
*/
|
|
27
|
+
readonly extraUsage?: "limit" | "allow";
|
|
28
|
+
readonly output?: "capture" | "stream";
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Runs one fresh, non-interactive Claude Code turn using the installed CLI and
|
|
32
|
+
* its existing login. Authentication, settings, and permission rules stay in
|
|
33
|
+
* Claude Code; this runner never reads or forwards credentials.
|
|
34
|
+
*/
|
|
35
|
+
export declare function claudeAgent(command?: string, args?: readonly string[], options?: ClaudeAgentOptions): AgentRunner;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import * as v from "valibot";
|
|
4
|
+
import { AgentLimitError } from "./agent-limit.js";
|
|
5
|
+
import { agentPrompt, readAgentEvent } from "./agent-protocol.js";
|
|
6
|
+
/**
|
|
7
|
+
* Runs one fresh, non-interactive Claude Code turn using the installed CLI and
|
|
8
|
+
* its existing login. Authentication, settings, and permission rules stay in
|
|
9
|
+
* Claude Code; this runner never reads or forwards credentials.
|
|
10
|
+
*/
|
|
11
|
+
export function claudeAgent(command = "claude", args = [], options = {}) {
|
|
12
|
+
return async (request, report) => {
|
|
13
|
+
if (request.cwd === undefined)
|
|
14
|
+
throw new Error("Claude Agent requires a working directory");
|
|
15
|
+
if (request.outcomes.length === 0)
|
|
16
|
+
throw new Error("Claude Agent requires at least one allowed outcome");
|
|
17
|
+
const harness = options.harness ?? command;
|
|
18
|
+
const env = { ...process.env, ...options.env };
|
|
19
|
+
// Claude Code exports its own CLAUDE_* variables (including CLAUDE_EFFORT) to
|
|
20
|
+
// child processes, so runner settings use names it does not own.
|
|
21
|
+
const model = options.model ?? env.MACHINES_CLAUDE_MODEL ?? env.MACHINES_AGENT_MODEL;
|
|
22
|
+
const effort = options.effort ?? env.MACHINES_CLAUDE_EFFORT ?? env.MACHINES_AGENT_EFFORT;
|
|
23
|
+
const outputMode = options.output ?? "stream";
|
|
24
|
+
report?.({
|
|
25
|
+
type: "identity",
|
|
26
|
+
harness,
|
|
27
|
+
...(model === undefined ? {} : { model }),
|
|
28
|
+
...(effort === undefined ? {} : { thinking: effort }),
|
|
29
|
+
});
|
|
30
|
+
let stderr = "";
|
|
31
|
+
try {
|
|
32
|
+
const child = spawn(command, [
|
|
33
|
+
...args,
|
|
34
|
+
"--print", "--output-format", "stream-json", "--verbose",
|
|
35
|
+
"--permission-mode", options.permissionMode ?? "dontAsk",
|
|
36
|
+
...(options.sessionPersistence === true ? [] : ["--no-session-persistence"]),
|
|
37
|
+
...(model === undefined ? [] : ["--model", model]),
|
|
38
|
+
...(effort === undefined ? [] : ["--effort", effort]),
|
|
39
|
+
...(options.tools === undefined ? [] : ["--tools", options.tools.join(",")]),
|
|
40
|
+
...(options.allowedTools === undefined ? [] : ["--allowed-tools", options.allowedTools.join(",")]),
|
|
41
|
+
...(options.disallowedTools === undefined ? [] : ["--disallowed-tools", options.disallowedTools.join(",")]),
|
|
42
|
+
], { cwd: request.cwd, env, stdio: ["pipe", "pipe", "pipe"] });
|
|
43
|
+
const exited = new Promise((resolve, reject) => {
|
|
44
|
+
child.once("error", reject);
|
|
45
|
+
child.stdin.once("error", reject);
|
|
46
|
+
child.once("close", (code, signal) => {
|
|
47
|
+
if (code === 0)
|
|
48
|
+
resolve();
|
|
49
|
+
else
|
|
50
|
+
reject(new Error(signal === null
|
|
51
|
+
? `Process exited with code ${code}`
|
|
52
|
+
: `Process terminated by ${signal}`));
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
// Attach handlers before reading stdout: errors can arrive while it drains.
|
|
56
|
+
void exited.catch(() => { });
|
|
57
|
+
child.stderr.setEncoding("utf8");
|
|
58
|
+
child.stderr.on("data", (chunk) => {
|
|
59
|
+
stderr += chunk;
|
|
60
|
+
if (outputMode === "stream")
|
|
61
|
+
process.stderr.write(chunk);
|
|
62
|
+
});
|
|
63
|
+
const lines = createInterface({ input: child.stdout });
|
|
64
|
+
const toolTitles = new Map();
|
|
65
|
+
let result;
|
|
66
|
+
// The CLI reports a limit beside its messages: on the last top-level
|
|
67
|
+
// assistant record and in the latest rate-limit information.
|
|
68
|
+
let assistantError = {};
|
|
69
|
+
let rateLimit;
|
|
70
|
+
// The CLI streams one record per content block, and its final result
|
|
71
|
+
// joins the text blocks of the closing message.
|
|
72
|
+
let closingMessage;
|
|
73
|
+
let closingText = "";
|
|
74
|
+
const emitMessage = (text) => {
|
|
75
|
+
if (text === "")
|
|
76
|
+
return;
|
|
77
|
+
report?.({ type: "output", text: `${text}${text.endsWith("\n") ? "" : "\n"}` });
|
|
78
|
+
if (outputMode === "stream")
|
|
79
|
+
process.stdout.write(`${text}${text.endsWith("\n") ? "" : "\n"}`);
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
child.stdin.end(agentPrompt(request));
|
|
83
|
+
for await (const line of lines) {
|
|
84
|
+
const record = decodeRecord(line);
|
|
85
|
+
if (record === undefined)
|
|
86
|
+
continue;
|
|
87
|
+
if (record.type === "malformed-result") {
|
|
88
|
+
// An unreadable final result must not leave an earlier one in effect.
|
|
89
|
+
result = { failed: true, text: "Claude returned a malformed final result" };
|
|
90
|
+
}
|
|
91
|
+
else if (record.type === "system") {
|
|
92
|
+
// The CLI resolves aliases and defaults; report what actually runs.
|
|
93
|
+
report?.({
|
|
94
|
+
type: "identity",
|
|
95
|
+
harness,
|
|
96
|
+
model: record.model,
|
|
97
|
+
...(effort === undefined ? {} : { thinking: effort }),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else if (record.type === "rate_limit_event") {
|
|
101
|
+
const info = record.rate_limit_info;
|
|
102
|
+
if (info?.isUsingOverage === true && options.extraUsage !== "allow") {
|
|
103
|
+
// The plan limit is already used up; continuing would spend paid credits.
|
|
104
|
+
throw new AgentLimitError("Claude plan limit reached; extra usage is not used", agentLimit(harness, info));
|
|
105
|
+
}
|
|
106
|
+
// Information without a recognised status does not replace what is known.
|
|
107
|
+
if (info?.status !== undefined)
|
|
108
|
+
rateLimit = info;
|
|
109
|
+
}
|
|
110
|
+
else if (record.type === "result") {
|
|
111
|
+
result = {
|
|
112
|
+
failed: record.is_error === true || record.subtype !== "success",
|
|
113
|
+
text: record.result ?? record.errors?.join("\n") ?? record.subtype,
|
|
114
|
+
...(record.is_error === true
|
|
115
|
+
? { apiError: { name: record.api_error, rateLimited: record.api_error_status === 429 } }
|
|
116
|
+
: {}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
else if (record.parent_tool_use_id !== undefined && record.parent_tool_use_id !== null) {
|
|
120
|
+
// Subagent activity belongs to the parent's tool call, not the final answer.
|
|
121
|
+
}
|
|
122
|
+
else if (record.type === "assistant") {
|
|
123
|
+
assistantError = { name: record.api_error, rateLimited: record.error === "rate_limit" };
|
|
124
|
+
const info = record.api_error_params?.rate_limit_info;
|
|
125
|
+
if (info?.status !== undefined)
|
|
126
|
+
rateLimit = info;
|
|
127
|
+
const message = record.message.id ?? undefined;
|
|
128
|
+
if (message === undefined || message !== closingMessage)
|
|
129
|
+
closingText = "";
|
|
130
|
+
closingMessage = message;
|
|
131
|
+
for (const block of record.message.content) {
|
|
132
|
+
if (block.type === "text") {
|
|
133
|
+
closingText += block.text;
|
|
134
|
+
emitMessage(block.text);
|
|
135
|
+
}
|
|
136
|
+
else if (block.type === "tool_use") {
|
|
137
|
+
toolTitles.set(block.id, block.name);
|
|
138
|
+
report?.({ type: "tool", id: block.id, title: block.name, status: "in_progress" });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (typeof record.message.content !== "string") {
|
|
143
|
+
for (const block of record.message.content) {
|
|
144
|
+
if (block.type !== "tool_result")
|
|
145
|
+
continue;
|
|
146
|
+
const title = toolTitles.get(block.tool_use_id);
|
|
147
|
+
report?.({
|
|
148
|
+
type: "tool",
|
|
149
|
+
id: block.tool_use_id,
|
|
150
|
+
...(title === undefined ? {} : { title }),
|
|
151
|
+
status: block.is_error === true ? "failed" : "completed",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Only a final result that ended on an API error can be a limit. The latest
|
|
157
|
+
// rate-limit information names it and its reset, whatever its own status.
|
|
158
|
+
const limit = result?.apiError !== undefined
|
|
159
|
+
&& reachedLimit(result.apiError, assistantError, rateLimit, result.text)
|
|
160
|
+
? agentLimit(harness, rateLimit)
|
|
161
|
+
: undefined;
|
|
162
|
+
try {
|
|
163
|
+
await exited;
|
|
164
|
+
}
|
|
165
|
+
catch (cause) {
|
|
166
|
+
// An empty result adds nothing to the exit, but a limit it carries is still reported.
|
|
167
|
+
if (result === undefined || (result.text === "" && limit === undefined))
|
|
168
|
+
throw cause;
|
|
169
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
170
|
+
throw turnFailure(result.text === "" ? message : `${message}: ${result.text}`, limit, { cause });
|
|
171
|
+
}
|
|
172
|
+
if (result === undefined)
|
|
173
|
+
throw new Error("Claude exited without a final result");
|
|
174
|
+
if (result.failed)
|
|
175
|
+
throw turnFailure(result.text, limit);
|
|
176
|
+
// Earlier messages may contain progress or a preliminary event. Only the
|
|
177
|
+
// CLI's final result determines the outcome, never accumulated activity.
|
|
178
|
+
if (withoutSpace(result.text) !== withoutSpace(closingText))
|
|
179
|
+
emitMessage(result.text);
|
|
180
|
+
return readAgentEvent(result.text, { adapter: "Claude", includeMessage: outputMode === "capture" });
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
lines.close();
|
|
184
|
+
child.kill();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (cause) {
|
|
188
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
189
|
+
const detail = stderr.trim();
|
|
190
|
+
const text = `Claude Agent "${command}" failed in "${request.cwd}": ${message}${detail === "" ? "" : `\n${detail}`}`;
|
|
191
|
+
throw cause instanceof AgentLimitError
|
|
192
|
+
? new AgentLimitError(text, cause.limit, { cause })
|
|
193
|
+
: new Error(text, { cause });
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
const planLimitErrors = new Set(["usage_limit_reached", "model_requires_usage_credits"]);
|
|
198
|
+
const limitText = /^(?:You've (?:hit|reached) your |You're out of (?:usage credits|extra usage))/u;
|
|
199
|
+
const maximumResetDelay = 8 * 24 * 60 * 60 * 1000;
|
|
200
|
+
// A 429 alone may be a temporary throttle; a plan limit also names itself, is
|
|
201
|
+
// rejected by the rate-limit information, or uses Claude Code's limit wording.
|
|
202
|
+
function reachedLimit(result, assistant, rateLimit, text) {
|
|
203
|
+
if ([result.name, assistant.name].some((name) => name !== undefined && planLimitErrors.has(name)))
|
|
204
|
+
return true;
|
|
205
|
+
if (result.rateLimited !== true && assistant.rateLimited !== true)
|
|
206
|
+
return false;
|
|
207
|
+
return (rateLimit?.status === "rejected" && rateLimit.isUsingOverage !== true) || limitText.test(text);
|
|
208
|
+
}
|
|
209
|
+
function agentLimit(harness, info) {
|
|
210
|
+
const now = Date.now();
|
|
211
|
+
const resetsAt = info?.resetsAt === undefined ? undefined : info.resetsAt * 1000;
|
|
212
|
+
return {
|
|
213
|
+
harness,
|
|
214
|
+
...(info?.rateLimitType === undefined ? {} : { kind: info.rateLimitType }),
|
|
215
|
+
// A reset time that is past or implausibly distant is not reported.
|
|
216
|
+
...(resetsAt !== undefined && Number.isFinite(resetsAt) && resetsAt > now && resetsAt <= now + maximumResetDelay
|
|
217
|
+
? { resetsAt }
|
|
218
|
+
: {}),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function turnFailure(message, limit, options) {
|
|
222
|
+
return limit === undefined ? new Error(message, options) : new AgentLimitError(message, limit, options);
|
|
223
|
+
}
|
|
224
|
+
const parentSchema = v.nullish(v.string());
|
|
225
|
+
// Unlisted block types (thinking, images, future additions) are skipped, not rejected.
|
|
226
|
+
const otherBlockSchema = v.pipe(v.object({ type: v.string() }), v.transform(() => ({ type: "other" })));
|
|
227
|
+
const assistantBlockSchema = v.union([
|
|
228
|
+
v.object({ type: v.literal("text"), text: v.string() }),
|
|
229
|
+
v.object({ type: v.literal("tool_use"), id: v.string(), name: v.string() }),
|
|
230
|
+
otherBlockSchema,
|
|
231
|
+
]);
|
|
232
|
+
const userBlockSchema = v.union([
|
|
233
|
+
v.object({ type: v.literal("tool_result"), tool_use_id: v.string(), is_error: v.nullish(v.boolean()) }),
|
|
234
|
+
otherBlockSchema,
|
|
235
|
+
]);
|
|
236
|
+
// The limit fields were read from the Claude Code bundle, not observed at a real
|
|
237
|
+
// limit, so a malformed one is dropped without rejecting the record that carries it.
|
|
238
|
+
// A value outside the listed ones counts as malformed.
|
|
239
|
+
const tolerant = (schema) => v.fallback(v.optional(schema), undefined);
|
|
240
|
+
const rateLimitInfoSchema = v.object({
|
|
241
|
+
status: tolerant(v.picklist(["allowed", "allowed_warning", "rejected"])),
|
|
242
|
+
// Whole Unix seconds.
|
|
243
|
+
resetsAt: tolerant(v.pipe(v.number(), v.safeInteger())),
|
|
244
|
+
rateLimitType: tolerant(v.picklist([
|
|
245
|
+
"five_hour", "seven_day", "seven_day_opus", "seven_day_sonnet", "seven_day_overage_included", "overage",
|
|
246
|
+
])),
|
|
247
|
+
isUsingOverage: tolerant(v.boolean()),
|
|
248
|
+
});
|
|
249
|
+
const recordSchema = v.variant("type", [
|
|
250
|
+
v.object({ type: v.literal("system"), subtype: v.literal("init"), model: v.string() }),
|
|
251
|
+
v.object({ type: v.literal("rate_limit_event"), rate_limit_info: tolerant(rateLimitInfoSchema) }),
|
|
252
|
+
v.object({
|
|
253
|
+
type: v.literal("assistant"),
|
|
254
|
+
parent_tool_use_id: parentSchema,
|
|
255
|
+
message: v.object({ id: v.nullish(v.string()), content: v.array(assistantBlockSchema) }),
|
|
256
|
+
error: tolerant(v.string()),
|
|
257
|
+
api_error: tolerant(v.string()),
|
|
258
|
+
api_error_params: tolerant(v.object({ rate_limit_info: tolerant(rateLimitInfoSchema) })),
|
|
259
|
+
}),
|
|
260
|
+
v.object({
|
|
261
|
+
type: v.literal("user"),
|
|
262
|
+
parent_tool_use_id: parentSchema,
|
|
263
|
+
message: v.object({ content: v.union([v.string(), v.array(userBlockSchema)]) }),
|
|
264
|
+
}),
|
|
265
|
+
v.object({
|
|
266
|
+
type: v.literal("result"),
|
|
267
|
+
subtype: v.string(),
|
|
268
|
+
is_error: v.nullish(v.boolean()),
|
|
269
|
+
result: v.nullish(v.string()),
|
|
270
|
+
errors: v.nullish(v.array(v.string())),
|
|
271
|
+
api_error: tolerant(v.string()),
|
|
272
|
+
api_error_status: tolerant(v.number()),
|
|
273
|
+
}),
|
|
274
|
+
]);
|
|
275
|
+
// Ignore unsupported events and malformed records without exposing raw protocol data.
|
|
276
|
+
function decodeRecord(line) {
|
|
277
|
+
let value;
|
|
278
|
+
try {
|
|
279
|
+
value = JSON.parse(line);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
const parsed = v.safeParse(recordSchema, value);
|
|
285
|
+
if (parsed.success)
|
|
286
|
+
return parsed.output;
|
|
287
|
+
return value !== null && typeof value === "object" && "type" in value && value.type === "result"
|
|
288
|
+
? { type: "malformed-result" }
|
|
289
|
+
: undefined;
|
|
290
|
+
}
|
|
291
|
+
// Block boundaries differ between streamed messages and the joined result.
|
|
292
|
+
function withoutSpace(text) {
|
|
293
|
+
return text.replace(/\s+/gu, "");
|
|
294
|
+
}
|