@runuai/host 0.9.164 → 0.9.166
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/images/standard/Dockerfile +1 -1
- package/lib/agent-cli.ts +28 -2
- package/lib/agents/codex.ts +156 -3
- package/lib/agents/grok.ts +82 -11
- package/package.json +1 -1
|
@@ -34,7 +34,7 @@ ENV ASDF_VERSION=v0.14.1
|
|
|
34
34
|
# freshness is requalified before any of them moves.
|
|
35
35
|
ENV UAI_CODEX_CLI_VERSION=0.157.0
|
|
36
36
|
ENV UAI_CURSOR_AGENT_VERSION=2026.08.25-3e8eec8
|
|
37
|
-
ENV UAI_GROK_CLI_VERSION=1.0.
|
|
37
|
+
ENV UAI_GROK_CLI_VERSION=1.0.67
|
|
38
38
|
|
|
39
39
|
# ---------------------------------------------------------------------------
|
|
40
40
|
# 1. System packages.
|
package/lib/agent-cli.ts
CHANGED
|
@@ -322,6 +322,29 @@ async function main() {
|
|
|
322
322
|
const launch = (a) => a.kind + (a.model ? "/" + a.model : "") + (a.effort ? "/" + a.effort : "");
|
|
323
323
|
if (action === "list") { out(await api("GET", "/api/agent/crew")); return; }
|
|
324
324
|
if (action === "status") { out(await api("GET", "/api/agent/crew-status")); return; }
|
|
325
|
+
if (action === "store") { out(await api("GET", "/api/agent/crew-store")); return; }
|
|
326
|
+
if (action === "suggest") {
|
|
327
|
+
// The Astronaut suggests a persona; a person creates it (ADR-157 addendum).
|
|
328
|
+
// From the Store (--template) or from scratch (--name and --brief).
|
|
329
|
+
if (!flags.why || !(flags.template || flags.name)) {
|
|
330
|
+
console.error('uai: crew suggest needs --template <slug> (see crew store), or --name and --brief for a new one, and --why "<reason>"');
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
// --skill repeats, and parseFlags keeps only the last, so read them off the raw argv.
|
|
334
|
+
const skills = [];
|
|
335
|
+
for (let i = 0; i < rest.length; i++) {
|
|
336
|
+
if (rest[i] === "--skill" && rest[i + 1]) skills.push(...rest[++i].split(",").filter(Boolean));
|
|
337
|
+
}
|
|
338
|
+
const body = { why: flags.why };
|
|
339
|
+
if (flags.template) body.template = flags.template;
|
|
340
|
+
for (const k of ["name", "role", "summary", "brief", "model", "effort"]) if (flags[k]) body[k] = flags[k];
|
|
341
|
+
if (flags.handle) body.handle = handleOf(flags.handle);
|
|
342
|
+
if (flags.engine) body.engine = flags.engine;
|
|
343
|
+
if (skills.length) body.skills = skills;
|
|
344
|
+
const r = await api("POST", "/api/agent/crew-suggest", body);
|
|
345
|
+
out("suggested " + r.suggestion.name + " (@" + r.suggestion.handle + "). " + r.message);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
325
348
|
if (action === "add") {
|
|
326
349
|
const personaId = pos[0];
|
|
327
350
|
if (!personaId || !flags.why) {
|
|
@@ -367,7 +390,7 @@ async function main() {
|
|
|
367
390
|
out("removed @" + id);
|
|
368
391
|
return;
|
|
369
392
|
}
|
|
370
|
-
console.error("uai: crew commands are list, status, add, model and remove (Astronaut only)");
|
|
393
|
+
console.error("uai: crew commands are list, status, store, suggest, add, model and remove (Astronaut only)");
|
|
371
394
|
process.exit(1);
|
|
372
395
|
}
|
|
373
396
|
const key = group + " " + (action || "");
|
|
@@ -572,8 +595,11 @@ async function main() {
|
|
|
572
595
|
" uai task history (list your past tasks)",
|
|
573
596
|
" uai task read <taskId> (full chat of a past task you were on)",
|
|
574
597
|
' uai dispatch @agent [@agent…] "instruction" (Secretary only)',
|
|
575
|
-
" uai crew list (Astronaut only: personas, engines, models, limits, roster)",
|
|
598
|
+
" uai crew list (Astronaut only: personas with summary, skills and permissions, engines, models, limits, roster)",
|
|
576
599
|
" uai crew status (Astronaut only: each agent's engine, model, account; account usage; credits)",
|
|
600
|
+
" uai crew store (Astronaut only: the Store's persona and skill templates, names and types only)",
|
|
601
|
+
' uai crew suggest --template <slug> --why "<reason>" (Astronaut only: propose a persona from the Store; a person creates it)',
|
|
602
|
+
' uai crew suggest --name <n> --brief <text> [--role <r>] [--summary <s>] [--handle <h>] [--engine <kind>] [--model <m>] [--effort <e>] [--skill <slug|id>]... --why "<reason>"',
|
|
577
603
|
' uai crew add <personaId> [--as <handle>] [--engine <kind>] [--model <m>] [--effort <e>] [--account <id|label|default>] --why "<reason>"',
|
|
578
604
|
' uai crew model @agent [--engine <kind>] [--model <m|default>] [--effort <e|default>] [--account <id|label|default>] --why "<reason>"',
|
|
579
605
|
' uai crew remove @agent --why "<reason>"',
|
package/lib/agents/codex.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* item/agentMessage/delta → message_delta
|
|
20
20
|
* item/completed (agentMessage) → message_complete
|
|
21
21
|
* item/completed (tool item) → tool_call
|
|
22
|
+
* thread/tokenUsage/updated → accumulated for its turn
|
|
22
23
|
* turn/completed → turn_complete (+ error if failed)
|
|
23
24
|
* error → error
|
|
24
25
|
*
|
|
@@ -49,6 +50,7 @@ import type {
|
|
|
49
50
|
AgentEventHandler,
|
|
50
51
|
AgentKind,
|
|
51
52
|
AgentSession,
|
|
53
|
+
AgentUsage,
|
|
52
54
|
RosterAgent,
|
|
53
55
|
} from "./types";
|
|
54
56
|
|
|
@@ -87,6 +89,120 @@ function str(v: unknown, fallback = ""): string {
|
|
|
87
89
|
return typeof v === "string" ? v : fallback;
|
|
88
90
|
}
|
|
89
91
|
|
|
92
|
+
function nonNegativeInt(v: unknown): number | undefined {
|
|
93
|
+
return typeof v === "number" &&
|
|
94
|
+
Number.isSafeInteger(v) &&
|
|
95
|
+
v >= 0
|
|
96
|
+
? v
|
|
97
|
+
: undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface CodexTokenBreakdown {
|
|
101
|
+
inputTokens: number;
|
|
102
|
+
outputTokens: number;
|
|
103
|
+
cachedInputTokens: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function codexTokenBreakdown(value: unknown): CodexTokenBreakdown | null {
|
|
107
|
+
if (!isObj(value)) return null;
|
|
108
|
+
const inputTokens = nonNegativeInt(value.inputTokens);
|
|
109
|
+
const outputTokens = nonNegativeInt(value.outputTokens);
|
|
110
|
+
const cachedInputTokens = nonNegativeInt(value.cachedInputTokens);
|
|
111
|
+
if (
|
|
112
|
+
inputTokens === undefined ||
|
|
113
|
+
outputTokens === undefined ||
|
|
114
|
+
cachedInputTokens === undefined
|
|
115
|
+
) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
inputTokens,
|
|
120
|
+
outputTokens,
|
|
121
|
+
cachedInputTokens,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function addBreakdowns(
|
|
126
|
+
left: CodexTokenBreakdown | undefined,
|
|
127
|
+
right: CodexTokenBreakdown,
|
|
128
|
+
): CodexTokenBreakdown {
|
|
129
|
+
return {
|
|
130
|
+
inputTokens: (left?.inputTokens ?? 0) + right.inputTokens,
|
|
131
|
+
outputTokens: (left?.outputTokens ?? 0) + right.outputTokens,
|
|
132
|
+
cachedInputTokens:
|
|
133
|
+
(left?.cachedInputTokens ?? 0) + right.cachedInputTokens,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function breakdownDelta(
|
|
138
|
+
current: CodexTokenBreakdown,
|
|
139
|
+
previous: CodexTokenBreakdown,
|
|
140
|
+
): CodexTokenBreakdown | null {
|
|
141
|
+
const delta = {
|
|
142
|
+
inputTokens: current.inputTokens - previous.inputTokens,
|
|
143
|
+
outputTokens: current.outputTokens - previous.outputTokens,
|
|
144
|
+
cachedInputTokens:
|
|
145
|
+
current.cachedInputTokens - previous.cachedInputTokens,
|
|
146
|
+
};
|
|
147
|
+
return Object.values(delta).every((value) => value >= 0) ? delta : null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Codex 0.157 emits one token update after every model call. `last` is that
|
|
152
|
+
* call, not the whole turn; `total` is the running thread total. A tool-using
|
|
153
|
+
* turn therefore emits several updates. Seed an unseen thread from `last`,
|
|
154
|
+
* then add positive `total` deltas so repeated snapshots add zero. Usage is
|
|
155
|
+
* kept by turn id and consumed exactly once at its turn/completed boundary.
|
|
156
|
+
*/
|
|
157
|
+
class CodexTurnUsage {
|
|
158
|
+
private readonly threadTotals = new Map<string, CodexTokenBreakdown>();
|
|
159
|
+
private readonly turns = new Map<string, CodexTokenBreakdown>();
|
|
160
|
+
|
|
161
|
+
observe(params: unknown): void {
|
|
162
|
+
if (!isObj(params)) return;
|
|
163
|
+
const threadId = str(params.threadId);
|
|
164
|
+
const turnId = str(params.turnId);
|
|
165
|
+
const tokenUsage = isObj(params.tokenUsage) ? params.tokenUsage : null;
|
|
166
|
+
if (!threadId || !turnId || !tokenUsage) return;
|
|
167
|
+
const last = codexTokenBreakdown(tokenUsage.last);
|
|
168
|
+
const total = codexTokenBreakdown(tokenUsage.total);
|
|
169
|
+
if (!last || !total) return;
|
|
170
|
+
|
|
171
|
+
const previousTotal = this.threadTotals.get(threadId);
|
|
172
|
+
const contribution = previousTotal
|
|
173
|
+
? breakdownDelta(total, previousTotal) ?? last
|
|
174
|
+
: last;
|
|
175
|
+
this.threadTotals.set(threadId, total);
|
|
176
|
+
this.turns.set(
|
|
177
|
+
turnId,
|
|
178
|
+
addBreakdowns(this.turns.get(turnId), contribution),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
complete(params: unknown, model: string | undefined): AgentUsage | undefined {
|
|
183
|
+
const p = isObj(params) ? params : {};
|
|
184
|
+
const turn = isObj(p.turn) ? p.turn : {};
|
|
185
|
+
const turnId = str(turn.id);
|
|
186
|
+
if (!turnId) return undefined;
|
|
187
|
+
const usage = this.turns.get(turnId);
|
|
188
|
+
this.turns.delete(turnId);
|
|
189
|
+
if (!usage) return undefined;
|
|
190
|
+
return {
|
|
191
|
+
...(model ? { model } : {}),
|
|
192
|
+
// OpenAI includes cached input inside inputTokens. Uai's shared shape,
|
|
193
|
+
// like Claude's, keeps fresh input and cache reads in separate columns.
|
|
194
|
+
// Codex 0.157 does not say whether cacheWriteInputTokens is already in
|
|
195
|
+
// inputTokens. Leave it in fresh input and do not also report a cache
|
|
196
|
+
// create until the protocol makes that relationship explicit.
|
|
197
|
+
inputTokens: Math.max(0, usage.inputTokens - usage.cachedInputTokens),
|
|
198
|
+
cacheReadTokens: usage.cachedInputTokens,
|
|
199
|
+
// reasoningOutputTokens is a subset of outputTokens in this protocol
|
|
200
|
+
// (totalTokens = inputTokens + outputTokens), so do not add it again.
|
|
201
|
+
outputTokens: usage.outputTokens,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
90
206
|
/** `item/completed` item types rendered as tool-call cards. */
|
|
91
207
|
const TOOL_ITEM_TYPES = new Set([
|
|
92
208
|
"commandExecution",
|
|
@@ -96,6 +212,10 @@ const TOOL_ITEM_TYPES = new Set([
|
|
|
96
212
|
"webSearch",
|
|
97
213
|
]);
|
|
98
214
|
|
|
215
|
+
function turnComplete(usage: AgentUsage | undefined): AgentEvent {
|
|
216
|
+
return usage ? { type: "turn_complete", usage } : { type: "turn_complete" };
|
|
217
|
+
}
|
|
218
|
+
|
|
99
219
|
/**
|
|
100
220
|
* Map one Codex JSON-RPC *notification* (method + params) to AgentEvents.
|
|
101
221
|
* Pure and total — an unrecognised method yields [].
|
|
@@ -104,6 +224,7 @@ export function mapCodexNotification(
|
|
|
104
224
|
method: string,
|
|
105
225
|
params: unknown,
|
|
106
226
|
now: number = Date.now(),
|
|
227
|
+
turnUsage?: AgentUsage,
|
|
107
228
|
): AgentEvent[] {
|
|
108
229
|
const p = isObj(params) ? params : {};
|
|
109
230
|
|
|
@@ -155,10 +276,10 @@ export function mapCodexNotification(
|
|
|
155
276
|
: { resetsAt: accountUsage.resetsAt }),
|
|
156
277
|
}
|
|
157
278
|
: { type: "error", message },
|
|
158
|
-
|
|
279
|
+
turnComplete(turnUsage),
|
|
159
280
|
];
|
|
160
281
|
}
|
|
161
|
-
return [
|
|
282
|
+
return [turnComplete(turnUsage)];
|
|
162
283
|
}
|
|
163
284
|
|
|
164
285
|
if (method === "error") {
|
|
@@ -468,6 +589,8 @@ export class CodexSession implements AgentSession {
|
|
|
468
589
|
private turnRunning = false;
|
|
469
590
|
private lastRateLimitsReadAt = 0;
|
|
470
591
|
private rateLimitsTimer: ReturnType<typeof setTimeout> | null = null;
|
|
592
|
+
private readonly turnUsage = new CodexTurnUsage();
|
|
593
|
+
private actualModel: string | undefined;
|
|
471
594
|
|
|
472
595
|
constructor(args: {
|
|
473
596
|
taskId: string;
|
|
@@ -637,6 +760,13 @@ export class CodexSession implements AgentSession {
|
|
|
637
760
|
// Notification (method, no id).
|
|
638
761
|
if ("method" in msg) {
|
|
639
762
|
const method = str(msg.method);
|
|
763
|
+
if (method === "thread/tokenUsage/updated") {
|
|
764
|
+
this.turnUsage.observe(msg.params);
|
|
765
|
+
}
|
|
766
|
+
const completedUsage =
|
|
767
|
+
method === "turn/completed"
|
|
768
|
+
? this.turnUsage.complete(msg.params, this.actualModel)
|
|
769
|
+
: undefined;
|
|
640
770
|
if (method === "turn/completed") {
|
|
641
771
|
this.turnRunning = false;
|
|
642
772
|
this.clearRateLimitsTimer();
|
|
@@ -649,9 +779,17 @@ export class CodexSession implements AgentSession {
|
|
|
649
779
|
if (isObj(thread) && typeof thread.id === "string") {
|
|
650
780
|
this.threadId = thread.id;
|
|
651
781
|
this.persistThreadId(thread.id);
|
|
782
|
+
if (typeof thread.model === "string" && thread.model.length > 0) {
|
|
783
|
+
this.actualModel = thread.model;
|
|
784
|
+
}
|
|
652
785
|
}
|
|
653
786
|
}
|
|
654
|
-
for (const ev of mapCodexNotification(
|
|
787
|
+
for (const ev of mapCodexNotification(
|
|
788
|
+
method,
|
|
789
|
+
msg.params,
|
|
790
|
+
Date.now(),
|
|
791
|
+
completedUsage,
|
|
792
|
+
)) {
|
|
655
793
|
this.emit(ev);
|
|
656
794
|
}
|
|
657
795
|
}
|
|
@@ -818,8 +956,14 @@ export class CodexSession implements AgentSession {
|
|
|
818
956
|
threadParams.developerInstructions = this.systemPreamble;
|
|
819
957
|
}
|
|
820
958
|
const result = await this.request("thread/start", threadParams);
|
|
959
|
+
if (isObj(result) && typeof result.model === "string") {
|
|
960
|
+
this.actualModel = result.model;
|
|
961
|
+
}
|
|
821
962
|
if (isObj(result) && isObj(result.thread)) {
|
|
822
963
|
const id = str(result.thread.id) || this.threadId;
|
|
964
|
+
if (!this.actualModel && typeof result.thread.model === "string") {
|
|
965
|
+
this.actualModel = result.thread.model;
|
|
966
|
+
}
|
|
823
967
|
this.threadId = id;
|
|
824
968
|
if (id) this.persistThreadId(id);
|
|
825
969
|
}
|
|
@@ -846,6 +990,15 @@ export class CodexSession implements AgentSession {
|
|
|
846
990
|
isObj(result) && isObj(result.thread) && str(result.thread.id)
|
|
847
991
|
? str(result.thread.id)
|
|
848
992
|
: threadId;
|
|
993
|
+
if (isObj(result) && typeof result.model === "string") {
|
|
994
|
+
this.actualModel = result.model;
|
|
995
|
+
} else if (
|
|
996
|
+
isObj(result) &&
|
|
997
|
+
isObj(result.thread) &&
|
|
998
|
+
typeof result.thread.model === "string"
|
|
999
|
+
) {
|
|
1000
|
+
this.actualModel = result.thread.model;
|
|
1001
|
+
}
|
|
849
1002
|
this.threadId = id;
|
|
850
1003
|
this.persistThreadId(id);
|
|
851
1004
|
return true;
|
package/lib/agents/grok.ts
CHANGED
|
@@ -15,11 +15,13 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Stream-json vocabulary (`--output-format streaming-json`, verified):
|
|
17
17
|
* {"type":"thought","data":"…"} → skipped (internal reasoning)
|
|
18
|
-
* {"type":"text","data":"…"}
|
|
19
|
-
* {"type":"
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* {"type":"text","data":"…"} → message_delta (streamed)
|
|
19
|
+
* {"type":"tool_call"|"tool_call_update",…} → safely skipped (no cards yet)
|
|
20
|
+
* {"type":"usage",…} → skipped (per model call)
|
|
21
|
+
* {"type":"available_commands",…} → skipped
|
|
22
|
+
* {"type":"end","stopReason","sessionId",…} → message_complete + turn usage/end
|
|
23
|
+
* Grok 1.0.67 surfaces tool calls, but tool cards are deliberately outside
|
|
24
|
+
* this adapter slice. The final `end.usage` aggregates the invocation.
|
|
23
25
|
*
|
|
24
26
|
* AUTH is the operator's Grok subscription: `~/.grok/{auth.json, config.toml}`
|
|
25
27
|
* is docker-cp'd into the container at task-up (the Codex/Kimi config-dir
|
|
@@ -49,6 +51,7 @@ import type {
|
|
|
49
51
|
AgentEventHandler,
|
|
50
52
|
AgentKind,
|
|
51
53
|
AgentSession,
|
|
54
|
+
AgentUsage,
|
|
52
55
|
RosterAgent,
|
|
53
56
|
} from "./types";
|
|
54
57
|
|
|
@@ -77,9 +80,68 @@ export interface MappedGrokLine {
|
|
|
77
80
|
end?: boolean;
|
|
78
81
|
/** The session id from the end event, for `-r` on the next turn. */
|
|
79
82
|
sessionId?: string;
|
|
83
|
+
/** Per-invocation usage from the final end event. */
|
|
84
|
+
usage?: AgentUsage;
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
|
|
87
|
+
function nonNegativeInt(value: unknown): number | undefined {
|
|
88
|
+
return typeof value === "number" &&
|
|
89
|
+
Number.isSafeInteger(value) &&
|
|
90
|
+
value >= 0
|
|
91
|
+
? value
|
|
92
|
+
: undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function grokEndUsage(
|
|
96
|
+
msg: Record<string, unknown>,
|
|
97
|
+
model: string | undefined,
|
|
98
|
+
): AgentUsage | undefined {
|
|
99
|
+
if (typeof msg.usage !== "object" || msg.usage === null) return undefined;
|
|
100
|
+
const raw = msg.usage as Record<string, unknown>;
|
|
101
|
+
const inputTokens = nonNegativeInt(raw.input_tokens);
|
|
102
|
+
const cacheReadTokens = nonNegativeInt(raw.cache_read_input_tokens);
|
|
103
|
+
const cacheCreateTokens = nonNegativeInt(raw.cache_creation_input_tokens);
|
|
104
|
+
const outputTokens = nonNegativeInt(raw.output_tokens);
|
|
105
|
+
const reasoningTokens = nonNegativeInt(raw.reasoning_tokens);
|
|
106
|
+
if (
|
|
107
|
+
inputTokens === undefined ||
|
|
108
|
+
cacheReadTokens === undefined ||
|
|
109
|
+
cacheCreateTokens === undefined ||
|
|
110
|
+
outputTokens === undefined ||
|
|
111
|
+
reasoningTokens === undefined
|
|
112
|
+
) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
const reportedCost = msg.total_cost_usd;
|
|
116
|
+
const costUsd =
|
|
117
|
+
typeof reportedCost === "number" &&
|
|
118
|
+
Number.isFinite(reportedCost) &&
|
|
119
|
+
reportedCost >= 0
|
|
120
|
+
? reportedCost
|
|
121
|
+
: undefined;
|
|
122
|
+
return {
|
|
123
|
+
// `modelUsage` exposes the backend build id (for example
|
|
124
|
+
// grok-4.5-build), while `model` is the semantic id passed to `-m` and
|
|
125
|
+
// therefore the id Uai's model guide resolves.
|
|
126
|
+
...(model ? { model } : {}),
|
|
127
|
+
// Headless input_tokens is already uncached; both cache buckets are
|
|
128
|
+
// additive according to the 1.0.67 headless contract.
|
|
129
|
+
inputTokens,
|
|
130
|
+
cacheReadTokens,
|
|
131
|
+
cacheCreateTokens,
|
|
132
|
+
// The documented total excludes reasoning_tokens even though it includes
|
|
133
|
+
// output_tokens. Uai has no separate reasoning column, so include that
|
|
134
|
+
// distinct billed output once here.
|
|
135
|
+
outputTokens: outputTokens + reasoningTokens,
|
|
136
|
+
// Missing cost means unreported/partial, never free.
|
|
137
|
+
...(costUsd === undefined ? {} : { costUsd }),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function mapGrokLine(
|
|
142
|
+
line: string,
|
|
143
|
+
model?: string,
|
|
144
|
+
): MappedGrokLine {
|
|
83
145
|
const trimmed = line.trim();
|
|
84
146
|
if (!trimmed.startsWith("{")) return {};
|
|
85
147
|
let msg: Record<string, unknown>;
|
|
@@ -92,9 +154,11 @@ export function mapGrokLine(line: string): MappedGrokLine {
|
|
|
92
154
|
return { textDelta: msg.data };
|
|
93
155
|
}
|
|
94
156
|
if (msg.type === "end") {
|
|
157
|
+
const usage = grokEndUsage(msg, model);
|
|
95
158
|
return {
|
|
96
159
|
end: true,
|
|
97
160
|
sessionId: typeof msg.sessionId === "string" ? msg.sessionId : undefined,
|
|
161
|
+
...(usage ? { usage } : {}),
|
|
98
162
|
};
|
|
99
163
|
}
|
|
100
164
|
// "thought" (reasoning) and any other types: no user-facing event.
|
|
@@ -196,9 +260,10 @@ export class GrokSession implements AgentSession {
|
|
|
196
260
|
// the same reasoning as --permission-mode bypassPermissions above
|
|
197
261
|
// (live 2026-09-08, first Grok agents with MCP servers).
|
|
198
262
|
GROK_FOLDER_TRUST: "0",
|
|
199
|
-
//
|
|
200
|
-
// reuses
|
|
201
|
-
// tasks refresh per command, so
|
|
263
|
+
// Grok 1.0.67 still enables login-shell capture by default and honors
|
|
264
|
+
// GROK_LOGIN_ENV. That capture reuses static state for
|
|
265
|
+
// LocalNonPersistent commands; loader tasks refresh per command, so
|
|
266
|
+
// keep the stale snapshot path disabled.
|
|
202
267
|
...(this.environment.descriptor.envMode === "loader"
|
|
203
268
|
? { GROK_LOGIN_ENV: GROK_LOADER_LOGIN_ENV }
|
|
204
269
|
: {}),
|
|
@@ -215,6 +280,7 @@ export class GrokSession implements AgentSession {
|
|
|
215
280
|
|
|
216
281
|
let acc = "";
|
|
217
282
|
let sawText = false;
|
|
283
|
+
let turnUsage: AgentUsage | undefined;
|
|
218
284
|
let buf = "";
|
|
219
285
|
const consume = (chunk: string): void => {
|
|
220
286
|
buf += chunk;
|
|
@@ -222,8 +288,9 @@ export class GrokSession implements AgentSession {
|
|
|
222
288
|
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
223
289
|
const line = buf.slice(0, nl);
|
|
224
290
|
buf = buf.slice(nl + 1);
|
|
225
|
-
const m = mapGrokLine(line);
|
|
291
|
+
const m = mapGrokLine(line, this.model);
|
|
226
292
|
if (m.sessionId) this.sessionId = m.sessionId;
|
|
293
|
+
if (m.end) turnUsage = m.usage;
|
|
227
294
|
if (typeof m.textDelta === "string") {
|
|
228
295
|
sawText = true;
|
|
229
296
|
acc += m.textDelta;
|
|
@@ -253,7 +320,11 @@ export class GrokSession implements AgentSession {
|
|
|
253
320
|
}
|
|
254
321
|
// Finalize the streamed message (no-op text if the turn produced none).
|
|
255
322
|
if (sawText) this.emit({ type: "message_complete", text: acc });
|
|
256
|
-
this.emit(
|
|
323
|
+
this.emit(
|
|
324
|
+
turnUsage
|
|
325
|
+
? { type: "turn_complete", usage: turnUsage }
|
|
326
|
+
: { type: "turn_complete" },
|
|
327
|
+
);
|
|
257
328
|
resolve();
|
|
258
329
|
};
|
|
259
330
|
child.on("exit", (code) => finish(code));
|