@zergai/ztc 0.2.6-dev.36102446685 → 0.3.0-beta.1
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 +43 -17
- package/bin/zmux-darwin-arm64 +0 -0
- package/bin/ztc-tui-darwin-arm64 +0 -0
- package/bin/zterm-darwin-arm64 +0 -0
- package/dist/App.js +19 -5
- package/dist/agent/agent.d.ts +5 -1
- package/dist/agent/agent.js +51 -8
- package/dist/agent/backends/account_pool.d.ts +5 -6
- package/dist/agent/backends/account_pool.js +39 -14
- package/dist/agent/backends/account_rotation.d.ts +3 -0
- package/dist/agent/backends/account_rotation.js +14 -3
- package/dist/agent/backends/codex_account_status.d.ts +42 -0
- package/dist/agent/backends/codex_account_status.js +196 -0
- package/dist/agent/backends/codex_app_server.d.ts +12 -2
- package/dist/agent/backends/codex_app_server.js +66 -6
- package/dist/agent/backends/index.d.ts +2 -0
- package/dist/agent/backends/index.js +13 -5
- package/dist/agent/backends/openai.d.ts +2 -2
- package/dist/agent/backends/openai.js +4 -3
- package/dist/agent/backends/openai_compatible.js +10 -10
- package/dist/agent/backends/openai_responses.d.ts +17 -5
- package/dist/agent/backends/openai_responses.js +2 -4
- package/dist/agent/backends/token_usage.js +1 -1
- package/dist/agent/backends/types.d.ts +3 -0
- package/dist/agent/commands/accounts.d.ts +7 -1
- package/dist/agent/commands/accounts.js +299 -101
- package/dist/agent/commands/config.js +11 -3
- package/dist/agent/commands/config_profile_switch.d.ts +2 -0
- package/dist/agent/commands/config_profile_switch.js +22 -0
- package/dist/agent/commands/context.js +4 -3
- package/dist/agent/commands/index.js +1 -2
- package/dist/agent/commands/input_mode.js +1 -1
- package/dist/agent/commands/model.js +7 -2
- package/dist/agent/commands/plan.d.ts +12 -0
- package/dist/agent/commands/plan.js +39 -28
- package/dist/agent/commands/profile.d.ts +1 -1
- package/dist/agent/commands/profile.js +34 -257
- package/dist/agent/commands/provider_choices.d.ts +12 -1
- package/dist/agent/commands/provider_choices.js +29 -2
- package/dist/agent/commands/provider_selection.d.ts +3 -0
- package/dist/agent/commands/provider_selection.js +63 -0
- package/dist/agent/commands/settings.d.ts +2 -0
- package/dist/agent/commands/settings.js +655 -50
- package/dist/agent/commands/types.d.ts +19 -2
- package/dist/agent/tools/ask_user.js +2 -2
- package/dist/app/agent_runner.d.ts +1 -1
- package/dist/app/agent_runner.js +2 -2
- package/dist/app/ask_user_response.d.ts +3 -0
- package/dist/app/ask_user_response.js +16 -0
- package/dist/app/command_context.js +29 -13
- package/dist/app/controller.d.ts +17 -2
- package/dist/app/controller.js +24 -1
- package/dist/app/selection_completion.d.ts +9 -3
- package/dist/app/selection_completion.js +54 -22
- package/dist/cli_args.js +10 -1
- package/dist/cli_main.js +85 -2
- package/dist/cli_ratatui.d.ts +22 -4
- package/dist/cli_ratatui.js +421 -146
- package/dist/config/defaults.d.ts +6 -0
- package/dist/config/defaults.js +20 -0
- package/dist/config/profiles.d.ts +9 -0
- package/dist/config/profiles.js +87 -0
- package/dist/config/types.d.ts +2 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +283 -40
- package/dist/headless.js +14 -0
- package/dist/panes/model_catalog.d.ts +1 -1
- package/dist/panes/model_catalog.js +24 -2
- package/dist/panes/ztc_launch.js +6 -0
- package/dist/router_auth.js +3 -2
- package/dist/session.d.ts +2 -0
- package/dist/session.js +3 -0
- package/dist/types.d.ts +23 -2
- package/dist/ui/views/app.d.ts +3 -1
- package/dist/ui/views/app.js +3 -2
- package/dist/ui/views/completion_menu.js +6 -6
- package/dist/ui/views/input_area.d.ts +2 -1
- package/dist/ui/views/input_area.js +9 -8
- package/dist/ui/views/status_bar.d.ts +2 -0
- package/dist/ui/views/status_bar.js +15 -4
- package/dist/ui/widgets/color_panel.d.ts +9 -0
- package/dist/ui/widgets/color_panel.js +41 -26
- package/dist/ui/widgets/navigator_panel.d.ts +9 -0
- package/dist/ui/widgets/navigator_panel.js +17 -16
- package/dist/ui/widgets/panel_frame.d.ts +83 -3
- package/dist/ui/widgets/panel_frame.js +107 -9
- package/dist/ui/widgets/questionnaire.d.ts +9 -0
- package/dist/ui/widgets/questionnaire.js +214 -16
- package/dist/ui/widgets/resume_panel.d.ts +9 -0
- package/dist/ui/widgets/resume_panel.js +18 -17
- package/dist/ui/widgets/rewind_panel.d.ts +9 -0
- package/dist/ui/widgets/rewind_panel.js +15 -16
- package/dist/ui/widgets/select.d.ts +6 -0
- package/dist/ui/widgets/select.js +47 -12
- package/dist/ui/widgets/settings_panel.d.ts +75 -0
- package/dist/ui/widgets/settings_panel.js +197 -39
- package/dist/ui/widgets/status_line_panel.d.ts +9 -0
- package/dist/ui/widgets/status_line_panel.js +18 -19
- package/dist/ui/widgets/transcript_preview.d.ts +8 -0
- package/dist/ui/widgets/transcript_preview.js +10 -0
- package/dist/utils/context.js +2 -0
- package/dist/utils/fireworks.d.ts +20 -0
- package/dist/utils/fireworks.js +23 -0
- package/dist/utils/models.d.ts +2 -1
- package/dist/utils/models.js +19 -0
- package/dist/web/accounts_mirror.d.ts +7 -0
- package/dist/web/accounts_mirror.js +59 -7
- package/dist/web/mirror_stream.js +19 -5
- package/dist/zlm-embed.mjs +5734 -5362
- package/package.json +5 -5
- package/dist/agent/commands/provider.d.ts +0 -2
- package/dist/agent/commands/provider.js +0 -75
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { CodexAppServerClient, CODEX_SUBSCRIPTION_CONFIG_ARGS, buildCodexAppServerEnv } from './codex_app_server.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimum spacing between probes of one credential home, even when forced, for
|
|
4
|
+
* the shared mirror cache. A forced read spawns the Codex App Server and
|
|
5
|
+
* refreshes the login's token, so repeated refresh clicks (or a looping client)
|
|
6
|
+
* share one recent probe.
|
|
7
|
+
*/
|
|
8
|
+
export const CODEX_STATUS_MIN_FORCED_INTERVAL_MS = 30_000;
|
|
9
|
+
/** Cache official Codex status per credential home so a mirror poll does not spawn a CLI each time. */
|
|
10
|
+
export class CodexStatusCache {
|
|
11
|
+
probe;
|
|
12
|
+
entries = new Map();
|
|
13
|
+
pending = new Map();
|
|
14
|
+
generations = new Map();
|
|
15
|
+
minForcedIntervalMs;
|
|
16
|
+
constructor(probe, options = {}) {
|
|
17
|
+
this.probe = probe;
|
|
18
|
+
this.minForcedIntervalMs = options.minForcedIntervalMs ?? 0;
|
|
19
|
+
}
|
|
20
|
+
async read(home, force = false) {
|
|
21
|
+
const pending = this.pending.get(home);
|
|
22
|
+
if (pending)
|
|
23
|
+
return pending;
|
|
24
|
+
const entry = this.entries.get(home);
|
|
25
|
+
if (entry && Date.now() < entry.nextReadAt
|
|
26
|
+
&& (!force || Date.now() - entry.probedAt < this.minForcedIntervalMs)) {
|
|
27
|
+
return entry.status;
|
|
28
|
+
}
|
|
29
|
+
const generation = this.generations.get(home) ?? 0;
|
|
30
|
+
const work = (async () => {
|
|
31
|
+
let status;
|
|
32
|
+
try {
|
|
33
|
+
status = await this.probe(home);
|
|
34
|
+
if (status.state === 'quota_unavailable' && entry?.status.windows.length && status.windows.length === 0) {
|
|
35
|
+
status = { ...status, windows: entry.status.windows,
|
|
36
|
+
...(entry.status.observedAt !== undefined ? { observedAt: entry.status.observedAt } : {}) };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
status = {
|
|
41
|
+
state: 'unavailable',
|
|
42
|
+
windows: entry?.status.windows ?? [],
|
|
43
|
+
...(entry?.status.observedAt !== undefined ? { observedAt: entry.status.observedAt } : {}),
|
|
44
|
+
...(entry?.status.email ? { email: entry.status.email } : {}),
|
|
45
|
+
...(entry?.status.plan ? { plan: entry.status.plan } : {}),
|
|
46
|
+
...(entry?.status.usage ? { usage: entry.status.usage, usageStale: true } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (status.usageStale && !status.usage && entry?.status.usage) {
|
|
50
|
+
status = { ...status, usage: entry.status.usage };
|
|
51
|
+
}
|
|
52
|
+
if ((this.generations.get(home) ?? 0) === generation) {
|
|
53
|
+
const probedAt = Date.now();
|
|
54
|
+
this.entries.set(home, { status, probedAt, nextReadAt: probedAt
|
|
55
|
+
+ (status.state === 'unavailable' || status.state === 'quota_unavailable' ? 15_000 : 5 * 60_000) });
|
|
56
|
+
}
|
|
57
|
+
return status;
|
|
58
|
+
})();
|
|
59
|
+
this.pending.set(home, work);
|
|
60
|
+
try {
|
|
61
|
+
return await work;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
if (this.pending.get(home) === work)
|
|
65
|
+
this.pending.delete(home);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
invalidate(home) {
|
|
69
|
+
this.entries.delete(home);
|
|
70
|
+
this.pending.delete(home);
|
|
71
|
+
this.generations.set(home, (this.generations.get(home) ?? 0) + 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function record(value) {
|
|
75
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
76
|
+
? value : null;
|
|
77
|
+
}
|
|
78
|
+
function projectWindow(name, value, reached) {
|
|
79
|
+
const bucket = record(value);
|
|
80
|
+
if (!bucket)
|
|
81
|
+
return null;
|
|
82
|
+
const used = bucket.usedPercent;
|
|
83
|
+
const remaining = bucket.remainingPercent;
|
|
84
|
+
const percent = reached ? 0 : typeof remaining === 'number' ? remaining
|
|
85
|
+
: typeof used === 'number' ? 100 - used : NaN;
|
|
86
|
+
if (!Number.isFinite(percent) || percent < 0 || percent > 100)
|
|
87
|
+
return null;
|
|
88
|
+
const rawReset = bucket.resetsAt ?? bucket.resetAt;
|
|
89
|
+
const duration = bucket.windowDurationMins;
|
|
90
|
+
const label = typeof duration === 'number' && Number.isSafeInteger(duration) && duration > 0
|
|
91
|
+
? duration % 1_440 === 0 ? `${duration / 1_440}d`
|
|
92
|
+
: duration % 60 === 0 ? `${duration / 60}h` : `${duration}m`
|
|
93
|
+
: name;
|
|
94
|
+
const resetAt = typeof rawReset === 'number'
|
|
95
|
+
? (rawReset < 10_000_000_000 ? rawReset * 1000 : rawReset)
|
|
96
|
+
: typeof rawReset === 'string' ? Date.parse(rawReset) : NaN;
|
|
97
|
+
return { name: label, remainingPercent: percent,
|
|
98
|
+
...(Number.isFinite(resetAt) && resetAt > 0 ? { resetAt } : {}) };
|
|
99
|
+
}
|
|
100
|
+
function projectWindows(value) {
|
|
101
|
+
const root = record(value);
|
|
102
|
+
if (!root)
|
|
103
|
+
return [];
|
|
104
|
+
const main = record(root.rateLimits);
|
|
105
|
+
const byId = record(root.rateLimitsByLimitId);
|
|
106
|
+
const windows = [];
|
|
107
|
+
const addBucket = (bucket, prefix) => {
|
|
108
|
+
const reached = bucket.rateLimitReachedType === 'rate_limit_reached';
|
|
109
|
+
for (const key of ['primary', 'secondary']) {
|
|
110
|
+
if (windows.length >= 12)
|
|
111
|
+
return;
|
|
112
|
+
const window = projectWindow(key, bucket[key], reached);
|
|
113
|
+
if (window)
|
|
114
|
+
windows.push({ ...window,
|
|
115
|
+
name: prefix ? `${prefix} ${window.name}` : window.name });
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const codex = record(byId?.codex);
|
|
119
|
+
if (codex)
|
|
120
|
+
addBucket(codex, '');
|
|
121
|
+
else if (main && (main.limitId === undefined || main.limitId === 'codex'))
|
|
122
|
+
addBucket(main, '');
|
|
123
|
+
if (byId) {
|
|
124
|
+
for (const [id, raw] of Object.entries(byId)) {
|
|
125
|
+
if (id === 'codex' || !/^[A-Za-z0-9_-]{1,40}$/.test(id))
|
|
126
|
+
continue;
|
|
127
|
+
const bucket = record(raw);
|
|
128
|
+
if (!bucket)
|
|
129
|
+
continue;
|
|
130
|
+
const label = typeof bucket.limitName === 'string'
|
|
131
|
+
&& bucket.limitName.length > 0 && bucket.limitName.length <= 120
|
|
132
|
+
&& !/[\u0000-\u001f\u007f]/.test(bucket.limitName)
|
|
133
|
+
? bucket.limitName : id;
|
|
134
|
+
addBucket(bucket, label);
|
|
135
|
+
if (windows.length >= 12)
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return windows;
|
|
140
|
+
}
|
|
141
|
+
function projectUsage(value, observedAt) {
|
|
142
|
+
const root = record(value);
|
|
143
|
+
const summary = record(root?.summary);
|
|
144
|
+
const lifetime = summary?.lifetimeTokens;
|
|
145
|
+
const lifetimeTokens = typeof lifetime === 'number' && Number.isSafeInteger(lifetime) && lifetime >= 0
|
|
146
|
+
? lifetime : undefined;
|
|
147
|
+
const today = new Date(observedAt).toISOString().slice(0, 10);
|
|
148
|
+
let todayTokens;
|
|
149
|
+
if (Array.isArray(root?.dailyUsageBuckets)) {
|
|
150
|
+
for (const value of root.dailyUsageBuckets) {
|
|
151
|
+
const bucket = record(value);
|
|
152
|
+
const tokens = bucket?.tokens;
|
|
153
|
+
if (bucket?.startDate === today && typeof tokens === 'number'
|
|
154
|
+
&& Number.isSafeInteger(tokens) && tokens >= 0) {
|
|
155
|
+
todayTokens = Math.max(todayTokens ?? 0, tokens);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return lifetimeTokens === undefined && todayTokens === undefined ? undefined
|
|
160
|
+
: { ...(lifetimeTokens === undefined ? {} : { lifetimeTokens }),
|
|
161
|
+
...(todayTokens === undefined ? {} : { todayTokens }), observedAt };
|
|
162
|
+
}
|
|
163
|
+
/** Read identity and quota through the official Codex App Server under one isolated CODEX_HOME. */
|
|
164
|
+
export async function inspectCodexAccount(home) {
|
|
165
|
+
const client = new CodexAppServerClient({
|
|
166
|
+
args: [...CODEX_SUBSCRIPTION_CONFIG_ARGS, 'app-server'],
|
|
167
|
+
env: buildCodexAppServerEnv(home), requestTimeoutMs: 6_000,
|
|
168
|
+
});
|
|
169
|
+
try {
|
|
170
|
+
const result = await client.readAccount(true);
|
|
171
|
+
const account = record(result.account);
|
|
172
|
+
if (!account || account.type !== 'chatgpt' || result.requiresOpenaiAuth !== true) {
|
|
173
|
+
return { state: 'reauth_required', windows: [] };
|
|
174
|
+
}
|
|
175
|
+
const [limitsResult, usageResult] = await Promise.allSettled([
|
|
176
|
+
client.readRateLimits(), client.readUsage(),
|
|
177
|
+
]);
|
|
178
|
+
const observedAt = Date.now();
|
|
179
|
+
const quotaAvailable = limitsResult.status === 'fulfilled';
|
|
180
|
+
const usageAvailable = usageResult.status === 'fulfilled';
|
|
181
|
+
return {
|
|
182
|
+
state: quotaAvailable ? 'ready' : 'quota_unavailable',
|
|
183
|
+
windows: projectWindows(quotaAvailable ? limitsResult.value : null),
|
|
184
|
+
...(quotaAvailable ? { observedAt } : {}),
|
|
185
|
+
...(usageAvailable ? { usage: projectUsage(usageResult.value, observedAt) } : { usageStale: true }),
|
|
186
|
+
...(typeof account.email === 'string' ? { email: account.email } : {}),
|
|
187
|
+
...(typeof account.planType === 'string' ? { plan: account.planType } : {}),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
client.close();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
export const codexStatusCache = new CodexStatusCache(inspectCodexAccount, {
|
|
195
|
+
minForcedIntervalMs: CODEX_STATUS_MIN_FORCED_INTERVAL_MS,
|
|
196
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RateLimitError } from './rate_limit.js';
|
|
2
|
-
import type { ToolDefinition } from '../../types.js';
|
|
2
|
+
import type { Message, ToolDefinition } from '../../types.js';
|
|
3
3
|
import type { AgentBackend, BackendObservation, BackendRequest, BackendResponse, ClientToolCallResult, LlmMessage, StreamEvent } from './types.js';
|
|
4
4
|
type JsonObject = Record<string, unknown>;
|
|
5
5
|
export interface CodexAppServerTransport {
|
|
@@ -9,6 +9,8 @@ export interface CodexAppServerTransport {
|
|
|
9
9
|
}
|
|
10
10
|
export declare const MAX_CODEX_APP_SERVER_FRAME_BYTES = 1048576;
|
|
11
11
|
export declare const MAX_CODEX_APP_SERVER_QUEUED_FRAMES = 1024;
|
|
12
|
+
/** Keep managed profiles on their own auth.json and ChatGPT subscription. */
|
|
13
|
+
export declare const CODEX_SUBSCRIPTION_CONFIG_ARGS: readonly ["-c", "cli_auth_credentials_store=\"file\"", "-c", "forced_login_method=\"chatgpt\""];
|
|
12
14
|
/** Codex App Server `SandboxMode` for model-executed commands and edits. */
|
|
13
15
|
export type CodexSandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access';
|
|
14
16
|
export interface CodexAppServerClientOptions {
|
|
@@ -29,6 +31,8 @@ export interface CodexAppServerClientOptions {
|
|
|
29
31
|
* Ignored when a `transport` is supplied (tests/fakes own their own env).
|
|
30
32
|
*/
|
|
31
33
|
env?: NodeJS.ProcessEnv;
|
|
34
|
+
/** Require the selected pooled profile to use ChatGPT subscription auth. */
|
|
35
|
+
requireChatGptSubscription?: boolean;
|
|
32
36
|
/**
|
|
33
37
|
* Sandbox for the threads ZTC starts. Defaults to `workspace-write`: an
|
|
34
38
|
* omitted `sandbox` makes the App Server fall back to read-only, and since
|
|
@@ -44,7 +48,7 @@ export interface CodexAppServerClientOptions {
|
|
|
44
48
|
/**
|
|
45
49
|
* Environment for the spawned `codex app-server`, pinned to the account's own
|
|
46
50
|
* credentials. Injects the per-account CODEX_HOME (when given) and SCRUBS a
|
|
47
|
-
* stray
|
|
51
|
+
* stray auth overrides from the parent env — otherwise the
|
|
48
52
|
* codex CLI would bill that API key instead of the account's ChatGPT
|
|
49
53
|
* subscription (auth.json under CODEX_HOME). This is the codex counterpart to
|
|
50
54
|
* {@link buildClaudeCliEnv}'s ANTHROPIC_API_KEY scrub, and it applies even with
|
|
@@ -118,6 +122,7 @@ export declare class CodexAppServerClient {
|
|
|
118
122
|
private command;
|
|
119
123
|
private args;
|
|
120
124
|
private env?;
|
|
125
|
+
private requireChatGptSubscription;
|
|
121
126
|
private requestTimeoutMs;
|
|
122
127
|
private turnTimeoutMs;
|
|
123
128
|
/** Sandbox requested on thread/start. Readable so the factory's plan-mode
|
|
@@ -142,6 +147,8 @@ export declare class CodexAppServerClient {
|
|
|
142
147
|
*/
|
|
143
148
|
private threadState;
|
|
144
149
|
constructor(options?: CodexAppServerClientOptions);
|
|
150
|
+
/** Submit text to the active Codex turn; return false so the TUI can retry on a new turn. */
|
|
151
|
+
steerUserMessage(message: Message): Promise<boolean>;
|
|
145
152
|
initialize(onActivity?: () => void): Promise<void>;
|
|
146
153
|
listModels(): Promise<string[]>;
|
|
147
154
|
streamTurn(options: CodexTurnOptions): AsyncIterable<StreamEvent>;
|
|
@@ -156,6 +163,8 @@ export declare class CodexAppServerClient {
|
|
|
156
163
|
account: unknown;
|
|
157
164
|
requiresOpenaiAuth: boolean;
|
|
158
165
|
}>;
|
|
166
|
+
readRateLimits(): Promise<unknown>;
|
|
167
|
+
readUsage(): Promise<unknown>;
|
|
159
168
|
private ensureTransport;
|
|
160
169
|
private request;
|
|
161
170
|
private requireAccount;
|
|
@@ -209,6 +218,7 @@ export declare class CodexAppServerBackend implements AgentBackend {
|
|
|
209
218
|
/** The sandbox this backend's thread runs under. Observable so the factory's
|
|
210
219
|
* plan-mode read-only downgrade can be asserted. */
|
|
211
220
|
get sandbox(): CodexSandboxMode;
|
|
221
|
+
steerUserMessage(message: Message): Promise<boolean>;
|
|
212
222
|
generate(request: BackendRequest): Promise<BackendResponse>;
|
|
213
223
|
generateStream(request: BackendRequest): AsyncIterable<StreamEvent>;
|
|
214
224
|
close(): void;
|
|
@@ -4,6 +4,10 @@ import { RateLimitError, parseResetAt } from './rate_limit.js';
|
|
|
4
4
|
import { CODEX_APP_SERVER_PROVIDER } from './provider_ids.js';
|
|
5
5
|
export const MAX_CODEX_APP_SERVER_FRAME_BYTES = 1_048_576;
|
|
6
6
|
export const MAX_CODEX_APP_SERVER_QUEUED_FRAMES = 1_024;
|
|
7
|
+
/** Keep managed profiles on their own auth.json and ChatGPT subscription. */
|
|
8
|
+
export const CODEX_SUBSCRIPTION_CONFIG_ARGS = [
|
|
9
|
+
'-c', 'cli_auth_credentials_store="file"', '-c', 'forced_login_method="chatgpt"',
|
|
10
|
+
];
|
|
7
11
|
const MAX_CODEX_APP_SERVER_STDOUT_CHUNK_BYTES = 4 * MAX_CODEX_APP_SERVER_FRAME_BYTES;
|
|
8
12
|
const DEFAULT_CODEX_APP_SERVER_REQUEST_TIMEOUT_MS = 30_000;
|
|
9
13
|
const MAX_CODEX_APP_SERVER_REQUEST_TIMEOUT_MS = 120_000;
|
|
@@ -15,7 +19,7 @@ const MAX_CODEX_APP_SERVER_MODELS = 2_000;
|
|
|
15
19
|
/**
|
|
16
20
|
* Environment for the spawned `codex app-server`, pinned to the account's own
|
|
17
21
|
* credentials. Injects the per-account CODEX_HOME (when given) and SCRUBS a
|
|
18
|
-
* stray
|
|
22
|
+
* stray auth overrides from the parent env — otherwise the
|
|
19
23
|
* codex CLI would bill that API key instead of the account's ChatGPT
|
|
20
24
|
* subscription (auth.json under CODEX_HOME). This is the codex counterpart to
|
|
21
25
|
* {@link buildClaudeCliEnv}'s ANTHROPIC_API_KEY scrub, and it applies even with
|
|
@@ -25,8 +29,11 @@ export function buildCodexAppServerEnv(codexHome, base = process.env) {
|
|
|
25
29
|
const env = { ...base };
|
|
26
30
|
if (codexHome)
|
|
27
31
|
env.CODEX_HOME = codexHome;
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
for (const name of [
|
|
33
|
+
'OPENAI_API_KEY', 'OPENAI_BASE_URL', 'CODEX_ACCESS_TOKEN', 'CODEX_API_KEY',
|
|
34
|
+
'OPENAI_IDENTITY_TOKEN_FILE', 'OPENAI_FEDERATION_RULE_ID', 'CODEX_SQLITE_HOME',
|
|
35
|
+
])
|
|
36
|
+
delete env[name];
|
|
30
37
|
return env;
|
|
31
38
|
}
|
|
32
39
|
/** Stable sha1 hex digest of a string — used to fingerprint reusable thread state. */
|
|
@@ -348,6 +355,7 @@ export class CodexAppServerClient {
|
|
|
348
355
|
command;
|
|
349
356
|
args;
|
|
350
357
|
env;
|
|
358
|
+
requireChatGptSubscription;
|
|
351
359
|
requestTimeoutMs;
|
|
352
360
|
turnTimeoutMs;
|
|
353
361
|
/** Sandbox requested on thread/start. Readable so the factory's plan-mode
|
|
@@ -377,10 +385,39 @@ export class CodexAppServerClient {
|
|
|
377
385
|
this.command = options.command || process.env.ZTC_CODEX_APP_SERVER_COMMAND || 'codex';
|
|
378
386
|
this.args = options.args || ['app-server'];
|
|
379
387
|
this.env = options.env;
|
|
388
|
+
this.requireChatGptSubscription = options.requireChatGptSubscription ?? false;
|
|
380
389
|
this.sandbox = options.sandbox || 'workspace-write';
|
|
381
390
|
this.requestTimeoutMs = resolveRequestTimeoutMs(options.requestTimeoutMs);
|
|
382
391
|
this.turnTimeoutMs = resolveTurnTimeoutMs(options.turnTimeoutMs);
|
|
383
392
|
}
|
|
393
|
+
/** Submit text to the active Codex turn; return false so the TUI can retry on a new turn. */
|
|
394
|
+
async steerUserMessage(message) {
|
|
395
|
+
// The app-server wire input below is text-only. Keep attachments on ZTC's
|
|
396
|
+
// ordinary next-turn path until image steering has an exact conversion.
|
|
397
|
+
if (message.attachments?.length)
|
|
398
|
+
return false;
|
|
399
|
+
const active = this.turnWaiters.filter(waiter => waiter.turnId && !waiter.terminalReceived);
|
|
400
|
+
if (active.length !== 1)
|
|
401
|
+
return false;
|
|
402
|
+
const waiter = active[0];
|
|
403
|
+
try {
|
|
404
|
+
const reply = await this.request('turn/steer', {
|
|
405
|
+
threadId: waiter.threadId,
|
|
406
|
+
expectedTurnId: waiter.turnId,
|
|
407
|
+
input: [{ type: 'text', text: message.content, text_elements: [] }],
|
|
408
|
+
clientUserMessageId: message.id,
|
|
409
|
+
});
|
|
410
|
+
if (reply.turnId !== waiter.turnId)
|
|
411
|
+
return false;
|
|
412
|
+
if (this.threadState?.threadId === waiter.threadId) {
|
|
413
|
+
this.threadState.acceptedSteering.push(message.content);
|
|
414
|
+
}
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
384
421
|
async initialize(onActivity) {
|
|
385
422
|
if (this.initialized) {
|
|
386
423
|
return;
|
|
@@ -507,10 +544,14 @@ export class CodexAppServerClient {
|
|
|
507
544
|
// cached thread is the wrong shape and must not be reused.
|
|
508
545
|
const signature = this.threadSignature(options, dynamicToolsPayload);
|
|
509
546
|
const cached = this.threadState;
|
|
547
|
+
const gap = cached ? options.messages.slice(cached.knownCount) : [];
|
|
548
|
+
const userGap = gap.filter(message => message.role === 'user');
|
|
510
549
|
const canReuse = Boolean(cached
|
|
511
550
|
&& cached.signature === signature
|
|
512
551
|
&& options.messages.length > cached.knownCount
|
|
513
|
-
&& sha1(JSON.stringify(options.messages.slice(0, cached.knownCount))) === cached.prefixHash
|
|
552
|
+
&& sha1(JSON.stringify(options.messages.slice(0, cached.knownCount))) === cached.prefixHash
|
|
553
|
+
&& userGap.length > cached.acceptedSteering.length
|
|
554
|
+
&& cached.acceptedSteering.every((text, index) => this.flattenMessageContent(userGap[index]) === text));
|
|
514
555
|
let threadId;
|
|
515
556
|
let toolsActive;
|
|
516
557
|
let input;
|
|
@@ -522,9 +563,8 @@ export class CodexAppServerClient {
|
|
|
522
563
|
// Send only the user messages added since the thread's last turn, as bare
|
|
523
564
|
// text. Assistant messages in the gap are the thread's OWN replies — it
|
|
524
565
|
// already holds them, so they're dropped from the input.
|
|
525
|
-
const gap = options.messages.slice(cached.knownCount);
|
|
526
566
|
input = [
|
|
527
|
-
{ type: 'text', text:
|
|
567
|
+
{ type: 'text', text: userGap.slice(cached.acceptedSteering.length).map((m) => this.flattenMessageContent(m)).join('\n\n'), text_elements: [] },
|
|
528
568
|
...this.collectLocalImages(gap),
|
|
529
569
|
];
|
|
530
570
|
}
|
|
@@ -617,6 +657,7 @@ export class CodexAppServerClient {
|
|
|
617
657
|
knownCount: options.messages.length,
|
|
618
658
|
prefixHash: sha1(JSON.stringify(options.messages)),
|
|
619
659
|
signature: this.threadSignature(options, toolsActive ? dynamicToolsPayload : null),
|
|
660
|
+
acceptedSteering: [],
|
|
620
661
|
};
|
|
621
662
|
if (options.signal?.aborted) {
|
|
622
663
|
abort();
|
|
@@ -710,6 +751,14 @@ export class CodexAppServerClient {
|
|
|
710
751
|
await this.initialize(onActivity);
|
|
711
752
|
return await this.request('account/read', { refreshToken }, onActivity);
|
|
712
753
|
}
|
|
754
|
+
async readRateLimits() {
|
|
755
|
+
await this.initialize();
|
|
756
|
+
return await this.request('account/rateLimits/read', {});
|
|
757
|
+
}
|
|
758
|
+
async readUsage() {
|
|
759
|
+
await this.initialize();
|
|
760
|
+
return await this.request('account/usage/read', {});
|
|
761
|
+
}
|
|
713
762
|
ensureTransport() {
|
|
714
763
|
if (!this.transport) {
|
|
715
764
|
this.transport = this.transportFactory
|
|
@@ -770,6 +819,10 @@ export class CodexAppServerClient {
|
|
|
770
819
|
if (!result.account) {
|
|
771
820
|
throw new Error("codex_app_server is not logged in. Run 'codex login status' or 'codex login' before using ZTC's official Codex provider.");
|
|
772
821
|
}
|
|
822
|
+
if (this.requireChatGptSubscription
|
|
823
|
+
&& (result.account.type !== 'chatgpt' || result.requiresOpenaiAuth !== true)) {
|
|
824
|
+
throw new Error('codex_app_server requires a ChatGPT subscription login; an API key or alternate provider is active. Run codex login for this account.');
|
|
825
|
+
}
|
|
773
826
|
}
|
|
774
827
|
sendNotification(method) {
|
|
775
828
|
this.ensureTransport();
|
|
@@ -918,6 +971,10 @@ export class CodexAppServerClient {
|
|
|
918
971
|
inputTokens,
|
|
919
972
|
outputTokens,
|
|
920
973
|
totalTokens,
|
|
974
|
+
...(typeof last.cachedInputTokens === 'number'
|
|
975
|
+
&& Number.isSafeInteger(last.cachedInputTokens)
|
|
976
|
+
&& last.cachedInputTokens >= 0
|
|
977
|
+
? { cachedInputTokens: last.cachedInputTokens } : {}),
|
|
921
978
|
estimated: false,
|
|
922
979
|
...(typeof contextWindow === 'number' && contextWindow > 0 ? { contextWindow } : {}),
|
|
923
980
|
};
|
|
@@ -1257,6 +1314,9 @@ export class CodexAppServerBackend {
|
|
|
1257
1314
|
get sandbox() {
|
|
1258
1315
|
return this.client.sandbox;
|
|
1259
1316
|
}
|
|
1317
|
+
async steerUserMessage(message) {
|
|
1318
|
+
return this.client.steerUserMessage(message);
|
|
1319
|
+
}
|
|
1260
1320
|
async generate(request) {
|
|
1261
1321
|
const content = [];
|
|
1262
1322
|
let stopReason = 'completed';
|
|
@@ -17,6 +17,7 @@ export { ClaudeCliBackend, ClaudeCliError, CLAUDE_CODE_AUTH_SENTINEL, hasUsableC
|
|
|
17
17
|
export type { AgentBackend, BackendObservation, BackendRequest, BackendResponse, ContentBlock, TextBlock, ToolUseBlock, LlmMessage, RequestContentBlock, RequestVideoBlock, ToolResultBlock, TokenUsage, StreamEvent } from './types.js';
|
|
18
18
|
import type { AgentBackend, BackendRequest, BackendResponse, StreamEvent } from './types.js';
|
|
19
19
|
import { AccountPool, type RotationMode } from './account_rotation.js';
|
|
20
|
+
import type { Message } from '../../types.js';
|
|
20
21
|
export interface CreateBackendOptions {
|
|
21
22
|
apiKey: string;
|
|
22
23
|
baseUrl?: string;
|
|
@@ -58,6 +59,7 @@ export declare class TracedBackend implements AgentBackend {
|
|
|
58
59
|
private readonly provider;
|
|
59
60
|
private readonly backend;
|
|
60
61
|
constructor(provider: string, backend: AgentBackend);
|
|
62
|
+
steerUserMessage(message: Message): Promise<boolean>;
|
|
61
63
|
/** Forward disposal so a process-backed inner (RotatingBackend, codex child)
|
|
62
64
|
* is closed when the agent is. Without this, tracing (ZTC_BACKEND_TRACE=1)
|
|
63
65
|
* leaks the child on every agent swap/dispose. */
|
|
@@ -21,7 +21,7 @@ import { OpenAICompatibleBackend } from './openai_compatible.js';
|
|
|
21
21
|
import { InceptionBackend } from './inception.js';
|
|
22
22
|
import { GeminiBackend } from './gemini.js';
|
|
23
23
|
import { KimiBackend } from './kimi.js';
|
|
24
|
-
import { CodexAppServerBackend, buildCodexAppServerEnv } from './codex_app_server.js';
|
|
24
|
+
import { CodexAppServerBackend, CODEX_SUBSCRIPTION_CONFIG_ARGS, buildCodexAppServerEnv } from './codex_app_server.js';
|
|
25
25
|
import { CodexAuthManager, CodexAuthBackend } from './codex_auth.js';
|
|
26
26
|
import { ClarifaiBackend } from './clarifai.js';
|
|
27
27
|
import { ClaudeCodeAuthBackend, ClaudeCodeAuthManager } from './claude_code_auth.js';
|
|
@@ -38,6 +38,9 @@ export class TracedBackend {
|
|
|
38
38
|
this.provider = provider;
|
|
39
39
|
this.backend = backend;
|
|
40
40
|
}
|
|
41
|
+
async steerUserMessage(message) {
|
|
42
|
+
return this.backend.steerUserMessage?.(message) ?? false;
|
|
43
|
+
}
|
|
41
44
|
/** Forward disposal so a process-backed inner (RotatingBackend, codex child)
|
|
42
45
|
* is closed when the agent is. Without this, tracing (ZTC_BACKEND_TRACE=1)
|
|
43
46
|
* leaks the child on every agent swap/dispose. */
|
|
@@ -98,14 +101,16 @@ function traced(provider, backend) {
|
|
|
98
101
|
* Replaces duplicated switch statements in sim/llm.ts, agent/subagent.ts, optimize/llm.ts, etc.
|
|
99
102
|
*/
|
|
100
103
|
export function createBackendForProvider(provider, opts) {
|
|
101
|
-
// Auto-rotate: when the caller opted in and >1
|
|
102
|
-
//
|
|
104
|
+
// Auto-rotate non-Codex providers only: when the caller opted in and >1
|
|
105
|
+
// account is REGISTERED, wrap the concrete backend so a 429 cools the active account and
|
|
103
106
|
// fails over. Gate on registered (not ready) count: with one ready account and
|
|
104
107
|
// the config-active pointer on a different cooling/logged-out one, a ready-only
|
|
105
108
|
// gate would build directly on the stale active and hard-fail the first turn —
|
|
106
109
|
// the wrapper's selectActive is what moves off it. The inner build recurses
|
|
107
110
|
// with rotation cleared + the chosen account's source.
|
|
108
|
-
if (
|
|
111
|
+
if (normalizeProviderId(provider) !== CODEX_APP_SERVER_PROVIDER
|
|
112
|
+
&& normalizeProviderId(provider) !== CODEX_LEGACY_PROVIDER
|
|
113
|
+
&& opts.rotation && opts.rotationPool && opts.rotationPool.accountsFor(provider).length > 1) {
|
|
109
114
|
const pool = opts.rotationPool;
|
|
110
115
|
return traced(provider, new RotatingBackend({
|
|
111
116
|
provider,
|
|
@@ -152,12 +157,15 @@ export function createBackendForProvider(provider, opts) {
|
|
|
152
157
|
case CODEX_APP_SERVER_PROVIDER:
|
|
153
158
|
return traced(provider, new CodexAppServerBackend(undefined, {
|
|
154
159
|
command: opts.codexAppServerCommand,
|
|
155
|
-
args: opts.
|
|
160
|
+
args: opts.codexHome
|
|
161
|
+
? [...CODEX_SUBSCRIPTION_CONFIG_ARGS, ...(opts.codexAppServerArgs ?? ['app-server'])]
|
|
162
|
+
: opts.codexAppServerArgs,
|
|
156
163
|
sandbox: opts.codexAppServerSandbox,
|
|
157
164
|
// Point the spawned app-server at this account's codex home AND scrub a
|
|
158
165
|
// stray OPENAI_API_KEY so it uses the account's ChatGPT subscription, not
|
|
159
166
|
// an API key (mirrors the claude_code ANTHROPIC_API_KEY scrub).
|
|
160
167
|
env: buildCodexAppServerEnv(opts.codexHome),
|
|
168
|
+
requireChatGptSubscription: opts.codexHome !== undefined,
|
|
161
169
|
}));
|
|
162
170
|
case CODEX_LEGACY_PROVIDER: {
|
|
163
171
|
// Per-account: read this account's auth.json (default ~/.codex when unset).
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenAI backend — smart router between Chat Completions and Responses API.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Codex models and GPT-6 Sol/Luna use Responses so reasoning and tool calls
|
|
5
|
+
* can be combined. Everything else goes through Chat Completions.
|
|
6
6
|
*/
|
|
7
7
|
import { AgentBackend, BackendRequest, BackendResponse, StreamEvent } from './types.js';
|
|
8
8
|
interface OpenAIConfig {
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenAI backend — smart router between Chat Completions and Responses API.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Codex models and GPT-6 Sol/Luna use Responses so reasoning and tool calls
|
|
5
|
+
* can be combined. Everything else goes through Chat Completions.
|
|
6
6
|
*/
|
|
7
7
|
import { OpenAICompatibleBackend } from './openai_compatible.js';
|
|
8
8
|
import { OpenAIResponsesBackend } from './openai_responses.js';
|
|
9
|
-
/** Models
|
|
9
|
+
/** Models routed to the Responses API. */
|
|
10
10
|
const RESPONSES_API_PATTERNS = [
|
|
11
11
|
/codex/i, // codex-mini-latest, gpt-5.1-codex-mini, gpt-5.2-codex, etc.
|
|
12
|
+
/^gpt-6-(?:sol|luna)$/i,
|
|
12
13
|
];
|
|
13
14
|
function needsResponsesAPI(model) {
|
|
14
15
|
return RESPONSES_API_PATTERNS.some(p => p.test(model));
|
|
@@ -36,11 +36,11 @@ export class OpenAICompatibleBackend {
|
|
|
36
36
|
* and generateStream(). generateStream() adds `stream`/`stream_options`. */
|
|
37
37
|
async buildCall(request) {
|
|
38
38
|
const url = `${this.baseUrl}${this.apiPath}`;
|
|
39
|
-
// O-series (o1, o3, o4-mini) and GPT-5
|
|
39
|
+
// O-series (o1, o3, o4-mini) and GPT-5/6 use max_completion_tokens instead of
|
|
40
40
|
// max_tokens; Moonshot marks max_tokens "Deprecated, please refer to
|
|
41
41
|
// max_completion_tokens" for the Kimi models too.
|
|
42
42
|
const useCompletionTokens = /^o\d/.test(request.model)
|
|
43
|
-
|| /^gpt-
|
|
43
|
+
|| /^gpt-[56]/.test(request.model)
|
|
44
44
|
|| isKimiModel(request.model);
|
|
45
45
|
// Cap max_tokens for models with lower limits (gpt-4o: 16384)
|
|
46
46
|
const isGpt4o = request.model.startsWith('gpt-4o');
|
|
@@ -49,19 +49,19 @@ export class OpenAICompatibleBackend {
|
|
|
49
49
|
const tokenParam = effectiveMaxTokens === undefined ? {} : useCompletionTokens
|
|
50
50
|
? { max_completion_tokens: effectiveMaxTokens }
|
|
51
51
|
: { max_tokens: effectiveMaxTokens };
|
|
52
|
+
const isGpt6Model = /^gpt-6-(?:sol|luna)$/i.test(request.model);
|
|
53
|
+
const reasoningEffort = (request.model === 'gpt-5.6-sol' || isGpt6Model) && request.tools.length > 0
|
|
54
|
+
? 'none'
|
|
55
|
+
: isGpt6Model ? request.codexEffort : undefined;
|
|
52
56
|
const body = {
|
|
53
57
|
...this.extraBody,
|
|
54
58
|
...(this.requestBodyExtras?.(request) ?? {}),
|
|
55
59
|
model: request.model,
|
|
56
60
|
...tokenParam,
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
// Tool-free turns keep the model's normal reasoning default.
|
|
62
|
-
...(request.model === 'gpt-5.6-sol' && request.tools.length > 0
|
|
63
|
-
? { reasoning_effort: 'none' }
|
|
64
|
-
: {}),
|
|
61
|
+
// Chat Completions accepts GPT-6 Sol/Luna function tools only with
|
|
62
|
+
// reasoning disabled. The direct OpenAI backend uses Responses instead;
|
|
63
|
+
// keep this rule for explicitly configured compatible endpoints.
|
|
64
|
+
...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
|
|
65
65
|
messages: buildOpenAIMessages(request),
|
|
66
66
|
...(request.tools.length > 0 ? { tools: buildOpenAIToolDefs(request.tools) } : {})
|
|
67
67
|
};
|
|
@@ -52,6 +52,22 @@ interface ResponsesReasoning {
|
|
|
52
52
|
summary: unknown[];
|
|
53
53
|
}
|
|
54
54
|
type ResponsesOutputItem = ResponsesMessage | ResponsesFunctionCall | ResponsesReasoning;
|
|
55
|
+
interface ResponsesAPIResponse {
|
|
56
|
+
id: string;
|
|
57
|
+
status: string;
|
|
58
|
+
incomplete_details?: {
|
|
59
|
+
reason?: string;
|
|
60
|
+
} | null;
|
|
61
|
+
output: ResponsesOutputItem[];
|
|
62
|
+
usage?: {
|
|
63
|
+
input_tokens?: number;
|
|
64
|
+
output_tokens?: number;
|
|
65
|
+
total_tokens?: number;
|
|
66
|
+
input_tokens_details?: {
|
|
67
|
+
cached_tokens?: number;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
}
|
|
55
71
|
export declare function isValidResponsesOutputItem(value: unknown): value is Record<string, unknown>;
|
|
56
72
|
export declare function requireValidResponsesOutputItem(value: unknown): asserts value is Record<string, unknown>;
|
|
57
73
|
/** A terminal event must carry a valid response shape or follow real output. */
|
|
@@ -98,11 +114,7 @@ export declare function parseResponsesOutput(data: {
|
|
|
98
114
|
reason?: string;
|
|
99
115
|
} | null;
|
|
100
116
|
output: ResponsesOutputItem[];
|
|
101
|
-
usage?:
|
|
102
|
-
input_tokens?: number;
|
|
103
|
-
output_tokens?: number;
|
|
104
|
-
total_tokens?: number;
|
|
105
|
-
};
|
|
117
|
+
usage?: ResponsesAPIResponse['usage'];
|
|
106
118
|
}): BackendResponse;
|
|
107
119
|
/**
|
|
108
120
|
* Consume a Responses API SSE response into incremental StreamEvents. Yields
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* - Token param → `max_output_tokens`
|
|
14
14
|
*/
|
|
15
15
|
import { BaseBackend } from './base.js';
|
|
16
|
+
import { parseTokenUsage } from './token_usage.js';
|
|
16
17
|
function isRecord(value) {
|
|
17
18
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
18
19
|
}
|
|
@@ -252,9 +253,6 @@ export function parseResponsesOutput(data) {
|
|
|
252
253
|
}
|
|
253
254
|
}
|
|
254
255
|
}
|
|
255
|
-
const inputTokens = data.usage?.input_tokens ?? 0;
|
|
256
|
-
const outputTokens = data.usage?.output_tokens ?? 0;
|
|
257
|
-
const totalTokens = data.usage?.total_tokens ?? (inputTokens + outputTokens);
|
|
258
256
|
// An incomplete terminal response outranks any partial function call it may
|
|
259
257
|
// contain. Executing that payload as a normal tool_use would turn truncated
|
|
260
258
|
// JSON into a side effect; the Agent consumes this stop state fail-closed.
|
|
@@ -266,7 +264,7 @@ export function parseResponsesOutput(data) {
|
|
|
266
264
|
return {
|
|
267
265
|
content: blocks,
|
|
268
266
|
stopReason,
|
|
269
|
-
usage: data.usage ?
|
|
267
|
+
usage: data.usage ? parseTokenUsage(data.usage) : undefined,
|
|
270
268
|
...(reasoningParts.length > 0 ? { reasoning: reasoningParts.join('\n\n') } : {}),
|
|
271
269
|
};
|
|
272
270
|
}
|
|
@@ -11,7 +11,7 @@ export function parseTokenUsage(value) {
|
|
|
11
11
|
const outputTokens = counter(raw.completion_tokens ?? raw.output_tokens) ?? 0;
|
|
12
12
|
const usage = { inputTokens, outputTokens, totalTokens: counter(raw.total_tokens) ?? inputTokens + outputTokens };
|
|
13
13
|
const optional = {
|
|
14
|
-
cachedInputTokens: counter(raw.cached_input_tokens ?? record(raw.prompt_tokens_details).cached_tokens ?? raw.prompt_cache_hit_tokens),
|
|
14
|
+
cachedInputTokens: counter(raw.cached_input_tokens ?? record(raw.input_tokens_details).cached_tokens ?? record(raw.prompt_tokens_details).cached_tokens ?? raw.prompt_cache_hit_tokens),
|
|
15
15
|
cacheWriteInputTokens: counter(raw.cache_creation_input_tokens),
|
|
16
16
|
reasoningTokens: counter(record(raw.completion_tokens_details).reasoning_tokens ?? raw.reasoning_tokens),
|
|
17
17
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ToolDefinition } from '../../types.js';
|
|
2
|
+
import type { Message } from '../../types.js';
|
|
2
3
|
export interface RequestTextBlock {
|
|
3
4
|
type: 'text';
|
|
4
5
|
text: string;
|
|
@@ -257,6 +258,8 @@ export type StreamEvent = {
|
|
|
257
258
|
stopReason: string;
|
|
258
259
|
};
|
|
259
260
|
export interface AgentBackend {
|
|
261
|
+
/** Deliver user input into a provider-owned active turn when supported. */
|
|
262
|
+
steerUserMessage?(message: Message): Promise<boolean>;
|
|
260
263
|
/**
|
|
261
264
|
* True when the backend owns a usable authentication source independently
|
|
262
265
|
* of AgentConfig.apiKey (for example, a host-provided rotating token).
|