@love-moon/ai-sdk 0.13.2 → 0.14.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/CHANGELOG.md +70 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.js +22 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/providers/chat-web-session.d.ts +18 -0
- package/dist/providers/chat-web-session.js +44 -1
- package/dist/providers/claude-agent-sdk-session.d.ts +34 -0
- package/dist/providers/claude-agent-sdk-session.js +33 -0
- package/dist/providers/codex-app-server-session.d.ts +76 -6
- package/dist/providers/codex-app-server-session.js +135 -21
- package/dist/providers/codex-exec-session.d.ts +14 -0
- package/dist/providers/codex-exec-session.js +20 -1
- package/dist/providers/copilot-sdk-session.d.ts +21 -0
- package/dist/providers/copilot-sdk-session.js +55 -12
- package/dist/providers/dsh-sdk-session.d.ts +53 -3
- package/dist/providers/dsh-sdk-session.js +24 -1
- package/dist/providers/kimi-cli-session.d.ts +22 -0
- package/dist/providers/kimi-cli-session.js +23 -1
- package/dist/providers/kimi-print-session.d.ts +13 -0
- package/dist/providers/kimi-print-session.js +29 -0
- package/dist/providers/kimi-slash-commands.d.ts +1 -0
- package/dist/providers/kimi-slash-commands.js +23 -0
- package/dist/providers/opencode-sdk-session.d.ts +52 -3
- package/dist/providers/opencode-sdk-session.js +33 -1
- package/dist/shared.d.ts +46 -0
- package/dist/shared.js +18 -1
- package/dist/transports/kimi-wire-transport.d.ts +12 -0
- package/dist/transports/kimi-wire-transport.js +40 -1
- package/dist/turn-usage.d.ts +31 -0
- package/dist/turn-usage.js +44 -0
- package/dist/worker.js +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# @love-moon/ai-sdk
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e168077: Support `/clear` in task chats. Fire detects a bare `/clear` message (like
|
|
8
|
+
`/compact`) and drops the AI's context instead of sending the text to the model.
|
|
9
|
+
Sessions advertise `capabilities.clear` and implement the new optional
|
|
10
|
+
`runClear()` on top of each backend's own native reset, so the backend process,
|
|
11
|
+
browser or RPC connection stays up:
|
|
12
|
+
|
|
13
|
+
- claude: native `/clear` slash command (the SDK's `conversation_reset` reports
|
|
14
|
+
the new conversation id)
|
|
15
|
+
- codex app-server: `thread/start` on the live transport
|
|
16
|
+
- codex exec: drops the replayed history, which is the whole context for a
|
|
17
|
+
stateless CLI
|
|
18
|
+
- copilot: `createSession()` on the same client; the old conversation is
|
|
19
|
+
detached, not deleted
|
|
20
|
+
- kimi wire and legacy print: the CLI's built-in `/clear` (prompt mode opts out)
|
|
21
|
+
- opencode: `session.create` on the same `opencode serve`
|
|
22
|
+
- dsh: rotates onto a fresh wire session id and keeps the runtime subprocess
|
|
23
|
+
- chat-web: `newChat()` in the same browser and profile
|
|
24
|
+
|
|
25
|
+
Backends without the capability (external providers) fall back to closing the
|
|
26
|
+
session and continuing on a brand-new one. Either way the task's chat history is
|
|
27
|
+
kept and the task is rebound to the session the backend ends up on, so a later
|
|
28
|
+
restart resumes the cleared conversation rather than the old one. The codex
|
|
29
|
+
fresh-session bootstrap lock now covers that fallback too, and the kimi wire
|
|
30
|
+
transport drops configured `--continue`/`--resume`/`--session` flags (like kimi
|
|
31
|
+
print already did) so they cannot reattach a cleared session to the old
|
|
32
|
+
conversation. Two caveats: the configured `pre_prompt` is not re-sent to the
|
|
33
|
+
cleared session, and a chat-web task running with
|
|
34
|
+
`CONDUCTOR_AI_SDK_DISABLE_WORKER=1` can only clear natively — its fallback would
|
|
35
|
+
hit the browser profile lock, because in that mode the lock owner is fire itself.
|
|
36
|
+
|
|
37
|
+
- 12a829c: Show each AI reply's token usage beside its timestamp: the turn's tokens, the
|
|
38
|
+
task total after it, and the input cache share (the part of the turn's input
|
|
39
|
+
served from the prompt cache).
|
|
40
|
+
|
|
41
|
+
- `@love-moon/ai-sdk`: new `summarizeTurnUsage(usage)` normalizes a turn's usage
|
|
42
|
+
into `{ tokens, inputTokens, cachedInputTokens }` (Claude: fresh input + cache
|
|
43
|
+
writes + cache reads; Codex: input already includes cached). The Codex
|
|
44
|
+
app-server provider's turn/goal `usage` now also carries `turnInputTokens` and
|
|
45
|
+
`turnCachedInputTokens`.
|
|
46
|
+
- `@love-moon/conductor-sdk`: `sendTurnUsage` accepts `input_tokens`,
|
|
47
|
+
`cached_input_tokens` and the reply's `message_id`.
|
|
48
|
+
- `conductor fire` reports a finished turn's usage after its reply, naming that
|
|
49
|
+
reply (for streamed sessions, the turn's last streamed reply), so the server
|
|
50
|
+
records it on the message (`metadata.turn_usage`) and pushes it to open chats.
|
|
51
|
+
|
|
52
|
+
### Patch Changes
|
|
53
|
+
|
|
54
|
+
- d4f6fb9: Add opt-in Codex reply timing diagnostics to distinguish first-text generation
|
|
55
|
+
from buffering before a complete message is forwarded. Traces contain IDs and
|
|
56
|
+
monotonic durations, never prompt or reply text.
|
|
57
|
+
|
|
58
|
+
## 0.13.3
|
|
59
|
+
|
|
60
|
+
### Patch Changes
|
|
61
|
+
|
|
62
|
+
- 0859251: Show Codex progress notes as soon as Codex finishes writing them. The session
|
|
63
|
+
did not recognize the app-server's `agentMessage` items, so a commentary message
|
|
64
|
+
was held back until the next message started or the turn ended, often minutes
|
|
65
|
+
later during long tool runs.
|
|
66
|
+
- c1b66d1: Fix Codex tasks that stopped mid-reply after the agent spawned sub-agents. In
|
|
67
|
+
multi-agent mode the Codex app-server also streams the sub-agent threads, and
|
|
68
|
+
the first sub-agent to finish ended the parent turn. The parent's later
|
|
69
|
+
messages and final answer were dropped, and a sub-agent's report could show up
|
|
70
|
+
as the AI reply. The session now ignores notifications from other threads.
|
|
71
|
+
- f6dbd6b: Show completed Codex replies while subsequent tools are still running, and keep replies intact when their final text and completion arrive together.
|
|
72
|
+
|
|
3
73
|
## 0.13.2
|
|
4
74
|
|
|
5
75
|
### Patch Changes
|
package/dist/client.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
19
19
|
capabilities: {
|
|
20
20
|
goal?: boolean | undefined;
|
|
21
21
|
compact?: boolean | undefined;
|
|
22
|
+
clear?: boolean | undefined;
|
|
22
23
|
};
|
|
23
24
|
};
|
|
24
25
|
sessionMessageHandler: any;
|
|
@@ -53,6 +54,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
53
54
|
capabilities: {
|
|
54
55
|
goal?: boolean | undefined;
|
|
55
56
|
compact?: boolean | undefined;
|
|
57
|
+
clear?: boolean | undefined;
|
|
56
58
|
};
|
|
57
59
|
};
|
|
58
60
|
usesSessionFileReplyStream(): boolean;
|
|
@@ -72,6 +74,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
72
74
|
runTurn(promptText: any, options?: {}): Promise<any>;
|
|
73
75
|
runGoal(goal: any, options?: {}): Promise<any>;
|
|
74
76
|
runCompact(request?: {}, options?: {}): Promise<any>;
|
|
77
|
+
runClear(request?: {}, options?: {}): Promise<any>;
|
|
75
78
|
getGoal(): Promise<any>;
|
|
76
79
|
clearGoal(): Promise<any>;
|
|
77
80
|
close(): Promise<void>;
|
|
@@ -112,6 +115,7 @@ declare class LocalAiSessionProxy extends EventEmitter<[never]> {
|
|
|
112
115
|
capabilities: {
|
|
113
116
|
goal?: boolean | undefined;
|
|
114
117
|
compact?: boolean | undefined;
|
|
118
|
+
clear?: boolean | undefined;
|
|
115
119
|
};
|
|
116
120
|
};
|
|
117
121
|
readyPromise: Promise<any>;
|
|
@@ -131,6 +135,7 @@ declare class LocalAiSessionProxy extends EventEmitter<[never]> {
|
|
|
131
135
|
runTurn(promptText: any, options?: {}): Promise<any>;
|
|
132
136
|
runGoal(goal: any, options?: {}): Promise<any>;
|
|
133
137
|
runCompact(request?: {}, options?: {}): Promise<any>;
|
|
138
|
+
runClear(request?: {}, options?: {}): Promise<any>;
|
|
134
139
|
getGoal(): Promise<any>;
|
|
135
140
|
clearGoal(): Promise<any>;
|
|
136
141
|
close(): Promise<void>;
|
package/dist/client.js
CHANGED
|
@@ -14,6 +14,11 @@ function createUnsupportedCompactError(name) {
|
|
|
14
14
|
err.reason = "unsupported_compact";
|
|
15
15
|
return err;
|
|
16
16
|
}
|
|
17
|
+
function createUnsupportedClearError(name) {
|
|
18
|
+
const err = new Error(`runClear() is not supported by backend session (${name || "unknown"}).`);
|
|
19
|
+
err.reason = "unsupported_clear";
|
|
20
|
+
return err;
|
|
21
|
+
}
|
|
17
22
|
const WORKER_PATH = fileURLToPath(new URL("./worker.js", import.meta.url));
|
|
18
23
|
function sanitizeOptionsForWorker(options = {}) {
|
|
19
24
|
const sanitized = {};
|
|
@@ -232,6 +237,16 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
232
237
|
progressHandler: typeof onProgress === "function" ? onProgress : null,
|
|
233
238
|
});
|
|
234
239
|
}
|
|
240
|
+
async runClear(request = {}, options = {}) {
|
|
241
|
+
await this.readyPromise;
|
|
242
|
+
if (!this.snapshot?.capabilities || this.snapshot.capabilities.clear !== true) {
|
|
243
|
+
throw createUnsupportedClearError(this.snapshot?.provider || this.snapshot?.backend);
|
|
244
|
+
}
|
|
245
|
+
const { onProgress, ...restOptions } = options || {};
|
|
246
|
+
return this.callWorker("runClear", [request, restOptions], {
|
|
247
|
+
progressHandler: typeof onProgress === "function" ? onProgress : null,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
235
250
|
async getGoal() {
|
|
236
251
|
// Propagate worker-creation errors instead of masking them as "no goal".
|
|
237
252
|
// See N3 in the review.
|
|
@@ -699,6 +714,13 @@ class LocalAiSessionProxy extends EventEmitter {
|
|
|
699
714
|
}
|
|
700
715
|
return await session.runCompact(request, options);
|
|
701
716
|
}
|
|
717
|
+
async runClear(request = {}, options = {}) {
|
|
718
|
+
const session = await this.readyPromise;
|
|
719
|
+
if (this.snapshot?.capabilities?.clear !== true || typeof session.runClear !== "function") {
|
|
720
|
+
throw createUnsupportedClearError(session?.constructor?.name);
|
|
721
|
+
}
|
|
722
|
+
return await session.runClear(request, options);
|
|
723
|
+
}
|
|
702
724
|
async getGoal() {
|
|
703
725
|
const session = await this.readyPromise;
|
|
704
726
|
const capabilities = resolveSessionCapabilities(session);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { BUILT_IN_BACKENDS } from "./built-in-backends.js";
|
|
2
2
|
export { PROVIDER_MEDIA_CAPABILITIES } from "./media-adapters.js";
|
|
3
|
+
export { summarizeTurnUsage } from "./turn-usage.js";
|
|
3
4
|
export { createAiSession, RemoteAiSession } from "./client.js";
|
|
4
5
|
export { isClaudeRootPermissionRestricted, resolveClaudeCommandForRoot, resolveClaudePermissionPolicy } from "./providers/claude-agent-sdk-session.js";
|
|
5
6
|
export { appendContextFilesToPrompt, normalizeContextFiles } from "./context-files.js";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { BUILT_IN_BACKENDS } from "./built-in-backends.js";
|
|
|
3
3
|
export { PROVIDER_MEDIA_CAPABILITIES } from "./media-adapters.js";
|
|
4
4
|
export { isClaudeRootPermissionRestricted, resolveClaudeCommandForRoot, resolveClaudePermissionPolicy, } from "./providers/claude-agent-sdk-session.js";
|
|
5
5
|
export { appendContextFilesToPrompt, normalizeContextFiles } from "./context-files.js";
|
|
6
|
+
export { summarizeTurnUsage } from "./turn-usage.js";
|
|
6
7
|
export { assertMediaCapabilities, fileMediaToBase64, fileMediaToDataUri, normalizeMediaInputs, resolveTurnMedia, } from "./media-input.js";
|
|
7
8
|
export { GOAL_STATUSES, TERMINAL_GOAL_STATUSES, isGoalStatus, isTerminalGoalStatus, } from "./shared.js";
|
|
8
9
|
export { AiManager, accountNameFromPath, checkInstall, checkInstallAll, checkNetwork, checkNetworkAll, getClaudeQuota, getCodexQuota, getCopilotQuota, getCurrentCodexAccount, getDshQuota, readCachedDshQuota, resolveDeepSeekCredential, getExternalQuota, getExternalQuotaList, getKimiQuota, listCodexAccounts, loadAiManagerConfig, normalizeExternalQuota, normalizeExternalQuotaList, parseAuthFile, parseAuthFileContents, parseCopilotQuotaSnapshots, readCachedCodexQuota, resolveClaudeCredential, switchCodexAccount, } from "./manager/index.js";
|
|
@@ -110,6 +110,7 @@ export class ChatWebSession extends EventEmitter<[never]> {
|
|
|
110
110
|
updated_at: string;
|
|
111
111
|
} | null;
|
|
112
112
|
capabilities: {
|
|
113
|
+
clear: boolean;
|
|
113
114
|
media: Readonly<{
|
|
114
115
|
image: "unsupported";
|
|
115
116
|
}>;
|
|
@@ -162,6 +163,23 @@ export class ChatWebSession extends EventEmitter<[never]> {
|
|
|
162
163
|
getChatWebModule(): Promise<any>;
|
|
163
164
|
boot(): Promise<void>;
|
|
164
165
|
bootInternal(): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Native clear: start a new conversation in the SAME browser and profile
|
|
168
|
+
* (chat-web clicks the provider's "new chat" control, falling back to
|
|
169
|
+
* navigating home — both land on an empty chat), so no Chromium relaunch and
|
|
170
|
+
* no re-login. The old conversation stays in the provider's account.
|
|
171
|
+
*
|
|
172
|
+
* The new `/c/{uuid}` only exists after the next reply, so we drop back to a
|
|
173
|
+
* synthetic id until then — `ensureSessionInfo()` reports `sessionIdDeferred`
|
|
174
|
+
* for exactly this window.
|
|
175
|
+
*/
|
|
176
|
+
runClear(): Promise<{
|
|
177
|
+
clear: {
|
|
178
|
+
status: string;
|
|
179
|
+
};
|
|
180
|
+
usage: null;
|
|
181
|
+
metadata: {};
|
|
182
|
+
}>;
|
|
165
183
|
/**
|
|
166
184
|
* Adopt the provider-side conversation id (ChatGPT /c/{uuid} or
|
|
167
185
|
* equivalent) as our canonical sessionId, update sessionInfo, and emit
|
|
@@ -212,7 +212,7 @@ export class ChatWebSession extends EventEmitter {
|
|
|
212
212
|
? { ready: true, command: this.providerConversationUrl() }
|
|
213
213
|
: null,
|
|
214
214
|
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
215
|
-
capabilities: { media: PROVIDER_MEDIA_CAPABILITIES[CHAT_WEB_SESSION_VARIANT] },
|
|
215
|
+
capabilities: { clear: true, media: PROVIDER_MEDIA_CAPABILITIES[CHAT_WEB_SESSION_VARIANT] },
|
|
216
216
|
chatWebProvider: this.chatWebProvider,
|
|
217
217
|
providerConversationId: this.providerConversationId,
|
|
218
218
|
providerUrl: this.providerConversationUrl(),
|
|
@@ -355,6 +355,49 @@ export class ChatWebSession extends EventEmitter {
|
|
|
355
355
|
this.trace(`session ready provider=${this.chatWebProvider} id=${this.sessionId}`);
|
|
356
356
|
this.emit("session", this.getSessionInfo());
|
|
357
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Native clear: start a new conversation in the SAME browser and profile
|
|
360
|
+
* (chat-web clicks the provider's "new chat" control, falling back to
|
|
361
|
+
* navigating home — both land on an empty chat), so no Chromium relaunch and
|
|
362
|
+
* no re-login. The old conversation stays in the provider's account.
|
|
363
|
+
*
|
|
364
|
+
* The new `/c/{uuid}` only exists after the next reply, so we drop back to a
|
|
365
|
+
* synthetic id until then — `ensureSessionInfo()` reports `sessionIdDeferred`
|
|
366
|
+
* for exactly this window.
|
|
367
|
+
*/
|
|
368
|
+
async runClear() {
|
|
369
|
+
if (this.closeRequested) {
|
|
370
|
+
throw this.createSessionClosedError();
|
|
371
|
+
}
|
|
372
|
+
if (this.currentTurn) {
|
|
373
|
+
const error = new Error("chat-web turn already in progress");
|
|
374
|
+
error.reason = "turn_already_running";
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
// Nothing has been said yet — fire boots the browser at announce time, so
|
|
378
|
+
// "booted" alone does not mean there is a conversation to walk away from.
|
|
379
|
+
if (!this.providerConversationId && this.history.length === 0) {
|
|
380
|
+
return { clear: { status: "noop" }, usage: null, metadata: {} };
|
|
381
|
+
}
|
|
382
|
+
await this.boot();
|
|
383
|
+
if (typeof this.chatSession?.newChat !== "function") {
|
|
384
|
+
throw new Error(`chat-web provider "${this.chatWebProvider}" cannot start a new chat`);
|
|
385
|
+
}
|
|
386
|
+
await this.chatSession.newChat();
|
|
387
|
+
this.providerConversationId = undefined;
|
|
388
|
+
this.sessionId = `chat-web-${this.chatWebProvider}-${Date.now().toString(36)}`;
|
|
389
|
+
this.sessionInfo = {
|
|
390
|
+
...(this.sessionInfo || {}),
|
|
391
|
+
backend: this.backend,
|
|
392
|
+
sessionId: this.sessionId,
|
|
393
|
+
providerConversationId: undefined,
|
|
394
|
+
providerUrl: undefined,
|
|
395
|
+
};
|
|
396
|
+
this.history = [];
|
|
397
|
+
this.emit("session", this.getSessionInfo());
|
|
398
|
+
// No id to report: the provider mints one on the next reply.
|
|
399
|
+
return { clear: { status: "cleared" }, usage: null, metadata: {} };
|
|
400
|
+
}
|
|
358
401
|
/**
|
|
359
402
|
* Adopt the provider-side conversation id (ChatGPT /c/{uuid} or
|
|
360
403
|
* equivalent) as our canonical sessionId, update sessionInfo, and emit
|
|
@@ -10,6 +10,7 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
10
10
|
static capabilities: Readonly<{
|
|
11
11
|
goal: true;
|
|
12
12
|
compact: true;
|
|
13
|
+
clear: true;
|
|
13
14
|
media: Readonly<{
|
|
14
15
|
image: "native";
|
|
15
16
|
}>;
|
|
@@ -18,6 +19,7 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
18
19
|
getCapabilities(): {
|
|
19
20
|
goal: true;
|
|
20
21
|
compact: true;
|
|
22
|
+
clear: true;
|
|
21
23
|
media: Readonly<{
|
|
22
24
|
image: "native";
|
|
23
25
|
}>;
|
|
@@ -47,6 +49,7 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
47
49
|
suppressReply: boolean;
|
|
48
50
|
compactMetadata: null;
|
|
49
51
|
compactError: string;
|
|
52
|
+
conversationReset: string;
|
|
50
53
|
emittedAssistantMessage: boolean;
|
|
51
54
|
fullText: string;
|
|
52
55
|
items: never[];
|
|
@@ -95,6 +98,7 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
95
98
|
capabilities: {
|
|
96
99
|
goal: true;
|
|
97
100
|
compact: true;
|
|
101
|
+
clear: true;
|
|
98
102
|
media: Readonly<{
|
|
99
103
|
image: "native";
|
|
100
104
|
}>;
|
|
@@ -175,6 +179,7 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
175
179
|
metadata?: undefined;
|
|
176
180
|
compactMetadata?: undefined;
|
|
177
181
|
compactError?: undefined;
|
|
182
|
+
conversationReset?: undefined;
|
|
178
183
|
} | {
|
|
179
184
|
text: any;
|
|
180
185
|
usage: any;
|
|
@@ -190,6 +195,7 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
190
195
|
};
|
|
191
196
|
compactMetadata: undefined;
|
|
192
197
|
compactError: string | undefined;
|
|
198
|
+
conversationReset: string | undefined;
|
|
193
199
|
}>;
|
|
194
200
|
/**
|
|
195
201
|
* Trigger Claude's native `/goal` slash command. Implemented by sending the
|
|
@@ -228,6 +234,34 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
228
234
|
runCompact(request?: import("../shared.js").CompactRequest, { onProgress }?: {
|
|
229
235
|
onProgress?: Function;
|
|
230
236
|
}): Promise<import("../shared.js").CompactResult>;
|
|
237
|
+
/**
|
|
238
|
+
* Native `/clear`: the CLI handles the slash command itself and continues on
|
|
239
|
+
* a new session id, so the process stays up and only the context is dropped.
|
|
240
|
+
* A session id that did NOT move means the command was not handled natively.
|
|
241
|
+
*/
|
|
242
|
+
runClear(request?: {}, { onProgress }?: {
|
|
243
|
+
onProgress?: null | undefined;
|
|
244
|
+
}): Promise<{
|
|
245
|
+
clear: {
|
|
246
|
+
status: string;
|
|
247
|
+
sessionId?: undefined;
|
|
248
|
+
};
|
|
249
|
+
usage: null;
|
|
250
|
+
metadata: {};
|
|
251
|
+
} | {
|
|
252
|
+
clear: {
|
|
253
|
+
status: string;
|
|
254
|
+
sessionId: any;
|
|
255
|
+
};
|
|
256
|
+
usage: any;
|
|
257
|
+
metadata: {
|
|
258
|
+
source: string;
|
|
259
|
+
sessionId: any;
|
|
260
|
+
totalCostUsd: number | undefined;
|
|
261
|
+
modelUsage: any;
|
|
262
|
+
structuredOutput: any;
|
|
263
|
+
} | undefined;
|
|
264
|
+
}>;
|
|
231
265
|
close(): Promise<void>;
|
|
232
266
|
}
|
|
233
267
|
import { EventEmitter } from "node:events";
|
|
@@ -234,6 +234,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
234
234
|
static capabilities = Object.freeze({
|
|
235
235
|
goal: true,
|
|
236
236
|
compact: true,
|
|
237
|
+
clear: true,
|
|
237
238
|
media: PROVIDER_MEDIA_CAPABILITIES[CLAUDE_PROVIDER_VARIANT],
|
|
238
239
|
});
|
|
239
240
|
getCapabilities() {
|
|
@@ -835,6 +836,12 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
835
836
|
}
|
|
836
837
|
}
|
|
837
838
|
return;
|
|
839
|
+
case "conversation_reset":
|
|
840
|
+
// Emitted by /clear (and other fresh-session flows): the CLI mounted a
|
|
841
|
+
// new transcript, so this is the authoritative "context cleared" signal.
|
|
842
|
+
currentTurn.conversationReset = normalizeText(message.new_conversation_id) || "";
|
|
843
|
+
this.updateSessionInfo(currentTurn.conversationReset);
|
|
844
|
+
return;
|
|
838
845
|
case "rate_limit_event":
|
|
839
846
|
this.updateSessionInfo(message.session_id || message.sessionId);
|
|
840
847
|
this.rateLimitInfo =
|
|
@@ -911,6 +918,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
911
918
|
suppressReply,
|
|
912
919
|
compactMetadata: null,
|
|
913
920
|
compactError: "",
|
|
921
|
+
conversationReset: "",
|
|
914
922
|
emittedAssistantMessage: false,
|
|
915
923
|
fullText: "",
|
|
916
924
|
items: [],
|
|
@@ -1014,6 +1022,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
1014
1022
|
},
|
|
1015
1023
|
compactMetadata: currentTurn.compactMetadata || undefined,
|
|
1016
1024
|
compactError: currentTurn.compactError || undefined,
|
|
1025
|
+
conversationReset: currentTurn.conversationReset || undefined,
|
|
1017
1026
|
};
|
|
1018
1027
|
}
|
|
1019
1028
|
catch (error) {
|
|
@@ -1202,6 +1211,30 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
1202
1211
|
metadata: turnResult.metadata,
|
|
1203
1212
|
};
|
|
1204
1213
|
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Native `/clear`: the CLI handles the slash command itself and continues on
|
|
1216
|
+
* a new session id, so the process stays up and only the context is dropped.
|
|
1217
|
+
* A session id that did NOT move means the command was not handled natively.
|
|
1218
|
+
*/
|
|
1219
|
+
async runClear(request = {}, { onProgress = null } = {}) {
|
|
1220
|
+
if (!this.sessionId || this.pendingHistorySeed) {
|
|
1221
|
+
return { clear: { status: "noop" }, usage: null, metadata: {} };
|
|
1222
|
+
}
|
|
1223
|
+
const previousSessionId = this.sessionId;
|
|
1224
|
+
const turnResult = await this.runTurn("/clear", { onProgress, suppressReply: true });
|
|
1225
|
+
const cleared = Boolean(turnResult.conversationReset || (this.sessionId && this.sessionId !== previousSessionId));
|
|
1226
|
+
if (cleared) {
|
|
1227
|
+
this.history = [];
|
|
1228
|
+
}
|
|
1229
|
+
return {
|
|
1230
|
+
clear: {
|
|
1231
|
+
status: cleared ? "cleared" : "noop",
|
|
1232
|
+
sessionId: cleared ? this.sessionId : undefined,
|
|
1233
|
+
},
|
|
1234
|
+
usage: turnResult.usage,
|
|
1235
|
+
metadata: turnResult.metadata,
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1205
1238
|
async close() {
|
|
1206
1239
|
if (this.closed) {
|
|
1207
1240
|
return;
|
|
@@ -13,6 +13,7 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
13
13
|
backend: string;
|
|
14
14
|
options: {};
|
|
15
15
|
logger: any;
|
|
16
|
+
traceReplyLatency: boolean;
|
|
16
17
|
cwd: any;
|
|
17
18
|
resumeSessionId: any;
|
|
18
19
|
sessionId: any;
|
|
@@ -31,13 +32,26 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
31
32
|
nativeSessionId: string;
|
|
32
33
|
rateLimits: any;
|
|
33
34
|
tokenUsage: any;
|
|
34
|
-
turnTokenBaseline:
|
|
35
|
+
turnTokenBaseline: {
|
|
36
|
+
totalTokens: number;
|
|
37
|
+
inputTokens: number;
|
|
38
|
+
cachedInputTokens: number;
|
|
39
|
+
} | {
|
|
40
|
+
totalTokens: number;
|
|
41
|
+
inputTokens?: undefined;
|
|
42
|
+
cachedInputTokens?: undefined;
|
|
43
|
+
} | {
|
|
44
|
+
inputTokens?: number | undefined;
|
|
45
|
+
cachedInputTokens?: number | undefined;
|
|
46
|
+
totalTokens: number;
|
|
47
|
+
} | null;
|
|
35
48
|
turnTokensSeen: boolean;
|
|
36
49
|
currentTurnStatus: any;
|
|
37
50
|
currentTurnActivityAt: number;
|
|
38
51
|
now: any;
|
|
39
52
|
turnDeadlineMs: any;
|
|
40
53
|
currentTurn: {
|
|
54
|
+
startedAt: number;
|
|
41
55
|
turnId: string;
|
|
42
56
|
fullText: string;
|
|
43
57
|
activeAssistantMessageId: string;
|
|
@@ -132,6 +146,13 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
132
146
|
markTurnStartedStatus(): void;
|
|
133
147
|
failPendingTurnStart(error: any): Promise<void>;
|
|
134
148
|
boot(): Promise<void>;
|
|
149
|
+
buildThreadParams(): {
|
|
150
|
+
cwd: any;
|
|
151
|
+
approvalPolicy: string;
|
|
152
|
+
sandbox: string;
|
|
153
|
+
personality: string;
|
|
154
|
+
ephemeral: boolean;
|
|
155
|
+
};
|
|
135
156
|
bootInternal(): Promise<void>;
|
|
136
157
|
applyThreadInfo(payload: any, { resumeReady }?: {
|
|
137
158
|
resumeReady?: boolean | undefined;
|
|
@@ -140,10 +161,11 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
140
161
|
/** Start counting a turn's tokens from the thread total known right now. */
|
|
141
162
|
beginTurnTokenAccounting(): void;
|
|
142
163
|
/**
|
|
143
|
-
* Thread token usage plus `turnTotalTokens
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* did has
|
|
164
|
+
* Thread token usage plus `turnTotalTokens` (and its `turnInputTokens` /
|
|
165
|
+
* `turnCachedInputTokens` split): tokens spent since the current turn (or
|
|
166
|
+
* goal) started. Codex only reports thread-cumulative totals, and only when
|
|
167
|
+
* a response completes, so a turn that ended before any response did has
|
|
168
|
+
* unknown usage.
|
|
147
169
|
*/
|
|
148
170
|
snapshotTokenUsage(): any;
|
|
149
171
|
resolveContextUsagePercent(): number | undefined;
|
|
@@ -188,6 +210,7 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
188
210
|
}): Promise<void>;
|
|
189
211
|
emitAssistantMessage(text: any): Promise<void>;
|
|
190
212
|
finalizeActiveAssistantMessage(currentTurn?: {
|
|
213
|
+
startedAt: number;
|
|
191
214
|
turnId: string;
|
|
192
215
|
fullText: string;
|
|
193
216
|
activeAssistantMessageId: string;
|
|
@@ -228,6 +251,7 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
228
251
|
useInitialImages?: boolean | undefined;
|
|
229
252
|
}): string;
|
|
230
253
|
getCurrentTurn(): {
|
|
254
|
+
startedAt: number;
|
|
231
255
|
turnId: string;
|
|
232
256
|
fullText: string;
|
|
233
257
|
activeAssistantMessageId: string;
|
|
@@ -252,12 +276,21 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
252
276
|
goalMode: boolean;
|
|
253
277
|
} | null;
|
|
254
278
|
ensureCurrentTurn(params: any): {
|
|
279
|
+
startedAt: number;
|
|
255
280
|
turnId: string;
|
|
256
281
|
fullText: string;
|
|
257
282
|
activeAssistantMessageId: string;
|
|
258
283
|
activeAssistantMessageText: string;
|
|
259
284
|
resolve: null;
|
|
260
285
|
reject: null;
|
|
286
|
+
} | {
|
|
287
|
+
turnId: string;
|
|
288
|
+
fullText: string;
|
|
289
|
+
activeAssistantMessageId: string;
|
|
290
|
+
activeAssistantMessageText: string;
|
|
291
|
+
resolve: null;
|
|
292
|
+
reject: null;
|
|
293
|
+
suppressReply: boolean;
|
|
261
294
|
} | {
|
|
262
295
|
turnId: string;
|
|
263
296
|
fullText: string;
|
|
@@ -287,7 +320,44 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
287
320
|
* @param {import("../shared.js").CompactRequest} [request]
|
|
288
321
|
* @returns {Promise<import("../shared.js").CompactResult>}
|
|
289
322
|
*/
|
|
290
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Native clear: start a fresh thread on the SAME app-server connection. The
|
|
325
|
+
* process, transport and model config stay up; only the conversation is
|
|
326
|
+
* replaced, so the next turn runs against an empty thread.
|
|
327
|
+
*/
|
|
328
|
+
runClear(): Promise<{
|
|
329
|
+
clear: {
|
|
330
|
+
status: string;
|
|
331
|
+
sessionId?: undefined;
|
|
332
|
+
};
|
|
333
|
+
usage: null;
|
|
334
|
+
metadata: {};
|
|
335
|
+
} | {
|
|
336
|
+
clear: {
|
|
337
|
+
status: string;
|
|
338
|
+
sessionId: any;
|
|
339
|
+
};
|
|
340
|
+
usage: null;
|
|
341
|
+
metadata: {};
|
|
342
|
+
}>;
|
|
343
|
+
runCompact(request?: {}): Promise<{
|
|
344
|
+
compact: {
|
|
345
|
+
status: string;
|
|
346
|
+
instructionsApplied: boolean;
|
|
347
|
+
};
|
|
348
|
+
usage: null;
|
|
349
|
+
metadata: {};
|
|
350
|
+
} | {
|
|
351
|
+
compact: {
|
|
352
|
+
status: string;
|
|
353
|
+
instructionsApplied: boolean;
|
|
354
|
+
};
|
|
355
|
+
usage: any;
|
|
356
|
+
metadata: {
|
|
357
|
+
source: string;
|
|
358
|
+
threadId: any;
|
|
359
|
+
};
|
|
360
|
+
}>;
|
|
291
361
|
/**
|
|
292
362
|
* Normalize a goal payload from a codex `thread/goal/*` notification into
|
|
293
363
|
* the cross-layer {@link GoalState} shape.
|