@gaia-ai/addon-grok 0.9.0
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/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/src/footprint.d.ts +2 -0
- package/dist/src/footprint.js +120 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/src/index.js +119 -0
- package/dist/src/preset.d.ts +2 -0
- package/dist/src/preset.js +5 -0
- package/package.json +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 keytec GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { emptyAgentFootprint, } from '@gaia-ai/conductor/contract';
|
|
2
|
+
function object(value) {
|
|
3
|
+
return typeof value === 'object' && value !== null
|
|
4
|
+
? value
|
|
5
|
+
: undefined;
|
|
6
|
+
}
|
|
7
|
+
function count(value) {
|
|
8
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0
|
|
9
|
+
? value
|
|
10
|
+
: 0;
|
|
11
|
+
}
|
|
12
|
+
function optionalCount(value) {
|
|
13
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0
|
|
14
|
+
? value
|
|
15
|
+
: undefined;
|
|
16
|
+
}
|
|
17
|
+
function words(text) {
|
|
18
|
+
const trimmed = text.trim();
|
|
19
|
+
return trimmed === '' ? 0 : trimmed.split(/\s+/).length;
|
|
20
|
+
}
|
|
21
|
+
function jsonLines(value) {
|
|
22
|
+
if (typeof value !== 'string')
|
|
23
|
+
return [];
|
|
24
|
+
const rows = [];
|
|
25
|
+
for (const raw of value.split('\n')) {
|
|
26
|
+
if (raw.trim() === '')
|
|
27
|
+
continue;
|
|
28
|
+
try {
|
|
29
|
+
const row = object(JSON.parse(raw));
|
|
30
|
+
if (row)
|
|
31
|
+
rows.push(row);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Grok may leave a partial final JSONL line while finalization races it.
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return rows;
|
|
38
|
+
}
|
|
39
|
+
function timestampMs(value) {
|
|
40
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
41
|
+
return value < 1_000_000_000_000 ? value * 1000 : value;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === 'string') {
|
|
44
|
+
const parsed = Date.parse(value);
|
|
45
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
export function parseGrokTranscript(log) {
|
|
50
|
+
const footprint = emptyAgentFootprint();
|
|
51
|
+
let bundle;
|
|
52
|
+
try {
|
|
53
|
+
bundle = object(JSON.parse(log)) ?? {};
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return footprint;
|
|
57
|
+
}
|
|
58
|
+
const summary = object(bundle.summary);
|
|
59
|
+
const signals = object(bundle.signals);
|
|
60
|
+
const signalAgentTurns = optionalCount(signals?.assistantMessageCount);
|
|
61
|
+
const signalToolCalls = optionalCount(signals?.toolCallCount);
|
|
62
|
+
const signalUserPrompts = optionalCount(signals?.userMessageCount);
|
|
63
|
+
if (typeof summary?.current_model_id === 'string') {
|
|
64
|
+
footprint.model = summary.current_model_id;
|
|
65
|
+
}
|
|
66
|
+
let chatAgentTurns = 0;
|
|
67
|
+
let chatToolCalls = 0;
|
|
68
|
+
let chatUserPrompts = 0;
|
|
69
|
+
for (const row of jsonLines(bundle.chatHistory)) {
|
|
70
|
+
if (row.type === 'assistant') {
|
|
71
|
+
chatAgentTurns += 1;
|
|
72
|
+
if (Array.isArray(row.tool_calls)) {
|
|
73
|
+
chatToolCalls += row.tool_calls.length;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (row.type === 'user' && typeof row.prompt_index === 'number') {
|
|
77
|
+
chatUserPrompts += 1;
|
|
78
|
+
if (Array.isArray(row.content)) {
|
|
79
|
+
for (const rawPart of row.content) {
|
|
80
|
+
const part = object(rawPart);
|
|
81
|
+
if (part?.type === 'text' && typeof part.text === 'string') {
|
|
82
|
+
footprint.user_prompt_words += words(part.text);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
footprint.agent_turns = signalAgentTurns ?? chatAgentTurns;
|
|
89
|
+
footprint.tool_calls = signalToolCalls ?? chatToolCalls;
|
|
90
|
+
footprint.user_prompts = signalUserPrompts ?? chatUserPrompts;
|
|
91
|
+
let min = Number.POSITIVE_INFINITY;
|
|
92
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
93
|
+
let completedTokens = 0;
|
|
94
|
+
let completedTotals = 0;
|
|
95
|
+
let latestTotalTokens = 0;
|
|
96
|
+
for (const row of jsonLines(bundle.updates)) {
|
|
97
|
+
const timestamp = timestampMs(row.timestamp);
|
|
98
|
+
if (timestamp !== undefined) {
|
|
99
|
+
min = Math.min(min, timestamp);
|
|
100
|
+
max = Math.max(max, timestamp);
|
|
101
|
+
}
|
|
102
|
+
const params = object(row.params);
|
|
103
|
+
const meta = object(params?._meta);
|
|
104
|
+
latestTotalTokens = Math.max(latestTotalTokens, count(meta?.totalTokens));
|
|
105
|
+
const update = object(params?.update);
|
|
106
|
+
if (update?.sessionUpdate === 'turn_completed') {
|
|
107
|
+
const usage = object(update.usage);
|
|
108
|
+
const totalTokens = optionalCount(usage?.totalTokens);
|
|
109
|
+
if (totalTokens !== undefined) {
|
|
110
|
+
completedTokens += totalTokens;
|
|
111
|
+
completedTotals += 1;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
footprint.tokens = completedTotals > 0 ? completedTokens : latestTotalTokens;
|
|
116
|
+
if (Number.isFinite(min) && Number.isFinite(max) && max > min) {
|
|
117
|
+
footprint.duration_s = Math.floor((max - min) / 1000);
|
|
118
|
+
}
|
|
119
|
+
return footprint;
|
|
120
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AgentFootprint, AgentPlugin, GaiaAgent } from '@gaia-ai/conductor/contract';
|
|
2
|
+
export { parseGrokTranscript } from './footprint.js';
|
|
3
|
+
export type GrokReasoningEffort = 'high' | 'medium' | 'low';
|
|
4
|
+
export interface GrokAgentOptions {
|
|
5
|
+
model: string;
|
|
6
|
+
reasoningEffort?: GrokReasoningEffort;
|
|
7
|
+
}
|
|
8
|
+
export declare function encodeGrokSessionCwd(cwd: string): string;
|
|
9
|
+
export declare class GrokAgent implements GaiaAgent {
|
|
10
|
+
private readonly options;
|
|
11
|
+
private readonly home;
|
|
12
|
+
readonly id = "grok";
|
|
13
|
+
constructor(options?: GrokAgentOptions, home?: string);
|
|
14
|
+
launchCommand(prompt: string): string;
|
|
15
|
+
getRunLog(worktreePath: string): Promise<string>;
|
|
16
|
+
parseFootprint(log: string): AgentFootprint;
|
|
17
|
+
}
|
|
18
|
+
export declare function grokAgent(options?: GrokAgentOptions): AgentPlugin;
|
|
19
|
+
export default grokAgent;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { shellQuote } from '@gaia-ai/core';
|
|
5
|
+
import { parseGrokTranscript } from './footprint.js';
|
|
6
|
+
export { parseGrokTranscript } from './footprint.js';
|
|
7
|
+
const DEFAULT_OPTIONS = { model: 'grok-4.5' };
|
|
8
|
+
export function encodeGrokSessionCwd(cwd) {
|
|
9
|
+
return encodeURIComponent(cwd);
|
|
10
|
+
}
|
|
11
|
+
async function readText(path) {
|
|
12
|
+
try {
|
|
13
|
+
return await readFile(path, 'utf8');
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function readJson(path) {
|
|
20
|
+
try {
|
|
21
|
+
const value = JSON.parse(await readFile(path, 'utf8'));
|
|
22
|
+
return typeof value === 'object' && value !== null
|
|
23
|
+
? value
|
|
24
|
+
: null;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class GrokAgent {
|
|
31
|
+
options;
|
|
32
|
+
home;
|
|
33
|
+
id = 'grok';
|
|
34
|
+
constructor(options = DEFAULT_OPTIONS, home = homedir()) {
|
|
35
|
+
this.options = options;
|
|
36
|
+
this.home = home;
|
|
37
|
+
}
|
|
38
|
+
launchCommand(prompt) {
|
|
39
|
+
const parts = ['grok'];
|
|
40
|
+
if (prompt !== '') {
|
|
41
|
+
// Positional — the interactive form Grok Build documents
|
|
42
|
+
// (`grok "fix the bug"`). NOT `-p/--single`: that prints one answer to
|
|
43
|
+
// stdout and exits, so the agent would drop the herdr tab it is
|
|
44
|
+
// dispatched into instead of holding a session there.
|
|
45
|
+
parts.push(shellQuote(prompt));
|
|
46
|
+
}
|
|
47
|
+
parts.push('--permission-mode', 'bypassPermissions', '-m', this.options.model);
|
|
48
|
+
if (this.options.reasoningEffort) {
|
|
49
|
+
parts.push('--reasoning-effort', this.options.reasoningEffort);
|
|
50
|
+
}
|
|
51
|
+
return parts.join(' ');
|
|
52
|
+
}
|
|
53
|
+
async getRunLog(worktreePath) {
|
|
54
|
+
const root = join(this.home, '.grok', 'sessions', encodeGrokSessionCwd(worktreePath));
|
|
55
|
+
let names;
|
|
56
|
+
try {
|
|
57
|
+
names = await readdir(root);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
let latest;
|
|
63
|
+
for (const name of names) {
|
|
64
|
+
const dir = join(root, name);
|
|
65
|
+
const summary = await readJson(join(dir, 'summary.json'));
|
|
66
|
+
if (!summary) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const info = summary?.info;
|
|
70
|
+
if (typeof info !== 'object' ||
|
|
71
|
+
info === null ||
|
|
72
|
+
info.cwd !== worktreePath) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
let updated = typeof summary?.updated_at === 'string'
|
|
76
|
+
? Date.parse(summary.updated_at)
|
|
77
|
+
: Number.NaN;
|
|
78
|
+
if (!Number.isFinite(updated)) {
|
|
79
|
+
try {
|
|
80
|
+
updated = (await stat(dir)).mtimeMs;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!latest || updated > latest.updated) {
|
|
87
|
+
latest = { dir, summary, updated };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!latest)
|
|
91
|
+
return '';
|
|
92
|
+
const [signals, chatHistory, updates] = await Promise.all([
|
|
93
|
+
readJson(join(latest.dir, 'signals.json')),
|
|
94
|
+
readText(join(latest.dir, 'chat_history.jsonl')),
|
|
95
|
+
readText(join(latest.dir, 'updates.jsonl')),
|
|
96
|
+
]);
|
|
97
|
+
return JSON.stringify({
|
|
98
|
+
summary: latest.summary,
|
|
99
|
+
signals: signals ?? {},
|
|
100
|
+
chatHistory,
|
|
101
|
+
updates,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
parseFootprint(log) {
|
|
105
|
+
return parseGrokTranscript(log);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function grokAgent(options = DEFAULT_OPTIONS) {
|
|
109
|
+
const agent = new GrokAgent(options);
|
|
110
|
+
return {
|
|
111
|
+
kind: 'agent',
|
|
112
|
+
id: 'grok',
|
|
113
|
+
requiredModules: [],
|
|
114
|
+
async createAgent() {
|
|
115
|
+
return agent;
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export default grokAgent;
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gaia-ai/addon-grok",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "GAIA agent plugin for xAI Grok Build, with a real session footprint.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/src/index.js",
|
|
9
|
+
"./preset": "./dist/src/preset.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/src"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://git.key-tec.de/keytec/gaia.git",
|
|
20
|
+
"directory": "gaia-cli/addons/grok"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"@gaia-ai/conductor": "^0.9.0",
|
|
24
|
+
"@gaia-ai/core": "^0.9.0"
|
|
25
|
+
}
|
|
26
|
+
}
|