pi-grok-agent 0.1.0 → 0.1.2
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 +34 -147
- package/docs/usage.md +2 -0
- package/package.json +1 -1
- package/src/model/provider.ts +1 -0
- package/src/model/session.ts +27 -4
package/README.md
CHANGED
|
@@ -2,178 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
Grok's agent. Pi's workflow.
|
|
4
4
|
|
|
5
|
-
Run Grok Build as a model in the [Pi coding agent](https://github.com/earendil-works/pi).
|
|
6
|
-
Grok keeps its native tools and history. You keep Pi's transcript, extensions, and permission dialogs.
|
|
7
|
-
|
|
8
|
-
[Quick start](#quick-start) · [First result](#first-result) · [Reference](docs/usage.md) · [Limitations](#limitations) · [Apache-2.0](LICENSE)
|
|
9
|
-
|
|
10
|
-
After [setup](#quick-start), select Grok like any Pi model:
|
|
11
|
-
|
|
12
|
-
```sh
|
|
13
|
-
pi --model grok/grok-4.7
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Status: version 0.1.0. Developed and tested on Linux with Node.js 26.10.0, Pi 0.87.1, and Grok Build 1.0.41. Other versions and platforms are untested.
|
|
17
|
-
|
|
18
|
-
This package connects an agent, not the xAI chat-completions API. It needs a logged-in Grok Build CLI and a local gateway.
|
|
19
|
-
The tool gates are not an operating-system sandbox.
|
|
20
|
-
|
|
21
|
-
## Requirements
|
|
22
|
-
|
|
23
|
-
| Item | Requirement |
|
|
24
|
-
| --- | --- |
|
|
25
|
-
| Node.js and npm | Node.js 22.19 or later is Pi's minimum. This package is tested only on 26.10.0. |
|
|
26
|
-
| [Pi](https://github.com/earendil-works/pi#quick-start) | Tested with 0.87.1. Other versions are untested. |
|
|
27
|
-
| [Grok Build CLI](https://docs.x.ai/build/overview) | `grok` on `PATH`, or its path in `PI_GROK_BINARY`. |
|
|
28
|
-
| Grok login | Run `/grok login` in Pi, or `grok login` in a terminal. Both use Grok's own sign-in, and Grok keeps the credential in `~/.grok/auth.json`. Pi's `/login` xAI entry is a separate login and does not sign in Grok Build. |
|
|
29
|
-
| Free local port | `127.0.0.1:2419`, or another loopback port in `GROK_ACP_URL`. |
|
|
30
|
-
| Optional | A terminal with inline image support. ImageMagick 7 (`magick`) to show JPEG, WebP, and GIF images inline. PNG needs no converter. |
|
|
31
|
-
|
|
32
|
-
Grok usage counts against your Grok account. Pi shows the cost that Grok reports for each turn.
|
|
33
|
-
|
|
34
|
-
## Quick start
|
|
35
|
-
|
|
36
5
|
```sh
|
|
37
6
|
pi install npm:pi-grok-agent
|
|
38
|
-
pi --model grok/grok-4.7
|
|
39
7
|
```
|
|
40
8
|
|
|
41
|
-
|
|
9
|
+
Run [Grok Build](https://docs.x.ai/build/overview) as an additional model provider in [Pi coding agent](https://github.com/earendil-works/pi). Grok keeps its native environment, tools, and session history. Pi provides the DIY harness, turn control, permission requests, and extensions.
|
|
42
10
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
### From a clone
|
|
46
|
-
|
|
47
|
-
```sh
|
|
48
|
-
git clone https://github.com/JangMan-J/pi-grok-agent.git
|
|
49
|
-
cd pi-grok-agent
|
|
50
|
-
npm install --omit=dev
|
|
51
|
-
npm run server # terminal A: the gateway
|
|
52
|
-
pi -e . --model grok/grok-4.7 # terminal B: this Pi process only; or pi install . for every session
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
`pi install git:github.com/JangMan-J/pi-grok-agent` also works. A clone auto-starts the gateway from its own checkout, so `npm run server` is optional. A git install uses the same code path, but that is not yet tested live.
|
|
56
|
-
|
|
57
|
-
## First result
|
|
58
|
-
|
|
59
|
-
Start Pi in any project directory that has a `package.json`, and send this prompt:
|
|
11
|
+
## How it connects
|
|
60
12
|
|
|
61
13
|
```text
|
|
62
|
-
|
|
14
|
+
┌─────────────────────┐ ┌──────────────────┐ ┌─────────────────────┐
|
|
15
|
+
│ PI — drives turn │ │ GATEWAY │ │ GROK BUILD — works │
|
|
16
|
+
│ transcript, gates, │ ACP │ one per machine │ stdio │ own tools, agents, │
|
|
17
|
+
│ dialogs │ over WS │ 127.0.0.1:2419 │ │ own history │
|
|
18
|
+
│ grok provider ext │◄───────►│ guard + MCP │◄───────►│ ~/.grok login │
|
|
19
|
+
└─────────────────────┘ └──────────────────┘ └─────────────────────┘
|
|
63
20
|
```
|
|
64
21
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
- One line for each Grok tool call, for example `✓ grok read_file …` or `✓ grok hashline_read …`, with its duration. Grok chooses the tool.
|
|
68
|
-
- Thinking text that contains `[grok <tool>]` lines.
|
|
69
|
-
- An answer that names the package and its scripts.
|
|
70
|
-
- A footer cost that comes from Grok's usage report.
|
|
71
|
-
|
|
72
|
-
Then run `/grok debug`. It shows the gateway connection, the Grok session ID, the permission modes, token usage, and the lent Pi tools.
|
|
22
|
+
Pi drives the session using [Agent Client Protocol](https://agentclientprotocol.com) over WebSockets. Grok returns streaming responses, thinking blocks, as well as image and video requests. All of Grok's tool requests are routed back to Pi through callbacks over an MCP loopback. The first Grok turn auto-starts the local gateway (`127.0.0.1:2419` by default) ; every Pi process on the machine attaches to it. Details: [docs/architecture-diagram.md](docs/architecture-diagram.md) · [docs/usage.md](docs/usage.md).
|
|
73
23
|
|
|
74
|
-
|
|
24
|
+
## Function
|
|
75
25
|
|
|
76
|
-
|
|
26
|
+
- **Pi sets permissions and boundaries.** Read-only, ask, auto, and YOLO permission modes are supported.
|
|
27
|
+
- **Pi makes decisions.** Grok's tool call requests, turn order, user_ask_question prompts are delegated to Pi.
|
|
28
|
+
- **Grok can use Pi's extension tools** Lent over MCP as `pi__<name>`, with the result continuing the same Grok turn.
|
|
29
|
+
- **Grok keeps all of his tools and extensions.** My own observations have been that Grok performs better with his native toolset, so this project's purpose is to keep his tools without buying the shed.
|
|
77
30
|
|
|
78
|
-
|
|
31
|
+
## Models
|
|
79
32
|
|
|
80
|
-
|
|
|
81
|
-
| --- | --- |
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
33
|
+
| Model ID | Name in `/models` | Reasoning efforts | Context window |
|
|
34
|
+
| --- | --- | --- | --- |
|
|
35
|
+
| `grok/grok-4.7` | Grok 4.7 | low, medium, high, xhigh | 500,000 tokens |
|
|
36
|
+
| `grok/grok-4.7-build-fast` | Grok 4.7 Build Fast | low, medium, high, xhigh | 500,000 tokens |
|
|
37
|
+
| `grok/grok-4.6` | Grok 4.6 | low, medium, high, xhigh | 500,000 tokens |
|
|
38
|
+
| `grok/grok-4.5` | Grok 4.5 | low, medium, high, xhigh | 500,000 tokens |
|
|
85
39
|
|
|
86
|
-
```text
|
|
87
|
-
Pi ⇄ local ACP gateway ⇄ Grok Build
|
|
88
|
-
└─ native tools and agent history
|
|
89
|
-
```
|
|
90
40
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
### More controls
|
|
94
|
-
|
|
95
|
-
- Grok's native tools do the work. Grok executes the tools its harness offers, such as file, shell, search, web, subagent, and media tools. Pi does not execute them.
|
|
96
|
-
- Grok keeps the full tool results in its own context. Pi shows a shortened copy: 400 characters in the thinking stream, up to 8000 characters in the stored `grok-tool` entry, and up to 600 characters in the expanded entry.
|
|
97
|
-
- Grok's permission prompts become Pi dialogs. Grok's `ask_user_question` becomes Pi dialogs, one per question.
|
|
98
|
-
- Pi can gate Grok's tools. By default, a Pi session without `edit` or `write` denies Grok's edit tools, and a session without `bash` denies Grok's shell. `/grok perms read-only`, `ask`, `auto`, and `yolo` change this gate. A `denyGrokTools` entry always wins. Grok's own permission prompts are separate ([details](docs/usage.md#grok-permission-prompts)).
|
|
99
|
-
- Optional checks run around Grok's tools. After a Grok edit, a syntax check runs on the file. A failure goes back to Grok in the same turn. A configured `stopCheck` can hold the end of a turn.
|
|
100
|
-
- Pi can lend its extension tools to Grok. They appear to Grok as `pi__<name>`, and Pi executes them.
|
|
101
|
-
- Mid-turn Enter sends the text to Grok's `_x.ai/interject` method. Its effect on the running turn is not yet verified live. Alt+Enter queues a follow-up turn, as usual in Pi.
|
|
102
|
-
- Pi's thinking level sets Grok's reasoning effort. Escape cancels the Grok turn.
|
|
103
|
-
- Grok plan mode, `/goal`, and `/compact` are available through `/grok plan`, `/grok goal`, and `/grok compact`.
|
|
104
|
-
|
|
105
|
-
Models: `grok/grok-4.7`, `grok/grok-4.7-build-fast`, `grok/grok-4.6`, `grok/grok-4.5`. Each has a 500,000-token context window.
|
|
106
|
-
|
|
107
|
-
## Generated images and video
|
|
108
|
-
|
|
109
|
-
When a Grok tool result has the type `ImageGen`, `ImageEdit`, `ImageToVideo`, `ReferenceToVideo`, or `VideoGen`, Pi copies the file to `.pi/grok-images/` in the Pi working directory. That directory gets a `.gitignore` that ignores everything in it.
|
|
110
|
-
|
|
111
|
-
After the turn, Pi shows a `grok-media` message with the file path. PNG, JPEG, WebP, and GIF images also show inline when the terminal supports images. PNG shows directly. Pi converts JPEG, WebP, and GIF to PNG with `magick` first. Without `magick`, you see only the path for those formats. Video files show as a path only. Pi does not play video.
|
|
112
|
-
|
|
113
|
-
Live probes cover `image_gen` only (`evidence/image-probe.json`, run recorded in [docs/launch-verification.md](docs/launch-verification.md)). The image-edit and video result types are recognized in code but not yet probed with a live Grok run. The media message is for display only. Pi does not send it back to Grok.
|
|
114
|
-
|
|
115
|
-
Images that you attach in Pi go to Grok as a temporary file path under the system temp directory (`pi-grok-images`). Grok reads the file with its own tools.
|
|
116
|
-
|
|
117
|
-
## Limitations
|
|
118
|
-
|
|
119
|
-
- The auto-started gateway runs until you stop it or log out. With auto-start off, the gateway must run before the first Grok turn; until it has created its secret file, Grok turns fail with a message that names the file and the command.
|
|
120
|
-
- Run one gateway for each port and leader socket. A second `npm run server` with the default settings exits with `EADDRINUSE` and leaves the running gateway and its leader alone. [Run a separate gateway](docs/usage.md#run-a-second-isolated-gateway) for a demo or a test.
|
|
121
|
-
- Grok's native tool calls are not Pi tool calls. Pi records them as thinking text and `grok-tool` entries, and no model receives those entries.
|
|
122
|
-
- Pi compaction and Grok compaction are separate. Pi sends only the new messages of each turn. Only when it creates a new Grok session does it also send the earlier Pi transcript as text, cut to the last 60,000 characters.
|
|
123
|
-
- Grok reads the lent Pi tool list once for each Grok session. A changed tool set needs a new Pi session.
|
|
124
|
-
- A gateway restart loses the turn in progress. The next turn reconnects and loads the same Grok session.
|
|
125
|
-
- Hashline edits (`hashline_read`, `hashline_edit`, `hashline_grep`) occur only when `~/.grok/config.toml` sets `[toolset] file_toolset = "hashline"`. Otherwise Grok uses tools such as `read_file` and `search_replace`.
|
|
126
|
-
- After a switch from `grok/*` to another model in the same Pi session, mid-turn Enter goes to that model, not to the earlier Grok session. The Grok session stays stored and is used again when you switch back.
|
|
127
|
-
- The provider does not call Pi's `onPayload` and `onResponse` stream hooks.
|
|
128
|
-
- Cost per token is set to zero in the model metadata. The per-turn cost comes from Grok's report.
|
|
41
|
+
Model availability in Pi is determined by your [account access](https://grok.com).
|
|
129
42
|
|
|
130
43
|
## Safety
|
|
131
44
|
|
|
132
|
-
-
|
|
133
|
-
- The gateway listens on loopback only
|
|
134
|
-
- `/grok perms yolo`, `grokMode: "yolo"` or `"auto"`, and `headlessPermissions: "allow"` each remove a check. Use them only in a workspace you can lose. [usage.md](docs/usage.md#grok-permission-prompts) explains how they differ.
|
|
135
|
-
- Pi sends its system prompt to Grok as session rules. Pi sends user messages and, for a new Grok session, the earlier Pi transcript.
|
|
136
|
-
- `postEditCheck` and `stopCheck` run as shell commands (`bash -lc`) in the working directory.
|
|
137
|
-
- Pi packages run code with your permissions. Read the source before you install it.
|
|
138
|
-
|
|
139
|
-
Files and network endpoints are listed in [docs/usage.md](docs/usage.md#files-and-network).
|
|
45
|
+
- This package connects the Grok Build agent over ACP, not the xAI chat-completions API. The ACP protocol does not provide complete visibility or control of an agent, and not all functions or extensions of Grok Build have been tested for safety in this configuration. The tool gates are not an operating-system sandbox: Grok runs with your user's permissions.
|
|
46
|
+
- The gateway listens on loopback only, requires a bearer secret, and never starts Grok with `--always-approve`. Headless use does not imply approval: by default, headless Pi cancels Grok's permission prompts. The `postEditCheck` and `stopCheck` settings run as shell commands — treat them as executable code.
|
|
140
47
|
|
|
141
|
-
##
|
|
142
|
-
|
|
143
|
-
- [docs/usage.md](docs/usage.md): settings, lent tools, permissions, gateway guard, hooks, `/grok` commands, isolated gateway, checks, and troubleshooting.
|
|
144
|
-
- [docs/first-class-model.md](docs/first-class-model.md): design, turn mapping, and the development record.
|
|
145
|
-
- [docs/demo.md](docs/demo.md): a reproducible storyboard for a 30 to 60 second demo.
|
|
146
|
-
|
|
147
|
-
## Checks
|
|
148
|
-
|
|
149
|
-
```sh
|
|
150
|
-
npm install # development dependencies, including TypeScript
|
|
151
|
-
npm run check # tsc --noEmit
|
|
152
|
-
npm test # unit tests plus gateway tests against a fake grok binary, no Grok calls
|
|
153
|
-
```
|
|
48
|
+
## Notes
|
|
154
49
|
|
|
155
|
-
|
|
50
|
+
- Not compatible with API key access. A Grok account is required, any membership tier. If your OAuth token expires you can '/login' from either Grok Build or Pi with '/grok login' to renew credentials.
|
|
51
|
+
- Token read, token write, cache read and cache hit % displayed in Pi are taken from Grok Build. Pi may occasionally report inaccurate data during long multistep tool calls, but will correct onn the next turn.
|
|
156
52
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
## Relation to other ACP clients
|
|
160
|
-
|
|
161
|
-
Grok Build can serve ACP to any ACP client. This package connects that agent to Pi as a model provider. Pi's transcript, dialogs, tool gates, lent tools, and model selection then apply to Grok's own harness.
|
|
162
|
-
|
|
163
|
-
## For coding agents
|
|
53
|
+
## Documentation
|
|
164
54
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
For changes to this repository, read [AGENTS.md](AGENTS.md). It contains the source map and test commands.
|
|
55
|
+
- [docs/usage.md](docs/usage.md) — settings, lent tools, permissions, the gateway guard, hooks, `/grok` commands, troubleshooting
|
|
56
|
+
- [docs/architecture-diagram.md](docs/architecture-diagram.md) — the diagram in mermaid and ASCII
|
|
57
|
+
- [docs/first-class-model.md](docs/first-class-model.md) — design and turn mapping
|
|
58
|
+
- [docs/launch-verification.md](docs/launch-verification.md) — recorded live runs behind the verified claims
|
|
170
59
|
|
|
171
60
|
## Feedback
|
|
172
61
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
Before you post, remove secrets, tokens, session IDs, and private paths from all output. Do not attach raw logs or session files. Post a short excerpt that you have read, preferably from a synthetic demo project.
|
|
62
|
+
- [Open an issue](https://github.com/JangMan-J/pi-grok-agent/issues) with the output of `node --version`, `pi --version`, and `grok --version`, the model ID, and a short redacted excerpt of `/grok debug`.
|
|
176
63
|
|
|
177
64
|
## License
|
|
178
65
|
|
|
179
|
-
[Apache License 2.0](LICENSE).
|
|
66
|
+
- [Apache License 2.0](LICENSE).
|
package/docs/usage.md
CHANGED
|
@@ -67,6 +67,8 @@ A clone also auto-starts the gateway: it runs `scripts/server.ts` from the check
|
|
|
67
67
|
| `grok/grok-4.6` | Grok 4.6 | low, medium, high, xhigh |
|
|
68
68
|
| `grok/grok-4.5` | Grok 4.5 | low, medium, high |
|
|
69
69
|
|
|
70
|
+
Which of these a Grok account may use depends on the account. Grok reports the allowed models for each session; a free account on 2026-09-28 had only `grok-4.7`. Pi switches the Grok session to the model picked in `/models`. A model the account lacks fails the turn with the list of available models; before 0.1.1, Grok silently ran its default model instead.
|
|
71
|
+
|
|
70
72
|
Each model has a 500,000-token context window and a 32,000-token output limit in Pi's metadata. Per-token cost is zero in the metadata. The turn cost comes from Grok's `turn_completed` report, converted at 1e9 ticks per US dollar. That ratio is inferred from Grok's rates. It is not documented by Grok.
|
|
71
73
|
|
|
72
74
|
Pi controls work as usual:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-grok-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Pi model provider for Grok Build: Grok keeps its native harness and tools while Pi drives the session over a local WebSocket ACP gateway",
|
package/src/model/provider.ts
CHANGED
|
@@ -129,6 +129,7 @@ export function createGrokStream(connection: GrokModelConnection, sessions: Sess
|
|
|
129
129
|
session.piToolNames = piTools.map((t) => t.name);
|
|
130
130
|
session.tools = selectPiTools(piTools, sessions.piTools ?? 'extensions');
|
|
131
131
|
await session.attach(getCurrentSystemPrompt(context.messages) || undefined);
|
|
132
|
+
await session.applyModel(model.id); // before the effort: grok-4.5 has no xhigh
|
|
132
133
|
await session.applyEffort(options?.reasoning); // Pi's thinking level drives Grok's reasoning_effort
|
|
133
134
|
signal?.throwIfAborted();
|
|
134
135
|
const { history, tail } = splitTail(context.messages);
|
package/src/model/session.ts
CHANGED
|
@@ -135,13 +135,33 @@ export class GrokModelSession {
|
|
|
135
135
|
if (this.activePrompt) { this.activePrompt = undefined; this.rejectParked('Grok connection dropped; the turn was lost.'); }
|
|
136
136
|
this.reconnected = this.connection.lastDrop ?? 'reconnected';
|
|
137
137
|
}
|
|
138
|
-
const { sessionId } = await this.connection.attachSession({
|
|
138
|
+
const { sessionId, response } = await this.connection.attachSession({
|
|
139
139
|
sessionId: this.grokSessionId, cwd: this.cwd, serverId: this.serverId, serverName: 'pi', rules,
|
|
140
140
|
offerPiTools: this.tools.length > 0, grokMode: this.grokMode,
|
|
141
141
|
handlers: { onUpdate: (n) => this.onUpdate(n), onMcp: (m) => this.onMcp(m), onPermission: (r) => this.permission(r), onHookRun: (p, gate) => this.onHookRun(p, gate), onHookEvent: (p) => { void this.onHookRun(p); }, onQuestion: (q) => this.ask(q), onSessionExt: (u) => this.onSessionExt(u) },
|
|
142
142
|
});
|
|
143
143
|
this.grokSessionId = sessionId;
|
|
144
144
|
this.attachedGeneration = this.connection.generation;
|
|
145
|
+
// Grok reports the session's model and the models this account may use as the `model` config option.
|
|
146
|
+
const modelOption = ((response as { configOptions?: { id?: string; currentValue?: string; options?: { value?: string }[] }[] }).configOptions ?? []).find((o) => o.id === 'model');
|
|
147
|
+
if (modelOption) {
|
|
148
|
+
this.grokModel = modelOption.currentValue;
|
|
149
|
+
this.grokModels = (modelOption.options ?? []).map((o) => o.value).filter((v): v is string => !!v);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Grok's current model for this session, and the models the signed-in account may use, from the `model` config option. */
|
|
154
|
+
grokModel?: string;
|
|
155
|
+
grokModels?: string[];
|
|
156
|
+
|
|
157
|
+
/** Switch Grok to the model picked in Pi. Without this Grok runs its default model whatever Pi shows. */
|
|
158
|
+
async applyModel(modelId: string) {
|
|
159
|
+
if (modelId === this.grokModel) return;
|
|
160
|
+
if (this.grokModels?.length && !this.grokModels.includes(modelId)) {
|
|
161
|
+
throw new Error(`${modelId} is not available on this Grok account. Available: ${this.grokModels.join(', ')}. Pick one of those in /models.`);
|
|
162
|
+
}
|
|
163
|
+
await this.setConfigOption('model', modelId);
|
|
164
|
+
this.grokModel = modelId;
|
|
145
165
|
}
|
|
146
166
|
|
|
147
167
|
detach() {
|
|
@@ -436,20 +456,23 @@ export class GrokModelSession {
|
|
|
436
456
|
|
|
437
457
|
/** Saved media path from a Grok media tool result (`{ type: "ImageGen", path, filename, session_folder }` and kin). */
|
|
438
458
|
export function mediaPath(value: unknown): string | undefined {
|
|
439
|
-
const v = (value ?? {}) as
|
|
459
|
+
const v = (value ?? {}) as { path?: unknown; type?: unknown };
|
|
440
460
|
return typeof v.path === 'string' && /^(ImageGen|ImageEdit|ImageToVideo|ReferenceToVideo|VideoGen)$/.test(String(v.type ?? '')) ? v.path : undefined;
|
|
441
461
|
}
|
|
442
462
|
|
|
463
|
+
/** The fields `resultText` looks at in a Grok tool result envelope. */
|
|
464
|
+
type ResultEnvelope = { FileContent?: { raw_output?: unknown; content?: unknown }; output?: unknown; stdout?: unknown; text?: unknown; content?: unknown; message?: unknown };
|
|
465
|
+
|
|
443
466
|
/** Best-effort plain text from a Grok tool result envelope (e.g. ReadFile.FileContent.raw_output, or a string). */
|
|
444
467
|
export function resultText(value: unknown, limit = 8000): string | undefined {
|
|
445
468
|
if (value == null) return undefined;
|
|
446
469
|
if (typeof value === 'string') return value.slice(0, limit);
|
|
447
470
|
const media = mediaPath(value);
|
|
448
471
|
if (media) return media;
|
|
449
|
-
const v = value as
|
|
472
|
+
const v = value as ResultEnvelope;
|
|
450
473
|
const nested = v.FileContent?.raw_output ?? v.FileContent?.content ?? v.output ?? v.stdout ?? v.text ?? v.content ?? v.message;
|
|
451
474
|
if (typeof nested === 'string') return nested.slice(0, limit);
|
|
452
|
-
if (Array.isArray(nested)) return nested.map((c) => (typeof c === 'string' ? c : c?.text ?? '')).join('').slice(0, limit) || undefined;
|
|
475
|
+
if (Array.isArray(nested)) return nested.map((c: unknown) => (typeof c === 'string' ? c : String((c as { text?: unknown } | null)?.text ?? ''))).join('').slice(0, limit) || undefined;
|
|
453
476
|
return JSON.stringify(value).slice(0, limit);
|
|
454
477
|
}
|
|
455
478
|
|