@pikiloom/kernel 0.1.3 → 0.1.5
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/dist/drivers/claude.d.ts +12 -1
- package/dist/drivers/claude.js +69 -5
- package/dist/drivers/codex.d.ts +28 -1
- package/dist/drivers/codex.js +161 -16
- package/package.json +1 -1
package/dist/drivers/claude.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentDriver, AgentTurnInput, DriverContext, DriverResult, DriverEvent, TuiInput, TuiSpec } from '../contracts/driver.js';
|
|
2
|
-
import type { UniversalPlan } from '../protocol/index.js';
|
|
2
|
+
import type { UniversalUsage, UniversalPlan } from '../protocol/index.js';
|
|
3
3
|
export declare class ClaudeDriver implements AgentDriver {
|
|
4
4
|
private readonly bin;
|
|
5
5
|
readonly id = "claude";
|
|
@@ -14,6 +14,17 @@ export declare class ClaudeDriver implements AgentDriver {
|
|
|
14
14
|
run(input: AgentTurnInput, ctx: DriverContext): Promise<DriverResult>;
|
|
15
15
|
private usage;
|
|
16
16
|
}
|
|
17
|
+
export interface ClaudeUsageState {
|
|
18
|
+
input: number | null;
|
|
19
|
+
output: number | null;
|
|
20
|
+
cached: number | null;
|
|
21
|
+
cacheCreation?: number | null;
|
|
22
|
+
contextWindow?: number | null;
|
|
23
|
+
turnOutputTokensBase?: number | null;
|
|
24
|
+
}
|
|
25
|
+
export declare function claudeUsageOf(s: ClaudeUsageState): UniversalUsage;
|
|
26
|
+
export declare function claudeContextWindowFromModel(model: unknown): number | null;
|
|
27
|
+
export declare function claudeEffectiveContextWindow(advertised: number | null): number | null;
|
|
17
28
|
export declare function handleClaudeEvent(ev: any, s: any, emit: (e: DriverEvent) => void): void;
|
|
18
29
|
export declare function claudeUserMessage(text: string): string;
|
|
19
30
|
export declare function emitClaudeImages(blocks: any[], s: any, emit: (e: DriverEvent) => void): void;
|
package/dist/drivers/claude.js
CHANGED
|
@@ -46,6 +46,8 @@ export class ClaudeDriver {
|
|
|
46
46
|
sessionId: null, model: null,
|
|
47
47
|
stopReason: null, error: null,
|
|
48
48
|
input: null, output: null, cached: null,
|
|
49
|
+
cacheCreation: null,
|
|
50
|
+
contextWindow: null, turnOutputTokensBase: 0,
|
|
49
51
|
subAgents: new Map(),
|
|
50
52
|
tools: new Map(),
|
|
51
53
|
};
|
|
@@ -141,9 +143,45 @@ export class ClaudeDriver {
|
|
|
141
143
|
});
|
|
142
144
|
}
|
|
143
145
|
usage(s) {
|
|
144
|
-
return
|
|
146
|
+
return claudeUsageOf(s);
|
|
145
147
|
}
|
|
146
148
|
}
|
|
149
|
+
export function claudeUsageOf(s) {
|
|
150
|
+
const used = (s.input ?? 0) + (s.cached ?? 0) + (s.cacheCreation ?? 0) + (s.output ?? 0);
|
|
151
|
+
const window = s.contextWindow ?? null;
|
|
152
|
+
const contextPercent = window && used > 0 ? Math.min(99.9, Math.round((used / window) * 1000) / 10) : null;
|
|
153
|
+
const turnOutput = (s.turnOutputTokensBase ?? 0) + (s.output ?? 0);
|
|
154
|
+
return {
|
|
155
|
+
inputTokens: s.input,
|
|
156
|
+
outputTokens: s.output,
|
|
157
|
+
cachedInputTokens: s.cached,
|
|
158
|
+
contextUsedTokens: used > 0 ? used : null,
|
|
159
|
+
contextPercent,
|
|
160
|
+
turnOutputTokens: turnOutput > 0 ? turnOutput : null,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// Advertised context window by Claude model id (best-effort; unknown -> null so the
|
|
164
|
+
// percent simply stays absent rather than wrong). Anchor-free so vendor-prefixed ids
|
|
165
|
+
// (us.anthropic.claude-…) still match.
|
|
166
|
+
export function claudeContextWindowFromModel(model) {
|
|
167
|
+
const id = String(model ?? '').trim().toLowerCase();
|
|
168
|
+
if (!id)
|
|
169
|
+
return null;
|
|
170
|
+
if (id === 'haiku' || /claude-haiku-/.test(id))
|
|
171
|
+
return 200_000;
|
|
172
|
+
if (id === 'opus' || id === 'sonnet' || id === 'fable')
|
|
173
|
+
return 1_000_000;
|
|
174
|
+
if (/claude-(opus|sonnet)-/.test(id) || /claude-fable-/.test(id))
|
|
175
|
+
return 1_000_000;
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
// Usable window = advertised minus Claude's max-output (20k) + autocompact (13k) reserve.
|
|
179
|
+
const CLAUDE_USABLE_WINDOW_RESERVE = 33_000;
|
|
180
|
+
export function claudeEffectiveContextWindow(advertised) {
|
|
181
|
+
if (advertised == null)
|
|
182
|
+
return null;
|
|
183
|
+
return advertised <= CLAUDE_USABLE_WINDOW_RESERVE ? advertised : advertised - CLAUDE_USABLE_WINDOW_RESERVE;
|
|
184
|
+
}
|
|
147
185
|
// Parse one claude stream-json event into kernel DriverEvents (pure + exported for
|
|
148
186
|
// hermetic testing). Faithful to pikiloom's claudeParse shapes.
|
|
149
187
|
export function handleClaudeEvent(ev, s, emit) {
|
|
@@ -178,15 +216,37 @@ export function handleClaudeEvent(ev, s, emit) {
|
|
|
178
216
|
emit({ type: 'session', sessionId: ev.session_id });
|
|
179
217
|
}
|
|
180
218
|
s.model = ev.model ?? s.model;
|
|
219
|
+
s.contextWindow = claudeEffectiveContextWindow(claudeContextWindowFromModel(s.model)) ?? s.contextWindow;
|
|
181
220
|
return;
|
|
182
221
|
}
|
|
183
222
|
if (t === 'stream_event') {
|
|
184
223
|
const inner = ev.event || {};
|
|
185
224
|
if (inner.type === 'message_start') {
|
|
186
225
|
const u = inner.message?.usage;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
226
|
+
// Claude emits one message per tool-use round within a single turn. Carry the prior
|
|
227
|
+
// message's output into the per-turn base, then reset to the new message's prompt size
|
|
228
|
+
// so contextUsedTokens tracks current occupancy while turnOutputTokens sums the turn.
|
|
229
|
+
s.turnOutputTokensBase = (s.turnOutputTokensBase ?? 0) + (s.output ?? 0);
|
|
230
|
+
s.input = u?.input_tokens ?? 0;
|
|
231
|
+
s.cached = u?.cache_read_input_tokens ?? 0;
|
|
232
|
+
s.cacheCreation = u?.cache_creation_input_tokens ?? 0;
|
|
233
|
+
s.output = 0;
|
|
234
|
+
}
|
|
235
|
+
else if (inner.type === 'content_block_start') {
|
|
236
|
+
// Claude emits multiple text/thinking blocks per turn (one set per tool-use round). Insert a
|
|
237
|
+
// paragraph break before a NEW block when prior content exists, so the live preview shows
|
|
238
|
+
// breaks between segments instead of running them together. Mirrors the legacy driver; the
|
|
239
|
+
// separator is emitted as a delta so the runtime's accumulated snapshot stays in sync with s.
|
|
240
|
+
const bt = inner.content_block?.type;
|
|
241
|
+
if (bt === 'text' && s.text && !s.text.endsWith('\n\n')) {
|
|
242
|
+
const sep = s.text.endsWith('\n') ? '\n' : '\n\n';
|
|
243
|
+
s.text += sep;
|
|
244
|
+
emit({ type: 'text', delta: sep });
|
|
245
|
+
}
|
|
246
|
+
else if (bt === 'thinking' && s.reasoning && !s.reasoning.endsWith('\n\n')) {
|
|
247
|
+
const sep = s.reasoning.endsWith('\n') ? '\n' : '\n\n';
|
|
248
|
+
s.reasoning += sep;
|
|
249
|
+
emit({ type: 'reasoning', delta: sep });
|
|
190
250
|
}
|
|
191
251
|
}
|
|
192
252
|
else if (inner.type === 'content_block_delta') {
|
|
@@ -211,7 +271,9 @@ export function handleClaudeEvent(ev, s, emit) {
|
|
|
211
271
|
s.input = u.input_tokens;
|
|
212
272
|
if (u.cache_read_input_tokens != null)
|
|
213
273
|
s.cached = u.cache_read_input_tokens;
|
|
214
|
-
|
|
274
|
+
if (u.cache_creation_input_tokens != null)
|
|
275
|
+
s.cacheCreation = u.cache_creation_input_tokens;
|
|
276
|
+
emit({ type: 'usage', usage: claudeUsageOf(s) });
|
|
215
277
|
}
|
|
216
278
|
if (inner.delta?.stop_reason)
|
|
217
279
|
s.stopReason = inner.delta.stop_reason;
|
|
@@ -312,6 +374,8 @@ export function handleClaudeEvent(ev, s, emit) {
|
|
|
312
374
|
const cached = u.cache_read_input_tokens ?? u.cached_input_tokens;
|
|
313
375
|
if (s.cached == null && cached != null)
|
|
314
376
|
s.cached = cached;
|
|
377
|
+
if (s.cacheCreation == null && u.cache_creation_input_tokens != null)
|
|
378
|
+
s.cacheCreation = u.cache_creation_input_tokens;
|
|
315
379
|
}
|
|
316
380
|
return;
|
|
317
381
|
}
|
package/dist/drivers/codex.d.ts
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
|
-
import type { AgentDriver, AgentTurnInput, DriverContext, DriverResult, TuiInput, TuiSpec } from '../contracts/driver.js';
|
|
1
|
+
import type { AgentDriver, AgentTurnInput, DriverContext, DriverEvent, DriverResult, TuiInput, TuiSpec } from '../contracts/driver.js';
|
|
2
|
+
import type { UniversalUsage } from '../protocol/index.js';
|
|
3
|
+
export declare function codexToolSummary(item: any): {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
summary: string;
|
|
7
|
+
} | null;
|
|
8
|
+
export interface CodexContentState {
|
|
9
|
+
text: string;
|
|
10
|
+
reasoning: string;
|
|
11
|
+
streamedReasoning: boolean;
|
|
12
|
+
msgs: string[];
|
|
13
|
+
thinkParts: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function codexReasoningItemText(item: any): string;
|
|
16
|
+
export declare function captureCodexAgentMessage(item: any, s: CodexContentState, deltaItems: Set<string>, phases: Map<string, string>, emit: (e: DriverEvent) => void): void;
|
|
17
|
+
export declare function captureCodexReasoning(text: string, s: CodexContentState, emit: (e: DriverEvent) => void): void;
|
|
18
|
+
export declare function codexFinalText(s: CodexContentState): string;
|
|
19
|
+
export declare function codexFinalReasoning(s: CodexContentState): string;
|
|
2
20
|
export declare class CodexDriver implements AgentDriver {
|
|
3
21
|
private readonly bin;
|
|
4
22
|
readonly id = "codex";
|
|
@@ -12,3 +30,12 @@ export declare class CodexDriver implements AgentDriver {
|
|
|
12
30
|
run(input: AgentTurnInput, ctx: DriverContext): Promise<DriverResult>;
|
|
13
31
|
tui(input: TuiInput): TuiSpec;
|
|
14
32
|
}
|
|
33
|
+
export interface CodexUsageState {
|
|
34
|
+
input: number | null;
|
|
35
|
+
output: number | null;
|
|
36
|
+
cached: number | null;
|
|
37
|
+
contextUsed?: number | null;
|
|
38
|
+
contextWindow?: number | null;
|
|
39
|
+
}
|
|
40
|
+
export declare function applyCodexTokenUsage(s: CodexUsageState, rawUsage: any): void;
|
|
41
|
+
export declare function codexUsageOf(s: CodexUsageState): UniversalUsage;
|
package/dist/drivers/codex.js
CHANGED
|
@@ -20,7 +20,12 @@ class AppServer {
|
|
|
20
20
|
onServerRequest(cb) { this.requestResponder = cb; }
|
|
21
21
|
async start() {
|
|
22
22
|
const args = ['app-server'];
|
|
23
|
-
|
|
23
|
+
// Do NOT force model_reasoning_summary — codex reasoning summaries stay OFF by default
|
|
24
|
+
// (respecting ~/.codex/config.toml, which is the original behavior). A caller that wants
|
|
25
|
+
// thinking can pass `model_reasoning_summary=...` via configOverrides; we never inject it.
|
|
26
|
+
const overrides = [...this.configOverrides];
|
|
27
|
+
if (!overrides.some(c => /^features\.goals\s*=/.test(c)))
|
|
28
|
+
overrides.push('features.goals=true');
|
|
24
29
|
for (const c of overrides)
|
|
25
30
|
args.push('-c', c);
|
|
26
31
|
try {
|
|
@@ -126,7 +131,12 @@ function codexFileChangeSummary(item) {
|
|
|
126
131
|
}
|
|
127
132
|
// Normalize a codex app-server item -> {id, name, summary}. The summary mirrors pikiloom's
|
|
128
133
|
// vocabulary ("Run shell: <cmd>", "Edit <path>") so the activity projection reads naturally.
|
|
129
|
-
|
|
134
|
+
// Only ACTUAL tool calls become Activity rows. agentMessage (the answer text) and reasoning
|
|
135
|
+
// (thinking) are CONTENT — they render below as message text / thinking, never as tools. The old
|
|
136
|
+
// fallback to `item.type` wrongly turned every item (incl. agentMessage/reasoning) into a bogus
|
|
137
|
+
// "tool" in the Activity card. Mirrors the legacy driver's isCodexToolCallItem whitelist.
|
|
138
|
+
const CODEX_TOOL_CALL_TYPES = new Set(['dynamicToolCall', 'mcpToolCall', 'collabAgentToolCall']);
|
|
139
|
+
export function codexToolSummary(item) {
|
|
130
140
|
const id = String(item?.id || '');
|
|
131
141
|
if (!id)
|
|
132
142
|
return null;
|
|
@@ -136,8 +146,13 @@ function codexToolSummary(item) {
|
|
|
136
146
|
}
|
|
137
147
|
if (item.type === 'fileChange' || item.type === 'patch')
|
|
138
148
|
return { id, name: 'edit', summary: codexFileChangeSummary(item) };
|
|
139
|
-
|
|
140
|
-
|
|
149
|
+
if (CODEX_TOOL_CALL_TYPES.has(item.type)) {
|
|
150
|
+
const raw = typeof item.tool === 'string' && item.tool.trim() ? item.tool.trim()
|
|
151
|
+
: typeof item.name === 'string' && item.name.trim() ? item.name.trim() : '';
|
|
152
|
+
const name = raw ? (raw.split('.').pop() || raw) : 'tool';
|
|
153
|
+
return { id, name, summary: raw ? `Use ${name}` : 'Use tool' };
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
141
156
|
}
|
|
142
157
|
// codex `item/tool/requestUserInput` params -> a normalized UniversalInteraction
|
|
143
158
|
// (faithful to the legacy codex driver's toAgentInteraction shape).
|
|
@@ -160,6 +175,51 @@ function codexUserInputToInteraction(params, promptId) {
|
|
|
160
175
|
return null;
|
|
161
176
|
return { promptId, kind: 'user-input', title: 'User Input Required', hint: 'Use the buttons when available, or reply with text.', questions };
|
|
162
177
|
}
|
|
178
|
+
// Reasoning text from a completed `reasoning` item (item/completed or rawResponseItem/completed):
|
|
179
|
+
// summary/content arrays of plain strings or {text} objects.
|
|
180
|
+
export function codexReasoningItemText(item) {
|
|
181
|
+
const parts = [
|
|
182
|
+
...(Array.isArray(item?.summary) ? item.summary : []),
|
|
183
|
+
...(Array.isArray(item?.content) ? item.content : []),
|
|
184
|
+
];
|
|
185
|
+
return parts.map((p) => (typeof p === 'string' ? p : p?.text || '')).filter(Boolean).join('\n').trim();
|
|
186
|
+
}
|
|
187
|
+
// A completed final_answer agentMessage that did NOT stream deltas: append + emit it live.
|
|
188
|
+
// deltaItems holds the ids already streamed, so a completed item echoing a streamed one is
|
|
189
|
+
// not double-counted (matches the legacy driver's deltaSeenForItem guard).
|
|
190
|
+
export function captureCodexAgentMessage(item, s, deltaItems, phases, emit) {
|
|
191
|
+
const phase = item?.phase || (item?.id ? phases.get(item.id) : null) || 'final_answer';
|
|
192
|
+
if (phase !== 'final_answer')
|
|
193
|
+
return;
|
|
194
|
+
const text = typeof item?.text === 'string' ? item.text.trim() : '';
|
|
195
|
+
if (!text)
|
|
196
|
+
return;
|
|
197
|
+
s.msgs.push(text);
|
|
198
|
+
if (item.id && deltaItems.has(item.id))
|
|
199
|
+
return;
|
|
200
|
+
const delta = s.text.trim() ? `\n\n${text}` : text;
|
|
201
|
+
s.text += delta;
|
|
202
|
+
emit({ type: 'text', delta });
|
|
203
|
+
}
|
|
204
|
+
// A completed reasoning item: keep it for the end-of-turn fallback, and stream it live only
|
|
205
|
+
// when nothing arrived as reasoning deltas (so the streamed path is never double-emitted).
|
|
206
|
+
export function captureCodexReasoning(text, s, emit) {
|
|
207
|
+
if (!text)
|
|
208
|
+
return;
|
|
209
|
+
s.thinkParts.push(text);
|
|
210
|
+
if (s.streamedReasoning)
|
|
211
|
+
return;
|
|
212
|
+
const delta = s.reasoning.trim() ? `\n\n${text}` : text;
|
|
213
|
+
s.reasoning += delta;
|
|
214
|
+
emit({ type: 'reasoning', delta });
|
|
215
|
+
}
|
|
216
|
+
// End-of-turn finalizers: prefer streamed text/reasoning, fall back to completed-item parts.
|
|
217
|
+
export function codexFinalText(s) {
|
|
218
|
+
return s.text.trim() ? s.text : s.msgs.join('\n\n');
|
|
219
|
+
}
|
|
220
|
+
export function codexFinalReasoning(s) {
|
|
221
|
+
return s.reasoning.trim() ? s.reasoning : s.thinkParts.join('\n\n');
|
|
222
|
+
}
|
|
163
223
|
export class CodexDriver {
|
|
164
224
|
bin;
|
|
165
225
|
id = 'codex';
|
|
@@ -173,9 +233,10 @@ export class CodexDriver {
|
|
|
173
233
|
// to the native account.
|
|
174
234
|
const config = [...(input.configOverrides || [])];
|
|
175
235
|
const srv = new AppServer(this.bin, config, input.env);
|
|
176
|
-
const state = { text: '', reasoning: '', sessionId: input.sessionId ?? null, input: null, output: null, cached: null, status: null, error: null, turnId: null };
|
|
236
|
+
const state = { text: '', reasoning: '', streamedReasoning: false, msgs: [], thinkParts: [], sessionId: input.sessionId ?? null, input: null, output: null, cached: null, contextUsed: null, contextWindow: null, status: null, error: null, turnId: null };
|
|
177
237
|
const phases = new Map();
|
|
178
238
|
const toolSummaries = new Map();
|
|
239
|
+
const deltaItems = new Set();
|
|
179
240
|
let steerRegistered = false;
|
|
180
241
|
const ok = await srv.start();
|
|
181
242
|
if (!ok)
|
|
@@ -239,6 +300,8 @@ export class CodexDriver {
|
|
|
239
300
|
const phase = params?.itemId ? (phases.get(params.itemId) || 'final_answer') : 'final_answer';
|
|
240
301
|
if (phase === 'final_answer' && params?.delta) {
|
|
241
302
|
state.text += params.delta;
|
|
303
|
+
if (params.itemId)
|
|
304
|
+
deltaItems.add(params.itemId);
|
|
242
305
|
ctx.emit({ type: 'text', delta: params.delta });
|
|
243
306
|
}
|
|
244
307
|
break;
|
|
@@ -247,16 +310,28 @@ export class CodexDriver {
|
|
|
247
310
|
case 'item/reasoning/summaryTextDelta':
|
|
248
311
|
if (params?.delta) {
|
|
249
312
|
state.reasoning += params.delta;
|
|
313
|
+
state.streamedReasoning = true;
|
|
250
314
|
ctx.emit({ type: 'reasoning', delta: params.delta });
|
|
251
315
|
}
|
|
252
316
|
break;
|
|
253
317
|
case 'item/completed': {
|
|
254
318
|
const item = params?.item || {};
|
|
319
|
+
// Final answer / reasoning delivered as a completed item (no preceding deltas).
|
|
320
|
+
if (item.type === 'agentMessage')
|
|
321
|
+
captureCodexAgentMessage(item, state, deltaItems, phases, ctx.emit);
|
|
322
|
+
else if (item.type === 'reasoning')
|
|
323
|
+
captureCodexReasoning(codexReasoningItemText(item), state, ctx.emit);
|
|
255
324
|
const t = codexToolSummary(item);
|
|
256
325
|
if (t && toolSummaries.has(t.id))
|
|
257
326
|
ctx.emit({ type: 'tool', call: { id: t.id, name: t.name, summary: toolSummaries.get(t.id) || t.summary, status: item.status === 'failed' ? 'failed' : 'done' } });
|
|
258
327
|
break;
|
|
259
328
|
}
|
|
329
|
+
case 'rawResponseItem/completed': {
|
|
330
|
+
const item = params?.item || {};
|
|
331
|
+
if (item?.type === 'reasoning')
|
|
332
|
+
captureCodexReasoning(codexReasoningItemText(item), state, ctx.emit);
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
260
335
|
case 'turn/plan/updated': {
|
|
261
336
|
const plan = planFromUpdate(params);
|
|
262
337
|
if (plan)
|
|
@@ -264,14 +339,8 @@ export class CodexDriver {
|
|
|
264
339
|
break;
|
|
265
340
|
}
|
|
266
341
|
case 'thread/tokenUsage/updated': {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
state.input = u.input_tokens ?? u.inputTokens;
|
|
270
|
-
if (u.output_tokens != null || u.outputTokens != null)
|
|
271
|
-
state.output = u.output_tokens ?? u.outputTokens;
|
|
272
|
-
if (u.cached_input_tokens != null || u.cachedInputTokens != null)
|
|
273
|
-
state.cached = u.cached_input_tokens ?? u.cachedInputTokens;
|
|
274
|
-
ctx.emit({ type: 'usage', usage: { inputTokens: state.input, outputTokens: state.output, cachedInputTokens: state.cached, contextPercent: null } });
|
|
342
|
+
applyCodexTokenUsage(state, params?.tokenUsage || params?.usage);
|
|
343
|
+
ctx.emit({ type: 'usage', usage: codexUsageOf(state) });
|
|
275
344
|
break;
|
|
276
345
|
}
|
|
277
346
|
case 'turn/completed': {
|
|
@@ -279,6 +348,8 @@ export class CodexDriver {
|
|
|
279
348
|
state.status = turn.status ?? 'completed';
|
|
280
349
|
if (turn.error)
|
|
281
350
|
state.error = turn.error.message || turn.error.code || 'turn error';
|
|
351
|
+
applyCodexTokenUsage(state, params?.tokenUsage || turn.tokenUsage || turn.usage);
|
|
352
|
+
ctx.emit({ type: 'usage', usage: codexUsageOf(state) });
|
|
282
353
|
settle();
|
|
283
354
|
break;
|
|
284
355
|
}
|
|
@@ -309,12 +380,13 @@ export class CodexDriver {
|
|
|
309
380
|
if (turnResp.error)
|
|
310
381
|
return { ok: false, text: state.text, error: turnResp.error.message || 'turn/start failed', stopReason: 'error', sessionId: state.sessionId };
|
|
311
382
|
await turnDone;
|
|
312
|
-
const usage =
|
|
383
|
+
const usage = codexUsageOf(state);
|
|
313
384
|
const ok2 = (state.status === 'completed' || state.status == null) && !state.error && !ctx.signal.aborted;
|
|
385
|
+
const finalReasoning = codexFinalReasoning(state);
|
|
314
386
|
return {
|
|
315
387
|
ok: ok2,
|
|
316
|
-
text: state
|
|
317
|
-
reasoning:
|
|
388
|
+
text: codexFinalText(state),
|
|
389
|
+
reasoning: finalReasoning || undefined,
|
|
318
390
|
error: state.error || (ctx.signal.aborted ? 'Interrupted by user.' : null),
|
|
319
391
|
stopReason: ctx.signal.aborted ? 'interrupted' : (state.status || 'end_turn'),
|
|
320
392
|
sessionId: state.sessionId,
|
|
@@ -344,3 +416,76 @@ function buildTurnInput(prompt, attachments) {
|
|
|
344
416
|
input.push({ type: 'text', text: prompt });
|
|
345
417
|
return input;
|
|
346
418
|
}
|
|
419
|
+
function codexNum(...vals) {
|
|
420
|
+
for (const v of vals) {
|
|
421
|
+
const n = typeof v === 'number' ? v : Number(v);
|
|
422
|
+
if (Number.isFinite(n))
|
|
423
|
+
return n;
|
|
424
|
+
}
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
// Context occupancy from one usage record: prefer total_tokens, else input(+output).
|
|
428
|
+
function codexContextUsed(raw) {
|
|
429
|
+
if (!raw || typeof raw !== 'object')
|
|
430
|
+
return null;
|
|
431
|
+
const total = codexNum(raw.total_tokens, raw.totalTokens);
|
|
432
|
+
if (total != null && total >= 0)
|
|
433
|
+
return total;
|
|
434
|
+
const i = codexNum(raw.input_tokens, raw.inputTokens);
|
|
435
|
+
const o = codexNum(raw.output_tokens, raw.outputTokens);
|
|
436
|
+
if (i != null && o != null)
|
|
437
|
+
return i + o;
|
|
438
|
+
return i;
|
|
439
|
+
}
|
|
440
|
+
// Fold a codex tokenUsage payload into driver state: last-turn counts, context
|
|
441
|
+
// occupancy, and the model window. Tolerant of nested {info:{…}} and flat shapes.
|
|
442
|
+
export function applyCodexTokenUsage(s, rawUsage) {
|
|
443
|
+
if (!rawUsage || typeof rawUsage !== 'object')
|
|
444
|
+
return;
|
|
445
|
+
const info = rawUsage.info && typeof rawUsage.info === 'object' ? rawUsage.info : rawUsage;
|
|
446
|
+
const last = info.last ?? info.lastTokenUsage ?? info.last_token_usage ?? rawUsage.last;
|
|
447
|
+
const li = codexNum(last?.input_tokens, last?.inputTokens);
|
|
448
|
+
const lo = codexNum(last?.output_tokens, last?.outputTokens);
|
|
449
|
+
const lc = codexNum(last?.cached_input_tokens, last?.cachedInputTokens);
|
|
450
|
+
if (li != null)
|
|
451
|
+
s.input = li;
|
|
452
|
+
if (lo != null)
|
|
453
|
+
s.output = lo;
|
|
454
|
+
if (lc != null)
|
|
455
|
+
s.cached = lc;
|
|
456
|
+
const used = codexContextUsed(last);
|
|
457
|
+
if (used != null)
|
|
458
|
+
s.contextUsed = used;
|
|
459
|
+
// Cumulative total as fallback for the per-turn counts (the trailing `?? rawUsage`
|
|
460
|
+
// also catches the flat shape, where there is no nested `last`).
|
|
461
|
+
const total = info.total ?? info.totalTokenUsage ?? info.total_token_usage ?? rawUsage.total ?? rawUsage;
|
|
462
|
+
if (total && typeof total === 'object') {
|
|
463
|
+
const ti = codexNum(total.input_tokens, total.inputTokens);
|
|
464
|
+
const to = codexNum(total.output_tokens, total.outputTokens);
|
|
465
|
+
const tc = codexNum(total.cached_input_tokens, total.cachedInputTokens);
|
|
466
|
+
if (li == null && ti != null)
|
|
467
|
+
s.input = ti;
|
|
468
|
+
if (lo == null && to != null)
|
|
469
|
+
s.output = to;
|
|
470
|
+
if (lc == null && tc != null)
|
|
471
|
+
s.cached = tc;
|
|
472
|
+
}
|
|
473
|
+
const cw = codexNum(info.modelContextWindow, info.model_context_window, rawUsage.modelContextWindow, rawUsage.model_context_window);
|
|
474
|
+
if (cw != null && cw > 0)
|
|
475
|
+
s.contextWindow = cw;
|
|
476
|
+
}
|
|
477
|
+
export function codexUsageOf(s) {
|
|
478
|
+
const fallback = (s.input ?? 0) + (s.cached ?? 0);
|
|
479
|
+
const used = s.contextUsed ?? (fallback > 0 ? fallback : null);
|
|
480
|
+
const window = s.contextWindow ?? null;
|
|
481
|
+
const contextPercent = used != null && window ? Math.min(99.9, Math.round((used / window) * 1000) / 10) : null;
|
|
482
|
+
const turnOutput = s.output ?? 0;
|
|
483
|
+
return {
|
|
484
|
+
inputTokens: s.input,
|
|
485
|
+
outputTokens: s.output,
|
|
486
|
+
cachedInputTokens: s.cached,
|
|
487
|
+
contextUsedTokens: used,
|
|
488
|
+
contextPercent,
|
|
489
|
+
turnOutputTokens: turnOutput > 0 ? turnOutput : null,
|
|
490
|
+
};
|
|
491
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikiloom/kernel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Heterogeneous coding agents (Claude / Codex / Gemini / ACP) -> an interaction-friendly, accumulating session snapshot + control handle, over pluggable surfaces (IM, Web, tunnel, TUI). The reusable core pikiloom itself is built on.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|