@dsiloed/silo-link 1.11.0 → 1.12.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/README.md +50 -5
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +58 -2
- package/dist/cli/commands.js.map +1 -1
- package/dist/core/bridge.d.ts.map +1 -1
- package/dist/core/bridge.js +20 -8
- package/dist/core/bridge.js.map +1 -1
- package/dist/core/codex-launcher.d.ts +85 -0
- package/dist/core/codex-launcher.d.ts.map +1 -0
- package/dist/core/codex-launcher.js +280 -0
- package/dist/core/codex-launcher.js.map +1 -0
- package/dist/core/launcher-factory.d.ts +1 -1
- package/dist/core/launcher-factory.d.ts.map +1 -1
- package/dist/core/launcher-factory.js +4 -4
- package/dist/core/launcher-factory.js.map +1 -1
- package/dist/core/report-usage.d.ts +51 -0
- package/dist/core/report-usage.d.ts.map +1 -0
- package/dist/core/report-usage.js +112 -0
- package/dist/core/report-usage.js.map +1 -0
- package/dist/core/transcript-usage.d.ts +36 -0
- package/dist/core/transcript-usage.d.ts.map +1 -1
- package/dist/core/transcript-usage.js +190 -0
- package/dist/core/transcript-usage.js.map +1 -1
- package/dist/core/workspace-manager.d.ts.map +1 -1
- package/dist/core/workspace-manager.js +21 -1
- package/dist/core/workspace-manager.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/types/index.d.ts +8 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, copyFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { BaseTmuxLauncher } from './base-tmux-launcher.js';
|
|
5
|
+
import { locateCodexTranscript, parseCodexTranscript, codexCostBasis, } from './transcript-usage.js';
|
|
6
|
+
// Env var the codex MCP config reads the dsiloed bearer token from. Codex's
|
|
7
|
+
// `bearer_token_env_var` mechanism keeps the JWT OUT of config.toml on disk.
|
|
8
|
+
const DSILOED_TOKEN_ENV = 'SILOLINK_DSILOED_TOKEN';
|
|
9
|
+
/**
|
|
10
|
+
* Codex CLI (OpenAI) implementation of the AgentLauncher interface.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors GeminiLauncher's tmux-based lifecycle with Codex-specific bits:
|
|
13
|
+
* - Command: `codex` (override via config.codex_command)
|
|
14
|
+
* - Auto-approval: `--dangerously-bypass-approvals-and-sandbox` so a headless
|
|
15
|
+
* tmux session never wedges on an approval/sandbox prompt (override via
|
|
16
|
+
* config.codex_args).
|
|
17
|
+
* - MCP: codex's NATIVE streamable-HTTP MCP support — `[mcp_servers.*]` tables
|
|
18
|
+
* with `url` (+ `bearer_token_env_var` for the dsiloed JWT) in
|
|
19
|
+
* `$CODEX_HOME/config.toml`. No `npx mcp-remote` bridge needed (unlike
|
|
20
|
+
* Claude/Gemini). We point CODEX_HOME at a per-env dir (so each daemon identity
|
|
21
|
+
* owns its own MCP config, the same isolation Claude/Gemini get via staging
|
|
22
|
+
* cwd) and copy the user's `auth.json` into it so the codex login carries over.
|
|
23
|
+
* - Instructions file: `AGENTS.md` (codex's analogue of CLAUDE.md / GEMINI.md).
|
|
24
|
+
* - MCP tool names: codex prefixes as `<server>__<tool>` (e.g.
|
|
25
|
+
* `silolink__remote_register`, `silolink_server__conversation_chat_tool`).
|
|
26
|
+
* - Usage: parsed from the rollout under ~/.codex/sessions (see transcript-usage).
|
|
27
|
+
*
|
|
28
|
+
* Validated against codex-cli 0.141.0:
|
|
29
|
+
* ✓ `--dangerously-bypass-approvals-and-sandbox` ✓ `codex resume <id>`
|
|
30
|
+
* ✓ `[mcp_servers.*]` url + bearer_token_env_var ✓ CODEX_HOME respected
|
|
31
|
+
* ✓ `<server>__<tool>` MCP tool naming (default; non_prefixed_mcp_tool_names off)
|
|
32
|
+
* Flags/command stay overridable via config for forward-compat across versions;
|
|
33
|
+
* the base launcher's pane watcher surfaces `error: unknown option …` on drift.
|
|
34
|
+
*/
|
|
35
|
+
export class CodexLauncher extends BaseTmuxLauncher {
|
|
36
|
+
constructor(config, sessionManager, dsiloedClient, messageQueue, workspaceManager) {
|
|
37
|
+
super(config, sessionManager, dsiloedClient, messageQueue, workspaceManager);
|
|
38
|
+
}
|
|
39
|
+
// --- Abstract implementation ---
|
|
40
|
+
getCommand() {
|
|
41
|
+
return this.config.codex_command || 'codex';
|
|
42
|
+
}
|
|
43
|
+
// Empty — codex's arg ORDER matters (the `resume` subcommand must come first),
|
|
44
|
+
// so buildLaunchPrompt() owns the full arg list incl. the auto-approval flags.
|
|
45
|
+
getArgs() {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
// codex-cli 0.141.0: validated flag (skips approvals + sandbox for headless run).
|
|
49
|
+
codexFlags() {
|
|
50
|
+
return this.config.codex_args ?? ['--dangerously-bypass-approvals-and-sandbox'];
|
|
51
|
+
}
|
|
52
|
+
// Best-effort match for the codex TUI's ready state. If a codex version prints
|
|
53
|
+
// none of these the base launcher still sends the prompt after its ready
|
|
54
|
+
// timeout, so a miss degrades to a small delay rather than a wedge.
|
|
55
|
+
getReadyIndicator() {
|
|
56
|
+
return ['Codex', 'codex', 'send a message', 'To get started', 'esc to interrupt', '▌'];
|
|
57
|
+
}
|
|
58
|
+
formatPrompt(prompt) {
|
|
59
|
+
return this.adaptPromptForCodex(prompt);
|
|
60
|
+
}
|
|
61
|
+
async setupMcpConfig(cwd, toolAllowlist) {
|
|
62
|
+
if (!this.config.jwt) {
|
|
63
|
+
throw new Error('No JWT in config. Run "silolink config" to log in.');
|
|
64
|
+
}
|
|
65
|
+
// The dsiloed MCP URL pointing at the same server SiloLink connects to.
|
|
66
|
+
let dsiloedUrl = `${this.config.host}/api/v1/mcp?categories=data,admin,conversation,utility`;
|
|
67
|
+
if (toolAllowlist && toolAllowlist.length > 0) {
|
|
68
|
+
const url = new URL(`${this.config.host}/api/v1/mcp`);
|
|
69
|
+
url.searchParams.set('tools', toolAllowlist.join(','));
|
|
70
|
+
dsiloedUrl = url.toString();
|
|
71
|
+
}
|
|
72
|
+
const codexHome = this.codexHome();
|
|
73
|
+
const configPath = join(codexHome, 'config.toml');
|
|
74
|
+
try {
|
|
75
|
+
mkdirSync(codexHome, { recursive: true });
|
|
76
|
+
this.ensureCodexAuth(codexHome);
|
|
77
|
+
// SiloLink OWNS this per-env config.toml — regenerate the MCP block each
|
|
78
|
+
// launch. Preserve any non-mcp_servers lines the user added, but drop prior
|
|
79
|
+
// [mcp_servers.*] tables so stale config can't linger. Codex talks to both
|
|
80
|
+
// servers over native streamable HTTP (no mcp-remote); the dsiloed JWT is
|
|
81
|
+
// read from an env var (DSILOED_TOKEN_ENV), never written to disk.
|
|
82
|
+
const preserved = existsSync(configPath)
|
|
83
|
+
? stripMcpServers(readFileSync(configPath, 'utf-8'))
|
|
84
|
+
: '';
|
|
85
|
+
const mcpToml = [
|
|
86
|
+
'# Managed by SiloLink — MCP servers (regenerated each launch).',
|
|
87
|
+
'[mcp_servers.silolink]',
|
|
88
|
+
`url = ${tomlStr(`http://localhost:${this.config.mcp_port}/mcp`)}`,
|
|
89
|
+
'',
|
|
90
|
+
'[mcp_servers.silolink_server]',
|
|
91
|
+
`url = ${tomlStr(dsiloedUrl)}`,
|
|
92
|
+
`bearer_token_env_var = ${tomlStr(DSILOED_TOKEN_ENV)}`,
|
|
93
|
+
'',
|
|
94
|
+
].join('\n');
|
|
95
|
+
const body = preserved.trim() ? `${preserved.trimEnd()}\n\n${mcpToml}` : mcpToml;
|
|
96
|
+
writeFileSync(configPath, body);
|
|
97
|
+
console.log(`[CodexLauncher] Configured MCP servers in ${configPath} (CODEX_HOME=${codexHome}, host: ${this.config.host}, cwd: ${cwd})`);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.error(`[CodexLauncher] Failed to configure ${configPath}: ${err}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
checkEnabled() {
|
|
104
|
+
return !!this.config.projects_path;
|
|
105
|
+
}
|
|
106
|
+
getWorkingDirectory() {
|
|
107
|
+
// Mirror Claude/Gemini: prefer the per-env staging dir as the default cwd.
|
|
108
|
+
const cwd = this.config.staging_path || this.config.projects_path || process.cwd();
|
|
109
|
+
if (this.config.staging_path && !existsSync(cwd)) {
|
|
110
|
+
try {
|
|
111
|
+
mkdirSync(cwd, { recursive: true });
|
|
112
|
+
console.log(`[CodexLauncher] Created staging dir ${cwd}`);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
console.warn(`[CodexLauncher] Failed to create staging dir ${cwd}: ${err}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return cwd;
|
|
119
|
+
}
|
|
120
|
+
isAutoRespawnConfigured() {
|
|
121
|
+
return !!this.config.codex_auto_respawn;
|
|
122
|
+
}
|
|
123
|
+
getIdleTimeoutMs() {
|
|
124
|
+
return this.config.codex_idle_timeout_ms || 30000;
|
|
125
|
+
}
|
|
126
|
+
getIdleKillAfterMs() {
|
|
127
|
+
return this.config.codex_idle_kill_after_ms ?? 0;
|
|
128
|
+
}
|
|
129
|
+
// Codex's TUI treats a multi-line paste as interactive input; send prompts in
|
|
130
|
+
// bracketed-paste mode like Gemini to avoid tripping shell/slash handling.
|
|
131
|
+
usesBracketedPaste() {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
getSessionPrompt() {
|
|
135
|
+
return this.config.codex_session_prompt;
|
|
136
|
+
}
|
|
137
|
+
getAgentEnv() {
|
|
138
|
+
return {
|
|
139
|
+
SILOLINK_MCP_URL: `http://localhost:${this.config.mcp_port}/mcp`,
|
|
140
|
+
SILOLINK_AGENT_PROVIDER: 'codex',
|
|
141
|
+
// Point codex at the per-env home so it reads OUR config.toml + the
|
|
142
|
+
// carried-over auth.json (and writes refreshed tokens there).
|
|
143
|
+
CODEX_HOME: this.codexHome(),
|
|
144
|
+
// The dsiloed MCP server's bearer token, referenced by config.toml's
|
|
145
|
+
// bearer_token_env_var — keeps the JWT out of the on-disk config.
|
|
146
|
+
[DSILOED_TOKEN_ENV]: this.config.jwt,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
getTmuxPrefix() {
|
|
150
|
+
return 'silolink-codex';
|
|
151
|
+
}
|
|
152
|
+
getLogTag() {
|
|
153
|
+
return '[CodexLauncher]';
|
|
154
|
+
}
|
|
155
|
+
getCostMetadataKey() {
|
|
156
|
+
return 'codex_session_cost_usd';
|
|
157
|
+
}
|
|
158
|
+
// Token-level usage from the codex rollout on disk, located by the launch cwd
|
|
159
|
+
// (-> matching session_meta) + the resumed session id. cost_basis reflects the
|
|
160
|
+
// live codex auth mode (API key = real $, ChatGPT sign-in = imputed).
|
|
161
|
+
extractTranscriptUsage(cwd) {
|
|
162
|
+
const transcriptPath = locateCodexTranscript(cwd, this.lastSessionId);
|
|
163
|
+
if (!transcriptPath)
|
|
164
|
+
return null;
|
|
165
|
+
const usage = parseCodexTranscript(readFileSync(transcriptPath, 'utf-8'));
|
|
166
|
+
if (usage.length === 0)
|
|
167
|
+
return null;
|
|
168
|
+
return { cli_type: 'codex', cost_basis: codexCostBasis(), usage };
|
|
169
|
+
}
|
|
170
|
+
buildLaunchPrompt(reason, conversationId, agentContext, workspaceContext) {
|
|
171
|
+
const flags = this.codexFlags();
|
|
172
|
+
// codex-cli 0.141.0: `codex resume <SESSION_ID>` (UUID) accepts the sandbox/
|
|
173
|
+
// approval flags after it. Disabled by default (codex_resume_enabled) — a
|
|
174
|
+
// fresh session reconstructs context via remote_load_context, also fine.
|
|
175
|
+
const canResume = this.config.codex_resume_enabled && this.lastSessionId &&
|
|
176
|
+
(reason === 'process-exit' || reason === 'inbound-message');
|
|
177
|
+
if (canResume) {
|
|
178
|
+
return {
|
|
179
|
+
prompt: 'Continue polling SiloLink for messages.' + workspaceContext,
|
|
180
|
+
extraArgs: ['resume', this.lastSessionId, ...flags],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (reason === 'process-exit' || reason === 'inbound-message') {
|
|
184
|
+
const agentIdParam = this.pendingLlmAgentId ? `, "llm_agent_id": ${this.pendingLlmAgentId}` : '';
|
|
185
|
+
this.pendingLlmAgentId = null;
|
|
186
|
+
const registerParams = conversationId
|
|
187
|
+
? `{ "conversation_id": ${conversationId}${agentIdParam} }`
|
|
188
|
+
: `{${agentIdParam ? agentIdParam.substring(2) : ''}}`;
|
|
189
|
+
const triggerLine = this.pendingTriggerMessage
|
|
190
|
+
? `Pending user message (address after load_context): "${this.pendingTriggerMessage}"`
|
|
191
|
+
: '';
|
|
192
|
+
this.pendingTriggerMessage = null;
|
|
193
|
+
return {
|
|
194
|
+
prompt: [
|
|
195
|
+
`Fresh session (no resume). Call silolink__remote_register with ${registerParams}, then follow the SiloLink section in AGENTS.md (call silolink__remote_load_context once, then enter the poll loop).`,
|
|
196
|
+
triggerLine,
|
|
197
|
+
agentContext,
|
|
198
|
+
workspaceContext,
|
|
199
|
+
].filter(Boolean).join('\n'),
|
|
200
|
+
extraArgs: [...flags],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
prompt: (this.config.codex_session_prompt || this.config.claude_session_prompt ||
|
|
205
|
+
'Register with SiloLink and enter the poll loop.') + agentContext + workspaceContext,
|
|
206
|
+
extraArgs: [...flags],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// --- Codex-specific ---
|
|
210
|
+
/**
|
|
211
|
+
* Per-env CODEX_HOME. When a staging_path is configured (per-env identity
|
|
212
|
+
* isolation), nest the codex home under it so each daemon owns its own
|
|
213
|
+
* config.toml + JWT; otherwise fall back to the user's default ~/.codex
|
|
214
|
+
* (single-identity machines).
|
|
215
|
+
*/
|
|
216
|
+
codexHome() {
|
|
217
|
+
if (this.config.staging_path)
|
|
218
|
+
return join(this.config.staging_path, '.codex-home');
|
|
219
|
+
return process.env.CODEX_HOME || join(homedir(), '.codex');
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Copy the user's codex auth (~/.codex/auth.json) into the per-env CODEX_HOME
|
|
223
|
+
* so the login carries over. No-op when not isolating (codexHome === ~/.codex)
|
|
224
|
+
* or when the source/target are the same. Failure-tolerant — codex will simply
|
|
225
|
+
* prompt for login if auth can't be copied.
|
|
226
|
+
*/
|
|
227
|
+
ensureCodexAuth(codexHome) {
|
|
228
|
+
const defaultHome = join(homedir(), '.codex');
|
|
229
|
+
if (codexHome === defaultHome)
|
|
230
|
+
return;
|
|
231
|
+
const src = join(defaultHome, 'auth.json');
|
|
232
|
+
const dest = join(codexHome, 'auth.json');
|
|
233
|
+
try {
|
|
234
|
+
if (existsSync(src) && !existsSync(dest)) {
|
|
235
|
+
copyFileSync(src, dest);
|
|
236
|
+
console.log(`[CodexLauncher] Copied codex auth into ${dest}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
console.warn(`[CodexLauncher] Could not copy codex auth to ${dest}: ${err}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Adapt a prompt originally written for Claude to codex conventions. Codex
|
|
245
|
+
* names MCP tools `<server>__<tool>`, so strip the Claude `mcp__` prefix:
|
|
246
|
+
* mcp__silolink__remote_* -> silolink__remote_*
|
|
247
|
+
* mcp__silolink_server__<tool> -> silolink_server__<tool>
|
|
248
|
+
* and point at codex's instructions file (AGENTS.md).
|
|
249
|
+
*/
|
|
250
|
+
adaptPromptForCodex(prompt) {
|
|
251
|
+
return prompt
|
|
252
|
+
.replace(/mcp__silolink/g, 'silolink')
|
|
253
|
+
.replace(/CLAUDE\.md/g, 'AGENTS.md');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/** Render a JS string as a TOML basic (double-quoted) string. */
|
|
257
|
+
function tomlStr(s) {
|
|
258
|
+
return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Remove every `[mcp_servers.*]` table (header line through the line before the
|
|
262
|
+
* next top-level `[` table or EOF) from a config.toml body, leaving all other
|
|
263
|
+
* content intact. Lets SiloLink own the MCP block without clobbering unrelated
|
|
264
|
+
* user config. Best-effort line scanner (no full TOML parse).
|
|
265
|
+
*/
|
|
266
|
+
function stripMcpServers(toml) {
|
|
267
|
+
const lines = toml.split('\n');
|
|
268
|
+
const out = [];
|
|
269
|
+
let skipping = false;
|
|
270
|
+
for (const line of lines) {
|
|
271
|
+
const header = line.trim();
|
|
272
|
+
if (header.startsWith('[')) {
|
|
273
|
+
skipping = header.startsWith('[mcp_servers.') || header === '[mcp_servers]';
|
|
274
|
+
}
|
|
275
|
+
if (!skipping)
|
|
276
|
+
out.push(line);
|
|
277
|
+
}
|
|
278
|
+
return out.join('\n');
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=codex-launcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-launcher.js","sourceRoot":"","sources":["../../src/core/codex-launcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import type { MessageQueue } from './message-queue.js';
|
|
|
4
4
|
import type { DSiloedClient } from '../api/dsiloed-client.js';
|
|
5
5
|
import type { WorkspaceManager } from './workspace-manager.js';
|
|
6
6
|
import type { AgentLauncher } from './agent-launcher.js';
|
|
7
|
-
export type AgentProvider = 'claude' | 'gemini' | 'openai';
|
|
7
|
+
export type AgentProvider = 'claude' | 'gemini' | 'openai' | 'codex';
|
|
8
8
|
/**
|
|
9
9
|
* Creates the appropriate AgentLauncher based on provider configuration.
|
|
10
10
|
* Falls back to ClaudeLauncher if no provider is specified.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launcher-factory.d.ts","sourceRoot":"","sources":["../../src/core/launcher-factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"launcher-factory.d.ts","sourceRoot":"","sources":["../../src/core/launcher-factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAMzD,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAErE;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,aAAa,GAAG,SAAS,EACnC,MAAM,EAAE,cAAc,EACtB,cAAc,EAAE,cAAc,EAC9B,aAAa,CAAC,EAAE,aAAa,EAC7B,YAAY,CAAC,EAAE,YAAY,EAC3B,gBAAgB,CAAC,EAAE,gBAAgB,GAClC,aAAa,CAkBf"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ClaudeLauncher } from './claude-launcher.js';
|
|
2
2
|
import { GeminiLauncher } from './gemini-launcher.js';
|
|
3
|
-
import {
|
|
3
|
+
import { CodexLauncher } from './codex-launcher.js';
|
|
4
4
|
/**
|
|
5
5
|
* Creates the appropriate AgentLauncher based on provider configuration.
|
|
6
6
|
* Falls back to ClaudeLauncher if no provider is specified.
|
|
@@ -12,9 +12,9 @@ export function createLauncher(provider, config, sessionManager, dsiloedClient,
|
|
|
12
12
|
return new ClaudeLauncher(config, sessionManager, dsiloedClient, messageQueue, workspaceManager);
|
|
13
13
|
case 'gemini':
|
|
14
14
|
return new GeminiLauncher(config, sessionManager, dsiloedClient, messageQueue, workspaceManager);
|
|
15
|
-
case '
|
|
16
|
-
|
|
17
|
-
return new
|
|
15
|
+
case 'codex':
|
|
16
|
+
case 'openai': // alias
|
|
17
|
+
return new CodexLauncher(config, sessionManager, dsiloedClient, messageQueue, workspaceManager);
|
|
18
18
|
default:
|
|
19
19
|
console.warn(`[LauncherFactory] Unknown provider "${resolvedProvider}", falling back to Claude`);
|
|
20
20
|
return new ClaudeLauncher(config, sessionManager, dsiloedClient, messageQueue, workspaceManager);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launcher-factory.js","sourceRoot":"","sources":["../../src/core/launcher-factory.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"launcher-factory.js","sourceRoot":"","sources":["../../src/core/launcher-factory.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAKpD;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAmC,EACnC,MAAsB,EACtB,cAA8B,EAC9B,aAA6B,EAC7B,YAA2B,EAC3B,gBAAmC;IAEnC,MAAM,gBAAgB,GAAG,QAAQ,IAAI,MAAM,CAAC,cAAc,IAAI,QAAQ,CAAC;IAEvE,QAAQ,gBAAgB,EAAE,CAAC;QACzB,KAAK,QAAQ;YACX,OAAO,IAAI,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC;QAEnG,KAAK,QAAQ;YACX,OAAO,IAAI,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC;QAEnG,KAAK,OAAO,CAAC;QACb,KAAK,QAAQ,EAAE,QAAQ;YACrB,OAAO,IAAI,aAAa,CAAC,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC;QAElG;YACE,OAAO,CAAC,IAAI,CAAC,uCAAuC,gBAAgB,2BAA2B,CAAC,CAAC;YACjG,OAAO,IAAI,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC;IACrG,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { SiloLinkConfig } from '../types/index.js';
|
|
2
|
+
import { type UsageEntry } from './transcript-usage.js';
|
|
3
|
+
export type CliType = 'claude' | 'gemini' | 'codex';
|
|
4
|
+
export interface CollectOptions {
|
|
5
|
+
cli: CliType;
|
|
6
|
+
/** Explicit transcript path (e.g. from a Claude Stop hook). Skips the locator. */
|
|
7
|
+
transcriptPath?: string | null;
|
|
8
|
+
/** The CLI's own session id, for locating the transcript + idempotent dedupe. */
|
|
9
|
+
sessionId?: string | null;
|
|
10
|
+
/** Working directory the session ran in (used by the locators). */
|
|
11
|
+
cwd?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface CollectedUsage {
|
|
14
|
+
cli: CliType;
|
|
15
|
+
cost_basis: 'real' | 'imputed';
|
|
16
|
+
cli_session_id: string | null;
|
|
17
|
+
usage: UsageEntry[];
|
|
18
|
+
transcriptPath: string | null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Locate + parse a coding-agent transcript into the per-model usage summary the
|
|
22
|
+
* report_coding_usage endpoint expects. Pure (no network) so it's unit-testable.
|
|
23
|
+
*/
|
|
24
|
+
export declare function collectUsage(opts: CollectOptions): CollectedUsage;
|
|
25
|
+
export interface ReportResult {
|
|
26
|
+
posted: boolean;
|
|
27
|
+
status?: number;
|
|
28
|
+
recorded?: number;
|
|
29
|
+
skipped?: unknown[];
|
|
30
|
+
agent_session_id?: number;
|
|
31
|
+
message?: string;
|
|
32
|
+
collected: CollectedUsage;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Collect usage for a coding session and POST it to model_api's
|
|
36
|
+
* report_coding_usage endpoint, attributed to the configured user. Returns the
|
|
37
|
+
* collected usage even when nothing is posted (empty usage / dry run) so callers
|
|
38
|
+
* can log what happened. Never throws on HTTP failure — returns posted: false.
|
|
39
|
+
*/
|
|
40
|
+
export declare function reportUsage(config: SiloLinkConfig, opts: CollectOptions & {
|
|
41
|
+
sessionName?: string;
|
|
42
|
+
dryRun?: boolean;
|
|
43
|
+
getToken: () => string;
|
|
44
|
+
}): Promise<ReportResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Read a coding-agent hook's JSON payload from stdin (when invoked as a Stop /
|
|
47
|
+
* SessionEnd hook). Returns {} when there's no piped input or it isn't JSON.
|
|
48
|
+
* Claude Code passes { session_id, transcript_path, cwd, ... } on stdin.
|
|
49
|
+
*/
|
|
50
|
+
export declare function readHookStdin(): Promise<Record<string, unknown>>;
|
|
51
|
+
//# sourceMappingURL=report-usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-usage.d.ts","sourceRoot":"","sources":["../../src/core/report-usage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,kFAAkF;IAClF,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAoBD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,cAAc,CAoBjE;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,cAAc,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,cAAc,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,MAAM,CAAA;CAAE,GACxF,OAAO,CAAC,YAAY,CAAC,CA4CvB;AAOD;;;;GAIG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAWtE"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import { parseClaudeTranscript, parseGeminiTranscript, parseCodexTranscript, locateClaudeTranscript, locateGeminiTranscript, locateCodexTranscript, claudeCostBasis, geminiCostBasis, codexCostBasis, } from './transcript-usage.js';
|
|
4
|
+
const PARSERS = {
|
|
5
|
+
claude: parseClaudeTranscript,
|
|
6
|
+
gemini: parseGeminiTranscript,
|
|
7
|
+
codex: parseCodexTranscript,
|
|
8
|
+
};
|
|
9
|
+
const LOCATORS = {
|
|
10
|
+
claude: locateClaudeTranscript,
|
|
11
|
+
gemini: locateGeminiTranscript,
|
|
12
|
+
codex: locateCodexTranscript,
|
|
13
|
+
};
|
|
14
|
+
const COST_BASIS = {
|
|
15
|
+
claude: claudeCostBasis,
|
|
16
|
+
gemini: geminiCostBasis,
|
|
17
|
+
codex: codexCostBasis,
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Locate + parse a coding-agent transcript into the per-model usage summary the
|
|
21
|
+
* report_coding_usage endpoint expects. Pure (no network) so it's unit-testable.
|
|
22
|
+
*/
|
|
23
|
+
export function collectUsage(opts) {
|
|
24
|
+
const cli = opts.cli;
|
|
25
|
+
const cwd = opts.cwd || process.cwd();
|
|
26
|
+
const sessionId = opts.sessionId ?? null;
|
|
27
|
+
const transcriptPath = opts.transcriptPath && existsSync(opts.transcriptPath)
|
|
28
|
+
? opts.transcriptPath
|
|
29
|
+
: LOCATORS[cli](cwd, sessionId);
|
|
30
|
+
const usage = transcriptPath && existsSync(transcriptPath)
|
|
31
|
+
? PARSERS[cli](readFileSync(transcriptPath, 'utf8'))
|
|
32
|
+
: [];
|
|
33
|
+
return {
|
|
34
|
+
cli,
|
|
35
|
+
cost_basis: COST_BASIS[cli](),
|
|
36
|
+
cli_session_id: sessionId,
|
|
37
|
+
usage,
|
|
38
|
+
transcriptPath,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Collect usage for a coding session and POST it to model_api's
|
|
43
|
+
* report_coding_usage endpoint, attributed to the configured user. Returns the
|
|
44
|
+
* collected usage even when nothing is posted (empty usage / dry run) so callers
|
|
45
|
+
* can log what happened. Never throws on HTTP failure — returns posted: false.
|
|
46
|
+
*/
|
|
47
|
+
export async function reportUsage(config, opts) {
|
|
48
|
+
const collected = collectUsage(opts);
|
|
49
|
+
if (collected.usage.length === 0) {
|
|
50
|
+
return { posted: false, message: 'No usage found in transcript', collected };
|
|
51
|
+
}
|
|
52
|
+
if (opts.dryRun) {
|
|
53
|
+
return { posted: false, message: 'Dry run — not posted', collected };
|
|
54
|
+
}
|
|
55
|
+
const body = {
|
|
56
|
+
cli_type: collected.cli,
|
|
57
|
+
cost_basis: collected.cost_basis,
|
|
58
|
+
cli_session_id: collected.cli_session_id,
|
|
59
|
+
session_name: opts.sessionName || defaultSessionName(opts.cli, opts.cwd || process.cwd()),
|
|
60
|
+
usage: collected.usage,
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
const res = await fetch(`${config.host}/api/v1/silolink/report_coding_usage`, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
'Authorization': `Bearer ${opts.getToken()}`,
|
|
68
|
+
'Tenant-Id': config.tenant_id,
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify(body),
|
|
71
|
+
});
|
|
72
|
+
const data = (await res.json().catch(() => ({})));
|
|
73
|
+
return {
|
|
74
|
+
posted: !!data.success,
|
|
75
|
+
status: res.status,
|
|
76
|
+
recorded: data.recorded,
|
|
77
|
+
skipped: data.skipped,
|
|
78
|
+
agent_session_id: data.agent_session_id,
|
|
79
|
+
message: data.message,
|
|
80
|
+
collected,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
return { posted: false, message: `POST failed: ${err.message}`, collected };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function defaultSessionName(cli, cwd) {
|
|
88
|
+
const label = { claude: 'Claude Code', gemini: 'Gemini', codex: 'Codex' }[cli];
|
|
89
|
+
return `${label} — ${basename(cwd) || cwd}`;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Read a coding-agent hook's JSON payload from stdin (when invoked as a Stop /
|
|
93
|
+
* SessionEnd hook). Returns {} when there's no piped input or it isn't JSON.
|
|
94
|
+
* Claude Code passes { session_id, transcript_path, cwd, ... } on stdin.
|
|
95
|
+
*/
|
|
96
|
+
export async function readHookStdin() {
|
|
97
|
+
if (process.stdin.isTTY)
|
|
98
|
+
return {};
|
|
99
|
+
try {
|
|
100
|
+
const chunks = [];
|
|
101
|
+
for await (const chunk of process.stdin)
|
|
102
|
+
chunks.push(chunk);
|
|
103
|
+
const raw = Buffer.concat(chunks).toString('utf8').trim();
|
|
104
|
+
if (!raw)
|
|
105
|
+
return {};
|
|
106
|
+
return JSON.parse(raw);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=report-usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-usage.js","sourceRoot":"","sources":["../../src/core/report-usage.ts"],"names":[],"mappings":"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"}
|
|
@@ -55,6 +55,42 @@ export declare function claudeCostBasis(): 'real' | 'imputed';
|
|
|
55
55
|
* the slug when the session id isn't known.
|
|
56
56
|
*/
|
|
57
57
|
export declare function locateGeminiTranscript(cwd: string, sessionId: string | null): string | null;
|
|
58
|
+
/**
|
|
59
|
+
* Parse a Codex CLI rollout transcript (JSONL) into per-model token totals.
|
|
60
|
+
*
|
|
61
|
+
* Codex writes one rollout file per session under ~/.codex/sessions/YYYY/MM/DD/.
|
|
62
|
+
* Each line is `{ timestamp, type, payload }`. Token usage arrives in
|
|
63
|
+
* `token_count` event payloads carrying `info.total_token_usage` — a CUMULATIVE
|
|
64
|
+
* running total for the whole session (NOT a per-turn delta). So we take the
|
|
65
|
+
* LAST total_token_usage we see rather than summing (summing cumulative totals
|
|
66
|
+
* would massively overcount). The model is read from the session_meta /
|
|
67
|
+
* turn_context line(s); the most recent wins.
|
|
68
|
+
*
|
|
69
|
+
* Codex `TokenUsage` keys map straight onto model_api's OpenAI pricing
|
|
70
|
+
* convention (input_tokens is cache-INCLUSIVE, output_tokens is
|
|
71
|
+
* reasoning-INCLUSIVE — model_api subtracts the subsets itself), so we forward
|
|
72
|
+
* the raw cumulative values:
|
|
73
|
+
* input_tokens <- input_tokens (incl. cached)
|
|
74
|
+
* cache_read_input_tokens<- cached_input_tokens
|
|
75
|
+
* output_tokens <- output_tokens (incl. reasoning)
|
|
76
|
+
* reasoning_tokens <- reasoning_output_tokens
|
|
77
|
+
*/
|
|
78
|
+
export declare function parseCodexTranscript(content: string): UsageEntry[];
|
|
79
|
+
/**
|
|
80
|
+
* Locate a Codex rollout transcript. Rollouts live under
|
|
81
|
+
* ~/.codex/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl. When sessionId is
|
|
82
|
+
* known, match it in the filename or the session_meta header; otherwise pick the
|
|
83
|
+
* newest rollout whose session_meta cwd matches, falling back to the newest
|
|
84
|
+
* rollout overall.
|
|
85
|
+
*/
|
|
86
|
+
export declare function locateCodexTranscript(cwd: string, sessionId: string | null): string | null;
|
|
87
|
+
/**
|
|
88
|
+
* Codex billing basis: API key (env OPENAI_API_KEY, or an api-key entry in
|
|
89
|
+
* ~/.codex/auth.json) => real metered dollars; "Sign in with ChatGPT" (OAuth
|
|
90
|
+
* tokens, no api key) => subscription/imputed. Defaults to 'imputed' when the
|
|
91
|
+
* auth config can't be read.
|
|
92
|
+
*/
|
|
93
|
+
export declare function codexCostBasis(): 'real' | 'imputed';
|
|
58
94
|
/**
|
|
59
95
|
* Real metered billing vs subscription-imputed, read from the live Gemini auth
|
|
60
96
|
* config: an explicit API key means real dollars; OAuth (personal account)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcript-usage.d.ts","sourceRoot":"","sources":["../../src/core/transcript-usage.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAgCnE;AA4BD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAgCnE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAe3F;AAED,mFAAmF;AACnF,wBAAgB,eAAe,IAAI,MAAM,GAAG,SAAS,CAEpD;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAmC3F;AAUD;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,SAAS,CAOpD"}
|
|
1
|
+
{"version":3,"file":"transcript-usage.d.ts","sourceRoot":"","sources":["../../src/core/transcript-usage.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAgCnE;AA4BD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAgCnE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAe3F;AAED,mFAAmF;AACnF,wBAAgB,eAAe,IAAI,MAAM,GAAG,SAAS,CAEpD;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAmC3F;AAMD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAqClE;AA2CD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAuC1F;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,SAAS,CAgBnD;AAUD;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,SAAS,CAOpD"}
|